// pages/judge/review.js const app = getApp() const { graphqlRequest, formatDate } = require('../../lib/utils') Page({ data: { loading: false, submitting: false, // 提交作品信息 submission: null, activityPlayerId: '', // 活动信息 activity: null, // 评审标准 criteria: [], // 评分数据 scores: {}, // 评审意见 comment: '', // 总分 totalScore: 0, maxScore: 0, // 评审状态 reviewStatus: 'PENDING', // PENDING, COMPLETED // 已有评审记录 existingReview: null, // 媒体预览 showMediaPreview: false, currentMedia: null, mediaType: 'image', // 文件下载 downloadingFiles: [], // 评分等级 scoreOptions: [ { value: 1, label: '1分 - 很差' }, { value: 2, label: '2分 - 较差' }, { value: 3, label: '3分 - 一般' }, { value: 4, label: '4分 - 良好' }, { value: 5, label: '5分 - 优秀' } ] }, onLoad(options) { if (options.id) { this.setData({ activityPlayerId: options.id }) this.loadSubmissionDetail() } }, onShow() { // 页面显示时检查评审状态 if (this.data.submissionId) { this.checkReviewStatus() } }, // 加载提交作品详情 async loadSubmissionDetail() { try { this.setData({ loading: true }) const query = ` query GetActivityPlayerDetail($id: ID!) { activityPlayerDetail(id: $id) { id projectName description activityName stageId state playerInfo { id name phone gender birthday education introduction userInfo { userId name phone avatarUrl } } regionInfo { id name fullPath } submissionFiles { id name url fullUrl fileExt fileSize mediaType thumbUrl fullThumbUrl } ratingForm { schemeId schemeName totalMaxScore items { id name description maxScore weight sortOrder } } } } ` const result = await graphqlRequest(query, { id: this.data.activityPlayerId }) if (result && result.activityPlayerDetail) { const detail = result.activityPlayerDetail // 构建submission对象以兼容现有的WXML模板 const submission = { id: detail.id, title: detail.projectName, description: detail.description, files: detail.submissionFiles ? detail.submissionFiles.map(file => ({ id: file.id, name: file.name, url: file.fullUrl || file.url, type: file.fileExt, size: file.fileSize, isDownloading: this.data.downloadingFiles.indexOf(file.id) > -1 })) : [], images: detail.submissionFiles ? detail.submissionFiles .filter(file => file.mediaType === 1) .map(file => file.fullUrl || file.url) : [], videos: detail.submissionFiles ? detail.submissionFiles .filter(file => file.mediaType === 2) .map(file => file.fullUrl || file.url) : [], participant: { id: detail.playerInfo.id, name: detail.playerInfo.name, school: detail.regionInfo ? detail.regionInfo.name : '', major: detail.playerInfo.education || '', avatar: detail.playerInfo.userInfo?.avatarUrl || '/images/default-avatar.svg' }, status: detail.state === 1 ? 'APPROVED' : detail.state === 2 ? 'REJECTED' : 'PENDING' } // 构建activity对象 const activity = { id: detail.stageId, title: detail.activityName, description: detail.description, judgeCriteria: detail.ratingForm ? detail.ratingForm.items || [] : [] } // 初始化评分数据 const scores = {} let maxScore = 0 if (activity.judgeCriteria) { activity.judgeCriteria.forEach(criterion => { scores[criterion.id] = 0 // 暂时设为0,后续需要查询已有评分 maxScore += criterion.maxScore }) } this.setData({ submission, activity, criteria: activity.judgeCriteria || [], scores, maxScore, existingReview: null, // 暂时设为null,后续需要查询已有评分 reviewStatus: 'PENDING', comment: '' }) this.calculateTotalScore() // 检查是否已有评分 this.checkReviewStatus() } } catch (error) { console.error('加载作品详情失败:', error) wx.showToast({ title: '加载失败', icon: 'error' }) } finally { this.setData({ loading: false }) } }, // 检查评审状态 async checkReviewStatus() { try { const query = ` query GetCurrentJudgeRating($activityPlayerId: ID!) { currentJudgeRating(activityPlayerId: $activityPlayerId) { id totalScore comment status ratedAt items { ratingItemId ratingItemName score maxScore } } } ` const result = await graphqlRequest(query, { activityPlayerId: this.data.activityPlayerId }) if (result && result.currentJudgeRating) { const rating = result.currentJudgeRating // 如果已有评分,填充数据 const scores = {} let totalScore = 0 if (rating.items) { rating.items.forEach(item => { scores[item.ratingItemId] = item.score totalScore += item.score }) } this.setData({ scores, totalScore, comment: rating.comment || '', existingReview: rating, reviewStatus: rating.status || 'COMPLETED' }) console.log('已加载现有评分:', rating) } else { console.log('当前评委尚未评分') } } catch (error) { console.error('检查评审状态失败:', error) } }, // 评分改变 onScoreChange(e) { const { criterionId } = e.currentTarget.dataset const { value } = e.detail this.setData({ [`scores.${criterionId}`]: parseInt(value) }) this.calculateTotalScore() }, // 计算总分 calculateTotalScore() { const { scores, criteria } = this.data let totalScore = 0 criteria.forEach(criterion => { const score = scores[criterion.id] || 0 totalScore += score * (criterion.weight || 1) }) this.setData({ totalScore }) }, // 评审意见输入 onCommentInput(e) { this.setData({ comment: e.detail.value }) }, // 媒体点击 onMediaTap(e) { const { url, type } = e.currentTarget.dataset if (type === 'image') { wx.previewImage({ current: url, urls: this.data.submission.images || [] }) } else if (type === 'video') { this.setData({ showMediaPreview: true, currentMedia: url, mediaType: 'video' }) } }, // 关闭媒体预览 onCloseMediaPreview() { this.setData({ showMediaPreview: false, currentMedia: null }) }, // 下载文件 async onDownloadFile(e) { const { fileId, fileName, fileUrl } = e.currentTarget.dataset try { // 添加到下载中列表 const downloadingFiles = [...this.data.downloadingFiles, fileId] // 同时更新文件的isDownloading字段 const submission = { ...this.data.submission } if (submission.files) { submission.files = submission.files.map(file => ({ ...file, isDownloading: file.id === fileId ? true : file.isDownloading })) } this.setData({ downloadingFiles, submission }) wx.showLoading({ title: '下载中...' }) const result = await wx.downloadFile({ url: fileUrl, success: (res) => { if (res.statusCode === 200) { // 保存到相册或文件 wx.saveFile({ tempFilePath: res.tempFilePath, success: () => { wx.showToast({ title: '下载成功', icon: 'success' }) }, fail: () => { wx.showToast({ title: '保存失败', icon: 'error' }) } }) } }, fail: () => { wx.showToast({ title: '下载失败', icon: 'error' }) } }) } catch (error) { console.error('下载文件失败:', error) wx.showToast({ title: '下载失败', icon: 'error' }) } finally { // 从下载中列表移除 const downloadingFiles = this.data.downloadingFiles.filter(id => id !== fileId) // 同时更新文件的isDownloading字段 const submission = { ...this.data.submission } if (submission.files) { submission.files = submission.files.map(file => ({ ...file, isDownloading: file.id === fileId ? false : file.isDownloading })) } this.setData({ downloadingFiles, submission }) wx.hideLoading() } }, // 验证评审数据 validateReview() { const { scores, criteria, comment } = this.data // 检查是否所有标准都已评分 for (let criterion of criteria) { if (!scores[criterion.id] || scores[criterion.id] === 0) { wx.showToast({ title: `请为"${criterion.name}"评分`, icon: 'error' }) return false } } // 检查评审意见 if (!comment.trim()) { wx.showToast({ title: '请填写评审意见', icon: 'error' }) return false } if (comment.trim().length < 10) { wx.showToast({ title: '评审意见至少10个字符', icon: 'error' }) return false } return true }, // 保存草稿 async onSaveDraft() { try { wx.showLoading({ title: '保存中...' }) const { activityPlayerId, scores, comment, criteria, activity } = this.data // 构建评分项数组 const ratings = criteria.map(criterion => ({ itemId: criterion.id, score: scores[criterion.id] || 0 })) const mutation = ` mutation SaveActivityPlayerRating($input: ActivityPlayerRatingInput!) { saveActivityPlayerRating(input: $input) } ` const input = { activityPlayerId, stageId: activity.stageId, ratings, comment: comment.trim() } const result = await graphqlRequest(mutation, { input }) if (result && result.saveActivityPlayerRating) { wx.showToast({ title: '草稿已保存', icon: 'success' }) // 重新加载评分状态 await this.checkReviewStatus() } } catch (error) { console.error('保存草稿失败:', error) wx.showToast({ title: '保存失败', icon: 'error' }) } finally { wx.hideLoading() } }, // 提交评审 async onSubmitReview() { if (!this.validateReview()) { return } wx.showModal({ title: '确认提交', content: '评审提交后将无法修改,确定要提交吗?', success: async (res) => { if (res.confirm) { await this.submitReview() } } }) }, // 执行提交评审 async submitReview() { try { this.setData({ submitting: true }) wx.showLoading({ title: '提交中...' }) const { activityPlayerId, scores, comment, criteria, activity } = this.data // 构建评分项数组 const ratings = criteria.map(criterion => ({ itemId: criterion.id, score: scores[criterion.id] || 0 })) const mutation = ` mutation SaveActivityPlayerRating($input: ActivityPlayerRatingInput!) { saveActivityPlayerRating(input: $input) } ` const input = { activityPlayerId, stageId: activity.stageId, ratings, comment: comment.trim() } const result = await graphqlRequest(mutation, { input }) if (result && result.saveActivityPlayerRating) { wx.showToast({ title: '评分提交成功', icon: 'success' }) // 更新状态 this.setData({ reviewStatus: 'COMPLETED' }) // 重新加载评分状态 await this.checkReviewStatus() // 延迟返回上一页 setTimeout(() => { wx.navigateBack() }, 1500) } } catch (error) { console.error('提交评分失败:', error) wx.showToast({ title: '提交失败', icon: 'error' }) } finally { this.setData({ submitting: false }) wx.hideLoading() } }, // 查看其他评审 onViewOtherReviews() { wx.navigateTo({ url: `/pages/judge/reviews?activityPlayerId=${this.data.activityPlayerId}` }) }, // 联系参赛者 onContactParticipant() { const { submission } = this.data if (submission.participant) { wx.showActionSheet({ itemList: ['发送消息', '查看详情'], success: (res) => { switch (res.tapIndex) { case 0: // 发送消息功能 wx.navigateTo({ url: `/pages/chat/chat?userId=${submission.participant.id}` }) break case 1: // 查看用户详情 wx.navigateTo({ url: `/pages/user/profile?userId=${submission.participant.id}` }) break } } }) } }, // 获取评分等级文本 getScoreLabel(score) { const option = this.data.scoreOptions.find(opt => opt.value === score) return option ? option.label : `${score}分` }, // 获取文件大小文本 getFileSizeText(size) { if (size < 1024) { return `${size}B` } else if (size < 1024 * 1024) { return `${(size / 1024).toFixed(1)}KB` } else { return `${(size / (1024 * 1024)).toFixed(1)}MB` } }, // 格式化日期 formatDate(dateString) { return formatDate(dateString, 'YYYY-MM-DD HH:mm') }, // 分享页面 onShareAppMessage() { return { title: '蓉易创 - 评审作品', path: '/pages/index/index' } } })