斗鱼客服导入账号 支持tag
This commit is contained in:
@@ -25,3 +25,4 @@ web/frontend/node_modules/
|
|||||||
web/frontend/dist/
|
web/frontend/dist/
|
||||||
data/web.db
|
data/web.db
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.reasonix/
|
||||||
|
|||||||
@@ -239,6 +239,7 @@ def task_types(current: User = Depends(require_permission("douyu:task"))):
|
|||||||
@router.get("/accounts")
|
@router.get("/accounts")
|
||||||
def list_task_accounts(
|
def list_task_accounts(
|
||||||
search: str = Query(""),
|
search: str = Query(""),
|
||||||
|
tag: str = Query(""),
|
||||||
ids: str = Query(""),
|
ids: str = Query(""),
|
||||||
page: int | None = Query(None, ge=1),
|
page: int | None = Query(None, ge=1),
|
||||||
page_size: int = Query(50, ge=1, le=200),
|
page_size: int = Query(50, ge=1, le=200),
|
||||||
@@ -247,12 +248,15 @@ def list_task_accounts(
|
|||||||
):
|
):
|
||||||
"""查看可执行斗鱼任务的账号(必须有成功 Cookie)。
|
"""查看可执行斗鱼任务的账号(必须有成功 Cookie)。
|
||||||
|
|
||||||
ids 为逗号分隔的账号 ID,用于工作台按已导入账号过滤;为空时返回全部。
|
tag 按账号标签精确筛选;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()]
|
id_list = [int(x) for x in ids.split(",") if x.strip().isdigit()]
|
||||||
if id_list:
|
if id_list:
|
||||||
query = query.filter(Account.id.in_(id_list))
|
query = query.filter(Account.id.in_(id_list))
|
||||||
|
tag_text = (tag or "").strip()
|
||||||
|
if tag_text:
|
||||||
|
query = query.filter(Account.tag == tag_text)
|
||||||
search_text = (search or "").strip()
|
search_text = (search or "").strip()
|
||||||
if search_text:
|
if search_text:
|
||||||
pattern = f"%{search_text}%"
|
pattern = f"%{search_text}%"
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ 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; ids?: string }) =>
|
listAccounts: (params?: { search?: string; tag?: string; ids?: string }) =>
|
||||||
api.get<DouyuTaskAccountItem[], DouyuTaskAccountItem[]>('/douyu/accounts', { params }),
|
api.get<DouyuTaskAccountItem[], DouyuTaskAccountItem[]>('/douyu/accounts', { params }),
|
||||||
listAccountsPaged: (params: PageParams & { ids?: string }) =>
|
listAccountsPaged: (params: PageParams & { tag?: string; ids?: string }) =>
|
||||||
api.get<PaginatedResponse<DouyuTaskAccountItem>, PaginatedResponse<DouyuTaskAccountItem>>('/douyu/accounts', { params }),
|
api.get<PaginatedResponse<DouyuTaskAccountItem>, PaginatedResponse<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),
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
SearchOutlined, SettingOutlined, ShoppingOutlined, StopOutlined,
|
SearchOutlined, SettingOutlined, ShoppingOutlined, StopOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import {
|
import {
|
||||||
|
accountApi,
|
||||||
douyuApi,
|
douyuApi,
|
||||||
type DouyuConfig,
|
type DouyuConfig,
|
||||||
type DouyuGoodsItem,
|
type DouyuGoodsItem,
|
||||||
@@ -307,6 +308,8 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
|||||||
const [importOpen, setImportOpen] = useState(false);
|
const [importOpen, setImportOpen] = useState(false);
|
||||||
const [importPool, setImportPool] = useState<DouyuTaskAccountItem[]>([]);
|
const [importPool, setImportPool] = useState<DouyuTaskAccountItem[]>([]);
|
||||||
const [importSearch, setImportSearch] = useState('');
|
const [importSearch, setImportSearch] = useState('');
|
||||||
|
const [importTag, setImportTag] = useState('');
|
||||||
|
const [importTags, setImportTags] = useState<string[]>([]);
|
||||||
const [importSelectedIds, setImportSelectedIds] = useState<number[]>([]);
|
const [importSelectedIds, setImportSelectedIds] = useState<number[]>([]);
|
||||||
const [importLoading, setImportLoading] = useState(false);
|
const [importLoading, setImportLoading] = useState(false);
|
||||||
|
|
||||||
@@ -838,14 +841,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, tag: importTag || undefined });
|
||||||
setImportPool(result.filter((a) => !workbenchIds.includes(a.id)));
|
setImportPool(result.filter((a) => !workbenchIds.includes(a.id)));
|
||||||
} finally {
|
} finally {
|
||||||
setImportLoading(false);
|
setImportLoading(false);
|
||||||
}
|
}
|
||||||
}, [importSearch, workbenchIds]);
|
}, [importSearch, importTag, workbenchIds]);
|
||||||
useEffect(() => { if (importOpen) void loadImportPool(); }, [importOpen, loadImportPool]);
|
useEffect(() => { if (importOpen) void loadImportPool(); }, [importOpen, loadImportPool]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!importOpen) return;
|
||||||
|
accountApi.listTags().then(setImportTags).catch(() => {});
|
||||||
|
}, [importOpen]);
|
||||||
|
|
||||||
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));
|
||||||
@@ -1746,11 +1753,20 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
|||||||
width={700}
|
width={700}
|
||||||
>
|
>
|
||||||
<Space style={{ marginBottom: 12, width: '100%', justifyContent: 'space-between' }}>
|
<Space style={{ marginBottom: 12, width: '100%', justifyContent: 'space-between' }}>
|
||||||
<Input
|
<Space>
|
||||||
placeholder="搜索账号/昵称" prefix={<SearchOutlined />}
|
<Input
|
||||||
value={importSearch} onChange={(e) => setImportSearch(e.target.value)}
|
placeholder="搜索账号/昵称" prefix={<SearchOutlined />}
|
||||||
style={{ width: 250 }}
|
value={importSearch} onChange={(e) => setImportSearch(e.target.value)}
|
||||||
/>
|
style={{ width: 220 }}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
placeholder="按标签筛选" allowClear showSearch
|
||||||
|
value={importTag || undefined}
|
||||||
|
onChange={(value?: string) => setImportTag(value || '')}
|
||||||
|
style={{ width: 160 }}
|
||||||
|
options={importTags.map((t) => ({ value: t, label: t }))}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
<Space>
|
<Space>
|
||||||
<Button onClick={() => importAccounts(importPool.map((a) => a.id))}>
|
<Button onClick={() => importAccounts(importPool.map((a) => a.id))}>
|
||||||
导入全部 ({importPool.length})
|
导入全部 ({importPool.length})
|
||||||
@@ -1767,6 +1783,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
|||||||
columns={[
|
columns={[
|
||||||
{ title: '昵称', dataIndex: 'nickname', render: (_, r) => r.nickname || r.username },
|
{ title: '昵称', dataIndex: 'nickname', render: (_, r) => r.nickname || r.username },
|
||||||
{ title: 'UID', dataIndex: 'uid' },
|
{ title: 'UID', dataIndex: 'uid' },
|
||||||
|
{ title: '标签', dataIndex: 'tag', width: 110, render: (v: string) => (v ? <Tag color="blue">{v}</Tag> : '-') },
|
||||||
{
|
{
|
||||||
title: '游戏名',
|
title: '游戏名',
|
||||||
dataIndex: isEsportsHandbook ? 'esports_game_name' : 'game_name',
|
dataIndex: isEsportsHandbook ? 'esports_game_name' : 'game_name',
|
||||||
|
|||||||
Reference in New Issue
Block a user