feat(douyu): 工作台默认空白账号,导入/移除通过 localStorage 持久化,刷新不再加载全部账号
This commit is contained in:
@@ -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}%"
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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[]>([]);
|
||||||
|
// 工作台已导入账号 ID(localStorage 持久化,默认空白,用户手动导入/移除)
|
||||||
|
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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user