diff --git a/resources/assets/js/components/business/AccountBills.vue b/resources/assets/js/components/business/AccountBills.vue index 608d96c6..c86fa136 100644 --- a/resources/assets/js/components/business/AccountBills.vue +++ b/resources/assets/js/components/business/AccountBills.vue @@ -94,11 +94,11 @@ const dateConfig = {dateFormat:'Y-m-d',locale:Mandarin,disableMobile:true,allowI const emptyFilters = billType => ({store_name:'',ledger_remark:'',bill_item:'',start:'',end:'',keyword:'',keyword_match:'contains'}); export default { name:'AccountBills', components:{ TableColumnSettings, flatPicker, [Select.name]:Select, [Option.name]:Option }, props:{ apiBase:{type:String,required:true}, billType:{type:String,required:true}, progressBase:{type:String,default:''}, userKey:{type:[String,Number],required:true} }, - data(){return{loading:false,loadError:false,sync:null,rows:[],sourceColumns:[],configuredColumns:[],columnsTitle:'',sourceGroups:[],summary:{income:0,expense:0,balance:0,count:0},total:0,currentPage:1,lastPage:1,perPage:20,filters:emptyFilters(this.billType),queryFilters:emptyFilters(this.billType),resultFilters:null,filterOptions:{shops:[],bill_items:[],ledger_remarks:[]},filterOptionsLoading:false,filterOptionsError:false,requestSerial:0,sort:{key:'occurred_at',dir:'desc'},exportMode:'filtered-visible',exporting:false,exportRows:0,exportTotal:0,exportTimer:null};}, + data(){return{loading:false,loadError:false,sync:null,rows:[],sourceColumns:[],configuredColumns:[],columnsTitle:'',sourceGroups:[],summary:{income:0,expense:0,balance:0,count:0},total:0,currentPage:1,lastPage:1,perPage:20,filters:emptyFilters(this.billType),queryFilters:emptyFilters(this.billType),resultFilters:null,filterOptions:{shops:[],bill_items:[],ledger_remarks:[]},filterOptionsLoading:false,filterOptionsError:false,requestSerial:0,sort:{key:'occurred_at',dir:'desc'},exportMode:'filtered-visible',exporting:false,exportedRows:0,exportTotal:0,exportTimer:null};}, computed:{ isDouyin(){return this.billType==='douyin';}, - exportPercent(){return this.exportTotal>0?Math.min(100,Math.round(this.exportRows*100/this.exportTotal)):0;}, - exportText(){return '导出已开始,浏览器会保存到下载目录,你可以继续查看其它页面 · 已导出 '+Number(this.exportRows||0).toLocaleString()+' / '+Number(this.exportTotal||0).toLocaleString()+' 行('+this.exportPercent+'%)';}, + exportPercent(){return this.exportTotal>0?Math.min(100,Math.round(this.exportedRows*100/this.exportTotal)):0;}, + exportText(){const total=Number(this.exportTotal||0);const minutes=Math.max(1,Math.ceil(total/1800/60));return '共 '+total.toLocaleString()+' 行,预计约 '+minutes+' 分钟 · 已导出 '+Number(this.exportedRows||0).toLocaleString()+' 行('+this.exportPercent+'%)· 导出在后台进行,可以继续查看其它页面';}, // 费用结算时间标签、来源说明与收支净额统计口径按京东系列页签(京东账单/京喜账单)统一。 isJdBillTab(){return JD_BILL_TABS.indexOf(this.billType)!==-1;}, dateFilterLabel(){return DATE_FILTER_LABELS[this.billType]||'发生时间';}, @@ -135,7 +135,6 @@ export default { if(this.exportMode==='filtered-visible')this.visibleColumns.forEach(column=>params.append('columns[]',column.field)); // 服务端逐行生成 CSV:行数大时要几分钟,先告知用户,避免误判成「导不出来」。 const rows=Number(this.total||0); - if(rows>20000){const minutes=Math.max(1,Math.ceil(rows/1800/60));if(!window.confirm(`即将导出 ${rows.toLocaleString('zh-CN')} 行,服务端逐行生成,预计需要 ${minutes} 分钟。导出会在后台进行,你可以继续看其它页面,页面上会显示导出进度。是否继续?`)){return;}} const token=`ab${Date.now().toString(36)}${Math.random().toString(36).slice(2,10)}`; params.set('token',token); params.set('total',String(rows)); @@ -146,7 +145,7 @@ export default { startExportWatch(token,total){ this.stopExportWatch(); this.exporting=true; - this.exportRows=0; + this.exportedRows=0; this.exportTotal=Number(total||0); const progressUrl=this.progressBase||`${this.apiBase}/export-progress`; this.exportTimer=window.setInterval(()=>{ @@ -154,7 +153,7 @@ export default { .then(response=>response.ok?response.json():null) .then(state=>{ if(!state||state.status==='unknown'){return;} - this.exportRows=Number(state.rows||0); + this.exportedRows=Number(state.rows||0); if(state.total){this.exportTotal=Number(state.total);} if(state.status==='done'||state.status==='failed'){this.stopExportWatch();} }) diff --git a/resources/assets/js/components/business/TaobaoAlipayBills.vue b/resources/assets/js/components/business/TaobaoAlipayBills.vue index 7bbfbe49..52f7373f 100644 --- a/resources/assets/js/components/business/TaobaoAlipayBills.vue +++ b/resources/assets/js/components/business/TaobaoAlipayBills.vue @@ -124,14 +124,14 @@ const SORTABLE = ['occurred_at', 'income', 'expense', 'balance', 'record_code']; export default { name: 'TaobaoAlipayBills', components: { TableColumnSettings, flatPicker, [Select.name]: Select, [Option.name]: Option }, props: { apiBase: { type: String, required: true }, exportBase: { type: String, required: true }, progressBase: { type: String, default: '' }, userKey: { type: [String, Number], required: true } }, - data() { return { loading: false, error: '', rows: [], configuredColumns: [], summary: { income: 0, expense: 0, balance: 0, count: 0 }, total: 0, currentPage: 1, lastPage: 1, perPage: 20, filters: { store_name: '', ledger_remark: '', source_channel: '', bill_item: '', business_type: '', start: '', end: '', keyword: '' }, filterOptions: { shops: [], bill_items: [], ledger_remarks: [] }, filterOptionsLoading: false, filterOptionsError: false, sort: { key: 'occurred_at', dir: 'desc' }, exportMode: 'filtered-visible', exporting: false, exportRows: 0, exportTotal: 0, exportTimer: null }; }, + data() { return { loading: false, error: '', rows: [], configuredColumns: [], summary: { income: 0, expense: 0, balance: 0, count: 0 }, total: 0, currentPage: 1, lastPage: 1, perPage: 20, filters: { store_name: '', ledger_remark: '', source_channel: '', bill_item: '', business_type: '', start: '', end: '', keyword: '' }, filterOptions: { shops: [], bill_items: [], ledger_remarks: [] }, filterOptionsLoading: false, filterOptionsError: false, sort: { key: 'occurred_at', dir: 'desc' }, exportMode: 'filtered-visible', exporting: false, exportedRows: 0, exportTotal: 0, exportTimer: null }; }, computed: { defaultColumns() { return COLUMNS; }, visibleColumns() { return this.configuredColumns.filter((column) => column.visible); }, visibleGroups() { return this.visibleColumns.reduce((groups, column) => { const last = groups[groups.length - 1]; if (last && last.label === column.group) last.span += 1; else groups.push({ label: column.group, span: 1 }); return groups; }, []); }, stats() { return [{ label: '收入合计', value: this.moneyWan(this.summary.income), note: `${this.summary.count} 笔交易`, className: 'is-income' }, { label: '支出合计', value: this.moneyWan(this.summary.expense), note: '按当前筛选', className: 'is-expense' }, { label: '收支差额', value: this.moneyWan(this.summary.balance), note: '收入合计 - 支出合计', className: '' }, { label: '交易笔数', value: this.summary.count, note: '当前筛选结果', className: '' }]; }, rangeStart() { return this.total === 0 ? 0 : (this.currentPage - 1) * this.perPage + 1; }, rangeEnd() { return Math.min(this.currentPage * this.perPage, this.total); }, - exportPercent() { return this.exportTotal > 0 ? Math.min(100, Math.round(this.exportRows * 100 / this.exportTotal)) : 0; }, - exportText() { return '导出已开始,浏览器会保存到下载目录,你可以继续查看其它页面 · 已导出 ' + Number(this.exportRows || 0).toLocaleString() + ' / ' + Number(this.exportTotal || 0).toLocaleString() + ' 行(' + this.exportPercent + '%)'; }, + exportPercent() { return this.exportTotal > 0 ? Math.min(100, Math.round(this.exportedRows * 100 / this.exportTotal)) : 0; }, + exportText() { const total = Number(this.exportTotal || 0); const minutes = Math.max(1, Math.ceil(total / 1800 / 60)); return '共 ' + total.toLocaleString() + ' 行,预计约 ' + minutes + ' 分钟 · 已导出 ' + Number(this.exportedRows || 0).toLocaleString() + ' 行(' + this.exportPercent + '%)· 导出在后台进行,可以继续查看其它页面'; }, invalidDateRange() { return Boolean(this.filters.start && this.filters.end && this.filters.start > this.filters.end); }, startDateConfig() { return { ...dateConfig, maxDate: this.filters.end || null }; }, endDateConfig() { return { ...dateConfig, minDate: this.filters.start || null }; }, @@ -156,10 +156,6 @@ export default { if (this.exportMode === 'filtered-visible') this.visibleColumns.forEach((column) => params.append('columns[]', column.field)); // 服务端逐行生成 CSV:行数大时要几分钟,先告知用户,避免误判成「导不出来」。 const rows = Number(this.total || 0); - if (rows > 20000) { - const minutes = Math.max(1, Math.ceil(rows / 1800 / 60)); - if (!window.confirm(`即将导出 ${rows.toLocaleString('zh-CN')} 行,服务端逐行生成,预计需要 ${minutes} 分钟。导出会在后台进行,你可以继续看其它页面,页面上会显示导出进度。是否继续?`)) { return; } - } const token = `ab${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`; params.set('token', token); params.set('total', String(rows)); @@ -170,7 +166,7 @@ export default { startExportWatch(token, total) { this.stopExportWatch(); this.exporting = true; - this.exportRows = 0; + this.exportedRows = 0; this.exportTotal = Number(total || 0); const progressUrl = this.progressBase || `${this.apiBase}/export-progress`; this.exportTimer = window.setInterval(() => { @@ -178,7 +174,7 @@ export default { .then((response) => (response.ok ? response.json() : null)) .then((state) => { if (!state || state.status === 'unknown') { return; } - this.exportRows = Number(state.rows || 0); + this.exportedRows = Number(state.rows || 0); if (state.total) { this.exportTotal = Number(state.total); } if (state.status === 'done' || state.status === 'failed') { this.stopExportWatch(); } })