| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- <template>
- <div class="task-manager">
- <el-card shadow="never">
- <template #header>
- <div class="card-header">
- <span class="title">任务管理</span>
- <div>
- <el-button size="small" @click="loadTasks" :loading="loading">
- <el-icon><Refresh /></el-icon> 刷新
- </el-button>
- <el-button size="small" type="primary" @click="showCreate = true">
- <el-icon><Plus /></el-icon> 创建任务
- </el-button>
- </div>
- </div>
- </template>
- <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="任务名称" 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="进度" 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="优先级" 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="操作" width="200" fixed="right">
- <template #default="{ row }">
- <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="创建仿真任务" width="600px">
- <el-form :model="createForm" label-width="120px" size="default">
- <el-form-item label="任务名称">
- <el-input v-model="createForm.task_name" placeholder="可选,默认自动生成" />
- </el-form-item>
- <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="优先级">
- <el-slider v-model="createForm.priority" :min="1" :max="10" show-input />
- </el-form-item>
- <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="参数列表">
- <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">取消</el-button>
- <el-button type="primary" :loading="creating" @click="doCreate">创建</el-button>
- </template>
- </el-dialog>
- <el-drawer v-model="showDetail" title="任务详情" size="50%">
- <div v-if="currentTask">
- <el-descriptions :column="2" border size="small">
- <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="优先级">{{ 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">实时进度</el-divider>
- <el-descriptions v-if="currentTask.progress_data" :column="2" border size="small">
- <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">结果统计</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">操作</el-divider>
- <div class="detail-actions">
- <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>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, reactive, onMounted } from 'vue'
- import { ElMessage, ElMessageBox } from 'element-plus'
- import { Refresh, Plus } from '@element-plus/icons-vue'
- import api from '@/api'
- const tasks = ref<any[]>([])
- const loading = ref(false)
- const showCreate = ref(false)
- const showDetail = ref(false)
- const currentTask = ref<any>(null)
- const creating = ref(false)
- const createForm = reactive({
- task_name: '',
- plan_id: null as number | null,
- priority: 5,
- plan_data_json: '{}',
- parameters_json: '[]',
- })
- const statusType = (s: string) => {
- const map: Record<string, string> = {
- pending: 'info', dispatched: 'warning', running: 'primary',
- completed: 'success', failed: 'danger', cancelled: 'info',
- }
- return map[s] || 'info'
- }
- const statusLabel = (s: string) => {
- const map: Record<string, string> = {
- pending: '待下发', dispatched: '已下发', running: '运行中',
- completed: '已完成', failed: '失败', cancelled: '已取消',
- }
- return map[s] || s
- }
- const formatTime = (t: string) => t ? new Date(t).toLocaleString('zh-CN') : '-'
- const loadTasks = async () => {
- loading.value = true
- try {
- const res: any = await api.get('/tasks', { params: { limit: 50 } })
- tasks.value = res.tasks || []
- } catch (e: any) {
- ElMessage.error('加载任务列表失败: ' + (e.message || e))
- } finally {
- loading.value = false
- }
- }
- const doCreate = async () => {
- let plan_data: any, parameters: any[]
- 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 {
- await api.post('/tasks', {
- task_name: createForm.task_name || undefined,
- plan_id: createForm.plan_id,
- priority: createForm.priority,
- plan_data, parameters,
- })
- ElMessage.success('任务创建成功')
- showCreate.value = false
- loadTasks()
- } catch (e: any) {
- ElMessage.error('创建失败: ' + (e.message || e))
- } finally {
- creating.value = false
- }
- }
- const dispatchTask = async (row: any) => {
- try {
- await api.post(`/tasks/${row.task_id}/dispatch`)
- ElMessage.success('任务已下发')
- loadTasks()
- } catch (e: any) {
- ElMessage.error('下发失败: ' + (e.message || e))
- }
- }
- const cancelTask = async (row: any) => {
- try {
- await ElMessageBox.confirm(`确定取消任务 "${row.task_name}"?`, '确认', { type: 'warning' })
- await api.post(`/tasks/${row.task_id}/cancel`)
- ElMessage.success('任务已取消')
- loadTasks()
- } catch { /* cancelled */ }
- }
- const viewTask = (row: any) => {
- currentTask.value = row
- showDetail.value = true
- }
- const downloadResults = (row: any) => {
- window.open(`/api/tasks/${row.task_id}/download`, '_blank')
- }
- onMounted(() => loadTasks())
- </script>
- <style scoped>
- .task-manager { padding: 20px; }
- .card-header { display: flex; justify-content: space-between; align-items: center; }
- .title { font-weight: 600; font-size: 16px; }
- .detail-actions { display: flex; gap: 10px; flex-wrap: wrap; }
- </style>
|