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/review-detail.vue | 183 ++++++++++++++++++++++++++++++++++++++------- 1 files changed, 155 insertions(+), 28 deletions(-) diff --git a/web/src/views/review-detail.vue b/web/src/views/review-detail.vue index e015e26..fd0a3be 100644 --- a/web/src/views/review-detail.vue +++ b/web/src/views/review-detail.vue @@ -14,12 +14,15 @@ <div class="project-section"> <!-- 椤圭洰鍩烘湰淇℃伅 --> <h4>椤圭洰淇℃伅</h4> - <el-descriptions :column="2" border> - <el-descriptions-item label="椤圭洰鍚嶇О"> - {{ projectDetail.projectName || '鏈~鍐�' }} + <el-descriptions :column="2" border class="project-info"> + <el-descriptions-item label="姣旇禌鍚嶇О" :span="2"> + {{ competitionName || '鏈~鍐�' }} </el-descriptions-item> - <el-descriptions-item label="姣旇禌鍚嶇О"> - {{ projectDetail.activityName }} + <el-descriptions-item label="姣旇禌闃舵" :span="2"> + {{ stageName || projectDetail.activityName || '鏈~鍐�' }} + </el-descriptions-item> + <el-descriptions-item label="鍙傝禌椤圭洰鍚嶇О" :span="2"> + {{ projectDetail.projectName || '鏈~鍐�' }} </el-descriptions-item> <el-descriptions-item label="椤圭洰鎻忚堪" :span="2"> <div class="description-content"> @@ -165,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> @@ -183,7 +190,7 @@ import { useRoute, useRouter } from 'vue-router' import { ElMessage, ElMessageBox } from 'element-plus' import { Document, UserFilled } from '@element-plus/icons-vue' -import { getProjectDetail, getRatingStats, submitRating, getCurrentJudgeRating } from '@/api/projectReview' +import { getProjectDetail, getRatingStats, submitRating, getCurrentJudgeRating, getActiveActivities } from '@/api/projectReview' import { userApi } from '@/api/user' import { getUserInfo } from '@/utils/auth' @@ -195,10 +202,14 @@ const submitting = ref(false) const projectDetail = ref(null) const ratingStats = ref({ ratingCount: 0, averageScore: 0 }) +const competitionName = ref('') +const stageName = ref('') const ratingItems = ref([]) 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) @@ -211,7 +222,25 @@ // 璁$畻灞炴�� const projectId = computed(() => route.params.id) -const stageId = computed(() => route.query.stageId) +const stageId = computed(() => route.query.stageId || (projectDetail.value ? projectDetail.value.stageId : null)) + +const loadStageMeta = async () => { + try { + if (!projectDetail.value || !projectDetail.value.stageId) return + const stages = await getActiveActivities() + const stage = (stages || []).find(s => String(s.id) === String(projectDetail.value.stageId)) + if (stage) { + stageName.value = stage.name || '' + competitionName.value = stage.parent?.name || '' + } else { + stageName.value = projectDetail.value.activityName || '' + competitionName.value = '' + } + } catch (e) { + stageName.value = projectDetail.value?.activityName || '' + competitionName.value = '' + } +} // 鏉冮檺楠岃瘉鏂规硶 const checkPermissions = async () => { @@ -331,6 +360,7 @@ try { const data = await getProjectDetail(projectId.value) projectDetail.value = data + await loadStageMeta() // 鍒濆鍖栬瘎鍒嗛」 if (data.ratingForm && data.ratingForm.items) { @@ -373,12 +403,13 @@ return } - // 楠岃瘉stageId - if (!stageId.value) { + // 缁熶竴鑾峰彇stageId锛堜紭鍏堣矾鐢卞弬鏁帮紝鍏舵璇︽儏閲岀殑stageId锛� + const sid = stageId.value ? parseInt(stageId.value) : (projectDetail.value?.stageId ? parseInt(projectDetail.value.stageId) : null) + if (!sid) { ElMessage.error('缂哄皯姣旇禌闃舵淇℃伅锛岃閲嶆柊杩涘叆椤甸潰') return } - + // 楠岃瘉璇勫垎 const hasEmptyScore = ratingItems.value.some(item => item.score === 0 || item.score === null) if (hasEmptyScore) { @@ -397,7 +428,7 @@ const ratingData = { activityPlayerId: parseInt(projectId.value), - stageId: parseInt(stageId.value), + stageId: sid, ratings: ratingItems.value.map(item => ({ itemId: item.id, score: item.score @@ -421,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 }) } } @@ -518,6 +616,7 @@ .description-content { line-height: 1.6; color: #606266; + white-space: pre-wrap; } .attachments { @@ -673,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; @@ -685,4 +791,25 @@ :deep(.el-card__body) { padding: 16px; } + +/* 浠呴拡瀵归」鐩俊鎭繖缁勬弿杩拌缃爣绛�/鍐呭瀹藉害姣斾緥 */ +.project-info :deep(.el-descriptions__label) { + width: 40% !important; + min-width: 40%; + box-sizing: border-box; +} +.project-info :deep(.el-descriptions__content) { + width: 60% !important; + min-width: 60%; + box-sizing: border-box; +} + +/* 绐勫睆鑷�傚簲锛氬皬灞忔椂鍥為��涓轰笂涓嬬粨鏋� */ +@media (max-width: 768px) { + .project-info :deep(.el-descriptions__label), + .project-info :deep(.el-descriptions__content) { + width: 100% !important; + min-width: 100%; + } +} </style> \ No newline at end of file -- Gitblit v1.8.0