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 (
{(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 查询与手动分配
先查询保留状态,再分配给指定用户
{checkResult && (
{checkResult.sid}{' '}
{checkResult.reserved ? (
保留号段
) : (
可分配
)}
{checkResult.reserved && !canOverride && (
分配保留号段需要 admin:sid:override 权限
)}
)}
setUserUid(e.target.value.toUpperCase())}
placeholder="01JXXXXXXXXXXXXXXXXXXXXXXX"
/>
)
}