Compare commits
113 Commits
57d1b9446b
...
feat/juguo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99c8026f61 | ||
| 05966b2acb | |||
|
|
4cf2b959b5 | ||
|
|
43610dcf99 | ||
|
|
05aa820466 | ||
|
|
b154e31f8f | ||
|
|
669ee2fe4e | ||
|
|
281ee2b746 | ||
|
|
132c74d27c | ||
|
|
6b6a4c9480 | ||
|
|
0f8dd44f5a | ||
| 82ba753b8b | |||
| 159d81ed12 | |||
| 22965eedf3 | |||
| 49935dd049 | |||
|
|
cab90aa1cb | ||
| 632da5112d | |||
| 28955e9da1 | |||
| 70a66fabdc | |||
| c47ebce43c | |||
| b0f4b5713d | |||
| f7f10f5d15 | |||
|
|
2bcdd93479 | ||
|
|
af2c472030 | ||
|
|
8d0ed5b1b3 | ||
|
|
e99986c52a | ||
|
|
4b2f6707cc | ||
|
|
a019fe473b | ||
|
|
1d0d2edaa2 | ||
| 5926e096b5 | |||
|
|
e07f2ad2d1 | ||
|
|
bfc6a149f0 | ||
|
|
6f73bb6d99 | ||
|
|
744169fe34 | ||
| 54b7a27af5 | |||
| 396ff4a347 | |||
|
|
b732bd361e | ||
|
|
5146894d92 | ||
|
|
07cf8e884e | ||
| 5416ea127c | |||
| a7bc517fae | |||
| 16b38539f6 | |||
|
|
0d46311bbc | ||
| e884b1f258 | |||
| 84159a4835 | |||
| 2acee85dd5 | |||
| ba72e0ec97 | |||
|
|
32f5339cc2 | ||
|
|
2cbbc7f432 | ||
| 694b00e011 | |||
| 87eaa31cef | |||
|
|
f131c9896d | ||
| b08f3325e6 | |||
| ff864fe64d | |||
|
|
da0ae6046c | ||
| 42025d49f8 | |||
|
|
536619ebfc | ||
|
|
5a10c73adf | ||
|
|
b29e000747 | ||
|
|
02841222a2 | ||
|
|
b417b3a4c2 | ||
|
|
9dca489aba | ||
|
|
8d729a0132 | ||
|
|
2d68a558da | ||
|
|
ce0a299b59 | ||
|
|
ca4b52570f | ||
|
|
cff9afd1e8 | ||
|
|
d149de1f42 | ||
|
|
969066591c | ||
| ebb7116c25 | |||
| 73bb56b1b2 | |||
| 9cde3a606c | |||
|
|
ee579df162 | ||
|
|
4c75368fe8 | ||
|
|
8abf6e6f2b | ||
|
|
0c83aab053 | ||
|
|
1cbec87f77 | ||
|
|
6d57654005 | ||
|
|
dade2e2491 | ||
|
|
4a6ac73ad7 | ||
|
|
de8677c64c | ||
|
|
3ab647f7c6 | ||
|
|
fa328f893d | ||
|
|
d7c24ca8b3 | ||
| 0a3cdbedd2 | |||
| af131f228a | |||
| b5f9d23615 | |||
| 76b105866c | |||
| 86581f3a11 | |||
| 29094c7e6a | |||
| 4578ca0cb1 | |||
| d5662e5810 | |||
| 9e53f7a9f5 | |||
| a0f7838895 | |||
| c9c19855c3 | |||
| 1b67693752 | |||
|
|
d90dcb053e | ||
|
|
e7ee8bc1de | ||
| 5aec62ae85 | |||
|
|
9f63a2369a | ||
|
|
e560d06106 | ||
|
|
4c5262441c | ||
|
|
293b9e6eba | ||
| c42055d2c3 | |||
| b7efbce737 | |||
|
|
26ab56fd1e | ||
|
|
28201d79b9 | ||
| e1c4990ada | |||
| 46a59ba282 | |||
|
|
7b620210a2 | ||
|
|
e2a8ed4e32 | ||
|
|
85566b448e | ||
|
|
0774cf5ae6 |
2
.env.dev_local
Normal file
2
.env.dev_local
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
APP_ENV=dev_local
|
||||||
|
TARO_APP_ID=wx815b533167eb7b53
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -8,4 +8,6 @@ node_modules/
|
|||||||
src/config/env.ts
|
src/config/env.ts
|
||||||
.vscode
|
.vscode
|
||||||
*.http
|
*.http
|
||||||
env.ts
|
.cursor
|
||||||
|
.codewiz
|
||||||
|
|
||||||
|
|||||||
@@ -149,3 +149,7 @@ src/
|
|||||||
## License
|
## License
|
||||||
|
|
||||||
MIT
|
MIT
|
||||||
|
|
||||||
|
"appid": "wx915ecf6c01bea4ec",
|
||||||
|
|
||||||
|
"appid": "wx815b533167eb7b53",
|
||||||
@@ -4,7 +4,11 @@ export default {
|
|||||||
quiet: false,
|
quiet: false,
|
||||||
stats: true
|
stats: true
|
||||||
},
|
},
|
||||||
mini: {},
|
mini: {
|
||||||
|
webpackChain(chain) {
|
||||||
|
chain.devtool('source-map')
|
||||||
|
}
|
||||||
|
},
|
||||||
h5: {},
|
h5: {},
|
||||||
// 添加这个配置来显示完整错误信息
|
// 添加这个配置来显示完整错误信息
|
||||||
compiler: {
|
compiler: {
|
||||||
|
|||||||
79
config/env.config.ts
Normal file
79
config/env.config.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
* 统一环境配置(dev/sit/pr)
|
||||||
|
* 构建时通过 APP_ENV 选择,defineConstants 注入业务代码
|
||||||
|
* project.config.json 的 appid 由 scripts/sync-project-config.js 同步
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type EnvType = "dev" | "dev_local" | "sit" | "pr";
|
||||||
|
|
||||||
|
export interface EnvConfig {
|
||||||
|
name: string;
|
||||||
|
apiBaseURL: string;
|
||||||
|
ossBaseURL: string;
|
||||||
|
appid: string;
|
||||||
|
timeout: number;
|
||||||
|
enableLog: boolean;
|
||||||
|
enableMock: boolean;
|
||||||
|
customerService: {
|
||||||
|
corpId: string;
|
||||||
|
serviceUrl: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseConfig = {
|
||||||
|
apiBaseURL: "https://tennis.bimwe.com",
|
||||||
|
ossBaseURL: "https://bimwe.oss-cn-shanghai.aliyuncs.com",
|
||||||
|
appid: "wx815b533167eb7b53", // 测试号
|
||||||
|
timeout: 15000,
|
||||||
|
enableLog: true,
|
||||||
|
enableMock: false,
|
||||||
|
customerService: {
|
||||||
|
corpId: "ww51fc969e8b76af82",
|
||||||
|
serviceUrl: "https://work.weixin.qq.com/kfid/kfc64085b93243c5c91",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const envConfigs: Record<EnvType, EnvConfig> = {
|
||||||
|
// 本地开发:API 指向本地或测试服
|
||||||
|
dev: {
|
||||||
|
name: "DEV",
|
||||||
|
// apiBaseURL: "http://localhost:9098",
|
||||||
|
...baseConfig
|
||||||
|
},
|
||||||
|
// 本地联调:API 指向本机
|
||||||
|
dev_local: {
|
||||||
|
name: "DEV_LOCAL",
|
||||||
|
|
||||||
|
...Object.assign(baseConfig, {
|
||||||
|
apiBaseURL: "http://localhost:9098",
|
||||||
|
})
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
// SIT 测试环境
|
||||||
|
sit: {
|
||||||
|
name: "SIT",
|
||||||
|
...Object.assign(baseConfig, {
|
||||||
|
apiBaseURL: "https://tennis.bimwe.com",
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// PR 生产环境
|
||||||
|
pr: {
|
||||||
|
name: "PR",
|
||||||
|
apiBaseURL: "https://youchang.qiongjingtiyu.com",
|
||||||
|
ossBaseURL: "https://youchang2026.oss-cn-shanghai.aliyuncs.com",
|
||||||
|
appid: "wx915ecf6c01bea4ec", // 生产小程序 appid,按实际填写
|
||||||
|
timeout: 10000,
|
||||||
|
enableLog: false,
|
||||||
|
enableMock: false,
|
||||||
|
customerService: {
|
||||||
|
corpId: "ww9a2d9a5d9410c664",
|
||||||
|
serviceUrl: "https://work.weixin.qq.com/kfid/kfcd355e162e0390684",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getEnvConfig(env: EnvType): EnvConfig {
|
||||||
|
return envConfigs[env];
|
||||||
|
}
|
||||||
128
config/env.ts
Normal file
128
config/env.ts
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import Taro from '@tarojs/taro'
|
||||||
|
|
||||||
|
// 环境类型
|
||||||
|
export type EnvType = 'development' | 'production'
|
||||||
|
|
||||||
|
// 环境配置接口
|
||||||
|
export interface EnvConfig {
|
||||||
|
name: string
|
||||||
|
apiBaseURL: string
|
||||||
|
timeout: number
|
||||||
|
enableLog: boolean
|
||||||
|
enableMock: boolean
|
||||||
|
// 客服配置
|
||||||
|
customerService: {
|
||||||
|
corpId: string
|
||||||
|
serviceUrl: string
|
||||||
|
phoneNumber?: string
|
||||||
|
email?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 各环境配置
|
||||||
|
const envConfigs: Record<EnvType, EnvConfig> = {
|
||||||
|
|
||||||
|
|
||||||
|
// 开发环境
|
||||||
|
development: {
|
||||||
|
name: '开发环境',
|
||||||
|
// apiBaseURL: 'https://tennis.bimwe.com',
|
||||||
|
apiBaseURL: 'http://localhost:9098',
|
||||||
|
timeout: 15000,
|
||||||
|
enableLog: true,
|
||||||
|
enableMock: false,
|
||||||
|
// 客服配置
|
||||||
|
customerService: {
|
||||||
|
corpId: 'ww51fc969e8b76af82', // 企业ID
|
||||||
|
serviceUrl: 'https://work.weixin.qq.com/kfid/kfc64085b93243c5c91',
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 生产环境1
|
||||||
|
// production: {
|
||||||
|
// name: '生产环境1',
|
||||||
|
// apiBaseURL: 'https://tennis.bimwe.com',
|
||||||
|
// timeout: 10000,
|
||||||
|
// enableLog: false,
|
||||||
|
// enableMock: false,
|
||||||
|
// // 客服配置
|
||||||
|
// customerService: {
|
||||||
|
// corpId: 'ww51fc969e8b76af82', // 企业ID
|
||||||
|
// serviceUrl: 'https://work.weixin.qq.com/kfid/kfc64085b93243c5c91',
|
||||||
|
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
|
||||||
|
// 生产环境2
|
||||||
|
production: {
|
||||||
|
name: '生产环境2',
|
||||||
|
apiBaseURL: 'https://youchang.qiongjingtiyu.com',
|
||||||
|
timeout: 10000,
|
||||||
|
enableLog: false,
|
||||||
|
enableMock: false,
|
||||||
|
// 客服配置
|
||||||
|
customerService: {
|
||||||
|
corpId: 'ww9a2d9a5d9410c664', // 企业ID
|
||||||
|
serviceUrl: 'https://work.weixin.qq.com/kfid/kfcd355e162e0390684',
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前环境
|
||||||
|
export const getCurrentEnv = (): EnvType => {
|
||||||
|
// 在小程序环境中,使用默认逻辑判断环境
|
||||||
|
// 可以根据实际需要配置不同的判断逻辑
|
||||||
|
|
||||||
|
// 可以根据实际部署情况添加更多判断逻辑
|
||||||
|
// 比如通过 Taro.getEnv() 获取当前平台环境
|
||||||
|
|
||||||
|
const isProd = process.env.NODE_ENV === 'production'
|
||||||
|
if (isProd) {
|
||||||
|
return 'production'
|
||||||
|
} else {
|
||||||
|
return 'development'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前环境配置
|
||||||
|
export const getCurrentConfig = (): EnvConfig => {
|
||||||
|
const env = getCurrentEnv()
|
||||||
|
return envConfigs[env]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取指定环境配置
|
||||||
|
export const getEnvConfig = (env: EnvType): EnvConfig => {
|
||||||
|
return envConfigs[env]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否为开发环境
|
||||||
|
export const isDevelopment = (): boolean => {
|
||||||
|
return getCurrentEnv() === 'development'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否为生产环境
|
||||||
|
export const isProduction = (): boolean => {
|
||||||
|
return getCurrentEnv() === 'production'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 环境配置调试信息
|
||||||
|
export const getEnvInfo = () => {
|
||||||
|
const config = getCurrentConfig()
|
||||||
|
return {
|
||||||
|
env: getCurrentEnv(),
|
||||||
|
config,
|
||||||
|
taroEnv: Taro.getEnv(),
|
||||||
|
platform: Taro.getEnv() === Taro.ENV_TYPE.WEAPP ? '微信小程序' :
|
||||||
|
Taro.getEnv() === Taro.ENV_TYPE.WEB ? 'Web' :
|
||||||
|
Taro.getEnv() === Taro.ENV_TYPE.RN ? 'React Native' : '未知'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出当前环境配置(方便直接使用)
|
||||||
|
export default getCurrentConfig()
|
||||||
@@ -2,11 +2,21 @@ import { defineConfig, type UserConfigExport } from '@tarojs/cli'
|
|||||||
import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin'
|
import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin'
|
||||||
import devConfig from './dev'
|
import devConfig from './dev'
|
||||||
import prodConfig from './prod'
|
import prodConfig from './prod'
|
||||||
// import vitePluginImp from 'vite-plugin-imp'
|
import { getEnvConfig, type EnvType } from './env.config'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
|
||||||
|
// 环境:dev(本地) | dev_local(联调) | sit(测试) | pr(生产)
|
||||||
|
const ENV_LIST: EnvType[] = ['dev', 'dev_local', 'sit', 'pr']
|
||||||
|
|
||||||
// https://taro-docs.jd.com/docs/next/config#defineconfig-辅助函数
|
// https://taro-docs.jd.com/docs/next/config#defineconfig-辅助函数
|
||||||
export default defineConfig<'webpack5'>(async (merge, { command, mode }) => {
|
export default defineConfig<'webpack5'>(async (merge, { command, mode }) => {
|
||||||
|
const appEnv = (
|
||||||
|
(ENV_LIST.includes(mode as EnvType) ? mode : process.env.APP_ENV) ||
|
||||||
|
(process.env.NODE_ENV === 'production' ? 'pr' : 'dev')
|
||||||
|
) as EnvType
|
||||||
|
|
||||||
|
const envConfig = getEnvConfig(appEnv)
|
||||||
|
|
||||||
const baseConfig: UserConfigExport<'webpack5'> = {
|
const baseConfig: UserConfigExport<'webpack5'> = {
|
||||||
projectName: 'playBallTogether',
|
projectName: 'playBallTogether',
|
||||||
date: '2025-8-9',
|
date: '2025-8-9',
|
||||||
@@ -22,6 +32,13 @@ export default defineConfig<'webpack5'>(async (merge, { command, mode }) => {
|
|||||||
outputRoot: 'dist',
|
outputRoot: 'dist',
|
||||||
plugins: ['@tarojs/plugin-html'],
|
plugins: ['@tarojs/plugin-html'],
|
||||||
defineConstants: {
|
defineConstants: {
|
||||||
|
'process.env.APP_ENV': JSON.stringify(appEnv),
|
||||||
|
'process.env.API_BASE_URL': JSON.stringify(envConfig.apiBaseURL),
|
||||||
|
'process.env.OSS_BASE_URL': JSON.stringify(envConfig.ossBaseURL),
|
||||||
|
'process.env.ENABLE_LOG': JSON.stringify(envConfig.enableLog),
|
||||||
|
'process.env.TIMEOUT': JSON.stringify(envConfig.timeout),
|
||||||
|
'process.env.CUSTOMER_CORP_ID': JSON.stringify(envConfig.customerService.corpId),
|
||||||
|
'process.env.CUSTOMER_SERVICE_URL': JSON.stringify(envConfig.customerService.serviceUrl),
|
||||||
},
|
},
|
||||||
alias: {
|
alias: {
|
||||||
'@': path.resolve(__dirname, '..', 'src'),
|
'@': path.resolve(__dirname, '..', 'src'),
|
||||||
@@ -76,6 +93,9 @@ export default defineConfig<'webpack5'>(async (merge, { command, mode }) => {
|
|||||||
},
|
},
|
||||||
// @ts-expect-error: Taro 类型定义缺少 mini.hot
|
// @ts-expect-error: Taro 类型定义缺少 mini.hot
|
||||||
hot: true,
|
hot: true,
|
||||||
|
projectConfig: {
|
||||||
|
appid: envConfig.appid,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
h5: {
|
h5: {
|
||||||
publicPath: '/',
|
publicPath: '/',
|
||||||
|
|||||||
34
package.json
34
package.json
@@ -10,32 +10,17 @@
|
|||||||
"framework": "React"
|
"framework": "React"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm run build:weapp ",
|
|
||||||
"dev": "npm run dev:weapp",
|
"dev": "npm run dev:weapp",
|
||||||
"build:weapp": "taro build --type weapp --mode production",
|
"dev:local": "npm run dev:weapp:dev_local",
|
||||||
"build:swan": "taro build --type swan",
|
"dev:weapp": "node scripts/sync-project-config.js dev && taro build --type weapp --mode dev --watch",
|
||||||
"build:alipay": "taro build --type alipay",
|
"dev:weapp:dev_local": "node scripts/sync-project-config.js dev_local && taro build --type weapp --mode dev_local --watch",
|
||||||
"build:tt": "taro build --type tt",
|
"build": "npm run build:weapp",
|
||||||
"build:h5": "taro build --type h5",
|
"build:weapp": "node scripts/sync-project-config.js pr && taro build --type weapp --mode pr",
|
||||||
"build:rn": "taro build --type rn",
|
"build:sit": "node scripts/sync-project-config.js sit && taro build --type weapp --mode sit",
|
||||||
"build:qq": "taro build --type qq",
|
"build:pr": "node scripts/sync-project-config.js pr && taro build --type weapp --mode pr",
|
||||||
"build:jd": "taro build --type jd",
|
"dev:h5": "npm run build:h5 -- --watch"
|
||||||
"build:quickapp": "taro build --type quickapp",
|
|
||||||
"dev:weapp": "npm run build:weapp -- --watch",
|
|
||||||
"dev:swan": "npm run build:swan -- --watch",
|
|
||||||
"dev:alipay": "npm run build:alipay -- --watch",
|
|
||||||
"dev:tt": "npm run build:tt -- --watch",
|
|
||||||
"dev:h5": "npm run build:h5 -- --watch",
|
|
||||||
"dev:rn": "npm run build:rn -- --watch",
|
|
||||||
"dev:qq": "npm run build:qq -- --watch",
|
|
||||||
"dev:jd": "npm run build:jd -- --watch",
|
|
||||||
"dev:quickapp": "npm run build:quickapp -- --watch"
|
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": ["last 3 versions", "Android >= 4.1", "ios >= 8"],
|
||||||
"last 3 versions",
|
|
||||||
"Android >= 4.1",
|
|
||||||
"ios >= 8"
|
|
||||||
],
|
|
||||||
"author": "",
|
"author": "",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/plugin-transform-runtime": "^7.28.3",
|
"@babel/plugin-transform-runtime": "^7.28.3",
|
||||||
@@ -57,6 +42,7 @@
|
|||||||
"@tarojs/shared": "4.1.5",
|
"@tarojs/shared": "4.1.5",
|
||||||
"@tarojs/taro": "4.1.5",
|
"@tarojs/taro": "4.1.5",
|
||||||
"babel-plugin-transform-remove-console": "^6.9.4",
|
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||||
|
"classnames": "^2.5.1",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"qweather-icons": "^1.8.0",
|
"qweather-icons": "^1.8.0",
|
||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
|
|||||||
@@ -15,9 +15,10 @@
|
|||||||
"useStaticServer": false,
|
"useStaticServer": false,
|
||||||
"useLanDebug": false,
|
"useLanDebug": false,
|
||||||
"showES6CompileOption": false,
|
"showES6CompileOption": false,
|
||||||
"compileHotReLoad": false,
|
"compileHotReLoad": true,
|
||||||
"checkInvalidKey": true,
|
"checkInvalidKey": true,
|
||||||
"ignoreDevUnusedFiles": true,
|
"ignoreDevUnusedFiles": true,
|
||||||
"bigPackageSizeSupport": true
|
"bigPackageSizeSupport": true,
|
||||||
|
"useIsolateContext": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
25
scripts/sync-project-config.js
Normal file
25
scripts/sync-project-config.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
require('ts-node/register/transpile-only');
|
||||||
|
|
||||||
|
const envArg = process.argv[2];
|
||||||
|
const appEnv = envArg || process.env.APP_ENV || (process.env.NODE_ENV === 'production' ? 'pr' : 'dev');
|
||||||
|
|
||||||
|
const envConfigPath = path.resolve(__dirname, '../config/env.config.ts');
|
||||||
|
const { envConfigs } = require(envConfigPath);
|
||||||
|
|
||||||
|
const config = envConfigs[appEnv];
|
||||||
|
if (!config) {
|
||||||
|
console.error(`[sync-project-config] Unknown APP_ENV: ${appEnv}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const projectConfigPath = path.resolve(__dirname, '../project.config.json');
|
||||||
|
const projectConfigRaw = fs.readFileSync(projectConfigPath, 'utf-8');
|
||||||
|
const projectConfig = JSON.parse(projectConfigRaw);
|
||||||
|
|
||||||
|
projectConfig.appid = config.appid;
|
||||||
|
|
||||||
|
fs.writeFileSync(projectConfigPath, JSON.stringify(projectConfig, null, 2) + '\n', 'utf-8');
|
||||||
|
console.log(`[sync-project-config] project.config.json appid -> ${config.appid} (${appEnv})`);
|
||||||
@@ -57,6 +57,7 @@ export default defineAppConfig({
|
|||||||
"ntrp-evaluate/index", // NTRP评估页
|
"ntrp-evaluate/index", // NTRP评估页
|
||||||
"enable_notification/index", // 开启消息通知
|
"enable_notification/index", // 开启消息通知
|
||||||
"emptyState/index", // 空状态页面
|
"emptyState/index", // 空状态页面
|
||||||
|
"bannerDetail/index", // Banner 图片详情页
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ page {
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Quicksand";
|
font-family: "Quicksand";
|
||||||
// 注意:此路径来自 @/config/api.ts 中的 OSS_BASE_URL 配置
|
// 注意:此路径对应 @/config/api.ts 中的 OSS_BASE
|
||||||
// 如需修改,请更新配置文件中的 OSS_BASE_URL
|
|
||||||
src: url("https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/other/57dc951f-f10e-45b7-9157-0b1e468187fd.ttf") format("truetype");
|
src: url("https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/other/57dc951f-f10e-45b7-9157-0b1e468187fd.ttf") format("truetype");
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
@@ -34,7 +34,7 @@ function toast(msg) {
|
|||||||
|
|
||||||
interface CommentInputProps {
|
interface CommentInputProps {
|
||||||
onConfirm?: (
|
onConfirm?: (
|
||||||
value: { content: string } & Partial<CommentInputReplyParamsType>
|
value: { content: string } & Partial<CommentInputReplyParamsType>,
|
||||||
) => void;
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,10 +49,8 @@ interface CommentInputReplyParamsType {
|
|||||||
nickname: string;
|
nickname: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CommentInput = forwardRef<CommentInputRef, CommentInputProps>(function (
|
const CommentInput = forwardRef<CommentInputRef, CommentInputProps>(
|
||||||
props,
|
function (props, ref) {
|
||||||
ref
|
|
||||||
) {
|
|
||||||
const { onConfirm } = props;
|
const { onConfirm } = props;
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [value, setValue] = useState("");
|
const [value, setValue] = useState("");
|
||||||
@@ -73,9 +71,8 @@ const CommentInput = forwardRef<CommentInputRef, CommentInputProps>(function (
|
|||||||
initializeKeyboardListener();
|
initializeKeyboardListener();
|
||||||
|
|
||||||
// 添加本地监听器
|
// 添加本地监听器
|
||||||
const removeListener = addListener((height, visible) => {
|
const removeListener = addListener(() => {
|
||||||
console.log("PublishBall 收到键盘变化:", height, visible);
|
// 布局是否响应交由 shouldReactToKeyboard 决定
|
||||||
// 这里只记录或用于其他逻辑,布局是否响应交由 shouldReactToKeyboard 决定
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@@ -112,7 +109,6 @@ const CommentInput = forwardRef<CommentInputRef, CommentInputProps>(function (
|
|||||||
setValue("");
|
setValue("");
|
||||||
inputDomRef.current && inputDomRef.current?.blur();
|
inputDomRef.current && inputDomRef.current?.blur();
|
||||||
}
|
}
|
||||||
console.log(keyboardHeight, "keyboardHeight");
|
|
||||||
return (
|
return (
|
||||||
<CommonPopup
|
<CommonPopup
|
||||||
visible={visible}
|
visible={visible}
|
||||||
@@ -124,7 +120,9 @@ const CommentInput = forwardRef<CommentInputRef, CommentInputProps>(function (
|
|||||||
// height: "60px!important",
|
// height: "60px!important",
|
||||||
minHeight: "unset",
|
minHeight: "unset",
|
||||||
bottom:
|
bottom:
|
||||||
isKeyboardVisible && keyboardHeight > 0 ? `${keyboardHeight}px` : "0",
|
isKeyboardVisible && keyboardHeight > 0
|
||||||
|
? `${keyboardHeight}px`
|
||||||
|
: "0",
|
||||||
}}
|
}}
|
||||||
enableDragToClose={false}
|
enableDragToClose={false}
|
||||||
>
|
>
|
||||||
@@ -149,7 +147,7 @@ const CommentInput = forwardRef<CommentInputRef, CommentInputProps>(function (
|
|||||||
<View
|
<View
|
||||||
className={classnames(
|
className={classnames(
|
||||||
styles.limit,
|
styles.limit,
|
||||||
value.length > 200 ? styles.red : ""
|
value.length > 200 ? styles.red : "",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Text>{value.length}</Text>/<Text>200</Text>
|
<Text>{value.length}</Text>/<Text>200</Text>
|
||||||
@@ -161,7 +159,8 @@ const CommentInput = forwardRef<CommentInputRef, CommentInputProps>(function (
|
|||||||
</View>
|
</View>
|
||||||
</CommonPopup>
|
</CommonPopup>
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
function isReplyComment(item: BaseComment<any>): item is ReplyComment {
|
function isReplyComment(item: BaseComment<any>): item is ReplyComment {
|
||||||
return "reply_to_user" in item;
|
return "reply_to_user" in item;
|
||||||
@@ -208,7 +207,7 @@ function CommentItem(props: {
|
|||||||
className={classnames(
|
className={classnames(
|
||||||
styles.commentItem,
|
styles.commentItem,
|
||||||
blink_id === comment.id && styles.blink,
|
blink_id === comment.id && styles.blink,
|
||||||
styles.weight_super
|
styles.weight_super,
|
||||||
)}
|
)}
|
||||||
key={comment.id}
|
key={comment.id}
|
||||||
id={`comment_id_${comment.id}`}
|
id={`comment_id_${comment.id}`}
|
||||||
@@ -293,7 +292,8 @@ function CommentItem(props: {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{!isReplyComment(comment) &&
|
{!isReplyComment(comment) &&
|
||||||
comment.replies.length !== comment.reply_count && (
|
comment.replies.length !== comment.reply_count &&
|
||||||
|
comment.replies.length > 3 && (
|
||||||
<View
|
<View
|
||||||
className={styles.viewMore}
|
className={styles.viewMore}
|
||||||
onClick={() => handleLoadMore(comment)}
|
onClick={() => handleLoadMore(comment)}
|
||||||
@@ -313,7 +313,7 @@ export default forwardRef(function Comments(
|
|||||||
message_id?: number;
|
message_id?: number;
|
||||||
onScrollTo: (id: string) => void;
|
onScrollTo: (id: string) => void;
|
||||||
},
|
},
|
||||||
ref
|
ref,
|
||||||
) {
|
) {
|
||||||
const { game_id, publisher_id, message_id, onScrollTo } = props;
|
const { game_id, publisher_id, message_id, onScrollTo } = props;
|
||||||
const [comments, setComments] = useState<Comment[]>([]);
|
const [comments, setComments] = useState<Comment[]>([]);
|
||||||
@@ -371,7 +371,7 @@ export default forwardRef(function Comments(
|
|||||||
replies: [res.data, ...item.replies].sort((a, b) =>
|
replies: [res.data, ...item.replies].sort((a, b) =>
|
||||||
dayjs(a.create_time).isAfter(dayjs(b.create_time))
|
dayjs(a.create_time).isAfter(dayjs(b.create_time))
|
||||||
? 1
|
? 1
|
||||||
: -1
|
: -1,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -435,7 +435,7 @@ export default forwardRef(function Comments(
|
|||||||
item.replies.splice(
|
item.replies.splice(
|
||||||
page === 1 ? 0 : page * PAGESIZE - 1,
|
page === 1 ? 0 : page * PAGESIZE - 1,
|
||||||
newReplies.length,
|
newReplies.length,
|
||||||
...newReplies
|
...newReplies,
|
||||||
);
|
);
|
||||||
item.reply_count = res.data.count;
|
item.reply_count = res.data.count;
|
||||||
}
|
}
|
||||||
@@ -502,7 +502,7 @@ export default forwardRef(function Comments(
|
|||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
replies: item.replies.filter(
|
replies: item.replies.filter(
|
||||||
(replyItem) => replyItem.id !== id
|
(replyItem) => replyItem.id !== id,
|
||||||
),
|
),
|
||||||
reply_count: item.reply_count - 1,
|
reply_count: item.reply_count - 1,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,11 +3,18 @@
|
|||||||
.common-popup {
|
.common-popup {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 9999 !important;
|
z-index: 9999 !important;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
max-height: calc(100vh - 10px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: theme.$page-background-color;
|
||||||
&:global(.nut-popup-bottom.nut-popup-round) {
|
&:global(.nut-popup-bottom.nut-popup-round) {
|
||||||
border-radius: 20px 20px 0 0 !important;
|
border-radius: 20px 20px 0 0 !important;
|
||||||
}
|
}
|
||||||
.common-popup__drag-handle-container {
|
.common-popup__drag-handle-container {
|
||||||
position: position;
|
position: relative;
|
||||||
|
height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.common-popup__drag-handle {
|
.common-popup__drag-handle {
|
||||||
@@ -31,12 +38,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
max-height: calc(100vh - 10px);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
background-color: theme.$page-background-color;
|
|
||||||
|
|
||||||
// .common-popup__header {
|
// .common-popup__header {
|
||||||
// padding: 12px 16px;
|
// padding: 12px 16px;
|
||||||
|
|||||||
215
src/components/CustomPopup/CustomPopup.tsx
Normal file
215
src/components/CustomPopup/CustomPopup.tsx
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
import React, { useRef, useState, useEffect } from 'react'
|
||||||
|
import type { CSSProperties, ReactNode } from 'react'
|
||||||
|
import { View, Text } from '@tarojs/components'
|
||||||
|
import { Button } from '@nutui/nutui-react-taro'
|
||||||
|
import { useKeyboardHeight } from '@/store/keyboardStore'
|
||||||
|
import styles from './index.module.scss'
|
||||||
|
|
||||||
|
export interface CustomPopupProps {
|
||||||
|
visible: boolean
|
||||||
|
onClose: () => void
|
||||||
|
title?: ReactNode
|
||||||
|
showHeader?: boolean
|
||||||
|
hideFooter?: boolean
|
||||||
|
cancelText?: string
|
||||||
|
confirmText?: string
|
||||||
|
onCancel?: () => void
|
||||||
|
onConfirm?: () => void
|
||||||
|
children?: ReactNode
|
||||||
|
className?: string
|
||||||
|
style?: CSSProperties
|
||||||
|
// 与 CommonPopup 保持入参一致
|
||||||
|
position?: 'center' | 'bottom' | 'top' | 'left' | 'right'
|
||||||
|
round?: boolean
|
||||||
|
zIndex?: number
|
||||||
|
enableDragToClose?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const CustomPopup: React.FC<CustomPopupProps> = ({
|
||||||
|
visible,
|
||||||
|
onClose,
|
||||||
|
title,
|
||||||
|
showHeader = false,
|
||||||
|
hideFooter = false,
|
||||||
|
cancelText = '返回',
|
||||||
|
confirmText = '完成',
|
||||||
|
onCancel,
|
||||||
|
onConfirm,
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
style,
|
||||||
|
position = 'bottom',
|
||||||
|
round = true,
|
||||||
|
zIndex,
|
||||||
|
enableDragToClose = true,
|
||||||
|
}) => {
|
||||||
|
const [dragOffset, setDragOffset] = useState(0)
|
||||||
|
const [isDragging, setIsDragging] = useState(false)
|
||||||
|
const touchStartY = useRef(0)
|
||||||
|
|
||||||
|
// 使用全局键盘状态
|
||||||
|
const { keyboardHeight, isKeyboardVisible, addListener, initializeKeyboardListener, setKeyboardVisible } = useKeyboardHeight()
|
||||||
|
|
||||||
|
// 当弹窗显示时,设置键盘为不可见
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible) {
|
||||||
|
setKeyboardVisible(false)
|
||||||
|
}
|
||||||
|
}, [visible, setKeyboardVisible])
|
||||||
|
|
||||||
|
// 使用全局键盘状态监听
|
||||||
|
useEffect(() => {
|
||||||
|
// 初始化全局键盘监听器
|
||||||
|
initializeKeyboardListener()
|
||||||
|
|
||||||
|
// 添加本地监听器
|
||||||
|
const removeListener = addListener((height, visible) => {
|
||||||
|
console.log('CustomPopup 收到键盘变化:', height, visible)
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
removeListener()
|
||||||
|
}
|
||||||
|
}, [initializeKeyboardListener, addListener])
|
||||||
|
|
||||||
|
if (!visible) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
if (onCancel) {
|
||||||
|
onCancel()
|
||||||
|
} else {
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTouchStart = (e: any) => {
|
||||||
|
if (!enableDragToClose) return
|
||||||
|
|
||||||
|
touchStartY.current = e.touches[0].clientY
|
||||||
|
setIsDragging(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTouchMove = (e: any) => {
|
||||||
|
if (!enableDragToClose || !isDragging) return
|
||||||
|
|
||||||
|
const currentY = e.touches[0].clientY
|
||||||
|
const deltaY = currentY - touchStartY.current
|
||||||
|
|
||||||
|
if (deltaY > 0) {
|
||||||
|
setDragOffset(Math.min(deltaY, 100))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTouchEnd = () => {
|
||||||
|
if (!enableDragToClose || !isDragging) return
|
||||||
|
|
||||||
|
setIsDragging(false)
|
||||||
|
|
||||||
|
if (dragOffset > 50) {
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
setDragOffset(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const overlayAlignItems =
|
||||||
|
position === 'center'
|
||||||
|
? 'center'
|
||||||
|
: position === 'top'
|
||||||
|
? 'flex-start'
|
||||||
|
: 'flex-end'
|
||||||
|
|
||||||
|
const handleOverlayClick = () => {
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 阻止弹窗内的触摸事件冒泡
|
||||||
|
const handleTouchMoveInPopup = (e: any) => {
|
||||||
|
if (!isKeyboardVisible) {
|
||||||
|
e.stopPropagation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
className={styles['custom-popup-overlay']}
|
||||||
|
style={{ zIndex: zIndex ?? undefined, alignItems: overlayAlignItems }}
|
||||||
|
onClick={handleOverlayClick}
|
||||||
|
>
|
||||||
|
<View className={styles['custom-popup-move']} onTouchMove={handleTouchMoveInPopup} catchMove></View>
|
||||||
|
<View
|
||||||
|
className={`${styles['custom-popup']} ${className ? className : ''}`}
|
||||||
|
style={{
|
||||||
|
...style,
|
||||||
|
paddingBottom: isKeyboardVisible ? `${keyboardHeight}px` : undefined,
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{enableDragToClose && (
|
||||||
|
<View
|
||||||
|
className={styles['custom-popup__drag-handle-container']}
|
||||||
|
onTouchStart={handleTouchStart}
|
||||||
|
onTouchMove={handleTouchMove}
|
||||||
|
onTouchEnd={handleTouchEnd}
|
||||||
|
>
|
||||||
|
<View
|
||||||
|
className={styles['custom-popup__drag-handle']}
|
||||||
|
style={{
|
||||||
|
transform: `translateX(-50%) translateY(${dragOffset * 0.3}px)`,
|
||||||
|
opacity: isDragging ? 0.8 : 1,
|
||||||
|
transition: isDragging ? 'none' : 'all 0.3s ease-out',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{showHeader && (
|
||||||
|
<View className={styles['custom-popup__header']}>
|
||||||
|
{typeof title === 'string' ? (
|
||||||
|
<Text className={styles['custom-popup__title']}>{title}</Text>
|
||||||
|
) : (
|
||||||
|
title
|
||||||
|
)}
|
||||||
|
<View className={styles['close_button']} onClick={onClose}>
|
||||||
|
<View className={styles['close_icon']}>
|
||||||
|
<View className={styles['close_line']} />
|
||||||
|
<View className={styles['close_line']} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<View className={styles['custom-popup__body']}>{children}</View>
|
||||||
|
|
||||||
|
{!hideFooter && !isKeyboardVisible && (
|
||||||
|
<View className={styles['custom-popup__footer']}>
|
||||||
|
<Button
|
||||||
|
className={`${styles['custom-popup__btn']} ${styles['custom-popup__btn-cancel']}`}
|
||||||
|
type="default"
|
||||||
|
size="small"
|
||||||
|
onClick={handleCancel}
|
||||||
|
>
|
||||||
|
{cancelText}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className={`${styles['custom-popup__btn']} ${styles['custom-popup__btn-confirm']}`}
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
onClick={onConfirm}
|
||||||
|
>
|
||||||
|
{confirmText}
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomPopup
|
||||||
|
|
||||||
155
src/components/CustomPopup/index.module.scss
Normal file
155
src/components/CustomPopup/index.module.scss
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
@use "~@/scss/themeColor.scss" as theme;
|
||||||
|
|
||||||
|
.custom-popup-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 999;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.custom-popup-move{
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 998;
|
||||||
|
}
|
||||||
|
.custom-popup {
|
||||||
|
position: relative;
|
||||||
|
z-index: 999;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: theme.$page-background-color;
|
||||||
|
border-radius: 20px 20px 0 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: padding-bottom 0.3s ease;
|
||||||
|
.custom-popup__drag-handle-container {
|
||||||
|
position: relative;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-popup__drag-handle {
|
||||||
|
position: absolute;
|
||||||
|
top: 6px;
|
||||||
|
left: 50%;
|
||||||
|
width: 90px;
|
||||||
|
height: 30px;
|
||||||
|
z-index: 10;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
width: 32px;
|
||||||
|
height: 4px;
|
||||||
|
background-color: rgba(22, 24, 35, 0.2);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-popup__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 16px 20px;
|
||||||
|
|
||||||
|
.custom-popup__title {
|
||||||
|
font-family: "PingFang SC";
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 1.27em;
|
||||||
|
color: #000000;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close_button {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0px 4px 36px 0px rgba(0, 0, 0, 0.06);
|
||||||
|
|
||||||
|
.close_icon {
|
||||||
|
position: relative;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
|
||||||
|
.close_line {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 17px;
|
||||||
|
height: 3px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #000000;
|
||||||
|
transform: translate(-50%, -50%) rotate(45deg);
|
||||||
|
|
||||||
|
&:nth-child(2) {
|
||||||
|
transform: translate(-50%, -50%) rotate(-45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-popup__body {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-popup__footer {
|
||||||
|
padding: 8px 10px 0 10px;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
background: #fafafa;
|
||||||
|
padding-bottom: max(10px, env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-popup__btn {
|
||||||
|
flex: 1;
|
||||||
|
font-feature-settings: "liga" off, "clig" off;
|
||||||
|
font-family: "PingFang SC";
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-popup__btn-cancel {
|
||||||
|
background: #f5f6f7;
|
||||||
|
color: #1f2329;
|
||||||
|
border: none;
|
||||||
|
width: 154px;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 12px !important;
|
||||||
|
border: 0.5px solid rgba(0, 0, 0, 0.06);
|
||||||
|
background: #fff;
|
||||||
|
padding: 4px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-popup__btn-confirm {
|
||||||
|
width: 154px;
|
||||||
|
height: 44px;
|
||||||
|
border: 0.5px solid rgba(0, 0, 0, 0.06);
|
||||||
|
background: #000;
|
||||||
|
border-radius: 12px !important;
|
||||||
|
padding: 4px 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
4
src/components/CustomPopup/index.ts
Normal file
4
src/components/CustomPopup/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import CustomPopup from './CustomPopup'
|
||||||
|
export default CustomPopup
|
||||||
|
export * from './CustomPopup'
|
||||||
|
|
||||||
@@ -85,6 +85,10 @@
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: #3c3c43;
|
color: #3c3c43;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap:4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.distanceWrap {
|
.distanceWrap {
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
import { useRef, useState, useEffect } from "react";
|
import { useRef, useState, useEffect } from "react";
|
||||||
import { Menu } from "@nutui/nutui-react-taro";
|
import { Menu } from "@nutui/nutui-react-taro";
|
||||||
import { Image, View, ScrollView } from "@tarojs/components";
|
import { Image, View, ScrollView } from "@tarojs/components";
|
||||||
|
import Taro from "@tarojs/taro";
|
||||||
import img from "@/config/images";
|
import img from "@/config/images";
|
||||||
import Bubble from "../Bubble";
|
import Bubble from "../Bubble";
|
||||||
import { useListState } from "@/store/listStore";
|
import { useListState, useListStore } from "@/store/listStore";
|
||||||
|
import { getCurrentLocation } from "@/utils/locationUtils";
|
||||||
|
import { updateUserLocation } from "@/services/userService";
|
||||||
|
import { useGlobalState } from "@/store/global";
|
||||||
|
import { useUserActions } from "@/store/userStore";
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
|
|
||||||
const DistanceQuickFilterV2 = (props) => {
|
const DistanceQuickFilterV2 = (props) => {
|
||||||
@@ -19,15 +24,19 @@ const DistanceQuickFilterV2 = (props) => {
|
|||||||
quickValue,
|
quickValue,
|
||||||
districtValue, // 新增:行政区选中值
|
districtValue, // 新增:行政区选中值
|
||||||
onMenuVisibleChange, // 菜单展开/收起回调
|
onMenuVisibleChange, // 菜单展开/收起回调
|
||||||
|
onRelocate, // 重新定位回调
|
||||||
} = props;
|
} = props;
|
||||||
const cityRef = useRef(null);
|
const cityRef = useRef(null);
|
||||||
const quickRef = useRef(null);
|
const quickRef = useRef(null);
|
||||||
const [changePosition, setChangePosition] = useState<number[]>([]);
|
const [changePosition, setChangePosition] = useState<number[]>([]);
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
const [keys, setKeys] = useState(0);
|
const [keys, setKeys] = useState(0);
|
||||||
|
const [isRelocating, setIsRelocating] = useState(false);
|
||||||
// 从 store 获取当前城市信息
|
// 从 store 获取当前城市信息
|
||||||
const { area } = useListState();
|
const { area } = useListState();
|
||||||
const currentCity = area?.at(-1) || ""; // 获取省份/城市名称
|
const currentCity = area?.at(-1) || ""; // 获取省份/城市名称
|
||||||
|
const { updateState } = useGlobalState() || {};
|
||||||
|
const { fetchUserInfo, updateCache } = useUserActions();
|
||||||
|
|
||||||
// 全城筛选显示的标题 - 如果选择了行政区,显示行政区名称
|
// 全城筛选显示的标题 - 如果选择了行政区,显示行政区名称
|
||||||
const getCityTitle = () => {
|
const getCityTitle = () => {
|
||||||
@@ -79,6 +88,64 @@ const DistanceQuickFilterV2 = (props) => {
|
|||||||
index === 1 && (quickRef.current as any)?.toggle(false);
|
index === 1 && (quickRef.current as any)?.toggle(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// 重新获取当前位置,调用接口把位置传递后端
|
||||||
|
const handleRelocate = async () => {
|
||||||
|
if (isRelocating) return;
|
||||||
|
|
||||||
|
setIsRelocating(true);
|
||||||
|
(Taro as any).showLoading({ title: '定位中...', mask: true });
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取当前位置
|
||||||
|
const location = await getCurrentLocation();
|
||||||
|
|
||||||
|
if (location && location.latitude && location.longitude) {
|
||||||
|
// 更新 store 中的位置信息
|
||||||
|
updateState?.({ location });
|
||||||
|
|
||||||
|
// 调用接口把位置传递给后端,传递一个值代表强制更新
|
||||||
|
const response = await updateUserLocation(location.latitude, location.longitude, true);
|
||||||
|
|
||||||
|
// 如果接口返回成功,重新调用用户信息接口来更新 USER_SELECTED_CITY
|
||||||
|
if (response?.code === 0) {
|
||||||
|
|
||||||
|
// 删除 缓存
|
||||||
|
(Taro as any).removeStorageSync("USER_SELECTED_CITY");
|
||||||
|
|
||||||
|
// 延时一下
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 600));
|
||||||
|
// 先清除缓存和 area,确保使用最新的用户信息
|
||||||
|
await updateCache( [ response.data.last_location_province, response.data.last_location_city ]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
(Taro as any).showToast({
|
||||||
|
title: '定位成功',
|
||||||
|
icon: 'success',
|
||||||
|
duration: 1500,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 通知父组件位置已更新,可以刷新列表
|
||||||
|
if (onRelocate) {
|
||||||
|
onRelocate(location);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('获取位置信息失败');
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('重新定位失败:', error);
|
||||||
|
(Taro as any).showToast({
|
||||||
|
title: error?.message || '定位失败,请检查定位权限',
|
||||||
|
icon: 'none',
|
||||||
|
duration: 2000,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsRelocating(false);
|
||||||
|
(Taro as any).hideLoading();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 监听菜单状态变化,通知父组件
|
// 监听菜单状态变化,通知父组件
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onMenuVisibleChange?.(isMenuOpen);
|
onMenuVisibleChange?.(isMenuOpen);
|
||||||
@@ -103,8 +170,11 @@ const DistanceQuickFilterV2 = (props) => {
|
|||||||
icon={<Image src={img.ICON_MENU_ITEM_SELECTED} />}
|
icon={<Image src={img.ICON_MENU_ITEM_SELECTED} />}
|
||||||
>
|
>
|
||||||
<div className="positionWrap">
|
<div className="positionWrap">
|
||||||
<p className="title">当前位置</p>
|
<p className="title">{currentCity}</p>
|
||||||
<p className="cityName">{currentCity}</p>
|
<p className="cityName" onClick={handleRelocate}>
|
||||||
|
<img src={img.ICON_RELOCATE} style={{ width: '12px', height: "12px" }} />
|
||||||
|
<span>重新定位</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="distanceWrap">
|
<div className="distanceWrap">
|
||||||
<Bubble
|
<Bubble
|
||||||
|
|||||||
@@ -13,7 +13,9 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
color: #000;
|
color: #000;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-feature-settings: 'liga' off, 'clig' off;
|
font-feature-settings:
|
||||||
|
"liga" off,
|
||||||
|
"clig" off;
|
||||||
font-family: "PingFang SC";
|
font-family: "PingFang SC";
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
@@ -32,7 +34,9 @@
|
|||||||
padding-top: 24px;
|
padding-top: 24px;
|
||||||
color: #000;
|
color: #000;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-feature-settings: 'liga' off, 'clig' off;
|
font-feature-settings:
|
||||||
|
"liga" off,
|
||||||
|
"clig" off;
|
||||||
font-family: "PingFang SC";
|
font-family: "PingFang SC";
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
@@ -48,8 +52,10 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.tips {
|
.tips {
|
||||||
color: rgba(60, 60, 67, 0.60);
|
color: rgba(60, 60, 67, 0.6);
|
||||||
font-feature-settings: 'liga' off, 'clig' off;
|
font-feature-settings:
|
||||||
|
"liga" off,
|
||||||
|
"clig" off;
|
||||||
font-family: "PingFang SC";
|
font-family: "PingFang SC";
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
@@ -62,13 +68,15 @@
|
|||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background: #F0F0F0;
|
background: #f0f0f0;
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
&:placeholder-shown {
|
&:placeholder-shown {
|
||||||
color: rgba(60, 60, 67, 0.30);
|
color: rgba(60, 60, 67, 0.3);
|
||||||
font-feature-settings: 'liga' off, 'clig' off;
|
font-feature-settings:
|
||||||
|
"liga" off,
|
||||||
|
"clig" off;
|
||||||
font-family: "PingFang SC";
|
font-family: "PingFang SC";
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
@@ -84,11 +92,12 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 44px;
|
height: 44px;
|
||||||
border-top: 0.5px solid #CECECE;
|
border-top: 0.5px solid #cecece;
|
||||||
background: #FFF;
|
background: #fff;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
|
|
||||||
.confirm, .cancel {
|
.confirm,
|
||||||
|
.cancel {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
height: 44px;
|
height: 44px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -96,7 +105,9 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
color: #000;
|
color: #000;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-feature-settings: 'liga' off, 'clig' off;
|
font-feature-settings:
|
||||||
|
"liga" off,
|
||||||
|
"clig" off;
|
||||||
font-family: "PingFang SC";
|
font-family: "PingFang SC";
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
|||||||
@@ -186,10 +186,11 @@ export default forwardRef(function GameManagePopup(props, ref) {
|
|||||||
.some((item) => item.user.id === userInfo.id);
|
.some((item) => item.user.id === userInfo.id);
|
||||||
|
|
||||||
const finished = [MATCH_STATUS.FINISHED, MATCH_STATUS.CANCELED].includes(
|
const finished = [MATCH_STATUS.FINISHED, MATCH_STATUS.CANCELED].includes(
|
||||||
detail.match_status
|
detail.match_status,
|
||||||
);
|
);
|
||||||
|
|
||||||
const inTwoHours = dayjs(detail.start_time).diff(dayjs(), "hour") < 2;
|
// const inTwoHours = dayjs(detail.start_time).diff(dayjs(), "hour") < 2;
|
||||||
|
const beforeStart = dayjs(detail.start_time).isAfter(dayjs());
|
||||||
|
|
||||||
const hasOtherParticiappants = (detail.participants || [])
|
const hasOtherParticiappants = (detail.participants || [])
|
||||||
.filter((item) => item.status === "joined")
|
.filter((item) => item.status === "joined")
|
||||||
@@ -207,7 +208,7 @@ export default forwardRef(function GameManagePopup(props, ref) {
|
|||||||
style={{ minHeight: "unset" }}
|
style={{ minHeight: "unset" }}
|
||||||
>
|
>
|
||||||
<View className={styles.container}>
|
<View className={styles.container}>
|
||||||
{!inTwoHours && !hasOtherParticiappants && (
|
{!finished && !hasOtherParticiappants && beforeStart && (
|
||||||
<View className={styles.button} onClick={handleEditGame}>
|
<View className={styles.button} onClick={handleEditGame}>
|
||||||
编辑活动
|
编辑活动
|
||||||
</View>
|
</View>
|
||||||
@@ -217,12 +218,12 @@ export default forwardRef(function GameManagePopup(props, ref) {
|
|||||||
重新发布
|
重新发布
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
{!inTwoHours && !hasOtherParticiappants && (
|
{!finished && beforeStart && (
|
||||||
<View className={styles.button} onClick={handleCancelGame}>
|
<View className={styles.button} onClick={handleCancelGame}>
|
||||||
取消活动
|
取消活动
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
{hasJoin && (
|
{!finished && beforeStart && hasJoin && (
|
||||||
<View className={styles.button} onClick={handleQuitGame}>
|
<View className={styles.button} onClick={handleQuitGame}>
|
||||||
退出活动
|
退出活动
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const GamePlayType = (props: IProps) => {
|
|||||||
const { name, onChange, value, options } = props;
|
const { name, onChange, value, options } = props;
|
||||||
return (
|
return (
|
||||||
<View className={styles.gamePlayWrapper}>
|
<View className={styles.gamePlayWrapper}>
|
||||||
<TitleComponent title="玩法" icon={<Image src={img.ICON_SITE} />} />
|
<TitleComponent title="玩法" icon={<Image src={img.ICON_GAME_PLAY} />} />
|
||||||
<Bubble
|
<Bubble
|
||||||
options={options}
|
options={options}
|
||||||
value={value}
|
value={value}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState, useRef } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { View, Text, Image } from "@tarojs/components";
|
import { View, Text, Image } from "@tarojs/components";
|
||||||
import img from "@/config/images";
|
import img from "@/config/images";
|
||||||
import { useGlobalState } from "@/store/global";
|
import { useGlobalState } from "@/store/global";
|
||||||
@@ -13,6 +13,12 @@ import LocationConfirmDialog from "@/components/LocationConfirmDialog";
|
|||||||
|
|
||||||
// 城市缓存 key
|
// 城市缓存 key
|
||||||
const CITY_CACHE_KEY = "USER_SELECTED_CITY";
|
const CITY_CACHE_KEY = "USER_SELECTED_CITY";
|
||||||
|
// 定位弹窗关闭时间缓存 key(用户选择"继续浏览"时记录)
|
||||||
|
const LOCATION_DIALOG_DISMISS_TIME_KEY = "LOCATION_DIALOG_DISMISS_TIME";
|
||||||
|
// 城市切换时间缓存 key(用户手动切换城市时记录)
|
||||||
|
const CITY_CHANGE_TIME_KEY = "CITY_CHANGE_TIME";
|
||||||
|
// 2小时的毫秒数
|
||||||
|
const TWO_HOURS_MS = 2 * 60 * 60 * 1000;
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
config?: {
|
config?: {
|
||||||
@@ -90,7 +96,6 @@ const HomeNavbar = (props: IProps) => {
|
|||||||
detectedProvince: string;
|
detectedProvince: string;
|
||||||
cachedCity: [string, string];
|
cachedCity: [string, string];
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
const hasShownLocationDialog = useRef(false); // 防止重复弹窗
|
|
||||||
|
|
||||||
// 监听城市选择器状态变化,通知父组件
|
// 监听城市选择器状态变化,通知父组件
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -100,45 +105,160 @@ const HomeNavbar = (props: IProps) => {
|
|||||||
const userInfo = useUserInfo();
|
const userInfo = useUserInfo();
|
||||||
// 使用用户详情接口中的 last_location 字段
|
// 使用用户详情接口中的 last_location 字段
|
||||||
// USER_SELECTED_CITY 第二个值应该是省份/直辖市,不能是区
|
// USER_SELECTED_CITY 第二个值应该是省份/直辖市,不能是区
|
||||||
const lastLocationProvince = (userInfo as any)?.last_location_province || "";
|
const lastLocationCity = (userInfo as any)?.last_location_city || "";
|
||||||
// 只使用省份/直辖市,不使用城市(城市可能是区)
|
// 只使用省份/直辖市,不使用城市(城市可能是区)
|
||||||
const detectedLocation = lastLocationProvince;
|
const detectedLocation = lastLocationCity;
|
||||||
|
|
||||||
// 初始化城市:优先使用缓存的定位信息,其次使用用户详情中的位置信息
|
// 检查是否应该显示定位确认弹窗
|
||||||
|
const should_show_location_dialog = (): boolean => {
|
||||||
|
try {
|
||||||
|
const current_time = Date.now();
|
||||||
|
|
||||||
|
// 检查是否在2小时内切换过城市
|
||||||
|
const city_change_time = (Taro as any).getStorageSync(CITY_CHANGE_TIME_KEY);
|
||||||
|
if (city_change_time) {
|
||||||
|
const time_diff = current_time - city_change_time;
|
||||||
|
// 如果距离上次切换城市还在2小时内,不显示弹窗
|
||||||
|
if (time_diff < TWO_HOURS_MS) {
|
||||||
|
console.log(`[HomeNavbar] 距离上次切换城市还不到2小时,剩余时间: ${Math.ceil((TWO_HOURS_MS - time_diff) / 1000 / 60)}分钟,不显示定位弹窗`);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
// 超过2小时,清除过期记录
|
||||||
|
(Taro as any).removeStorageSync(CITY_CHANGE_TIME_KEY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否在2小时内已选择"继续浏览"
|
||||||
|
const dismiss_time = (Taro as any).getStorageSync(LOCATION_DIALOG_DISMISS_TIME_KEY);
|
||||||
|
if (!dismiss_time) {
|
||||||
|
return true; // 没有记录,可以显示
|
||||||
|
}
|
||||||
|
|
||||||
|
const time_diff = current_time - dismiss_time;
|
||||||
|
// 如果距离上次选择"继续浏览"已超过2小时,可以再次显示
|
||||||
|
if (time_diff >= TWO_HOURS_MS) {
|
||||||
|
// 清除过期记录
|
||||||
|
(Taro as any).removeStorageSync(LOCATION_DIALOG_DISMISS_TIME_KEY);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在2小时内,不显示弹窗
|
||||||
|
console.log(`[HomeNavbar] 距离上次选择"继续浏览"还不到2小时,剩余时间: ${Math.ceil((TWO_HOURS_MS - time_diff) / 1000 / 60)}分钟`);
|
||||||
|
return false;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[HomeNavbar] 检查定位弹窗显示条件失败:', error);
|
||||||
|
return true; // 出错时默认显示
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 显示定位确认弹窗
|
||||||
|
const showLocationConfirmDialog = (detectedLocation: string, cachedCity: [string, string]) => {
|
||||||
|
// 检查是否应该显示弹窗
|
||||||
|
if (!should_show_location_dialog()) {
|
||||||
|
console.log('[HomeNavbar] 用户在2小时内已选择"继续浏览"或切换过城市,不显示弹窗');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[HomeNavbar] 准备显示定位确认弹窗,隐藏 GuideBar');
|
||||||
|
setLocationDialogData({ detectedProvince: detectedLocation, cachedCity });
|
||||||
|
setLocationDialogVisible(true);
|
||||||
|
// 显示弹窗时隐藏 GuideBar
|
||||||
|
setShowGuideBar(false);
|
||||||
|
console.log('[HomeNavbar] setShowGuideBar(false) 已调用');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化城市:优先使用缓存的定位信息,如果缓存城市和用户详情位置不一致,且时间过期,则弹出选择框
|
||||||
|
// 只在组件挂载时执行一次,避免重复执行
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 1. 尝试从缓存中读取上次的定位信息
|
// 1. 优先尝试从缓存中读取上次的定位信息
|
||||||
const cachedCity = (Taro as any).getStorageSync(CITY_CACHE_KEY);
|
const cachedCity = (Taro as any).getStorageSync(CITY_CACHE_KEY);
|
||||||
|
|
||||||
if (cachedCity && Array.isArray(cachedCity) && cachedCity.length === 2) {
|
if (cachedCity && Array.isArray(cachedCity) && cachedCity.length === 2) {
|
||||||
// 如果有缓存的定位信息,使用缓存
|
// 如果有缓存的定位信息,使用缓存
|
||||||
const cachedCityArray = cachedCity as [string, string];
|
const cachedCityArray = cachedCity as [string, string];
|
||||||
console.log("使用缓存的定位城市:", cachedCityArray);
|
console.log("[HomeNavbar] 使用缓存的定位城市:", cachedCityArray);
|
||||||
updateArea(cachedCityArray);
|
updateArea(cachedCityArray);
|
||||||
|
|
||||||
// 如果用户详情中有位置信息且与缓存不同,弹窗询问是否切换
|
// 如果用户详情中有位置信息,且与缓存不一致,检查是否需要弹窗
|
||||||
if (detectedLocation && cachedCityArray[1] !== detectedLocation && !hasShownLocationDialog.current) {
|
if (detectedLocation && cachedCityArray[1] !== detectedLocation) {
|
||||||
hasShownLocationDialog.current = true;
|
// 检查时间缓存,如果没有或过期,则弹出选择框
|
||||||
|
if (should_show_location_dialog()) {
|
||||||
|
console.log("[HomeNavbar] 缓存城市与用户详情位置不一致,且时间过期,弹出选择框");
|
||||||
showLocationConfirmDialog(detectedLocation, cachedCityArray);
|
showLocationConfirmDialog(detectedLocation, cachedCityArray);
|
||||||
|
} else {
|
||||||
|
console.log("[HomeNavbar] 缓存城市与用户详情位置不一致,但时间未过期,不弹出选择框");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (detectedLocation) {
|
} else if (detectedLocation) {
|
||||||
// 如果没有缓存但有用户详情中的位置信息,直接使用并保存到缓存
|
// 只有在完全没有缓存的情况下,才使用用户详情中的位置信息
|
||||||
console.log("没有缓存,使用用户详情中的位置信息:", detectedLocation);
|
console.log("[HomeNavbar] 没有缓存,使用用户详情中的位置信息:", detectedLocation);
|
||||||
const newArea: [string, string] = ["中国", detectedLocation];
|
const newArea: [string, string] = [(userInfo as any)?.last_location_province || "", detectedLocation];
|
||||||
updateArea(newArea);
|
updateArea(newArea);
|
||||||
// 保存定位信息到缓存
|
// 保存定位信息到缓存
|
||||||
(Taro as any).setStorageSync(CITY_CACHE_KEY, newArea);
|
(Taro as any).setStorageSync(CITY_CACHE_KEY, newArea);
|
||||||
}
|
}
|
||||||
}, [detectedLocation]);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []); // 空依赖数组,确保只在组件挂载时执行一次
|
||||||
|
|
||||||
// 显示定位确认弹窗
|
// 检查是否在2小时内已选择"继续浏览"或切换过城市(当前不使用,首页重新进入时直接使用缓存中的位置)
|
||||||
const showLocationConfirmDialog = (detectedLocation: string, cachedCity: [string, string]) => {
|
// const should_show_location_dialog = (): boolean => {
|
||||||
console.log('[LocationDialog] 准备显示定位确认弹窗,隐藏 GuideBar');
|
// try {
|
||||||
setLocationDialogData({ detectedProvince: detectedLocation, cachedCity });
|
// // 检查是否在2小时内切换过城市
|
||||||
setLocationDialogVisible(true);
|
// const city_change_time = (Taro as any).getStorageSync(CITY_CHANGE_TIME_KEY);
|
||||||
// 显示弹窗时隐藏 GuideBar
|
// if (city_change_time) {
|
||||||
setShowGuideBar(false);
|
// const current_time = Date.now();
|
||||||
console.log('[LocationDialog] setShowGuideBar(false) 已调用');
|
// const time_diff = current_time - city_change_time;
|
||||||
};
|
//
|
||||||
|
// // 如果距离上次切换城市还在2小时内,不显示弹窗
|
||||||
|
// if (time_diff < TWO_HOURS_MS) {
|
||||||
|
// console.log(`距离上次切换城市还不到2小时,剩余时间: ${Math.ceil((TWO_HOURS_MS - time_diff) / 1000 / 60)}分钟,不显示定位弹窗`);
|
||||||
|
// return false;
|
||||||
|
// } else {
|
||||||
|
// // 超过2小时,清除过期记录
|
||||||
|
// (Taro as any).removeStorageSync(CITY_CHANGE_TIME_KEY);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 检查是否在2小时内已选择"继续浏览"
|
||||||
|
// const dismiss_time = (Taro as any).getStorageSync(LOCATION_DIALOG_DISMISS_TIME_KEY);
|
||||||
|
// if (!dismiss_time) {
|
||||||
|
// return true; // 没有记录,可以显示
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// const current_time = Date.now();
|
||||||
|
// const time_diff = current_time - dismiss_time;
|
||||||
|
//
|
||||||
|
// // 如果距离上次选择"继续浏览"已超过2小时,可以再次显示
|
||||||
|
// if (time_diff >= TWO_HOURS_MS) {
|
||||||
|
// // 清除过期记录
|
||||||
|
// (Taro as any).removeStorageSync(LOCATION_DIALOG_DISMISS_TIME_KEY);
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 在2小时内,不显示弹窗
|
||||||
|
// console.log(`距离上次选择"继续浏览"还不到2小时,剩余时间: ${Math.ceil((TWO_HOURS_MS - time_diff) / 1000 / 60)}分钟`);
|
||||||
|
// return false;
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error('检查定位弹窗显示条件失败:', error);
|
||||||
|
// return true; // 出错时默认显示
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// 显示定位确认弹窗(当前不使用,首页重新进入时直接使用缓存中的位置)
|
||||||
|
// const showLocationConfirmDialog = (detectedLocation: string, cachedCity: [string, string]) => {
|
||||||
|
// // 检查是否在2小时内已选择"继续浏览"
|
||||||
|
// if (!should_show_location_dialog()) {
|
||||||
|
// console.log('[LocationDialog] 用户在2小时内已选择"继续浏览",不显示弹窗');
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// console.log('[LocationDialog] 准备显示定位确认弹窗,隐藏 GuideBar');
|
||||||
|
// setLocationDialogData({ detectedProvince: detectedLocation, cachedCity });
|
||||||
|
// setLocationDialogVisible(true);
|
||||||
|
// // 显示弹窗时隐藏 GuideBar
|
||||||
|
// setShowGuideBar(false);
|
||||||
|
// console.log('[LocationDialog] setShowGuideBar(false) 已调用');
|
||||||
|
// };
|
||||||
|
|
||||||
// 处理定位弹窗确认
|
// 处理定位弹窗确认
|
||||||
const handleLocationDialogConfirm = () => {
|
const handleLocationDialogConfirm = () => {
|
||||||
@@ -146,10 +266,18 @@ const HomeNavbar = (props: IProps) => {
|
|||||||
|
|
||||||
const { detectedProvince } = locationDialogData;
|
const { detectedProvince } = locationDialogData;
|
||||||
// 用户选择"切换到",使用用户详情中的位置信息
|
// 用户选择"切换到",使用用户详情中的位置信息
|
||||||
const newArea: [string, string] = ["中国", detectedProvince];
|
const newArea: [string, string] = [(userInfo as any)?.last_location_province || "", detectedProvince];
|
||||||
updateArea(newArea);
|
updateArea(newArea);
|
||||||
// 更新缓存为新的定位信息
|
// 更新缓存为新的定位信息
|
||||||
(Taro as any).setStorageSync(CITY_CACHE_KEY, newArea);
|
(Taro as any).setStorageSync(CITY_CACHE_KEY, newArea);
|
||||||
|
// 记录切换城市的时间戳,2小时内不再提示
|
||||||
|
try {
|
||||||
|
const current_time = Date.now();
|
||||||
|
(Taro as any).setStorageSync(CITY_CHANGE_TIME_KEY, current_time);
|
||||||
|
console.log(`[LocationDialog] 已记录用户切换城市的时间,2小时内不再提示`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存城市切换时间失败:', error);
|
||||||
|
}
|
||||||
console.log("切换到用户详情中的位置信息并更新缓存:", detectedProvince);
|
console.log("切换到用户详情中的位置信息并更新缓存:", detectedProvince);
|
||||||
|
|
||||||
// 关闭弹窗
|
// 关闭弹窗
|
||||||
@@ -162,14 +290,23 @@ const HomeNavbar = (props: IProps) => {
|
|||||||
handleCityChangeWithoutCache();
|
handleCityChangeWithoutCache();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理定位弹窗取消
|
// 处理定位弹窗取消(用户选择"继续浏览")
|
||||||
const handleLocationDialogCancel = () => {
|
const handleLocationDialogCancel = () => {
|
||||||
if (!locationDialogData) return;
|
if (!locationDialogData) return;
|
||||||
|
|
||||||
const { cachedCity } = locationDialogData;
|
const { cachedCity } = locationDialogData;
|
||||||
// 用户选择"否",保持缓存的定位城市
|
// 用户选择"继续浏览",保持缓存的定位城市
|
||||||
console.log("保持缓存的定位城市:", cachedCity[1]);
|
console.log("保持缓存的定位城市:", cachedCity[1]);
|
||||||
|
|
||||||
|
// 记录用户选择"继续浏览"的时间戳,2小时内不再提示
|
||||||
|
try {
|
||||||
|
const current_time = Date.now();
|
||||||
|
(Taro as any).setStorageSync(LOCATION_DIALOG_DISMISS_TIME_KEY, current_time);
|
||||||
|
console.log(`[LocationDialog] 已记录用户选择"继续浏览"的时间,2小时内不再提示`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存定位弹窗关闭时间失败:', error);
|
||||||
|
}
|
||||||
|
|
||||||
// 关闭弹窗
|
// 关闭弹窗
|
||||||
setLocationDialogVisible(false);
|
setLocationDialogVisible(false);
|
||||||
setLocationDialogData(null);
|
setLocationDialogData(null);
|
||||||
@@ -258,12 +395,23 @@ const HomeNavbar = (props: IProps) => {
|
|||||||
|
|
||||||
// 处理城市切换(用户手动选择)
|
// 处理城市切换(用户手动选择)
|
||||||
const handleCityChange = async (_newArea: any) => {
|
const handleCityChange = async (_newArea: any) => {
|
||||||
// 用户手动选择的城市不保存到缓存(临时切换)
|
// 用户手动选择的城市保存到缓存
|
||||||
console.log("用户手动选择城市(不保存缓存):", _newArea);
|
console.log("用户手动选择城市,更新缓存:", _newArea);
|
||||||
|
|
||||||
// 先更新 area 状态(用于界面显示和接口参数)
|
// 先更新 area 状态(用于界面显示和接口参数)
|
||||||
updateArea(_newArea);
|
updateArea(_newArea);
|
||||||
|
|
||||||
|
// 保存城市到缓存
|
||||||
|
try {
|
||||||
|
(Taro as any).setStorageSync(CITY_CACHE_KEY, _newArea);
|
||||||
|
// 记录切换时间,2小时内不再弹出定位弹窗
|
||||||
|
const current_time = Date.now();
|
||||||
|
(Taro as any).setStorageSync(CITY_CHANGE_TIME_KEY, current_time);
|
||||||
|
console.log("已保存城市到缓存并记录切换时间:", _newArea, current_time);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("保存城市缓存失败:", error);
|
||||||
|
}
|
||||||
|
|
||||||
// 先调用列表接口(会使用更新后的 state.area)
|
// 先调用列表接口(会使用更新后的 state.area)
|
||||||
if (refreshBothLists) {
|
if (refreshBothLists) {
|
||||||
await refreshBothLists();
|
await refreshBothLists();
|
||||||
@@ -333,8 +481,7 @@ const HomeNavbar = (props: IProps) => {
|
|||||||
{/* 搜索导航 */}
|
{/* 搜索导航 */}
|
||||||
{!showTitle && (
|
{!showTitle && (
|
||||||
<View
|
<View
|
||||||
className={`inputCustomerNavbarContainer toggleElement secondElement hidden ${
|
className={`inputCustomerNavbarContainer toggleElement secondElement hidden ${showInput && "visible"
|
||||||
showInput && "visible"
|
|
||||||
} ${showInput ? "inputCustomerNavbarShowInput" : ""}`}
|
} ${showInput ? "inputCustomerNavbarShowInput" : ""}`}
|
||||||
style={navbarStyle}
|
style={navbarStyle}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.location-position {
|
.location-position {
|
||||||
flex: 1;
|
// flex: 1;
|
||||||
min-width: 0; // 允许缩小
|
min-width: 0; // 允许缩小
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import img from "../../config/images";
|
|||||||
import { ListCardProps } from "../../../types/list/types";
|
import { ListCardProps } from "../../../types/list/types";
|
||||||
import { formatGameTime, calculateDuration } from "@/utils/timeUtils";
|
import { formatGameTime, calculateDuration } from "@/utils/timeUtils";
|
||||||
import { navigateTo } from "@/utils/navigation";
|
import { navigateTo } from "@/utils/navigation";
|
||||||
import images from '@/config/images'
|
import images from "@/config/images";
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
|
import { OSS_BASE } from "@/config/api";
|
||||||
|
|
||||||
const ListCard: React.FC<ListCardProps> = ({
|
const ListCard: React.FC<ListCardProps> = ({
|
||||||
id,
|
id,
|
||||||
@@ -45,7 +46,7 @@ const ListCard: React.FC<ListCardProps> = ({
|
|||||||
className="image"
|
className="image"
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
lazyLoad
|
lazyLoad
|
||||||
defaultSource="https://images.unsplash.com/photo-1554068865-24cecd4e34b8?w=200&h=200&fit=crop&crop=center"
|
defaultSource={`${OSS_BASE}/front/ball/images/publish-empty-card.svg`}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -67,7 +68,9 @@ const ListCard: React.FC<ListCardProps> = ({
|
|||||||
const containerWidthPx = screenWidth - 130;
|
const containerWidthPx = screenWidth - 130;
|
||||||
|
|
||||||
// 计算固定信息宽度
|
// 计算固定信息宽度
|
||||||
const extraInfo = `${court_type ? `・${court_type}` : ''}${distance_km ? `・${distance_km}km` : ''}`;
|
const extraInfo = `${court_type ? `・${court_type}` : ""}${
|
||||||
|
distance_km ? `・${distance_km}km` : ""
|
||||||
|
}`;
|
||||||
|
|
||||||
// 估算字符宽度(基于 12px 字体)
|
// 估算字符宽度(基于 12px 字体)
|
||||||
const getTextWidth = (text: string) => {
|
const getTextWidth = (text: string) => {
|
||||||
@@ -98,7 +101,9 @@ const ListCard: React.FC<ListCardProps> = ({
|
|||||||
let currentWidth = 0;
|
let currentWidth = 0;
|
||||||
for (let i = 0; i < location.length; i++) {
|
for (let i = 0; i < location.length; i++) {
|
||||||
const char = location[i];
|
const char = location[i];
|
||||||
const charWidth = /[\u4e00-\u9fa5\u3000-\u303f\uff00-\uffef]/.test(char) ? 12 : 6;
|
const charWidth = /[\u4e00-\u9fa5\u3000-\u303f\uff00-\uffef]/.test(char)
|
||||||
|
? 12
|
||||||
|
: 6;
|
||||||
if (currentWidth + charWidth > availableWidth) {
|
if (currentWidth + charWidth > availableWidth) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -106,7 +111,7 @@ const ListCard: React.FC<ListCardProps> = ({
|
|||||||
maxChars++;
|
maxChars++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return location.slice(0, maxChars) + '...';
|
return location.slice(0, maxChars) + "...";
|
||||||
}, [location, court_type, distance_km]);
|
}, [location, court_type, distance_km]);
|
||||||
|
|
||||||
// 根据图片数量决定展示样式
|
// 根据图片数量决定展示样式
|
||||||
@@ -127,10 +132,10 @@ const ListCard: React.FC<ListCardProps> = ({
|
|||||||
return (
|
return (
|
||||||
<View className="double-image">
|
<View className="double-image">
|
||||||
<View className="image-container">
|
<View className="image-container">
|
||||||
{renderItemImage(image_list?.[0])}
|
{renderItemImage(image_list?.[1])}
|
||||||
</View>
|
</View>
|
||||||
<View className="image-container">
|
<View className="image-container">
|
||||||
{renderItemImage(image_list?.[1])}
|
{renderItemImage(image_list?.[0])}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
@@ -220,9 +225,10 @@ const ListCard: React.FC<ListCardProps> = ({
|
|||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className="tag ntprTag">
|
<View className="tag ntprTag">
|
||||||
<Image src={images.ICON_LIST_NTPR} className='ntprIcon' />
|
<Image src={images.ICON_LIST_NTPR} className="ntprIcon" />
|
||||||
<Text className="tag-text">
|
<Text className="tag-text">
|
||||||
{Number(skill_level_min)?.toFixed(1)} - {Number(skill_level_max)?.toFixed(1)}
|
{Number(skill_level_min)?.toFixed(1)} -{" "}
|
||||||
|
{Number(skill_level_max)?.toFixed(1)}
|
||||||
</Text>
|
</Text>
|
||||||
{/* 分割线 */}
|
{/* 分割线 */}
|
||||||
<View className="typeLine" />
|
<View className="typeLine" />
|
||||||
@@ -251,13 +257,8 @@ const ListCard: React.FC<ListCardProps> = ({
|
|||||||
/>
|
/>
|
||||||
{/* <Text className="smoothTitle">{game_type}</Text> */}
|
{/* <Text className="smoothTitle">{game_type}</Text> */}
|
||||||
</View>
|
</View>
|
||||||
{
|
{venue_description && <View className="line" />}
|
||||||
venue_description && (<View className="line" />)
|
{venue_description && (
|
||||||
}
|
|
||||||
{
|
|
||||||
venue_description &&
|
|
||||||
(
|
|
||||||
|
|
||||||
<View className="localAreaContainer">
|
<View className="localAreaContainer">
|
||||||
<View className="localAreaTitle">场馆方:</View>
|
<View className="localAreaTitle">场馆方:</View>
|
||||||
<View className="localAreaWrapper">
|
<View className="localAreaWrapper">
|
||||||
@@ -265,8 +266,7 @@ const ListCard: React.FC<ListCardProps> = ({
|
|||||||
<Text className="localAreaText">{venue_description}</Text>
|
<Text className="localAreaText">{venue_description}</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ const ListLoadError = (props: IProps) => {
|
|||||||
wrapperHeight = "",
|
wrapperHeight = "",
|
||||||
width = "",
|
width = "",
|
||||||
height = "",
|
height = "",
|
||||||
scale = "",
|
|
||||||
} = props;
|
} = props;
|
||||||
const handleReload = () => {
|
const handleReload = () => {
|
||||||
reload && typeof reload === "function" && reload();
|
reload && typeof reload === "function" && reload();
|
||||||
@@ -34,7 +33,7 @@ const ListLoadError = (props: IProps) => {
|
|||||||
<View className={styles.listLoadError} style={{ height: wrapperHeight }}>
|
<View className={styles.listLoadError} style={{ height: wrapperHeight }}>
|
||||||
<Image
|
<Image
|
||||||
className={styles.listLoadErrorImg}
|
className={styles.listLoadErrorImg}
|
||||||
style={{ width, height, transform: `scale(${scale})` }}
|
style={{ width, height }}
|
||||||
src={errorImg ? img[errorImg] : img.ICON_LIST_LOAD_ERROR}
|
src={errorImg ? img[errorImg] : img.ICON_LIST_LOAD_ERROR}
|
||||||
/>
|
/>
|
||||||
{text && <Text className={styles.listLoadErrorText}>{text}</Text>}
|
{text && <Text className={styles.listLoadErrorText}>{text}</Text>}
|
||||||
|
|||||||
@@ -79,6 +79,10 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: opacity 0.2s;
|
transition: opacity 0.2s;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
@@ -111,6 +115,8 @@
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
&.primary {
|
&.primary {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
|
|||||||
@@ -40,10 +40,22 @@ const LocationConfirmDialog: React.FC<LocationConfirmDialogProps> = ({
|
|||||||
<View className="locationDialogContent">
|
<View className="locationDialogContent">
|
||||||
<Text className="locationDialogTitle">定位显示您在{detectedCity}</Text>
|
<Text className="locationDialogTitle">定位显示您在{detectedCity}</Text>
|
||||||
<View className="locationDialogButtons">
|
<View className="locationDialogButtons">
|
||||||
<View className="locationDialogButton primary" onClick={onConfirm}>
|
<View
|
||||||
|
className="locationDialogButton primary"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onConfirm();
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Text className="locationDialogButtonText primary">切换到{detectedCity}</Text>
|
<Text className="locationDialogButtonText primary">切换到{detectedCity}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className="locationDialogButton secondary" onClick={onCancel}>
|
<View
|
||||||
|
className="locationDialogButton secondary"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onCancel();
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Text className="locationDialogButtonText secondary">继续浏览{currentCity}</Text>
|
<Text className="locationDialogButtonText secondary">继续浏览{currentCity}</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ const NTRPEvaluatePopup = (props: NTRPEvaluatePopupProps, ref) => {
|
|||||||
showGuide = false,
|
showGuide = false,
|
||||||
} = props;
|
} = props;
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [ntrp, setNtrp] = useState<string>("");
|
const [ntrp, setNtrp] = useState<string>("1.5");
|
||||||
const [guideShow, setGuideShow] = useState(() => showGuide);
|
const [guideShow, setGuideShow] = useState(() => showGuide);
|
||||||
const { updateUserInfo } = useUserActions();
|
const { updateUserInfo } = useUserActions();
|
||||||
const userInfo = useUserInfo();
|
const userInfo = useUserInfo();
|
||||||
@@ -105,10 +105,10 @@ const NTRPEvaluatePopup = (props: NTRPEvaluatePopupProps, ref) => {
|
|||||||
if (match) {
|
if (match) {
|
||||||
setNtrp(match[0]);
|
setNtrp(match[0]);
|
||||||
} else {
|
} else {
|
||||||
setNtrp("");
|
setNtrp("1.5");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setNtrp("");
|
setNtrp("1.5");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [visible, userInfo?.ntrp_level]);
|
}, [visible, userInfo?.ntrp_level]);
|
||||||
|
|||||||
@@ -4,10 +4,39 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
border: 0.5px solid rgba(0, 0, 0, 0.08);
|
border: 0.5px solid rgba(0, 0, 0, 0.08);
|
||||||
background: linear-gradient(180deg, #BFFFEF 0%, #F2FFFC 100%), var(--Backgrounds-Primary, #FFF);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
position: relative;
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, #bfffef 0%, #f2fffc 100%),
|
||||||
|
var(--Backgrounds-Primary, #fff);
|
||||||
|
|
||||||
|
.lines {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 20px;
|
||||||
|
z-index: 1;
|
||||||
|
background-position-y: 85%;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
// .gradient {
|
||||||
|
// inset: 0;
|
||||||
|
// position: absolute;
|
||||||
|
// top: 0;
|
||||||
|
// left: 0;
|
||||||
|
// width: 100%;
|
||||||
|
// height: 100%;
|
||||||
|
// z-index: -2;
|
||||||
|
// border-radius: 20px;
|
||||||
|
// background:
|
||||||
|
// linear-gradient(180deg, #bfffef 0%, #f2fffc 100%),
|
||||||
|
// var(--Backgrounds-Primary, #fff);
|
||||||
|
// pointer-events: none;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
.higher {
|
.higher {
|
||||||
@@ -18,8 +47,6 @@
|
|||||||
.lower {
|
.lower {
|
||||||
height: 80px;
|
height: 80px;
|
||||||
@include commonCardStyle();
|
@include commonCardStyle();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.desc {
|
.desc {
|
||||||
@@ -30,7 +57,7 @@
|
|||||||
gap: 7px;
|
gap: 7px;
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
color: #2A4D44;
|
color: #2a4d44;
|
||||||
font-family: "Noto Sans SC";
|
font-family: "Noto Sans SC";
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
@@ -38,7 +65,7 @@
|
|||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
|
|
||||||
.colorTip {
|
.colorTip {
|
||||||
color: #00E5AD;
|
color: #00e5ad;
|
||||||
font-family: "Noto Sans SC";
|
font-family: "Noto Sans SC";
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
@@ -47,7 +74,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.strongTip {
|
.strongTip {
|
||||||
color: #00E5AD;
|
color: #00e5ad;
|
||||||
font-family: "Noto Sans SC";
|
font-family: "Noto Sans SC";
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
@@ -68,8 +95,10 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
color: #5CA693;
|
color: #5ca693;
|
||||||
font-feature-settings: 'liga' off, 'clig' off;
|
font-feature-settings:
|
||||||
|
"liga" off,
|
||||||
|
"clig" off;
|
||||||
font-family: "PingFang SC";
|
font-family: "PingFang SC";
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
@@ -94,7 +123,9 @@
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
border: 1px solid #efefef;
|
border: 1px solid #efefef;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.20), 0 8px 20px 0 rgba(0, 0, 0, 0.12);
|
box-shadow:
|
||||||
|
0 0 1px 0 rgba(0, 0, 0, 0.2),
|
||||||
|
0 8px 20px 0 rgba(0, 0, 0, 0.12);
|
||||||
|
|
||||||
.avatarUrl {
|
.avatarUrl {
|
||||||
width: calc(90px * $multiple);
|
width: calc(90px * $multiple);
|
||||||
@@ -112,8 +143,14 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
aspect-ratio: 1/1;
|
aspect-ratio: 1/1;
|
||||||
border-radius: calc(20px * $multiple);
|
border-radius: calc(20px * $multiple);
|
||||||
border: 4px solid #FFF;
|
border: 4px solid #fff;
|
||||||
background: linear-gradient(0deg, rgba(89, 255, 214, 0.20) 0%, rgba(89, 255, 214, 0.20) 100%), #FFF;
|
background:
|
||||||
|
linear-gradient(
|
||||||
|
0deg,
|
||||||
|
rgba(89, 255, 214, 0.2) 0%,
|
||||||
|
rgba(89, 255, 214, 0.2) 100%
|
||||||
|
),
|
||||||
|
#fff;
|
||||||
box-shadow: 0 4px 36px 0 rgba(0, 0, 0, 0.12);
|
box-shadow: 0 4px 36px 0 rgba(0, 0, 0, 0.12);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -2,8 +2,13 @@ import React, { useState, useEffect, useCallback, memo } from "react";
|
|||||||
import { View, Image, Text } from "@tarojs/components";
|
import { View, Image, Text } from "@tarojs/components";
|
||||||
import { requireLoginWithPhone } from "@/utils/helper";
|
import { requireLoginWithPhone } from "@/utils/helper";
|
||||||
import Taro from "@tarojs/taro";
|
import Taro from "@tarojs/taro";
|
||||||
import { useUserInfo, useUserActions, useLastTestResult } from "@/store/userStore";
|
import {
|
||||||
|
useUserInfo,
|
||||||
|
useUserActions,
|
||||||
|
useLastTestResult,
|
||||||
|
} from "@/store/userStore";
|
||||||
// import { getCurrentFullPath } from "@/utils";
|
// import { getCurrentFullPath } from "@/utils";
|
||||||
|
import { OSS_BASE } from "@/config/api";
|
||||||
import { StageType } from "@/services/evaluateService";
|
import { StageType } from "@/services/evaluateService";
|
||||||
import { waitForAuthInit } from "@/utils/authInit";
|
import { waitForAuthInit } from "@/utils/authInit";
|
||||||
import DocCopy from "@/static/ntrp/ntrp_doc_copy.svg";
|
import DocCopy from "@/static/ntrp/ntrp_doc_copy.svg";
|
||||||
@@ -26,8 +31,6 @@ function NTRPTestEntryCard(props: {
|
|||||||
// 使用全局状态中的测试结果,避免重复调用接口
|
// 使用全局状态中的测试结果,避免重复调用接口
|
||||||
const lastTestResult = useLastTestResult();
|
const lastTestResult = useLastTestResult();
|
||||||
|
|
||||||
console.log(userInfo);
|
|
||||||
|
|
||||||
// 从全局状态中获取测试结果,如果不存在则调用接口(使用请求锁避免重复调用)
|
// 从全局状态中获取测试结果,如果不存在则调用接口(使用请求锁避免重复调用)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
@@ -121,7 +124,7 @@ function NTRPTestEntryCard(props: {
|
|||||||
if (!testFlag && !userInfo.phone) {
|
if (!testFlag && !userInfo.phone) {
|
||||||
Taro.navigateTo({
|
Taro.navigateTo({
|
||||||
url: `/login_pages/index/index?redirect=${encodeURIComponent(
|
url: `/login_pages/index/index?redirect=${encodeURIComponent(
|
||||||
`/other_pages/ntrp-evaluate/index?stage=${StageType.INTRO}`
|
`/other_pages/ntrp-evaluate/index?stage=${StageType.INTRO}`,
|
||||||
)}`,
|
)}`,
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
@@ -132,7 +135,7 @@ function NTRPTestEntryCard(props: {
|
|||||||
}`,
|
}`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[setCallback, testFlag, type, evaluateCallback, userInfo.phone]
|
[setCallback, testFlag, type, evaluateCallback, userInfo.phone],
|
||||||
);
|
);
|
||||||
|
|
||||||
// 如果最近一个月有测试记录,则不展示
|
// 如果最近一个月有测试记录,则不展示
|
||||||
@@ -142,6 +145,12 @@ function NTRPTestEntryCard(props: {
|
|||||||
|
|
||||||
return type === EvaluateScene.list ? (
|
return type === EvaluateScene.list ? (
|
||||||
<View className={styles.higher} onClick={handleTest}>
|
<View className={styles.higher} onClick={handleTest}>
|
||||||
|
<View
|
||||||
|
className={styles.lines}
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url(${OSS_BASE}/front/ball/images/215f1ce1-be52-4a92-8250-5a4a69e7f2b3.png)`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<View className={styles.desc}>
|
<View className={styles.desc}>
|
||||||
<View>
|
<View>
|
||||||
<View className={styles.title}>
|
<View className={styles.title}>
|
||||||
@@ -176,6 +185,12 @@ function NTRPTestEntryCard(props: {
|
|||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<View className={styles.lower} onClick={handleTest}>
|
<View className={styles.lower} onClick={handleTest}>
|
||||||
|
<View
|
||||||
|
className={styles.lines}
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url(${OSS_BASE}/front/ball/images/215f1ce1-be52-4a92-8250-5a4a69e7f2b3.png)`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<View className={styles.desc}>
|
<View className={styles.desc}>
|
||||||
<View className={styles.title}>
|
<View className={styles.title}>
|
||||||
<Text>不知道自己的</Text>
|
<Text>不知道自己的</Text>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect, useRef } from "react";
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
import CommonPopup from "@/components/CommonPopup";
|
import CommonPopup from "@/components/CommonPopup";
|
||||||
import { View } from "@tarojs/components";
|
import { View } from "@tarojs/components";
|
||||||
|
import Taro from "@tarojs/taro";
|
||||||
import CalendarUI, {
|
import CalendarUI, {
|
||||||
CalendarUIRef,
|
CalendarUIRef,
|
||||||
} from "@/components/Picker/CalendarUI/CalendarUI";
|
} from "@/components/Picker/CalendarUI/CalendarUI";
|
||||||
@@ -47,6 +48,13 @@ const DialogCalendarCard: React.FC<DialogCalendarCardProps> = ({
|
|||||||
onClose();
|
onClose();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!selected) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '请选择日期',
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
// 年份选择完成后,进入月份选择
|
// 年份选择完成后,进入月份选择
|
||||||
setType("time");
|
setType("time");
|
||||||
} else if (type === "month") {
|
} else if (type === "month") {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useState, useEffect, useRef } from "react";
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
import CommonPopup from "@/components/CommonPopup";
|
import CommonPopup from "@/components/CommonPopup";
|
||||||
|
import Taro from "@tarojs/taro";
|
||||||
import { View } from "@tarojs/components";
|
import { View } from "@tarojs/components";
|
||||||
import CalendarUI, {
|
import CalendarUI, {
|
||||||
CalendarUIRef,
|
CalendarUIRef,
|
||||||
@@ -32,6 +33,13 @@ const DayDialog: React.FC<DayDialogProps> = ({
|
|||||||
} | null>(null);
|
} | null>(null);
|
||||||
const handleConfirm = () => {
|
const handleConfirm = () => {
|
||||||
console.log(selected, 'selectedselected');
|
console.log(selected, 'selectedselected');
|
||||||
|
if (!selected) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '请选择日期',
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
const finalDate = dayjs(selected as Date).format("YYYY-MM-DD");
|
const finalDate = dayjs(selected as Date).format("YYYY-MM-DD");
|
||||||
if (onChange){
|
if (onChange){
|
||||||
onChange(finalDate)
|
onChange(finalDate)
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ const PopupPicker = ({
|
|||||||
ntrpTested,
|
ntrpTested,
|
||||||
}: PickerProps) => {
|
}: PickerProps) => {
|
||||||
const [defaultValue, setDefaultValue] = useState<(string | number)[]>([]);
|
const [defaultValue, setDefaultValue] = useState<(string | number)[]>([]);
|
||||||
const [defaultOptions, setDefaultOptions] = useState<PickerOption[][]>([]);
|
const [defaultOptions, setDefaultOptions] = useState<PickerOption[][]>([...options]);
|
||||||
const [pickerCurrentValue, setPickerCurrentValue] =
|
const [pickerCurrentValue, setPickerCurrentValue] =
|
||||||
useState<(string | number)[]>(value);
|
useState<(string | number)[]>(value);
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import {
|
|||||||
EvaluateCallback,
|
EvaluateCallback,
|
||||||
EvaluateScene,
|
EvaluateScene,
|
||||||
} from "@/store/evaluateStore";
|
} from "@/store/evaluateStore";
|
||||||
|
import { useListState } from "@/store/listStore";
|
||||||
|
|
||||||
import { navigateTo, redirectTo, navigateBack } from "@/utils/navigation";
|
import { navigateTo, redirectTo, navigateBack } from "@/utils/navigation";
|
||||||
import { requireLoginWithPhone } from "@/utils/helper";
|
import { requireLoginWithPhone } from "@/utils/helper";
|
||||||
import styles from "./index.module.scss";
|
import styles from "./index.module.scss";
|
||||||
@@ -24,6 +26,11 @@ const PublishMenu: React.FC<PublishMenuProps> = (props) => {
|
|||||||
const { onVisibleChange } = props;
|
const { onVisibleChange } = props;
|
||||||
const [isVisible, setIsVisible] = useState(false);
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
area
|
||||||
|
} = useListState();
|
||||||
|
|
||||||
|
|
||||||
// 使用 useEffect 监听 isVisible 变化,确保所有情况都能触发回调
|
// 使用 useEffect 监听 isVisible 变化,确保所有情况都能触发回调
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onVisibleChange?.(isVisible);
|
onVisibleChange?.(isVisible);
|
||||||
@@ -59,6 +66,16 @@ const PublishMenu: React.FC<PublishMenuProps> = (props) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
const handleMenuItemClick = (type: "individual" | "group" | "ai") => {
|
const handleMenuItemClick = (type: "individual" | "group" | "ai") => {
|
||||||
|
const [_, address] = area;
|
||||||
|
if (address !== '上海市') {
|
||||||
|
(Taro as any).showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '仅上海地区开放,您可加入社群或切换城市',
|
||||||
|
showCancel: false,
|
||||||
|
confirmText: '知道了'
|
||||||
|
})
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!userInfo.ntrp_level) {
|
if (!userInfo.ntrp_level) {
|
||||||
ntrpRef.current.show({
|
ntrpRef.current.show({
|
||||||
type: EvaluateScene.publish,
|
type: EvaluateScene.publish,
|
||||||
|
|||||||
@@ -89,25 +89,15 @@ const RadarChart: React.FC = forwardRef((props, ref) => {
|
|||||||
ctx.lineWidth = 1;
|
ctx.lineWidth = 1;
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
// 标签
|
// 标签:沿轴线外侧延伸,文字中心对齐轴线端点
|
||||||
const offset = 10;
|
const labelOffset = 28;
|
||||||
const textX = center.x + (radius + offset) * Math.cos(angle);
|
const textX = center.x + (radius + labelOffset) * Math.cos(angle);
|
||||||
const textY = center.y + (radius + offset) * Math.sin(angle);
|
const textY = center.y + (radius + labelOffset) * Math.sin(angle);
|
||||||
|
|
||||||
ctx.font = "12px sans-serif";
|
ctx.font = "12px sans-serif";
|
||||||
ctx.fillStyle = "#333";
|
ctx.fillStyle = "#333";
|
||||||
ctx.textBaseline = "middle";
|
ctx.textBaseline = "middle";
|
||||||
|
|
||||||
if (
|
|
||||||
Math.abs(angle) < 0.01 ||
|
|
||||||
Math.abs(Math.abs(angle) - Math.PI) < 0.01
|
|
||||||
) {
|
|
||||||
ctx.textAlign = "center";
|
ctx.textAlign = "center";
|
||||||
} else if (angle > -Math.PI / 2 && angle < Math.PI / 2) {
|
|
||||||
ctx.textAlign = "left";
|
|
||||||
} else {
|
|
||||||
ctx.textAlign = "right";
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.fillText(label, textX, textY);
|
ctx.fillText(label, textX, textY);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import Taro from "@tarojs/taro";
|
|||||||
import { View, Canvas } from "@tarojs/components";
|
import { View, Canvas } from "@tarojs/components";
|
||||||
import { forwardRef, useImperativeHandle } from "react";
|
import { forwardRef, useImperativeHandle } from "react";
|
||||||
import shareLogoSvg from "@/static/ntrp/ntrp_share_logo.png";
|
import shareLogoSvg from "@/static/ntrp/ntrp_share_logo.png";
|
||||||
import docCopySvg from "@/static/ntrp/ntrp_doc_copy.svg";
|
import docCopyPng from "@/static/ntrp/ntrp_doc_copy.png";
|
||||||
import { OSS_BASE_URL } from "@/config/api";
|
import { OSS_BASE } from "@/config/api";
|
||||||
|
|
||||||
interface RadarChartV2Props {
|
interface RadarChartV2Props {
|
||||||
data: [string, number][];
|
data: [string, number][];
|
||||||
@@ -29,18 +29,24 @@ export interface RadarChartV2Ref {
|
|||||||
}) => Promise<string>;
|
}) => Promise<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref) => {
|
const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>(
|
||||||
|
(props, ref) => {
|
||||||
const { data } = props;
|
const { data } = props;
|
||||||
|
|
||||||
const maxValue = 100;
|
const maxValue = 100;
|
||||||
const levels = 5;
|
const levels = 5;
|
||||||
|
|
||||||
// 在 exportCanvasV2 中绘制雷达图的函数
|
// 在 exportCanvasV2 中绘制雷达图的函数
|
||||||
function drawRadarChart(ctx: CanvasRenderingContext2D, radarX: number, radarY: number, radarSize: number) {
|
function drawRadarChart(
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
radarX: number,
|
||||||
|
radarY: number,
|
||||||
|
radarSize: number,
|
||||||
|
) {
|
||||||
// 雷达图中心点位置(radarSize 已经是2倍图尺寸)
|
// 雷达图中心点位置(radarSize 已经是2倍图尺寸)
|
||||||
const center = {
|
const center = {
|
||||||
x: radarX + radarSize / 2,
|
x: radarX + radarSize / 2,
|
||||||
y: radarY + radarSize / 2
|
y: radarY + radarSize / 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 计算实际半径(radarSize 是直径,半径是直径的一半)
|
// 计算实际半径(radarSize 是直径,半径是直径的一半)
|
||||||
@@ -48,9 +54,9 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
|
|||||||
|
|
||||||
// 启用抗锯齿
|
// 启用抗锯齿
|
||||||
ctx.imageSmoothingEnabled = true;
|
ctx.imageSmoothingEnabled = true;
|
||||||
ctx.imageSmoothingQuality = 'high';
|
ctx.imageSmoothingQuality = "high";
|
||||||
ctx.lineCap = 'round';
|
ctx.lineCap = "round";
|
||||||
ctx.lineJoin = 'round';
|
ctx.lineJoin = "round";
|
||||||
|
|
||||||
// 解析数据
|
// 解析数据
|
||||||
const { texts, vals } = data.reduce(
|
const { texts, vals } = data.reduce(
|
||||||
@@ -61,7 +67,7 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
|
|||||||
vals: [...res.vals, val],
|
vals: [...res.vals, val],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{ texts: [], vals: [] }
|
{ texts: [], vals: [] },
|
||||||
);
|
);
|
||||||
|
|
||||||
// === 绘制圆形网格 ===
|
// === 绘制圆形网格 ===
|
||||||
@@ -102,26 +108,15 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
|
|||||||
ctx.lineWidth = 1 * (radarSize / 200); // 根据2倍图调整线宽
|
ctx.lineWidth = 1 * (radarSize / 200); // 根据2倍图调整线宽
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
// 标签 - 文字显示在圆圈外面
|
// 标签:沿轴线外侧延伸,文字中心对齐轴线端点(与 index.tsx 一致)
|
||||||
const offset = 10 * (radarSize / 200); // 文字距离圆圈的偏移量(2倍图)
|
const labelOffset = 28 * (radarSize / 200); // 文字距离圆圈的偏移量(2倍图)
|
||||||
const textX = center.x + (radius + offset) * Math.cos(angle);
|
const textX = center.x + (radius + labelOffset) * Math.cos(angle);
|
||||||
const textY = center.y + (radius + offset) * Math.sin(angle);
|
const textY = center.y + (radius + labelOffset) * Math.sin(angle);
|
||||||
|
|
||||||
ctx.font = `${12 * (radarSize / 200)}px sans-serif`; // 根据2倍图调整字体大小
|
ctx.font = `${12 * (radarSize / 200)}px sans-serif`; // 根据2倍图调整字体大小
|
||||||
ctx.fillStyle = "#333";
|
ctx.fillStyle = "#333";
|
||||||
ctx.textBaseline = "middle";
|
ctx.textBaseline = "middle";
|
||||||
|
|
||||||
// 调整文字对齐方式
|
|
||||||
if (
|
|
||||||
Math.abs(angle) < 0.01 ||
|
|
||||||
Math.abs(Math.abs(angle) - Math.PI) < 0.01
|
|
||||||
) {
|
|
||||||
ctx.textAlign = "center";
|
ctx.textAlign = "center";
|
||||||
} else if (angle > -Math.PI / 2 && angle < Math.PI / 2) {
|
|
||||||
ctx.textAlign = "left";
|
|
||||||
} else {
|
|
||||||
ctx.textAlign = "right";
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.fillText(label, textX, textY);
|
ctx.fillText(label, textX, textY);
|
||||||
});
|
});
|
||||||
@@ -159,25 +154,39 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取图片信息(宽高)
|
// 获取图片信息(宽高)
|
||||||
function getImageInfo(src: string): Promise<{ width: number; height: number }> {
|
function getImageInfo(
|
||||||
|
src: string,
|
||||||
|
): Promise<{ width: number; height: number }> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
(Taro as any).getImageInfo({
|
(Taro as any).getImageInfo({
|
||||||
src,
|
src,
|
||||||
success: (res: any) => resolve({ width: res.width, height: res.height }),
|
success: (res: any) =>
|
||||||
|
resolve({ width: res.width, height: res.height }),
|
||||||
fail: reject,
|
fail: reject,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 绘制圆角矩形
|
// 绘制圆角矩形
|
||||||
function roundRect(ctx: any, x: number, y: number, width: number, height: number, radius: number) {
|
function roundRect(
|
||||||
|
ctx: any,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
radius: number,
|
||||||
|
) {
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(x + radius, y);
|
ctx.moveTo(x + radius, y);
|
||||||
ctx.lineTo(x + width - radius, y);
|
ctx.lineTo(x + width - radius, y);
|
||||||
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
||||||
ctx.lineTo(x + width, y + height - radius);
|
ctx.lineTo(x + width, y + height - radius);
|
||||||
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
ctx.quadraticCurveTo(
|
||||||
|
x + width,
|
||||||
|
y + height,
|
||||||
|
x + width - radius,
|
||||||
|
y + height,
|
||||||
|
);
|
||||||
ctx.lineTo(x + radius, y + height);
|
ctx.lineTo(x + radius, y + height);
|
||||||
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
||||||
ctx.lineTo(x, y + radius);
|
ctx.lineTo(x, y + radius);
|
||||||
@@ -198,8 +207,7 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
|
|||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
// 生成原始雷达图(已废弃,现在直接在 exportCanvasV2 中绘制)
|
// 生成原始雷达图(已废弃,现在直接在 exportCanvasV2 中绘制)
|
||||||
generateImage: () =>
|
generateImage: () => Promise.resolve(""),
|
||||||
Promise.resolve(""),
|
|
||||||
|
|
||||||
// 生成完整图片(包含标题、雷达图、底部文字和二维码)
|
// 生成完整图片(包含标题、雷达图、底部文字和二维码)
|
||||||
generateFullImage: async (options: {
|
generateFullImage: async (options: {
|
||||||
@@ -240,11 +248,11 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
|
|||||||
|
|
||||||
// 启用抗锯齿
|
// 启用抗锯齿
|
||||||
ctx.imageSmoothingEnabled = true;
|
ctx.imageSmoothingEnabled = true;
|
||||||
ctx.imageSmoothingQuality = 'high';
|
ctx.imageSmoothingQuality = "high";
|
||||||
|
|
||||||
// 绘制背景 - 使用 share_bg.png 背景图,撑满整个画布(从 OSS 动态加载)
|
// 绘制背景 - 使用 share_bg.png 背景图,撑满整个画布(从 OSS 动态加载)
|
||||||
try {
|
try {
|
||||||
const shareBgUrl = `${OSS_BASE_URL}/images/share_bg.png`;
|
const shareBgUrl = `${OSS_BASE}/front/ball/images/share_bg.png`;
|
||||||
const bgImg = await loadImage(canvas, shareBgUrl);
|
const bgImg = await loadImage(canvas, shareBgUrl);
|
||||||
ctx.drawImage(bgImg, 0, 0, width, height);
|
ctx.drawImage(bgImg, 0, 0, width, height);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -264,18 +272,28 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
|
|||||||
if (options.avatarUrl) {
|
if (options.avatarUrl) {
|
||||||
try {
|
try {
|
||||||
const avatarSize = 43.46 * scale; // 设计稿头像尺寸
|
const avatarSize = 43.46 * scale; // 设计稿头像尺寸
|
||||||
const avatarImg = await loadImage(canvas, options.avatarUrl);
|
const avatarImg = await loadImage(
|
||||||
|
canvas,
|
||||||
|
options.avatarUrl,
|
||||||
|
);
|
||||||
const avatarInfo = await getImageInfo(options.avatarUrl);
|
const avatarInfo = await getImageInfo(options.avatarUrl);
|
||||||
|
|
||||||
// 头像区域总宽度(头像 + 装饰图片重叠部分)
|
// 头像区域总宽度(头像 + 装饰图片重叠部分)
|
||||||
const avatarWrapWidth = 84.7 * scale; // 设计稿 Frame 1912055063 宽度
|
const avatarWrapWidth = 84.7 * scale; // 设计稿 Frame 1912055063 宽度
|
||||||
const avatarX = sidePadding + (294 * scale - avatarWrapWidth) / 2; // 294 是 Frame 1912055062 宽度,居中
|
const avatarX =
|
||||||
|
sidePadding + (294 * scale - avatarWrapWidth) / 2; // 294 是 Frame 1912055062 宽度,居中
|
||||||
const avatarY = currentY;
|
const avatarY = currentY;
|
||||||
|
|
||||||
// 绘制头像圆形背景
|
// 绘制头像圆形背景
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2, 0, Math.PI * 2);
|
ctx.arc(
|
||||||
|
avatarX + avatarSize / 2,
|
||||||
|
avatarY + avatarSize / 2,
|
||||||
|
avatarSize / 2,
|
||||||
|
0,
|
||||||
|
Math.PI * 2,
|
||||||
|
);
|
||||||
ctx.fillStyle = "#FFFFFF";
|
ctx.fillStyle = "#FFFFFF";
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
ctx.strokeStyle = "#EFEFEF";
|
ctx.strokeStyle = "#EFEFEF";
|
||||||
@@ -284,7 +302,8 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
|
|||||||
|
|
||||||
// 计算头像绘制尺寸,保持宽高比
|
// 计算头像绘制尺寸,保持宽高比
|
||||||
const innerSize = avatarSize - 1.94 * scale; // 内部可用尺寸
|
const innerSize = avatarSize - 1.94 * scale; // 内部可用尺寸
|
||||||
const avatarAspectRatio = avatarInfo.width / avatarInfo.height;
|
const avatarAspectRatio =
|
||||||
|
avatarInfo.width / avatarInfo.height;
|
||||||
let drawWidth = innerSize;
|
let drawWidth = innerSize;
|
||||||
let drawHeight = innerSize;
|
let drawHeight = innerSize;
|
||||||
let drawX = avatarX + 0.97 * scale;
|
let drawX = avatarX + 0.97 * scale;
|
||||||
@@ -303,9 +322,21 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
|
|||||||
|
|
||||||
// 绘制头像(圆形裁剪)
|
// 绘制头像(圆形裁剪)
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2 - 0.97 * scale, 0, Math.PI * 2);
|
ctx.arc(
|
||||||
|
avatarX + avatarSize / 2,
|
||||||
|
avatarY + avatarSize / 2,
|
||||||
|
avatarSize / 2 - 0.97 * scale,
|
||||||
|
0,
|
||||||
|
Math.PI * 2,
|
||||||
|
);
|
||||||
ctx.clip();
|
ctx.clip();
|
||||||
ctx.drawImage(avatarImg, drawX, drawY, drawWidth, drawHeight);
|
ctx.drawImage(
|
||||||
|
avatarImg,
|
||||||
|
drawX,
|
||||||
|
drawY,
|
||||||
|
drawWidth,
|
||||||
|
drawHeight,
|
||||||
|
);
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
|
|
||||||
// 绘制装饰图片(DocCopy)- 在头像右侧
|
// 绘制装饰图片(DocCopy)- 在头像右侧
|
||||||
@@ -315,7 +346,7 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
|
|||||||
const addonRotation = 8 * (Math.PI / 180); // 旋转 8 度
|
const addonRotation = 8 * (Math.PI / 180); // 旋转 8 度
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const docCopyImg = await loadImage(canvas, docCopySvg);
|
const docCopyImg = await loadImage(canvas, docCopyPng);
|
||||||
ctx.save();
|
ctx.save();
|
||||||
|
|
||||||
// 移动到旋转中心
|
// 移动到旋转中心
|
||||||
@@ -328,7 +359,14 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
|
|||||||
const borderRadius = 9.66 * scale; // 设计稿圆角
|
const borderRadius = 9.66 * scale; // 设计稿圆角
|
||||||
ctx.fillStyle = "#FFFFFF";
|
ctx.fillStyle = "#FFFFFF";
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
roundRect(ctx, -addonSize / 2, -addonSize / 2, addonSize, addonSize, borderRadius);
|
roundRect(
|
||||||
|
ctx,
|
||||||
|
-addonSize / 2,
|
||||||
|
-addonSize / 2,
|
||||||
|
addonSize,
|
||||||
|
addonSize,
|
||||||
|
borderRadius,
|
||||||
|
);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
|
|
||||||
// 添加渐变背景色
|
// 添加渐变背景色
|
||||||
@@ -345,7 +383,13 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
|
|||||||
const docSize = 26.18 * scale; // 设计稿内部图片尺寸
|
const docSize = 26.18 * scale; // 设计稿内部图片尺寸
|
||||||
const docRotation = -7 * (Math.PI / 180); // 内部旋转 -7 度
|
const docRotation = -7 * (Math.PI / 180); // 内部旋转 -7 度
|
||||||
ctx.rotate(docRotation);
|
ctx.rotate(docRotation);
|
||||||
ctx.drawImage(docCopyImg, -docSize / 2, -docSize / 2, docSize, docSize);
|
ctx.drawImage(
|
||||||
|
docCopyImg,
|
||||||
|
-docSize / 2,
|
||||||
|
-docSize / 2,
|
||||||
|
docSize,
|
||||||
|
docSize,
|
||||||
|
);
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to load docCopy image:", error);
|
console.error("Failed to load docCopy image:", error);
|
||||||
@@ -420,7 +464,9 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
|
|||||||
const qrX = 276 * scale; // 设计稿二维码 x 位置
|
const qrX = 276 * scale; // 设计稿二维码 x 位置
|
||||||
const qrY = 523 * scale; // 设计稿二维码 y 位置
|
const qrY = 523 * scale; // 设计稿二维码 y 位置
|
||||||
|
|
||||||
const bottomTextContent = options.bottomText || "长按识别二维码,快来加入,有你就有场!";
|
const bottomTextContent =
|
||||||
|
options.bottomText ||
|
||||||
|
"长按识别二维码,快来加入,有你就有场!";
|
||||||
|
|
||||||
// 绘制底部文字 - 设计稿:fontSize: 12, fontWeight: 400, line-height: 1.5(2倍图)
|
// 绘制底部文字 - 设计稿:fontSize: 12, fontWeight: 400, line-height: 1.5(2倍图)
|
||||||
ctx.fillStyle = "rgba(0, 0, 0, 0.45)";
|
ctx.fillStyle = "rgba(0, 0, 0, 0.45)";
|
||||||
@@ -469,7 +515,13 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
|
|||||||
const iconImg = await loadImage(canvas, shareLogoSvg);
|
const iconImg = await loadImage(canvas, shareLogoSvg);
|
||||||
// 图标位置:文字顶部上方 iconSize + gap
|
// 图标位置:文字顶部上方 iconSize + gap
|
||||||
const iconY = textY - iconSize - iconGap;
|
const iconY = textY - iconSize - iconGap;
|
||||||
ctx.drawImage(iconImg, topTitleX, iconY, 235 * scale, iconSize);
|
ctx.drawImage(
|
||||||
|
iconImg,
|
||||||
|
topTitleX,
|
||||||
|
iconY,
|
||||||
|
235 * scale,
|
||||||
|
iconSize,
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to load icon:", error);
|
console.error("Failed to load icon:", error);
|
||||||
}
|
}
|
||||||
@@ -479,7 +531,6 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
|
|||||||
ctx.fillText(lineText, textX, textY + index * lineHeight);
|
ctx.fillText(lineText, textX, textY + index * lineHeight);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// 绘制二维码 - 设计稿位置(带白色背景、边框、阴影和圆角)
|
// 绘制二维码 - 设计稿位置(带白色背景、边框、阴影和圆角)
|
||||||
|
|
||||||
if (options.qrCodeUrl) {
|
if (options.qrCodeUrl) {
|
||||||
@@ -528,10 +579,23 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
|
|||||||
// 绘制二维码图片(在圆角矩形内)
|
// 绘制二维码图片(在圆角矩形内)
|
||||||
ctx.save();
|
ctx.save();
|
||||||
// 创建圆角裁剪区域
|
// 创建圆角裁剪区域
|
||||||
roundRect(ctx, qrInnerX, qrInnerY, qrInnerSize, qrInnerSize, borderRadius - borderWidth);
|
roundRect(
|
||||||
|
ctx,
|
||||||
|
qrInnerX,
|
||||||
|
qrInnerY,
|
||||||
|
qrInnerSize,
|
||||||
|
qrInnerSize,
|
||||||
|
borderRadius - borderWidth,
|
||||||
|
);
|
||||||
ctx.clip();
|
ctx.clip();
|
||||||
// 绘制二维码图片
|
// 绘制二维码图片
|
||||||
ctx.drawImage(qrImg, qrInnerX, qrInnerY, qrInnerSize, qrInnerSize);
|
ctx.drawImage(
|
||||||
|
qrImg,
|
||||||
|
qrInnerX,
|
||||||
|
qrInnerY,
|
||||||
|
qrInnerSize,
|
||||||
|
qrInnerSize,
|
||||||
|
);
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
|
|
||||||
// 恢复上下文状态
|
// 恢复上下文状态
|
||||||
@@ -544,8 +608,8 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
|
|||||||
// 导出图片
|
// 导出图片
|
||||||
Taro.canvasToTempFilePath({
|
Taro.canvasToTempFilePath({
|
||||||
canvas,
|
canvas,
|
||||||
fileType: 'png',
|
fileType: "png",
|
||||||
quality: 1,
|
quality: 0.7,
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
resolve(res.tempFilePath);
|
resolve(res.tempFilePath);
|
||||||
},
|
},
|
||||||
@@ -567,13 +631,19 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
|
|||||||
<Canvas
|
<Canvas
|
||||||
type="2d"
|
type="2d"
|
||||||
id="exportCanvasV2"
|
id="exportCanvasV2"
|
||||||
style={{ position: "fixed", top: "-9999px", left: "-9999px", width: "700px", height: "1200px" }}
|
style={{
|
||||||
|
position: "fixed",
|
||||||
|
top: "-9999px",
|
||||||
|
left: "-9999px",
|
||||||
|
width: "700px",
|
||||||
|
height: "1200px",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
RadarChartV2.displayName = "RadarChartV2";
|
RadarChartV2.displayName = "RadarChartV2";
|
||||||
|
|
||||||
export default RadarChartV2;
|
export default RadarChartV2;
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ const SearchBarComponent = (props: IProps) => {
|
|||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
className={styles.searchBar}
|
className={styles.searchBar}
|
||||||
placeholder="搜索上海的球局和场地"
|
placeholder="搜索球局和场地"
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
value={value}
|
value={value}
|
||||||
onInputClick={onInputClick}
|
onInputClick={onInputClick}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { View, Canvas } from '@tarojs/components'
|
import { View, Canvas } from '@tarojs/components'
|
||||||
import Taro from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
import { OSS_BASE_URL } from "@/config/api";
|
import { OSS_BASE } from "@/config/api";
|
||||||
|
|
||||||
// 分享卡片数据接口
|
// 分享卡片数据接口
|
||||||
export interface ShareCardData {
|
export interface ShareCardData {
|
||||||
@@ -506,7 +506,7 @@ const ShareCardCanvas: React.FC<ShareCardCanvasProps> = ({
|
|||||||
const textX = iconX + iconSize + 20
|
const textX = iconX + iconSize + 20
|
||||||
|
|
||||||
// 绘制网球图标
|
// 绘制网球图标
|
||||||
const tennisBallPath = await loadImage(`${OSS_BASE_URL}/images/b3eaf45e-ef28-4e45-9195-823b832e0451.jpg`, canvasNode)
|
const tennisBallPath = await loadImage(`${OSS_BASE}/front/ball/images/b3eaf45e-ef28-4e45-9195-823b832e0451.jpg`, canvasNode)
|
||||||
ctx.drawImage(tennisBallPath, iconX, gameInfoY, iconSize, iconSize)
|
ctx.drawImage(tennisBallPath, iconX, gameInfoY, iconSize, iconSize)
|
||||||
|
|
||||||
// 绘制"单打"标签
|
// 绘制"单打"标签
|
||||||
@@ -542,7 +542,7 @@ const ShareCardCanvas: React.FC<ShareCardCanvasProps> = ({
|
|||||||
const dateX = danDaX
|
const dateX = danDaX
|
||||||
const timeInfoY = infoStartY + infoSpacing
|
const timeInfoY = infoStartY + infoSpacing
|
||||||
const timeInfoFontSize = scale * 24 * dpr
|
const timeInfoFontSize = scale * 24 * dpr
|
||||||
const calendarPath = await loadImage(`${OSS_BASE_URL}/images/ea792a5d-b105-4c95-bfc4-8af558f2b33b.jpg`, canvasNode)
|
const calendarPath = await loadImage(`${OSS_BASE}/front/ball/images/ea792a5d-b105-4c95-bfc4-8af558f2b33b.jpg`, canvasNode)
|
||||||
ctx.drawImage(calendarPath, iconX, timeInfoY, iconSize, iconSize)
|
ctx.drawImage(calendarPath, iconX, timeInfoY, iconSize, iconSize)
|
||||||
|
|
||||||
// 绘制日期(绿色)
|
// 绘制日期(绿色)
|
||||||
@@ -556,7 +556,7 @@ const ShareCardCanvas: React.FC<ShareCardCanvasProps> = ({
|
|||||||
// 绘制地点
|
// 绘制地点
|
||||||
const locationInfoY = infoStartY + infoSpacing * 2
|
const locationInfoY = infoStartY + infoSpacing * 2
|
||||||
const locationFontSize = scale * 22 * dpr
|
const locationFontSize = scale * 22 * dpr
|
||||||
const locationPath = await loadImage(`${OSS_BASE_URL}/images/adc9a167-2ea9-4e3b-b963-6a894a1fd91b.jpg`, canvasNode)
|
const locationPath = await loadImage(`${OSS_BASE}/front/ball/images/adc9a167-2ea9-4e3b-b963-6a894a1fd91b.jpg`, canvasNode)
|
||||||
ctx.drawImage(locationPath, iconX, locationInfoY, iconSize, iconSize)
|
ctx.drawImage(locationPath, iconX, locationInfoY, iconSize, iconSize)
|
||||||
drawBoldText(ctx, data.venueName, danDaX, locationInfoY + 10, locationFontSize, '#000000')
|
drawBoldText(ctx, data.venueName, danDaX, locationInfoY + 10, locationFontSize, '#000000')
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,10 @@ const TextareaTag: React.FC<TextareaTagProps> = ({
|
|||||||
// 处理文本输入变化
|
// 处理文本输入变化
|
||||||
const handleTextChange = useCallback((val: string) => {
|
const handleTextChange = useCallback((val: string) => {
|
||||||
console.log(val,'e.detail.value')
|
console.log(val,'e.detail.value')
|
||||||
onChange({...value, description: val})
|
const maxAllowedLength = Math.floor(maxLength * 1.2)
|
||||||
}, [onChange])
|
const truncatedVal = val.length > maxAllowedLength ? val.slice(0, maxAllowedLength) : val
|
||||||
|
onChange({...value, description: truncatedVal})
|
||||||
|
}, [onChange, maxLength, value])
|
||||||
|
|
||||||
// 处理标签选择变化
|
// 处理标签选择变化
|
||||||
const handleTagChange = useCallback((selectedTags: string[]) => {
|
const handleTagChange = useCallback((selectedTags: string[]) => {
|
||||||
@@ -77,6 +79,7 @@ const TextareaTag: React.FC<TextareaTagProps> = ({
|
|||||||
autoHeight={true}
|
autoHeight={true}
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
|
adjustPosition={false}
|
||||||
/>
|
/>
|
||||||
<View className={`char-count${isOverflow ? ' char-count--error' : ''}`}>
|
<View className={`char-count${isOverflow ? ' char-count--error' : ''}`}>
|
||||||
{value.description.length}/{maxLength}
|
{value.description.length}/{maxLength}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback } from 'react'
|
import React, { useCallback, useState } from 'react'
|
||||||
import { View } from '@tarojs/components'
|
import { View } from '@tarojs/components'
|
||||||
import { TextArea } from '@nutui/nutui-react-taro';
|
import { TextArea } from '@nutui/nutui-react-taro';
|
||||||
|
|
||||||
@@ -22,6 +22,10 @@ const TitleTextarea: React.FC<TitleTextareaProps> = ({
|
|||||||
onBlur
|
onBlur
|
||||||
}) => {
|
}) => {
|
||||||
const isOverflow = value.length > maxLength
|
const isOverflow = value.length > maxLength
|
||||||
|
// const [isFocused, setIsFocused] = useState(false)
|
||||||
|
|
||||||
|
// const showPlaceholder = !isFocused && !value
|
||||||
|
|
||||||
const handleChange = useCallback((values) => {
|
const handleChange = useCallback((values) => {
|
||||||
// if (values.length > maxLength ) {
|
// if (values.length > maxLength ) {
|
||||||
// const newValues = values.slice(0, maxLength)
|
// const newValues = values.slice(0, maxLength)
|
||||||
@@ -29,20 +33,38 @@ const TitleTextarea: React.FC<TitleTextareaProps> = ({
|
|||||||
// return;
|
// return;
|
||||||
// }
|
// }
|
||||||
onChange(values)
|
onChange(values)
|
||||||
}, [])
|
}, [onChange])
|
||||||
|
|
||||||
|
const handleFocus = useCallback(() => {
|
||||||
|
// setIsFocused(true)
|
||||||
|
onFocus?.()
|
||||||
|
}, [onFocus])
|
||||||
|
|
||||||
|
const handleBlur = useCallback(() => {
|
||||||
|
// setIsFocused(false)
|
||||||
|
onBlur?.()
|
||||||
|
}, [onBlur])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='title-input-wrapper'>
|
<View className='title-input-wrapper'>
|
||||||
|
<View className='title-input-box'>
|
||||||
<TextArea
|
<TextArea
|
||||||
className='title-input'
|
className='title-input'
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
value={value}
|
value={value}
|
||||||
onInput={(e) => handleChange(e.detail.value)}
|
onInput={(e) => handleChange(e.detail.value)}
|
||||||
// maxlength={maxLength}
|
// maxlength={maxLength}
|
||||||
autoSize={true}
|
|
||||||
placeholderClass='title-input-placeholder'
|
placeholderClass='title-input-placeholder'
|
||||||
onFocus={onFocus}
|
autoSize={true}
|
||||||
onBlur={onBlur}
|
onFocus={handleFocus}
|
||||||
|
onBlur={handleBlur}
|
||||||
/>
|
/>
|
||||||
|
{/* {showPlaceholder && (
|
||||||
|
<View className='title-input-placeholder-custom'>
|
||||||
|
{placeholder}
|
||||||
|
</View>
|
||||||
|
)} */}
|
||||||
|
</View>
|
||||||
<View className={`char-count${isOverflow ? ' char-count--error' : ''}`}>
|
<View className={`char-count${isOverflow ? ' char-count--error' : ''}`}>
|
||||||
{value.length}/{maxLength}
|
{value.length}/{maxLength}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -4,9 +4,22 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 8px 12px;
|
padding: 10px 12px;
|
||||||
min-height: 36px;
|
min-height: 32px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
.title-input-box {
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.title-input-placeholder {
|
||||||
|
color: rgba(60, 60, 67, 0.60) !important;
|
||||||
|
font-weight: normal !important;
|
||||||
|
position: relative;
|
||||||
|
line-height: 22px !important;
|
||||||
|
height: 22px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
.title-input {
|
.title-input {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -17,22 +30,23 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
resize: none;
|
resize: none;
|
||||||
line-height: 26px;
|
line-height: 22px;
|
||||||
min-height: 26px;
|
min-height: 22px;
|
||||||
}
|
}
|
||||||
|
.title-input-placeholder-custom {
|
||||||
// 使用 placeholderClass 来控制 placeholder 样式
|
position: absolute;
|
||||||
.title-input-placeholder {
|
left: 7px;
|
||||||
color: rgba(60, 60, 67, 0.60) !important;
|
top: 50%;
|
||||||
font-size: 16px !important;
|
width: 100%;
|
||||||
font-weight: normal !important;
|
padding: 0;
|
||||||
line-height: 26px !important;
|
color: rgba(60, 60, 67, 0.60);
|
||||||
height: 26px;
|
font-size: 16px;
|
||||||
flex: 1;
|
font-weight: normal;
|
||||||
|
line-height: 22px;
|
||||||
|
pointer-events: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.char-count {
|
.char-count {
|
||||||
color: #999;
|
color: #999;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ async function convert_to_jpg_and_compress(
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
Taro.canvasToTempFilePath({
|
Taro.canvasToTempFilePath({
|
||||||
canvas: canvas as unknown as Taro.Canvas,
|
canvas: canvas as unknown as Taro.Canvas,
|
||||||
fileType: "jpg",
|
fileType: "png",
|
||||||
quality: 0.7,
|
quality: 0.7,
|
||||||
success: (res) => resolve(res.tempFilePath),
|
success: (res) => resolve(res.tempFilePath),
|
||||||
fail: reject,
|
fail: reject,
|
||||||
|
|||||||
@@ -6,7 +6,12 @@ import "./index.scss";
|
|||||||
import { EditModal } from "@/components";
|
import { EditModal } from "@/components";
|
||||||
import { UserService, PickerOption } from "@/services/userService";
|
import { UserService, PickerOption } from "@/services/userService";
|
||||||
import { PopupPicker } from "@/components/Picker/index";
|
import { PopupPicker } from "@/components/Picker/index";
|
||||||
import { useUserActions, useNicknameChangeStatus, useLastTestResult } from "@/store/userStore";
|
import {
|
||||||
|
useUserActions,
|
||||||
|
useNicknameChangeStatus,
|
||||||
|
useLastTestResult,
|
||||||
|
useUserInfo,
|
||||||
|
} from "@/store/userStore";
|
||||||
import { UserInfoType } from "@/services/userService";
|
import { UserInfoType } from "@/services/userService";
|
||||||
import {
|
import {
|
||||||
useCities,
|
useCities,
|
||||||
@@ -69,7 +74,6 @@ const on_edit = () => {
|
|||||||
// 用户信息卡片组件
|
// 用户信息卡片组件
|
||||||
const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
||||||
editable = true,
|
editable = true,
|
||||||
user_info,
|
|
||||||
is_current_user,
|
is_current_user,
|
||||||
is_following = false,
|
is_following = false,
|
||||||
collapseProfile,
|
collapseProfile,
|
||||||
@@ -80,9 +84,12 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
|||||||
set_user_info,
|
set_user_info,
|
||||||
onTab,
|
onTab,
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
|
const user_info = useUserInfo();
|
||||||
const nickname_change_status = useNicknameChangeStatus();
|
const nickname_change_status = useNicknameChangeStatus();
|
||||||
const { setShowGuideBar } = useGlobalState();
|
const { setShowGuideBar } = useGlobalState();
|
||||||
const { updateUserInfo, updateNickname, fetchLastTestResult } = useUserActions();
|
const { updateUserInfo, updateNickname, fetchLastTestResult } =
|
||||||
|
useUserActions();
|
||||||
const ntrpLevels = useNtrpLevels();
|
const ntrpLevels = useNtrpLevels();
|
||||||
// 使用全局状态中的测试结果,避免重复调用接口
|
// 使用全局状态中的测试结果,避免重复调用接口
|
||||||
const lastTestResult = useLastTestResult();
|
const lastTestResult = useLastTestResult();
|
||||||
@@ -117,11 +124,15 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
|||||||
useState(false);
|
useState(false);
|
||||||
|
|
||||||
// 表单状态
|
// 表单状态
|
||||||
const [form_data, set_form_data] = useState<Partial<UserInfoType>>({});
|
const [form_data, set_form_data] = useState<Partial<UserInfoType>>({ ...user_info });
|
||||||
|
|
||||||
useDidShow(() => {
|
// useDidShow(() => {
|
||||||
|
// set_form_data({ ...user_info });
|
||||||
|
// });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
set_form_data({ ...user_info });
|
set_form_data({ ...user_info });
|
||||||
});
|
}, [user_info])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const visibles = [
|
const visibles = [
|
||||||
@@ -129,6 +140,7 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
|||||||
location_picker_visible,
|
location_picker_visible,
|
||||||
ntrp_picker_visible,
|
ntrp_picker_visible,
|
||||||
occupation_picker_visible,
|
occupation_picker_visible,
|
||||||
|
edit_modal_visible,
|
||||||
];
|
];
|
||||||
const allPickersClosed = visibles.every((item) => !item);
|
const allPickersClosed = visibles.every((item) => !item);
|
||||||
// 所有选择器都关闭时,显示 GuideBar;否则隐藏
|
// 所有选择器都关闭时,显示 GuideBar;否则隐藏
|
||||||
@@ -138,6 +150,7 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
|||||||
location_picker_visible,
|
location_picker_visible,
|
||||||
ntrp_picker_visible,
|
ntrp_picker_visible,
|
||||||
occupation_picker_visible,
|
occupation_picker_visible,
|
||||||
|
edit_modal_visible,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 职业数据
|
// 职业数据
|
||||||
@@ -295,8 +308,8 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
|||||||
|
|
||||||
// 处理地区选择
|
// 处理地区选择
|
||||||
const handle_location_change = (e: any) => {
|
const handle_location_change = (e: any) => {
|
||||||
const [country, province, city] = e;
|
const [province, city, district] = e;
|
||||||
handle_field_edit({ country, province, city });
|
handle_field_edit({ province, city, district });
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理NTRP水平选择
|
// 处理NTRP水平选择
|
||||||
@@ -307,8 +320,8 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
|||||||
|
|
||||||
// 处理职业选择
|
// 处理职业选择
|
||||||
const handle_occupation_change = (e: any) => {
|
const handle_occupation_change = (e: any) => {
|
||||||
const [country, province, city] = e;
|
const [firstVal, secondVal, thirdVal] = e;
|
||||||
handle_field_edit("occupation", `${country} ${province} ${city}`);
|
handle_field_edit("occupation", `${firstVal} ${secondVal} ${thirdVal}`);
|
||||||
};
|
};
|
||||||
const handle_edit_modal_cancel = () => {
|
const handle_edit_modal_cancel = () => {
|
||||||
// 关闭编辑弹窗时显示 GuideBar
|
// 关闭编辑弹窗时显示 GuideBar
|
||||||
@@ -365,7 +378,6 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
|||||||
urls: [url],
|
urls: [url],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className="user_info_card">
|
<View className="user_info_card">
|
||||||
{/* 头像和基本信息 */}
|
{/* 头像和基本信息 */}
|
||||||
@@ -565,12 +577,12 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
|||||||
<Text>选择职业</Text>
|
<Text>选择职业</Text>
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
{user_info.country || user_info.province || user_info.city ? (
|
{user_info.province || user_info.city || user_info.district ? (
|
||||||
<View
|
<View
|
||||||
className="tag_item"
|
className="tag_item"
|
||||||
onClick={() => editable && handle_open_edit_modal("location")}
|
onClick={() => editable && handle_open_edit_modal("location")}
|
||||||
>
|
>
|
||||||
<Text className="tag_text">{`${user_info.province}${user_info.city}`}</Text>
|
<Text className="tag_text">{`${user_info.city}${user_info.district}`}</Text>
|
||||||
</View>
|
</View>
|
||||||
) : is_current_user ? (
|
) : is_current_user ? (
|
||||||
<View
|
<View
|
||||||
@@ -643,16 +655,16 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
|||||||
<PopupPicker
|
<PopupPicker
|
||||||
showHeader={true}
|
showHeader={true}
|
||||||
title="选择性别"
|
title="选择性别"
|
||||||
options={[
|
options={
|
||||||
[
|
[
|
||||||
{ text: "男", value: "0" },
|
{ text: "男", value: "0" },
|
||||||
{ text: "女", value: "1" },
|
{ text: "女", value: "1" },
|
||||||
{ text: "保密", value: "2" },
|
{ text: "保密", value: "2" },
|
||||||
],
|
]
|
||||||
]}
|
}
|
||||||
visible={gender_picker_visible}
|
visible={gender_picker_visible}
|
||||||
setvisible={setGenderPickerVisible}
|
setvisible={setGenderPickerVisible}
|
||||||
value={form_data.gender === "" ? ["0"] : [form_data.gender]}
|
value={!form_data.gender ? ["0"] : [form_data.gender]}
|
||||||
onChange={handle_gender_change}
|
onChange={handle_gender_change}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -665,8 +677,8 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
|||||||
visible={location_picker_visible}
|
visible={location_picker_visible}
|
||||||
setvisible={setLocationPickerVisible}
|
setvisible={setLocationPickerVisible}
|
||||||
value={
|
value={
|
||||||
form_data.country
|
form_data.province
|
||||||
? [form_data.country, form_data.province, form_data.city]
|
? [form_data.province, form_data.city, form_data.district]
|
||||||
: getDefaultOption(cities)
|
: getDefaultOption(cities)
|
||||||
}
|
}
|
||||||
onChange={handle_location_change}
|
onChange={handle_location_change}
|
||||||
@@ -678,15 +690,12 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
|
|||||||
showHeader={true}
|
showHeader={true}
|
||||||
title="选择 NTRP 自评水平"
|
title="选择 NTRP 自评水平"
|
||||||
ntrpTested={ntrpTested}
|
ntrpTested={ntrpTested}
|
||||||
options={ntrpLevels.map((level) => ({
|
options={ntrpLevels}
|
||||||
text: level,
|
|
||||||
value: level,
|
|
||||||
}))}
|
|
||||||
type="ntrp"
|
type="ntrp"
|
||||||
img={user_info.avatar_url || ""}
|
img={user_info.avatar_url || ""}
|
||||||
visible={ntrp_picker_visible}
|
visible={ntrp_picker_visible}
|
||||||
setvisible={setNtrpPickerVisible}
|
setvisible={setNtrpPickerVisible}
|
||||||
value={[form_data.ntrp_level || "2.5"]}
|
value={!form_data.ntrp_level ? ["2.5"] : [form_data.ntrp_level]}
|
||||||
onChange={handle_ntrp_level_change}
|
onChange={handle_ntrp_level_change}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -864,8 +873,7 @@ export const GameTabs: React.FC<GameTabsProps> = ({
|
|||||||
<Text className="tab_text">{hosted_text}</Text>
|
<Text className="tab_text">{hosted_text}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
className={`tab_item ${
|
className={`tab_item ${active_tab === "participated" ? "active" : ""
|
||||||
active_tab === "participated" ? "active" : ""
|
|
||||||
}`}
|
}`}
|
||||||
onClick={() => on_tab_change("participated")}
|
onClick={() => on_tab_change("participated")}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import NumberInterval from "./NumberInterval";
|
|||||||
import TimeSelector from "./TimeSelector";
|
import TimeSelector from "./TimeSelector";
|
||||||
import TitleTextarea from "./TitleTextarea";
|
import TitleTextarea from "./TitleTextarea";
|
||||||
import CommonPopup from "./CommonPopup";
|
import CommonPopup from "./CommonPopup";
|
||||||
|
import CustomPopup from "./CustomPopup";
|
||||||
import { CalendarUI, DialogCalendarCard } from "./Picker";
|
import { CalendarUI, DialogCalendarCard } from "./Picker";
|
||||||
import CommonDialog from "./CommonDialog";
|
import CommonDialog from "./CommonDialog";
|
||||||
import PublishMenu from "./PublishMenu/PublishMenu";
|
import PublishMenu from "./PublishMenu/PublishMenu";
|
||||||
@@ -37,6 +38,7 @@ export {
|
|||||||
TimeSelector,
|
TimeSelector,
|
||||||
TitleTextarea,
|
TitleTextarea,
|
||||||
CommonPopup,
|
CommonPopup,
|
||||||
|
CustomPopup,
|
||||||
DialogCalendarCard,
|
DialogCalendarCard,
|
||||||
CalendarUI,
|
CalendarUI,
|
||||||
CommonDialog,
|
CommonDialog,
|
||||||
|
|||||||
@@ -46,8 +46,7 @@ function genRefundNotice(refund_policy) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderCancelContent(checkOrderInfo) {
|
function renderCancelContent(refund_policy = []) {
|
||||||
const { refund_policy = [] } = checkOrderInfo;
|
|
||||||
const current = dayjs();
|
const current = dayjs();
|
||||||
const policyList = [
|
const policyList = [
|
||||||
{
|
{
|
||||||
@@ -65,7 +64,6 @@ function renderCancelContent(checkOrderInfo) {
|
|||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
console.log("policyList", policyList);
|
|
||||||
const targetIndex = policyList.findIndex((item) => item.beforeCurrent);
|
const targetIndex = policyList.findIndex((item) => item.beforeCurrent);
|
||||||
const { notice } = genRefundNotice(refund_policy);
|
const { notice } = genRefundNotice(refund_policy);
|
||||||
return (
|
return (
|
||||||
@@ -107,7 +105,7 @@ export type RefundRef = {
|
|||||||
|
|
||||||
export default forwardRef<RefundRef>(function RefundPopup(_props, ref) {
|
export default forwardRef<RefundRef>(function RefundPopup(_props, ref) {
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [checkOrderInfo, setCheckOrderInfo] = useState({});
|
const [refundPolicy, setRefundPolicy] = useState([]);
|
||||||
const [orderData, setOrderData] = useState({});
|
const [orderData, setOrderData] = useState({});
|
||||||
const onDown = useRef<((result: boolean) => void) | null>(null);
|
const onDown = useRef<((result: boolean) => void) | null>(null);
|
||||||
|
|
||||||
@@ -116,11 +114,10 @@ export default forwardRef<RefundRef>(function RefundPopup(_props, ref) {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
async function onShow(orderItem, onFinish: (result: boolean) => void) {
|
async function onShow(orderItem, onFinish: (result: boolean) => void) {
|
||||||
const { game_info } = orderItem;
|
const { refund_policy } = orderItem;
|
||||||
onDown.current = onFinish;
|
onDown.current = onFinish;
|
||||||
setOrderData(orderItem);
|
setOrderData(orderItem);
|
||||||
const res = await orderService.getCheckOrderInfo(game_info.id);
|
setRefundPolicy(refund_policy);
|
||||||
setCheckOrderInfo(res.data);
|
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +169,7 @@ export default forwardRef<RefundRef>(function RefundPopup(_props, ref) {
|
|||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
{renderCancelContent(checkOrderInfo)}
|
{renderCancelContent(refundPolicy)}
|
||||||
<Button className={styles.action} onClick={handleConfirmQuit}>
|
<Button className={styles.action} onClick={handleConfirmQuit}>
|
||||||
确认并退出
|
确认并退出
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import envConfig from './env'// API配置
|
import envConfig from './env'// API配置
|
||||||
|
|
||||||
// OSS 基础路径配置
|
// OSS 配置:仅域名,调用处拼接 /front/ball 及后续路径
|
||||||
export const OSS_BASE_URL = 'https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball'
|
// export const OSS_BASE = "https://bimwe-oss.oss-cn-shanghai.aliyuncs.com";
|
||||||
|
|
||||||
|
// 因乐驰OSS 配置:仅域名,调用处拼接 /front/ball 及后续路径
|
||||||
|
export const OSS_BASE = envConfig.ossBaseURL;
|
||||||
|
|
||||||
export const API_CONFIG = {
|
export const API_CONFIG = {
|
||||||
// 基础URL
|
// 基础URL
|
||||||
|
|||||||
61
src/config/env.ts
Normal file
61
src/config/env.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import Taro from "@tarojs/taro";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 环境配置:从 config/env.config.ts 经 defineConstants 注入
|
||||||
|
* 构建时由 config/index.ts 根据 APP_ENV 选择并注入
|
||||||
|
*/
|
||||||
|
export type EnvType = "dev" | "dev_local" | "sit" | "pr";
|
||||||
|
|
||||||
|
export interface EnvConfig {
|
||||||
|
name: string;
|
||||||
|
apiBaseURL: string;
|
||||||
|
ossBaseURL: string;
|
||||||
|
timeout: number;
|
||||||
|
enableLog: boolean;
|
||||||
|
enableMock: boolean;
|
||||||
|
customerService: {
|
||||||
|
corpId: string;
|
||||||
|
serviceUrl: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从 defineConstants 注入的编译时常量读取
|
||||||
|
const getInjectedConfig = (): EnvConfig => ({
|
||||||
|
name: process.env.APP_ENV || "dev",
|
||||||
|
apiBaseURL: process.env.API_BASE_URL || "",
|
||||||
|
ossBaseURL: process.env.OSS_BASE_URL || "",
|
||||||
|
timeout: Number(process.env.TIMEOUT) || 10000,
|
||||||
|
enableLog: process.env.ENABLE_LOG === "true",
|
||||||
|
enableMock: false,
|
||||||
|
customerService: {
|
||||||
|
corpId: process.env.CUSTOMER_CORP_ID || "",
|
||||||
|
serviceUrl: process.env.CUSTOMER_SERVICE_URL || "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getCurrentEnv = (): EnvType =>
|
||||||
|
(process.env.APP_ENV as EnvType) || "dev";
|
||||||
|
|
||||||
|
export const getCurrentConfig = (): EnvConfig => getInjectedConfig();
|
||||||
|
|
||||||
|
export const isDevelopment = (): boolean =>
|
||||||
|
getCurrentEnv() === "dev" || getCurrentEnv() === "dev_local" || getCurrentEnv() === "sit";
|
||||||
|
|
||||||
|
export const isProduction = (): boolean => getCurrentEnv() === "pr";
|
||||||
|
|
||||||
|
export const getEnvInfo = () => {
|
||||||
|
const config = getCurrentConfig();
|
||||||
|
return {
|
||||||
|
env: getCurrentEnv(),
|
||||||
|
config,
|
||||||
|
taroEnv: (Taro as any).getEnv?.(),
|
||||||
|
platform:
|
||||||
|
(Taro as any).getEnv?.() === (Taro as any).ENV_TYPE?.WEAPP
|
||||||
|
? "微信小程序"
|
||||||
|
: (Taro as any).getEnv?.() === (Taro as any).ENV_TYPE?.WEB
|
||||||
|
? "Web"
|
||||||
|
: "未知",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getCurrentConfig();
|
||||||
@@ -1,73 +1,76 @@
|
|||||||
|
import { OSS_BASE } from "@/config/api";
|
||||||
export default {
|
export default {
|
||||||
ICON_REMOVE: require('@/static/publishBall/icon-remove.svg'),
|
ICON_REMOVE: require("@/static/publishBall/icon-remove.svg"),
|
||||||
ICON_UPLOAD: require('@/static/publishBall/icon-upload.svg'),
|
ICON_UPLOAD: require("@/static/publishBall/icon-upload.svg"),
|
||||||
ICON_LOCATION: require('@/static/publishBall/icon-location.svg'),
|
ICON_LOCATION: require("@/static/publishBall/icon-location.svg"),
|
||||||
ICON_GAMEPLAY: require('@/static/publishBall/icon-gameplay.svg'),
|
ICON_GAMEPLAY: require("@/static/publishBall/icon-gameplay.svg"),
|
||||||
ICON_PERSONAL: require('@/static/publishBall/icon-personal.svg'),
|
ICON_PERSONAL: require("@/static/publishBall/icon-personal.svg"),
|
||||||
ICON_CHANGDA: require('@/static/publishBall/icon-changda.svg'),
|
ICON_CHANGDA: require("@/static/publishBall/icon-changda.svg"),
|
||||||
ICON_COST: require('@/static/publishBall/icon-cost.svg'),
|
ICON_COST: require("@/static/publishBall/icon-cost.svg"),
|
||||||
ICON_TIPS: require('@/static/publishBall/icon-tips.svg'),
|
ICON_TIPS: require("@/static/publishBall/icon-tips.svg"),
|
||||||
ICON_ARROW_RIGHT: require('@/static/publishBall/icon-arrow-right.svg'),
|
ICON_ARROW_RIGHT: require("@/static/publishBall/icon-arrow-right.svg"),
|
||||||
ICON_FILTER: require('@/static/list/icon-filter.svg'),
|
ICON_FILTER: require("@/static/list/icon-filter.svg"),
|
||||||
ICON_FILTER_SELECTED: require('@/static/list/icon-filter-selected.svg'),
|
ICON_FILTER_SELECTED: require("@/static/list/icon-filter-selected.svg"),
|
||||||
ICON_SEARCH: require('@/static/list/icon-search.svg'),
|
ICON_SEARCH: require("@/static/list/icon-search.svg"),
|
||||||
ICON_PLAY: require('@/static/list/icon-play.svg'),
|
ICON_PLAY: require("@/static/list/icon-play.svg"),
|
||||||
ICON_SITE: require('@/static/list/icon-site.svg'),
|
ICON_SITE: require("@/static/list/icon-site.svg"),
|
||||||
ICON_ARROW_DOWN: require('@/static/list/icon-arrow-down.svg'),
|
ICON_ARROW_DOWN: require("@/static/list/icon-arrow-down.svg"),
|
||||||
ICON_MENU_ITEM_SELECTED: require('@/static/list/icon-menu-item-selected.svg'),
|
ICON_MENU_ITEM_SELECTED: require("@/static/list/icon-menu-item-selected.svg"),
|
||||||
ICON_ARROW_DOWN_WHITE: require('@/static/list/icon-arrow-down-white.svg'),
|
ICON_ARROW_DOWN_WHITE: require("@/static/list/icon-arrow-down-white.svg"),
|
||||||
ICON_LIST_RIGHT_ARROW: require('@/static/list/icon-list-right-arrow.svg'),
|
ICON_LIST_RIGHT_ARROW: require("@/static/list/icon-list-right-arrow.svg"),
|
||||||
ICON_ARROW_LEFT: require('@/static/detail/icon-arrow-left.svg'),
|
ICON_ARROW_LEFT: require("@/static/detail/icon-arrow-left.svg"),
|
||||||
ICON_LOGO_GO: require('@/static/detail/icon-logo-go.svg'),
|
ICON_LOGO_GO: require("@/static/detail/icon-logo-go.svg"),
|
||||||
ICON_MAP: require('@/static/publishBall/icon-map.svg'),
|
ICON_MAP: require("@/static/publishBall/icon-map.svg"),
|
||||||
ICON_STADIUM: require('@/static/publishBall/icon-stadium.svg'),
|
ICON_STADIUM: require("@/static/publishBall/icon-stadium.svg"),
|
||||||
ICON_ARRORW_SMALL: require('@/static/publishBall/icon-arrow-small.svg'),
|
ICON_ARRORW_SMALL: require("@/static/publishBall/icon-arrow-small.svg"),
|
||||||
ICON_MAP_SEARCH: require('@/static/publishBall/icon-map-search.svg'),
|
ICON_MAP_SEARCH: require("@/static/publishBall/icon-map-search.svg"),
|
||||||
ICON_HEART_CIRCLE: require('@/static/publishBall/icon-heartcircle.png'),
|
ICON_HEART_CIRCLE: require("@/static/publishBall/icon-heartcircle.png"),
|
||||||
ICON_ADD: require('@/static/publishBall/icon-add.svg'),
|
ICON_ADD: require("@/static/publishBall/icon-add.svg"),
|
||||||
ICON_COPY: require('@/static/publishBall/icon-arrow-right.svg'),
|
ICON_COPY: require("@/static/publishBall/icon-arrow-right.svg"),
|
||||||
ICON_DELETE: require('@/static/publishBall/icon-delete.svg'),
|
ICON_DELETE: require("@/static/publishBall/icon-delete.svg"),
|
||||||
ICON_RIGHT_MAX: require('@/static/publishBall/icon-right-max.svg'),
|
ICON_RIGHT_MAX: require("@/static/publishBall/icon-right-max.svg"),
|
||||||
ICON_PLUS: require('@/static/publishBall/icon-plus.svg'),
|
ICON_PLUS: require("@/static/publishBall/icon-plus.svg"),
|
||||||
ICON_GROUP: require('@/static/publishBall/icon-group.svg'),
|
ICON_GROUP: require("@/static/publishBall/icon-group.svg"),
|
||||||
ICON_PERSON: require('@/static/publishBall/icon-person.svg'),
|
ICON_PERSON: require("@/static/publishBall/icon-person.svg"),
|
||||||
ICON_PUBLISH: require('@/static/publishBall/icon-publish.png'),
|
ICON_PUBLISH: require("@/static/publishBall/icon-publish.png"),
|
||||||
ICON_CIRCLE_UNSELECT: require('@/static/publishBall/icon-circle-unselect.svg'),
|
ICON_CIRCLE_UNSELECT: require("@/static/publishBall/icon-circle-unselect.svg"),
|
||||||
ICON_CIRCLE_SELECT: require('@/static/publishBall/icon-circle-select-ring.svg'),
|
ICON_CIRCLE_SELECT: require("@/static/publishBall/icon-circle-select-ring.svg"),
|
||||||
ICON_CIRCLE_SELECT_ARROW: require('@/static/publishBall/icon-circle-select-arrow.svg'),
|
ICON_CIRCLE_SELECT_ARROW: require("@/static/publishBall/icon-circle-select-arrow.svg"),
|
||||||
ICON_LOGO: require('@/static/logo.svg'),
|
ICON_LOGO: require("@/static/logo.svg"),
|
||||||
ICON_CHANGE: require('@/static/list/icon-change.svg'),
|
ICON_CHANGE: require("@/static/list/icon-change.svg"),
|
||||||
ICON_DETAIL_MAP: require('@/static/detail/icon-map.svg'),
|
ICON_DETAIL_MAP: require("@/static/detail/icon-map.svg"),
|
||||||
ICON_DETAIL_ARROW_RIGHT: require('@/static/detail/icon-arrow-right.svg'),
|
ICON_DETAIL_ARROW_RIGHT: require("@/static/detail/icon-arrow-right.svg"),
|
||||||
ICON_DETAIL_NOTICE: require('@/static/detail/icon-notice.svg'),
|
ICON_DETAIL_NOTICE: require("@/static/detail/icon-notice.svg"),
|
||||||
ICON_DETAIL_APPLICATION_ADD: require('@/static/detail/icon-application-add.svg'),
|
ICON_DETAIL_APPLICATION_ADD: require("@/static/detail/icon-application-add.svg"),
|
||||||
ICON_DETAIL_COMMENT: require('@/static/detail/icon-comment.svg'),
|
ICON_DETAIL_COMMENT: require("@/static/detail/icon-comment.svg"),
|
||||||
ICON_DETAIL_COMMENT_LIGHT: require('@/static/detail/icon-comment-light.svg'),
|
ICON_DETAIL_COMMENT_LIGHT: require("@/static/detail/icon-comment-light.svg"),
|
||||||
ICON_DETAIL_SHARE: require('@/static/detail/icon-share-light.svg'),
|
ICON_DETAIL_SHARE: require("@/static/detail/icon-share-light.svg"),
|
||||||
ICON_GUIDE_BAR_PUBLISH: require('@/static/common/guide-bar-publish.svg'),
|
ICON_GUIDE_BAR_PUBLISH: require("@/static/common/guide-bar-publish.svg"),
|
||||||
ICON_NAVIGATOR_BACK: require('@/static/common/navigator-back.svg'),
|
ICON_NAVIGATOR_BACK: require("@/static/common/navigator-back.svg"),
|
||||||
ICON_LIST_PLAYING_GAME: require('@/static/list/icon-paying-game.svg'),
|
ICON_LIST_PLAYING_GAME: require("@/static/list/icon-paying-game.svg"),
|
||||||
ICON_LIST_LOAD_ERROR: require('@/static/list/icon-load-error.svg'),
|
ICON_LIST_LOAD_ERROR: require("@/static/list/icon-load-error.svg"),
|
||||||
ICON_LIST_RELOAD: require('@/static/list/icon-reload.svg'),
|
ICON_LIST_RELOAD: require("@/static/list/icon-reload.svg"),
|
||||||
ICON_LIST_EMPTY: require('@/static/emptyStatus/publish-empty.png'),
|
ICON_LIST_EMPTY: require("@/static/emptyStatus/publish-empty.png"),
|
||||||
ICON_LIST_EMPTY_CARD: require('@/static/emptyStatus/publish-empty-card.png'),
|
ICON_LIST_EMPTY_CARD: `${OSS_BASE}/front/ball/images/publish-empty-card.svg`,
|
||||||
ICON_LIST_SEARCH_SEARCH: require('@/static/search/icon-search.svg'),
|
ICON_LIST_SEARCH_SEARCH: require("@/static/search/icon-search.svg"),
|
||||||
ICON_LIST_SEARCH_BACK: require('@/static/search/icon-back.svg'),
|
ICON_LIST_SEARCH_BACK: require("@/static/search/icon-back.svg"),
|
||||||
ICON_LIST_SEARCH_CLEAR: require('@/static/search/icon-search-clear.svg'),
|
ICON_LIST_SEARCH_CLEAR: require("@/static/search/icon-search-clear.svg"),
|
||||||
ICON_LIST_SEARCH_CLEAR_HISTORY: require('@/static/search/icon-clear-history.svg'),
|
ICON_LIST_SEARCH_CLEAR_HISTORY: require("@/static/search/icon-clear-history.svg"),
|
||||||
ICON_LIST_SEARCH_SUGGESTION: require('@/static/search/icon-search-suggestion.svg'),
|
ICON_LIST_SEARCH_SUGGESTION: require("@/static/search/icon-search-suggestion.svg"),
|
||||||
ICON_LIST_INPUT_LOGO: require('@/static/list/icon-input-logo.svg'),
|
ICON_LIST_INPUT_LOGO: require("@/static/list/icon-input-logo.svg"),
|
||||||
ICON_IMPORTANT_BTN: require('@/static/publishBall/icon-important-btn.svg'),
|
ICON_IMPORTANT_BTN: require("@/static/publishBall/icon-important-btn.svg"),
|
||||||
ICON_IMPORTANT_BLACK: require('@/static/publishBall/icon-important-black.svg'),
|
ICON_IMPORTANT_BLACK: require("@/static/publishBall/icon-important-black.svg"),
|
||||||
ICON_ARROW_RIGHT_WHITE: require('@/static/publishBall/icon-arrow-right-white.svg'),
|
ICON_ARROW_RIGHT_WHITE: require("@/static/publishBall/icon-arrow-right-white.svg"),
|
||||||
ICON_ARROW_RIGHT_BLACK: require('@/static/publishBall/icon-arrow-right-black.svg'),
|
ICON_ARROW_RIGHT_BLACK: require("@/static/publishBall/icon-arrow-right-black.svg"),
|
||||||
ICON_EXAMINATION: require('@/static/userInfo/examination.svg'),
|
ICON_EXAMINATION: require("@/static/userInfo/examination.svg"),
|
||||||
ICON_ARROW_GREEN: require('@/static/userInfo/arrow-green.svg'),
|
ICON_ARROW_GREEN: require("@/static/userInfo/arrow-green.svg"),
|
||||||
ICON_COPY: require('@/static/publishBall/icon-copy.svg'),
|
ICON_COPY: require("@/static/publishBall/icon-copy.svg"),
|
||||||
ICON_UPLOAD_IMG: require('@/static/publishBall/icon-upload-img.svg'),
|
ICON_UPLOAD_IMG: require("@/static/publishBall/icon-upload-img.svg"),
|
||||||
ICON_UPLOAD_SUCCESS: require('@/static/publishBall/icon-upload-success.svg'),
|
ICON_UPLOAD_SUCCESS: require("@/static/publishBall/icon-upload-success.svg"),
|
||||||
ICON_CLOSE: require('@/static/publishBall/icon-close.svg'),
|
ICON_CLOSE: require("@/static/publishBall/icon-close.svg"),
|
||||||
ICON_LIST_NTPR: require('@/static/list/ntpr.svg'),
|
ICON_LIST_NTPR: require("@/static/list/ntpr.svg"),
|
||||||
ICON_LIST_CHANGDA: require('@/static/list/icon-changda.svg'),
|
ICON_LIST_CHANGDA: require("@/static/list/icon-changda.svg"),
|
||||||
ICON_LIST_CHANGDA_QIuju: require('@/static/list/changdaqiuju.png'),
|
ICON_LIST_CHANGDA_QIuju: require("@/static/list/changdaqiuju.png"),
|
||||||
}
|
ICON_RELOCATE: require("@/static/list/icon-relocate.svg"),
|
||||||
|
ICON_GAME_PLAY: require("@/static/list/icon_game_type.svg"),
|
||||||
|
};
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import ListCard from "@/components/ListCard";
|
|||||||
import ListLoadError from "@/components/ListLoadError";
|
import ListLoadError from "@/components/ListLoadError";
|
||||||
import ListCardSkeleton from "@/components/ListCardSkeleton";
|
import ListCardSkeleton from "@/components/ListCardSkeleton";
|
||||||
import { useReachBottom } from "@tarojs/taro";
|
import { useReachBottom } from "@tarojs/taro";
|
||||||
|
import Taro from "@tarojs/taro";
|
||||||
import { useUserInfo, useUserActions, useLastTestResult } from "@/store/userStore";
|
import { useUserInfo, useUserActions, useLastTestResult } from "@/store/userStore";
|
||||||
import { NTRPTestEntryCard } from "@/components";
|
import { NTRPTestEntryCard } from "@/components";
|
||||||
import { EvaluateScene } from "@/store/evaluateStore";
|
import { EvaluateScene } from "@/store/evaluateStore";
|
||||||
import { waitForAuthInit } from "@/utils/authInit";
|
import { waitForAuthInit } from "@/utils/authInit";
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
import { useRef, useEffect, useState, useMemo } from "react";
|
import { useRef, useEffect, useState, useMemo } from "react";
|
||||||
|
import { useDictionaryStore } from "@/store/dictionaryStore";
|
||||||
|
|
||||||
const ListContainer = (props) => {
|
const ListContainer = (props) => {
|
||||||
const {
|
const {
|
||||||
@@ -27,6 +29,7 @@ const ListContainer = (props) => {
|
|||||||
collapse = false,
|
collapse = false,
|
||||||
defaultShowNum,
|
defaultShowNum,
|
||||||
evaluateFlag,
|
evaluateFlag,
|
||||||
|
enableHomeCards = false, // 仅首页需要 banner 和 NTRP 测评卡片
|
||||||
listLoadErrorWrapperHeight,
|
listLoadErrorWrapperHeight,
|
||||||
listLoadErrorWidth,
|
listLoadErrorWidth,
|
||||||
listLoadErrorHeight,
|
listLoadErrorHeight,
|
||||||
@@ -43,7 +46,7 @@ const ListContainer = (props) => {
|
|||||||
const { fetchUserInfo, fetchLastTestResult } = useUserActions();
|
const { fetchUserInfo, fetchLastTestResult } = useUserActions();
|
||||||
// 使用全局状态中的测试结果,避免重复调用接口
|
// 使用全局状态中的测试结果,避免重复调用接口
|
||||||
const lastTestResult = useLastTestResult();
|
const lastTestResult = useLastTestResult();
|
||||||
|
const { bannerListImage, bannerDetailImage, bannerListIndex = 0 } = useDictionaryStore((s) => s.bannerDict) || {};
|
||||||
useReachBottom(() => {
|
useReachBottom(() => {
|
||||||
// 加载更多方法
|
// 加载更多方法
|
||||||
if (loading) {
|
if (loading) {
|
||||||
@@ -92,10 +95,10 @@ const ListContainer = (props) => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 获取测试结果,判断最近一个月是否有测试记录
|
// 获取测试结果,判断最近一个月是否有测试记录(仅首页需要)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
if (!evaluateFlag) return;
|
if (!evaluateFlag || !enableHomeCards) return;
|
||||||
// 先等待静默登录完成
|
// 先等待静默登录完成
|
||||||
await waitForAuthInit();
|
await waitForAuthInit();
|
||||||
// 然后再获取用户信息
|
// 然后再获取用户信息
|
||||||
@@ -110,7 +113,7 @@ const ListContainer = (props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
init();
|
init();
|
||||||
}, [evaluateFlag, userInfo, lastTestResult, fetchLastTestResult]);
|
}, [evaluateFlag, enableHomeCards, userInfo, lastTestResult, fetchLastTestResult]);
|
||||||
|
|
||||||
// 从全局状态中获取测试状态
|
// 从全局状态中获取测试状态
|
||||||
const hasTestInLastMonth = lastTestResult?.has_test_in_last_month || false;
|
const hasTestInLastMonth = lastTestResult?.has_test_in_last_month || false;
|
||||||
@@ -129,39 +132,83 @@ const ListContainer = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 对于没有ntrp等级的用户每个月展示一次, 插在第二个位置后面
|
// showNumber 为 0 表示尚未同步,不参与截断;截断时只限制「数据条数」,插卡不占数据条数
|
||||||
function insertEvaluateCard(list) {
|
const shouldLimitByShowNumber = showNumber > 0;
|
||||||
if (!evaluateFlag)
|
|
||||||
return showNumber !== undefined ? list.slice(0, showNumber) : list;
|
|
||||||
if (!list || list.length === 0) {
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
// 如果最近一个月有测试记录,则不插入 card
|
|
||||||
if (hasTestInLastMonth) {
|
|
||||||
return showNumber !== undefined ? list.slice(0, showNumber) : list;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (list.length <= 2) {
|
// 插入 banner 卡片(在 bannerListIndex 位置插入,不替换数据)
|
||||||
return [...list, { type: "evaluateCard" }];
|
function insertBannerCard(list) {
|
||||||
|
if (!bannerListImage) return list;
|
||||||
|
if (!list || !Array.isArray(list)) {
|
||||||
|
list = [];
|
||||||
}
|
}
|
||||||
const [item1, item2, ...rest] = list;
|
const idx = Number(bannerListIndex);
|
||||||
return [
|
return [
|
||||||
item1,
|
...list.slice(0, idx),
|
||||||
item2,
|
{ type: "banner", banner_image_url: bannerListImage, banner_detail_url: bannerDetailImage },
|
||||||
{ type: "evaluateCard" },
|
...list.slice(idx),
|
||||||
...(showNumber !== undefined ? rest.slice(0, showNumber - 3) : rest),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 对于没有 ntrp 等级的用户每个月展示一次,插在第 2 条数据后面;插卡是插入不替换,保留全部 showNumber 条数据
|
||||||
|
function insertEvaluateCard(list) {
|
||||||
|
if (!list || !Array.isArray(list)) return insertBannerCard(list ?? []);
|
||||||
|
|
||||||
|
const limitedList = shouldLimitByShowNumber ? list.slice(0, showNumber) : list;
|
||||||
|
|
||||||
|
if (!evaluateFlag || hasTestInLastMonth) {
|
||||||
|
return insertBannerCard(limitedList);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (limitedList.length <= 2) {
|
||||||
|
return insertBannerCard([...limitedList, { type: "evaluateCard" }]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [item1, item2, ...rest] = limitedList;
|
||||||
|
const result = [item1, item2, { type: "evaluateCard" }, ...rest];
|
||||||
|
return insertBannerCard(result);
|
||||||
|
}
|
||||||
|
|
||||||
const memoizedList = useMemo(
|
const memoizedList = useMemo(
|
||||||
() => insertEvaluateCard(data),
|
() => (enableHomeCards ? insertEvaluateCard(data) : data),
|
||||||
[evaluateFlag, data, hasTestInLastMonth, showNumber]
|
[enableHomeCards, evaluateFlag, data, hasTestInLastMonth, showNumber, bannerListImage, bannerDetailImage, bannerListIndex]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 渲染 banner 卡片
|
||||||
|
const renderBanner = (item, index) => {
|
||||||
|
if (!item?.banner_image_url) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
key={item.id || `banner-${index}`}
|
||||||
|
onClick={() => {
|
||||||
|
const target = item.banner_detail_url;
|
||||||
|
if (target) {
|
||||||
|
(Taro as any).navigateTo({
|
||||||
|
url: `/other_pages/bannerDetail/index?img=${encodeURIComponent(target)}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
height: "100px",
|
||||||
|
overflow: "hidden",
|
||||||
|
borderRadius: "12px",
|
||||||
|
backgroundImage: `url(${item.banner_image_url})`,
|
||||||
|
backgroundSize: "cover",
|
||||||
|
backgroundPosition: "center",
|
||||||
|
backgroundRepeat: "no-repeat",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const showNoData = isShowNoData && !loading && memoizedList?.length === 0;
|
||||||
|
|
||||||
// 渲染列表
|
// 渲染列表
|
||||||
const renderList = () => {
|
const renderList = () => {
|
||||||
// 请求数据为空
|
// 请求数据为空
|
||||||
if (isShowNoData) {
|
if (showNoData) {
|
||||||
return (
|
return (
|
||||||
<ListLoadError
|
<ListLoadError
|
||||||
reload={reload}
|
reload={reload}
|
||||||
@@ -181,9 +228,12 @@ const ListContainer = (props) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{memoizedList.map((match, index) => {
|
{memoizedList.map((match, index) => {
|
||||||
if (match.type === "evaluateCard") {
|
if (enableHomeCards && match?.type === "banner") {
|
||||||
|
return renderBanner(match, index);
|
||||||
|
}
|
||||||
|
if (enableHomeCards && match?.type === "evaluateCard") {
|
||||||
return (
|
return (
|
||||||
<NTRPTestEntryCard key="evaluate" type={EvaluateScene.list} />
|
<NTRPTestEntryCard key={`evaluate-${index}`} type={EvaluateScene.list} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <ListCard key={match?.id || index} {...match} />;
|
return <ListCard key={match?.id || index} {...match} />;
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ function isFull(counts) {
|
|||||||
function matchNtrpRequestment(
|
function matchNtrpRequestment(
|
||||||
target?: string,
|
target?: string,
|
||||||
min?: string,
|
min?: string,
|
||||||
max?: string
|
max?: string,
|
||||||
): boolean {
|
): boolean {
|
||||||
// 目标值为空或 undefined
|
// 目标值为空或 undefined
|
||||||
if (!target?.trim()) return true;
|
if (!target?.trim()) return true;
|
||||||
@@ -73,6 +73,7 @@ export default function Participants(props) {
|
|||||||
}>({ show: () => {} });
|
}>({ show: () => {} });
|
||||||
const userInfo = useUserInfo();
|
const userInfo = useUserInfo();
|
||||||
const participants = detail.participants || [];
|
const participants = detail.participants || [];
|
||||||
|
const substitute_members = detail.substitute_members || [];
|
||||||
// const participants = Array(10)
|
// const participants = Array(10)
|
||||||
// .fill(0)
|
// .fill(0)
|
||||||
// .map((_, index) => ({
|
// .map((_, index) => ({
|
||||||
@@ -84,7 +85,7 @@ export default function Participants(props) {
|
|||||||
// id: 18,
|
// id: 18,
|
||||||
// nickname: "小猫开刀削面店往猫毛里面下面条",
|
// nickname: "小猫开刀削面店往猫毛里面下面条",
|
||||||
// avatar_url:
|
// avatar_url:
|
||||||
// "https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/d284060f-248b-4d58-a153-4d37c0ca77c8.jpg",
|
// "https://youchang2026.oss-cn-shanghai.aliyuncs.com/front/ball/images/d284060f-248b-4d58-a153-4d37c0ca77c8.jpg",
|
||||||
// phone: "18513125687",
|
// phone: "18513125687",
|
||||||
// ntrp_level: "1.5",
|
// ntrp_level: "1.5",
|
||||||
// },
|
// },
|
||||||
@@ -92,6 +93,8 @@ export default function Participants(props) {
|
|||||||
const {
|
const {
|
||||||
participant_count,
|
participant_count,
|
||||||
max_participants,
|
max_participants,
|
||||||
|
substitute_count,
|
||||||
|
max_substitute_players,
|
||||||
user_action_status = {},
|
user_action_status = {},
|
||||||
start_time,
|
start_time,
|
||||||
price,
|
price,
|
||||||
@@ -107,7 +110,7 @@ export default function Participants(props) {
|
|||||||
user_action_status;
|
user_action_status;
|
||||||
const showApplicationEntry =
|
const showApplicationEntry =
|
||||||
[can_pay, can_substitute, is_substituting, waiting_start].every(
|
[can_pay, can_substitute, is_substituting, waiting_start].every(
|
||||||
(item) => !item
|
(item) => !item,
|
||||||
) &&
|
) &&
|
||||||
can_join &&
|
can_join &&
|
||||||
dayjs(start_time).isAfter(dayjs());
|
dayjs(start_time).isAfter(dayjs());
|
||||||
@@ -135,7 +138,7 @@ export default function Participants(props) {
|
|||||||
|
|
||||||
Taro.navigateTo({
|
Taro.navigateTo({
|
||||||
url: `/login_pages/index/index?redirect=${encodeURIComponent(
|
url: `/login_pages/index/index?redirect=${encodeURIComponent(
|
||||||
fullPath
|
fullPath,
|
||||||
)}`,
|
)}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -150,7 +153,7 @@ export default function Participants(props) {
|
|||||||
const matchNtrpReq = matchNtrpRequestment(
|
const matchNtrpReq = matchNtrpRequestment(
|
||||||
userInfo?.ntrp_level,
|
userInfo?.ntrp_level,
|
||||||
skill_level_min,
|
skill_level_min,
|
||||||
skill_level_max
|
skill_level_max,
|
||||||
);
|
);
|
||||||
|
|
||||||
function handleSelfEvaluate() {
|
function handleSelfEvaluate() {
|
||||||
@@ -177,7 +180,7 @@ export default function Participants(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function generateTextAndAction(
|
function generateTextAndAction(
|
||||||
user_action_status: null | { [key: string]: boolean }
|
user_action_status: null | { [key: string]: boolean },
|
||||||
):
|
):
|
||||||
| undefined
|
| undefined
|
||||||
| { text: string | React.FC; action?: () => void; available?: boolean } {
|
| { text: string | React.FC; action?: () => void; available?: boolean } {
|
||||||
@@ -256,7 +259,7 @@ export default function Participants(props) {
|
|||||||
const res = await OrderService.getUnpaidOrder(id);
|
const res = await OrderService.getUnpaidOrder(id);
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
navto(
|
navto(
|
||||||
`/order_pages/orderDetail/index?id=${res.data.order_info.order_id}`
|
`/order_pages/orderDetail/index?id=${res.data.order_info.order_id}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@@ -293,6 +296,14 @@ export default function Participants(props) {
|
|||||||
const { action = () => {} } = generateTextAndAction(user_action_status)!;
|
const { action = () => {} } = generateTextAndAction(user_action_status)!;
|
||||||
|
|
||||||
const leftCount = max_participants - participant_count;
|
const leftCount = max_participants - participant_count;
|
||||||
|
const leftSubstituteCount =
|
||||||
|
(max_substitute_players || 0) - (substitute_count || 0);
|
||||||
|
const showSubstituteApplicationEntry =
|
||||||
|
[can_pay, can_join, is_substituting, waiting_start].every(
|
||||||
|
(item) => !item,
|
||||||
|
) &&
|
||||||
|
can_substitute &&
|
||||||
|
dayjs(start_time).isAfter(dayjs());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -326,7 +337,7 @@ export default function Participants(props) {
|
|||||||
refresherBackground="#FAFAFA"
|
refresherBackground="#FAFAFA"
|
||||||
className={classnames(
|
className={classnames(
|
||||||
styles["participants-list-scroll"],
|
styles["participants-list-scroll"],
|
||||||
showApplicationEntry ? styles.withApplication : ""
|
showApplicationEntry ? styles.withApplication : "",
|
||||||
)}
|
)}
|
||||||
scrollX
|
scrollX
|
||||||
>
|
>
|
||||||
@@ -367,14 +378,14 @@ export default function Participants(props) {
|
|||||||
src={avatar_url}
|
src={avatar_url}
|
||||||
onClick={handleViewUserInfo.bind(
|
onClick={handleViewUserInfo.bind(
|
||||||
null,
|
null,
|
||||||
participant_user_id
|
participant_user_id,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Text className={styles["participants-list-item-name"]}>
|
<Text className={styles["participants-list-item-name"]}>
|
||||||
{nickname || "未知"}
|
{nickname || "未知"}
|
||||||
</Text>
|
</Text>
|
||||||
<Text className={styles["participants-list-item-level"]}>
|
<Text className={styles["participants-list-item-level"]}>
|
||||||
{displayNtrp}
|
NTRP {displayNtrp}
|
||||||
</Text>
|
</Text>
|
||||||
<Text className={styles["participants-list-item-role"]}>
|
<Text className={styles["participants-list-item-role"]}>
|
||||||
{role}
|
{role}
|
||||||
@@ -389,6 +400,108 @@ export default function Participants(props) {
|
|||||||
""
|
""
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
{/* 候补区域 */}
|
||||||
|
{max_substitute_players > 0 &&
|
||||||
|
(substitute_count > 0 || showSubstituteApplicationEntry) && (
|
||||||
|
<View className={styles["detail-page-content-participants"]}>
|
||||||
|
<View className={styles["participants-title"]}>
|
||||||
|
<Text>候补</Text>
|
||||||
|
<Text>·</Text>
|
||||||
|
<Text>
|
||||||
|
{leftSubstituteCount > 0
|
||||||
|
? `剩余空位 ${leftSubstituteCount}`
|
||||||
|
: "已满员"}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View className={styles["participants-list"]}>
|
||||||
|
{/* 候补申请入口 */}
|
||||||
|
{showSubstituteApplicationEntry && (
|
||||||
|
<View
|
||||||
|
className={styles["participants-list-application"]}
|
||||||
|
onClick={() => {
|
||||||
|
action?.();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
className={styles["participants-list-application-icon"]}
|
||||||
|
src={img.ICON_DETAIL_APPLICATION_ADD}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
className={styles["participants-list-application-text"]}
|
||||||
|
>
|
||||||
|
申请候补
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
{/* 候补成员列表 */}
|
||||||
|
<ScrollView
|
||||||
|
refresherBackground="#FAFAFA"
|
||||||
|
className={classnames(
|
||||||
|
styles["participants-list-scroll"],
|
||||||
|
showSubstituteApplicationEntry ? styles.withApplication : "",
|
||||||
|
)}
|
||||||
|
scrollX
|
||||||
|
>
|
||||||
|
<View
|
||||||
|
className={styles["participants-list-scroll-content"]}
|
||||||
|
style={{
|
||||||
|
width: `${
|
||||||
|
Math.max(substitute_members.length, 1) * 103 +
|
||||||
|
(Math.max(substitute_members.length, 1) - 1) * 8
|
||||||
|
}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{substitute_members.map((substitute) => {
|
||||||
|
const {
|
||||||
|
is_organizer,
|
||||||
|
user: {
|
||||||
|
avatar_url,
|
||||||
|
nickname,
|
||||||
|
level,
|
||||||
|
ntrp_level,
|
||||||
|
id: substitute_user_id,
|
||||||
|
},
|
||||||
|
} = substitute;
|
||||||
|
const role = is_organizer ? "组织者" : "参与者";
|
||||||
|
// 优先使用 ntrp_level,如果没有则使用 level
|
||||||
|
const ntrpValue = ntrp_level || level;
|
||||||
|
// 格式化显示 NTRP,如果没有值则显示"初学者"
|
||||||
|
const displayNtrp = ntrpValue
|
||||||
|
? formatNtrpDisplay(ntrpValue)
|
||||||
|
: "初学者";
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
key={substitute.id}
|
||||||
|
className={styles["participants-list-item"]}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
className={styles["participants-list-item-avatar"]}
|
||||||
|
mode="aspectFill"
|
||||||
|
src={avatar_url}
|
||||||
|
onClick={handleViewUserInfo.bind(
|
||||||
|
null,
|
||||||
|
substitute_user_id,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Text className={styles["participants-list-item-name"]}>
|
||||||
|
{nickname || "未知"}
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
className={styles["participants-list-item-level"]}
|
||||||
|
>
|
||||||
|
{displayNtrp}
|
||||||
|
</Text>
|
||||||
|
<Text className={styles["participants-list-item-role"]}>
|
||||||
|
{role}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
<NTRPEvaluatePopup type={EvaluateScene.detail} ref={ntrpRef} showGuide />
|
<NTRPEvaluatePopup type={EvaluateScene.detail} ref={ntrpRef} showGuide />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,32 @@
|
|||||||
.shareContainer {
|
.shareContainer {
|
||||||
|
.opacityContainer {
|
||||||
|
height: 140px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.shareImageContainer {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 50%;
|
||||||
|
width: 220px;
|
||||||
|
height: 180px;
|
||||||
|
padding: 6px;
|
||||||
|
background-color: #fafafa;
|
||||||
|
border-radius: 16px;
|
||||||
|
transform: rotateZ(-5deg) translateX(-50%);
|
||||||
|
box-shadow: 0 3px 32px 0 rgba(0, 0, 0, 0.16);
|
||||||
|
|
||||||
|
.shareImage {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.contentContainer {
|
||||||
|
background-color: #fafafa;
|
||||||
|
border-top-left-radius: 16px;
|
||||||
|
border-top-right-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
padding: 20px 20px 16px;
|
padding: 20px 20px 16px;
|
||||||
color: #000;
|
color: #000;
|
||||||
@@ -12,6 +40,18 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&.publishTitle {
|
||||||
|
height: 100px;
|
||||||
|
|
||||||
|
.publishText {
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeIconWrap {
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.publishText {
|
.publishText {
|
||||||
color: #2a4d44;
|
color: #2a4d44;
|
||||||
font-family: "Noto Sans SC";
|
font-family: "Noto Sans SC";
|
||||||
@@ -62,6 +102,21 @@
|
|||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
padding-bottom: 60px;
|
padding-bottom: 60px;
|
||||||
|
|
||||||
|
.customBtnWrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
opacity: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customButton,
|
||||||
.button {
|
.button {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -76,7 +131,7 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
|
|
||||||
background-color: #fff;
|
// background-color: #fff;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import CrossIcon from "@/static/detail/cross.svg";
|
|||||||
import { genNTRPRequirementText, navto } from "@/utils/helper";
|
import { genNTRPRequirementText, navto } from "@/utils/helper";
|
||||||
import { waitForAuthInit } from "@/utils/authInit";
|
import { waitForAuthInit } from "@/utils/authInit";
|
||||||
import { useUserActions } from "@/store/userStore";
|
import { useUserActions } from "@/store/userStore";
|
||||||
import { OSS_BASE_URL } from "@/config/api";
|
import { OSS_BASE } from "@/config/api";
|
||||||
import { generatePosterImage, base64ToTempFilePath, delay } from "@/utils";
|
import { generatePosterImage, base64ToTempFilePath, delay } from "@/utils";
|
||||||
import { DayOfWeekMap } from "../../config";
|
import { DayOfWeekMap } from "../../config";
|
||||||
import styles from "./index.module.scss";
|
import styles from "./index.module.scss";
|
||||||
@@ -25,8 +25,10 @@ dayjs.locale("zh-cn");
|
|||||||
// 分享弹窗
|
// 分享弹窗
|
||||||
export default forwardRef(({ id, from, detail, userInfo }, ref) => {
|
export default forwardRef(({ id, from, detail, userInfo }, ref) => {
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [publishFlag, setPublishFlag] = useState(false);
|
const [shareImageUrl, setShareImageUrl] = useState("");
|
||||||
const { fetchUserInfo } = useUserActions();
|
const { fetchUserInfo } = useUserActions();
|
||||||
|
|
||||||
|
const publishFlag = from === "publish";
|
||||||
// const posterRef = useRef();
|
// const posterRef = useRef();
|
||||||
const { max_participants, participant_count } = detail || {};
|
const { max_participants, participant_count } = detail || {};
|
||||||
|
|
||||||
@@ -56,13 +58,21 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
show: (publish_flag = false) => {
|
show: async () => {
|
||||||
setPublishFlag(publish_flag);
|
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
useShareAppMessage(async (res) => {
|
useEffect(() => {
|
||||||
|
if (from === "publish") {
|
||||||
|
generateShareImageUrl().then((url) => {
|
||||||
|
setShareImageUrl(url);
|
||||||
|
setVisible(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [from]);
|
||||||
|
|
||||||
|
async function generateShareImageUrl() {
|
||||||
const {
|
const {
|
||||||
play_type,
|
play_type,
|
||||||
skill_level_max,
|
skill_level_max,
|
||||||
@@ -76,24 +86,30 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
|
|||||||
const endTime = dayjs(end_time);
|
const endTime = dayjs(end_time);
|
||||||
const dayofWeek = DayOfWeekMap.get(startTime.day());
|
const dayofWeek = DayOfWeekMap.get(startTime.day());
|
||||||
const gameLength = `${endTime.diff(startTime, "hour")}小时`;
|
const gameLength = `${endTime.diff(startTime, "hour")}小时`;
|
||||||
await changeMessageType();
|
console.log(userInfo, "userInfo");
|
||||||
const url = await generateShareImage({
|
const url = await generateShareImage({
|
||||||
userAvatar: userInfo.avatar_url,
|
userAvatar: userInfo.avatar_url,
|
||||||
userNickname: userInfo.nickname,
|
userNickname: userInfo.nickname,
|
||||||
gameType: play_type,
|
gameType: play_type,
|
||||||
skillLevel: `NTRP ${genNTRPRequirementText(
|
skillLevel: `NTRP ${genNTRPRequirementText(
|
||||||
skill_level_min,
|
skill_level_min,
|
||||||
skill_level_max
|
skill_level_max,
|
||||||
)}`,
|
)}`,
|
||||||
gameDate: `${startTime.format("M月D日")} (${dayofWeek})`,
|
gameDate: `${startTime.format("M月D日")} (${dayofWeek})`,
|
||||||
gameTime: `${startTime.format("ah")}点 ${gameLength}`,
|
gameTime: `${startTime.format("ah")}点 ${gameLength}`,
|
||||||
venueName: location_name,
|
venueName: location_name,
|
||||||
venueImages: image_list ? image_list : [],
|
venueImages: image_list ? image_list : [],
|
||||||
});
|
});
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
useShareAppMessage(async (res) => {
|
||||||
|
await changeMessageType();
|
||||||
|
const url = await generateShareImageUrl();
|
||||||
// console.log(res, "res");
|
// console.log(res, "res");
|
||||||
return {
|
return {
|
||||||
title: detail.title,
|
title: detail.title,
|
||||||
imageUrl: url || "https://img.yzcdn.cn/vant/cat.jpeg",
|
imageUrl: url,
|
||||||
path: `/game_pages/detail/index?id=${id}&from=share`,
|
path: `/game_pages/detail/index?id=${id}&from=share`,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -118,22 +134,25 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
|
|||||||
const endTime = dayjs(end_time);
|
const endTime = dayjs(end_time);
|
||||||
const dayofWeek = DayOfWeekMap.get(startTime.day());
|
const dayofWeek = DayOfWeekMap.get(startTime.day());
|
||||||
const gameLength = `${endTime.diff(startTime, "hour")}小时`;
|
const gameLength = `${endTime.diff(startTime, "hour")}小时`;
|
||||||
Taro.showLoading({ title: "生成中..." });
|
// Taro.showLoading({ title: "生成中..." });
|
||||||
const qrCodeUrlRes = await DetailService.getQrCodeUrl({
|
const qrCodeUrlRes = await DetailService.getQrCodeUrl({
|
||||||
page: "game_pages/detail/index",
|
page: "game_pages/detail/index",
|
||||||
scene: `id=${id}`,
|
scene: `id=${id}`,
|
||||||
});
|
});
|
||||||
const qrCodeUrl = await base64ToTempFilePath(
|
// const qrCodeUrl = await base64ToTempFilePath(
|
||||||
qrCodeUrlRes.data.qr_code_base64
|
// qrCodeUrlRes.data.qr_code_base64
|
||||||
);
|
// );
|
||||||
|
const qrCodeUrl = qrCodeUrlRes.data.ossPath;
|
||||||
await delay(100);
|
await delay(100);
|
||||||
|
// Taro.showLoading({ title: "生成中..." });
|
||||||
|
console.log('url', qrCodeUrl)
|
||||||
const url = await generatePosterImage({
|
const url = await generatePosterImage({
|
||||||
playType: play_type,
|
playType: play_type,
|
||||||
ntrp: `NTRP ${genNTRPRequirementText(skill_level_min, skill_level_max)}`,
|
ntrp: `NTRP ${genNTRPRequirementText(skill_level_min, skill_level_max)}`,
|
||||||
mainCoursal:
|
mainCoursal:
|
||||||
image_list[0] && image_list[0].startsWith("http")
|
image_list[0] && image_list[0].startsWith("http")
|
||||||
? image_list[0]
|
? image_list[0]
|
||||||
: `${OSS_BASE_URL}/images/0621b8cf-f7d6-43ad-b852-7dc39f29a782.png`,
|
: `${OSS_BASE}/front/ball/images/0621b8cf-f7d6-43ad-b852-7dc39f29a782.png`,
|
||||||
nickname,
|
nickname,
|
||||||
avatarUrl: avatar_url,
|
avatarUrl: avatar_url,
|
||||||
title,
|
title,
|
||||||
@@ -142,7 +161,9 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
|
|||||||
time: `${startTime.format("ah")}点 ${gameLength}`,
|
time: `${startTime.format("ah")}点 ${gameLength}`,
|
||||||
qrCodeUrl,
|
qrCodeUrl,
|
||||||
});
|
});
|
||||||
Taro.hideLoading();
|
|
||||||
|
console.log('urlend', url)
|
||||||
|
// Taro.hideLoading();
|
||||||
Taro.showShareImageMenu({
|
Taro.showShareImageMenu({
|
||||||
path: url,
|
path: url,
|
||||||
});
|
});
|
||||||
@@ -154,9 +175,20 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
|
|||||||
setVisible(false);
|
setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleCopyLink() {
|
||||||
|
const linkUrlRes = await DetailService.getLinkUrl({
|
||||||
|
path: "game_pages/detail/index",
|
||||||
|
query: `id=${id}`,
|
||||||
|
});
|
||||||
|
await Taro.setClipboardData({
|
||||||
|
data: linkUrlRes.data.url_link,
|
||||||
|
});
|
||||||
|
Taro.showToast({ title: "链接已复制到剪贴板", icon: "success" });
|
||||||
|
setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
function onClose() {
|
function onClose() {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
setPublishFlag(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -168,11 +200,31 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
|
|||||||
showHeader={false}
|
showHeader={false}
|
||||||
hideFooter
|
hideFooter
|
||||||
enableDragToClose={false}
|
enableDragToClose={false}
|
||||||
style={{ minHeight: "100px" }}
|
style={{ minHeight: "100px", background: "unset" }}
|
||||||
zIndex={1000}
|
zIndex={1000}
|
||||||
>
|
>
|
||||||
<View className={styles.shareContainer}>
|
<View className={styles.shareContainer}>
|
||||||
<View catchMove className={styles.title}>
|
{publishFlag && (
|
||||||
|
<>
|
||||||
|
<View className={styles.opacityContainer} />
|
||||||
|
<View className={styles.shareImageContainer}>
|
||||||
|
<Image className={styles.shareImage} src={shareImageUrl} />
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<View
|
||||||
|
className={styles.contentContainer}
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url(${OSS_BASE}/front/ball/images/215f1ce1-be52-4a92-8250-5a4a69e7f2b3.png)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View
|
||||||
|
catchMove
|
||||||
|
className={classnames(
|
||||||
|
styles.title,
|
||||||
|
publishFlag ? styles.publishTitle : "",
|
||||||
|
)}
|
||||||
|
>
|
||||||
{publishFlag ? (
|
{publishFlag ? (
|
||||||
<Text className={styles.publishText}>球局发布成功 🎉</Text>
|
<Text className={styles.publishText}>球局发布成功 🎉</Text>
|
||||||
) : (
|
) : (
|
||||||
@@ -195,24 +247,49 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
<View className={styles.shareItems}>
|
<View className={styles.shareItems}>
|
||||||
|
<View className={styles.customBtnWrapper}>
|
||||||
<Button className={styles.button} openType="share">
|
<Button className={styles.button} openType="share">
|
||||||
<View className={classnames(styles.icon, styles.wechatIcon)}>
|
<View className={classnames(styles.icon, styles.wechatIcon)}>
|
||||||
<Image className={styles.wechat} src={WechatLogo} />
|
<Image className={styles.wechat} src={WechatLogo} />
|
||||||
</View>
|
</View>
|
||||||
<Text>微信好友</Text>
|
<Text>微信好友</Text>
|
||||||
</Button>
|
</Button>
|
||||||
|
<View className={styles.customButton}>
|
||||||
|
<View className={classnames(styles.icon, styles.wechatIcon)}>
|
||||||
|
<Image className={styles.wechat} src={WechatLogo} />
|
||||||
|
</View>
|
||||||
|
<Text>微信好友</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View className={styles.customBtnWrapper}>
|
||||||
<Button className={styles.button} onClick={handlePost}>
|
<Button className={styles.button} onClick={handlePost}>
|
||||||
<View className={styles.icon}>
|
<View className={styles.icon}>
|
||||||
<Image className={styles.download} src={DownloadIcon} />
|
<Image className={styles.download} src={DownloadIcon} />
|
||||||
</View>
|
</View>
|
||||||
<Text>生成分享图</Text>
|
<Text>生成分享图</Text>
|
||||||
</Button>
|
</Button>
|
||||||
<Button className={styles.button}>
|
<View className={styles.customButton}>
|
||||||
|
<View className={styles.icon}>
|
||||||
|
<Image className={styles.download} src={DownloadIcon} />
|
||||||
|
</View>
|
||||||
|
<Text>生成分享图</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View className={styles.customBtnWrapper}>
|
||||||
|
<Button className={styles.button} onClick={handleCopyLink}>
|
||||||
<View className={styles.icon}>
|
<View className={styles.icon}>
|
||||||
<Image className={styles.linkIcon} src={LinkIcon} />
|
<Image className={styles.linkIcon} src={LinkIcon} />
|
||||||
</View>
|
</View>
|
||||||
<Text>复制链接</Text>
|
<Text>复制链接</Text>
|
||||||
</Button>
|
</Button>
|
||||||
|
<View className={styles.customButton}>
|
||||||
|
<View className={styles.icon}>
|
||||||
|
<Image className={styles.linkIcon} src={LinkIcon} />
|
||||||
|
</View>
|
||||||
|
<Text>复制链接</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</CommonPopup>
|
</CommonPopup>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react";
|
|||||||
import Taro from "@tarojs/taro";
|
import Taro from "@tarojs/taro";
|
||||||
import classnames from "classnames";
|
import classnames from "classnames";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import { debounce } from "@tarojs/runtime";
|
||||||
import { Text, View, Image } from "@tarojs/components";
|
import { Text, View, Image } from "@tarojs/components";
|
||||||
import OrderService from "@/services/orderService";
|
import OrderService from "@/services/orderService";
|
||||||
import { EvaluateCallback, EvaluateScene } from "@/store/evaluateStore";
|
import { EvaluateCallback, EvaluateScene } from "@/store/evaluateStore";
|
||||||
@@ -348,6 +349,8 @@ export default function StickyButton(props) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const debounceAction = debounce(action, 300);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<View className={styles["sticky-bottom-bar"]}>
|
<View className={styles["sticky-bottom-bar"]}>
|
||||||
@@ -390,7 +393,7 @@ export default function StickyButton(props) {
|
|||||||
<View
|
<View
|
||||||
style={is_organizer ? {} : { margin: "auto" }}
|
style={is_organizer ? {} : { margin: "auto" }}
|
||||||
className={styles["sticky-bottom-bar-join-game"]}
|
className={styles["sticky-bottom-bar-join-game"]}
|
||||||
onClick={action}
|
onClick={debounceAction}
|
||||||
>
|
>
|
||||||
<ActionText />
|
<ActionText />
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -81,9 +81,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.venue-screenshot-scroll-view {
|
.venue-screenshot-scroll-view {
|
||||||
max-height: calc(100vh - 260px);
|
max-height: calc(100dvh - 260px);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding-bottom: 40px;
|
padding-bottom: 40px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
.venue-screenshot-image-list {
|
.venue-screenshot-image-list {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -92,9 +93,11 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
gap: 10px 10px;
|
gap: 10px 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
.venue-screenshot-image-item {
|
.venue-screenshot-image-item {
|
||||||
aspect-ratio: 1/1;
|
aspect-ratio: 1/1;
|
||||||
|
min-height: 100%;
|
||||||
border-radius: 9px;
|
border-radius: 9px;
|
||||||
border: 1px solid rgba(0, 0, 0, 0.12);
|
border: 1px solid rgba(0, 0, 0, 0.12);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ export default function VenueInfo(props) {
|
|||||||
function previewImage(current_url) {
|
function previewImage(current_url) {
|
||||||
Taro.previewImage({
|
Taro.previewImage({
|
||||||
current: current_url,
|
current: current_url,
|
||||||
urls:
|
urls: venue_image_list || [],
|
||||||
venue_image_list?.length > 0 ? venue_image_list.map((c) => c.url) : [],
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
@@ -83,16 +82,17 @@ export default function VenueInfo(props) {
|
|||||||
<ScrollView scrollY className={styles["venue-screenshot-scroll-view"]}>
|
<ScrollView scrollY className={styles["venue-screenshot-scroll-view"]}>
|
||||||
<View className={styles["venue-screenshot-image-list"]}>
|
<View className={styles["venue-screenshot-image-list"]}>
|
||||||
{venue_image_list?.length > 0 &&
|
{venue_image_list?.length > 0 &&
|
||||||
venue_image_list.map((item) => {
|
venue_image_list.map((url, index) => {
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
className={styles["venue-screenshot-image-item"]}
|
className={styles["venue-screenshot-image-item"]}
|
||||||
onClick={previewImage.bind(null, item.url)}
|
onClick={previewImage.bind(null, url)}
|
||||||
|
key={index}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
className={styles["venue-screenshot-image-item-image"]}
|
className={styles["venue-screenshot-image-item-image"]}
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
src={item.url}
|
src={url}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import SupplementalNotes from "./components/SupplementalNotes";
|
|||||||
import OrganizerInfo from "./components/OrganizerInfo";
|
import OrganizerInfo from "./components/OrganizerInfo";
|
||||||
import SharePopup from "./components/SharePopup";
|
import SharePopup from "./components/SharePopup";
|
||||||
import { navto, toast } from "@/utils/helper";
|
import { navto, toast } from "@/utils/helper";
|
||||||
|
import { delay } from "@/utils";
|
||||||
import ArrowLeft from "@/static/detail/icon-arrow-left.svg";
|
import ArrowLeft from "@/static/detail/icon-arrow-left.svg";
|
||||||
// import Logo from "@/static/detail/icon-logo-go.svg";
|
// import Logo from "@/static/detail/icon-logo-go.svg";
|
||||||
import styles from "./index.module.scss";
|
import styles from "./index.module.scss";
|
||||||
@@ -79,10 +80,11 @@ function Index() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 位置更新后,重新获取详情页数据(因为距离等信息可能发生变化)
|
// 位置更新后,重新获取详情页数据(因为距离等信息可能发生变化)
|
||||||
await fetchDetail();
|
// 注意:这里不调用 fetchDetail,避免与 useDidShow 中的调用重复
|
||||||
if (from === "publish") {
|
// 如果需要更新距离信息,可以在 fetchDetail 成功后根据当前位置重新计算
|
||||||
handleShare(true);
|
// if (from === "publish") {
|
||||||
}
|
// handleShare(true);
|
||||||
|
// }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("用户位置更新失败", error);
|
console.error("用户位置更新失败", error);
|
||||||
}
|
}
|
||||||
@@ -118,8 +120,12 @@ function Index() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleShare(flag = false) {
|
function handleShare() {
|
||||||
sharePopupRef.current.show(flag);
|
if (!detail.id) {
|
||||||
|
toast("球局未加载完成,请稍后再试");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sharePopupRef.current.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleJoinGame = async () => {
|
const handleJoinGame = async () => {
|
||||||
@@ -158,9 +164,9 @@ function Index() {
|
|||||||
|
|
||||||
function handleViewUserInfo(userId) {
|
function handleViewUserInfo(userId) {
|
||||||
navto(
|
navto(
|
||||||
isMyOwn
|
userId === myInfo.id
|
||||||
? "/user_pages/myself/index"
|
? "/user_pages/myself/index"
|
||||||
: `/user_pages/other/index?userid=${userId}`
|
: `/user_pages/other/index?userid=${userId}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +200,7 @@ function Index() {
|
|||||||
<View
|
<View
|
||||||
className={classnames(
|
className={classnames(
|
||||||
styles["custom-navbar"],
|
styles["custom-navbar"],
|
||||||
glass ? styles.glass : ""
|
glass ? styles.glass : "",
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
height: `${totalHeight}px`,
|
height: `${totalHeight}px`,
|
||||||
@@ -285,13 +291,15 @@ function Index() {
|
|||||||
currentUserInfo={myInfo}
|
currentUserInfo={myInfo}
|
||||||
/>
|
/>
|
||||||
{/* share popup */}
|
{/* share popup */}
|
||||||
|
{detail.id && myInfo.id && (
|
||||||
<SharePopup
|
<SharePopup
|
||||||
ref={sharePopupRef}
|
ref={sharePopupRef}
|
||||||
id={id as string}
|
id={id as string}
|
||||||
from={from as string}
|
from={from as string}
|
||||||
detail={detail}
|
detail={detail}
|
||||||
userInfo={userInfo}
|
userInfo={myInfo}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ const ListSearch = () => {
|
|||||||
<View className="topSearch">
|
<View className="topSearch">
|
||||||
<Image className="searchIcon" src={img.ICON_LIST_SEARCH_SEARCH} />
|
<Image className="searchIcon" src={img.ICON_LIST_SEARCH_SEARCH} />
|
||||||
<Input
|
<Input
|
||||||
placeholder="搜索上海的球局和场地"
|
placeholder="搜索球局和场地"
|
||||||
value={searchValue}
|
value={searchValue}
|
||||||
defaultValue={searchValue}
|
defaultValue={searchValue}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Taro, { useRouter } from "@tarojs/taro";
|
|||||||
import classnames from "classnames";
|
import classnames from "classnames";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import "dayjs/locale/zh-cn";
|
import "dayjs/locale/zh-cn";
|
||||||
import { generatePosterImage, base64ToTempFilePath, delay } from "@/utils";
|
import { generatePosterImage, delay } from "@/utils";
|
||||||
import { withAuth } from "@/components";
|
import { withAuth } from "@/components";
|
||||||
import GeneralNavbar from "@/components/GeneralNavbar";
|
import GeneralNavbar from "@/components/GeneralNavbar";
|
||||||
import DetailService from "@/services/detailService";
|
import DetailService from "@/services/detailService";
|
||||||
@@ -16,7 +16,7 @@ import { useUserActions } from "@/store/userStore";
|
|||||||
import { DayOfWeekMap } from "../detail/config";
|
import { DayOfWeekMap } from "../detail/config";
|
||||||
import { genNTRPRequirementText } from "@/utils/helper";
|
import { genNTRPRequirementText } from "@/utils/helper";
|
||||||
import { waitForAuthInit } from "@/utils/authInit";
|
import { waitForAuthInit } from "@/utils/authInit";
|
||||||
import { OSS_BASE_URL } from "@/config/api";
|
import { OSS_BASE } from "@/config/api";
|
||||||
import styles from "./index.module.scss";
|
import styles from "./index.module.scss";
|
||||||
|
|
||||||
dayjs.locale("zh-cn");
|
dayjs.locale("zh-cn");
|
||||||
@@ -59,9 +59,11 @@ function SharePoster(props) {
|
|||||||
page: "game_pages/detail/index",
|
page: "game_pages/detail/index",
|
||||||
scene: `id=${id}`,
|
scene: `id=${id}`,
|
||||||
});
|
});
|
||||||
const qrCodeUrl = await base64ToTempFilePath(
|
const qrCodeUrl = qrCodeUrlRes.data.ossPath;
|
||||||
qrCodeUrlRes.data.qr_code_base64
|
// const qrCodeUrl = await base64ToTempFilePath(
|
||||||
);
|
// qrCodeUrlRes.data.qr_code_base64
|
||||||
|
// );
|
||||||
|
// debugger
|
||||||
await delay(100);
|
await delay(100);
|
||||||
const url = await generatePosterImage({
|
const url = await generatePosterImage({
|
||||||
playType: play_type,
|
playType: play_type,
|
||||||
@@ -69,7 +71,7 @@ function SharePoster(props) {
|
|||||||
mainCoursal:
|
mainCoursal:
|
||||||
image_list[0] && image_list[0].startsWith("http")
|
image_list[0] && image_list[0].startsWith("http")
|
||||||
? image_list[0]
|
? image_list[0]
|
||||||
: `${OSS_BASE_URL}/images/0621b8cf-f7d6-43ad-b852-7dc39f29a782.png`,
|
: `${OSS_BASE}/front/ball/images/0621b8cf-f7d6-43ad-b852-7dc39f29a782.png`,
|
||||||
nickname,
|
nickname,
|
||||||
avatarUrl: avatar_url,
|
avatarUrl: avatar_url,
|
||||||
title,
|
title,
|
||||||
|
|||||||
@@ -9,6 +9,23 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.link_button
|
||||||
|
{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12px;
|
||||||
|
height: 52px;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: none;
|
||||||
|
position: relative;
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
.button_text {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 背景图片和渐变覆盖层
|
// 背景图片和渐变覆盖层
|
||||||
.background_image {
|
.background_image {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { View, Text, Button, Image } from "@tarojs/components";
|
import { View, Text, Button, Image } from "@tarojs/components";
|
||||||
import Taro, { useRouter } from "@tarojs/taro";
|
import Taro, { useRouter } from "@tarojs/taro";
|
||||||
|
import { GeneralNavbar } from "@/components";
|
||||||
import {
|
import {
|
||||||
wechat_auth_login,
|
wechat_auth_login,
|
||||||
save_login_state,
|
save_login_state,
|
||||||
@@ -155,6 +156,11 @@ const LoginPage: React.FC = () => {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 返回首页
|
||||||
|
const handle_return_home = () => {
|
||||||
|
Taro.navigateTo({ url: "/main_pages/index" });
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className="login_page">
|
<View className="login_page">
|
||||||
<View className="background_image">
|
<View className="background_image">
|
||||||
@@ -166,6 +172,8 @@ const LoginPage: React.FC = () => {
|
|||||||
<View className="bg_overlay"></View>
|
<View className="bg_overlay"></View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
<GeneralNavbar title="" showBack={true} showAvatar={false} onBack={handle_return_home} />
|
||||||
|
|
||||||
{/* 主要内容 */}
|
{/* 主要内容 */}
|
||||||
<View className="login_main_content">
|
<View className="login_main_content">
|
||||||
{/* 品牌区域 */}
|
{/* 品牌区域 */}
|
||||||
@@ -193,7 +201,7 @@ const LoginPage: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<Text className="button_text">
|
<Text className="button_text">
|
||||||
{is_loading ? "登录中..." : "微信授权登录"}
|
{is_loading ? "登录中..." : "一键登录"}
|
||||||
</Text>
|
</Text>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
@@ -208,9 +216,13 @@ const LoginPage: React.FC = () => {
|
|||||||
src={require("@/static/login/phone_icon.svg")}
|
src={require("@/static/login/phone_icon.svg")}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<Text className="button_text">手机号验证码登录</Text>
|
<Text className="button_text">手机号快捷登录</Text>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
{/* <View className="return_home_button link_button" onClick={handle_return_home}>
|
||||||
|
<Text className="button_text">返回首页</Text>
|
||||||
|
</View> */}
|
||||||
|
|
||||||
{/* 用户协议复选框 */}
|
{/* 用户协议复选框 */}
|
||||||
<View className="terms_checkbox_section">
|
<View className="terms_checkbox_section">
|
||||||
<View className="checkbox_container" onClick={handle_toggle_terms}>
|
<View className="checkbox_container" onClick={handle_toggle_terms}>
|
||||||
@@ -224,13 +236,13 @@ const LoginPage: React.FC = () => {
|
|||||||
className="terms_link"
|
className="terms_link"
|
||||||
onClick={() => handle_view_terms("terms")}
|
onClick={() => handle_view_terms("terms")}
|
||||||
>
|
>
|
||||||
《开场的条款和条件》
|
《有场的条款和条件》
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
className="terms_link"
|
className="terms_link"
|
||||||
onClick={() => handle_view_terms("binding")}
|
onClick={() => handle_view_terms("binding")}
|
||||||
>
|
>
|
||||||
《开场与微信号绑定协议》
|
《有场与微信号绑定协议》
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
className="terms_link"
|
className="terms_link"
|
||||||
@@ -259,13 +271,13 @@ const LoginPage: React.FC = () => {
|
|||||||
className="terms_item"
|
className="terms_item"
|
||||||
onClick={() => handle_view_terms("terms")}
|
onClick={() => handle_view_terms("terms")}
|
||||||
>
|
>
|
||||||
《开场的条款和条件》
|
《有场的条款和条件》
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
className="terms_item"
|
className="terms_item"
|
||||||
onClick={() => handle_view_terms("binding")}
|
onClick={() => handle_view_terms("binding")}
|
||||||
>
|
>
|
||||||
《开场与微信号绑定协议》
|
《有场与微信号绑定协议》
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
className="terms_item"
|
className="terms_item"
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# 条款页面 - 开场的条款和条件
|
# 条款页面 - 有场的条款和条件
|
||||||
|
|
||||||
## 功能概述
|
## 功能概述
|
||||||
|
|
||||||
条款页面展示完整的《开场的条款和条件》内容,用户需要仔细阅读并同意后才能继续使用平台服务。
|
条款页面展示完整的《有场的条款和条件》内容,用户需要仔细阅读并同意后才能继续使用平台服务。
|
||||||
|
|
||||||
## 🎨 设计特点
|
## 🎨 设计特点
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ TermsPage
|
|||||||
|
|
||||||
## 📋 条款内容
|
## 📋 条款内容
|
||||||
|
|
||||||
本页面包含完整的《开场的条款和条件》,涵盖以下十个主要部分:
|
本页面包含完整的《有场的条款和条件》,涵盖以下十个主要部分:
|
||||||
|
|
||||||
### 1. 服务内容
|
### 1. 服务内容
|
||||||
- 活动发布、报名、聊天室沟通、活动提醒等服务
|
- 活动发布、报名、聊天室沟通、活动提醒等服务
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const TermsPage: React.FC = () => {
|
|||||||
// 获取页面参数
|
// 获取页面参数
|
||||||
const [termsType, setTermsType] = React.useState('terms');
|
const [termsType, setTermsType] = React.useState('terms');
|
||||||
const [pageTitle, setPageTitle] = React.useState('条款和条件');
|
const [pageTitle, setPageTitle] = React.useState('条款和条件');
|
||||||
const [termsTitle, setTermsTitle] = React.useState('《开场的条款和条件》');
|
const [termsTitle, setTermsTitle] = React.useState('《有场的条款和条件》');
|
||||||
const [termsContent, setTermsContent] = React.useState('');
|
const [termsContent, setTermsContent] = React.useState('');
|
||||||
|
|
||||||
// 返回上一页
|
// 返回上一页
|
||||||
@@ -23,7 +23,7 @@ const TermsPage: React.FC = () => {
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case 'terms':
|
case 'terms':
|
||||||
setPageTitle('条款和条件');
|
setPageTitle('条款和条件');
|
||||||
setTermsTitle('《开场的条款和条件》');
|
setTermsTitle('《有场的条款和条件》');
|
||||||
setTermsContent(`<span class="terms_first_line">欢迎使用本平台(以下简称"本平台")发布与参与网球活动。为保障您的权益,请您务必仔细阅读并理解以下服务条款。</span>
|
setTermsContent(`<span class="terms_first_line">欢迎使用本平台(以下简称"本平台")发布与参与网球活动。为保障您的权益,请您务必仔细阅读并理解以下服务条款。</span>
|
||||||
|
|
||||||
一、服务内容
|
一、服务内容
|
||||||
@@ -69,7 +69,7 @@ const TermsPage: React.FC = () => {
|
|||||||
break;
|
break;
|
||||||
case 'binding':
|
case 'binding':
|
||||||
setPageTitle('微信号绑定协议');
|
setPageTitle('微信号绑定协议');
|
||||||
setTermsTitle('《开场与微信号绑定协议》');
|
setTermsTitle('《有场与微信号绑定协议》');
|
||||||
setTermsContent(`<span class="terms_first_line">欢迎使用本平台(以下简称"本平台")的微信绑定服务。为保障您的权益,请您务必仔细阅读并理解以下协议内容。</span>
|
setTermsContent(`<span class="terms_first_line">欢迎使用本平台(以下简称"本平台")的微信绑定服务。为保障您的权益,请您务必仔细阅读并理解以下协议内容。</span>
|
||||||
|
|
||||||
一、绑定服务说明
|
一、绑定服务说明
|
||||||
@@ -171,7 +171,7 @@ const TermsPage: React.FC = () => {
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
setPageTitle('条款和条件');
|
setPageTitle('条款和条件');
|
||||||
setTermsTitle('《开场的条款和条件》');
|
setTermsTitle('《有场的条款和条件》');
|
||||||
setTermsContent('条款内容加载中...');
|
setTermsContent('条款内容加载中...');
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ VerificationPage
|
|||||||
- **页面跳转**:登录成功后跳转到首页
|
- **页面跳转**:登录成功后跳转到首页
|
||||||
|
|
||||||
### 协议支持
|
### 协议支持
|
||||||
- **条款链接**:《开场的条款和条件》
|
- **条款链接**:《有场的条款和条件》
|
||||||
- **隐私政策**:《隐私权政策》
|
- **隐私政策**:《隐私权政策》
|
||||||
- **动态跳转**:支持通过 URL 参数指定协议类型
|
- **动态跳转**:支持通过 URL 参数指定协议类型
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
|||||||
gamesNum, // 新增:获取球局数量
|
gamesNum, // 新增:获取球局数量
|
||||||
} = store;
|
} = store;
|
||||||
|
|
||||||
|
const supportedCitiesList = useDictionaryStore((s) => s.getDictionaryValue('supported_cities', ['上海市'])) || [];
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isShowFilterPopup,
|
isShowFilterPopup,
|
||||||
data: matches,
|
data: matches,
|
||||||
@@ -92,6 +94,8 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
|||||||
// 记录上一次加载数据时的城市,用于检测城市变化
|
// 记录上一次加载数据时的城市,用于检测城市变化
|
||||||
const lastLoadedAreaRef = useRef<[string, string] | null>(null);
|
const lastLoadedAreaRef = useRef<[string, string] | null>(null);
|
||||||
const prevIsActiveRef = useRef(isActive);
|
const prevIsActiveRef = useRef(isActive);
|
||||||
|
// 记录是否是进入列表页的第一次调用 updateUserLocation(首次传 force: true)
|
||||||
|
const hasUpdatedLocationRef = useRef(false);
|
||||||
|
|
||||||
// 处理距离筛选显示/隐藏
|
// 处理距离筛选显示/隐藏
|
||||||
const handleDistanceFilterVisibleChange = useCallback(
|
const handleDistanceFilterVisibleChange = useCallback(
|
||||||
@@ -129,15 +133,37 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
|||||||
// ScrollView 滚动处理
|
// ScrollView 滚动处理
|
||||||
const handleScrollViewScroll = useCallback(
|
const handleScrollViewScroll = useCallback(
|
||||||
(e: any) => {
|
(e: any) => {
|
||||||
|
|
||||||
|
|
||||||
const currentScrollTop = e?.detail?.scrollTop || 0;
|
const currentScrollTop = e?.detail?.scrollTop || 0;
|
||||||
|
const scrollHeight = e?.detail?.scrollHeight || 0;
|
||||||
|
const clientHeight = e?.detail?.clientHeight || 0;
|
||||||
const lastScrollTop = lastScrollTopRef.current;
|
const lastScrollTop = lastScrollTopRef.current;
|
||||||
const currentTime = Date.now();
|
const currentTime = Date.now();
|
||||||
const timeDiff = currentTime - lastScrollTimeRef.current;
|
const timeDiff = currentTime - lastScrollTimeRef.current;
|
||||||
|
|
||||||
if (timeDiff < 100) return;
|
if (timeDiff < 100) return;
|
||||||
|
|
||||||
|
// 计算距离底部的距离,提前加载(距离底部600px时开始加载)
|
||||||
|
// 注意:如果 scrollHeight 或 clientHeight 不可用,则使用 lowerThreshold 触发
|
||||||
|
if (scrollHeight > 0 && clientHeight > 0) {
|
||||||
|
const distanceToBottom = scrollHeight - currentScrollTop - clientHeight;
|
||||||
|
const preloadThreshold = 600; // 提前加载阈值
|
||||||
|
|
||||||
|
// 如果距离底部小于阈值,且正在向下滚动,且有更多数据,则提前加载
|
||||||
|
if (
|
||||||
|
distanceToBottom < preloadThreshold &&
|
||||||
|
distanceToBottom > 0 &&
|
||||||
|
!loading &&
|
||||||
|
!loadingMoreRef.current &&
|
||||||
|
listPageState?.isHasMoreData &&
|
||||||
|
currentScrollTop > lastScrollTop // 向下滚动
|
||||||
|
) {
|
||||||
|
loadingMoreRef.current = true;
|
||||||
|
loadMoreMatches().finally(() => {
|
||||||
|
loadingMoreRef.current = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const scrollDiff = currentScrollTop - lastScrollTop;
|
const scrollDiff = currentScrollTop - lastScrollTop;
|
||||||
let newDirection = scrollDirectionRef.current;
|
let newDirection = scrollDirectionRef.current;
|
||||||
if (Math.abs(scrollDiff) > 15) {
|
if (Math.abs(scrollDiff) > 15) {
|
||||||
@@ -195,7 +221,7 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
|||||||
lastScrollTopRef.current = currentScrollTop;
|
lastScrollTopRef.current = currentScrollTop;
|
||||||
lastScrollTimeRef.current = currentTime;
|
lastScrollTimeRef.current = currentTime;
|
||||||
},
|
},
|
||||||
[updateListPageState, onNavStateChange]
|
[updateListPageState, onNavStateChange, loading, loadMoreMatches, listPageState?.isHasMoreData]
|
||||||
// 移除 showSearchBar 和 isShowInputCustomerNavBar 依赖,使用 ref 获取最新值
|
// 移除 showSearchBar 和 isShowInputCustomerNavBar 依赖,使用 ref 获取最新值
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -214,6 +240,38 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
|||||||
}
|
}
|
||||||
}, [isActive]);
|
}, [isActive]);
|
||||||
|
|
||||||
|
// 记录上一次的城市,用于检测城市变化
|
||||||
|
const prevAreaRef = useRef<[string, string] | null>(null);
|
||||||
|
|
||||||
|
// 监听城市变化,重新获取行政区列表并清空已选择的行政区
|
||||||
|
useEffect(() => {
|
||||||
|
if (area && area.length >= 2) {
|
||||||
|
const currentProvince = area[1];
|
||||||
|
const prevProvince = prevAreaRef.current?.[1];
|
||||||
|
|
||||||
|
// 只有当城市真正改变时才执行(避免初始化时也触发)
|
||||||
|
if (prevProvince && prevProvince !== currentProvince) {
|
||||||
|
console.log("城市改变,重新获取行政区列表:", {
|
||||||
|
prevProvince,
|
||||||
|
currentProvince,
|
||||||
|
});
|
||||||
|
// 城市改变时,重新获取行政区列表
|
||||||
|
getDistricts();
|
||||||
|
// 清空已选择的行政区,避免显示错误的行政区
|
||||||
|
const currentState = useListStore.getState();
|
||||||
|
const currentPageState = currentState.isSearchResult
|
||||||
|
? currentState.searchPageState
|
||||||
|
: currentState.listPageState;
|
||||||
|
if (currentPageState?.distanceQuickFilter?.district) {
|
||||||
|
updateDistanceQuickFilter({ district: undefined });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 更新记录的城市
|
||||||
|
prevAreaRef.current = area as [string, string];
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [area?.[1]]); // 只监听省份(area[1])的变化
|
||||||
|
|
||||||
// 当页面从非激活状态切换为激活状态时,检查城市是否变化,如果变化则重新加载数据
|
// 当页面从非激活状态切换为激活状态时,检查城市是否变化,如果变化则重新加载数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 如果从非激活状态变为激活状态(切回列表页)
|
// 如果从非激活状态变为激活状态(切回列表页)
|
||||||
@@ -235,9 +293,9 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
|||||||
currentProvince,
|
currentProvince,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 地址发生变化或不一致,重新加载数据和球局数量
|
// 延迟刷新,等 tab 切换动画完成后再加载,避免切换时列表重渲染导致抖动
|
||||||
// 先调用列表接口,然后在列表接口完成后调用数量接口
|
const delayMs = 280;
|
||||||
(async () => {
|
const timer = setTimeout(async () => {
|
||||||
try {
|
try {
|
||||||
if (refreshBothLists) {
|
if (refreshBothLists) {
|
||||||
await refreshBothLists();
|
await refreshBothLists();
|
||||||
@@ -253,7 +311,9 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("重新加载数据失败:", error);
|
console.error("重新加载数据失败:", error);
|
||||||
}
|
}
|
||||||
})();
|
}, delayMs);
|
||||||
|
prevIsActiveRef.current = isActive;
|
||||||
|
return () => clearTimeout(timer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,7 +370,10 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
|||||||
updateState({ location });
|
updateState({ location });
|
||||||
if (location && location.latitude && location.longitude) {
|
if (location && location.latitude && location.longitude) {
|
||||||
try {
|
try {
|
||||||
await updateUserLocation(location.latitude, location.longitude);
|
// 进入列表页的第一次调用传 force: true,后续调用传 false
|
||||||
|
const isFirstCall = !hasUpdatedLocationRef.current;
|
||||||
|
await updateUserLocation(location.latitude, location.longitude, isFirstCall);
|
||||||
|
hasUpdatedLocationRef.current = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("更新用户位置失败:", error);
|
console.error("更新用户位置失败:", error);
|
||||||
}
|
}
|
||||||
@@ -331,7 +394,9 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleRefresh = async () => {
|
const handleRefresh = async () => {
|
||||||
|
if (refreshing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setRefreshing(true);
|
setRefreshing(true);
|
||||||
try {
|
try {
|
||||||
@@ -390,6 +455,17 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 处理重新定位
|
||||||
|
const handleRelocate = async (location) => {
|
||||||
|
try {
|
||||||
|
// 位置已更新到后端,刷新列表数据
|
||||||
|
await getMatchesData();
|
||||||
|
await fetchGetGamesCount();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("刷新列表失败:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleSearchClick = () => {
|
const handleSearchClick = () => {
|
||||||
navigateTo({
|
navigateTo({
|
||||||
url: "/game_pages/search/index",
|
url: "/game_pages/search/index",
|
||||||
@@ -409,7 +485,7 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
|||||||
initDictionaryData();
|
initDictionaryData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 获取省份名称(area 格式: ["中国", "省份"])
|
|
||||||
const province = area?.at(1) || "上海";
|
const province = area?.at(1) || "上海";
|
||||||
|
|
||||||
function renderCityQrcode() {
|
function renderCityQrcode() {
|
||||||
@@ -451,8 +527,12 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 判定是否显示"暂无球局"页面
|
// 判定是否显示"暂无球局"页面
|
||||||
// 条件:省份不是上海 或 (已加载完成且球局数量为0)
|
// 从配置接口 /parameter/many_key 获取 supported_cities(格式如 "上海市||北京市")
|
||||||
const shouldShowNoGames = province !== "上海";
|
// 当前省份在有球局城市列表中则显示列表,否则显示暂无球局
|
||||||
|
const shouldShowNoGames =
|
||||||
|
supportedCitiesList.length > 0
|
||||||
|
? !supportedCitiesList.includes(province)
|
||||||
|
: province !== "上海市"; // 配置未加载时默认按上海判断
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -503,6 +583,7 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
|||||||
quickValue={distanceQuickFilter?.order}
|
quickValue={distanceQuickFilter?.order}
|
||||||
districtValue={distanceQuickFilter?.district}
|
districtValue={distanceQuickFilter?.district}
|
||||||
onMenuVisibleChange={handleDistanceFilterVisibleChange}
|
onMenuVisibleChange={handleDistanceFilterVisibleChange}
|
||||||
|
onRelocate={handleRelocate}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -518,7 +599,7 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
|||||||
refresherEnabled={true}
|
refresherEnabled={true}
|
||||||
refresherTriggered={refreshing}
|
refresherTriggered={refreshing}
|
||||||
onRefresherRefresh={handleRefresh}
|
onRefresherRefresh={handleRefresh}
|
||||||
lowerThreshold={100}
|
lowerThreshold={600}
|
||||||
onScrollToLower={async () => {
|
onScrollToLower={async () => {
|
||||||
if (
|
if (
|
||||||
!loading &&
|
!loading &&
|
||||||
@@ -546,6 +627,7 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
|||||||
reload={refreshMatches}
|
reload={refreshMatches}
|
||||||
loadMoreMatches={loadMoreMatches}
|
loadMoreMatches={loadMoreMatches}
|
||||||
evaluateFlag
|
evaluateFlag
|
||||||
|
enableHomeCards
|
||||||
/>
|
/>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -64,11 +64,17 @@ const MessagePageContent: React.FC<MessagePageContentProps> = ({ isActive = true
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 只有当页面激活且未加载过数据时才加载接口
|
// 当切换到消息 tab 时,调用红点信息接口
|
||||||
|
useEffect(() => {
|
||||||
|
if (isActive) {
|
||||||
|
fetchReddotInfo();
|
||||||
|
}
|
||||||
|
}, [isActive]);
|
||||||
|
|
||||||
|
// 只有当页面激活且未加载过数据时才加载通知列表
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isActive && !hasLoaded) {
|
if (isActive && !hasLoaded) {
|
||||||
getNoticeList();
|
getNoticeList();
|
||||||
fetchReddotInfo();
|
|
||||||
setHasLoaded(true);
|
setHasLoaded(true);
|
||||||
}
|
}
|
||||||
}, [isActive, hasLoaded]);
|
}, [isActive, hasLoaded]);
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ interface MyselfPageContentProps {
|
|||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MyselfPageContent: React.FC<MyselfPageContentProps> = ({ isActive = true }) => {
|
const MyselfPageContent: React.FC<MyselfPageContentProps> = ({
|
||||||
|
isActive = true,
|
||||||
|
}) => {
|
||||||
const pickerOption = usePickerOption();
|
const pickerOption = usePickerOption();
|
||||||
const { statusNavbarHeightInfo } = useGlobalState() || {};
|
const { statusNavbarHeightInfo } = useGlobalState() || {};
|
||||||
const { totalHeight = 98 } = statusNavbarHeightInfo || {};
|
const { totalHeight = 98 } = statusNavbarHeightInfo || {};
|
||||||
@@ -37,6 +39,7 @@ const MyselfPageContent: React.FC<MyselfPageContentProps> = ({ isActive = true }
|
|||||||
const [hasLoaded, setHasLoaded] = useState(false); // 记录是否已经加载过数据
|
const [hasLoaded, setHasLoaded] = useState(false); // 记录是否已经加载过数据
|
||||||
|
|
||||||
const [collapseProfile, setCollapseProfile] = useState(false);
|
const [collapseProfile, setCollapseProfile] = useState(false);
|
||||||
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
pickerOption.getCities();
|
pickerOption.getCities();
|
||||||
@@ -64,20 +67,22 @@ const MyselfPageContent: React.FC<MyselfPageContentProps> = ({ isActive = true }
|
|||||||
game_records: TennisMatch[]
|
game_records: TennisMatch[]
|
||||||
): { notEndGames: TennisMatch[]; finishedGames: TennisMatch[] } => {
|
): { notEndGames: TennisMatch[]; finishedGames: TennisMatch[] } => {
|
||||||
const now = new Date().getTime();
|
const now = new Date().getTime();
|
||||||
return game_records.reduce(
|
|
||||||
(result, cur) => {
|
// 使用for
|
||||||
let { end_time } = cur;
|
const notEndGames: TennisMatch[] = [];
|
||||||
end_time = end_time.replace(/\s/, "T");
|
const finishedGames: TennisMatch[] = [];
|
||||||
new Date(end_time).getTime() > now
|
for (const game of game_records) {
|
||||||
? result.notEndGames.push(cur)
|
const { end_time } = game;
|
||||||
: result.finishedGames.push(cur);
|
const end_time_str = end_time.replace(/\s/, "T");
|
||||||
return result;
|
new Date(end_time_str).getTime() > now
|
||||||
},
|
? notEndGames.push(game)
|
||||||
{
|
: finishedGames.push(game);
|
||||||
notEndGames: [] as TennisMatch[],
|
|
||||||
finishedGames: [] as TennisMatch[],
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
console.log("notEndGames", notEndGames);
|
||||||
|
|
||||||
|
return { notEndGames, finishedGames };
|
||||||
|
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
@@ -94,6 +99,8 @@ const MyselfPageContent: React.FC<MyselfPageContentProps> = ({ isActive = true }
|
|||||||
} else {
|
} else {
|
||||||
games_data = await UserService.get_participated_games(user_info.id);
|
games_data = await UserService.get_participated_games(user_info.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const sorted_games = games_data.sort((a, b) => {
|
const sorted_games = games_data.sort((a, b) => {
|
||||||
return (
|
return (
|
||||||
new Date(a.original_start_time.replace(/\s/, "T")).getTime() -
|
new Date(a.original_start_time.replace(/\s/, "T")).getTime() -
|
||||||
@@ -101,6 +108,8 @@ const MyselfPageContent: React.FC<MyselfPageContentProps> = ({ isActive = true }
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
const { notEndGames, finishedGames } = classifyGameRecords(sorted_games);
|
const { notEndGames, finishedGames } = classifyGameRecords(sorted_games);
|
||||||
|
console.log("notEndGames", notEndGames);
|
||||||
|
|
||||||
set_game_records(notEndGames);
|
set_game_records(notEndGames);
|
||||||
setEndedGameRecords(finishedGames);
|
setEndedGameRecords(finishedGames);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -169,6 +178,23 @@ const MyselfPageContent: React.FC<MyselfPageContentProps> = ({ isActive = true }
|
|||||||
setActiveTab(tab);
|
setActiveTab(tab);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 下拉刷新:刷新用户信息和球局数据
|
||||||
|
const handle_refresh = async () => {
|
||||||
|
setRefreshing(true);
|
||||||
|
try {
|
||||||
|
await Promise.all([fetchUserInfo(), load_game_data()]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("刷新失败:", error);
|
||||||
|
(Taro as any).showToast({
|
||||||
|
title: "刷新失败,请重试",
|
||||||
|
icon: "none",
|
||||||
|
duration: 2000,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setRefreshing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// const handleScroll = (event: any) => {
|
// const handleScroll = (event: any) => {
|
||||||
// const scrollData = event.detail;
|
// const scrollData = event.detail;
|
||||||
// setCollapseProfile(scrollData.scrollTop > 1);
|
// setCollapseProfile(scrollData.scrollTop > 1);
|
||||||
@@ -178,6 +204,9 @@ const MyselfPageContent: React.FC<MyselfPageContentProps> = ({ isActive = true }
|
|||||||
<ScrollView
|
<ScrollView
|
||||||
scrollY
|
scrollY
|
||||||
refresherBackground="#FAFAFA"
|
refresherBackground="#FAFAFA"
|
||||||
|
refresherEnabled
|
||||||
|
refresherTriggered={refreshing}
|
||||||
|
onRefresherRefresh={handle_refresh}
|
||||||
className={styles.myselfPage}
|
className={styles.myselfPage}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
@@ -229,16 +258,14 @@ const MyselfPageContent: React.FC<MyselfPageContentProps> = ({ isActive = true }
|
|||||||
<View className={styles.gameTabsSection}>
|
<View className={styles.gameTabsSection}>
|
||||||
<View className={styles.tabContainer}>
|
<View className={styles.tabContainer}>
|
||||||
<View
|
<View
|
||||||
className={`${styles.tabItem} ${
|
className={`${styles.tabItem} ${active_tab === "hosted" ? styles.active : ""
|
||||||
active_tab === "hosted" ? styles.active : ""
|
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setActiveTab("hosted")}
|
onClick={() => setActiveTab("hosted")}
|
||||||
>
|
>
|
||||||
<Text className={styles.tabText}>我主办的</Text>
|
<Text className={styles.tabText}>我主办的</Text>
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
className={`${styles.tabItem} ${
|
className={`${styles.tabItem} ${active_tab === "participated" ? styles.active : ""
|
||||||
active_tab === "participated" ? styles.active : ""
|
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setActiveTab("participated")}
|
onClick={() => setActiveTab("participated")}
|
||||||
>
|
>
|
||||||
@@ -267,9 +294,8 @@ const MyselfPageContent: React.FC<MyselfPageContentProps> = ({ isActive = true }
|
|||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
listLoadErrorWrapperHeight="fit-content"
|
listLoadErrorWrapperHeight="fit-content"
|
||||||
listLoadErrorWidth="320px"
|
listLoadErrorWidth="410px"
|
||||||
listLoadErrorHeight="152px"
|
listLoadErrorHeight="185px"
|
||||||
listLoadErrorScale="1.2"
|
|
||||||
defaultShowNum={3}
|
defaultShowNum={3}
|
||||||
/>
|
/>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
@@ -291,9 +317,8 @@ const MyselfPageContent: React.FC<MyselfPageContentProps> = ({ isActive = true }
|
|||||||
collapse={true}
|
collapse={true}
|
||||||
style={{ paddingBottom: "90px", overflow: "hidden" }}
|
style={{ paddingBottom: "90px", overflow: "hidden" }}
|
||||||
listLoadErrorWrapperHeight="fit-content"
|
listLoadErrorWrapperHeight="fit-content"
|
||||||
listLoadErrorWidth="320px"
|
listLoadErrorWidth="410px"
|
||||||
listLoadErrorHeight="152px"
|
listLoadErrorHeight="185px"
|
||||||
listLoadErrorScale="1.2"
|
|
||||||
defaultShowNum={3}
|
defaultShowNum={3}
|
||||||
/>
|
/>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export default definePageConfig({
|
export default definePageConfig({
|
||||||
navigationBarTitleText: '首页',
|
navigationBarTitleText: '首页',
|
||||||
navigationStyle: 'custom',
|
navigationStyle: 'custom',
|
||||||
navigationBarBackgroundColor: '#FAFAFA'
|
navigationBarBackgroundColor: '#FAFAFA',
|
||||||
|
enableShareAppMessage: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -21,21 +21,17 @@
|
|||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: scale(0.98);
|
transition: opacity 0.25s ease-out;
|
||||||
transition: opacity 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94),
|
|
||||||
transform 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
will-change: opacity, transform;
|
visibility: hidden;
|
||||||
backface-visibility: hidden;
|
|
||||||
-webkit-backface-visibility: hidden;
|
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: scale(1);
|
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
|
visibility: visible;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect, useCallback } from "react";
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
import { View } from "@tarojs/components";
|
import { View } from "@tarojs/components";
|
||||||
import Taro from "@tarojs/taro";
|
import Taro, { useRouter, useShareAppMessage } from "@tarojs/taro";
|
||||||
|
import { OSS_BASE } from "@/config/api";
|
||||||
import { wechat_auth_login, save_login_state } from "@/services/loginService";
|
import { wechat_auth_login, save_login_state } from "@/services/loginService";
|
||||||
import { useUserActions } from "@/store/userStore";
|
import { useUserActions } from "@/store/userStore";
|
||||||
import { useGlobalState } from "@/store/global";
|
import { useGlobalState } from "@/store/global";
|
||||||
@@ -13,11 +14,16 @@ import MessagePageContent from "./components/MessagePageContent";
|
|||||||
import MyselfPageContent from "./components/MyselfPageContent";
|
import MyselfPageContent from "./components/MyselfPageContent";
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
import FamilyContext from "@/context";
|
import FamilyContext from "@/context";
|
||||||
|
import { useDictionaryStore } from "@/store/dictionaryStore";
|
||||||
|
|
||||||
type TabType = "list" | "message" | "personal";
|
type TabType = "list" | "message" | "personal";
|
||||||
|
|
||||||
const MainPage: React.FC = () => {
|
const MainPage: React.FC = () => {
|
||||||
const [currentTab, setCurrentTab] = useState<TabType>("list");
|
const { params } = useRouter();
|
||||||
|
const [currentTab, setCurrentTab] = useState<TabType>(() => {
|
||||||
|
const tab = params?.tab as TabType | undefined;
|
||||||
|
return tab === "list" || tab === "message" || tab === "personal" ? tab : "list";
|
||||||
|
});
|
||||||
const [isPublishMenuVisible, setIsPublishMenuVisible] = useState(false);
|
const [isPublishMenuVisible, setIsPublishMenuVisible] = useState(false);
|
||||||
const [isDistanceFilterVisible, setIsDistanceFilterVisible] = useState(false);
|
const [isDistanceFilterVisible, setIsDistanceFilterVisible] = useState(false);
|
||||||
const [isCityPickerVisible, setIsCityPickerVisible] = useState(false);
|
const [isCityPickerVisible, setIsCityPickerVisible] = useState(false);
|
||||||
@@ -34,6 +40,14 @@ const MainPage: React.FC = () => {
|
|||||||
const { showGuideBar, guideBarZIndex, setShowGuideBar, setGuideBarZIndex } =
|
const { showGuideBar, guideBarZIndex, setShowGuideBar, setGuideBarZIndex } =
|
||||||
useGlobalState();
|
useGlobalState();
|
||||||
|
|
||||||
|
// 从分享链接进入时根据 ?tab= 定位到对应 tab
|
||||||
|
useEffect(() => {
|
||||||
|
const tab = params?.tab as TabType | undefined;
|
||||||
|
if (tab === "list" || tab === "message" || tab === "personal") {
|
||||||
|
setCurrentTab(tab);
|
||||||
|
}
|
||||||
|
}, [params?.tab]);
|
||||||
|
|
||||||
// 初始化:自动微信授权并获取用户信息
|
// 初始化:自动微信授权并获取用户信息
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
@@ -152,6 +166,21 @@ const MainPage: React.FC = () => {
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 分享:首页、个人页均支持转发
|
||||||
|
// 分享图:配置 OSS 地址 + 路径(不含 ? 后参数),首页用 share_home.png,个人页用 share_self.png
|
||||||
|
useShareAppMessage(() => {
|
||||||
|
const isList = currentTab === "list";
|
||||||
|
const isPersonal = currentTab === "personal";
|
||||||
|
const title = isList ? "约球 - 发现身边的球局" : isPersonal ? "约球 - 我的约球" : "约球";
|
||||||
|
const image_path = isPersonal ? "system/share_self.png" : "system/share_home.png";
|
||||||
|
const imageUrl = OSS_BASE ? `${OSS_BASE.replace(/\/$/, "")}/${image_path}` : "";
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
path: "/main_pages/index" + (isList ? "?tab=list" : isPersonal ? "?tab=personal" : ""),
|
||||||
|
imageUrl: imageUrl,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
// 滚动到顶部
|
// 滚动到顶部
|
||||||
const scrollToTop = useCallback(() => {
|
const scrollToTop = useCallback(() => {
|
||||||
// 如果当前是列表页,触发列表页内部滚动
|
// 如果当前是列表页,触发列表页内部滚动
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import orderService, {
|
|||||||
OrderStatus,
|
OrderStatus,
|
||||||
refundTextMap,
|
refundTextMap,
|
||||||
} from "@/services/orderService";
|
} from "@/services/orderService";
|
||||||
|
import { debounce } from "@tarojs/runtime";
|
||||||
import {
|
import {
|
||||||
payOrder,
|
payOrder,
|
||||||
delay,
|
delay,
|
||||||
@@ -25,7 +26,7 @@ import { useGlobalStore } from "@/store/global";
|
|||||||
import { useOrder } from "@/store/orderStore";
|
import { useOrder } from "@/store/orderStore";
|
||||||
import detailService, { GameData } from "@/services/detailService";
|
import detailService, { GameData } from "@/services/detailService";
|
||||||
import { withAuth, RefundPopup, GeneralNavbar } from "@/components";
|
import { withAuth, RefundPopup, GeneralNavbar } from "@/components";
|
||||||
import { OSS_BASE_URL } from "@/config/api";
|
import { OSS_BASE } from "@/config/api";
|
||||||
import img from "@/config/images";
|
import img from "@/config/images";
|
||||||
import CustomerIcon from "@/static/order/customer.svg";
|
import CustomerIcon from "@/static/order/customer.svg";
|
||||||
import { handleCustomerService } from "@/services/userService";
|
import { handleCustomerService } from "@/services/userService";
|
||||||
@@ -300,7 +301,7 @@ function GameInfo(props) {
|
|||||||
<View className={styles.locationMessageIcon}>
|
<View className={styles.locationMessageIcon}>
|
||||||
<Image
|
<Image
|
||||||
className={styles.locationMessageIconImage}
|
className={styles.locationMessageIconImage}
|
||||||
src={`${OSS_BASE_URL}/images/3ee5c89c-fe58-4a56-9471-1295da09c743.png`}
|
src={`${OSS_BASE}/front/ball/images/3ee5c89c-fe58-4a56-9471-1295da09c743.png`}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
{/* location message */}
|
{/* location message */}
|
||||||
@@ -380,8 +381,13 @@ function OrderMsg(props) {
|
|||||||
wechat_contact,
|
wechat_contact,
|
||||||
price,
|
price,
|
||||||
} = detail;
|
} = detail;
|
||||||
const { order_no } = orderDetail;
|
const { order_no, registrant_phone: registrant_phone_from_order } =
|
||||||
const { order_info: { registrant_phone } = {} } = checkOrderInfo;
|
orderDetail;
|
||||||
|
const {
|
||||||
|
order_info: { registrant_phone: registrant_phone_from_check_order } = {},
|
||||||
|
} = checkOrderInfo || {};
|
||||||
|
const registrant_phone =
|
||||||
|
registrant_phone_from_order || registrant_phone_from_check_order;
|
||||||
const startTime = dayjs(start_time);
|
const startTime = dayjs(start_time);
|
||||||
const endTime = dayjs(end_time);
|
const endTime = dayjs(end_time);
|
||||||
const startDate = startTime.format("YYYY年M月D日");
|
const startDate = startTime.format("YYYY年M月D日");
|
||||||
@@ -402,13 +408,11 @@ function OrderMsg(props) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "报名人电话",
|
title: "报名人电话",
|
||||||
// content: registrant_phone,
|
|
||||||
content: registrant_phone ? (
|
content: registrant_phone ? (
|
||||||
<Text
|
<Text
|
||||||
selectable={true} // 支持长按复制
|
selectable={true} // 支持长按复制
|
||||||
style={{
|
style={{
|
||||||
color: "#007AFF",
|
color: "#007AFF",
|
||||||
// textDecoration: "underline",
|
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -427,7 +431,6 @@ function OrderMsg(props) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "组织人电话",
|
title: "组织人电话",
|
||||||
// content: wechat_contact,
|
|
||||||
content:
|
content:
|
||||||
wechat_contact && isPhoneNumber(wechat_contact) ? (
|
wechat_contact && isPhoneNumber(wechat_contact) ? (
|
||||||
<Text
|
<Text
|
||||||
@@ -489,8 +492,7 @@ function OrderMsg(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function RefundPolicy(props) {
|
function RefundPolicy(props) {
|
||||||
const { checkOrderInfo } = props;
|
const { refund_policy = [] } = props;
|
||||||
const { refund_policy = [] } = checkOrderInfo;
|
|
||||||
const current = dayjs();
|
const current = dayjs();
|
||||||
const policyList = [
|
const policyList = [
|
||||||
{
|
{
|
||||||
@@ -563,7 +565,7 @@ const OrderCheck = () => {
|
|||||||
const [id, gameId] = [Number(stringId), Number(stringGameId)];
|
const [id, gameId] = [Number(stringId), Number(stringGameId)];
|
||||||
const [detail, setDetail] = useState<GameData | {}>({});
|
const [detail, setDetail] = useState<GameData | {}>({});
|
||||||
const [location, setLocation] = useState<number[]>([0, 0]);
|
const [location, setLocation] = useState<number[]>([0, 0]);
|
||||||
const [checkOrderInfo, setCheckOrderInfo] = useState<GameOrderRes | {}>({});
|
const [checkOrderInfo, setCheckOrderInfo] = useState<GameOrderRes>();
|
||||||
const [orderDetail, setOrderDetail] = useState({});
|
const [orderDetail, setOrderDetail] = useState({});
|
||||||
const { paying, setPaying } = useOrder();
|
const { paying, setPaying } = useOrder();
|
||||||
|
|
||||||
@@ -584,11 +586,11 @@ const OrderCheck = () => {
|
|||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
gameDetail = res.data;
|
gameDetail = res.data;
|
||||||
}
|
}
|
||||||
|
checkOrder(gameId);
|
||||||
}
|
}
|
||||||
if (gameDetail.id) {
|
|
||||||
setDetail(gameDetail);
|
setDetail(gameDetail);
|
||||||
onInit(gameDetail.id);
|
const location = await getCurrentLocation();
|
||||||
}
|
setLocation([location.latitude, location.longitude]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkOrder(gid) {
|
async function checkOrder(gid) {
|
||||||
@@ -596,12 +598,6 @@ const OrderCheck = () => {
|
|||||||
setCheckOrderInfo(orderRes.data);
|
setCheckOrderInfo(orderRes.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onInit(gid) {
|
|
||||||
checkOrder(gid);
|
|
||||||
const location = await getCurrentLocation();
|
|
||||||
setLocation([location.latitude, location.longitude]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getPaymentParams() {
|
async function getPaymentParams() {
|
||||||
// 检查登录状态和手机号(创建订单前检查)
|
// 检查登录状态和手机号(创建订单前检查)
|
||||||
if (!requireLoginWithPhone()) {
|
if (!requireLoginWithPhone()) {
|
||||||
@@ -620,16 +616,12 @@ const OrderCheck = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//TODO: get order msg from id
|
//TODO: get order msg from id
|
||||||
const handlePay = async () => {
|
const handlePay = debounce(async () => {
|
||||||
// 检查登录状态和手机号
|
// 检查登录状态和手机号
|
||||||
if (!requireLoginWithPhone()) {
|
if (!requireLoginWithPhone()) {
|
||||||
return; // 未登录或未绑定手机号,已跳转到登录页
|
return; // 未登录或未绑定手机号,已跳转到登录页
|
||||||
}
|
}
|
||||||
setPaying(true);
|
setPaying(true);
|
||||||
Taro.showLoading({
|
|
||||||
title: "支付中...",
|
|
||||||
mask: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
let payment_params = {};
|
let payment_params = {};
|
||||||
try {
|
try {
|
||||||
@@ -641,7 +633,6 @@ const OrderCheck = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
await payOrder(payment_params);
|
await payOrder(payment_params);
|
||||||
Taro.hideLoading();
|
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: "支付成功",
|
title: "支付成功",
|
||||||
icon: "success",
|
icon: "success",
|
||||||
@@ -655,7 +646,6 @@ const OrderCheck = () => {
|
|||||||
// delta: 1,
|
// delta: 1,
|
||||||
// });
|
// });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Taro.hideLoading();
|
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: error.message,
|
title: error.message,
|
||||||
icon: "none",
|
icon: "none",
|
||||||
@@ -665,7 +655,8 @@ const OrderCheck = () => {
|
|||||||
init();
|
init();
|
||||||
setPaying(false);
|
setPaying(false);
|
||||||
}
|
}
|
||||||
};
|
}, 300);
|
||||||
|
|
||||||
if (!id && !gameId) {
|
if (!id && !gameId) {
|
||||||
return (
|
return (
|
||||||
<View className={styles.errorTip}>
|
<View className={styles.errorTip}>
|
||||||
@@ -712,20 +703,25 @@ const OrderCheck = () => {
|
|||||||
checkOrderInfo={checkOrderInfo}
|
checkOrderInfo={checkOrderInfo}
|
||||||
/>
|
/>
|
||||||
{/* Refund policy */}
|
{/* Refund policy */}
|
||||||
<RefundPolicy checkOrderInfo={checkOrderInfo} />
|
<RefundPolicy
|
||||||
|
refund_policy={
|
||||||
|
checkOrderInfo?.refund_policy || orderDetail?.refund_policy || []
|
||||||
|
}
|
||||||
|
/>
|
||||||
{/* Disclaimer */}
|
{/* Disclaimer */}
|
||||||
<Disclaimer />
|
<Disclaimer />
|
||||||
{(!id ||
|
{(!id ||
|
||||||
(order_status === OrderStatus.PENDING &&
|
(order_status === OrderStatus.PENDING &&
|
||||||
cancel_type === CancelType.NONE)) &&
|
cancel_type === CancelType.NONE)) && (
|
||||||
!paying && (
|
|
||||||
<Button
|
<Button
|
||||||
className={styles.payButton}
|
className={styles.payButton}
|
||||||
disabled={paying}
|
disabled={paying}
|
||||||
onClick={handlePay}
|
onClick={handlePay}
|
||||||
|
loading={paying}
|
||||||
>
|
>
|
||||||
{order_status === OrderStatus.PENDING ? "继续" : "确认"}
|
{paying
|
||||||
支付
|
? "支付中..."
|
||||||
|
: `${order_status === OrderStatus.PENDING ? "继续" : "确认"}支付`}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ function generateTimeMsg(game_info) {
|
|||||||
const OrderList = () => {
|
const OrderList = () => {
|
||||||
const [list, setList] = useState<any[][]>([]);
|
const [list, setList] = useState<any[][]>([]);
|
||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
const refundRef = useRef(null);
|
const refundRef = useRef(null);
|
||||||
|
|
||||||
const end = list.length * PAGESIZE >= total;
|
const end = list.length * PAGESIZE >= total;
|
||||||
@@ -114,6 +115,22 @@ const OrderList = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 下拉刷新:重新加载第一页订单
|
||||||
|
async function handle_refresh() {
|
||||||
|
setRefreshing(true);
|
||||||
|
try {
|
||||||
|
await getOrders(1);
|
||||||
|
} catch (error) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: "刷新失败,请重试",
|
||||||
|
icon: "none",
|
||||||
|
duration: 2000,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setRefreshing(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handlePayNow(item) {
|
async function handlePayNow(item) {
|
||||||
// 检查登录状态和手机号
|
// 检查登录状态和手机号
|
||||||
if (!requireLoginWithPhone()) {
|
if (!requireLoginWithPhone()) {
|
||||||
@@ -285,6 +302,10 @@ const OrderList = () => {
|
|||||||
scrollWithAnimation
|
scrollWithAnimation
|
||||||
lowerThreshold={20}
|
lowerThreshold={20}
|
||||||
onScrollToLower={handleFetchNext}
|
onScrollToLower={handleFetchNext}
|
||||||
|
refresherBackground="#FAFAFA"
|
||||||
|
refresherEnabled
|
||||||
|
refresherTriggered={refreshing}
|
||||||
|
onRefresherRefresh={handle_refresh}
|
||||||
enhanced
|
enhanced
|
||||||
showScrollbar={false}
|
showScrollbar={false}
|
||||||
className={styles.list}
|
className={styles.list}
|
||||||
|
|||||||
8
src/other_pages/bannerDetail/index.config.ts
Normal file
8
src/other_pages/bannerDetail/index.config.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default definePageConfig({
|
||||||
|
navigationBarTitleText: '',
|
||||||
|
navigationStyle: 'custom',
|
||||||
|
navigationBarBackgroundColor: '#FFFFFF',
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
20
src/other_pages/bannerDetail/index.scss
Normal file
20
src/other_pages/bannerDetail/index.scss
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
.banner_detail_page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #ffffff;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner_detail_content {
|
||||||
|
padding: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner_detail_image {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 12px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
36
src/other_pages/bannerDetail/index.tsx
Normal file
36
src/other_pages/bannerDetail/index.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { View, Image } from '@tarojs/components';
|
||||||
|
import Taro from '@tarojs/taro';
|
||||||
|
import { GeneralNavbar } from '@/components';
|
||||||
|
import './index.scss';
|
||||||
|
|
||||||
|
function Index() {
|
||||||
|
const [imgUrl, setImgUrl] = useState<string>('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const instance = (Taro as any).getCurrentInstance?.();
|
||||||
|
const params = instance?.router?.params || {};
|
||||||
|
const url = params?.img ? decodeURIComponent(params.img) : '';
|
||||||
|
setImgUrl(url);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View className="banner_detail_page">
|
||||||
|
<GeneralNavbar title="" showBack={true} />
|
||||||
|
<View className="banner_detail_content">
|
||||||
|
{imgUrl ? (
|
||||||
|
<Image
|
||||||
|
className="banner_detail_image"
|
||||||
|
src={imgUrl}
|
||||||
|
mode="widthFix"
|
||||||
|
showMenuByLongpress
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Index;
|
||||||
|
|
||||||
|
|
||||||
@@ -71,14 +71,12 @@ const CommentReply = () => {
|
|||||||
|
|
||||||
setCommentList(mappedList);
|
setCommentList(mappedList);
|
||||||
|
|
||||||
// 获取未读评论ID并标记已读
|
// 获取所有评论ID列表并标记已读(传入所有ID,包括已读和未读)
|
||||||
const unreadIds = res.data.rows
|
const allCommentIds = res.data.rows.map((item: any) => item.id);
|
||||||
.filter((item: any) => item.is_read === 0)
|
|
||||||
.map((item: any) => item.id);
|
|
||||||
|
|
||||||
if (unreadIds.length > 0) {
|
if (allCommentIds.length > 0) {
|
||||||
// 使用统一接口标记已读
|
// 使用统一接口标记已读,传入所有评论ID
|
||||||
messageService.markAsRead('comment', unreadIds).catch(e => {
|
messageService.markAsRead('comment', allCommentIds).catch(e => {
|
||||||
console.error("标记评论已读失败:", e);
|
console.error("标记评论已读失败:", e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -217,6 +215,15 @@ const CommentReply = () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
setCommentList(mappedList);
|
setCommentList(mappedList);
|
||||||
|
|
||||||
|
// 获取所有评论ID列表并标记已读(传入所有ID)
|
||||||
|
const allCommentIds = res.data.rows.map((item: any) => item.id);
|
||||||
|
|
||||||
|
if (allCommentIds.length > 0) {
|
||||||
|
messageService.markAsRead('comment', allCommentIds).catch(e => {
|
||||||
|
console.error("标记评论已读失败:", e);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
@@ -242,7 +249,7 @@ const CommentReply = () => {
|
|||||||
<View className="comment-left">
|
<View className="comment-left">
|
||||||
<Image
|
<Image
|
||||||
className="user-avatar"
|
className="user-avatar"
|
||||||
src={item.user_avatar || "https://img.yzcdn.cn/vant/cat.jpeg"}
|
src={item.user_avatar }
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
onClick={(e) => handleUserClick(e, item.user_id)}
|
onClick={(e) => handleUserClick(e, item.user_id)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
height: calc(100vh - 98px);
|
height: calc(100vh);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 空状态图片
|
// 空状态图片
|
||||||
@@ -56,13 +56,13 @@
|
|||||||
// 按钮区域
|
// 按钮区域
|
||||||
&__buttons {
|
&__buttons {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 110px;
|
bottom: 48px;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding: 0 20px;
|
padding: 8px 20px;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 251px;
|
width: 100%;
|
||||||
height: 52px;
|
height: 52px;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
box-shadow: 0px 8px 64px 0px rgba(0, 0, 0, 0.1),
|
box-shadow: 0px 8px 64px 0px rgba(0, 0, 0, 0.1),
|
||||||
@@ -87,13 +87,12 @@
|
|||||||
|
|
||||||
// 按钮文字样式
|
// 按钮文字样式
|
||||||
&_text {
|
&_text {
|
||||||
font-family: 'DingTalk JinBuTi';
|
|
||||||
font-weight: 400;
|
font-family: 'PingFang SC';
|
||||||
font-size: 18px;
|
font-style: normal;
|
||||||
line-height: 1.11;
|
font-weight: 600;
|
||||||
letter-spacing: -0.05em;
|
font-size: 16px;
|
||||||
text-align: center;
|
line-height: 22px;
|
||||||
color: #000000;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 主要按钮(去看看其他球局)
|
// 主要按钮(去看看其他球局)
|
||||||
@@ -101,6 +100,8 @@
|
|||||||
background: #000000;
|
background: #000000;
|
||||||
border: 2px solid rgba(0, 0, 0, 0.06);
|
border: 2px solid rgba(0, 0, 0, 0.06);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.empty_state_page__button_text {
|
.empty_state_page__button_text {
|
||||||
color: #ffffff; // 黑色背景下使用白色文字
|
color: #ffffff; // 黑色背景下使用白色文字
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import './index.scss';
|
|||||||
|
|
||||||
function Index() {
|
function Index() {
|
||||||
const { statusNavbarHeightInfo } = useGlobalState() || {};
|
const { statusNavbarHeightInfo } = useGlobalState() || {};
|
||||||
const { totalHeight = 98 } = statusNavbarHeightInfo || {};
|
|
||||||
const [countdown, setCountdown] = useState(5);
|
const [countdown, setCountdown] = useState(5);
|
||||||
|
|
||||||
// 倒计时自动返回
|
// 倒计时自动返回
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export default definePageConfig({
|
export default definePageConfig({
|
||||||
navigationBarTitleText: '开启消息通知',
|
navigationBarTitleText: '开启消息通知',
|
||||||
navigationStyle: 'custom',
|
navigationStyle: 'custom',
|
||||||
enablePullDownRefresh: false,
|
backgroundColor:"#FAFAFA"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
.enable_notification_page {
|
.enable_notification_page {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
// min-height: 100vh;
|
height: 100%;
|
||||||
background: radial-gradient(circle at 50% 0%, rgba(191, 255, 239, 1) 0%, rgba(255, 255, 255, 1) 37%);
|
background: radial-gradient(circle at 50% 0%, rgba(191, 255, 239, 1) 0%, rgba(255, 255, 255, 1) 37%);
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
@@ -10,9 +12,8 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: calc(100vh - 98px);
|
flex: 1;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 示例消息卡片区域
|
// 示例消息卡片区域
|
||||||
@@ -30,12 +31,12 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 10px 16px;
|
padding: 10px 16px;
|
||||||
background: #ffffff;
|
|
||||||
border: 0.5px solid rgba(0, 0, 0, 0.08);
|
border: 0.5px solid rgba(0, 0, 0, 0.08);
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
box-shadow: 0px 4px 20px 0px rgba(0, 0, 0, 0.08);
|
box-shadow: 0px 4px 20px 0px rgba(0, 0, 0, 0.08);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
background: #ffffff;
|
||||||
|
|
||||||
// 第三个卡片(最上面)
|
// 第三个卡片(最上面)
|
||||||
&--3 {
|
&--3 {
|
||||||
@@ -163,7 +164,6 @@
|
|||||||
|
|
||||||
&__qr_image {
|
&__qr_image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__qr_placeholder {
|
&__qr_placeholder {
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ const OrderCheck = () => {
|
|||||||
|
|
||||||
//TODO: get order msg from id
|
//TODO: get order msg from id
|
||||||
const handlePay = async () => {
|
const handlePay = async () => {
|
||||||
Taro.showLoading({
|
// Taro.showLoading({
|
||||||
title: '支付中...',
|
// title: '支付中...',
|
||||||
mask: true
|
// mask: true
|
||||||
})
|
// })
|
||||||
const res = await orderService.createOrder(Number(gameId))
|
const res = await orderService.createOrder(Number(gameId))
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
const { payment_required, payment_params } = res.data
|
const { payment_required, payment_params } = res.data
|
||||||
@@ -37,7 +37,7 @@ const OrderCheck = () => {
|
|||||||
signType,
|
signType,
|
||||||
paySign,
|
paySign,
|
||||||
success: async () => {
|
success: async () => {
|
||||||
Taro.hideLoading()
|
// Taro.hideLoading()
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: '支付成功',
|
title: '支付成功',
|
||||||
icon: 'success'
|
icon: 'success'
|
||||||
@@ -48,7 +48,7 @@ const OrderCheck = () => {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
fail: () => {
|
fail: () => {
|
||||||
Taro.hideLoading()
|
// Taro.hideLoading()
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: '支付失败',
|
title: '支付失败',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
|
|||||||
@@ -56,14 +56,12 @@ const NewFollow = () => {
|
|||||||
|
|
||||||
setFollowList(mappedList);
|
setFollowList(mappedList);
|
||||||
|
|
||||||
// 获取未读关注ID并标记已读
|
// 获取所有关注者ID列表并标记已读(传入所有ID,包括已读和未读)
|
||||||
const unreadFanIds = res.list
|
const allFanIds = res.list.map((item: any) => item.id);
|
||||||
.filter((item: any) => item.is_read === 0)
|
|
||||||
.map((item: any) => item.id);
|
|
||||||
|
|
||||||
if (unreadFanIds.length > 0) {
|
if (allFanIds.length > 0) {
|
||||||
// 使用统一接口标记已读
|
// 使用统一接口标记已读,传入所有关注者ID
|
||||||
messageService.markAsRead('follow', unreadFanIds).catch(e => {
|
messageService.markAsRead('follow', allFanIds).catch(e => {
|
||||||
console.error("标记关注已读失败:", e);
|
console.error("标记关注已读失败:", e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -164,6 +162,15 @@ const NewFollow = () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
setFollowList(mappedList);
|
setFollowList(mappedList);
|
||||||
|
|
||||||
|
// 获取所有关注者ID列表并标记已读(传入所有ID)
|
||||||
|
const allFanIds = res.list.map((item: any) => item.id);
|
||||||
|
|
||||||
|
if (allFanIds.length > 0) {
|
||||||
|
messageService.markAsRead('follow', allFanIds).catch(e => {
|
||||||
|
console.error("标记关注已读失败:", e);
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 如果没有数据,设置为空数组以显示空状态
|
// 如果没有数据,设置为空数组以显示空状态
|
||||||
setFollowList([]);
|
setFollowList([]);
|
||||||
@@ -186,7 +193,7 @@ const NewFollow = () => {
|
|||||||
<View className="follow-left" onClick={() => handleUserClick(item.user_id)}>
|
<View className="follow-left" onClick={() => handleUserClick(item.user_id)}>
|
||||||
<Image
|
<Image
|
||||||
className="user-avatar" mode="aspectFill"
|
className="user-avatar" mode="aspectFill"
|
||||||
src={item.user_avatar || "https://img.yzcdn.cn/vant/cat.jpeg"}
|
src={item.user_avatar || require("@/static/userInfo/default_avatar.svg")}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -146,8 +146,22 @@
|
|||||||
.introContainer {
|
.introContainer {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
background-size: cover;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
background: radial-gradient(227.15% 100% at 50% 0%, #bfffef 0%, #fff 36.58%),
|
background: radial-gradient(227.15% 100% at 50% 0%, #bfffef 0%, #fff 36.58%),
|
||||||
#fafafa;
|
#fafafa;
|
||||||
|
z-index: -1;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.result {
|
.result {
|
||||||
.avatarWrap {
|
.avatarWrap {
|
||||||
@@ -235,6 +249,8 @@
|
|||||||
height: 52px;
|
height: 52px;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 8px 64px 0 rgba(0, 0, 0, 0.1);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
@@ -258,6 +274,16 @@
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
|
|
||||||
|
&.primary {
|
||||||
|
color: #fff;
|
||||||
|
background: #000;
|
||||||
|
|
||||||
|
.arrowImage {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,8 +389,23 @@
|
|||||||
.testContainer {
|
.testContainer {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
background: radial-gradient(227.15% 100% at 50% 0%, #bfffef 0%, #fff 36.58%),
|
background: radial-gradient(227.15% 100% at 50% 0%, #bfffef 0%, #fff 36.58%),
|
||||||
#fafafa;
|
#fafafa;
|
||||||
|
z-index: -1;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.bar {
|
.bar {
|
||||||
margin: 12px 20px 36px;
|
margin: 12px 20px 36px;
|
||||||
|
|||||||
@@ -16,10 +16,9 @@ import { useGlobalState } from "@/store/global";
|
|||||||
import { delay, getCurrentFullPath } from "@/utils";
|
import { delay, getCurrentFullPath } from "@/utils";
|
||||||
import { formatNtrpDisplay } from "@/utils/helper";
|
import { formatNtrpDisplay } from "@/utils/helper";
|
||||||
import { waitForAuthInit } from "@/utils/authInit";
|
import { waitForAuthInit } from "@/utils/authInit";
|
||||||
import httpService from "@/services/httpService";
|
// import httpService from "@/services/httpService";
|
||||||
import DetailService from "@/services/detailService";
|
import DetailService from "@/services/detailService";
|
||||||
import { base64ToTempFilePath } from "@/utils/genPoster";
|
import { OSS_BASE } from "@/config/api";
|
||||||
import { OSS_BASE_URL } from "@/config/api";
|
|
||||||
import CloseIcon from "@/static/ntrp/ntrp_close_icon.svg";
|
import CloseIcon from "@/static/ntrp/ntrp_close_icon.svg";
|
||||||
import DocCopy from "@/static/ntrp/ntrp_doc_copy.svg";
|
import DocCopy from "@/static/ntrp/ntrp_doc_copy.svg";
|
||||||
import ArrowRight from "@/static/ntrp/ntrp_arrow_right.svg";
|
import ArrowRight from "@/static/ntrp/ntrp_arrow_right.svg";
|
||||||
@@ -38,7 +37,7 @@ const sourceTypeToTextMap = new Map([
|
|||||||
|
|
||||||
function adjustRadarLabels(
|
function adjustRadarLabels(
|
||||||
source: [string, number][],
|
source: [string, number][],
|
||||||
topK: number = 4 // 默认挑前4个最长的标签保护
|
topK: number = 4, // 默认挑前4个最长的标签保护
|
||||||
): [string, number][] {
|
): [string, number][] {
|
||||||
if (source.length === 0) return source;
|
if (source.length === 0) return source;
|
||||||
|
|
||||||
@@ -170,7 +169,8 @@ function Intro() {
|
|||||||
const { setCallback } = useEvaluate();
|
const { setCallback } = useEvaluate();
|
||||||
|
|
||||||
const { last_test_result = null } = ntrpData || {};
|
const { last_test_result = null } = ntrpData || {};
|
||||||
const { ntrp_level, create_time, id } = last_test_result || {};
|
const { ntrp_level, create_time, id, level_description } =
|
||||||
|
last_test_result || {};
|
||||||
const lastTestTime = create_time
|
const lastTestTime = create_time
|
||||||
? dayjs(create_time).format("YYYY年M月D日")
|
? dayjs(create_time).format("YYYY年M月D日")
|
||||||
: "";
|
: "";
|
||||||
@@ -222,7 +222,12 @@ function Intro() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className={styles.introContainer}>
|
<View
|
||||||
|
className={styles.introContainer}
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url(${OSS_BASE}/front/ball/images/215f1ce1-be52-4a92-8250-5a4a69e7f2b3.png)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<CommonGuideBar />
|
<CommonGuideBar />
|
||||||
{ntrpData?.has_test_record ? (
|
{ntrpData?.has_test_record ? (
|
||||||
<View className={styles.result}>
|
<View className={styles.result}>
|
||||||
@@ -247,7 +252,7 @@ function Intro() {
|
|||||||
<View className={styles.tip}>
|
<View className={styles.tip}>
|
||||||
<Image
|
<Image
|
||||||
className={styles.tipImage}
|
className={styles.tipImage}
|
||||||
src={`${OSS_BASE_URL}/images/b7cb47aa-b609-4112-899f-3fde02ed2431.png`}
|
src={`${OSS_BASE}/front/ball/images/b7cb47aa-b609-4112-899f-3fde02ed2431.png`}
|
||||||
mode="aspectFit"
|
mode="aspectFit"
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@@ -266,11 +271,11 @@ function Intro() {
|
|||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className={styles.slogan}>
|
<View className={styles.slogan}>
|
||||||
<Text>变线+网前,下一步就是赢比赛!</Text>
|
<Text>{level_description}</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View className={styles.actions}>
|
<View className={styles.actions}>
|
||||||
<View className={styles.buttonWrap}>
|
<View className={classnames(styles.buttonWrap, styles.customBtn)}>
|
||||||
<Button
|
<Button
|
||||||
className={classnames(styles.button, styles.primary)}
|
className={classnames(styles.button, styles.primary)}
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -279,6 +284,12 @@ function Intro() {
|
|||||||
<Text>再次测试</Text>
|
<Text>再次测试</Text>
|
||||||
<Image className={styles.arrowImage} src={ArrowRight} />
|
<Image className={styles.arrowImage} src={ArrowRight} />
|
||||||
</Button>
|
</Button>
|
||||||
|
<View
|
||||||
|
className={classnames(styles.customBtnCover, styles.primary)}
|
||||||
|
>
|
||||||
|
<Text>再次测试</Text>
|
||||||
|
<Image className={styles.arrowImage} src={ArrowRight} />
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View className={classnames(styles.buttonWrap, styles.customBtn)}>
|
<View className={classnames(styles.buttonWrap, styles.customBtn)}>
|
||||||
<Button
|
<Button
|
||||||
@@ -299,7 +310,7 @@ function Intro() {
|
|||||||
<View className={styles.tip}>
|
<View className={styles.tip}>
|
||||||
<Image
|
<Image
|
||||||
className={styles.tipImage}
|
className={styles.tipImage}
|
||||||
src={`${OSS_BASE_URL}/images/b7cb47aa-b609-4112-899f-3fde02ed2431.png`}
|
src={`${OSS_BASE}/front/ball/images/b7cb47aa-b609-4112-899f-3fde02ed2431.png`}
|
||||||
mode="aspectFit"
|
mode="aspectFit"
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@@ -307,7 +318,7 @@ function Intro() {
|
|||||||
<View className={styles.radar}>
|
<View className={styles.radar}>
|
||||||
<Image
|
<Image
|
||||||
className={styles.radarImage}
|
className={styles.radarImage}
|
||||||
src={`${OSS_BASE_URL}/images/a2e1b639-82a9-4ab8-b767-8605556eafcb.png`}
|
src={`${OSS_BASE}/front/ball/images/a2e1b639-82a9-4ab8-b767-8605556eafcb.png`}
|
||||||
mode="aspectFit"
|
mode="aspectFit"
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@@ -369,7 +380,7 @@ function Test() {
|
|||||||
prev.map((item, pIndex) => ({
|
prev.map((item, pIndex) => ({
|
||||||
...item,
|
...item,
|
||||||
...(pIndex === index ? { choosen: i } : {}),
|
...(pIndex === index ? { choosen: i } : {}),
|
||||||
}))
|
})),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,7 +424,12 @@ function Test() {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<View className={styles.testContainer}>
|
<View
|
||||||
|
className={styles.testContainer}
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url(${OSS_BASE}/front/ball/images/215f1ce1-be52-4a92-8250-5a4a69e7f2b3.png)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<CommonGuideBar confirm title={`${index + 1} / ${questions.length}`} />
|
<CommonGuideBar confirm title={`${index + 1} / ${questions.length}`} />
|
||||||
<View className={styles.bar}>
|
<View className={styles.bar}>
|
||||||
<View
|
<View
|
||||||
@@ -506,13 +522,14 @@ function Result() {
|
|||||||
page: "other_pages/ntrp-evaluate/index",
|
page: "other_pages/ntrp-evaluate/index",
|
||||||
scene: `stage=${StageType.INTRO}`,
|
scene: `stage=${StageType.INTRO}`,
|
||||||
});
|
});
|
||||||
if (qrCodeUrlRes.code === 0 && qrCodeUrlRes.data?.qr_code_base64) {
|
setQrCodeUrl(qrCodeUrlRes.data.ossPath);
|
||||||
// 将 base64 转换为临时文件路径
|
// if (qrCodeUrlRes.code === 0 && qrCodeUrlRes.data?.qr_code_base64) {
|
||||||
const tempFilePath = await base64ToTempFilePath(
|
// // 将 base64 转换为临时文件路径
|
||||||
qrCodeUrlRes.data.qr_code_base64
|
// const tempFilePath = await base64ToTempFilePath(
|
||||||
);
|
// qrCodeUrlRes.data.qr_code_base64
|
||||||
setQrCodeUrl(tempFilePath);
|
// );
|
||||||
}
|
// setQrCodeUrl(tempFilePath);
|
||||||
|
// }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取二维码失败:", error);
|
console.error("获取二维码失败:", error);
|
||||||
}
|
}
|
||||||
@@ -522,18 +539,25 @@ function Result() {
|
|||||||
const res = await evaluateService.getTestResult({ record_id: Number(id) });
|
const res = await evaluateService.getTestResult({ record_id: Number(id) });
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
setResult(res.data);
|
setResult(res.data);
|
||||||
// delay(1000);
|
|
||||||
setRadarData(
|
const sortOrder = res.data.sort || [];
|
||||||
adjustRadarLabels(
|
const abilities = res.data.radar_data.abilities;
|
||||||
Object.entries(res.data.radar_data.abilities).map(([key, value]) => [
|
const sortedKeys = sortOrder.filter((k) => k in abilities);
|
||||||
|
const remainingKeys = Object.keys(abilities).filter(
|
||||||
|
(k) => !sortOrder.includes(k),
|
||||||
|
);
|
||||||
|
const allKeys = [...sortedKeys, ...remainingKeys];
|
||||||
|
let radarData: [string, number][] = allKeys.map((key) => [
|
||||||
key,
|
key,
|
||||||
Math.min(
|
Math.min(
|
||||||
100,
|
100,
|
||||||
Math.floor((value.current_score / value.max_score) * 100)
|
Math.floor(
|
||||||
|
(abilities[key].current_score / abilities[key].max_score) * 100,
|
||||||
),
|
),
|
||||||
])
|
),
|
||||||
)
|
]);
|
||||||
);
|
// 直接使用接口 sort 顺序,不经过 adjustRadarLabels 重新排序
|
||||||
|
setRadarData(radarData);
|
||||||
updateUserLevel(res.data.record_id, res.data.ntrp_level);
|
updateUserLevel(res.data.record_id, res.data.ntrp_level);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -571,7 +595,7 @@ function Result() {
|
|||||||
if (!userInfo?.phone) {
|
if (!userInfo?.phone) {
|
||||||
Taro.redirectTo({
|
Taro.redirectTo({
|
||||||
url: `/login_pages/index/index?redirect=${encodeURIComponent(
|
url: `/login_pages/index/index?redirect=${encodeURIComponent(
|
||||||
`/main_pages/index`
|
`/main_pages/index`,
|
||||||
)}`,
|
)}`,
|
||||||
});
|
});
|
||||||
clear();
|
clear();
|
||||||
@@ -596,11 +620,12 @@ function Result() {
|
|||||||
page: "other_pages/ntrp-evaluate/index",
|
page: "other_pages/ntrp-evaluate/index",
|
||||||
scene: `stage=${StageType.INTRO}`,
|
scene: `stage=${StageType.INTRO}`,
|
||||||
});
|
});
|
||||||
if (qrCodeUrlRes.code === 0 && qrCodeUrlRes.data?.qr_code_base64) {
|
finalQrCodeUrl = qrCodeUrlRes.data.ossPath;
|
||||||
finalQrCodeUrl = await base64ToTempFilePath(
|
// if (qrCodeUrlRes.code === 0 && qrCodeUrlRes.data?.qr_code_base64) {
|
||||||
qrCodeUrlRes.data.qr_code_base64
|
// finalQrCodeUrl = await base64ToTempFilePath(
|
||||||
);
|
// qrCodeUrlRes.data.qr_code_base64
|
||||||
}
|
// );
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用 RadarV2 的 generateFullImage 方法生成完整图片
|
// 使用 RadarV2 的 generateFullImage 方法生成完整图片
|
||||||
@@ -695,7 +720,7 @@ function Result() {
|
|||||||
<View
|
<View
|
||||||
className={styles.card}
|
className={styles.card}
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url(${OSS_BASE_URL}/images/f5b45cea-5015-41d6-aaf4-83b2e76678e1.png)`,
|
backgroundImage: `url(${OSS_BASE}/front/ball/images/f5b45cea-5015-41d6-aaf4-83b2e76678e1.png)`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View className={styles.avatarWrap}>
|
<View className={styles.avatarWrap}>
|
||||||
@@ -745,7 +770,8 @@ function Result() {
|
|||||||
{userInfo?.phone ? (
|
{userInfo?.phone ? (
|
||||||
<View className={styles.updateTip}>
|
<View className={styles.updateTip}>
|
||||||
<Text>
|
<Text>
|
||||||
你的 NTRP 水平已更新为 {formatNtrpDisplay(result?.ntrp_level || "")}{" "}
|
你的 NTRP 水平已更新为{" "}
|
||||||
|
{formatNtrpDisplay(result?.ntrp_level || "")}{" "}
|
||||||
</Text>
|
</Text>
|
||||||
<Text className={styles.grayTip}>(可在个人信息中修改)</Text>
|
<Text className={styles.grayTip}>(可在个人信息中修改)</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState, useEffect } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import { View, Text, Textarea, Image } from '@tarojs/components'
|
import { View, Text, Textarea, Image } from '@tarojs/components'
|
||||||
import Taro from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
import { ConfigProvider, Loading, Popup, Toast } from '@nutui/nutui-react-taro'
|
import { ConfigProvider, Loading, Toast } from '@nutui/nutui-react-taro'
|
||||||
import styles from './index.module.scss'
|
import styles from './index.module.scss'
|
||||||
import uploadFiles from '@/services/uploadFiles'
|
import uploadFiles from '@/services/uploadFiles'
|
||||||
import publishService from '@/services/publishService'
|
import publishService from '@/services/publishService'
|
||||||
@@ -109,7 +109,10 @@ const AiImportPopup: React.FC<AiImportPopupProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleTextChange = (e: any) => {
|
const handleTextChange = (e: any) => {
|
||||||
setText(e.detail.value)
|
const text = e.detail.value;
|
||||||
|
const maxAllowedLength = 120;
|
||||||
|
const truncatedVal = text.length > maxAllowedLength ? text.slice(0, maxAllowedLength) : text
|
||||||
|
setText(truncatedVal)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用全局键盘状态监听
|
// 使用全局键盘状态监听
|
||||||
@@ -191,14 +194,23 @@ const AiImportPopup: React.FC<AiImportPopupProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const showManualButton = uploadFailCount >= maxFailCount
|
const showManualButton = uploadFailCount >= maxFailCount
|
||||||
|
if (!visible) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 阻止弹窗内的触摸事件冒泡
|
||||||
|
const handleTouchMoveInPopup = (e) => {
|
||||||
|
if (!isKeyboardVisible) {
|
||||||
|
e.stopPropagation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popup
|
<View
|
||||||
visible={visible}
|
className={styles.aiImportPopupOverlay}
|
||||||
position="bottom"
|
>
|
||||||
round={true}
|
<View className={styles.aiImportPopupWrapper} onTouchMove={handleTouchMoveInPopup} catchMove></View>
|
||||||
closeable={false}
|
<View
|
||||||
onClose={closePopupBefore}
|
|
||||||
className={styles.aiImportPopup}
|
className={styles.aiImportPopup}
|
||||||
style={{ paddingBottom: isKeyboardVisible ? `${keyboardHeight}px` : undefined }}
|
style={{ paddingBottom: isKeyboardVisible ? `${keyboardHeight}px` : undefined }}
|
||||||
>
|
>
|
||||||
@@ -239,12 +251,21 @@ const AiImportPopup: React.FC<AiImportPopupProps> = ({
|
|||||||
|
|
||||||
{/* 图片识别按钮 */}
|
{/* 图片识别按钮 */}
|
||||||
<View className={styles.imageRecognitionContainer}>
|
<View className={styles.imageRecognitionContainer}>
|
||||||
<View className={`${styles.imageRecognitionButton} ${uploadLoading ? styles.uploadLoadingContainer : ''}`} onClick={handleImageRecognition}>
|
<View
|
||||||
{
|
className={`${styles.imageRecognitionButton} ${
|
||||||
uploadLoading ? (<Image src={images.ICON_UPLOAD_SUCCESS} className={styles.cameraIcon} />) : (<Image src={images.ICON_UPLOAD_IMG} className={styles.cameraIcon} />)
|
uploadLoading ? styles.uploadLoadingContainer : ''
|
||||||
}
|
}`}
|
||||||
|
onClick={handleImageRecognition}
|
||||||
|
>
|
||||||
|
{uploadLoading ? (
|
||||||
|
<Image src={images.ICON_UPLOAD_SUCCESS} className={styles.cameraIcon} />
|
||||||
|
) : (
|
||||||
|
<Image src={images.ICON_UPLOAD_IMG} className={styles.cameraIcon} />
|
||||||
|
)}
|
||||||
<Text className={styles.imageRecognitionText}>图片识别</Text>
|
<Text className={styles.imageRecognitionText}>图片识别</Text>
|
||||||
<Text className={styles.imageRecognitionDesc}>{uploadLoading ? '已上传 1 张图片' : '支持订场截图/小红书笔记截图等图片'}</Text>
|
<Text className={styles.imageRecognitionDesc}>
|
||||||
|
{uploadLoading ? '已上传 1 张图片' : '支持订场截图/小红书笔记截图等图片'}
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@@ -256,8 +277,7 @@ const AiImportPopup: React.FC<AiImportPopupProps> = ({
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
<View className={styles.pasteButton} onClick={handlePasteAndRecognize}>
|
<View className={styles.pasteButton} onClick={handlePasteAndRecognize}>
|
||||||
{
|
{loading ? (
|
||||||
loading ? (
|
|
||||||
<View className={styles.loadingContainer}>
|
<View className={styles.loadingContainer}>
|
||||||
<ConfigProvider theme={{ nutuiLoadingIconColor: '#fff', nutuiLoadingIconSize: '20px' }}>
|
<ConfigProvider theme={{ nutuiLoadingIconColor: '#fff', nutuiLoadingIconSize: '20px' }}>
|
||||||
<Loading type="circular" />
|
<Loading type="circular" />
|
||||||
@@ -269,13 +289,13 @@ const AiImportPopup: React.FC<AiImportPopupProps> = ({
|
|||||||
<Image src={images.ICON_COPY} className={styles.clipboardIcon} />
|
<Image src={images.ICON_COPY} className={styles.clipboardIcon} />
|
||||||
<Text className={styles.pasteButtonText}>粘贴并识别</Text>
|
<Text className={styles.pasteButtonText}>粘贴并识别</Text>
|
||||||
</>
|
</>
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<Toast id="toast" />
|
<Toast id="toast" />
|
||||||
</Popup>
|
</View>
|
||||||
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,34 @@
|
|||||||
@use '~@/scss/themeColor.scss' as theme;
|
@use '~@/scss/themeColor.scss' as theme;
|
||||||
|
|
||||||
.aiImportPopup {
|
.aiImportPopupOverlay {
|
||||||
background-color: #fff;
|
position: fixed;
|
||||||
&:global(.nut-popup-bottom.nut-popup-round) {
|
top: 0;
|
||||||
border-radius: 20px 20px 0 0!important;
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 9998;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.aiImportPopupWrapper {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 9998;
|
||||||
|
}
|
||||||
|
.aiImportPopup {
|
||||||
|
width: 100%;
|
||||||
|
background-color:#fafafa;
|
||||||
|
border-radius: 16px 16px 0 0;
|
||||||
|
position: relative;
|
||||||
|
z-index: 9999;
|
||||||
.popupContent {
|
.popupContent {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: #fff;
|
|
||||||
border-radius: 16px 16px 0 0;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding-bottom: env(safe-area-inset-bottom);
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
box-sizing: border-box;
|
||||||
// 搜索区域
|
// 搜索区域
|
||||||
.search-section {
|
.search-section {
|
||||||
background: #f5f5f5;
|
background: #f5f5f5;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { View, Text, Input, ScrollView, Image } from '@tarojs/components'
|
|||||||
import Taro from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
import { Loading } from '@nutui/nutui-react-taro'
|
import { Loading } from '@nutui/nutui-react-taro'
|
||||||
import StadiumDetail, { StadiumDetailRef } from './StadiumDetail'
|
import StadiumDetail, { StadiumDetailRef } from './StadiumDetail'
|
||||||
import { CommonPopup } from '../../../../components'
|
import { CommonPopup, CustomPopup } from '../../../../components'
|
||||||
import { getLocation } from '@/utils/locationUtils'
|
import { getLocation } from '@/utils/locationUtils'
|
||||||
import PublishService from '@/services/publishService'
|
import PublishService from '@/services/publishService'
|
||||||
import images from '@/config/images'
|
import images from '@/config/images'
|
||||||
@@ -106,8 +106,15 @@ const SelectStadium: React.FC<SelectStadiumProps> = ({
|
|||||||
})
|
})
|
||||||
setShowDetail(true)
|
setShowDetail(true)
|
||||||
},
|
},
|
||||||
fail: (err) => {
|
fail: (err: { errMsg: string }) => {
|
||||||
console.error('选择位置失败:', err)
|
console.error('选择位置失败:', err)
|
||||||
|
const { errMsg } = err || {};
|
||||||
|
if (!errMsg.includes('fail cancel')) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: errMsg,
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -181,24 +188,20 @@ const SelectStadium: React.FC<SelectStadiumProps> = ({
|
|||||||
// 如果显示详情页面
|
// 如果显示详情页面
|
||||||
if (showDetail && selectedStadium) {
|
if (showDetail && selectedStadium) {
|
||||||
return (
|
return (
|
||||||
<CommonPopup
|
<CustomPopup
|
||||||
visible={visible}
|
visible={visible}
|
||||||
onClose={handleCancel}
|
onClose={handleCancel}
|
||||||
cancelText="返回"
|
cancelText="返回"
|
||||||
confirmText="确认"
|
confirmText="确认"
|
||||||
className="select-stadium-popup"
|
|
||||||
onCancel={handleDetailCancel}
|
onCancel={handleDetailCancel}
|
||||||
onConfirm={handleConfirm}
|
onConfirm={handleConfirm}
|
||||||
position="bottom"
|
|
||||||
//style={{ paddingBottom: keyboardVisible ? `20px` : undefined }}
|
|
||||||
round
|
|
||||||
>
|
>
|
||||||
|
{/* 内容区域 */}
|
||||||
<StadiumDetail
|
<StadiumDetail
|
||||||
ref={stadiumDetailRef}
|
ref={stadiumDetailRef}
|
||||||
stadium={selectedStadium}
|
stadium={selectedStadium}
|
||||||
//onAnyInput={handleAnyInput}
|
|
||||||
/>
|
/>
|
||||||
</CommonPopup>
|
</CustomPopup>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
.stadium-detail-scroll{
|
.stadium-detail-scroll{
|
||||||
height:60vh;
|
max-height:60vh;
|
||||||
}
|
}
|
||||||
// 已选球场
|
// 已选球场
|
||||||
// 场馆列表
|
// 场馆列表
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import images from '@/config/images'
|
|||||||
import TextareaTag from '@/components/TextareaTag'
|
import TextareaTag from '@/components/TextareaTag'
|
||||||
// import CoverImageUpload, { type CoverImage } from '@/components/ImageUpload'
|
// import CoverImageUpload, { type CoverImage } from '@/components/ImageUpload'
|
||||||
import UploadCover, { type CoverImageValue } from '@/components/UploadCover'
|
import UploadCover, { type CoverImageValue } from '@/components/UploadCover'
|
||||||
|
import { useKeyboardHeight } from '@/store/keyboardStore'
|
||||||
import { useDictionaryActions } from '@/store/dictionaryStore'
|
import { useDictionaryActions } from '@/store/dictionaryStore'
|
||||||
|
|
||||||
import './StadiumDetail.scss'
|
import './StadiumDetail.scss'
|
||||||
@@ -69,11 +70,16 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
|
|||||||
stadium,
|
stadium,
|
||||||
onAnyInput
|
onAnyInput
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
const [openPicker, setOpenPicker] = useState(false);
|
const [openPicker, setOpenPicker] = useState(false); //为了解决上传图片时按钮样式问题
|
||||||
|
const [scrollTop, setScrollTop] = useState(0);
|
||||||
const { getDictionaryValue } = useDictionaryActions()
|
const { getDictionaryValue } = useDictionaryActions()
|
||||||
const court_type = getDictionaryValue('court_type') || []
|
const court_type = getDictionaryValue('court_type') || []
|
||||||
const court_surface = getDictionaryValue('court_surface') || []
|
const court_surface = getDictionaryValue('court_surface') || []
|
||||||
const supplementary_information = getDictionaryValue('supplementary_information') || []
|
const supplementary_information = getDictionaryValue('supplementary_information') || []
|
||||||
|
|
||||||
|
// 使用全局键盘状态
|
||||||
|
const { keyboardHeight, isKeyboardVisible, addListener, initializeKeyboardListener } = useKeyboardHeight()
|
||||||
|
|
||||||
const stadiumInfo = [
|
const stadiumInfo = [
|
||||||
{
|
{
|
||||||
label: '场地类型',
|
label: '场地类型',
|
||||||
@@ -145,12 +151,15 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
|
|||||||
istance: null
|
istance: null
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
fail: (err) => {
|
fail: (err: { errMsg: string }) => {
|
||||||
console.error('选择位置失败:', err)
|
console.error('选择位置失败:', err)
|
||||||
|
const { errMsg } = err || {};
|
||||||
|
if (!errMsg.includes('fail cancel')) {
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: '位置选择失败',
|
title: errMsg,
|
||||||
icon: 'error'
|
icon: "none",
|
||||||
})
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -167,14 +176,49 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
const changePicker = (value) => {
|
// 使用全局键盘状态监听
|
||||||
setOpenPicker(value)
|
useEffect(() => {
|
||||||
|
// 初始化全局键盘监听器
|
||||||
|
initializeKeyboardListener()
|
||||||
|
|
||||||
|
// 添加本地监听器
|
||||||
|
const removeListener = addListener((height, visible) => {
|
||||||
|
console.log('AiImportPopup 收到键盘变化:', height, visible)
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
removeListener()
|
||||||
|
}
|
||||||
|
}, [initializeKeyboardListener, addListener])
|
||||||
|
|
||||||
|
const changeTextarea = (value: boolean) => {
|
||||||
|
if (value) {
|
||||||
|
// 先滚动到底部
|
||||||
|
setScrollTop(140);
|
||||||
|
// 使用 setTimeout 确保滚动后再更新 openPicker
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const changePicker = (value:boolean) => {
|
||||||
|
setOpenPicker(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(stadium,'stadiumstadium');
|
console.log(stadium,'stadiumstadium');
|
||||||
|
|
||||||
|
// 计算滚动区域的最大高度
|
||||||
|
const scrollMaxHeight = isKeyboardVisible
|
||||||
|
? `calc(100vh - ${keyboardHeight+40}px)`
|
||||||
|
: '60vh'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='stadium-detail'>
|
<View className='stadium-detail'>
|
||||||
<ScrollView className='stadium-detail-scroll' refresherBackground="#FAFAFA" scrollY={!openPicker}>
|
<ScrollView
|
||||||
|
className='stadium-detail-scroll'
|
||||||
|
refresherBackground="#FAFAFA"
|
||||||
|
scrollY={!openPicker}
|
||||||
|
scrollTop={scrollTop}
|
||||||
|
style={{ maxHeight: scrollMaxHeight }}
|
||||||
|
>
|
||||||
{/* 已选球场 */}
|
{/* 已选球场 */}
|
||||||
<View
|
<View
|
||||||
className={`stadium-item`}
|
className={`stadium-item`}
|
||||||
@@ -217,9 +261,14 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
|
|||||||
<View className='textarea-tag-container'>
|
<View className='textarea-tag-container'>
|
||||||
<TextareaTag
|
<TextareaTag
|
||||||
value={formData[item.prop]}
|
value={formData[item.prop]}
|
||||||
onChange={(value) => updateFormData(item.prop, value)}
|
onChange={(value) => {
|
||||||
onBlur={() => changePicker(false)}
|
updateFormData(item.prop, value)
|
||||||
onFocus={() => changePicker(true)}
|
}}
|
||||||
|
// onBlur={() => {
|
||||||
|
// }}
|
||||||
|
onFocus={() => {
|
||||||
|
changeTextarea(true)
|
||||||
|
}}
|
||||||
placeholder='有其他场地信息可备注'
|
placeholder='有其他场地信息可备注'
|
||||||
options={(item.options || []).map((o) => ({ label: o, value: o }))}
|
options={(item.options || []).map((o) => ({ label: o, value: o }))}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
export default definePageConfig({
|
export default definePageConfig({
|
||||||
navigationStyle: 'custom'
|
navigationStyle: 'custom',
|
||||||
|
// 禁止原生页面滚动,改用内部自定义滚动容器,避免顶部/底部回弹后中间无法继续滚动的问题
|
||||||
|
disableScroll: true,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -78,13 +78,13 @@ const PublishBall: React.FC = () => {
|
|||||||
} = useKeyboardHeight();
|
} = useKeyboardHeight();
|
||||||
// 获取页面参数并设置导航标题
|
// 获取页面参数并设置导航标题
|
||||||
const [optionsConfig, setOptionsConfig] = useState<FormFieldConfig[]>(
|
const [optionsConfig, setOptionsConfig] = useState<FormFieldConfig[]>(
|
||||||
publishBallFormSchema
|
publishBallFormSchema,
|
||||||
);
|
);
|
||||||
console.log(userInfo, "userInfo");
|
|
||||||
const [formData, setFormData] = useState<PublishBallFormData[]>([
|
const [formData, setFormData] = useState<PublishBallFormData[]>([
|
||||||
defaultFormData,
|
defaultFormData,
|
||||||
]);
|
]);
|
||||||
const [checked, setChecked] = useState(true);
|
const [checked, setChecked] = useState(true);
|
||||||
|
const [publishLoading, setPublishLoading] = useState(false);
|
||||||
const [titleBar, setTitleBar] = useState("发布球局");
|
const [titleBar, setTitleBar] = useState("发布球局");
|
||||||
// 控制是否响应全局键盘(由具体输入框 focus/blur 控制)
|
// 控制是否响应全局键盘(由具体输入框 focus/blur 控制)
|
||||||
const [shouldReactToKeyboard, setShouldReactToKeyboard] = useState(false);
|
const [shouldReactToKeyboard, setShouldReactToKeyboard] = useState(false);
|
||||||
@@ -102,13 +102,11 @@ const PublishBall: React.FC = () => {
|
|||||||
const updateFormData = (
|
const updateFormData = (
|
||||||
key: keyof PublishBallFormData,
|
key: keyof PublishBallFormData,
|
||||||
value: any,
|
value: any,
|
||||||
index: number
|
index: number,
|
||||||
) => {
|
) => {
|
||||||
console.log(key, value, index, "key, value, index");
|
|
||||||
setFormData((prev) => {
|
setFormData((prev) => {
|
||||||
const newData = [...prev];
|
const newData = [...prev];
|
||||||
newData[index] = { ...newData[index], [key]: value };
|
newData[index] = { ...newData[index], [key]: value };
|
||||||
console.log(newData, "newData");
|
|
||||||
return newData;
|
return newData;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -185,7 +183,7 @@ const PublishBall: React.FC = () => {
|
|||||||
const confirmDelete = () => {
|
const confirmDelete = () => {
|
||||||
if (deleteConfirm.index >= 0) {
|
if (deleteConfirm.index >= 0) {
|
||||||
setFormData((prev) =>
|
setFormData((prev) =>
|
||||||
prev.filter((_, index) => index !== deleteConfirm.index)
|
prev.filter((_, index) => index !== deleteConfirm.index),
|
||||||
);
|
);
|
||||||
closeDeleteConfirm();
|
closeDeleteConfirm();
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
@@ -197,7 +195,7 @@ const PublishBall: React.FC = () => {
|
|||||||
|
|
||||||
const validateFormData = (
|
const validateFormData = (
|
||||||
formData: PublishBallFormData,
|
formData: PublishBallFormData,
|
||||||
isOnSubmit: boolean = false
|
isOnSubmit: boolean = false,
|
||||||
) => {
|
) => {
|
||||||
const {
|
const {
|
||||||
activityInfo,
|
activityInfo,
|
||||||
@@ -206,7 +204,7 @@ const PublishBall: React.FC = () => {
|
|||||||
image_list,
|
image_list,
|
||||||
players,
|
players,
|
||||||
current_players,
|
current_players,
|
||||||
descriptionInfo
|
descriptionInfo,
|
||||||
} = formData;
|
} = formData;
|
||||||
const { play_type, price, location_name } = activityInfo;
|
const { play_type, price, location_name } = activityInfo;
|
||||||
const { description } = descriptionInfo;
|
const { description } = descriptionInfo;
|
||||||
@@ -224,7 +222,7 @@ const PublishBall: React.FC = () => {
|
|||||||
// 判断图片是否上传完成
|
// 判断图片是否上传完成
|
||||||
if (image_list?.length > 0) {
|
if (image_list?.length > 0) {
|
||||||
const uploadInProgress = image_list.some((item) =>
|
const uploadInProgress = image_list.some((item) =>
|
||||||
item.url.startsWith("http://tmp/")
|
item?.url?.startsWith?.("http://tmp/"),
|
||||||
);
|
);
|
||||||
if (uploadInProgress) {
|
if (uploadInProgress) {
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
@@ -367,14 +365,14 @@ const PublishBall: React.FC = () => {
|
|||||||
// 提交表单
|
// 提交表单
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
// 基础验证
|
// 基础验证
|
||||||
console.log(formData, "formData");
|
|
||||||
const params = getParams();
|
const params = getParams();
|
||||||
const { republish } = params || {};
|
const { republish } = params || {};
|
||||||
if (activityType === "individual") {
|
if (activityType === "individual") {
|
||||||
const isValid = validateFormData(formData[0]);
|
const isValid = validateFormData(formData[0]);
|
||||||
if (!isValid) {
|
if (!isValid || publishLoading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setPublishLoading(true);
|
||||||
const {
|
const {
|
||||||
activityInfo,
|
activityInfo,
|
||||||
descriptionInfo,
|
descriptionInfo,
|
||||||
@@ -435,13 +433,15 @@ const PublishBall: React.FC = () => {
|
|||||||
title: res.message,
|
title: res.message,
|
||||||
icon: "none",
|
icon: "none",
|
||||||
});
|
});
|
||||||
|
setPublishLoading(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (activityType === "group") {
|
if (activityType === "group") {
|
||||||
const isValid = formData.every((item) => validateFormData(item));
|
const isValid = formData.every((item) => validateFormData(item));
|
||||||
if (!isValid) {
|
if (!isValid || publishLoading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setPublishLoading(true);
|
||||||
if (checkAdjacentDataSame(formData)) {
|
if (checkAdjacentDataSame(formData)) {
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: "信息不可与前序场完全一致",
|
title: "信息不可与前序场完全一致",
|
||||||
@@ -505,13 +505,14 @@ const PublishBall: React.FC = () => {
|
|||||||
title: res.message,
|
title: res.message,
|
||||||
icon: "none",
|
icon: "none",
|
||||||
});
|
});
|
||||||
|
setPublishLoading(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const mergeWithDefault = (
|
const mergeWithDefault = (
|
||||||
data: any,
|
data: any,
|
||||||
isDetail: boolean = false
|
isDetail: boolean = false,
|
||||||
): PublishBallFormData => {
|
): PublishBallFormData => {
|
||||||
// ai导入与详情数据处理
|
// ai导入与详情数据处理
|
||||||
const {
|
const {
|
||||||
@@ -736,7 +737,6 @@ const PublishBall: React.FC = () => {
|
|||||||
} else {
|
} else {
|
||||||
setIsSubmitDisabled(false);
|
setIsSubmitDisabled(false);
|
||||||
}
|
}
|
||||||
console.log(formData, "formData");
|
|
||||||
}, [formData]);
|
}, [formData]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -749,9 +749,8 @@ const PublishBall: React.FC = () => {
|
|||||||
initializeKeyboardListener();
|
initializeKeyboardListener();
|
||||||
|
|
||||||
// 添加本地监听器
|
// 添加本地监听器
|
||||||
const removeListener = addListener((height, visible) => {
|
const removeListener = addListener(() => {
|
||||||
console.log("PublishBall 收到键盘变化:", height, visible);
|
// 布局是否响应交由 shouldReactToKeyboard 决定
|
||||||
// 这里只记录或用于其他逻辑,布局是否响应交由 shouldReactToKeyboard 决定
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@@ -784,6 +783,7 @@ const PublishBall: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<GeneralNavbar
|
<GeneralNavbar
|
||||||
title={titleBar}
|
title={titleBar}
|
||||||
|
backgroundColor={'#FAFAFA'}
|
||||||
className={styles["publish-ball-navbar"]}
|
className={styles["publish-ball-navbar"]}
|
||||||
/>
|
/>
|
||||||
<View
|
<View
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ class CommonApiService {
|
|||||||
data: results.map(result => result.data)
|
data: results.map(result => result.data)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error
|
|
||||||
} finally {
|
} finally {
|
||||||
Taro.hideLoading()
|
Taro.hideLoading()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,14 +158,19 @@ class GameDetailService {
|
|||||||
async getQrCodeUrl(req: { page: string, scene: string }): Promise<ApiResponse<{
|
async getQrCodeUrl(req: { page: string, scene: string }): Promise<ApiResponse<{
|
||||||
qr_code_base64: string,
|
qr_code_base64: string,
|
||||||
image_size: number,
|
image_size: number,
|
||||||
|
ossPath: string,
|
||||||
page: string,
|
page: string,
|
||||||
scene: string,
|
scene: string,
|
||||||
width: number
|
width: number
|
||||||
}>> {
|
}>> {
|
||||||
return httpService.post('/user/generate_qrcode', req, {
|
return httpService.post('/user/generate_qrcode', req, {
|
||||||
showLoading: false
|
showLoading: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getLinkUrl(req: { path: string, query: string }): Promise<ApiResponse<{ url_link: string, path: string, query: string }>> {
|
||||||
|
return httpService.post('/user/generate_url_link', req, { showLoading: false })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导出认证服务实例
|
// 导出认证服务实例
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ export interface TestResultData {
|
|||||||
level_img?: string; // 等级图片URL
|
level_img?: string; // 等级图片URL
|
||||||
radar_data: RadarData;
|
radar_data: RadarData;
|
||||||
answers: Answer[];
|
answers: Answer[];
|
||||||
|
sort?: string[]; // 雷达图能力项排序,如 ["正手球质", "正手控制", ...]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 单条测试记录
|
// 单条测试记录
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ class HttpService {
|
|||||||
|
|
||||||
// 隐藏loading(支持多个并发请求)
|
// 隐藏loading(支持多个并发请求)
|
||||||
private hideLoading(): void {
|
private hideLoading(): void {
|
||||||
|
try {
|
||||||
this.loadingCount = Math.max(0, this.loadingCount - 1)
|
this.loadingCount = Math.max(0, this.loadingCount - 1)
|
||||||
|
|
||||||
// 只有所有请求都完成时才隐藏loading
|
// 只有所有请求都完成时才隐藏loading
|
||||||
@@ -146,6 +147,12 @@ class HttpService {
|
|||||||
this.hideLoadingTimer = null
|
this.hideLoadingTimer = null
|
||||||
}, 800)
|
}, 800)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.warn(e)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理响应
|
// 处理响应
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ export const getCityQrCode = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取行政区列表
|
// 获取行政区列表
|
||||||
export const getDistricts = async (params: { country: string; state: string }) => {
|
export const getDistricts = async (params: { province: string; city: string }) => {
|
||||||
try {
|
try {
|
||||||
// 调用HTTP服务获取行政区列表
|
// 调用HTTP服务获取行政区列表
|
||||||
return httpService.post('/cities/cities', params)
|
return httpService.post('/cities/cities', params)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user