Sfoglia il codice sorgente

feat:修改区域检查计划

sunqijun 3 settimane fa
parent
commit
8f9538c566
16 ha cambiato i file con 570 aggiunte e 54 eliminazioni
  1. 7 13
      src/api/production-safety-system/index.ts
  2. 18 2
      src/api/safety-system-construction-work-plan/index.ts
  3. 2 1
      src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagement/areaCheckPlanManagement.vue
  4. 29 20
      src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagement/components/areaCheckPlanManagementDetail.vue
  5. 7 7
      src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagement/configs/form.ts
  6. 2 2
      src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagement/configs/types.ts
  7. 1 1
      src/views/production-safety/productionSafetySystem/lawManagement/lawManagement.vue
  8. 1 0
      src/views/production-safety/productionSafetySystem/safetySystemConstructionWorkPlanManagement/components/safetySystemConstructionWorkPlanManagementDetail.vue
  9. 443 0
      src/views/production-safety/productionSafetySystem/safetySystemConstructionWorkPlanManagement/components/safetySystemConstructionWorkPlanManagementReview.vue
  10. 5 2
      src/views/production-safety/productionSafetySystem/safetySystemConstructionWorkPlanManagement/configs/tables.ts
  11. 15 1
      src/views/production-safety/productionSafetySystem/safetySystemConstructionWorkPlanManagement/safetySystemConstructionWorkPlanManagement.vue
  12. 9 2
      src/views/production-safety/productionSafetySystem/safetySystemConstructionWorkPlanManagement/safetySystemConstructionWorkPlanManagementItem.vue
  13. 16 1
      src/views/production-safety/productionSafetySystem/safetySystemConstructionWorkPlanManagement/safetySystemConstructionWorkPlanManagementViewSender.vue
  14. 8 0
      src/views/production-safety/productionSafetySystem/safetySystemConstructionWorkPlanManagementDept/components/safetySystemConstructionWorkPlanManagementDeptDetail.vue
  15. 6 2
      src/views/production-safety/productionSafetySystem/safetySystemConstructionWorkPlanManagementDept/configs/tables.ts
  16. 1 0
      src/views/production-safety/productionSafetySystem/safetySystemConstructionWorkPlanManagementDept/safetySystemConstructionWorkPlanManagementDept.vue

+ 7 - 13
src/api/production-safety-system/index.ts

@@ -778,11 +778,7 @@ interface AreaCheckPlanManageApiRecord {
   checkPlaceCategory?: string; // 检查场所所属类别
   checkCategory?: string; // 检查类别
   status?: number | string; // 状态:0=未开始 1=进行中 2=已完成 3=已终止
-  /** save/update 请求体使用:主责部门名称 */
-  primaryResponsibleDeptName?: string;
   primaryResponsibleDeptPersonCode?: string;
-  /** save/update 请求体使用:主责部门 code */
-  primaryResponsibleDeptCode?: string;
   responsibleDeptName?: string; // 主责部门名称(兼容后端返回)
   responsibleDeptCode?: string; // 主责部门code(兼容后端返回)
   selfCheckFrequency?: number | string; // 自查频次:0=每日 1=每周 2=每月 3=每季度 4=每半年 5=每年
@@ -835,8 +831,8 @@ export interface AreaCheckPlanRecord {
   status?: AreaCheckPlanStatus;
   venueCategoryName?: string;
   planName?: string;
-  primaryResponsibleDeptName?: string;
-  primaryResponsibleDeptCode?: string;
+  responsibleDeptName?: string;
+  responsibleDeptCode?: string;
   selfCheckFrequency?: string;
   mainDeptExecutorGroupName?: string;
   mainDeptExecGroupCode?: string;
@@ -886,8 +882,8 @@ export function mapAreaCheckPlanApiRecordToUi(api: AreaCheckPlanManageApiRecord
     createdPersonName: api.createdPersonName,
     venueCategoryName: api.checkPlaceCategory ?? api.checkCategory,
     status: api.status as AreaCheckPlanStatus | undefined,
-    primaryResponsibleDeptName: api.primaryResponsibleDeptName ?? api.responsibleDeptName ?? api.responsibleDept,
-    primaryResponsibleDeptCode: api.primaryResponsibleDeptCode ?? api.responsibleDeptCode,
+    responsibleDeptName: api.responsibleDeptName ?? api.responsibleDeptName ?? api.responsibleDept,
+    responsibleDeptCode: api.responsibleDeptCode,
     selfCheckFrequency: toFreq(api.selfCheckFrequency),
     mainDeptExecutorGroupName: api.responsibleDeptExecGroupName ?? api.responsibleDeptExecGroup,
     mainDeptExecGroupCode: api.responsibleDeptExecGroupCode,
@@ -925,8 +921,6 @@ export function mapAreaCheckPlanApiRecordToUi(api: AreaCheckPlanManageApiRecord
     hospitalLeaderExecGroupName: api.hospitalLeaderExecGroupName,
     hospitalLeaderPersonName: api.hospitalLeaderPersonName,
     businessWork: api.businessWork,
-    responsibleDeptName: api.responsibleDeptName,
-    responsibleDeptCode: api.responsibleDeptCode,
   };
 }
 
@@ -934,15 +928,15 @@ function uiRecordToApi(ui: AreaCheckPlanRecord & { id?: number }): AreaCheckPlan
   const toFreqNum = (v: string | undefined) => (v ? LABEL_TO_FREQUENCY[v] ?? undefined : undefined);
   const toNum = (v: boolean | undefined) => (v === true ? 1 : v === false ? 0 : undefined);
   const code = (ui as Record<string, unknown>);
-  const primaryResponsibleDeptCode = (code.primaryResponsibleDeptCode as string | undefined)
+  const responsibleDeptCode = (code.responsibleDeptCode as string | undefined)
     ?? (code.primaryResponsibleDeptId != null ? String(code.primaryResponsibleDeptId) : undefined);
   return {
     id: ui.id,
     areaCheckPlanName: ui.planName,
     checkPlace: ui.checkVenue,
     checkPlaceCategory: ui.venueCategoryName,
-    primaryResponsibleDeptName: ui.primaryResponsibleDeptName,
-    primaryResponsibleDeptCode,
+    responsibleDeptName: ui.responsibleDeptName,
+    responsibleDeptCode,
     selfCheckFrequency: toFreqNum(ui.selfCheckFrequency),
     responsibleDeptExecGroupName: ui.mainDeptExecutorGroupName,
     responsibleDeptExecGroupCode: code.mainDeptExecGroupCode as string | undefined,

+ 18 - 2
src/api/safety-system-construction-work-plan/index.ts

@@ -2,7 +2,7 @@
  * @Author: liuJie
  * @Date: 2026-02-06 16:29:04
  * @LastEditors: liuJie
- * @LastEditTime: 2026-04-08 10:27:40
+ * @LastEditTime: 2026-04-09 10:46:51
  * @Describe: 安全体系建设工作计划管理(管理员)
  */
 import { http } from '@/utils/http/axios';
@@ -78,7 +78,11 @@ export interface IssueWorkPlanParams {
   plannedStartTime: string;
   plannedEndTime: string;
 }
-
+export interface ReviewWorkPlanParams{
+  id: number; // 部门责任书ID
+  approveType:number;
+  refuseReason?: string; // 拒绝原因
+}
 export interface QueryParam {
   planId: number | string;
   keyword: string;
@@ -202,6 +206,18 @@ export function issueWorkPlan(data: IssueWorkPlanParams) {
   });
 }
 
+
+/**
+ * 审核安全体系建设工作计划
+ */
+export function reviewWorkPlan(data: ReviewWorkPlanParams) {
+  return http.request<void>({
+    url: `${ADMIN_BASE}/approve`,
+    method: 'put',
+    data,
+  });
+}
+
 /**
  * 作废安全体系建设工作计划
  */

+ 2 - 1
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagement/areaCheckPlanManagement.vue

@@ -89,7 +89,8 @@
             </template>
             <template #primaryResponsibleDeptName="scope">
                     <!-- 如果有下发按钮就展示原来的字段primaryResponsibleDeptName,没有下发按钮就展示responsibleDeptName -->
-                    {{ Number(scope.row.status) === 0 ? scope.row.primaryResponsibleDeptName: scope.row.responsibleDeptName}}
+                     <!-- 产品需求要求 使用responsibleDeptName展示 -->
+                    {{ scope.row.responsibleDeptName}}
             </template>
             <template #needSigneeSign="scope">
               <span>{{

+ 29 - 20
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagement/components/areaCheckPlanManagementDetail.vue

@@ -29,9 +29,8 @@
         <div class="row">
           <div class="col">
             <div class="label">主责部门:</div>
-            <!-- <div class="value">{{ viewDetail.primaryResponsibleDeptName || '-' }}</div> -->
             <div class="value">
-                {{ Number(viewDetail.status) === 0 ? viewDetail.primaryResponsibleDeptName: viewDetail.responsibleDeptName ||'-'}}
+                {{ viewDetail.responsibleDeptName ||'-'}}
             </div>
           </div>
           <div class="col">
@@ -218,13 +217,16 @@
       :formRules="isViewMode ? undefined : dynamicFormRules"
       :formConfig="computedFormConfig"
     >
-      <template #primaryResponsibleDept>
+      <template #responsibleDeptName>
         <el-cascader
           ref="primaryResponsibleDeptCascaderRef"
-          v-model="ruleFormData.primaryResponsibleDeptId"
-          :options="deptTree"
-          :props="cascaderDeptProp"
+          v-model="ruleFormData.responsibleDeptId"
+          :options="deptTreeOne"
+          :props="primaryResponsibleDeptCascaderProp"
           :show-all-levels="false"
+          collapse-tags
+          collapse-tags-tooltip
+          :max-collapse-tags="3"
           placeholder="请选择部门"
           filterable
           clearable
@@ -683,6 +685,7 @@
   const safetyEmergencyDeptCascaderRef = ref();
   const hospitalLeaderDeptCascaderRef = ref();
   const deptTree = ref<DeptTree[]>([]);
+  const deptTreeOne = ref<DeptTree[]>([]);
   const cascaderDeptProp = {
     checkStrictly: true,
     expandTrigger: 'hover' as const,
@@ -749,9 +752,14 @@
     try {
       const res = await getAllDepartments();
       deptTree.value = res?.[0]?.children ?? [];
+      deptTreeOne.value = res?.[0]?.children.map(item => ({
+        ...item,
+        children: []
+      }));
     } catch (e) {
       console.error('获取部门树失败:', e);
       deptTree.value = [];
+      deptTreeOne.value = [];
     }
   };
   const cascaderDeptRef = ref<CascaderInstance | null>(null)
@@ -842,20 +850,20 @@
       dangerTypeList.value = [];
     }
   }
-  const setDeptNameFromCascader = (refVal: any, nameKey: 'primaryResponsibleDeptName' | 'safetyEmergencyDeptName' | 'hospitalLeaderDeptName') => {
-    if (nameKey === 'primaryResponsibleDeptName') {
+  const setDeptNameFromCascader = (refVal: any, nameKey: 'responsibleDeptName' | 'safetyEmergencyDeptName' | 'hospitalLeaderDeptName') => {
+    if (nameKey === 'responsibleDeptName') {
       const nodes = refVal?.getCheckedNodes?.() ?? [];
       if (!nodes.length) {
-        ruleFormData.primaryResponsibleDeptName = '';
-        ruleFormData.primaryResponsibleDeptCode = '';
+        ruleFormData.responsibleDeptName = '';
+        ruleFormData.responsibleDeptCode = '';
         return;
       }
       const labels = nodes.map((n: { label?: string }) => n?.label ?? '').filter(Boolean);
       const values = nodes
         .map((n: { value?: unknown }) => n?.value)
         .filter((v: unknown) => v != null && v !== '');
-      ruleFormData.primaryResponsibleDeptName = labels.join(',');
-      ruleFormData.primaryResponsibleDeptCode = values.map(String).join(',');
+      ruleFormData.responsibleDeptName = labels.join(',');
+      ruleFormData.responsibleDeptCode = values.map(String).join(',');
       return;
     }
     const nodes = refVal?.getCheckedNodes?.();
@@ -864,7 +872,7 @@
   };
 
   const onPrimaryResponsibleDeptChange = () => {
-    setDeptNameFromCascader(primaryResponsibleDeptCascaderRef.value, 'primaryResponsibleDeptName');
+    setDeptNameFromCascader(primaryResponsibleDeptCascaderRef.value, 'responsibleDeptName');
   };
   const onSafetyEmergencyDeptChange = () => {
     setDeptNameFromCascader(safetyEmergencyDeptCascaderRef.value, 'safetyEmergencyDeptName');
@@ -982,12 +990,12 @@
     const status = d?.status as number | undefined;
     return {
       ...d,
-      responsibleDeptName: d?.responsibleDeptName ?? '-',
+    //   responsibleDeptName: d?.responsibleDeptName ?? '-',
       statusName: status != null ? AREA_CHECK_PLAN_STATUS_LABEL[String(status)] ?? '-' : '-',
       planName: d?.planName ?? ruleFormData.planName ?? '-',
       venueCategoryName: d?.venueCategoryName ?? ruleFormData.venueCategoryName ?? '-',
       checkVenue: d?.checkVenue ?? ruleFormData.checkVenue ?? '-',
-      primaryResponsibleDeptName: d?.primaryResponsibleDeptName ?? ruleFormData.primaryResponsibleDeptName ?? '-',
+      responsibleDeptName: d?.responsibleDeptName ?? ruleFormData.responsibleDeptName ?? '-',
       selfCheckFrequency: d?.selfCheckFrequency ?? ruleFormData.selfCheckFrequency ?? '-',
       mainDeptExecutorGroupName: d?.mainDeptExecutorGroupName ?? '-',
       mainDeptResponsiblePerson: d?.mainDeptResponsiblePerson ?? '-',
@@ -1338,11 +1346,11 @@
       ruleFormData.planName = detail.planName ?? '';
       ruleFormData.venueCategoryName = detail.venueCategoryName ?? '';
       ruleFormData.checkVenue = detail.checkVenue ?? '';
-      ruleFormData.primaryResponsibleDeptName = detail.primaryResponsibleDeptName ?? '';
-      ruleFormData.primaryResponsibleDeptCode = detail.primaryResponsibleDeptCode ?? '';
-      ruleFormData.primaryResponsibleDeptId = parsePrimaryResponsibleDeptIds(
-        String(detail.primaryResponsibleDeptCode ?? ''),
-        String(detail.primaryResponsibleDeptName ?? ''),
+      ruleFormData.responsibleDeptName = detail.responsibleDeptName ?? '';
+      ruleFormData.responsibleDeptCode = detail.responsibleDeptCode ?? '';
+      ruleFormData.responsibleDeptId = parsePrimaryResponsibleDeptIds(
+        String(detail.responsibleDeptCode ?? ''),
+        String(detail.responsibleDeptName ?? ''),
         deptTree.value,
       );
       ruleFormData.selfCheckFrequency = detail.selfCheckFrequency ?? '';
@@ -1380,6 +1388,7 @@
     const valid = await handleValidate();
     if (!valid) return;
     try {
+        console.log(ruleFormData, '提交数据');
       if (isCreateMode.value) {
         await saveAreaCheckPlanManage(ruleFormData as AreaCheckPlanRecord);
         ElMessage.success('创建成功');

+ 7 - 7
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagement/configs/form.ts

@@ -153,9 +153,9 @@ export const AREA_CHECK_PLAN_FORM_CONFIG: FormConfig[] = [
     },
   },
   {
-    prop: 'primaryResponsibleDeptName',
+    prop: 'responsibleDeptName',
     label: '主责部门:',
-    slot: 'primaryResponsibleDept', // 与新增物品领取记录页的部门下拉一致(el-cascader + getAllDepartments)
+    slot: 'responsibleDeptName', // 与新增物品领取记录页的部门下拉一致(el-cascader + getAllDepartments)
     componentProps: {
       placeholder: '请选择部门',
     },
@@ -233,10 +233,10 @@ export const AREA_CHECK_PLAN_FORM_DATA: Record<string, unknown> = {
   planName: '',
   venueCategoryName: '',
   checkVenue: '',
-  /** 主责部门多选:级联绑定 id 数组;提交给后端用 primaryResponsibleDeptName / primaryResponsibleDeptCode(逗号分隔) */
-  primaryResponsibleDeptId: [] as number[],
-  primaryResponsibleDeptName: '',
-  primaryResponsibleDeptCode: '',
+  /** 主责部门多选:级联绑定 id 数组;提交给后端用 responsibleDeptName / responsibleDeptCode(逗号分隔) */
+  responsibleDeptId: [] as number[],
+  responsibleDeptName: '',
+  responsibleDeptCode: '',
   selfCheckFrequency: '',
   safetyEmergencyDeptId: null as number | null,
   safetyEmergencyDeptName: '',
@@ -254,7 +254,7 @@ export const AREA_CHECK_PLAN_FORM_RULES = {
   planName: [{ required: true, message: '请输入区域检查计划名称', trigger: 'blur' }],
   venueCategoryName: [{ required: true, message: '请输入检查类别', trigger: 'blur' }],
   checkVenue: [{ required: true, message: '请输入检查场所', trigger: 'blur' }],
-  primaryResponsibleDeptName: [{ required: true, message: '请选择主责部门', trigger: 'change' }],
+  responsibleDeptName: [{ required: true, message: '请选择主责部门', trigger: 'change' }],
   selfCheckFrequency: [{ required: true, message: '请选择自查频次', trigger: 'change' }],
   safetyEmergencyCheckFrequency: [{ required: true, message: '请选择安全应急部检查频次', trigger: 'change' }],
   safetyEmergencyDeptName: [{ required: true, message: '请选择安全应急部门', trigger: 'change' }],

+ 2 - 2
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagement/configs/types.ts

@@ -7,8 +7,8 @@ export interface AreaCheckPlanRecord {
   status?: AreaCheckPlanStatus;
   venueCategoryName?: string;
   planName?: string;
-  primaryResponsibleDeptName?: string;
-  primaryResponsibleDeptCode?: string;
+  responsibleDeptName?: string;
+  responsibleDeptCode?: string;
   selfCheckFrequency?: string;
   mainDeptExecutorGroupName?: string;
   mainDeptExecGroupCode?: string;

+ 1 - 1
src/views/production-safety/productionSafetySystem/lawManagement/lawManagement.vue

@@ -233,7 +233,7 @@
   // 批量导入
   const batchImportVisible = ref(false);
   const { urlPrefix } = useGlobSetting();
-  const importApiUrl = ref(urlJoin(urlPrefix, '/productionSafety/lawRegulation/import'));
+  const importApiUrl = ref(urlJoin(urlPrefix, '/admin/prod/lawRegulation/import'));
   const templateUrl = ref('./skyeye-file-upload/sfysecurity/TEMPLATE/法律法规导入模版.xlsx');
 
   const handleImport = () => {

+ 1 - 0
src/views/production-safety/productionSafetySystem/safetySystemConstructionWorkPlanManagement/components/safetySystemConstructionWorkPlanManagementDetail.vue

@@ -119,6 +119,7 @@
     saveWorkPlan,
     updateWorkPlan,
     queryWorkPlanDetail,
+    reviewWorkPlan,
     type SaveWorkPlanRequest,
   } from '@/api/safety-system-construction-work-plan';
   import UploadFiles from '@/components/UploadFiles/UploadFiles.vue';

+ 443 - 0
src/views/production-safety/productionSafetySystem/safetySystemConstructionWorkPlanManagement/components/safetySystemConstructionWorkPlanManagementReview.vue

@@ -0,0 +1,443 @@
+<template>
+  <main class="safety-platform-container__main">
+    <el-form
+      ref="formRef"
+      :model="form"
+      :rules="rules"
+      label-width="150px"
+      style="max-width: 600px"
+      label-position="left"
+    >
+      <el-form-item label="生产安全计划名称:" prop="trainingPlanName" required>
+        <el-input v-model="form.trainingPlanName" placeholder="请输入生产安全计划名称" :disabled="isViewMode || isReviewMode" />
+      </el-form-item>
+
+      <el-form-item label="分类名称:" prop="categoryName" required>
+        <el-select v-model="form.categoryName" placeholder="请选择分类名称" :disabled="isViewMode || isReviewMode">
+          <el-option v-for="item in classifyNameOptions" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="责任部门:" prop="responsibleDeptIds" required>
+        <!-- 新需求,增加全选功能 -->
+        <el-select
+          v-model="form.responsibleDeptIds"
+          multiple
+          :disabled="isViewMode || isReviewMode"
+          collapse-tags
+          collapse-tags-tooltip
+          :max-collapse-tags="3"
+          placeholder="请选择责任部门"
+          filterable
+        >
+          <template #header>
+            <el-checkbox v-model="checkAll" :indeterminate="indeterminate" @change="handleCheckAllChange">
+              全部单位
+            </el-checkbox>
+          </template>
+          <el-option
+            v-for="item in deptTreeOne"
+            :key="item.id"
+            :label="item.deptName"
+            :value="item.id"
+          />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="配合部门:" prop="cooperateDeptIds" required>
+        <el-cascader
+          v-model="form.cooperateDeptIds"
+          :options="deptTree"
+          :props="cascaderProp"
+          clearable
+          collapse-tags
+          :show-all-levels="false"
+          :max-collapse-tags="3"
+          popper-class="cascader-popper--custom"
+          placeholder="请选择配合部门"
+          style="width: 100%"
+          :disabled="isViewMode || isReviewMode"
+        />
+      </el-form-item>
+
+      <el-form-item label="计划完成时间:" prop="plannedComplateTime" required>
+        <el-date-picker
+          v-model="form.plannedComplateTime"
+          type="date"
+          placeholder="请选择完成时间"
+          style="width: 100%"
+          :disabled="isViewMode || isReviewMode"
+        />
+      </el-form-item>
+
+      <el-form-item label="工作内容:" prop="workContent" required>
+        <el-input
+          v-model="form.workContent"
+          type="textarea"
+          :rows="4"
+          :maxlength="300"
+          placeholder="请填写工作内容"
+          :disabled="isViewMode || isReviewMode"
+        />
+      </el-form-item>
+      <el-form-item label="附件上传:" v-if="isViewMode || isReviewMode" prop="fileUrl" :required="isViewMode || isReviewMode">
+        <div class="file-list" v-if="form.fileUrl && form.fileUrl.length != 0">
+          <div class="file-item" v-for="file in form.fileUrl" :key="file.fileId">
+            <span class="file-item--name">{{ file.fileName }}</span>
+            <div class="file-item--footer">
+              <el-button link type="primary" @click="previewOnline(file.fileUrl, file.fileType)"
+                >预览</el-button
+              >
+              <el-button link type="primary" @click.stop="downloadFile(file.fileUrl, file.fileName)"
+                >下载</el-button
+              >
+            </div>
+          </div>
+        </div>
+        <div v-else>
+          暂无附件
+        </div>
+      </el-form-item>
+    </el-form>
+    <PreviewOnline ref="previewOnlineRef" />
+  </main>
+  <footer class="safety-platform-container__footer">
+    <el-button @click="router.back()">返回</el-button>
+    <el-button v-if="!isViewMode && !isReviewMode" type="primary" @click="handleSubmit">
+      {{ isCreateMode ? '提交' : '保存' }}
+    </el-button>
+    <el-button v-if="isReviewMode" type="primary" @click="handleReview('reject')">
+      审核不通过
+    </el-button>
+    <el-button v-if="isReviewMode" type="primary" @click="handleReview('approve')">
+      审核通过
+    </el-button>
+  </footer>
+  <el-dialog v-model="reviewDialogVisible"  title="审核不通过" width="40%" >
+    <p style="margin-bottom: 12px;">审核不通过的记录,需要填写驳回原因</p>
+    <el-input type="textarea" :maxlength="300" show-word-limit  :autosize="{ minRows: 6, maxRows: 8 }"  v-model="rejectReason" placeholder="请填写驳回审批原因"></el-input>
+    <template #footer>
+      <el-button @click="reviewDialogVisible = false">取 消</el-button>
+      <el-button type="primary" @click="handleReviewSubmit">确 定</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+  import { computed, onMounted, ref, reactive } from 'vue';
+  import { useRoute, useRouter } from 'vue-router';
+  import { ElMessage } from 'element-plus';
+  import type { FormInstance } from 'element-plus';
+  import { FORM_RULES } from '../configs/form';
+  import {
+    saveWorkPlan,
+    updateWorkPlan,
+    queryWorkPlanDetail,
+    reviewWorkPlan,
+    queryWorkPlanDepartmentDetail,
+    type SaveWorkPlanRequest,
+  } from '@/api/safety-system-construction-work-plan';
+  import UploadFiles from '@/components/UploadFiles/UploadFiles.vue';
+  import type { FileItem } from '@/components/UploadFiles/types';
+  import { formatAttachmentList } from '@/components/UploadFiles/utils';
+  import PreviewOnline from '@/views/disaster/components/PreviewOnline.vue';
+  import { DeptTree } from '@/types/dept/type';
+  import { getAllDepartments } from '@/api/auth/dept';
+  import { downloadFile } from '@/views/disaster/utils';
+
+  const router = useRouter();
+  const route = useRoute();
+
+  const operate = computed(() => (route.query.operate as string) || 'work-plan-create');
+  const currentId = computed(() => Number(route.query.id));
+  const planId = computed(() => Number(route.query.planId));
+
+  const isCreateMode = computed(() => operate.value === 'work-plan-create');
+  const isEditMode = computed(() => operate.value === 'work-plan-edit');
+  const isViewMode = computed(() => operate.value === 'work-plan-view');
+  const isReviewMode = computed(() => operate.value === 'work-plan-review');
+
+  const form = reactive<SaveWorkPlanRequest>({
+    workContent: '',
+    categoryName: '',
+    trainingPlanName: '',
+    responsibleDeptIds: '',
+    cooperateDeptIds: '',
+    executGroupIds: '',
+    plannedComplateTime: '',
+    fileUrl: '',
+  });
+
+  // 分类名称选项
+  const classifyNameOptions = ref<Array<{ label: string; value: string }>>([
+    { label: '安全综合工作', value: '安全综合工作' },
+    { label: '生产安全工作', value: '生产安全工作' },
+    { label: '安全文化活动', value: '安全文化活动' },
+  ]);
+
+  /** 表单校验规则 */
+  const rules = ref<Record<string, import('element-plus').FormItemRule[]>>(FORM_RULES);
+
+  const cascaderProp = {
+    multiple: true,
+    expandTrigger: 'hover',
+    checkStrictly: true,
+    emitPath: false,
+    value: 'id',
+    label: 'deptName',
+  };
+
+  // 获取级联部门数据
+  const deptTree = ref<DeptTree[]>();
+  const deptTreeOne = ref<DeptTree[]>();
+  const loadDeptTreeData = async () => {
+    const result = await getAllDepartments();
+    deptTree.value = result[0].children;
+    deptTreeOne.value = result?.[0]?.children.map(item => ({
+        ...item,
+        children: []
+      }));
+    //编辑、查看时当一级部门个数和所选长度一致,则是全选
+    if (isEditMode.value || isViewMode.value) {
+        if(deptTreeOne.value.length === form.responsibleDeptIds.length){
+            checkAll.value = true
+        }
+    }
+  };
+  const checkAll = ref(false);
+  const indeterminate = ref(false);
+  const handleCheckAllChange = () => {
+    if (checkAll.value && deptTreeOne.value) {
+      form.responsibleDeptIds = deptTreeOne.value.map((item) => item.id);
+    } else {
+      form.responsibleDeptIds = [];
+    }
+  };
+  /** 附件 JSON [{file_name, url}] 转 FileItem 列表,供 UploadFiles 使用(新增/编辑/查看/审核统一展示) */
+  function convertAttachmentJsonToFileItems(attachmentStr: string): FileItem[] {
+    if (!attachmentStr || !String(attachmentStr).trim()) return [];
+    try {
+      const arr = JSON.parse(attachmentStr);
+      if (!Array.isArray(arr)) return [];
+      return arr.map((item: any, index: number) => {
+        const fileName = item.file_name || item.fileName || `附件${index + 1}`;
+        const url = item.url || item.fileUrl || '';
+        const ext = (fileName || '').split('.').pop()?.toLowerCase() || '';
+        let fileType = 'pdf';
+        if (['doc', 'docx'].includes(ext)) fileType = 'word';
+        else if (['xls', 'xlsx'].includes(ext)) fileType = 'excel';
+        else if (['ppt', 'pptx'].includes(ext)) fileType = 'ppt';
+        return { fileId: index + 1, fileName, fileType, fileSize: '0', fileUrl: url };
+      });
+    } catch {
+      return [];
+    }
+  }
+
+  const attachmentFileList = computed(() => convertAttachmentJsonToFileItems(form.fileUrl || ''));
+
+  async function handleAttachmentUploadSuccess(files: FileItem[]) {
+    if (!files?.length) {
+      form.fileUrl = '';
+      return;
+    }
+    try {
+      const list = await formatAttachmentList(files);
+      const jsonArr = (list || [])
+        .map((r) => ({
+          file_name: r.fileName,
+          url: r.fileUrl || '',
+        }))
+        .filter((x) => x.url);
+      form.fileUrl = JSON.stringify(jsonArr);
+      handleSubmit();
+    } catch (e) {
+      console.error('附件上传失败:', e);
+      ElMessage.error('附件上传失败,请重试');
+    }
+  }
+  const reviewDialogVisible = ref(false);
+  const rejectReason = ref(''); // 驳回原因
+  // 审核
+  const handleReview = async (action: 'approve' | 'reject') => {
+    if (!currentId.value) return;
+    if(action === 'approve'){
+        // 直接审核通过,无需填写驳回原因
+        try {
+            await reviewWorkPlan({
+            id: currentId.value,
+            approveType: 1, // 1=审核通过
+            });
+            ElMessage.success('审核通过');
+            router.back();
+        } catch (e) {
+            console.error('审核失败:', e);
+            ElMessage.error('审核失败,请重试');
+        }
+    } else {
+        reviewDialogVisible.value = true;
+    }
+  };
+  // 提交审核结果(驳回)
+  const handleReviewSubmit = async () => {
+    if (!currentId.value) return;
+    if (!rejectReason.value.trim()) {
+      ElMessage.warning('请填写驳回原因');
+      return;
+    }
+    try {
+      await reviewWorkPlan({
+        id: currentId.value,
+        approveType: 0, // 0=审核不通过
+        refuseReason: rejectReason.value.trim(),
+      });
+      ElMessage.success('审核已驳回');
+      router.back();
+    } catch (e) {
+      console.error('提交审核结果失败:', e);
+      ElMessage.error('提交审核结果失败,请重试');
+    }
+  };
+  const previewOnlineRef = ref<InstanceType<typeof PreviewOnline>>();
+  const handlePreview = (url: string) => {
+    if (url) {
+      // 根据文件扩展名判断文件类型
+      const extension = url.split('.').pop()?.toLowerCase() || '';
+      let fileType: 'pdf' | 'word' | 'excel' | 'ppt' = 'pdf';
+      if (extension === 'doc' || extension === 'docx') {
+        fileType = 'word';
+      } else if (extension === 'xls' || extension === 'xlsx') {
+        fileType = 'excel';
+      } else if (extension === 'ppt' || extension === 'pptx') {
+        fileType = 'ppt';
+      }
+      previewOnlineRef.value?.open(url, fileType);
+    }
+  };
+  
+  const previewOnline = (url: string | undefined, type) => {
+    if (url) {
+      previewOnlineRef.value?.open(url, type);
+    }
+  };
+  //  时间格式化
+  const formatDate = (date) => {
+    if (!date) return '';
+    const dateObj = typeof date === 'object' && date instanceof Date ? date : new Date(date);
+    if (isNaN(dateObj.getTime())) return '';
+    const year = dateObj.getFullYear();
+    const month = String(dateObj.getMonth() + 1).padStart(2, '0');
+    const day = String(dateObj.getDate()).padStart(2, '0');
+    return `${year}-${month}-${day}`; // 输出:****-**-**
+  };
+  //转部门格式 “2,3“ -> [2,3]
+  const parseDeptIds = (ids: string | string[] | number[] | undefined | null): number[] => {
+    if (!ids) {
+      return [];
+    }
+
+    if (Array.isArray(ids)) {
+      return ids.map((v: any) => Number(v)).filter((id) => !isNaN(id));
+    }
+
+    return String(ids)
+      .split(',')
+      .map(Number)
+      .filter((id) => !isNaN(id));
+  };
+
+  const formRef = ref<FormInstance | null>(null);
+
+  const handleValidate = async (): Promise<boolean> => {
+    if (!formRef.value) return false;
+    try {
+      await formRef.value.validate();
+      return true;
+    } catch {
+      return false;
+    }
+  };
+
+  const getDetail = async () => {
+    if (!currentId.value) return;
+    if(planId.value){
+        const res = await queryWorkPlanDepartmentDetail(currentId.value);
+      if (res) {
+        // 映射接口字段到表单字段
+        const cooperateDeptIdsArray = parseDeptIds(res.cooperateDeptIds);
+        const responsibleDeptIdsArray = parseDeptIds(res.responsibleDeptIds);
+        Object.assign(form, {
+          ...res,
+          cooperateDeptIds: cooperateDeptIdsArray,
+          responsibleDeptIds: responsibleDeptIdsArray,
+          fileUrl: JSON.parse(res.fileUrl || '[]'),
+        });
+      }
+
+    } else {
+      const res = await queryWorkPlanDetail(currentId.value);
+      if (res) {
+        // 映射接口字段到表单字段
+        const cooperateDeptIdsArray = parseDeptIds(res.cooperateDeptIds);
+        const responsibleDeptIdsArray = parseDeptIds(res.responsibleDeptIds);
+        Object.assign(form, {
+          ...res,
+          cooperateDeptIds: cooperateDeptIdsArray,
+          responsibleDeptIds: responsibleDeptIdsArray,
+          fileUrl: JSON.parse(res.fileUrl || '[]'),
+        });
+      }
+    }
+    
+  };
+
+  const handleSubmit = async () => {
+    const res = await handleValidate();
+    if (!res) return;
+    try {
+      if (isViewMode.value && form.fileUrl) {
+        // 处理附件格式
+        form.fileUrl = JSON.parse(form.fileUrl)[0].url;
+      }
+      const basePayload = {
+        workContent: form.workContent,
+        categoryName: form.categoryName,
+        trainingPlanName: form.trainingPlanName,
+        executGroupIds: '',
+        plannedComplateTime: formatDate(form.plannedComplateTime),
+        cooperateDeptIds: form.cooperateDeptIds.toString(),
+        responsibleDeptIds: form.responsibleDeptIds.toString(),
+        fileUrl: form.fileUrl,
+      };
+      // console.log(basePayload, 'basePayload')
+      if (isCreateMode.value) {
+        await saveWorkPlan(basePayload);
+        ElMessage.success('创建成功');
+      } else if ((isEditMode.value && currentId.value) || (isViewMode && currentId.value)) {
+        await updateWorkPlan({
+          id: currentId.value,
+          ...basePayload,
+        });
+        ElMessage.success('保存成功');
+      }
+
+      router.back();
+    } catch (e) {
+      console.error('保存物品库存失败:', e);
+      ElMessage.error('保存失败,请重试');
+    }
+  };
+
+  onMounted(() => {
+    loadDeptTreeData();
+    // cloneRuleFormData();
+    // beforeRouteLeave();
+    if (isEditMode.value || isViewMode.value || isReviewMode.value) {
+      getDetail();
+    }
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+</style>

+ 5 - 2
src/views/production-safety/productionSafetySystem/safetySystemConstructionWorkPlanManagement/configs/tables.ts

@@ -2,7 +2,7 @@
  * @Author: liuJie
  * @Date: 2026-01-28 11:03:32
  * @LastEditors: liuJie
- * @LastEditTime: 2026-04-08 16:35:52
+ * @LastEditTime: 2026-04-09 10:15:53
  * @Describe: file describe
  */
 import type { TableColumnProps } from '@/types/basic-table';
@@ -19,6 +19,7 @@ export const WORK_PLAN_STATUS_OPTIONS = [
   { label: '全部', value: '' },
   { label: '未下发', value: 1 },
   { label: '待反馈', value: 2 },
+  { label: '待审核', value: 5 },
   { label: '已完成', value: 3 },
   { label: '已作废', value: 4 },
 ];
@@ -27,6 +28,7 @@ export const WORK_PLAN_STATUS_OPTIONS = [
 export const VIEW_SENDER_WORK_PLAN_STATUS_OPTIONS = [
   { label: '全部', value: '' },
   { label: '待反馈', value: 2 },
+  { label: '待审核', value: 5 },
   { label: '已完成', value: 3 },
   { label: '已作废', value: 4 },
 ];
@@ -37,6 +39,7 @@ export const WORK_PLAN_STATUS_LABEL: Record<string, string> = {
   '2': '待反馈',
   '3': '已完成',
   '4': '已作废',
+  '5': '待审核'
 };
 // 安全体系建设工作规划管理(管理员列表)
 export const WORK_PLAN_TABLE_COLUMNS: TableColumnProps[] = [
@@ -115,7 +118,7 @@ export const WORK_PLAN_TABLE_COLUMNS: TableColumnProps[] = [
     prop: 'action',
     slot: 'action',
     fixed: 'right',
-    width: '280px',
+    width: '380px',
     align: 'left',
   },
 ];

+ 15 - 1
src/views/production-safety/productionSafetySystem/safetySystemConstructionWorkPlanManagement/safetySystemConstructionWorkPlanManagement.vue

@@ -92,7 +92,7 @@
             </template>
             <template #action="scope">
               <div class="action-container--div" style="justify-content: left">
-                <!-- 1-未下发、2-待反馈、3-已完成 4- 已作废 -->
+                <!-- 1-未下发、2-待反馈、3-已完成 4- 已作废 5-待审核 -->
                 <!-- 未下发(1):编辑、删除、下发 -->
                 <template v-if="Number(scope.row.status) === 1">
                   <ActionButton text="查看" @click="handleView(scope.row.id)" />
@@ -129,7 +129,12 @@
                   />
                   <ActionButton text="查看发送对象" @click="handleViewRecipients(scope.row.id)" />
                 </template>
+                <!-- 复制 -->
                 <ActionButton text="复制" @click="handleCopyData(scope.row.id)" />
+                <!-- 待审核(5)-->
+                <template v-if="Number(scope.row.status) === 5">
+                  <ActionButton text="审核" @click="handleReview(scope.row.id)" />
+                </template>
               </div>
             </template>
             
@@ -487,6 +492,15 @@
       },
     });
   };
+  const handleReview = (id: number) => {
+    router.push({
+      name: 'SafetySystemConstructionWorkPlanManagementItem',
+      query: {
+        id,
+        operate: 'work-plan-review',
+      },
+    });
+  };
 
   const handleViewInvalid = async (id: number) => {
     try {

+ 9 - 2
src/views/production-safety/productionSafetySystem/safetySystemConstructionWorkPlanManagement/safetySystemConstructionWorkPlanManagementItem.vue

@@ -2,7 +2,7 @@
  * @Author: liuJie
  * @Date: 2026-01-27 16:29:28
  * @LastEditors: liuJie
- * @LastEditTime: 2026-02-08 11:14:00
+ * @LastEditTime: 2026-04-09 14:00:54
  * @Describe: file describe
 -->
 <template>
@@ -11,7 +11,9 @@
       <BreadcrumbBack />
       <span class="breadcrumb-title">{{ headerTitle }}</span>
     </header>
-    <SafetySystemConstructionWorkPlanManagementDetail />
+    <!-- 审核模式 -->
+    <SafetySystemConstructionWorkPlanManagementReview v-if="isReviewMode" />
+    <SafetySystemConstructionWorkPlanManagementDetail v-else />
   </div>
 </template>
 
@@ -20,9 +22,12 @@
   import { useRoute } from 'vue-router';
   import BreadcrumbBack from '@/components/BreadcrumbBack.vue';
   import SafetySystemConstructionWorkPlanManagementDetail from './components/safetySystemConstructionWorkPlanManagementDetail.vue';
+  import SafetySystemConstructionWorkPlanManagementReview from './components/safetySystemConstructionWorkPlanManagementReview.vue';
 
   const route = useRoute();
   const operate = route.query.operate as string;
+  const operate1 = computed(() => (operate ? operate : 'work-plan-create')); // 默认操作为新增
+  const isReviewMode = computed(() => operate1.value === 'work-plan-review');
 
   const headerTitle = computed(() => {
     switch (operate) {
@@ -32,6 +37,8 @@
         return '编辑安全体系建设工作计划';
       case 'work-plan-view':
         return '查看安全体系建设工作计划';
+      case 'work-plan-review':
+        return '审核安全体系建设工作计划';
       default:
         return '未知操作';
     }

+ 16 - 1
src/views/production-safety/productionSafetySystem/safetySystemConstructionWorkPlanManagement/safetySystemConstructionWorkPlanManagementViewSender.vue

@@ -76,7 +76,7 @@
           >
             <template #action="scope">
               <div class="action-container--div" style="justify-content: left">
-                <!-- 1-未下发、2-待反馈、3-已完成 4- 已作废 -->
+                <!-- 1-未下发、2-待反馈、3-已完成 4- 已作废 5-待审核 -->
                 <!-- (2)待反馈-->
                 <template v-if="Number(scope.row.status) === 2">
                   <ActionButton text="查看" @click="handleView(scope.row)" />
@@ -94,6 +94,10 @@
                   />
                   <ActionButton text="查看" @click="handleView(scope.row)" />
                 </template>
+                <!-- 待审核(5)-->
+                <template v-if="Number(scope.row.status) === 5">
+                  <ActionButton text="审核" @click="handleReview(scope.row)" />
+                </template>
               </div>
             </template>
           </BasicTable>
@@ -283,6 +287,17 @@ import BreadcrumbBack from '@/components/BreadcrumbBack.vue';
       },
     });
   };
+   // 审核
+   const handleReview = (row) => {
+    router.push({
+      name: 'SafetySystemConstructionWorkPlanManagementItem',
+      query: {
+        id: row.id,
+        planId: row.planId,
+        operate: 'work-plan-review',
+      },
+    });
+  };
 
   // 作废
   const handleViewInvalid = async (id: number) => {

+ 8 - 0
src/views/production-safety/productionSafetySystem/safetySystemConstructionWorkPlanManagementDept/components/safetySystemConstructionWorkPlanManagementDeptDetail.vue

@@ -1,4 +1,11 @@
 <template>
+    <el-alert
+      type="error"
+      v-if="form.refuseReason"
+      :title="'不通过原因:' + form.refuseReason"
+      show-icon
+      class="detail-reject-alert"
+    />
   <main class="safety-platform-container__main">
     <el-form
       ref="formRef"
@@ -148,6 +155,7 @@
   const classifyNameOptions = ref<Array<{ label: string; value: string }>>([
     { label: '安全综合工作', value: '安全综合工作' },
     { label: '生产安全工作', value: '生产安全工作' },
+    { label: '安全文化活动', value: '安全文化活动' },
   ]);
 
   /** 表单校验规则 */

+ 6 - 2
src/views/production-safety/productionSafetySystem/safetySystemConstructionWorkPlanManagementDept/configs/tables.ts

@@ -2,7 +2,7 @@
  * @Author: liuJie
  * @Date: 2026-01-28 11:03:32
  * @LastEditors: liuJie
- * @LastEditTime: 2026-02-09 14:30:47
+ * @LastEditTime: 2026-04-09 15:09:20
  * @Describe: file describe
  */
 import type { TableColumnProps } from '@/types/basic-table';
@@ -21,6 +21,7 @@ export const WORK_PLAN_STATUS_OPTIONS = [
   { label: '进行中', value: 2 },
   { label: '已完成', value: 3 },
   { label: '已作废', value: 4 },
+  { label: '待审核', value: 5 },
 ];
 
 // 状态标签映射
@@ -29,6 +30,7 @@ export const WORK_PLAN_STATUS_LABEL: Record<string, string> = {
   '2': '进行中',
   '3': '已完成',
   '4': '已作废',
+  '5': '待审核',
 };
 
 export const WORK_PLAN_TABLE_COLUMNS: TableColumnProps[] = [
@@ -55,7 +57,7 @@ export const WORK_PLAN_TABLE_COLUMNS: TableColumnProps[] = [
     label: '分类名称',
     prop: 'categoryName',
     align: 'left',
-    minWidth: '120px',
+    minWidth: '140px',
   },
   {
     label: '培训计划名称',
@@ -69,12 +71,14 @@ export const WORK_PLAN_TABLE_COLUMNS: TableColumnProps[] = [
     prop: 'responsibleDeptNames',
     align: 'left',
     minWidth: '120px',
+    showOverflowTooltip: true,
   },
   {
     label: '配合部门',
     prop: 'cooperateDeptNames',
     align: 'left',
     minWidth: '120px',
+    showOverflowTooltip: true,
   },
   {
     label: '计划完成时间',

+ 1 - 0
src/views/production-safety/productionSafetySystem/safetySystemConstructionWorkPlanManagementDept/safetySystemConstructionWorkPlanManagementDept.vue

@@ -132,6 +132,7 @@
     { label: '全部', value: '全部' },
     { label: '安全综合工作', value: '安全综合工作' },
     { label: '生产安全工作', value: '生产安全工作' },
+    { label: '安全文化活动', value: '安全文化活动' },
   ]);
 
   const tableQuery = reactive<QueryPageRequest<any>>({