From c4938f6f4e839890b032c75c7a57333a6a9157a9 Mon Sep 17 00:00:00 2001
From: peng <peng.com>
Date: 星期四, 06 十一月 2025 17:06:10 +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