EvaluationTarget.vue 27 KB

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