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.
This commit is contained in:
parent
459fa02eb9
commit
1a530ebf02
@ -26,6 +26,7 @@ App({
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 微信登录(第一步:获取openid和session_key)
|
// 微信登录(第一步:获取openid和session_key)
|
||||||
|
// 已有手机号用户会直接返回 token,完成登录;无手机号用户需后续授权手机号
|
||||||
wxLogin() {
|
wxLogin() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
wx.login({
|
wx.login({
|
||||||
@ -34,10 +35,9 @@ App({
|
|||||||
url: `${this.globalData.baseUrl}/api/user/login`,
|
url: `${this.globalData.baseUrl}/api/user/login`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
data: { code: res.code },
|
data: { code: res.code },
|
||||||
success: (loginRes) => {
|
success: async (loginRes) => {
|
||||||
if (loginRes.data.code === 0) {
|
if (loginRes.data.code === 0) {
|
||||||
const data = loginRes.data.data;
|
const data = loginRes.data.data;
|
||||||
// 保存微信登录信息(用于后续手机号授权)
|
|
||||||
this.globalData.wxLoginInfo = {
|
this.globalData.wxLoginInfo = {
|
||||||
openid: data.openid,
|
openid: data.openid,
|
||||||
unionid: data.unionid,
|
unionid: data.unionid,
|
||||||
@ -46,9 +46,20 @@ App({
|
|||||||
hasPhone: data.hasPhone,
|
hasPhone: data.hasPhone,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 如果已有token(老用户),直接使用
|
if (data.token && data.userInfo && data.hasPhone) {
|
||||||
if (data.userInfo && data.hasPhone) {
|
// 已有手机号的老用户:直接登录,无需授权手机号
|
||||||
// 老用户已绑定手机号,生成token并登录
|
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;
|
this.globalData.userInfo = data.userInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,6 +179,51 @@ App({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取默认门店(无需登录,供新用户浏览)
|
||||||
|
*/
|
||||||
|
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));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 优先使用上次选择的门店,没有或失败时再请求最近门店(新用户逻辑)
|
* 优先使用上次选择的门店,没有或失败时再请求最近门店(新用户逻辑)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -52,23 +52,23 @@ Page({
|
|||||||
},
|
},
|
||||||
|
|
||||||
async initData() {
|
async initData() {
|
||||||
// 检查是否已登录(有 token)
|
// 获取当前门店(已登录用 ensureCurrentStore,未登录用 getDefaultStore 可浏览排名)
|
||||||
if (!app.globalData.token) {
|
|
||||||
// 未登录,跳转到用户页面进行登录
|
|
||||||
wx.switchTab({ url: "/pages/user/index" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取当前门店
|
|
||||||
try {
|
try {
|
||||||
|
if (app.globalData.token) {
|
||||||
const store = await app.ensureCurrentStore();
|
const store = await app.ensureCurrentStore();
|
||||||
this.setData({ currentStore: store });
|
this.setData({ currentStore: store });
|
||||||
|
} else {
|
||||||
|
const store = await app.getDefaultStore();
|
||||||
|
this.setData({ currentStore: store || { storeName: "请选择门店" } });
|
||||||
|
}
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("获取门店失败:", e);
|
console.error("获取门店失败:", e);
|
||||||
// 如果是认证失败,跳转到登录页
|
|
||||||
if (e.code === 401) {
|
if (e.code === 401) {
|
||||||
wx.switchTab({ url: "/pages/user/index" });
|
app.globalData.token = null;
|
||||||
|
const store = await app.getDefaultStore();
|
||||||
|
this.setData({ currentStore: store || { storeName: "请选择门店" } });
|
||||||
|
this.fetchData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -28,20 +28,16 @@ Page({
|
|||||||
},
|
},
|
||||||
|
|
||||||
async initData() {
|
async initData() {
|
||||||
// 检查是否已登录(有 token)
|
// 获取门店(已登录用 ensureCurrentStore,未登录用 getDefaultStore 可浏览)
|
||||||
if (!app.globalData.token) {
|
|
||||||
// 未登录,跳转到用户页面进行登录
|
|
||||||
wx.switchTab({ url: "/pages/user/index" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 每次显示页面时重新获取门店和天梯信息
|
|
||||||
try {
|
try {
|
||||||
|
if (app.globalData.token) {
|
||||||
await app.ensureCurrentStore();
|
await app.ensureCurrentStore();
|
||||||
// 如果有门店,获取该门店的天梯信息
|
if (app.globalData.currentStore?.storeId) {
|
||||||
if (app.globalData.currentStore && app.globalData.currentStore.storeId) {
|
|
||||||
await app.getLadderUser(app.globalData.currentStore.storeId);
|
await app.getLadderUser(app.globalData.currentStore.storeId);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
await app.getDefaultStore();
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("获取门店/天梯信息失败:", e);
|
console.error("获取门店/天梯信息失败:", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,9 +56,12 @@ Page({
|
|||||||
async initData() {
|
async initData() {
|
||||||
if (!app.globalData.token) {
|
if (!app.globalData.token) {
|
||||||
try {
|
try {
|
||||||
await app.login();
|
await app.wxLogin();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("登录失败:", e);
|
console.error("微信登录失败:", e);
|
||||||
|
}
|
||||||
|
if (!app.globalData.currentStore) {
|
||||||
|
await app.getDefaultStore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const store = app.globalData.currentStore;
|
const store = app.globalData.currentStore;
|
||||||
|
|||||||
@ -158,15 +158,15 @@ Page({
|
|||||||
storeAddress: store.address
|
storeAddress: store.address
|
||||||
})
|
})
|
||||||
|
|
||||||
// 清空旧的天梯用户信息
|
|
||||||
app.globalData.ladderUser = null
|
app.globalData.ladderUser = null
|
||||||
|
|
||||||
// 获取该门店的天梯用户信息
|
if (app.globalData.token) {
|
||||||
try {
|
try {
|
||||||
await app.getLadderUser(store.id)
|
await app.getLadderUser(store.id)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('获取天梯信息失败:', e)
|
console.error('获取天梯信息失败:', e)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 标记需要刷新数据
|
// 标记需要刷新数据
|
||||||
app.globalData.storeChanged = true
|
app.globalData.storeChanged = true
|
||||||
|
|||||||
@ -68,7 +68,7 @@ Page({
|
|||||||
},
|
},
|
||||||
|
|
||||||
async initData() {
|
async initData() {
|
||||||
// 先进行微信登录获取openid
|
// 微信登录:已有手机号用户会直接完成登录,无手机号用户需授权手机号
|
||||||
if (!app.globalData.wxLoginInfo) {
|
if (!app.globalData.wxLoginInfo) {
|
||||||
try {
|
try {
|
||||||
await app.wxLogin();
|
await app.wxLogin();
|
||||||
@ -79,6 +79,13 @@ Page({
|
|||||||
|
|
||||||
if (app.globalData.token) {
|
if (app.globalData.token) {
|
||||||
await this.refreshData();
|
await this.refreshData();
|
||||||
|
} else {
|
||||||
|
// 无 token 时同步当前状态(如 wxLoginInfo)到页面,用于判断是否显示手机号登录
|
||||||
|
this.setData({
|
||||||
|
userInfo: app.globalData.userInfo,
|
||||||
|
ladderUser: null,
|
||||||
|
currentStore: app.globalData.currentStore,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -74,6 +74,62 @@ class StoreController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取默认门店(无需登录,供新用户浏览排名等)
|
||||||
|
async getDefaultStore(req, res) {
|
||||||
|
try {
|
||||||
|
const { latitude, longitude } = req.query;
|
||||||
|
const lastStore = req.query.last_store_id; // 可选:上次选择的门店 ID
|
||||||
|
|
||||||
|
if (lastStore) {
|
||||||
|
const store = await Store.findByPk(lastStore);
|
||||||
|
if (store && store.status === 1) {
|
||||||
|
return res.json(success({
|
||||||
|
storeId: store.id,
|
||||||
|
storeName: store.name,
|
||||||
|
storeAddress: store.address,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (latitude && longitude) {
|
||||||
|
const stores = await Store.findAll({ where: { status: 1 } });
|
||||||
|
let nearest = stores[0];
|
||||||
|
let minDist = Infinity;
|
||||||
|
for (const s of stores) {
|
||||||
|
if (s.latitude && s.longitude) {
|
||||||
|
const d = calculateDistance(
|
||||||
|
parseFloat(latitude),
|
||||||
|
parseFloat(longitude),
|
||||||
|
parseFloat(s.latitude),
|
||||||
|
parseFloat(s.longitude)
|
||||||
|
);
|
||||||
|
if (d < minDist) {
|
||||||
|
minDist = d;
|
||||||
|
nearest = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nearest) {
|
||||||
|
return res.json(success({
|
||||||
|
storeId: nearest.id,
|
||||||
|
storeName: nearest.name,
|
||||||
|
storeAddress: nearest.address,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const first = await Store.findOne({ where: { status: 1 } });
|
||||||
|
res.json(success(first ? {
|
||||||
|
storeId: first.id,
|
||||||
|
storeName: first.name,
|
||||||
|
storeAddress: first.address,
|
||||||
|
} : null));
|
||||||
|
} catch (err) {
|
||||||
|
console.error('获取默认门店失败:', err);
|
||||||
|
res.status(500).json(error('获取失败'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 获取附近门店(默认显示所有门店,按距离由近到远排序)
|
// 获取附近门店(默认显示所有门店,按距离由近到远排序)
|
||||||
async getNearby(req, res) {
|
async getNearby(req, res) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -47,26 +47,52 @@ class UserController {
|
|||||||
|
|
||||||
// 查找用户
|
// 查找用户
|
||||||
let user = await User.findOne({ where: { openid } });
|
let user = await User.findOne({ where: { openid } });
|
||||||
let isNewUser = false;
|
let isNewUser = !user;
|
||||||
|
const hasPhone = !!(user?.phone);
|
||||||
if (!user) {
|
|
||||||
isNewUser = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回登录信息(包含session_key用于后续手机号解密)
|
// 返回登录信息(包含session_key用于后续手机号解密)
|
||||||
// 注意:实际生产环境中session_key不应该直接返回给前端
|
|
||||||
// 这里为了简化流程,使用加密后的session_key
|
|
||||||
const encryptedSessionKey = this.encryptSessionKey(session_key, openid);
|
const encryptedSessionKey = this.encryptSessionKey(session_key, openid);
|
||||||
|
|
||||||
res.json(
|
const responseData = {
|
||||||
success(
|
|
||||||
{
|
|
||||||
openid,
|
openid,
|
||||||
unionid,
|
unionid,
|
||||||
sessionKey: encryptedSessionKey,
|
sessionKey: encryptedSessionKey,
|
||||||
isNewUser,
|
isNewUser,
|
||||||
hasPhone: user?.phone ? true : false,
|
hasPhone,
|
||||||
userInfo: user
|
};
|
||||||
|
|
||||||
|
if (hasPhone && user) {
|
||||||
|
// 已有手机号的老用户:直接生成 token 登录,无需再次授权手机号
|
||||||
|
const token = jwt.sign(
|
||||||
|
{ userId: user.id },
|
||||||
|
process.env.JWT_SECRET,
|
||||||
|
{ expiresIn: process.env.JWT_EXPIRES_IN || "7d" }
|
||||||
|
);
|
||||||
|
const ladderUsers = await LadderUser.findAll({
|
||||||
|
where: { user_id: user.id, status: 1 },
|
||||||
|
include: [{ model: Store, as: "store", attributes: ["id", "name"] }],
|
||||||
|
});
|
||||||
|
responseData.token = token;
|
||||||
|
responseData.userInfo = {
|
||||||
|
id: user.id,
|
||||||
|
nickname: user.nickname,
|
||||||
|
avatar: getFullUrl(user.avatar, req),
|
||||||
|
phone: user.phone,
|
||||||
|
gender: user.gender,
|
||||||
|
memberCode: user.member_code,
|
||||||
|
totalPoints: user.total_points,
|
||||||
|
ladderUsers: ladderUsers.map((lu) => ({
|
||||||
|
id: lu.id,
|
||||||
|
storeId: lu.store_id,
|
||||||
|
storeName: lu.store?.name,
|
||||||
|
realName: lu.real_name,
|
||||||
|
level: lu.level,
|
||||||
|
levelName: LADDER_LEVEL_NAMES[lu.level] || `Lv${lu.level}`,
|
||||||
|
powerScore: lu.power_score,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
responseData.userInfo = user
|
||||||
? {
|
? {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
nickname: user.nickname,
|
nickname: user.nickname,
|
||||||
@ -76,8 +102,12 @@ class UserController {
|
|||||||
memberCode: user.member_code,
|
memberCode: user.member_code,
|
||||||
totalPoints: user.total_points,
|
totalPoints: user.total_points,
|
||||||
}
|
}
|
||||||
: null,
|
: null;
|
||||||
},
|
}
|
||||||
|
|
||||||
|
res.json(
|
||||||
|
success(
|
||||||
|
responseData,
|
||||||
isNewUser ? "请授权手机号完成注册" : "登录成功",
|
isNewUser ? "请授权手机号完成注册" : "登录成功",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -6,6 +6,8 @@ const { authUser } = require('../middlewares/auth');
|
|||||||
// 获取门店列表
|
// 获取门店列表
|
||||||
router.get('/list', storeController.getList);
|
router.get('/list', storeController.getList);
|
||||||
|
|
||||||
|
// 获取默认/最近门店(无需登录,供新用户浏览使用)
|
||||||
|
router.get('/default', storeController.getDefaultStore);
|
||||||
// 获取附近门店(必须放在 /:id 之前,否则 "nearby" 会被当作 id 匹配)
|
// 获取附近门店(必须放在 /:id 之前,否则 "nearby" 会被当作 id 匹配)
|
||||||
router.get('/nearby', authUser, storeController.getNearby);
|
router.get('/nearby', authUser, storeController.getNearby);
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user