TaskManager.vue 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. <template>
  2. <div class="task-manager">
  3. <el-card shadow="never">
  4. <template #header>
  5. <div class="card-header">
  6. <span class="title">任务管理</span>
  7. <div>
  8. <el-button size="small" @click="loadTasks" :loading="loading">
  9. <el-icon><Refresh /></el-icon> 刷新
  10. </el-button>
  11. <el-button size="small" type="primary" @click="showCreate = true">
  12. <el-icon><Plus /></el-icon> 创建任务
  13. </el-button>
  14. </div>
  15. </div>
  16. </template>
  17. <el-table :data="tasks" border v-loading="loading" size="small">
  18. <el-table-column prop="task_id" label="ID" width="100" />
  19. <el-table-column prop="task_name" label="任务名称" min-width="150" />
  20. <el-table-column label="状态" width="100">
  21. <template #default="{ row }">
  22. <el-tag :type="statusType(row.status)" size="small">{{ statusLabel(row.status) }}</el-tag>
  23. </template>
  24. </el-table-column>
  25. <el-table-column label="进度" width="180">
  26. <template #default="{ row }">
  27. <el-progress :percentage="row.progress" :status="row.status === 'completed' ? 'success' : ''" :stroke-width="8" />
  28. <span style="font-size: 12px; color: #909399;">{{ row.completed_points }}/{{ row.total_points }}</span>
  29. </template>
  30. </el-table-column>
  31. <el-table-column prop="priority" label="优先级" width="80" />
  32. <el-table-column prop="created_at" label="创建时间" width="170">
  33. <template #default="{ row }">{{ formatTime(row.created_at) }}</template>
  34. </el-table-column>
  35. <el-table-column label="操作" width="200" fixed="right">
  36. <template #default="{ row }">
  37. <el-button size="small" link type="primary" @click="viewTask(row)">详情</el-button>
  38. <el-button v-if="row.status === 'pending'" size="small" link type="success" @click="dispatchTask(row)">下发</el-button>
  39. <el-button v-if="['pending','dispatched','running'].includes(row.status)" size="small" link type="danger" @click="cancelTask(row)">取消</el-button>
  40. <el-button v-if="row.status === 'completed'" size="small" link @click="downloadResults(row)">下载结果</el-button>
  41. </template>
  42. </el-table-column>
  43. </el-table>
  44. </el-card>
  45. <el-dialog v-model="showCreate" title="创建仿真任务" width="600px">
  46. <el-form :model="createForm" label-width="120px" size="default">
  47. <el-form-item label="任务名称">
  48. <el-input v-model="createForm.task_name" placeholder="可选,默认自动生成" />
  49. </el-form-item>
  50. <el-form-item label="关联方案ID">
  51. <el-input-number v-model="createForm.plan_id" :min="1" controls-position="right" />
  52. </el-form-item>
  53. <el-form-item label="优先级">
  54. <el-slider v-model="createForm.priority" :min="1" :max="10" show-input />
  55. </el-form-item>
  56. <el-form-item label="模型数据">
  57. <el-input v-model="createForm.plan_data_json" type="textarea" :rows="4" placeholder='JSON格式的方案数据' />
  58. </el-form-item>
  59. <el-form-item label="参数列表">
  60. <el-input v-model="createForm.parameters_json" type="textarea" :rows="4" placeholder='JSON数组,如 [{"airgap_mm":1.0},...]' />
  61. </el-form-item>
  62. </el-form>
  63. <template #footer>
  64. <el-button @click="showCreate = false">取消</el-button>
  65. <el-button type="primary" :loading="creating" @click="doCreate">创建</el-button>
  66. </template>
  67. </el-dialog>
  68. <el-drawer v-model="showDetail" title="任务详情" size="50%">
  69. <div v-if="currentTask">
  70. <el-descriptions :column="2" border size="small">
  71. <el-descriptions-item label="任务ID">{{ currentTask.task_id }}</el-descriptions-item>
  72. <el-descriptions-item label="名称">{{ currentTask.task_name }}</el-descriptions-item>
  73. <el-descriptions-item label="状态">
  74. <el-tag :type="statusType(currentTask.status)" size="small">{{ statusLabel(currentTask.status) }}</el-tag>
  75. </el-descriptions-item>
  76. <el-descriptions-item label="优先级">{{ currentTask.priority }}</el-descriptions-item>
  77. <el-descriptions-item label="总点数">{{ currentTask.total_points }}</el-descriptions-item>
  78. <el-descriptions-item label="已完成">{{ currentTask.completed_points }}</el-descriptions-item>
  79. <el-descriptions-item label="创建时间" :span="2">{{ formatTime(currentTask.created_at) }}</el-descriptions-item>
  80. </el-descriptions>
  81. <el-progress :percentage="currentTask.progress" style="margin: 20px 0;" :stroke-width="12" />
  82. <el-divider v-if="currentTask.progress_data" content-position="left">实时进度</el-divider>
  83. <el-descriptions v-if="currentTask.progress_data" :column="2" border size="small">
  84. <el-descriptions-item label="当前点">{{ currentTask.progress_data.current_point }}</el-descriptions-item>
  85. <el-descriptions-item label="已用时间">{{ currentTask.progress_data.elapsed_time?.toFixed(1) }}s</el-descriptions-item>
  86. </el-descriptions>
  87. <el-divider v-if="currentTask.result_metrics" content-position="left">结果统计</el-divider>
  88. <el-descriptions v-if="currentTask.result_metrics" :column="2" border size="small">
  89. <el-descriptions-item v-for="(v, k) in currentTask.result_metrics" :key="k" :label="k">{{ v }}</el-descriptions-item>
  90. </el-descriptions>
  91. <el-divider content-position="left">操作</el-divider>
  92. <div class="detail-actions">
  93. <el-button v-if="currentTask.status === 'pending'" type="success" @click="dispatchTask(currentTask)">下发任务</el-button>
  94. <el-button v-if="currentTask.status === 'completed'" type="primary" @click="downloadResults(currentTask)">下载结果JSON</el-button>
  95. <el-button @click="loadTasks">刷新状态</el-button>
  96. </div>
  97. </div>
  98. </el-drawer>
  99. </div>
  100. </template>
  101. <script setup lang="ts">
  102. import { ref, reactive, onMounted } from 'vue'
  103. import { ElMessage, ElMessageBox } from 'element-plus'
  104. import { Refresh, Plus } from '@element-plus/icons-vue'
  105. import api from '@/api'
  106. const tasks = ref<any[]>([])
  107. const loading = ref(false)
  108. const showCreate = ref(false)
  109. const showDetail = ref(false)
  110. const currentTask = ref<any>(null)
  111. const creating = ref(false)
  112. const createForm = reactive({
  113. task_name: '',
  114. plan_id: null as number | null,
  115. priority: 5,
  116. plan_data_json: '{}',
  117. parameters_json: '[]',
  118. })
  119. const statusType = (s: string) => {
  120. const map: Record<string, string> = {
  121. pending: 'info', dispatched: 'warning', running: 'primary',
  122. completed: 'success', failed: 'danger', cancelled: 'info',
  123. }
  124. return map[s] || 'info'
  125. }
  126. const statusLabel = (s: string) => {
  127. const map: Record<string, string> = {
  128. pending: '待下发', dispatched: '已下发', running: '运行中',
  129. completed: '已完成', failed: '失败', cancelled: '已取消',
  130. }
  131. return map[s] || s
  132. }
  133. const formatTime = (t: string) => t ? new Date(t).toLocaleString('zh-CN') : '-'
  134. const loadTasks = async () => {
  135. loading.value = true
  136. try {
  137. const res: any = await api.get('/tasks', { params: { limit: 50 } })
  138. tasks.value = res.tasks || []
  139. } catch (e: any) {
  140. ElMessage.error('加载任务列表失败: ' + (e.message || e))
  141. } finally {
  142. loading.value = false
  143. }
  144. }
  145. const doCreate = async () => {
  146. let plan_data: any, parameters: any[]
  147. try { plan_data = JSON.parse(createForm.plan_data_json) } catch { ElMessage.error('模型数据不是有效JSON'); return }
  148. try { parameters = JSON.parse(createForm.parameters_json) } catch { ElMessage.error('参数列表不是有效JSON'); return }
  149. if (!Array.isArray(parameters) || !parameters.length) { ElMessage.error('参数列表不能为空'); return }
  150. creating.value = true
  151. try {
  152. await api.post('/tasks', {
  153. task_name: createForm.task_name || undefined,
  154. plan_id: createForm.plan_id,
  155. priority: createForm.priority,
  156. plan_data, parameters,
  157. })
  158. ElMessage.success('任务创建成功')
  159. showCreate.value = false
  160. loadTasks()
  161. } catch (e: any) {
  162. ElMessage.error('创建失败: ' + (e.message || e))
  163. } finally {
  164. creating.value = false
  165. }
  166. }
  167. const dispatchTask = async (row: any) => {
  168. try {
  169. await api.post(`/tasks/${row.task_id}/dispatch`)
  170. ElMessage.success('任务已下发')
  171. loadTasks()
  172. } catch (e: any) {
  173. ElMessage.error('下发失败: ' + (e.message || e))
  174. }
  175. }
  176. const cancelTask = async (row: any) => {
  177. try {
  178. await ElMessageBox.confirm(`确定取消任务 "${row.task_name}"?`, '确认', { type: 'warning' })
  179. await api.post(`/tasks/${row.task_id}/cancel`)
  180. ElMessage.success('任务已取消')
  181. loadTasks()
  182. } catch { /* cancelled */ }
  183. }
  184. const viewTask = (row: any) => {
  185. currentTask.value = row
  186. showDetail.value = true
  187. }
  188. const downloadResults = (row: any) => {
  189. window.open(`/api/tasks/${row.task_id}/download`, '_blank')
  190. }
  191. onMounted(() => loadTasks())
  192. </script>
  193. <style scoped>
  194. .task-manager { padding: 20px; }
  195. .card-header { display: flex; justify-content: space-between; align-items: center; }
  196. .title { font-weight: 600; font-size: 16px; }
  197. .detail-actions { display: flex; gap: 10px; flex-wrap: wrap; }
  198. </style>