From 0a48616045ddce1562584543a0e89e5144051fde Mon Sep 17 00:00:00 2001
From: Codex Assistant <codex@example.com>
Date: 星期日, 05 十月 2025 14:52:44 +0800
Subject: [PATCH] 报名审核

---
 wx/pages/judge/review.js |  655 ++++++++++++++++++++++++++++++++---------------------------
 1 files changed, 358 insertions(+), 297 deletions(-)

diff --git a/wx/pages/judge/review.js b/wx/pages/judge/review.js
index babef8a..e317235 100644
--- a/wx/pages/judge/review.js
+++ b/wx/pages/judge/review.js
@@ -1,6 +1,6 @@
 // pages/judge/review.js
 const app = getApp()
-const { graphqlRequest, formatDate } = require('../../lib/utils')
+const { graphqlRequest, formatDate: formatDateUtil } = require('../../lib/utils')
 
 Page({
   data: {
@@ -9,14 +9,16 @@
     
     // 鎻愪氦浣滃搧淇℃伅
     submission: null,
-    submissionId: '',
-    
+    activityPlayerId: '',
+    stageId: null,
+    submissionId: null,
+
     // 娲诲姩淇℃伅
     activity: null,
-    
+
     // 璇勫鏍囧噯
     criteria: [],
-    
+
     // 璇勫垎鏁版嵁
     scores: {},
     
@@ -31,29 +33,12 @@
     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鍒� - 浼樼' }
-    ]
+    existingReview: null
   },
 
   onLoad(options) {
     if (options.id) {
-      this.setData({ submissionId: options.id })
+      this.setData({ activityPlayerId: options.id })
       this.loadSubmissionDetail()
     }
   },
@@ -65,105 +50,160 @@
     }
   },
 
+  transformMediaFile(file) {
+    const url = file.fullUrl || file.url
+    const thumbUrl = file.fullThumbUrl || url
+    const ext = (file.fileExt || '').toLowerCase()
+    let mediaType = 'file'
+
+    if (file.mediaType === 1 || ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'heic'].includes(ext)) {
+      mediaType = 'image'
+    } else if (file.mediaType === 2 || ['mp4', 'mov', 'avi', 'wmv', 'mkv', 'webm', 'flv'].includes(ext)) {
+      mediaType = 'video'
+    } else if (ext === 'pdf') {
+      mediaType = 'pdf'
+    } else if (['doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx', 'wps', 'txt', 'rtf'].includes(ext)) {
+      mediaType = 'word'
+    }
+
+    return {
+      id: file.id,
+      name: file.name,
+      url,
+      thumbUrl,
+      mediaType,
+      size: file.fileSize || 0
+    }
+  },
+
   // 鍔犺浇鎻愪氦浣滃搧璇︽儏
   async loadSubmissionDetail() {
     try {
       this.setData({ loading: true })
       
       const query = `
-        query GetSubmissionDetail($id: ID!) {
-          submission(id: $id) {
+        query GetActivityPlayerDetail($id: ID!) {
+          activityPlayerDetail(id: $id) {
             id
-            title
+            projectName
             description
-            files {
+            activityName
+            stageId
+            state
+            playerInfo {
               id
               name
-              url
-              type
-              size
-            }
-            images
-            videos
-            submittedAt
-            status
-            participant {
-              id
-              name
-              school
-              major
-              avatar
-            }
-            team {
-              id
-              name
-              members {
-                id
+              phone
+              gender
+              birthday
+              education
+              introduction
+              userInfo {
+                userId
                 name
-                role
+                phone
+                avatarUrl
               }
             }
-            activity {
+            regionInfo {
               id
-              title
-              description
-              judgeCriteria {
+              name
+              fullPath
+            }
+            submissionFiles {
+              id
+              name
+              fullUrl
+              fullThumbUrl
+              fileExt
+              fileSize
+              mediaType
+            }
+            ratingForm {
+              schemeId
+              schemeName
+              totalMaxScore
+              items {
                 id
                 name
-                description
                 maxScore
-                weight
+                orderNo
               }
-            }
-            myReview {
-              id
-              scores
-              comment
-              totalScore
-              status
-              reviewedAt
             }
           }
         }
       `
       
-      const result = await graphqlRequest(query, { id: this.data.submissionId })
+      const result = await graphqlRequest(query, { id: this.data.activityPlayerId })
       
-      if (result && result.submission) {
-        const submission = result.submission
+      if (result && result.activityPlayerDetail) {
+        const detail = result.activityPlayerDetail
         
-        // 涓烘瘡涓枃浠舵坊鍔犱笅杞界姸鎬�
-        if (submission.files) {
-          submission.files = submission.files.map(file => ({
-            ...file,
-            isDownloading: this.data.downloadingFiles.indexOf(file.id) > -1
-          }))
+        // 鏋勫缓submission瀵硅薄浠ュ吋瀹圭幇鏈夌殑WXML妯℃澘
+        const submission = {
+          id: detail.id,
+          title: detail.projectName,
+          description: detail.description,
+          submittedAt: detail.submitTime || null,
+          team: detail.team || null,
+          participant: {
+            id: detail.playerInfo?.id,
+            name: detail.playerInfo?.name,
+            phone: detail.playerInfo?.phone || detail.playerInfo?.userInfo?.phone || '',
+            avatar: detail.playerInfo?.userInfo?.avatarUrl || '/images/default-avatar.svg',
+            gender: this.getGenderLabel(detail.playerInfo?.gender),
+            birthday: this.getBirthdayText(detail.playerInfo?.birthday),
+            region: detail.regionInfo?.fullPath || detail.regionInfo?.name || '',
+            education: detail.playerInfo?.education || '',
+            school: detail.regionInfo ? detail.regionInfo.name : '',
+            major: detail.playerInfo?.education || ''
+          },
+          status: detail.state === 1 ? 'APPROVED' : detail.state === 2 ? 'REJECTED' : 'PENDING',
+          mediaList: (detail.submissionFiles || []).map(file => this.transformMediaFile(file))
         }
-        
-        // 鍒濆鍖栬瘎鍒嗘暟鎹�
+
+        const criteria = (detail.ratingForm?.items || []).map(item => {
+          const maxScore = item.maxScore || 0
+          return {
+            id: item.id,
+            name: item.name,
+            maxScore,
+            description: item.description || '鏆傛棤鎻忚堪',
+            step: maxScore > 20 ? 1 : 0.5,
+            currentScore: 0
+          }
+        })
+
         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
-          })
-        }
-        
+        criteria.forEach(criterion => {
+          scores[criterion.id] = 0
+        })
+
+        const maxScore = detail.ratingForm?.totalMaxScore || criteria.reduce((sum, item) => sum + (item.maxScore || 0), 0)
+
         this.setData({
           submission,
-          activity: submission.activity,
-          criteria: submission.activity.judgeCriteria || [],
+          activity: {
+            id: detail.id,
+            stageId: detail.stageId,
+            ratingSchemeId: detail.ratingForm?.schemeId || null,
+            totalMaxScore: maxScore
+          },
+          stageId: detail.stageId || null,
+          submissionId: detail.id,
+          criteria,
           scores,
           maxScore,
-          existingReview: submission.myReview,
-          reviewStatus: submission.myReview ? submission.myReview.status : 'PENDING',
-          comment: submission.myReview ? submission.myReview.comment : ''
+          totalScore: 0,
+          existingReview: null,
+          reviewStatus: 'PENDING',
+          comment: ''
         })
-        
+
         this.calculateTotalScore()
+
+        // 妫�鏌ユ槸鍚﹀凡鏈夎瘎鍒�
+        this.checkReviewStatus()
       }
     } catch (error) {
       console.error('鍔犺浇浣滃搧璇︽儏澶辫触:', error)
@@ -180,46 +220,126 @@
   async checkReviewStatus() {
     try {
       const query = `
-        query CheckReviewStatus($submissionId: ID!) {
-          reviewStatus(submissionId: $submissionId) {
+        query GetCurrentJudgeRating($activityPlayerId: ID!) {
+          currentJudgeRating(activityPlayerId: $activityPlayerId) {
+            id
+            totalScore
+            remark
             status
-            canReview
-            deadline
+            ratedAt
+            items {
+              ratingItemId
+              ratingItemName
+              score
+              maxScore
+            }
           }
         }
       `
       
-      const result = await graphqlRequest(query, { submissionId: this.data.submissionId })
+      const result = await graphqlRequest(query, { activityPlayerId: this.data.activityPlayerId })
       
-      if (result && result.reviewStatus) {
-        const { status, canReview, deadline } = result.reviewStatus
+      if (result && result.currentJudgeRating) {
+        const rating = result.currentJudgeRating
         
-        if (!canReview) {
-          wx.showModal({
-            title: '鏃犳硶璇勫',
-            content: deadline ? `璇勫宸叉埅姝紙鎴鏃堕棿锛�${formatDate(deadline)}锛塦 : '褰撳墠鏃犳硶杩涜璇勫',
-            showCancel: false,
-            success: () => {
-              wx.navigateBack()
-            }
+        // 濡傛灉宸叉湁璇勫垎锛屽~鍏呮暟鎹�
+        const scores = {}
+        let totalScore = 0
+        
+        if (rating.items) {
+          rating.items.forEach(item => {
+            const numericScore = item.score !== undefined && item.score !== null ? Number(item.score) : 0
+            scores[item.ratingItemId] = numericScore
+            totalScore += numericScore
           })
         }
+
+        const updatedCriteria = this.data.criteria.map(criterion => {
+          const value = scores[criterion.id] !== undefined ? scores[criterion.id] : 0
+          return {
+            ...criterion,
+            currentScore: value
+          }
+        })
+
+        const normalizedTotal = Number(totalScore.toFixed(2))
+
+        this.setData({
+          scores,
+          criteria: updatedCriteria,
+          totalScore: normalizedTotal,
+          comment: rating.remark || rating.comment || '',
+          existingReview: {
+            ...rating,
+            totalScore: rating.totalScore ? Number(rating.totalScore) : normalizedTotal,
+            reviewedAt: rating.ratedAt || rating.reviewedAt || rating.updateTime || null
+          },
+          reviewStatus: 'COMPLETED'
+        })
+        
+        console.log('宸插姞杞界幇鏈夎瘎鍒�:', rating)
+      } else {
+        console.log('褰撳墠璇勫灏氭湭璇勫垎')
       }
+
+      this.calculateTotalScore()
     } catch (error) {
       console.error('妫�鏌ヨ瘎瀹$姸鎬佸け璐�:', error)
     }
   },
 
+  normalizeScore(value, criterion) {
+    const maxScore = Number(criterion.maxScore || 0)
+    const step = Number(criterion.step || (maxScore > 20 ? 1 : 0.5))
+    if (Number.isNaN(value)) {
+      value = 0
+    }
+    let normalized = Math.round(value / step) * step
+    if (normalized < 0) normalized = 0
+    if (normalized > maxScore) normalized = maxScore
+    return Number(normalized.toFixed(2))
+  },
+
+  updateCriterionScore(criterionId, index, value) {
+    const criterion = this.data.criteria[index]
+    if (!criterion) return
+    const normalized = this.normalizeScore(value, criterion)
+
+    this.setData({
+      [`scores.${criterionId}`]: normalized,
+      [`criteria[${index}].currentScore`]: normalized
+    })
+
+    this.calculateTotalScore()
+  },
+
   // 璇勫垎鏀瑰彉
   onScoreChange(e) {
-    const { criterionId } = e.currentTarget.dataset
-    const { value } = e.detail
-    
-    this.setData({
-      [`scores.${criterionId}`]: parseInt(value)
-    })
-    
-    this.calculateTotalScore()
+    const { criterionId, index } = e.currentTarget.dataset
+    const criterion = this.data.criteria[index]
+    if (!criterion) return
+
+    const inputValue = Number(e.detail.value)
+    const newScore = this.normalizeScore(inputValue, criterion)
+    this.updateCriterionScore(criterionId, index, newScore)
+  },
+
+  increaseScore(e) {
+    const { criterionId, index } = e.currentTarget.dataset
+    const criterion = this.data.criteria[index]
+    if (!criterion) return
+    const current = Number(this.data.scores[criterionId] || criterion.currentScore || 0)
+    const step = criterion.step || (criterion.maxScore > 20 ? 1 : 0.5)
+    this.updateCriterionScore(criterionId, index, current + step)
+  },
+
+  decreaseScore(e) {
+    const { criterionId, index } = e.currentTarget.dataset
+    const criterion = this.data.criteria[index]
+    if (!criterion) return
+    const current = Number(this.data.scores[criterionId] || criterion.currentScore || 0)
+    const step = criterion.step || (criterion.maxScore > 20 ? 1 : 0.5)
+    this.updateCriterionScore(criterionId, index, current - step)
   },
 
   // 璁$畻鎬诲垎
@@ -228,11 +348,11 @@
     let totalScore = 0
     
     criteria.forEach(criterion => {
-      const score = scores[criterion.id] || 0
-      totalScore += score * (criterion.weight || 1)
+      const score = Number(scores[criterion.id] || 0)
+      totalScore += score
     })
     
-    this.setData({ totalScore })
+    this.setData({ totalScore: Number(totalScore.toFixed(2)) })
   },
 
   // 璇勫鎰忚杈撳叆
@@ -244,106 +364,58 @@
 
   // 濯掍綋鐐瑰嚮
   onMediaTap(e) {
-    const { url, type } = e.currentTarget.dataset
-    
-    if (type === 'image') {
+    const index = Number(e.currentTarget.dataset.index)
+    const mediaList = this.data.submission?.mediaList || []
+    const media = mediaList[index]
+    if (!media) return
+
+    if (media.mediaType === 'image') {
+      const imageUrls = mediaList
+        .filter(item => item.mediaType === 'image')
+        .map(item => item.url)
       wx.previewImage({
-        current: url,
-        urls: this.data.submission.images || []
+        current: media.url,
+        urls: imageUrls
       })
-    } else if (type === 'video') {
-      this.setData({
-        showMediaPreview: true,
-        currentMedia: url,
-        mediaType: 'video'
+    } else if (media.mediaType === 'video') {
+      wx.navigateTo({
+        url: `/pages/video/video?url=${encodeURIComponent(media.url)}&title=${encodeURIComponent(media.name)}`
       })
+    } else {
+      this.openDocumentMedia(media)
     }
   },
 
-  // 鍏抽棴濯掍綋棰勮
-  onCloseMediaPreview() {
-    this.setData({
-      showMediaPreview: false,
-      currentMedia: null
-    })
-  },
-
-  // 涓嬭浇鏂囦欢
-  async onDownloadFile(e) {
-    const { fileId, fileName, fileUrl } = e.currentTarget.dataset
-    
+  async openDocumentMedia(media) {
     try {
-      // 娣诲姞鍒颁笅杞戒腑鍒楄〃
-      const downloadingFiles = [...this.data.downloadingFiles, fileId]
-      
-      // 鍚屾椂鏇存柊鏂囦欢鐨刬sDownloading瀛楁
-      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 downloadRes = await new Promise((resolve, reject) => {
+        wx.downloadFile({
+          url: media.url,
+          success: resolve,
+          fail: reject
+        })
       })
-      
-      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'
-          })
-        }
+
+      if (downloadRes.statusCode !== 200) {
+        throw new Error('鏂囦欢涓嬭浇澶辫触')
+      }
+
+      await new Promise((resolve, reject) => {
+        wx.openDocument({
+          filePath: downloadRes.tempFilePath,
+          showMenu: true,
+          success: resolve,
+          fail: reject
+        })
       })
     } catch (error) {
-      console.error('涓嬭浇鏂囦欢澶辫触:', error)
+      console.error('鎵撳紑鏂囦欢澶辫触:', error)
       wx.showToast({
-        title: '涓嬭浇澶辫触',
+        title: '鏃犳硶鎵撳紑鏂囦欢',
         icon: 'error'
       })
     } finally {
-      // 浠庝笅杞戒腑鍒楄〃绉婚櫎
-      const downloadingFiles = this.data.downloadingFiles.filter(id => id !== fileId)
-      
-      // 鍚屾椂鏇存柊鏂囦欢鐨刬sDownloading瀛楁
-      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()
     }
   },
@@ -351,6 +423,7 @@
   // 楠岃瘉璇勫鏁版嵁
   validateReview() {
     const { scores, criteria, comment } = this.data
+    const commentText = (comment || '').trim()
     
     // 妫�鏌ユ槸鍚︽墍鏈夋爣鍑嗛兘宸茶瘎鍒�
     for (let criterion of criteria) {
@@ -364,7 +437,7 @@
     }
     
     // 妫�鏌ヨ瘎瀹℃剰瑙�
-    if (!comment.trim()) {
+    if (!commentText) {
       wx.showToast({
         title: '璇峰~鍐欒瘎瀹℃剰瑙�',
         icon: 'error'
@@ -372,7 +445,7 @@
       return false
     }
     
-    if (comment.trim().length < 10) {
+    if (commentText.length < 10) {
       wx.showToast({
         title: '璇勫鎰忚鑷冲皯10涓瓧绗�',
         icon: 'error'
@@ -381,51 +454,6 @@
     }
     
     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()
-    }
   },
 
   // 鎻愪氦璇勫
@@ -451,41 +479,53 @@
       this.setData({ submitting: true })
       wx.showLoading({ title: '鎻愪氦涓�...' })
       
-      const { submissionId, scores, comment, totalScore } = this.data
+      const { activityPlayerId, scores, comment, criteria, stageId } = this.data
+      const commentText = (comment || '').trim()
+
+      if (!stageId) {
+        wx.showToast({
+          title: '缂哄皯闃舵淇℃伅锛屾棤娉曟彁浜�',
+          icon: 'none'
+        })
+        this.setData({ submitting: false })
+        wx.hideLoading()
+        return
+      }
+      
+      // 鏋勫缓璇勫垎椤规暟缁�
+      const ratings = criteria.map(criterion => ({
+        itemId: criterion.id,
+        score: Number(scores[criterion.id] || 0)
+      }))
       
       const mutation = `
-        mutation SubmitReview($input: ReviewSubmitInput!) {
-          submitReview(input: $input) {
-            success
-            review {
-              id
-              status
-              reviewedAt
-            }
-          }
+        mutation SaveActivityPlayerRating($input: ActivityPlayerRatingInput!) {
+          saveActivityPlayerRating(input: $input)
         }
       `
       
       const input = {
-        submissionId,
-        scores,
-        comment: comment.trim(),
-        totalScore
+        activityPlayerId,
+        stageId,
+        ratings,
+        comment: commentText
       }
       
       const result = await graphqlRequest(mutation, { input })
       
-      if (result && result.submitReview.success) {
+      if (result && result.saveActivityPlayerRating) {
         wx.showToast({
-          title: '璇勫鎻愪氦鎴愬姛',
+          title: '璇勫垎鎻愪氦鎴愬姛',
           icon: 'success'
         })
         
         // 鏇存柊鐘舵��
         this.setData({
-          reviewStatus: 'COMPLETED',
-          existingReview: result.submitReview.review
+          reviewStatus: 'COMPLETED'
         })
+        
+        // 閲嶆柊鍔犺浇璇勫垎鐘舵��
+        await this.checkReviewStatus()
         
         // 寤惰繜杩斿洖涓婁竴椤�
         setTimeout(() => {
@@ -493,7 +533,7 @@
         }, 1500)
       }
     } catch (error) {
-      console.error('鎻愪氦璇勫澶辫触:', error)
+      console.error('鎻愪氦璇勫垎澶辫触:', error)
       wx.showToast({
         title: '鎻愪氦澶辫触',
         icon: 'error'
@@ -507,41 +547,24 @@
   // 鏌ョ湅鍏朵粬璇勫
   onViewOtherReviews() {
     wx.navigateTo({
-      url: `/pages/judge/reviews?submissionId=${this.data.submissionId}`
+      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
-          }
-        }
+    const phone = submission?.participant?.phone
+    if (phone) {
+      wx.makePhoneCall({
+        phoneNumber: phone
+      })
+    } else {
+      wx.showToast({
+        title: '鏆傛棤鑱旂郴鏂瑰紡',
+        icon: 'none'
       })
     }
-  },
-
-  // 鑾峰彇璇勫垎绛夌骇鏂囨湰
-  getScoreLabel(score) {
-    const option = this.data.scoreOptions.find(opt => opt.value === score)
-    return option ? option.label : `${score}鍒哷
   },
 
   // 鑾峰彇鏂囦欢澶у皬鏂囨湰
@@ -555,9 +578,47 @@
     }
   },
 
+  // 缁熶竴澶勭悊鎬у埆鏄剧ず鏂囨湰
+  getGenderLabel(gender) {
+    if (gender === null || gender === undefined || gender === '') {
+      return '鏈~鍐�'
+    }
+
+    const normalized = String(gender).trim().toLowerCase()
+
+    if (normalized === '') {
+      return '鏈~鍐�'
+    }
+
+    if (normalized === 'male' || normalized === 'm') {
+      return '鐢�'
+    }
+    if (normalized === 'female' || normalized === 'f') {
+      return '濂�'
+    }
+
+    if (/^-?\d+$/.test(normalized)) {
+      const numeric = Number(normalized)
+      if (numeric === 1) return '鐢�'
+      if (numeric === 0) return '濂�'
+      if (numeric === 2) return '濂�'
+    }
+
+    return gender === undefined || gender === null ? '鏈~鍐�' : String(gender)
+  },
+
+  // 缁熶竴澶勭悊鍑虹敓鏃ユ湡鏄剧ず鏂囨湰
+  getBirthdayText(dateString) {
+    if (!dateString) {
+      return '鏈~鍐�'
+    }
+    const formatted = formatDateUtil(dateString, 'YYYY-MM-DD')
+    return formatted || '鏈~鍐�'
+  },
+
   // 鏍煎紡鍖栨棩鏈�
   formatDate(dateString) {
-    return formatDate(dateString, 'YYYY-MM-DD HH:mm')
+    return formatDateUtil(dateString, 'YYYY-MM-DD HH:mm')
   },
 
   // 鍒嗕韩椤甸潰
@@ -567,4 +628,4 @@
       path: '/pages/index/index'
     }
   }
-})
\ No newline at end of file
+})

--
Gitblit v1.8.0