- 在小程序天梯排名页添加“定位我”按钮,点击可滚动到当前用户所在位置 - 新增获取用户排名接口 `/ladder/my-rank` 用于定位计算 - 调整挑战赛权重从 1.5 降至 1.0,与日常畅打保持一致 - 新增数据库脚本 `setChallengeMatchWeightTo1.js` 用于更新历史数据 - 在管理员界面创建天梯用户时,根据所选等级自动填充默认战力值 - 修复管理员更新比赛时挑战赛权重强制设置为 1.0 的问题 - 新增天梯汇总大屏页面及相关路由 - 添加大屏比赛列表接口 `/match/display-list` 用于展示进行中和近期比赛 - 优化用户详情页的胜负场和胜率显示逻辑 - 修复小程序用户注册时的性别选择逻辑
453 lines
12 KiB
JavaScript
453 lines
12 KiB
JavaScript
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,
|
||
},
|
||
});
|
||
},
|
||
|
||
// 选择头像(新API:button 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() {
|
||
// 空函数,仅用于阻止事件冒泡
|
||
},
|
||
});
|