|
|
@@ -3,13 +3,13 @@
|
|
|
<el-card shadow="never">
|
|
|
<template #header>
|
|
|
<div class="card-header">
|
|
|
- <span class="title">\u4efb\u52a1\u7ba1\u7406</span>
|
|
|
+ <span class="title">任务管理</span>
|
|
|
<div>
|
|
|
<el-button size="small" @click="loadTasks" :loading="loading">
|
|
|
- <el-icon><Refresh /></el-icon> \u5237\u65b0
|
|
|
+ <el-icon><Refresh /></el-icon> 刷新
|
|
|
</el-button>
|
|
|
<el-button size="small" type="primary" @click="showCreate = true">
|
|
|
- <el-icon><Plus /></el-icon> \u521b\u5efa\u4efb\u52a1
|
|
|
+ <el-icon><Plus /></el-icon> 创建任务
|
|
|
</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -17,89 +17,89 @@
|
|
|
|
|
|
<el-table :data="tasks" border v-loading="loading" size="small">
|
|
|
<el-table-column prop="task_id" label="ID" width="100" />
|
|
|
- <el-table-column prop="task_name" label="\u4efb\u52a1\u540d\u79f0" min-width="150" />
|
|
|
- <el-table-column label="\u72b6\u6001" width="100">
|
|
|
+ <el-table-column prop="task_name" label="任务名称" min-width="150" />
|
|
|
+ <el-table-column label="状态" width="100">
|
|
|
<template #default="{ row }">
|
|
|
<el-tag :type="statusType(row.status)" size="small">{{ statusLabel(row.status) }}</el-tag>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column label="\u8fdb\u5ea6" width="180">
|
|
|
+ <el-table-column label="进度" width="180">
|
|
|
<template #default="{ row }">
|
|
|
<el-progress :percentage="row.progress" :status="row.status === 'completed' ? 'success' : ''" :stroke-width="8" />
|
|
|
<span style="font-size: 12px; color: #909399;">{{ row.completed_points }}/{{ row.total_points }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column prop="priority" label="\u4f18\u5148\u7ea7" width="80" />
|
|
|
- <el-table-column prop="created_at" label="\u521b\u5efa\u65f6\u95f4" width="170">
|
|
|
+ <el-table-column prop="priority" label="优先级" width="80" />
|
|
|
+ <el-table-column prop="created_at" label="创建时间" width="170">
|
|
|
<template #default="{ row }">{{ formatTime(row.created_at) }}</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column label="\u64cd\u4f5c" width="200" fixed="right">
|
|
|
+ <el-table-column label="操作" width="200" fixed="right">
|
|
|
<template #default="{ row }">
|
|
|
- <el-button size="small" link type="primary" @click="viewTask(row)">\u8be6\u60c5</el-button>
|
|
|
- <el-button v-if="row.status === 'pending'" size="small" link type="success" @click="dispatchTask(row)">\u4e0b\u53d1</el-button>
|
|
|
- <el-button v-if="['pending','dispatched','running'].includes(row.status)" size="small" link type="danger" @click="cancelTask(row)">\u53d6\u6d88</el-button>
|
|
|
- <el-button v-if="row.status === 'completed'" size="small" link @click="downloadResults(row)">\u4e0b\u8f7d\u7ed3\u679c</el-button>
|
|
|
+ <el-button size="small" link type="primary" @click="viewTask(row)">详情</el-button>
|
|
|
+ <el-button v-if="row.status === 'pending'" size="small" link type="success" @click="dispatchTask(row)">下发</el-button>
|
|
|
+ <el-button v-if="['pending','dispatched','running'].includes(row.status)" size="small" link type="danger" @click="cancelTask(row)">取消</el-button>
|
|
|
+ <el-button v-if="row.status === 'completed'" size="small" link @click="downloadResults(row)">下载结果</el-button>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
</el-card>
|
|
|
|
|
|
- <el-dialog v-model="showCreate" title="\u521b\u5efa\u4eff\u771f\u4efb\u52a1" width="600px">
|
|
|
+ <el-dialog v-model="showCreate" title="创建仿真任务" width="600px">
|
|
|
<el-form :model="createForm" label-width="120px" size="default">
|
|
|
- <el-form-item label="\u4efb\u52a1\u540d\u79f0">
|
|
|
- <el-input v-model="createForm.task_name" placeholder="\u53ef\u9009\uff0c\u9ed8\u8ba4\u81ea\u52a8\u751f\u6210" />
|
|
|
+ <el-form-item label="任务名称">
|
|
|
+ <el-input v-model="createForm.task_name" placeholder="可选,默认自动生成" />
|
|
|
</el-form-item>
|
|
|
- <el-form-item label="\u5173\u8054\u65b9\u6848ID">
|
|
|
+ <el-form-item label="关联方案ID">
|
|
|
<el-input-number v-model="createForm.plan_id" :min="1" controls-position="right" />
|
|
|
</el-form-item>
|
|
|
- <el-form-item label="\u4f18\u5148\u7ea7">
|
|
|
+ <el-form-item label="优先级">
|
|
|
<el-slider v-model="createForm.priority" :min="1" :max="10" show-input />
|
|
|
</el-form-item>
|
|
|
- <el-form-item label="\u6a21\u578b\u6570\u636e">
|
|
|
- <el-input v-model="createForm.plan_data_json" type="textarea" :rows="4" placeholder='JSON\u683c\u5f0f\u7684\u65b9\u6848\u6570\u636e' />
|
|
|
+ <el-form-item label="模型数据">
|
|
|
+ <el-input v-model="createForm.plan_data_json" type="textarea" :rows="4" placeholder='JSON格式的方案数据' />
|
|
|
</el-form-item>
|
|
|
- <el-form-item label="\u53c2\u6570\u5217\u8868">
|
|
|
- <el-input v-model="createForm.parameters_json" type="textarea" :rows="4" placeholder='JSON\u6570\u7ec4\uff0c\u5982 [{"airgap_mm":1.0},...]' />
|
|
|
+ <el-form-item label="参数列表">
|
|
|
+ <el-input v-model="createForm.parameters_json" type="textarea" :rows="4" placeholder='JSON数组,如 [{"airgap_mm":1.0},...]' />
|
|
|
</el-form-item>
|
|
|
</el-form>
|
|
|
<template #footer>
|
|
|
- <el-button @click="showCreate = false">\u53d6\u6d88</el-button>
|
|
|
- <el-button type="primary" :loading="creating" @click="doCreate">\u521b\u5efa</el-button>
|
|
|
+ <el-button @click="showCreate = false">取消</el-button>
|
|
|
+ <el-button type="primary" :loading="creating" @click="doCreate">创建</el-button>
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
|
|
|
- <el-drawer v-model="showDetail" title="\u4efb\u52a1\u8be6\u60c5" size="50%">
|
|
|
+ <el-drawer v-model="showDetail" title="任务详情" size="50%">
|
|
|
<div v-if="currentTask">
|
|
|
<el-descriptions :column="2" border size="small">
|
|
|
- <el-descriptions-item label="\u4efb\u52a1ID">{{ currentTask.task_id }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="\u540d\u79f0">{{ currentTask.task_name }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="\u72b6\u6001">
|
|
|
+ <el-descriptions-item label="任务ID">{{ currentTask.task_id }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="名称">{{ currentTask.task_name }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="状态">
|
|
|
<el-tag :type="statusType(currentTask.status)" size="small">{{ statusLabel(currentTask.status) }}</el-tag>
|
|
|
</el-descriptions-item>
|
|
|
- <el-descriptions-item label="\u4f18\u5148\u7ea7">{{ currentTask.priority }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="\u603b\u70b9\u6570">{{ currentTask.total_points }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="\u5df2\u5b8c\u6210">{{ currentTask.completed_points }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="\u521b\u5efa\u65f6\u95f4" :span="2">{{ formatTime(currentTask.created_at) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="优先级">{{ currentTask.priority }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="总点数">{{ currentTask.total_points }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="已完成">{{ currentTask.completed_points }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="创建时间" :span="2">{{ formatTime(currentTask.created_at) }}</el-descriptions-item>
|
|
|
</el-descriptions>
|
|
|
|
|
|
<el-progress :percentage="currentTask.progress" style="margin: 20px 0;" :stroke-width="12" />
|
|
|
|
|
|
- <el-divider v-if="currentTask.progress_data" content-position="left">\u5b9e\u65f6\u8fdb\u5ea6</el-divider>
|
|
|
+ <el-divider v-if="currentTask.progress_data" content-position="left">实时进度</el-divider>
|
|
|
<el-descriptions v-if="currentTask.progress_data" :column="2" border size="small">
|
|
|
- <el-descriptions-item label="\u5f53\u524d\u70b9">{{ currentTask.progress_data.current_point }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="\u5df2\u7528\u65f6\u95f4">{{ currentTask.progress_data.elapsed_time?.toFixed(1) }}s</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="当前点">{{ currentTask.progress_data.current_point }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="已用时间">{{ currentTask.progress_data.elapsed_time?.toFixed(1) }}s</el-descriptions-item>
|
|
|
</el-descriptions>
|
|
|
|
|
|
- <el-divider v-if="currentTask.result_metrics" content-position="left">\u7ed3\u679c\u7edf\u8ba1</el-divider>
|
|
|
+ <el-divider v-if="currentTask.result_metrics" content-position="left">结果统计</el-divider>
|
|
|
<el-descriptions v-if="currentTask.result_metrics" :column="2" border size="small">
|
|
|
<el-descriptions-item v-for="(v, k) in currentTask.result_metrics" :key="k" :label="k">{{ v }}</el-descriptions-item>
|
|
|
</el-descriptions>
|
|
|
|
|
|
- <el-divider content-position="left">\u64cd\u4f5c</el-divider>
|
|
|
+ <el-divider content-position="left">操作</el-divider>
|
|
|
<div class="detail-actions">
|
|
|
- <el-button v-if="currentTask.status === 'pending'" type="success" @click="dispatchTask(currentTask)">\u4e0b\u53d1\u4efb\u52a1</el-button>
|
|
|
- <el-button v-if="currentTask.status === 'completed'" type="primary" @click="downloadResults(currentTask)">\u4e0b\u8f7d\u7ed3\u679cJSON</el-button>
|
|
|
- <el-button @click="loadTasks">\u5237\u65b0\u72b6\u6001</el-button>
|
|
|
+ <el-button v-if="currentTask.status === 'pending'" type="success" @click="dispatchTask(currentTask)">下发任务</el-button>
|
|
|
+ <el-button v-if="currentTask.status === 'completed'" type="primary" @click="downloadResults(currentTask)">下载结果JSON</el-button>
|
|
|
+ <el-button @click="loadTasks">刷新状态</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</el-drawer>
|
|
|
@@ -136,8 +136,8 @@ const statusType = (s: string) => {
|
|
|
}
|
|
|
const statusLabel = (s: string) => {
|
|
|
const map: Record<string, string> = {
|
|
|
- pending: '\u5f85\u4e0b\u53d1', dispatched: '\u5df2\u4e0b\u53d1', running: '\u8fd0\u884c\u4e2d',
|
|
|
- completed: '\u5df2\u5b8c\u6210', failed: '\u5931\u8d25', cancelled: '\u5df2\u53d6\u6d88',
|
|
|
+ pending: '待下发', dispatched: '已下发', running: '运行中',
|
|
|
+ completed: '已完成', failed: '失败', cancelled: '已取消',
|
|
|
}
|
|
|
return map[s] || s
|
|
|
}
|
|
|
@@ -149,7 +149,7 @@ const loadTasks = async () => {
|
|
|
const res: any = await api.get('/tasks', { params: { limit: 50 } })
|
|
|
tasks.value = res.tasks || []
|
|
|
} catch (e: any) {
|
|
|
- ElMessage.error('\u52a0\u8f7d\u4efb\u52a1\u5217\u8868\u5931\u8d25: ' + (e.message || e))
|
|
|
+ ElMessage.error('加载任务列表失败: ' + (e.message || e))
|
|
|
} finally {
|
|
|
loading.value = false
|
|
|
}
|
|
|
@@ -157,9 +157,9 @@ const loadTasks = async () => {
|
|
|
|
|
|
const doCreate = async () => {
|
|
|
let plan_data: any, parameters: any[]
|
|
|
- try { plan_data = JSON.parse(createForm.plan_data_json) } catch { ElMessage.error('\u6a21\u578b\u6570\u636e\u4e0d\u662f\u6709\u6548JSON'); return }
|
|
|
- try { parameters = JSON.parse(createForm.parameters_json) } catch { ElMessage.error('\u53c2\u6570\u5217\u8868\u4e0d\u662f\u6709\u6548JSON'); return }
|
|
|
- if (!Array.isArray(parameters) || !parameters.length) { ElMessage.error('\u53c2\u6570\u5217\u8868\u4e0d\u80fd\u4e3a\u7a7a'); return }
|
|
|
+ try { plan_data = JSON.parse(createForm.plan_data_json) } catch { ElMessage.error('模型数据不是有效JSON'); return }
|
|
|
+ try { parameters = JSON.parse(createForm.parameters_json) } catch { ElMessage.error('参数列表不是有效JSON'); return }
|
|
|
+ if (!Array.isArray(parameters) || !parameters.length) { ElMessage.error('参数列表不能为空'); return }
|
|
|
|
|
|
creating.value = true
|
|
|
try {
|
|
|
@@ -169,11 +169,11 @@ const doCreate = async () => {
|
|
|
priority: createForm.priority,
|
|
|
plan_data, parameters,
|
|
|
})
|
|
|
- ElMessage.success('\u4efb\u52a1\u521b\u5efa\u6210\u529f')
|
|
|
+ ElMessage.success('任务创建成功')
|
|
|
showCreate.value = false
|
|
|
loadTasks()
|
|
|
} catch (e: any) {
|
|
|
- ElMessage.error('\u521b\u5efa\u5931\u8d25: ' + (e.message || e))
|
|
|
+ ElMessage.error('创建失败: ' + (e.message || e))
|
|
|
} finally {
|
|
|
creating.value = false
|
|
|
}
|
|
|
@@ -182,18 +182,18 @@ const doCreate = async () => {
|
|
|
const dispatchTask = async (row: any) => {
|
|
|
try {
|
|
|
await api.post(`/tasks/${row.task_id}/dispatch`)
|
|
|
- ElMessage.success('\u4efb\u52a1\u5df2\u4e0b\u53d1')
|
|
|
+ ElMessage.success('任务已下发')
|
|
|
loadTasks()
|
|
|
} catch (e: any) {
|
|
|
- ElMessage.error('\u4e0b\u53d1\u5931\u8d25: ' + (e.message || e))
|
|
|
+ ElMessage.error('下发失败: ' + (e.message || e))
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const cancelTask = async (row: any) => {
|
|
|
try {
|
|
|
- await ElMessageBox.confirm(`\u786e\u5b9a\u53d6\u6d88\u4efb\u52a1 "${row.task_name}"?`, '\u786e\u8ba4', { type: 'warning' })
|
|
|
+ await ElMessageBox.confirm(`确定取消任务 "${row.task_name}"?`, '确认', { type: 'warning' })
|
|
|
await api.post(`/tasks/${row.task_id}/cancel`)
|
|
|
- ElMessage.success('\u4efb\u52a1\u5df2\u53d6\u6d88')
|
|
|
+ ElMessage.success('任务已取消')
|
|
|
loadTasks()
|
|
|
} catch { /* cancelled */ }
|
|
|
}
|