From 9f5fdfd1a5c471f724582c852fbdae961e019cc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=88=90?= Date: Sat, 18 Oct 2025 20:17:31 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=20=E5=88=97=E8=A1=A8=20?= =?UTF-8?q?=E6=97=B6=E9=97=B4=E6=A0=BC=E5=BC=8F=E5=8C=96=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ListCard/index.tsx | 8 +- src/login_pages/index/index.tsx | 63 +++++++++---- src/login_pages/verification/index.tsx | 77 ++++++++++++++-- src/utils/timeUtils.ts | 120 +++++++++++++++++++++++-- types/list/types.ts | 1 + 5 files changed, 239 insertions(+), 30 deletions(-) diff --git a/src/components/ListCard/index.tsx b/src/components/ListCard/index.tsx index 44abf01..0336f5b 100644 --- a/src/components/ListCard/index.tsx +++ b/src/components/ListCard/index.tsx @@ -2,12 +2,14 @@ import { View, Text, Image } from "@tarojs/components"; import Taro from "@tarojs/taro"; import img from "../../config/images"; import { ListCardProps } from "../../../types/list/types"; +import { formatGameTime, calculateDuration } from "@/utils/timeUtils"; import "./index.scss"; const ListCard: React.FC = ({ id, title, start_time, + end_time, location, distance_km, current_players, @@ -112,7 +114,11 @@ const ListCard: React.FC = ({ {/* 时间信息 */} - {start_time} + {formatGameTime(start_time)} + {/* 时长 如 2小时 */} + {end_time && ( + {calculateDuration(start_time, end_time)} + )} {/* 地点,室内外,距离 */} diff --git a/src/login_pages/index/index.tsx b/src/login_pages/index/index.tsx index 233ad6a..418fe59 100644 --- a/src/login_pages/index/index.tsx +++ b/src/login_pages/index/index.tsx @@ -8,24 +8,16 @@ const LoginPage: React.FC = () => { const [is_loading, set_is_loading] = useState(false); const [agree_terms, set_agree_terms] = useState(false); const [show_terms_layer, set_show_terms_layer] = useState(false); + const [pending_login_type, set_pending_login_type] = useState<'wechat' | 'phone' | null>(null); // 记录待执行的登录类型 + const [pending_phone_event, set_pending_phone_event] = useState(null); // 记录微信登录的事件数据 const { params: { redirect } } = useRouter(); - // 微信授权登录 - const handle_wechat_login = async (e: any) => { - if (!agree_terms) { - set_show_terms_layer(true); - Taro.showToast({ - title: '请先同意用户协议', - icon: 'none', - duration: 2000 - }); - return; - } - + // 执行微信登录的核心逻辑 + const execute_wechat_login = async (e: any) => { // 检查是否获取到手机号 if (!e.detail || !e.detail.code) { Taro.showToast({ @@ -68,9 +60,12 @@ const LoginPage: React.FC = () => { } }; - // 手机号验证码登录 - const handle_phone_login = async () => { + // 微信授权登录 + const handle_wechat_login = async (e: any) => { if (!agree_terms) { + // 记录待执行的登录类型和事件数据 + set_pending_login_type('wechat'); + set_pending_phone_event(e); set_show_terms_layer(true); Taro.showToast({ title: '请先同意用户协议', @@ -80,16 +75,54 @@ const LoginPage: React.FC = () => { return; } + // 如果已同意条款,直接执行登录 + await execute_wechat_login(e); + }; + + // 执行手机号登录的核心逻辑 + const execute_phone_login = () => { // 跳转到验证码页面 Taro.navigateTo({ url: `/login_pages/verification/index?redirect=${redirect}` }); }; - // 同意协议并关闭浮层 + // 手机号验证码登录 + const handle_phone_login = async () => { + if (!agree_terms) { + // 记录待执行的登录类型 + set_pending_login_type('phone'); + set_show_terms_layer(true); + Taro.showToast({ + title: '请先同意用户协议', + icon: 'none', + duration: 2000 + }); + return; + } + + // 如果已同意条款,直接执行登录 + execute_phone_login(); + }; + + // 同意协议并关闭浮层,继续执行未完成的登录 const handle_agree_terms = () => { set_agree_terms(true); set_show_terms_layer(false); + + // 根据待执行的登录类型,继续执行登录 + if (pending_login_type === 'wechat' && pending_phone_event) { + // 继续执行微信登录 + execute_wechat_login(pending_phone_event); + // 清空待执行状态 + set_pending_login_type(null); + set_pending_phone_event(null); + } else if (pending_login_type === 'phone') { + // 继续执行手机号登录 + execute_phone_login(); + // 清空待执行状态 + set_pending_login_type(null); + } }; // 切换协议同意状态(复选框用) diff --git a/src/login_pages/verification/index.tsx b/src/login_pages/verification/index.tsx index fa89165..f71e6e3 100644 --- a/src/login_pages/verification/index.tsx +++ b/src/login_pages/verification/index.tsx @@ -10,8 +10,10 @@ import { import "./index.scss"; const VerificationPage: React.FC = () => { - const [phone, setPhone] = useState(""); - const [verification_code, setVerificationCode] = useState(""); + const [phone, setPhone] = useState(""); // 存储纯数字的手机号 + const [display_phone, setDisplayPhone] = useState(""); // 显示带空格的手机号 + const [verification_code, setVerificationCode] = useState(""); // 存储纯数字的验证码 + const [display_code, setDisplayCode] = useState(""); // 显示带空格的验证码 const [countdown, setCountdown] = useState(0); const [can_send_code, setCanSendCode] = useState(true); const [is_loading, setIsLoading] = useState(false); @@ -23,6 +25,62 @@ const VerificationPage: React.FC = () => { params: { redirect }, } = useRouter(); + // 格式化手机号为 3-4-4 格式 + const formatPhone = (value: string): string => { + // 移除所有非数字字符 + const numbers = value.replace(/\D/g, ""); + + // 按 3-4-4 格式添加空格 + if (numbers.length <= 3) { + return numbers; + } else if (numbers.length <= 7) { + return `${numbers.slice(0, 3)} ${numbers.slice(3)}`; + } else { + return `${numbers.slice(0, 3)} ${numbers.slice(3, 7)} ${numbers.slice(7, 11)}`; + } + }; + + // 格式化验证码为 3-3 格式 + const formatCode = (value: string): string => { + // 移除所有非数字字符 + const numbers = value.replace(/\D/g, ""); + + // 按 3-3 格式添加空格 + if (numbers.length <= 3) { + return numbers; + } else { + return `${numbers.slice(0, 3)} ${numbers.slice(3, 6)}`; + } + }; + + // 处理手机号输入 + const handlePhoneInput = (e) => { + const inputValue = e.detail.value; + // 移除所有非数字字符 + const numbers = inputValue.replace(/\D/g, ""); + // 限制最多11位 + const limitedNumbers = numbers.slice(0, 11); + + // 保存纯数字版本用于提交 + setPhone(limitedNumbers); + // 保存格式化版本用于显示 + setDisplayPhone(formatPhone(limitedNumbers)); + }; + + // 处理验证码输入 + const handleCodeInput = (e) => { + const inputValue = e.detail.value; + // 移除所有非数字字符 + const numbers = inputValue.replace(/\D/g, ""); + // 限制最多6位 + const limitedNumbers = numbers.slice(0, 6); + + // 保存纯数字版本用于提交 + setVerificationCode(limitedNumbers); + // 保存格式化版本用于显示 + setDisplayCode(formatCode(limitedNumbers)); + }; + // 计算登录按钮是否应该启用 const can_login = phone.length === 11 && verification_code.length === 6 && !is_loading; @@ -69,6 +127,7 @@ const VerificationPage: React.FC = () => { setCodeInputFocus(true); // 清空验证码,让用户重新输入 setVerificationCode(""); + setDisplayCode(""); console.log("设置验证码输入框聚焦"); }, 500); // 延迟500ms确保Toast显示完成后再聚焦 } else { @@ -232,12 +291,12 @@ const VerificationPage: React.FC = () => { setPhone(e.detail.value)} - maxlength={11} + value={display_phone} + onInput={handlePhoneInput} + maxlength={13} /> { placeholderClass="input_placeholder" placeholderStyle="color:#999999;" focus={code_input_focus} - value={verification_code} - onInput={(e) => setVerificationCode(e.detail.value)} + value={display_code} + onInput={handleCodeInput} onFocus={() => setCodeInputFocus(true)} onBlur={() => setCodeInputFocus(false)} - maxlength={6} + maxlength={7} /> { */ export const formatShortRelativeTime = (timeStr: string): string => { if (!timeStr) return ""; - + const date = new Date(timeStr); const now = new Date(); - + // 获取日期部分(年-月-日),忽略时间 const getDateString = (d: Date) => { const year = d.getFullYear(); @@ -150,10 +150,10 @@ export const formatShortRelativeTime = (timeStr: string): string => { const day = d.getDate(); return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`; }; - + const dateStr = getDateString(date); const nowStr = getDateString(now); - + // 计算日期差 const dateObj = new Date(dateStr); const nowObj = new Date(nowStr); @@ -165,7 +165,7 @@ export const formatShortRelativeTime = (timeStr: string): string => { const diff = now.getTime() - date.getTime(); const minutes = Math.floor(diff / (1000 * 60)); const hours = Math.floor(diff / (1000 * 60 * 60)); - + if (minutes < 1) { return "刚刚"; } else if (minutes < 60) { @@ -182,4 +182,114 @@ export const formatShortRelativeTime = (timeStr: string): string => { const day = date.getDate(); return `${month}月${day}日`; } +} + +/** + * 格式化球局时间显示(例如:明天(周五)下午5点) + * @param timeStr 时间字符串 + * @returns 格式化后的时间字符串 + */ +export const formatGameTime = (timeStr: string): string => { + if (!timeStr) return ""; + + const date = new Date(timeStr); + const now = new Date(); + + // 获取星期几 + const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; + const weekDay = weekDays[date.getDay()]; + + // 获取小时和分钟 + const hours = date.getHours(); + const minutes = date.getMinutes(); + + // 判断上午/下午/晚上 + let period = ''; + let displayHour = hours; + + if (hours >= 0 && hours < 6) { + period = '凌晨'; + } else if (hours >= 6 && hours < 12) { + period = '上午'; + } else if (hours >= 12 && hours < 18) { + period = '下午'; + displayHour = hours === 12 ? 12 : hours - 12; + } else { + period = '晚上'; + displayHour = hours - 12; + } + + // 格式化时间部分 + const timeStr2 = minutes === 0 ? `${displayHour}点` : `${displayHour}点${minutes}分`; + + // 获取日期部分(年-月-日),忽略时间 + const getDateString = (d: Date) => { + const year = d.getFullYear(); + const month = d.getMonth() + 1; + const day = d.getDate(); + return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`; + }; + + const dateStr = getDateString(date); + const nowStr = getDateString(now); + + // 计算日期差 + const dateObj = new Date(dateStr); + const nowObj = new Date(nowStr); + const diffTime = dateObj.getTime() - nowObj.getTime(); + const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24)); + + // 根据日期差返回不同格式 + if (diffDays === 0) { + // 今天 + return `今天(${weekDay})${period}${timeStr2}`; + } else if (diffDays === 1) { + // 明天 + return `明天(${weekDay})${period}${timeStr2}`; + } else if (diffDays === 2) { + // 后天 + return `后天(${weekDay})${period}${timeStr2}`; + } else if (diffDays > 2 && diffDays <= 7) { + // 一周内 + return `${weekDay}${period}${timeStr2}`; + } else { + // 超过一周,显示具体日期 + const month = date.getMonth() + 1; + const day = date.getDate(); + return `${month}月${day}日(${weekDay})${period}${timeStr2}`; + } +} + +/** + * 计算时长(结束时间 - 开始时间) + * @param startTime 开始时间字符串 + * @param endTime 结束时间字符串 + * @returns 格式化后的时长字符串(如:2小时、1.5小时、30分钟) + */ +export const calculateDuration = (startTime: string, endTime: string): string => { + if (!startTime || !endTime) return ""; + + const start = new Date(startTime); + const end = new Date(endTime); + + // 计算时间差(毫秒) + const diffMs = end.getTime() - start.getTime(); + + // 转换为分钟 + const diffMinutes = Math.floor(diffMs / (1000 * 60)); + + // 转换为小时 + const hours = Math.floor(diffMinutes / 60); + const minutes = diffMinutes % 60; + + // 格式化输出 + if (hours > 0 && minutes > 0) { + return `${hours}.5小时`; + } else if (hours > 0) { + return `${hours}小时`; + } else if (minutes > 0) { + return `${minutes}分钟`; + } else { + return ""; + } } \ No newline at end of file diff --git a/types/list/types.ts b/types/list/types.ts index 4ae190f..c29ae66 100644 --- a/types/list/types.ts +++ b/types/list/types.ts @@ -207,6 +207,7 @@ export interface ListCardProps { id: string | number, title: string, start_time: string, + end_time?: string, // 结束时间 location: string, distance_km: number, current_players: number,