yingsa/miniprogram/pages/user/index.js
ethanfly 1a530ebf02 feat: Implement default store retrieval for new users and enhance login flow
- Added a new method to fetch the default store for users without a login, allowing them to browse store rankings.
- Updated the wxLogin function to streamline the login process for users with existing phone numbers, enabling direct token retrieval.
- Refactored various page components to utilize the new getDefaultStore method for better user experience when accessing store information.
- Enhanced error handling and data synchronization for user and store information across multiple pages.
2026-02-07 14:04:31 +08:00

595 lines
17 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();
const util = require("../../utils/util");
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,
// 活动公告(最近 10 条)
announcements: [],
},
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,
});
this.fetchAnnouncements();
}
},
async onPullDownRefresh() {
try {
await this.refreshData();
} finally {
wx.stopPullDownRefresh();
}
},
async initData() {
// 微信登录:已有手机号用户会直接完成登录,无手机号用户需授权手机号
if (!app.globalData.wxLoginInfo) {
try {
await app.wxLogin();
} catch (e) {
console.error("微信登录失败:", e);
}
}
if (app.globalData.token) {
await this.refreshData();
} else {
// 无 token 时同步当前状态(如 wxLoginInfo到页面用于判断是否显示手机号登录
this.setData({
userInfo: app.globalData.userInfo,
ladderUser: null,
currentStore: app.globalData.currentStore,
});
}
},
async refreshData() {
if (!app.globalData.token) return;
try {
const store = app.globalData.currentStore;
// 传入当前门店 ID获取该门店的积分和天梯信息
await app.getUserInfo(store?.storeId);
// 有当前门店时,拉取该门店的天梯用户信息(确保战力数据与当前门店一致)
if (store && store.storeId) {
try {
await app.getLadderUser(store.storeId);
} catch (e) {
console.error("获取天梯用户信息失败:", e);
}
}
this.setData({
userInfo: app.globalData.userInfo,
ladderUser: this.normalizeLadderUser(app.globalData.ladderUser),
currentStore: app.globalData.currentStore,
announcements: [], // 先清空,避免切换门店时短暂显示旧门店公告
});
await this.fetchAnnouncements();
} catch (e) {
console.error("获取用户信息失败:", e);
}
},
// 获取当前门店的活动公告(最近 10 条)
async fetchAnnouncements() {
if (!app.globalData.token) return;
const store = app.globalData.currentStore;
if (!store || !store.storeId) return;
try {
const res = await app.request("/api/article", {
limit: 10,
store_id: store.storeId,
});
const raw = res.data || [];
const list = raw.map((a) => ({
id: a.id,
title: a.title || "",
isTop: !!a.isTop,
timeText: a.createdAt ? util.formatDate(a.createdAt) : "",
}));
this.setData({ announcements: list });
} 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.ensureCurrentStore();
const userInfo = app.globalData.userInfo;
this.setData({
userInfo: userInfo,
ladderUser: this.normalizeLadderUser(app.globalData.ladderUser),
currentStore: app.globalData.currentStore,
});
await this.fetchAnnouncements();
// 检查是否需要完善资料(没有头像或昵称为默认值)
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.ensureCurrentStore();
const userInfo = app.globalData.userInfo;
this.setData({
userInfo: userInfo,
ladderUser: this.normalizeLadderUser(app.globalData.ladderUser),
currentStore: app.globalData.currentStore,
});
await this.fetchAnnouncements();
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();
}
},
// 查看更多活动公告
goAnnouncementMore() {
if (!app.globalData.token) {
wx.showToast({ title: "请先登录", icon: "none" });
return;
}
wx.navigateTo({
url: "/pages/article/list/index",
});
},
// 查看公告详情
goAnnouncementDetail(e) {
const id = e.currentTarget.dataset.id;
if (!id) return;
wx.navigateTo({
url: `/pages/article/detail/index?id=${id}`,
});
},
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() {
// 空函数,仅用于阻止事件冒泡
},
});