feat(douyu): 工作台默认空白账号,导入/移除通过 localStorage 持久化,刷新不再加载全部账号

This commit is contained in:
yml2213
2026-08-04 16:25:49 +08:00
parent a466e11c9c
commit dff0d7db9e
3 changed files with 43 additions and 8 deletions
+8 -1
View File
@@ -169,11 +169,18 @@ def task_types(current: User = Depends(require_permission("douyu:task"))):
@router.get("/accounts", response_model=list[DouyuTaskAccountOut]) @router.get("/accounts", response_model=list[DouyuTaskAccountOut])
def list_task_accounts( def list_task_accounts(
search: str = Query(""), search: str = Query(""),
ids: str = Query(""),
db: Session = Depends(get_db), db: Session = Depends(get_db),
current: User = Depends(get_current_user), current: User = Depends(get_current_user),
): ):
"""查看可执行斗鱼任务的账号(必须有成功 Cookie)。""" """查看可执行斗鱼任务的账号(必须有成功 Cookie)。
ids 为逗号分隔的账号 ID,用于工作台按已导入账号过滤;为空时返回全部。
"""
query = _visible_task_accounts_query(db, current) query = _visible_task_accounts_query(db, current)
id_list = [int(x) for x in ids.split(",") if x.strip().isdigit()]
if id_list:
query = query.filter(Account.id.in_(id_list))
search_text = (search or "").strip() search_text = (search or "").strip()
if search_text: if search_text:
pattern = f"%{search_text}%" pattern = f"%{search_text}%"
+1 -1
View File
@@ -11,7 +11,7 @@ import type {
export const douyuApi = { export const douyuApi = {
taskTypes: () => api.get<Record<string, string>, Record<string, string>>('/douyu/task-types'), taskTypes: () => api.get<Record<string, string>, Record<string, string>>('/douyu/task-types'),
listAccounts: (params?: { search?: string }) => listAccounts: (params?: { search?: string; ids?: string }) =>
api.get<DouyuTaskAccountItem[], DouyuTaskAccountItem[]>('/douyu/accounts', { params }), api.get<DouyuTaskAccountItem[], DouyuTaskAccountItem[]>('/douyu/accounts', { params }),
getConfig: () => api.get<DouyuConfig, DouyuConfig>('/douyu/config'), getConfig: () => api.get<DouyuConfig, DouyuConfig>('/douyu/config'),
updateConfig: (data: Partial<DouyuConfig>) => api.put<DouyuConfig, DouyuConfig>('/douyu/config', data), updateConfig: (data: Partial<DouyuConfig>) => api.put<DouyuConfig, DouyuConfig>('/douyu/config', data),
+34 -6
View File
@@ -117,6 +117,7 @@ const ESPORTS_TASK_TYPES = new Set([
const DOUYU_GOLD_AMOUNT_STORAGE_KEY = 'douyu_task_gold_amount'; const DOUYU_GOLD_AMOUNT_STORAGE_KEY = 'douyu_task_gold_amount';
const DOUYU_GIFT_COUNT_STORAGE_KEY = 'douyu_task_gift_count'; const DOUYU_GIFT_COUNT_STORAGE_KEY = 'douyu_task_gift_count';
const DOUYU_LAYOUT_MODE_STORAGE_KEY = 'douyu_task_layout_mode'; const DOUYU_LAYOUT_MODE_STORAGE_KEY = 'douyu_task_layout_mode';
const DOUYU_WORKBENCH_IDS_STORAGE_KEY = (kind: HandbookKind) => `douyu_task_workbench_ids_${kind}`;
const CONFIRM_FAIL_TIP_SECONDS = 6; const CONFIRM_FAIL_TIP_SECONDS = 6;
function resultText(result: Record<string, unknown> | null | undefined, key: string): string { function resultText(result: Record<string, unknown> | null | undefined, key: string): string {
@@ -272,6 +273,12 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
const quickActions = isEsportsHandbook ? ESPORTS_QUICK_ACTIONS : ELITE_QUICK_ACTIONS; const quickActions = isEsportsHandbook ? ESPORTS_QUICK_ACTIONS : ELITE_QUICK_ACTIONS;
const { can } = usePermissions(); const { can } = usePermissions();
const [accounts, setAccounts] = useState<DouyuTaskAccountItem[]>([]); const [accounts, setAccounts] = useState<DouyuTaskAccountItem[]>([]);
// 工作台已导入账号 IDlocalStorage 持久化,默认空白,用户手动导入/移除)
const [workbenchIds, setWorkbenchIds] = useState<number[]>(() => {
const raw = localStorage.getItem(DOUYU_WORKBENCH_IDS_STORAGE_KEY(handbook));
if (!raw) return [];
return raw.split(',').map(Number).filter(Number.isInteger).filter((id) => id >= 1);
});
const [selectedIds, setSelectedIds] = useState<number[]>([]); const [selectedIds, setSelectedIds] = useState<number[]>([]);
const [goods, setGoods] = useState<DouyuGoodsItem[]>([]); const [goods, setGoods] = useState<DouyuGoodsItem[]>([]);
const [tasks, setTasks] = useState<DouyuTaskItem[]>([]); const [tasks, setTasks] = useState<DouyuTaskItem[]>([]);
@@ -450,13 +457,21 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
setLoading(true); setLoading(true);
try { try {
const [accResult, goodsResult, taskResult, cfgResult, typeResult] = await Promise.allSettled([ const [accResult, goodsResult, taskResult, cfgResult, typeResult] = await Promise.allSettled([
douyuApi.listAccounts(), workbenchIds.length > 0 ? douyuApi.listAccounts({ ids: workbenchIds.join(',') }) : Promise.resolve([]),
isEsportsHandbook ? douyuApi.listEsportsGoods() : douyuApi.listGoods(), isEsportsHandbook ? douyuApi.listEsportsGoods() : douyuApi.listGoods(),
douyuApi.listTasks(), douyuApi.listTasks(),
canConfig ? douyuApi.getConfig() : Promise.resolve(null), canConfig ? douyuApi.getConfig() : Promise.resolve(null),
douyuApi.taskTypes(), douyuApi.taskTypes(),
]); ]);
if (accResult.status === 'fulfilled') setAccounts(accResult.value); if (accResult.status === 'fulfilled') {
setAccounts(accResult.value);
// 账号被删除等场景下清理已失效的工作台 ID,避免请求残留空 ID
if (workbenchIds.length > 0) {
const validIds = accResult.value.map((a) => a.id);
const next = workbenchIds.filter((id) => validIds.includes(id));
if (next.length !== workbenchIds.length) setWorkbenchIds(next);
}
}
if (goodsResult.status === 'fulfilled') setGoods(goodsResult.value); if (goodsResult.status === 'fulfilled') setGoods(goodsResult.value);
if (taskResult.status === 'fulfilled') { if (taskResult.status === 'fulfilled') {
setTasks(taskResult.value); setTasks(taskResult.value);
@@ -489,7 +504,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [canConfig, isEsportsHandbook]); }, [canConfig, isEsportsHandbook, workbenchIds]);
const loadTasks = useCallback(async () => { const loadTasks = useCallback(async () => {
try { try {
@@ -510,6 +525,14 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
localStorage.setItem(DOUYU_LAYOUT_MODE_STORAGE_KEY, layoutMode); localStorage.setItem(DOUYU_LAYOUT_MODE_STORAGE_KEY, layoutMode);
}, [layoutMode]); }, [layoutMode]);
// 工作台账号集合持久化:移除账号后刷新不再出现
useEffect(() => {
localStorage.setItem(
DOUYU_WORKBENCH_IDS_STORAGE_KEY(handbook),
workbenchIds.length ? workbenchIds.join(',') : '',
);
}, [handbook, workbenchIds]);
// 监听账号表格区域高度变化,动态计算 scroll.y 实现表体内部滚动 // 监听账号表格区域高度变化,动态计算 scroll.y 实现表体内部滚动
useEffect(() => { useEffect(() => {
const node = accountTableAreaRef.current; const node = accountTableAreaRef.current;
@@ -810,16 +833,18 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
const loadImportPool = useCallback(async () => { const loadImportPool = useCallback(async () => {
setImportLoading(true); setImportLoading(true);
try { try {
// 候选池 = 账号库全量,过滤已导入工作台的账号
const result = await douyuApi.listAccounts({ search: importSearch }); const result = await douyuApi.listAccounts({ search: importSearch });
setImportPool(result.filter((a) => !accounts.find((x) => x.id === a.id))); setImportPool(result.filter((a) => !workbenchIds.includes(a.id)));
} finally { } finally {
setImportLoading(false); setImportLoading(false);
} }
}, [importSearch, accounts]); }, [importSearch, workbenchIds]);
useEffect(() => { if (importOpen) void loadImportPool(); }, [importOpen, loadImportPool]); useEffect(() => { if (importOpen) void loadImportPool(); }, [importOpen, loadImportPool]);
const importAccounts = (ids: number[]) => { const importAccounts = (ids: number[]) => {
const toImport = importPool.filter((a) => ids.includes(a.id)); const toImport = importPool.filter((a) => ids.includes(a.id));
setWorkbenchIds((prev) => [...new Set([...prev, ...toImport.map((a) => a.id)])]);
setAccounts((prev) => { setAccounts((prev) => {
const existing = new Set(prev.map((a) => a.id)); const existing = new Set(prev.map((a) => a.id));
return [...prev, ...toImport.filter((a) => !existing.has(a.id))]; return [...prev, ...toImport.filter((a) => !existing.has(a.id))];
@@ -831,8 +856,10 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
const removeSelected = () => { const removeSelected = () => {
const sel = new Set(selectedIds); const sel = new Set(selectedIds);
setWorkbenchIds((prev) => prev.filter((id) => !sel.has(id)));
setAccounts((prev) => prev.filter((a) => !sel.has(a.id))); setAccounts((prev) => prev.filter((a) => !sel.has(a.id)));
setSelectedIds([]); setSelectedIds([]);
message.success(`已移出 ${sel.size} 个账号`);
}; };
// ---- Context menu ---- // ---- Context menu ----
@@ -1524,6 +1551,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
size="small" size="small"
className="douyu-task-record-table" className="douyu-task-record-table"
loading={loading} loading={loading}
locale={{ emptyText: '工作台暂无账号,点击右上角「导入账号」添加' }}
rowSelection={{ rowSelection={{
selectedRowKeys: selectedIds, selectedRowKeys: selectedIds,
onChange: (keys) => setSelectedIds(keys.map(Number)), onChange: (keys) => setSelectedIds(keys.map(Number)),
@@ -1746,7 +1774,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
}, },
]} ]}
pagination={{ pageSize: 10 }} pagination={{ pageSize: 10 }}
locale={{ emptyText: '没有可导入的账号(操作台已包含所有账号)' }} locale={{ emptyText: '没有可导入的账号' }}
/> />
</Modal> </Modal>