From afeeed281e60466b576fbe74d339634cc5d07b82 Mon Sep 17 00:00:00 2001
From: Codex Assistant <codex@example.com>
Date: 星期三, 08 十月 2025 08:56:42 +0800
Subject: [PATCH] 修复评审功能和用户认证问题

---
 web/src/views/check-detail.vue |  154 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 153 insertions(+), 1 deletions(-)

diff --git a/web/src/views/check-detail.vue b/web/src/views/check-detail.vue
index 139047a..d60d400 100644
--- a/web/src/views/check-detail.vue
+++ b/web/src/views/check-detail.vue
@@ -115,6 +115,13 @@
               <el-button 
                 type="primary" 
                 size="small" 
+                @click="previewAttachment(attachment)"
+              >
+                棰勮
+              </el-button>
+              <el-button 
+                type="primary" 
+                size="small" 
                 @click="downloadAttachment(attachment)"
               >
                 涓嬭浇
@@ -130,7 +137,24 @@
           <div class="card-header">
             <span>瀹℃牳绠$悊</span>
           </div>
-        </template>
+          <!-- 闄勪欢棰勮瀵硅瘽妗� -->
+  <el-dialog v-model="previewVisible" title="鏂囦欢棰勮" width="80%" center>
+    <div class="preview-content">
+      <!-- 鍥剧墖棰勮 -->
+      <img v-if="previewType === 'image' && previewUrl" :src="previewUrl" style="max-width: 100%; max-height: 70vh; object-fit: contain;" />
+      <!-- 瑙嗛棰勮 -->
+      <video v-else-if="previewType === 'video' && previewUrl" :src="previewUrl" controls style="width: 100%; max-height: 70vh;"></video>
+      <!-- PDF 棰勮 -->
+      <iframe v-else-if="previewType === 'pdf' && previewUrl" :src="previewUrl" style="width: 100%; height: 70vh; border: none;"></iframe>
+      <!-- DOCX 棰勮 -->
+      <div v-else-if="previewType === 'docx'" ref="docxContainer" class="docx-preview"></div>
+      <!-- 鍏跺畠涓嶆敮鎸� -->
+      <div v-else class="preview-error">
+        <el-empty description="鏃犳硶棰勮姝ゆ枃浠剁被鍨嬶紝璇蜂笅杞芥煡鐪�" />
+      </div>
+    </div>
+  </el-dialog>
+</template>
         
         <div class="review-section">
           <div class="review-status">
@@ -223,6 +247,12 @@
 const playerData = ref<any>(null)
 const activityPlayerData = ref<any>(null)
 const attachments = ref<any[]>([])
+
+// 棰勮鐩稿叧
+const previewVisible = ref(false)
+const previewUrl = ref('')
+const previewType = ref<'' | 'image' | 'video' | 'pdf' | 'docx' | 'unknown'>('')
+const docxContainer = ref<HTMLElement | null>(null)
 
 // 瀹℃牳鐩稿叧鏁版嵁
 const feedbackText = ref('')
@@ -422,6 +452,113 @@
 const downloadAttachment = (attachment: any) => {
   // TODO: 瀹炵幇闄勪欢涓嬭浇鍔熻兘
   window.open(attachment.url, '_blank')
+}
+
+/**
+ * 棰勮闄勪欢锛氭寜鎵╁睍鍚嶅垎鍙� 鍥剧墖/瑙嗛/PDF/DOCX
+ */
+const previewAttachment = async (attachment: any) => {
+  const name: string = attachment.originalName || attachment.name || ''
+  const url: string = attachment.url
+  const ext = (name.split('.').pop() || '').toLowerCase()
+
+  previewVisible.value = true
+  previewUrl.value = ''
+  previewType.value = ''
+
+  const imageExts = ['jpg','jpeg','png','gif','bmp','webp']
+  const videoExts = ['mp4','webm','ogg','avi','mov','wmv','flv','mkv']
+
+  if (imageExts.includes(ext)) {
+    previewType.value = 'image'
+    previewUrl.value = url
+    return
+  }
+  if (videoExts.includes(ext)) {
+    previewType.value = 'video'
+    previewUrl.value = url
+    return
+  }
+  if (ext === 'pdf') {
+    previewType.value = 'pdf'
+    previewUrl.value = url
+    return
+  }
+  if (ext === 'docx') {
+    previewType.value = 'docx'
+    try {
+      await renderDocx(url)
+    } catch (e: any) {
+      console.error('DOCX 棰勮澶辫触:', e)
+      ElMessage.warning('DOCX 棰勮澶辫触锛屽缓璁笅杞芥煡鐪�')
+      previewType.value = 'unknown'
+    }
+    return
+  }
+  if (ext === 'doc') {
+    ElMessage.info('鏆備笉鏀寔 .doc 棰勮锛岃涓嬭浇鏌ョ湅')
+    previewType.value = 'unknown'
+    return
+  }
+
+  ElMessage.warning('姝ゆ枃浠剁被鍨嬩笉鏀寔棰勮锛岃涓嬭浇鏌ョ湅')
+  previewType.value = 'unknown'
+}
+
+/**
+ * 鍔ㄦ�佸姞杞� docx-preview 骞舵覆鏌� DOCX
+ */
+const renderDocx = async (url: string) => {
+  // 鍔ㄦ�佸姞杞� docx-preview
+  const ensureScript = () => new Promise((resolve, reject) => {
+    if ((window as any).docx && (window as any).docx.renderAsync) return resolve(true)
+    const existed = document.querySelector('script[data-docx-preview]')
+    if (existed) {
+      existed.addEventListener('load', () => resolve(true))
+      existed.addEventListener('error', reject)
+      return
+    }
+    const s = document.createElement('script')
+    s.src = 'https://unpkg.com/docx-preview/dist/docx-preview.min.js'
+    s.async = true
+    s.setAttribute('data-docx-preview', '1')
+    s.onload = () => resolve(true)
+    s.onerror = reject
+    document.head.appendChild(s)
+  })
+
+  // 瑙勮寖鍖� URL锛堟敮鎸佺浉瀵硅矾寰勶級
+  const buildUrl = (u: string) => {
+    if (!u) return u
+    if (u.startsWith('http://') || u.startsWith('https://')) return u
+    if (u.startsWith('/')) return location.origin + u
+    return u
+  }
+
+  // 鎼哄甫閴存潈澶磋闂檮浠讹紙寰堝鏂囦欢鏈嶅姟涓嶈蛋 cookie锛岃�屾槸 JWT Header锛�
+  const { getToken } = await import('@/utils/auth')
+  const token = getToken ? getToken() : ''
+  const requestUrl = buildUrl(url)
+
+  let res: Response
+  try {
+    res = await fetch(requestUrl, {
+      credentials: 'include',
+      headers: token ? { Authorization: `Bearer ${token}` } : undefined,
+      mode: 'cors'
+    } as RequestInit)
+  } catch (e) {
+    throw new Error('鑾峰彇 DOCX 澶辫触: 缃戠粶涓嶅彲杈炬垨琚嫤鎴�')
+  }
+  if (!res.ok) throw new Error('鑾峰彇 DOCX 澶辫触: ' + res.status)
+
+  const blob = await res.blob()
+
+  await ensureScript()
+  if (docxContainer.value) {
+    docxContainer.value.innerHTML = ''
+    await (window as any).docx.renderAsync(blob, docxContainer.value, null, { inWrapper: true })
+  }
 }
 
 // 瀹℃牳閫氳繃
@@ -770,4 +907,19 @@
     grid-template-columns: 1fr;
   }
 }
+  .preview-content {
+    text-align: center;
+  }
+
+  .preview-error {
+    padding: 40px 0;
+  }
+
+  .docx-preview {
+    text-align: left;
+    max-height: 70vh;
+    overflow: auto;
+    background: #fff;
+    padding: 12px;
+  }
 </style>
\ No newline at end of file

--
Gitblit v1.8.0