Merge branch 'feat/liujie'

This commit is contained in:
2026-02-08 22:57:48 +08:00
7 changed files with 715 additions and 610 deletions

View File

@@ -22,7 +22,7 @@ export interface EnvConfig {
const baseConfig = { const baseConfig = {
apiBaseURL: "https://tennis.bimwe.com", apiBaseURL: "https://tennis.bimwe.com",
ossBaseURL: "https://bimwe-oss.oss-cn-shanghai.aliyuncs.com", ossBaseURL: "https://bimwe.oss-cn-shanghai.aliyuncs.com",
appid: "wx815b533167eb7b53", // 测试号 appid: "wx815b533167eb7b53", // 测试号
timeout: 15000, timeout: 15000,
enableLog: true, enableLog: true,

View File

@@ -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;
@@ -109,4 +120,4 @@
} }
} }
} }
} }

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -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,97 +401,107 @@ export default function Participants(props) {
)} )}
</View> </View>
{/* 候补区域 */} {/* 候补区域 */}
{max_substitute_players > 0 && (substitute_count > 0 || showSubstituteApplicationEntry) && ( {max_substitute_players > 0 &&
<View className={styles["detail-page-content-participants"]}> (substitute_count > 0 || showSubstituteApplicationEntry) && (
<View className={styles["participants-title"]}> <View className={styles["detail-page-content-participants"]}>
<Text></Text> <View className={styles["participants-title"]}>
<Text>·</Text> <Text></Text>
<Text>{leftSubstituteCount > 0 ? `剩余空位 ${leftSubstituteCount}` : "已满员"}</Text> <Text>·</Text>
</View> <Text>
<View className={styles["participants-list"]}> {leftSubstituteCount > 0
{/* 候补申请入口 */} ? `剩余空位 ${leftSubstituteCount}`
{showSubstituteApplicationEntry && ( : "已满员"}
<View </Text>
className={styles["participants-list-application"]} </View>
onClick={() => { <View className={styles["participants-list"]}>
action?.(); {/* 候补申请入口 */}
}} {showSubstituteApplicationEntry && (
> <View
<Image className={styles["participants-list-application"]}
className={styles["participants-list-application-icon"]} onClick={() => {
src={img.ICON_DETAIL_APPLICATION_ADD} action?.();
/> }}
<Text className={styles["participants-list-application-text"]}> >
<Image
</Text> className={styles["participants-list-application-icon"]}
</View> src={img.ICON_DETAIL_APPLICATION_ADD}
)} />
{/* 候补成员列表 */} <Text
<ScrollView className={styles["participants-list-application-text"]}
refresherBackground="#FAFAFA" >
className={classnames(
styles["participants-list-scroll"], </Text>
showSubstituteApplicationEntry ? styles.withApplication : "" </View>
)} )}
scrollX {/* 候补成员列表 */}
> <ScrollView
<View refresherBackground="#FAFAFA"
className={styles["participants-list-scroll-content"]} className={classnames(
style={{ styles["participants-list-scroll"],
width: `${ showSubstituteApplicationEntry ? styles.withApplication : "",
Math.max(substitute_members.length, 1) * 103 + (Math.max(substitute_members.length, 1) - 1) * 8 )}
}px`, scrollX
}}
> >
{substitute_members.map((substitute) => { <View
const { className={styles["participants-list-scroll-content"]}
is_organizer, style={{
user: { width: `${
avatar_url, Math.max(substitute_members.length, 1) * 103 +
nickname, (Math.max(substitute_members.length, 1) - 1) * 8
level, }px`,
ntrp_level, }}
id: substitute_user_id, >
}, {substitute_members.map((substitute) => {
} = substitute; const {
const role = is_organizer ? "组织者" : "参与者"; is_organizer,
// 优先使用 ntrp_level如果没有则使用 level user: {
const ntrpValue = ntrp_level || level; avatar_url,
// 格式化显示 NTRP如果没有值则显示"初学者" nickname,
const displayNtrp = ntrpValue level,
? formatNtrpDisplay(ntrpValue) ntrp_level,
: "初学者"; id: substitute_user_id,
return ( },
<View } = substitute;
key={substitute.id} const role = is_organizer ? "组织者" : "参与者";
className={styles["participants-list-item"]} // 优先使用 ntrp_level如果没有则使用 level
> const ntrpValue = ntrp_level || level;
<Image // 格式化显示 NTRP如果没有值则显示"初学者"
className={styles["participants-list-item-avatar"]} const displayNtrp = ntrpValue
mode="aspectFill" ? formatNtrpDisplay(ntrpValue)
src={avatar_url} : "初学者";
onClick={handleViewUserInfo.bind( return (
null, <View
substitute_user_id key={substitute.id}
)} className={styles["participants-list-item"]}
/> >
<Text className={styles["participants-list-item-name"]}> <Image
{nickname || "未知"} className={styles["participants-list-item-avatar"]}
</Text> mode="aspectFill"
<Text className={styles["participants-list-item-level"]}> src={avatar_url}
{displayNtrp} onClick={handleViewUserInfo.bind(
</Text> null,
<Text className={styles["participants-list-item-role"]}> substitute_user_id,
{role} )}
</Text> />
</View> <Text className={styles["participants-list-item-name"]}>
); {nickname || "未知"}
})} </Text>
</View> <Text
</ScrollView> className={styles["participants-list-item-level"]}
>
{displayNtrp}
</Text>
<Text className={styles["participants-list-item-role"]}>
{role}
</Text>
</View>
);
})}
</View>
</ScrollView>
</View>
</View> </View>
</View> )}
)}
<NTRPEvaluatePopup type={EvaluateScene.detail} ref={ntrpRef} showGuide /> <NTRPEvaluatePopup type={EvaluateScene.detail} ref={ntrpRef} showGuide />
</> </>
); );

View File

@@ -16,7 +16,7 @@ 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 { OSS_BASE } from "@/config/api"; import { OSS_BASE } from "@/config/api";
import CloseIcon from "@/static/ntrp/ntrp_close_icon.svg"; import CloseIcon from "@/static/ntrp/ntrp_close_icon.svg";

View File

@@ -282,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;
@@ -433,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;
} }