From 7ec68e3f7a678c4dd17e5ba4df6d1344de5b4298 Mon Sep 17 00:00:00 2001 From: ethanfly Date: Fri, 23 Jan 2026 01:38:42 +0800 Subject: [PATCH] Refactor and enhance miniprogram UI with new designs, improve match and points functionality, and update server routes for QR code generation --- admin/node_modules/.vite/deps/_metadata.json | 517 ++++---- .../node_modules/.vite/deps/chunk-JUCAMQ7P.js | 3 - .../.vite/deps/chunk-JUCAMQ7P.js.map | 7 - .../{chunk-6CKQ2YFZ.js => chunk-W7GFOP2W.js} | 8 +- ...-6CKQ2YFZ.js.map => chunk-W7GFOP2W.js.map} | 0 admin/node_modules/.vite/deps/element-plus.js | 4 +- .../.vite/deps/element-plus_es.js | 4 +- ...s_es_components_collapse-item_style_css.js | 5 - ..._components_collapse-item_style_css.js.map | 7 - ...t-plus_es_components_collapse_style_css.js | 5 - ...us_es_components_collapse_style_css.js.map | 7 - ..._components_descriptions-item_style_css.js | 3 - ...ponents_descriptions-item_style_css.js.map | 7 - ...us_es_components_descriptions_style_css.js | 6 - ...s_components_descriptions_style_css.js.map | 7 - ...nt-plus_es_components_divider_style_css.js | 5 - ...lus_es_components_divider_style_css.js.map | 7 - ...t-plus_es_components_dropdown_style_css.js | 2 +- ...ment-plus_es_components_image_style_css.js | 8 - ...-plus_es_components_image_style_css.js.map | 7 - ...plus_es_components_pagination_style_css.js | 2 +- ...ent-plus_es_components_select_style_css.js | 2 +- ...ent-plus_es_components_switch_style_css.js | 5 - ...plus_es_components_switch_style_css.js.map | 7 - ...us_es_components_table-column_style_css.js | 2 +- ...ment-plus_es_components_table_style_css.js | 2 +- ...ement-plus_es_components_text_style_css.js | 5 - ...t-plus_es_components_text_style_css.js.map | 7 - ...ent-plus_es_components_upload_style_css.js | 8 - ...plus_es_components_upload_style_css.js.map | 7 - miniprogram/app.js | 3 + miniprogram/app.wxss | 652 +++++++--- miniprogram/pages/index/index.js | 38 +- miniprogram/pages/index/index.wxml | 185 +-- miniprogram/pages/index/index.wxss | 369 ++++-- miniprogram/pages/match/challenge/index.js | 93 +- miniprogram/pages/match/challenge/index.json | 3 +- miniprogram/pages/match/challenge/index.wxml | 273 ++-- miniprogram/pages/match/challenge/index.wxss | 1055 +++++++++++++--- miniprogram/pages/match/history/index.js | 9 + miniprogram/pages/match/history/index.wxss | 402 ++++-- miniprogram/pages/match/ranking/index.js | 14 +- miniprogram/pages/match/ranking/index.wxml | 157 ++- miniprogram/pages/match/ranking/index.wxss | 504 ++++++-- miniprogram/pages/points/mall/index.js | 29 +- miniprogram/pages/points/mall/index.wxml | 126 +- miniprogram/pages/points/mall/index.wxss | 502 ++++++-- miniprogram/pages/points/order/index.js | 51 +- miniprogram/pages/points/order/index.wxml | 171 ++- miniprogram/pages/points/order/index.wxss | 550 +++++--- miniprogram/pages/points/records/index.js | 9 + miniprogram/pages/points/records/index.wxss | 335 ++++- miniprogram/pages/store/index.js | 139 +- miniprogram/pages/store/index.json | 3 +- miniprogram/pages/store/index.wxml | 116 +- miniprogram/pages/store/index.wxss | 371 +++++- miniprogram/pages/user/index.js | 254 +++- miniprogram/pages/user/index.json | 3 +- miniprogram/pages/user/index.wxml | 342 +++-- miniprogram/pages/user/index.wxss | 1116 ++++++++++++++--- server/.env | 4 +- server/src/app.js | 4 +- server/src/controllers/matchController.js | 156 +++ server/src/controllers/pointsController.js | 35 + server/src/controllers/userController.js | 101 +- server/src/models/index.js | 3 + server/src/routes/match.js | 3 + server/src/routes/points.js | 3 + server/src/routes/upload.js | 15 +- server/src/routes/user.js | 6 + server/src/utils/helper.js | 21 +- .../3658676b-da52-4725-a2a5-d03c9bac5dc8.jpeg | Bin 0 -> 4139 bytes .../a3f854e6-e663-4aed-82f4-4ada1bcc21ce.jpeg | Bin 0 -> 4139 bytes .../d78d705e-3e4f-4459-9bd5-3af0af114c0b.jpeg | Bin 0 -> 4139 bytes 74 files changed, 6849 insertions(+), 2042 deletions(-) delete mode 100644 admin/node_modules/.vite/deps/chunk-JUCAMQ7P.js delete mode 100644 admin/node_modules/.vite/deps/chunk-JUCAMQ7P.js.map rename admin/node_modules/.vite/deps/{chunk-6CKQ2YFZ.js => chunk-W7GFOP2W.js} (99%) rename admin/node_modules/.vite/deps/{chunk-6CKQ2YFZ.js.map => chunk-W7GFOP2W.js.map} (100%) delete mode 100644 admin/node_modules/.vite/deps/element-plus_es_components_collapse-item_style_css.js delete mode 100644 admin/node_modules/.vite/deps/element-plus_es_components_collapse-item_style_css.js.map delete mode 100644 admin/node_modules/.vite/deps/element-plus_es_components_collapse_style_css.js delete mode 100644 admin/node_modules/.vite/deps/element-plus_es_components_collapse_style_css.js.map delete mode 100644 admin/node_modules/.vite/deps/element-plus_es_components_descriptions-item_style_css.js delete mode 100644 admin/node_modules/.vite/deps/element-plus_es_components_descriptions-item_style_css.js.map delete mode 100644 admin/node_modules/.vite/deps/element-plus_es_components_descriptions_style_css.js delete mode 100644 admin/node_modules/.vite/deps/element-plus_es_components_descriptions_style_css.js.map delete mode 100644 admin/node_modules/.vite/deps/element-plus_es_components_divider_style_css.js delete mode 100644 admin/node_modules/.vite/deps/element-plus_es_components_divider_style_css.js.map delete mode 100644 admin/node_modules/.vite/deps/element-plus_es_components_image_style_css.js delete mode 100644 admin/node_modules/.vite/deps/element-plus_es_components_image_style_css.js.map delete mode 100644 admin/node_modules/.vite/deps/element-plus_es_components_switch_style_css.js delete mode 100644 admin/node_modules/.vite/deps/element-plus_es_components_switch_style_css.js.map delete mode 100644 admin/node_modules/.vite/deps/element-plus_es_components_text_style_css.js delete mode 100644 admin/node_modules/.vite/deps/element-plus_es_components_text_style_css.js.map delete mode 100644 admin/node_modules/.vite/deps/element-plus_es_components_upload_style_css.js delete mode 100644 admin/node_modules/.vite/deps/element-plus_es_components_upload_style_css.js.map create mode 100644 server/uploads/3658676b-da52-4725-a2a5-d03c9bac5dc8.jpeg create mode 100644 server/uploads/a3f854e6-e663-4aed-82f4-4ada1bcc21ce.jpeg create mode 100644 server/uploads/d78d705e-3e4f-4459-9bd5-3af0af114c0b.jpeg diff --git a/admin/node_modules/.vite/deps/_metadata.json b/admin/node_modules/.vite/deps/_metadata.json index ebfe4c12..7aa928d9 100644 --- a/admin/node_modules/.vite/deps/_metadata.json +++ b/admin/node_modules/.vite/deps/_metadata.json @@ -1,334 +1,277 @@ { - "hash": "72015d08", + "hash": "0b0fcdca", "configHash": "0bd4dba1", - "lockfileHash": "39601b45", - "browserHash": "b4faa0d4", + "lockfileHash": "45a4e0fd", + "browserHash": "0f8eb059", "optimized": { "@element-plus/icons-vue": { "src": "../../@element-plus/icons-vue/dist/index.js", "file": "@element-plus_icons-vue.js", - "fileHash": "a0465eac", + "fileHash": "63fef99e", "needsInterop": false }, "axios": { "src": "../../axios/index.js", "file": "axios.js", - "fileHash": "5dd68ace", + "fileHash": "c4c91103", "needsInterop": false }, "dayjs": { "src": "../../dayjs/dayjs.min.js", "file": "dayjs.js", - "fileHash": "43487a3d", + "fileHash": "45ab92bc", "needsInterop": true }, "element-plus": { "src": "../../element-plus/es/index.mjs", "file": "element-plus.js", - "fileHash": "641ecf31", + "fileHash": "a9eb7ac2", "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": "c69676ce", + "fileHash": "2e509bd7", "needsInterop": false }, "pinia": { "src": "../../pinia/dist/pinia.mjs", "file": "pinia.js", - "fileHash": "bfa2bd84", - "needsInterop": false - }, - "vue": { - "src": "../../vue/dist/vue.runtime.esm-bundler.js", - "file": "vue.js", - "fileHash": "95941b62", - "needsInterop": false - }, - "vue-router": { - "src": "../../vue-router/dist/vue-router.mjs", - "file": "vue-router.js", - "fileHash": "799d6557", - "needsInterop": false - }, - "element-plus/es": { - "src": "../../element-plus/es/index.mjs", - "file": "element-plus_es.js", - "fileHash": "bf7435a6", - "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": "69992cb3", - "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": "ebec77cf", - "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": "911a2184", - "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": "36203619", - "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": "3cb1f96d", - "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": "76e79a0a", - "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": "3871ef81", - "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": "3d6a71ca", - "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": "88603798", - "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": "9e766544", - "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": "b8031298", - "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": "a1a95087", - "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": "9aca49c4", - "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": "3bc001aa", - "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": "be124fe0", - "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": "c068ded5", - "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": "403f5873", - "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": "27234326", - "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": "235345a9", - "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": "9b08cd36", - "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": "9f589faf", - "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": "638d7e8c", - "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": "bb1da678", - "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": "eef180be", - "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": "4cb57571", - "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": "0f719728", - "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": "42e6236a", - "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": "20763031", - "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": "2b02ba24", - "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": "06a565f3", - "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": "0c0e3f42", - "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": "b8c8ea9a", - "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": "4174369f", - "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": "3b71271e", - "needsInterop": false - }, - "element-plus/es/components/upload/style/css": { - "src": "../../element-plus/es/components/upload/style/css.mjs", - "file": "element-plus_es_components_upload_style_css.js", - "fileHash": "e7cad837", - "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": "4e0a87a4", - "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": "c846d7f7", - "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": "3e8a90d0", - "needsInterop": false - }, - "element-plus/es/components/collapse/style/css": { - "src": "../../element-plus/es/components/collapse/style/css.mjs", - "file": "element-plus_es_components_collapse_style_css.js", - "fileHash": "51560080", - "needsInterop": false - }, - "element-plus/es/components/collapse-item/style/css": { - "src": "../../element-plus/es/components/collapse-item/style/css.mjs", - "file": "element-plus_es_components_collapse-item_style_css.js", - "fileHash": "94bca0a2", + "fileHash": "5573d424", "needsInterop": false }, "qrcode": { "src": "../../qrcode/lib/browser.js", "file": "qrcode.js", - "fileHash": "ad0f2956", + "fileHash": "2f402876", "needsInterop": true + }, + "vue": { + "src": "../../vue/dist/vue.runtime.esm-bundler.js", + "file": "vue.js", + "fileHash": "0f609a55", + "needsInterop": false + }, + "vue-router": { + "src": "../../vue-router/dist/vue-router.mjs", + "file": "vue-router.js", + "fileHash": "25c74ef8", + "needsInterop": false + }, + "element-plus/es": { + "src": "../../element-plus/es/index.mjs", + "file": "element-plus_es.js", + "fileHash": "a13f022c", + "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": "b4220b72", + "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": "0122de81", + "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": "f74604cc", + "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": "664f43b9", + "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": "ff29c02c", + "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": "9516c0f3", + "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": "acc11f71", + "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": "63d6cae1", + "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": "8d500fe6", + "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": "dff655d7", + "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": "18fa9e47", + "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": "ec5bec4c", + "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": "4e0ee820", + "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": "d81f84e6", + "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": "05c2c583", + "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": "a80d717e", + "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": "e687ec69", + "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": "59835c76", + "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": "23e3be80", + "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": "85b49429", + "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": "936aa490", + "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": "875c584d", + "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": "b43c6781", + "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": "e712c224", + "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": "b95e1cd9", + "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": "c8593318", + "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": "67b6ba2c", + "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": "4e4350dd", + "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": "43cd095d", + "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": "86c77dd9", + "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": "e7a084fb", + "needsInterop": false } }, "chunks": { - "chunk-4PW274X2": { - "file": "chunk-4PW274X2.js" - }, - "chunk-JUCAMQ7P": { - "file": "chunk-JUCAMQ7P.js" + "chunk-SMFPDFTD": { + "file": "chunk-SMFPDFTD.js" }, "chunk-75C4BP7B": { "file": "chunk-75C4BP7B.js" }, + "chunk-5KK3TTMN": { + "file": "chunk-5KK3TTMN.js" + }, "chunk-UBLR4G7Q": { "file": "chunk-UBLR4G7Q.js" }, - "chunk-B2YDYSZR": { - "file": "chunk-B2YDYSZR.js" + "chunk-NKQWFVTF": { + "file": "chunk-NKQWFVTF.js" }, "chunk-R5DNQ3QC": { "file": "chunk-R5DNQ3QC.js" }, - "chunk-5KK3TTMN": { - "file": "chunk-5KK3TTMN.js" - }, - "chunk-NKQWFVTF": { - "file": "chunk-NKQWFVTF.js" + "chunk-B2YDYSZR": { + "file": "chunk-B2YDYSZR.js" }, "chunk-REWOA3VH": { "file": "chunk-REWOA3VH.js" @@ -336,21 +279,18 @@ "chunk-TX5YLZ4O": { "file": "chunk-TX5YLZ4O.js" }, - "chunk-SMFPDFTD": { - "file": "chunk-SMFPDFTD.js" + "chunk-4PW274X2": { + "file": "chunk-4PW274X2.js" }, "chunk-IV6PSERC": { "file": "chunk-IV6PSERC.js" }, - "chunk-6CKQ2YFZ": { - "file": "chunk-6CKQ2YFZ.js" + "chunk-W7GFOP2W": { + "file": "chunk-W7GFOP2W.js" }, "chunk-OP4ZUAFM": { "file": "chunk-OP4ZUAFM.js" }, - "chunk-QZC7O2C6": { - "file": "chunk-QZC7O2C6.js" - }, "chunk-YFT6OQ5R": { "file": "chunk-YFT6OQ5R.js" }, @@ -360,6 +300,9 @@ "chunk-H2732BJL": { "file": "chunk-H2732BJL.js" }, + "chunk-QZC7O2C6": { + "file": "chunk-QZC7O2C6.js" + }, "chunk-G3PMV62Z": { "file": "chunk-G3PMV62Z.js" } diff --git a/admin/node_modules/.vite/deps/chunk-JUCAMQ7P.js b/admin/node_modules/.vite/deps/chunk-JUCAMQ7P.js deleted file mode 100644 index c24505ad..00000000 --- a/admin/node_modules/.vite/deps/chunk-JUCAMQ7P.js +++ /dev/null @@ -1,3 +0,0 @@ -// node_modules/element-plus/es/components/descriptions-item/style/css.mjs -import "E:/workspace/yingsha/admin/node_modules/element-plus/theme-chalk/el-descriptions-item.css"; -//# sourceMappingURL=chunk-JUCAMQ7P.js.map diff --git a/admin/node_modules/.vite/deps/chunk-JUCAMQ7P.js.map b/admin/node_modules/.vite/deps/chunk-JUCAMQ7P.js.map deleted file mode 100644 index 0a90deac..00000000 --- a/admin/node_modules/.vite/deps/chunk-JUCAMQ7P.js.map +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": 3, - "sources": ["../../element-plus/es/components/descriptions-item/style/css.mjs"], - "sourcesContent": ["import '../../base/style/css.mjs';\nimport 'element-plus/theme-chalk/el-descriptions-item.css';\n//# sourceMappingURL=css.mjs.map\n"], - "mappings": ";AACA,OAAO;", - "names": [] -} diff --git a/admin/node_modules/.vite/deps/chunk-6CKQ2YFZ.js b/admin/node_modules/.vite/deps/chunk-W7GFOP2W.js similarity index 99% rename from admin/node_modules/.vite/deps/chunk-6CKQ2YFZ.js rename to admin/node_modules/.vite/deps/chunk-W7GFOP2W.js index b8a7c882..a51b60f1 100644 --- a/admin/node_modules/.vite/deps/chunk-6CKQ2YFZ.js +++ b/admin/node_modules/.vite/deps/chunk-W7GFOP2W.js @@ -42,9 +42,6 @@ import { zoom_in_default, zoom_out_default } from "./chunk-OP4ZUAFM.js"; -import { - require_dayjs_min -} from "./chunk-QZC7O2C6.js"; import { isVue2 } from "./chunk-HYZ2CRGS.js"; @@ -136,6 +133,9 @@ import { withKeys, withModifiers } from "./chunk-H2732BJL.js"; +import { + require_dayjs_min +} from "./chunk-QZC7O2C6.js"; import { __commonJS, __toESM @@ -73659,4 +73659,4 @@ normalize-wheel-es/dist/index.mjs: * @license Modernizr 3.0.0pre (Custom Build) | MIT *) */ -//# sourceMappingURL=chunk-6CKQ2YFZ.js.map +//# sourceMappingURL=chunk-W7GFOP2W.js.map diff --git a/admin/node_modules/.vite/deps/chunk-6CKQ2YFZ.js.map b/admin/node_modules/.vite/deps/chunk-W7GFOP2W.js.map similarity index 100% rename from admin/node_modules/.vite/deps/chunk-6CKQ2YFZ.js.map rename to admin/node_modules/.vite/deps/chunk-W7GFOP2W.js.map diff --git a/admin/node_modules/.vite/deps/element-plus.js b/admin/node_modules/.vite/deps/element-plus.js index 41576738..ad4f922b 100644 --- a/admin/node_modules/.vite/deps/element-plus.js +++ b/admin/node_modules/.vite/deps/element-plus.js @@ -514,11 +514,11 @@ import { virtualizedScrollbarProps, watermarkProps, zIndexContextKey -} from "./chunk-6CKQ2YFZ.js"; +} from "./chunk-W7GFOP2W.js"; import "./chunk-OP4ZUAFM.js"; -import "./chunk-QZC7O2C6.js"; import "./chunk-HYZ2CRGS.js"; import "./chunk-H2732BJL.js"; +import "./chunk-QZC7O2C6.js"; import "./chunk-G3PMV62Z.js"; var export_dayjs = import_dayjs.default; export { diff --git a/admin/node_modules/.vite/deps/element-plus_es.js b/admin/node_modules/.vite/deps/element-plus_es.js index 4b4bc581..28e2a73e 100644 --- a/admin/node_modules/.vite/deps/element-plus_es.js +++ b/admin/node_modules/.vite/deps/element-plus_es.js @@ -514,11 +514,11 @@ import { virtualizedScrollbarProps, watermarkProps, zIndexContextKey -} from "./chunk-6CKQ2YFZ.js"; +} from "./chunk-W7GFOP2W.js"; import "./chunk-OP4ZUAFM.js"; -import "./chunk-QZC7O2C6.js"; import "./chunk-HYZ2CRGS.js"; import "./chunk-H2732BJL.js"; +import "./chunk-QZC7O2C6.js"; import "./chunk-G3PMV62Z.js"; var export_dayjs = import_dayjs.default; export { diff --git a/admin/node_modules/.vite/deps/element-plus_es_components_collapse-item_style_css.js b/admin/node_modules/.vite/deps/element-plus_es_components_collapse-item_style_css.js deleted file mode 100644 index 694afd40..00000000 --- a/admin/node_modules/.vite/deps/element-plus_es_components_collapse-item_style_css.js +++ /dev/null @@ -1,5 +0,0 @@ -import "./chunk-IV6PSERC.js"; - -// node_modules/element-plus/es/components/collapse-item/style/css.mjs -import "E:/workspace/yingsha/admin/node_modules/element-plus/theme-chalk/el-collapse-item.css"; -//# sourceMappingURL=element-plus_es_components_collapse-item_style_css.js.map diff --git a/admin/node_modules/.vite/deps/element-plus_es_components_collapse-item_style_css.js.map b/admin/node_modules/.vite/deps/element-plus_es_components_collapse-item_style_css.js.map deleted file mode 100644 index b4e7d894..00000000 --- a/admin/node_modules/.vite/deps/element-plus_es_components_collapse-item_style_css.js.map +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": 3, - "sources": ["../../element-plus/es/components/collapse-item/style/css.mjs"], - "sourcesContent": ["import '../../base/style/css.mjs';\nimport 'element-plus/theme-chalk/el-collapse-item.css';\n//# sourceMappingURL=css.mjs.map\n"], - "mappings": ";;;AACA,OAAO;", - "names": [] -} diff --git a/admin/node_modules/.vite/deps/element-plus_es_components_collapse_style_css.js b/admin/node_modules/.vite/deps/element-plus_es_components_collapse_style_css.js deleted file mode 100644 index faeaf841..00000000 --- a/admin/node_modules/.vite/deps/element-plus_es_components_collapse_style_css.js +++ /dev/null @@ -1,5 +0,0 @@ -import "./chunk-IV6PSERC.js"; - -// node_modules/element-plus/es/components/collapse/style/css.mjs -import "E:/workspace/yingsha/admin/node_modules/element-plus/theme-chalk/el-collapse.css"; -//# sourceMappingURL=element-plus_es_components_collapse_style_css.js.map diff --git a/admin/node_modules/.vite/deps/element-plus_es_components_collapse_style_css.js.map b/admin/node_modules/.vite/deps/element-plus_es_components_collapse_style_css.js.map deleted file mode 100644 index a6b72aed..00000000 --- a/admin/node_modules/.vite/deps/element-plus_es_components_collapse_style_css.js.map +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": 3, - "sources": ["../../element-plus/es/components/collapse/style/css.mjs"], - "sourcesContent": ["import '../../base/style/css.mjs';\nimport 'element-plus/theme-chalk/el-collapse.css';\n//# sourceMappingURL=css.mjs.map\n"], - "mappings": ";;;AACA,OAAO;", - "names": [] -} diff --git a/admin/node_modules/.vite/deps/element-plus_es_components_descriptions-item_style_css.js b/admin/node_modules/.vite/deps/element-plus_es_components_descriptions-item_style_css.js deleted file mode 100644 index a2296169..00000000 --- a/admin/node_modules/.vite/deps/element-plus_es_components_descriptions-item_style_css.js +++ /dev/null @@ -1,3 +0,0 @@ -import "./chunk-JUCAMQ7P.js"; -import "./chunk-IV6PSERC.js"; -//# sourceMappingURL=element-plus_es_components_descriptions-item_style_css.js.map diff --git a/admin/node_modules/.vite/deps/element-plus_es_components_descriptions-item_style_css.js.map b/admin/node_modules/.vite/deps/element-plus_es_components_descriptions-item_style_css.js.map deleted file mode 100644 index 98652118..00000000 --- a/admin/node_modules/.vite/deps/element-plus_es_components_descriptions-item_style_css.js.map +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": 3, - "sources": [], - "sourcesContent": [], - "mappings": "", - "names": [] -} diff --git a/admin/node_modules/.vite/deps/element-plus_es_components_descriptions_style_css.js b/admin/node_modules/.vite/deps/element-plus_es_components_descriptions_style_css.js deleted file mode 100644 index 6393dfa9..00000000 --- a/admin/node_modules/.vite/deps/element-plus_es_components_descriptions_style_css.js +++ /dev/null @@ -1,6 +0,0 @@ -import "./chunk-JUCAMQ7P.js"; -import "./chunk-IV6PSERC.js"; - -// node_modules/element-plus/es/components/descriptions/style/css.mjs -import "E:/workspace/yingsha/admin/node_modules/element-plus/theme-chalk/el-descriptions.css"; -//# sourceMappingURL=element-plus_es_components_descriptions_style_css.js.map diff --git a/admin/node_modules/.vite/deps/element-plus_es_components_descriptions_style_css.js.map b/admin/node_modules/.vite/deps/element-plus_es_components_descriptions_style_css.js.map deleted file mode 100644 index ee8e1988..00000000 --- a/admin/node_modules/.vite/deps/element-plus_es_components_descriptions_style_css.js.map +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": 3, - "sources": ["../../element-plus/es/components/descriptions/style/css.mjs"], - "sourcesContent": ["import '../../base/style/css.mjs';\nimport 'element-plus/theme-chalk/el-descriptions.css';\nimport '../../descriptions-item/style/css.mjs';\n//# sourceMappingURL=css.mjs.map\n"], - "mappings": ";;;;AACA,OAAO;", - "names": [] -} diff --git a/admin/node_modules/.vite/deps/element-plus_es_components_divider_style_css.js b/admin/node_modules/.vite/deps/element-plus_es_components_divider_style_css.js deleted file mode 100644 index 60fb63eb..00000000 --- a/admin/node_modules/.vite/deps/element-plus_es_components_divider_style_css.js +++ /dev/null @@ -1,5 +0,0 @@ -import "./chunk-IV6PSERC.js"; - -// node_modules/element-plus/es/components/divider/style/css.mjs -import "E:/workspace/yingsha/admin/node_modules/element-plus/theme-chalk/el-divider.css"; -//# sourceMappingURL=element-plus_es_components_divider_style_css.js.map diff --git a/admin/node_modules/.vite/deps/element-plus_es_components_divider_style_css.js.map b/admin/node_modules/.vite/deps/element-plus_es_components_divider_style_css.js.map deleted file mode 100644 index 1311d557..00000000 --- a/admin/node_modules/.vite/deps/element-plus_es_components_divider_style_css.js.map +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": 3, - "sources": ["../../element-plus/es/components/divider/style/css.mjs"], - "sourcesContent": ["import '../../base/style/css.mjs';\nimport 'element-plus/theme-chalk/el-divider.css';\n//# sourceMappingURL=css.mjs.map\n"], - "mappings": ";;;AACA,OAAO;", - "names": [] -} diff --git a/admin/node_modules/.vite/deps/element-plus_es_components_dropdown_style_css.js b/admin/node_modules/.vite/deps/element-plus_es_components_dropdown_style_css.js index fea2e829..8e5fc633 100644 --- a/admin/node_modules/.vite/deps/element-plus_es_components_dropdown_style_css.js +++ b/admin/node_modules/.vite/deps/element-plus_es_components_dropdown_style_css.js @@ -1,6 +1,6 @@ +import "./chunk-SMFPDFTD.js"; import "./chunk-REWOA3VH.js"; import "./chunk-TX5YLZ4O.js"; -import "./chunk-SMFPDFTD.js"; import "./chunk-IV6PSERC.js"; // node_modules/element-plus/es/components/button-group/style/css.mjs diff --git a/admin/node_modules/.vite/deps/element-plus_es_components_image_style_css.js b/admin/node_modules/.vite/deps/element-plus_es_components_image_style_css.js deleted file mode 100644 index df565ab2..00000000 --- a/admin/node_modules/.vite/deps/element-plus_es_components_image_style_css.js +++ /dev/null @@ -1,8 +0,0 @@ -import "./chunk-IV6PSERC.js"; - -// node_modules/element-plus/es/components/image/style/css.mjs -import "E:/workspace/yingsha/admin/node_modules/element-plus/theme-chalk/el-image.css"; - -// node_modules/element-plus/es/components/image-viewer/style/css.mjs -import "E:/workspace/yingsha/admin/node_modules/element-plus/theme-chalk/el-image-viewer.css"; -//# sourceMappingURL=element-plus_es_components_image_style_css.js.map diff --git a/admin/node_modules/.vite/deps/element-plus_es_components_image_style_css.js.map b/admin/node_modules/.vite/deps/element-plus_es_components_image_style_css.js.map deleted file mode 100644 index 033cab80..00000000 --- a/admin/node_modules/.vite/deps/element-plus_es_components_image_style_css.js.map +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": 3, - "sources": ["../../element-plus/es/components/image/style/css.mjs", "../../element-plus/es/components/image-viewer/style/css.mjs"], - "sourcesContent": ["import '../../base/style/css.mjs';\nimport 'element-plus/theme-chalk/el-image.css';\nimport '../../image-viewer/style/css.mjs';\n//# sourceMappingURL=css.mjs.map\n", "import '../../base/style/css.mjs';\nimport 'element-plus/theme-chalk/el-image-viewer.css';\n//# sourceMappingURL=css.mjs.map\n"], - "mappings": ";;;AACA,OAAO;;;ACAP,OAAO;", - "names": [] -} diff --git a/admin/node_modules/.vite/deps/element-plus_es_components_pagination_style_css.js b/admin/node_modules/.vite/deps/element-plus_es_components_pagination_style_css.js index 231a9f7e..ff3bac4c 100644 --- a/admin/node_modules/.vite/deps/element-plus_es_components_pagination_style_css.js +++ b/admin/node_modules/.vite/deps/element-plus_es_components_pagination_style_css.js @@ -1,6 +1,6 @@ import "./chunk-75C4BP7B.js"; -import "./chunk-UBLR4G7Q.js"; import "./chunk-5KK3TTMN.js"; +import "./chunk-UBLR4G7Q.js"; import "./chunk-NKQWFVTF.js"; import "./chunk-REWOA3VH.js"; import "./chunk-TX5YLZ4O.js"; diff --git a/admin/node_modules/.vite/deps/element-plus_es_components_select_style_css.js b/admin/node_modules/.vite/deps/element-plus_es_components_select_style_css.js index 66002177..563c0e0b 100644 --- a/admin/node_modules/.vite/deps/element-plus_es_components_select_style_css.js +++ b/admin/node_modules/.vite/deps/element-plus_es_components_select_style_css.js @@ -1,6 +1,6 @@ import "./chunk-75C4BP7B.js"; -import "./chunk-UBLR4G7Q.js"; import "./chunk-5KK3TTMN.js"; +import "./chunk-UBLR4G7Q.js"; import "./chunk-REWOA3VH.js"; import "./chunk-TX5YLZ4O.js"; import "./chunk-IV6PSERC.js"; diff --git a/admin/node_modules/.vite/deps/element-plus_es_components_switch_style_css.js b/admin/node_modules/.vite/deps/element-plus_es_components_switch_style_css.js deleted file mode 100644 index 71cfb125..00000000 --- a/admin/node_modules/.vite/deps/element-plus_es_components_switch_style_css.js +++ /dev/null @@ -1,5 +0,0 @@ -import "./chunk-IV6PSERC.js"; - -// node_modules/element-plus/es/components/switch/style/css.mjs -import "E:/workspace/yingsha/admin/node_modules/element-plus/theme-chalk/el-switch.css"; -//# sourceMappingURL=element-plus_es_components_switch_style_css.js.map diff --git a/admin/node_modules/.vite/deps/element-plus_es_components_switch_style_css.js.map b/admin/node_modules/.vite/deps/element-plus_es_components_switch_style_css.js.map deleted file mode 100644 index 4d224fc5..00000000 --- a/admin/node_modules/.vite/deps/element-plus_es_components_switch_style_css.js.map +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": 3, - "sources": ["../../element-plus/es/components/switch/style/css.mjs"], - "sourcesContent": ["import '../../base/style/css.mjs';\nimport 'element-plus/theme-chalk/el-switch.css';\n//# sourceMappingURL=css.mjs.map\n"], - "mappings": ";;;AACA,OAAO;", - "names": [] -} diff --git a/admin/node_modules/.vite/deps/element-plus_es_components_table-column_style_css.js b/admin/node_modules/.vite/deps/element-plus_es_components_table-column_style_css.js index 66d448f9..2671bd87 100644 --- a/admin/node_modules/.vite/deps/element-plus_es_components_table-column_style_css.js +++ b/admin/node_modules/.vite/deps/element-plus_es_components_table-column_style_css.js @@ -1,5 +1,5 @@ -import "./chunk-B2YDYSZR.js"; import "./chunk-5KK3TTMN.js"; +import "./chunk-B2YDYSZR.js"; import "./chunk-IV6PSERC.js"; // node_modules/element-plus/es/components/table-column/style/css.mjs diff --git a/admin/node_modules/.vite/deps/element-plus_es_components_table_style_css.js b/admin/node_modules/.vite/deps/element-plus_es_components_table_style_css.js index 5bf77a91..6df74b60 100644 --- a/admin/node_modules/.vite/deps/element-plus_es_components_table_style_css.js +++ b/admin/node_modules/.vite/deps/element-plus_es_components_table_style_css.js @@ -1,5 +1,5 @@ -import "./chunk-B2YDYSZR.js"; import "./chunk-R5DNQ3QC.js"; +import "./chunk-B2YDYSZR.js"; import "./chunk-REWOA3VH.js"; import "./chunk-TX5YLZ4O.js"; import "./chunk-IV6PSERC.js"; diff --git a/admin/node_modules/.vite/deps/element-plus_es_components_text_style_css.js b/admin/node_modules/.vite/deps/element-plus_es_components_text_style_css.js deleted file mode 100644 index 5a1a2a82..00000000 --- a/admin/node_modules/.vite/deps/element-plus_es_components_text_style_css.js +++ /dev/null @@ -1,5 +0,0 @@ -import "./chunk-IV6PSERC.js"; - -// node_modules/element-plus/es/components/text/style/css.mjs -import "E:/workspace/yingsha/admin/node_modules/element-plus/theme-chalk/el-text.css"; -//# sourceMappingURL=element-plus_es_components_text_style_css.js.map diff --git a/admin/node_modules/.vite/deps/element-plus_es_components_text_style_css.js.map b/admin/node_modules/.vite/deps/element-plus_es_components_text_style_css.js.map deleted file mode 100644 index b2af8289..00000000 --- a/admin/node_modules/.vite/deps/element-plus_es_components_text_style_css.js.map +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": 3, - "sources": ["../../element-plus/es/components/text/style/css.mjs"], - "sourcesContent": ["import '../../base/style/css.mjs';\nimport 'element-plus/theme-chalk/el-text.css';\n//# sourceMappingURL=css.mjs.map\n"], - "mappings": ";;;AACA,OAAO;", - "names": [] -} diff --git a/admin/node_modules/.vite/deps/element-plus_es_components_upload_style_css.js b/admin/node_modules/.vite/deps/element-plus_es_components_upload_style_css.js deleted file mode 100644 index 1176a0c8..00000000 --- a/admin/node_modules/.vite/deps/element-plus_es_components_upload_style_css.js +++ /dev/null @@ -1,8 +0,0 @@ -import "./chunk-IV6PSERC.js"; - -// node_modules/element-plus/es/components/upload/style/css.mjs -import "E:/workspace/yingsha/admin/node_modules/element-plus/theme-chalk/el-upload.css"; - -// node_modules/element-plus/es/components/progress/style/css.mjs -import "E:/workspace/yingsha/admin/node_modules/element-plus/theme-chalk/el-progress.css"; -//# sourceMappingURL=element-plus_es_components_upload_style_css.js.map diff --git a/admin/node_modules/.vite/deps/element-plus_es_components_upload_style_css.js.map b/admin/node_modules/.vite/deps/element-plus_es_components_upload_style_css.js.map deleted file mode 100644 index b269187e..00000000 --- a/admin/node_modules/.vite/deps/element-plus_es_components_upload_style_css.js.map +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": 3, - "sources": ["../../element-plus/es/components/upload/style/css.mjs", "../../element-plus/es/components/progress/style/css.mjs"], - "sourcesContent": ["import '../../base/style/css.mjs';\nimport 'element-plus/theme-chalk/el-upload.css';\nimport '../../progress/style/css.mjs';\n//# sourceMappingURL=css.mjs.map\n", "import '../../base/style/css.mjs';\nimport 'element-plus/theme-chalk/el-progress.css';\n//# sourceMappingURL=css.mjs.map\n"], - "mappings": ";;;AACA,OAAO;;;ACAP,OAAO;", - "names": [] -} diff --git a/miniprogram/app.js b/miniprogram/app.js index 27e0cbb9..515e101a 100644 --- a/miniprogram/app.js +++ b/miniprogram/app.js @@ -157,6 +157,9 @@ App({ (res) => { if (res.data && res.data.length > 0) { this.globalData.ladderUser = res.data[0]; + } else { + // 没有天梯用户时清空 + this.globalData.ladderUser = null; } return res.data; } diff --git a/miniprogram/app.wxss b/miniprogram/app.wxss index c8b9b98c..158e0a3c 100644 --- a/miniprogram/app.wxss +++ b/miniprogram/app.wxss @@ -1,259 +1,613 @@ -/* 全局样式 */ +/* ========================================== + 影沙俱乐部 - 全局样式 + 设计理念:浅色高级感 · 橙色点缀 · 流畅动画 + ========================================== */ + page { - --primary-color: #FF6B35; + /* 主色调:活力橙系 */ + --primary: #FF6B35; + --primary-dark: #E85A28; --primary-light: #FF8C5A; - --secondary-color: #2EC4B6; - --accent-color: #FFBA08; - --dark-bg: #1A1A2E; - --light-bg: #F8F9FA; - --text-primary: #2C3E50; - --text-secondary: #7F8C8D; - --border-color: #E8E8E8; + --primary-soft: #FFF0EB; + --primary-gradient: linear-gradient(135deg, #FF6B35 0%, #FF8C5A 50%, #FFBA08 100%); + --primary-gradient-soft: linear-gradient(135deg, rgba(255,107,53,0.1) 0%, rgba(255,186,8,0.05) 100%); - font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', sans-serif; + /* 强调色 */ + --accent: #00C9A7; + --accent-light: #E6FBF7; + --accent-soft: rgba(0, 201, 167, 0.1); + + /* 浅色背景系 */ + --bg-page: #F7F8FA; + --bg-white: #FFFFFF; + --bg-card: #FFFFFF; + --bg-card-hover: #FAFBFC; + --bg-soft: #F2F3F5; + --bg-warm: #FFFBF8; + + /* 文字颜色 */ + --text-primary: #1A1A1A; + --text-secondary: #5C5C5C; + --text-muted: #8C8C8C; + --text-hint: #BFBFBF; + --text-white: #FFFFFF; + + /* 边框 */ + --border-light: #EBEDF0; + --border-soft: #F0F1F2; + --border-primary: rgba(255, 107, 53, 0.2); + + /* 阴影 */ + --shadow-sm: 0 2rpx 8rpx rgba(0, 0, 0, 0.04); + --shadow-md: 0 4rpx 16rpx rgba(0, 0, 0, 0.06); + --shadow-lg: 0 8rpx 32rpx rgba(0, 0, 0, 0.08); + --shadow-primary: 0 8rpx 24rpx rgba(255, 107, 53, 0.25); + --shadow-card: 0 4rpx 20rpx rgba(0, 0, 0, 0.05); + + /* 圆角 */ + --radius-sm: 12rpx; + --radius-md: 16rpx; + --radius-lg: 24rpx; + --radius-xl: 32rpx; + --radius-full: 100rpx; + + font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'PingFang SC', 'Helvetica Neue', sans-serif; font-size: 28rpx; color: var(--text-primary); - background-color: var(--light-bg); + background: var(--bg-page); + line-height: 1.6; + -webkit-font-smoothing: antialiased; } -/* 通用样式 */ +/* ========================================== + 布局工具类 + ========================================== */ .container { - padding: 20rpx; -} - -.card { - background: #fff; - border-radius: 16rpx; padding: 24rpx; + min-height: 100vh; + background: var(--bg-page); +} + +.flex { display: flex; } +.flex-center { display: flex; align-items: center; justify-content: center; } +.flex-between { display: flex; align-items: center; justify-content: space-between; } +.flex-column { display: flex; flex-direction: column; } +.flex-1 { flex: 1; } + +/* ========================================== + 卡片组件 - 高级感设计 + ========================================== */ +.card { + background: var(--bg-card); + border-radius: var(--radius-lg); + padding: 28rpx; margin-bottom: 20rpx; - box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05); + box-shadow: var(--shadow-card); + position: relative; + overflow: hidden; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } -.flex { - display: flex; +.card:active { + transform: scale(0.985); + box-shadow: var(--shadow-sm); } -.flex-center { - display: flex; - align-items: center; - justify-content: center; +.card-highlight { + background: var(--bg-white); + border: 1rpx solid var(--border-primary); + box-shadow: var(--shadow-card), 0 0 0 1rpx rgba(255, 107, 53, 0.05); } -.flex-between { - display: flex; - align-items: center; - justify-content: space-between; +.card-highlight::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 4rpx; + background: var(--primary-gradient); } -.flex-column { - display: flex; - flex-direction: column; -} - -/* 按钮样式 */ +/* ========================================== + 按钮样式 - 精致设计 + ========================================== */ .btn-primary { - background: linear-gradient(135deg, var(--primary-color), var(--primary-light)); - color: #fff; + background: var(--primary-gradient); + color: var(--text-white); border: none; - border-radius: 40rpx; - padding: 20rpx 60rpx; + border-radius: var(--radius-full); + padding: 24rpx 64rpx; font-size: 30rpx; - font-weight: 500; + font-weight: 600; + letter-spacing: 2rpx; + box-shadow: var(--shadow-primary); + position: relative; + overflow: hidden; + transition: all 0.3s ease; +} + +.btn-primary:active { + transform: scale(0.96); + box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.3); +} + +.btn-primary::after { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent); + animation: btn-shimmer 2.5s ease-in-out infinite; +} + +@keyframes btn-shimmer { + 0% { left: -100%; } + 50%, 100% { left: 100%; } } .btn-secondary { - background: var(--secondary-color); - color: #fff; - border: none; - border-radius: 40rpx; - padding: 20rpx 60rpx; + background: var(--bg-white); + color: var(--primary); + border: 2rpx solid var(--primary); + border-radius: var(--radius-full); + padding: 22rpx 62rpx; font-size: 30rpx; -} - -.btn-outline { - background: transparent; - color: var(--primary-color); - border: 2rpx solid var(--primary-color); - border-radius: 40rpx; - padding: 18rpx 58rpx; - font-size: 30rpx; -} - -/* 等级标签 */ -.level-tag { - display: inline-block; - padding: 6rpx 16rpx; - border-radius: 20rpx; - font-size: 22rpx; font-weight: 500; + transition: all 0.3s ease; +} + +.btn-secondary:active { + background: var(--primary-soft); +} + +.btn-ghost { + background: var(--bg-soft); + color: var(--text-secondary); + border: none; + border-radius: var(--radius-full); + padding: 22rpx 48rpx; + font-size: 28rpx; + transition: all 0.3s ease; +} + +/* ========================================== + 等级标签 - 渐变精致风格 + ========================================== */ +.level-tag { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8rpx 20rpx; + border-radius: var(--radius-full); + font-size: 24rpx; + font-weight: 600; + letter-spacing: 1rpx; } .level-tag.lv1 { - background: #E8F5E9; - color: #4CAF50; + background: linear-gradient(135deg, #E8F5E9, #C8E6C9); + color: #2E7D32; } .level-tag.lv2 { - background: #E3F2FD; - color: #2196F3; + background: linear-gradient(135deg, #E3F2FD, #BBDEFB); + color: #1565C0; } .level-tag.lv3 { - background: #FFF3E0; - color: #FF9800; + background: linear-gradient(135deg, #FFF3E0, #FFE0B2); + color: #E65100; } .level-tag.lv4 { - background: #FCE4EC; - color: #E91E63; + background: linear-gradient(135deg, #FCE4EC, #F8BBD9); + color: #C2185B; } .level-tag.lv5 { - background: #F3E5F5; - color: #9C27B0; + background: linear-gradient(135deg, #F3E5F5, #E1BEE7); + color: #7B1FA2; } -/* 性别标签 */ -.gender-male { - color: #2196F3; -} - -.gender-female { - color: #E91E63; -} - -/* 排名样式 */ +/* ========================================== + 排名徽章 - 精致金银铜 + ========================================== */ .rank-badge { display: flex; align-items: center; justify-content: center; - width: 48rpx; - height: 48rpx; + width: 56rpx; + height: 56rpx; border-radius: 50%; - font-size: 24rpx; - font-weight: 600; + font-size: 26rpx; + font-weight: 700; + transition: transform 0.3s ease; } .rank-badge.top1 { - background: linear-gradient(135deg, #FFD700, #FFA500); - color: #fff; + background: linear-gradient(135deg, #FFD700 0%, #FFAA00 100%); + color: #8B4513; + box-shadow: 0 4rpx 16rpx rgba(255, 170, 0, 0.4); } .rank-badge.top2 { - background: linear-gradient(135deg, #C0C0C0, #A8A8A8); - color: #fff; + background: linear-gradient(135deg, #E8E8E8 0%, #C0C0C0 100%); + color: #4A4A4A; + box-shadow: 0 4rpx 12rpx rgba(192, 192, 192, 0.4); } .rank-badge.top3 { - background: linear-gradient(135deg, #CD7F32, #B87333); - color: #fff; + background: linear-gradient(135deg, #DEB887 0%, #CD853F 100%); + color: #5C4033; + box-shadow: 0 4rpx 12rpx rgba(205, 133, 63, 0.4); } .rank-badge.normal { - background: var(--light-bg); - color: var(--text-secondary); + background: var(--bg-soft); + color: var(--text-muted); } -/* 空状态 */ +/* ========================================== + 头像样式 + ========================================== */ +.avatar { + width: 80rpx; + height: 80rpx; + border-radius: 50%; + background: var(--bg-soft); + border: 2rpx solid var(--border-light); +} + +.avatar-lg { + width: 140rpx; + height: 140rpx; + border-radius: 50%; + border: 4rpx solid var(--bg-white); + box-shadow: var(--shadow-md); +} + +.avatar-xl { + width: 180rpx; + height: 180rpx; + border-radius: 50%; + border: 6rpx solid var(--bg-white); + box-shadow: 0 8rpx 32rpx rgba(255, 107, 53, 0.2); +} + +/* ========================================== + 空状态 + ========================================== */ .empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; - padding: 100rpx 0; - color: var(--text-secondary); + padding: 120rpx 48rpx; + color: var(--text-muted); } .empty-state image { width: 200rpx; height: 200rpx; - margin-bottom: 20rpx; - opacity: 0.5; + margin-bottom: 32rpx; + opacity: 0.7; } -/* 加载状态 */ +.empty-state text { + font-size: 28rpx; + margin-bottom: 8rpx; +} + +.empty-state .sub-text { + font-size: 24rpx; + color: var(--text-hint); +} + +/* ========================================== + 加载状态 + ========================================== */ .loading-state { display: flex; align-items: center; justify-content: center; - padding: 40rpx 0; - color: var(--text-secondary); + padding: 48rpx; + color: var(--text-muted); + font-size: 26rpx; } -/* 战力值变动 */ -.power-change { - font-weight: 600; -} - -.power-change.positive { - color: #52C41A; -} - -.power-change.negative { - color: #F5222D; -} - -/* 列表项 */ +/* ========================================== + 列表项 + ========================================== */ .list-item { display: flex; align-items: center; - padding: 24rpx; - background: #fff; - border-bottom: 1rpx solid var(--border-color); + padding: 28rpx 24rpx; + background: var(--bg-white); + border-bottom: 1rpx solid var(--border-soft); + transition: all 0.2s ease; +} + +.list-item:active { + background: var(--bg-card-hover); } .list-item:last-child { border-bottom: none; } -/* 头像 */ -.avatar { - width: 80rpx; - height: 80rpx; - border-radius: 50%; - background: var(--light-bg); -} - -.avatar-large { - width: 120rpx; - height: 120rpx; - border-radius: 50%; -} - -/* 标签 */ +/* ========================================== + 标签 + ========================================== */ .tag { display: inline-block; - padding: 4rpx 12rpx; + padding: 6rpx 16rpx; border-radius: 8rpx; font-size: 22rpx; + font-weight: 500; } .tag-success { - background: #E8F5E9; - color: #4CAF50; + background: var(--accent-light); + color: var(--accent); } .tag-warning { - background: #FFF3E0; - color: #FF9800; + background: #FFF8E6; + color: #FA8C16; } .tag-danger { - background: #FFEBEE; - color: #F44336; + background: #FFF1F0; + color: #FF4D4F; } -/* 页面标题 */ -.page-title { - font-size: 36rpx; +/* ========================================== + 战力值变动 + ========================================== */ +.power-change { font-weight: 600; - color: var(--text-primary); - margin-bottom: 20rpx; + font-size: 28rpx; } -/* 分割线 */ +.power-change.positive { + color: var(--accent); +} + +.power-change.positive::before { + content: '+'; +} + +.power-change.negative { + color: #FF4D4F; +} + +/* ========================================== + 分割线 + ========================================== */ .divider { height: 1rpx; - background: var(--border-color); - margin: 20rpx 0; + background: var(--border-soft); + margin: 24rpx 0; +} + +/* ========================================== + 页面标题 + ========================================== */ +.page-title { + font-size: 44rpx; + font-weight: 700; + color: var(--text-primary); + margin-bottom: 8rpx; + letter-spacing: 2rpx; +} + +.page-subtitle { + font-size: 26rpx; + color: var(--text-muted); +} + +/* ========================================== + 丰富的过场动画 + ========================================== */ + +/* 淡入上移 */ +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30rpx); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* 淡入缩放 */ +@keyframes fadeInScale { + from { + opacity: 0; + transform: scale(0.9); + } + to { + opacity: 1; + transform: scale(1); + } +} + +/* 从左滑入 */ +@keyframes slideInLeft { + from { + opacity: 0; + transform: translateX(-40rpx); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +/* 从右滑入 */ +@keyframes slideInRight { + from { + opacity: 0; + transform: translateX(40rpx); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +/* 弹性入场 */ +@keyframes bounceIn { + 0% { + opacity: 0; + transform: scale(0.8); + } + 50% { + transform: scale(1.05); + } + 100% { + opacity: 1; + transform: scale(1); + } +} + +/* 脉冲效果 */ +@keyframes pulse { + 0%, 100% { + transform: scale(1); + opacity: 1; + } + 50% { + transform: scale(1.02); + opacity: 0.9; + } +} + +/* 呼吸发光 */ +@keyframes breathe { + 0%, 100% { + box-shadow: 0 8rpx 24rpx rgba(255, 107, 53, 0.25); + } + 50% { + box-shadow: 0 8rpx 40rpx rgba(255, 107, 53, 0.4); + } +} + +/* 渐变流动 */ +@keyframes gradientFlow { + 0% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } +} + +/* 旋转 */ +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +/* 摇摆 */ +@keyframes swing { + 0%, 100% { transform: rotate(0deg); } + 25% { transform: rotate(3deg); } + 75% { transform: rotate(-3deg); } +} + +/* 闪烁 */ +@keyframes blink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.6; } +} + +/* 波纹扩散 */ +@keyframes ripple { + 0% { + transform: scale(1); + opacity: 0.4; + } + 100% { + transform: scale(1.5); + opacity: 0; + } +} + +/* 动画应用类 */ +.animate-fadeInUp { + animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) backwards; +} + +.animate-fadeInScale { + animation: fadeInScale 0.4s cubic-bezier(0.4, 0, 0.2, 1) backwards; +} + +.animate-slideInLeft { + animation: slideInLeft 0.5s cubic-bezier(0.4, 0, 0.2, 1) backwards; +} + +.animate-slideInRight { + animation: slideInRight 0.5s cubic-bezier(0.4, 0, 0.2, 1) backwards; +} + +.animate-bounceIn { + animation: bounceIn 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55) backwards; +} + +.animate-pulse { + animation: pulse 2s ease-in-out infinite; +} + +.animate-breathe { + animation: breathe 2.5s ease-in-out infinite; +} + +.animate-spin { + animation: spin 1s linear infinite; +} + +/* 延迟类 */ +.delay-1 { animation-delay: 0.1s; } +.delay-2 { animation-delay: 0.2s; } +.delay-3 { animation-delay: 0.3s; } +.delay-4 { animation-delay: 0.4s; } +.delay-5 { animation-delay: 0.5s; } + +/* ========================================== + 页面过渡效果 + ========================================== */ +.page-transition { + animation: fadeInUp 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* 列表项依次入场 */ +.stagger-item:nth-child(1) { animation-delay: 0s; } +.stagger-item:nth-child(2) { animation-delay: 0.05s; } +.stagger-item:nth-child(3) { animation-delay: 0.1s; } +.stagger-item:nth-child(4) { animation-delay: 0.15s; } +.stagger-item:nth-child(5) { animation-delay: 0.2s; } +.stagger-item:nth-child(6) { animation-delay: 0.25s; } +.stagger-item:nth-child(7) { animation-delay: 0.3s; } +.stagger-item:nth-child(8) { animation-delay: 0.35s; } +.stagger-item:nth-child(9) { animation-delay: 0.4s; } +.stagger-item:nth-child(10) { animation-delay: 0.45s; } + +/* ========================================== + 装饰元素 + ========================================== */ +.decoration-circle { + position: absolute; + border-radius: 50%; + background: var(--primary-gradient-soft); + pointer-events: none; +} + +.decoration-dot { + width: 8rpx; + height: 8rpx; + border-radius: 50%; + background: var(--primary); } diff --git a/miniprogram/pages/index/index.js b/miniprogram/pages/index/index.js index 859736e2..1f26461d 100644 --- a/miniprogram/pages/index/index.js +++ b/miniprogram/pages/index/index.js @@ -17,8 +17,28 @@ Page({ }, onShow() { - if (app.globalData.currentStore) { - this.setData({ currentStore: app.globalData.currentStore }) + const newStore = app.globalData.currentStore + const oldStoreId = this.data.currentStore?.storeId + + // 检查门店是否切换 + if (newStore && newStore.storeId !== oldStoreId) { + this.setData({ + currentStore: newStore, + page: 1, + hasMore: true, + list: [] + }) + this.fetchData() + } else if (app.globalData.storeChanged) { + // 全局标记门店已切换 + app.globalData.storeChanged = false + this.setData({ + currentStore: newStore, + page: 1, + hasMore: true, + list: [] + }) + this.fetchData() } }, @@ -36,13 +56,11 @@ Page({ }, async initData() { - // 确保已登录 + // 检查是否已登录(有 token) if (!app.globalData.token) { - try { - await app.login() - } catch (e) { - console.error('登录失败:', e) - } + // 未登录,跳转到用户页面进行登录 + wx.switchTab({ url: '/pages/user/index' }) + return } // 获取当前门店 @@ -52,6 +70,10 @@ Page({ this.fetchData() } catch (e) { console.error('获取门店失败:', e) + // 如果是认证失败,跳转到登录页 + if (e.code === 401) { + wx.switchTab({ url: '/pages/user/index' }) + } } }, diff --git a/miniprogram/pages/index/index.wxml b/miniprogram/pages/index/index.wxml index f77f4ad7..f060ab4a 100644 --- a/miniprogram/pages/index/index.wxml +++ b/miniprogram/pages/index/index.wxml @@ -1,80 +1,119 @@ - - - - - - - {{currentStore.storeName || '选择门店'}} - - - - - - - 全部 - 男子组 - 女子组 - - - - - - 排名 - 选手 - 等级 - 战力 - - - - - - - {{item.rank}} - - - - - - - - Lv{{item.level}} - - - {{item.powerScore}} - + + + + + + + + + + + + {{currentStore.storeName || '请选择门店'}} - + + 切换门店 + + + - - - 暂无排名数据 - 每月完成3场比赛即可上榜 + + + 天梯排名 + 挑战自我,超越巅峰 - - - 加载中... + + + + + + 全部 + + + ♂ 男子 + + + ♀ 女子 + + + + + + + + + + + {{item.rank === 1 ? '👑' : item.rank === 2 ? '🥈' : '🥉'}} + {{item.rank}} + + + + + + + + {{item.realName}} + + Lv{{item.level}} + 胜率 {{item.winRate}}% + + + + + + {{item.powerScore}} + 战力 + + + + + + + + 暂无排名数据 + 每月完成3场比赛即可上榜 + + + + + 加载中... + + + + + — 已显示全部选手 — + + + + + + diff --git a/miniprogram/pages/index/index.wxss b/miniprogram/pages/index/index.wxss index 852b5f1b..72bf65f8 100644 --- a/miniprogram/pages/index/index.wxss +++ b/miniprogram/pages/index/index.wxss @@ -1,149 +1,340 @@ -/* 门店选择器 */ -.store-selector { +/* ========================================== + 天梯排名页面 - 浅色高级感设计 + ========================================== */ + +.page-container { + min-height: 100vh; + background: var(--bg-page); + position: relative; + overflow: hidden; +} + +/* 顶部装饰背景 */ +.hero-section { + position: relative; + padding: 32rpx 24rpx 24rpx; + background: linear-gradient(180deg, #FFF5F0 0%, var(--bg-page) 100%); +} + +.hero-pattern { + position: absolute; + top: -80rpx; + right: -60rpx; + width: 300rpx; + height: 300rpx; + background: radial-gradient(circle, rgba(255, 107, 53, 0.1) 0%, transparent 70%); + border-radius: 50%; + animation: pulse 4s ease-in-out infinite; +} + +.hero-pattern-2 { + position: absolute; + top: 120rpx; + left: -80rpx; + width: 200rpx; + height: 200rpx; + background: radial-gradient(circle, rgba(0, 201, 167, 0.06) 0%, transparent 70%); + border-radius: 50%; +} + +/* 门店信息头部 */ +.store-header { + position: relative; + z-index: 1; display: flex; align-items: center; justify-content: space-between; - background: #fff; - border-radius: 16rpx; - padding: 24rpx; margin-bottom: 20rpx; + animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1); } .store-info { display: flex; align-items: center; + gap: 12rpx; } -.store-icon { - width: 40rpx; - height: 40rpx; - margin-right: 16rpx; +.store-dot { + width: 12rpx; + height: 12rpx; + border-radius: 50%; + background: var(--accent); + animation: pulse 2s ease-in-out infinite; } .store-name { - font-size: 30rpx; - font-weight: 500; -} - -.arrow-icon { - width: 32rpx; - height: 32rpx; - opacity: 0.5; -} - -/* 性别筛选 */ -.gender-tabs { - display: flex; - background: #fff; - border-radius: 16rpx; - padding: 8rpx; - margin-bottom: 20rpx; -} - -.gender-tabs .tab { - flex: 1; - text-align: center; - padding: 16rpx 0; font-size: 28rpx; - color: var(--text-secondary); - border-radius: 12rpx; - transition: all 0.3s; + font-weight: 600; + color: var(--text-primary); } -.gender-tabs .tab.active { - background: var(--primary-color); - color: #fff; - font-weight: 500; +.change-store-btn { + display: flex; + align-items: center; + gap: 6rpx; + padding: 12rpx 20rpx; + background: var(--bg-white); + border-radius: var(--radius-full); + box-shadow: var(--shadow-sm); + transition: all 0.3s ease; +} + +.change-store-btn:active { + transform: scale(0.96); +} + +.change-store-text { + font-size: 24rpx; + color: var(--text-secondary); +} + +.change-store-arrow { + font-size: 20rpx; + color: var(--text-muted); +} + +/* 页面标题 */ +.page-header { + position: relative; + z-index: 1; + text-align: center; + margin-bottom: 8rpx; +} + +.page-title { + display: block; + font-size: 44rpx; + font-weight: 700; + color: var(--text-primary); + margin-bottom: 8rpx; + letter-spacing: 2rpx; +} + +.page-subtitle { + display: block; + font-size: 26rpx; + color: var(--text-muted); +} + +/* 主要内容区域 */ +.main-content { + padding: 0 24rpx; +} + +/* 筛选标签栏 */ +.filter-bar { + display: flex; + gap: 16rpx; + margin-bottom: 24rpx; +} + +.filter-item { + flex: 1; + padding: 20rpx; + background: var(--bg-white); + border-radius: var(--radius-full); + text-align: center; + font-size: 26rpx; + color: var(--text-secondary); + box-shadow: var(--shadow-sm); + transition: all 0.3s ease; +} + +.filter-item.active { + background: var(--primary-gradient); + color: var(--text-white); + box-shadow: var(--shadow-primary); +} + +.filter-item:active { + transform: scale(0.96); } /* 排名列表 */ .ranking-list { - background: #fff; - border-radius: 16rpx; - overflow: hidden; + } -.list-header { +.ranking-scroll { + +} + +.ranking-item { display: flex; align-items: center; padding: 20rpx 24rpx; - background: var(--light-bg); - font-size: 24rpx; - color: var(--text-secondary); + background: var(--bg-white); + border-radius: var(--radius-lg); + margin-bottom: 12rpx; + box-shadow: var(--shadow-sm); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } -.list-item { +.ranking-item:active { + transform: scale(0.98); +} + +.ranking-item.top-rank { + background: linear-gradient(135deg, #FFFBF8, var(--bg-white)); + border: 1rpx solid rgba(255, 107, 53, 0.1); +} + +/* 排名徽章 */ +.rank-badge { + width: 52rpx; + height: 52rpx; display: flex; align-items: center; - padding: 24rpx; - border-bottom: 1rpx solid var(--border-color); + justify-content: center; + border-radius: 50%; + font-size: 26rpx; + font-weight: 700; + margin-right: 16rpx; + flex-shrink: 0; } -.list-item:last-child { - border-bottom: none; +.rank-badge.top1 { + background: linear-gradient(135deg, #FFE082 0%, #FFD700 100%); + color: #8B4513; + box-shadow: 0 4rpx 16rpx rgba(255, 215, 0, 0.4); + font-size: 28rpx; } -.col-rank { - width: 80rpx; +.rank-badge.top2 { + background: linear-gradient(135deg, #F5F5F5 0%, #C0C0C0 100%); + color: #4A4A4A; + box-shadow: 0 4rpx 12rpx rgba(192, 192, 192, 0.4); + font-size: 28rpx; } -.col-user { - flex: 1; - display: flex; - align-items: center; +.rank-badge.top3 { + background: linear-gradient(135deg, #DEB887 0%, #CD853F 100%); + color: #5C4033; + box-shadow: 0 4rpx 12rpx rgba(205, 133, 63, 0.4); + font-size: 28rpx; } -.col-level { - width: 120rpx; - text-align: center; +.rank-badge.normal { + background: var(--bg-soft); + color: var(--text-muted); } -.col-power { - width: 100rpx; - text-align: right; -} - -.list-item .avatar { +/* 选手头像 */ +.player-avatar { width: 72rpx; height: 72rpx; border-radius: 50%; margin-right: 16rpx; - background: var(--light-bg); + border: 2rpx solid var(--bg-white); + box-shadow: var(--shadow-sm); + flex-shrink: 0; } -.list-item .user-info { - display: flex; - flex-direction: column; +/* 选手信息 */ +.player-info { + flex: 1; + overflow: hidden; } -.list-item .name { +.player-name { + display: block; font-size: 28rpx; - font-weight: 500; - margin-bottom: 4rpx; -} - -.list-item .rate { - font-size: 22rpx; - color: var(--text-secondary); -} - -.list-item .power-score { - font-size: 32rpx; font-weight: 600; - color: var(--primary-color); + color: var(--text-primary); + margin-bottom: 6rpx; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.player-meta { + display: flex; + align-items: center; + gap: 12rpx; +} + +.player-level { + display: inline-flex; + padding: 4rpx 12rpx; + border-radius: var(--radius-full); + font-size: 20rpx; + font-weight: 600; +} + +.player-level.lv1 { background: #E8F5E9; color: #2E7D32; } +.player-level.lv2 { background: #E3F2FD; color: #1565C0; } +.player-level.lv3 { background: #FFF3E0; color: #E65100; } +.player-level.lv4 { background: #FCE4EC; color: #C2185B; } +.player-level.lv5 { background: #F3E5F5; color: #7B1FA2; } + +.player-stats { + font-size: 22rpx; + color: var(--text-muted); +} + +/* 战力值 */ +.player-power { + text-align: right; + flex-shrink: 0; +} + +.power-value { + display: block; + font-size: 32rpx; + font-weight: 700; + color: var(--primary); +} + +.power-label { + font-size: 22rpx; + color: var(--text-muted); } /* 空状态 */ .empty-state { - padding: 100rpx 0; + display: flex; + flex-direction: column; + align-items: center; + padding: 80rpx 48rpx; } -.empty-state image { - width: 240rpx; - height: 240rpx; +.empty-icon { + width: 160rpx; + height: 160rpx; + margin-bottom: 24rpx; + opacity: 0.7; } -.empty-state .sub-text { +.empty-title { + font-size: 28rpx; + color: var(--text-secondary); + margin-bottom: 8rpx; +} + +.empty-desc { font-size: 24rpx; - margin-top: 8rpx; + color: var(--text-muted); +} + +/* 加载状态 */ +.loading-state { + display: flex; + align-items: center; + justify-content: center; + padding: 48rpx; + color: var(--text-muted); + font-size: 26rpx; +} + +/* 加载更多 */ +.load-more { + text-align: center; + padding: 32rpx; + color: var(--text-muted); + font-size: 26rpx; +} + +/* 底部安全区域 */ +.safe-bottom { + height: 80rpx; } diff --git a/miniprogram/pages/match/challenge/index.js b/miniprogram/pages/match/challenge/index.js index c432c88d..db93be55 100644 --- a/miniprogram/pages/match/challenge/index.js +++ b/miniprogram/pages/match/challenge/index.js @@ -5,7 +5,8 @@ Page({ userInfo: null, ladderUser: null, currentStore: null, - pendingGames: [] + ongoingMatches: [], // 正在进行中的比赛 + pendingGames: [] // 待确认的比赛 }, onLoad() { @@ -13,17 +14,34 @@ Page({ }, onShow() { - this.refreshData() + this.initData() + }, + + async onPullDownRefresh() { + try { + await this.initData() + } finally { + wx.stopPullDownRefresh() + } }, async initData() { + // 检查是否已登录(有 token) if (!app.globalData.token) { - try { - await app.login() - await app.getCurrentStore() - } catch (e) { - console.error('登录失败:', e) + // 未登录,跳转到用户页面进行登录 + wx.switchTab({ url: '/pages/user/index' }) + return + } + + // 每次显示页面时重新获取门店和天梯信息 + try { + await app.getCurrentStore() + // 如果有门店,获取该门店的天梯信息 + if (app.globalData.currentStore?.storeId) { + await app.getLadderUser(app.globalData.currentStore.storeId) } + } catch (e) { + console.error('获取门店/天梯信息失败:', e) } this.refreshData() @@ -37,10 +55,51 @@ Page({ }) if (app.globalData.ladderUser) { + this.fetchOngoingMatches() this.fetchPendingGames() } }, + // 获取正在进行中的比赛 + async fetchOngoingMatches() { + try { + const res = await app.request('/api/match/ongoing', { + store_id: this.data.currentStore?.storeId + }) + this.setData({ ongoingMatches: res.data || [] }) + } catch (e) { + console.error('获取进行中比赛失败:', e) + } + }, + + // 手动刷新天梯信息 + async refreshLadderInfo() { + wx.showLoading({ title: '刷新中...' }) + + try { + // 重新获取门店信息 + await app.getCurrentStore() + + // 重新获取天梯信息 + if (app.globalData.currentStore?.storeId) { + await app.getLadderUser(app.globalData.currentStore.storeId) + } + + this.refreshData() + wx.hideLoading() + + if (app.globalData.ladderUser) { + wx.showToast({ title: '已加入天梯', icon: 'success' }) + } else { + wx.showToast({ title: '暂未开通天梯', icon: 'none' }) + } + } catch (e) { + wx.hideLoading() + console.error('刷新天梯信息失败:', e) + wx.showToast({ title: '刷新失败', icon: 'none' }) + } + }, + async fetchPendingGames() { try { const res = await app.request('/api/match/pending-confirm', { @@ -164,6 +223,26 @@ Page({ }) }, + goToStore() { + wx.navigateTo({ url: '/pages/store/index' }) + }, + + // 跳转到比赛详情 + goToMatchDetail(e) { + const match = e.currentTarget.dataset.match + if (match.type === 1) { + // 挑战赛详情 - 暂时跳转到历史记录页 + wx.navigateTo({ + url: `/pages/match/history/index` + }) + } else { + // 排位赛详情 + wx.navigateTo({ + url: `/pages/match/ranking/index?code=${match.matchCode}` + }) + } + }, + confirmGame(e) { const game = e.currentTarget.dataset.game diff --git a/miniprogram/pages/match/challenge/index.json b/miniprogram/pages/match/challenge/index.json index 5edee95c..f9e0ecb6 100644 --- a/miniprogram/pages/match/challenge/index.json +++ b/miniprogram/pages/match/challenge/index.json @@ -1,3 +1,4 @@ { - "navigationBarTitleText": "比赛" + "navigationBarTitleText": "比赛", + "enablePullDownRefresh": true } diff --git a/miniprogram/pages/match/challenge/index.wxml b/miniprogram/pages/match/challenge/index.wxml index 3d8c2564..045358e9 100644 --- a/miniprogram/pages/match/challenge/index.wxml +++ b/miniprogram/pages/match/challenge/index.wxml @@ -1,96 +1,211 @@ - - - - - - 仅天梯用户可使用比赛功能,请联系门店工作人员加入天梯系统 + + + + + - - - - - - - - {{ladderUser.realName}} - - Lv{{ladderUser.level}} - 战力 {{ladderUser.powerScore}} + + + + + 🏸 发起挑战 + 扫描对手会员码,开启对决 + + + + + 📍 + {{currentStore.storeName}} + + + + + + 🏸 + + 暂未开通天梯 + 请联系门店工作人员加入天梯系统 + + + 刷新 + + + + + + + + + + + + - - - - - - 挑战赛 - - 扫描对手会员码发起挑战,挑战赛权重 x1.5 - - - - - - - - 排位赛 - - 扫描比赛二维码加入排位赛 - - - - - - - 待确认比分 - {{pendingGames.length}} - - - - - vs {{item.opponentName}} - {{item.myScore}} : {{item.opponentScore}} + + + + + ⚔️ - 确认 + 挑战赛 + 1v1 对决 + 权重 ×1.5 + + + + + 🏆 + + 排位赛 + 多人竞技 + 扫码加入 - - - - - - 战力值规则 - - - - 胜方 - +15 基础分 + + + + + 🔥 + 进行中的比赛 + + {{ongoingMatches.length}} + + + + + + {{item.typeName}} + + + {{item.statusName}} + + + + + + + + + VS {{item.opponent.realName}} + + Lv{{item.opponent.level}} + 战力 {{item.opponent.powerScore}} + + + + + + + + {{item.name || '排位赛'}} + + {{item.stageName}} + {{item.playerCount}}人参赛 + + + 当前对手: + {{item.opponent.realName}} + + + ⏳ 等待中 + 🎾 比赛中 + ✅ 已完成 + + + + + + 权重 ×{{item.weight}} + 查看详情 › + + + - - 败方 - -5 基础分 + + + + + 📋 待确认比分 + {{pendingGames.length}} + + + + + VS + {{item.opponentName}} + + {{item.myScore}} : {{item.opponentScore}} + 确认 + + - - 以下克上 - 分差≥100 额外+10% + + + + + + 📖 + 战力值规则 - - 新手保护 - Lv1-2前5场输分减半 + + + + + 胜方 + +15 基础分 + + + + + + 败方 + -5 基础分 + + + + + + 以下克上 + 额外 +10% + + + + 🛡 + + 新手保护 + 输分减半 + + - - 挑战冷却 - 同一对手30天限1次 + + 💡 同一对手30天内仅限挑战1次 diff --git a/miniprogram/pages/match/challenge/index.wxss b/miniprogram/pages/match/challenge/index.wxss index 00953a7f..edeefa6e 100644 --- a/miniprogram/pages/match/challenge/index.wxss +++ b/miniprogram/pages/match/challenge/index.wxss @@ -1,196 +1,933 @@ -/* 我的信息卡片 */ -.my-info-card { - background: linear-gradient(135deg, var(--dark-bg), #16213E); - border-radius: 20rpx; - padding: 30rpx; - margin-bottom: 20rpx; - color: #fff; +/* ========================================== + 挑战赛页面 - 全新设计 + ========================================== */ + +.page-container { + min-height: 100vh; + background: linear-gradient(180deg, #FEF7F3 0%, #FAFAFA 30%, #F5F5F5 100%); + position: relative; + overflow: hidden; } -.info-header { - display: flex; - align-items: center; +/* 顶部装饰背景 */ +.hero-bg { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 420rpx; + background: linear-gradient(135deg, #FF8A65 0%, #FF6B35 50%, #F4511E 100%); + border-radius: 0 0 60rpx 60rpx; + pointer-events: none; + overflow: hidden; } -.info-header .avatar { - width: 80rpx; - height: 80rpx; +.hero-bg::before { + content: ''; + position: absolute; + top: -100rpx; + right: -80rpx; + width: 300rpx; + height: 300rpx; + background: rgba(255, 255, 255, 0.15); border-radius: 50%; - margin-right: 20rpx; } -.info-meta .name { - font-size: 32rpx; - font-weight: 600; - margin-bottom: 8rpx; +.hero-bg::after { + content: ''; + position: absolute; + bottom: -60rpx; + left: -60rpx; + width: 200rpx; + height: 200rpx; + background: rgba(255, 255, 255, 0.1); + border-radius: 50%; +} + +.hero-pattern { + position: absolute; + top: 40rpx; + left: 50%; + transform: translateX(-50%); + width: 200rpx; + height: 200rpx; + background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='50' r='40' fill='none' stroke='rgba(255,255,255,0.15)' stroke-width='2' stroke-dasharray='10 5'/%3E%3C/svg%3E"); + background-size: contain; + animation: spin 20s linear infinite; +} + +@keyframes spin { + to { transform: translateX(-50%) rotate(360deg); } +} + +/* 主要内容 */ +.main-content { + position: relative; + z-index: 1; + padding: 24rpx; +} + +/* 页面标题 */ +.page-header { + text-align: center; + padding: 20rpx 0 30rpx; +} + +.page-title { display: block; -} - -.level-power { - display: flex; - align-items: center; - gap: 12rpx; -} - -.level-power .power { - font-size: 24rpx; - opacity: 0.8; -} - -/* 操作卡片 */ -.action-card { - background: #fff; - border-radius: 20rpx; - padding: 30rpx; - margin-bottom: 20rpx; -} - -.card-title { - display: flex; - align-items: center; - font-size: 32rpx; - font-weight: 600; - margin-bottom: 16rpx; -} - -.card-title image { - width: 44rpx; - height: 44rpx; - margin-right: 12rpx; -} - -.card-title .badge { - background: var(--primary-color); + font-size: 48rpx; + font-weight: 800; color: #fff; - font-size: 22rpx; - padding: 4rpx 12rpx; - border-radius: 20rpx; - margin-left: 12rpx; + margin-bottom: 10rpx; + text-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.15); + letter-spacing: 4rpx; } -.card-desc { +.page-subtitle { + display: block; font-size: 26rpx; - color: var(--text-secondary); - margin-bottom: 24rpx; + color: rgba(255, 255, 255, 0.9); } -.action-card .btn-primary, -.action-card .btn-secondary { +/* 当前门店栏 */ +.store-bar { display: flex; align-items: center; justify-content: center; - width: 100%; + gap: 10rpx; + padding: 16rpx 24rpx; + background: rgba(255, 255, 255, 0.95); + border-radius: 50rpx; + box-shadow: 0 8rpx 24rpx rgba(255, 107, 53, 0.2); + margin: 0 auto 24rpx; + width: fit-content; + backdrop-filter: blur(10px); } -.action-card button image { - width: 36rpx; - height: 36rpx; - margin-right: 12rpx; -} - -/* 待确认卡片 */ -.pending-card { - background: #fff; - border-radius: 20rpx; - padding: 30rpx; - margin-bottom: 20rpx; -} - -.pending-list { - margin-top: 16rpx; -} - -.pending-item { - display: flex; - align-items: center; - justify-content: space-between; - padding: 20rpx; - background: var(--light-bg); - border-radius: 12rpx; - margin-bottom: 12rpx; -} - -.pending-item:last-child { - margin-bottom: 0; -} - -.game-info .opponent { - display: block; +.store-icon { font-size: 28rpx; - font-weight: 500; - margin-bottom: 4rpx; } -.game-info .score { - font-size: 32rpx; - font-weight: 600; - color: var(--primary-color); -} - -.btn-confirm { - background: var(--primary-color); - color: #fff; - padding: 12rpx 24rpx; - border-radius: 20rpx; +.store-name { font-size: 26rpx; + font-weight: 600; + color: var(--text-primary); + max-width: 300rpx; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } -/* 规则卡片 */ -.rules-card { - background: #fff; - border-radius: 20rpx; - padding: 30rpx; -} - -.rules-content { - margin-top: 16rpx; -} - -.rule-item { - display: flex; - justify-content: space-between; - padding: 12rpx 0; - border-bottom: 1rpx solid var(--border-color); -} - -.rule-item:last-child { - border-bottom: none; -} - -.rule-label { - color: var(--text-secondary); -} - -.rule-value { - font-weight: 500; -} - -.rule-value.positive { - color: #52C41A; -} - -.rule-value.negative { - color: #F5222D; +.store-arrow { + font-size: 32rpx; + color: var(--primary); + font-weight: 600; } /* 提示卡片 */ .notice-card { display: flex; align-items: center; - background: #FFF9E6; - border-radius: 16rpx; - padding: 24rpx; - margin-bottom: 20rpx; + gap: 20rpx; + padding: 28rpx 24rpx; + background: linear-gradient(135deg, #FFF9F5 0%, #FFFFFF 100%); + border: 2rpx solid #FFE8D5; + border-radius: 24rpx; + margin-bottom: 24rpx; + box-shadow: 0 4rpx 16rpx rgba(255, 152, 0, 0.1); } -.notice-card image { - width: 40rpx; - height: 40rpx; - margin-right: 16rpx; +.notice-icon { + width: 80rpx; + height: 80rpx; + background: linear-gradient(135deg, #FFF3E0, #FFE0B2); + border-radius: 20rpx; + display: flex; + align-items: center; + justify-content: center; + font-size: 40rpx; } -.notice-card text { - font-size: 26rpx; - color: #B7791F; +.notice-content { flex: 1; } + +.notice-title { + display: block; + font-size: 30rpx; + font-weight: 700; + color: #E65100; + margin-bottom: 6rpx; +} + +.notice-desc { + display: block; + font-size: 24rpx; + color: #F57C00; +} + +.notice-action { + padding: 16rpx 28rpx; + background: linear-gradient(135deg, #FF8A65, #FF6B35); + border-radius: 50rpx; + box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.3); +} + +.notice-action:active { + transform: scale(0.95); +} + +.refresh-text { + font-size: 26rpx; + color: #fff; + font-weight: 600; +} + +/* ========================================== + 用户信息卡片 - 全新设计 + ========================================== */ +.user-card { + background: linear-gradient(135deg, #FFFFFF 0%, #FAFAFA 100%); + border-radius: 28rpx; + padding: 0; + margin-bottom: 24rpx; + box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.08); + overflow: hidden; + border: 1rpx solid rgba(255, 107, 53, 0.1); +} + +.user-card-inner { + display: flex; + align-items: center; + gap: 24rpx; + padding: 28rpx; + position: relative; +} + +.user-card-inner::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 6rpx; + background: linear-gradient(90deg, #FF8A65, #FF6B35, #FFB74D); +} + +.user-avatar-box { + width: 120rpx; + height: 120rpx; + flex-shrink: 0; + position: relative; +} + +.user-avatar-box::before { + content: ''; + position: absolute; + inset: -6rpx; + background: linear-gradient(135deg, #FF8A65, #FFB74D); + border-radius: 50%; + z-index: 0; +} + +.user-avatar { + width: 120rpx; + height: 120rpx; + border-radius: 50%; + border: 4rpx solid #fff; + position: relative; + z-index: 1; +} + +.user-info-box { + flex: 1; + min-width: 0; +} + +.user-name-row { + display: flex; + align-items: center; + gap: 14rpx; + margin-bottom: 16rpx; +} + +.user-name { + font-size: 36rpx; + font-weight: 800; + color: var(--text-primary); + letter-spacing: 2rpx; +} + +.user-level { + padding: 6rpx 18rpx; + border-radius: 50rpx; + font-size: 24rpx; + font-weight: 700; + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1); +} + +.user-level.lv1 { background: linear-gradient(135deg, #81C784, #66BB6A); color: #fff; } +.user-level.lv2 { background: linear-gradient(135deg, #64B5F6, #42A5F5); color: #fff; } +.user-level.lv3 { background: linear-gradient(135deg, #FFB74D, #FFA726); color: #fff; } +.user-level.lv4 { background: linear-gradient(135deg, #F06292, #EC407A); color: #fff; } +.user-level.lv5 { background: linear-gradient(135deg, #BA68C8, #AB47BC); color: #fff; } + +.user-stats-row { + display: flex; + gap: 32rpx; +} + +.mini-stat { + display: flex; + flex-direction: column; + align-items: flex-start; +} + +.mini-stat-value { + font-size: 36rpx; + font-weight: 800; + color: var(--text-primary); + line-height: 1.2; +} + +.mini-stat-value.win { + color: #00C853; +} + +.mini-stat-label { + font-size: 22rpx; + color: var(--text-muted); + margin-top: 4rpx; +} + +/* ========================================== + 扫码入口 - 全新设计 + ========================================== */ +.scan-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20rpx; + margin-bottom: 24rpx; +} + +.scan-card { + background: #fff; + border-radius: 28rpx; + padding: 32rpx 20rpx; + text-align: center; + box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.06); + position: relative; + overflow: hidden; + transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); + border: 2rpx solid transparent; +} + +.scan-card.challenge { + border-color: rgba(255, 107, 53, 0.15); +} + +.scan-card.ranking { + border-color: rgba(255, 193, 7, 0.2); +} + +.scan-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 100%; + opacity: 0; + transition: opacity 0.25s; +} + +.scan-card.challenge::before { + background: linear-gradient(180deg, rgba(255, 107, 53, 0.08) 0%, transparent 50%); +} + +.scan-card.ranking::before { + background: linear-gradient(180deg, rgba(255, 193, 7, 0.1) 0%, transparent 50%); +} + +.scan-card:active { + transform: scale(0.95); + box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08); +} + +.scan-card:active::before { + opacity: 1; +} + +.scan-icon-wrapper { + width: 100rpx; + height: 100rpx; + margin: 0 auto 20rpx; + border-radius: 28rpx; + display: flex; + align-items: center; + justify-content: center; + position: relative; +} + +.scan-card.challenge .scan-icon-wrapper { + background: linear-gradient(135deg, #FFE8DD, #FFCCBC); +} + +.scan-card.ranking .scan-icon-wrapper { + background: linear-gradient(135deg, #FFF8E1, #FFE082); +} + +.scan-icon { + font-size: 52rpx; +} + +.scan-title { + display: block; + font-size: 34rpx; + font-weight: 800; + color: var(--text-primary); + margin-bottom: 8rpx; + letter-spacing: 2rpx; +} + +.scan-desc { + display: block; + font-size: 24rpx; + color: var(--text-muted); + margin-bottom: 16rpx; +} + +.scan-badge { + display: inline-block; + padding: 8rpx 20rpx; + background: linear-gradient(135deg, #FF8A65, #FF6B35); + color: #fff; + font-size: 22rpx; + font-weight: 700; + border-radius: 50rpx; + box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.25); +} + +.scan-badge.accent { + background: linear-gradient(135deg, #FFD54F, #FFB300); + color: #5D4037; + box-shadow: 0 4rpx 12rpx rgba(255, 179, 0, 0.3); +} + +/* ========================================== + 进行中的比赛 + ========================================== */ +.ongoing-card { + background: #fff; + border-radius: 28rpx; + margin-bottom: 24rpx; + box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.06); + overflow: hidden; + border: 2rpx solid rgba(255, 87, 34, 0.15); +} + +.ongoing-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 20rpx 24rpx; + background: linear-gradient(90deg, #FFF3E0, #FFFFFF); + border-bottom: 1rpx solid rgba(255, 152, 0, 0.1); +} + +.ongoing-header-left { + display: flex; + align-items: center; + gap: 10rpx; +} + +.ongoing-icon { + font-size: 28rpx; +} + +.ongoing-title { + font-size: 28rpx; + font-weight: 700; + color: var(--text-primary); +} + +.ongoing-count { + min-width: 40rpx; + height: 40rpx; + padding: 0 14rpx; + background: linear-gradient(135deg, #FF5722, #FF8A65); + color: #fff; + font-size: 24rpx; + font-weight: 700; + border-radius: 20rpx; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 4rpx 12rpx rgba(255, 87, 34, 0.3); +} + +.ongoing-list { + padding: 16rpx; +} + +.ongoing-item { + background: linear-gradient(135deg, #FAFAFA, #F5F5F5); + border-radius: 20rpx; + margin-bottom: 16rpx; + overflow: hidden; + border: 2rpx solid transparent; + transition: all 0.2s; +} + +.ongoing-item:last-child { + margin-bottom: 0; +} + +.ongoing-item.challenge { + border-color: rgba(255, 107, 53, 0.15); +} + +.ongoing-item.ranking { + border-color: rgba(255, 193, 7, 0.2); +} + +.ongoing-item:active { + transform: scale(0.98); +} + +.ongoing-item-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 14rpx 18rpx; + background: rgba(255, 255, 255, 0.6); +} + +.match-type-tag { + padding: 6rpx 16rpx; + border-radius: 8rpx; + font-size: 22rpx; + font-weight: 700; +} + +.match-type-tag.challenge { + background: linear-gradient(135deg, #FFE8DD, #FFCCBC); + color: #E65100; +} + +.match-type-tag.ranking { + background: linear-gradient(135deg, #FFF8E1, #FFE082); + color: #F57C00; +} + +.match-status-tag { + padding: 6rpx 14rpx; + border-radius: 8rpx; + font-size: 20rpx; + font-weight: 600; +} + +.match-status-tag.waiting { + background: #E3F2FD; + color: #1565C0; +} + +.match-status-tag.playing { + background: #E8F5E9; + color: #2E7D32; + animation: pulse 2s infinite; +} + +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.7; } +} + +.ongoing-item-body { + padding: 18rpx; +} + +/* 挑战赛对手信息 */ +.opponent-info { + display: flex; + align-items: center; + gap: 16rpx; +} + +.opponent-avatar { + width: 80rpx; + height: 80rpx; + border-radius: 50%; + border: 3rpx solid #FFE0B2; +} + +.opponent-detail { + flex: 1; +} + +.opponent-name { + font-size: 30rpx; + font-weight: 700; + color: var(--text-primary); + margin-bottom: 6rpx; + display: block; +} + +.opponent-stats { + display: flex; + gap: 16rpx; +} + +.opponent-level { + padding: 4rpx 12rpx; + background: linear-gradient(135deg, #FFB74D, #FFA726); + color: #fff; + font-size: 20rpx; + font-weight: 600; + border-radius: 6rpx; +} + +.opponent-power { + font-size: 22rpx; + color: var(--text-muted); +} + +/* 排位赛信息 */ +.ranking-info { + display: flex; + flex-direction: column; + gap: 10rpx; +} + +.ranking-name { + font-size: 30rpx; + font-weight: 700; + color: var(--text-primary); +} + +.ranking-meta { + display: flex; + gap: 16rpx; + font-size: 22rpx; + color: var(--text-muted); +} + +.ranking-stage { + padding: 4rpx 12rpx; + background: #E3F2FD; + color: #1565C0; + border-radius: 6rpx; + font-weight: 600; +} + +.ranking-players { + color: var(--text-secondary); +} + +.current-opponent { + display: flex; + align-items: center; + gap: 8rpx; + font-size: 24rpx; +} + +.current-label { + color: var(--text-muted); +} + +.current-name { + color: var(--primary); + font-weight: 600; +} + +.my-status { + font-size: 24rpx; + font-weight: 600; +} + +.my-status.waiting { + color: #1565C0; +} + +.my-status.playing { + color: #2E7D32; +} + +.my-status.finished { + color: var(--text-muted); +} + +.ongoing-item-footer { + display: flex; + align-items: center; + justify-content: space-between; + padding: 14rpx 18rpx; + background: rgba(255, 255, 255, 0.8); + border-top: 1rpx solid rgba(0, 0, 0, 0.04); +} + +.match-weight { + padding: 4rpx 12rpx; + background: linear-gradient(135deg, #FF8A65, #FF6B35); + color: #fff; + font-size: 20rpx; + font-weight: 600; + border-radius: 6rpx; +} + +.enter-btn { + font-size: 24rpx; + color: var(--primary); + font-weight: 600; +} + +/* ========================================== + 待确认比赛 + ========================================== */ +.pending-card { + background: #fff; + border-radius: 28rpx; + margin-bottom: 24rpx; + box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.06); + overflow: hidden; + border: 2rpx solid rgba(255, 107, 53, 0.15); +} + +.pending-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 20rpx 24rpx; + background: linear-gradient(90deg, #FFF5F2, #FFFFFF); + border-bottom: 1rpx solid rgba(255, 107, 53, 0.1); +} + +.pending-title { + font-size: 28rpx; + font-weight: 700; + color: var(--text-primary); +} + +.pending-count { + min-width: 40rpx; + height: 40rpx; + padding: 0 14rpx; + background: linear-gradient(135deg, #FF8A65, #FF6B35); + color: #fff; + font-size: 24rpx; + font-weight: 700; + border-radius: 20rpx; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.3); +} + +.pending-list { + padding: 16rpx; +} + +.pending-item { + display: flex; + align-items: center; + padding: 18rpx 20rpx; + background: linear-gradient(135deg, #FAFAFA, #F5F5F5); + border-radius: 16rpx; + margin-bottom: 12rpx; + transition: all 0.2s; +} + +.pending-item:last-child { + margin-bottom: 0; +} + +.pending-item:active { + background: linear-gradient(135deg, #F5F5F5, #EEEEEE); +} + +.game-info { + flex: 1; + display: flex; + align-items: center; + gap: 12rpx; +} + +.vs-tag { + padding: 6rpx 14rpx; + background: linear-gradient(135deg, #FF8A65, #FF6B35); + color: #fff; + font-size: 20rpx; + font-weight: 800; + border-radius: 8rpx; +} + +.opponent-name { + font-size: 28rpx; + font-weight: 700; + color: var(--text-primary); +} + +.game-score { + font-size: 32rpx; + font-weight: 800; + color: var(--text-primary); + padding: 0 20rpx; + font-family: 'SF Mono', 'Monaco', monospace; +} + +.confirm-btn { + padding: 14rpx 28rpx; + background: linear-gradient(135deg, #00C853, #00E676); + color: #fff; + font-size: 24rpx; + font-weight: 700; + border-radius: 50rpx; + box-shadow: 0 4rpx 12rpx rgba(0, 200, 83, 0.3); +} + +.confirm-btn:active { + transform: scale(0.95); +} + +/* ========================================== + 规则卡片 + ========================================== */ +.rules-card { + background: #fff; + border-radius: 28rpx; + padding: 24rpx; + box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.06); +} + +.rules-header { + display: flex; + align-items: center; + gap: 12rpx; + padding-bottom: 18rpx; + margin-bottom: 20rpx; + border-bottom: 2rpx dashed rgba(0, 0, 0, 0.06); +} + +.rules-icon { + width: 48rpx; + height: 48rpx; + background: linear-gradient(135deg, #FFF3E0, #FFE0B2); + border-radius: 14rpx; + display: flex; + align-items: center; + justify-content: center; + font-size: 26rpx; +} + +.rules-title { + font-size: 30rpx; + font-weight: 700; + color: var(--text-primary); +} + +.rules-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 14rpx; +} + +.rule-item { + display: flex; + align-items: center; + gap: 14rpx; + padding: 18rpx; + background: linear-gradient(135deg, #FAFAFA, #F5F5F5); + border-radius: 18rpx; + transition: all 0.2s; +} + +.rule-item:active { + transform: scale(0.98); +} + +.rule-icon { + width: 48rpx; + height: 48rpx; + border-radius: 14rpx; + display: flex; + align-items: center; + justify-content: center; + font-size: 24rpx; + font-weight: 800; + flex-shrink: 0; +} + +.rule-icon.win { + background: linear-gradient(135deg, #E8F5E9, #C8E6C9); + color: #2E7D32; +} + +.rule-icon.lose { + background: linear-gradient(135deg, #FFEBEE, #FFCDD2); + color: #C62828; +} + +.rule-icon.bonus { + background: linear-gradient(135deg, #FFF8E1, #FFECB3); + color: #F57C00; +} + +.rule-icon.shield { + background: linear-gradient(135deg, #E3F2FD, #BBDEFB); + color: #1565C0; +} + +.rule-text { + flex: 1; + min-width: 0; +} + +.rule-label { + display: block; + font-size: 22rpx; + color: var(--text-muted); + margin-bottom: 4rpx; +} + +.rule-value { + display: block; + font-size: 26rpx; + font-weight: 700; + color: var(--text-primary); +} + +.rule-value.positive { + color: #2E7D32; +} + +.rule-value.negative { + color: #C62828; +} + +.rules-note { + margin-top: 18rpx; + padding: 18rpx; + background: linear-gradient(135deg, #FFF8E1, #FFFDE7); + border-radius: 14rpx; + text-align: center; + font-size: 24rpx; + color: #F57C00; + font-weight: 600; + border: 1rpx solid rgba(255, 152, 0, 0.15); +} + +/* 动画 */ +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30rpx); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.animate-fadeInUp { + animation: fadeInUp 0.5s ease-out forwards; +} diff --git a/miniprogram/pages/match/history/index.js b/miniprogram/pages/match/history/index.js index 2aea47f2..9b2b12f5 100644 --- a/miniprogram/pages/match/history/index.js +++ b/miniprogram/pages/match/history/index.js @@ -14,6 +14,15 @@ Page({ this.fetchMatches() }, + onShow() { + // 门店切换后刷新数据 + if (app.globalData.storeChanged) { + app.globalData.storeChanged = false + this.setData({ page: 1, hasMore: true, matches: [] }) + this.fetchMatches() + } + }, + onPullDownRefresh() { this.setData({ page: 1, hasMore: true }) this.fetchMatches().then(() => { diff --git a/miniprogram/pages/match/history/index.wxss b/miniprogram/pages/match/history/index.wxss index 5d54c07d..1ebce7f0 100644 --- a/miniprogram/pages/match/history/index.wxss +++ b/miniprogram/pages/match/history/index.wxss @@ -1,108 +1,364 @@ -.history-list { +/* ========================================== + 比赛记录页面 - 浅色高级感设计 + ========================================== */ + +.page-container { + min-height: 100vh; + background: var(--bg-page); + position: relative; +} + +/* 顶部装饰背景 */ +.hero-bg { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 300rpx; + background: linear-gradient(180deg, #FFF5F0 0%, var(--bg-page) 100%); + pointer-events: none; +} + +.hero-pattern { + position: absolute; + top: -60rpx; + right: -40rpx; + width: 240rpx; + height: 240rpx; + background: radial-gradient(circle, rgba(255, 107, 53, 0.08) 0%, transparent 70%); + border-radius: 50%; +} + +/* 页面标题 */ +.page-header { + position: relative; + z-index: 1; + text-align: center; + padding: 32rpx 24rpx 24rpx; + animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1); +} + +.page-title { + display: block; + font-size: 40rpx; + font-weight: 700; + color: var(--text-primary); + margin-bottom: 8rpx; + letter-spacing: 2rpx; +} + +.page-subtitle { + display: block; + font-size: 26rpx; + color: var(--text-muted); +} + +/* 筛选标签栏 */ +.filter-bar { + position: relative; + z-index: 1; display: flex; - flex-direction: column; - gap: 20rpx; -} - -.match-item { - background: #fff; - border-radius: 16rpx; - padding: 24rpx; -} - -.match-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 16rpx; -} - -.match-name { - font-size: 28rpx; - font-weight: 500; -} - -.match-type { - font-size: 22rpx; - color: var(--text-secondary); - background: var(--light-bg); - padding: 4rpx 12rpx; - border-radius: 10rpx; -} - -.match-content { - display: flex; - align-items: center; gap: 16rpx; - margin-bottom: 16rpx; + padding: 0 24rpx 24rpx; + animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s backwards; } -.result { - width: 60rpx; - height: 60rpx; +.filter-item { + flex: 1; display: flex; align-items: center; justify-content: center; - border-radius: 50%; - font-size: 28rpx; - font-weight: 600; + padding: 20rpx; + background: var(--bg-white); + border-radius: var(--radius-full); + box-shadow: var(--shadow-sm); + font-size: 26rpx; + color: var(--text-secondary); + transition: all 0.3s ease; } -.result.win { - background: #E8F5E9; - color: #4CAF50; +.filter-item.active { + background: var(--primary-gradient); + color: var(--text-white); + box-shadow: var(--shadow-primary); } -.result.lose { - background: #FFEBEE; - color: #F44336; +.filter-item:active { + transform: scale(0.96); } -.score-info { - flex: 1; +/* 比赛列表 */ +.match-list { + position: relative; + z-index: 1; + padding: 0 24rpx; +} + +.match-card { + background: var(--bg-white); + border-radius: var(--radius-lg); + margin-bottom: 20rpx; + box-shadow: var(--shadow-card); + overflow: hidden; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) backwards; +} + +.match-card:nth-child(1) { animation-delay: 0.15s; } +.match-card:nth-child(2) { animation-delay: 0.2s; } +.match-card:nth-child(3) { animation-delay: 0.25s; } +.match-card:nth-child(4) { animation-delay: 0.3s; } +.match-card:nth-child(5) { animation-delay: 0.35s; } + +.match-card:active { + transform: scale(0.98); +} + +.match-card.win { + border-left: 6rpx solid var(--accent); +} + +.match-card.lose { + border-left: 6rpx solid #FF6B6B; +} + +/* 比赛头部 */ +.match-header { display: flex; - flex-direction: column; + align-items: center; + justify-content: space-between; + padding: 20rpx 24rpx; + background: linear-gradient(90deg, #FAFBFC, var(--bg-white)); + border-bottom: 1rpx solid var(--border-soft); } -.opponent { - font-size: 28rpx; - margin-bottom: 4rpx; +.match-type { + display: flex; + align-items: center; + gap: 8rpx; } -.score { - font-size: 36rpx; - font-weight: 600; - color: var(--primary-color); -} - -.power-change { - font-size: 32rpx; +.type-badge { + padding: 6rpx 14rpx; + border-radius: var(--radius-full); + font-size: 22rpx; font-weight: 600; } -.power-change.positive { - color: #52C41A; +.type-badge.ladder { + background: var(--primary-soft); + color: var(--primary); } -.power-change.negative { - color: #F5222D; +.type-badge.friendly { + background: var(--accent-light); + color: var(--accent); } -.match-footer { - border-top: 1rpx solid var(--border-color); - padding-top: 12rpx; +.result-badge { + padding: 6rpx 14rpx; + border-radius: var(--radius-full); + font-size: 22rpx; + font-weight: 600; +} + +.result-badge.win { + background: var(--accent-light); + color: var(--accent); +} + +.result-badge.lose { + background: #FFF1F0; + color: #FF4D4F; } .match-time { font-size: 24rpx; - color: var(--text-secondary); + color: var(--text-muted); } +/* 比赛内容 */ +.match-content { + padding: 24rpx; +} + +.match-players { + display: flex; + align-items: center; + justify-content: space-between; +} + +.player-card { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; +} + +.player-card.self { + position: relative; +} + +.player-card.self::before { + content: '我'; + position: absolute; + top: -8rpx; + right: 20%; + padding: 2rpx 10rpx; + background: var(--primary); + color: #FFF; + font-size: 18rpx; + border-radius: var(--radius-full); +} + +.player-avatar { + width: 80rpx; + height: 80rpx; + border-radius: 50%; + margin-bottom: 12rpx; + border: 3rpx solid var(--bg-white); + box-shadow: var(--shadow-sm); +} + +.player-name { + font-size: 26rpx; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 6rpx; +} + +.player-level { + padding: 4rpx 12rpx; + border-radius: var(--radius-full); + font-size: 20rpx; + font-weight: 600; +} + +.player-level.lv1 { background: #E8F5E9; color: #2E7D32; } +.player-level.lv2 { background: #E3F2FD; color: #1565C0; } +.player-level.lv3 { background: #FFF3E0; color: #E65100; } +.player-level.lv4 { background: #FCE4EC; color: #C2185B; } +.player-level.lv5 { background: #F3E5F5; color: #7B1FA2; } + +/* 比分区域 */ +.score-area { + display: flex; + flex-direction: column; + align-items: center; + padding: 0 16rpx; +} + +.vs-text { + font-size: 24rpx; + color: var(--text-hint); + font-weight: 600; +} + +.score-display { + display: flex; + align-items: center; + gap: 12rpx; + margin-top: 8rpx; +} + +.score-value { + font-size: 44rpx; + font-weight: 700; + color: var(--text-primary); +} + +.score-colon { + font-size: 32rpx; + color: var(--text-hint); +} + +/* 比赛详情 */ +.match-details { + display: flex; + align-items: center; + justify-content: space-between; + margin-top: 20rpx; + padding-top: 20rpx; + border-top: 1rpx solid var(--border-soft); +} + +.detail-item { + display: flex; + align-items: center; + gap: 8rpx; +} + +.detail-label { + font-size: 24rpx; + color: var(--text-muted); +} + +.detail-value { + font-size: 26rpx; + font-weight: 600; +} + +.detail-value.positive { + color: var(--accent); +} + +.detail-value.positive::before { + content: '+'; +} + +.detail-value.negative { + color: #FF4D4F; +} + +/* 空状态 */ .empty-state { - padding: 100rpx 0; + display: flex; + flex-direction: column; + align-items: center; + padding: 100rpx 48rpx; + animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.2s backwards; } -.empty-state image { - width: 200rpx; - height: 200rpx; +.empty-icon { + width: 180rpx; + height: 180rpx; + margin-bottom: 32rpx; + opacity: 0.7; +} + +.empty-title { + font-size: 30rpx; + font-weight: 600; + color: var(--text-secondary); + margin-bottom: 12rpx; +} + +.empty-desc { + font-size: 26rpx; + color: var(--text-muted); + text-align: center; + line-height: 1.6; +} + +/* 加载状态 */ +.loading-state { + display: flex; + align-items: center; + justify-content: center; + padding: 48rpx; + color: var(--text-muted); + font-size: 26rpx; +} + +/* 底部安全区域 */ +.safe-bottom { + height: 80rpx; +} + +/* 加载更多 */ +.load-more { + text-align: center; + padding: 32rpx; + color: var(--text-muted); + font-size: 26rpx; } diff --git a/miniprogram/pages/match/ranking/index.js b/miniprogram/pages/match/ranking/index.js index bb77d227..4f9edd6d 100644 --- a/miniprogram/pages/match/ranking/index.js +++ b/miniprogram/pages/match/ranking/index.js @@ -1,10 +1,11 @@ const app = getApp() -const util = require('../../../utils/util') Page({ data: { matchCode: '', - match: {}, + match: { + players: [] + }, myPlayer: null, currentGame: null }, @@ -20,15 +21,6 @@ Page({ }) }, - getStatusText(status) { - return util.getMatchStatusText(status) - }, - - getStageText(stage) { - const texts = { 0: '报名中', 1: '循环赛', 2: '淘汰赛', 3: '已结束' } - return texts[stage] || '未知' - }, - async fetchMatchDetail() { try { const res = await app.request(`/api/match/ranking/${this.data.matchCode}`) diff --git a/miniprogram/pages/match/ranking/index.wxml b/miniprogram/pages/match/ranking/index.wxml index 5e40f751..46c089c9 100644 --- a/miniprogram/pages/match/ranking/index.wxml +++ b/miniprogram/pages/match/ranking/index.wxml @@ -1,58 +1,119 @@ - - - - {{match.name}} - {{getStatusText(match.status)}} + + + + + - - - 比赛码 - {{match.matchCode}} - - - 权重 - x{{match.weight}} - - - 阶段 - {{getStageText(match.stage)}} - - - 参赛人数 - {{match.players.length || 0}}人 - - - - - - 我的状态 - - {{myPlayer.status === 'playing' ? '比赛中' : myPlayer.status === 'finished' ? '已完成' : '等待匹配'}} - {{myPlayer.winCount}}胜 {{myPlayer.loseCount}}负 - - - 当前对手 - - {{currentGame.opponent.realName}} - Lv{{currentGame.opponent.level}} 战力{{currentGame.opponent.powerScore}} + + + + + 🏆 + {{match.name || '排位赛'}} + + + {{match.statusName || '待开始'}} - - - - 参赛选手 - - - {{index + 1}} - - {{item.realName}} - Lv{{item.level}} + + + + + {{match.matchCode}} + 比赛码 - - {{item.winCount}}胜{{item.loseCount}}负 + + ×{{match.weight}} + 权重 + + {{match.players.length || 0}} + 参赛人数 + + + {{match.stageName || '报名中'}} + 当前阶段 + + + + + + + + 👤 + 我的状态 + + + + + 🎾 比赛中 + ✅ 已完成 + ⏳ 等待匹配 + + + + {{myPlayer.winCount || 0}} + + + + {{myPlayer.loseCount || 0}} + + + + + + + + + 当前对局 + + + + + VS {{currentGame.opponent.realName}} + + Lv{{currentGame.opponent.level}} + 战力 {{currentGame.opponent.powerScore}} + + + + + + + + + + + 👥 + 参赛选手 + {{match.players.length || 0}}人 + + + + + {{index + 1}} + + {{item.realName}} + + Lv{{item.level}} + + + + + {{item.winCount || 0}}胜 + {{item.loseCount || 0}}负 + + + + + + + 🏸 + 暂无参赛选手 diff --git a/miniprogram/pages/match/ranking/index.wxss b/miniprogram/pages/match/ranking/index.wxss index 821df974..50d5deca 100644 --- a/miniprogram/pages/match/ranking/index.wxss +++ b/miniprogram/pages/match/ranking/index.wxss @@ -1,125 +1,384 @@ -.match-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 20rpx; +/* ========================================== + 排位赛详情页 - 全新设计 + ========================================== */ + +.page-container { + min-height: 100vh; + background: linear-gradient(180deg, #FEF7F3 0%, #FAFAFA 30%, #F5F5F5 100%); + position: relative; + overflow: hidden; } -.match-name { - font-size: 36rpx; - font-weight: 600; +/* 顶部背景 */ +.hero-bg { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 380rpx; + background: linear-gradient(135deg, #FFB74D 0%, #FF9800 50%, #F57C00 100%); + border-radius: 0 0 60rpx 60rpx; + pointer-events: none; + overflow: hidden; +} + +.hero-bg::before { + content: ''; + position: absolute; + top: -80rpx; + right: -60rpx; + width: 260rpx; + height: 260rpx; + background: rgba(255, 255, 255, 0.15); + border-radius: 50%; +} + +.hero-bg::after { + content: ''; + position: absolute; + bottom: -40rpx; + left: -40rpx; + width: 180rpx; + height: 180rpx; + background: rgba(255, 255, 255, 0.1); + border-radius: 50%; +} + +.hero-pattern { + position: absolute; + top: 60rpx; + left: 50%; + transform: translateX(-50%); + width: 120rpx; + height: 120rpx; + background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='50' r='40' fill='none' stroke='rgba(255,255,255,0.2)' stroke-width='2' stroke-dasharray='8 4'/%3E%3C/svg%3E"); + background-size: contain; + animation: spin 15s linear infinite; +} + +@keyframes spin { + to { transform: translateX(-50%) rotate(360deg); } +} + +/* 主要内容 */ +.main-content { + position: relative; + z-index: 1; + padding: 24rpx; +} + +/* 比赛头部 */ +.match-header { + text-align: center; + padding: 32rpx 0 40rpx; +} + +.match-badge { + font-size: 64rpx; + margin-bottom: 16rpx; +} + +.match-title { + display: block; + font-size: 44rpx; + font-weight: 800; + color: #fff; + margin-bottom: 16rpx; + text-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15); + letter-spacing: 4rpx; } .match-status { - padding: 8rpx 16rpx; - border-radius: 20rpx; - font-size: 24rpx; + display: inline-flex; + align-items: center; + gap: 8rpx; + padding: 10rpx 24rpx; + background: rgba(255, 255, 255, 0.25); + border-radius: 50rpx; + font-size: 26rpx; + font-weight: 600; + color: #fff; + backdrop-filter: blur(10px); } -.status-0 { background: #E3F2FD; color: #2196F3; } -.status-1 { background: #FFF3E0; color: #FF9800; } -.status-2 { background: #E8F5E9; color: #4CAF50; } +.status-dot { + width: 12rpx; + height: 12rpx; + border-radius: 50%; + background: #fff; +} -.match-info { +.match-status.status-1 .status-dot { + animation: pulse 1.5s infinite; +} + +@keyframes pulse { + 0%, 100% { opacity: 1; transform: scale(1); } + 50% { opacity: 0.5; transform: scale(1.2); } +} + +/* 信息卡片 */ +.info-card { + background: #fff; + border-radius: 28rpx; + padding: 28rpx; margin-bottom: 20rpx; + box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.06); } -.info-row { - display: flex; - justify-content: space-between; +.info-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 16rpx; +} + +.info-item { + text-align: center; padding: 12rpx 0; - border-bottom: 1rpx solid var(--border-color); } -.info-row:last-child { - border-bottom: none; +.info-value { + display: block; + font-size: 30rpx; + font-weight: 700; + color: var(--text-primary); + margin-bottom: 8rpx; } -.info-row .label { - color: var(--text-secondary); +.info-value.accent { + color: #FF9800; } -.info-row .value { - font-weight: 500; +.info-label { + display: block; + font-size: 22rpx; + color: var(--text-muted); } -.my-status { +.stage-tag { + display: inline-block; + padding: 6rpx 16rpx; + border-radius: 8rpx; + font-size: 24rpx; + font-weight: 600; + margin-bottom: 8rpx; +} + +.stage-tag.stage-0 { background: #E3F2FD; color: #1565C0; } +.stage-tag.stage-1 { background: #E8F5E9; color: #2E7D32; } +.stage-tag.stage-2 { background: #FFF3E0; color: #E65100; } +.stage-tag.stage-3 { background: #ECEFF1; color: #546E7A; } + +/* 我的状态卡片 */ +.my-status-card { + background: #fff; + border-radius: 28rpx; margin-bottom: 20rpx; + box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.06); + overflow: hidden; +} + +.card-header { + display: flex; + align-items: center; + gap: 12rpx; + padding: 20rpx 24rpx; + background: linear-gradient(90deg, #FFF8E1, #FFFFFF); + border-bottom: 1rpx solid rgba(255, 152, 0, 0.1); +} + +.card-icon { + font-size: 28rpx; } .card-title { + flex: 1; font-size: 28rpx; - font-weight: 600; - margin-bottom: 16rpx; - padding-bottom: 12rpx; - border-bottom: 1rpx solid var(--border-color); + font-weight: 700; + color: var(--text-primary); } -.status-info { +.player-count { + font-size: 24rpx; + color: var(--text-muted); + background: var(--bg-soft); + padding: 6rpx 16rpx; + border-radius: 20rpx; +} + +.status-content { + padding: 24rpx; +} + +.status-main { display: flex; + align-items: center; justify-content: space-between; +} + +.status-badge { + padding: 12rpx 24rpx; + border-radius: 12rpx; + font-size: 28rpx; + font-weight: 700; +} + +.status-badge.waiting { + background: linear-gradient(135deg, #E3F2FD, #BBDEFB); + color: #1565C0; +} + +.status-badge.playing { + background: linear-gradient(135deg, #E8F5E9, #C8E6C9); + color: #2E7D32; +} + +.status-badge.finished { + background: linear-gradient(135deg, #ECEFF1, #CFD8DC); + color: #546E7A; +} + +.win-lose-stats { + display: flex; + gap: 24rpx; +} + +.stat { + display: flex; + align-items: baseline; + gap: 4rpx; +} + +.stat-num { + font-size: 40rpx; + font-weight: 800; +} + +.stat.win .stat-num { + color: #2E7D32; +} + +.stat.lose .stat-num { + color: #C62828; +} + +.stat-text { + font-size: 24rpx; + color: var(--text-muted); +} + +/* 当前对局 */ +.current-game { + margin-top: 24rpx; +} + +.game-divider { + display: flex; align-items: center; margin-bottom: 16rpx; } -.status-label { - font-size: 30rpx; - font-weight: 500; - color: var(--primary-color); +.game-divider::before, +.game-divider::after { + content: ''; + flex: 1; + height: 1rpx; + background: linear-gradient(90deg, transparent, rgba(0,0,0,0.08), transparent); } -.win-lose { - font-size: 26rpx; - color: var(--text-secondary); +.divider-text { + padding: 0 20rpx; + font-size: 22rpx; + color: var(--text-muted); } -.current-game { - background: var(--light-bg); - border-radius: 12rpx; - padding: 16rpx; +.opponent-card { + display: flex; + align-items: center; + gap: 16rpx; + padding: 18rpx; + background: linear-gradient(135deg, #FFF8E1, #FFFDE7); + border-radius: 16rpx; + border: 2rpx solid rgba(255, 152, 0, 0.15); } -.vs-label { - font-size: 24rpx; - color: var(--text-secondary); - margin-bottom: 8rpx; - display: block; +.opponent-avatar { + width: 80rpx; + height: 80rpx; + border-radius: 50%; + border: 3rpx solid #FFE082; } .opponent-info { - display: flex; - justify-content: space-between; - align-items: center; + flex: 1; } .opponent-name { - font-size: 28rpx; - font-weight: 500; + display: block; + font-size: 30rpx; + font-weight: 700; + color: var(--text-primary); + margin-bottom: 8rpx; } -.opponent-level { +.opponent-meta { + display: flex; + align-items: center; + gap: 12rpx; +} + +.level-tag { + padding: 4rpx 12rpx; + border-radius: 6rpx; + font-size: 20rpx; + font-weight: 600; +} + +.level-tag.lv1 { background: linear-gradient(135deg, #81C784, #66BB6A); color: #fff; } +.level-tag.lv2 { background: linear-gradient(135deg, #64B5F6, #42A5F5); color: #fff; } +.level-tag.lv3 { background: linear-gradient(135deg, #FFB74D, #FFA726); color: #fff; } +.level-tag.lv4 { background: linear-gradient(135deg, #F06292, #EC407A); color: #fff; } +.level-tag.lv5 { background: linear-gradient(135deg, #BA68C8, #AB47BC); color: #fff; } + +.opponent-power { font-size: 24rpx; color: var(--text-secondary); } -.players-section { - margin-bottom: 20rpx; +/* 参赛选手卡片 */ +.players-card { + background: #fff; + border-radius: 28rpx; + box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.06); + overflow: hidden; } .players-list { - display: flex; - flex-direction: column; + padding: 16rpx; } .player-item { display: flex; align-items: center; - padding: 16rpx 0; - border-bottom: 1rpx solid var(--border-color); + gap: 14rpx; + padding: 16rpx 18rpx; + background: linear-gradient(135deg, #FAFAFA, #F5F5F5); + border-radius: 16rpx; + margin-bottom: 12rpx; + transition: all 0.2s; } .player-item:last-child { - border-bottom: none; + margin-bottom: 0; +} + +.player-item.is-me { + background: linear-gradient(135deg, #FFF8E1, #FFFDE7); + border: 2rpx solid rgba(255, 152, 0, 0.2); +} + +.player-item:active { + transform: scale(0.98); } .player-rank { @@ -128,26 +387,127 @@ display: flex; align-items: center; justify-content: center; - background: var(--light-bg); - border-radius: 50%; + border-radius: 12rpx; font-size: 24rpx; - font-weight: 500; - margin-right: 16rpx; + font-weight: 700; + background: var(--bg-soft); + color: var(--text-muted); } -.player-info { +.player-rank.rank-1 { + background: linear-gradient(135deg, #FFD54F, #FFB300); + color: #fff; +} + +.player-rank.rank-2 { + background: linear-gradient(135deg, #E0E0E0, #BDBDBD); + color: #fff; +} + +.player-rank.rank-3 { + background: linear-gradient(135deg, #FFCC80, #FF9800); + color: #fff; +} + +.player-main { flex: 1; - display: flex; - align-items: center; - gap: 12rpx; + min-width: 0; } .player-name { + display: block; font-size: 28rpx; - font-weight: 500; + font-weight: 700; + color: var(--text-primary); + margin-bottom: 6rpx; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } -.player-stats { - font-size: 24rpx; - color: var(--text-secondary); +.player-tags { + display: flex; + align-items: center; + gap: 8rpx; } + +.player-me { + padding: 2rpx 10rpx; + background: linear-gradient(135deg, #FF8A65, #FF6B35); + color: #fff; + font-size: 18rpx; + font-weight: 600; + border-radius: 6rpx; +} + +.player-record { + display: flex; + gap: 8rpx; + font-size: 24rpx; +} + +.record-win { + color: #2E7D32; + font-weight: 600; +} + +.record-lose { + color: #C62828; + font-weight: 600; +} + +.player-status-dot { + width: 12rpx; + height: 12rpx; + border-radius: 50%; + background: #BDBDBD; +} + +.player-status-dot.waiting { + background: #1565C0; +} + +.player-status-dot.playing { + background: #2E7D32; + animation: pulse 1.5s infinite; +} + +.player-status-dot.finished { + background: #9E9E9E; +} + +/* 空状态 */ +.empty-players { + display: flex; + flex-direction: column; + align-items: center; + padding: 60rpx 40rpx; +} + +.empty-icon { + font-size: 80rpx; + margin-bottom: 16rpx; + opacity: 0.5; +} + +.empty-text { + font-size: 28rpx; + color: var(--text-muted); +} + +/* 动画 */ +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30rpx); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.animate-fadeInUp { + animation: fadeInUp 0.5s ease-out forwards; +} + diff --git a/miniprogram/pages/points/mall/index.js b/miniprogram/pages/points/mall/index.js index f157ff7d..a1933f26 100644 --- a/miniprogram/pages/points/mall/index.js +++ b/miniprogram/pages/points/mall/index.js @@ -3,6 +3,7 @@ const app = getApp() Page({ data: { userInfo: null, + currentStore: null, products: [], loading: false, page: 1, @@ -17,7 +18,17 @@ Page({ }, onShow() { - this.setData({ userInfo: app.globalData.userInfo }) + this.setData({ + userInfo: app.globalData.userInfo, + currentStore: app.globalData.currentStore + }) + + // 门店切换后刷新商品 + if (app.globalData.storeChanged) { + app.globalData.storeChanged = false + this.setData({ page: 1, hasMore: true, products: [] }) + this.fetchProducts() + } }, onPullDownRefresh() { @@ -42,7 +53,10 @@ Page({ } } - this.setData({ userInfo: app.globalData.userInfo }) + this.setData({ + userInfo: app.globalData.userInfo, + currentStore: app.globalData.currentStore + }) this.fetchProducts() }, @@ -50,10 +64,17 @@ Page({ this.setData({ loading: true }) try { - const res = await app.request('/api/points/products', { + const params = { page: this.data.page, pageSize: this.data.pageSize - }) + } + + // 根据当前门店筛选商品 + if (this.data.currentStore?.storeId) { + params.store_id = this.data.currentStore.storeId + } + + const res = await app.request('/api/points/products', params) const products = res.data.list || [] this.setData({ diff --git a/miniprogram/pages/points/mall/index.wxml b/miniprogram/pages/points/mall/index.wxml index 79ad1770..846cbd06 100644 --- a/miniprogram/pages/points/mall/index.wxml +++ b/miniprogram/pages/points/mall/index.wxml @@ -1,84 +1,116 @@ - - - - - - 我的积分 - {{userInfo.totalPoints || 0}} - - - - - 积分记录 + + + + + + + + + + + 我的积分 + + {{userInfo.totalPoints || 0}} + + - - - 我的订单 + + 积分记录 + - - 积分好物 + + + 积分好物 + 共 {{products.length}} 件 + - + - + + + 热门 + 仅剩{{item.stock}}件 + {{item.name}} - - - {{item.pointsRequired}} - 积分 + + + {{item.pointsRequired}} + 积分 + + + {{item.stock <= 0 ? '售罄' : '兑换'}} - 库存 {{item.stock}} - {{item.storeName}} + - - 暂无可兑换商品 + + 暂无可兑换商品 + 敬请期待更多精彩好物 - + 加载中... + + + - - - - - {{currentProduct.name}} - {{currentProduct.description || '暂无描述'}} - - - {{currentProduct.pointsRequired}} - 积分 - - 原价 ¥{{currentProduct.originalPrice}} + + + + 商品详情 + × + + + + + + + {{currentProduct.name}} + {{currentProduct.description || '暂无描述'}} - - 兑换门店: + + + + {{currentProduct.pointsRequired}} + 积分 + + 库存: {{currentProduct.stock}} + + + + 兑换门店 {{currentProduct.storeName}} - {{currentProduct.storeAddress}} - + + + + diff --git a/miniprogram/pages/points/mall/index.wxss b/miniprogram/pages/points/mall/index.wxss index faa774e9..b141ea90 100644 --- a/miniprogram/pages/points/mall/index.wxss +++ b/miniprogram/pages/points/mall/index.wxss @@ -1,82 +1,190 @@ -/* 积分头部 */ -.points-header { - background: linear-gradient(135deg, var(--accent-color), #FFC93C); - border-radius: 20rpx; - padding: 30rpx; - margin-bottom: 20rpx; +/* ========================================== + 积分商城页面 - 浅色高级感设计 + ========================================== */ + +.page-container { + min-height: 100vh; + background: var(--bg-page); + position: relative; + padding-bottom: 40rpx; +} + +/* 顶部装饰背景 */ +.hero-section { + position: relative; + padding: 32rpx 24rpx; + background: linear-gradient(180deg, #FFF5F0 0%, var(--bg-page) 100%); +} + +.hero-pattern { + position: absolute; + top: -60rpx; + right: -60rpx; + width: 280rpx; + height: 280rpx; + background: radial-gradient(circle, rgba(255, 107, 53, 0.1) 0%, transparent 70%); + border-radius: 50%; + pointer-events: none; +} + +/* 积分信息卡片 */ +.points-card { + position: relative; + z-index: 1; + display: flex; + align-items: center; + justify-content: space-between; + padding: 28rpx 24rpx; + background: var(--bg-white); + border-radius: var(--radius-xl); + box-shadow: var(--shadow-lg); + overflow: hidden; + animation: fadeInScale 0.5s cubic-bezier(0.4, 0, 0.2, 1); +} + +.points-card-accent { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 4rpx; + background: var(--primary-gradient); } .points-info { - text-align: center; - margin-bottom: 24rpx; -} - -.points-info .label { - display: block; - font-size: 26rpx; - color: rgba(0, 0, 0, 0.6); - margin-bottom: 8rpx; -} - -.points-info .value { - font-size: 64rpx; - font-weight: 700; - color: #333; -} - -.points-actions { - display: flex; - justify-content: center; - gap: 60rpx; -} - -.action-btn { display: flex; flex-direction: column; - align-items: center; } -.action-btn image { - width: 48rpx; - height: 48rpx; - margin-bottom: 8rpx; -} - -.action-btn text { +.points-label { font-size: 24rpx; - color: rgba(0, 0, 0, 0.7); + color: var(--text-muted); + margin-bottom: 6rpx; } -/* 商品区域 */ -.products-section { - background: #fff; - border-radius: 20rpx; +.points-value { + display: flex; + align-items: baseline; + gap: 6rpx; +} + +.points-number { + font-size: 48rpx; + font-weight: 700; + color: var(--primary); + line-height: 1; +} + +.points-unit { + font-size: 22rpx; + color: var(--text-muted); +} + +.points-action { + display: flex; + align-items: center; + gap: 6rpx; + padding: 14rpx 24rpx; + background: var(--primary-soft); + border-radius: var(--radius-full); + transition: all 0.3s ease; +} + +.points-action:active { + transform: scale(0.96); +} + +.action-text { + font-size: 24rpx; + color: var(--primary); + font-weight: 500; +} + +.action-arrow { + font-size: 22rpx; + color: var(--primary); +} + +/* 商品列表区域 */ +.product-section { padding: 24rpx; } +.section-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 20rpx; + animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s backwards; +} + .section-title { font-size: 32rpx; - font-weight: 600; - margin-bottom: 20rpx; + font-weight: 700; + color: var(--text-primary); } -.products-grid { - display: flex; - flex-wrap: wrap; - gap: 20rpx; +.section-count { + font-size: 24rpx; + color: var(--text-muted); } -.product-item { - width: calc(50% - 10rpx); - background: var(--light-bg); - border-radius: 16rpx; +/* 商品网格 */ +.product-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 16rpx; +} + +.product-card { + background: var(--bg-white); + border-radius: var(--radius-lg); overflow: hidden; + box-shadow: var(--shadow-sm); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) backwards; +} + +.product-card:active { + transform: scale(0.97); +} + +.product-image-wrapper { + position: relative; + width: 100%; + padding-top: 100%; + background: var(--bg-soft); } .product-image { + position: absolute; + top: 0; + left: 0; width: 100%; - height: 280rpx; - background: #eee; + height: 100%; +} + +.product-tag { + position: absolute; + top: 12rpx; + left: 12rpx; + padding: 6rpx 14rpx; + background: var(--primary-gradient); + color: #FFF; + font-size: 20rpx; + font-weight: 600; + border-radius: var(--radius-full); +} + +.product-stock-low { + position: absolute; + bottom: 12rpx; + right: 12rpx; + padding: 6rpx 12rpx; + background: rgba(255, 107, 53, 0.9); + color: #FFF; + font-size: 20rpx; + border-radius: var(--radius-sm); } .product-info { @@ -85,152 +193,278 @@ .product-name { display: block; - font-size: 28rpx; - font-weight: 500; - margin-bottom: 8rpx; + font-size: 26rpx; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 12rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.product-meta { +.product-footer { display: flex; align-items: center; justify-content: space-between; +} + +.product-price { + display: flex; + align-items: baseline; + gap: 4rpx; +} + +.price-value { + font-size: 32rpx; + font-weight: 700; + color: var(--primary); +} + +.price-unit { + font-size: 20rpx; + color: var(--primary); +} + +.exchange-btn { + padding: 8rpx 16rpx; + background: var(--primary-soft); + border-radius: var(--radius-full); + font-size: 22rpx; + color: var(--primary); + font-weight: 500; + transition: all 0.2s ease; +} + +.exchange-btn:active { + background: var(--primary); + color: #FFF; +} + +.exchange-btn.disabled { + background: var(--bg-soft); + color: var(--text-hint); +} + +/* 空状态 */ +.empty-state { + display: flex; + flex-direction: column; + align-items: center; + padding: 80rpx 48rpx; + animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.15s backwards; +} + +.empty-icon { + width: 160rpx; + height: 160rpx; + margin-bottom: 24rpx; + opacity: 0.7; +} + +.empty-title { + font-size: 28rpx; + font-weight: 600; + color: var(--text-secondary); margin-bottom: 8rpx; } -.points-need { +.empty-desc { + font-size: 24rpx; + color: var(--text-muted); +} + +/* 加载状态 */ +.loading-state { display: flex; - align-items: baseline; - color: var(--primary-color); + align-items: center; + justify-content: center; + padding: 40rpx; + color: var(--text-muted); + font-size: 26rpx; } -.points-need .num { - font-size: 32rpx; - font-weight: 600; +/* 底部安全区域 */ +.safe-bottom { + height: 80rpx; } -.points-need .unit { - font-size: 22rpx; - margin-left: 4rpx; -} - -.stock { - font-size: 22rpx; - color: var(--text-secondary); -} - -.store-name { - font-size: 22rpx; - color: var(--secondary-color); -} - -/* 商品弹窗 */ -.product-modal { +/* ========================================== + 商品详情弹窗 + ========================================== */ +.product-modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; - background: rgba(0, 0, 0, 0.6); + background: rgba(0, 0, 0, 0); display: flex; align-items: flex-end; z-index: 999; + visibility: hidden; + transition: all 0.35s ease; +} + +.product-modal-overlay.show { + visibility: visible; + background: rgba(0, 0, 0, 0.5); +} + +.product-modal { + width: 100%; + max-height: 85vh; + background: var(--bg-white); + border-radius: var(--radius-xl) var(--radius-xl) 0 0; + transform: translateY(100%); + transition: all 0.35s cubic-bezier(0.34, 1.56, 0.64, 1); +} + +.product-modal.show { + transform: translateY(0); +} + +.modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 20rpx 24rpx; + border-bottom: 1rpx solid var(--border-soft); +} + +.modal-title { + font-size: 30rpx; + font-weight: 600; + color: var(--text-primary); +} + +.modal-close { + width: 52rpx; + height: 52rpx; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-soft); + border-radius: 50%; + font-size: 32rpx; + color: var(--text-muted); + transition: all 0.2s; +} + +.modal-close:active { + transform: scale(0.9); } .modal-content { - background: #fff; - border-radius: 30rpx 30rpx 0 0; - padding: 30rpx; - width: 100%; - max-height: 80vh; + padding: 20rpx 24rpx; + max-height: 60vh; + overflow-y: auto; } -.modal-image { +.detail-image { width: 100%; - height: 400rpx; - border-radius: 16rpx; + height: 360rpx; + border-radius: var(--radius-lg); + background: var(--bg-soft); margin-bottom: 20rpx; } -.modal-info { - margin-bottom: 30rpx; +.detail-info { + margin-bottom: 20rpx; } -.modal-name { +.detail-name { display: block; - font-size: 36rpx; - font-weight: 600; - margin-bottom: 12rpx; + font-size: 32rpx; + font-weight: 700; + color: var(--text-primary); + margin-bottom: 10rpx; } -.modal-desc { +.detail-desc { display: block; font-size: 26rpx; color: var(--text-secondary); - margin-bottom: 16rpx; + line-height: 1.5; } -.modal-meta { +.detail-price-row { display: flex; align-items: center; - gap: 20rpx; + justify-content: space-between; + padding: 16rpx 0; + border-top: 1rpx solid var(--border-soft); + border-bottom: 1rpx solid var(--border-soft); margin-bottom: 16rpx; } -.modal-meta .points-need .num { - font-size: 40rpx; +.detail-price { + display: flex; + align-items: baseline; + gap: 6rpx; } -.original-price { +.detail-price-value { + font-size: 44rpx; + font-weight: 700; + color: var(--primary); +} + +.detail-price-unit { font-size: 24rpx; - color: var(--text-secondary); - text-decoration: line-through; + color: var(--primary); +} + +.detail-stock { + font-size: 24rpx; + color: var(--text-muted); } .store-info { display: flex; align-items: center; - margin-bottom: 8rpx; + gap: 12rpx; + padding: 12rpx 0; } .store-label { - font-size: 26rpx; - color: var(--text-secondary); - margin-right: 8rpx; -} - -.store-info .store-name { - font-size: 26rpx; -} - -.store-address { font-size: 24rpx; - color: var(--text-secondary); + color: var(--text-muted); } -.btn-exchange { - width: 100%; - background: linear-gradient(135deg, var(--primary-color), var(--primary-light)); - color: #fff; - border: none; - border-radius: 40rpx; - padding: 24rpx; - font-size: 32rpx; +.store-name { + font-size: 24rpx; + color: var(--text-primary); font-weight: 500; } -.btn-exchange[disabled] { - background: #ccc; +.modal-footer { + padding: 20rpx 24rpx; + padding-bottom: calc(20rpx + env(safe-area-inset-bottom)); + border-top: 1rpx solid var(--border-soft); } -/* 空状态 */ -.empty-state { - padding: 100rpx 0; +.exchange-btn-large { + width: 100%; + height: 92rpx; + display: flex; + align-items: center; + justify-content: center; + background: var(--primary-gradient); + border: none; + border-radius: var(--radius-full); + font-size: 30rpx; + font-weight: 600; + color: var(--text-white); + box-shadow: var(--shadow-primary); + transition: all 0.3s ease; } -.empty-state image { - width: 200rpx; - height: 200rpx; +.exchange-btn-large:active { + transform: scale(0.97); +} + +.exchange-btn-large.disabled { + background: var(--bg-soft); + color: var(--text-hint); + box-shadow: none; } diff --git a/miniprogram/pages/points/order/index.js b/miniprogram/pages/points/order/index.js index e5d086a9..93f66625 100644 --- a/miniprogram/pages/points/order/index.js +++ b/miniprogram/pages/points/order/index.js @@ -1,6 +1,5 @@ const app = getApp() const util = require('../../../utils/util') -const QRCode = require('../../../utils/qrcode.js') Page({ data: { @@ -31,10 +30,6 @@ Page({ } }, - getStatusText(status) { - return util.getOrderStatusText(status) - }, - async fetchOrders() { this.setData({ loading: true }) @@ -86,30 +81,50 @@ Page({ wx.hideLoading() + const orderData = { + ...res.data, + createdAt: util.formatDate(res.data.createdAt), + qrcodeImage: '' + } + this.setData({ - currentOrder: res.data, + currentOrder: orderData, showOrderModal: true }) - // 如果待核销,生成二维码 - if (res.data.status === 0) { - setTimeout(() => { - new QRCode('orderQrcode', { - text: res.data.exchangeCode, - width: 200, - height: 200, - colorDark: '#000000', - colorLight: '#ffffff' - }) - }, 100) + // 如果待核销,获取二维码图片 + if (res.data.status === 0 && res.data.exchangeCode) { + this.generateQrcode(res.data.exchangeCode) } } catch (e) { wx.hideLoading() console.error('获取订单详情失败:', e) + wx.showToast({ title: '获取订单详情失败', icon: 'none' }) + } + }, + + // 生成二维码 + async generateQrcode(code) { + try { + const res = await app.request('/api/points/orders/qrcode', { code }) + if (res.data && res.data.qrcode) { + this.setData({ + 'currentOrder.qrcodeImage': res.data.qrcode + }) + } + } catch (e) { + console.error('生成二维码失败:', e) } }, closeOrderModal() { - this.setData({ showOrderModal: false }) + this.setData({ + showOrderModal: false, + currentOrder: null + }) + }, + + goToMall() { + wx.switchTab({ url: '/pages/points/mall/index' }) } }) diff --git a/miniprogram/pages/points/order/index.wxml b/miniprogram/pages/points/order/index.wxml index 2e26640c..12f559cc 100644 --- a/miniprogram/pages/points/order/index.wxml +++ b/miniprogram/pages/points/order/index.wxml @@ -1,87 +1,158 @@ - - - + + + + + + + + + + 兑换订单 + + + 全部 待核销 已完成 - - - - 订单号: {{item.orderNo}} - {{getStatusText(item.status)}} - - - - - {{item.productName}} - {{item.storeName}} - -{{item.pointsUsed}} 积分 + + + + + 订单号: {{item.orderNo}} + + {{item.status === 0 ? '待核销' : item.status === 1 ? '已完成' : '已取消'}} + + + + + + {{item.productName}} + {{item.storeName}} + + + {{item.pointsUsed}} + 积分 + + + + + + {{item.createdAt}} + + 查看兑换码 + - - {{item.createdAt}} - 查看兑换码 - + + + + + + 暂无订单 + 快去积分商城兑换心仪好物吧 + 去兑换 - - - 暂无订单 - - - + 加载中... + + + - - - - 兑换详情 - {{getStatusText(currentOrder.status)}} + + + + 兑换详情 + × - - - {{currentOrder.productName}} - 使用积分: {{currentOrder.pointsUsed}} + + + + + + {{currentOrder.productName}} + + {{currentOrder.pointsUsed}} + 积分 + + + + - - {{currentOrder.exchangeCode}} + + + + 加载中... + + + {{currentOrder.exchangeCode}} 请出示此码给工作人员核销 - - 领取门店 - {{currentOrder.storeName}} - {{currentOrder.storeAddress}} - 电话: {{currentOrder.storeContact}} + + + 订单信息 + + 订单编号 + {{currentOrder.orderNo}} + + + 下单时间 + {{currentOrder.createdAt}} + + + 订单状态 + + {{currentOrder.status === 0 ? '待核销' : '已完成'}} + + + + + + + 领取门店 + + 门店名称 + {{currentOrder.storeName}} + + + 门店地址 + {{currentOrder.storeAddress}} + diff --git a/miniprogram/pages/points/order/index.wxss b/miniprogram/pages/points/order/index.wxss index 14b82c7f..db86264c 100644 --- a/miniprogram/pages/points/order/index.wxss +++ b/miniprogram/pages/points/order/index.wxss @@ -1,251 +1,517 @@ -/* 状态筛选 */ -.status-tabs { - display: flex; - background: #fff; - border-radius: 16rpx; - padding: 8rpx; - margin-bottom: 20rpx; +/* ========================================== + 兑换订单页面 - 浅色高级感设计 + ========================================== */ + +.page-container { + min-height: 100vh; + background: var(--bg-page); + position: relative; + padding-bottom: 40rpx; } -.status-tabs .tab { - flex: 1; +/* 顶部装饰背景 */ +.hero-bg { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 280rpx; + background: linear-gradient(180deg, #FFF5F0 0%, var(--bg-page) 100%); + pointer-events: none; +} + +.hero-pattern { + position: absolute; + top: -60rpx; + right: -40rpx; + width: 220rpx; + height: 220rpx; + background: radial-gradient(circle, rgba(255, 107, 53, 0.08) 0%, transparent 70%); + border-radius: 50%; +} + +/* 页面标题 */ +.page-header { + position: relative; + z-index: 1; text-align: center; - padding: 16rpx 0; - font-size: 28rpx; - color: var(--text-secondary); - border-radius: 12rpx; + padding: 32rpx 24rpx 20rpx; + animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1); } -.status-tabs .tab.active { - background: var(--primary-color); - color: #fff; - font-weight: 500; +.page-title { + display: block; + font-size: 36rpx; + font-weight: 700; + color: var(--text-primary); + letter-spacing: 2rpx; } -/* 订单列表 */ -.order-list { +/* 状态筛选标签 */ +.status-tabs { + position: relative; + z-index: 1; display: flex; - flex-direction: column; - gap: 20rpx; + gap: 12rpx; + padding: 0 24rpx 20rpx; + animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s backwards; } -.order-item { - background: #fff; - border-radius: 16rpx; - padding: 24rpx; +.status-tab { + flex: 1; + padding: 16rpx; + background: var(--bg-white); + border-radius: var(--radius-full); + text-align: center; + font-size: 26rpx; + color: var(--text-secondary); + box-shadow: var(--shadow-sm); + transition: all 0.3s ease; } +.status-tab.active { + background: var(--primary-gradient); + color: var(--text-white); + box-shadow: var(--shadow-primary); +} + +.status-tab:active { + transform: scale(0.96); +} + +/* 订单列表区域 */ +.order-section { + position: relative; + z-index: 1; + padding: 0 24rpx; +} + +.order-list { + +} + +.order-card { + background: var(--bg-white); + border-radius: var(--radius-lg); + margin-bottom: 16rpx; + box-shadow: var(--shadow-card); + overflow: hidden; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) backwards; +} + +.order-card:active { + transform: scale(0.98); +} + +/* 订单头部 */ .order-header { display: flex; - justify-content: space-between; align-items: center; - margin-bottom: 16rpx; - padding-bottom: 16rpx; - border-bottom: 1rpx solid var(--border-color); + justify-content: space-between; + padding: 16rpx 20rpx; + background: linear-gradient(90deg, #FAFBFC, var(--bg-white)); + border-bottom: 1rpx solid var(--border-soft); } .order-no { - font-size: 24rpx; - color: var(--text-secondary); + font-size: 22rpx; + color: var(--text-muted); } .order-status { - font-size: 24rpx; - font-weight: 500; + padding: 6rpx 14rpx; + border-radius: var(--radius-full); + font-size: 22rpx; + font-weight: 600; } -.order-status.status-0 { - color: #FAAD14; +.order-status.pending { + background: #FFF8E1; + color: #F9A825; } -.order-status.status-1 { - color: #52C41A; +.order-status.completed { + background: var(--accent-light); + color: var(--accent); } -.order-status.status-2 { - color: #999; +.order-status.cancelled { + background: var(--bg-soft); + color: var(--text-muted); } +/* 订单内容 */ .order-content { display: flex; + padding: 20rpx; gap: 16rpx; - margin-bottom: 16rpx; } -.order-content .product-image { - width: 140rpx; - height: 140rpx; - border-radius: 12rpx; - background: #eee; +.product-image { + width: 120rpx; + height: 120rpx; + border-radius: var(--radius-md); + background: var(--bg-soft); + flex-shrink: 0; } -.order-content .product-info { +.order-info { flex: 1; display: flex; flex-direction: column; justify-content: space-between; + min-width: 0; } .product-name { - font-size: 28rpx; - font-weight: 500; + display: block; + font-size: 26rpx; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 6rpx; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .store-name { - font-size: 24rpx; - color: var(--secondary-color); + font-size: 22rpx; + color: var(--text-muted); + margin-bottom: 8rpx; } -.points-used { - font-size: 28rpx; - color: var(--primary-color); - font-weight: 500; +.order-price-row { + display: flex; + align-items: center; + justify-content: space-between; } +.order-price { + display: flex; + align-items: baseline; + gap: 4rpx; +} + +.price-value { + font-size: 30rpx; + font-weight: 700; + color: var(--primary); +} + +.price-unit { + font-size: 20rpx; + color: var(--primary); +} + +/* 订单底部 */ .order-footer { display: flex; - justify-content: space-between; align-items: center; + justify-content: space-between; + padding: 14rpx 20rpx; + background: var(--bg-soft); + border-top: 1rpx solid var(--border-soft); } .order-time { - font-size: 24rpx; + font-size: 22rpx; + color: var(--text-muted); +} + +.order-actions { + display: flex; + gap: 12rpx; +} + +.action-btn { + padding: 10rpx 20rpx; + border-radius: var(--radius-full); + font-size: 22rpx; + font-weight: 500; + transition: all 0.3s ease; +} + +.action-btn.primary { + background: var(--primary-gradient); + color: var(--text-white); + box-shadow: var(--shadow-primary); +} + +.action-btn.primary:active { + transform: scale(0.96); +} + +/* 空状态 */ +.empty-state { + display: flex; + flex-direction: column; + align-items: center; + padding: 80rpx 48rpx; + animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.15s backwards; +} + +.empty-icon { + width: 160rpx; + height: 160rpx; + margin-bottom: 24rpx; + opacity: 0.7; +} + +.empty-title { + font-size: 28rpx; + font-weight: 600; color: var(--text-secondary); + margin-bottom: 8rpx; } -.btn-qrcode { - background: var(--primary-color); - color: #fff; - padding: 12rpx 24rpx; - border-radius: 20rpx; +.empty-desc { font-size: 24rpx; + color: var(--text-muted); + margin-bottom: 28rpx; } -/* 订单弹窗 */ -.order-modal { +.empty-btn { + padding: 16rpx 48rpx; + background: var(--primary-gradient); + color: var(--text-white); + border-radius: var(--radius-full); + font-size: 28rpx; + font-weight: 500; + box-shadow: var(--shadow-primary); +} + +.empty-btn:active { + transform: scale(0.96); +} + +/* 加载状态 */ +.loading-state { + display: flex; + align-items: center; + justify-content: center; + padding: 40rpx; + color: var(--text-muted); + font-size: 26rpx; +} + +/* 底部安全区域 */ +.safe-bottom { + height: 80rpx; +} + +/* ========================================== + 订单详情弹窗 + ========================================== */ +.order-detail-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; - background: rgba(0, 0, 0, 0.6); + background: rgba(0, 0, 0, 0); + display: flex; + align-items: flex-end; + z-index: 999; + visibility: hidden; + transition: all 0.35s ease; +} + +.order-detail-overlay.show { + visibility: visible; + background: rgba(0, 0, 0, 0.5); +} + +.order-detail-modal { + width: 100%; + max-height: 85vh; + background: var(--bg-white); + border-radius: var(--radius-xl) var(--radius-xl) 0 0; + transform: translateY(100%); + transition: all 0.35s cubic-bezier(0.34, 1.56, 0.64, 1); +} + +.order-detail-modal.show { + transform: translateY(0); +} + +.detail-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 20rpx 24rpx; + border-bottom: 1rpx solid var(--border-soft); +} + +.detail-title { + font-size: 30rpx; + font-weight: 600; + color: var(--text-primary); +} + +.detail-close { + width: 52rpx; + height: 52rpx; display: flex; align-items: center; justify-content: center; - z-index: 999; + background: var(--bg-soft); + border-radius: 50%; + font-size: 32rpx; + color: var(--text-muted); } -.order-modal .modal-content { - background: #fff; - border-radius: 20rpx; - padding: 30rpx; - width: 85%; - max-height: 80vh; +.detail-close:active { + transform: scale(0.9); +} + +.detail-content { + padding: 20rpx 24rpx; + max-height: 70vh; overflow-y: auto; } -.modal-header { +/* 商品信息 */ +.detail-product { display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 24rpx; + gap: 16rpx; + padding-bottom: 20rpx; + border-bottom: 1rpx solid var(--border-soft); + margin-bottom: 20rpx; } -.modal-title { - font-size: 32rpx; +.detail-product-image { + width: 100rpx; + height: 100rpx; + border-radius: var(--radius-md); + background: var(--bg-soft); + flex-shrink: 0; +} + +.detail-product-info { + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; +} + +.detail-product-name { + font-size: 28rpx; font-weight: 600; -} - -.modal-body { - text-align: center; -} - -.modal-body .product-image { - width: 200rpx; - height: 200rpx; - border-radius: 12rpx; - margin-bottom: 16rpx; -} - -.modal-body .product-name { - display: block; - font-size: 30rpx; - font-weight: 500; + color: var(--text-primary); margin-bottom: 8rpx; } -.modal-body .points-used { - display: block; - margin-bottom: 24rpx; +.detail-product-price { + display: flex; + align-items: baseline; + gap: 4rpx; } -.qrcode-section { - background: var(--light-bg); - border-radius: 16rpx; - padding: 30rpx; - margin-bottom: 24rpx; -} - -.qrcode-canvas { - width: 400rpx; - height: 400rpx; - margin: 0 auto 16rpx; -} - -.qrcode-text { - display: block; +.detail-price-value { font-size: 32rpx; - font-weight: 600; - color: var(--primary-color); + font-weight: 700; + color: var(--primary); +} + +.detail-price-unit { + font-size: 22rpx; + color: var(--primary); +} + +/* 二维码区域 */ +.qrcode-section { + display: flex; + flex-direction: column; + align-items: center; + padding: 28rpx 20rpx; + background: var(--bg-soft); + border-radius: var(--radius-lg); + margin-bottom: 20rpx; +} + +.qrcode-wrapper { + width: 280rpx; + height: 280rpx; + background: #FFFFFF; + border-radius: var(--radius-md); + padding: 16rpx; + box-shadow: var(--shadow-sm); + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 16rpx; +} + +.qrcode-image { + width: 248rpx; + height: 248rpx; +} + +.qrcode-loading { + display: flex; + align-items: center; + justify-content: center; + width: 248rpx; + height: 248rpx; + color: var(--text-muted); + font-size: 24rpx; +} + +.qrcode-code { + font-size: 32rpx; + font-weight: 700; + color: var(--text-primary); + letter-spacing: 4rpx; margin-bottom: 8rpx; } .qrcode-tip { - display: block; font-size: 24rpx; - color: var(--text-secondary); + color: var(--primary); } -.store-section { - text-align: left; - background: var(--light-bg); - border-radius: 12rpx; - padding: 20rpx; +/* 详情区块 */ +.detail-section { + margin-bottom: 20rpx; } -.section-title { +.detail-section-title { display: block; font-size: 26rpx; - color: var(--text-secondary); - margin-bottom: 8rpx; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 12rpx; + padding-bottom: 8rpx; + border-bottom: 1rpx solid var(--border-soft); } -.store-section .store-name { - display: block; - font-size: 28rpx; - font-weight: 500; - margin-bottom: 4rpx; +.detail-row { + display: flex; + align-items: flex-start; + justify-content: space-between; + padding: 10rpx 0; } -.store-address { - display: block; +.detail-label { font-size: 24rpx; - color: var(--text-secondary); + color: var(--text-muted); + flex-shrink: 0; } -.store-contact { - display: block; +.detail-value { font-size: 24rpx; - color: var(--secondary-color); - margin-top: 8rpx; + color: var(--text-primary); + text-align: right; + flex: 1; + margin-left: 20rpx; + word-break: break-all; } -/* 空状态 */ -.empty-state { - padding: 100rpx 0; +.detail-value.status-pending { + color: #F9A825; } -.empty-state image { - width: 200rpx; - height: 200rpx; +.detail-value.status-completed { + color: var(--accent); } diff --git a/miniprogram/pages/points/records/index.js b/miniprogram/pages/points/records/index.js index bf32a85d..38b952a4 100644 --- a/miniprogram/pages/points/records/index.js +++ b/miniprogram/pages/points/records/index.js @@ -14,6 +14,15 @@ Page({ this.fetchRecords() }, + onShow() { + // 门店切换后刷新数据 + if (app.globalData.storeChanged) { + app.globalData.storeChanged = false + this.setData({ page: 1, hasMore: true, records: [] }) + this.fetchRecords() + } + }, + onPullDownRefresh() { this.setData({ page: 1, hasMore: true }) this.fetchRecords().then(() => { diff --git a/miniprogram/pages/points/records/index.wxss b/miniprogram/pages/points/records/index.wxss index 5be9006b..b69b2cbb 100644 --- a/miniprogram/pages/points/records/index.wxss +++ b/miniprogram/pages/points/records/index.wxss @@ -1,54 +1,331 @@ +/* ========================================== + 积分记录页面 - 浅色高级感设计 + ========================================== */ + +.page-container { + min-height: 100vh; + background: var(--bg-page); + position: relative; +} + +/* 顶部装饰背景 */ +.hero-bg { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 280rpx; + background: linear-gradient(180deg, #FFF5F0 0%, var(--bg-page) 100%); + pointer-events: none; +} + +.hero-pattern { + position: absolute; + top: -60rpx; + right: -40rpx; + width: 220rpx; + height: 220rpx; + background: radial-gradient(circle, rgba(255, 107, 53, 0.08) 0%, transparent 70%); + border-radius: 50%; +} + +/* 页面标题 */ +.page-header { + position: relative; + z-index: 1; + text-align: center; + padding: 32rpx 24rpx 24rpx; + animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1); +} + +.page-title { + display: block; + font-size: 40rpx; + font-weight: 700; + color: var(--text-primary); + margin-bottom: 8rpx; + letter-spacing: 2rpx; +} + +.page-subtitle { + display: block; + font-size: 26rpx; + color: var(--text-muted); +} + +/* 积分概览卡片 */ +.points-overview { + position: relative; + z-index: 1; + margin: 0 24rpx 24rpx; + padding: 28rpx; + background: var(--bg-white); + border-radius: var(--radius-xl); + box-shadow: var(--shadow-lg); + overflow: hidden; + animation: fadeInScale 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s backwards; +} + +.overview-accent { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 4rpx; + background: var(--primary-gradient); +} + +.overview-content { + display: flex; + align-items: center; + justify-content: space-around; +} + +.overview-item { + text-align: center; +} + +.overview-value { + display: block; + font-size: 44rpx; + font-weight: 700; + color: var(--text-primary); + margin-bottom: 6rpx; +} + +.overview-value.income { + color: var(--accent); +} + +.overview-value.expense { + color: var(--primary); +} + +.overview-label { + font-size: 24rpx; + color: var(--text-muted); +} + +.overview-divider { + width: 1rpx; + height: 64rpx; + background: var(--border-light); +} + +/* 筛选标签 */ +.filter-bar { + position: relative; + z-index: 1; + display: flex; + gap: 16rpx; + padding: 0 24rpx 24rpx; + animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.15s backwards; +} + +.filter-item { + flex: 1; + padding: 18rpx; + background: var(--bg-white); + border-radius: var(--radius-full); + text-align: center; + font-size: 26rpx; + color: var(--text-secondary); + box-shadow: var(--shadow-sm); + transition: all 0.3s ease; +} + +.filter-item.active { + background: var(--primary-gradient); + color: var(--text-white); + box-shadow: var(--shadow-primary); +} + +.filter-item:active { + transform: scale(0.96); +} + +/* 记录列表 */ +.records-section { + position: relative; + z-index: 1; + padding: 0 24rpx; +} + .records-list { - background: #fff; - border-radius: 16rpx; + } .record-item { display: flex; align-items: center; - justify-content: space-between; padding: 24rpx; - border-bottom: 1rpx solid var(--border-color); + background: var(--bg-white); + border-radius: var(--radius-lg); + margin-bottom: 12rpx; + box-shadow: var(--shadow-sm); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) backwards; } -.record-item:last-child { - border-bottom: none; +.record-item:nth-child(1) { animation-delay: 0.2s; } +.record-item:nth-child(2) { animation-delay: 0.25s; } +.record-item:nth-child(3) { animation-delay: 0.3s; } +.record-item:nth-child(4) { animation-delay: 0.35s; } +.record-item:nth-child(5) { animation-delay: 0.4s; } + +.record-item:active { + transform: scale(0.98); +} + +.record-icon { + width: 72rpx; + height: 72rpx; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin-right: 16rpx; +} + +.record-icon.income { + background: linear-gradient(135deg, #E6FBF7, #CCEFE8); +} + +.record-icon.expense { + background: linear-gradient(135deg, #FFF0EB, #FFE4D9); +} + +.record-icon-text { + font-size: 32rpx; } .record-info { - display: flex; - flex-direction: column; + flex: 1; + overflow: hidden; } -.action-name { +.record-title { + display: block; font-size: 28rpx; - font-weight: 500; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 6rpx; +} + +.record-desc { + display: block; + font-size: 24rpx; + color: var(--text-muted); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.record-value { + text-align: right; +} + +.value-number { + display: block; + font-size: 32rpx; + font-weight: 700; margin-bottom: 4rpx; } -.record-time { - font-size: 24rpx; +.value-number.income { + color: var(--accent); +} + +.value-number.income::before { + content: '+'; +} + +.value-number.expense { + color: var(--primary); +} + +.value-number.expense::before { + content: '-'; +} + +.value-time { + font-size: 22rpx; + color: var(--text-muted); +} + +/* 日期分组 */ +.date-group { + margin-bottom: 24rpx; +} + +.date-header { + display: flex; + align-items: center; + gap: 12rpx; + margin-bottom: 16rpx; + padding-left: 8rpx; +} + +.date-dot { + width: 10rpx; + height: 10rpx; + border-radius: 50%; + background: var(--primary); +} + +.date-text { + font-size: 26rpx; + font-weight: 600; color: var(--text-secondary); } -.record-points { - font-size: 32rpx; - font-weight: 600; -} - -.record-points.positive { - color: #52C41A; -} - -.record-points.negative { - color: #F5222D; -} - +/* 空状态 */ .empty-state { - padding: 100rpx 0; + display: flex; + flex-direction: column; + align-items: center; + padding: 100rpx 48rpx; + animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.2s backwards; } -.empty-state image { - width: 200rpx; - height: 200rpx; +.empty-icon { + width: 180rpx; + height: 180rpx; + margin-bottom: 32rpx; + opacity: 0.7; +} + +.empty-title { + font-size: 30rpx; + font-weight: 600; + color: var(--text-secondary); + margin-bottom: 12rpx; +} + +.empty-desc { + font-size: 26rpx; + color: var(--text-muted); +} + +/* 加载状态 */ +.loading-state { + display: flex; + align-items: center; + justify-content: center; + padding: 48rpx; + color: var(--text-muted); + font-size: 26rpx; +} + +/* 加载更多 */ +.load-more { + text-align: center; + padding: 32rpx; + color: var(--text-muted); + font-size: 26rpx; +} + +/* 底部安全区域 */ +.safe-bottom { + height: 80rpx; } diff --git a/miniprogram/pages/store/index.js b/miniprogram/pages/store/index.js index 05d221b8..24ea5b63 100644 --- a/miniprogram/pages/store/index.js +++ b/miniprogram/pages/store/index.js @@ -3,17 +3,44 @@ const app = getApp() Page({ data: { stores: [], - currentStoreId: null + currentStoreId: null, + currentStoreName: '', + currentStoreAddress: '', + loading: false }, onLoad() { + this.initCurrentStore() this.fetchStores() - if (app.globalData.currentStore) { - this.setData({ currentStoreId: app.globalData.currentStore.storeId }) + }, + + onShow() { + // 每次显示页面时刷新当前门店信息 + this.initCurrentStore() + }, + + async onPullDownRefresh() { + try { + await this.fetchStores() + } finally { + wx.stopPullDownRefresh() + } + }, + + // 初始化当前门店信息 + initCurrentStore() { + const currentStore = app.globalData.currentStore + if (currentStore) { + this.setData({ + currentStoreId: currentStore.storeId, + currentStoreName: currentStore.storeName || '', + currentStoreAddress: currentStore.storeAddress || '' + }) } }, formatDistance(meters) { + if (!meters) return '' if (meters < 1000) { return Math.round(meters) + 'm' } @@ -21,40 +48,102 @@ Page({ }, async fetchStores() { + this.setData({ loading: true }) + try { - // 尝试获取位置 - wx.getLocation({ - type: 'gcj02', - success: async (loc) => { - const res = await app.request('/api/store/nearby', { - latitude: loc.latitude, - longitude: loc.longitude, - radius: 50000 - }) - this.setData({ stores: res.data || [] }) - }, - fail: async () => { - // 无法获取位置,获取全部门店 - const res = await app.request('/api/store/list') - this.setData({ stores: res.data.list || [] }) - } - }) + // 如果已登录,尝试获取附近门店 + if (app.globalData.token) { + wx.getLocation({ + type: 'gcj02', + success: async (loc) => { + try { + const res = await app.request('/api/store/nearby', { + latitude: loc.latitude, + longitude: loc.longitude, + radius: 50000 + }) + this.setData({ + stores: res.data || [], + loading: false + }) + // 更新当前门店的完整信息(地址等) + this.updateCurrentStoreInfo(res.data || []) + } catch (e) { + // 如果失败,fallback 到门店列表 + this.fetchStoreList() + } + }, + fail: () => { + this.fetchStoreList() + } + }) + } else { + // 未登录,直接获取门店列表 + this.fetchStoreList() + } } catch (e) { console.error('获取门店列表失败:', e) + this.setData({ loading: false }) + } + }, + + async fetchStoreList() { + try { + const res = await app.request('/api/store/list') + const stores = res.data.list || [] + this.setData({ + stores: stores, + loading: false + }) + // 更新当前门店的完整信息 + this.updateCurrentStoreInfo(stores) + } catch (e) { + console.error('获取门店列表失败:', e) + this.setData({ loading: false }) + } + }, + + // 从门店列表中更新当前门店的完整信息 + updateCurrentStoreInfo(stores) { + const { currentStoreId } = this.data + if (!currentStoreId || !stores.length) return + + const currentStore = stores.find(s => s.id === currentStoreId) + if (currentStore) { + this.setData({ + currentStoreName: currentStore.name, + currentStoreAddress: currentStore.address + }) + // 同时更新全局数据 + app.globalData.currentStore = { + storeId: currentStore.id, + storeName: currentStore.name, + storeAddress: currentStore.address + } } }, async selectStore(e) { const store = e.currentTarget.dataset.store - this.setData({ currentStoreId: store.id }) + wx.showLoading({ title: '切换中...' }) + + this.setData({ + currentStoreId: store.id, + currentStoreName: store.name, + currentStoreAddress: store.address + }) // 更新全局门店 app.globalData.currentStore = { storeId: store.id, - storeName: store.name + storeName: store.name, + storeAddress: store.address } + // 清空旧的天梯用户信息 + app.globalData.ladderUser = null + // 获取该门店的天梯用户信息 try { await app.getLadderUser(store.id) @@ -62,10 +151,14 @@ Page({ console.error('获取天梯信息失败:', e) } + // 标记需要刷新数据 + app.globalData.storeChanged = true + + wx.hideLoading() wx.showToast({ title: '切换成功', icon: 'success' }) setTimeout(() => { wx.navigateBack() - }, 1000) + }, 800) } }) diff --git a/miniprogram/pages/store/index.json b/miniprogram/pages/store/index.json index 1b183e19..a1589951 100644 --- a/miniprogram/pages/store/index.json +++ b/miniprogram/pages/store/index.json @@ -1,3 +1,4 @@ { - "navigationBarTitleText": "选择门店" + "navigationBarTitleText": "选择门店", + "enablePullDownRefresh": true } diff --git a/miniprogram/pages/store/index.wxml b/miniprogram/pages/store/index.wxml index 32ea35c2..c8c5362a 100644 --- a/miniprogram/pages/store/index.wxml +++ b/miniprogram/pages/store/index.wxml @@ -1,29 +1,101 @@ - - - - - - {{item.name}} - {{item.address}} - - {{item.sportType === 1 ? '羽毛球' : '网球'}} - {{formatDistance(item.distance)}} - + + +function formatDistance(meters) { + if (!meters) return ''; + if (meters < 1000) { + return Math.round(meters) + 'm'; + } + return (meters / 1000).toFixed(1) + 'km'; +} +module.exports = { formatDistance: formatDistance }; + + + + + + + + + + + 选择门店 + 加入您附近的门店,开始运动 + + + + + + + + + 当前门店 - - + 可切换其他门店 + + + + + + + {{currentStoreName || '暂无门店'}} + {{currentStoreAddress || '请选择门店'}} - - - 暂无门店 + + + + + 📍 + 附近门店 + + 共 {{stores.length}} 家 + + + + + + + + + + {{item.name}} + {{item.address}} + + {{util.formatDistance(item.distance)}} + {{item.sportType === 1 ? '🏸 羽毛球' : '🎾 网球'}} + + + + + + + + + + + + + + + 暂无门店 + 附近暂无可用门店 + + + + + + 加载中... + + + + diff --git a/miniprogram/pages/store/index.wxss b/miniprogram/pages/store/index.wxss index a692bc8f..0d7ee98b 100644 --- a/miniprogram/pages/store/index.wxss +++ b/miniprogram/pages/store/index.wxss @@ -1,76 +1,361 @@ -.store-list { - display: flex; - flex-direction: column; - gap: 20rpx; +/* ========================================== + 门店选择页面 - 浅色高级感设计 + ========================================== */ + +.page-container { + min-height: 100vh; + background: var(--bg-page); + position: relative; } -.store-item { - display: flex; - align-items: center; - background: #fff; - border-radius: 16rpx; - padding: 24rpx; - border: 2rpx solid transparent; - transition: all 0.3s; +/* 顶部装饰背景 */ +.hero-bg { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 320rpx; + background: linear-gradient(180deg, #FFF5F0 0%, var(--bg-page) 100%); + pointer-events: none; } -.store-item.active { - border-color: var(--primary-color); - background: #FFF5F0; +.hero-pattern { + position: absolute; + top: -80rpx; + right: -60rpx; + width: 280rpx; + height: 280rpx; + background: radial-gradient(circle, rgba(255, 107, 53, 0.1) 0%, transparent 70%); + border-radius: 50%; } -.store-info { - flex: 1; +/* 页面标题 */ +.page-header { + position: relative; + z-index: 1; + text-align: center; + padding: 32rpx 24rpx 24rpx; + animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1); } -.store-name { +.page-title { display: block; - font-size: 30rpx; - font-weight: 600; + font-size: 40rpx; + font-weight: 700; + color: var(--text-primary); margin-bottom: 8rpx; + letter-spacing: 2rpx; } -.store-address { +.page-subtitle { display: block; font-size: 26rpx; - color: var(--text-secondary); - margin-bottom: 8rpx; + color: var(--text-muted); } -.store-meta { +/* 当前门店卡片 */ +.current-store-card { + position: relative; + z-index: 1; + margin: 0 24rpx 24rpx; + background: var(--bg-white); + border-radius: var(--radius-xl); + padding: 28rpx; + box-shadow: var(--shadow-lg); + overflow: hidden; + animation: fadeInScale 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s backwards; +} + +.current-store-accent { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 4rpx; + background: var(--primary-gradient); +} + +.current-store-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16rpx; +} + +.current-store-badge { + display: flex; + align-items: center; + gap: 8rpx; + padding: 8rpx 16rpx; + background: var(--primary-soft); + border-radius: var(--radius-full); +} + +.badge-dot { + width: 10rpx; + height: 10rpx; + border-radius: 50%; + background: var(--primary); + animation: pulse 2s ease-in-out infinite; +} + +.badge-text { + font-size: 24rpx; + color: var(--primary); + font-weight: 500; +} + +.current-store-change { + font-size: 24rpx; + color: var(--text-muted); +} + +.current-store-info { display: flex; align-items: center; gap: 16rpx; } -.sport-type { - font-size: 22rpx; - color: var(--secondary-color); - background: #E0F7FA; +.store-icon-wrapper { + width: 80rpx; + height: 80rpx; + border-radius: var(--radius-md); + background: linear-gradient(135deg, #FFF0EB, #FFE4D9); + display: flex; + align-items: center; + justify-content: center; +} + +.store-icon-wrapper image { + width: 44rpx; + height: 44rpx; +} + +.store-details { + flex: 1; +} + +.store-name { + display: block; + font-size: 32rpx; + font-weight: 700; + color: var(--text-primary); + margin-bottom: 6rpx; +} + +.store-address { + display: block; + font-size: 24rpx; + color: var(--text-muted); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* 门店列表区域 */ +.store-section { + position: relative; + z-index: 1; + padding: 0 24rpx; +} + +.section-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 20rpx; + animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.15s backwards; +} + +.section-title { + display: flex; + align-items: center; + gap: 12rpx; +} + +.section-icon { + font-size: 28rpx; +} + +.section-text { + font-size: 30rpx; + font-weight: 600; + color: var(--text-primary); +} + +.section-count { + font-size: 24rpx; + color: var(--text-muted); +} + +/* 门店列表 */ +.store-list { + +} + +.store-item { + display: flex; + align-items: center; + padding: 24rpx; + background: var(--bg-white); + border-radius: var(--radius-lg); + margin-bottom: 16rpx; + box-shadow: var(--shadow-sm); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) backwards; +} + +.store-item:nth-child(1) { animation-delay: 0.2s; } +.store-item:nth-child(2) { animation-delay: 0.25s; } +.store-item:nth-child(3) { animation-delay: 0.3s; } +.store-item:nth-child(4) { animation-delay: 0.35s; } +.store-item:nth-child(5) { animation-delay: 0.4s; } + +.store-item:active { + transform: scale(0.98); +} + +.store-item.selected { + background: var(--bg-warm); + border: 2rpx solid var(--border-primary); +} + +.store-item-icon { + width: 72rpx; + height: 72rpx; + border-radius: var(--radius-md); + background: var(--bg-soft); + display: flex; + align-items: center; + justify-content: center; + margin-right: 16rpx; +} + +.store-item-icon.selected { + background: linear-gradient(135deg, #FFF0EB, #FFE4D9); +} + +.store-item-icon image { + width: 40rpx; + height: 40rpx; +} + +.store-item-info { + flex: 1; + overflow: hidden; +} + +.store-item-name { + display: block; + font-size: 28rpx; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 6rpx; +} + +.store-item-address { + display: block; + font-size: 24rpx; + color: var(--text-muted); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.store-item-meta { + display: flex; + align-items: center; + gap: 12rpx; + margin-top: 8rpx; +} + +.store-distance { padding: 4rpx 12rpx; - border-radius: 8rpx; -} - -.distance { + background: var(--bg-soft); + border-radius: var(--radius-full); font-size: 22rpx; - color: var(--text-secondary); + color: var(--text-muted); } -.store-check { - width: 48rpx; - height: 48rpx; +.store-users { + font-size: 22rpx; + color: var(--text-muted); } -.store-check image { - width: 100%; - height: 100%; +.store-item-action { + display: flex; + align-items: center; + justify-content: center; + width: 72rpx; + height: 72rpx; } +.select-btn { + width: 56rpx; + height: 56rpx; + border-radius: 50%; + border: 2rpx solid var(--border-light); + background: var(--bg-white); + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s ease; +} + +.select-btn.selected { + border-color: var(--primary); + background: var(--primary); +} + +.select-icon { + font-size: 28rpx; + color: #FFF; + display: none; +} + +.select-btn.selected .select-icon { + display: block; +} + +/* 空状态 */ .empty-state { - padding: 100rpx 0; + display: flex; + flex-direction: column; + align-items: center; + padding: 100rpx 48rpx; + animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.2s backwards; } -.empty-state image { - width: 200rpx; - height: 200rpx; +.empty-icon { + width: 180rpx; + height: 180rpx; + margin-bottom: 32rpx; + opacity: 0.7; +} + +.empty-title { + font-size: 30rpx; + font-weight: 600; + color: var(--text-secondary); + margin-bottom: 12rpx; +} + +.empty-desc { + font-size: 26rpx; + color: var(--text-muted); +} + +/* 加载状态 */ +.loading-state { + display: flex; + align-items: center; + justify-content: center; + padding: 48rpx; + color: var(--text-muted); + font-size: 26rpx; +} + +/* 底部安全区域 */ +.safe-bottom { + height: 80rpx; } diff --git a/miniprogram/pages/user/index.js b/miniprogram/pages/user/index.js index c12a12aa..9b3b4e69 100644 --- a/miniprogram/pages/user/index.js +++ b/miniprogram/pages/user/index.js @@ -1,5 +1,4 @@ const app = getApp() -const QRCode = require('../../utils/qrcode.js') Page({ data: { @@ -7,8 +6,15 @@ Page({ ladderUser: null, currentStore: null, showQrcode: false, - needProfile: false, - tempUserProfile: null + qrcodeImage: '', + qrcodeLoading: false, + // 完善资料弹框 + showProfileModal: false, + profileForm: { + avatar: '', + nickname: '' + }, + isEditMode: false // true: 编辑模式,false: 完善模式(登录时) }, onLoad() { @@ -16,7 +22,26 @@ Page({ }, onShow() { - this.refreshData() + // 检查门店是否切换 + if (app.globalData.storeChanged) { + app.globalData.storeChanged = false + this.refreshData() + } else { + // 同步最新数据 + this.setData({ + userInfo: app.globalData.userInfo, + ladderUser: app.globalData.ladderUser, + currentStore: app.globalData.currentStore + }) + } + }, + + async onPullDownRefresh() { + try { + await this.refreshData() + } finally { + wx.stopPullDownRefresh() + } }, async initData() { @@ -64,36 +89,43 @@ Page({ await app.wxLogin() } - // 获取用户头像昵称 - let userProfile = this.data.tempUserProfile - if (!userProfile) { - try { - const profileRes = await wx.getUserProfile({ - desc: '用于完善会员资料' - }) - userProfile = profileRes.userInfo - } catch (err) { - // 用户拒绝授权头像昵称,使用默认值 - userProfile = { nickName: '新用户', avatarUrl: '' } - } - } - - // 手机号登录 - await app.phoneLogin(e.detail.encryptedData, e.detail.iv, userProfile) + // 手机号登录(先不传头像昵称) + await app.phoneLogin(e.detail.encryptedData, e.detail.iv, null) // 获取门店信息 await app.getCurrentStore() + const userInfo = app.globalData.userInfo + this.setData({ - userInfo: app.globalData.userInfo, + userInfo: userInfo, ladderUser: app.globalData.ladderUser, - currentStore: app.globalData.currentStore, - needProfile: false, - tempUserProfile: null + currentStore: app.globalData.currentStore }) wx.hideLoading() - wx.showToast({ title: '登录成功', icon: 'success' }) + + // 检查是否需要完善资料(没有头像或昵称为默认值) + 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) { wx.hideLoading() console.error('登录失败:', e) @@ -101,47 +133,162 @@ Page({ } }, - // 选择头像 - async onChooseAvatar() { + // 点击头像,打开编辑资料弹框 + onTapAvatar() { + if (!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 || '' + } + }) + }, + + // 选择头像(新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 + }) + }, + + // 确认保存资料 + async saveProfile() { + const { avatar, nickname } = this.data.profileForm + + if (!nickname || nickname.trim() === '') { + wx.showToast({ title: '请输入昵称', icon: 'none' }) + return + } + + wx.showLoading({ title: '保存中...' }) + try { - const res = await wx.getUserProfile({ - desc: '用于完善会员资料' - }) + // 如果选择了新头像,先上传 + let avatarUrl = avatar + if (avatar && (avatar.startsWith('wxfile://') || avatar.startsWith('http://tmp'))) { + avatarUrl = await this.uploadAvatar(avatar) + } + + // 调用更新资料接口 + const res = await app.request('/api/user/profile', { + nickname: nickname.trim(), + avatar: avatarUrl + }, 'PUT') + + // 更新本地数据(服务端已返回完整URL) + const userInfo = { + ...this.data.userInfo, + nickname: res.data?.nickname || nickname.trim(), + avatar: res.data?.avatar || avatarUrl + } + app.globalData.userInfo = userInfo + this.setData({ - tempUserProfile: res.userInfo, - needProfile: false + userInfo: userInfo, + showProfileModal: false, + profileForm: { avatar: '', nickname: '' } }) - wx.showToast({ title: '已获取头像昵称', icon: 'success' }) + + wx.hideLoading() + wx.showToast({ title: '保存成功', icon: 'success' }) } catch (e) { - wx.showToast({ title: '获取头像昵称失败', icon: 'none' }) + wx.hideLoading() + console.error('保存资料失败:', e) + wx.showToast({ title: e.message || '保存失败', icon: 'none' }) } }, - // 旧的登录方法(兼容) - async handleLogin() { - // 触发手机号授权按钮 - wx.showToast({ title: '请点击手机号登录按钮', 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?.url) { + resolve(data.data.url) + } else { + console.error('上传头像失败:', data) + resolve(filePath) + } + } catch (e) { + resolve(filePath) + } + }, + fail: (err) => { + console.error('上传头像失败:', err) + resolve(filePath) + } + }) + }) }, - showMemberCode() { + // 关闭资料弹框 + 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?.memberCode) return - this.setData({ showQrcode: true }) + this.setData({ + showQrcode: true, + qrcodeLoading: true + }) - // 生成二维码 - setTimeout(() => { - new QRCode('qrcode', { - text: this.data.userInfo.memberCode, - width: 200, - height: 200, - colorDark: '#000000', - colorLight: '#ffffff' - }) - }, 100) + 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 }) + this.setData({ + showQrcode: false, + qrcodeImage: '' + }) }, goTo(e) { @@ -151,5 +298,10 @@ Page({ return } wx.navigateTo({ url }) + }, + + // 阻止事件冒泡 + preventBubble() { + // 空函数,仅用于阻止事件冒泡 } }) diff --git a/miniprogram/pages/user/index.json b/miniprogram/pages/user/index.json index 4ec55be3..a195f4c8 100644 --- a/miniprogram/pages/user/index.json +++ b/miniprogram/pages/user/index.json @@ -1,3 +1,4 @@ { - "navigationBarTitleText": "我的" + "navigationBarTitleText": "我的", + "enablePullDownRefresh": true } diff --git a/miniprogram/pages/user/index.wxml b/miniprogram/pages/user/index.wxml index 41991a8d..c61d8114 100644 --- a/miniprogram/pages/user/index.wxml +++ b/miniprogram/pages/user/index.wxml @@ -1,108 +1,268 @@ - - - - + + + + + + + + + - - - - {{userInfo.nickname || '新用户'}} - - 会员码: {{userInfo.memberCode}} - + + + + + + + ✏️ + + + + + + + + + + + + + + + 我的会员码 + {{userInfo.memberCode}} + + + 查看 + + + + 出示给对方扫码挑战 + + + + + + + + + + + + + 天梯战绩 + {{currentStore.storeName}} + + + + + + {{ladderUser.levelName || 'Lv' + ladderUser.level}} + + {{ladderUser.realName}} + + + + + 战力值 + {{ladderUser.powerScore}} + + + + + + + + + {{ladderUser.winCount || 0}} + 胜场 + + + {{ladderUser.matchCount - ladderUser.winCount || 0}} + 负场 + + + {{ladderUser.matchCount > 0 ? Math.round(ladderUser.winCount / ladderUser.matchCount * 100) : 0}}% + 胜率 - - - - - - - {{userInfo.totalPoints || 0}} - 我的积分 - - - - 天梯信息 - {{currentStore.storeName}} - - - {{ladderUser.realName}} - 姓名 - - - Lv{{ladderUser.level}} - 等级 - - - {{ladderUser.powerScore}} - 战力值 - - - {{ladderUser.matchCount}} - 比赛场次 - + + + 🏸 + + 尚未加入天梯系统 + 请联系门店工作人员,开启你的天梯之旅 - - - 您还不是天梯用户,请联系门店工作人员加入天梯系统 - - - - - - 比赛记录 - - - - - 积分记录 - - - - - 兑换订单 - - - - - 切换门店 - + + + + + + + 比赛记录 + + + + + + + 积分记录 + + + + + + + 兑换订单 + + + + + + + 切换门店 + - - - 我的会员码 - - {{userInfo.memberCode}} - 请将会员码出示给对方扫描 + + + + 我的会员码 + × + + + + + + + + 生成中... + + + + + + + + + + + + + + 会员码 + {{userInfo.memberCode}} + + + + + 📱 + 请出示给对方扫描发起挑战 + + + + + + + + + + {{isEditMode ? '修改资料' : '完善个人资料'}} + × + + + + + + 💡 + 完善资料后,好友可以更容易找到你 + + + + + 头像 + + 点击更换头像 + + + + + 昵称 + + + + + + + + diff --git a/miniprogram/pages/user/index.wxss b/miniprogram/pages/user/index.wxss index 2e9fe295..23955708 100644 --- a/miniprogram/pages/user/index.wxss +++ b/miniprogram/pages/user/index.wxss @@ -1,267 +1,1033 @@ -/* 用户卡片 */ -.user-card { - background: linear-gradient(135deg, var(--primary-color), var(--primary-light)); - border-radius: 20rpx; - padding: 40rpx; - margin-bottom: 20rpx; - color: #fff; +/* ========================================== + 个人中心页面 - 浅色高级感设计 + ========================================== */ + +.page-container { + min-height: 100vh; + background: var(--bg-page); + padding-bottom: 120rpx; + position: relative; + overflow: hidden; } -.user-header { - display: flex; - align-items: center; - margin-bottom: 30rpx; +/* 顶部装饰背景 */ +.hero-bg { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 480rpx; + background: linear-gradient(180deg, #FFF5F0 0%, var(--bg-page) 100%); + pointer-events: none; } -.user-header .avatar-large { - width: 120rpx; - height: 120rpx; +.hero-pattern { + position: absolute; + top: -100rpx; + right: -80rpx; + width: 400rpx; + height: 400rpx; + background: radial-gradient(circle, rgba(255, 107, 53, 0.08) 0%, transparent 70%); border-radius: 50%; - border: 4rpx solid rgba(255, 255, 255, 0.3); - margin-right: 24rpx; + animation: pulse 4s ease-in-out infinite; } -/* 登录面板 */ -.login-panel { +/* 用户信息区域 */ +.user-section { + position: relative; + z-index: 1; + padding: 32rpx 24rpx 24rpx; +} + +/* ========================================== + 已登录 - 个人资料卡片 + ========================================== */ +.profile-card { display: flex; flex-direction: column; align-items: center; - padding: 40rpx 0; + padding: 48rpx 32rpx 40rpx; + background: var(--bg-white); + border-radius: var(--radius-xl); + box-shadow: var(--shadow-lg); + margin-bottom: 20rpx; + animation: fadeInScale 0.5s cubic-bezier(0.4, 0, 0.2, 1); } -.login-panel .avatar-large { +/* 头像容器 - 已登录 */ +.avatar-wrapper { + position: relative; + width: 160rpx; + height: 160rpx; + margin-bottom: 20rpx; +} + +.avatar-xl { width: 160rpx; height: 160rpx; border-radius: 50%; - border: 4rpx solid rgba(255, 255, 255, 0.3); - margin-bottom: 30rpx; + border: 4rpx solid var(--bg-white); + box-shadow: 0 8rpx 32rpx rgba(255, 107, 53, 0.2); + box-sizing: border-box; } -.login-tips { +.avatar-ring { + position: absolute; + top: -12rpx; + left: -12rpx; + width: 184rpx; + height: 184rpx; + border: 2rpx dashed var(--primary); + border-radius: 50%; + opacity: 0.4; + animation: spin 20s linear infinite; + box-sizing: border-box; +} + +.user-info { text-align: center; - margin-bottom: 40rpx; + width: 100%; } -.login-tips .title { - display: block; +.nickname { font-size: 36rpx; - font-weight: 600; - margin-bottom: 12rpx; -} - -.login-tips .desc { + font-weight: 700; + color: var(--text-primary); display: block; - font-size: 26rpx; - opacity: 0.8; -} - -.login-btn { - width: 80%; - height: 88rpx; - line-height: 88rpx; - border-radius: 44rpx; - font-size: 30rpx; - font-weight: 500; margin-bottom: 20rpx; - border: none; + letter-spacing: 1rpx; } -.login-btn.phone-btn { - background: #fff; - color: var(--primary-color); -} - -.login-btn.profile-btn { - background: rgba(255, 255, 255, 0.2); - color: #fff; -} - -.user-meta { - flex: 1; -} - -.user-meta .nickname { - font-size: 36rpx; - font-weight: 600; - display: block; - margin-bottom: 8rpx; -} - -.member-code { +/* 数据统计区 */ +.user-stats { display: flex; align-items: center; - font-size: 24rpx; - opacity: 0.9; + justify-content: center; + padding: 20rpx 24rpx; + background: var(--bg-soft); + border-radius: var(--radius-lg); } -.member-code image { - width: 32rpx; - height: 32rpx; - margin-left: 8rpx; -} - -.points-display { - text-align: center; - padding: 30rpx; - background: rgba(255, 255, 255, 0.15); - border-radius: 16rpx; -} - -.points-value { - font-size: 56rpx; - font-weight: 700; -} - -.points-label { - font-size: 24rpx; - opacity: 0.8; - margin-top: 8rpx; -} - -/* 天梯卡片 */ -.ladder-card { - background: #fff; - border-radius: 20rpx; - padding: 30rpx; - margin-bottom: 20rpx; -} - -.card-title { - font-size: 28rpx; - font-weight: 500; - margin-bottom: 24rpx; - padding-bottom: 16rpx; - border-bottom: 1rpx solid var(--border-color); -} - -.ladder-info { - display: flex; - justify-content: space-around; -} - -.info-item { +.stat-item { + flex: 1; display: flex; flex-direction: column; align-items: center; + padding: 0 16rpx; } -.info-item .value { - font-size: 32rpx; - font-weight: 600; +.stat-value { + font-size: 36rpx; + font-weight: 700; + color: var(--text-primary); + line-height: 1.2; +} + +.stat-value.highlight { + color: var(--primary); +} + +.stat-label { + font-size: 22rpx; + color: var(--text-muted); + margin-top: 4rpx; +} + +.stat-divider { + width: 1rpx; + height: 48rpx; + background: var(--border-light); + flex-shrink: 0; +} + +/* ========================================== + 会员码卡片 - 优化布局 + ========================================== */ +.member-card { + position: relative; + margin-bottom: 20rpx; + border-radius: var(--radius-xl); + overflow: hidden; + background: var(--bg-white); + box-shadow: var(--shadow-md); + animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s backwards; +} + +.member-card-bg { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(135deg, #FFF8F5 0%, #FFFFFF 50%, #FFF5F0 100%); +} + +.member-card-content { + position: relative; + display: flex; + align-items: center; + padding: 28rpx 24rpx; +} + +.member-icon { + width: 80rpx; + height: 80rpx; + border-radius: 50%; + background: var(--primary-gradient); + display: flex; + align-items: center; + justify-content: center; + box-shadow: var(--shadow-primary); + flex-shrink: 0; + margin-right: 20rpx; +} + +.member-icon image { + width: 40rpx; + height: 40rpx; + filter: brightness(10); +} + +.member-info { + flex: 1; + min-width: 0; + overflow: hidden; +} + +.member-title { + display: block; + font-size: 22rpx; + color: var(--text-muted); + margin-bottom: 4rpx; +} + +.member-code { + display: block; + font-size: 28rpx; + font-weight: 700; + color: var(--text-primary); + letter-spacing: 1rpx; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.member-action { + display: flex; + align-items: center; + gap: 4rpx; + padding: 10rpx 16rpx; + background: var(--primary-soft); + border-radius: var(--radius-full); + flex-shrink: 0; + margin-left: 12rpx; +} + +.action-text { + font-size: 22rpx; + color: var(--primary); + font-weight: 500; + white-space: nowrap; +} + +.action-arrow { + font-size: 24rpx; + color: var(--primary); + font-weight: 300; +} + +.scan-hint { + position: relative; + padding: 12rpx 24rpx; + background: linear-gradient(90deg, var(--primary-soft), #FFF8F5); + text-align: center; + font-size: 22rpx; + color: var(--primary); + border-top: 1rpx solid rgba(255, 107, 53, 0.1); +} + +/* ========================================== + 未登录卡片 + ========================================== */ +.login-card { + display: flex; + flex-direction: column; + align-items: center; + padding: 48rpx 40rpx; + background: var(--bg-white); + border-radius: var(--radius-xl); + box-shadow: var(--shadow-lg); + animation: bounceIn 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55); + margin-bottom: 24rpx; +} + +/* 头像容器 - 未登录 */ +.login-avatar-wrapper { + position: relative; + width: 160rpx; + height: 160rpx; + margin-bottom: 28rpx; +} + +.login-avatar { + width: 160rpx; + height: 160rpx; + border-radius: 50%; + border: 4rpx solid var(--border-light); + box-sizing: border-box; +} + +.login-avatar-pulse { + position: absolute; + top: -12rpx; + left: -12rpx; + width: 184rpx; + height: 184rpx; + border: 2rpx dashed var(--primary); + border-radius: 50%; + opacity: 0.4; + animation: spin 15s linear infinite; + box-sizing: border-box; +} + +.login-content { + text-align: center; + margin-bottom: 32rpx; +} + +.login-title { + display: block; + font-size: 36rpx; + font-weight: 700; + color: var(--text-primary); margin-bottom: 8rpx; } -.info-item .value.highlight { - color: var(--primary-color); +.login-subtitle { + display: block; + font-size: 24rpx; + color: var(--text-muted); } -.info-item .label { - font-size: 22rpx; - color: var(--text-secondary); -} - -/* 提示卡片 */ -.notice-card { +.login-btn-primary { + width: 100%; + height: 96rpx; display: flex; align-items: center; - background: #FFF9E6; - border-radius: 16rpx; + justify-content: center; + gap: 12rpx; + background: var(--primary-gradient); + border: none; + border-radius: var(--radius-full); + margin-bottom: 16rpx; + box-shadow: var(--shadow-primary); + white-space: nowrap; + padding: 0 40rpx; + box-sizing: border-box; +} + +.login-btn-primary .btn-icon { + font-size: 32rpx; + flex-shrink: 0; +} + +.login-btn-primary .btn-text { + font-size: 30rpx; + font-weight: 600; + color: var(--text-white); + letter-spacing: 1rpx; + white-space: nowrap; +} + +.login-btn-secondary { + width: 100%; + height: 84rpx; + background: var(--bg-soft); + border: none; + border-radius: var(--radius-full); + color: var(--text-secondary); + font-size: 28rpx; +} + +/* ========================================== + 天梯信息区域 + ========================================== */ +.ladder-section { + margin: 0 24rpx 20rpx; + background: var(--bg-white); + border-radius: var(--radius-xl); + box-shadow: var(--shadow-card); + overflow: hidden; + animation: slideInLeft 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.2s backwards; +} + +.section-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 20rpx 24rpx; + background: linear-gradient(90deg, #FFF8F5, var(--bg-white)); + border-bottom: 1rpx solid var(--border-soft); +} + +.section-title { + font-size: 28rpx; + font-weight: 600; + color: var(--text-primary); +} + +.section-badge { + font-size: 20rpx; + color: var(--primary); + background: var(--primary-soft); + padding: 4rpx 12rpx; + border-radius: var(--radius-full); +} + +.ladder-stats { padding: 24rpx; +} + +.ladder-stat-item { + display: flex; + align-items: center; + gap: 16rpx; margin-bottom: 20rpx; } -.notice-card image { - width: 40rpx; - height: 40rpx; - margin-right: 16rpx; +.stat-icon { + padding: 8rpx 16rpx; + border-radius: var(--radius-full); + font-size: 22rpx; + font-weight: 600; + color: #fff; } -.notice-card text { +.stat-icon.lv1 { background: linear-gradient(135deg, #4CAF50, #8BC34A); } +.stat-icon.lv2 { background: linear-gradient(135deg, #2196F3, #03A9F4); } +.stat-icon.lv3 { background: linear-gradient(135deg, #FF9800, #FFC107); } +.stat-icon.lv4 { background: linear-gradient(135deg, #E91E63, #FF5722); } +.stat-icon.lv5 { background: linear-gradient(135deg, #9C27B0, #673AB7); } + +.stat-name { + font-size: 30rpx; + font-weight: 600; + color: var(--text-primary); +} + +.ladder-progress { + margin-bottom: 20rpx; +} + +.progress-info { + display: flex; + justify-content: space-between; + margin-bottom: 10rpx; +} + +.progress-label { + font-size: 22rpx; + color: var(--text-muted); +} + +.progress-value { font-size: 26rpx; - color: #B7791F; + font-weight: 700; + color: var(--primary); +} + +.progress-bar { + height: 10rpx; + background: var(--bg-soft); + border-radius: 5rpx; + overflow: hidden; +} + +.progress-fill { + height: 100%; + background: var(--primary-gradient); + border-radius: 5rpx; + transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1); + max-width: 100%; +} + +.ladder-record { + display: flex; + justify-content: space-around; + padding-top: 20rpx; + border-top: 1rpx solid var(--border-soft); +} + +.record-item { + text-align: center; +} + +.record-value { + display: block; + font-size: 32rpx; + font-weight: 700; + color: var(--text-primary); + margin-bottom: 4rpx; +} + +.record-value.win { + color: var(--accent); +} + +.record-value.rate { + color: var(--primary); +} + +.record-label { + font-size: 20rpx; + color: var(--text-muted); +} + +/* ========================================== + 提示卡片 + ========================================== */ +.notice-card { + display: flex; + align-items: flex-start; + gap: 16rpx; + margin: 0 24rpx 20rpx; + padding: 24rpx; + background: #FFFBF5; + border: 1rpx solid #FFE8D5; + border-radius: var(--radius-lg); + animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.2s backwards; +} + +.notice-icon { + font-size: 36rpx; +} + +.notice-content { flex: 1; } -/* 菜单列表 */ -.menu-list { - background: #fff; - border-radius: 20rpx; - overflow: hidden; +.notice-title { + display: block; + font-size: 26rpx; + font-weight: 600; + color: #B7791F; + margin-bottom: 6rpx; +} + +.notice-desc { + display: block; + font-size: 22rpx; + color: #C68A42; + line-height: 1.5; +} + +/* ========================================== + 功能菜单 - 网格布局 + ========================================== */ +.menu-section { + margin: 0 24rpx; + animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.3s backwards; +} + +.menu-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 12rpx; } .menu-item { display: flex; + flex-direction: column; align-items: center; - padding: 30rpx; - border-bottom: 1rpx solid var(--border-color); + padding: 24rpx 12rpx; + background: var(--bg-white); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-sm); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } -.menu-item:last-child { - border-bottom: none; +.menu-item:active { + transform: scale(0.94); + box-shadow: none; } -.menu-item image:first-child { - width: 44rpx; - height: 44rpx; - margin-right: 20rpx; +.menu-icon { + width: 72rpx; + height: 72rpx; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 12rpx; } -.menu-item text { - flex: 1; - font-size: 28rpx; +.menu-icon.history { + background: linear-gradient(135deg, #E3F2FD, #BBDEFB); } -.menu-item .arrow { - width: 32rpx; - height: 32rpx; - opacity: 0.4; +.menu-icon.points { + background: linear-gradient(135deg, #FFF8E1, #FFECB3); } -/* 二维码弹窗 */ -.qrcode-modal { +.menu-icon.order { + background: linear-gradient(135deg, #E8F5E9, #C8E6C9); +} + +.menu-icon.store { + background: linear-gradient(135deg, #FFF3E0, #FFE0B2); +} + +.menu-icon image { + width: 36rpx; + height: 36rpx; +} + +.menu-text { + font-size: 22rpx; + color: var(--text-secondary); + font-weight: 500; +} + +/* ========================================== + 会员码弹窗 - 高级感设计 + ========================================== */ +.qrcode-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; - background: rgba(0, 0, 0, 0.6); + background: rgba(0, 0, 0, 0); display: flex; align-items: center; justify-content: center; z-index: 999; + visibility: hidden; + transition: all 0.35s ease; } -.modal-content { - background: #fff; - border-radius: 20rpx; - padding: 40rpx; - text-align: center; - width: 80%; +.qrcode-overlay.show { + visibility: visible; + background: rgba(0, 0, 0, 0.5); +} + +.qrcode-modal { + width: 85%; max-width: 600rpx; + background: var(--bg-white); + border-radius: var(--radius-xl); + overflow: hidden; + transform: scale(0.85) translateY(40rpx); + opacity: 0; + transition: all 0.35s cubic-bezier(0.34, 1.56, 0.64, 1); + box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.15); +} + +.qrcode-modal.show { + transform: scale(1) translateY(0); + opacity: 1; +} + +.modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 24rpx 28rpx; + background: linear-gradient(90deg, #FFF8F5, var(--bg-white)); + border-bottom: 1rpx solid var(--border-soft); } .modal-title { - font-size: 32rpx; + font-size: 30rpx; font-weight: 600; - margin-bottom: 30rpx; + color: var(--text-primary); } -.qrcode-canvas { - width: 400rpx; - height: 400rpx; - margin: 0 auto 20rpx; +.modal-close { + width: 52rpx; + height: 52rpx; + display: flex; + align-items: center; + justify-content: center; + font-size: 32rpx; + color: var(--text-muted); + background: var(--bg-soft); + border-radius: 50%; + transition: all 0.2s; } -.qrcode-text { - display: block; - font-size: 32rpx; - font-weight: 600; - color: var(--primary-color); +.modal-close:active { + background: var(--border-light); + transform: scale(0.9); +} + +.qrcode-wrapper { + position: relative; + padding: 40rpx; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-soft); +} + +.qrcode-border { + width: 320rpx; + height: 320rpx; + background: #FFFFFF; + border-radius: var(--radius-lg); + padding: 16rpx; + box-shadow: var(--shadow-md); + display: flex; + align-items: center; + justify-content: center; + position: relative; +} + +.qrcode-image { + width: 288rpx; + height: 288rpx; +} + +.qrcode-loading { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 288rpx; + height: 288rpx; +} + +.loading-spinner { + width: 48rpx; + height: 48rpx; + border: 4rpx solid var(--border-light); + border-top-color: var(--primary); + border-radius: 50%; + animation: spin 0.8s linear infinite; margin-bottom: 16rpx; } -.qrcode-tip { - display: block; +.loading-text { font-size: 24rpx; + color: var(--text-muted); +} + +.qrcode-corners { + position: absolute; + top: 32rpx; + left: 32rpx; + right: 32rpx; + bottom: 32rpx; + pointer-events: none; +} + +.corner { + position: absolute; + width: 40rpx; + height: 40rpx; + border-color: var(--primary); + border-style: solid; +} + +.corner.tl { top: 0; left: 0; border-width: 6rpx 0 0 6rpx; border-radius: 10rpx 0 0 0; } +.corner.tr { top: 0; right: 0; border-width: 6rpx 6rpx 0 0; border-radius: 0 10rpx 0 0; } +.corner.bl { bottom: 0; left: 0; border-width: 0 0 6rpx 6rpx; border-radius: 0 0 0 10rpx; } +.corner.br { bottom: 0; right: 0; border-width: 0 6rpx 6rpx 0; border-radius: 0 0 10rpx 0; } + +.qrcode-info { + text-align: center; + padding: 24rpx 28rpx; +} + +.code-label { + display: block; + font-size: 22rpx; + color: var(--text-muted); + margin-bottom: 6rpx; +} + +.code-value { + display: block; + font-size: 36rpx; + font-weight: 700; + color: var(--primary); + letter-spacing: 4rpx; +} + +.qrcode-tips { + padding: 20rpx 28rpx; + background: var(--primary-soft); + border-top: 1rpx solid rgba(255, 107, 53, 0.1); +} + +.tip-item { + display: flex; + align-items: center; + justify-content: center; + gap: 10rpx; +} + +.tip-icon { + font-size: 24rpx; +} + +.tip-text { + font-size: 24rpx; + color: var(--primary); +} + +/* ========================================== + 头像编辑标识 + ========================================== */ +.avatar-edit-badge { + position: absolute; + bottom: 4rpx; + right: 4rpx; + width: 40rpx; + height: 40rpx; + background: var(--bg-white); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + box-shadow: var(--shadow-sm); + z-index: 2; +} + +.edit-icon { + font-size: 20rpx; +} + +/* ========================================== + 完善资料弹窗 + ========================================== */ +.profile-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + visibility: hidden; + transition: all 0.35s ease; +} + +.profile-overlay.show { + visibility: visible; + background: rgba(0, 0, 0, 0.55); +} + +.profile-modal { + width: 88%; + max-width: 640rpx; + background: var(--bg-white); + border-radius: var(--radius-xl); + overflow: hidden; + transform: scale(0.85) translateY(40rpx); + opacity: 0; + transition: all 0.35s cubic-bezier(0.34, 1.56, 0.64, 1); + box-shadow: 0 24rpx 80rpx rgba(0, 0, 0, 0.2); +} + +.profile-modal.show { + transform: scale(1) translateY(0); + opacity: 1; +} + +.profile-modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 28rpx 32rpx; + background: linear-gradient(90deg, #FFF8F5, var(--bg-white)); + border-bottom: 1rpx solid var(--border-soft); +} + +.profile-modal-title { + font-size: 32rpx; + font-weight: 600; + color: var(--text-primary); +} + +.profile-modal-close { + width: 52rpx; + height: 52rpx; + display: flex; + align-items: center; + justify-content: center; + font-size: 36rpx; + color: var(--text-muted); + background: var(--bg-soft); + border-radius: 50%; + transition: all 0.2s; +} + +.profile-modal-close:active { + background: var(--border-light); + transform: scale(0.9); +} + +.profile-modal-body { + padding: 32rpx; +} + +.profile-tips { + display: flex; + align-items: center; + gap: 12rpx; + padding: 20rpx 24rpx; + background: #FFFBF5; + border: 1rpx solid #FFE8D5; + border-radius: var(--radius-lg); + margin-bottom: 32rpx; +} + +.tips-icon { + font-size: 32rpx; +} + +.tips-text { + font-size: 24rpx; + color: #B7791F; + line-height: 1.4; +} + +/* 头像选择区域 */ +.profile-avatar-section { + display: flex; + flex-direction: column; + align-items: center; + margin-bottom: 32rpx; +} + +.profile-label { + font-size: 26rpx; + font-weight: 500; + color: var(--text-secondary); + margin-bottom: 20rpx; + align-self: flex-start; +} + +.avatar-choose-btn { + position: relative; + width: 160rpx; + height: 160rpx; + padding: 0; + margin: 0; + border: none; + background: transparent; + border-radius: 50%; + overflow: visible; +} + +.avatar-choose-btn::after { + display: none; +} + +.profile-avatar-preview { + width: 160rpx; + height: 160rpx; + border-radius: 50%; + border: 4rpx solid var(--primary-soft); + box-sizing: border-box; + background: var(--bg-soft); +} + +/* 头像编辑小图标 - 右下角 */ +.avatar-choose-badge { + position: absolute; + bottom: 0; + right: 0; + width: 48rpx; + height: 48rpx; + background: var(--primary); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.3); + border: 3rpx solid #fff; +} + +.avatar-choose-badge .choose-icon { + font-size: 24rpx; + line-height: 1; +} + +.avatar-tip { + font-size: 22rpx; + color: var(--text-muted); + margin-top: 12rpx; +} + +/* 昵称输入区域 */ +.profile-nickname-section { + display: flex; + flex-direction: column; +} + +.nickname-input { + height: 96rpx; + padding: 0 24rpx; + background: var(--bg-soft); + border: 2rpx solid var(--border-light); + border-radius: var(--radius-lg); + font-size: 30rpx; + color: var(--text-primary); + transition: all 0.2s; +} + +.nickname-input:focus { + border-color: var(--primary); + background: var(--bg-white); +} + +.nickname-input::placeholder { + color: var(--text-muted); +} + +/* 弹窗底部按钮 */ +.profile-modal-footer { + display: flex; + gap: 20rpx; + padding: 24rpx 32rpx 32rpx; + background: var(--bg-white); +} + +.profile-btn-cancel, +.profile-btn-confirm { + flex: 1; + height: 88rpx; + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--radius-lg); + font-size: 28rpx; + font-weight: 600; + border: none; + transition: all 0.2s; +} + +.profile-btn-cancel { + background: var(--bg-soft); color: var(--text-secondary); } + +.profile-btn-cancel:active { + background: var(--border-light); +} + +.profile-btn-confirm { + background: var(--primary-gradient); + color: #fff; + box-shadow: var(--shadow-primary); +} + +.profile-btn-confirm:active { + transform: scale(0.96); + box-shadow: none; +} diff --git a/server/.env b/server/.env index 03ff4cb0..c42fa886 100644 --- a/server/.env +++ b/server/.env @@ -51,12 +51,12 @@ JWT_EXPIRES_IN=7d # ------------------------------------------ # 微信小程序 AppID # 在微信公众平台 -> 开发管理 -> 开发设置 中获取 -WX_APPID=your_wechat_appid_here +WX_APPID=wxcadb23af01ea7229 # 微信小程序 AppSecret # 在微信公众平台 -> 开发管理 -> 开发设置 中获取 # 注意:请妥善保管,不要泄露 -WX_SECRET=your_wechat_secret_here +WX_SECRET=34b3995fd79e8fec64f4ffcda63dd92e # ------------------------------------------ # 文件上传配置(可选) diff --git a/server/src/app.js b/server/src/app.js index ee5d564e..e916e2b3 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -60,8 +60,10 @@ async function startServer() { console.log('数据库连接成功'); // 同步模型(开发环境) + // 注意:如果遇到 ER_TOO_MANY_KEYS 错误,需要手动清理数据库中多余的索引 if (process.env.NODE_ENV === 'development') { - await sequelize.sync({ alter: true }); + // 使用 alter: false 避免索引过多的问题 + await sequelize.sync({ alter: false }); console.log('数据库模型同步完成'); } diff --git a/server/src/controllers/matchController.js b/server/src/controllers/matchController.js index b3cb0693..00d35a5d 100644 --- a/server/src/controllers/matchController.js +++ b/server/src/controllers/matchController.js @@ -494,12 +494,17 @@ class MatchController { return res.status(404).json(error('比赛不存在', 404)); } + const stageNames = ['报名中', '循环赛', '淘汰赛', '已结束']; + const statusNames = ['待开始', '进行中', '已结束', '已取消']; + res.json(success({ id: match.id, matchCode: match.match_code, name: match.name, status: match.status, + statusName: statusNames[match.status] || '未知', stage: match.stage, + stageName: stageNames[match.stage] || '未知', weight: match.weight, storeName: match.store?.name, startTime: match.start_time, @@ -694,6 +699,157 @@ class MatchController { } } + // 获取正在进行中的比赛 + async getOngoingMatches(req, res) { + try { + const { store_id } = req.query; + const user = req.user; + + const ladderUser = await LadderUser.findOne({ + where: { user_id: user.id, store_id, status: 1 } + }); + + if (!ladderUser) { + return res.json(success([])); + } + + // 先获取用户参与的排位赛比赛ID + const myMatchPlayers = await MatchPlayer.findAll({ + where: { ladder_user_id: ladderUser.id }, + attributes: ['match_id'] + }); + const rankingMatchIds = myMatchPlayers.map(mp => mp.match_id); + + // 查找用户参与的正在进行中的比赛 + const whereCondition = { + store_id, + status: { [Op.in]: [MATCH_STATUS.PENDING, MATCH_STATUS.ONGOING] }, + [Op.or]: [ + // 挑战赛:作为挑战者或被挑战者 + { challenger_id: ladderUser.id }, + { defender_id: ladderUser.id } + ] + }; + + // 如果有排位赛参与记录,添加到查询条件 + if (rankingMatchIds.length > 0) { + whereCondition[Op.or].push({ id: { [Op.in]: rankingMatchIds } }); + } + + const ongoingMatches = await Match.findAll({ + where: whereCondition, + include: [ + { model: Store, as: 'store', attributes: ['id', 'name'] } + ], + order: [['created_at', 'DESC']] + }); + + const list = await Promise.all(ongoingMatches.map(async match => { + let opponent = null; + let myStatus = 'waiting'; + let currentGame = null; + + if (match.type === MATCH_TYPES.CHALLENGE) { + // 挑战赛:获取对手信息 + const opponentId = match.challenger_id === ladderUser.id + ? match.defender_id + : match.challenger_id; + const opponentLadder = await LadderUser.findByPk(opponentId, { + include: [{ model: User, as: 'user', attributes: ['nickname', 'avatar'] }] + }); + if (opponentLadder) { + opponent = { + id: opponentLadder.id, + realName: opponentLadder.real_name, + nickname: opponentLadder.user?.nickname, + avatar: opponentLadder.user?.avatar, + level: opponentLadder.level, + powerScore: opponentLadder.power_score + }; + } + myStatus = match.status === MATCH_STATUS.PENDING ? 'waiting' : 'playing'; + } else { + // 排位赛:获取参赛者状态和当前对局 + const player = await MatchPlayer.findOne({ + where: { match_id: match.id, ladder_user_id: ladderUser.id } + }); + if (player) { + myStatus = player.player_status; + + // 获取当前对局 + const game = await MatchGame.findOne({ + where: { + match_id: match.id, + [Op.or]: [ + { player1_id: ladderUser.id }, + { player2_id: ladderUser.id } + ], + status: { [Op.lt]: 2 } + } + }); + + if (game) { + const opponentId = game.player1_id === ladderUser.id + ? game.player2_id + : game.player1_id; + const opponentLadder = await LadderUser.findByPk(opponentId, { + include: [{ model: User, as: 'user', attributes: ['nickname', 'avatar'] }] + }); + if (opponentLadder) { + opponent = { + id: opponentLadder.id, + realName: opponentLadder.real_name, + nickname: opponentLadder.user?.nickname, + avatar: opponentLadder.user?.avatar, + level: opponentLadder.level, + powerScore: opponentLadder.power_score + }; + } + currentGame = { + id: game.id, + myScore: game.player1_id === ladderUser.id ? game.player1_score : game.player2_score, + opponentScore: game.player1_id === ladderUser.id ? game.player2_score : game.player1_score, + confirmStatus: game.confirm_status, + gameStatus: game.status + }; + } + } + } + + // 获取排位赛参赛人数 + let playerCount = 0; + if (match.type === MATCH_TYPES.RANKING) { + playerCount = await MatchPlayer.count({ where: { match_id: match.id } }); + } + + return { + id: match.id, + matchCode: match.match_code, + type: match.type, + typeName: match.type === MATCH_TYPES.CHALLENGE ? '挑战赛' : '排位赛', + name: match.name, + weight: match.weight, + status: match.status, + statusName: match.status === MATCH_STATUS.PENDING ? '待开始' : '进行中', + stage: match.stage, + stageName: match.type === MATCH_TYPES.RANKING ? + ['报名中', '循环赛', '淘汰赛', '已结束'][match.stage] : null, + myStatus, + opponent, + currentGame, + playerCount, + startTime: match.start_time, + createdAt: match.created_at + }; + })); + + res.json(success(list)); + } catch (err) { + console.error('获取进行中比赛失败:', err); + res.status(500).json(error('获取失败')); + } + } + // 获取待确认的比赛 async getPendingConfirm(req, res) { try { diff --git a/server/src/controllers/pointsController.js b/server/src/controllers/pointsController.js index 5c43a7b8..7659d7dc 100644 --- a/server/src/controllers/pointsController.js +++ b/server/src/controllers/pointsController.js @@ -3,6 +3,7 @@ const { ORDER_STATUS } = require('../config/constants'); const { generateOrderNo, generateExchangeCode, success, error, getPagination, pageResult } = require('../utils/helper'); const { Op } = require('sequelize'); const sequelize = require('../config/database'); +const QRCode = require('qrcode'); class PointsController { // 获取积分余额 @@ -272,6 +273,40 @@ class PointsController { res.status(500).json(error('获取失败')); } } + + // 生成兑换码二维码 + async getOrderQrcode(req, res) { + try { + const { code } = req.query; + + if (!code) { + return res.status(400).json(error('缺少兑换码', 400)); + } + + // 生成二维码配置 + const qrOptions = { + errorCorrectionLevel: 'M', + type: 'image/png', + margin: 2, + width: 280, + color: { + dark: '#1A1A1A', + light: '#FFFFFF' + } + }; + + // 生成二维码为 base64 + const qrcodeDataUrl = await QRCode.toDataURL(code, qrOptions); + + res.json(success({ + code: code, + qrcode: qrcodeDataUrl + })); + } catch (err) { + console.error('生成二维码失败:', err); + res.status(500).json(error('生成二维码失败')); + } + } } module.exports = new PointsController(); diff --git a/server/src/controllers/userController.js b/server/src/controllers/userController.js index c3e2d864..03d66af6 100644 --- a/server/src/controllers/userController.js +++ b/server/src/controllers/userController.js @@ -1,13 +1,15 @@ 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 { generateMemberCode, success, error, calculateDistance } = require('../utils/helper'); +const { generateMemberCode, success, error, calculateDistance, getFullUrl } = require('../utils/helper'); +const { LADDER_LEVEL_NAMES } = require('../config/constants'); const { Op } = require('sequelize'); class UserController { // 微信登录(获取 session_key,用于后续手机号解密) - async login(req, res) { + login = async (req, res) => { try { const { code } = req.body; @@ -87,7 +89,7 @@ class UserController { } // 手机号授权登录(解密手机号并完成注册/登录) - async phoneLogin(req, res) { + phoneLogin = async (req, res) => { try { const { openid, unionid, sessionKey, encryptedData, iv, nickname, avatar, gender } = req.body; @@ -165,7 +167,7 @@ class UserController { userInfo: { id: user.id, nickname: user.nickname, - avatar: user.avatar, + avatar: getFullUrl(user.avatar, req), phone: user.phone, gender: user.gender, memberCode: user.member_code, @@ -176,6 +178,7 @@ class UserController { storeName: lu.store?.name, realName: lu.real_name, level: lu.level, + levelName: LADDER_LEVEL_NAMES[lu.level] || `Lv${lu.level}`, powerScore: lu.power_score })) } @@ -224,7 +227,7 @@ class UserController { } // 更新用户资料(头像、昵称) - async updateProfile(req, res) { + updateProfile = async (req, res) => { try { const { nickname, avatar, gender } = req.body; const user = req.user; @@ -238,7 +241,7 @@ class UserController { res.json(success({ nickname: user.nickname, - avatar: user.avatar, + avatar: getFullUrl(user.avatar, req), gender: user.gender }, '更新成功')); } catch (err) { @@ -248,7 +251,7 @@ class UserController { } // 获取用户信息 - async getInfo(req, res) { + getInfo = async (req, res) => { try { const user = req.user; @@ -261,7 +264,7 @@ class UserController { res.json(success({ id: user.id, nickname: user.nickname, - avatar: user.avatar, + avatar: getFullUrl(user.avatar, req), phone: user.phone, gender: user.gender, memberCode: user.member_code, @@ -272,6 +275,7 @@ class UserController { storeName: lu.store?.name, realName: lu.real_name, level: lu.level, + levelName: LADDER_LEVEL_NAMES[lu.level] || `Lv${lu.level}`, powerScore: lu.power_score, matchCount: lu.match_count, winCount: lu.win_count @@ -284,7 +288,7 @@ class UserController { } // 更新用户信息 - async updateInfo(req, res) { + updateInfo = async (req, res) => { try { const { nickname, avatar, phone, gender } = req.body; const user = req.user; @@ -304,7 +308,7 @@ class UserController { } // 获取会员码 - async getMemberCode(req, res) { + getMemberCode = async (req, res) => { try { res.json(success({ memberCode: req.user.member_code @@ -316,7 +320,7 @@ class UserController { } // 通过会员码查询用户 - async getByMemberCode(req, res) { + getByMemberCode = async (req, res) => { try { const { code } = req.params; const { store_id } = req.query; @@ -356,7 +360,7 @@ class UserController { } // 获取用户天梯信息 - async getLadderInfo(req, res) { + getLadderInfo = async (req, res) => { try { const { store_id } = req.query; const user = req.user; @@ -378,6 +382,7 @@ class UserController { realName: lu.real_name, gender: lu.gender, level: lu.level, + levelName: LADDER_LEVEL_NAMES[lu.level] || `Lv${lu.level}`, powerScore: lu.power_score, matchCount: lu.match_count, winCount: lu.win_count, @@ -391,7 +396,7 @@ class UserController { } // 获取当前门店 - async getCurrentStore(req, res) { + getCurrentStore = async (req, res) => { try { const { latitude, longitude } = req.query; const user = req.user; @@ -409,6 +414,7 @@ class UserController { return res.json(success({ storeId: lu.store_id, storeName: lu.store?.name, + storeAddress: lu.store?.address, ladderUserId: lu.id, source: 'last_match' })); @@ -442,6 +448,7 @@ class UserController { return res.json(success({ storeId: nearestStore.id, storeName: nearestStore.name, + storeAddress: nearestStore.address, ladderUserId: null, source: 'nearest' })); @@ -453,6 +460,7 @@ class UserController { res.json(success(firstStore ? { storeId: firstStore.id, storeName: firstStore.name, + storeAddress: firstStore.address, ladderUserId: null, source: 'default' } : null)); @@ -461,6 +469,73 @@ class UserController { res.status(500).json(error('获取失败')); } } + + // 生成会员二维码图片 + getQrcode = async (req, res) => { + try { + const user = req.user; + + if (!user.member_code) { + return res.status(400).json(error('会员码不存在', 400)); + } + + // 生成二维码配置 + const qrOptions = { + errorCorrectionLevel: 'M', + type: 'image/png', + margin: 2, + width: 300, + color: { + dark: '#1A1A1A', + light: '#FFFFFF' + } + }; + + // 生成二维码为 base64 + const qrcodeDataUrl = await QRCode.toDataURL(user.member_code, qrOptions); + + res.json(success({ + memberCode: user.member_code, + qrcode: qrcodeDataUrl + })); + } catch (err) { + console.error('生成二维码失败:', err); + res.status(500).json(error('生成二维码失败')); + } + } + + // 直接返回二维码图片 + getQrcodeImage = async (req, res) => { + try { + const user = req.user; + + if (!user.member_code) { + return res.status(400).send('会员码不存在'); + } + + // 生成二维码配置 + const qrOptions = { + errorCorrectionLevel: 'M', + type: 'png', + margin: 2, + width: 300, + color: { + dark: '#1A1A1A', + light: '#FFFFFF' + } + }; + + // 设置响应头 + res.setHeader('Content-Type', 'image/png'); + res.setHeader('Cache-Control', 'public, max-age=86400'); + + // 直接输出二维码图片 + await QRCode.toFileStream(res, user.member_code, qrOptions); + } catch (err) { + console.error('生成二维码图片失败:', err); + res.status(500).send('生成二维码失败'); + } + } } module.exports = new UserController(); diff --git a/server/src/models/index.js b/server/src/models/index.js index a2f7e997..b1906ff6 100644 --- a/server/src/models/index.js +++ b/server/src/models/index.js @@ -42,6 +42,9 @@ PointProduct.belongsTo(Store, { foreignKey: 'store_id', as: 'store' }); Store.hasMany(PointOrder, { foreignKey: 'store_id', as: 'pointOrders' }); PointOrder.belongsTo(Store, { foreignKey: 'store_id', as: 'store' }); +Store.hasMany(PointRecord, { foreignKey: 'store_id', as: 'pointRecords' }); +PointRecord.belongsTo(Store, { foreignKey: 'store_id', as: 'store' }); + // 系统用户-门店关联 SystemUser.belongsTo(Store, { foreignKey: 'store_id', as: 'store' }); Store.hasMany(SystemUser, { foreignKey: 'store_id', as: 'systemUsers' }); diff --git a/server/src/routes/match.js b/server/src/routes/match.js index 126fc1cc..370f779f 100644 --- a/server/src/routes/match.js +++ b/server/src/routes/match.js @@ -36,6 +36,9 @@ router.post('/ranking/submit-score', authUser, matchController.submitRankingScor router.post('/ranking/confirm-score', authUser, matchController.confirmRankingScore); // === 通用 === +// 获取正在进行中的比赛 +router.get('/ongoing', authUser, matchController.getOngoingMatches); + // 获取我的比赛记录 router.get('/my-matches', authUser, matchController.getMyMatches); diff --git a/server/src/routes/points.js b/server/src/routes/points.js index 5eaa614b..d3d9547c 100644 --- a/server/src/routes/points.js +++ b/server/src/routes/points.js @@ -21,6 +21,9 @@ router.post('/exchange', authUser, pointsController.exchangeProduct); // 获取我的兑换订单 router.get('/orders', authUser, pointsController.getOrders); +// 获取兑换码二维码 +router.get('/orders/qrcode', authUser, pointsController.getOrderQrcode); + // 获取订单详情(含兑换码) router.get('/orders/:id', authUser, pointsController.getOrderDetail); diff --git a/server/src/routes/upload.js b/server/src/routes/upload.js index 07dde79c..48bc022e 100644 --- a/server/src/routes/upload.js +++ b/server/src/routes/upload.js @@ -3,8 +3,8 @@ const router = express.Router(); const multer = require('multer'); const path = require('path'); const { v4: uuidv4 } = require('uuid'); -const { authAdmin } = require('../middlewares/auth'); -const { success, error } = require('../utils/helper'); +const { authAdmin, authUser } = require('../middlewares/auth'); +const { success, error, getFullUrl } = require('../utils/helper'); // 配置文件存储 const storage = multer.diskStorage({ @@ -55,4 +55,15 @@ router.post('/images', authAdmin, upload.array('files', 10), (req, res) => { res.json(success({ urls }, '上传成功')); }); +// 用户头像上传(需要登录) +router.post('/avatar', authUser, upload.single('file'), (req, res) => { + if (!req.file) { + return res.status(400).json(error('请选择要上传的图片')); + } + + const relativePath = `/uploads/${req.file.filename}`; + const fullUrl = getFullUrl(relativePath, req); + res.json(success({ url: fullUrl }, '上传成功')); +}); + module.exports = router; diff --git a/server/src/routes/user.js b/server/src/routes/user.js index e6806655..9be1dfc0 100644 --- a/server/src/routes/user.js +++ b/server/src/routes/user.js @@ -21,6 +21,12 @@ router.put('/info', authUser, userController.updateInfo); // 获取会员码 router.get('/member-code', authUser, userController.getMemberCode); +// 获取会员二维码(返回JSON,包含base64图片) +router.get('/qrcode', authUser, userController.getQrcode); + +// 获取会员二维码图片(直接返回图片流) +router.get('/qrcode-image', authUser, userController.getQrcodeImage); + // 通过会员码查询用户 router.get('/by-member-code/:code', authUser, userController.getByMemberCode); diff --git a/server/src/utils/helper.js b/server/src/utils/helper.js index cf51d827..67ff57e6 100644 --- a/server/src/utils/helper.js +++ b/server/src/utils/helper.js @@ -91,6 +91,24 @@ function pageResult(list, total, page, pageSize) { }; } +/** + * 获取完整URL(处理头像等资源路径) + * @param {string} path - 相对路径或完整URL + * @param {object} req - Express request对象(用于获取host) + * @returns {string} 完整URL + */ +function getFullUrl(path, req) { + if (!path) return ''; + // 已经是完整URL,直接返回 + if (path.startsWith('http://') || path.startsWith('https://')) { + return path; + } + // 相对路径,拼接服务器地址 + const protocol = req?.protocol || 'http'; + const host = req?.get('host') || `localhost:${process.env.PORT || 3000}`; + return `${protocol}://${host}${path}`; +} + module.exports = { generateMemberCode, generateMatchCode, @@ -100,5 +118,6 @@ module.exports = { getPagination, success, error, - pageResult + pageResult, + getFullUrl }; diff --git a/server/uploads/3658676b-da52-4725-a2a5-d03c9bac5dc8.jpeg b/server/uploads/3658676b-da52-4725-a2a5-d03c9bac5dc8.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..ab9153d53f1e2a65886fbb5d3bfdbf731cefeeb7 GIT binary patch literal 4139 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 literal 0 HcmV?d00001 diff --git a/server/uploads/a3f854e6-e663-4aed-82f4-4ada1bcc21ce.jpeg b/server/uploads/a3f854e6-e663-4aed-82f4-4ada1bcc21ce.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..0dc6ded0912cc4b98b2dc53cdf48368779e72df5 GIT binary patch literal 4139 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 literal 0 HcmV?d00001 diff --git a/server/uploads/d78d705e-3e4f-4459-9bd5-3af0af114c0b.jpeg b/server/uploads/d78d705e-3e4f-4459-9bd5-3af0af114c0b.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..f032419ef17c35cfbe933d0573ea71948b67097b GIT binary patch literal 4139 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 literal 0 HcmV?d00001