import { createFileRoute } from '@tanstack/react-router' import { PERM, requirePermission } from '@/auth/permissions' import { useCan } from '@/auth/store' import { useQuery, useQueryClient } from '@tanstack/react-query' import { useState } from 'react' import { Plus, Trash2 } from 'lucide-react' import { toast } from 'sonner' import type { LuckyNumber, SidCheckResult, SidRule } from '@/api/types' import { assignSid, checkSid, createLuckyNumber, deleteLuckyNumber, fetchLuckyNumbers, fetchSidRules, updateLuckyNumber, updateSidRule, } from '@/api/modules/sid' import { handleApiError } from '@/lib/errors' import { ConfirmDialog } from '@/components/confirm-dialog' import { PageHeader } from '@/components/page-header' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Switch } from '@/components/ui/switch' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' export const Route = createFileRoute('/_authed/sid')({ beforeLoad: () => requirePermission(PERM.SID_MANAGE), component: SidPage, }) function SidPage() { return (
保留规则 幸运号码 查询 / 分配
) } function RulesTab() { const queryClient = useQueryClient() const rules = useQuery({ queryKey: ['sid', 'rules'], queryFn: fetchSidRules }) const toggle = async (rule: SidRule, enabled: boolean) => { try { await updateSidRule(rule.key, { enabled, description: rule.description }) toast.success(`规则「${rule.key}」已${enabled ? '启用' : '停用'}`) void queryClient.invalidateQueries({ queryKey: ['sid', 'rules'] }) } catch (error) { handleApiError(error) } } if (rules.isPending) return

正在加载保留规则…

if (rules.isError) { return (

保留规则加载失败。

) } return (
{(rules.data?.data ?? []).map((rule) => (
{rule.key}

{rule.description ?? '—'}

toggle(rule, v)} />
))} {rules.isSuccess && (rules.data?.data ?? []).length === 0 && (

暂无规则

)}
) } function LuckyTab() { const queryClient = useQueryClient() const numbers = useQuery({ queryKey: ['sid', 'lucky'], queryFn: fetchLuckyNumbers }) const [draft, setDraft] = useState({ number: '', category: '' }) const refresh = () => queryClient.invalidateQueries({ queryKey: ['sid', 'lucky'] }) const add = async () => { try { await createLuckyNumber({ number: draft.number, category: draft.category || null, enabled: true, }) toast.success(`已添加幸运号码 ${draft.number}`) setDraft({ number: '', category: '' }) void refresh() } catch (error) { handleApiError(error) } } const toggle = async (row: LuckyNumber, enabled: boolean) => { try { await updateLuckyNumber(row.id, { enabled }) void refresh() } catch (error) { handleApiError(error) } } const remove = async (row: LuckyNumber) => { try { await deleteLuckyNumber(row.id) toast.success(`已删除 ${row.number}`) void refresh() } catch (error) { handleApiError(error) } } if (numbers.isPending) return

正在加载幸运号码…

if (numbers.isError) { return (

幸运号码加载失败。

) } return (
setDraft((d) => ({ ...d, number: e.target.value.replace(/\D/g, '') }))} placeholder="888" />
setDraft((d) => ({ ...d, category: e.target.value }))} placeholder="豹子号" />
{(numbers.data?.data ?? []).map((row) => (
{row.number} {row.category && {row.category}}
toggle(row, v)} /> } title={`删除幸运号码 ${row.number}?`} confirmLabel="删除" destructive onConfirm={() => remove(row)} />
))}
{numbers.isSuccess && (numbers.data?.data ?? []).length === 0 && (

暂无幸运号码

)}
) } function AssignTab() { const canOverride = useCan(PERM.SID_OVERRIDE) const [sid, setSid] = useState('') const [checkResult, setCheckResult] = useState(null) const [userUid, setUserUid] = useState('') const [busy, setBusy] = useState(false) const validSid = /^[A-HJ-NPR-Z]{3}-?\d{6}$/i.test(sid) const validUid = /^[0-9A-HJKMNP-TV-Z]{26}$/i.test(userUid) const check = async () => { setBusy(true) setCheckResult(null) try { const res = await checkSid(sid) setCheckResult(res.data) } catch (error) { handleApiError(error) } finally { setBusy(false) } } const assign = async () => { setBusy(true) try { await assignSid(userUid, sid) toast.success(`已将 SID ${sid} 分配给 ${userUid}`) } catch (error) { handleApiError(error) } finally { setBusy(false) } } return ( SID 查询与手动分配 先查询保留状态,再分配给指定用户
{ setSid(e.target.value.toUpperCase()) setCheckResult(null) }} placeholder="ABC-668888" />
{checkResult && (
{checkResult.sid}{' '} {checkResult.reserved ? ( 保留号段 ) : ( 可分配 )} {checkResult.reserved && !canOverride && (

分配保留号段需要 admin:sid:override 权限

)}
)}
setUserUid(e.target.value.toUpperCase())} placeholder="01JXXXXXXXXXXXXXXXXXXXXXXX" />
) }