Refactor and enhance miniprogram UI with new designs, improve match and points functionality, and update server routes for QR code generation
This commit is contained in:
parent
379ecb029a
commit
7ec68e3f7a
517
admin/node_modules/.vite/deps/_metadata.json
generated
vendored
517
admin/node_modules/.vite/deps/_metadata.json
generated
vendored
@ -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"
|
||||
}
|
||||
|
||||
3
admin/node_modules/.vite/deps/chunk-JUCAMQ7P.js
generated
vendored
3
admin/node_modules/.vite/deps/chunk-JUCAMQ7P.js
generated
vendored
@ -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
|
||||
7
admin/node_modules/.vite/deps/chunk-JUCAMQ7P.js.map
generated
vendored
7
admin/node_modules/.vite/deps/chunk-JUCAMQ7P.js.map
generated
vendored
@ -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": []
|
||||
}
|
||||
@ -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
|
||||
4
admin/node_modules/.vite/deps/element-plus.js
generated
vendored
4
admin/node_modules/.vite/deps/element-plus.js
generated
vendored
@ -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 {
|
||||
|
||||
4
admin/node_modules/.vite/deps/element-plus_es.js
generated
vendored
4
admin/node_modules/.vite/deps/element-plus_es.js
generated
vendored
@ -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 {
|
||||
|
||||
5
admin/node_modules/.vite/deps/element-plus_es_components_collapse-item_style_css.js
generated
vendored
5
admin/node_modules/.vite/deps/element-plus_es_components_collapse-item_style_css.js
generated
vendored
@ -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
|
||||
@ -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": []
|
||||
}
|
||||
5
admin/node_modules/.vite/deps/element-plus_es_components_collapse_style_css.js
generated
vendored
5
admin/node_modules/.vite/deps/element-plus_es_components_collapse_style_css.js
generated
vendored
@ -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
|
||||
7
admin/node_modules/.vite/deps/element-plus_es_components_collapse_style_css.js.map
generated
vendored
7
admin/node_modules/.vite/deps/element-plus_es_components_collapse_style_css.js.map
generated
vendored
@ -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": []
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
import "./chunk-JUCAMQ7P.js";
|
||||
import "./chunk-IV6PSERC.js";
|
||||
//# sourceMappingURL=element-plus_es_components_descriptions-item_style_css.js.map
|
||||
@ -1,7 +0,0 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": [],
|
||||
"sourcesContent": [],
|
||||
"mappings": "",
|
||||
"names": []
|
||||
}
|
||||
6
admin/node_modules/.vite/deps/element-plus_es_components_descriptions_style_css.js
generated
vendored
6
admin/node_modules/.vite/deps/element-plus_es_components_descriptions_style_css.js
generated
vendored
@ -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
|
||||
@ -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": []
|
||||
}
|
||||
5
admin/node_modules/.vite/deps/element-plus_es_components_divider_style_css.js
generated
vendored
5
admin/node_modules/.vite/deps/element-plus_es_components_divider_style_css.js
generated
vendored
@ -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
|
||||
7
admin/node_modules/.vite/deps/element-plus_es_components_divider_style_css.js.map
generated
vendored
7
admin/node_modules/.vite/deps/element-plus_es_components_divider_style_css.js.map
generated
vendored
@ -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": []
|
||||
}
|
||||
2
admin/node_modules/.vite/deps/element-plus_es_components_dropdown_style_css.js
generated
vendored
2
admin/node_modules/.vite/deps/element-plus_es_components_dropdown_style_css.js
generated
vendored
@ -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
|
||||
|
||||
8
admin/node_modules/.vite/deps/element-plus_es_components_image_style_css.js
generated
vendored
8
admin/node_modules/.vite/deps/element-plus_es_components_image_style_css.js
generated
vendored
@ -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
|
||||
7
admin/node_modules/.vite/deps/element-plus_es_components_image_style_css.js.map
generated
vendored
7
admin/node_modules/.vite/deps/element-plus_es_components_image_style_css.js.map
generated
vendored
@ -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": []
|
||||
}
|
||||
2
admin/node_modules/.vite/deps/element-plus_es_components_pagination_style_css.js
generated
vendored
2
admin/node_modules/.vite/deps/element-plus_es_components_pagination_style_css.js
generated
vendored
@ -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";
|
||||
|
||||
2
admin/node_modules/.vite/deps/element-plus_es_components_select_style_css.js
generated
vendored
2
admin/node_modules/.vite/deps/element-plus_es_components_select_style_css.js
generated
vendored
@ -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";
|
||||
|
||||
5
admin/node_modules/.vite/deps/element-plus_es_components_switch_style_css.js
generated
vendored
5
admin/node_modules/.vite/deps/element-plus_es_components_switch_style_css.js
generated
vendored
@ -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
|
||||
7
admin/node_modules/.vite/deps/element-plus_es_components_switch_style_css.js.map
generated
vendored
7
admin/node_modules/.vite/deps/element-plus_es_components_switch_style_css.js.map
generated
vendored
@ -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": []
|
||||
}
|
||||
2
admin/node_modules/.vite/deps/element-plus_es_components_table-column_style_css.js
generated
vendored
2
admin/node_modules/.vite/deps/element-plus_es_components_table-column_style_css.js
generated
vendored
@ -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
|
||||
|
||||
2
admin/node_modules/.vite/deps/element-plus_es_components_table_style_css.js
generated
vendored
2
admin/node_modules/.vite/deps/element-plus_es_components_table_style_css.js
generated
vendored
@ -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";
|
||||
|
||||
5
admin/node_modules/.vite/deps/element-plus_es_components_text_style_css.js
generated
vendored
5
admin/node_modules/.vite/deps/element-plus_es_components_text_style_css.js
generated
vendored
@ -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
|
||||
7
admin/node_modules/.vite/deps/element-plus_es_components_text_style_css.js.map
generated
vendored
7
admin/node_modules/.vite/deps/element-plus_es_components_text_style_css.js.map
generated
vendored
@ -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": []
|
||||
}
|
||||
8
admin/node_modules/.vite/deps/element-plus_es_components_upload_style_css.js
generated
vendored
8
admin/node_modules/.vite/deps/element-plus_es_components_upload_style_css.js
generated
vendored
@ -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
|
||||
7
admin/node_modules/.vite/deps/element-plus_es_components_upload_style_css.js.map
generated
vendored
7
admin/node_modules/.vite/deps/element-plus_es_components_upload_style_css.js.map
generated
vendored
@ -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": []
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -1,259 +1,613 @@
|
||||
/* 全局样式 */
|
||||
page {
|
||||
--primary-color: #FF6B35;
|
||||
--primary-light: #FF8C5A;
|
||||
--secondary-color: #2EC4B6;
|
||||
--accent-color: #FFBA08;
|
||||
--dark-bg: #1A1A2E;
|
||||
--light-bg: #F8F9FA;
|
||||
--text-primary: #2C3E50;
|
||||
--text-secondary: #7F8C8D;
|
||||
--border-color: #E8E8E8;
|
||||
/* ==========================================
|
||||
影沙俱乐部 - 全局样式
|
||||
设计理念:浅色高级感 · 橙色点缀 · 流畅动画
|
||||
========================================== */
|
||||
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||||
page {
|
||||
/* 主色调:活力橙系 */
|
||||
--primary: #FF6B35;
|
||||
--primary-dark: #E85A28;
|
||||
--primary-light: #FF8C5A;
|
||||
--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%);
|
||||
|
||||
/* 强调色 */
|
||||
--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);
|
||||
}
|
||||
|
||||
@ -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' })
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -1,80 +1,119 @@
|
||||
<!--天梯排名页面-->
|
||||
<view class="container">
|
||||
<!-- 门店选择 -->
|
||||
<view class="store-selector" bindtap="selectStore">
|
||||
<!--天梯排名页面 - 浅色高级感设计-->
|
||||
<view class="page-container">
|
||||
<!-- 顶部装饰背景 -->
|
||||
<view class="hero-section">
|
||||
<view class="hero-pattern"></view>
|
||||
<view class="hero-pattern-2"></view>
|
||||
|
||||
<!-- 门店信息 -->
|
||||
<view class="store-header">
|
||||
<view class="store-info">
|
||||
<image class="store-icon" src="/images/icon-store.svg" mode="aspectFit"></image>
|
||||
<text class="store-name">{{currentStore.storeName || '选择门店'}}</text>
|
||||
<view class="store-dot"></view>
|
||||
<text class="store-name">{{currentStore.storeName || '请选择门店'}}</text>
|
||||
</view>
|
||||
<view class="change-store-btn" bindtap="selectStore">
|
||||
<text class="change-store-text">切换门店</text>
|
||||
<text class="change-store-arrow">›</text>
|
||||
</view>
|
||||
<image class="arrow-icon" src="/images/icon-arrow.svg" mode="aspectFit"></image>
|
||||
</view>
|
||||
|
||||
<!-- 性别筛选 -->
|
||||
<view class="gender-tabs">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header animate-fadeInUp" style="animation-delay: 0.05s">
|
||||
<text class="page-title">天梯排名</text>
|
||||
<text class="page-subtitle">挑战自我,超越巅峰</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<view class="main-content">
|
||||
<!-- 性别筛选标签 -->
|
||||
<view class="filter-bar animate-fadeInUp" style="animation-delay: 0.1s">
|
||||
<view
|
||||
class="tab {{gender === '' ? 'active' : ''}}"
|
||||
class="filter-item {{gender === '' ? 'active' : ''}}"
|
||||
bindtap="setGender"
|
||||
data-gender=""
|
||||
>全部</view>
|
||||
>
|
||||
全部
|
||||
</view>
|
||||
<view
|
||||
class="tab {{gender === '1' ? 'active' : ''}}"
|
||||
class="filter-item {{gender === '1' ? 'active' : ''}}"
|
||||
bindtap="setGender"
|
||||
data-gender="1"
|
||||
>男子组</view>
|
||||
>
|
||||
♂ 男子
|
||||
</view>
|
||||
<view
|
||||
class="tab {{gender === '2' ? 'active' : ''}}"
|
||||
class="filter-item {{gender === '2' ? 'active' : ''}}"
|
||||
bindtap="setGender"
|
||||
data-gender="2"
|
||||
>女子组</view>
|
||||
>
|
||||
♀ 女子
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 排名列表 -->
|
||||
<view class="ranking-list">
|
||||
<view class="list-header">
|
||||
<text class="col-rank">排名</text>
|
||||
<text class="col-user">选手</text>
|
||||
<text class="col-level">等级</text>
|
||||
<text class="col-power">战力</text>
|
||||
</view>
|
||||
|
||||
<scroll-view
|
||||
scroll-y="true"
|
||||
class="ranking-scroll"
|
||||
bindscrolltolower="loadMore"
|
||||
lower-threshold="100"
|
||||
style="height: calc(100vh - 480rpx);"
|
||||
>
|
||||
<block wx:if="{{list.length > 0}}">
|
||||
<view
|
||||
class="list-item"
|
||||
class="ranking-item stagger-item {{index < 3 ? 'top-rank' : ''}} animate-fadeInUp"
|
||||
wx:for="{{list}}"
|
||||
wx:key="id"
|
||||
bindtap="viewPlayer"
|
||||
data-id="{{item.id}}"
|
||||
>
|
||||
<view class="col-rank">
|
||||
<view class="rank-badge {{item.rank <= 3 ? 'top' + item.rank : 'normal'}}">
|
||||
{{item.rank}}
|
||||
<!-- 排名徽章 -->
|
||||
<view class="rank-badge {{item.rank === 1 ? 'top1' : item.rank === 2 ? 'top2' : item.rank === 3 ? 'top3' : 'normal'}}">
|
||||
<text wx:if="{{item.rank <= 3}}">{{item.rank === 1 ? '👑' : item.rank === 2 ? '🥈' : '🥉'}}</text>
|
||||
<text wx:else>{{item.rank}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 选手头像 -->
|
||||
<image class="player-avatar" src="{{item.avatar || '/images/avatar-default.svg'}}" mode="aspectFill"></image>
|
||||
|
||||
<!-- 选手信息 -->
|
||||
<view class="player-info">
|
||||
<text class="player-name">{{item.realName}}</text>
|
||||
<view class="player-meta">
|
||||
<text class="player-level lv{{item.level}}">Lv{{item.level}}</text>
|
||||
<text class="player-stats">胜率 {{item.winRate}}%</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="col-user">
|
||||
<image class="avatar" src="{{item.avatar || '/images/avatar-default.svg'}}" mode="aspectFill"></image>
|
||||
<view class="user-info">
|
||||
<text class="name">{{item.realName}}</text>
|
||||
<text class="rate">胜率 {{item.winRate}}%</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="col-level">
|
||||
<view class="level-tag lv{{item.level}}">Lv{{item.level}}</view>
|
||||
</view>
|
||||
<view class="col-power">
|
||||
<text class="power-score">{{item.powerScore}}</text>
|
||||
|
||||
<!-- 战力值 -->
|
||||
<view class="player-power">
|
||||
<text class="power-value">{{item.powerScore}}</text>
|
||||
<text class="power-label">战力</text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<view wx:else class="empty-state">
|
||||
<image src="/images/empty-ranking.svg" mode="aspectFit"></image>
|
||||
<text>暂无排名数据</text>
|
||||
<text class="sub-text">每月完成3场比赛即可上榜</text>
|
||||
</view>
|
||||
<!-- 空状态 -->
|
||||
<view wx:elif="{{!loading}}" class="empty-state">
|
||||
<image class="empty-icon" src="/images/empty-ranking.svg" mode="aspectFit"></image>
|
||||
<text class="empty-title">暂无排名数据</text>
|
||||
<text class="empty-desc">每月完成3场比赛即可上榜</text>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view wx:if="{{loading}}" class="loading-state">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 到底提示 -->
|
||||
<view wx:if="{{list.length > 0 && !hasMore && !loading}}" class="load-more">
|
||||
<text>— 已显示全部选手 —</text>
|
||||
</view>
|
||||
|
||||
<!-- 底部安全区域 -->
|
||||
<view class="safe-bottom"></view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "比赛"
|
||||
"navigationBarTitleText": "比赛",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
|
||||
@ -1,96 +1,211 @@
|
||||
<!--挑战赛页面-->
|
||||
<view class="container">
|
||||
<!--挑战赛页面 - 全新设计-->
|
||||
<view class="page-container">
|
||||
<!-- 顶部装饰背景 -->
|
||||
<view class="hero-bg">
|
||||
<view class="hero-pattern"></view>
|
||||
</view>
|
||||
|
||||
<!-- 主要内容 -->
|
||||
<view class="main-content">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<text class="page-title">🏸 发起挑战</text>
|
||||
<text class="page-subtitle">扫描对手会员码,开启对决</text>
|
||||
</view>
|
||||
|
||||
<!-- 当前门店 -->
|
||||
<view class="store-bar" wx:if="{{currentStore}}" bindtap="goToStore">
|
||||
<text class="store-icon">📍</text>
|
||||
<text class="store-name">{{currentStore.storeName}}</text>
|
||||
<text class="store-arrow">›</text>
|
||||
</view>
|
||||
|
||||
<!-- 未登录或非天梯用户提示 -->
|
||||
<view class="notice-card" wx:if="{{!ladderUser}}">
|
||||
<image src="/images/icon-info.svg" mode="aspectFit"></image>
|
||||
<text>仅天梯用户可使用比赛功能,请联系门店工作人员加入天梯系统</text>
|
||||
<view class="notice-card animate-fadeInUp" wx:if="{{!ladderUser}}">
|
||||
<view class="notice-icon">🏸</view>
|
||||
<view class="notice-content">
|
||||
<text class="notice-title">暂未开通天梯</text>
|
||||
<text class="notice-desc">请联系门店工作人员加入天梯系统</text>
|
||||
</view>
|
||||
<view class="notice-action" bindtap="refreshLadderInfo">
|
||||
<text class="refresh-text">刷新</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 比赛功能区 -->
|
||||
<block wx:else>
|
||||
<!-- 我的信息 -->
|
||||
<view class="my-info-card">
|
||||
<view class="info-header">
|
||||
<image class="avatar" src="{{userInfo.avatar || '/images/avatar-default.svg'}}" mode="aspectFill"></image>
|
||||
<view class="info-meta">
|
||||
<text class="name">{{ladderUser.realName}}</text>
|
||||
<view class="level-power">
|
||||
<view class="level-tag lv{{ladderUser.level}}">Lv{{ladderUser.level}}</view>
|
||||
<text class="power">战力 {{ladderUser.powerScore}}</text>
|
||||
<!-- 用户信息卡片 -->
|
||||
<view class="user-card animate-fadeInUp">
|
||||
<view class="user-card-inner">
|
||||
<view class="user-avatar-box">
|
||||
<image class="user-avatar" src="{{userInfo.avatar || '/images/avatar-default.svg'}}" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view class="user-info-box">
|
||||
<view class="user-name-row">
|
||||
<text class="user-name">{{ladderUser.realName}}</text>
|
||||
<view class="user-level lv{{ladderUser.level}}">{{ladderUser.levelName || 'Lv' + ladderUser.level}}</view>
|
||||
</view>
|
||||
<view class="user-stats-row">
|
||||
<view class="mini-stat">
|
||||
<text class="mini-stat-value">{{ladderUser.powerScore}}</text>
|
||||
<text class="mini-stat-label">战力分</text>
|
||||
</view>
|
||||
<view class="mini-stat">
|
||||
<text class="mini-stat-value win">{{ladderUser.winCount || 0}}</text>
|
||||
<text class="mini-stat-label">胜场</text>
|
||||
</view>
|
||||
<view class="mini-stat">
|
||||
<text class="mini-stat-value">{{ladderUser.matchCount || 0}}</text>
|
||||
<text class="mini-stat-label">总场次</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 挑战赛入口 -->
|
||||
<view class="action-card">
|
||||
<view class="card-title">
|
||||
<image src="/images/icon-challenge.svg" mode="aspectFit"></image>
|
||||
<text>挑战赛</text>
|
||||
<!-- 扫码入口 - 并排展示 -->
|
||||
<view class="scan-grid animate-fadeInUp" style="animation-delay: 0.1s">
|
||||
<view class="scan-card challenge" bindtap="startChallenge">
|
||||
<view class="scan-icon-wrapper">
|
||||
<text class="scan-icon">⚔️</text>
|
||||
</view>
|
||||
<view class="card-desc">扫描对手会员码发起挑战,挑战赛权重 x1.5</view>
|
||||
<button class="btn-primary" bindtap="startChallenge">
|
||||
<image src="/images/icon-scan.svg" mode="aspectFit"></image>
|
||||
扫码挑战
|
||||
</button>
|
||||
<text class="scan-title">挑战赛</text>
|
||||
<text class="scan-desc">1v1 对决</text>
|
||||
<view class="scan-badge">权重 ×1.5</view>
|
||||
</view>
|
||||
|
||||
<!-- 排位赛入口 -->
|
||||
<view class="action-card">
|
||||
<view class="card-title">
|
||||
<image src="/images/icon-ranking.svg" mode="aspectFit"></image>
|
||||
<text>排位赛</text>
|
||||
<view class="scan-card ranking" bindtap="joinRankingMatch">
|
||||
<view class="scan-icon-wrapper">
|
||||
<text class="scan-icon">🏆</text>
|
||||
</view>
|
||||
<text class="scan-title">排位赛</text>
|
||||
<text class="scan-desc">多人竞技</text>
|
||||
<view class="scan-badge accent">扫码加入</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 正在进行中的比赛 -->
|
||||
<view class="ongoing-card animate-fadeInUp" style="animation-delay: 0.12s" wx:if="{{ongoingMatches.length > 0}}">
|
||||
<view class="ongoing-header">
|
||||
<view class="ongoing-header-left">
|
||||
<text class="ongoing-icon">🔥</text>
|
||||
<text class="ongoing-title">进行中的比赛</text>
|
||||
</view>
|
||||
<view class="ongoing-count">{{ongoingMatches.length}}</view>
|
||||
</view>
|
||||
<view class="ongoing-list">
|
||||
<view class="ongoing-item {{item.type === 1 ? 'challenge' : 'ranking'}}"
|
||||
wx:for="{{ongoingMatches}}"
|
||||
wx:key="id"
|
||||
bindtap="goToMatchDetail"
|
||||
data-match="{{item}}">
|
||||
<view class="ongoing-item-header">
|
||||
<view class="match-type-tag {{item.type === 1 ? 'challenge' : 'ranking'}}">
|
||||
<text>{{item.typeName}}</text>
|
||||
</view>
|
||||
<view class="match-status-tag {{item.status === 0 ? 'waiting' : 'playing'}}">
|
||||
{{item.statusName}}
|
||||
</view>
|
||||
</view>
|
||||
<view class="ongoing-item-body">
|
||||
<block wx:if="{{item.type === 1}}">
|
||||
<!-- 挑战赛:显示对手信息 -->
|
||||
<view class="opponent-info" wx:if="{{item.opponent}}">
|
||||
<image class="opponent-avatar" src="{{item.opponent.avatar || '/images/avatar-default.svg'}}" mode="aspectFill"></image>
|
||||
<view class="opponent-detail">
|
||||
<text class="opponent-name">VS {{item.opponent.realName}}</text>
|
||||
<view class="opponent-stats">
|
||||
<text class="opponent-level">Lv{{item.opponent.level}}</text>
|
||||
<text class="opponent-power">战力 {{item.opponent.powerScore}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
<block wx:else>
|
||||
<!-- 排位赛:显示比赛信息 -->
|
||||
<view class="ranking-info">
|
||||
<view class="ranking-name">{{item.name || '排位赛'}}</view>
|
||||
<view class="ranking-meta">
|
||||
<text class="ranking-stage">{{item.stageName}}</text>
|
||||
<text class="ranking-players">{{item.playerCount}}人参赛</text>
|
||||
</view>
|
||||
<view class="current-opponent" wx:if="{{item.opponent}}">
|
||||
<text class="current-label">当前对手:</text>
|
||||
<text class="current-name">{{item.opponent.realName}}</text>
|
||||
</view>
|
||||
<view class="my-status {{item.myStatus}}">
|
||||
<text wx:if="{{item.myStatus === 'waiting'}}">⏳ 等待中</text>
|
||||
<text wx:elif="{{item.myStatus === 'playing'}}">🎾 比赛中</text>
|
||||
<text wx:else>✅ 已完成</text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
<view class="ongoing-item-footer">
|
||||
<text class="match-weight" wx:if="{{item.weight > 1}}">权重 ×{{item.weight}}</text>
|
||||
<text class="enter-btn">查看详情 ›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-desc">扫描比赛二维码加入排位赛</view>
|
||||
<button class="btn-secondary" bindtap="joinRankingMatch">
|
||||
<image src="/images/icon-scan.svg" mode="aspectFit"></image>
|
||||
扫码加入
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 待确认比赛 -->
|
||||
<view class="pending-card" wx:if="{{pendingGames.length > 0}}">
|
||||
<view class="card-title">
|
||||
<text>待确认比分</text>
|
||||
<text class="badge">{{pendingGames.length}}</text>
|
||||
<view class="pending-card animate-fadeInUp" style="animation-delay: 0.15s" wx:if="{{pendingGames.length > 0}}">
|
||||
<view class="pending-header">
|
||||
<text class="pending-title">📋 待确认比分</text>
|
||||
<view class="pending-count">{{pendingGames.length}}</view>
|
||||
</view>
|
||||
<view class="pending-list">
|
||||
<view class="pending-item" wx:for="{{pendingGames}}" wx:key="id" bindtap="confirmGame" data-game="{{item}}">
|
||||
<view class="game-info">
|
||||
<text class="opponent">vs {{item.opponentName}}</text>
|
||||
<text class="score">{{item.myScore}} : {{item.opponentScore}}</text>
|
||||
<text class="vs-tag">VS</text>
|
||||
<text class="opponent-name">{{item.opponentName}}</text>
|
||||
</view>
|
||||
<view class="btn-confirm">确认</view>
|
||||
<text class="game-score">{{item.myScore}} : {{item.opponentScore}}</text>
|
||||
<view class="confirm-btn">确认</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 比赛规则说明 -->
|
||||
<view class="rules-card">
|
||||
<view class="card-title">
|
||||
<text>战力值规则</text>
|
||||
<!-- 战力值规则 -->
|
||||
<view class="rules-card animate-fadeInUp" style="animation-delay: 0.2s">
|
||||
<view class="rules-header">
|
||||
<view class="rules-icon">📖</view>
|
||||
<text class="rules-title">战力值规则</text>
|
||||
</view>
|
||||
<view class="rules-content">
|
||||
<view class="rules-grid">
|
||||
<view class="rule-item">
|
||||
<view class="rule-icon win">↑</view>
|
||||
<view class="rule-text">
|
||||
<text class="rule-label">胜方</text>
|
||||
<text class="rule-value positive">+15 基础分</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rule-item">
|
||||
<view class="rule-icon lose">↓</view>
|
||||
<view class="rule-text">
|
||||
<text class="rule-label">败方</text>
|
||||
<text class="rule-value negative">-5 基础分</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rule-item">
|
||||
<view class="rule-icon bonus">★</view>
|
||||
<view class="rule-text">
|
||||
<text class="rule-label">以下克上</text>
|
||||
<text class="rule-value">分差≥100 额外+10%</text>
|
||||
<text class="rule-value">额外 +10%</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rule-item">
|
||||
<view class="rule-icon shield">🛡</view>
|
||||
<view class="rule-text">
|
||||
<text class="rule-label">新手保护</text>
|
||||
<text class="rule-value">Lv1-2前5场输分减半</text>
|
||||
<text class="rule-value">输分减半</text>
|
||||
</view>
|
||||
<view class="rule-item">
|
||||
<text class="rule-label">挑战冷却</text>
|
||||
<text class="rule-value">同一对手30天限1次</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rules-note">
|
||||
💡 同一对手30天内仅限挑战1次
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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(() => {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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}`)
|
||||
|
||||
@ -1,58 +1,119 @@
|
||||
<!--排位赛详情页面-->
|
||||
<view class="container">
|
||||
<view class="match-header">
|
||||
<text class="match-name">{{match.name}}</text>
|
||||
<text class="match-status status-{{match.status}}">{{getStatusText(match.status)}}</text>
|
||||
<!--排位赛详情页面 - 全新设计-->
|
||||
<view class="page-container">
|
||||
<!-- 顶部背景 -->
|
||||
<view class="hero-bg">
|
||||
<view class="hero-pattern"></view>
|
||||
</view>
|
||||
|
||||
<view class="match-info card">
|
||||
<view class="info-row">
|
||||
<text class="label">比赛码</text>
|
||||
<text class="value">{{match.matchCode}}</text>
|
||||
<!-- 主要内容 -->
|
||||
<view class="main-content">
|
||||
<!-- 比赛头部信息 -->
|
||||
<view class="match-header animate-fadeInUp">
|
||||
<view class="match-badge">🏆</view>
|
||||
<view class="match-title">{{match.name || '排位赛'}}</view>
|
||||
<view class="match-status status-{{match.status}}">
|
||||
<text class="status-dot"></text>
|
||||
<text>{{match.statusName || '待开始'}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">权重</text>
|
||||
<text class="value">x{{match.weight}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">阶段</text>
|
||||
<text class="value">{{getStageText(match.stage)}}</text>
|
||||
|
||||
<!-- 比赛信息卡片 -->
|
||||
<view class="info-card animate-fadeInUp" style="animation-delay: 0.05s">
|
||||
<view class="info-grid">
|
||||
<view class="info-item">
|
||||
<text class="info-value">{{match.matchCode}}</text>
|
||||
<text class="info-label">比赛码</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="info-value accent">×{{match.weight}}</text>
|
||||
<text class="info-label">权重</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="info-value">{{match.players.length || 0}}</text>
|
||||
<text class="info-label">参赛人数</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="stage-tag stage-{{match.stage}}">{{match.stageName || '报名中'}}</view>
|
||||
<text class="info-label">当前阶段</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">参赛人数</text>
|
||||
<text class="value">{{match.players.length || 0}}人</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 我的状态 -->
|
||||
<view class="my-status card" wx:if="{{myPlayer}}">
|
||||
<view class="card-title">我的状态</view>
|
||||
<view class="status-info">
|
||||
<text class="status-label">{{myPlayer.status === 'playing' ? '比赛中' : myPlayer.status === 'finished' ? '已完成' : '等待匹配'}}</text>
|
||||
<text class="win-lose">{{myPlayer.winCount}}胜 {{myPlayer.loseCount}}负</text>
|
||||
<view class="my-status-card animate-fadeInUp" style="animation-delay: 0.1s" wx:if="{{myPlayer}}">
|
||||
<view class="card-header">
|
||||
<text class="card-icon">👤</text>
|
||||
<text class="card-title">我的状态</text>
|
||||
</view>
|
||||
<view class="status-content">
|
||||
<view class="status-main">
|
||||
<view class="status-badge {{myPlayer.status}}">
|
||||
<text wx:if="{{myPlayer.status === 'playing'}}">🎾 比赛中</text>
|
||||
<text wx:elif="{{myPlayer.status === 'finished'}}">✅ 已完成</text>
|
||||
<text wx:else>⏳ 等待匹配</text>
|
||||
</view>
|
||||
<view class="win-lose-stats">
|
||||
<view class="stat win">
|
||||
<text class="stat-num">{{myPlayer.winCount || 0}}</text>
|
||||
<text class="stat-text">胜</text>
|
||||
</view>
|
||||
<view class="stat lose">
|
||||
<text class="stat-num">{{myPlayer.loseCount || 0}}</text>
|
||||
<text class="stat-text">负</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 当前对局 -->
|
||||
<view class="current-game" wx:if="{{currentGame}}">
|
||||
<text class="vs-label">当前对手</text>
|
||||
<view class="game-divider">
|
||||
<text class="divider-text">当前对局</text>
|
||||
</view>
|
||||
<view class="opponent-card">
|
||||
<image class="opponent-avatar" src="{{currentGame.opponent.avatar || '/images/avatar-default.svg'}}" mode="aspectFill"></image>
|
||||
<view class="opponent-info">
|
||||
<text class="opponent-name">{{currentGame.opponent.realName}}</text>
|
||||
<text class="opponent-level">Lv{{currentGame.opponent.level}} 战力{{currentGame.opponent.powerScore}}</text>
|
||||
<text class="opponent-name">VS {{currentGame.opponent.realName}}</text>
|
||||
<view class="opponent-meta">
|
||||
<view class="level-tag lv{{currentGame.opponent.level}}">Lv{{currentGame.opponent.level}}</view>
|
||||
<text class="opponent-power">战力 {{currentGame.opponent.powerScore}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 参赛选手 -->
|
||||
<view class="players-section card">
|
||||
<view class="card-title">参赛选手</view>
|
||||
<view class="players-list">
|
||||
<view class="player-item" wx:for="{{match.players}}" wx:key="id">
|
||||
<view class="player-rank">{{index + 1}}</view>
|
||||
<view class="player-info">
|
||||
<view class="players-card animate-fadeInUp" style="animation-delay: 0.15s">
|
||||
<view class="card-header">
|
||||
<text class="card-icon">👥</text>
|
||||
<text class="card-title">参赛选手</text>
|
||||
<text class="player-count">{{match.players.length || 0}}人</text>
|
||||
</view>
|
||||
|
||||
<view class="players-list" wx:if="{{match.players && match.players.length > 0}}">
|
||||
<view class="player-item {{item.ladderUserId === myPlayer.ladderUserId ? 'is-me' : ''}}"
|
||||
wx:for="{{match.players}}"
|
||||
wx:key="id">
|
||||
<view class="player-rank rank-{{index + 1}}">{{index + 1}}</view>
|
||||
<view class="player-main">
|
||||
<text class="player-name">{{item.realName}}</text>
|
||||
<view class="player-tags">
|
||||
<view class="level-tag lv{{item.level}}">Lv{{item.level}}</view>
|
||||
<text class="player-me" wx:if="{{item.ladderUserId === myPlayer.ladderUserId}}">我</text>
|
||||
</view>
|
||||
<view class="player-stats">
|
||||
<text>{{item.winCount}}胜{{item.loseCount}}负</text>
|
||||
</view>
|
||||
<view class="player-record">
|
||||
<text class="record-win">{{item.winCount || 0}}胜</text>
|
||||
<text class="record-lose">{{item.loseCount || 0}}负</text>
|
||||
</view>
|
||||
<view class="player-status-dot {{item.status}}"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="empty-players" wx:else>
|
||||
<text class="empty-icon">🏸</text>
|
||||
<text class="empty-text">暂无参赛选手</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -1,84 +1,116 @@
|
||||
<!--积分商城页面-->
|
||||
<view class="container">
|
||||
<!-- 积分信息 -->
|
||||
<view class="points-header">
|
||||
<!--积分商城页面 - 浅色高级感设计-->
|
||||
<view class="page-container">
|
||||
<!-- 顶部装饰背景 -->
|
||||
<view class="hero-section">
|
||||
<view class="hero-pattern"></view>
|
||||
|
||||
<!-- 积分信息卡片 -->
|
||||
<view class="points-card">
|
||||
<view class="points-card-accent"></view>
|
||||
<view class="points-info">
|
||||
<text class="label">我的积分</text>
|
||||
<text class="value">{{userInfo.totalPoints || 0}}</text>
|
||||
<text class="points-label">我的积分</text>
|
||||
<view class="points-value">
|
||||
<text class="points-number">{{userInfo.totalPoints || 0}}</text>
|
||||
<text class="points-unit">分</text>
|
||||
</view>
|
||||
<view class="points-actions">
|
||||
<view class="action-btn" bindtap="goToRecords">
|
||||
<image src="/images/icon-records.svg" mode="aspectFit"></image>
|
||||
<text>积分记录</text>
|
||||
</view>
|
||||
<view class="action-btn" bindtap="goToOrders">
|
||||
<image src="/images/icon-order.svg" mode="aspectFit"></image>
|
||||
<text>我的订单</text>
|
||||
<view class="points-action" bindtap="goToRecords">
|
||||
<text class="action-text">积分记录</text>
|
||||
<text class="action-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 商品列表 -->
|
||||
<view class="products-section">
|
||||
<view class="section-title">积分好物</view>
|
||||
<view class="product-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">积分好物</text>
|
||||
<text class="section-count" wx:if="{{products.length > 0}}">共 {{products.length}} 件</text>
|
||||
</view>
|
||||
|
||||
<view class="products-grid" wx:if="{{products.length > 0}}">
|
||||
<view class="product-grid" wx:if="{{products.length > 0}}">
|
||||
<view
|
||||
class="product-item"
|
||||
class="product-card stagger-item"
|
||||
wx:for="{{products}}"
|
||||
wx:key="id"
|
||||
bindtap="viewProduct"
|
||||
data-product="{{item}}"
|
||||
>
|
||||
<view class="product-image-wrapper">
|
||||
<image class="product-image" src="{{item.image || '/images/product-default.svg'}}" mode="aspectFill"></image>
|
||||
<view class="product-tag" wx:if="{{item.isHot}}">热门</view>
|
||||
<view class="product-stock-low" wx:if="{{item.stock > 0 && item.stock <= 5}}">仅剩{{item.stock}}件</view>
|
||||
</view>
|
||||
<view class="product-info">
|
||||
<text class="product-name">{{item.name}}</text>
|
||||
<view class="product-meta">
|
||||
<view class="points-need">
|
||||
<text class="num">{{item.pointsRequired}}</text>
|
||||
<text class="unit">积分</text>
|
||||
<view class="product-footer">
|
||||
<view class="product-price">
|
||||
<text class="price-value">{{item.pointsRequired}}</text>
|
||||
<text class="price-unit">积分</text>
|
||||
</view>
|
||||
<view class="exchange-btn {{userInfo.totalPoints < item.pointsRequired || item.stock <= 0 ? 'disabled' : ''}}">
|
||||
{{item.stock <= 0 ? '售罄' : '兑换'}}
|
||||
</view>
|
||||
<text class="stock">库存 {{item.stock}}</text>
|
||||
</view>
|
||||
<text class="store-name">{{item.storeName}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" wx:else>
|
||||
<image src="/images/empty-products.svg" mode="aspectFit"></image>
|
||||
<text>暂无可兑换商品</text>
|
||||
<image class="empty-icon" src="/images/empty-products.svg" mode="aspectFit"></image>
|
||||
<text class="empty-title">暂无可兑换商品</text>
|
||||
<text class="empty-desc">敬请期待更多精彩好物</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<!-- 加载状态 -->
|
||||
<view wx:if="{{loading}}" class="loading-state">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 底部安全区域 -->
|
||||
<view class="safe-bottom"></view>
|
||||
</view>
|
||||
|
||||
<!-- 商品详情弹窗 -->
|
||||
<view class="product-modal" wx:if="{{showProductModal}}" bindtap="closeProductModal">
|
||||
<view class="modal-content" catchtap="">
|
||||
<image class="modal-image" src="{{currentProduct.image || '/images/product-default.svg'}}" mode="aspectFill"></image>
|
||||
<view class="modal-info">
|
||||
<text class="modal-name">{{currentProduct.name}}</text>
|
||||
<text class="modal-desc">{{currentProduct.description || '暂无描述'}}</text>
|
||||
<view class="modal-meta">
|
||||
<view class="points-need">
|
||||
<text class="num">{{currentProduct.pointsRequired}}</text>
|
||||
<text class="unit">积分</text>
|
||||
<view class="product-modal-overlay {{showProductModal ? 'show' : ''}}" bindtap="closeProductModal">
|
||||
<view class="product-modal {{showProductModal ? 'show' : ''}}" catchtap="">
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">商品详情</text>
|
||||
<view class="modal-close" bindtap="closeProductModal">×</view>
|
||||
</view>
|
||||
<text class="original-price" wx:if="{{currentProduct.originalPrice}}">原价 ¥{{currentProduct.originalPrice}}</text>
|
||||
|
||||
<view class="modal-content">
|
||||
<image class="detail-image" src="{{currentProduct.image || '/images/product-default.svg'}}" mode="aspectFill"></image>
|
||||
|
||||
<view class="detail-info">
|
||||
<text class="detail-name">{{currentProduct.name}}</text>
|
||||
<text class="detail-desc">{{currentProduct.description || '暂无描述'}}</text>
|
||||
</view>
|
||||
<view class="store-info">
|
||||
<text class="store-label">兑换门店:</text>
|
||||
|
||||
<view class="detail-price-row">
|
||||
<view class="detail-price">
|
||||
<text class="detail-price-value">{{currentProduct.pointsRequired}}</text>
|
||||
<text class="detail-price-unit">积分</text>
|
||||
</view>
|
||||
<text class="detail-stock">库存: {{currentProduct.stock}}</text>
|
||||
</view>
|
||||
|
||||
<view class="store-info" wx:if="{{currentProduct.storeName}}">
|
||||
<text class="store-label">兑换门店</text>
|
||||
<text class="store-name">{{currentProduct.storeName}}</text>
|
||||
</view>
|
||||
<text class="store-address">{{currentProduct.storeAddress}}</text>
|
||||
</view>
|
||||
<button class="btn-exchange" bindtap="exchangeProduct" disabled="{{userInfo.totalPoints < currentProduct.pointsRequired || currentProduct.stock <= 0}}">
|
||||
|
||||
<view class="modal-footer">
|
||||
<button
|
||||
class="exchange-btn-large {{userInfo.totalPoints < currentProduct.pointsRequired || currentProduct.stock <= 0 ? 'disabled' : ''}}"
|
||||
bindtap="exchangeProduct"
|
||||
disabled="{{userInfo.totalPoints < currentProduct.pointsRequired || currentProduct.stock <= 0}}"
|
||||
>
|
||||
{{currentProduct.stock <= 0 ? '已售罄' : userInfo.totalPoints < currentProduct.pointsRequired ? '积分不足' : '立即兑换'}}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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' })
|
||||
}
|
||||
})
|
||||
|
||||
@ -1,28 +1,39 @@
|
||||
<!--兑换订单页面-->
|
||||
<view class="container">
|
||||
<!-- 状态筛选 -->
|
||||
<!--兑换订单页面 - 浅色高级感设计-->
|
||||
<view class="page-container">
|
||||
<!-- 顶部装饰背景 -->
|
||||
<view class="hero-bg">
|
||||
<view class="hero-pattern"></view>
|
||||
</view>
|
||||
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<text class="page-title">兑换订单</text>
|
||||
</view>
|
||||
|
||||
<!-- 状态筛选标签 -->
|
||||
<view class="status-tabs">
|
||||
<view
|
||||
class="tab {{status === '' ? 'active' : ''}}"
|
||||
class="status-tab {{status === '' ? 'active' : ''}}"
|
||||
bindtap="setStatus"
|
||||
data-status=""
|
||||
>全部</view>
|
||||
<view
|
||||
class="tab {{status === '0' ? 'active' : ''}}"
|
||||
class="status-tab {{status === '0' ? 'active' : ''}}"
|
||||
bindtap="setStatus"
|
||||
data-status="0"
|
||||
>待核销</view>
|
||||
<view
|
||||
class="tab {{status === '1' ? 'active' : ''}}"
|
||||
class="status-tab {{status === '1' ? 'active' : ''}}"
|
||||
bindtap="setStatus"
|
||||
data-status="1"
|
||||
>已完成</view>
|
||||
</view>
|
||||
|
||||
<!-- 订单列表 -->
|
||||
<view class="order-section">
|
||||
<view class="order-list" wx:if="{{orders.length > 0}}">
|
||||
<view
|
||||
class="order-item"
|
||||
class="order-card stagger-item"
|
||||
wx:for="{{orders}}"
|
||||
wx:key="id"
|
||||
bindtap="viewOrder"
|
||||
@ -30,58 +41,118 @@
|
||||
>
|
||||
<view class="order-header">
|
||||
<text class="order-no">订单号: {{item.orderNo}}</text>
|
||||
<text class="order-status status-{{item.status}}">{{getStatusText(item.status)}}</text>
|
||||
<text class="order-status {{item.status === 0 ? 'pending' : item.status === 1 ? 'completed' : 'cancelled'}}">
|
||||
{{item.status === 0 ? '待核销' : item.status === 1 ? '已完成' : '已取消'}}
|
||||
</text>
|
||||
</view>
|
||||
<view class="order-content">
|
||||
<image class="product-image" src="{{item.productImage || '/images/product-default.svg'}}" mode="aspectFill"></image>
|
||||
<view class="product-info">
|
||||
<view class="order-info">
|
||||
<text class="product-name">{{item.productName}}</text>
|
||||
<text class="store-name">{{item.storeName}}</text>
|
||||
<text class="points-used">-{{item.pointsUsed}} 积分</text>
|
||||
<view class="order-price-row">
|
||||
<view class="order-price">
|
||||
<text class="price-value">{{item.pointsUsed}}</text>
|
||||
<text class="price-unit">积分</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="order-footer">
|
||||
<text class="order-time">{{item.createdAt}}</text>
|
||||
<view class="btn-qrcode" wx:if="{{item.status === 0}}">查看兑换码</view>
|
||||
<view class="order-actions">
|
||||
<view class="action-btn primary" wx:if="{{item.status === 0}}">查看兑换码</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" wx:else>
|
||||
<image src="/images/empty-order.svg" mode="aspectFit"></image>
|
||||
<text>暂无订单</text>
|
||||
<image class="empty-icon" src="/images/empty-order.svg" mode="aspectFit"></image>
|
||||
<text class="empty-title">暂无订单</text>
|
||||
<text class="empty-desc">快去积分商城兑换心仪好物吧</text>
|
||||
<view class="empty-btn" bindtap="goToMall">去兑换</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<!-- 加载状态 -->
|
||||
<view wx:if="{{loading}}" class="loading-state">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 底部安全区域 -->
|
||||
<view class="safe-bottom"></view>
|
||||
</view>
|
||||
|
||||
<!-- 订单详情弹窗 -->
|
||||
<view class="order-modal" wx:if="{{showOrderModal}}" bindtap="closeOrderModal">
|
||||
<view class="modal-content" catchtap="">
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">兑换详情</text>
|
||||
<text class="order-status status-{{currentOrder.status}}">{{getStatusText(currentOrder.status)}}</text>
|
||||
<view class="order-detail-overlay {{showOrderModal ? 'show' : ''}}" bindtap="closeOrderModal">
|
||||
<view class="order-detail-modal {{showOrderModal ? 'show' : ''}}" catchtap="">
|
||||
<view class="detail-header">
|
||||
<text class="detail-title">兑换详情</text>
|
||||
<view class="detail-close" bindtap="closeOrderModal">×</view>
|
||||
</view>
|
||||
|
||||
<view class="modal-body">
|
||||
<image class="product-image" src="{{currentOrder.productImage || '/images/product-default.svg'}}" mode="aspectFill"></image>
|
||||
<text class="product-name">{{currentOrder.productName}}</text>
|
||||
<text class="points-used">使用积分: {{currentOrder.pointsUsed}}</text>
|
||||
<view class="detail-content">
|
||||
<!-- 商品信息 -->
|
||||
<view class="detail-product">
|
||||
<image class="detail-product-image" src="{{currentOrder.productImage || '/images/product-default.svg'}}" mode="aspectFill"></image>
|
||||
<view class="detail-product-info">
|
||||
<text class="detail-product-name">{{currentOrder.productName}}</text>
|
||||
<view class="detail-product-price">
|
||||
<text class="detail-price-value">{{currentOrder.pointsUsed}}</text>
|
||||
<text class="detail-price-unit">积分</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 兑换码 -->
|
||||
<view class="qrcode-section" wx:if="{{currentOrder.status === 0}}">
|
||||
<canvas canvas-id="orderQrcode" class="qrcode-canvas"></canvas>
|
||||
<text class="qrcode-text">{{currentOrder.exchangeCode}}</text>
|
||||
<view class="qrcode-wrapper">
|
||||
<image
|
||||
class="qrcode-image"
|
||||
src="{{currentOrder.qrcodeImage}}"
|
||||
mode="aspectFit"
|
||||
wx:if="{{currentOrder.qrcodeImage}}"
|
||||
></image>
|
||||
<view class="qrcode-loading" wx:else>
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="qrcode-code">{{currentOrder.exchangeCode}}</text>
|
||||
<text class="qrcode-tip">请出示此码给工作人员核销</text>
|
||||
</view>
|
||||
|
||||
<view class="store-section">
|
||||
<text class="section-title">领取门店</text>
|
||||
<text class="store-name">{{currentOrder.storeName}}</text>
|
||||
<text class="store-address">{{currentOrder.storeAddress}}</text>
|
||||
<text class="store-contact" wx:if="{{currentOrder.storeContact}}">电话: {{currentOrder.storeContact}}</text>
|
||||
<!-- 订单信息 -->
|
||||
<view class="detail-section">
|
||||
<text class="detail-section-title">订单信息</text>
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">订单编号</text>
|
||||
<text class="detail-value">{{currentOrder.orderNo}}</text>
|
||||
</view>
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">下单时间</text>
|
||||
<text class="detail-value">{{currentOrder.createdAt}}</text>
|
||||
</view>
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">订单状态</text>
|
||||
<text class="detail-value {{currentOrder.status === 0 ? 'status-pending' : 'status-completed'}}">
|
||||
{{currentOrder.status === 0 ? '待核销' : '已完成'}}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 门店信息 -->
|
||||
<view class="detail-section">
|
||||
<text class="detail-section-title">领取门店</text>
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">门店名称</text>
|
||||
<text class="detail-value">{{currentOrder.storeName}}</text>
|
||||
</view>
|
||||
<view class="detail-row" wx:if="{{currentOrder.storeAddress}}">
|
||||
<text class="detail-label">门店地址</text>
|
||||
<text class="detail-value">{{currentOrder.storeAddress}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
// 尝试获取位置
|
||||
// 如果已登录,尝试获取附近门店
|
||||
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 || [] })
|
||||
this.setData({
|
||||
stores: res.data || [],
|
||||
loading: false
|
||||
})
|
||||
// 更新当前门店的完整信息(地址等)
|
||||
this.updateCurrentStoreInfo(res.data || [])
|
||||
} catch (e) {
|
||||
// 如果失败,fallback 到门店列表
|
||||
this.fetchStoreList()
|
||||
}
|
||||
},
|
||||
fail: async () => {
|
||||
// 无法获取位置,获取全部门店
|
||||
const res = await app.request('/api/store/list')
|
||||
this.setData({ stores: res.data.list || [] })
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "选择门店"
|
||||
"navigationBarTitleText": "选择门店",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
|
||||
@ -1,29 +1,101 @@
|
||||
<!--门店选择页面-->
|
||||
<view class="container">
|
||||
<!--门店选择页面 - 浅色高级感设计-->
|
||||
<wxs module="util">
|
||||
function formatDistance(meters) {
|
||||
if (!meters) return '';
|
||||
if (meters < 1000) {
|
||||
return Math.round(meters) + 'm';
|
||||
}
|
||||
return (meters / 1000).toFixed(1) + 'km';
|
||||
}
|
||||
module.exports = { formatDistance: formatDistance };
|
||||
</wxs>
|
||||
|
||||
<view class="page-container">
|
||||
<!-- 顶部装饰背景 -->
|
||||
<view class="hero-bg">
|
||||
<view class="hero-pattern"></view>
|
||||
</view>
|
||||
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<text class="page-title">选择门店</text>
|
||||
<text class="page-subtitle">加入您附近的门店,开始运动</text>
|
||||
</view>
|
||||
|
||||
<!-- 当前门店卡片 -->
|
||||
<view class="current-store-card" wx:if="{{currentStoreId}}">
|
||||
<view class="current-store-accent"></view>
|
||||
<view class="current-store-header">
|
||||
<view class="current-store-badge">
|
||||
<view class="badge-dot"></view>
|
||||
<text class="badge-text">当前门店</text>
|
||||
</view>
|
||||
<text class="current-store-change">可切换其他门店</text>
|
||||
</view>
|
||||
<view class="current-store-info">
|
||||
<view class="store-icon-wrapper">
|
||||
<image src="/images/icon-store.svg" mode="aspectFit"></image>
|
||||
</view>
|
||||
<view class="store-details">
|
||||
<text class="store-name">{{currentStoreName || '暂无门店'}}</text>
|
||||
<text class="store-address">{{currentStoreAddress || '请选择门店'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 门店列表区域 -->
|
||||
<view class="store-section">
|
||||
<view class="section-header">
|
||||
<view class="section-title">
|
||||
<text class="section-icon">📍</text>
|
||||
<text class="section-text">附近门店</text>
|
||||
</view>
|
||||
<text class="section-count">共 {{stores.length}} 家</text>
|
||||
</view>
|
||||
|
||||
<view class="store-list">
|
||||
<view
|
||||
class="store-item {{currentStoreId === item.id ? 'active' : ''}}"
|
||||
class="store-item stagger-item {{currentStoreId === item.id ? 'selected' : ''}}"
|
||||
wx:for="{{stores}}"
|
||||
wx:key="id"
|
||||
bindtap="selectStore"
|
||||
data-store="{{item}}"
|
||||
>
|
||||
<view class="store-info">
|
||||
<text class="store-name">{{item.name}}</text>
|
||||
<text class="store-address">{{item.address}}</text>
|
||||
<view class="store-meta">
|
||||
<text class="sport-type">{{item.sportType === 1 ? '羽毛球' : '网球'}}</text>
|
||||
<text class="distance" wx:if="{{item.distance}}">{{formatDistance(item.distance)}}</text>
|
||||
<view class="store-item-icon {{currentStoreId === item.id ? 'selected' : ''}}">
|
||||
<image src="/images/icon-store.svg" mode="aspectFit"></image>
|
||||
</view>
|
||||
|
||||
<view class="store-item-info">
|
||||
<text class="store-item-name">{{item.name}}</text>
|
||||
<text class="store-item-address">{{item.address}}</text>
|
||||
<view class="store-item-meta">
|
||||
<text class="store-distance" wx:if="{{item.distance}}">{{util.formatDistance(item.distance)}}</text>
|
||||
<text class="store-users">{{item.sportType === 1 ? '🏸 羽毛球' : '🎾 网球'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="store-check" wx:if="{{currentStoreId === item.id}}">
|
||||
<image src="/images/icon-check.svg" mode="aspectFit"></image>
|
||||
|
||||
<view class="store-item-action">
|
||||
<view class="select-btn {{currentStoreId === item.id ? 'selected' : ''}}">
|
||||
<text class="select-icon">✓</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="empty-state" wx:if="{{stores.length === 0}}">
|
||||
<image src="/images/empty-store.svg" mode="aspectFit"></image>
|
||||
<text>暂无门店</text>
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" wx:if="{{stores.length === 0 && !loading}}">
|
||||
<image class="empty-icon" src="/images/empty-store.svg" mode="aspectFit"></image>
|
||||
<text class="empty-title">暂无门店</text>
|
||||
<text class="empty-desc">附近暂无可用门店</text>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-state" wx:if="{{loading}}">
|
||||
<view class="loading-spinner"></view>
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部安全区域 -->
|
||||
<view class="safe-bottom"></view>
|
||||
</view>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
// 检查门店是否切换
|
||||
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()
|
||||
|
||||
// 检查是否需要完善资料(没有头像或昵称为默认值)
|
||||
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() {
|
||||
try {
|
||||
const res = await wx.getUserProfile({
|
||||
desc: '用于完善会员资料'
|
||||
})
|
||||
// 点击头像,打开编辑资料弹框
|
||||
onTapAvatar() {
|
||||
if (!this.data.userInfo?.phone) return
|
||||
|
||||
this.setData({
|
||||
tempUserProfile: res.userInfo,
|
||||
needProfile: false
|
||||
showProfileModal: true,
|
||||
isEditMode: true,
|
||||
profileForm: {
|
||||
avatar: this.data.userInfo.avatar || '/images/avatar-default.svg',
|
||||
nickname: this.data.userInfo.nickname || ''
|
||||
}
|
||||
})
|
||||
wx.showToast({ title: '已获取头像昵称', icon: 'success' })
|
||||
},
|
||||
|
||||
// 选择头像(新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 {
|
||||
// 如果选择了新头像,先上传
|
||||
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({
|
||||
userInfo: userInfo,
|
||||
showProfileModal: false,
|
||||
profileForm: { avatar: '', nickname: '' }
|
||||
})
|
||||
|
||||
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 })
|
||||
|
||||
// 生成二维码
|
||||
setTimeout(() => {
|
||||
new QRCode('qrcode', {
|
||||
text: this.data.userInfo.memberCode,
|
||||
width: 200,
|
||||
height: 200,
|
||||
colorDark: '#000000',
|
||||
colorLight: '#ffffff'
|
||||
this.setData({
|
||||
showQrcode: true,
|
||||
qrcodeLoading: true
|
||||
})
|
||||
}, 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() {
|
||||
// 空函数,仅用于阻止事件冒泡
|
||||
}
|
||||
})
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "我的"
|
||||
"navigationBarTitleText": "我的",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
|
||||
@ -1,108 +1,268 @@
|
||||
<!--个人中心页面-->
|
||||
<view class="container">
|
||||
<!-- 用户信息卡片 -->
|
||||
<view class="user-card">
|
||||
<!--个人中心页面 - 运动感设计-->
|
||||
<view class="page-container">
|
||||
<!-- 顶部装饰背景 -->
|
||||
<view class="hero-bg">
|
||||
<view class="hero-pattern"></view>
|
||||
</view>
|
||||
|
||||
<!-- 用户信息区域 -->
|
||||
<view class="user-section">
|
||||
<!-- 已登录状态 -->
|
||||
<view class="user-header" wx:if="{{userInfo && userInfo.phone}}">
|
||||
<image class="avatar-large" src="{{userInfo.avatar || '/images/avatar-default.svg'}}" mode="aspectFill"></image>
|
||||
<view class="user-meta">
|
||||
<block wx:if="{{userInfo && userInfo.phone}}">
|
||||
<view class="profile-card animate-fadeInUp">
|
||||
<view class="avatar-wrapper" bindtap="onTapAvatar">
|
||||
<image class="avatar-xl" src="{{userInfo.avatar || '/images/avatar-default.svg'}}" mode="aspectFill"></image>
|
||||
<view class="avatar-ring"></view>
|
||||
<view class="avatar-edit-badge">
|
||||
<text class="edit-icon">✏️</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="user-info">
|
||||
<text class="nickname">{{userInfo.nickname || '新用户'}}</text>
|
||||
<view class="member-code" bindtap="showMemberCode">
|
||||
<text>会员码: {{userInfo.memberCode}}</text>
|
||||
<image src="/images/icon-qrcode.svg" mode="aspectFit"></image>
|
||||
<view class="user-stats">
|
||||
<view class="stat-item">
|
||||
<text class="stat-value">{{userInfo.totalPoints || 0}}</text>
|
||||
<text class="stat-label">积分</text>
|
||||
</view>
|
||||
<view class="stat-divider"></view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-value highlight">{{ladderUser.powerScore || 0}}</text>
|
||||
<text class="stat-label">战力</text>
|
||||
</view>
|
||||
<view class="stat-divider" wx:if="{{ladderUser}}"></view>
|
||||
<view class="stat-item" wx:if="{{ladderUser}}">
|
||||
<text class="stat-value">{{ladderUser.matchCount || 0}}</text>
|
||||
<text class="stat-label">场次</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 未登录状态 -->
|
||||
<view class="login-panel" wx:else>
|
||||
<image class="avatar-large" src="/images/avatar-default.svg" mode="aspectFill"></image>
|
||||
<view class="login-tips">
|
||||
<text class="title">欢迎来到羽动俱乐部</text>
|
||||
<text class="desc">授权手机号,开启您的运动之旅</text>
|
||||
<!-- 会员码卡片 -->
|
||||
<view class="member-card" bindtap="showMemberCode">
|
||||
<view class="member-card-bg"></view>
|
||||
<view class="member-card-content">
|
||||
<view class="member-icon">
|
||||
<image src="/images/icon-qrcode.svg" mode="aspectFit"></image>
|
||||
</view>
|
||||
<view class="member-info">
|
||||
<text class="member-title">我的会员码</text>
|
||||
<text class="member-code">{{userInfo.memberCode}}</text>
|
||||
</view>
|
||||
<view class="member-action">
|
||||
<text class="action-text">查看</text>
|
||||
<text class="action-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="scan-hint">出示给对方扫码挑战</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 未登录状态 -->
|
||||
<block wx:else>
|
||||
<view class="login-card animate-fadeInUp">
|
||||
<view class="login-avatar-wrapper">
|
||||
<image class="login-avatar" src="/images/avatar-default.svg" mode="aspectFill"></image>
|
||||
<view class="login-avatar-pulse"></view>
|
||||
</view>
|
||||
|
||||
<view class="login-content">
|
||||
<text class="login-title">开启你的运动之旅</text>
|
||||
<text class="login-subtitle">授权手机号,加入影沙俱乐部</text>
|
||||
</view>
|
||||
|
||||
<button
|
||||
class="login-btn phone-btn"
|
||||
class="login-btn-primary"
|
||||
open-type="getPhoneNumber"
|
||||
bindgetphonenumber="onGetPhoneNumber"
|
||||
>
|
||||
手机号快捷登录
|
||||
</button>
|
||||
<button
|
||||
class="login-btn profile-btn"
|
||||
bindtap="onChooseAvatar"
|
||||
wx:if="{{needProfile}}"
|
||||
>
|
||||
完善头像昵称
|
||||
<text class="btn-icon">📱</text>
|
||||
<text class="btn-text">手机号快捷登录</text>
|
||||
</button>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<!-- 积分展示 -->
|
||||
<view class="points-display" wx:if="{{userInfo}}">
|
||||
<view class="points-value">{{userInfo.totalPoints || 0}}</view>
|
||||
<view class="points-label">我的积分</view>
|
||||
<!-- 天梯信息卡片 -->
|
||||
<view class="ladder-section animate-fadeInUp" style="animation-delay: 0.2s" wx:if="{{ladderUser}}">
|
||||
<view class="section-header">
|
||||
<text class="section-title">天梯战绩</text>
|
||||
<text class="section-badge">{{currentStore.storeName}}</text>
|
||||
</view>
|
||||
|
||||
<view class="ladder-stats">
|
||||
<view class="ladder-stat-item">
|
||||
<view class="stat-icon lv{{ladderUser.level}}">
|
||||
<text>{{ladderUser.levelName || 'Lv' + ladderUser.level}}</text>
|
||||
</view>
|
||||
<text class="stat-name">{{ladderUser.realName}}</text>
|
||||
</view>
|
||||
|
||||
<view class="ladder-progress">
|
||||
<view class="progress-info">
|
||||
<text class="progress-label">战力值</text>
|
||||
<text class="progress-value">{{ladderUser.powerScore}}</text>
|
||||
</view>
|
||||
<view class="progress-bar">
|
||||
<view class="progress-fill" style="width: {{ladderUser.powerScore / 30}}%;"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 天梯信息 -->
|
||||
<view class="ladder-card" wx:if="{{ladderUser}}">
|
||||
<view class="card-title">天梯信息 - {{currentStore.storeName}}</view>
|
||||
<view class="ladder-info">
|
||||
<view class="info-item">
|
||||
<text class="value">{{ladderUser.realName}}</text>
|
||||
<text class="label">姓名</text>
|
||||
<view class="ladder-record">
|
||||
<view class="record-item">
|
||||
<text class="record-value win">{{ladderUser.winCount || 0}}</text>
|
||||
<text class="record-label">胜场</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="level-tag lv{{ladderUser.level}}">Lv{{ladderUser.level}}</view>
|
||||
<text class="label">等级</text>
|
||||
<view class="record-item">
|
||||
<text class="record-value">{{ladderUser.matchCount - ladderUser.winCount || 0}}</text>
|
||||
<text class="record-label">负场</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="value highlight">{{ladderUser.powerScore}}</text>
|
||||
<text class="label">战力值</text>
|
||||
<view class="record-item">
|
||||
<text class="record-value rate">{{ladderUser.matchCount > 0 ? Math.round(ladderUser.winCount / ladderUser.matchCount * 100) : 0}}%</text>
|
||||
<text class="record-label">胜率</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="value">{{ladderUser.matchCount}}</text>
|
||||
<text class="label">比赛场次</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="notice-card" wx:else>
|
||||
<image src="/images/icon-info.svg" mode="aspectFit"></image>
|
||||
<text>您还不是天梯用户,请联系门店工作人员加入天梯系统</text>
|
||||
<!-- 未加入天梯提示 -->
|
||||
<view class="notice-card animate-fadeInUp" style="animation-delay: 0.2s" wx:elif="{{userInfo && userInfo.phone}}">
|
||||
<view class="notice-icon">🏸</view>
|
||||
<view class="notice-content">
|
||||
<text class="notice-title">尚未加入天梯系统</text>
|
||||
<text class="notice-desc">请联系门店工作人员,开启你的天梯之旅</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 功能菜单 -->
|
||||
<view class="menu-list">
|
||||
<view class="menu-section animate-fadeInUp" style="animation-delay: 0.3s">
|
||||
<view class="menu-grid">
|
||||
<view class="menu-item" bindtap="goTo" data-url="/pages/match/history/index">
|
||||
<view class="menu-icon history">
|
||||
<image src="/images/icon-history.svg" mode="aspectFit"></image>
|
||||
<text>比赛记录</text>
|
||||
<image class="arrow" src="/images/icon-arrow.svg" mode="aspectFit"></image>
|
||||
</view>
|
||||
<text class="menu-text">比赛记录</text>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" bindtap="goTo" data-url="/pages/points/records/index">
|
||||
<view class="menu-icon points">
|
||||
<image src="/images/icon-points.svg" mode="aspectFit"></image>
|
||||
<text>积分记录</text>
|
||||
<image class="arrow" src="/images/icon-arrow.svg" mode="aspectFit"></image>
|
||||
</view>
|
||||
<text class="menu-text">积分记录</text>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" bindtap="goTo" data-url="/pages/points/order/index">
|
||||
<view class="menu-icon order">
|
||||
<image src="/images/icon-order.svg" mode="aspectFit"></image>
|
||||
<text>兑换订单</text>
|
||||
<image class="arrow" src="/images/icon-arrow.svg" mode="aspectFit"></image>
|
||||
</view>
|
||||
<text class="menu-text">兑换订单</text>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" bindtap="goTo" data-url="/pages/store/index">
|
||||
<view class="menu-icon store">
|
||||
<image src="/images/icon-store.svg" mode="aspectFit"></image>
|
||||
<text>切换门店</text>
|
||||
<image class="arrow" src="/images/icon-arrow.svg" mode="aspectFit"></image>
|
||||
</view>
|
||||
<text class="menu-text">切换门店</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 会员码弹窗 -->
|
||||
<view class="qrcode-modal" wx:if="{{showQrcode}}" bindtap="hideQrcode">
|
||||
<view class="modal-content" catchtap="">
|
||||
<view class="modal-title">我的会员码</view>
|
||||
<canvas canvas-id="qrcode" class="qrcode-canvas"></canvas>
|
||||
<text class="qrcode-text">{{userInfo.memberCode}}</text>
|
||||
<text class="qrcode-tip">请将会员码出示给对方扫描</text>
|
||||
<view class="qrcode-overlay {{showQrcode ? 'show' : ''}}" bindtap="hideQrcode">
|
||||
<view class="qrcode-modal {{showQrcode ? 'show' : ''}}" catchtap="preventBubble">
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">我的会员码</text>
|
||||
<view class="modal-close" bindtap="hideQrcode">×</view>
|
||||
</view>
|
||||
|
||||
<view class="qrcode-wrapper">
|
||||
<view class="qrcode-border">
|
||||
<!-- 加载中 -->
|
||||
<view class="qrcode-loading" wx:if="{{qrcodeLoading}}">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">生成中...</text>
|
||||
</view>
|
||||
<!-- 二维码图片 -->
|
||||
<image
|
||||
wx:else
|
||||
class="qrcode-image"
|
||||
src="{{qrcodeImage}}"
|
||||
mode="aspectFit"
|
||||
show-menu-by-longpress="{{true}}"
|
||||
></image>
|
||||
</view>
|
||||
<view class="qrcode-corners">
|
||||
<view class="corner tl"></view>
|
||||
<view class="corner tr"></view>
|
||||
<view class="corner bl"></view>
|
||||
<view class="corner br"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="qrcode-info">
|
||||
<text class="code-label">会员码</text>
|
||||
<text class="code-value">{{userInfo.memberCode}}</text>
|
||||
</view>
|
||||
|
||||
<view class="qrcode-tips">
|
||||
<view class="tip-item">
|
||||
<text class="tip-icon">📱</text>
|
||||
<text class="tip-text">请出示给对方扫描发起挑战</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 完善资料弹窗 -->
|
||||
<view class="profile-overlay {{showProfileModal ? 'show' : ''}}" bindtap="closeProfileModal">
|
||||
<view class="profile-modal {{showProfileModal ? 'show' : ''}}" catchtap="preventBubble">
|
||||
<view class="profile-modal-header">
|
||||
<text class="profile-modal-title">{{isEditMode ? '修改资料' : '完善个人资料'}}</text>
|
||||
<view class="profile-modal-close" bindtap="closeProfileModal">×</view>
|
||||
</view>
|
||||
|
||||
<view class="profile-modal-body">
|
||||
<!-- 提示信息 -->
|
||||
<view class="profile-tips" wx:if="{{!isEditMode}}">
|
||||
<text class="tips-icon">💡</text>
|
||||
<text class="tips-text">完善资料后,好友可以更容易找到你</text>
|
||||
</view>
|
||||
|
||||
<!-- 头像选择 -->
|
||||
<view class="profile-avatar-section">
|
||||
<text class="profile-label">头像</text>
|
||||
<button class="avatar-choose-btn" open-type="chooseAvatar" bindchooseavatar="onChooseAvatarNew">
|
||||
<image
|
||||
class="profile-avatar-preview"
|
||||
src="{{profileForm.avatar || '/images/avatar-default.svg'}}"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
<view class="avatar-choose-badge">
|
||||
<text class="choose-icon">📷</text>
|
||||
</view>
|
||||
</button>
|
||||
<text class="avatar-tip">点击更换头像</text>
|
||||
</view>
|
||||
|
||||
<!-- 昵称输入 -->
|
||||
<view class="profile-nickname-section">
|
||||
<text class="profile-label">昵称</text>
|
||||
<input
|
||||
class="nickname-input"
|
||||
type="nickname"
|
||||
placeholder="请输入昵称"
|
||||
value="{{profileForm.nickname}}"
|
||||
bindinput="onNicknameInput"
|
||||
maxlength="20"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="profile-modal-footer">
|
||||
<button class="profile-btn-cancel" bindtap="closeProfileModal">{{isEditMode ? '取消' : '跳过'}}</button>
|
||||
<button class="profile-btn-confirm" bindtap="saveProfile">保存</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
||||
# ------------------------------------------
|
||||
# 文件上传配置(可选)
|
||||
|
||||
@ -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('数据库模型同步完成');
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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' });
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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
|
||||
};
|
||||
|
||||
BIN
server/uploads/3658676b-da52-4725-a2a5-d03c9bac5dc8.jpeg
Normal file
BIN
server/uploads/3658676b-da52-4725-a2a5-d03c9bac5dc8.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
BIN
server/uploads/a3f854e6-e663-4aed-82f4-4ada1bcc21ce.jpeg
Normal file
BIN
server/uploads/a3f854e6-e663-4aed-82f4-4ada1bcc21ce.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
BIN
server/uploads/d78d705e-3e4f-4459-9bd5-3af0af114c0b.jpeg
Normal file
BIN
server/uploads/d78d705e-3e4f-4459-9bd5-3af0af114c0b.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
Loading…
Reference in New Issue
Block a user