Преглед на файлове

Merge branch 'feat/production-safety' into 'dev'

Feat/production safety

See merge request product-group-fe/sfy-safety-group/sfy-safety!378
ai0197(吴云丰) преди 4 седмици
родител
ревизия
1798a09406
променени са 15 файла, в които са добавени 3033 реда и са изтрити 17 реда
  1. 2 0
      src/api/production-safety-system/index.ts
  2. 56 0
      src/router/routers/production-safety-router/hiddenTroubleInvestigationAndGovernance.ts
  3. 0 2
      src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagement/areaCheckPlanManagement.vue
  4. 61 6
      src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagement/components/areaCheckPlanManagementDetail.vue
  5. 2 1
      src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagement/configs/form.ts
  6. 1 1
      src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagement/configs/tables.ts
  7. 550 0
      src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanTask/areaCheckPlanTask.vue
  8. 49 0
      src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanTask/areaCheckPlanTaskItem.vue
  9. 1593 0
      src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanTask/components/areaCheckPlanManagementDetail.vue
  10. 178 0
      src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanTask/components/areaCheckPlanRecordDetail.vue
  11. 346 0
      src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanTask/configs/form.ts
  12. 15 0
      src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanTask/configs/status.ts
  13. 118 0
      src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanTask/configs/tables.ts
  14. 51 0
      src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanTask/configs/types.ts
  15. 11 7
      src/views/production-safety/hiddenTroubleInvestigationAndGovernance/employeeReportHiddenTroubleManagement/components/employeeReportHiddenTroubleManagementDetail.vue

+ 2 - 0
src/api/production-safety-system/index.ts

@@ -925,6 +925,8 @@ export function mapAreaCheckPlanApiRecordToUi(api: AreaCheckPlanManageApiRecord
     hospitalLeaderExecGroupName: api.hospitalLeaderExecGroupName,
     hospitalLeaderPersonName: api.hospitalLeaderPersonName,
     businessWork: api.businessWork,
+    responsibleDeptName: api.responsibleDeptName,
+    responsibleDeptCode: api.responsibleDeptCode,
   };
 }
 

+ 56 - 0
src/router/routers/production-safety-router/hiddenTroubleInvestigationAndGovernance.ts

@@ -267,6 +267,62 @@
           noCache: false,
         },
       },
+      {
+        id: 900409,
+        parentId: 9004,
+        name: 'areaCheckPlanTask',
+        path: 'area-check-plan-task',
+        component: '/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanTask/areaCheckPlanTask',
+        meta: {
+          title: '区域检查计划任务(管理员)',
+          icon: 'OverviewIcon',
+          isRoot: false,
+          hidden: false,
+          noCache: false,
+        },
+      },
+      {
+        id: 90040901,
+        parentId: 900409,
+        name: 'areaCheckPlanTaskItem',
+        path: 'area-check-plan-task-item',
+        component: '/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanTask/areaCheckPlanTaskItem',
+        meta: {
+          title: '区域检查计划任务(管理员)详情',
+          icon: 'OverviewIcon',
+          isRoot: false,
+          hidden: true,
+          noCache: false,
+        },
+      },
+      // {
+      //   id: 900409,
+      //   parentId: 9004,
+      //   name: 'areaCheckPlanTaskDept',
+      //   path: 'area-check-plan-task-dept',
+      //   component: '/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanTaskDept/areaCheckPlanTaskDept',
+      //   meta: {
+      //     title: '区域检查计划任务(部门)',
+      //     icon: 'OverviewIcon',
+      //     isRoot: false,
+      //     hidden: false,
+      //     noCache: false,
+      //   },
+      // },
+      // {
+      //   id: 90040901,
+      //   parentId: 900409,
+      //   name: 'areaCheckPlanTaskDeptItem',
+      //   path: 'area-check-plan-task-dept-item',
+      //   component: '/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanTaskDept/areaCheckPlanTaskDeptItem',
+      //   meta: {
+      //     title: '区域检查计划任务(部门)详情',
+      //     icon: 'OverviewIcon',
+      //     isRoot: false,
+      //     hidden: true,
+      //     noCache: false,
+      //   },
+      // },
     ],
 }];
 

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

@@ -88,10 +88,8 @@
               }}</span>
             </template>
             <template #primaryResponsibleDeptName="scope">
-                <div>
                     <!-- 如果有下发按钮就展示原来的字段primaryResponsibleDeptName,没有下发按钮就展示responsibleDeptName -->
                     {{ Number(scope.row.status) === 0 ? scope.row.primaryResponsibleDeptName: scope.row.responsibleDeptName}}
-                </div>
             </template>
             <template #needSigneeSign="scope">
               <span>{{

+ 61 - 6
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagement/components/areaCheckPlanManagementDetail.vue

@@ -215,7 +215,7 @@
     <BasicForm
       ref="basicFormRef"
       :formData="ruleFormData"
-      :formRules="isViewMode ? undefined : formRules"
+      :formRules="isViewMode ? undefined : dynamicFormRules"
       :formConfig="computedFormConfig"
     >
       <template #primaryResponsibleDept>
@@ -690,6 +690,11 @@
     label: 'deptName',
     emitPath: false,
   };
+  /** 仅主责部门级联多选,与 cascaderDeptProp 同配置并开启 multiple */
+  const primaryResponsibleDeptCascaderProp = {
+    ...cascaderDeptProp,
+    multiple: true,
+  };
   const issueDeptTree = ref<DeptTree[]>([]);
   const issueUserList = ref<Array<{ id: number; realname?: string; username?: string }>>([]);
 
@@ -791,6 +796,31 @@
     }
     return undefined;
   };
+
+  /** 详情回显:主责部门 code/name 均为逗号分隔字符串时,解析为级联多选 id 数组 */
+  const parsePrimaryResponsibleDeptIds = (
+    codeStr: string,
+    nameStr: string,
+    nodes: DeptTree[] | undefined,
+  ): number[] => {
+    const codeTrim = (codeStr ?? '').trim();
+    if (codeTrim) {
+      const fromCode = codeTrim
+        .split(',')
+        .map((s) => Number(s.trim()))
+        .filter((n) => !Number.isNaN(n));
+      if (fromCode.length) return fromCode;
+    }
+    const nameTrim = (nameStr ?? '').trim();
+    if (!nameTrim) return [];
+    const names = nameTrim.split(',').map((s) => s.trim()).filter(Boolean);
+    const ids: number[] = [];
+    for (const name of names) {
+      const id = findDeptIdByName(nodes, name);
+      if (id != null) ids.push(id);
+    }
+    return ids;
+  };
   // 隐患类别数据
   const dangerTypeList = ref<any[]>([])
   const dangerTypeData = async ()=> {
@@ -813,13 +843,24 @@
     }
   }
   const setDeptNameFromCascader = (refVal: any, nameKey: 'primaryResponsibleDeptName' | 'safetyEmergencyDeptName' | 'hospitalLeaderDeptName') => {
+    if (nameKey === 'primaryResponsibleDeptName') {
+      const nodes = refVal?.getCheckedNodes?.() ?? [];
+      if (!nodes.length) {
+        ruleFormData.primaryResponsibleDeptName = '';
+        ruleFormData.primaryResponsibleDeptCode = '';
+        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(',');
+      return;
+    }
     const nodes = refVal?.getCheckedNodes?.();
     const label = nodes?.[0]?.label ?? '';
-    const value = nodes?.[0]?.value;
     ruleFormData[nameKey] = label;
-    if (nameKey === 'primaryResponsibleDeptName') {
-      ruleFormData.primaryResponsibleDeptCode = value != null ? String(value) : '';
-    }
   };
 
   const onPrimaryResponsibleDeptChange = () => {
@@ -922,6 +963,16 @@
     });
   });
 
+  /** 新增和下发阶段,院领导部门改为非必填 */
+  const isIssueStage = computed(() => Number((viewDetailData.value?.status as number | undefined) ?? -1) === 0);
+  const dynamicFormRules = computed(() => {
+    const rules = cloneDeep(formRules) as Record<string, unknown>;
+    if (isCreateMode.value || isIssueStage.value) {
+      delete rules.hospitalLeaderDeptName;
+    }
+    return rules;
+  });
+
   const basicFormRef = ref<InstanceType<typeof BasicForm>>();
 
   // 查看页:详情数据
@@ -1289,7 +1340,11 @@
       ruleFormData.checkVenue = detail.checkVenue ?? '';
       ruleFormData.primaryResponsibleDeptName = detail.primaryResponsibleDeptName ?? '';
       ruleFormData.primaryResponsibleDeptCode = detail.primaryResponsibleDeptCode ?? '';
-      ruleFormData.primaryResponsibleDeptId = findDeptIdByName(deptTree.value, ruleFormData.primaryResponsibleDeptName as string) ?? null;
+      ruleFormData.primaryResponsibleDeptId = parsePrimaryResponsibleDeptIds(
+        String(detail.primaryResponsibleDeptCode ?? ''),
+        String(detail.primaryResponsibleDeptName ?? ''),
+        deptTree.value,
+      );
       ruleFormData.selfCheckFrequency = detail.selfCheckFrequency ?? '';
       ruleFormData.safetyEmergencyDeptName = detail.safetyEmergencyDeptName ?? '';
       ruleFormData.safetyEmergencyDeptId = findDeptIdByName(deptTree.value, ruleFormData.safetyEmergencyDeptName as string) ?? null;

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

@@ -233,7 +233,8 @@ export const AREA_CHECK_PLAN_FORM_DATA: Record<string, unknown> = {
   planName: '',
   venueCategoryName: '',
   checkVenue: '',
-  primaryResponsibleDeptId: null as number | null,
+  /** 主责部门多选:级联绑定 id 数组;提交给后端用 primaryResponsibleDeptName / primaryResponsibleDeptCode(逗号分隔) */
+  primaryResponsibleDeptId: [] as number[],
   primaryResponsibleDeptName: '',
   primaryResponsibleDeptCode: '',
   selfCheckFrequency: '',

+ 1 - 1
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagement/configs/tables.ts

@@ -43,7 +43,7 @@ export const AREA_CHECK_PLAN_TABLE_COLUMNS: TableColumnProps[] = [
     prop: 'primaryResponsibleDeptName',
     slot: 'primaryResponsibleDeptName',
     align: 'left',
-    minWidth: '140px',
+    width: '140px',
     showOverflowTooltip: true,
   },
   {

+ 550 - 0
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanTask/areaCheckPlanTask.vue

@@ -0,0 +1,550 @@
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <div class="breadcrumb-title">区域检查计划任务(管理员)</div>
+    </header>
+    <main class="safety-platform-container__main">
+      <div class="search-table-container">
+        <header>
+          <div style="position: relative">
+            <el-button plain class="search-table-container--button" @click="handleImport"> 导入 </el-button>
+          </div>
+
+          <div class="act-search">
+            <section class="select-box">
+              <div class="select-box--item">
+                <span>检查场所/检查人员:</span>
+                <el-input
+                  v-model="tableQuery.queryParam.keyword"
+                  placeholder="搜索检查场所或计划名称"
+                  class="act-search-input"
+                  style="min-width: 200px;"
+                  clearable
+                />
+              </div>
+              <div class="select-box--item">
+                <span>状态:</span>
+                <el-select v-model="tableQuery.queryParam.status" placeholder="请选择状态" clearable>
+                  <el-option
+                    v-for="item in AREA_CHECK_PLAN_STATUS_OPTIONS"
+                    :key="String(item.value)"
+                    :label="item.label"
+                    :value="item.value"
+                  />
+                </el-select>
+              </div>
+              <div class="select-box--item">
+                <span>日期范围:</span>
+                <el-date-picker
+                  v-model="dateRange"
+                  type="daterange"
+                  range-separator="-"
+                  start-placeholder="开始日期"
+                  end-placeholder="结束日期"
+                  value-format="YYYY-MM-DD"
+                  format="YYYY-MM-DD"
+                />
+              </div>
+            </section>
+            <section class="search-btn">
+              <el-button type="primary" @click="handleSearch">查询</el-button>
+              <el-button @click="handleReset">重置</el-button>
+              <el-button plain @click="handleDownload"> 导出 </el-button>
+            </section>
+          </div>
+        </header>
+
+        <div class="batch-table">
+          <BasicTable
+            ref="basicTableRef"
+            :tableData="tableData"
+            :tableConfig="tableConfig"
+            @update:pageSize="handleSizeChange"
+            @update:pageNumber="handleCurrentChange"
+          >
+            <template #status="scope">
+              <span>{{ AREA_CHECK_PLAN_STATUS_LABEL[String(scope.row.status)] ?? '-' }}</span>
+            </template>
+            <template #needOverallDesc="scope">
+              <span>{{
+               scope.row.status === 0 ? '-' : scope.row.needOverallDesc === true ? '是' : scope.row.needOverallDesc === false ? '否' : '-'
+              }}</span>
+            </template>
+            <template #primaryResponsibleDeptName="scope">
+                    <!-- 如果有下发按钮就展示原来的字段primaryResponsibleDeptName,没有下发按钮就展示responsibleDeptName -->
+                    {{ Number(scope.row.status) === 0 ? scope.row.primaryResponsibleDeptName: scope.row.responsibleDeptName}}
+            </template>
+            <template #needSigneeSign="scope">
+              <span>{{
+                scope.row.status === 0 ? '-' : scope.row.needSigneeSign === true ? '是' : scope.row.needSigneeSign === false ? '否' : '-'
+              }}</span>
+            </template>
+            <template #planStartTime="scope">
+              <span>{{ scope.row.planStartTime ?? '-' }}</span>
+            </template>
+            <template #planEndTime="scope">
+              <span>{{ scope.row.planEndTime ?? '-' }}</span>
+            </template>
+            <template #action="scope">
+              <div class="action-container--div" style="justify-content: left">
+                <template v-if="Number(scope.row.status) === 0">
+                  <ActionButton text="查看" @click="handleView(scope.row.id)" />
+                </template>
+              </div>
+            </template>
+          </BasicTable>
+        </div>
+      </div>
+    </main>
+
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { onMounted, reactive, ref } from 'vue';
+  import { ElMessage } from 'element-plus';
+  import BasicTable from '@/components/BasicTable.vue';
+  import useTableConfig from '@/hooks/useTableConfigHook';
+  import ActionButton from '@/components/ActionButton.vue';
+  import { Plus } from '@element-plus/icons-vue';
+  import {
+    TABLE_OPTIONS,
+    AREA_CHECK_PLAN_TABLE_COLUMNS,
+    AREA_CHECK_PLAN_STATUS_OPTIONS,
+    AREA_CHECK_PLAN_STATUS_LABEL,
+  } from './configs/tables';
+  import { useRouter } from 'vue-router';
+  import type { QueryPageRequest } from '@/types/basic-query';
+  import type { AreaCheckPlanQuery, AreaCheckPlanRecord } from './configs/types';
+  import {
+    queryAreaCheckPlanManagePage,
+    deleteAreaCheckPlanManage,
+    sendAreaCheckPlanToDep,
+    cancelAreaCheckPlanManage,
+    queryAreaCheckPlanManageDetail,
+    mapAreaCheckPlanApiRecordToUi,
+    exportAreaCheckPlanAdministration,
+  } from '@/api/production-safety-system';
+  import { getAllDepartments } from '@/api/auth/dept';
+  import type { DeptTree } from '@/types/dept/type';
+  import { queryUserGroupPage } from '@/api/system/person-group';
+  import type { PersonGroupListItem } from '@/types/person-group/type';
+  import BatchImport from '@/components/batch-import/BatchImport.vue';
+  import { useGlobSetting } from '@/hooks/setting';
+  import urlJoin from 'url-join';
+  import { downloadByData } from '@/utils/file/download';
+  const router = useRouter();
+
+  const basicTableRef = ref<InstanceType<typeof BasicTable>>();
+  const { tableConfig, pagination } = useTableConfig(AREA_CHECK_PLAN_TABLE_COLUMNS, TABLE_OPTIONS);
+  const tableData = ref<AreaCheckPlanRecord[]>([]);
+  const dateRange = ref<[string, string] | null>(null);
+
+  // 场所所属类别选项(示例数据,本页面不调用接口)
+  const venueCategoryOptions = ref<Array<{ label: string; value: string }>>([]);
+
+  // 下发弹窗
+  const showIssueDialog = ref(false);
+  const currentIssuePlanId = ref<number | null>(null);
+  const issueFormRef = ref();
+  const issueResponsibleDeptCascaderRef = ref();
+  const issueForm = reactive({
+    responsibleDeptIds: [] as number[],
+    responsibleDeptPersonnelGroupCode: undefined as string | undefined,
+    safetyEmergencyDeptId: undefined as number | undefined,
+    safetyEmergencyGroupId: undefined as number | undefined,
+    hospitalLeaderDeptId: undefined as number | undefined,
+    hospitalLeaderGroupId: undefined as number | undefined,
+    planStartTime: '',
+    planEndTime: '',
+    needOverallDesc: false,
+    needSigneeSign: false,
+  });
+  const issueRules = {
+    responsibleDeptIds: [{ required: true, message: '请选择责任部门', trigger: 'change', type: 'array', min: 1 }],
+    responsibleDeptPersonnelGroupCode: [{ required: true, message: '请选择责任部门人员分组', trigger: 'change' }],
+    safetyEmergencyDeptId: [{ required: true, message: '请选择安全应急部门', trigger: 'change' }],
+    safetyEmergencyGroupId: [{ required: true, message: '请选择安全应急部人员分组', trigger: 'change' }],
+    hospitalLeaderDeptId: [{ required: true, message: '请选择院领导部门', trigger: 'change' }],
+    hospitalLeaderGroupId: [{ required: true, message: '请选择院领导人员分组', trigger: 'change' }],
+    planStartTime: [{ required: true, message: '请选择计划开始日期', trigger: 'change' }],
+    planEndTime: [{ required: true, message: '请选择计划结束时间', trigger: 'change' }],
+    needOverallDesc: [{ required: true, message: '请选择是否需要整体检查情况描述', trigger: 'change' }],
+    needSigneeSign: [{ required: true, message: '请选择是否需要被检查人签字', trigger: 'change' }],
+  };
+  // 下发弹窗:责任部门/安全应急部门/院领导部门复用新增区域检查计划的主责部门下拉(getAllDepartments)
+  const issueDeptTree = ref<DeptTree[]>([]);
+  const issueDeptTreeTow = ref<DeptTree[]>([]);
+  const cascaderDeptProp = {
+    checkStrictly: true,
+    expandTrigger: 'hover' as const,
+    value: 'id',
+    label: 'deptName',
+    emitPath: false,
+  };
+  const cascaderDeptPropMultiple = {
+    ...cascaderDeptProp,
+    multiple: true,
+  };
+  // 责任部门人员分组/安全应急部人员分组/院领导人员分组复用 queryUserGroupPage 查询用户分组接口
+  const userGroupOptions = ref<PersonGroupListItem[]>([]);
+
+  /** 下发弹窗打开时加载部门树(与新增区域检查计划主责部门一致) */
+  const loadIssueDeptTree = async () => {
+    try {
+      const res = await getAllDepartments();
+      issueDeptTree.value = res?.[0]?.children ?? [];
+      issueDeptTreeTow.value = res?.[0]?.children.map(item => ({
+        ...item,
+        children: []
+      }));
+    } catch (e) {
+      console.error('获取部门树失败:', e);
+      issueDeptTree.value = [];
+    }
+  };
+
+  /** 下发弹窗打开时加载用户分组列表 */
+  const loadUserGroupOptions = async () => {
+    try {
+      const res = await queryUserGroupPage({
+        pageNumber: 1,
+        pageSize: 9999,
+        queryParam: '',
+      });
+      userGroupOptions.value = res?.records ?? [];
+    } catch (e) {
+      console.error('获取用户分组列表失败:', e);
+      userGroupOptions.value = [];
+    }
+  };
+
+  const onIssueDialogOpen = async () => {
+    await Promise.all([loadIssueDeptTree(), loadUserGroupOptions()]);
+
+    if (!currentIssuePlanId.value) return;
+
+    try {
+      const raw = await queryAreaCheckPlanManageDetail(currentIssuePlanId.value);
+      const detail = mapAreaCheckPlanApiRecordToUi(raw);
+
+      const responsibleDeptIdsByCode = parseNumberList(
+        detail.primaryResponsibleDeptCode ?? (detail as Record<string, unknown>).responsibleDeptCode,
+      );
+
+      issueForm.responsibleDeptIds = responsibleDeptIdsByCode.length
+        ? responsibleDeptIdsByCode
+        : (detail.primaryResponsibleDeptName || '')
+            .split(',')
+            .map((name) => findDeptIdByName(issueDeptTree.value, name.trim()))
+            .filter((id): id is number => id != null);
+
+      const safetyEmergencyDeptIdByCode = parseNumber(detail.safetyEmergencyDeptCode);
+      issueForm.safetyEmergencyDeptId =
+        safetyEmergencyDeptIdByCode ?? findDeptIdByName(issueDeptTree.value, detail.safetyEmergencyDeptName);
+
+      const hospitalLeaderDeptIdByCode = parseNumber(detail.hospitalLeaderDeptCode);
+      issueForm.hospitalLeaderDeptId =
+        hospitalLeaderDeptIdByCode ?? findDeptIdByName(issueDeptTree.value, detail.hospitalLeaderDeptName);
+    } catch (e) {
+      console.error('加载下发默认数据失败:', e);
+    }
+  };
+
+  const findDeptNameById = (nodes: DeptTree[] | undefined, id: number | undefined): string | undefined => {
+    if (!nodes?.length || id == null) return undefined;
+    for (const n of nodes) {
+      if (n.id === id) return n.deptName;
+      const found = findDeptNameById(n.children, id);
+      if (found) return found;
+    }
+    return undefined;
+  };
+
+  const findDeptIdByName = (nodes: DeptTree[] | undefined, name: string | undefined): number | undefined => {
+    if (!nodes?.length || !name) return undefined;
+    for (const n of nodes) {
+      if (n.deptName === name) return n.id ?? undefined;
+      const found = findDeptIdByName(n.children, name);
+      if (found != null) return found;
+    }
+    return undefined;
+  };
+
+  const parseNumber = (val: unknown): number | undefined => {
+    if (val == null || val === '') return undefined;
+    const n = Number(val);
+    return Number.isFinite(n) ? n : undefined;
+  };
+
+  const parseNumberList = (val: unknown): number[] => {
+    if (val == null || val === '') return [];
+    return String(val)
+      .split(',')
+      .map((s) => parseNumber(s.trim()))
+      .filter((n): n is number => n != null);
+  };
+
+  const findDeptNamesByIds = (nodes: DeptTree[] | undefined, ids: number[] | undefined): string[] => {
+    const list = (ids ?? []).filter((v) => v != null);
+    if (!list.length) return [];
+    return list
+      .map((id) => findDeptNameById(nodes, id))
+      .filter((name): name is string => !!name && String(name).trim().length > 0);
+  };
+
+  const findGroupNameById = (id: number | undefined): string | undefined => {
+    if (id == null) return undefined;
+    return userGroupOptions.value.find((g) => g.id === id)?.name;
+  };
+
+  const resetIssueForm = () => {
+    issueForm.responsibleDeptIds = [];
+    issueForm.responsibleDeptPersonnelGroupCode = undefined;
+    issueForm.safetyEmergencyDeptId = undefined;
+    issueForm.safetyEmergencyGroupId = undefined;
+    issueForm.hospitalLeaderDeptId = undefined;
+    issueForm.hospitalLeaderGroupId = undefined;
+    issueForm.planStartTime = '';
+    issueForm.planEndTime = '';
+    issueForm.needOverallDesc = false;
+    issueForm.needSigneeSign = false;
+    issueFormRef.value?.resetFields?.();
+  };
+
+  const tableQuery = reactive<QueryPageRequest<AreaCheckPlanQuery>>({
+    pageNumber: pagination.pageNumber,
+    pageSize: pagination.pageSize,
+    queryParam: {
+      keyword: '',
+      status: '' as AreaCheckPlanQuery['status'],
+      venueCategory: '',
+      planStartTime: '',
+      planEndTime: '',
+    },
+  });
+
+  const handleSizeChange = (value: number) => {
+    pagination.pageSize = value;
+    tableQuery.pageSize = value;
+    getTableData();
+  };
+
+  const handleCurrentChange = (value: number) => {
+    pagination.pageNumber = value;
+    tableQuery.pageNumber = value;
+    getTableData();
+  };
+
+  async function getTableData() {
+    tableConfig.loading = true;
+    try {
+      const res = await queryAreaCheckPlanManagePage({
+        pageNumber: tableQuery.pageNumber,
+        pageSize: tableQuery.pageSize,
+        queryParam: tableQuery.queryParam,
+      });
+      const raw = (res as { data?: { records?: unknown[]; totalRow?: number } })?.data ?? res;
+      const list = raw?.records ?? [];
+      tableData.value = list.map((r: unknown) => mapAreaCheckPlanApiRecordToUi(r));
+      pagination.total = raw?.totalRow ?? 0;
+    } catch (e) {
+      console.error('查询区域检查计划列表失败:', e);
+      tableData.value = [];
+      pagination.total = 0;
+    } finally {
+      tableConfig.loading = false;
+    }
+  }
+
+  const handleSearch = () => {
+    if (dateRange.value && dateRange.value.length === 2) {
+      tableQuery.queryParam.planStartTime = dateRange.value[0];
+      tableQuery.queryParam.planEndTime = dateRange.value[1];
+    } else {
+      tableQuery.queryParam.planStartTime = '';
+      tableQuery.queryParam.planEndTime = '';
+    }
+    pagination.pageNumber = 1;
+    tableQuery.pageNumber = 1;
+    getTableData();
+  };
+
+  const handleReset = () => {
+    tableQuery.queryParam.keyword = '';
+    tableQuery.queryParam.status = '';
+    tableQuery.queryParam.venueCategory = '';
+    tableQuery.queryParam.planStartTime = '';
+    tableQuery.queryParam.planEndTime = '';
+    dateRange.value = null;
+    handleSearch();
+  };
+
+
+
+  const handleEdit = (id: number) => {
+    router.push({
+      name: 'areaCheckPlanManagementItem',
+      query: { id, operate: 'area-check-plan-edit' },
+    });
+  };
+
+  const handleView = (id: number) => {
+    router.push({
+      name: 'areaCheckPlanManagementItem',
+      query: { id, operate: 'area-check-plan-view' },
+    });
+  };
+
+  const handleDelete = async (id: number) => {
+    try {
+      await deleteAreaCheckPlanManage(id);
+      ElMessage.success('删除成功');
+      getTableData();
+    } catch (e) {
+      console.error('删除失败:', e);
+    }
+  };
+
+  const handleIssue = (id: number) => {
+    currentIssuePlanId.value = id;
+    resetIssueForm();
+    showIssueDialog.value = true;
+  };
+
+  const handleIssueSave = async () => {
+    if (!issueFormRef.value || !currentIssuePlanId.value) return;
+    const valid = await issueFormRef.value.validate().catch(() => false);
+    if (!valid) return;
+    if (
+      issueForm.planStartTime &&
+      issueForm.planEndTime &&
+      new Date(issueForm.planStartTime) > new Date(issueForm.planEndTime)
+    ) {
+      ElMessage.error('计划开始日期不能大于计划结束日期');
+      return;
+    }
+    try {
+      // 仅提交:弹窗页面字段 + id(下拉同时提交 name + value)
+      const responsibleDeptIds = (issueForm.responsibleDeptIds ?? []).filter((v) => v != null);
+      const responsibleDeptNames = findDeptNamesByIds(issueDeptTree.value, responsibleDeptIds);
+      const responsibleDeptName = responsibleDeptNames.length ? responsibleDeptNames.join(',') : undefined;
+      const responsibleDeptCode = responsibleDeptIds.length ? responsibleDeptIds.join(',') : undefined;
+
+      const responsibleDeptPersonnelGroupName = (() => {
+        const code = issueForm.responsibleDeptPersonnelGroupCode;
+        if (code == null || code === '') return undefined;
+        const byId = userGroupOptions.value.find((g) => String(g.id) === code);
+        return byId?.name ?? undefined;
+      })();
+
+      const safetyEmergencyDeptName = findDeptNameById(issueDeptTree.value, issueForm.safetyEmergencyDeptId);
+      const hospitalLeaderDeptName = findDeptNameById(issueDeptTree.value, issueForm.hospitalLeaderDeptId);
+
+      const payload = {
+        id: currentIssuePlanId.value,
+        responsibleDeptName,
+        responsibleDeptCode,
+        responsibleDeptPersonnelGroupName,
+        responsibleDeptPersonnelGroupCode: issueForm.responsibleDeptPersonnelGroupCode,
+
+        // 安全应急部门
+        safetyEmergencyDeptName,
+        SafetyEmergencyDeptCode: issueForm.safetyEmergencyDeptId,
+        safetyEmergencyExecutorGroupName: findGroupNameById(issueForm.safetyEmergencyGroupId),
+        safetyEmergencyExecGroupCode:
+          issueForm.safetyEmergencyGroupId != null ? String(issueForm.safetyEmergencyGroupId) : undefined,
+
+        // 院领导部门
+        hospitalLeaderDeptName,
+        hospitalLeaderDeptCode: issueForm.hospitalLeaderDeptId,
+        hospitalLeaderExecutorGroupName: findGroupNameById(issueForm.hospitalLeaderGroupId),
+        hospitalLeaderExecGroupCode:
+          issueForm.hospitalLeaderGroupId != null ? String(issueForm.hospitalLeaderGroupId) : undefined,
+
+        // 计划时间与开关项
+        planStartTime: issueForm.planStartTime,
+        planEndTime: issueForm.planEndTime,
+        needOverallDesc: issueForm.needOverallDesc,
+        needSigneeSign: issueForm.needSigneeSign,
+      } as AreaCheckPlanRecord & { id: number } & Record<string, unknown>;
+
+      await sendAreaCheckPlanToDep(payload);
+      ElMessage.success('下发成功');
+      showIssueDialog.value = false;
+      getTableData();
+    } catch (e) {
+      ElMessage.error(e.message);
+    }
+  };
+
+  const handleCancel = async (id: number) => {
+    try {
+      await cancelAreaCheckPlanManage(id);
+      ElMessage.success('作废成功');
+      getTableData();
+    } catch (e) {
+      console.error('作废失败:', e);
+    }
+  };
+
+
+
+  const handleDownload = async () => {
+    try {
+      const response = await exportAreaCheckPlanAdministration(tableQuery.queryParam);
+      if (response) {
+        const fileName = `区域检查计划管理_${new Date().toISOString().split('T')[0]}.xlsx`;
+        downloadByData(response, fileName);
+        ElMessage.success('导出成功');
+      }
+    } catch (e) {
+      console.error('导出院级文件失败:', e);
+      ElMessage.error('导出失败,请重试');
+    }
+  };
+
+  onMounted(() => {
+    venueCategoryOptions.value = [
+      { label: '各级风险点', value: '各级风险点' },
+      { label: '关键业务活动', value: '关键业务活动' },
+      { label: '日常安全', value: '日常安全' },
+      { label: '各级危险点', value: '各级危险点' },
+      { label: '试验室及试验过程', value: '试验室及试验过程' },
+      {
+        label: '办公区域(含地下车库、图书馆、档案库房、仓库等)',
+        value: '办公区域(含地下车库、图书馆、档案库房、仓库等)',
+      },
+      { label: '老旧厂房', value: '老旧厂房' },
+      { label: '施工现场', value: '施工现场' },
+      { label: '职工食堂', value: '职工食堂' },
+      { label: '职工宿舍', value: '职工宿舍' },
+      { label: '体育活动场所', value: '体育活动场所' },
+      { label: '托育园', value: '托育园' },
+      { label: '租、出借房屋', value: '租、出借房屋' },
+      { label: '院内经营服务场所', value: '院内经营服务场所' },
+      { label: '垃圾房', value: '垃圾房' },
+      { label: '院内交通', value: '院内交通' },
+      { label: '消防设施设备', value: '消防设施设备' },
+      { label: '特种设备', value: '特种设备' },
+      { label: '防雷设施', value: '防雷设施' },
+      { label: '供、配电设施设备(含弱电)', value: '供、配电设施设备(含弱电)' },
+      { label: '公务车辆', value: '公务车辆' },
+      { label: '燃气管道设施(含报警装置)', value: '燃气管道设施(含报警装置)' },
+      { label: '建筑物外墙标识物、装饰物', value: '建筑物外墙标识物、装饰物' },
+      { label: 'UPS电源', value: 'UPS电源' },
+      { label: '危险化学品', value: '危险化学品' },
+      { label: '设施设备应急操作流程', value: '设施设备应急操作流程' },
+      { label: '堆场、物资库房', value: '堆场、物资库房' },
+      { label: '室内外停车场', value: '室内外停车场' },
+    ];
+    getTableData();
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-main-layout.scss' as *;
+  @use '@/styles/basic-table-action.scss' as *;
+  @use '@/views/traffic/violation/style/act-search-table.scss' as *;
+</style>

+ 49 - 0
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanTask/areaCheckPlanTaskItem.vue

@@ -0,0 +1,49 @@
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <BreadcrumbBack />
+      <span class="breadcrumb-title">{{ headerTitle }}</span>
+    </header>
+    <AreaCheckPlanManagementDetail v-if="!isRecordDetail" />
+    <AreaCheckPlanRecordDetail v-else />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { computed } from 'vue';
+  import { useRoute } from 'vue-router';
+  import BreadcrumbBack from '@/components/BreadcrumbBack.vue';
+  import AreaCheckPlanManagementDetail from './components/areaCheckPlanManagementDetail.vue';
+  import AreaCheckPlanRecordDetail from './components/areaCheckPlanRecordDetail.vue';
+
+  const route = useRoute();
+  const operate = computed(() => (route.query.operate as string) || '');
+
+  const isRecordDetail = computed(() => operate.value === 'area-check-plan-record-view');
+
+  const headerTitle = computed(() => {
+    if (isRecordDetail.value) return '检查记录查看';
+    switch (operate.value) {
+      case 'area-check-plan-create':
+        return '新增区域检查计划';
+      case 'area-check-plan-edit':
+        return '编辑区域检查计划';
+      case 'area-check-plan-view':
+        return '查看区域检查计划';
+      default:
+        return '未知操作';
+    }
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-main-layout.scss' as *;
+
+  .safety-platform-container__header {
+    flex-direction: row !important;
+    justify-content: flex-start !important;
+    gap: 8px !important;
+  }
+</style>
+

Файловите разлики са ограничени, защото са твърде много
+ 1593 - 0
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanTask/components/areaCheckPlanManagementDetail.vue


+ 178 - 0
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanTask/components/areaCheckPlanRecordDetail.vue

@@ -0,0 +1,178 @@
+<template>
+  <div class="area-check-plan-record-detail">
+    <main class="safety-platform-container__main">
+      <el-form :model="formData" label-width="auto" class="check-record-form">
+        <el-form-item label="被检查单位:">
+          <el-input v-model="formData.inspectedUnit" disabled />
+        </el-form-item>
+        <el-form-item label="检查人员:">
+          <el-input v-model="formData.inspector" disabled />
+        </el-form-item>
+        <el-form-item label="检查时间:">
+          <el-input v-model="formData.checkTime" disabled />
+        </el-form-item>
+        <el-form-item label="检查地点:">
+          <el-input v-model="formData.checkPlace" disabled />
+        </el-form-item>
+        <el-form-item label="整体检查情况描述:">
+          <el-input
+            v-model="formData.overallDesc"
+            type="textarea"
+            :rows="5"
+            maxlength="300"
+            show-word-limit
+            disabled
+            placeholder="整体检查情况描述(限300字)"
+          />
+        </el-form-item>
+        <el-form-item label="被检查人签字文件:">
+          <div class="upload-files-disabled">
+            <UploadFiles
+              label="上传附件"
+              :file-list="signFileList"
+            />
+          </div>
+        </el-form-item>
+      </el-form>
+
+      <div class="check-items-section">
+        <div class="section-header">
+          <span class="section-title">检查明细</span>
+        </div>
+        <div class="check-items-table">
+          <el-table :data="checkItems" border>
+            <el-table-column type="index" label="编号" width="80" align="center" />
+            <el-table-column prop="checkContent" label="检查内容" min-width="180" />
+            <el-table-column prop="checkStandard" label="检查标准" min-width="180" />
+            <el-table-column prop="checkResult" label="检查结果" min-width="120" align="center" />
+            <el-table-column prop="checkProblem" label="检查发现问题" min-width="200" />
+          </el-table>
+        </div>
+      </div>
+    </main>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { computed, onMounted, ref } from 'vue';
+  import { useRoute } from 'vue-router';
+  import UploadFiles from '@/components/UploadFiles/UploadFiles.vue';
+  import type { FileItem } from '@/components/UploadFiles/types';
+  import { queryAreaCheckRecord } from '@/api/production-safety-system';
+
+  const route = useRoute();
+  const recordId = computed(() => Number(route.query.recordId));
+
+  const formData = ref({
+    inspectedUnit: '',
+    inspector: '',
+    checkTime: '',
+    checkPlace: '',
+    overallDesc: '',
+    signFile: '',
+  });
+
+  /** 将签字文件(逗号分隔URL或文件名)转换为 UploadFiles 使用的 FileItem 列表,复用新增安全考核上传附件文档字段样式 */
+  function convertSignFileToFileItems(attachmentsStr: string): FileItem[] {
+    if (!attachmentsStr || !String(attachmentsStr).trim()) return [];
+    const urls = String(attachmentsStr)
+      .split(',')
+      .map((u) => u.trim())
+      .filter(Boolean);
+    return urls.map((url, index) => {
+      const parts = url.split('/');
+      const fileName = parts[parts.length - 1] || `附件${index + 1}`;
+      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 };
+    });
+  }
+
+  const signFileList = computed<FileItem[]>(() => convertSignFileToFileItems(formData.value.signFile || ''));
+
+  const checkItems = ref<Array<{
+    checkContent?: string;
+    checkStandard?: string;
+    checkResult?: string;
+    checkProblem?: string;
+  }>>([]);
+
+  const loadRecordDetail = async () => {
+    if (!recordId.value) {
+      // 新增检查日志:全部为空数据
+      formData.value = {
+        inspectedUnit: '',
+        inspector: '',
+        checkTime: '',
+        checkPlace: '',
+        overallDesc: '',
+        signFile: '',
+      };
+      checkItems.value = [];
+      return;
+    }
+    try {
+      const res = await queryAreaCheckRecord(recordId.value);
+      const raw = (res as { data?: Record<string, unknown> })?.data ?? res;
+      const r = raw as Record<string, unknown>;
+      formData.value = {
+        inspectedUnit: String(r?.checkedCompanyName ?? r?.checkedCompany ?? ''),
+        inspector: String(r?.checkPersonName ?? r?.checkPerson ?? ''),
+        checkTime: String(r?.checkTime ?? ''),
+        checkPlace: String(r?.checkAddress ?? r?.checkPlace ?? ''),
+        overallDesc: String(r?.overallCheckDesc ?? ''),
+        signFile: String(r?.checkedPersonSign ?? ''),
+      };
+      const records = (r?.areaCheckRecords ?? []) as Array<Record<string, unknown>>;
+      checkItems.value = records.map((r) => ({
+        checkContent: r.checkContent as string,
+        checkStandard: r.checkStandard as string,
+        checkResult: r.checkResult as string,
+        checkProblem: r.checkProblem as string,
+      }));
+    } catch (e) {
+      console.error('获取检查记录详情失败:', e);
+    }
+  };
+
+  onMounted(() => loadRecordDetail());
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-main-layout.scss' as *;
+
+  .check-record-form {
+    max-width: 800px;
+  }
+
+  .check-items-section {
+    margin-top: 16px;
+
+    .section-header {
+      margin-bottom: 8px;
+      .section-title {
+        font-weight: 600;
+      }
+    }
+  }
+
+  .upload-files-disabled {
+    pointer-events: none;
+    opacity: 0.6;
+
+    :deep(.upload-button) {
+      cursor: not-allowed;
+      background-color: #f5f5f5;
+      color: #aaa;
+    }
+
+    :deep(.delete-button) {
+      display: none;
+    }
+  }
+</style>
+

+ 346 - 0
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanTask/configs/form.ts

@@ -0,0 +1,346 @@
+import { FormConfig } from '@/types/basic-form';
+
+/** 检查频次选项:每日/每周/每月/每季度/每半年/每年 */
+export const CHECK_FREQUENCY_OPTIONS = [
+  { label: '每日', value: '每日' },
+  { label: '每周', value: '每周' },
+  { label: '每月', value: '每月' },
+  { label: '每季度', value: '每季度' },
+  { label: '每半年', value: '每半年' },
+  { label: '每年', value: '每年' },
+];
+
+/** 检查单模版类别选项(单选):1-安全管理检查单 2-关键业务活动专项检查单 3-日常安全检查单 */
+export const CHECKLIST_CATEGORY_OPTIONS = [
+  { label: '安全管理检查单', value: '安全管理检查单' },
+  { label: '关键业务活动专项检查单', value: '关键业务活动专项检查单' },
+  { label: '日常安全检查单', value: '日常安全检查单' },
+];
+
+// 检查记录入账隐患台账
+export const HIDDEN_DANGER_FORM_CONFIG: FormConfig[] = [
+//   {
+//     prop: 'typeId',
+//     label: '隐患问题类别:',
+//     component: 'ElSelect',
+//     slot: 'typeId',
+//     componentProps: {
+//       placeholder: '请选择隐患问题类别',
+//       style: { width: '100%' },
+//     },
+//     // selectOptions: DANGER_TYPE_OPTIONS,
+//   },
+//   {
+//     prop: 'dangerProblem',
+//     label: '隐患问题:',
+//     component: 'ElInput',
+//     componentProps: {
+//       placeholder: '请输入隐患问题描述',
+//     },
+//   },
+//   {
+//     prop: 'reasonId',
+//     label: '问题主要原因:',
+//     component: 'ElSelect',
+//     componentProps: {
+//       placeholder: '请选择问题主要原因',
+//       style: { width: '100%' },
+//     },
+//     selectOptions: REASON_OPTIONS,
+//   },
+//   {
+//     prop: 'taskSource',
+//     label: '任务来源:',
+//     component: 'ElInput',
+//     componentProps: {
+//       placeholder: '如:上级检查、院内自查',
+//     },
+//   },
+//   {
+//     prop: 'rectificationRequirement',
+//     label: '整改要求:',
+//     component: 'ElInput',
+//     componentProps: {
+//       placeholder: '请输入整改要求',
+//     },
+//   },
+//   {
+//     prop: 'rectificationDepartmentIds',
+//     label: '整改责任部门:',
+//     slot: 'rectificationDepartmentIds',
+//   }, 
+//   {
+//     prop: 'rectificationResponsibleIds',
+//     label: '整改负责人:',
+//     slot: 'rectificationResponsibleIds',
+//   },
+//   {
+//     prop: 'rectificationDeadline',
+//     label: '整改日期:',
+//     component: 'ElDatePicker',
+//     componentProps: {
+//       type: 'date',
+//       placeholder: '请选择整改日期',
+//       valueFormat: 'YYYY-MM-DD',
+//       style: { width: '100%' },
+//     },
+//   },
+//   {
+//     prop: 'reviewDepartmentId',
+//     label: '复查人员所属部门:',
+//     slot: 'reviewDepartmentId',
+//     componentProps: {
+//       placeholder: '请选择复查人员所属部门',
+//       style: { width: '100%' },
+//     },
+//   },
+//   {
+//     prop: 'reviewPersonId',
+//     label: '复查人员:',
+//     slot: 'reviewPerson',
+//     componentProps: {
+//       placeholder: '请选择复查人员',
+//       style: { width: '100%' },
+//     },
+//   },
+//   {
+//     prop: 'isDrawLessonsPush',
+//     label: '举一反三是否推送:',
+//     slot: 'isDrawLessonsPush',
+//   },
+//   {
+//     prop: 'drawLessonsContent',
+//     slot:'drawLessonsContent',
+//     label: '',
+//     component: 'ElInput',
+//     componentProps: {
+//       placeholder: '如:上级检查、院内自查',
+//       style: { paddingLeft: '136.5px' },
+//     },
+//   },
+//   {
+//     prop: 'drawLessonsDepartmentIds',
+//     label: '', //举一反三责任部门:
+//     slot: 'drawLessonsDepartmentIds',
+//   },
+//   {
+//     prop: 'drawLessonsDeadline',
+//     label: '', //举一反三时限:
+//     slot: 'drawLessonsDeadline',
+//   },
+];
+/** 区域检查计划新增/编辑表单配置(与截图字段顺序一致) */
+export const AREA_CHECK_PLAN_FORM_CONFIG: FormConfig[] = [
+  {
+    prop: 'planName',
+    label: '区域检查计划名称:',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '输入区域检查计划名称',
+    },
+  },
+  {
+    prop: 'venueCategoryName',
+    label: '检查类别:',
+    slot: 'venueCategoryName',
+  },
+  {
+    prop: 'checkVenue',
+    label: '检查场所:',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '输入检查场所',
+    },
+  },
+  {
+    prop: 'primaryResponsibleDeptName',
+    label: '主责部门:',
+    slot: 'primaryResponsibleDept', // 与新增物品领取记录页的部门下拉一致(el-cascader + getAllDepartments)
+    componentProps: {
+      placeholder: '请选择部门',
+    },
+  },
+  {
+    prop: 'selfCheckFrequency',
+    label: '自查频次:',
+    slot: 'selfCheckFrequency',
+  },
+  {
+    prop: 'safetyEmergencyDeptName',
+    label: '安全应急部门:',
+    slot: 'safetyEmergencyDept', // 与新增物品领取记录页的部门下拉一致(el-cascader + getAllDepartments)
+    componentProps: {
+      placeholder: '请选择部门',
+    },
+  },
+  {
+    prop: 'safetyEmergencyCheckFrequency',
+    label: '安全应急部检查频次:',
+    slot: 'safetyEmergencyCheckFrequency',
+  },
+  {
+    prop: 'hospitalLeaderDeptName',
+    label: '院领导部门:',
+    slot: 'hospitalLeaderDept', // 与新增物品领取记录页的部门下拉一致(el-cascader + getAllDepartments)
+    componentProps: {
+      placeholder: '请选择部门',
+    },
+  },
+  {
+    prop: 'hospitalLeaderCheckFrequency',
+    label: '院领导检查频次:',
+    slot: 'hospitalLeaderCheckFrequency',
+  },
+  {
+    prop: 'categoryCode',
+    label: '检查单模版类别:',
+    component: 'ElSelect',
+    componentProps: {
+      placeholder: '选择检查单模版类别,单选',
+      filterable: true,
+      clearable: true,
+      style: { width: '100%' },
+    },
+    selectOptions: [], // 由详情页 queryDictTypeDetail 注入,value=itemCode, label=itemValue
+  },
+  {
+    prop: 'checklistTemplateName',
+    label: '检查单模版名称:',
+    component: 'ElSelect',
+    componentProps: {
+      placeholder: '选择检查单模版名称,单选',
+      filterable: true,
+      clearable: true,
+      style: { width: '100%' },
+    },
+    selectOptions: [], // 由页面根据类别请求模版列表后注入
+  },
+  {
+    prop: 'checkKeyContent',
+    label: '检查重点内容:',
+    component: 'ElInput',
+    componentProps: {
+      type: 'textarea',
+      rows: 4,
+      placeholder: '请输入检查重点内容',
+      maxlength: 300,
+      showWordLimit: true,
+    },
+  },
+];
+
+export const AREA_CHECK_PLAN_FORM_DATA: Record<string, unknown> = {
+  planName: '',
+  venueCategoryName: '',
+  checkVenue: '',
+  /** 主责部门多选:级联绑定 id 数组;提交给后端用 primaryResponsibleDeptName / primaryResponsibleDeptCode(逗号分隔) */
+  primaryResponsibleDeptId: [] as number[],
+  primaryResponsibleDeptName: '',
+  primaryResponsibleDeptCode: '',
+  selfCheckFrequency: '',
+  safetyEmergencyDeptId: null as number | null,
+  safetyEmergencyDeptName: '',
+  safetyEmergencyCheckFrequency: '',
+  hospitalLeaderDeptId: null as number | null,
+  hospitalLeaderDeptName: '',
+  hospitalLeaderCheckFrequency: '',
+  categoryName: '',
+  categoryCode: '',
+  checklistTemplateName: '',
+  checkKeyContent: '',
+};
+
+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' }],
+  selfCheckFrequency: [{ required: true, message: '请选择自查频次', trigger: 'change' }],
+  safetyEmergencyCheckFrequency: [{ required: true, message: '请选择安全应急部检查频次', trigger: 'change' }],
+  safetyEmergencyDeptName: [{ required: true, message: '请选择安全应急部门', trigger: 'change' }],
+  hospitalLeaderDeptName: [{ required: true, message: '请选择院领导部门', trigger: 'change' }],
+  hospitalLeaderCheckFrequency: [{ required: true, message: '请选择院领导检查频次', trigger: 'change' }],
+  categoryCode: [{ required: true, message: '请选择检查单模版类别', trigger: 'change' }],
+  checklistTemplateName: [{ required: true, message: '请选择检查单模版名称', trigger: 'change' }],
+  checkKeyContent: [{ required: true, message: '请输入检查重点内容', trigger: 'blur' }],
+};
+/** 问题主要原因:1-人的不安全行为,2-物的不安全状态,3-环境的不利影响,4-管理缺陷 */
+export const REASON_OPTIONS = [
+  { label: '人的不安全行为', value: 1 },
+  { label: '物的不安全状态', value: 2 },
+  { label: '环境的不利影响', value: 3 },
+  { label: '管理缺陷', value: 4 },
+];
+
+
+export const HIDDEN_DANGER_FORM_DATA = {
+  typeId: undefined as number | undefined,
+  dangerProblem: '',
+  reasonId: undefined as number | undefined,
+  taskSource: '', // 任务来源
+  rectificationRequirement: '', // 整改要求
+  rectificationDepartmentIds: '', // 整改责任部门
+  rectificationResponsibleIds: '', // 整改负责人
+  rectificationDeadline: '', //整改日期
+  reviewDepartmentId: undefined as number | undefined, // 复查人员所属部门
+  reviewPersonId: undefined as number | undefined, // 复查人员
+  reviewPersonName: '',
+  isDrawLessonsPush: 0, // 举一反三是否推送
+  drawLessonsContent: '', // 举一反三内容
+  drawLessonsDepartmentIds: '', // 举一反三责任部门
+  drawLessonsDeadline: '', // 举一反三时限
+  // 提交接口需要,下发时再填
+  rectificationResponsiblePerson: '',
+  // 整改详情展示(接口可能返回)
+  rectificationCompletionStatus: '',
+  rectificationCompletionTime: '',
+  attachments: '',
+  reviewComments: '',
+  rectificationResponsibleUserId: '',
+};
+
+
+export const HIDDEN_DANGER_FORM_RULES = {
+  typeId: [{ required: true, message: '请选择隐患问题类别', trigger: 'change' }],
+  dangerProblem: [{ required: true, message: '请输入隐患问题', trigger: 'blur' }],
+  reasonId: [{ required: true, message: '请选择问题主要原因', trigger: 'change' }],
+  taskSource: [{ required: true, message: '请输入任务来源', trigger: 'blur' }],
+  rectificationRequirement: [{ required: true, message: '请输入整改要求', trigger: 'blur' }],
+  rectificationDeadline: [{ required: true, message: '请选择整改日期', trigger: 'change' }],
+  reviewDepartmentId: [{ required: true, message: '请选择复查人员所属部门', trigger: 'change' }],
+  reviewPersonId: [{ required: true, message: '请选择复查人员', trigger: 'change' }],
+  isDrawLessonsPush: [{ required: true, message: '请选择举一反三是否推送', trigger: 'change' }],
+  drawLessonsContent: [
+    {
+      required: true,
+      message: '请输入举一反三内容',
+      trigger: 'blur',
+    },
+  ],
+  drawLessonsDepartmentIds: [
+    {
+      required: true,
+      message: '请选择举一反三责任部门',
+      trigger: 'change',
+    },
+  ],
+  drawLessonsDeadline: [
+    {
+      required: true,
+      message: '请选择举一反三截止日期',
+      trigger: 'change',
+    },
+  ],
+  rectificationCompletionStatus:[
+    { required: true, message: '请输入整改完成情况', trigger: 'blur' }
+  ],
+  rectificationCompletionTime: [
+    { required: true, message: '请选择整改完成时间', trigger: 'change' }
+  ],
+  rectificationDepartmentIds: [
+    { required: true, message: '请选择整改责任部门', trigger: 'change' }
+  ],
+  rectificationResponsibleIds: [
+    { required: true, message: '请选择整改负责人', trigger: 'change' }
+  ],
+};

+ 15 - 0
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanTask/configs/status.ts

@@ -0,0 +1,15 @@
+// 区域检查计划状态配置(管理员与部门一致):0=未下发 1=进行中 2=已完成 3=已作废
+export const AREA_CHECK_PLAN_STATUS_OPTIONS = [
+  { label: '全部', value: '' as const },
+//   { label: '未下发', value: 0 },
+  { label: '进行中', value: 1 },
+  { label: '已完成', value: 2 },
+  { label: '已作废', value: 3 },
+];
+
+export const AREA_CHECK_PLAN_STATUS_LABEL: Record<string, string> = {
+  '0': '未下发',
+  '1': '进行中',
+  '2': '已完成',
+  '3': '已作废',
+};

+ 118 - 0
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanTask/configs/tables.ts

@@ -0,0 +1,118 @@
+import type { TableColumnProps } from '@/types/basic-table';
+import { AREA_CHECK_PLAN_STATUS_OPTIONS, AREA_CHECK_PLAN_STATUS_LABEL } from './status';
+
+export { AREA_CHECK_PLAN_STATUS_OPTIONS, AREA_CHECK_PLAN_STATUS_LABEL };
+
+export const TABLE_OPTIONS = {
+  emptyText: '暂无数据',
+  loading: true,
+  maxHeight: 'calc(70vh - 150px)',
+};
+
+// 表格样式与检查单模版管理列表一致(TABLE_OPTIONS、编号/操作列宽)
+export const AREA_CHECK_PLAN_TABLE_COLUMNS: TableColumnProps[] = [
+  {
+    label: '编号',
+    type: 'index',
+    align: 'center',
+    width: '80px',
+  },
+  {
+    label: '完成(检查)时间',
+    prop: 'venueCategoryName',
+    align: 'left',
+    minWidth: '180px',
+    showOverflowTooltip: true,
+  },
+  {
+    label: '状态',
+    prop: 'checkVenue',
+    align: 'left',
+    minWidth: '120px',
+    showOverflowTooltip: true,
+  },
+  {
+    label: '检查人员',
+    prop: 'checkVenue',
+    align: 'left',
+    minWidth: '120px',
+    showOverflowTooltip: true,
+  },
+  {
+    label: '检查场所',
+    prop: 'checkVenue',
+    align: 'left',
+    minWidth: '120px',
+    showOverflowTooltip: true,
+  },
+  {
+    label: '检查频次',
+    prop: 'planName',
+    align: 'left',
+    minWidth: '180px',
+    showOverflowTooltip: true,
+  },
+  {
+    label: '检查重点内容',
+    prop: 'primaryResponsibleDeptName',
+    slot: 'primaryResponsibleDeptName',
+    align: 'left',
+    width: '140px',
+    showOverflowTooltip: true,
+  },
+  {
+    label: '整体检查情况描述',
+    prop: 'primaryResponsibleDeptPersonName',
+    align: 'left',
+    minWidth: '160px',
+    showOverflowTooltip: true,
+  },
+  {
+    label: '被检查人签字',
+    prop: 'selfCheckFrequency',
+    align: 'left',
+    minWidth: '140px',
+    showOverflowTooltip: true,
+  },
+  {
+    label: '检查项总数',
+    prop: 'categoryName',
+    align: 'left',
+    minWidth: '180px',
+    showOverflowTooltip: true,
+  },
+  {
+    label: '合格项数',
+    prop: 'primaryResponsibleDeptExecGroupName',
+    align: 'left',
+    minWidth: '240px',
+    showOverflowTooltip: true,
+  },
+  {
+    label: '不合格项数',
+    prop: 'checklistTemplateName',
+    align: 'left',
+    minWidth: '180px',
+    showOverflowTooltip: true,
+  },
+  {
+    label: '计划开始时间',
+    prop: 'planStartTime',
+    minWidth: '240px',
+    slot: 'planStartTime',
+  },
+  {
+    label: '计划结束时间',
+    prop: 'planEndTime',
+    minWidth: '240px',
+    slot: 'planEndTime',
+  },
+  {
+    label: '操作',
+    prop: 'action',
+    slot: 'action',
+    fixed: 'right',
+    width: '250px',
+    align: 'left',
+  },
+];

+ 51 - 0
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanTask/configs/types.ts

@@ -0,0 +1,51 @@
+/** 区域检查计划状态:0=未开始 1=进行中 2=已完成 3=已终止 */
+export type AreaCheckPlanStatus = 0 | 1 | 2 | 3;
+
+export interface AreaCheckPlanRecord {
+  id?: number;
+  checkVenue?: string;
+  status?: AreaCheckPlanStatus;
+  venueCategoryName?: string;
+  planName?: string;
+  primaryResponsibleDeptName?: string;
+  primaryResponsibleDeptCode?: string;
+  selfCheckFrequency?: string;
+  mainDeptExecutorGroupName?: string;
+  mainDeptExecGroupCode?: string;
+  mainDeptResponsiblePerson?: string;
+  mainDeptResponsiblePersonCode?: string;
+  safetyEmergencyDeptName?: string;
+  safetyEmergencyDeptCode?: string;
+  safetyEmergencyCheckFrequency?: string;
+  safetyEmergencyExecutorGroupName?: string;
+  safetyEmergencyExecGroupCode?: string;
+  safetyEmergencyResponsiblePerson?: string;
+  safetyEmergencyPersonCode?: string;
+  hospitalLeaderDeptName?: string;
+  hospitalLeaderDeptCode?: string;
+  hospitalLeaderCheckFrequency?: string;
+  hospitalLeaderExecutorGroupName?: string;
+  hospitalLeaderExecGroupCode?: string;
+  hospitalLeaderResponsiblePerson?: string;
+  hospitalLeaderPersonCode?: string;
+  checkKeyContent?: string;
+  categoryName?: string;
+  categoryCode?: string;
+  checklistTemplateName?: string;
+  needOverallDesc?: boolean;
+  needInspectedSign?: boolean;
+  planStartTime?: string;
+  planEndTime?: string;
+  isDeleted?: number | string;
+  createdAt?: string;
+  updatedAt?: string;
+  [key: string]: unknown;
+}
+
+export interface AreaCheckPlanQuery {
+  keyword?: string;
+  status?: AreaCheckPlanStatus | '';
+  venueCategory?: string;
+  planStartTime?: string;
+  planEndTime?: string;
+}

+ 11 - 7
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/employeeReportHiddenTroubleManagement/components/employeeReportHiddenTroubleManagementDetail.vue

@@ -37,20 +37,20 @@
     </BasicForm>
     <!-- 审核弹窗 -->
     <el-dialog v-model="approveDialogVisible" title="审核" width="500px" @close="handleApproveDialogClose">
-      <el-form :model="approveForm" label-width="100px">
+      <el-form ref="approveFormRef" :model="approveForm" :rules="approveFormRules" label-width="100px">
         <!-- <el-form-item label="审批节点:">
           <el-select v-model="approveForm.node" placeholder="请选择审批节点">
             <el-option label="需求部门" :value="1" />
             <el-option label="安全部门" :value="2" />
           </el-select>
         </el-form-item> -->
-        <el-form-item label="审批状态:">
+        <el-form-item label="审批状态:" prop="approvalStatus">
           <el-radio-group v-model="approveForm.approvalStatus">
             <el-radio :value="2">通过</el-radio>
             <el-radio :value="3">退回</el-radio>
           </el-radio-group>
         </el-form-item>
-        <el-form-item label="审批意见:">
+        <el-form-item label="审批意见:" prop="approvalContent">
           <el-input v-model="approveForm.approvalContent" type="textarea" :rows="4" placeholder="请输入审批意见" />
         </el-form-item>
       </el-form>
@@ -168,7 +168,7 @@
 
   // 支持员工上报与管理员审核两套 operate
   const isViewMode = computed(
-    () => operate.value === 'employee-report-hidden-trouble-view' || operate.value === 'hidden-trouble-review-view',
+    () => operate.value === 'employee-report-hidden-trouble-view' || operate.value === 'hidden-trouble-review-view'|| operate.value === 'hidden-trouble-review-approve',
   );
   const isApproveMode = computed(
     () =>
@@ -267,6 +267,11 @@
 
   // 审核相关
   const approveDialogVisible = ref(false);
+  const approveFormRef = ref();
+  const approveFormRules = {
+    approvalStatus: [{ required: true, message: '请选择审批状态', trigger: 'change' }],
+    approvalContent: [{ required: true, message: '请输入审批意见', trigger: 'blur' }],
+  };
   const approveForm = ref<ApproveEmployeeHazardReportReq>({
     hazardId: 0,
     node: 1,
@@ -332,9 +337,8 @@
   };
 
   const handleApproveSubmit = async () => {
-    // !approveForm.value.node || 
-    if (!approveForm.value.approvalStatus) {
-      ElMessage.warning('请选择状态');
+    const valid = await approveFormRef.value?.validate?.().catch(() => false);
+    if (!valid) {
       return;
     }
     try {