diff --git a/src/components/pages/BatchJobManager.vue b/src/components/pages/BatchJobManager.vue
index deb2810..dd64868 100644
--- a/src/components/pages/BatchJobManager.vue
+++ b/src/components/pages/BatchJobManager.vue
@@ -18,28 +18,28 @@
-
0
+
{{ jobs.filter(j => j.status === 'running').length }}
运行中任务
-
0
+
{{ jobs.filter(j => ['completed', 'completed_with_errors'].includes(j.status)).length }}
已完成
-
0
+
{{ jobs.filter(j => j.status === 'pending').length }}
排队中
-
0
+
{{ jobs.filter(j => j.status === 'failed').length }}
失败/异常
@@ -52,9 +52,9 @@
+
-
-
+
@@ -114,8 +114,8 @@
- 取消批次
- 详情与报告
+ 取消批次
+ 详情与报告
@@ -202,15 +202,16 @@
-
-
- 粗糙 (Coarse)
- 中等 (Medium)
- 精细 (Fine)
-
+
+
+
+
+
+
+
-
-
+
+
@@ -279,7 +280,7 @@ const cadSettings = ref({
},
revit: {
strategy: 'shell_execute',
- params: { lod: '粗糙', removeInternal: true }
+ params: { mode: 'EnvelopeOnly', backupOriginal: false }
},
pdms: {
strategy: 'pdms_simplify',
@@ -306,7 +307,7 @@ const detectedCadTypes = computed(() => {
const newModelPath = ref('')
-// 模拟列表数据 - 配合后端的 queued -> dispatching -> waiting_callback -> succeeded/failed 状态
+// 队列数据及过滤
const jobs = ref([])
const filteredJobs = computed(() => {
@@ -315,15 +316,129 @@ const filteredJobs = computed(() => {
})
// === WebSocket 模拟与生命周期 ===
-// 这里为后续真实接入 WebSocket 通道(submit_batch_tasks / batch_item_update)留出框架
+const activeBatchIds = new Set()
+let pollTimer = null
+
+const handleWsMessage = (msg) => {
+ if (msg.type === 'batch_created' || msg.type === 'batch_completed') {
+ if (msg.data && msg.data.batch) {
+ updateBatchInJobs(msg.data.batch, msg.data.items || [])
+ }
+ } else if (msg.type === 'batch_item_update') {
+ if (msg.data && msg.data.batch && msg.data.item) {
+ mergeBatchItemUpdate(msg.data.batch, msg.data.item)
+ }
+ } else if (msg.type === 'info' && msg.data && msg.data.batch) {
+ // Check if backend uses generic info or batch_status_response for updates
+ updateBatchInJobs(msg.data.batch, msg.data.items || [])
+ }
+}
+
+const isTerminalBatchStatus = (status) => {
+ return status === 'completed' || status === 'completed_with_errors' || status === 'failed'
+}
+
+const mapItemData = (it) => {
+ return {
+ id: it.id,
+ modelPath: it.model_path,
+ taskType: it.task_type,
+ status: it.status,
+ retry: `${it.retry_count || 0}/${it.max_retries || 1}`,
+ errorMessage: it.error_message || '',
+ finishedAt: it.finished_at || it.started_at || it.created_at || '-'
+ }
+}
+
+const updateBatchInJobs = (batchData, itemsData) => {
+ const existingJob = jobs.value.find(j => j.batchId === batchData.id)
+
+ const mappedItems = itemsData.map(mapItemData).sort((a,b) => {
+ const aItem = itemsData.find(i => i.id === a.id)
+ const bItem = itemsData.find(i => i.id === b.id)
+ return (aItem?.sequence || 0) - (bItem?.sequence || 0)
+ })
+
+ const progressCalc = batchData.total_count ? Math.round(((batchData.succeeded_count + batchData.failed_count) / batchData.total_count) * 100) : 0
+
+ if (existingJob) {
+ existingJob.status = batchData.status
+ existingJob.completedCount = batchData.succeeded_count + batchData.failed_count
+ existingJob.totalCount = batchData.total_count
+ existingJob.progress = progressCalc
+ existingJob.items = mappedItems
+ } else {
+ jobs.value.unshift({
+ batchId: batchData.id,
+ strategy: batchData.metadata?.strategy || batchData.name || '批量处理',
+ priority: batchData.metadata?.priority || '普通',
+ progress: progressCalc,
+ status: batchData.status,
+ totalCount: batchData.total_count || 0,
+ completedCount: (batchData.succeeded_count || 0) + (batchData.failed_count || 0),
+ createdAt: new Date(batchData.created_at).toLocaleString(),
+ items: mappedItems,
+ rawBatch: batchData
+ })
+ }
+
+ managePolling(batchData)
+}
+
+const mergeBatchItemUpdate = (batchData, itemData) => {
+ const existingJob = jobs.value.find(j => j.batchId === batchData.id)
+ if (!existingJob) {
+ // 如果还没记录,可能错过了 batch_created,进行一次获取全量状态
+ websocketService.send({ type: 'get_batch_status', batch_id: batchData.id })
+ return
+ }
+
+ existingJob.status = batchData.status
+ existingJob.completedCount = batchData.succeeded_count + batchData.failed_count
+ existingJob.totalCount = batchData.total_count
+ existingJob.progress = batchData.total_count ? Math.round((existingJob.completedCount / batchData.total_count) * 100) : 0
+
+ const mappedItem = mapItemData(itemData)
+ const itemIndex = existingJob.items.findIndex(it => it.id === itemData.id)
+
+ if (itemIndex > -1) {
+ existingJob.items.splice(itemIndex, 1, mappedItem)
+ } else {
+ existingJob.items.push(mappedItem)
+ }
+
+ managePolling(batchData)
+}
+
+const managePolling = (batchData) => {
+ if (!isTerminalBatchStatus(batchData.status)) {
+ activeBatchIds.add(batchData.id)
+ } else {
+ activeBatchIds.delete(batchData.id)
+ }
+
+ if (activeBatchIds.size > 0 && !pollTimer) {
+ pollTimer = window.setInterval(() => {
+ activeBatchIds.forEach(id => {
+ websocketService.send({ type: 'get_batch_status', batch_id: id })
+ })
+ }, 4000)
+ } else if (activeBatchIds.size === 0 && pollTimer) {
+ window.clearInterval(pollTimer)
+ pollTimer = null
+ }
+}
+
onMounted(() => {
- console.log('BatchJobManager mounted')
- // 如果需要监听事件,可以在这里 websocketService.on(...)
+ websocketService.on('onMessage', handleWsMessage)
})
onUnmounted(() => {
- console.log('BatchJobManager unmounted')
- // websocketService.off(...)
+ websocketService.off('onMessage', handleWsMessage)
+ if (pollTimer) {
+ window.clearInterval(pollTimer)
+ pollTimer = null
+ }
})
// 向导控制
@@ -340,7 +455,7 @@ const openWizard = () => {
},
revit: {
strategy: 'shell_execute',
- params: { lod: '粗糙', removeInternal: true }
+ params: { mode: 'EnvelopeOnly', backupOriginal: false }
},
pdms: {
strategy: 'pdms_simplify',
@@ -354,7 +469,7 @@ const onStrategyChange = (cadType, val) => {
if (val === 'creo_shrinkwrap') {
cadSettings.value[cadType].params = { quality: 5, exportFormat: 'step', ignoreSkeletons: true, ignoreQuilts: false, fillHoles: false }
} else if (val === 'shell_execute') {
- cadSettings.value[cadType].params = { lod: '粗糙', removeInternal: true }
+ cadSettings.value[cadType].params = { mode: 'EnvelopeOnly', backupOriginal: false }
} else if (val.includes('pdms')) {
cadSettings.value[cadType].params = { levelFilter: '', removeAttachments: true }
} else {
@@ -369,7 +484,8 @@ const submitTask = () => {
return
}
- const batchId = `B-${Date.now().toString().slice(-8)}`
+ const batchName = `B-${Date.now().toString().slice(-8)}`
+ const strategiesUsed = detectedCadTypes.value.map(t => cadSettings.value[t].strategy).join(', ') || '混合组合策略'
// 组装并发送 submit_batch_tasks WebSocket 消息给后端
const items = taskForm.value.modelPaths.map(p => {
@@ -380,43 +496,31 @@ const submitTask = () => {
strategy = cadSettings.value[type].strategy;
params = cadSettings.value[type].params;
}
+
+ // 如果是Revit,并且没有特定参数,按接入文档建议
+ if (type === 'revit' && (!strategy || strategy === 'revit_shell')) {
+ strategy = 'shell_execute';
+ }
+
return {
model_path: p,
task_type: strategy,
- params: params
+ task_params: params // 注意:文档要求 task_params 而不是 params
}
})
websocketService.send({
type: 'submit_batch_tasks',
- batch_name: batchId,
+ batch_name: batchName,
+ metadata: {
+ strategy: strategiesUsed,
+ priority: taskForm.value.priority
+ },
items: items
})
- // Display logic config
- const strategiesUsed = detectedCadTypes.value.map(t => cadSettings.value[t].strategy).join(', ') || '混合组合策略'
-
- ElMessage.success('批量任务已成功提交至全局串行队列!')
+ ElMessage.success('批量任务已成功提交至全局串行队列,等待状态同步...')
wizardVisible.value = false
-
- // 将新任务加入前端列表 (未来应该通过后端 batch_created 事件推送进行更新,目前暂留以显示即时反馈)
- jobs.value.unshift({
- batchId: batchId,
- strategy: strategiesUsed,
- priority: taskForm.value.priority,
- params: { }, // mix params
- progress: 0,
- status: 'queued',
- totalCount: taskForm.value.modelPaths.length,
- completedCount: 0,
- createdAt: new Date().toLocaleString(),
- items: taskForm.value.modelPaths.map(p => ({
- modelPath: p,
- status: 'queued',
- errorMessage: '',
- finishedAt: '-'
- }))
- })
}
const addModelPath = () => {
@@ -425,7 +529,7 @@ const addModelPath = () => {
// 按照路由规范,增加简单的扩展名限制校验
const lowerPath = path.toLowerCase();
- const validExtensions = ['.prt', '.asm', '.rvt', '.rfa', '.ifc', 'sam', 'mdb']
+ const validExtensions = ['.prt', '.asm', '.rvt', '.rfa', '.ifc', '.sam', '.mdb']
const isValid = validExtensions.some(ext => lowerPath.endsWith(ext) || lowerPath.includes(ext))
if (!isValid) {
@@ -447,11 +551,11 @@ const removeModelPath = (index) => {
// 格式辅助
const getStatusLabel = (status) => {
- const map = { running: '执行中', succeeded: '已完成', queued: '排队中', failed: '失败/异常' }
+ const map = { pending: '待执行', running: '执行中', completed: '已完成', completed_with_errors: '部分失败', queued: '排队中', failed: '失败/异常' }
return map[status] || status
}
const getStatusType = (status) => {
- const map = { running: 'primary', succeeded: 'success', queued: 'info', failed: 'danger' }
+ const map = { pending: 'info', running: 'primary', completed: 'success', completed_with_errors: 'warning', queued: 'info', failed: 'danger' }
return map[status] || 'info'
}
const getPriorityType = (priority) => {
@@ -459,7 +563,8 @@ const getPriorityType = (priority) => {
return map[priority] || 'info'
}
const getProgressStatus = (status) => {
- if (status === 'succeeded') return 'success'
+ if (status === 'completed') return 'success'
+ if (status === 'completed_with_errors') return 'warning'
if (status === 'failed') return 'exception'
return '' /* running/queued 无特殊状态效果 */
}
@@ -468,8 +573,8 @@ const getProgressStatus = (status) => {
const getItemStatusLabel = (status) => {
const map = {
queued: '排队中',
- dispatching: '正在下发',
- waiting_callback: '执行中(等待回调)',
+ dispatching: '处理中',
+ waiting_callback: '等待插件回调',
succeeded: '成功',
failed: '失败'
}