Huanyi 2 месяцев назад
Родитель
Сommit
c6797a632c

+ 1 - 1
.kiro/specs/frontend-implementation-plan.md

@@ -30,7 +30,7 @@
 
 **标准初始化方式**:
 ```typescript
-// src/components/DocumentAuditDialog/index.vue
+// src/components/DocumentWpsAuditDialog/index.vue
 
 const initWpsEditor = async () => {
   if (!wpsContainerRef.value || !props.document?.ossId) {

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

@@ -34,6 +34,11 @@ export interface DocumentVO {
    */
   actualDocumentName?: string;
 
+  /**
+   * 实际文档地址
+   */
+  actualDocumentUrl?: string;
+
   /**
    * 计划文件类型
    */

+ 431 - 1155
src/components/DocumentAuditDialog/index.vue

@@ -1,250 +1,154 @@
 <template>
-  <el-dialog v-model="dialogVisible" :title="title" width="1200px" top="5vh" append-to-body destroy-on-close class="audit-dialog">
+  <el-dialog v-model="visible" :title="title" width="1200px" @close="handleClose">
     <div class="audit-container">
-      <!-- 左侧:文档编辑区域 -->
+      <!-- 左侧:PDF 预览 -->
       <div class="preview-section">
-        <div class="preview-header">
-          <div class="file-info">
-            <el-icon class="file-icon"><Document /></el-icon>
-            <span class="file-name">{{ document?.fileName || t('document.document.documentAudit.untitledDocument') }}</span>
-          </div>
-          <div class="header-actions">
-            <el-tooltip :content="t('document.document.documentAudit.viewVersionsTooltip')" placement="bottom">
-              <el-button size="small" @click="handleViewVersions" :loading="loadingVersions">
-                <el-icon><Clock /></el-icon>
-                {{ t('document.document.documentAudit.viewVersions') }}
-              </el-button>
-            </el-tooltip>
-            <el-tooltip :content="t('document.document.documentAudit.cleanCommentsTooltip')" placement="bottom">
-              <el-button size="small" @click="handleCleanComments" :loading="cleaningComments">
-                <el-icon><Delete /></el-icon>
-                {{ t('document.document.documentAudit.cleanComments') }}
-              </el-button>
-            </el-tooltip>
-            <el-tooltip :content="t('document.document.documentAudit.copySignatureTooltip')" placement="bottom">
-              <el-button type="primary" size="small" @click="handleCopyAvatar" class="copy-avatar-btn" :loading="copyingAvatar">
-                <el-icon><Picture /></el-icon>
-                {{ t('document.document.documentAudit.copySignature') }}
-              </el-button>
-            </el-tooltip>
-          </div>
+        <div v-if="previewLoading" v-loading="true" class="preview-loading">
+          {{ t('document.document.auditForm.loading') }}
         </div>
-        <div class="preview-container">
-          <!-- WPS 编辑器容器 -->
-          <div
-            v-if="document?.ossId"
-            ref="wpsContainerRef"
-            class="wps-container"
-            @dragenter="handleDragEnter"
-            @dragleave="handleDragLeave"
-            @dragover.prevent="handleDragOver"
-            @drop.prevent="handleDrop"
-          >
-            <!-- 拖拽提示层 -->
-            <div v-if="isDragging" class="drag-overlay">
-              <div class="drag-hint">
-                <el-icon class="drag-icon"><Upload /></el-icon>
-                <p>{{ t('document.document.documentAudit.dropImageHint') }}</p>
-              </div>
-            </div>
-
-            <!-- 降级方案:iframe 预览 -->
-            <iframe v-if="wpsError && document?.url" :src="document.url" class="document-iframe" frameborder="0"></iframe>
-          </div>
-
-          <!-- 加载状态 -->
-          <div v-if="wpsLoading" class="loading-state">
-            <el-icon class="is-loading"><Loading /></el-icon>
-            <p>{{ t('document.document.documentAudit.loadingEditor') }}</p>
-          </div>
-
-          <!-- 错误状态 -->
-          <div v-if="wpsError && !document?.url" class="error-state">
-            <el-result icon="error" :title="t('document.document.documentAudit.loadFailed')" :sub-title="wpsError">
-              <template #extra>
-                <el-button type="primary" @click="() => initWpsEditor(false)">{{ t('document.document.documentAudit.reload') }}</el-button>
-              </template>
-            </el-result>
-          </div>
-
-          <!-- 空状态 -->
-          <el-empty v-if="!document?.ossId" :description="t('document.document.documentAudit.noDocument')" :image-size="100" />
+        <div v-else-if="previewError" class="preview-error">
+          <el-alert :title="previewError" type="error" :closable="false" />
+        </div>
+        <div v-else-if="pdfBlob" class="preview-content">
+          <VueOfficePdf :src="pdfBlob" @rendered="handleRendered" @error="handlePreviewError" />
+        </div>
+        <div v-else class="preview-empty">
+          <el-empty :description="t('document.document.auditForm.noDocument')" />
         </div>
       </div>
 
-      <!-- 右侧:审核表单区域 -->
-      <div class="audit-section">
-        <div class="section-header">
-          <el-icon><Edit /></el-icon>
-          <span>{{ t('document.document.documentAudit.auditInfo') }}</span>
+      <!-- 右侧:审核表单 -->
+      <div class="form-section">
+        <div class="form-header">
+          <div class="header-icon">
+            <el-icon :size="24"><DocumentChecked /></el-icon>
+          </div>
+          <div class="header-content">
+            <h3 class="header-title">{{ t('document.document.auditForm.title') }}</h3>
+            <p class="header-subtitle">{{ t('document.document.auditForm.subtitle') }}</p>
+          </div>
         </div>
 
-        <el-form ref="auditFormRef" :model="auditForm" :rules="auditRules" label-position="top" class="audit-form">
-          <!-- 文档信息卡片 -->
-          <el-card shadow="never" class="info-card">
-            <template #header>
-              <div class="card-header">
-                <el-icon><InfoFilled /></el-icon>
-                <span>{{ t('document.document.documentAudit.documentInfo') }}</span>
-              </div>
-            </template>
-            <div class="info-item">
-              <span class="info-label">{{ t('document.document.documentAudit.documentName') }}:</span>
-              <span class="info-value">{{ document?.fileName || '-' }}</span>
-            </div>
-            <div class="info-item">
-              <span class="info-label">{{ t('document.document.documentAudit.documentId') }}:</span>
-              <span class="info-value">{{ document?.id || '-' }}</span>
-            </div>
-          </el-card>
-
-          <!-- 审核结果 -->
-          <el-form-item :label="t('document.document.auditForm.result')" prop="result">
-            <el-radio-group v-model="auditForm.result" class="result-radio-group">
-              <el-radio label="3" border>
-                <div class="radio-content">
-                  <el-icon class="radio-icon success"><CircleCheck /></el-icon>
-                  <div class="radio-text">
-                    <div class="radio-title">{{ t('document.document.auditForm.pass') }}</div>
-                    <div class="radio-desc">{{ t('document.document.documentAudit.passDesc') }}</div>
+        <div class="form-body">
+          <el-form ref="formRef" :model="form" :rules="rules" label-position="top">
+            <el-form-item :label="t('document.document.auditForm.result')" prop="result" class="result-item">
+              <el-radio-group v-model="form.result" class="result-radio-group">
+                <el-radio :value="3" class="result-radio result-radio-pass" border>
+                  <div class="radio-content">
+                    <el-icon :size="20"><CircleCheck /></el-icon>
+                    <span>{{ t('document.document.auditForm.pass') }}</span>
                   </div>
-                </div>
-              </el-radio>
-              <el-radio label="2" border>
-                <div class="radio-content">
-                  <el-icon class="radio-icon danger"><CircleClose /></el-icon>
-                  <div class="radio-text">
-                    <div class="radio-title">{{ t('document.document.auditForm.reject') }}</div>
-                    <div class="radio-desc">{{ t('document.document.documentAudit.rejectDesc') }}</div>
+                </el-radio>
+                <el-radio :value="2" class="result-radio result-radio-reject" border>
+                  <div class="radio-content">
+                    <el-icon :size="20"><CircleClose /></el-icon>
+                    <span>{{ t('document.document.auditForm.reject') }}</span>
                   </div>
-                </div>
-              </el-radio>
-            </el-radio-group>
-          </el-form-item>
-
-          <!-- 驳回原因 -->
-          <el-form-item v-if="auditForm.result === '2'" :label="t('document.document.auditForm.reason')" prop="reason">
-            <el-input
-              v-model="auditForm.reason"
-              type="textarea"
-              :rows="6"
-              :placeholder="t('document.document.auditForm.reasonPlaceholder')"
-              maxlength="500"
-              show-word-limit
-            />
-          </el-form-item>
-
-          <!-- 审核提示 -->
-          <el-alert v-if="auditForm.result === '3'" :title="t('document.document.documentAudit.passAlert')" type="success" :closable="false" show-icon />
-          <el-alert v-if="auditForm.result === '2'" :title="t('document.document.documentAudit.rejectAlert')" type="warning" :closable="false" show-icon />
-        </el-form>
-      </div>
-    </div>
+                </el-radio>
+              </el-radio-group>
+            </el-form-item>
+
+            <transition name="el-fade-in">
+              <el-form-item
+                v-if="form.result === 2"
+                :label="t('document.document.auditForm.reason')"
+                prop="rejectReason"
+                class="reason-item"
+              >
+                <el-input
+                  v-model="form.rejectReason"
+                  type="textarea"
+                  :rows="6"
+                  :placeholder="t('document.document.auditForm.reasonPlaceholder')"
+                  maxlength="500"
+                  show-word-limit
+                  class="reason-textarea"
+                />
+              </el-form-item>
+            </transition>
+          </el-form>
+        </div>
 
-    <template #footer>
-      <div class="dialog-footer">
-        <el-button @click="handleCancel" size="large">
-          <el-icon><Close /></el-icon>
-          {{ t('document.document.button.cancel') }}
-        </el-button>
-        <el-button type="primary" @click="submitForm" :loading="loading" size="large">
-          <el-icon><Check /></el-icon>
-          {{ t('document.document.button.submit') }}
-        </el-button>
+        <div class="form-footer">
+          <el-button size="large" @click="handleClose" class="footer-btn">
+            <el-icon><Close /></el-icon>
+            <span>{{ t('document.document.button.cancel') }}</span>
+          </el-button>
+          <el-button
+            type="primary"
+            size="large"
+            :loading="loading"
+            @click="handleSubmit"
+            class="footer-btn footer-btn-primary"
+          >
+            <el-icon v-if="!loading"><Check /></el-icon>
+            <span>{{ t('document.document.button.confirm') }}</span>
+          </el-button>
+        </div>
       </div>
-    </template>
-  </el-dialog>
-
-  <!-- 历史版本对话框 -->
-  <el-dialog v-model="showVersionDialog" :title="t('document.document.documentAudit.historyVersions')" width="800px" append-to-body destroy-on-close">
-    <el-table :data="versionList" v-loading="loadingVersions" stripe>
-      <el-table-column prop="version" :label="t('document.document.documentAudit.versionNumber')" width="150" align="center" />
-      <el-table-column prop="createTime" :label="t('document.document.documentAudit.createTime')" min-width="180" align="center" />
-      <el-table-column prop="updateTime" :label="t('document.document.documentAudit.updateTime')" min-width="180" align="center" />
-      <el-table-column :label="t('document.document.documentAudit.action')" width="120" align="center" fixed="right">
-        <template #default="{ row }">
-          <el-button type="primary" size="small" @click="handleSelectVersion(row.version)"> {{ t('document.document.documentAudit.select') }} </el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-
-    <template #footer>
-      <el-button @click="showVersionDialog = false">{{ t('document.document.button.cancel') }}</el-button>
-    </template>
+    </div>
   </el-dialog>
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, watch, nextTick, onBeforeUnmount } from 'vue';
+import { ref, reactive, computed, nextTick, watch } from 'vue';
 import { useI18n } from 'vue-i18n';
+import { DocumentChecked, CircleCheck, CircleClose, Check, Close } from '@element-plus/icons-vue';
 import type { FormInstance } from 'element-plus';
-import { ElMessage, ElMessageBox } from 'element-plus';
-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 { cleanDocumentComments, getFileVersionList, getFinalFile, initWpsDocument, cancelWpsDocument, type FileVersion } from '@/api/wps/save';
-
-interface Document {
-  id: number | string;
-  name?: string;
-  ossId?: number | string;
-  fileName?: string;
-  url?: string;
+import VueOfficePdf from '@vue-office/pdf';
+import axios from 'axios';
+import { globalHeaders } from '@/utils/request';
+
+const baseURL = import.meta.env.VITE_APP_BASE_API;
+
+interface DocumentInfo {
+  id: string | number;
+  actualDocument?: string | number;
+  [key: string]: any;
 }
 
-interface AuditData {
-  documentId: number | string;
+interface AuditFormData {
+  documentId: string | number;
   result: number;
   rejectReason?: string;
-  ossId?: number | string; // 最终的 ossId
 }
 
 interface Props {
   modelValue: boolean;
-  document?: Document | null;
+  document: DocumentInfo | null;
   title?: string;
 }
 
-interface Emits {
-  (e: 'update:modelValue', value: boolean): void;
-  (e: 'submit', data: AuditData): void; // 提交审核数据
-}
+const props = withDefaults(defineProps<Props>(), {
+  title: ''
+});
 
-const props = defineProps<Props>();
-const emit = defineEmits<Emits>();
+const emit = defineEmits<{
+  'update:modelValue': [value: boolean];
+  submit: [data: AuditFormData];
+}>();
 
 const { t } = useI18n();
-const userStore = useUserStore();
-
-const dialogVisible = ref(false);
+const formRef = ref<FormInstance>();
 const loading = ref(false);
-const auditFormRef = ref<FormInstance>();
-
-// WPS 编辑器相关
-const wpsContainerRef = ref<HTMLDivElement>();
-const wpsLoading = ref(false);
-const wpsError = ref('');
-const isDragging = 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 dragCounter = 0; // 用于跟踪拖拽进入/离开次数
-
-// WPS 配置
-const WPS_APP_ID = 'SX20260105YMMIXV';
-
-// 审核表单数据
-const auditForm = ref({
-  id: 0 as number | string,
-  result: '3',
-  reason: ''
+const previewLoading = ref(false);
+const previewError = ref('');
+const pdfBlob = ref<ArrayBuffer | null>(null);
+
+const visible = computed({
+  get: () => props.modelValue,
+  set: (val) => emit('update:modelValue', val)
+});
+
+const title = computed(() => props.title || t('document.document.dialog.auditDocument'));
+
+const form = ref<AuditFormData>({
+  documentId: 0,
+  result: 3,
+  rejectReason: ''
 });
 
-// 审核表单验证规则
-const auditRules = reactive({
+const rules = reactive({
   result: [
     {
       required: true,
@@ -252,7 +156,7 @@ const auditRules = reactive({
       trigger: 'change'
     }
   ],
-  reason: [
+  rejectReason: [
     {
       required: true,
       message: t('document.document.auditRule.reasonRequired'),
@@ -261,1023 +165,395 @@ const auditRules = reactive({
   ]
 });
 
-// 获取文件类型
-const getFileType = (fileName: string) => {
-  const name = fileName.toLowerCase();
-  if (name.endsWith('.docx') || name.endsWith('.doc')) return 'w';
-  if (name.endsWith('.xlsx') || name.endsWith('.xls')) return 's';
-  if (name.endsWith('.pptx') || name.endsWith('.ppt')) return 'p';
-  if (name.endsWith('.pdf')) return 'f';
-  return 'w';
-};
-
-// 初始化 WPS 编辑器
-// shouldCallInitApi: 是否需要调用后端初始化接口(只在 dialog 首次打开时为 true)
-const initWpsEditor = async (shouldCallInitApi = false) => {
-  if (!wpsContainerRef.value || !props.document?.ossId) {
-    return;
-  }
-
-  try {
-    wpsLoading.value = true;
-    wpsError.value = '';
-
-    // 检查 WPS SDK
-    if (!(window as any).WebOfficeSDK) {
-      wpsError.value = 'WPS SDK 未加载';
-      wpsLoading.value = false;
-      return;
-    }
+// 监听对话框打开和 document 变化,初始化表单和加载预览
+watch(
+  [() => props.modelValue, () => props.document],
+  ([isVisible, newDoc]) => {
+    // 只有在对话框打开且有文档时才加载
+    if (isVisible && newDoc) {
+      form.value = {
+        documentId: newDoc.id as number,
+        result: 3,
+        rejectReason: ''
+      };
+      nextTick(() => {
+        formRef.value?.clearValidate();
+      });
 
-    const WebOfficeSDK = (window as any).WebOfficeSDK;
-
-    // 获取文件类型
-    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);
+      // 加载 PDF 预览
+      loadPdfPreview(newDoc);
+    } else if (!isVisible) {
+      // 对话框关闭时清理状态
+      pdfBlob.value = null;
+      previewError.value = '';
+      previewLoading.value = false;
     }
+  },
+  { immediate: true }
+);
 
-    // 使用 ossId + 当前版本号组成 fileId
-    const fileId = `${props.document.ossId}_${currentVersion.value}`;
-
-    console.log('[WPS] 初始化配置:', {
-      appId: WPS_APP_ID,
-      officeType: officeType,
-      fileId: fileId,
-      ossId: props.document.ossId,
-      version: currentVersion.value,
-      fileName: props.document.fileName,
-      fileUrl: props.document.url
-    });
-
-    // 标准初始化配置(按官方文档)
-    const config = {
-      // 必需参数
-      appId: WPS_APP_ID,
-      officeType: officeType,
-      fileId: fileId,
-
-      // 可选参数
-      mount: wpsContainerRef.value,
-      // 指定当前用户ID为编辑者ID
-      userId: String(userStore.userId)
-    };
-
-    // 初始化 WPS 编辑器
-    wpsInstance = WebOfficeSDK.init(config);
-
-    wpsLoading.value = false;
-    console.log('[WPS] 编辑器初始化成功');
-  } catch (err: any) {
-    console.error('[WPS] 初始化失败:', err);
-    wpsError.value = err.message || '初始化失败';
-    wpsLoading.value = false;
-  }
-};
+// 加载 PDF 预览
+const loadPdfPreview = async (doc: DocumentInfo) => {
+  previewLoading.value = true;
+  previewError.value = '';
+  pdfBlob.value = null;
 
-// 销毁 WPS 编辑器
-const destroyWpsEditor = () => {
-  if (wpsInstance) {
-    try {
-      if (wpsInstance.destroy) {
-        wpsInstance.destroy();
-      }
-      wpsInstance = null;
-      console.log('[WPS] 编辑器已销毁');
-    } catch (err) {
-      console.error('[WPS] 销毁编辑器失败:', err);
-    }
-  }
-};
-
-// 保存文档
-const saveWpsDocument = async () => {
-  if (!wpsInstance) {
-    return null;
+  if (!doc.actualDocument) {
+    previewError.value = t('document.document.auditForm.documentNotExist');
+    previewLoading.value = false;
+    return;
   }
 
   try {
-    console.log('[WPS] 开始保存文档');
-    const result = await wpsInstance.save();
-    console.log('[WPS] 保存成功:', result);
-    return result;
-  } catch (err: any) {
-    console.error('[WPS] 保存失败:', err);
-    throw err;
-  }
-};
+    // 使用统一的文件下载接口获取 PDF 文件
+    const response = await axios({
+      method: 'post',
+      url: `${baseURL}/document/document/download/${doc.actualDocument}`,
+      responseType: 'arraybuffer',
+      headers: globalHeaders()
+    });
 
-// 拖拽进入
-const handleDragEnter = (e: DragEvent) => {
-  dragCounter++;
-  if (dragCounter === 1) {
-    isDragging.value = true;
+    pdfBlob.value = response.data;
+    previewLoading.value = false;
+  } catch (error) {
+    console.error('下载PDF文件失败:', error);
+    previewError.value = t('document.document.auditForm.loadFailed');
+    previewLoading.value = false;
   }
 };
 
-// 拖拽离开
-const handleDragLeave = (e: DragEvent) => {
-  dragCounter--;
-  if (dragCounter === 0) {
-    isDragging.value = false;
-  }
+// PDF 渲染完成
+const handleRendered = () => {
+  previewLoading.value = false;
 };
 
-// 拖拽悬停
-const handleDragOver = (e: DragEvent) => {
-  e.preventDefault();
-  e.stopPropagation();
+// PDF 预览错误
+const handlePreviewError = (error: any) => {
+  previewLoading.value = false;
+  previewError.value = t('document.document.auditForm.previewError');
+  console.error('PDF预览错误:', error);
 };
 
-// 处理拖放
-const handleDrop = async (e: DragEvent) => {
-  e.preventDefault();
-  e.stopPropagation();
-
-  isDragging.value = false;
-  dragCounter = 0;
-
-  if (!wpsInstance) {
-    ElMessage.warning(t('document.document.documentAudit.wpsNotInitialized'));
-    return;
-  }
-
-  // 处理图片拖放
-  const files = e.dataTransfer?.files;
-  if (!files || files.length === 0) {
-    return;
-  }
-
-  // 只处理第一个文件
-  const file = files[0];
-
-  // 检查是否是图片
-  if (!file.type.startsWith('image/')) {
-    ElMessage.warning(t('document.document.documentAudit.onlyImageSupported'));
-    return;
-  }
-
-  try {
-    console.log('[WPS] 开始插入图片:', file.name);
-
-    // 读取图片为 base64
-    const reader = new FileReader();
-    reader.onload = async (event) => {
-      const base64 = event.target?.result as string;
-
-      try {
-        // 获取 WPS Application 对象
-        const app = await wpsInstance.Application;
-
-        if (!app) {
-          ElMessage.error('无法获取 WPS Application 对象');
-          return;
-        }
-
-        // 根据文件类型插入图片
-        const officeType = getFileType(props.document?.fileName || '');
-
-        if (officeType === 'w') {
-          // Word 文档:插入图片到光标位置
-          const selection = await app.ActiveDocument.Application.Selection;
-          await selection.InlineShapes.AddPicture(base64);
-          ElMessage.success(t('document.document.documentAudit.imageInserted'));
-        } else if (officeType === 's') {
-          // Excel 表格:插入图片到当前单元格
-          const activeSheet = await app.ActiveSheet;
-          const activeCell = await app.ActiveCell;
-          const row = await activeCell.Row;
-          const col = await activeCell.Column;
-          await activeSheet.Shapes.AddPicture(base64, false, true, col * 64, row * 20, 200, 150);
-          ElMessage.success(t('document.document.documentAudit.imageInserted'));
-        } else if (officeType === 'p') {
-          // PowerPoint:插入图片到当前幻灯片
-          const activeSlide = await app.ActivePresentation.Slides.Item(await app.ActiveWindow.Selection.SlideRange.SlideIndex);
-          await activeSlide.Shapes.AddPicture(base64, false, true, 100, 100, 200, 150);
-          ElMessage.success(t('document.document.documentAudit.imageInserted'));
-        } else {
-          ElMessage.warning(t('document.document.documentAudit.currentTypeNotSupported'));
-        }
-
-        console.log('[WPS] 图片插入成功');
-      } catch (err: any) {
-        console.error('[WPS] 插入图片失败:', err);
-        ElMessage.error(t('document.document.documentAudit.insertImageFailed') + ': ' + err.message);
-      }
-    };
-
-    reader.onerror = () => {
-      ElMessage.error(t('document.document.documentAudit.readImageFailed'));
-    };
-
-    reader.readAsDataURL(file);
-  } catch (err: any) {
-    console.error('[WPS] 处理拖放失败:', err);
-    ElMessage.error(t('document.document.documentAudit.dropHandleFailed'));
-  }
+const handleClose = () => {
+  visible.value = false;
+  formRef.value?.resetFields();
+  form.value = {
+    documentId: 0,
+    result: 3,
+    rejectReason: ''
+  };
+  pdfBlob.value = null;
+  previewError.value = '';
 };
 
-// 复制头像到剪贴板
-const handleCopyAvatar = async () => {
-  try {
-    // 先让用户选择审核结果
-    let reviewResult: string;
+const handleSubmit = async () => {
+  if (!formRef.value) return;
 
+  // 如果选择驳回,需要验证驳回理由
+  if (form.value.result === 2) {
     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;
-    console.log('[签名] 开始生成审核信息图片,审核结果:', reviewResult);
-
-    // 获取当前用户昵称
-    const reviewerName = userStore.nickname || userStore.name || t('document.document.copySignature.unknown');
-
-    // 获取当前时间
-    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 });
-
-    // 审核结果文本
-    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;
+      await formRef.value.validate();
+    } catch (error) {
       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);
-
-    // 审核结果靠右显示
-    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(t('document.document.documentAudit.documentInfoIncomplete'));
-    return;
   }
 
+  loading.value = true;
   try {
-    await ElMessageBox.confirm(t('document.document.documentAudit.cleanCommentsConfirm'), t('document.document.documentAudit.cleanCommentsTitle'), {
-      confirmButtonText: t('document.document.message.confirmButton'),
-      cancelButtonText: t('document.document.message.cancelButton'),
-      type: 'warning'
-    });
-
-    cleaningComments.value = true;
-    console.log('[清空批注] 开始清空文档批注,文档ID:', props.document.id, 'ossId:', props.document.ossId, '当前版本:', currentVersion.value);
-
-    // 调用后端接口清空批注,获取新版本号
-    const res = await cleanDocumentComments(props.document.ossId);
-    const newVersion = res.data; // 后端返回的新版本号
-    currentVersion.value = newVersion;
-
-    ElMessage.success(t('document.document.documentAudit.cleanCommentsSuccess'));
-    console.log('[清空批注] 批注清空成功,新版本号:', newVersion);
-
-    // 销毁当前 WPS 编辑器
-    destroyWpsEditor();
-
-    // 等待 DOM 更新
-    await nextTick();
-
-    // 使用新版本号重新初始化 WPS 编辑器(不调用后端初始化接口)
-    await initWpsEditor(false);
-
-    console.log('[清空批注] WPS 编辑器已使用新版本重新初始化,fileId:', `${props.document.ossId}_${currentVersion.value}`);
-  } catch (err: any) {
-    if (err === 'cancel') {
-      console.log('[清空批注] 用户取消操作');
-      return;
-    }
-
-    console.error('[清空批注] 清空失败:', err);
-    ElMessage.error(t('document.document.documentAudit.cleanCommentsFailed') + ': ' + (err.message || t('document.document.copySignature.unknownError')));
-  } finally {
-    cleaningComments.value = false;
-  }
-};
-
-// 查看历史版本
-const handleViewVersions = async () => {
-  if (!props.document?.ossId) {
-    ElMessage.warning(t('document.document.documentAudit.documentInfoIncomplete'));
-    return;
-  }
-
-  try {
-    loadingVersions.value = true;
-    showVersionDialog.value = true;
-
-    console.log('[历史版本] 获取历史版本列表,ossId:', props.document.ossId);
-
-    const res = await getFileVersionList(props.document.ossId);
-    versionList.value = res.data || [];
+    const submitData: AuditFormData = {
+      documentId: form.value.documentId,
+      result: form.value.result,
+      rejectReason: form.value.result === 2 ? form.value.rejectReason : undefined
+    };
 
-    console.log('[历史版本] 获取成功,版本数量:', versionList.value.length);
-  } catch (err: any) {
-    console.error('[历史版本] 获取失败:', err);
-    ElMessage.error(t('document.document.documentAudit.getVersionsFailed') + ': ' + (err.message || t('document.document.copySignature.unknownError')));
-    showVersionDialog.value = false;
+    // 触发提交事件,由父组件处理实际的审核请求
+    emit('submit', submitData);
   } finally {
-    loadingVersions.value = false;
-  }
-};
-
-// 选择历史版本
-const handleSelectVersion = async (version: number) => {
-  if (!props.document?.ossId) {
-    ElMessage.warning(t('document.document.documentAudit.documentInfoIncomplete'));
-    return;
-  }
-
-  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(t('document.document.documentAudit.switchVersionSuccess', { version }));
-    console.log('[历史版本] WPS 编辑器已切换到版本:', version, 'fileId:', `${props.document.ossId}_${version}`);
-  } catch (err: any) {
-    console.error('[历史版本] 切换版本失败:', err);
-    ElMessage.error(t('document.document.documentAudit.switchVersionFailed') + ': ' + (err.message || t('document.document.copySignature.unknownError')));
-  }
-};
-
-// 监听 modelValue 变化
-watch(
-  () => props.modelValue,
-  (val) => {
-    dialogVisible.value = val;
-    if (val && props.document) {
-      // 重置版本号为 1
-      currentVersion.value = 1;
-
-      auditForm.value = {
-        id: props.document.id,
-        result: '3',
-        reason: ''
-      };
-      nextTick(() => {
-        auditFormRef.value?.clearValidate();
-        // 自动初始化 WPS 编辑器,使用版本号 1(调用后端初始化接口)
-        if (props.document?.ossId) {
-          console.log('[WPS] 对话框打开,初始化编辑器,版本号:', currentVersion.value);
-          initWpsEditor(true);
-        }
-      });
-    }
+    loading.value = false;
   }
-);
-
-// 监听dialogVisible变化
-watch(dialogVisible, async (val) => {
-  emit('update:modelValue', 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 = {
-      id: 0,
-      result: '3',
-      reason: ''
-    };
-    // 关闭对话框时重置版本号
-    currentVersion.value = 1;
-    destroyWpsEditor();
-  }
-});
-
-// 取消操作
-const handleCancel = () => {
-  dialogVisible.value = false;
 };
 
-// 提交表单
-const submitForm = () => {
-  auditFormRef.value?.validate(async (valid: boolean) => {
-    if (valid) {
-      loading.value = true;
-      try {
-        // 如果使用了 WPS 编辑器,先保存文档
-        let savedFileInfo = null;
-        if (wpsInstance) {
-          try {
-            savedFileInfo = await saveWpsDocument();
-            if (savedFileInfo) {
-              console.log('[审核提交] 文档保存成功:', savedFileInfo);
-              ElMessage.success(t('document.document.documentAudit.documentSaved'));
-            }
-          } catch (err) {
-            console.error('[审核提交] 保存文档失败:', err);
-            ElMessage.warning(t('document.document.documentAudit.documentSaveFailed'));
-          }
-        }
-
-        // 获取当前 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(t('document.document.documentAudit.getFinalFileFailed'));
-        }
-
-        // 构建审核数据
-        const auditData: AuditData = {
-          documentId: auditForm.value.id,
-          result: parseInt(auditForm.value.result),
-          rejectReason: auditForm.value.reason,
-          ossId: finalOssId // 使用最终的 ossId
-        };
-
-        console.log('[审核提交] 提交审核数据到父组件:', auditData);
-
-        // 关闭对话框
-        dialogVisible.value = false;
-
-        // 通过 emit 将审核数据传递给父组件
-        emit('submit', auditData);
-      } catch (error) {
-        console.error('[审核提交] 处理失败:', error);
-        ElMessage.error(t('document.document.documentAudit.processFailed') + ': ' + (error as any).message || t('document.document.copySignature.unknownError'));
-      } finally {
-        loading.value = false;
-      }
-    }
-  });
+// 暴露方法供父组件调用
+const closeDialog = () => {
+  handleClose();
 };
 
-// 组件卸载前清理
-onBeforeUnmount(() => {
-  destroyWpsEditor();
+defineExpose({
+  closeDialog
 });
 </script>
 
 <style scoped lang="scss">
-.audit-dialog {
-  :deep(.el-dialog__header) {
-    padding: 20px 24px;
-    border-bottom: 1px solid #e4e7ed;
-    margin: 0;
-
-    .el-dialog__title {
-      font-size: 18px;
-      font-weight: 600;
-      color: #303133;
-    }
-  }
-
-  :deep(.el-dialog__body) {
-    padding: 0;
-    height: calc(80vh - 140px);
-    overflow: hidden;
-  }
-
-  :deep(.el-dialog__footer) {
-    padding: 16px 24px;
-    border-top: 1px solid #e4e7ed;
-  }
-}
-
 .audit-container {
   display: flex;
-  height: 100%;
-  gap: 1px;
-  background: #e4e7ed;
-}
-
-.preview-section {
-  flex: 1;
-  display: flex;
-  flex-direction: column;
-  background: #fff;
-  min-width: 0;
-  position: relative;
-}
-
-.preview-header {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding: 16px 20px;
-  background: #fff;
-  border-bottom: 1px solid #e4e7ed;
-  flex-shrink: 0;
-
-  .file-info {
-    display: flex;
-    align-items: center;
-    gap: 12px;
-
-    .file-icon {
-      font-size: 24px;
-      color: #409eff;
-    }
+  gap: 20px;
+  height: 70vh;
+  min-height: 500px;
 
-    .file-name {
-      font-size: 16px;
-      font-weight: 500;
-      color: #303133;
-      max-width: 600px;
-      overflow: hidden;
-      text-overflow: ellipsis;
-      white-space: nowrap;
+  .preview-section {
+    flex: 1;
+    border: 1px solid #dcdfe6;
+    border-radius: 8px;
+    overflow: hidden;
+    background-color: #f5f7fa;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+
+    .preview-loading,
+    .preview-error,
+    .preview-empty {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      height: 100%;
     }
-  }
 
-  .header-actions {
-    display: flex;
-    gap: 8px;
+    .preview-content {
+      height: 100%;
+      overflow: auto;
+      background-color: #fff;
 
-    .copy-avatar-btn {
-      .el-icon {
-        margin-right: 4px;
+      :deep(.vue-office-pdf) {
+        height: 100%;
       }
     }
   }
-}
-
-.preview-container {
-  position: absolute;
-  top: 68px;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  overflow: hidden;
-
-  .wps-container {
-    width: 100%;
-    height: 100%;
-    position: relative;
-  }
-
-  .document-iframe {
-    width: 100%;
-    height: 100%;
-    border: none;
-  }
 
-  .drag-overlay {
-    position: absolute;
-    top: 0;
-    left: 0;
-    right: 0;
-    bottom: 0;
-    background: rgba(64, 158, 255, 0.1);
-    border: 2px dashed #409eff;
+  .form-section {
+    width: 420px;
     display: flex;
-    align-items: center;
-    justify-content: center;
-    z-index: 9999;
-    pointer-events: none;
-
-    .drag-hint {
-      text-align: center;
-
-      .drag-icon {
-        font-size: 64px;
-        color: #409eff;
-        margin-bottom: 16px;
-      }
+    flex-direction: column;
+    background: linear-gradient(to bottom, #ffffff 0%, #fafbfc 100%);
+    border-radius: 8px;
+    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
+    overflow: hidden;
 
-      p {
-        font-size: 18px;
-        font-weight: 500;
-        color: #409eff;
+    .form-header {
+      display: flex;
+      align-items: center;
+      gap: 16px;
+      padding: 24px;
+      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+      color: #fff;
+
+      .header-icon {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 48px;
+        height: 48px;
+        background: rgba(255, 255, 255, 0.2);
+        border-radius: 12px;
+        backdrop-filter: blur(10px);
       }
-    }
-  }
-
-  .loading-state {
-    position: absolute;
-    top: 50%;
-    left: 50%;
-    transform: translate(-50%, -50%);
-    text-align: center;
-
-    .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%;
-  }
-}
 
-.audit-section {
-  width: 400px;
-  display: flex;
-  flex-direction: column;
-  background: #fff;
-  flex-shrink: 0;
-}
+      .header-content {
+        flex: 1;
 
-.section-header {
-  display: flex;
-  align-items: center;
-  gap: 8px;
-  padding: 16px 20px;
-  background: #f5f7fa;
-  border-bottom: 1px solid #e4e7ed;
-  font-size: 15px;
-  font-weight: 500;
-  color: #303133;
-
-  .el-icon {
-    font-size: 18px;
-    color: #409eff;
-  }
-}
-
-.audit-form {
-  flex: 1;
-  padding: 20px;
-  overflow-y: auto;
-
-  &::-webkit-scrollbar {
-    width: 6px;
-  }
-
-  &::-webkit-scrollbar-thumb {
-    background: #dcdfe6;
-    border-radius: 3px;
+        .header-title {
+          margin: 0;
+          font-size: 18px;
+          font-weight: 600;
+          line-height: 1.4;
+        }
 
-    &:hover {
-      background: #c0c4cc;
+        .header-subtitle {
+          margin: 4px 0 0;
+          font-size: 13px;
+          opacity: 0.9;
+          line-height: 1.4;
+        }
+      }
     }
-  }
-}
 
-.info-card {
-  margin-bottom: 20px;
+    .form-body {
+      flex: 1;
+      padding: 24px;
+      overflow-y: auto;
 
-  :deep(.el-card__header) {
-    padding: 12px 16px;
-    background: #f5f7fa;
-  }
+      .el-form {
+        height: 100%;
 
-  .card-header {
-    display: flex;
-    align-items: center;
-    gap: 8px;
-    font-size: 14px;
-    font-weight: 500;
-    color: #303133;
-
-    .el-icon {
-      font-size: 16px;
-      color: #409eff;
-    }
-  }
+        .result-item {
+          margin-bottom: 24px;
 
-  :deep(.el-card__body) {
-    padding: 16px;
-  }
-}
-
-.info-item {
-  display: flex;
-  align-items: center;
-  padding: 8px 0;
-  font-size: 14px;
-
-  &:not(:last-child) {
-    border-bottom: 1px solid #f0f2f5;
-  }
+          :deep(.el-form-item__label) {
+            font-weight: 600;
+            font-size: 14px;
+            color: #303133;
+            margin-bottom: 12px;
+          }
 
-  .info-label {
-    color: #909399;
-    min-width: 80px;
-  }
+          .result-radio-group {
+            display: flex;
+            flex-direction: column;
+            gap: 12px;
+            width: 100%;
+
+            .result-radio {
+              width: 100%;
+              margin: 0;
+              padding: 0;
+              border-radius: 8px;
+              transition: all 0.3s ease;
+
+              :deep(.el-radio__input) {
+                display: none;
+              }
+
+              :deep(.el-radio__label) {
+                width: 100%;
+                padding: 16px 20px;
+                display: flex;
+                align-items: center;
+                justify-content: center;
+              }
+
+              .radio-content {
+                display: flex;
+                align-items: center;
+                gap: 10px;
+                font-size: 15px;
+                font-weight: 500;
+              }
+
+              &.result-radio-pass {
+                border-color: #e8f4ea;
+                background-color: #f0f9f1;
+
+                &:hover {
+                  border-color: #67c23a;
+                  background-color: #e8f4ea;
+                  transform: translateY(-2px);
+                  box-shadow: 0 4px 12px rgba(103, 194, 58, 0.2);
+                }
+
+                &.is-checked {
+                  border-color: #67c23a;
+                  background: linear-gradient(135deg, #67c23a 0%, #85ce61 100%);
+                  box-shadow: 0 4px 12px rgba(103, 194, 58, 0.3);
+
+                  :deep(.el-radio__label) {
+                    color: #fff;
+                  }
+
+                  .radio-content {
+                    color: #fff;
+                  }
+                }
+              }
+
+              &.result-radio-reject {
+                border-color: #fde8e8;
+                background-color: #fef0f0;
+
+                &:hover {
+                  border-color: #f56c6c;
+                  background-color: #fde8e8;
+                  transform: translateY(-2px);
+                  box-shadow: 0 4px 12px rgba(245, 108, 108, 0.2);
+                }
+
+                &.is-checked {
+                  border-color: #f56c6c;
+                  background: linear-gradient(135deg, #f56c6c 0%, #f78989 100%);
+                  box-shadow: 0 4px 12px rgba(245, 108, 108, 0.3);
+
+                  :deep(.el-radio__label) {
+                    color: #fff;
+                  }
+
+                  .radio-content {
+                    color: #fff;
+                  }
+                }
+              }
+            }
+          }
+        }
 
-  .info-value {
-    color: #303133;
-    flex: 1;
-    word-break: break-all;
-  }
-}
+        .reason-item {
+          :deep(.el-form-item__label) {
+            font-weight: 600;
+            font-size: 14px;
+            color: #303133;
+            margin-bottom: 12px;
+          }
 
-.result-radio-group {
-  width: 100%;
-  display: flex;
-  flex-direction: column;
-  gap: 12px;
-
-  :deep(.el-radio) {
-    margin: 0;
-    padding: 0;
-    height: auto;
-
-    &.is-bordered {
-      padding: 16px;
-      border-radius: 8px;
-      border: 2px solid #dcdfe6;
-      transition: all 0.3s;
-
-      &:hover {
-        border-color: #409eff;
-      }
+          .reason-textarea {
+            :deep(.el-textarea__inner) {
+              border-radius: 8px;
+              border: 2px solid #e4e7ed;
+              padding: 12px;
+              font-size: 14px;
+              line-height: 1.6;
+              transition: all 0.3s ease;
+
+              &:focus {
+                border-color: #667eea;
+                box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
+              }
+            }
 
-      &.is-checked {
-        border-color: #409eff;
-        background: #ecf5ff;
+            :deep(.el-input__count) {
+              background-color: transparent;
+              font-size: 12px;
+            }
+          }
+        }
       }
     }
 
-    .el-radio__input {
-      display: none;
-    }
-
-    .el-radio__label {
-      padding: 0;
-    }
-  }
-}
-
-.radio-content {
-  display: flex;
-  align-items: center;
-  gap: 12px;
-  width: 100%;
-
-  .radio-icon {
-    font-size: 32px;
-    flex-shrink: 0;
-
-    &.success {
-      color: #67c23a;
-    }
+    .form-footer {
+      display: flex;
+      gap: 12px;
+      padding: 20px 24px;
+      background-color: #fff;
+      border-top: 1px solid #e4e7ed;
+      box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.04);
+
+      .footer-btn {
+        flex: 1;
+        height: 44px;
+        font-size: 15px;
+        font-weight: 500;
+        border-radius: 8px;
+        transition: all 0.3s ease;
 
-    &.danger {
-      color: #f56c6c;
-    }
-  }
+        .el-icon {
+          margin-right: 6px;
+        }
 
-  .radio-text {
-    flex: 1;
+        &:hover {
+          transform: translateY(-2px);
+          box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+        }
 
-    .radio-title {
-      font-size: 15px;
-      font-weight: 500;
-      color: #303133;
-      margin-bottom: 4px;
-    }
+        &.footer-btn-primary {
+          background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+          border: none;
 
-    .radio-desc {
-      font-size: 13px;
-      color: #909399;
+          &:hover {
+            background: linear-gradient(135deg, #5568d3 0%, #6a3f8f 100%);
+          }
+        }
+      }
     }
   }
 }
 
-:deep(.el-form-item) {
-  margin-bottom: 20px;
-
-  .el-form-item__label {
-    font-weight: 500;
-    color: #303133;
-    margin-bottom: 8px;
-  }
-}
-
-:deep(.el-alert) {
-  margin-top: 12px;
-  border-radius: 6px;
+// 过渡动画
+.el-fade-in-enter-active,
+.el-fade-in-leave-active {
+  transition: all 0.3s ease;
 }
 
-.dialog-footer {
-  display: flex;
-  justify-content: flex-end;
-  gap: 12px;
-
-  .el-button {
-    min-width: 100px;
-
-    .el-icon {
-      margin-right: 4px;
-    }
-  }
-}
-
-// 响应式设计
-@media (max-width: 1400px) {
-  .audit-dialog {
-    :deep(.el-dialog) {
-      width: 95% !important;
-    }
-  }
-
-  .audit-section {
-    width: 350px;
-  }
+.el-fade-in-enter-from {
+  opacity: 0;
+  transform: translateY(-10px);
 }
 
-@media (max-width: 1200px) {
-  .audit-container {
-    flex-direction: column;
-  }
-
-  .preview-section {
-    height: 50%;
-  }
-
-  .audit-section {
-    width: 100%;
-    height: 50%;
-  }
+.el-fade-in-leave-to {
+  opacity: 0;
+  transform: translateY(-10px);
 }
 </style>

+ 8 - 1
src/lang/modules/document/document/en_US.ts

@@ -198,7 +198,14 @@ export default {
     pass: 'Pass',
     reject: 'Reject',
     reason: 'Rejection Reason',
-    reasonPlaceholder: 'Please enter rejection reason'
+    reasonPlaceholder: 'Please enter rejection reason',
+    title: 'Document Audit',
+    subtitle: 'Please review the document carefully before making an audit decision',
+    loading: 'Loading...',
+    noDocument: 'No document preview available',
+    documentNotExist: 'Document does not exist',
+    loadFailed: 'Failed to load document, please try again',
+    previewError: 'Document preview failed, please check if the document format is correct'
   },
   // Audit Validation Rules
   auditRule: {

+ 8 - 1
src/lang/modules/document/document/zh_CN.ts

@@ -194,7 +194,14 @@ export default {
     pass: '通过',
     reject: '驳回',
     reason: '驳回理由',
-    reasonPlaceholder: '请输入驳回理由'
+    reasonPlaceholder: '请输入驳回理由',
+    title: '文档审核',
+    subtitle: '请仔细审阅文档内容后做出审核决定',
+    loading: '加载中...',
+    noDocument: '暂无文档预览',
+    documentNotExist: '文档不存在',
+    loadFailed: '文档加载失败,请重试',
+    previewError: '文档预览失败,请检查文档格式是否正确'
   },
   // 标识验证规则
   markRule: {

+ 10 - 1
src/lang/modules/qc/task/en_US.ts

@@ -151,7 +151,16 @@ export default {
         deadline: 'Deadline',
         deadlinePlaceholder: 'Please select deadline',
         cancel: 'Cancel',
-        confirm: 'Confirm'
+        confirm: 'Confirm',
+        documentInfo: 'Document Information',
+        documentInfoDesc: 'View detailed information of the document to be audited',
+        auditInfo: 'Audit Information',
+        auditInfoDesc: 'Please carefully review the document before making an audit decision',
+        loading: 'Loading...',
+        noDocument: 'No document preview available',
+        documentNotExist: 'Document does not exist',
+        loadFailed: 'Failed to load document, please try again',
+        previewError: 'Document preview failed, please check if the document format is correct'
     },
     // Audit Validation Rules
     auditRule: {

+ 10 - 1
src/lang/modules/qc/task/zh_CN.ts

@@ -151,7 +151,16 @@ export default {
         deadline: '截止日期',
         deadlinePlaceholder: '请选择截止日期',
         cancel: '取消',
-        confirm: '确认'
+        confirm: '确认',
+        documentInfo: '文档信息',
+        documentInfoDesc: '查看待审核文档的详细信息',
+        auditInfo: '审核信息',
+        auditInfoDesc: '请仔细审阅文档后做出审核决定',
+        loading: '加载中...',
+        noDocument: '暂无文档预览',
+        documentNotExist: '文档不存在',
+        loadFailed: '文档加载失败,请重试',
+        previewError: '文档预览失败,请检查文档格式是否正确'
     },
     // 审核验证规则
     auditRule: {

+ 16 - 3
src/views/document/folder/document/DocumentList.vue

@@ -260,7 +260,7 @@
     <SubmitDialog v-model="submitDialog.visible" :document="currentDocument" @success="handleDialogSuccess" />
 
     <!-- 审核文档对话框 -->
-    <AuditDialog v-model="auditDialog.visible" :document="currentDocument" @success="handleDialogSuccess" />
+    <DocumentAuditDialog v-model="auditDialog.visible" :document="currentDocument" @submit="handleAuditSubmit" />
 
     <!-- 指定文档对话框 -->
     <SpecifyDialog
@@ -284,7 +284,7 @@
 <script setup lang="ts">
 import { ref, reactive, getCurrentInstance, watch, toRefs } from 'vue';
 import { useI18n } from 'vue-i18n';
-import { listDocument, confirmSubmit, filingDocument, downloadDocumentFile, removeTempDocument } from '@/api/document/document';
+import { listDocument, confirmSubmit, filingDocument, downloadDocumentFile, removeTempDocument, auditDocument } from '@/api/document/document';
 import { DocumentQuery, DocumentVO } from '@/api/document/document/types';
 import { FolderListVO } from '@/api/document/folder/types';
 import { Select, Upload } from '@element-plus/icons-vue';
@@ -297,7 +297,7 @@ import { listDocumentAuditLog } from '@/api/document/document';
 import DocumentStatusTag from '@/components/DocumentStatusTag/index.vue';
 import MarkDialog from './components/MarkDialog.vue';
 import SubmitDialog from './components/SubmitDialog.vue';
-import AuditDialog from './components/AuditDialog.vue';
+import DocumentAuditDialog from '@/components/DocumentAuditDialog/index.vue';
 import SpecifyDialog from './components/SpecifyDialog.vue';
 
 interface Props {
@@ -418,6 +418,19 @@ const handleAuditClick = (row: DocumentVO) => {
   auditDialog.visible = true;
 };
 
+// 处理审核提交
+const handleAuditSubmit = async (data: any) => {
+  try {
+    await auditDocument(data);
+    ElMessage.success(t('document.document.message.auditSuccess'));
+    auditDialog.visible = false;
+    await getDocumentList();
+  } catch (error) {
+    console.error('审核失败:', error);
+    ElMessage.error(t('document.document.message.auditFailed'));
+  }
+};
+
 // 下载文档
 const handleDownload = async (row: DocumentVO) => {
   if (!row.actualDocument) {

+ 0 - 140
src/views/document/folder/document/components/AuditDialog.vue

@@ -1,140 +0,0 @@
-<template>
-  <el-dialog v-model="visible" :title="title" width="500px" @close="handleClose">
-    <el-form ref="formRef" :model="form" :rules="rules" label-width="110px">
-      <el-form-item :label="t('document.document.auditForm.result')" prop="result">
-        <el-radio-group v-model="form.result">
-          <el-radio :label="3">{{ t('document.document.auditForm.pass') }}</el-radio>
-          <el-radio :label="2">{{ t('document.document.auditForm.reject') }}</el-radio>
-        </el-radio-group>
-      </el-form-item>
-      <el-form-item v-if="form.result === 2" :label="t('document.document.auditForm.reason')" prop="rejectReason">
-        <el-input
-          v-model="form.rejectReason"
-          type="textarea"
-          :rows="4"
-          :placeholder="t('document.document.auditForm.reasonPlaceholder')"
-          maxlength="500"
-          show-word-limit
-        />
-      </el-form-item>
-    </el-form>
-    <template #footer>
-      <el-button @click="handleClose">{{ t('document.document.button.cancel') }}</el-button>
-      <el-button type="primary" :loading="loading" @click="handleSubmit">{{ t('document.document.button.confirm') }}</el-button>
-    </template>
-  </el-dialog>
-</template>
-
-<script setup lang="ts">
-import { ref, reactive, computed, nextTick, watch } from 'vue';
-import { useI18n } from 'vue-i18n';
-import { ElMessage } from 'element-plus';
-import type { FormInstance } from 'element-plus';
-import { auditDocument } from '@/api/document/document';
-import { DocumentVO } from '@/api/document/document/types';
-
-interface Props {
-  modelValue: boolean;
-  document: DocumentVO | null;
-}
-
-const props = defineProps<Props>();
-const emit = defineEmits<{
-  'update:modelValue': [value: boolean];
-  success: [];
-}>();
-
-const { t } = useI18n();
-const formRef = ref<FormInstance>();
-const loading = ref(false);
-
-const visible = computed({
-  get: () => props.modelValue,
-  set: (val) => emit('update:modelValue', val)
-});
-
-const title = computed(() => t('document.document.dialog.auditDocument'));
-
-const form = ref({
-  documentId: 0,
-  result: 3,
-  rejectReason: ''
-});
-
-const rules = reactive({
-  result: [
-    {
-      required: true,
-      message: t('document.document.auditRule.resultRequired'),
-      trigger: 'change'
-    }
-  ],
-  rejectReason: [
-    {
-      required: true,
-      message: t('document.document.auditRule.reasonRequired'),
-      trigger: 'blur'
-    }
-  ]
-});
-
-// 监听 document 变化,初始化表单
-watch(
-  () => props.document,
-  (newDoc) => {
-    if (newDoc) {
-      form.value = {
-        documentId: newDoc.id as number,
-        result: 3,
-        rejectReason: ''
-      };
-      nextTick(() => {
-        formRef.value?.clearValidate();
-      });
-    }
-  },
-  { immediate: true }
-);
-
-const handleClose = () => {
-  visible.value = false;
-  formRef.value?.resetFields();
-  form.value = {
-    documentId: 0,
-    result: 3,
-    rejectReason: ''
-  };
-};
-
-const handleSubmit = async () => {
-  if (!formRef.value) return;
-
-  // 如果选择驳回,需要验证驳回理由
-  if (form.value.result === 2) {
-    try {
-      await formRef.value.validate();
-    } catch (error) {
-      return;
-    }
-  }
-
-  loading.value = true;
-  try {
-    const submitData = {
-      documentId: form.value.documentId,
-      result: form.value.result,
-      rejectReason: form.value.result === 2 ? form.value.rejectReason : undefined
-    };
-
-    await auditDocument(submitData);
-    ElMessage.success(t('document.document.message.auditSuccess'));
-    handleClose();
-    emit('success');
-  } catch (error) {
-    console.error('审核失败:', error);
-    ElMessage.error(t('document.document.message.auditFailed'));
-  } finally {
-    loading.value = false;
-  }
-};
-</script>

+ 9 - 103
src/views/home/taskCenter/audit/index.vue

@@ -130,47 +130,21 @@
     </el-card>
 
     <!-- 审核文档对话框 -->
-    <el-dialog v-model="auditDialog.visible" :title="auditDialog.title" width="500px" @close="handleAuditDialogClose">
-      <el-form ref="auditFormRef" :model="auditForm" :rules="auditRules" label-width="110px">
-        <el-form-item :label="t('home.taskCenter.audit.dialog.result')" prop="result">
-          <el-radio-group v-model="auditForm.result">
-            <el-radio :label="3">{{ t('home.taskCenter.audit.dialog.pass') }}</el-radio>
-            <el-radio :label="2">{{ t('home.taskCenter.audit.dialog.reject') }}</el-radio>
-          </el-radio-group>
-        </el-form-item>
-        <el-form-item v-if="auditForm.result === 2" :label="t('home.taskCenter.audit.dialog.rejectReason')" prop="rejectReason">
-          <el-input
-            v-model="auditForm.rejectReason"
-            type="textarea"
-            :rows="4"
-            :placeholder="t('home.taskCenter.audit.dialog.rejectReasonPlaceholder')"
-            maxlength="500"
-            show-word-limit
-          />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <el-button @click="auditDialog.visible = false">{{ t('home.taskCenter.audit.dialog.cancel') }}</el-button>
-        <el-button type="primary" :loading="auditDialog.loading" @click="handleAuditConfirm">{{
-          t('home.taskCenter.audit.dialog.confirm')
-        }}</el-button>
-      </template>
-    </el-dialog>
+    <DocumentAuditDialog v-model="auditDialog.visible" :document="auditDialog.document" @submit="handleAuditSubmit" />
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, onMounted, nextTick, getCurrentInstance } from 'vue';
-import { ElMessage, ElMessageBox } from 'element-plus';
+import { ref, reactive, onMounted, getCurrentInstance } from 'vue';
+import { ElMessage } from 'element-plus';
 import { useI18n } from 'vue-i18n';
-import type { FormInstance } from 'element-plus';
 import { listAuditTasks, auditDocument } from '@/api/home/taskCenter/audit';
-import { AuditTaskVO, AuditTaskAuditForm } from '@/api/home/taskCenter/audit/types';
+import { AuditTaskVO } from '@/api/home/taskCenter/audit/types';
 import { downloadDocumentFile } from '@/api/document/document';
 import { formatDocumentName } from '@/utils/ruoyi';
-import { Edit, Download, Check } from '@element-plus/icons-vue';
 import DictTag from '@/components/DictTag/index.vue';
 import DocumentStatusTag from '@/components/DocumentStatusTag/index.vue';
+import DocumentAuditDialog from '@/components/DocumentAuditDialog/index.vue';
 
 const { t } = useI18n();
 const { proxy } = getCurrentInstance() as any;
@@ -194,37 +168,7 @@ const total = ref(0);
 // 审核对话框
 const auditDialog = reactive({
   visible: false,
-  title: t('home.taskCenter.audit.dialog.title'),
-  document: null as AuditTaskVO | null,
-  loading: false
-});
-
-// 审核表单数据
-const auditForm = ref({
-  documentId: 0,
-  result: 3, // 默认通过
-  rejectReason: ''
-});
-
-// 审核表单引用
-const auditFormRef = ref<FormInstance>();
-
-// 审核表单验证规则
-const auditRules = reactive({
-  result: [
-    {
-      required: true,
-      message: t('home.taskCenter.audit.rule.resultRequired'),
-      trigger: 'change'
-    }
-  ],
-  rejectReason: [
-    {
-      required: true,
-      message: t('home.taskCenter.audit.rule.rejectReasonRequired'),
-      trigger: 'blur'
-    }
-  ]
+  document: null as AuditTaskVO | null
 });
 
 /**
@@ -299,59 +243,21 @@ const resetQuery = () => {
  */
 const handleAudit = (row: AuditTaskVO) => {
   auditDialog.document = row;
-  auditForm.value = {
-    documentId: row.id,
-    result: 3, // 默认通过
-    rejectReason: ''
-  };
   auditDialog.visible = true;
-  nextTick(() => {
-    auditFormRef.value?.clearValidate();
-  });
-};
-
-/**
- * 审核对话框关闭事件
- */
-const handleAuditDialogClose = () => {
-  auditFormRef.value?.resetFields();
-  auditForm.value = {
-    documentId: 0,
-    result: 3,
-    rejectReason: ''
-  };
 };
 
 /**
- * 处理审核确认
+ * 处理审核提交
  */
-const handleAuditConfirm = async () => {
-  // 如果选择驳回,需要验证驳回理由
-  if (auditForm.value.result === 2) {
-    try {
-      await auditFormRef.value?.validate();
-    } catch (error) {
-      return;
-    }
-  }
-
-  auditDialog.loading = true;
+const handleAuditSubmit = async (data: any) => {
   try {
-    const submitData: AuditTaskAuditForm = {
-      documentId: auditForm.value.documentId,
-      result: auditForm.value.result,
-      rejectReason: auditForm.value.result === 2 ? auditForm.value.rejectReason : ''
-    };
-
-    await auditDocument(submitData);
+    await auditDocument(data);
     ElMessage.success(t('home.taskCenter.audit.message.auditSuccess'));
     auditDialog.visible = false;
     await getTaskList();
   } catch (error) {
     console.error(t('home.taskCenter.audit.message.auditFailed'), error);
     ElMessage.error(t('home.taskCenter.audit.message.auditFailed'));
-  } finally {
-    auditDialog.loading = false;
   }
 };
 

+ 51 - 165
src/views/home/taskCenter/qc/index.vue

@@ -152,68 +152,13 @@
     </el-dialog>
 
     <!-- 审核弹窗 -->
-    <el-dialog v-model="auditDialog.visible" :title="t('home.taskCenter.qc.auditDialog.title')" width="500px" append-to-body>
-      <el-form ref="auditFormRef" :model="auditForm" :rules="auditRules" label-width="100px">
-        <el-form-item :label="t('home.taskCenter.qc.auditDialog.result')" prop="result">
-          <el-radio-group v-model="auditForm.result">
-            <el-radio :label="1">{{ t('home.taskCenter.qc.auditDialog.pass') }}</el-radio>
-            <el-radio :label="2">{{ t('home.taskCenter.qc.auditDialog.reject') }}</el-radio>
-          </el-radio-group>
-        </el-form-item>
-
-        <template v-if="auditForm.result === 2">
-          <el-form-item :label="t('home.taskCenter.qc.auditDialog.rejectionType')" prop="rejectionType">
-            <el-select
-              v-model="auditForm.rejectionType"
-              :placeholder="t('home.taskCenter.qc.auditDialog.rejectionTypePlaceholder')"
-              style="width: 100%"
-            >
-              <el-option v-for="dict in qc_question_type" :key="dict.value" :label="parseI18nName(dict.label)" :value="dict.value" />
-            </el-select>
-          </el-form-item>
-
-          <el-form-item :label="t('home.taskCenter.qc.auditDialog.opinion')" prop="opinion">
-            <el-input v-model="auditForm.opinion" type="textarea" :rows="3" :placeholder="t('home.taskCenter.qc.auditDialog.opinionPlaceholder')" />
-          </el-form-item>
-
-          <el-form-item :label="t('home.taskCenter.qc.auditDialog.designatedDealer')" prop="designatedDealer">
-            <el-select
-              v-model="auditForm.designatedDealer"
-              filterable
-              remote
-              reserve-keyword
-              :placeholder="t('home.taskCenter.qc.auditDialog.designatedDealerPlaceholder')"
-              :remote-method="searchDealers"
-              :loading="dealerSearchLoading"
-              style="width: 100%"
-            >
-              <el-option
-                v-for="dealer in dealerOptions"
-                :key="dealer.id"
-                :label="`${dealer.name} / ${dealer.dept} --- ${dealer.phoneNumber}`"
-                :value="dealer.id"
-              />
-            </el-select>
-          </el-form-item>
-
-          <el-form-item :label="t('home.taskCenter.qc.auditDialog.deadline')" prop="deadline">
-            <el-date-picker
-              v-model="auditForm.deadline"
-              type="date"
-              value-format="YYYY-MM-DD"
-              :placeholder="t('home.taskCenter.qc.auditDialog.deadlinePlaceholder')"
-              style="width: 100%"
-            />
-          </el-form-item>
-        </template>
-      </el-form>
-      <template #footer>
-        <div class="dialog-footer">
-          <el-button @click="cancelAudit">{{ t('home.taskCenter.qc.auditDialog.cancel') }}</el-button>
-          <el-button type="primary" :loading="auditLoading" @click="submitAudit">{{ t('home.taskCenter.qc.auditDialog.confirm') }}</el-button>
-        </div>
-      </template>
-    </el-dialog>
+    <QcAuditDialog
+      v-model="auditDialog.visible"
+      :task-detail="currentTask"
+      :qc-question-type="qc_question_type"
+      :dealer-search-method="handleDealerSearch"
+      @submit="handleAuditSubmit"
+    />
 
     <!-- 审核记录弹窗 -->
     <QcLogDialog v-model="logDialogVisible" :detail-id="currentLogDetailId" />
@@ -233,6 +178,7 @@ import { parseI18nName } from '@/utils/i18n';
 import { formatDocumentName } from '@/utils/ruoyi';
 import fileUpload from '@/components/FileUpload/index.vue';
 import QcLogDialog from '@/components/QcLogDialog/index.vue';
+import QcAuditDialog from '@/components/QcAuditDialog/index.vue';
 
 const { t } = useI18n();
 const { proxy } = getCurrentInstance() as any;
@@ -240,7 +186,6 @@ const { qc_question_type } = toRefs<any>(proxy?.useDict('qc_question_type'));
 
 const loading = ref(false);
 const submitButtonLoading = ref(false);
-const auditLoading = ref(false);
 
 // 当前任务
 const currentTask = ref<QcTaskVO | null>(null);
@@ -279,40 +224,10 @@ const submitRules = {
 };
 
 // 审核相关
-const auditFormRef = ref<FormInstance>();
 const auditDialog = reactive({
   visible: false
 });
 
-// 审核表单初始数据
-const initAuditForm: QcTaskAuditForm = {
-  taskId: 0,
-  detailId: 0,
-  result: 1,
-  rejectionType: undefined,
-  opinion: undefined,
-  designatedDealer: undefined,
-  deadline: undefined
-};
-
-const auditForm = ref<QcTaskAuditForm>({ ...initAuditForm });
-
-// 审核表单验证规则
-const auditRules = computed(() => ({
-  result: [{ required: true, message: t('home.taskCenter.qc.rule.resultRequired'), trigger: 'change' }],
-  rejectionType:
-    auditForm.value.result === 2 ? [{ required: true, message: t('home.taskCenter.qc.rule.rejectionTypeRequired'), trigger: 'change' }] : [],
-  opinion: auditForm.value.result === 2 ? [{ required: true, message: t('home.taskCenter.qc.rule.opinionRequired'), trigger: 'blur' }] : [],
-  designatedDealer:
-    auditForm.value.result === 2 ? [{ required: true, message: t('home.taskCenter.qc.rule.designatedDealerRequired'), trigger: 'change' }] : [],
-  deadline: auditForm.value.result === 2 ? [{ required: true, message: t('home.taskCenter.qc.rule.deadlineRequired'), trigger: 'change' }] : []
-}));
-
-// 处理人搜索相关
-const dealerSearchLoading = ref(false);
-const dealerOptions = ref<MemberNotInCenterVO[]>([]);
-let dealerSearchTimer: NodeJS.Timeout | null = null;
-
 // 审核记录相关
 const logDialogVisible = ref(false);
 const currentLogDetailId = ref<number | undefined>(undefined);
@@ -411,86 +326,57 @@ const submitSubmitForm = () => {
 
 /** 审核任务 */
 const handleAudit = (row: QcTaskVO) => {
-  currentTask.value = row;
-  auditForm.value = {
-    ...initAuditForm,
-    taskId: row.taskId || 0,
-    detailId: row.id
+  currentTask.value = {
+    ...row,
+    actualDocument: row.actualDocument
   };
-  dealerOptions.value = [];
   auditDialog.visible = true;
 };
 
-/** 搜索处理人 */
-const searchDealers = async (query: string) => {
-  if (!query || query.trim() === '') {
-    dealerOptions.value = [];
-    return;
-  }
-
-  if (dealerSearchTimer) {
-    clearTimeout(dealerSearchTimer);
+/** 搜索处理人 - 提供给 QcAuditDialog 组件 */
+const handleDealerSearch = async (query: string): Promise<any[]> => {
+  try {
+    const queryParams: MemberNotInCenterQuery = {
+      pageNum: 1,
+      pageSize: 10,
+      projectId: currentTask.value?.projectId || 0,
+      folderId: 0,
+      name: query
+    };
+    const res = await queryMemberNotInCenter(queryParams);
+    return res.rows || [];
+  } catch (error) {
+    console.error(t('home.taskCenter.qc.message.searchDealerFailed'), error);
+    ElMessage.error(t('home.taskCenter.qc.message.searchDealerFailed'));
+    return [];
   }
-
-  dealerSearchTimer = setTimeout(async () => {
-    dealerSearchLoading.value = true;
-    try {
-      const queryParams: MemberNotInCenterQuery = {
-        pageNum: 1,
-        pageSize: 10,
-        projectId: currentTask.value?.projectId || 0,
-        folderId: 0,
-        name: query
-      };
-      const res = await queryMemberNotInCenter(queryParams);
-      dealerOptions.value = res.rows || [];
-    } catch (error) {
-      console.error(t('home.taskCenter.qc.message.searchDealerFailed'), error);
-      ElMessage.error(t('home.taskCenter.qc.message.searchDealerFailed'));
-    } finally {
-      dealerSearchLoading.value = false;
-    }
-  }, 300);
 };
 
-/** 取消审核 */
-const cancelAudit = () => {
-  auditDialog.visible = false;
-  auditFormRef.value?.resetFields();
-  currentTask.value = null;
-};
-
-/** 提交审核 */
-const submitAudit = () => {
-  auditFormRef.value?.validate(async (valid: boolean) => {
-    if (valid) {
-      auditLoading.value = true;
-      try {
-        const submitData: QcTaskAuditForm = {
-          taskId: auditForm.value.taskId,
-          detailId: auditForm.value.detailId,
-          result: auditForm.value.result
-        };
-
-        // 驳回时添加额外字段
-        if (auditForm.value.result === 2) {
-          submitData.rejectionType = auditForm.value.rejectionType;
-          submitData.opinion = auditForm.value.opinion;
-          submitData.designatedDealer = auditForm.value.designatedDealer;
-          submitData.deadline = auditForm.value.deadline;
-        }
-
-        await auditQcTask(submitData);
-        ElMessage.success(t('home.taskCenter.qc.message.auditSuccess'));
-        auditDialog.visible = false;
-        await getTaskList();
-      } catch (error) {
-        console.error(t('home.taskCenter.qc.message.auditFailed'), error);
-      } finally {
-        auditLoading.value = false;
-      }
+/** 处理审核提交 */
+const handleAuditSubmit = async (formData: any) => {
+  try {
+    const submitData: QcTaskAuditForm = {
+      taskId: currentTask.value?.taskId || 0,
+      detailId: currentTask.value?.id || 0,
+      result: formData.result
+    };
+
+    // 驳回时添加额外字段
+    if (formData.result === 2) {
+      submitData.rejectionType = formData.rejectionType;
+      submitData.opinion = formData.opinion;
+      submitData.designatedDealer = formData.designatedDealer;
+      submitData.deadline = formData.deadline;
     }
-  });
+
+    await auditQcTask(submitData);
+    ElMessage.success(t('home.taskCenter.qc.message.auditSuccess'));
+    auditDialog.visible = false;
+    await getTaskList();
+  } catch (error) {
+    console.error(t('home.taskCenter.qc.message.auditFailed'), error);
+    ElMessage.error(t('home.taskCenter.qc.message.auditFailed'));
+  }
 };
 
 /** 查看审核记录 */