hiddenTroubleAccountManagementDetail.vue 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874
  1. <template>
  2. <el-alert
  3. v-if="isRectifyMode && detailReviewRejectReason"
  4. type="error"
  5. :title="'不通过原因:' + detailReviewRejectReason"
  6. show-icon
  7. class="detail-reject-alert"
  8. />
  9. <main class="safety-platform-container__main">
  10. <el-form label-width="150px" :model="ruleFormData" :rules="isViewMode ? undefined : formRules" ref="basicFormRef">
  11. <el-form-item label="隐患问题类别:" prop="typeId">
  12. <el-select
  13. v-model="ruleFormData.typeId"
  14. placeholder="请选择隐患问题类别"
  15. clearable
  16. filterable
  17. :disabled="isViewMode"
  18. style="width: 450px"
  19. >
  20. <el-option
  21. v-for="option in ruleFormConfig.find((c) => c.prop === 'typeId')?.selectOptions || []"
  22. :key="option.value"
  23. :label="option.label"
  24. :value="option.value"
  25. />
  26. </el-select>
  27. </el-form-item>
  28. <el-form-item label="隐患问题:" prop="dangerProblem">
  29. <el-input
  30. v-model="ruleFormData.dangerProblem"
  31. placeholder="请输入隐患问题描述"
  32. :disabled="isViewMode"
  33. style="width: 450px"
  34. />
  35. </el-form-item>
  36. <el-form-item label="问题主要原因:" prop="reasonId">
  37. <el-select
  38. v-model="ruleFormData.reasonId"
  39. placeholder="请选择问题主要原因"
  40. clearable
  41. filterable
  42. :disabled="isViewMode"
  43. style="width: 450px"
  44. >
  45. <el-option
  46. v-for="option in ruleFormConfig.find((c) => c.prop === 'reasonId')?.selectOptions || []"
  47. :key="option.value"
  48. :label="option.label"
  49. :value="option.value"
  50. />
  51. </el-select>
  52. </el-form-item>
  53. <el-form-item label="任务来源:" prop="taskSource">
  54. <el-input
  55. v-model="ruleFormData.taskSource"
  56. placeholder="如:上级检查、院内自查"
  57. :disabled="isViewMode"
  58. style="width: 450px"
  59. />
  60. </el-form-item>
  61. <el-form-item label="整改要求:" prop="rectificationRequirement">
  62. <el-input
  63. v-model="ruleFormData.rectificationRequirement"
  64. placeholder="请输入整改要求"
  65. :disabled="isViewMode"
  66. style="width: 450px"
  67. />
  68. </el-form-item>
  69. <el-form-item label="整改责任部门:" prop="rectificationDepartmentId" v-if="ruleFormData.statusId !== 1 && !isCreateMode">
  70. <el-input
  71. v-model="ruleFormData.rectificationDepartmentNames"
  72. placeholder="整改责任部门"
  73. disabled
  74. style="width: 450px"
  75. />
  76. </el-form-item>
  77. <el-form-item label="整改负责人:" prop="rectificationResponsibleUserId" v-if="ruleFormData.statusId !== 1 && !isCreateMode">
  78. <el-input
  79. v-model="ruleFormData.rectificationResponsiblePerson"
  80. placeholder="整改负责人"
  81. disabled
  82. style="width: 450px"
  83. />
  84. </el-form-item>
  85. <el-form-item label="整改日期:" prop="rectificationDeadline">
  86. <el-date-picker
  87. v-model="ruleFormData.rectificationDeadline"
  88. type="date"
  89. value-format="YYYY-MM-DD"
  90. placeholder="请选择整改日期"
  91. :disabled="isViewMode"
  92. style="width: 450px"
  93. />
  94. </el-form-item>
  95. <el-form-item label="复查人员所属部门:" prop="reviewDepartmentId">
  96. <el-cascader
  97. ref="reviewDeptCascaderRef"
  98. v-model="ruleFormData.reviewDepartmentId"
  99. :options="deptTree"
  100. :props="cascaderDeptProp"
  101. :show-all-levels="false"
  102. placeholder="请选择复查人员所属部门"
  103. filterable
  104. clearable
  105. :disabled="isViewMode"
  106. style="width: 450px"
  107. />
  108. </el-form-item>
  109. <el-form-item label="复查人员:" prop="reviewPersonId">
  110. <el-select
  111. v-model="ruleFormData.reviewPersonId"
  112. placeholder="请选择复查人员"
  113. clearable
  114. filterable
  115. :disabled="isViewMode"
  116. style="width: 450px"
  117. @change="onReviewPersonChange"
  118. >
  119. <el-option v-for="u in reviewUserList" :key="u.id" :label="u.realname || u.username" :value="u.id" />
  120. </el-select>
  121. </el-form-item>
  122. <el-form-item label="举一反三是否推送:" prop="isDrawLessonsPush">
  123. <el-radio-group v-model="ruleFormData.isDrawLessonsPush" :disabled="isViewMode" @change="changeDrawLessonsPush">
  124. <el-radio :value="0">否</el-radio>
  125. <el-radio :value="1">是</el-radio>
  126. </el-radio-group>
  127. </el-form-item>
  128. <section v-if="ruleFormData.isDrawLessonsPush === 1">
  129. <el-form-item label="" prop="drawLessonsContent">
  130. <el-input
  131. v-model="ruleFormData.drawLessonsContent"
  132. placeholder="请输入举一反三内容(选填)"
  133. show-word-limit
  134. style="width: 450px"
  135. :disabled="isViewMode"
  136. />
  137. </el-form-item>
  138. <el-form-item label="举一反三责任部门:" prop="drawLessonsDepartmentIds">
  139. <el-select
  140. v-model="drawLessonsDeptIdsArray"
  141. placeholder="请选择举一反三责任部门,可多选"
  142. clearable
  143. filterable
  144. multiple
  145. collapse-tags
  146. collapse-tags-tooltip
  147. :disabled="isViewMode"
  148. style="width: 450px"
  149. @change="onDrawLessonsDeptsChange"
  150. >
  151. <el-option v-for="d in deptOptions" :key="d.id" :label="d.deptName" :value="d.id" />
  152. </el-select>
  153. </el-form-item>
  154. <el-form-item label="举一反三截止日期:" prop="drawLessonsDeadline">
  155. <el-date-picker
  156. v-model="ruleFormData.drawLessonsDeadline"
  157. type="date"
  158. value-format="YYYY-MM-DD"
  159. placeholder="请选择举一反三截止日期(选填)"
  160. style="width: 450px"
  161. :disabled="isViewMode"
  162. />
  163. </el-form-item>
  164. </section>
  165. <section v-if="detailStatusOrder>=2&&!isEditMode">
  166. <el-form-item label="整改完成情况:" prop="rectificationCompletionStatus">
  167. <el-input
  168. v-model="ruleFormData.rectificationCompletionStatus"
  169. placeholder="请输入整改完成情况"
  170. show-word-limit
  171. :disabled="!isRectifyMode"
  172. style="width: 450px"
  173. />
  174. </el-form-item>
  175. <el-form-item label="整改完成时间:" prop="rectificationCompletionTime">
  176. <el-date-picker
  177. v-model="ruleFormData.rectificationCompletionTime"
  178. type="date"
  179. :disabled="!isRectifyMode"
  180. value-format="YYYY-MM-DD"
  181. placeholder="请选择整改完成时间"
  182. style="width: 450px"
  183. />
  184. </el-form-item>
  185. <el-form-item label="附件上传" prop="attachments">
  186. <UploadFiles
  187. label="选择附件"
  188. :maxCount="1"
  189. v-if="detailStatusOrder==2"
  190. :disabled="!isRectifyMode"
  191. :file-list="attachmentsFileList"
  192. :allow-all-file-types="true"
  193. @uploadSuccess="(list: FileItem[]) => handleAttachmentsUploadSuccess(list)"
  194. @preview="handlePreview"
  195. />
  196. <div class="file-list" v-if="detailStatusOrder>=3">
  197. <div class="file-item" v-for="file in attachmentsFileList" :key="file.fileId">
  198. <span class="file-item--name">{{ file.fileName }}</span>
  199. <div class="file-item--footer">
  200. <el-button link type="primary" @click="previewOnline(file.fileUrl, file.fileType)"
  201. >预览</el-button
  202. >
  203. </div>
  204. </div>
  205. </div>
  206. </el-form-item>
  207. <el-form-item label="复查意见:" prop="reviewComments" v-if="detailStatusOrder>=3" >
  208. <el-input
  209. v-model="ruleFormData.reviewComments"
  210. type="textarea"
  211. :rows="3"
  212. placeholder="请输入复查意见(选填),限300字"
  213. maxlength="300"
  214. style="width: 450px"
  215. show-word-limit
  216. :disabled="isViewMode && !isReviewMode"
  217. />
  218. </el-form-item>
  219. </section>
  220. </el-form>
  221. </main>
  222. <footer class="safety-platform-container__footer">
  223. <el-button @click="router.back()">返回</el-button>
  224. <template v-if="isReviewMode">
  225. <el-button type="warning" @click="openReviewRejectDialog">审查不通过</el-button>
  226. <el-button type="primary" @click="handleReviewPass">审查通过</el-button>
  227. </template>
  228. <template v-else-if="!isViewMode">
  229. <el-button type="primary" @click="handleSubmit">
  230. {{ isCreateMode ? '提交' : '保存' }}
  231. </el-button>
  232. </template>
  233. <template v-else-if="isRectifyMode">
  234. <el-button type="primary" @click="handleRectifySubmitDirect">整改</el-button>
  235. </template>
  236. <!-- 纯查看时仅保留上面的返回,不显示其他按钮 -->
  237. </footer>
  238. <!-- 预览 -->
  239. <PreviewOnline ref="previewOnlineRef" />
  240. <!-- 复查弹窗 -->
  241. <el-dialog v-model="showReviewDialog" title="复查隐患" width="520px" destroy-on-close @close="resetReviewForm">
  242. <el-form ref="reviewFormRef" :model="reviewForm" :rules="reviewRules" label-width="120px">
  243. <el-form-item label="复查结论" prop="reviewResult">
  244. <el-radio-group v-model="reviewForm.reviewResult">
  245. <el-radio :value="1">通过</el-radio>
  246. <el-radio :value="0">不通过</el-radio>
  247. </el-radio-group>
  248. </el-form-item>
  249. <el-form-item label="复查意见" prop="reviewComments">
  250. <el-input v-model="reviewForm.reviewComments" type="textarea" :rows="2" placeholder="选填" />
  251. </el-form-item>
  252. <el-form-item v-if="reviewForm.reviewResult === 0" label="不通过原因" prop="reviewReason">
  253. <el-input v-model="reviewForm.reviewReason" type="textarea" :rows="2" placeholder="不通过时必填" />
  254. </el-form-item>
  255. <el-form-item label="复查时间" prop="reviewTime">
  256. <el-date-picker
  257. v-model="reviewForm.reviewTime"
  258. type="datetime"
  259. value-format="YYYY-MM-DDTHH:mm:ss"
  260. placeholder="选填,默认当前时间"
  261. style="width: 100%"
  262. />
  263. </el-form-item>
  264. <el-form-item label="附件" prop="attachments">
  265. <el-input v-model="reviewForm.attachments" placeholder="选填,多个用逗号分隔" />
  266. </el-form-item>
  267. </el-form>
  268. <template #footer>
  269. <el-button @click="showReviewDialog = false">取消</el-button>
  270. <el-button type="primary" @click="handleReviewSubmit">提交复查</el-button>
  271. </template>
  272. </el-dialog>
  273. <!-- 审查不通过原因弹窗(复查详情页使用) -->
  274. <el-dialog
  275. v-model="showReviewRejectDialog"
  276. title="审核不通过原因"
  277. width="560px"
  278. :close-on-click-modal="false"
  279. destroy-on-close
  280. @close="reviewRejectReason = ''"
  281. >
  282. <el-input
  283. v-model="reviewRejectReason"
  284. type="textarea"
  285. :rows="6"
  286. maxlength="300"
  287. show-word-limit
  288. placeholder="请填写审核不通过原因(限300字)"
  289. />
  290. <template #footer>
  291. <el-button @click="showReviewRejectDialog = false">取消</el-button>
  292. <el-button type="primary" @click="handleReviewRejectSubmit">确定</el-button>
  293. </template>
  294. </el-dialog>
  295. <!-- 扣分弹窗 -->
  296. <el-dialog v-model="showDeductDialog" title="扣分记录" width="400px" destroy-on-close @close="resetDeductForm">
  297. <el-form ref="deductFormRef" :model="deductForm" :rules="deductRules" label-width="100px">
  298. <el-form-item label="扣分值" prop="deductionScore">
  299. <el-input-number
  300. v-model="deductForm.deductionScore"
  301. :min="0"
  302. :max="9999"
  303. :precision="0"
  304. placeholder="请输入扣分值(0-9999整数)"
  305. style="width: 100%"
  306. />
  307. </el-form-item>
  308. <el-form-item label="扣分部门">
  309. <el-select
  310. v-model="deductDeptIdsArray"
  311. placeholder="请选择扣分部门,可多选(复用复查人员所属部门)"
  312. clearable
  313. filterable
  314. multiple
  315. collapse-tags
  316. collapse-tags-tooltip
  317. style="width: 100%"
  318. >
  319. <el-option v-for="d in deptOptions" :key="d.id" :label="d.deptName" :value="d.id" />
  320. </el-select>
  321. </el-form-item>
  322. </el-form>
  323. <template #footer>
  324. <el-button @click="showDeductDialog = false">取消</el-button>
  325. <el-button type="primary" @click="handleDeductSubmit">确认扣分</el-button>
  326. </template>
  327. </el-dialog>
  328. </template>
  329. <script setup lang="ts">
  330. import { computed, onMounted, ref } from 'vue';
  331. import { useRoute, useRouter } from 'vue-router';
  332. import { ElMessage } from 'element-plus';
  333. import type { FormInstance, FormRules } from 'element-plus';
  334. import BasicForm from '@/components/BasicForm.vue';
  335. import UploadFiles from '@/components/UploadFiles/UploadFiles.vue';
  336. import type { FileItem } from '@/components/UploadFiles/types';
  337. import { formatAttachmentList } from '@/components/UploadFiles/utils';
  338. import { useFormConfigHook } from '@/hooks/useFormConfigHook';
  339. import {
  340. HIDDEN_DANGER_FORM_CONFIG,
  341. HIDDEN_DANGER_FORM_DATA,
  342. HIDDEN_DANGER_FORM_RULES,
  343. HIDDEN_DANGER_RECTIFY_FORM_CONFIG,
  344. HIDDEN_DANGER_REVIEW_FORM_CONFIG,
  345. } from '@/views/production-safety/hiddenTroubleInvestigationAndGovernance/hiddenTroubleAccountManagement/configs/form';
  346. import {
  347. getHiddenDangerDetail,
  348. saveHiddenDanger,
  349. updateHiddenDanger,
  350. rectifyHiddenDanger,
  351. reviewHiddenDanger,
  352. deductHiddenDangerPoints,
  353. type SaveHiddenDangerRequest,
  354. type UpdateHiddenDangerRequest,
  355. type ReviewHiddenDangerRequest,
  356. type HiddenDangerDeductPointsRequest,
  357. } from '@/api/hiddenDanger';
  358. import type { HiddenDangerItem } from '@/api/hiddenDanger';
  359. import { getAllDepartments } from '@/api/auth/dept';
  360. import type { DeptTree } from '@/types/dept/type';
  361. import { queryAvailableUserList } from '@/api/production-safety/responsibility-implementation';
  362. import PreviewOnline from '@/views/disaster/components/PreviewOnline.vue';
  363. import { ru } from 'element-plus/es/locale';
  364. const router = useRouter();
  365. const route = useRoute();
  366. const operate = computed(() => (route.query.operate as string) || 'hidden-trouble-account-create');
  367. const currentId = computed(() => Number(route.query.id));
  368. const isCreateMode = computed(() => operate.value === 'hidden-trouble-account-create');
  369. const isEditMode = computed(() => operate.value === 'hidden-trouble-account-edit');
  370. const isViewMode = computed(
  371. () =>
  372. operate.value === 'hidden-trouble-account-view' ||
  373. operate.value === 'hidden-trouble-account-rectify' ||
  374. operate.value === 'hidden-trouble-account-review',
  375. );
  376. const isRectifyMode = computed(() => operate.value === 'hidden-trouble-account-rectify');
  377. const isReviewMode = computed(() => operate.value === 'hidden-trouble-account-review');
  378. const { ruleFormData, formRules, ruleFormConfig, cloneRuleFormData } = useFormConfigHook(
  379. HIDDEN_DANGER_FORM_CONFIG,
  380. HIDDEN_DANGER_FORM_DATA,
  381. HIDDEN_DANGER_FORM_RULES,
  382. );
  383. const previewOnlineRef = ref<InstanceType<typeof PreviewOnline>>();
  384. /** 查看详情:与复查时字段一致,全部为禁用状态 */
  385. const viewFormConfig = computed(() =>
  386. HIDDEN_DANGER_REVIEW_FORM_CONFIG.map((item) => ({
  387. ...item,
  388. componentProps: { ...item.componentProps, disabled: true },
  389. })),
  390. );
  391. const computedFormConfig = computed(() => {
  392. if (isRectifyMode.value) return HIDDEN_DANGER_RECTIFY_FORM_CONFIG;
  393. if (isReviewMode.value) return HIDDEN_DANGER_REVIEW_FORM_CONFIG;
  394. if (isViewMode.value) return viewFormConfig.value;
  395. return ruleFormConfig.value;
  396. });
  397. /** 附件:逗号分隔 URL 转 FileItem 列表,供 UploadFiles 使用(复用新增安全考核上传附件) */
  398. function convertAttachmentsToFileItems(
  399. attachmentsStr: string,
  400. ): Array<{ fileId: number; fileName: string; fileType: string; fileSize: string; fileUrl?: string }> {
  401. if (!attachmentsStr || !String(attachmentsStr).trim()) return [];
  402. const urls = String(attachmentsStr)
  403. .split(',')
  404. .map((u) => u.trim())
  405. .filter(Boolean);
  406. return urls.map((url, index) => {
  407. const parts = url.split('/');
  408. const fileName = parts[parts.length - 1] || `附件${index + 1}`;
  409. const ext = fileName.split('.').pop()?.toLowerCase() || '';
  410. let fileType = 'pdf';
  411. if (['doc', 'docx'].includes(ext)) fileType = 'word';
  412. else if (['xls', 'xlsx'].includes(ext)) fileType = 'excel';
  413. else if (['ppt', 'pptx'].includes(ext)) fileType = 'ppt';
  414. return { fileId: index + 1, fileName, fileType, fileSize: '0', fileUrl: url };
  415. });
  416. }
  417. // const attachmentsFileList = computed(() => convertAttachmentsToFileItems(ruleFormData.attachments || ''));
  418. const attachmentsFileList = ref([]) as any
  419. async function handleAttachmentsUploadSuccess(files: FileItem[]) {
  420. if (!files?.length) {
  421. ruleFormData.attachments = '';
  422. return;
  423. }
  424. console.log(files)
  425. attachmentsFileList.value = files
  426. ruleFormData.attachments = JSON.stringify(files) || '';
  427. // try {
  428. // const list = await formatAttachmentList(files);
  429. // ruleFormData.attachments = (list || [])
  430. // .map((r) => r.fileUrl)
  431. // .filter(Boolean)
  432. // .join(',');
  433. // } catch (e) {
  434. // console.error('附件上传失败:', e);
  435. // ElMessage.error(e?.message || e?.data || '附件上传失败,请重试');
  436. // }
  437. }
  438. const handlePreview = (url: string) => {
  439. if (url) {
  440. // 根据文件扩展名判断文件类型
  441. const extension = url.split('.').pop()?.toLowerCase() || '';
  442. let fileType: 'pdf' | 'word' | 'excel' | 'ppt' = 'pdf';
  443. if (extension === 'doc' || extension === 'docx') {
  444. fileType = 'word';
  445. } else if (extension === 'xls' || extension === 'xlsx') {
  446. fileType = 'excel';
  447. } else if (extension === 'ppt' || extension === 'pptx') {
  448. fileType = 'ppt';
  449. }
  450. previewOnlineRef.value?.open(url, fileType);
  451. }
  452. };
  453. const previewOnline = (url: string | undefined, type) => {
  454. if (url) {
  455. previewOnlineRef.value?.open(url, type);
  456. }
  457. };
  458. const basicFormRef = ref<InstanceType<typeof BasicForm>>();
  459. /** 部门树,与新增物品领取记录的部门一致(getAllDepartments,取第一级 children) */
  460. const reviewDeptCascaderRef = ref();
  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. /** 部门树扁平化为下拉选项,举一反三责任部门与复查人员所属部门同数据源 */
  470. function flattenDeptTree(nodes: DeptTree[] | undefined): Array<{ id: number; deptName: string }> {
  471. if (!nodes?.length) return [];
  472. const list: Array<{ id: number; deptName: string }> = [];
  473. const walk = (items: DeptTree[]) => {
  474. items.forEach((n) => {
  475. if (n.id != null) list.push({ id: n.id, deptName: n.deptName });
  476. if (n.children?.length) walk(n.children);
  477. });
  478. };
  479. walk(nodes);
  480. return list;
  481. }
  482. const deptOptions = computed(() => flattenDeptTree(deptTree.value));
  483. const drawLessonsDeptIdsArray = ref<number[]>([]);
  484. const reviewUserList = ref<Array<{ id: number; realname?: string; username?: string }>>([]);
  485. const loadDeptAndUserOptions = async () => {
  486. try {
  487. const [deptRes, userRes] = await Promise.all([
  488. getAllDepartments(),
  489. queryAvailableUserList({ pageNumber: 1, pageSize: 9999, queryParam: {} }),
  490. ]);
  491. const fullTree = (deptRes as DeptTree[]) ?? [];
  492. deptTree.value = Array.isArray(fullTree) && fullTree[0]?.children ? fullTree[0].children : [];
  493. reviewUserList.value = (userRes as any)?.records ?? [];
  494. } catch (e) {
  495. console.error('获取部门/用户列表失败:', e);
  496. deptTree.value = [];
  497. reviewUserList.value = [];
  498. }
  499. };
  500. function onReviewPersonChange() {
  501. const u = reviewUserList.value.find((x) => x.id === ruleFormData.reviewPersonId);
  502. ruleFormData.reviewPersonName = u ? u.realname ?? u.username ?? '' : '';
  503. }
  504. function onDrawLessonsDeptsChange() {
  505. ruleFormData.drawLessonsDepartmentIds = drawLessonsDeptIdsArray.value.join(',');
  506. }
  507. function changeDrawLessonsPush() {
  508. if (ruleFormData.isDrawLessonsPush === 1) {
  509. ruleFormData.drawLessonsContent = '';
  510. ruleFormData.drawLessonsDepartmentIds = '';
  511. ruleFormData.drawLessonsDeadline = '';
  512. drawLessonsDeptIdsArray.value = [];
  513. }
  514. }
  515. /** 状态:1待下发 2待整改 3待复查 4已完成 5待入账(用 statusId,兼容 statusOrder) */
  516. /** 整改页:详情接口返回的审查不通过原因,供 el-alert 展示 */
  517. const detailReviewRejectReason = ref('');
  518. const detailStatusOrder = ref<number>(0);
  519. const canRectify = computed(() => detailStatusOrder.value === 2);
  520. const canReview = computed(() => detailStatusOrder.value === 3);
  521. const getDetail = async () => {
  522. if (!currentId.value) return;
  523. try {
  524. const res = await getHiddenDangerDetail(currentId.value);
  525. const data = (res as any)?.data ?? res;
  526. if (data && typeof data === 'object') {
  527. const d = data as HiddenDangerItem;
  528. detailStatusOrder.value = d.statusId ?? d.statusOrder ?? 0;
  529. ruleFormData.dangerProblem = d.dangerProblem ?? '';
  530. ruleFormData.typeId = d.typeId;
  531. ruleFormData.reasonId = d.reasonId;
  532. ruleFormData.taskSource = d.taskSource ?? '';
  533. ruleFormData.rectificationRequirement = d.rectificationRequirement ?? '';
  534. ruleFormData.rectificationDeadline = d.rectificationDeadline ?? '';
  535. ruleFormData.rectificationDepartmentIds = d.rectificationDepartmentIds ?? '';
  536. ruleFormData.rectificationResponsiblePerson = d.rectificationResponsiblePerson ?? '';
  537. ruleFormData.reviewDepartmentId = d.reviewDepartmentId;
  538. ruleFormData.reviewPersonId = d.reviewPersonId;
  539. ruleFormData.reviewPersonName = d.reviewPersonName ?? '';
  540. ruleFormData.isDrawLessonsPush = d.isDrawLessonsPush ?? 0;
  541. ruleFormData.drawLessonsContent = d.drawLessonsContent ?? '';
  542. ruleFormData.drawLessonsDepartmentIds = d.drawLessonsDepartmentIds ?? '';
  543. ruleFormData.drawLessonsDeadline = d.drawLessonsDeadline ?? '';
  544. drawLessonsDeptIdsArray.value = ruleFormData.drawLessonsDepartmentIds
  545. ? ruleFormData.drawLessonsDepartmentIds
  546. .split(',')
  547. .map((s) => Number(s.trim()))
  548. .filter(Boolean)
  549. : [];
  550. ruleFormData.rectificationCompletionStatus = d.rectificationCompletionStatus ?? '';
  551. ruleFormData.rectificationCompletionTime = d.rectificationCompletionTime ?? '';
  552. ruleFormData.attachments = d.attachments ?? '';
  553. attachmentsFileList.value = JSON.parse(d.attachments|| '[]');
  554. ruleFormData.reviewComments = d.reviewComments?? '';
  555. ruleFormData.rectificationResponsibleUserId = d.rectificationResponsibleUserId;
  556. ruleFormData.rectificationDepartmentNames = d.rectificationDepartmentNames ?? '';
  557. // 整改页:保存审查不通过原因供 el-alert 展示(后端字段 reviewReason)
  558. if (isRectifyMode.value && d.reviewReason != null && String(d.reviewReason).trim()) {
  559. detailReviewRejectReason.value = String(d.reviewReason).trim();
  560. } else {
  561. detailReviewRejectReason.value = '';
  562. }
  563. }
  564. // if (isReviewMode.value) {
  565. // ruleFormData.reviewComments = '';
  566. // }
  567. cloneRuleFormData();
  568. } catch (e) {
  569. console.error('获取隐患详情失败:', e);
  570. ElMessage.error(e?.message || e?.data || '获取详情失败');
  571. }
  572. };
  573. const handleValidate = async () => {
  574. if (!basicFormRef.value) return;
  575. console.log('basicFormRef.value:', basicFormRef.value);
  576. return await basicFormRef.value.validate();
  577. };
  578. const handleSubmit = async () => {
  579. const ok = await handleValidate();
  580. if (!ok) return;
  581. try {
  582. let rectificationDeadline = ruleFormData.rectificationDeadline;
  583. if (rectificationDeadline) {
  584. if (rectificationDeadline.includes('T') || rectificationDeadline.includes(' ')) {
  585. rectificationDeadline = rectificationDeadline.split('T')[0].split(' ')[0];
  586. }
  587. }
  588. let drawLessonsDeadline = ruleFormData.drawLessonsDeadline;
  589. if (drawLessonsDeadline) {
  590. if (drawLessonsDeadline.includes('T') || drawLessonsDeadline.includes(' ')) {
  591. drawLessonsDeadline = drawLessonsDeadline.split('T')[0].split(' ')[0];
  592. }
  593. }
  594. const payload: SaveHiddenDangerRequest = {
  595. dangerProblem: ruleFormData.dangerProblem,
  596. typeId: ruleFormData.typeId!,
  597. reasonId: ruleFormData.reasonId!,
  598. taskSource: ruleFormData.taskSource,
  599. rectificationRequirement: ruleFormData.rectificationRequirement,
  600. rectificationDeadline: rectificationDeadline,
  601. rectificationDepartmentId: ruleFormData.rectificationDepartmentId,
  602. rectificationDepartmentIds: ruleFormData.rectificationDepartmentIds,
  603. rectificationResponsiblePerson: ruleFormData.rectificationResponsiblePerson,
  604. reviewDepartmentId: ruleFormData.reviewDepartmentId!,
  605. reviewPersonId: ruleFormData.reviewPersonId!,
  606. reviewPersonName: ruleFormData.reviewPersonName,
  607. isDrawLessonsPush: ruleFormData.isDrawLessonsPush ?? 0,
  608. drawLessonsContent: ruleFormData.drawLessonsContent || undefined,
  609. drawLessonsDepartmentIds: ruleFormData.drawLessonsDepartmentIds || undefined,
  610. drawLessonsDeadline: drawLessonsDeadline || undefined,
  611. attachments: ruleFormData.attachments || undefined,
  612. };
  613. if (isCreateMode.value) {
  614. await saveHiddenDanger(payload);
  615. ElMessage.success('创建成功');
  616. } else if (currentId.value) {
  617. await updateHiddenDanger({ id: currentId.value, ...payload } as UpdateHiddenDangerRequest);
  618. ElMessage.success('保存成功');
  619. }
  620. router.back();
  621. } catch (e) {
  622. console.error('保存隐患台账失败:', e);
  623. ElMessage.error(e?.message || e?.data || '保存失败,请重试');
  624. }
  625. };
  626. // ---------- 整改(详情页直接提交,无弹窗) ----------
  627. async function handleRectifySubmitDirect() {
  628. if (!currentId.value) return;
  629. try {
  630. const uploadedFileList = await formatAttachmentList(attachmentsFileList.value);
  631. await rectifyHiddenDanger({
  632. dangerId: currentId.value,
  633. rectificationPlan: '',
  634. rectificationResponsiblePerson: ruleFormData.rectificationResponsiblePerson || '',
  635. rectificationStartTime: '',
  636. rectificationEndTime: '',
  637. rectificationCompletionStatus: ruleFormData.rectificationCompletionStatus || '',
  638. rectificationCompletionTime: ruleFormData.rectificationCompletionTime || '',
  639. attachments: JSON.stringify(uploadedFileList) || undefined,
  640. isCompleted: 1,
  641. });
  642. ElMessage.success('整改提交成功');
  643. router.back();
  644. } catch (e) {
  645. console.error('整改提交失败:', e);
  646. ElMessage.error(e?.message || e?.data || '整改提交失败,请重试');
  647. }
  648. }
  649. // ---------- 复查 ----------
  650. const showReviewDialog = ref(false);
  651. const reviewFormRef = ref<FormInstance>();
  652. const reviewForm = ref<ReviewHiddenDangerRequest & { reviewResult: number }>({
  653. dangerId: 0,
  654. reviewResult: 1,
  655. reviewComments: '',
  656. reviewReason: '',
  657. reviewTime: '',
  658. attachments: '',
  659. });
  660. const reviewRules: FormRules = {
  661. reviewResult: [{ required: true, message: '请选择复查结论', trigger: 'change' }],
  662. };
  663. function resetReviewForm() {
  664. reviewForm.value = {
  665. dangerId: currentId.value || 0,
  666. reviewResult: 1,
  667. reviewComments: '',
  668. reviewReason: '',
  669. reviewTime: '',
  670. attachments: '',
  671. };
  672. }
  673. function openReviewDialog() {
  674. resetReviewForm();
  675. showReviewDialog.value = true;
  676. }
  677. async function handleReviewSubmit() {
  678. if (!currentId.value) return;
  679. if (reviewForm.value.reviewResult === 0 && !reviewForm.value.reviewReason) {
  680. ElMessage.warning('不通过时请填写不通过原因');
  681. return;
  682. }
  683. await reviewFormRef.value?.validate?.().catch(() => {});
  684. try {
  685. reviewForm.value.dangerId = currentId.value;
  686. await reviewHiddenDanger(reviewForm.value);
  687. ElMessage.success('复查提交成功');
  688. showReviewDialog.value = false;
  689. getDetail();
  690. } catch (e) {
  691. console.error('复查提交失败:', e);
  692. ElMessage.error(e?.message || e?.data || '复查提交失败,请重试');
  693. }
  694. }
  695. // ---------- 复查详情页:审查通过 / 审查不通过 ----------
  696. const showReviewRejectDialog = ref(false);
  697. const reviewRejectReason = ref('');
  698. function openReviewRejectDialog() {
  699. reviewRejectReason.value = '';
  700. showReviewRejectDialog.value = true;
  701. }
  702. async function handleReviewPass() {
  703. if (!currentId.value) return;
  704. try {
  705. await reviewHiddenDanger({
  706. dangerId: currentId.value,
  707. reviewResult: 1,
  708. reviewComments: ruleFormData.reviewComments || undefined,
  709. });
  710. ElMessage.success('审查通过');
  711. router.back();
  712. } catch (e) {
  713. console.error('审查通过提交失败:', e);
  714. ElMessage.error(e?.message || e?.data || '提交失败,请重试');
  715. }
  716. }
  717. async function handleReviewRejectSubmit() {
  718. const reason = (reviewRejectReason.value || '').trim();
  719. if (!reason) {
  720. ElMessage.warning('请填写审核不通过原因');
  721. return;
  722. }
  723. if (!currentId.value) return;
  724. try {
  725. await reviewHiddenDanger({
  726. dangerId: currentId.value,
  727. reviewResult: 0,
  728. reviewReason: reason,
  729. });
  730. ElMessage.success('已提交审查不通过');
  731. showReviewRejectDialog.value = false;
  732. router.back();
  733. } catch (e) {
  734. console.error('审查不通过提交失败:', e);
  735. ElMessage.error(e?.message || e?.data || '提交失败,请重试');
  736. }
  737. }
  738. // ---------- 扣分 ----------
  739. const showDeductDialog = ref(false);
  740. const deductFormRef = ref<FormInstance>();
  741. const deductDeptIdsArray = ref<number[]>([]);
  742. const deductForm = ref<HiddenDangerDeductPointsRequest>({
  743. dangerId: 0,
  744. deductionScore: 0,
  745. });
  746. const deductRules: FormRules = {
  747. deductionScore: [
  748. { required: true, message: '请输入扣分值', trigger: 'blur' },
  749. { type: 'number', min: 0, max: 9999, message: '扣分值须为0-9999的整数', trigger: 'blur' },
  750. ],
  751. };
  752. function resetDeductForm() {
  753. deductDeptIdsArray.value = [];
  754. deductForm.value = {
  755. dangerId: currentId.value || 0,
  756. deductionScore: 0,
  757. };
  758. }
  759. function openDeductDialog() {
  760. resetDeductForm();
  761. showDeductDialog.value = true;
  762. }
  763. async function handleDeductSubmit() {
  764. if (!currentId.value) return;
  765. await deductFormRef.value?.validate?.().catch(() => {});
  766. const score = Number(deductForm.value.deductionScore);
  767. if (score < 0 || score > 9999 || !Number.isInteger(score)) {
  768. ElMessage.warning('扣分值须为0-9999的整数');
  769. return;
  770. }
  771. try {
  772. deductForm.value.dangerId = currentId.value;
  773. deductForm.value.departmentIds =
  774. deductDeptIdsArray.value.length > 0 ? deductDeptIdsArray.value.join(',') : undefined;
  775. await deductHiddenDangerPoints(deductForm.value);
  776. ElMessage.success('扣分记录成功');
  777. showDeductDialog.value = false;
  778. } catch (e) {
  779. console.error('扣分失败:', e);
  780. ElMessage.error(e?.message || e?.data || '扣分失败,请重试');
  781. }
  782. }
  783. const issueDeptTree = ref<DeptTree[]>([]);
  784. const issueUserList = ref<Array<{ id: number; realname?: string; username?: string }>>([]);
  785. const onIssueDialogOpen = async () => {
  786. try {
  787. const [deptRes, userRes] = await Promise.all([
  788. getAllDepartments(),
  789. queryAvailableUserList({ pageNumber: 1, pageSize: 9999, queryParam: {} }),
  790. ]);
  791. const fullTree = (deptRes as DeptTree[]) ?? [];
  792. issueDeptTree.value = Array.isArray(fullTree) && fullTree[0]?.children ? fullTree[0].children : [];
  793. issueUserList.value = (userRes as any)?.records ?? [];
  794. } catch (e) {
  795. console.error('获取部门/用户列表失败:', e);
  796. ElMessage.error(e?.message || e?.data || '加载部门或负责人列表失败');
  797. issueDeptTree.value = [];
  798. issueUserList.value = [];
  799. }
  800. };
  801. onMounted(async () => {
  802. cloneRuleFormData();
  803. onIssueDialogOpen();
  804. await loadDeptAndUserOptions();
  805. if (isEditMode.value || isViewMode.value) {
  806. await getDetail();
  807. if (route.query.action === 'review' && isViewMode.value && canReview.value) {
  808. openReviewDialog();
  809. }
  810. }
  811. });
  812. </script>
  813. <style scoped lang="scss">
  814. @use '@/styles/page-details-layout.scss' as *;
  815. .file-item{
  816. display: flex;
  817. justify-content: flex-start;
  818. gap: 10px;
  819. }
  820. </style>