yingsa/miniprogram/pages/user/index.js
Ethanfly 2ee017bccf feat(branding): Update logo and favicon assets across all platforms
- Replace favicon.svg in admin panel with new brand logo design
- Add logo.png and logo.svg files to dedicated logo directory
- Update miniprogram with new logo assets (logo.png and logo.svg)
- Add README-UPLOAD.md documentation for miniprogram upload functionality
- Update miniprogram app.js and config.js for logo integration
- Update miniprogram user page (pages/user/index.js) to use new branding
- Update server upload route to handle new logo assets
- Add test-upload.js for upload functionality testing
- Update server .env configuration for asset handling
- Standardize brand visual identity across admin, miniprogram, and server platforms
2026-02-03 17:26:02 +08:00

537 lines
15 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 app = getApp();
Page({
data: {
userInfo: null,
ladderUser: null,
currentStore: null,
showQrcode: false,
qrcodeImage: "",
qrcodeLoading: false,
// 完善资料弹框
showProfileModal: false,
profileForm: {
avatar: "",
nickname: "",
gender: 0,
},
isEditMode: false, // true: 编辑模式false: 完善模式(登录时)
showGenderModal: false,
registerGender: 0,
},
normalizeLadderUser(ladderUser) {
if (!ladderUser) return null;
const matchCount = Number(ladderUser.matchCount || 0);
const winCount = Number(ladderUser.winCount || 0);
const loseCount = Math.max(matchCount - winCount, 0);
const winRate = matchCount > 0 ? Math.round((winCount / matchCount) * 100) : 0;
return Object.assign({}, ladderUser, {
matchCount,
winCount,
loseCount,
winRate,
});
},
onLoad() {
this.initData();
},
onShow() {
// 检查门店是否切换
if (app.globalData.storeChanged) {
app.globalData.storeChanged = false;
this.refreshData();
} else {
// 同步最新数据
this.setData({
userInfo: app.globalData.userInfo,
ladderUser: this.normalizeLadderUser(app.globalData.ladderUser),
currentStore: app.globalData.currentStore,
});
}
},
async onPullDownRefresh() {
try {
await this.refreshData();
} finally {
wx.stopPullDownRefresh();
}
},
async initData() {
// 先进行微信登录获取openid
if (!app.globalData.wxLoginInfo) {
try {
await app.wxLogin();
} catch (e) {
console.error("微信登录失败:", e);
}
}
if (app.globalData.token) {
await this.refreshData();
}
},
async refreshData() {
if (!app.globalData.token) return;
try {
await app.getUserInfo();
// 如果当前门店有 ladderUserId确保获取该门店的天梯用户信息
if (
app.globalData.currentStore &&
app.globalData.currentStore.storeId &&
!app.globalData.ladderUser
) {
try {
await app.getLadderUser(app.globalData.currentStore.storeId);
} catch (e) {
console.error("获取天梯用户信息失败:", e);
}
}
this.setData({
userInfo: app.globalData.userInfo,
ladderUser: this.normalizeLadderUser(app.globalData.ladderUser),
currentStore: app.globalData.currentStore,
});
} catch (e) {
console.error("获取用户信息失败:", e);
}
},
// 获取手机号授权
async onGetPhoneNumber(e) {
if (e.detail.errMsg !== "getPhoneNumber:ok") {
wx.showToast({ title: "需要授权手机号才能登录", icon: "none" });
return;
}
try {
wx.showLoading({ title: "登录中..." });
// 如果没有微信登录信息,先登录
if (!app.globalData.wxLoginInfo) {
await app.wxLogin();
}
const needGender =
app.globalData.wxLoginInfo && app.globalData.wxLoginInfo.isNewUser;
if (
needGender &&
!(this.data.registerGender === 1 || this.data.registerGender === 2)
) {
this._pendingPhoneLogin = {
encryptedData: e.detail.encryptedData,
iv: e.detail.iv,
};
wx.hideLoading();
this.setData({ showGenderModal: true });
return;
}
await this.doPhoneLogin(
e.detail.encryptedData,
e.detail.iv,
needGender ? this.data.registerGender : undefined,
);
// 获取门店信息
await app.getCurrentStore();
const userInfo = app.globalData.userInfo;
this.setData({
userInfo: userInfo,
ladderUser: this.normalizeLadderUser(app.globalData.ladderUser),
currentStore: app.globalData.currentStore,
});
// 检查是否需要完善资料(没有头像或昵称为默认值)
const needProfile =
!userInfo.avatar ||
userInfo.avatar === "" ||
!userInfo.nickname ||
userInfo.nickname === "新用户" ||
userInfo.nickname === "";
if (needProfile) {
// 弹出完善资料弹框
this.setData({
showProfileModal: true,
isEditMode: false,
profileForm: {
avatar: userInfo.avatar || "/images/avatar-default.svg",
nickname:
userInfo.nickname === "新用户" ? "" : userInfo.nickname || "",
},
});
wx.showToast({ title: "登录成功,请完善资料", icon: "none" });
} else {
wx.showToast({ title: "登录成功", icon: "success" });
}
} catch (e) {
console.error("登录失败:", e);
wx.showToast({ title: e.message || "登录失败", icon: "none" });
} finally {
wx.hideLoading();
}
},
async doPhoneLogin(encryptedData, iv, gender) {
const g = gender === 1 || gender === 2 ? gender : undefined;
await app.phoneLogin(encryptedData, iv, g);
this._pendingPhoneLogin = null;
this.setData({ showGenderModal: false });
},
onSelectRegisterGender(e) {
const gender = Number(e.currentTarget.dataset.gender);
if (gender !== 1 && gender !== 2) return;
this.setData({ registerGender: gender });
},
async onConfirmRegisterGender() {
if (!(this.data.registerGender === 1 || this.data.registerGender === 2)) {
wx.showToast({ title: "请选择性别", icon: "none" });
return;
}
const pending = this._pendingPhoneLogin;
if (!pending) {
this.setData({ showGenderModal: false });
return;
}
wx.showLoading({ title: "登录中..." });
try {
await this.doPhoneLogin(
pending.encryptedData,
pending.iv,
this.data.registerGender,
);
await app.getCurrentStore();
const userInfo = app.globalData.userInfo;
this.setData({
userInfo: userInfo,
ladderUser: this.normalizeLadderUser(app.globalData.ladderUser),
currentStore: app.globalData.currentStore,
});
const needProfile =
!userInfo.avatar ||
userInfo.avatar === "" ||
!userInfo.nickname ||
userInfo.nickname === "新用户" ||
userInfo.nickname === "";
if (needProfile) {
this.setData({
showProfileModal: true,
isEditMode: false,
profileForm: {
avatar: userInfo.avatar || "/images/avatar-default.svg",
nickname:
userInfo.nickname === "新用户" ? "" : userInfo.nickname || "",
gender: userInfo.gender || 0,
},
});
wx.showToast({ title: "登录成功,请完善资料", icon: "none" });
} else {
wx.showToast({ title: "登录成功", icon: "success" });
}
} catch (e) {
console.error("登录失败:", e);
wx.showToast({ title: e.message || "登录失败", icon: "none" });
} finally {
wx.hideLoading();
}
},
onCancelRegisterGender() {
this._pendingPhoneLogin = null;
this.setData({ showGenderModal: false, registerGender: 0 });
},
// 点击头像,打开编辑资料弹框
onTapAvatar() {
if (!this.data.userInfo || !this.data.userInfo.phone) return;
this.setData({
showProfileModal: true,
isEditMode: true,
profileForm: {
avatar: this.data.userInfo.avatar || "/images/avatar-default.svg",
nickname: this.data.userInfo.nickname || "",
gender: this.data.userInfo.gender || 0,
},
});
},
// 选择头像新APIbutton open-type="chooseAvatar"
async onChooseAvatarNew(e) {
const avatarUrl = e.detail.avatarUrl;
console.log('选择头像:', avatarUrl);
// 检查是否是临时文件
if (avatarUrl && (avatarUrl.startsWith("wxfile://") ||
avatarUrl.startsWith("http://tmp") ||
avatarUrl.includes("/tmp/"))) {
console.log('检测到临时头像,立即上传');
wx.showLoading({ title: "上传头像中..." });
try {
const uploadedUrl = await this.uploadAvatar(avatarUrl);
console.log('头像上传成功:', uploadedUrl);
this.setData({
"profileForm.avatar": uploadedUrl,
});
wx.hideLoading();
wx.showToast({ title: "头像上传成功", icon: "success", duration: 1500 });
} catch (uploadErr) {
wx.hideLoading();
console.error("头像上传失败:", uploadErr);
wx.showToast({
title: uploadErr.message || "头像上传失败",
icon: "none",
duration: 2000
});
// 上传失败,不设置头像
}
} else {
// 不是临时文件,直接使用
this.setData({
"profileForm.avatar": avatarUrl,
});
}
},
// 选择头像(使用 wx.chooseMedia
onChooseAvatar() {
wx.chooseMedia({
count: 1,
mediaType: ['image'],
sourceType: ['album', 'camera'],
sizeType: ['compressed'],
success: (res) => {
console.log('选择图片成功:', res);
const tempFilePath = res.tempFiles[0].tempFilePath;
this.setData({
"profileForm.avatar": tempFilePath,
});
},
fail: (err) => {
console.error('选择图片失败:', err);
// 如果 chooseMedia 不支持,降级使用 chooseImage
wx.chooseImage({
count: 1,
sourceType: ['album', 'camera'],
sizeType: ['compressed'],
success: (res) => {
console.log('选择图片成功(降级):', res);
const tempFilePath = res.tempFilePaths[0];
this.setData({
"profileForm.avatar": tempFilePath,
});
},
fail: (err) => {
console.error('选择图片失败:', err);
wx.showToast({ title: '选择图片失败', icon: 'none' });
}
});
}
});
},
// 输入昵称
onNicknameInput(e) {
this.setData({
"profileForm.nickname": e.detail.value,
});
},
onProfileGenderSelect(e) {
const gender = Number(e.currentTarget.dataset.gender);
if (gender !== 1 && gender !== 2) return;
this.setData({
"profileForm.gender": gender,
});
},
// 确认保存资料
async saveProfile() {
const { avatar, nickname, gender } = this.data.profileForm;
if (!nickname || nickname.trim() === "") {
wx.showToast({ title: "请输入昵称", icon: "none" });
return;
}
wx.showLoading({ title: "保存中..." });
try {
// 头像已经在选择时上传,这里直接使用
const avatarUrl = avatar;
// 调用更新资料接口
const payload = {
nickname: nickname.trim(),
avatar: avatarUrl,
};
if (gender === 1 || gender === 2) {
payload.gender = gender;
}
console.log("保存资料请求:", payload);
const res = await app.request("/api/user/profile", payload, "PUT");
// 更新本地数据服务端已返回完整URL
const userInfo = Object.assign({}, this.data.userInfo, {
nickname: (res.data && res.data.nickname) || nickname.trim(),
avatar: (res.data && res.data.avatar) || avatarUrl,
gender: (res.data && res.data.gender) || this.data.userInfo.gender || 0,
});
app.globalData.userInfo = userInfo;
this.setData({
userInfo: userInfo,
showProfileModal: false,
profileForm: { avatar: "", nickname: "", gender: 0 },
});
wx.hideLoading();
wx.showToast({ title: "保存成功", icon: "success" });
} catch (e) {
wx.hideLoading();
console.error("保存资料失败:", e);
wx.showToast({ title: e.message || "保存失败", icon: "none" });
}
},
// 上传头像
async uploadAvatar(filePath) {
return new Promise((resolve, reject) => {
console.log('开始上传头像');
console.log('文件路径:', filePath);
console.log('上传URL:', `${app.globalData.baseUrl}/api/upload/avatar`);
console.log('Token:', app.globalData.token);
if (!app.globalData.token) {
reject(new Error('未登录,无法上传头像'));
return;
}
wx.uploadFile({
url: `${app.globalData.baseUrl}/api/upload/avatar`,
filePath: filePath,
name: "file",
header: {
Authorization: `Bearer ${app.globalData.token}`,
},
success: (res) => {
try {
console.log("上传头像响应状态:", res.statusCode);
console.log("上传头像响应数据:", res.data);
if (res.statusCode !== 200) {
reject(new Error(`上传失败,状态码: ${res.statusCode}`));
return;
}
const data = JSON.parse(res.data);
if (data.code === 0 && data.data && data.data.url) {
console.log("头像上传成功:", data.data.url);
resolve(data.data.url);
} else {
console.error("上传头像失败:", data);
reject(new Error(data.message || "上传头像失败"));
}
} catch (e) {
console.error("解析上传响应失败:", e);
reject(new Error("上传头像失败"));
}
},
fail: (err) => {
console.error("上传头像请求失败:", err);
reject(new Error("上传头像失败,请检查网络"));
},
});
});
},
// 关闭资料弹框
closeProfileModal() {
// 如果是完善模式,提示用户
if (!this.data.isEditMode) {
wx.showModal({
title: "提示",
content: "完善资料后可以让好友更容易找到你,确定跳过?",
confirmText: "跳过",
cancelText: "继续完善",
success: (res) => {
if (res.confirm) {
this.setData({ showProfileModal: false });
}
},
});
} else {
this.setData({ showProfileModal: false });
}
},
async showMemberCode() {
if (!this.data.userInfo || !this.data.userInfo.memberCode) return;
this.setData({
showQrcode: true,
qrcodeLoading: true,
});
try {
// 调用接口获取二维码
const res = await app.request("/api/user/qrcode");
if (res.data && res.data.qrcode) {
this.setData({
qrcodeImage: res.data.qrcode,
qrcodeLoading: false,
});
}
} catch (e) {
console.error("获取二维码失败:", e);
this.setData({ qrcodeLoading: false });
wx.showToast({ title: "获取二维码失败", icon: "none" });
}
},
hideQrcode() {
this.setData({
showQrcode: false,
qrcodeImage: "",
});
},
goTo(e) {
const url = e.currentTarget.dataset.url;
if (!app.globalData.token) {
wx.showToast({ title: "请先登录", icon: "none" });
return;
}
wx.navigateTo({ url });
},
// 阻止事件冒泡
preventBubble() {
// 空函数,仅用于阻止事件冒泡
},
});