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-list.vue |  464 +++++++++++++++++++++++++++++++++++++++++++--------------
 1 files changed, 349 insertions(+), 115 deletions(-)

diff --git a/web/src/views/review-list.vue b/web/src/views/review-list.vue
index 1df7c38..932877b 100644
--- a/web/src/views/review-list.vue
+++ b/web/src/views/review-list.vue
@@ -21,10 +21,25 @@
           clearable
         />
         <el-select
+          v-model="selectedRegion"
+          placeholder="璇烽�夋嫨鍖哄煙"
+          @change="handleRegionChange"
+          style="width: 200px"
+          clearable
+          filterable
+        >
+          <el-option
+            v-for="region in regions"
+            :key="region.id"
+            :label="region.name"
+            :value="region.id"
+          />
+        </el-select>
+        <el-select
           v-model="selectedActivity"
           placeholder="璇烽�夋嫨姣旇禌"
           @change="handleActivityChange"
-          style="width: 200px"
+          style="width: 220px"
           clearable
           filterable
         >
@@ -43,9 +58,9 @@
             </span>
           </el-option>
         </el-select>
-        <el-button 
-          type="primary" 
-          :icon="Search" 
+        <el-button
+          type="primary"
+          :icon="Search"
           @click="loadProjects"
           :loading="projectsLoading"
         >
@@ -54,24 +69,30 @@
       </div>
     </div>
 
-    <el-table 
-      :data="projects" 
-      style="width: 100%" 
+    <el-table
+      :data="projects"
+      style="width: 100%"
       v-loading="projectsLoading"
       empty-text="璇峰厛閫夋嫨姣旇禌"
     >
-      <el-table-column prop="playerName" label="椤圭洰鍚嶇О" min-width="150">
+      <el-table-column prop="projectName" label="椤圭洰鍚嶇О" min-width="150">
         <template #default="scope">
-          {{ scope.row.projectName || scope.row.playerName }}
+          {{ scope.row.projectName || '鏈~鍐欓」鐩悕绉�' }}
         </template>
       </el-table-column>
       <el-table-column prop="playerName" label="鍙傝禌浜哄鍚�" min-width="120" />
       <el-table-column prop="phone" label="鑱旂郴鐢佃瘽" min-width="120" />
       <el-table-column prop="ratingCount" label="璇勫娆℃暟" width="100" align="center">
         <template #default="scope">
-          <el-tag :type="scope.row.ratingCount > 0 ? 'success' : 'info'">
+          <el-button
+            text
+            :type="scope.row.ratingCount > 0 ? 'success' : 'info'"
+            @click="showRatingList(scope.row)"
+            :disabled="scope.row.ratingCount === 0"
+            class="rating-count-btn"
+          >
             {{ scope.row.ratingCount }}
-          </el-tag>
+          </el-button>
         </template>
       </el-table-column>
       <el-table-column prop="averageScore" label="骞冲潎鍒�" width="100" align="center">
@@ -119,31 +140,172 @@
         @current-change="handleCurrentChange"
       />
     </div>
+
+    <!-- 璇勫鍒楄〃寮圭獥 -->
+    <el-dialog
+      v-model="ratingListVisible"
+      title="璇勫鍒楄〃"
+      width="60%"
+      :before-close="handleRatingListClose"
+    >
+      <div v-if="selectedProject">
+        <div class="dialog-header">
+          <h4>{{ selectedProject.projectName || selectedProject.playerName }} - 璇勫璇︽儏</h4>
+          <p class="project-info">鍙傝禌浜猴細{{ selectedProject.playerName }} | 鑱旂郴鐢佃瘽锛歿{ selectedProject.phone }}</p>
+        </div>
+
+        <el-table
+          :data="judgeRatings"
+          v-loading="ratingsLoading"
+          style="width: 100%"
+        >
+          <el-table-column prop="judgeName" label="璇勫濮撳悕" min-width="120" />
+          <el-table-column prop="totalScore" label="璇勫垎" width="100" align="center">
+            <template #default="scope">
+              <span v-if="scope.row.hasRated && scope.row.totalScore" class="score">
+                {{ scope.row.totalScore.toFixed(1) }}
+              </span>
+              <span v-else class="no-score">鏈瘎鍒�</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="hasRated" label="鐘舵��" width="100" align="center">
+            <template #default="scope">
+              <el-tag :type="scope.row.hasRated ? 'success' : 'info'">
+                {{ scope.row.hasRated ? '宸茶瘎鍒�' : '鏈瘎鍒�' }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="鎿嶄綔" width="100" align="center">
+            <template #default="scope">
+              <el-button
+                text
+                type="primary"
+                @click="viewRatingDetail(scope.row)"
+                :disabled="!scope.row.hasRated"
+                size="small"
+              >
+                鏌ョ湅璇︽儏
+              </el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="handleRatingListClose">鍏抽棴</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 璇勫垎璇︽儏寮圭獥 -->
+    <el-dialog
+      v-model="ratingDetailVisible"
+      title="璇勫垎璇︽儏"
+      width="50%"
+      :before-close="handleRatingDetailClose"
+    >
+      <div v-if="ratingDetail">
+        <div class="dialog-header">
+          <h4>{{ ratingDetail.judgeName }} 璇勫缁撴灉</h4>
+          <p class="project-info">
+            鎬诲垎锛�
+            <span class="total-score">
+              {{ ratingDetail.totalScore?.toFixed(1) ?? '鏆傛棤' }}
+            </span>
+          </p>
+        </div>
+
+        <el-table
+          :data="ratingDetail.items || []"
+          border
+          size="small"
+        >
+          <el-table-column prop="itemName" label="璇勫垎椤�" min-width="160" />
+          <el-table-column prop="score" label="寰楀垎" width="100" align="center">
+            <template #default="scope">
+              <span class="item-score">{{ scope.row.score?.toFixed(1) ?? '-' }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="maxScore" label="婊″垎" width="100" align="center">
+            <template #default="scope">
+              <span class="max-score">{{ scope.row.maxScore?.toFixed(1) ?? '-' }}</span>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <div class="rating-comment" v-if="ratingDetail.comment">
+          <h5>璇勮</h5>
+          <p>{{ ratingDetail.comment }}</p>
+        </div>
+      </div>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="handleRatingDetailClose">鍏抽棴</el-button>
+        </div>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
-<script setup>
+<script setup lang="ts">
 import { ref, onMounted } from 'vue'
 import { useRouter } from 'vue-router'
 import { ElMessage } from 'element-plus'
 import { Search, Trophy, View } from '@element-plus/icons-vue'
-import { getActiveActivities, getRatingStats } from '@/api/projectReview'
+import { getActiveActivities, getRatingStats, getJudgeRatingDetail } from '@/api/projectReview'
 import { getProjectReviewApplications } from '@/api/projectReviewNew'
+import { getLeafRegions } from '@/api/region'
+import { userApi } from '@/api/user'
+import { getUserInfo } from '@/utils/auth'
 
 const router = useRouter()
 
-// 鍝嶅簲寮忔暟鎹�
-const activities = ref([])
-const selectedActivity = ref(null)
-const projects = ref([])
+interface ActivityItem {
+  id: number
+  name: string
+  pid: number
+  parent?: ActivityItem
+}
+
+interface RegionItem {
+  id: number
+  name: string
+}
+
+const activities = ref<ActivityItem[]>([])
+const regions = ref<RegionItem[]>([])
+const selectedActivity = ref<number | null>(null)
+const selectedRegion = ref<number | null>(null)
+const projects = ref<any[]>([])
 const searchName = ref('')
 const activitiesLoading = ref(false)
 const projectsLoading = ref(false)
 
-// 鍒嗛〉鏁版嵁
+// 寮圭獥鐩稿叧鐘舵��
+const ratingListVisible = ref(false)
+const ratingDetailVisible = ref(false)
+const selectedProject = ref<any | null>(null)
+const judgeRatings = ref<any[]>([])
+const ratingDetail = ref<any | null>(null)
+const ratingsLoading = ref(false)
+const ratingDetailLoading = ref(false)
+
+// 鍒嗛〉
 const currentPage = ref(1)
 const pageSize = ref(10)
 const total = ref(0)
+
+const loadRegions = async () => {
+  try {
+    const data = await getLeafRegions()
+    regions.value = data || []
+  } catch (error: any) {
+    console.error('鍔犺浇鍖哄煙鍒楄〃澶辫触:', error)
+    ElMessage.error(error?.message || '鍔犺浇鍖哄煙鍒楄〃澶辫触')
+  }
+}
 
 // 鍔犺浇姣旇禌鍒楄〃
 const loadActivities = async () => {
@@ -152,16 +314,20 @@
   try {
     console.log('璋冪敤 getActiveActivities...')
     const data = await getActiveActivities()
-    console.log('getActiveActivities 杩斿洖鏁版嵁:', data)
-    activities.value = data
-    console.log('activities.value 璁剧疆涓�:', activities.value)
-    console.log('activities.value.length:', activities.value?.length)
-  } catch (error) {
+    console.log('姣旇禌鏁版嵁锛�', data)
+
+    activities.value = data || []
+
+    if (activities.value.length === 1) {
+      selectedActivity.value = activities.value[0].id
+      console.log('鑷姩閫変腑鍞竴姣旇禌:', selectedActivity.value)
+      loadProjects()
+    }
+  } catch (error: any) {
     console.error('鍔犺浇姣旇禌鍒楄〃澶辫触:', error)
-    ElMessage.error(error.message)
+    ElMessage.error(error?.message || '鍔犺浇姣旇禌鍒楄〃澶辫触')
   } finally {
     activitiesLoading.value = false
-    console.log('=== 姣旇禌鍒楄〃鍔犺浇瀹屾垚 ===')
   }
 }
 
@@ -171,51 +337,54 @@
     ElMessage.warning('璇峰厛閫夋嫨姣旇禌')
     return
   }
-  
+
   console.log('=== 寮�濮嬪姞杞介」鐩垪琛� ===')
   console.log('selectedActivity.value:', selectedActivity.value)
+  console.log('selectedRegion.value:', selectedRegion.value)
   console.log('currentPage.value:', currentPage.value)
   console.log('pageSize.value:', pageSize.value)
-  
+
   projectsLoading.value = true
   try {
-    const params = {
+    const params: Record<string, unknown> = {
       activityId: selectedActivity.value,
       page: currentPage.value,
       size: pageSize.value
     }
-    
-    // 濡傛灉鏈夋悳绱㈠悕绉帮紝娣诲姞鍒板弬鏁颁腑
+
+    if (selectedRegion.value) {
+      params.regionId = selectedRegion.value
+    }
+
     if (searchName.value && searchName.value.trim()) {
       params.name = searchName.value.trim()
     }
-    
-    console.log('API璋冪敤鍙傛暟:', params)
-    
+
+    console.log('璇锋眰鍙傛暟:', params)
     const response = await getProjectReviewApplications(params)
     console.log('API鍝嶅簲:', response)
-    
-    // 澶勭悊鍝嶅簲鏁版嵁 - 鏂扮殑鍒嗛〉缁撴瀯
+
     const pageData = response.projectReviewApplications
     projects.value = pageData?.content || []
     total.value = pageData?.totalElements || 0
-    
-    console.log('璁剧疆 projects.value:', projects.value)
-    console.log('projects.value.length:', projects.value.length)
-    console.log('璁剧疆 total.value:', total.value)
-  } catch (error) {
+  } catch (error: any) {
     console.error('鍔犺浇椤圭洰鍒楄〃澶辫触:', error)
-    ElMessage.error('鍔犺浇椤圭洰鍒楄〃澶辫触')
-    // 濡傛灉API璋冪敤澶辫触锛屾樉绀虹┖鏁版嵁
-    projects.value = []
-    total.value = 0
+    ElMessage.error(error?.message || '鍔犺浇椤圭洰鍒楄〃澶辫触')
   } finally {
     projectsLoading.value = false
   }
 }
 
+// 澶勭悊鍖哄煙閫夋嫨鍙樺寲
+const handleRegionChange = () => {
+  currentPage.value = 1
+  if (selectedActivity.value) {
+    loadProjects()
+  }
+}
+
 // 澶勭悊姣旇禌閫夋嫨鍙樺寲
-const handleActivityChange = (activityId) => {
+const handleActivityChange = () => {
   currentPage.value = 1
   loadProjects()
 }
@@ -223,78 +392,86 @@
 // 娓呯┖鎼滅储
 const handleClear = () => {
   searchName.value = ''
-  currentPage.value = 1
-  if (selectedActivity.value) {
-    loadProjects()
-  }
+  loadProjects()
 }
 
-// 閲嶇疆鎼滅储
-const resetSearch = () => {
-  searchName.value = ''
-  selectedActivity.value = null
-  currentPage.value = 1
-  projects.value = []
-  total.value = 0
-}
-
-// 鍒嗛〉澶勭悊
-const handleSizeChange = (size) => {
+// 鍒嗛〉澶у皬鏀瑰彉
+const handleSizeChange = (size: number) => {
   pageSize.value = size
   currentPage.value = 1
   loadProjects()
 }
 
-const handleCurrentChange = (page) => {
+// 褰撳墠椤垫敼鍙�
+const handleCurrentChange = (page: number) => {
   currentPage.value = page
   loadProjects()
 }
 
-// 鏌ョ湅璇︽儏
-const viewDetails = (projectId) => {
-  router.push(`/project-review/${projectId}/detail`)
-}
-
-// 鏍煎紡鍖栨棩鏈�
-const formatDate = (dateString) => {
-  if (!dateString) return '-'
-  return new Date(dateString).toLocaleString('zh-CN')
-}
-
-// 鑾峰彇鐘舵�佺被鍨�
-const getStateType = (state) => {
-  const stateMap = {
-    0: 'danger',  // 宸叉嫆缁�
-    1: 'warning', // 寰呭鏍�
-    2: 'success', // 宸查�氳繃
-    3: 'info'     // 宸茬粨鏉�
+// 灞曠ず璇勫鍒楄〃
+const showRatingList = async (project: any) => {
+  selectedProject.value = project
+  ratingsLoading.value = true
+  ratingListVisible.value = true
+  try {
+    const data = await getRatingStats(project.id)
+    judgeRatings.value = data || []
+  } catch (error: any) {
+    console.error('鍔犺浇璇勫鍒楄〃澶辫触:', error)
+    ElMessage.error(error?.message || '鍔犺浇璇勫鍒楄〃澶辫触')
+  } finally {
+    ratingsLoading.value = false
   }
-  return stateMap[state] || 'info'
 }
 
-// 鑾峰彇鐘舵�佸悕绉�
-const getStateName = (state) => {
-  const stateMap = {
-    0: '宸叉嫆缁�',
-    1: '寰呰瘎瀹�',
-    2: '宸查�氳繃',
-    3: '宸茬粨鏉�'
+const handleRatingListClose = () => {
+  ratingListVisible.value = false
+  judgeRatings.value = []
+}
+
+// 鏌ョ湅璇勫垎璇︽儏
+const viewRatingDetail = async (rating: any) => {
+  ratingDetailVisible.value = true
+  ratingDetailLoading.value = true
+  try {
+    const data = await getJudgeRatingDetail(rating.id)
+    ratingDetail.value = data
+  } catch (error: any) {
+    console.error('鍔犺浇璇勫垎璇︽儏澶辫触:', error)
+    ElMessage.error(error?.message || '鍔犺浇璇勫垎璇︽儏澶辫触')
+  } finally {
+    ratingDetailLoading.value = false
   }
-  return stateMap[state] || '鏈煡'
+}
+
+const handleRatingDetailClose = () => {
+  ratingDetailVisible.value = false
+  ratingDetail.value = null
+}
+
+// 鏍煎紡鍖栨椂闂�
+const formatDate = (value: string | number | Date) => {
+  if (!value) return '-'
+  const date = new Date(value)
+  return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(
+    date.getDate()
+  ).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(
+    date.getMinutes()
+  ).padStart(2, '0')}`
 }
 
 // 鑾峰彇璇勫鐘舵�佺被鍨嬶紙鍩轰簬璇勫娆℃暟锛�
-const getReviewStatusType = (ratingCount) => {
+const getReviewStatusType = (ratingCount: number) => {
   return ratingCount > 0 ? 'success' : 'warning'
 }
 
 // 鑾峰彇璇勫鐘舵�佸悕绉帮紙鍩轰簬璇勫娆℃暟锛�
-const getReviewStatusName = (ratingCount) => {
+const getReviewStatusName = (ratingCount: number) => {
   return ratingCount > 0 ? '宸茶瘎瀹�' : '鏈瘎瀹�'
 }
 
 // 鑾峰彇姣旇禌鍚嶇О锛堝鏋滄槸闃舵锛岃繑鍥炵埗姣旇禌鍚嶇О锛涘鏋滄槸姣旇禌锛岃繑鍥炶嚜宸辩殑鍚嶇О锛�
-const getActivityName = (activity) => {
+const getActivityName = (activity: ActivityItem) => {
   if (activity.pid > 0 && activity.parent) {
     return activity.parent.name
   }
@@ -302,22 +479,30 @@
 }
 
 // 鑾峰彇娲诲姩鏄剧ず鍚嶇О锛堢敤浜庢悳绱㈠拰閫変腑鏃舵樉绀猴級
-const getActivityDisplayName = (activity) => {
+const getActivityDisplayName = (activity: ActivityItem) => {
   if (activity.pid > 0 && activity.parent) {
     return `${activity.parent.name} - ${activity.name}`
   }
   return activity.name
 }
 
-// 鑾峰彇闃舵鍚嶇О锛堝鏋滄槸闃舵锛岃繑鍥為樁娈靛悕绉帮紱濡傛灉鏄瘮璧涳紝杩斿洖姣旇禌鍚嶇О锛�
-const getStageName = (activity) => {
-  return activity.name
+// 鏉冮檺妫�鏌ワ細鍙湁绠$悊鍛樺彲浠ユ煡鐪嬮」鐩瘎瀹�
+const checkPermission = async () => {
+  const userInfo = getUserInfo()
+  if (!userInfo) {
+    router.push('/login')
+  }
 }
 
-
+// 鏌ョ湅椤圭洰璇︽儏
+const viewDetails = (projectId: number) => {
+  router.push(`/project-review/${projectId}/detail`)
+}
 
 // 缁勪欢鎸傝浇鏃跺姞杞芥暟鎹�
 onMounted(() => {
+  checkPermission()
+  loadRegions()
   loadActivities()
 })
 </script>
@@ -350,6 +535,71 @@
   line-height: 1.5;
 }
 
+/* 璇勫娆℃暟鎸夐挳鏍峰紡 */
+.rating-count-btn {
+  font-weight: 500;
+}
+
+/* 寮圭獥鏍峰紡 */
+.dialog-header {
+  margin-bottom: 20px;
+
+  h4 {
+    margin: 0 0 8px 0;
+    font-size: 18px;
+    font-weight: 600;
+    color: #1f2937;
+  }
+
+  .project-info {
+    margin: 0;
+    color: #6b7280;
+    font-size: 13px;
+  }
+}
+
+.dialog-footer {
+  text-align: right;
+}
+
+.rating-comment {
+  margin-top: 20px;
+  padding: 16px;
+  background-color: #f5f7fa;
+  border-radius: 8px;
+
+  h5 {
+    margin: 0 0 8px 0;
+    color: #303133;
+    font-size: 14px;
+    font-weight: 600;
+  }
+
+  p {
+    margin: 0;
+    color: #606266;
+    line-height: 1.6;
+  }
+}
+
+.score {
+  color: #67c23a;
+  font-weight: 600;
+}
+
+.no-score {
+  color: #909399;
+}
+
+.item-score {
+  color: #409eff;
+  font-weight: 500;
+}
+
+.max-score {
+  color: #909399;
+}
+
 /* 鎼滅储宸ュ叿鏍� */
 .search-toolbar {
   display: flex;
@@ -361,12 +611,6 @@
   display: flex;
   gap: 12px;
   align-items: center;
-}
-
-.search-area {
-  display: flex;
-  align-items: center;
-  gap: 12px;
 }
 
 /* 鎿嶄綔鎸夐挳鏍峰紡 */
@@ -384,16 +628,6 @@
 .view-btn:hover {
   background-color: rgba(59, 130, 246, 0.1) !important;
   transform: scale(1.2);
-}
-
-.score {
-  color: #67c23a;
-  font-weight: 600;
-}
-
-.no-score {
-  color: #909399;
-  font-style: italic;
 }
 
 .pagination-container {
@@ -421,10 +655,10 @@
     gap: 12px;
     align-items: stretch;
   }
-  
-  .search-area {
+
+  .search-form {
     justify-content: center;
     flex-wrap: wrap;
   }
 }
-</style>
\ No newline at end of file
+</style>

--
Gitblit v1.8.0