refactor: Enhance store and user management features

- Updated user information retrieval to support store-specific points and ladder user data.
- Refactored store initialization logic to prioritize last selected store.
- Improved API endpoints for fetching matches and user data, ensuring compatibility with store context.
- Adjusted UI components to display store-related information consistently across various pages.
- Enhanced error handling and data fetching logic for better user experience.
This commit is contained in:
ethanfly 2026-02-07 11:09:37 +08:00
parent a684b617f2
commit 98f9e64d91
22 changed files with 292 additions and 192 deletions

View File

@ -2,288 +2,300 @@
"hash": "481070f0",
"configHash": "0bd4dba1",
"lockfileHash": "aa252f00",
"browserHash": "f45a29de",
"browserHash": "aa056658",
"optimized": {
"@element-plus/icons-vue": {
"src": "../../@element-plus/icons-vue/dist/index.js",
"file": "@element-plus_icons-vue.js",
"fileHash": "303763e5",
"fileHash": "c76bcc44",
"needsInterop": false
},
"@wangeditor/editor-for-vue": {
"src": "../../@wangeditor/editor-for-vue/dist/index.esm.js",
"file": "@wangeditor_editor-for-vue.js",
"fileHash": "f00791f0",
"fileHash": "37aca601",
"needsInterop": false
},
"axios": {
"src": "../../axios/index.js",
"file": "axios.js",
"fileHash": "82f2009c",
"fileHash": "3f28a8a9",
"needsInterop": false
},
"dayjs": {
"src": "../../dayjs/dayjs.min.js",
"file": "dayjs.js",
"fileHash": "5645dea7",
"fileHash": "d74a620b",
"needsInterop": true
},
"element-plus": {
"src": "../../element-plus/es/index.mjs",
"file": "element-plus.js",
"fileHash": "e550aa25",
"fileHash": "5589b60e",
"needsInterop": false
},
"element-plus/dist/locale/zh-cn.mjs": {
"src": "../../element-plus/dist/locale/zh-cn.mjs",
"file": "element-plus_dist_locale_zh-cn__mjs.js",
"fileHash": "3b744d4a",
"fileHash": "c0fd1204",
"needsInterop": false
},
"pinia": {
"src": "../../pinia/dist/pinia.mjs",
"file": "pinia.js",
"fileHash": "8c748394",
"fileHash": "dc1f88e8",
"needsInterop": false
},
"qrcode": {
"src": "../../qrcode/lib/browser.js",
"file": "qrcode.js",
"fileHash": "e1329e14",
"fileHash": "6c0a34f3",
"needsInterop": true
},
"vue": {
"src": "../../vue/dist/vue.runtime.esm-bundler.js",
"file": "vue.js",
"fileHash": "853300f0",
"fileHash": "868e6988",
"needsInterop": false
},
"vue-router": {
"src": "../../vue-router/dist/vue-router.mjs",
"file": "vue-router.js",
"fileHash": "d904f03a",
"fileHash": "b4f98d74",
"needsInterop": false
},
"element-plus/es": {
"src": "../../element-plus/es/index.mjs",
"file": "element-plus_es.js",
"fileHash": "29301e56",
"fileHash": "8a699395",
"needsInterop": false
},
"element-plus/es/components/base/style/css": {
"src": "../../element-plus/es/components/base/style/css.mjs",
"file": "element-plus_es_components_base_style_css.js",
"fileHash": "1d49b62e",
"fileHash": "ad6081a9",
"needsInterop": false
},
"element-plus/es/components/dialog/style/css": {
"src": "../../element-plus/es/components/dialog/style/css.mjs",
"file": "element-plus_es_components_dialog_style_css.js",
"fileHash": "5bba98de",
"fileHash": "ebd107ef",
"needsInterop": false
},
"element-plus/es/components/button/style/css": {
"src": "../../element-plus/es/components/button/style/css.mjs",
"file": "element-plus_es_components_button_style_css.js",
"fileHash": "fd8b0f8b",
"fileHash": "ff2f2a5b",
"needsInterop": false
},
"element-plus/es/components/form/style/css": {
"src": "../../element-plus/es/components/form/style/css.mjs",
"file": "element-plus_es_components_form_style_css.js",
"fileHash": "2ee6e663",
"fileHash": "df7fdb18",
"needsInterop": false
},
"element-plus/es/components/form-item/style/css": {
"src": "../../element-plus/es/components/form-item/style/css.mjs",
"file": "element-plus_es_components_form-item_style_css.js",
"fileHash": "cf1450df",
"fileHash": "e9c6d01b",
"needsInterop": false
},
"element-plus/es/components/input/style/css": {
"src": "../../element-plus/es/components/input/style/css.mjs",
"file": "element-plus_es_components_input_style_css.js",
"fileHash": "6aca7cf0",
"fileHash": "e2b09618",
"needsInterop": false
},
"element-plus/es/components/container/style/css": {
"src": "../../element-plus/es/components/container/style/css.mjs",
"file": "element-plus_es_components_container_style_css.js",
"fileHash": "4717aecb",
"fileHash": "c0984641",
"needsInterop": false
},
"element-plus/es/components/main/style/css": {
"src": "../../element-plus/es/components/main/style/css.mjs",
"file": "element-plus_es_components_main_style_css.js",
"fileHash": "f3ba5b99",
"fileHash": "a73569af",
"needsInterop": false
},
"element-plus/es/components/header/style/css": {
"src": "../../element-plus/es/components/header/style/css.mjs",
"file": "element-plus_es_components_header_style_css.js",
"fileHash": "96b2ed2f",
"fileHash": "b592e09f",
"needsInterop": false
},
"element-plus/es/components/dropdown/style/css": {
"src": "../../element-plus/es/components/dropdown/style/css.mjs",
"file": "element-plus_es_components_dropdown_style_css.js",
"fileHash": "d0a6dac3",
"fileHash": "f7bb8b58",
"needsInterop": false
},
"element-plus/es/components/dropdown-menu/style/css": {
"src": "../../element-plus/es/components/dropdown-menu/style/css.mjs",
"file": "element-plus_es_components_dropdown-menu_style_css.js",
"fileHash": "35005d29",
"fileHash": "60954d93",
"needsInterop": false
},
"element-plus/es/components/dropdown-item/style/css": {
"src": "../../element-plus/es/components/dropdown-item/style/css.mjs",
"file": "element-plus_es_components_dropdown-item_style_css.js",
"fileHash": "82f476ab",
"fileHash": "018afb10",
"needsInterop": false
},
"element-plus/es/components/avatar/style/css": {
"src": "../../element-plus/es/components/avatar/style/css.mjs",
"file": "element-plus_es_components_avatar_style_css.js",
"fileHash": "a9f261cc",
"fileHash": "6a7d147e",
"needsInterop": false
},
"element-plus/es/components/breadcrumb/style/css": {
"src": "../../element-plus/es/components/breadcrumb/style/css.mjs",
"file": "element-plus_es_components_breadcrumb_style_css.js",
"fileHash": "3f3f3b42",
"fileHash": "a2b08aeb",
"needsInterop": false
},
"element-plus/es/components/breadcrumb-item/style/css": {
"src": "../../element-plus/es/components/breadcrumb-item/style/css.mjs",
"file": "element-plus_es_components_breadcrumb-item_style_css.js",
"fileHash": "a5e97d32",
"fileHash": "df52cc96",
"needsInterop": false
},
"element-plus/es/components/aside/style/css": {
"src": "../../element-plus/es/components/aside/style/css.mjs",
"file": "element-plus_es_components_aside_style_css.js",
"fileHash": "64e45da0",
"fileHash": "ac5e261e",
"needsInterop": false
},
"element-plus/es/components/menu/style/css": {
"src": "../../element-plus/es/components/menu/style/css.mjs",
"file": "element-plus_es_components_menu_style_css.js",
"fileHash": "2f316cd2",
"fileHash": "4e7cd965",
"needsInterop": false
},
"element-plus/es/components/menu-item/style/css": {
"src": "../../element-plus/es/components/menu-item/style/css.mjs",
"file": "element-plus_es_components_menu-item_style_css.js",
"fileHash": "afb95054",
"fileHash": "51fb1a5d",
"needsInterop": false
},
"element-plus/es/components/icon/style/css": {
"src": "../../element-plus/es/components/icon/style/css.mjs",
"file": "element-plus_es_components_icon_style_css.js",
"fileHash": "9d19ab9f",
"fileHash": "ceb23baa",
"needsInterop": false
},
"element-plus/es/components/loading/style/css": {
"src": "../../element-plus/es/components/loading/style/css.mjs",
"file": "element-plus_es_components_loading_style_css.js",
"fileHash": "8679a851",
"fileHash": "19b92455",
"needsInterop": false
},
"element-plus/es/components/pagination/style/css": {
"src": "../../element-plus/es/components/pagination/style/css.mjs",
"file": "element-plus_es_components_pagination_style_css.js",
"fileHash": "e7d9082b",
"fileHash": "94ca04a5",
"needsInterop": false
},
"element-plus/es/components/table/style/css": {
"src": "../../element-plus/es/components/table/style/css.mjs",
"file": "element-plus_es_components_table_style_css.js",
"fileHash": "c2ed39c2",
"fileHash": "624bb620",
"needsInterop": false
},
"element-plus/es/components/tag/style/css": {
"src": "../../element-plus/es/components/tag/style/css.mjs",
"file": "element-plus_es_components_tag_style_css.js",
"fileHash": "cb057059",
"fileHash": "20423d57",
"needsInterop": false
},
"element-plus/es/components/image/style/css": {
"src": "../../element-plus/es/components/image/style/css.mjs",
"file": "element-plus_es_components_image_style_css.js",
"fileHash": "351b3909",
"fileHash": "994dd216",
"needsInterop": false
},
"element-plus/es/components/table-column/style/css": {
"src": "../../element-plus/es/components/table-column/style/css.mjs",
"file": "element-plus_es_components_table-column_style_css.js",
"fileHash": "6eb55fa5",
"fileHash": "1b6a0c06",
"needsInterop": false
},
"element-plus/es/components/select/style/css": {
"src": "../../element-plus/es/components/select/style/css.mjs",
"file": "element-plus_es_components_select_style_css.js",
"fileHash": "daf87f44",
"fileHash": "c8ac9a5a",
"needsInterop": false
},
"element-plus/es/components/option/style/css": {
"src": "../../element-plus/es/components/option/style/css.mjs",
"file": "element-plus_es_components_option_style_css.js",
"fileHash": "8cdd90ba",
"fileHash": "ee3e00ce",
"needsInterop": false
},
"element-plus/es/components/radio-group/style/css": {
"src": "../../element-plus/es/components/radio-group/style/css.mjs",
"file": "element-plus_es_components_radio-group_style_css.js",
"fileHash": "33078822",
"fileHash": "56871901",
"needsInterop": false
},
"element-plus/es/components/radio/style/css": {
"src": "../../element-plus/es/components/radio/style/css.mjs",
"file": "element-plus_es_components_radio_style_css.js",
"fileHash": "aee22efa",
"fileHash": "4857765a",
"needsInterop": false
},
"element-plus/es/components/switch/style/css": {
"src": "../../element-plus/es/components/switch/style/css.mjs",
"file": "element-plus_es_components_switch_style_css.js",
"fileHash": "5d58ccb8",
"fileHash": "3bf1b3df",
"needsInterop": false
},
"element-plus/es/components/input-number/style/css": {
"src": "../../element-plus/es/components/input-number/style/css.mjs",
"file": "element-plus_es_components_input-number_style_css.js",
"fileHash": "8826e069",
"fileHash": "70e0ea9b",
"needsInterop": false
},
"element-plus/es/components/autocomplete/style/css": {
"src": "../../element-plus/es/components/autocomplete/style/css.mjs",
"file": "element-plus_es_components_autocomplete_style_css.js",
"fileHash": "50eaf0f2",
"fileHash": "f192d727",
"needsInterop": false
},
"element-plus/es/components/row/style/css": {
"src": "../../element-plus/es/components/row/style/css.mjs",
"file": "element-plus_es_components_row_style_css.js",
"fileHash": "c8e609ae",
"fileHash": "c97af601",
"needsInterop": false
},
"element-plus/es/components/col/style/css": {
"src": "../../element-plus/es/components/col/style/css.mjs",
"file": "element-plus_es_components_col_style_css.js",
"fileHash": "8683f4d3",
"fileHash": "c5850ceb",
"needsInterop": false
},
"element-plus/es/components/descriptions/style/css": {
"src": "../../element-plus/es/components/descriptions/style/css.mjs",
"file": "element-plus_es_components_descriptions_style_css.js",
"fileHash": "3fde99e8",
"fileHash": "fab03c68",
"needsInterop": false
},
"element-plus/es/components/descriptions-item/style/css": {
"src": "../../element-plus/es/components/descriptions-item/style/css.mjs",
"file": "element-plus_es_components_descriptions-item_style_css.js",
"fileHash": "96d9d5dc",
"fileHash": "3eed0a24",
"needsInterop": false
},
"element-plus/es/components/divider/style/css": {
"src": "../../element-plus/es/components/divider/style/css.mjs",
"file": "element-plus_es_components_divider_style_css.js",
"fileHash": "dc0118ee",
"needsInterop": false
},
"element-plus/es/components/text/style/css": {
"src": "../../element-plus/es/components/text/style/css.mjs",
"file": "element-plus_es_components_text_style_css.js",
"fileHash": "7829d239",
"needsInterop": false
}
},
@ -291,6 +303,9 @@
"chunk-4PW274X2": {
"file": "chunk-4PW274X2.js"
},
"chunk-JUCAMQ7P": {
"file": "chunk-JUCAMQ7P.js"
},
"chunk-B2YDYSZR": {
"file": "chunk-B2YDYSZR.js"
},
@ -306,42 +321,39 @@
"chunk-R5DNQ3QC": {
"file": "chunk-R5DNQ3QC.js"
},
"chunk-NKQWFVTF": {
"file": "chunk-NKQWFVTF.js"
},
"chunk-REWOA3VH": {
"file": "chunk-REWOA3VH.js"
},
"chunk-TX5YLZ4O": {
"file": "chunk-TX5YLZ4O.js"
},
"chunk-YFT6OQ5R": {
"file": "chunk-YFT6OQ5R.js"
},
"chunk-SMFPDFTD": {
"file": "chunk-SMFPDFTD.js"
},
"chunk-JUCAMQ7P": {
"file": "chunk-JUCAMQ7P.js"
"chunk-YFT6OQ5R": {
"file": "chunk-YFT6OQ5R.js"
},
"chunk-NKQWFVTF": {
"file": "chunk-NKQWFVTF.js"
},
"chunk-IV6PSERC": {
"file": "chunk-IV6PSERC.js"
},
"chunk-ZLPQR2PE": {
"file": "chunk-ZLPQR2PE.js"
"chunk-LYXQVMFB": {
"file": "chunk-LYXQVMFB.js"
},
"chunk-HYZ2CRGS": {
"file": "chunk-HYZ2CRGS.js"
},
"chunk-QZC7O2C6": {
"file": "chunk-QZC7O2C6.js"
},
"chunk-OP4ZUAFM": {
"file": "chunk-OP4ZUAFM.js"
},
"chunk-H2732BJL": {
"file": "chunk-H2732BJL.js"
},
"chunk-QZC7O2C6": {
"file": "chunk-QZC7O2C6.js"
},
"chunk-G3PMV62Z": {
"file": "chunk-G3PMV62Z.js"
}

View File

@ -514,11 +514,11 @@ import {
virtualizedScrollbarProps,
watermarkProps,
zIndexContextKey
} from "./chunk-ZLPQR2PE.js";
} from "./chunk-LYXQVMFB.js";
import "./chunk-HYZ2CRGS.js";
import "./chunk-QZC7O2C6.js";
import "./chunk-OP4ZUAFM.js";
import "./chunk-H2732BJL.js";
import "./chunk-QZC7O2C6.js";
import "./chunk-G3PMV62Z.js";
var export_dayjs = import_dayjs.default;
export {

View File

@ -514,11 +514,11 @@ import {
virtualizedScrollbarProps,
watermarkProps,
zIndexContextKey
} from "./chunk-ZLPQR2PE.js";
} from "./chunk-LYXQVMFB.js";
import "./chunk-HYZ2CRGS.js";
import "./chunk-QZC7O2C6.js";
import "./chunk-OP4ZUAFM.js";
import "./chunk-H2732BJL.js";
import "./chunk-QZC7O2C6.js";
import "./chunk-G3PMV62Z.js";
var export_dayjs = import_dayjs.default;
export {

View File

@ -1,9 +1,9 @@
import "./chunk-75C4BP7B.js";
import "./chunk-5KK3TTMN.js";
import "./chunk-UBLR4G7Q.js";
import "./chunk-NKQWFVTF.js";
import "./chunk-REWOA3VH.js";
import "./chunk-TX5YLZ4O.js";
import "./chunk-NKQWFVTF.js";
import "./chunk-IV6PSERC.js";
// node_modules/element-plus/es/components/pagination/style/css.mjs

View File

@ -15,11 +15,13 @@ App({
},
onLaunch() {
// 从本地存储读取token
// 从本地存储读取 token 和上次选择的门店
const token = wx.getStorageSync("token");
if (token) {
this.globalData.token = token;
this.getUserInfo();
this.ensureCurrentStore()
.then(() => this.getUserInfo(this.globalData.currentStore?.storeId))
.catch(() => {});
}
},
@ -94,12 +96,11 @@ App({
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 (
loginRes.data.data.userInfo.ladderUsers &&
loginRes.data.data.userInfo.ladderUsers.length > 0
) {
if (
this.globalData.currentStore &&
this.globalData.currentStore.storeId
@ -108,20 +109,12 @@ App({
loginRes.data.data.userInfo.ladderUsers.find(
(lu) => lu.storeId === this.globalData.currentStore.storeId,
);
if (currentStoreLadderUser) {
this.globalData.ladderUser = currentStoreLadderUser;
} else {
// 当前门店没有天梯用户,取第一个
this.globalData.ladderUser =
loginRes.data.data.userInfo.ladderUsers[0];
}
// 仅当当前门店有天梯用户时才显示,否则为 null避免显示其他门店数据
this.globalData.ladderUser = currentStoreLadderUser || null;
} else {
// 没有当前门店,取第一个天梯用户
this.globalData.ladderUser =
loginRes.data.data.userInfo.ladderUsers[0];
this.globalData.ladderUser = null;
}
} else {
// 没有天梯用户
this.globalData.ladderUser = null;
}
@ -142,16 +135,16 @@ App({
return this.wxLogin();
},
// 获取用户信息
getUserInfo() {
// 获取用户信息storeId 可选,传入时返回该门店的积分 storePoints
getUserInfo(storeId) {
return new Promise((resolve, reject) => {
this.request("/api/user/info")
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
@ -159,18 +152,12 @@ App({
const currentStoreLadderUser = res.data.ladderUsers.find(
(lu) => lu.storeId === this.globalData.currentStore.storeId,
);
if (currentStoreLadderUser) {
this.globalData.ladderUser = currentStoreLadderUser;
} else {
// 当前门店没有天梯用户,取第一个
this.globalData.ladderUser = res.data.ladderUsers[0];
}
// 仅当当前门店有天梯用户时才显示,否则为 null避免显示其他门店数据
this.globalData.ladderUser = currentStoreLadderUser || null;
} else {
// 没有当前门店,取第一个天梯用户
this.globalData.ladderUser = res.data.ladderUsers[0];
this.globalData.ladderUser = null;
}
} else {
// 没有天梯用户
this.globalData.ladderUser = null;
}
@ -181,7 +168,28 @@ App({
});
},
// 获取当前门店
/**
* 优先使用上次选择的门店没有或失败时再请求最近门店新用户逻辑
*/
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({
@ -194,11 +202,9 @@ App({
.then((res) => {
this.globalData.currentStore = res.data;
// 如果当前门店有 ladderUserId获取该门店的天梯用户信息
if (res.data && res.data.ladderUserId) {
this.getLadderUser(res.data.storeId);
} else if (res.data && res.data.storeId) {
// 如果当前门店没有 ladderUserId但用户信息中有该门店的天梯用户使用它
if (
this.globalData.userInfo &&
this.globalData.userInfo.ladderUsers
@ -210,27 +216,21 @@ App({
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;
// 如果当前门店有 ladderUserId获取该门店的天梯用户信息
if (res.data && res.data.ladderUserId) {
this.getLadderUser(res.data.storeId);
} else if (res.data && res.data.storeId) {
// 如果当前门店没有 ladderUserId但用户信息中有该门店的天梯用户使用它
if (
this.globalData.userInfo &&
this.globalData.userInfo.ladderUsers
@ -242,12 +242,10 @@ App({
if (currentStoreLadderUser) {
this.globalData.ladderUser = currentStoreLadderUser;
} else {
// 当前门店没有天梯用户,清空
this.globalData.ladderUser = null;
}
}
}
resolve(res.data);
})
.catch(reject);
@ -256,14 +254,13 @@ App({
});
},
// 获取天梯用户信息
// 获取天梯用户信息(请求失败时 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;

View File

@ -26,10 +26,7 @@ Page({
if (storeId) {
data.store_id = storeId;
}
const res = await app.request("/api/article", {
method: "GET",
data,
});
const res = await app.request("/api/article", data);
const raw = res.data || [];
const articles = raw.map((a) => ({
id: a.id,

View File

@ -61,7 +61,7 @@ Page({
// 获取当前门店
try {
const store = await app.getCurrentStore();
const store = await app.ensureCurrentStore();
this.setData({ currentStore: store });
this.fetchData();
} catch (e) {

View File

@ -37,7 +37,7 @@ Page({
// 每次显示页面时重新获取门店和天梯信息
try {
await app.getCurrentStore();
await app.ensureCurrentStore();
// 如果有门店,获取该门店的天梯信息
if (app.globalData.currentStore && app.globalData.currentStore.storeId) {
await app.getLadderUser(app.globalData.currentStore.storeId);
@ -83,7 +83,7 @@ Page({
try {
// 重新获取门店信息
await app.getCurrentStore();
await app.ensureCurrentStore();
// 重新获取天梯信息
if (app.globalData.currentStore && app.globalData.currentStore.storeId) {

View File

@ -37,16 +37,11 @@ Page({
},
async fetchMatches() {
const currentStore = app.globalData.currentStore;
if (!currentStore || !currentStore.storeId) {
return;
}
this.setData({ loading: true });
try {
// 不传 store_id获取全部门店的比赛记录
const res = await app.request("/api/match/my-matches", {
store_id: currentStore.storeId,
page: this.data.page,
pageSize: this.data.pageSize,
});

View File

@ -3,7 +3,10 @@
<view class="history-list" wx:if="{{matches.length > 0}}">
<view class="match-item" wx:for="{{matches}}" wx:key="id">
<view class="match-header">
<text class="match-name">{{item.matchName}}</text>
<view class="match-header-main">
<text class="match-name">{{item.matchName}}</text>
<text class="match-store" wx:if="{{item.storeName}}">{{item.storeName}}</text>
</view>
<text class="match-type">{{item.matchType === 1 ? '挑战赛' : '排位赛'}}</text>
</view>
<view class="match-content">

View File

@ -36,11 +36,22 @@
border-bottom: 2rpx solid rgba(255, 255, 255, 0.2);
}
.match-header-main {
display: flex;
flex-direction: column;
gap: 8rpx;
flex: 1;
}
.match-name {
font-size: 32rpx;
font-weight: 600;
color: #fff;
flex: 1;
}
.match-store {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.85);
}
.match-type {

View File

@ -17,7 +17,16 @@ Page({
this.initData();
},
onShow() {
async onShow() {
const store = app.globalData.currentStore;
// 获取当前门店的积分
if (app.globalData.token && store?.storeId) {
try {
await app.getUserInfo(store.storeId);
} catch (e) {
console.error("获取用户信息失败:", e);
}
}
this.setData({
userInfo: app.globalData.userInfo,
currentStore: app.globalData.currentStore,
@ -52,7 +61,14 @@ Page({
console.error("登录失败:", e);
}
}
const store = app.globalData.currentStore;
if (app.globalData.token && store?.storeId) {
try {
await app.getUserInfo(store.storeId);
} catch (e) {
console.error("获取用户信息失败:", e);
}
}
this.setData({
userInfo: app.globalData.userInfo,
currentStore: app.globalData.currentStore,
@ -128,12 +144,17 @@ Page({
wx.hideLoading();
// 更新用户积分
const newPoints =
this.data.userInfo.totalPoints - product.pointsRequired;
app.globalData.userInfo.totalPoints = newPoints;
// 重新获取用户信息(含当前门店积分)
const store = app.globalData.currentStore;
if (store?.storeId) {
try {
await app.getUserInfo(store.storeId);
} catch (e) {
console.error("刷新积分失败:", e);
}
}
this.setData({
"userInfo.totalPoints": newPoints,
userInfo: app.globalData.userInfo,
showProductModal: false,
});

View File

@ -10,7 +10,7 @@
<view class="points-info">
<text class="points-label">我的积分</text>
<view class="points-value">
<text class="points-number">{{userInfo.totalPoints || 0}}</text>
<text class="points-number">{{userInfo.storePoints !== undefined ? userInfo.storePoints : (userInfo.totalPoints || 0)}}</text>
<text class="points-unit">分</text>
</view>
</view>
@ -48,7 +48,7 @@
<text class="price-value">{{item.pointsRequired}}</text>
<text class="price-unit">积分</text>
</view>
<view class="exchange-btn {{userInfo.totalPoints < item.pointsRequired || item.stock <= 0 ? 'disabled' : ''}}">
<view class="exchange-btn {{(userInfo.storePoints !== undefined ? userInfo.storePoints : userInfo.totalPoints || 0) < item.pointsRequired || item.stock <= 0 ? 'disabled' : ''}}">
{{item.stock <= 0 ? '售罄' : '兑换'}}
</view>
</view>
@ -105,11 +105,11 @@
<view class="modal-footer">
<button
class="exchange-btn-large {{userInfo.totalPoints < currentProduct.pointsRequired || currentProduct.stock <= 0 ? 'disabled' : ''}}"
class="exchange-btn-large {{(userInfo.storePoints !== undefined ? userInfo.storePoints : userInfo.totalPoints || 0) < currentProduct.pointsRequired || currentProduct.stock <= 0 ? 'disabled' : ''}}"
bindtap="exchangeProduct"
disabled="{{userInfo.totalPoints < currentProduct.pointsRequired || currentProduct.stock <= 0}}"
disabled="{{(userInfo.storePoints !== undefined ? userInfo.storePoints : userInfo.totalPoints || 0) < currentProduct.pointsRequired || currentProduct.stock <= 0}}"
>
{{currentProduct.stock <= 0 ? '已售罄' : userInfo.totalPoints < currentProduct.pointsRequired ? '积分不足' : '立即兑换'}}
{{currentProduct.stock <= 0 ? '已售罄' : (userInfo.storePoints !== undefined ? userInfo.storePoints : userInfo.totalPoints || 0) < currentProduct.pointsRequired ? '积分不足' : '立即兑换'}}
</button>
</view>
</view>

View File

@ -4,6 +4,7 @@
<view class="record-item" wx:for="{{records}}" wx:key="id">
<view class="record-info">
<text class="action-name">{{item.actionName}}</text>
<text class="record-store" wx:if="{{item.storeName}}">{{item.storeName}}</text>
<text class="record-time">{{item.createdAt}}</text>
</view>
<view class="record-points {{item.points > 0 ? 'positive' : 'negative'}}">

View File

@ -199,6 +199,25 @@
.record-info {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
gap: 6rpx;
}
.record-info .action-name {
font-size: 28rpx;
font-weight: 600;
color: var(--text-primary);
}
.record-store {
font-size: 24rpx;
color: var(--text-muted);
}
.record-info .record-time {
font-size: 24rpx;
color: var(--text-muted);
}
.record-title {

View File

@ -27,9 +27,20 @@ Page({
}
},
// 初始化当前门店信息
// 初始化当前门店信息(优先全局,其次从上次选择恢复)
initCurrentStore() {
const currentStore = app.globalData.currentStore
let currentStore = app.globalData.currentStore
if (!currentStore) {
const lastStore = wx.getStorageSync('last_store')
if (lastStore && lastStore.storeId) {
currentStore = {
storeId: lastStore.storeId,
storeName: lastStore.storeName || '',
storeAddress: lastStore.storeAddress || ''
}
app.globalData.currentStore = currentStore
}
}
if (currentStore) {
this.setData({
currentStoreId: currentStore.storeId,
@ -59,8 +70,7 @@ Page({
try {
const res = await app.request('/api/store/nearby', {
latitude: loc.latitude,
longitude: loc.longitude,
radius: 50000
longitude: loc.longitude
})
this.setData({
stores: res.data || [],
@ -141,6 +151,13 @@ Page({
storeAddress: store.address
}
// 持久化上次选择的门店,下次启动优先使用
wx.setStorageSync('last_store', {
storeId: store.id,
storeName: store.name,
storeAddress: store.address
})
// 清空旧的天梯用户信息
app.globalData.ladderUser = null

View File

@ -86,16 +86,14 @@ Page({
if (!app.globalData.token) return;
try {
await app.getUserInfo();
const store = app.globalData.currentStore;
// 传入当前门店 ID获取该门店的积分和天梯信息
await app.getUserInfo(store?.storeId);
// 如果当前门店有 ladderUserId确保获取该门店的天梯用户信息
if (
app.globalData.currentStore &&
app.globalData.currentStore.storeId &&
!app.globalData.ladderUser
) {
// 有当前门店时,拉取该门店的天梯用户信息(确保战力数据与当前门店一致)
if (store && store.storeId) {
try {
await app.getLadderUser(app.globalData.currentStore.storeId);
await app.getLadderUser(store.storeId);
} catch (e) {
console.error("获取天梯用户信息失败:", e);
}
@ -105,6 +103,7 @@ Page({
userInfo: app.globalData.userInfo,
ladderUser: this.normalizeLadderUser(app.globalData.ladderUser),
currentStore: app.globalData.currentStore,
announcements: [], // 先清空,避免切换门店时短暂显示旧门店公告
});
await this.fetchAnnouncements();
@ -120,11 +119,8 @@ Page({
if (!store || !store.storeId) return;
try {
const res = await app.request("/api/article", {
method: "GET",
data: {
limit: 10,
store_id: store.storeId,
},
limit: 10,
store_id: store.storeId,
});
const raw = res.data || [];
const list = raw.map((a) => ({
@ -177,7 +173,7 @@ Page({
);
// 获取门店信息
await app.getCurrentStore();
await app.ensureCurrentStore();
const userInfo = app.globalData.userInfo;
@ -252,7 +248,7 @@ Page({
this.data.registerGender,
);
await app.getCurrentStore();
await app.ensureCurrentStore();
const userInfo = app.globalData.userInfo;
this.setData({
userInfo: userInfo,

View File

@ -20,7 +20,7 @@
<text class="nickname">{{userInfo.nickname || '新用户'}}</text>
<view class="user-stats">
<view class="stat-item">
<text class="stat-value">{{userInfo.totalPoints || 0}}</text>
<text class="stat-value">{{userInfo.storePoints !== undefined ? userInfo.storePoints : (userInfo.totalPoints || 0)}}</text>
<text class="stat-label">积分</text>
</view>
<view class="stat-divider"></view>

View File

@ -1288,31 +1288,40 @@ class MatchController {
return this.confirmScore(req, res);
}
// 获取我的比赛记录
// 获取我的比赛记录(不传 store_id 时返回全部门店记录)
async getMyMatches(req, res) {
try {
const { store_id, page = 1, pageSize = 20 } = req.query;
const { limit, offset } = getPagination(page, pageSize);
const user = req.user;
const ladderUser = await LadderUser.findOne({
where: { user_id: user.id, store_id, status: 1 }
const ladderWhere = { user_id: user.id, status: 1 };
if (store_id) {
ladderWhere.store_id = store_id;
}
const ladderUsers = await LadderUser.findAll({
where: ladderWhere,
attributes: ['id', 'store_id'],
include: [{ model: Store, as: 'store', attributes: ['id', 'name'] }]
});
if (!ladderUser) {
if (ladderUsers.length === 0) {
return res.json(pageResult([], 0, page, pageSize));
}
const ladderUserIds = ladderUsers.map(lu => lu.id);
const ladderUserMap = Object.fromEntries(ladderUsers.map(lu => [lu.id, lu]));
const { rows, count } = await MatchGame.findAndCountAll({
where: {
[Op.or]: [
{ player1_id: ladderUser.id },
{ player2_id: ladderUser.id }
{ player1_id: { [Op.in]: ladderUserIds } },
{ player2_id: { [Op.in]: ladderUserIds } }
],
confirm_status: CONFIRM_STATUS.CONFIRMED
},
include: [
{ model: Match, as: 'match', attributes: ['id', 'name', 'type', 'weight'] }
{ model: Match, as: 'match', attributes: ['id', 'name', 'type', 'weight', 'store_id'] }
],
order: [['confirmed_at', 'DESC']],
limit,
@ -1320,20 +1329,28 @@ class MatchController {
});
const list = await Promise.all(rows.map(async game => {
const isPlayer1 = game.player1_id === ladderUser.id;
const myLadderId = ladderUserIds.find(id =>
game.player1_id === id || game.player2_id === id
);
const myLadderUser = ladderUserMap[myLadderId];
const isPlayer1 = game.player1_id === myLadderId;
const opponentId = isPlayer1 ? game.player2_id : game.player1_id;
const opponent = await LadderUser.findByPk(opponentId);
const opponent = await LadderUser.findByPk(opponentId, {
include: [{ model: Store, as: 'store', attributes: ['id', 'name'] }]
});
return {
id: game.id,
matchId: game.match_id,
matchName: game.match?.name,
matchType: game.match?.type,
storeId: myLadderUser?.store_id,
storeName: myLadderUser?.store?.name,
opponentName: opponent?.real_name,
myScore: isPlayer1 ? game.player1_score : game.player2_score,
opponentScore: isPlayer1 ? game.player2_score : game.player1_score,
isWin: game.winner_id === ladderUser.id,
powerChange: game.winner_id === ladderUser.id
isWin: game.winner_id === myLadderId,
powerChange: game.winner_id === myLadderId
? game.winner_power_change
: game.loser_power_change,
confirmedAt: game.confirmed_at

View File

@ -74,10 +74,10 @@ class StoreController {
}
}
// 获取附近门店
// 获取附近门店(默认显示所有门店,按距离由近到远排序)
async getNearby(req, res) {
try {
const { latitude, longitude, radius = 10000 } = req.query;
const { latitude, longitude } = req.query;
if (!latitude || !longitude) {
return res.status(400).json(error('缺少位置信息', 400));
@ -110,8 +110,7 @@ class StoreController {
distance
};
})
.filter(store => store.distance === null || store.distance <= parseFloat(radius))
.sort((a, b) => (a.distance || Infinity) - (b.distance || Infinity));
.sort((a, b) => (a.distance ?? Infinity) - (b.distance ?? Infinity));
res.json(success(nearbyStores));
} catch (err) {

View File

@ -2,7 +2,7 @@ const jwt = require("jsonwebtoken");
const axios = require("axios");
const crypto = require("crypto");
const QRCode = require("qrcode");
const { User, LadderUser, Store, Match, MatchGame } = require("../models");
const { User, LadderUser, Store, Match, MatchGame, PointRecord } = require("../models");
const {
generateMemberCode,
success,
@ -355,10 +355,11 @@ class UserController {
}
};
// 获取用户信息
// 获取用户信息(支持 store_id 查询参数,返回该门店的积分)
getInfo = async (req, res) => {
try {
const user = req.user;
const { store_id: storeId } = req.query;
// 获取天梯信息
const ladderUsers = await LadderUser.findAll({
@ -366,16 +367,27 @@ class UserController {
include: [{ model: Store, as: "store", attributes: ["id", "name"] }],
});
res.json(
success({
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) => ({
// 按门店的积分:从 PointRecord 汇总该门店的积分变动
let storePoints = null;
if (storeId) {
const sumResult = await PointRecord.sum("points", {
where: {
user_id: user.id,
store_id: storeId,
},
});
storePoints = Number(sumResult) || 0;
}
const result = {
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,
@ -386,8 +398,11 @@ class UserController {
matchCount: lu.match_count,
winCount: lu.win_count,
})),
}),
);
};
if (storePoints !== null) {
result.storePoints = storePoints;
}
res.json(success(result));
} catch (err) {
console.error("获取用户信息失败:", err);
res.status(500).json(error("获取用户信息失败"));

View File

@ -6,13 +6,13 @@ const { authUser } = require('../middlewares/auth');
// 获取门店列表
router.get('/list', storeController.getList);
// 获取门店详情
router.get('/:id', storeController.getDetail);
// 获取附近门店
// 获取附近门店(必须放在 /:id 之前,否则 "nearby" 会被当作 id 匹配)
router.get('/nearby', authUser, storeController.getNearby);
// 获取用户参与天梯的门店
router.get('/ladder-stores', authUser, storeController.getLadderStores);
// 获取门店详情(放在最后,避免 /list、/nearby 等被当作 id
router.get('/:id', storeController.getDetail);
module.exports = router;