Codex Assistant
昨天 58d9f460b2f8c34430285115e2557d18333c5cab
web/src/views/review-detail.vue
@@ -168,13 +168,17 @@
    <!-- 文件预览对话框 -->
    <el-dialog v-model="previewVisible" title="文件预览" width="80%" center>
      <div class="preview-content">
        <iframe
          v-if="previewUrl"
          :src="previewUrl"
          style="width: 100%; height: 500px; border: none;"
        ></iframe>
        <!-- 图片预览 -->
        <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="无法预览此文件类型" />
          <el-empty description="无法预览此文件类型,请下载查看" />
        </div>
      </div>
    </el-dialog>
@@ -204,6 +208,8 @@
const ratingComment = ref('')
const previewVisible = ref(false)
const previewUrl = ref('')
const previewType = ref('') // image | video | pdf | docx | unknown
const docxContainer = ref(null)
// 权限验证相关
const currentJudge = ref(null)
@@ -446,17 +452,84 @@
  }
}
// 文件预览
const previewFile = (file) => {
  // 根据文件类型决定预览方式
  const fileExtension = file.name.split('.').pop().toLowerCase()
  const previewableTypes = ['pdf', 'txt', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'mp4', 'avi', 'mov', 'wmv', 'flv', 'webm']
  if (previewableTypes.includes(fileExtension)) {
    // 在新窗口中打开预览
    window.open(file.url, '_blank')
  } else {
    ElMessage.warning('此文件类型不支持预览,请下载查看')
/**
 * 文件预览:按扩展名分发到图片/视频/PDF/DOCX
 */
const previewFile = async (file) => {
  const ext = (file.name?.split('.').pop() || '').toLowerCase()
  previewVisible.value = true
  previewUrl.value = ''
  previewType.value = ''
  // 统一取可用的完整 URL
  const url = file.url || file.fullUrl || file.full_path || file.path
  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) {
      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) => {
  const ensureScript = () => new Promise((resolve, reject) => {
    if (window.docx && window.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)
  })
  await ensureScript()
  const res = await fetch(url, { credentials: 'include' })
  if (!res.ok) throw new Error('获取 DOCX 失败: ' + res.status)
  const blob = await res.blob()
  if (docxContainer.value) {
    docxContainer.value.innerHTML = ''
    await window.docx.renderAsync(blob, docxContainer.value, null, { inWrapper: true })
  }
}
@@ -699,6 +772,13 @@
.preview-content {
  text-align: center;
}
.docx-preview {
  text-align: left;
  max-height: 70vh;
  overflow: auto;
  background: #fff;
  padding: 12px;
}
.preview-error {
  padding: 40px 0;