- 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.
617 lines
21 KiB
JavaScript
617 lines
21 KiB
JavaScript
const config = require("./config");
|
||
|
||
App({
|
||
globalData: {
|
||
userInfo: null,
|
||
token: null,
|
||
currentStore: null,
|
||
ladderUser: null,
|
||
wsConnected: false,
|
||
// 微信登录临时信息
|
||
wxLoginInfo: null,
|
||
// 从配置文件读取
|
||
baseUrl: config.baseUrl,
|
||
wsUrl: config.wsUrl,
|
||
},
|
||
|
||
onLaunch() {
|
||
// 从本地存储读取 token 和上次选择的门店
|
||
const token = wx.getStorageSync("token");
|
||
if (token) {
|
||
this.globalData.token = token;
|
||
this.ensureCurrentStore()
|
||
.then(() => this.getUserInfo(this.globalData.currentStore?.storeId))
|
||
.catch(() => {});
|
||
}
|
||
},
|
||
|
||
// 微信登录(第一步:获取openid和session_key)
|
||
// 已有手机号用户会直接返回 token,完成登录;无手机号用户需后续授权手机号
|
||
wxLogin() {
|
||
return new Promise((resolve, reject) => {
|
||
wx.login({
|
||
success: (res) => {
|
||
wx.request({
|
||
url: `${this.globalData.baseUrl}/api/user/login`,
|
||
method: "POST",
|
||
data: { code: res.code },
|
||
success: async (loginRes) => {
|
||
if (loginRes.data.code === 0) {
|
||
const data = loginRes.data.data;
|
||
this.globalData.wxLoginInfo = {
|
||
openid: data.openid,
|
||
unionid: data.unionid,
|
||
sessionKey: data.sessionKey,
|
||
isNewUser: data.isNewUser,
|
||
hasPhone: data.hasPhone,
|
||
};
|
||
|
||
if (data.token && data.userInfo && data.hasPhone) {
|
||
// 已有手机号的老用户:直接登录,无需授权手机号
|
||
this.globalData.token = data.token;
|
||
this.globalData.userInfo = data.userInfo;
|
||
wx.setStorageSync("token", data.token);
|
||
this.connectWebSocket();
|
||
try {
|
||
await this.ensureCurrentStore();
|
||
await this.getUserInfo(this.globalData.currentStore?.storeId);
|
||
} catch (e) {
|
||
console.error("初始化门店/用户信息失败:", e);
|
||
}
|
||
} else if (data.userInfo && data.hasPhone) {
|
||
// 兼容:若后端返回 userInfo 但未带 token(旧版)
|
||
this.globalData.userInfo = data.userInfo;
|
||
}
|
||
|
||
resolve(data);
|
||
} else {
|
||
reject(loginRes.data);
|
||
}
|
||
},
|
||
fail: reject,
|
||
});
|
||
},
|
||
fail: reject,
|
||
});
|
||
});
|
||
},
|
||
|
||
// 手机号授权登录(第二步:解密手机号完成注册/登录)
|
||
phoneLogin(encryptedData, iv, gender) {
|
||
return new Promise((resolve, reject) => {
|
||
const wxInfo = this.globalData.wxLoginInfo;
|
||
if (!wxInfo) {
|
||
reject({ message: "请先进行微信登录" });
|
||
return;
|
||
}
|
||
|
||
const requestData = {
|
||
openid: wxInfo.openid,
|
||
unionid: wxInfo.unionid,
|
||
sessionKey: wxInfo.sessionKey,
|
||
encryptedData,
|
||
iv,
|
||
};
|
||
|
||
// 如果提供了性别参数,添加到请求中
|
||
if (gender === 1 || gender === 2) {
|
||
requestData.gender = gender;
|
||
}
|
||
|
||
wx.request({
|
||
url: `${this.globalData.baseUrl}/api/user/phone-login`,
|
||
method: "POST",
|
||
data: requestData,
|
||
success: (loginRes) => {
|
||
if (loginRes.data.code === 0) {
|
||
this.globalData.token = loginRes.data.data.token;
|
||
this.globalData.userInfo = loginRes.data.data.userInfo;
|
||
|
||
// 处理天梯用户信息(战力按当前门店,当前门店无天梯则不显示)
|
||
if (
|
||
loginRes.data.data.userInfo.ladderUsers &&
|
||
loginRes.data.data.userInfo.ladderUsers.length > 0
|
||
) {
|
||
if (
|
||
this.globalData.currentStore &&
|
||
this.globalData.currentStore.storeId
|
||
) {
|
||
const currentStoreLadderUser =
|
||
loginRes.data.data.userInfo.ladderUsers.find(
|
||
(lu) => lu.storeId === this.globalData.currentStore.storeId,
|
||
);
|
||
// 仅当当前门店有天梯用户时才显示,否则为 null,避免显示其他门店数据
|
||
this.globalData.ladderUser = currentStoreLadderUser || null;
|
||
} else {
|
||
this.globalData.ladderUser = null;
|
||
}
|
||
} else {
|
||
this.globalData.ladderUser = null;
|
||
}
|
||
|
||
wx.setStorageSync("token", loginRes.data.data.token);
|
||
this.connectWebSocket();
|
||
resolve(loginRes.data.data);
|
||
} else {
|
||
reject(loginRes.data);
|
||
}
|
||
},
|
||
fail: reject,
|
||
});
|
||
});
|
||
},
|
||
|
||
// 旧的登录方法(兼容)
|
||
login() {
|
||
return this.wxLogin();
|
||
},
|
||
|
||
// 获取用户信息(storeId 可选,传入时返回该门店的积分 storePoints)
|
||
getUserInfo(storeId) {
|
||
return new Promise((resolve, reject) => {
|
||
const data = storeId ? { store_id: storeId } : {};
|
||
this.request("/api/user/info", data)
|
||
.then((res) => {
|
||
this.globalData.userInfo = res.data;
|
||
|
||
// 处理天梯用户信息(战力按当前门店,当前门店无天梯则不显示)
|
||
if (res.data.ladderUsers && res.data.ladderUsers.length > 0) {
|
||
if (
|
||
this.globalData.currentStore &&
|
||
this.globalData.currentStore.storeId
|
||
) {
|
||
const currentStoreLadderUser = res.data.ladderUsers.find(
|
||
(lu) => lu.storeId === this.globalData.currentStore.storeId,
|
||
);
|
||
// 仅当当前门店有天梯用户时才显示,否则为 null,避免显示其他门店数据
|
||
this.globalData.ladderUser = currentStoreLadderUser || null;
|
||
} else {
|
||
this.globalData.ladderUser = null;
|
||
}
|
||
} else {
|
||
this.globalData.ladderUser = null;
|
||
}
|
||
|
||
this.connectWebSocket();
|
||
resolve(res.data);
|
||
})
|
||
.catch(reject);
|
||
});
|
||
},
|
||
|
||
/**
|
||
* 获取默认门店(无需登录,供新用户浏览)
|
||
*/
|
||
getDefaultStore() {
|
||
return new Promise((resolve) => {
|
||
const lastStore = wx.getStorageSync("last_store");
|
||
const params = {};
|
||
if (lastStore?.storeId) params.last_store_id = lastStore.storeId;
|
||
|
||
wx.getLocation({
|
||
type: "gcj02",
|
||
success: (loc) => {
|
||
params.latitude = loc.latitude;
|
||
params.longitude = loc.longitude;
|
||
this.request("/api/store/default", params)
|
||
.then((res) => {
|
||
if (res.data) {
|
||
this.globalData.currentStore = {
|
||
storeId: res.data.storeId,
|
||
storeName: res.data.storeName || "",
|
||
storeAddress: res.data.storeAddress || "",
|
||
};
|
||
}
|
||
resolve(this.globalData.currentStore);
|
||
})
|
||
.catch(() => resolve(null));
|
||
},
|
||
fail: () => {
|
||
this.request("/api/store/default", params)
|
||
.then((res) => {
|
||
if (res.data) {
|
||
this.globalData.currentStore = {
|
||
storeId: res.data.storeId,
|
||
storeName: res.data.storeName || "",
|
||
storeAddress: res.data.storeAddress || "",
|
||
};
|
||
}
|
||
resolve(this.globalData.currentStore);
|
||
})
|
||
.catch(() => resolve(null));
|
||
},
|
||
});
|
||
});
|
||
},
|
||
|
||
/**
|
||
* 优先使用上次选择的门店,没有或失败时再请求最近门店(新用户逻辑)
|
||
*/
|
||
ensureCurrentStore() {
|
||
const lastStore = wx.getStorageSync("last_store");
|
||
if (lastStore && lastStore.storeId) {
|
||
this.globalData.currentStore = {
|
||
storeId: lastStore.storeId,
|
||
storeName: lastStore.storeName || "",
|
||
storeAddress: lastStore.storeAddress || "",
|
||
};
|
||
return this.getLadderUser(lastStore.storeId)
|
||
.then(() => this.globalData.currentStore)
|
||
.catch(() => {
|
||
wx.removeStorageSync("last_store");
|
||
return this.getCurrentStore();
|
||
});
|
||
}
|
||
return this.getCurrentStore();
|
||
},
|
||
|
||
// 获取当前门店(新用户或未选过门店时:按位置取最近门店)
|
||
getCurrentStore() {
|
||
return new Promise((resolve, reject) => {
|
||
wx.getLocation({
|
||
type: "gcj02",
|
||
success: (loc) => {
|
||
this.request("/api/user/current-store", {
|
||
latitude: loc.latitude,
|
||
longitude: loc.longitude,
|
||
})
|
||
.then((res) => {
|
||
this.globalData.currentStore = res.data;
|
||
|
||
if (res.data && res.data.ladderUserId) {
|
||
this.getLadderUser(res.data.storeId);
|
||
} else if (res.data && res.data.storeId) {
|
||
if (
|
||
this.globalData.userInfo &&
|
||
this.globalData.userInfo.ladderUsers
|
||
) {
|
||
const currentStoreLadderUser =
|
||
this.globalData.userInfo.ladderUsers.find(
|
||
(lu) => lu.storeId === res.data.storeId,
|
||
);
|
||
if (currentStoreLadderUser) {
|
||
this.globalData.ladderUser = currentStoreLadderUser;
|
||
} else {
|
||
this.globalData.ladderUser = null;
|
||
}
|
||
}
|
||
}
|
||
resolve(res.data);
|
||
})
|
||
.catch(reject);
|
||
},
|
||
fail: () => {
|
||
this.request("/api/user/current-store")
|
||
.then((res) => {
|
||
this.globalData.currentStore = res.data;
|
||
if (res.data && res.data.ladderUserId) {
|
||
this.getLadderUser(res.data.storeId);
|
||
} else if (res.data && res.data.storeId) {
|
||
if (
|
||
this.globalData.userInfo &&
|
||
this.globalData.userInfo.ladderUsers
|
||
) {
|
||
const currentStoreLadderUser =
|
||
this.globalData.userInfo.ladderUsers.find(
|
||
(lu) => lu.storeId === res.data.storeId,
|
||
);
|
||
if (currentStoreLadderUser) {
|
||
this.globalData.ladderUser = currentStoreLadderUser;
|
||
} else {
|
||
this.globalData.ladderUser = null;
|
||
}
|
||
}
|
||
}
|
||
resolve(res.data);
|
||
})
|
||
.catch(reject);
|
||
},
|
||
});
|
||
});
|
||
},
|
||
|
||
// 获取天梯用户信息(请求失败时 reject,便于上层做降级)
|
||
getLadderUser(storeId) {
|
||
return this.request("/api/user/ladder-info", { store_id: storeId }).then(
|
||
(res) => {
|
||
if (res.data && res.data.length > 0) {
|
||
this.globalData.ladderUser = res.data[0];
|
||
} else {
|
||
this.globalData.ladderUser = null;
|
||
}
|
||
return res.data;
|
||
},
|
||
);
|
||
},
|
||
|
||
// WebSocket连接
|
||
connectWebSocket() {
|
||
if (this.globalData.wsConnected || !this.globalData.token) return;
|
||
|
||
const wsUrl = this.globalData.wsUrl || "ws://localhost:3000/ws";
|
||
|
||
this.ws = wx.connectSocket({
|
||
url: wsUrl,
|
||
success: () => {
|
||
console.log("WebSocket连接中...");
|
||
},
|
||
});
|
||
|
||
wx.onSocketOpen(() => {
|
||
console.log("WebSocket已连接");
|
||
this.globalData.wsConnected = true;
|
||
// 发送认证
|
||
wx.sendSocketMessage({
|
||
data: JSON.stringify({
|
||
type: "auth",
|
||
token: this.globalData.token,
|
||
}),
|
||
});
|
||
});
|
||
|
||
wx.onSocketMessage((res) => {
|
||
const data = JSON.parse(res.data);
|
||
this.handleWsMessage(data);
|
||
});
|
||
|
||
wx.onSocketClose(() => {
|
||
console.log("WebSocket已断开");
|
||
this.globalData.wsConnected = false;
|
||
// 尝试重连
|
||
setTimeout(() => {
|
||
this.connectWebSocket();
|
||
}, 5000);
|
||
});
|
||
|
||
wx.onSocketError((err) => {
|
||
console.error("WebSocket错误:", err);
|
||
});
|
||
},
|
||
|
||
// 处理WebSocket消息
|
||
handleWsMessage(data) {
|
||
switch (data.type) {
|
||
case "challenge_request":
|
||
// 收到挑战请求 - 使用自定义弹框
|
||
const challengeData = data.data;
|
||
const pages = getCurrentPages();
|
||
const currentPage = pages[pages.length - 1];
|
||
|
||
// 如果当前页面有处理挑战请求的方法,调用它
|
||
if (
|
||
currentPage &&
|
||
typeof currentPage.handleChallengeRequest === "function"
|
||
) {
|
||
currentPage.handleChallengeRequest(challengeData);
|
||
} else {
|
||
// 否则使用系统弹框
|
||
wx.showModal({
|
||
title: "收到挑战",
|
||
content: `${challengeData.challenger.realName}(Lv${challengeData.challenger.level}, 战力${challengeData.challenger.powerScore}) 向你发起挑战`,
|
||
confirmText: "接受",
|
||
cancelText: "拒绝",
|
||
success: (res) => {
|
||
this.request(
|
||
"/api/match/challenge/respond",
|
||
{
|
||
match_id: challengeData.matchId,
|
||
accept: res.confirm,
|
||
},
|
||
"POST",
|
||
)
|
||
.then(() => {
|
||
if (res.confirm) {
|
||
wx.showToast({ title: "已接受挑战", icon: "success" });
|
||
// 跳转到挑战赛详情
|
||
setTimeout(() => {
|
||
wx.navigateTo({
|
||
url: `/pages/match/challenge-detail/index?id=${challengeData.matchId}`,
|
||
});
|
||
}, 1500);
|
||
} else {
|
||
wx.showToast({ title: "已拒绝挑战", icon: "success" });
|
||
}
|
||
})
|
||
.catch((err) => {
|
||
console.error("响应挑战失败:", err);
|
||
wx.showToast({ title: "操作失败", icon: "none" });
|
||
});
|
||
},
|
||
});
|
||
}
|
||
break;
|
||
case "challenge_accepted":
|
||
// 挑战被接受
|
||
wx.showToast({ title: "对方已接受挑战", icon: "success" });
|
||
// 如果当前在挑战赛详情页面,刷新数据
|
||
const pages2 = getCurrentPages();
|
||
const currentPage2 = pages2[pages2.length - 1];
|
||
if (
|
||
currentPage2 &&
|
||
currentPage2.route === "pages/match/challenge-detail/index"
|
||
) {
|
||
if (typeof currentPage2.loadMatchDetail === "function") {
|
||
currentPage2.loadMatchDetail();
|
||
}
|
||
}
|
||
break;
|
||
case "challenge_rejected":
|
||
// 挑战被拒绝
|
||
wx.showToast({ title: "对方已拒绝挑战", icon: "none" });
|
||
break;
|
||
case "score_confirm_request":
|
||
// 收到比分确认请求
|
||
wx.showModal({
|
||
title: "确认比分",
|
||
content: `比分: ${data.data.player1Score} : ${data.data.player2Score}`,
|
||
confirmText: "确认",
|
||
cancelText: "有争议",
|
||
success: (res) => {
|
||
this.request(
|
||
"/api/match/challenge/confirm-score",
|
||
{
|
||
game_id: data.data.gameId,
|
||
confirm: res.confirm,
|
||
},
|
||
"POST",
|
||
);
|
||
},
|
||
});
|
||
break;
|
||
case "match_paired":
|
||
// 排位赛匹配通知
|
||
try {
|
||
const payload = data.data || {};
|
||
const opponent = payload.opponent || {};
|
||
const matchCode = payload.matchCode;
|
||
|
||
wx.showModal({
|
||
title: "匹配成功",
|
||
content: `你的对手是: ${opponent.realName || "对手"}`,
|
||
showCancel: false,
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
// 检查当前页面
|
||
const pagesMatch = getCurrentPages();
|
||
const currentMatchPage = pagesMatch[pagesMatch.length - 1];
|
||
|
||
if (currentMatchPage && currentMatchPage.route === "pages/match/ranking/index") {
|
||
// 如果已在比赛详情页,检查是否是同一场比赛
|
||
const targetCode = matchCode;
|
||
if (
|
||
!targetCode ||
|
||
!currentMatchPage.data ||
|
||
currentMatchPage.data.matchCode === targetCode
|
||
) {
|
||
// 刷新详情页的所有接口信息
|
||
if (typeof currentMatchPage.fetchMatchDetail === "function") {
|
||
currentMatchPage.fetchMatchDetail();
|
||
} else if (typeof currentMatchPage.fetchCurrentGame === "function") {
|
||
currentMatchPage.fetchCurrentGame();
|
||
}
|
||
} else {
|
||
// 不同比赛,跳转到新的比赛详情页
|
||
wx.redirectTo({
|
||
url: `/pages/match/ranking/index?code=${targetCode}`
|
||
});
|
||
}
|
||
} else {
|
||
// 如果不在比赛详情页,自动跳转到比赛详情页
|
||
if (matchCode) {
|
||
wx.navigateTo({
|
||
url: `/pages/match/ranking/index?code=${matchCode}`
|
||
});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
});
|
||
} catch (e) {
|
||
console.error("处理 match_paired 消息失败:", e);
|
||
}
|
||
break;
|
||
case "ranking_game_updated":
|
||
// 排位赛比分/匹配更新:刷新当前排位赛详情页
|
||
try {
|
||
const pages3 = getCurrentPages();
|
||
const currentPage3 = pages3[pages3.length - 1];
|
||
if (currentPage3) {
|
||
if (currentPage3.route === "pages/match/ranking/index") {
|
||
const targetCode = data.data && data.data.matchCode;
|
||
if (
|
||
!targetCode ||
|
||
!currentPage3.data ||
|
||
currentPage3.data.matchCode === targetCode
|
||
) {
|
||
if (typeof currentPage3.fetchMatchDetail === "function") {
|
||
currentPage3.fetchMatchDetail();
|
||
}
|
||
}
|
||
} else if (currentPage3.route === "pages/match/challenge/index") {
|
||
// 比赛首页:比分确认后也刷新“当前比赛”卡片
|
||
if (typeof currentPage3.fetchOngoingMatches === "function") {
|
||
currentPage3.fetchOngoingMatches();
|
||
}
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.error("处理 ranking_game_updated 消息失败:", e);
|
||
}
|
||
break;
|
||
case "points_order_verified":
|
||
// 后台核销积分订单后,通知小程序刷新订单列表/详情
|
||
try {
|
||
wx.showToast({ title: "积分订单已核销", icon: "success" });
|
||
|
||
const pages = getCurrentPages();
|
||
const currentPage = pages[pages.length - 1];
|
||
const payload = data.data || {};
|
||
|
||
if (
|
||
currentPage &&
|
||
currentPage.route === "pages/points/order/index"
|
||
) {
|
||
// 如果页面实现了下拉刷新逻辑,优先复用
|
||
if (typeof currentPage.onPullDownRefresh === "function") {
|
||
currentPage.onPullDownRefresh();
|
||
} else if (typeof currentPage.fetchOrders === "function") {
|
||
// 兜底:重置分页并主动刷新
|
||
currentPage.setData({ page: 1, hasMore: true });
|
||
currentPage.fetchOrders();
|
||
}
|
||
|
||
// 精确刷新当前弹窗和列表中的该笔订单
|
||
if (
|
||
payload.orderId &&
|
||
typeof currentPage.refreshOrderById === "function"
|
||
) {
|
||
currentPage.refreshOrderById(payload.orderId);
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.error("处理 points_order_verified 消息失败:", e);
|
||
}
|
||
break;
|
||
}
|
||
},
|
||
|
||
// 封装请求
|
||
request(url, data = {}, method = "GET") {
|
||
return new Promise((resolve, reject) => {
|
||
const showErrorToast = (message) => {
|
||
const title =
|
||
(message && String(message).trim()) || "网络异常,请稍后重试";
|
||
wx.showToast({ title, icon: "none" });
|
||
};
|
||
|
||
wx.request({
|
||
url: `${this.globalData.baseUrl}${url}`,
|
||
method,
|
||
data,
|
||
header: {
|
||
Authorization: `Bearer ${this.globalData.token}`,
|
||
},
|
||
success: (res) => {
|
||
if (res.data.code === 0) {
|
||
resolve(res.data);
|
||
} else if (res.data.code === 401) {
|
||
// 登录过期
|
||
this.globalData.token = null;
|
||
wx.removeStorageSync("token");
|
||
showErrorToast("登录已过期,请重新登录");
|
||
wx.reLaunch({ url: "/pages/user/index" });
|
||
reject({ message: res.data.message || "登录已过期", ...res.data });
|
||
} else {
|
||
showErrorToast(res.data.message || "请求失败");
|
||
reject({ message: res.data.message || "请求失败", ...res.data });
|
||
}
|
||
},
|
||
fail: (err) => {
|
||
showErrorToast("网络异常,请检查网络后重试");
|
||
reject({ message: "网络异常,请检查网络后重试", err });
|
||
},
|
||
});
|
||
});
|
||
},
|
||
});
|