Compare commits
10 Commits
5926e096b5
...
feat/liuji
| Author | SHA1 | Date | |
|---|---|---|---|
| c47ebce43c | |||
| b0f4b5713d | |||
| f7f10f5d15 | |||
|
|
2bcdd93479 | ||
|
|
af2c472030 | ||
|
|
8d0ed5b1b3 | ||
|
|
e99986c52a | ||
|
|
4b2f6707cc | ||
|
|
a019fe473b | ||
|
|
1d0d2edaa2 |
2
.env.dev_local
Normal file
2
.env.dev_local
Normal file
@@ -0,0 +1,2 @@
|
||||
APP_ENV=dev_local
|
||||
TARO_APP_ID=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 devConfig from './dev'
|
||||
import prodConfig from './prod'
|
||||
// import vitePluginImp from 'vite-plugin-imp'
|
||||
import { getEnvConfig, type EnvType } from './env.config'
|
||||
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-辅助函数
|
||||
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'> = {
|
||||
projectName: 'playBallTogether',
|
||||
date: '2025-8-9',
|
||||
@@ -22,6 +32,13 @@ export default defineConfig<'webpack5'>(async (merge, { command, mode }) => {
|
||||
outputRoot: 'dist',
|
||||
plugins: ['@tarojs/plugin-html'],
|
||||
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: {
|
||||
'@': path.resolve(__dirname, '..', 'src'),
|
||||
@@ -76,6 +93,9 @@ export default defineConfig<'webpack5'>(async (merge, { command, mode }) => {
|
||||
},
|
||||
// @ts-expect-error: Taro 类型定义缺少 mini.hot
|
||||
hot: true,
|
||||
projectConfig: {
|
||||
appid: envConfig.appid,
|
||||
},
|
||||
},
|
||||
h5: {
|
||||
publicPath: '/',
|
||||
|
||||
35
package.json
35
package.json
@@ -10,32 +10,17 @@
|
||||
"framework": "React"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "npm run build:weapp ",
|
||||
"dev": "npm run dev:weapp ",
|
||||
"build:weapp": "taro build --type weapp --mode production",
|
||||
"build:swan": "taro build --type swan",
|
||||
"build:alipay": "taro build --type alipay",
|
||||
"build:tt": "taro build --type tt",
|
||||
"build:h5": "taro build --type h5",
|
||||
"build:rn": "taro build --type rn",
|
||||
"build:qq": "taro build --type qq",
|
||||
"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"
|
||||
"dev": "npm run dev:weapp",
|
||||
"dev:local": "npm run dev:weapp:dev_local",
|
||||
"dev:weapp": "node scripts/sync-project-config.js dev && taro build --type weapp --mode dev --watch",
|
||||
"dev:weapp:dev_local": "node scripts/sync-project-config.js dev_local && taro build --type weapp --mode dev_local --watch",
|
||||
"build": "npm run build:weapp",
|
||||
"build:weapp": "node scripts/sync-project-config.js pr && taro build --type weapp --mode pr",
|
||||
"build:sit": "node scripts/sync-project-config.js sit && taro build --type weapp --mode sit",
|
||||
"build:pr": "node scripts/sync-project-config.js pr && taro build --type weapp --mode pr",
|
||||
"dev:h5": "npm run build:h5 -- --watch"
|
||||
},
|
||||
"browserslist": [
|
||||
"last 3 versions",
|
||||
"Android >= 4.1",
|
||||
"ios >= 8"
|
||||
],
|
||||
"browserslist": ["last 3 versions", "Android >= 4.1", "ios >= 8"],
|
||||
"author": "",
|
||||
"dependencies": {
|
||||
"@babel/plugin-transform-runtime": "^7.28.3",
|
||||
|
||||
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})`);
|
||||
@@ -13,7 +13,9 @@
|
||||
align-items: center;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
font-feature-settings: 'liga' off, 'clig' off;
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
@@ -32,7 +34,9 @@
|
||||
padding-top: 24px;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
font-feature-settings: 'liga' off, 'clig' off;
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
@@ -48,8 +52,10 @@
|
||||
align-items: center;
|
||||
|
||||
.tips {
|
||||
color: rgba(60, 60, 67, 0.60);
|
||||
font-feature-settings: 'liga' off, 'clig' off;
|
||||
color: rgba(60, 60, 67, 0.6);
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
@@ -62,13 +68,15 @@
|
||||
margin-top: 8px;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
background: #F0F0F0;
|
||||
background: #f0f0f0;
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
&:placeholder-shown {
|
||||
color: rgba(60, 60, 67, 0.30);
|
||||
font-feature-settings: 'liga' off, 'clig' off;
|
||||
color: rgba(60, 60, 67, 0.3);
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
@@ -84,11 +92,12 @@
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 44px;
|
||||
border-top: 0.5px solid #CECECE;
|
||||
background: #FFF;
|
||||
border-top: 0.5px solid #cecece;
|
||||
background: #fff;
|
||||
margin-top: 2px;
|
||||
|
||||
.confirm, .cancel {
|
||||
.confirm,
|
||||
.cancel {
|
||||
width: 50%;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
@@ -96,7 +105,9 @@
|
||||
align-items: center;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
font-feature-settings: 'liga' off, 'clig' off;
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
|
||||
@@ -186,7 +186,7 @@ export default forwardRef(function GameManagePopup(props, ref) {
|
||||
.some((item) => item.user.id === userInfo.id);
|
||||
|
||||
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;
|
||||
@@ -207,7 +207,7 @@ export default forwardRef(function GameManagePopup(props, ref) {
|
||||
style={{ minHeight: "unset" }}
|
||||
>
|
||||
<View className={styles.container}>
|
||||
{!inTwoHours && !hasOtherParticiappants && (
|
||||
{!finished && !inTwoHours && !hasOtherParticiappants && (
|
||||
<View className={styles.button} onClick={handleEditGame}>
|
||||
编辑活动
|
||||
</View>
|
||||
@@ -217,12 +217,12 @@ export default forwardRef(function GameManagePopup(props, ref) {
|
||||
重新发布
|
||||
</View>
|
||||
)}
|
||||
{!inTwoHours && !hasOtherParticiappants && (
|
||||
{!finished && !inTwoHours && !hasOtherParticiappants && (
|
||||
<View className={styles.button} onClick={handleCancelGame}>
|
||||
取消活动
|
||||
</View>
|
||||
)}
|
||||
{hasJoin && (
|
||||
{!finished && hasJoin && (
|
||||
<View className={styles.button} onClick={handleQuitGame}>
|
||||
退出活动
|
||||
</View>
|
||||
|
||||
@@ -127,10 +127,10 @@ const ListCard: React.FC<ListCardProps> = ({
|
||||
return (
|
||||
<View className="double-image">
|
||||
<View className="image-container">
|
||||
{renderItemImage(image_list?.[0])}
|
||||
{renderItemImage(image_list?.[1])}
|
||||
</View>
|
||||
<View className="image-container">
|
||||
{renderItemImage(image_list?.[1])}
|
||||
{renderItemImage(image_list?.[0])}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,135 +1,61 @@
|
||||
import Taro from '@tarojs/taro'
|
||||
import Taro from "@tarojs/taro";
|
||||
|
||||
// 环境类型
|
||||
export type EnvType = 'development' | 'production'
|
||||
/**
|
||||
* 环境配置:从 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
|
||||
// 客服配置
|
||||
name: string;
|
||||
apiBaseURL: string;
|
||||
ossBaseURL: string;
|
||||
timeout: number;
|
||||
enableLog: boolean;
|
||||
enableMock: boolean;
|
||||
customerService: {
|
||||
corpId: string
|
||||
serviceUrl: string
|
||||
phoneNumber?: string
|
||||
email?: string
|
||||
}
|
||||
corpId: string;
|
||||
serviceUrl: string;
|
||||
};
|
||||
}
|
||||
|
||||
// 各环境配置
|
||||
const envConfigs: Record<EnvType, EnvConfig> = {
|
||||
|
||||
|
||||
// 开发环境
|
||||
development: {
|
||||
name: '开发环境',
|
||||
apiBaseURL: 'https://tennis.bimwe.com',
|
||||
ossBaseURL: 'https://bimwe.oss-cn-shanghai.aliyuncs.com',
|
||||
//apiBaseURL: 'http://localhost:9098',
|
||||
timeout: 15000,
|
||||
enableLog: true,
|
||||
enableMock: false,
|
||||
// 客服配置
|
||||
customerService: {
|
||||
corpId: 'ww51fc969e8b76af82', // 企业ID
|
||||
serviceUrl: 'https://work.weixin.qq.com/kfid/kfc64085b93243c5c91',
|
||||
|
||||
}
|
||||
// 从 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";
|
||||
|
||||
|
||||
|
||||
// 生产环境1
|
||||
production: {
|
||||
name: '生产环境1',
|
||||
apiBaseURL: 'https://tennis.bimwe.com',
|
||||
ossBaseURL: 'https://bimwe.oss-cn-shanghai.aliyuncs.com',
|
||||
timeout: 10000,
|
||||
enableLog: false,
|
||||
enableMock: false,
|
||||
// 客服配置
|
||||
customerService: {
|
||||
corpId: 'ww51fc969e8b76af82', // 企业ID
|
||||
serviceUrl: 'https://work.weixin.qq.com/kfid/kfc64085b93243c5c91',
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
//生产环境2
|
||||
// production: {
|
||||
// name: '生产环境2',
|
||||
// apiBaseURL: 'https://youchang.qiongjingtiyu.com',
|
||||
// ossBaseURL: 'https://youchang2026.oss-cn-shanghai.aliyuncs.com',
|
||||
// timeout: 10000,
|
||||
// enableLog: false,
|
||||
// enableMock: false,
|
||||
// // 客服配置
|
||||
// customerService: {
|
||||
// corpId: 'ww9a2d9a5d9410c664', // 企业ID
|
||||
// serviceUrl: 'https://work.weixin.qq.com/kfid/kfcd355e162e0390684',
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
// 获取当前环境
|
||||
export const getCurrentEnv = (): EnvType => {
|
||||
// 在小程序环境中,使用默认逻辑判断环境
|
||||
// 可以根据实际需要配置不同的判断逻辑
|
||||
|
||||
// 可以根据实际部署情况添加更多判断逻辑
|
||||
// 比如通过 Taro.getEnv() 获取当前平台环境
|
||||
|
||||
const isProd = process.env.NODE_ENV === 'production'
|
||||
if (isProd) {
|
||||
return 'production'
|
||||
} else {
|
||||
return 'development'
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前环境配置
|
||||
export const getCurrentConfig = (): EnvConfig => {
|
||||
const env = getCurrentEnv()
|
||||
return envConfigs[env]
|
||||
}
|
||||
|
||||
// 获取指定环境配置
|
||||
export const getEnvConfig = (env: EnvType): EnvConfig => {
|
||||
return envConfigs[env]
|
||||
}
|
||||
|
||||
// 是否为开发环境
|
||||
export const isDevelopment = (): boolean => {
|
||||
return getCurrentEnv() === 'development'
|
||||
}
|
||||
|
||||
// 是否为生产环境
|
||||
export const isProduction = (): boolean => {
|
||||
return getCurrentEnv() === 'production'
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 环境配置调试信息
|
||||
export const getEnvInfo = () => {
|
||||
const config = getCurrentConfig()
|
||||
const config = getCurrentConfig();
|
||||
return {
|
||||
env: getCurrentEnv(),
|
||||
config,
|
||||
taroEnv: Taro.getEnv(),
|
||||
platform: Taro.getEnv() === Taro.ENV_TYPE.WEAPP ? '微信小程序' :
|
||||
Taro.getEnv() === Taro.ENV_TYPE.WEB ? 'Web' :
|
||||
Taro.getEnv() === Taro.ENV_TYPE.RN ? 'React Native' : '未知'
|
||||
}
|
||||
}
|
||||
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()
|
||||
export default getCurrentConfig();
|
||||
|
||||
@@ -40,7 +40,7 @@ function isFull(counts) {
|
||||
function matchNtrpRequestment(
|
||||
target?: string,
|
||||
min?: string,
|
||||
max?: string
|
||||
max?: string,
|
||||
): boolean {
|
||||
// 目标值为空或 undefined
|
||||
if (!target?.trim()) return true;
|
||||
@@ -110,7 +110,7 @@ export default function Participants(props) {
|
||||
user_action_status;
|
||||
const showApplicationEntry =
|
||||
[can_pay, can_substitute, is_substituting, waiting_start].every(
|
||||
(item) => !item
|
||||
(item) => !item,
|
||||
) &&
|
||||
can_join &&
|
||||
dayjs(start_time).isAfter(dayjs());
|
||||
@@ -138,7 +138,7 @@ export default function Participants(props) {
|
||||
|
||||
Taro.navigateTo({
|
||||
url: `/login_pages/index/index?redirect=${encodeURIComponent(
|
||||
fullPath
|
||||
fullPath,
|
||||
)}`,
|
||||
});
|
||||
}
|
||||
@@ -153,7 +153,7 @@ export default function Participants(props) {
|
||||
const matchNtrpReq = matchNtrpRequestment(
|
||||
userInfo?.ntrp_level,
|
||||
skill_level_min,
|
||||
skill_level_max
|
||||
skill_level_max,
|
||||
);
|
||||
|
||||
function handleSelfEvaluate() {
|
||||
@@ -180,7 +180,7 @@ export default function Participants(props) {
|
||||
}
|
||||
|
||||
function generateTextAndAction(
|
||||
user_action_status: null | { [key: string]: boolean }
|
||||
user_action_status: null | { [key: string]: boolean },
|
||||
):
|
||||
| undefined
|
||||
| { text: string | React.FC; action?: () => void; available?: boolean } {
|
||||
@@ -259,7 +259,7 @@ export default function Participants(props) {
|
||||
const res = await OrderService.getUnpaidOrder(id);
|
||||
if (res.code === 0) {
|
||||
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 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 =
|
||||
[can_pay, can_join, is_substituting, waiting_start].every(
|
||||
(item) => !item
|
||||
(item) => !item,
|
||||
) &&
|
||||
can_substitute &&
|
||||
dayjs(start_time).isAfter(dayjs());
|
||||
@@ -336,7 +337,7 @@ export default function Participants(props) {
|
||||
refresherBackground="#FAFAFA"
|
||||
className={classnames(
|
||||
styles["participants-list-scroll"],
|
||||
showApplicationEntry ? styles.withApplication : ""
|
||||
showApplicationEntry ? styles.withApplication : "",
|
||||
)}
|
||||
scrollX
|
||||
>
|
||||
@@ -377,14 +378,14 @@ export default function Participants(props) {
|
||||
src={avatar_url}
|
||||
onClick={handleViewUserInfo.bind(
|
||||
null,
|
||||
participant_user_id
|
||||
participant_user_id,
|
||||
)}
|
||||
/>
|
||||
<Text className={styles["participants-list-item-name"]}>
|
||||
{nickname || "未知"}
|
||||
</Text>
|
||||
<Text className={styles["participants-list-item-level"]}>
|
||||
{displayNtrp}
|
||||
NTRP {displayNtrp}
|
||||
</Text>
|
||||
<Text className={styles["participants-list-item-role"]}>
|
||||
{role}
|
||||
@@ -400,97 +401,107 @@ export default function Participants(props) {
|
||||
)}
|
||||
</View>
|
||||
{/* 候补区域 */}
|
||||
{max_substitute_players > 0 && (substitute_count > 0 || showSubstituteApplicationEntry) && (
|
||||
<View className={styles["detail-page-content-participants"]}>
|
||||
<View className={styles["participants-title"]}>
|
||||
<Text>候补</Text>
|
||||
<Text>·</Text>
|
||||
<Text>{leftSubstituteCount > 0 ? `剩余空位 ${leftSubstituteCount}` : "已满员"}</Text>
|
||||
</View>
|
||||
<View className={styles["participants-list"]}>
|
||||
{/* 候补申请入口 */}
|
||||
{showSubstituteApplicationEntry && (
|
||||
<View
|
||||
className={styles["participants-list-application"]}
|
||||
onClick={() => {
|
||||
action?.();
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
className={styles["participants-list-application-icon"]}
|
||||
src={img.ICON_DETAIL_APPLICATION_ADD}
|
||||
/>
|
||||
<Text className={styles["participants-list-application-text"]}>
|
||||
申请候补
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
{/* 候补成员列表 */}
|
||||
<ScrollView
|
||||
refresherBackground="#FAFAFA"
|
||||
className={classnames(
|
||||
styles["participants-list-scroll"],
|
||||
showSubstituteApplicationEntry ? styles.withApplication : ""
|
||||
{max_substitute_players > 0 &&
|
||||
(substitute_count > 0 || showSubstituteApplicationEntry) && (
|
||||
<View className={styles["detail-page-content-participants"]}>
|
||||
<View className={styles["participants-title"]}>
|
||||
<Text>候补</Text>
|
||||
<Text>·</Text>
|
||||
<Text>
|
||||
{leftSubstituteCount > 0
|
||||
? `剩余空位 ${leftSubstituteCount}`
|
||||
: "已满员"}
|
||||
</Text>
|
||||
</View>
|
||||
<View className={styles["participants-list"]}>
|
||||
{/* 候补申请入口 */}
|
||||
{showSubstituteApplicationEntry && (
|
||||
<View
|
||||
className={styles["participants-list-application"]}
|
||||
onClick={() => {
|
||||
action?.();
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
className={styles["participants-list-application-icon"]}
|
||||
src={img.ICON_DETAIL_APPLICATION_ADD}
|
||||
/>
|
||||
<Text
|
||||
className={styles["participants-list-application-text"]}
|
||||
>
|
||||
申请候补
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
scrollX
|
||||
>
|
||||
<View
|
||||
className={styles["participants-list-scroll-content"]}
|
||||
style={{
|
||||
width: `${
|
||||
Math.max(substitute_members.length, 1) * 103 + (Math.max(substitute_members.length, 1) - 1) * 8
|
||||
}px`,
|
||||
}}
|
||||
{/* 候补成员列表 */}
|
||||
<ScrollView
|
||||
refresherBackground="#FAFAFA"
|
||||
className={classnames(
|
||||
styles["participants-list-scroll"],
|
||||
showSubstituteApplicationEntry ? styles.withApplication : "",
|
||||
)}
|
||||
scrollX
|
||||
>
|
||||
{substitute_members.map((substitute) => {
|
||||
const {
|
||||
is_organizer,
|
||||
user: {
|
||||
avatar_url,
|
||||
nickname,
|
||||
level,
|
||||
ntrp_level,
|
||||
id: substitute_user_id,
|
||||
},
|
||||
} = substitute;
|
||||
const role = is_organizer ? "组织者" : "参与者";
|
||||
// 优先使用 ntrp_level,如果没有则使用 level
|
||||
const ntrpValue = ntrp_level || level;
|
||||
// 格式化显示 NTRP,如果没有值则显示"初学者"
|
||||
const displayNtrp = ntrpValue
|
||||
? formatNtrpDisplay(ntrpValue)
|
||||
: "初学者";
|
||||
return (
|
||||
<View
|
||||
key={substitute.id}
|
||||
className={styles["participants-list-item"]}
|
||||
>
|
||||
<Image
|
||||
className={styles["participants-list-item-avatar"]}
|
||||
mode="aspectFill"
|
||||
src={avatar_url}
|
||||
onClick={handleViewUserInfo.bind(
|
||||
null,
|
||||
substitute_user_id
|
||||
)}
|
||||
/>
|
||||
<Text className={styles["participants-list-item-name"]}>
|
||||
{nickname || "未知"}
|
||||
</Text>
|
||||
<Text className={styles["participants-list-item-level"]}>
|
||||
{displayNtrp}
|
||||
</Text>
|
||||
<Text className={styles["participants-list-item-role"]}>
|
||||
{role}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</ScrollView>
|
||||
<View
|
||||
className={styles["participants-list-scroll-content"]}
|
||||
style={{
|
||||
width: `${
|
||||
Math.max(substitute_members.length, 1) * 103 +
|
||||
(Math.max(substitute_members.length, 1) - 1) * 8
|
||||
}px`,
|
||||
}}
|
||||
>
|
||||
{substitute_members.map((substitute) => {
|
||||
const {
|
||||
is_organizer,
|
||||
user: {
|
||||
avatar_url,
|
||||
nickname,
|
||||
level,
|
||||
ntrp_level,
|
||||
id: substitute_user_id,
|
||||
},
|
||||
} = substitute;
|
||||
const role = is_organizer ? "组织者" : "参与者";
|
||||
// 优先使用 ntrp_level,如果没有则使用 level
|
||||
const ntrpValue = ntrp_level || level;
|
||||
// 格式化显示 NTRP,如果没有值则显示"初学者"
|
||||
const displayNtrp = ntrpValue
|
||||
? formatNtrpDisplay(ntrpValue)
|
||||
: "初学者";
|
||||
return (
|
||||
<View
|
||||
key={substitute.id}
|
||||
className={styles["participants-list-item"]}
|
||||
>
|
||||
<Image
|
||||
className={styles["participants-list-item-avatar"]}
|
||||
mode="aspectFill"
|
||||
src={avatar_url}
|
||||
onClick={handleViewUserInfo.bind(
|
||||
null,
|
||||
substitute_user_id,
|
||||
)}
|
||||
/>
|
||||
<Text className={styles["participants-list-item-name"]}>
|
||||
{nickname || "未知"}
|
||||
</Text>
|
||||
<Text
|
||||
className={styles["participants-list-item-level"]}
|
||||
>
|
||||
{displayNtrp}
|
||||
</Text>
|
||||
<Text className={styles["participants-list-item-role"]}>
|
||||
{role}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
)}
|
||||
<NTRPEvaluatePopup type={EvaluateScene.detail} ref={ntrpRef} showGuide />
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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 {
|
||||
position: absolute;
|
||||
|
||||
@@ -155,6 +155,11 @@ const LoginPage: React.FC = () => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
// 返回首页
|
||||
const handle_return_home = () => {
|
||||
Taro.navigateTo({ url: "/main_pages/index" });
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="login_page">
|
||||
<View className="background_image">
|
||||
@@ -211,6 +216,10 @@ const LoginPage: React.FC = () => {
|
||||
<Text className="button_text">手机号快捷登录</Text>
|
||||
</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="checkbox_container" onClick={handle_toggle_terms}>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
.enable_notification_page {
|
||||
width: 100%;
|
||||
// min-height: 100vh;
|
||||
// background: radial-gradient(circle at 50% 0%, rgba(191, 255, 239, 1) 0%, rgba(255, 255, 255, 1) 37%);
|
||||
height: 100%;
|
||||
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;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -34,6 +36,7 @@
|
||||
box-shadow: 0px 4px 20px 0px rgba(0, 0, 0, 0.08);
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
background: #ffffff;
|
||||
|
||||
// 第三个卡片(最上面)
|
||||
&--3 {
|
||||
|
||||
@@ -16,7 +16,7 @@ import { useGlobalState } from "@/store/global";
|
||||
import { delay, getCurrentFullPath } from "@/utils";
|
||||
import { formatNtrpDisplay } from "@/utils/helper";
|
||||
import { waitForAuthInit } from "@/utils/authInit";
|
||||
import httpService from "@/services/httpService";
|
||||
// import httpService from "@/services/httpService";
|
||||
import DetailService from "@/services/detailService";
|
||||
import { OSS_BASE } from "@/config/api";
|
||||
import CloseIcon from "@/static/ntrp/ntrp_close_icon.svg";
|
||||
|
||||
@@ -263,7 +263,7 @@ export const save_login_state = (token: string, user_info: WechatUserInfo) => {
|
||||
export const clear_login_state = () => {
|
||||
try {
|
||||
// 使用 tokenManager 清除令牌
|
||||
tokenManager.clearTokens();
|
||||
// tokenManager.clearTokens();
|
||||
|
||||
// 清除其他登录状态
|
||||
Taro.removeStorageSync("user_info");
|
||||
|
||||
@@ -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 (
|
||||
<View className="download-bill-records-page">
|
||||
{/* 导航栏 */}
|
||||
@@ -111,7 +139,7 @@ const DownloadBillRecords: React.FC = () => {
|
||||
</View>
|
||||
<View className="info-item">
|
||||
<Text></Text>
|
||||
<Text className="btn">查看材料</Text>
|
||||
<Text className="btn" onClick={() => handlePreviewFile(record.file_url)}>查看材料</Text>
|
||||
</View>
|
||||
</View>
|
||||
)) : <EmptyState text="暂无数据" />}
|
||||
|
||||
@@ -282,7 +282,9 @@ function drawTextWrap(
|
||||
/** 核心纯函数:生成海报图片 */
|
||||
export async function generatePosterImage(data: any): Promise<string> {
|
||||
console.log("start !!!!");
|
||||
const dpr = Taro.getWindowInfo().pixelRatio;
|
||||
// const dpr = Taro.getWindowInfo().pixelRatio;
|
||||
const dpr = 1;
|
||||
// console.log(dpr, 'dpr')
|
||||
const width = 600;
|
||||
const height = 1000;
|
||||
|
||||
@@ -433,7 +435,7 @@ export async function generatePosterImage(data: any): Promise<string> {
|
||||
const { tempFilePath } = await Taro.canvasToTempFilePath({
|
||||
canvas,
|
||||
fileType: 'png',
|
||||
quality: 1,
|
||||
quality: 0.7,
|
||||
});
|
||||
return tempFilePath;
|
||||
}
|
||||
|
||||
2
types/global.d.ts
vendored
2
types/global.d.ts
vendored
@@ -17,6 +17,8 @@ declare namespace NodeJS {
|
||||
NODE_ENV: 'development' | 'production',
|
||||
/** 当前构建的平台 */
|
||||
TARO_ENV: 'weapp' | 'swan' | 'alipay' | 'h5' | 'rn' | 'tt' | 'quickapp' | 'qq' | 'jd'
|
||||
/** 应用环境标识 */
|
||||
APP_ENV: 'dev' | 'dev_local' | 'sit' | 'pr'
|
||||
/**
|
||||
* 当前构建的小程序 appid
|
||||
* @description 若不同环境有不同的小程序,可通过在 env 文件中配置环境变量`TARO_APP_ID`来方便快速切换 appid, 而不必手动去修改 dist/project.config.json 文件
|
||||
|
||||
Reference in New Issue
Block a user