findme-miniprogram-frontend/subpackages/profile/personal-details/personal-details.js
2025-12-27 17:16:03 +08:00

658 lines
No EOL
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const apiClient = require('../../../utils/api-client.js');
const nimUserManager = require('../../../utils/nim-user-manager.js');
const { calculateConstellation, calculateConstellationFromBirthday } = require('../../../utils/formatDate.js');
const cosManager = require('../../../utils/cos-manager.js');
const occupationOptions = [
"初中生", "高中生", "大学生", "研究生", "留学生", "科研", "警察", "医生", "护士",
"程序员", "老师", "化妆师", "摄影师", "音乐", "美术", "金融", "厨师", "工程师",
"公务员", "互联网", "产品经理", "模特", "演员", "导演", "律师", "创业者", "其他"
];
const schoolOptions = ["博士", "硕士", "本科", "专科", "中专", "高中", "初中", "小学", "其他"];
const genderOptions = ["未知", "男", "女", "其他"];
const genderMap = {
"未知": 0,
"男": 1,
"女": 2,
"其他": 3
};
// 反向映射:数字转文字
const genderReverseMap = {
0: "未知",
1: "男",
2: "女",
3: "其他"
};
const zodiacSignOptions = ["双鱼座", "白羊座", "金牛座", "双子座", "巨蟹座", "狮子座", "处女座", "天秤座", "天蝎座", "射手座", "摩羯座", "水瓶座"];
const mbtiTypeOptions = ["INTJ", "INTP", "ENTJ", "INFP", "ENTP", "INFJ", "ENFP", "ENFJ", "ISTJ", "ISFJ", "ISTP", "ISFP", "ESTJ", "ESFJ", "ESTP", "ESFP"];
const sleepHabitOptions = ["早起鸟儿", "夜猫子", "规律型", "深度睡眠追求者", "碎片化睡眠者", "失眠困扰者", "咖啡因敏感型", "数字戒断者", "运动调节型", "挑战打卡型", "鼾声监测者", "生物钟调节者", "社区分享型"];
const socialActivityOptions = ["内容创作者", "观察者", "吃瓜者", "潜水者", "机器人", "社群型用户", "KOL", "KOC", "普通用户", "算法依赖型用户", "事件驱动型用户", "季节性活跃用户", "社交维系型用户", "兴趣社群型用户", "职业网络型用户", "娱乐消遣型用户", "购物种草型用户", "互动型用户"];
const heightOptions = Array.from({ length: 71 }, (_, i) => (140 + i));
Page({
data: {
occupationOptions,
schoolOptions,
genderOptions,
zodiacSignOptions,
mbtiTypeOptions,
sleepHabitOptions,
socialActivityOptions,
heightOptions,
user: {
nickname: "",
avatar: "",
bio: "",
gender: 0, // 0=未知 1=男 2=女 3=其他
birthday: "",
ethnicity: "",
occupation: "",
school: "",
hometown: [],
zodiacSign: "",
height: 0,
mbtiType: "",
sleepHabit: "",
socialActivity: ""
},
// 性别显示文字(用于 WXML 显示)
genderDisplayText: "请选择",
// sheet 控制
showSheet: false,
sheetOptions: [],
sheetTitle: "",
sheetKey: "",
selectedValue: "",
// 下拉刷新
refreshing: false
},
getKeyByValue(obj, value) {
return Object.keys(obj).find(key => obj[key] === value);
},
// 转换用户数据格式:从旧格式转换为新格式
transformUserData(data) {
if (!data) return this.clearObjectValues(this.data.user);
const transformed = {
nickname: data.nickname || "",
avatar: data.avatar || "",
bio: data.bio || "",
gender: typeof data.gender === 'number' ? data.gender : (genderMap[data.gender] !== undefined ? genderMap[data.gender] : 0),
birthday: data.birthday || "",
ethnicity: data.ethnicity || "",
occupation: data.occupation || "",
school: data.school || "", // 兼容旧字段 education
hometown: Array.isArray(data.hometown) ? data.hometown : [],
zodiacSign: data.zodiacSign || "", // 兼容旧字段 constellation
height: typeof data.height === 'number' ? data.height : (data.height ? parseInt(data.height) || 0 : 0),
mbtiType: data.mbtiType || "", // 兼容旧字段 personality
sleepHabit: data.sleepHabit || "", // 兼容旧字段 sleep
socialActivity: data.socialActivity || "" // 兼容旧字段 social
};
// 如果有生日但没有星座,根据生日自动生成星座
if (transformed.birthday && !transformed.zodiacSign) {
const generatedZodiacSign = calculateConstellationFromBirthday(transformed.birthday);
if (generatedZodiacSign) {
transformed.zodiacSign = generatedZodiacSign;
console.log('根据生日自动生成星座:', transformed.birthday, '->', generatedZodiacSign);
}
}
// 更新性别显示文字
this.updateGenderDisplay(transformed.gender);
return transformed;
},
// 更新性别显示文字
updateGenderDisplay(gender) {
let displayText = "请选择";
if (gender !== undefined && gender !== null) {
if (typeof gender === 'number' && genderReverseMap[gender] !== undefined) {
displayText = genderReverseMap[gender];
} else if (typeof gender === 'string') {
displayText = gender;
}
}
this.setData({ genderDisplayText: displayText });
},
async onLoad() {
try {
// 获取 app 实例
const app = getApp();
// 初始化屏幕适配
await app.initScreen(this);
const pages = getCurrentPages();
const currPage = pages[pages.length - 1]; // 当前页面实例
const profile = pages[pages.length - 2]; // 上一个页面实例profile
if (profile && profile.initSystemInfo) {
profile.initSystemInfo();
}
if (profile && profile.loadUserData) {
profile.loadUserData();
}
const clearData = this.clearObjectValues(this.data.user);
// 获取用户信息
try {
const appData = await apiClient.getUserInfo();
if (appData && appData.code === 0 && appData.data) {
// 转换数据格式以匹配新的数据结构
const userData = this.transformUserData(appData.data);
this.setData({ user: userData });
// app.globalData.userInfo = userData;
// wx.setStorageSync('userInfo', userData);
} else {
// 如果获取失败,使用默认数据
this.setData({ user: clearData });
this.updateGenderDisplay(clearData.gender);
}
} catch (error) {
console.error('获取用户信息失败:', error);
// 使用默认数据
this.setData({ user: clearData });
this.updateGenderDisplay(clearData.gender);
}
} catch (error) {
console.error('页面加载失败:', error);
}
},
onUnload() {
// 移除自动保存功能,改为点击保存按钮才保存
// 用户可以通过保存按钮主动保存,或者直接返回(不保存)
},
clearObjectValues(obj) {
Object.keys(obj).forEach(key => {
const value = obj[key];
if (Array.isArray(value)) {
// 清空为新数组
obj[key] = [];
} else {
const map = {
'object': {},
'string': '',
'number': 0,
'boolean': false,
}
const type = typeof value;
obj[key] = map[type] ?? null;
}
});
return obj
},
/* ---------- 头像 -------------- */
async chooseAvatar() {
try {
// 选择图片
const res = await new Promise((resolve, reject) => {
wx.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: resolve,
fail: reject
});
});
if (!res.tempFilePaths || res.tempFilePaths.length === 0) {
return;
}
const filePath = res.tempFilePaths[0];
// 显示上传中提示
wx.showLoading({ title: '上传中...', mask: true });
// 使用 COS 上传头像
// const uploadResult = await cosManager.upload(filePath, {
// fileType: 'avatar',
// enableDedup: false, // 启用去重,节省存储空间
// onProgress: (percent) => {
// console.log('上传进度:', percent + '%');
// }
// });
// 使用 COS 上传头像
const uploadResult = await cosManager.upload(filePath, {
fileType: 'avatar',
enableDedup: true, // 启用去重,节省存储空间
onProgress: (percent) => {
console.log('上传进度:', percent + '%');
},
enableCompress: false,
compressOptions: {
maxWidth: 400,
maxHeight: 400,
targetSize: 30 * 1024
}
});
console.log('上传结果:', uploadResult);
// 检查上传结果
if (!uploadResult) {
throw new Error('上传失败:未获取到上传结果');
}
if (uploadResult.success !== true) {
const errorMsg = uploadResult?.error || uploadResult?.message || '上传失败';
console.error('上传失败:', errorMsg, uploadResult);
throw new Error(errorMsg);
}
// 获取上传后的文件URL
const avatarUrl = uploadResult.fileUrl || uploadResult.originalUrl;
if (!avatarUrl) {
console.error('上传返回数据:', uploadResult);
throw new Error('上传失败未获取到文件URL');
}
// 更新本地头像
const user = Object.assign({}, this.data.user, { avatar: avatarUrl });
this.setData({ user });
// app.globalData.userInfo.user= {
// ...app.globalData.userInfo.user,
// avatar:avatarUrl
// };
// 立即同步头像到 NIM
try {
await nimUserManager.updateSelfUserInfo({
avatar: avatarUrl
});
console.log('✅ 头像已同步到 NIM');
} catch (nimError) {
console.error('⚠️ 同步头像到 NIM 失败:', nimError);
// 不影响主流程,静默失败
}
// 立即同步头像到业务后端
try {
await apiClient.updateUserProfile({ avatar: avatarUrl });
console.log('✅ 头像已同步到后端');
} catch (apiError) {
console.error('⚠️ 同步头像到后端失败:', apiError);
// 不影响主流程,静默失败
}
// 通知 profile 页面更新头像
try {
const pages = getCurrentPages();
const profilePage = pages[pages.length - 2];
if (profilePage) {
profilePage.onLoad();
}
} catch (error) {
console.error('通知 profile 页面更新头像失败:', error);
// 不影响主流程,静默失败
}
wx.hideLoading();
wx.showToast({
title: '上传成功',
icon: 'success',
duration: 2000
});
} catch (error) {
wx.hideLoading();
console.error('上传头像失败:', error);
wx.showToast({
title: error.message || '上传失败,请重试',
icon: 'none',
duration: 2000
});
}
},
// chooseImage(useCamera) {
// const self = this;
// wx.chooseImage({
// count: 1,
// sizeType: ['compressed'],
// sourceType: useCamera ? ['camera'] : ['album'],
// success(res) {
// const tempPath = res.tempFilePaths[0];
// const user = Object.assign({}, self.data.user, { avatar: tempPath });
// self.setData({ user });
// wx.setStorageSync('user_profile_v1', user);
// }
// });
// },
// 昵称输入
onNicknameInput(e) {
const user = Object.assign({}, this.data.user, { nickname: e.detail.value });
this.setData({ user });
},
/* ---------- 个人简介 ---------- */
onIntroInput(e) {
// 直接更新 user.bio
const user = Object.assign({}, this.data.user, { bio: e.detail.value });
this.setData({ user });
},
/* ---------- 种族 ---------- */
onEthnicityInput(e) {
const user = Object.assign({}, this.data.user, { ethnicity: e.detail.value });
this.setData({ user });
},
/* ---------- 毕业院校 ---------- */
onSchoolInput(e) {
const user = Object.assign({}, this.data.user, { school: e.detail.value });
this.setData({ user });
},
/* ---------- bottom sheet 通用打开 ---------- */
openSheet(e) {
// data-options 会以字符串形式传入,如果直接传对象需要自定义组件
// 这里我们支持两种写法:如果元素上有 data-options 属性且为 JSON 字符串则尝试解析注意wxml 中直接传数组会被字符串化)
const dataset = e.currentTarget.dataset || {};
const key = dataset.key;
const title = dataset.title || '请选择';
let options = dataset.options;
// dataset.options 在 wxml 传递数组时通常会变成字符串 "[object Object]" —— 我们优先根据 key 在定义好的数组中取
if (!options || typeof options === 'string') {
const map = {
occupation: occupationOptions,
school: schoolOptions,
gender: genderOptions,
zodiacSign: zodiacSignOptions,
mbtiType: mbtiTypeOptions,
sleepHabit: sleepHabitOptions,
socialActivity: socialActivityOptions,
height: heightOptions,
}
options = map[key] || [];
}
// 获取当前选中值,如果是性别需要转换为文字显示
let selectedValue = "";
if (key === 'gender') {
const genderNum = this.data.user[key];
if (typeof genderNum === 'number' && genderReverseMap[genderNum] !== undefined) {
selectedValue = genderReverseMap[genderNum];
} else if (typeof genderNum === 'string') {
selectedValue = genderNum;
}
} else {
selectedValue = this.data.user[key] || "";
}
this.setData({
showSheet: true,
sheetOptions: options,
sheetTitle: title,
sheetKey: key,
selectedValue
});
},
closeSheet() {
this.setData({ showSheet: false, sheetOptions: [], sheetTitle: "", sheetKey: "", selectedValue: "" });
},
onSheetSelect(e) {
const idx = e.currentTarget.dataset.index;
const val = this.data.sheetOptions[idx];
const key = this.data.sheetKey;
if (!key) return this.closeSheet();
const user = Object.assign({}, this.data.user);
// 如果是性别选择,需要转换为数字
if (key === 'gender') {
user[key] = genderMap[val] !== undefined ? genderMap[val] : 0;
// 更新性别显示文字
this.updateGenderDisplay(user[key]);
} else {
user[key] = val;
}
this.setData({ user, selectedValue: val }, () => {
this.closeSheet();
});
},
/* ---------- 生日 ---------- */
onBirthdayChange(e) {
const val = e.detail.value;
const user = Object.assign({}, this.data.user, { birthday: val });
// 更新星座(使用公共方法)
user.zodiacSign = calculateConstellationFromBirthday(val);
this.setData({ user });
},
/* ---------- 家乡 ---------- */
onHometownChange(e) {
const nick = e.detail?.value ?? this.data.user.hometown;
const user = Object.assign({}, this.data.user, { hometown: nick });
this.setData({ user });
// 更新用户信息
// apiClient.updateUserProfile(user)
// .then(res => {
// console.log('更新用户信息成功', res);
// wx.showToast({ title: '更新用户信息成功', icon: 'success' });
// this.setData({ user });
// })
// .catch(err => {
// console.error('更新用户信息失败', err);
// wx.showToast({ title: '更新用户信息失败', icon: 'none' });
// });
},
/* ---------- 下拉刷新 ---------- */
onRefresh() {
this.setData({ refreshing: true });
// 重新加载用户信息
setTimeout(async () => {
try {
const clearData = this.clearObjectValues(this.data.user);
const appData = await apiClient.getUserInfo();
if (appData && appData.code === 0 && appData.data) {
// 转换数据格式以匹配新的数据结构
const userData = this.transformUserData(appData.data);
this.setData({ user: userData });
} else {
this.setData({ user: clearData });
this.updateGenderDisplay(clearData.gender);
}
} catch (error) {
console.error('刷新用户信息失败:', error);
} finally {
this.setData({ refreshing: false });
}
}, 500);
},
/* ---------- 保存按钮 ---------- */
async handleSave() {
try {
// 延迟一下,确保失焦完成
await new Promise(resolve => setTimeout(resolve, 100));
wx.showLoading({ title: '保存中...', mask: true });
// 更新用户信息
const user = Object.assign({}, this.data.user);
// 确保性别是数字格式0=未知 1=男 2=女 3=其他)
if (typeof user.gender === 'string') {
user.gender = genderMap[user.gender] !== undefined ? genderMap[user.gender] : 0;
} else if (typeof user.gender !== 'number') {
user.gender = 0;
}
// 确保身高是数字
if (typeof user.height === 'string') {
user.height = parseInt(user.height) || 0;
}
if (user.height === 0) {//==0提交会报错 所以删除
delete user.height;
}
const res = await apiClient.updateUserProfile(user);
// 同步昵称和头像到 NIM
if (user.nickname) {
try {
await nimUserManager.updateSelfUserInfo({
nickname: user.nickname,
avatar: user.avatar || undefined
});
console.log('✅ 用户资料已同步到 NIM');
} catch (nimError) {
console.error('⚠️ 同步用户资料到 NIM 失败:', nimError);
// 不影响主流程,静默失败
}
}
wx.hideLoading();
wx.showToast({
title: '保存成功',
icon: 'success',
duration: 2000
});
// 刷新全局用户信息缓存
try {
await apiClient.refreshUserInfoCache();
} catch (error) {
console.error('刷新全局用户信息缓存失败:', error);
}
// 更新全局用户信息缓存
try {
const app = getApp();
if (app.globalData.userInfo) {
// 更新全局用户信息,包括所有字段
const globalUserInfo = app.globalData.userInfo;
if (globalUserInfo.user) {
// 更新所有字段到全局用户信息(使用新字段名)
Object.assign(globalUserInfo.user, {
nickname: user.nickname,
avatar: user.avatar,
bio: user.bio,
gender: user.gender,
birthday: user.birthday,
ethnicity: user.ethnicity,
occupation: user.occupation,
school: user.school,
hometown: user.hometown,
zodiacSign: user.zodiacSign,
height: user.height,
mbtiType: user.mbtiType,
sleepHabit: user.sleepHabit,
socialActivity: user.socialActivity
});
}
}
} catch (error) {
console.error('更新全局用户信息失败:', error);
}
// 通知 profile 页面更新用户信息
try {
const pages = getCurrentPages();
// 查找 profile 页面(上一个页面)
const profilePage = pages[pages.length - 2];
if (profilePage) {
// 重新加载用户数据(会从全局缓存读取最新数据)
if (typeof profilePage.loadUserData === 'function') {
profilePage.loadUserData();
}
// 直接更新 profile 页面的 userInfo包括所有字段
const profileUserInfo = profilePage.data.userInfo || {};
if (!profileUserInfo.user) {
profileUserInfo.user = {};
}
// 重新计算年龄(根据生日)
let calculatedAge = null;
if (user.birthday && typeof profilePage.calculateAge === 'function') {
calculatedAge = profilePage.calculateAge(user.birthday);
}
// 更新所有字段
const updateData = {
'userInfo.user.nickname': user.nickname,
'userInfo.user.avatar': user.avatar,
'userInfo.user.bio': user.bio,
'userInfo.user.gender': user.gender,
'userInfo.user.birthday': user.birthday,
'userInfo.user.ethnicity': user.ethnicity,
'userInfo.user.occupation': user.occupation,
'userInfo.user.school': user.school,
'userInfo.user.hometown': user.hometown,
'userInfo.user.zodiacSign': user.zodiacSign,
'userInfo.user.height': user.height,
'userInfo.user.mbtiType': user.mbtiType,
'userInfo.user.sleepHabit': user.sleepHabit,
'userInfo.user.socialActivity': user.socialActivity,
'userInfo.avatar': user.avatar,
'userInfo.avatarUrl': user.avatar,
'userInfo.nickname': user.nickname,
'userInfo.bio': user.bio,
'userInfo.gender': user.gender,
'userInfo.birthday': user.birthday,
'userInfo.ethnicity': user.ethnicity,
'userInfo.occupation': user.occupation,
'userInfo.school': user.school,
'userInfo.hometown': user.hometown,
'userInfo.zodiacSign': user.zodiacSign,
'userInfo.height': user.height,
'userInfo.mbtiType': user.mbtiType,
'userInfo.sleepHabit': user.sleepHabit,
'userInfo.socialActivity': user.socialActivity
};
// 如果有计算出的年龄,也更新
if (calculatedAge !== null) {
updateData.calculatedAge = calculatedAge;
}
profilePage.setData(updateData);
}
} catch (error) {
console.error('通知 profile 页面更新失败:', error);
// 不影响主流程,静默失败
}
// 保存成功后,延迟返回上一页
setTimeout(() => {
wx.navigateBack();
}, 1500);
} catch (err) {
wx.hideLoading();
console.error('保存用户信息失败', err);
wx.showToast({
title: err.message || '保存失败,请重试',
icon: 'none',
duration: 2000
});
}
}
});