16 Commits

Author SHA1 Message Date
8090d679b4 Merge remote-tracking branch 'refs/remotes/origin/master' 2026-04-09 14:58:43 +08:00
4965d6c40e fix: 分享卡片走兜底图优化 2026-04-09 14:58:02 +08:00
56fb3ade00 fix: unify game time formatting and game length display in share poster 2026-04-06 22:16:44 +08:00
8c6fb1190e fix: 修复球局详情页组织者组织球局title换行问题 2026-03-28 10:52:11 +08:00
李瑞
4dc8e84f5c 修改日期筛选交互 2026-03-27 22:30:46 +08:00
b84c3bb409 fix: 时间展示修复 2026-03-25 06:03:07 +08:00
fa41842e75 fix: 逻辑补全 2026-03-20 23:24:22 +08:00
3bbb64d58c style: 修复地址展示的问题 2026-03-20 23:10:23 +08:00
58cf46e93d fix: 修复订单详情页球局时间展示问题、修复候补列表NTRP文案缺失的问题 2026-03-20 22:56:18 +08:00
张成
8004b26bd1 换logo 2026-03-19 15:00:08 +08:00
张成
98baa371ee 1 2026-03-16 11:25:50 +08:00
张成
f87859da0e 1 2026-03-11 15:33:26 +08:00
d3390d5e81 fix: debug 分享卡片生成问题 2026-03-11 11:48:40 +08:00
筱野
883ce3c2c4 修改标题换行替换 2026-03-10 21:34:45 +08:00
张成
63bcf6fe86 Merge branch 'fix/jgh/0310' 2026-03-10 11:35:19 +08:00
a68da08c85 feat: 订单详情页退款政策和报名须知修改文案和页面结构 2026-03-10 11:09:32 +08:00
19 changed files with 611 additions and 158 deletions

View File

@@ -32,36 +32,52 @@ const FilterPopup = (props: FilterPopupProps) => {
const { timeBubbleData, gamesNum } = store; const { timeBubbleData, gamesNum } = store;
/** /**
* @description 处理字典选项 * @description 日期排序
* @param dictionaryValue 字典选项 * @param a 日期字符串
* @returns 选项列表 * @param b 日期字符串
* @returns 日期差值
*/ */
// const [selectedDates, setSelectedDates] = useState<String[]>([]) const sortByDate = (a: string, b: string) => {
return new Date(a).getTime() - new Date(b).getTime();
}
const handleDateChange = (dates: Date[]) => { const handleDateChange = (dates: Date[]) => {
let times: String[] = []; // ================================ 日期处理 ================================
if (dates.length > 1) { // 默认是是当前日期为开始日期,结束日期为当前日期 + 30天
times = [dayjs(dates[0]).format('YYYY-MM-DD'), dayjs(dates[dates.length - 1]).format('YYYY-MM-DD')] const defaultDateRange = [dayjs().format('YYYY-MM-DD'), dayjs().add(1, 'M').format('YYYY-MM-DD')];
onChange({ // 处理空数组的情况
'dateRange': times, if (!dates.length) {
}) onChange({ dateRange: defaultDateRange });
return; return;
} }
if (Array.isArray(dates)) { // 处理多日期范围选择超过1个日期
if (dates.length > 1) {
const currentDay = dayjs(dates[0]).format('YYYY-MM-DD'); const dateRange = [
if (filterOptions.dateRange.length === 0 || filterOptions.dateRange.length === 2) { dayjs(dates[0]).format('YYYY-MM-DD'),
times.push(currentDay); dayjs(dates[dates.length - 1]).format('YYYY-MM-DD')
} else { ];
times = [...filterOptions.dateRange, currentDay].sort( onChange({ dateRange });
(a, b) => new Date(a).getTime() - new Date(b).getTime() return;
)
}
} }
// 处理单个日期选择
onChange({ const currentFilterOptionsDateRange = Array.isArray(filterOptions?.dateRange)
'dateRange': times, ? filterOptions.dateRange
}) : defaultDateRange;
// 当前选择的日期
const currentDay = dayjs(dates?.[0]).format('YYYY-MM-DD');
// 当 dates 每次只返回单个日期时,使用已选范围判断是“第一次点”还是“第二次点”
let dateRange: string[];
if (
currentFilterOptionsDateRange.length === 2 &&
currentFilterOptionsDateRange?.[0] === currentFilterOptionsDateRange?.[1]
) {
// 已是单日,点击当前日期扩展为日期范围
dateRange = [currentFilterOptionsDateRange[0], currentDay].sort(sortByDate);
} else {
// 默认区间/已选区间/异常状态,点击当前日期统一收敛为单日
dateRange = [currentDay, currentDay];
}
onChange({ dateRange });
} }
const handleOptions = (dictionaryValue: []) => { const handleOptions = (dictionaryValue: []) => {

View File

@@ -1,41 +1,13 @@
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
import dayjs, { Dayjs } from "dayjs"; import dayjs, { Dayjs } from "dayjs";
import "dayjs/locale/zh-cn"; import "dayjs/locale/zh-cn";
import { calculateDistance } from "@/utils"; import { calculateDistance, genGameLength } from "@/utils";
import { View, Image, Text, Map } from "@tarojs/components"; import { View, Image, Text, Map } from "@tarojs/components";
import img from "@/config/images"; import img from "@/config/images";
import styles from "./index.module.scss"; import styles from "./index.module.scss";
dayjs.locale("zh-cn"); dayjs.locale("zh-cn");
function genGameLength(startTime: Dayjs, endTime: Dayjs) {
if (!startTime || !endTime) {
return "";
}
const totalMinutes = endTime.diff(startTime, "minute");
const totalHours = totalMinutes / 60;
if (totalHours >= 24) {
const days = Math.floor(totalHours / 24);
const remainingHours = totalHours % 24;
if (remainingHours === 0) {
return `${days}`;
}
// 保留一位小数
const displayHours = parseFloat(remainingHours.toFixed(1));
return `${days}${displayHours}小时`;
}
// 如果是整数小时,不显示小数点
if (Number.isInteger(totalHours)) {
return `${totalHours}小时`;
}
// 保留一位小数去除末尾的0
return `${parseFloat(totalHours.toFixed(1))}小时`;
}
function genGameRange(startTime: Dayjs, endTime: Dayjs) { function genGameRange(startTime: Dayjs, endTime: Dayjs) {
if (!startTime || !endTime) { if (!startTime || !endTime) {
return ""; return "";

View File

@@ -177,23 +177,32 @@
} }
&-title { &-title {
display: flex; display: flex;
align-items: center; align-items: center;
height: 24px; height: 24px;
gap: 2px; gap: 2px;
overflow: hidden; overflow: hidden;
color: rgba(255, 255, 255, 0.85); color: rgba(255, 255, 255, 0.85);
text-overflow: ellipsis; text-overflow: ellipsis;
font-size: 16px; font-size: 16px;
font-style: normal; font-style: normal;
font-weight: 600; font-weight: 600;
line-height: 24px; /* 150% */ line-height: 24px; /* 150% */
&-arrow { &-text {
width: 12px; flex: 1;
height: 12px; min-width: 0;
} overflow: hidden;
} text-overflow: ellipsis;
white-space: nowrap;
}
&-arrow {
flex: 0 0 12px;
width: 12px;
height: 12px;
}
}
&-time-range { &-time-range {
overflow: hidden; overflow: hidden;

View File

@@ -86,12 +86,12 @@ export default function OrganizerInfo(props) {
await LoginService.followUser(id); await LoginService.followUser(id);
} }
onUpdateUserInfo(); onUpdateUserInfo();
Taro.showToast({ (Taro as any).showToast({
title: `${nickname} ${follow ? "已取消关注" : "已关注"}`, title: `${nickname} ${follow ? "已取消关注" : "已关注"}`,
icon: "success", icon: "success",
}); });
} catch (e) { } catch (e) {
Taro.showToast({ (Taro as any).showToast({
title: `${nickname} ${follow ? "取消关注失败" : "关注失败"}`, title: `${nickname} ${follow ? "取消关注失败" : "关注失败"}`,
icon: "error", icon: "error",
}); });
@@ -193,13 +193,17 @@ export default function OrganizerInfo(props) {
className={styles["recommend-games-list-item"]} className={styles["recommend-games-list-item"]}
onClick={handleViewGame.bind(null, game.id)} onClick={handleViewGame.bind(null, game.id)}
> >
{/* game title */} {/* game title */}
<View className={styles["recommend-games-list-item-title"]}> <View className={styles["recommend-games-list-item-title"]}>
<Text>{game.title}</Text> <Text
<Image className={styles["recommend-games-list-item-title-text"]}
className={ >
styles["recommend-games-list-item-title-arrow"] {game.title}
} </Text>
<Image
className={
styles["recommend-games-list-item-title-arrow"]
}
src={img.ICON_DETAIL_ARROW_RIGHT} src={img.ICON_DETAIL_ARROW_RIGHT}
/> />
</View> </View>

View File

@@ -489,7 +489,7 @@ export default function Participants(props) {
<Text <Text
className={styles["participants-list-item-level"]} 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}

View File

@@ -1,7 +1,7 @@
import { forwardRef, useState, useEffect, useImperativeHandle } from "react"; import { forwardRef, useState, useEffect, useImperativeHandle } from "react";
import { View, Button, Image, Text } from "@tarojs/components"; import { View, Button, Image, Text } from "@tarojs/components";
import Taro, { useShareAppMessage } from "@tarojs/taro"; import Taro, { useShareAppMessage } from "@tarojs/taro";
import dayjs from "dayjs"; import dayjs, { Dayjs } from "dayjs";
import "dayjs/locale/zh-cn"; import "dayjs/locale/zh-cn";
import classnames from "classnames"; import classnames from "classnames";
import { generateShareImage } from "@/utils"; import { generateShareImage } from "@/utils";
@@ -12,7 +12,12 @@ import WechatLogo from "@/static/detail/wechat_icon.svg";
// import WechatTimeline from "@/static/detail/wechat_timeline.svg"; // import WechatTimeline from "@/static/detail/wechat_timeline.svg";
import LinkIcon from "@/static/detail/link.svg"; import LinkIcon from "@/static/detail/link.svg";
import CrossIcon from "@/static/detail/cross.svg"; import CrossIcon from "@/static/detail/cross.svg";
import { genNTRPRequirementText, navto } from "@/utils/helper"; import {
genNTRPRequirementText,
navto,
genGameLength,
formatGameStartTime,
} 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 } from "@/config/api"; import { OSS_BASE } from "@/config/api";
@@ -28,7 +33,19 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
const [shareImageUrl, setShareImageUrl] = useState(""); const [shareImageUrl, setShareImageUrl] = useState("");
const { fetchUserInfo } = useUserActions(); const { fetchUserInfo } = useUserActions();
async function ensureUserInfo() {
if (userInfo?.avatar_url && userInfo?.nickname) {
return userInfo;
}
const fetchedUserInfo = await fetchUserInfo();
return {
avatar_url: fetchedUserInfo?.avatar_url || userInfo?.avatar_url || "",
nickname: fetchedUserInfo?.nickname || userInfo?.nickname || "",
};
}
const publishFlag = from === "publish"; const publishFlag = from === "publish";
// const posterRef = useRef(); // const posterRef = useRef();
const { max_participants, participant_count } = detail || {}; const { max_participants, participant_count } = detail || {};
@@ -50,6 +67,16 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
withShareTicket: false, // 是否需要返回 shareTicket withShareTicket: false, // 是否需要返回 shareTicket
isUpdatableMessage: true, // 是否是动态消息(需要服务端配置过模版) isUpdatableMessage: true, // 是否是动态消息(需要服务端配置过模版)
activityId: res.data.activity_id, // 动态消息的活动 id activityId: res.data.activity_id, // 动态消息的活动 id
templateInfo: {
parameterList: [
{
name: "member_count",
value: (participant_count ?? 0).toString(),
},
{ name: "room_limit", value: (max_participants ?? 0).toString() },
],
templateId: "666F374D69D16C932E45D7E7D9F10CEF6177F5F5",
},
}); });
} }
} catch (e) { } catch (e) {
@@ -85,28 +112,34 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
const startTime = dayjs(start_time); const startTime = dayjs(start_time);
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 = genGameLength(startTime, endTime);
console.log(userInfo, "userInfo"); const currentUserInfo = await ensureUserInfo();
const url = await generateShareImage({ try {
userAvatar: userInfo.avatar_url, const url = await generateShareImage({
userNickname: userInfo.nickname, userAvatar: currentUserInfo.avatar_url,
gameType: play_type, userNickname: currentUserInfo.nickname,
skillLevel: `NTRP ${genNTRPRequirementText( gameType: play_type,
skill_level_min, skillLevel: `NTRP ${genNTRPRequirementText(
skill_level_max, skill_level_min,
)}`, skill_level_max,
gameDate: `${startTime.format("M月D日")} (${dayofWeek})`, )}`,
gameTime: `${startTime.format("ah")} ${gameLength}`, gameDate: `${startTime.format("M月D日")} (${dayofWeek})`,
venueName: location_name, gameTime: `${formatGameStartTime(startTime)} ${gameLength}`,
venueImages: image_list ? image_list : [], venueName: location_name,
}); venueImages: image_list ? image_list : [],
return url; });
if (!url) {
throw new Error("生成分享图片失败URL 为空");
}
return url;
} catch (e) {
console.error("生成分享卡片失败", e);
return `${OSS_BASE}/system/game_dou_di_tu.png`;
}
} }
useShareAppMessage(async (res) => { useShareAppMessage(async () => {
await changeMessageType(); const url = shareImageUrl || (await generateShareImageUrl());
const url = await generateShareImageUrl();
// console.log(res, "res");
return { return {
title: detail.title, title: detail.title,
imageUrl: url, imageUrl: url,
@@ -128,12 +161,12 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
} = detail || {}; } = detail || {};
// 先等待静默登录完成 // 先等待静默登录完成
await waitForAuthInit(); await waitForAuthInit();
const userInfo = await fetchUserInfo(); const currentUserInfo = await ensureUserInfo();
const { avatar_url, nickname } = userInfo; const { avatar_url, nickname } = currentUserInfo;
const startTime = dayjs(start_time); const startTime = dayjs(start_time);
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 game_length = genGameLength(startTime, endTime);
let qrCodeUrl = ""; let qrCodeUrl = "";
try { try {
const qrCodeUrlRes = await DetailService.getQrCodeUrl({ const qrCodeUrlRes = await DetailService.getQrCodeUrl({
@@ -161,7 +194,7 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
title, title,
locationName: location_name, locationName: location_name,
date: `${startTime.format("M月D日")} (${dayofWeek})`, date: `${startTime.format("M月D日")} (${dayofWeek})`,
time: `${startTime.format("ah")} ${gameLength}`, time: `${formatGameStartTime(startTime)} ${game_length}`,
qrCodeUrl, qrCodeUrl,
}); });
} catch (e) { } catch (e) {

View File

@@ -24,12 +24,12 @@ function isFull(counts) {
} = counts; } = counts;
if ( if (
max_players === current_players && current_players >= max_players &&
is_substitute_supported === IsSubstituteSupported.NOTSUPPORT is_substitute_supported === IsSubstituteSupported.NOTSUPPORT
) { ) {
return true; return true;
} else if ( } else if (
max_players === current_players && current_players >= max_players &&
is_substitute_supported === IsSubstituteSupported.SUPPORT is_substitute_supported === IsSubstituteSupported.SUPPORT
) { ) {
return max_substitute_players === current_substitute_count; return max_substitute_players === current_substitute_count;
@@ -45,7 +45,7 @@ function RmbIcon() {
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;
@@ -123,7 +123,7 @@ export default function StickyButton(props) {
Taro.navigateTo({ Taro.navigateTo({
url: `/login_pages/index/index?redirect=${encodeURIComponent( url: `/login_pages/index/index?redirect=${encodeURIComponent(
fullPath fullPath,
)}`, )}`,
}); });
} }
@@ -138,7 +138,7 @@ export default function StickyButton(props) {
const matchNtrpReq = matchNtrpRequestment( const matchNtrpReq = matchNtrpRequestment(
ntrp_level, ntrp_level,
skill_level_min, skill_level_min,
skill_level_max skill_level_max,
); );
const gameManageRef = useRef(); const gameManageRef = useRef();
@@ -173,7 +173,7 @@ export default function StickyButton(props) {
}, [getCommentCount]); }, [getCommentCount]);
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 } {
@@ -271,7 +271,7 @@ export default function StickyButton(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}`,
); );
} }
}), }),
@@ -387,7 +387,7 @@ export default function StickyButton(props) {
<View <View
className={classnames( className={classnames(
styles["detail-main-action"], styles["detail-main-action"],
available ? "" : styles.disabled available ? "" : styles.disabled,
)} )}
> >
<View <View

View File

@@ -14,7 +14,11 @@ import WechatLogo from "@/static/detail/wechat_icon.svg";
import WechatTimeline from "@/static/detail/wechat_timeline.svg"; import WechatTimeline from "@/static/detail/wechat_timeline.svg";
import { useUserActions } from "@/store/userStore"; import { useUserActions } from "@/store/userStore";
import { DayOfWeekMap } from "../detail/config"; import { DayOfWeekMap } from "../detail/config";
import { genNTRPRequirementText } from "@/utils/helper"; import {
genNTRPRequirementText,
genGameLength,
formatGameStartTime,
} from "@/utils/helper";
import { waitForAuthInit } from "@/utils/authInit"; import { waitForAuthInit } from "@/utils/authInit";
import { OSS_BASE } from "@/config/api"; import { OSS_BASE } from "@/config/api";
import styles from "./index.module.scss"; import styles from "./index.module.scss";
@@ -53,7 +57,7 @@ function SharePoster(props) {
const startTime = dayjs(start_time); const startTime = dayjs(start_time);
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 = genGameLength(startTime, endTime);
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",
@@ -77,7 +81,7 @@ function SharePoster(props) {
title, title,
locationName: location_name, locationName: location_name,
date: `${startTime.format("M月D日")} (${dayofWeek})`, date: `${startTime.format("M月D日")} (${dayofWeek})`,
time: `${startTime.format("ah")} ${gameLength}`, time: `${formatGameStartTime(startTime)} ${gameLength}`,
qrCodeUrl, qrCodeUrl,
}); });
Taro.hideLoading(); Taro.hideLoading();

View File

@@ -22,3 +22,135 @@ export const DECLAIMER = `
发起人临时失联/爽约发起人恶意删除队员GO支持全额退款 发起人临时失联/爽约发起人恶意删除队员GO支持全额退款
参与者爽约不通知,不可退款但鼓励用户评分机制中反馈,平台将限制其部分功能使用(如发起权限、报名权限等)。 参与者爽约不通知,不可退款但鼓励用户评分机制中反馈,平台将限制其部分功能使用(如发起权限、报名权限等)。
`; `;
interface RegInsChildTipType {
text: string
strong?: boolean
}
interface RegInsChildTableType {
refundApplicationTime: string
participantRefundableAmount: string
liquidatedDamages: string
}
interface RegInsChildType {
title: string
desc: string
table?: RegInsChildTableType[]
tips: RegInsChildTipType[]
}
interface RegInsType {
title: string,
desc: string,
children: RegInsChildType[]
}
export const RegistrationInstructions: RegInsType = {
title: '报名须知',
desc: '请在确认支付前仔细阅读以下内容,完成支付即视为您已同意本须知全部内容。',
children: [
{
title: '一、退款规则',
desc: '',
table: [
{
refundApplicationTime: '申请退款时间',
participantRefundableAmount: '参与者可退',
liquidatedDamages: '违约金',
},
{
refundApplicationTime: '活动开始前24小时',
participantRefundableAmount: '报名费 100%',
liquidatedDamages: '无',
},
{
refundApplicationTime: '活动开始前1224小时',
participantRefundableAmount: '报名费 50%',
liquidatedDamages: '报名费 50%',
},
{
refundApplicationTime: '活动开始前12小时内',
participantRefundableAmount: '报名费 20%',
liquidatedDamages: '报名费 80%',
},
{
refundApplicationTime: '未申请 / 直接缺席',
participantRefundableAmount: '0%',
liquidatedDamages: '视为放弃,全归组织者',
},
],
tips: [
{
text: '以上时间节点以提交申请时间为准,非活动开始时间;',
strong: false,
},
{
text: '退款申请入口:活动详情页 > 退出活动',
},
{
text: '退款原路退回至微信支付账户,到账时间 15 个工作日;',
},
{
text: '违约金由组织者95%与平台5%)按比例分配。其中组织者所得部分用于补偿其因人数临时变动产生的场地费损失,平台所得部分用于覆盖违约事务的处理成本;',
},
{
text: '未申请退款直接缺席的,报名费于活动结束后自动结算给组织者,平台不参与分配',
},
],
},
{
title: '二、特殊情形退款',
desc: '以下特殊情形可申请全额退款,需联系客服并提供相关证明材料:',
tips: [
{
text: '活动当天遭遇极端恶劣天气(台风、暴雨红色预警等);',
},
{
text: '球场临时关闭或其他不可抗力导致活动无法进行;',
},
{
text: '参与者本人突发疾病或意外(需提供医院证明)。',
},
],
},
{
title: '三、活动取消规则',
desc: '',
tips: [
{
text: '到达活动开始时间时,报名人数仍未达到最低成局人数,活动自动取消,已付款参与者全额退款;',
},
{
text: '组织者主动取消活动,所有已付款参与者全额退款;',
},
{
text: '以上退款均由系统自动处理,无需申请。',
},
],
},
{
title: '四、免责声明',
desc: '',
tips: [
{
text: '本平台仅为网球约球信息撮合平台,不直接提供场地或运动服务,不对活动中的人身安全及财物损失承担责任;',
},
{
text: '网球运动存在固有运动风险,请在参与前评估自身身体状况,患有心脏病、高血压等基础疾病者请在医生许可下参与;',
},
{
text: '平台强烈建议参与者购买运动意外保险;',
strong: true,
},
{
text: '因组织者或场地方原因导致活动变更或取消,平台将协助处理但不承担连带责任;',
},
{
text: '本平台不对因网络故障、系统维护或不可抗力导致的服务中断承担责任。',
},
],
},
],
}

View File

@@ -424,6 +424,7 @@
align-items: flex-end; align-items: flex-end;
justify-content: center; justify-content: center;
gap: 8px; gap: 8px;
word-break: break-all;
} }
.orderNo { .orderNo {
@@ -544,21 +545,160 @@
.time { .time {
text-align: left; text-align: left;
padding-left: 30px; padding-left: 30px;
border-right: 1px solid rgba(0, 0, 0, 0.06);
} }
.rule { // .rule {
border-left: 1px solid rgba(0, 0, 0, 0.06); // border-left: 1px solid rgba(0, 0, 0, 0.06);
} // }
} }
} }
.refundTip {
margin-top: 16px;
color: rgba(60, 60, 67, 0.6);
text-align: center;
font-feature-settings:
"liga" off,
"clig" off;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
}
} }
.declaimer { .disclaimer {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-start; gap: 16px;
gap: 8px; margin-top: 16px;
padding-bottom: 100px;
.disclaimerTitle {
font-size: 14px;
font-weight: bold;
color: #000;
line-height: 20px;
}
.disclaimerDesc {
font-size: 12px;
color: rgba(0, 0, 0, 0.65);
line-height: 18px;
}
.disclaimerSection {
display: flex;
flex-direction: column;
gap: 8px;
.sectionTitle {
font-size: 14px;
font-weight: bold;
color: #000;
line-height: 20px;
}
.sectionDesc {
font-size: 12px;
color: rgba(0, 0, 0, 0.65);
line-height: 18px;
}
.tableContainer {
display: flex;
flex-direction: column;
// gap: 8px;
margin: 8px 0;
border-radius: 12px;
border: 1px solid rgba(0, 0, 0, 0.06);
background: #fff;
box-shadow: 0 4px 36px 0 rgba(0, 0, 0, 0.06);
overflow: hidden;
.tableRow {
display: flex;
min-height: 44px;
&:first-child {
.tableCell {
color: #000;
font-weight: 600;
}
}
&:not(:last-child) {
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
}
.tableCell {
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
color: rgba(0, 0, 0, 0.65);
line-height: 18px;
word-break: break-word;
padding: 4px 0;
&:not(:last-child) {
border-right: 1px solid rgba(0, 0, 0, 0.06);
}
&:nth-child(1) {
flex: 1;
}
&:nth-child(2) {
flex: 0.6;
}
&:nth-child(3) {
flex: 1;
}
.tipText {
text-align: center;
}
}
}
}
.tipsList {
display: flex;
flex-direction: column;
gap: 6px;
margin: 8px 0;
.tipItem {
display: flex;
align-items: flex-start;
&::before {
content: "";
margin-right: 6px;
margin-top: -2px;
font-size: 12px;
color: rgba(0, 0, 0, 0.65);
flex-shrink: 0;
line-height: 18px;
}
.tipText {
font-size: 12px;
color: rgba(0, 0, 0, 0.65);
line-height: 18px;
}
.tipTextStrong {
font-size: 12px;
color: #000;
font-weight: bold;
line-height: 18px;
}
}
}
}
.title { .title {
display: flex; display: flex;
padding: 15px 0 0; padding: 15px 0 0;

View File

@@ -2,7 +2,7 @@ import React, { useState, useRef } from "react";
import { View, Text, Button, Image } from "@tarojs/components"; import { View, Text, Button, Image } from "@tarojs/components";
import { Dialog } from "@nutui/nutui-react-taro"; import { Dialog } from "@nutui/nutui-react-taro";
import Taro, { useDidShow, useRouter } from "@tarojs/taro"; import Taro, { useDidShow, useRouter } from "@tarojs/taro";
import dayjs from "dayjs"; import dayjs, { Dayjs } from "dayjs";
import "dayjs/locale/zh-cn"; import "dayjs/locale/zh-cn";
import classnames from "classnames"; import classnames from "classnames";
import orderService, { import orderService, {
@@ -21,6 +21,7 @@ import {
getOrderStatus, getOrderStatus,
generateOrderActions, generateOrderActions,
isPhoneNumber, isPhoneNumber,
genGameLength,
} from "@/utils"; } from "@/utils";
import { getStorage, setStorage } from "@/store/storage"; import { getStorage, setStorage } from "@/store/storage";
import { useGlobalStore } from "@/store/global"; import { useGlobalStore } from "@/store/global";
@@ -32,7 +33,7 @@ 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";
import { requireLoginWithPhone } from "@/utils/helper"; import { requireLoginWithPhone } from "@/utils/helper";
import { DECLAIMER } from "./config"; import { RegistrationInstructions } from "./config";
import styles from "./index.module.scss"; import styles from "./index.module.scss";
dayjs.locale("zh-cn"); dayjs.locale("zh-cn");
@@ -75,6 +76,17 @@ function genGameNotice(order_status, start_time) {
return gameNoticeMap.get(key) || {}; return gameNoticeMap.get(key) || {};
} }
function genGameRange(startTime: Dayjs, endTime: Dayjs) {
if (!startTime || !endTime) {
return "";
}
// 如果跨天(自然日)
if (!startTime.isSame(endTime, "day")) {
return `${startTime.format("HH:mm")} - ${endTime.format("MM月DD日 HH:mm")}`;
}
return `${startTime.format("HH:mm")} - ${endTime.format("HH:mm")}`;
}
function GameInfo(props) { function GameInfo(props) {
const { detail, currentLocation, orderDetail, init } = props; const { detail, currentLocation, orderDetail, init } = props;
const { order_status, refund_status, amount, refund_amount } = orderDetail; const { order_status, refund_status, amount, refund_amount } = orderDetail;
@@ -111,15 +123,17 @@ function GameInfo(props) {
const startTime = dayjs(start_time); const startTime = dayjs(start_time);
const endTime = dayjs(end_time); const endTime = dayjs(end_time);
const game_length = Number( // const game_length = Number(
(endTime.diff(startTime, "minutes") / 60).toFixed(), // (endTime.diff(startTime, "minutes") / 60).toFixed(),
); // );
const game_length = genGameLength(startTime, endTime);
const startMonth = startTime.format("M"); const startMonth = startTime.format("M");
const startDay = startTime.format("D"); const startDay = startTime.format("D");
const theDayOfWeek = startTime.format("dddd"); const theDayOfWeek = startTime.format("dddd");
const startDate = `${startMonth}${startDay}${theDayOfWeek}`; const startDate = `${startMonth}${startDay}${theDayOfWeek}`;
const gameRange = `${startTime.format("HH:mm")} - ${endTime.format("HH:mm")}`; // const gameRange = `${startTime.format("HH:mm")} - ${endTime.format("HH:mm")}`;
const gameRange = genGameRange(startTime, endTime);
const orderStatus = getOrderStatus(orderDetail); const orderStatus = getOrderStatus(orderDetail);
@@ -277,7 +291,7 @@ function GameInfo(props) {
<View className={styles.gameInfoDateWeatherCalendarDateDate}> <View className={styles.gameInfoDateWeatherCalendarDateDate}>
<View className={styles.date}>{startDate}</View> <View className={styles.date}>{startDate}</View>
<View className={styles.venueTime}> <View className={styles.venueTime}>
{gameRange} {game_length} {gameRange} {game_length}
</View> </View>
</View> </View>
</View> </View>
@@ -550,15 +564,66 @@ function RefundPolicy(props) {
</View> </View>
))} ))}
</View> </View>
<Text className={styles.refundTip}>
95%5%481退
</Text>
</View> </View>
); );
} }
function Disclaimer() { function Disclaimer() {
return ( return (
<View className={styles.declaimer}> <View className={styles.disclaimer}>
<Text className={styles.title}></Text> <View className={styles.disclaimerTitle}>
<Text className={styles.content}>{DECLAIMER}</Text> <Text>{RegistrationInstructions.title}</Text>
</View>
<View className={styles.disclaimerDesc}>
<Text>{RegistrationInstructions.desc}</Text>
</View>
{RegistrationInstructions.children.map((section, sectionIndex) => (
<View key={sectionIndex} className={styles.disclaimerSection}>
<View className={styles.sectionTitle}>
<Text>{section.title}</Text>
</View>
{section.desc && (
<View className={styles.sectionDesc}>
<Text>{section.desc}</Text>
</View>
)}
{section.table && (
<View className={styles.tableContainer}>
{section.table.map((row, rowIndex) => (
<View key={rowIndex} className={styles.tableRow}>
<View className={styles.tableCell}>
<Text>{row.refundApplicationTime}</Text>
</View>
<View className={styles.tableCell}>
<Text>{row.participantRefundableAmount}</Text>
</View>
<View className={styles.tableCell}>
<Text>{row.liquidatedDamages}</Text>
</View>
</View>
))}
</View>
)}
{section.tips && (
<View className={styles.tipsList}>
{section.tips.map((tip, tipIndex) => (
<View key={tipIndex} className={styles.tipItem}>
<Text
className={
tip.strong ? styles.tipTextStrong : styles.tipText
}
>
{tip.text}
</Text>
</View>
))}
</View>
)}
</View>
))}
</View> </View>
); );
} }

View File

@@ -5,6 +5,7 @@ import img from '@/config/images';
import { FormFieldConfig } from '@/config/formSchema/publishBallFormSchema'; import { FormFieldConfig } from '@/config/formSchema/publishBallFormSchema';
import SelectStadium from '../SelectStadium/SelectStadium' import SelectStadium from '../SelectStadium/SelectStadium'
import { Stadium } from '../SelectStadium/StadiumDetail' import { Stadium } from '../SelectStadium/StadiumDetail'
import { normalize_address } from '@/utils/locationUtils'
import './FormBasicInfo.scss' import './FormBasicInfo.scss'
type PlayGame = { type PlayGame = {
@@ -54,7 +55,7 @@ const FormBasicInfo: React.FC<FormBasicInfoProps> = ({
onChange({...value, onChange({...value,
venue_id, venue_id,
location_name: name, location_name: name,
location: address, location: normalize_address(address || ''),
latitude, latitude,
longitude, longitude,
court_type, court_type,

View File

@@ -4,7 +4,7 @@ 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, CustomPopup } from '../../../../components' import { CommonPopup, CustomPopup } from '../../../../components'
import { getLocation } from '@/utils/locationUtils' import { getLocation, normalize_address } from '@/utils/locationUtils'
import PublishService from '@/services/publishService' import PublishService from '@/services/publishService'
import images from '@/config/images' import images from '@/config/images'
import './SelectStadium.scss' import './SelectStadium.scss'
@@ -100,7 +100,7 @@ const SelectStadium: React.FC<SelectStadiumProps> = ({
success: (res) => { success: (res) => {
setSelectedStadium({ setSelectedStadium({
name: res.name, name: res.name,
address: res.address, address: normalize_address(res.address || ''),
longitude: res.longitude, longitude: res.longitude,
latitude: res.latitude latitude: res.latitude
}) })

View File

@@ -7,6 +7,7 @@ import TextareaTag from '@/components/TextareaTag'
import UploadCover, { type CoverImageValue } from '@/components/UploadCover' import UploadCover, { type CoverImageValue } from '@/components/UploadCover'
import { useKeyboardHeight } from '@/store/keyboardStore' import { useKeyboardHeight } from '@/store/keyboardStore'
import { useDictionaryActions } from '@/store/dictionaryStore' import { useDictionaryActions } from '@/store/dictionaryStore'
import { normalize_address } from '@/utils/locationUtils'
import './StadiumDetail.scss' import './StadiumDetail.scss'
@@ -145,7 +146,7 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
setFormData({ setFormData({
...formData, ...formData,
name: res.name, name: res.name,
address: res.address, address: normalize_address(res.address || ''),
latitude: res.latitude, latitude: res.latitude,
longitude: res.longitude, longitude: res.longitude,
istance: null istance: null

View File

@@ -381,6 +381,7 @@ const PublishBall: React.FC = () => {
image_list, image_list,
wechat, wechat,
id, id,
title,
...rest ...rest
} = formData[0]; } = formData[0];
const { min, max, organizer_joined } = players; const { min, max, organizer_joined } = players;
@@ -389,6 +390,7 @@ const PublishBall: React.FC = () => {
...activityInfo, ...activityInfo,
...descriptionInfo, ...descriptionInfo,
...timeRange, ...timeRange,
title: title?.replace(/\n/g, ''),
max_players: max, max_players: max,
min_players: min, min_players: min,
organizer_joined: organizer_joined === true ? 1 : 0, organizer_joined: organizer_joined === true ? 1 : 0,
@@ -453,6 +455,7 @@ const PublishBall: React.FC = () => {
skill_level, skill_level,
is_substitute_supported, is_substitute_supported,
id, id,
title,
...rest ...rest
} = item; } = item;
const { min, max, organizer_joined } = players; const { min, max, organizer_joined } = players;
@@ -461,6 +464,7 @@ const PublishBall: React.FC = () => {
...activityInfo, ...activityInfo,
...descriptionInfo, ...descriptionInfo,
...timeRange, ...timeRange,
title: title?.replace(/\n/g, ' '),
max_players: max, max_players: max,
min_players: min, min_players: min,
organizer_joined: organizer_joined === true ? 1 : 0, organizer_joined: organizer_joined === true ? 1 : 0,

View File

@@ -1,11 +1,18 @@
<svg width="24" height="32" viewBox="0 0 24 32" fill="none" xmlns="http://www.w3.org/2000/svg"> <?xml version="1.0" encoding="UTF-8"?>
<g clip-path="url(#clip0_7504_18610)"> <svg id="_图层_2" data-name="图层 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 74.87 89.85">
<path d="M12.3076 0.0971837C12.7381 0.438102 12.2325 2.22445 12.328 2.76282C13.7134 2.51292 15.1145 2.01928 16.5062 1.81874C16.8928 1.76321 17.9933 1.57501 17.89 2.28307C17.8274 2.71346 16.6377 4.52912 16.73 4.62167C17.9667 4.78673 19.2128 4.97647 20.4214 5.28962C20.8346 5.39607 21.9226 5.57501 21.8209 6.15503C21.7223 6.71192 20.0394 8.18974 20.0848 8.37331C21.1211 9.25414 22.4017 9.9838 23.402 10.8909C23.712 11.1716 24.119 11.457 23.9671 11.926C23.8028 12.4366 22.0009 12.6541 21.5469 12.9965C21.525 13.1215 22.552 14.7351 22.7069 15.0204C22.8995 15.3752 23.8028 17.0613 23.7871 17.3236C23.726 18.2754 22.051 17.5827 21.4827 17.6383C21.6596 18.3648 21.8131 19.1408 21.8929 19.8843C21.9774 20.6679 22.2514 21.6753 21.0632 21.4932C21.237 29.4763 12.3624 34.5546 5.2928 30.6641C-0.183159 27.6514 -1.69069 20.6001 2.09614 15.6467C2.21199 15.494 2.76459 14.9402 2.78964 14.8801C2.81782 14.8122 2.73642 14.2831 2.75364 14.0902C2.80999 13.4624 3.15439 12.8824 3.58802 12.4366C2.80686 11.693 1.97874 10.8461 1.52945 9.85268C1.36977 9.50096 1.30559 9.0027 1.16 8.70189C1.04103 8.45661 0.72324 8.31623 0.593307 8.0216C0.190985 7.11762 0.835953 6.08561 1.87228 6.20902C2.38262 6.26919 2.4609 6.6317 3.13091 6.54069C4.43807 6.36637 5.06425 4.00154 5.53545 2.98804C5.73583 2.5592 6.59839 0.737369 6.94749 0.59082C7.976 0.161974 7.82102 2.05939 8.23899 2.54994C9.08747 1.94986 9.8702 1.2526 10.7281 0.664866C11.0991 0.410335 11.8724 -0.251447 12.3076 0.0925559V0.0971837ZM11.1993 6.0563C10.9629 6.20285 10.2944 5.55804 10.022 5.41149C8.99824 4.86232 7.73648 4.72348 6.62814 5.09988C5.22236 5.57809 5.13626 6.89394 3.68039 7.42923C3.52854 7.48477 3.02133 7.55418 2.9775 7.59738C2.94462 7.62977 2.87418 7.99846 2.75364 8.16814C2.6331 8.33475 2.4061 8.51832 2.19477 8.55071C2.40297 9.81874 3.62873 11.7146 5.15661 11.0189C5.63407 10.8014 5.73583 10.263 5.89707 10.1643C6.15694 10.0069 6.41367 10.1658 6.45437 10.4466C6.57178 11.258 5.30689 11.9676 4.57113 11.9213L5.26776 12.3702C6.99289 13.2865 8.93719 13.6429 10.8846 13.3575C10.9989 13.322 11.2165 12.5245 11.3605 12.3116C11.5515 12.0278 11.9178 11.8072 12.1182 11.5418C12.5033 11.0297 12.6598 9.76166 12.6066 9.13228C12.5847 8.87466 12.3452 8.2931 12.4297 8.13266C12.9792 7.84265 13.5897 7.80409 13.8793 7.14693C14.5384 5.64905 12.6035 4.40879 11.4904 5.55033C11.3777 5.66602 11.2259 6.03934 11.2008 6.05476L11.1993 6.0563ZM12.8665 12.2808C12.2513 12.418 11.6439 13.683 12.1511 14.1273C12.7882 14.6826 14.2582 13.5488 14.6981 13.049C14.4006 12.5183 13.4473 12.1512 12.8665 12.2808ZM6.45594 13.7802C6.16163 13.7324 5.8642 13.6645 5.58085 13.575C4.81847 13.3313 4.25647 12.7312 3.85572 13.7324C3.66786 14.1998 3.64751 14.823 4.27213 14.9387C4.7809 15.0328 6.34323 14.4142 6.45594 13.7817V13.7802ZM10.9973 13.8511C9.58215 14.1921 8.2343 14.1859 6.80817 13.919C6.76746 14.8091 5.6012 15.4477 4.79656 15.5526C4.52573 15.5881 4.07645 15.5156 3.87294 15.6066C3.74144 15.6653 3.02603 16.4165 2.89609 16.5708C0.767073 19.1192 0.635574 22.5885 2.06484 25.5118C2.36853 26.1319 2.43428 26.3756 3.20918 26.4512C5.43839 26.6672 6.16633 25.3606 7.9713 24.6016C11.4137 23.1562 15.7767 24.2237 17.6051 27.5526C20.7376 24.0617 20.4417 18.7906 17.2576 15.4323C16.9257 15.0837 15.7328 14.118 15.3117 13.9699C15.0675 13.8835 14.8123 14.297 14.6292 14.4296C13.3659 15.3521 11.3589 15.9105 10.9989 13.8527L10.9973 13.8511ZM3.59115 27.6915C3.55985 27.8103 3.64125 27.8288 3.69447 27.8982C4.0968 28.4103 4.98284 29.0305 5.55267 29.3729C8.98102 31.4354 13.5036 31.1145 16.5719 28.5553C15.415 25.5164 11.647 24.6232 8.74463 25.6429C6.98819 26.2599 5.62468 28.0972 3.59115 27.6915Z" fill="black"/> <defs>
<path d="M7.69734 6.77362C8.84169 6.70883 9.28002 8.38257 8.18264 8.8361C6.57804 9.49788 6.06457 6.86463 7.69734 6.77362Z" fill="black"/> <style>
</g> .cls-1 {
<defs> fill: #e6ff54;
<clipPath id="clip0_7504_18610"> }
<rect width="24" height="32" fill="white"/> </style>
</clipPath> </defs>
</defs> <g id="_图层_1-2" data-name=" 图层 1">
</svg> <path class="cls-1" d="M39.79,82.22h0c-3.1,1.7-6.7,2.6-10.3,2.6-9.9,0-18.3-6.7-21-15.7,1.7,1.4,3.6,2.3,5.6,2.7,4.7,1,8.3,0,11.8-1,3.1-.8,5.9-1.6,9.7-1.2,3.9.5,6.6,2.6,7.3,5.7.6,2.7-.5,5.4-3,6.9h-.1Z"/>
<path class="cls-1" d="M51.29,62.92c0,.9,0,1.9-.2,2.8-.5,3.7-1.8,7.2-4,10.2,0-.5,0-1-.2-1.5-1.1-4.9-5.3-8.3-10.9-9-4.5-.6-8.1.4-11.3,1.3-3.3,1-6.2,1.7-9.9,1-3.9-.8-7.6-4.7-6.6-10.4h0c0-.6.3-1.1.5-1.7,0-.4.3-.8.5-1.2,1.1.2,2.2.2,3.3,0h.4c2.6-.9,4.4-2.8,4.8-5.2v-.2c4.8,1.4,10.4,1.3,15.4,0h0c.2-.2.4-.2.6-.3,0,1,.5,2,1.1,2.9,1.1,1.6,2.9,2.6,5,2.8,1.1,0,2.2,0,3.3-.4h0c1-.2,2-.7,3-1.3.6-.4,1.1-.8,1.6-1.2,2.3,3.5,3.5,7.6,3.5,11.9v-.3l.1-.2Z"/>
<g>
<path d="M74.59,37.52c-2.1-6.1-6.4-11.7-12.2-15.9h0c1.7-2,2.6-3,4.5-4.6.5-.4.7-1,.6-1.6,0-.6-.4-1.1-1-1.4-6.5-3.2-12.1-4.7-18.9-5,.9-2.4,1.8-4.1,3-6.4.3-.6.3-1.3,0-1.9-.4-.6-1.1-.8-1.8-.7-7.6,1.7-13.8,4-20.9,8.1-1.9-2.4-4.1-4.3-6.8-6-.5-.3-1.2-.3-1.8,0s-.9.9-.9,1.5c0,5.6-1,9.9-3.2,14.4-1.6,3.3-3.3,5.7-4.4,7.4-1.3,1.9-2.1,2.9-3,3.4-1.35-1.86-4.14-2.86-6.42-.58-1.03,1.03-1.51,2.46-1.33,3.91.21,1.67,1.05,2.27,2.25,2.87.7,2.3,1.7,4.5,2.9,6.3-1.4,1.1-2.3,2.7-2.7,4.5-.6,2.7,0,5.3,1.9,7.2-1.2,3.1-1.8,6.4-1.8,9.8,0,14.9,13.39,27.65,27,27,6.31-.3,9.55-.58,15.79-5.21,1.57-1.16,3.02-2.5,4.26-4.01,3.31-4.03,5.89-8.65,6.65-13.38,1.2.4,2.4.9,3.3,1.3.4.2.8.2,1.3,0h.2c.5-.2.8-.6,1-1.1.9-2.9,1.4-7.5,1.6-10.6,1.5,0,3.1.3,5.2.7.5,0,1.1,0,1.5-.4s.6-.9.6-1.4c-.2-5.6-1-10-2.5-14.3,1.8-.8,3.3-1.4,5.1-1.8.5,0,.9-.4,1.1-.9.2-.4.3-.9,0-1.4v.2h-.1ZM44.09,41.82c.7,0,1.2.3,1.7.8l.17.17c.26.26.52,1.15.72,1.96.29,1.21-.08,2.48-.97,3.36,0,0-.01.01-.02.02-.4.4-.8.7-1.3,1-1.4.9-2.8,1.3-4.1,1.2-1,0-1.8-.5-2.3-1.2-.5-.8-.7-1.6-.5-2.4,0-.4.2-.8.4-1.2.2-.3.4-.6.6-.9h0c.2-.3.5-.6.8-.8s.6-.4.9-.6c.8-.5,1.6-.9,2.3-1.1.48-.09.65-.19,1.21-.27l.39-.03ZM7.09,34.12h.4c3.7-.5,5.6-3.2,7.3-5.8,1.3-1.8,2.6-3.8,4.8-5.3,3.7-2.5,8.6-2.6,12.6,0,.3.2.6.2,1,.2h.4c.5,0,.9-.5,1.1-.9.6-1.3,1.7-2.2,3-2.4,1.1-.2,2.1,0,2.8.7.8.7,1.2,1.8,1,2.9-.2,1.2-1.2,2.1-2.6,2.7-.6.2-.9.6-1.1,1.2-.2.6,0,1.2.3,1.6,2.1,2.7,2.9,6.5,2.1,9.5,0,.3-.2.6-.3.9-.6.3-1.1.6-1.7,1-1.6,1-2.8,2.2-3.5,3.6,0,0-.2.3-.2.5l-1.1.3c-1.1.4-2.2.7-3.4.9-2.7.5-5.5.6-8.2.2-1.2-.2-2.3-.4-3.4-.8h0c-.4,0-.8-.2-1.2-.4-.3,0-.5-.2-.8-.3h-.3c-.2,0-.4-.2-.7-.3-.2,0-.4-.2-.6-.3.2,0,.4-.2.6-.4,0,0,.2,0,.3-.2,1.1-.9,2.1-2.1,2.7-3.7.2-.5.2-1,0-1.5s-.6-.9-1.1-1c-1-.4-2.2,0-2.6,1.1-.6,1.4-1.5,2.3-2.6,2.4-.5,0-1.1,0-1.7-.3h0c-1.3-1.6-2.3-3.5-3-5.6v-.2h-.2l-.1-.3ZM7.79,49.72c-.2-.2-.3-.5-.5-.9h0c0-.4-.2-.8,0-1.4h0v-.5c.3-1.4,1.2-2,1.8-2.3h.7c.2,0,.3.3.5.4.2.2.5.3.7.5.4.2.7.5,1.1.7.5.3,1,.6,1.5.8,0,0,.2,0,.4.2h0v1.1c-.2,1.1-1.1,2-2.5,2.3h-.5c-.8,0-1.6,0-2.3-.3-.3-.2-.6-.4-.8-.6s0,0-.2-.2h.2l-.1.2ZM39.79,82.22h0c-3.1,1.7-6.7,2.6-10.3,2.6-9.9,0-18.3-6.7-21-15.7,1.7,1.4,3.6,2.3,5.6,2.7,4.7,1,8.3,0,11.8-1,3.1-.8,5.9-1.6,9.7-1.2,3.9.5,6.6,2.6,7.3,5.7.6,2.7-.5,5.4-3,6.9h-.1ZM47.09,75.92c0-.5,0-1-.2-1.5-1.1-4.9-5.3-8.3-10.9-9-4.5-.6-8.1.4-11.3,1.3-3.3,1-6.2,1.7-9.9,1-3.9-.8-7.6-4.7-6.6-10.4h0c0-.6.3-1.1.5-1.7,0-.4.3-.8.5-1.2,1.1.2,2.2.2,3.3,0h.4c2.6-.9,4.4-2.8,4.8-5.2v-.2c4.8,1.4,10.2,1.4,15.1,0,0,0,.6-.3.8-.4,0,1,.5,2,1.1,2.9,1.1,1.6,2.9,2.6,5,2.8,1.1,0,3.4-.3,3.4-.3,1-.3,2-.8,3-1.4.6-.4,1.1-.8,1.6-1.2,2.3,3.5,3.52,7.6,3.5,11.9-.01,2.59,0,1.9-.2,2.8,0,0-1.7,6.8-3.9,9.8Z"/>
<path d="M24.69,34.92c-1.6.2-3.1-.9-3.3-2.5-.3-1.6.8-3.1,2.4-3.3h0c1.6-.2,3.1.9,3.3,2.5.3,1.6-.8,3.1-2.4,3.3Z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -1,6 +1,7 @@
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
import { check_login_status, get_user_info } from "@/services/loginService"; import { check_login_status, get_user_info } from "@/services/loginService";
import { useUser } from "@/store/userStore"; import { useUser } from "@/store/userStore";
import { Dayjs } from "dayjs";
// 普通函数,不调用 useLoad // 普通函数,不调用 useLoad
export const sceneRedirectLogic = (options, defaultPage: string) => { export const sceneRedirectLogic = (options, defaultPage: string) => {
@@ -135,3 +136,40 @@ export function genNTRPRequirementText(min, max) {
export function isPhoneNumber(str) { export function isPhoneNumber(str) {
return /^1[3-9]\d{9}$/.test(str); return /^1[3-9]\d{9}$/.test(str);
} }
export function genGameLength(startTime: Dayjs, endTime: Dayjs) {
if (!startTime || !endTime) {
return "";
}
const totalMinutes = endTime.diff(startTime, "minute");
const totalHours = totalMinutes / 60;
if (totalHours >= 24) {
const days = Math.floor(totalHours / 24);
const remainingHours = totalHours % 24;
if (remainingHours === 0) {
return `${days}`;
}
// 保留一位小数
const displayHours = parseFloat(remainingHours.toFixed(1));
return `${days}${displayHours}小时`;
}
// 如果是整数小时,不显示小数点
if (Number.isInteger(totalHours)) {
return `${totalHours}小时`;
}
// 保留一位小数去除末尾的0
return `${parseFloat(totalHours.toFixed(1))}小时`;
}
export function formatGameStartTime(startTime: Dayjs) {
if (!startTime || !startTime.isValid()) {
return "";
}
const hour = startTime.hour();
const minute = startTime.minute();
return minute === 0 ? `${hour}` : `${hour}${minute}`;
}

View File

@@ -14,10 +14,20 @@ export interface LocationInfo {
name?: string name?: string
} }
/** 规范化地址:去掉类似“上海市上海市...”的重复前缀 */
export const normalize_address = (address: string): string => {
if (!address) return ''
// 去空格(包含全角空格)
const trimmed = address.replace(/[\s\u3000]+/g, '')
// 处理 “xx市xx市...” / “xx省xx省...” 等连续重复
// 例:上海市上海市静安区... -> 上海市静安区...
return trimmed.replace(/^(.{2,6}?[市省])\1+/, '$1')
}
// 获取当前位置 // 获取当前位置
export const getCurrentLocation = (): Promise<LocationInfo> => { export const getCurrentLocation = (): Promise<LocationInfo> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
Taro.getLocation({ ;(Taro as any).getLocation({
type: 'wgs84', type: 'wgs84',
success: (res) => { success: (res) => {
console.log('===获取地理位置', res) console.log('===获取地理位置', res)
@@ -27,7 +37,7 @@ export const getCurrentLocation = (): Promise<LocationInfo> => {
resolve({ resolve({
latitude: res.latitude, latitude: res.latitude,
longitude: res.longitude, longitude: res.longitude,
address address: normalize_address(address)
}) })
}) })
.catch(() => { .catch(() => {
@@ -48,12 +58,12 @@ export const getCurrentLocation = (): Promise<LocationInfo> => {
// 选择地图位置 // 选择地图位置
export const chooseLocation = (): Promise<LocationInfo> => { export const chooseLocation = (): Promise<LocationInfo> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
Taro.chooseLocation({ ;(Taro as any).chooseLocation({
success: (res) => { success: (res) => {
resolve({ resolve({
latitude: res.latitude, latitude: res.latitude,
longitude: res.longitude, longitude: res.longitude,
address: res.address, address: normalize_address(res.address),
name: res.name name: res.name
}) })
}, },
@@ -66,7 +76,7 @@ export const chooseLocation = (): Promise<LocationInfo> => {
export const getLocation = (): Promise<Location> => { export const getLocation = (): Promise<Location> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
Taro.getLocation({ ;(Taro as any).getLocation({
success: (res) => { success: (res) => {
resolve({ resolve({
latitude: res.latitude, latitude: res.latitude,

View File

@@ -106,19 +106,34 @@ const drawLabel = (ctx: any, x: number, y: number, width: number, height: number
ctx.restore() ctx.restore()
} }
/** 给图片 URL 加随机参数,避免同一链接二次加载不触发 onload */
function with_cache_bust(url: string): string {
const sep = url.includes('?') ? '&' : '?';
return `${url}${sep}_t=${Date.now()}_${Math.random().toString(36).slice(2)}`;
}
// 工具函数 - OffscreenCanvas 下加载图片(使用 offscreen.createImage // 工具函数 - OffscreenCanvas 下加载图片(使用 offscreen.createImage
const loadImage = (src: string): Promise<any> => { const loadImage = (src: string): Promise<any> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let timer: ReturnType<typeof setTimeout> | null = null
try { try {
const off = runtime.offscreen const off = runtime.offscreen
if (!off || typeof off.createImage !== 'function') { if (!off || typeof off.createImage !== 'function') {
throw new Error('OffscreenCanvas 未初始化或不支持 createImage') throw new Error('OffscreenCanvas 未初始化或不支持 createImage')
} }
const img = off.createImage() const img = off.createImage()
img.onload = () => resolve(img) timer = setTimeout(() => reject(new Error(`图片加载超时: ${src}`)), 8000)
img.onerror = reject img.onload = () => {
img.src = src if (timer) clearTimeout(timer)
resolve(img)
}
img.onerror = (e: any) => {
if (timer) clearTimeout(timer)
reject(e)
}
img.src = with_cache_bust(src)
} catch (e) { } catch (e) {
if (timer) clearTimeout(timer)
reject(e) reject(e)
} }
}) })
@@ -407,7 +422,7 @@ const drawShareCard = async (ctx: any, data: ShareCardData, offscreen: any): Pro
ctx.restore() ctx.restore()
} catch (error) { } catch (error) {
// 如果头像加载失败,绘制默认头像 // 如果头像加载失败,绘制默认头像
ctx.setFillStyle('#CCCCCC') ctx.fillStyle = '#CCCCCC'
ctx.beginPath() ctx.beginPath()
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2, 0, 2 * Math.PI) ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2, 0, 2 * Math.PI)
ctx.fill() ctx.fill()
@@ -588,10 +603,12 @@ export async function generateShareImage(data: ShareCardData): Promise<string> {
// 记录到 runtime供 loadImage 使用) // 记录到 runtime供 loadImage 使用)
runtime.offscreen = offscreen runtime.offscreen = offscreen
isDrawing = true isDrawing = true
try {
const imagePath = await drawShareCard(ctx, data, offscreen) const imagePath = await drawShareCard(ctx, data, offscreen)
isDrawing = false return imagePath
return imagePath } finally {
isDrawing = false
}
} }
export default generateShareImage export default generateShareImage