虎牙支持先导入账号密码再登录
This commit is contained in:
@@ -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) =>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user