压缩历史至 95081aff

This commit is contained in:
ryan
2026-06-08 20:34:27 +08:00
commit 8a782525de
435 changed files with 71146 additions and 0 deletions
@@ -0,0 +1,59 @@
"use client"
import { useEffect, useState } from "react"
import { AlertTriangle } from "lucide-react"
import { RiskInfo, RiskInfoBox } from "@/components/common/risk/risk-info-box"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
const RISK_BLOCKED_EVENT = "credit-risk-blocked"
function isRiskInfo(value: unknown): value is RiskInfo {
if (!value || typeof value !== "object") return false
const riskInfo = value as Partial<RiskInfo>
return typeof riskInfo.risk_level === "string" && Array.isArray(riskInfo.risk_labels) && Array.isArray(riskInfo.risks)
}
export function RiskBlockDialog() {
const [riskInfo, setRiskInfo] = useState<RiskInfo | null>(null)
useEffect(() => {
const handleRiskBlocked = (event: Event) => {
const detail = (event as CustomEvent<unknown>).detail
if (!isRiskInfo(detail)) return
setRiskInfo(detail)
}
window.addEventListener(RISK_BLOCKED_EVENT, handleRiskBlocked)
return () => window.removeEventListener(RISK_BLOCKED_EVENT, handleRiskBlocked)
}, [])
return (
<Dialog open={!!riskInfo}>
<DialogContent
showCloseButton={false}
onEscapeKeyDown={(event) => event.preventDefault()}
onPointerDownOutside={(event) => event.preventDefault()}
onInteractOutside={(event) => event.preventDefault()}
className="sm:max-w-md"
>
<DialogHeader className="items-center text-center sm:text-center">
<div className="mb-1 flex size-12 items-center justify-center rounded-full bg-destructive/10">
<AlertTriangle className="size-6 text-destructive" />
</div>
<DialogTitle className="text-center text-lg">账号存在风险</DialogTitle>
<DialogDescription className="text-center text-sm leading-6">
因触发风控,账号暂时无法使用需登录的功能
</DialogDescription>
</DialogHeader>
<RiskInfoBox riskInfo={riskInfo} />
</DialogContent>
</Dialog>
)
}
@@ -0,0 +1,56 @@
import { cn } from "@/lib/utils"
import { Badge } from "@/components/ui/badge"
export interface RiskItem {
label: string
value?: string
desc?: string
}
export interface RiskInfo {
risk_level: string
risk_labels: string[]
risks: RiskItem[]
}
export function RiskInfoBox({
riskInfo,
label = "风险等级",
labelClassName,
}: {
riskInfo: RiskInfo | null
label?: string
labelClassName?: string
}) {
return (
<div className="rounded-lg border bg-card p-4 shadow-sm">
<div className="flex flex-wrap items-center gap-2">
<span className={cn("text-sm font-medium text-muted-foreground", labelClassName)}>{label}</span>
<Badge variant="destructive">{riskInfo?.risk_level || "未知"}</Badge>
</div>
<div className="mt-4 space-y-2">
{riskInfo?.risks.length ? (
riskInfo.risks.map(risk => (
<div key={risk.value || risk.label} className="rounded-md border bg-muted/35 px-3 py-2.5">
<span className="text-sm font-medium leading-5 text-foreground">{risk.label}</span>
{risk.desc ? (
<p className="mt-1 text-xs leading-5 text-muted-foreground">{risk.desc}</p>
) : null}
</div>
))
) : riskInfo?.risk_labels.length ? (
<div className="flex flex-wrap gap-2">
{riskInfo.risk_labels.map(label => (
<Badge key={label} variant="secondary">
{label}
</Badge>
))}
</div>
) : (
<span className="text-sm text-muted-foreground">暂无风险标签详情</span>
)}
</div>
</div>
)
}
@@ -0,0 +1,39 @@
import { toast } from "sonner"
import { X } from "lucide-react"
import { RiskInfo, RiskInfoBox } from "@/components/common/risk/risk-info-box"
const RISK_TOAST_ID = "credit-risk-warning"
let isRiskWarningDismissed = false
function dismissRiskWarningToast(id: string | number) {
isRiskWarningDismissed = true
toast.dismiss(id)
}
export function showRiskWarningToast(riskInfo: RiskInfo) {
if (isRiskWarningDismissed) return
toast.custom(
(id) => (
<div className="relative w-[min(calc(100vw-2rem),28rem)]">
<button
type="button"
aria-label="关闭风险提示"
className="absolute right-2 top-2 inline-flex size-6 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
onClick={() => dismissRiskWarningToast(id)}
>
<X className="size-4" />
</button>
<RiskInfoBox riskInfo={riskInfo} label="账号存在风险提示" labelClassName="font-semibold text-foreground" />
</div>
),
{
id: RISK_TOAST_ID,
duration: Infinity,
dismissible: true,
onDismiss: () => {
isRiskWarningDismissed = true
},
},
)
}