@@ -14,21 +14,53 @@
end -placeholder = " 结束日期 "
/ >
< / el-form-item >
< el-form-item label = "销方(开票方税号) " >
< el-input
< el-form-item label = "开票单位 " >
< el-input v-model = "query.salerName" clearable @keyup.enter="handleQuery" / >
< / el-form-item >
< el-form-item label = "受票单位" >
< el-input v-model = "query.buyerName" clearable @keyup.enter="handleQuery" / >
< / el-form-item >
< el-form-item label = "开票单位(销方)" >
< el-select
v-model = "query.salerTaxNo"
clearable
style = "width: 180px"
filterable
remote
reserve -keyword
: remote -method = " keyword = > searchCustomers ( keyword , 'saler' ) "
:loading=" customerLoading . saler "
placeholder=" 输入名称搜索或直接粘贴税号 "
style=" width : 220 px "
@keyup.enter=" handleQuery "
/ >
>
<el-option
v-for=" item in customerOptions . saler "
:key=" item . value "
:label=" item . label "
:value=" item . value "
/>
</el-select>
</el-form-item>
< el-form-item label = "购方(收票方税号)" >
< el-input
<el-form-item label=" 受票单位 ( 购方 ) ">
<el-select
v-model=" query . buyerTaxNo "
clearable
style = "width: 180px"
filterable
remote
reserve-keyword
:remote-method=" keyword => searchCustomers ( keyword , 'buyer' ) "
:loading=" customerLoading . buyer "
placeholder=" 输入名称搜索或直接粘贴税号 "
style=" width : 220 px "
@keyup.enter=" handleQuery "
/ >
>
<el-option
v-for=" item in customerOptions . buyer "
:key=" item . value "
:label=" item . label "
:value=" item . value "
/>
</el-select>
</el-form-item>
<el-form-item label=" 发票类型 ">
<el-select
@@ -46,6 +78,18 @@
/>
</el-select>
</el-form-item>
<el-form-item label=" 认领状态 ">
<el-select v-model=" query . claimed " clearable placeholder=" 不限 " style=" width : 120 px ">
<el-option label=" 未认领 " :value=" false " />
<el-option label=" 已认领 " :value=" true " />
</el-select>
</el-form-item>
<el-form-item label=" 登记状态 ">
<el-select v-model=" query . registered " clearable placeholder=" 不限 " style=" width : 120 px ">
<el-option label=" 未登记 " :value=" false " />
<el-option label=" 已登记 " :value=" true " />
</el-select>
</el-form-item>
<el-form-item>
<el-button :disabled=" loading " @click=" resetQuery ">重置</el-button>
<el-button type=" primary " :disabled=" loading " @click=" handleQuery ">查询</el-button>
@@ -91,7 +135,7 @@
<el-table-column label=" 剩余可用金额 " min-width=" 130 " align=" right ">
<template #default=" { row } ">{{ formatMoney(row.availableTaxAmount) }}</template>
</el-table-column>
<el-table-column prop=" kingdeeBillNo " label=" 金蝶单据号 " min-width=" 210 " show-overflow-tooltip />
<!--< el-table-column prop=" kingdeeBillNo " label=" 金蝶单据号 " min-width=" 210 " show-overflow-tooltip />-->
<el-table-column label=" 认领状态 " min-width=" 100 " align=" center ">
<template #default=" { row } ">
<el-tag v-if=" row . claimed " type=" danger " size=" small ">已认领</el-tag>
@@ -129,12 +173,17 @@
</el-table-column>
</el-table>
<div class=" kingdee - pool - dialog _ _pager ">
<span class=" kingdee - pool - dialog _ _pager - info ">第 {{ page.current }} 页 </span>
<el-button :disabled=" page . current <= 1 || loading " @click=" prevPage ">上一页</el-butt on>
<el-button :disabled=" ! page . hasMore || loading " @click=" nextPage ">下一页</el-button>
<el-select v-model=" page . size " style=" width : 110 px " @change=" handleSizeChange ">
<el-option v-for=" n in [ 10 , 20 , 50 , 100 ] " :key=" n " :label=" ` ${ n } 条/页 ` " :value=" n " />
</el-select>
<span class=" kingdee - pool - dialog _ _pager - tip ">按名称检索范围为已同步票,未找到请改用发票号码或税号精确查找 </span>
<el-paginati on
v-model:current-page=" page . current "
v-model:page-size=" page . size "
:total=" page . total "
:page-sizes=" [ 10 , 20 , 50 , 100 ] "
layout=" total , sizes , prev , pager , next , jumper "
:disabled=" loading "
@current-change=" load "
@size-change=" handleSizeChange "
/>
</div>
<template #footer>
<el-button @click=" visible = false ">取消</el-button>
@@ -144,6 +193,8 @@
</template>
<script>
import request from '@/axios';
import { getList as getCustomerList } from '@/api/vehicle/customer-archive';
import {
downloadKingdeeInvoiceFile,
isKingdeePathAttachment,
@@ -164,7 +215,7 @@ export default {
data() {
return {
loading: false,
// 与后端 BfiConstant.INVOICE_TYPE_NAMES 对齐:value 为发票类型编码, label 为中文名
// 与后端 BfiConstant.INVOICE_TYPE_NAMES 对齐:value 为发票类型编码(镜像按 invoice_type_code 精确匹配)
// 选票场景只开放数电票(普票/专票)
invoiceTypeOptions: [
{ value: '26', label: '数电发票(普通发票)' },
@@ -173,15 +224,12 @@ export default {
rows: [],
selected: [],
current: null,
page: { current: 1, size: 10, hasMore: false },
// 续扫游标(服务端过滤补页后翻页须按游标对齐,防漏行/重行)
nextCursor: null,
// 下一页请求待回传的游标:nextPage 时暂存,load 成功或失败后清空
pendingCursor: null,
// 已浏览页缓存:上一页直接回显不再请求(0 远程调用)
pageCache: {},
page: { current: 1, size: 10, total: 0 },
// 正在查看/下载附件的发票号(行级 loading 守卫)
attachmentBusy: null,
// 销/购方客商下拉:远程按名称搜客商档案,选中带出税号查询;手动输入的裸税号直接作为值过滤
customerOptions: { saler: [], buyer: [] },
customerLoading: { saler: false, buyer: false },
query: this.emptyQuery(),
};
},
@@ -201,14 +249,18 @@ export default {
invoiceNo: '',
// 默认查近一个月,随请求传后端并在页面回显
invoiceDateRange: [this.$dayjs().subtract(1, 'month').format('YYYY-MM-DD'), this.$dayjs().format('YYYY-MM-DD')],
salerName: '',
buyerName: '',
salerTaxNo: '',
buyerTaxNo: '',
invoiceType: '',
claimed: null,
registered: null,
};
},
// 远程实时查询的票未入本地镜像,id 恒空 ,用发票号码兜底作为行主键
// 镜像行主键为 id,回源入库前的响应仍可能无 id ,用发票号码兜底
rowKey(row) {
return row.invoiceNo || row.id ;
return row.id || row.invoiceNo ;
},
// 票池行附件字段为复数 attachmentsJson,行内首项即发票文件({source,path,name})
rowAttachment(row = {}) {
@@ -261,46 +313,52 @@ export default {
this.selected = [];
this.$refs.poolTable?.clearSelection();
this.page.current = 1;
this.resetScanState();
this.load();
},
handleQuery() {
this.page.current = 1;
this.resetScanState();
this.load();
},
handleSizeChange() {
this.page.current = 1;
this.resetScanState();
this.load();
},
prevPage() {
// 已浏览页直接回显缓存,不再请求
const cached = this.pageCache[this.page.current - 1];
if (cached) {
this.page.current -= 1;
this.rows = cached.rows;
this.page.hasMore = cached.hasMore;
this.nextCursor = cached.nextCursor;
return;
}
this.page.current -= 1;
this.load();
},
nextPage() {
this.pendingCursor = this.nextCursor;
this.page.current += 1;
this.load();
},
resetQuery() {
this.query = this.emptyQuery();
this.handleQuery();
},
// 新查询/换页容量/重新打开时清空游标与已浏览页缓存,重新锚定扫描
resetScanState() {
this.nextCursor = null;
this.pendingCursor = null ;
this.pageCache = {} ;
// 客商名称远程搜索:含 18 位社会信用代码特征(数字+大写字母)时按裸税号直查;
// 否则搜客商档案,取客商发票信息(blade_customer_invoice_info)的抬头/税号作选项——票池税号是开票税号,非统一信用代码
async searchCustomers(keyword, side) {
const text = String(keyword || '').trim() ;
if (!text) return ;
if (/^[0-9A-Z]{15,20}$/.test(text)) {
this.customerOptions[side] = [{ label: `${text}(手输税号)`, value: text }];
return;
}
this.customerLoading[side] = true;
try {
const data = this.unwrap(await getCustomerList(1, 20, { fullName: text }));
const customers = (data.records || []).filter(item => item.id);
if (!customers.length) {
this.customerOptions[side] = [];
return;
}
// 列表接口不带发票信息,逐户取 detail( detail 组装 invoices,每户一张发票信息)
const options = await Promise.all(
customers.map(async customer => {
const detail = this.unwrap(await request({ url: '/blade-transport/customer-archive/detail', method: 'get', params: { id: customer.id } }));
return (detail?.invoices || [])
.filter(invoice => invoice.taxNo)
.map(invoice => ({ label: `${invoice.invoiceTitle || customer.fullName}( ${invoice.taxNo}) `, value: invoice.taxNo }));
})
);
this.customerOptions[side] = options.flat().filter(
(option, index, list) => list.findIndex(item => item.value === option.value) === index
);
} finally {
this.customerLoading[side] = false;
}
},
unwrap(response) {
const body = response?.data || response || {};
@@ -308,34 +366,26 @@ export default {
},
async load() {
this.loading = true;
// 请求前快照页码:prev/next 会先改写 current,远程失败时回滚避免停留在错页
const lastRequested = this.page.current;
const cursor = this.pendingCursor;
this.pendingCursor = null;
try {
const range = this.query.invoiceDateRange || [];
const data = this.unwrap(
await this.queryApi({
current: this.page.current,
size: this.page.size,
cursor: cursor || undefined,
invoiceNo: this.query.invoiceNo || undefined,
invoiceDateStart: range[0] || undefined,
invoiceDateEnd: range[1] || undefined,
salerName: this.query.salerName || undefined,
buyerName: this.query.buyerName || undefined,
salerTaxNo: this.query.salerTaxNo || undefined,
buyerTaxNo: this.query.buyerTaxNo || undefined,
invoiceType: this.query.invoiceType || undefined,
claimed: this.query.claimed ?? undefined,
registered: this.query.registered ?? undefined,
})
);
this.rows = data.records || [];
this.page.hasMore = !!data.hasMore ;
this.nextCursor = data.nextCursor || null;
this.pageCache[this.page.current] = { rows: this.rows, hasMore: this.page.hasMore, nextCursor: this.nextCursor };
} catch (error) {
if (this.page.current !== lastRequested) {
this.page.current = lastRequested;
}
throw error;
this.page.total = Number(data.total || 0) ;
} finally {
this.loading = false;
}
@@ -373,8 +423,12 @@ export default {
. kingdee - pool - dialog _ _pager {
display : flex ;
align - items : center ;
gap : 12 px ;
justify - content : flex - end ;
justify - content : space - between ;
margin - top : 12 px ;
gap : 16 px ;
}
. kingdee - pool - dialog _ _pager - tip {
color : # 909399 ;
font - size : 12 px ;
}
< / style >