LTobeHandled.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691
  1. <!-- 待办预警 -->
  2. <template>
  3. <div class="app-container todo-warning-page" v-loading="loading">
  4. <el-card shadow="never" class="page-card header-card">
  5. <div class="page-header">
  6. <div>
  7. <div class="page-title">待办预警</div>
  8. <div class="page-subtitle">预警任务与督办通知</div>
  9. </div>
  10. <el-button type="primary" icon="Refresh" @click="loadCurrentTabData">刷新</el-button>
  11. </div>
  12. </el-card>
  13. <div class="summary-grid">
  14. <el-card shadow="hover" class="summary-card danger">
  15. <div class="summary-label">我的待办</div>
  16. <div class="summary-value">{{ todoCount }}</div>
  17. <div class="summary-desc">当前用户待处理预警数量</div>
  18. </el-card>
  19. <el-card shadow="hover" class="summary-card success">
  20. <div class="summary-label">我的已办</div>
  21. <div class="summary-value">{{ doneCount }}</div>
  22. <div class="summary-desc">当前用户已处理完成预警数量</div>
  23. </el-card>
  24. <el-card shadow="hover" class="summary-card primary">
  25. <div class="summary-label">当前视图</div>
  26. <div class="summary-value">{{ pagedList.length }}</div>
  27. <div class="summary-desc">本页筛选后可查看记录数</div>
  28. </el-card>
  29. </div>
  30. <el-card shadow="never" class="page-card">
  31. <el-form :model="queryParams" :inline="true">
  32. <el-form-item label="预警编号">
  33. <el-input
  34. v-model="queryParams.warningNo"
  35. placeholder="请输入预警编号"
  36. clearable
  37. style="width: 220px"
  38. @keyup.enter="handleQuery"
  39. />
  40. </el-form-item>
  41. <el-form-item label="预警专项">
  42. <el-input v-model="queryParams.warningSpecial" placeholder="请输入预警专项" clearable style="width: 200px" />
  43. </el-form-item>
  44. <el-form-item label="预警类型">
  45. <el-select v-model="queryParams.warningType" clearable placeholder="请选择预警类型" style="width: 180px">
  46. <el-option
  47. v-for="item in warningTypeOptions"
  48. :key="item.dictValue"
  49. :label="item.dictLabel"
  50. :value="item.dictValue"
  51. />
  52. </el-select>
  53. </el-form-item>
  54. <el-form-item label="预警级别">
  55. <el-select v-model="queryParams.warningLevel" clearable placeholder="请选择预警级别" style="width: 180px">
  56. <el-option
  57. v-for="item in warningLevelOptions"
  58. :key="item.dictValue"
  59. :label="item.dictLabel"
  60. :value="item.dictValue"
  61. />
  62. </el-select>
  63. </el-form-item>
  64. <el-form-item label="处置人">
  65. <el-input v-model="queryParams.handler" placeholder="请输入处置人" clearable style="width: 180px" />
  66. </el-form-item>
  67. <el-form-item label="发布人">
  68. <el-input v-model="queryParams.publisher" placeholder="请输入发布人" clearable style="width: 180px" />
  69. </el-form-item>
  70. <el-form-item label="报警编号">
  71. <el-input v-model="queryParams.alarmCode" placeholder="请输入报警编号" clearable style="width: 180px" />
  72. </el-form-item>
  73. <el-form-item label="权属单位">
  74. <el-input v-model="queryParams.ownershipUnit" placeholder="请输入权属单位" clearable style="width: 180px" />
  75. </el-form-item>
  76. <el-form-item label="发布时间">
  77. <el-date-picker
  78. v-model="queryParams.dateRange"
  79. type="datetimerange"
  80. value-format="YYYY-MM-DD HH:mm:ss"
  81. range-separator="至"
  82. start-placeholder="开始时间"
  83. end-placeholder="结束时间"
  84. style="width: 360px"
  85. />
  86. </el-form-item>
  87. <el-form-item>
  88. <el-button type="primary" icon="Search" @click="handleQuery">查询</el-button>
  89. <el-button icon="Refresh" @click="resetQuery">重置</el-button>
  90. </el-form-item>
  91. </el-form>
  92. </el-card>
  93. <el-card shadow="never" class="page-card">
  94. <el-tabs v-model="activeTab" @tab-change="handleTabChange">
  95. <el-tab-pane :label="`我的待办 (${todoCount})`" name="TODO" />
  96. <el-tab-pane :label="`我的已办 (${doneCount})`" name="DONE" />
  97. <el-tab-pane :label="`系统通知 (${noticeCount})`" name="NOTICE" />
  98. </el-tabs>
  99. <el-table :data="pagedList" @row-click="handleViewDetail">
  100. <el-table-column label="序号" width="70" align="center">
  101. <template #default="scope">
  102. {{ (pagination.pageNum - 1) * pagination.pageSize + scope.$index + 1 }}
  103. </template>
  104. </el-table-column>
  105. <el-table-column v-if="activeTab === 'NOTICE'" label="督办通知" prop="taskName" min-width="250" show-overflow-tooltip />
  106. <el-table-column v-if="activeTab === 'NOTICE'" label="阅读状态" width="90">
  107. <template #default="scope">{{ scope.row.readStatus === 'READ' ? '已读' : '未读' }}</template>
  108. </el-table-column>
  109. <el-table-column label="当前环节截止" min-width="165">
  110. <template #default="{ row }">{{ formatDateTime(row.stage_deadline || row.stageDeadline) }}</template>
  111. </el-table-column>
  112. <el-table-column label="预警编号" prop="warningNo" width="170" />
  113. <el-table-column label="预警专项" prop="warningSpecial" width="140" show-overflow-tooltip />
  114. <el-table-column label="预警类型" prop="warningTypeText" width="120" />
  115. <el-table-column label="处置人" prop="handler" width="120" />
  116. <el-table-column label="发布人" prop="publisher" width="120" />
  117. <el-table-column label="报警编号" width="160">
  118. <template #default="scope">
  119. {{ scope.row.alarmCodeList.length ? scope.row.alarmCodeList.join(",") : "-" }}
  120. </template>
  121. </el-table-column>
  122. <el-table-column label="权属单位" prop="ownershipUnit" width="160" show-overflow-tooltip />
  123. <el-table-column label="预警级别" width="110" align="center">
  124. <template #default="scope">
  125. <el-tag :type="getLevelTagType(scope.row.warningLevel)">
  126. {{ scope.row.warningLevelText || "-" }}
  127. </el-tag>
  128. </template>
  129. </el-table-column>
  130. <el-table-column label="发布时间" width="180">
  131. <template #default="scope">{{ formatDateTime(scope.row.publishTime) }}</template>
  132. </el-table-column>
  133. <el-table-column label="状态" width="100" align="center">
  134. <template #default="scope">
  135. <el-tag :type="getStatusTagType(scope.row.status)">
  136. {{ getStatusText(scope.row.status) }}
  137. </el-tag>
  138. </template>
  139. </el-table-column>
  140. <el-table-column label="操作" fixed="right" width="300" align="center">
  141. <template #default="scope">
  142. <el-button v-hasPermi="['warning:warning:query']" link type="primary" @click.stop="handleViewDetail(scope.row)">查看详情</el-button>
  143. <template v-if="activeTab === 'TODO'">
  144. <WarningWorkflowActions :row="scope.row" @changed="refreshLists" />
  145. </template>
  146. </template>
  147. </el-table-column>
  148. </el-table>
  149. <pagination
  150. v-show="currentTotal > 0"
  151. :total="currentTotal"
  152. v-model:page="pagination.pageNum"
  153. v-model:limit="pagination.pageSize"
  154. @pagination="handlePageChange"
  155. />
  156. </el-card>
  157. <el-drawer v-model="detailVisible" title="预警详情" size="760px" append-to-body direction="rtl" class="detail-drawer">
  158. <el-alert v-if="activeTab === 'NOTICE' && selectedNotice" :title="selectedNotice" type="info" :closable="false" style="margin-bottom: 16px" />
  159. <template v-if="detailData.basicInfo">
  160. <WarningItemSummary :warning="detailData.basicInfo" />
  161. <el-descriptions :column="2" border>
  162. <el-descriptions-item label="预警编号">{{ detailBasic.warningNo || "-" }}</el-descriptions-item>
  163. <el-descriptions-item label="预警专项">{{ detailBasic.warningSpecial || "-" }}</el-descriptions-item>
  164. <el-descriptions-item label="预警类型">{{ detailBasic.warningTypeText || "-" }}</el-descriptions-item>
  165. <el-descriptions-item label="预警级别">{{ detailBasic.warningLevelText || "-" }}</el-descriptions-item>
  166. <el-descriptions-item label="处置人">{{ detailBasic.handler || "-" }}</el-descriptions-item>
  167. <el-descriptions-item label="发布人">{{ detailBasic.publisher || "-" }}</el-descriptions-item>
  168. <el-descriptions-item label="权属单位">{{ detailBasic.ownershipUnit || "-" }}</el-descriptions-item>
  169. <el-descriptions-item label="发布时间">{{ formatDateTime(detailBasic.publishTime) }}</el-descriptions-item>
  170. <el-descriptions-item label="预警内容" :span="2"><span class="warning-content">{{ detailBasic.warningContent || "-" }}</span></el-descriptions-item>
  171. <el-descriptions-item label="备注" :span="2">{{ detailBasic.remark || "-" }}</el-descriptions-item>
  172. </el-descriptions>
  173. <el-divider content-position="left">关联报警</el-divider>
  174. <el-table :data="detailData.alarmList || []" border size="small">
  175. <el-table-column label="报警编号" prop="warning_code" min-width="160" />
  176. <el-table-column label="报警类型" prop="warning_type" min-width="120" />
  177. <el-table-column label="设备编码" prop="device_code" min-width="160" />
  178. <el-table-column label="实际值" prop="actual_value" min-width="100" />
  179. <el-table-column label="报警时间" prop="createTime" min-width="180" />
  180. </el-table>
  181. <el-divider content-position="left">处置记录</el-divider>
  182. <el-timeline v-if="(detailData.disposalHistory || []).length">
  183. <el-timeline-item
  184. v-for="(item, index) in detailData.disposalHistory"
  185. :key="index"
  186. :timestamp="formatDateTime(item.createTime || item.create_time || item.disposalTime || item.disposal_time)"
  187. >
  188. {{ item.disposalTypeName || item.disposal_type_name || item.disposalType || item.disposal_type || "处置记录" }}
  189. <span v-if="item.disposalContent || item.disposal_content || item.content">:{{ item.disposalContent || item.disposal_content || item.content }}</span>
  190. <span v-if="item.disposalUser || item.disposal_user">({{ item.disposalUser || item.disposal_user }})</span>
  191. <WarningDisposalReasons :record="item" />
  192. <WarningAttachments :attachments="attachmentsForDisposal(item, detailData.attachmentList)" />
  193. </el-timeline-item>
  194. </el-timeline>
  195. <el-empty v-else description="暂无处置记录" />
  196. <template v-if="unlinkedWarningAttachments(detailData.disposalHistory, detailData.attachmentList).length">
  197. <el-divider content-position="left">其他预警附件</el-divider>
  198. <WarningAttachments :attachments="unlinkedWarningAttachments(detailData.disposalHistory, detailData.attachmentList)" />
  199. </template>
  200. </template>
  201. </el-drawer>
  202. <!-- 预警解除对话框 -->
  203. <el-dialog v-model="resolveDialog.visible" title="解除预警" width="500px" append-to-body>
  204. <el-form label-width="100px">
  205. <el-form-item label="预警编号">
  206. <el-input :model-value="resolveDialog.row?.warningNo || '-'" disabled />
  207. </el-form-item>
  208. <el-form-item label="处置说明" required>
  209. <el-input
  210. v-model="resolveDialog.disposalContent"
  211. type="textarea"
  212. :rows="4"
  213. maxlength="500"
  214. show-word-limit
  215. placeholder="请输入处置说明"
  216. />
  217. </el-form-item>
  218. </el-form>
  219. <template #footer>
  220. <div class="dialog-footer">
  221. <el-button @click="resolveDialog.visible = false">取 消</el-button>
  222. <el-button type="primary" :loading="actionLoading" @click="confirmResolve">确 定</el-button>
  223. </div>
  224. </template>
  225. </el-dialog>
  226. <!-- 退回重办对话框 -->
  227. <el-dialog v-model="returnDialog.visible" title="退回重办" width="500px" append-to-body>
  228. <el-form label-width="100px">
  229. <el-form-item label="预警编号">
  230. <el-input :model-value="returnDialog.row?.warningNo || '-'" disabled />
  231. </el-form-item>
  232. <el-form-item label="退回说明" required>
  233. <el-input
  234. v-model="returnDialog.disposalContent"
  235. type="textarea"
  236. :rows="4"
  237. maxlength="500"
  238. show-word-limit
  239. placeholder="请输入退回说明"
  240. />
  241. </el-form-item>
  242. </el-form>
  243. <template #footer>
  244. <div class="dialog-footer">
  245. <el-button @click="returnDialog.visible = false">取 消</el-button>
  246. <el-button type="primary" :loading="actionLoading" @click="confirmReturn">确 定</el-button>
  247. </div>
  248. </template>
  249. </el-dialog>
  250. </div>
  251. </template>
  252. <script setup>
  253. import { useWarningStatuses } from '@/views/subSystem/lifeCompany/warningWorkflow'
  254. const { warningStatusOptions, databaseStatusLabels } = useWarningStatuses()
  255. import WarningItemSummary from '../components/WarningItemSummary.vue';
  256. import WarningDisposalReasons from '../components/WarningDisposalReasons.vue';
  257. import WarningAttachments from "../components/WarningAttachments.vue";
  258. import { attachmentsForDisposal, unlinkedWarningAttachments } from "../warningAttachments";
  259. import { computed, onMounted, reactive, ref } from "vue";
  260. import auth from '@/plugins/auth';
  261. import { ElMessage, ElMessageBox } from "element-plus";
  262. import { parseTime } from "@/utils/ruoyi";
  263. import WarningWorkflowActions from "../WarningWorkflowActions.vue";
  264. import { getAlertTypeDictionaryData, getTodoListData, getWarningDetailData, readWarningNotification, resolveWarningData, returnWarningData } from "@/api/smxyjldczxt/yjxxfb/yjfb.js";
  265. const loading = ref(false);
  266. const activeTab = ref("TODO");
  267. const todoList = ref([]);
  268. const doneList = ref([]);
  269. const noticeList = ref([]);
  270. const todoTotal = ref(0);
  271. const doneTotal = ref(0);
  272. const noticeTotal = ref(0);
  273. const warningTypeOptions = ref([]);
  274. const warningLevelOptions = ref([]);
  275. const detailVisible = ref(false);
  276. const detailData = ref({});
  277. const selectedNotice = ref("");
  278. const actionLoading = ref(false);
  279. const resolveDialog = reactive({
  280. visible: false,
  281. row: null,
  282. disposalContent: ""
  283. });
  284. const returnDialog = reactive({
  285. visible: false,
  286. row: null,
  287. disposalContent: ""
  288. });
  289. const queryParams = reactive({
  290. warningNo: "",
  291. warningSpecial: "",
  292. warningType: "",
  293. warningLevel: "",
  294. handler: "",
  295. publisher: "",
  296. alarmCode: "",
  297. ownershipUnit: "",
  298. dateRange: []
  299. });
  300. const pagination = reactive({
  301. pageNum: 1,
  302. pageSize: 10
  303. });
  304. const warningTypeLabelMap = computed(() => {
  305. return warningTypeOptions.value.reduce((acc, item) => {
  306. acc[item.dictValue] = item.dictLabel;
  307. return acc;
  308. }, {});
  309. });
  310. const warningLevelLabelMap = computed(() => {
  311. return warningLevelOptions.value.reduce((acc, item) => {
  312. acc[item.dictValue] = item.dictLabel;
  313. return acc;
  314. }, {});
  315. });
  316. const currentList = computed(() => ({ TODO: todoList.value, DONE: doneList.value, NOTICE: noticeList.value })[activeTab.value] || []);
  317. const currentTotal = computed(() => ({ TODO: todoTotal.value, DONE: doneTotal.value, NOTICE: noticeTotal.value })[activeTab.value] || 0);
  318. const todoCount = computed(() => todoTotal.value);
  319. const doneCount = computed(() => doneTotal.value);
  320. const noticeCount = computed(() => noticeTotal.value);
  321. const pagedList = computed(() => currentList.value);
  322. function getStatusText(status) {
  323. return databaseStatusLabels.value[status] || status || "-"
  324. }
  325. function getStatusTagType(status) {
  326. if (status === "CLOSED") return "success";
  327. if (status === "DRAFT" || status === "PENDING") return "info";
  328. return "warning";
  329. }
  330. const detailBasic = computed(() => {
  331. const basic = detailData.value.basicInfo || {};
  332. const warningType = basic.warningType || basic.warning_type || "";
  333. const warningLevel = basic.warningLevel || basic.warning_level || "";
  334. return {
  335. warningNo: basic.warningNo || basic.warning_no || "",
  336. warningSpecial: basic.warningSpecial || basic.warning_special || "",
  337. warningTypeText: warningTypeLabelMap.value[warningType] || warningType || "-",
  338. warningLevelText: warningLevelLabelMap.value[warningLevel] || "-",
  339. handler: basic.handler || "",
  340. publisher: basic.publisher || "",
  341. ownershipUnit: basic.ownershipUnit || basic.ownership_unit || "",
  342. publishTime: basic.publishTime || basic.publish_time || "",
  343. warningContent: basic.warningContent || basic.warning_content || "",
  344. remark: basic.remark || ""
  345. };
  346. });
  347. function formatDateTime(value) {
  348. return value ? parseTime(value) : "-";
  349. }
  350. function getLevelTagType(level) {
  351. return {
  352. "1": "danger",
  353. "2": "warning",
  354. "3": "success",
  355. "4": "info"
  356. }[level] || "info";
  357. }
  358. async function loadDicts() {
  359. const [typeResult, levelResult] = await Promise.allSettled([
  360. getAlertTypeDictionaryData({ dictType: "alert_type" }),
  361. getAlertTypeDictionaryData({ dictType: "alert_level" })
  362. ]);
  363. if (typeResult.status === "fulfilled") {
  364. warningTypeOptions.value = typeResult.value.rows || [];
  365. }
  366. if (levelResult.status === "fulfilled") {
  367. warningLevelOptions.value = levelResult.value.rows || [];
  368. }
  369. }
  370. function enrichTodoRows(records) {
  371. return (records || []).map(item => {
  372. const warningType = item.warning_type || item.warningType || "";
  373. const warningLevel = item.warning_level || item.warningLevel || "";
  374. return {
  375. ...item,
  376. todoId: item.todo_id || item.todoId,
  377. warningId: item.warning_id || item.warningId,
  378. warningNo: item.warning_no || item.warningNo || "",
  379. warningSpecial: item.warning_special || item.warningSpecial || "",
  380. warningType,
  381. warningTypeText: warningTypeLabelMap.value[warningType] || warningType || "-",
  382. warningLevel,
  383. warningLevelText: warningLevelLabelMap.value[warningLevel] || "-",
  384. ownershipUnit: item.ownership_unit || item.ownershipUnit || "",
  385. publishTime: item.publish_time || item.publishTime || "",
  386. reviewer: item.reviewer || item.publisher || "",
  387. alarmCodeList: String(item.alarm_numbers || item.alarmNumbers || "").split(",").filter(Boolean),
  388. availableActions: item.availableActions || [],
  389. todoType: item.todo_type || item.todoType,
  390. actionType: item.action_type || item.actionType,
  391. taskName: item.task_name || item.taskName || "",
  392. readStatus: item.read_status || item.readStatus || ""
  393. };
  394. });
  395. }
  396. async function loadTodoList(type) {
  397. const [startTime, endTime] = queryParams.dateRange || [];
  398. const res = await getTodoListData({
  399. pageNum: pagination.pageNum,
  400. pageSize: pagination.pageSize,
  401. todoType: type,
  402. warningNo: queryParams.warningNo || undefined,
  403. warningSpecial: queryParams.warningSpecial || undefined,
  404. warningType: queryParams.warningType || undefined,
  405. warningLevel: queryParams.warningLevel || undefined,
  406. handler: queryParams.handler || undefined,
  407. publisher: queryParams.publisher || undefined,
  408. alarmCode: queryParams.alarmCode || undefined,
  409. ownershipUnit: queryParams.ownershipUnit || undefined,
  410. startTime: startTime || undefined,
  411. endTime: endTime || undefined
  412. });
  413. const records = res.data?.records || [];
  414. return { rows: enrichTodoRows(records), total: Number(res.data?.total || 0) };
  415. }
  416. async function loadCurrentTabData() {
  417. loading.value = true;
  418. try {
  419. if (activeTab.value === "TODO") {
  420. const result = await loadTodoList("TODO");
  421. todoList.value = result.rows;
  422. todoTotal.value = result.total;
  423. } else {
  424. const result = await loadTodoList(activeTab.value);
  425. if (activeTab.value === "NOTICE") {
  426. noticeList.value = result.rows;
  427. noticeTotal.value = result.total;
  428. } else {
  429. doneList.value = result.rows;
  430. doneTotal.value = result.total;
  431. }
  432. }
  433. } finally {
  434. loading.value = false;
  435. }
  436. }
  437. async function preloadCounts() {
  438. loading.value = true;
  439. try {
  440. const [todoResult, doneResult, noticeResult] = await Promise.allSettled([loadTodoList("TODO"), loadTodoList("DONE"), loadTodoList("NOTICE")]);
  441. if (todoResult.status === "fulfilled") {
  442. todoList.value = todoResult.value.rows;
  443. todoTotal.value = todoResult.value.total;
  444. }
  445. if (doneResult.status === "fulfilled") {
  446. doneList.value = doneResult.value.rows;
  447. doneTotal.value = doneResult.value.total;
  448. }
  449. if (noticeResult.status === "fulfilled") {
  450. noticeList.value = noticeResult.value.rows;
  451. noticeTotal.value = noticeResult.value.total;
  452. }
  453. } finally {
  454. loading.value = false;
  455. }
  456. }
  457. function handleQuery() {
  458. pagination.pageNum = 1;
  459. refreshLists();
  460. }
  461. function resetQuery() {
  462. queryParams.warningNo = "";
  463. queryParams.warningSpecial = "";
  464. queryParams.warningType = "";
  465. queryParams.warningLevel = "";
  466. queryParams.handler = "";
  467. queryParams.publisher = "";
  468. queryParams.alarmCode = "";
  469. queryParams.ownershipUnit = "";
  470. queryParams.dateRange = [];
  471. pagination.pageNum = 1;
  472. refreshLists();
  473. }
  474. function handlePageChange() {
  475. loadCurrentTabData();
  476. }
  477. function handleTabChange() {
  478. pagination.pageNum = 1;
  479. loadCurrentTabData();
  480. }
  481. async function refreshLists() {
  482. await preloadCounts();
  483. if (pagination.pageNum > 1 && currentList.value.length === 0) {
  484. pagination.pageNum = Math.max(1, Math.ceil(currentTotal.value / pagination.pageSize));
  485. await preloadCounts();
  486. }
  487. }
  488. async function handleViewDetail(row) {
  489. if (!auth.hasPermi('warning:warning:query')) return;
  490. const res = await getWarningDetailData(row.warningId);
  491. detailData.value = res.data || {};
  492. selectedNotice.value = activeTab.value === "NOTICE" ? row.taskName : "";
  493. detailVisible.value = true;
  494. if (activeTab.value === "NOTICE" && row.readStatus !== "READ") {
  495. await readWarningNotification(row.todoId);
  496. row.readStatus = "READ";
  497. }
  498. }
  499. function openResolveDialog(row) {
  500. resolveDialog.row = row;
  501. resolveDialog.disposalContent = "";
  502. resolveDialog.visible = true;
  503. }
  504. function openReturnDialog(row) {
  505. returnDialog.row = row;
  506. returnDialog.disposalContent = "";
  507. returnDialog.visible = true;
  508. }
  509. async function confirmResolve() {
  510. if (!resolveDialog.disposalContent) {
  511. ElMessage.warning("请输入处置说明");
  512. return;
  513. }
  514. actionLoading.value = true;
  515. try {
  516. const res = await resolveWarningData({
  517. warningId: resolveDialog.row.warningId,
  518. disposalContent: resolveDialog.disposalContent
  519. });
  520. if (res.code === 200) {
  521. ElMessage.success("解除成功");
  522. resolveDialog.visible = false;
  523. await refreshLists();
  524. } else {
  525. ElMessage.error(res.msg || "解除失败");
  526. }
  527. } finally {
  528. actionLoading.value = false;
  529. }
  530. }
  531. async function confirmReturn() {
  532. if (!returnDialog.disposalContent) {
  533. ElMessage.warning("请输入退回说明");
  534. return;
  535. }
  536. actionLoading.value = true;
  537. try {
  538. const res = await returnWarningData({
  539. warningId: returnDialog.row.warningId,
  540. disposalContent: returnDialog.disposalContent
  541. });
  542. if (res.code === 200) {
  543. ElMessage.success("退回成功");
  544. returnDialog.visible = false;
  545. await refreshLists();
  546. } else {
  547. ElMessage.error(res.msg || "退回失败");
  548. }
  549. } finally {
  550. actionLoading.value = false;
  551. }
  552. }
  553. onMounted(async () => {
  554. await loadDicts();
  555. await preloadCounts();
  556. });
  557. </script>
  558. <style scoped>
  559. .todo-warning-page {
  560. background: #f5f7fb;
  561. }
  562. .page-card {
  563. border-radius: 16px;
  564. margin-bottom: 16px;
  565. }
  566. .header-card {
  567. background: linear-gradient(135deg, #eff6ff, #f8fbff);
  568. border: 1px solid #dbeafe;
  569. }
  570. .page-header {
  571. display: flex;
  572. justify-content: space-between;
  573. align-items: center;
  574. gap: 16px;
  575. }
  576. .page-title {
  577. font-size: 22px;
  578. font-weight: 700;
  579. color: #1e3a8a;
  580. }
  581. .page-subtitle {
  582. margin-top: 8px;
  583. font-size: 13px;
  584. color: #64748b;
  585. }
  586. .summary-grid {
  587. display: grid;
  588. grid-template-columns: repeat(3, minmax(0, 1fr));
  589. gap: 16px;
  590. margin-bottom: 16px;
  591. }
  592. .summary-card {
  593. border: none;
  594. color: #fff;
  595. }
  596. .summary-card :deep(.el-card__body) {
  597. padding: 20px;
  598. }
  599. .summary-card.danger {
  600. background: linear-gradient(135deg, #dc2626, #f87171);
  601. }
  602. .summary-card.success {
  603. background: linear-gradient(135deg, #059669, #34d399);
  604. }
  605. .summary-card.primary {
  606. background: linear-gradient(135deg, #2563eb, #5b93ff);
  607. }
  608. .summary-label {
  609. font-size: 14px;
  610. }
  611. .summary-value {
  612. margin: 8px 0 10px;
  613. font-size: 30px;
  614. font-weight: 700;
  615. }
  616. .summary-desc {
  617. font-size: 12px;
  618. opacity: 0.9;
  619. }
  620. @media (max-width: 1200px) {
  621. .summary-grid {
  622. grid-template-columns: 1fr;
  623. }
  624. }
  625. </style>