Refactor user search functionality in admin routes, enhance match challenge logic to prevent overlapping challenges, and update user-related data handling in the miniprogram. Improve UI consistency across various pages with design updates and optimize image upload responses to include full URLs.
This commit is contained in:
parent
a3b4bd7cb0
commit
15413c85cc
197
admin/node_modules/.vite/deps/_metadata.json
generated
vendored
197
admin/node_modules/.vite/deps/_metadata.json
generated
vendored
@ -2,307 +2,370 @@
|
|||||||
"hash": "0b0fcdca",
|
"hash": "0b0fcdca",
|
||||||
"configHash": "0bd4dba1",
|
"configHash": "0bd4dba1",
|
||||||
"lockfileHash": "45a4e0fd",
|
"lockfileHash": "45a4e0fd",
|
||||||
"browserHash": "0f8eb059",
|
"browserHash": "f9d297a9",
|
||||||
"optimized": {
|
"optimized": {
|
||||||
"@element-plus/icons-vue": {
|
"@element-plus/icons-vue": {
|
||||||
"src": "../../@element-plus/icons-vue/dist/index.js",
|
"src": "../../@element-plus/icons-vue/dist/index.js",
|
||||||
"file": "@element-plus_icons-vue.js",
|
"file": "@element-plus_icons-vue.js",
|
||||||
"fileHash": "63fef99e",
|
"fileHash": "9f66cf3c",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"axios": {
|
"axios": {
|
||||||
"src": "../../axios/index.js",
|
"src": "../../axios/index.js",
|
||||||
"file": "axios.js",
|
"file": "axios.js",
|
||||||
"fileHash": "c4c91103",
|
"fileHash": "d0e8827a",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"dayjs": {
|
"dayjs": {
|
||||||
"src": "../../dayjs/dayjs.min.js",
|
"src": "../../dayjs/dayjs.min.js",
|
||||||
"file": "dayjs.js",
|
"file": "dayjs.js",
|
||||||
"fileHash": "45ab92bc",
|
"fileHash": "67e70480",
|
||||||
"needsInterop": true
|
"needsInterop": true
|
||||||
},
|
},
|
||||||
"element-plus": {
|
"element-plus": {
|
||||||
"src": "../../element-plus/es/index.mjs",
|
"src": "../../element-plus/es/index.mjs",
|
||||||
"file": "element-plus.js",
|
"file": "element-plus.js",
|
||||||
"fileHash": "a9eb7ac2",
|
"fileHash": "6462e4a2",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/dist/locale/zh-cn.mjs": {
|
"element-plus/dist/locale/zh-cn.mjs": {
|
||||||
"src": "../../element-plus/dist/locale/zh-cn.mjs",
|
"src": "../../element-plus/dist/locale/zh-cn.mjs",
|
||||||
"file": "element-plus_dist_locale_zh-cn__mjs.js",
|
"file": "element-plus_dist_locale_zh-cn__mjs.js",
|
||||||
"fileHash": "2e509bd7",
|
"fileHash": "63d98427",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"pinia": {
|
"pinia": {
|
||||||
"src": "../../pinia/dist/pinia.mjs",
|
"src": "../../pinia/dist/pinia.mjs",
|
||||||
"file": "pinia.js",
|
"file": "pinia.js",
|
||||||
"fileHash": "5573d424",
|
"fileHash": "1cc5fe52",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"qrcode": {
|
"qrcode": {
|
||||||
"src": "../../qrcode/lib/browser.js",
|
"src": "../../qrcode/lib/browser.js",
|
||||||
"file": "qrcode.js",
|
"file": "qrcode.js",
|
||||||
"fileHash": "2f402876",
|
"fileHash": "8f92023e",
|
||||||
"needsInterop": true
|
"needsInterop": true
|
||||||
},
|
},
|
||||||
"vue": {
|
"vue": {
|
||||||
"src": "../../vue/dist/vue.runtime.esm-bundler.js",
|
"src": "../../vue/dist/vue.runtime.esm-bundler.js",
|
||||||
"file": "vue.js",
|
"file": "vue.js",
|
||||||
"fileHash": "0f609a55",
|
"fileHash": "97abd90d",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vue-router": {
|
"vue-router": {
|
||||||
"src": "../../vue-router/dist/vue-router.mjs",
|
"src": "../../vue-router/dist/vue-router.mjs",
|
||||||
"file": "vue-router.js",
|
"file": "vue-router.js",
|
||||||
"fileHash": "25c74ef8",
|
"fileHash": "b189be4f",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es": {
|
"element-plus/es": {
|
||||||
"src": "../../element-plus/es/index.mjs",
|
"src": "../../element-plus/es/index.mjs",
|
||||||
"file": "element-plus_es.js",
|
"file": "element-plus_es.js",
|
||||||
"fileHash": "a13f022c",
|
"fileHash": "4e5350bd",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/base/style/css": {
|
"element-plus/es/components/base/style/css": {
|
||||||
"src": "../../element-plus/es/components/base/style/css.mjs",
|
"src": "../../element-plus/es/components/base/style/css.mjs",
|
||||||
"file": "element-plus_es_components_base_style_css.js",
|
"file": "element-plus_es_components_base_style_css.js",
|
||||||
"fileHash": "b4220b72",
|
"fileHash": "2f24955e",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/loading/style/css": {
|
"element-plus/es/components/loading/style/css": {
|
||||||
"src": "../../element-plus/es/components/loading/style/css.mjs",
|
"src": "../../element-plus/es/components/loading/style/css.mjs",
|
||||||
"file": "element-plus_es_components_loading_style_css.js",
|
"file": "element-plus_es_components_loading_style_css.js",
|
||||||
"fileHash": "0122de81",
|
"fileHash": "5a7f8660",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/dialog/style/css": {
|
"element-plus/es/components/dialog/style/css": {
|
||||||
"src": "../../element-plus/es/components/dialog/style/css.mjs",
|
"src": "../../element-plus/es/components/dialog/style/css.mjs",
|
||||||
"file": "element-plus_es_components_dialog_style_css.js",
|
"file": "element-plus_es_components_dialog_style_css.js",
|
||||||
"fileHash": "f74604cc",
|
"fileHash": "27c0aa21",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/input/style/css": {
|
"element-plus/es/components/input/style/css": {
|
||||||
"src": "../../element-plus/es/components/input/style/css.mjs",
|
"src": "../../element-plus/es/components/input/style/css.mjs",
|
||||||
"file": "element-plus_es_components_input_style_css.js",
|
"file": "element-plus_es_components_input_style_css.js",
|
||||||
"fileHash": "664f43b9",
|
"fileHash": "1e44aa38",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/pagination/style/css": {
|
"element-plus/es/components/pagination/style/css": {
|
||||||
"src": "../../element-plus/es/components/pagination/style/css.mjs",
|
"src": "../../element-plus/es/components/pagination/style/css.mjs",
|
||||||
"file": "element-plus_es_components_pagination_style_css.js",
|
"file": "element-plus_es_components_pagination_style_css.js",
|
||||||
"fileHash": "ff29c02c",
|
"fileHash": "c46c3f28",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/table/style/css": {
|
"element-plus/es/components/table/style/css": {
|
||||||
"src": "../../element-plus/es/components/table/style/css.mjs",
|
"src": "../../element-plus/es/components/table/style/css.mjs",
|
||||||
"file": "element-plus_es_components_table_style_css.js",
|
"file": "element-plus_es_components_table_style_css.js",
|
||||||
"fileHash": "9516c0f3",
|
"fileHash": "f2cad5d9",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/tag/style/css": {
|
"element-plus/es/components/tag/style/css": {
|
||||||
"src": "../../element-plus/es/components/tag/style/css.mjs",
|
"src": "../../element-plus/es/components/tag/style/css.mjs",
|
||||||
"file": "element-plus_es_components_tag_style_css.js",
|
"file": "element-plus_es_components_tag_style_css.js",
|
||||||
"fileHash": "acc11f71",
|
"fileHash": "6026e653",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/table-column/style/css": {
|
"element-plus/es/components/table-column/style/css": {
|
||||||
"src": "../../element-plus/es/components/table-column/style/css.mjs",
|
"src": "../../element-plus/es/components/table-column/style/css.mjs",
|
||||||
"file": "element-plus_es_components_table-column_style_css.js",
|
"file": "element-plus_es_components_table-column_style_css.js",
|
||||||
"fileHash": "63d6cae1",
|
"fileHash": "20fe1fe1",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/form/style/css": {
|
"element-plus/es/components/form/style/css": {
|
||||||
"src": "../../element-plus/es/components/form/style/css.mjs",
|
"src": "../../element-plus/es/components/form/style/css.mjs",
|
||||||
"file": "element-plus_es_components_form_style_css.js",
|
"file": "element-plus_es_components_form_style_css.js",
|
||||||
"fileHash": "8d500fe6",
|
"fileHash": "acb1358d",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/form-item/style/css": {
|
"element-plus/es/components/form-item/style/css": {
|
||||||
"src": "../../element-plus/es/components/form-item/style/css.mjs",
|
"src": "../../element-plus/es/components/form-item/style/css.mjs",
|
||||||
"file": "element-plus_es_components_form-item_style_css.js",
|
"file": "element-plus_es_components_form-item_style_css.js",
|
||||||
"fileHash": "dff655d7",
|
"fileHash": "0d000125",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/select/style/css": {
|
"element-plus/es/components/select/style/css": {
|
||||||
"src": "../../element-plus/es/components/select/style/css.mjs",
|
"src": "../../element-plus/es/components/select/style/css.mjs",
|
||||||
"file": "element-plus_es_components_select_style_css.js",
|
"file": "element-plus_es_components_select_style_css.js",
|
||||||
"fileHash": "18fa9e47",
|
"fileHash": "f9c4f2b6",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/option/style/css": {
|
"element-plus/es/components/option/style/css": {
|
||||||
"src": "../../element-plus/es/components/option/style/css.mjs",
|
"src": "../../element-plus/es/components/option/style/css.mjs",
|
||||||
"file": "element-plus_es_components_option_style_css.js",
|
"file": "element-plus_es_components_option_style_css.js",
|
||||||
"fileHash": "ec5bec4c",
|
"fileHash": "5b17115f",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/button/style/css": {
|
"element-plus/es/components/button/style/css": {
|
||||||
"src": "../../element-plus/es/components/button/style/css.mjs",
|
"src": "../../element-plus/es/components/button/style/css.mjs",
|
||||||
"file": "element-plus_es_components_button_style_css.js",
|
"file": "element-plus_es_components_button_style_css.js",
|
||||||
"fileHash": "4e0ee820",
|
"fileHash": "072165a7",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/icon/style/css": {
|
"element-plus/es/components/icon/style/css": {
|
||||||
"src": "../../element-plus/es/components/icon/style/css.mjs",
|
"src": "../../element-plus/es/components/icon/style/css.mjs",
|
||||||
"file": "element-plus_es_components_icon_style_css.js",
|
"file": "element-plus_es_components_icon_style_css.js",
|
||||||
"fileHash": "d81f84e6",
|
"fileHash": "91400fe8",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/container/style/css": {
|
"element-plus/es/components/container/style/css": {
|
||||||
"src": "../../element-plus/es/components/container/style/css.mjs",
|
"src": "../../element-plus/es/components/container/style/css.mjs",
|
||||||
"file": "element-plus_es_components_container_style_css.js",
|
"file": "element-plus_es_components_container_style_css.js",
|
||||||
"fileHash": "05c2c583",
|
"fileHash": "42960329",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/main/style/css": {
|
"element-plus/es/components/main/style/css": {
|
||||||
"src": "../../element-plus/es/components/main/style/css.mjs",
|
"src": "../../element-plus/es/components/main/style/css.mjs",
|
||||||
"file": "element-plus_es_components_main_style_css.js",
|
"file": "element-plus_es_components_main_style_css.js",
|
||||||
"fileHash": "a80d717e",
|
"fileHash": "79d917a7",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/header/style/css": {
|
"element-plus/es/components/header/style/css": {
|
||||||
"src": "../../element-plus/es/components/header/style/css.mjs",
|
"src": "../../element-plus/es/components/header/style/css.mjs",
|
||||||
"file": "element-plus_es_components_header_style_css.js",
|
"file": "element-plus_es_components_header_style_css.js",
|
||||||
"fileHash": "e687ec69",
|
"fileHash": "784162ca",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/dropdown/style/css": {
|
"element-plus/es/components/dropdown/style/css": {
|
||||||
"src": "../../element-plus/es/components/dropdown/style/css.mjs",
|
"src": "../../element-plus/es/components/dropdown/style/css.mjs",
|
||||||
"file": "element-plus_es_components_dropdown_style_css.js",
|
"file": "element-plus_es_components_dropdown_style_css.js",
|
||||||
"fileHash": "59835c76",
|
"fileHash": "92871da6",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/dropdown-menu/style/css": {
|
"element-plus/es/components/dropdown-menu/style/css": {
|
||||||
"src": "../../element-plus/es/components/dropdown-menu/style/css.mjs",
|
"src": "../../element-plus/es/components/dropdown-menu/style/css.mjs",
|
||||||
"file": "element-plus_es_components_dropdown-menu_style_css.js",
|
"file": "element-plus_es_components_dropdown-menu_style_css.js",
|
||||||
"fileHash": "23e3be80",
|
"fileHash": "c0a9f980",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/dropdown-item/style/css": {
|
"element-plus/es/components/dropdown-item/style/css": {
|
||||||
"src": "../../element-plus/es/components/dropdown-item/style/css.mjs",
|
"src": "../../element-plus/es/components/dropdown-item/style/css.mjs",
|
||||||
"file": "element-plus_es_components_dropdown-item_style_css.js",
|
"file": "element-plus_es_components_dropdown-item_style_css.js",
|
||||||
"fileHash": "85b49429",
|
"fileHash": "846c18b1",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/avatar/style/css": {
|
"element-plus/es/components/avatar/style/css": {
|
||||||
"src": "../../element-plus/es/components/avatar/style/css.mjs",
|
"src": "../../element-plus/es/components/avatar/style/css.mjs",
|
||||||
"file": "element-plus_es_components_avatar_style_css.js",
|
"file": "element-plus_es_components_avatar_style_css.js",
|
||||||
"fileHash": "936aa490",
|
"fileHash": "094b2abf",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/breadcrumb/style/css": {
|
"element-plus/es/components/breadcrumb/style/css": {
|
||||||
"src": "../../element-plus/es/components/breadcrumb/style/css.mjs",
|
"src": "../../element-plus/es/components/breadcrumb/style/css.mjs",
|
||||||
"file": "element-plus_es_components_breadcrumb_style_css.js",
|
"file": "element-plus_es_components_breadcrumb_style_css.js",
|
||||||
"fileHash": "875c584d",
|
"fileHash": "6f37c7f8",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/breadcrumb-item/style/css": {
|
"element-plus/es/components/breadcrumb-item/style/css": {
|
||||||
"src": "../../element-plus/es/components/breadcrumb-item/style/css.mjs",
|
"src": "../../element-plus/es/components/breadcrumb-item/style/css.mjs",
|
||||||
"file": "element-plus_es_components_breadcrumb-item_style_css.js",
|
"file": "element-plus_es_components_breadcrumb-item_style_css.js",
|
||||||
"fileHash": "b43c6781",
|
"fileHash": "8aabd46b",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/aside/style/css": {
|
"element-plus/es/components/aside/style/css": {
|
||||||
"src": "../../element-plus/es/components/aside/style/css.mjs",
|
"src": "../../element-plus/es/components/aside/style/css.mjs",
|
||||||
"file": "element-plus_es_components_aside_style_css.js",
|
"file": "element-plus_es_components_aside_style_css.js",
|
||||||
"fileHash": "e712c224",
|
"fileHash": "3976d9c5",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/menu/style/css": {
|
"element-plus/es/components/menu/style/css": {
|
||||||
"src": "../../element-plus/es/components/menu/style/css.mjs",
|
"src": "../../element-plus/es/components/menu/style/css.mjs",
|
||||||
"file": "element-plus_es_components_menu_style_css.js",
|
"file": "element-plus_es_components_menu_style_css.js",
|
||||||
"fileHash": "b95e1cd9",
|
"fileHash": "2fd5552a",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/menu-item/style/css": {
|
"element-plus/es/components/menu-item/style/css": {
|
||||||
"src": "../../element-plus/es/components/menu-item/style/css.mjs",
|
"src": "../../element-plus/es/components/menu-item/style/css.mjs",
|
||||||
"file": "element-plus_es_components_menu-item_style_css.js",
|
"file": "element-plus_es_components_menu-item_style_css.js",
|
||||||
"fileHash": "c8593318",
|
"fileHash": "c1f41a14",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/input-number/style/css": {
|
"element-plus/es/components/input-number/style/css": {
|
||||||
"src": "../../element-plus/es/components/input-number/style/css.mjs",
|
"src": "../../element-plus/es/components/input-number/style/css.mjs",
|
||||||
"file": "element-plus_es_components_input-number_style_css.js",
|
"file": "element-plus_es_components_input-number_style_css.js",
|
||||||
"fileHash": "67b6ba2c",
|
"fileHash": "f2f57f0e",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/row/style/css": {
|
"element-plus/es/components/row/style/css": {
|
||||||
"src": "../../element-plus/es/components/row/style/css.mjs",
|
"src": "../../element-plus/es/components/row/style/css.mjs",
|
||||||
"file": "element-plus_es_components_row_style_css.js",
|
"file": "element-plus_es_components_row_style_css.js",
|
||||||
"fileHash": "4e4350dd",
|
"fileHash": "d116f174",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/col/style/css": {
|
"element-plus/es/components/col/style/css": {
|
||||||
"src": "../../element-plus/es/components/col/style/css.mjs",
|
"src": "../../element-plus/es/components/col/style/css.mjs",
|
||||||
"file": "element-plus_es_components_col_style_css.js",
|
"file": "element-plus_es_components_col_style_css.js",
|
||||||
"fileHash": "43cd095d",
|
"fileHash": "df159c48",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/radio-group/style/css": {
|
"element-plus/es/components/radio-group/style/css": {
|
||||||
"src": "../../element-plus/es/components/radio-group/style/css.mjs",
|
"src": "../../element-plus/es/components/radio-group/style/css.mjs",
|
||||||
"file": "element-plus_es_components_radio-group_style_css.js",
|
"file": "element-plus_es_components_radio-group_style_css.js",
|
||||||
"fileHash": "86c77dd9",
|
"fileHash": "a88d5658",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"element-plus/es/components/radio/style/css": {
|
"element-plus/es/components/radio/style/css": {
|
||||||
"src": "../../element-plus/es/components/radio/style/css.mjs",
|
"src": "../../element-plus/es/components/radio/style/css.mjs",
|
||||||
"file": "element-plus_es_components_radio_style_css.js",
|
"file": "element-plus_es_components_radio_style_css.js",
|
||||||
"fileHash": "e7a084fb",
|
"fileHash": "a04c3aea",
|
||||||
|
"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": "f4ed4f68",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"element-plus/es/components/autocomplete/style/css": {
|
||||||
|
"src": "../../element-plus/es/components/autocomplete/style/css.mjs",
|
||||||
|
"file": "element-plus_es_components_autocomplete_style_css.js",
|
||||||
|
"fileHash": "44ebd843",
|
||||||
|
"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": "3ad4a3c9",
|
||||||
|
"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": "2253b6b1",
|
||||||
|
"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": "9c51f142",
|
||||||
|
"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": "48d89583",
|
||||||
|
"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": "bf70a099",
|
||||||
|
"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": "d3299a84",
|
||||||
|
"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": "b737d8d8",
|
||||||
|
"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": "ac7c5b8f",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chunks": {
|
"chunks": {
|
||||||
|
"chunk-4PW274X2": {
|
||||||
|
"file": "chunk-4PW274X2.js"
|
||||||
|
},
|
||||||
|
"chunk-JUCAMQ7P": {
|
||||||
|
"file": "chunk-JUCAMQ7P.js"
|
||||||
|
},
|
||||||
"chunk-SMFPDFTD": {
|
"chunk-SMFPDFTD": {
|
||||||
"file": "chunk-SMFPDFTD.js"
|
"file": "chunk-SMFPDFTD.js"
|
||||||
},
|
},
|
||||||
"chunk-75C4BP7B": {
|
|
||||||
"file": "chunk-75C4BP7B.js"
|
|
||||||
},
|
|
||||||
"chunk-5KK3TTMN": {
|
|
||||||
"file": "chunk-5KK3TTMN.js"
|
|
||||||
},
|
|
||||||
"chunk-UBLR4G7Q": {
|
|
||||||
"file": "chunk-UBLR4G7Q.js"
|
|
||||||
},
|
|
||||||
"chunk-NKQWFVTF": {
|
|
||||||
"file": "chunk-NKQWFVTF.js"
|
|
||||||
},
|
|
||||||
"chunk-R5DNQ3QC": {
|
"chunk-R5DNQ3QC": {
|
||||||
"file": "chunk-R5DNQ3QC.js"
|
"file": "chunk-R5DNQ3QC.js"
|
||||||
},
|
},
|
||||||
"chunk-B2YDYSZR": {
|
"chunk-B2YDYSZR": {
|
||||||
"file": "chunk-B2YDYSZR.js"
|
"file": "chunk-B2YDYSZR.js"
|
||||||
},
|
},
|
||||||
|
"chunk-75C4BP7B": {
|
||||||
|
"file": "chunk-75C4BP7B.js"
|
||||||
|
},
|
||||||
|
"chunk-5KK3TTMN": {
|
||||||
|
"file": "chunk-5KK3TTMN.js"
|
||||||
|
},
|
||||||
"chunk-REWOA3VH": {
|
"chunk-REWOA3VH": {
|
||||||
"file": "chunk-REWOA3VH.js"
|
"file": "chunk-REWOA3VH.js"
|
||||||
},
|
},
|
||||||
"chunk-TX5YLZ4O": {
|
"chunk-TX5YLZ4O": {
|
||||||
"file": "chunk-TX5YLZ4O.js"
|
"file": "chunk-TX5YLZ4O.js"
|
||||||
},
|
},
|
||||||
"chunk-4PW274X2": {
|
"chunk-UBLR4G7Q": {
|
||||||
"file": "chunk-4PW274X2.js"
|
"file": "chunk-UBLR4G7Q.js"
|
||||||
},
|
|
||||||
"chunk-IV6PSERC": {
|
|
||||||
"file": "chunk-IV6PSERC.js"
|
|
||||||
},
|
|
||||||
"chunk-W7GFOP2W": {
|
|
||||||
"file": "chunk-W7GFOP2W.js"
|
|
||||||
},
|
|
||||||
"chunk-OP4ZUAFM": {
|
|
||||||
"file": "chunk-OP4ZUAFM.js"
|
|
||||||
},
|
},
|
||||||
"chunk-YFT6OQ5R": {
|
"chunk-YFT6OQ5R": {
|
||||||
"file": "chunk-YFT6OQ5R.js"
|
"file": "chunk-YFT6OQ5R.js"
|
||||||
},
|
},
|
||||||
|
"chunk-NKQWFVTF": {
|
||||||
|
"file": "chunk-NKQWFVTF.js"
|
||||||
|
},
|
||||||
|
"chunk-IV6PSERC": {
|
||||||
|
"file": "chunk-IV6PSERC.js"
|
||||||
|
},
|
||||||
|
"chunk-STH2JMDO": {
|
||||||
|
"file": "chunk-STH2JMDO.js"
|
||||||
|
},
|
||||||
"chunk-HYZ2CRGS": {
|
"chunk-HYZ2CRGS": {
|
||||||
"file": "chunk-HYZ2CRGS.js"
|
"file": "chunk-HYZ2CRGS.js"
|
||||||
},
|
},
|
||||||
"chunk-H2732BJL": {
|
"chunk-OP4ZUAFM": {
|
||||||
"file": "chunk-H2732BJL.js"
|
"file": "chunk-OP4ZUAFM.js"
|
||||||
},
|
},
|
||||||
"chunk-QZC7O2C6": {
|
"chunk-QZC7O2C6": {
|
||||||
"file": "chunk-QZC7O2C6.js"
|
"file": "chunk-QZC7O2C6.js"
|
||||||
},
|
},
|
||||||
|
"chunk-H2732BJL": {
|
||||||
|
"file": "chunk-H2732BJL.js"
|
||||||
|
},
|
||||||
"chunk-G3PMV62Z": {
|
"chunk-G3PMV62Z": {
|
||||||
"file": "chunk-G3PMV62Z.js"
|
"file": "chunk-G3PMV62Z.js"
|
||||||
}
|
}
|
||||||
|
|||||||
73662
admin/node_modules/.vite/deps/chunk-W7GFOP2W.js
generated
vendored
73662
admin/node_modules/.vite/deps/chunk-W7GFOP2W.js
generated
vendored
File diff suppressed because it is too large
Load Diff
7
admin/node_modules/.vite/deps/chunk-W7GFOP2W.js.map
generated
vendored
7
admin/node_modules/.vite/deps/chunk-W7GFOP2W.js.map
generated
vendored
File diff suppressed because one or more lines are too long
6
admin/node_modules/.vite/deps/element-plus.js
generated
vendored
6
admin/node_modules/.vite/deps/element-plus.js
generated
vendored
@ -514,11 +514,11 @@ import {
|
|||||||
virtualizedScrollbarProps,
|
virtualizedScrollbarProps,
|
||||||
watermarkProps,
|
watermarkProps,
|
||||||
zIndexContextKey
|
zIndexContextKey
|
||||||
} from "./chunk-W7GFOP2W.js";
|
} from "./chunk-STH2JMDO.js";
|
||||||
import "./chunk-OP4ZUAFM.js";
|
|
||||||
import "./chunk-HYZ2CRGS.js";
|
import "./chunk-HYZ2CRGS.js";
|
||||||
import "./chunk-H2732BJL.js";
|
import "./chunk-OP4ZUAFM.js";
|
||||||
import "./chunk-QZC7O2C6.js";
|
import "./chunk-QZC7O2C6.js";
|
||||||
|
import "./chunk-H2732BJL.js";
|
||||||
import "./chunk-G3PMV62Z.js";
|
import "./chunk-G3PMV62Z.js";
|
||||||
var export_dayjs = import_dayjs.default;
|
var export_dayjs = import_dayjs.default;
|
||||||
export {
|
export {
|
||||||
|
|||||||
6
admin/node_modules/.vite/deps/element-plus_es.js
generated
vendored
6
admin/node_modules/.vite/deps/element-plus_es.js
generated
vendored
@ -514,11 +514,11 @@ import {
|
|||||||
virtualizedScrollbarProps,
|
virtualizedScrollbarProps,
|
||||||
watermarkProps,
|
watermarkProps,
|
||||||
zIndexContextKey
|
zIndexContextKey
|
||||||
} from "./chunk-W7GFOP2W.js";
|
} from "./chunk-STH2JMDO.js";
|
||||||
import "./chunk-OP4ZUAFM.js";
|
|
||||||
import "./chunk-HYZ2CRGS.js";
|
import "./chunk-HYZ2CRGS.js";
|
||||||
import "./chunk-H2732BJL.js";
|
import "./chunk-OP4ZUAFM.js";
|
||||||
import "./chunk-QZC7O2C6.js";
|
import "./chunk-QZC7O2C6.js";
|
||||||
|
import "./chunk-H2732BJL.js";
|
||||||
import "./chunk-G3PMV62Z.js";
|
import "./chunk-G3PMV62Z.js";
|
||||||
var export_dayjs = import_dayjs.default;
|
var export_dayjs = import_dayjs.default;
|
||||||
export {
|
export {
|
||||||
|
|||||||
4
admin/node_modules/.vite/deps/element-plus_es_components_pagination_style_css.js
generated
vendored
4
admin/node_modules/.vite/deps/element-plus_es_components_pagination_style_css.js
generated
vendored
@ -1,9 +1,9 @@
|
|||||||
import "./chunk-75C4BP7B.js";
|
import "./chunk-75C4BP7B.js";
|
||||||
import "./chunk-5KK3TTMN.js";
|
import "./chunk-5KK3TTMN.js";
|
||||||
import "./chunk-UBLR4G7Q.js";
|
|
||||||
import "./chunk-NKQWFVTF.js";
|
|
||||||
import "./chunk-REWOA3VH.js";
|
import "./chunk-REWOA3VH.js";
|
||||||
import "./chunk-TX5YLZ4O.js";
|
import "./chunk-TX5YLZ4O.js";
|
||||||
|
import "./chunk-UBLR4G7Q.js";
|
||||||
|
import "./chunk-NKQWFVTF.js";
|
||||||
import "./chunk-IV6PSERC.js";
|
import "./chunk-IV6PSERC.js";
|
||||||
|
|
||||||
// node_modules/element-plus/es/components/pagination/style/css.mjs
|
// node_modules/element-plus/es/components/pagination/style/css.mjs
|
||||||
|
|||||||
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,7 +1,7 @@
|
|||||||
import "./chunk-75C4BP7B.js";
|
import "./chunk-75C4BP7B.js";
|
||||||
import "./chunk-5KK3TTMN.js";
|
import "./chunk-5KK3TTMN.js";
|
||||||
import "./chunk-UBLR4G7Q.js";
|
|
||||||
import "./chunk-REWOA3VH.js";
|
import "./chunk-REWOA3VH.js";
|
||||||
import "./chunk-TX5YLZ4O.js";
|
import "./chunk-TX5YLZ4O.js";
|
||||||
|
import "./chunk-UBLR4G7Q.js";
|
||||||
import "./chunk-IV6PSERC.js";
|
import "./chunk-IV6PSERC.js";
|
||||||
//# sourceMappingURL=element-plus_es_components_select_style_css.js.map
|
//# sourceMappingURL=element-plus_es_components_select_style_css.js.map
|
||||||
|
|||||||
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-5KK3TTMN.js";
|
|
||||||
import "./chunk-B2YDYSZR.js";
|
import "./chunk-B2YDYSZR.js";
|
||||||
|
import "./chunk-5KK3TTMN.js";
|
||||||
import "./chunk-IV6PSERC.js";
|
import "./chunk-IV6PSERC.js";
|
||||||
|
|
||||||
// node_modules/element-plus/es/components/table-column/style/css.mjs
|
// node_modules/element-plus/es/components/table-column/style/css.mjs
|
||||||
|
|||||||
@ -17,6 +17,7 @@ export const getDashboard = () => request.get('/admin/dashboard')
|
|||||||
|
|
||||||
// === 用户管理 ===
|
// === 用户管理 ===
|
||||||
export const getUsers = params => request.get('/admin/users', { params })
|
export const getUsers = params => request.get('/admin/users', { params })
|
||||||
|
export const searchUsers = params => request.get('/admin/users/search', { params }) // 搜索用户(用于积分操作)
|
||||||
export const getUserDetail = id => request.get(`/admin/users/${id}`)
|
export const getUserDetail = id => request.get(`/admin/users/${id}`)
|
||||||
export const updateUserStatus = (id, data) => request.put(`/admin/users/${id}/status`, data)
|
export const updateUserStatus = (id, data) => request.put(`/admin/users/${id}/status`, data)
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -89,6 +89,29 @@ App({
|
|||||||
if (loginRes.data.code === 0) {
|
if (loginRes.data.code === 0) {
|
||||||
this.globalData.token = loginRes.data.data.token;
|
this.globalData.token = loginRes.data.data.token;
|
||||||
this.globalData.userInfo = loginRes.data.data.userInfo;
|
this.globalData.userInfo = loginRes.data.data.userInfo;
|
||||||
|
|
||||||
|
// 处理天梯用户信息
|
||||||
|
if (loginRes.data.data.userInfo.ladderUsers && loginRes.data.data.userInfo.ladderUsers.length > 0) {
|
||||||
|
// 如果有当前门店,优先选择当前门店的天梯用户
|
||||||
|
if (this.globalData.currentStore?.storeId) {
|
||||||
|
const currentStoreLadderUser = loginRes.data.data.userInfo.ladderUsers.find(
|
||||||
|
lu => lu.storeId === this.globalData.currentStore.storeId
|
||||||
|
);
|
||||||
|
if (currentStoreLadderUser) {
|
||||||
|
this.globalData.ladderUser = currentStoreLadderUser;
|
||||||
|
} else {
|
||||||
|
// 当前门店没有天梯用户,取第一个
|
||||||
|
this.globalData.ladderUser = loginRes.data.data.userInfo.ladderUsers[0];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 没有当前门店,取第一个天梯用户
|
||||||
|
this.globalData.ladderUser = loginRes.data.data.userInfo.ladderUsers[0];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 没有天梯用户
|
||||||
|
this.globalData.ladderUser = null;
|
||||||
|
}
|
||||||
|
|
||||||
wx.setStorageSync("token", loginRes.data.data.token);
|
wx.setStorageSync("token", loginRes.data.data.token);
|
||||||
this.connectWebSocket();
|
this.connectWebSocket();
|
||||||
resolve(loginRes.data.data);
|
resolve(loginRes.data.data);
|
||||||
@ -112,6 +135,29 @@ App({
|
|||||||
this.request("/api/user/info")
|
this.request("/api/user/info")
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.globalData.userInfo = res.data;
|
this.globalData.userInfo = res.data;
|
||||||
|
|
||||||
|
// 处理天梯用户信息
|
||||||
|
if (res.data.ladderUsers && res.data.ladderUsers.length > 0) {
|
||||||
|
// 如果有当前门店,优先选择当前门店的天梯用户
|
||||||
|
if (this.globalData.currentStore?.storeId) {
|
||||||
|
const currentStoreLadderUser = res.data.ladderUsers.find(
|
||||||
|
lu => lu.storeId === this.globalData.currentStore.storeId
|
||||||
|
);
|
||||||
|
if (currentStoreLadderUser) {
|
||||||
|
this.globalData.ladderUser = currentStoreLadderUser;
|
||||||
|
} else {
|
||||||
|
// 当前门店没有天梯用户,取第一个
|
||||||
|
this.globalData.ladderUser = res.data.ladderUsers[0];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 没有当前门店,取第一个天梯用户
|
||||||
|
this.globalData.ladderUser = res.data.ladderUsers[0];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 没有天梯用户
|
||||||
|
this.globalData.ladderUser = null;
|
||||||
|
}
|
||||||
|
|
||||||
this.connectWebSocket();
|
this.connectWebSocket();
|
||||||
resolve(res.data);
|
resolve(res.data);
|
||||||
})
|
})
|
||||||
@ -131,9 +177,25 @@ App({
|
|||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.globalData.currentStore = res.data;
|
this.globalData.currentStore = res.data;
|
||||||
|
|
||||||
|
// 如果当前门店有 ladderUserId,获取该门店的天梯用户信息
|
||||||
if (res.data?.ladderUserId) {
|
if (res.data?.ladderUserId) {
|
||||||
this.getLadderUser(res.data.storeId);
|
this.getLadderUser(res.data.storeId);
|
||||||
|
} else if (res.data?.storeId) {
|
||||||
|
// 如果当前门店没有 ladderUserId,但用户信息中有该门店的天梯用户,使用它
|
||||||
|
if (this.globalData.userInfo?.ladderUsers) {
|
||||||
|
const currentStoreLadderUser = this.globalData.userInfo.ladderUsers.find(
|
||||||
|
lu => lu.storeId === res.data.storeId
|
||||||
|
);
|
||||||
|
if (currentStoreLadderUser) {
|
||||||
|
this.globalData.ladderUser = currentStoreLadderUser;
|
||||||
|
} else {
|
||||||
|
// 当前门店没有天梯用户,清空
|
||||||
|
this.globalData.ladderUser = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(res.data);
|
resolve(res.data);
|
||||||
})
|
})
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
@ -143,6 +205,25 @@ App({
|
|||||||
this.request("/api/user/current-store")
|
this.request("/api/user/current-store")
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.globalData.currentStore = res.data;
|
this.globalData.currentStore = res.data;
|
||||||
|
|
||||||
|
// 如果当前门店有 ladderUserId,获取该门店的天梯用户信息
|
||||||
|
if (res.data?.ladderUserId) {
|
||||||
|
this.getLadderUser(res.data.storeId);
|
||||||
|
} else if (res.data?.storeId) {
|
||||||
|
// 如果当前门店没有 ladderUserId,但用户信息中有该门店的天梯用户,使用它
|
||||||
|
if (this.globalData.userInfo?.ladderUsers) {
|
||||||
|
const currentStoreLadderUser = this.globalData.userInfo.ladderUsers.find(
|
||||||
|
lu => lu.storeId === res.data.storeId
|
||||||
|
);
|
||||||
|
if (currentStoreLadderUser) {
|
||||||
|
this.globalData.ladderUser = currentStoreLadderUser;
|
||||||
|
} else {
|
||||||
|
// 当前门店没有天梯用户,清空
|
||||||
|
this.globalData.ladderUser = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resolve(res.data);
|
resolve(res.data);
|
||||||
})
|
})
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
@ -214,23 +295,64 @@ App({
|
|||||||
handleWsMessage(data) {
|
handleWsMessage(data) {
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case "challenge_request":
|
case "challenge_request":
|
||||||
// 收到挑战请求
|
// 收到挑战请求 - 使用自定义弹框
|
||||||
wx.showModal({
|
const challengeData = data.data;
|
||||||
title: "收到挑战",
|
const pages = getCurrentPages();
|
||||||
content: `${data.data.challenger.realName} 向你发起挑战`,
|
const currentPage = pages[pages.length - 1];
|
||||||
confirmText: "接受",
|
|
||||||
cancelText: "拒绝",
|
// 如果当前页面有处理挑战请求的方法,调用它
|
||||||
success: (res) => {
|
if (currentPage && typeof currentPage.handleChallengeRequest === 'function') {
|
||||||
this.request(
|
currentPage.handleChallengeRequest(challengeData);
|
||||||
"/api/match/challenge/respond",
|
} else {
|
||||||
{
|
// 否则使用系统弹框
|
||||||
match_id: data.data.matchId,
|
wx.showModal({
|
||||||
accept: res.confirm,
|
title: "收到挑战",
|
||||||
},
|
content: `${challengeData.challenger.realName}(Lv${challengeData.challenger.level}, 战力${challengeData.challenger.powerScore}) 向你发起挑战`,
|
||||||
"POST"
|
confirmText: "接受",
|
||||||
);
|
cancelText: "拒绝",
|
||||||
},
|
success: (res) => {
|
||||||
});
|
this.request(
|
||||||
|
"/api/match/challenge/respond",
|
||||||
|
{
|
||||||
|
match_id: challengeData.matchId,
|
||||||
|
accept: res.confirm,
|
||||||
|
},
|
||||||
|
"POST"
|
||||||
|
).then(() => {
|
||||||
|
if (res.confirm) {
|
||||||
|
wx.showToast({ title: '已接受挑战', icon: 'success' });
|
||||||
|
// 跳转到挑战赛详情
|
||||||
|
setTimeout(() => {
|
||||||
|
wx.navigateTo({
|
||||||
|
url: `/pages/match/challenge-detail/index?id=${challengeData.matchId}`
|
||||||
|
});
|
||||||
|
}, 1500);
|
||||||
|
} else {
|
||||||
|
wx.showToast({ title: '已拒绝挑战', icon: 'success' });
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('响应挑战失败:', err);
|
||||||
|
wx.showToast({ title: '操作失败', icon: 'none' });
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "challenge_accepted":
|
||||||
|
// 挑战被接受
|
||||||
|
wx.showToast({ title: '对方已接受挑战', icon: 'success' });
|
||||||
|
// 如果当前在挑战赛详情页面,刷新数据
|
||||||
|
const pages2 = getCurrentPages();
|
||||||
|
const currentPage2 = pages2[pages2.length - 1];
|
||||||
|
if (currentPage2 && currentPage2.route === 'pages/match/challenge-detail/index') {
|
||||||
|
if (typeof currentPage2.loadMatchDetail === 'function') {
|
||||||
|
currentPage2.loadMatchDetail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "challenge_rejected":
|
||||||
|
// 挑战被拒绝
|
||||||
|
wx.showToast({ title: '对方已拒绝挑战', icon: 'none' });
|
||||||
break;
|
break;
|
||||||
case "score_confirm_request":
|
case "score_confirm_request":
|
||||||
// 收到比分确认请求
|
// 收到比分确认请求
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
"pages/index/index",
|
"pages/index/index",
|
||||||
"pages/user/index",
|
"pages/user/index",
|
||||||
"pages/match/challenge/index",
|
"pages/match/challenge/index",
|
||||||
|
"pages/match/challenge-detail/index",
|
||||||
"pages/match/ranking/index",
|
"pages/match/ranking/index",
|
||||||
"pages/match/history/index",
|
"pages/match/history/index",
|
||||||
"pages/points/mall/index",
|
"pages/points/mall/index",
|
||||||
|
|||||||
@ -376,10 +376,6 @@ page {
|
|||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.power-change.positive::before {
|
|
||||||
content: '+';
|
|
||||||
}
|
|
||||||
|
|
||||||
.power-change.negative {
|
.power-change.negative {
|
||||||
color: #FF4D4F;
|
color: #FF4D4F;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,17 +6,17 @@
|
|||||||
// 开发环境配置
|
// 开发环境配置
|
||||||
const devConfig = {
|
const devConfig = {
|
||||||
// API 基础地址(本地开发)
|
// API 基础地址(本地开发)
|
||||||
baseUrl: "http://localhost:3000",
|
baseUrl: "https://yingsa-server.ethan.team",
|
||||||
// WebSocket 地址(本地开发)
|
// WebSocket 地址(本地开发)
|
||||||
wsUrl: "ws://localhost:3000/ws",
|
wsUrl: "wss://yingsa-server.ethan.team/ws",
|
||||||
};
|
};
|
||||||
|
|
||||||
// 生产环境配置
|
// 生产环境配置
|
||||||
const prodConfig = {
|
const prodConfig = {
|
||||||
// API 基础地址(生产环境,请替换为实际域名)
|
// API 基础地址(生产环境,请替换为实际域名)
|
||||||
baseUrl: "https://your-domain.com",
|
baseUrl: "https://yingsa-server.ethan.team",
|
||||||
// WebSocket 地址(生产环境,请替换为实际域名)
|
// WebSocket 地址(生产环境,请替换为实际域名)
|
||||||
wsUrl: "wss://your-domain.com/ws",
|
wsUrl: "wss://yingsa-server.ethan.team/ws",
|
||||||
};
|
};
|
||||||
|
|
||||||
// 根据环境变量选择配置
|
// 根据环境变量选择配置
|
||||||
|
|||||||
@ -26,94 +26,85 @@
|
|||||||
|
|
||||||
<!-- 主要内容区域 -->
|
<!-- 主要内容区域 -->
|
||||||
<view class="main-content">
|
<view class="main-content">
|
||||||
<!-- 性别筛选标签 -->
|
<!-- 性别筛选标签 - 吸附在顶部 -->
|
||||||
<view class="filter-bar animate-fadeInUp" style="animation-delay: 0.1s">
|
<view class="filter-bar-wrapper">
|
||||||
<view
|
<view class="filter-bar animate-fadeInUp" style="animation-delay: 0.1s">
|
||||||
class="filter-item {{gender === '' ? 'active' : ''}}"
|
<view
|
||||||
bindtap="setGender"
|
class="filter-item {{gender === '' ? 'active' : ''}}"
|
||||||
data-gender=""
|
bindtap="setGender"
|
||||||
>
|
data-gender=""
|
||||||
全部
|
>
|
||||||
</view>
|
全部
|
||||||
<view
|
</view>
|
||||||
class="filter-item {{gender === '1' ? 'active' : ''}}"
|
<view
|
||||||
bindtap="setGender"
|
class="filter-item {{gender === '1' ? 'active' : ''}}"
|
||||||
data-gender="1"
|
bindtap="setGender"
|
||||||
>
|
data-gender="1"
|
||||||
♂ 男子
|
>
|
||||||
</view>
|
♂ 男子
|
||||||
<view
|
</view>
|
||||||
class="filter-item {{gender === '2' ? 'active' : ''}}"
|
<view
|
||||||
bindtap="setGender"
|
class="filter-item {{gender === '2' ? 'active' : ''}}"
|
||||||
data-gender="2"
|
bindtap="setGender"
|
||||||
>
|
data-gender="2"
|
||||||
♀ 女子
|
>
|
||||||
|
♀ 女子
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 排名列表 -->
|
<!-- 排名列表 -->
|
||||||
<view class="ranking-list">
|
<view class="ranking-list">
|
||||||
<scroll-view
|
<block wx:if="{{list.length > 0}}">
|
||||||
scroll-y="true"
|
<view
|
||||||
class="ranking-scroll"
|
class="ranking-item stagger-item {{index < 3 ? 'top-rank' : ''}} animate-fadeInUp"
|
||||||
bindscrolltolower="loadMore"
|
wx:for="{{list}}"
|
||||||
lower-threshold="100"
|
wx:key="id"
|
||||||
style="height: calc(100vh - 480rpx);"
|
bindtap="viewPlayer"
|
||||||
>
|
data-id="{{item.id}}"
|
||||||
<block wx:if="{{list.length > 0}}">
|
>
|
||||||
<view
|
<!-- 排名徽章 -->
|
||||||
class="ranking-item stagger-item {{index < 3 ? 'top-rank' : ''}} animate-fadeInUp"
|
<view class="rank-badge {{item.rank === 1 ? 'top1' : item.rank === 2 ? 'top2' : item.rank === 3 ? 'top3' : 'normal'}}">
|
||||||
wx:for="{{list}}"
|
<text wx:if="{{item.rank <= 3}}">{{item.rank === 1 ? '👑' : item.rank === 2 ? '🥈' : '🥉'}}</text>
|
||||||
wx:key="id"
|
<text wx:else>{{item.rank}}</text>
|
||||||
bindtap="viewPlayer"
|
</view>
|
||||||
data-id="{{item.id}}"
|
|
||||||
>
|
<!-- 选手头像 -->
|
||||||
<!-- 排名徽章 -->
|
<image class="player-avatar" src="{{item.avatar || '/images/avatar-default.svg'}}" mode="aspectFill"></image>
|
||||||
<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 class="player-info">
|
||||||
</view>
|
<text class="player-name">{{item.realName}}</text>
|
||||||
|
<view class="player-meta">
|
||||||
<!-- 选手头像 -->
|
<text class="player-level lv{{item.level}}">Lv{{item.level}}</text>
|
||||||
<image class="player-avatar" src="{{item.avatar || '/images/avatar-default.svg'}}" mode="aspectFill"></image>
|
<text class="player-stats">胜率 {{item.winRate}}%</text>
|
||||||
|
|
||||||
<!-- 选手信息 -->
|
|
||||||
<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="player-power">
|
|
||||||
<text class="power-value">{{item.powerScore}}</text>
|
|
||||||
<text class="power-label">战力</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</block>
|
|
||||||
|
<!-- 战力值 -->
|
||||||
<!-- 空状态 -->
|
<view class="player-power">
|
||||||
<view wx:elif="{{!loading}}" class="empty-state">
|
<text class="power-value">{{item.powerScore}}</text>
|
||||||
<image class="empty-icon" src="/images/empty-ranking.svg" mode="aspectFit"></image>
|
<text class="power-label">战力</text>
|
||||||
<text class="empty-title">暂无排名数据</text>
|
</view>
|
||||||
<text class="empty-desc">每月完成3场比赛即可上榜</text>
|
|
||||||
</view>
|
</view>
|
||||||
|
</block>
|
||||||
|
|
||||||
<!-- 加载更多 -->
|
<!-- 空状态 -->
|
||||||
<view wx:if="{{loading}}" class="loading-state">
|
<view wx:elif="{{!loading}}" class="empty-state">
|
||||||
<text>加载中...</text>
|
<image class="empty-icon" src="/images/empty-ranking.svg" mode="aspectFit"></image>
|
||||||
</view>
|
<text class="empty-title">暂无排名数据</text>
|
||||||
|
<text class="empty-desc">每月完成3场比赛即可上榜</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 到底提示 -->
|
<!-- 加载更多 -->
|
||||||
<view wx:if="{{list.length > 0 && !hasMore && !loading}}" class="load-more">
|
<view wx:if="{{loading}}" class="loading-state">
|
||||||
<text>— 已显示全部选手 —</text>
|
<text>加载中...</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 底部安全区域 -->
|
<!-- 到底提示 -->
|
||||||
<view class="safe-bottom"></view>
|
<view wx:if="{{list.length > 0 && !hasMore && !loading}}" class="load-more">
|
||||||
</scroll-view>
|
<text>— 已显示全部选手 —</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@ -6,35 +6,22 @@
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: var(--bg-page);
|
background: var(--bg-page);
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 顶部装饰背景 */
|
/* 顶部装饰背景 */
|
||||||
.hero-section {
|
.hero-section {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 32rpx 24rpx 24rpx;
|
padding: 48rpx 24rpx 32rpx;
|
||||||
background: linear-gradient(180deg, #FFF5F0 0%, var(--bg-page) 100%);
|
background: transparent;
|
||||||
|
border-bottom: 1rpx solid rgba(0, 0, 0, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-pattern {
|
.hero-pattern {
|
||||||
position: absolute;
|
display: none;
|
||||||
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 {
|
.hero-pattern-2 {
|
||||||
position: absolute;
|
display: none;
|
||||||
top: 120rpx;
|
|
||||||
left: -80rpx;
|
|
||||||
width: 200rpx;
|
|
||||||
height: 200rpx;
|
|
||||||
background: radial-gradient(circle, rgba(0, 201, 167, 0.06) 0%, transparent 70%);
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 门店信息头部 */
|
/* 门店信息头部 */
|
||||||
@ -44,7 +31,11 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 20rpx;
|
margin-bottom: 32rpx;
|
||||||
|
padding: 16rpx 20rpx;
|
||||||
|
background: var(--bg-white);
|
||||||
|
border-radius: 16rpx;
|
||||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||||
animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,24 +49,25 @@
|
|||||||
width: 12rpx;
|
width: 12rpx;
|
||||||
height: 12rpx;
|
height: 12rpx;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: var(--accent);
|
background: #ff6b35;
|
||||||
|
box-shadow: 0 0 8rpx rgba(255, 107, 53, 0.4);
|
||||||
animation: pulse 2s ease-in-out infinite;
|
animation: pulse 2s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
.store-name {
|
.store-name {
|
||||||
font-size: 28rpx;
|
font-size: 30rpx;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--text-primary);
|
color: #333;
|
||||||
|
letter-spacing: 0.5rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.change-store-btn {
|
.change-store-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6rpx;
|
gap: 6rpx;
|
||||||
padding: 12rpx 20rpx;
|
padding: 10rpx 18rpx;
|
||||||
background: var(--bg-white);
|
background: var(--bg-soft);
|
||||||
border-radius: var(--radius-full);
|
border-radius: 20rpx;
|
||||||
box-shadow: var(--shadow-sm);
|
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,12 +77,14 @@
|
|||||||
|
|
||||||
.change-store-text {
|
.change-store-text {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: var(--text-secondary);
|
color: #666;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.change-store-arrow {
|
.change-store-arrow {
|
||||||
font-size: 20rpx;
|
font-size: 22rpx;
|
||||||
color: var(--text-muted);
|
color: #999;
|
||||||
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 页面标题 */
|
/* 页面标题 */
|
||||||
@ -98,34 +92,48 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 8rpx;
|
margin-bottom: 0;
|
||||||
|
padding: 24rpx 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-title {
|
.page-title {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 44rpx;
|
font-size: 52rpx;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--text-primary);
|
color: #1a1a1a;
|
||||||
margin-bottom: 8rpx;
|
margin-bottom: 8rpx;
|
||||||
letter-spacing: 2rpx;
|
letter-spacing: 1rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-subtitle {
|
.page-subtitle {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
color: var(--text-muted);
|
color: #999;
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: 0.5rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 主要内容区域 */
|
/* 主要内容区域 */
|
||||||
.main-content {
|
.main-content {
|
||||||
padding: 0 24rpx;
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 筛选标签栏容器 - 用于吸附效果 */
|
||||||
|
.filter-bar-wrapper {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
background: var(--bg-page);
|
||||||
|
padding: 24rpx 24rpx;
|
||||||
|
/* margin-bottom: 24rpx; */
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 筛选标签栏 */
|
/* 筛选标签栏 */
|
||||||
.filter-bar {
|
.filter-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16rpx;
|
gap: 16rpx;
|
||||||
margin-bottom: 24rpx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-item {
|
.filter-item {
|
||||||
@ -141,9 +149,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.filter-item.active {
|
.filter-item.active {
|
||||||
background: var(--primary-gradient);
|
background: linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%);
|
||||||
color: var(--text-white);
|
color: #fff;
|
||||||
box-shadow: var(--shadow-primary);
|
box-shadow: 0 4rpx 16rpx rgba(255, 107, 53, 0.3);
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-item:active {
|
.filter-item:active {
|
||||||
@ -152,82 +161,93 @@
|
|||||||
|
|
||||||
/* 排名列表 */
|
/* 排名列表 */
|
||||||
.ranking-list {
|
.ranking-list {
|
||||||
|
padding: 0 24rpx 40rpx;
|
||||||
}
|
|
||||||
|
|
||||||
.ranking-scroll {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ranking-item {
|
.ranking-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 20rpx 24rpx;
|
padding: 24rpx;
|
||||||
background: var(--bg-white);
|
background: #fff;
|
||||||
border-radius: var(--radius-lg);
|
border-radius: 20rpx;
|
||||||
margin-bottom: 12rpx;
|
margin-bottom: 16rpx;
|
||||||
box-shadow: var(--shadow-sm);
|
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
border: 1rpx solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ranking-item:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ranking-item:active {
|
.ranking-item:active {
|
||||||
transform: scale(0.98);
|
transform: scale(0.98);
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ranking-item.top-rank {
|
.ranking-item.top-rank {
|
||||||
background: linear-gradient(135deg, #FFFBF8, var(--bg-white));
|
background: linear-gradient(135deg, #fff5f0 0%, #fff 100%);
|
||||||
border: 1rpx solid rgba(255, 107, 53, 0.1);
|
border: 2rpx solid rgba(255, 107, 53, 0.2);
|
||||||
|
box-shadow: 0 6rpx 20rpx rgba(255, 107, 53, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 排名徽章 */
|
/* 排名徽章 */
|
||||||
.rank-badge {
|
.rank-badge {
|
||||||
width: 52rpx;
|
width: 56rpx;
|
||||||
height: 52rpx;
|
height: 56rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin-right: 16rpx;
|
margin-right: 20rpx;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rank-badge.top1 {
|
.rank-badge.top1 {
|
||||||
background: linear-gradient(135deg, #FFE082 0%, #FFD700 100%);
|
background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
|
||||||
color: #8B4513;
|
color: #8B4513;
|
||||||
box-shadow: 0 4rpx 16rpx rgba(255, 215, 0, 0.4);
|
box-shadow: 0 6rpx 20rpx rgba(255, 215, 0, 0.5);
|
||||||
font-size: 28rpx;
|
font-size: 32rpx;
|
||||||
|
width: 64rpx;
|
||||||
|
height: 64rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rank-badge.top2 {
|
.rank-badge.top2 {
|
||||||
background: linear-gradient(135deg, #F5F5F5 0%, #C0C0C0 100%);
|
background: linear-gradient(135deg, #E8E8E8 0%, #C0C0C0 100%);
|
||||||
color: #4A4A4A;
|
color: #4A4A4A;
|
||||||
box-shadow: 0 4rpx 12rpx rgba(192, 192, 192, 0.4);
|
box-shadow: 0 4rpx 16rpx rgba(192, 192, 192, 0.5);
|
||||||
font-size: 28rpx;
|
font-size: 32rpx;
|
||||||
|
width: 64rpx;
|
||||||
|
height: 64rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rank-badge.top3 {
|
.rank-badge.top3 {
|
||||||
background: linear-gradient(135deg, #DEB887 0%, #CD853F 100%);
|
background: linear-gradient(135deg, #CD853F 0%, #B8860B 100%);
|
||||||
color: #5C4033;
|
color: #fff;
|
||||||
box-shadow: 0 4rpx 12rpx rgba(205, 133, 63, 0.4);
|
box-shadow: 0 4rpx 16rpx rgba(205, 133, 63, 0.5);
|
||||||
font-size: 28rpx;
|
font-size: 32rpx;
|
||||||
|
width: 64rpx;
|
||||||
|
height: 64rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rank-badge.normal {
|
.rank-badge.normal {
|
||||||
background: var(--bg-soft);
|
background: linear-gradient(135deg, #f5f5f5 0%, #e8e8e8 100%);
|
||||||
color: var(--text-muted);
|
color: #666;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 选手头像 */
|
/* 选手头像 */
|
||||||
.player-avatar {
|
.player-avatar {
|
||||||
width: 72rpx;
|
width: 80rpx;
|
||||||
height: 72rpx;
|
height: 80rpx;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
margin-right: 16rpx;
|
margin-right: 20rpx;
|
||||||
border: 2rpx solid var(--bg-white);
|
border: 3rpx solid #fff;
|
||||||
box-shadow: var(--shadow-sm);
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
background: #f5f5f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 选手信息 */
|
/* 选手信息 */
|
||||||
@ -238,10 +258,10 @@
|
|||||||
|
|
||||||
.player-name {
|
.player-name {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 28rpx;
|
font-size: 30rpx;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--text-primary);
|
color: #333;
|
||||||
margin-bottom: 6rpx;
|
margin-bottom: 8rpx;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@ -250,15 +270,18 @@
|
|||||||
.player-meta {
|
.player-meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12rpx;
|
gap: 16rpx;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.player-level {
|
.player-level {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
padding: 4rpx 12rpx;
|
align-items: center;
|
||||||
border-radius: var(--radius-full);
|
padding: 4rpx 14rpx;
|
||||||
font-size: 20rpx;
|
border-radius: 20rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.player-level.lv1 { background: #E8F5E9; color: #2E7D32; }
|
.player-level.lv1 { background: #E8F5E9; color: #2E7D32; }
|
||||||
@ -268,8 +291,9 @@
|
|||||||
.player-level.lv5 { background: #F3E5F5; color: #7B1FA2; }
|
.player-level.lv5 { background: #F3E5F5; color: #7B1FA2; }
|
||||||
|
|
||||||
.player-stats {
|
.player-stats {
|
||||||
font-size: 22rpx;
|
font-size: 24rpx;
|
||||||
color: var(--text-muted);
|
color: #666;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 战力值 */
|
/* 战力值 */
|
||||||
@ -280,14 +304,17 @@
|
|||||||
|
|
||||||
.power-value {
|
.power-value {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 32rpx;
|
font-size: 36rpx;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--primary);
|
color: #ff6b35;
|
||||||
|
line-height: 1.2;
|
||||||
|
margin-bottom: 4rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.power-label {
|
.power-label {
|
||||||
font-size: 22rpx;
|
font-size: 22rpx;
|
||||||
color: var(--text-muted);
|
color: #999;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 空状态 */
|
/* 空状态 */
|
||||||
@ -329,12 +356,8 @@
|
|||||||
/* 加载更多 */
|
/* 加载更多 */
|
||||||
.load-more {
|
.load-more {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 32rpx;
|
padding: 16rpx 32rpx 8rpx;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 底部安全区域 */
|
|
||||||
.safe-bottom {
|
|
||||||
height: 80rpx;
|
|
||||||
}
|
|
||||||
|
|||||||
588
miniprogram/pages/match/challenge-detail/index.js
Normal file
588
miniprogram/pages/match/challenge-detail/index.js
Normal file
@ -0,0 +1,588 @@
|
|||||||
|
const app = getApp()
|
||||||
|
|
||||||
|
Page({
|
||||||
|
data: {
|
||||||
|
matchId: null,
|
||||||
|
matchInfo: null,
|
||||||
|
myRole: null, // 'challenger' | 'defender' | null
|
||||||
|
canAccept: false,
|
||||||
|
canReject: false,
|
||||||
|
canSubmitScore: false,
|
||||||
|
canConfirmScore: false,
|
||||||
|
showScoreModal: false,
|
||||||
|
myScore: '',
|
||||||
|
opponentScore: '',
|
||||||
|
loading: false
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoad(options) {
|
||||||
|
if (options.id) {
|
||||||
|
this.setData({ matchId: options.id })
|
||||||
|
this.loadMatchDetail()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onShow() {
|
||||||
|
// 每次显示页面时刷新数据
|
||||||
|
if (this.data.matchId) {
|
||||||
|
this.loadMatchDetail()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 加载比赛详情
|
||||||
|
async loadMatchDetail() {
|
||||||
|
this.setData({ loading: true })
|
||||||
|
try {
|
||||||
|
const res = await app.request(`/api/match/${this.data.matchId}`)
|
||||||
|
console.log('API完整响应:', JSON.stringify(res, null, 2))
|
||||||
|
|
||||||
|
// app.request 返回的是 { code: 0, message, data }
|
||||||
|
// 所以 res.data 才是真正的数据
|
||||||
|
const matchInfo = res.data
|
||||||
|
console.log('比赛详情数据:', matchInfo)
|
||||||
|
console.log('数据字段:', Object.keys(matchInfo || {}))
|
||||||
|
|
||||||
|
if (!matchInfo) {
|
||||||
|
console.error('比赛详情数据为空')
|
||||||
|
wx.showToast({ title: '数据格式错误', icon: 'none' })
|
||||||
|
this.setData({ loading: false })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查关键字段
|
||||||
|
console.log('关键字段检查:', {
|
||||||
|
hasMyRole: 'myRole' in matchInfo,
|
||||||
|
myRole: matchInfo.myRole,
|
||||||
|
myRoleType: typeof matchInfo.myRole,
|
||||||
|
hasCanAccept: 'canAccept' in matchInfo,
|
||||||
|
canAccept: matchInfo.canAccept,
|
||||||
|
canAcceptType: typeof matchInfo.canAccept,
|
||||||
|
hasCanReject: 'canReject' in matchInfo,
|
||||||
|
canReject: matchInfo.canReject,
|
||||||
|
canRejectType: typeof matchInfo.canReject,
|
||||||
|
status: matchInfo.status,
|
||||||
|
statusType: typeof matchInfo.status
|
||||||
|
})
|
||||||
|
|
||||||
|
// 确保布尔值正确设置
|
||||||
|
let canAccept = Boolean(matchInfo.canAccept)
|
||||||
|
let canReject = Boolean(matchInfo.canReject)
|
||||||
|
let canSubmitScore = Boolean(matchInfo.canSubmitScore)
|
||||||
|
let canConfirmScore = Boolean(matchInfo.canConfirmScore)
|
||||||
|
let myRole = matchInfo.myRole || null
|
||||||
|
|
||||||
|
// 临时方案:如果 myRole 为 null,尝试通过其他方式判断角色
|
||||||
|
// 适用于待接受状态(status=0)和进行中状态(status=1)
|
||||||
|
if (!myRole && (matchInfo.status === 0 || matchInfo.status === 1)) {
|
||||||
|
const currentUser = app.globalData.userInfo
|
||||||
|
console.log('尝试临时方案识别角色:', {
|
||||||
|
status: matchInfo.status,
|
||||||
|
currentUser: currentUser ? { id: currentUser.id, phone: currentUser.phone } : null,
|
||||||
|
defender: matchInfo.defender ? { userId: matchInfo.defender.userId, phone: matchInfo.defender.phone } : null,
|
||||||
|
challenger: matchInfo.challenger ? { userId: matchInfo.challenger.userId, phone: matchInfo.challenger.phone } : null
|
||||||
|
})
|
||||||
|
|
||||||
|
if (currentUser) {
|
||||||
|
// 尝试通过 user_id 判断
|
||||||
|
if (matchInfo.defender && matchInfo.defender.userId && matchInfo.defender.userId == currentUser.id) {
|
||||||
|
myRole = 'defender'
|
||||||
|
console.log('临时方案:通过defender.userId识别为被挑战者', {
|
||||||
|
defenderUserId: matchInfo.defender.userId,
|
||||||
|
currentUserId: currentUser.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 尝试通过手机号判断
|
||||||
|
else if (matchInfo.defender && currentUser.phone && matchInfo.defender.phone && matchInfo.defender.phone === currentUser.phone) {
|
||||||
|
myRole = 'defender'
|
||||||
|
console.log('临时方案:通过手机号识别为被挑战者', {
|
||||||
|
defenderPhone: matchInfo.defender.phone,
|
||||||
|
currentUserPhone: currentUser.phone
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 如果都不匹配,检查是否是挑战者
|
||||||
|
else if (matchInfo.challenger && matchInfo.challenger.userId && matchInfo.challenger.userId == currentUser.id) {
|
||||||
|
myRole = 'challenger'
|
||||||
|
console.log('临时方案:识别为挑战者(通过userId)')
|
||||||
|
}
|
||||||
|
// 通过手机号识别挑战者
|
||||||
|
else if (matchInfo.challenger && currentUser.phone && matchInfo.challenger.phone && matchInfo.challenger.phone === currentUser.phone) {
|
||||||
|
myRole = 'challenger'
|
||||||
|
console.log('临时方案:识别为挑战者(通过手机号)')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果状态是待接受且角色是被挑战者,强制设置权限(即使后端没有返回)
|
||||||
|
if (matchInfo.status === 0 && myRole === 'defender') {
|
||||||
|
canAccept = true
|
||||||
|
canReject = true
|
||||||
|
console.log('强制设置接受/拒绝权限(状态=0,角色=defender)')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最后的备用方案:如果状态是待接受,且当前用户不是挑战者,则可能是被挑战者
|
||||||
|
// 这种情况下,显示按钮让用户尝试接受/拒绝(如果用户不是被挑战者,后端会拒绝)
|
||||||
|
if (matchInfo.status === 0 && !myRole && !canAccept && !canReject) {
|
||||||
|
// 检查当前用户是否是挑战者
|
||||||
|
const currentUser = app.globalData.userInfo
|
||||||
|
const isChallenger = currentUser && matchInfo.challenger &&
|
||||||
|
(matchInfo.challenger.userId == currentUser.id ||
|
||||||
|
(currentUser.phone && matchInfo.challenger.phone === currentUser.phone))
|
||||||
|
|
||||||
|
// 如果不是挑战者,可能是被挑战者,显示按钮
|
||||||
|
if (!isChallenger) {
|
||||||
|
myRole = 'defender'
|
||||||
|
canAccept = true
|
||||||
|
canReject = true
|
||||||
|
console.log('备用方案:状态为待接受且不是挑战者,假设是被挑战者,显示按钮')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理"进行中"状态(status=1)的操作权限
|
||||||
|
if (matchInfo.status === 1) {
|
||||||
|
const game = matchInfo.games && matchInfo.games[0]
|
||||||
|
if (game) {
|
||||||
|
console.log('处理进行中状态的操作权限:', {
|
||||||
|
gameStatus: game.status,
|
||||||
|
submitBy: game.submitBy,
|
||||||
|
confirmStatus: game.confirmStatus,
|
||||||
|
myRole,
|
||||||
|
canSubmitScore,
|
||||||
|
canConfirmScore
|
||||||
|
})
|
||||||
|
|
||||||
|
// 如果游戏状态为1(进行中)且未提交比分,双方都可以填写比分
|
||||||
|
if (game.status === 1 && !game.submitBy) {
|
||||||
|
// 如果后端没有返回 canSubmitScore,但状态允许,则设置权限
|
||||||
|
// 即使 myRole 为 null,也允许填写(后续会通过游戏信息识别角色)
|
||||||
|
if (!canSubmitScore) {
|
||||||
|
canSubmitScore = true
|
||||||
|
console.log('进行中状态:设置填写比分权限(游戏状态=1,未提交)')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果游戏状态为2(已提交)且对方已提交,等待我确认
|
||||||
|
else if (game.status === 2 && game.submitBy) {
|
||||||
|
// 判断当前用户是否是提交者(submitBy 是 ladder_user_id)
|
||||||
|
let isSubmitter = false
|
||||||
|
|
||||||
|
if (myRole) {
|
||||||
|
// 如果已识别角色,通过比较 submitBy 和 challenger/defender 的 id(ladder_user_id)来判断
|
||||||
|
if (myRole === 'challenger' && matchInfo.challenger && game.submitBy == matchInfo.challenger.id) {
|
||||||
|
isSubmitter = true
|
||||||
|
console.log('当前用户是提交者(挑战者)')
|
||||||
|
} else if (myRole === 'defender' && matchInfo.defender && game.submitBy == matchInfo.defender.id) {
|
||||||
|
isSubmitter = true
|
||||||
|
console.log('当前用户是提交者(被挑战者)')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果 myRole 为 null,通过比较当前用户的 user_id 和 challenger/defender 的 user_id 来判断
|
||||||
|
const currentUser = app.globalData.userInfo
|
||||||
|
if (currentUser) {
|
||||||
|
// 检查提交者是否是挑战者
|
||||||
|
if (matchInfo.challenger && game.submitBy == matchInfo.challenger.id &&
|
||||||
|
currentUser.id == matchInfo.challenger.userId) {
|
||||||
|
isSubmitter = true
|
||||||
|
console.log('当前用户是提交者(通过challenger判断)')
|
||||||
|
}
|
||||||
|
// 检查提交者是否是被挑战者
|
||||||
|
else if (matchInfo.defender && game.submitBy == matchInfo.defender.id &&
|
||||||
|
currentUser.id == matchInfo.defender.userId) {
|
||||||
|
isSubmitter = true
|
||||||
|
console.log('当前用户是提交者(通过defender判断)')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果不是提交者,且确认状态为待确认,则可以确认比分
|
||||||
|
if (!isSubmitter && game.confirmStatus === 0) {
|
||||||
|
if (!canConfirmScore) {
|
||||||
|
canConfirmScore = true
|
||||||
|
console.log('进行中状态:设置确认比分权限(对方已提交,等待确认)', {
|
||||||
|
submitBy: game.submitBy,
|
||||||
|
challengerId: matchInfo.challenger?.id,
|
||||||
|
defenderId: matchInfo.defender?.id,
|
||||||
|
myRole
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果 myRole 仍然为 null,但状态是进行中,尝试通过游戏中的 player1_id 和 player2_id 判断
|
||||||
|
if (!myRole && game) {
|
||||||
|
const currentUser = app.globalData.userInfo
|
||||||
|
if (currentUser) {
|
||||||
|
console.log('尝试通过游戏信息识别角色:', {
|
||||||
|
player1Id: game.player1Id,
|
||||||
|
player2Id: game.player2Id,
|
||||||
|
challengerId: matchInfo.challenger?.id,
|
||||||
|
defenderId: matchInfo.defender?.id,
|
||||||
|
currentUserId: currentUser.id,
|
||||||
|
challengerUserId: matchInfo.challenger?.userId,
|
||||||
|
defenderUserId: matchInfo.defender?.userId
|
||||||
|
})
|
||||||
|
|
||||||
|
// 通过比较 challenger/defender 的 id(ladder_user_id)和 player1_id/player2_id 来判断
|
||||||
|
if (matchInfo.challenger && matchInfo.challenger.id == game.player1Id) {
|
||||||
|
// 如果当前用户是挑战者,且挑战者是 player1
|
||||||
|
if (currentUser.id == matchInfo.challenger.userId) {
|
||||||
|
myRole = 'challenger'
|
||||||
|
console.log('通过游戏player1Id识别为挑战者')
|
||||||
|
}
|
||||||
|
} else if (matchInfo.challenger && matchInfo.challenger.id == game.player2Id) {
|
||||||
|
// 如果当前用户是挑战者,且挑战者是 player2
|
||||||
|
if (currentUser.id == matchInfo.challenger.userId) {
|
||||||
|
myRole = 'challenger'
|
||||||
|
console.log('通过游戏player2Id识别为挑战者')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchInfo.defender && matchInfo.defender.id == game.player1Id) {
|
||||||
|
// 如果当前用户是被挑战者,且被挑战者是 player1
|
||||||
|
if (currentUser.id == matchInfo.defender.userId) {
|
||||||
|
myRole = 'defender'
|
||||||
|
console.log('通过游戏player1Id识别为被挑战者')
|
||||||
|
}
|
||||||
|
} else if (matchInfo.defender && matchInfo.defender.id == game.player2Id) {
|
||||||
|
// 如果当前用户是被挑战者,且被挑战者是 player2
|
||||||
|
if (currentUser.id == matchInfo.defender.userId) {
|
||||||
|
myRole = 'defender'
|
||||||
|
console.log('通过游戏player2Id识别为被挑战者')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果识别到角色,重新检查操作权限
|
||||||
|
if (myRole) {
|
||||||
|
console.log('识别到角色后,重新检查操作权限:', { myRole, gameStatus: game.status, submitBy: game.submitBy })
|
||||||
|
|
||||||
|
// 如果游戏状态为1(进行中)且未提交比分,可以填写比分
|
||||||
|
if (game.status === 1 && !game.submitBy) {
|
||||||
|
canSubmitScore = true
|
||||||
|
console.log('识别角色后,设置填写比分权限')
|
||||||
|
}
|
||||||
|
// 如果游戏状态为2(已提交)且对方已提交,等待我确认
|
||||||
|
else if (game.status === 2 && game.submitBy) {
|
||||||
|
// 判断当前用户是否是提交者
|
||||||
|
const isSubmitter = (myRole === 'challenger' && game.submitBy == matchInfo.challenger?.id) ||
|
||||||
|
(myRole === 'defender' && game.submitBy == matchInfo.defender?.id)
|
||||||
|
|
||||||
|
if (!isSubmitter && game.confirmStatus === 0) {
|
||||||
|
canConfirmScore = true
|
||||||
|
console.log('识别角色后,设置确认比分权限')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('最终设置的操作权限:', {
|
||||||
|
canAccept,
|
||||||
|
canReject,
|
||||||
|
canSubmitScore,
|
||||||
|
canConfirmScore,
|
||||||
|
myRole,
|
||||||
|
status: matchInfo.status,
|
||||||
|
defenderInfo: matchInfo.defender ? {
|
||||||
|
id: matchInfo.defender.id,
|
||||||
|
userId: matchInfo.defender.userId,
|
||||||
|
phone: matchInfo.defender.phone,
|
||||||
|
realName: matchInfo.defender.realName
|
||||||
|
} : null,
|
||||||
|
challengerInfo: matchInfo.challenger ? {
|
||||||
|
id: matchInfo.challenger.id,
|
||||||
|
userId: matchInfo.challenger.userId,
|
||||||
|
phone: matchInfo.challenger.phone,
|
||||||
|
realName: matchInfo.challenger.realName
|
||||||
|
} : null,
|
||||||
|
currentUser: app.globalData.userInfo ? {
|
||||||
|
id: app.globalData.userInfo.id,
|
||||||
|
phone: app.globalData.userInfo.phone
|
||||||
|
} : null
|
||||||
|
})
|
||||||
|
|
||||||
|
this.setData({
|
||||||
|
matchInfo,
|
||||||
|
myRole,
|
||||||
|
canAccept,
|
||||||
|
canReject,
|
||||||
|
canSubmitScore,
|
||||||
|
canConfirmScore,
|
||||||
|
loading: false
|
||||||
|
})
|
||||||
|
|
||||||
|
// 再次检查,如果还是没有按钮,输出详细日志
|
||||||
|
if (matchInfo.status === 0 && !canAccept && !canReject) {
|
||||||
|
console.error('警告:状态为待接受但没有操作按钮!', {
|
||||||
|
myRole,
|
||||||
|
canAccept,
|
||||||
|
canReject,
|
||||||
|
matchInfoStatus: matchInfo.status,
|
||||||
|
defenderUserId: matchInfo.defender?.userId,
|
||||||
|
currentUserId: app.globalData.userInfo?.id,
|
||||||
|
defenderPhone: matchInfo.defender?.phone,
|
||||||
|
currentUserPhone: app.globalData.userInfo?.phone
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.setData({ loading: false })
|
||||||
|
console.error('加载比赛详情失败:', e)
|
||||||
|
console.error('错误详情:', e.message, e.data, e)
|
||||||
|
wx.showToast({ title: '加载失败: ' + (e.message || '未知错误'), icon: 'none', duration: 3000 })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 处理挑战请求(从WebSocket调用)
|
||||||
|
handleChallengeRequest(challengeData) {
|
||||||
|
// 如果当前页面是挑战赛详情且是同一个比赛,显示弹框
|
||||||
|
if (this.data.matchId == challengeData.matchId) {
|
||||||
|
this.showChallengeModal(challengeData)
|
||||||
|
} else {
|
||||||
|
// 否则跳转到挑战赛详情页面
|
||||||
|
wx.navigateTo({
|
||||||
|
url: `/pages/match/challenge-detail/index?id=${challengeData.matchId}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 显示挑战弹框
|
||||||
|
showChallengeModal(challengeData) {
|
||||||
|
wx.showModal({
|
||||||
|
title: '收到挑战',
|
||||||
|
content: `${challengeData.challenger.realName}(Lv${challengeData.challenger.level}, 战力${challengeData.challenger.powerScore}) 向你发起挑战`,
|
||||||
|
confirmText: '接受',
|
||||||
|
cancelText: '拒绝',
|
||||||
|
success: (res) => {
|
||||||
|
this.respondChallenge(res.confirm)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 响应挑战
|
||||||
|
async respondChallenge(accept) {
|
||||||
|
wx.showLoading({ title: accept ? '接受中...' : '拒绝中...' })
|
||||||
|
try {
|
||||||
|
await app.request('/api/match/challenge/respond', {
|
||||||
|
match_id: this.data.matchId,
|
||||||
|
accept: accept
|
||||||
|
}, 'POST')
|
||||||
|
|
||||||
|
wx.hideLoading()
|
||||||
|
wx.showToast({
|
||||||
|
title: accept ? '已接受挑战' : '已拒绝挑战',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 刷新数据
|
||||||
|
setTimeout(() => {
|
||||||
|
this.loadMatchDetail()
|
||||||
|
}, 1500)
|
||||||
|
} catch (e) {
|
||||||
|
wx.hideLoading()
|
||||||
|
console.error('响应挑战失败:', e)
|
||||||
|
wx.showToast({ title: '操作失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 接受挑战
|
||||||
|
acceptChallenge() {
|
||||||
|
this.respondChallenge(true)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 拒绝挑战
|
||||||
|
rejectChallenge() {
|
||||||
|
wx.showModal({
|
||||||
|
title: '确认拒绝',
|
||||||
|
content: '确定要拒绝这个挑战吗?',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
this.respondChallenge(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 打开填写比分弹框
|
||||||
|
openScoreModal() {
|
||||||
|
this.setData({
|
||||||
|
showScoreModal: true,
|
||||||
|
myScore: '',
|
||||||
|
opponentScore: ''
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 关闭填写比分弹框
|
||||||
|
closeScoreModal() {
|
||||||
|
this.setData({ showScoreModal: false })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 输入我的比分
|
||||||
|
onMyScoreInput(e) {
|
||||||
|
this.setData({ myScore: e.detail.value })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 输入对手比分
|
||||||
|
onOpponentScoreInput(e) {
|
||||||
|
this.setData({ opponentScore: e.detail.value })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 提交比分
|
||||||
|
async submitScore() {
|
||||||
|
const { myScore, opponentScore } = this.data
|
||||||
|
if (!myScore || !opponentScore) {
|
||||||
|
wx.showToast({ title: '请填写完整比分', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const myScoreNum = parseInt(myScore)
|
||||||
|
const opponentScoreNum = parseInt(opponentScore)
|
||||||
|
|
||||||
|
if (isNaN(myScoreNum) || isNaN(opponentScoreNum)) {
|
||||||
|
wx.showToast({ title: '请输入有效数字', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (myScoreNum === opponentScoreNum) {
|
||||||
|
wx.showToast({ title: '比分不能相同', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wx.showLoading({ title: '提交中...' })
|
||||||
|
try {
|
||||||
|
await app.request('/api/match/challenge/submit-score', {
|
||||||
|
match_id: this.data.matchId,
|
||||||
|
my_score: myScoreNum,
|
||||||
|
opponent_score: opponentScoreNum
|
||||||
|
}, 'POST')
|
||||||
|
|
||||||
|
wx.hideLoading()
|
||||||
|
wx.showToast({ title: '比分已提交,等待对方确认', icon: 'success' })
|
||||||
|
this.closeScoreModal()
|
||||||
|
|
||||||
|
// 刷新数据
|
||||||
|
setTimeout(() => {
|
||||||
|
this.loadMatchDetail()
|
||||||
|
}, 1500)
|
||||||
|
} catch (e) {
|
||||||
|
wx.hideLoading()
|
||||||
|
console.error('提交比分失败:', e)
|
||||||
|
wx.showToast({ title: e.message || '提交失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 确认比分
|
||||||
|
async confirmScore(confirm) {
|
||||||
|
const game = this.data.matchInfo.games?.[0]
|
||||||
|
if (!game) {
|
||||||
|
wx.showToast({ title: '比赛信息错误', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wx.showLoading({ title: '处理中...' })
|
||||||
|
try {
|
||||||
|
await app.request('/api/match/challenge/confirm-score', {
|
||||||
|
game_id: game.id,
|
||||||
|
confirm: confirm
|
||||||
|
}, 'POST')
|
||||||
|
|
||||||
|
wx.hideLoading()
|
||||||
|
wx.showToast({
|
||||||
|
title: confirm ? '已确认比分' : '已标记争议',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 刷新数据
|
||||||
|
setTimeout(() => {
|
||||||
|
this.loadMatchDetail()
|
||||||
|
}, 1500)
|
||||||
|
} catch (e) {
|
||||||
|
wx.hideLoading()
|
||||||
|
console.error('确认比分失败:', e)
|
||||||
|
wx.showToast({ title: '操作失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 确认比分按钮
|
||||||
|
confirmScoreBtn() {
|
||||||
|
const game = this.data.matchInfo.games?.[0]
|
||||||
|
if (!game) {
|
||||||
|
wx.showToast({ title: '比赛信息错误', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据当前用户角色显示正确的比分信息
|
||||||
|
let myScore = 0
|
||||||
|
let opponentScore = 0
|
||||||
|
let myName = ''
|
||||||
|
let opponentName = ''
|
||||||
|
|
||||||
|
if (this.data.myRole === 'challenger') {
|
||||||
|
// 挑战者是 player1 还是 player2?
|
||||||
|
if (this.data.matchInfo.challenger && this.data.matchInfo.challenger.id == game.player1Id) {
|
||||||
|
myScore = game.player1Score || 0
|
||||||
|
opponentScore = game.player2Score || 0
|
||||||
|
myName = this.data.matchInfo.challenger.realName || '挑战者'
|
||||||
|
opponentName = this.data.matchInfo.defender?.realName || '被挑战者'
|
||||||
|
} else if (this.data.matchInfo.challenger && this.data.matchInfo.challenger.id == game.player2Id) {
|
||||||
|
myScore = game.player2Score || 0
|
||||||
|
opponentScore = game.player1Score || 0
|
||||||
|
myName = this.data.matchInfo.challenger.realName || '挑战者'
|
||||||
|
opponentName = this.data.matchInfo.defender?.realName || '被挑战者'
|
||||||
|
} else {
|
||||||
|
// 如果无法确定,使用默认显示
|
||||||
|
myScore = game.player1Score || 0
|
||||||
|
opponentScore = game.player2Score || 0
|
||||||
|
}
|
||||||
|
} else if (this.data.myRole === 'defender') {
|
||||||
|
// 被挑战者是 player1 还是 player2?
|
||||||
|
if (this.data.matchInfo.defender && this.data.matchInfo.defender.id == game.player1Id) {
|
||||||
|
myScore = game.player1Score || 0
|
||||||
|
opponentScore = game.player2Score || 0
|
||||||
|
myName = this.data.matchInfo.defender.realName || '被挑战者'
|
||||||
|
opponentName = this.data.matchInfo.challenger?.realName || '挑战者'
|
||||||
|
} else if (this.data.matchInfo.defender && this.data.matchInfo.defender.id == game.player2Id) {
|
||||||
|
myScore = game.player2Score || 0
|
||||||
|
opponentScore = game.player1Score || 0
|
||||||
|
myName = this.data.matchInfo.defender.realName || '被挑战者'
|
||||||
|
opponentName = this.data.matchInfo.challenger?.realName || '挑战者'
|
||||||
|
} else {
|
||||||
|
// 如果无法确定,使用默认显示
|
||||||
|
myScore = game.player1Score || 0
|
||||||
|
opponentScore = game.player2Score || 0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果角色未知,使用默认显示
|
||||||
|
myScore = game.player1Score || 0
|
||||||
|
opponentScore = game.player2Score || 0
|
||||||
|
}
|
||||||
|
|
||||||
|
wx.showModal({
|
||||||
|
title: '确认比分',
|
||||||
|
content: `对方提交的比分为:\n${opponentName}: ${opponentScore}\n${myName}: ${myScore}\n\n请确认此比分是否正确?`,
|
||||||
|
confirmText: '确认',
|
||||||
|
cancelText: '有争议',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
this.confirmScore(true)
|
||||||
|
} else {
|
||||||
|
// 有争议
|
||||||
|
wx.showModal({
|
||||||
|
title: '确认争议',
|
||||||
|
content: '确定要标记为有争议吗?标记后需要重新比赛。',
|
||||||
|
confirmText: '确定',
|
||||||
|
cancelText: '取消',
|
||||||
|
success: (res2) => {
|
||||||
|
if (res2.confirm) {
|
||||||
|
this.confirmScore(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 阻止事件冒泡
|
||||||
|
stopPropagation() {
|
||||||
|
// 空函数,用于阻止事件冒泡
|
||||||
|
}
|
||||||
|
})
|
||||||
5
miniprogram/pages/match/challenge-detail/index.json
Normal file
5
miniprogram/pages/match/challenge-detail/index.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"navigationBarTitleText": "挑战赛详情",
|
||||||
|
"enablePullDownRefresh": true,
|
||||||
|
"backgroundColor": "#f5f7fa"
|
||||||
|
}
|
||||||
121
miniprogram/pages/match/challenge-detail/index.wxml
Normal file
121
miniprogram/pages/match/challenge-detail/index.wxml
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
<!--挑战赛详情页面-->
|
||||||
|
<view class="page-container" catchtap="stopPropagation">
|
||||||
|
<!-- 加载中 -->
|
||||||
|
<view class="loading-container" wx:if="{{loading}}">
|
||||||
|
<text>加载中...</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 比赛信息 -->
|
||||||
|
<view class="match-info" wx:if="{{!loading && matchInfo}}">
|
||||||
|
<!-- 比赛标题 -->
|
||||||
|
<view class="match-header">
|
||||||
|
<text class="match-title">{{matchInfo.name}}</text>
|
||||||
|
<view class="match-status status-{{matchInfo.status}}">
|
||||||
|
<text wx:if="{{matchInfo.status === 0}}">待接受</text>
|
||||||
|
<text wx:elif="{{matchInfo.status === 1}}">进行中</text>
|
||||||
|
<text wx:elif="{{matchInfo.status === 2}}">已结束</text>
|
||||||
|
<text wx:elif="{{matchInfo.status === 3}}">已取消</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 对手信息 -->
|
||||||
|
<view class="opponent-section">
|
||||||
|
<view class="opponent-card" wx:if="{{matchInfo.challenger}}">
|
||||||
|
<view class="opponent-label">挑战者</view>
|
||||||
|
<view class="opponent-info">
|
||||||
|
<image class="opponent-avatar" src="{{matchInfo.challenger.avatar || '/images/avatar-default.svg'}}" mode="aspectFill"></image>
|
||||||
|
<view class="opponent-details">
|
||||||
|
<text class="opponent-name">{{matchInfo.challenger.realName}}</text>
|
||||||
|
<text class="opponent-level">Lv{{matchInfo.challenger.level}} · 战力{{matchInfo.challenger.powerScore}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="vs-divider">
|
||||||
|
<text>VS</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="opponent-card" wx:if="{{matchInfo.defender}}">
|
||||||
|
<view class="opponent-label">被挑战者</view>
|
||||||
|
<view class="opponent-info">
|
||||||
|
<image class="opponent-avatar" src="{{matchInfo.defender.avatar || '/images/avatar-default.svg'}}" mode="aspectFill"></image>
|
||||||
|
<view class="opponent-details">
|
||||||
|
<text class="opponent-name">{{matchInfo.defender.realName}}</text>
|
||||||
|
<text class="opponent-level">Lv{{matchInfo.defender.level}} · 战力{{matchInfo.defender.powerScore}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 比赛进度 -->
|
||||||
|
<view class="match-progress" wx:if="{{matchInfo.games && matchInfo.games.length > 0}}">
|
||||||
|
<view class="progress-title">比赛进度</view>
|
||||||
|
<view class="game-item" wx:for="{{matchInfo.games}}" wx:key="id">
|
||||||
|
<view class="game-score">
|
||||||
|
<text class="score-label">比分:</text>
|
||||||
|
<text class="score-value">{{item.player1Score !== null && item.player1Score !== undefined ? item.player1Score : 0}} : {{item.player2Score !== null && item.player2Score !== undefined ? item.player2Score : 0}}</text>
|
||||||
|
</view>
|
||||||
|
<view class="game-status">
|
||||||
|
<text wx:if="{{item.status === 0}}">未开始</text>
|
||||||
|
<text wx:elif="{{item.status === 1}}">进行中</text>
|
||||||
|
<text wx:elif="{{item.status === 2 && item.confirmStatus === 0}}">等待确认</text>
|
||||||
|
<text wx:elif="{{item.status === 2 && item.confirmStatus === 1}}">已确认</text>
|
||||||
|
<text wx:elif="{{item.status === 2 && item.confirmStatus === 2}}">有争议</text>
|
||||||
|
</view>
|
||||||
|
<!-- 等待确认时显示提示信息 -->
|
||||||
|
<view class="confirm-tip" wx:if="{{item.status === 2 && item.confirmStatus === 0 && canConfirmScore}}">
|
||||||
|
<text class="tip-text">对方已提交比分,请确认</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<view class="action-buttons" wx:if="{{canAccept || canReject || canSubmitScore || canConfirmScore}}">
|
||||||
|
<!-- 待接受状态:显示接受/拒绝按钮 -->
|
||||||
|
<block wx:if="{{canAccept || canReject}}">
|
||||||
|
<button class="action-btn accept-btn" wx:if="{{canAccept}}" bindtap="acceptChallenge">接受挑战</button>
|
||||||
|
<button class="action-btn reject-btn" wx:if="{{canReject}}" bindtap="rejectChallenge">拒绝挑战</button>
|
||||||
|
</block>
|
||||||
|
|
||||||
|
<!-- 进行中状态:显示填写比分按钮 -->
|
||||||
|
<button class="action-btn submit-btn" wx:if="{{canSubmitScore}}" bindtap="openScoreModal">填写比分</button>
|
||||||
|
|
||||||
|
<!-- 等待确认状态:显示确认比分按钮 -->
|
||||||
|
<button class="action-btn confirm-btn" wx:if="{{canConfirmScore}}" bindtap="confirmScoreBtn">确认比分</button>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 无操作权限提示 -->
|
||||||
|
<view class="no-action-tip" wx:if="{{!canAccept && !canReject && !canSubmitScore && !canConfirmScore && matchInfo.status === 0 && myRole === 'challenger'}}">
|
||||||
|
<text class="tip-text">等待对方接受挑战...</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 被挑战者但无按钮时的提示 -->
|
||||||
|
<view class="no-action-tip" wx:if="{{!canAccept && !canReject && !canSubmitScore && !canConfirmScore && matchInfo.status === 0 && myRole === 'defender'}}">
|
||||||
|
<text class="tip-text">无法操作,请联系管理员</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 填写比分弹框 -->
|
||||||
|
<view class="score-modal" wx:if="{{showScoreModal}}" bindtap="closeScoreModal">
|
||||||
|
<view class="score-modal-content" catchtap="stopPropagation">
|
||||||
|
<view class="modal-header">
|
||||||
|
<text class="modal-title">填写比分</text>
|
||||||
|
<text class="modal-close" bindtap="closeScoreModal">×</text>
|
||||||
|
</view>
|
||||||
|
<view class="modal-body">
|
||||||
|
<view class="score-input-group">
|
||||||
|
<text class="input-label">我的比分</text>
|
||||||
|
<input class="score-input" type="number" placeholder="请输入比分" value="{{myScore}}" bindinput="onMyScoreInput" />
|
||||||
|
</view>
|
||||||
|
<view class="score-input-group">
|
||||||
|
<text class="input-label">对手比分</text>
|
||||||
|
<input class="score-input" type="number" placeholder="请输入比分" value="{{opponentScore}}" bindinput="onOpponentScoreInput" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="modal-footer">
|
||||||
|
<button class="modal-btn cancel-btn" bindtap="closeScoreModal">取消</button>
|
||||||
|
<button class="modal-btn submit-btn" bindtap="submitScore">提交</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
383
miniprogram/pages/match/challenge-detail/index.wxss
Normal file
383
miniprogram/pages/match/challenge-detail/index.wxss
Normal file
@ -0,0 +1,383 @@
|
|||||||
|
.page-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 60vh;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.match-info {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
padding: 40rpx;
|
||||||
|
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.match-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
padding-bottom: 30rpx;
|
||||||
|
border-bottom: 2rpx solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.match-title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.match-status {
|
||||||
|
padding: 8rpx 20rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-0 {
|
||||||
|
background: #fff3cd;
|
||||||
|
color: #856404;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-1 {
|
||||||
|
background: #d1ecf1;
|
||||||
|
color: #0c5460;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-2 {
|
||||||
|
background: #d4edda;
|
||||||
|
color: #155724;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-3 {
|
||||||
|
background: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opponent-section {
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opponent-card {
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opponent-label {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opponent-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opponent-avatar {
|
||||||
|
width: 100rpx;
|
||||||
|
height: 100rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 4rpx solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opponent-details {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opponent-name {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opponent-level {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vs-divider {
|
||||||
|
text-align: center;
|
||||||
|
margin: 30rpx 0;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.match-progress {
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
padding-top: 30rpx;
|
||||||
|
border-top: 2rpx solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-item {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-score {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-label {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-value {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-left: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-status {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-tip {
|
||||||
|
margin-top: 16rpx;
|
||||||
|
padding: 16rpx 20rpx;
|
||||||
|
background: linear-gradient(135deg, #fff5f0 0%, #ffe8d6 100%);
|
||||||
|
border-radius: 12rpx;
|
||||||
|
border-left: 4rpx solid #ff6b35;
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(255, 107, 53, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-tip .tip-text {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #d84315;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 20rpx;
|
||||||
|
margin-top: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-action-tip {
|
||||||
|
margin-top: 40rpx;
|
||||||
|
padding: 30rpx;
|
||||||
|
text-align: center;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
flex: 1;
|
||||||
|
height: 88rpx;
|
||||||
|
border-radius: 44rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
border: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accept-btn {
|
||||||
|
background: linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%);
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(255, 107, 53, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.accept-btn:active {
|
||||||
|
background: linear-gradient(135deg, #e55a2b 0%, #e67e2f 100%);
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(255, 107, 53, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.reject-btn {
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
background: linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%);
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(255, 107, 53, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn:active {
|
||||||
|
background: linear-gradient(135deg, #e55a2b 0%, #e67e2f 100%);
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(255, 107, 53, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-btn {
|
||||||
|
background: linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%);
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(255, 107, 53, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-btn:active {
|
||||||
|
background: linear-gradient(135deg, #e55a2b 0%, #e67e2f 100%);
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(255, 107, 53, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 填写比分弹框 */
|
||||||
|
.score-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-modal-content {
|
||||||
|
width: 600rpx;
|
||||||
|
max-width: 90%;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 32rpx 40rpx;
|
||||||
|
border-bottom: 2rpx solid rgba(255, 255, 255, 0.2);
|
||||||
|
background: linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header .modal-title {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header .modal-close {
|
||||||
|
color: #fff;
|
||||||
|
opacity: 0.9;
|
||||||
|
font-size: 40rpx;
|
||||||
|
width: 48rpx;
|
||||||
|
height: 48rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header .modal-close:active {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-input-group {
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-input-group:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-input {
|
||||||
|
width: 100%;
|
||||||
|
height: 88rpx;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #333;
|
||||||
|
border: 2rpx solid #e0e0e0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-input:focus {
|
||||||
|
border-color: #ff6b35;
|
||||||
|
background: #fff5f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
gap: 20rpx;
|
||||||
|
padding: 32rpx 40rpx;
|
||||||
|
border-top: 2rpx solid #f0f0f0;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn {
|
||||||
|
flex: 1;
|
||||||
|
height: 88rpx;
|
||||||
|
border-radius: 44rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
border: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
background: #fff;
|
||||||
|
color: #666;
|
||||||
|
border: 2rpx solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn:active {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn.submit-btn {
|
||||||
|
background: linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%);
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(255, 107, 53, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn.submit-btn:active {
|
||||||
|
background: linear-gradient(135deg, #e55a2b 0%, #e67e2f 100%);
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(255, 107, 53, 0.4);
|
||||||
|
}
|
||||||
@ -172,16 +172,27 @@ Page({
|
|||||||
wx.showLoading({ title: '发起挑战中...' })
|
wx.showLoading({ title: '发起挑战中...' })
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await app.request('/api/match/challenge/create', {
|
const res = await app.request('/api/match/challenge/create', {
|
||||||
store_id: this.data.currentStore.storeId,
|
store_id: this.data.currentStore.storeId,
|
||||||
target_member_code: memberCode
|
target_member_code: memberCode
|
||||||
}, 'POST')
|
}, 'POST')
|
||||||
|
|
||||||
wx.hideLoading()
|
wx.hideLoading()
|
||||||
wx.showToast({ title: '挑战已发起', icon: 'success' })
|
wx.showToast({ title: '挑战已发起', icon: 'success' })
|
||||||
|
|
||||||
|
// 跳转到挑战赛详情页面
|
||||||
|
if (res.data && res.data.matchId) {
|
||||||
|
setTimeout(() => {
|
||||||
|
wx.navigateTo({
|
||||||
|
url: `/pages/match/challenge-detail/index?id=${res.data.matchId}`
|
||||||
|
})
|
||||||
|
}, 1500)
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
wx.hideLoading()
|
wx.hideLoading()
|
||||||
console.error('发起挑战失败:', e)
|
console.error('发起挑战失败:', e)
|
||||||
|
const errorMsg = e.message || e.data?.message || '发起挑战失败'
|
||||||
|
wx.showToast({ title: errorMsg, icon: 'none', duration: 2000 })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -231,9 +242,9 @@ Page({
|
|||||||
goToMatchDetail(e) {
|
goToMatchDetail(e) {
|
||||||
const match = e.currentTarget.dataset.match
|
const match = e.currentTarget.dataset.match
|
||||||
if (match.type === 1) {
|
if (match.type === 1) {
|
||||||
// 挑战赛详情 - 暂时跳转到历史记录页
|
// 挑战赛详情
|
||||||
wx.navigateTo({
|
wx.navigateTo({
|
||||||
url: `/pages/match/history/index`
|
url: `/pages/match/challenge-detail/index?id=${match.id}`
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// 排位赛详情
|
// 排位赛详情
|
||||||
|
|||||||
@ -16,32 +16,17 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: 420rpx;
|
height: 420rpx;
|
||||||
background: linear-gradient(135deg, #FF8A65 0%, #FF6B35 50%, #F4511E 100%);
|
background: transparent;
|
||||||
border-radius: 0 0 60rpx 60rpx;
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-bg::before {
|
.hero-bg::before {
|
||||||
content: '';
|
display: none;
|
||||||
position: absolute;
|
|
||||||
top: -100rpx;
|
|
||||||
right: -80rpx;
|
|
||||||
width: 300rpx;
|
|
||||||
height: 300rpx;
|
|
||||||
background: rgba(255, 255, 255, 0.15);
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-bg::after {
|
.hero-bg::after {
|
||||||
content: '';
|
display: none;
|
||||||
position: absolute;
|
|
||||||
bottom: -60rpx;
|
|
||||||
left: -60rpx;
|
|
||||||
width: 200rpx;
|
|
||||||
height: 200rpx;
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-pattern {
|
.hero-pattern {
|
||||||
@ -70,23 +55,25 @@
|
|||||||
/* 页面标题 */
|
/* 页面标题 */
|
||||||
.page-header {
|
.page-header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 20rpx 0 30rpx;
|
padding: 32rpx 0 40rpx;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-title {
|
.page-title {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 48rpx;
|
font-size: 52rpx;
|
||||||
font-weight: 800;
|
font-weight: 700;
|
||||||
color: #fff;
|
color: #1a1a1a;
|
||||||
margin-bottom: 10rpx;
|
margin-bottom: 12rpx;
|
||||||
text-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.15);
|
letter-spacing: 1rpx;
|
||||||
letter-spacing: 4rpx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-subtitle {
|
.page-subtitle {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
color: rgba(255, 255, 255, 0.9);
|
color: #999;
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: 0.5rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 当前门店栏 */
|
/* 当前门店栏 */
|
||||||
@ -95,13 +82,13 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 10rpx;
|
gap: 10rpx;
|
||||||
padding: 16rpx 24rpx;
|
padding: 20rpx 24rpx;
|
||||||
background: rgba(255, 255, 255, 0.95);
|
background: var(--bg-white);
|
||||||
border-radius: 50rpx;
|
border-radius: 16rpx;
|
||||||
box-shadow: 0 8rpx 24rpx rgba(255, 107, 53, 0.2);
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||||
margin: 0 auto 24rpx;
|
margin: 0 auto 24rpx;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
backdrop-filter: blur(10px);
|
border: 1rpx solid rgba(0, 0, 0, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
.store-icon {
|
.store-icon {
|
||||||
|
|||||||
@ -51,10 +51,31 @@ Page({
|
|||||||
pageSize: this.data.pageSize
|
pageSize: this.data.pageSize
|
||||||
})
|
})
|
||||||
|
|
||||||
const matches = (res.data.list || []).map(match => ({
|
const matches = (res.data.list || []).map(match => {
|
||||||
...match,
|
// 确保 powerChange 是数字类型,移除可能存在的加号和其他非数字字符
|
||||||
confirmedAt: util.formatDate(match.confirmedAt)
|
let powerChange = match.powerChange
|
||||||
}))
|
if (powerChange != null && powerChange !== undefined) {
|
||||||
|
// 如果是字符串,移除所有加号、空格等非数字字符(保留负号)
|
||||||
|
if (typeof powerChange === 'string') {
|
||||||
|
// 保留负号,移除所有加号和其他字符
|
||||||
|
const cleaned = powerChange.replace(/\+/g, '').trim()
|
||||||
|
powerChange = parseFloat(cleaned) || 0
|
||||||
|
}
|
||||||
|
// 确保是数字类型
|
||||||
|
powerChange = Number(powerChange)
|
||||||
|
// 如果是 NaN,设为 0
|
||||||
|
if (isNaN(powerChange)) {
|
||||||
|
powerChange = 0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
powerChange = 0
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...match,
|
||||||
|
powerChange: powerChange,
|
||||||
|
confirmedAt: util.formatDate(match.confirmedAt)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
this.setData({
|
this.setData({
|
||||||
matches: this.data.page === 1 ? matches : [...this.data.matches, ...matches],
|
matches: this.data.page === 1 ? matches : [...this.data.matches, ...matches],
|
||||||
|
|||||||
@ -7,15 +7,17 @@
|
|||||||
<text class="match-type">{{item.matchType === 1 ? '挑战赛' : '排位赛'}}</text>
|
<text class="match-type">{{item.matchType === 1 ? '挑战赛' : '排位赛'}}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="match-content">
|
<view class="match-content">
|
||||||
<view class="result {{item.isWin ? 'win' : 'lose'}}">
|
<view class="content-row">
|
||||||
{{item.isWin ? '胜' : '负'}}
|
<view class="result {{item.isWin ? 'win' : 'lose'}}">
|
||||||
</view>
|
{{item.isWin ? '胜' : '负'}}
|
||||||
<view class="score-info">
|
</view>
|
||||||
<text class="opponent">vs {{item.opponentName}}</text>
|
<view class="score-info">
|
||||||
<text class="score">{{item.myScore}} : {{item.opponentScore}}</text>
|
<text class="opponent">vs {{item.opponentName}}</text>
|
||||||
</view>
|
<text class="score">{{item.myScore}} : {{item.opponentScore}}</text>
|
||||||
<view class="power-change {{item.powerChange > 0 ? 'positive' : 'negative'}}">
|
</view>
|
||||||
{{item.powerChange > 0 ? '+' : ''}}{{item.powerChange}}
|
<view class="power-change {{item.powerChange > 0 ? 'positive' : 'negative'}}">
|
||||||
|
<text wx:if="{{item.powerChange > 0}}">+</text><text>{{item.powerChange}}</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="match-footer">
|
<view class="match-footer">
|
||||||
|
|||||||
@ -1,364 +1,166 @@
|
|||||||
/* ==========================================
|
/* ==========================================
|
||||||
比赛记录页面 - 浅色高级感设计
|
比赛记录页面 - 橙色主题优化
|
||||||
========================================== */
|
========================================== */
|
||||||
|
|
||||||
.page-container {
|
.container {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: var(--bg-page);
|
background: #f5f5f5;
|
||||||
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;
|
|
||||||
gap: 16rpx;
|
|
||||||
padding: 0 24rpx 24rpx;
|
|
||||||
animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s backwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-item {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 20rpx;
|
padding: 20rpx;
|
||||||
background: var(--bg-white);
|
}
|
||||||
border-radius: var(--radius-full);
|
|
||||||
box-shadow: var(--shadow-sm);
|
.history-list {
|
||||||
font-size: 26rpx;
|
display: flex;
|
||||||
color: var(--text-secondary);
|
flex-direction: column;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.match-item {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-item.active {
|
.match-item:active {
|
||||||
background: var(--primary-gradient);
|
|
||||||
color: var(--text-white);
|
|
||||||
box-shadow: var(--shadow-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-item:active {
|
|
||||||
transform: scale(0.96);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 比赛列表 */
|
|
||||||
.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);
|
transform: scale(0.98);
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
.match-card.win {
|
|
||||||
border-left: 6rpx solid var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.match-card.lose {
|
|
||||||
border-left: 6rpx solid #FF6B6B;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 比赛头部 */
|
|
||||||
.match-header {
|
.match-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 20rpx 24rpx;
|
align-items: center;
|
||||||
background: linear-gradient(90deg, #FAFBFC, var(--bg-white));
|
padding: 24rpx 28rpx;
|
||||||
border-bottom: 1rpx solid var(--border-soft);
|
background: linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%);
|
||||||
|
border-bottom: 2rpx solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.match-name {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #fff;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.match-type {
|
.match-type {
|
||||||
|
padding: 6rpx 16rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.25);
|
||||||
|
border-radius: 20rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.match-content {
|
||||||
|
padding: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result {
|
||||||
|
display: inline-block;
|
||||||
|
width: 60rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
line-height: 60rpx;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result.win {
|
||||||
|
background: linear-gradient(135deg, #4caf50 0%, #66bb6a 100%);
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(76, 175, 80, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result.lose {
|
||||||
|
background: linear-gradient(135deg, #f44336 0%, #ef5350 100%);
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(244, 67, 54, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
gap: 8rpx;
|
gap: 8rpx;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.type-badge {
|
.opponent {
|
||||||
padding: 6rpx 14rpx;
|
font-size: 28rpx;
|
||||||
border-radius: var(--radius-full);
|
color: #666;
|
||||||
font-size: 22rpx;
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score {
|
||||||
|
font-size: 40rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #333;
|
||||||
|
letter-spacing: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.power-change {
|
||||||
|
font-size: 32rpx;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
padding: 8rpx 16rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
text-align: center;
|
||||||
|
min-width: 100rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.type-badge.ladder {
|
.power-change.positive {
|
||||||
background: var(--primary-soft);
|
background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
|
||||||
color: var(--primary);
|
color: #2e7d32;
|
||||||
}
|
}
|
||||||
|
|
||||||
.type-badge.friendly {
|
.power-change.negative {
|
||||||
background: var(--accent-light);
|
background: linear-gradient(135deg, #ffebee 0%, #ffcdd2 100%);
|
||||||
color: var(--accent);
|
color: #c62828;
|
||||||
}
|
}
|
||||||
|
|
||||||
.result-badge {
|
.match-footer {
|
||||||
padding: 6rpx 14rpx;
|
padding: 20rpx 28rpx;
|
||||||
border-radius: var(--radius-full);
|
background: #fafafa;
|
||||||
font-size: 22rpx;
|
border-top: 1rpx solid #f0f0f0;
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-badge.win {
|
|
||||||
background: var(--accent-light);
|
|
||||||
color: var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-badge.lose {
|
|
||||||
background: #FFF1F0;
|
|
||||||
color: #FF4D4F;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.match-time {
|
.match-time {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: var(--text-muted);
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 比赛内容 */
|
|
||||||
.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 {
|
.empty-state {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 100rpx 48rpx;
|
justify-content: center;
|
||||||
animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.2s backwards;
|
padding: 120rpx 40rpx;
|
||||||
|
min-height: 60vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-icon {
|
.empty-state image {
|
||||||
width: 180rpx;
|
width: 200rpx;
|
||||||
height: 180rpx;
|
height: 200rpx;
|
||||||
|
opacity: 0.6;
|
||||||
margin-bottom: 32rpx;
|
margin-bottom: 32rpx;
|
||||||
opacity: 0.7;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-title {
|
.empty-state text {
|
||||||
font-size: 30rpx;
|
font-size: 28rpx;
|
||||||
font-weight: 600;
|
color: #999;
|
||||||
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 {
|
.loading-state {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 48rpx;
|
padding: 40rpx;
|
||||||
color: var(--text-muted);
|
color: #999;
|
||||||
font-size: 26rpx;
|
font-size: 28rpx;
|
||||||
}
|
|
||||||
|
|
||||||
/* 底部安全区域 */
|
|
||||||
.safe-bottom {
|
|
||||||
height: 80rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 加载更多 */
|
|
||||||
.load-more {
|
|
||||||
text-align: center;
|
|
||||||
padding: 32rpx;
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-size: 26rpx;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,32 +16,17 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: 380rpx;
|
height: 380rpx;
|
||||||
background: linear-gradient(135deg, #FFB74D 0%, #FF9800 50%, #F57C00 100%);
|
background: transparent;
|
||||||
border-radius: 0 0 60rpx 60rpx;
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-bg::before {
|
.hero-bg::before {
|
||||||
content: '';
|
display: none;
|
||||||
position: absolute;
|
|
||||||
top: -80rpx;
|
|
||||||
right: -60rpx;
|
|
||||||
width: 260rpx;
|
|
||||||
height: 260rpx;
|
|
||||||
background: rgba(255, 255, 255, 0.15);
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-bg::after {
|
.hero-bg::after {
|
||||||
content: '';
|
display: none;
|
||||||
position: absolute;
|
|
||||||
bottom: -40rpx;
|
|
||||||
left: -40rpx;
|
|
||||||
width: 180rpx;
|
|
||||||
height: 180rpx;
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-pattern {
|
.hero-pattern {
|
||||||
@ -70,7 +55,9 @@
|
|||||||
/* 比赛头部 */
|
/* 比赛头部 */
|
||||||
.match-header {
|
.match-header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 32rpx 0 40rpx;
|
padding: 48rpx 0 40rpx;
|
||||||
|
border-bottom: 1rpx solid rgba(0, 0, 0, 0.04);
|
||||||
|
margin-bottom: 24rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.match-badge {
|
.match-badge {
|
||||||
@ -80,12 +67,11 @@
|
|||||||
|
|
||||||
.match-title {
|
.match-title {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 44rpx;
|
font-size: 48rpx;
|
||||||
font-weight: 800;
|
font-weight: 700;
|
||||||
color: #fff;
|
color: #1a1a1a;
|
||||||
margin-bottom: 16rpx;
|
margin-bottom: 16rpx;
|
||||||
text-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
letter-spacing: 1rpx;
|
||||||
letter-spacing: 4rpx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.match-status {
|
.match-status {
|
||||||
@ -93,19 +79,18 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8rpx;
|
gap: 8rpx;
|
||||||
padding: 10rpx 24rpx;
|
padding: 10rpx 24rpx;
|
||||||
background: rgba(255, 255, 255, 0.25);
|
background: var(--bg-soft);
|
||||||
border-radius: 50rpx;
|
border-radius: 50rpx;
|
||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #fff;
|
color: #666;
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-dot {
|
.status-dot {
|
||||||
width: 12rpx;
|
width: 12rpx;
|
||||||
height: 12rpx;
|
height: 12rpx;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: #fff;
|
background: #ff6b35;
|
||||||
}
|
}
|
||||||
|
|
||||||
.match-status.status-1 .status-dot {
|
.match-status.status-1 .status-dot {
|
||||||
|
|||||||
@ -12,19 +12,13 @@
|
|||||||
/* 顶部装饰背景 */
|
/* 顶部装饰背景 */
|
||||||
.hero-section {
|
.hero-section {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 32rpx 24rpx;
|
padding: 48rpx 24rpx 32rpx;
|
||||||
background: linear-gradient(180deg, #FFF5F0 0%, var(--bg-page) 100%);
|
background: transparent;
|
||||||
|
border-bottom: 1rpx solid rgba(0, 0, 0, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-pattern {
|
.hero-pattern {
|
||||||
position: absolute;
|
display: none;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 积分信息卡片 */
|
/* 积分信息卡片 */
|
||||||
@ -34,10 +28,11 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 28rpx 24rpx;
|
padding: 32rpx 28rpx;
|
||||||
background: var(--bg-white);
|
background: var(--bg-white);
|
||||||
border-radius: var(--radius-xl);
|
border-radius: 20rpx;
|
||||||
box-shadow: var(--shadow-lg);
|
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.06);
|
||||||
|
border: 1rpx solid rgba(0, 0, 0, 0.04);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
animation: fadeInScale 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
animation: fadeInScale 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
@ -47,8 +42,9 @@
|
|||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: 4rpx;
|
height: 3rpx;
|
||||||
background: var(--primary-gradient);
|
background: var(--primary-gradient);
|
||||||
|
border-radius: 20rpx 20rpx 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.points-info {
|
.points-info {
|
||||||
|
|||||||
@ -16,18 +16,12 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: 280rpx;
|
height: 280rpx;
|
||||||
background: linear-gradient(180deg, #FFF5F0 0%, var(--bg-page) 100%);
|
background: transparent;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-pattern {
|
.hero-pattern {
|
||||||
position: absolute;
|
display: none;
|
||||||
top: -60rpx;
|
|
||||||
right: -40rpx;
|
|
||||||
width: 220rpx;
|
|
||||||
height: 220rpx;
|
|
||||||
background: radial-gradient(circle, rgba(255, 107, 53, 0.08) 0%, transparent 70%);
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 页面标题 */
|
/* 页面标题 */
|
||||||
@ -35,16 +29,18 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 32rpx 24rpx 20rpx;
|
padding: 48rpx 24rpx 32rpx;
|
||||||
animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
border-bottom: 1rpx solid rgba(0, 0, 0, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-title {
|
.page-title {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 36rpx;
|
font-size: 52rpx;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--text-primary);
|
color: #1a1a1a;
|
||||||
letter-spacing: 2rpx;
|
letter-spacing: 1rpx;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 状态筛选标签 */
|
/* 状态筛选标签 */
|
||||||
|
|||||||
@ -15,18 +15,12 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: 280rpx;
|
height: 280rpx;
|
||||||
background: linear-gradient(180deg, #FFF5F0 0%, var(--bg-page) 100%);
|
background: transparent;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-pattern {
|
.hero-pattern {
|
||||||
position: absolute;
|
display: none;
|
||||||
top: -60rpx;
|
|
||||||
right: -40rpx;
|
|
||||||
width: 220rpx;
|
|
||||||
height: 220rpx;
|
|
||||||
background: radial-gradient(circle, rgba(255, 107, 53, 0.08) 0%, transparent 70%);
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 页面标题 */
|
/* 页面标题 */
|
||||||
@ -34,23 +28,26 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 32rpx 24rpx 24rpx;
|
padding: 48rpx 24rpx 32rpx;
|
||||||
animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
border-bottom: 1rpx solid rgba(0, 0, 0, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-title {
|
.page-title {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 40rpx;
|
font-size: 52rpx;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--text-primary);
|
color: #1a1a1a;
|
||||||
margin-bottom: 8rpx;
|
margin-bottom: 8rpx;
|
||||||
letter-spacing: 2rpx;
|
letter-spacing: 1rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-subtitle {
|
.page-subtitle {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
color: var(--text-muted);
|
color: #999;
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: 0.5rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 积分概览卡片 */
|
/* 积分概览卡片 */
|
||||||
|
|||||||
@ -15,18 +15,12 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: 320rpx;
|
height: 320rpx;
|
||||||
background: linear-gradient(180deg, #FFF5F0 0%, var(--bg-page) 100%);
|
background: transparent;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-pattern {
|
.hero-pattern {
|
||||||
position: absolute;
|
display: none;
|
||||||
top: -80rpx;
|
|
||||||
right: -60rpx;
|
|
||||||
width: 280rpx;
|
|
||||||
height: 280rpx;
|
|
||||||
background: radial-gradient(circle, rgba(255, 107, 53, 0.1) 0%, transparent 70%);
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 页面标题 */
|
/* 页面标题 */
|
||||||
@ -34,23 +28,26 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 32rpx 24rpx 24rpx;
|
padding: 48rpx 24rpx 32rpx;
|
||||||
animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
border-bottom: 1rpx solid rgba(0, 0, 0, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-title {
|
.page-title {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 40rpx;
|
font-size: 52rpx;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--text-primary);
|
color: #1a1a1a;
|
||||||
margin-bottom: 8rpx;
|
margin-bottom: 8rpx;
|
||||||
letter-spacing: 2rpx;
|
letter-spacing: 1rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-subtitle {
|
.page-subtitle {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
color: var(--text-muted);
|
color: #999;
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: 0.5rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 当前门店卡片 */
|
/* 当前门店卡片 */
|
||||||
|
|||||||
@ -64,6 +64,16 @@ Page({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await app.getUserInfo()
|
await app.getUserInfo()
|
||||||
|
|
||||||
|
// 如果当前门店有 ladderUserId,确保获取该门店的天梯用户信息
|
||||||
|
if (app.globalData.currentStore?.storeId && !app.globalData.ladderUser) {
|
||||||
|
try {
|
||||||
|
await app.getLadderUser(app.globalData.currentStore.storeId)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('获取天梯用户信息失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.setData({
|
this.setData({
|
||||||
userInfo: app.globalData.userInfo,
|
userInfo: app.globalData.userInfo,
|
||||||
ladderUser: app.globalData.ladderUser,
|
ladderUser: app.globalData.ladderUser,
|
||||||
|
|||||||
@ -115,11 +115,11 @@
|
|||||||
<text class="record-label">胜场</text>
|
<text class="record-label">胜场</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="record-item">
|
<view class="record-item">
|
||||||
<text class="record-value">{{ladderUser.matchCount - ladderUser.winCount || 0}}</text>
|
<text class="record-value">{{(ladderUser.matchCount || 0) - (ladderUser.winCount || 0)}}</text>
|
||||||
<text class="record-label">负场</text>
|
<text class="record-label">负场</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="record-item">
|
<view class="record-item">
|
||||||
<text class="record-value rate">{{ladderUser.matchCount > 0 ? Math.round(ladderUser.winCount / ladderUser.matchCount * 100) : 0}}%</text>
|
<text class="record-value rate">{{(ladderUser.matchCount > 0 && ladderUser.winCount !== null && ladderUser.winCount !== undefined) ? Math.round((Number(ladderUser.winCount) || 0) / Number(ladderUser.matchCount) * 100) : 0}}%</text>
|
||||||
<text class="record-label">胜率</text>
|
<text class="record-label">胜率</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@ -17,26 +17,19 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: 480rpx;
|
height: 480rpx;
|
||||||
background: linear-gradient(180deg, #FFF5F0 0%, var(--bg-page) 100%);
|
background: transparent;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-pattern {
|
.hero-pattern {
|
||||||
position: absolute;
|
display: none;
|
||||||
top: -100rpx;
|
|
||||||
right: -80rpx;
|
|
||||||
width: 400rpx;
|
|
||||||
height: 400rpx;
|
|
||||||
background: radial-gradient(circle, rgba(255, 107, 53, 0.08) 0%, transparent 70%);
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: pulse 4s ease-in-out infinite;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 用户信息区域 */
|
/* 用户信息区域 */
|
||||||
.user-section {
|
.user-section {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
padding: 32rpx 24rpx 24rpx;
|
padding: 48rpx 24rpx 24rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================
|
/* ==========================================
|
||||||
@ -46,11 +39,12 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 48rpx 32rpx 40rpx;
|
padding: 52rpx 32rpx 44rpx;
|
||||||
background: var(--bg-white);
|
background: var(--bg-white);
|
||||||
border-radius: var(--radius-xl);
|
border-radius: 24rpx;
|
||||||
box-shadow: var(--shadow-lg);
|
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.06);
|
||||||
margin-bottom: 20rpx;
|
margin-bottom: 24rpx;
|
||||||
|
border: 1rpx solid rgba(0, 0, 0, 0.04);
|
||||||
animation: fadeInScale 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
animation: fadeInScale 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,6 +16,17 @@ NODE_ENV=development
|
|||||||
# 服务端口(默认3000)
|
# 服务端口(默认3000)
|
||||||
PORT=3000
|
PORT=3000
|
||||||
|
|
||||||
|
# 基础URL(用于生成完整的文件URL,支持HTTPS)
|
||||||
|
# 示例:https://api.example.com 或 http://localhost:3000
|
||||||
|
# 如果设置了此项,上传接口返回的URL将使用此值
|
||||||
|
# 如果不设置,将根据请求自动检测协议(支持反向代理)
|
||||||
|
BASE_URL=
|
||||||
|
|
||||||
|
# 强制使用HTTPS(true/false)
|
||||||
|
# 如果设置为 true,所有返回的URL将使用 https://
|
||||||
|
# 适用于部署在反向代理(如nginx)后面的情况
|
||||||
|
FORCE_HTTPS=false
|
||||||
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# 数据库配置 (MySQL)
|
# 数据库配置 (MySQL)
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
|
|||||||
@ -6,7 +6,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node src/app.js",
|
"start": "node src/app.js",
|
||||||
"dev": "nodemon src/app.js",
|
"dev": "nodemon src/app.js",
|
||||||
"db:init": "node src/scripts/initDatabase.js"
|
"db:init": "node src/scripts/initDatabase.js",
|
||||||
|
"db:mock": "node src/scripts/generateMockLadderData.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
|||||||
@ -18,6 +18,10 @@ const uploadRoutes = require('./routes/upload');
|
|||||||
const app = express();
|
const app = express();
|
||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
|
|
||||||
|
// 信任代理(支持反向代理的 HTTPS)
|
||||||
|
// 如果应用部署在 nginx 等反向代理后面,需要设置此项
|
||||||
|
app.set('trust proxy', true);
|
||||||
|
|
||||||
// 中间件
|
// 中间件
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|||||||
@ -142,6 +142,45 @@ class AdminController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 搜索用户(用于积分操作,不需要超级管理员权限)
|
||||||
|
async searchUsers(req, res) {
|
||||||
|
try {
|
||||||
|
const { keyword, pageSize = 10 } = req.query;
|
||||||
|
|
||||||
|
if (!keyword || keyword.trim().length < 2) {
|
||||||
|
return res.json(success([]));
|
||||||
|
}
|
||||||
|
|
||||||
|
const where = {
|
||||||
|
status: 1 // 只搜索正常状态的用户
|
||||||
|
};
|
||||||
|
|
||||||
|
where[Op.or] = [
|
||||||
|
{ nickname: { [Op.like]: `%${keyword.trim()}%` } },
|
||||||
|
{ phone: { [Op.like]: `%${keyword.trim()}%` } },
|
||||||
|
{ member_code: { [Op.like]: `%${keyword.trim()}%` } }
|
||||||
|
];
|
||||||
|
|
||||||
|
const users = await User.findAll({
|
||||||
|
where,
|
||||||
|
limit: parseInt(pageSize) || 10,
|
||||||
|
order: [['created_at', 'DESC']],
|
||||||
|
attributes: ['id', 'nickname', 'phone', 'avatar', 'member_code']
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json(success(users.map(user => ({
|
||||||
|
id: user.id,
|
||||||
|
nickname: user.nickname,
|
||||||
|
phone: user.phone,
|
||||||
|
avatar: user.avatar,
|
||||||
|
memberCode: user.member_code
|
||||||
|
}))));
|
||||||
|
} catch (err) {
|
||||||
|
console.error('搜索用户失败:', err);
|
||||||
|
res.status(500).json(error('搜索失败'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async getUserDetail(req, res) {
|
async getUserDetail(req, res) {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
|||||||
@ -114,6 +114,42 @@ class MatchController {
|
|||||||
return res.status(400).json(error('天梯用户信息无效', 400));
|
return res.status(400).json(error('天梯用户信息无效', 400));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查发起者是否有未结束的挑战赛
|
||||||
|
const myOngoingMatch = await Match.findOne({
|
||||||
|
where: {
|
||||||
|
store_id,
|
||||||
|
type: MATCH_TYPES.CHALLENGE,
|
||||||
|
status: { [Op.in]: [MATCH_STATUS.PENDING, MATCH_STATUS.ONGOING] },
|
||||||
|
[Op.or]: [
|
||||||
|
{ challenger_id: myLadderUser.id },
|
||||||
|
{ defender_id: myLadderUser.id }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (myOngoingMatch) {
|
||||||
|
await t.rollback();
|
||||||
|
return res.status(400).json(error('您有未结束的挑战赛,请先完成后再发起新的挑战', 400));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查被挑战者是否有未结束的挑战赛
|
||||||
|
const targetOngoingMatch = await Match.findOne({
|
||||||
|
where: {
|
||||||
|
store_id,
|
||||||
|
type: MATCH_TYPES.CHALLENGE,
|
||||||
|
status: { [Op.in]: [MATCH_STATUS.PENDING, MATCH_STATUS.ONGOING] },
|
||||||
|
[Op.or]: [
|
||||||
|
{ challenger_id: targetLadderUser.id },
|
||||||
|
{ defender_id: targetLadderUser.id }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (targetOngoingMatch) {
|
||||||
|
await t.rollback();
|
||||||
|
return res.status(400).json(error('对方有未结束的挑战赛,请稍后再试', 400));
|
||||||
|
}
|
||||||
|
|
||||||
// 创建挑战赛
|
// 创建挑战赛
|
||||||
const match = await Match.create({
|
const match = await Match.create({
|
||||||
store_id,
|
store_id,
|
||||||
@ -184,6 +220,27 @@ class MatchController {
|
|||||||
return res.status(403).json(error('您不是被挑战者', 403));
|
return res.status(403).json(error('您不是被挑战者', 403));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果接受挑战,检查是否有其他未结束的挑战赛
|
||||||
|
if (accept) {
|
||||||
|
const otherOngoingMatch = await Match.findOne({
|
||||||
|
where: {
|
||||||
|
store_id: match.store_id,
|
||||||
|
type: MATCH_TYPES.CHALLENGE,
|
||||||
|
status: { [Op.in]: [MATCH_STATUS.PENDING, MATCH_STATUS.ONGOING] },
|
||||||
|
id: { [Op.ne]: match_id },
|
||||||
|
[Op.or]: [
|
||||||
|
{ challenger_id: defenderLadderUser.id },
|
||||||
|
{ defender_id: defenderLadderUser.id }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (otherOngoingMatch) {
|
||||||
|
await t.rollback();
|
||||||
|
return res.status(400).json(error('您有未结束的挑战赛,请先完成后再接受新的挑战', 400));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (accept) {
|
if (accept) {
|
||||||
// 接受挑战
|
// 接受挑战
|
||||||
await match.update({ status: MATCH_STATUS.ONGOING, start_time: new Date() }, { transaction: t });
|
await match.update({ status: MATCH_STATUS.ONGOING, start_time: new Date() }, { transaction: t });
|
||||||
@ -901,6 +958,7 @@ class MatchController {
|
|||||||
async getMatchDetail(req, res) {
|
async getMatchDetail(req, res) {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
const user = req.user;
|
||||||
|
|
||||||
const match = await Match.findByPk(id, {
|
const match = await Match.findByPk(id, {
|
||||||
include: [
|
include: [
|
||||||
@ -913,7 +971,179 @@ class MatchController {
|
|||||||
return res.status(404).json(error('比赛不存在', 404));
|
return res.status(404).json(error('比赛不存在', 404));
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json(success({
|
// 获取当前用户的天梯用户信息
|
||||||
|
const myLadderUser = await LadderUser.findOne({
|
||||||
|
where: { user_id: user.id, store_id: match.store_id, status: 1 },
|
||||||
|
include: [{ model: User, as: 'user', attributes: ['nickname', 'avatar'] }]
|
||||||
|
});
|
||||||
|
|
||||||
|
// 调试日志
|
||||||
|
console.log('获取比赛详情 - 用户信息:', {
|
||||||
|
userId: user.id,
|
||||||
|
storeId: match.store_id,
|
||||||
|
myLadderUserId: myLadderUser?.id,
|
||||||
|
matchChallengerId: match.challenger_id,
|
||||||
|
matchDefenderId: match.defender_id,
|
||||||
|
matchStatus: match.status
|
||||||
|
});
|
||||||
|
|
||||||
|
let challengerInfo = null;
|
||||||
|
let defenderInfo = null;
|
||||||
|
let myRole = null; // 'challenger' | 'defender' | null
|
||||||
|
let canAccept = false;
|
||||||
|
let canReject = false;
|
||||||
|
let canSubmitScore = false;
|
||||||
|
let canConfirmScore = false;
|
||||||
|
|
||||||
|
if (match.type === MATCH_TYPES.CHALLENGE) {
|
||||||
|
// 挑战赛:获取挑战者和被挑战者信息
|
||||||
|
const challengerLadder = await LadderUser.findByPk(match.challenger_id, {
|
||||||
|
include: [{ model: User, as: 'user', attributes: ['nickname', 'avatar'] }]
|
||||||
|
});
|
||||||
|
const defenderLadder = await LadderUser.findByPk(match.defender_id, {
|
||||||
|
include: [{ model: User, as: 'user', attributes: ['nickname', 'avatar'] }]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (challengerLadder) {
|
||||||
|
challengerInfo = {
|
||||||
|
id: challengerLadder.id,
|
||||||
|
realName: challengerLadder.real_name,
|
||||||
|
nickname: challengerLadder.user?.nickname,
|
||||||
|
avatar: challengerLadder.user?.avatar,
|
||||||
|
level: challengerLadder.level,
|
||||||
|
powerScore: challengerLadder.power_score,
|
||||||
|
userId: challengerLadder.user_id, // 添加 user_id,用于前端判断
|
||||||
|
phone: challengerLadder.phone // 添加手机号,用于前端判断
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defenderLadder) {
|
||||||
|
defenderInfo = {
|
||||||
|
id: defenderLadder.id,
|
||||||
|
realName: defenderLadder.real_name,
|
||||||
|
nickname: defenderLadder.user?.nickname,
|
||||||
|
avatar: defenderLadder.user?.avatar,
|
||||||
|
level: defenderLadder.level,
|
||||||
|
powerScore: defenderLadder.power_score,
|
||||||
|
userId: defenderLadder.user_id, // 添加 user_id,用于前端判断
|
||||||
|
phone: defenderLadder.phone // 添加手机号,用于前端判断
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确定当前用户角色
|
||||||
|
// 方法1:通过 myLadderUser.id 比较(如果用户是该门店的天梯用户)
|
||||||
|
if (myLadderUser) {
|
||||||
|
// 使用 == 比较,因为可能是数字或字符串
|
||||||
|
if (myLadderUser.id == match.challenger_id) {
|
||||||
|
myRole = 'challenger';
|
||||||
|
console.log('用户是挑战者(通过myLadderUser):', { myLadderUserId: myLadderUser.id, challengerId: match.challenger_id });
|
||||||
|
} else if (myLadderUser.id == match.defender_id) {
|
||||||
|
myRole = 'defender';
|
||||||
|
console.log('用户是被挑战者(通过myLadderUser):', { myLadderUserId: myLadderUser.id, defenderId: match.defender_id });
|
||||||
|
} else {
|
||||||
|
console.log('用户角色不匹配(通过myLadderUser):', {
|
||||||
|
myLadderUserId: myLadderUser.id,
|
||||||
|
challengerId: match.challenger_id,
|
||||||
|
defenderId: match.defender_id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 方法2:如果 myLadderUser 为 null,通过挑战者和被挑战者的 user_id 来判断
|
||||||
|
if (!myRole && challengerLadder && defenderLadder) {
|
||||||
|
// 检查挑战者的 user_id
|
||||||
|
if (challengerLadder.user_id && challengerLadder.user_id == user.id) {
|
||||||
|
myRole = 'challenger';
|
||||||
|
console.log('用户是挑战者(通过user_id):', {
|
||||||
|
userId: user.id,
|
||||||
|
challengerUserId: challengerLadder.user_id,
|
||||||
|
challengerLadderId: challengerLadder.id,
|
||||||
|
matchChallengerId: match.challenger_id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 检查被挑战者的 user_id
|
||||||
|
else if (defenderLadder.user_id && defenderLadder.user_id == user.id) {
|
||||||
|
myRole = 'defender';
|
||||||
|
console.log('用户是被挑战者(通过user_id):', {
|
||||||
|
userId: user.id,
|
||||||
|
defenderUserId: defenderLadder.user_id,
|
||||||
|
defenderLadderId: defenderLadder.id,
|
||||||
|
matchDefenderId: match.defender_id
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('用户角色不匹配(通过user_id):', {
|
||||||
|
userId: user.id,
|
||||||
|
challengerUserId: challengerLadder.user_id,
|
||||||
|
challengerLadderId: challengerLadder.id,
|
||||||
|
defenderUserId: defenderLadder.user_id,
|
||||||
|
defenderLadderId: defenderLadder.id,
|
||||||
|
matchChallengerId: match.challenger_id,
|
||||||
|
matchDefenderId: match.defender_id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 方法3:如果前两种方法都失败,尝试通过手机号匹配(如果天梯用户有手机号且用户有手机号)
|
||||||
|
if (!myRole && challengerLadder && defenderLadder && user.phone) {
|
||||||
|
if (challengerLadder.phone && challengerLadder.phone === user.phone) {
|
||||||
|
myRole = 'challenger';
|
||||||
|
console.log('用户是挑战者(通过手机号):', { userPhone: user.phone, challengerPhone: challengerLadder.phone });
|
||||||
|
} else if (defenderLadder.phone && defenderLadder.phone === user.phone) {
|
||||||
|
myRole = 'defender';
|
||||||
|
console.log('用户是被挑战者(通过手机号):', { userPhone: user.phone, defenderPhone: defenderLadder.phone });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!myRole) {
|
||||||
|
console.log('无法确定用户角色:', {
|
||||||
|
userId: user.id,
|
||||||
|
userPhone: user.phone,
|
||||||
|
storeId: match.store_id,
|
||||||
|
hasMyLadderUser: !!myLadderUser,
|
||||||
|
myLadderUserId: myLadderUser?.id,
|
||||||
|
challengerLadderId: challengerLadder?.id,
|
||||||
|
challengerUserId: challengerLadder?.user_id,
|
||||||
|
challengerPhone: challengerLadder?.phone,
|
||||||
|
defenderLadderId: defenderLadder?.id,
|
||||||
|
defenderUserId: defenderLadder?.user_id,
|
||||||
|
defenderPhone: defenderLadder?.phone,
|
||||||
|
matchChallengerId: match.challenger_id,
|
||||||
|
matchDefenderId: match.defender_id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断操作权限
|
||||||
|
// 待接受状态且是被挑战者,可以接受或拒绝
|
||||||
|
if (match.status === MATCH_STATUS.PENDING) {
|
||||||
|
if (myRole === 'defender') {
|
||||||
|
canAccept = true;
|
||||||
|
canReject = true;
|
||||||
|
console.log('设置接受/拒绝权限为true:', { myRole, status: match.status, canAccept, canReject });
|
||||||
|
} else {
|
||||||
|
console.log('不能接受/拒绝:', { myRole, status: match.status, isDefender: myRole === 'defender' });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('比赛状态不是待接受:', { status: match.status, myRole });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match.status === MATCH_STATUS.ONGOING && myLadderUser) {
|
||||||
|
const game = match.games?.[0];
|
||||||
|
if (game) {
|
||||||
|
// 检查是否可以提交比分(必须是胜者且未提交)
|
||||||
|
if (game.status === 1 && !game.submit_by) {
|
||||||
|
// 比赛进行中,还未提交比分,双方都可以填写
|
||||||
|
canSubmitScore = true;
|
||||||
|
} else if (game.status === 2 && game.submit_by && game.submit_by !== myLadderUser.id) {
|
||||||
|
// 对方已提交比分,等待我确认
|
||||||
|
if (game.confirm_status === CONFIRM_STATUS.PENDING) {
|
||||||
|
canConfirmScore = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {
|
||||||
id: match.id,
|
id: match.id,
|
||||||
matchCode: match.match_code,
|
matchCode: match.match_code,
|
||||||
type: match.type,
|
type: match.type,
|
||||||
@ -924,8 +1154,49 @@ class MatchController {
|
|||||||
storeName: match.store?.name,
|
storeName: match.store?.name,
|
||||||
startTime: match.start_time,
|
startTime: match.start_time,
|
||||||
endTime: match.end_time,
|
endTime: match.end_time,
|
||||||
games: match.games
|
challenger: challengerInfo,
|
||||||
}));
|
defender: defenderInfo,
|
||||||
|
myRole: myRole || null,
|
||||||
|
canAccept: Boolean(canAccept),
|
||||||
|
canReject: Boolean(canReject),
|
||||||
|
canSubmitScore: Boolean(canSubmitScore),
|
||||||
|
canConfirmScore: Boolean(canConfirmScore),
|
||||||
|
games: match.games?.map(game => ({
|
||||||
|
id: game.id,
|
||||||
|
player1Id: game.player1_id,
|
||||||
|
player2Id: game.player2_id,
|
||||||
|
player1Score: game.player1_score,
|
||||||
|
player2Score: game.player2_score,
|
||||||
|
winnerId: game.winner_id,
|
||||||
|
loserId: game.loser_id,
|
||||||
|
submitBy: game.submit_by,
|
||||||
|
confirmStatus: game.confirm_status,
|
||||||
|
status: game.status
|
||||||
|
})) || []
|
||||||
|
};
|
||||||
|
|
||||||
|
// 调试日志
|
||||||
|
console.log('比赛详情返回数据:', {
|
||||||
|
matchId: match.id,
|
||||||
|
matchStatus: match.status,
|
||||||
|
storeId: match.store_id,
|
||||||
|
userId: user.id,
|
||||||
|
myLadderUserId: myLadderUser?.id,
|
||||||
|
myLadderUserType: typeof myLadderUser?.id,
|
||||||
|
challengerId: match.challenger_id,
|
||||||
|
challengerIdType: typeof match.challenger_id,
|
||||||
|
defenderId: match.defender_id,
|
||||||
|
defenderIdType: typeof match.defender_id,
|
||||||
|
myRole,
|
||||||
|
canAccept: result.canAccept,
|
||||||
|
canReject: result.canReject,
|
||||||
|
hasMyLadderUser: !!myLadderUser,
|
||||||
|
challengerMatch: myLadderUser ? (myLadderUser.id == match.challenger_id) : false,
|
||||||
|
defenderMatch: myLadderUser ? (myLadderUser.id == match.defender_id) : false
|
||||||
|
});
|
||||||
|
|
||||||
|
// 确保返回所有必要的字段
|
||||||
|
res.json(success(result));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('获取比赛详情失败:', err);
|
console.error('获取比赛详情失败:', err);
|
||||||
res.status(500).json(error('获取失败'));
|
res.status(500).json(error('获取失败'));
|
||||||
|
|||||||
@ -128,8 +128,28 @@ class PointsAdminController {
|
|||||||
return res.status(404).json(error('行为不存在', 404));
|
return res.status(404).json(error('行为不存在', 404));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 通过会员码获取用户
|
// 通过会员码、手机号或昵称获取用户
|
||||||
const user = await User.findOne({ where: { member_code } });
|
let user = null;
|
||||||
|
if (member_code) {
|
||||||
|
// 先尝试通过会员码查询
|
||||||
|
user = await User.findOne({ where: { member_code } });
|
||||||
|
|
||||||
|
// 如果会员码查询失败,尝试通过手机号查询
|
||||||
|
if (!user && /^1[3-9]\d{9}$/.test(member_code)) {
|
||||||
|
user = await User.findOne({ where: { phone: member_code } });
|
||||||
|
}
|
||||||
|
image.png
|
||||||
|
// 如果还是没找到,尝试通过昵称查询
|
||||||
|
if (!user) {
|
||||||
|
user = await User.findOne({
|
||||||
|
where: {
|
||||||
|
nickname: { [Op.like]: `%${member_code}%` },
|
||||||
|
status: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
await t.rollback();
|
await t.rollback();
|
||||||
return res.status(404).json(error('用户不存在', 404));
|
return res.status(404).json(error('用户不存在', 404));
|
||||||
|
|||||||
@ -14,6 +14,7 @@ router.get('/dashboard', authAdmin, adminController.getDashboard);
|
|||||||
|
|
||||||
// === 用户管理(超管) ===
|
// === 用户管理(超管) ===
|
||||||
router.get('/users', authAdmin, requireSuperAdmin, adminController.getUsers);
|
router.get('/users', authAdmin, requireSuperAdmin, adminController.getUsers);
|
||||||
|
router.get('/users/search', authAdmin, adminController.searchUsers); // 搜索用户(用于积分操作,普通管理员可用)
|
||||||
router.get('/users/:id', authAdmin, requireSuperAdmin, adminController.getUserDetail);
|
router.get('/users/:id', authAdmin, requireSuperAdmin, adminController.getUserDetail);
|
||||||
router.put('/users/:id/status', authAdmin, requireSuperAdmin, adminController.updateUserStatus);
|
router.put('/users/:id/status', authAdmin, requireSuperAdmin, adminController.updateUserStatus);
|
||||||
|
|
||||||
|
|||||||
@ -41,8 +41,9 @@ router.post('/image', authAdmin, upload.single('file'), (req, res) => {
|
|||||||
return res.status(400).json(error('请选择要上传的图片'));
|
return res.status(400).json(error('请选择要上传的图片'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = `/uploads/${req.file.filename}`;
|
const relativePath = `/uploads/${req.file.filename}`;
|
||||||
res.json(success({ url }, '上传成功'));
|
const fullUrl = getFullUrl(relativePath, req);
|
||||||
|
res.json(success({ url: fullUrl }, '上传成功'));
|
||||||
});
|
});
|
||||||
|
|
||||||
// 上传多张图片
|
// 上传多张图片
|
||||||
@ -51,7 +52,10 @@ router.post('/images', authAdmin, upload.array('files', 10), (req, res) => {
|
|||||||
return res.status(400).json(error('请选择要上传的图片'));
|
return res.status(400).json(error('请选择要上传的图片'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const urls = req.files.map(file => `/uploads/${file.filename}`);
|
const urls = req.files.map(file => {
|
||||||
|
const relativePath = `/uploads/${file.filename}`;
|
||||||
|
return getFullUrl(relativePath, req);
|
||||||
|
});
|
||||||
res.json(success({ urls }, '上传成功'));
|
res.json(success({ urls }, '上传成功'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
168
server/src/scripts/generateMockLadderData.js
Normal file
168
server/src/scripts/generateMockLadderData.js
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
const sequelize = require('../config/database');
|
||||||
|
const { LadderUser, Store, User } = require('../models');
|
||||||
|
const { LADDER_LEVELS, POWER_CALC } = require('../config/constants');
|
||||||
|
|
||||||
|
// 中文姓名库
|
||||||
|
const surnames = ['张', '王', '李', '赵', '刘', '陈', '杨', '黄', '周', '吴', '徐', '孙', '马', '朱', '胡', '林', '郭', '何', '高', '罗'];
|
||||||
|
const givenNames = ['伟', '芳', '娜', '秀英', '敏', '静', '丽', '强', '磊', '军', '洋', '勇', '艳', '杰', '涛', '明', '超', '秀兰', '霞', '平', '刚', '桂英', '建华', '文', '华', '建国', '建军', '志强', '秀华', '秀珍'];
|
||||||
|
|
||||||
|
// 生成随机姓名
|
||||||
|
function generateName() {
|
||||||
|
const surname = surnames[Math.floor(Math.random() * surnames.length)];
|
||||||
|
const givenName = givenNames[Math.floor(Math.random() * givenNames.length)];
|
||||||
|
return surname + givenName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成随机手机号
|
||||||
|
function generatePhone() {
|
||||||
|
const prefixes = ['138', '139', '150', '151', '152', '158', '159', '186', '187', '188'];
|
||||||
|
const prefix = prefixes[Math.floor(Math.random() * prefixes.length)];
|
||||||
|
const suffix = Math.floor(10000000 + Math.random() * 90000000);
|
||||||
|
return prefix + suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据等级生成合理的战力值
|
||||||
|
function generatePowerScore(level) {
|
||||||
|
const baseScores = {
|
||||||
|
1: { min: 800, max: 1200 }, // 新锐
|
||||||
|
2: { min: 1200, max: 1600 }, // 精锐
|
||||||
|
3: { min: 1600, max: 2000 }, // 高手
|
||||||
|
4: { min: 2000, max: 2500 }, // 大师
|
||||||
|
5: { min: 2500, max: 3500 } // 宗师
|
||||||
|
};
|
||||||
|
const range = baseScores[level] || baseScores[1];
|
||||||
|
return Math.floor(range.min + Math.random() * (range.max - range.min));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成比赛数据
|
||||||
|
function generateMatchData() {
|
||||||
|
const matchCount = Math.floor(3 + Math.random() * 50); // 3-53场
|
||||||
|
const monthlyMatchCount = Math.floor(3 + Math.random() * 15); // 3-18场(确保>=3)
|
||||||
|
const winCount = Math.floor(matchCount * (0.3 + Math.random() * 0.5)); // 胜率30%-80%
|
||||||
|
return { matchCount, monthlyMatchCount, winCount };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateMockLadderData() {
|
||||||
|
try {
|
||||||
|
console.log('开始生成模拟天梯数据...\n');
|
||||||
|
|
||||||
|
// 获取第一个门店,如果没有则创建
|
||||||
|
let store = await Store.findOne({ where: { status: 1 } });
|
||||||
|
if (!store) {
|
||||||
|
console.log('未找到门店,创建默认门店...');
|
||||||
|
store = await Store.create({
|
||||||
|
name: '英飒羽毛球馆',
|
||||||
|
address: '测试地址',
|
||||||
|
contact: '13800138000',
|
||||||
|
status: 1
|
||||||
|
});
|
||||||
|
console.log(`已创建门店: ${store.name} (ID: ${store.id})\n`);
|
||||||
|
} else {
|
||||||
|
console.log(`使用门店: ${store.name} (ID: ${store.id})\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const storeId = store.id;
|
||||||
|
const count = 50; // 生成50个天梯用户
|
||||||
|
const users = [];
|
||||||
|
|
||||||
|
console.log(`正在生成 ${count} 个天梯用户...`);
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const realName = generateName();
|
||||||
|
const phone = generatePhone();
|
||||||
|
const gender = Math.random() > 0.5 ? 1 : 2; // 随机性别
|
||||||
|
const level = Math.floor(1 + Math.random() * 5); // 1-5级
|
||||||
|
const powerScore = generatePowerScore(level);
|
||||||
|
const { matchCount, monthlyMatchCount, winCount } = generateMatchData();
|
||||||
|
|
||||||
|
// 检查手机号是否已存在
|
||||||
|
const existing = await LadderUser.findOne({
|
||||||
|
where: { store_id: storeId, phone }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
console.log(`跳过重复手机号: ${phone}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const ladderUser = await LadderUser.create({
|
||||||
|
store_id: storeId,
|
||||||
|
real_name: realName,
|
||||||
|
phone: phone,
|
||||||
|
gender: gender,
|
||||||
|
level: level,
|
||||||
|
power_score: powerScore,
|
||||||
|
match_count: matchCount,
|
||||||
|
monthly_match_count: monthlyMatchCount,
|
||||||
|
win_count: winCount,
|
||||||
|
status: 1,
|
||||||
|
last_match_time: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000) // 最近30天内
|
||||||
|
});
|
||||||
|
|
||||||
|
users.push({
|
||||||
|
id: ladderUser.id,
|
||||||
|
name: realName,
|
||||||
|
level,
|
||||||
|
powerScore,
|
||||||
|
matchCount,
|
||||||
|
winCount
|
||||||
|
});
|
||||||
|
|
||||||
|
if ((i + 1) % 10 === 0) {
|
||||||
|
console.log(`已生成 ${i + 1}/${count} 个用户...`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`创建用户失败 (${realName}, ${phone}):`, err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n✅ 成功生成 ${users.length} 个天梯用户!\n`);
|
||||||
|
|
||||||
|
// 显示统计信息
|
||||||
|
const stats = {
|
||||||
|
byLevel: {},
|
||||||
|
byGender: { 1: 0, 2: 0 },
|
||||||
|
totalPower: 0,
|
||||||
|
totalMatches: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const allUsers = await LadderUser.findAll({
|
||||||
|
where: { store_id: storeId, status: 1 },
|
||||||
|
order: [['power_score', 'DESC']]
|
||||||
|
});
|
||||||
|
|
||||||
|
allUsers.forEach(user => {
|
||||||
|
stats.byLevel[user.level] = (stats.byLevel[user.level] || 0) + 1;
|
||||||
|
stats.byGender[user.gender] = (stats.byGender[user.gender] || 0) + 1;
|
||||||
|
stats.totalPower += user.power_score;
|
||||||
|
stats.totalMatches += user.match_count;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('📊 数据统计:');
|
||||||
|
console.log(` 总用户数: ${allUsers.length}`);
|
||||||
|
console.log(` 按等级分布:`);
|
||||||
|
console.log(` - 新锐 (Lv1): ${stats.byLevel[1] || 0} 人`);
|
||||||
|
console.log(` - 精锐 (Lv2): ${stats.byLevel[2] || 0} 人`);
|
||||||
|
console.log(` - 高手 (Lv3): ${stats.byLevel[3] || 0} 人`);
|
||||||
|
console.log(` - 大师 (Lv4): ${stats.byLevel[4] || 0} 人`);
|
||||||
|
console.log(` - 宗师 (Lv5): ${stats.byLevel[5] || 0} 人`);
|
||||||
|
console.log(` 按性别分布:`);
|
||||||
|
console.log(` - 男: ${stats.byGender[1]} 人`);
|
||||||
|
console.log(` - 女: ${stats.byGender[2]} 人`);
|
||||||
|
console.log(` 平均战力: ${Math.round(stats.totalPower / allUsers.length)}`);
|
||||||
|
console.log(` 总比赛场次: ${stats.totalMatches}`);
|
||||||
|
console.log(`\n🏆 排行榜前10名:`);
|
||||||
|
allUsers.slice(0, 10).forEach((user, index) => {
|
||||||
|
console.log(` ${index + 1}. ${user.real_name} - Lv${user.level} - 战力${user.power_score} - ${user.match_count}场 (${user.win_count}胜)`);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('\n✨ 模拟数据生成完成!');
|
||||||
|
process.exit(0);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 生成模拟数据失败:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
generateMockLadderData();
|
||||||
@ -103,9 +103,45 @@ function getFullUrl(path, req) {
|
|||||||
if (path.startsWith('http://') || path.startsWith('https://')) {
|
if (path.startsWith('http://') || path.startsWith('https://')) {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 相对路径,拼接服务器地址
|
// 相对路径,拼接服务器地址
|
||||||
const protocol = req?.protocol || 'http';
|
// 支持反向代理(nginx等)的 HTTPS 检测
|
||||||
const host = req?.get('host') || `localhost:${process.env.PORT || 3000}`;
|
let protocol = 'http';
|
||||||
|
if (req) {
|
||||||
|
// 优先使用 X-Forwarded-Proto 头(反向代理设置)
|
||||||
|
const forwardedProto = req.get('X-Forwarded-Proto');
|
||||||
|
if (forwardedProto) {
|
||||||
|
protocol = forwardedProto;
|
||||||
|
} else {
|
||||||
|
// 使用 req.protocol(Express 会自动处理)
|
||||||
|
protocol = req.protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果设置了环境变量强制使用 HTTPS
|
||||||
|
if (process.env.FORCE_HTTPS === 'true') {
|
||||||
|
protocol = 'https';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取主机地址
|
||||||
|
let host = `localhost:${process.env.PORT || 3000}`;
|
||||||
|
if (req) {
|
||||||
|
// 优先使用 X-Forwarded-Host(反向代理设置)
|
||||||
|
const forwardedHost = req.get('X-Forwarded-Host');
|
||||||
|
if (forwardedHost) {
|
||||||
|
host = forwardedHost;
|
||||||
|
} else {
|
||||||
|
host = req.get('host') || host;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果设置了 BASE_URL 环境变量,直接使用
|
||||||
|
if (process.env.BASE_URL) {
|
||||||
|
const baseUrl = process.env.BASE_URL.replace(/\/$/, ''); // 移除末尾斜杠
|
||||||
|
const pathWithoutLeadingSlash = path.startsWith('/') ? path : `/${path}`;
|
||||||
|
return `${baseUrl}${pathWithoutLeadingSlash}`;
|
||||||
|
}
|
||||||
|
|
||||||
return `${protocol}://${host}${path}`;
|
return `${protocol}://${host}${path}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user