Compare commits
54 Commits
feature/ju
...
feat/liuji
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
2
.env.dev_local
Normal file
2
.env.dev_local
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
APP_ENV=dev_local
|
||||||
|
TARO_APP_ID=wx815b533167eb7b53
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -8,4 +8,4 @@ node_modules/
|
|||||||
src/config/env.ts
|
src/config/env.ts
|
||||||
.vscode
|
.vscode
|
||||||
*.http
|
*.http
|
||||||
env.ts
|
|
||||||
|
|||||||
@@ -149,3 +149,7 @@ src/
|
|||||||
## License
|
## License
|
||||||
|
|
||||||
MIT
|
MIT
|
||||||
|
|
||||||
|
"appid": "wx915ecf6c01bea4ec",
|
||||||
|
|
||||||
|
"appid": "wx815b533167eb7b53",
|
||||||
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];
|
||||||
|
}
|
||||||
@@ -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: '/',
|
||||||
|
|||||||
36
package.json
36
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 ",
|
"dev:local": "npm run dev:weapp:dev_local",
|
||||||
"build:weapp": "taro build --type weapp --mode production",
|
"dev:weapp": "node scripts/sync-project-config.js dev && taro build --type weapp --mode dev --watch",
|
||||||
"build:swan": "taro build --type swan",
|
"dev:weapp:dev_local": "node scripts/sync-project-config.js dev_local && taro build --type weapp --mode dev_local --watch",
|
||||||
"build:alipay": "taro build --type alipay",
|
"build": "npm run build:weapp",
|
||||||
"build:tt": "taro build --type tt",
|
"build:weapp": "node scripts/sync-project-config.js pr && taro build --type weapp --mode pr",
|
||||||
"build:h5": "taro build --type h5",
|
"build:sit": "node scripts/sync-project-config.js sit && taro build --type weapp --mode sit",
|
||||||
"build:rn": "taro build --type rn",
|
"build:pr": "node scripts/sync-project-config.js pr && taro build --type weapp --mode pr",
|
||||||
"build:qq": "taro build --type qq",
|
"dev:h5": "npm run build:h5 -- --watch"
|
||||||
"build:jd": "taro build --type jd",
|
|
||||||
"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",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"miniprogramRoot": "dist/",
|
"miniprogramRoot": "dist/",
|
||||||
"projectname": "playBallTogether",
|
"projectname": "playBallTogether",
|
||||||
"description": "playBallTogether",
|
"description": "playBallTogether",
|
||||||
"appid": "wx915ecf6c01bea4ec",
|
"appid": "wx815b533167eb7b53",
|
||||||
"setting": {
|
"setting": {
|
||||||
"urlCheck": true,
|
"urlCheck": true,
|
||||||
"es6": true,
|
"es6": true,
|
||||||
|
|||||||
@@ -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})`);
|
||||||
@@ -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://youchang2026.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,
|
||||||
};
|
};
|
||||||
|
|||||||
208
src/components/CustomPopup/CustomPopup.tsx
Normal file
208
src/components/CustomPopup/CustomPopup.tsx
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
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 } = useKeyboardHeight()
|
||||||
|
|
||||||
|
// 使用全局键盘状态监听
|
||||||
|
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,7 +186,7 @@ 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;
|
||||||
@@ -207,7 +207,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 && !inTwoHours && !hasOtherParticiappants && (
|
||||||
<View className={styles.button} onClick={handleEditGame}>
|
<View className={styles.button} onClick={handleEditGame}>
|
||||||
编辑活动
|
编辑活动
|
||||||
</View>
|
</View>
|
||||||
@@ -217,12 +217,12 @@ export default forwardRef(function GameManagePopup(props, ref) {
|
|||||||
重新发布
|
重新发布
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
{!inTwoHours && !hasOtherParticiappants && (
|
{!finished && !inTwoHours && !hasOtherParticiappants && (
|
||||||
<View className={styles.button} onClick={handleCancelGame}>
|
<View className={styles.button} onClick={handleCancelGame}>
|
||||||
取消活动
|
取消活动
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
{hasJoin && (
|
{!finished && hasJoin && (
|
||||||
<View className={styles.button} onClick={handleQuitGame}>
|
<View className={styles.button} onClick={handleQuitGame}>
|
||||||
退出活动
|
退出活动
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -105,9 +105,9 @@ 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 => {
|
const should_show_location_dialog = (): boolean => {
|
||||||
@@ -192,7 +192,7 @@ const HomeNavbar = (props: IProps) => {
|
|||||||
} else if (detectedLocation) {
|
} else if (detectedLocation) {
|
||||||
// 只有在完全没有缓存的情况下,才使用用户详情中的位置信息
|
// 只有在完全没有缓存的情况下,才使用用户详情中的位置信息
|
||||||
console.log("[HomeNavbar] 没有缓存,使用用户详情中的位置信息:", 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);
|
||||||
@@ -266,7 +266,7 @@ 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);
|
||||||
@@ -481,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;
|
||||||
|
|||||||
@@ -45,7 +45,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={require("@/static/emptyStatus/publish-empty-card.png")}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -127,10 +127,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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ const PublishMenu: React.FC<PublishMenuProps> = (props) => {
|
|||||||
};
|
};
|
||||||
const handleMenuItemClick = (type: "individual" | "group" | "ai") => {
|
const handleMenuItemClick = (type: "individual" | "group" | "ai") => {
|
||||||
const [_, address] = area;
|
const [_, address] = area;
|
||||||
if (address !== '上海') {
|
if (address !== '上海市') {
|
||||||
(Taro as any).showModal({
|
(Taro as any).showModal({
|
||||||
title: '提示',
|
title: '提示',
|
||||||
content: '仅上海地区开放,您可加入社群或切换城市',
|
content: '仅上海地区开放,您可加入社群或切换城市',
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ 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 docCopyPng from "@/static/ntrp/ntrp_doc_copy.png";
|
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)- 在头像右侧
|
||||||
@@ -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')
|
||||||
|
|
||||||
|
|||||||
@@ -79,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}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import envConfig from './env'// API配置
|
import envConfig from './env'// API配置
|
||||||
|
|
||||||
// OSS 基础路径配置
|
// OSS 配置:仅域名,调用处拼接 /front/ball 及后续路径
|
||||||
export const OSS_BASE_URL = 'https://youchang2026.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();
|
||||||
@@ -70,4 +70,5 @@ export default {
|
|||||||
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'),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ 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 {
|
||||||
@@ -28,6 +29,7 @@ const ListContainer = (props) => {
|
|||||||
collapse = false,
|
collapse = false,
|
||||||
defaultShowNum,
|
defaultShowNum,
|
||||||
evaluateFlag,
|
evaluateFlag,
|
||||||
|
enableHomeCards = false, // 仅首页需要 banner 和 NTRP 测评卡片
|
||||||
listLoadErrorWrapperHeight,
|
listLoadErrorWrapperHeight,
|
||||||
listLoadErrorWidth,
|
listLoadErrorWidth,
|
||||||
listLoadErrorHeight,
|
listLoadErrorHeight,
|
||||||
@@ -44,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) {
|
||||||
@@ -93,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();
|
||||||
// 然后再获取用户信息
|
// 然后再获取用户信息
|
||||||
@@ -111,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;
|
||||||
@@ -130,23 +132,33 @@ const ListContainer = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 对于没有ntrp等级的用户每个月展示一次, 插在第二个位置后面
|
// 插入 banner 卡片
|
||||||
function insertEvaluateCard(list) {
|
function insertBannerCard(list) {
|
||||||
if (!evaluateFlag)
|
if (!bannerListImage) return list;
|
||||||
return showNumber !== undefined ? list.slice(0, showNumber) : list;
|
if (!list || !Array.isArray(list)) return list ?? [];
|
||||||
if (!list || list.length === 0) {
|
return [
|
||||||
return list;
|
...list.slice(0, Number(bannerListIndex)),
|
||||||
}
|
{ type: "banner", banner_image_url: bannerListImage, banner_detail_url: bannerDetailImage },
|
||||||
// 如果最近一个月有测试记录,则不插入 card
|
...list.slice(Number(bannerListIndex))
|
||||||
if (hasTestInLastMonth) {
|
];
|
||||||
return showNumber !== undefined ? list.slice(0, showNumber) : list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (list.length <= 2) {
|
// 对于没有ntrp等级的用户每个月展示一次, 插在第二个位置后面
|
||||||
return [...list, { type: "evaluateCard" }];
|
// insertBannerCard 需在最后统一执行,否则前面分支直接 return 时 banner 不会被插入
|
||||||
}
|
function insertEvaluateCard(list) {
|
||||||
|
let result: any[];
|
||||||
|
|
||||||
|
if (!evaluateFlag) {
|
||||||
|
result = showNumber !== undefined ? list.slice(0, showNumber) : list;
|
||||||
|
} else if (!list || list.length === 0) {
|
||||||
|
result = list;
|
||||||
|
} else if (hasTestInLastMonth) {
|
||||||
|
result = showNumber !== undefined ? list.slice(0, showNumber) : list;
|
||||||
|
} else if (list.length <= 2) {
|
||||||
|
result = [...list, { type: "evaluateCard" }];
|
||||||
|
} else {
|
||||||
const [item1, item2, ...rest] = list;
|
const [item1, item2, ...rest] = list;
|
||||||
return [
|
result = [
|
||||||
item1,
|
item1,
|
||||||
item2,
|
item2,
|
||||||
{ type: "evaluateCard" },
|
{ type: "evaluateCard" },
|
||||||
@@ -154,27 +166,22 @@ const ListContainer = (props) => {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 卡片
|
// 渲染 banner 卡片
|
||||||
const renderBanner = (item, index) => {
|
const renderBanner = (item, index) => {
|
||||||
if (!item?.banner_image_url) return null;
|
if (!item?.banner_image_url) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
key={item.id || `banner-${index}`}
|
key={item.id || `banner-${index}`}
|
||||||
style={{
|
|
||||||
maxHeight: "122px",
|
|
||||||
overflow: "hidden",
|
|
||||||
borderRadius: "12px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
src={item.banner_image_url}
|
|
||||||
mode="widthFix"
|
|
||||||
style={{ width: "100%", display: "block", maxHeight: "122px" }}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const target = item.banner_detail_url;
|
const target = item.banner_detail_url;
|
||||||
if (target) {
|
if (target) {
|
||||||
@@ -183,7 +190,16 @@ const ListContainer = (props) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
style={{
|
||||||
|
height: "100px",
|
||||||
|
overflow: "hidden",
|
||||||
|
borderRadius: "12px",
|
||||||
|
backgroundImage: `url(${item.banner_image_url})`,
|
||||||
|
backgroundSize: "cover",
|
||||||
|
backgroundPosition: "center",
|
||||||
|
backgroundRepeat: "no-repeat",
|
||||||
|
}}
|
||||||
|
>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -211,12 +227,12 @@ const ListContainer = (props) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{memoizedList.map((match, index) => {
|
{memoizedList.map((match, index) => {
|
||||||
if (match.type === "banner") {
|
if (enableHomeCards && match?.type === "banner") {
|
||||||
return renderBanner(match, index);
|
return renderBanner(match, index);
|
||||||
}
|
}
|
||||||
if (match.type === "evaluateCard") {
|
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;
|
||||||
@@ -110,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());
|
||||||
@@ -138,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,
|
||||||
)}`,
|
)}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -153,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() {
|
||||||
@@ -180,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 } {
|
||||||
@@ -259,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}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@@ -296,10 +296,11 @@ 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 leftSubstituteCount =
|
||||||
|
(max_substitute_players || 0) - (substitute_count || 0);
|
||||||
const showSubstituteApplicationEntry =
|
const showSubstituteApplicationEntry =
|
||||||
[can_pay, can_join, is_substituting, waiting_start].every(
|
[can_pay, can_join, is_substituting, waiting_start].every(
|
||||||
(item) => !item
|
(item) => !item,
|
||||||
) &&
|
) &&
|
||||||
can_substitute &&
|
can_substitute &&
|
||||||
dayjs(start_time).isAfter(dayjs());
|
dayjs(start_time).isAfter(dayjs());
|
||||||
@@ -336,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
|
||||||
>
|
>
|
||||||
@@ -377,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}
|
||||||
@@ -400,12 +401,17 @@ export default function Participants(props) {
|
|||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
{/* 候补区域 */}
|
{/* 候补区域 */}
|
||||||
{max_substitute_players > 0 && (substitute_count > 0 || showSubstituteApplicationEntry) && (
|
{max_substitute_players > 0 &&
|
||||||
|
(substitute_count > 0 || showSubstituteApplicationEntry) && (
|
||||||
<View className={styles["detail-page-content-participants"]}>
|
<View className={styles["detail-page-content-participants"]}>
|
||||||
<View className={styles["participants-title"]}>
|
<View className={styles["participants-title"]}>
|
||||||
<Text>候补</Text>
|
<Text>候补</Text>
|
||||||
<Text>·</Text>
|
<Text>·</Text>
|
||||||
<Text>{leftSubstituteCount > 0 ? `剩余空位 ${leftSubstituteCount}` : "已满员"}</Text>
|
<Text>
|
||||||
|
{leftSubstituteCount > 0
|
||||||
|
? `剩余空位 ${leftSubstituteCount}`
|
||||||
|
: "已满员"}
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className={styles["participants-list"]}>
|
<View className={styles["participants-list"]}>
|
||||||
{/* 候补申请入口 */}
|
{/* 候补申请入口 */}
|
||||||
@@ -420,7 +426,9 @@ export default function Participants(props) {
|
|||||||
className={styles["participants-list-application-icon"]}
|
className={styles["participants-list-application-icon"]}
|
||||||
src={img.ICON_DETAIL_APPLICATION_ADD}
|
src={img.ICON_DETAIL_APPLICATION_ADD}
|
||||||
/>
|
/>
|
||||||
<Text className={styles["participants-list-application-text"]}>
|
<Text
|
||||||
|
className={styles["participants-list-application-text"]}
|
||||||
|
>
|
||||||
申请候补
|
申请候补
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
@@ -430,7 +438,7 @@ export default function Participants(props) {
|
|||||||
refresherBackground="#FAFAFA"
|
refresherBackground="#FAFAFA"
|
||||||
className={classnames(
|
className={classnames(
|
||||||
styles["participants-list-scroll"],
|
styles["participants-list-scroll"],
|
||||||
showSubstituteApplicationEntry ? styles.withApplication : ""
|
showSubstituteApplicationEntry ? styles.withApplication : "",
|
||||||
)}
|
)}
|
||||||
scrollX
|
scrollX
|
||||||
>
|
>
|
||||||
@@ -438,7 +446,8 @@ export default function Participants(props) {
|
|||||||
className={styles["participants-list-scroll-content"]}
|
className={styles["participants-list-scroll-content"]}
|
||||||
style={{
|
style={{
|
||||||
width: `${
|
width: `${
|
||||||
Math.max(substitute_members.length, 1) * 103 + (Math.max(substitute_members.length, 1) - 1) * 8
|
Math.max(substitute_members.length, 1) * 103 +
|
||||||
|
(Math.max(substitute_members.length, 1) - 1) * 8
|
||||||
}px`,
|
}px`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -471,13 +480,15 @@ export default function Participants(props) {
|
|||||||
src={avatar_url}
|
src={avatar_url}
|
||||||
onClick={handleViewUserInfo.bind(
|
onClick={handleViewUserInfo.bind(
|
||||||
null,
|
null,
|
||||||
substitute_user_id
|
substitute_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}
|
{displayNtrp}
|
||||||
</Text>
|
</Text>
|
||||||
<Text className={styles["participants-list-item-role"]}>
|
<Text className={styles["participants-list-item-role"]}>
|
||||||
|
|||||||
@@ -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";
|
||||||
@@ -60,8 +60,10 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
|
|||||||
show: async (publish_flag = false) => {
|
show: async (publish_flag = false) => {
|
||||||
setPublishFlag(publish_flag);
|
setPublishFlag(publish_flag);
|
||||||
if (publish_flag) {
|
if (publish_flag) {
|
||||||
|
try {
|
||||||
const url = await generateShareImageUrl();
|
const url = await generateShareImageUrl();
|
||||||
setShareImageUrl(url);
|
setShareImageUrl(url);
|
||||||
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
},
|
},
|
||||||
@@ -81,13 +83,14 @@ 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")}小时`;
|
||||||
|
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}`,
|
||||||
@@ -128,22 +131,24 @@ 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: "生成中..." });
|
||||||
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,
|
||||||
@@ -152,7 +157,7 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
|
|||||||
time: `${startTime.format("ah")}点 ${gameLength}`,
|
time: `${startTime.format("ah")}点 ${gameLength}`,
|
||||||
qrCodeUrl,
|
qrCodeUrl,
|
||||||
});
|
});
|
||||||
Taro.hideLoading();
|
// Taro.hideLoading();
|
||||||
Taro.showShareImageMenu({
|
Taro.showShareImageMenu({
|
||||||
path: url,
|
path: url,
|
||||||
});
|
});
|
||||||
@@ -164,6 +169,18 @@ 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);
|
setPublishFlag(false);
|
||||||
@@ -193,14 +210,14 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
|
|||||||
<View
|
<View
|
||||||
className={styles.contentContainer}
|
className={styles.contentContainer}
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url(${OSS_BASE_URL}/images/215f1ce1-be52-4a92-8250-5a4a69e7f2b3.png)`,
|
backgroundImage: `url(${OSS_BASE}/front/ball/images/215f1ce1-be52-4a92-8250-5a4a69e7f2b3.png)`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
catchMove
|
catchMove
|
||||||
className={classnames(
|
className={classnames(
|
||||||
styles.title,
|
styles.title,
|
||||||
publishFlag ? styles.publishTitle : ""
|
publishFlag ? styles.publishTitle : "",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{publishFlag ? (
|
{publishFlag ? (
|
||||||
@@ -254,7 +271,7 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View className={styles.customBtnWrapper}>
|
<View className={styles.customBtnWrapper}>
|
||||||
<Button className={styles.button}>
|
<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>
|
||||||
|
|||||||
@@ -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";
|
||||||
@@ -53,6 +54,12 @@ function Index() {
|
|||||||
await waitForAuthInit();
|
await waitForAuthInit();
|
||||||
// 然后再获取用户信息
|
// 然后再获取用户信息
|
||||||
await fetchUserInfo();
|
await fetchUserInfo();
|
||||||
|
|
||||||
|
await delay(1000);
|
||||||
|
|
||||||
|
if (from === "publish") {
|
||||||
|
handleShare(true);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
init();
|
init();
|
||||||
}, []);
|
}, []);
|
||||||
@@ -81,9 +88,9 @@ function Index() {
|
|||||||
// 位置更新后,重新获取详情页数据(因为距离等信息可能发生变化)
|
// 位置更新后,重新获取详情页数据(因为距离等信息可能发生变化)
|
||||||
// 注意:这里不调用 fetchDetail,避免与 useDidShow 中的调用重复
|
// 注意:这里不调用 fetchDetail,避免与 useDidShow 中的调用重复
|
||||||
// 如果需要更新距离信息,可以在 fetchDetail 成功后根据当前位置重新计算
|
// 如果需要更新距离信息,可以在 fetchDetail 成功后根据当前位置重新计算
|
||||||
if (from === "publish") {
|
// if (from === "publish") {
|
||||||
handleShare(true);
|
// handleShare(true);
|
||||||
}
|
// }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("用户位置更新失败", error);
|
console.error("用户位置更新失败", error);
|
||||||
}
|
}
|
||||||
@@ -161,7 +168,7 @@ function Index() {
|
|||||||
navto(
|
navto(
|
||||||
userId === myInfo.id
|
userId === myInfo.id
|
||||||
? "/user_pages/myself/index"
|
? "/user_pages/myself/index"
|
||||||
: `/user_pages/other/index?userid=${userId}`
|
: `/user_pages/other/index?userid=${userId}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,7 +202,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`,
|
||||||
@@ -291,7 +298,7 @@ function Index() {
|
|||||||
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;
|
||||||
|
|||||||
@@ -155,6 +155,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">
|
||||||
@@ -193,7 +198,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 +213,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 +233,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 +268,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 参数指定协议类型
|
||||||
|
|
||||||
|
|||||||
@@ -63,10 +63,11 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
|||||||
area,
|
area,
|
||||||
cityQrCode,
|
cityQrCode,
|
||||||
districts,
|
districts,
|
||||||
fetchMatches,
|
|
||||||
gamesNum, // 新增:获取球局数量
|
gamesNum, // 新增:获取球局数量
|
||||||
} = store;
|
} = store;
|
||||||
|
|
||||||
|
const supportedCitiesList = useDictionaryStore((s) => s.getDictionaryValue('supported_cities', ['上海市'])) || [];
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isShowFilterPopup,
|
isShowFilterPopup,
|
||||||
data: matches,
|
data: matches,
|
||||||
@@ -78,7 +79,6 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
|||||||
pageOption,
|
pageOption,
|
||||||
isShowNoData,
|
isShowNoData,
|
||||||
} = listPageState || {};
|
} = listPageState || {};
|
||||||
console.log('===matches', matches)
|
|
||||||
|
|
||||||
const scrollContextRef = useRef(null);
|
const scrollContextRef = useRef(null);
|
||||||
const scrollViewRef = useRef(null);
|
const scrollViewRef = useRef(null);
|
||||||
@@ -94,8 +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);
|
||||||
// 首次加载标记:避免切回 tab 时使用 isRefresh 导致智能排序顺序抖动
|
// 记录是否是进入列表页的第一次调用 updateUserLocation(首次传 force: true)
|
||||||
const hasLoadedOnceRef = useRef(false);
|
const hasUpdatedLocationRef = useRef(false);
|
||||||
|
|
||||||
// 处理距离筛选显示/隐藏
|
// 处理距离筛选显示/隐藏
|
||||||
const handleDistanceFilterVisibleChange = useCallback(
|
const handleDistanceFilterVisibleChange = useCallback(
|
||||||
@@ -234,14 +234,7 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
|||||||
|
|
||||||
// 只有当页面激活时才加载位置和列表数据
|
// 只有当页面激活时才加载位置和列表数据
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
const firstLoad = !hasLoadedOnceRef.current;
|
getLocation().catch((error) => {
|
||||||
getLocation(firstLoad)
|
|
||||||
.then(() => {
|
|
||||||
if (firstLoad) {
|
|
||||||
hasLoadedOnceRef.current = true;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('获取位置信息失败:', error);
|
console.error('获取位置信息失败:', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -300,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();
|
||||||
@@ -318,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,18 +365,21 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const getLocation = async (useRefresh = true) => {
|
const getLocation = async () => {
|
||||||
const location = await getCurrentLocationInfo();
|
const location = await getCurrentLocationInfo();
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 先调用列表接口
|
// 先调用列表接口
|
||||||
await fetchMatches({}, useRefresh);
|
await getMatchesData();
|
||||||
// 列表接口完成后,再调用数量接口
|
// 列表接口完成后,再调用数量接口
|
||||||
await fetchGetGamesCount();
|
await fetchGetGamesCount();
|
||||||
// 初始数据加载完成后,记录当前城市
|
// 初始数据加载完成后,记录当前城市
|
||||||
@@ -457,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",
|
||||||
@@ -476,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() {
|
||||||
@@ -518,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 (
|
||||||
<>
|
<>
|
||||||
@@ -570,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>
|
||||||
@@ -613,6 +627,7 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
|||||||
reload={refreshMatches}
|
reload={refreshMatches}
|
||||||
loadMoreMatches={loadMoreMatches}
|
loadMoreMatches={loadMoreMatches}
|
||||||
evaluateFlag
|
evaluateFlag
|
||||||
|
enableHomeCards
|
||||||
/>
|
/>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -37,6 +37,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();
|
||||||
@@ -169,6 +170,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 +196,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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,12 +67,6 @@ const MainPage: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
await fetchUserInfo();
|
await fetchUserInfo();
|
||||||
await checkNicknameChangeStatus();
|
await checkNicknameChangeStatus();
|
||||||
// 启动时预取 Banner 字典(与业务无强依赖,失败不影响主流程)
|
|
||||||
try {
|
|
||||||
await useDictionaryStore.getState().fetchBannerDictionary();
|
|
||||||
} catch (e) {
|
|
||||||
console.error("预取 Banner 字典失败:", e);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取用户信息失败:", error);
|
console.error("获取用户信息失败:", error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,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";
|
||||||
@@ -301,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 */}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
.banner_detail_page {
|
.banner_detail_page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.banner_detail_content {
|
.banner_detail_content {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.banner_detail_image {
|
.banner_detail_image {
|
||||||
@@ -12,5 +18,3 @@
|
|||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -193,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")}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
@@ -226,7 +225,7 @@ function Intro() {
|
|||||||
<View
|
<View
|
||||||
className={styles.introContainer}
|
className={styles.introContainer}
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url(${OSS_BASE_URL}/images/215f1ce1-be52-4a92-8250-5a4a69e7f2b3.png)`,
|
backgroundImage: `url(${OSS_BASE}/front/ball/images/215f1ce1-be52-4a92-8250-5a4a69e7f2b3.png)`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CommonGuideBar />
|
<CommonGuideBar />
|
||||||
@@ -253,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>
|
||||||
@@ -311,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>
|
||||||
@@ -319,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>
|
||||||
@@ -381,7 +380,7 @@ function Test() {
|
|||||||
prev.map((item, pIndex) => ({
|
prev.map((item, pIndex) => ({
|
||||||
...item,
|
...item,
|
||||||
...(pIndex === index ? { choosen: i } : {}),
|
...(pIndex === index ? { choosen: i } : {}),
|
||||||
}))
|
})),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,7 +427,7 @@ function Test() {
|
|||||||
<View
|
<View
|
||||||
className={styles.testContainer}
|
className={styles.testContainer}
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url(${OSS_BASE_URL}/images/215f1ce1-be52-4a92-8250-5a4a69e7f2b3.png)`,
|
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}`} />
|
||||||
@@ -523,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);
|
||||||
}
|
}
|
||||||
@@ -539,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -588,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();
|
||||||
@@ -613,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 方法生成完整图片
|
||||||
@@ -712,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}>
|
||||||
@@ -762,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;
|
||||||
|
|
||||||
|
.aiImportPopupOverlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
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 {
|
.aiImportPopup {
|
||||||
background-color: #fff;
|
width: 100%;
|
||||||
&:global(.nut-popup-bottom.nut-popup-round) {
|
background-color:#fafafa;
|
||||||
border-radius: 20px 20px 0 0!important;
|
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;
|
||||||
|
|||||||
@@ -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'
|
||||||
@@ -188,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: '场地类型',
|
||||||
@@ -170,14 +176,56 @@ 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当键盘显示时触发 changeTextarea
|
||||||
|
useEffect(() => {
|
||||||
|
if (isKeyboardVisible) {
|
||||||
|
changeTextarea(true)
|
||||||
|
}
|
||||||
|
}, [isKeyboardVisible])
|
||||||
|
|
||||||
|
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`}
|
||||||
@@ -220,9 +268,12 @@ 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)}
|
//changeTextarea(true)
|
||||||
onFocus={() => changePicker(true)}
|
updateFormData(item.prop, value)
|
||||||
|
}}
|
||||||
|
// onBlur={() => changeTextarea(false)}
|
||||||
|
onFocus={() => changeTextarea(true)}
|
||||||
placeholder='有其他场地信息可备注'
|
placeholder='有其他场地信息可备注'
|
||||||
options={(item.options || []).map((o) => ({ label: o, value: o }))}
|
options={(item.options || []).map((o) => ({ label: o, value: o }))}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -78,9 +78,8 @@ 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,
|
||||||
]);
|
]);
|
||||||
@@ -103,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;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -186,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({
|
||||||
@@ -198,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,
|
||||||
@@ -207,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;
|
||||||
@@ -225,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({
|
||||||
@@ -368,7 +365,6 @@ 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") {
|
||||||
@@ -516,7 +512,7 @@ const PublishBall: React.FC = () => {
|
|||||||
|
|
||||||
const mergeWithDefault = (
|
const mergeWithDefault = (
|
||||||
data: any,
|
data: any,
|
||||||
isDetail: boolean = false
|
isDetail: boolean = false,
|
||||||
): PublishBallFormData => {
|
): PublishBallFormData => {
|
||||||
// ai导入与详情数据处理
|
// ai导入与详情数据处理
|
||||||
const {
|
const {
|
||||||
@@ -741,7 +737,6 @@ const PublishBall: React.FC = () => {
|
|||||||
} else {
|
} else {
|
||||||
setIsSubmitDisabled(false);
|
setIsSubmitDisabled(false);
|
||||||
}
|
}
|
||||||
console.log(formData, "formData");
|
|
||||||
}, [formData]);
|
}, [formData]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -754,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 () => {
|
||||||
@@ -789,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: true })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导出认证服务实例
|
// 导出认证服务实例
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ export const save_login_state = (token: string, user_info: WechatUserInfo) => {
|
|||||||
export const clear_login_state = () => {
|
export const clear_login_state = () => {
|
||||||
try {
|
try {
|
||||||
// 使用 tokenManager 清除令牌
|
// 使用 tokenManager 清除令牌
|
||||||
tokenManager.clearTokens();
|
// tokenManager.clearTokens();
|
||||||
|
|
||||||
// 清除其他登录状态
|
// 清除其他登录状态
|
||||||
Taro.removeStorageSync("user_info");
|
Taro.removeStorageSync("user_info");
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { UserInfo } from "@/components/UserInfo";
|
|||||||
import { API_CONFIG } from "@/config/api";
|
import { API_CONFIG } from "@/config/api";
|
||||||
import httpService, { ApiResponse } from "./httpService";
|
import httpService, { ApiResponse } from "./httpService";
|
||||||
import uploadFiles from "./uploadFiles";
|
import uploadFiles from "./uploadFiles";
|
||||||
import Taro from "@tarojs/taro";
|
import * as Taro from "@tarojs/taro";
|
||||||
import getCurrentConfig from "@/config/env";
|
import getCurrentConfig from "@/config/env";
|
||||||
import { clear_login_state } from "@/services/loginService";
|
import { clear_login_state } from "@/services/loginService";
|
||||||
|
|
||||||
@@ -740,12 +740,14 @@ export const updateUserProfile = async (payload: Partial<UserInfoType>) => {
|
|||||||
// 更新用户坐标位置
|
// 更新用户坐标位置
|
||||||
export const updateUserLocation = async (
|
export const updateUserLocation = async (
|
||||||
latitude: number,
|
latitude: number,
|
||||||
longitude: number
|
longitude: number,
|
||||||
|
force: boolean = false
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const response = await httpService.post("/user/update_location", {
|
const response = await httpService.post("/user/update_location", {
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
|
force
|
||||||
});
|
});
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
10
src/static/list/icon-relocate.svg
Normal file
10
src/static/list/icon-relocate.svg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_8289_53268)">
|
||||||
|
<path d="M10.9775 4.82657H8.04862C7.97102 4.82657 7.89658 4.7958 7.84163 4.74101C7.78668 4.68622 7.7557 4.61188 7.75547 4.53428V4.36457C7.75497 4.32525 7.76254 4.28625 7.77773 4.24998C7.79292 4.21371 7.81539 4.18094 7.84376 4.15371L8.88604 3.11143C8.50894 2.72825 8.05949 2.4238 7.56377 2.21574C7.06805 2.00768 6.53594 1.90016 5.99833 1.89943C5.1996 1.90138 4.41883 2.13662 3.75196 2.57623C3.08509 3.01584 2.56116 3.64067 2.24453 4.37397C1.92791 5.10727 1.83238 5.91708 1.9697 6.70392C2.10701 7.49077 2.47118 8.22036 3.01746 8.80307C3.56375 9.38578 4.26835 9.79622 5.0447 9.98397C5.82106 10.1717 6.63535 10.1286 7.38753 9.8599C8.13971 9.59121 8.79702 9.10864 9.2787 8.47149C9.76039 7.83435 10.0455 7.07037 10.0989 6.27343C10.1075 6.11828 10.236 5.99743 10.3912 5.99743H10.9775C11.0574 6.00001 11.1331 6.03387 11.1883 6.09171C11.2415 6.15 11.2698 6.22885 11.2638 6.30771C11.1937 7.51221 10.7124 8.6562 9.90046 9.54861C9.08847 10.441 7.99488 11.0278 6.80233 11.211C5.60978 11.3942 4.39049 11.1627 3.34808 10.5551C2.30568 9.94759 1.50328 9.00078 1.0749 7.87285C0.646012 6.74568 0.616794 5.50548 0.992127 4.35935C1.36746 3.21323 2.12463 2.23056 3.13719 1.57543C4.15001 0.920266 5.35687 0.632222 6.55641 0.759351C7.75595 0.88648 8.87562 1.42109 9.72862 2.274L10.6012 1.40143C10.6279 1.37384 10.6599 1.35189 10.6952 1.33687C10.7305 1.32186 10.7685 1.31408 10.8069 1.314H10.9766C11.0541 1.31422 11.1283 1.34509 11.183 1.39986C11.2378 1.45462 11.2687 1.52883 11.2689 1.60628V4.53428C11.2687 4.61173 11.2378 4.68595 11.183 4.74071C11.1283 4.79548 11.0549 4.82634 10.9775 4.82657Z" fill="#A6A6A6"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_8289_53268">
|
||||||
|
<rect width="12" height="12" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 18 KiB |
@@ -20,7 +20,6 @@ interface DictionaryState {
|
|||||||
bannerDetailImage: string
|
bannerDetailImage: string
|
||||||
bannerListIndex: string
|
bannerListIndex: string
|
||||||
} | null
|
} | null
|
||||||
fetchBannerDictionary: () => Promise<void>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建字典Store
|
// 创建字典Store
|
||||||
@@ -36,20 +35,32 @@ export const useDictionaryStore = create<DictionaryState>()((set, get) => ({
|
|||||||
set({ isLoading: true, error: null })
|
set({ isLoading: true, error: null })
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const keys = 'publishing_requirements,court_type,court_surface,supplementary_information,game_play,fabu_tip';
|
const keys = 'publishing_requirements,court_type,court_surface,supplementary_information,game_play,fabu_tip,supported_cities,bannerListImage,bannerDetailImage,bannerListIndex';
|
||||||
const response = await commonApi.getDictionaryManyKey(keys)
|
const response = await commonApi.getDictionaryManyKey(keys)
|
||||||
|
|
||||||
if (response.code === 0 && response.data) {
|
if (response.code === 0 && response.data) {
|
||||||
const dictionaryData = {};
|
const dictionaryData = {};
|
||||||
keys.split(',').forEach(key => {
|
keys.split(',').forEach(key => {
|
||||||
const list = response.data[key];
|
const list = response.data[key];
|
||||||
const listData = list.split('|');
|
// supported_cities 格式如 "上海市||北京市",用 || 分割
|
||||||
|
const listData = key === 'supported_cities'
|
||||||
|
? (list ? String(list).split('||').map((s) => s.trim()).filter(Boolean) : [])
|
||||||
|
: (list ? list.split('|') : []);
|
||||||
dictionaryData[key] = listData;
|
dictionaryData[key] = listData;
|
||||||
})
|
})
|
||||||
set({
|
set({
|
||||||
dictionaryData: dictionaryData || {},
|
dictionaryData: dictionaryData || {},
|
||||||
isLoading: false
|
isLoading: false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
set({
|
||||||
|
bannerDict: {
|
||||||
|
bannerListImage: response.data.bannerListImage || '',
|
||||||
|
bannerDetailImage: response.data.bannerDetailImage || '',
|
||||||
|
bannerListIndex: (response.data.bannerListIndex ?? '').toString(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
console.log('字典数据获取成功:', response.data)
|
console.log('字典数据获取成功:', response.data)
|
||||||
} else {
|
} else {
|
||||||
throw new Error(response.message || '获取字典数据失败')
|
throw new Error(response.message || '获取字典数据失败')
|
||||||
@@ -64,26 +75,7 @@ export const useDictionaryStore = create<DictionaryState>()((set, get) => ({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 获取 Banner 字典(启动时或手动调用)
|
|
||||||
fetchBannerDictionary: async () => {
|
|
||||||
try {
|
|
||||||
const keys = 'bannerListImage,bannerDetailImage,bannerListIndex';
|
|
||||||
const response = await commonApi.getDictionaryManyKey(keys)
|
|
||||||
if (response.code === 0 && response.data) {
|
|
||||||
const data = response.data || {};
|
|
||||||
set({
|
|
||||||
bannerDict: {
|
|
||||||
bannerListImage: data.bannerListImage || '',
|
|
||||||
bannerDetailImage: data.bannerDetailImage || '',
|
|
||||||
bannerListIndex: (data.bannerListIndex ?? '').toString(),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 保持静默,避免影响启动流程
|
|
||||||
console.error('获取 Banner 字典失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// 获取字典值
|
// 获取字典值
|
||||||
getDictionaryValue: (key: string, defaultValue?: any) => {
|
getDictionaryValue: (key: string, defaultValue?: any) => {
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import {
|
|||||||
getCityQrCode,
|
getCityQrCode,
|
||||||
getDistricts,
|
getDistricts,
|
||||||
} from "../services/listApi";
|
} from "../services/listApi";
|
||||||
// 不再在这里请求 banner 字典,统一由 dictionaryStore 启动时获取
|
|
||||||
import { useDictionaryStore } from "./dictionaryStore";
|
|
||||||
import {
|
import {
|
||||||
ListActions,
|
ListActions,
|
||||||
IFilterOptions,
|
IFilterOptions,
|
||||||
@@ -20,26 +18,6 @@ import {
|
|||||||
IPayload,
|
IPayload,
|
||||||
} from "../../types/list/types";
|
} from "../../types/list/types";
|
||||||
|
|
||||||
// 将 banner 按索引插入到列表的工具方法(0基;长度不足则插末尾;先移除已存在的 banner)
|
|
||||||
function insertBannersToRows(rows: any[], dictData: any) {
|
|
||||||
if (!Array.isArray(rows) || !dictData) return rows;
|
|
||||||
const img = (dictData?.bannerListImage || "").trim();
|
|
||||||
const indexRaw = (dictData?.bannerListIndex || "").toString().trim();
|
|
||||||
if (!img) return rows;
|
|
||||||
const parsed = parseInt(indexRaw, 10);
|
|
||||||
const normalized = Number.isFinite(parsed) ? parsed : 0;
|
|
||||||
// 先移除已有的 banner,确保列表中仅一条 banner
|
|
||||||
const resultRows = rows?.filter((item) => item?.type !== "banner") || [];
|
|
||||||
const target = Math.max(0, Math.min(normalized, resultRows.length));
|
|
||||||
resultRows.splice(target, 0, {
|
|
||||||
type: "banner",
|
|
||||||
id: `banner-${target}`,
|
|
||||||
banner_image_url: img,
|
|
||||||
banner_detail_url: (dictData?.bannerDetailImage || "").trim(),
|
|
||||||
} as any);
|
|
||||||
return resultRows;
|
|
||||||
}
|
|
||||||
|
|
||||||
function translateCityData(dataTree) {
|
function translateCityData(dataTree) {
|
||||||
return dataTree.map((item) => {
|
return dataTree.map((item) => {
|
||||||
const { children, ...rest } = item;
|
const { children, ...rest } = item;
|
||||||
@@ -216,18 +194,17 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
|||||||
const { distanceFilter, order, district } = distanceQuickFilter || {};
|
const { distanceFilter, order, district } = distanceQuickFilter || {};
|
||||||
|
|
||||||
// 始终使用 state.area,确保所有接口使用一致的城市参数
|
// 始终使用 state.area,确保所有接口使用一致的城市参数
|
||||||
const areaProvince = state.area?.at(1) || "";
|
const areaProvince = state.area?.at(0) || "";
|
||||||
|
const areaCity = state.area?.at(1) || "";
|
||||||
const last_location_province = areaProvince;
|
const last_location_province = areaProvince;
|
||||||
|
|
||||||
// city 参数逻辑:
|
|
||||||
// 1. 如果选择了行政区(district 有值),使用行政区的名称(label)
|
let county: string | undefined = undefined;
|
||||||
// 2. 如果是"全城"(distanceFilter 为空),不传 city
|
|
||||||
let city: string | undefined = undefined;
|
|
||||||
if (district) {
|
if (district) {
|
||||||
// 从 districts 数组中查找对应的行政区名称
|
// 从 districts 数组中查找对应的行政区名称
|
||||||
const selectedDistrict = state.districts.find(item => item.value === district);
|
const selectedDistrict = state.districts.find(item => item.value === district);
|
||||||
if (selectedDistrict) {
|
if (selectedDistrict) {
|
||||||
city = selectedDistrict.label; // 传递行政区名称,如"静安"
|
county = selectedDistrict.label; // 传递行政区名称,如"静安"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 如果是"全城"(distanceFilter 为空),city 保持 undefined,不会被传递
|
// 如果是"全城"(distanceFilter 为空),city 保持 undefined,不会被传递
|
||||||
@@ -246,11 +223,12 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
|||||||
distanceFilter: distanceFilter,
|
distanceFilter: distanceFilter,
|
||||||
// 显式设置 province,确保始终使用 state.area 中的最新值
|
// 显式设置 province,确保始终使用 state.area 中的最新值
|
||||||
province: last_location_province, // 始终使用 state.area 中的 province,确保城市参数一致
|
province: last_location_province, // 始终使用 state.area 中的 province,确保城市参数一致
|
||||||
|
city: areaCity,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 只在有值时添加 city 参数
|
// 只在有值时添加 city 参数
|
||||||
if (city) {
|
if (county) {
|
||||||
searchOption.city = city;
|
searchOption.county = county;
|
||||||
}
|
}
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
@@ -272,14 +250,11 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
|||||||
const currentPageState = state.isSearchResult ? state.searchPageState : state.listPageState;
|
const currentPageState = state.isSearchResult ? state.searchPageState : state.listPageState;
|
||||||
const currentData = currentPageState?.data || [];
|
const currentData = currentPageState?.data || [];
|
||||||
const newData = isAppend ? [...currentData, ...(data || [])] : (data || []);
|
const newData = isAppend ? [...currentData, ...(data || [])] : (data || []);
|
||||||
// 从字典缓存获取 banner,并将其插入到最终列表指定位置(全局索引)
|
|
||||||
const dictData = useDictionaryStore.getState().bannerDict;
|
|
||||||
const processedData = dictData ? insertBannersToRows(newData, dictData) : newData;
|
|
||||||
state.updateCurrentPageState({
|
state.updateCurrentPageState({
|
||||||
data: processedData,
|
data: newData,
|
||||||
isHasMoreData,
|
isHasMoreData,
|
||||||
// 使用插入后的最终数据判断是否显示空状态,避免有 banner 时仍显示空
|
// 使用插入后的最终数据判断是否显示空状态,避免有 banner 时仍显示空
|
||||||
isShowNoData: processedData?.length === 0,
|
isShowNoData: newData?.length === 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
set({
|
set({
|
||||||
@@ -729,18 +704,18 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
|||||||
async getDistricts() {
|
async getDistricts() {
|
||||||
try {
|
try {
|
||||||
const state = get();
|
const state = get();
|
||||||
// 从 area 中获取省份,area 格式: ["中国", 省份, 城市]
|
// 从 area 中获取省份,area 格式: [ 省份, 城市]
|
||||||
const country = "中国";
|
const province = state.area?.at(0) || "上海";
|
||||||
const province = state.area?.at(1) || "上海"; // area[1] 是省份
|
const cn_city = state.area?.at(1) || "上海市"; // area[1] 是省份
|
||||||
|
|
||||||
const res = await getDistricts({
|
const res = await getDistricts({
|
||||||
country,
|
province,
|
||||||
state: province
|
city: cn_city
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.code === 0 && res.data) {
|
if (res.code === 0 && res.data) {
|
||||||
const districts = res.data.map((item) => ({
|
const districts = res.data.map((item) => ({
|
||||||
label: item.cn_city,
|
label: item.cn_county,
|
||||||
value: item.id.toString(),
|
value: item.id.toString(),
|
||||||
id: item.id,
|
id: item.id,
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ const getTimeNextDate = (time: string) => {
|
|||||||
// 请求锁,避免重复调用
|
// 请求锁,避免重复调用
|
||||||
let isFetchingLastTestResult = false;
|
let isFetchingLastTestResult = false;
|
||||||
let isCheckingNicknameStatus = false;
|
let isCheckingNicknameStatus = false;
|
||||||
|
const CITY_CACHE_KEY = "USER_SELECTED_CITY";
|
||||||
|
|
||||||
export const useUser = create<UserState>()((set) => ({
|
export const useUser = create<UserState>()((set) => ({
|
||||||
user: {},
|
user: {},
|
||||||
@@ -47,9 +48,11 @@ export const useUser = create<UserState>()((set) => ({
|
|||||||
|
|
||||||
// 优先使用缓存中的城市,不使用用户信息中的位置
|
// 优先使用缓存中的城市,不使用用户信息中的位置
|
||||||
// 检查是否有缓存的城市
|
// 检查是否有缓存的城市
|
||||||
const CITY_CACHE_KEY = "USER_SELECTED_CITY";
|
|
||||||
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) {
|
||||||
// 如果有缓存的城市,使用缓存,不更新 area
|
// 如果有缓存的城市,使用缓存,不更新 area
|
||||||
console.log("[userStore] 检测到缓存的城市,使用缓存,不更新 area");
|
console.log("[userStore] 检测到缓存的城市,使用缓存,不更新 area");
|
||||||
@@ -60,16 +63,13 @@ export const useUser = create<UserState>()((set) => ({
|
|||||||
if (userData?.last_location_province) {
|
if (userData?.last_location_province) {
|
||||||
const listStore = useListStore.getState();
|
const listStore = useListStore.getState();
|
||||||
const currentArea = listStore.area;
|
const currentArea = listStore.area;
|
||||||
|
|
||||||
// 只有当 area 不存在时才使用用户信息中的位置
|
// 只有当 area 不存在时才使用用户信息中的位置
|
||||||
if (!currentArea) {
|
if (!currentArea) {
|
||||||
const newArea: [string, string] = ["中国", userData.last_location_province];
|
const newArea: [string, string] = [userData.last_location_province||"", userData.last_location_city||""];
|
||||||
listStore.updateArea(newArea);
|
listStore.updateArea(newArea);
|
||||||
// 保存到缓存
|
// 保存到缓存
|
||||||
try {
|
useUser.getState().updateCache(newArea);
|
||||||
(Taro as any).setStorageSync?.(CITY_CACHE_KEY, newArea);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("保存城市缓存失败:", error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +79,16 @@ export const useUser = create<UserState>()((set) => ({
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 更新缓存
|
||||||
|
updateCache: async (newArea: [string, string]) => {
|
||||||
|
try {
|
||||||
|
(Taro as any).setStorageSync?.(CITY_CACHE_KEY, newArea);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("保存城市缓存失败:", error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
updateUserInfo: async (userInfo: Partial<UserInfoType>) => {
|
updateUserInfo: async (userInfo: Partial<UserInfoType>) => {
|
||||||
try {
|
try {
|
||||||
// 先更新后端
|
// 先更新后端
|
||||||
@@ -93,7 +103,7 @@ export const useUser = create<UserState>()((set) => ({
|
|||||||
const currentArea = listStore.area;
|
const currentArea = listStore.area;
|
||||||
// 只有当 area 不存在或与 userLastLocationProvince 不一致时才更新
|
// 只有当 area 不存在或与 userLastLocationProvince 不一致时才更新
|
||||||
if (!currentArea || currentArea[1] !== userInfo.last_location_province) {
|
if (!currentArea || currentArea[1] !== userInfo.last_location_province) {
|
||||||
const newArea: [string, string] = ["中国", userInfo.last_location_province];
|
const newArea: [string, string] = [userInfo.last_location_province || "", userInfo.last_location_city || ""];
|
||||||
listStore.updateArea(newArea);
|
listStore.updateArea(newArea);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,6 +205,7 @@ export const useNicknameChangeStatus = () =>
|
|||||||
export const useUserActions = () =>
|
export const useUserActions = () =>
|
||||||
useUser((state) => ({
|
useUser((state) => ({
|
||||||
fetchUserInfo: state.fetchUserInfo,
|
fetchUserInfo: state.fetchUserInfo,
|
||||||
|
updateCache: state.updateCache,
|
||||||
updateUserInfo: state.updateUserInfo,
|
updateUserInfo: state.updateUserInfo,
|
||||||
checkNicknameChangeStatus: state.checkNicknameChangeStatus,
|
checkNicknameChangeStatus: state.checkNicknameChangeStatus,
|
||||||
updateNickname: state.updateNickname,
|
updateNickname: state.updateNickname,
|
||||||
|
|||||||
@@ -66,6 +66,34 @@ const DownloadBillRecords: React.FC = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handlePreviewFile = (fileUrl: string) => {
|
||||||
|
wx.downloadFile({
|
||||||
|
url: fileUrl,
|
||||||
|
success: (res) => {
|
||||||
|
if (res.statusCode === 200) {
|
||||||
|
// 确保文件路径正确并添加扩展名
|
||||||
|
const filePath = res.tempFilePath;
|
||||||
|
wx.openDocument({
|
||||||
|
filePath: filePath,
|
||||||
|
fileType: 'xlsx', // 指定文件类型为xlsx
|
||||||
|
showMenu: true, // 显示右上角菜单按钮
|
||||||
|
success: (openRes) => {
|
||||||
|
console.log('打开文档成功');
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('打开文档失败', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error('下载失败,状态码:', res.statusCode);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('下载失败', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<View className="download-bill-records-page">
|
<View className="download-bill-records-page">
|
||||||
{/* 导航栏 */}
|
{/* 导航栏 */}
|
||||||
@@ -111,7 +139,7 @@ const DownloadBillRecords: React.FC = () => {
|
|||||||
</View>
|
</View>
|
||||||
<View className="info-item">
|
<View className="info-item">
|
||||||
<Text></Text>
|
<Text></Text>
|
||||||
<Text className="btn">查看材料</Text>
|
<Text className="btn" onClick={() => handlePreviewFile(record.file_url)}>查看材料</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)) : <EmptyState text="暂无数据" />}
|
)) : <EmptyState text="暂无数据" />}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
|
|
||||||
.qrcode {
|
.qrcode {
|
||||||
width: 240px;
|
width: 240px;
|
||||||
|
height: 240px;
|
||||||
margin: 32px 0 -20px;
|
margin: 32px 0 -20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ const OtherUserPage: React.FC = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const [collapseProfile, setCollapseProfile] = useState(false);
|
const [collapseProfile, setCollapseProfile] = useState(false);
|
||||||
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
|
|
||||||
// 进入页面时检查 user_id,只在组件挂载时执行一次
|
// 进入页面时检查 user_id,只在组件挂载时执行一次
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -82,15 +83,12 @@ const OtherUserPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}, []); // 空依赖数组,确保只在进入时执行一次
|
}, []); // 空依赖数组,确保只在进入时执行一次
|
||||||
|
|
||||||
// 页面加载时获取用户信息
|
// 加载用户信息(使用 useCallback 便于下拉刷新复用)
|
||||||
useEffect(() => {
|
const load_user_data = useCallback(async () => {
|
||||||
const load_user_data = async () => {
|
if (!user_id) return;
|
||||||
if (user_id) {
|
|
||||||
try {
|
try {
|
||||||
// const user_data = await UserService.get_user_info(user_id);
|
|
||||||
const res = await LoginService.getUserInfoById(user_id);
|
const res = await LoginService.getUserInfoById(user_id);
|
||||||
const { data: userData } = res;
|
const { data: userData } = res;
|
||||||
// setUserInfo({...res.data as UserInfo, avatar: data.avatar_url || require("@/static/userInfo/default_avatar.svg")});
|
|
||||||
setUserInfo({
|
setUserInfo({
|
||||||
id: parseInt(user_id || "") || 0,
|
id: parseInt(user_id || "") || 0,
|
||||||
nickname: userData.nickname || "",
|
nickname: userData.nickname || "",
|
||||||
@@ -107,7 +105,6 @@ const OtherUserPage: React.FC = () => {
|
|||||||
participated_games_count:
|
participated_games_count:
|
||||||
userData.stats?.participated_games_count || 0,
|
userData.stats?.participated_games_count || 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
personal_profile: userData.personal_profile || "",
|
personal_profile: userData.personal_profile || "",
|
||||||
province: userData.province || "",
|
province: userData.province || "",
|
||||||
city: userData.city || "",
|
city: userData.city || "",
|
||||||
@@ -126,12 +123,12 @@ const OtherUserPage: React.FC = () => {
|
|||||||
icon: "none",
|
icon: "none",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
load_user_data();
|
|
||||||
}, [user_id]);
|
}, [user_id]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
load_user_data();
|
||||||
|
}, [load_user_data]);
|
||||||
|
|
||||||
// 分类球局数据(使用 useCallback 包装,避免每次渲染都创建新函数)
|
// 分类球局数据(使用 useCallback 包装,避免每次渲染都创建新函数)
|
||||||
const classifyGameRecords = useCallback(
|
const classifyGameRecords = useCallback(
|
||||||
(
|
(
|
||||||
@@ -232,6 +229,18 @@ const OtherUserPage: React.FC = () => {
|
|||||||
setCollapseProfile(scrollData.scrollTop > 1);
|
setCollapseProfile(scrollData.scrollTop > 1);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// 下拉刷新:刷新用户信息和球局数据
|
||||||
|
const handle_refresh = useCallback(async () => {
|
||||||
|
setRefreshing(true);
|
||||||
|
try {
|
||||||
|
await Promise.all([load_user_data(), load_game_data()]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("刷新失败:", error);
|
||||||
|
} finally {
|
||||||
|
setRefreshing(false);
|
||||||
|
}
|
||||||
|
}, [load_user_data, load_game_data]);
|
||||||
|
|
||||||
// 处理球局详情
|
// 处理球局详情
|
||||||
// const handle_game_detail = (game_id: string) => {
|
// const handle_game_detail = (game_id: string) => {
|
||||||
// Taro.navigateTo({
|
// Taro.navigateTo({
|
||||||
@@ -244,6 +253,9 @@ const OtherUserPage: React.FC = () => {
|
|||||||
scrollY
|
scrollY
|
||||||
className="other_user_page"
|
className="other_user_page"
|
||||||
refresherBackground="#FAFAFA"
|
refresherBackground="#FAFAFA"
|
||||||
|
refresherEnabled
|
||||||
|
refresherTriggered={refreshing}
|
||||||
|
onRefresherRefresh={handle_refresh}
|
||||||
>
|
>
|
||||||
{/* <CustomNavbar>
|
{/* <CustomNavbar>
|
||||||
<View className="navbar_content">
|
<View className="navbar_content">
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
// @use '../../scss/common.scss' as *;
|
// @use '../../scss/common.scss' as *;
|
||||||
|
|
||||||
.wallet_page {
|
.wallet_page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
overflow-y: auto;
|
overflow: hidden;
|
||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
padding-bottom: 5px;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.wallet_scroll {
|
||||||
|
flex: 1;
|
||||||
|
height: 0;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
width: 0;
|
width: 0;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState, useCallback } from "react";
|
||||||
import { View, Text, Input, Button, Image } from "@tarojs/components";
|
import { View, Text, Input, Button, Image, ScrollView } from "@tarojs/components";
|
||||||
import Taro, { useDidShow, useReachBottom } from "@tarojs/taro";
|
import Taro, { useDidShow } from "@tarojs/taro";
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
import { CommonPopup, EmptyState } from "@/components";
|
import { CommonPopup, EmptyState } from "@/components";
|
||||||
import httpService from "@/services/httpService";
|
import httpService from "@/services/httpService";
|
||||||
@@ -109,16 +109,6 @@ const WalletPage: React.FC = () => {
|
|||||||
const pageConfig = currentPage.page?.config;
|
const pageConfig = currentPage.page?.config;
|
||||||
const pageTitle = pageConfig?.navigationBarTitleText;
|
const pageTitle = pageConfig?.navigationBarTitleText;
|
||||||
|
|
||||||
useReachBottom(() => {
|
|
||||||
if (load_transactions_params.page >= totalPages) return;
|
|
||||||
// 加载更多方法
|
|
||||||
set_load_transactions_params((prev) => {
|
|
||||||
return {
|
|
||||||
...prev,
|
|
||||||
page: prev.page + 1,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// 钱包信息状态
|
// 钱包信息状态
|
||||||
const [wallet_info, set_wallet_info] = useState<WalletInfo>({
|
const [wallet_info, set_wallet_info] = useState<WalletInfo>({
|
||||||
balance: 0,
|
balance: 0,
|
||||||
@@ -158,6 +148,7 @@ const WalletPage: React.FC = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const [totalPages, setTotalPages] = useState(1);
|
const [totalPages, setTotalPages] = useState(1);
|
||||||
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
load_transactions();
|
load_transactions();
|
||||||
@@ -452,6 +443,33 @@ const WalletPage: React.FC = () => {
|
|||||||
setShowFilterPopup(true);
|
setShowFilterPopup(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 下拉刷新:刷新钱包余额和交易记录
|
||||||
|
const handle_refresh = useCallback(async () => {
|
||||||
|
setRefreshing(true);
|
||||||
|
try {
|
||||||
|
await load_wallet_data();
|
||||||
|
set_transactions([]);
|
||||||
|
set_load_transactions_params((prev) => ({ ...prev, page: 1 }));
|
||||||
|
} catch (error) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: "刷新失败,请重试",
|
||||||
|
icon: "none",
|
||||||
|
duration: 2000,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setRefreshing(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 滚动到底部加载更多交易记录
|
||||||
|
const handle_scroll_to_lower = useCallback(() => {
|
||||||
|
if (load_transactions_params.page >= totalPages) return;
|
||||||
|
set_load_transactions_params((prev) => ({
|
||||||
|
...prev,
|
||||||
|
page: prev.page + 1,
|
||||||
|
}));
|
||||||
|
}, [load_transactions_params.page, totalPages]);
|
||||||
|
|
||||||
const handleFilterCancel = () => {
|
const handleFilterCancel = () => {
|
||||||
setShowFilterPopup(false);
|
setShowFilterPopup(false);
|
||||||
setFilterParams({
|
setFilterParams({
|
||||||
@@ -488,6 +506,16 @@ const WalletPage: React.FC = () => {
|
|||||||
Taro.navigateBack();
|
Taro.navigateBack();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<ScrollView
|
||||||
|
scrollY
|
||||||
|
refresherBackground="#FAFAFA"
|
||||||
|
refresherEnabled
|
||||||
|
refresherTriggered={refreshing}
|
||||||
|
onRefresherRefresh={handle_refresh}
|
||||||
|
lowerThreshold={50}
|
||||||
|
onScrollToLower={handle_scroll_to_lower}
|
||||||
|
className="wallet_scroll"
|
||||||
|
>
|
||||||
{/* 钱包主卡片 */}
|
{/* 钱包主卡片 */}
|
||||||
<View
|
<View
|
||||||
className="wallet_main_card"
|
className="wallet_main_card"
|
||||||
@@ -649,6 +677,7 @@ const WalletPage: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
{/* 提现弹窗 */}
|
{/* 提现弹窗 */}
|
||||||
<CommonPopup
|
<CommonPopup
|
||||||
|
|||||||
@@ -1,17 +1,11 @@
|
|||||||
import Taro from "@tarojs/taro";
|
import Taro from "@tarojs/taro";
|
||||||
import { OSS_BASE_URL } from "@/config/api";
|
import { OSS_BASE } from "@/config/api";
|
||||||
|
|
||||||
const bgUrl = `${OSS_BASE_URL}/images/5e2c85ab-fb0c-4026-974d-1e0725181542.png`;
|
const bgUrl = `${OSS_BASE}/front/ball/images/5e2c85ab-fb0c-4026-974d-1e0725181542.png`;
|
||||||
|
const ringUrl = `${OSS_BASE}/front/ball/images/b635164f-ecec-434a-a00b-69614a918f2f.png`;
|
||||||
const ringUrl = `${OSS_BASE_URL}/images/b635164f-ecec-434a-a00b-69614a918f2f.png`;
|
const dateIcon = `${OSS_BASE}/front/ball/images/1b49476e-0eda-42ff-b08c-002ce510df82.jpg`;
|
||||||
|
const mapIcon = `${OSS_BASE}/front/ball/images/06b994fa-9227-4708-8555-8a07af8d0c3b.jpg`;
|
||||||
const dateIcon = `${OSS_BASE_URL}/images/1b49476e-0eda-42ff-b08c-002ce510df82.jpg`;
|
const logoText = `${OSS_BASE}/system/youchang_tip_text.png`;
|
||||||
|
|
||||||
const mapIcon = `${OSS_BASE_URL}/images/06b994fa-9227-4708-8555-8a07af8d0c3b.jpg`;
|
|
||||||
|
|
||||||
// const logo = `${OSS_BASE_URL}/images/fb732da6-11b9-4022-a524-a377b17635eb.jpg`
|
|
||||||
|
|
||||||
const logoText = `${OSS_BASE_URL}/images/9d8cbc9d-9601-4e2d-ab23-76420a4537d6.png`;
|
|
||||||
|
|
||||||
export function base64ToTempFilePath(base64Data: string): Promise<string> {
|
export function base64ToTempFilePath(base64Data: string): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -288,7 +282,9 @@ function drawTextWrap(
|
|||||||
/** 核心纯函数:生成海报图片 */
|
/** 核心纯函数:生成海报图片 */
|
||||||
export async function generatePosterImage(data: any): Promise<string> {
|
export async function generatePosterImage(data: any): Promise<string> {
|
||||||
console.log("start !!!!");
|
console.log("start !!!!");
|
||||||
const dpr = Taro.getWindowInfo().pixelRatio;
|
// const dpr = Taro.getWindowInfo().pixelRatio;
|
||||||
|
const dpr = 1;
|
||||||
|
// console.log(dpr, 'dpr')
|
||||||
const width = 600;
|
const width = 600;
|
||||||
const height = 1000;
|
const height = 1000;
|
||||||
|
|
||||||
@@ -439,7 +435,7 @@ export async function generatePosterImage(data: any): Promise<string> {
|
|||||||
const { tempFilePath } = await Taro.canvasToTempFilePath({
|
const { tempFilePath } = await Taro.canvasToTempFilePath({
|
||||||
canvas,
|
canvas,
|
||||||
fileType: 'png',
|
fileType: 'png',
|
||||||
quality: 1,
|
quality: 0.7,
|
||||||
});
|
});
|
||||||
return tempFilePath;
|
return tempFilePath;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
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 {
|
||||||
userAvatar: string
|
userAvatar: string
|
||||||
@@ -481,7 +481,7 @@ const drawShareCard = async (ctx: any, data: ShareCardData, offscreen: any): Pro
|
|||||||
const textX = iconX + iconSize + 20
|
const textX = iconX + iconSize + 20
|
||||||
|
|
||||||
// 绘制网球图标
|
// 绘制网球图标
|
||||||
const tennisBallPath = await loadImage(`${OSS_BASE_URL}/images/b3eaf45e-ef28-4e45-9195-823b832e0451.jpg`)
|
const tennisBallPath = await loadImage(`${OSS_BASE}/front/ball/images/b3eaf45e-ef28-4e45-9195-823b832e0451.jpg`)
|
||||||
ctx.drawImage(tennisBallPath, iconX, gameInfoY, iconSize, iconSize)
|
ctx.drawImage(tennisBallPath, iconX, gameInfoY, iconSize, iconSize)
|
||||||
|
|
||||||
// 绘制"单打"标签
|
// 绘制"单打"标签
|
||||||
@@ -517,7 +517,7 @@ const drawShareCard = async (ctx: any, data: ShareCardData, offscreen: any): Pro
|
|||||||
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`)
|
const calendarPath = await loadImage(`${OSS_BASE}/front/ball/images/ea792a5d-b105-4c95-bfc4-8af558f2b33b.jpg`)
|
||||||
ctx.drawImage(calendarPath, iconX, timeInfoY, iconSize, iconSize)
|
ctx.drawImage(calendarPath, iconX, timeInfoY, iconSize, iconSize)
|
||||||
|
|
||||||
// 绘制日期(绿色,非描边粗体)
|
// 绘制日期(绿色,非描边粗体)
|
||||||
@@ -530,7 +530,7 @@ const drawShareCard = async (ctx: any, data: ShareCardData, offscreen: any): Pro
|
|||||||
// 绘制地点
|
// 绘制地点
|
||||||
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`)
|
const locationPath = await loadImage(`${OSS_BASE}/front/ball/images/adc9a167-2ea9-4e3b-b963-6a894a1fd91b.jpg`)
|
||||||
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')
|
||||||
|
|
||||||
|
|||||||
2
types/global.d.ts
vendored
2
types/global.d.ts
vendored
@@ -17,6 +17,8 @@ declare namespace NodeJS {
|
|||||||
NODE_ENV: 'development' | 'production',
|
NODE_ENV: 'development' | 'production',
|
||||||
/** 当前构建的平台 */
|
/** 当前构建的平台 */
|
||||||
TARO_ENV: 'weapp' | 'swan' | 'alipay' | 'h5' | 'rn' | 'tt' | 'quickapp' | 'qq' | 'jd'
|
TARO_ENV: 'weapp' | 'swan' | 'alipay' | 'h5' | 'rn' | 'tt' | 'quickapp' | 'qq' | 'jd'
|
||||||
|
/** 应用环境标识 */
|
||||||
|
APP_ENV: 'dev' | 'dev_local' | 'sit' | 'pr'
|
||||||
/**
|
/**
|
||||||
* 当前构建的小程序 appid
|
* 当前构建的小程序 appid
|
||||||
* @description 若不同环境有不同的小程序,可通过在 env 文件中配置环境变量`TARO_APP_ID`来方便快速切换 appid, 而不必手动去修改 dist/project.config.json 文件
|
* @description 若不同环境有不同的小程序,可通过在 env 文件中配置环境变量`TARO_APP_ID`来方便快速切换 appid, 而不必手动去修改 dist/project.config.json 文件
|
||||||
|
|||||||
Reference in New Issue
Block a user