- 任务 {{ job.id }} 状态:
{{ job.status }} 行数:{{ job.total_rows || '' }}
-
下载
+
+
+
+
+
+ {{ jobPercent(scope.row) }}
+
+
+
+
+
+
+
+ 下载
+
+
+
+
- 暂无任务
+ 暂无任务
@@ -143,13 +160,13 @@
保存
-
+
筛选条件
-
+
-
+
@@ -164,7 +181,9 @@
-
+
+
+
diff --git a/web/main.js b/web/main.js
index 4b6eb9d..da33c38 100644
--- a/web/main.js
+++ b/web/main.js
@@ -3,6 +3,12 @@ const { createApp, reactive } = Vue;
setup(){
const state = reactive({
templates: [],
+ jobs: [],
+ jobsVisible: false,
+ jobsTplId: null,
+ jobsPage: 1,
+ jobsPageSize: 15,
+ jobsTotal: 0,
job: {},
form: {
name: '',
@@ -345,6 +351,7 @@ const { createApp, reactive } = Vue;
return v || ''
}
const creatorOptions = Vue.ref([])
+ const resellerOptions = Vue.ref([])
const hasCreators = Vue.computed(()=> Array.isArray(state.exportForm.creatorIds) && state.exportForm.creatorIds.length>0 )
const hasReseller = Vue.computed(()=> !!state.exportForm.resellerId)
const hasPlan = Vue.computed(()=> !!state.exportForm.planId)
@@ -358,6 +365,16 @@ const { createApp, reactive } = Vue;
creatorOptions.value = arr.map(it=>({label: it.name || String(it.id), value: Number(it.id)}))
}catch(_e){ creatorOptions.value = [] }
}
+ const loadResellers = async ()=>{
+ const ids = Array.isArray(state.exportForm.creatorIds) ? state.exportForm.creatorIds : []
+ if(!ids.length){ resellerOptions.value = []; return }
+ try{
+ const res = await fetch(API_BASE + '/api/resellers?creator=' + ids.join(','))
+ const data = await res.json()
+ const arr = Array.isArray(data?.data) ? data.data : (Array.isArray(data) ? data : [])
+ resellerOptions.value = arr.map(it=>({label: (it.name||'') + (it.name?'':'') , value: Number(it.id)}))
+ }catch(_e){ resellerOptions.value = [] }
+ }
const exportType = Vue.computed(()=>{
const f = state.exportTpl && state.exportTpl.filters
if(!f) return null
@@ -404,6 +421,12 @@ const { createApp, reactive } = Vue;
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 yearRange = ()=>{
+ const now = new Date()
+ const start = new Date(now.getFullYear(), 0, 1, 0, 0, 0)
+ const end = new Date(now.getFullYear(), 11, 31, 23, 59, 59)
+ return [ fmtDT(start), fmtDT(end) ]
+ }
const loadTemplates = async ()=>{
try{
const res = await fetch(API_BASE + '/api/templates');
@@ -420,6 +443,33 @@ const { createApp, reactive } = Vue;
state.templates = []
}
}
+ const loadJobs = async (page)=>{
+ if(!page) page = state.jobsPage
+ try{
+ const qs = new URLSearchParams()
+ qs.set('page', String(page))
+ qs.set('page_size', String(state.jobsPageSize))
+ if(state.jobsTplId){ qs.set('template_id', String(state.jobsTplId)) }
+ const res = await fetch(API_BASE + '/api/exports?' + qs.toString());
+ if(!res.ok){ state.jobs = []; return }
+ const data = await res.json();
+ const payload = data?.data || data || {}
+ const arr = Array.isArray(payload.items) ? payload.items : (Array.isArray(payload) ? payload : [])
+ state.jobs = arr
+ state.jobsTotal = Number(payload.total || 0)
+ state.jobsPage = Number(payload.page || page)
+ }catch(_e){ state.jobs = [] }
+ }
+ const openJobs = (row)=>{ state.jobsTplId = row.id; state.jobsVisible = true; loadJobs(1) }
+ const closeJobs = ()=>{ state.jobsVisible = false }
+ const jobPercent = (row)=>{
+ const est = Number(row.row_estimate || 0)
+ const done = Number(row.total_rows || 0)
+ if(row.status==='completed') return '100%'
+ if(row.status==='queued') return '0%'
+ if(est>0 && done>=0){ const p = Math.max(0, Math.min(100, Math.floor(done*100/est))); return p + '%' }
+ return row.status || ''
+ }
const createTemplate = async ()=>{
const formRef = createFormRef.value
const ok = formRef ? await formRef.validate().catch(()=>false) : true
@@ -463,6 +513,7 @@ const { createApp, reactive } = Vue;
state.exportForm.datasource = state.exportTpl.datasource || row.datasource || 'marketing'
state.exportForm.file_format = state.exportTpl.file_format || row.file_format || 'xlsx'
if(state.exportForm.datasource==='marketing'){ loadCreators() }
+ if(!Array.isArray(state.exportForm.dateRange) || state.exportForm.dateRange.length!==2){ state.exportForm.dateRange = yearRange() }
state.exportVisible = true
}
const loadTemplateDetail = async (id)=>{
@@ -501,9 +552,9 @@ const { createApp, reactive } = Vue;
const j=await r.json();
const jid = j?.data?.id ?? j?.id
state.exportVisible=false
- if(jid){ loadJob(jid) } else { msg('任务创建返回异常','error') }
+ if(jid){ loadJob(jid); loadJobs() } else { msg('任务创建返回异常','error') }
}
- Vue.watch(()=>state.exportForm.creatorIds, ()=>{ state.exportForm.resellerId=null; state.exportForm.planId=null; state.exportForm.keyBatchId=null; state.exportForm.codeBatchId=null; state.exportForm.productId=null })
+ Vue.watch(()=>state.exportForm.creatorIds, ()=>{ state.exportForm.resellerId=null; state.exportForm.planId=null; state.exportForm.keyBatchId=null; state.exportForm.codeBatchId=null; state.exportForm.productId=null; loadResellers() })
Vue.watch(()=>state.exportForm.resellerId, ()=>{ state.exportForm.planId=null; state.exportForm.keyBatchId=null; state.exportForm.codeBatchId=null; state.exportForm.productId=null })
Vue.watch(()=>state.exportForm.planId, ()=>{ state.exportForm.keyBatchId=null; state.exportForm.codeBatchId=null; state.exportForm.productId=null })
Vue.watch(()=>state.exportForm.keyBatchId, ()=>{ state.exportForm.codeBatchId=null; state.exportForm.productId=null })
@@ -562,7 +613,8 @@ const { createApp, reactive } = Vue;
}
const download = (id)=>{ window.open(API_BASE + '/api/exports/'+id+'/download','_blank') }
loadTemplates()
- return { ...Vue.toRefs(state), visibilityOptions, formatOptions, datasourceOptions, fieldOptions, loadTemplates, createTemplate, openExport, submitExport, loadJob, download, openEdit, saveEdit, removeTemplate, resizeDialog, createRules, exportRules, editRules, createFormRef, exportFormRef, editFormRef, dsLabel, exportType, isOrder, exportTitle }
+
+ return { ...Vue.toRefs(state), visibilityOptions, formatOptions, datasourceOptions, fieldOptions, loadTemplates, createTemplate, openExport, submitExport, loadJob, loadJobs, openJobs, closeJobs, download, openEdit, saveEdit, removeTemplate, resizeDialog, createRules, exportRules, editRules, createFormRef, exportFormRef, editFormRef, dsLabel, exportType, isOrder, exportTitle, creatorOptions, resellerOptions, hasCreators, hasReseller, hasPlan, hasKeyBatch, hasCodeBatch, jobPercent }
}
})
app.use(ElementPlus)