@@ -9,12 +9,18 @@
< el-form :model = "query" :inline = "true" class = "search-form" @submit.prevent ="handleQuery" >
< el -form -item label = "Goods ID" > < el-input v-model = "query.goodsId" clearable placeholder="输入 goods_id" @keyup.enter="handleQuery" / > < / el-form-item >
< el-form-item label = "状态" > < el-select v-model = "query.status" clearable placeholder="全部状态" style="width:160px"><el-option v-for="item in statuses" :key="item.value" :label="item.label" :value="item.value" / > < / el-select > < / el-form-item >
< el-form-item > < el-button type = "primary" :icon = "Search" @click ="handleQuery" > 查询 < / el -button > < el-button :icon = "RefreshLeft" @click ="resetQuery" > 重置 < / el -button > < / el-form-item >
< el-form-item label = "来源" > < el-select v-model = "query.source" clearable placeholder="全部来源" style="width:160px"><el-option v-for="item in sources" :key="item.value" :label="item.label" :value="item.value" / > < / el-select > < / el-form-item >
< el-form-item >
< el-button type = "primary" :icon = "Search" @click ="handleQuery" > 查询 < / el -button >
< el-button :icon = "RefreshLeft" @click ="resetQuery" > 重置 < / el -button >
< el-button type = "danger" plain :loading = "cancelBatch.loading" : disabled = "pendingCancelCount === 0" @click ="openCancelBatch" > 取消未开始的任务 < span class = "action-count" > { { pendingCancelCount } } < / span > < / el-button >
< / el-form-item >
< / el-form >
< el-table v-loading = "loading" :data="tasks" border stripe empty-text="暂无采集任务" >
< el -table -column label = "任务 ID" prop = "id" width = "100" / >
< el-table-column label = "执行次数" width = "90" align = "center" > < template # default = "{ row }" > 第 { { row . attemptNumber || 1 } } 次 < / template > < / el-table-column >
< el-table-column label = "Goods ID" prop = "goodsIdSnapshot" min -width = " 160 " / >
< el-table-column label = "来源" width = "130" > < template # default = "{ row }" > { { sourceLabel ( row . source ) } } < / template > < / el-table-column >
< el-table-column label = "规则" prop = "ruleName" min -width = " 150 " show -overflow -tooltip / >
< el-table-column label = "设备" min -width = " 150 " > < template # default = "{ row }" > { { row . deviceName || '空闲设备领取' } } < / template > < / el-table-column >
< el-table-column label = "状态" width = "120" align = "center" > < template # default = "{ row }" > < el-tag :type = "statusType(row.status)" > { { statusLabel ( row . status ) } } < / el-tag > < / template > < / el-table-column >
@@ -38,6 +44,21 @@
< template # footer > < el-button @click ="createDialog.open=false" > 取消 < / el -button > < el-button type = "primary" :loading = "createDialog.saving" @click ="createTask" > 创建 < / el -button > < / template >
< / el-dialog >
< el-dialog v-model = "cancelBatch.open" title="取消未开始的任务" width="680px" :close-on-click-modal="false" >
< el -alert
: title = "`将取消整个筛选条件(来源:${sourceLabel(query.source) || '全部'};状态:${statusLabel(query.status) || '全部'}; Goods ID: ${query.goodsId || '全部'})下所有待执行任务,不只是当前页。`"
type = "warning" :closable = "false" show -icon class = "dialog-alert" / >
< div v-loading = "cancelBatch.previewLoading" >
< p > 共 { { cancelBatch . previewTotal } } 个待执行任务将被取消 { { cancelBatch . previewTotal > cancelBatch . preview . length ? ` (仅列出前 ${ cancelBatch . preview . length } 个) ` : '' } } : < / p >
< el-table :data = "cancelBatch.preview" border max -height = " 320 " empty -text = " 暂无待执行任务 " >
< el-table-column label = "任务 ID" prop = "id" width = "100" / >
< el-table-column label = "来源" width = "130" > < template # default = "{ row }" > { { sourceLabel ( row . source ) } } < / template > < / el-table-column >
< el-table-column label = "Goods ID" prop = "goodsIdSnapshot" min -width = " 160 " / >
< / el-table >
< / div >
< template # footer > < el-button @click ="cancelBatch.open=false" > 取消 < / el -button > < el-button type = "danger" :loading = "cancelBatch.submitting" : disabled = "cancelBatch.previewTotal === 0" @click ="submitCancelBatch" > 确认取消 < / el -button > < / template >
< / el-dialog >
< el-drawer v-model = "detail.open" title="采集任务详情" size="720px" >
< div v-if = "detail.data" v-loading="detail.loading" class="detail-body" >
< el -descriptions :column = "2" border >
@@ -72,7 +93,7 @@
< script >
import { ElMessage , ElMessageBox } from 'element-plus'
import { Plus , RefreshLeft , Search } from '@element-plus/icons-vue'
import { createCollectionTask , deleteCollectionTask , getCollectionTask , listCollectionTasks , resetCollectionTask } from '@/api/goauto/collection-tasks'
import { batchCancelCollectionTasks , createCollectionTask , deleteCollectionTask , getCollectionTask , listCollectionTasks , resetCollectionTask } from '@/api/goauto/collection-tasks'
import { listPddProducts } from '@/api/goauto/pdd-products'
import { listCollectionRules } from '@/api/goauto/collection-rules'
import { listDevices } from '@/api/goauto/devices'
@@ -80,13 +101,69 @@ import { createRequestId } from '@/utils/request-id'
export default {
name : 'GoAutoCollectionTasks' , setup ( ) { return { Plus , RefreshLeft , Search } } ,
data ( ) { return { loading : false , tasks : [ ] , total : 0 , query : { page : 1 , pageSize : 20 , goodsId : '' , status : '' } , statuses : [ { value : 'pending' , label : '待执行' } , { value : 'running' , label : '执行中' } , { value : 'completed' , label : '已完成' } , { value : 'completed_partial' , label : '部分完成' } , { value : 'failed' , label : '失败' } ] , createDialog : { open : false , saving : false } , form : { pddProductId : null , ruleId : null , deviceId : null } , formRules : { pddProductId : [ { required : true , message : '请选择 PDD 商品' , trigger : 'change' } ] , ruleId : [ { required : true , message : '请选择采集规则' , trigger : 'change' } ] } , options : { products : [ ] , rules : [ ] , devices : [ ] } , detail : { open : false , loading : false , data : null } } } ,
created ( ) { this . getList ( ) } ,
data ( ) {
return {
loading : false , tasks : [ ] , total : 0 , query : { page : 1 , pageSize : 20 , goodsId : '' , status : '' , source : '' } ,
statuses : [ { value : 'pending' , label : '待执行' } , { value : 'running' , label : '执行中' } , { value : 'completed' , label : '已完成' } , { value : 'completed_partial' , label : '部分完成' } , { value : 'failed' , label : '失败' } , { value : 'cancelled' , label : '已取消' } ] ,
sources : [ { value : 'admin' , label : '手动创建' } , { value : 'agent_current_page' , label : '拍照临时采集' } , { value : 'image_search' , label : '图搜采集' } ] ,
createDialog : { open : false , saving : false } , form : { pddProductId : null , ruleId : null , deviceId : null } ,
formRules : { pddProductId : [ { required : true , message : '请选择 PDD 商品' , trigger : 'change' } ] , ruleId : [ { required : true , message : '请选择采集规则' , trigger : 'change' } ] } ,
options : { products : [ ] , rules : [ ] , devices : [ ] } , detail : { open : false , loading : false , data : null } ,
pendingCancelCount : 0 ,
cancelBatch : { open : false , previewLoading : false , submitting : false , preview : [ ] , previewTotal : 0 }
}
} ,
created ( ) { this . getList ( ) ; this . refreshPendingCancelCount ( ) } ,
watch : {
// `[必须]` 取消范围必须与眼前的列表一致,三个筛选项都要联动。少了 Goods ID
// 那一维,按 goods_id 筛出两条、按钮却显示并取消三十几条——那正是当初
// 放弃「两个固定按钮」、改用「筛选 + 一个按钮」想避免的事(#298)。
'query.source' ( ) { this . refreshPendingCancelCount ( ) } ,
'query.status' ( ) { this . refreshPendingCancelCount ( ) } ,
'query.goodsId' ( ) { this . refreshPendingCancelCount ( ) }
} ,
methods : {
async getList ( ) { this . loading = true ; try { const r = await listCollectionTasks ( this . query ) ; this . tasks = r . data . items ; this . total = r . data . total } finally { this . loading = false } } ,
handleQuery ( ) { this . query . page = 1 ; this . getList ( ) } , resetQuery ( ) { this . query = { page : 1 , pageSize : 20 , goodsId : '' , status : '' } ; this . getList ( ) } ,
statusLabel ( value ) { return this . statuses . find ( item => item . value === value ) ? . label || value } , statusType ( value ) { return { pending : 'info' , running : 'warning' , completed : 'success' , completed _partial : 'warning' , failed : 'danger' } [ value ] || 'info' } ,
handleQuery ( ) { this . query . page = 1 ; this . getList ( ) } , resetQuery ( ) { this . query = { page : 1 , pageSize : 20 , goodsId : '' , status : '' , source : '' } ; this . getList ( ) } ,
statusLabel ( value ) { return this . statuses . find ( item => item . value === value ) ? . label || value } , statusType ( value ) { return { pending : 'info' , running : 'warning' , completed : 'success' , completed _partial : 'warning' , failed : 'danger' , cancelled : 'info' } [ value ] || 'info' } ,
sourceLabel ( value ) { return this . sources . find ( item => item . value === value ) ? . label || value || '' } ,
isTerminal ( value ) { return [ 'completed' , 'completed_partial' , 'failed' ] . includes ( value ) } , specsText ( specs ) { return Object . entries ( specs || { } ) . map ( ( [ key , value ] ) => ` ${ key } : ${ value } ` ) . join ( ' / ' ) } ,
// 取消范围只对「状态筛选为空或恰好是待执行」才有意义:其它状态下这个筛选
// 条件里不可能存在待执行任务,角标应该直接显示 0 并禁用按钮,而不是拿一个
// 与实际可取消数量无关的数字误导采购员。
cancelScopeStatus ( ) { return this . query . status === '' || this . query . status === 'pending' ? 'pending' : null } ,
async refreshPendingCancelCount ( ) {
const status = this . cancelScopeStatus ( )
if ( ! status ) { this . pendingCancelCount = 0 ; return }
const r = await listCollectionTasks ( { page : 1 , pageSize : 1 , status , source : this . query . source || undefined , goodsId : this . query . goodsId || undefined } )
this . pendingCancelCount = r . data . total
} ,
async openCancelBatch ( ) {
const status = this . cancelScopeStatus ( )
if ( ! status ) return
this . cancelBatch = { open : true , previewLoading : true , submitting : false , preview : [ ] , previewTotal : 0 }
try {
const r = await listCollectionTasks ( { page : 1 , pageSize : 50 , status , source : this . query . source || undefined , goodsId : this . query . goodsId || undefined } )
this . cancelBatch . preview = r . data . items
this . cancelBatch . previewTotal = r . data . total
} finally { this . cancelBatch . previewLoading = false }
} ,
async submitCancelBatch ( ) {
const status = this . cancelScopeStatus ( )
if ( ! status ) return
this . cancelBatch . submitting = true
try {
const response = await batchCancelCollectionTasks ( { source : this . query . source || undefined , status , goodsId : this . query . goodsId || undefined } )
const skippedText = response . data . skipped ? . length ? ` , ${ response . data . skipped . length } 个因执行中或已被设备领取而跳过(任务号: ${ response . data . skipped . map ( item => item . taskId ) . join ( '、' ) } ) ` : ''
ElMessage . success ( ` 已取消 ${ response . data . cancelledCount } 个任务 ${ skippedText } ` )
if ( response . data . hasMore ) {
ElMessage . warning ( '范围内还有未处理的任务,请再次执行' )
}
this . cancelBatch . open = false
await this . getList ( )
await this . refreshPendingCancelCount ( )
} finally { this . cancelBatch . submitting = false }
} ,
formatRuleSnapshot ( value ) { try { return JSON . stringify ( typeof value === 'string' ? JSON . parse ( value ) : value , null , 2 ) } catch ( _ ) { return String ( value || '' ) } } ,
async openCreate ( ) { this . form = { pddProductId : null , ruleId : null , deviceId : null } ; this . createDialog . open = true ; const [ p , r , d ] = await Promise . all ( [ listPddProducts ( { page : 1 , pageSize : 100 } ) , listCollectionRules ( { page : 1 , pageSize : 100 } ) , listDevices ( { page : 1 , pageSize : 100 } ) ] ) ; this . options = { products : p . data . items , rules : r . data . items , devices : d . data . items } ; this . $nextTick ( ( ) => this . $refs . taskForm ? . clearValidate ( ) ) } ,
async createTask ( ) { if ( ! await this . $refs . taskForm . validate ( ) . catch ( ( ) => false ) ) return ; this . createDialog . saving = true ; try { await createCollectionTask ( { requestId : createRequestId ( ) , pddProductId : this . form . pddProductId , ruleId : this . form . ruleId , deviceId : this . form . deviceId || null } ) ; ElMessage . success ( '任务已创建' ) ; this . createDialog . open = false ; await this . getList ( ) } finally { this . createDialog . saving = false } } ,