Scenarioediting.vue 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936
  1. <template>
  2. <div>
  3. <DarkLayout :query-form="queryForm">
  4. <!-- 查询表单插槽 -->
  5. <template #query-form>
  6. <el-form-item label="推演任务名称">
  7. <el-input
  8. v-model="queryForm.simulationName"
  9. placeholder="请输入推演任务名称"
  10. clearable
  11. />
  12. </el-form-item>
  13. </template>
  14. <!-- Header右侧操作按钮 -->
  15. <template #header-actions>
  16. <el-button icon="el-icon-search" type="primary" @click="handleQuery">
  17. 查询
  18. </el-button>
  19. <el-button
  20. @click="openAddDialog"
  21. icon="el-icon-plus"
  22. class="blue-btn"
  23. >
  24. 添加
  25. </el-button>
  26. </template>
  27. <!-- 主要内容 -->
  28. <template #main>
  29. <ScenarioeditingCard
  30. v-if="planList.length > 0"
  31. v-for="plan in planList"
  32. :key="plan.id + '-' + plan.createTime"
  33. :plan="plan"
  34. @edit="openExpectedEditDialog(plan)"
  35. @view-detail="viewDetails(plan)"
  36. />
  37. <el-empty v-else description="暂无方案数据" />
  38. </template>
  39. <!-- 底部右侧分页 -->
  40. <template #footer-actions>
  41. <el-pagination
  42. @size-change="handleSizeChange"
  43. @current-change="handleCurrentChange"
  44. :current-page="queryForm.pageNo"
  45. :page-sizes="[10, 20, 50, 100]"
  46. :page-size="queryForm.pageSize"
  47. layout="total, sizes, prev, pager, next, jumper"
  48. :total="total"
  49. />
  50. </template>
  51. </DarkLayout>
  52. <!-- 任务详情弹窗 -->
  53. <DarkDrawer
  54. title="任务详情"
  55. :visible.sync="dialogVisible"
  56. width="40%"
  57. :before-close="handleClose"
  58. >
  59. <el-row :gutter="20">
  60. <el-col :span="24" v-for="(task, index) in taskList" :key="index">
  61. <h3 class="section-title">
  62. <i class="el-icon-document mr-2"></i> 总体任务
  63. </h3>
  64. <div
  65. class="task-card"
  66. >
  67. <div class="flex justify-between items-center mb-3">
  68. <h4 class="task-title">{{ task.taskName }}</h4>
  69. </div>
  70. <div class="task-info">
  71. <li>任务代号: {{ task.taskCode || "-" }}</li>
  72. <li>任务类型: {{ $getDictNameByValue('task_type',task.taskType+'') || "-" }}</li>
  73. <li>任务时间: {{ task.testTime || "-" }}</li>
  74. <li>创建时间: {{ task.createTime || "-" }}</li>
  75. </div>
  76. </div>
  77. </el-col>
  78. <el-col :span="24" v-for="(task, index) in subTaskList" :key="index">
  79. <h3 class="section-title mt-4">
  80. <i class="el-icon-document mr-2"></i> 子任务
  81. </h3>
  82. <div
  83. class="task-card"
  84. >
  85. <div class="flex justify-between items-center mb-3">
  86. <h4 class="task-title">{{ task.subTaskName || "-" }}</h4>
  87. </div>
  88. <div class="task-info">
  89. <li>任务代号: {{ task.subTaskCode || "-" }}</li>
  90. <li>任务类型: {{ $getDictNameByValue('task_type',task.subTaskType+'') || "-" }}</li>
  91. <li>任务时间: {{ task.subTestTime || "-" }}</li>
  92. <li>打击维度: {{ task.fightAway || "对陆" }}</li>
  93. <li>创建时间: {{ task.createTime || "-" }}</li>
  94. </div>
  95. </div>
  96. </el-col>
  97. </el-row>
  98. </DarkDrawer>
  99. <!-- 新建/编辑 推演任务设定(单选总体方案) -->
  100. <DarkDrawer :title="editDialogTitle" :visible.sync="editVisible" size="55%">
  101. <el-form
  102. ref="editFormRef"
  103. :model="editForm"
  104. :rules="baseRules"
  105. label-width="120px"
  106. class="edit-form-dark"
  107. >
  108. <div class="form-grid">
  109. <!-- 基础信息 -->
  110. <el-form-item label="推演任务名称" prop="simulationName">
  111. <el-input v-model="editForm.simulationName" clearable />
  112. </el-form-item>
  113. <!-- 选择总体任务 -->
  114. <div class="task-select-section span-2">
  115. <h3 class="section-title">
  116. <i class="el-icon-document mr-2"></i> 选择总体任务
  117. </h3>
  118. <p class="section-desc">请选择要关联的总体任务,系统将根据总体任务推荐对应的子任务</p>
  119. <el-row :gutter="20">
  120. <el-col :span="8" class="mb-4" v-for="(task, index) in taskList" :key="index">
  121. <div
  122. class="task-card"
  123. :class="{ 'task-card--active': selectedTask && selectedTask.id === task.id }"
  124. @click="handleTaskSelect(task)"
  125. >
  126. <div class="flex justify-between items-center mb-3">
  127. <h4 class="task-title">{{ task.taskName }}</h4>
  128. </div>
  129. <div class="task-info">
  130. <li>任务代号: {{ task.taskCode || "-" }}</li>
  131. <li>任务类型: {{ $getDictNameByValue('task_type',task.taskType+'') || "-" }}</li>
  132. <li>任务时间: {{ task.testTime || "-" }}</li>
  133. <li>创建时间: {{ task.createTime || "-" }}</li>
  134. </div>
  135. <el-button class="task-btn" type="primary">选择此任务</el-button>
  136. </div>
  137. </el-col>
  138. </el-row>
  139. </div>
  140. <!-- 选择子任务 -->
  141. <div v-if="selectedTask" class="task-select-section span-2">
  142. <h3 class="section-title">
  143. <i class="el-icon-document mr-2"></i> 选择子任务
  144. </h3>
  145. <el-row :gutter="20">
  146. <el-col :span="8" class="mb-4" v-for="(task, index) in subTaskList" :key="index">
  147. <div
  148. class="task-card"
  149. :class="{ 'task-card--active': selectedSubTask && selectedSubTask.id === task.id }"
  150. @click="handleSubTaskSelect(task)"
  151. >
  152. <div class="flex justify-between items-center mb-3">
  153. <h4 class="task-title">{{ task.subTaskName || "-" }}</h4>
  154. </div>
  155. <div class="task-info">
  156. <li>任务代号: {{ task.subTaskCode || "-" }}</li>
  157. <li>任务类型: {{ $getDictNameByValue('task_type',task.subTaskType+'') || "-" }}</li>
  158. <li>任务时间: {{ task.subTestTime || "-" }}</li>
  159. <li>打击维度: {{ task.fightAway || "对陆" }}</li>
  160. <li>创建时间: {{ task.createTime || "-" }}</li>
  161. </div>
  162. <el-button class="task-btn" type="primary">选择此任务</el-button>
  163. </div>
  164. </el-col>
  165. </el-row>
  166. </div>
  167. <div v-if="selectedSubTask" class="task-select-section span-2">
  168. <h3 class="section-title">
  169. <i class="el-icon-document mr-2"></i> 选择方案
  170. </h3>
  171. <el-row :gutter="20">
  172. <!-- 树形表格实现 -->
  173. <el-table
  174. ref="treeTable"
  175. :data="schemeTable"
  176. style="width: 100%"
  177. row-key="id"
  178. border
  179. :tree-props="{ children: 'verList'}"
  180. @select="handleTreeSelect"
  181. @select-all="handleTreeSelectAll"
  182. >
  183. <!-- 单选列:不显示表头的选择框 -->
  184. <el-table-column
  185. type="selection"
  186. width="50"
  187. align="center"
  188. :selectable="canSelect"
  189. :show-header="false"
  190. />
  191. <!-- 树形表格字段 -->
  192. <el-table-column
  193. prop="schemeVersion"
  194. label="版本号"
  195. width="120"
  196. align="center"
  197. ></el-table-column>
  198. <el-table-column
  199. prop="batchId"
  200. label="批次ID"
  201. width=""
  202. align="center"
  203. ></el-table-column>
  204. <el-table-column
  205. prop="measurementSchemeStatus"
  206. label="测量方案"
  207. width="120"
  208. align="center"
  209. >
  210. <template slot-scope="scope">
  211. <el-tag size="mini" :type="getStatusTagType(scope.row.measurementSchemeStatus)">
  212. {{ scope.row.measurementSchemeStatus }}
  213. </el-tag>
  214. </template>
  215. </el-table-column>
  216. <!-- 干扰方案 -->
  217. <el-table-column
  218. label="干扰方案"
  219. width="120"
  220. align="center"
  221. >
  222. <template slot-scope="scope">
  223. <el-tag
  224. size="mini"
  225. :type="getStatusTagType(scope.row.interferenceSchemeStatus)"
  226. style="cursor: pointer;"
  227. @click="handleDocxClick(scope.row, '干扰')"
  228. >
  229. {{ scope.row.interferenceSchemeStatus }}
  230. </el-tag>
  231. </template>
  232. </el-table-column>
  233. <!-- 靶标方案 -->
  234. <el-table-column
  235. label="靶标方案"
  236. width="120"
  237. align="center"
  238. >
  239. <template slot-scope="scope">
  240. <el-tag
  241. size="mini"
  242. :type="getStatusTagType(scope.row.targetSchemeStatus)"
  243. style="cursor: pointer;"
  244. @click="handleDocxClick(scope.row, '靶标')"
  245. >
  246. {{ scope.row.targetSchemeStatus }}
  247. </el-tag>
  248. </template>
  249. </el-table-column>
  250. </el-table>
  251. </el-row>
  252. </div>
  253. </div>
  254. </el-form>
  255. <span class="pt-4 w-full flex justify-center">
  256. <el-button @click="editVisible = false">取 消</el-button>
  257. <el-button type="primary" @click="onEditSave">保 存</el-button>
  258. </span>
  259. </DarkDrawer>
  260. <!-- 文档预览弹窗 -->
  261. <el-dialog
  262. :visible.sync="docxDialogVisible"
  263. :title="dialogTitle"
  264. width="80%"
  265. top="5vh"
  266. @close="docxDialogVisible = false"
  267. v-if="docxDialogVisible"
  268. >
  269. <VueOfficeDocx
  270. v-if="docxSrc"
  271. :src="docxSrc"
  272. @error="onError"
  273. @rendered="onRendered"
  274. style="height: 70vh; overflow-y: auto;"
  275. />
  276. </el-dialog>
  277. </div>
  278. </template>
  279. <script>
  280. import DarkLayout from '@/components/GlobalComponents/DarkLayout.vue'
  281. import TaskUserCard from '@/components/Components/TaskUserCard.vue'
  282. import DarkDialog from "@/components/Components/DarkDialog.vue";
  283. import ScenarioeditingCard from "@/components/GlobalComponents/ScenarioeditingCard.vue";
  284. import {getList, insertDeductionTask, updateDeductionTask} from "@/api/deductionTask";
  285. import {findPageTask, findSubTaskPage} from "@/api/taskMage/taskMage";
  286. import {getListVer} from "@/api/planningScheme";
  287. export default {
  288. components: {
  289. ScenarioeditingCard,
  290. DarkDialog,
  291. DarkLayout,
  292. TaskUserCard
  293. },
  294. data() {
  295. return {
  296. /* 可选试验任务 */
  297. taskList: [],
  298. subTaskList:[],
  299. // 文档预览
  300. docxDialogVisible: false,
  301. dialogTitle: '',
  302. docxSrc: null,
  303. /* 当前选择状态(单选) */
  304. editVisible: false,
  305. editMode: 'edit', // 'add' | 'edit'
  306. selectedTask: null,
  307. selectedSubTask: null,
  308. schemeTable: [],
  309. selectedTreeNode: null, // 记录当前选中的树形节点
  310. /* 表单数据 */
  311. editForm: {
  312. id: '',
  313. simulationName: '',
  314. intendedEditing:0,
  315. simType:0,
  316. taskId:'',
  317. subTaskId:'',
  318. schemeId:'',
  319. versionId: '' // 记录选中的版本ID
  320. },
  321. baseRules: {
  322. simulationName: [{ required: true, message: '请输入任务名称', trigger: 'blur' }],
  323. },
  324. /* 其他弹窗与数据 */
  325. selectedPlan: {},
  326. tableData: [
  327. { date: '2023-06-01', name: '张三', address: '北京市海淀区' },
  328. { date: '2023-06-02', name: '李四', address: '上海市浦东新区' }
  329. ],
  330. dialogVisible: false,
  331. currentPlan: {},
  332. /* 列表和分页 */
  333. queryForm: { simulationName: '',intendedEditing:0,simType:0,pageNo: 1,pageSize: 10 },
  334. planList: [],
  335. total: 0,
  336. }
  337. },
  338. computed: {
  339. isAddMode() {
  340. return this.editMode === 'add'
  341. },
  342. editDialogTitle() {
  343. return this.editMode === 'add' ? '新建推演任务设定' : '编辑推演任务设定'
  344. },
  345. secretChipClass() {
  346. const map = { '绝密': 'danger', '机密': 'warning', '秘密': 'primary' }
  347. return map[this.selectedPlan?.secretLevel] || 'neutral'
  348. },
  349. selectedOverallPlanNames() {
  350. const p = this.currentOverallPlans.find(p => p.id === this.selectedOverallPlanId)
  351. return p ? [p.planName] : []
  352. }
  353. },
  354. mounted() {
  355. this.fetchData()
  356. },
  357. methods: {
  358. // 解析 schemeSubPlan 获取 docx 文件链接
  359. getDocxUrl(row, type) {
  360. if (!row.schemeSubPlan) return null;
  361. try {
  362. const planList = row.schemeSubPlan.startsWith('[')
  363. ? JSON.parse(row.schemeSubPlan)
  364. : [JSON.parse(row.schemeSubPlan)];
  365. // 查找对应类型的 docx 文件
  366. const targetType = type === '干扰' ? 1 : 2; // 假设 1=干扰,2=靶标(根据后端定义调整)
  367. const file = planList.find(item =>
  368. item.type === targetType && item.fileType === 'docx'
  369. );
  370. return file ? file.fileHttp : null;
  371. } catch (e) {
  372. console.error('解析 schemeSubPlan 失败:', e);
  373. return null;
  374. }
  375. },
  376. // 点击 tag 触发
  377. handleDocxClick(row, type) {
  378. const status = type === '干扰'
  379. ? row.interferenceSchemeStatus
  380. : row.targetSchemeStatus;
  381. if (status !== '已导入') return;
  382. const url = this.getDocxUrl(row, type);
  383. if (!url) {
  384. this.$message.warning(`${type}方案未找到可预览的文档`);
  385. return;
  386. }
  387. this.docxSrc = url;
  388. this.dialogTitle = `${type}方案文档预览 - ${row.schemeVersion}`;
  389. this.docxDialogVisible = true;
  390. },
  391. onError(e) {
  392. this.$message.error('文档加载失败,请稍后重试');
  393. console.error(e);
  394. },
  395. onRendered() {
  396. console.log('文档渲染完成');
  397. },
  398. getStatusTagType(status) {
  399. if (!status) return 'info';
  400. switch (status) {
  401. case '已编制':
  402. case '已导入':
  403. return 'success';
  404. case '未导入':
  405. case '未编制':
  406. return 'info';
  407. case '进行中':
  408. return 'warning';
  409. default:
  410. return 'info';
  411. }
  412. },
  413. // 控制哪些行可被选择(全部可选)
  414. canSelect(row) {
  415. return true; // 可根据条件禁用某些行的选择
  416. },
  417. // 处理树形表格选择事件
  418. handleTreeSelect(selection, row) {
  419. // 如果已选中,则取消
  420. if (this.selectedTreeNode === row) {
  421. this.$refs.treeTable.clearSelection();
  422. this.selectedTreeNode = null;
  423. } else {
  424. // 清除之前的选择,只选中当前行
  425. this.$refs.treeTable.clearSelection();
  426. this.$nextTick(() => {
  427. this.$refs.treeTable.toggleRowSelection(row, true);
  428. });
  429. this.selectedTreeNode = row;
  430. }
  431. },
  432. // 禁用树形表格全选功能
  433. handleTreeSelectAll(selection) {
  434. if (selection.length > 1) {
  435. // 强制只保留最后一项
  436. const lastSelected = selection[selection.length - 1];
  437. this.$refs.treeTable.clearSelection();
  438. this.$refs.treeTable.toggleRowSelection(lastSelected, true);
  439. this.selectedTreeNode = lastSelected;
  440. }
  441. },
  442. /* 任务选择:加载其总体方案并清空已选 */
  443. async handleTaskSelect(task) {
  444. this.selectedTask = task;
  445. this.selectedSubTask = null;
  446. const params = {
  447. taskId_EQ: task.id,
  448. pageNo: 1,
  449. pageSize: 9999
  450. };
  451. const res = await findSubTaskPage(params);
  452. this.subTaskList = res.data.rows;
  453. },
  454. /* 单选工具 */
  455. isPlanSelected(plan) {
  456. return this.selectedOverallPlanId === plan.id;
  457. },
  458. togglePlan(plan) {
  459. this.selectedOverallPlanId = (this.selectedOverallPlanId === plan.id) ? '' : plan.id;
  460. },
  461. async getTaskList() {
  462. const params = {
  463. taskName_LIKE: '',
  464. taskType_EQ: '',
  465. pageNo: 1,
  466. pageSize: 9999
  467. };
  468. const res = await findPageTask(params);
  469. this.taskList = res.data.rows;
  470. },
  471. async getSubTaskList(taskId) {
  472. const params = {
  473. taskId_EQ: taskId,
  474. pageNo: 1,
  475. pageSize: 9999
  476. };
  477. const res = await findSubTaskPage(params);
  478. this.subTaskList = res.data.rows;
  479. },
  480. /* 添加:打开空白同一套表单 */
  481. async openAddDialog() {
  482. await this.getTaskList();
  483. this.editMode = 'add';
  484. this.selectedTask = null;
  485. this.selectedSubTask = null;
  486. this.editForm = {
  487. id: '',
  488. simulationName: '',
  489. intendedEditing: 0,
  490. simType: 0,
  491. taskId: '',
  492. subTaskId: '',
  493. schemeId: '',
  494. versionId: ''
  495. };
  496. this.editVisible = true;
  497. this.$nextTick(() => this.$refs.editFormRef && this.$refs.editFormRef.clearValidate());
  498. },
  499. /* 编辑:回填同一套表单 + 自动选中任务与总体方案(单选) */
  500. async openEditDialog(plan) {
  501. this.editMode = 'edit';
  502. this.editForm = JSON.parse(JSON.stringify(plan));
  503. // 重置选择状态
  504. this.selectedTreeNode = null;
  505. if (this.$refs.treeTable) {
  506. this.$refs.treeTable.clearSelection();
  507. }
  508. await this.getTaskList();
  509. const task = this.taskList.find(item => item.id === plan.taskId);
  510. if (task) {
  511. await this.handleTaskSelect(task);
  512. const subTask = this.subTaskList.find(item => item.id === plan.subTaskId);
  513. if (subTask) {
  514. // 等待子任务选择完成和数据加载
  515. await new Promise(resolve => {
  516. this.handleSubTaskSelect(subTask);
  517. // 给数据加载一点时间
  518. setTimeout(resolve, 500);
  519. });
  520. // 使用双重nextTick确保表格已渲染
  521. this.$nextTick(() => {
  522. this.$nextTick(() => {
  523. if (!this.$refs.treeTable) return;
  524. // 递归查找节点
  525. const findNode = (nodes, targetId) => {
  526. for (const node of nodes) {
  527. if (node.id === targetId) {
  528. return node;
  529. }
  530. if (node.verList && node.verList.length) {
  531. const found = findNode(node.verList, targetId);
  532. if (found) return found;
  533. }
  534. }
  535. return null;
  536. };
  537. const targetNode = findNode(this.schemeTable, plan.schemeId);
  538. if (targetNode) {
  539. // 先清除所有选择
  540. this.$refs.treeTable.clearSelection();
  541. // 选中目标节点
  542. this.$refs.treeTable.toggleRowSelection(targetNode, true);
  543. this.selectedTreeNode = targetNode;
  544. // 展开父节点
  545. this.expandParentNodes(this.schemeTable, targetNode.id);
  546. }
  547. });
  548. });
  549. }
  550. }
  551. this.editVisible = true;
  552. this.$nextTick(() => this.$refs.editFormRef && this.$refs.editFormRef.clearValidate());
  553. },
  554. // 优化展开父节点方法
  555. expandParentNodes(nodes, targetId) {
  556. const expandNodes = [];
  557. // 先收集需要展开的节点
  558. const findAndCollectParents = (nodes, targetId) => {
  559. for (const node of nodes) {
  560. if (node.id === targetId) {
  561. return true;
  562. }
  563. if (node.verList && node.verList.length) {
  564. const found = findAndCollectParents(node.verList, targetId);
  565. if (found) {
  566. expandNodes.push(node);
  567. return true;
  568. }
  569. }
  570. }
  571. return false;
  572. };
  573. findAndCollectParents(nodes, targetId);
  574. // 延迟展开,确保表格已准备好
  575. setTimeout(() => {
  576. expandNodes.forEach(node => {
  577. this.$refs.treeTable.toggleRowExpansion(node, true);
  578. });
  579. }, 300);
  580. },
  581. // 同时修改handleSubTaskSelect方法,确保数据加载完成
  582. handleSubTaskSelect(task) {
  583. return new Promise((resolve) => {
  584. this.selectedSubTask = task;
  585. this.selectedTreeNode = null;
  586. getListVer({subTaskId:task.id}).then((res) => {
  587. this.schemeTable = res.data;
  588. // 确保表格数据更新后重新渲染
  589. this.$nextTick(() => {
  590. if (this.$refs.treeTable) {
  591. this.$refs.treeTable.doLayout();
  592. }
  593. resolve();
  594. });
  595. console.log(this.schemeTable)
  596. });
  597. });
  598. },
  599. openExpectedEditDialog(plan) {
  600. this.$router.push({
  601. path: '/Deduction/taskSettingssss',
  602. query: {
  603. plan: JSON.stringify(plan)
  604. }
  605. });
  606. },
  607. /* 保存:处理树形表格选择 */
  608. onEditSave() {
  609. this.$refs.editFormRef.validate((valid) => {
  610. if (!valid) return;
  611. if (!this.selectedTask) {
  612. this.$message.error('请先选择一个总体任务');
  613. return;
  614. }
  615. if (!this.selectedSubTask) {
  616. this.$message.error('请选择一个子任务');
  617. return;
  618. }
  619. // 检查是否选择了节点
  620. if (!this.selectedTreeNode) {
  621. this.$message.error('请选择一个方案或版本');
  622. return;
  623. }
  624. // 写入所选方案和版本
  625. this.editForm.taskId = this.selectedTask.id;
  626. this.editForm.subTaskId = this.selectedSubTask.id;
  627. this.editForm.schemeId = this.selectedTreeNode.id;
  628. this.editForm.simulationType = 0;
  629. if(this.editMode==='add'){
  630. insertDeductionTask(this.editForm).then((res) => {
  631. if (res.code === 0) {
  632. this.$message.success('新建成功');
  633. this.taskList = [];
  634. this.selectedTask = null;
  635. this.subTaskList = [];
  636. this.selectedSubTask = null;
  637. this.selectedTreeNode = null;
  638. this.editVisible = false;
  639. this.handleQuery()
  640. } else {
  641. this.$message.error('新建失败');
  642. }
  643. });
  644. }else{
  645. updateDeductionTask(this.editForm).then((res)=>{
  646. if (res.code === 0) {
  647. this.$message.success('修改成功');
  648. this.taskList = [];
  649. this.selectedTask = null;
  650. this.subTaskList = [];
  651. this.selectedSubTask = null;
  652. this.selectedTreeNode = null;
  653. this.editVisible = false;
  654. this.handleQuery()
  655. } else {
  656. this.$message.error('修改失败');
  657. }
  658. })
  659. }
  660. });
  661. },
  662. /* 其它原有方法 */
  663. statusClass(s) {
  664. const map = { '已确认': 'success', '未确认': 'warning', '已失效': 'danger', '草稿': 'info' };
  665. return map[s] || 'info';
  666. },
  667. statusTagType(status) {
  668. const map = { '有效': 'warning', '待审核': 'warning', '草稿': 'info', '已失效': 'danger' };
  669. return map[status] || 'info';
  670. },
  671. handleClose(done) { done(); },
  672. async viewDetails(plan) {
  673. await this.getTaskList()
  674. this.taskList = this.taskList.filter(item=>item.id===plan.taskId)
  675. await this.getSubTaskList(plan.taskId)
  676. this.subTaskList = this.subTaskList.filter(item=>item.id===plan.subTaskId)
  677. this.dialogVisible = true;
  678. },
  679. /* 列表&分页占位 */
  680. handleQuery() {
  681. getList(this.queryForm).then((res) => {
  682. this.planList = res.data.records;
  683. this.total = res.data.total;
  684. });
  685. },
  686. resetQuery() {
  687. this.queryForm = { simulationName: '',intendedEditing:0,pageNo: 1,pageSize: 10 };
  688. this.handleQuery();
  689. },
  690. handleSizeChange(val) {
  691. this.queryForm.pageSize = val;
  692. this.handleQuery();
  693. },
  694. handleCurrentChange(val) {
  695. this.queryForm.pageNo = val;
  696. this.handleQuery();
  697. },
  698. fetchData() {
  699. this.queryForm.pageNo = 1;
  700. this.handleQuery();
  701. }
  702. }
  703. }
  704. </script>
  705. <style scoped>
  706. /* 弹窗体在 DarkDialog 里已经是深色,这里只美化表单 */
  707. .edit-form-dark {
  708. background: rgba(12, 33, 66, 0.6);
  709. border: 1px solid #2c3f59;
  710. border-radius: 8px;
  711. padding: 14px 16px;
  712. }
  713. .form-grid {
  714. display: grid;
  715. grid-template-columns: 1fr 1fr;
  716. column-gap: 18px;
  717. row-gap: 10px;
  718. }
  719. .form-grid .span-2 { grid-column: 1 / span 2; }
  720. ::v-deep .el-form-item__label { color: #9db2c9; }
  721. ::v-deep .el-input__inner,
  722. ::v-deep .el-textarea__inner,
  723. ::v-deep .el-select .el-input__inner,
  724. ::v-deep .el-date-editor .el-input__inner {
  725. background: rgba(0,0,0,0.18);
  726. border-color: rgba(255,255,255,0.12);
  727. color: #e6efff;
  728. }
  729. ::v-deep .el-input__inner::placeholder { color: #94a3b8; }
  730. ::v-deep .el-select-dropdown,
  731. ::v-deep .el-picker-panel {
  732. background: #1b2d4c;
  733. border-color: #2c3f59;
  734. color: #e6efff;
  735. }
  736. @media (max-width: 860px) {
  737. .form-grid { grid-template-columns: 1fr; }
  738. .form-grid .span-2 { grid-column: auto; }
  739. }
  740. .blue-btn:hover{
  741. background: #004466;
  742. border: 2px solid #1e3a5f;
  743. border-color: #4085ac;
  744. color: #fff;
  745. }
  746. /* Dialog 外观更暗、更清晰的分层 */
  747. ::v-deep .el-dialog__header {
  748. background: linear-gradient(180deg, #153b73, #0e2a53);
  749. border-bottom: 1px solid #364a64;
  750. }
  751. ::v-deep .el-dialog__title { color: #e6eefc; font-weight: 600; }
  752. ::v-deep .el-dialog__body { background: #0b254a; padding: 18px 22px; }
  753. ::v-deep .el-dialog__footer{
  754. background: #0b254a; border-top: 1px solid #364a64;
  755. }
  756. /* 内容容器 */
  757. .basic-info {
  758. border: 1px solid #2c3f59;
  759. border-radius: 8px;
  760. background: rgba(12, 33, 66, 0.6);
  761. box-shadow: inset 0 0 0 1px rgba(255,255,255,0.02);
  762. }
  763. /* 顶部条:密级与状态 */
  764. .top-line{
  765. display:flex; gap:10px; align-items:center;
  766. padding:12px 14px; border-bottom:1px dashed #2c3f59;
  767. }
  768. .chip{
  769. display:inline-block; padding:4px 10px; border-radius:999px;
  770. color:#fff; font-size:12px; letter-spacing:0.5px;
  771. }
  772. .chip.primary{ background:#3475b5; } /* 秘密 */
  773. .chip.warning{ background:#b88230; } /* 机密 */
  774. .chip.danger{ background:#c45656; } /* 绝密 */
  775. .chip.neutral{ background:#6b7280; }
  776. .chip.soft{ opacity:.9; }
  777. .chip.success{ background:#1f9d55; }
  778. .chip.info{ background:#4b5563; }
  779. /* 分组标题与栅格 */
  780. .section { padding: 10px 14px 14px; }
  781. .section + .section { border-top: 1px dashed #2c3f59; }
  782. .section-title{
  783. font-size:13px; color:#9db2c9; margin-bottom:8px;
  784. position:relative; padding-left:10px;
  785. }
  786. .section-title::before{
  787. content:''; position:absolute; left:0; top:4px; bottom:4px; width:3px;
  788. background: linear-gradient(180deg, #66a3ff, #3a7bd5);
  789. border-radius:2px;
  790. }
  791. /* 双栏对齐的键值栅格 */
  792. .info-grid{
  793. display:grid;
  794. grid-template-columns: 1fr 1fr;
  795. gap: 10px 18px;
  796. }
  797. .kv{ display:grid; grid-template-columns: 120px 1fr; align-items:center; }
  798. .kv.span-2{ grid-column: 1 / span 2; }
  799. .kv-label{ color:#8aa0bb; font-size:13px; justify-self:start; }
  800. .kv-value{
  801. color:#e7eef8; font-size:13px; line-height:1.6;
  802. background: rgba(0,0,0,0.12);
  803. border: 1px solid rgba(255,255,255,0.06);
  804. padding: 6px 10px; border-radius: 6px;
  805. }
  806. /* 任务卡片 */
  807. .task-select-section { margin-top: 20px; }
  808. .section-desc { font-size: 13px; color: #94a3b8; margin-bottom: 14px; }
  809. .task-card {
  810. background: #09264c;
  811. border: 1px solid #334155;
  812. border-radius: 8px;
  813. padding: 16px;
  814. color: #e2e8f0;
  815. transition: all 0.25s ease;
  816. display: flex; flex-direction: column; justify-content: space-between;
  817. height: 100%;
  818. }
  819. .task-card:hover {
  820. border-color: #3b82f6;
  821. box-shadow: 0 4px 12px rgba(0,0,0,0.5);
  822. transform: translateY(-2px);
  823. cursor: pointer;
  824. }
  825. .task-card--active {
  826. border: 2px solid #3b82f6;
  827. box-shadow: 0 0 10px rgba(59,130,246,0.7);
  828. }
  829. .task-title { height:45px;font-size: 15px; font-weight: 600; color: #fff; }
  830. .task-info { font-size: 13px; line-height: 1.6; margin-bottom: 12px; }
  831. .task-btn { width: 100%; font-weight: 600; border-radius: 6px; }
  832. /* 总体方案卡片(单选) */
  833. .plan-select-section { margin-top: 10px; }
  834. .plan-select-header {
  835. display:flex; align-items:center; justify-content: space-between; margin-bottom: 6px;
  836. }
  837. .plan-tools { display:flex; align-items:center; gap:10px; }
  838. .tool-label { color:#9db2c9; font-size:12px; margin-right:4px; }
  839. .plan-card {
  840. background: #0f1f38;
  841. border: 1px solid #2b3b55;
  842. border-radius: 8px;
  843. padding: 14px;
  844. color: #e2e8f0;
  845. transition: all .2s ease;
  846. height: 100%;
  847. }
  848. .plan-card:hover { border-color:#3b82f6; transform: translateY(-2px); cursor: pointer; }
  849. .plan-card--selected { border: 2px solid #3b82f6; box-shadow: 0 0 10px rgba(59,130,246,.5); }
  850. .plan-card__header { display:flex; align-items:center; justify-content: space-between; margin-bottom: 8px; }
  851. .plan-card__title { font-weight: 600; }
  852. .plan-card__check { font-size: 16px; }
  853. .plan-card__meta { display:flex; gap:12px; font-size:12px; color:#9db2c9; margin-bottom:8px; }
  854. .plan-card__desc { font-size: 12px; color:#cbd5e1; margin-bottom:10px; line-height: 1.7; }
  855. .plan-card__footer { display:flex; gap:8px; align-items:center; flex-wrap: wrap; }
  856. .selected-summary { margin-top: 10px; }
  857. </style>