From 58d9f460b2f8c34430285115e2557d18333c5cab Mon Sep 17 00:00:00 2001
From: Codex Assistant <codex@example.com>
Date: 星期三, 08 十月 2025 14:16:55 +0800
Subject: [PATCH] feat: 修复Player实体phone字段数据冗余问题并优化小程序报名逻辑

---
 web/src/views/check-detail.vue |  173 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 162 insertions(+), 11 deletions(-)

diff --git a/web/src/views/check-detail.vue b/web/src/views/check-detail.vue
index 6843618..57d9776 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">
@@ -224,6 +248,12 @@
 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('')
 const approving = ref(false)
@@ -240,11 +270,7 @@
   try {
     loading.value = true
     const playerId = route.params.id as string
-    
-    // 杩欓噷搴旇璋冪敤API鑾峰彇鏁版嵁
-    // 鏆傛椂浣跨敤妯℃嫙鏁版嵁
     await loadPlayerData(playerId)
-    
   } catch (error) {
     console.error('鍔犺浇鏁版嵁澶辫触:', error)
     ElMessage.error('鍔犺浇鏁版嵁澶辫触')
@@ -282,7 +308,6 @@
       regionInfo {
         id
         name
-        fullPath
       }
       activityName
       projectName
@@ -296,6 +321,7 @@
         fileExt
         fileSize
         mediaType
+        thumbUrl
       }
     }
   }
@@ -307,7 +333,7 @@
 const loadPlayerData = async (playerId: string) => {
   try {
     const data = await graphqlRequest(ACTIVITY_PLAYER_DETAIL_QUERY, { id: playerId })
-    const detail = data.activityPlayerDetail
+    const detail = data?.data?.activityPlayerDetail || data?.activityPlayerDetail
     
     if (detail) {
       // 璁剧疆player鍩烘湰淇℃伅
@@ -345,9 +371,12 @@
       
       // 鍒濆鍖栧鏍告剰瑙�
       feedbackText.value = detail.feedback || ''
+    } else {
+      ElMessage.warning('鏈壘鍒板搴旂殑鍙傝禌浜哄憳璇︽儏淇℃伅')
     }
   } catch (error) {
     console.error('鍔犺浇鏁版嵁澶辫触:', error)
+    ElMessage.error(`鍔犺浇鏁版嵁澶辫触: ${error.message || error}`)
     throw error
   }
 }
@@ -382,7 +411,7 @@
 const getGenderText = (gender: number) => {
   const genderMap: Record<number, string> = {
     1: '鐢�',
-    2: '濂�'
+    0: '濂�'
   }
   return genderMap[gender] || '-'
 }
@@ -425,6 +454,113 @@
   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 })
+  }
+}
+
 // 瀹℃牳閫氳繃
 const handleApprove = async () => {
   try {
@@ -437,7 +573,7 @@
     approving.value = true
     const result = await approveActivityPlayer(activityPlayerData.value.id, feedbackText.value)
     
-    if (result.approveActivityPlayer) {
+    if (result.data.approveActivityPlayer) {
       ElMessage.success('瀹℃牳閫氳繃鎴愬姛')
       activityPlayerData.value.state = 1
       activityPlayerData.value.feedback = feedbackText.value
@@ -471,7 +607,7 @@
     rejecting.value = true
     const result = await rejectActivityPlayer(activityPlayerData.value.id, feedbackText.value)
     
-    if (result.rejectActivityPlayer) {
+    if (result.data.rejectActivityPlayer) {
       ElMessage.success('瀹℃牳椹冲洖鎴愬姛')
       activityPlayerData.value.state = 2
       activityPlayerData.value.feedback = feedbackText.value
@@ -499,7 +635,7 @@
     updating.value = true
     const result = await updatePlayerFeedback(activityPlayerData.value.id, feedbackText.value)
     
-    if (result.updatePlayerFeedback) {
+    if (result.data.updatePlayerFeedback) {
       ElMessage.success('瀹℃牳鎰忚鏇存柊鎴愬姛')
       activityPlayerData.value.feedback = feedbackText.value
     } else {
@@ -771,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