Просмотр исходного кода

Merge branch 'feat/production-safety' of http://192.168.6.110/product-group-fe/sfy-safety-group/sfy-safety into feat/production-safety

sunqijun 2 месяцев назад
Родитель
Сommit
57f64081e7
19 измененных файлов с 1408 добавлено и 899 удалено
  1. 103 3
      src/api/safety-culture/index.ts
  2. 1 0
      src/components/UploadFiles/UploadFiles.vue
  3. 15 1
      src/router/routers/production-safety-router/productionSafetySystem.ts
  4. 68 34
      src/views/production-safety/productionSafetySystem/safetyStandardizationSystemManagement/components/safetyStandardizationSystemManagementDetail.vue
  5. 1 0
      src/views/production-safety/productionSafetySystem/safetyStandardizationSystemManagement/configs/form.ts
  6. 1 1
      src/views/production-safety/productionSafetySystem/safetyStandardizationSystemManagement/safetyStandardizationSystemManagement.vue
  7. 2 2
      src/views/production-safety/risk-identification-and-control/work-injury-apply-manage/list.vue
  8. 2 2
      src/views/production-safety/risk-identification-and-control/work-injury-apply-manage/listAdmin.vue
  9. 69 39
      src/views/production-safety/safety-culture/accidentCaseManagement/components/accidentCaseManagementDetail.vue
  10. 2 1
      src/views/production-safety/safety-culture/safetyCultureActivityManagement/components/activityRegistrationManagement.vue
  11. 275 203
      src/views/production-safety/safety-culture/safetyCultureActivityManagement/components/safetyCultureActivityManagementDetail.vue
  12. 27 50
      src/views/production-safety/safety-culture/safetyCultureActivityManagement/configs/form.ts
  13. 4 4
      src/views/production-safety/safety-culture/safetyCultureActivityManagement/configs/tables.ts
  14. 126 6
      src/views/production-safety/safety-culture/safetyCultureActivityManagement/safetyCultureActivityManagement.vue
  15. 367 267
      src/views/production-safety/safety-culture/safetyCultureMaterialManagement/components/safetyCultureMaterialManagementDetail.vue
  16. 3 3
      src/views/production-safety/safety-culture/safetyCultureMaterialManagement/configs/form.ts
  17. 306 259
      src/views/production-safety/safety-culture/safetyPublicityBoardManagement/components/safetyPublicityBoardManagementDetail.vue
  18. 4 8
      src/views/production-safety/safety-culture/safetyPublicityBoardManagement/configs/form.ts
  19. 32 16
      src/views/production-safety/safety-culture/safetyPublicityBoardManagement/safetyPublicityBoardManagement.vue

+ 103 - 3
src/api/safety-culture/index.ts

@@ -1,6 +1,6 @@
 import { http } from '@/utils/http/axios';
 import type { QueryPageResponse } from '@/types/basic-query';
-import exp from 'node:constants';
+// import exp from 'node:constants';
 import { DeptTree, addDeptProps, editDeptProps } from '@/types/dept/type';
 
 /**
@@ -64,10 +64,25 @@ export interface newAccidentCases {
   caseName: string; // 事故案例名称
   categoryName: string; // 分类名称
   description: string; // 案例描述
-  attachmentUrl: string[]; // 文档上传(多个文件路径)
+  attachmentUrl: string[] | string; // 文档上传(多个文件路径)
   status?: number; // 启用状态:1-启用,0-禁用
 }
 
+
+/**
+ * 保存安全活动
+ */
+
+ export interface addSafetyCultureFilePageQuery {
+  id?: number;
+  planName: string;
+  actionContent: string;
+  categoryName: string;
+  responsibleDeptId: number;
+  responsiblePersonId: number;
+  cooperateDeptIds: string;
+}
+
 /**
  * 导入响应
  */
@@ -261,6 +276,36 @@ export function deleteSafetyCultureActivityManagement(id: number) {
   });
 }
 
+
+/**
+ * 新增安全文化活动
+ */
+export function addSafetyCultureActivityManagement(data: addSafetyCultureFilePageQuery) {
+  return http.request({
+    url: '/safetyCulture/activity/save',
+    method: 'post',
+    data,
+    headers: {
+      'satoken': '7c1689ff62d6401483d1cb7235370b3c',
+    },
+  });
+}
+
+/**
+ * 更新安全文化活动
+ */
+export function updateSafetyCultureActivity(data: addSafetyCultureFilePageQuery) {
+  return http.request({
+    url: '/safetyCulture/activity/update',
+    method: 'put',
+    data,
+    headers: {
+      'satoken': '7c1689ff62d6401483d1cb7235370b3c',
+    },
+  });
+}
+
+
 /**
  * 查询安全文化活动详情
  */
@@ -352,4 +397,59 @@ export function querySafetyPublicityBoardPage(data: safetyCultureFilePageQuery)
       'satoken': '7c1689ff62d6401483d1cb7235370b3c',
     },
   });
-}
+}
+
+
+/**
+ * 添加安全宣传栏管理分页查询
+ */
+export function saveSafetyPublicityBoardPage(data) {
+  return http.request({
+    url: '/safetypublicitybulletinboard/save',
+    method: 'post',
+    data,
+    headers: {
+      'satoken': '7c1689ff62d6401483d1cb7235370b3c',
+    },
+  });
+}
+
+/**
+ * 安全宣传栏管理详情
+ */
+export function querySafetyPublicityBoardDetail(id: number) {
+  return http.request<safetyCultureFile>({
+    url: `/safetypublicitybulletinboard/detail?id=${id}`,
+    method: 'get',
+    headers: {
+      'satoken': '7c1689ff62d6401483d1cb7235370b3c',
+    },
+  });
+}
+
+/**
+ * 更新安全宣传栏管理
+ */
+export function updateSafetyPublicityBoardPage(data) {
+  return http.request({
+    url: '/safetypublicitybulletinboard/update',
+    method: 'put',
+    data,
+    headers: {
+      'satoken': '7c1689ff62d6401483d1cb7235370b3c',
+    },
+  });
+}
+
+/**
+ * 删除安全宣传栏管理
+ */
+export function deleteSafetyPublicityBoardPage(id: number) {
+  return http.request({
+    url: `/safetypublicitybulletinboard/delete?id=${id}`,
+    method: 'delete',
+    headers: {
+      'satoken': '7c1689ff62d6401483d1cb7235370b3c',
+    },
+  });
+}

+ 1 - 0
src/components/UploadFiles/UploadFiles.vue

@@ -124,6 +124,7 @@
 
   // 方法
   const handleFileSelect = (event: Event) => {
+    if (props.disabled) return;
     const input = event.target as HTMLInputElement;
     if (!input.files || input.files.length === 0) return;
 

+ 15 - 1
src/router/routers/production-safety-router/productionSafetySystem.ts

@@ -238,7 +238,21 @@ const productionSafetySystemRoutes: RouteComponent[] = [{
         hidden: false,
         noCache: false,
       },
-    }
+    },
+    {
+        id: 90029,
+        parentId: 9002,
+        name: 'SafetyOrganizationSystemManagementItem',
+        path: '/production-safety/productionSafetySystem/safetyOrganizationSystemManagement/safetyOrganizationSystemManagementItem',
+        component: () => import('/src/views/production-safety/productionSafetySystem/safetyOrganizationSystemManagement/safetyOrganizationSystemManagementItem.vue'),
+        meta: {
+            title: '安全组织体系管理详情',
+            icon: 'OverviewIcon',
+            isRoot: false,
+            hidden: false,
+            noCache: false,
+        },
+    },
   ],
 }];
 

+ 68 - 34
src/views/production-safety/productionSafetySystem/safetyStandardizationSystemManagement/components/safetyStandardizationSystemManagementDetail.vue

@@ -13,17 +13,40 @@
         </el-radio-group>
       </template>
       <template #fileUrl>
-        <UploadFiles
+        <!-- <UploadFiles
           label="上传文件"
           :maxCount="1"
           :file-list="ruleFormData.fileUrlList"
           :disabled="isViewMode"
           :allow-all-file-types="true"
           @uploadSuccess="handleUploadSuccess"
+        /> -->
+        <UploadFiles
+          v-if="!isViewMode"
+          label="上传地址确认书"
+          :maxCount="1"
+          :file-list="ruleFormData.fileUrlList"
+          @uploadSuccess="handleUploadSuccess"
         />
+        <div class="file-list" v-else>
+          <div v-if="ruleFormData.fileUrlList && ruleFormData.fileUrlList.length > 0">
+            <div class="file-item" v-for="file in ruleFormData.fileUrlList" :key="file.fileId">
+              <span class="file-item--name">{{ file.fileName }}</span>
+              <div class="file-item--footer">
+                <el-button link type="primary" @click="previewOnline(file.fileUrl, file.fileType)"
+                  >预览</el-button
+                >
+                <el-button link type="primary" @click.stop="downloadFile(file.fileUrl, file.fileName)"
+                  >下载</el-button
+                >
+              </div>
+            </div>
+          </div>
+          <div v-else class="no-attachment">暂无附件</div>
+        </div>
       </template>
       <template #content>
-        <div class="editor-container">
+        <div class="editor-container" v-if="!isViewMode">
           <Toolbar style="border-bottom: 1px solid #dcdfe6" :editor="editorRef" />
           <Editor
             style="height: 400px; overflow-y: auto"
@@ -34,6 +57,7 @@
             @on-change="handleEditorChange"
           />
         </div>
+        <div v-html="ruleFormData.content" v-else></div>
       </template>
       <template #status>
         <el-radio-group v-model="ruleFormData.status" :disabled="isViewMode">
@@ -42,6 +66,7 @@
         </el-radio-group>
       </template>
     </BasicForm>
+    <PreviewOnline ref="previewOnlineRef" />
   </main>
   <footer class="safety-platform-container__footer">
     <el-button @click="router.back()">返回</el-button>
@@ -73,6 +98,8 @@
   } from '@/api/production-safety-system';
   import type { FileItem } from '@/components/UploadFiles/types';
   import { formatAttachmentList } from '@/components/UploadFiles/utils';
+  import PreviewOnline from '@/views/disaster/components/PreviewOnline.vue';
+  import { downloadFile } from '@/views/disaster/utils';
 
   const router = useRouter();
   const route = useRoute();
@@ -83,6 +110,7 @@
   const isCreateMode = computed(() => operate.value === 'safety-standardization-create');
   const isEditMode = computed(() => operate.value === 'safety-standardization-edit');
   const isViewMode = computed(() => operate.value === 'safety-standardization-view');
+  const previewOnlineRef = ref<InstanceType<typeof PreviewOnline>>();
 
   const { ruleFormData, formRules, ruleFormConfig, cloneRuleFormData, beforeRouteLeave } = useFormConfigHook(
     SAFETY_STANDARDIZATION_FORM_CONFIG,
@@ -128,6 +156,7 @@
   // 文件上传
   const handleUploadSuccess = (files: FileItem[]) => {
     ruleFormData.fileUrlList = files;
+    ruleFormData.fileUrl = JSON.stringify(files) || '';
   };
 
   // 将逗号分隔的URL字符串转换为FileItem数组
@@ -189,9 +218,7 @@
         ruleFormData.fileUrl = res.fileUrl || '';
         ruleFormData.content = res.content || '';
         ruleFormData.status = res.status ?? 1;
-
-        // 如果有文件URL,转换为FileItem格式
-        ruleFormData.fileUrlList = convertFileUrlToFileItems(res.fileUrl || '');
+        ruleFormData.fileUrlList = JSON.parse(res.fileUrl || '[]');
       }
       cloneRuleFormData();
     } catch (e) {
@@ -212,33 +239,34 @@
 
     try {
       // 处理文件上传:先上传文件获取 URL,然后提取 fileUrl
-      let fileUrl = '';
-      if (ruleFormData.fileUrlList && ruleFormData.fileUrlList.length > 0) {
-        // 分离已有URL的文件和新上传的文件
-        const existingFiles: string[] = [];
-        const newFiles: FileItem[] = [];
-
-        ruleFormData.fileUrlList.forEach((file: FileItem) => {
-          // 如果文件已经有 fileUrl 且没有 file 对象,说明是已有文件
-          if (file.fileUrl && !file.file) {
-            existingFiles.push(file.fileUrl);
-          } else {
-            // 否则是需要上传的新文件
-            newFiles.push(file);
-          }
-        });
-
-        // 上传新文件
-        let uploadedUrls: string[] = [];
-        if (newFiles.length > 0) {
-          const uploadedFiles = await formatAttachmentList(newFiles);
-          uploadedUrls = uploadedFiles.map((file: any) => file.fileUrl || file.url || '').filter((url: string) => url);
-        }
-
-        // 合并已有URL和新上传的URL,取第一个作为fileUrl
-        const allUrls = [...existingFiles, ...uploadedUrls].filter((url: string) => url);
-        fileUrl = allUrls.length > 0 ? allUrls[0] : '';
-      }
+      // let fileUrl = '';
+      // if (ruleFormData.fileUrlList && ruleFormData.fileUrlList.length > 0) {
+      //   // 分离已有URL的文件和新上传的文件
+      //   const existingFiles: string[] = [];
+      //   const newFiles: FileItem[] = [];
+
+      //   ruleFormData.fileUrlList.forEach((file: FileItem) => {
+      //     // 如果文件已经有 fileUrl 且没有 file 对象,说明是已有文件
+      //     if (file.fileUrl && !file.file) {
+      //       existingFiles.push(file.fileUrl);
+      //     } else {
+      //       // 否则是需要上传的新文件
+      //       newFiles.push(file);
+      //     }
+      //   });
+
+      //   // 上传新文件
+      //   let uploadedUrls: string[] = [];
+      //   if (newFiles.length > 0) {
+      //     const uploadedFiles = await formatAttachmentList(newFiles);
+      //     uploadedUrls = uploadedFiles.map((file: any) => file.fileUrl || file.url || '').filter((url: string) => url);
+      //   }
+
+      //   // 合并已有URL和新上传的URL,取第一个作为fileUrl
+      //   const allUrls = [...existingFiles, ...uploadedUrls].filter((url: string) => url);
+      //   fileUrl = allUrls.length > 0 ? allUrls[0] : '';
+      // }
+      const uploadedFileList = await formatAttachmentList(ruleFormData.fileUrlList);
 
       const basePayload: ProductionSafetyFile = {
         fileName: ruleFormData.fileName,
@@ -247,7 +275,7 @@
         fileVersion: ruleFormData.fileVersion,
         fileFormat: ruleFormData.fileFormat,
         releaseDate: ruleFormData.releaseDate,
-        fileUrl: fileUrl || undefined,
+        fileUrl: JSON.stringify(uploadedFileList) || undefined,
         content: ruleFormData.content || undefined,
         status: ruleFormData.status ?? 1,
       };
@@ -262,7 +290,7 @@
         });
         ElMessage.success('保存成功');
       }
-
+      cloneRuleFormData();
       router.back();
     } catch (e) {
       console.error('保存安全标准化体系建设失败:', e);
@@ -270,6 +298,12 @@
     }
   };
 
+  const previewOnline = (url: string | undefined, type) => {
+    if (url) {
+      previewOnlineRef.value?.open(url, type);
+    }
+  };
+
   onMounted(() => {
     cloneRuleFormData();
     beforeRouteLeave();

+ 1 - 0
src/views/production-safety/productionSafetySystem/safetyStandardizationSystemManagement/configs/form.ts

@@ -89,4 +89,5 @@ export const SAFETY_STANDARDIZATION_FORM_RULES = {
   fileVersion: [{ required: true, message: '请输入文件版本号', trigger: 'blur' }],
   fileFormat: [{ required: true, message: '请选择文件格式', trigger: 'change' }],
   releaseDate: [{ required: true, message: '请选择发布日期', trigger: 'change' }],
+  fileUrl: [{ required: true, message: '请上传文件', trigger: 'change' }],
 };

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

@@ -212,7 +212,7 @@
   // 批量导入
   const batchImportVisible = ref(false);
   const { urlPrefix } = useGlobSetting();
-  const importApiUrl = ref(urlJoin(urlPrefix, '/productionSafety/safetyStandardization/import'));
+  const importApiUrl = ref(urlJoin(urlPrefix, '/admin/prod/safetyStandardization/importSafetyStandardization'));
   const templateUrl = ref('./skyeye-file-upload/sfysecurity/TEMPLATE/import-safety-standardization-template.xlsx');
 
   const handleImport = () => {

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

@@ -44,7 +44,7 @@
               <div class="select-box--item">
                 <span>所属部门:</span>
                  <el-cascader
-                  v-model="tableQuery.queryParam.applicationDepartmentId"
+                  v-model="tableQuery.queryParam.deptCode"
                   style="width: 170px"
                   ref="cascaderRef"
                   :options="firstLevelDepts"
@@ -215,7 +215,7 @@
       queryKey: '', // 名称
       status: '', // 状态,默认启用
       ids: [], // 选择数据的ID
-      applicationDepartmentId: '', // 所属部门ID
+      deptCode: '', // 所属部门ID
     },
   });
 

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

@@ -44,7 +44,7 @@
               <div class="select-box--item">
                 <span>所属部门:</span>
                  <el-cascader
-                  v-model="tableQuery.queryParam.applicationDepartmentId"
+                  v-model="tableQuery.queryParam.deptCode"
                   style="width: 170px"
                   ref="cascaderRef"
                   :options="firstLevelDepts"
@@ -216,7 +216,7 @@
       queryKey: '', // 物品名称
       status: '', // 状态,默认启用
       ids: [], // 选择数据的ID
-      applicationDepartmentId: '', // 所属部门ID
+      deptCode: '', // 所属部门ID
     },
   });
 

+ 69 - 39
src/views/production-safety/safety-culture/accidentCaseManagement/components/accidentCaseManagementDetail.vue

@@ -3,8 +3,30 @@
     <BasicForm ref="basicFormRef" :formData="ruleFormData" :formRules="isViewMode ? undefined : formRules"
       :formConfig="computedFormConfig">
       <template #fileUrl>
-        <UploadFiles label="上传文件" :maxCount="1" :file-list="ruleFormData.attachmentUrl" :disabled="isViewMode"
-          :allow-all-file-types="true" @uploadSuccess="handleUploadSuccess" />
+        <!-- <UploadFiles label="上传文件" :maxCount="1" :file-list="ruleFormData.attachmentUrl" :disabled="isViewMode"
+          :allow-all-file-types="true" @uploadSuccess="handleUploadSuccess" /> -->
+          <UploadFiles
+            v-if="!isViewMode"
+            label="上传文件"
+            :maxCount="1"
+            :file-list="ruleFormData.attachmentUrl"
+            :disabled="isViewMode"
+            :allow-all-file-types="true"
+            @uploadSuccess="(list: FileItem[]) => handleUploadSuccess(list)"
+          />
+          <div class="file-list" v-else>
+            <div class="file-item" v-for="file in ruleFormData.attachmentUrl" :key="file.fileId">
+              <span class="file-item--name">{{ file.fileName }}</span>
+              <div class="file-item--footer">
+                <el-button link type="primary" @click="previewOnline(file.fileUrl, file.fileType)"
+                  >预览</el-button
+                >
+                <!-- <el-button link type="primary" @click.stop="downloadFile(file.fileUrl, file.fileName)"
+                  >下载</el-button
+                > -->
+              </div>
+            </div>
+          </div>
       </template>
       <template #content>
         <div class="editor-container">
@@ -14,12 +36,13 @@
         </div>
       </template>
       <template #status>
-        <el-radio-group v-model="ruleFormData.status" :disabled="isViewMode">
+        <el-radio-group v-model="ruleFormData.status" :disabled="isViewMode"> 
           <el-radio :value="1">启用</el-radio>
           <el-radio :value="0">禁用</el-radio>
         </el-radio-group>
       </template>
     </BasicForm>
+    <PreviewOnline ref="previewOnlineRef" />
   </main>
   <footer class="safety-platform-container__footer">
     <el-button @click="router.back()">返回</el-button>
@@ -47,6 +70,7 @@ import {
 } from '@/api/safety-culture';
 import type { FileItem } from '@/components/UploadFiles/types';
 import { formatAttachmentList } from '@/components/UploadFiles/utils';
+import PreviewOnline from '@/views/disaster/components/PreviewOnline.vue';
 
 const router = useRouter();
 const route = useRoute();
@@ -58,6 +82,8 @@ const isCreateMode = computed(() => operate.value === 'safety-culture-material-c
 const isEditMode = computed(() => operate.value === 'safety-culture-material-edit');
 const isViewMode = computed(() => operate.value === 'safety-culture-material-view');
 
+const previewOnlineRef = ref<InstanceType<typeof PreviewOnline>>();
+
 const { ruleFormData, formRules, ruleFormConfig, cloneRuleFormData, beforeRouteLeave } =
   useFormConfigHook(ACADEMY_FILE_FORM_CONFIG, ACADEMY_FILE_FORM_DATA, ACADEMY_FILE_FORM_RULES);
 
@@ -88,26 +114,36 @@ const editorConfig = computed(() => ({
 
 const handleEditorCreated = (editor: any) => {
   editorRef.value = editor;
+  if (isViewMode.value) {
+    editor.disable();
+  }
 };
 
 const handleEditorChange = () => {
 };
 
-const handleUploadSuccess = (files: FileItem[]) => {
+
+const handleUploadSuccess = (files) => {
   ruleFormData.attachmentUrl = files;
 };
 
+
+
+// 将逗号分隔的URL字符串转换为FileItem数组
 const convertFileUrlToFileItems = (fileUrl: string): FileItem[] => {
   if (!fileUrl || !fileUrl.trim()) {
     return [];
   }
-
+  
+  // 按逗号分割URL
   const urls = fileUrl.split(',').map(url => url.trim()).filter(url => url);
-
+  
   return urls.map((url, index) => {
+    // 从URL中提取文件名
     const urlParts = url.split('/');
     const fileName = urlParts[urlParts.length - 1] || `附件${index + 1}`;
-
+    
+    // 根据文件扩展名判断文件类型
     const extension = fileName.split('.').pop()?.toLowerCase() || '';
     let fileType = 'pdf';
     if (extension === 'doc' || extension === 'docx') {
@@ -117,7 +153,7 @@ const convertFileUrlToFileItems = (fileUrl: string): FileItem[] => {
     } else if (extension === 'ppt' || extension === 'pptx') {
       fileType = 'ppt';
     }
-
+    
     return {
       fileId: Date.now() + index,
       fileName,
@@ -143,7 +179,7 @@ const getDetail = async () => {
       ruleFormData.categoryName = res.categoryName || '';
       ruleFormData.status = res.status ?? 1;
       ruleFormData.description = res.description || '';
-      ruleFormData.attachmentUrl = convertFileUrlToFileItems(res.fileUrl || '');
+      ruleFormData.attachmentUrl = JSON.parse(res.attachmentUrl || res.fileUrl || '');
     }
     cloneRuleFormData();
   } catch (e) {
@@ -156,44 +192,21 @@ const handleSubmit = async () => {
   const res = await handleValidate();
   if (!res) return;
 
-  // if (!ruleFormData.attachmentUrl || ruleFormData.attachmentUrl.length === 0) {
-  //   ElMessage.warning('请上传文件');
-  //   return;
-  // }
+  if (!ruleFormData.attachmentUrl || ruleFormData.attachmentUrl.length === 0) {
+    ElMessage.warning('请上传文件');
+    return;
+  }
 
   console.log('ruleFormData', ruleFormData);
 
   try {
-    // let fileUrl = '';
-    // if (ruleFormData.attachmentUrl && ruleFormData.attachmentUrl.length > 0) {
-    //   const existingFiles: string[] = [];
-    //   const newFiles: FileItem[] = [];
-
-    //   ruleFormData.attachmentUrl.forEach((file: FileItem) => {
-    //     if (file.fileUrl && !file.file) {
-    //       existingFiles.push(file.fileUrl);
-    //     } else {
-    //       newFiles.push(file);
-    //     }
-    //   });
-
-    //   let uploadedUrls: string[] = [];
-    //   if (newFiles.length > 0) {
-    //     const uploadedFiles = await formatAttachmentList(newFiles);
-    //     uploadedUrls = uploadedFiles
-    //       .map((file: any) => file.fileUrl || file.url || '')
-    //       .filter((url: string) => url);
-    //   }
-
-    //   const allUrls = [...existingFiles, ...uploadedUrls].filter((url: string) => url);
-    //   fileUrl = allUrls.length > 0 ? allUrls[0] : '';
-    // }
+    const uploadedFileList = await formatAttachmentList(ruleFormData.attachmentUrl);
 
     const basePayload: newAccidentCases = {
       caseName: ruleFormData.caseName,
       categoryName: ruleFormData.categoryName,
       description: ruleFormData.description,
-      attachmentUrl: ruleFormData.attachmentUrl.map((file: FileItem) => file.fileUrl || 'https://xxxxx/attachment.pdf'),
+      attachmentUrl:JSON.stringify(uploadedFileList),
       status: ruleFormData.status,
     };
     if (isCreateMode.value) {
@@ -214,6 +227,12 @@ const handleSubmit = async () => {
   }
 };
 
+const previewOnline = (url: string | undefined, type) => {
+  if (url) {
+    previewOnlineRef.value?.open(url, type);
+  }
+};
+
 onMounted(() => {
   cloneRuleFormData();
   // beforeRouteLeave();
@@ -274,4 +293,15 @@ onBeforeUnmount(() => {
     height: 80px !important;
   }
 }
-</style>
+</style>
+
+<style lang="scss">
+.w-e-full-screen-container {
+  inset: 0 !important;
+  z-index: 3000 !important;
+}
+
+.w-e-full-screen-container .w-e-text-container {
+  height: calc(100vh - 42px) !important;
+}
+</style>

+ 2 - 1
src/views/production-safety/safety-culture/safetyCultureActivityManagement/components/activityRegistrationManagement.vue

@@ -53,7 +53,7 @@
     </main>
 
     <!-- 添加/编辑先进个人对话框 -->
-    <el-dialog v-model="addDialogVisible" :title="`${isEditMode ? '编辑' : '添加'}活动报名信息`" width="800px"
+    <el-dialog v-model="addDialogVisible" :title="`${isEditMode && normalForm.id ? '编辑' : '添加'}活动报名信息`" width="800px"
       :close-on-click-modal="false" @close="handleDialogClose">
       <div class="add-dialog-content">
 
@@ -273,6 +273,7 @@ const handleDialogClose = () => {
   // 重置表单
   if (normalFormRef.value) {
     normalFormRef.value.resetFields();
+    normalForm.deptId = '';
   }
 };
 

+ 275 - 203
src/views/production-safety/safety-culture/safetyCultureActivityManagement/components/safetyCultureActivityManagementDetail.vue

@@ -1,7 +1,58 @@
 <template>
   <main class="safety-platform-container__main">
-    <BasicForm ref="basicFormRef" :formData="ruleFormData" :formRules="isViewMode ? undefined : formRules"
-      :formConfig="computedFormConfig">
+    <BasicForm
+      ref="basicFormRef"
+      :formData="ruleFormData"
+      :formRules="isViewMode ? undefined : formRules"
+      :formConfig="computedFormConfig"
+    >
+      <template #categoryName>
+        <el-select v-model="ruleFormData.categoryName" placeholder="请选择分类名称" :disabled="isViewMode">
+          <el-option v-for="item in classifyNameOptions" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </template>
+      <template #responsibleDeptId>
+        <el-cascader
+          v-model="ruleFormData.responsibleDeptId"
+          :options="deptTree"
+          :props="responsibleDeptCascaderProp"
+          clearable
+          :show-all-levels="false"
+          placeholder="请选择责任部门"
+          style="width: 100%"
+          :disabled="isViewMode"
+        />
+      </template>
+      <template #cooperateDeptIds>
+        <el-cascader
+          v-model="ruleFormData.cooperateDeptIds"
+          :options="deptTree"
+          :props="cooperateDeptCascaderProp"
+          clearable
+          collapse-tags
+          :show-all-levels="false"
+          :max-collapse-tags="3"
+          popper-class="cascader-popper--custom"
+          placeholder="请选择配合部门"
+          style="width: 100%"
+          :disabled="isViewMode"
+        />
+      </template>
+      <template #responsiblePersonId>
+        <el-select
+          v-model="ruleFormData.responsiblePersonId"
+          placeholder="请选择责任人"
+          :disabled="isViewMode"
+          filterable
+        >
+          <el-option
+            v-for="user in responsiblePersonOptions"
+            :key="user.value"
+            :label="user.label"
+            :value="user.value"
+          />
+        </el-select>
+      </template>
     </BasicForm>
   </main>
   <footer class="safety-platform-container__footer">
@@ -13,225 +64,246 @@
 </template>
 
 <script setup lang="ts">
-import { computed, onMounted, ref, shallowRef, onBeforeUnmount } from 'vue';
-import { useRoute, useRouter } from 'vue-router';
-import { ElMessage } from 'element-plus';
-import BasicForm from '@/components/BasicForm.vue';
-import UploadFiles from '@/components/UploadFiles/UploadFiles.vue';
-import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
-import '@wangeditor/editor/dist/css/style.css';
-import { useFormConfigHook } from '@/hooks/useFormConfigHook';
-import { ACADEMY_FILE_FORM_CONFIG, ACADEMY_FILE_FORM_DATA, ACADEMY_FILE_FORM_RULES } from '../configs/form';
-import {
-  querySafetyCultureActivityDetail,
-  // saveSafetyCultureActivity,
-  // updateSafetyCultureActivity,
-  // type newSafetyCultureActivities,
-} from '@/api/safety-culture';
-import type { FileItem } from '@/components/UploadFiles/types';
-import { formatAttachmentList } from '@/components/UploadFiles/utils';
-
-const router = useRouter();
-const route = useRoute();
-
-const operate = computed(() => (route.query.operate as string) || 'safety-culture-material-create');
-const currentId = computed(() => Number(route.query.id));
-
-const isCreateMode = computed(() => operate.value === 'safety-culture-material-create');
-const isEditMode = computed(() => operate.value === 'safety-culture-material-edit');
-const isViewMode = computed(() => operate.value === 'safety-culture-material-view');
-
-const { ruleFormData, formRules, ruleFormConfig, cloneRuleFormData, beforeRouteLeave } =
-  useFormConfigHook(ACADEMY_FILE_FORM_CONFIG, ACADEMY_FILE_FORM_DATA, ACADEMY_FILE_FORM_RULES);
-
-const viewFormConfig = ref(
-  ACADEMY_FILE_FORM_CONFIG.map((item) => ({
-    ...item,
-    componentProps: {
-      ...item.componentProps,
-      disabled: true,
-    },
-  })),
-);
-
-const computedFormConfig = computed(() => {
-  if (isViewMode.value) {
-    return viewFormConfig.value;
-  }
-  return ruleFormConfig.value;
-});
+  import { computed, onMounted, ref } from 'vue';
+  import { useRoute, useRouter } from 'vue-router';
+  import { ElMessage } from 'element-plus';
+  import BasicForm from '@/components/BasicForm.vue';
+  // import UploadFiles from '@/components/UploadFiles/UploadFiles.vue';
+  // import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
+  import '@wangeditor/editor/dist/css/style.css';
+  import { useFormConfigHook } from '@/hooks/useFormConfigHook';
+  import { ACADEMY_FILE_FORM_CONFIG, ACADEMY_FILE_FORM_DATA, ACADEMY_FILE_FORM_RULES } from '../configs/form';
+  import {
+    querySafetyCultureActivityDetail,
+    addSafetyCultureActivityManagement,
+    updateSafetyCultureActivity,
+    getAllDepartments,
+  } from '@/api/safety-culture';
+  import { queryAvailableUserList } from '@/api/system/person-group';
+  import type { addSafetyCultureFilePageQuery } from '@/api/safety-culture';
+  import type { DeptTree } from '@/types/dept/type';
+
+  const router = useRouter();
+  const route = useRoute();
+
+  const operate = computed(() => (route.query.operate as string) || 'safety-culture-material-create');
+  const currentId = computed(() => Number(route.query.id));
+
+  const isCreateMode = computed(() => operate.value === 'safety-culture-material-create');
+  const isEditMode = computed(() => operate.value === 'safety-culture-material-edit');
+  const isViewMode = computed(() => operate.value === 'safety-culture-material-view');
+
+  const { ruleFormData, formRules, ruleFormConfig, cloneRuleFormData } = useFormConfigHook(
+    ACADEMY_FILE_FORM_CONFIG,
+    ACADEMY_FILE_FORM_DATA,
+    ACADEMY_FILE_FORM_RULES,
+  );
+
+  const viewFormConfig = ref(
+    ACADEMY_FILE_FORM_CONFIG.map((item) => ({
+      ...item,
+      componentProps: {
+        ...item.componentProps,
+        disabled: true,
+      },
+    })),
+  );
+
+  const computedFormConfig = computed(() => {
+    if (isViewMode.value) {
+      return viewFormConfig.value;
+    }
+    return ruleFormConfig.value;
+  });
 
-const basicFormRef = ref<InstanceType<typeof BasicForm>>();
+  const basicFormRef = ref<InstanceType<typeof BasicForm>>();
+
+  const responsibleDeptCascaderProp = {
+    expandTrigger: 'hover' as const,
+    checkStrictly: true,
+    emitPath: false,
+    value: 'id',
+    label: 'deptName',
+  };
+
+  const cooperateDeptCascaderProp = {
+    multiple: true,
+    expandTrigger: 'hover' as const,
+    checkStrictly: true,
+    emitPath: false,
+    value: 'id',
+    label: 'deptName',
+  };
+
+  const deptTree = ref<DeptTree[]>([]);
+  const loadDeptTreeData = async () => {
+    const result = await getAllDepartments();
+    deptTree.value = result?.[0]?.children || [];
+  };
+
+  const classifyNameOptions = ref<Array<{ label: string; value: string }>>([
+    { label: '安全综合工作', value: '安全综合工作' },
+    { label: '生产安全工作', value: '生产安全工作' },
+  ]);
+
+  const responsiblePersonOptions = ref<Array<{ label: string; value: number }>>([]);
+  const loadResponsiblePersonOptions = async () => {
+    const result = await queryAvailableUserList({
+      pageNumber: 1,
+      pageSize: 1000,
+      queryParam: {},
+    });
+    responsiblePersonOptions.value = (result.records || []).map((item) => ({
+      label: item.realname || item.staffNo,
+      value: item.id,
+    }));
+  };
+
+  const normalizeToNumberArray = (value: unknown): number[] => {
+    if (Array.isArray(value)) {
+      return value.map((item) => Number(item)).filter((item) => Number.isFinite(item));
+    }
 
-const convertFileUrlToFileItems = (fileUrl: string): FileItem[] => {
-  if (!fileUrl || !fileUrl.trim()) {
-    return [];
-  }
+    if (value == null || value === '') {
+      return [];
+    }
 
-  const urls = fileUrl.split(',').map(url => url.trim()).filter(url => url);
+    if (typeof value === 'string' && value.includes(',')) {
+      return value
+        .split(',')
+        .map((item) => Number(item.trim()))
+        .filter((item) => Number.isFinite(item));
+    }
 
-  return urls.map((url, index) => {
-    const urlParts = url.split('/');
-    const fileName = urlParts[urlParts.length - 1] || `附件${index + 1}`;
+    const numericValue = Number(value);
+    return Number.isFinite(numericValue) ? [numericValue] : [];
+  };
 
-    const extension = fileName.split('.').pop()?.toLowerCase() || '';
-    let fileType = 'pdf';
-    if (extension === 'doc' || extension === 'docx') {
-      fileType = 'word';
-    } else if (extension === 'xls' || extension === 'xlsx') {
-      fileType = 'excel';
-    } else if (extension === 'ppt' || extension === 'pptx') {
-      fileType = 'ppt';
-    }
+  const normalizeCooperateDeptIds = (value: unknown): string => {
+    return normalizeToNumberArray(value)
+      .map((item) => String(item))
+      .join(',');
+  };
+
+  const buildPayload = (): addSafetyCultureFilePageQuery => {
+    const responsibleDeptId = Number(ruleFormData.responsibleDeptId);
+    const responsiblePersonId = Number(ruleFormData.responsiblePersonId);
 
     return {
-      fileId: Date.now() + index,
-      fileName,
-      fileType,
-      fileSize: '0',
-      fileUrl: url,
+      planName: String(ruleFormData.planName || ''),
+      actionContent: String(ruleFormData.actionContent || ''),
+      categoryName: String(ruleFormData.categoryName || ''),
+      responsibleDeptId,
+      responsiblePersonId,
+      cooperateDeptIds: normalizeCooperateDeptIds(ruleFormData.cooperateDeptIds),
     };
-  });
-};
-
-const handleValidate = async () => {
-  if (!basicFormRef.value) return;
-  const res = await basicFormRef.value.validateForm();
-  return res;
-};
-
-const getDetail = async () => {
-  if (!currentId.value) return;
-  try {
-    const res = await querySafetyCultureActivityDetail(currentId.value);
-    if (res) {
-      ruleFormData.caseName = res.caseName || '';
-      ruleFormData.categoryName = res.categoryName || '';
-      ruleFormData.status = res.status ?? 1;
-      ruleFormData.description = res.description || '';
+  };
+
+  const handleValidate = async () => {
+    if (!basicFormRef.value) return;
+    const res = await basicFormRef.value.validateForm();
+    return res;
+  };
+
+  const getDetail = async () => {
+    if (!currentId.value) return;
+    try {
+      const res = await querySafetyCultureActivityDetail(currentId.value);
+      if (res) {
+        ruleFormData.planName = res.planName || '';
+        ruleFormData.actionContent = res.actionContent || '';
+        ruleFormData.categoryName = res.categoryName != null ? String(res.categoryName) : '';
+        ruleFormData.responsibleDeptId = res.responsibleDeptId != null ? Number(res.responsibleDeptId) : undefined;
+        ruleFormData.responsiblePersonId =
+          res.responsiblePersonId != null ? Number(res.responsiblePersonId) : undefined;
+        ruleFormData.cooperateDeptIds = normalizeToNumberArray(res.cooperateDeptIds);
+      }
+      cloneRuleFormData();
+    } catch (e) {
+      console.error('获取安全文化活动详情失败:', e);
+      ElMessage.error('获取详情失败');
     }
+  };
+
+  const handleSubmit = async () => {
+    const res = await handleValidate();
+    if (!res) return;
+
+    console.log('ruleFormData', ruleFormData);
+    try {
+      const payload = buildPayload();
+      if (isCreateMode.value) {
+        await addSafetyCultureActivityManagement(payload);
+        ElMessage.success('创建成功');
+      } else if (isEditMode.value && currentId.value) {
+        await updateSafetyCultureActivity({
+          id: currentId.value,
+          ...payload,
+        });
+        ElMessage.success('保存成功');
+      }
+
+      router.back();
+    } catch (e) {
+      console.error('保存安全文化活动失败:', e);
+      ElMessage.error('保存失败,请重试');
+    }
+  };
+
+  onMounted(() => {
     cloneRuleFormData();
-  } catch (e) {
-    console.error('获取事故案例详情失败:', e);
-    ElMessage.error('获取详情失败');
-  }
-};
-
-const handleSubmit = async () => {
-  const res = await handleValidate();
-  if (!res) return;
-
-  // if (!ruleFormData.attachmentUrl || ruleFormData.attachmentUrl.length === 0) {
-  //   ElMessage.warning('请上传文件');
-  //   return;
-  // }
-
-  console.log('ruleFormData', ruleFormData);
-
-  try {
-    // let fileUrl = '';
-    // if (ruleFormData.attachmentUrl && ruleFormData.attachmentUrl.length > 0) {
-    //   const existingFiles: string[] = [];
-    //   const newFiles: FileItem[] = [];
-
-    //   ruleFormData.attachmentUrl.forEach((file: FileItem) => {
-    //     if (file.fileUrl && !file.file) {
-    //       existingFiles.push(file.fileUrl);
-    //     } else {
-    //       newFiles.push(file);
-    //     }
-    //   });
-
-    //   let uploadedUrls: string[] = [];
-    //   if (newFiles.length > 0) {
-    //     const uploadedFiles = await formatAttachmentList(newFiles);
-    //     uploadedUrls = uploadedFiles
-    //       .map((file: any) => file.fileUrl || file.url || '')
-    //       .filter((url: string) => url);
-    //   }
-
-    //   const allUrls = [...existingFiles, ...uploadedUrls].filter((url: string) => url);
-    //   fileUrl = allUrls.length > 0 ? allUrls[0] : '';
-    // }
-
-    // const basePayload: newAccidentCases = {
-    //   caseName: ruleFormData.caseName,
-    //   categoryName: ruleFormData.categoryName,
-    //   description: ruleFormData.description,
-    //   attachmentUrl: ruleFormData.attachmentUrl.map((file: FileItem) => file.fileUrl || 'https://xxxxx/attachment.pdf'),
-    //   status: ruleFormData.status,
-    // };
-    // if (isCreateMode.value) {
-    //   await saveAccidentCase(basePayload);
-    //   ElMessage.success('创建成功');
-    // } else if (isEditMode.value && currentId.value) {
-    //   await updateAccidentCase({
-    //     id: currentId.value,
-    //     ...basePayload,
-    //   });
-    //   ElMessage.success('保存成功');
-    // }
-
-    router.back();
-  } catch (e) {
-    console.error('保存事故案例失败:', e);
-    ElMessage.error('保存失败,请重试');
+    loadDeptTreeData();
+    loadResponsiblePersonOptions();
+    // beforeRouteLeave();
+    if (isEditMode.value || isViewMode.value) {
+      getDetail();
+    }
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+
+  .editor-container {
+    width: 100%;
+    border: 1px solid #dcdfe6;
+    border-radius: 4px;
+    overflow: hidden;
   }
-};
 
-onMounted(() => {
-  cloneRuleFormData();
-  // beforeRouteLeave();
-  if (isEditMode.value || isViewMode.value) {
-    getDetail();
+  .content-display {
+    min-height: 200px;
+    padding: 12px;
+    border: 1px solid #dcdfe6;
+    border-radius: 4px;
+    background-color: #f5f7fa;
   }
-});
 
-</script>
+  .file-display {
+    .file-link {
+      color: #409eff;
+      text-decoration: none;
 
-<style scoped lang="scss">
-@use '@/styles/page-details-layout.scss' as *;
-
-.editor-container {
-  width: 100%;
-  border: 1px solid #dcdfe6;
-  border-radius: 4px;
-  overflow: hidden;
-}
-
-.content-display {
-  min-height: 200px;
-  padding: 12px;
-  border: 1px solid #dcdfe6;
-  border-radius: 4px;
-  background-color: #f5f7fa;
-}
-
-.file-display {
-  .file-link {
-    color: #409eff;
-    text-decoration: none;
-
-    &:hover {
-      text-decoration: underline;
+      &:hover {
+        text-decoration: underline;
+      }
     }
   }
-}
-
-.no-file {
-  color: rgba(0, 0, 0, 0.65);
-}
 
-.image-uploader {
-  :deep(.el-upload--picture-card) {
-    width: 80px !important;
-    height: 80px !important;
-    line-height: 80px;
+  .no-file {
+    color: rgba(0, 0, 0, 0.65);
   }
 
-  :deep(.el-upload-list--picture-card .el-upload-list__item) {
-    width: 80px !important;
-    height: 80px !important;
+  .image-uploader {
+    :deep(.el-upload--picture-card) {
+      width: 80px !important;
+      height: 80px !important;
+      line-height: 80px;
+    }
+
+    :deep(.el-upload-list--picture-card .el-upload-list__item) {
+      width: 80px !important;
+      height: 80px !important;
+    }
   }
-}
-</style>
+</style>

+ 27 - 50
src/views/production-safety/safety-culture/safetyCultureActivityManagement/configs/form.ts

@@ -1,17 +1,17 @@
-import { FileItem } from '@/components/UploadFiles/types';
 import { FormConfig } from '@/types/basic-form';
 
 export const ACADEMY_FILE_FORM_CONFIG: FormConfig[] = [
   {
-    prop: 'caseName',
+    prop: 'planName',
     label: '安全文化活动计划名称:',
     component: 'ElInput',
     componentProps: {
       placeholder: '请输入文件名称',
     },
-  },{
-    prop: 'caseName',
-    label: '行动项内容',
+  },
+  {
+    prop: 'actionContent',
+    label: '行动项内容:',
     component: 'ElInput',
     componentProps: {
       placeholder: '请输入行动项内容',
@@ -20,62 +20,39 @@ export const ACADEMY_FILE_FORM_CONFIG: FormConfig[] = [
   {
     prop: 'categoryName',
     label: '分类名称:',
-    component: 'ElSelect',
-    componentProps: {
-      placeholder: '请选择分类名称',
-    },
-    selectOptions: [
-      { label: '外部院级文件', value: '外部院级文件' },
-      { label: '内部院级文件', value: '内部院级文件' },
-    ],
+    slot: 'categoryName',
   },
   {
-    prop: 'categoryName',
+    prop: 'responsibleDeptId',
     label: '责任部门:',
-    component: 'ElSelect',
-    componentProps: {
-      placeholder: '请选择责任部门',
-    },
-    selectOptions: [
-      { label: '外部院级文件', value: '外部院级文件' },
-      { label: '内部院级文件', value: '内部院级文件' },
-    ],
-  },{
-    prop: 'categoryName',
+    slot: 'responsibleDeptId',
+  },
+  {
+    prop: 'responsiblePersonId',
     label: '责任人:',
-    component: 'ElSelect',
-    componentProps: {
-      placeholder: '请选择责任人',
-    },
-    selectOptions: [
-      { label: '外部院级文件', value: '外部院级文件' },
-      { label: '内部院级文件', value: '内部院级文件' },
-    ],
-  },{
-    prop: 'categoryName',
+    slot: 'responsiblePersonId',
+  },
+  {
+    prop: 'cooperateDeptIds',
     label: '配合部门:',
-    component: 'ElSelect',
-    componentProps: {
-      placeholder: '请选择配合部门',
-    },
-    selectOptions: [
-      { label: '外部院级文件', value: '外部院级文件' },
-      { label: '内部院级文件', value: '内部院级文件' },
-    ],
+    slot: 'cooperateDeptIds',
   },
 ];
 
 export const ACADEMY_FILE_FORM_DATA = {
-  caseName: '',
+  planName: '',
+  actionContent: '',
   categoryName: '',
-  attachmentUrl: [] as FileItem[],
-  description: '',
-  status: 1,
+  responsibleDeptId: undefined as number | undefined,
+  responsiblePersonId: undefined as number | undefined,
+  cooperateDeptIds: [] as number[],
 };
 
 export const ACADEMY_FILE_FORM_RULES = {
-  caseName: [{ required: true, message: '请输入事故案例名称', trigger: 'blur' }],
-  categoryName: [{ required: true, message: '请选择分类名称', trigger: 'change' }],
-  description: [{ required: true, message: '请输入案例描述', trigger: 'blur' }],
-  status: [{ required: true, message: '请选择状态', trigger: 'change' }],
+  planName: [{ required: true, message: '请输入安全活动计划名称', trigger: 'blur' }],
+  actionContent: [{ required: true, message: '请选择行动项内容', trigger: 'change' }],
+  categoryName: [{ required: true, message: '请输入分类名称', trigger: 'blur' }],
+  responsibleDeptId: [{ required: true, message: '请选择责任部门', trigger: 'change' }],
+  responsiblePersonId: [{ required: true, message: '请选择责任人', trigger: 'change' }],
+  cooperateDeptIds: [{ required: true, message: '请选择配合部门', trigger: 'change' }],
 };

+ 4 - 4
src/views/production-safety/safety-culture/safetyCultureActivityManagement/configs/tables.ts

@@ -30,7 +30,7 @@ export const INVENTORY_TABLE_COLUMNS: TableColumnProps[] = [
   },
   {
     label: '分类名称',
-    prop: 'categoryName',
+    prop: 'categoryNameDisplay',
     align: 'left',
     minWidth: '120px',
   },
@@ -43,19 +43,19 @@ export const INVENTORY_TABLE_COLUMNS: TableColumnProps[] = [
   },
   {
     label: '责任部门',
-    prop: 'responsibleDeptName',
+    prop: 'responsibleDeptNameDisplay',
     align: 'left',
     minWidth: '160px',
   },
   {
     label: '责任人',
-    prop: 'specificPersonName',
+    prop: 'responsiblePersonNameDisplay',
     align: 'left',
     minWidth: '160px',
   },
   {
     label: '配合部门',
-    prop: 'specificDeptName',
+    prop: 'cooperateDeptNameDisplay',
     align: 'left',
     minWidth: '160px',
   },

+ 126 - 6
src/views/production-safety/safety-culture/safetyCultureActivityManagement/safetyCultureActivityManagement.vue

@@ -35,8 +35,8 @@
               <div class="select-box--item">
                 <span>分类名称:</span>
                 <el-select v-model="queryParams.classifyName" placeholder="请选择分类" clearable>
-                  <el-option label="外部院级文件" value="外部院级文件" />
-                  <el-option label="内部院级文件" value="内部院级文件" />
+                  <el-option label="安全综合工作" value="安全综合工作" />
+                  <el-option label="生产安全工作" value="生产安全工作" />
                 </el-select>
               </div>
               <div class="select-box--item">
@@ -90,9 +90,11 @@ import { useRouter } from 'vue-router';
 import {
   safetyCultureActivityManagementFilePage,
   deleteSafetyCultureActivityManagement,
+  getAllDepartments,
   type safetyCultureFileQuery,
   type safetyCultureFilePageQuery,
 } from '@/api/safety-culture';
+import type { DeptTree } from '@/types/dept/type';
 import { downloadByData } from '@/utils/file/download';
 import BatchImport from '@/components/batch-import/BatchImport.vue';
 import { useGlobSetting } from '@/hooks/setting';
@@ -108,6 +110,115 @@ const basicTableRef = ref<InstanceType<typeof BasicTable>>();
 const { tableConfig, pagination } = useTableConfig(INVENTORY_TABLE_COLUMNS, TABLE_OPTIONS);
 
 const tableData = ref<any[]>([]);
+const deptNameMap = ref<Record<string, string>>({});
+
+const CATEGORY_NAME_MAP: Record<string, string> = {
+  '0': '外部院级文件',
+  '1': '内部院级文件',
+  '2': '内部院级文件',
+};
+
+const normalizeCategoryName = (row: any): string => {
+  const raw = row?.categoryName ?? row?.classifyName ?? row?.category;
+  if (raw == null || raw === '') {
+    return '-';
+  }
+  const stringValue = String(raw);
+  if (stringValue.includes('外部') || stringValue.includes('内部')) {
+    return stringValue;
+  }
+  return CATEGORY_NAME_MAP[stringValue] || stringValue;
+};
+
+const normalizeListText = (value: unknown): string => {
+  if (Array.isArray(value)) {
+    const list = value.map((item) => String(item).trim()).filter((item) => item);
+    return list.length ? list.join('、') : '-';
+  }
+  if (value == null || value === '') {
+    return '-';
+  }
+  const text = String(value).trim();
+  if (!text) {
+    return '-';
+  }
+  return text.includes(',') ? text.split(',').map((item) => item.trim()).filter((item) => item).join('、') || '-' : text;
+};
+
+const parseIdList = (value: unknown): string[] => {
+  if (Array.isArray(value)) {
+    return value.map((item) => String(item).trim()).filter((item) => item);
+  }
+  if (value == null || value === '') {
+    return [];
+  }
+  const text = String(value).trim();
+  if (!text) {
+    return [];
+  }
+  return text.split(',').map((item) => item.trim()).filter((item) => item);
+};
+
+const flattenDeptTree = (tree: DeptTree[]): DeptTree[] => {
+  const result: DeptTree[] = [];
+  const walk = (nodes: DeptTree[]) => {
+    nodes.forEach((node) => {
+      result.push(node);
+      if (node.children?.length) {
+        walk(node.children);
+      }
+    });
+  };
+  walk(tree || []);
+  return result;
+};
+
+const loadDeptNameMap = async () => {
+  try {
+    const deptTree = await getAllDepartments();
+    const flatList = flattenDeptTree(deptTree || []);
+    const map: Record<string, string> = {};
+    flatList.forEach((dept) => {
+      if (dept.id != null) {
+        map[String(dept.id)] = dept.deptName;
+      }
+    });
+    deptNameMap.value = map;
+  } catch (error) {
+    console.error('加载部门字典失败:', error);
+    deptNameMap.value = {};
+  }
+};
+
+const normalizeCooperateDeptName = (row: any): string => {
+  const rawName = row?.cooperateDeptName || row?.specificDeptName;
+  if (rawName) {
+    return normalizeListText(rawName);
+  }
+
+  const ids = parseIdList(row?.cooperateDeptIds);
+  if (!ids.length) {
+    return '-';
+  }
+
+  const names = ids.map((id) => deptNameMap.value[id] || id).filter((item) => item);
+  return names.length ? names.join('、') : '-';
+};
+
+const normalizeResponsibleDeptName = (row: any): string => {
+  const rawName = row?.responsibleDeptName;
+  if (rawName) {
+    return normalizeListText(rawName);
+  }
+
+  const ids = parseIdList(row?.responsibleDeptId);
+  if (!ids.length) {
+    return '-';
+  }
+
+  const names = ids.map((id) => deptNameMap.value[id] || id).filter((item) => item);
+  return names.length ? names.join('、') : '-';
+};
 
 const queryParams = reactive<safetyCultureFileQuery>({
   keyword: '', // 文件名称/编号(模糊查询)
@@ -147,8 +258,15 @@ async function getTableData() {
     };
     const res = await safetyCultureActivityManagementFilePage(pageQuery);
     if (res) {
-      // 映射返回数据字段到表格字段
-      tableData.value = res.records
+      tableData.value = (res.records || []).map((item: any) => ({
+        ...item,
+        categoryNameDisplay: normalizeCategoryName(item),
+        responsibleDeptNameDisplay: normalizeResponsibleDeptName(item),
+        responsiblePersonNameDisplay: normalizeListText(
+          item.responsiblePersonName || item.specificPersonName || item.responsiblePersonId,
+        ),
+        cooperateDeptNameDisplay: normalizeCooperateDeptName(item),
+      }));
       pagination.total = res.totalRow || 0;
     }
   } catch (e) {
@@ -271,7 +389,9 @@ const activityRegistration = async (id: number) => {
 };
 
 onMounted(() => {
-  getTableData();
+  loadDeptNameMap().finally(() => {
+    getTableData();
+  });
   // loginSw();
 });
 </script>
@@ -288,4 +408,4 @@ onMounted(() => {
 }
 
 
-</style>
+</style>

+ 367 - 267
src/views/production-safety/safety-culture/safetyCultureMaterialManagement/components/safetyCultureMaterialManagementDetail.vue

@@ -1,349 +1,449 @@
 <template>
-    <main class="safety-platform-container__main">
-        <BasicForm ref="basicFormRef" :formData="ruleFormData" :formRules="isViewMode ? undefined : formRules"
-            :formConfig="computedFormConfig">
-            <template #fileFormat>
-                <el-radio-group v-model="ruleFormData.fileFormat" :disabled="isViewMode">
-                    <el-radio value="PDF">PDF</el-radio>
-                    <el-radio value="WORD">WORD</el-radio>
-                </el-radio-group>
-            </template>
-            <template #fileUrl>
-                <UploadFiles label="上传文件" :maxCount="1" :file-list="ruleFormData.fileUrlList" :disabled="isViewMode"
-                    :allow-all-file-types="true" @uploadSuccess="handleUploadSuccess" />
-            </template>
-            <template #content>
-                <div class="editor-container">
-                    <Toolbar style="border-bottom: 1px solid #dcdfe6" :editor="editorRef" />
-                    <Editor style="height: 400px; overflow-y: auto" v-model="ruleFormData.content" mode="default"
-                        :defaultConfig="editorConfig" @on-created="handleEditorCreated"
-                        @on-change="handleEditorChange" />
-                </div>
-            </template>
-            <template #imageFileUrl>
-                <el-upload class="image-uploader" action="/api/admin/minio/uploadFile"
-                    :file-list="ruleFormData.imageFileUrl" :disabled="isViewMode" :limit="5"
-                    :on-success="handleImageUploadSuccess" :on-remove="handleImageRemove" list-type="picture-card">
-                    <el-icon>
-                        <Plus />
-                    </el-icon>
-                    <template #tip>
-                        <div class="el-upload__tip">
-                            支持格式:.rar .zip .doc .docx .pdf ,单个文件不能超过20MB
-                        </div>
-                    </template>
-                </el-upload>
-            </template>
-            <template #status>
-                <el-radio-group v-model="ruleFormData.status" :disabled="isViewMode">
-                    <el-radio :value="1">启用</el-radio>
-                    <el-radio :value="0">禁用</el-radio>
-                </el-radio-group>
-            </template>
-        </BasicForm>
-    </main>
-    <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 class="safety-platform-container__main">
+    <BasicForm
+      ref="basicFormRef"
+      :formData="ruleFormData"
+      :formRules="isViewMode ? undefined : formRules"
+      :formConfig="computedFormConfig"
+    >
+      <template #fileFormat>
+        <el-radio-group v-model="ruleFormData.fileFormat" :disabled="isViewMode">
+          <el-radio value="PDF">PDF</el-radio>
+          <el-radio value="WORD">WORD</el-radio>
+        </el-radio-group>
+      </template>
+      <template #fileUrl>
+        <!-- <UploadFiles label="上传文件" :maxCount="1" :file-list="ruleFormData.fileUrlList" :disabled="isViewMode"
+          :allow-all-file-types="true" @uploadSuccess="handleUploadSuccess" /> -->
+
+        <UploadFiles
+          v-if="!isViewMode"
+          label="上传文件"
+          :maxCount="1"
+          :file-list="ruleFormData.attachmentUrl"
+          :disabled="isViewMode"
+          :allow-all-file-types="true"
+          @uploadSuccess="(list: FileItem[]) => handleUploadSuccess(list)"
+        />
+        <div class="file-list" v-else>
+          <div class="file-item" v-for="file in ruleFormData.attachmentUrl" :key="file.fileId">
+            <span class="file-item--name">{{ file.fileName }}</span>
+            <div class="file-item--footer">
+              <el-button link type="primary" @click="previewOnline(file.fileUrl, file.fileType)">预览</el-button>
+              <!-- <el-button link type="primary" @click.stop="downloadFile(file.fileUrl, file.fileName)"
+                  >下载</el-button
+                > -->
+            </div>
+          </div>
+        </div>
+      </template>
+      <template #content>
+        <div class="editor-container">
+          <Toolbar style="border-bottom: 1px solid #dcdfe6" :editor="editorRef" />
+          <Editor
+            style="height: 400px; overflow-y: auto"
+            v-model="ruleFormData.content"
+            mode="default"
+            :defaultConfig="editorConfig"
+            @on-created="handleEditorCreated"
+            @on-change="handleEditorChange"
+          />
+        </div>
+      </template>
+      <template #imageFileUrl>
+        <el-upload
+          :key="approvalUploadKey"
+          ref="approvalUploadRef"
+          v-model:file-list="approvalImageFileList"
+          :auto-upload="false"
+          list-type="picture-card"
+          :limit="1"
+          :disabled="isViewMode"
+          :on-change="handleApprovalImageChange"
+          :on-exceed="handleApprovalImageExceed"
+          :on-preview="handleApprovalPictureCardPreview"
+          :on-remove="handleApprovalImageRemove"
+        >
+          <el-icon><Plus /></el-icon>
+          <template #file="{ file }">
+            <div>
+              <img class="el-upload-list__item-thumbnail" :src="file.url" alt="" />
+              <span class="el-upload-list__item-actions" v-if="!isViewMode">
+                <span class="el-upload-list__item-preview" @click="handleApprovalPictureCardPreview(file)">
+                  <el-icon><ZoomIn /></el-icon>
+                </span>
+                <span class="el-upload-list__item-delete" @click.stop="handleApprovalDeleteClick()">
+                  <el-icon><Delete /></el-icon>
+                </span>
+              </span>
+            </div>
+          </template>
+        </el-upload>
+      </template>
+      <template #status>
+        <el-radio-group v-model="ruleFormData.status" :disabled="isViewMode">
+          <el-radio :value="1">启用</el-radio>
+          <el-radio :value="0">禁用</el-radio>
+        </el-radio-group>
+      </template>
+    </BasicForm>
+    <PreviewOnline ref="previewOnlineRef" />
+  </main>
+  <el-dialog v-model="dialogVisible" width="30%">
+    <div class="dialog-content">
+      <img :src="dialogImageUrl" alt="Preview Image" />
+    </div>
+  </el-dialog>
+  <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>
 </template>
 
 <script setup lang="ts">
-import { computed, onMounted, ref, shallowRef, onBeforeUnmount } from 'vue';
-import { useRoute, useRouter } from 'vue-router';
-import { ElMessage } from 'element-plus';
-import BasicForm from '@/components/BasicForm.vue';
-import UploadFiles from '@/components/UploadFiles/UploadFiles.vue';
-import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
-import '@wangeditor/editor/dist/css/style.css';
-import { useFormConfigHook } from '@/hooks/useFormConfigHook';
-import { ACADEMY_FILE_FORM_CONFIG, ACADEMY_FILE_FORM_DATA, ACADEMY_FILE_FORM_RULES } from '../configs/form';
-import {
+  import { computed, onMounted, ref, shallowRef, onBeforeUnmount } from 'vue';
+  import { useRoute, useRouter } from 'vue-router';
+  import { ElMessage } from 'element-plus';
+  import BasicForm from '@/components/BasicForm.vue';
+  import UploadFiles from '@/components/UploadFiles/UploadFiles.vue';
+  import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
+  import '@wangeditor/editor/dist/css/style.css';
+  import { useFormConfigHook } from '@/hooks/useFormConfigHook';
+  import { ACADEMY_FILE_FORM_CONFIG, ACADEMY_FILE_FORM_DATA, ACADEMY_FILE_FORM_RULES } from '../configs/form';
+  import {
     queryAcademyFileById,
     saveAcademyFile,
     updateAcademyFile,
     type safetyCultureFile,
-} from '@/api/safety-culture';
-import type { FileItem } from '@/components/UploadFiles/types';
-import { formatAttachmentList } from '@/components/UploadFiles/utils';
-import { Plus, Delete } from '@element-plus/icons-vue';
-
-const router = useRouter();
-const route = useRoute();
-
-const operate = computed(() => (route.query.operate as string) || 'safety-culture-material-create');
-const currentId = computed(() => Number(route.query.id));
-
-const isCreateMode = computed(() => operate.value === 'safety-culture-material-create');
-const isEditMode = computed(() => operate.value === 'safety-culture-material-edit');
-const isViewMode = computed(() => operate.value === 'safety-culture-material-view');
-
-const { ruleFormData, formRules, ruleFormConfig, cloneRuleFormData, beforeRouteLeave } =
-    useFormConfigHook(ACADEMY_FILE_FORM_CONFIG, ACADEMY_FILE_FORM_DATA, ACADEMY_FILE_FORM_RULES);
-
-// 查看模式下,所有字段设为只读
-const viewFormConfig = ref(
+  } from '@/api/safety-culture';
+  import { uploadFileApi, UPLOAD_BIZ_TYPE } from '@/api/minio';
+  import type { FileItem } from '@/components/UploadFiles/types';
+  import { formatAttachmentList } from '@/components/UploadFiles/utils';
+  import { Plus, Delete, ZoomIn } from '@element-plus/icons-vue';
+  import { genFileId, type UploadProps, type UploadUserFile, type UploadRawFile, type UploadInstance } from 'element-plus';
+  import PreviewOnline from '@/views/disaster/components/PreviewOnline.vue';
+
+  const router = useRouter();
+  const route = useRoute();
+
+  const operate = computed(() => (route.query.operate as string) || 'safety-culture-material-create');
+  const currentId = computed(() => Number(route.query.id));
+
+  const isCreateMode = computed(() => operate.value === 'safety-culture-material-create');
+  const isEditMode = computed(() => operate.value === 'safety-culture-material-edit');
+  const isViewMode = computed(() => operate.value === 'safety-culture-material-view');
+  const previewOnlineRef = ref<InstanceType<typeof PreviewOnline>>();
+  const approvalUploadRef = ref<UploadInstance>();
+  const approvalImageFileList = ref<UploadUserFile[]>([]);
+  const approvalUploadKey = ref(0);
+  const dialogImageUrl = ref('');
+  const dialogVisible = ref(false);
+
+  const { ruleFormData, formRules, ruleFormConfig, cloneRuleFormData, beforeRouteLeave } = useFormConfigHook(
+    ACADEMY_FILE_FORM_CONFIG,
+    ACADEMY_FILE_FORM_DATA,
+    ACADEMY_FILE_FORM_RULES,
+  );
+
+  // 查看模式下,所有字段设为只读
+  const viewFormConfig = ref(
     ACADEMY_FILE_FORM_CONFIG.map((item) => ({
-        ...item,
-        componentProps: {
-            ...item.componentProps,
-            disabled: true,
-        },
+      ...item,
+      componentProps: {
+        ...item.componentProps,
+        disabled: true,
+      },
     })),
-);
+  );
 
-const computedFormConfig = computed(() => {
+  const computedFormConfig = computed(() => {
     if (isViewMode.value) {
-        return viewFormConfig.value;
+      return viewFormConfig.value;
     }
     return ruleFormConfig.value;
-});
+  });
 
-const basicFormRef = ref<InstanceType<typeof BasicForm>>();
+  const basicFormRef = ref<InstanceType<typeof BasicForm>>();
 
-// 富文本编辑器
-const editorRef = shallowRef();
-const editorConfig = computed(() => ({
+  // 富文本编辑器
+  const editorRef = shallowRef();
+  const editorConfig = computed(() => ({
     placeholder: '请输入文档内容',
     MENU_CONF: {},
-}));
+  }));
 
-const handleEditorCreated = (editor: any) => {
+  const handleEditorCreated = (editor: any) => {
     editorRef.value = editor;
-};
+    if (isViewMode.value) {
+      editor.disable();
+    }
+  };
 
-const handleEditorChange = () => {
+  const handleEditorChange = () => {
     // 编辑器内容变化时的处理
-};
-
-// 文件上传
-const handleUploadSuccess = (files: FileItem[]) => {
-    ruleFormData.fileUrlList = files;
-};
-
-// 将逗号分隔的URL字符串转换为FileItem数组
-const convertFileUrlToFileItems = (fileUrl: string): FileItem[] => {
+  };
+
+  // 文件上传
+  const handleUploadSuccess = (files: FileItem[]) => {
+    ruleFormData.attachmentUrl = files;
+  };
+
+  const handleApprovalPictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
+    dialogImageUrl.value = uploadFile.url || '';
+    dialogVisible.value = true;
+  };
+
+  const resetApprovalImageUpload = () => {
+    approvalUploadRef.value?.clearFiles();
+    ruleFormData.imageUrls = [];
+    approvalImageFileList.value = [];
+    approvalUploadKey.value += 1;
+  };
+
+  const handleApprovalDeleteClick = () => {
+    resetApprovalImageUpload();
+  };
+
+  const handleApprovalImageRemove: UploadProps['onRemove'] = () => {
+    resetApprovalImageUpload();
+  };
+
+  const handleApprovalImageChange = (uploadFile: UploadUserFile) => {
+    if (!uploadFile.raw) return;
+
+    uploadFileApi({
+      file: uploadFile.raw,
+      bizType: UPLOAD_BIZ_TYPE['DICTIONARY'],
+      fileName: uploadFile.raw.name,
+    })
+      .then((res) => {
+        ruleFormData.imageUrls = [res.url];
+        approvalImageFileList.value = [
+          {
+            name: uploadFile.name || res.url.split('/').pop() || 'image',
+            url: res.url,
+          },
+        ];
+      })
+      .catch(() => {
+        ElMessage.error('图片上传失败,请重试');
+      });
+  };
+
+  const handleApprovalImageExceed: UploadProps['onExceed'] = (files) => {
+    const uploadInstance = approvalUploadRef.value;
+    if (!uploadInstance) return;
+
+    uploadInstance.clearFiles();
+    const file = files[0] as UploadRawFile;
+    file.uid = genFileId();
+    uploadInstance.handleStart(file);
+  };
+
+  // 将逗号分隔的URL字符串转换为FileItem数组
+  const convertFileUrlToFileItems = (fileUrl: string): FileItem[] => {
     if (!fileUrl || !fileUrl.trim()) {
-        return [];
+      return [];
     }
 
     // 按逗号分割URL
-    const urls = fileUrl.split(',').map(url => url.trim()).filter(url => url);
+    const urls = fileUrl
+      .split(',')
+      .map((url) => url.trim())
+      .filter((url) => url);
 
     return urls.map((url, index) => {
-        // 从URL中提取文件名
-        const urlParts = url.split('/');
-        const fileName = urlParts[urlParts.length - 1] || `附件${index + 1}`;
-
-        // 根据文件扩展名判断文件类型
-        const extension = fileName.split('.').pop()?.toLowerCase() || '';
-        let fileType = 'pdf';
-        if (extension === 'doc' || extension === 'docx') {
-            fileType = 'word';
-        } else if (extension === 'xls' || extension === 'xlsx') {
-            fileType = 'excel';
-        } else if (extension === 'ppt' || extension === 'pptx') {
-            fileType = 'ppt';
-        }
-
-        return {
-            fileId: Date.now() + index,
-            fileName,
-            fileType,
-            fileSize: '0',
-            fileUrl: url,
-        };
+      // 从URL中提取文件名
+      const urlParts = url.split('/');
+      const fileName = urlParts[urlParts.length - 1] || `附件${index + 1}`;
+
+      // 根据文件扩展名判断文件类型
+      const extension = fileName.split('.').pop()?.toLowerCase() || '';
+      let fileType = 'pdf';
+      if (extension === 'doc' || extension === 'docx') {
+        fileType = 'word';
+      } else if (extension === 'xls' || extension === 'xlsx') {
+        fileType = 'excel';
+      } else if (extension === 'ppt' || extension === 'pptx') {
+        fileType = 'ppt';
+      }
+
+      return {
+        fileId: Date.now() + index,
+        fileName,
+        fileType,
+        fileSize: '0',
+        fileUrl: url,
+      };
     });
-};
+  };
 
-const handleValidate = async () => {
+  const handleValidate = async () => {
     if (!basicFormRef.value) return;
     const res = await basicFormRef.value.validateForm();
     return res;
-};
+  };
 
-const getDetail = async () => {
+  const getDetail = async () => {
     if (!currentId.value) return;
     try {
-        const res = await queryAcademyFileById(currentId.value);
-        if (res) {
-            // 映射接口字段到表单字段
-            ruleFormData.fileName = res.fileName || '';
-            ruleFormData.categoryName = res.categoryName || '';
-            ruleFormData.fileCode = res.fileCode || '';
-            ruleFormData.fileVersion = res.fileVersion || '';
-            ruleFormData.fileFormat = res.fileFormat === 1 ? 'PDF' : 'WORD';
-            ruleFormData.publishDate = res.publishDate || '';
-            ruleFormData.fileUrl = res.fileUrl || '';
-            ruleFormData.content = res.content || '';
-            ruleFormData.status = res.status ?? 1;
-
-            // 如果有文件URL,转换为FileItem格式
-            ruleFormData.fileUrlList = convertFileUrlToFileItems(res.fileUrl || '');
-        }
-        cloneRuleFormData();
+      const res = await queryAcademyFileById(currentId.value);
+      if (res) {
+        // 映射接口字段到表单字段
+        ruleFormData.fileName = res.fileName || '';
+        ruleFormData.categoryName = res.categoryName || '';
+        ruleFormData.fileCode = res.fileCode || '';
+        ruleFormData.fileVersion = res.fileVersion || '';
+        ruleFormData.fileFormat = res.fileFormat === 1 ? 'PDF' : 'WORD';
+        ruleFormData.publishDate = res.publishDate || '';
+        ruleFormData.content = res.content || '';
+        ruleFormData.status = res.status ?? 1;
+        ruleFormData.imageUrls = JSON.parse(res.imageUrls || '[]');
+        approvalImageFileList.value = (ruleFormData.imageUrls || []).map((url, index) => ({
+          name: `图片${index + 1}`,
+          url,
+        }));
+
+        // 如果有文件URL,转换为FileItem格式
+        ruleFormData.attachmentUrl = JSON.parse(res.attachmentUrl || res.fileUrl || '');
+      }
+      cloneRuleFormData();
     } catch (e) {
-        console.error('获取院级文件详情失败:', e);
-        ElMessage.error('获取详情失败');
+      console.error('获取院级文件详情失败:', e);
+      ElMessage.error('获取详情失败');
     }
-};
+  };
 
-const handleSubmit = async () => {
+  const handleSubmit = async () => {
     const res = await handleValidate();
     if (!res) return;
 
     // 验证文件上传(必填)
-    if (!ruleFormData.fileUrlList || ruleFormData.fileUrlList.length === 0) {
-        ElMessage.warning('请上传文件');
-        return;
+    if (!ruleFormData.attachmentUrl || ruleFormData.attachmentUrl.length === 0) {
+      ElMessage.warning('请上传文件');
+      return;
     }
     try {
-        // 处理文件上传:先上传文件获取 URL,然后提取 fileUrl
-        let fileUrl = '';
-        if (ruleFormData.fileUrlList && ruleFormData.fileUrlList.length > 0) {
-            // 分离已有URL的文件和新上传的文件
-            const existingFiles: string[] = [];
-            const newFiles: FileItem[] = [];
-
-            ruleFormData.fileUrlList.forEach((file: FileItem) => {
-                // 如果文件已经有 fileUrl 且没有 file 对象,说明是已有文件
-                if (file.fileUrl && !file.file) {
-                    existingFiles.push(file.fileUrl);
-                } else {
-                    // 否则是需要上传的新文件
-                    newFiles.push(file);
-                }
-            });
-
-            // 上传新文件
-            let uploadedUrls: string[] = [];
-            if (newFiles.length > 0) {
-                const uploadedFiles = await formatAttachmentList(newFiles);
-                uploadedUrls = uploadedFiles
-                    .map((file: any) => file.fileUrl || file.url || '')
-                    .filter((url: string) => url);
-            }
-
-            // 合并已有URL和新上传的URL,取第一个作为fileUrl
-            const allUrls = [...existingFiles, ...uploadedUrls].filter((url: string) => url);
-            fileUrl = allUrls.length > 0 ? allUrls[0] : '';
-        }
-
-        const basePayload: safetyCultureFile = {
-            fileName: ruleFormData.fileName,
-            classifyName: ruleFormData.classifyName,
-            categoryName: ruleFormData.categoryName,
-            fileCode: ruleFormData.fileCode,
-            fileVersion: ruleFormData.fileVersion,
-            fileFormat: ruleFormData.fileFormat === 'PDF' ? 1 : 2,
-            publishDate: ruleFormData.publishDate,
-            attachmentUrl: fileUrl || '',
-            content: ruleFormData.content || undefined,
-            status: ruleFormData.status ?? 1,
-            releaseDate: ruleFormData.publishDate,
-            description: '',
-            caseName: '',
-            imageUrls: ruleFormData.imageFileUrl.map(item => item.fileUrl || item.url || '').join(',') || '',
-        };
-        if (isCreateMode.value) {
-            await saveAcademyFile(basePayload);
-            ElMessage.success('创建成功');
-        } else if (isEditMode.value && currentId.value) {
-            await updateAcademyFile({
-                id: currentId.value,
-                ...basePayload,
-            });
-            ElMessage.success('保存成功');
-        }
-
-        router.back();
+      const uploadedFileList = await formatAttachmentList(ruleFormData.attachmentUrl);
+
+      const basePayload: any = {
+        fileName: ruleFormData.fileName,
+        classifyName: ruleFormData.classifyName,
+        categoryName: ruleFormData.categoryName,
+        fileCode: ruleFormData.fileCode,
+        fileVersion: ruleFormData.fileVersion,
+        fileFormat: ruleFormData.fileFormat === 'PDF' ? 1 : 2,
+        publishDate: ruleFormData.publishDate,
+        attachmentUrl: JSON.stringify(uploadedFileList),
+        content: ruleFormData.content || undefined,
+        status: ruleFormData.status ?? 1,
+        releaseDate: ruleFormData.publishDate,
+        description: '',
+        caseName: '',
+        imageUrls: JSON.stringify(ruleFormData.imageUrls),
+      };
+      if (isCreateMode.value) {
+        await saveAcademyFile(basePayload);
+        ElMessage.success('创建成功');
+      } else if (isEditMode.value && currentId.value) {
+        await updateAcademyFile({
+          id: currentId.value,
+          ...basePayload,
+        });
+        ElMessage.success('保存成功');
+      }
+
+      router.back();
     } catch (e) {
-        console.error('保存院级文件失败:', e);
-        ElMessage.error('保存失败,请重试');
+      console.error('保存院级文件失败:', e);
+      ElMessage.error('保存失败,请重试');
     }
-};
-
-const handleImageUploadSuccess = (response: any, file: any, fileList: any[]) => {
-    const imageUrl = response.data?.url || file.url;
-    ruleFormData.imageFileUrl = fileList.map(item => ({
-        fileId: Date.now() + Math.random(),
-        fileName: item.name,
-        fileType: 'image',
-        fileSize: (item.size / 1024).toFixed(2) + 'KB',
-        fileUrl: imageUrl || item.url
-    }));
-};
-
-const handleImageRemove = (file: any, fileList: any[]) => {
-    ruleFormData.imageFileUrl = fileList.map(item => ({
-        fileId: item.fileId || Date.now() + Math.random(),
-        fileName: item.name || item.fileName,
-        fileType: 'image',
-        fileSize: item.size ? (item.size / 1024).toFixed(2) + 'KB' : item.fileSize,
-        fileUrl: item.url || item.fileUrl
-    }));
-};
-
-onMounted(() => {
+  };
+
+  const previewOnline = (url: string | undefined, type) => {
+    if (url) {
+      previewOnlineRef.value?.open(url, type);
+    }
+  };
+
+  onMounted(() => {
     cloneRuleFormData();
     // beforeRouteLeave();
     if (isEditMode.value || isViewMode.value) {
-        getDetail();
+      getDetail();
     }
-});
+  });
 
-onBeforeUnmount(() => {
+  onBeforeUnmount(() => {
     const editor = editorRef.value;
     if (editor == null) return;
     editor.destroy();
-});
+  });
 </script>
 
 <style scoped lang="scss">
-@use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-details-layout.scss' as *;
 
-.editor-container {
+  .editor-container {
     width: 100%;
     border: 1px solid #dcdfe6;
     border-radius: 4px;
     overflow: hidden;
-}
+  }
 
-.content-display {
+  .content-display {
     min-height: 200px;
     padding: 12px;
     border: 1px solid #dcdfe6;
     border-radius: 4px;
     background-color: #f5f7fa;
-}
+  }
 
-.file-display {
+  .file-display {
     .file-link {
-        color: #409eff;
-        text-decoration: none;
+      color: #409eff;
+      text-decoration: none;
 
-        &:hover {
-            text-decoration: underline;
-        }
+      &:hover {
+        text-decoration: underline;
+      }
     }
-}
+  }
 
-.no-file {
+  .no-file {
     color: rgba(0, 0, 0, 0.65);
-}
+  }
 
-.image-uploader {
+  .image-uploader {
     :deep(.el-upload--picture-card) {
-        width: 80px !important;
-        height: 80px !important;
-        line-height: 80px;
+      width: 80px !important;
+      height: 80px !important;
+      line-height: 80px;
     }
 
     :deep(.el-upload-list--picture-card .el-upload-list__item) {
-        width: 80px !important;
-        height: 80px !important;
+      width: 80px !important;
+      height: 80px !important;
+    }
+  }
+
+  .dialog-content {
+    img {
+      width: 100%;
+      height: 100%;
     }
-}
+  }
+</style>
+
+<style lang="scss">
+  .w-e-full-screen-container {
+    inset: 0 !important;
+    z-index: 3000 !important;
+  }
+
+  .w-e-full-screen-container .w-e-text-container {
+    height: calc(100vh - 42px) !important;
+  }
 </style>

+ 3 - 3
src/views/production-safety/safety-culture/safetyCultureMaterialManagement/configs/form.ts

@@ -68,7 +68,7 @@ export const ACADEMY_FILE_FORM_CONFIG: FormConfig[] = [
     slot: 'status',
   },
   {
-    prop: 'imageFileUrl',
+    prop: 'imageUrls',
     label: '图片上传:',
     slot: 'imageFileUrl',
   },
@@ -82,10 +82,10 @@ export const ACADEMY_FILE_FORM_DATA = {
   fileFormat: '',
   publishDate: '',
   fileUrl: '',
-  fileUrlList: [] as any[], // 文件列表(FileItem数组)
+  attachmentUrl: '' as any, // 文件列表(FileItem数组)
   content: '',
   status: 1, // 默认启用
-  imageFileUrl:  [] as any[],
+  imageUrls:  '' as any,
   categoryName: '',
 };
 

+ 306 - 259
src/views/production-safety/safety-culture/safetyPublicityBoardManagement/components/safetyPublicityBoardManagementDetail.vue

@@ -1,7 +1,11 @@
 <template>
   <main class="safety-platform-container__main">
-    <BasicForm ref="basicFormRef" :formData="ruleFormData" :formRules="isViewMode ? undefined : formRules"
-      :formConfig="computedFormConfig">
+    <BasicForm
+      ref="basicFormRef"
+      :formData="ruleFormData"
+      :formRules="isViewMode ? undefined : formRules"
+      :formConfig="computedFormConfig"
+    >
       <template #fileFormat>
         <el-radio-group v-model="ruleFormData.fileFormat" :disabled="isViewMode">
           <el-radio value="PDF">PDF</el-radio>
@@ -9,23 +13,52 @@
         </el-radio-group>
       </template>
       <template #fileUrl>
-        <UploadFiles label="上传文件" :maxCount="1" :file-list="ruleFormData.fileUrlList" :disabled="isViewMode"
-          :allow-all-file-types="true" @uploadSuccess="handleUploadSuccess" />
+        <UploadFiles
+            v-if="!isViewMode"
+            label="上传文件"
+            :maxCount="1"
+            :file-list="ruleFormData.attachmentUrl"
+            :disabled="isViewMode"
+            :allow-all-file-types="true"
+            @uploadSuccess="(list: FileItem[]) => handleUploadSuccess(list)"
+          />
+          <div class="file-list" v-else>
+            <div class="file-item" v-for="file in ruleFormData.attachmentUrl" :key="file.fileId">
+              <span class="file-item--name">{{ file.fileName }}</span>
+              <div class="file-item--footer">
+                <el-button link type="primary" @click="previewOnline(file.fileUrl, file.fileType)"
+                  >预览</el-button
+                >
+                <!-- <el-button link type="primary" @click.stop="downloadFile(file.fileUrl, file.fileName)"
+                  >下载</el-button
+                > -->
+              </div>
+            </div>
+          </div>
+        <!-- <UploadFiles
+          label="上传文件"
+          :maxCount="1"
+          :file-list="ruleFormData.attachmentUrl"
+          :disabled="isViewMode"
+          :allow-all-file-types="true"
+          @upload-success="handleUploadSuccess"
+        /> -->
       </template>
       <template #content>
         <div class="editor-container">
           <Toolbar style="border-bottom: 1px solid #dcdfe6" :editor="editorRef" />
-          <Editor style="height: 400px; overflow-y: auto" v-model="ruleFormData.content" mode="default"
-            :defaultConfig="editorConfig" @on-created="handleEditorCreated" @on-change="handleEditorChange" />
+          <Editor
+            style="height: 400px; overflow-y: auto"
+            v-model="ruleFormData.description"
+            mode="default"
+            :defaultConfig="editorConfig"
+            @on-created="handleEditorCreated"
+            @on-change="handleEditorChange"
+          />
         </div>
       </template>
-      <template #status>
-        <el-radio-group v-model="ruleFormData.status" :disabled="isViewMode">
-          <el-radio :value="1">启用</el-radio>
-          <el-radio :value="0">禁用</el-radio>
-        </el-radio-group>
-      </template>
     </BasicForm>
+    <PreviewOnline ref="previewOnlineRef" />
   </main>
   <footer class="safety-platform-container__footer">
     <el-button @click="router.back()">返回</el-button>
@@ -36,283 +69,297 @@
 </template>
 
 <script setup lang="ts">
-import { computed, onMounted, ref, shallowRef, onBeforeUnmount } from 'vue';
-import { useRoute, useRouter } from 'vue-router';
-import { ElMessage } from 'element-plus';
-import BasicForm from '@/components/BasicForm.vue';
-import UploadFiles from '@/components/UploadFiles/UploadFiles.vue';
-import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
-import '@wangeditor/editor/dist/css/style.css';
-import { useFormConfigHook } from '@/hooks/useFormConfigHook';
-import { ACADEMY_FILE_FORM_CONFIG, ACADEMY_FILE_FORM_DATA, ACADEMY_FILE_FORM_RULES } from '../configs/form';
-import {
-  queryAcademyFileById,
-  saveAcademyFile,
-  updateAcademyFile,
-  type safetyCultureFile,
-} from '@/api/safety-culture';
-import type { FileItem } from '@/components/UploadFiles/types';
-import { formatAttachmentList } from '@/components/UploadFiles/utils';
-import { Plus, Delete } from '@element-plus/icons-vue';
-
-const router = useRouter();
-const route = useRoute();
-
-const operate = computed(() => (route.query.operate as string) || 'safety-publicity-board-create');
-const currentId = computed(() => Number(route.query.id));
-
-const isCreateMode = computed(() => operate.value === 'safety-publicity-board-create');
-const isEditMode = computed(() => operate.value === 'safety-publicity-board-edit');
-const isViewMode = computed(() => operate.value === 'safety-publicity-board-view');
-
-const { ruleFormData, formRules, ruleFormConfig, cloneRuleFormData, beforeRouteLeave } =
-  useFormConfigHook(ACADEMY_FILE_FORM_CONFIG, ACADEMY_FILE_FORM_DATA, ACADEMY_FILE_FORM_RULES);
-
-// 查看模式下,所有字段设为只读
-const viewFormConfig = ref(
-  ACADEMY_FILE_FORM_CONFIG.map((item) => ({
-    ...item,
-    componentProps: {
-      ...item.componentProps,
-      disabled: true,
-    },
-  })),
-);
-
-const computedFormConfig = computed(() => {
-  if (isViewMode.value) {
-    return viewFormConfig.value;
-  }
-  return ruleFormConfig.value;
-});
-
-const basicFormRef = ref<InstanceType<typeof BasicForm>>();
+  import { computed, onMounted, ref, shallowRef, onBeforeUnmount } from 'vue';
+  import { useRoute, useRouter } from 'vue-router';
+  import { ElMessage } from 'element-plus';
+  import BasicForm from '@/components/BasicForm.vue';
+  import UploadFiles from '@/components/UploadFiles/UploadFiles.vue';
+  import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
+  import '@wangeditor/editor/dist/css/style.css';
+  import { useFormConfigHook } from '@/hooks/useFormConfigHook';
+  import { ACADEMY_FILE_FORM_CONFIG, ACADEMY_FILE_FORM_DATA, ACADEMY_FILE_FORM_RULES } from '../configs/form';
+  import {
+    querySafetyPublicityBoardDetail,
+    saveSafetyPublicityBoardPage,
+    updateSafetyPublicityBoardPage,
+  } from '@/api/safety-culture';
+  import type { FileItem } from '@/components/UploadFiles/types';
+  import { formatAttachmentList } from '@/components/UploadFiles/utils';
+  import PreviewOnline from '@/views/disaster/components/PreviewOnline.vue';
+
+  const router = useRouter();
+  const route = useRoute();
+
+  const operate = computed(() => (route.query.operate as string) || 'safety-publicity-board-create');
+  const currentId = computed(() => Number(route.query.id));
+
+  const isCreateMode = computed(() => operate.value === 'safety-publicity-board-create');
+  const isEditMode = computed(() => operate.value === 'safety-publicity-board-edit');
+  const isViewMode = computed(() => operate.value === 'safety-publicity-board-view');
+  const previewOnlineRef = ref<InstanceType<typeof PreviewOnline>>();
+
+  const { ruleFormData, formRules, ruleFormConfig, cloneRuleFormData } = useFormConfigHook(
+    ACADEMY_FILE_FORM_CONFIG,
+    ACADEMY_FILE_FORM_DATA,
+    ACADEMY_FILE_FORM_RULES,
+  );
+
+  // 查看模式下,所有字段设为只读
+  const viewFormConfig = ref(
+    ACADEMY_FILE_FORM_CONFIG.map((item) => ({
+      ...item,
+      componentProps: {
+        ...item.componentProps,
+        disabled: true,
+      },
+    })),
+  );
+
+  const computedFormConfig = computed(() => {
+    if (isViewMode.value) {
+      return viewFormConfig.value;
+    }
+    return ruleFormConfig.value;
+  });
 
-// 富文本编辑器
-const editorRef = shallowRef();
-const editorConfig = computed(() => ({
-  placeholder: '请输入文档内容',
-  MENU_CONF: {},
-}));
+  const basicFormRef = ref<InstanceType<typeof BasicForm>>();
 
-const handleEditorCreated = (editor: any) => {
-  editorRef.value = editor;
-};
+  // 富文本编辑器
+  const editorRef = shallowRef();
+  const editorConfig = computed(() => ({
+    placeholder: '请输入文档内容',
+    MENU_CONF: {},
+  }));
 
-const handleEditorChange = () => {
-  // 编辑器内容变化时的处理
-};
+  const handleEditorCreated = (editor: any) => {
+    editorRef.value = editor;
+    if (isViewMode.value) {
+      editor.disable();
+    }
+  };
 
-// 文件上传
-const handleUploadSuccess = (files: FileItem[]) => {
-  ruleFormData.fileUrl = files;
-};
+  const handleEditorChange = () => {
+    // 编辑器内容变化时的处理
+  };
 
-// 将逗号分隔的URL字符串转换为FileItem数组
-const convertFileUrlToFileItems = (fileUrl: string): FileItem[] => {
-  if (!fileUrl || !fileUrl.trim()) {
-    return [];
-  }
+  // 文件上传
+  const handleUploadSuccess = (files: FileItem[]) => {
+    ruleFormData.attachmentUrl = files;
+  };
 
-  // 按逗号分割URL
-  const urls = fileUrl.split(',').map(url => url.trim()).filter(url => url);
-
-  return urls.map((url, index) => {
-    // 从URL中提取文件名
-    const urlParts = url.split('/');
-    const fileName = urlParts[urlParts.length - 1] || `附件${index + 1}`;
-
-    // 根据文件扩展名判断文件类型
-    const extension = fileName.split('.').pop()?.toLowerCase() || '';
-    let fileType = 'pdf';
-    if (extension === 'doc' || extension === 'docx') {
-      fileType = 'word';
-    } else if (extension === 'xls' || extension === 'xlsx') {
-      fileType = 'excel';
-    } else if (extension === 'ppt' || extension === 'pptx') {
-      fileType = 'ppt';
+  const parseAttachmentUrls = (attachmentUrl?: string): string[] => {
+    if (!attachmentUrl || !attachmentUrl.trim()) {
+      return [];
     }
 
-    return {
-      fileId: Date.now() + index,
-      fileName,
-      fileType,
-      fileSize: '0',
-      fileUrl: url,
-    };
-  });
-};
+    const normalized = attachmentUrl.trim();
+
+    if (normalized.startsWith('[') && normalized.endsWith(']')) {
+      try {
+        const parsed = JSON.parse(normalized);
+        if (Array.isArray(parsed)) {
+          return parsed.map((item) => String(item).trim()).filter((item) => item);
+        }
+      } catch (error) {
+        console.error('解析 attachmentUrl 失败:', error);
+      }
+    }
 
-const handleValidate = async () => {
-  if (!basicFormRef.value) return;
-  const res = await basicFormRef.value.validateForm();
-  return res;
-};
+    return normalized
+      .split(',')
+      .map((url) => url.trim())
+      .filter((url) => url);
+  };
 
-const getDetail = async () => {
-  if (!currentId.value) return;
-  try {
-    const res = await queryAcademyFileById(currentId.value);
-    if (res) {
-      // 映射接口字段到表单字段
-      ruleFormData.materialName = res.materialName || '';
-      ruleFormData.categoryName = res.categoryName || '';
-      ruleFormData.description = res.description || '';
-      // ruleFormData.fileUrl = res.fileUrl ? convertFileUrlToFileItems(res.fileUrl) : [];
-      ruleFormData.status = res.status ?? 1;
+  // 将逗号分隔的URL字符串转换为FileItem数组
+  const convertFileUrlToFileItems = (fileUrl: string): FileItem[] => {
+    if (!fileUrl || !fileUrl.trim()) {
+      return [];
     }
-    cloneRuleFormData();
-  } catch (e) {
-    console.error('获取院级文件详情失败:', e);
-    ElMessage.error('获取详情失败');
-  }
-};
+    
+    // 按逗号分割URL
+    const urls = fileUrl.split(',').map(url => url.trim()).filter(url => url);
+    
+    return urls.map((url, index) => {
+      // 从URL中提取文件名
+      const urlParts = url.split('/');
+      const fileName = urlParts[urlParts.length - 1] || `附件${index + 1}`;
+      
+      // 根据文件扩展名判断文件类型
+      const extension = fileName.split('.').pop()?.toLowerCase() || '';
+      let fileType = 'pdf';
+      if (extension === 'doc' || extension === 'docx') {
+        fileType = 'word';
+      } else if (extension === 'xls' || extension === 'xlsx') {
+        fileType = 'excel';
+      } else if (extension === 'ppt' || extension === 'pptx') {
+        fileType = 'ppt';
+      }
+      
+      return {
+        fileId: Date.now() + index,
+        fileName,
+        fileType,
+        fileSize: '0',
+        fileUrl: url,
+      };
+    });
+  };
+
+  const handleValidate = async () => {
+    if (!basicFormRef.value) return;
+    const res = await basicFormRef.value.validateForm();
+    return res;
+  };
+
+  const getDetail = async () => {
+    if (!currentId.value) return;
+    try {
+      const res = await querySafetyPublicityBoardDetail(currentId.value);
+      if (res) {
+        ruleFormData.materialName = res.materialName || '';
+        ruleFormData.categoryName = res.categoryName || '';
+        ruleFormData.description = res.description || '';
+       ruleFormData.attachmentUrl = JSON.parse(res.attachmentUrl || res.fileUrl || '');
+        ruleFormData.status = res.status ?? 1;
+      }
+      cloneRuleFormData();
+    } catch (e) {
+      console.error('获取院级文件详情失败:', e);
+      ElMessage.error('获取详情失败');
+    }
+  };
 
-const handleSubmit = async () => {
-  const res = await handleValidate();
-  if (!res) return;
+  const handleSubmit = async () => {
+    const res = await handleValidate();
+    if (!res) return;
 
-  // 验证文件上传(必填)
-  if (!ruleFormData.fileUrlList || ruleFormData.fileUrlList.length === 0) {
-    ElMessage.warning('请上传文件');
-    return;
-  }
-  try {
-    // 处理文件上传:先上传文件获取 URL,然后提取 fileUrl
-    // let fileUrl = '';
-    // if (ruleFormData.fileUrlList && ruleFormData.fileUrlList.length > 0) {
-    //   // 分离已有URL的文件和新上传的文件
-    //   const existingFiles: string[] = [];
-    //   const newFiles: FileItem[] = [];
-
-    //   ruleFormData.fileUrlList.forEach((file: FileItem) => {
-    //     // 如果文件已经有 fileUrl 且没有 file 对象,说明是已有文件
-    //     if (file.fileUrl && !file.file) {
-    //       existingFiles.push(file.fileUrl);
-    //     } else {
-    //       // 否则是需要上传的新文件
-    //       newFiles.push(file);
-    //     }
-    //   });
-
-    //   // 上传新文件
-    //   let uploadedUrls: string[] = [];
-    //   if (newFiles.length > 0) {
-    //     const uploadedFiles = await formatAttachmentList(newFiles);
-    //     uploadedUrls = uploadedFiles
-    //       .map((file: any) => file.fileUrl || file.url || '')
-    //       .filter((url: string) => url);
-    //   }
-
-    //   // 合并已有URL和新上传的URL,取第一个作为fileUrl
-    //   const allUrls = [...existingFiles, ...uploadedUrls].filter((url: string) => url);
-    //   fileUrl = allUrls.length > 0 ? allUrls[0] : '';
-    // }
-
-    const basePayload = {
-      materialName: ruleFormData.materialName,
-      categoryName: ruleFormData.categoryName,
-      description: ruleFormData.description,
-      fileFormat: ruleFormData.fileFormat === 'PDF' ? 1 : 2,
-      status: ruleFormData.status ?? 1,
-    };
-    if (isCreateMode.value) {
-      // await saveAcademyFile(basePayload);
-      ElMessage.success('创建成功');
-    } else if (isEditMode.value && currentId.value) {
-      // await updateAcademyFile({
-      //   id: currentId.value,
-      //   ...basePayload,
-      // });
-      ElMessage.success('保存成功');
+    // 验证文件上传(必填)
+    if (!ruleFormData.attachmentUrl || ruleFormData.attachmentUrl.length === 0) {
+      ElMessage.warning('请上传文件');
+      return;
     }
-
-    router.back();
-  } catch (e) {
-    console.error('保存院级文件失败:', e);
-    ElMessage.error('保存失败,请重试');
+    try {
+      const uploadedFileList = await formatAttachmentList(ruleFormData.attachmentUrl);
+      const basePayload = {
+        materialName: ruleFormData.materialName,
+        categoryName: ruleFormData.categoryName,
+        description: ruleFormData.description,
+        attachmentUrl: JSON.stringify(uploadedFileList),
+      };
+      if (isCreateMode.value) {
+        await saveSafetyPublicityBoardPage(basePayload);
+        ElMessage.success('创建成功');
+      } else if (isEditMode.value && currentId.value) {
+        await updateSafetyPublicityBoardPage({
+          id: currentId.value,
+          ...basePayload,
+        });
+        ElMessage.success('保存成功');
+      }
+
+      router.back();
+    } catch (e) {
+      console.error('保存院级文件失败:', e);
+      ElMessage.error('保存失败,请重试');
+    }
+  };
+  const previewOnline = (url: string | undefined, type) => {
+  if (url) {
+    previewOnlineRef.value?.open(url, type);
   }
 };
 
-const handleImageUploadSuccess = (response: any, file: any, fileList: any[]) => {
-  const imageUrl = response.data?.url || file.url;
-  ruleFormData.imageFileUrl = fileList.map(item => ({
-    fileId: Date.now() + Math.random(),
-    fileName: item.name,
-    fileType: 'image',
-    fileSize: (item.size / 1024).toFixed(2) + 'KB',
-    fileUrl: imageUrl || item.url
-  }));
-};
-
-const handleImageRemove = (file: any, fileList: any[]) => {
-  ruleFormData.imageFileUrl = fileList.map(item => ({
-    fileId: item.fileId || Date.now() + Math.random(),
-    fileName: item.name || item.fileName,
-    fileType: 'image',
-    fileSize: item.size ? (item.size / 1024).toFixed(2) + 'KB' : item.fileSize,
-    fileUrl: item.url || item.fileUrl
-  }));
-};
-
-onMounted(() => {
-  cloneRuleFormData();
-  // beforeRouteLeave();
-  if (isEditMode.value || isViewMode.value) {
-    getDetail();
-  }
-});
+  // const handleImageUploadSuccess = (response: any, file: any, fileList: any[]) => {
+  //   const imageUrl = response.data?.url || file.url;
+  //   ruleFormData.imageFileUrl = fileList.map((item) => ({
+  //     fileId: Date.now() + Math.random(),
+  //     fileName: item.name,
+  //     fileType: 'image',
+  //     fileSize: (item.size / 1024).toFixed(2) + 'KB',
+  //     fileUrl: imageUrl || item.url,
+  //   }));
+  // };
+
+  // const handleImageRemove = (file: any, fileList: any[]) => {
+  //   ruleFormData.imageFileUrl = fileList.map((item) => ({
+  //     fileId: item.fileId || Date.now() + Math.random(),
+  //     fileName: item.name || item.fileName,
+  //     fileType: 'image',
+  //     fileSize: item.size ? (item.size / 1024).toFixed(2) + 'KB' : item.fileSize,
+  //     fileUrl: item.url || item.fileUrl,
+  //   }));
+  // };
+
+  onMounted(() => {
+    cloneRuleFormData();
+    // beforeRouteLeave();
+    if (isEditMode.value || isViewMode.value) {
+      getDetail();
+    }
+  });
 
-onBeforeUnmount(() => {
-  const editor = editorRef.value;
-  if (editor == null) return;
-  editor.destroy();
-});
+  onBeforeUnmount(() => {
+    const editor = editorRef.value;
+    if (editor == null) return;
+    editor.destroy();
+  });
 </script>
 
 <style scoped lang="scss">
-@use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-details-layout.scss' as *;
 
-.editor-container {
-  width: 100%;
-  border: 1px solid #dcdfe6;
-  border-radius: 4px;
-  overflow: hidden;
-}
+  .editor-container {
+    width: 100%;
+    border: 1px solid #dcdfe6;
+    border-radius: 4px;
+    overflow: hidden;
+  }
 
-.content-display {
-  min-height: 200px;
-  padding: 12px;
-  border: 1px solid #dcdfe6;
-  border-radius: 4px;
-  background-color: #f5f7fa;
-}
+  .content-display {
+    min-height: 200px;
+    padding: 12px;
+    border: 1px solid #dcdfe6;
+    border-radius: 4px;
+    background-color: #f5f7fa;
+  }
 
-.file-display {
-  .file-link {
-    color: #409eff;
-    text-decoration: none;
+  .file-display {
+    .file-link {
+      color: #409eff;
+      text-decoration: none;
 
-    &:hover {
-      text-decoration: underline;
+      &:hover {
+        text-decoration: underline;
+      }
     }
   }
-}
 
-.no-file {
-  color: rgba(0, 0, 0, 0.65);
-}
-
-.image-uploader {
-  :deep(.el-upload--picture-card) {
-    width: 80px !important;
-    height: 80px !important;
-    line-height: 80px;
+  .no-file {
+    color: rgba(0, 0, 0, 0.65);
   }
 
-  :deep(.el-upload-list--picture-card .el-upload-list__item) {
-    width: 80px !important;
-    height: 80px !important;
+  .image-uploader {
+    :deep(.el-upload--picture-card) {
+      width: 80px !important;
+      height: 80px !important;
+      line-height: 80px;
+    }
+
+    :deep(.el-upload-list--picture-card .el-upload-list__item) {
+      width: 80px !important;
+      height: 80px !important;
+    }
   }
+</style>
+
+<style lang="scss">
+.w-e-full-screen-container {
+  inset: 0 !important;
+  z-index: 3000 !important;
+}
+
+.w-e-full-screen-container .w-e-text-container {
+  height: calc(100vh - 42px) !important;
 }
 </style>

+ 4 - 8
src/views/production-safety/safety-culture/safetyPublicityBoardManagement/configs/form.ts

@@ -1,4 +1,5 @@
 import { FormConfig } from '@/types/basic-form';
+import type { FileItem } from '@/components/UploadFiles/types';
 
 export const ACADEMY_FILE_FORM_CONFIG: FormConfig[] = [
   {
@@ -27,15 +28,10 @@ export const ACADEMY_FILE_FORM_CONFIG: FormConfig[] = [
     slot: 'content',
   },
   {
-    prop: 'fileUrl',
+    prop: 'attachmentUrl',
     label: '文档上传:',
     slot: 'fileUrl',
   },
-  {
-    prop: 'status',
-    label: '状态:',
-    slot: 'status',
-  }
 ];
 
 export const ACADEMY_FILE_FORM_DATA = {
@@ -44,11 +40,11 @@ export const ACADEMY_FILE_FORM_DATA = {
   description: '',
   fileFormat: '',
   status: 1,
-  fileUrl: [] as string[],
+  attachmentUrl: [] as FileItem[],
 };
 
 export const ACADEMY_FILE_FORM_RULES = {
   materialName: [{ required: true, message: '请输入宣传资料名称', trigger: 'blur' }],
   categoryName: [{ required: true, message: '请选择分类名称', trigger: 'change' }],
-  status: [{ required: true, message: '请选择状态', trigger: 'change' }]
+  description: [{ required: true, message: '请输入资料描述', trigger: 'blur' }],
 };

+ 32 - 16
src/views/production-safety/safety-culture/safetyPublicityBoardManagement/safetyPublicityBoardManagement.vue

@@ -89,7 +89,7 @@ import { TABLE_OPTIONS, INVENTORY_TABLE_COLUMNS } from './configs/tables';
 import { useRouter } from 'vue-router';
 import {
   querySafetyPublicityBoardPage,
-  // deleteSafetyPublicityBoard,
+  deleteSafetyPublicityBoardPage,
   type safetyCultureFilePageQuery,
   type safetyCultureFileQuery,
 } from '@/api/safety-culture';
@@ -147,20 +147,15 @@ async function getTableData() {
     };
     const res = await querySafetyPublicityBoardPage(pageQuery);
     if (res) {
-      // 映射返回数据字段到表格字段
       tableData.value = res.records.map((item) => ({
         id: item.id,
-        fileName: item.fileName, // 文件名称
-        fileCode: item.fileCode, // 文件编号
-        classifyName: item.classifyName, // 分类名称
-        fileVersion: item.fileVersion, // 文件版本号
-        fileFormat: item.fileFormat === 1 ? 'PDF' : 'WORD', // 文件格式
-        releaseDate: item.releaseDate, // 发布日期
-        status: item.status, // 状态:1-启用,0-禁用
-        publishDate: item.publishDate, // 发布日期
-        fileUrl: item.fileUrl, // 文件地址
-        uploadTime: item.uploadTime || item.createdAt, // 上传时间
-        categoryName: item.categoryName, // 分类名称
+        fileName: item.materialName || item.fileName,
+        status: item.status,
+        categoryName: item.categoryName || item.classifyName,
+        fileCode: item.description || '-',
+        fileVersion: item.uploadTime || item.createdAt || item.updatedAt || '-',
+        attachmentUrl: item.attachmentUrl,
+        fileUrl: item.fileUrl,
       }));
       pagination.total = res.totalRow || 0;
     }
@@ -235,7 +230,28 @@ const handleDownload = async () => {
 
 // 文件下载
 const handleDownloadFile = (row: any) => {
-  const url = row?.fileUrl;
+  const parseAttachmentUrls = (attachmentUrl?: string): string[] => {
+    if (!attachmentUrl || !attachmentUrl.trim()) {
+      return [];
+    }
+
+    const normalized = attachmentUrl.trim();
+    if (normalized.startsWith('[') && normalized.endsWith(']')) {
+      try {
+        const parsed = JSON.parse(normalized);
+        if (Array.isArray(parsed)) {
+          return parsed.map((item) => String(item).trim()).filter((item) => item);
+        }
+      } catch (error) {
+        console.error('解析 attachmentUrl 失败:', error);
+      }
+    }
+
+    return normalized.split(',').map((url) => url.trim()).filter((url) => url);
+  };
+
+  const attachmentUrls = parseAttachmentUrls(row?.attachmentUrl);
+  const url = attachmentUrls[0] || row?.fileUrl;
   if (!url) {
     ElMessage.warning('暂无文件可下载');
     return;
@@ -264,7 +280,7 @@ const handleEdit = (id: number) => {
 
 const handleDelete = async (id: number) => {
   try {
-    // await deleteSafetyCultureMaterials(id);
+    await deleteSafetyPublicityBoardPage(id);
     ElMessage.success('删除成功');
     getTableData();
   } catch (e) {
@@ -306,4 +322,4 @@ onMounted(() => {
 @use '@/styles/page-main-layout.scss' as *;
 @use '@/styles/basic-table-action.scss' as *;
 @use '@/views/traffic/violation/style/act-search-table.scss' as *;
-</style>
+</style>