feat: integrate public settings into Play Rules screen and update button properties

- Added functionality to fetch and display dynamic rules content in the Play Rules screen.
- Updated button components in HallBetResultDialog and WinningResultDialog to include nativeButton property for improved behavior.
This commit is contained in:
2026-05-22 16:11:58 +08:00
parent b61a2ab07b
commit 52702c9fbb
5 changed files with 58 additions and 167 deletions

View File

@@ -22,3 +22,4 @@ export {
type WalletTransferBody,
type WalletTransferResultData,
} from "@/api/wallet";
export { getPublicSettings, type SettingItem, type SettingListResponse } from "@/api/settings";

19
src/api/settings.ts Normal file
View File

@@ -0,0 +1,19 @@
import { lotteryRequest } from "@/lib/lottery-http";
import { API_V1_PREFIX } from "@/api/paths";
export type SettingItem = {
key: string;
value: unknown;
group: string;
description: string | null;
};
export type SettingListResponse = {
items: SettingItem[];
};
export async function getPublicSettings(group: string): Promise<SettingListResponse> {
return lotteryRequest.get<SettingListResponse>(`${API_V1_PREFIX}/settings`, {
params: { group },
});
}

View File

@@ -210,6 +210,7 @@ export function HallBetResultDialog({
<div className="grid shrink-0 grid-cols-2 gap-2.5 border-t border-[#e8eef7] bg-white p-3 pb-[calc(0.75rem+env(safe-area-inset-bottom))] sm:p-4">
<Button
type="button"
nativeButton={false}
variant="outline"
className="h-12 rounded-lg border-[#8dadf0] bg-white text-base font-black text-[#0b3f96] hover:bg-[#f4f8ff]"
render={<Link href="/orders" />}

View File

@@ -286,6 +286,7 @@ function WinningResultDialog({
<div className="mt-5 grid gap-3">
<Button
type="button"
nativeButton={false}
className="h-12 rounded-xl bg-[#07459f] text-base font-black text-white hover:bg-[#063b88]"
render={<Link href={`/orders?draw_no=${encodeURIComponent(data.draw.draw_no)}&number=${encodeURIComponent(query)}`} />}
>

View File

@@ -1,82 +1,36 @@
"use client";
import Link from "next/link";
import { BookOpen, ShieldCheck, TimerReset } from "lucide-react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Loader2 } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { getPublicSettings } from "@/api";
import { PlayerPanel } from "@/components/layout/player-panel";
const prizeRows = [
{ label: "rules.prizes.first", count: "1" },
{ label: "rules.prizes.second", count: "1" },
{ label: "rules.prizes.third", count: "1" },
{ label: "rules.prizes.starter", count: "10" },
{ label: "rules.prizes.consolation", count: "10" },
] as const;
const playSections = [
{
title: "rules.sections.dimensions.title",
items: [
"rules.sections.dimensions.d4",
"rules.sections.dimensions.d3",
"rules.sections.dimensions.d2",
],
},
{
title: "rules.sections.bigSmall.title",
items: [
"rules.sections.bigSmall.big",
"rules.sections.bigSmall.small",
],
},
{
title: "rules.sections.positions.title",
items: [
"rules.sections.positions.d4",
"rules.sections.positions.d3",
"rules.sections.positions.d2",
],
},
{
title: "rules.sections.box.title",
items: [
"rules.sections.box.straight",
"rules.sections.box.box",
"rules.sections.box.ibox",
"rules.sections.box.mbox",
"rules.sections.box.roll",
"rules.sections.box.half",
],
},
{
title: "rules.sections.attributes.title",
items: [
"rules.sections.attributes.headTail",
"rules.sections.attributes.oddEven",
"rules.sections.attributes.digitSize",
],
},
{
title: "rules.sections.wallet.title",
items: [
"rules.sections.wallet.rebate",
"rules.sections.wallet.jackpot",
"rules.sections.wallet.close",
"rules.sections.wallet.soldOut",
],
},
] as const;
import { Card, CardContent } from "@/components/ui/card";
export function PlayRulesScreen() {
const { t } = useTranslation("player");
const [htmlContent, setHtmlContent] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function loadRules() {
try {
const res = await getPublicSettings("frontend");
const ruleItem = res.items.find((item) => item.key === "frontend.play_rules_html");
if (ruleItem && typeof ruleItem.value === "string" && ruleItem.value.trim() !== "") {
setHtmlContent(ruleItem.value);
} else {
setHtmlContent(`<div style="text-align:center;padding:2rem;color:#64748b;">${t("rules.empty", { defaultValue: "暂无玩法规则说明" })}</div>`);
}
} catch {
setHtmlContent(`<div style="text-align:center;padding:2rem;color:#64748b;">${t("rules.loadFailed", { defaultValue: "规则加载失败" })}</div>`);
} finally {
setLoading(false);
}
}
void loadRules();
}, [t]);
return (
<PlayerPanel
@@ -85,103 +39,18 @@ export function PlayRulesScreen() {
className="bg-[#f7f9fd]"
>
<div className="space-y-3">
<Card className="border-[#dbe7fb] bg-white shadow-sm">
<CardHeader className="space-y-3 pb-3">
<div className="flex items-center gap-2">
<span className="flex size-9 items-center justify-center rounded-2xl bg-[#0b3f96] text-white">
<BookOpen className="size-4" aria-hidden />
</span>
<div className="min-w-0">
<CardTitle className="text-base text-[#0b3f96]">
{t("rules.quick.title")}
</CardTitle>
<p className="mt-0.5 text-xs text-slate-500">
{t("rules.quick.description")}
</p>
<Card className="border-[#dbe7fb] bg-white shadow-sm overflow-hidden min-h-[300px]">
<CardContent className="p-4 sm:p-6">
{loading ? (
<div className="flex h-[200px] items-center justify-center">
<Loader2 className="size-6 animate-spin text-slate-400" />
</div>
</div>
<div className="grid grid-cols-3 gap-2 text-center">
<div className="rounded-2xl bg-[#eef5ff] p-3">
<p className="text-lg font-black text-[#0b3f96]">23</p>
<p className="text-[11px] font-semibold text-slate-500">
{t("rules.quick.totalPrizes")}
</p>
</div>
<div className="rounded-2xl bg-[#fff2f4] p-3">
<p className="text-lg font-black text-[#f10b32]">15</p>
<p className="text-[11px] font-semibold text-slate-500">
{t("rules.quick.cooldown")}
</p>
</div>
<div className="rounded-2xl bg-[#eefbf4] p-3">
<p className="text-lg font-black text-[#168a4a]">1</p>
<p className="text-[11px] font-semibold text-slate-500">
{t("rules.quick.snapshot")}
</p>
</div>
</div>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 gap-2">
{prizeRows.map((row) => (
) : (
<div
key={row.label}
className="flex items-center justify-between rounded-xl border border-[#e6edf7] bg-white px-3 py-2 text-sm"
>
<span className="font-semibold text-slate-700">
{t(row.label)}
</span>
<Badge variant="secondary">{row.count}</Badge>
</div>
))}
</div>
</CardContent>
</Card>
<div className="grid gap-3">
{playSections.map((section) => (
<Card key={section.title} className="border-[#e1e8f2] bg-white shadow-sm">
<CardHeader className="pb-2">
<CardTitle className="text-sm font-black text-slate-900">
{t(section.title)}
</CardTitle>
</CardHeader>
<CardContent>
<ul className="space-y-2">
{section.items.map((item) => (
<li
key={item}
className="rounded-xl bg-[#f8fafc] px-3 py-2 text-sm leading-6 text-slate-700"
>
{t(item)}
</li>
))}
</ul>
</CardContent>
</Card>
))}
</div>
<Card className="border-[#d9e5f5] bg-[#0b3f96] text-white shadow-sm">
<CardContent className="space-y-3 p-4">
<div className="flex gap-3">
<ShieldCheck className="mt-0.5 size-5 shrink-0" aria-hidden />
<p className="text-sm leading-6 text-white/90">
{t("rules.footer.config")}
</p>
</div>
<div className="flex gap-3">
<TimerReset className="mt-0.5 size-5 shrink-0" aria-hidden />
<p className="text-sm leading-6 text-white/90">
{t("rules.footer.phaseTwo")}
</p>
</div>
<Link
href="/hall"
className="inline-flex w-full items-center justify-center rounded-full bg-white px-4 py-2 text-sm font-black text-[#0b3f96]"
>
{t("rules.footer.backBet")}
</Link>
className="prose prose-sm max-w-none text-slate-700"
dangerouslySetInnerHTML={{ __html: htmlContent || "" }}
/>
)}
</CardContent>
</Card>
</div>