10 Commits

Author SHA1 Message Date
c47ebce43c fix: 修复取消活动后还可以编辑和取消、详情页参与者卡片展示NTRP 等级、生成海报的图片质量降低到1M以下, 详情页海报与测试结果页海报 2026-02-08 22:57:35 +08:00
b0f4b5713d Merge branch 'master' into feat/liujie 2026-02-08 21:25:19 +08:00
f7f10f5d15 查询下载账单 2026-02-08 16:03:08 +08:00
李瑞
2bcdd93479 Merge branch 'feat/juguohong/20260206' 2026-02-08 12:46:27 +08:00
李瑞
af2c472030 处理图片顺序 2026-02-08 12:46:01 +08:00
张成
8d0ed5b1b3 1 2026-02-08 12:36:21 +08:00
张成
e99986c52a 修改审核不通过的问题 2026-02-08 12:29:48 +08:00
张成
4b2f6707cc 1 2026-02-08 12:18:04 +08:00
张成
a019fe473b Merge branch 'master' of https://git.bimwe.com/bimwe/mini-programs 2026-02-08 12:14:11 +08:00
张成
1d0d2edaa2 修改项目 build 结构 2026-02-08 12:14:10 +08:00
22 changed files with 967 additions and 760 deletions

2
.env.dev Normal file
View File

@@ -0,0 +1,2 @@
APP_ENV=dev
TARO_APP_ID=wx815b533167eb7b53

2
.env.dev_local Normal file
View File

@@ -0,0 +1,2 @@
APP_ENV=dev_local
TARO_APP_ID=wx815b533167eb7b53

2
.env.pr Normal file
View File

@@ -0,0 +1,2 @@
APP_ENV=pr
TARO_APP_ID=wx915ecf6c01bea4ec

2
.env.sit Normal file
View File

@@ -0,0 +1,2 @@
APP_ENV=sit
TARO_APP_ID=wx815b533167eb7b53

79
config/env.config.ts Normal file
View 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];
}

View File

@@ -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: '/',

View File

@@ -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",

View 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})`);

View File

@@ -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;

View File

@@ -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>

View File

@@ -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>
);

View File

@@ -29,18 +29,24 @@ export interface RadarChartV2Ref {
}) => Promise<string>;
}
const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref) => {
const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>(
(props, ref) => {
const { data } = props;
const maxValue = 100;
const levels = 5;
// 在 exportCanvasV2 中绘制雷达图的函数
function drawRadarChart(ctx: CanvasRenderingContext2D, radarX: number, radarY: number, radarSize: number) {
function drawRadarChart(
ctx: CanvasRenderingContext2D,
radarX: number,
radarY: number,
radarSize: number,
) {
// 雷达图中心点位置radarSize 已经是2倍图尺寸
const center = {
x: radarX + radarSize / 2,
y: radarY + radarSize / 2
y: radarY + radarSize / 2,
};
// 计算实际半径radarSize 是直径,半径是直径的一半)
@@ -48,9 +54,9 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
// 启用抗锯齿
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.imageSmoothingQuality = "high";
ctx.lineCap = "round";
ctx.lineJoin = "round";
// 解析数据
const { texts, vals } = data.reduce(
@@ -61,7 +67,7 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
vals: [...res.vals, val],
};
},
{ texts: [], vals: [] }
{ texts: [], vals: [] },
);
// === 绘制圆形网格 ===
@@ -148,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) => {
(Taro as any).getImageInfo({
src,
success: (res: any) => resolve({ width: res.width, height: res.height }),
success: (res: any) =>
resolve({ width: res.width, height: res.height }),
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.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + 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.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
@@ -187,8 +207,7 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
useImperativeHandle(ref, () => ({
// 生成原始雷达图(已废弃,现在直接在 exportCanvasV2 中绘制)
generateImage: () =>
Promise.resolve(""),
generateImage: () => Promise.resolve(""),
// 生成完整图片(包含标题、雷达图、底部文字和二维码)
generateFullImage: async (options: {
@@ -229,7 +248,7 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
// 启用抗锯齿
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
ctx.imageSmoothingQuality = "high";
// 绘制背景 - 使用 share_bg.png 背景图,撑满整个画布(从 OSS 动态加载)
try {
@@ -253,18 +272,28 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
if (options.avatarUrl) {
try {
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 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;
// 绘制头像圆形背景
ctx.save();
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.fill();
ctx.strokeStyle = "#EFEFEF";
@@ -273,7 +302,8 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
// 计算头像绘制尺寸,保持宽高比
const innerSize = avatarSize - 1.94 * scale; // 内部可用尺寸
const avatarAspectRatio = avatarInfo.width / avatarInfo.height;
const avatarAspectRatio =
avatarInfo.width / avatarInfo.height;
let drawWidth = innerSize;
let drawHeight = innerSize;
let drawX = avatarX + 0.97 * scale;
@@ -292,9 +322,21 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
// 绘制头像(圆形裁剪)
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.drawImage(avatarImg, drawX, drawY, drawWidth, drawHeight);
ctx.drawImage(
avatarImg,
drawX,
drawY,
drawWidth,
drawHeight,
);
ctx.restore();
// 绘制装饰图片DocCopy- 在头像右侧
@@ -317,7 +359,14 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
const borderRadius = 9.66 * scale; // 设计稿圆角
ctx.fillStyle = "#FFFFFF";
ctx.beginPath();
roundRect(ctx, -addonSize / 2, -addonSize / 2, addonSize, addonSize, borderRadius);
roundRect(
ctx,
-addonSize / 2,
-addonSize / 2,
addonSize,
addonSize,
borderRadius,
);
ctx.fill();
// 添加渐变背景色
@@ -334,7 +383,13 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
const docSize = 26.18 * scale; // 设计稿内部图片尺寸
const docRotation = -7 * (Math.PI / 180); // 内部旋转 -7 度
ctx.rotate(docRotation);
ctx.drawImage(docCopyImg, -docSize / 2, -docSize / 2, docSize, docSize);
ctx.drawImage(
docCopyImg,
-docSize / 2,
-docSize / 2,
docSize,
docSize,
);
ctx.restore();
} catch (error) {
console.error("Failed to load docCopy image:", error);
@@ -409,7 +464,9 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
const qrX = 276 * scale; // 设计稿二维码 x 位置
const qrY = 523 * scale; // 设计稿二维码 y 位置
const bottomTextContent = options.bottomText || "长按识别二维码,快来加入,有你就有场!";
const bottomTextContent =
options.bottomText ||
"长按识别二维码,快来加入,有你就有场!";
// 绘制底部文字 - 设计稿fontSize: 12, fontWeight: 400, line-height: 1.52倍图
ctx.fillStyle = "rgba(0, 0, 0, 0.45)";
@@ -458,7 +515,13 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
const iconImg = await loadImage(canvas, shareLogoSvg);
// 图标位置:文字顶部上方 iconSize + gap
const iconY = textY - iconSize - iconGap;
ctx.drawImage(iconImg, topTitleX, iconY, 235 * scale, iconSize);
ctx.drawImage(
iconImg,
topTitleX,
iconY,
235 * scale,
iconSize,
);
} catch (error) {
console.error("Failed to load icon:", error);
}
@@ -468,7 +531,6 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
ctx.fillText(lineText, textX, textY + index * lineHeight);
});
// 绘制二维码 - 设计稿位置(带白色背景、边框、阴影和圆角)
if (options.qrCodeUrl) {
@@ -517,10 +579,23 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
// 绘制二维码图片(在圆角矩形内)
ctx.save();
// 创建圆角裁剪区域
roundRect(ctx, qrInnerX, qrInnerY, qrInnerSize, qrInnerSize, borderRadius - borderWidth);
roundRect(
ctx,
qrInnerX,
qrInnerY,
qrInnerSize,
qrInnerSize,
borderRadius - borderWidth,
);
ctx.clip();
// 绘制二维码图片
ctx.drawImage(qrImg, qrInnerX, qrInnerY, qrInnerSize, qrInnerSize);
ctx.drawImage(
qrImg,
qrInnerX,
qrInnerY,
qrInnerSize,
qrInnerSize,
);
ctx.restore();
// 恢复上下文状态
@@ -533,8 +608,8 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
// 导出图片
Taro.canvasToTempFilePath({
canvas,
fileType: 'png',
quality: 1,
fileType: "png",
quality: 0.7,
success: (res) => {
resolve(res.tempFilePath);
},
@@ -556,13 +631,19 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
<Canvas
type="2d"
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>
);
});
},
);
RadarChartV2.displayName = "RadarChartV2";
export default RadarChartV2;

View File

@@ -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,
// 从 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: 'ww51fc969e8b76af82', // 企业ID
serviceUrl: 'https://work.weixin.qq.com/kfid/kfc64085b93243c5c91',
}
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();

View File

@@ -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,12 +401,17 @@ export default function Participants(props) {
)}
</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["participants-title"]}>
<Text></Text>
<Text>·</Text>
<Text>{leftSubstituteCount > 0 ? `剩余空位 ${leftSubstituteCount}` : "已满员"}</Text>
<Text>
{leftSubstituteCount > 0
? `剩余空位 ${leftSubstituteCount}`
: "已满员"}
</Text>
</View>
<View className={styles["participants-list"]}>
{/* 候补申请入口 */}
@@ -420,7 +426,9 @@ export default function Participants(props) {
className={styles["participants-list-application-icon"]}
src={img.ICON_DETAIL_APPLICATION_ADD}
/>
<Text className={styles["participants-list-application-text"]}>
<Text
className={styles["participants-list-application-text"]}
>
</Text>
</View>
@@ -430,7 +438,7 @@ export default function Participants(props) {
refresherBackground="#FAFAFA"
className={classnames(
styles["participants-list-scroll"],
showSubstituteApplicationEntry ? styles.withApplication : ""
showSubstituteApplicationEntry ? styles.withApplication : "",
)}
scrollX
>
@@ -438,7 +446,8 @@ export default function Participants(props) {
className={styles["participants-list-scroll-content"]}
style={{
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`,
}}
>
@@ -471,13 +480,15 @@ export default function Participants(props) {
src={avatar_url}
onClick={handleViewUserInfo.bind(
null,
substitute_user_id
substitute_user_id,
)}
/>
<Text className={styles["participants-list-item-name"]}>
{nickname || "未知"}
</Text>
<Text className={styles["participants-list-item-level"]}>
<Text
className={styles["participants-list-item-level"]}
>
{displayNtrp}
</Text>
<Text className={styles["participants-list-item-role"]}>

View File

@@ -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;

View File

@@ -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}>

View File

@@ -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 {

View File

@@ -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";

View File

@@ -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");

View File

@@ -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="暂无数据" />}

View File

@@ -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
View File

@@ -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 文件