feat: enhance draw status and scheduling display
- Updated DrawStatusHud to include a new countdown kind "start" for better status representation. - Refactored HallDrawPanel to improve time display logic, differentiating between scheduled start and close times. - Added new translations for scheduled start and close times in multiple languages. - Enhanced time formatting functions to support new scheduling features and improve user experience.
This commit is contained in:
@@ -2,7 +2,13 @@
|
||||
|
||||
export function formatSecondsClock(total: number): string {
|
||||
const s = Math.max(0, Math.floor(total));
|
||||
const mm = String(Math.floor(s / 60)).padStart(2, "0");
|
||||
const ss = String(s % 60).padStart(2, "0");
|
||||
const h = Math.floor(s / 3600);
|
||||
const m = Math.floor((s % 3600) / 60);
|
||||
const sec = s % 60;
|
||||
if (h > 0) {
|
||||
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(sec).padStart(2, "0")}`;
|
||||
}
|
||||
const mm = String(m).padStart(2, "0");
|
||||
const ss = String(sec).padStart(2, "0");
|
||||
return `${mm}:${ss}`;
|
||||
}
|
||||
|
||||
5
src/lib/lottery-schedule-timezone.ts
Normal file
5
src/lib/lottery-schedule-timezone.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* PRD:服务端计划时刻按 UTC(GMT)存储与下发。
|
||||
* 管理端展示/录入仍按此时区;玩家端界面将 API 时刻换算为浏览器本地时区显示。
|
||||
*/
|
||||
export const LOTTERY_SCHEDULE_TIMEZONE = "UTC";
|
||||
@@ -1,10 +1,74 @@
|
||||
function pad2(n: number): string {
|
||||
return String(n).padStart(2, "0");
|
||||
function formatParts(date: Date, timeZone?: string): string {
|
||||
const parts = new Intl.DateTimeFormat("en-CA", {
|
||||
timeZone,
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hourCycle: "h23",
|
||||
}).formatToParts(date);
|
||||
|
||||
const map = new Map(parts.map((part) => [part.type, part.value]));
|
||||
const year = map.get("year") ?? "0000";
|
||||
const month = map.get("month") ?? "00";
|
||||
const day = map.get("day") ?? "00";
|
||||
const hour = map.get("hour") ?? "00";
|
||||
const minute = map.get("minute") ?? "00";
|
||||
const second = map.get("second") ?? "00";
|
||||
|
||||
return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
|
||||
}
|
||||
|
||||
const NAIVE_SCHEDULE_CLOCK_RE =
|
||||
/^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2}):(\d{2})$/;
|
||||
|
||||
function parseScheduleClockToMs(
|
||||
clock: string,
|
||||
scheduleTimezone: string,
|
||||
): number | null {
|
||||
const trimmed = clock.trim();
|
||||
if (/[zZ]|[+-]\d{2}:?\d{2}$/.test(trimmed) || trimmed.includes("T")) {
|
||||
const ms = Date.parse(trimmed);
|
||||
return Number.isNaN(ms) ? null : ms;
|
||||
}
|
||||
const match = NAIVE_SCHEDULE_CLOCK_RE.exec(trimmed);
|
||||
if (!match) {
|
||||
const ms = Date.parse(trimmed);
|
||||
return Number.isNaN(ms) ? null : ms;
|
||||
}
|
||||
const [, y, mo, d, h, mi, s] = match;
|
||||
if (scheduleTimezone !== "UTC") {
|
||||
const ms = Date.parse(`${y}-${mo}-${d}T${h}:${mi}:${s}`);
|
||||
return Number.isNaN(ms) ? null : ms;
|
||||
}
|
||||
return Date.UTC(
|
||||
Number(y),
|
||||
Number(mo) - 1,
|
||||
Number(d),
|
||||
Number(h),
|
||||
Number(mi),
|
||||
Number(s),
|
||||
);
|
||||
}
|
||||
|
||||
/** 浏览器本地时区短标签(如 CST、GMT+8),用于界面说明。 */
|
||||
export function getBrowserTimeZoneLabel(date = new Date()): string {
|
||||
try {
|
||||
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
const parts = new Intl.DateTimeFormat(undefined, {
|
||||
timeZone,
|
||||
timeZoneName: "short",
|
||||
}).formatToParts(date);
|
||||
return parts.find((part) => part.type === "timeZoneName")?.value ?? timeZone;
|
||||
} catch {
|
||||
return "Local";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将接口 ISO 时间串格式化为 **浏览器本地时区** 下的 `YYYY-MM-DD HH:mm:ss`,
|
||||
* 与后台 `lotteryadmin/src/lib/admin-datetime.ts` {@link formatAdminInstant} 行为一致。
|
||||
* 将接口 ISO 时间串格式化为 **浏览器本地时区** 下的 `YYYY-MM-DD HH:mm:ss`。
|
||||
*/
|
||||
export function formatLotteryInstant(iso: string | null | undefined): string {
|
||||
if (iso == null || iso === "") {
|
||||
@@ -14,12 +78,44 @@ export function formatLotteryInstant(iso: string | null | undefined): string {
|
||||
if (Number.isNaN(ms)) {
|
||||
return "—";
|
||||
}
|
||||
const date = new Date(ms);
|
||||
const y = date.getFullYear();
|
||||
const m = pad2(date.getMonth() + 1);
|
||||
const d = pad2(date.getDate());
|
||||
const h = pad2(date.getHours());
|
||||
const min = pad2(date.getMinutes());
|
||||
const s = pad2(date.getSeconds());
|
||||
return `${y}-${m}-${d} ${h}:${min}:${s}`;
|
||||
return formatParts(new Date(ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将服务端计划时刻(无时区的 `YYYY-MM-DD HH:mm:ss`,按 schedule_timezone 解释,默认 UTC)
|
||||
* 格式化为浏览器本地时区的 `YYYY-MM-DD HH:mm:ss`。
|
||||
*/
|
||||
export function formatLotteryScheduleClock(
|
||||
clock: string | null | undefined,
|
||||
scheduleTimezone = "UTC",
|
||||
): string {
|
||||
if (clock == null || clock === "") {
|
||||
return "—";
|
||||
}
|
||||
const ms = parseScheduleClockToMs(clock, scheduleTimezone);
|
||||
if (ms == null) {
|
||||
return "—";
|
||||
}
|
||||
return formatParts(new Date(ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* 按指定 IANA 时区格式化(管理端等场景);玩家端展示请用 {@link formatLotteryInstant}。
|
||||
*/
|
||||
export function formatLotteryInstantInTimeZone(
|
||||
iso: string | null | undefined,
|
||||
timeZone: string,
|
||||
): string {
|
||||
if (iso == null || iso === "") {
|
||||
return "—";
|
||||
}
|
||||
const ms = Date.parse(iso);
|
||||
if (Number.isNaN(ms)) {
|
||||
return "—";
|
||||
}
|
||||
try {
|
||||
return formatParts(new Date(ms), timeZone);
|
||||
} catch {
|
||||
return formatParts(new Date(ms));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user