yingsa/miniprogram/pages/user/index.js
ethanfly 02937ca33c feat(天梯): 新增选手定位功能并调整挑战赛权重
- 在小程序天梯排名页添加“定位我”按钮,点击可滚动到当前用户所在位置
- 新增获取用户排名接口 `/ladder/my-rank` 用于定位计算
- 调整挑战赛权重从 1.5 降至 1.0,与日常畅打保持一致
- 新增数据库脚本 `setChallengeMatchWeightTo1.js` 用于更新历史数据
- 在管理员界面创建天梯用户时,根据所选等级自动填充默认战力值
- 修复管理员更新比赛时挑战赛权重强制设置为 1.0 的问题
- 新增天梯汇总大屏页面及相关路由
- 添加大屏比赛列表接口 `/match/display-list` 用于展示进行中和近期比赛
- 优化用户详情页的胜负场和胜率显示逻辑
- 修复小程序用户注册时的性别选择逻辑
2026-02-02 03:22:36 +08:00

453 lines
12 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 : 0,
);
// 获取门店信息
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 : 0;
await app.phoneLogin(encryptedData, iv, g ? { gender: g } : null);
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"
onChooseAvatarNew(e) {
const avatarUrl = e.detail.avatarUrl;
this.setData({
"profileForm.avatar": avatarUrl,
});
},
// 输入昵称
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 {
// 如果选择了新头像,先上传
let avatarUrl = avatar;
if (
avatar &&
(avatar.startsWith("wxfile://") || avatar.startsWith("http://tmp"))
) {
avatarUrl = await this.uploadAvatar(avatar);
}
// 调用更新资料接口
const payload = {
nickname: nickname.trim(),
avatar: avatarUrl,
};
if (gender === 1 || gender === 2) {
payload.gender = gender;
}
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) => {
wx.uploadFile({
url: `${app.globalData.baseUrl}/api/upload/avatar`,
filePath: filePath,
name: "file",
header: {
Authorization: `Bearer ${app.globalData.token}`,
},
success: (res) => {
try {
const data = JSON.parse(res.data);
if (data.code === 0 && data.data && data.data.url) {
resolve(data.data.url);
} else {
console.error("上传头像失败:", data);
resolve(filePath);
}
} catch (e) {
resolve(filePath);
}
},
fail: (err) => {
console.error("上传头像失败:", err);
resolve(filePath);
},
});
});
},
// 关闭资料弹框
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() {
// 空函数,仅用于阻止事件冒泡
},
});