hiddenTroubleAccountManagementDetail.vue 27 KB

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