ソースを参照

- WPS基本对接完成

Huanyi 3 ヶ月 前
コミット
f77ec0debd

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

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { TaskVO, TaskForm, TaskQuery } from '@/api/qc/task/types';
+
+/**
+ * 查询文档质控任务列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listTask = (query?: TaskQuery): AxiosPromise<TaskVO[]> => {
+  return request({
+    url: '/qc/task/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询文档质控任务详细
+ * @param id
+ */
+export const getTask = (id: string | number): AxiosPromise<TaskVO> => {
+  return request({
+    url: '/qc/task/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增文档质控任务
+ * @param data
+ */
+export const addTask = (data: TaskForm) => {
+  return request({
+    url: '/qc/task',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改文档质控任务
+ * @param data
+ */
+export const updateTask = (data: TaskForm) => {
+  return request({
+    url: '/qc/task',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除文档质控任务
+ * @param id
+ */
+export const delTask = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/qc/task/' + id,
+    method: 'delete'
+  });
+};

+ 163 - 0
src/api/Qc/task/types.ts

@@ -0,0 +1,163 @@
+export interface TaskVO {
+  /**
+   * 序号
+   */
+  id: string | number;
+
+  /**
+   * 质控名称
+   */
+  name: string;
+
+  /**
+   * 发起人
+   */
+  initiator: number;
+
+  /**
+   * 质控项目
+   */
+  projectId: string | number;
+
+  /**
+   * 开始时间
+   */
+  startDate: string;
+
+  /**
+   * 截止时间
+   */
+  deadline: string;
+
+  /**
+   * 状态
+   */
+  status: number;
+
+  /**
+   * 备注
+   */
+  note: string;
+
+  /**
+   * 创建者
+   */
+  createBy: number;
+
+  /**
+   * 创建时间
+   */
+  createTime: string;
+
+  /**
+   * 更新者
+   */
+  updateBy: number;
+
+  /**
+   * 更新时间
+   */
+  updateTime: string;
+
+}
+
+export interface TaskForm extends BaseEntity {
+  /**
+   * 序号
+   */
+  id?: string | number;
+
+  /**
+   * 质控名称
+   */
+  name?: string;
+
+  /**
+   * 发起人
+   */
+  initiator?: number;
+
+  /**
+   * 质控项目
+   */
+  projectId?: string | number;
+
+  /**
+   * 开始时间
+   */
+  startDate?: string;
+
+  /**
+   * 截止时间
+   */
+  deadline?: string;
+
+  /**
+   * 状态
+   */
+  status?: number;
+
+  /**
+   * 备注
+   */
+  note?: string;
+
+}
+
+export interface TaskQuery extends PageQuery {
+
+  /**
+   * 质控名称
+   */
+  name?: string;
+
+  /**
+   * 发起人
+   */
+  initiator?: number;
+
+  /**
+   * 质控项目
+   */
+  projectId?: string | number;
+
+  /**
+   * 开始时间
+   */
+  startDate?: string;
+
+  /**
+   * 截止时间
+   */
+  deadline?: string;
+
+  /**
+   * 状态
+   */
+  status?: number;
+
+  /**
+   * 创建者
+   */
+  createBy?: number;
+
+  /**
+   * 创建时间
+   */
+  createTime?: string;
+
+  /**
+   * 更新者
+   */
+  updateBy?: number;
+
+  /**
+   * 更新时间
+   */
+  updateTime?: string;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}

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

@@ -1,6 +1,8 @@
 export interface AuditTaskVO {
 export interface AuditTaskVO {
   id: number;
   id: number;
   name: string;
   name: string;
+  type?: number; // 文档类型:0-非计划文档,1-计划文档
+  documentType?: string; // 计划文档类型
   status: number;
   status: number;
   submitter: string;
   submitter: string;
   deadline: string;
   deadline: string;
@@ -8,7 +10,8 @@ export interface AuditTaskVO {
   createBy: string;
   createBy: string;
   createTime: string;
   createTime: string;
   ossId: number;
   ossId: number;
-  ossUrl: string;
+  ossUrl?: string; // OSS 文件 URL
+  fileName?: string; // 文件名(可选,如果后端返回)
 }
 }
 
 
 export interface AuditTaskQuery extends PageQuery {
 export interface AuditTaskQuery extends PageQuery {

+ 12 - 0
src/api/project/management/index.ts

@@ -213,3 +213,15 @@ export const assignFolders = (data: AssignFoldersForm) => {
     data: data
     data: data
   });
   });
 };
 };
+
+/**
+ * 根据名称模糊查询项目
+ * @param name 项目名称
+ */
+export const getProjectByName = (name: string): AxiosPromise<Array<{ id: number; name: string }>> => {
+  return request({
+    url: '/project/management/getByName',
+    method: 'get',
+    params: { name }
+  });
+};

+ 79 - 0
src/api/wps/save.ts

@@ -442,3 +442,82 @@ 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'
+    });
+};
+
+/**
+ * 获取文档历史版本列表
+ * @param ossId OSS文件ID
+ */
+export interface FileVersion {
+    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 }
+    });
+};
+
+/**
+ * 初始化 WPS 文档
+ * @param ossId OSS文件ID
+ * @returns 返回当前版本号
+ */
+export const initWpsDocument = (ossId: string | number): Promise<{ data: number }> => {
+    return request({
+        url: `/wps/callback/v3/3rd/init/${ossId}`,
+        method: 'post'
+    });
+};
+
+/**
+ * 取消 WPS 文档编辑
+ * @param ossId OSS文件ID
+ */
+export const cancelWpsDocument = (ossId: string | number): Promise<any> => {
+    return request({
+        url: `/wps/callback/v3/3rd/cancel/${ossId}`,
+        method: 'delete'
+    });
+};
+
+/**
+ * 获取最终文档信息
+ * @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;
+}
+
+export const getFinalFile = (fileId: string): Promise<{ data: FinalFileInfo }> => {
+    return request({
+        url: '/wps/callback/v3/3rd/getFinal',
+        method: 'get',
+        params: { id: fileId }
+    });
+};

+ 411 - 161
src/components/DocumentAuditDialog/index.vue

@@ -9,14 +9,20 @@
             <span class="file-name">{{ document?.fileName || '未命名文档' }}</span>
             <span class="file-name">{{ document?.fileName || '未命名文档' }}</span>
           </div>
           </div>
           <div class="header-actions">
           <div class="header-actions">
+            <el-tooltip content="查看文档的历史版本" placement="bottom">
+              <el-button size="small" @click="handleViewVersions" :loading="loadingVersions">
+                <el-icon><Clock /></el-icon>
+                查看历史版本
+              </el-button>
+            </el-tooltip>
+            <el-tooltip content="清空文档中的所有批注" placement="bottom">
+              <el-button size="small" @click="handleCleanComments" :loading="cleaningComments">
+                <el-icon><Delete /></el-icon>
+                清空批注
+              </el-button>
+            </el-tooltip>
             <el-tooltip content="点击复制签名图片到剪贴板" placement="bottom">
             <el-tooltip content="点击复制签名图片到剪贴板" placement="bottom">
-              <el-button 
-                type="primary" 
-                size="small"
-                @click="handleCopyAvatar"
-                class="copy-avatar-btn"
-                :loading="copyingAvatar"
-              >
+              <el-button type="primary" size="small" @click="handleCopyAvatar" class="copy-avatar-btn" :loading="copyingAvatar">
                 <el-icon><Picture /></el-icon>
                 <el-icon><Picture /></el-icon>
                 复制签名
                 复制签名
               </el-button>
               </el-button>
@@ -25,9 +31,9 @@
         </div>
         </div>
         <div class="preview-container">
         <div class="preview-container">
           <!-- WPS 编辑器容器 -->
           <!-- WPS 编辑器容器 -->
-          <div 
-            v-if="document?.ossId" 
-            ref="wpsContainerRef" 
+          <div
+            v-if="document?.ossId"
+            ref="wpsContainerRef"
             class="wps-container"
             class="wps-container"
             @dragenter="handleDragEnter"
             @dragenter="handleDragEnter"
             @dragleave="handleDragLeave"
             @dragleave="handleDragLeave"
@@ -41,7 +47,7 @@
                 <p>松开鼠标插入图片</p>
                 <p>松开鼠标插入图片</p>
               </div>
               </div>
             </div>
             </div>
-            
+
             <!-- 降级方案:iframe 预览 -->
             <!-- 降级方案:iframe 预览 -->
             <iframe v-if="wpsError && document?.url" :src="document.url" class="document-iframe" frameborder="0"></iframe>
             <iframe v-if="wpsError && document?.url" :src="document.url" class="document-iframe" frameborder="0"></iframe>
           </div>
           </div>
@@ -56,7 +62,7 @@
           <div v-if="wpsError && !document?.url" class="error-state">
           <div v-if="wpsError && !document?.url" class="error-state">
             <el-result icon="error" title="加载失败" :sub-title="wpsError">
             <el-result icon="error" title="加载失败" :sub-title="wpsError">
               <template #extra>
               <template #extra>
-                <el-button type="primary" @click="initWpsEditor">重新加载</el-button>
+                <el-button type="primary" @click="() => initWpsEditor(false)">重新加载</el-button>
               </template>
               </template>
             </el-result>
             </el-result>
           </div>
           </div>
@@ -148,6 +154,26 @@
       </div>
       </div>
     </template>
     </template>
   </el-dialog>
   </el-dialog>
+
+  <!-- 历史版本对话框 -->
+  <el-dialog v-model="showVersionDialog" title="历史版本" width="800px" append-to-body destroy-on-close>
+    <el-table :data="versionList" v-loading="loadingVersions" stripe>
+      <el-table-column prop="version" label="版本号" width="150" align="center" />
+      <el-table-column prop="createTime" label="创建时间" min-width="180" align="center" />
+      <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>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <template #footer>
+      <el-button @click="showVersionDialog = false">关闭</el-button>
+    </template>
+  </el-dialog>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
@@ -155,9 +181,9 @@ import { ref, reactive, watch, nextTick, onBeforeUnmount } from 'vue';
 import { useI18n } from 'vue-i18n';
 import { useI18n } from 'vue-i18n';
 import type { FormInstance } from 'element-plus';
 import type { FormInstance } from 'element-plus';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import { ElMessage, ElMessageBox } from 'element-plus';
-import { Document, Edit, InfoFilled, CircleCheck, CircleClose, Close, Check, Loading, Upload, Picture } from '@element-plus/icons-vue';
+import { Document, Edit, InfoFilled, CircleCheck, CircleClose, Close, Check, Loading, Upload, Picture, Delete, Clock } from '@element-plus/icons-vue';
 import { useUserStore } from '@/store/modules/user';
 import { useUserStore } from '@/store/modules/user';
-import { getSignature, downloadSignature, updateDocumentVersion } from '@/api/system/signature';
+import { cleanDocumentComments, getFileVersionList, getFinalFile, initWpsDocument, cancelWpsDocument, type FileVersion } from '@/api/wps/save';
 
 
 interface Document {
 interface Document {
   id: number | string;
   id: number | string;
@@ -171,18 +197,18 @@ interface AuditData {
   documentId: number | string;
   documentId: number | string;
   result: number;
   result: number;
   rejectReason?: string;
   rejectReason?: string;
+  ossId?: number | string; // 最终的 ossId
 }
 }
 
 
 interface Props {
 interface Props {
   modelValue: boolean;
   modelValue: boolean;
   document?: Document | null;
   document?: Document | null;
   title?: string;
   title?: string;
-  auditApi: (data: AuditData) => Promise<any>;
 }
 }
 
 
 interface Emits {
 interface Emits {
   (e: 'update:modelValue', value: boolean): void;
   (e: 'update:modelValue', value: boolean): void;
-  (e: 'success'): void;
+  (e: 'submit', data: AuditData): void; // 提交审核数据
 }
 }
 
 
 const props = defineProps<Props>();
 const props = defineProps<Props>();
@@ -201,11 +227,16 @@ const wpsLoading = ref(false);
 const wpsError = ref('');
 const wpsError = ref('');
 const isDragging = ref(false);
 const isDragging = ref(false);
 const copyingAvatar = ref(false);
 const copyingAvatar = ref(false);
+const cleaningComments = ref(false);
+const currentVersion = ref(1); // 当前文档版本号
+const showVersionDialog = ref(false); // 显示历史版本对话框
+const versionList = ref<FileVersion[]>([]); // 历史版本列表
+const loadingVersions = ref(false); // 加载历史版本中
 let wpsInstance: any = null;
 let wpsInstance: any = null;
 let dragCounter = 0; // 用于跟踪拖拽进入/离开次数
 let dragCounter = 0; // 用于跟踪拖拽进入/离开次数
 
 
 // WPS 配置
 // WPS 配置
-const WPS_APP_ID = 'SX20251229FLIAPD';
+const WPS_APP_ID = 'SX20260105YMMIXV';
 
 
 // 审核表单数据
 // 审核表单数据
 const auditForm = ref({
 const auditForm = ref({
@@ -243,7 +274,8 @@ const getFileType = (fileName: string) => {
 };
 };
 
 
 // 初始化 WPS 编辑器
 // 初始化 WPS 编辑器
-const initWpsEditor = async () => {
+// shouldCallInitApi: 是否需要调用后端初始化接口(只在 dialog 首次打开时为 true)
+const initWpsEditor = async (shouldCallInitApi = false) => {
   if (!wpsContainerRef.value || !props.document?.ossId) {
   if (!wpsContainerRef.value || !props.document?.ossId) {
     return;
     return;
   }
   }
@@ -262,15 +294,44 @@ const initWpsEditor = async () => {
     const WebOfficeSDK = (window as any).WebOfficeSDK;
     const WebOfficeSDK = (window as any).WebOfficeSDK;
 
 
     // 获取文件类型
     // 获取文件类型
-    const officeType = getFileType(props.document.fileName || '');
+    const officeType = getFileType(props.document.fileName || '', props.document.url);
+
+    // 只在 dialog 首次打开时调用后端接口初始化文档
+    if (shouldCallInitApi) {
+      console.log('[WPS] 调用后端初始化接口,ossId:', props.document.ossId);
+      try {
+        const initRes = await initWpsDocument(props.document.ossId);
+        const backendVersion = initRes.data; // data 直接是版本号
+        currentVersion.value = backendVersion;
+        console.log('[WPS] 后端返回版本号:', backendVersion);
+      } catch (err: any) {
+        console.error('[WPS] 调用后端初始化接口失败:', err);
+        wpsLoading.value = false;
+        wpsError.value = '初始化文档失败';
+        
+        // 显示错误提示
+        ElMessage.error('初始化文档失败: ' + (err.message || '未知错误'));
+        
+        // 关闭对话框
+        setTimeout(() => {
+          dialogVisible.value = false;
+        }, 1500);
+        
+        return; // 终止初始化流程
+      }
+    } else {
+      console.log('[WPS] 使用当前版本号重新初始化编辑器,版本:', currentVersion.value);
+    }
 
 
-    // 使用原始文档 ID(不添加时间戳)
-    const fileId = `${props.document.id}`;
+    // 使用 ossId + 当前版本号组成 fileId
+    const fileId = `${props.document.ossId}_${currentVersion.value}`;
 
 
     console.log('[WPS] 初始化配置:', {
     console.log('[WPS] 初始化配置:', {
       appId: WPS_APP_ID,
       appId: WPS_APP_ID,
       officeType: officeType,
       officeType: officeType,
       fileId: fileId,
       fileId: fileId,
+      ossId: props.document.ossId,
+      version: currentVersion.value,
       fileName: props.document.fileName,
       fileName: props.document.fileName,
       fileUrl: props.document.url
       fileUrl: props.document.url
     });
     });
@@ -283,7 +344,9 @@ const initWpsEditor = async () => {
       fileId: fileId,
       fileId: fileId,
 
 
       // 可选参数
       // 可选参数
-      mount: wpsContainerRef.value
+      mount: wpsContainerRef.value,
+      // 指定当前用户ID为编辑者ID
+      userId: String(userStore.userId)
     };
     };
 
 
     // 初始化 WPS 编辑器
     // 初始化 WPS 编辑器
@@ -356,50 +419,50 @@ const handleDragOver = (e: DragEvent) => {
 const handleDrop = async (e: DragEvent) => {
 const handleDrop = async (e: DragEvent) => {
   e.preventDefault();
   e.preventDefault();
   e.stopPropagation();
   e.stopPropagation();
-  
+
   isDragging.value = false;
   isDragging.value = false;
   dragCounter = 0;
   dragCounter = 0;
-  
+
   if (!wpsInstance) {
   if (!wpsInstance) {
     ElMessage.warning('WPS 编辑器未初始化');
     ElMessage.warning('WPS 编辑器未初始化');
     return;
     return;
   }
   }
-  
+
   // 处理图片拖放
   // 处理图片拖放
   const files = e.dataTransfer?.files;
   const files = e.dataTransfer?.files;
   if (!files || files.length === 0) {
   if (!files || files.length === 0) {
     return;
     return;
   }
   }
-  
+
   // 只处理第一个文件
   // 只处理第一个文件
   const file = files[0];
   const file = files[0];
-  
+
   // 检查是否是图片
   // 检查是否是图片
   if (!file.type.startsWith('image/')) {
   if (!file.type.startsWith('image/')) {
     ElMessage.warning('只支持插入图片文件');
     ElMessage.warning('只支持插入图片文件');
     return;
     return;
   }
   }
-  
+
   try {
   try {
     console.log('[WPS] 开始插入图片:', file.name);
     console.log('[WPS] 开始插入图片:', file.name);
-    
+
     // 读取图片为 base64
     // 读取图片为 base64
     const reader = new FileReader();
     const reader = new FileReader();
     reader.onload = async (event) => {
     reader.onload = async (event) => {
       const base64 = event.target?.result as string;
       const base64 = event.target?.result as string;
-      
+
       try {
       try {
         // 获取 WPS Application 对象
         // 获取 WPS Application 对象
         const app = await wpsInstance.Application;
         const app = await wpsInstance.Application;
-        
+
         if (!app) {
         if (!app) {
           ElMessage.error('无法获取 WPS Application 对象');
           ElMessage.error('无法获取 WPS Application 对象');
           return;
           return;
         }
         }
-        
+
         // 根据文件类型插入图片
         // 根据文件类型插入图片
         const officeType = getFileType(props.document?.fileName || '');
         const officeType = getFileType(props.document?.fileName || '');
-        
+
         if (officeType === 'w') {
         if (officeType === 'w') {
           // Word 文档:插入图片到光标位置
           // Word 文档:插入图片到光标位置
           const selection = await app.ActiveDocument.Application.Selection;
           const selection = await app.ActiveDocument.Application.Selection;
@@ -421,20 +484,19 @@ const handleDrop = async (e: DragEvent) => {
         } else {
         } else {
           ElMessage.warning('当前文档类型不支持插入图片');
           ElMessage.warning('当前文档类型不支持插入图片');
         }
         }
-        
+
         console.log('[WPS] 图片插入成功');
         console.log('[WPS] 图片插入成功');
       } catch (err: any) {
       } catch (err: any) {
         console.error('[WPS] 插入图片失败:', err);
         console.error('[WPS] 插入图片失败:', err);
         ElMessage.error('插入图片失败: ' + err.message);
         ElMessage.error('插入图片失败: ' + err.message);
       }
       }
     };
     };
-    
+
     reader.onerror = () => {
     reader.onerror = () => {
       ElMessage.error('读取图片文件失败');
       ElMessage.error('读取图片文件失败');
     };
     };
-    
+
     reader.readAsDataURL(file);
     reader.readAsDataURL(file);
-    
   } catch (err: any) {
   } catch (err: any) {
     console.error('[WPS] 处理拖放失败:', err);
     console.error('[WPS] 处理拖放失败:', err);
     ElMessage.error('处理拖放失败');
     ElMessage.error('处理拖放失败');
@@ -444,123 +506,278 @@ const handleDrop = async (e: DragEvent) => {
 // 复制头像到剪贴板
 // 复制头像到剪贴板
 const handleCopyAvatar = async () => {
 const handleCopyAvatar = async () => {
   try {
   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'
+        }
+      );
+      // 点击确认按钮 = 通过
+      reviewResult = 'pass';
+    } catch (action) {
+      if (action === 'cancel') {
+        // 点击取消按钮 = 驳回
+        reviewResult = 'reject';
+      } else {
+        // 点击关闭或按 ESC = 取消操作
+        console.log('[签名] 用户取消选择');
+        return;
+      }
+    }
+
     copyingAvatar.value = true;
     copyingAvatar.value = true;
-    console.log('[签名] 开始获取签名信息');
+    console.log('[签名] 开始生成审核信息图片,审核结果:', reviewResult);
+
+    // 获取当前用户昵称
+    const reviewerName = userStore.nickname || userStore.name || t('document.document.copySignature.unknown');
     
     
-    // 1. 调用获取签名接口
-    const signatureRes = await getSignature();
-    const ossId = signatureRes.data.id;
+    // 获取当前时间
+    const now = new Date();
+    const year = now.getFullYear();
+    const month = now.getMonth() + 1;
+    const day = now.getDate();
+    const hour = now.getHours();
+    const minute = now.getMinutes();
+    const reviewTime = t('document.document.copySignature.timeFormat', { year, month, day, hour, minute });
     
     
-    if (!ossId) {
-      ElMessage.warning('未找到签名图片');
+    // 审核结果文本
+    const resultText = reviewResult === 'pass' 
+      ? t('document.document.copySignature.passText') 
+      : t('document.document.copySignature.rejectText');
+    
+    // 根据审核结果确定颜色
+    const color = reviewResult === 'pass' ? '#00aa00' : '#ff0000';
+
+    console.log('[签名] 审核结果:', reviewResult, '颜色:', color, '结果文本:', resultText);
+
+    // 创建 canvas
+    const canvas = document.createElement('canvas');
+    const ctx = canvas.getContext('2d');
+    
+    if (!ctx) {
+      ElMessage.error(t('document.document.copySignature.canvasNotSupported'));
+      copyingAvatar.value = false;
       return;
       return;
     }
     }
+
+    // 设置 canvas 尺寸
+    const width = 300;
+    const height = 100;
+    canvas.width = width;
+    canvas.height = height;
+
+    // 不填充背景,保持透明
+
+    // 绘制边框(5px 粗,根据审核结果变色)
+    ctx.strokeStyle = color;
+    ctx.lineWidth = 5;
+    ctx.strokeRect(2.5, 2.5, width - 5, height - 5);
+
+    // 设置字体样式(根据审核结果变色)
+    ctx.fillStyle = color;
+    ctx.textBaseline = 'middle';
+
+    // 第一行:审核人(左边)和审核结果(右边)
+    ctx.font = 'bold 18px Arial, "Microsoft YaHei", sans-serif';
+    const reviewerText = t('document.document.copySignature.reviewer', { name: reviewerName });
+    ctx.fillText(reviewerText, 20, height / 3);
     
     
-    console.log('[签名] 签名 OSS ID:', ossId);
-    
-    // 2. 下载签名图片
-    console.log('[签名] 开始下载签名图片');
-    const blob = await downloadSignature(ossId);
+    // 审核结果靠右显示
+    const resultWidth = ctx.measureText(resultText).width;
+    ctx.fillText(resultText, width - resultWidth - 20, height / 3);
+
+    // 第二行:审核时间(居左对齐,加粗)
+    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);
+
+    console.log('[签名] Canvas 绘制完成');
+
+    // 将 canvas 转换为 Blob
+    canvas.toBlob(async (blob) => {
+      if (!blob) {
+        ElMessage.error(t('document.document.copySignature.generateFailed'));
+        copyingAvatar.value = false;
+        return;
+      }
+
+      console.log('[签名] 图片生成成功,大小:', blob.size);
+
+      try {
+        // 复制到剪贴板
+        await navigator.clipboard.write([
+          new ClipboardItem({
+            'image/png': blob
+          })
+        ]);
+
+        ElMessage({
+          type: 'success',
+          message: t('document.document.copySignature.copySuccess'),
+          duration: 3000
+        });
+
+        // 显示使用提示
+        setTimeout(() => {
+          ElMessage({
+            type: 'info',
+            dangerouslyUseHTMLString: true,
+            message: t('document.document.copySignature.usageHint'),
+            duration: 6000,
+            showClose: true
+          });
+        }, 500);
+
+        console.log('[签名] 复制成功');
+      } catch (err: any) {
+        console.error('[签名] 复制失败:', err);
+
+        // 根据错误类型显示不同提示
+        if (err.message?.includes('clipboard') || err.message?.includes('Clipboard')) {
+          ElMessage({
+            type: 'warning',
+            dangerouslyUseHTMLString: true,
+            message: t('document.document.copySignature.browserNotSupported'),
+            duration: 6000,
+            showClose: true
+          });
+        } else {
+          ElMessage({
+            type: 'error',
+            message: t('document.document.copySignature.copyFailed', { error: err.message || t('document.document.copySignature.unknownError') }),
+            duration: 5000
+          });
+        }
+      } finally {
+        copyingAvatar.value = false;
+      }
+    }, 'image/png');
+  } catch (err: any) {
+    console.error('[签名] 生成图片失败:', err);
+    ElMessage.error(t('document.document.copySignature.generateFailed') + ': ' + (err.message || t('document.document.copySignature.unknownError')));
+    copyingAvatar.value = false;
+  }
+};
+
+// 清空批注
+const handleCleanComments = async () => {
+  if (!props.document?.id || !props.document?.ossId) {
+    ElMessage.warning('文档信息不完整');
+    return;
+  }
+
+  try {
+    await ElMessageBox.confirm(
+      '确定要清空文档中的所有批注吗?此操作不可恢复。',
+      '清空批注',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }
+    );
+
+    cleaningComments.value = true;
+    console.log('[清空批注] 开始清空文档批注,文档ID:', props.document.id, 'ossId:', props.document.ossId, '当前版本:', currentVersion.value);
+
+    await cleanDocumentComments(props.document.ossId);
+
+    ElMessage.success('批注已清空');
+    console.log('[清空批注] 批注清空成功');
+
+    // 版本号递增
+    currentVersion.value += 1;
+    console.log('[清空批注] 版本号递增至:', currentVersion.value);
+
+    // 销毁当前 WPS 编辑器
+    destroyWpsEditor();
     
     
-    console.log('[签名] 图片下载成功,大小:', blob.size, '原始类型:', blob.type);
+    // 等待 DOM 更新
+    await nextTick();
     
     
-    // 3. 检测图片真实类型并转换 Blob
-    let imageBlob = blob;
-    let mimeType = blob.type;
+    // 使用新版本号重新初始化 WPS 编辑器(不调用后端初始化接口)
+    await initWpsEditor(false);
     
     
-    // 如果是 application/octet-stream,需要检测真实的图片类型
-    if (mimeType === 'application/octet-stream' || !mimeType.startsWith('image/')) {
-      console.log('[签名] 检测到非图片 MIME 类型,尝试转换');
-      
-      // 通过读取文件头来判断图片类型
-      const arrayBuffer = await blob.arrayBuffer();
-      const uint8Array = new Uint8Array(arrayBuffer);
-      
-      // 检测文件头
-      if (uint8Array[0] === 0xFF && uint8Array[1] === 0xD8 && uint8Array[2] === 0xFF) {
-        mimeType = 'image/jpeg';
-      } else if (uint8Array[0] === 0x89 && uint8Array[1] === 0x50 && uint8Array[2] === 0x4E && uint8Array[3] === 0x47) {
-        mimeType = 'image/png';
-      } else if (uint8Array[0] === 0x47 && uint8Array[1] === 0x49 && uint8Array[2] === 0x46) {
-        mimeType = 'image/gif';
-      } else if (uint8Array[0] === 0x42 && uint8Array[1] === 0x4D) {
-        mimeType = 'image/bmp';
-      } else if (uint8Array[0] === 0x52 && uint8Array[1] === 0x49 && uint8Array[2] === 0x46 && uint8Array[3] === 0x46) {
-        mimeType = 'image/webp';
-      } else {
-        // 默认使用 PNG
-        mimeType = 'image/png';
-      }
-      
-      console.log('[签名] 检测到的图片类型:', mimeType);
-      
-      // 创建新的 Blob,使用正确的 MIME 类型
-      imageBlob = new Blob([arrayBuffer], { type: mimeType });
+    console.log('[清空批注] WPS 编辑器已使用新版本重新初始化,fileId:', `${props.document.ossId}_${currentVersion.value}`);
+  } catch (err: any) {
+    if (err === 'cancel') {
+      console.log('[清空批注] 用户取消操作');
+      return;
     }
     }
+
+    console.error('[清空批注] 清空失败:', err);
+    ElMessage.error('清空批注失败: ' + (err.message || '未知错误'));
+  } finally {
+    cleaningComments.value = false;
+  }
+};
+
+// 查看历史版本
+const handleViewVersions = async () => {
+  if (!props.document?.ossId) {
+    ElMessage.warning('文档信息不完整');
+    return;
+  }
+
+  try {
+    loadingVersions.value = true;
+    showVersionDialog.value = true;
     
     
-    console.log('[签名] 最终 MIME 类型:', imageBlob.type);
+    console.log('[历史版本] 获取历史版本列表,ossId:', props.document.ossId);
     
     
-    // 4. 复制到剪贴板
-    await navigator.clipboard.write([
-      new ClipboardItem({
-        [imageBlob.type]: imageBlob
-      })
-    ]);
+    const res = await getFileVersionList(props.document.ossId);
+    versionList.value = res.data || [];
     
     
-    ElMessage({
-      type: 'success',
-      message: '签名图片已复制到剪贴板',
-      duration: 3000
-    });
+    console.log('[历史版本] 获取成功,版本数量:', versionList.value.length);
+  } catch (err: any) {
+    console.error('[历史版本] 获取失败:', err);
+    ElMessage.error('获取历史版本失败: ' + (err.message || '未知错误'));
+    showVersionDialog.value = false;
+  } finally {
+    loadingVersions.value = false;
+  }
+};
+
+// 选择历史版本
+const handleSelectVersion = async (version: number) => {
+  if (!props.document?.ossId) {
+    ElMessage.warning('文档信息不完整');
+    return;
+  }
+
+  try {
+    console.log('[历史版本] 选择版本:', version, 'ossId:', props.document.ossId);
     
     
-    // 显示使用提示
-    setTimeout(() => {
-      ElMessage({
-        type: 'info',
-        dangerouslyUseHTMLString: true,
-        message: `
-          <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>
-        `,
-        duration: 6000,
-        showClose: true
-      });
-    }, 500);
+    // 更新当前版本号
+    currentVersion.value = version;
     
     
-    console.log('[签名] 复制成功');
-  } catch (err: any) {
-    console.error('[签名] 复制失败:', err);
+    // 关闭历史版本对话框
+    showVersionDialog.value = false;
     
     
-    // 根据错误类型显示不同提示
-    if (err.message?.includes('clipboard') || err.message?.includes('Clipboard')) {
-      ElMessage({
-        type: 'warning',
-        dangerouslyUseHTMLString: true,
-        message: `
-          <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>
-        `,
-        duration: 6000,
-        showClose: true
-      });
-    } else if (err.response?.status === 404) {
-      ElMessage.warning('未找到签名图片,请先设置签名');
-    } else {
-      ElMessage({
-        type: 'error',
-        message: '获取签名失败: ' + (err.message || '未知错误'),
-        duration: 5000
-      });
-    }
-  } finally {
-    copyingAvatar.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) {
+    console.error('[历史版本] 切换版本失败:', err);
+    ElMessage.error('切换版本失败: ' + (err.message || '未知错误'));
   }
   }
 };
 };
 
 
@@ -570,6 +787,9 @@ watch(
   (val) => {
   (val) => {
     dialogVisible.value = val;
     dialogVisible.value = val;
     if (val && props.document) {
     if (val && props.document) {
+      // 重置版本号为 1
+      currentVersion.value = 1;
+      
       auditForm.value = {
       auditForm.value = {
         id: props.document.id,
         id: props.document.id,
         result: '3',
         result: '3',
@@ -577,9 +797,10 @@ watch(
       };
       };
       nextTick(() => {
       nextTick(() => {
         auditFormRef.value?.clearValidate();
         auditFormRef.value?.clearValidate();
-        // 自动初始化 WPS 编辑器
+        // 自动初始化 WPS 编辑器,使用版本号 1(调用后端初始化接口)
         if (props.document?.ossId) {
         if (props.document?.ossId) {
-          initWpsEditor();
+          console.log('[WPS] 对话框打开,初始化编辑器,版本号:', currentVersion.value);
+          initWpsEditor(true);
         }
         }
       });
       });
     }
     }
@@ -587,14 +808,28 @@ watch(
 );
 );
 
 
 // 监听dialogVisible变化
 // 监听dialogVisible变化
-watch(dialogVisible, (val) => {
+watch(dialogVisible, async (val) => {
   emit('update:modelValue', val);
   emit('update:modelValue', val);
   if (!val) {
   if (!val) {
+    // 对话框关闭时,调用取消接口
+    if (props.document?.ossId) {
+      try {
+        console.log('[WPS] 对话框关闭,调用取消接口,ossId:', props.document.ossId);
+        await cancelWpsDocument(props.document.ossId);
+        console.log('[WPS] 取消接口调用成功');
+      } catch (err) {
+        console.error('[WPS] 取消接口调用失败:', err);
+        // 取消接口失败不影响关闭流程
+      }
+    }
+    
     auditForm.value = {
     auditForm.value = {
       id: 0,
       id: 0,
       result: '3',
       result: '3',
       reason: ''
       reason: ''
     };
     };
+    // 关闭对话框时重置版本号
+    currentVersion.value = 1;
     destroyWpsEditor();
     destroyWpsEditor();
   }
   }
 });
 });
@@ -616,34 +851,49 @@ const submitForm = () => {
           try {
           try {
             savedFileInfo = await saveWpsDocument();
             savedFileInfo = await saveWpsDocument();
             if (savedFileInfo) {
             if (savedFileInfo) {
-              console.log('文档保存成功:', savedFileInfo);
+              console.log('[审核提交] 文档保存成功:', savedFileInfo);
               ElMessage.success('文档已保存');
               ElMessage.success('文档已保存');
             }
             }
           } catch (err) {
           } catch (err) {
-            console.error('保存文档失败:', err);
+            console.error('[审核提交] 保存文档失败:', err);
             ElMessage.warning('文档保存失败,将继续提交审核');
             ElMessage.warning('文档保存失败,将继续提交审核');
           }
           }
         }
         }
 
 
+        // 获取当前 fileId
+        const currentFileId = `${props.document?.ossId}_${currentVersion.value}`;
+        console.log('[审核提交] 当前 fileId:', currentFileId);
+
+        // 调用接口获取最终文档信息
+        let finalOssId = props.document?.ossId;
+        try {
+          console.log('[审核提交] 获取最终文档信息...');
+          const finalFileRes = await getFinalFile(currentFileId);
+          finalOssId = finalFileRes.data.ossId;
+          console.log('[审核提交] 获取到最终 ossId:', finalOssId);
+        } catch (err) {
+          console.error('[审核提交] 获取最终文档信息失败:', err);
+          ElMessage.warning('获取最终文档信息失败,将使用原始 ossId');
+        }
+
         // 构建审核数据
         // 构建审核数据
-        const auditData: any = {
+        const auditData: AuditData = {
           documentId: auditForm.value.id,
           documentId: auditForm.value.id,
           result: parseInt(auditForm.value.result),
           result: parseInt(auditForm.value.result),
-          rejectReason: auditForm.value.reason
+          rejectReason: auditForm.value.reason,
+          ossId: finalOssId // 使用最终的 ossId
         };
         };
 
 
-        // 添加保存的文件信息
-        if (savedFileInfo) {
-          auditData.fileInfo = savedFileInfo;
-        }
-
-        await props.auditApi(auditData);
-        ElMessage.success(t('document.document.message.auditSuccess'));
+        console.log('[审核提交] 提交审核数据到父组件:', auditData);
+        
+        // 关闭对话框
         dialogVisible.value = false;
         dialogVisible.value = false;
-        emit('success');
+        
+        // 通过 emit 将审核数据传递给父组件
+        emit('submit', auditData);
       } catch (error) {
       } catch (error) {
-        console.error(t('document.document.message.auditFailed'), error);
-        ElMessage.error(t('document.document.message.auditFailed'));
+        console.error('[审核提交] 处理失败:', error);
+        ElMessage.error('处理失败: ' + (error as any).message || '未知错误');
       } finally {
       } finally {
         loading.value = false;
         loading.value = false;
       }
       }
@@ -728,11 +978,11 @@ onBeforeUnmount(() => {
       white-space: nowrap;
       white-space: nowrap;
     }
     }
   }
   }
-  
+
   .header-actions {
   .header-actions {
     display: flex;
     display: flex;
     gap: 8px;
     gap: 8px;
-    
+
     .copy-avatar-btn {
     .copy-avatar-btn {
       .el-icon {
       .el-icon {
         margin-right: 4px;
         margin-right: 4px;
@@ -760,7 +1010,7 @@ onBeforeUnmount(() => {
     height: 100%;
     height: 100%;
     border: none;
     border: none;
   }
   }
-  
+
   .drag-overlay {
   .drag-overlay {
     position: absolute;
     position: absolute;
     top: 0;
     top: 0;
@@ -774,16 +1024,16 @@ onBeforeUnmount(() => {
     justify-content: center;
     justify-content: center;
     z-index: 9999;
     z-index: 9999;
     pointer-events: none;
     pointer-events: none;
-    
+
     .drag-hint {
     .drag-hint {
       text-align: center;
       text-align: center;
-      
+
       .drag-icon {
       .drag-icon {
         font-size: 64px;
         font-size: 64px;
         color: #409eff;
         color: #409eff;
         margin-bottom: 16px;
         margin-bottom: 16px;
       }
       }
-      
+
       p {
       p {
         font-size: 18px;
         font-size: 18px;
         font-weight: 500;
         font-weight: 500;

+ 20 - 0
src/lang/modules/document/document/en_US.ts

@@ -223,5 +223,25 @@ export default {
     auditorName: 'Auditor',
     auditorName: 'Auditor',
     rejectReason: 'Reject Reason',
     rejectReason: 'Reject Reason',
     operation: 'Operation'
     operation: 'Operation'
+  },
+  // Copy Signature
+  copySignature: {
+    selectResult: 'Select Audit Result',
+    selectResultMessage: 'Please select the audit result to generate',
+    pass: 'Pass',
+    reject: 'Reject',
+    passText: 'Pass',
+    rejectText: 'Reject',
+    unknown: 'Unknown',
+    timeFormat: '{month}/{day}/{year} {hour}:{minute}',
+    reviewer: 'Reviewer: {name}',
+    reviewTime: 'Review Time: {time}',
+    canvasNotSupported: 'Browser does not support Canvas',
+    generateFailed: 'Failed to generate image',
+    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>'
   }
   }
 };
 };

+ 25 - 5
src/lang/modules/document/document/zh_CN.ts

@@ -8,10 +8,10 @@ export default {
   // 按钮
   // 按钮
   button: {
   button: {
     newFolder: '新建文件夹',
     newFolder: '新建文件夹',
-    submit: '确 定',
-    cancel: '取 消',
-    search: '搜 索',
-    reset: '重 置',
+    submit: '递交',
+    cancel: '取消',
+    search: '搜索',
+    reset: '重置',
     audit: '审核',
     audit: '审核',
     mark: '标记',
     mark: '标记',
     download: '下载',
     download: '下载',
@@ -223,5 +223,25 @@ export default {
     auditorName: '审核人',
     auditorName: '审核人',
     rejectReason: '驳回理由',
     rejectReason: '驳回理由',
     operation: '操作'
     operation: '操作'
+  },
+  // 复制签名
+  copySignature: {
+    selectResult: '选择审核结果',
+    selectResultMessage: '请选择要生成的审核结果',
+    pass: '通过',
+    reject: '驳回',
+    passText: '通过',
+    rejectText: '驳回',
+    unknown: '未知',
+    timeFormat: '{year}年{month}月{day}日{hour}时{minute}分',
+    reviewer: '审核人:{name}',
+    reviewTime: '审核时间:{time}',
+    canvasNotSupported: '浏览器不支持 Canvas',
+    generateFailed: '生成图片失败',
+    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>'
   }
   }
-};
+};

+ 418 - 0
src/views/Qc/task/index.vue

@@ -0,0 +1,418 @@
+<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>
+
+      <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" />
+        <el-table-column label="发起人" align="center" prop="initiator" />
+        <el-table-column label="质控项目" align="center" prop="projectId" />
+        <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" />
+        <el-table-column label="备注" align="center" prop="note" />
+        <el-table-column label="创建者" align="center" prop="createBy" />
+        <el-table-column label="创建时间" align="center" prop="createTime" width="180">
+          <template #default="scope">
+            <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="更新者" align="center" prop="updateBy" />
+        <el-table-column label="更新时间" align="center" prop="updateTime" width="180">
+          <template #default="scope">
+            <span>{{ parseTime(scope.row.updateTime, '{y}-{m}-{d}') }}</span>
+          </template>
+        </el-table-column>
+        <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="['Qc:task:edit']"></el-button>
+            </el-tooltip>
+            <el-tooltip content="删除" placement="top">
+              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['Qc:task:remove']"></el-button>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+    </el-card>
+    <!-- 添加或修改文档质控任务对话框 -->
+    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
+      <el-form ref="taskFormRef" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="质控名称" prop="name">
+          <el-input v-model="form.name" placeholder="请输入质控名称" />
+        </el-form-item>
+        <el-form-item label="发起人" prop="initiator">
+          <el-input v-model="form.initiator" placeholder="请输入发起人" />
+        </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%"
+          >
+            <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="startDate">
+          <el-date-picker clearable
+            v-model="form.startDate"
+            type="datetime"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            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="datetime"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            placeholder="请选择截止时间"
+            style="width: 100%">
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label="备注" prop="note">
+            <el-input v-model="form.note" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="Task" lang="ts">
+import { listTask, getTask, delTask, addTask, updateTask } from '@/api/qc/task';
+import { TaskVO, TaskQuery, TaskForm } from '@/api/qc/task/types';
+import { getProjectByName } from '@/api/project/management';
+import { useUserStore } from '@/store/modules/user';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const userStore = useUserStore();
+
+const taskList = ref<TaskVO[]>([]);
+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 }>>([]);
+const projectLoading = ref(false);
+
+const queryFormRef = ref<ElFormInstance>();
+const taskFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: TaskForm = {
+  id: undefined,
+  name: undefined,
+  initiator: undefined,
+  projectId: undefined,
+  startDate: undefined,
+  deadline: undefined,
+  status: undefined,
+  note: undefined,
+}
+const data = reactive<PageData<TaskForm, TaskQuery>>({
+  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" }
+    ],
+    initiator: [
+      { required: true, message: "发起人不能为空", trigger: "blur" }
+    ],
+    projectId: [
+      { required: true, message: "质控项目不能为空", trigger: "change" }
+    ],
+    startDate: [
+      { required: true, message: "开始时间不能为空", trigger: "blur" }
+    ],
+    deadline: [
+      { required: true, message: "截止时间不能为空", trigger: "blur" }
+    ],
+    status: [
+      { required: true, message: "状态不能为空", trigger: "change" }
+    ],
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 远程搜索项目 */
+const remoteSearchProject = async (query: string) => {
+  if (query) {
+    projectLoading.value = true;
+    try {
+      const res = await getProjectByName(query);
+      projectList.value = res.data || [];
+    } catch (error) {
+      console.error('搜索项目失败:', error);
+      projectList.value = [];
+    } finally {
+      projectLoading.value = false;
+    }
+  } else {
+    projectList.value = [];
+  }
+};
+
+/** 查询文档质控任务列表 */
+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();
+  dialog.visible = false;
+}
+
+/** 表单重置 */
+const reset = () => {
+  form.value = {...initFormData};
+  projectList.value = [];
+  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();
+  // 设置默认发起人为当前用户
+  form.value.initiator = userStore.nickname || userStore.name;
+  dialog.visible = true;
+  dialog.title = "添加文档质控任务";
+}
+
+/** 修改按钮操作 */
+const handleUpdate = async (row?: TaskVO) => {
+  reset();
+  const _id = row?.id || ids.value[0]
+  const res = await getTask(_id);
+  Object.assign(form.value, res.data);
+  
+  // 如果有项目ID,加载项目信息到下拉列表
+  if (form.value.projectId) {
+    try {
+      const projectRes = await getProjectByName('');
+      projectList.value = projectRes.data || [];
+    } catch (error) {
+      console.error('加载项目信息失败:', error);
+    }
+  }
+  
+  dialog.visible = true;
+  dialog.title = "修改文档质控任务";
+}
+
+/** 提交按钮 */
+const submitForm = () => {
+  taskFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (form.value.id) {
+        await updateTask(form.value).finally(() =>  buttonLoading.value = false);
+      } else {
+        await addTask(form.value).finally(() =>  buttonLoading.value = false);
+      }
+      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>

+ 19 - 2
src/views/document/folder/document/DocumentList.vue

@@ -235,8 +235,7 @@
       v-model="auditDialog.visible"
       v-model="auditDialog.visible"
       :document="auditDialog.document"
       :document="auditDialog.document"
       :title="t('document.document.dialog.auditDocument')"
       :title="t('document.document.dialog.auditDocument')"
-      :audit-api="auditDocument"
-      @success="getDocumentList"
+      @submit="handleAuditSubmit"
     />
     />
   </div>
   </div>
   <el-empty v-else :description="t('document.document.empty.description')"> </el-empty>
   <el-empty v-else :description="t('document.document.empty.description')"> </el-empty>
@@ -428,6 +427,24 @@ const handleAuditClick = (row: DocumentVO) => {
   auditDialog.visible = true;
   auditDialog.visible = true;
 };
 };
 
 
+// 处理审核提交
+const handleAuditSubmit = async (auditData: any) => {
+  try {
+    console.log('[页面] 收到审核数据:', auditData);
+    
+    // 调用审核接口
+    await auditDocument(auditData);
+    
+    ElMessage.success(t('document.document.message.auditSuccess'));
+    
+    // 刷新列表
+    getDocumentList();
+  } catch (error) {
+    console.error('[页面] 审核失败:', error);
+    ElMessage.error(t('document.document.message.auditFailed'));
+  }
+};
+
 // 下载文档
 // 下载文档
 const handleDownload = async (row: DocumentVO) => {
 const handleDownload = async (row: DocumentVO) => {
   if (!row.ossId) {
   if (!row.ossId) {

+ 51 - 96
src/views/home/taskCenter/audit/index.vue

@@ -74,17 +74,25 @@
           </template>
           </template>
         </el-table-column>
         </el-table-column>
         <!-- 操作列 -->
         <!-- 操作列 -->
-        <el-table-column label="操作" width="150" align="center" fixed="right">
+        <el-table-column label="操作" width="200" align="center" fixed="right" class-name="small-padding fixed-width">
           <template #default="scope">
           <template #default="scope">
-            <el-button v-if="scope.row.status === 1" type="primary" link @click="handleAudit(scope.row)" title="审核">
-              <el-icon>
-                <Check />
-              </el-icon>
+            <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>
-            <el-button v-if="scope.row.ossId" type="primary" link @click="handleDownload(scope.row)" title="下载">
-              <el-icon>
-                <Download />
-              </el-icon>
+            <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>
             </el-button>
           </template>
           </template>
         </el-table-column>
         </el-table-column>
@@ -96,25 +104,12 @@
     </el-card>
     </el-card>
 
 
     <!-- 审核文档对话框 -->
     <!-- 审核文档对话框 -->
-    <el-dialog v-model="auditDialog.visible" :title="auditDialog.title" width="500px" append-to-body>
-      <el-form ref="auditFormRef" :model="auditForm" :rules="auditRules" label-width="120px">
-        <el-form-item label="审批结果" prop="result">
-          <el-radio-group v-model="auditForm.result">
-            <el-radio :label="3">通过</el-radio>
-            <el-radio :label="2">拒绝</el-radio>
-          </el-radio-group>
-        </el-form-item>
-        <el-form-item label="拒绝原因" prop="rejectReason" v-if="auditForm.result === 2">
-          <el-input v-model="auditForm.rejectReason" type="textarea" :rows="3" placeholder="请输入拒绝原因" />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <div class="dialog-footer">
-          <el-button :loading="auditButtonLoading" type="primary" @click="submitAuditForm"> 确认审批 </el-button>
-          <el-button @click="cancelAudit">取消</el-button>
-        </div>
-      </template>
-    </el-dialog>
+    <DocumentAuditDialog
+      v-model="auditDialog.visible"
+      :document="auditDialog.document"
+      :title="auditDialog.title"
+      @submit="handleAuditSubmit"
+    />
   </div>
   </div>
 </template>
 </template>
 
 
@@ -128,15 +123,12 @@ import { downloadDocumentFile } from '@/api/document/document';
 import { Edit, Download, Check } from '@element-plus/icons-vue';
 import { Edit, Download, Check } from '@element-plus/icons-vue';
 import DictTag from '@/components/DictTag/index.vue';
 import DictTag from '@/components/DictTag/index.vue';
 import DocumentStatusTag from '@/components/DocumentStatusTag/index.vue';
 import DocumentStatusTag from '@/components/DocumentStatusTag/index.vue';
+import DocumentAuditDialog from '@/components/DocumentAuditDialog/index.vue';
 
 
 const { proxy } = getCurrentInstance() as any;
 const { proxy } = getCurrentInstance() as any;
 const { plan_document_type } = proxy.useDict('plan_document_type');
 const { plan_document_type } = proxy.useDict('plan_document_type');
 
 
 const loading = ref(false);
 const loading = ref(false);
-const auditButtonLoading = ref(false);
-
-// 当前任务
-const currentTask = ref<AuditTaskVO | null>(null);
 
 
 // 查询参数
 // 查询参数
 const queryParams = reactive({
 const queryParams = reactive({
@@ -154,25 +146,10 @@ const total = ref(0);
 // 审核对话框
 // 审核对话框
 const auditDialog = reactive({
 const auditDialog = reactive({
   visible: false,
   visible: false,
-  title: ''
+  title: '审核文档',
+  document: null as AuditTaskVO | null
 });
 });
 
 
-// 审核表单ref
-const auditFormRef = ref<FormInstance>();
-
-// 审核表单数据
-const auditForm = ref({
-  documentId: 0,
-  result: 3, // 默认通过
-  rejectReason: ''
-});
-
-// 审核表单验证规则
-const auditRules = {
-  result: [{ required: true, message: '请选择审批结果', trigger: 'change' }],
-  rejectReason: [{ required: true, message: '请输入拒绝原因', trigger: 'blur' }]
-};
-
 /**
 /**
  * 解析时间格式
  * 解析时间格式
  * @param time 时间字符串
  * @param time 时间字符串
@@ -244,59 +221,37 @@ const resetQuery = () => {
  * 处理审核
  * 处理审核
  */
  */
 const handleAudit = (row: AuditTaskVO) => {
 const handleAudit = (row: AuditTaskVO) => {
-  currentTask.value = row;
-  auditForm.value = {
-    documentId: row.id,
-    result: 3, // 默认通过
-    rejectReason: ''
+  // 将 AuditTaskVO 转换为 Document 接口格式
+  auditDialog.document = {
+    id: row.id,
+    name: row.name,
+    fileName: row.fileName || row.name, // 优先使用 fileName(带后缀),否则使用 name
+    ossId: row.ossId,
+    url: row.ossUrl || '' // 使用 ossUrl 作为 url
   };
   };
   auditDialog.visible = true;
   auditDialog.visible = true;
-  auditDialog.title = '审核文档';
-  // 重置表单验证
-  nextTick(() => {
-    auditFormRef.value?.clearValidate();
-  });
-};
-
-/**
- * 取消审核
- */
-const cancelAudit = () => {
-  auditDialog.visible = false;
-  auditForm.value = {
-    documentId: 0,
-    result: 3,
-    rejectReason: ''
-  };
-  currentTask.value = null;
+  
+  console.log('[任务中心] 打开审核对话框,文档信息:', auditDialog.document);
 };
 };
 
 
 /**
 /**
- * 提交审核表单
+ * 处理审核提交
  */
  */
-const submitAuditForm = () => {
-  auditFormRef.value?.validate(async (valid: boolean) => {
-    if (valid && currentTask.value) {
-      auditButtonLoading.value = true;
-      try {
-        const auditData: AuditTaskAuditForm = {
-          documentId: auditForm.value.documentId,
-          result: auditForm.value.result,
-          rejectReason: auditForm.value.rejectReason
-        };
-        await auditDocument(auditData);
-        ElMessage.success('审批成功');
-        auditDialog.visible = false;
-        // 刷新任务列表
-        await getTaskList();
-      } catch (error) {
-        console.error('审批失败:', error);
-        ElMessage.error('审批失败');
-      } finally {
-        auditButtonLoading.value = false;
-      }
-    }
-  });
+const handleAuditSubmit = async (auditData: any) => {
+  try {
+    console.log('[任务中心] 收到审核数据:', auditData);
+    
+    // 调用审核接口
+    await auditDocument(auditData);
+    
+    ElMessage.success('审批成功');
+    
+    // 刷新任务列表
+    await getTaskList();
+  } catch (error) {
+    console.error('[任务中心] 审批失败:', error);
+    ElMessage.error('审批失败');
+  }
 };
 };
 
 
 /**
 /**

+ 12 - 11
src/views/home/taskCenter/filing/index.vue

@@ -79,23 +79,24 @@
           </template>
           </template>
         </el-table-column>
         </el-table-column>
         <!-- 操作列 -->
         <!-- 操作列 -->
-        <el-table-column label="操作" width="150" align="center" fixed="right">
+        <el-table-column label="操作" width="200" align="center" fixed="right" class-name="small-padding fixed-width">
           <template #default="scope">
           <template #default="scope">
             <el-button 
             <el-button 
               v-if="scope.row.status === 3" 
               v-if="scope.row.status === 3" 
               v-hasPermi="['taskCenter:filing:filing']"
               v-hasPermi="['taskCenter:filing:filing']"
               type="primary" 
               type="primary" 
-              link 
-              @click="handleArchive(scope.row)" 
-              title="归档">
-              <el-icon>
-                <FolderAdd />
-              </el-icon>
+              icon="FolderAdd" 
+              style="padding: 0 5px; font-size: 10px; height: 24px;"
+              @click="handleArchive(scope.row)">
+              归档
             </el-button>
             </el-button>
-            <el-button v-if="scope.row.ossId" type="primary" link @click="handleDownload(scope.row)" title="下载">
-              <el-icon>
-                <Download />
-              </el-icon>
+            <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>
             </el-button>
           </template>
           </template>
         </el-table-column>
         </el-table-column>

+ 20 - 18
src/views/home/taskCenter/submission/index.vue

@@ -97,30 +97,32 @@
           </template>
           </template>
         </el-table-column>
         </el-table-column>
         <!-- 操作列 -->
         <!-- 操作列 -->
-        <el-table-column label="操作" width="200" align="center" fixed="right">
+        <el-table-column label="操作" width="280" align="center" fixed="right" class-name="small-padding fixed-width">
           <template #default="scope">
           <template #default="scope">
-            <el-button v-if="scope.row.status === 0 || scope.row.status === 2" type="primary" link
-              @click="handleTaskSubmit(scope.row)" title="递交">
-              <el-icon>
-                <Upload />
-              </el-icon>
+            <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>
-            <el-button v-hasPermi="['taskCenter:submission:logAudit']" type="primary" link
-              @click="handleViewAuditLog(scope.row)" title="查看审核记录">
-              <el-icon>
-                <DocumentCopy />
-              </el-icon>
+            <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>
             <el-button 
             <el-button 
               v-if="scope.row.sendFlag && !scope.row.sendStatus" 
               v-if="scope.row.sendFlag && !scope.row.sendStatus" 
               v-hasPermi="['taskCenter:submission:send']"
               v-hasPermi="['taskCenter:submission:send']"
-              type="primary" 
-              link
-              @click="handleSend(scope.row)" 
-              title="寄送">
-              <el-icon>
-                <Promotion />
-              </el-icon>
+              type="success" 
+              icon="Promotion"
+              style="padding: 0 5px; font-size: 10px; height: 24px;"
+              @click="handleSend(scope.row)">
+              寄送
             </el-button>
             </el-button>
           </template>
           </template>
         </el-table-column>
         </el-table-column>

+ 0 - 7
src/views/qualityControl/index.vue

@@ -1,7 +0,0 @@
-<script setup lang="ts"></script>
-
-<template>
-  <div>文件质控</div>
-</template>
-
-<style scoped lang="scss"></style>