areaCheckPlanManagementDeptDetail.vue 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876
  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.mainDeptExecutorGroupName || '-' }}</div>
  42. </div>
  43. <div class="col">
  44. <div class="label">主责部门责任人:</div>
  45. <div class="value">{{ viewDetail.mainDeptResponsiblePerson || '-' }}</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.needInspectedSign === true ? '是' : viewDetail.needInspectedSign === 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="parseSignFiles(scope.row.checkedPersonSign || scope.row.signFile).length">
  178. <div
  179. class="file-container--div"
  180. v-for="item in parseSignFiles(scope.row.checkedPersonSign || scope.row.signFile)"
  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 #reviewDepartmentId>
  251. <el-cascader
  252. v-model="sandHiddenDangerFormDataDept.reviewDepartmentId"
  253. :options="deptTree"
  254. :props="cascaderDeptProp"
  255. :show-all-levels="false"
  256. placeholder="请选择复查人员所属部门"
  257. filterable
  258. clearable
  259. style="width: 100%"
  260. />
  261. </template>
  262. <template #reviewPerson>
  263. <el-select
  264. v-model="sandHiddenDangerFormDataDept.reviewPersonId"
  265. placeholder="请选择复查人员"
  266. clearable
  267. filterable
  268. style="width: 100%"
  269. >
  270. <el-option
  271. v-for="u in reviewUserListDept"
  272. :key="u.id"
  273. :label="u.realname || u.username"
  274. :value="u.id"
  275. />
  276. </el-select>
  277. </template>
  278. <template #isDrawLessonsPush>
  279. <el-radio-group v-model="sandHiddenDangerFormDataDept.isDrawLessonsPush">
  280. <el-radio :value="0">否</el-radio>
  281. <el-radio :value="1">是</el-radio>
  282. </el-radio-group>
  283. </template>
  284. <template #drawLessonsDepartmentIds>
  285. <el-select
  286. v-model="drawLessonsDeptIdsArrayDept"
  287. placeholder="请选择举一反三责任部门,可多选"
  288. clearable
  289. filterable
  290. multiple
  291. collapse-tags
  292. collapse-tags-tooltip
  293. style="width: 100%"
  294. @change="() => { sandHiddenDangerFormDataDept.drawLessonsDepartmentIds = drawLessonsDeptIdsArrayDept.join(','); }"
  295. >
  296. <el-option
  297. v-for="d in deptOptions"
  298. :key="d.id"
  299. :label="d.deptName"
  300. :value="d.id"
  301. />
  302. </el-select>
  303. </template>
  304. </BasicForm>
  305. <template #footer>
  306. <el-button @click="showSandConfirmDialogDept = false">取消</el-button>
  307. <el-button type="primary" :loading="sandConfirmLoadingDept" @click="confirmSandToHiddenDangerDept">
  308. 提交
  309. </el-button>
  310. </template>
  311. </el-dialog>
  312. </template>
  313. <script setup lang="ts">
  314. import { computed, onMounted, ref } from 'vue';
  315. import { useRoute, useRouter } from 'vue-router';
  316. import { ElMessage } from 'element-plus';
  317. import { Document } from '@element-plus/icons-vue';
  318. import BasicTable from '@/components/BasicTable.vue';
  319. import { FILE_TYPE_ICON } from '@/components/UploadFiles/constants';
  320. import DownloadIcon from '@/views/disaster/disaster-control/src/svg/download.svg';
  321. import PreviewOnline from '@/views/disaster/components/PreviewOnline.vue';
  322. import { downloadFile } from '@/views/disaster/utils';
  323. import useTableConfig from '@/hooks/useTableConfigHook';
  324. import type { TableColumnProps } from '@/types/basic-table';
  325. import { AREA_CHECK_PLAN_STATUS_LABEL } from '../../areaCheckPlanManagement/configs/status';
  326. import {
  327. queryAreaCheckPlanManageDeptDetail,
  328. queryAreaCheckPlanDetailDeptPage,
  329. mapAreaCheckPlanApiRecordToUi,
  330. queryUnqualifiedItemNumDeptPage,
  331. sandAreaCheckRecordToProductionHiddenDanger,
  332. type UnqualifiedItemNumRecord,
  333. type SandAreaCheckRecordToHiddenDangerReq,
  334. } from '@/api/production-safety-system';
  335. import BasicForm from '@/components/BasicForm.vue';
  336. import { useFormConfigHook } from '@/hooks/useFormConfigHook';
  337. import { getAllDepartments } from '@/api/auth/dept';
  338. import type { DeptTree } from '@/types/dept/type';
  339. import {
  340. HIDDEN_DANGER_FORM_CONFIG,
  341. HIDDEN_DANGER_FORM_DATA,
  342. HIDDEN_DANGER_FORM_RULES,
  343. } from '@/views/production-safety/hiddenTroubleInvestigationAndGovernance/hiddenTroubleAccountManagement/configs/form';
  344. import { queryAvailableUserList } from '@/api/production-safety/responsibility-implementation';
  345. const router = useRouter();
  346. const route = useRoute();
  347. const currentId = computed(() => Number(route.query.id));
  348. const viewDetailData = ref<Record<string, unknown>>({});
  349. const viewDetail = computed(() => {
  350. const d = viewDetailData.value;
  351. const status = d?.status as number | undefined;
  352. return {
  353. ...d,
  354. statusName: status != null ? AREA_CHECK_PLAN_STATUS_LABEL[String(status)] ?? '-' : '-',
  355. planName: d?.planName ?? '-',
  356. venueCategoryName: d?.venueCategoryName ?? '-',
  357. checkVenue: d?.checkVenue ?? '-',
  358. mainDeptName: d?.mainDeptName ?? '-',
  359. primaryResponsibleDeptName: (d?.primaryResponsibleDeptName ?? d?.mainDeptName) ?? '-',
  360. selfCheckFrequency: d?.selfCheckFrequency ?? '-',
  361. mainDeptExecutorGroupName: d?.mainDeptExecutorGroupName ?? '-',
  362. mainDeptResponsiblePerson: d?.mainDeptResponsiblePerson ?? '-',
  363. safetyEmergencyDeptName: d?.safetyEmergencyDeptName ?? '-',
  364. safetyEmergencyCheckFrequency: d?.safetyEmergencyCheckFrequency ?? '-',
  365. safetyEmergencyExecutorGroupName: d?.safetyEmergencyExecutorGroupName ?? '-',
  366. safetyEmergencyResponsiblePerson: d?.safetyEmergencyResponsiblePerson ?? '-',
  367. hospitalLeaderDeptName: d?.hospitalLeaderDeptName ?? '-',
  368. hospitalLeaderCheckFrequency: d?.hospitalLeaderCheckFrequency ?? '-',
  369. hospitalLeaderExecutorGroupName: d?.hospitalLeaderExecutorGroupName ?? '-',
  370. hospitalLeaderResponsiblePerson: d?.hospitalLeaderResponsiblePerson ?? '-',
  371. checklistCategoryName: d?.checklistCategoryName ?? '-',
  372. categoryName: (d?.categoryName ?? d?.checklistCategoryName) ?? '-',
  373. checklistTemplateName: d?.checklistTemplateName ?? '-',
  374. needOverallDesc: d?.needOverallDesc,
  375. needInspectedSign: d?.needInspectedSign,
  376. planStartTime: d?.planStartTime ?? '-',
  377. planEndTime: d?.planEndTime ?? '-',
  378. createdPersonName: (d?.createdPersonName ?? '') || '-',
  379. createdAt: (d?.createdAt ?? '') || '-',
  380. businessWork: (d?.businessWork ?? '') || '-',
  381. };
  382. });
  383. const inspectionContentList = computed(() => {
  384. const content = (viewDetailData.value?.checkKeyContent ?? '') as string;
  385. if (!content || typeof content !== 'string') {
  386. return [
  387. '工作场所布局是否合理,通道是否畅通;',
  388. '各种机械、电力、电气等设备的安装和使用是否符合安全技术要求;',
  389. '安全防护装置是否齐全、灵敏、有效;',
  390. '易燃易爆、有限空间和高处作业等作业场所是否符合安全条件;',
  391. '有较大危险因素和职业危害因素的科研试验场所和有关;',
  392. ];
  393. }
  394. return content.split(/[;;]/).map((s) => s.trim()).filter(Boolean);
  395. });
  396. const RECORD_TABLE_COLUMNS: TableColumnProps[] = [
  397. { label: '编号', type: 'index', align: 'center', width: '60px' },
  398. { label: '检查时间', prop: 'checkTime', minWidth: '160px' },
  399. { label: '检查人员', prop: 'checkPerson', minWidth: '100px' },
  400. { label: '检查场所类别', prop: 'checkPlaceCategory', minWidth: '120px' },
  401. { label: '检查场所', prop: 'checkPlace', minWidth: '120px' },
  402. { label: '检查项总数', prop: 'checkItemTotal', align: 'center', width: '100px' },
  403. { label: '合格项数', prop: 'qualifiedItemNum', align: 'center', width: '90px' },
  404. { label: '不合格项数', prop: 'unqualifiedItemNum', slot: 'unqualifiedItemNum', align: 'center', width: '100px' },
  405. { label: '整体检查情况描述', prop: 'overallCheckDesc', minWidth: '180px', showOverflowTooltip: true },
  406. { label: '被检查人签字', slot: 'sign', align: 'center', width: '140px' },
  407. { label: '操作', slot: 'action', align: 'center', width: '160px', fixed: 'right' },
  408. ];
  409. const RECORD_TABLE_OPTIONS = {
  410. emptyText: '暂无检查记录',
  411. loading: false,
  412. maxHeight: '400px',
  413. stripe: true,
  414. };
  415. const { tableConfig: recordTableConfig, pagination: recordPagination } = useTableConfig(
  416. RECORD_TABLE_COLUMNS,
  417. RECORD_TABLE_OPTIONS,
  418. true,
  419. );
  420. const recordSearchKeyword = ref('');
  421. const recordDateRange = ref<[string, string] | null>(null);
  422. const inspectionRecordList = ref<Array<Record<string, unknown>>>([]);
  423. const paginatedRecordList = computed(() => inspectionRecordList.value);
  424. // 检查不合格数据弹窗
  425. const showUnqualifiedDialog = ref(false);
  426. const currentRecordIdForUnqualified = ref<number | null>(null);
  427. const currentRowForUnqualified = ref<Record<string, unknown> | null>(null);
  428. const unqualifiedList = ref<UnqualifiedItemNumRecord[]>([]);
  429. const UNQUALIFIED_TABLE_COLUMNS: TableColumnProps[] = [
  430. { label: '编号', type: 'index', align: 'center', width: '60px' },
  431. { label: '检查场所', prop: 'checkPlace', minWidth: '200px' },
  432. { label: '发现问题', prop: 'checkProblem', minWidth: '220px' },
  433. { label: '检查时间', prop: 'checkTime', minWidth: '180px' },
  434. { label: '操作', slot: 'action', align: 'center', width: '100px' },
  435. ];
  436. const UNQUALIFIED_TABLE_OPTIONS = {
  437. emptyText: '暂无不合格数据',
  438. loading: false,
  439. maxHeight: '400px',
  440. stripe: true,
  441. };
  442. const { tableConfig: unqualifiedTableConfig, pagination: unqualifiedPagination } = useTableConfig(
  443. UNQUALIFIED_TABLE_COLUMNS,
  444. UNQUALIFIED_TABLE_OPTIONS,
  445. true,
  446. );
  447. // 部门端入账弹窗数据(复用隐患台账新增表单字段与样式)
  448. const showSandConfirmDialogDept = ref(false);
  449. const sandConfirmLoadingDept = ref(false);
  450. const sandHiddenDangerFormRefDept = ref<InstanceType<typeof BasicForm>>();
  451. const {
  452. ruleFormData: sandHiddenDangerFormDataDept,
  453. formRules: sandHiddenDangerFormRulesDept,
  454. ruleFormConfig: sandHiddenDangerFormConfigDept,
  455. } = useFormConfigHook(
  456. HIDDEN_DANGER_FORM_CONFIG,
  457. HIDDEN_DANGER_FORM_DATA as Record<string, unknown>,
  458. HIDDEN_DANGER_FORM_RULES,
  459. );
  460. // 复用部门树与用户下拉,用于复查部门/人员 & 举一反三责任部门
  461. const deptTree = ref<DeptTree[]>([]);
  462. const cascaderDeptProp = {
  463. checkStrictly: true,
  464. expandTrigger: 'hover' as const,
  465. value: 'id',
  466. label: 'deptName',
  467. emitPath: false,
  468. };
  469. function flattenDeptTree(nodes: DeptTree[] | undefined): Array<{ id: number; deptName: string }> {
  470. if (!nodes?.length) return [];
  471. const list: Array<{ id: number; deptName: string }> = [];
  472. const walk = (items: DeptTree[]) => {
  473. items.forEach((n) => {
  474. if (n.id != null) list.push({ id: n.id, deptName: n.deptName });
  475. if (n.children?.length) walk(n.children);
  476. });
  477. };
  478. walk(nodes);
  479. return list;
  480. }
  481. const deptOptions = computed(() => flattenDeptTree(deptTree.value));
  482. const drawLessonsDeptIdsArrayDept = ref<number[]>([]);
  483. const reviewUserListDept = ref<Array<{ id: number; realname?: string; username?: string }>>([]);
  484. const loadDeptAndUserOptionsDept = async () => {
  485. try {
  486. const [deptRes, userRes] = await Promise.all([
  487. getAllDepartments(),
  488. queryAvailableUserList({ pageNumber: 1, pageSize: 9999, queryParam: {} }),
  489. ]);
  490. const fullTree = (deptRes as DeptTree[]) ?? [];
  491. deptTree.value = Array.isArray(fullTree) && fullTree[0]?.children ? fullTree[0].children : [];
  492. reviewUserListDept.value = (userRes as any)?.records ?? [];
  493. } catch (e) {
  494. console.error('获取部门/用户列表失败:', e);
  495. deptTree.value = [];
  496. reviewUserListDept.value = [];
  497. }
  498. };
  499. const loadRecordList = async () => {
  500. if (!currentId.value) return;
  501. recordTableConfig.loading = true;
  502. try {
  503. const [start, end] = recordDateRange.value && recordDateRange.value.length === 2
  504. ? recordDateRange.value
  505. : ['', ''];
  506. const res = await queryAreaCheckPlanDetailDeptPage(currentId.value, {
  507. pageNumber: recordPagination.pageNumber,
  508. pageSize: recordPagination.pageSize,
  509. queryParam: {
  510. searchKey: recordSearchKeyword.value || undefined,
  511. startDate: start || undefined,
  512. endDate: end || undefined,
  513. },
  514. });
  515. const raw = (res as { data?: { records?: Array<Record<string, unknown>>; totalRow?: number } })?.data ?? res;
  516. const records = raw?.records ?? [];
  517. inspectionRecordList.value = records.map((r: Record<string, unknown>) => ({
  518. ...r,
  519. checkPerson: r.checkPerson ?? r.checkPersonName,
  520. checkedCompany: r.checkedCompany ?? r.checkedCompanyName,
  521. }));
  522. recordPagination.total = raw?.totalRow ?? 0;
  523. } catch (e) {
  524. console.error('查询检查记录失败:', e);
  525. inspectionRecordList.value = [];
  526. recordPagination.total = 0;
  527. } finally {
  528. recordTableConfig.loading = false;
  529. }
  530. };
  531. const handleRecordSizeChange = (value: number) => {
  532. recordPagination.pageSize = value;
  533. recordPagination.pageNumber = 1;
  534. loadRecordList();
  535. };
  536. const handleRecordPageChange = (value: number) => {
  537. recordPagination.pageNumber = value;
  538. loadRecordList();
  539. };
  540. const onRecordSearch = () => {
  541. recordPagination.pageNumber = 1;
  542. loadRecordList();
  543. };
  544. const onRecordExport = () => {
  545. ElMessage.success('导出功能开发中');
  546. };
  547. const loadUnqualifiedList = async () => {
  548. if (!currentRecordIdForUnqualified.value) return;
  549. unqualifiedTableConfig.loading = true;
  550. try {
  551. const res = await queryUnqualifiedItemNumDeptPage({
  552. pageNumber: unqualifiedPagination.pageNumber,
  553. pageSize: unqualifiedPagination.pageSize,
  554. queryParam: {
  555. id: currentRecordIdForUnqualified.value,
  556. checkPlace: String(currentRowForUnqualified.value?.checkPlace ?? ''),
  557. checkProblem: String(currentRowForUnqualified.value?.checkProblem ?? ''),
  558. checkTime: String(currentRowForUnqualified.value?.checkTime ?? ''),
  559. },
  560. });
  561. const raw = (res as { data?: { records?: UnqualifiedItemNumRecord[]; totalRow?: number } })?.data ?? res;
  562. unqualifiedList.value = raw?.records ?? [];
  563. unqualifiedPagination.total = raw?.totalRow ?? 0;
  564. } catch (e) {
  565. console.error('查询不合格数据失败:', e);
  566. unqualifiedList.value = [];
  567. unqualifiedPagination.total = 0;
  568. } finally {
  569. unqualifiedTableConfig.loading = false;
  570. }
  571. };
  572. const handleUnqualifiedSizeChange = (value: number) => {
  573. unqualifiedPagination.pageSize = value;
  574. unqualifiedPagination.pageNumber = 1;
  575. loadUnqualifiedList();
  576. };
  577. const handleUnqualifiedPageChange = (value: number) => {
  578. unqualifiedPagination.pageNumber = value;
  579. loadUnqualifiedList();
  580. };
  581. const openUnqualifiedDialog = (row: { id?: number } & Record<string, unknown>) => {
  582. if (!row.id) return;
  583. currentRecordIdForUnqualified.value = Number(row.id);
  584. currentRowForUnqualified.value = row;
  585. unqualifiedPagination.pageNumber = 1;
  586. showUnqualifiedDialog.value = true;
  587. loadUnqualifiedList();
  588. };
  589. const handleSandToHiddenDangerDept = (row: { id?: number } & Record<string, unknown>) => {
  590. if (!row.id) return;
  591. currentRecordIdForUnqualified.value = Number(row.id);
  592. // 部门端入账同样不带入检查记录旧数据,每次打开还原为隐患台账初始表单
  593. Object.assign(sandHiddenDangerFormDataDept, HIDDEN_DANGER_FORM_DATA);
  594. showSandConfirmDialogDept.value = true;
  595. };
  596. const validateSandHiddenDangerFormDept = async () => {
  597. if (!sandHiddenDangerFormRefDept.value) return false;
  598. return await sandHiddenDangerFormRefDept.value.validateForm();
  599. };
  600. const confirmSandToHiddenDangerDept = async () => {
  601. if (!currentRecordIdForUnqualified.value) return;
  602. const valid = await validateSandHiddenDangerFormDept();
  603. if (!valid) return;
  604. sandConfirmLoadingDept.value = true;
  605. try {
  606. const d = sandHiddenDangerFormDataDept;
  607. const payload: SandAreaCheckRecordToHiddenDangerReq = {
  608. areaCheckRecordId: currentRecordIdForUnqualified.value,
  609. sourceType: 4,
  610. sourceRefId: currentRecordIdForUnqualified.value,
  611. dangerProblem: d.dangerProblem || '',
  612. typeId: d.typeId,
  613. reasonId: d.reasonId,
  614. taskSource: d.taskSource || '',
  615. rectificationRequirement: d.rectificationRequirement || '',
  616. rectificationDeadline: d.rectificationDeadline || '',
  617. rectificationDepartmentIds: d.rectificationDepartmentIds || '',
  618. rectificationResponsiblePerson: d.rectificationResponsiblePerson || '',
  619. reviewDepartmentId: d.reviewDepartmentId,
  620. reviewPersonId: d.reviewPersonId,
  621. reviewPersonName: d.reviewPersonName || '',
  622. isDrawLessonsPush: d.isDrawLessonsPush ?? 0,
  623. drawLessonsContent: d.drawLessonsContent || '',
  624. drawLessonsDepartmentIds: d.drawLessonsDepartmentIds || '',
  625. drawLessonsDeadline: d.drawLessonsDeadline || '',
  626. attachments: d.attachments || '',
  627. };
  628. await sandAreaCheckRecordToProductionHiddenDanger(payload);
  629. ElMessage.success('入账成功');
  630. // 关闭入账表单弹窗和“不合格数据”弹窗
  631. showSandConfirmDialogDept.value = false;
  632. showUnqualifiedDialog.value = false;
  633. await loadRecordList();
  634. } catch (e) {
  635. console.error('部门端下入账失败:', e);
  636. ElMessage.error(e?.message || e?.data || '下入账失败,请稍后重试');
  637. } finally {
  638. sandConfirmLoadingDept.value = false;
  639. }
  640. };
  641. const onAddRecord = () => {
  642. router.push({
  643. name: 'areaCheckPlanManagementDeptItem',
  644. query: {
  645. operate: 'area-check-plan-record-add',
  646. planId: currentId.value,
  647. },
  648. });
  649. };
  650. const previewOnlineRef = ref<InstanceType<typeof PreviewOnline>>();
  651. const previewOnline = (url: string | undefined, type: keyof typeof FILE_TYPE_ICON) => {
  652. if (url) previewOnlineRef.value?.open(url, type);
  653. };
  654. const parseSignFiles = (signFile: string | undefined): Array<{ fileUrl: string; fileName: string; fileType: string }> => {
  655. if (!signFile || !String(signFile).trim()) return [];
  656. const parts = String(signFile)
  657. .split(',')
  658. .map((s) => s.trim())
  659. .filter(Boolean);
  660. return parts.map((part) => {
  661. const urlParts = part.split('/');
  662. const fileName = urlParts[urlParts.length - 1] || part || '未知文件';
  663. const extension = fileName.split('.').pop()?.toLowerCase() || '';
  664. let fileType = 'pdf';
  665. if (extension === 'doc' || extension === 'docx') fileType = 'word';
  666. else if (extension === 'xls' || extension === 'xlsx') fileType = 'excel';
  667. else if (extension === 'ppt' || extension === 'pptx') fileType = 'ppt';
  668. return { fileUrl: part, fileName, fileType };
  669. });
  670. };
  671. const onViewRecord = (row: Record<string, unknown>) => {
  672. router.push({
  673. name: 'areaCheckPlanManagementDeptItem',
  674. query: {
  675. operate: 'area-check-plan-record-view',
  676. recordId: row.id,
  677. planId: currentId.value,
  678. },
  679. });
  680. };
  681. const getDetail = async () => {
  682. if (!currentId.value) return;
  683. try {
  684. const res = await queryAreaCheckPlanManageDeptDetail(currentId.value);
  685. const raw = (res as { data?: unknown })?.data ?? res;
  686. const detail = mapAreaCheckPlanApiRecordToUi(raw);
  687. viewDetailData.value = { ...detail };
  688. loadRecordList();
  689. } catch (e) {
  690. console.error('获取详情失败:', e);
  691. }
  692. };
  693. onMounted(() => {
  694. loadDeptAndUserOptionsDept();
  695. getDetail();
  696. });
  697. </script>
  698. <style scoped lang="scss">
  699. @use '@/styles/page-details-layout.scss' as *;
  700. @use '@/styles/basic-table-file.scss' as *;
  701. .area-check-plan-view {
  702. padding: 16px 24px;
  703. display: flex;
  704. flex-direction: column;
  705. gap: 24px;
  706. }
  707. .view-section {
  708. .view-section__title {
  709. font-weight: 600;
  710. margin-bottom: 12px;
  711. display: flex;
  712. align-items: center;
  713. gap: 6px;
  714. &--small {
  715. font-size: 13px;
  716. margin-bottom: 8px;
  717. }
  718. }
  719. .view-section__icon {
  720. width: 4px;
  721. height: 14px;
  722. background: var(--el-color-primary);
  723. border-radius: 2px;
  724. }
  725. }
  726. .view-summary {
  727. .view-summary__title {
  728. font-weight: 600;
  729. font-size: 15px;
  730. margin-bottom: 4px;
  731. }
  732. .view-summary__venue {
  733. margin-bottom: 8px;
  734. }
  735. .view-summary__meta {
  736. font-size: 13px;
  737. color: var(--el-text-color-secondary);
  738. span + span {
  739. margin-left: 16px;
  740. }
  741. }
  742. }
  743. .audit-content {
  744. .section-title {
  745. display: flex;
  746. align-items: center;
  747. gap: 8px;
  748. margin: 20px 0 12px 0;
  749. font-size: 16px;
  750. font-weight: 600;
  751. color: #333;
  752. .section-title__icon {
  753. font-size: 18px;
  754. color: #333;
  755. }
  756. }
  757. .section-title:first-child {
  758. margin-top: 0;
  759. }
  760. .detail-ct {
  761. font-size: 14px;
  762. margin-bottom: 20px;
  763. &--table {
  764. border: 1px solid #dcdfe6;
  765. .row {
  766. display: flex;
  767. border-bottom: 1px solid #dcdfe6;
  768. &:last-child {
  769. border-bottom: none;
  770. }
  771. }
  772. .col {
  773. display: flex;
  774. flex: 1;
  775. min-height: 40px;
  776. align-items: stretch;
  777. .label {
  778. display: flex;
  779. align-items: center;
  780. justify-content: flex-end;
  781. flex-shrink: 0;
  782. width: 200px;
  783. padding: 0 12px;
  784. background-color: #f5f5f5;
  785. border-right: 1px solid #dcdfe6;
  786. color: #333;
  787. }
  788. .value {
  789. flex: 1;
  790. display: flex;
  791. align-items: center;
  792. padding: 10px 20px;
  793. background-color: #fff;
  794. border-right: 1px solid #dcdfe6;
  795. color: #333;
  796. &--list {
  797. align-items: flex-start;
  798. .inspection-content-list {
  799. margin: 0;
  800. padding-left: 20px;
  801. line-height: 1.8;
  802. color: #333;
  803. }
  804. }
  805. }
  806. }
  807. .row .col:last-child .value {
  808. border-right: none;
  809. }
  810. .row .col:nth-child(2) .label {
  811. border-left: 1px solid #dcdfe6;
  812. }
  813. }
  814. }
  815. }
  816. .view-record-toolbar {
  817. margin-bottom: 12px;
  818. display: flex;
  819. align-items: center;
  820. flex-wrap: wrap;
  821. gap: 8px;
  822. }
  823. .view-record-table {
  824. margin-bottom: 16px;
  825. }
  826. </style>