refactor(bf-bot): 插件重构为 handlers/services/utils 分层,资源归入 res/
- 旧平铺模块(data/data_utils/database_op/image_builder*/get_bf6_data/ gametools_bf6/bf6_data/img_utils)拆分为 handlers(绑定/查询)、 services(fetchers/processing/query)、utils(渲染/参数)分层; 新增 repository.py / config.py / paths.py - 字体/图片/模板资源迁入 res/,新增 BF2042 卡面素材(bg/object_icon 等) - browser_ext/:BF2042 扩展捕获桥(WS 直连兜底通道)源码入库 - 新增 hexi/web/src/plugins/bf_bot 前端管理页 - 新增 tests:test_bf_bot_helpers / repository / tracker_api Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
1 parent
614e1d7e00
commit
a31aab00e8
752 files changed
+6241
-2506
No files matched your search
@@ -0,0 +1,136 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { Button, Card, Spinner, toast } from '@heroui/react'
|
||||
import { TrashBin } from '@gravity-ui/icons'
|
||||
import { api } from '../../api/client'
|
||||
import { PageHead, Pager, tdCls, thCls } from '../../components/ui'
|
||||
|
||||
// QQ↔EAID 绑定管理(后端 hexi/plugins/nonebot_plugin_bf_bot/web/admin.py,
|
||||
// 与群里的「绑定 / 解绑」命令改的是同一份 SQLite 绑定库)
|
||||
const bf = api('bf_bot')
|
||||
const PER_PAGE = 10
|
||||
|
||||
function data(res: any) {
|
||||
if (res && typeof res === 'object' && typeof res.status === 'number') {
|
||||
if (res.status !== 0) throw new Error(res.msg || '请求失败')
|
||||
return res.data
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
interface BindingRow {
|
||||
qq_id: string
|
||||
ea_player_name: string
|
||||
ea_player_id: string
|
||||
ea_user_id: string
|
||||
designation: string
|
||||
create_time: string
|
||||
}
|
||||
|
||||
export default function BfBotPage() {
|
||||
const [rows, setRows] = useState<BindingRow[]>([])
|
||||
const [total, setTotal] = useState(0)
|
||||
const [page, setPage] = useState(1)
|
||||
const [err, setErr] = useState('')
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [deleting, setDeleting] = useState('')
|
||||
|
||||
const load = useCallback(async (p: number) => {
|
||||
setLoading(true); setErr('')
|
||||
try {
|
||||
const d = data(await bf.get(`bindings?page=${p}&per_page=${PER_PAGE}`))
|
||||
setRows((d && d.bindings) || [])
|
||||
setTotal((d && d.total) || 0)
|
||||
} catch (e: any) {
|
||||
setErr(e.message || '加载失败')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [])
|
||||
useEffect(() => { load(page) }, [load, page])
|
||||
|
||||
const remove = async (row: BindingRow) => {
|
||||
if (!window.confirm(`确定解绑 ${row.qq_id}(${row.ea_player_name})?群里需要重新发「绑定 EAID」。`)) return
|
||||
setDeleting(row.qq_id)
|
||||
try {
|
||||
const res = data(await bf.del(`bindings/${encodeURIComponent(row.qq_id)}`))
|
||||
toast.success(res?.msg || '已解绑')
|
||||
// 删掉本页最后一条且不在第一页时退回上一页,否则刷新当前页
|
||||
if (rows.length === 1 && page > 1) setPage(page - 1)
|
||||
else await load(page)
|
||||
} catch (e: any) {
|
||||
toast.danger(e.message || '解绑失败')
|
||||
} finally {
|
||||
setDeleting('')
|
||||
}
|
||||
}
|
||||
|
||||
if (loading && !rows.length) return <div className="flex justify-center py-20"><Spinner /></div>
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<PageHead
|
||||
title="BF查询"
|
||||
actions={
|
||||
<span className="text-sm text-slate-400">
|
||||
群里发「绑定 EAID」新增绑定,这里只做查看与解绑
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
{err && <p className="text-xs text-red-500">{err}</p>}
|
||||
|
||||
<Card className="border border-slate-200 bg-white">
|
||||
<Card.Header>
|
||||
<Card.Title>
|
||||
绑定列表
|
||||
<span className="ml-2 text-xs font-normal text-slate-400">{total} 条</span>
|
||||
</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className={thCls}>QQ</th>
|
||||
<th className={thCls}>EA 名称</th>
|
||||
<th className={thCls}>EA 双 ID</th>
|
||||
<th className={thCls}>绑定时间</th>
|
||||
<th className={thCls}></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map(r => (
|
||||
<tr key={r.qq_id} className="transition-colors hover:bg-slate-50">
|
||||
<td className={tdCls + ' font-mono text-xs'}>{r.qq_id}</td>
|
||||
<td className={tdCls}>{r.ea_player_name || '-'}</td>
|
||||
<td className={tdCls + ' font-mono text-xs'}>
|
||||
{r.ea_player_id || '-'} / {r.ea_user_id || '-'}
|
||||
</td>
|
||||
<td className={tdCls + ' whitespace-nowrap text-xs text-slate-400'}>
|
||||
{r.create_time || '-'}
|
||||
</td>
|
||||
<td className={tdCls + ' whitespace-nowrap text-right'}>
|
||||
<Button
|
||||
size="sm" variant="tertiary" className="text-red-500"
|
||||
isDisabled={deleting === r.qq_id}
|
||||
onPress={() => remove(r)}
|
||||
>
|
||||
<TrashBin className="size-4" />
|
||||
{deleting === r.qq_id ? '解绑中…' : '解绑'}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{!rows.length && (
|
||||
<tr>
|
||||
<td colSpan={5} className={tdCls + ' text-center text-slate-400'}>
|
||||
{loading ? '加载中…' : '还没有任何绑定,群里 @机器人 发「绑定 EAID」即可'}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
<Pager page={page} total={total} perPage={PER_PAGE} onPage={setPage} />
|
||||
</Card.Content>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user