From ba94ceae1315174798ae1967ef62268c6d16cd5b Mon Sep 17 00:00:00 2001 From: Codex Assistant <codex@example.com> Date: 星期一, 06 十月 2025 22:07:06 +0800 Subject: [PATCH] feat: 评审与活动相关改动 - backend(GraphQL): Activity schema 增加 updateActivityState(id, state);实现 resolver/service 仅更新 state=2 作为逻辑删除 - backend(GraphQL): region.graphqls 新增 Query leafRegions - backend(GraphQL): player.graphqls 的 projectReviewApplications 增加可选参数 regionId - backend(Service): listProjectReviewApplications 绑定 regionId 参数,修复 QueryParameterException - frontend(web): 新增 api/activity.js 的 updateActivityState 并接入 activity-list 删除逻辑 - frontend(web): review-list.vue 权限仅校验登录,移除角色限制;查询参数修正为 name/regionId - frontend(web): 删除未引用的 ActivityList.vue - frontend(web): projectReviewNew.js GraphQL 查询增加 name 参数 --- web/src/views/review-list.vue | 458 ++++++++++++++++++++++----------------------------------- 1 files changed, 178 insertions(+), 280 deletions(-) diff --git a/web/src/views/review-list.vue b/web/src/views/review-list.vue index c63b301..f2e344d 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,9 +69,9 @@ </div> </div> - <el-table - :data="projects" - style="width: 100%" + <el-table + :data="projects" + style="width: 100%" v-loading="projectsLoading" empty-text="璇峰厛閫夋嫨姣旇禌" > @@ -138,9 +153,9 @@ <h4>{{ selectedProject.projectName || selectedProject.playerName }} - 璇勫璇︽儏</h4> <p class="project-info">鍙傝禌浜猴細{{ selectedProject.playerName }} | 鑱旂郴鐢佃瘽锛歿{ selectedProject.phone }}</p> </div> - - <el-table - :data="judgeRatings" + + <el-table + :data="judgeRatings" v-loading="ratingsLoading" style="width: 100%" > @@ -175,7 +190,7 @@ </el-table-column> </el-table> </div> - + <template #footer> <div class="dialog-footer"> <el-button @click="handleRatingListClose">鍏抽棴</el-button> @@ -190,36 +205,41 @@ width="50%" :before-close="handleRatingDetailClose" > - <div v-if="selectedRating"> - <div class="rating-detail-header"> - <h4>璇勫锛歿{ selectedRating.judgeName }}</h4> - <p>鎬诲垎锛�<span class="total-score">{{ selectedRating.totalScore.toFixed(1) }}</span></p> + <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="ratingItems" - v-loading="ratingDetailLoading" - style="width: 100%" + + <el-table + :data="ratingDetail.items || []" + border + size="small" > - <el-table-column prop="itemName" label="璇勫垎椤圭洰" min-width="150" /> + <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 }}</span> + <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 || 100 }}</span> + <span class="max-score">{{ scope.row.maxScore?.toFixed(1) ?? '-' }}</span> </template> </el-table-column> </el-table> - - <div v-if="selectedRating.remark" class="rating-comment"> - <h5>璇勮锛�</h5> - <p>{{ selectedRating.remark }}</p> + + <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> @@ -229,40 +249,63 @@ </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, 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(null) -const selectedRating = ref(null) -const judgeRatings = ref([]) -const ratingItems = ref([]) +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 () => { @@ -271,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('=== 姣旇禌鍒楄〃鍔犺浇瀹屾垚 ===') } } @@ -290,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() } @@ -342,217 +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 = async (projectId) => { - // 浼犻�抯tageId鍙傛暟锛宻electedActivity.value灏辨槸褰撳墠閫変腑鐨剆tageId - const stageId = selectedActivity.value - if (!stageId) { - ElMessage.warning('璇峰厛閫夋嫨姣旇禌闃舵') - return - } - - try { - // 鑾峰彇褰撳墠鐢ㄦ埛淇℃伅 - const userInfo = getUserInfo() - if (!userInfo) { - ElMessage.error('鐢ㄦ埛鏈櫥褰曪紝璇烽噸鏂扮櫥褰�') - return - } - - // 妫�鏌ョ敤鎴锋槸鍚︽湁employee韬唤 - const hasEmployeeRole = !!userInfo.employee - - if (hasEmployeeRole) { - // 濡傛灉鐢ㄦ埛鏈塭mployee韬唤锛岀洿鎺ュ厑璁告煡鐪嬶紙涓嶉渶瑕佹潈闄愭鏌ワ級 - console.log('鐢ㄦ埛鍏锋湁鍛樺伐韬唤锛屽厑璁告煡鐪嬫墍鏈夎瘎鍒嗚褰�') - router.push(`/project-review/${projectId}/detail?stageId=${stageId}`) - return - } - - // 濡傛灉鐢ㄦ埛鍙湁judge韬唤锛堟病鏈塭mployee韬唤锛夛紝闇�瑕佹鏌ヨ瘎濮旀潈闄� - const judgeInfo = await userApi.getCurrentJudgeInfo() - - if (!judgeInfo) { - ElMessage.error('鎮ㄦ病鏈夎瘎濮旀潈闄愶紝鏃犳硶杩涜璇勫') - return - } - - // 妫�鏌ヨ瘎濮旀槸鍚︽湁褰撳墠姣旇禌闃舵鐨勬潈闄� - const hasPermission = await userApi.checkJudgeInActivity(stageId, judgeInfo.judgeId) - - if (!hasPermission) { - ElMessage.error('鎮ㄦ病鏈夊綋鍓嶆瘮璧涢樁娈电殑璇勫鏉冮檺锛屾棤娉曡繘鍏ヨ瘎瀹¢〉闈�') - return - } - - // 鏉冮檺妫�鏌ラ�氳繃锛岃烦杞埌璇勫椤甸潰 - router.push(`/project-review/${projectId}/detail?stageId=${stageId}`) - } catch (error) { - console.error('鏉冮檺妫�鏌ュけ璐�:', error) - ElMessage.error('鏉冮檺楠岃瘉澶辫触锛岃閲嶆柊鐧诲綍') - } -} - -// 鏄剧ず璇勫鍒楄〃 -const showRatingList = async (project) => { - if (project.ratingCount === 0) { - ElMessage.warning('璇ラ」鐩殏鏃犺瘎瀹¤褰�') - return - } - +// 灞曠ず璇勫鍒楄〃 +const showRatingList = async (project: any) => { selectedProject.value = project - ratingListVisible.value = true - - // 鍔犺浇璇勫鍒楄〃 - await loadJudgeRatings(project.id) -} - -// 鍔犺浇璇勫璇勫垎鍒楄〃 -const loadJudgeRatings = async (activityPlayerId) => { ratingsLoading.value = true + ratingListVisible.value = true try { - const result = await getRatingStats(activityPlayerId) - console.log('getRatingStats 杩斿洖鏁版嵁:', result) - - // 浣跨敤姝g‘鐨勫瓧娈靛悕 - judgeRatings.value = result.ratings || [] - console.log('judgeRatings 璁剧疆涓�:', judgeRatings.value) - } catch (error) { + const data = await getRatingStats(project.id) + judgeRatings.value = data || [] + } catch (error: any) { console.error('鍔犺浇璇勫鍒楄〃澶辫触:', error) - ElMessage.error('鍔犺浇璇勫鍒楄〃澶辫触') - judgeRatings.value = [] + ElMessage.error(error?.message || '鍔犺浇璇勫鍒楄〃澶辫触') } finally { ratingsLoading.value = false } } -// 鏌ョ湅璇勫垎璇︽儏 -const viewRatingDetail = async (rating) => { - if (!rating.hasRated) { - ElMessage.warning('璇ヨ瘎濮斿皻鏈瘎鍒�') - return - } - - selectedRating.value = rating - ratingDetailVisible.value = true - - // 鍔犺浇璇勫垎鏄庣粏 - await loadRatingDetail(selectedProject.value.id, rating.judgeId) +const handleRatingListClose = () => { + ratingListVisible.value = false + judgeRatings.value = [] } -// 鍔犺浇璇勫垎鏄庣粏 -const loadRatingDetail = async (activityPlayerId, judgeId) => { +// 鏌ョ湅璇勫垎璇︽儏 +const viewRatingDetail = async (rating: any) => { + ratingDetailVisible.value = true ratingDetailLoading.value = true try { - console.log('鍔犺浇璇勫垎鏄庣粏锛宎ctivityPlayerId:', activityPlayerId, 'judgeId:', judgeId) - - const result = await getJudgeRatingDetail(activityPlayerId, judgeId) - console.log('璇勫垎鏄庣粏API杩斿洖:', result) - - if (result && result.items) { - ratingItems.value = result.items.map(item => ({ - itemName: item.ratingItemName, - score: item.score, - maxScore: item.maxScore || 100 - })) - - // 鏇存柊閫変腑璇勫垎鐨勫娉ㄤ俊鎭� - if (selectedRating.value) { - selectedRating.value.remark = result.remark - } - } else { - ratingItems.value = [] - } - } catch (error) { - console.error('鍔犺浇璇勫垎鏄庣粏澶辫触:', error) - ElMessage.error('鍔犺浇璇勫垎鏄庣粏澶辫触: ' + (error.message || '鏈煡閿欒')) - ratingItems.value = [] + const data = await getJudgeRatingDetail(rating.id) + ratingDetail.value = data + } catch (error: any) { + console.error('鍔犺浇璇勫垎璇︽儏澶辫触:', error) + ElMessage.error(error?.message || '鍔犺浇璇勫垎璇︽儏澶辫触') } finally { ratingDetailLoading.value = false } } -// 鍏抽棴璇勫鍒楄〃寮圭獥 -const handleRatingListClose = () => { - ratingListVisible.value = false - selectedProject.value = null - judgeRatings.value = [] -} - -// 鍏抽棴璇勫垎璇︽儏寮圭獥 const handleRatingDetailClose = () => { ratingDetailVisible.value = false - selectedRating.value = null - ratingItems.value = [] + ratingDetail.value = null } -// 鏍煎紡鍖栨棩鏈� -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' // 宸茬粨鏉� - } - return stateMap[state] || 'info' -} - -// 鑾峰彇鐘舵�佸悕绉� -const getStateName = (state) => { - const stateMap = { - 0: '宸叉嫆缁�', - 1: '寰呰瘎瀹�', - 2: '宸查�氳繃', - 3: '宸茬粨鏉�' - } - return stateMap[state] || '鏈煡' +// 鏍煎紡鍖栨椂闂� +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 } @@ -560,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(`/review/detail/${projectId}`) +} // 缁勪欢鎸傝浇鏃跺姞杞芥暟鎹� onMounted(() => { + checkPermission() + loadRegions() loadActivities() }) </script> @@ -616,36 +543,23 @@ /* 寮圭獥鏍峰紡 */ .dialog-header { margin-bottom: 20px; - + h4 { margin: 0 0 8px 0; - color: #303133; - font-size: 16px; + font-size: 18px; font-weight: 600; + color: #1f2937; } - + .project-info { margin: 0; - color: #606266; - font-size: 14px; + color: #6b7280; + font-size: 13px; } } -.rating-detail-header { - margin-bottom: 20px; - - h4 { - margin: 0 0 8px 0; - color: #303133; - font-size: 16px; - font-weight: 600; - } - - .total-score { - color: #409eff; - font-weight: 600; - font-size: 18px; - } +.dialog-footer { + text-align: right; } .rating-comment { @@ -653,14 +567,14 @@ 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; @@ -699,12 +613,6 @@ align-items: center; } -.search-area { - display: flex; - align-items: center; - gap: 12px; -} - /* 鎿嶄綔鎸夐挳鏍峰紡 */ .action-btn { padding: 8px !important; @@ -720,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 { @@ -757,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