Bladeren bron

fix: 安全模块功能

hewei 2 maanden geleden
bovenliggende
commit
fe9ffd5ce2

+ 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;
 

+ 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>

+ 259 - 247
src/views/production-safety/safety-culture/safetyCultureMaterialManagement/components/safetyCultureMaterialManagementDetail.vue

@@ -1,53 +1,82 @@
 <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 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> -->
+        <UploadImages
+          ref="uploadImagesRef"
+          :maxCount="1"
+          :image-list="approvalImageList"
+          @upload-success="handleApprovalUploadChange"
+        />
+      </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>
+    <el-button v-if="!isViewMode" type="primary" @click="handleSubmit">
+      {{ isCreateMode ? '提交' : '保存' }}
+    </el-button>
+  </footer>
 </template>
 
 <script setup lang="ts">
@@ -61,14 +90,16 @@ 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,
+  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';
+import PreviewOnline from '@/views/disaster/components/PreviewOnline.vue';
+import UploadImages from '@/components/UploadImages/UploadImages.vue';
 
 const router = useRouter();
 const route = useRoute();
@@ -79,225 +110,194 @@ 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 uploadImagesRef = ref<InstanceType<typeof UploadImages>>();
+const approvalImageList = ref([])
 
 const { ruleFormData, formRules, ruleFormConfig, cloneRuleFormData, beforeRouteLeave } =
-    useFormConfigHook(ACADEMY_FILE_FORM_CONFIG, ACADEMY_FILE_FORM_DATA, ACADEMY_FILE_FORM_RULES);
+  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,
-        },
-    })),
+  ACADEMY_FILE_FORM_CONFIG.map((item) => ({
+    ...item,
+    componentProps: {
+      ...item.componentProps,
+      disabled: true,
+    },
+  })),
 );
 
 const computedFormConfig = computed(() => {
-    if (isViewMode.value) {
-        return viewFormConfig.value;
-    }
-    return ruleFormConfig.value;
+  if (isViewMode.value) {
+    return viewFormConfig.value;
+  }
+  return ruleFormConfig.value;
 });
 
 const basicFormRef = ref<InstanceType<typeof BasicForm>>();
 
+
 // 富文本编辑器
 const editorRef = shallowRef();
 const editorConfig = computed(() => ({
-    placeholder: '请输入文档内容',
-    MENU_CONF: {},
+  placeholder: '请输入文档内容',
+  MENU_CONF: {},
 }));
 
 const handleEditorCreated = (editor: any) => {
-    editorRef.value = editor;
+  editorRef.value = editor;
+  if (isViewMode.value) {
+    editor.disable();
+  }
 };
 
 const handleEditorChange = () => {
-    // 编辑器内容变化时的处理
+  // 编辑器内容变化时的处理
 };
 
 // 文件上传
 const handleUploadSuccess = (files: FileItem[]) => {
-    ruleFormData.fileUrlList = files;
+  ruleFormData.attachmentUrl = files;
 };
 
 // 将逗号分隔的URL字符串转换为FileItem数组
 const convertFileUrlToFileItems = (fileUrl: string): FileItem[] => {
-    if (!fileUrl || !fileUrl.trim()) {
-        return [];
+  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') {
+      fileType = 'word';
+    } else if (extension === 'xls' || extension === 'xlsx') {
+      fileType = 'excel';
+    } else if (extension === 'ppt' || extension === 'pptx') {
+      fileType = 'ppt';
     }
-
-    // 按逗号分割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,
-        };
-    });
+    
+    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;
+  if (!basicFormRef.value) return;
+  const res = await basicFormRef.value.validateForm();
+  return res;
 };
 
 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();
-    } catch (e) {
-        console.error('获取院级文件详情失败:', e);
-        ElMessage.error('获取详情失败');
+  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.content = res.content || '';
+      ruleFormData.status = res.status ?? 1;
+      ruleFormData.imageUrls = JSON.parse(res.imageUrls);
+      approvalImageList.value = ruleFormData.imageUrls;
+      
+
+      // 如果有文件URL,转换为FileItem格式
+      ruleFormData.attachmentUrl = JSON.parse(res.attachmentUrl || res.fileUrl || '');
     }
+    cloneRuleFormData();
+  } catch (e) {
+    console.error('获取院级文件详情失败:', e);
+    ElMessage.error('获取详情失败');
+  }
 };
 
-const handleSubmit = async () => {
-    const res = await handleValidate();
-    if (!res) return;
+  const handleApprovalUploadChange = async () => {
+    ruleFormData.imageUrls = uploadImagesRef.value!.getUploadedImages();
+  };
 
-    // 验证文件上传(必填)
-    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: 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();
-    } 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;
+  }
+  try {
+    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('保存成功');
     }
-};
 
-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
-    }));
+    router.back();
+  } catch (e) {
+    console.error('保存院级文件失败:', e);
+    ElMessage.error('保存失败,请重试');
+  }
 };
 
-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
-    }));
+const previewOnline = (url: string | undefined, type) => {
+  if (url) {
+    previewOnlineRef.value?.open(url, type);
+  }
 };
 
 onMounted(() => {
-    cloneRuleFormData();
-    // beforeRouteLeave();
-    if (isEditMode.value || isViewMode.value) {
-        getDetail();
-    }
+  cloneRuleFormData();
+  // beforeRouteLeave();
+  if (isEditMode.value || isViewMode.value) {
+    getDetail();
+  }
 });
 
 onBeforeUnmount(() => {
-    const editor = editorRef.value;
-    if (editor == null) return;
-    editor.destroy();
+  const editor = editorRef.value;
+  if (editor == null) return;
+  editor.destroy();
 });
 </script>
 
@@ -305,45 +305,57 @@ onBeforeUnmount(() => {
 @use '@/styles/page-details-layout.scss' as *;
 
 .editor-container {
-    width: 100%;
-    border: 1px solid #dcdfe6;
-    border-radius: 4px;
-    overflow: hidden;
+  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;
+  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-link {
+    color: #409eff;
+    text-decoration: none;
 
-        &:hover {
-            text-decoration: underline;
-        }
+    &:hover {
+      text-decoration: underline;
     }
+  }
 }
 
 .no-file {
-    color: rgba(0, 0, 0, 0.65);
+  color: rgba(0, 0, 0, 0.65);
 }
 
 .image-uploader {
-    :deep(.el-upload--picture-card) {
-        width: 80px !important;
-        height: 80px !important;
-        line-height: 80px;
-    }
+  :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>
 
-    :deep(.el-upload-list--picture-card .el-upload-list__item) {
-        width: 80px !important;
-        height: 80px !important;
-    }
+
+<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>