虎牙支持先导入账号密码再登录

This commit is contained in:
yml2213
2026-07-05 20:47:16 +08:00
parent 552eb9fa82
commit 2203420146
9 changed files with 493 additions and 73 deletions
+2
View File
@@ -148,6 +148,8 @@ _SENSITIVE_COLUMNS: tuple[tuple[str, str], ...] = (
("accounts", "email"),
("accounts", "email_password"),
("login_tasks", "cookie"),
("huya_accounts", "account_password"),
("huya_accounts", "cookie"),
("proxy_config", "api_url"),
("proxy_config", "http"),
("proxy_config", "https"),
@@ -0,0 +1,42 @@
"""虎牙账号增加密码导入字段
Revision ID: 20260705_0006
Revises: 20260704_0005
Create Date: 2026-07-05
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = "20260705_0006"
down_revision: Union[str, None] = "20260704_0005"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def _has_table(bind, table_name: str) -> bool:
return sa.inspect(bind).has_table(table_name)
def _columns(bind, table_name: str) -> set[str]:
if not _has_table(bind, table_name):
return set()
return {column["name"] for column in sa.inspect(bind).get_columns(table_name)}
def upgrade() -> None:
bind = op.get_bind()
if "account_password" not in _columns(bind, "huya_accounts"):
op.add_column(
"huya_accounts",
sa.Column("account_password", sa.Text(), nullable=True),
)
def downgrade() -> None:
bind = op.get_bind()
if "account_password" in _columns(bind, "huya_accounts"):
op.drop_column("huya_accounts", "account_password")
+2 -1
View File
@@ -79,13 +79,14 @@ class LoginTask(Base):
class HuyaAccount(Base):
"""虎牙 Cookie 账号"""
"""虎牙账号"""
__tablename__ = "huya_accounts"
id = Column(Integer, primary_key=True, autoincrement=True)
uid = Column(String(32), default="", index=True)
yyuid = Column(String(32), default="", index=True)
username = Column(String(128), default="")
account_password = Column(EncryptedText(), default="")
nickname = Column(String(128), default="")
cookie = Column(EncryptedText(), nullable=False)
tag = Column(String(64), default="")
+152
View File
@@ -27,7 +27,9 @@ from ..schemas import (
HuyaConfigUpdate,
HuyaCookieImport,
HuyaGoodsOut,
HuyaPasswordAccountImport,
HuyaPasswordLoginRequest,
HuyaPasswordLoginSelectedRequest,
HuyaRechargeGoodsOut,
HuyaTaskBatchRequest,
HuyaTaskOut,
@@ -40,6 +42,8 @@ from ..services.huya_service import (
ensure_huya_config,
huya_config_value,
import_huya_cookies,
import_huya_password_accounts,
save_huya_login_cookie_to_account,
upsert_huya_cookie,
)
from ..services.huya_runner import HuyaBatchRunner, huya_batch_registry
@@ -93,6 +97,7 @@ def _account_out(account: HuyaAccount, include_cookie: bool = True) -> HuyaAccou
uid=account.uid or "",
yyuid=account.yyuid or "",
username=account.username or "",
has_password=bool(account.account_password),
nickname=account.nickname or "",
cookie=cookie if include_cookie else "",
cookie_preview=_fmt_cookie_preview(cookie) if include_cookie else "***",
@@ -200,6 +205,23 @@ def import_cookies(
}
@router.post("/accounts/import-passwords")
def import_password_accounts(
req: HuyaPasswordAccountImport,
db: Session = Depends(get_db),
current: User = Depends(get_current_user),
):
"""导入虎牙账号密码,只入库不立即登录。"""
_require_huya_perm(current, "huya:import")
count, skipped = import_huya_password_accounts(db, req.text, req.tag)
return {
"message": f"导入/更新 {count} 个虎牙账号,跳过 {skipped}",
"success": True,
"count": count,
"skipped": skipped,
}
@router.post("/accounts/password-login")
def password_login_account(
req: HuyaPasswordLoginRequest,
@@ -236,6 +258,136 @@ def password_login_account(
}
@router.post("/accounts/password-login/selected")
def password_login_selected_accounts(
req: HuyaPasswordLoginSelectedRequest,
db: Session = Depends(get_db),
current: User = Depends(get_current_user),
):
"""对已导入的虎牙账号执行密码登录。"""
_require_huya_perm(current, "huya:import")
if not req.account_ids:
raise HTTPException(status_code=400, detail="请选择虎牙账号")
accounts = (
_visible_huya_accounts_query(db, current)
.filter(HuyaAccount.id.in_(req.account_ids))
.all()
)
account_map = {account.id: account for account in accounts}
results = []
success_count = 0
failed_count = 0
include_cookie = _can_view_huya_cookie(current)
for account_id in req.account_ids:
account = account_map.get(account_id)
if account is None:
failed_count += 1
results.append({
"line": account_id,
"username": "",
"success": False,
"message": "账号不存在或无权登录",
})
continue
username = (account.username or "").strip()
password = (account.account_password or "").strip()
if not username or not password:
failed_count += 1
results.append({
"line": account.id,
"username": username,
"success": False,
"message": "该账号未导入密码",
"account": _account_out(account, include_cookie=include_cookie),
})
continue
try:
result = login_huya_password(
username=username,
password=password,
cookie=account.cookie or None,
)
if not result.success or not result.cookie:
account.status = "login_failed"
account.updated_at = datetime.now(timezone.utc)
db.commit()
failed_count += 1
results.append({
"line": account.id,
"username": username,
"success": False,
"message": result.message or "虎牙密码登录失败",
"account": _account_out(account, include_cookie=include_cookie),
"sdid": result.sdid,
})
continue
saved = save_huya_login_cookie_to_account(
db,
account,
result.cookie,
tag=account.tag or "",
username_hint=username,
)
success_count += 1
results.append({
"line": account.id,
"username": username,
"success": True,
"message": "登录成功,Cookie 已保存",
"account": _account_out(saved, include_cookie=include_cookie),
"sdid": result.sdid,
})
except HuyaCredentialError as exc:
account.status = "login_failed"
account.updated_at = datetime.now(timezone.utc)
db.commit()
failed_count += 1
results.append({
"line": account.id,
"username": username,
"success": False,
"message": str(exc),
"account": _account_out(account, include_cookie=include_cookie),
})
except (HuyaLoginError, ValueError) as exc:
account.status = "login_failed"
account.updated_at = datetime.now(timezone.utc)
db.commit()
failed_count += 1
results.append({
"line": account.id,
"username": username,
"success": False,
"message": str(exc),
"account": _account_out(account, include_cookie=include_cookie),
})
except Exception as exc:
account.status = "login_failed"
account.updated_at = datetime.now(timezone.utc)
db.commit()
failed_count += 1
results.append({
"line": account.id,
"username": username,
"success": False,
"message": f"虎牙密码登录失败: {exc}",
"account": _account_out(account, include_cookie=include_cookie),
})
return {
"message": f"登录完成:成功 {success_count} 条,失败 {failed_count}",
"success": True,
"count": success_count,
"failed": failed_count,
"results": results,
}
@router.delete("/accounts/batch")
def delete_accounts_batch(
account_ids: str = Query(..., description="逗号分隔的虎牙账号ID"),
+13
View File
@@ -183,11 +183,23 @@ class HuyaPasswordLoginRequest(BaseModel):
cookie: str = ""
class HuyaPasswordAccountImport(BaseModel):
"""导入虎牙账号密码,稍后再选择登录。"""
text: str = Field(..., min_length=1)
tag: str = ""
class HuyaPasswordLoginSelectedRequest(BaseModel):
"""选择已导入的虎牙账号执行密码登录。"""
account_ids: list[int]
class HuyaAccountOut(BaseModel):
id: int
uid: str = ""
yyuid: str = ""
username: str = ""
has_password: bool = False
nickname: str = ""
cookie: str = ""
cookie_preview: str = ""
@@ -212,6 +224,7 @@ class HuyaAccountOut(BaseModel):
"uid": self.uid,
"yyuid": self.yyuid,
"username": self.username,
"has_password": self.has_password,
"nickname": self.nickname,
"cookie": self.cookie,
"cookie_preview": self.cookie_preview,
+98
View File
@@ -1,5 +1,6 @@
"""虎牙基础业务服务。"""
import csv
import uuid
from datetime import datetime, timezone
@@ -93,6 +94,76 @@ def parse_huya_cookie_line(line: str) -> dict | None:
}
def split_huya_password_line(line: str) -> tuple[str, str, str] | None:
"""拆分虎牙账号密码行,返回账号、密码、预置 Cookie。"""
raw = (line or "").strip()
if not raw or raw.startswith("#"):
return None
if "----" in raw:
parts = [part.strip() for part in raw.split("----")]
elif "|" in raw:
parts = [part.strip() for part in raw.split("|")]
elif "\t" in raw:
parts = [part.strip() for part in raw.split("\t")]
elif "," in raw:
parts = [part.strip() for part in next(csv.reader([raw]))]
else:
parts = raw.split()
if len(parts) < 2:
return None
username = parts[0].strip()
password = parts[1].strip()
cookie = "----".join(part.strip() for part in parts[2:] if part.strip())
if not username or not password:
return None
return username, password, cookie
def import_huya_password_accounts(db: Session, text: str, tag: str = "") -> tuple[int, int]:
"""导入虎牙账号密码,返回 (导入/更新数, 跳过数)。"""
created_or_updated = 0
skipped = 0
tag = (tag or "").strip()
for line in (text or "").splitlines():
parsed = split_huya_password_line(line)
if not parsed:
if line.strip():
skipped += 1
continue
username, password, cookie = parsed
account = db.query(HuyaAccount).filter(HuyaAccount.username == username).first()
if account is None:
account = HuyaAccount(
uid="",
yyuid="",
username=username,
account_password=password,
cookie=normalize_huya_cookie(cookie) if cookie else "",
tag=tag,
status="password_imported",
)
db.add(account)
else:
account.account_password = password
if cookie:
account.cookie = normalize_huya_cookie(cookie)
if tag:
account.tag = tag
if not account.cookie:
account.status = "password_imported"
account.updated_at = datetime.now(timezone.utc)
created_or_updated += 1
if created_or_updated:
db.commit()
return created_or_updated, skipped
def _upsert_huya_account(db: Session, parsed: dict, tag: str = "", status: str | None = None) -> HuyaAccount:
"""按 uid/yyuid 新增或更新虎牙账号。"""
account = None
@@ -125,6 +196,33 @@ def _upsert_huya_account(db: Session, parsed: dict, tag: str = "", status: str |
return account
def save_huya_login_cookie_to_account(
db: Session,
account: HuyaAccount,
cookie: str,
tag: str = "",
username_hint: str = "",
) -> HuyaAccount:
"""把登录成功后的 Cookie 回填到指定虎牙账号。"""
line = f"{username_hint}----{cookie}" if username_hint else cookie
parsed = parse_huya_cookie_line(line)
if not parsed:
raise ValueError("登录成功但 Cookie 中没有识别到虎牙 uid")
account.uid = parsed["uid"] or account.uid
account.yyuid = parsed["yyuid"] or account.yyuid
account.username = parsed["username"] or account.username
account.cookie = parsed["cookie"]
account.game_phone = parsed["game_phone"] or account.game_phone
if tag:
account.tag = tag
account.status = "login_success"
account.updated_at = datetime.now(timezone.utc)
db.commit()
db.refresh(account)
return account
def upsert_huya_cookie(db: Session, cookie: str, tag: str = "", username_hint: str = "") -> HuyaAccount:
"""保存单条登录得到的虎牙 Cookie。"""
line = f"{username_hint}----{cookie}" if username_hint else cookie
+7
View File
@@ -5,8 +5,11 @@ import type {
HuyaCookieItem,
HuyaCookieImportResult,
HuyaGoodsItem,
HuyaPasswordAccountImportResult,
HuyaPasswordLoginBatchResult,
HuyaPasswordLoginRequest,
HuyaPasswordLoginResult,
HuyaPasswordLoginSelectedRequest,
HuyaRechargeGoodsItem,
HuyaTaskBatchRequest,
HuyaTaskBatchResult,
@@ -22,8 +25,12 @@ export const huyaApi = {
api.get<HuyaAccountItem[], HuyaAccountItem[]>('/huya/accounts', { params }),
importCookies: (text: string, tag: string = '') =>
api.post<HuyaCookieImportResult, HuyaCookieImportResult>('/huya/accounts/import-cookies', { text, tag }),
importPasswordAccounts: (text: string, tag: string = '') =>
api.post<HuyaPasswordAccountImportResult, HuyaPasswordAccountImportResult>('/huya/accounts/import-passwords', { text, tag }),
passwordLogin: (data: HuyaPasswordLoginRequest) =>
api.post<HuyaPasswordLoginResult, HuyaPasswordLoginResult>('/huya/accounts/password-login', data),
passwordLoginSelected: (data: HuyaPasswordLoginSelectedRequest) =>
api.post<HuyaPasswordLoginBatchResult, HuyaPasswordLoginBatchResult>('/huya/accounts/password-login/selected', data),
assign: (id: number, assigned_to: number | null) =>
api.put<MessageResponse, MessageResponse>(`/huya/accounts/${id}/assign`, { assigned_to }),
batchAssign: (account_ids: number[], assigned_to: number | null) =>
+23
View File
@@ -116,6 +116,7 @@ export interface HuyaAccountItem {
uid: string;
yyuid: string;
username: string;
has_password: boolean;
nickname: string;
cookie: string;
cookie_preview: string;
@@ -148,6 +149,28 @@ export interface HuyaPasswordLoginResult extends MessageResponse {
sdid: string;
}
export interface HuyaPasswordLoginBatchItem {
line: number;
username: string;
success: boolean;
message: string;
account?: HuyaAccountItem;
sdid?: string;
}
export interface HuyaPasswordLoginBatchResult extends MessageCountResponse {
failed: number;
results: HuyaPasswordLoginBatchItem[];
}
export interface HuyaPasswordAccountImportResult extends MessageCountResponse {
skipped: number;
}
export interface HuyaPasswordLoginSelectedRequest {
account_ids: number[];
}
export interface HuyaCookieItem {
id: number;
account_id: number;
+154 -72
View File
@@ -1,10 +1,15 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import {
Button, Card, Col, Form, Input, message, Modal, Popconfirm, Row, Select, Space, Statistic, Table, Tag, Typography,
Button, Card, Col, Input, message, Modal, Popconfirm, Row, Select, Space, Statistic, Table, Tag, Typography,
} from 'antd';
import type { TableProps } from 'antd';
import { DeleteOutlined, FilterOutlined, ImportOutlined, LoginOutlined, ReloadOutlined, SearchOutlined, TagOutlined } from '@ant-design/icons';
import { huyaApi, type HuyaAccountItem, type SupportUserItem } from '../api/modules';
import {
huyaApi,
type HuyaAccountItem,
type HuyaPasswordLoginBatchItem,
type SupportUserItem,
} from '../api/modules';
import { usePermissions } from '../hooks/usePermissions';
import { formatTime } from '../utils/time';
import { getErrorMessage } from '../utils/error';
@@ -15,7 +20,9 @@ const { TextArea } = Input;
const STATUS_LABELS: Record<string, string> = {
imported: '已导入',
updated: '已更新',
password_imported: '待登录',
login_success: '登录成功',
login_failed: '登录失败',
active: '正常',
invalid: '失效',
};
@@ -23,18 +30,13 @@ const STATUS_LABELS: Record<string, string> = {
const STATUS_COLORS: Record<string, string> = {
imported: 'blue',
updated: 'cyan',
password_imported: 'warning',
login_success: 'success',
login_failed: 'error',
active: 'success',
invalid: 'error',
};
interface PasswordLoginFormValues {
username: string;
password: string;
tag?: string;
cookie?: string;
}
export default function HuyaAccountsPage() {
const [accounts, setAccounts] = useState<HuyaAccountItem[]>([]);
const [users, setUsers] = useState<SupportUserItem[]>([]);
@@ -44,8 +46,13 @@ export default function HuyaAccountsPage() {
const [importText, setImportText] = useState('');
const [importTag, setImportTag] = useState('');
const [importing, setImporting] = useState(false);
const [passwordLoginOpen, setPasswordLoginOpen] = useState(false);
const [passwordImportOpen, setPasswordImportOpen] = useState(false);
const [passwordImportText, setPasswordImportText] = useState('');
const [passwordImportTag, setPasswordImportTag] = useState<string[]>([]);
const [passwordImporting, setPasswordImporting] = useState(false);
const [passwordLoginResultOpen, setPasswordLoginResultOpen] = useState(false);
const [passwordLogging, setPasswordLogging] = useState(false);
const [passwordLoginResults, setPasswordLoginResults] = useState<HuyaPasswordLoginBatchItem[]>([]);
const [searchText, setSearchText] = useState('');
const [tagFilter, setTagFilter] = useState('');
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
@@ -56,7 +63,6 @@ export default function HuyaAccountsPage() {
return v ? Number(v) || 20 : 20;
});
const [currentPage, setCurrentPage] = useState(1);
const [passwordLoginForm] = Form.useForm<PasswordLoginFormValues>();
const { can } = usePermissions();
const canManage = can('huya:account');
@@ -146,29 +152,52 @@ export default function HuyaAccountsPage() {
}
};
const openPasswordLogin = () => {
passwordLoginForm.resetFields();
setPasswordLoginOpen(true);
const openPasswordImport = () => {
setPasswordImportText('');
setPasswordImportTag([]);
setPasswordLoginResults([]);
setPasswordImportOpen(true);
};
const closePasswordLogin = () => {
if (passwordLogging) return;
setPasswordLoginOpen(false);
passwordLoginForm.resetFields();
const handleImportPasswordAccounts = async () => {
if (!passwordImportText.trim()) {
message.warning('请先粘贴虎牙账号密码');
return;
}
setPasswordImporting(true);
try {
const tag = passwordImportTag.length > 0 ? passwordImportTag[passwordImportTag.length - 1].trim() : '';
const result = await huyaApi.importPasswordAccounts(passwordImportText, tag);
message.success(result.message);
setPasswordImportOpen(false);
setPasswordImportText('');
setPasswordImportTag([]);
loadAccounts();
loadTags();
} catch (e: unknown) {
message.error(getErrorMessage(e));
} finally {
setPasswordImporting(false);
}
};
const handlePasswordLogin = async (values: PasswordLoginFormValues) => {
const handleLoginSelected = async () => {
if (selectedRowKeys.length === 0) {
message.warning('请先选择虎牙账号');
return;
}
setPasswordLogging(true);
try {
const result = await huyaApi.passwordLogin({
username: values.username.trim(),
password: values.password,
tag: values.tag?.trim() || '',
cookie: values.cookie?.trim() || '',
const result = await huyaApi.passwordLoginSelected({
account_ids: selectedRowKeys.map((key) => Number(key)),
});
message.success(result.message || '登录成功,Cookie 已保存');
setPasswordLoginOpen(false);
passwordLoginForm.resetFields();
setPasswordLoginResults(result.results || []);
setPasswordLoginResultOpen(true);
if (result.failed > 0) {
message.warning(result.message);
} else {
message.success(result.message || '登录成功,Cookie 已保存');
}
loadAccounts();
loadTags();
} catch (e: unknown) {
@@ -192,7 +221,7 @@ export default function HuyaAccountsPage() {
const handleDeleteSelected = async () => {
if (selectedRowKeys.length === 0) {
message.warning('请先选择虎牙 CK');
message.warning('请先选择虎牙账号');
return;
}
try {
@@ -248,6 +277,28 @@ export default function HuyaAccountsPage() {
const boundCount = accounts.filter((item) => item.game_name || item.game_channel || item.game_phone).length;
const pointCount = accounts.filter((item) => item.points !== null && item.points !== undefined).length;
const assignedCount = accounts.filter((item) => item.assigned_to).length;
const passwordReadyCount = accounts.filter((item) => item.has_password).length;
const passwordLoginResultColumns: TableProps<HuyaPasswordLoginBatchItem>['columns'] = [
{ title: '账号ID', dataIndex: 'line', width: 80, align: 'center' },
{ title: '账号', dataIndex: 'username', width: 160, ellipsis: true },
{
title: '状态',
dataIndex: 'success',
width: 90,
align: 'center',
render: (success: boolean) => (
<Tag color={success ? 'success' : 'error'}>{success ? '成功' : '失败'}</Tag>
),
},
{
title: '保存账号',
width: 160,
ellipsis: true,
render: (_: unknown, record) => record.account?.nickname || record.account?.username || record.account?.uid || '-',
},
{ title: '提示', dataIndex: 'message', ellipsis: true },
];
const columns: TableProps<HuyaAccountItem>['columns'] = [
{ title: 'ID', dataIndex: 'id', width: 70, align: 'center' },
@@ -260,6 +311,7 @@ export default function HuyaAccountsPage() {
<Text type="secondary" style={{ fontSize: 12 }}>
UID {record.uid || record.yyuid || '-'}
</Text>
{record.has_password ? <Tag color="gold"></Tag> : null}
</Space>
),
},
@@ -422,16 +474,26 @@ export default function HuyaAccountsPage() {
</Button>
)}
{canImport && (
<Button
disabled={selectedRowKeys.length === 0}
icon={<LoginOutlined />}
loading={passwordLogging}
onClick={handleLoginSelected}
>
</Button>
)}
{canDelete && selectedRowKeys.length > 0 && (
<Popconfirm title={`确认删除选中的 ${selectedRowKeys.length} 条虎牙 CK`} onConfirm={handleDeleteSelected}>
<Popconfirm title={`确认删除选中的 ${selectedRowKeys.length} 条虎牙账号`} onConfirm={handleDeleteSelected}>
<Button danger icon={<DeleteOutlined />}>
({selectedRowKeys.length})
</Button>
</Popconfirm>
)}
{canImport && (
<Button icon={<LoginOutlined />} onClick={openPasswordLogin}>
<Button icon={<ImportOutlined />} onClick={openPasswordImport}>
</Button>
)}
{canImport && (
@@ -449,6 +511,9 @@ export default function HuyaAccountsPage() {
<Col xs={24} sm={4}>
<Card size="small"><Statistic title="标签数" value={tags.length} /></Card>
</Col>
<Col xs={24} sm={4}>
<Card size="small"><Statistic title="已导入密码" value={passwordReadyCount} /></Card>
</Col>
<Col xs={24} sm={4}>
<Card size="small"><Statistic title="已查积分" value={pointCount} /></Card>
</Col>
@@ -553,48 +618,65 @@ export default function HuyaAccountsPage() {
</Modal>
<Modal
title="虎牙密码登录"
open={passwordLoginOpen}
onCancel={closePasswordLogin}
onOk={() => passwordLoginForm.submit()}
okText="登录并保存"
cancelButtonProps={{ disabled: passwordLogging }}
confirmLoading={passwordLogging}
maskClosable={!passwordLogging}
closable={!passwordLogging}
width={560}
title="导入虎牙账号密码"
open={passwordImportOpen}
onCancel={() => {
if (passwordImporting) return;
setPasswordImportOpen(false);
}}
onOk={handleImportPasswordAccounts}
okText="导入"
cancelButtonProps={{ disabled: passwordImporting }}
confirmLoading={passwordImporting}
maskClosable={!passwordImporting}
closable={!passwordImporting}
width={720}
>
<Form
form={passwordLoginForm}
layout="vertical"
requiredMark={false}
onFinish={handlePasswordLogin}
disabled={passwordLogging}
>
<Form.Item
name="username"
label="账号"
rules={[{ required: true, message: '请输入虎牙账号' }]}
>
<Input autoComplete="username" placeholder="手机号、邮箱或虎牙账号" />
</Form.Item>
<Form.Item
name="password"
label="密码"
rules={[{ required: true, message: '请输入密码' }]}
>
<Input.Password autoComplete="current-password" placeholder="虎牙登录密码" />
</Form.Item>
<Form.Item name="tag" label="标签">
<Input placeholder="可选,保存到账号标签" />
</Form.Item>
<Form.Item name="cookie" label="预置 Cookie">
<TextArea
rows={3}
placeholder="可选;若已有 hdid/sdid 等风控 Cookie,可粘贴在这里"
/>
</Form.Item>
</Form>
<Space direction="vertical" style={{ width: '100%' }} size={12}>
<TextArea
rows={10}
value={passwordImportText}
onChange={(e) => setPasswordImportText(e.target.value)}
placeholder="每行一条:账号----密码;如需预置 Cookie:账号----密码----Cookie"
disabled={passwordImporting}
/>
<Select
mode="tags"
style={{ width: '100%' }}
placeholder="可选,保存到账号标签"
maxCount={1}
value={passwordImportTag}
onChange={setPasswordImportTag}
options={tags.map((tag) => ({ value: tag, label: tag }))}
disabled={passwordImporting}
/>
<Paragraph type="secondary" style={{ marginBottom: 0 }}>
</Paragraph>
</Space>
</Modal>
<Modal
title="虎牙登录结果"
open={passwordLoginResultOpen}
onCancel={() => setPasswordLoginResultOpen(false)}
footer={[
<Button key="ok" type="primary" onClick={() => setPasswordLoginResultOpen(false)}>
</Button>,
]}
width={860}
>
{passwordLoginResults.length > 0 && (
<Table
columns={passwordLoginResultColumns}
dataSource={passwordLoginResults}
rowKey={(record) => `${record.line}-${record.username || 'empty'}`}
size="small"
pagination={false}
scroll={{ x: 720, y: 260 }}
/>
)}
</Modal>
</div>
);