const { createApp, reactive } = Vue; const app = createApp({ setup(){ const state = reactive({ templates: [], job: {}, form: { name: '', datasource: 'marketing', main_table: 'order', orderType: 1, fieldsRaw: 'order_number,creator,out_trade_no,type,status,contract_price,num,total,pay_amount,create_time', fieldsSel: [], creatorRaw: '', permissionMode: 'all', timeRange: [], file_format: 'xlsx', visibility: 'private', owner_id: '1' }, createVisible: false, editVisible: false, exportVisible: false, createWidth: (localStorage.getItem('tplDialogWidth') || '900px'), editWidth: (localStorage.getItem('tplEditDialogWidth') || '600px'), edit: { id: null, name: '', visibility: 'private', file_format: 'csv' }, export: { tplId: null, orderType: 1, permissionMode: 'all', creatorRaw: '', timeRange: [], file_format: 'xlsx' } }) const FIELDS_MAP = { marketing: { order: [ { value: 'order_number', label: '订单编号' }, { value: 'creator', label: '创建者ID' }, { value: 'out_trade_no', label: '支付流水号' }, { value: 'type', label: '订单类型' }, { value: 'status', label: '订单状态' }, { value: 'contract_price', label: '合同单价' }, { value: 'num', label: '数量' }, { value: 'total', label: '总金额' }, { value: 'pay_amount', label: '支付金额' }, { value: 'create_time', label: '创建时间' }, { value: 'update_time', label: '更新时间' } ] , order_detail: [ { value: 'plan_title', label: '计划标题' }, { value: 'reseller_name', label: '分销商名称' }, { value: 'product_name', label: '商品名称' }, { value: 'show_url', label: '商品图片URL' }, { value: 'official_price', label: '官方价' }, { value: 'cost_price', label: '成本价' }, { value: 'create_time', label: '创建时间' }, { value: 'update_time', label: '更新时间' } ], order_cash: [ { value: 'channel', label: '渠道' }, { value: 'cash_activity_id', label: '红包批次号' }, { value: 'receive_status', label: '领取状态' }, { value: 'receive_time', label: '拆红包时间' }, { value: 'cash_packet_id', label: '红包ID' }, { value: 'cash_id', label: '红包规则ID' }, { value: 'amount', label: '红包额度' }, { value: 'status', label: '状态' }, { value: 'expire_time', label: '过期时间' }, { value: 'update_time', label: '更新时间' } ], order_voucher: [ { value: 'channel', label: '渠道' }, { value: 'channel_activity_id', label: '渠道立减金批次' }, { value: 'channel_voucher_id', label: '渠道立减金ID' }, { value: 'status', label: '状态' }, { value: 'grant_time', label: '领取时间' }, { value: 'usage_time', label: '核销时间' }, { value: 'refund_time', label: '退款时间' }, { value: 'status_modify_time', label: '状态更新时间' }, { value: 'overdue_time', label: '过期时间' }, { value: 'refund_amount', label: '退款金额' }, { value: 'official_price', label: '官方价' }, { value: 'out_biz_no', label: '外部业务号' }, { value: 'account_no', label: '账户号' } ], plan: [ { value: 'id', label: '计划ID' }, { value: 'title', label: '计划标题' }, { value: 'status', label: '状态' }, { value: 'begin_time', label: '开始时间' }, { value: 'end_time', label: '结束时间' } ], key_batch: [ { value: 'id', label: '批次ID' }, { value: 'batch_name', label: '批次名称' }, { value: 'bind_object', label: '绑定对象' }, { value: 'quantity', label: '发放数量' }, { value: 'stock', label: '剩余库存' }, { value: 'begin_time', label: '开始时间' }, { value: 'end_time', label: '结束时间' } ], code_batch: [ { value: 'id', label: '兑换批次ID' }, { value: 'title', label: '标题' }, { value: 'status', label: '状态' }, { value: 'begin_time', label: '开始时间' }, { value: 'end_time', label: '结束时间' }, { value: 'quantity', label: '数量' }, { value: 'usage', label: '使用数' }, { value: 'stock', label: '库存' } ], voucher: [ { value: 'channel', label: '渠道' }, { value: 'channel_activity_id', label: '渠道批次号' }, { value: 'price', label: '合同单价' }, { value: 'balance', label: '剩余额度' }, { value: 'used_amount', label: '已用额度' }, { value: 'denomination', label: '面额' } ], voucher_batch: [ { value: 'channel_activity_id', label: '渠道批次号' }, { value: 'temp_no', label: '模板编号' }, { value: 'provider', label: '服务商' }, { value: 'weight', label: '权重' } ], merchant_key_send: [ { value: 'merchant_id', label: '商户ID' }, { value: 'out_biz_no', label: '商户业务号' }, { value: 'key', label: '券码' }, { value: 'status', label: '状态' }, { value: 'usage_time', label: '核销时间' }, { value: 'create_time', label: '创建时间' } ] } } const TABLE_LABELS = { order: '订单主表', order_detail: '订单详情', order_cash: '红包订单', order_voucher: '立减金订单', plan: '活动计划', key_batch: 'KEY批次', code_batch: '兑换码批次', voucher: '立减金', voucher_batch: '立减金批次', merchant_key_send: '开放平台发放记录' } const fieldOptions = Vue.computed(()=>{ const ds = state.form.datasource const FM = FIELDS_MAP[ds] || {} const node = (table, children=[])=>({ value: table, label: TABLE_LABELS[table]||table, children }) const fieldsNode = (table)=> (FM[table]||[]) const orderChildrenBase = [] orderChildrenBase.push(...fieldsNode('order')) orderChildrenBase.push(node('order_detail', fieldsNode('order_detail'))) const planChildren = [] planChildren.push(...fieldsNode('plan')) planChildren.push(node('key_batch', [ ...fieldsNode('key_batch'), node('code_batch', fieldsNode('code_batch')) ])) const voucherChildren = [] voucherChildren.push(...fieldsNode('order_voucher')) voucherChildren.push(node('voucher', [ ...fieldsNode('voucher'), node('voucher_batch', fieldsNode('voucher_batch')) ])) const orderChildrenFor = (type)=>{ const ch = [...orderChildrenBase] if(type===1){ // 直充卡密:排除红包与立减金 ch.push(node('plan', planChildren)) ch.push(node('merchant_key_send', fieldsNode('merchant_key_send'))) } else if(type===2){ // 立减金:排除红包,保留立减金链 ch.push(node('order_voucher', voucherChildren)) ch.push(node('plan', planChildren)) } else if(type===3){ // 红包:仅红包链 ch.push(node('order_cash', fieldsNode('order_cash'))) ch.push(node('plan', planChildren)) } else { // 未选择类型:全部显示 ch.push(node('order_cash', fieldsNode('order_cash'))) ch.push(node('order_voucher', voucherChildren)) ch.push(node('plan', planChildren)) ch.push(node('merchant_key_send', fieldsNode('merchant_key_send'))) } return ch } const type = Number(state.form.orderType || 0) const orderNode = node('order', orderChildrenFor(type)) if(type){ return [ orderNode ] } return [ { value: 'scene_order', label: '订单数据', children: [ orderNode ] } ] }) const msg = (t, type='success')=>ElementPlus.ElMessage({message:t,type}); const visibilityOptions = [ { label: '个人', value: 'private' }, { label: '公共', value: 'public' } ] const formatOptions = [ { label: 'XLSX', value: 'xlsx' }, { label: 'CSV', value: 'csv' } ] const fmtDT = (d)=>{ const pad=(n)=>String(n).padStart(2,'0'); return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`; } const loadTemplates = async ()=>{ const res = await fetch('/api/templates'); state.templates = await res.json(); } const createTemplate = async ()=>{ let fields = [] if(state.form.fieldsSel && state.form.fieldsSel.length){ fields = state.form.fieldsSel.map(path=>{ if(Array.isArray(path)){ if(path.length===4){ return `${path[2]}.${path[3]}` } if(path.length===3){ return `${path[1]}.${path[2]}` } if(path.length===2){ return `${path[0]}.${path[1]}` } } return path }) } else { fields = state.form.fieldsRaw.split(',').map(s=>s.trim()).filter(Boolean) } const payload = { name: state.form.name, datasource: state.form.datasource, main_table: state.form.main_table, fields, file_format: state.form.file_format, owner_id: Number(state.form.owner_id), visibility: state.form.visibility } const res = await fetch('/api/templates',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}); if(res.ok){ msg('创建成功'); state.createVisible=false; loadTemplates() } else { msg(await res.text(),'error') } } const openExport = (row)=>{ state.export.tplId = row.id state.export.file_format = row.file_format || 'xlsx' state.exportVisible = true } const submitExport = async ()=>{ const id = state.export.tplId const filters = {} if(state.export.permissionMode==='creator' && state.export.creatorRaw){ filters.creator_in = state.export.creatorRaw.split(',').map(s=>Number(s.trim())).filter(x=>!isNaN(x)) } if(state.export.orderType){ filters.type_eq = Number(state.export.orderType) } if(state.export.timeRange && state.export.timeRange.length===2){ filters.create_time_between = [fmtDT(new Date(state.export.timeRange[0])), fmtDT(new Date(state.export.timeRange[1]))] } const payload={template_id:Number(id),requested_by:1,permission:{},options:{},filters, file_format: state.export.file_format}; const r=await fetch('/api/exports',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}); const j=await r.json(); state.exportVisible=false loadJob(j.id); } const clampWidth = (w)=>{ const n = Math.max(500, Math.min(1400, w)) return n + 'px' } const resizeDialog = (kind, delta)=>{ if(kind==='create'){ const cur = parseInt(String(state.createWidth).replace('px','')||'900',10) const next = clampWidth(cur + delta) state.createWidth = next localStorage.setItem('tplDialogWidth', next) } else if(kind==='edit'){ const cur = parseInt(String(state.editWidth).replace('px','')||'600',10) const next = clampWidth(cur + delta) state.editWidth = next localStorage.setItem('tplEditDialogWidth', next) } } const openEdit = (row)=>{ state.edit.id = row.id state.edit.name = row.name state.edit.visibility = row.visibility state.edit.file_format = row.file_format state.editVisible = true } const saveEdit = async ()=>{ const id = state.edit.id const payload = { name: state.edit.name, visibility: state.edit.visibility, file_format: state.edit.file_format } const res = await fetch('/api/templates/'+id,{method:'PATCH',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}) if(res.ok){ msg('保存成功'); state.editVisible=false; loadTemplates() } else { msg(await res.text(),'error') } } const removeTemplate = async (id)=>{ const r = await fetch('/api/templates/'+id,{method:'DELETE'}) if(r.ok){ msg('删除成功'); loadTemplates() } else { msg(await r.text(),'error') } } const loadJob = async (id)=>{ const res=await fetch('/api/exports/'+id); state.job = await res.json(); } const download = (id)=>{ window.open('/api/exports/'+id+'/download','_blank') } loadTemplates() return { ...Vue.toRefs(state), visibilityOptions, formatOptions, fieldOptions, loadTemplates, createTemplate, openExport, submitExport, loadJob, download, openEdit, saveEdit, removeTemplate, resizeDialog } } }) app.use(ElementPlus) app.mount('#app')