Ver código fonte

文档管理:
- 新增逻辑修复
- 递交文件完成
- 审核文件完成
- 查看审核记录完成
- 两个文件的下载完成

Huanyi 6 dias atrás
pai
commit
0400201fb0

+ 49 - 1
src/api/document/document/index.ts

@@ -1,5 +1,5 @@
 import request from '@/utils/request';
-import { DocumentForm, DocumentQuery, DocumentVO, DocumentMarkForm } from './types';
+import { DocumentForm, DocumentQuery, DocumentVO, DocumentMarkForm, DocumentAuditForm, DocumentSubmitForm } from './types';
 import { AxiosPromise } from 'axios';
 
 /**
@@ -37,3 +37,51 @@ export const markDocument = (data: DocumentMarkForm) => {
     data: data
   });
 };
+
+/**
+ * 审核文档
+ * @param data
+ */
+export const auditDocument = (data: DocumentAuditForm) => {
+  return request({
+    url: '/document/document/audit',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 递交文档
+ * @param data
+ */
+export const submitDocument = (data: DocumentSubmitForm) => {
+  return request({
+    url: '/document/document/submit',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 确认递交文档
+ * @param id 文档ID
+ */
+export const confirmSubmit = (id: number) => {
+  return request({
+    url: '/document/document/confirmSubmit',
+    method: 'delete',
+    params: { id }
+  });
+};
+
+/**
+ * 查询文档审核记录
+ * @param query 查询参数
+ */
+export const listDocumentAuditLog = (query: any): AxiosPromise<any> => {
+  return request({
+    url: '/document/document/logAudit',
+    method: 'get',
+    params: query
+  });
+};

+ 40 - 0
src/api/document/document/types.ts

@@ -126,6 +126,11 @@ export interface DocumentForm extends BaseEntity {
    */
   ossId?: string | number;
 
+  /**
+   * 状态
+   */
+  status?: number;
+
   /**
    * 递交时间
    */
@@ -163,3 +168,38 @@ export interface DocumentMarkForm {
    */
   type: string;
 }
+
+/**
+ * 文档审核表单
+ */
+export interface DocumentAuditForm {
+  /**
+   * 文档ID
+   */
+  documentId: string | number;
+
+  /**
+   * 审核结果:3-通过,2-驳回
+   */
+  result: number;
+
+  /**
+   * 驳回理由
+   */
+  rejectReason?: string;
+}
+
+/**
+ * 文档递交表单
+ */
+export interface DocumentSubmitForm {
+  /**
+   * 文档ID
+   */
+  documentId: string | number;
+
+  /**
+   * 文件本体OSS ID
+   */
+  ossId: string | number;
+}

+ 1 - 0
src/assets/icons/svg/document/document.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1765935444141" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9771" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M224 831.936V192.096L223.808 192H576v159.936c0 35.328 28.736 64.064 64.064 64.064h159.712c0.032 0.512 0.224 1.184 0.224 1.664L800.256 832 224 831.936zM757.664 352L640 351.936V224.128L757.664 352z m76.064-11.872l-163.872-178.08C651.712 142.336 619.264 128 592.672 128H223.808A64.032 64.032 0 0 0 160 192.096v639.84A64 64 0 0 0 223.744 896h576.512A64 64 0 0 0 864 831.872V417.664c0-25.856-12.736-58.464-30.272-77.536z" fill="#3E3A39" p-id="9772"></path><path d="M640 512h-256a32 32 0 0 0 0 64h256a32 32 0 0 0 0-64M640 672h-256a32 32 0 0 0 0 64h256a32 32 0 0 0 0-64" fill="#3E3A39" p-id="9773"></path></svg>

+ 1 - 0
src/assets/icons/svg/document/excel.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1765935395549" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7714" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M145.6 0C100.8 0 64 36.8 64 81.6v860.8C64 987.2 100.8 1024 145.6 1024h732.8c44.8 0 81.6-36.8 81.6-81.6V324.8L657.6 0h-512z" fill="#45B058" p-id="7715"></path><path d="M374.4 862.4c-3.2 0-6.4-1.6-8-3.2l-59.2-80-60.8 80c-1.6 1.6-4.8 3.2-8 3.2-6.4 0-11.2-4.8-11.2-11.2 0-1.6 0-4.8 1.6-6.4l62.4-81.6-57.6-78.4c-1.6-1.6-3.2-3.2-3.2-6.4 0-4.8 4.8-11.2 11.2-11.2 4.8 0 8 1.6 9.6 4.8l56 73.6 54.4-73.6c1.6-3.2 4.8-4.8 8-4.8 6.4 0 12.8 4.8 12.8 11.2 0 3.2-1.6 4.8-1.6 6.4l-59.2 76.8 62.4 83.2c1.6 1.6 3.2 4.8 3.2 6.4 0 6.4-6.4 11.2-12.8 11.2z m160-1.6H448c-9.6 0-17.6-8-17.6-17.6V678.4c0-6.4 4.8-11.2 12.8-11.2 6.4 0 11.2 4.8 11.2 11.2v161.6h80c6.4 0 11.2 4.8 11.2 9.6 0 6.4-4.8 11.2-11.2 11.2z m112 3.2c-28.8 0-51.2-9.6-67.2-24-3.2-1.6-3.2-4.8-3.2-8 0-6.4 3.2-12.8 11.2-12.8 1.6 0 4.8 1.6 6.4 3.2 12.8 11.2 32 20.8 54.4 20.8 33.6 0 44.8-19.2 44.8-33.6 0-49.6-113.6-22.4-113.6-89.6 0-32 27.2-54.4 65.6-54.4 24 0 46.4 8 60.8 20.8 3.2 1.6 4.8 4.8 4.8 8 0 6.4-4.8 12.8-11.2 12.8-1.6 0-4.8-1.6-6.4-3.2-14.4-11.2-32-16-49.6-16-24 0-40 11.2-40 30.4 0 43.2 113.6 17.6 113.6 89.6 0 27.2-19.2 56-70.4 56z" fill="#FFFFFF" p-id="7716"></path><path d="M960 326.4v16H755.2s-102.4-20.8-99.2-108.8c0 0 3.2 92.8 96 92.8h208z" fill="#349C42" p-id="7717"></path><path d="M656 0v233.6c0 25.6 19.2 92.8 99.2 92.8H960L656 0z" fill="#FFFFFF" opacity=".5" p-id="7718"></path></svg>

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
src/assets/icons/svg/document/pdf.svg


+ 1 - 0
src/assets/icons/svg/document/ppt.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1765935337019" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5597" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M145.6 0C100.8 0 64 36.8 64 81.6v860.8C64 987.2 100.8 1024 145.6 1024h732.8c44.8 0 81.6-36.8 81.6-81.6V324.8L657.6 0h-512z" fill="#E34221" p-id="5598"></path><path d="M960 326.4v16H755.2s-100.8-20.8-99.2-108.8c0 0 4.8 92.8 97.6 92.8H960z" fill="#DC3119" p-id="5599"></path><path d="M657.6 0v233.6c0 25.6 17.6 92.8 97.6 92.8H960L657.6 0z" fill="#FFFFFF" p-id="5600"></path><path d="M304 784h-54.4v67.2c0 6.4-4.8 11.2-11.2 11.2-6.4 0-12.8-4.8-12.8-11.2V686.4c0-9.6 8-17.6 17.6-17.6H304c38.4 0 59.2 25.6 59.2 57.6S340.8 784 304 784z m-3.2-94.4h-51.2v73.6h51.2c22.4 0 38.4-16 38.4-36.8 0-22.4-16-36.8-38.4-36.8zM480 784h-54.4v67.2c0 6.4-4.8 11.2-11.2 11.2-6.4 0-11.2-4.8-11.2-11.2V686.4c0-9.6 6.4-17.6 16-17.6H480c38.4 0 59.2 25.6 59.2 57.6S518.4 784 480 784z m-3.2-94.4h-49.6v73.6h49.6c22.4 0 38.4-16 38.4-36.8 0-22.4-16-36.8-38.4-36.8z m225.6 0h-52.8v161.6c0 6.4-4.8 11.2-11.2 11.2-6.4 0-12.8-4.8-12.8-11.2V689.6h-51.2c-6.4 0-11.2-4.8-11.2-11.2 0-4.8 4.8-9.6 11.2-9.6h128c6.4 0 11.2 4.8 11.2 11.2 0 4.8-4.8 9.6-11.2 9.6z" fill="#FFFFFF" p-id="5601"></path></svg>

+ 1 - 0
src/assets/icons/svg/document/word.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1765935423741" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8763" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M791.94 155.36H332.48c-42.23 0-76.54 34.31-76.54 76.54v76.55h-51.03c-28.16 0-51.04 22.87-51.04 51.03v306.37c0 28.15 22.88 51.02 51.04 51.02h51.03v76.54c0 42.24 34.32 76.55 76.54 76.55h459.46c42.23 0 76.54-34.32 76.54-76.54V231.9c0.1-42.24-34.31-76.54-76.54-76.54z m-547.68 298.6h40.28l26.04 93.58h5.7l29.16-93.58h34.45l29.02 93.71h6.24l26.18-93.71h40.41l-40.41 133.72H384.1l-19.53-70.25h-5.43l-19.26 69.98h-55.74l-39.88-133.45zM817.45 793.5c-0.01 14.09-11.42 25.5-25.51 25.51H332.48c-14.09-0.01-25.5-11.43-25.51-25.51v-76.54h204.21c28.15 0 51.03-22.88 51.03-51.03V614.9H740.9c14.09-0.01 25.51-11.43 25.52-25.52-0.02-14.09-11.43-25.5-25.52-25.51H562.21v-51.03H740.9c14.09-0.01 25.51-11.43 25.52-25.52-0.01-14.09-11.43-25.5-25.52-25.52H562.21v-51.03H740.9c14.09-0.01 25.51-11.43 25.52-25.51-0.01-14.09-11.43-25.51-25.52-25.52H562.21c0-28.16-22.89-51.03-51.03-51.03H306.97v-76.54c0.01-14.09 11.43-25.5 25.51-25.52h459.46c14.08 0.01 25.5 11.43 25.51 25.52V793.5z m0 0" fill="#54A0FF" p-id="8764"></path></svg>

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
src/assets/icons/svg/ppt.svg


+ 24 - 66
src/components/DataPermisionTree/index.vue

@@ -1,14 +1,8 @@
 <template>
     <div class="data-permission-tree">
-        <tree-node v-for="node in treeData" :key="node.id" :node="node" :selected-keys="uiSelectedKeys"
+        <tree-node v-for="node in treeData" :key="node.id" :node="node" :selected-keys="selectedKeys"
             :indeterminate-keys="indeterminateKeys" :disabled-keys="disabledKeys" :expanded-keys="expandedKeys"
-            @check="handleCheck" @expand="handleExpand">
-            <template #default="{ node: treeNode, data }">
-                <slot name="default" :node="treeNode" :data="data">
-                    <span>{{ data.name }}</span>
-                </slot>
-            </template>
-        </tree-node>
+            @check="handleCheck" @expand="handleExpand" />
     </div>
 </template>
 
@@ -64,12 +58,15 @@ onMounted(() => {
     updateIndeterminateState()
 
     // 默认展开所有节点
-    // 默认只展开根节点
-    if (props.data && props.data.length > 0) {
-        props.data.forEach(node => {
+    const expandAll = (nodes: TreeData[]) => {
+        nodes.forEach(node => {
             expandedKeys.value.add(node.id)
+            if (node.children && node.children.length > 0) {
+                expandAll(node.children)
+            }
         })
     }
+    expandAll(props.data)
 })
 
 // 监听外部选中状态变化
@@ -89,23 +86,16 @@ const updateIndeterminateState = () => {
                 traverse(node.children)
 
                 // 检查当前节点是否为半选状态
-                const isNodeSelected = innerSelectedKeys.value.includes(node.id)
-
-                // 检查是否有子节点直接被选中
-                const hasDirectlySelectedChild = node.children.some(child => {
-                    return innerSelectedKeys.value.includes(child.id)
-                })
-
-                // 检查是否有子节点被选中或半选
-                const hasCheckedOrIndeterminateChild = node.children.some(child => {
-                    return innerSelectedKeys.value.includes(child.id) || newIndeterminateKeys.has(child.id)
-                })
-
-                // 半选条件:
-                // 1. 节点本身未被选中
-                // 2. 至少有一个子节点被直接选中或半选
-                // 3. 即使所有子节点都被选中,如果不是用户直接选择的父节点,仍显示为半选
-                if (!isNodeSelected && hasCheckedOrIndeterminateChild) {
+                const childCheckedCount = node.children.filter(child =>
+                    innerSelectedKeys.value.includes(child.id)
+                ).length
+
+                const childIndeterminateCount = node.children.filter(child =>
+                    newIndeterminateKeys.has(child.id)
+                ).length
+
+                // 如果有子节点被选中或半选,则父节点为半选状态
+                if (childCheckedCount > 0 || childIndeterminateCount > 0) {
                     newIndeterminateKeys.add(node.id)
                 }
             }
@@ -119,7 +109,7 @@ const updateIndeterminateState = () => {
 // 获取节点的所有后代节点ID
 const getAllDescendantIds = (node: TreeData): number[] => {
     const ids: number[] = []
-
+    
     const traverse = (children: TreeData[] | undefined) => {
         if (children && children.length > 0) {
             children.forEach(child => {
@@ -128,7 +118,7 @@ const getAllDescendantIds = (node: TreeData): number[] => {
             })
         }
     }
-
+    
     traverse(node.children)
     return ids
 }
@@ -139,7 +129,7 @@ const findNodePath = (nodes: TreeData[], targetId: number): TreeData[] | null =>
         if (node.id === targetId) {
             return [node]
         }
-
+        
         if (node.children && node.children.length > 0) {
             const childPath = findNodePath(node.children, targetId)
             if (childPath) {
@@ -164,11 +154,11 @@ const handleCheck = (node: TreeData, checked: boolean) => {
         if (keyIndex === -1) {
             newSelectedKeys.push(node.id)
         }
-
+        
         // 获取所有后代节点ID并从选中列表中移除
         const descendantIds = getAllDescendantIds(node)
         newSelectedKeys = newSelectedKeys.filter(id => !descendantIds.includes(id))
-
+        
         // 获取所有祖先节点ID并从选中列表中移除
         const path = findNodePath(props.data, node.id)
         if (path) {
@@ -208,44 +198,12 @@ const handleExpand = (nodeId: number) => {
 // 计算最终的树数据
 const treeData = computed(() => props.data)
 
-// 计算UI显示的选中键值
-const uiSelectedKeys = computed(() => {
-    const keys = new Set<number>()
-
-    const traverse = (nodes: TreeData[]) => {
-        nodes.forEach(node => {
-            // 如果节点本身被选中,或者有祖先节点被选中,则UI显示为选中
-            if (innerSelectedKeys.value.includes(node.id) || isAncestorSelected(node.id)) {
-                keys.add(node.id)
-            }
-
-            if (node.children && node.children.length > 0) {
-                traverse(node.children)
-            }
-        })
-    }
-
-    // 辅助函数:检查节点是否有祖先节点被选中
-    const isAncestorSelected = (nodeId: number): boolean => {
-        const path = findNodePath(props.data, nodeId)
-        if (path) {
-            // 检查路径上除了当前节点外的其他节点是否有被选中的
-            return path.slice(0, -1).some(node => innerSelectedKeys.value.includes(node.id))
-        }
-        return false
-    }
-
-    traverse(props.data)
-    return Array.from(keys)
-})
-
-// 计算选中键值(实际值)
+// 计算选中键值
 const selectedKeys = computed(() => innerSelectedKeys.value)
 </script>
 
 <style scoped>
 .data-permission-tree {
     width: 100%;
-    /* 移除内部高度限制和滚动条,由外部容器控制 */
 }
 </style>

+ 60 - 7
src/lang/modules/document/document/en_US.ts

@@ -14,7 +14,9 @@ export default {
     reset: 'Reset',
     audit: 'Audit',
     mark: 'Mark',
-    download: 'Download'
+    download: 'Download',
+    confirmSubmit: 'Confirm Submit',
+    viewAuditLog: 'View Audit Log'
   },
   // Menu
   menu: {
@@ -35,7 +37,10 @@ export default {
     addCenter: 'Add Center',
     confirmEdit: 'Confirm Edit',
     addDocument: 'Add Document',
-    markDocument: 'Mark Document'
+    markDocument: 'Mark Document',
+    auditDocument: 'Audit Document',
+    submitDocument: 'Submit Document',
+    auditLog: 'Audit Log'
   },
   // Form
   form: {
@@ -87,7 +92,11 @@ export default {
     noFileToDownload: 'No file available for download',
     markDocument: 'Marking document: {name}',
     markSuccess: 'Mark successfully',
-    markFailed: 'Mark failed'
+    markFailed: 'Mark failed',
+    auditSuccess: 'Audit successfully',
+    auditFailed: 'Audit failed',
+    submitSuccess: 'Submit successfully',
+    submitFailed: 'Submit failed'
   },
   // Validation Rules
   rule: {
@@ -128,6 +137,27 @@ export default {
   markRule: {
     typeRequired: 'Please select document specification'
   },
+  // Audit Form
+  auditForm: {
+    result: 'Audit Result',
+    pass: 'Pass',
+    reject: 'Reject',
+    reason: 'Rejection Reason',
+    reasonPlaceholder: 'Please enter rejection reason'
+  },
+  // Audit Validation Rules
+  auditRule: {
+    resultRequired: 'Please select audit result',
+    reasonRequired: 'Please enter rejection reason'
+  },
+  // Submit Form
+  submitForm: {
+    file: 'File'
+  },
+  // Submit Validation Rules
+  submitRule: {
+    fileRequired: 'Please upload file'
+  },
   // Empty State
   empty: {
     description: 'Please select a folder to view documents'
@@ -140,6 +170,7 @@ export default {
     name: 'Name',
     specification: 'Specification',
     planDocumentType: 'Plan Document Type',
+    status: 'Status',
     submitter: 'Submitter',
     submitDeadline: 'Submit Deadline',
     submitTime: 'Submit Time',
@@ -148,8 +179,30 @@ export default {
     note: 'Note',
     createTime: 'Create Time',
     updateTime: 'Update Time',
-    action: 'Action'
+    action: 'Action',
+    statusOptions: {
+      unUpload: 'Un Upload',
+      unAudit: 'Un Audit',
+      auditReject: 'Audit Reject',
+      unFiling: 'Un Filing',
+      filing: 'Filing',
+      unQualityControl: 'Un Quality Control',
+      qualityControlPass: 'Quality Control Pass',
+      qualityControlReject: 'Quality Control Reject'
+    }
+  },
+  // 审核记录
+  auditLog: {
+    result: 'Audit Result',
+    selectResult: 'Please select audit result',
+    pass: 'Pass',
+    reject: 'Reject',
+    auditTime: 'Audit Time',
+    startTime: 'Start Time',
+    endTime: 'End Time',
+    documentName: 'Document Name',
+    auditorType: 'Auditor Type',
+    auditorName: 'Auditor',
+    rejectReason: 'Reject Reason'
   }
-};
-
-
+};

+ 45 - 6
src/lang/modules/document/document/zh_CN.ts

@@ -13,8 +13,10 @@ export default {
     search: '搜 索',
     reset: '重 置',
     audit: '审核',
-    mark: '标识',
-    download: '下载'
+    mark: '标记',
+    download: '下载',
+    confirmSubmit: '确认递交',
+    viewAuditLog: '查看审核记录'
   },
   // 菜单
   menu: {
@@ -35,8 +37,10 @@ export default {
     addCenter: '新增中心',
     confirmEdit: '确认修改信息',
     addDocument: '添加文档',
-    markDocument: '标识文档',
-    auditDocument: '审核文档'
+    markDocument: '标记文档',
+    auditDocument: '审核文档',
+    submitDocument: '递交文档',
+    auditLog: '审核记录'
   },
   // 表单
   form: {
@@ -90,7 +94,9 @@ export default {
     markSuccess: '标识成功',
     markFailed: '标识失败',
     auditSuccess: '审核成功',
-    auditFailed: '审核失败'
+    auditFailed: '审核失败',
+    submitSuccess: '递交成功',
+    submitFailed: '递交失败'
   },
   // 验证规则
   rule: {
@@ -144,6 +150,14 @@ export default {
     resultRequired: '请选择审核结果',
     reasonRequired: '请输入驳回理由'
   },
+  // 递交表单
+  submitForm: {
+    file: '文件'
+  },
+  // 递交验证规则
+  submitRule: {
+    fileRequired: '请上传文件'
+  },
   // 空状态
   empty: {
     description: '请选择文件夹查看文档列表'
@@ -156,6 +170,7 @@ export default {
     name: '文件名称',
     specification: '文档标识',
     planDocumentType: '计划文件类型',
+    status: '状态',
     submitter: '计划递交人',
     submitDeadline: '递交截止日期',
     submitTime: '递交时间',
@@ -164,6 +179,30 @@ export default {
     note: '备注',
     createTime: '创建时间',
     updateTime: '更新时间',
-    action: '操作'
+    action: '操作',
+    statusOptions: {
+      unUpload: '未上传',
+      unAudit: '待审核',
+      auditReject: '审核拒绝',
+      unFiling: '待归档',
+      filing: '已归档',
+      unQualityControl: '待质控',
+      qualityControlPass: '质控通过',
+      qualityControlReject: '质控拒绝'
+    }
+  },
+  // 审核记录
+  auditLog: {
+    result: '审核结果',
+    selectResult: '请选择审核结果',
+    pass: '通过',
+    reject: '驳回',
+    auditTime: '审核时间',
+    startTime: '开始时间',
+    endTime: '结束时间',
+    documentName: '文档名称',
+    auditorType: '审核人类型',
+    auditorName: '审核人',
+    rejectReason: '驳回理由'
   }
 };

+ 182 - 0
src/views/document/folder/components/documentAuditLog.vue

@@ -0,0 +1,182 @@
+<template>
+    <div>
+        <el-dialog v-model="dialogVisible" :title="t('document.document.dialog.auditLog')" width="1000px"
+            append-to-body>
+            <!-- 搜索栏 -->
+            <el-form :model="queryParams" :inline="true" class="search-form mb-4">
+                <el-form-item :label="t('document.document.auditLog.result')">
+                    <el-select v-model="queryParams.result" :placeholder="t('document.document.auditLog.selectResult')"
+                        clearable>
+                        <el-option :label="t('document.document.auditLog.pass')" value="3" />
+                        <el-option :label="t('document.document.auditLog.reject')" value="2" />
+                    </el-select>
+                </el-form-item>
+                <el-form-item :label="t('document.document.auditLog.auditTime')">
+                    <el-date-picker v-model="auditTimeRange" type="daterange" range-separator="-"
+                        :start-placeholder="t('document.document.auditLog.startTime')"
+                        :end-placeholder="t('document.document.auditLog.endTime')" value-format="YYYY-MM-DD HH:mm:ss" />
+                </el-form-item>
+                <el-form-item>
+                    <el-button type="primary" icon="Search" @click="handleQuery">{{ t('document.document.button.search')
+                    }}</el-button>
+                    <el-button icon="Refresh" @click="handleReset">{{ t('document.document.button.reset') }}</el-button>
+                </el-form-item>
+            </el-form>
+
+            <!-- 审核记录列表 -->
+            <el-table v-loading="loading" :data="auditLogList" border>
+                <el-table-column prop="id" label="ID" width="80" align="center" />
+                <el-table-column prop="documentName" :label="t('document.document.auditLog.documentName')"
+                    min-width="150" show-overflow-tooltip />
+                <el-table-column prop="auditorType" :label="t('document.document.auditLog.auditorType')" width="120"
+                    align="center">
+                    <template #default="scope">
+                        <dict-tag v-if="scope.row.auditorType" :options="auditor_type" :value="scope.row.auditorType" />
+                        <span v-else>-</span>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="auditorName" :label="t('document.document.auditLog.auditorName')" width="120"
+                    align="center" />
+                <el-table-column prop="result" :label="t('document.document.auditLog.result')" width="100"
+                    align="center">
+                    <template #default="scope">
+                        <el-tag v-if="scope.row.result === 3" size="small" type="success">
+                            {{ t('document.document.auditLog.pass') }}
+                        </el-tag>
+                        <el-tag v-else-if="scope.row.result === 2" size="small" type="danger">
+                            {{ t('document.document.auditLog.reject') }}
+                        </el-tag>
+                        <span v-else>-</span>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="rejectReason" :label="t('document.document.auditLog.rejectReason')"
+                    min-width="180" show-overflow-tooltip />
+                <el-table-column prop="auditTime" :label="t('document.document.auditLog.auditTime')" width="180"
+                    align="center">
+                    <template #default="scope">
+                        <span>{{ parseTime(scope.row.auditTime) }}</span>
+                    </template>
+                </el-table-column>
+                <!-- 操作栏 -->
+                <el-table-column label="操作" width="120" align="center" fixed="right">
+                    <template #default="scope">
+                        <el-button v-if="scope.row.ossUrl" type="primary" link icon="Download"
+                            @click="handleDownload(scope.row.ossUrl, scope.row.documentName)"
+                            :title="t('document.document.button.download')" />
+                        <span v-else>-</span>
+                    </template>
+                </el-table-column>
+            </el-table>
+
+            <!-- 分页 -->
+            <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
+                :total="total" @pagination="getList" />
+        </el-dialog>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, watch, onMounted, getCurrentInstance, toRefs } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { listDocumentAuditLog } from '@/api/document/document';
+import { parseTime } from '@/utils/ruoyi';
+import type { ComponentInternalInstance } from 'vue';
+
+const props = defineProps({
+    documentId: {
+        type: [Number, String],
+        required: true
+    },
+    visible: {
+        type: Boolean,
+        default: false
+    }
+});
+
+const emit = defineEmits(['update:visible']);
+
+const { t } = useI18n();
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+// 获取审核人类型字典
+const { auditor_type } = toRefs<any>(proxy?.useDict('auditor_type'));
+
+const dialogVisible = computed({
+    get: () => props.visible,
+    set: (val) => emit('update:visible', val)
+});
+
+const loading = ref(false);
+const total = ref(0);
+const auditLogList = ref<any[]>([]);
+const auditTimeRange = ref<[string, string]>(['', '']);
+
+const queryParams = ref({
+    pageNum: 1,
+    pageSize: 10,
+    documentId: props.documentId,
+    result: undefined,
+    params: {}
+});
+
+/** 查询审核记录列表 */
+const getList = async () => {
+    loading.value = true;
+    // 更新documentId
+    queryParams.value.documentId = props.documentId;
+    // 处理时间范围
+    queryParams.value.params = {};
+    proxy?.addDateRange(queryParams.value, auditTimeRange.value, 'AuditTime', 'earliestTime', 'latestTime');
+
+    const res = await listDocumentAuditLog(queryParams.value);
+    auditLogList.value = res.rows;
+    total.value = res.total;
+    loading.value = false;
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+    queryParams.value.pageNum = 1;
+    getList();
+};
+
+/** 重置按钮操作 */
+const handleReset = () => {
+    queryParams.value.result = undefined;
+    auditTimeRange.value = ['', ''];
+    handleQuery();
+};
+
+/** 监听文档ID变化,重新查询 */
+watch(() => props.documentId, (newVal) => {
+    if (newVal && dialogVisible.value) {
+        getList();
+    }
+});
+
+/** 监听可见性变化,打开时重新查询 */
+watch(() => props.visible, (newVal) => {
+    if (newVal) {
+        getList();
+    }
+});
+
+/** 下载历史文件 */
+const handleDownload = (ossUrl: string, fileName: string) => {
+    if (!ossUrl) return;
+
+    // 创建临时a标签下载文件
+    const link = document.createElement('a');
+    link.href = ossUrl;
+    link.download = fileName;
+    document.body.appendChild(link);
+    link.click();
+    document.body.removeChild(link);
+};
+</script>
+
+<style scoped>
+.search-form {
+    margin-bottom: 16px;
+}
+</style>

+ 256 - 36
src/views/document/folder/document.vue

@@ -115,6 +115,28 @@
                   <span v-else>-</span>
                 </template>
               </el-table-column>
+              <el-table-column prop="status" :label="t('document.document.documentList.status')" width="120"
+                align="center">
+                <template #default="scope">
+                  <el-tag v-if="scope.row.status === 0" size="small" type="info">{{
+                    t('document.document.documentList.statusOptions.unUpload') }}</el-tag>
+                  <el-tag v-else-if="scope.row.status === 1" size="small" type="warning">{{
+                    t('document.document.documentList.statusOptions.unAudit') }}</el-tag>
+                  <el-tag v-else-if="scope.row.status === 2" size="small" type="danger">{{
+                    t('document.document.documentList.statusOptions.auditReject') }}</el-tag>
+                  <el-tag v-else-if="scope.row.status === 3" size="small" type="warning">{{
+                    t('document.document.documentList.statusOptions.unFiling') }}</el-tag>
+                  <el-tag v-else-if="scope.row.status === 4" size="small" type="success">{{
+                    t('document.document.documentList.statusOptions.filing') }}</el-tag>
+                  <el-tag v-else-if="scope.row.status === 5" size="small" type="warning">{{
+                    t('document.document.documentList.statusOptions.unQualityControl') }}</el-tag>
+                  <el-tag v-else-if="scope.row.status === 6" size="small" type="success">{{
+                    t('document.document.documentList.statusOptions.qualityControlPass') }}</el-tag>
+                  <el-tag v-else-if="scope.row.status === 7" size="small" type="danger">{{
+                    t('document.document.documentList.statusOptions.qualityControlReject') }}</el-tag>
+                  <el-tag v-else size="small" type="default">-</el-tag>
+                </template>
+              </el-table-column>
               <el-table-column prop="submitter" :label="t('document.document.documentList.submitter')" width="120"
                 align="center" />
               <el-table-column prop="submitDeadline" :label="t('document.document.documentList.submitDeadline')"
@@ -134,18 +156,8 @@
               <el-table-column prop="url" :label="t('document.document.documentList.url')" min-width="200">
                 <template #default="scope">
                   <div v-if="scope.row.fileName" class="file-name-cell">
-                    <el-icon :size="18" class="file-icon">
-                      <Document v-if="isWordFile(scope.row.fileName)" style="color: #2b579a;" />
-                      <Grid v-else-if="isExcelFile(scope.row.fileName)" style="color: #217346;" />
-                      <Monitor v-else-if="isPPTFile(scope.row.fileName)" style="color: #d24726;" />
-                      <Reading v-else-if="isPDFFile(scope.row.fileName)" style="color: #e74c3c;" />
-                      <Document v-else style="color: #606266;" />
-                    </el-icon>
-                    <span class="file-name-text">{{ scope.row.fileName }}</span>
-                    <!-- 下载按钮已注释 -->
-                    <!-- <el-link v-if="scope.row.url" type="primary" :href="scope.row.url" :download="scope.row.fileName" target="_blank" :underline="false" class="download-btn">
-                      <el-icon><Download /></el-icon>
-                    </el-link> -->
+                    <svg-icon :icon-class="getFileIconClass(scope.row.fileName)" class="file-icon" :size="18" />
+                    <span class="file-name-text show-overflow-tooltip">{{ scope.row.fileName }}</span>
                   </div>
                   <span v-else>-</span>
                 </template>
@@ -166,15 +178,36 @@
                   <span v-else>-</span>
                 </template>
               </el-table-column>
-              <el-table-column :label="t('document.document.documentList.action')" width="150" align="center"
+              <!-- 操作列 -->
+              <el-table-column :label="t('document.document.documentList.action')" width="200" align="center"
                 fixed="right">
                 <template #default="scope">
-                  <el-button v-hasPermi="['document:document:audit']" type="primary" link :icon="Select"
-                    @click="handleAudit(scope.row)" :title="t('document.document.button.audit')" />
+                  <!-- 审核按钮 -->
+                  <el-button v-if="scope.row.url && scope.row.status === 1" v-hasPermi="['document:document:audit']"
+                    type="primary" link :icon="Select" @click="handleAudit(scope.row)"
+                    :title="t('document.document.button.audit')" />
+
+                  <!-- 递交按钮 -->
+                  <el-button
+                    v-if="(scope.row.status === 0 || scope.row.status === 2) && scope.row.submitterId === userStore.userId"
+                    v-hasPermi="['document:document:submit']" type="primary" link :icon="Upload"
+                    @click="handleSubmit(scope.row)" :title="t('document.document.button.submit')" />
+
+                  <!-- 确认递交按钮 -->
+                  <el-button
+                    v-if="(scope.row.status === 0 || scope.row.status === 2) && scope.row.submitterId === userStore.userId"
+                    v-hasPermi="['document:document:confirmSubmit']" type="primary" link icon="Delete"
+                    @click="handleConfirmSubmit(scope.row)" :title="t('document.document.button.confirmSubmit')" />
+
+                  <!-- 原有按钮 -->
                   <el-button v-hasPermi="['document:document:mark']" type="primary" link icon="Flag"
                     @click="handleMark(scope.row)" :title="t('document.document.button.mark')" />
-                  <el-button type="primary" link icon="Download" @click="handleDownload(scope.row)"
-                    :title="t('document.document.button.download')" :disabled="!scope.row.url" />
+                  <el-button v-if="scope.row.url" type="primary" link icon="Download" @click="handleDownload(scope.row)"
+                    :title="t('document.document.button.download')" />
+
+                  <!-- 查看审核记录按钮 -->
+                  <el-button v-hasPermi="['document:document:logAudit']" type="primary" link icon="DocumentCopy"
+                    @click="handleViewAuditLog(scope.row)" :title="t('document.document.button.viewAuditLog')" />
                 </template>
               </el-table-column>
             </el-table>
@@ -213,7 +246,8 @@
       </el-form>
       <template #footer>
         <div class="dialog-footer">
-          <el-button :loading="buttonLoading" type="primary" @click="submitForm">{{ t('document.document.button.submit')
+          <el-button :loading="buttonLoading" type="primary" @click="submitFolderForm">{{
+            t('document.document.button.submit')
           }}</el-button>
           <el-button @click="cancel">{{ t('document.document.button.cancel') }}</el-button>
         </div>
@@ -311,11 +345,11 @@
       <el-form ref="auditFormRef" :model="auditForm" :rules="auditRules" label-width="120px">
         <el-form-item :label="t('document.document.auditForm.result')" prop="result">
           <el-radio-group v-model="auditForm.result">
-            <el-radio label="0">{{ t('document.document.auditForm.pass') }}</el-radio>
-            <el-radio label="1">{{ t('document.document.auditForm.reject') }}</el-radio>
+            <el-radio label="3">{{ t('document.document.auditForm.pass') }}</el-radio>
+            <el-radio label="2">{{ t('document.document.auditForm.reject') }}</el-radio>
           </el-radio-group>
         </el-form-item>
-        <el-form-item v-if="auditForm.result === '1'" :label="t('document.document.auditForm.reason')" prop="reason">
+        <el-form-item v-if="auditForm.result === '2'" :label="t('document.document.auditForm.reason')" prop="reason">
           <el-input v-model="auditForm.reason" type="textarea" :rows="4"
             placeholder="{{ t('document.document.auditForm.reasonPlaceholder') }}" />
         </el-form-item>
@@ -328,6 +362,25 @@
         </div>
       </template>
     </el-dialog>
+
+    <!-- 递交文档对话框 -->
+    <el-dialog v-model="submitDialog.visible" :title="submitDialog.title" width="500px" append-to-body>
+      <el-form ref="submitFormRef" :model="submitForm" :rules="submitRules" label-width="120px">
+        <el-form-item :label="t('document.document.submitForm.file')" prop="ossId">
+          <fileUpload v-model="submitForm.ossId" :limit="1" :action="'/common/resource/oss/upload'" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="submitButtonLoading" type="primary" @click="submitSubmitForm">{{
+            t('document.document.button.submit') }}</el-button>
+          <el-button @click="cancelSubmit">{{ t('document.document.button.cancel') }}</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 文档审核记录对话框 -->
+    <DocumentAuditLog v-model:visible="auditLogDialog.visible" :document-id="auditLogDialog.documentId" />
   </div>
 </template>
 
@@ -336,17 +389,17 @@ import { ref, reactive, onMounted, onUnmounted, nextTick, getCurrentInstance, wa
 import { useI18n } from 'vue-i18n';
 import { listFolder, addFolder, delFolder, getFolder, updateFolder } from '@/api/document/folder';
 import { FolderListVO, FolderForm } from '@/api/document/folder/types';
-import { addDocument, listDocument, markDocument } from '@/api/document/document';
-import { DocumentForm, DocumentQuery, DocumentVO, DocumentMarkForm } from '@/api/document/document/types';
-import { queryMemberNotInCenter } from '@/api/project/management';
+import { addDocument, listDocument, markDocument, auditDocument, submitDocument, confirmSubmit, listDocumentAuditLog } from '@/api/document/document';
+import { DocumentForm, DocumentQuery, DocumentVO, DocumentMarkForm, DocumentAuditForm, DocumentSubmitForm, DocumentAuditLogVO, DocumentAuditLogQuery } from '@/api/document/document/types'; import { queryMemberNotInCenter } from '@/api/project/management';
 import { MemberNotInCenterVO, MemberNotInCenterQuery } from '@/api/project/management/types';
-import { Folder, Document, Edit, Delete, Plus, MoreFilled, Location, OfficeBuilding, ArrowRight, Download, Select, Grid, Monitor, Reading, Flag } from '@element-plus/icons-vue';
+import { Folder, Document, Edit, Delete, Plus, MoreFilled, Location, OfficeBuilding, ArrowRight, Download, Select, Grid, Monitor, Reading, Flag, Upload } from '@element-plus/icons-vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import type { FormInstance } from 'element-plus';
 import type { ComponentInternalInstance } from 'vue';
 import { useUserStore } from '@/store/modules/user';
 import { checkPermi } from '@/utils/permission';
 import fileUpload from '@/components/FileUpload/index.vue';
+import DocumentAuditLog from './components/documentAuditLog.vue';
 
 interface Props {
   projectId?: number | string;
@@ -377,6 +430,12 @@ const hasAddPlanPermission = computed(() => checkPermi(['document:document:addPl
 // 当前用户信息
 const currentUserName = ref(userStore.nickname || '');
 
+// 审核记录对话框
+const auditLogDialog = reactive({
+  visible: false,
+  documentId: ''
+});
+
 // 对话框
 const dialog = reactive({
   visible: false,
@@ -461,13 +520,13 @@ const markForm = ref<DocumentMarkForm>({
 // 审核表单数据
 interface AuditForm {
   id: number;
-  result: string; // 0: 通过, 1: 驳回
-  reason: string; // 驳回理由
+  result: string; // 3: 通过, 2: reject
+  reason: string; // reject reason
 }
 
 const auditForm = ref<AuditForm>({
   id: 0,
-  result: '0', // 默认通过
+  result: '3', // 默认通过
   reason: ''
 });
 
@@ -489,6 +548,36 @@ const auditRules = reactive({
   ]
 });
 
+// 递交文档对话框
+const submitDialog = reactive({
+  visible: false,
+  title: ''
+});
+
+// 递交表单ref
+const submitFormRef = ref<FormInstance>();
+const submitButtonLoading = ref(false);
+
+// 递交表单数据
+interface SubmitForm {
+  ossId: string; // 文件OSS ID
+}
+
+const submitForm = ref<SubmitForm>({
+  ossId: ''
+});
+
+// 递交表单验证规则
+const submitRules = reactive({
+  ossId: [
+    {
+      required: true,
+      message: t('document.document.submitRule.fileRequired'),
+      trigger: 'blur'
+    }
+  ]
+});
+
 // 当前选中的文档
 const currentDocument = ref<DocumentVO | null>(null);
 
@@ -735,7 +824,7 @@ const handleAddChildWithType = (data: FolderListVO, type: number) => {
 };
 
 // 提交表单
-const submitForm = () => {
+const submitFolderForm = () => {
   folderFormRef.value?.validate(async (valid: boolean) => {
     if (valid) {
       // 如果是编辑,显示确认对话框
@@ -996,10 +1085,13 @@ watch(uploadedFileId, (newVal) => {
       // 自动设置递交时间为当前时间
       const now = new Date();
       documentForm.value.submitTime = proxy?.parseTime(now, '{y}-{m}-{d} {h}:{i}:{s}');
+      // 自动设置递交人为当前用户
+      documentForm.value.submitterId = userStore.userId;
     }
   } else {
     documentForm.value.ossId = undefined;
     documentForm.value.submitTime = undefined;
+    documentForm.value.submitterId = undefined;
   }
 });
 
@@ -1015,6 +1107,9 @@ const submitDocumentForm = () => {
     if (valid) {
       documentButtonLoading.value = true;
       try {
+        // 根据是否上传了文件设置status字段:上传了文件status为1,否则为0
+        const hasUploadedFile = !!uploadedFileId.value;
+
         // 构建完整的请求数据,确保所有字段都存在(参考文件夹的实现)
         const submitData: DocumentForm = {
           id: documentForm.value.id || 0,
@@ -1024,15 +1119,15 @@ const submitDocumentForm = () => {
           folderId: documentForm.value.folderId || 0,
           submitDeadline: documentForm.value.submitDeadline || '',
           planType: documentForm.value.planType || '',
-          ossId: documentForm.value.ossId || null,
-          submitTime: documentForm.value.submitTime || '',
+          ossId: hasUploadedFile ? uploadedFileId.value : null,
+          submitTime: hasUploadedFile ? new Date().toISOString() : '',
+          status: hasUploadedFile ? 1 : 0,
           note: documentForm.value.note || ''
         };
 
         await addDocument(submitData);
         proxy?.$modal.msgSuccess(t('document.document.message.addDocumentSuccess'));
         documentDialog.visible = false;
-        await getList();
         // 刷新文档列表
         await getDocumentList();
       } catch (error) {
@@ -1094,12 +1189,19 @@ const handleDocumentReset = () => {
   getDocumentList();
 };
 
+// 查看审核记录
+const handleViewAuditLog = (row: DocumentVO) => {
+  console.log('handleViewAuditLog called with row:', row);
+  auditLogDialog.documentId = row.id;
+  auditLogDialog.visible = true;
+};
+
 // 审核文档
 const handleAudit = (row: DocumentVO) => {
   currentDocument.value = row;
   auditForm.value = {
     id: row.id,
-    result: '0', // 默认通过
+    result: '3', // 默认通过
     reason: ''
   };
   auditDialog.visible = true;
@@ -1115,7 +1217,7 @@ const cancelAudit = () => {
   auditDialog.visible = false;
   auditForm.value = {
     id: 0,
-    result: '0',
+    result: '3',
     reason: ''
   };
   currentDocument.value = null;
@@ -1127,7 +1229,12 @@ const submitAuditForm = () => {
     if (valid) {
       auditButtonLoading.value = true;
       try {
-        // 暂时不与后端进行交互,仅关闭弹窗
+        const auditData: DocumentAuditForm = {
+          documentId: auditForm.value.id,
+          result: parseInt(auditForm.value.result),
+          rejectReason: auditForm.value.reason
+        };
+        await auditDocument(auditData);
         proxy?.$modal.msgSuccess(t('document.document.message.auditSuccess'));
         auditDialog.visible = false;
         // 刷新文档列表
@@ -1157,6 +1264,78 @@ const handleDownload = (row: DocumentVO) => {
   document.body.removeChild(a);
 };
 
+// 递交文档
+const handleSubmit = (row: DocumentVO) => {
+  currentDocument.value = row;
+  submitForm.value = {
+    ossId: ''
+  };
+  submitDialog.visible = true;
+  submitDialog.title = t('document.document.dialog.submitDocument');
+  // 重置表单验证
+  nextTick(() => {
+    submitFormRef.value?.clearValidate();
+  });
+};
+
+// 取消递交
+const cancelSubmit = () => {
+  submitDialog.visible = false;
+  submitForm.value = {
+    ossId: ''
+  };
+  currentDocument.value = null;
+};
+
+// 提交递交表单
+const submitSubmitForm = () => {
+  submitFormRef.value?.validate(async (valid: boolean) => {
+    if (valid && currentDocument.value) {
+      submitButtonLoading.value = true;
+      try {
+        const submitData: DocumentSubmitForm = {
+          documentId: currentDocument.value.id,
+          ossId: submitForm.value.ossId
+        };
+        await submitDocument(submitData);
+        proxy?.$modal.msgSuccess(t('document.document.message.submitSuccess'));
+        submitDialog.visible = false;
+        // 刷新文档列表
+        await getDocumentList();
+      } catch (error) {
+        console.error(t('document.document.message.submitFailed'), error);
+      } finally {
+        submitButtonLoading.value = false;
+      }
+    }
+  });
+};
+
+// 确认递交文档
+const handleConfirmSubmit = async (row: DocumentVO) => {
+  try {
+    await ElMessageBox.confirm(
+      '确认该文件已成功递交?',
+      '操作确认',
+      {
+        confirmButtonText: '确认',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }
+    );
+
+    await confirmSubmit(row.id);
+    ElMessage.success('确认递交成功');
+    // 刷新文档列表
+    await getDocumentList();
+  } catch (error) {
+    if (error !== 'cancel') {
+      console.error('确认递交失败:', error);
+      ElMessage.error('确认递交失败');
+    }
+  }
+};
+
 // 标识文档
 const handleMark = (row: DocumentVO) => {
   currentDocument.value = row;
@@ -1233,6 +1412,21 @@ const isPDFFile = (fileName: string): boolean => {
   return fileName.toLowerCase().endsWith('.pdf');
 };
 
+// 获取文件类型对应的图标类名
+const getFileIconClass = (fileName: string): string => {
+  if (isWordFile(fileName)) {
+    return 'document-word';
+  } else if (isExcelFile(fileName)) {
+    return 'document-excel';
+  } else if (isPPTFile(fileName)) {
+    return 'document-ppt';
+  } else if (isPDFFile(fileName)) {
+    return 'document-pdf';
+  } else {
+    return 'document-document';
+  }
+};
+
 // 关闭所有菜单
 const closeAllMenus = () => {
   showSecondaryMenu.value = false;
@@ -1413,6 +1607,9 @@ onUnmounted(() => {
 
     .file-icon {
       flex-shrink: 0;
+      width: 18px;
+      height: 18px;
+      vertical-align: middle;
     }
 
     .file-name-text {
@@ -1502,4 +1699,27 @@ onUnmounted(() => {
     }
   }
 }
-</style>
+
+.show-overflow-tooltip {
+  position: relative;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  max-width: 100%;
+  cursor: help;
+}
+
+.show-overflow-tooltip:hover {
+  overflow: visible;
+  white-space: normal;
+  word-break: break-all;
+  position: absolute;
+  background-color: rgba(0, 0, 0, 0.8);
+  color: white;
+  padding: 4px 8px;
+  border-radius: 4px;
+  z-index: 1000;
+  max-width: 300px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2);
+}
+</style>

+ 111 - 119
src/views/document/folder/project.vue

@@ -2,128 +2,120 @@
   <div>
     <!-- 项目列表视图 -->
     <div v-if="!showDocumentView">
-    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
-      <div v-show="showSearch" class="mb-[10px]">
-        <el-card shadow="hover">
-          <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="100px">
-            <el-form-item :label="t('project.management.search.code')" prop="code">
-              <el-input v-model="queryParams.code" :placeholder="t('project.management.search.codePlaceholder')" clearable @keyup.enter="handleQuery" style="width: 240px;" />
-            </el-form-item>
-            <el-form-item :label="t('project.management.search.name')" prop="name">
-              <el-input v-model="queryParams.name" :placeholder="t('project.management.search.namePlaceholder')" clearable @keyup.enter="handleQuery" style="width: 240px;" />
-            </el-form-item>
-            <el-form-item :label="t('project.management.search.language')" prop="language">
-              <el-select v-model="queryParams.language" :placeholder="t('project.management.search.languagePlaceholder')" clearable style="width: 240px;">
-                <el-option v-for="dict in project_language" :key="dict.value" :label="parseI18nName(dict.label)" :value="dict.value"/>
-              </el-select>
-            </el-form-item>
-            <el-form-item :label="t('project.management.search.type')" prop="type">
-              <el-select v-model="queryParams.type" :placeholder="t('project.management.search.typePlaceholder')" clearable style="width: 240px;">
-                <el-option v-for="dict in project_type" :key="dict.value" :label="parseI18nName(dict.label)" :value="dict.value"/>
-              </el-select>
-            </el-form-item>
-            <el-form-item :label="t('project.management.search.startTime')">
-              <el-date-picker
-                v-model="dateRangeStartTime"
-                value-format="YYYY-MM-DD HH:mm:ss"
-                type="daterange"
-                range-separator="-"
-                :start-placeholder="t('project.management.search.startDate')"
-                :end-placeholder="t('project.management.search.endDate')"
-                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
-                style="width: 240px;"
-              />
-            </el-form-item>
-            <el-form-item :label="t('project.management.search.endTime')">
-              <el-date-picker
-                v-model="dateRangeEndTime"
-                value-format="YYYY-MM-DD HH:mm:ss"
-                type="daterange"
-                range-separator="-"
-                :start-placeholder="t('project.management.search.startDate')"
-                :end-placeholder="t('project.management.search.endDate')"
-                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
-                style="width: 240px;"
-              />
-            </el-form-item>
-            <el-form-item :label="t('project.management.search.createTime')">
-              <el-date-picker
-                v-model="dateRangeCreateTime"
-                value-format="YYYY-MM-DD HH:mm:ss"
-                type="daterange"
-                range-separator="-"
-                :start-placeholder="t('project.management.search.startDate')"
-                :end-placeholder="t('project.management.search.endDate')"
-                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
-                style="width: 240px;"
-              />
-            </el-form-item>
-            <el-form-item :label="t('project.management.search.updateTime')">
-              <el-date-picker
-                v-model="dateRangeUpdateTime"
-                value-format="YYYY-MM-DD HH:mm:ss"
-                type="daterange"
-                range-separator="-"
-                :start-placeholder="t('project.management.search.startDate')"
-                :end-placeholder="t('project.management.search.endDate')"
-                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
-                style="width: 240px;"
-              />
-            </el-form-item>
-            <el-form-item>
-              <el-button type="primary" icon="Search" @click="handleQuery">{{ t('project.management.search.search') }}</el-button>
-              <el-button icon="Refresh" @click="resetQuery">{{ t('project.management.search.reset') }}</el-button>
-            </el-form-item>
-          </el-form>
-        </el-card>
-      </div>
-    </transition>
+      <transition :enter-active-class="proxy?.animate.searchAnimate.enter"
+        :leave-active-class="proxy?.animate.searchAnimate.leave">
+        <div v-show="showSearch" class="mb-[10px]">
+          <el-card shadow="hover">
+            <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="100px">
+              <el-form-item :label="t('project.management.search.code')" prop="code">
+                <el-input v-model="queryParams.code" :placeholder="t('project.management.search.codePlaceholder')"
+                  clearable @keyup.enter="handleQuery" style="width: 240px;" />
+              </el-form-item>
+              <el-form-item :label="t('project.management.search.name')" prop="name">
+                <el-input v-model="queryParams.name" :placeholder="t('project.management.search.namePlaceholder')"
+                  clearable @keyup.enter="handleQuery" style="width: 240px;" />
+              </el-form-item>
+              <el-form-item :label="t('project.management.search.language')" prop="language">
+                <el-select v-model="queryParams.language"
+                  :placeholder="t('project.management.search.languagePlaceholder')" clearable style="width: 240px;">
+                  <el-option v-for="dict in project_language" :key="dict.value" :label="parseI18nName(dict.label)"
+                    :value="dict.value" />
+                </el-select>
+              </el-form-item>
+              <el-form-item :label="t('project.management.search.type')" prop="type">
+                <el-select v-model="queryParams.type" :placeholder="t('project.management.search.typePlaceholder')"
+                  clearable style="width: 240px;">
+                  <el-option v-for="dict in project_type" :key="dict.value" :label="parseI18nName(dict.label)"
+                    :value="dict.value" />
+                </el-select>
+              </el-form-item>
+              <el-form-item :label="t('project.management.search.startTime')">
+                <el-date-picker v-model="dateRangeStartTime" value-format="YYYY-MM-DD HH:mm:ss" type="daterange"
+                  range-separator="-" :start-placeholder="t('project.management.search.startDate')"
+                  :end-placeholder="t('project.management.search.endDate')"
+                  :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
+                  style="width: 240px;" />
+              </el-form-item>
+              <el-form-item :label="t('project.management.search.endTime')">
+                <el-date-picker v-model="dateRangeEndTime" value-format="YYYY-MM-DD HH:mm:ss" type="daterange"
+                  range-separator="-" :start-placeholder="t('project.management.search.startDate')"
+                  :end-placeholder="t('project.management.search.endDate')"
+                  :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
+                  style="width: 240px;" />
+              </el-form-item>
+              <el-form-item :label="t('project.management.search.createTime')">
+                <el-date-picker v-model="dateRangeCreateTime" value-format="YYYY-MM-DD HH:mm:ss" type="daterange"
+                  range-separator="-" :start-placeholder="t('project.management.search.startDate')"
+                  :end-placeholder="t('project.management.search.endDate')"
+                  :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
+                  style="width: 240px;" />
+              </el-form-item>
+              <el-form-item :label="t('project.management.search.updateTime')">
+                <el-date-picker v-model="dateRangeUpdateTime" value-format="YYYY-MM-DD HH:mm:ss" type="daterange"
+                  range-separator="-" :start-placeholder="t('project.management.search.startDate')"
+                  :end-placeholder="t('project.management.search.endDate')"
+                  :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
+                  style="width: 240px;" />
+              </el-form-item>
+              <el-form-item>
+                <el-button type="primary" icon="Search" @click="handleQuery">{{ t('project.management.search.search')
+                }}</el-button>
+                <el-button icon="Refresh" @click="resetQuery">{{ t('project.management.search.reset') }}</el-button>
+              </el-form-item>
+            </el-form>
+          </el-card>
+        </div>
+      </transition>
 
-    <el-card shadow="never">
-      <template #header>
-        <el-row :gutter="10" class="mb8">
-          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
-        </el-row>
-      </template>
+      <el-card shadow="never">
+        <template #header>
+          <el-row :gutter="10" class="mb8">
+            <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+          </el-row>
+        </template>
 
-      <el-table v-loading="loading" border :data="projectList">
-        <el-table-column :label="t('project.management.table.id')" align="center" prop="id" width="100" />
-        <el-table-column :label="t('project.management.table.code')" align="center" prop="code" width="200" />
-        <el-table-column :label="t('project.management.table.name')" align="center" prop="name" width="200" />
-        <el-table-column :label="t('project.management.table.language')" align="center" prop="language" width="150">
-          <template #default="scope">
-            <dict-tag :options="project_language" :value="scope.row.language" />
-          </template>
-        </el-table-column>
-        <el-table-column :label="t('project.management.table.type')" align="center" prop="type" width="200">
-          <template #default="scope">
-            <dict-tag :options="project_type" :value="scope.row.type" />
-          </template>
-        </el-table-column>
-        <el-table-column :label="t('project.management.table.status')" align="center" prop="status" width="100" />
-        <el-table-column :label="t('project.management.table.startTime')" align="center" prop="startTime" width="120">
-          <template #default="scope">
-            <span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }}</span>
-          </template>
-        </el-table-column>
-        <el-table-column :label="t('project.management.table.endTime')" align="center" prop="endTime" width="120">
-          <template #default="scope">
-            <span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d}') }}</span>
-          </template>
-        </el-table-column>
-        <el-table-column :label="t('project.management.table.createTime')" align="center" prop="createTime" min-width="180" />
-        <el-table-column :label="t('project.management.table.updateTime')" align="center" prop="updateTime" min-width="180" />
-        <el-table-column :label="t('project.management.table.actions')" align="center" width="80" fixed="right">
-          <template #default="scope">
-            <el-tooltip :content="t('project.management.table.enterProject')" placement="top">
-              <el-button v-hasPermi="['document:folder:entry']" type="primary" link icon="ArrowRight" @click="handleEnterProject(scope.row)" />
-            </el-tooltip>
-          </template>
-        </el-table-column>
-      </el-table>
+        <el-table v-loading="loading" border :data="projectList">
+          <el-table-column :label="t('project.management.table.id')" align="center" prop="id" width="100" />
+          <el-table-column :label="t('project.management.table.code')" align="center" prop="code" width="200" />
+          <el-table-column :label="t('project.management.table.name')" align="center" prop="name" width="200" />
+          <el-table-column :label="t('project.management.table.language')" align="center" prop="language" width="150">
+            <template #default="scope">
+              <dict-tag :options="project_language" :value="scope.row.language" />
+            </template>
+          </el-table-column>
+          <el-table-column :label="t('project.management.table.type')" align="center" prop="type" width="200">
+            <template #default="scope">
+              <dict-tag :options="project_type" :value="scope.row.type" />
+            </template>
+          </el-table-column>
+          <el-table-column :label="t('project.management.table.status')" align="center" prop="status" width="100" />
+          <el-table-column :label="t('project.management.table.startTime')" align="center" prop="startTime" width="120">
+            <template #default="scope">
+              <span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column :label="t('project.management.table.endTime')" align="center" prop="endTime" width="120">
+            <template #default="scope">
+              <span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d}') }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column :label="t('project.management.table.createTime')" align="center" prop="createTime"
+            min-width="180" />
+          <el-table-column :label="t('project.management.table.updateTime')" align="center" prop="updateTime"
+            min-width="180" />
+          <el-table-column :label="t('project.management.table.actions')" align="center" width="80" fixed="right">
+            <template #default="scope">
+              <el-tooltip :content="t('project.management.table.enterProject')" placement="top">
+                <el-button v-hasPermi="['document:folder:entry']" type="primary" link icon="ArrowRight"
+                  @click="handleEnterProject(scope.row)" />
+              </el-tooltip>
+            </template>
+          </el-table-column>
+        </el-table>
 
-      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
-    </el-card>
+        <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+          v-model:limit="queryParams.pageSize" @pagination="getList" />
+      </el-card>
     </div>
 
     <!-- 文档管理视图 -->

+ 1 - 1
src/views/project/management/detail/pages/projectMember.vue

@@ -22,7 +22,7 @@
           <template #default="scope">
             <el-tooltip content="修改文件夹权限" placement="top">
               <el-button v-hasPermi="['project:management:queryProjectMemberAssignFolders']" type="primary" link
-                icon="Folder" @click="handleAssignFolders(scope.row)" />
+                icon="Setting" @click="handleAssignFolders(scope.row)" />
             </el-tooltip>
           </template>
         </el-table-column>

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff