// pages/profile/personal-info.js const app = getApp() const { graphqlRequest } = require('../../lib/utils') Page({ data: { userInfo: { avatar: '', name: '', gender: '', phone: '', birthday: '' }, currentDate: '', saving: false }, onLoad(options) { this.initCurrentDate() this.loadUserInfo() }, // 初始化当前日期 initCurrentDate() { const now = new Date() const year = now.getFullYear() const month = String(now.getMonth() + 1).padStart(2, '0') const day = String(now.getDate()).padStart(2, '0') this.setData({ currentDate: `${year}-${month}-${day}` }) }, // 加载用户信息 async loadUserInfo() { try { // 先从全局数据获取 const globalUserInfo = app.globalData.userInfo || {} // 从后端获取最新用户信息 const query = ` query GetUserProfile { userProfile { id name avatar phone gender birthday } } ` const result = await graphqlRequest(query) let userInfo = globalUserInfo if (result && result.userProfile) { userInfo = result.userProfile // 更新全局数据 app.globalData.userInfo = userInfo } this.setData({ userInfo: { avatar: userInfo.avatar || '', name: userInfo.name || '', gender: userInfo.gender || '', phone: userInfo.phone || '', birthday: userInfo.birthday || '' } }) } catch (error) { console.error('加载用户信息失败:', error) // 使用全局数据作为备选 const globalUserInfo = app.globalData.userInfo || {} this.setData({ userInfo: { avatar: globalUserInfo.avatar || '', name: globalUserInfo.name || '', gender: globalUserInfo.gender || '', phone: globalUserInfo.phone || '', birthday: globalUserInfo.birthday || '' } }) } }, // 选择头像 chooseAvatar() { wx.chooseMedia({ count: 1, mediaType: ['image'], sourceType: ['album', 'camera'], maxDuration: 30, camera: 'back', success: (res) => { const tempFilePath = res.tempFiles[0].tempFilePath this.uploadAvatar(tempFilePath) }, fail: (err) => { console.error('选择头像失败:', err) wx.showToast({ title: '选择头像失败', icon: 'none' }) } }) }, // 上传头像 async uploadAvatar(filePath) { wx.showLoading({ title: '上传中...' }) try { // 暂时保存本地路径,在保存用户信息时一起处理 this.setData({ 'userInfo.avatar': filePath, localAvatarPath: filePath // 保存本地路径用于后续上传 }) wx.hideLoading() wx.showToast({ title: '头像选择成功', icon: 'success' }) } catch (error) { wx.hideLoading() wx.showToast({ title: '头像选择失败', icon: 'none' }) } }, // 姓名输入 onNameInput(e) { this.setData({ 'userInfo.name': e.detail.value }) }, // 选择性别 selectGender(e) { const gender = e.currentTarget.dataset.gender this.setData({ 'userInfo.gender': gender }) }, // 获取手机号 async getPhoneNumber(e) { if (e.detail.errMsg === 'getPhoneNumber:ok') { try { wx.showLoading({ title: '获取中...' }) // TODO: 调用后端API解密手机号 const mutation = ` mutation DecryptPhoneNumber($encryptedData: String!, $iv: String!) { decryptPhoneNumber(encryptedData: $encryptedData, iv: $iv) { phoneNumber } } ` const variables = { encryptedData: e.detail.encryptedData, iv: e.detail.iv } const result = await graphqlRequest(mutation, variables) if (result && result.decryptPhoneNumber) { this.setData({ 'userInfo.phone': result.decryptPhoneNumber.phoneNumber }) wx.showToast({ title: '手机号获取成功', icon: 'success' }) } else { throw new Error('解密失败') } } catch (error) { console.error('获取手机号失败:', error) wx.showToast({ title: '获取手机号失败', icon: 'none' }) } finally { wx.hideLoading() } } else { wx.showToast({ title: '获取手机号失败', icon: 'none' }) } }, // 生日选择 onBirthdayChange(e) { this.setData({ 'userInfo.birthday': e.detail.value }) }, // 保存用户信息 async saveUserInfo() { const { userInfo, localAvatarPath } = this.data // 验证必填字段 if (!userInfo.name.trim()) { wx.showToast({ title: '请输入姓名', icon: 'none' }) return } if (!userInfo.gender) { wx.showToast({ title: '请选择性别', icon: 'none' }) return } if (!userInfo.phone) { wx.showToast({ title: '请获取手机号', icon: 'none' }) return } this.setData({ saving: true }) try { // 第一步:保存用户基本信息(不包含头像) const mutation = ` mutation SaveUserInfo($input: UserInput!) { saveUserInfo(input: $input) { id name avatar phone gender birthday wxOpenId unionId } } ` const variables = { input: { name: userInfo.name.trim(), phone: userInfo.phone, gender: userInfo.gender, birthday: userInfo.birthday } } const result = await graphqlRequest(mutation, variables) if (result && result.saveUserInfo) { const savedUserInfo = result.saveUserInfo // 第二步:如果有新头像,上传到COS并保存到t_media if (localAvatarPath) { await this.uploadAvatarWithUserId(savedUserInfo.id) } // 重新获取用户信息(包含头像) await this.loadUserInfo() // 更新全局用户信息 app.globalData.userInfo = { ...app.globalData.userInfo, ...this.data.userInfo } // 保存到本地存储 wx.setStorageSync('userInfo', app.globalData.userInfo) wx.showToast({ title: '保存成功', icon: 'success' }) // 延迟返回上一页 setTimeout(() => { wx.navigateBack() }, 1500) } else { throw new Error('保存失败') } } catch (error) { console.error('保存用户信息失败:', error) wx.showToast({ title: '保存失败', icon: 'none' }) } finally { this.setData({ saving: false }) } }, // 使用用户ID上传头像 async uploadAvatarWithUserId(userId) { const { localAvatarPath } = this.data if (!localAvatarPath) return try { // 引入cosUtil const cosUtil = require('../../lib/cosUtil') // 第一步:上传到COS const uploadResult = await cosUtil.uploadAvatar( localAvatarPath, 'avatar.jpg', (percent) => { console.log('头像上传进度:', percent + '%') } ) // 第二步:保存媒体记录到数据库 await this.saveMediaRecord({ targetType: 'player', // 用户头像 targetId: parseInt(userId), path: uploadResult.key, fileName: uploadResult.fileName || 'avatar.jpg', fileExt: this.getFileExtension(uploadResult.fileName || 'avatar.jpg'), fileSize: uploadResult.fileSize, mediaType: 1 // 图片 }) // 清空本地路径 this.setData({ localAvatarPath: '' }) console.log('头像上传成功:', uploadResult) } catch (error) { console.error('头像上传失败:', error) throw error } }, // 保存媒体记录到数据库 async saveMediaRecord(mediaData) { const mutation = ` mutation SaveMediaV2($input: MediaSaveInput!) { saveMediaV2(input: $input) { success message mediaId } } ` const variables = { input: { targetType: mediaData.targetType, targetId: mediaData.targetId, path: mediaData.path, fileName: mediaData.fileName, fileExt: mediaData.fileExt, fileSize: mediaData.fileSize, mediaType: mediaData.mediaType } } try { const result = await graphqlRequest(mutation, variables) console.log('媒体记录保存成功:', result.saveMediaV2) return result.saveMediaV2 } catch (error) { console.error('媒体记录保存失败:', error) throw error } }, // 获取文件扩展名 getFileExtension(fileName) { return fileName.split('.').pop().toLowerCase() } })