EvaluationTarget.vue 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776
  1. <template>
  2. <div class="safety-platform-container">
  3. <header class="safety-platform-container__header">
  4. <div class="evaluation-header">
  5. <h1 class="evaluation-title">{{ evaluationDetail.exName || '考核对象' }}</h1>
  6. <div class="evaluation-meta">
  7. <span>考核部门: {{ evaluationDetail.deptNames || '-' }}</span>
  8. <span>创建人: {{ evaluationDetail.createdUserName || '-' }}</span>
  9. <span>创建时间: {{ formatDateTime(evaluationDetail.createdAt) || '-' }}</span>
  10. </div>
  11. </div>
  12. </header>
  13. <main class="safety-platform-container__main">
  14. <div class="search-table-container">
  15. <!-- tabs 放在搜索条件上面 -->
  16. <div class="header-tabs-wrapper" v-if="showViewTab">
  17. <!-- 顶部切换:按状态 / 按部门考核结果(先进集体排名) / 先进个人 -->
  18. <el-tabs v-model="viewTab">
  19. <el-tab-pane v-if="operate !== 'evaluationSystem-advanced-group'" label="状态" name="status" />
  20. <el-tab-pane label="部门考核结果" name="dept" />
  21. <el-tab-pane v-if="operate !== 'evaluationSystem-advanced-group'" label="先进个人" name="person" />
  22. </el-tabs>
  23. <!-- 状态模式下,展示原有状态 tabs -->
  24. <el-tabs
  25. v-if="viewTab === 'status'"
  26. v-model="activeTab"
  27. @tab-change="handleTabChange"
  28. >
  29. <template v-if="showAllTabs">
  30. <el-tab-pane label="全部" name="ALL" />
  31. <el-tab-pane
  32. v-for="item in EVALUATION_SYSTEM_STATUS_OPTIONS"
  33. :key="item.value"
  34. :label="item.label"
  35. :name="String(item.value)"
  36. />
  37. </template>
  38. <template v-else>
  39. <el-tab-pane :label="singleTabLabel" :name="singleStatus" />
  40. </template>
  41. </el-tabs>
  42. </div>
  43. <!-- 从考核对象入口进来(非先进集体/先进个人),仅展示状态 tabs -->
  44. <div class="header-tabs-wrapper" v-else>
  45. <el-tabs v-model="activeTab" @tab-change="handleTabChange">
  46. <template v-if="showAllTabs">
  47. <el-tab-pane label="全部" name="ALL" />
  48. <el-tab-pane
  49. v-for="item in EVALUATION_SYSTEM_STATUS_OPTIONS"
  50. :key="item.value"
  51. :label="item.label"
  52. :name="String(item.value)"
  53. />
  54. </template>
  55. <template v-else>
  56. <el-tab-pane :label="singleTabLabel" :name="singleStatus" />
  57. </template>
  58. </el-tabs>
  59. </div>
  60. <header>
  61. <div class="act-search">
  62. <section class="select-box">
  63. <!-- 部门考核结果视图:显示部门和部门负责人输入框 -->
  64. <template v-if="viewTab === 'dept' && operate === 'evaluationSystem-advanced-group'">
  65. <div class="select-box--item">
  66. <span>部门:</span>
  67. <el-input
  68. v-model="tableQuery.queryParam.deptName"
  69. placeholder="请输入部门名称"
  70. clearable
  71. />
  72. </div>
  73. <div class="select-box--item">
  74. <span>部门负责人:</span>
  75. <el-input
  76. v-model="tableQuery.queryParam.userName"
  77. placeholder="请输入部门负责人"
  78. clearable
  79. />
  80. </div>
  81. <!-- <div class="select-box--item">
  82. <span>时间范围:</span>
  83. <el-date-picker
  84. v-model="tableQuery.queryParam.dateRange"
  85. type="daterange"
  86. range-separator="至"
  87. start-placeholder="开始日期"
  88. end-placeholder="结束日期"
  89. value-format="YYYY-MM-DD"
  90. format="YYYY-MM-DD"
  91. />
  92. </div> -->
  93. </template>
  94. <!-- 其他视图:显示原有搜索条件 -->
  95. <template v-else>
  96. <!-- 状态筛选仅在"全部"tab 下显示(包含文字和下拉框) -->
  97. <div
  98. class="select-box--item"
  99. v-if="(showViewTab && viewTab === 'status' && activeTab === 'ALL') || (!showViewTab && activeTab === 'ALL')"
  100. >
  101. <span>状态:</span>
  102. <el-select
  103. v-model="tableQuery.queryParam.status"
  104. placeholder="请选择状态"
  105. clearable
  106. >
  107. <el-option
  108. v-for="item in EVALUATION_SYSTEM_STATUS_OPTIONS"
  109. :key="item.value"
  110. :label="item.label"
  111. :value="item.value"
  112. />
  113. </el-select>
  114. </div>
  115. <div class="select-box--item">
  116. <span>考核对象:</span>
  117. <el-cascader
  118. ref="targetDeptCascaderRef"
  119. v-model="targetDeptId"
  120. :options="deptTree"
  121. :props="cascaderDeptProp"
  122. :show-all-levels="false"
  123. placeholder="请选择考核对象部门"
  124. filterable
  125. @change="handleTargetDeptChange"
  126. />
  127. </div>
  128. <div class="select-box--item">
  129. <span>时间范围:</span>
  130. <el-date-picker
  131. v-model="tableQuery.queryParam.dateRange"
  132. type="daterange"
  133. range-separator="至"
  134. start-placeholder="开始日期"
  135. end-placeholder="结束日期"
  136. value-format="YYYY-MM-DD"
  137. format="YYYY-MM-DD"
  138. />
  139. </div>
  140. </template>
  141. </section>
  142. <section class="search-btn">
  143. <el-button type="primary" @click="handleSearch">查询</el-button>
  144. <el-button @click="handleReset">重置</el-button>
  145. <el-button plain @click="handleExport">
  146. 导出
  147. </el-button>
  148. <!-- 先进集体排名 / 先进个人模式下,顶部按钮只保留“导出” -->
  149. <!-- <el-button
  150. v-if="viewTab === 'status'"
  151. plain
  152. class="search-table-container--button"
  153. @click="handleBatchDelete"
  154. >
  155. 删除
  156. </el-button> -->
  157. </section>
  158. </div>
  159. </header>
  160. <div class="batch-table">
  161. <BasicTable
  162. ref="basicTableRef"
  163. :tableData="tableData"
  164. :tableConfig="tableConfig"
  165. @update:pageSize="handleSizeChange"
  166. @update:pageNumber="handleCurrentChange"
  167. >
  168. <template #status="scope">
  169. <span>
  170. {{ EVALUATION_SYSTEM_STATUS_LABEL[String(scope.row.status)] || '-' }}
  171. </span>
  172. </template>
  173. <template #isAdvancedGroup="scope">
  174. <span>{{ scope.row.isAdvancedGroup ? '是' : '否' }}</span>
  175. </template>
  176. <template #evaluationDocument="scope">
  177. <div
  178. class="file-container--div"
  179. v-for="item in parseAttachments(scope.row.evaluationDocument)"
  180. :key="item.fileUrl"
  181. >
  182. <img
  183. class="file-container--div__icon"
  184. @click="previewOnline(item.fileUrl, item.fileType as keyof typeof FILE_TYPE_ICON)"
  185. :src="FILE_TYPE_ICON[item.fileType]"
  186. />
  187. <span
  188. class="file-container--div__name"
  189. @click="previewOnline(item.fileUrl, item.fileType as keyof typeof FILE_TYPE_ICON)"
  190. >{{ item.fileName }}</span
  191. >
  192. <img
  193. class="file-container--div__download"
  194. :src="DownloadIcon"
  195. @click="downloadFile(item.fileUrl, item.fileName)"
  196. />
  197. </div>
  198. </template>
  199. <template #action="scope">
  200. <div class="action-container--div" style="justify-content: left">
  201. <!-- 先进集体视图:只显示详情 -->
  202. <template v-if="viewTab === 'dept' && operate === 'evaluationSystem-advanced-group'">
  203. <ActionButton text="详情" @click="handleViewDetail(scope.row.id)" />
  204. </template>
  205. <!-- 待反馈:删除 / 作废 -->
  206. <template v-else-if="Number(scope.row.status) === 2">
  207. <ActionButton
  208. text="删除"
  209. :popconfirm="{
  210. title: '确定要删除?',
  211. }"
  212. @confirm="handleDelete(scope.row.id)"
  213. />
  214. <ActionButton
  215. text="作废"
  216. :popconfirm="{
  217. title: '确定要作废?',
  218. }"
  219. @confirm="handleCancel(scope.row.id)"
  220. />
  221. </template>
  222. <!-- 待评分:评分 / 删除 / 作废 -->
  223. <template v-else-if="Number(scope.row.status) === 3">
  224. <ActionButton text="评分" @click="handleScore(scope.row.id)" />
  225. <ActionButton
  226. text="删除"
  227. :popconfirm="{
  228. title: '确定要删除?',
  229. }"
  230. @confirm="handleDelete(scope.row.id)"
  231. />
  232. <ActionButton
  233. text="作废"
  234. :popconfirm="{
  235. title: '确定要作废?',
  236. }"
  237. @confirm="handleCancel(scope.row.id)"
  238. />
  239. </template>
  240. <!-- 待审核:审核 / 删除 / 作废 -->
  241. <template v-else-if="Number(scope.row.status) === 4">
  242. <ActionButton text="审核" @click="handleAudit(scope.row.id)" />
  243. <ActionButton
  244. text="删除"
  245. :popconfirm="{
  246. title: '确定要删除?',
  247. }"
  248. @confirm="handleDelete(scope.row.id)"
  249. />
  250. <ActionButton
  251. text="作废"
  252. :popconfirm="{
  253. title: '确定要作废?',
  254. }"
  255. @confirm="handleCancel(scope.row.id)"
  256. />
  257. </template>
  258. <!-- 已作废:删除 -->
  259. <template v-else-if="Number(scope.row.status) === 5">
  260. <ActionButton
  261. text="删除"
  262. :popconfirm="{
  263. title: '确定要删除?',
  264. }"
  265. @confirm="handleDelete(scope.row.id)"
  266. />
  267. </template>
  268. <!-- 已完成:删除 -->
  269. <template v-else-if="Number(scope.row.status) === 1">
  270. <ActionButton
  271. text="删除"
  272. :popconfirm="{
  273. title: '确定要删除?',
  274. }"
  275. @confirm="handleDelete(scope.row.id)"
  276. />
  277. </template>
  278. </div>
  279. </template>
  280. </BasicTable>
  281. </div>
  282. </div>
  283. </main>
  284. <PreviewOnline ref="previewOnlineRef" />
  285. </div>
  286. </template>
  287. <script lang="ts" setup>
  288. import { computed, onMounted, reactive, ref, watch } from 'vue';
  289. import { useRoute, useRouter } from 'vue-router';
  290. import BasicTable from '@/components/BasicTable.vue';
  291. import useTableConfig from '@/hooks/useTableConfigHook';
  292. import ActionButton from '@/components/ActionButton.vue';
  293. import { TABLE_OPTIONS } from '../configs/tables';
  294. import {
  295. EVALUATION_TARGET_TABLE_COLUMNS,
  296. EVALUATION_ADVANCED_GROUP_TABLE_COLUMNS,
  297. EVALUATION_ADVANCED_PERSON_TABLE_COLUMNS,
  298. } from '../configs/targetTables';
  299. import { EVALUATION_SYSTEM_STATUS_OPTIONS, EVALUATION_SYSTEM_STATUS_LABEL } from '../configs/status';
  300. import type { QueryPageRequest } from '@/types/basic-query';
  301. import { getAllDepartments } from '@/api/auth/dept';
  302. import type { DeptTree } from '@/types/dept/type';
  303. import {
  304. querySecurityExamineIssue,
  305. querySecurityExamineDetail,
  306. updateSecurityExamineIssueRepeal,
  307. deleteSecurityExamineIssue,
  308. querySecurityExamineIssueAdvanced,
  309. } from '@/api/evaluationSystem';
  310. import type { QuerySecurityExamineIssueParams, EvaluationSystemItem } from '@/api/evaluationSystem';
  311. import { ElMessage } from 'element-plus';
  312. import DownloadIcon from '@/views/disaster/disaster-control/src/svg/download.svg';
  313. import { downloadFile } from '@/views/disaster/utils';
  314. import PreviewOnline from '@/views/disaster/components/PreviewOnline.vue';
  315. import { FILE_TYPE_ICON } from '@/components/UploadFiles/constants';
  316. const route = useRoute();
  317. const router = useRouter();
  318. const operate = route.query.operate as string | undefined;
  319. // 考核表详情
  320. const evaluationDetail = ref<Partial<EvaluationSystemItem>>({});
  321. // 是否展示“状态 / 部门考核结果 / 先进个人”顶层 tabs
  322. const showViewTab = computed(
  323. () =>
  324. operate === 'evaluationSystem-advanced-group' ||
  325. operate === 'evaluationSystem-advanced-person',
  326. );
  327. // 顶部 tabs:按状态 / 按部门考核结果(先进集体排名)/ 先进个人
  328. const viewTab = ref<'status' | 'dept' | 'person'>(
  329. operate === 'evaluationSystem-advanced-group'
  330. ? 'dept'
  331. : operate === 'evaluationSystem-advanced-person'
  332. ? 'person'
  333. : 'status',
  334. );
  335. // 如果从上级页传入 singleStatus(例如 3:待评分),则只展示对应状态的 tab
  336. const singleStatus = (route.query.singleStatus as string | undefined) || '';
  337. const showAllTabs = computed(() => !singleStatus);
  338. const singleTabLabel = computed(() => {
  339. if (!singleStatus) return '全部';
  340. const found = EVALUATION_SYSTEM_STATUS_OPTIONS.find(
  341. (item) => String(item.value) === String(singleStatus),
  342. );
  343. return found?.label || '全部';
  344. });
  345. // tabs(ALL:全部,其它为对应状态码字符串)
  346. const activeTab = ref<'ALL' | '0' | '2' | '3' | '4' | '5' | '1'>(
  347. (singleStatus as any) || 'ALL',
  348. );
  349. // 表格
  350. const basicTableRef = ref<InstanceType<typeof BasicTable>>();
  351. const { tableConfig, pagination } = useTableConfig(EVALUATION_TARGET_TABLE_COLUMNS, TABLE_OPTIONS);
  352. const tableData = ref<any[]>([]);
  353. // 部门树(复用下发考核表的部门下拉逻辑)
  354. const targetDeptCascaderRef = ref();
  355. const deptTree = ref<DeptTree[]>([]);
  356. const targetDeptId = ref<number | null>(null);
  357. const cascaderDeptProp = {
  358. checkStrictly: true,
  359. expandTrigger: 'hover' as const,
  360. value: 'id',
  361. label: 'deptName',
  362. emitPath: false,
  363. };
  364. const tableQuery = reactive<QueryPageRequest<any>>({
  365. pageNumber: pagination.pageNumber,
  366. pageSize: pagination.pageSize,
  367. queryParam: {
  368. status: '',
  369. target: '',
  370. deptName: '', // 部门名称(用于部门考核结果视图)
  371. userName: '', // 部门负责人(用于部门考核结果视图)
  372. dateRange: null as any,
  373. startTime: '',
  374. endTime: '',
  375. },
  376. });
  377. // 从路由获取考核表ID(如果有)
  378. const evaluationId = computed(() => {
  379. const id = route.query.id;
  380. return id ? Number(id) : undefined;
  381. });
  382. // 如果是单状态模式(例如待评分),初始化筛选条件
  383. if (singleStatus) {
  384. tableQuery.queryParam.status = Number(singleStatus);
  385. }
  386. // 根据顶部 tabs 切换表格列配置
  387. watch(
  388. viewTab,
  389. (val) => {
  390. if (val === 'status') {
  391. tableConfig.columns = EVALUATION_TARGET_TABLE_COLUMNS;
  392. } else if (val === 'dept') {
  393. tableConfig.columns = EVALUATION_ADVANCED_GROUP_TABLE_COLUMNS;
  394. } else {
  395. tableConfig.columns = EVALUATION_ADVANCED_PERSON_TABLE_COLUMNS;
  396. }
  397. // 切换视图时重查
  398. handleSearch();
  399. },
  400. { immediate: true },
  401. );
  402. const handleSizeChange = (value: number) => {
  403. pagination.pageSize = value;
  404. tableQuery.pageSize = value;
  405. getTableData();
  406. };
  407. const handleCurrentChange = (value: number) => {
  408. pagination.pageNumber = value;
  409. tableQuery.pageNumber = value;
  410. getTableData();
  411. };
  412. async function getTableData() {
  413. tableConfig.loading = true;
  414. try {
  415. // 如果是先进集体(部门考核结果视图),调用先进集体接口
  416. if (viewTab.value === 'dept' && operate === 'evaluationSystem-advanced-group') {
  417. const params = {
  418. pageNumber: tableQuery.pageNumber,
  419. pageSize: tableQuery.pageSize,
  420. queryParam: {
  421. psemId: evaluationId.value, // 考核表ID(从路由参数获取)
  422. planStartTime: tableQuery.queryParam.startTime || undefined,
  423. planEndTime: tableQuery.queryParam.endTime || undefined,
  424. deptName: tableQuery.queryParam.deptName || undefined, // 部门名称
  425. userName: tableQuery.queryParam.userName || undefined, // 部门负责人
  426. },
  427. };
  428. const res = await querySecurityExamineIssueAdvanced(params);
  429. if (res) {
  430. // 映射返回数据字段到表格字段(先进集体排名表格)
  431. tableData.value = res.records.map((item) => {
  432. // 计算总分数、加分项分数、减分项分数
  433. let totalScore = 0;
  434. let addScore = 0;
  435. let subtractScore = 0;
  436. if (item.scores && item.scores.length > 0) {
  437. item.scores.forEach((score: any) => {
  438. const scoreValue = Number(score.reviewScore) || 0;
  439. totalScore += scoreValue;
  440. if (scoreValue >= 0) {
  441. addScore += scoreValue;
  442. } else {
  443. subtractScore += Math.abs(scoreValue);
  444. }
  445. });
  446. }
  447. return {
  448. id: item.id,
  449. departmentName: item.deptName, // 部门考核结果
  450. isAdvancedGroup: item.isAdvancedGroup || false, // 是否先进集体(需要接口返回,暂时使用 false)
  451. departmentSort: item.scoreRank || 0, // 部门排序(使用排名)
  452. departmentLeader: item.deptUserName || '-', // 部门负责人
  453. baseScore: 100, // 基础分,默认为100
  454. totalScore, // 总分数
  455. addScore, // 加分项分数
  456. subtractScore, // 减分项分数
  457. // 保留原始数据
  458. psemId: item.psemId,
  459. deptId: item.deptId,
  460. scores: item.scores,
  461. scoreRank: item.scoreRank,
  462. };
  463. });
  464. pagination.total = res.totalRow;
  465. }
  466. } else if (viewTab.value === 'status') {
  467. // 状态视图下调用原有接口
  468. const params: QuerySecurityExamineIssueParams = {
  469. pageNumber: tableQuery.pageNumber,
  470. pageSize: tableQuery.pageSize,
  471. queryParam: {
  472. psemId: evaluationId.value, // 考核表ID(从路由参数获取)
  473. planStartTime: tableQuery.queryParam.startTime || undefined,
  474. planEndTime: tableQuery.queryParam.endTime || undefined,
  475. deptName: tableQuery.queryParam.target || undefined,
  476. status:
  477. tableQuery.queryParam.status !== '' && tableQuery.queryParam.status !== null
  478. ? Number(tableQuery.queryParam.status)
  479. : undefined,
  480. },
  481. };
  482. const res = await querySecurityExamineIssue(params);
  483. if (res) {
  484. // 映射返回数据字段到表格字段
  485. tableData.value = res.records.map((item) => ({
  486. id: item.id,
  487. evaluationTableName: item.exName, // 考核表名称
  488. status: item.status, // 状态
  489. statusName: item.statusName, // 状态名称
  490. issueDepartment: item.deptName, // 下发部门(部门考核结果)
  491. departmentLeader: item.deptUserName || '-', // 部门负责人
  492. contactPhone: item.deptUserLink || '-', // 联系方式
  493. evaluationDocument: item.attachments, // 考核文档
  494. plannedCompletionTime: item.planEndTime || '-', // 计划完成时间(使用计划结束时间)
  495. // 保留原始数据,供其他操作使用
  496. psemId: item.psemId,
  497. deptId: item.deptId,
  498. scores: item.scores,
  499. scoreRank: item.scoreRank,
  500. }));
  501. pagination.total = res.totalRow;
  502. }
  503. } else {
  504. // 其他视图(如先进个人)暂时为空
  505. tableData.value = [];
  506. pagination.total = 0;
  507. }
  508. } catch (e) {
  509. console.error('获取列表失败:', e);
  510. tableData.value = [];
  511. pagination.total = 0;
  512. } finally {
  513. tableConfig.loading = false;
  514. }
  515. }
  516. const getDeptTreeData = async () => {
  517. try {
  518. const res = await getAllDepartments();
  519. deptTree.value = res?.[0]?.children ?? [];
  520. } catch (e) {
  521. console.error('获取部门树失败:', e);
  522. }
  523. };
  524. const handleTargetDeptChange = () => {
  525. const nodes = targetDeptCascaderRef.value?.getCheckedNodes?.();
  526. tableQuery.queryParam.target = nodes?.[0]?.label ?? '';
  527. };
  528. function handleSearch() {
  529. if (tableQuery.queryParam.dateRange && tableQuery.queryParam.dateRange.length === 2) {
  530. tableQuery.queryParam.startTime = tableQuery.queryParam.dateRange[0];
  531. tableQuery.queryParam.endTime = tableQuery.queryParam.dateRange[1];
  532. } else {
  533. tableQuery.queryParam.startTime = '';
  534. tableQuery.queryParam.endTime = '';
  535. }
  536. pagination.pageNumber = 1;
  537. tableQuery.pageNumber = 1;
  538. getTableData();
  539. }
  540. const handleReset = () => {
  541. tableQuery.queryParam.status = '';
  542. tableQuery.queryParam.target = '';
  543. tableQuery.queryParam.deptName = '';
  544. tableQuery.queryParam.userName = '';
  545. tableQuery.queryParam.dateRange = null;
  546. targetDeptId.value = null;
  547. handleSearch();
  548. };
  549. const handleTabChange = (name: string | number) => {
  550. if (name === 'ALL') {
  551. tableQuery.queryParam.status = '';
  552. } else {
  553. tableQuery.queryParam.status = Number(name);
  554. }
  555. handleSearch();
  556. };
  557. const handleExport = () => {
  558. // TODO: 导出当前筛选结果
  559. console.log('export evaluation target list', tableQuery);
  560. };
  561. const handleBatchDelete = () => {
  562. // TODO: 批量删除
  563. console.log('batch delete evaluation targets');
  564. };
  565. const handleDelete = async (id: number) => {
  566. try {
  567. await deleteSecurityExamineIssue(id);
  568. ElMessage.success('删除成功');
  569. getTableData();
  570. } catch (e: any) {
  571. console.error('删除失败:', e);
  572. ElMessage.error(e?.message || '删除失败,请重试');
  573. }
  574. };
  575. const handleCancel = async (id: number) => {
  576. try {
  577. await updateSecurityExamineIssueRepeal(id);
  578. ElMessage.success('作废成功');
  579. getTableData();
  580. } catch (e: any) {
  581. console.error('作废失败:', e);
  582. ElMessage.error(e?.message || '作废失败,请重试');
  583. }
  584. };
  585. const handleScore = (id: number) => {
  586. router.push({
  587. name: 'EvaluationSystemItem',
  588. query: {
  589. id,
  590. operate: 'evaluationSystem-feedback',
  591. },
  592. });
  593. };
  594. const handleAudit = (id: number) => {
  595. router.push({
  596. name: 'EvaluationSystemItem',
  597. query: {
  598. id,
  599. operate: 'evaluationSystem-audit',
  600. },
  601. });
  602. };
  603. const handleViewDetail = (id: number) => {
  604. router.push({
  605. name: 'EvaluationSystemItem',
  606. query: {
  607. id,
  608. operate: 'evaluationSystem-feedback-view',
  609. fromDeptView: viewTab.value === 'dept' && operate === 'evaluationSystem-advanced-group' ? 'true' : undefined,
  610. },
  611. });
  612. };
  613. // 预览
  614. const previewOnlineRef = ref<InstanceType<typeof PreviewOnline>>();
  615. const previewOnline = (url: string | undefined, type: keyof typeof FILE_TYPE_ICON) => {
  616. if (url) {
  617. previewOnlineRef.value?.open(url, type);
  618. }
  619. };
  620. // 解析逗号分隔的URL字符串为文件列表
  621. const parseAttachments = (attachmentsStr: string | undefined): Array<{
  622. fileUrl: string;
  623. fileName: string;
  624. fileType: string;
  625. }> => {
  626. if (!attachmentsStr || !attachmentsStr.trim()) {
  627. return [];
  628. }
  629. // 按逗号分割URL
  630. const urls = attachmentsStr.split(',').map(url => url.trim()).filter(url => url);
  631. return urls.map((url) => {
  632. // 从URL中提取文件名
  633. const urlParts = url.split('/');
  634. const fileName = urlParts[urlParts.length - 1] || '未知文件';
  635. // 根据文件扩展名判断文件类型
  636. const extension = fileName.split('.').pop()?.toLowerCase() || '';
  637. let fileType = 'pdf';
  638. if (extension === 'doc' || extension === 'docx') {
  639. fileType = 'word';
  640. } else if (extension === 'xls' || extension === 'xlsx') {
  641. fileType = 'excel';
  642. } else if (extension === 'ppt' || extension === 'pptx') {
  643. fileType = 'ppt';
  644. }
  645. return {
  646. fileUrl: url,
  647. fileName,
  648. fileType,
  649. };
  650. });
  651. };
  652. // 格式化日期时间
  653. const formatDateTime = (dateTimeStr: string | undefined): string => {
  654. if (!dateTimeStr) return '';
  655. try {
  656. const date = new Date(dateTimeStr);
  657. const year = date.getFullYear();
  658. const month = String(date.getMonth() + 1).padStart(2, '0');
  659. const day = String(date.getDate()).padStart(2, '0');
  660. const hours = String(date.getHours()).padStart(2, '0');
  661. const minutes = String(date.getMinutes()).padStart(2, '0');
  662. const seconds = String(date.getSeconds()).padStart(2, '0');
  663. return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  664. } catch (e) {
  665. return dateTimeStr;
  666. }
  667. };
  668. // 获取考核表详情
  669. const getEvaluationDetail = async () => {
  670. if (!evaluationId.value) return;
  671. try {
  672. const res = await querySecurityExamineDetail(evaluationId.value);
  673. if (res) {
  674. evaluationDetail.value = {
  675. exName: res.exName,
  676. deptNames: res.deptNames,
  677. createdUserName: res.createdUserName,
  678. createdAt: res.createdAt,
  679. };
  680. }
  681. } catch (e) {
  682. console.error('获取考核表详情失败:', e);
  683. }
  684. };
  685. onMounted(() => {
  686. getDeptTreeData();
  687. getTableData();
  688. getEvaluationDetail();
  689. });
  690. </script>
  691. <style scoped lang="scss">
  692. @use '@/styles/page-details-layout.scss' as *;
  693. @use '@/styles/page-main-layout.scss' as *;
  694. @use '@/styles/basic-table-action.scss' as *;
  695. @use '@/styles/basic-table-file.scss' as *;
  696. @use '@/views/traffic/violation/style/act-search-table.scss' as *;
  697. .safety-platform-container__header {
  698. padding-bottom: 0 !important;
  699. }
  700. .evaluation-header {
  701. width: 100%;
  702. }
  703. .evaluation-title {
  704. font-size: 20px;
  705. font-weight: bold;
  706. margin: 0 0 12px 0;
  707. color: #333;
  708. }
  709. .evaluation-meta {
  710. margin-bottom: 12px;
  711. display: flex;
  712. gap: 24px;
  713. font-size: 14px;
  714. color: #666;
  715. }
  716. .evaluation-meta span {
  717. white-space: nowrap;
  718. }
  719. </style>