Explorar el Código

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

Feat/production safety

See merge request product-group-fe/sfy-safety-group/sfy-safety!367
ai0197(吴云丰) hace 2 meses
padre
commit
baccab9052
Se han modificado 29 ficheros con 1559 adiciones y 590 borrados
  1. 33 0
      src/api/employee-training-record-card-management/index.ts
  2. 27 2
      src/api/safety-organization-management/index.ts
  3. 16 16
      src/router/routers/production-safety-router/productionSafetySystem.ts
  4. 2 2
      src/router/routers/production-safety-router/risk-identification-and-control.ts
  5. 14 0
      src/router/routers/production-safety-router/safetyTrainingAndEducation.ts
  6. 10 4
      src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagement/areaCheckPlanManagement.vue
  7. 15 11
      src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagement/components/areaCheckPlanManagementDetail.vue
  8. 1 91
      src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagement/configs/tables.ts
  9. 1 1
      src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagementDept/areaCheckPlanManagementDept.vue
  10. 2 1
      src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagementDept/components/areaCheckPlanManagementDeptDetail.vue
  11. 15 0
      src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagementDept/configs/status.ts
  12. 64 23
      src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagementDept/configs/tables.ts
  13. 141 0
      src/views/production-safety/productionSafetySystem/safetyOrganizationSystemManagement/components/TeamDetailDrawer.vue
  14. 2 2
      src/views/production-safety/productionSafetySystem/safetyOrganizationSystemManagement/components/addSafetySystem.vue
  15. 127 0
      src/views/production-safety/productionSafetySystem/safetyOrganizationSystemManagement/components/collapseItem.vue
  16. 195 0
      src/views/production-safety/productionSafetySystem/safetyOrganizationSystemManagement/components/orgChart.vue
  17. 2 1
      src/views/production-safety/productionSafetySystem/safetyOrganizationSystemManagement/configs/tables.ts
  18. 285 352
      src/views/production-safety/productionSafetySystem/safetyOrganizationSystemManagement/safetyOrganizationSystemManagement.vue
  19. 7 1
      src/views/production-safety/productionSafetySystem/securityOrganizationalStructure/components/safetyOrganizationSystemManagementDetail.vue
  20. 8 1
      src/views/production-safety/productionSafetySystem/securityOrganizationalStructure/configs/form.ts
  21. 10 2
      src/views/production-safety/productionSafetySystem/securityOrganizationalStructure/configs/tables.ts
  22. 104 69
      src/views/production-safety/productionSafetySystem/securityOrganizationalStructure/securityOrganizationalStructure.vue
  23. 0 0
      src/views/production-safety/productionSafetySystem/securityOrganizationalStructure/securityOrganizationalStructureItem.vue
  24. 2 2
      src/views/production-safety/risk-identification-and-control/labor-products-purchase-apply-manage-admin/components/detail.vue
  25. 3 3
      src/views/production-safety/risk-identification-and-control/labor-products-purchase-apply-manage/components/detail.vue
  26. 4 4
      src/views/production-safety/risk-identification-and-control/work-injury-apply-manage/listAdmin.vue
  27. 86 0
      src/views/production-safety/safetyTrainingAndEducation/employeeTrainingRecordCardManagement/configs/form.ts
  28. 15 2
      src/views/production-safety/safetyTrainingAndEducation/employeeTrainingRecordCardManagement/employeeTrainingRecordCardManagement.vue
  29. 368 0
      src/views/production-safety/safetyTrainingAndEducation/employeeTrainingRecordCardManagement/employeeTrainingRecordCardManagementEdit.vue

+ 33 - 0
src/api/employee-training-record-card-management/index.ts

@@ -12,6 +12,7 @@ export interface EmployeeTableType {
   staffAddress: string;
   staffImg?: string;
   deptName: string;
+  deptId?: string;
   dateOfJoining: Date;
   highestDegree: string;
   staffJob: string;
@@ -56,6 +57,28 @@ export interface EducationStaffTrainingCardItem {
   createdAt: string;
   updatedAt: string;
 }
+// 编辑员工记录卡
+export interface FormDataType {
+  id?: string; 
+  staffNo: string;
+  /** 后端存取:部门名称(仅最后一级所选部门) */
+  deptName: string;
+  /** 后端存取:部门 id(单个,最后一级) */
+  deptId?: string;
+  /** 仅前端级联:选中的部门 id(单选,叶子) */
+  deptIdForSelect?: number;
+  staffName: string;
+  staffBirthday: string;
+  staffIdCard: string;
+  staffAddress: string;
+  dateOfJoining: string;
+  staffJob: string;
+  technicalLvl: string;
+  professionalTitle: string;
+  highestDegree: string;
+  jobSeniority: number | string;
+  staffImg?:any
+}
 
 /**
  * 分页查询员工培训记录卡列表
@@ -144,3 +167,13 @@ export function delateEducationStaffTrainingCardScore(id: number) {
     method: 'delete',
   });
 }
+/**
+ * 员工培训记录卡--编辑
+ */
+export function updateEducationStaffTrainingCard(data: any) {
+  return http.request({
+    url: '/educationStaffTrainingCard/updateEducationStaffTrainingCard',
+    method: 'post',
+    data: data,
+  });
+}

+ 27 - 2
src/api/safety-organization-management/index.ts

@@ -2,12 +2,15 @@
  * @Author: liuJie
  * @Date: 2026-02-05 11:15:09
  * @LastEditors: liuJie
- * @LastEditTime: 2026-03-07 22:05:07
+ * @LastEditTime: 2026-03-30 13:26:55
  * @Describe: 安全组织体系管理
  */
 
 import { http } from '@/utils/http/axios';
-
+interface SafetyOrgUserParams {
+    userNum: string|number;
+    depResp: string
+}
 // ————————————————左侧菜单——————————————————————
 /**
  * 新增安全组织列表
@@ -98,6 +101,28 @@ export const delEmployee = (id) => {
   });
 };
 
+/**
+ * @description: 保存组织人员信息和职责
+ * @return {*}
+ */
+export const safetyOrgUserSave = (data:SafetyOrgUserParams)=>{
+    return http.request({
+        url: '/safetyorguser/saveorg',
+        method: 'post',
+        data
+  });
+}
+
+/**
+ * @description: 读取组织人员信息和职责
+ * @return {*}
+ */
+export const safetyOrgUserDetail = (id)=>{
+    return http.request({
+        url: '/safetyorguser/queryOrgDetail?id='+id,
+        method: 'get',
+  });
+}
     
 /**
  * 导出数据

+ 16 - 16
src/router/routers/production-safety-router/productionSafetySystem.ts

@@ -226,30 +226,30 @@ const productionSafetySystemRoutes: RouteComponent[] = [{
       },
     },
     {
-      id: 90030,
+      id: 90027,
       parentId: 9002,
-      name: 'securityOrganizationalStructure',
-      path: 'security-organizational-structure',
-      component: '/production-safety/productionSafetySystem/securityOrganizationalStructure/securityOrganizationalStructure',
+      name: 'SafetyOrganizationSystemManagement',
+      path: 'safety-organization-system-management',
+      component: '/production-safety/productionSafetySystem/safetyOrganizationSystemManagement/safetyOrganizationSystemManagement',
       meta: {
-        title: '安全组织体系架构',
+        title: '安全组织体系管理(架构)',
         icon: 'OverviewIcon',
-        activeMenu: '/work-safety/productionSafetySystem/securityOrganizationalStructure',
+        activeMenu: '/work-safety/production-safety-system/safety-organization-system-management',
         isRoot: false,
         hidden: false,
         noCache: false,
       },
     },
     {
-      id: 90027,
+      id: 90030,
       parentId: 9002,
-      name: 'SafetyOrganizationSystemManagement',
-      path: 'safety-organization-system-management',
-      component: '/production-safety/productionSafetySystem/safetyOrganizationSystemManagement/safetyOrganizationSystemManagement',
+      name: 'securityOrganizationalStructure',
+      path: 'security-organizational-structure',
+      component: '/production-safety/productionSafetySystem/securityOrganizationalStructure/securityOrganizationalStructure',
       meta: {
-        title: '安全组织体系管理',
+        title: '安全组织体系管理(组织员工)',
         icon: 'OverviewIcon',
-        activeMenu: '/work-safety/productionSafetySystem/safetyOrganizationSystemManagement',
+        activeMenu: '/work-safety/production-safety-system/safety-organization-system-management',
         isRoot: false,
         hidden: false,
         noCache: false,
@@ -258,12 +258,12 @@ const productionSafetySystemRoutes: RouteComponent[] = [{
     {
         id: 90029,
         parentId: 9002,
-        name: 'SafetyOrganizationSystemManagementItem',
-        path: 'safety-organization-system-management-item',
-        component: '/production-safety/productionSafetySystem/safetyOrganizationSystemManagement/safetyOrganizationSystemManagementItem',
+        name: 'SecurityOrganizationalStructureItem',
+        path: 'security-organizational-structure-item',
+        component: '/production-safety/productionSafetySystem/securityOrganizationalStructure/securityOrganizationalStructureItem',
         meta: {
             title: '安全组织体系管理详情',
-            activeMenu: '/work-safety/productionSafetySystem/safetyOrganizationSystemManagement',
+            activeMenu: '/work-safety/production-safety-system/safety-organization-system-management',
             icon: 'OverviewIcon',
             isRoot: false,
             hidden: false,

+ 2 - 2
src/router/routers/production-safety-router/risk-identification-and-control.ts

@@ -633,7 +633,7 @@ const riskIdentificationAndControlRoutes: RouteComponent[] = [{
       path: 'labor-products-purchase-apply-manage-admin',
       component: '/production-safety/risk-identification-and-control/labor-products-purchase-apply-manage-admin/listAdmin',
       meta: {
-        title: '劳防用品采购申请(管理员)',
+        title: '劳防用品采购申请审核列表(管理员)',
         icon: 'OverviewIcon',
         isRoot: false,
         hidden: false,
@@ -647,7 +647,7 @@ const riskIdentificationAndControlRoutes: RouteComponent[] = [{
       path: 'labor-products-purchase-apply-manage-admin-item',
       component: '/production-safety/risk-identification-and-control/labor-products-purchase-apply-manage-admin/Item',
       meta: {
-        title: '劳防用品采购申请详情(管理员)',
+        title: '劳防用品采购申请审核详情(管理员)',
         activeMenu: '/work-safety/risk-identification-and-control/labor-products-purchase-apply-manage-admin',
         icon: 'OverviewIcon',
         isRoot: false,

+ 14 - 0
src/router/routers/production-safety-router/safetyTrainingAndEducation.ts

@@ -98,6 +98,20 @@ const safetyTrainingAndEducationRoutes: RouteComponent[] = [{
         noCache: false,
       },
     },
+    {
+      id: 90060204,
+      parentId: 9006,
+      name: 'employeeTrainingRecordCardManagementEdit',
+      path: 'employee-training-record-card-management-edit',
+      component: '/production-safety/safetyTrainingAndEducation/employeeTrainingRecordCardManagement/employeeTrainingRecordCardManagementEdit',
+      meta: {
+        title: '员工培训记录卡管理编辑',
+        icon: 'OverviewIcon',
+        isRoot: false,
+        hidden: true,
+        noCache: false,
+      },
+    },
   ],
 }];
 

+ 10 - 4
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagement/areaCheckPlanManagement.vue

@@ -87,6 +87,12 @@
                scope.row.status === 0 ? '-' : scope.row.needOverallDesc === true ? '是' : scope.row.needOverallDesc === false ? '否' : '-'
               }}</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>{{
                 scope.row.status === 0 ? '-' : scope.row.needSigneeSign === true ? '是' : scope.row.needSigneeSign === false ? '否' : '-'
@@ -144,12 +150,12 @@
     <el-dialog
       v-model="showIssueDialog"
       title="下发区域检查计划任务"
-      width="560px"
+      width="680px"
       destroy-on-close
       @open="onIssueDialogOpen"
       @close="currentIssuePlanId = null"
     >
-      <el-form ref="issueFormRef" :model="issueForm" :rules="issueRules" label-width="160px">
+      <el-form ref="issueFormRef" :model="issueForm" :rules="issueRules" label-width="220px">
         <el-form-item label="责任部门:" prop="responsibleDeptIds">
           <el-cascader
             ref="issueResponsibleDeptCascaderRef"
@@ -184,7 +190,7 @@
         <el-form-item label="安全应急部门:" prop="safetyEmergencyDeptId">
           <el-cascader
             v-model="issueForm.safetyEmergencyDeptId"
-            :options="issueDeptTree"
+            :options="issueDeptTreeTow"
             :props="cascaderDeptProp"
             :show-all-levels="false"
             placeholder="请选择安全应急部门"
@@ -207,7 +213,7 @@
         <el-form-item label="院领导部门:" prop="hospitalLeaderDeptId">
           <el-cascader
             v-model="issueForm.hospitalLeaderDeptId"
-            :options="issueDeptTree"
+            :options="issueDeptTreeTow"
             :props="cascaderDeptProp"
             :show-all-levels="false"
             placeholder="请选择院领导部门"

+ 15 - 11
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagement/components/areaCheckPlanManagementDetail.vue

@@ -29,7 +29,10 @@
         <div class="row">
           <div class="col">
             <div class="label">主责部门:</div>
-            <div class="value">{{ viewDetail.primaryResponsibleDeptName || '-' }}</div>
+            <!-- <div class="value">{{ viewDetail.primaryResponsibleDeptName || '-' }}</div> -->
+            <div class="value">
+                {{ Number(viewDetail.status) === 0 ? viewDetail.primaryResponsibleDeptName: viewDetail.responsibleDeptName ||'-'}}
+            </div>
           </div>
           <div class="col">
             <div class="label">自查频率:</div>
@@ -239,8 +242,8 @@
           :show-all-levels="false"
           placeholder="请选择部门"
           filterable
+          :disabled="isViewMode"
           clearable
-          :disabled="true"
           style="width: 100%"
           @change="onSafetyEmergencyDeptChange"
         />
@@ -255,7 +258,7 @@
           placeholder="请选择部门"
           filterable
           clearable
-          :disabled="true"
+          :disabled="isViewMode"
           style="width: 100%"
           @change="onHospitalLeaderDeptChange"
         />
@@ -928,6 +931,7 @@
     const status = d?.status as number | undefined;
     return {
       ...d,
+      responsibleDeptName: d?.responsibleDeptName ?? '-',
       statusName: status != null ? AREA_CHECK_PLAN_STATUS_LABEL[String(status)] ?? '-' : '-',
       planName: d?.planName ?? ruleFormData.planName ?? '-',
       venueCategoryName: d?.venueCategoryName ?? ruleFormData.venueCategoryName ?? '-',
@@ -1287,15 +1291,15 @@
       ruleFormData.primaryResponsibleDeptCode = detail.primaryResponsibleDeptCode ?? '';
       ruleFormData.primaryResponsibleDeptId = findDeptIdByName(deptTree.value, ruleFormData.primaryResponsibleDeptName as string) ?? null;
       ruleFormData.selfCheckFrequency = detail.selfCheckFrequency ?? '';
-      // ruleFormData.safetyEmergencyDeptName = detail.safetyEmergencyDeptName ?? '';
-      ruleFormData.safetyEmergencyDeptName = '安全与应急管理部';
-      // ruleFormData.safetyEmergencyDeptId = findDeptIdByName(deptTree.value, ruleFormData.safetyEmergencyDeptName as string) ?? null;
-      ruleFormData.safetyEmergencyDeptId = 1698;
+      ruleFormData.safetyEmergencyDeptName = detail.safetyEmergencyDeptName ?? '';
+      ruleFormData.safetyEmergencyDeptId = findDeptIdByName(deptTree.value, ruleFormData.safetyEmergencyDeptName as string) ?? null;
+      //   ruleFormData.safetyEmergencyDeptName = '安全与应急管理部';
+    //   ruleFormData.safetyEmergencyDeptId = 1698;
       ruleFormData.safetyEmergencyCheckFrequency = detail.safetyEmergencyCheckFrequency ?? '';
-      // ruleFormData.hospitalLeaderDeptName = detail.hospitalLeaderDeptName ?? '';
-      ruleFormData.hospitalLeaderDeptName = '院领导';
-      // ruleFormData.hospitalLeaderDeptId = findDeptIdByName(deptTree.value, ruleFormData.hospitalLeaderDeptName as string) ?? null;
-      ruleFormData.hospitalLeaderDeptId = 1693;
+      ruleFormData.hospitalLeaderDeptName = detail.hospitalLeaderDeptName ?? '';
+      //ruleFormData.hospitalLeaderDeptName = '院领导';
+      //ruleFormData.hospitalLeaderDeptId = 1693;
+      ruleFormData.hospitalLeaderDeptId = findDeptIdByName(deptTree.value, ruleFormData.hospitalLeaderDeptName as string) ?? null;
       ruleFormData.hospitalLeaderCheckFrequency = detail.hospitalLeaderCheckFrequency ?? '';
       (ruleFormData as Record<string, unknown>).categoryCode = detail.categoryCode ?? '';
       ruleFormData.categoryName = detail.categoryName ?? '';

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

@@ -41,6 +41,7 @@ export const AREA_CHECK_PLAN_TABLE_COLUMNS: TableColumnProps[] = [
   {
     label: '主责部门',
     prop: 'primaryResponsibleDeptName',
+    slot: 'primaryResponsibleDeptName',
     align: 'left',
     minWidth: '140px',
     showOverflowTooltip: true,
@@ -92,97 +93,6 @@ export const AREA_CHECK_PLAN_TABLE_COLUMNS: TableColumnProps[] = [
     minWidth: '240px',
     slot: 'planEndTime',
   },
-  // {
-  //   label: '状态',
-  //   prop: 'status',
-  //   slot: 'status',
-  //   align: 'center',
-  //   minWidth: '120px',
-  // },
-  // {
-  //   label: '安全应急部门名称',
-  //   prop: 'safetyEmergencyDeptName',
-  //   align: 'left',
-  //   minWidth: '180px',
-  //   showOverflowTooltip: true,
-  // },
-  // {
-  //   label: '安全应急部检查频次',
-  //   prop: 'safetyEmergencyCheckFrequency',
-  //   align: 'left',
-  //   minWidth: '180px',
-  //   showOverflowTooltip: true,
-  // },
-  // {
-  //   label: '安全应急部执行人所属分组名称',
-  //   prop: 'safetyEmergencyExecutorGroupName',
-  //   align: 'left',
-  //   minWidth: '260px',
-  //   showOverflowTooltip: true,
-  // },
-  // {
-  //   label: '安全应急部责任人',
-  //   prop: 'safetyEmergencyResponsiblePerson',
-  //   align: 'left',
-  //   minWidth: '180px',
-  //   showOverflowTooltip: true,
-  // },
-  // {
-  //   label: '院领导部门名称',
-  //   prop: 'hospitalLeaderDeptName',
-  //   align: 'left',
-  //   minWidth: '180px',
-  //   showOverflowTooltip: true,
-  // },
-  // {
-  //   label: '院领导检查频次',
-  //   prop: 'hospitalLeaderCheckFrequency',
-  //   align: 'left',
-  //   minWidth: '160px',
-  //   showOverflowTooltip: true,
-  // },
-  // {
-  //   label: '院领导执行人所属分组名称',
-  //   prop: 'hospitalLeaderExecutorGroupName',
-  //   align: 'left',
-  //   minWidth: '220px',
-  //   showOverflowTooltip: true,
-  // },
-  // {
-  //   label: '院领导责任人',
-  //   prop: 'hospitalLeaderResponsiblePerson',
-  //   align: 'left',
-  //   minWidth: '140px',
-  //   showOverflowTooltip: true,
-  // },
-  // {
-  //   label: '检查重点内容',
-  //   prop: 'checkKeyContent',
-  //   align: 'left',
-  //   minWidth: '150px',
-  //   showOverflowTooltip: true,
-  // },
-  // {
-  //   label: '检查单模版名称',
-  //   prop: 'checklistTemplateName',
-  //   align: 'left',
-  //   minWidth: '160px',
-  //   showOverflowTooltip: true,
-  // },
-  // {
-  //   label: '是否需要整体检查情况描述',
-  //   prop: 'needOverallDesc',
-  //   slot: 'needOverallDesc',
-  //   align: 'center',
-  //   minWidth: '240px',
-  // },
-  // {
-  //   label: '是否需要被检查人签字',
-  //   prop: 'needSigneeSign',
-  //   slot: 'needSigneeSign',
-  //   align: 'center',
-  //   minWidth: '200px',
-  // },
   {
     label: '操作',
     prop: 'action',

+ 1 - 1
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagementDept/areaCheckPlanManagementDept.vue

@@ -118,7 +118,7 @@
     AREA_CHECK_PLAN_TABLE_COLUMNS,
     AREA_CHECK_PLAN_STATUS_OPTIONS,
     AREA_CHECK_PLAN_STATUS_LABEL,
-  } from '../areaCheckPlanManagement/configs/tables';
+  } from './configs/tables';
   import { useRouter } from 'vue-router';
   import type { QueryPageRequest } from '@/types/basic-query';
   import type { AreaCheckPlanQuery, AreaCheckPlanRecord } from '../areaCheckPlanManagement/configs/types';

+ 2 - 1
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagementDept/components/areaCheckPlanManagementDeptDetail.vue

@@ -29,7 +29,7 @@
         <div class="row">
           <div class="col">
             <div class="label">主责部门:</div>
-            <div class="value">{{ viewDetail.primaryResponsibleDeptName || '-' }}</div>
+            <div class="value">{{ viewDetail.responsibleDeptName || '-' }}</div>
           </div>
           <div class="col">
             <div class="label">自查频率:</div>
@@ -611,6 +611,7 @@
     console.log(d,'d')
     return {
       ...d,
+      responsibleDeptName: d?.responsibleDeptName ?? '-',
       statusName: status != null ? AREA_CHECK_PLAN_STATUS_LABEL[String(status)] ?? '-' : '-',
       planName: d?.planName ?? '-',
       venueCategoryName: d?.venueCategoryName ?? '-',

+ 15 - 0
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagementDept/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': '已作废',
+};

+ 64 - 23
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagementDept/configs/tables.ts

@@ -1,13 +1,16 @@
 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)',
 };
 
-export const INVENTORY_TABLE_COLUMNS: TableColumnProps[] = [
+// 表格样式与检查单模版管理列表一致(TABLE_OPTIONS、编号/操作列宽)
+export const AREA_CHECK_PLAN_TABLE_COLUMNS: TableColumnProps[] = [
   {
     label: '编号',
     type: 'index',
@@ -15,48 +18,86 @@ export const INVENTORY_TABLE_COLUMNS: TableColumnProps[] = [
     width: '80px',
   },
   {
-    label: '物品名称',
-    prop: 'itemName',
+    label: '检查场所所属类别',
+    prop: 'venueCategoryName',
     align: 'left',
-    minWidth: '120px',
+    minWidth: '180px',
+    showOverflowTooltip: true,
   },
   {
-    label: '入库日期',
-    prop: 'warehouseDate',
+    label: '检查场所',
+    prop: 'checkVenue',
     align: 'left',
     minWidth: '120px',
+    showOverflowTooltip: true,
   },
   {
-    label: '物品数量',
-    prop: 'itemQuantity',
-    align: 'center',
-    minWidth: '120px',
+    label: '区域检查计划名称',
+    prop: 'planName',
+    align: 'left',
+    minWidth: '180px',
+    showOverflowTooltip: true,
   },
   {
-    label: '经办人',
-    prop: 'handler',
+    label: '主责部门',
+    prop: 'responsibleDeptName',
     align: 'left',
-    minWidth: '120px',
+    minWidth: '140px',
+    showOverflowTooltip: true,
   },
   {
-    label: '备注',
-    prop: 'remarks',
+    label: '主责部门责任人',
+    prop: 'primaryResponsibleDeptPersonName',
     align: 'left',
-    minWidth: '150px',
+    minWidth: '160px',
+    showOverflowTooltip: true,
   },
   {
-    label: '状态',
-    prop: 'status',
-    slot: 'status',
-    align: 'center',
-    minWidth: '100px',
+    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: '180px',
+    width: '250px',
     align: 'left',
   },
 ];

+ 141 - 0
src/views/production-safety/productionSafetySystem/safetyOrganizationSystemManagement/components/TeamDetailDrawer.vue

@@ -0,0 +1,141 @@
+<template>
+  <el-drawer v-model="showDrawer" direction="rtl" @close="clearData">
+    <div class="team-detail__info">
+      <div class="team-detail__info__team-name">{{ teamAndPersonInfo?.teamName }}</div>
+      <div class="team-detail__info__member-count">
+        共 <span>{{ teamAndPersonInfo?.memberCount || 0 }}</span> 人
+      </div>
+    </div>
+
+    <div class="team-detail__level" v-for="level in levelAndPersonList" :key="level.positionLevel">
+      <div class="team-detail__level__title">
+        {{ level.title }}
+      </div>
+      <div class="team-detail__level__content">
+        <div v-for="(person, index) in level.personList" :key="index">{{ person }}</div>
+      </div>
+    </div>
+
+    <div class="team-detail__description">
+      <div class="team-detail__description__title"> 队伍职责 </div>
+      <div class="team-detail__description__content"> {{ teamAndPersonInfo?.description }} </div>
+    </div>
+  </el-drawer>
+</template>
+
+<script setup lang="ts">
+  import { ref, watch } from 'vue';
+  import { queryEmergencyTeamDetail } from '@/api/emergency-organization/teams';
+  import { TeamAndPersonInfoType } from '../team-management/type';
+
+  const props = defineProps<{ selectedTeamId: number | null }>();
+
+  type LevelAndPersonType = {
+    positionLevel: number;
+    title: string;
+    personList: string[];
+  };
+
+  const showDrawer = ref(false);
+  const teamAndPersonInfo = ref<TeamAndPersonInfoType>();
+  const levelAndPersonList = ref<LevelAndPersonType[]>([]);
+
+  function drawerShow() {
+    showDrawer.value = true;
+  }
+
+  function clearData() {
+    teamAndPersonInfo.value = undefined;
+    levelAndPersonList.value = [];
+  }
+
+  // 按照职位等级排序队伍人员
+  function setLevelAndPerson() {
+    const levelMap: { [key: number]: LevelAndPersonType } = {};
+
+    teamAndPersonInfo.value?.personnelList.map((person) => {
+      if (!(person.positionLevel in levelMap)) {
+        levelMap[person.positionLevel] = { positionLevel: person.positionLevel, title: person.title, personList: [] };
+      }
+
+      levelMap[person.positionLevel].personList.push(person.realname);
+    });
+
+    for (let level in levelMap) {
+      levelAndPersonList.value.push(levelMap[level]);
+    }
+  }
+
+  watch(
+    () => showDrawer.value,
+    async () => {
+      if (showDrawer.value) {
+        teamAndPersonInfo.value = await queryEmergencyTeamDetail(props.selectedTeamId!);
+        setLevelAndPerson();
+      }
+    },
+  );
+
+  defineExpose({
+    drawerShow,
+  });
+</script>
+
+<style scoped lang="scss">
+  .team-detail__info {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 20px;
+
+    &__team-name {
+      font-size: 20px;
+      font-weight: bold;
+    }
+    &__member-count {
+      span {
+        color: #1777ff;
+      }
+    }
+  }
+
+  .team-detail__level {
+    display: flex;
+    align-items: center;
+    margin-bottom: 10px;
+    padding: 10px 0;
+    border-left: 3px solid #1777ff;
+    background-color: #e5effe;
+
+    &__title {
+      flex: 1;
+      height: inherit;
+      padding: 0 10px;
+      color: #1777ff;
+      text-align: center;
+      white-space: nowrap;
+    }
+
+    &__content {
+      flex: 10;
+      display: flex;
+      flex-wrap: wrap;
+      padding: 0 15px;
+      gap: 15px;
+      border-left: 1px solid #c5c3c3;
+    }
+  }
+
+  .team-detail__description {
+    margin-top: 20px;
+    &__title {
+      margin-bottom: 10px;
+      font-size: 20px;
+      font-weight: bold;
+    }
+    &__content {
+      color: #aaaaaa;
+      white-space: pre-wrap;
+    }
+  }
+</style>

+ 2 - 2
src/views/production-safety/productionSafetySystem/safetyOrganizationSystemManagement/components/addSafetySystem.vue

@@ -2,8 +2,8 @@
  * @Author: liuJie
  * @Date: 2026-02-05 10:20:24
  * @LastEditors: liuJie
- * @LastEditTime: 2026-02-05 20:25:10
- * @Describe: 添加组织
+ * @LastEditTime: 2026-03-29 16:20:32
+ * @Describe: 添加、编辑组织
 -->
 <script lang="ts" setup>
   import { ref, watch } from 'vue';

+ 127 - 0
src/views/production-safety/productionSafetySystem/safetyOrganizationSystemManagement/components/collapseItem.vue

@@ -0,0 +1,127 @@
+<template>
+  <div v-if="level <= 10">
+    <el-collapse-item :name="String(data.orgId)">
+      <!-- 标题 -->
+      <template #title>
+        <div class="node">
+            <div @click.stop="handleClick" class="cursor-pointer">
+            {{ data?.orgName }}
+            </div>
+            <el-popconfirm hide-icon  popper-class="node-popconfirm">
+                <template #reference>
+                    <el-button size="small" class="handle" :icon="MoreFilled" @click.stop />
+                </template>
+                <template #actions="{ confirm, cancel }">
+                    <ul>
+                        <li><el-button type="primary" link :icon="Plus" @click.stop="bindingEvents('create', 'children', data)">新增</el-button></li>
+                        <li><el-button type="primary" link :icon="Edit" @click.stop="bindingEvents('edit', 'children', data)">编辑</el-button></li>
+                        <li><el-button type="primary" link :icon="Delete" @click.stop="bindingEvents('delate', 'children', data)">删除</el-button></li>
+                    </ul>
+                </template>
+            </el-popconfirm>
+        </div>
+      </template>
+
+      <!-- 子级递归 -->
+      <div
+        v-if="data.children && data.children.length && level < 10"
+        class="ml-4">
+        <el-collapse accordion>
+          <CollapseItem
+            v-for="child in data.children"
+            :key="child.id"
+            :data="child"
+            :level="level + 1"
+            @click-node="handleClickItem"
+            @create-node="(type, item) => emit('create-node', type, item)"
+            @edit-node="(type, item) => emit('edit-node', type, item)"
+            @delete-node="(type, item) => emit('delete-node', type, item)"
+          />
+        </el-collapse>
+      </div>
+      <div v-else>
+        <el-empty description="未添加子组织" :image-size="40" />
+      </div>
+    </el-collapse-item>
+  </div>
+</template>
+
+<script setup>
+  import { defineProps, defineEmits, defineOptions } from 'vue'
+  import {
+  Delete,
+  Edit,
+  Plus,
+  MoreFilled
+} from '@element-plus/icons-vue'
+// 必须给组件命名,才能递归
+defineOptions({
+  name: 'CollapseItem'
+})
+
+// 定义 props
+const props = defineProps({
+  data: {
+    type: Object,
+    required: true
+  },
+  level: {
+    type: Number,
+    default: 1
+  }
+})
+
+// 定义事件
+const emit = defineEmits(['click-node', 'create-node', 'edit-node', 'delete-node'])
+// 新增、编辑、删除事件
+const bindingEvents = (state, type, item)=>{
+    switch(state){
+        case 'create':
+            emit('create-node',type, item)
+            break;
+        case 'edit':
+            emit('edit-node',type, item)
+            break;
+        case 'delate':
+            emit('delete-node',type, item)
+            break;
+        default:
+            emit('create-node',type, item)
+    }
+}
+// 点击当前项
+const handleClick = () => {
+  emit('click-node', props.data)
+}
+
+// 接收子组件点击
+const handleClickItem = (item) => {
+  emit('click-node', item)
+}
+</script>
+
+<style scoped>
+.ml-4 {
+  margin-left: 16px;
+}
+.cursor-pointer {
+  cursor: pointer;
+}
+.node {
+  display:flex;
+  justify-content: space-between;
+  align-items: center;
+  width: 100%;
+}
+.handle {
+    margin-right: 8px;
+}
+li{
+    list-style: none;
+    width:100%;
+    text-align: center;
+    padding:4px 0;
+    margin: 4px 0;
+    cursor: pointer;
+}
+</style>

+ 195 - 0
src/views/production-safety/productionSafetySystem/safetyOrganizationSystemManagement/components/orgChart.vue

@@ -0,0 +1,195 @@
+<template>
+  <div class="org-chart-container" ref="container"></div>
+</template>
+
+<script setup lang="ts">
+  import { ref, onMounted, onBeforeUnmount, watch, nextTick } from 'vue';
+  import { Graph, treeToGraphData, NodeEvent, CanvasEvent } from '@antv/g6';
+  // import { Graph, treeToGraphData, register, ExtensionCategory, NodeEvent, Polyline, CanvasEvent } from '@antv/g6';
+
+  /**
+   * @description: 为了确保图的正确渲染和交互,建议按照 G6 标准数据结构组织数据。
+   * 每个元素(节点、边、组合)应包含一个 data 字段,用于存放业务数据和自定义属性。
+   */
+
+  // 定义 props 接口
+  interface TreeData {
+    id: string;
+    data: { name: string };
+    children?: TreeData[];
+  }
+
+  const props = defineProps<{
+    treeData: TreeData;
+  }>();
+
+  const emits = defineEmits<{
+    (event: 'node-click', nodeData: any): void;
+    (event: 'canvas-click'): void;
+  }>();
+
+  let data = treeToGraphData(props.treeData); // 通过 treeToGraphData 方法,将树形结构数据转换为 G6 的标准数据结构
+  const container = ref<HTMLDivElement | null>(null); // 图表容器引用
+  let graph: any = null;
+  let pending = false; // 防止并发 initGraph
+
+  // 初始化图表
+  const initGraph = async () => {
+    if (pending || !container.value) return;
+    pending = true;
+
+    // 销毁旧实例
+    if (graph) {
+      graph.off();
+      graph.destroy();
+      graph = null;
+    }
+
+    // 等待下一帧,确保销毁完成
+    await nextTick();
+
+    // 创建新的 G6 实例
+    graph = new Graph({
+      container: container.value,
+      padding: [20, 20, 20, 20], // 图表内边距
+      data,
+      node: {
+        type: 'rect', // 使用内置的矩形节点类型
+        style: {
+          labelText: (d: any) => d.data.name, // 节点文本
+          labelFill: '#333', // 文本颜色
+          labelFontSize: 14, // 文本大小
+          size: [250, 50],
+          lineWidth: 1, // 边框宽度
+          // lineDash: [5, 5], // 虚线边框
+          stroke: '#1777FF', // 边框色
+          fill: '#E7F1FF', // 填充色
+          radius: 8,
+          labelPlacement: 'center',
+          ports: [{ placement: 'top' }, { placement: 'bottom' }],
+        },
+        // 节点状态样式
+        state: {
+          selected: {
+            fill: '#1777FF',
+            stroke: '#1777FF',
+            lineWidth: 1,
+            labelFill: '#fff', // 选中状态下文本颜色
+            labelFontSize: 16, // 选中状态下文本大小
+          },
+        },
+      },
+      edge: {
+        type: 'ant-line', // 边类型
+        style: {
+          stroke: '#1777FF', // 边的颜色
+          lineWidth: 1, // 边的宽度
+          lineDash: [10, 10],
+          endArrow: true, // 是否有箭头
+          router: {
+            type: 'orth',
+          },
+        },
+      },
+      layout: {
+        type: 'dagre',
+        nodesep: 100,
+        ranksep: 120,
+        preventOverlap: true, // 防止节点重叠
+        nodeStrength: -50, // 节点之间的斥力
+        edgeStrength: 0.5, // 边的弹性系数
+        iterations: 200, // 迭代次数
+        animation: true, // 启用布局动画
+      },
+      autoFit: {
+        type: 'center', // 自适应类型:'view' 或 'center'
+        // options: {
+        //   // 仅适用于 'view' 类型
+        //   when: 'always', // 何时适配:'overflow'(仅当内容溢出时) 或 'always'(总是适配)
+        //   direction: 'both', // 适配方向:'x'、'y' 或 'both'
+        // },
+        // animation: {
+        //   // 自适应动画效果
+        //   duration: 1000, // 动画持续时间(毫秒)
+        //   easing: 'ease-in-out', // 动画缓动函数
+        // },
+      },
+      autoResize: true, // 自动调整大小
+      behaviors: [
+        'drag-canvas',
+        {
+          type: 'zoom-canvas',
+          sensitivity: 0.5, // 配置灵敏度
+          key: 'zoom-behavior', // 为交互指定key,便于后续更新
+        },
+        'focus-element',
+        {
+          type: 'click-select',
+          state: 'selected',
+          unselectedState: 'inactive',
+          multiple: true,
+          trigger: ['shift'],
+        },
+      ],
+    });
+
+    // 渲染
+    graph.render();
+
+    // 监听节点点击事件
+    graph.on(NodeEvent.CLICK, (evt) => {
+      const { target } = evt;
+      const nodeData = graph.getNodeData(target.id); // 获取节点数据
+      emits('node-click', nodeData);
+    });
+
+    graph.on(CanvasEvent.CLICK, () => {
+      graph?.fitCenter();
+      emits('canvas-click');
+    });
+
+    window.addEventListener('resize', handleResize);
+
+    pending = false;
+  };
+
+  // 处理窗口大小变化
+  const handleResize = () => {
+    if (graph && container.value) {
+      graph.resize(container.value.offsetWidth, container.value.offsetHeight);
+      graph.fitCenter();
+    }
+  };
+
+  // 监听 treeData 变化
+  watch(
+    () => props.treeData,
+    () => {
+      data = treeToGraphData(props.treeData);
+      initGraph();
+    },
+    { deep: true },
+  );
+
+  // 生命周期钩子
+  onMounted(() => {
+    data = treeToGraphData(props.treeData);
+    initGraph();
+  });
+
+  onBeforeUnmount(() => {
+    window.removeEventListener('resize', handleResize);
+    if (graph) {
+      graph.off(); // 移除所有事件监听
+      graph.destroy();
+      graph = null;
+    }
+  });
+</script>
+
+<style lang="scss" scoped>
+  .org-chart-container {
+    width: 100%;
+    height: 100%;
+  }
+</style>

+ 2 - 1
src/views/production-safety/productionSafetySystem/safetyOrganizationSystemManagement/configs/tables.ts

@@ -2,7 +2,7 @@
  * @Author: liuJie
  * @Date: 2026-01-27 16:29:28
  * @LastEditors: liuJie
- * @LastEditTime: 2026-03-07 20:34:07
+ * @LastEditTime: 2026-03-30 10:16:11
  * @Describe: file describe
  */
 import type { TableColumnProps } from '@/types/basic-table';
@@ -52,6 +52,7 @@ export const TABLE_COLUMNS: TableColumnProps[] = [
     prop: 'jobResp',
     align: 'left',
     minWidth: '150px',
+    showOverflowTooltip: true,
   },
   {
     label: '添加时间',

+ 285 - 352
src/views/production-safety/productionSafetySystem/safetyOrganizationSystemManagement/safetyOrganizationSystemManagement.vue

@@ -8,63 +8,25 @@
         <el-button type="primary" :icon="Plus" @click="addTeam('parent')"> 添加组织 </el-button>
         
         <div class="collapse-wrapper">
-            <!-- expand-icon-position="left" 版本小了,不支持 -->
-          <el-collapse accordion v-model="activeName" v-if="fetchSafetyOrganizationList.length > 0">
-            <el-collapse-item :name="item.orgId" v-for="item in fetchSafetyOrganizationList">
-              <template #title="{ isActive }">
-                <div :class="['title-wrapper', { 'is-active': isActive }]">
-                  <span @click.stop="querySafetyTeamData('parent', item)">
-                    {{ item.orgName }}
-                  </span>
-                  <div class="handler">
-                    <el-button :icon="Plus" size="small" type="default" dashed @click.stop="handleCreateSafetySystem('children', item)"
-                      ></el-button
-                    >
-                    <el-button size="small" type="primary" link @click.stop="handleEditSafetySystem('parent', item, '')"
-                      >编辑</el-button
-                    >
-                    <el-button  size="small" type="danger" link @click.stop="handleDelSafetySystem('parent', item)"
-                      >删除</el-button
-                    >
-                  </div>
-                </div>
-              </template>
-              <div class="collapse-item-content">
-                <div v-if="item.children.length > 0">
-                  <ul>
-                    <li class="flex" v-for="children in item.children" :key="children.orgId">
-                      <span @click="querySafetyTeamData('children', children)">
-                        {{ children.orgName }}
-                      </span>
-                      <div class="handler">
-                        <el-button
-                          link
-                          size="small"
-                          type="primary"
-                          @click.stop="handleEditSafetySystem('children', children, item.orgId)"
-                          >编辑</el-button
-                        >
-                        <el-button
-                          link
-                          size="small"
-                          type="danger"
-                          @click.stop="handleDelSafetySystem('children', children)"
-                          >删除</el-button
-                        >
-                      </div>
-                    </li>
-                  </ul>
-                </div>
-                <div v-else>
-                  <el-empty description="未添加子组织" :image-size="40" />
-                </div>
-              </div>
-            </el-collapse-item>
-          </el-collapse>
+            <!-- 组织树 -->
+            <el-collapse v-model="activeName" accordion v-if="fetchSafetyOrganizationList.length > 0">
+                <CollapseItem
+                    v-for="item in fetchSafetyOrganizationList"
+                    :key="item.id"
+                    :data="item"
+                    :level="level"
+                    @click-node="querySafetyTeamData"
+                    @create-node="handleCreateSafetySystem"
+                    @edit-node="handleEditSafetySystem"
+                    @delete-node="handleDelSafetySystem"
+                />
+            </el-collapse>
+           
           <div v-else>
             <el-empty description="未添加组织" />
           </div>
         </div>
+        <!-- 添加、编辑组织弹窗 -->
         <AddSafetySystem
           v-model:visible="addSafetySystemVisible"
           :data="addSafetyOrganizationSystemFormData"
@@ -72,83 +34,15 @@
         />
       </div>
       <div class="search-table-container table-content">
-        <div style="margin-bottom:20px">
-            <el-button type="primary" :icon="Plus"  @click="handleCreate"> 添加 </el-button>
-            <el-button plain  @click="handleImport">导入</el-button>
-            <!-- <el-button plain  @click="handlerEdit">架构</el-button> -->
-        </div>
-        <header>
-          <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"
-                />
-              </div>
-              <div class="select-box--item">
-                <span>状态:</span>
-                <el-select v-model="tableQuery.queryParam.status" placeholder="请选择状态" clearable>
-                  <el-option label="启用" :value="1" />
-                  <el-option label="禁用" :value="2" />
-                </el-select>
-              </div>
-              <div class="select-box--item">
-                <span>日期范围:</span>
-                <el-date-picker
-                  v-model="dateRange"
-                  @change="onchangeDateRange"
-                  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>
-                {{ scope.row.status === 1 ? '启用' : scope.row.status === 2 ? '禁用' : '-' }}
-              </span>
-            </template>
-            <template #action="scope">
-              <div class="action-container--div" style="justify-content: left">
-                <ActionButton text="编辑" @click="handleEdit(scope.row.id)" />
-                <ActionButton
-                  text="删除"
-                  :popconfirm="{
-                    title: '确定要删除?',
-                  }"
-                  @confirm="handleDelete(scope.row.id)"
-                />
-                <ActionButton text="查看" @click="handleView(scope.row.id)" />
-              </div>
-            </template>
-          </BasicTable>
+        <!-- 架构图 -->
+        <OrgChart :treeData="treeData" @node-click="handleNodeClick" />
+        <TeamDetailDrawer ref="teamDetailDrawerRef" :selected-team-id="selectedTeamId" />
+        <div class="text-right mb-4">
+            <el-button  @click="toStaff"> 编辑 </el-button>
         </div>
-        <!-- <OrgChart :treeData="treeData" @node-click="handleNodeClick" /> -->
       </div>
     </main>
-    <BatchImport
+    <!-- <BatchImport
       v-if="batchImportVisible"
       :visible="batchImportVisible"
       :import-api-url="importApiUrl"
@@ -157,16 +51,15 @@
       :show-template="true"
       @close="batchImportVisible = false"
       @update="handleUpdate"
-    />
+    /> -->
   </div>
 </template>
 
 <script setup lang="ts">
-  import { onMounted, reactive, ref } from 'vue';
+  import { onMounted, reactive, ref, defineComponent } from 'vue';
   import { ElMessage, ElMessageBox } from 'element-plus';
   import BasicTable from '@/components/BasicTable.vue';
   import useTableConfig from '@/hooks/useTableConfigHook';
-  import ActionButton from '@/components/ActionButton.vue';
   import { TABLE_OPTIONS, TABLE_COLUMNS } from './configs/tables';
   import { useRouter } from 'vue-router';
   import type { QueryPageRequest } from '@/types/basic-query';
@@ -179,22 +72,24 @@
     delEmployee,
     exportSafetyOrganizationSystemManagement
   } from '@/api/safety-organization-management';
-  import { downloadByData } from '@/utils/file/download';
-  import BatchImport from '@/components/batch-import/BatchImport.vue';
-  import { useGlobSetting } from '@/hooks/setting';
-  import urlJoin from 'url-join';
+//   import { downloadByData } from '@/utils/file/download';
+//   import BatchImport from '@/components/batch-import/BatchImport.vue';
+//   import { useGlobSetting } from '@/hooks/setting';
+//   import urlJoin from 'url-join';
   import AddSafetySystem from './components/addSafetySystem.vue';
   import {
   Delete,
   Edit,
   Plus,
 } from '@element-plus/icons-vue'
-  import OrgChart from '@/views/emergency/components/OrgChart.vue';
+  import OrgChart from './components/orgChart.vue';
+  import CollapseItem from './components/collapseItem.vue'
+  import TeamDetailDrawer from './components/TeamDetailDrawer.vue';
 const position = ref('left')
 
   const router = useRouter();
   // 表格
-  const basicTableRef = ref<InstanceType<typeof BasicTable>>();
+//   const basicTableRef = ref<InstanceType<typeof BasicTable>>();
 
   const { tableConfig, pagination } = useTableConfig(TABLE_COLUMNS, TABLE_OPTIONS);
 
@@ -203,6 +98,8 @@ const position = ref('left')
   const fetchSafetyOrganizationList = ref<any[]>([]);
 
   const activeName = ref('');
+
+  const level = ref(1)
   // 日期范围(用于日期选择器)
   const dateRange = ref<[string, string] | string>('');
 
@@ -226,58 +123,48 @@ const position = ref('left')
 
   const treeData = ref<OrganizationTreeType>({
     id: 'root',
-    data: { name: '应急领导小组' },
-    children: [
-      {
-        id: 'group1',
-        data: { name: '应急指挥小组' },
-      },
-      {
-        id: 'group2',
-        data: { name: '应急响应小组' },
-      },
-      {
-        id: 'group3',
-        data: { name: '应急支援小组' },
-      },
-    ],
+    data: { name: '请添加组织' },
+    children: [],
   });
-
-  const handlerEdit = ()=>{
-    router.push({name: 'securityOrganizationalStructure'})
-  }
-  const handleSizeChange = (value: number) => {
-    pagination.pageSize = value;
-    tableQuery.pageSize = value;
-    getTableData();
-  };
-
-  const handleCurrentChange = (value: number) => {
-    pagination.pageNumber = value;
-    tableQuery.pageNumber = value;
-    getTableData();
-  };
+  
+//   const handlerEdit = ()=>{
+//     router.push({name: 'securityOrganizationalStructure'})
+//   }
+//   const handleSizeChange = (value: number) => {
+//     pagination.pageSize = value;
+//     tableQuery.pageSize = value;
+//     getTableData();
+//   };
+
+//   const handleCurrentChange = (value: number) => {
+//     pagination.pageNumber = value;
+//     tableQuery.pageNumber = value;
+//     getTableData();
+//   };
+
+  const teamDetailDrawerRef = ref<InstanceType<typeof TeamDetailDrawer>>();
+  const selectedTeamId = ref<number | null>(null);
   const handleNodeClick = (nodeData: any) => {
-    // selectedTeamId.value = Number(nodeData.id);
+    selectedTeamId.value = Number(nodeData.id);
 
-    // teamDetailDrawerRef.value?.drawerShow();
+    teamDetailDrawerRef.value?.drawerShow();
   };
-  async function getTableData() {
-    tableConfig.loading = true;
-    try {
-      const res = await fetchTableList(tableQuery);
-      if (res) {
-        tableData.value = res.records
-        pagination.total = res.totalRow;
-      }
-    } catch (e) {
-      console.error('获取列表失败:', e);
-      tableData.value = [];
-      pagination.total = 0;
-    } finally {
-      tableConfig.loading = false;
-    }
-  }
+//   async function getTableData() {
+//     tableConfig.loading = true;
+//     try {
+//       const res = await fetchTableList(tableQuery);
+//       if (res) {
+//         tableData.value = res.records
+//         pagination.total = res.totalRow;
+//       }
+//     } catch (e) {
+//       console.error('获取列表失败:', e);
+//       tableData.value = [];
+//       pagination.total = 0;
+//     } finally {
+//       tableConfig.loading = false;
+//     }
+//   }
 
   interface addSafetyOrganizationSystemFormDataType {
     type: String;
@@ -295,99 +182,88 @@ const position = ref('left')
     action: '',
     parentid: '',
   });
-  // 一级新增
-  const addTeam = (type) => {
-    addSafetyOrganizationSystemFormData.value = {
-      type,
-      action: 'add',
+
+/**
+ * 递归给树形结构添加 id 、data 、children 字段
+ * @param {Array} tree 原始树形数组
+ * @returns 格式化后的标准树结构
+ */
+const formatTreeData = (tree)=> {
+  if (!tree || !Array.isArray(tree)) return [];
+
+  return tree.map(item => {
+    // 给每一层节点都加上 id 和 data
+    const formattedItem = {
+      children: item.children || [],
+      id: `org-${item.orgId}`, 
+      data: {
+        name: item.orgName
+      }
     };
-    addSafetySystemVisible.value = true;
-  };
 
-// id: 'root',
-//     data: { name: '应急领导小组' },
-//     children: [
-//       {
-//         id: 'group1',
-//         data: { name: '应急指挥小组' },
-//       },
-//       {
-//         id: 'group2',
-//         data: { name: '应急响应小组' },
-//       },
-//       {
-//         id: 'group3',
-//         data: { name: '应急支援小组' },
-//       },
-//     ],
+    // 递归处理子节点
+    if (formattedItem.children && formattedItem.children.length > 0) {
+      formattedItem.children = formatTreeData(formattedItem.children);
+    }
+
+    return formattedItem;
+  });
+}
+  function convertData(leaderTeams): OrganizationTreeType {
+    return {
+      id: `org-${leaderTeams.orgId}`, 
+      data: {
+        name: leaderTeams.orgName
+      },
+      children: leaderTeams.children?.map((child) => convertData(child)),
+    };
+  }
   // 获取组织列表
   const fetchSafetyOrganizationTeamList = async () => {
     try {
       const res = await getSafetySystemList();
       fetchSafetyOrganizationList.value = res;
-    //   treeData.value = res.map(li=>({id:li.orgId,data:{name: li.orgName}}))
+      // 默认选择第一个组织
+      if(res[0].orgId){
+        treeNodePreview(res[0])
+        activeName.value = res[0].orgId
+        tableQuery.queryParam.classifyName = res[0].orgId
+      }
+
     } catch (error) {
       ElMessage.error('获取组织列表失败');
     }
   };
-  // 定义组织数据类型
-  interface SafetySystemFormData {
-    value: string; // 输入的组织名称
-    action: 'add' | 'edit'; // 操作类型:新增或编辑
-    orgId?: string | number; // 组织ID(编辑时必传)
-    parentid?: string | number; // 父组织ID(新增子组织时必传)
-    type?: 'children' | 'parent'; // 组织类型(子组织或根组织)
+  // 给架构图赋值
+  const treeNodePreview = (data)=>{
+    let TreeNode = convertData(data)
+    treeData.value = TreeNode
   }
 
-  // 保存弹窗回调
-  const confirmAddSafetySystemCallback = async (formData: SafetySystemFormData) => {
-    try {
-      if (!formData.value?.trim()) {
-        ElMessage.warning('请输入有效的组织名称!');
-        return;
-      }
-      const requestData = {
-        orgName: formData.value.trim(),
-        id: formData.action === 'edit' ? formData.orgId : undefined,
-        parentid: formData.action === 'add' ? formData.parentid : undefined,
-      };
-      // console.log(formData, 'formData')
-      if (formData.action === 'add') {
-        if (formData.type === 'children' && formData.orgId) {
-          requestData.parentid = formData.orgId;
-        }
-        await addSafetySystem(requestData);
-        ElMessage.success('新增组织成功!');
-      } else {
-        // 如果是子类,补充父级ID
-        if (formData.type === 'children' && formData.parentid) {
-          requestData.parentid = formData.parentid;
-        }
-        await updateSafetySystem(requestData);
-        ElMessage.success('编辑组织成功!');
-      }
-      // 刷新列表
-      fetchSafetyOrganizationTeamList();
-    } catch (error) {
-      console.error('操作失败:', error);
-      ElMessage.error(formData.action === 'add' ? '新增组织失败!' : '编辑组织失败!');
-    }
+  // 一级新增
+  const addTeam = (type) => {
+    addSafetyOrganizationSystemFormData.value = {
+      type,
+      action: 'add',
+    };
+    addSafetySystemVisible.value = true;
   };
-  // 级新增
+  // 子级新增
   const handleCreateSafetySystem = async (type, value) => {
+    // console.log('新增参数--',type, value)
     addSafetyOrganizationSystemFormData.value = {
-      type,
+      type:'children',
       action: 'add',
       orgName: value.orgName,
       orgId: value.orgId,
     };
     addSafetySystemVisible.value = true;
     // 打开某一个
-    activeName.value = value.orgId;
+    // activeName.value = value.orgId;
   };
   // 编辑
   const handleEditSafetySystem = (type, value, parentid) => {
-    // console.log(value)
+    // console.log('编辑参数--', type, value, parentid)
     addSafetySystemVisible.value = true;
     addSafetyOrganizationSystemFormData.value = {
       type,
@@ -397,130 +273,183 @@ const position = ref('left')
       parentid,
     };
   };
+
   // 删除
   const handleDelSafetySystem = async (type, value) => {
     // console.log('删除', type, value)
     ElMessageBox.confirm('确认删除该组织吗?', '警告', { type: 'warning' }).then(async () => {
       try {
+        if(value.children.length > 0){
+            ElMessage.error('当前一级组织存在子级数据,无法删除,请先删除子级组织')
+            return 
+        }
         await deleteSafetySystem(value.orgId);
-
         ElMessage.success('删除成功');
-        // 刷新列表
+        // 刷新组织列表
         fetchSafetyOrganizationTeamList();
-        handleReset();
       } catch (error) {
         ElMessage.error(error || '删除失败');
       }
     });
   };
   // 查询
-  const querySafetyTeamData = (type, value) => {
-    console.log(type, '查询', value);
+  const querySafetyTeamData = (value) => {
+    // console.log('查询', value);
     tableQuery.queryParam.classifyName = value.orgId;
-    getTableData();
-  };
-  // 时间查询
-  const onchangeDateRange = () => {
-    if (dateRange.value && Array.isArray(dateRange.value) && dateRange.value.length === 2) {
-      tableQuery.queryParam.startTime = dateRange.value[0] || '';
-      tableQuery.queryParam.endTime = dateRange.value[1] || '';
-    } else {
-      tableQuery.queryParam.startTime = '';
-      tableQuery.queryParam.endTime = '';
-    }
-    getTableData();
-  };
-  const handleSearch = () => {
-    pagination.pageNumber = 1;
-    tableQuery.pageNumber = 1;
-    getTableData();
-  };
-
-  const handleReset = () => {
-    pagination.pageNumber = 1;
-    tableQuery.queryParam = {
-      classifyName: '',
-      keyword: '',
-      status: '', // 重置为默认启用状态
-      startTime: '',
-      endTime: '',
-    };
-    dateRange.value = '';
-    handleSearch();
-  };
-
-  // 批量导入
-  const batchImportVisible = ref(false);
-  const { urlPrefix } = useGlobSetting();
-  const importApiUrl = ref(urlJoin(urlPrefix, '/safetyorguser/importSafetyOrgUser'));
-  const templateUrl = ref('./skyeye-file-upload/sfysecurity/TEMPLATE/安全组织体系管理导入模版.xlsx');
-
-  const handleImport = () => {
-    batchImportVisible.value = true;
+    treeNodePreview(value)
   };
 
-  const handleUpdate = () => {
-    batchImportVisible.value = false;
-    getTableData();
-  };
-
-  const handleDownload = async () => {
+    // 定义组织数据类型
+  interface SafetySystemFormData {
+    value: string; // 输入的组织名称
+    action: 'add' | 'edit'; // 操作类型:新增或编辑
+    orgId?: string | number; // 组织ID(编辑时必传)
+    parentid?: string | number; // 父组织ID(新增子组织时必传)
+    type?: 'children' | 'parent'; // 组织类型(子组织或根组织)
+  }
+   // 保存弹窗回调
+  const confirmAddSafetySystemCallback = async (formData: SafetySystemFormData) => {
     try {
-      const response = await exportSafetyOrganizationSystemManagement(tableQuery.queryParam);
-      if (response) {
-        const fileName = `安全组织体系管理_${new Date().toISOString().split('T')[0]}.xlsx`;
-        downloadByData(response, fileName);
-        ElMessage.success('导出成功');
+      if (!formData.value?.trim()) {
+        ElMessage.warning('请输入有效的组织名称!');
+        return;
+      }
+      // 新增时,传orgName(组织名称)、parentid(父组织ID)
+      // 编辑时,传当前ID和orgName
+      const requestData = {
+        orgName: formData.value.trim(),
+        id: formData.action === 'edit' ? formData.orgId : undefined,
+        // 第一级不需要传parentid
+        parentid: formData.action === 'add' ? formData.parentid : undefined,
+      };
+      console.log(formData, '参数--formData')
+      if (formData.action === 'add') {
+        if (formData.type === 'children' && formData.orgId) {
+          requestData.parentid = formData.orgId;
+        }
+        await addSafetySystem(requestData);
+        ElMessage.success('新增组织成功!');
+      } else {
+        // 如果是子类,补充父级ID
+        if (formData.type === 'children' && formData.parentid) {
+          requestData.parentid = formData.parentid;
+        }
+        await updateSafetySystem(requestData);
+        ElMessage.success('编辑组织成功!');
       }
-    } catch (e) {
-      console.error('导出安全组织体系管理失败:', e);
-      ElMessage.error('导出失败,请重试');
+      // 刷新列表
+      fetchSafetyOrganizationTeamList();
+    } catch (error) {
+      console.error('操作失败:', error);
+      ElMessage.error(formData.action === 'add' ? '新增组织失败!' : '编辑组织失败!');
     }
   };
 
-  const handleCreate = () => {
-    router.push({
-      name: 'SafetyOrganizationSystemManagementItem',
-      query: {
-        operate: 'employee-create',
-      },
-    });
-  };
-
-  const handleEdit = (id: number) => {
-    router.push({
-      name: 'SafetyOrganizationSystemManagementItem',
-      query: {
-        id,
-        operate: 'employee-edit',
-      },
-    });
-  };
-
-  const handleDelete = async (id: number) => {
-    try {
-      await delEmployee(id);
-      ElMessage.success('删除成功');
-      getTableData();
-    } catch (e) {
-      console.error('删除员工失败:', e);
-      ElMessage.error('删除失败,请重试');
-    }
-  };
+  const toStaff = ()=>{
+    router.push({name: 'securityOrganizationalStructure', query: {id: tableQuery.queryParam.classifyName}})
+  }
+  // 时间查询
+//   const onchangeDateRange = () => {
+//     if (dateRange.value && Array.isArray(dateRange.value) && dateRange.value.length === 2) {
+//       tableQuery.queryParam.startTime = dateRange.value[0] || '';
+//       tableQuery.queryParam.endTime = dateRange.value[1] || '';
+//     } else {
+//       tableQuery.queryParam.startTime = '';
+//       tableQuery.queryParam.endTime = '';
+//     }
+//     getTableData();
+//   };
+//   const handleSearch = () => {
+//     pagination.pageNumber = 1;
+//     tableQuery.pageNumber = 1;
+//     getTableData();
+//   };
+
+//   const handleReset = () => {
+//     pagination.pageNumber = 1;
+//     tableQuery.queryParam = {
+//       classifyName: '',
+//       keyword: '',
+//       status: '', // 重置为默认启用状态
+//       startTime: '',
+//       endTime: '',
+//     };
+//     dateRange.value = '';
+//     handleSearch();
+//   };
 
-  const handleView = (id: number) => {
-    router.push({
-      name: 'SafetyOrganizationSystemManagementItem',
-      query: {
-        id,
-        operate: 'employee-view',
-      },
-    });
-  };
+  // 批量导入
+//   const batchImportVisible = ref(false);
+//   const { urlPrefix } = useGlobSetting();
+//   const importApiUrl = ref(urlJoin(urlPrefix, '/safetyorguser/importSafetyOrgUser'));
+//   const templateUrl = ref('./skyeye-file-upload/sfysecurity/TEMPLATE/安全组织体系管理导入模版.xlsx');
+
+//   const handleImport = () => {
+//     batchImportVisible.value = true;
+//   };
+
+//   const handleUpdate = () => {
+//     batchImportVisible.value = false;
+//     getTableData();
+//   };
+
+//   const handleDownload = async () => {
+//     try {
+//       const response = await exportSafetyOrganizationSystemManagement(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('导出失败,请重试');
+//     }
+//   };
+
+//   const handleCreate = () => {
+//     router.push({
+//       name: 'SafetyOrganizationSystemManagementItem',
+//       query: {
+//         operate: 'employee-create',
+//       },
+//     });
+//   };
+
+//   const handleEdit = (id: number) => {
+//     router.push({
+//       name: 'SafetyOrganizationSystemManagementItem',
+//       query: {
+//         id,
+//         operate: 'employee-edit',
+//       },
+//     });
+//   };
+
+//   const handleDelete = async (id: number) => {
+//     try {
+//       await delEmployee(id);
+//       ElMessage.success('删除成功');
+//       getTableData();
+//     } catch (e) {
+//       console.error('删除员工失败:', e);
+//       ElMessage.error('删除失败,请重试');
+//     }
+//   };
+
+//   const handleView = (id: number) => {
+//     router.push({
+//       name: 'SafetyOrganizationSystemManagementItem',
+//       query: {
+//         id,
+//         operate: 'employee-view',
+//       },
+//     });
+//   };
 
   onMounted(() => {
     fetchSafetyOrganizationTeamList();
-    getTableData();
+    // getTableData();
   });
 </script>
 
@@ -529,8 +458,12 @@ const position = ref('left')
   @use '@/styles/page-main-layout.scss' as *;
   @use '@/styles/basic-table-action.scss' as *;
   @use '@/views/traffic/violation/style/act-search-table.scss' as *;
-  .table-content {
-  }
+ .text-right{
+    text-align: right;
+ }
+ .mb-4{
+    margin-bottom: 12px;
+ }
   .nav {
     flex: 0 0 300px;
     margin-right: 15px;

+ 7 - 1
src/views/production-safety/productionSafetySystem/securityOrganizationalStructure/components/safetyOrganizationSystemManagementDetail.vue

@@ -29,12 +29,16 @@
         </el-tree-select>
       </el-form-item>
 
+      <el-form-item label="岗位名称:" prop="jobName" required>
+        <el-input v-model="form.jobName" placeholder="请输入岗位名称" :disabled="isViewMode" />
+      </el-form-item>
+
       <el-form-item label="岗位职责:" prop="jobResp" required>
         <el-input
           v-model="form.jobResp"
           type="textarea"
           :rows="4"
-          :maxlength="300"
+          :maxlength="200"
           placeholder="请填写岗位职责"
           :disabled="isViewMode"
         />
@@ -85,6 +89,7 @@
     employeeId: string;
     employeeName: string;
     jobResp: string;
+    jobName: string;
     status: string|number;
     selectedOrg?: any;
   }
@@ -94,6 +99,7 @@
     orgId: '',
     employeeId: '',
     employeeName: '',
+    jobName: '',
     jobResp: '',
     status: 1,
   });

+ 8 - 1
src/views/production-safety/productionSafetySystem/securityOrganizationalStructure/configs/form.ts

@@ -2,7 +2,7 @@
  * @Author: liuJie
  * @Date: 2026-01-27 16:29:28
  * @LastEditors: liuJie
- * @LastEditTime: 2026-02-06 13:35:38
+ * @LastEditTime: 2026-03-30 11:32:06
  * @Describe: file describe
  */
 import { FormConfig } from '@/types/basic-form';
@@ -68,9 +68,16 @@ export const FORM_RULES = {
     { min: 1, max: 10, message: '长度在 1 到 10 个字符', trigger: 'blur' },
   ],
   orgId: [{ required: true, message: '请选择组织名称', trigger: 'change' }],
+  jobName: [{ required: true, message: '请输入岗位名称', trigger: 'blur' }],
   jobResp: [
     { required: true, message: '请填写岗位职责', trigger: 'blur' },
     { min: 1, max: 300, message: '最大字数300字', trigger: 'blur' },
   ],
   status: [{ required: true, message: '请选择状态', trigger: 'blur' }],
 };
+
+
+export const SafetyOrgUserRules = {
+    userNum: [{ required: true, message: '请输入组织人数', trigger: 'blur' }],
+    depResp: [{ required: true, message: '请填写组织职责', trigger: 'blur' }]
+}

+ 10 - 2
src/views/production-safety/productionSafetySystem/securityOrganizationalStructure/configs/tables.ts

@@ -2,7 +2,7 @@
  * @Author: liuJie
  * @Date: 2026-01-27 16:29:28
  * @LastEditors: liuJie
- * @LastEditTime: 2026-03-07 20:34:07
+ * @LastEditTime: 2026-03-30 13:42:36
  * @Describe: file describe
  */
 import type { TableColumnProps } from '@/types/basic-table';
@@ -38,7 +38,7 @@ export const TABLE_COLUMNS: TableColumnProps[] = [
     prop: 'status',
     slot: 'status',
     align: 'left',
-    minWidth: '120px',
+    minWidth: '90px',
   },
 
   {
@@ -46,12 +46,20 @@ export const TABLE_COLUMNS: TableColumnProps[] = [
     prop: 'orgName',
     align: 'left',
     minWidth: '140px',
+  },
+    {
+    label: '岗位名称',
+    prop: 'jobName',
+    align: 'left',
+    minWidth: '150px',
+    showOverflowTooltip: true,
   },
   {
     label: '岗位职责',
     prop: 'jobResp',
     align: 'left',
     minWidth: '150px',
+    showOverflowTooltip: true,
   },
   {
     label: '添加时间',

+ 104 - 69
src/views/production-safety/productionSafetySystem/securityOrganizationalStructure/securityOrganizationalStructure.vue

@@ -3,68 +3,30 @@
     <header class="safety-platform-container__header">
       <div class="breadcrumb-title"> 安全组织体系架构 </div>
     </header>
-    <main class="safety-platform-container__main flex">
+    <main class="safety-platform-container__main flex platform-main">
       <div class="nav">
         <el-button type="primary" :icon="Plus" @click="addTeam('parent')"> 添加组织 </el-button>
         
         <div class="collapse-wrapper">
-            <!-- expand-icon-position="left" 版本小了,不支持 -->
-          <el-collapse accordion v-model="activeName" v-if="fetchSafetyOrganizationList.length > 0">
-            <el-collapse-item :name="item.orgId" v-for="item in fetchSafetyOrganizationList">
-              <template #title="{ isActive }">
-                <div :class="['title-wrapper', { 'is-active': isActive }]">
-                  <span @click.stop="querySafetyTeamData('parent', item)">
-                    {{ item.orgName }}
-                  </span>
-                  <div class="handler">
-                    <el-button :icon="Plus" size="small" type="default" dashed @click.stop="handleCreateSafetySystem('children', item)"
-                      ></el-button
-                    >
-                    <el-button size="small" type="primary" link @click.stop="handleEditSafetySystem('parent', item, '')"
-                      >编辑</el-button
-                    >
-                    <el-button  size="small" type="danger" link @click.stop="handleDelSafetySystem('parent', item)"
-                      >删除</el-button
-                    >
-                  </div>
-                </div>
-              </template>
-              <div class="collapse-item-content">
-                <div v-if="item.children.length > 0">
-                  <ul>
-                    <li class="flex" v-for="children in item.children" :key="children.orgId">
-                      <span @click="querySafetyTeamData('children', children)">
-                        {{ children.orgName }}
-                      </span>
-                      <div class="handler">
-                        <el-button
-                          link
-                          size="small"
-                          type="primary"
-                          @click.stop="handleEditSafetySystem('children', children, item.orgId)"
-                          >编辑</el-button
-                        >
-                        <el-button
-                          link
-                          size="small"
-                          type="danger"
-                          @click.stop="handleDelSafetySystem('children', children)"
-                          >删除</el-button
-                        >
-                      </div>
-                    </li>
-                  </ul>
-                </div>
-                <div v-else>
-                  <el-empty description="未添加子组织" :image-size="40" />
-                </div>
-              </div>
-            </el-collapse-item>
-          </el-collapse>
+            <!-- 组织树 -->
+            <el-collapse v-model="activeName" accordion v-if="fetchSafetyOrganizationList.length > 0">
+                <CollapseItem
+                    v-for="item in fetchSafetyOrganizationList"
+                    :key="item.id"
+                    :data="item"
+                    :level="level"
+                    @click-node="querySafetyTeamData"
+                    @create-node="handleCreateSafetySystem"
+                    @edit-node="handleEditSafetySystem"
+                    @delete-node="handleDelSafetySystem"
+                />
+            </el-collapse>
+           
           <div v-else>
             <el-empty description="未添加组织" />
           </div>
         </div>
+        <!-- 添加、编辑组织弹窗 -->
         <AddSafetySystem
           v-model:visible="addSafetySystemVisible"
           :data="addSafetyOrganizationSystemFormData"
@@ -72,7 +34,22 @@
         />
       </div>
       <div class="search-table-container table-content">
-        <div style="margin-bottom:20px">
+        <div>
+            <p class="label-title">组织信息</p>
+            <el-form :model="safetyOrgUser" ref="formRef" :rules="safetyOrgUserRules">
+                <el-form-item label="组织人数" prop="userNum">
+                    <el-input placeholder="请输入组织人数" type="number" v-model="safetyOrgUser.userNum" style="width:450px" />
+                </el-form-item>
+                <el-form-item label="组织职责" prop="depResp">
+                    <el-input placeholder="请填写组织职责" v-model="safetyOrgUser.depResp"  :autosize="{ minRows: 2, maxRows: 6 }" :maxlength="300" show-word-limit type="textarea" style="width:450px" />
+                </el-form-item>
+                <el-form-item>
+                    <el-button type="primary" @click="handleSave"> 保存 </el-button>
+                </el-form-item>
+            </el-form>
+        </div>
+        <div>
+            <p class="label-title">人员信息</p>
             <el-button type="primary" :icon="Plus"  @click="handleCreate"> 添加 </el-button>
             <el-button plain  @click="handleImport">导入</el-button>
         </div>
@@ -166,7 +143,7 @@
   import useTableConfig from '@/hooks/useTableConfigHook';
   import ActionButton from '@/components/ActionButton.vue';
   import { TABLE_OPTIONS, TABLE_COLUMNS } from './configs/tables';
-  import { useRouter } from 'vue-router';
+  import { useRouter, useRoute } from 'vue-router';
   import type { QueryPageRequest } from '@/types/basic-query';
   import {
     getSafetySystemList,
@@ -175,6 +152,8 @@
     deleteSafetySystem,
     fetchTableList,
     delEmployee,
+    safetyOrgUserSave,
+    safetyOrgUserDetail,
     exportSafetyOrganizationSystemManagement
   } from '@/api/safety-organization-management';
   import { downloadByData } from '@/utils/file/download';
@@ -182,14 +161,15 @@
   import { useGlobSetting } from '@/hooks/setting';
   import urlJoin from 'url-join';
   import AddSafetySystem from './components/addSafetySystem.vue';
+  import {SafetyOrgUserRules} from "./configs/form"
   import {
   Delete,
   Edit,
   Plus,
 } from '@element-plus/icons-vue'
-
+import CollapseItem from '../safetyOrganizationSystemManagement/components/collapseItem.vue'
 const position = ref('left')
-
+  const route = useRoute();
   const router = useRouter();
   // 表格
   const basicTableRef = ref<InstanceType<typeof BasicTable>>();
@@ -203,7 +183,7 @@ const position = ref('left')
   const activeName = ref('');
   // 日期范围(用于日期选择器)
   const dateRange = ref<[string, string] | string>('');
-
+  const level = ref(1)
   const tableQuery = reactive<QueryPageRequest<any>>({
     pageNumber: pagination.pageNumber,
     pageSize: pagination.pageSize,
@@ -215,7 +195,41 @@ const position = ref('left')
       endTime: '',
     },
   });
-
+  const safetyOrgUser = reactive({
+    userNum: '',
+    depResp: ''
+  })
+  const formRef = ref()
+  const safetyOrgUserRules = ref(SafetyOrgUserRules)
+  // 校验员工数量和职责
+  const handleValidate = async () => {
+    if (!formRef.value) return;
+    const res = await formRef.value.validateField();
+    return res;
+  };
+  // 保存员工数量和职责
+  const handleSave = async ()=>{
+    const res = await handleValidate()
+    if (!res) return;
+    try {
+        await safetyOrgUserSave(safetyOrgUser)
+        ElMessage.success('保存成功');
+    } catch (error) {
+        ElMessage.error('保存失败');
+    }
+  }
+  // 查询组织详情
+  const safetyOrgDetail = async (id)=>{
+    try {
+        const res = await safetyOrgUserDetail(id)
+        Object.assign(safetyOrgUser, {
+            userNum: res.userNum,
+            depResp: res.depResp
+        })
+    } catch (error) {
+        ElMessage.error('获取详情失败');
+    }
+  }
   const handleSizeChange = (value: number) => {
     pagination.pageSize = value;
     tableQuery.pageSize = value;
@@ -324,14 +338,14 @@ const position = ref('left')
   // 二级新增
   const handleCreateSafetySystem = async (type, value) => {
     addSafetyOrganizationSystemFormData.value = {
-      type,
+      type:'children',
       action: 'add',
       orgName: value.orgName,
       orgId: value.orgId,
     };
     addSafetySystemVisible.value = true;
     // 打开某一个
-    activeName.value = value.orgId;
+    // activeName.value = value.orgId;
   };
   // 编辑
   const handleEditSafetySystem = (type, value, parentid) => {
@@ -350,8 +364,11 @@ const position = ref('left')
     // console.log('删除', type, value)
     ElMessageBox.confirm('确认删除该组织吗?', '警告', { type: 'warning' }).then(async () => {
       try {
+        if(value.children.length > 0){
+            ElMessage.error('当前一级组织存在子级数据,无法删除,请先删除子级组织')
+            return 
+        }
         await deleteSafetySystem(value.orgId);
-
         ElMessage.success('删除成功');
         // 刷新列表
         fetchSafetyOrganizationTeamList();
@@ -362,11 +379,14 @@ const position = ref('left')
     });
   };
   // 查询
-  const querySafetyTeamData = (type, value) => {
-    console.log(type, '查询', value);
+  const querySafetyTeamData = (value) => {
+    // console.log('查询', value);
     tableQuery.queryParam.classifyName = value.orgId;
+    safetyOrgDetail(value.orgId)
     getTableData();
   };
+
+
   // 时间查询
   const onchangeDateRange = () => {
     if (dateRange.value && Array.isArray(dateRange.value) && dateRange.value.length === 2) {
@@ -428,7 +448,7 @@ const position = ref('left')
 
   const handleCreate = () => {
     router.push({
-      name: 'SafetyOrganizationSystemManagementItem',
+      name: 'SecurityOrganizationalStructureItem',
       query: {
         operate: 'employee-create',
       },
@@ -437,7 +457,7 @@ const position = ref('left')
 
   const handleEdit = (id: number) => {
     router.push({
-      name: 'SafetyOrganizationSystemManagementItem',
+      name: 'SecurityOrganizationalStructureItem',
       query: {
         id,
         operate: 'employee-edit',
@@ -458,17 +478,22 @@ const position = ref('left')
 
   const handleView = (id: number) => {
     router.push({
-      name: 'SafetyOrganizationSystemManagementItem',
+      name: 'SecurityOrganizationalStructureItem',
       query: {
         id,
         operate: 'employee-view',
       },
     });
   };
-
   onMounted(() => {
     fetchSafetyOrganizationTeamList();
+    // 默认读取上个选中架构组织的员工数据
+    const orgId = route.query.id as string;
+    tableQuery.queryParam.classifyName = orgId || undefined
     getTableData();
+    if(orgId){
+        safetyOrgDetail(orgId)
+    }
   });
 </script>
 
@@ -477,13 +502,20 @@ const position = ref('left')
   @use '@/styles/page-main-layout.scss' as *;
   @use '@/styles/basic-table-action.scss' as *;
   @use '@/views/traffic/violation/style/act-search-table.scss' as *;
+  .platform-main {
+    width:100%;
+  }
   .table-content {
+    flex:1;
+    width: 0;
+    min-width: 0;
   }
   .nav {
     flex: 0 0 300px;
     margin-right: 15px;
     padding-right: 15px;
     border-right: 1px solid #eee;
+    overflow-y: auto;
     :deep(.collapse-title) {
         flex: 1 0 90%;
         order: 1;
@@ -530,4 +562,7 @@ const position = ref('left')
       }
     }
   }
+  .label-title{
+    margin-bottom:16px;
+  }
 </style>

src/views/production-safety/productionSafetySystem/securityOrganizationalStructure/securityOrganizationalStructureItme.vue → src/views/production-safety/productionSafetySystem/securityOrganizationalStructure/securityOrganizationalStructureItem.vue


+ 2 - 2
src/views/production-safety/risk-identification-and-control/labor-products-purchase-apply-manage-admin/components/detail.vue

@@ -171,10 +171,10 @@
             <template #default="{ row }">
               <!-- v-if="isViewMode || isAuditMode || isCreateMode" -->
               <!-- <span>{{ row.productNo || row.equipmentId || '-' }}</span> -->
-              <span v-if="isViewMode || isAuditMode">{{ row.equipmentId ?? '-' }}</span>
+              <span v-if="isViewMode || isAuditMode">{{ row.productNo ?? '-' }}</span>
               <el-input
                 v-else
-                v-model="row.equipmentId"
+                v-model="row.productNo"
                 placeholder="请输入..."
                 clearable
               />

+ 3 - 3
src/views/production-safety/risk-identification-and-control/labor-products-purchase-apply-manage/components/detail.vue

@@ -171,10 +171,10 @@
             <template #default="{ row }">
               <!-- v-if="isViewMode || isAuditMode || isCreateMode" -->
               <!-- <span>{{ row.productNo || row.equipmentId || '-' }}</span> -->
-              <span v-if="isViewMode || isAuditMode">{{ row.equipmentId ?? '-' }}</span>
+              <span v-if="isViewMode || isAuditMode">{{ row.productNo ?? '-' }}</span>
               <el-input
                 v-else
-                v-model="row.equipmentId"
+                v-model="row.productNo"
                 placeholder="请输入..."
                 clearable
               />
@@ -563,7 +563,7 @@
     if (found?.id) {
       row.ppeId = found.id;
       // row.equipmentId = found.id;
-      row.productNo = String(found.id);
+    //   row.productNo = String(found.id);
     }
   }
 

+ 4 - 4
src/views/production-safety/risk-identification-and-control/work-injury-apply-manage/listAdmin.vue

@@ -316,7 +316,7 @@
 
   const handleCreate = () => {
     router.push({
-      name: 'workInjuryApplyManageItem',
+      name: 'workInjuryApplyManageAdminItem',
       query: {
         operate: 'inventory-create',
       },
@@ -325,7 +325,7 @@
 
   const handleEdit = (id: number,row: any) => {
     router.push({
-      name: 'workInjuryApplyManageItem',
+      name: 'workInjuryApplyManageAdminItem',
       query: {
         id,
         rejectReason:row.rejectReason ?? '',
@@ -340,7 +340,7 @@
 
   const handleAudit = (id: number,row: any) => {
     router.push({
-      name: 'workInjuryApplyManageItem',
+      name: 'workInjuryApplyManageAdminItem',
       query: {
         id,
         approvalTemplateId:row.templateId ?? '',
@@ -363,7 +363,7 @@
 
   const handleView = (id: number) => {
     router.push({
-      name: 'workInjuryApplyManageItem',
+      name: 'workInjuryApplyManageAdminItem',
       query: {
         id,
         operate: 'inventory-view',

+ 86 - 0
src/views/production-safety/safetyTrainingAndEducation/employeeTrainingRecordCardManagement/configs/form.ts

@@ -13,3 +13,89 @@ export const EDIT_EMPLOYEE_FORM_RULES = {
   score: [{ required: true, message: '请输入成绩', trigger: 'blur' }],
   operationCertificateNum: [{ required: true, message: '请输入操作证号', trigger: 'blur' }],
 };
+
+const isValidYYYYMMDD = (value: string): boolean => {
+  const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
+  if (!match) return false;
+
+  const year = Number(match[1]);
+  const month = Number(match[2]); // 1-12
+  const day = Number(match[3]); // 1-31
+  if (month < 1 || month > 12) return false;
+  if (day < 1 || day > 31) return false;
+
+  // 利用 Date 校验闰年、每月天数
+  const date = new Date(year, month - 1, day);
+  return (
+    date.getFullYear() === year &&
+    date.getMonth() === month - 1 &&
+    date.getDate() === day
+  );
+};
+
+const isValidChinaIdCard = (value: string): boolean => {
+  const id = value.trim().toUpperCase();
+  // 15位:纯数字
+  if (/^\d{15}$/.test(id)) return true;
+
+  // 18位:前17位数字 + 校验位数字或X
+  if (!/^\d{17}[\dX]$/.test(id)) return false;
+
+  const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
+  const checkCodes = ['1', '0', 'X', '9', '8', '7', '6', '5', '4'];
+
+  let sum = 0;
+  for (let i = 0; i < 17; i++) {
+    sum += Number(id[i]) * weights[i];
+  }
+  const mod = sum % 11;
+  const expected = checkCodes[mod];
+  return id[17] === expected;
+};
+
+export const FORM_CARD_RULES = {
+  staffNo: [{ required: true, message: '请输入工号', trigger: 'blur' }],
+  deptIdForSelect: [{ required: true, message: '请选择所/部/中心', trigger: 'change' }],
+  staffName: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
+  staffBirthday: [
+    { required: true, message: '请输入出生年月', trigger: 'blur' },
+    {
+      validator: (_rule: any, value: any, callback: any) => {
+        const v = String(value ?? '').trim();
+        if (!v) return callback(); // required 负责兜底
+        if (!isValidYYYYMMDD(v)) return callback(new Error('出生年月必须为 YYYY-MM-DD 格式'));
+        return callback();
+      },
+      trigger: 'blur',
+    },
+  ],
+  staffIdCard: [
+    { required: true, message: '请输入身份证号', trigger: 'blur' },
+    {
+      validator: (_rule: any, value: any, callback: any) => {
+        const v = String(value ?? '').trim();
+        if (!v) return callback();
+        if (!isValidChinaIdCard(v)) return callback(new Error('身份证号格式不正确'));
+        return callback();
+      },
+      trigger: 'blur',
+    },
+  ],
+  staffAddress: [{ required: true, message: '请输入家庭住址', trigger: 'blur' }],
+  dateOfJoining: [
+    { required: true, message: '请输入入职日期', trigger: 'blur' },
+    {
+      validator: (_rule: any, value: any, callback: any) => {
+        const v = String(value ?? '').trim();
+        if (!v) return callback();
+        if (!isValidYYYYMMDD(v)) return callback(new Error('入职日期必须为 YYYY-MM-DD 格式'));
+        return callback();
+      },
+      trigger: 'blur',
+    },
+  ],
+  staffJob: [{ required: true, message: '请输入从事岗位', trigger: 'blur' }],
+  jobSeniority: [{ required: true, message: '请输入本岗位工龄', trigger: 'blur' }],
+  technicalLvl: [{ required: true, message: '请输入技术等级', trigger: 'blur' }],
+  professionalTitle: [{ required: true, message: '请输入职称', trigger: 'blur' }],
+};

+ 15 - 2
src/views/production-safety/safetyTrainingAndEducation/employeeTrainingRecordCardManagement/employeeTrainingRecordCardManagement.vue

@@ -46,7 +46,7 @@
             <section class="search-btn">
               <el-button type="primary" @click="handleSearch">查询</el-button>
               <el-button @click="handleReset">重置</el-button>
-              <el-button plain class="search-table-container--button" @click="handleDownload">
+              <el-button plain  @click="handleDownload">
                 导出
             </el-button>
             </section>
@@ -63,6 +63,10 @@
           >
             <template #action="scope">
               <div class="action-container--div" style="justify-content: left">
+                <ActionButton
+                  text="编辑"
+                  @click="handleEdit(scope.row.id)"
+                />
                 <ActionButton
                   text="删除"
                   :popconfirm="{
@@ -226,7 +230,16 @@
       },
     });
   };
-
+  
+  const handleEdit = (id: number) => {
+    router.push({
+      name: 'employeeTrainingRecordCardManagementEdit',
+      query: {
+        id,
+        operate: 'employee-training-record-card-management-edit',
+      },
+    });
+  };
   onMounted(() => {
     getTableData();
   });

+ 368 - 0
src/views/production-safety/safetyTrainingAndEducation/employeeTrainingRecordCardManagement/employeeTrainingRecordCardManagementEdit.vue

@@ -0,0 +1,368 @@
+<template>
+  <div>
+    <header class="safety-platform-container__header">
+      <BreadcrumbBack />
+      <span class="breadcrumb-title">编辑员工培训记录卡</span>
+    </header>
+    <main class="safety-platform-container__main">
+      <el-form
+        :model="form"
+        :rules="rules"
+        ref="formRef"
+        label-width="150px"
+        style="max-width: 600px"
+        label-position="left"
+      >
+        <el-form-item label="工号:" prop="staffNo">
+          <el-input v-model="form.staffNo" placeholder="输入工号" />
+        </el-form-item>
+        <el-form-item label="姓名:" prop="staffName">
+          <el-input v-model="form.staffName" placeholder="输入姓名" />
+        </el-form-item>
+        <el-form-item label="出生年月:" prop="staffBirthday">
+          <el-input v-model="form.staffBirthday" placeholder="输入出生年月" />
+        </el-form-item>
+        <el-form-item label="身份证号:" prop="staffIdCard">
+          <el-input v-model="form.staffIdCard" placeholder="输入身份证号" />
+        </el-form-item>
+        <el-form-item label="所/部/中心:" prop="deptIdForSelect">
+          <el-cascader
+            v-model="form.deptIdForSelect"
+            :options="deptTree"
+            :props="cascaderProp"
+            clearable
+            :show-all-levels="false"
+            placeholder="选择所/部/中心"
+            style="width: 100%"
+          />
+        </el-form-item>
+        <el-form-item label="家庭住址:" prop="staffAddress">
+          <el-input v-model="form.staffAddress" placeholder="输入家庭住址" />
+        </el-form-item>
+        <el-form-item label="入职日期:" prop="dateOfJoining">
+          <el-input v-model="form.dateOfJoining" placeholder="输入入职日期" />
+        </el-form-item>
+        <el-form-item label="文化程度:" prop="highestDegree">
+          <el-input v-model="form.highestDegree" placeholder="输入文化程度" />
+        </el-form-item>
+        <el-form-item label="从事岗位:" prop="staffJob">
+          <el-input v-model="form.staffJob" placeholder="输入从事岗位" />
+        </el-form-item>
+        <el-form-item label="本岗位工龄:" prop="jobSeniority">
+          <el-input v-model="form.jobSeniority" placeholder="输入本岗位工龄" />
+        </el-form-item>
+        <el-form-item label="技术等级:" prop="technicalLvl">
+          <el-input v-model="form.technicalLvl" placeholder="输入技术等级" />
+        </el-form-item>
+        <el-form-item label="职称:" prop="professionalTitle">
+          <el-input v-model="form.professionalTitle" placeholder="输入职称" />
+        </el-form-item>
+        <el-form-item label="员工头像:" prop="staffImg">
+            <el-upload
+              class="image-uploader"
+              ref="staffImgRef"
+              action="#"
+              :file-list="staffImgList"
+              :disabled="isViewMode"
+              :auto-upload="false"
+              accept="image/*"
+              :on-change="handleImageUploadChange"
+              :on-remove="handlePictureCardDelete"
+              :on-preview="handlePictureCardPreview"
+              :before-upload="validateImage"
+              list-type="picture-card">
+                  <el-icon>
+                      <Plus />
+                  </el-icon>
+              <template #tip>
+                <div class="el-upload__tip"> 支持格式:.jpg .png .jpeg,单个文件不能超过300k,设置一个默认图片。</div>
+              </template>
+            </el-upload>
+            <el-dialog v-model="dialogVisible">
+              <img w-full :src="dialogImageUrl" alt="Preview Image" style="width: 100%;" />
+            </el-dialog>
+          </el-form-item>
+        </el-form>
+  
+  
+  
+  
+      <!-- 提交按钮 -->
+      <footer class="safety-platform-container__footer">
+        <el-button @click="router.back()">返回</el-button>
+        <el-button v-if="!isViewMode" type="primary" @click="handleSubmit">
+          {{ isCreateMode ? '提交' : '保存' }}
+        </el-button>
+      </footer>
+    </main>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { computed, onMounted, ref, reactive } from 'vue';
+  import { useRoute, useRouter } from 'vue-router';
+  import { ElMessage, UploadRawFile } from 'element-plus';
+  import type { FormInstance } from 'element-plus';
+  import { Plus, Delete, ZoomIn } from '@element-plus/icons-vue';
+  import { FORM_CARD_RULES } from './configs/form';
+  import {
+    getEducationStaffTrainingCardDetail,
+    updateEducationStaffTrainingCard,
+    type FormDataType,
+  } from '@/api/employee-training-record-card-management';
+  import { DeptTree } from '@/types/dept/type';
+  import { getAllDepartments } from '@/api/auth/dept';
+  import { debounce } from 'lodash-es';
+  import { uploadFileApi, UPLOAD_BIZ_TYPE } from '@/api/minio';
+  import type { FileItem } from '@/components/UploadFiles/types';
+  const router = useRouter();
+  const route = useRoute();
+
+  const operate = computed(() => (route.query.operate as string) || 'education-training-plan-management-create');
+  const currentId = computed(() => Number(route.query.id));
+
+  const isCreateMode = computed(() => operate.value === 'education-training-plan-management-create');
+  const isEditMode = computed(() => operate.value === 'education-training-plan-management-edit');
+  const isViewMode = computed(() => operate.value === 'education-training-plan-management-view');
+
+  // 表单数据
+  const form = reactive<FormDataType>({
+    staffNo: '',
+    deptName: '',
+    deptId: '',
+    deptIdForSelect: undefined,
+    staffName: '',
+    staffBirthday: '',
+    staffIdCard: '',
+    staffAddress: '',
+    dateOfJoining: '',
+    staffJob: '',
+    technicalLvl: '',
+    professionalTitle: '',
+    highestDegree: '',
+    jobSeniority: 0,
+    staffImg:[]
+  });
+  const cascaderProp = {
+    multiple: false,
+    expandTrigger: 'hover',
+    checkStrictly: true,
+    emitPath: false,
+    value: 'id',
+    label: 'deptName',
+  };
+  // 获取级联部门数据
+  const deptTree = ref<DeptTree[]>();
+  const loadDeptTreeData = async () => {
+    const result = await getAllDepartments();
+    deptTree.value = result[0].children;
+  };
+
+  /** 根据部门 id 从树中取名称(最后一级所选节点) */
+  const getDeptNameById = (nodes: DeptTree[] | undefined, id: number): string => {
+    if (!nodes?.length || id == null || Number.isNaN(id)) return '';
+    const nameById = new Map<number, string>();
+    const walk = (list: DeptTree[]) => {
+      for (const n of list) {
+        if (n.id != null) nameById.set(Number(n.id), n.deptName);
+        if (n.children?.length) walk(n.children);
+      }
+    };
+    walk(nodes);
+    return nameById.get(id) ?? '';
+  };
+  const fileList = ref([])
+  const staffImgList = ref<FileItem[]>([]);
+  // 表单引用
+  const formRef = ref<FormInstance>();
+
+  // 表单验证规则
+  const rules = reactive(FORM_CARD_RULES);
+
+  const handleValidate = async () => {
+    if (!formRef.value) return;
+    const res = await formRef.value.validateField();
+    return res;
+  };
+  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));
+  };
+
+  /** 后端若返回逗号分隔 id,只取最后一位(最后一级部门) */
+  const parseLastDeptId = (raw: string | string[] | number[] | undefined | null): number | undefined => {
+    const ids = parseDeptIds(raw);
+    return ids.length ? ids[ids.length - 1] : undefined;
+  };
+
+  const getDetail = async () => {
+    if (!currentId.value) return;
+    try {
+      const res = await getEducationStaffTrainingCardDetail(currentId.value);
+      if (res) {
+        Object.assign(form, {
+          ...res,
+          deptIdForSelect: parseLastDeptId((res as FormDataType).deptId),
+        });
+        if(res.staffImg){
+            form.staffImg = JSON.parse(res.staffImg)
+            staffImgList.value = JSON.parse(res.staffImg) || []
+        }
+      }
+    } catch (e) {
+      ElMessage.error('获取详情失败');
+    }
+  };
+     // 上传文件
+ const formatAttachment = async (data: any) => {
+    if (!data) return data;
+    const uuid = Math.random().toString(36).substring(2, 9);
+    const timestamp = Date.now().toString();
+    const random = Math.random().toString(36).substring(2, 4);
+    const fileName = data.name;
+    const res = await uploadFileApi({
+        bizType: UPLOAD_BIZ_TYPE.ATTACHMENT,
+        fileName: `${uuid}-${timestamp}-${random}`,
+        file: data,
+    });
+    return res;
+};
+    //  上传图片
+  const handleImageUploadChange = async (file: any, fileLists: any) => {
+    if(file.raw){
+        try {
+            const res = await formatAttachment(file.raw);
+            
+            const targetFile = fileLists.find(f => f.uid === file.uid);
+            if (targetFile) {
+                targetFile.url = res.url; 
+                targetFile.contentType = res.contentType
+            }
+            staffImgList.value = fileLists; 
+            form.staffImg = JSON.stringify(fileLists);
+            ElMessage.success('上传成功');
+        } catch (error) {
+            ElMessage.error('上传失败,请重试');
+            // 上传失败时,可以从 fileLists 中移除该文件
+            staffImgList.value = fileLists.filter(f => f.uid !== file.uid);
+        }
+    }
+  };
+  // 替换图片
+  const staffImgRef = ref();
+  const handleImageExceed = (files) => {
+    staffImgRef.value!.clearFiles(); 
+    const file = files[0] as UploadRawFile;
+    console.log(file)
+    if (!validateImage(file)) {
+      return;
+    }
+    staffImgRef.value!.handleStart(file); 
+  };
+// 图片预览
+  const dialogVisible = ref(false);
+  const dialogImageUrl = ref('');
+  const handlePictureCardPreview = (file: any) => {
+    dialogImageUrl.value = file.url;
+    dialogVisible.value = true;
+  };
+
+  const handlePictureCardDelete = (file, fileLists)=>{
+    staffImgRef.value = fileLists.filter(f => f.uid !== file.uid);
+    form.staffImg = JSON.stringify(staffImgRef.value);
+  }
+
+// 图片格式校验
+  const validateImage = (file) => {
+    const validMIME = [
+      'image/jpeg',
+      'image/png',
+      'image/gif',
+      'image/bmp',
+      'image/webp',
+      'image/svg+xml',
+      'image/tiff',
+      'image/heic',
+      'image/heif',
+      'image/avif',
+    ];
+
+    if (!validMIME.includes(file.type)) {
+      ElMessage.error('仅支持图片文件(JPEG/PNG/GIF/BMP/WEBP/SVG/TIFF/HEIC/AVIF等)');
+      return false;
+    }
+
+    if (!validMIME.includes(file.type)) {
+      ElMessage.error('仅支持JPG/PNG格式图片');
+      return false; // 阻止上传
+    }
+
+    // 可选:添加文件大小限制
+    const maxSize = 300 * 1024 ; // 5MB
+    if (file.size > maxSize) {
+      ElMessage.error('图片大小不能超过300K');
+      return false;
+    }
+
+    return true; // 验证通过
+  };
+  const handleSubmit = debounce(async () => {
+    const res = await handleValidate();
+    if (!res) return;
+    try {
+      const { deptIdForSelect, ...rest } = form;
+      const id = deptIdForSelect;
+      const basePayload = {
+        ...rest,
+        deptId: id != null && !Number.isNaN(id) ? String(id) : '',
+        deptName: id != null ? getDeptNameById(deptTree.value, id) : '',
+      };
+
+      await updateEducationStaffTrainingCard({
+        id: currentId.value,
+        ...basePayload,
+      });
+      ElMessage.success('保存成功');
+
+      router.back();
+    } catch (e) {
+      ElMessage.error('保存失败,请重试');
+    }
+  }, 1000);
+
+  onMounted(() => {
+    loadDeptTreeData();
+    getDetail();
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+
+  .safety-platform-container__header {
+    flex-direction: row !important;
+    justify-content: flex-start !important;
+    gap: 8px !important;
+  }
+  .el-form-item {
+    margin-bottom: 25px;
+  }
+  li{
+    list-style: none;
+  }
+  .border-b{
+    border-bottom: 1px solid #efefef;
+    padding-bottom:10px;
+    margin-bottom:10px;
+  }
+</style>