areaCheckPlanManagementDeptDetail.vue 36 KB

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