feat: 发票管理

This commit is contained in:
2026-07-27 11:34:31 +08:00
parent 2d8a31a3cc
commit 5bbfba353d
10 changed files with 1081 additions and 0 deletions
@@ -0,0 +1,509 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="结算单号">
<el-input clearable placeholder="请输入结算单号" v-model="pageForm.settlementId"></el-input>
</search-item>
<search-item label="开票结果">
<el-select clearable placeholder="请选择开票结果" style="width: 100%" v-model="pageForm.bSuccess">
<el-option label="成功" :value="0"></el-option>
<el-option label="失败" :value="1"></el-option>
</el-select>
</search-item>
<search-item label="成功订单">
<el-input clearable placeholder="请输入订单ID" v-model="pageForm.sucOrderIds"></el-input>
</search-item>
<search-item label="失败订单">
<el-input clearable placeholder="请输入订单ID" v-model="pageForm.failOrderIds"></el-input>
</search-item>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<table-tool label="发票管理">
<el-button @click="openApplyDialog" size="mini" type="success">开票申请</el-button>
<el-button @click="openOrderDialog" size="mini" type="primary">可开票订单</el-button>
</table-tool>
<el-table :data="tableData" border class="vi-table" size="small" style="width: 100%" v-loading="tableLoading">
<el-table-column :index="indexMethod" align="center" label="序号" type="index" width="70"></el-table-column>
<el-table-column align="center" label="结算单号" min-width="180" prop="settlementId" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="开票结果" min-width="110">
<template slot-scope="{row}">
<el-tag :type="invoiceResultType(row.bSuccess)" size="mini">{{ invoiceResultName(row.bSuccess) }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="成功订单" min-width="220" prop="sucOrderIds" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="失败订单" min-width="220" prop="failOrderIds" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="失败原因" min-width="220" prop="failMsg" show-overflow-tooltip></el-table-column>
<el-table-column align="center" fixed="right" label="操作" width="220">
<template slot-scope="{row}">
<el-button @click="openInfoDialog(row)" size="mini" type="primary">发票明细</el-button>
<el-button @click="openOrderDialog(row.sucOrderIds)" size="mini">成功订单</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
:current-page="pageForm.pageNumber"
:page-size="pageForm.pageSize"
:page-sizes="[10, 20, 30, 50]"
:total="pageForm.totalCount"
@current-change="pageChange"
@size-change="sizeChange"
layout="total, sizes, prev, pager, next"
style="margin-top: 16px; text-align: right"
></el-pagination>
</el-card>
<el-dialog :close-on-click-modal="false" title="开票申请" :visible.sync="applyDialogVisible" top="5vh" width="720px">
<el-form :model="applyForm" label-width="120px" size="small">
<el-form-item label="供应商" required>
<el-select clearable filterable placeholder="请选择供应商" style="width: 100%" v-model="applyForm.supplierId">
<el-option :key="item.supplierId" :label="item.supplierName" :value="item.supplierId" v-for="item in supplierList"></el-option>
</el-select>
</el-form-item>
<el-form-item label="订单完成日期" required>
<el-date-picker
end-placeholder="结束日期"
range-separator="至"
start-placeholder="开始日期"
style="width: 100%"
type="daterange"
value-format="yyyy-MM-dd"
v-model="applyDateRange"
></el-date-picker>
</el-form-item>
<el-form-item label="订单ID" required>
<el-input placeholder="多个订单ID用英文逗号分隔" type="textarea" v-model="applyForm.orderIds"></el-input>
</el-form-item>
<el-form-item label="开票日期" required>
<el-date-picker placeholder="请选择开票日期" style="width: 100%" type="date" value-format="yyyy-MM-dd" v-model="applyForm.invoiceDate"></el-date-picker>
</el-form-item>
<el-form-item label="发票抬头">
<el-input clearable placeholder="请输入发票抬头" v-model="applyForm.invoiceTitle"></el-input>
</el-form-item>
<el-form-item label="纳税人识别号" required>
<el-input clearable placeholder="请输入纳税人识别号" v-model="applyForm.invoiceCode"></el-input>
</el-form-item>
<el-form-item label="发票地址" required>
<el-input clearable placeholder="请输入发票地址" v-model="applyForm.invoiceAddress"></el-input>
</el-form-item>
<el-form-item label="联系电话" required>
<el-input clearable placeholder="请输入联系电话" v-model="applyForm.invoiceContact"></el-input>
</el-form-item>
<el-form-item label="发票类型">
<el-select clearable placeholder="请选择发票类型" style="width: 100%" v-model="applyForm.invoiceType">
<el-option label="全电专票" :value="9"></el-option>
<el-option label="全电普票" :value="10"></el-option>
</el-select>
</el-form-item>
<el-form-item label="开票内容">
<el-input clearable placeholder="请输入开票内容" v-model="applyForm.invoiceContent"></el-input>
</el-form-item>
<el-form-item label="开户银行">
<el-input clearable placeholder="请输入开户银行" v-model="applyForm.bankName"></el-input>
</el-form-item>
<el-form-item label="银行账号">
<el-input clearable placeholder="请输入银行账号" v-model="applyForm.bankAccount"></el-input>
</el-form-item>
<el-form-item label="备注">
<el-input placeholder="请输入备注" type="textarea" v-model="applyForm.remark"></el-input>
</el-form-item>
<el-form-item label="订单积分合计">
<span>{{ orderTotalPoints }}</span>
<el-button @click="loadOrderTotalPoints" size="mini" style="margin-left: 12px">计算</el-button>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="applyDialogVisible = false">取消</el-button>
<el-button @click="submitApply" :loading="applyLoading" type="primary">提交申请</el-button>
</span>
</el-dialog>
<el-dialog :close-on-click-modal="false" title="可开票订单" :visible.sync="orderDialogVisible" top="5vh" width="90%">
<el-form :inline="true" size="small" @submit.native.prevent>
<el-form-item label="供应商">
<el-select clearable filterable placeholder="供应商" style="width: 180px" v-model="orderForm.supplierId">
<el-option :key="item.supplierId" :label="item.supplierName" :value="item.supplierId" v-for="item in supplierList"></el-option>
</el-select>
</el-form-item>
<el-form-item label="订单ID">
<el-input clearable placeholder="多个订单ID用英文逗号分隔" v-model="orderForm.orderId"></el-input>
</el-form-item>
<el-form-item label="完成日期">
<el-date-picker
end-placeholder="结束日期"
range-separator="至"
start-placeholder="开始日期"
style="width: 260px"
type="daterange"
value-format="yyyy-MM-dd"
v-model="orderDateRange"
></el-date-picker>
</el-form-item>
<el-form-item label="开票状态">
<el-select clearable placeholder="开票状态" style="width: 130px" v-model="orderForm.invoiceStatus">
<el-option label="未开票" :value="0"></el-option>
<el-option label="已开票" :value="1"></el-option>
<el-option label="开票中" :value="2"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="orderSearch" type="primary">查询</el-button>
<el-button @click="orderReset">重置</el-button>
<el-button @click="useOrdersForApply" type="success">用于开票</el-button>
</el-form-item>
</el-form>
<el-table :data="orderList" @selection-change="orderSelectionChange" border size="small" style="width: 100%" v-loading="orderLoading">
<el-table-column align="center" type="selection" width="50"></el-table-column>
<el-table-column :index="orderIndexMethod" align="center" label="序号" type="index" width="70"></el-table-column>
<el-table-column align="center" label="订单ID" min-width="170" prop="orderId" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="供应商" min-width="150" prop="supplierName" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="用户ID" min-width="120" prop="userId" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="完成时间" min-width="160" prop="orderCompleteTime"></el-table-column>
<el-table-column align="center" label="订单总金额" min-width="110" prop="totalPrice"></el-table-column>
<el-table-column align="center" label="积分金额" min-width="110" prop="pointsPrice"></el-table-column>
<el-table-column align="center" label="退款积分" min-width="110" prop="refund"></el-table-column>
<el-table-column align="center" label="开票状态" min-width="100">
<template slot-scope="{row}">
<el-tag :type="invoiceStatusType(row.invoiceStatus)" size="mini">{{ invoiceStatusName(row.invoiceStatus) }}</el-tag>
</template>
</el-table-column>
</el-table>
<el-pagination
:current-page="orderForm.pageNumber"
:page-size="orderForm.pageSize"
:page-sizes="[10, 20, 30, 50]"
:total="orderForm.totalCount"
@current-change="orderPageChange"
@size-change="orderSizeChange"
layout="total, sizes, prev, pager, next"
style="margin-top: 16px; text-align: right"
></el-pagination>
<span slot="footer">
<el-button @click="orderDialogVisible = false">关闭</el-button>
</span>
</el-dialog>
<el-dialog :close-on-click-modal="false" title="发票明细" :visible.sync="infoDialogVisible" top="5vh" width="90%">
<el-form :inline="true" size="small" @submit.native.prevent>
<el-form-item label="发票号码">
<el-input clearable placeholder="发票号码" v-model="infoForm.invoiceId"></el-input>
</el-form-item>
<el-form-item label="发票代码">
<el-input clearable placeholder="发票代码" v-model="infoForm.invoiceCode"></el-input>
</el-form-item>
<el-form-item label="发票日期">
<el-date-picker clearable placeholder="发票日期" type="date" value-format="yyyy-MM-dd" v-model="infoForm.invoiceDate"></el-date-picker>
</el-form-item>
<el-form-item>
<el-button @click="infoSearch" type="primary">查询</el-button>
</el-form-item>
</el-form>
<el-table :data="infoList" border size="small" style="width: 100%" v-loading="infoLoading">
<el-table-column :index="infoIndexMethod" align="center" label="序号" type="index" width="70"></el-table-column>
<el-table-column align="center" label="发票号码" min-width="150" prop="invoiceId" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="发票代码" min-width="130" prop="invoiceCode" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="发票日期" min-width="120" prop="invoiceDate"></el-table-column>
<el-table-column align="center" label="发票类型" min-width="110">
<template slot-scope="{row}">{{ invoiceTypeName(row.invoiceType) }}</template>
</el-table-column>
<el-table-column align="center" label="价税合计" min-width="110" prop="invoiceAmount"></el-table-column>
<el-table-column align="center" label="税额" min-width="100" prop="invoiceTaxAmount"></el-table-column>
<el-table-column align="center" label="纳税人识别号" min-width="160" prop="taxIdNumber" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="电子发票地址" min-width="220" prop="url" show-overflow-tooltip></el-table-column>
</el-table>
<el-pagination
:current-page="infoForm.pageNumber"
:page-size="infoForm.pageSize"
:total="infoForm.totalCount"
@current-change="infoPageChange"
layout="total, prev, pager, next"
style="margin-top: 16px; text-align: right"
></el-pagination>
<span slot="footer">
<el-button @click="infoDialogVisible = false">关闭</el-button>
</span>
</el-dialog>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
return {
supplierList: [],
tableData: [],
tableLoading: false,
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
settlementId: null,
bSuccess: null,
sucOrderIds: null,
failOrderIds: null,
failMsg: null
},
applyDialogVisible: false,
applyLoading: false,
applyDateRange: [],
orderTotalPoints: 0,
applyForm: this.emptyApplyForm(),
orderDialogVisible: false,
orderLoading: false,
orderDateRange: [],
orderList: [],
orderSelections: [],
orderForm: this.emptyOrderForm(),
infoDialogVisible: false,
infoLoading: false,
infoList: [],
infoForm: this.emptyInfoForm()
}
},
methods: {
emptyApplyForm() {
return {
supplierId: null,
orderIds: null,
startDate: null,
endDate: null,
invoiceDate: null,
invoiceTitle: null,
invoiceCode: null,
invoiceAddress: null,
invoiceContact: null,
invoiceType: 10,
invoiceContent: null,
remark: null,
bankAccount: null,
registeredAddress: null,
bankName: null
}
},
emptyOrderForm() {
return {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
supplierId: null,
orderId: null,
startDate: null,
endDate: null,
invoiceStatus: 0
}
},
emptyInfoForm() {
return {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
mainId: null,
invoiceId: null,
invoiceCode: null,
invoiceDate: null,
invoiceType: null
}
},
indexMethod(index) {
return index + 1 + (this.pageForm.pageNumber - 1) * this.pageForm.pageSize
},
orderIndexMethod(index) {
return index + 1 + (this.orderForm.pageNumber - 1) * this.orderForm.pageSize
},
infoIndexMethod(index) {
return index + 1 + (this.infoForm.pageNumber - 1) * this.infoForm.pageSize
},
doSearch() {
this.pageForm.pageNumber = 1
this.pageData()
},
async pageData() {
this.tableLoading = true
const resp = await this.$axios.post(loc() + "/pageData", this.pageForm)
this.tableLoading = false
if (resp.code === 0) {
this.tableData = resp.data.list || []
this.pageForm.totalCount = resp.data.totalCount || 0
} else {
this.notifyWarning(resp.msg)
}
},
pageChange(val) {
this.pageForm.pageNumber = val
this.pageData()
},
sizeChange(val) {
this.pageForm.pageSize = val
this.pageForm.pageNumber = 1
this.pageData()
},
async loadSuppliers() {
const resp = await this.$axios.post(loc() + "/supplierList")
if (resp.code === 0) {
this.supplierList = resp.data || []
}
},
openApplyDialog() {
this.applyForm = this.emptyApplyForm()
this.applyDateRange = []
this.orderTotalPoints = 0
this.applyDialogVisible = true
},
syncApplyDateRange() {
this.applyForm.startDate = this.applyDateRange && this.applyDateRange.length ? this.applyDateRange[0] : null
this.applyForm.endDate = this.applyDateRange && this.applyDateRange.length ? this.applyDateRange[1] : null
},
async loadOrderTotalPoints() {
this.syncApplyDateRange()
if (!this.applyForm.supplierId || !this.applyForm.orderIds) {
this.notifyWarning("请先选择供应商并填写订单ID")
return
}
const resp = await this.$axios.post(loc() + "/orderTotalPoints", {
supplierId: this.applyForm.supplierId,
orderId: this.applyForm.orderIds,
startDate: this.applyForm.startDate,
endDate: this.applyForm.endDate,
invoiceStatus: 0
})
if (resp.code === 0) {
this.orderTotalPoints = resp.data || 0
} else {
this.notifyWarning(resp.msg)
}
},
async submitApply() {
this.syncApplyDateRange()
this.applyLoading = true
const resp = await this.$axios.post(loc() + "/applyInvoice", this.applyForm)
this.applyLoading = false
if (resp.code === 0) {
this.notifySuccess(resp.data || "开票申请成功")
this.applyDialogVisible = false
this.pageData()
} else {
this.notifyWarning(resp.msg)
}
},
openOrderDialog(orderIds) {
this.orderDialogVisible = true
this.orderForm = this.emptyOrderForm()
this.orderDateRange = []
if (typeof orderIds === "string" && orderIds) {
this.orderForm.orderId = orderIds
this.orderForm.invoiceStatus = null
}
this.orderPageData()
},
syncOrderDateRange() {
this.orderForm.startDate = this.orderDateRange && this.orderDateRange.length ? this.orderDateRange[0] : null
this.orderForm.endDate = this.orderDateRange && this.orderDateRange.length ? this.orderDateRange[1] : null
},
orderSearch() {
this.orderForm.pageNumber = 1
this.orderPageData()
},
orderReset() {
this.orderForm = this.emptyOrderForm()
this.orderDateRange = []
this.orderPageData()
},
async orderPageData() {
this.syncOrderDateRange()
this.orderLoading = true
const resp = await this.$axios.post(loc() + "/orderPage", this.orderForm)
this.orderLoading = false
if (resp.code === 0) {
this.orderList = resp.data.list || []
this.orderForm.totalCount = resp.data.totalCount || 0
} else {
this.notifyWarning(resp.msg)
}
},
orderPageChange(val) {
this.orderForm.pageNumber = val
this.orderPageData()
},
orderSizeChange(val) {
this.orderForm.pageSize = val
this.orderForm.pageNumber = 1
this.orderPageData()
},
orderSelectionChange(rows) {
this.orderSelections = rows || []
},
useOrdersForApply() {
if (!this.orderSelections.length) {
this.notifyWarning("请先选择订单")
return
}
const first = this.orderSelections[0]
this.applyForm = this.emptyApplyForm()
this.applyForm.supplierId = first.supplierId
this.applyForm.orderIds = this.orderSelections.map((item) => item.orderId).join(",")
this.applyForm.invoiceDate = new Date().toISOString().slice(0, 10)
this.applyDateRange = [this.orderForm.startDate, this.orderForm.endDate].filter(Boolean)
this.orderDialogVisible = false
this.applyDialogVisible = true
this.loadOrderTotalPoints()
},
openInfoDialog(row) {
this.infoForm = this.emptyInfoForm()
this.infoForm.mainId = row.id
this.infoDialogVisible = true
this.infoPageData()
},
infoSearch() {
this.infoForm.pageNumber = 1
this.infoPageData()
},
async infoPageData() {
this.infoLoading = true
const resp = await this.$axios.post(loc() + "/infoPage", this.infoForm)
this.infoLoading = false
if (resp.code === 0) {
this.infoList = resp.data.list || []
this.infoForm.totalCount = resp.data.totalCount || 0
} else {
this.notifyWarning(resp.msg)
}
},
infoPageChange(val) {
this.infoForm.pageNumber = val
this.infoPageData()
},
invoiceResultName(value) {
return Number(value) === 0 ? "成功" : "失败"
},
invoiceResultType(value) {
return Number(value) === 0 ? "success" : "danger"
},
invoiceStatusName(value) {
const map = { 0: "未开票", 1: "已开票", 2: "开票中" }
return map[value] || "-"
},
invoiceStatusType(value) {
const map = { 0: "info", 1: "success", 2: "warning" }
return map[value] || "info"
},
invoiceTypeName(value) {
const map = { 9: "全电专票", 10: "全电普票" }
return map[value] || "-"
}
},
created() {
this.loadSuppliers()
this.pageData()
}
})
</script>
<!--#
}
#-->