diff --git a/app/Http/Controllers/Business/Pages.php b/app/Http/Controllers/Business/Pages.php index 4b1e236f..3c9feae9 100644 --- a/app/Http/Controllers/Business/Pages.php +++ b/app/Http/Controllers/Business/Pages.php @@ -914,7 +914,7 @@ class Pages extends Controller * 支持的营销账户平台列表 */ protected const MARKETING_PLATFORMS = [ - 'taobao-alipay', 'taobao-wechat', 'pinduoduo', + 'promotion-marketing', 'taobao-alipay', 'taobao-wechat', 'pinduoduo', 'jd-wallet', 'douyin', 'dewu', 'vipshop', 'jd-promotion', 'douyin-promotion', 'dewu-promotion', 'vip-promotion', ]; @@ -9341,6 +9341,11 @@ class Pages extends Controller return null; } + // 推广费页面由 pages.js 挂载共享 AccountBills 组件,服务端不重复查询。 + if ($tab === 'promotion-marketing') { + return null; + } + if (in_array($tab, self::PROMOTION_MARKETING_TABS, true)) { return app(PlatformPromotionAccountPageData::class)->content($tab); } diff --git a/config/business_navigation.php b/config/business_navigation.php index 2afe02e6..d18c90bd 100644 --- a/config/business_navigation.php +++ b/config/business_navigation.php @@ -554,13 +554,13 @@ $navigation = [ ['slug' => 'dewu', 'title' => '得物账户'], ['slug' => 'vipshop', 'title' => '唯品会MP账户'], ['slug' => 'vipshop-jx', 'title' => '唯品会JX账户'], - ['slug' => 'promotion-marketing', 'title' => '推广费(营销账户)'], ], ], [ 'slug' => 'marketing-account-bills', 'title' => '店铺营销账户账单', 'tabs' => [ + ['slug' => 'promotion-marketing', 'title' => '推广费(营销账户)'], ['slug' => 'taobao-alipay', 'title' => '淘系支付宝账户'], ['slug' => 'taobao-wechat', 'title' => '淘系微信账户'], ['slug' => 'pinduoduo', 'title' => '拼多多账户'], diff --git a/resources/assets/js/components/business/AccountBills.vue b/resources/assets/js/components/business/AccountBills.vue index 9b2b9ef9..a726eb3f 100644 --- a/resources/assets/js/components/business/AccountBills.vue +++ b/resources/assets/js/components/business/AccountBills.vue @@ -1,6 +1,12 @@ - + @@ -262,11 +269,13 @@ const DATE_FILTER_LABELS = {'jd-transactions':'费用结算时间','jingxi':'费 const dateConfig = {dateFormat:'Y-m-d',locale:Mandarin,disableMobile:true,allowInput:false,onReady:(_,value,picker)=>picker.calendarContainer.classList.add('account-bill-calendar')}; // 默认账期=本月:不填日期时查全表(几百万行)会很慢,默认落到当月既快又符合日常用法。 const currentMonthRange = () => { const now = new Date(); const year = now.getFullYear(); const month = now.getMonth() + 1; const pad = value => String(value).padStart(2, '0'); const lastDay = new Date(year, month, 0).getDate(); return { start: `${year}-${pad(month)}-01`, end: `${year}-${pad(month)}-${pad(lastDay)}` }; }; -const emptyFilters = billType => ({store_name:'',ledger_remark:'',bill_item:'',...currentMonthRange(),keyword:'',keyword_match:'contains'}); +const filtersForRange=(start,end)=>{const range=currentMonthRange();return {store_name:'',ledger_remark:'',bill_item:'',start:start||range.start,end:end||range.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,promotionRequestSerial:0,sort:{key:'occurred_at',dir:'desc'},exportMode:'filtered-visible',exporting:false,exportModalOpen:false,exportTasks:[],exportHint:'导出在后台生成,成功后点「下载」;期间可以继续查看其它页面。',exportTimer:null,promotionModalOpen:false,promotionExpenseTab:'platform',promotionExpenseLoading:false,promotionExpenseError:false,promotionStoreSummary:[],promotionExpenseSummary:{expense:0,count:0},promotionExpenseMessage:'',promotionPlatformFilter:'',promotionStoreFilter:'',promotionNoteFilter:'',promotionNoteTimer: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,promotionRequestSerial:0,sort:{key:'occurred_at',dir:'desc'},exportMode:'filtered-visible',exporting:false,exportModalOpen:false,exportTasks:[],exportHint:'导出在后台生成,成功后点「下载」;期间可以继续查看其它页面。',exportTimer:null,promotionModalOpen:false,promotionExpenseTab:'platform',promotionExpenseLoading:false,promotionExpenseError:false,promotionStoreSummary:[],promotionExpenseSummary:{expense:0,count:0},promotionExpenseMessage:'',promotionPlatformFilter:'',promotionStoreFilter:''};}, + 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}, pageKey:{type:String,default:'store-receiving-bills'}, compact:{type:Boolean,default:false}, initialStart:{type:String,default:''}, initialEnd:{type:String,default:''} }, + data(){ + const filters=filtersForRange(this.initialStart,this.initialEnd); + 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:this.compact?1:20,filters,queryFilters:{...filters},resultFilters:null,filterOptions:{shops:[],bill_items:[],ledger_remarks:[]},filterOptionsLoading:false,filterOptionsError:false,requestSerial:0,promotionRequestSerial:0,sort:{key:'occurred_at',dir:'desc'},exportMode:'filtered-visible',exporting:false,exportModalOpen:false,exportTasks:[],exportHint:'导出在后台生成,成功后点「下载」;期间可以继续查看其它页面。',exportTimer:null,promotionModalOpen:false,promotionExpenseTab:'platform',promotionExpenseLoading:false,promotionExpenseError:false,promotionStoreSummary:[],promotionExpenseSummary:{expense:0,count:0},promotionExpenseMessage:'',promotionPlatformFilter:'',promotionStoreFilter:'',promotionNoteFilter:'',promotionNoteTimer:null}; + }, computed:{ isDouyin(){return this.billType==='douyin';}, isPromotionMarketing(){return this.billType==='promotion-marketing';}, @@ -289,7 +298,7 @@ export default { visibleGroups(){return this.visibleColumns.reduce((groups,column)=>{if(!column.group)return groups;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(){ if(this.isPromotionMarketing){ - return [{label:'推广费(营销账户)',value:this.moneyWan(this.promotionExpenseSummary.expense),note:`${this.promotionExpenseSummary.count} 笔支出 · 按当前筛选`,className:'is-expense',action:true}]; + return [{label:'推广费(营销账户)',value:this.loadError?'—':this.moneyWan(this.promotionExpenseSummary.expense),note:this.loadError?'加载失败,请刷新页面重试':`${this.promotionExpenseSummary.count} 笔支出 · 按当前筛选`,className:'is-expense',action:true}]; } const netLabel=JD_BILL_TABS.concat(['vipshop-jx']).indexOf(this.billType)!==-1; const items=[ @@ -307,13 +316,13 @@ export default { promotionFilteredExpenseTotal(){return this.promotionFilteredStoreRows.reduce((sum,row)=>sum+Number(row.amount||0),0);}, rangeStart(){return this.total===0?0:(this.currentPage-1)*this.perPage+1;},rangeEnd(){return Math.min(this.currentPage*this.perPage,this.total);} }, - mounted(){if(this.usesMirrorOptions)this.fetchFilterOptions();this.fetch();}, + mounted(){if(this.usesMirrorOptions&&!this.compact)this.fetchFilterOptions();this.fetch();}, beforeDestroy(){if(this.promotionNoteTimer)window.clearTimeout(this.promotionNoteTimer);}, methods:{ async fetchFilterOptions(){this.filterOptionsLoading=true;this.filterOptionsError=false;try{const response=await window.axios.get(`${this.apiBase}/filter-options`,{params:{start:this.filters.start,end:this.filters.end}});const body=response.data||{};this.filterOptions={shops:Array.isArray(body.shops)?body.shops:[],bill_items:Array.isArray(body.bill_items)?body.bill_items:[],ledger_remarks:Array.isArray(body.ledger_remarks)?body.ledger_remarks:[]};}catch(error){this.filterOptionsError=true;}finally{this.filterOptionsLoading=false;}}, async fetch(){const requestId=++this.requestSerial;const requestedFilters={...this.queryFilters};this.loading=true;this.loadError=false;try{const response=await window.axios.get(this.apiBase,{params:{...requestedFilters,sort:this.sort.key,dir:this.sort.dir,limit:this.perPage,page:this.currentPage}});if(requestId!==this.requestSerial)return;const body=response.data||{};this.resultFilters=requestedFilters;this.sync=body.sync||null;this.rows=body.data||[];this.total=body.total||0;this.currentPage=body.current_page||1;this.lastPage=body.last_page||1;this.perPage=body.per_page||this.perPage;this.summary=body.summary||{income:0,expense:0,balance:0,count:0};if(this.isPromotionMarketing){this.promotionExpenseSummary={expense:Number(this.summary.expense||0),count:Number(this.summary.count||0)};}if(body.columns){this.columnsTitle=body.columns.title||'';this.sourceGroups=body.columns.groups||[];this.sourceColumns=this.attachGroups(body.columns.columns||body.columns||[],this.sourceGroups);}}catch(error){if(requestId!==this.requestSerial)return;this.rows=[];this.total=0;this.sync=null;this.summary={income:0,expense:0,balance:0,count:0};this.loadError=true;}finally{if(requestId===this.requestSerial)this.loading=false;}}, attachGroups(columns,groups){let index=0;return columns.map((column,columnIndex)=>{while(groups[index]&&columnIndex>=groups.slice(0,index+1).reduce((sum,group)=>sum+Number(group.span||0),0))index+=1;return{...column,key:column.field||`column-${columnIndex}`,group:groups[index]?groups[index].label:''};});}, - applyFilters(){if(this.invalidDateRange)return;this.queryFilters={...this.filters};this.currentPage=1;return this.fetch();},resetFilters(){this.filters=emptyFilters(this.billType);return this.applyFilters();},changePage(page){this.currentPage=page;this.fetch();this.$nextTick(()=>{if(this.$refs.scroll)this.$refs.scroll.scrollTop=0;});}, + applyFilters(){if(this.invalidDateRange)return;this.queryFilters={...this.filters};this.currentPage=1;return this.fetch();},resetFilters(){this.filters=filtersForRange();return this.applyFilters();},changePage(page){this.currentPage=page;this.fetch();this.$nextTick(()=>{if(this.$refs.scroll)this.$refs.scroll.scrollTop=0;});}, openDatePicker(refName){const picker=this.$refs[refName];if(picker&&picker.fp)picker.fp.open();}, async fetchPromotionExpenseDetails(filters=null){ const requestId=++this.promotionRequestSerial; @@ -326,15 +335,10 @@ export default { if(requestId!==this.promotionRequestSerial)return; const body=response.data||{}; this.promotionStoreSummary=Array.isArray(body.store_summary)?body.store_summary:[]; - this.promotionExpenseSummary={ - expense:Number((body.summary||{}).expense||0), - count:Number((body.summary||{}).count||0), - }; this.promotionExpenseMessage=body.message||''; }catch(error){ if(requestId!==this.promotionRequestSerial)return; this.promotionStoreSummary=[]; - this.promotionExpenseSummary={expense:0,count:0}; this.promotionExpenseError=true; }finally{ if(requestId===this.promotionRequestSerial)this.promotionExpenseLoading=false; @@ -525,9 +529,15 @@ export default {