feat(huya): 精英宝典工作台支持导入外部 CK
- 新增 POST /api/huya/workbench-accounts/import-cookies(huya:task + huya:import): 外部 CK 复用现有解析入库后直接加入当前用户的工作台集合,导入完即可在工作台执行 「生成开通支付码/绑定二维码/查询角色/刷新积分」等操作 - 新账号自动 assigned_to=导入者:客服的账号列表按归属过滤,不设归属会「导入成功但看不到」 - import_huya_cookies_detailed 返回账号对象供工作台复用,原 import_huya_cookies 行为不变 - 工作台账号卡片新增「导入外部 CK」入口(huya:import 权限控制)与导入弹窗 验证:新增 tests/test_huya_workbench_import_ck.py(入库、归属、工作台集合、重复导入幂等、 全脏数据不建记录);按 AGENTS.md 做了反向验证(关掉归属赋值后该用例变红,还原后恢复)。 前端 tsc -b 与全量 pytest 通过。
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
"""工作台「导入外部 CK」:入库 + 自动加入工作台 + 归属导入者。"""
|
||||
|
||||
from tests.support.db import get_test_engine, make_test_session
|
||||
from web.backend.models import HuyaAccount, HuyaWorkbench, HuyaWorkbenchAccount, User
|
||||
from web.backend.routers.huya import import_workbench_cookies
|
||||
from web.backend.schemas import HuyaWorkbenchCookieImport
|
||||
|
||||
COOKIE_1 = (
|
||||
"yyuid=1001; udb_uid=1001; udb_passport=hy_test_1; "
|
||||
"udb_cred=cred1; udb_biztoken=biz1"
|
||||
)
|
||||
COOKIE_2 = (
|
||||
"yyuid=1002; udb_uid=1002; udb_passport=hy_test_2; "
|
||||
"udb_cred=cred2; udb_biztoken=biz2"
|
||||
)
|
||||
|
||||
|
||||
class TestImportExternalCkToWorkbench:
|
||||
def setup_method(self):
|
||||
self.engine = get_test_engine()
|
||||
self.session = make_test_session(self.engine)
|
||||
self.user = User(username="operator", password_hash="hash", role="super_admin")
|
||||
self.session.add(self.user)
|
||||
self.session.commit()
|
||||
|
||||
def teardown_method(self):
|
||||
self.session.close()
|
||||
|
||||
def test_import_creates_accounts_and_joins_workbench(self):
|
||||
result = import_workbench_cookies(
|
||||
HuyaWorkbenchCookieImport(
|
||||
text=f"小号A----{COOKIE_1}\n这不是一行 CK\n{COOKIE_2}",
|
||||
tag="外部CK",
|
||||
handbook_scope="elite",
|
||||
),
|
||||
db=self.session,
|
||||
current=self.user,
|
||||
)
|
||||
|
||||
assert result["success"] is True
|
||||
assert result["count"] == 2
|
||||
assert result["skipped"] == 1
|
||||
assert len(result["account_ids"]) == 2
|
||||
|
||||
accounts = {
|
||||
account.yyuid: account for account in self.session.query(HuyaAccount).all()
|
||||
}
|
||||
assert set(accounts) == {"1001", "1002"}
|
||||
# 无归属的新账号自动归属导入者,否则客服在工作台看不到自己导入的账号
|
||||
assert all(account.assigned_to == self.user.id for account in accounts.values())
|
||||
assert accounts["1001"].tag == "外部CK"
|
||||
|
||||
joined = {
|
||||
row.account_id
|
||||
for row in self.session.query(HuyaWorkbenchAccount)
|
||||
.filter(HuyaWorkbenchAccount.user_id == self.user.id)
|
||||
.all()
|
||||
}
|
||||
assert joined == set(result["account_ids"])
|
||||
assert self.session.query(HuyaWorkbench).count() == 1
|
||||
|
||||
def test_reimport_is_idempotent(self):
|
||||
payload = HuyaWorkbenchCookieImport(text=COOKIE_1, handbook_scope="elite")
|
||||
first = import_workbench_cookies(payload, db=self.session, current=self.user)
|
||||
second = import_workbench_cookies(payload, db=self.session, current=self.user)
|
||||
assert first["account_ids"] == second["account_ids"]
|
||||
assert len(first["account_ids"]) == 1
|
||||
assert self.session.query(HuyaAccount).count() == 1
|
||||
assert self.session.query(HuyaWorkbenchAccount).count() == 1
|
||||
|
||||
def test_all_invalid_lines_reports_zero(self):
|
||||
result = import_workbench_cookies(
|
||||
HuyaWorkbenchCookieImport(text="垃圾行\n更多垃圾", handbook_scope="elite"),
|
||||
db=self.session,
|
||||
current=self.user,
|
||||
)
|
||||
assert result["count"] == 0
|
||||
assert result["skipped"] == 2
|
||||
assert result["account_ids"] == []
|
||||
assert self.session.query(HuyaAccount).count() == 0
|
||||
assert self.session.query(HuyaWorkbench).count() == 0
|
||||
@@ -39,6 +39,7 @@ from .workbench import (
|
||||
create_task_batch,
|
||||
get_config,
|
||||
get_task,
|
||||
import_workbench_cookies,
|
||||
list_goods,
|
||||
list_huya_workbench_accounts,
|
||||
list_recharge_goods,
|
||||
@@ -79,6 +80,7 @@ __all__ = [
|
||||
"get_task",
|
||||
"import_cookies",
|
||||
"import_password_accounts",
|
||||
"import_workbench_cookies",
|
||||
"list_accounts",
|
||||
"list_goods",
|
||||
"list_huya_account_ids",
|
||||
|
||||
@@ -89,6 +89,7 @@ from ...schemas import (
|
||||
HuyaTaskOut,
|
||||
HuyaTestSmsUrlRequest,
|
||||
HuyaWorkbenchAccountsUpdate,
|
||||
HuyaWorkbenchCookieImport,
|
||||
)
|
||||
from ...services.audit_service import record_audit
|
||||
from ...services.huya_exchange_targets import (
|
||||
@@ -111,6 +112,7 @@ from ...services.huya_service import (
|
||||
ensure_huya_config,
|
||||
huya_config_value,
|
||||
import_huya_cookies,
|
||||
import_huya_cookies_detailed,
|
||||
import_huya_password_accounts,
|
||||
rebuild_web_cookie_from_raw,
|
||||
save_huya_login_cookie_to_account,
|
||||
|
||||
@@ -384,3 +384,70 @@ async def ws_huya_logs(websocket: WebSocket, batch_id: str):
|
||||
latest = huya_batch_registry.get(batch_id)
|
||||
if latest and latest.get("finished"):
|
||||
huya_batch_registry.pop(batch_id)
|
||||
|
||||
|
||||
@router.post("/workbench-accounts/import-cookies")
|
||||
def import_workbench_cookies(
|
||||
req: HuyaWorkbenchCookieImport,
|
||||
db: Session = Depends(get_db),
|
||||
current: User = Depends(require_permission("huya:task")),
|
||||
):
|
||||
"""导入外部 CK:入库为虎牙账号并直接加入当前用户的工作台。
|
||||
|
||||
导入后的账号会出现在工作台账号列表里,可照常执行「生成开通支付码 / 绑定二维码 /
|
||||
查询角色 / 刷新积分」等操作。没有归属的新账号自动归属到导入者,否则客服看不到。
|
||||
"""
|
||||
_require_huya_perm(current, "huya:import")
|
||||
accounts, skipped = import_huya_cookies_detailed(
|
||||
db, req.text, req.tag, assign_to=current.id
|
||||
)
|
||||
imported_ids = sorted({account.id for account in accounts})
|
||||
if imported_ids:
|
||||
workbench = (
|
||||
db.query(HuyaWorkbench)
|
||||
.filter(
|
||||
HuyaWorkbench.user_id == current.id,
|
||||
HuyaWorkbench.handbook_scope == req.handbook_scope,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if workbench is None:
|
||||
db.add(HuyaWorkbench(user_id=current.id, handbook_scope=req.handbook_scope))
|
||||
else:
|
||||
workbench.updated_at = datetime.now(UTC)
|
||||
existing_ids = {
|
||||
account_id
|
||||
for (account_id,) in db.query(HuyaWorkbenchAccount.account_id)
|
||||
.filter(
|
||||
HuyaWorkbenchAccount.user_id == current.id,
|
||||
HuyaWorkbenchAccount.handbook_scope == req.handbook_scope,
|
||||
)
|
||||
.all()
|
||||
}
|
||||
db.add_all(
|
||||
[
|
||||
HuyaWorkbenchAccount(
|
||||
user_id=current.id,
|
||||
handbook_scope=req.handbook_scope,
|
||||
account_id=account_id,
|
||||
)
|
||||
for account_id in imported_ids
|
||||
if account_id not in existing_ids
|
||||
]
|
||||
)
|
||||
record_audit(
|
||||
db,
|
||||
current,
|
||||
action="huya:workbench:import_ck",
|
||||
target=f"huya_workbench:{req.handbook_scope}",
|
||||
detail={"count": len(imported_ids), "skipped": skipped, "tag": req.tag},
|
||||
)
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"message": f"导入 {len(imported_ids)} 条,跳过 {skipped} 条",
|
||||
"success": True,
|
||||
"count": len(imported_ids),
|
||||
"skipped": skipped,
|
||||
"account_ids": _workbench_account_ids(db, current, req.handbook_scope),
|
||||
}
|
||||
|
||||
@@ -657,6 +657,14 @@ class HuyaWorkbenchAccountsUpdate(BaseModel):
|
||||
account_ids: list[int] = Field(default_factory=list)
|
||||
|
||||
|
||||
class HuyaWorkbenchCookieImport(BaseModel):
|
||||
"""工作台「导入外部 CK」:把外部 Cookie 入库并直接加入自己的工作台。"""
|
||||
|
||||
text: str = Field(..., min_length=1)
|
||||
tag: str = ""
|
||||
handbook_scope: str = Field("elite", pattern="^elite$|^browser_exchange$")
|
||||
|
||||
|
||||
class HuyaBrowserExchangeRequest(BaseModel):
|
||||
"""浏览器兑换的目标页选择(常量见 services/huya_exchange_targets.py)。"""
|
||||
|
||||
|
||||
@@ -349,9 +349,16 @@ def upsert_huya_cookie(
|
||||
return account
|
||||
|
||||
|
||||
def import_huya_cookies(db: Session, text: str, tag: str = "") -> tuple[int, int]:
|
||||
"""导入虎牙 Cookie,返回 (成功数, 跳过数)。"""
|
||||
created_or_updated = 0
|
||||
def import_huya_cookies_detailed(
|
||||
db: Session, text: str, tag: str = "", assign_to: int | None = None
|
||||
) -> tuple[list[HuyaAccount], int]:
|
||||
"""导入虎牙 Cookie,返回 (账号列表, 跳过数)。
|
||||
|
||||
与 ``import_huya_cookies`` 的差别:把每个账号对象交回调用方 —— 工作台
|
||||
「导入外部 CK」需要拿到刚导入的账号加入自己的工作台集合;``assign_to``
|
||||
用于给尚无归属的新账号打上导入者(否则客服看不到自己导入的账号)。
|
||||
"""
|
||||
accounts: list[HuyaAccount] = []
|
||||
skipped = 0
|
||||
tag = (tag or "").strip()
|
||||
|
||||
@@ -361,12 +368,22 @@ def import_huya_cookies(db: Session, text: str, tag: str = "") -> tuple[int, int
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
_upsert_huya_account(db, parsed, tag=tag)
|
||||
created_or_updated += 1
|
||||
account = _upsert_huya_account(db, parsed, tag=tag)
|
||||
if assign_to and account.assigned_to is None:
|
||||
account.assigned_to = assign_to
|
||||
accounts.append(account)
|
||||
|
||||
if created_or_updated:
|
||||
if accounts:
|
||||
db.commit()
|
||||
return created_or_updated, skipped
|
||||
for account in accounts:
|
||||
db.refresh(account)
|
||||
return accounts, skipped
|
||||
|
||||
|
||||
def import_huya_cookies(db: Session, text: str, tag: str = "") -> tuple[int, int]:
|
||||
"""导入虎牙 Cookie,返回 (成功数, 跳过数)。"""
|
||||
accounts, skipped = import_huya_cookies_detailed(db, text, tag)
|
||||
return len(accounts), skipped
|
||||
|
||||
|
||||
def persist_huya_session_cookie(
|
||||
|
||||
@@ -32,6 +32,8 @@ import type {
|
||||
HuyaTaskItem,
|
||||
HuyaBrowserExchangeKit,
|
||||
HuyaExchangeTargetKey,
|
||||
HuyaExternalCkImportRequest,
|
||||
HuyaExternalCkImportResult,
|
||||
HuyaWorkbenchScope,
|
||||
MessageCountResponse,
|
||||
MessageDeletedResponse,
|
||||
@@ -140,6 +142,9 @@ export const huyaApi = {
|
||||
updateConfig: (data: Partial<HuyaConfig>) => api.put<HuyaConfig, HuyaConfig>('/huya/config', data),
|
||||
listGoods: () => api.get<HuyaGoodsItem[], HuyaGoodsItem[]>('/huya/goods'),
|
||||
listRechargeGoods: () => api.get<HuyaRechargeGoodsItem[], HuyaRechargeGoodsItem[]>('/huya/recharge-goods'),
|
||||
/** 工作台导入外部 CK: 入库并把账号加入自己的工作台 (text 每行一条 CK) */
|
||||
importWorkbenchCookies: (data: HuyaExternalCkImportRequest) =>
|
||||
api.post<HuyaExternalCkImportResult, HuyaExternalCkImportResult>('/huya/workbench-accounts/import-cookies', data),
|
||||
listWorkbenchAccounts: (scope: HuyaWorkbenchScope = 'elite') =>
|
||||
api.get<{ account_ids: number[]; configured: boolean }, { account_ids: number[]; configured: boolean }>(
|
||||
'/huya/workbench-accounts', { params: { handbook_scope: scope } },
|
||||
|
||||
@@ -1027,6 +1027,19 @@ export interface HuyaRechargeGoodsItem {
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
/** 工作台「导入外部 CK」:请求体 */
|
||||
export interface HuyaExternalCkImportRequest {
|
||||
text: string; // 多行文本,每行一条 CK(支持「账号----密码----CK」)
|
||||
tag?: string;
|
||||
handbook_scope?: HuyaWorkbenchScope;
|
||||
}
|
||||
|
||||
/** 工作台「导入外部 CK」:结果(account_ids 是导入后的完整工作台账号集合) */
|
||||
export interface HuyaExternalCkImportResult extends MessageCountResponse {
|
||||
skipped: number;
|
||||
account_ids: number[];
|
||||
}
|
||||
|
||||
// ==================== Proxy ====================
|
||||
|
||||
export interface ProxyConfig {
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Alert, Input, Modal, Select, Space, Typography } from 'antd';
|
||||
import { huyaApi, type HuyaExternalCkImportResult, type HuyaWorkbenchScope } from '../api/modules';
|
||||
import { getErrorMessage } from '../utils/error';
|
||||
import { message } from '../utils/antdMessage';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
export interface HuyaExternalCkImportModalProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
/** 已有标签,供下拉选择 */
|
||||
tags: string[];
|
||||
defaultTag?: string;
|
||||
scope?: HuyaWorkbenchScope;
|
||||
/** 导入成功后回调(带完整的工作台账号集合) */
|
||||
onImported: (result: HuyaExternalCkImportResult) => void;
|
||||
}
|
||||
|
||||
/** 工作台「导入外部 CK」:粘贴外部 Cookie,入库并直接加入工作台,随后可在工作台操作 */
|
||||
export default function HuyaExternalCkImportModal({
|
||||
open, onClose, tags, defaultTag, scope = 'elite', onImported,
|
||||
}: HuyaExternalCkImportModalProps) {
|
||||
const [text, setText] = useState('');
|
||||
const [tag, setTag] = useState(defaultTag || '');
|
||||
const [importing, setImporting] = useState(false);
|
||||
const [result, setResult] = useState<HuyaExternalCkImportResult | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
setResult(null);
|
||||
setTag(defaultTag || '');
|
||||
}, [defaultTag, open]);
|
||||
|
||||
const submit = async () => {
|
||||
const value = text.trim();
|
||||
if (!value) { message.warning('请先粘贴 CK,每行一条'); return; }
|
||||
setImporting(true);
|
||||
try {
|
||||
const saved = await huyaApi.importWorkbenchCookies({ text: value, tag, handbook_scope: scope });
|
||||
setResult(saved);
|
||||
message.success(saved.message || `已导入 ${saved.count} 条`);
|
||||
onImported(saved);
|
||||
} catch (error) {
|
||||
message.error(getErrorMessage(error));
|
||||
} finally {
|
||||
setImporting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
title="导入外部 CK 到工作台"
|
||||
width={640}
|
||||
onCancel={onClose}
|
||||
onOk={() => void submit()}
|
||||
confirmLoading={importing}
|
||||
okText="导入"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Space direction="vertical" size={8} style={{ width: '100%' }}>
|
||||
<Input.TextArea
|
||||
rows={8}
|
||||
value={text}
|
||||
onChange={(event) => setText(event.target.value)}
|
||||
placeholder={'每行一条 CK,支持 N 行批量导入\n支持格式:纯 CK 或 账号----密码----CK'}
|
||||
/>
|
||||
<Space size={8}>
|
||||
<Text type="secondary">标签</Text>
|
||||
<Select
|
||||
allowClear
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
placeholder="可选"
|
||||
value={tag || undefined}
|
||||
onChange={(value) => setTag(value || '')}
|
||||
options={tags.map((item) => ({ value: item, label: item }))}
|
||||
style={{ width: 220 }}
|
||||
/>
|
||||
</Space>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
导入后账号会直接加入当前工作台,可照常执行「生成开通支付码 / 绑定二维码 / 查询角色 / 刷新积分」等操作。
|
||||
</Text>
|
||||
{result && (
|
||||
<Alert
|
||||
type={result.count ? 'success' : 'warning'}
|
||||
showIcon
|
||||
message={result.message}
|
||||
description={`成功 ${result.count} 条,跳过 ${result.skipped} 条;工作台现有 ${result.account_ids.length} 个账号`}
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
ReloadOutlined, SearchOutlined, SettingOutlined, ShoppingOutlined, StopOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { huyaApi, type HuyaAccountItem, type HuyaConfig, type HuyaGoodsItem, type HuyaRechargeGoodsItem, type HuyaTaskItem } from '../api/modules';
|
||||
import HuyaExternalCkImportModal from '../components/HuyaExternalCkImportModal';
|
||||
import RealtimeLogPanel from '../components/RealtimeLogPanel';
|
||||
import { usePermissions } from '../hooks/usePermissions';
|
||||
import { useWebSocketLogs } from '../hooks/useWebSocketLogs';
|
||||
@@ -69,6 +70,7 @@ function taskQrUrl(task: HuyaTaskItem | undefined): string {
|
||||
export default function HuyaElitePage() {
|
||||
const { can } = usePermissions();
|
||||
const canConfig = can('huya:config');
|
||||
const canImport = can('huya:import');
|
||||
const [pool, setPool] = useState<HuyaAccountItem[]>([]);
|
||||
const [accounts, setAccounts] = useState<HuyaAccountItem[]>([]);
|
||||
const [selectedIds, setSelectedIds] = useState<number[]>([]);
|
||||
@@ -90,6 +92,7 @@ export default function HuyaElitePage() {
|
||||
const [importTags, setImportTags] = useState<string[]>([]);
|
||||
const [importSelected, setImportSelected] = useState<number[]>([]);
|
||||
const [qrTask, setQrTask] = useState<HuyaTaskItem | null>(null);
|
||||
const [ckImportOpen, setCkImportOpen] = useState(false);
|
||||
const [config, setConfig] = useState<HuyaConfig | null>(null);
|
||||
const [configDraft, setConfigDraft] = useState<HuyaConfig | null>(null);
|
||||
const [configOpen, setConfigOpen] = useState(false);
|
||||
@@ -127,7 +130,7 @@ export default function HuyaElitePage() {
|
||||
}, []);
|
||||
|
||||
useEffect(() => { void load(); }, [load]);
|
||||
useEffect(() => { if (importOpen) void huyaApi.listTags().then(setImportTags).catch(() => {}); }, [importOpen]);
|
||||
useEffect(() => { if (importOpen || ckImportOpen) void huyaApi.listTags().then(setImportTags).catch(() => {}); }, [ckImportOpen, importOpen]);
|
||||
useEffect(() => {
|
||||
const active = tasks.some((task) => ['planned', 'pending', 'running'].includes(task.status));
|
||||
const timer = window.setInterval(() => { void loadTasks(); }, (active ? 3 : refreshInterval) * 1000);
|
||||
@@ -262,7 +265,7 @@ export default function HuyaElitePage() {
|
||||
</Space>
|
||||
);
|
||||
|
||||
const accountsBody = <><Space style={{ marginBottom: 8, width: '100%', justifyContent: 'space-between' }} wrap><Input size="small" allowClear prefix={<SearchOutlined />} placeholder="搜索账号/昵称/游戏名" value={search} onChange={(event) => setSearch(event.target.value)} style={{ width: 240 }} /><Space><Button size="small" icon={<ImportOutlined />} onClick={() => setImportOpen(true)}>导入账号</Button>{selectedIds.length > 0 && <Button size="small" danger onClick={() => void saveAccounts(accounts.map((item) => item.id).filter((id) => !selectedIds.includes(id)))}>移出选中</Button>}</Space></Space><div className="huya-account-table"><Table rowKey="id" size="small" loading={loading} tableLayout="fixed" rowSelection={{ selectedRowKeys: selectedIds, onChange: (keys) => setSelectedIds(keys.map(Number)) }} columns={accountColumns} dataSource={filteredAccounts} locale={{ emptyText: '工作台暂无账号,点击右上角「导入账号」添加' }} pagination={{ pageSize: 20, showSizeChanger: true, showTotal: (total) => `共 ${total} 条` }} scroll={{ x: 1040, y: 'calc(100vh - 300px)' }} onRow={(row) => ({ onClick: () => { const task = latestTask(tasks, row.id); if (task && ['get_bind_qr', 'create_recharge_order'].includes(task.task_type) && task.result) void openQrTask(task); }, onContextMenu: (event) => handleRowContextMenu(row, event), style: { cursor: 'context-menu' }, title: '右键打开账号动作' })} /></div></>;
|
||||
const accountsBody = <><Space style={{ marginBottom: 8, width: '100%', justifyContent: 'space-between' }} wrap><Input size="small" allowClear prefix={<SearchOutlined />} placeholder="搜索账号/昵称/游戏名" value={search} onChange={(event) => setSearch(event.target.value)} style={{ width: 240 }} /><Space><Button size="small" icon={<ImportOutlined />} onClick={() => setImportOpen(true)}>导入账号</Button>{canImport && <Button size="small" icon={<ImportOutlined />} onClick={() => setCkImportOpen(true)}>导入外部 CK</Button>}{selectedIds.length > 0 && <Button size="small" danger onClick={() => void saveAccounts(accounts.map((item) => item.id).filter((id) => !selectedIds.includes(id)))}>移出选中</Button>}</Space></Space><div className="huya-account-table"><Table rowKey="id" size="small" loading={loading} tableLayout="fixed" rowSelection={{ selectedRowKeys: selectedIds, onChange: (keys) => setSelectedIds(keys.map(Number)) }} columns={accountColumns} dataSource={filteredAccounts} locale={{ emptyText: '工作台暂无账号,点击右上角「导入账号」添加' }} pagination={{ pageSize: 20, showSizeChanger: true, showTotal: (total) => `共 ${total} 条` }} scroll={{ x: 1040, y: 'calc(100vh - 300px)' }} onRow={(row) => ({ onClick: () => { const task = latestTask(tasks, row.id); if (task && ['get_bind_qr', 'create_recharge_order'].includes(task.task_type) && task.result) void openQrTask(task); }, onContextMenu: (event) => handleRowContextMenu(row, event), style: { cursor: 'context-menu' }, title: '右键打开账号动作' })} /></div></>;
|
||||
|
||||
const configField = (key: Exclude<keyof HuyaConfig, 'updated_at'>, label: string) => configDraft && <Space key={key} style={{ width: '100%', justifyContent: 'space-between' }}><Text>{label}</Text><Input value={configDraft[key] || ''} onChange={(event) => setConfigDraft({ ...configDraft, [key]: event.target.value })} style={{ width: 300 }} /></Space>;
|
||||
const contextMenuStyle = contextMenu ? {
|
||||
@@ -282,5 +285,6 @@ export default function HuyaElitePage() {
|
||||
<Modal open={qrTask !== null} title={qrTask?.task_type === 'create_recharge_order' ? '开通支付二维码' : '绑定二维码'} footer={null} onCancel={() => setQrTask(null)} centered>{qrTask && <div style={{ textAlign: 'center' }}>{resultText(qrTask, 'mini_qrcode_image') ? <img src={`data:image/png;base64,${resultText(qrTask, 'mini_qrcode_image')}`} alt="绑定二维码" style={{ width: 240, height: 240 }} /> : <QRCode value={resultText(qrTask, 'pay_url') || resultText(qrTask, 'bind_redirect_url') || 'https://zt.huya.com/b02faae1/pc/index.html'} size={240} />}<p><Tag color={TASK_COLORS[qrTask.status] || 'default'}>{qrTask.message || qrTask.status}</Tag></p>{qrTask.task_type === 'create_recharge_order' && <Text type="secondary">扫码后订单状态会自动轮询更新</Text>}</div>}</Modal>
|
||||
<Modal open={importOpen} title="导入精英宝典账号" onCancel={() => setImportOpen(false)} onOk={() => { void saveAccounts([...new Set([...accounts.map((item) => item.id), ...importSelected])]).then(() => { setImportSelected([]); setImportOpen(false); }).catch((error) => message.error(getErrorMessage(error))); }} okText="导入" cancelText="取消"><Input.Search allowClear placeholder="搜索账号" value={importSearch} onChange={(event) => setImportSearch(event.target.value)} style={{ marginBottom: 8 }} /><Select allowClear placeholder="标签" value={importTag || undefined} onChange={(value) => setImportTag(value || '')} options={importTags.map((tag) => ({ value: tag, label: tag }))} style={{ width: '100%', marginBottom: 8 }} /><Table rowKey="id" size="small" dataSource={pool.filter((item) => !accounts.some((current) => current.id === item.id) && (!importSearch || [item.uid, item.nickname, item.username].some((value) => String(value || '').includes(importSearch))) && (!importTag || item.tag === importTag))} columns={[{ title: '账号', render: (_value, row) => row.nickname || row.username || row.uid }, { title: '标签', dataIndex: 'tag' }]} pagination={{ pageSize: 8 }} rowSelection={{ selectedRowKeys: importSelected, onChange: (keys) => setImportSelected(keys.map(Number)) }} /></Modal>
|
||||
<Modal open={configOpen} title="精英宝典设置" onCancel={() => setConfigOpen(false)} onOk={() => void saveConfig()} confirmLoading={configSaving} okText="保存" cancelText="取消" width={500}>{configLoading ? <div style={{ padding: 24, textAlign: 'center' }}>加载中...</div> : configDraft && <Space direction="vertical" size={10} style={{ width: '100%' }}>{configField('sid', '活动 SID')}{configField('bind_act_id', '绑定活动 ID')}{configField('outer_act_id', '外部活动 ID')}{configField('room_pid', '直播间 PID')}{configField('pay_channel', '支付渠道')}<Space style={{ width: '100%', justifyContent: 'space-between' }}><Text>空闲轮询间隔(秒)</Text><InputNumber min={5} max={60} value={refreshInterval} onChange={(value) => { const next = Math.min(60, Math.max(5, value || 15)); setRefreshInterval(next); localStorage.setItem('huya_elite_refresh_interval', String(next)); }} style={{ width: 300 }} /></Space></Space>}</Modal>
|
||||
<HuyaExternalCkImportModal open={ckImportOpen} onClose={() => setCkImportOpen(false)} tags={importTags} scope={SCOPE} onImported={(result) => { localStorage.setItem('huya_elite_workbench_account_ids', JSON.stringify(result.account_ids)); void load(); }} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user