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

质控完成一半
前面操作大部分修改

Huanyi преди 3 месеца
родител
ревизия
3ec0e91f37
променени са 100 файла, в които са добавени 4548 реда и са изтрити 4061 реда
  1. 23 0
      src/api/Qc/task/index.ts
  2. 10 3
      src/api/Qc/task/types.ts
  3. 2 2
      src/api/document/document/index.ts
  4. 6 1
      src/api/document/document/types.ts
  5. 0 3
      src/api/document/folder/types.ts
  6. 4 2
      src/api/home/taskCenter/audit/index.ts
  7. 1 1
      src/api/home/taskCenter/audit/types.ts
  8. 15 13
      src/api/home/taskCenter/filing/index.ts
  9. 23 23
      src/api/home/taskCenter/filing/types.ts
  10. 4 2
      src/api/home/taskCenter/submission/index.ts
  11. 1 1
      src/api/home/taskCenter/submission/types.ts
  12. 46 1
      src/api/project/management/index.ts
  13. 70 3
      src/api/project/management/types.ts
  14. 5 5
      src/api/search/index.ts
  15. 9 9
      src/api/setting/ai/index.ts
  16. 8 8
      src/api/setting/ai/types.ts
  17. 0 3
      src/api/setting/carousel/types.ts
  18. 3 6
      src/api/setting/textin/types.ts
  19. 9 9
      src/api/system/signature.ts
  20. 1 1
      src/api/system/user/types.ts
  21. 4 10
      src/api/workflow/spel/types.ts
  22. 374 383
      src/api/wps/save.ts
  23. 549 593
      src/components/AnnotationEditor/index.vue
  24. 13 30
      src/components/ArchiveConfirmDialog/index.vue
  25. 27 17
      src/components/AuditLogDialog/index.vue
  26. 2 3
      src/components/Breadcrumb/index.vue
  27. 153 146
      src/components/DataPermisionTree/TreeNode.vue
  28. 93 91
      src/components/DataPermisionTree/demo.vue
  29. 181 174
      src/components/DataPermisionTree/index.vue
  30. 7 7
      src/components/DataPermisionTree/types.ts
  31. 1 1
      src/components/DictTag/index.vue
  32. 41 53
      src/components/DocumentAuditDialog/index.vue
  33. 8 6
      src/components/Editor/index.vue
  34. 20 4
      src/components/Process/submitVerify.vue
  35. 7 1
      src/components/RightToolbar/index.vue
  36. 12 2
      src/components/RoleSelect/index.vue
  37. 25 18
      src/components/TopNav/index.vue
  38. 12 2
      src/components/UserSelect/index.vue
  39. 198 195
      src/components/WpsEditor/index.vue
  40. 52 52
      src/enums/documentStatus.ts
  41. 9 3
      src/lang/modules/document/document/en_US.ts
  42. 9 3
      src/lang/modules/document/document/zh_CN.ts
  43. 1 1
      src/lang/modules/home/index.ts
  44. 1 1
      src/lang/modules/home/index_en.ts
  45. 137 141
      src/lang/modules/home/taskCenter/en_US.ts
  46. 138 138
      src/lang/modules/home/taskCenter/zh_CN.ts
  47. 56 56
      src/lang/modules/search/en_US.ts
  48. 56 56
      src/lang/modules/search/zh_CN.ts
  49. 28 28
      src/lang/modules/setting/agreement/en_US.ts
  50. 28 28
      src/lang/modules/setting/agreement/zh_CN.ts
  51. 19 19
      src/lang/modules/setting/ai/en_US.ts
  52. 19 19
      src/lang/modules/setting/ai/zh_CN.ts
  53. 1 1
      src/lang/modules/system/menu/en_US.ts
  54. 1 1
      src/lang/modules/system/menu/zh_CN.ts
  55. 1 1
      src/lang/modules/system/role/en_US.ts
  56. 1 1
      src/lang/modules/system/role/zh_CN.ts
  57. 2 2
      src/lang/modules/system/user/en_US.ts
  58. 40 42
      src/layout/components/Navbar.vue
  59. 12 12
      src/layout/components/Settings/index.vue
  60. 1 1
      src/layout/components/Sidebar/Logo.vue
  61. 2 2
      src/layout/components/TagsView/index.vue
  62. 6 3
      src/main.ts
  63. 10 10
      src/router/demo.ts
  64. 3 5
      src/router/index.ts
  65. 7 7
      src/utils/dict.ts
  66. 67 36
      src/utils/web-office-sdk-solution-v2.0.7/index.d.ts
  67. 0 0
      src/utils/web-office-sdk-solution-v2.0.7/web-office-sdk-solution-v2.0.7.cjs.js
  68. 0 0
      src/utils/web-office-sdk-solution-v2.0.7/web-office-sdk-solution-v2.0.7.es.js
  69. 0 0
      src/utils/web-office-sdk-solution-v2.0.7/web-office-sdk-solution-v2.0.7.umd.js
  70. 228 0
      src/views/Qc/task/detail.vue
  71. 86 283
      src/views/Qc/task/index.vue
  72. 248 0
      src/views/Qc/task/list.vue
  73. 114 115
      src/views/demo/FixVerification.vue
  74. 161 151
      src/views/demo/PermissionTreeDemo.vue
  75. 92 93
      src/views/demo/TestPermissionTree.vue
  76. 92 92
      src/views/demo/TreeDemo.vue
  77. 80 81
      src/views/demo/TreeTest.vue
  78. 28 55
      src/views/document/folder/document/DocumentList.vue
  79. 54 62
      src/views/document/folder/document/index.vue
  80. 68 42
      src/views/document/folder/project.vue
  81. 23 33
      src/views/home/taskCenter/audit/index.vue
  82. 27 24
      src/views/home/taskCenter/filing/index.vue
  83. 25 38
      src/views/home/taskCenter/submission/index.vue
  84. 36 24
      src/views/home/workbench/index.vue
  85. 1 2
      src/views/index.vue
  86. 20 20
      src/views/login.vue
  87. 5 5
      src/views/monitor/online/index.vue
  88. 1 1
      src/views/project/management/detail/components/header.vue
  89. 8 27
      src/views/project/management/detail/components/sidebar.vue
  90. 4 4
      src/views/project/management/detail/index.vue
  91. 11 17
      src/views/project/management/detail/pages/basicInfo.vue
  92. 17 85
      src/views/project/management/detail/pages/centerInfo.vue
  93. 4 27
      src/views/project/management/detail/pages/centerMember.vue
  94. 189 88
      src/views/project/management/detail/pages/projectMember.vue
  95. 1 1
      src/views/project/management/index.vue
  96. 148 103
      src/views/project/management/list.vue
  97. 50 33
      src/views/search/index.vue
  98. 6 6
      src/views/setting/agreement/index.vue
  99. 1 3
      src/views/setting/ai/index.vue
  100. 46 45
      src/views/setting/carousel/index.vue

+ 23 - 0
src/api/Qc/task/index.ts

@@ -62,6 +62,17 @@ export const delTask = (id: string | number | Array<string | number>) => {
   });
 };
 
+/**
+ * 开始文档质控任务
+ * @param id
+ */
+export const startTask = (id: string | number): AxiosPromise<any> => {
+  return request({
+    url: '/qc/task/' + id,
+    method: 'put'
+  });
+};
+
 /**
  * 生成质控任务详情
  * @param projectId
@@ -73,3 +84,15 @@ export const generateTaskDetail = (projectId: string | number): AxiosPromise<any
     params: { projectId }
   });
 };
+
+/**
+ * 查询质控任务详情列表
+ * @param query
+ */
+export const listTaskDetail = (query?: any): AxiosPromise<any> => {
+  return request({
+    url: '/qcTask/detail/listPage',
+    method: 'get',
+    params: query
+  });
+};

+ 10 - 3
src/api/Qc/task/types.ts

@@ -9,6 +9,11 @@ export interface TaskVO {
    */
   name: string;
 
+  /**
+   * 进度
+   */
+  schedule?: number;
+
   /**
    * 发起人
    */
@@ -73,7 +78,6 @@ export interface TaskVO {
    * 质控任务详情列表
    */
   details?: TaskDetailVO[];
-
 }
 
 export interface TaskForm extends BaseEntity {
@@ -97,6 +101,11 @@ export interface TaskForm extends BaseEntity {
    */
   projectId?: string | number;
 
+  /**
+   * 占比(整数)
+   */
+  proportion?: number;
+
   /**
    * 开始时间
    */
@@ -121,11 +130,9 @@ export interface TaskForm extends BaseEntity {
    * 质控任务详情列表
    */
   details?: TaskDetailForm[];
-
 }
 
 export interface TaskQuery extends PageQuery {
-
   /**
    * 质控名称
    */

+ 2 - 2
src/api/document/document/index.ts

@@ -130,7 +130,7 @@ export const downloadDocumentFile = async (ossId: number, fileName?: string) =>
       // 优先使用后端返回的文件名(包含正确的扩展名)
       const downloadFileName = res.headers['download-filename']
         ? decodeURIComponent(res.headers['download-filename'])
-        : (fileName || `download_${ossId}`);
+        : fileName || `download_${ossId}`;
       FileSaver.saveAs(blob, downloadFileName);
       return true;
     } else {
@@ -147,4 +147,4 @@ export const downloadDocumentFile = async (ossId: number, fileName?: string) =>
   } finally {
     loadingInstance.close();
   }
-};
+};

+ 6 - 1
src/api/document/document/types.ts

@@ -79,10 +79,15 @@ export interface DocumentVO {
    */
   submitter?: string;
 
+  /**
+   * 计划递交人ID
+   */
+  planSubmitter?: string | number;
+
   /**
    * 递交人ID
    */
-  submitterId?: string | number;
+  submitter?: string | number;
 
   /**
    * 文档类型:0-非计划文档,1-计划文档

+ 0 - 3
src/api/document/folder/types.ts

@@ -38,7 +38,6 @@ export interface FolderVO {
    * 更新时间
    */
   updateTime: string;
-
 }
 
 export interface FolderForm extends BaseEntity {
@@ -81,11 +80,9 @@ export interface FolderForm extends BaseEntity {
    * 限制层级
    */
   restrictionLevel?: number;
-
 }
 
 export interface FolderQuery extends PageQuery {
-
   /**
    * 所属项目
    */

+ 4 - 2
src/api/home/taskCenter/audit/index.ts

@@ -6,7 +6,9 @@ import { AuditTaskVO, AuditTaskQuery, AuditTaskAuditForm } from './types';
  * 查询文件审批任务列表
  * @param query 查询参数
  */
-export const listAuditTasks = (query: AuditTaskQuery): AxiosPromise<{
+export const listAuditTasks = (
+  query: AuditTaskQuery
+): AxiosPromise<{
   total: number;
   rows: AuditTaskVO[];
 }> => {
@@ -27,4 +29,4 @@ export const auditDocument = (data: AuditTaskAuditForm) => {
     method: 'put',
     data: data
   });
-};
+};

+ 1 - 1
src/api/home/taskCenter/audit/types.ts

@@ -24,4 +24,4 @@ export interface AuditTaskAuditForm {
   documentId: number;
   result: number;
   rejectReason: string;
-}
+}

+ 15 - 13
src/api/home/taskCenter/filing/index.ts

@@ -6,15 +6,17 @@ import { FilingTaskVO, FilingTaskQuery, FilingTaskFilingForm } from './types';
  * 查询归档任务列表
  * @param query 查询参数
  */
-export const listFilingTasks = (query: FilingTaskQuery): AxiosPromise<{
-    total: number;
-    rows: FilingTaskVO[];
+export const listFilingTasks = (
+  query: FilingTaskQuery
+): AxiosPromise<{
+  total: number;
+  rows: FilingTaskVO[];
 }> => {
-    return request({
-        url: '/home/taskCenter/filing/list',
-        method: 'get',
-        params: query
-    });
+  return request({
+    url: '/home/taskCenter/filing/list',
+    method: 'get',
+    params: query
+  });
 };
 
 /**
@@ -22,9 +24,9 @@ export const listFilingTasks = (query: FilingTaskQuery): AxiosPromise<{
  * @param data 归档参数
  */
 export const filingDocument = (data: FilingTaskFilingForm) => {
-    return request({
-        url: '/home/taskCenter/filing/filing',
-        method: 'put',
-        data: data
-    });
+  return request({
+    url: '/home/taskCenter/filing/filing',
+    method: 'put',
+    data: data
+  });
 };

+ 23 - 23
src/api/home/taskCenter/filing/types.ts

@@ -1,31 +1,31 @@
 export interface FilingTaskVO {
-    id: number;
-    name: string;
-    status: number;
-    type: number;
-    documentType: string;
-    submitter: string;
-    submitterId: number;
-    deadline: string;
-    submitTime: string;
-    createBy: string;
-    createTime: string;
-    ossId: number;
-    ossUrl: string;
-    folderId?: number | string;
-    folderName?: string;
-    projectId?: number | string;
-    projectCode?: string;
-    projectName?: string;
+  id: number;
+  name: string;
+  status: number;
+  type: number;
+  documentType: string;
+  submitter: string;
+  submitterId: number;
+  deadline: string;
+  submitTime: string;
+  createBy: string;
+  createTime: string;
+  ossId: number;
+  ossUrl: string;
+  folderId?: number | string;
+  folderName?: string;
+  projectId?: number | string;
+  projectCode?: string;
+  projectName?: string;
 }
 
 export interface FilingTaskQuery extends PageQuery {
-    name?: string;
-    projectCode?: string;
-    projectName?: string;
-    centerName?: string;
+  name?: string;
+  projectCode?: string;
+  projectName?: string;
+  centerName?: string;
 }
 
 export interface FilingTaskFilingForm {
-    documentId: number;
+  documentId: number;
 }

+ 4 - 2
src/api/home/taskCenter/submission/index.ts

@@ -6,7 +6,9 @@ import { SubmissionTaskVO, SubmissionTaskQuery, SubmissionTaskSubmitForm } from
  * 查询文件递交任务列表
  * @param query 查询参数
  */
-export const listSubmissionTasks = (query: SubmissionTaskQuery): AxiosPromise<{
+export const listSubmissionTasks = (
+  query: SubmissionTaskQuery
+): AxiosPromise<{
   total: number;
   rows: SubmissionTaskVO[];
 }> => {
@@ -51,4 +53,4 @@ export const sendDocument = (id: number) => {
     method: 'put',
     data: { id }
   });
-};
+};

+ 1 - 1
src/api/home/taskCenter/submission/types.ts

@@ -23,4 +23,4 @@ export interface SubmissionTaskQuery extends PageQuery {
 export interface SubmissionTaskSubmitForm {
   documentId: number;
   ossId: number;
-}
+}

+ 46 - 1
src/api/project/management/index.ts

@@ -1,6 +1,27 @@
 import request from '@/utils/request';
 import { AxiosPromise } from 'axios';
-import { ManagementVO, ManagementForm, ManagementQuery, ProjectMemberVO, ProjectMemberQuery, UserNotInProjectQuery, UserVO, InviteMemberForm, CenterInfoVO, CenterInfoQuery, MemberNotInCenterVO, MemberNotInCenterQuery, InviteCenterMemberForm, CenterMemberVO, CenterMemberQuery, AddProjectMemberForm, AssignFoldersForm, GetFoldersResponse } from '@/api/project/management/types';
+import {
+  ManagementVO,
+  ManagementForm,
+  ManagementQuery,
+  ProjectMemberVO,
+  ProjectMemberQuery,
+  UserNotInProjectQuery,
+  UserVO,
+  InviteMemberForm,
+  CenterInfoVO,
+  CenterInfoQuery,
+  MemberNotInCenterVO,
+  MemberNotInCenterQuery,
+  InviteCenterMemberForm,
+  CenterMemberVO,
+  CenterMemberQuery,
+  AddProjectMemberForm,
+  AssignFoldersForm,
+  GetFoldersResponse,
+  QueryUserResponse,
+  EditProjectMemberForm
+} from '@/api/project/management/types';
 
 /**
  * 查询项目管理列表
@@ -225,3 +246,27 @@ export const getProjectByName = (name: string): AxiosPromise<Array<{ id: number;
     params: { name }
   });
 };
+
+/**
+ * 查询用户详情
+ * @param id 用户ID
+ */
+export const queryUser = (id: string | number): AxiosPromise<QueryUserResponse> => {
+  return request({
+    url: '/project/management/queryUser',
+    method: 'get',
+    params: { id }
+  });
+};
+
+/**
+ * 修改项目成员信息
+ * @param data
+ */
+export const editProjectMember = (data: EditProjectMemberForm) => {
+  return request({
+    url: '/project/management/queryProjectMemberEditMember',
+    method: 'put',
+    data: data
+  });
+};

+ 70 - 3
src/api/project/management/types.ts

@@ -73,7 +73,6 @@ export interface ManagementVO {
    * 更新时间
    */
   updateTime: string;
-
 }
 
 export interface ManagementForm extends BaseEntity {
@@ -151,11 +150,9 @@ export interface ManagementForm extends BaseEntity {
    * 结束时间
    */
   endTime?: string;
-
 }
 
 export interface ManagementQuery extends PageQuery {
-
   /**
    * 项目编号
    */
@@ -601,3 +598,73 @@ export interface GetFoldersResponse {
    */
   folders: string;
 }
+
+/**
+ * 查询用户详情响应
+ */
+export interface QueryUserResponse {
+  userId: number;
+  tenantId: string;
+  deptId: number;
+  userName: string;
+  nickName: string;
+  userType: string;
+  email: string;
+  phonenumber: string;
+  sex: string;
+  avatar: number;
+  status: string;
+  appletStatus: number;
+  loginIp: string;
+  loginDate: string;
+  remark: string;
+  createTime: string;
+  deptName: string;
+  roles: Array<{
+    roleId: number;
+    roleName: string;
+    roleKey: string;
+    roleSort: number;
+    dataScope: string;
+    menuCheckStrictly: boolean;
+    deptCheckStrictly: boolean;
+    status: string;
+    remark: string;
+    createTime: string;
+    flag: boolean;
+  }>;
+  roleIds: number[];
+  postIds: number[];
+  roleId: number;
+  projects: string;
+}
+
+/**
+ * 修改项目成员表单
+ */
+export interface EditProjectMemberForm {
+  /**
+   * 用户ID
+   */
+  id: string | number;
+
+  /**
+   * 昵称
+   */
+  nickname: string;
+
+  /**
+   * 手机号
+   */
+  phoneNumber: string;
+
+  /**
+   * 邮箱
+   */
+  email: string;
+
+  /**
+   * 新密码
+   */
+  password?: string;
+}

+ 5 - 5
src/api/search/index.ts

@@ -4,9 +4,9 @@ import request from '@/utils/request';
  * 搜索文档列表
  */
 export function listSearchDocuments(query: any) {
-    return request({
-        url: '/search/list',
-        method: 'get',
-        params: query
-    });
+  return request({
+    url: '/search/list',
+    method: 'get',
+    params: query
+  });
 }

+ 9 - 9
src/api/setting/ai/index.ts

@@ -5,19 +5,19 @@ import { AiSettingVO } from './types';
  * 获取AI设置
  */
 export function getAiSetting() {
-    return request({
-        url: '/setting/ai',
-        method: 'get'
-    });
+  return request({
+    url: '/setting/ai',
+    method: 'get'
+  });
 }
 
 /**
  * 更新AI设置
  */
 export function updateAiSetting(data: AiSettingVO) {
-    return request({
-        url: '/setting/ai',
-        method: 'put',
-        data: data
-    });
+  return request({
+    url: '/setting/ai',
+    method: 'put',
+    data: data
+  });
 }

+ 8 - 8
src/api/setting/ai/types.ts

@@ -2,12 +2,12 @@
  * AI设置对象
  */
 export interface AiSettingVO {
-    id?: number;
-    enabledFlag?: number;
-    createDept?: number;
-    createBy?: number;
-    createTime?: string;
-    updateBy?: number;
-    updateTime?: string;
-    params?: Record<string, any>;
+  id?: number;
+  enabledFlag?: number;
+  createDept?: number;
+  createBy?: number;
+  createTime?: string;
+  updateBy?: number;
+  updateTime?: string;
+  params?: Record<string, any>;
 }

+ 0 - 3
src/api/setting/carousel/types.ts

@@ -22,7 +22,6 @@ export interface CarouselVO {
    * 备注
    */
   note: string;
-
 }
 
 export interface CarouselForm extends BaseEntity {
@@ -45,11 +44,9 @@ export interface CarouselForm extends BaseEntity {
    * 备注
    */
   note?: string;
-
 }
 
 export interface CarouselQuery extends PageQuery {
-
   /**
    * 日期范围参数
    */

+ 3 - 6
src/api/setting/textin/types.ts

@@ -5,7 +5,7 @@ export interface TextinVO {
   id: string | number;
 
   /**
-   * x-ti-app-id 
+   * x-ti-app-id
    */
   appId: string | number;
 
@@ -13,7 +13,6 @@ export interface TextinVO {
    * x-ti-secret-code
    */
   secretCode: string;
-
 }
 
 export interface TextinForm extends BaseEntity {
@@ -23,7 +22,7 @@ export interface TextinForm extends BaseEntity {
   id?: string | number;
 
   /**
-   * x-ti-app-id 
+   * x-ti-app-id
    */
   appId?: string | number;
 
@@ -31,13 +30,11 @@ export interface TextinForm extends BaseEntity {
    * x-ti-secret-code
    */
   secretCode?: string;
-
 }
 
 export interface TextinQuery extends PageQuery {
-
   /**
-   * x-ti-app-id 
+   * x-ti-app-id
    */
   appId?: string | number;
 

+ 9 - 9
src/api/system/signature.ts

@@ -5,10 +5,10 @@ import { AxiosPromise } from 'axios';
  * 获取用户签名信息
  */
 export function getSignature(): AxiosPromise<{ id: number }> {
-    return request({
-        url: '/system/user/getSignature',
-        method: 'get'
-    });
+  return request({
+    url: '/system/user/getSignature',
+    method: 'get'
+  });
 }
 
 /**
@@ -16,9 +16,9 @@ export function getSignature(): AxiosPromise<{ id: number }> {
  * @param ossId OSS 文件 ID
  */
 export function downloadSignature(ossId: number): Promise<Blob> {
-    return request({
-        url: `/resource/oss/downloadWithoutPermission/${ossId}`,
-        method: 'get',
-        responseType: 'blob'
-    });
+  return request({
+    url: `/resource/oss/downloadWithoutPermission/${ossId}`,
+    method: 'get',
+    responseType: 'blob'
+  });
 }

+ 1 - 1
src/api/system/user/types.ts

@@ -20,7 +20,7 @@ export interface UserQuery extends PageQuery {
   status?: string;
   deptId?: string | number;
   roleId?: string | number;
-  userIds?:  string | number | (string | number)[] | undefined;
+  userIds?: string | number | (string | number)[] | undefined;
 }
 
 /**

+ 4 - 10
src/api/workflow/spel/types.ts

@@ -33,7 +33,6 @@ export interface SpelVO {
    * 备注
    */
   remark?: string;
-
 }
 
 export interface SpelForm extends BaseEntity {
@@ -71,11 +70,9 @@ export interface SpelForm extends BaseEntity {
    * 备注
    */
   remark?: string;
-
 }
 
 export interface SpelQuery extends PageQuery {
-
   /**
    * 组件名称
    */
@@ -101,11 +98,8 @@ export interface SpelQuery extends PageQuery {
    */
   status?: string;
 
-    /**
-     * 日期范围参数
-     */
-    params?: any;
+  /**
+   * 日期范围参数
+   */
+  params?: any;
 }
-
-
-

+ 374 - 383
src/api/wps/save.ts

@@ -3,7 +3,7 @@ import CryptoJS from 'crypto-js';
 
 /**
  * WPS 三阶段保存接口实现
- * 
+ *
  * 注意:当前实现包含前端模拟版本,用于开发和测试
  * 生产环境应该使用真实的后端接口
  */
@@ -13,56 +13,56 @@ const USE_MOCK = true; // 设置为 false 使用真实后端接口
 
 // 文档信息接口
 export interface FileInfo {
-    id: string;
-    name: string;
-    version: number;
-    size: number;
-    create_time: number;
-    modify_time: number;
-    creator_id: string;
-    modifier_id: string;
+  id: string;
+  name: string;
+  version: number;
+  size: number;
+  create_time: number;
+  modify_time: number;
+  creator_id: string;
+  modifier_id: string;
 }
 
 // 准备上传参数
 export interface PrepareUploadParams {
-    file_id: string;
+  file_id: string;
 }
 
 // 准备上传返回
 export interface PrepareUploadResponse {
-    digest_types: string[];
+  digest_types: string[];
 }
 
 // 获取上传地址参数
 export interface GetUploadAddressParams {
-    file_id: string;
-    name: string;
-    size: number;
-    digest: Record<string, string>;
-    is_manual: boolean;
-    attachment_size?: number;
-    content_type?: string;
+  file_id: string;
+  name: string;
+  size: number;
+  digest: Record<string, string>;
+  is_manual: boolean;
+  attachment_size?: number;
+  content_type?: string;
 }
 
 // 获取上传地址返回
 export interface GetUploadAddressResponse {
-    url: string;
-    method: string;
-    headers?: Record<string, string>;
-    params?: Record<string, string>;
-    send_back_params?: Record<string, string>;
+  url: string;
+  method: string;
+  headers?: Record<string, string>;
+  params?: Record<string, string>;
+  send_back_params?: Record<string, string>;
 }
 
 // 上传完成参数
 export interface UploadCompleteParams {
-    file_id: string;
-    request: GetUploadAddressParams;
-    response: {
-        status_code: number;
-        headers?: Record<string, string>;
-        body?: string; // base64 编码
-    };
-    send_back_params?: Record<string, string>;
+  file_id: string;
+  request: GetUploadAddressParams;
+  response: {
+    status_code: number;
+    headers?: Record<string, string>;
+    body?: string; // base64 编码
+  };
+  send_back_params?: Record<string, string>;
 }
 
 /**
@@ -70,17 +70,17 @@ export interface UploadCompleteParams {
  * 协商摘要算法
  */
 export const prepareUpload = (params: PrepareUploadParams): Promise<PrepareUploadResponse> => {
-    if (USE_MOCK) {
-        // 前端模拟实现
-        return Promise.resolve({
-            digest_types: ['sha1', 'md5', 'sha256']
-        });
-    }
-
-    return request({
-        url: `/v3/3rd/files/${params.file_id}/upload/prepare`,
-        method: 'get'
+  if (USE_MOCK) {
+    // 前端模拟实现
+    return Promise.resolve({
+      digest_types: ['sha1', 'md5', 'sha256']
     });
+  }
+
+  return request({
+    url: `/v3/3rd/files/${params.file_id}/upload/prepare`,
+    method: 'get'
+  });
 };
 
 /**
@@ -88,32 +88,32 @@ export const prepareUpload = (params: PrepareUploadParams): Promise<PrepareUploa
  * 获取文件上传的目标地址
  */
 export const getUploadAddress = (params: GetUploadAddressParams): Promise<GetUploadAddressResponse> => {
-    if (USE_MOCK) {
-        // 前端模拟实现 - 使用 Blob URL
-        return Promise.resolve({
-            url: 'mock://upload', // 模拟上传地址
-            method: 'PUT',
-            headers: {},
-            params: {},
-            send_back_params: {
-                file_id: params.file_id,
-                timestamp: Date.now().toString()
-            }
-        });
-    }
-
-    return request({
-        url: `/v3/3rd/files/${params.file_id}/upload/address`,
-        method: 'post',
-        data: {
-            name: params.name,
-            size: params.size,
-            digest: params.digest,
-            is_manual: params.is_manual,
-            attachment_size: params.attachment_size,
-            content_type: params.content_type
-        }
+  if (USE_MOCK) {
+    // 前端模拟实现 - 使用 Blob URL
+    return Promise.resolve({
+      url: 'mock://upload', // 模拟上传地址
+      method: 'PUT',
+      headers: {},
+      params: {},
+      send_back_params: {
+        file_id: params.file_id,
+        timestamp: Date.now().toString()
+      }
     });
+  }
+
+  return request({
+    url: `/v3/3rd/files/${params.file_id}/upload/address`,
+    method: 'post',
+    data: {
+      name: params.name,
+      size: params.size,
+      digest: params.digest,
+      is_manual: params.is_manual,
+      attachment_size: params.attachment_size,
+      content_type: params.content_type
+    }
+  });
 };
 
 /**
@@ -121,57 +121,57 @@ export const getUploadAddress = (params: GetUploadAddressParams): Promise<GetUpl
  * 通知接入方上传已完成
  */
 export const uploadComplete = (params: UploadCompleteParams): Promise<FileInfo> => {
-    if (USE_MOCK) {
-        // 前端模拟实现 - 使用 localStorage 存储文件信息
-        const fileId = params.file_id;
-        const currentTime = Math.floor(Date.now() / 1000);
-
-        // 从 localStorage 获取或创建文件记录
-        const storageKey = `wps_file_${fileId}`;
-        let fileRecord: any = null;
-
-        try {
-            const stored = localStorage.getItem(storageKey);
-            if (stored) {
-                fileRecord = JSON.parse(stored);
-            }
-        } catch (err) {
-            console.warn('读取文件记录失败:', err);
-        }
-
-        // 创建或更新文件信息
-        const version = fileRecord ? fileRecord.version + 1 : 1;
-        const fileInfo: FileInfo = {
-            id: fileId,
-            name: params.request.name,
-            version: version,
-            size: params.request.size,
-            create_time: fileRecord ? fileRecord.create_time : currentTime,
-            modify_time: currentTime,
-            creator_id: fileRecord ? fileRecord.creator_id : 'user_' + Date.now(),
-            modifier_id: 'user_' + Date.now()
-        };
-
-        // 保存到 localStorage
-        try {
-            localStorage.setItem(storageKey, JSON.stringify(fileInfo));
-            console.log('[前端模拟] 文件信息已保存到 localStorage:', fileInfo);
-        } catch (err) {
-            console.error('[前端模拟] 保存文件信息失败:', err);
-        }
-
-        return Promise.resolve(fileInfo);
+  if (USE_MOCK) {
+    // 前端模拟实现 - 使用 localStorage 存储文件信息
+    const fileId = params.file_id;
+    const currentTime = Math.floor(Date.now() / 1000);
+
+    // 从 localStorage 获取或创建文件记录
+    const storageKey = `wps_file_${fileId}`;
+    let fileRecord: any = null;
+
+    try {
+      const stored = localStorage.getItem(storageKey);
+      if (stored) {
+        fileRecord = JSON.parse(stored);
+      }
+    } catch (err) {
+      console.warn('读取文件记录失败:', err);
     }
 
-    return request({
-        url: `/v3/3rd/files/${params.file_id}/upload/complete`,
-        method: 'post',
-        data: {
-            request: params.request,
-            response: params.response,
-            send_back_params: params.send_back_params
-        }
-    });
+    // 创建或更新文件信息
+    const version = fileRecord ? fileRecord.version + 1 : 1;
+    const fileInfo: FileInfo = {
+      id: fileId,
+      name: params.request.name,
+      version: version,
+      size: params.request.size,
+      create_time: fileRecord ? fileRecord.create_time : currentTime,
+      modify_time: currentTime,
+      creator_id: fileRecord ? fileRecord.creator_id : 'user_' + Date.now(),
+      modifier_id: 'user_' + Date.now()
+    };
+
+    // 保存到 localStorage
+    try {
+      localStorage.setItem(storageKey, JSON.stringify(fileInfo));
+      console.log('[前端模拟] 文件信息已保存到 localStorage:', fileInfo);
+    } catch (err) {
+      console.error('[前端模拟] 保存文件信息失败:', err);
+    }
+
+    return Promise.resolve(fileInfo);
+  }
+
+  return request({
+    url: `/v3/3rd/files/${params.file_id}/upload/complete`,
+    method: 'post',
+    data: {
+      request: params.request,
+      response: params.response,
+      send_back_params: params.send_back_params
+    }
+  });
 };
 
 /**
@@ -179,38 +179,35 @@ export const uploadComplete = (params: UploadCompleteParams): Promise<FileInfo>
  * @param file 文件 Blob 或 ArrayBuffer
  * @param algorithm 算法类型 md5/sha1/sha256
  */
-export const calculateDigest = async (
-    file: Blob | ArrayBuffer,
-    algorithm: 'md5' | 'sha1' | 'sha256'
-): Promise<string> => {
-    let arrayBuffer: ArrayBuffer;
-
-    if (file instanceof Blob) {
-        arrayBuffer = await file.arrayBuffer();
-    } else {
-        arrayBuffer = file;
-    }
-
-    // 转换为 WordArray
-    const wordArray = CryptoJS.lib.WordArray.create(arrayBuffer as any);
-
-    // 计算摘要
-    let hash: any;
-    switch (algorithm) {
-        case 'md5':
-            hash = CryptoJS.MD5(wordArray);
-            break;
-        case 'sha1':
-            hash = CryptoJS.SHA1(wordArray);
-            break;
-        case 'sha256':
-            hash = CryptoJS.SHA256(wordArray);
-            break;
-        default:
-            throw new Error(`不支持的算法: ${algorithm}`);
-    }
-
-    return hash.toString();
+export const calculateDigest = async (file: Blob | ArrayBuffer, algorithm: 'md5' | 'sha1' | 'sha256'): Promise<string> => {
+  let arrayBuffer: ArrayBuffer;
+
+  if (file instanceof Blob) {
+    arrayBuffer = await file.arrayBuffer();
+  } else {
+    arrayBuffer = file;
+  }
+
+  // 转换为 WordArray
+  const wordArray = CryptoJS.lib.WordArray.create(arrayBuffer as any);
+
+  // 计算摘要
+  let hash: any;
+  switch (algorithm) {
+    case 'md5':
+      hash = CryptoJS.MD5(wordArray);
+      break;
+    case 'sha1':
+      hash = CryptoJS.SHA1(wordArray);
+      break;
+    case 'sha256':
+      hash = CryptoJS.SHA256(wordArray);
+      break;
+    default:
+      throw new Error(`不支持的算法: ${algorithm}`);
+  }
+
+  return hash.toString();
 };
 
 /**
@@ -220,227 +217,221 @@ export const calculateDigest = async (
  * @param fileBlob 文件内容
  * @param isManual 是否手动保存
  */
-export const saveFileThreePhase = async (
-    fileId: string,
-    fileName: string,
-    fileBlob: Blob,
-    isManual: boolean = false
-): Promise<FileInfo> => {
+export const saveFileThreePhase = async (fileId: string, fileName: string, fileBlob: Blob, isManual: boolean = false): Promise<FileInfo> => {
+  try {
+    // 第一阶段:准备上传,协商摘要算法
+    console.log('[WPS保存] 第一阶段:准备上传');
+    const prepareResult = await prepareUpload({ file_id: fileId });
+    const digestTypes = prepareResult.digest_types || ['sha1'];
+    console.log('[WPS保存] 支持的摘要算法:', digestTypes);
+
+    // 计算文件摘要
+    console.log('[WPS保存] 计算文件摘要...');
+    const digest: Record<string, string> = {};
+    for (const type of digestTypes) {
+      if (['md5', 'sha1', 'sha256'].includes(type)) {
+        digest[type] = await calculateDigest(fileBlob, type as any);
+      }
+    }
+    console.log('[WPS保存] 文件摘要:', digest);
+
+    // 第二阶段:获取上传地址
+    console.log('[WPS保存] 第二阶段:获取上传地址');
+    const uploadAddressParams: GetUploadAddressParams = {
+      file_id: fileId,
+      name: fileName,
+      size: fileBlob.size,
+      digest: digest,
+      is_manual: isManual,
+      content_type: fileBlob.type
+    };
+
+    const addressResult = await getUploadAddress(uploadAddressParams);
+    console.log('[WPS保存] 上传地址:', addressResult.url);
+
+    // 上传文件到指定地址
+    console.log('[WPS保存] 上传文件...');
+
+    let uploadResponse: Response;
+    if (USE_MOCK && addressResult.url === 'mock://upload') {
+      // 前端模拟 - 不实际上传,直接模拟响应
+      console.log('[前端模拟] 跳过实际上传,使用模拟响应');
+      uploadResponse = new Response(null, {
+        status: 200,
+        statusText: 'OK',
+        headers: new Headers({
+          'content-type': 'application/json',
+          'x-mock': 'true'
+        })
+      });
+
+      // 可选:将文件保存到 IndexedDB(用于更持久的存储)
+      try {
+        await saveFileToIndexedDB(fileId, fileBlob);
+        console.log('[前端模拟] 文件已保存到 IndexedDB');
+      } catch (err) {
+        console.warn('[前端模拟] 保存到 IndexedDB 失败:', err);
+      }
+    } else {
+      // 真实上传
+      uploadResponse = await fetch(addressResult.url, {
+        method: addressResult.method || 'PUT',
+        headers: addressResult.headers || {},
+        body: fileBlob
+      });
+    }
+
+    console.log('[WPS保存] 上传响应状态:', uploadResponse.status);
+
+    // 获取响应头
+    const responseHeaders: Record<string, string> = {};
+    uploadResponse.headers.forEach((value, key) => {
+      responseHeaders[key] = value;
+    });
+
+    // 获取响应体(如果有)
+    let responseBody: string | undefined;
     try {
-        // 第一阶段:准备上传,协商摘要算法
-        console.log('[WPS保存] 第一阶段:准备上传');
-        const prepareResult = await prepareUpload({ file_id: fileId });
-        const digestTypes = prepareResult.digest_types || ['sha1'];
-        console.log('[WPS保存] 支持的摘要算法:', digestTypes);
-
-        // 计算文件摘要
-        console.log('[WPS保存] 计算文件摘要...');
-        const digest: Record<string, string> = {};
-        for (const type of digestTypes) {
-            if (['md5', 'sha1', 'sha256'].includes(type)) {
-                digest[type] = await calculateDigest(fileBlob, type as any);
-            }
-        }
-        console.log('[WPS保存] 文件摘要:', digest);
-
-        // 第二阶段:获取上传地址
-        console.log('[WPS保存] 第二阶段:获取上传地址');
-        const uploadAddressParams: GetUploadAddressParams = {
-            file_id: fileId,
-            name: fileName,
-            size: fileBlob.size,
-            digest: digest,
-            is_manual: isManual,
-            content_type: fileBlob.type
-        };
-
-        const addressResult = await getUploadAddress(uploadAddressParams);
-        console.log('[WPS保存] 上传地址:', addressResult.url);
-
-        // 上传文件到指定地址
-        console.log('[WPS保存] 上传文件...');
-
-        let uploadResponse: Response;
-        if (USE_MOCK && addressResult.url === 'mock://upload') {
-            // 前端模拟 - 不实际上传,直接模拟响应
-            console.log('[前端模拟] 跳过实际上传,使用模拟响应');
-            uploadResponse = new Response(null, {
-                status: 200,
-                statusText: 'OK',
-                headers: new Headers({
-                    'content-type': 'application/json',
-                    'x-mock': 'true'
-                })
-            });
-
-            // 可选:将文件保存到 IndexedDB(用于更持久的存储)
-            try {
-                await saveFileToIndexedDB(fileId, fileBlob);
-                console.log('[前端模拟] 文件已保存到 IndexedDB');
-            } catch (err) {
-                console.warn('[前端模拟] 保存到 IndexedDB 失败:', err);
-            }
-        } else {
-            // 真实上传
-            uploadResponse = await fetch(addressResult.url, {
-                method: addressResult.method || 'PUT',
-                headers: addressResult.headers || {},
-                body: fileBlob
-            });
-        }
-
-        console.log('[WPS保存] 上传响应状态:', uploadResponse.status);
-
-        // 获取响应头
-        const responseHeaders: Record<string, string> = {};
-        uploadResponse.headers.forEach((value, key) => {
-            responseHeaders[key] = value;
-        });
-
-        // 获取响应体(如果有)
-        let responseBody: string | undefined;
-        try {
-            const bodyText = await uploadResponse.text();
-            if (bodyText) {
-                responseBody = btoa(bodyText); // base64 编码
-            }
-        } catch (err) {
-            console.warn('[WPS保存] 无法读取响应体:', err);
-        }
-
-        // 第三阶段:上传完成通知
-        console.log('[WPS保存] 第三阶段:上传完成通知');
-        const completeParams: UploadCompleteParams = {
-            file_id: fileId,
-            request: uploadAddressParams,
-            response: {
-                status_code: uploadResponse.status,
-                headers: responseHeaders,
-                body: responseBody
-            },
-            send_back_params: addressResult.send_back_params
-        };
-
-        const fileInfo = await uploadComplete(completeParams);
-        console.log('[WPS保存] 保存完成,文件信息:', fileInfo);
-
-        return fileInfo;
-    } catch (error) {
-        console.error('[WPS保存] 保存失败:', error);
-        throw error;
+      const bodyText = await uploadResponse.text();
+      if (bodyText) {
+        responseBody = btoa(bodyText); // base64 编码
+      }
+    } catch (err) {
+      console.warn('[WPS保存] 无法读取响应体:', err);
     }
-};
 
+    // 第三阶段:上传完成通知
+    console.log('[WPS保存] 第三阶段:上传完成通知');
+    const completeParams: UploadCompleteParams = {
+      file_id: fileId,
+      request: uploadAddressParams,
+      response: {
+        status_code: uploadResponse.status,
+        headers: responseHeaders,
+        body: responseBody
+      },
+      send_back_params: addressResult.send_back_params
+    };
+
+    const fileInfo = await uploadComplete(completeParams);
+    console.log('[WPS保存] 保存完成,文件信息:', fileInfo);
+
+    return fileInfo;
+  } catch (error) {
+    console.error('[WPS保存] 保存失败:', error);
+    throw error;
+  }
+};
 
 /**
  * 前端模拟:将文件保存到 IndexedDB
  * 用于更持久的本地存储
  */
 const saveFileToIndexedDB = (fileId: string, fileBlob: Blob): Promise<void> => {
-    return new Promise((resolve, reject) => {
-        const dbName = 'WPS_Files';
-        const storeName = 'files';
-        const request = indexedDB.open(dbName, 1);
-
-        request.onerror = () => {
-            reject(new Error('无法打开 IndexedDB'));
-        };
-
-        request.onsuccess = (event) => {
-            const db = (event.target as IDBOpenDBRequest).result;
-            const transaction = db.transaction([storeName], 'readwrite');
-            const store = transaction.objectStore(storeName);
-
-            const fileRecord = {
-                id: fileId,
-                blob: fileBlob,
-                timestamp: Date.now()
-            };
-
-            const putRequest = store.put(fileRecord);
-
-            putRequest.onsuccess = () => {
-                resolve();
-            };
-
-            putRequest.onerror = () => {
-                reject(new Error('保存文件到 IndexedDB 失败'));
-            };
-        };
-
-        request.onupgradeneeded = (event) => {
-            const db = (event.target as IDBOpenDBRequest).result;
-            if (!db.objectStoreNames.contains(storeName)) {
-                db.createObjectStore(storeName, { keyPath: 'id' });
-            }
-        };
-    });
+  return new Promise((resolve, reject) => {
+    const dbName = 'WPS_Files';
+    const storeName = 'files';
+    const request = indexedDB.open(dbName, 1);
+
+    request.onerror = () => {
+      reject(new Error('无法打开 IndexedDB'));
+    };
+
+    request.onsuccess = (event) => {
+      const db = (event.target as IDBOpenDBRequest).result;
+      const transaction = db.transaction([storeName], 'readwrite');
+      const store = transaction.objectStore(storeName);
+
+      const fileRecord = {
+        id: fileId,
+        blob: fileBlob,
+        timestamp: Date.now()
+      };
+
+      const putRequest = store.put(fileRecord);
+
+      putRequest.onsuccess = () => {
+        resolve();
+      };
+
+      putRequest.onerror = () => {
+        reject(new Error('保存文件到 IndexedDB 失败'));
+      };
+    };
+
+    request.onupgradeneeded = (event) => {
+      const db = (event.target as IDBOpenDBRequest).result;
+      if (!db.objectStoreNames.contains(storeName)) {
+        db.createObjectStore(storeName, { keyPath: 'id' });
+      }
+    };
+  });
 };
 
 /**
  * 前端模拟:从 IndexedDB 读取文件
  */
 export const getFileFromIndexedDB = (fileId: string): Promise<Blob | null> => {
-    return new Promise((resolve, reject) => {
-        const dbName = 'WPS_Files';
-        const storeName = 'files';
-        const request = indexedDB.open(dbName, 1);
-
-        request.onerror = () => {
-            reject(new Error('无法打开 IndexedDB'));
-        };
-
-        request.onsuccess = (event) => {
-            const db = (event.target as IDBOpenDBRequest).result;
-            const transaction = db.transaction([storeName], 'readonly');
-            const store = transaction.objectStore(storeName);
-            const getRequest = store.get(fileId);
-
-            getRequest.onsuccess = () => {
-                const result = getRequest.result;
-                resolve(result ? result.blob : null);
-            };
-
-            getRequest.onerror = () => {
-                reject(new Error('读取文件失败'));
-            };
-        };
-
-        request.onupgradeneeded = (event) => {
-            const db = (event.target as IDBOpenDBRequest).result;
-            if (!db.objectStoreNames.contains(storeName)) {
-                db.createObjectStore(storeName, { keyPath: 'id' });
-            }
-        };
-    });
+  return new Promise((resolve, reject) => {
+    const dbName = 'WPS_Files';
+    const storeName = 'files';
+    const request = indexedDB.open(dbName, 1);
+
+    request.onerror = () => {
+      reject(new Error('无法打开 IndexedDB'));
+    };
+
+    request.onsuccess = (event) => {
+      const db = (event.target as IDBOpenDBRequest).result;
+      const transaction = db.transaction([storeName], 'readonly');
+      const store = transaction.objectStore(storeName);
+      const getRequest = store.get(fileId);
+
+      getRequest.onsuccess = () => {
+        const result = getRequest.result;
+        resolve(result ? result.blob : null);
+      };
+
+      getRequest.onerror = () => {
+        reject(new Error('读取文件失败'));
+      };
+    };
+
+    request.onupgradeneeded = (event) => {
+      const db = (event.target as IDBOpenDBRequest).result;
+      if (!db.objectStoreNames.contains(storeName)) {
+        db.createObjectStore(storeName, { keyPath: 'id' });
+      }
+    };
+  });
 };
 
 /**
  * 前端模拟:清除所有保存的文件
  */
 export const clearAllFiles = (): Promise<void> => {
-    return new Promise((resolve, reject) => {
-        // 清除 localStorage
-        const keys = Object.keys(localStorage);
-        keys.forEach(key => {
-            if (key.startsWith('wps_file_')) {
-                localStorage.removeItem(key);
-            }
-        });
-
-        // 清除 IndexedDB
-        const dbName = 'WPS_Files';
-        const request = indexedDB.deleteDatabase(dbName);
-
-        request.onsuccess = () => {
-            console.log('[前端模拟] 所有文件已清除');
-            resolve();
-        };
-
-        request.onerror = () => {
-            reject(new Error('清除 IndexedDB 失败'));
-        };
+  return new Promise((resolve, reject) => {
+    // 清除 localStorage
+    const keys = Object.keys(localStorage);
+    keys.forEach((key) => {
+      if (key.startsWith('wps_file_')) {
+        localStorage.removeItem(key);
+      }
     });
+
+    // 清除 IndexedDB
+    const dbName = 'WPS_Files';
+    const request = indexedDB.deleteDatabase(dbName);
+
+    request.onsuccess = () => {
+      console.log('[前端模拟] 所有文件已清除');
+      resolve();
+    };
+
+    request.onerror = () => {
+      reject(new Error('清除 IndexedDB 失败'));
+    };
+  });
 };
 
 /**
@@ -448,10 +439,10 @@ export const clearAllFiles = (): Promise<void> => {
  * @param documentId 文档ID
  */
 export const cleanDocumentComments = (documentId: string | number): Promise<any> => {
-    return request({
-        url: `/wps/callback/v3/3rd/clean/${documentId}`,
-        method: 'put'
-    });
+  return request({
+    url: `/wps/callback/v3/3rd/clean/${documentId}`,
+    method: 'put'
+  });
 };
 
 /**
@@ -459,18 +450,18 @@ export const cleanDocumentComments = (documentId: string | number): Promise<any>
  * @param ossId OSS文件ID
  */
 export interface FileVersion {
-    version: number;
-    url: string;
-    createTime: number;
-    updateTime: number;
+  version: number;
+  url: string;
+  createTime: number;
+  updateTime: number;
 }
 
 export const getFileVersionList = (ossId: string | number): Promise<{ data: FileVersion[] }> => {
-    return request({
-        url: '/wps/callback/v3/3rd/files/list',
-        method: 'get',
-        params: { ossId }
-    });
+  return request({
+    url: '/wps/callback/v3/3rd/files/list',
+    method: 'get',
+    params: { ossId }
+  });
 };
 
 /**
@@ -479,10 +470,10 @@ export const getFileVersionList = (ossId: string | number): Promise<{ data: File
  * @returns 返回当前版本号
  */
 export const initWpsDocument = (ossId: string | number): Promise<{ data: number }> => {
-    return request({
-        url: `/wps/callback/v3/3rd/init/${ossId}`,
-        method: 'post'
-    });
+  return request({
+    url: `/wps/callback/v3/3rd/init/${ossId}`,
+    method: 'post'
+  });
 };
 
 /**
@@ -490,10 +481,10 @@ export const initWpsDocument = (ossId: string | number): Promise<{ data: number
  * @param ossId OSS文件ID
  */
 export const cancelWpsDocument = (ossId: string | number): Promise<any> => {
-    return request({
-        url: `/wps/callback/v3/3rd/cancel/${ossId}`,
-        method: 'delete'
-    });
+  return request({
+    url: `/wps/callback/v3/3rd/cancel/${ossId}`,
+    method: 'delete'
+  });
 };
 
 /**
@@ -501,23 +492,23 @@ export const cancelWpsDocument = (ossId: string | number): Promise<any> => {
  * @param fileId 文件ID(格式:ossId_version)
  */
 export interface FinalFileInfo {
-    ossId: number;
-    fileName: string;
-    originalName: string;
-    fileSuffix: string;
-    url: string;
-    ext1: string;
-    createTime: string;
-    createBy: number;
-    createByName: string;
-    service: string;
-    updateTime: string;
+  ossId: number;
+  fileName: string;
+  originalName: string;
+  fileSuffix: string;
+  url: string;
+  ext1: string;
+  createTime: string;
+  createBy: number;
+  createByName: string;
+  service: string;
+  updateTime: string;
 }
 
 export const getFinalFile = (fileId: string): Promise<{ data: FinalFileInfo }> => {
-    return request({
-        url: '/wps/callback/v3/3rd/getFinal',
-        method: 'get',
-        params: { id: fileId }
-    });
+  return request({
+    url: '/wps/callback/v3/3rd/getFinal',
+    method: 'get',
+    params: { id: fileId }
+  });
 };

+ 549 - 593
src/components/AnnotationEditor/index.vue

@@ -1,259 +1,211 @@
 <template>
-    <div class="annotation-editor">
-        <!-- 文档预览区域 -->
-        <div v-if="ossId && canPreview" class="editor-wrapper">
-            <!-- 顶部工具栏 -->
-            <div class="editor-header">
-                <!-- 文件信息 -->
-                <div class="file-info">
-                    <el-icon class="file-icon"><Document /></el-icon>
-                    <span class="file-name">{{ fileName || '未命名文档' }}</span>
-                    <el-tag v-if="fileType" size="small" effect="plain">{{ fileType.toUpperCase() }}</el-tag>
+  <div class="annotation-editor">
+    <!-- 文档预览区域 -->
+    <div v-if="ossId && canPreview" class="editor-wrapper">
+      <!-- 顶部工具栏 -->
+      <div class="editor-header">
+        <!-- 文件信息 -->
+        <div class="file-info">
+          <el-icon class="file-icon"><Document /></el-icon>
+          <span class="file-name">{{ fileName || '未命名文档' }}</span>
+          <el-tag v-if="fileType" size="small" effect="plain">{{ fileType.toUpperCase() }}</el-tag>
+        </div>
+
+        <!-- 操作按钮 -->
+        <div class="editor-actions">
+          <!-- 编辑模式切换(所有文档类型) -->
+          <el-switch v-model="useWpsEditor" active-text="在线编辑" inactive-text="预览模式" inline-prompt size="large" />
+
+          <!-- WPS 设置按钮 -->
+          <el-button v-if="useWpsEditor" @click="showSettings" circle>
+            <el-icon><Setting /></el-icon>
+          </el-button>
+
+          <!-- 保存按钮 -->
+          <el-button v-if="useWpsEditor && wpsEditorRef" type="primary" @click="handleSaveEdit" :loading="saving">
+            <el-icon><Download /></el-icon>
+            保存
+          </el-button>
+        </div>
+      </div>
+
+      <!-- 文档内容区域 -->
+      <div class="editor-content">
+        <!-- WPS 编辑器(支持所有格式:Word/Excel/PPT/PDF) -->
+        <div v-if="useWpsEditor && fileUrl" class="wps-wrapper">
+          <WpsEditor
+            :file-url="fileUrl"
+            :file-name="fileName"
+            :file-type="fileType"
+            :mode="wpsMode"
+            :user-id="userStore.userId?.toString()"
+            :user-name="userStore.userName"
+            :enable-comment="wpsOptions.enableComment"
+            :enable-revision="wpsOptions.enableRevision"
+            :enable-watermark="wpsOptions.enableWatermark"
+            :watermark-text="wpsOptions.watermarkText"
+            :enable-download="wpsOptions.enableDownload"
+            :enable-print="wpsOptions.enablePrint"
+            :enable-copy="wpsOptions.enableCopy"
+            :enable-save="wpsOptions.enableSave"
+            :enable-share="wpsOptions.enableShare"
+            :enable-history="wpsOptions.enableHistory"
+            :read-only="wpsOptions.readOnly"
+            @ready="handleWpsReady"
+            @error="handleWpsError"
+            @save="handleWpsSave"
+            @file-change="handleWpsFileChange"
+            ref="wpsEditorRef"
+          />
+        </div>
+
+        <!-- Word 预览 -->
+        <div v-else-if="fileType === 'docx' && fileBlob && !useWpsEditor" class="preview-wrapper">
+          <VueOfficeDocx :src="fileBlob" @rendered="handleRendered" @error="handlePreviewError" />
+        </div>
+
+        <!-- Excel 预览 -->
+        <div v-else-if="fileType === 'xlsx' && fileBlob && !useWpsEditor" class="preview-wrapper">
+          <VueOfficeExcel :src="fileBlob" @rendered="handleRendered" @error="handlePreviewError" />
+        </div>
+
+        <!-- PDF 预览 -->
+        <div v-else-if="fileType === 'pdf' && fileBlob && !useWpsEditor" class="pdf-wrapper">
+          <div class="pdf-viewer">
+            <VueOfficePdf :src="fileBlob" @rendered="handleRendered" @error="handlePreviewError" />
+          </div>
+        </div>
+
+        <!-- 加载状态 -->
+        <div v-if="previewLoading" class="loading-state">
+          <el-icon class="is-loading"><Loading /></el-icon>
+          <p>文档加载中...</p>
+        </div>
+
+        <!-- 错误状态 -->
+        <el-result v-if="previewError" icon="error" title="加载失败" :sub-title="previewError">
+          <template #extra>
+            <el-button type="primary" @click="downloadFileForPreview">重新加载</el-button>
+          </template>
+        </el-result>
+      </div>
+    </div>
+
+    <!-- 空状态 -->
+    <el-empty v-else description="暂无文档" :image-size="120" />
+
+    <!-- WPS 设置抽屉 -->
+    <el-drawer v-model="settingsVisible" title="编辑器设置" direction="rtl" size="400px">
+      <el-tabs v-model="activeTab" class="settings-tabs">
+        <!-- 编辑模式 -->
+        <el-tab-pane label="编辑模式" name="mode">
+          <div class="settings-section">
+            <el-radio-group v-model="wpsMode" class="mode-radio-group">
+              <el-radio value="normal" border>
+                <div class="radio-label">
+                  <div class="label-title">完整模式</div>
+                  <div class="label-desc">显示所有编辑工具</div>
                 </div>
-                
-                <!-- 操作按钮 -->
-                <div class="editor-actions">
-                    <!-- 编辑模式切换(所有文档类型) -->
-                    <el-switch
-                        v-model="useWpsEditor"
-                        active-text="在线编辑"
-                        inactive-text="预览模式"
-                        inline-prompt
-                        size="large"
-                    />
-                    
-                    <!-- WPS 设置按钮 -->
-                    <el-button 
-                        v-if="useWpsEditor"
-                        @click="showSettings"
-                        circle
-                    >
-                        <el-icon><Setting /></el-icon>
-                    </el-button>
-                    
-                    <!-- 保存按钮 -->
-                    <el-button 
-                        v-if="useWpsEditor && wpsEditorRef" 
-                        type="primary"
-                        @click="handleSaveEdit"
-                        :loading="saving"
-                    >
-                        <el-icon><Download /></el-icon>
-                        保存
-                    </el-button>
+              </el-radio>
+              <el-radio value="simple" border>
+                <div class="radio-label">
+                  <div class="label-title">简洁模式</div>
+                  <div class="label-desc">只显示基本工具</div>
                 </div>
+              </el-radio>
+            </el-radio-group>
+          </div>
+        </el-tab-pane>
+
+        <!-- 功能开关 -->
+        <el-tab-pane label="功能开关" name="features">
+          <div class="settings-section">
+            <div class="setting-item">
+              <div class="item-label">
+                <span>批注功能</span>
+                <span class="item-desc">允许添加和查看批注</span>
+              </div>
+              <el-switch v-model="wpsOptions.enableComment" />
+            </div>
+            <el-divider />
+
+            <div class="setting-item">
+              <div class="item-label">
+                <span>修订功能</span>
+                <span class="item-desc">跟踪文档修改记录</span>
+              </div>
+              <el-switch v-model="wpsOptions.enableRevision" />
+            </div>
+            <el-divider />
+
+            <div class="setting-item">
+              <div class="item-label">
+                <span>水印功能</span>
+                <span class="item-desc">在文档中显示水印</span>
+              </div>
+              <el-switch v-model="wpsOptions.enableWatermark" />
             </div>
-            
-            <!-- 文档内容区域 -->
-            <div class="editor-content">
-                <!-- WPS 编辑器(支持所有格式:Word/Excel/PPT/PDF) -->
-                <div v-if="useWpsEditor && fileUrl" class="wps-wrapper">
-                    <WpsEditor
-                        :file-url="fileUrl"
-                        :file-name="fileName"
-                        :file-type="fileType"
-                        :mode="wpsMode"
-                        :user-id="userStore.userId?.toString()"
-                        :user-name="userStore.userName"
-                        :enable-comment="wpsOptions.enableComment"
-                        :enable-revision="wpsOptions.enableRevision"
-                        :enable-watermark="wpsOptions.enableWatermark"
-                        :watermark-text="wpsOptions.watermarkText"
-                        :enable-download="wpsOptions.enableDownload"
-                        :enable-print="wpsOptions.enablePrint"
-                        :enable-copy="wpsOptions.enableCopy"
-                        :enable-save="wpsOptions.enableSave"
-                        :enable-share="wpsOptions.enableShare"
-                        :enable-history="wpsOptions.enableHistory"
-                        :read-only="wpsOptions.readOnly"
-                        @ready="handleWpsReady"
-                        @error="handleWpsError"
-                        @save="handleWpsSave"
-                        @file-change="handleWpsFileChange"
-                        ref="wpsEditorRef"
-                    />
-                </div>
 
-                <!-- Word 预览 -->
-                <div v-else-if="fileType === 'docx' && fileBlob && !useWpsEditor" class="preview-wrapper">
-                    <VueOfficeDocx 
-                        :src="fileBlob" 
-                        @rendered="handleRendered"
-                        @error="handlePreviewError"
-                    />
-                </div>
-                
-                <!-- Excel 预览 -->
-                <div v-else-if="fileType === 'xlsx' && fileBlob && !useWpsEditor" class="preview-wrapper">
-                    <VueOfficeExcel 
-                        :src="fileBlob"
-                        @rendered="handleRendered"
-                        @error="handlePreviewError"
-                    />
-                </div>
-                
-                <!-- PDF 预览 -->
-                <div v-else-if="fileType === 'pdf' && fileBlob && !useWpsEditor" class="pdf-wrapper">
-                    <div class="pdf-viewer">
-                        <VueOfficePdf 
-                            :src="fileBlob"
-                            @rendered="handleRendered"
-                            @error="handlePreviewError"
-                        />
-                    </div>
-                </div>
-                
-                <!-- 加载状态 -->
-                <div v-if="previewLoading" class="loading-state">
-                    <el-icon class="is-loading"><Loading /></el-icon>
-                    <p>文档加载中...</p>
-                </div>
-                
-                <!-- 错误状态 -->
-                <el-result 
-                    v-if="previewError"
-                    icon="error"
-                    title="加载失败"
-                    :sub-title="previewError"
-                >
-                    <template #extra>
-                        <el-button type="primary" @click="downloadFileForPreview">重新加载</el-button>
-                    </template>
-                </el-result>
+            <div v-if="wpsOptions.enableWatermark" class="watermark-input">
+              <el-input v-model="wpsOptions.watermarkText" placeholder="输入水印文字" clearable />
+            </div>
+          </div>
+        </el-tab-pane>
+
+        <!-- 权限控制 -->
+        <el-tab-pane label="权限控制" name="permissions">
+          <div class="settings-section">
+            <div class="setting-item">
+              <div class="item-label">
+                <span>下载权限</span>
+                <span class="item-desc">允许下载文档</span>
+              </div>
+              <el-switch v-model="wpsOptions.enableDownload" />
+            </div>
+            <el-divider />
+
+            <div class="setting-item">
+              <div class="item-label">
+                <span>打印权限</span>
+                <span class="item-desc">允许打印文档</span>
+              </div>
+              <el-switch v-model="wpsOptions.enablePrint" />
             </div>
+            <el-divider />
+
+            <div class="setting-item">
+              <div class="item-label">
+                <span>复制权限</span>
+                <span class="item-desc">允许复制内容</span>
+              </div>
+              <el-switch v-model="wpsOptions.enableCopy" />
+            </div>
+            <el-divider />
+
+            <div class="setting-item">
+              <div class="item-label">
+                <span>只读模式</span>
+                <span class="item-desc">文档只能查看</span>
+              </div>
+              <el-switch v-model="wpsOptions.readOnly" />
+            </div>
+          </div>
+        </el-tab-pane>
+      </el-tabs>
+
+      <template #footer>
+        <div class="drawer-footer">
+          <el-button @click="settingsVisible = false">取消</el-button>
+          <el-button type="primary" @click="applySettings">应用设置</el-button>
         </div>
-        
-        <!-- 空状态 -->
-        <el-empty 
-            v-else
-            description="暂无文档"
-            :image-size="120"
-        />
-        
-        <!-- WPS 设置抽屉 -->
-        <el-drawer
-            v-model="settingsVisible"
-            title="编辑器设置"
-            direction="rtl"
-            size="400px"
-        >
-            <el-tabs v-model="activeTab" class="settings-tabs">
-                <!-- 编辑模式 -->
-                <el-tab-pane label="编辑模式" name="mode">
-                    <div class="settings-section">
-                        <el-radio-group v-model="wpsMode" class="mode-radio-group">
-                            <el-radio value="normal" border>
-                                <div class="radio-label">
-                                    <div class="label-title">完整模式</div>
-                                    <div class="label-desc">显示所有编辑工具</div>
-                                </div>
-                            </el-radio>
-                            <el-radio value="simple" border>
-                                <div class="radio-label">
-                                    <div class="label-title">简洁模式</div>
-                                    <div class="label-desc">只显示基本工具</div>
-                                </div>
-                            </el-radio>
-                        </el-radio-group>
-                    </div>
-                </el-tab-pane>
-                
-                <!-- 功能开关 -->
-                <el-tab-pane label="功能开关" name="features">
-                    <div class="settings-section">
-                        <div class="setting-item">
-                            <div class="item-label">
-                                <span>批注功能</span>
-                                <span class="item-desc">允许添加和查看批注</span>
-                            </div>
-                            <el-switch v-model="wpsOptions.enableComment" />
-                        </div>
-                        <el-divider />
-                        
-                        <div class="setting-item">
-                            <div class="item-label">
-                                <span>修订功能</span>
-                                <span class="item-desc">跟踪文档修改记录</span>
-                            </div>
-                            <el-switch v-model="wpsOptions.enableRevision" />
-                        </div>
-                        <el-divider />
-                        
-                        <div class="setting-item">
-                            <div class="item-label">
-                                <span>水印功能</span>
-                                <span class="item-desc">在文档中显示水印</span>
-                            </div>
-                            <el-switch v-model="wpsOptions.enableWatermark" />
-                        </div>
-                        
-                        <div v-if="wpsOptions.enableWatermark" class="watermark-input">
-                            <el-input 
-                                v-model="wpsOptions.watermarkText" 
-                                placeholder="输入水印文字"
-                                clearable
-                            />
-                        </div>
-                    </div>
-                </el-tab-pane>
-                
-                <!-- 权限控制 -->
-                <el-tab-pane label="权限控制" name="permissions">
-                    <div class="settings-section">
-                        <div class="setting-item">
-                            <div class="item-label">
-                                <span>下载权限</span>
-                                <span class="item-desc">允许下载文档</span>
-                            </div>
-                            <el-switch v-model="wpsOptions.enableDownload" />
-                        </div>
-                        <el-divider />
-                        
-                        <div class="setting-item">
-                            <div class="item-label">
-                                <span>打印权限</span>
-                                <span class="item-desc">允许打印文档</span>
-                            </div>
-                            <el-switch v-model="wpsOptions.enablePrint" />
-                        </div>
-                        <el-divider />
-                        
-                        <div class="setting-item">
-                            <div class="item-label">
-                                <span>复制权限</span>
-                                <span class="item-desc">允许复制内容</span>
-                            </div>
-                            <el-switch v-model="wpsOptions.enableCopy" />
-                        </div>
-                        <el-divider />
-                        
-                        <div class="setting-item">
-                            <div class="item-label">
-                                <span>只读模式</span>
-                                <span class="item-desc">文档只能查看</span>
-                            </div>
-                            <el-switch v-model="wpsOptions.readOnly" />
-                        </div>
-                    </div>
-                </el-tab-pane>
-            </el-tabs>
-            
-            <template #footer>
-                <div class="drawer-footer">
-                    <el-button @click="settingsVisible = false">取消</el-button>
-                    <el-button type="primary" @click="applySettings">应用设置</el-button>
-                </div>
-            </template>
-        </el-drawer>
-    </div>
+      </template>
+    </el-drawer>
+  </div>
 </template>
 
 <script setup lang="ts">
 import { ref, computed, watch, nextTick } from 'vue';
-import { 
-    Document, Loading, Close, Edit, ChatLineSquare, 
-    Setting, Download, Delete 
-} from '@element-plus/icons-vue';
+import { Document, Loading, Close, Edit, ChatLineSquare, Setting, Download, Delete } from '@element-plus/icons-vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import VueOfficeDocx from '@vue-office/docx';
 import VueOfficeExcel from '@vue-office/excel';
@@ -265,13 +217,13 @@ import WpsEditor from '@/components/WpsEditor/index.vue';
 import { useUserStore } from '@/store/modules/user';
 
 interface Props {
-    ossId?: number | string;
-    fileName?: string;
+  ossId?: number | string;
+  fileName?: string;
 }
 
 interface Emits {
-    (e: 'ready'): void;
-    (e: 'save', data: any): void;
+  (e: 'ready'): void;
+  (e: 'save', data: any): void;
 }
 
 const props = defineProps<Props>();
@@ -292,497 +244,501 @@ const activeTab = ref('mode');
 
 // WPS 高级选项
 const wpsOptions = ref({
-    enableComment: true,
-    enableRevision: true,
-    enableWatermark: false,
-    watermarkText: '',
-    enableDownload: true,
-    enablePrint: true,
-    enableCopy: true,
-    enableSave: true,
-    enableShare: false,
-    enableHistory: false,
-    readOnly: false
+  enableComment: true,
+  enableRevision: true,
+  enableWatermark: false,
+  watermarkText: '',
+  enableDownload: true,
+  enablePrint: true,
+  enableCopy: true,
+  enableSave: true,
+  enableShare: false,
+  enableHistory: false,
+  readOnly: false
 });
 
 // 获取文件类型
 const fileType = computed(() => {
-    if (!props.fileName) return '';
-    const fileName = props.fileName.toLowerCase();
-    if (fileName.endsWith('.docx') || fileName.endsWith('.doc')) return 'docx';
-    if (fileName.endsWith('.xlsx') || fileName.endsWith('.xls')) return 'xlsx';
-    if (fileName.endsWith('.pdf')) return 'pdf';
-    return '';
+  if (!props.fileName) return '';
+  const fileName = props.fileName.toLowerCase();
+  if (fileName.endsWith('.docx') || fileName.endsWith('.doc')) return 'docx';
+  if (fileName.endsWith('.xlsx') || fileName.endsWith('.xls')) return 'xlsx';
+  if (fileName.endsWith('.pdf')) return 'pdf';
+  return '';
 });
 
 // 判断是否可以预览
 const canPreview = computed(() => {
-    return ['docx', 'xlsx', 'pdf'].includes(fileType.value);
+  return ['docx', 'xlsx', 'pdf'].includes(fileType.value);
 });
 
 // 下载文件用于预览
 const downloadFileForPreview = async () => {
-    if (!props.ossId || !canPreview.value) {
-        previewLoading.value = false;
-        return;
-    }
-
-    try {
-        previewLoading.value = true;
-        previewError.value = '';
-        fileBlob.value = null;
-        fileUrl.value = '';
-
-        const baseURL = import.meta.env.VITE_APP_BASE_API;
-        fileUrl.value = `${baseURL}/resource/oss/downloadWithoutPermission/${props.ossId}`;
-
-        if (!useWpsEditor.value) {
-            const response = await request({
-                url: `/resource/oss/downloadWithoutPermission/${props.ossId}`,
-                method: 'get',
-                responseType: 'arraybuffer'
-            });
-            fileBlob.value = response;
-        }
-    } catch (error) {
-        console.error('下载文件失败:', error);
-        previewError.value = '文件下载失败,请重试';
-        previewLoading.value = false;
+  if (!props.ossId || !canPreview.value) {
+    previewLoading.value = false;
+    return;
+  }
+
+  try {
+    previewLoading.value = true;
+    previewError.value = '';
+    fileBlob.value = null;
+    fileUrl.value = '';
+
+    const baseURL = import.meta.env.VITE_APP_BASE_API;
+    fileUrl.value = `${baseURL}/resource/oss/downloadWithoutPermission/${props.ossId}`;
+
+    if (!useWpsEditor.value) {
+      const response = await request({
+        url: `/resource/oss/downloadWithoutPermission/${props.ossId}`,
+        method: 'get',
+        responseType: 'arraybuffer'
+      });
+      fileBlob.value = response;
     }
+  } catch (error) {
+    console.error('下载文件失败:', error);
+    previewError.value = '文件下载失败,请重试';
+    previewLoading.value = false;
+  }
 };
 
 // WPS 编辑器就绪
 const handleWpsReady = () => {
-    previewLoading.value = false;
-    emit('ready');
+  previewLoading.value = false;
+  emit('ready');
 };
 
 // WPS 编辑器错误
 const handleWpsError = (error: string) => {
-    previewError.value = error;
-    previewLoading.value = false;
+  previewError.value = error;
+  previewLoading.value = false;
 };
 
 // WPS 编辑器保存
 const handleWpsSave = (data: any) => {
-    ElMessage.success('文档已保存');
-    emit('save', data);
+  ElMessage.success('文档已保存');
+  emit('save', data);
 };
 
 // WPS 文件变化
 const handleWpsFileChange = (data: any) => {
-    console.log('WPS 文件变化:', data);
+  console.log('WPS 文件变化:', data);
 };
 
 // 文档渲染完成
 const handleRendered = () => {
-    previewLoading.value = false;
+  previewLoading.value = false;
 };
 
 // 文档预览错误
 const handlePreviewError = (error: any) => {
-    previewLoading.value = false;
-    previewError.value = '文档预览失败,请尝试重新加载';
-    console.error('文档预览错误:', error);
+  previewLoading.value = false;
+  previewError.value = '文档预览失败,请尝试重新加载';
+  console.error('文档预览错误:', error);
 };
 
 // 显示设置
 const showSettings = () => {
-    settingsVisible.value = true;
+  settingsVisible.value = true;
 };
 
 // 应用设置
 const applySettings = () => {
-    settingsVisible.value = false;
-    ElMessage.success('设置已应用');
-    // 重新加载编辑器
-    if (useWpsEditor.value) {
-        downloadFileForPreview();
-    }
+  settingsVisible.value = false;
+  ElMessage.success('设置已应用');
+  // 重新加载编辑器
+  if (useWpsEditor.value) {
+    downloadFileForPreview();
+  }
 };
 
 // 保存编辑
 const handleSaveEdit = async () => {
-    if (!wpsEditorRef.value) {
-        ElMessage.warning('编辑器未初始化');
-        return;
-    }
-    
-    saving.value = true;
-    try {
-        const result = await wpsEditorRef.value.saveDocument();
-        ElMessage.success('文档已保存');
-        emit('save', result);
-    } catch (error) {
-        console.error('保存编辑失败:', error);
-        ElMessage.error('保存编辑失败');
-    } finally {
-        saving.value = false;
-    }
+  if (!wpsEditorRef.value) {
+    ElMessage.warning('编辑器未初始化');
+    return;
+  }
+
+  saving.value = true;
+  try {
+    const result = await wpsEditorRef.value.saveDocument();
+    ElMessage.success('文档已保存');
+    emit('save', result);
+  } catch (error) {
+    console.error('保存编辑失败:', error);
+    ElMessage.error('保存编辑失败');
+  } finally {
+    saving.value = false;
+  }
 };
 
 // 获取所有批注数据(WPS 自带的批注)
 const getAnnotations = () => {
-    // WPS 的批注数据由 WPS SDK 管理,不需要我们自己维护
-    return {
-        textAnnotations: [],
-        signatures: []
-    };
+  // WPS 的批注数据由 WPS SDK 管理,不需要我们自己维护
+  return {
+    textAnnotations: [],
+    signatures: []
+  };
 };
 
 // 保存文档
 const saveDocument = async () => {
-    if (useWpsEditor.value && wpsEditorRef.value) {
-        return await wpsEditorRef.value.saveDocument();
-    }
-    return null;
+  if (useWpsEditor.value && wpsEditorRef.value) {
+    return await wpsEditorRef.value.saveDocument();
+  }
+  return null;
 };
 
 // 监听 ossId 变化
-watch(() => props.ossId, (newId) => {
+watch(
+  () => props.ossId,
+  (newId) => {
     if (newId) {
-        downloadFileForPreview();
+      downloadFileForPreview();
     }
-}, { immediate: true });
+  },
+  { immediate: true }
+);
 
 // 监听 useWpsEditor 变化
 watch(useWpsEditor, () => {
-    downloadFileForPreview();
+  downloadFileForPreview();
 });
 
 // 暴露方法
 defineExpose({
-    getAnnotations,
-    saveDocument
+  getAnnotations,
+  saveDocument
 });
 </script>
 
 <style scoped lang="scss">
 .annotation-editor {
-    width: 100%;
-    height: 100%;
-    display: flex;
-    flex-direction: column;
-    background: #f5f7fa;
-    position: relative;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  background: #f5f7fa;
+  position: relative;
 }
 
 .editor-wrapper {
-    position: absolute;
-    top: 0;
-    left: 0;
-    right: 0;
-    bottom: 0;
-    display: flex;
-    flex-direction: column;
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  flex-direction: column;
 }
 
 .editor-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 16px 20px;
+  background: #fff;
+  border-bottom: 1px solid #e4e7ed;
+  flex-shrink: 0;
+  z-index: 10;
+
+  .file-info {
     display: flex;
     align-items: center;
-    justify-content: space-between;
-    padding: 16px 20px;
-    background: #fff;
-    border-bottom: 1px solid #e4e7ed;
-    flex-shrink: 0;
-    z-index: 10;
-    
-    .file-info {
-        display: flex;
-        align-items: center;
-        gap: 12px;
-        
-        .file-icon {
-            font-size: 24px;
-            color: #409eff;
-        }
-        
-        .file-name {
-            font-size: 16px;
-            font-weight: 500;
-            color: #303133;
-            max-width: 300px;
-            overflow: hidden;
-            text-overflow: ellipsis;
-            white-space: nowrap;
-        }
+    gap: 12px;
+
+    .file-icon {
+      font-size: 24px;
+      color: #409eff;
     }
-    
-    .editor-actions {
-        display: flex;
-        align-items: center;
-        gap: 12px;
+
+    .file-name {
+      font-size: 16px;
+      font-weight: 500;
+      color: #303133;
+      max-width: 300px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
     }
+  }
+
+  .editor-actions {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+  }
 }
 
 .editor-content {
-    position: absolute;
-    top: 68px;
-    left: 0;
-    right: 0;
-    bottom: 0;
-    background: #fff;
+  position: absolute;
+  top: 68px;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: #fff;
 }
 
 .wps-wrapper,
 .preview-wrapper,
 .pdf-wrapper {
-    position: absolute;
-    top: 0;
-    left: 0;
-    right: 0;
-    bottom: 0;
-    overflow: auto;
-    
-    // 自定义滚动条样式
-    &::-webkit-scrollbar {
-        width: 8px;
-        height: 8px;
-    }
-    
-    &::-webkit-scrollbar-track {
-        background: #f1f1f1;
-        border-radius: 4px;
-    }
-    
-    &::-webkit-scrollbar-thumb {
-        background: #c1c1c1;
-        border-radius: 4px;
-        
-        &:hover {
-            background: #a8a8a8;
-        }
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  overflow: auto;
+
+  // 自定义滚动条样式
+  &::-webkit-scrollbar {
+    width: 8px;
+    height: 8px;
+  }
+
+  &::-webkit-scrollbar-track {
+    background: #f1f1f1;
+    border-radius: 4px;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background: #c1c1c1;
+    border-radius: 4px;
+
+    &:hover {
+      background: #a8a8a8;
     }
+  }
 }
 
 .wps-wrapper {
-    :deep(.wps-editor-container) {
-        height: 100%;
-        
-        .wps-container {
-            height: 100%;
-        }
+  :deep(.wps-editor-container) {
+    height: 100%;
+
+    .wps-container {
+      height: 100%;
     }
+  }
 }
 
 .preview-wrapper {
-    padding: 20px;
-    
-    :deep(.docx-wrapper),
-    :deep(.excel-wrapper) {
-        height: auto !important;
-        max-height: none !important;
-    }
+  padding: 20px;
+
+  :deep(.docx-wrapper),
+  :deep(.excel-wrapper) {
+    height: auto !important;
+    max-height: none !important;
+  }
 }
 
 .pdf-wrapper {
-    position: absolute;
-    top: 0;
-    left: 0;
-    right: 0;
-    bottom: 0;
-    
-    .pdf-viewer {
-        width: 100%;
-        height: 100%;
-        overflow: auto;
-        
-        // 自定义滚动条样式
-        &::-webkit-scrollbar {
-            width: 8px;
-            height: 8px;
-        }
-        
-        &::-webkit-scrollbar-track {
-            background: #f1f1f1;
-            border-radius: 4px;
-        }
-        
-        &::-webkit-scrollbar-thumb {
-            background: #c1c1c1;
-            border-radius: 4px;
-            
-            &:hover {
-                background: #a8a8a8;
-            }
-        }
-        
-        :deep(canvas) {
-            display: block;
-            margin: 20px auto;
-            box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
-        }
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+
+  .pdf-viewer {
+    width: 100%;
+    height: 100%;
+    overflow: auto;
+
+    // 自定义滚动条样式
+    &::-webkit-scrollbar {
+      width: 8px;
+      height: 8px;
+    }
+
+    &::-webkit-scrollbar-track {
+      background: #f1f1f1;
+      border-radius: 4px;
     }
+
+    &::-webkit-scrollbar-thumb {
+      background: #c1c1c1;
+      border-radius: 4px;
+
+      &:hover {
+        background: #a8a8a8;
+      }
+    }
+
+    :deep(canvas) {
+      display: block;
+      margin: 20px auto;
+      box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+    }
+  }
 }
 
 .signature-item,
 .annotation-item {
+  position: absolute;
+  z-index: 10;
+
+  .remove-btn {
     position: absolute;
-    z-index: 10;
-    
-    .remove-btn {
-        position: absolute;
-        top: -10px;
-        right: -10px;
-        opacity: 0;
-        transition: opacity 0.3s;
-    }
-    
-    &:hover .remove-btn {
-        opacity: 1;
-    }
+    top: -10px;
+    right: -10px;
+    opacity: 0;
+    transition: opacity 0.3s;
+  }
+
+  &:hover .remove-btn {
+    opacity: 1;
+  }
 }
 
 .signature-item {
-    width: 100px;
-    height: 50px;
-    
-    img {
-        width: 100%;
-        height: 100%;
-        border: 2px solid #409eff;
-        border-radius: 4px;
-        background: rgba(64, 158, 255, 0.05);
-        transition: transform 0.3s;
-    }
-    
-    &:hover img {
-        transform: scale(1.05);
-    }
+  width: 100px;
+  height: 50px;
+
+  img {
+    width: 100%;
+    height: 100%;
+    border: 2px solid #409eff;
+    border-radius: 4px;
+    background: rgba(64, 158, 255, 0.05);
+    transition: transform 0.3s;
+  }
+
+  &:hover img {
+    transform: scale(1.05);
+  }
 }
 
 .annotation-item {
-    min-width: 200px;
-    max-width: 300px;
-    background: #fff;
-    border: 2px solid #ffc107;
-    border-radius: 8px;
-    padding: 12px;
-    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
-    
-    .annotation-display {
-        cursor: pointer;
-        
-        .annotation-text {
-            color: #303133;
-            font-size: 14px;
-            line-height: 1.6;
-            word-break: break-all;
-            min-height: 40px;
-        }
-    }
-    
-    .annotation-actions {
-        display: flex;
-        gap: 8px;
-        margin-top: 8px;
-        justify-content: flex-end;
+  min-width: 200px;
+  max-width: 300px;
+  background: #fff;
+  border: 2px solid #ffc107;
+  border-radius: 8px;
+  padding: 12px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+
+  .annotation-display {
+    cursor: pointer;
+
+    .annotation-text {
+      color: #303133;
+      font-size: 14px;
+      line-height: 1.6;
+      word-break: break-all;
+      min-height: 40px;
     }
+  }
+
+  .annotation-actions {
+    display: flex;
+    gap: 8px;
+    margin-top: 8px;
+    justify-content: flex-end;
+  }
 }
 
 .loading-state {
-    position: absolute;
-    top: 50%;
-    left: 50%;
-    transform: translate(-50%, -50%);
-    text-align: center;
-    z-index: 100;
-    
-    .el-icon {
-        font-size: 48px;
-        color: #409eff;
-        margin-bottom: 16px;
-    }
-    
-    p {
-        font-size: 14px;
-        color: #909399;
-    }
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  text-align: center;
+  z-index: 100;
+
+  .el-icon {
+    font-size: 48px;
+    color: #409eff;
+    margin-bottom: 16px;
+  }
+
+  p {
+    font-size: 14px;
+    color: #909399;
+  }
 }
 
 :deep(.el-result),
 :deep(.el-empty) {
-    position: absolute;
-    top: 50%;
-    left: 50%;
-    transform: translate(-50%, -50%);
-    width: 100%;
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  width: 100%;
 }
 
 .settings-tabs {
-    :deep(.el-tabs__content) {
-        padding: 0;
-    }
+  :deep(.el-tabs__content) {
+    padding: 0;
+  }
 }
 
 .settings-section {
-    padding: 20px 0;
+  padding: 20px 0;
 }
 
 .mode-radio-group {
-    display: flex;
-    flex-direction: column;
-    gap: 12px;
-    
-    :deep(.el-radio) {
-        margin: 0;
-        padding: 16px;
-        
-        &.is-bordered {
-            border-radius: 8px;
-        }
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+
+  :deep(.el-radio) {
+    margin: 0;
+    padding: 16px;
+
+    &.is-bordered {
+      border-radius: 8px;
     }
-    
-    .radio-label {
-        .label-title {
-            font-size: 14px;
-            font-weight: 500;
-            color: #303133;
-            margin-bottom: 4px;
-        }
-        
-        .label-desc {
-            font-size: 12px;
-            color: #909399;
-        }
+  }
+
+  .radio-label {
+    .label-title {
+      font-size: 14px;
+      font-weight: 500;
+      color: #303133;
+      margin-bottom: 4px;
     }
+
+    .label-desc {
+      font-size: 12px;
+      color: #909399;
+    }
+  }
 }
 
 .setting-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 12px 0;
+
+  .item-label {
     display: flex;
-    align-items: center;
-    justify-content: space-between;
-    padding: 12px 0;
-    
-    .item-label {
-        display: flex;
-        flex-direction: column;
-        gap: 4px;
-        
-        > span:first-child {
-            font-size: 14px;
-            color: #303133;
-        }
-        
-        .item-desc {
-            font-size: 12px;
-            color: #909399;
-        }
+    flex-direction: column;
+    gap: 4px;
+
+    > span:first-child {
+      font-size: 14px;
+      color: #303133;
+    }
+
+    .item-desc {
+      font-size: 12px;
+      color: #909399;
     }
+  }
 }
 
 .watermark-input {
-    margin-top: 12px;
-    padding-left: 20px;
+  margin-top: 12px;
+  padding-left: 20px;
 }
 
 .drawer-footer {
-    display: flex;
-    justify-content: flex-end;
-    gap: 12px;
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
 }
 
 :deep(.el-divider) {
-    margin: 12px 0;
+  margin: 12px 0;
 }
 </style>

+ 13 - 30
src/components/ArchiveConfirmDialog/index.vue

@@ -1,13 +1,7 @@
 <template>
   <div>
     <!-- 第一步:选择文件夹 -->
-    <el-dialog 
-      v-model="dialogVisible" 
-      :title="t('document.document.dialog.archiveDialog.title')" 
-      width="600px" 
-      append-to-body
-      @close="handleClose"
-    >
+    <el-dialog v-model="dialogVisible" :title="t('document.document.dialog.archiveDialog.title')" width="600px" append-to-body @close="handleClose">
       <div class="archive-content">
         <!-- 文档信息 -->
         <div class="document-info" v-if="document">
@@ -24,13 +18,7 @@
         <!-- 文件夹选择 -->
         <div class="folder-select-section">
           <div class="section-title">{{ t('document.document.dialog.archiveDialog.selectFolder') }}</div>
-          <el-alert 
-            v-if="!treeData || treeData.length === 0" 
-            title="暂无可选文件夹" 
-            type="warning" 
-            :closable="false"
-            style="margin-bottom: 10px"
-          />
+          <el-alert v-if="!treeData || treeData.length === 0" title="暂无可选文件夹" type="warning" :closable="false" style="margin-bottom: 10px" />
           <el-tree
             ref="treeRef"
             :data="treeData"
@@ -67,20 +55,15 @@
     </el-dialog>
 
     <!-- 第二步:确认归档 -->
-    <el-dialog 
-      v-model="confirmDialogVisible" 
-      :title="t('document.document.dialog.archiveDialog.confirmTitle')" 
-      width="500px" 
+    <el-dialog
+      v-model="confirmDialogVisible"
+      :title="t('document.document.dialog.archiveDialog.confirmTitle')"
+      width="500px"
       append-to-body
       @close="handleConfirmDialogClose"
     >
       <div class="confirm-content">
-        <el-alert 
-          :title="t('document.document.dialog.archiveDialog.confirmMessage')" 
-          type="warning" 
-          show-icon
-          :closable="false" 
-        />
+        <el-alert :title="t('document.document.dialog.archiveDialog.confirmMessage')" type="warning" show-icon :closable="false" />
         <div class="confirm-info" v-if="document">
           <el-descriptions :column="1" size="default" border>
             <el-descriptions-item :label="t('document.document.dialog.archiveDialog.documentName')">
@@ -211,19 +194,19 @@ const handleCheck = (data: FolderNode, checked: any) => {
   if (isSettingChecked.value) {
     return;
   }
-  
+
   const clickedId = data.id;
   const checkedKeys = checked.checkedKeys || [];
-  
-  console.log('handleCheck 被调用', { 
+
+  console.log('handleCheck 被调用', {
     clickedId,
     checkedKeys,
     currentSelected: selectedFolderId.value
   });
-  
+
   // 设置标志位,防止循环触发
   isSettingChecked.value = true;
-  
+
   // 实现单选:只保留当前点击的节点
   if (checkedKeys.includes(clickedId)) {
     // 如果当前节点在选中列表中,说明是选中操作
@@ -237,7 +220,7 @@ const handleCheck = (data: FolderNode, checked: any) => {
     console.log('不允许取消选中,保持选中状态');
     treeRef.value?.setCheckedKeys([selectedFolderId.value || clickedId]);
   }
-  
+
   // 延迟重置标志位
   setTimeout(() => {
     isSettingChecked.value = false;

+ 27 - 17
src/components/AuditLogDialog/index.vue

@@ -10,9 +10,14 @@
           </el-select>
         </el-form-item>
         <el-form-item :label="auditTimeLabel">
-          <el-date-picker v-model="auditTimeRange" type="daterange" range-separator="-"
-            :start-placeholder="startTimePlaceholder" :end-placeholder="endTimePlaceholder"
-            value-format="YYYY-MM-DD HH:mm:ss" />
+          <el-date-picker
+            v-model="auditTimeRange"
+            type="daterange"
+            range-separator="-"
+            :start-placeholder="startTimePlaceholder"
+            :end-placeholder="endTimePlaceholder"
+            value-format="YYYY-MM-DD HH:mm:ss"
+          />
         </el-form-item>
         <el-form-item>
           <el-button type="primary" icon="Search" @click="handleQuery">{{ searchButtonText }}</el-button>
@@ -51,11 +56,11 @@
         <!-- 操作栏 -->
         <el-table-column :label="operationLabel" width="140" align="center" fixed="right">
           <template #default="scope">
-            <el-button 
-              v-if="scope.row.ossId" 
-              type="primary" 
+            <el-button
+              v-if="scope.row.ossId"
+              type="primary"
               icon="Download"
-              style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px;"
+              style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px"
               @click="handleDownload(scope.row.ossId, scope.row.documentName)"
             >
               {{ downloadButtonText }}
@@ -66,8 +71,7 @@
       </el-table>
 
       <!-- 分页 -->
-      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
-        :total="total" @pagination="getList" />
+      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
     </el-dialog>
   </div>
 </template>
@@ -168,18 +172,24 @@ const handleReset = () => {
 };
 
 /** 监听文档ID变化,重新查询 */
-watch(() => props.documentId, (newVal) => {
-  if (newVal && dialogVisible.value) {
-    getList();
+watch(
+  () => props.documentId,
+  (newVal) => {
+    if (newVal && dialogVisible.value) {
+      getList();
+    }
   }
-});
+);
 
 /** 监听可见性变化,打开时重新查询 */
-watch(() => props.visible, (newVal) => {
-  if (newVal) {
-    getList();
+watch(
+  () => props.visible,
+  (newVal) => {
+    if (newVal) {
+      getList();
+    }
   }
-});
+);
 
 /** 下载历史文件 */
 const handleDownload = async (ossId: number, fileName: string) => {

+ 2 - 3
src/components/Breadcrumb/index.vue

@@ -2,8 +2,7 @@
   <el-breadcrumb class="app-breadcrumb" separator="/">
     <transition-group name="breadcrumb">
       <el-breadcrumb-item v-for="(item, index) in parsedLevelList" :key="item.path">
-        <span v-if="item.redirect === 'noRedirect' || index == parsedLevelList.length - 1" class="no-redirect">{{
-          item.displayTitle }}</span>
+        <span v-if="item.redirect === 'noRedirect' || index == parsedLevelList.length - 1" class="no-redirect">{{ item.displayTitle }}</span>
         <a v-else @click.prevent="handleLink(item)">{{ item.displayTitle }}</a>
       </el-breadcrumb-item>
     </transition-group>
@@ -26,7 +25,7 @@ const levelList = ref<RouteLocationMatched[]>([]);
 const parsedLevelList = computed(() => {
   // 访问 locale.value 以建立响应式依赖
   const _ = locale.value;
-  return levelList.value.map(item => ({
+  return levelList.value.map((item) => ({
     ...item,
     displayTitle: parseI18nName(item.meta?.title)
   }));

+ 153 - 146
src/components/DataPermisionTree/TreeNode.vue

@@ -1,243 +1,250 @@
 <template>
-    <div class="tree-node">
-        <div class="node-content" :class="{ disabled: isDisabled }">
-            <span v-if="hasChildren" class="expand-icon" @click="handleExpand">
-                <svg v-if="isExpanded" width="16" height="16" viewBox="0 0 16 16">
-                    <path d="M8 4L12 8L8 12L11 8L8 4Z" fill="currentColor" transform="rotate(90 8 8)" />
-                </svg>
-                <svg v-else width="16" height="16" viewBox="0 0 16 16">
-                    <path d="M4 8L8 12L12 8L8 4L4 8Z" fill="currentColor" />
-                </svg>
-            </span>
-            <span v-else class="expand-placeholder"></span>
-
-            <label class="checkbox-container">
-                <input type="checkbox" :checked="isChecked" :indeterminate="isIndeterminate" :disabled="isDisabled"
-                    @change="handleCheck" />
-                <span class="checkmark"></span>
-            </label>
-
-            <span class="node-label">
-                <slot :node="node" :data="node">
-                    <span>{{ node.name }}</span>
-                </slot>
-            </span>
-        </div>
-
-        <div v-if="isExpanded && hasChildren" class="children">
-            <tree-node v-for="child in node.children" :key="child.id" :node="child" :selected-keys="selectedKeys"
-                :indeterminate-keys="indeterminateKeys" :disabled-keys="disabledKeys" :expanded-keys="expandedKeys"
-                @check="handleChildCheck" @expand="handleChildExpand">
-                <template #default="{ node: childNode, data }">
-                    <slot :node="childNode" :data="data"></slot>
-                </template>
-            </tree-node>
-        </div>
+  <div class="tree-node">
+    <div class="node-content" :class="{ disabled: isDisabled }">
+      <span v-if="hasChildren" class="expand-icon" @click="handleExpand">
+        <svg v-if="isExpanded" width="16" height="16" viewBox="0 0 16 16">
+          <path d="M8 4L12 8L8 12L11 8L8 4Z" fill="currentColor" transform="rotate(90 8 8)" />
+        </svg>
+        <svg v-else width="16" height="16" viewBox="0 0 16 16">
+          <path d="M4 8L8 12L12 8L8 4L4 8Z" fill="currentColor" />
+        </svg>
+      </span>
+      <span v-else class="expand-placeholder"></span>
+
+      <label class="checkbox-container">
+        <input type="checkbox" :checked="isChecked" :indeterminate="isIndeterminate" :disabled="isDisabled" @change="handleCheck" />
+        <span class="checkmark"></span>
+      </label>
+
+      <span class="node-label">
+        <slot :node="node" :data="node">
+          <span>{{ node.name }}</span>
+        </slot>
+      </span>
     </div>
+
+    <div v-if="isExpanded && hasChildren" class="children">
+      <tree-node
+        v-for="child in node.children"
+        :key="child.id"
+        :node="child"
+        :selected-keys="selectedKeys"
+        :indeterminate-keys="indeterminateKeys"
+        :disabled-keys="disabledKeys"
+        :expanded-keys="expandedKeys"
+        @check="handleChildCheck"
+        @expand="handleChildExpand"
+      >
+        <template #default="{ node: childNode, data }">
+          <slot :node="childNode" :data="data"></slot>
+        </template>
+      </tree-node>
+    </div>
+  </div>
 </template>
 
 <script setup lang="ts">
-import { computed } from 'vue'
-import type { TreeData } from './types'
-import { Folder, Document, Location, OfficeBuilding } from '@element-plus/icons-vue'
+import { computed } from 'vue';
+import type { TreeData } from './types';
+import { Folder, Document, Location, OfficeBuilding } from '@element-plus/icons-vue';
 
 interface Props {
-    node: TreeData
-    selectedKeys: number[]
-    indeterminateKeys: Set<number>
-    disabledKeys: Set<number>
-    expandedKeys: Set<number>
+  node: TreeData;
+  selectedKeys: number[];
+  indeterminateKeys: Set<number>;
+  disabledKeys: Set<number>;
+  expandedKeys: Set<number>;
 }
 
 interface Emits {
-    (e: 'check', node: TreeData, checked: boolean): void
-    (e: 'expand', nodeId: number): void
+  (e: 'check', node: TreeData, checked: boolean): void;
+  (e: 'expand', nodeId: number): void;
 }
 
-const props = defineProps<Props>()
-const emit = defineEmits<Emits>()
+const props = defineProps<Props>();
+const emit = defineEmits<Emits>();
 
 // 检查节点是否被选中(包括视觉上的级联选中)
 const isChecked = computed(() => {
-    // 检查节点是否在选中列表中(这已经包含了级联效果)
-    return props.selectedKeys.includes(props.node.id)
-})
+  // 检查节点是否在选中列表中(这已经包含了级联效果)
+  return props.selectedKeys.includes(props.node.id);
+});
 
 // 检查节点是否为半选状态
 const isIndeterminate = computed(() => {
-    return props.indeterminateKeys.has(props.node.id)
-})
+  return props.indeterminateKeys.has(props.node.id);
+});
 
 // 检查节点是否被禁用
 const isDisabled = computed(() => {
-    return props.disabledKeys.has(props.node.id)
-})
+  return props.disabledKeys.has(props.node.id);
+});
 
 // 检查是否有子节点
 const hasChildren = computed(() => {
-    return props.node.children && props.node.children.length > 0
-})
+  return props.node.children && props.node.children.length > 0;
+});
 
 // 检查是否展开
 const isExpanded = computed(() => {
-    return props.expandedKeys.has(props.node.id)
-})
+  return props.expandedKeys.has(props.node.id);
+});
 
 // 处理选中状态变化
 const handleCheck = (event: Event) => {
-    const target = event.target as HTMLInputElement
-    emit('check', props.node, target.checked)
-}
+  const target = event.target as HTMLInputElement;
+  emit('check', props.node, target.checked);
+};
 
 // 处理展开/折叠
 const handleExpand = () => {
-    emit('expand', props.node.id)
-}
+  emit('expand', props.node.id);
+};
 
 // 处理子节点选中
 const handleChildCheck = (node: TreeData, checked: boolean) => {
-    emit('check', node, checked)
-}
+  emit('check', node, checked);
+};
 
 // 处理子节点展开
 const handleChildExpand = (nodeId: number) => {
-    emit('expand', nodeId)
-}
+  emit('expand', nodeId);
+};
 </script>
 
 <style scoped>
 .tree-node {
-    width: 100%;
+  width: 100%;
 }
 
 .node-content {
-    display: flex;
-    align-items: center;
-    padding: 4px 0;
-    cursor: pointer;
+  display: flex;
+  align-items: center;
+  padding: 4px 0;
+  cursor: pointer;
 }
 
 .node-content.disabled {
-    cursor: not-allowed;
-    opacity: 0.6;
+  cursor: not-allowed;
+  opacity: 0.6;
 }
 
 .node-content.disabled input {
-    cursor: not-allowed;
+  cursor: not-allowed;
 }
 
 .expand-icon {
-    width: 16px;
-    height: 16px;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    margin-right: 4px;
-    cursor: pointer;
-    color: #666;
+  width: 16px;
+  height: 16px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 4px;
+  cursor: pointer;
+  color: #666;
 }
 
 .expand-icon:hover {
-    color: #333;
+  color: #333;
 }
 
 .expand-placeholder {
-    width: 16px;
-    height: 16px;
-    margin-right: 4px;
+  width: 16px;
+  height: 16px;
+  margin-right: 4px;
 }
 
 .checkbox-container {
-    display: flex;
-    align-items: center;
-    margin-right: 8px;
-    position: relative;
+  display: flex;
+  align-items: center;
+  margin-right: 8px;
+  position: relative;
 }
 
 .checkbox-container input {
-    position: absolute;
-    opacity: 0;
-    cursor: pointer;
-    height: 0;
-    width: 0;
+  position: absolute;
+  opacity: 0;
+  cursor: pointer;
+  height: 0;
+  width: 0;
 }
 
 .checkmark {
-    height: 16px;
-    width: 16px;
-    background-color: #fff;
-    border: 1px solid #dcdfe6;
-    border-radius: 2px;
-    transition: all 0.3s;
-    position: relative;
+  height: 16px;
+  width: 16px;
+  background-color: #fff;
+  border: 1px solid #dcdfe6;
+  border-radius: 2px;
+  transition: all 0.3s;
+  position: relative;
 }
 
-.checkbox-container:hover input~.checkmark {
-    border-color: #409eff;
+.checkbox-container:hover input ~ .checkmark {
+  border-color: #409eff;
 }
 
-.checkbox-container input:checked~.checkmark {
-    background-color: #409eff;
-    border-color: #409eff;
+.checkbox-container input:checked ~ .checkmark {
+  background-color: #409eff;
+  border-color: #409eff;
 }
 
-.checkbox-container input:indeterminate~.checkmark {
-    background-color: #409eff;
-    border-color: #409eff;
+.checkbox-container input:indeterminate ~ .checkmark {
+  background-color: #409eff;
+  border-color: #409eff;
 }
 
-.checkbox-container input:checked~.checkmark::after {
-    content: "";
-    position: absolute;
-    left: 5px;
-    top: 1px;
-    width: 4px;
-    height: 8px;
-    border: solid white;
-    border-width: 0 2px 2px 0;
-    transform: rotate(45deg);
+.checkbox-container input:checked ~ .checkmark::after {
+  content: '';
+  position: absolute;
+  left: 5px;
+  top: 1px;
+  width: 4px;
+  height: 8px;
+  border: solid white;
+  border-width: 0 2px 2px 0;
+  transform: rotate(45deg);
 }
 
-.checkbox-container input:indeterminate~.checkmark::after {
-    content: "";
-    position: absolute;
-    left: 3px;
-    top: 6px;
-    width: 8px;
-    height: 2px;
-    background-color: white;
+.checkbox-container input:indeterminate ~ .checkmark::after {
+  content: '';
+  position: absolute;
+  left: 3px;
+  top: 6px;
+  width: 8px;
+  height: 2px;
+  background-color: white;
 }
 
-.checkbox-container input:disabled~.checkmark {
-    background-color: #f5f7fa;
-    border-color: #e4e7ed;
-    cursor: not-allowed;
+.checkbox-container input:disabled ~ .checkmark {
+  background-color: #f5f7fa;
+  border-color: #e4e7ed;
+  cursor: not-allowed;
 }
 
-.checkbox-container input:disabled:checked~.checkmark {
-    background-color: #c0c4cc;
-    border-color: #c0c4cc;
+.checkbox-container input:disabled:checked ~ .checkmark {
+  background-color: #c0c4cc;
+  border-color: #c0c4cc;
 }
 
 .node-label {
-    font-size: 14px;
-    color: #606266;
-    flex: 1;
-    user-select: none;
+  font-size: 14px;
+  color: #606266;
+  flex: 1;
+  user-select: none;
 }
 
 .node-icon {
-    margin-right: 8px;
-    color: #606266;
+  margin-right: 8px;
+  color: #606266;
 }
 
 .node-content.disabled .node-label {
-    color: #c0c4cc;
+  color: #c0c4cc;
 }
 
 .children {
-    margin-left: 20px;
-    border-left: 1px dashed #e4e7ed;
-    padding-left: 10px;
+  margin-left: 20px;
+  border-left: 1px dashed #e4e7ed;
+  padding-left: 10px;
 }
-</style>
+</style>

+ 93 - 91
src/components/DataPermisionTree/demo.vue

@@ -1,140 +1,142 @@
 <template>
-    <div class="demo-container">
-        <h2>Data Permission Tree Demo</h2>
-
-        <div class="demo-section">
-            <h3>Basic Usage</h3>
-            <data-permission-tree :data="treeData" :selected-keys="selectedKeys"
-                @update:selectedKeys="handleSelectedKeysChange" @check="handleCheck" />
-        </div>
-
-        <div class="demo-section">
-            <h3>Disabled State</h3>
-            <data-permission-tree :data="treeData" :selected-keys="selectedKeys" :disabled="true"
-                @update:selectedKeys="handleSelectedKeysChange" @check="handleCheck" />
-        </div>
-
-        <div class="selected-info">
-            <h3>Selected Keys:</h3>
-            <p>{{ selectedKeys }}</p>
-        </div>
-
-        <div class="instructions">
-            <h3>Expected Behavior:</h3>
-            <ul>
-                <li>Select "中国" (ID: 1) → UI shows all children selected, but only records value: 1</li>
-                <li>Select "湖北省" (ID: 2) → UI shows "中国" indeterminate, "湖北省" and children selected, records: 2</li>
-                <li>Select "成都市" (ID: 6) → UI shows "中国" and "长沙省" indeterminate, "成都市" selected, records: 6</li>
-            </ul>
-        </div>
+  <div class="demo-container">
+    <h2>Data Permission Tree Demo</h2>
+
+    <div class="demo-section">
+      <h3>Basic Usage</h3>
+      <data-permission-tree :data="treeData" :selected-keys="selectedKeys" @update:selectedKeys="handleSelectedKeysChange" @check="handleCheck" />
+    </div>
+
+    <div class="demo-section">
+      <h3>Disabled State</h3>
+      <data-permission-tree
+        :data="treeData"
+        :selected-keys="selectedKeys"
+        :disabled="true"
+        @update:selectedKeys="handleSelectedKeysChange"
+        @check="handleCheck"
+      />
+    </div>
+
+    <div class="selected-info">
+      <h3>Selected Keys:</h3>
+      <p>{{ selectedKeys }}</p>
     </div>
+
+    <div class="instructions">
+      <h3>Expected Behavior:</h3>
+      <ul>
+        <li>Select "中国" (ID: 1) → UI shows all children selected, but only records value: 1</li>
+        <li>Select "湖北省" (ID: 2) → UI shows "中国" indeterminate, "湖北省" and children selected, records: 2</li>
+        <li>Select "成都市" (ID: 6) → UI shows "中国" and "长沙省" indeterminate, "成都市" selected, records: 6</li>
+      </ul>
+    </div>
+  </div>
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue'
-import DataPermissionTree from './index.vue'
-import type { TreeData } from './types'
+import { ref } from 'vue';
+import DataPermissionTree from './index.vue';
+import type { TreeData } from './types';
 
 // 树形数据示例 (matching the requirement)
 const treeData: TreeData[] = [
-    {
-        id: 1,
-        name: '中国',
+  {
+    id: 1,
+    name: '中国',
+    children: [
+      {
+        id: 2,
+        name: '湖北省',
         children: [
-            {
-                id: 2,
-                name: '湖北省',
-                children: [
-                    { id: 3, name: '武汉市' },
-                    { id: 4, name: '孝感市' }
-                ]
-            },
-            {
-                id: 5,
-                name: '长沙省',
-                children: [
-                    { id: 6, name: '成都市' }
-                ]
-            }
+          { id: 3, name: '武汉市' },
+          { id: 4, name: '孝感市' }
         ]
-    },
-    {
-        id: 7,
-        name: '测试文件夹'
-    }
-]
+      },
+      {
+        id: 5,
+        name: '长沙省',
+        children: [{ id: 6, name: '成都市' }]
+      }
+    ]
+  },
+  {
+    id: 7,
+    name: '测试文件夹'
+  }
+];
 
 // 选中的键值
-const selectedKeys = ref<number[]>([])
+const selectedKeys = ref<number[]>([]);
 
 // 处理选中键值变化
 const handleSelectedKeysChange = (keys: number[]) => {
-    selectedKeys.value = keys
-}
+  selectedKeys.value = keys;
+};
 
 // 处理选中事件
-const handleCheck = (keys: { checked: number[], indeterminate: number[] }) => {
-    console.log('Checked keys:', keys.checked)
-    console.log('Indeterminate keys:', keys.indeterminate)
-}
+const handleCheck = (keys: { checked: number[]; indeterminate: number[] }) => {
+  console.log('Checked keys:', keys.checked);
+  console.log('Indeterminate keys:', keys.indeterminate);
+};
 </script>
 
 <style scoped>
 .demo-container {
-    padding: 20px;
-    max-width: 800px;
-    margin: 0 auto;
+  padding: 20px;
+  max-width: 800px;
+  margin: 0 auto;
 }
 
 .demo-section {
-    margin-bottom: 30px;
-    padding: 20px;
-    border: 1px solid #e4e7ed;
-    border-radius: 4px;
+  margin-bottom: 30px;
+  padding: 20px;
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
 }
 
 .demo-section h3 {
-    margin-top: 0;
-    color: #303133;
+  margin-top: 0;
+  color: #303133;
 }
 
 .selected-info {
-    padding: 20px;
-    background-color: #f5f7fa;
-    border-radius: 4px;
+  padding: 20px;
+  background-color: #f5f7fa;
+  border-radius: 4px;
 }
 
 .selected-info h3 {
-    margin-top: 0;
-    color: #303133;
+  margin-top: 0;
+  color: #303133;
 }
 
 .selected-info p {
-    font-family: monospace;
-    background-color: #fff;
-    padding: 10px;
-    border-radius: 4px;
-    border: 1px solid #e4e7ed;
+  font-family: monospace;
+  background-color: #fff;
+  padding: 10px;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
 }
 
 .instructions {
-    padding: 20px;
-    background-color: #fffbe6;
-    border-radius: 4px;
-    border: 1px solid #ffe58f;
+  padding: 20px;
+  background-color: #fffbe6;
+  border-radius: 4px;
+  border: 1px solid #ffe58f;
 }
 
 .instructions h3 {
-    margin-top: 0;
-    color: #303133;
+  margin-top: 0;
+  color: #303133;
 }
 
 .instructions ul {
-    margin: 0;
-    padding-left: 20px;
+  margin: 0;
+  padding-left: 20px;
 }
 
 .instructions li {
-    margin-bottom: 10px;
+  margin-bottom: 10px;
 }
-</style>
+</style>

+ 181 - 174
src/components/DataPermisionTree/index.vue

@@ -1,239 +1,246 @@
 <template>
-    <div class="data-permission-tree">
-        <tree-node v-for="node in treeData" :key="node.id" :node="node" :selected-keys="computedSelectedKeys"
-            :indeterminate-keys="indeterminateKeys" :disabled-keys="disabledKeys" :expanded-keys="expandedKeys"
-            @check="handleCheck" @expand="handleExpand" />
-    </div>
+  <div class="data-permission-tree">
+    <tree-node
+      v-for="node in treeData"
+      :key="node.id"
+      :node="node"
+      :selected-keys="computedSelectedKeys"
+      :indeterminate-keys="indeterminateKeys"
+      :disabled-keys="disabledKeys"
+      :expanded-keys="expandedKeys"
+      @check="handleCheck"
+      @expand="handleExpand"
+    />
+  </div>
 </template>
 
 <script setup lang="ts">
-import { ref, computed, watch, onMounted } from 'vue'
-import TreeNode from './TreeNode.vue'
-import type { TreeData, CheckedKeys } from './types'
+import { ref, computed, watch, onMounted } from 'vue';
+import TreeNode from './TreeNode.vue';
+import type { TreeData, CheckedKeys } from './types';
 
 interface Props {
-    data?: TreeData[]
-    selectedKeys?: number[]
-    disabled?: boolean
+  data?: TreeData[];
+  selectedKeys?: number[];
+  disabled?: boolean;
 }
 
 interface Emits {
-    (e: 'update:selectedKeys', keys: number[]): void
-    (e: 'check', keys: CheckedKeys): void
+  (e: 'update:selectedKeys', keys: number[]): void;
+  (e: 'check', keys: CheckedKeys): void;
 }
 
 const props = withDefaults(defineProps<Props>(), {
-    data: () => [],
-    selectedKeys: () => [],
-    disabled: false
-})
+  data: () => [],
+  selectedKeys: () => [],
+  disabled: false
+});
 
-const emit = defineEmits<Emits>()
+const emit = defineEmits<Emits>();
 
 // 内部选中键值
-const innerSelectedKeys = ref<number[]>([])
-const expandedKeys = ref<Set<number>>(new Set())
-const indeterminateKeys = ref<Set<number>>(new Set())
+const innerSelectedKeys = ref<number[]>([]);
+const expandedKeys = ref<Set<number>>(new Set());
+const indeterminateKeys = ref<Set<number>>(new Set());
 
 // 视觉上的级联选中键值(用于显示效果,不影响实际值)
-const visualSelectedKeys = ref<Set<number>>(new Set())
+const visualSelectedKeys = ref<Set<number>>(new Set());
 
 // 计算禁用的键值
 const disabledKeys = computed(() => {
-    const keys = new Set<number>()
-    if (props.disabled) {
-        const collectKeys = (nodes: TreeData[]) => {
-            nodes.forEach(node => {
-                keys.add(node.id)
-                if (node.children && node.children.length > 0) {
-                    collectKeys(node.children)
-                }
-            })
+  const keys = new Set<number>();
+  if (props.disabled) {
+    const collectKeys = (nodes: TreeData[]) => {
+      nodes.forEach((node) => {
+        keys.add(node.id);
+        if (node.children && node.children.length > 0) {
+          collectKeys(node.children);
         }
-        collectKeys(props.data)
-    }
-    return keys
-})
+      });
+    };
+    collectKeys(props.data);
+  }
+  return keys;
+});
 
 // 初始化选中状态
 onMounted(() => {
-    innerSelectedKeys.value = [...props.selectedKeys]
-    updateIndeterminateState()
-
-    // 默认展开所有节点
-    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)
-})
+  innerSelectedKeys.value = [...props.selectedKeys];
+  updateIndeterminateState();
+
+  // 默认展开所有节点
+  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);
+});
 
 // 监听外部选中状态变化
-watch(() => props.selectedKeys, (newVal) => {
-    innerSelectedKeys.value = [...newVal]
-    updateIndeterminateState()
-})
+watch(
+  () => props.selectedKeys,
+  (newVal) => {
+    innerSelectedKeys.value = [...newVal];
+    updateIndeterminateState();
+  }
+);
 
 // 更新半选状态
 const updateIndeterminateState = () => {
-    const newIndeterminateKeys = new Set<number>()
-
-    const traverse = (nodes: TreeData[]) => {
-        nodes.forEach(node => {
-            if (node.children && node.children.length > 0) {
-                // 递归处理子节点
-                traverse(node.children)
-
-                // 检查当前节点是否为半选状态
-                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)
-                }
-            }
-        })
-    }
+  const newIndeterminateKeys = new Set<number>();
 
-    traverse(props.data)
-    indeterminateKeys.value = newIndeterminateKeys
-}
+  const traverse = (nodes: TreeData[]) => {
+    nodes.forEach((node) => {
+      if (node.children && node.children.length > 0) {
+        // 递归处理子节点
+        traverse(node.children);
+
+        // 检查当前节点是否为半选状态
+        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);
+        }
+      }
+    });
+  };
+
+  traverse(props.data);
+  indeterminateKeys.value = newIndeterminateKeys;
+};
 
 // 获取节点的所有后代节点ID
 const getAllDescendantIds = (node: TreeData): number[] => {
-    const ids: number[] = []
-
-    const traverse = (children: TreeData[] | undefined) => {
-        if (children && children.length > 0) {
-            children.forEach(child => {
-                ids.push(child.id)
-                traverse(child.children)
-            })
-        }
+  const ids: number[] = [];
+
+  const traverse = (children: TreeData[] | undefined) => {
+    if (children && children.length > 0) {
+      children.forEach((child) => {
+        ids.push(child.id);
+        traverse(child.children);
+      });
     }
+  };
 
-    traverse(node.children)
-    return ids
-}
+  traverse(node.children);
+  return ids;
+};
 
 // 查找指定ID节点的路径(包含该节点本身)
 const findNodePath = (nodes: TreeData[], targetId: number): TreeData[] | null => {
-    for (const node of nodes) {
-        if (node.id === targetId) {
-            return [node]
-        }
+  for (const node of nodes) {
+    if (node.id === targetId) {
+      return [node];
+    }
 
-        if (node.children && node.children.length > 0) {
-            const childPath = findNodePath(node.children, targetId)
-            if (childPath) {
-                return [node, ...childPath]
-            }
-        }
+    if (node.children && node.children.length > 0) {
+      const childPath = findNodePath(node.children, targetId);
+      if (childPath) {
+        return [node, ...childPath];
+      }
     }
-    return null
-}
+  }
+  return null;
+};
 
 // 处理节点选中
 const handleCheck = (node: TreeData, checked: boolean) => {
-    if (props.disabled || disabledKeys.value.has(node.id)) {
-        return
-    }
+  if (props.disabled || disabledKeys.value.has(node.id)) {
+    return;
+  }
 
-    let newSelectedKeys = [...innerSelectedKeys.value]
-    const keyIndex = newSelectedKeys.indexOf(node.id)
+  let newSelectedKeys = [...innerSelectedKeys.value];
+  const keyIndex = newSelectedKeys.indexOf(node.id);
 
-    if (checked) {
-        // 选中节点:只添加当前节点
-        if (keyIndex === -1) {
-            newSelectedKeys.push(node.id)
-        }
+  if (checked) {
+    // 选中节点:只添加当前节点
+    if (keyIndex === -1) {
+      newSelectedKeys.push(node.id);
+    }
 
-        // 获取所有后代节点ID并从选中列表中移除
-        const descendantIds = getAllDescendantIds(node)
-        newSelectedKeys = newSelectedKeys.filter(id => !descendantIds.includes(id))
+    // 获取所有后代节点ID并从选中列表中移除
+    const descendantIds = getAllDescendantIds(node);
+    newSelectedKeys = newSelectedKeys.filter((id) => !descendantIds.includes(id));
 
-        // 获取所有祖先节点ID并从选中列表中移除
-        const path = findNodePath(props.data, node.id)
-        if (path) {
-            // 排除当前节点本身,只获取祖先节点
-            const ancestorIds = path.slice(0, -1).map(ancestor => ancestor.id)
-            newSelectedKeys = newSelectedKeys.filter(id => !ancestorIds.includes(id))
-        }
-    } else {
-        // 取消选中节点
-        if (keyIndex !== -1) {
-            newSelectedKeys.splice(keyIndex, 1)
-        }
+    // 获取所有祖先节点ID并从选中列表中移除
+    const path = findNodePath(props.data, node.id);
+    if (path) {
+      // 排除当前节点本身,只获取祖先节点
+      const ancestorIds = path.slice(0, -1).map((ancestor) => ancestor.id);
+      newSelectedKeys = newSelectedKeys.filter((id) => !ancestorIds.includes(id));
+    }
+  } else {
+    // 取消选中节点
+    if (keyIndex !== -1) {
+      newSelectedKeys.splice(keyIndex, 1);
     }
+  }
 
-    innerSelectedKeys.value = newSelectedKeys
-    updateIndeterminateState()
+  innerSelectedKeys.value = newSelectedKeys;
+  updateIndeterminateState();
 
-    // 触发事件
-    emit('update:selectedKeys', newSelectedKeys)
-    emit('check', {
-        checked: newSelectedKeys,
-        indeterminate: Array.from(indeterminateKeys.value)
-    })
-}
+  // 触发事件
+  emit('update:selectedKeys', newSelectedKeys);
+  emit('check', {
+    checked: newSelectedKeys,
+    indeterminate: Array.from(indeterminateKeys.value)
+  });
+};
 
 // 处理展开/折叠
 const handleExpand = (nodeId: number) => {
-    const newExpandedKeys = new Set(expandedKeys.value)
-    if (newExpandedKeys.has(nodeId)) {
-        newExpandedKeys.delete(nodeId)
-    } else {
-        newExpandedKeys.add(nodeId)
-    }
-    expandedKeys.value = newExpandedKeys
-}
+  const newExpandedKeys = new Set(expandedKeys.value);
+  if (newExpandedKeys.has(nodeId)) {
+    newExpandedKeys.delete(nodeId);
+  } else {
+    newExpandedKeys.add(nodeId);
+  }
+  expandedKeys.value = newExpandedKeys;
+};
 
 // 计算最终的树数据
-const treeData = computed(() => props.data)
+const treeData = computed(() => props.data);
 
 // 计算选中键值
-const selectedKeys = computed(() => innerSelectedKeys.value)
+const selectedKeys = computed(() => innerSelectedKeys.value);
 
 // 计算用于显示的选中键值(包括级联效果)
 const computedSelectedKeys = computed(() => {
-    // 合并实际选中的键值和视觉上级联选中的键值
-    const allSelectedKeys = new Set(innerSelectedKeys.value)
-
-    // 对于每个选中的节点,将其所有子节点也添加到视觉选中集合中
-    const addDescendants = (nodes: TreeData[], parentSelected: boolean) => {
-        nodes.forEach(node => {
-            // 如果父节点被选中或者当前节点本身被选中
-            const isSelected = parentSelected || innerSelectedKeys.value.includes(node.id)
-
-            // 如果应该显示为选中状态,则添加到集合中
-            if (isSelected) {
-                allSelectedKeys.add(node.id)
-            }
-
-            // 递归处理子节点
-            if (node.children && node.children.length > 0) {
-                addDescendants(node.children, isSelected)
-            }
-        })
-    }
-
-    addDescendants(props.data, false)
-    return Array.from(allSelectedKeys)
-})
+  // 合并实际选中的键值和视觉上级联选中的键值
+  const allSelectedKeys = new Set(innerSelectedKeys.value);
+
+  // 对于每个选中的节点,将其所有子节点也添加到视觉选中集合中
+  const addDescendants = (nodes: TreeData[], parentSelected: boolean) => {
+    nodes.forEach((node) => {
+      // 如果父节点被选中或者当前节点本身被选中
+      const isSelected = parentSelected || innerSelectedKeys.value.includes(node.id);
+
+      // 如果应该显示为选中状态,则添加到集合中
+      if (isSelected) {
+        allSelectedKeys.add(node.id);
+      }
+
+      // 递归处理子节点
+      if (node.children && node.children.length > 0) {
+        addDescendants(node.children, isSelected);
+      }
+    });
+  };
+
+  addDescendants(props.data, false);
+  return Array.from(allSelectedKeys);
+});
 </script>
 
 <style scoped>
 .data-permission-tree {
-    width: 100%;
+  width: 100%;
 }
-</style>
+</style>

+ 7 - 7
src/components/DataPermisionTree/types.ts

@@ -1,11 +1,11 @@
 export interface TreeData {
-    id: number
-    name: string
-    type?: number
-    children?: TreeData[]
+  id: number;
+  name: string;
+  type?: number;
+  children?: TreeData[];
 }
 
 export interface CheckedKeys {
-    checked: number[]
-    indeterminate: number[]
-}
+  checked: number[];
+  indeterminate: number[];
+}

+ 1 - 1
src/components/DictTag/index.vue

@@ -52,7 +52,7 @@ const props = withDefaults(defineProps<Props>(), {
 
 // 解析国际化标签
 const parsedOptions = computed(() => {
-  return props.options.map(option => ({
+  return props.options.map((option) => ({
     ...option,
     label: parseI18nName(option.label)
   }));

+ 41 - 53
src/components/DocumentAuditDialog/index.vue

@@ -163,9 +163,7 @@
       <el-table-column prop="updateTime" label="更新时间" min-width="180" align="center" />
       <el-table-column label="操作" width="120" align="center" fixed="right">
         <template #default="{ row }">
-          <el-button type="primary" size="small" @click="handleSelectVersion(row.version)">
-            选择
-          </el-button>
+          <el-button type="primary" size="small" @click="handleSelectVersion(row.version)"> 选择 </el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -308,15 +306,15 @@ const initWpsEditor = async (shouldCallInitApi = false) => {
         console.error('[WPS] 调用后端初始化接口失败:', err);
         wpsLoading.value = false;
         wpsError.value = '初始化文档失败';
-        
+
         // 显示错误提示
         ElMessage.error('初始化文档失败: ' + (err.message || '未知错误'));
-        
+
         // 关闭对话框
         setTimeout(() => {
           dialogVisible.value = false;
         }, 1500);
-        
+
         return; // 终止初始化流程
       }
     } else {
@@ -508,20 +506,16 @@ const handleCopyAvatar = async () => {
   try {
     // 先让用户选择审核结果
     let reviewResult: string;
-    
+
     try {
-      await ElMessageBox.confirm(
-        t('document.document.copySignature.selectResultMessage'),
-        t('document.document.copySignature.selectResult'),
-        {
-          confirmButtonText: t('document.document.copySignature.pass'),
-          cancelButtonText: t('document.document.copySignature.reject'),
-          distinguishCancelAndClose: true,
-          closeOnClickModal: false,
-          closeOnPressEscape: false,
-          type: 'info'
-        }
-      );
+      await ElMessageBox.confirm(t('document.document.copySignature.selectResultMessage'), t('document.document.copySignature.selectResult'), {
+        confirmButtonText: t('document.document.copySignature.pass'),
+        cancelButtonText: t('document.document.copySignature.reject'),
+        distinguishCancelAndClose: true,
+        closeOnClickModal: false,
+        closeOnPressEscape: false,
+        type: 'info'
+      });
       // 点击确认按钮 = 通过
       reviewResult = 'pass';
     } catch (action) {
@@ -540,7 +534,7 @@ const handleCopyAvatar = async () => {
 
     // 获取当前用户昵称
     const reviewerName = userStore.nickname || userStore.name || t('document.document.copySignature.unknown');
-    
+
     // 获取当前时间
     const now = new Date();
     const year = now.getFullYear();
@@ -549,12 +543,10 @@ const handleCopyAvatar = async () => {
     const hour = now.getHours();
     const minute = now.getMinutes();
     const reviewTime = t('document.document.copySignature.timeFormat', { year, month, day, hour, minute });
-    
+
     // 审核结果文本
-    const resultText = reviewResult === 'pass' 
-      ? t('document.document.copySignature.passText') 
-      : t('document.document.copySignature.rejectText');
-    
+    const resultText = reviewResult === 'pass' ? t('document.document.copySignature.passText') : t('document.document.copySignature.rejectText');
+
     // 根据审核结果确定颜色
     const color = reviewResult === 'pass' ? '#00aa00' : '#ff0000';
 
@@ -563,7 +555,7 @@ const handleCopyAvatar = async () => {
     // 创建 canvas
     const canvas = document.createElement('canvas');
     const ctx = canvas.getContext('2d');
-    
+
     if (!ctx) {
       ElMessage.error(t('document.document.copySignature.canvasNotSupported'));
       copyingAvatar.value = false;
@@ -591,7 +583,7 @@ const handleCopyAvatar = async () => {
     ctx.font = 'bold 18px Arial, "Microsoft YaHei", sans-serif';
     const reviewerText = t('document.document.copySignature.reviewer', { name: reviewerName });
     ctx.fillText(reviewerText, 20, height / 3);
-    
+
     // 审核结果靠右显示
     const resultWidth = ctx.measureText(resultText).width;
     ctx.fillText(resultText, width - resultWidth - 20, height / 3);
@@ -599,7 +591,7 @@ const handleCopyAvatar = async () => {
     // 第二行:审核时间(居左对齐,加粗)
     ctx.font = 'bold 16px Arial, "Microsoft YaHei", sans-serif';
     const line2 = t('document.document.copySignature.reviewTime', { time: reviewTime });
-    ctx.fillText(line2, 20, height * 2 / 3);
+    ctx.fillText(line2, 20, (height * 2) / 3);
 
     console.log('[签名] Canvas 绘制完成');
 
@@ -677,15 +669,11 @@ const handleCleanComments = async () => {
   }
 
   try {
-    await ElMessageBox.confirm(
-      '确定要清空文档中的所有批注吗?此操作不可恢复。',
-      '清空批注',
-      {
-        confirmButtonText: '确定',
-        cancelButtonText: '取消',
-        type: 'warning'
-      }
-    );
+    await ElMessageBox.confirm('确定要清空文档中的所有批注吗?此操作不可恢复。', '清空批注', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning'
+    });
 
     cleaningComments.value = true;
     console.log('[清空批注] 开始清空文档批注,文档ID:', props.document.id, 'ossId:', props.document.ossId, '当前版本:', currentVersion.value);
@@ -700,13 +688,13 @@ const handleCleanComments = async () => {
 
     // 销毁当前 WPS 编辑器
     destroyWpsEditor();
-    
+
     // 等待 DOM 更新
     await nextTick();
-    
+
     // 使用新版本号重新初始化 WPS 编辑器(不调用后端初始化接口)
     await initWpsEditor(false);
-    
+
     console.log('[清空批注] WPS 编辑器已使用新版本重新初始化,fileId:', `${props.document.ossId}_${currentVersion.value}`);
   } catch (err: any) {
     if (err === 'cancel') {
@@ -731,12 +719,12 @@ const handleViewVersions = async () => {
   try {
     loadingVersions.value = true;
     showVersionDialog.value = true;
-    
+
     console.log('[历史版本] 获取历史版本列表,ossId:', props.document.ossId);
-    
+
     const res = await getFileVersionList(props.document.ossId);
     versionList.value = res.data || [];
-    
+
     console.log('[历史版本] 获取成功,版本数量:', versionList.value.length);
   } catch (err: any) {
     console.error('[历史版本] 获取失败:', err);
@@ -756,22 +744,22 @@ const handleSelectVersion = async (version: number) => {
 
   try {
     console.log('[历史版本] 选择版本:', version, 'ossId:', props.document.ossId);
-    
+
     // 更新当前版本号
     currentVersion.value = version;
-    
+
     // 关闭历史版本对话框
     showVersionDialog.value = false;
-    
+
     // 销毁当前 WPS 编辑器
     destroyWpsEditor();
-    
+
     // 等待 DOM 更新
     await nextTick();
-    
+
     // 使用选择的版本号重新初始化 WPS 编辑器(不调用后端初始化接口)
     await initWpsEditor(false);
-    
+
     ElMessage.success(`已切换到版本 ${version}`);
     console.log('[历史版本] WPS 编辑器已切换到版本:', version, 'fileId:', `${props.document.ossId}_${version}`);
   } catch (err: any) {
@@ -788,7 +776,7 @@ watch(
     if (val && props.document) {
       // 重置版本号为 1
       currentVersion.value = 1;
-      
+
       auditForm.value = {
         id: props.document.id,
         result: '3',
@@ -821,7 +809,7 @@ watch(dialogVisible, async (val) => {
         // 取消接口失败不影响关闭流程
       }
     }
-    
+
     auditForm.value = {
       id: 0,
       result: '3',
@@ -884,10 +872,10 @@ const submitForm = () => {
         };
 
         console.log('[审核提交] 提交审核数据到父组件:', auditData);
-        
+
         // 关闭对话框
         dialogVisible.value = false;
-        
+
         // 通过 emit 将审核数据传递给父组件
         emit('submit', auditData);
       } catch (error) {

+ 8 - 6
src/components/Editor/index.vue

@@ -59,13 +59,13 @@ const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const injectEditorStyles = () => {
   const styleId = 'quill-i18n-styles';
   let styleEl = document.getElementById(styleId);
-  
+
   if (!styleEl) {
     styleEl = document.createElement('style');
     styleEl.id = styleId;
     document.head.appendChild(styleEl);
   }
-  
+
   styleEl.textContent = `
     .ql-snow .ql-tooltip[data-mode='link']::before {
       content: '${t('components.editor.toolbar.link')}';
@@ -126,9 +126,12 @@ onMounted(() => {
 });
 
 // 监听语言切换,重新注入样式
-watch(() => proxy?.$i18n.locale, () => {
-  injectEditorStyles();
-});
+watch(
+  () => proxy?.$i18n.locale,
+  () => {
+    injectEditorStyles();
+  }
+);
 
 const upload = reactive<UploadOption>({
   headers: globalHeaders(),
@@ -194,7 +197,6 @@ watch(
   { immediate: true }
 );
 
-
 // 图片上传成功返回图片地址
 const handleUploadSuccess = (res: any) => {
   // 如果上传成功

+ 20 - 4
src/components/Process/submitVerify.vue

@@ -80,7 +80,13 @@
     <!-- 加签组件 -->
     <UserSelect ref="multiInstanceUserRef" :multiple="true" @confirm-call-back="addMultiInstanceUser"></UserSelect>
     <!-- 弹窗选人 -->
-    <UserSelect ref="porUserRef" :data="form.assigneeMap[nodeCode]" :multiple="true" :userIds="popUserIds" @confirm-call-back="handlePopUser"></UserSelect>
+    <UserSelect
+      ref="porUserRef"
+      :data="form.assigneeMap[nodeCode]"
+      :multiple="true"
+      :userIds="popUserIds"
+      @confirm-call-back="handlePopUser"
+    ></UserSelect>
 
     <!-- 驳回开始 -->
     <el-dialog v-model="backVisible" draggable :title="$t('components.process.backTitle')" width="40%" :close-on-click-modal="false">
@@ -116,14 +122,24 @@
       </template>
     </el-dialog>
     <!-- 驳回结束 -->
-    <el-dialog v-model="deleteSignatureVisible" draggable :title="$t('components.process.reduceSignTitle')" width="700px" height="400px" append-to-body :close-on-click-modal="false">
+    <el-dialog
+      v-model="deleteSignatureVisible"
+      draggable
+      :title="$t('components.process.reduceSignTitle')"
+      width="700px"
+      height="400px"
+      append-to-body
+      :close-on-click-modal="false"
+    >
       <div>
         <el-table :data="deleteUserList" border>
           <el-table-column prop="nodeName" :label="$t('components.process.taskName')" />
           <el-table-column prop="nickName" :label="$t('components.process.handler')" />
           <el-table-column :label="$t('components.process.operation')" align="center" width="160">
             <template #default="scope">
-              <el-button type="danger" size="small" icon="Delete" @click="deleteMultiInstanceUser(scope.row)">{{ $t('components.process.delete') }} </el-button>
+              <el-button type="danger" size="small" icon="Delete" @click="deleteMultiInstanceUser(scope.row)"
+                >{{ $t('components.process.delete') }}
+              </el-button>
             </template>
           </el-table-column>
         </el-table>
@@ -265,7 +281,7 @@ const openDialog = async (id?: string) => {
   selectCopyUserList.value = task.value.copyList;
   selectCopyUserIds.value = task.value.copyList.map((e) => e.userId).join(',');
   varNodeList.value = task.value.varList;
-  console.log('varNodeList', varNodeList.value)
+  console.log('varNodeList', varNodeList.value);
   buttonDisabled.value = false;
   try {
     const data = {

+ 7 - 1
src/components/RightToolbar/index.vue

@@ -1,7 +1,13 @@
 <template>
   <div class="top-right-btn" :style="style">
     <el-row>
-      <el-tooltip v-if="search" class="item" effect="dark" :content="showSearch ? t('components.rightToolbar.hideSearch') : t('components.rightToolbar.showSearch')" placement="top">
+      <el-tooltip
+        v-if="search"
+        class="item"
+        effect="dark"
+        :content="showSearch ? t('components.rightToolbar.hideSearch') : t('components.rightToolbar.showSearch')"
+        placement="top"
+      >
         <el-button circle icon="Search" @click="toggleSearch()" />
       </el-tooltip>
       <el-tooltip class="item" effect="dark" :content="t('components.rightToolbar.refresh')" placement="top">

+ 12 - 2
src/components/RoleSelect/index.vue

@@ -6,10 +6,20 @@
           <el-card shadow="hover">
             <el-form ref="queryFormRef" :model="queryParams" :inline="true">
               <el-form-item :label="$t('components.roleSelect.roleName')" prop="roleName">
-                <el-input v-model="queryParams.roleName" :placeholder="$t('components.roleSelect.roleNamePlaceholder')" clearable @keyup.enter="handleQuery" />
+                <el-input
+                  v-model="queryParams.roleName"
+                  :placeholder="$t('components.roleSelect.roleNamePlaceholder')"
+                  clearable
+                  @keyup.enter="handleQuery"
+                />
               </el-form-item>
               <el-form-item :label="$t('components.roleSelect.roleKey')" prop="roleKey">
-                <el-input v-model="queryParams.roleKey" :placeholder="$t('components.roleSelect.roleKeyPlaceholder')" clearable @keyup.enter="handleQuery" />
+                <el-input
+                  v-model="queryParams.roleKey"
+                  :placeholder="$t('components.roleSelect.roleKeyPlaceholder')"
+                  clearable
+                  @keyup.enter="handleQuery"
+                />
               </el-form-item>
 
               <el-form-item>

+ 25 - 18
src/components/TopNav/index.vue

@@ -1,10 +1,10 @@
 <template>
   <el-menu ref="menuRef" :default-active="activeMenu" mode="horizontal" :ellipsis="false" @select="handleSelect">
     <template v-for="(item, index) in topMenus">
-      <el-menu-item 
-        v-if="index < visibleNumber" 
-        :key="index" 
-        :style="{ '--theme': theme }" 
+      <el-menu-item
+        v-if="index < visibleNumber"
+        :key="index"
+        :style="{ '--theme': theme }"
         :index="item.path"
         :ref="(el) => setMenuItemRef(el, index)"
       >
@@ -208,12 +208,12 @@ const calculateVisibleMenus = () => {
       const menuItems = menuElement.querySelectorAll('.el-menu-item:not(.is-disabled)');
       // "更多"按钮的大概宽度(包含margin和padding)
       const moreButtonWidth = 100;
-      
+
       for (let i = 0; i < menuItems.length && i < topMenus.value.length; i++) {
         const item = menuItems[i] as HTMLElement;
         // 获取包含 margin 的实际宽度
         const itemWidth = item.offsetWidth + 20; // 20px 是左右 margin (0 10px)
-        
+
         // 如果不是最后一项,且剩余空间不足以容纳当前项+更多按钮,则停止
         if (i < topMenus.value.length - 1) {
           if (accumulatedWidth + itemWidth + moreButtonWidth <= availableWidth) {
@@ -307,9 +307,9 @@ onMounted(() => {
       calculateVisibleMenus();
     }, 200);
   };
-  
+
   window.addEventListener('resize', handleResize);
-  
+
   onBeforeUnmount(() => {
     window.removeEventListener('resize', handleResize);
     if (resizeTimer) {
@@ -324,10 +324,14 @@ onMounted(() => {
 });
 
 // 监听菜单变化和语言切换,重新计算
-watch([topMenus, locale], () => {
-  isFirstCalculation.value = true;
-  calculateVisibleMenus();
-}, { deep: true });
+watch(
+  [topMenus, locale],
+  () => {
+    isFirstCalculation.value = true;
+    calculateVisibleMenus();
+  },
+  { deep: true }
+);
 
 // 监听侧边栏展开/收起状态变化,重新计算菜单宽度
 watch(sidebarOpened, () => {
@@ -358,12 +362,15 @@ watch(topNav, (newVal) => {
 });
 
 // 监听路由变化,因为切换菜单可能影响布局
-watch(() => route.path, () => {
-  // 路由变化时使用防抖计算
-  nextTick(() => {
-    debouncedCalculateVisibleMenus(150);
-  });
-});
+watch(
+  () => route.path,
+  () => {
+    // 路由变化时使用防抖计算
+    nextTick(() => {
+      debouncedCalculateVisibleMenus(150);
+    });
+  }
+);
 
 // 清理定时器
 onBeforeUnmount(() => {

+ 12 - 2
src/components/UserSelect/index.vue

@@ -26,10 +26,20 @@
               <el-card shadow="hover">
                 <el-form ref="queryFormRef" :model="queryParams" :inline="true">
                   <el-form-item :label="$t('components.userSelect.userName')" prop="userName">
-                    <el-input v-model="queryParams.userName" :placeholder="$t('components.userSelect.userNamePlaceholder')" clearable @keyup.enter="handleQuery" />
+                    <el-input
+                      v-model="queryParams.userName"
+                      :placeholder="$t('components.userSelect.userNamePlaceholder')"
+                      clearable
+                      @keyup.enter="handleQuery"
+                    />
                   </el-form-item>
                   <el-form-item :label="$t('components.userSelect.phonenumber')" prop="phonenumber">
-                    <el-input v-model="queryParams.phonenumber" :placeholder="$t('components.userSelect.phonenumberPlaceholder')" clearable @keyup.enter="handleQuery" />
+                    <el-input
+                      v-model="queryParams.phonenumber"
+                      :placeholder="$t('components.userSelect.phonenumberPlaceholder')"
+                      clearable
+                      @keyup.enter="handleQuery"
+                    />
                   </el-form-item>
                   <el-form-item>
                     <el-button type="primary" icon="Search" @click="handleQuery">{{ $t('components.userSelect.search') }}</el-button>

+ 198 - 195
src/components/WpsEditor/index.vue

@@ -1,16 +1,16 @@
 <template>
-    <div class="wps-editor-container">
-        <div v-if="loading" class="loading-container">
-            <el-icon class="is-loading"><Loading /></el-icon>
-            <span>正在加载 WPS 编辑器...</span>
-        </div>
-        <div v-if="error" class="error-container">
-            <el-alert type="error" title="加载失败" :closable="false">
-                <div style="white-space: pre-line; line-height: 1.6;">{{ error }}</div>
-            </el-alert>
-        </div>
-        <div ref="wpsContainerRef" class="wps-container"></div>
+  <div class="wps-editor-container">
+    <div v-if="loading" class="loading-container">
+      <el-icon class="is-loading"><Loading /></el-icon>
+      <span>正在加载 WPS 编辑器...</span>
     </div>
+    <div v-if="error" class="error-container">
+      <el-alert type="error" title="加载失败" :closable="false">
+        <div style="white-space: pre-line; line-height: 1.6">{{ error }}</div>
+      </el-alert>
+    </div>
+    <div ref="wpsContainerRef" class="wps-container"></div>
+  </div>
 </template>
 
 <script setup lang="ts">
@@ -19,48 +19,48 @@ import { Loading } from '@element-plus/icons-vue';
 import { ElMessage } from 'element-plus';
 
 interface Props {
-    fileUrl: string;
-    fileName?: string;
-    fileType?: 'doc' | 'docx' | 'xls' | 'xlsx' | 'ppt' | 'pptx' | 'pdf';
-    mode?: 'simple' | 'normal';
-    userId?: string;
-    userName?: string;
-    enableComment?: boolean;
-    enableRevision?: boolean;
-    enableWatermark?: boolean;
-    watermarkText?: string;
-    enableDownload?: boolean;
-    enablePrint?: boolean;
-    enableCopy?: boolean;
-    enableSave?: boolean;
-    enableShare?: boolean;
-    enableHistory?: boolean;
-    readOnly?: boolean;
+  fileUrl: string;
+  fileName?: string;
+  fileType?: 'doc' | 'docx' | 'xls' | 'xlsx' | 'ppt' | 'pptx' | 'pdf';
+  mode?: 'simple' | 'normal';
+  userId?: string;
+  userName?: string;
+  enableComment?: boolean;
+  enableRevision?: boolean;
+  enableWatermark?: boolean;
+  watermarkText?: string;
+  enableDownload?: boolean;
+  enablePrint?: boolean;
+  enableCopy?: boolean;
+  enableSave?: boolean;
+  enableShare?: boolean;
+  enableHistory?: boolean;
+  readOnly?: boolean;
 }
 
 interface Emits {
-    (e: 'ready'): void;
-    (e: 'error', error: string): void;
-    (e: 'save', data: any): void;
-    (e: 'fileChange', data: any): void;
+  (e: 'ready'): void;
+  (e: 'error', error: string): void;
+  (e: 'save', data: any): void;
+  (e: 'fileChange', data: any): void;
 }
 
 const props = withDefaults(defineProps<Props>(), {
-    mode: 'normal',
-    fileName: '未命名文档',
-    userId: 'user_' + Date.now(),
-    userName: '当前用户',
-    enableComment: true,
-    enableRevision: true,
-    enableWatermark: false,
-    watermarkText: '',
-    enableDownload: true,
-    enablePrint: true,
-    enableCopy: true,
-    enableSave: true,
-    enableShare: false,
-    enableHistory: false,
-    readOnly: false
+  mode: 'normal',
+  fileName: '未命名文档',
+  userId: 'user_' + Date.now(),
+  userName: '当前用户',
+  enableComment: true,
+  enableRevision: true,
+  enableWatermark: false,
+  watermarkText: '',
+  enableDownload: true,
+  enablePrint: true,
+  enableCopy: true,
+  enableSave: true,
+  enableShare: false,
+  enableHistory: false,
+  readOnly: false
 });
 
 const emit = defineEmits<Emits>();
@@ -75,187 +75,190 @@ const WPS_APP_ID = 'SX20251229FLIAPDAPP';
 
 // 初始化 WPS 编辑器(符合官方文档)
 const initWpsEditor = async () => {
-    if (!wpsContainerRef.value || !props.fileUrl) {
-        error.value = '缺少必要参数';
-        loading.value = false;
-        emit('error', error.value);
-        return;
+  if (!wpsContainerRef.value || !props.fileUrl) {
+    error.value = '缺少必要参数';
+    loading.value = false;
+    emit('error', error.value);
+    return;
+  }
+
+  try {
+    loading.value = true;
+    error.value = '';
+
+    // 检查 WPS SDK 是否已加载
+    if (!(window as any).WebOfficeSDK) {
+      const errorMsg =
+        'WPS SDK 未加载。请按以下步骤操作:\n\n1. 从 WPS 官网下载 JSSDK\n2. 将 SDK 文件放到 public 目录\n3. 在 index.html 中引入 SDK 脚本\n\n或使用 CDN(需要网络连接)';
+      error.value = errorMsg;
+      loading.value = false;
+      emit('error', errorMsg);
+      return;
     }
 
-    try {
-        loading.value = true;
-        error.value = '';
-
-        // 检查 WPS SDK 是否已加载
-        if (!(window as any).WebOfficeSDK) {
-            const errorMsg = 'WPS SDK 未加载。请按以下步骤操作:\n\n1. 从 WPS 官网下载 JSSDK\n2. 将 SDK 文件放到 public 目录\n3. 在 index.html 中引入 SDK 脚本\n\n或使用 CDN(需要网络连接)';
-            error.value = errorMsg;
-            loading.value = false;
-            emit('error', errorMsg);
-            return;
-        }
-
-        const WebOfficeSDK = (window as any).WebOfficeSDK;
-        
-        // 根据文件类型确定 officeType
-        let officeType = WebOfficeSDK.OfficeType.Writer;
-        if (props.fileType === 'xlsx' || props.fileType === 'xls') {
-            officeType = WebOfficeSDK.OfficeType.Spreadsheet;
-        } else if (props.fileType === 'pptx' || props.fileType === 'ppt') {
-            officeType = WebOfficeSDK.OfficeType.Presentation;
-        } else if (props.fileType === 'pdf') {
-            officeType = WebOfficeSDK.OfficeType.Pdf;
-        }
-
-        // 生成唯一的文件ID
-        const fileId = `${props.userId}_${Date.now()}`;
-
-        // 构建初始化配置(符合官方文档)
-        const config: any = {
-            // 必需参数
-            appId: WPS_APP_ID,
-            officeType: officeType,
-            fileId: fileId,
-            
-            // 挂载节点
-            mount: wpsContainerRef.value,
-            
-            // 自定义参数(会传递到回调接口)
-            customArgs: {
-                fileName: props.fileName,
-                fileUrl: props.fileUrl,
-                userId: props.userId,
-                userName: props.userName,
-                readOnly: props.readOnly,
-                enableComment: props.enableComment,
-                enableRevision: props.enableRevision
-            }
-        };
-
-        console.log('[WPS] 初始化配置:', config);
-
-        // 初始化 WebOffice
-        wpsInstance = await WebOfficeSDK.init(config);
-        
-        loading.value = false;
-        emit('ready');
-        console.log('[WPS] 编辑器初始化成功');
-        
-    } catch (err: any) {
-        console.error('[WPS] 初始化失败:', err);
-        error.value = err.message || '初始化失败';
-        loading.value = false;
-        emit('error', error.value);
+    const WebOfficeSDK = (window as any).WebOfficeSDK;
+
+    // 根据文件类型确定 officeType
+    let officeType = WebOfficeSDK.OfficeType.Writer;
+    if (props.fileType === 'xlsx' || props.fileType === 'xls') {
+      officeType = WebOfficeSDK.OfficeType.Spreadsheet;
+    } else if (props.fileType === 'pptx' || props.fileType === 'ppt') {
+      officeType = WebOfficeSDK.OfficeType.Presentation;
+    } else if (props.fileType === 'pdf') {
+      officeType = WebOfficeSDK.OfficeType.Pdf;
     }
+
+    // 生成唯一的文件ID
+    const fileId = `${props.userId}_${Date.now()}`;
+
+    // 构建初始化配置(符合官方文档)
+    const config: any = {
+      // 必需参数
+      appId: WPS_APP_ID,
+      officeType: officeType,
+      fileId: fileId,
+
+      // 挂载节点
+      mount: wpsContainerRef.value,
+
+      // 自定义参数(会传递到回调接口)
+      customArgs: {
+        fileName: props.fileName,
+        fileUrl: props.fileUrl,
+        userId: props.userId,
+        userName: props.userName,
+        readOnly: props.readOnly,
+        enableComment: props.enableComment,
+        enableRevision: props.enableRevision
+      }
+    };
+
+    console.log('[WPS] 初始化配置:', config);
+
+    // 初始化 WebOffice
+    wpsInstance = await WebOfficeSDK.init(config);
+
+    loading.value = false;
+    emit('ready');
+    console.log('[WPS] 编辑器初始化成功');
+  } catch (err: any) {
+    console.error('[WPS] 初始化失败:', err);
+    error.value = err.message || '初始化失败';
+    loading.value = false;
+    emit('error', error.value);
+  }
 };
 
 // 保存文档
 const saveDocument = async () => {
-    if (!wpsInstance) {
-        ElMessage.warning('编辑器未初始化');
-        return null;
-    }
+  if (!wpsInstance) {
+    ElMessage.warning('编辑器未初始化');
+    return null;
+  }
 
-    try {
-        console.log('[WPS] 开始保存文档');
-        // 调用 WPS 的保存方法
-        const result = await wpsInstance.save();
-        console.log('[WPS] 保存成功:', result);
-        ElMessage.success('保存成功');
-        emit('save', result);
-        return result;
-    } catch (err: any) {
-        console.error('[WPS] 保存失败:', err);
-        ElMessage.error('保存失败');
-        throw err;
-    }
+  try {
+    console.log('[WPS] 开始保存文档');
+    // 调用 WPS 的保存方法
+    const result = await wpsInstance.save();
+    console.log('[WPS] 保存成功:', result);
+    ElMessage.success('保存成功');
+    emit('save', result);
+    return result;
+  } catch (err: any) {
+    console.error('[WPS] 保存失败:', err);
+    ElMessage.error('保存失败');
+    throw err;
+  }
 };
 
 // 销毁编辑器
 const destroyEditor = () => {
-    if (wpsInstance) {
-        try {
-            if (wpsInstance.destroy) {
-                wpsInstance.destroy();
-            }
-            wpsInstance = null;
-            console.log('[WPS] 编辑器已销毁');
-        } catch (err) {
-            console.error('[WPS] 销毁编辑器失败:', err);
-        }
+  if (wpsInstance) {
+    try {
+      if (wpsInstance.destroy) {
+        wpsInstance.destroy();
+      }
+      wpsInstance = null;
+      console.log('[WPS] 编辑器已销毁');
+    } catch (err) {
+      console.error('[WPS] 销毁编辑器失败:', err);
     }
+  }
 };
 
 // 监听文件 URL 变化
-watch(() => props.fileUrl, (newUrl) => {
+watch(
+  () => props.fileUrl,
+  (newUrl) => {
     if (newUrl) {
-        destroyEditor();
-        initWpsEditor();
+      destroyEditor();
+      initWpsEditor();
     }
-});
+  }
+);
 
 onMounted(() => {
-    // 等待 WPS SDK 加载完成
-    let checkCount = 0;
-    const maxChecks = 100; // 10秒超时(100 * 100ms)
-    
-    const checkWpsSDK = setInterval(() => {
-        checkCount++;
-        
-        if ((window as any).WebOfficeSDK) {
-            clearInterval(checkWpsSDK);
-            console.log('[WPS] SDK 加载成功');
-            if (props.fileUrl) {
-                initWpsEditor();
-            }
-        } else if (checkCount >= maxChecks) {
-            clearInterval(checkWpsSDK);
-            console.error('[WPS] SDK 加载超时');
-            error.value = 'WPS SDK 加载超时。请检查:\n1. 网络连接是否正常\n2. index.html 中的 SDK 脚本是否正确引入\n3. SDK CDN 地址是否可访问';
-            loading.value = false;
-            emit('error', error.value);
-        }
-    }, 100);
+  // 等待 WPS SDK 加载完成
+  let checkCount = 0;
+  const maxChecks = 100; // 10秒超时(100 * 100ms)
+
+  const checkWpsSDK = setInterval(() => {
+    checkCount++;
+
+    if ((window as any).WebOfficeSDK) {
+      clearInterval(checkWpsSDK);
+      console.log('[WPS] SDK 加载成功');
+      if (props.fileUrl) {
+        initWpsEditor();
+      }
+    } else if (checkCount >= maxChecks) {
+      clearInterval(checkWpsSDK);
+      console.error('[WPS] SDK 加载超时');
+      error.value = 'WPS SDK 加载超时。请检查:\n1. 网络连接是否正常\n2. index.html 中的 SDK 脚本是否正确引入\n3. SDK CDN 地址是否可访问';
+      loading.value = false;
+      emit('error', error.value);
+    }
+  }, 100);
 });
 
 onBeforeUnmount(() => {
-    destroyEditor();
+  destroyEditor();
 });
 
 // 暴露方法
 defineExpose({
-    saveDocument,
-    destroyEditor
+  saveDocument,
+  destroyEditor
 });
 </script>
 
 <style scoped lang="scss">
 .wps-editor-container {
+  width: 100%;
+  height: 100%;
+  position: relative;
+
+  .loading-container,
+  .error-container {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    z-index: 10;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    gap: 10px;
+
+    .el-icon {
+      font-size: 32px;
+      color: #409eff;
+    }
+  }
+
+  .wps-container {
     width: 100%;
     height: 100%;
-    position: relative;
-    
-    .loading-container,
-    .error-container {
-        position: absolute;
-        top: 50%;
-        left: 50%;
-        transform: translate(-50%, -50%);
-        z-index: 10;
-        display: flex;
-        flex-direction: column;
-        align-items: center;
-        gap: 10px;
-        
-        .el-icon {
-            font-size: 32px;
-            color: #409eff;
-        }
-    }
-    
-    .wps-container {
-        width: 100%;
-        height: 100%;
-    }
+  }
 }
 </style>

+ 52 - 52
src/enums/documentStatus.ts

@@ -2,69 +2,69 @@
  * 文档状态枚举
  */
 export enum DocumentStatus {
-    /** 未上传 */
-    UN_UPLOAD = 0,
-    /** 待审核 */
-    UN_AUDIT = 1,
-    /** 审核拒绝 */
-    AUDIT_REJECT = 2,
-    /** 待归档 */
-    UN_FILING = 3,
-    /** 已归档 */
-    FILING = 4,
-    /** 待质控 */
-    UN_QUALITY_CONTROL = 5,
-    /** 质控通过 */
-    QUALITY_CONTROL_PASS = 6,
-    /** 质控拒绝 */
-    QUALITY_CONTROL_REJECT = 7
+  /** 未上传 */
+  UN_UPLOAD = 0,
+  /** 待审核 */
+  UN_AUDIT = 1,
+  /** 审核拒绝 */
+  AUDIT_REJECT = 2,
+  /** 待归档 */
+  UN_FILING = 3,
+  /** 已归档 */
+  FILING = 4,
+  /** 待质控 */
+  UN_QUALITY_CONTROL = 5,
+  /** 质控通过 */
+  QUALITY_CONTROL_PASS = 6,
+  /** 质控拒绝 */
+  QUALITY_CONTROL_REJECT = 7
 }
 
 /**
  * 文档状态配置
  */
 export interface DocumentStatusConfig {
-    label: string;
-    type?: 'success' | 'warning' | 'danger' | 'info' | '';
-    color?: string;
+  label: string;
+  type?: 'success' | 'warning' | 'danger' | 'info' | '';
+  color?: string;
 }
 
 /**
  * 文档状态配置映射
  */
 export const DOCUMENT_STATUS_CONFIG: Record<DocumentStatus, DocumentStatusConfig> = {
-    [DocumentStatus.UN_UPLOAD]: {
-        label: 'document.document.documentList.statusOptions.unUpload',
-        type: 'info'
-    },
-    [DocumentStatus.UN_AUDIT]: {
-        label: 'document.document.documentList.statusOptions.unAudit',
-        type: 'warning'
-    },
-    [DocumentStatus.AUDIT_REJECT]: {
-        label: 'document.document.documentList.statusOptions.auditReject',
-        type: 'danger'
-    },
-    [DocumentStatus.UN_FILING]: {
-        label: 'document.document.documentList.statusOptions.unFiling',
-        type: ''
-    },
-    [DocumentStatus.FILING]: {
-        label: 'document.document.documentList.statusOptions.filing',
-        type: 'success'
-    },
-    [DocumentStatus.UN_QUALITY_CONTROL]: {
-        label: 'document.document.documentList.statusOptions.unQualityControl',
-        color: '#E6A23C'
-    },
-    [DocumentStatus.QUALITY_CONTROL_PASS]: {
-        label: 'document.document.documentList.statusOptions.qualityControlPass',
-        color: '#67C23A'
-    },
-    [DocumentStatus.QUALITY_CONTROL_REJECT]: {
-        label: 'document.document.documentList.statusOptions.qualityControlReject',
-        color: '#F56C6C'
-    }
+  [DocumentStatus.UN_UPLOAD]: {
+    label: 'document.document.documentList.statusOptions.unUpload',
+    type: 'info'
+  },
+  [DocumentStatus.UN_AUDIT]: {
+    label: 'document.document.documentList.statusOptions.unAudit',
+    type: 'warning'
+  },
+  [DocumentStatus.AUDIT_REJECT]: {
+    label: 'document.document.documentList.statusOptions.auditReject',
+    type: 'danger'
+  },
+  [DocumentStatus.UN_FILING]: {
+    label: 'document.document.documentList.statusOptions.unFiling',
+    type: ''
+  },
+  [DocumentStatus.FILING]: {
+    label: 'document.document.documentList.statusOptions.filing',
+    type: 'success'
+  },
+  [DocumentStatus.UN_QUALITY_CONTROL]: {
+    label: 'document.document.documentList.statusOptions.unQualityControl',
+    color: '#E6A23C'
+  },
+  [DocumentStatus.QUALITY_CONTROL_PASS]: {
+    label: 'document.document.documentList.statusOptions.qualityControlPass',
+    color: '#67C23A'
+  },
+  [DocumentStatus.QUALITY_CONTROL_REJECT]: {
+    label: 'document.document.documentList.statusOptions.qualityControlReject',
+    color: '#F56C6C'
+  }
 };
 
 /**
@@ -73,5 +73,5 @@ export const DOCUMENT_STATUS_CONFIG: Record<DocumentStatus, DocumentStatusConfig
  * @returns 状态配置
  */
 export const getDocumentStatusConfig = (status: number): DocumentStatusConfig | null => {
-    return DOCUMENT_STATUS_CONFIG[status as DocumentStatus] || null;
+  return DOCUMENT_STATUS_CONFIG[status as DocumentStatus] || null;
 };

+ 9 - 3
src/lang/modules/document/document/en_US.ts

@@ -129,6 +129,8 @@ export default {
     planDocument: 'Plan Document',
     submitter: 'Submitter',
     submitterPlaceholder: 'Search member by nickname',
+    planSubmitter: 'Plan Submitter',
+    planSubmitterPlaceholder: 'Search member by nickname',
     submitDeadline: 'Submit Deadline',
     submitDeadlinePlaceholder: 'Please select submit deadline',
     planType: 'Plan Document Type',
@@ -144,6 +146,7 @@ export default {
   documentRule: {
     nameRequired: 'Please enter name',
     submitterRequired: 'Please select submitter',
+    planSubmitterRequired: 'Please select plan submitter',
     fileRequired: 'Please upload file'
   },
   // Mark Form
@@ -190,6 +193,7 @@ export default {
     planDocumentType: 'Plan Document Type',
     status: 'Status',
     submitter: 'Submitter',
+    planSubmitter: 'Plan Submitter',
     submitDeadline: 'Submit Deadline',
     submitTime: 'Submit Time',
     url: 'Actual Document',
@@ -241,7 +245,9 @@ export default {
     copySuccess: 'Audit information copied to clipboard',
     copyFailed: 'Copy failed: {error}',
     unknownError: 'Unknown error',
-    usageHint: '<div style="text-align: left;"><p style="margin: 0 0 8px 0; font-weight: bold;">Please follow these steps:</p><p style="margin: 0 0 4px 0;">• <strong>PDF</strong>: Use annotation tool → Select image → Paste</p><p style="margin: 0 0 4px 0;">• <strong>Word/Excel/PPT</strong>: Press Ctrl+V to paste</p><p style="margin: 0;">• Or drag and drop external images into the document</p></div>',
-    browserNotSupported: '<div style="text-align: left;"><p style="margin: 0 0 4px 0;">Browser does not support copying images, please try:</p><p style="margin: 0 0 4px 0;">1. Use Chrome or Edge browser</p><p style="margin: 0;">2. Or use WPS insert image function</p></div>'
+    usageHint:
+      '<div style="text-align: left;"><p style="margin: 0 0 8px 0; font-weight: bold;">Please follow these steps:</p><p style="margin: 0 0 4px 0;">• <strong>PDF</strong>: Use annotation tool → Select image → Paste</p><p style="margin: 0 0 4px 0;">• <strong>Word/Excel/PPT</strong>: Press Ctrl+V to paste</p><p style="margin: 0;">• Or drag and drop external images into the document</p></div>',
+    browserNotSupported:
+      '<div style="text-align: left;"><p style="margin: 0 0 4px 0;">Browser does not support copying images, please try:</p><p style="margin: 0 0 4px 0;">1. Use Chrome or Edge browser</p><p style="margin: 0;">2. Or use WPS insert image function</p></div>'
   }
-};
+};

+ 9 - 3
src/lang/modules/document/document/zh_CN.ts

@@ -129,6 +129,8 @@ export default {
     planDocument: '计划文档',
     submitter: '递交人',
     submitterPlaceholder: '请输入成员昵称搜索',
+    planSubmitter: '计划递交人',
+    planSubmitterPlaceholder: '请输入成员昵称搜索',
     submitDeadline: '递交截止日期',
     submitDeadlinePlaceholder: '请选择递交截止日期',
     planType: '计划文件类型',
@@ -144,6 +146,7 @@ export default {
   documentRule: {
     nameRequired: '请输入名称',
     submitterRequired: '请选择递交人',
+    planSubmitterRequired: '请选择计划递交人',
     fileRequired: '请上传文件'
   },
   // 标识表单
@@ -189,7 +192,8 @@ export default {
     specification: '文档标识',
     planDocumentType: '计划文件类型',
     status: '状态',
-    submitter: '计划递交人',
+    submitter: '递交人',
+    planSubmitter: '计划递交人',
     submitDeadline: '递交截止日期',
     submitTime: '递交时间',
     url: '实际文档',
@@ -241,7 +245,9 @@ export default {
     copySuccess: '审核信息已复制到剪贴板',
     copyFailed: '复制失败: {error}',
     unknownError: '未知错误',
-    usageHint: '<div style="text-align: left;"><p style="margin: 0 0 8px 0; font-weight: bold;">请按以下步骤操作:</p><p style="margin: 0 0 4px 0;">• <strong>PDF</strong>:使用批注工具 → 选择图片 → 粘贴</p><p style="margin: 0 0 4px 0;">• <strong>Word/Excel/PPT</strong>:在文档中按 Ctrl+V 粘贴</p><p style="margin: 0;">• 或者直接拖拽外部图片到文档中</p></div>',
-    browserNotSupported: '<div style="text-align: left;"><p style="margin: 0 0 4px 0;">浏览器不支持复制图片,请尝试以下方法:</p><p style="margin: 0 0 4px 0;">1. 使用 Chrome 或 Edge 浏览器</p><p style="margin: 0;">2. 或者使用 WPS 的插入图片功能</p></div>'
+    usageHint:
+      '<div style="text-align: left;"><p style="margin: 0 0 8px 0; font-weight: bold;">请按以下步骤操作:</p><p style="margin: 0 0 4px 0;">• <strong>PDF</strong>:使用批注工具 → 选择图片 → 粘贴</p><p style="margin: 0 0 4px 0;">• <strong>Word/Excel/PPT</strong>:在文档中按 Ctrl+V 粘贴</p><p style="margin: 0;">• 或者直接拖拽外部图片到文档中</p></div>',
+    browserNotSupported:
+      '<div style="text-align: left;"><p style="margin: 0 0 4px 0;">浏览器不支持复制图片,请尝试以下方法:</p><p style="margin: 0 0 4px 0;">1. 使用 Chrome 或 Edge 浏览器</p><p style="margin: 0;">2. 或者使用 WPS 的插入图片功能</p></div>'
   }
 };

+ 1 - 1
src/lang/modules/home/index.ts

@@ -2,5 +2,5 @@
 import taskCenter from './taskCenter/zh_CN';
 
 export default {
-    taskCenter
+  taskCenter
 };

+ 1 - 1
src/lang/modules/home/index_en.ts

@@ -2,5 +2,5 @@
 import taskCenter from './taskCenter/en_US';
 
 export default {
-    taskCenter
+  taskCenter
 };

+ 137 - 141
src/lang/modules/home/taskCenter/en_US.ts

@@ -1,146 +1,142 @@
 // Task Center Module - English Translation
 export default {
-    // Audit Task
-    audit: {
-        title: 'File Approval Task',
-        button: {
-            search: 'Search',
-            reset: 'Reset',
-            download: 'Download'
-        },
-        search: {
-            projectCode: 'Project Code',
-            projectCodePlaceholder: 'Please enter project code',
-            projectName: 'Project Name',
-            projectNamePlaceholder: 'Please enter project name',
-            name: 'Name',
-            namePlaceholder: 'Please enter name',
-            search: 'Search',
-            reset: 'Reset'
-        },
-        table: {
-            id: 'No.',
-            name: 'Name',
-            type: 'Type',
-            normalDocument: 'Normal Document',
-            planDocument: 'Plan Document',
-            documentType: 'Plan Document Type',
-            status: 'Status',
-            submitter: 'Submitter',
-            deadline: 'Deadline',
-            submitTime: 'Submit Time',
-            createTime: 'Create Time',
-            action: 'Action',
-            audit: 'Audit',
-            download: 'Download'
-        },
-        dialog: {
-            title: 'Audit Document',
-            result: 'Audit Result',
-            pass: 'Pass',
-            reject: 'Reject',
-            rejectReason: 'Rejection Reason',
-            rejectReasonPlaceholder: 'Please enter rejection reason',
-            confirm: 'Confirm Audit',
-            cancel: 'Cancel'
-        },
-        message: {
-            getListFailed: 'Failed to get task list',
-            auditSuccess: 'Audit successfully',
-            auditFailed: 'Audit failed',
-            noFileToDownload: 'No file available for download'
-        },
-        rule: {
-            resultRequired: 'Please select audit result',
-            rejectReasonRequired: 'Please enter rejection reason'
-        },
-        dialog: {
-            auditLog: 'Audit Log'
-        },
-        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',
-            operation: 'Operation'
-        }
+  // Audit Task
+  audit: {
+    title: 'File Approval Task',
+    button: {
+      search: 'Search',
+      reset: 'Reset',
+      download: 'Download'
     },
-    // Submission Task
-    submission: {
-        title: 'File Submission Task',
-        button: {
-            search: 'Search',
-            reset: 'Reset',
-            download: 'Download'
-        },
-        search: {
-            name: 'Name',
-            namePlaceholder: 'Please enter name',
-            projectCode: 'Project Code',
-            projectCodePlaceholder: 'Please enter project code',
-            projectName: 'Project Name',
-            projectNamePlaceholder: 'Please enter project name',
-            status: 'Status',
-            statusPlaceholder: 'Please select status',
-            unSubmit: 'Un Submit',
-            unAudit: 'Un Audit',
-            auditReject: 'Audit Reject',
-            search: 'Search',
-            reset: 'Reset'
-        },
-        table: {
-            id: 'No.',
-            name: 'Name',
-            type: 'Type',
-            normalDocument: 'Normal Document',
-            planDocument: 'Plan Document',
-            documentType: 'Plan Document Type',
-            status: 'Status',
-            submitter: 'Submitter',
-            deadline: 'Deadline',
-            overdueDays: 'Overdue Days',
-            submitTime: 'Submit Time',
-            createTime: 'Create Time',
-            action: 'Action',
-            submit: 'Submit'
-        },
-        dialog: {
-            title: 'Submit Document',
-            file: 'File',
-            confirm: 'Confirm Submit',
-            cancel: 'Cancel'
-        },
-        message: {
-            getListFailed: 'Failed to get task list',
-            submitSuccess: 'Submit successfully',
-            submitFailed: 'Submit failed'
-        },
-        rule: {
-            fileRequired: 'Please upload file'
-        },
-        dialog: {
-            auditLog: 'Audit Log'
-        },
-        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',
-            operation: 'Operation'
-        }
+    search: {
+      projectCode: 'Project Code',
+      projectCodePlaceholder: 'Please enter project code',
+      projectName: 'Project Name',
+      projectNamePlaceholder: 'Please enter project name',
+      name: 'Name',
+      namePlaceholder: 'Please enter name',
+      search: 'Search',
+      reset: 'Reset'
+    },
+    table: {
+      id: 'No.',
+      name: 'Name',
+      type: 'Type',
+      normalDocument: 'Normal Document',
+      planDocument: 'Plan Document',
+      documentType: 'Plan Document Type',
+      status: 'Status',
+      submitter: 'Submitter',
+      deadline: 'Deadline',
+      submitTime: 'Submit Time',
+      createTime: 'Create Time',
+      action: 'Action',
+      audit: 'Audit',
+      download: 'Download'
+    },
+    dialog: {
+      title: 'Audit Document',
+      result: 'Audit Result',
+      pass: 'Pass',
+      reject: 'Reject',
+      rejectReason: 'Rejection Reason',
+      rejectReasonPlaceholder: 'Please enter rejection reason',
+      confirm: 'Confirm Audit',
+      cancel: 'Cancel'
+    },
+    message: {
+      getListFailed: 'Failed to get task list',
+      auditSuccess: 'Audit successfully',
+      auditFailed: 'Audit failed',
+      noFileToDownload: 'No file available for download'
+    },
+    rule: {
+      resultRequired: 'Please select audit result',
+      rejectReasonRequired: 'Please enter rejection reason'
+    },
+    auditLog: {
+      title: 'Audit Log',
+      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',
+      operation: 'Operation'
+    }
+  },
+  // Submission Task
+  submission: {
+    title: 'File Submission Task',
+    button: {
+      search: 'Search',
+      reset: 'Reset',
+      download: 'Download'
+    },
+    search: {
+      name: 'Name',
+      namePlaceholder: 'Please enter name',
+      projectCode: 'Project Code',
+      projectCodePlaceholder: 'Please enter project code',
+      projectName: 'Project Name',
+      projectNamePlaceholder: 'Please enter project name',
+      status: 'Status',
+      statusPlaceholder: 'Please select status',
+      unSubmit: 'Un Submit',
+      unAudit: 'Un Audit',
+      auditReject: 'Audit Reject',
+      search: 'Search',
+      reset: 'Reset'
+    },
+    table: {
+      id: 'No.',
+      name: 'Name',
+      type: 'Type',
+      normalDocument: 'Normal Document',
+      planDocument: 'Plan Document',
+      documentType: 'Plan Document Type',
+      status: 'Status',
+      submitter: 'Submitter',
+      deadline: 'Deadline',
+      overdueDays: 'Overdue Days',
+      submitTime: 'Submit Time',
+      createTime: 'Create Time',
+      action: 'Action',
+      submit: 'Submit'
+    },
+    dialog: {
+      title: 'Submit Document',
+      file: 'File',
+      confirm: 'Confirm Submit',
+      cancel: 'Cancel'
+    },
+    message: {
+      getListFailed: 'Failed to get task list',
+      submitSuccess: 'Submit successfully',
+      submitFailed: 'Submit failed'
+    },
+    rule: {
+      fileRequired: 'Please upload file'
+    },
+    auditLog: {
+      title: 'Audit Log',
+      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',
+      operation: 'Operation'
     }
+  }
 };

+ 138 - 138
src/lang/modules/home/taskCenter/zh_CN.ts

@@ -1,143 +1,143 @@
 // 任务中心模块 - 中文翻译
 export default {
-    // 审核任务
-    audit: {
-        title: '文件审批任务',
-        button: {
-            search: '搜索',
-            reset: '重置',
-            download: '下载'
-        },
-        search: {
-            projectCode: '项目编号',
-            projectCodePlaceholder: '请输入项目编号',
-            projectName: '项目名称',
-            projectNamePlaceholder: '请输入项目名称',
-            name: '名称',
-            namePlaceholder: '请输入名称',
-            search: '搜索',
-            reset: '重置'
-        },
-        table: {
-            id: '序号',
-            name: '名称',
-            type: '类型',
-            normalDocument: '非计划文档',
-            planDocument: '计划文档',
-            documentType: '计划文档类型',
-            status: '状态',
-            submitter: '递交人',
-            deadline: '截止时间',
-            submitTime: '递交时间',
-            createTime: '创建时间',
-            action: '操作',
-            audit: '审核',
-            download: '下载'
-        },
-        dialog: {
-            title: '审核文档',
-            result: '审批结果',
-            pass: '通过',
-            reject: '拒绝',
-            rejectReason: '拒绝原因',
-            rejectReasonPlaceholder: '请输入拒绝原因',
-            confirm: '确认审批',
-            cancel: '取消'
-        },
-        message: {
-            getListFailed: '获取任务列表失败',
-            auditSuccess: '审批成功',
-            auditFailed: '审批失败',
-            noFileToDownload: '暂无文件可下载'
-        },
-        rule: {
-            resultRequired: '请选择审批结果',
-            rejectReasonRequired: '请输入拒绝原因'
-        },
-        auditLog: {
-            result: '审核结果',
-            selectResult: '请选择审核结果',
-            pass: '通过',
-            reject: '驳回',
-            auditTime: '审核时间',
-            startTime: '开始时间',
-            endTime: '结束时间',
-            documentName: '文档名称',
-            auditorType: '审核人类型',
-            auditorName: '审核人',
-            rejectReason: '驳回理由',
-            operation: '操作'
-        }
+  // 审核任务
+  audit: {
+    title: '文件审批任务',
+    button: {
+      search: '搜索',
+      reset: '重置',
+      download: '下载'
     },
-    // 递交任务
-    submission: {
-        title: '文件递交任务',
-        button: {
-            search: '搜索',
-            reset: '重置',
-            download: '下载'
-        },
-        search: {
-            name: '名称',
-            namePlaceholder: '请输入名称',
-            projectCode: '项目编号',
-            projectCodePlaceholder: '请输入项目编号',
-            projectName: '项目名称',
-            projectNamePlaceholder: '请输入项目名称',
-            status: '状态',
-            statusPlaceholder: '请选择状态',
-            unSubmit: '未递交',
-            unAudit: '待审核',
-            auditReject: '审核拒绝',
-            search: '搜索',
-            reset: '重置'
-        },
-        table: {
-            id: '序号',
-            name: '名称',
-            type: '类型',
-            normalDocument: '非计划文档',
-            planDocument: '计划文档',
-            documentType: '计划文档类型',
-            status: '状态',
-            submitter: '递交人',
-            deadline: '截止时间',
-            overdueDays: '递交逾期天数',
-            submitTime: '递交时间',
-            createTime: '创建时间',
-            action: '操作',
-            submit: '递交'
-        },
-        dialog: {
-            title: '递交文档',
-            file: '文件',
-            confirm: '确认递交',
-            cancel: '取消'
-        },
-        message: {
-            getListFailed: '获取任务列表失败',
-            submitSuccess: '递交成功',
-            submitFailed: '递交失败'
-        },
-        rule: {
-            fileRequired: '请上传文件'
-        },
-        dialog: {
-            auditLog: '审核记录'
-        },
-        auditLog: {
-            result: '审核结果',
-            selectResult: '请选择审核结果',
-            pass: '通过',
-            reject: '驳回',
-            auditTime: '审核时间',
-            startTime: '开始时间',
-            endTime: '结束时间',
-            documentName: '文档名称',
-            auditorType: '审核人类型',
-            auditorName: '审核人',
-            rejectReason: '驳回理由',
-            operation: '操作'
-        }
+    search: {
+      projectCode: '项目编号',
+      projectCodePlaceholder: '请输入项目编号',
+      projectName: '项目名称',
+      projectNamePlaceholder: '请输入项目名称',
+      name: '名称',
+      namePlaceholder: '请输入名称',
+      search: '搜索',
+      reset: '重置'
+    },
+    table: {
+      id: '序号',
+      name: '名称',
+      type: '类型',
+      normalDocument: '非计划文档',
+      planDocument: '计划文档',
+      documentType: '计划文档类型',
+      status: '状态',
+      submitter: '递交人',
+      deadline: '截止时间',
+      submitTime: '递交时间',
+      createTime: '创建时间',
+      action: '操作',
+      audit: '审核',
+      download: '下载'
+    },
+    dialog: {
+      title: '审核文档',
+      result: '审批结果',
+      pass: '通过',
+      reject: '拒绝',
+      rejectReason: '拒绝原因',
+      rejectReasonPlaceholder: '请输入拒绝原因',
+      confirm: '确认审批',
+      cancel: '取消'
+    },
+    message: {
+      getListFailed: '获取任务列表失败',
+      auditSuccess: '审批成功',
+      auditFailed: '审批失败',
+      noFileToDownload: '暂无文件可下载'
+    },
+    rule: {
+      resultRequired: '请选择审批结果',
+      rejectReasonRequired: '请输入拒绝原因'
+    },
+    auditLog: {
+      result: '审核结果',
+      selectResult: '请选择审核结果',
+      pass: '通过',
+      reject: '驳回',
+      auditTime: '审核时间',
+      startTime: '开始时间',
+      endTime: '结束时间',
+      documentName: '文档名称',
+      auditorType: '审核人类型',
+      auditorName: '审核人',
+      rejectReason: '驳回理由',
+      operation: '操作'
+    }
+  },
+  // 递交任务
+  submission: {
+    title: '文件递交任务',
+    button: {
+      search: '搜索',
+      reset: '重置',
+      download: '下载'
+    },
+    search: {
+      name: '名称',
+      namePlaceholder: '请输入名称',
+      projectCode: '项目编号',
+      projectCodePlaceholder: '请输入项目编号',
+      projectName: '项目名称',
+      projectNamePlaceholder: '请输入项目名称',
+      status: '状态',
+      statusPlaceholder: '请选择状态',
+      unSubmit: '未递交',
+      unAudit: '待审核',
+      auditReject: '审核拒绝',
+      search: '搜索',
+      reset: '重置'
+    },
+    table: {
+      id: '序号',
+      name: '名称',
+      type: '类型',
+      normalDocument: '非计划文档',
+      planDocument: '计划文档',
+      documentType: '计划文档类型',
+      status: '状态',
+      submitter: '递交人',
+      deadline: '截止时间',
+      overdueDays: '递交逾期天数',
+      submitTime: '递交时间',
+      createTime: '创建时间',
+      action: '操作',
+      submit: '递交'
+    },
+    dialog: {
+      title: '递交文档',
+      file: '文件',
+      confirm: '确认递交',
+      cancel: '取消'
+    },
+    message: {
+      getListFailed: '获取任务列表失败',
+      submitSuccess: '递交成功',
+      submitFailed: '递交失败'
+    },
+    rule: {
+      fileRequired: '请上传文件'
+    },
+    dialog: {
+      auditLog: '审核记录'
+    },
+    auditLog: {
+      result: '审核结果',
+      selectResult: '请选择审核结果',
+      pass: '通过',
+      reject: '驳回',
+      auditTime: '审核时间',
+      startTime: '开始时间',
+      endTime: '结束时间',
+      documentName: '文档名称',
+      auditorType: '审核人类型',
+      auditorName: '审核人',
+      rejectReason: '驳回理由',
+      operation: '操作'
     }
+  }
 };

+ 56 - 56
src/lang/modules/search/en_US.ts

@@ -1,59 +1,59 @@
 // Search Module - English Translation
 export default {
-    title: 'Document Search',
-    search: {
-        name: 'Document Name',
-        namePlaceholder: 'Please enter document name',
-        projectCode: 'Project Code',
-        projectCodePlaceholder: 'Please enter project code',
-        projectName: 'Project Name',
-        projectNamePlaceholder: 'Please enter project name',
-        type: 'Type',
-        typePlaceholder: 'Please select type',
-        status: 'Status',
-        statusPlaceholder: 'Please select status',
-        createTime: 'Create Time',
-        updateTime: 'Update Time',
-        startTime: 'Start Time',
-        endTime: 'End Time'
-    },
-    button: {
-        search: 'Search',
-        reset: 'Reset',
-        download: 'Download'
-    },
-    table: {
-        id: 'No.',
-        folderName: 'Folder',
-        name: 'Document Name',
-        type: 'Type',
-        specification: 'Specification',
-        planDocumentType: 'Plan Document Type',
-        status: 'Status',
-        submitter: 'Submitter',
-        submitDeadline: 'Submit Deadline',
-        submitTime: 'Submit Time',
-        fileName: 'File Name',
-        note: 'Note',
-        createTime: 'Create Time',
-        updateTime: 'Update Time',
-        action: 'Action'
-    },
-    type: {
-        normalDocument: 'Normal Document',
-        planDocument: 'Plan Document'
-    },
-    status: {
-        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'
-    },
-    message: {
-        fetchFailed: 'Failed to get document list'
-    }
+  title: 'Document Search',
+  search: {
+    name: 'Document Name',
+    namePlaceholder: 'Please enter document name',
+    projectCode: 'Project Code',
+    projectCodePlaceholder: 'Please enter project code',
+    projectName: 'Project Name',
+    projectNamePlaceholder: 'Please enter project name',
+    type: 'Type',
+    typePlaceholder: 'Please select type',
+    status: 'Status',
+    statusPlaceholder: 'Please select status',
+    createTime: 'Create Time',
+    updateTime: 'Update Time',
+    startTime: 'Start Time',
+    endTime: 'End Time'
+  },
+  button: {
+    search: 'Search',
+    reset: 'Reset',
+    download: 'Download'
+  },
+  table: {
+    id: 'No.',
+    folderName: 'Folder',
+    name: 'Document Name',
+    type: 'Type',
+    specification: 'Specification',
+    planDocumentType: 'Plan Document Type',
+    status: 'Status',
+    submitter: 'Submitter',
+    submitDeadline: 'Submit Deadline',
+    submitTime: 'Submit Time',
+    fileName: 'File Name',
+    note: 'Note',
+    createTime: 'Create Time',
+    updateTime: 'Update Time',
+    action: 'Action'
+  },
+  type: {
+    normalDocument: 'Normal Document',
+    planDocument: 'Plan Document'
+  },
+  status: {
+    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'
+  },
+  message: {
+    fetchFailed: 'Failed to get document list'
+  }
 };

+ 56 - 56
src/lang/modules/search/zh_CN.ts

@@ -1,59 +1,59 @@
 // 搜索模块 - 中文翻译
 export default {
-    title: '文档搜索',
-    search: {
-        name: '文档名称',
-        namePlaceholder: '请输入文档名称',
-        projectCode: '项目编号',
-        projectCodePlaceholder: '请输入项目编号',
-        projectName: '项目名称',
-        projectNamePlaceholder: '请输入项目名称',
-        type: '类型',
-        typePlaceholder: '请选择类型',
-        status: '状态',
-        statusPlaceholder: '请选择状态',
-        createTime: '创建时间',
-        updateTime: '更新时间',
-        startTime: '开始时间',
-        endTime: '结束时间'
-    },
-    button: {
-        search: '搜索',
-        reset: '重置',
-        download: '下载'
-    },
-    table: {
-        id: '序号',
-        folderName: '文件夹',
-        name: '文档名称',
-        type: '类型',
-        specification: '文档标识',
-        planDocumentType: '计划文档类型',
-        status: '状态',
-        submitter: '递交人',
-        submitDeadline: '递交截止日期',
-        submitTime: '递交时间',
-        fileName: '文件名',
-        note: '备注',
-        createTime: '创建时间',
-        updateTime: '更新时间',
-        action: '操作'
-    },
-    type: {
-        normalDocument: '非计划文档',
-        planDocument: '计划文档'
-    },
-    status: {
-        unUpload: '未上传',
-        unAudit: '待审核',
-        auditReject: '审核拒绝',
-        unFiling: '待归档',
-        filing: '已归档',
-        unQualityControl: '待质控',
-        qualityControlPass: '质控通过',
-        qualityControlReject: '质控拒绝'
-    },
-    message: {
-        fetchFailed: '获取文档列表失败'
-    }
+  title: '文档搜索',
+  search: {
+    name: '文档名称',
+    namePlaceholder: '请输入文档名称',
+    projectCode: '项目编号',
+    projectCodePlaceholder: '请输入项目编号',
+    projectName: '项目名称',
+    projectNamePlaceholder: '请输入项目名称',
+    type: '类型',
+    typePlaceholder: '请选择类型',
+    status: '状态',
+    statusPlaceholder: '请选择状态',
+    createTime: '创建时间',
+    updateTime: '更新时间',
+    startTime: '开始时间',
+    endTime: '结束时间'
+  },
+  button: {
+    search: '搜索',
+    reset: '重置',
+    download: '下载'
+  },
+  table: {
+    id: '序号',
+    folderName: '文件夹',
+    name: '文档名称',
+    type: '类型',
+    specification: '文档标识',
+    planDocumentType: '计划文档类型',
+    status: '状态',
+    submitter: '递交人',
+    submitDeadline: '递交截止日期',
+    submitTime: '递交时间',
+    fileName: '文件名',
+    note: '备注',
+    createTime: '创建时间',
+    updateTime: '更新时间',
+    action: '操作'
+  },
+  type: {
+    normalDocument: '非计划文档',
+    planDocument: '计划文档'
+  },
+  status: {
+    unUpload: '未上传',
+    unAudit: '待审核',
+    auditReject: '审核拒绝',
+    unFiling: '待归档',
+    filing: '已归档',
+    unQualityControl: '待质控',
+    qualityControlPass: '质控通过',
+    qualityControlReject: '质控拒绝'
+  },
+  message: {
+    fetchFailed: '获取文档列表失败'
+  }
 };

+ 28 - 28
src/lang/modules/setting/agreement/en_US.ts

@@ -1,36 +1,36 @@
 // Agreement Settings Module - English Translation
 export default {
-    // Page Title
-    title: 'Agreement Settings',
+  // Page Title
+  title: 'Agreement Settings',
 
-    // Tabs
-    tab: {
-        userAgreement: 'User Agreement',
-        privacyAgreement: 'Privacy Agreement'
-    },
+  // Tabs
+  tab: {
+    userAgreement: 'User Agreement',
+    privacyAgreement: 'Privacy Agreement'
+  },
 
-    // Language Labels
-    lang: {
-        chinese: 'Chinese',
-        english: 'English'
-    },
+  // Language Labels
+  lang: {
+    chinese: 'Chinese',
+    english: 'English'
+  },
 
-    // Buttons
-    button: {
-        save: 'Save'
-    },
+  // Buttons
+  button: {
+    save: 'Save'
+  },
 
-    // Messages
-    message: {
-        saveSuccess: 'Saved successfully',
-        saveFailed: 'Save failed',
-        fetchFailed: 'Failed to fetch data',
-        noData: 'No data available'
-    },
+  // Messages
+  message: {
+    saveSuccess: 'Saved successfully',
+    saveFailed: 'Save failed',
+    fetchFailed: 'Failed to fetch data',
+    noData: 'No data available'
+  },
 
-    // Editor Placeholders
-    editor: {
-        userAgreementPlaceholder: 'Please enter user agreement content',
-        privacyAgreementPlaceholder: 'Please enter privacy agreement content'
-    }
+  // Editor Placeholders
+  editor: {
+    userAgreementPlaceholder: 'Please enter user agreement content',
+    privacyAgreementPlaceholder: 'Please enter privacy agreement content'
+  }
 };

+ 28 - 28
src/lang/modules/setting/agreement/zh_CN.ts

@@ -1,36 +1,36 @@
 // 协议设置模块 - 中文翻译
 export default {
-    // 页面标题
-    title: '协议设置',
+  // 页面标题
+  title: '协议设置',
 
-    // 标签页
-    tab: {
-        userAgreement: '用户协议',
-        privacyAgreement: '隐私协议'
-    },
+  // 标签页
+  tab: {
+    userAgreement: '用户协议',
+    privacyAgreement: '隐私协议'
+  },
 
-    // 语言标签
-    lang: {
-        chinese: '中文',
-        english: '英文'
-    },
+  // 语言标签
+  lang: {
+    chinese: '中文',
+    english: '英文'
+  },
 
-    // 按钮
-    button: {
-        save: '保存'
-    },
+  // 按钮
+  button: {
+    save: '保存'
+  },
 
-    // 消息
-    message: {
-        saveSuccess: '保存成功',
-        saveFailed: '保存失败',
-        fetchFailed: '获取数据失败',
-        noData: '暂无数据'
-    },
+  // 消息
+  message: {
+    saveSuccess: '保存成功',
+    saveFailed: '保存失败',
+    fetchFailed: '获取数据失败',
+    noData: '暂无数据'
+  },
 
-    // 编辑器占位符
-    editor: {
-        userAgreementPlaceholder: '请输入用户协议内容',
-        privacyAgreementPlaceholder: '请输入隐私协议内容'
-    }
+  // 编辑器占位符
+  editor: {
+    userAgreementPlaceholder: '请输入用户协议内容',
+    privacyAgreementPlaceholder: '请输入隐私协议内容'
+  }
 };

+ 19 - 19
src/lang/modules/setting/ai/en_US.ts

@@ -1,22 +1,22 @@
 // AI Settings Module - English Translation
 export default {
-    title: 'AI Settings',
-    form: {
-        enabledFlag: 'Enabled Status',
-        enabled: 'Enabled',
-        disabled: 'Disabled'
-    },
-    button: {
-        save: 'Save',
-        reset: 'Reset'
-    },
-    message: {
-        noData: 'No Data',
-        fetchFailed: 'Failed to get AI settings',
-        saveSuccess: 'Save successfully',
-        saveFailed: 'Save failed'
-    },
-    rule: {
-        enabledFlagRequired: 'Please select enabled status'
-    }
+  title: 'AI Settings',
+  form: {
+    enabledFlag: 'Enabled Status',
+    enabled: 'Enabled',
+    disabled: 'Disabled'
+  },
+  button: {
+    save: 'Save',
+    reset: 'Reset'
+  },
+  message: {
+    noData: 'No Data',
+    fetchFailed: 'Failed to get AI settings',
+    saveSuccess: 'Save successfully',
+    saveFailed: 'Save failed'
+  },
+  rule: {
+    enabledFlagRequired: 'Please select enabled status'
+  }
 };

+ 19 - 19
src/lang/modules/setting/ai/zh_CN.ts

@@ -1,22 +1,22 @@
 // AI设置模块 - 中文翻译
 export default {
-    title: 'AI设置',
-    form: {
-        enabledFlag: '启用状态',
-        enabled: '启用',
-        disabled: '禁用'
-    },
-    button: {
-        save: '保存',
-        reset: '重置'
-    },
-    message: {
-        noData: '暂无数据',
-        fetchFailed: '获取AI设置失败',
-        saveSuccess: '保存成功',
-        saveFailed: '保存失败'
-    },
-    rule: {
-        enabledFlagRequired: '请选择启用状态'
-    }
+  title: 'AI设置',
+  form: {
+    enabledFlag: '启用状态',
+    enabled: '启用',
+    disabled: '禁用'
+  },
+  button: {
+    save: '保存',
+    reset: '重置'
+  },
+  message: {
+    noData: '暂无数据',
+    fetchFailed: '获取AI设置失败',
+    saveSuccess: '保存成功',
+    saveFailed: '保存失败'
+  },
+  rule: {
+    enabledFlagRequired: '请选择启用状态'
+  }
 };

+ 1 - 1
src/lang/modules/system/menu/en_US.ts

@@ -55,7 +55,7 @@ export default {
     componentTip: 'The component path to access, such as: `system/user/index`, default under `views` directory',
     componentPlaceholder: 'Please enter component path',
     perms: 'Permission String',
-    permsTip: 'Permission character defined in controller, such as: @SaCheckPermission(\'system:user:list\')',
+    permsTip: "Permission character defined in controller, such as: @SaCheckPermission('system:user:list')",
     permsPlaceholder: 'Please enter permission identifier',
     queryParam: 'Route Parameters',
     queryParamTip: 'Default parameters passed to the route, such as: `{"id": 1, "name": "ry"}`',

+ 1 - 1
src/lang/modules/system/menu/zh_CN.ts

@@ -55,7 +55,7 @@ export default {
     componentTip: '访问的组件路径,如:`system/user/index`,默认在`views`目录下',
     componentPlaceholder: '请输入组件路径',
     perms: '权限字符',
-    permsTip: '控制器中定义的权限字符,如:@SaCheckPermission(\'system:user:list\')',
+    permsTip: "控制器中定义的权限字符,如:@SaCheckPermission('system:user:list')",
     permsPlaceholder: '请输入权限标识',
     queryParam: '路由参数',
     queryParamTip: '访问路由的默认传递参数,如:`{"id": 1, "name": "ry"}`',

+ 1 - 1
src/lang/modules/system/role/en_US.ts

@@ -39,7 +39,7 @@ export default {
     roleNamePlaceholder: 'Please enter role name',
     roleKey: 'Role Key',
     roleKeyPlaceholder: 'Please enter role key',
-    roleKeyTooltip: 'Permission character defined in controller, e.g.: @SaCheckRole(\'admin\')',
+    roleKeyTooltip: "Permission character defined in controller, e.g.: @SaCheckRole('admin')",
     roleSort: 'Sort Order',
     status: 'Status',
     menuPermission: 'Menu Permission',

+ 1 - 1
src/lang/modules/system/role/zh_CN.ts

@@ -39,7 +39,7 @@ export default {
     roleNamePlaceholder: '请输入角色名称',
     roleKey: '权限字符',
     roleKeyPlaceholder: '请输入权限字符',
-    roleKeyTooltip: '控制器中定义的权限字符,如:@SaCheckRole(\'admin\')',
+    roleKeyTooltip: "控制器中定义的权限字符,如:@SaCheckRole('admin')",
     roleSort: '角色顺序',
     status: '状态',
     menuPermission: '菜单权限',

+ 2 - 2
src/lang/modules/system/user/en_US.ts

@@ -89,7 +89,7 @@ export default {
     nickNameRequired: 'Nick name cannot be empty',
     passwordRequired: 'Password cannot be empty',
     passwordLength: 'Password length must be between 5 and 20',
-    passwordPattern: "Cannot contain illegal characters: < > \" ' \\ |",
+    passwordPattern: 'Cannot contain illegal characters: < > " \' \\ |',
     emailFormat: 'Please enter a valid email address',
     phonenumberFormat: 'Please enter a valid phone number',
     roleIdsRequired: 'User role cannot be empty'
@@ -110,7 +110,7 @@ export default {
     resetPwdText: 'Please enter new password for "{userName}"',
     resetPwdSuccess: 'Modified successfully, new password is: {password}',
     resetPwdLengthError: 'Password length must be between 5 and 20',
-    resetPwdPatternError: "Cannot contain illegal characters: < > \" ' \\ |",
+    resetPwdPatternError: 'Cannot contain illegal characters: < > " \' \\ |',
     confirmButton: 'Confirm',
     cancelButton: 'Cancel',
     operationSuccess: 'Operation successful'

+ 40 - 42
src/layout/components/Navbar.vue

@@ -1,7 +1,6 @@
 <template>
   <div class="navbar">
-    <hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container"
-               @toggle-click="toggleSideBar" />
+    <hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggle-click="toggleSideBar" />
     <breadcrumb v-if="!settingsStore.topNav" id="breadcrumb-container" class="breadcrumb-container" />
     <top-nav v-if="settingsStore.topNav" id="topmenu-container" class="topmenu-container" />
 
@@ -18,55 +17,54 @@
           @change="dynamicTenantEvent"
           @clear="dynamicClearEvent"
         >
-          <el-option v-for="item in tenantList" :key="item.tenantId" :label="item.companyName"
-                     :value="item.tenantId"></el-option>
+          <el-option v-for="item in tenantList" :key="item.tenantId" :label="item.companyName" :value="item.tenantId"></el-option>
           <template #prefix>
             <svg-icon icon-class="company" class="el-input__icon input-icon" />
           </template>
         </el-select>
 
         <search-menu ref="searchMenuRef" />
-<!--        <el-tooltip content="搜索" effect="dark" placement="bottom">-->
-<!--          <div class="right-menu-item hover-effect" @click="openSearchMenu">-->
-<!--            <svg-icon class-name="search-icon" icon-class="search" />-->
-<!--          </div>-->
-<!--        </el-tooltip>-->
+        <!--        <el-tooltip content="搜索" effect="dark" placement="bottom">-->
+        <!--          <div class="right-menu-item hover-effect" @click="openSearchMenu">-->
+        <!--            <svg-icon class-name="search-icon" icon-class="search" />-->
+        <!--          </div>-->
+        <!--        </el-tooltip>-->
         <!-- 消息 -->
-<!--        <el-tooltip :content="proxy.$t('navbar.message')" effect="dark" placement="bottom">-->
-<!--          <div>-->
-<!--            <el-popover placement="bottom" trigger="click" transition="el-zoom-in-top" :width="300" :persistent="false">-->
-<!--              <template #reference>-->
-<!--                <el-badge :value="newNotice > 0 ? newNotice : ''" :max="99">-->
-<!--                  <div class="right-menu-item hover-effect" style="display: block">-->
-<!--                    <svg-icon icon-class="message" />-->
-<!--                  </div>-->
-<!--                </el-badge>-->
-<!--              </template>-->
-<!--              <template #default>-->
-<!--                <notice></notice>-->
-<!--              </template>-->
-<!--            </el-popover>-->
-<!--          </div>-->
-<!--        </el-tooltip>-->
-<!--        <el-tooltip content="Github" effect="dark" placement="bottom">-->
-<!--          <ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />-->
-<!--        </el-tooltip>-->
-
-<!--        <el-tooltip :content="proxy.$t('navbar.document')" effect="dark" placement="bottom">-->
-<!--          <ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" />-->
-<!--        </el-tooltip>-->
-
-<!--        <el-tooltip :content="proxy.$t('navbar.full')" effect="dark" placement="bottom">-->
-<!--          <screenfull id="screenfull" class="right-menu-item hover-effect" />-->
-<!--        </el-tooltip>-->
+        <!--        <el-tooltip :content="proxy.$t('navbar.message')" effect="dark" placement="bottom">-->
+        <!--          <div>-->
+        <!--            <el-popover placement="bottom" trigger="click" transition="el-zoom-in-top" :width="300" :persistent="false">-->
+        <!--              <template #reference>-->
+        <!--                <el-badge :value="newNotice > 0 ? newNotice : ''" :max="99">-->
+        <!--                  <div class="right-menu-item hover-effect" style="display: block">-->
+        <!--                    <svg-icon icon-class="message" />-->
+        <!--                  </div>-->
+        <!--                </el-badge>-->
+        <!--              </template>-->
+        <!--              <template #default>-->
+        <!--                <notice></notice>-->
+        <!--              </template>-->
+        <!--            </el-popover>-->
+        <!--          </div>-->
+        <!--        </el-tooltip>-->
+        <!--        <el-tooltip content="Github" effect="dark" placement="bottom">-->
+        <!--          <ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />-->
+        <!--        </el-tooltip>-->
+
+        <!--        <el-tooltip :content="proxy.$t('navbar.document')" effect="dark" placement="bottom">-->
+        <!--          <ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" />-->
+        <!--        </el-tooltip>-->
+
+        <!--        <el-tooltip :content="proxy.$t('navbar.full')" effect="dark" placement="bottom">-->
+        <!--          <screenfull id="screenfull" class="right-menu-item hover-effect" />-->
+        <!--        </el-tooltip>-->
 
         <el-tooltip :content="proxy.$t('navbar.language')" effect="dark" placement="bottom">
           <lang-select id="lang-select" class="right-menu-item hover-effect" />
         </el-tooltip>
 
-<!--        <el-tooltip :content="proxy.$t('navbar.layoutSize')" effect="dark" placement="bottom">-->
-<!--          <size-select id="size-select" class="right-menu-item hover-effect" />-->
-<!--        </el-tooltip>-->
+        <!--        <el-tooltip :content="proxy.$t('navbar.layoutSize')" effect="dark" placement="bottom">-->
+        <!--          <size-select id="size-select" class="right-menu-item hover-effect" />-->
+        <!--        </el-tooltip>-->
       </template>
       <div class="avatar-container">
         <el-dropdown class="right-menu-item hover-effect" trigger="click" @command="handleCommand">
@@ -81,9 +79,9 @@
               <router-link v-if="!dynamic" to="/user/profile">
                 <el-dropdown-item>{{ proxy.$t('navbar.personalCenter') }}</el-dropdown-item>
               </router-link>
-<!--              <el-dropdown-item v-if="settingsStore.showSettings" command="setLayout">-->
-<!--                <span>{{ proxy.$t('navbar.layoutSetting') }}</span>-->
-<!--              </el-dropdown-item>-->
+              <!--              <el-dropdown-item v-if="settingsStore.showSettings" command="setLayout">-->
+              <!--                <span>{{ proxy.$t('navbar.layoutSetting') }}</span>-->
+              <!--              </el-dropdown-item>-->
               <el-dropdown-item divided command="logout">
                 <span>{{ proxy.$t('navbar.logout') }}</span>
               </el-dropdown-item>

+ 12 - 12
src/layout/components/Settings/index.vue

@@ -28,18 +28,18 @@
         </div>
       </div>
     </div>
-<!--    <div class="drawer-item">-->
-<!--      <span>主题颜色</span>-->
-<!--      <span class="comp-style">-->
-<!--        <el-color-picker v-model="theme" :predefine="predefineColors" @change="themeChange" />-->
-<!--      </span>-->
-<!--    </div>-->
-<!--    <div class="drawer-item">-->
-<!--      <span>深色模式</span>-->
-<!--      <span class="comp-style">-->
-<!--        <el-switch v-model="isDark" class="drawer-switch" @change="toggleDark" />-->
-<!--      </span>-->
-<!--    </div>-->
+    <!--    <div class="drawer-item">-->
+    <!--      <span>主题颜色</span>-->
+    <!--      <span class="comp-style">-->
+    <!--        <el-color-picker v-model="theme" :predefine="predefineColors" @change="themeChange" />-->
+    <!--      </span>-->
+    <!--    </div>-->
+    <!--    <div class="drawer-item">-->
+    <!--      <span>深色模式</span>-->
+    <!--      <span class="comp-style">-->
+    <!--        <el-switch v-model="isDark" class="drawer-switch" @change="toggleDark" />-->
+    <!--      </span>-->
+    <!--    </div>-->
 
     <el-divider />
 

+ 1 - 1
src/layout/components/Sidebar/Logo.vue

@@ -12,7 +12,7 @@
         </h1>
       </router-link>
       <router-link v-else key="expand" class="sidebar-logo-link" to="/">
-<!--        <img v-if="logo" :src="logo" class="sidebar-logo" />-->
+        <!--        <img v-if="logo" :src="logo" class="sidebar-logo" />-->
         <h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">
           {{ title }}
         </h1>

+ 2 - 2
src/layout/components/TagsView/index.vue

@@ -12,7 +12,7 @@
         @click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
         @contextmenu.prevent="openMenu(tag, $event)"
       >
-        <svg-icon v-if="tagsIcon && tag.meta && tag.meta.icon && tag.meta.icon !== '#'" :icon-class="tag.meta.icon"/>
+        <svg-icon v-if="tagsIcon && tag.meta && tag.meta.icon && tag.meta.icon !== '#'" :icon-class="tag.meta.icon" />
         <span class="tags-view-item-title">{{ getTagTitle(tag) }}</span>
         <span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
           <close class="el-icon-close" style="width: 1em; height: 1em; vertical-align: middle" />
@@ -55,7 +55,7 @@ const { locale } = useI18n();
 const visitedViews = computed(() => useTagsViewStore().getVisitedViews());
 const routes = computed(() => usePermissionStore().getRoutes());
 const theme = computed(() => useSettingsStore().theme);
-const tagsIcon = computed(() => useSettingsStore().tagsIcon)
+const tagsIcon = computed(() => useSettingsStore().tagsIcon);
 
 watch(route, () => {
   addTags();

+ 6 - 3
src/main.ts

@@ -65,6 +65,9 @@ const updateTitle = () => {
 updateTitle();
 
 // 监听语言变化,动态更新标题
-watch(() => i18n.global.locale.value, () => {
-  updateTitle();
-});
+watch(
+  () => i18n.global.locale.value,
+  () => {
+    updateTitle();
+  }
+);

+ 10 - 10
src/router/demo.ts

@@ -1,14 +1,14 @@
-import { RouteRecordRaw } from 'vue-router'
+import { RouteRecordRaw } from 'vue-router';
 
 const demoRoutes: RouteRecordRaw[] = [
-    {
-        path: '/demo/permission-tree',
-        component: () => import('@/views/demo/PermissionTreeDemo.vue'),
-        meta: {
-            title: '权限树演示',
-            icon: 'tree'
-        }
+  {
+    path: '/demo/permission-tree',
+    component: () => import('@/views/demo/PermissionTreeDemo.vue'),
+    meta: {
+      title: '权限树演示',
+      icon: 'tree'
     }
-]
+  }
+];
 
-export default demoRoutes
+export default demoRoutes;

+ 3 - 5
src/router/index.ts

@@ -74,7 +74,7 @@ export const constantRoutes: RouteRecordRaw[] = [
         path: '/index',
         component: () => import('@/views/index.vue'),
         name: 'Index',
-        meta: { title: '{"zh_CN":"首页","en_US":"Home Page"}', icon: 'dashboard'/*, affix: true*/ }
+        meta: { title: '{"zh_CN":"首页","en_US":"Home Page"}', icon: 'dashboard' /*, affix: true*/ }
       }
     ]
   },
@@ -103,9 +103,7 @@ export const constantRoutes: RouteRecordRaw[] = [
 ];
 
 // 动态路由,基于用户权限动态去加载
-export const dynamicRoutes: RouteRecordRaw[] = [
-
-];
+export const dynamicRoutes: RouteRecordRaw[] = [];
 
 /**
  * 创建路由
@@ -122,4 +120,4 @@ const router = createRouter({
   }
 });
 
-export default router;
+export default router;

+ 7 - 7
src/utils/dict.ts

@@ -13,7 +13,7 @@ const convertToI18nLabel = (dictLabel: string, dictLabelZh?: string, dictLabelEn
   if (dictLabel && (dictLabel.startsWith('{') || dictLabel.startsWith('{'))) {
     return dictLabel;
   }
-  
+
   // 如果有中英文标签,构建JSON格式
   if (dictLabelZh || dictLabelEn) {
     const i18nObj: any = {};
@@ -23,7 +23,7 @@ const convertToI18nLabel = (dictLabel: string, dictLabelZh?: string, dictLabelEn
     if (!dictLabelZh && dictLabel) i18nObj.zh_CN = dictLabel;
     return JSON.stringify(i18nObj);
   }
-  
+
   // 否则返回原标签
   return dictLabel;
 };
@@ -44,11 +44,11 @@ export const useDict = (...args: string[]): { [key: string]: DictDataOption[] }
     } else {
       await getDicts(dictType).then((resp) => {
         res.value[dictType] = resp.data.map(
-          (p): DictDataOption => ({ 
-            label: convertToI18nLabel(p.dictLabel, (p as any).dictLabelZh, (p as any).dictLabelEn), 
-            value: p.dictValue, 
-            elTagType: p.listClass, 
-            elTagClass: p.cssClass 
+          (p): DictDataOption => ({
+            label: convertToI18nLabel(p.dictLabel, (p as any).dictLabelZh, (p as any).dictLabelEn),
+            value: p.dictValue,
+            elTagType: p.listClass,
+            elTagClass: p.cssClass
           })
         );
         useDictStore().setDict(dictType, res.value[dictType]);

+ 67 - 36
src/utils/web-office-sdk-solution-v2.0.7/index.d.ts

@@ -135,11 +135,11 @@ interface IWppOptions {
 /**
  * 数据表自定义配置
  */
- interface IDBOptions {
+interface IDBOptions {
   /**
    * 是否显示使用反馈按钮
    */
-   isShowFeedback?: boolean
+  isShowFeedback?: boolean;
 }
 
 /**
@@ -159,8 +159,8 @@ interface ISubscriptionsConf {
    * 打印事件
    */
   print?: {
-    custom?: boolean,
-    subscribe: (arg0?: any) => any,
+    custom?: boolean;
+    subscribe: (arg0?: any) => any;
   };
   /**
    * 导出 PDF 事件
@@ -195,7 +195,7 @@ interface IConfig {
    * 头部
    */
   headers?: IUserHeadersConf;
-   /**
+  /**
    * 头部
    */
   mode?: 'nomal' | 'simple';
@@ -234,23 +234,25 @@ interface IConfig {
   debug?: boolean;
   commandBars?: IWpsCommandBars[];
   print?: {
-    custom?: boolean,
-    callback?: string,
+    custom?: boolean;
+    callback?: string;
   };
 
   exportPdf?: {
-    callback?: string,
+    callback?: string;
   };
 
   // 获取token
   refreshToken?: TRefreshToken;
 
   cooperUserAttribute?: {
-    isCooperUsersAvatarVisible?: boolean,
-    cooperUsersColor?: [{
-      userId: string | number,
-      color:  string,
-    }],
+    isCooperUsersAvatarVisible?: boolean;
+    cooperUsersColor?: [
+      {
+        userId: string | number;
+        color: string;
+      }
+    ];
   };
 }
 
@@ -268,7 +270,7 @@ interface IMessage {
   result?: any;
   error?: any;
   _self?: boolean;
-  sdkInstanceId?: number
+  sdkInstanceId?: number;
 }
 
 /**
@@ -308,14 +310,14 @@ interface IWps extends IWpsCompatible {
   version: string;
   url: string;
   iframe: any;
-  Enum? : any; // 即将废弃
+  Enum?: any; // 即将废弃
   Events?: any; // 即将废弃
   Props?: string;
   advancedApiReady: () => Promise<any>;
   /**
    * 兼容1.x用法
    */
-  ready?:() => Promise<any>;
+  ready?: () => Promise<any>;
   destroy: () => Promise<any>;
   WpsApplication?: () => any;
   WordApplication?: () => any;
@@ -327,9 +329,7 @@ interface IWps extends IWpsCompatible {
   Application?: any;
   CommonApi?: any;
   commonApiReady: () => Promise<any>;
-  setToken: (tokenData: {
-    token: string, timeout?: number, hasRefreshTokenConfig: boolean,
-  }) => Promise<any>;
+  setToken: (tokenData: { token: string; timeout?: number; hasRefreshTokenConfig: boolean }) => Promise<any>;
   tokenData?: { token: string } | null;
   commandBars?: IWpsCommandBars[] | null;
   iframeReady?: boolean;
@@ -342,18 +342,18 @@ interface IWps extends IWpsCompatible {
 /**
  * 兼容1.x用法
  */
-interface IWpsCompatible { 
+interface IWpsCompatible {
   tabs?: {
-    getTabs: () => Promise<Array<{tabKey: number, text: string}>>
-    switchTab: (tabKey: number) => Promise<any>,
-  }
+    getTabs: () => Promise<Array<{ tabKey: number; text: string }>>;
+    switchTab: (tabKey: number) => Promise<any>;
+  };
   setCommandBars?: (args: Array<IWpsCommandBars>) => Promise<void>;
   save?: () => Promise<any>;
   ApiEvent?: {
-    AddApiEventListener: (eventName: string, handle: (event?: any) => void) => void
-    RemoveApiEventListener: (eventName: string, handle: (event?: any) => void) => void
-  }
-  executeCommandBar?: (id: string) => void
+    AddApiEventListener: (eventName: string, handle: (event?: any) => void) => void;
+    RemoveApiEventListener: (eventName: string, handle: (event?: any) => void) => void;
+  };
+  executeCommandBar?: (id: string) => void;
 }
 
 interface IFlag {
@@ -384,7 +384,7 @@ type TRefreshToken = () => ITokenData | Promise<ITokenData>;
 type sendMsgToWps = (msg: IMessage) => void;
 type getId = () => string;
 
-interface IAppConfig extends IConfig { 
+interface IAppConfig extends IConfig {
   appId: string;
   fileId: string | number;
   officeType: string;
@@ -405,11 +405,42 @@ interface IAppConfig extends IConfig {
 }
 
 type OfficeType = {
-  Spreadsheet: string,
-  Writer: string,
-  Presentation: string,
-  Pdf: string,
-  Otl: string
-}
-
-export { IAppConfig, ICbEvent, IClipboardData, ICommonOptions, IConfig, IDBOptions, IEtOptions, IFlag, IIframeWH, IMessage, IPDFOptions, IReadyEvent, ISubscriptionsConf, ITokenData, IUserHeaderButtonConf, IUserHeaderSubItemsConf, IUserHeadersConf, IWebOfficeSDK, IWppOptions, IWps, IWpsCommandBarAttr, IWpsCommandBarObjectAttr, IWpsCommandBars, IWpsCompatible, IWpsOptions, IWpsWebApi, OfficeType, TRefreshToken, getId, sendMsgToWps };
+  Spreadsheet: string;
+  Writer: string;
+  Presentation: string;
+  Pdf: string;
+  Otl: string;
+};
+
+export {
+  IAppConfig,
+  ICbEvent,
+  IClipboardData,
+  ICommonOptions,
+  IConfig,
+  IDBOptions,
+  IEtOptions,
+  IFlag,
+  IIframeWH,
+  IMessage,
+  IPDFOptions,
+  IReadyEvent,
+  ISubscriptionsConf,
+  ITokenData,
+  IUserHeaderButtonConf,
+  IUserHeaderSubItemsConf,
+  IUserHeadersConf,
+  IWebOfficeSDK,
+  IWppOptions,
+  IWps,
+  IWpsCommandBarAttr,
+  IWpsCommandBarObjectAttr,
+  IWpsCommandBars,
+  IWpsCompatible,
+  IWpsOptions,
+  IWpsWebApi,
+  OfficeType,
+  TRefreshToken,
+  getId,
+  sendMsgToWps
+};

Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
src/utils/web-office-sdk-solution-v2.0.7/web-office-sdk-solution-v2.0.7.cjs.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
src/utils/web-office-sdk-solution-v2.0.7/web-office-sdk-solution-v2.0.7.es.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
src/utils/web-office-sdk-solution-v2.0.7/web-office-sdk-solution-v2.0.7.umd.js


+ 228 - 0
src/views/Qc/task/detail.vue

@@ -0,0 +1,228 @@
+<template>
+  <div class="p-2">
+    <el-card shadow="never">
+      <template #header>
+        <div class="flex items-center justify-between">
+          <span>任务详情</span>
+          <el-button type="primary" plain size="small" @click="handleBack">返回列表</el-button>
+        </div>
+      </template>
+
+      <div v-if="loading" class="text-center py-10">
+        <el-skeleton :rows="15" animated />
+      </div>
+
+      <div v-else-if="taskDetail" class="detail-content">
+        <!-- 顶部布局:左上角任务名称和状态,右上角项目名称和完成度 -->
+        <el-row :gutter="20" class="mb-6">
+          <el-col :span="12">
+            <div class="flex items-center">
+              <h1 class="text-2xl font-bold mr-4">{{ taskDetail.name }}</h1>
+              <el-tag v-if="taskDetail.status === 0" type="info" size="large">未开始</el-tag>
+              <el-tag v-else-if="taskDetail.status === 1" type="warning" size="large">进行中</el-tag>
+              <el-tag v-else-if="taskDetail.status === 2" type="success" size="large">已完成</el-tag>
+              <el-tag v-else type="info" size="large">未知</el-tag>
+            </div>
+          </el-col>
+          <el-col :span="12">
+            <div class="flex flex-col items-end">
+              <div class="mb-4 p-3 bg-gray-50 rounded-lg w-full max-w-md">
+                <div class="mb-2">
+                  <span class="text-sm text-gray-500">质控项目</span>
+                  <div class="text-xl font-semibold">{{ taskDetail.projectName || taskDetail.projectId }}</div>
+                </div>
+                <div>
+                  <span class="text-sm text-gray-500">完成度</span>
+                  <div class="mt-1">
+                    <el-progress :percentage="Math.round((Number(taskDetail.schedule) || 0) * 100 * 100) / 100"
+                      :stroke-width="12" :show-text="true" :format="(percentage) => `${percentage}%`"
+                      :empty-color="'#e0e0e0'" color="#409eff" style="width: 100%" />
+                  </div>
+                </div>
+              </div>
+            </div>
+          </el-col>
+        </el-row>
+
+        <el-divider />
+
+        <!-- 任务基本信息 -->
+        <el-descriptions title="基本信息" :column="3" border>
+          <el-descriptions-item label="创建时间">{{ parseTime(taskDetail.createTime) }}</el-descriptions-item>
+          <el-descriptions-item label="截止时间">{{ parseTime(taskDetail.deadline, '{y}-{m}-{d}') }}</el-descriptions-item>
+          <el-descriptions-item label="备注" :span="3">{{ taskDetail.note || '-' }}</el-descriptions-item>
+        </el-descriptions>
+
+        <el-divider />
+
+        <!-- 任务详情列表 -->
+        <div class="mb-8">
+          <h3 class="text-xl font-semibold mb-3">任务列表</h3>
+          <el-table v-loading="taskItemsLoading" :data="taskItems" border>
+            <el-table-column prop="id" label="序号" width="80" />
+            <el-table-column prop="documentName" label="文档名称" width="180" show-overflow-tooltip />
+            <el-table-column prop="executorName" label="质控人" width="150" />
+            <el-table-column label="状态" width="120">
+              <template #default="scope">
+                <el-tag v-if="scope.row.status === 0 || scope.row.status === 3" type="info">待审核</el-tag>
+                <el-tag v-else-if="scope.row.status === 1" type="success">审核通过</el-tag>
+                <el-tag v-else-if="scope.row.status === 2" type="danger">审核拒绝</el-tag>
+                <el-tag v-else type="warning">未知</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column prop="executionTime" label="执行时间" width="180" />
+            <el-table-column prop="finishTime" label="完成时间" width="180" />
+            <el-table-column prop="note" label="备注" />
+          </el-table>
+
+          <div class="mt-4">
+            <pagination v-show="taskItemsTotal > 0" :total="taskItemsTotal" v-model:page="taskItemsQuery.pageNum"
+              v-model:limit="taskItemsQuery.pageSize" @pagination="handleTaskItemsPagination" />
+          </div>
+        </div>
+      </div>
+
+      <div v-else class="text-center py-20">
+        <el-result icon="error" title="获取任务详情失败" sub-title="请重试或返回列表">
+          <template #extra>
+            <el-button type="primary" @click="handleBack">返回列表</el-button>
+          </template>
+        </el-result>
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<script setup name="TaskDetail" lang="ts">
+import { ref, onMounted, watch } from 'vue';
+import { getTask, listTaskDetail } from '@/api/qc/task';
+import { TaskVO } from '@/api/qc/task/types';
+
+// 定义任务详情项类型
+interface TaskDetailItem {
+  id: number;
+  document: number;
+  documentName: string;
+  ossId: number;
+  executor: number;
+  executorName: string;
+  status: number;
+  note: string;
+  executionTime: string;
+  finishTime: string;
+}
+
+// 任务列表数据
+const taskItems = ref<TaskDetailItem[]>([]);
+const taskItemsLoading = ref(false);
+const taskItemsTotal = ref(0);
+const taskItemsQuery = ref({
+  pageNum: 1,
+  pageSize: 10,
+  projectId: undefined,
+  taskId: undefined,
+  documentName: undefined,
+  status: undefined
+});
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const props = defineProps<{
+  taskId?: string | number;
+}>();
+
+const emit = defineEmits<{
+  back: [];
+}>();
+
+const loading = ref(true);
+const taskDetail = ref<TaskVO | null>(null);
+
+const fetchTaskDetail = async () => {
+  if (!props.taskId) return;
+
+  try {
+    loading.value = true;
+    const res = await getTask(props.taskId);
+    taskDetail.value = res.data;
+    // 获取任务列表
+    await fetchTaskItems();
+  } catch (error) {
+    console.error('获取任务详情失败:', error);
+    proxy?.$modal.msgError('获取任务详情失败');
+    taskDetail.value = null;
+  } finally {
+    loading.value = false;
+  }
+};
+
+/** 获取任务列表 */
+const fetchTaskItems = async () => {
+  if (!taskDetail.value?.id) return;
+
+  try {
+    taskItemsLoading.value = true;
+    taskItemsQuery.value.projectId = taskDetail.value.projectId;
+    taskItemsQuery.value.taskId = taskDetail.value.id;
+
+    const res = await listTaskDetail(taskItemsQuery.value);
+    if (res.code === 200) {
+      taskItems.value = res.rows;
+      taskItemsTotal.value = res.total;
+    } else {
+      proxy?.$modal.msgError(res.msg || '获取任务列表失败');
+    }
+  } catch (error) {
+    console.error('获取任务列表失败:', error);
+    proxy?.$modal.msgError('获取任务列表失败');
+  } finally {
+    taskItemsLoading.value = false;
+  }
+};
+
+/** 任务列表分页变化 */
+const handleTaskItemsPagination = (pageNum: number, pageSize: number) => {
+  taskItemsQuery.value.pageNum = pageNum;
+  taskItemsQuery.value.pageSize = pageSize;
+  fetchTaskItems();
+};
+
+const handleBack = () => {
+  emit('back');
+};
+
+watch(
+  () => props.taskId,
+  (newId) => {
+    if (newId) {
+      fetchTaskDetail();
+    }
+  }
+);
+
+onMounted(() => {
+  fetchTaskDetail();
+});
+</script>
+
+<style scoped>
+.detail-content {
+  width: 100%;
+  margin: 0;
+}
+
+/* 确保卡片内容占满宽度 */
+:deep(.el-card__body) {
+  padding: 20px;
+}
+
+/* 调整描述列表的样式 */
+:deep(.el-descriptions) {
+  margin-bottom: 20px;
+}
+
+/* 调整表格的样式 */
+:deep(.el-table) {
+  margin-top: 20px;
+}
+</style>

+ 86 - 283
src/views/Qc/task/index.vue

@@ -1,155 +1,11 @@
 <template>
   <div class="p-2">
-    <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">
-            <el-form-item label="任务名称" prop="name">
-              <el-input v-model="queryParams.name" placeholder="请输入任务名称" clearable @keyup.enter="handleQuery" />
-            </el-form-item>
-            <el-form-item label="发起人" prop="initiator">
-              <el-input v-model="queryParams.initiator" placeholder="请输入发起人" clearable @keyup.enter="handleQuery" />
-            </el-form-item>
-            <el-form-item label="质控项目" prop="projectId">
-              <el-input v-model="queryParams.projectId" placeholder="请输入质控项目" clearable @keyup.enter="handleQuery" />
-            </el-form-item>
-            <el-form-item label="开始时间" style="width: 308px">
-              <el-date-picker
-                v-model="dateRangeStartDate"
-                value-format="YYYY-MM-DD HH:mm:ss"
-                type="daterange"
-                range-separator="-"
-                start-placeholder="开始日期"
-                end-placeholder="结束日期"
-                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
-              />
-            </el-form-item>
-            <el-form-item label="截止时间" style="width: 308px">
-              <el-date-picker
-                v-model="dateRangeDeadline"
-                value-format="YYYY-MM-DD HH:mm:ss"
-                type="daterange"
-                range-separator="-"
-                start-placeholder="开始日期"
-                end-placeholder="结束日期"
-                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
-              />
-            </el-form-item>
-            <el-form-item label="创建者" prop="createBy">
-              <el-input v-model="queryParams.createBy" placeholder="请输入创建者" clearable @keyup.enter="handleQuery" />
-            </el-form-item>
-            <el-form-item label="创建时间" style="width: 308px">
-              <el-date-picker
-                v-model="dateRangeCreateTime"
-                value-format="YYYY-MM-DD HH:mm:ss"
-                type="daterange"
-                range-separator="-"
-                start-placeholder="开始日期"
-                end-placeholder="结束日期"
-                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
-              />
-            </el-form-item>
-            <el-form-item label="更新者" prop="updateBy">
-              <el-input v-model="queryParams.updateBy" placeholder="请输入更新者" clearable @keyup.enter="handleQuery" />
-            </el-form-item>
-            <el-form-item label="更新时间" style="width: 308px">
-              <el-date-picker
-                v-model="dateRangeUpdateTime"
-                value-format="YYYY-MM-DD HH:mm:ss"
-                type="daterange"
-                range-separator="-"
-                start-placeholder="开始日期"
-                end-placeholder="结束日期"
-                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
-              />
-            </el-form-item>
-            <el-form-item>
-              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-            </el-form-item>
-          </el-form>
-        </el-card>
-      </div>
-    </transition>
-
-    <el-card shadow="never">
-      <template #header>
-        <el-row :gutter="10" class="mb8">
-          <el-col :span="1.5">
-            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['qc:task:add']">新增</el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['qc:task:edit']">修改</el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['qc:task:remove']">删除</el-button>
-          </el-col>
-          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
-        </el-row>
-      </template>
+    <!-- 列表视图 -->
+    <TaskList v-if="currentView === 'list'" @add="handleAdd" @view="handleView" />
+
+    <!-- 详情视图 -->
+    <TaskDetail v-else-if="currentView === 'detail'" :taskId="currentTaskId" @back="handleBack" />
 
-      <el-table v-loading="loading" border :data="taskList" @selection-change="handleSelectionChange">
-        <el-table-column type="selection" width="55" align="center" />
-        <el-table-column label="序号" align="center" prop="id" v-if="true" />
-        <el-table-column label="任务名称" align="center" prop="name" width="200" />
-        <el-table-column label="发起人" align="center" prop="initiator" width="150">
-          <template #default="scope">
-            <span>{{ scope.row.initiatorName || scope.row.initiator }}</span>
-          </template>
-        </el-table-column>
-        <el-table-column label="质控项目" align="center" prop="projectId" width="200">
-          <template #default="scope">
-            <span>{{ scope.row.projectName || scope.row.projectId }}</span>
-          </template>
-        </el-table-column>
-        <el-table-column label="开始时间" align="center" prop="startDate" width="180">
-          <template #default="scope">
-            <span>{{ parseTime(scope.row.startDate, '{y}-{m}-{d}') }}</span>
-          </template>
-        </el-table-column>
-        <el-table-column label="截止时间" align="center" prop="deadline" width="180">
-          <template #default="scope">
-            <span>{{ parseTime(scope.row.deadline, '{y}-{m}-{d}') }}</span>
-          </template>
-        </el-table-column>
-        <el-table-column label="状态" align="center" prop="status" width="100">
-          <template #default="scope">
-            <el-tag :type="scope.row.status === 1 ? 'success' : 'warning'">
-              {{ scope.row.status === 1 ? '已完成' : '未完成' }}
-            </el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column label="备注" align="center" prop="note" />
-        <el-table-column label="创建者" align="center" prop="createBy" width="150" />
-        <el-table-column label="创建时间" align="center" prop="createTime" width="180" />
-        <el-table-column label="更新者" align="center" prop="updateBy" width="150" />
-        <el-table-column label="更新时间" align="center" prop="updateTime" width="180" />
-        <el-table-column label="操作" align="center" fixed="right" width="200" class-name="small-padding fixed-width">
-          <template #default="scope">
-            <el-button
-              v-hasPermi="['qc:task:edit']"
-              type="primary"
-              icon="Edit"
-              style="padding: 0 5px; font-size: 10px; height: 24px"
-              @click="handleUpdate(scope.row)"
-            >
-              修改
-            </el-button>
-            <el-button
-              v-hasPermi="['qc:task:remove']"
-              type="danger"
-              icon="Delete"
-              style="padding: 0 5px; font-size: 10px; height: 24px"
-              @click="handleDelete(scope.row)"
-            >
-              删除
-            </el-button>
-          </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>
     <!-- 添加或修改文档质控任务对话框 -->
     <el-dialog :title="dialog.title" v-model="dialog.visible" width="900px" append-to-body>
       <el-form ref="taskFormRef" :model="form" :rules="rules" label-width="80px">
@@ -157,30 +13,28 @@
           <el-input v-model="form.name" placeholder="请输入任务名称" />
         </el-form-item>
         <el-form-item label="发起人" prop="initiator">
-          <el-input v-model="initiatorName" placeholder="请选择发起人" readonly @click="handleSelectInitiator" style="cursor: pointer">
+          <el-input v-model="initiatorName" placeholder="请选择发起人" readonly @click="handleSelectInitiator"
+            style="cursor: pointer">
             <template #suffix>
-              <el-icon><User /></el-icon>
+              <el-icon>
+                <User />
+              </el-icon>
             </template>
           </el-input>
         </el-form-item>
         <el-form-item label="质控项目" prop="projectId">
-          <el-select
-            v-model="form.projectId"
-            filterable
-            remote
-            reserve-keyword
-            placeholder="请输入项目名称搜索"
-            :remote-method="remoteSearchProject"
-            :loading="projectLoading"
-            style="width: 100%"
-            @change="handleProjectChange"
-          >
+          <el-select v-model="form.projectId" filterable remote reserve-keyword placeholder="请输入项目名称搜索"
+            :remote-method="remoteSearchProject" :loading="projectLoading" style="width: 100%">
             <el-option v-for="item in projectList" :key="item.id" :label="item.name" :value="item.id" />
           </el-select>
         </el-form-item>
+        <el-form-item label="占比" prop="proportion">
+          <el-input-number v-model="form.proportion" :min="0" :precision="0" :step="1" placeholder="请输入占比(整数)"
+            style="width: 100%" controls-position="right" />
+        </el-form-item>
 
-        <!-- 质控任务列表 -->
-        <el-form-item v-if="taskDetailList.length > 0" label="质控任务">
+        <!-- 质控任务列表(仅修改时显示) -->
+        <el-form-item v-if="form.id && taskDetailList.length > 0" label="质控任务">
           <el-table :data="taskDetailList" border style="width: 100%" max-height="400">
             <el-table-column prop="documentId" label="文档序号" width="100" align="center" />
             <el-table-column prop="documentName" label="文档名称" min-width="150" />
@@ -201,13 +55,10 @@
             </el-table-column>
             <el-table-column label="操作" width="120" align="center">
               <template #default="scope">
-                <el-button
-                  v-if="scope.row.executorStatus && scope.row.executorStatus !== '0'"
-                  type="warning"
+                <el-button v-if="scope.row.executorStatus && scope.row.executorStatus !== '0'" type="warning"
                   icon="RefreshRight"
                   style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px"
-                  @click="handleChangeExecutor(scope.row)"
-                >
+                  @click="handleChangeExecutor(scope.row)">
                   更换质控人
                 </el-button>
               </template>
@@ -215,12 +66,9 @@
           </el-table>
         </el-form-item>
 
-        <el-form-item label="开始时间" prop="startDate">
-          <el-date-picker clearable v-model="form.startDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择开始时间" style="width: 100%">
-          </el-date-picker>
-        </el-form-item>
         <el-form-item label="截止时间" prop="deadline">
-          <el-date-picker clearable v-model="form.deadline" type="date" value-format="YYYY-MM-DD" placeholder="请选择截止时间" style="width: 100%">
+          <el-date-picker clearable v-model="form.deadline" type="date" value-format="YYYY-MM-DD" placeholder="请选择截止时间"
+            style="width: 100%">
           </el-date-picker>
         </el-form-item>
         <el-form-item label="备注" prop="note">
@@ -244,28 +92,23 @@
 </template>
 
 <script setup name="Task" lang="ts">
-import { listTask, getTask, delTask, addTask, updateTask, generateTaskDetail } from '@/api/qc/task';
-import { TaskVO, TaskQuery, TaskForm, TaskDetailVO } from '@/api/qc/task/types';
+import { addTask, updateTask, generateTaskDetail, getTask } from '@/api/qc/task';
+import { TaskVO, TaskForm, TaskDetailVO } from '@/api/qc/task/types';
 import { getProjectByName } from '@/api/project/management';
 import { useUserStore } from '@/store/modules/user';
 import UserSelect from '@/components/UserSelect/index.vue';
 import { UserVO } from '@/api/system/user/types';
+import TaskList from './list.vue';
+import TaskDetail from './detail.vue';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const userStore = useUserStore();
 
-const taskList = ref<TaskVO[]>([]);
+// 视图状态管理
+const currentView = ref('list'); // 'list' 或 'detail'
+const currentTaskId = ref<string | number>();
+
 const buttonLoading = ref(false);
-const loading = ref(true);
-const showSearch = ref(true);
-const ids = ref<Array<string | number>>([]);
-const single = ref(true);
-const multiple = ref(true);
-const total = ref(0);
-const dateRangeStartDate = ref<[DateModelType, DateModelType]>(['', '']);
-const dateRangeDeadline = ref<[DateModelType, DateModelType]>(['', '']);
-const dateRangeCreateTime = ref<[DateModelType, DateModelType]>(['', '']);
-const dateRangeUpdateTime = ref<[DateModelType, DateModelType]>(['', '']);
 
 // 项目列表
 const projectList = ref<Array<{ id: number; name: string }>>([]);
@@ -283,7 +126,6 @@ const initiatorSelectRef = ref<InstanceType<typeof UserSelect>>();
 // 发起人昵称(用于显示)
 const initiatorName = ref<string>('');
 
-const queryFormRef = ref<ElFormInstance>();
 const taskFormRef = ref<ElFormInstance>();
 
 const dialog = reactive<DialogOption>({
@@ -308,41 +150,44 @@ const initFormData: TaskForm = {
   name: undefined,
   initiator: undefined,
   projectId: undefined,
-  startDate: undefined,
+  proportion: undefined,
   deadline: undefined,
   status: undefined,
   note: undefined
 };
-const data = reactive<PageData<TaskForm, TaskQuery>>({
+const data = reactive<PageData<TaskForm, any>>({
   form: { ...initFormData },
-  queryParams: {
-    pageNum: 1,
-    pageSize: 10,
-    name: undefined,
-    initiator: undefined,
-    projectId: undefined,
-    status: undefined,
-    createBy: undefined,
-    updateBy: undefined,
-    params: {
-      startDate: undefined,
-      deadline: undefined,
-      createTime: undefined,
-      updateTime: undefined
-    }
-  },
   rules: {
-    id: [{ required: true, message: '序号不能为空', trigger: 'blur' }],
-    name: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }],
+    name: [{ required: true, message: '质控名称不能为空', trigger: 'blur' }],
     initiator: [{ required: true, message: '发起人不能为空', trigger: 'blur' }],
     projectId: [{ required: true, message: '质控项目不能为空', trigger: 'change' }],
-    startDate: [{ required: true, message: '开始时间不能为空', trigger: 'blur' }],
+    proportion: [
+      { required: true, message: '占比不能为空', trigger: 'blur' },
+      { type: 'number', message: '占比必须为数字', trigger: 'blur' },
+      {
+        validator: (rule: any, value: any, callback: any) => {
+          if (value !== undefined && value !== null && value !== '') {
+            if (!Number.isInteger(Number(value))) {
+              callback(new Error('占比必须为整数'));
+            } else if (value < 0) {
+              callback(new Error('占比不能为负数'));
+            } else {
+              callback();
+            }
+          } else {
+            callback();
+          }
+        },
+        trigger: 'blur'
+      }
+    ],
+
     deadline: [{ required: true, message: '截止时间不能为空', trigger: 'blur' }],
     status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
   }
 });
 
-const { queryParams, form, rules } = toRefs(data);
+const { form, rules } = toRefs(data);
 
 /** 远程搜索项目 */
 const remoteSearchProject = async (query: string) => {
@@ -362,8 +207,13 @@ const remoteSearchProject = async (query: string) => {
   }
 };
 
-/** 项目选择变化时加载质控任务详情 */
+/** 项目选择变化时加载质控任务详情(仅修改时) */
 const handleProjectChange = async (projectId: number) => {
+  // 新增时不加载质控任务详情
+  if (!form.value.id) {
+    return;
+  }
+
   if (!projectId) {
     taskDetailList.value = [];
     return;
@@ -415,20 +265,6 @@ const handleInitiatorSelectCallback = (users: UserVO[]) => {
   }
 };
 
-/** 查询文档质控任务列表 */
-const getList = async () => {
-  loading.value = true;
-  queryParams.value.params = {};
-  proxy?.addDateRange(queryParams.value, dateRangeStartDate.value, 'StartDate');
-  proxy?.addDateRange(queryParams.value, dateRangeDeadline.value, 'Deadline');
-  proxy?.addDateRange(queryParams.value, dateRangeCreateTime.value, 'CreateTime');
-  proxy?.addDateRange(queryParams.value, dateRangeUpdateTime.value, 'UpdateTime');
-  const res = await listTask(queryParams.value);
-  taskList.value = res.rows;
-  total.value = res.total;
-  loading.value = false;
-};
-
 /** 取消按钮 */
 const cancel = () => {
   reset();
@@ -445,29 +281,6 @@ const reset = () => {
   taskFormRef.value?.resetFields();
 };
 
-/** 搜索按钮操作 */
-const handleQuery = () => {
-  queryParams.value.pageNum = 1;
-  getList();
-};
-
-/** 重置按钮操作 */
-const resetQuery = () => {
-  dateRangeStartDate.value = ['', ''];
-  dateRangeDeadline.value = ['', ''];
-  dateRangeCreateTime.value = ['', ''];
-  dateRangeUpdateTime.value = ['', ''];
-  queryFormRef.value?.resetFields();
-  handleQuery();
-};
-
-/** 多选框选中数据 */
-const handleSelectionChange = (selection: TaskVO[]) => {
-  ids.value = selection.map((item) => item.id);
-  single.value = selection.length != 1;
-  multiple.value = !selection.length;
-};
-
 /** 新增按钮操作 */
 const handleAdd = () => {
   reset();
@@ -478,11 +291,22 @@ const handleAdd = () => {
   dialog.title = '添加文档质控任务';
 };
 
+/** 查看任务详情 */
+const handleView = (taskId: string | number) => {
+  currentTaskId.value = taskId;
+  currentView.value = 'detail';
+};
+
+/** 返回列表 */
+const handleBack = () => {
+  currentView.value = 'list';
+  currentTaskId.value = undefined;
+};
+
 /** 修改按钮操作 */
-const handleUpdate = async (row?: TaskVO) => {
+const handleUpdate = async (row: TaskVO) => {
   reset();
-  const _id = row?.id || ids.value[0];
-  const res = await getTask(_id);
+  const res = await getTask(row.id);
   Object.assign(form.value, res.data);
 
   // 设置发起人昵称
@@ -518,15 +342,19 @@ const submitForm = () => {
     if (valid) {
       buttonLoading.value = true;
 
-      // 构建提交数据,将 taskDetailList 转换为 details 格式
-      const submitData = {
-        ...form.value,
-        details: taskDetailList.value.map((item) => ({
+      // 构建提交数据
+      const submitData: any = {
+        ...form.value
+      };
+
+      // 仅修改时才包含 details
+      if (form.value.id && taskDetailList.value.length > 0) {
+        submitData.details = taskDetailList.value.map((item) => ({
           id: item.id,
           taskId: item.taskId || form.value.id,
           documentId: item.documentId,
           executor: item.executor,
-          projectId: form.value.projectId, // 添加 projectId
+          projectId: form.value.projectId,
           status: item.status,
           note: item.note,
           createDept: item.createDept || form.value.createDept,
@@ -535,8 +363,8 @@ const submitForm = () => {
           updateBy: item.updateBy || form.value.updateBy,
           updateTime: item.updateTime || form.value.updateTime,
           params: item.params || {}
-        }))
-      };
+        }));
+      }
 
       if (form.value.id) {
         await updateTask(submitData).finally(() => (buttonLoading.value = false));
@@ -545,32 +373,7 @@ const submitForm = () => {
       }
       proxy?.$modal.msgSuccess('操作成功');
       dialog.visible = false;
-      await getList();
     }
   });
 };
-
-/** 删除按钮操作 */
-const handleDelete = async (row?: TaskVO) => {
-  const _ids = row?.id || ids.value;
-  await proxy?.$modal.confirm('是否确认删除文档质控任务编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
-  await delTask(_ids);
-  proxy?.$modal.msgSuccess('删除成功');
-  await getList();
-};
-
-/** 导出按钮操作 */
-const handleExport = () => {
-  proxy?.download(
-    'Qc/task/export',
-    {
-      ...queryParams.value
-    },
-    `task_${new Date().getTime()}.xlsx`
-  );
-};
-
-onMounted(() => {
-  getList();
-});
 </script>

+ 248 - 0
src/views/Qc/task/list.vue

@@ -0,0 +1,248 @@
+<template>
+  <div>
+    <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">
+            <el-form-item label="任务名称" prop="name">
+              <el-input v-model="queryParams.name" placeholder="请输入任务名称" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="发起人" prop="initiator">
+              <el-input v-model="queryParams.initiator" placeholder="请输入发起人" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="质控项目" prop="projectId">
+              <el-input v-model="queryParams.projectId" placeholder="请输入质控项目" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+
+            <el-form-item label="截止时间" style="width: 308px">
+              <el-date-picker v-model="dateRangeDeadline" value-format="YYYY-MM-DD HH:mm:ss" type="daterange"
+                range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
+                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" />
+            </el-form-item>
+            <el-form-item label="创建者" prop="createBy">
+              <el-input v-model="queryParams.createBy" placeholder="请输入创建者" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="创建时间" style="width: 308px">
+              <el-date-picker v-model="dateRangeCreateTime" value-format="YYYY-MM-DD HH:mm:ss" type="daterange"
+                range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
+                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" />
+            </el-form-item>
+            <el-form-item label="更新者" prop="updateBy">
+              <el-input v-model="queryParams.updateBy" placeholder="请输入更新者" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="更新时间" style="width: 308px">
+              <el-date-picker v-model="dateRangeUpdateTime" value-format="YYYY-MM-DD HH:mm:ss" type="daterange"
+                range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
+                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <el-card shadow="never">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['qc:task:add']">新增</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()"
+              v-hasPermi="['qc:task:remove']">删除</el-button>
+          </el-col>
+          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+        </el-row>
+      </template>
+
+      <el-table v-loading="loading" border :data="taskList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="序号" align="center" prop="id" v-if="true" />
+        <el-table-column label="任务名称" align="center" prop="name" width="200" />
+        <el-table-column label="发起人" align="center" prop="initiator" width="150">
+          <template #default="scope">
+            <span>{{ scope.row.initiatorName || scope.row.initiator }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="质控项目" align="center" prop="projectId" width="200">
+          <template #default="scope">
+            <span>{{ scope.row.projectName || scope.row.projectId }}</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="截止时间" align="center" prop="deadline" width="180">
+          <template #default="scope">
+            <span>{{ parseTime(scope.row.deadline, '{y}-{m}-{d}') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="任务状态" align="center" prop="status" width="100">
+          <template #default="scope">
+            <el-tag v-if="scope.row.status === 0" type="info">未开始</el-tag>
+            <el-tag v-else-if="scope.row.status === 1" type="warning">进行中</el-tag>
+            <el-tag v-else-if="scope.row.status === 2" type="success">已完成</el-tag>
+            <el-tag v-else type="info">未知</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="完成度" align="center" width="200">
+          <template #default="scope">
+            <div style="width: 100%; padding: 5px 0">
+              <el-progress :percentage="Math.round((Number(scope.row.schedule) || 0) * 100 * 100) / 100"
+                :stroke-width="14" :show-text="true" :format="(percentage) => `${percentage}%`" :empty-color="'#e0e0e0'"
+                color="#409eff" style="width: 100%" />
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="备注" align="center" prop="note" />
+        <el-table-column label="创建者" align="center" prop="createBy" width="150" />
+        <el-table-column label="创建时间" align="center" prop="createTime" width="180" />
+        <el-table-column label="更新者" align="center" prop="updateBy" width="150" />
+        <el-table-column label="更新时间" align="center" prop="updateTime" width="180" />
+        <el-table-column label="操作" align="center" fixed="right" width="240" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-button v-hasPermi="['qc:task:query']" type="info" icon="View"
+              style="padding: 0 5px; font-size: 10px; height: 24px; margin-right: 5px"
+              @click="handleView(scope.row.id)">
+              查看
+            </el-button>
+            <el-button v-if="scope.row.status === 0" v-hasPermi="['qc:task:start']" type="primary" icon="VideoPlay"
+              style="padding: 0 5px; font-size: 10px; height: 24px; margin-right: 5px" @click="handleStart(scope.row)">
+              开始
+            </el-button>
+            <el-button v-hasPermi="['qc:task:remove']" type="danger" icon="Delete"
+              style="padding: 0 5px; font-size: 10px; height: 24px" @click="handleDelete(scope.row)">
+              删除
+            </el-button>
+          </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>
+  </div>
+</template>
+
+<script setup name="TaskList" lang="ts">
+import { listTask, getTask, delTask, startTask } from '@/api/qc/task';
+import { TaskVO, TaskQuery } from '@/api/qc/task/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const emit = defineEmits<{
+  add: [];
+  view: [taskId: string | number];
+}>();
+
+const taskList = ref<TaskVO[]>([]);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+
+const dateRangeDeadline = ref<[DateModelType, DateModelType]>(['', '']);
+const dateRangeCreateTime = ref<[DateModelType, DateModelType]>(['', '']);
+const dateRangeUpdateTime = ref<[DateModelType, DateModelType]>(['', '']);
+
+const queryFormRef = ref<ElFormInstance>();
+
+const data = reactive<PageData<any, TaskQuery>>({
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    name: undefined,
+    initiator: undefined,
+    projectId: undefined,
+    status: undefined,
+    createBy: undefined,
+    updateBy: undefined,
+    params: {
+      deadline: undefined,
+      createTime: undefined,
+      updateTime: undefined
+    }
+  }
+});
+
+const { queryParams } = toRefs(data);
+
+/** 查询文档质控任务列表 */
+const getList = async () => {
+  loading.value = true;
+  queryParams.value.params = {};
+
+  proxy?.addDateRange(queryParams.value, dateRangeDeadline.value, 'Deadline');
+  proxy?.addDateRange(queryParams.value, dateRangeCreateTime.value, 'CreateTime');
+  proxy?.addDateRange(queryParams.value, dateRangeUpdateTime.value, 'UpdateTime');
+  const res = await listTask(queryParams.value);
+  taskList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  dateRangeDeadline.value = ['', ''];
+  dateRangeCreateTime.value = ['', ''];
+  dateRangeUpdateTime.value = ['', ''];
+  queryFormRef.value?.resetFields();
+  handleQuery();
+};
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: TaskVO[]) => {
+  ids.value = selection.map((item) => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+};
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  emit('add');
+};
+
+/** 查看任务详情 */
+const handleView = (taskId: string | number) => {
+  emit('view', taskId);
+};
+
+/** 开始任务 */
+const handleStart = async (row: TaskVO) => {
+  try {
+    await proxy?.$modal.confirm(`是否确认开始任务 "${row.name}"?`);
+    const res = await startTask(row.id);
+    if (res.code === 200) {
+      proxy?.$modal.msgSuccess('任务开始成功');
+      getList(); // 重新获取列表
+    } else {
+      proxy?.$modal.msgError(res.msg || '任务开始失败');
+    }
+  } catch (error) {
+    console.error('开始任务失败:', error);
+  }
+};
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: TaskVO) => {
+  const _ids = row?.id || ids.value;
+  await proxy?.$modal.confirm('是否确认删除文档质控任务编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
+  await delTask(_ids);
+  proxy?.$modal.msgSuccess('删除成功');
+  await getList();
+};
+
+onMounted(() => {
+  getList();
+});
+</script>

+ 114 - 115
src/views/demo/FixVerification.vue

@@ -1,181 +1,180 @@
 <template>
-    <div class="fix-verification">
-        <el-card shadow="never">
-            <template #header>
-                <h3>修复验证 - 权限树行为测试</h3>
-            </template>
-
-            <div class="test-content">
-                <p>验证当选中"成都市"时,"四川省"是否保持半选状态而不是全选状态</p>
-
-                <div class="test-scenario">
-                    <h4>测试场景:</h4>
-                    <ol>
-                        <li>初始状态:无任何选中项</li>
-                        <li>操作:选中"成都市"</li>
-                        <li>预期结果:"成都市"被选中,"四川省"显示为半选状态(-)</li>
-                        <li>关键点:"四川省"不应该显示为全选状态</li>
-                    </ol>
-                </div>
-
-                <div class="tree-container">
-                    <data-permission-tree :data="treeData" :selected-keys="selectedKeys"
-                        @update:selectedKeys="handleSelectedKeysChange" @check="handleCheck" />
-                </div>
-
-                <div class="selected-info">
-                    <h4>当前选中的节点ID:</h4>
-                    <p>{{ selectedKeys }}</p>
-
-                    <h4>当前半选的节点ID:</h4>
-                    <p>{{ indeterminateKeys }}</p>
-                </div>
-
-                <div class="actions">
-                    <el-button @click="selectChengdu">选中成都市</el-button>
-                    <el-button @click="selectAllSichuan">选中四川省所有城市</el-button>
-                    <el-button @click="clearSelection">清空选择</el-button>
-                </div>
-            </div>
-        </el-card>
-    </div>
+  <div class="fix-verification">
+    <el-card shadow="never">
+      <template #header>
+        <h3>修复验证 - 权限树行为测试</h3>
+      </template>
+
+      <div class="test-content">
+        <p>验证当选中"成都市"时,"四川省"是否保持半选状态而不是全选状态</p>
+
+        <div class="test-scenario">
+          <h4>测试场景:</h4>
+          <ol>
+            <li>初始状态:无任何选中项</li>
+            <li>操作:选中"成都市"</li>
+            <li>预期结果:"成都市"被选中,"四川省"显示为半选状态(-)</li>
+            <li>关键点:"四川省"不应该显示为全选状态</li>
+          </ol>
+        </div>
+
+        <div class="tree-container">
+          <data-permission-tree :data="treeData" :selected-keys="selectedKeys" @update:selectedKeys="handleSelectedKeysChange" @check="handleCheck" />
+        </div>
+
+        <div class="selected-info">
+          <h4>当前选中的节点ID:</h4>
+          <p>{{ selectedKeys }}</p>
+
+          <h4>当前半选的节点ID:</h4>
+          <p>{{ indeterminateKeys }}</p>
+        </div>
+
+        <div class="actions">
+          <el-button @click="selectChengdu">选中成都市</el-button>
+          <el-button @click="selectAllSichuan">选中四川省所有城市</el-button>
+          <el-button @click="clearSelection">清空选择</el-button>
+        </div>
+      </div>
+    </el-card>
+  </div>
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue'
-import DataPermissionTree from '@/components/DataPermisionTree/index.vue'
-import type { TreeData, CheckedKeys } from '@/components/DataPermisionTree/types'
+import { ref } from 'vue';
+import DataPermissionTree from '@/components/DataPermisionTree/index.vue';
+import type { TreeData, CheckedKeys } from '@/components/DataPermisionTree/types';
 
 // 树形数据示例 - 模拟地理层级结构
 const treeData: TreeData[] = [
-    {
-        id: 1,
-        name: '中国',
+  {
+    id: 1,
+    name: '中国',
+    children: [
+      {
+        id: 2,
+        name: '四川省',
         children: [
-            {
-                id: 2,
-                name: '四川省',
-                children: [
-                    { id: 3, name: '成都市' },
-                    { id: 4, name: '绵阳市' },
-                    { id: 5, name: '德阳市' }
-                ]
-            },
-            {
-                id: 6,
-                name: '广东省',
-                children: [
-                    { id: 7, name: '广州市' },
-                    { id: 8, name: '深圳市' },
-                    { id: 9, name: '珠海市' }
-                ]
-            }
+          { id: 3, name: '成都市' },
+          { id: 4, name: '绵阳市' },
+          { id: 5, name: '德阳市' }
         ]
-    }
-]
+      },
+      {
+        id: 6,
+        name: '广东省',
+        children: [
+          { id: 7, name: '广州市' },
+          { id: 8, name: '深圳市' },
+          { id: 9, name: '珠海市' }
+        ]
+      }
+    ]
+  }
+];
 
 // 选中的键值
-const selectedKeys = ref<number[]>([])
+const selectedKeys = ref<number[]>([]);
 
 // 半选的键值
-const indeterminateKeys = ref<number[]>([])
+const indeterminateKeys = ref<number[]>([]);
 
 // 处理选中键值变化
 const handleSelectedKeysChange = (keys: number[]) => {
-    selectedKeys.value = keys
-}
+  selectedKeys.value = keys;
+};
 
 // 处理选中事件
 const handleCheck = (keys: CheckedKeys) => {
-    console.log('Checked keys:', keys.checked)
-    console.log('Indeterminate keys:', keys.indeterminate)
-    selectedKeys.value = keys.checked
-    indeterminateKeys.value = keys.indeterminate
-}
+  console.log('Checked keys:', keys.checked);
+  console.log('Indeterminate keys:', keys.indeterminate);
+  selectedKeys.value = keys.checked;
+  indeterminateKeys.value = keys.indeterminate;
+};
 
 // 选中成都市
 const selectChengdu = () => {
-    selectedKeys.value = [3]
-}
+  selectedKeys.value = [3];
+};
 
 // 选中四川省所有城市
 const selectAllSichuan = () => {
-    selectedKeys.value = [3, 4, 5]
-}
+  selectedKeys.value = [3, 4, 5];
+};
 
 // 清空选择
 const clearSelection = () => {
-    selectedKeys.value = []
-}
+  selectedKeys.value = [];
+};
 </script>
 
 <style scoped>
 .fix-verification {
-    padding: 20px;
-    max-width: 800px;
-    margin: 0 auto;
+  padding: 20px;
+  max-width: 800px;
+  margin: 0 auto;
 }
 
 .test-content {
-    padding: 20px 0;
+  padding: 20px 0;
 }
 
 .test-scenario {
-    background-color: #f0f9eb;
-    padding: 15px;
-    border-radius: 4px;
-    margin-bottom: 20px;
+  background-color: #f0f9eb;
+  padding: 15px;
+  border-radius: 4px;
+  margin-bottom: 20px;
 }
 
 .test-scenario h4 {
-    margin-top: 0;
-    color: #303133;
+  margin-top: 0;
+  color: #303133;
 }
 
 .test-scenario ol {
-    padding-left: 20px;
-    margin: 10px 0 0 0;
+  padding-left: 20px;
+  margin: 10px 0 0 0;
 }
 
 .test-scenario li {
-    margin: 8px 0;
+  margin: 8px 0;
 }
 
 .tree-container {
-    margin: 20px 0;
-    padding: 15px;
-    border: 1px solid #e4e7ed;
-    border-radius: 4px;
-    background-color: #f5f7fa;
-    min-height: 200px;
+  margin: 20px 0;
+  padding: 15px;
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+  background-color: #f5f7fa;
+  min-height: 200px;
 }
 
 .selected-info {
-    margin-top: 20px;
-    padding: 15px;
-    background-color: #ecf5ff;
-    border-radius: 4px;
+  margin-top: 20px;
+  padding: 15px;
+  background-color: #ecf5ff;
+  border-radius: 4px;
 }
 
 .selected-info h4 {
-    margin-top: 0;
-    color: #303133;
+  margin-top: 0;
+  color: #303133;
 }
 
 .selected-info p {
-    font-family: monospace;
-    background-color: #fff;
-    padding: 10px;
-    border-radius: 4px;
-    border: 1px solid #e4e7ed;
-    margin: 10px 0 0 0;
+  font-family: monospace;
+  background-color: #fff;
+  padding: 10px;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+  margin: 10px 0 0 0;
 }
 
 .actions {
-    margin-top: 20px;
+  margin-top: 20px;
 }
 
 .actions .el-button {
-    margin-right: 10px;
+  margin-right: 10px;
 }
-</style>
+</style>

+ 161 - 151
src/views/demo/PermissionTreeDemo.vue

@@ -1,221 +1,231 @@
 <template>
-    <div class="permission-tree-demo">
-        <el-card shadow="never">
-            <template #header>
-                <h3>权限树演示</h3>
-            </template>
-
-            <div class="demo-content">
-                <p>此演示展示了权限树的行为:</p>
-                <ul>
-                    <li>当选中"成都市"时,即使"四川省"下的所有城市都被选中,"四川省"也不会显示为全选状态</li>
-                    <li>父节点将始终保持半选状态(-),以表明其子节点被部分选择</li>
-                    <li>这避免了暗示整个层级都被完全选中的误解</li>
-                </ul>
-
-                <el-tabs v-model="activeTab">
-                    <el-tab-pane label="基础演示" name="basic">
-                        <div class="tree-container">
-                            <data-permission-tree :data="treeData" :selected-keys="selectedKeys"
-                                @update:selectedKeys="handleSelectedKeysChange" @check="handleCheck" />
-                        </div>
-                    </el-tab-pane>
-
-                    <el-tab-pane label="全选功能演示" name="selectAll">
-                        <div class="tree-container">
-                            <el-checkbox v-model="isSelectAll" @change="handleSelectAllChange">
-                                全选所有权限
-                            </el-checkbox>
-
-                            <data-permission-tree v-if="!isSelectAll" :data="treeData" :selected-keys="selectedKeys"
-                                @update:selectedKeys="handleSelectedKeysChange" @check="handleCheck" />
-
-                            <div v-else class="select-all-message">
-                                <p>已启用全选模式,所有权限都将被授予。</p>
-                                <p>树形结构已被隐藏以避免混淆。</p>
-                            </div>
-                        </div>
-                    </el-tab-pane>
-
-                    <el-tab-pane label="禁用状态演示" name="disabled">
-                        <div class="tree-container">
-                            <el-checkbox v-model="isDisabled" style="margin-bottom: 15px;">
-                                禁用权限选择
-                            </el-checkbox>
-
-                            <data-permission-tree :data="treeData" :selected-keys="selectedKeys" :disabled="isDisabled"
-                                @update:selectedKeys="handleSelectedKeysChange" @check="handleCheck" />
-                        </div>
-                    </el-tab-pane>
-                </el-tabs>
-
-                <div class="selected-info">
-                    <h4>当前选中的节点ID:</h4>
-                    <p>{{ selectedKeys }}</p>
-                </div>
-
-                <div class="instructions">
-                    <h4>使用说明:</h4>
-                    <ol>
-                        <li><strong>基础演示</strong>:展示树形结构的基本交互和半选状态行为</li>
-                        <li><strong>全选功能演示</strong>:当启用全选时,树形结构会隐藏,避免用户误操作</li>
-                        <li><strong>禁用状态演示</strong>:展示组件在禁用状态下的表现</li>
-                    </ol>
-                </div>
+  <div class="permission-tree-demo">
+    <el-card shadow="never">
+      <template #header>
+        <h3>权限树演示</h3>
+      </template>
+
+      <div class="demo-content">
+        <p>此演示展示了权限树的行为:</p>
+        <ul>
+          <li>当选中"成都市"时,即使"四川省"下的所有城市都被选中,"四川省"也不会显示为全选状态</li>
+          <li>父节点将始终保持半选状态(-),以表明其子节点被部分选择</li>
+          <li>这避免了暗示整个层级都被完全选中的误解</li>
+        </ul>
+
+        <el-tabs v-model="activeTab">
+          <el-tab-pane label="基础演示" name="basic">
+            <div class="tree-container">
+              <data-permission-tree
+                :data="treeData"
+                :selected-keys="selectedKeys"
+                @update:selectedKeys="handleSelectedKeysChange"
+                @check="handleCheck"
+              />
             </div>
-        </el-card>
-    </div>
+          </el-tab-pane>
+
+          <el-tab-pane label="全选功能演示" name="selectAll">
+            <div class="tree-container">
+              <el-checkbox v-model="isSelectAll" @change="handleSelectAllChange"> 全选所有权限 </el-checkbox>
+
+              <data-permission-tree
+                v-if="!isSelectAll"
+                :data="treeData"
+                :selected-keys="selectedKeys"
+                @update:selectedKeys="handleSelectedKeysChange"
+                @check="handleCheck"
+              />
+
+              <div v-else class="select-all-message">
+                <p>已启用全选模式,所有权限都将被授予。</p>
+                <p>树形结构已被隐藏以避免混淆。</p>
+              </div>
+            </div>
+          </el-tab-pane>
+
+          <el-tab-pane label="禁用状态演示" name="disabled">
+            <div class="tree-container">
+              <el-checkbox v-model="isDisabled" style="margin-bottom: 15px"> 禁用权限选择 </el-checkbox>
+
+              <data-permission-tree
+                :data="treeData"
+                :selected-keys="selectedKeys"
+                :disabled="isDisabled"
+                @update:selectedKeys="handleSelectedKeysChange"
+                @check="handleCheck"
+              />
+            </div>
+          </el-tab-pane>
+        </el-tabs>
+
+        <div class="selected-info">
+          <h4>当前选中的节点ID:</h4>
+          <p>{{ selectedKeys }}</p>
+        </div>
+
+        <div class="instructions">
+          <h4>使用说明:</h4>
+          <ol>
+            <li><strong>基础演示</strong>:展示树形结构的基本交互和半选状态行为</li>
+            <li><strong>全选功能演示</strong>:当启用全选时,树形结构会隐藏,避免用户误操作</li>
+            <li><strong>禁用状态演示</strong>:展示组件在禁用状态下的表现</li>
+          </ol>
+        </div>
+      </div>
+    </el-card>
+  </div>
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue'
-import DataPermissionTree from '@/components/DataPermisionTree/index.vue'
-import type { TreeData } from '@/components/DataPermisionTree/types'
+import { ref } from 'vue';
+import DataPermissionTree from '@/components/DataPermisionTree/index.vue';
+import type { TreeData } from '@/components/DataPermisionTree/types';
 
 // 活动标签页
-const activeTab = ref('basic')
+const activeTab = ref('basic');
 
 // 是否全选
-const isSelectAll = ref(false)
+const isSelectAll = ref(false);
 
 // 是否禁用
-const isDisabled = ref(false)
+const isDisabled = ref(false);
 
 // 树形数据示例 - 模拟地理层级结构
 const treeData: TreeData[] = [
-    {
-        id: 1,
-        name: '中国',
+  {
+    id: 1,
+    name: '中国',
+    children: [
+      {
+        id: 2,
+        name: '四川省',
+        children: [
+          { id: 3, name: '成都市' },
+          { id: 4, name: '绵阳市' },
+          { id: 5, name: '德阳市' }
+        ]
+      },
+      {
+        id: 6,
+        name: '广东省',
         children: [
-            {
-                id: 2,
-                name: '四川省',
-                children: [
-                    { id: 3, name: '成都市' },
-                    { id: 4, name: '绵阳市' },
-                    { id: 5, name: '德阳市' }
-                ]
-            },
-            {
-                id: 6,
-                name: '广东省',
-                children: [
-                    { id: 7, name: '广州市' },
-                    { id: 8, name: '深圳市' },
-                    { id: 9, name: '珠海市' }
-                ]
-            }
+          { id: 7, name: '广州市' },
+          { id: 8, name: '深圳市' },
+          { id: 9, name: '珠海市' }
         ]
-    }
-]
+      }
+    ]
+  }
+];
 
 // 默认选中"成都市"
-const selectedKeys = ref<number[]>([3])
+const selectedKeys = ref<number[]>([3]);
 
 // 处理选中键值变化
 const handleSelectedKeysChange = (keys: number[]) => {
-    selectedKeys.value = keys
-}
+  selectedKeys.value = keys;
+};
 
 // 处理选中事件
-const handleCheck = (keys: { checked: number[], indeterminate: number[] }) => {
-    console.log('Checked keys:', keys.checked)
-    console.log('Indeterminate keys:', keys.indeterminate)
-}
+const handleCheck = (keys: { checked: number[]; indeterminate: number[] }) => {
+  console.log('Checked keys:', keys.checked);
+  console.log('Indeterminate keys:', keys.indeterminate);
+};
 
 // 处理全选变化
 const handleSelectAllChange = (value: boolean) => {
-    isSelectAll.value = value
-    if (value) {
-        // 在全选模式下,可以设置一个特殊值表示全选
-        selectedKeys.value = [-1] // -1表示全选
-    } else {
-        // 退出全选模式时,恢复之前的选中状态或默认状态
-        selectedKeys.value = [3] // 恢复默认选中"成都市"
-    }
-}
+  isSelectAll.value = value;
+  if (value) {
+    // 在全选模式下,可以设置一个特殊值表示全选
+    selectedKeys.value = [-1]; // -1表示全选
+  } else {
+    // 退出全选模式时,恢复之前的选中状态或默认状态
+    selectedKeys.value = [3]; // 恢复默认选中"成都市"
+  }
+};
 </script>
 
 <style scoped>
 .permission-tree-demo {
-    padding: 20px;
-    max-width: 800px;
-    margin: 0 auto;
+  padding: 20px;
+  max-width: 800px;
+  margin: 0 auto;
 }
 
 .demo-content {
-    padding: 20px 0;
+  padding: 20px 0;
 }
 
 .demo-content ul {
-    margin: 15px 0;
-    padding-left: 20px;
+  margin: 15px 0;
+  padding-left: 20px;
 }
 
 .demo-content li {
-    margin: 8px 0;
-    line-height: 1.5;
+  margin: 8px 0;
+  line-height: 1.5;
 }
 
 .tree-container {
-    margin: 20px 0;
-    padding: 15px;
-    border: 1px solid #e4e7ed;
-    border-radius: 4px;
-    background-color: #f5f7fa;
-    min-height: 200px;
+  margin: 20px 0;
+  padding: 15px;
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+  background-color: #f5f7fa;
+  min-height: 200px;
 }
 
 .select-all-message {
-    text-align: center;
-    padding: 40px 20px;
-    color: #909399;
+  text-align: center;
+  padding: 40px 20px;
+  color: #909399;
 }
 
 .select-all-message p {
-    margin: 10px 0;
+  margin: 10px 0;
 }
 
 .selected-info {
-    margin-top: 20px;
-    padding: 15px;
-    background-color: #ecf5ff;
-    border-radius: 4px;
+  margin-top: 20px;
+  padding: 15px;
+  background-color: #ecf5ff;
+  border-radius: 4px;
 }
 
 .selected-info h4 {
-    margin-top: 0;
-    color: #303133;
+  margin-top: 0;
+  color: #303133;
 }
 
 .selected-info p {
-    font-family: monospace;
-    background-color: #fff;
-    padding: 10px;
-    border-radius: 4px;
-    border: 1px solid #e4e7ed;
-    margin: 10px 0 0 0;
+  font-family: monospace;
+  background-color: #fff;
+  padding: 10px;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+  margin: 10px 0 0 0;
 }
 
 .instructions {
-    margin-top: 20px;
-    padding: 15px;
-    background-color: #f0f9eb;
-    border-radius: 4px;
-    border-left: 4px solid #67c23a;
+  margin-top: 20px;
+  padding: 15px;
+  background-color: #f0f9eb;
+  border-radius: 4px;
+  border-left: 4px solid #67c23a;
 }
 
 .instructions h4 {
-    margin-top: 0;
-    color: #303133;
+  margin-top: 0;
+  color: #303133;
 }
 
 .instructions ol {
-    padding-left: 20px;
-    margin: 10px 0 0 0;
+  padding-left: 20px;
+  margin: 10px 0 0 0;
 }
 
 .instructions li {
-    margin: 8px 0;
+  margin: 8px 0;
 }
-</style>
+</style>

+ 92 - 93
src/views/demo/TestPermissionTree.vue

@@ -1,144 +1,143 @@
 <template>
-    <div class="test-permission-tree">
-        <el-card shadow="never">
-            <template #header>
-                <h3>权限树测试</h3>
-            </template>
-
-            <div class="test-content">
-                <p>测试当选中"成都市"时,"四川省"是否保持半选状态而不是全选状态</p>
-
-                <div class="tree-container">
-                    <data-permission-tree :data="treeData" :selected-keys="selectedKeys"
-                        @update:selectedKeys="handleSelectedKeysChange" @check="handleCheck" />
-                </div>
-
-                <div class="selected-info">
-                    <h4>当前选中的节点ID:</h4>
-                    <p>{{ selectedKeys }}</p>
-
-                    <h4>当前半选的节点ID:</h4>
-                    <p>{{ indeterminateKeys }}</p>
-                </div>
-
-                <div class="actions">
-                    <el-button @click="selectChengdu">选中成都市</el-button>
-                    <el-button @click="clearSelection">清空选择</el-button>
-                </div>
-            </div>
-        </el-card>
-    </div>
+  <div class="test-permission-tree">
+    <el-card shadow="never">
+      <template #header>
+        <h3>权限树测试</h3>
+      </template>
+
+      <div class="test-content">
+        <p>测试当选中"成都市"时,"四川省"是否保持半选状态而不是全选状态</p>
+
+        <div class="tree-container">
+          <data-permission-tree :data="treeData" :selected-keys="selectedKeys" @update:selectedKeys="handleSelectedKeysChange" @check="handleCheck" />
+        </div>
+
+        <div class="selected-info">
+          <h4>当前选中的节点ID:</h4>
+          <p>{{ selectedKeys }}</p>
+
+          <h4>当前半选的节点ID:</h4>
+          <p>{{ indeterminateKeys }}</p>
+        </div>
+
+        <div class="actions">
+          <el-button @click="selectChengdu">选中成都市</el-button>
+          <el-button @click="clearSelection">清空选择</el-button>
+        </div>
+      </div>
+    </el-card>
+  </div>
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue'
-import DataPermissionTree from '@/components/DataPermisionTree/index.vue'
-import type { TreeData, CheckedKeys } from '@/components/DataPermisionTree/types'
+import { ref } from 'vue';
+import DataPermissionTree from '@/components/DataPermisionTree/index.vue';
+import type { TreeData, CheckedKeys } from '@/components/DataPermisionTree/types';
 
 // 树形数据示例 - 模拟地理层级结构
 const treeData: TreeData[] = [
-    {
-        id: 1,
-        name: '中国',
+  {
+    id: 1,
+    name: '中国',
+    children: [
+      {
+        id: 2,
+        name: '四川省',
         children: [
-            {
-                id: 2,
-                name: '四川省',
-                children: [
-                    { id: 3, name: '成都市' },
-                    { id: 4, name: '绵阳市' },
-                    { id: 5, name: '德阳市' }
-                ]
-            },
-            {
-                id: 6,
-                name: '广东省',
-                children: [
-                    { id: 7, name: '广州市' },
-                    { id: 8, name: '深圳市' },
-                    { id: 9, name: '珠海市' }
-                ]
-            }
+          { id: 3, name: '成都市' },
+          { id: 4, name: '绵阳市' },
+          { id: 5, name: '德阳市' }
         ]
-    }
-]
+      },
+      {
+        id: 6,
+        name: '广东省',
+        children: [
+          { id: 7, name: '广州市' },
+          { id: 8, name: '深圳市' },
+          { id: 9, name: '珠海市' }
+        ]
+      }
+    ]
+  }
+];
 
 // 选中的键值
-const selectedKeys = ref<number[]>([])
+const selectedKeys = ref<number[]>([]);
 
 // 半选的键值
-const indeterminateKeys = ref<number[]>([])
+const indeterminateKeys = ref<number[]>([]);
 
 // 处理选中键值变化
 const handleSelectedKeysChange = (keys: number[]) => {
-    selectedKeys.value = keys
-}
+  selectedKeys.value = keys;
+};
 
 // 处理选中事件
 const handleCheck = (keys: CheckedKeys) => {
-    console.log('Checked keys:', keys.checked)
-    console.log('Indeterminate keys:', keys.indeterminate)
-    selectedKeys.value = keys.checked
-    indeterminateKeys.value = keys.indeterminate
-}
+  console.log('Checked keys:', keys.checked);
+  console.log('Indeterminate keys:', keys.indeterminate);
+  selectedKeys.value = keys.checked;
+  indeterminateKeys.value = keys.indeterminate;
+};
 
 // 选中成都市
 const selectChengdu = () => {
-    selectedKeys.value = [3]
-}
+  selectedKeys.value = [3];
+};
 
 // 清空选择
 const clearSelection = () => {
-    selectedKeys.value = []
-}
+  selectedKeys.value = [];
+};
 </script>
 
 <style scoped>
 .test-permission-tree {
-    padding: 20px;
-    max-width: 800px;
-    margin: 0 auto;
+  padding: 20px;
+  max-width: 800px;
+  margin: 0 auto;
 }
 
 .test-content {
-    padding: 20px 0;
+  padding: 20px 0;
 }
 
 .tree-container {
-    margin: 20px 0;
-    padding: 15px;
-    border: 1px solid #e4e7ed;
-    border-radius: 4px;
-    background-color: #f5f7fa;
-    min-height: 200px;
+  margin: 20px 0;
+  padding: 15px;
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+  background-color: #f5f7fa;
+  min-height: 200px;
 }
 
 .selected-info {
-    margin-top: 20px;
-    padding: 15px;
-    background-color: #ecf5ff;
-    border-radius: 4px;
+  margin-top: 20px;
+  padding: 15px;
+  background-color: #ecf5ff;
+  border-radius: 4px;
 }
 
 .selected-info h4 {
-    margin-top: 0;
-    color: #303133;
+  margin-top: 0;
+  color: #303133;
 }
 
 .selected-info p {
-    font-family: monospace;
-    background-color: #fff;
-    padding: 10px;
-    border-radius: 4px;
-    border: 1px solid #e4e7ed;
-    margin: 10px 0 0 0;
+  font-family: monospace;
+  background-color: #fff;
+  padding: 10px;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+  margin: 10px 0 0 0;
 }
 
 .actions {
-    margin-top: 20px;
+  margin-top: 20px;
 }
 
 .actions .el-button {
-    margin-right: 10px;
+  margin-right: 10px;
 }
-</style>
+</style>

+ 92 - 92
src/views/demo/TreeDemo.vue

@@ -1,139 +1,139 @@
 <template>
-    <div class="tree-demo">
-        <h2>自定义树形组件演示</h2>
-
-        <div class="demo-section">
-            <h3>功能说明</h3>
-            <ul>
-                <li>这是一个完全使用原生HTML/CSS/JavaScript实现的树形组件</li>
-                <li>不依赖任何第三方UI库</li>
-                <li>父节点在子节点被部分选中时显示为半选状态</li>
-                <li>即使所有子节点都被选中,父节点也不会自动变为全选状态</li>
-            </ul>
-        </div>
-
-        <div class="demo-section">
-            <h3>演示案例</h3>
-            <p>如您所述:选择"成都市"时,其父级"四川省"应该显示为半选状态而不是全选状态</p>
-
-            <div class="tree-container">
-                <data-permission-tree :data="treeData" v-model="selectedKeys" />
-            </div>
-
-            <div class="result-section">
-                <h4>当前选中的节点:</h4>
-                <p>{{ selectedKeys }}</p>
-
-                <h4>说明:</h4>
-                <ul>
-                    <li>尝试选中"成都市",观察"四川省"的状态</li>
-                    <li>"四川省"应该显示为半选状态(方框中有横线)</li>
-                    <li>即使您选中"绵阳市"和"德阳市","四川省"仍然保持半选状态</li>
-                    <li>只有直接点击"四川省"复选框才会使其变为全选状态</li>
-                </ul>
-            </div>
-        </div>
+  <div class="tree-demo">
+    <h2>自定义树形组件演示</h2>
+
+    <div class="demo-section">
+      <h3>功能说明</h3>
+      <ul>
+        <li>这是一个完全使用原生HTML/CSS/JavaScript实现的树形组件</li>
+        <li>不依赖任何第三方UI库</li>
+        <li>父节点在子节点被部分选中时显示为半选状态</li>
+        <li>即使所有子节点都被选中,父节点也不会自动变为全选状态</li>
+      </ul>
     </div>
+
+    <div class="demo-section">
+      <h3>演示案例</h3>
+      <p>如您所述:选择"成都市"时,其父级"四川省"应该显示为半选状态而不是全选状态</p>
+
+      <div class="tree-container">
+        <data-permission-tree :data="treeData" v-model="selectedKeys" />
+      </div>
+
+      <div class="result-section">
+        <h4>当前选中的节点:</h4>
+        <p>{{ selectedKeys }}</p>
+
+        <h4>说明:</h4>
+        <ul>
+          <li>尝试选中"成都市",观察"四川省"的状态</li>
+          <li>"四川省"应该显示为半选状态(方框中有横线)</li>
+          <li>即使您选中"绵阳市"和"德阳市","四川省"仍然保持半选状态</li>
+          <li>只有直接点击"四川省"复选框才会使其变为全选状态</li>
+        </ul>
+      </div>
+    </div>
+  </div>
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue'
-import DataPermissionTree from '@/components/DataPermisionTree/index.vue'
-import type { TreeData } from '@/components/DataPermisionTree/types'
+import { ref } from 'vue';
+import DataPermissionTree from '@/components/DataPermisionTree/index.vue';
+import type { TreeData } from '@/components/DataPermisionTree/types';
 
 // 树形数据
 const treeData: TreeData[] = [
-    {
-        id: 1,
-        name: '中国',
+  {
+    id: 1,
+    name: '中国',
+    children: [
+      {
+        id: 2,
+        name: '四川省',
+        children: [
+          { id: 3, name: '成都市' },
+          { id: 4, name: '绵阳市' },
+          { id: 5, name: '德阳市' }
+        ]
+      },
+      {
+        id: 6,
+        name: '广东省',
         children: [
-            {
-                id: 2,
-                name: '四川省',
-                children: [
-                    { id: 3, name: '成都市' },
-                    { id: 4, name: '绵阳市' },
-                    { id: 5, name: '德阳市' }
-                ]
-            },
-            {
-                id: 6,
-                name: '广东省',
-                children: [
-                    { id: 7, name: '广州市' },
-                    { id: 8, name: '深圳市' },
-                    { id: 9, name: '珠海市' }
-                ]
-            }
+          { id: 7, name: '广州市' },
+          { id: 8, name: '深圳市' },
+          { id: 9, name: '珠海市' }
         ]
-    }
-]
+      }
+    ]
+  }
+];
 
 // 选中的节点ID
-const selectedKeys = ref<number[]>([])
+const selectedKeys = ref<number[]>([]);
 </script>
 
 <style scoped>
 .tree-demo {
-    padding: 20px;
-    max-width: 800px;
-    margin: 0 auto;
+  padding: 20px;
+  max-width: 800px;
+  margin: 0 auto;
 }
 
 .demo-section {
-    margin-bottom: 30px;
+  margin-bottom: 30px;
 }
 
 .demo-section h3 {
-    color: #333;
-    margin-bottom: 15px;
+  color: #333;
+  margin-bottom: 15px;
 }
 
 .demo-section ul {
-    padding-left: 20px;
+  padding-left: 20px;
 }
 
 .demo-section li {
-    margin-bottom: 8px;
-    line-height: 1.5;
+  margin-bottom: 8px;
+  line-height: 1.5;
 }
 
 .tree-container {
-    margin: 20px 0;
-    padding: 20px;
-    border: 1px solid #ddd;
-    border-radius: 4px;
-    background-color: #f9f9f9;
-    min-height: 200px;
+  margin: 20px 0;
+  padding: 20px;
+  border: 1px solid #ddd;
+  border-radius: 4px;
+  background-color: #f9f9f9;
+  min-height: 200px;
 }
 
 .result-section {
-    margin-top: 20px;
-    padding: 15px;
-    background-color: #e8f4ff;
-    border-radius: 4px;
+  margin-top: 20px;
+  padding: 15px;
+  background-color: #e8f4ff;
+  border-radius: 4px;
 }
 
 .result-section h4 {
-    margin-top: 0;
-    color: #333;
+  margin-top: 0;
+  color: #333;
 }
 
 .result-section p {
-    font-family: monospace;
-    background-color: #fff;
-    padding: 10px;
-    border-radius: 4px;
-    border: 1px solid #ddd;
+  font-family: monospace;
+  background-color: #fff;
+  padding: 10px;
+  border-radius: 4px;
+  border: 1px solid #ddd;
 }
 
 .result-section ul {
-    padding-left: 20px;
-    margin: 10px 0;
+  padding-left: 20px;
+  margin: 10px 0;
 }
 
 .result-section li {
-    margin-bottom: 8px;
-    line-height: 1.5;
+  margin-bottom: 8px;
+  line-height: 1.5;
 }
-</style>
+</style>

+ 80 - 81
src/views/demo/TreeTest.vue

@@ -1,120 +1,119 @@
 <template>
-    <div class="tree-test">
-        <el-card shadow="never">
-            <template #header>
-                <h3>树形组件测试</h3>
-            </template>
-
-            <div class="test-content">
-                <p>测试目标:当选中"成都市"时,"四川省"应该保持半选状态,而不是全选状态</p>
-
-                <div class="tree-container">
-                    <data-permission-tree :data="treeData" :selected-keys="selectedKeys"
-                        @update:selectedKeys="handleSelectedKeysChange" @check="handleCheck" />
-                </div>
-
-                <div class="info-panel">
-                    <h4>当前选中的节点:</h4>
-                    <p>{{ selectedKeys }}</p>
-
-                    <h4>当前半选的节点:</h4>
-                    <p>{{ indeterminateKeys }}</p>
-                </div>
-            </div>
-        </el-card>
-    </div>
+  <div class="tree-test">
+    <el-card shadow="never">
+      <template #header>
+        <h3>树形组件测试</h3>
+      </template>
+
+      <div class="test-content">
+        <p>测试目标:当选中"成都市"时,"四川省"应该保持半选状态,而不是全选状态</p>
+
+        <div class="tree-container">
+          <data-permission-tree :data="treeData" :selected-keys="selectedKeys" @update:selectedKeys="handleSelectedKeysChange" @check="handleCheck" />
+        </div>
+
+        <div class="info-panel">
+          <h4>当前选中的节点:</h4>
+          <p>{{ selectedKeys }}</p>
+
+          <h4>当前半选的节点:</h4>
+          <p>{{ indeterminateKeys }}</p>
+        </div>
+      </div>
+    </el-card>
+  </div>
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue'
-import DataPermissionTree from '@/components/DataPermisionTree/index.vue'
-import type { TreeData, CheckedKeys } from '@/components/DataPermisionTree/types'
+import { ref } from 'vue';
+import DataPermissionTree from '@/components/DataPermisionTree/index.vue';
+import type { TreeData, CheckedKeys } from '@/components/DataPermisionTree/types';
 
 // 树形数据示例 - 模拟地理层级结构
 const treeData: TreeData[] = [
-    {
-        id: 1,
-        name: '中国',
+  {
+    id: 1,
+    name: '中国',
+    children: [
+      {
+        id: 2,
+        name: '四川省',
         children: [
-            {
-                id: 2,
-                name: '四川省',
-                children: [
-                    { id: 3, name: '成都市' },
-                    { id: 4, name: '绵阳市' },
-                    { id: 5, name: '德阳市' }
-                ]
-            },
-            {
-                id: 6,
-                name: '广东省',
-                children: [
-                    { id: 7, name: '广州市' },
-                    { id: 8, name: '深圳市' },
-                    { id: 9, name: '珠海市' }
-                ]
-            }
+          { id: 3, name: '成都市' },
+          { id: 4, name: '绵阳市' },
+          { id: 5, name: '德阳市' }
         ]
-    }
-]
+      },
+      {
+        id: 6,
+        name: '广东省',
+        children: [
+          { id: 7, name: '广州市' },
+          { id: 8, name: '深圳市' },
+          { id: 9, name: '珠海市' }
+        ]
+      }
+    ]
+  }
+];
 
 // 选中键值
-const selectedKeys = ref<number[]>([])
+const selectedKeys = ref<number[]>([]);
 
 // 半选键值
-const indeterminateKeys = ref<number[]>([])
+const indeterminateKeys = ref<number[]>([]);
 
 // 处理选中键值变化
 const handleSelectedKeysChange = (keys: number[]) => {
-    selectedKeys.value = keys
-}
+  selectedKeys.value = keys;
+};
 
 // 处理选中事件
 const handleCheck = (keys: CheckedKeys) => {
-    console.log('Checked keys:', keys.checked)
-    console.log('Indeterminate keys:', keys.indeterminate)
-    indeterminateKeys.value = keys.indeterminate
-}
+  console.log('Checked keys:', keys.checked);
+  console.log('Indeterminate keys:', keys.indeterminate);
+  indeterminateKeys.value = keys.indeterminate;
+};
 </script>
 
 <style scoped>
 .tree-test {
-    padding: 20px;
-    max-width: 800px;
-    margin: 0 auto;
+  padding: 20px;
+  max-width: 800px;
+  margin: 0 auto;
 }
 
 .test-content {
-    padding: 20px 0;
+  padding: 20px 0;
 }
 
 .tree-container {
-    margin: 20px 0;
-    padding: 15px;
-    border: 1px solid #e4e7ed;
-    border-radius: 4px;
-    background-color: #f5f7fa;
-    min-height: 200px;
+  margin: 20px 0;
+  padding: 15px;
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+  background-color: #f5f7fa;
+  min-height: 200px;
 }
 
 .info-panel {
-    margin-top: 20px;
-    padding: 15px;
-    background-color: #ecf5ff;
-    border-radius: 4px;
+  margin-top: 20px;
+  padding: 15px;
+  background-color: #ecf5ff;
+  border-radius: 4px;
 }
 
 .info-panel h4 {
-    margin-top: 0;
-    color: #303133;
+  margin-top: 0;
+  color: #303133;
 }
 
 .info-panel p {
-    font-family: monospace;
-    background-color: #fff;
-    padding: 10px;
-    border-radius: 4px;
-    border: 1px solid #e4e7ed;
-    margin: 10px 0 0 0;
+  font-family: monospace;
+  background-color: #fff;
+  padding: 10px;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+  margin: 10px 0 0 0;
 }
-</style>
+</style>

+ 28 - 55
src/views/document/folder/document/DocumentList.vue

@@ -25,25 +25,16 @@
         </template>
       </el-table-column>
       <el-table-column prop="name" :label="t('document.document.documentList.name')" min-width="150" show-overflow-tooltip />
-      <el-table-column
-        prop="specification"
-        :label="t('document.document.documentList.specification')"
-        min-width="120"
-        show-overflow-tooltip
-      >
+      <el-table-column prop="specification" :label="t('document.document.documentList.specification')" min-width="120" show-overflow-tooltip>
         <template #default="scope">
-          <dict-tag 
-            v-if="scope.row.specification" 
-            :options="getSpecificationDict(scope.row.specificationType)" 
-            :value="scope.row.specification" 
-          />
+          <dict-tag v-if="scope.row.specification" :options="getSpecificationDict(scope.row.specificationType)" :value="scope.row.specification" />
           <span v-else>-</span>
         </template>
       </el-table-column>
-      <el-table-column 
-        prop="planType" 
-        :label="t('document.document.documentList.planDocumentType')" 
-        min-width="120" 
+      <el-table-column
+        prop="planType"
+        :label="t('document.document.documentList.planDocumentType')"
+        min-width="120"
         align="center"
         show-overflow-tooltip
       >
@@ -57,7 +48,8 @@
           <DocumentStatusTag :status="scope.row.status" />
         </template>
       </el-table-column>
-      <el-table-column prop="submitter" :label="t('document.document.documentList.submitter')" width="120" align="center" />
+      <el-table-column prop="planSubmitterName" :label="t('document.document.documentList.planSubmitter')" width="120" align="center" />
+      <el-table-column prop="submitterName" :label="t('document.document.documentList.submitter')" width="120" align="center" />
       <el-table-column prop="submitDeadline" :label="t('document.document.documentList.submitDeadline')" width="110" align="center">
         <template #default="scope">
           <span v-if="scope.row.submitDeadline">{{ parseTime(scope.row.submitDeadline, '{y}-{m}-{d}') }}</span>
@@ -100,17 +92,17 @@
             v-hasPermi="['document:document:audit']"
             type="primary"
             :icon="Select"
-            style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px;"
+            style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px"
             @click="handleAuditClick(scope.row)"
           >
             {{ t('document.document.button.audit') }}
           </el-button>
           <el-button
-            v-if="(scope.row.status === 0 || scope.row.status === 2) && scope.row.submitterId === userStore.userId"
+            v-if="(scope.row.status === 0 || scope.row.status === 2) && scope.row.planSubmitter === userStore.userId"
             v-hasPermi="['document:document:submit']"
             type="success"
             :icon="Upload"
-            style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px;"
+            style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px"
             @click="handleSubmit(scope.row)"
           >
             {{ t('document.document.button.submit') }}
@@ -120,7 +112,7 @@
             v-hasPermi="['document:document:confirmSubmit']"
             type="danger"
             icon="Delete"
-            style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px;"
+            style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px"
             @click="handleConfirmSubmit(scope.row)"
           >
             {{ t('document.document.button.confirmSubmit') }}
@@ -129,7 +121,7 @@
             v-hasPermi="['document:document:mark']"
             type="warning"
             icon="Flag"
-            style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px;"
+            style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px"
             @click="handleMark(scope.row)"
           >
             {{ t('document.document.button.mark') }}
@@ -138,7 +130,7 @@
             v-if="scope.row.ossId"
             type="info"
             icon="Download"
-            style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px;"
+            style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px"
             @click="handleDownload(scope.row)"
           >
             {{ t('document.document.button.download') }}
@@ -147,7 +139,7 @@
             v-hasPermi="['document:document:logAudit']"
             type="primary"
             icon="DocumentCopy"
-            style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px;"
+            style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px"
             @click="handleViewAuditLog(scope.row)"
           >
             {{ t('document.document.button.viewAuditLog') }}
@@ -157,7 +149,7 @@
             v-hasPermi="['document:document:filing']"
             type="success"
             icon="UploadFilled"
-            style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px;"
+            style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px"
             @click="handleArchive(scope.row)"
           >
             {{ t('document.document.button.archive') }}
@@ -180,11 +172,11 @@
       <el-form ref="markFormRef" :model="markForm" :rules="markRules" label-width="120px">
         <el-form-item :label="t('document.document.markForm.specification')" prop="type">
           <el-select v-model="markForm.type" :placeholder="t('document.document.markForm.specificationPlaceholder')" clearable style="width: 100%">
-            <el-option 
-              v-for="dict in getSpecificationDict(currentDocument?.specificationType)" 
-              :key="dict.value" 
-              :label="parseI18nName(dict.label)" 
-              :value="dict.value" 
+            <el-option
+              v-for="dict in getSpecificationDict(currentDocument?.specificationType)"
+              :key="dict.value"
+              :label="parseI18nName(dict.label)"
+              :value="dict.value"
             />
           </el-select>
         </el-form-item>
@@ -213,20 +205,15 @@
     </el-dialog>
 
     <!-- 文档审核记录对话框 -->
-    <AuditLogDialog 
-      v-model:visible="auditLogDialog.visible" 
-      :document-id="auditLogDialog.documentId" 
+    <AuditLogDialog
+      v-model:visible="auditLogDialog.visible"
+      :document-id="auditLogDialog.documentId"
       :api-function="listDocumentAuditLog"
       i18n-prefix="document.document"
     />
 
     <!-- 审核文档对话框 -->
-    <el-dialog 
-      v-model="auditDialog.visible" 
-      :title="t('document.document.dialog.auditDocument')" 
-      width="500px"
-      @close="handleAuditDialogClose"
-    >
+    <el-dialog v-model="auditDialog.visible" :title="t('document.document.dialog.auditDocument')" width="500px" @close="handleAuditDialogClose">
       <el-form ref="auditFormRef" :model="auditForm" :rules="auditRules" label-width="100px">
         <el-form-item label="审核结果" prop="result">
           <el-radio-group v-model="auditForm.result">
@@ -235,14 +222,7 @@
           </el-radio-group>
         </el-form-item>
         <el-form-item v-if="auditForm.result === 2" label="驳回理由" prop="rejectReason">
-          <el-input 
-            v-model="auditForm.rejectReason" 
-            type="textarea" 
-            :rows="4"
-            placeholder="请输入驳回理由"
-            maxlength="500"
-            show-word-limit
-          />
+          <el-input v-model="auditForm.rejectReason" type="textarea" :rows="4" placeholder="请输入驳回理由" maxlength="500" show-word-limit />
         </el-form-item>
       </el-form>
       <template #footer>
@@ -257,14 +237,7 @@
 <script setup lang="ts">
 import { ref, reactive, nextTick, getCurrentInstance, computed, watch, toRefs } from 'vue';
 import { useI18n } from 'vue-i18n';
-import {
-  listDocument,
-  markDocument,
-  submitDocument,
-  confirmSubmit,
-  filingDocument,
-  downloadDocumentFile
-} from '@/api/document/document';
+import { listDocument, markDocument, submitDocument, confirmSubmit, filingDocument, downloadDocumentFile } from '@/api/document/document';
 import { DocumentQuery, DocumentVO, DocumentMarkForm, DocumentSubmitForm } from '@/api/document/document/types';
 import { FolderListVO } from '@/api/document/folder/types';
 import { Select, Upload } from '@element-plus/icons-vue';
@@ -496,7 +469,7 @@ const handleAuditConfirm = async () => {
       result: auditForm.value.result,
       rejectReason: auditForm.value.result === 2 ? auditForm.value.rejectReason : undefined
     };
-    
+
     await auditDocument(submitData);
     ElMessage.success(t('document.document.message.auditSuccess'));
     auditDialog.visible = false;

+ 54 - 62
src/views/document/folder/document/index.vue

@@ -10,17 +10,13 @@
 
       <div class="content-wrapper">
         <!-- 文件夹树组件 -->
-        <FolderTree
-          ref="folderTreeRef"
-          :project-id="projectId"
-          @folder-click="handleFolderClick"
-          @add-document="handleAddDocument"
-          @refresh="handleTreeRefresh"
-        />
+        <FolderTree ref="folderTreeRef" :project-id="projectId" @folder-click="handleFolderClick"
+          @add-document="handleAddDocument" @refresh="handleTreeRefresh" />
 
         <!-- 文档列表组件 -->
         <div class="content-container">
-          <DocumentList ref="documentListRef" :selected-folder="selectedFolder" :tree-data="treeData" :project-id="projectId" />
+          <DocumentList ref="documentListRef" :selected-folder="selectedFolder" :tree-data="treeData"
+            :project-id="projectId" />
         </div>
       </div>
     </el-card>
@@ -30,9 +26,9 @@
       <el-form ref="documentFormRef" :model="documentForm" :rules="documentRules" label-width="140px">
         <el-form-item
           :label="documentForm.type === 1 ? t('document.document.documentForm.planName') : t('document.document.documentForm.name')"
-          prop="name"
-        >
-          <el-input v-model="documentForm.name" :placeholder="t('document.document.documentForm.namePlaceholder')" clearable />
+          prop="name">
+          <el-input v-model="documentForm.name" :placeholder="t('document.document.documentForm.namePlaceholder')"
+            clearable />
         </el-form-item>
 
         <el-form-item :label="t('document.document.documentForm.type')" prop="type" v-if="hasAddPlanPermission">
@@ -42,48 +38,33 @@
           </el-radio-group>
         </el-form-item>
 
-        <el-form-item v-if="documentForm.type === 1" :label="t('document.document.documentForm.submitter')" prop="submitterId">
-          <el-select
-            v-model="documentForm.submitterId"
-            filterable
-            remote
-            reserve-keyword
-            :placeholder="t('document.document.documentForm.submitterPlaceholder')"
-            :remote-method="searchSubmitters"
-            :loading="submitterSearchLoading"
-            style="width: 100%"
-          >
-            <el-option
-              v-for="submitter in submitterOptions"
-              :key="submitter.id"
-              :label="`${submitter.name} / ${submitter.dept} --- ${submitter.phoneNumber}`"
-              :value="submitter.id"
-            />
+        <el-form-item v-if="documentForm.type === 1" :label="t('document.document.documentForm.planSubmitter')"
+          prop="planSubmitter">
+          <el-select v-model="documentForm.planSubmitter" filterable remote reserve-keyword
+            :placeholder="t('document.document.documentForm.planSubmitterPlaceholder')"
+            :remote-method="searchSubmitters" :loading="submitterSearchLoading" style="width: 100%">
+            <el-option v-for="submitter in submitterOptions" :key="submitter.id"
+              :label="`${submitter.name} / ${submitter.dept} --- ${submitter.phoneNumber}`" :value="submitter.id" />
           </el-select>
         </el-form-item>
 
-        <el-form-item v-if="documentForm.type === 1" :label="t('document.document.documentForm.submitDeadline')" prop="submitDeadline">
-          <el-date-picker
-            v-model="documentForm.submitDeadline"
-            type="date"
-            value-format="YYYY-MM-DD"
-            :placeholder="t('document.document.documentForm.submitDeadlinePlaceholder')"
-            style="width: 100%"
-          />
+        <el-form-item v-if="documentForm.type === 1" :label="t('document.document.documentForm.submitDeadline')"
+          prop="submitDeadline">
+          <el-date-picker v-model="documentForm.submitDeadline" type="date" value-format="YYYY-MM-DD"
+            :placeholder="t('document.document.documentForm.submitDeadlinePlaceholder')" style="width: 100%" />
         </el-form-item>
 
-        <el-form-item v-if="documentForm.type === 1" :label="t('document.document.documentForm.planType')" prop="planType">
-          <el-select
-            v-model="documentForm.planType"
-            :placeholder="t('document.document.documentForm.planTypePlaceholder')"
-            clearable
-            style="width: 100%"
-          >
-            <el-option v-for="dict in plan_document_type" :key="dict.value" :label="parseI18nName(dict.label)" :value="dict.value" />
+        <el-form-item v-if="documentForm.type === 1" :label="t('document.document.documentForm.planType')"
+          prop="planType">
+          <el-select v-model="documentForm.planType"
+            :placeholder="t('document.document.documentForm.planTypePlaceholder')" clearable style="width: 100%">
+            <el-option v-for="dict in plan_document_type" :key="dict.value" :label="parseI18nName(dict.label)"
+              :value="dict.value" />
           </el-select>
         </el-form-item>
 
-        <el-form-item v-if="documentForm.type === 0" :label="t('document.document.documentForm.actualFile')" prop="ossId">
+        <el-form-item v-if="documentForm.type === 0" :label="t('document.document.documentForm.actualFile')"
+          prop="ossId">
           <fileUpload v-model="uploadedFileId" :limit="1" />
         </el-form-item>
 
@@ -92,15 +73,12 @@
         </el-form-item>
 
         <el-form-item :label="t('document.document.documentForm.sendFlag')" prop="sendFlag">
-          <el-switch
-            v-model="documentForm.sendFlag"
-            :active-value="true"
-            :inactive-value="false"
-          />
+          <el-switch v-model="documentForm.sendFlag" :active-value="true" :inactive-value="false" />
         </el-form-item>
 
         <el-form-item :label="t('document.document.documentForm.note')" prop="note">
-          <el-input v-model="documentForm.note" type="textarea" :rows="4" :placeholder="t('document.document.documentForm.notePlaceholder')" />
+          <el-input v-model="documentForm.note" type="textarea" :rows="4"
+            :placeholder="t('document.document.documentForm.notePlaceholder')" />
         </el-form-item>
       </el-form>
       <template #footer>
@@ -175,7 +153,7 @@ const initDocumentFormData: DocumentForm = {
   id: undefined,
   name: '',
   type: 0,
-  submitterId: undefined,
+  planSubmitter: undefined,
   folderId: undefined,
   submitDeadline: undefined,
   planType: undefined,
@@ -200,7 +178,7 @@ let submitterSearchTimer: NodeJS.Timeout | null = null;
 // 文档表单验证规则
 const documentRules = {
   name: [{ required: true, message: t('document.document.documentRule.nameRequired'), trigger: 'blur' }],
-  submitterId: [{ required: true, message: t('document.document.documentRule.submitterRequired'), trigger: 'change' }],
+  planSubmitter: [{ required: true, message: t('document.document.documentRule.planSubmitterRequired'), trigger: 'change' }],
   ossId: [{ required: true, message: t('document.document.documentRule.fileRequired'), trigger: 'change' }]
 };
 
@@ -245,7 +223,15 @@ const resetDocumentForm = () => {
   if (!hasAddPlanPermission.value) {
     documentForm.value.type = 0;
   }
-  documentForm.value.submitterId = userStore.userId;
+  // 非计划文件,计划递交人默认为空,递交人为自己
+  if (documentForm.value.type === 0) {
+    documentForm.value.planSubmitter = undefined;
+    documentForm.value.submitter = userStore.userId;
+  } else {
+    // 计划文件,计划递交人需要填写,递交人不默认
+    documentForm.value.planSubmitter = undefined;
+    documentForm.value.submitter = undefined;
+  }
   documentForm.value.projectId = props.projectId;
   submitterOptions.value = [];
   documentFormRef.value?.resetFields();
@@ -254,13 +240,17 @@ const resetDocumentForm = () => {
 // 处理文档类型变化
 const handleDocumentTypeChange = (value: number) => {
   if (value === 0) {
-    documentForm.value.submitterId = userStore.userId;
+    // 非计划文件,计划递交人默认为空,递交人为自己
+    documentForm.value.planSubmitter = undefined;
+    documentForm.value.submitter = userStore.userId;
     documentForm.value.submitDeadline = undefined;
     documentForm.value.planType = undefined;
   } else {
-    documentForm.value.submitterId = undefined;
+    // 计划文件,计划递交人需要填写,递交人不默认
+    documentForm.value.planSubmitter = undefined;
+    documentForm.value.submitter = undefined;
   }
-};
+}
 
 // 搜索递交人
 const searchSubmitters = async (query: string) => {
@@ -303,14 +293,13 @@ watch(uploadedFileId, (newVal) => {
       const now = new Date();
       documentForm.value.submitTime = proxy?.parseTime(now, '{y}-{m}-{d} {h}:{i}:{s}');
 
-      if (documentForm.value.type === 0) {
-        documentForm.value.submitterId = userStore.userId;
-      }
+      // 设置递交人为当前用户
+      documentForm.value.submitter = userStore.userId;
     }
   } else {
     documentForm.value.ossId = undefined;
     documentForm.value.submitTime = undefined;
-    documentForm.value.submitterId = undefined;
+    documentForm.value.submitter = undefined;
   }
 });
 
@@ -332,7 +321,10 @@ const submitDocumentForm = () => {
           id: documentForm.value.id || 0,
           name: documentForm.value.name || '',
           type: documentForm.value.type !== undefined ? documentForm.value.type : 0,
-          submitterId: documentForm.value.submitterId || (documentForm.value.type === 0 ? userStore.userId : 0),
+          // 非计划文件:计划递交人默认为空,递交人为当前用户
+          // 计划文件:计划递交人需要填写,递交人不默认
+          planSubmitter: documentForm.value.type === 0 ? undefined : documentForm.value.planSubmitter,
+          submitter: documentForm.value.type === 0 ? userStore.userId : documentForm.value.submitter,
           folderId: documentForm.value.folderId || 0,
           submitDeadline: documentForm.value.submitDeadline || '',
           planType: documentForm.value.planType || '',

+ 68 - 42
src/views/document/folder/project.vue

@@ -2,64 +2,93 @@
   <div>
     <!-- 项目列表视图 -->
     <div v-if="!showDocumentView">
-      <transition :enter-active-class="proxy?.animate.searchAnimate.enter"
-        :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <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-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-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
+                  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 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')"
+                <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;" />
+                  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')"
+                <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;" />
+                  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')"
+                <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;" />
+                  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')"
+                <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;" />
+                  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 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>
@@ -99,17 +128,15 @@
               <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.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="120" fixed="right">
             <template #default="scope">
-              <el-button 
-                v-hasPermi="['document:folder:entry']" 
-                type="primary" 
-                icon="ArrowRight" 
-                style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px;"
+              <el-button
+                v-hasPermi="['document:folder:entry']"
+                type="primary"
+                icon="ArrowRight"
+                style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px"
                 @click="handleEnterProject(scope.row)"
               >
                 {{ t('project.management.table.enterProject') }}
@@ -118,8 +145,7 @@
           </el-table-column>
         </el-table>
 
-        <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
-          v-model:limit="queryParams.pageSize" @pagination="getList" />
+        <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
       </el-card>
     </div>
 
@@ -175,13 +201,13 @@ const getList = async () => {
   projectList.value = res.rows;
   total.value = res.total;
   loading.value = false;
-}
+};
 
 /** 搜索按钮操作 */
 const handleQuery = () => {
   queryParams.value.pageNum = 1;
   getList();
-}
+};
 
 /** 重置按钮操作 */
 const resetQuery = () => {
@@ -191,7 +217,7 @@ const resetQuery = () => {
   dateRangeUpdateTime.value = ['', ''];
   queryFormRef.value?.resetFields();
   handleQuery();
-}
+};
 
 /** 进入项目 */
 const handleEnterProject = (row: ProjectVO) => {

+ 23 - 33
src/views/home/taskCenter/audit/index.vue

@@ -10,16 +10,13 @@
       <!-- 搜索栏 -->
       <el-form :model="queryParams" :inline="true" class="search-form">
         <el-form-item label="项目编号">
-          <el-input v-model="queryParams.projectCode" placeholder="请输入项目编号" clearable style="width: 240px"
-            @keyup.enter="handleQuery" />
+          <el-input v-model="queryParams.projectCode" placeholder="请输入项目编号" clearable style="width: 240px" @keyup.enter="handleQuery" />
         </el-form-item>
         <el-form-item label="项目名称">
-          <el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable style="width: 240px"
-            @keyup.enter="handleQuery" />
+          <el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
         </el-form-item>
         <el-form-item label="名称">
-          <el-input v-model="queryParams.name" placeholder="请输入名称" clearable style="width: 240px"
-            @keyup.enter="handleQuery" />
+          <el-input v-model="queryParams.name" placeholder="请输入名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
         </el-form-item>
         <el-form-item>
           <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
@@ -76,20 +73,20 @@
         <!-- 操作列 -->
         <el-table-column label="操作" width="200" align="center" fixed="right" class-name="small-padding fixed-width">
           <template #default="scope">
-            <el-button 
-              v-if="scope.row.status === 1" 
-              type="primary" 
-              icon="Check" 
-              style="padding: 0 5px; font-size: 10px; height: 24px;"
+            <el-button
+              v-if="scope.row.status === 1"
+              type="primary"
+              icon="Check"
+              style="padding: 0 5px; font-size: 10px; height: 24px"
               @click="handleAudit(scope.row)"
             >
               审核
             </el-button>
-            <el-button 
-              v-if="scope.row.ossId" 
-              type="success" 
-              icon="Download" 
-              style="padding: 0 5px; font-size: 10px; height: 24px;"
+            <el-button
+              v-if="scope.row.ossId"
+              type="success"
+              icon="Download"
+              style="padding: 0 5px; font-size: 10px; height: 24px"
               @click="handleDownload(scope.row)"
             >
               下载
@@ -99,17 +96,17 @@
       </el-table>
 
       <!-- 分页 -->
-      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
-        :total="total" @pagination="getTaskList" />
+      <pagination
+        v-show="total > 0"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        :total="total"
+        @pagination="getTaskList"
+      />
     </el-card>
 
     <!-- 审核文档对话框 -->
-    <el-dialog 
-      v-model="auditDialog.visible" 
-      :title="auditDialog.title" 
-      width="500px"
-      @close="handleAuditDialogClose"
-    >
+    <el-dialog v-model="auditDialog.visible" :title="auditDialog.title" width="500px" @close="handleAuditDialogClose">
       <el-form ref="auditFormRef" :model="auditForm" :rules="auditRules" label-width="100px">
         <el-form-item label="审核结果" prop="result">
           <el-radio-group v-model="auditForm.result">
@@ -118,14 +115,7 @@
           </el-radio-group>
         </el-form-item>
         <el-form-item v-if="auditForm.result === 2" label="驳回理由" prop="rejectReason">
-          <el-input 
-            v-model="auditForm.rejectReason" 
-            type="textarea" 
-            :rows="4"
-            placeholder="请输入驳回理由"
-            maxlength="500"
-            show-word-limit
-          />
+          <el-input v-model="auditForm.rejectReason" type="textarea" :rows="4" placeholder="请输入驳回理由" maxlength="500" show-word-limit />
         </el-form-item>
       </el-form>
       <template #footer>
@@ -316,7 +306,7 @@ const handleAuditConfirm = async () => {
       result: auditForm.value.result,
       rejectReason: auditForm.value.result === 2 ? auditForm.value.rejectReason : ''
     };
-    
+
     await auditDocument(submitData);
     ElMessage.success('审批成功');
     auditDialog.visible = false;

+ 27 - 24
src/views/home/taskCenter/filing/index.vue

@@ -10,20 +10,16 @@
       <!-- 搜索栏 -->
       <el-form :model="queryParams" :inline="true" class="search-form">
         <el-form-item label="项目编号">
-          <el-input v-model="queryParams.projectCode" placeholder="请输入项目编号" clearable style="width: 240px"
-            @keyup.enter="handleQuery" />
+          <el-input v-model="queryParams.projectCode" placeholder="请输入项目编号" clearable style="width: 240px" @keyup.enter="handleQuery" />
         </el-form-item>
         <el-form-item label="项目名称">
-          <el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable style="width: 240px"
-            @keyup.enter="handleQuery" />
+          <el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
         </el-form-item>
         <el-form-item label="中心名称">
-          <el-input v-model="queryParams.centerName" placeholder="请输入中心名称" clearable style="width: 240px"
-            @keyup.enter="handleQuery" />
+          <el-input v-model="queryParams.centerName" placeholder="请输入中心名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
         </el-form-item>
         <el-form-item label="名称">
-          <el-input v-model="queryParams.name" placeholder="请输入名称" clearable style="width: 240px"
-            @keyup.enter="handleQuery" />
+          <el-input v-model="queryParams.name" placeholder="请输入名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
         </el-form-item>
         <el-form-item>
           <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
@@ -60,9 +56,9 @@
         </el-table-column>
         <el-table-column prop="folderName" label="所属文件夹" min-width="150" show-overflow-tooltip />
         <el-table-column prop="submitter" label="递交人" width="120" align="center" />
-        <el-table-column prop="deadline" label="截止时间" width="160" align="center">
+        <el-table-column prop="passTime" label="审核通过时间" width="160" align="center">
           <template #default="scope">
-            <span v-if="scope.row.deadline">{{ parseTime(scope.row.deadline) }}</span>
+            <span v-if="scope.row.passTime">{{ parseTime(scope.row.passTime) }}</span>
             <span v-else>-</span>
           </template>
         </el-table-column>
@@ -81,21 +77,23 @@
         <!-- 操作列 -->
         <el-table-column label="操作" width="200" align="center" fixed="right" class-name="small-padding fixed-width">
           <template #default="scope">
-            <el-button 
-              v-if="scope.row.status === 3" 
+            <el-button
+              v-if="scope.row.status === 3"
               v-hasPermi="['taskCenter:filing:filing']"
-              type="primary" 
-              icon="FolderAdd" 
-              style="padding: 0 5px; font-size: 10px; height: 24px;"
-              @click="handleArchive(scope.row)">
+              type="primary"
+              icon="FolderAdd"
+              style="padding: 0 5px; font-size: 10px; height: 24px"
+              @click="handleArchive(scope.row)"
+            >
               归档
             </el-button>
-            <el-button 
-              v-if="scope.row.ossId" 
-              type="success" 
-              icon="Download" 
-              style="padding: 0 5px; font-size: 10px; height: 24px;"
-              @click="handleDownload(scope.row)">
+            <el-button
+              v-if="scope.row.ossId"
+              type="success"
+              icon="Download"
+              style="padding: 0 5px; font-size: 10px; height: 24px"
+              @click="handleDownload(scope.row)"
+            >
               下载
             </el-button>
           </template>
@@ -103,8 +101,13 @@
       </el-table>
 
       <!-- 分页 -->
-      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
-        :total="total" @pagination="getTaskList" />
+      <pagination
+        v-show="total > 0"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        :total="total"
+        @pagination="getTaskList"
+      />
     </el-card>
   </div>
 </template>

+ 25 - 38
src/views/home/taskCenter/submission/index.vue

@@ -65,7 +65,8 @@
             <DocumentStatusTag :status="scope.row.status" />
           </template>
         </el-table-column>
-        <el-table-column prop="submitter" label="递交人" width="120" align="center" />
+        <el-table-column prop="planSubmitter" label="计划递交人" width="120" align="center" />
+
         <el-table-column prop="deadline" label="截止时间" width="160" align="center">
           <template #default="scope">
             <span v-if="scope.row.deadline">{{ parseTime(scope.row.deadline) }}</span>
@@ -99,28 +100,16 @@
         <!-- 操作列 -->
         <el-table-column label="操作" width="280" align="center" fixed="right" class-name="small-padding fixed-width">
           <template #default="scope">
-            <el-button 
-              v-if="scope.row.status === 0 || scope.row.status === 2" 
-              type="primary" 
-              icon="Upload"
-              style="padding: 0 5px; font-size: 10px; height: 24px;"
-              @click="handleTaskSubmit(scope.row)">
+            <el-button v-if="scope.row.status === 0 || scope.row.status === 2" type="primary" icon="Upload"
+              style="padding: 0 5px; font-size: 10px; height: 24px" @click="handleTaskSubmit(scope.row)">
               递交
             </el-button>
-            <el-button 
-              v-hasPermi="['taskCenter:submission:logAudit']" 
-              type="info" 
-              icon="DocumentCopy"
-              style="padding: 0 5px; font-size: 10px; height: 24px;"
-              @click="handleViewAuditLog(scope.row)">
+            <el-button v-hasPermi="['taskCenter:submission:logAudit']" type="info" icon="DocumentCopy"
+              style="padding: 0 5px; font-size: 10px; height: 24px" @click="handleViewAuditLog(scope.row)">
               审核记录
             </el-button>
-            <el-button 
-              v-if="scope.row.sendFlag && !scope.row.sendStatus" 
-              v-hasPermi="['taskCenter:submission:send']"
-              type="success" 
-              icon="Promotion"
-              style="padding: 0 5px; font-size: 10px; height: 24px;"
+            <el-button v-if="scope.row.sendFlag && !scope.row.sendStatus" v-hasPermi="['taskCenter:submission:send']"
+              type="success" icon="Promotion" style="padding: 0 5px; font-size: 10px; height: 24px"
               @click="handleSend(scope.row)">
               寄送
             </el-button>
@@ -149,12 +138,8 @@
     </el-dialog>
 
     <!-- 审核记录对话框 -->
-    <AuditLogDialog 
-      v-model:visible="auditLogDialog.visible" 
-      :document-id="auditLogDialog.documentId"
-      :api-function="listSubmissionAuditLog"
-      i18n-prefix="home.taskCenter.submission"
-    />
+    <AuditLogDialog v-model:visible="auditLogDialog.visible" :document-id="auditLogDialog.documentId"
+      :api-function="listSubmissionAuditLog" i18n-prefix="home.taskCenter.submission" />
   </div>
 </template>
 
@@ -415,19 +400,21 @@ const handleSend = (row: SubmissionTaskVO) => {
     confirmButtonText: '确认',
     cancelButtonText: '取消',
     type: 'warning'
-  }).then(async () => {
-    try {
-      await sendDocument(row.id);
-      ElMessage.success('寄送确认成功');
-      // 刷新任务列表
-      await getTaskList();
-    } catch (error) {
-      console.error('寄送确认失败:', error);
-      ElMessage.error('寄送确认失败');
-    }
-  }).catch(() => {
-    // 用户取消操作
-  });
+  })
+    .then(async () => {
+      try {
+        await sendDocument(row.id);
+        ElMessage.success('寄送确认成功');
+        // 刷新任务列表
+        await getTaskList();
+      } catch (error) {
+        console.error('寄送确认失败:', error);
+        ElMessage.error('寄送确认失败');
+      }
+    })
+    .catch(() => {
+      // 用户取消操作
+    });
 };
 
 onMounted(() => {

+ 36 - 24
src/views/home/workbench/index.vue

@@ -71,19 +71,19 @@ const formatTime = (timeStr: string, dateOnly = false) => {
   try {
     const options: Intl.DateTimeFormatOptions = dateOnly
       ? {
-        year: 'numeric',
-        month: '2-digit',
-        day: '2-digit'
-      }
+          year: 'numeric',
+          month: '2-digit',
+          day: '2-digit'
+        }
       : {
-        year: 'numeric',
-        month: '2-digit',
-        day: '2-digit',
-        hour: '2-digit',
-        minute: '2-digit',
-        second: '2-digit',
-        hour12: false
-      };
+          year: 'numeric',
+          month: '2-digit',
+          day: '2-digit',
+          hour: '2-digit',
+          minute: '2-digit',
+          second: '2-digit',
+          hour12: false
+        };
 
     const formatted = new Date(timeStr).toLocaleString('zh-CN', options);
     return formatted.replace(/\//g, '-');
@@ -115,8 +115,7 @@ onMounted(async () => {
   await getProjectList();
 });
 
-onUnmounted(() => {
-});
+onUnmounted(() => {});
 </script>
 
 <template>
@@ -132,8 +131,13 @@ onUnmounted(() => {
 
         <!-- 搜索栏 -->
         <div class="search-container">
-          <el-input v-model="queryParams.content" placeholder="请输入项目编号/项目名/PM/PD/CTA" style="width: 300px" clearable
-            @keyup.enter="handleSearch">
+          <el-input
+            v-model="queryParams.content"
+            placeholder="请输入项目编号/项目名/PM/PD/CTA"
+            style="width: 300px"
+            clearable
+            @keyup.enter="handleSearch"
+          >
             <template #append>
               <el-button :icon="Search" @click="handleSearch" />
             </template>
@@ -150,7 +154,11 @@ onUnmounted(() => {
           <!-- 项目进度相关 -->
           <el-table-column prop="onTimeSubmissionRate" label="按时提交率" min-width="130" align="center">
             <template #default="{ row }">
-              {{ row.onTimeSubmissionRate !== null ? (row.onTimeSubmissionRate).toFixed(2) + '%' : '-' }}
+              {{
+                row.onTimeSubmissionRate !== null && row.onTimeSubmissionRate !== undefined && typeof row.onTimeSubmissionRate === 'number'
+                  ? row.onTimeSubmissionRate.toFixed(2) + '%'
+                  : '-'
+              }}
             </template>
           </el-table-column>
 
@@ -158,10 +166,8 @@ onUnmounted(() => {
 
           <el-table-column prop="submissionProgress" label="提交进度" min-width="150">
             <template #default="{ row }">
-              <el-progress :percentage="row.submissionProgress || 0" :show-text="false"
-                :status="getProgressStatus(row.submissionProgress)" />
-              <span style="margin-left: 10px">{{ row.submissionProgress !== null ? row.submissionProgress + '%' : '-'
-              }}</span>
+              <el-progress :percentage="row.submissionProgress || 0" :show-text="false" :status="getProgressStatus(row.submissionProgress)" />
+              <span style="margin-left: 10px">{{ row.submissionProgress !== null ? row.submissionProgress + '%' : '-' }}</span>
             </template>
           </el-table-column>
 
@@ -208,9 +214,15 @@ onUnmounted(() => {
 
         <!-- 分页 -->
         <div class="pagination-container">
-          <el-pagination v-model:current-page="queryParams.pageNum" v-model:page-size="queryParams.pageSize"
-            :page-sizes="[10, 20, 30, 50]" layout="total, sizes, prev, pager, next, jumper" :total="total"
-            @size-change="handleSizeChange" @current-change="handleCurrentChange" />
+          <el-pagination
+            v-model:current-page="queryParams.pageNum"
+            v-model:page-size="queryParams.pageSize"
+            :page-sizes="[10, 20, 30, 50]"
+            layout="total, sizes, prev, pager, next, jumper"
+            :total="total"
+            @size-change="handleSizeChange"
+            @current-change="handleCurrentChange"
+          />
         </div>
       </el-card>
     </div>

+ 1 - 2
src/views/index.vue

@@ -1,6 +1,5 @@
 <template>
-  <div class="app-container home">
-  </div>
+  <div class="app-container home"></div>
 </template>
 
 <script setup name="Index" lang="ts">

+ 20 - 20
src/views/login.vue

@@ -44,23 +44,23 @@
         </div>
       </el-form-item>
       <el-checkbox v-model="loginForm.rememberMe" style="margin: 0 0 25px 0">{{ proxy.$t('login.rememberPassword') }}</el-checkbox>
-<!--      <el-form-item style="float: right">-->
-<!--        <el-button circle :title="proxy.$t('login.social.wechat')" @click="doSocialLogin('wechat')">-->
-<!--          <svg-icon icon-class="wechat" />-->
-<!--        </el-button>-->
-<!--        <el-button circle :title="proxy.$t('login.social.maxkey')" @click="doSocialLogin('maxkey')">-->
-<!--          <svg-icon icon-class="maxkey" />-->
-<!--        </el-button>-->
-<!--        <el-button circle :title="proxy.$t('login.social.topiam')" @click="doSocialLogin('topiam')">-->
-<!--          <svg-icon icon-class="topiam" />-->
-<!--        </el-button>-->
-<!--        <el-button circle :title="proxy.$t('login.social.gitee')" @click="doSocialLogin('gitee')">-->
-<!--          <svg-icon icon-class="gitee" />-->
-<!--        </el-button>-->
-<!--        <el-button circle :title="proxy.$t('login.social.github')" @click="doSocialLogin('github')">-->
-<!--          <svg-icon icon-class="github" />-->
-<!--        </el-button>-->
-<!--      </el-form-item>-->
+      <!--      <el-form-item style="float: right">-->
+      <!--        <el-button circle :title="proxy.$t('login.social.wechat')" @click="doSocialLogin('wechat')">-->
+      <!--          <svg-icon icon-class="wechat" />-->
+      <!--        </el-button>-->
+      <!--        <el-button circle :title="proxy.$t('login.social.maxkey')" @click="doSocialLogin('maxkey')">-->
+      <!--          <svg-icon icon-class="maxkey" />-->
+      <!--        </el-button>-->
+      <!--        <el-button circle :title="proxy.$t('login.social.topiam')" @click="doSocialLogin('topiam')">-->
+      <!--          <svg-icon icon-class="topiam" />-->
+      <!--        </el-button>-->
+      <!--        <el-button circle :title="proxy.$t('login.social.gitee')" @click="doSocialLogin('gitee')">-->
+      <!--          <svg-icon icon-class="gitee" />-->
+      <!--        </el-button>-->
+      <!--        <el-button circle :title="proxy.$t('login.social.github')" @click="doSocialLogin('github')">-->
+      <!--          <svg-icon icon-class="github" />-->
+      <!--        </el-button>-->
+      <!--      </el-form-item>-->
       <el-form-item style="width: 100%">
         <el-button :loading="loading" size="large" type="primary" style="width: 100%" @click.prevent="handleLogin">
           <span v-if="!loading">{{ proxy.$t('login.login') }}</span>
@@ -72,9 +72,9 @@
       </el-form-item>
     </el-form>
     <!--  底部  -->
-<!--    <div class="el-login-footer">-->
-<!--      <span>Copyright © 2018-2025 疯狂的狮子Li All Rights Reserved.</span>-->
-<!--    </div>-->
+    <!--    <div class="el-login-footer">-->
+    <!--      <span>Copyright © 2018-2025 疯狂的狮子Li All Rights Reserved.</span>-->
+    <!--    </div>-->
   </div>
 </template>
 

+ 5 - 5
src/views/monitor/online/index.vue

@@ -48,11 +48,11 @@
         </el-table-column>
         <el-table-column :label="t('online.table.operation')" align="center" fixed="right" class-name="small-padding fixed-width" width="140">
           <template #default="scope">
-            <el-button 
-              v-hasPermi="['monitor:online:forceLogout']" 
-              type="danger" 
-              icon="Delete" 
-              style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px;"
+            <el-button
+              v-hasPermi="['monitor:online:forceLogout']"
+              type="danger"
+              icon="Delete"
+              style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px"
               @click="handleForceLogout(scope.row)"
             >
               {{ t('online.tooltip.forceLogout') }}

+ 1 - 1
src/views/project/management/detail/components/header.vue

@@ -35,7 +35,7 @@ const handleBack = () => {
     switchComponent(ListComponent);
   }
   emit('back');
-}
+};
 </script>
 
 <style scoped>

+ 8 - 27
src/views/project/management/detail/components/sidebar.vue

@@ -1,52 +1,33 @@
 <template>
   <div class="detail-sidebar">
-    <el-menu
-      :default-active="activeMenu"
-      class="sidebar-menu"
-      @select="handleMenuSelect"
-    >
+    <el-menu :default-active="activeMenu" class="sidebar-menu" @select="handleMenuSelect">
       <!-- 项目基本信息 -->
-      <el-menu-item 
-        index="basicInfo" 
-        v-hasPermi="['project:management:queryBasicInfo']"
-      >
+      <el-menu-item index="basicInfo" v-hasPermi="['project:management:queryBasicInfo']">
         <el-icon><Document /></el-icon>
         <span>{{ t('project.management.detail.menu.basicInfo') }}</span>
       </el-menu-item>
 
       <!-- 中心信息 -->
-      <el-menu-item 
-        index="centerInfo" 
-        v-hasPermi="['project:management:queryCenterInfo']"
-      >
+      <el-menu-item index="centerInfo" v-hasPermi="['project:management:queryCenterInfo']">
         <el-icon><OfficeBuilding /></el-icon>
         <span>{{ t('project.management.detail.menu.centerInfo') }}</span>
       </el-menu-item>
 
       <!-- 成员信息 - 带子菜单 -->
-      <el-sub-menu 
-        index="memberInfo" 
-        v-hasPermi="['project:management:queryMemberInfo']"
-      >
+      <el-sub-menu index="memberInfo" v-hasPermi="['project:management:queryMemberInfo']">
         <template #title>
           <el-icon><User /></el-icon>
           <span>{{ t('project.management.detail.menu.memberInfo') }}</span>
         </template>
-        
+
         <!-- 项目成员 -->
-        <el-menu-item 
-          index="projectMember" 
-          v-hasPermi="['project:management:queryProjectMember']"
-        >
+        <el-menu-item index="projectMember" v-hasPermi="['project:management:queryProjectMember']">
           <el-icon><UserFilled /></el-icon>
           <span>{{ t('project.management.detail.menu.projectMember') }}</span>
         </el-menu-item>
 
         <!-- 中心成员 -->
-        <el-menu-item 
-          index="centerMember" 
-          v-hasPermi="['project:management:queryCenterMember']"
-        >
+        <el-menu-item index="centerMember" v-hasPermi="['project:management:queryCenterMember']">
           <el-icon><Avatar /></el-icon>
           <span>{{ t('project.management.detail.menu.centerMember') }}</span>
         </el-menu-item>
@@ -74,7 +55,7 @@ const emit = defineEmits<{
 // 菜单选择事件
 const handleMenuSelect = (index: string) => {
   emit('menuSelect', index);
-}
+};
 </script>
 
 <style scoped>

+ 4 - 4
src/views/project/management/detail/index.vue

@@ -65,12 +65,12 @@ const currentPageComponent = computed(() => {
 // 返回列表
 const handleBack = () => {
   // Header 组件内部已处理返回逻辑
-}
+};
 
 // 菜单选择事件
 const handleMenuSelect = (index: string) => {
   activeMenu.value = index;
-}
+};
 
 // 获取项目详细信息
 const getProjectDetail = async () => {
@@ -78,7 +78,7 @@ const getProjectDetail = async () => {
     console.warn('项目ID不存在');
     return;
   }
-  
+
   try {
     loading.value = true;
     const res = await getManagement(projectId.value);
@@ -89,7 +89,7 @@ const getProjectDetail = async () => {
   } finally {
     loading.value = false;
   }
-}
+};
 
 // 通过 provide 将项目数据提供给子组件
 provide('projectData', projectData);

+ 11 - 17
src/views/project/management/detail/pages/basicInfo.vue

@@ -105,7 +105,7 @@
             <span class="header-title">{{ t('project.management.detail.content.timeInfo') }}</span>
           </div>
         </template>
-        
+
         <!-- 项目时间线进度条 -->
         <div class="timeline-section">
           <div class="timeline-header">
@@ -118,15 +118,10 @@
               <span class="time-value">{{ parseTime(projectData.endTime, '{y}-{m}-{d}') || '-' }}</span>
             </div>
           </div>
-          
+
           <!-- 进度条 -->
           <div class="progress-container">
-            <el-progress 
-              :percentage="projectProgress" 
-              :color="progressColor"
-              :stroke-width="12"
-              :show-text="false"
-            />
+            <el-progress :percentage="projectProgress" :color="progressColor" :stroke-width="12" :show-text="false" />
           </div>
         </div>
 
@@ -180,23 +175,23 @@ const projectProgress = computed(() => {
   if (!projectData?.value?.startTime || !projectData?.value?.endTime) {
     return 0;
   }
-  
+
   const start = new Date(projectData.value.startTime).getTime();
   const end = new Date(projectData.value.endTime).getTime();
   const now = Date.now();
-  
+
   if (now < start) {
     return 0; // 项目未开始
   }
-  
+
   if (now > end) {
     return 100; // 项目已结束
   }
-  
+
   const total = end - start;
   const elapsed = now - start;
   const progress = (elapsed / total) * 100;
-  
+
   return Math.min(Math.max(Math.round(progress), 0), 100);
 });
 
@@ -215,7 +210,6 @@ const progressColor = computed(() => {
     return '#67C23A'; // 绿色 - 刚开始
   }
 });
-
 </script>
 
 <style scoped>
@@ -386,17 +380,17 @@ const progressColor = computed(() => {
   .info-card :deep(.el-card__body) {
     padding: 16px;
   }
-  
+
   .info-item {
     margin-bottom: 16px;
     padding-bottom: 12px;
   }
-  
+
   .timeline-header {
     flex-direction: column;
     gap: 12px;
   }
-  
+
   .time-value {
     font-size: 14px;
   }

+ 17 - 85
src/views/project/management/detail/pages/centerInfo.vue

@@ -8,13 +8,7 @@
     <!-- 搜索表单 -->
     <el-form :model="queryParams" :inline="true" class="search-form">
       <el-form-item label="中心名称">
-        <el-input
-          v-model="queryParams.name"
-          placeholder="请输入中心名称"
-          clearable
-          style="width: 240px"
-          @keyup.enter="handleQuery"
-        />
+        <el-input v-model="queryParams.name" placeholder="请输入中心名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
       </el-form-item>
       <el-form-item>
         <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
@@ -24,12 +18,7 @@
 
     <!-- 数据表格 -->
     <el-card shadow="never">
-      <el-table
-        v-loading="loading"
-        border
-        :data="centerList"
-        style="width: 100%"
-      >
+      <el-table v-loading="loading" border :data="centerList" style="width: 100%">
         <el-table-column label="序号" align="center" prop="id" width="80" />
         <el-table-column label="中心名称" align="center" prop="name" min-width="150" show-overflow-tooltip />
         <el-table-column label="状态" align="center" prop="status" width="100">
@@ -44,22 +33,11 @@
         <el-table-column label="更新时间" align="center" prop="updateTime" width="180" />
       </el-table>
 
-      <pagination
-        v-show="total > 0"
-        :total="total"
-        v-model:page="queryParams.pageNum"
-        v-model:limit="queryParams.pageSize"
-        @pagination="getList"
-      />
+      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
     </el-card>
 
     <!-- 邀请成员对话框 -->
-    <el-dialog
-      v-model="inviteDialogVisible"
-      title="邀请成员"
-      width="700px"
-      @close="handleInviteDialogClose"
-    >
+    <el-dialog v-model="inviteDialogVisible" title="邀请成员" width="700px" @close="handleInviteDialogClose">
       <el-form label-width="100px">
         <el-form-item label="模糊搜索">
           <el-select
@@ -81,19 +59,9 @@
             />
             <template #footer>
               <div v-if="memberTotal > memberQueryParams.pageSize" class="select-pagination">
-                <el-button
-                  text
-                  :disabled="memberQueryParams.pageNum === 1"
-                  @click="loadPrevPage"
-                >
-                  上一页
-                </el-button>
+                <el-button text :disabled="memberQueryParams.pageNum === 1" @click="loadPrevPage"> 上一页 </el-button>
                 <span>{{ memberQueryParams.pageNum }} / {{ Math.ceil(memberTotal / memberQueryParams.pageSize) }}</span>
-                <el-button
-                  text
-                  :disabled="memberQueryParams.pageNum * memberQueryParams.pageSize >= memberTotal"
-                  @click="loadNextPage"
-                >
+                <el-button text :disabled="memberQueryParams.pageNum * memberQueryParams.pageSize >= memberTotal" @click="loadNextPage">
                   下一页
                 </el-button>
               </div>
@@ -104,23 +72,9 @@
         <!-- 已选择的成员列表 -->
         <el-form-item label="已选成员" v-if="selectedMembers.length > 0">
           <div class="selected-members-list">
-            <div
-              v-for="(member, index) in selectedMembers"
-              :key="member.id"
-              class="member-card"
-            >
-              <div class="member-info">
-                {{ member.name }} / {{ member.dept }} / {{ member.phoneNumber }}
-              </div>
-              <el-button
-                type="danger"
-                size="small"
-                text
-                @click="removeSelectedMember(index)"
-                class="remove-btn"
-              >
-                移除
-              </el-button>
+            <div v-for="(member, index) in selectedMembers" :key="member.id" class="member-card">
+              <div class="member-info">{{ member.name }} / {{ member.dept }} / {{ member.phoneNumber }}</div>
+              <el-button type="danger" size="small" text @click="removeSelectedMember(index)" class="remove-btn"> 移除 </el-button>
             </div>
           </div>
         </el-form-item>
@@ -129,33 +83,17 @@
       <template #footer>
         <span class="dialog-footer">
           <el-button @click="inviteDialogVisible = false">取消</el-button>
-          <el-button
-            type="primary"
-            @click="handleConfirmInvite"
-            :disabled="selectedMembers.length === 0"
-          >
-            确认
-          </el-button>
+          <el-button type="primary" @click="handleConfirmInvite" :disabled="selectedMembers.length === 0"> 确认 </el-button>
         </span>
       </template>
     </el-dialog>
 
     <!-- 确认邀请对话框 -->
-    <el-dialog
-      v-model="confirmDialogVisible"
-      title="确认邀请"
-      width="500px"
-    >
+    <el-dialog v-model="confirmDialogVisible" title="确认邀请" width="500px">
       <div class="confirm-content">
-        <p style="margin-bottom: 15px; font-weight: bold;">
-          确认邀请以下成员进入 {{ currentCenter?.name }} 中?
-        </p>
+        <p style="margin-bottom: 15px; font-weight: bold">确认邀请以下成员进入 {{ currentCenter?.name }} 中?</p>
         <div class="confirm-member-list">
-          <div
-            v-for="member in selectedMembers"
-            :key="member.id"
-            class="confirm-member-item"
-          >
+          <div v-for="member in selectedMembers" :key="member.id" class="confirm-member-item">
             <span>{{ member.name }} / {{ member.dept }} / {{ member.phoneNumber }}</span>
           </div>
         </div>
@@ -164,13 +102,7 @@
       <template #footer>
         <span class="dialog-footer">
           <el-button @click="confirmDialogVisible = false">取消</el-button>
-          <el-button
-            type="primary"
-            @click="confirmInvite"
-            :loading="inviteLoading"
-          >
-            确认
-          </el-button>
+          <el-button type="primary" @click="confirmInvite" :loading="inviteLoading"> 确认 </el-button>
         </span>
       </template>
     </el-dialog>
@@ -326,10 +258,10 @@ const loadNextPage = async () => {
 
 /** 选择成员 */
 const handleSelectMember = (memberId: string | number) => {
-  const member = memberOptions.value.find(m => m.id === memberId);
+  const member = memberOptions.value.find((m) => m.id === memberId);
   if (member) {
     // 检查是否已经选择过该成员
-    const isAlreadySelected = selectedMembers.value.some(m => m.id === memberId);
+    const isAlreadySelected = selectedMembers.value.some((m) => m.id === memberId);
     if (!isAlreadySelected) {
       selectedMembers.value.push(member);
     } else {
@@ -363,7 +295,7 @@ const confirmInvite = async () => {
   try {
     const inviteData: InviteCenterMemberForm = {
       folderId: currentCenter.value.id,
-      userIds: selectedMembers.value.map(m => m.id)
+      userIds: selectedMembers.value.map((m) => m.id)
     };
 
     await inviteCenterMember(inviteData);

+ 4 - 27
src/views/project/management/detail/pages/centerMember.vue

@@ -8,22 +8,10 @@
     <!-- 搜索表单 -->
     <el-form :model="queryParams" :inline="true" class="search-form">
       <el-form-item label="成员昵称">
-        <el-input
-          v-model="queryParams.name"
-          placeholder="请输入成员昵称"
-          clearable
-          style="width: 200px"
-          @keyup.enter="handleQuery"
-        />
+        <el-input v-model="queryParams.name" placeholder="请输入成员昵称" clearable style="width: 200px" @keyup.enter="handleQuery" />
       </el-form-item>
       <el-form-item label="中心">
-        <el-input
-          v-model="queryParams.center"
-          placeholder="请输入中心名称"
-          clearable
-          style="width: 200px"
-          @keyup.enter="handleQuery"
-        />
+        <el-input v-model="queryParams.center" placeholder="请输入中心名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
       </el-form-item>
       <el-form-item>
         <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
@@ -33,12 +21,7 @@
 
     <!-- 数据表格 -->
     <el-card shadow="never">
-      <el-table
-        v-loading="loading"
-        border
-        :data="memberList"
-        style="width: 100%"
-      >
+      <el-table v-loading="loading" border :data="memberList" style="width: 100%">
         <el-table-column label="序号" align="center" prop="id" width="80" />
         <el-table-column label="姓名" align="center" prop="name" width="120" />
         <el-table-column label="手机号" align="center" prop="phoneNumber" width="150" />
@@ -47,13 +30,7 @@
         <el-table-column label="时间" align="center" prop="time" width="180" />
       </el-table>
 
-      <pagination
-        v-show="total > 0"
-        :total="total"
-        v-model:page="queryParams.pageNum"
-        v-model:limit="queryParams.pageSize"
-        @pagination="getList"
-      />
+      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
     </el-card>
   </div>
 </template>

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

@@ -2,8 +2,7 @@
   <div class="project-member-page">
     <div class="header-section">
       <h3>{{ t('project.management.detail.menu.projectMember') }}</h3>
-      <el-button v-hasPermi="['project:management:queryProjectMemberAddMember']" type="primary" icon="Plus"
-        @click="handleAddMember">
+      <el-button v-hasPermi="['project:management:queryProjectMemberAddMember']" type="primary" icon="Plus" @click="handleAddMember">
         添加成员
       </el-button>
     </div>
@@ -13,18 +12,25 @@
       <el-table v-loading="loading" border :data="memberList" style="width: 100%">
         <el-table-column prop="id" :label="t('project.management.table.id')" width="60" align="center" />
         <el-table-column :label="t('project.management.member.name')" align="center" prop="name" width="150" />
-        <el-table-column :label="t('project.management.member.phoneNumber')" align="center" prop="phoneNumber"
-          width="150" />
-        <el-table-column :label="t('project.management.member.dept')" align="center" prop="dept" min-width="200"
-          show-overflow-tooltip />
+        <el-table-column :label="t('project.management.member.phoneNumber')" align="center" prop="phoneNumber" width="150" />
+        <el-table-column :label="t('project.management.member.dept')" align="center" prop="dept" min-width="200" show-overflow-tooltip />
         <el-table-column :label="t('project.management.member.time')" align="center" prop="time" width="180" />
-        <el-table-column label="操作栏" align="center" width="180" fixed="right">
+        <el-table-column label="操作栏" align="center" width="280" fixed="right">
           <template #default="scope">
-            <el-button 
-              v-hasPermi="['project:management:queryProjectMemberAssignFolders']" 
-              type="primary" 
-              icon="Setting" 
-              style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px;"
+            <el-button
+              v-hasPermi="['project:management:queryProjectMemberEditMember']"
+              type="primary"
+              icon="Edit"
+              style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px"
+              @click="handleEditMember(scope.row)"
+            >
+              修改
+            </el-button>
+            <el-button
+              v-hasPermi="['project:management:queryProjectMemberAssignFolders']"
+              type="primary"
+              icon="Setting"
+              style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px"
               @click="handleAssignFolders(scope.row)"
             >
               修改文件夹权限
@@ -33,8 +39,7 @@
         </el-table-column>
       </el-table>
 
-      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
-        v-model:limit="queryParams.pageSize" @pagination="getList" />
+      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
     </el-card>
 
     <!-- 添加成员对话框 -->
@@ -47,67 +52,59 @@
             </el-form-item>
           </el-col>
           <el-col :span="12">
-            <el-form-item label="昵称" prop="nickname">
-              <el-input v-model="addForm.nickname" placeholder="请输入昵称" maxlength="30" />
+            <el-form-item label="姓名" prop="nickname">
+              <el-input v-model="addForm.nickname" placeholder="请输入姓名" maxlength="30" />
             </el-form-item>
           </el-col>
         </el-row>
         <el-row :gutter="20">
           <el-col :span="12">
-            <el-form-item label="用户名" prop="username">
-              <el-input v-model="addForm.username" placeholder="请输入用户名" maxlength="30" />
+            <el-form-item label="角色" prop="roleIds">
+              <el-select v-model="addForm.roleIds" multiple placeholder="请选择角色" style="width: 100%">
+                <el-option
+                  v-for="item in roleOptions"
+                  :key="item.roleId"
+                  :label="item.roleName"
+                  :value="item.roleId"
+                  :disabled="item.status == '1'"
+                />
+              </el-select>
             </el-form-item>
           </el-col>
           <el-col :span="12">
-            <el-form-item label="密码" prop="password">
-              <el-input v-model="addForm.password" type="password" placeholder="请输入密码" maxlength="20" show-password />
+            <el-form-item label="归属部门" prop="deptId">
+              <el-tree-select
+                v-model="addForm.deptId"
+                :data="deptOptions"
+                :props="{ value: 'id', label: 'label', children: 'children' }"
+                value-key="id"
+                placeholder="请选择归属部门"
+                check-strictly
+                style="width: 100%"
+              />
             </el-form-item>
           </el-col>
         </el-row>
         <el-row :gutter="20">
-          <el-col :span="12">
+          <el-col :span="24">
             <el-form-item label="邮箱" prop="email">
               <el-input v-model="addForm.email" placeholder="请输入邮箱" maxlength="50" />
             </el-form-item>
           </el-col>
-          <el-col :span="12">
-            <el-form-item label="性别">
-              <el-select v-model="addForm.gender" placeholder="请选择性别" style="width: 100%">
-                <el-option v-for="dict in sys_user_sex" :key="dict.value" :label="dict.label" :value="dict.value" />
-              </el-select>
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="归属部门" prop="deptId">
-              <el-tree-select v-model="addForm.deptId" :data="deptOptions"
-                :props="{ value: 'id', label: 'label', children: 'children' }" value-key="id" placeholder="请选择归属部门"
-                check-strictly style="width: 100%" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="角色" prop="roleIds">
-              <el-select v-model="addForm.roleIds" multiple placeholder="请选择角色" style="width: 100%">
-                <el-option v-for="item in roleOptions" :key="item.roleId" :label="item.roleName" :value="item.roleId"
-                  :disabled="item.status == '1'" />
-              </el-select>
-            </el-form-item>
-          </el-col>
         </el-row>
         <el-row>
           <el-col :span="24">
             <el-form-item label="文件夹权限">
-              <div style="width: 100%;">
-                <el-checkbox v-model="isAllFoldersSelected" @change="handleSelectAllFolders"
-                  style="margin-bottom: 10px;">
-                  全选
-                </el-checkbox>
-                <div
-                  style="width: 100%; max-height: 300px; overflow-y: auto; border: 1px solid #dcdfe6; border-radius: 4px; padding: 10px;">
-                  <data-permision-tree ref="addFolderTreeRef" :data="folderOptions"
-                    v-model:selected-keys="addSelectedFolders" @check="handleAddFolderCheck"
-                    :disabled="isAllFoldersSelected">
+              <div style="width: 100%">
+                <el-checkbox v-model="isAllFoldersSelected" @change="handleSelectAllFolders" style="margin-bottom: 10px"> 全选 </el-checkbox>
+                <div style="width: 100%; max-height: 300px; overflow-y: auto; border: 1px solid #dcdfe6; border-radius: 4px; padding: 10px">
+                  <data-permision-tree
+                    ref="addFolderTreeRef"
+                    :data="folderOptions"
+                    v-model:selected-keys="addSelectedFolders"
+                    @check="handleAddFolderCheck"
+                    :disabled="isAllFoldersSelected"
+                  >
                     <template #default="{ node, data }">
                       <span class="custom-tree-node">
                         <el-icon>
@@ -143,15 +140,16 @@
 
     <!-- 修改文件夹权限对话框 -->
     <el-dialog v-model="folderDialog.visible" :title="folderDialog.title" width="600px" append-to-body>
-      <div style="width: 100%;">
-        <el-checkbox v-model="isAllFoldersSelectedForEdit" @change="handleSelectAllFoldersForEdit"
-          style="margin-bottom: 10px;">
-          全选
-        </el-checkbox>
-        <div
-          style="width: 100%; max-height: 400px; overflow-y: auto; border: 1px solid #dcdfe6; border-radius: 4px; padding: 10px;">
-          <data-permision-tree ref="folderTreeRef" :data="folderOptions" v-model:selected-keys="selectedFolders"
-            @check="handleFolderCheck" :disabled="isAllFoldersSelectedForEdit">
+      <div style="width: 100%">
+        <el-checkbox v-model="isAllFoldersSelectedForEdit" @change="handleSelectAllFoldersForEdit" style="margin-bottom: 10px"> 全选 </el-checkbox>
+        <div style="width: 100%; max-height: 400px; overflow-y: auto; border: 1px solid #dcdfe6; border-radius: 4px; padding: 10px">
+          <data-permision-tree
+            ref="folderTreeRef"
+            :data="folderOptions"
+            v-model:selected-keys="selectedFolders"
+            @check="handleFolderCheck"
+            :disabled="isAllFoldersSelectedForEdit"
+          >
             <template #default="{ node, data }">
               <span class="custom-tree-node">
                 <el-icon>
@@ -173,14 +171,38 @@
         </div>
       </template>
     </el-dialog>
+
+    <!-- 修改成员信息对话框 -->
+    <el-dialog v-model="editDialog.visible" :title="editDialog.title" width="600px" append-to-body>
+      <el-form ref="editFormRef" :model="editForm" :rules="editRules" label-width="100px">
+        <el-form-item label="名称" prop="nickname">
+          <el-input v-model="editForm.nickname" placeholder="请输入名称" maxlength="30" />
+        </el-form-item>
+        <el-form-item label="手机号" prop="phoneNumber">
+          <el-input v-model="editForm.phoneNumber" placeholder="请输入手机号" maxlength="11" />
+        </el-form-item>
+        <el-form-item label="邮箱" prop="email">
+          <el-input v-model="editForm.email" placeholder="请输入邮箱" maxlength="50" />
+        </el-form-item>
+        <el-form-item label="新密码" prop="password">
+          <el-input v-model="editForm.password" type="password" placeholder="请输入新密码(不修改请留空)" maxlength="20" show-password />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="editButtonLoading" type="primary" @click="submitEditForm">确定</el-button>
+          <el-button @click="cancelEdit">取消</el-button>
+        </div>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup lang="ts">
 import { inject, ref, onMounted, getCurrentInstance, toRefs } from 'vue';
 import { useI18n } from 'vue-i18n';
-import { queryProjectMember, addProjectMember, getFolders, assignFolders } from '@/api/project/management';
-import { ProjectMemberVO, ProjectMemberQuery, AddProjectMemberForm, AssignFoldersForm } from '@/api/project/management/types';
+import { queryProjectMember, addProjectMember, getFolders, assignFolders, queryUser, editProjectMember } from '@/api/project/management';
+import { ProjectMemberVO, ProjectMemberQuery, AddProjectMemberForm, AssignFoldersForm, EditProjectMemberForm } from '@/api/project/management/types';
 import { deptTreeSelect, getUser } from '@/api/system/user';
 import { DeptTreeVO } from '@/api/system/dept/types';
 import { RoleVO } from '@/api/system/role/types';
@@ -208,6 +230,7 @@ const total = ref(0);
 const addFormRef = ref<FormInstance>();
 const folderTreeRef = ref<any>();
 const addFolderTreeRef = ref<any>();
+const editFormRef = ref<FormInstance>();
 
 // 添加成员对话框
 const addDialog = ref({
@@ -221,9 +244,16 @@ const folderDialog = ref({
   title: '修改文件夹权限'
 });
 
+// 修改成员信息对话框
+const editDialog = ref({
+  visible: false,
+  title: '修改成员信息'
+});
+
 // 按钮 loading
 const addButtonLoading = ref(false);
 const folderButtonLoading = ref(false);
+const editButtonLoading = ref(false);
 
 // 部门和角色选项
 const deptOptions = ref<DeptTreeVO[]>([]);
@@ -255,6 +285,15 @@ const addForm = ref<AddProjectMemberForm>({
   folders: ''
 });
 
+// 修改成员表单
+const editForm = ref<EditProjectMemberForm>({
+  id: 0,
+  nickname: '',
+  phoneNumber: '',
+  email: '',
+  password: ''
+});
+
 /**
  * 处理全选文件夹
  * @param checked 是否选中
@@ -284,33 +323,31 @@ const handleSelectAllFoldersForEdit = (checked: boolean) => {
   }
 };
 
-
 // 表单验证规则
 const addRules = {
   phoneNumber: [
     { required: true, message: '请输入手机号', trigger: 'blur' },
     { pattern: /^1[3456789][0-9]\d{8}$/, message: '请输入正确的手机号', trigger: 'blur' }
   ],
-  nickname: [
-    { required: true, message: '请输入昵称', trigger: 'blur' }
-  ],
-  username: [
-    { required: true, message: '请输入用户名', trigger: 'blur' },
-    { min: 2, max: 20, message: '用户名长度必须介于2和20之间', trigger: 'blur' }
-  ],
-  password: [
-    { required: true, message: '请输入密码', trigger: 'blur' },
-    { min: 5, max: 20, message: '密码长度必须介于5和20之间', trigger: 'blur' },
-    { pattern: /^[^<>"'|\\]+$/, message: '不能包含非法字符:< > " \' \\ |', trigger: 'blur' }
-  ],
+  nickname: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
   email: [
+    { required: true, message: '请输入邮箱', trigger: 'blur' },
     { type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }
   ],
-  roleIds: [
-    { required: true, message: '请选择角色', trigger: 'change' }
+  roleIds: [{ required: true, message: '请选择角色', trigger: 'change' }],
+  deptId: [{ required: true, message: '请选择归属部门', trigger: 'change' }]
+};
+
+// 修改成员表单验证规则
+const editRules = {
+  nickname: [{ required: true, message: '请输入名称', trigger: 'blur' }],
+  phoneNumber: [
+    { required: true, message: '请输入手机号', trigger: 'blur' },
+    { pattern: /^1[3456789][0-9]\d{8}$/, message: '请输入正确的手机号', trigger: 'blur' }
   ],
-  deptId: [
-    { required: true, message: '请选择归属部门', trigger: 'change' }
+  email: [
+    { required: true, message: '请输入邮箱', trigger: 'blur' },
+    { type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }
   ]
 };
 
@@ -363,10 +400,10 @@ const handleAddMember = async () => {
     projectId: projectId?.value || 0,
     phoneNumber: '',
     nickname: '',
-    username: '',
-    password: '',
+    username: '', // 将在提交时设置为手机号
+    password: '123456', // 默认密码
     email: '',
-    gender: '',
+    gender: '', // 默认为空
     roleIds: [],
     deptId: undefined,
     folders: ''
@@ -407,6 +444,9 @@ const submitAddForm = () => {
     if (valid) {
       addButtonLoading.value = true;
       try {
+        // 设置用户名为手机号
+        addForm.value.username = addForm.value.phoneNumber;
+
         // 如果不是全选状态,才需要设置文件夹
         if (!isAllFoldersSelected.value) {
           // 直接使用addSelectedFolders,因为DataPermisionTree已经过滤了顶级节点
@@ -434,7 +474,7 @@ const cancelAdd = () => {
     phoneNumber: '',
     nickname: '',
     username: '',
-    password: '',
+    password: '123456',
     email: '',
     gender: '',
     roleIds: [],
@@ -464,7 +504,10 @@ const handleAssignFolders = async (row: ProjectMemberVO) => {
       isAllFoldersSelectedForEdit.value = true;
       selectedFolders.value = [];
     } else if (folders) {
-      selectedFolders.value = folders.split(',').map(id => parseInt(id)).filter(id => !isNaN(id));
+      selectedFolders.value = folders
+        .split(',')
+        .map((id) => parseInt(id))
+        .filter((id) => !isNaN(id));
     } else {
       selectedFolders.value = [];
     }
@@ -526,6 +569,64 @@ const cancelFolder = () => {
   isAllFoldersSelectedForEdit.value = false;
 };
 
+/** 打开修改成员信息对话框 */
+const handleEditMember = async (row: ProjectMemberVO) => {
+  // 重置表单
+  editForm.value = {
+    id: row.id,
+    nickname: '',
+    phoneNumber: '',
+    email: '',
+    password: ''
+  };
+  editFormRef.value?.resetFields();
+
+  // 获取用户详情
+  try {
+    const res = await queryUser(row.id);
+    editForm.value.nickname = res.data.nickName;
+    editForm.value.phoneNumber = res.data.phonenumber;
+    editForm.value.email = res.data.email;
+  } catch (error) {
+    console.error('Failed to fetch user details:', error);
+    ElMessage.error('获取用户信息失败');
+    return;
+  }
+
+  editDialog.value.visible = true;
+};
+
+/** 提交修改成员信息表单 */
+const submitEditForm = () => {
+  editFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      editButtonLoading.value = true;
+      try {
+        await editProjectMember(editForm.value);
+        ElMessage.success('修改成员信息成功');
+        editDialog.value.visible = false;
+        await getList();
+      } catch (error) {
+        console.error('Failed to edit member:', error);
+      } finally {
+        editButtonLoading.value = false;
+      }
+    }
+  });
+};
+
+/** 取消修改成员信息 */
+const cancelEdit = () => {
+  editDialog.value.visible = false;
+  editForm.value = {
+    id: 0,
+    nickname: '',
+    phoneNumber: '',
+    email: '',
+    password: ''
+  };
+};
+
 // 组件挂载时加载数据
 onMounted(() => {
   getList();

+ 1 - 1
src/views/project/management/index.vue

@@ -22,7 +22,7 @@ const switchComponent = (component: any, params?: any) => {
     projectId.value = params.projectId;
   }
   currentComponent.value = component;
-}
+};
 
 // 通过 provide 向子组件提供数据和方法
 provide('switchComponent', switchComponent);

+ 148 - 103
src/views/project/management/list.vue

@@ -5,35 +5,82 @@
         <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-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-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
+                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 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.pdGpd')" prop="pdGpd">
-              <el-input v-model="queryParams.pdGpd" :placeholder="t('project.management.search.pdGpdPlaceholder')" clearable @keyup.enter="handleQuery" style="width: 240px;" />
+              <el-input
+                v-model="queryParams.pdGpd"
+                :placeholder="t('project.management.search.pdGpdPlaceholder')"
+                clearable
+                @keyup.enter="handleQuery"
+                style="width: 240px"
+              />
             </el-form-item>
             <el-form-item :label="t('project.management.search.pmGpm')" prop="pmGpm">
-              <el-input v-model="queryParams.pmGpm" :placeholder="t('project.management.search.pmGpmPlaceholder')" clearable @keyup.enter="handleQuery" style="width: 240px;" />
+              <el-input
+                v-model="queryParams.pmGpm"
+                :placeholder="t('project.management.search.pmGpmPlaceholder')"
+                clearable
+                @keyup.enter="handleQuery"
+                style="width: 240px"
+              />
             </el-form-item>
             <el-form-item :label="t('project.management.search.ctaGcta')" prop="ctaGcta">
-              <el-input v-model="queryParams.ctaGcta" :placeholder="t('project.management.search.ctaGctaPlaceholder')" clearable @keyup.enter="handleQuery" style="width: 240px;" />
+              <el-input
+                v-model="queryParams.ctaGcta"
+                :placeholder="t('project.management.search.ctaGctaPlaceholder')"
+                clearable
+                @keyup.enter="handleQuery"
+                style="width: 240px"
+              />
             </el-form-item>
             <el-form-item :label="t('project.management.search.sponsor')" prop="sponsor">
-              <el-input v-model="queryParams.sponsor" :placeholder="t('project.management.search.sponsorPlaceholder')" clearable @keyup.enter="handleQuery" style="width: 240px;" />
+              <el-input
+                v-model="queryParams.sponsor"
+                :placeholder="t('project.management.search.sponsorPlaceholder')"
+                clearable
+                @keyup.enter="handleQuery"
+                style="width: 240px"
+              />
             </el-form-item>
             <el-form-item :label="t('project.management.search.cro')" prop="cro">
-              <el-input v-model="queryParams.cro" :placeholder="t('project.management.search.croPlaceholder')" clearable @keyup.enter="handleQuery" style="width: 240px;" />
+              <el-input
+                v-model="queryParams.cro"
+                :placeholder="t('project.management.search.croPlaceholder')"
+                clearable
+                @keyup.enter="handleQuery"
+                style="width: 240px"
+              />
             </el-form-item>
             <el-form-item :label="t('project.management.search.startTime')">
               <el-date-picker
@@ -44,7 +91,7 @@
                 :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;"
+                style="width: 240px"
               />
             </el-form-item>
             <el-form-item :label="t('project.management.search.endTime')">
@@ -56,7 +103,7 @@
                 :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;"
+                style="width: 240px"
               />
             </el-form-item>
             <el-form-item :label="t('project.management.search.createTime')">
@@ -68,7 +115,7 @@
                 :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;"
+                style="width: 240px"
               />
             </el-form-item>
             <el-form-item :label="t('project.management.search.updateTime')">
@@ -80,7 +127,7 @@
                 :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;"
+                style="width: 240px"
               />
             </el-form-item>
             <el-form-item>
@@ -96,13 +143,19 @@
       <template #header>
         <el-row :gutter="10" class="mb8">
           <el-col :span="1.5">
-            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['project:management:add']">{{ t('project.management.button.add') }}</el-button>
+            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['project:management:add']">{{
+              t('project.management.button.add')
+            }}</el-button>
           </el-col>
           <el-col :span="1.5">
-            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['project:management:remove']">{{ t('project.management.button.delete') }}</el-button>
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['project:management:remove']">{{
+              t('project.management.button.delete')
+            }}</el-button>
           </el-col>
           <el-col :span="1.5">
-            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['project:management:export']">{{ t('project.management.button.export') }}</el-button>
+            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['project:management:export']">{{
+              t('project.management.button.export')
+            }}</el-button>
           </el-col>
           <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
         </el-row>
@@ -153,40 +206,46 @@
             <span>{{ parseTime(scope.row.updateTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
           </template>
         </el-table-column>
-        <el-table-column :label="t('project.management.table.operation')" align="center" fixed="right" class-name="small-padding fixed-width" width="360">
+        <el-table-column
+          :label="t('project.management.table.operation')"
+          align="center"
+          fixed="right"
+          class-name="small-padding fixed-width"
+          width="360"
+        >
           <template #default="scope">
-            <el-button 
-              type="primary" 
-              icon="View" 
-              style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px;"
-              @click="handleViewDetail(scope.row)" 
+            <el-button
+              type="primary"
+              icon="View"
+              style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px"
+              @click="handleViewDetail(scope.row)"
               v-hasPermi="['project:management:query']"
             >
               {{ t('project.management.tooltip.detail') }}
             </el-button>
-            <el-button 
-              type="success" 
-              icon="Edit" 
-              style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px;"
-              @click="handleUpdate(scope.row)" 
+            <el-button
+              type="success"
+              icon="Edit"
+              style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px"
+              @click="handleUpdate(scope.row)"
               v-hasPermi="['project:management:edit']"
             >
               {{ t('project.management.tooltip.edit') }}
             </el-button>
-            <el-button 
-              type="danger" 
-              icon="Delete" 
-              style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px;"
-              @click="handleDelete(scope.row)" 
+            <el-button
+              type="danger"
+              icon="Delete"
+              style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px"
+              @click="handleDelete(scope.row)"
               v-hasPermi="['project:management:remove']"
             >
               {{ t('project.management.tooltip.delete') }}
             </el-button>
-            <el-button 
-              type="warning" 
-              icon="Refresh" 
-              style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px;"
-              @click="handleUpdateStatus(scope.row)" 
+            <el-button
+              type="warning"
+              icon="Refresh"
+              style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px"
+              @click="handleUpdateStatus(scope.row)"
               v-hasPermi="['project:management:updateStatus']"
             >
               {{ t('project.management.tooltip.updateStatus') }}
@@ -208,7 +267,7 @@
           <el-input v-model="statusForm.name" disabled />
         </el-form-item>
         <el-form-item :label="t('project.management.table.status')" prop="status">
-          <el-select v-model="statusForm.status" :placeholder="t('project.management.message.selectStatus')" style="width: 100%;">
+          <el-select v-model="statusForm.status" :placeholder="t('project.management.message.selectStatus')" style="width: 100%">
             <el-option :label="t('project.management.status.unstarted')" :value="0" />
             <el-option :label="t('project.management.status.underway')" :value="1" />
             <el-option :label="t('project.management.status.paused')" :value="2" />
@@ -229,8 +288,8 @@
       <el-form ref="managementFormRef" :model="form" :rules="rules" label-width="100px">
         <!-- 图标 - 居中显示在最顶部 -->
         <el-row :gutter="20" justify="center">
-          <el-col :span="24" style="text-align: center; margin-bottom: 20px;">
-            <image-upload v-model="form.icon" :limit="1" style="display: inline-block;"/>
+          <el-col :span="24" style="text-align: center; margin-bottom: 20px">
+            <image-upload v-model="form.icon" :limit="1" style="display: inline-block" />
           </el-col>
         </el-row>
 
@@ -254,25 +313,15 @@
         <el-row :gutter="20">
           <el-col :span="12">
             <el-form-item :label="t('project.management.form.language')" prop="language">
-              <el-select v-model="form.language" :placeholder="t('project.management.form.languagePlaceholder')" style="width: 100%;">
-                <el-option
-                    v-for="dict in project_language"
-                    :key="dict.value"
-                    :label="parseI18nName(dict.label)"
-                    :value="dict.value"
-                ></el-option>
+              <el-select v-model="form.language" :placeholder="t('project.management.form.languagePlaceholder')" style="width: 100%">
+                <el-option v-for="dict in project_language" :key="dict.value" :label="parseI18nName(dict.label)" :value="dict.value"></el-option>
               </el-select>
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item :label="t('project.management.form.type')" prop="type">
-              <el-select v-model="form.type" :placeholder="t('project.management.form.typePlaceholder')" style="width: 100%;">
-                <el-option
-                    v-for="dict in project_type"
-                    :key="dict.value"
-                    :label="parseI18nName(dict.label)"
-                    :value="dict.value"
-                ></el-option>
+              <el-select v-model="form.type" :placeholder="t('project.management.form.typePlaceholder')" style="width: 100%">
+                <el-option v-for="dict in project_type" :key="dict.value" :label="parseI18nName(dict.label)" :value="dict.value"></el-option>
               </el-select>
             </el-form-item>
           </el-col>
@@ -325,23 +374,27 @@
         <el-row :gutter="20">
           <el-col :span="12">
             <el-form-item :label="t('project.management.form.startTime')" prop="startTime">
-              <el-date-picker clearable
+              <el-date-picker
+                clearable
                 v-model="form.startTime"
                 type="date"
                 value-format="YYYY-MM-DD"
                 :placeholder="t('project.management.form.startTimePlaceholder')"
-                style="width: 100%;">
+                style="width: 100%"
+              >
               </el-date-picker>
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item :label="t('project.management.form.endTime')" prop="endTime">
-              <el-date-picker clearable
+              <el-date-picker
+                clearable
                 v-model="form.endTime"
                 type="date"
                 value-format="YYYY-MM-DD"
                 :placeholder="t('project.management.form.endTimePlaceholder')"
-                style="width: 100%;">
+                style="width: 100%"
+              >
               </el-date-picker>
             </el-form-item>
           </el-col>
@@ -450,9 +503,7 @@ const statusForm = ref<{
 const statusButtonLoading = ref(false);
 
 const statusRules = {
-  status: [
-    { required: true, message: t('project.management.rule.statusRequired'), trigger: 'change' }
-  ]
+  status: [{ required: true, message: t('project.management.rule.statusRequired'), trigger: 'change' }]
 };
 
 const initFormData: ManagementForm = {
@@ -470,10 +521,10 @@ const initFormData: ManagementForm = {
   cro: undefined,
   note: undefined,
   startTime: undefined,
-  endTime: undefined,
-}
+  endTime: undefined
+};
 const data = reactive<PageData<ManagementForm, ManagementQuery>>({
-  form: {...initFormData},
+  form: { ...initFormData },
   queryParams: {
     pageNum: 1,
     pageSize: 10,
@@ -491,25 +542,15 @@ const data = reactive<PageData<ManagementForm, ManagementQuery>>({
       startTime: undefined,
       endTime: undefined,
       createTime: undefined,
-      updateTime: undefined,
+      updateTime: undefined
     }
   },
   rules: {
-    id: [
-      { required: true, message: t('project.management.rule.idRequired'), trigger: "blur" }
-    ],
-    code: [
-      { required: true, message: t('project.management.rule.codeRequired'), trigger: "blur" }
-    ],
-    name: [
-      { required: true, message: t('project.management.rule.nameRequired'), trigger: "blur" }
-    ],
-    language: [
-      { required: true, message: t('project.management.rule.languageRequired'), trigger: "change" }
-    ],
-    type: [
-      { required: true, message: t('project.management.rule.typeRequired'), trigger: "change" }
-    ],
+    id: [{ required: true, message: t('project.management.rule.idRequired'), trigger: 'blur' }],
+    code: [{ required: true, message: t('project.management.rule.codeRequired'), trigger: 'blur' }],
+    name: [{ required: true, message: t('project.management.rule.nameRequired'), trigger: 'blur' }],
+    language: [{ required: true, message: t('project.management.rule.languageRequired'), trigger: 'change' }],
+    type: [{ required: true, message: t('project.management.rule.typeRequired'), trigger: 'change' }]
   }
 });
 
@@ -527,25 +568,25 @@ const getList = async () => {
   managementList.value = res.rows;
   total.value = res.total;
   loading.value = false;
-}
+};
 
 /** 取消按钮 */
 const cancel = () => {
   reset();
   dialog.visible = false;
-}
+};
 
 /** 表单重置 */
 const reset = () => {
-  form.value = {...initFormData};
+  form.value = { ...initFormData };
   managementFormRef.value?.resetFields();
-}
+};
 
 /** 搜索按钮操作 */
 const handleQuery = () => {
   queryParams.value.pageNum = 1;
   getList();
-}
+};
 
 /** 重置按钮操作 */
 const resetQuery = () => {
@@ -555,31 +596,31 @@ const resetQuery = () => {
   dateRangeUpdateTime.value = ['', ''];
   queryFormRef.value?.resetFields();
   handleQuery();
-}
+};
 
 /** 多选框选中数据 */
 const handleSelectionChange = (selection: ManagementVO[]) => {
-  ids.value = selection.map(item => item.id);
+  ids.value = selection.map((item) => item.id);
   single.value = selection.length != 1;
   multiple.value = !selection.length;
-}
+};
 
 /** 新增按钮操作 */
 const handleAdd = () => {
   reset();
   dialog.visible = true;
   dialog.title = t('project.management.dialog.add');
-}
+};
 
 /** 修改按钮操作 */
 const handleUpdate = async (row?: ManagementVO) => {
   reset();
-  const _id = row?.id || ids.value[0]
+  const _id = row?.id || ids.value[0];
   const res = await getManagement(_id);
   Object.assign(form.value, res.data);
   dialog.visible = true;
   dialog.title = t('project.management.dialog.edit');
-}
+};
 
 /** 提交按钮 */
 const submitForm = () => {
@@ -587,39 +628,43 @@ const submitForm = () => {
     if (valid) {
       buttonLoading.value = true;
       if (form.value.id) {
-        await updateManagement(form.value).finally(() =>  buttonLoading.value = false);
+        await updateManagement(form.value).finally(() => (buttonLoading.value = false));
       } else {
-        await addManagement(form.value).finally(() =>  buttonLoading.value = false);
+        await addManagement(form.value).finally(() => (buttonLoading.value = false));
       }
       proxy?.$modal.msgSuccess(t('project.management.message.operationSuccess'));
       dialog.visible = false;
       await getList();
     }
   });
-}
+};
 
 /** 删除按钮操作 */
 const handleDelete = async (row?: ManagementVO) => {
   const _ids = row?.id || ids.value;
-  await proxy?.$modal.confirm(t('project.management.message.deleteConfirm', { ids: _ids })).finally(() => loading.value = false);
+  await proxy?.$modal.confirm(t('project.management.message.deleteConfirm', { ids: _ids })).finally(() => (loading.value = false));
   await delManagement(_ids);
   proxy?.$modal.msgSuccess(t('project.management.message.deleteSuccess'));
   await getList();
-}
+};
 
 /** 导出按钮操作 */
 const handleExport = () => {
-  proxy?.download('project/management/export', {
-    ...queryParams.value
-  }, `management_${new Date().getTime()}.xlsx`)
-}
+  proxy?.download(
+    'project/management/export',
+    {
+      ...queryParams.value
+    },
+    `management_${new Date().getTime()}.xlsx`
+  );
+};
 
 /** 查看详情按钮操作 */
 const handleViewDetail = (row: ManagementVO) => {
   if (switchComponent && DetailComponent) {
     switchComponent(DetailComponent, { projectId: row.id });
   }
-}
+};
 
 /** 更新状态按钮操作 */
 const handleUpdateStatus = (row: ManagementVO) => {

+ 50 - 33
src/views/search/index.vue

@@ -10,27 +10,40 @@
       <!-- 搜索栏 -->
       <el-form :model="queryParams" :inline="true" class="search-form">
         <el-form-item :label="t('search.search.name')">
-          <el-input v-model="queryParams.name" :placeholder="t('search.search.namePlaceholder')" clearable
-            style="width: 200px" @keyup.enter="handleQuery" />
+          <el-input
+            v-model="queryParams.name"
+            :placeholder="t('search.search.namePlaceholder')"
+            clearable
+            style="width: 200px"
+            @keyup.enter="handleQuery"
+          />
         </el-form-item>
         <el-form-item :label="t('search.search.projectCode')">
-          <el-input v-model="queryParams.projectCode" :placeholder="t('search.search.projectCodePlaceholder')" clearable
-            style="width: 200px" @keyup.enter="handleQuery" />
+          <el-input
+            v-model="queryParams.projectCode"
+            :placeholder="t('search.search.projectCodePlaceholder')"
+            clearable
+            style="width: 200px"
+            @keyup.enter="handleQuery"
+          />
         </el-form-item>
         <el-form-item :label="t('search.search.projectName')">
-          <el-input v-model="queryParams.projectName" :placeholder="t('search.search.projectNamePlaceholder')" clearable
-            style="width: 200px" @keyup.enter="handleQuery" />
+          <el-input
+            v-model="queryParams.projectName"
+            :placeholder="t('search.search.projectNamePlaceholder')"
+            clearable
+            style="width: 200px"
+            @keyup.enter="handleQuery"
+          />
         </el-form-item>
         <el-form-item :label="t('search.search.type')">
-          <el-select v-model="queryParams.type" :placeholder="t('search.search.typePlaceholder')" clearable
-            style="width: 150px">
+          <el-select v-model="queryParams.type" :placeholder="t('search.search.typePlaceholder')" clearable style="width: 150px">
             <el-option :label="t('search.type.normalDocument')" :value="0" />
             <el-option :label="t('search.type.planDocument')" :value="1" />
           </el-select>
         </el-form-item>
         <el-form-item :label="t('search.search.status')">
-          <el-select v-model="queryParams.status" :placeholder="t('search.search.statusPlaceholder')" clearable
-            style="width: 150px">
+          <el-select v-model="queryParams.status" :placeholder="t('search.search.statusPlaceholder')" clearable style="width: 150px">
             <el-option :label="t('search.status.unUpload')" :value="0" />
             <el-option :label="t('search.status.unAudit')" :value="1" />
             <el-option :label="t('search.status.auditReject')" :value="2" />
@@ -42,14 +55,26 @@
           </el-select>
         </el-form-item>
         <el-form-item :label="t('search.search.createTime')">
-          <el-date-picker v-model="createTimeRange" type="daterange" range-separator="-"
-            :start-placeholder="t('search.search.startTime')" :end-placeholder="t('search.search.endTime')"
-            value-format="YYYY-MM-DD HH:mm:ss" style="width: 240px" />
+          <el-date-picker
+            v-model="createTimeRange"
+            type="daterange"
+            range-separator="-"
+            :start-placeholder="t('search.search.startTime')"
+            :end-placeholder="t('search.search.endTime')"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            style="width: 240px"
+          />
         </el-form-item>
         <el-form-item :label="t('search.search.updateTime')">
-          <el-date-picker v-model="updateTimeRange" type="daterange" range-separator="-"
-            :start-placeholder="t('search.search.startTime')" :end-placeholder="t('search.search.endTime')"
-            value-format="YYYY-MM-DD HH:mm:ss" style="width: 240px" />
+          <el-date-picker
+            v-model="updateTimeRange"
+            type="daterange"
+            range-separator="-"
+            :start-placeholder="t('search.search.startTime')"
+            :end-placeholder="t('search.search.endTime')"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            style="width: 240px"
+          />
         </el-form-item>
         <el-form-item>
           <el-button type="primary" icon="Search" @click="handleQuery">{{ t('search.button.search') }}</el-button>
@@ -60,8 +85,7 @@
       <!-- 文档列表 -->
       <el-table v-loading="loading" :data="documentList" border style="margin-top: 10px; width: 100%">
         <el-table-column prop="id" :label="t('search.table.id')" width="80" align="center" />
-        <el-table-column prop="folderName" :label="t('search.table.folderName')" min-width="120"
-          show-overflow-tooltip />
+        <el-table-column prop="folderName" :label="t('search.table.folderName')" min-width="120" show-overflow-tooltip />
         <el-table-column prop="name" :label="t('search.table.name')" min-width="150" show-overflow-tooltip />
         <el-table-column prop="type" :label="t('search.table.type')" width="120" align="center">
           <template #default="scope">
@@ -70,19 +94,13 @@
             <span v-else>-</span>
           </template>
         </el-table-column>
-        <el-table-column prop="specificationLabel" :label="t('search.table.specification')" min-width="120"
-          show-overflow-tooltip>
+        <el-table-column prop="specificationLabel" :label="t('search.table.specification')" min-width="120" show-overflow-tooltip>
           <template #default="scope">
-            <dict-tag 
-              v-if="scope.row.specification" 
-              :options="getSpecificationDict(scope.row.specificationType)" 
-              :value="scope.row.specification" 
-            />
+            <dict-tag v-if="scope.row.specification" :options="getSpecificationDict(scope.row.specificationType)" :value="scope.row.specification" />
             <span v-else>-</span>
           </template>
         </el-table-column>
-        <el-table-column prop="planDocumentTypeLabel" :label="t('search.table.planDocumentType')" min-width="120"
-          show-overflow-tooltip>
+        <el-table-column prop="planDocumentTypeLabel" :label="t('search.table.planDocumentType')" min-width="120" show-overflow-tooltip>
           <template #default="scope">
             <dict-tag v-if="scope.row.planType" :options="plan_document_type" :value="scope.row.planType" />
             <span v-else>-</span>
@@ -131,11 +149,11 @@
         <!-- 操作列 -->
         <el-table-column :label="t('search.table.action')" width="120" align="center" fixed="right">
           <template #default="scope">
-            <el-button 
-              v-if="scope.row.ossId" 
-              type="primary" 
+            <el-button
+              v-if="scope.row.ossId"
+              type="primary"
               icon="Download"
-              style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px;"
+              style="padding: 0 5px; font-size: 10px; height: 24px; --el-button-icon-span-gap: 2px"
               @click="handleDownload(scope.row.ossId, scope.row.fileName)"
             >
               {{ t('search.button.download') }}
@@ -146,8 +164,7 @@
       </el-table>
 
       <!-- 分页 -->
-      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
-        :total="total" @pagination="getList" />
+      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
     </el-card>
   </div>
 </template>

+ 6 - 6
src/views/setting/agreement/index.vue

@@ -11,10 +11,10 @@
         <el-tab-pane :label="t('agreement.tab.userAgreement')" name="userAgreement">
           <el-tabs v-model="userAgreementLang" type="card" class="mb-4">
             <el-tab-pane :label="t('agreement.lang.chinese')" name="zh_CN">
-              <editor v-model="userAgreementData.zh_CN" :min-height="400"/>
+              <editor v-model="userAgreementData.zh_CN" :min-height="400" />
             </el-tab-pane>
             <el-tab-pane :label="t('agreement.lang.english')" name="en_US">
-              <editor v-model="userAgreementData.en_US" :min-height="400"/>
+              <editor v-model="userAgreementData.en_US" :min-height="400" />
             </el-tab-pane>
           </el-tabs>
           <div class="mt-4">
@@ -24,10 +24,10 @@
         <el-tab-pane :label="t('agreement.tab.privacyAgreement')" name="privacyAgreement">
           <el-tabs v-model="privacyAgreementLang" type="card" class="mb-4">
             <el-tab-pane :label="t('agreement.lang.chinese')" name="zh_CN">
-              <editor v-model="privacyAgreementData.zh_CN" :min-height="400"/>
+              <editor v-model="privacyAgreementData.zh_CN" :min-height="400" />
             </el-tab-pane>
             <el-tab-pane :label="t('agreement.lang.english')" name="en_US">
-              <editor v-model="privacyAgreementData.en_US" :min-height="400"/>
+              <editor v-model="privacyAgreementData.en_US" :min-height="400" />
             </el-tab-pane>
           </el-tabs>
           <div class="mt-4">
@@ -127,7 +127,7 @@ const fetchData = async () => {
   } finally {
     loading.value = false;
   }
-}
+};
 
 /** 保存数据 */
 const handleSave = async () => {
@@ -160,7 +160,7 @@ const handleSave = async () => {
   } finally {
     saveLoading.value = false;
   }
-}
+};
 
 onMounted(() => {
   fetchData();

+ 1 - 3
src/views/setting/ai/index.vue

@@ -46,9 +46,7 @@ const aiData = ref<AiSettingVO | null>(null);
 
 // 表单验证规则
 const rules: FormRules = {
-  enabledFlag: [
-    { required: true, message: t('ai.rule.enabledFlagRequired'), trigger: 'change' }
-  ]
+  enabledFlag: [{ required: true, message: t('ai.rule.enabledFlagRequired'), trigger: 'change' }]
 };
 
 /**

+ 46 - 45
src/views/setting/carousel/index.vue

@@ -7,14 +7,18 @@
             <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['setting:carousel:add']">新增</el-button>
           </el-col>
           <el-col :span="1.5">
-            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['setting:carousel:edit']">修改</el-button>
+            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['setting:carousel:edit']"
+              >修改</el-button
+            >
           </el-col>
           <el-col :span="1.5">
-            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['setting:carousel:remove']">删除</el-button>
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['setting:carousel:remove']"
+              >删除</el-button
+            >
           </el-col>
-<!--          <el-col :span="1.5">-->
-<!--            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['setting:carousel:export']">导出</el-button>-->
-<!--          </el-col>-->
+          <!--          <el-col :span="1.5">-->
+          <!--            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['setting:carousel:export']">导出</el-button>-->
+          <!--          </el-col>-->
         </el-row>
       </template>
 
@@ -23,12 +27,12 @@
         <el-table-column label="序号" align="center" prop="id" v-if="true" />
         <el-table-column label="图片" align="center" prop="ossidUrl" width="100">
           <template #default="scope">
-            <image-preview :src="scope.row.ossidUrl" :width="50" :height="50"/>
+            <image-preview :src="scope.row.ossidUrl" :width="50" :height="50" />
           </template>
         </el-table-column>
         <el-table-column label="排序" align="center" prop="sort" />
         <el-table-column label="备注" align="center" prop="note" />
-        <el-table-column label="操作" align="center" fixed="right"  class-name="small-padding fixed-width">
+        <el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
           <template #default="scope">
             <el-tooltip content="修改" placement="top">
               <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['setting:carousel:edit']"></el-button>
@@ -46,7 +50,7 @@
     <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
       <el-form ref="carouselFormRef" :model="form" :rules="rules" label-width="80px">
         <el-form-item label="图片" prop="ossid">
-          <image-upload v-model="form.ossid"/>
+          <image-upload v-model="form.ossid" />
         </el-form-item>
         <el-form-item label="排序" prop="sort">
           <el-input v-model="form.sort" placeholder="请输入排序" />
@@ -90,26 +94,19 @@ const initFormData: CarouselForm = {
   id: undefined,
   ossid: undefined,
   sort: undefined,
-  note: undefined,
-}
+  note: undefined
+};
 const data = reactive<PageData<CarouselForm, CarouselQuery>>({
-  form: {...initFormData},
+  form: { ...initFormData },
   queryParams: {
     pageNum: 1,
     pageSize: 10,
-    params: {
-    }
+    params: {}
   },
   rules: {
-    id: [
-      { required: true, message: "序号不能为空", trigger: "blur" }
-    ],
-    ossid: [
-      { required: true, message: "图片不能为空", trigger: "blur" }
-    ],
-    sort: [
-      { required: true, message: "排序不能为空", trigger: "blur" }
-    ],
+    id: [{ required: true, message: '序号不能为空', trigger: 'blur' }],
+    ossid: [{ required: true, message: '图片不能为空', trigger: 'blur' }],
+    sort: [{ required: true, message: '排序不能为空', trigger: 'blur' }]
   }
 });
 
@@ -122,49 +119,49 @@ const getList = async () => {
   carouselList.value = res.rows;
   total.value = res.total;
   loading.value = false;
-}
+};
 
 /** 取消按钮 */
 const cancel = () => {
   reset();
   dialog.visible = false;
-}
+};
 
 /** 表单重置 */
 const reset = () => {
-  form.value = {...initFormData};
+  form.value = { ...initFormData };
   carouselFormRef.value?.resetFields();
-}
+};
 
 /** 搜索按钮操作 */
 const handleQuery = () => {
   queryParams.value.pageNum = 1;
   getList();
-}
+};
 
 /** 多选框选中数据 */
 const handleSelectionChange = (selection: CarouselVO[]) => {
-  ids.value = selection.map(item => item.id);
+  ids.value = selection.map((item) => item.id);
   single.value = selection.length != 1;
   multiple.value = !selection.length;
-}
+};
 
 /** 新增按钮操作 */
 const handleAdd = () => {
   reset();
   dialog.visible = true;
-  dialog.title = "添加轮播图设置";
-}
+  dialog.title = '添加轮播图设置';
+};
 
 /** 修改按钮操作 */
 const handleUpdate = async (row?: CarouselVO) => {
   reset();
-  const _id = row?.id || ids.value[0]
+  const _id = row?.id || ids.value[0];
   const res = await getCarousel(_id);
   Object.assign(form.value, res.data);
   dialog.visible = true;
-  dialog.title = "修改轮播图设置";
-}
+  dialog.title = '修改轮播图设置';
+};
 
 /** 提交按钮 */
 const submitForm = () => {
@@ -172,32 +169,36 @@ const submitForm = () => {
     if (valid) {
       buttonLoading.value = true;
       if (form.value.id) {
-        await updateCarousel(form.value).finally(() =>  buttonLoading.value = false);
+        await updateCarousel(form.value).finally(() => (buttonLoading.value = false));
       } else {
-        await addCarousel(form.value).finally(() =>  buttonLoading.value = false);
+        await addCarousel(form.value).finally(() => (buttonLoading.value = false));
       }
-      proxy?.$modal.msgSuccess("操作成功");
+      proxy?.$modal.msgSuccess('操作成功');
       dialog.visible = false;
       await getList();
     }
   });
-}
+};
 
 /** 删除按钮操作 */
 const handleDelete = async (row?: CarouselVO) => {
   const _ids = row?.id || ids.value;
-  await proxy?.$modal.confirm('是否确认删除轮播图设置编号为"' + _ids + '"的数据项?').finally(() => loading.value = false);
+  await proxy?.$modal.confirm('是否确认删除轮播图设置编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
   await delCarousel(_ids);
-  proxy?.$modal.msgSuccess("删除成功");
+  proxy?.$modal.msgSuccess('删除成功');
   await getList();
-}
+};
 
 /** 导出按钮操作 */
 const handleExport = () => {
-  proxy?.download('setting/carousel/export', {
-    ...queryParams.value
-  }, `carousel_${new Date().getTime()}.xlsx`)
-}
+  proxy?.download(
+    'setting/carousel/export',
+    {
+      ...queryParams.value
+    },
+    `carousel_${new Date().getTime()}.xlsx`
+  );
+};
 
 onMounted(() => {
   getList();

Някои файлове не бяха показани, защото твърде много файлове са промени