areaCheckPlanManagementDeptDetail.vue 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048
  1. <template>
  2. <!-- 区域检查计划管理(部门)查看:与管理员查看样式一致 -->
  3. <main class="safety-platform-container__main area-check-plan-view">
  4. <section class="view-section view-summary">
  5. <div class="view-summary__title">一级危险点:{{ viewDetail.checkVenue || '-' }}</div>
  6. <div class="view-summary__meta">
  7. <span>检查类别:{{ viewDetail.venueCategoryName || '-' }}</span>
  8. <span>创建人:{{ viewDetail.createdPersonName || '-' }}</span>
  9. <span>创建时间:{{ viewDetail.createdAt || '-' }}</span>
  10. </div>
  11. </section>
  12. <section class="view-section audit-content">
  13. <h4 class="section-title">
  14. <el-icon class="section-title__icon"><Document /></el-icon>
  15. <span>基本信息</span>
  16. </h4>
  17. <div class="detail-ct detail-ct--table">
  18. <div class="row">
  19. <div class="col">
  20. <div class="label">区域计划名称:</div>
  21. <div class="value">{{ viewDetail.planName || '-' }}</div>
  22. </div>
  23. <div class="col">
  24. <div class="label">状态:</div>
  25. <div class="value">{{ viewDetail.statusName || '进行中' }}</div>
  26. </div>
  27. </div>
  28. <div class="row">
  29. <div class="col">
  30. <div class="label">主责部门:</div>
  31. <div class="value">{{ viewDetail.primaryResponsibleDeptName || '-' }}</div>
  32. </div>
  33. <div class="col">
  34. <div class="label">自查频率:</div>
  35. <div class="value">{{ viewDetail.selfCheckFrequency || '-' }}</div>
  36. </div>
  37. </div>
  38. <div class="row">
  39. <div class="col">
  40. <div class="label">主责部门执行人所属分组名称:</div>
  41. <div class="value">{{ viewDetail.primaryResponsibleDeptExecGroupName || '-' }}</div>
  42. </div>
  43. <div class="col">
  44. <div class="label">主责部门责任人:</div>
  45. <div class="value">{{ viewDetail.primaryResponsibleDeptPersonName || '-' }}</div>
  46. </div>
  47. </div>
  48. <div class="row">
  49. <div class="col">
  50. <div class="label">安全应急部部门名称:</div>
  51. <div class="value">{{ viewDetail.safetyEmergencyDeptName || '-' }}</div>
  52. </div>
  53. <div class="col">
  54. <div class="label">安全应急部检查频次:</div>
  55. <div class="value">{{ viewDetail.safetyEmergencyCheckFrequency || '-' }}</div>
  56. </div>
  57. </div>
  58. <div class="row">
  59. <div class="col">
  60. <div class="label">安全应急部执行人所属分组名称:</div>
  61. <div class="value">{{ viewDetail.safetyEmergencyExecutorGroupName || '-' }}</div>
  62. </div>
  63. <div class="col">
  64. <div class="label">安全应急部责任人:</div>
  65. <div class="value">{{ viewDetail.safetyEmergencyResponsiblePerson || '-' }}</div>
  66. </div>
  67. </div>
  68. <div class="row">
  69. <div class="col">
  70. <div class="label">院领导部门名称:</div>
  71. <div class="value">{{ viewDetail.hospitalLeaderDeptName || '-' }}</div>
  72. </div>
  73. <div class="col">
  74. <div class="label">院领导检查频次:</div>
  75. <div class="value">{{ viewDetail.hospitalLeaderCheckFrequency || '-' }}</div>
  76. </div>
  77. </div>
  78. <div class="row">
  79. <div class="col">
  80. <div class="label">院领导执行人所属分组名称:</div>
  81. <div class="value">{{ viewDetail.hospitalLeaderExecutorGroupName || '-' }}</div>
  82. </div>
  83. <div class="col">
  84. <div class="label">院领导责任人:</div>
  85. <div class="value">{{ viewDetail.hospitalLeaderResponsiblePerson || '-' }}</div>
  86. </div>
  87. </div>
  88. <div class="row">
  89. <div class="col">
  90. <div class="label">检查单所属类别名称:</div>
  91. <div class="value">{{ viewDetail.categoryName || '-' }}</div>
  92. </div>
  93. <div class="col">
  94. <div class="label">检查单模版名称:</div>
  95. <div class="value">{{ viewDetail.checklistTemplateName || '-' }}</div>
  96. </div>
  97. </div>
  98. <div class="row">
  99. <div class="col">
  100. <div class="label">是否需要整体检查情况描述:</div>
  101. <div class="value">{{ viewDetail.needOverallDesc === true ? '是' : viewDetail.needOverallDesc === false ? '否' : '-' }}</div>
  102. </div>
  103. <div class="col">
  104. <div class="label">是否需要被检查人签字:</div>
  105. <div class="value">{{ viewDetail.needSigneeSign === true ? '是' : viewDetail.needSigneeSign === false ? '否' : '-' }}</div>
  106. </div>
  107. </div>
  108. <div class="row">
  109. <div class="col">
  110. <div class="label">计划开始日期:</div>
  111. <div class="value">{{ viewDetail.planStartTime || '-' }}</div>
  112. </div>
  113. <div class="col">
  114. <div class="label">计划完成日期:</div>
  115. <div class="value">{{ viewDetail.planEndTime || '-' }}</div>
  116. </div>
  117. </div>
  118. <div class="row">
  119. <div class="col">
  120. <div class="label">检查内容:</div>
  121. <div class="value value--list">
  122. <ol class="inspection-content-list">
  123. <li v-for="(item, i) in inspectionContentList" :key="i">{{ item }}</li>
  124. </ol>
  125. </div>
  126. </div>
  127. <div class="col">
  128. <div class="label">业务工作:</div>
  129. <div class="value">{{ viewDetail.businessWork || '-' }}</div>
  130. </div>
  131. </div>
  132. </div>
  133. </section>
  134. <section class="view-section">
  135. <div class="view-section__title">
  136. <span class="view-section__icon"></span>
  137. 检查记录
  138. </div>
  139. <div style="margin-bottom: 20px;">
  140. <el-button type="primary" @click="onAddRecord">新增检查日志</el-button>
  141. </div>
  142. <div class="view-record-toolbar">
  143. <el-input
  144. v-model="recordSearchKeyword"
  145. placeholder="检查场所类别/检查场所/检查人员"
  146. clearable
  147. style="width: 280px; margin-right: 12px"
  148. />
  149. <el-date-picker
  150. v-model="recordDateRange"
  151. type="daterange"
  152. range-separator="-"
  153. start-placeholder="开始日期"
  154. end-placeholder="结束日期"
  155. value-format="YYYY-MM-DD"
  156. style="margin-right: 12px;max-width: 280px;"
  157. />
  158. <el-button type="primary" class="view-record-toolbar-btn" @click="onRecordSearch">查询</el-button>
  159. <el-button @click="onRecordExport">导出</el-button>
  160. </div>
  161. <BasicTable
  162. :tableData="paginatedRecordList"
  163. :tableConfig="recordTableConfig"
  164. class="view-record-table"
  165. @update:pageSize="handleRecordSizeChange"
  166. @update:pageNumber="handleRecordPageChange"
  167. >
  168. <template #unqualifiedItemNum="scope">
  169. <el-button
  170. type="primary"
  171. link
  172. size="small"
  173. @click="openUnqualifiedDialog(scope.row)"
  174. >
  175. {{ scope.row.unqualifiedItemNum ?? 0 }}
  176. </el-button>
  177. </template>
  178. <template #sign="scope">
  179. <template v-if="JSON.parse(scope.row.checkedPersonSign || '[]').length">
  180. <div
  181. class="file-container--div"
  182. v-for="item in JSON.parse(scope.row.checkedPersonSign || '[]')"
  183. :key="item.fileUrl || item.fileName"
  184. >
  185. <img
  186. class="file-container--div__icon"
  187. :src="FILE_TYPE_ICON[item.fileType as keyof typeof FILE_TYPE_ICON]"
  188. @click="previewOnline(item.fileUrl, item.fileType as keyof typeof FILE_TYPE_ICON)"
  189. />
  190. <span
  191. class="file-container--div__name"
  192. @click="previewOnline(item.fileUrl, item.fileType as keyof typeof FILE_TYPE_ICON)"
  193. >{{ item.fileName }}</span
  194. >
  195. <img
  196. class="file-container--div__download"
  197. :src="DownloadIcon"
  198. @click="downloadFile(item.fileUrl, item.fileName)"
  199. />
  200. </div>
  201. </template>
  202. <span v-else>-</span>
  203. </template>
  204. <template #action="scope">
  205. <el-button type="primary" link size="small" @click="onViewRecord(scope.row)">检查记录查看</el-button>
  206. </template>
  207. </BasicTable>
  208. </section>
  209. </main>
  210. <PreviewOnline ref="previewOnlineRef" />
  211. <!-- 检查不合格数据弹窗(部门) -->
  212. <el-dialog
  213. v-model="showUnqualifiedDialog"
  214. title="检查不合格数据"
  215. width="800px"
  216. destroy-on-close
  217. >
  218. <BasicTable
  219. :tableData="unqualifiedList"
  220. :tableConfig="unqualifiedTableConfig"
  221. @update:pageSize="handleUnqualifiedSizeChange"
  222. @update:pageNumber="handleUnqualifiedPageChange"
  223. >
  224. <template #action="scope">
  225. <el-button
  226. v-if="Number(scope.row.IsSand) == 0"
  227. type="primary"
  228. link
  229. size="small"
  230. @click="handleSandToHiddenDangerDept(scope.row)"
  231. >
  232. 入账
  233. </el-button>
  234. <span v-else-if="Number(scope.row.IsSand) == 1" style="color: #999999">已入账</span>
  235. <span v-else>-</span>
  236. </template>
  237. </BasicTable>
  238. </el-dialog>
  239. <!-- 部门端:检查记录入账隐患台账确认弹窗(仅查看提示,不真正入账) -->
  240. <el-dialog
  241. v-model="showSandConfirmDialogDept"
  242. title="检查记录入账隐患台账"
  243. width="900px"
  244. destroy-on-close
  245. >
  246. <BasicForm
  247. ref="sandHiddenDangerFormRefDept"
  248. :formData="sandHiddenDangerFormDataDept"
  249. :formRules="sandHiddenDangerFormRulesDept"
  250. :formConfig="sandHiddenDangerFormConfigDept"
  251. >
  252. <template #typeId>
  253. <el-form-item prop="typeId" style="width: 100%;">
  254. <el-select
  255. v-model="sandHiddenDangerFormDataDept.typeId"
  256. placeholder="请选择隐患问题类别"
  257. filterable
  258. clearable
  259. style="width: 100%"
  260. >
  261. <el-option
  262. v-for="item in dangerTypeList"
  263. :key="item.id"
  264. :label="item.categoryName"
  265. :value="item.id"
  266. />
  267. </el-select>
  268. </el-form-item>
  269. </template>
  270. <template #rectificationDepartmentIds>
  271. <el-form-item prop="rectificationDepartmentIds" style="width: 100%;">
  272. <el-cascader
  273. v-model="sandHiddenDangerFormDataDept.rectificationDepartmentIds"
  274. :options="issueDeptTree"
  275. :props="cascaderDeptProp"
  276. :show-all-levels="false"
  277. placeholder="请选择整改责任部门"
  278. filterable
  279. clearable
  280. @change="onIssueDeptChange"
  281. />
  282. </el-form-item>
  283. </template>
  284. <template #rectificationResponsibleIds>
  285. <el-form-item prop="rectificationResponsibleIds" style="width: 100%;">
  286. <el-select
  287. v-model="sandHiddenDangerFormDataDept.rectificationResponsibleIds"
  288. placeholder="选择整改负责人"
  289. filterable
  290. clearable
  291. style="width: 100%"
  292. >
  293. <el-option
  294. v-for="user in issueUserList"
  295. :key="user.id"
  296. :label="user.realname ?? user.username"
  297. :value="user.id"
  298. />
  299. </el-select>
  300. </el-form-item>
  301. </template>
  302. <template #reviewDepartmentId>
  303. <el-cascader
  304. v-model="sandHiddenDangerFormDataDept.reviewDepartmentId"
  305. :options="deptTree"
  306. :props="cascaderDeptProp"
  307. :show-all-levels="false"
  308. placeholder="请选择复查人员所属部门"
  309. filterable
  310. clearable
  311. style="width: 100%"
  312. />
  313. </template>
  314. <template #reviewPerson>
  315. <el-select
  316. v-model="sandHiddenDangerFormDataDept.reviewPersonId"
  317. placeholder="请选择复查人员"
  318. clearable
  319. filterable
  320. style="width: 100%"
  321. >
  322. <el-option
  323. v-for="u in reviewUserListDept"
  324. :key="u.id"
  325. :label="u.realname || u.username"
  326. :value="u.id"
  327. />
  328. </el-select>
  329. </template>
  330. <template #isDrawLessonsPush>
  331. <el-radio-group v-model="sandHiddenDangerFormDataDept.isDrawLessonsPush" @change="isDrawLessonsPushChange">
  332. <el-radio :value="0">否</el-radio>
  333. <el-radio :value="1">是</el-radio>
  334. </el-radio-group>
  335. </template>
  336. <template #drawLessonsDepartmentIds>
  337. <el-select
  338. v-model="drawLessonsDeptIdsArrayDept"
  339. placeholder="请选择举一反三责任部门,可多选"
  340. clearable
  341. filterable
  342. multiple
  343. collapse-tags
  344. collapse-tags-tooltip
  345. style="width: 100%"
  346. @change="() => { sandHiddenDangerFormDataDept.drawLessonsDepartmentIds = drawLessonsDeptIdsArrayDept.join(','); }"
  347. >
  348. <el-option
  349. v-for="d in deptOptions"
  350. :key="d.id"
  351. :label="d.deptName"
  352. :value="d.id"
  353. />
  354. </el-select>
  355. </template>
  356. <template #drawLessonsDeadline>
  357. <el-form-item prop="drawLessonsDeadline" style="width: 100%;">
  358. <el-date-picker
  359. v-model="sandHiddenDangerFormDataDept.drawLessonsDeadline"
  360. type="date"
  361. value-format="YYYY-MM-DD"
  362. placeholder="请选择举一反三截止日期(选填)"
  363. />
  364. </el-form-item>
  365. </template>
  366. </BasicForm>
  367. <template #footer>
  368. <el-button @click="showSandConfirmDialogDept = false">取消</el-button>
  369. <el-button type="primary" :loading="sandConfirmLoadingDept" @click="confirmSandToHiddenDangerDept">
  370. 提交
  371. </el-button>
  372. </template>
  373. </el-dialog>
  374. </template>
  375. <script setup lang="ts">
  376. import { computed, onMounted, ref } from 'vue';
  377. import { useRoute, useRouter } from 'vue-router';
  378. import { ElMessage } from 'element-plus';
  379. import { Document } from '@element-plus/icons-vue';
  380. import BasicTable from '@/components/BasicTable.vue';
  381. import { FILE_TYPE_ICON } from '@/components/UploadFiles/constants';
  382. import DownloadIcon from '@/views/disaster/disaster-control/src/svg/download.svg';
  383. import PreviewOnline from '@/views/disaster/components/PreviewOnline.vue';
  384. import { downloadFile } from '@/views/disaster/utils';
  385. import useTableConfig from '@/hooks/useTableConfigHook';
  386. import type { TableColumnProps } from '@/types/basic-table';
  387. import { AREA_CHECK_PLAN_STATUS_LABEL } from '../../areaCheckPlanManagement/configs/status';
  388. import {
  389. queryAreaCheckPlanManageDeptDetail,
  390. queryAreaCheckPlanDetailDeptPage,
  391. mapAreaCheckPlanApiRecordToUi,
  392. queryUnqualifiedItemNumDeptPage,
  393. sandAreaCheckRecordToProductionHiddenDanger,
  394. exportAreaCheckInspectionRecord,
  395. type UnqualifiedItemNumRecord,
  396. type SandAreaCheckRecordToHiddenDangerReq,
  397. } from '@/api/production-safety-system';
  398. import BasicForm from '@/components/BasicForm.vue';
  399. import { useFormConfigHook } from '@/hooks/useFormConfigHook';
  400. import { getAllDepartments } from '@/api/auth/dept';
  401. import type { DeptTree } from '@/types/dept/type';
  402. import {
  403. HIDDEN_DANGER_FORM_CONFIG,
  404. HIDDEN_DANGER_FORM_DATA,
  405. HIDDEN_DANGER_FORM_RULES,
  406. } from '@/views/production-safety/hiddenTroubleInvestigationAndGovernance/hiddenTroubleAccountManagement/configs/form';
  407. import { queryAvailableUserList } from '@/api/production-safety/responsibility-implementation';
  408. import { downloadByData } from '@/utils/file/download';
  409. import {
  410. queryDangerTypePage,
  411. } from '@/api/production-safety';
  412. const router = useRouter();
  413. const route = useRoute();
  414. const currentId = computed(() => Number(route.query.id));
  415. const viewDetailData = ref<Record<string, unknown>>({});
  416. const viewDetail = computed(() => {
  417. const d = viewDetailData.value;
  418. const status = d?.status as number | undefined;
  419. console.log(d,'d')
  420. return {
  421. ...d,
  422. statusName: status != null ? AREA_CHECK_PLAN_STATUS_LABEL[String(status)] ?? '-' : '-',
  423. planName: d?.planName ?? '-',
  424. venueCategoryName: d?.venueCategoryName ?? '-',
  425. checkVenue: d?.checkVenue ?? '-',
  426. mainDeptName: d?.mainDeptName ?? '-',
  427. primaryResponsibleDeptName: (d?.primaryResponsibleDeptName ?? d?.mainDeptName) ?? '-',
  428. selfCheckFrequency: d?.selfCheckFrequency ?? '-',
  429. mainDeptExecutorGroupName: d?.mainDeptExecutorGroupName ?? '-',
  430. mainDeptResponsiblePerson: d?.mainDeptResponsiblePerson ?? '-',
  431. safetyEmergencyDeptName: d?.safetyEmergencyDeptName ?? '-',
  432. safetyEmergencyCheckFrequency: d?.safetyEmergencyCheckFrequency ?? '-',
  433. safetyEmergencyExecutorGroupName: d?.safetyEmergencyExecutorGroupName ?? '-',
  434. safetyEmergencyResponsiblePerson: d?.safetyEmergencyResponsiblePerson ?? '-',
  435. hospitalLeaderDeptName: d?.hospitalLeaderDeptName ?? '-',
  436. hospitalLeaderCheckFrequency: d?.hospitalLeaderCheckFrequency ?? '-',
  437. hospitalLeaderExecutorGroupName: d?.hospitalLeaderExecutorGroupName ?? '-',
  438. hospitalLeaderResponsiblePerson: d?.hospitalLeaderResponsiblePerson ?? '-',
  439. checklistCategoryName: d?.checklistCategoryName ?? '-',
  440. categoryName: (d?.categoryName ?? d?.checklistCategoryName) ?? '-',
  441. checklistTemplateName: d?.checklistTemplateName ?? '-',
  442. needOverallDesc: d?.needOverallDesc,
  443. needSigneeSign: d?.needSigneeSign,
  444. planStartTime: d?.planStartTime ?? '-',
  445. planEndTime: d?.planEndTime ?? '-',
  446. createdPersonName: (d?.createdPersonName ?? '') || '-',
  447. createdAt: (d?.createdAt ?? '') || '-',
  448. businessWork: (d?.businessWork ?? '') || '-',
  449. primaryResponsibleDeptExecGroupName: d?.primaryResponsibleDeptExecGroupName ?? '-',
  450. primaryResponsibleDeptPersonName: d?.primaryResponsibleDeptPersonName ?? '-',
  451. };
  452. });
  453. const issueDeptTree = ref<DeptTree[]>([]);
  454. const issueUserList = ref<Array<{ id: number; realname?: string; username?: string }>>([]);
  455. const onIssueDialogOpen = async () => {
  456. try {
  457. const [deptRes, userRes] = await Promise.all([
  458. getAllDepartments(),
  459. queryAvailableUserList({ pageNumber: 1, pageSize: 9999, queryParam: {} }),
  460. ]);
  461. const fullTree = (deptRes as DeptTree[]) ?? [];
  462. issueDeptTree.value = Array.isArray(fullTree) && fullTree[0]?.children ? fullTree[0].children : [];
  463. issueUserList.value = (userRes as any)?.records ?? [];
  464. } catch (e:any) {
  465. console.error('获取部门/用户列表失败:', e);
  466. ElMessage.error(e?.message || e?.data || '加载部门或负责人列表失败');
  467. issueDeptTree.value = [];
  468. issueUserList.value = [];
  469. }
  470. };
  471. const inspectionContentList = computed(() => {
  472. const content = (viewDetailData.value?.checkKeyContent ?? '') as string;
  473. if (!content || typeof content !== 'string') {
  474. return [
  475. '工作场所布局是否合理,通道是否畅通;',
  476. '各种机械、电力、电气等设备的安装和使用是否符合安全技术要求;',
  477. '安全防护装置是否齐全、灵敏、有效;',
  478. '易燃易爆、有限空间和高处作业等作业场所是否符合安全条件;',
  479. '有较大危险因素和职业危害因素的科研试验场所和有关;',
  480. ];
  481. }
  482. return content.split(/[;;]/).map((s) => s.trim()).filter(Boolean);
  483. });
  484. const RECORD_TABLE_COLUMNS: TableColumnProps[] = [
  485. { label: '编号', type: 'index', align: 'center', width: '80px' },
  486. { label: '检查时间', prop: 'checkTime', minWidth: '160px' },
  487. { label: '检查人员', prop: 'checkPerson', minWidth: '100px' },
  488. { label: '检查场所类别', prop: 'checkPlaceCategory', minWidth: '120px' },
  489. { label: '检查场所', prop: 'checkPlace', minWidth: '120px' },
  490. { label: '检查项总数', prop: 'checkItemTotal', align: 'center', width: '100px' },
  491. { label: '合格项数', prop: 'qualifiedItemNum', align: 'center', width: '90px' },
  492. { label: '不合格项数', prop: 'unqualifiedItemNum', slot: 'unqualifiedItemNum', align: 'center', width: '100px' },
  493. { label: '整体检查情况描述', prop: 'overallCheckDesc', minWidth: '180px', showOverflowTooltip: true },
  494. { label: '被检查人签字', slot: 'sign', align: 'center', width: '140px' },
  495. { label: '操作', slot: 'action', align: 'center', width: '160px', fixed: 'right' },
  496. ];
  497. const RECORD_TABLE_OPTIONS = {
  498. emptyText: '暂无检查记录',
  499. loading: false,
  500. maxHeight: '400px',
  501. stripe: true,
  502. };
  503. const { tableConfig: recordTableConfig, pagination: recordPagination } = useTableConfig(
  504. RECORD_TABLE_COLUMNS,
  505. RECORD_TABLE_OPTIONS,
  506. true,
  507. );
  508. const recordSearchKeyword = ref('');
  509. const recordDateRange = ref<[string, string] | null>(null);
  510. const inspectionRecordList = ref<Array<Record<string, unknown>>>([]);
  511. const paginatedRecordList = computed(() => inspectionRecordList.value);
  512. // 检查不合格数据弹窗
  513. const showUnqualifiedDialog = ref(false);
  514. const currentRecordIdForUnqualified = ref<number | null>(null);
  515. const currentRowForUnqualified = ref<Record<string, unknown> | null>(null);
  516. const unqualifiedList = ref<UnqualifiedItemNumRecord[]>([]);
  517. const UNQUALIFIED_TABLE_COLUMNS: TableColumnProps[] = [
  518. { label: '编号', type: 'index', align: 'center', width: '80px' },
  519. { label: '检查场所', prop: 'checkPlace', minWidth: '200px' },
  520. { label: '发现问题', prop: 'checkProblem', minWidth: '220px' },
  521. { label: '检查时间', prop: 'checkTime', minWidth: '180px' },
  522. { label: '操作', slot: 'action', align: 'center', width: '100px' },
  523. ];
  524. const UNQUALIFIED_TABLE_OPTIONS = {
  525. emptyText: '暂无不合格数据',
  526. loading: false,
  527. maxHeight: '400px',
  528. stripe: true,
  529. };
  530. const { tableConfig: unqualifiedTableConfig, pagination: unqualifiedPagination } = useTableConfig(
  531. UNQUALIFIED_TABLE_COLUMNS,
  532. UNQUALIFIED_TABLE_OPTIONS,
  533. true,
  534. );
  535. // 部门端入账弹窗数据(复用隐患台账新增表单字段与样式)
  536. const showSandConfirmDialogDept = ref(false);
  537. const sandConfirmLoadingDept = ref(false);
  538. const sandHiddenDangerFormRefDept = ref<InstanceType<typeof BasicForm>>();
  539. const {
  540. ruleFormData: sandHiddenDangerFormDataDept,
  541. formRules: sandHiddenDangerFormRulesDept,
  542. ruleFormConfig: sandHiddenDangerFormConfigDept,
  543. } = useFormConfigHook(
  544. HIDDEN_DANGER_FORM_CONFIG,
  545. HIDDEN_DANGER_FORM_DATA as Record<string, unknown>,
  546. HIDDEN_DANGER_FORM_RULES,
  547. );
  548. // 隐患类别数据
  549. const dangerTypeList = ref<any[]>([])
  550. const getTableData = async ()=> {
  551. try {
  552. // 将日期范围同步到查询参数
  553. let params = {
  554. pageNumber: 1,
  555. pageSize: 1000,
  556. queryParam: {
  557. status: 1,
  558. },
  559. }
  560. const res = await queryDangerTypePage(params);
  561. if (res) {
  562. dangerTypeList.value = res.records || [];
  563. }
  564. } catch (e) {
  565. console.error('获取隐患类别列表失败:', e);
  566. dangerTypeList.value = [];
  567. }
  568. }
  569. // 复用部门树与用户下拉,用于复查部门/人员 & 举一反三责任部门
  570. const deptTree = ref<DeptTree[]>([]);
  571. const cascaderDeptProp = {
  572. checkStrictly: true,
  573. expandTrigger: 'hover' as const,
  574. value: 'id',
  575. label: 'deptName',
  576. emitPath: false,
  577. };
  578. function flattenDeptTree(nodes: DeptTree[] | undefined): Array<{ id: number; deptName: string }> {
  579. if (!nodes?.length) return [];
  580. const list: Array<{ id: number; deptName: string }> = [];
  581. const walk = (items: DeptTree[]) => {
  582. items.forEach((n) => {
  583. if (n.id != null) list.push({ id: n.id, deptName: n.deptName });
  584. if (n.children?.length) walk(n.children);
  585. });
  586. };
  587. walk(nodes);
  588. return list;
  589. }
  590. const deptOptions = computed(() => flattenDeptTree(deptTree.value));
  591. const drawLessonsDeptIdsArrayDept = ref<number[]>([]);
  592. const reviewUserListDept = ref<Array<{ id: number; realname?: string; username?: string }>>([]);
  593. const loadDeptAndUserOptionsDept = async () => {
  594. try {
  595. const [deptRes, userRes] = await Promise.all([
  596. getAllDepartments(),
  597. queryAvailableUserList({ pageNumber: 1, pageSize: 9999, queryParam: {} }),
  598. ]);
  599. const fullTree = (deptRes as DeptTree[]) ?? [];
  600. deptTree.value = Array.isArray(fullTree) && fullTree[0]?.children ? fullTree[0].children : [];
  601. reviewUserListDept.value = (userRes as any)?.records ?? [];
  602. } catch (e) {
  603. console.error('获取部门/用户列表失败:', e);
  604. deptTree.value = [];
  605. reviewUserListDept.value = [];
  606. }
  607. };
  608. const loadRecordList = async () => {
  609. if (!currentId.value) return;
  610. recordTableConfig.loading = true;
  611. try {
  612. const [start, end] = recordDateRange.value && recordDateRange.value.length === 2
  613. ? recordDateRange.value
  614. : ['', ''];
  615. const res = await queryAreaCheckPlanDetailDeptPage(currentId.value, {
  616. pageNumber: recordPagination.pageNumber,
  617. pageSize: recordPagination.pageSize,
  618. queryParam: {
  619. searchKey: recordSearchKeyword.value || undefined,
  620. startDate: start || undefined,
  621. endDate: end || undefined,
  622. id: currentId.value,
  623. },
  624. });
  625. const raw = (res as { data?: { records?: Array<Record<string, unknown>>; totalRow?: number } })?.data ?? res;
  626. const records = raw?.records ?? [];
  627. inspectionRecordList.value = records.map((r: Record<string, unknown>) => ({
  628. ...r,
  629. checkPerson: r.checkPerson ?? r.checkPersonName,
  630. checkedCompany: r.checkedCompany ?? r.checkedCompanyName,
  631. }));
  632. recordPagination.total = raw?.totalRow ?? 0;
  633. } catch (e) {
  634. console.error('查询检查记录失败:', e);
  635. inspectionRecordList.value = [];
  636. recordPagination.total = 0;
  637. } finally {
  638. recordTableConfig.loading = false;
  639. }
  640. };
  641. const handleRecordSizeChange = (value: number) => {
  642. recordPagination.pageSize = value;
  643. recordPagination.pageNumber = 1;
  644. loadRecordList();
  645. };
  646. const handleRecordPageChange = (value: number) => {
  647. recordPagination.pageNumber = value;
  648. loadRecordList();
  649. };
  650. const onRecordSearch = () => {
  651. recordPagination.pageNumber = 1;
  652. loadRecordList();
  653. };
  654. const onRecordExport = async () => {
  655. try {
  656. const response = await exportAreaCheckInspectionRecord(currentId.value);
  657. if (response) {
  658. const fileName = `检查记录_${new Date().toISOString().split('T')[0]}.xlsx`;
  659. downloadByData(response, fileName);
  660. ElMessage.success('导出成功');
  661. }
  662. } catch (e) {
  663. console.error('导出院级文件失败:', e);
  664. ElMessage.error('导出失败,请重试');
  665. }
  666. };
  667. const loadUnqualifiedList = async () => {
  668. if (!currentRecordIdForUnqualified.value) return;
  669. unqualifiedTableConfig.loading = true;
  670. try {
  671. const res = await queryUnqualifiedItemNumDeptPage({
  672. pageNumber: unqualifiedPagination.pageNumber,
  673. pageSize: unqualifiedPagination.pageSize,
  674. queryParam: {
  675. id: currentRecordIdForUnqualified.value,
  676. checkPlace: String(currentRowForUnqualified.value?.checkPlace ?? ''),
  677. checkProblem: String(currentRowForUnqualified.value?.checkProblem ?? ''),
  678. checkTime: String(currentRowForUnqualified.value?.checkTime ?? ''),
  679. },
  680. });
  681. const raw = (res as { data?: { records?: UnqualifiedItemNumRecord[]; totalRow?: number } })?.data ?? res;
  682. unqualifiedList.value = raw?.records ?? [];
  683. unqualifiedPagination.total = raw?.totalRow ?? 0;
  684. } catch (e) {
  685. console.error('查询不合格数据失败:', e);
  686. unqualifiedList.value = [];
  687. unqualifiedPagination.total = 0;
  688. } finally {
  689. unqualifiedTableConfig.loading = false;
  690. }
  691. };
  692. const handleUnqualifiedSizeChange = (value: number) => {
  693. unqualifiedPagination.pageSize = value;
  694. unqualifiedPagination.pageNumber = 1;
  695. loadUnqualifiedList();
  696. };
  697. const handleUnqualifiedPageChange = (value: number) => {
  698. unqualifiedPagination.pageNumber = value;
  699. loadUnqualifiedList();
  700. };
  701. const openUnqualifiedDialog = (row: { id?: number } & Record<string, unknown>) => {
  702. if (!row.id) return;
  703. currentRecordIdForUnqualified.value = Number(row.id);
  704. currentRowForUnqualified.value = row;
  705. unqualifiedPagination.pageNumber = 1;
  706. showUnqualifiedDialog.value = true;
  707. loadUnqualifiedList();
  708. };
  709. const handleSandToHiddenDangerDept = (row: { id?: number } & Record<string, unknown>) => {
  710. if (!row.id) return;
  711. getTableData()
  712. currentRecordIdForUnqualified.value = Number(row.id);
  713. // 部门端入账同样不带入检查记录旧数据,每次打开还原为隐患台账初始表单
  714. Object.assign(sandHiddenDangerFormDataDept, HIDDEN_DANGER_FORM_DATA);
  715. showSandConfirmDialogDept.value = true;
  716. };
  717. const validateSandHiddenDangerFormDept = async () => {
  718. if (!sandHiddenDangerFormRefDept.value) return false;
  719. return await sandHiddenDangerFormRefDept.value.validateForm();
  720. };
  721. const confirmSandToHiddenDangerDept = async () => {
  722. if (!currentRecordIdForUnqualified.value) return;
  723. const valid = await validateSandHiddenDangerFormDept();
  724. if (!valid) return;
  725. sandConfirmLoadingDept.value = true;
  726. try {
  727. const d = sandHiddenDangerFormDataDept;
  728. const payload: SandAreaCheckRecordToHiddenDangerReq = {
  729. areaCheckRecordId: currentRecordIdForUnqualified.value,
  730. sourceType: 4,
  731. sourceRefId: currentRecordIdForUnqualified.value,
  732. dangerProblem: d.dangerProblem || '',
  733. typeId: d.typeId,
  734. reasonId: d.reasonId,
  735. taskSource: d.taskSource || '',
  736. rectificationRequirement: d.rectificationRequirement || '',
  737. rectificationDeadline: d.rectificationDeadline || '',
  738. rectificationDepartmentIds: d.rectificationDepartmentIds || '',
  739. rectificationResponsiblePerson: d.rectificationResponsiblePerson || '',
  740. reviewDepartmentId: d.reviewDepartmentId,
  741. reviewPersonId: d.reviewPersonId,
  742. reviewPersonName: d.reviewPersonName || '',
  743. isDrawLessonsPush: d.isDrawLessonsPush ?? 0,
  744. drawLessonsContent: d.drawLessonsContent || '',
  745. drawLessonsDepartmentIds: d.drawLessonsDepartmentIds || '',
  746. drawLessonsDeadline: d.drawLessonsDeadline || '',
  747. attachments: d.attachments || '',
  748. confirmSandToHiddenDangerDept: d.confirmSandToHiddenDangerDept ?? 0,
  749. rectificationResponsibleIds: d.rectificationResponsibleIds || '',
  750. drawLessonsContent: d.drawLessonsContent || '',
  751. drawLessonsDepartmentIds: d.drawLessonsDepartmentIds || '',
  752. drawLessonsDeadline: d.drawLessonsDeadline || '',
  753. };
  754. await sandAreaCheckRecordToProductionHiddenDanger(payload);
  755. ElMessage.success('入账成功');
  756. // 关闭入账表单弹窗和“不合格数据”弹窗
  757. showSandConfirmDialogDept.value = false;
  758. showUnqualifiedDialog.value = false;
  759. await loadRecordList();
  760. } catch (e) {
  761. console.error('部门端下入账失败:', e);
  762. ElMessage.error(e?.message || e?.data || '下入账失败,请稍后重试');
  763. } finally {
  764. sandConfirmLoadingDept.value = false;
  765. }
  766. };
  767. const onAddRecord = () => {
  768. const needOverallDesc = !viewDetail.value.needOverallDesc ? 0 : 1;
  769. const needSigneeSign = !viewDetail.value.needSigneeSign ? 0 : 1;
  770. router.push({
  771. name: 'areaCheckPlanManagementDeptItem',
  772. query: {
  773. operate: 'area-check-plan-record-add',
  774. planId: currentId.value,
  775. needOverallDesc,
  776. needSigneeSign,
  777. },
  778. });
  779. };
  780. const previewOnlineRef = ref<InstanceType<typeof PreviewOnline>>();
  781. const previewOnline = (url: string | undefined, type: keyof typeof FILE_TYPE_ICON) => {
  782. if (url) previewOnlineRef.value?.open(url, type);
  783. };
  784. const parseSignFiles = (signFile: string | undefined): Array<{ fileUrl: string; fileName: string; fileType: string }> => {
  785. if (!signFile || !String(signFile).trim()) return [];
  786. const parts = String(signFile)
  787. .split(',')
  788. .map((s) => s.trim())
  789. .filter(Boolean);
  790. return parts.map((part) => {
  791. const urlParts = part.split('/');
  792. const fileName = urlParts[urlParts.length - 1] || part || '未知文件';
  793. const extension = fileName.split('.').pop()?.toLowerCase() || '';
  794. let fileType = 'pdf';
  795. if (extension === 'doc' || extension === 'docx') fileType = 'word';
  796. else if (extension === 'xls' || extension === 'xlsx') fileType = 'excel';
  797. else if (extension === 'ppt' || extension === 'pptx') fileType = 'ppt';
  798. return { fileUrl: part, fileName, fileType };
  799. });
  800. };
  801. const onViewRecord = (row: Record<string, unknown>) => {
  802. const needOverallDesc = !viewDetail.value.needOverallDesc ? 0 : 1;
  803. const needSigneeSign = !viewDetail.value.needSigneeSign ? 0 : 1;
  804. router.push({
  805. name: 'areaCheckPlanManagementDeptItem',
  806. query: {
  807. operate: 'area-check-plan-record-view',
  808. recordId: row.id,
  809. planId: currentId.value,
  810. needOverallDesc,
  811. needSigneeSign,
  812. },
  813. });
  814. };
  815. const getDetail = async () => {
  816. if (!currentId.value) return;
  817. try {
  818. const res = await queryAreaCheckPlanManageDeptDetail(currentId.value);
  819. const raw = (res as { data?: unknown })?.data ?? res;
  820. const detail = mapAreaCheckPlanApiRecordToUi(raw);
  821. viewDetailData.value = { ...detail };
  822. loadRecordList();
  823. } catch (e) {
  824. console.error('获取详情失败:', e);
  825. }
  826. };
  827. const isDrawLessonsPushChange = () => {
  828. console.log(sandHiddenDangerFormDataDept.isDrawLessonsPush)
  829. console.log('HIDDEN_DANGER_FORM_CONFIG', HIDDEN_DANGER_FORM_CONFIG)
  830. let data = [
  831. {
  832. prop: 'drawLessonsContent',
  833. label: '举一反三内容:',
  834. component: 'ElInput',
  835. componentProps: {
  836. placeholder: '如:上级检查、院内自查',
  837. },
  838. },
  839. {
  840. prop: 'drawLessonsDepartmentIds',
  841. label: '举一反三责任部门:',
  842. slot: 'drawLessonsDepartmentIds',
  843. },
  844. {
  845. prop: 'drawLessonsDeadline',
  846. label: '举一反三时限:',
  847. slot: 'drawLessonsDeadline',
  848. },
  849. ]
  850. };
  851. const onIssueDeptChange = () => {
  852. sandHiddenDangerFormDataDept.rectificationResponsibleIds = undefined;
  853. };
  854. onMounted(() => {
  855. loadDeptAndUserOptionsDept();
  856. getDetail();
  857. onIssueDialogOpen();
  858. });
  859. </script>
  860. <style scoped lang="scss">
  861. @use '@/styles/page-details-layout.scss' as *;
  862. @use '@/styles/basic-table-file.scss' as *;
  863. .area-check-plan-view {
  864. padding: 16px 24px;
  865. display: flex;
  866. flex-direction: column;
  867. gap: 24px;
  868. }
  869. .view-section {
  870. .view-section__title {
  871. font-weight: 600;
  872. margin-bottom: 12px;
  873. display: flex;
  874. align-items: center;
  875. gap: 6px;
  876. &--small {
  877. font-size: 13px;
  878. margin-bottom: 8px;
  879. }
  880. }
  881. .view-section__icon {
  882. width: 4px;
  883. height: 14px;
  884. background: var(--el-color-primary);
  885. border-radius: 2px;
  886. }
  887. }
  888. .view-summary {
  889. .view-summary__title {
  890. font-weight: 600;
  891. font-size: 15px;
  892. margin-bottom: 4px;
  893. }
  894. .view-summary__venue {
  895. margin-bottom: 8px;
  896. }
  897. .view-summary__meta {
  898. font-size: 13px;
  899. color: var(--el-text-color-secondary);
  900. span + span {
  901. margin-left: 16px;
  902. }
  903. }
  904. }
  905. .audit-content {
  906. .section-title {
  907. display: flex;
  908. align-items: center;
  909. gap: 8px;
  910. margin: 20px 0 12px 0;
  911. font-size: 16px;
  912. font-weight: 600;
  913. color: #333;
  914. .section-title__icon {
  915. font-size: 18px;
  916. color: #333;
  917. }
  918. }
  919. .section-title:first-child {
  920. margin-top: 0;
  921. }
  922. .detail-ct {
  923. font-size: 14px;
  924. margin-bottom: 20px;
  925. &--table {
  926. border: 1px solid #dcdfe6;
  927. .row {
  928. display: flex;
  929. border-bottom: 1px solid #dcdfe6;
  930. &:last-child {
  931. border-bottom: none;
  932. }
  933. }
  934. .col {
  935. display: flex;
  936. flex: 1;
  937. min-height: 40px;
  938. align-items: stretch;
  939. .label {
  940. display: flex;
  941. align-items: center;
  942. justify-content: flex-end;
  943. flex-shrink: 0;
  944. width: 260px;
  945. padding: 0 12px;
  946. background-color: #f5f5f5;
  947. border-right: 1px solid #dcdfe6;
  948. color: #333;
  949. }
  950. .value {
  951. flex: 1;
  952. display: flex;
  953. align-items: center;
  954. padding: 10px 20px;
  955. background-color: #fff;
  956. border-right: 1px solid #dcdfe6;
  957. color: #333;
  958. &--list {
  959. align-items: flex-start;
  960. .inspection-content-list {
  961. margin: 0;
  962. padding-left: 20px;
  963. line-height: 1.8;
  964. color: #333;
  965. }
  966. }
  967. }
  968. }
  969. .row .col:last-child .value {
  970. border-right: none;
  971. }
  972. .row .col:nth-child(2) .label {
  973. border-left: 1px solid #dcdfe6;
  974. }
  975. }
  976. }
  977. }
  978. .view-record-toolbar {
  979. margin-bottom: 12px;
  980. display: flex;
  981. align-items: center;
  982. flex-wrap: wrap;
  983. gap: 8px;
  984. }
  985. .view-record-table {
  986. margin-bottom: 16px;
  987. }
  988. .view-record-toolbar-btn{
  989. margin-left: auto;
  990. }
  991. </style>