// pages/project/detail.js const app = getApp() Page({ data: { projectId: '', projectDetail: null, timeline: [], loading: true, error: '', statusText: '', genderText: '', educationText: '', timelineLoading: false, timelineError: '', showRatingDetail: false, ratingDetail: null, ratingDetailLoading: false, ratingDetailError: '' }, onLoad(options) { if (options.id) { this.setData({ projectId: options.id }) this.loadProjectDetail() } else { this.setData({ error: '缺少项目ID参数', loading: false }) } }, // 加载项目详情 async loadProjectDetail() { try { this.setData({ loading: true, error: '' }) const projectDetail = await this.getProjectDetailFromAPI(this.data.projectId) if (!projectDetail) { throw new Error('项目详情获取失败') } if (projectDetail.submissionFiles) { projectDetail.submissionFiles.forEach(file => { file.fileSizeText = this.formatFileSize(file.fileSize) }) } this.setData({ projectDetail, statusText: this.getStatusText(projectDetail.state), genderText: this.getGenderText(projectDetail.playerInfo?.gender), educationText: this.getEducationText(projectDetail.playerInfo?.education) }) await this.loadProjectTimeline(this.data.projectId) } catch (error) { console.error('加载项目详情失败:', error) this.setData({ error: error.message || '加载失败,请重试' }) } finally { this.setData({ loading: false }) } }, // 从API获取项目详情 async getProjectDetailFromAPI(projectId) { // 构建GraphQL查询 const query = ` query GetProjectDetail($id: ID!) { activityPlayerDetail(id: $id) { id playerInfo { id name phone gender birthday education introduction description avatarUrl avatar { id fullUrl fullThumbUrl name fileSize fileExt } userInfo { userId name phone avatarUrl } } regionInfo { id name fullPath } activityName projectName description feedback state stageId submissionFiles { id fullUrl fullThumbUrl name fileSize fileExt mediaType } ratingForm { schemeId schemeName items { id name maxScore orderNo } totalMaxScore } } } ` try { const result = await app.graphqlRequest(query, { id: projectId }) return result.activityPlayerDetail } catch (error) { throw error } }, async loadProjectTimeline(activityPlayerId) { if (!activityPlayerId) { return } const idNumber = Number(activityPlayerId) const variables = { activityPlayerId: Number.isNaN(idNumber) ? activityPlayerId : idNumber } this.setData({ timelineLoading: true, timelineError: '' }) const query = ` query ProjectStageTimeline($activityPlayerId: ID!) { projectStageTimeline(activityPlayerId: $activityPlayerId) { activityId activityName stages { stageId stageName matchTime sortOrder participated activityPlayerId averageScore ratingCount hasRating latestRatingTime } } } ` try { const result = await app.graphqlRequest(query, variables) const projectStageTimeline = result && result.projectStageTimeline ? result.projectStageTimeline : null const stages = projectStageTimeline && projectStageTimeline.stages ? projectStageTimeline.stages : [] const timeline = stages.map(stage => { const hasScore = stage.hasRating && stage.averageScore !== null && stage.averageScore !== undefined let scoreText = '未参赛' if (stage.participated) { scoreText = hasScore ? `平均分:${Number(stage.averageScore).toFixed(2)}` : '未评分' } return { ...stage, matchTimeText: stage.matchTime ? this.formatDateTime(stage.matchTime) : '', scoreText, displayAverageScore: hasScore ? Number(stage.averageScore).toFixed(2) : null, isClickable: stage.participated && hasScore && !!stage.activityPlayerId } }) this.setData({ timeline, timelineLoading: false }) } catch (error) { console.error('加载阶段时间轴失败:', error) this.setData({ timelineError: error.message || '时间轴加载失败', timelineLoading: false }) } }, async fetchStageRatingDetail(activityPlayerId) { const idNumber = Number(activityPlayerId) const variables = { activityPlayerId: Number.isNaN(idNumber) ? activityPlayerId : idNumber } const query = ` query StageJudgeRatings($activityPlayerId: ID!) { stageJudgeRatings(activityPlayerId: $activityPlayerId) { activityPlayerId stageId stageName matchTime ratingCount averageScore judgeRatings { judgeId judgeName totalScore feedback ratingTime } } } ` const result = await app.graphqlRequest(query, variables) const detail = result && result.stageJudgeRatings ? result.stageJudgeRatings : null const sourceJudgeRatings = detail && detail.judgeRatings ? detail.judgeRatings : [] const judgeRatings = sourceJudgeRatings.map(item => ({ ...item, totalScoreText: item.totalScore !== null && item.totalScore !== undefined ? `${Number(item.totalScore).toFixed(2)}分` : '未评分', ratingTimeText: item.ratingTime ? this.formatDateTime(item.ratingTime) : '' })) const averageScoreValue = detail && detail.averageScore !== undefined && detail.averageScore !== null ? detail.averageScore : null return { activityPlayerId: detail && detail.activityPlayerId ? detail.activityPlayerId : variables.activityPlayerId, stageId: detail && detail.stageId ? detail.stageId : null, stageName: detail && detail.stageName ? detail.stageName : '阶段信息', matchTime: detail && detail.matchTime ? detail.matchTime : null, matchTimeText: detail && detail.matchTime ? this.formatDateTime(detail.matchTime) : '', ratingCount: detail && detail.ratingCount ? detail.ratingCount : 0, averageScore: averageScoreValue, averageScoreText: averageScoreValue !== null ? Number(averageScoreValue).toFixed(2) : '暂无评分', judgeRatings } }, async openStageDetail(e) { const { playerId } = e.currentTarget.dataset const clickable = e.currentTarget.dataset.clickable === true || e.currentTarget.dataset.clickable === 'true' const participated = e.currentTarget.dataset.participated === true || e.currentTarget.dataset.participated === 'true' if (!playerId || !participated) { return } if (!clickable) { wx.showToast({ title: '暂无评分', icon: 'none' }) return } this.setData({ showRatingDetail: true, ratingDetailLoading: true, ratingDetailError: '', ratingDetail: { judgeRatings: [] } }) try { const detail = await this.fetchStageRatingDetail(playerId) this.setData({ ratingDetail: detail, ratingDetailLoading: false }) } catch (error) { console.error('加载阶段评分详情失败:', error) this.setData({ ratingDetailError: error.message || '加载失败', ratingDetailLoading: false }) } }, closeStageDetail() { this.setData({ showRatingDetail: false, ratingDetail: null, ratingDetailError: '' }) }, // 预览文件 previewFile(e) { const file = e.currentTarget.dataset.file if (!file || !file.fullUrl) { wx.showToast({ title: '文件链接无效', icon: 'none' }) return } // 根据文件类型进行不同处理 const fileExt = (file.fileExt || '').toLowerCase() if (this.isImageFile(fileExt)) { // 图片预览 wx.previewImage({ urls: [file.fullUrl], current: file.fullUrl }) } else if (this.isVideoFile(fileExt)) { // 视频预览 wx.navigateTo({ url: `/pages/video/video?url=${encodeURIComponent(file.fullUrl)}&title=${encodeURIComponent(file.name)}` }) } else if (this.isPdfFile(fileExt)) { // PDF文档预览 this.previewPdfFile(file) } else if (this.isOfficeFile(fileExt)) { // Office文档预览 this.previewOfficeFile(file) } else { // 其他文件类型,尝试下载打开 this.downloadAndOpenFile(file) } }, // PDF文档预览 previewPdfFile(file) { wx.showLoading({ title: '正在打开PDF...' }) wx.downloadFile({ url: file.fullUrl, success: (downloadRes) => { wx.hideLoading() if (downloadRes.statusCode === 200) { wx.openDocument({ filePath: downloadRes.tempFilePath, fileType: 'pdf', success: () => { console.log('PDF打开成功') }, fail: (error) => { console.error('PDF打开失败:', error) wx.showToast({ title: 'PDF打开失败', icon: 'none' }) } }) } else { wx.showToast({ title: 'PDF下载失败', icon: 'none' }) } }, fail: (error) => { wx.hideLoading() console.error('PDF下载失败:', error) wx.showToast({ title: 'PDF下载失败', icon: 'none' }) } }) }, // Office文档预览 previewOfficeFile(file) { const fileExt = (file.fileExt || '').toLowerCase() wx.showLoading({ title: '正在打开文档...', mask: true }) wx.downloadFile({ url: file.fullUrl, success: (downloadRes) => { wx.hideLoading() if (downloadRes.statusCode === 200) { // 文件类型映射 const fileTypeMap = { 'doc': 'doc', 'docx': 'docx', 'xls': 'xls', 'xlsx': 'xlsx', 'ppt': 'ppt', 'pptx': 'pptx' } wx.openDocument({ filePath: downloadRes.tempFilePath, fileType: fileTypeMap[fileExt] || 'doc', success: () => { console.log('文档打开成功') }, fail: (error) => { console.error('文档打开失败:', error) wx.showModal({ title: '打开失败', content: '文档打开失败,可能是文件格式不支持或文件损坏', showCancel: false, confirmText: '确定' }) } }) } else { wx.showToast({ title: '文件下载失败', icon: 'none' }) } }, fail: (error) => { wx.hideLoading() console.error('文档下载失败:', error) wx.showToast({ title: '文件下载失败', icon: 'none' }) } }) }, // 下载并打开文件 downloadAndOpenFile(file) { wx.showModal({ title: '文件预览', content: '是否要下载并打开此文件?', confirmText: '下载', cancelText: '取消', success: (res) => { if (res.confirm) { wx.showLoading({ title: '正在下载...' }) wx.downloadFile({ url: file.fullUrl, success: (downloadRes) => { wx.hideLoading() if (downloadRes.statusCode === 200) { wx.openDocument({ filePath: downloadRes.tempFilePath, success: () => { console.log('文档打开成功') }, fail: (error) => { console.error('文档打开失败:', error) wx.showToast({ title: '文件打开失败', icon: 'none' }) } }) } else { wx.showToast({ title: '文件下载失败', icon: 'none' }) } }, fail: (error) => { wx.hideLoading() console.error('文件下载失败:', error) wx.showToast({ title: '文件下载失败', icon: 'none' }) } }) } } }) }, // 判断是否为图片文件 isImageFile(fileExt) { const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'] return imageExts.includes(fileExt) }, // 判断是否为视频文件 isVideoFile(fileExt) { const videoExts = ['mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv', 'webm'] return videoExts.includes(fileExt) }, // 判断是否为PDF文件 isPdfFile(fileExt) { return fileExt === 'pdf' }, // 判断是否为Office文件 isOfficeFile(fileExt) { const officeExts = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'dotx', 'xlsb', 'xlsm', 'ppsx', 'pps', 'potx', 'ppsm'] return officeExts.includes(fileExt) }, // 格式化文件大小 formatFileSize(bytes) { if (!bytes || bytes === 0) return '0 B' const k = 1024 const sizes = ['B', 'KB', 'MB', 'GB'] const i = Math.floor(Math.log(bytes) / Math.log(k)) return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] }, // 格式化日期时间 formatDateTime(dateTimeStr) { if (!dateTimeStr) return '' try { const date = new Date(dateTimeStr) const year = date.getFullYear() const month = String(date.getMonth() + 1).padStart(2, '0') const day = String(date.getDate()).padStart(2, '0') const hours = String(date.getHours()).padStart(2, '0') const minutes = String(date.getMinutes()).padStart(2, '0') return `${year}-${month}-${day} ${hours}:${minutes}` } catch (error) { return dateTimeStr } }, // 获取状态文本 getStatusText(state) { const statusMap = { 0: '未审核', 1: '审核通过', 2: '审核不通过' } return statusMap[state] || '未知状态' }, // 获取性别文本 getGenderText(gender) { const genderMap = { 'MALE': '男', 'FEMALE': '女' } return genderMap[gender] || '' }, // 获取学历文本 getEducationText(education) { const educationMap = { 'HIGH_SCHOOL': '高中及以下', 'COLLEGE': '大专', 'BACHELOR': '本科', 'MASTER': '硕士', 'DOCTOR': '博士' } return educationMap[education] || '' }, // 分享功能 onShareAppMessage() { return { title: `项目详情 - ${this.data.projectDetail?.projectName || ''}`, path: `/pages/project/detail?id=${this.data.projectId}` } } })