feat: 增强 iframe 通信机制与通知处理功能

实现 resolvePostMessageTargetOrigin,优化 iframe 消息通信中的目标来源(origin)解析与校验。
更新 IframeBridge:支持定期刷新允许的来源列表,并优化消息事件管理机制。
重构 usePendingWalletReconcile:优化待对账通知的获取与缓存逻辑,提升性能与用户体验。
增强 NotificationsScreen:新增待对账通知内容,并优化界面展示效果。
更新英文、尼泊尔语与中文语言包,新增待对账通知相关翻译文案。
This commit is contained in:
2026-06-01 13:38:30 +08:00
parent aeaba5eea3
commit b819894e75
17 changed files with 362 additions and 72 deletions

View File

@@ -166,14 +166,6 @@ export function useHallDrawLive(): HallDrawLiveSnapshot {
return () => window.removeEventListener(PLAYER_CURRENCY_CHANGE_EVENT, onCurrencyChange);
}, [load]);
// 初始加载
useEffect(() => {
const timer = window.setTimeout(() => {
void load();
}, 0);
return () => window.clearTimeout(timer);
}, [load]);
// 爆池等场景:刷新大厅快照(含奖池余额)
useEffect(() => {
const onHallRefresh = () => {

View File

@@ -4,6 +4,7 @@ import { useEffect } from "react";
import { getPublicCurrencies } from "@/api/currency";
import { getPlayerMe } from "@/api/player";
import { loadCurrencyDisplayFormat } from "@/lib/currency-display-settings";
import { usePlayerSessionStore } from "@/stores/player-session-store";
/**
@@ -19,6 +20,11 @@ export function HydratePlayerAuth(): null {
useEffect(() => {
usePlayerSessionStore.getState().reconcileSelectedCurrency();
void loadCurrencyDisplayFormat();
const refreshFormat = setInterval(() => {
void loadCurrencyDisplayFormat(true);
}, 60_000);
const token = restoreBearerToken();
void (async () => {
try {
@@ -40,6 +46,10 @@ export function HydratePlayerAuth(): null {
/* 401 由 lottery-http 拦截跳转 */
}
})();
return () => {
clearInterval(refreshFormat);
};
}, [restoreBearerToken, setCurrencies, setProfile]);
return null;

View File

@@ -6,10 +6,13 @@ import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button";
import { PlayerPanel } from "@/components/layout/player-panel";
import { logTypeLabel } from "@/features/wallet/wallet-logs-block";
import { usePendingWalletReconcile } from "@/hooks/use-pending-wallet-reconcile";
import { formatLocalDateTime } from "@/lib/format-local-datetime";
import { formatMinorAsCurrency } from "@/lib/money";
import {
pendingReconcileDescriptionKey,
pendingReconcileTitleKey,
} from "@/lib/pending-reconcile-notification";
import { cn } from "@/lib/utils";
export function NotificationsScreen() {
@@ -21,6 +24,10 @@ export function NotificationsScreen() {
return (
<PlayerPanel title={t("notifications.title")} backHref="/hall">
<div className="space-y-3">
<p className="rounded-xl border border-amber-200 bg-amber-50/90 px-3 py-2.5 text-xs leading-relaxed text-amber-900">
{t("notifications.subtitle")}
</p>
<div className="flex items-center justify-between rounded-xl border border-[#dce7f7] bg-[#f8fbff] px-3 py-2.5">
<p className="text-sm font-semibold text-[#0b3f96]">
{t("notifications.unreadCount", { count: unreadCount })}
@@ -67,26 +74,36 @@ export function NotificationsScreen() {
>
<div className="flex items-start justify-between gap-2">
<div className="min-w-0">
<p className="text-sm font-bold text-[#0b3f96]">
{logTypeLabel(item.type, t)}
<p className="text-sm font-bold text-amber-900">
{t(pendingReconcileTitleKey(item.type))}
</p>
<p className="mt-0.5 text-xs text-slate-500">
{formatLocalDateTime(item.created_at)}
</p>
</div>
<span
className={cn(
"shrink-0 rounded-full px-2 py-0.5 text-[11px] font-bold",
cardRead
? "bg-slate-100 text-slate-500"
: "bg-amber-100 text-amber-700",
)}
>
{cardRead ? t("notifications.read") : t("notifications.unread")}
</span>
<div className="flex shrink-0 flex-col items-end gap-1">
<span className="rounded-full bg-amber-100 px-2 py-0.5 text-[11px] font-bold text-amber-800">
{t("notifications.pendingBadge")}
</span>
<span
className={cn(
"text-[10px] font-semibold",
cardRead ? "text-slate-400" : "text-amber-700",
)}
>
{cardRead ? t("notifications.read") : t("notifications.unread")}
</span>
</div>
</div>
<p className="mt-2 text-xs leading-relaxed text-amber-950/85">
{t(pendingReconcileDescriptionKey(item.type))}
</p>
<p className="mt-2 text-sm text-slate-700">
<span className="text-xs font-medium text-slate-500">
{t("notifications.amountLabel")}{" "}
</span>
{formatMinorAsCurrency(item.amount, item.currency_code)}
</p>
@@ -105,7 +122,7 @@ export function NotificationsScreen() {
className="inline-flex h-8 items-center rounded-full bg-[#07459f] px-3 text-xs font-semibold text-white hover:bg-[#063b88]"
onClick={() => markAsRead(item.transfer_no)}
>
{t("notifications.open")}
{t("notifications.viewLogs")}
</Link>
</div>
</li>