// pages/judge/review.js const app = getApp() const { graphqlRequest, formatDate } = require('../../lib/utils') Page({ data: { loading: false, submitting: false, // 提交作品信息 submission: null, submissionId: '', // 活动信息 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({ submissionId: options.id }) this.loadSubmissionDetail() } }, onShow() { // 页面显示时检查评审状态 if (this.data.submissionId) { this.checkReviewStatus() } }, // 加载提交作品详情 async loadSubmissionDetail() { try { this.setData({ loading: true }) const query = ` query GetSubmissionDetail($id: ID!) { submission(id: $id) { id title description files { id name url type size } images videos submittedAt status participant { id name school major avatar } team { id name members { id name role } } activity { id title description judgeCriteria { id name description maxScore weight } } myReview { id scores comment totalScore status reviewedAt } } } ` const result = await graphqlRequest(query, { id: this.data.submissionId }) if (result && result.submission) { const submission = result.submission // 为每个文件添加下载状态 if (submission.files) { submission.files = submission.files.map(file => ({ ...file, isDownloading: this.data.downloadingFiles.indexOf(file.id) > -1 })) } // 初始化评分数据 const scores = {} let maxScore = 0 if (submission.activity.judgeCriteria) { submission.activity.judgeCriteria.forEach(criterion => { scores[criterion.id] = submission.myReview ? submission.myReview.scores[criterion.id] || 0 : 0 maxScore += criterion.maxScore }) } this.setData({ submission, activity: submission.activity, criteria: submission.activity.judgeCriteria || [], scores, maxScore, existingReview: submission.myReview, reviewStatus: submission.myReview ? submission.myReview.status : 'PENDING', comment: submission.myReview ? submission.myReview.comment : '' }) this.calculateTotalScore() } } catch (error) { console.error('加载作品详情失败:', error) wx.showToast({ title: '加载失败', icon: 'error' }) } finally { this.setData({ loading: false }) } }, // 检查评审状态 async checkReviewStatus() { try { const query = ` query CheckReviewStatus($submissionId: ID!) { reviewStatus(submissionId: $submissionId) { status canReview deadline } } ` const result = await graphqlRequest(query, { submissionId: this.data.submissionId }) if (result && result.reviewStatus) { const { status, canReview, deadline } = result.reviewStatus if (!canReview) { wx.showModal({ title: '无法评审', content: deadline ? `评审已截止(截止时间:${formatDate(deadline)})` : '当前无法进行评审', showCancel: false, success: () => { wx.navigateBack() } }) } } } 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 { submissionId, scores, comment, totalScore } = this.data const mutation = ` mutation SaveReviewDraft($input: ReviewDraftInput!) { saveReviewDraft(input: $input) { success review { id status } } } ` const input = { submissionId, scores, comment: comment.trim(), totalScore } const result = await graphqlRequest(mutation, { input }) if (result && result.saveReviewDraft.success) { wx.showToast({ title: '草稿已保存', icon: 'success' }) } } 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 { submissionId, scores, comment, totalScore } = this.data const mutation = ` mutation SubmitReview($input: ReviewSubmitInput!) { submitReview(input: $input) { success review { id status reviewedAt } } } ` const input = { submissionId, scores, comment: comment.trim(), totalScore } const result = await graphqlRequest(mutation, { input }) if (result && result.submitReview.success) { wx.showToast({ title: '评审提交成功', icon: 'success' }) // 更新状态 this.setData({ reviewStatus: 'COMPLETED', existingReview: result.submitReview.review }) // 延迟返回上一页 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?submissionId=${this.data.submissionId}` }) }, // 联系参赛者 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' } } })