Codex Assistant
1 天以前 58d9f460b2f8c34430285115e2557d18333c5cab
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>