优化双平台后台界面
This commit is contained in:
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "douyu-login-py"
|
name = "douyu-login-py"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
description = "斗鱼批量登录 Web 后台"
|
description = "直播账号运营 Web 后台"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12,<3.13"
|
requires-python = ">=3.12,<3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
+1
-1
@@ -29,7 +29,7 @@ async def lifespan(app: FastAPI):
|
|||||||
|
|
||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="斗鱼批量登录后台",
|
title="直播运营后台",
|
||||||
version=get_app_version(),
|
version=get_app_version(),
|
||||||
lifespan=lifespan,
|
lifespan=lifespan,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>frontend</title>
|
<title>直播运营后台</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Layout, Menu, Avatar, Space, Typography, Button, Modal, Dropdown } from 'antd';
|
import { Layout, Menu, Avatar, Space, Typography, Button, Modal, Dropdown } from 'antd';
|
||||||
|
import type { MenuProps } from 'antd';
|
||||||
import {
|
import {
|
||||||
DashboardOutlined, UserOutlined, LogoutOutlined,
|
DashboardOutlined, UserOutlined, LogoutOutlined,
|
||||||
CloudServerOutlined, TeamOutlined, ApiOutlined, KeyOutlined,
|
CloudServerOutlined, TeamOutlined, ApiOutlined, KeyOutlined,
|
||||||
MenuFoldOutlined, MenuUnfoldOutlined, SwapOutlined,
|
MenuFoldOutlined, MenuUnfoldOutlined, SwapOutlined,
|
||||||
SunOutlined, MoonOutlined, DesktopOutlined, GiftOutlined, ShoppingCartOutlined,
|
SunOutlined, MoonOutlined, DesktopOutlined, GiftOutlined, ShoppingCartOutlined, ApartmentOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { useNavigate, useLocation, Outlet } from 'react-router-dom';
|
import { useNavigate, useLocation, Outlet } from 'react-router-dom';
|
||||||
import { getUser, clearAuth, type AuthUser } from '../store/auth';
|
import { getUser, clearAuth, type AuthUser } from '../store/auth';
|
||||||
@@ -44,49 +45,52 @@ export default function MainLayout({ onLogout }: { onLogout?: () => void }) {
|
|||||||
|
|
||||||
if (!user) return null;
|
if (!user) return null;
|
||||||
|
|
||||||
const menuItems: { key: string; label: string; icon: React.ReactNode }[] = [];
|
const menuItems: MenuProps['items'] = [];
|
||||||
|
const douyuItems: NonNullable<MenuProps['items']> = [];
|
||||||
|
const huyaItems: NonNullable<MenuProps['items']> = [];
|
||||||
|
const systemItems: NonNullable<MenuProps['items']> = [];
|
||||||
|
|
||||||
// Dashboard - 所有人可见
|
// Dashboard - 所有人可见
|
||||||
menuItems.push({ key: '/', label: '概览', icon: <DashboardOutlined /> });
|
menuItems.push({ key: '/', label: '概览', icon: <DashboardOutlined /> });
|
||||||
|
|
||||||
// 账号管理
|
// 斗鱼
|
||||||
if (canAny(['account:view_all', 'account:view_assigned'])) {
|
if (canAny(['account:view_all', 'account:view_assigned'])) {
|
||||||
menuItems.push({ key: '/accounts', label: '账号管理', icon: <UserOutlined /> });
|
douyuItems.push({ key: '/accounts', label: '账号管理', icon: <UserOutlined /> });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分配管理
|
|
||||||
if (can('account:assign')) {
|
if (can('account:assign')) {
|
||||||
menuItems.push({ key: '/assignments', label: '分配管理', icon: <SwapOutlined /> });
|
douyuItems.push({ key: '/assignments', label: '分配管理', icon: <SwapOutlined /> });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 登录任务
|
|
||||||
if (canAny(['login:batch', 'login:view_all'])) {
|
if (canAny(['login:batch', 'login:view_all'])) {
|
||||||
menuItems.push({ key: '/login-tasks', label: '登录任务', icon: <ApiOutlined /> });
|
douyuItems.push({ key: '/login-tasks', label: '登录任务', icon: <ApiOutlined /> });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cookie 管理
|
|
||||||
if (can('cookie:view')) {
|
if (can('cookie:view')) {
|
||||||
menuItems.push({ key: '/cookies', label: 'Cookie 管理', icon: <KeyOutlined /> });
|
douyuItems.push({ key: '/cookies', label: 'Cookie 管理', icon: <KeyOutlined /> });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 虎牙 CK 管理
|
// 虎牙
|
||||||
if (can('huya:account')) {
|
if (can('huya:account')) {
|
||||||
menuItems.push({ key: '/huya/accounts', label: '虎牙 CK', icon: <GiftOutlined /> });
|
huyaItems.push({ key: '/huya/accounts', label: '账号管理', icon: <GiftOutlined /> });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 虎牙兑换与充值
|
|
||||||
if (can('huya:task')) {
|
if (can('huya:task')) {
|
||||||
menuItems.push({ key: '/huya/tasks', label: '虎牙任务', icon: <ShoppingCartOutlined /> });
|
huyaItems.push({ key: '/huya/tasks', label: '任务操作台', icon: <ShoppingCartOutlined /> });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 代理配置
|
if (douyuItems.length > 0) {
|
||||||
|
menuItems.push({ key: 'group-douyu', type: 'group', label: '斗鱼', children: douyuItems });
|
||||||
|
}
|
||||||
|
if (huyaItems.length > 0) {
|
||||||
|
menuItems.push({ key: 'group-huya', type: 'group', label: '虎牙', children: huyaItems });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 系统
|
||||||
if (can('proxy:manage')) {
|
if (can('proxy:manage')) {
|
||||||
menuItems.push({ key: '/proxy', label: '代理配置', icon: <CloudServerOutlined /> });
|
systemItems.push({ key: '/proxy', label: '代理配置', icon: <CloudServerOutlined /> });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用户管理
|
|
||||||
if (can('user:view')) {
|
if (can('user:view')) {
|
||||||
menuItems.push({ key: '/users', label: '用户管理', icon: <TeamOutlined /> });
|
systemItems.push({ key: '/users', label: '用户管理', icon: <TeamOutlined /> });
|
||||||
|
}
|
||||||
|
if (systemItems.length > 0) {
|
||||||
|
menuItems.push({ key: 'group-system', type: 'group', label: '系统', children: systemItems });
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
@@ -129,7 +133,7 @@ export default function MainLayout({ onLogout }: { onLogout?: () => void }) {
|
|||||||
color: siderTextColor,
|
color: siderTextColor,
|
||||||
}}>
|
}}>
|
||||||
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||||
{collapsed ? '鱼' : '斗鱼登录后台'}
|
{collapsed ? <ApartmentOutlined /> : '直播运营后台'}
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
@@ -1,48 +1,191 @@
|
|||||||
import { Card, Col, Row, Statistic, theme } from 'antd';
|
import { Card, Col, Row, Space, Statistic, Tag, Typography, theme } from 'antd';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { accountApi, loginApi, type LoginTaskItem } from '../api/modules';
|
import {
|
||||||
|
accountApi,
|
||||||
|
cookieApi,
|
||||||
|
huyaApi,
|
||||||
|
loginApi,
|
||||||
|
type HuyaTaskItem,
|
||||||
|
type LoginTaskItem,
|
||||||
|
} from '../api/modules';
|
||||||
|
import { usePermissions } from '../hooks/usePermissions';
|
||||||
|
|
||||||
|
const { Title, Text } = Typography;
|
||||||
|
|
||||||
|
interface PlatformStats {
|
||||||
|
accounts: number;
|
||||||
|
tasks: number;
|
||||||
|
success: number;
|
||||||
|
failed: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DashboardStats {
|
||||||
|
douyu: PlatformStats & {
|
||||||
|
cookies: number;
|
||||||
|
};
|
||||||
|
huya: PlatformStats & {
|
||||||
|
goods: number;
|
||||||
|
rechargeGoods: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const EMPTY_STATS: DashboardStats = {
|
||||||
|
douyu: {
|
||||||
|
accounts: 0,
|
||||||
|
tasks: 0,
|
||||||
|
success: 0,
|
||||||
|
failed: 0,
|
||||||
|
cookies: 0,
|
||||||
|
},
|
||||||
|
huya: {
|
||||||
|
accounts: 0,
|
||||||
|
tasks: 0,
|
||||||
|
success: 0,
|
||||||
|
failed: 0,
|
||||||
|
goods: 0,
|
||||||
|
rechargeGoods: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function countFailed<T extends { status: string }>(items: T[]) {
|
||||||
|
return items.filter((item) => ['failed', 'error'].includes(item.status)).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function StatCard({
|
||||||
|
title,
|
||||||
|
value,
|
||||||
|
color,
|
||||||
|
loading,
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
value: number;
|
||||||
|
color?: string;
|
||||||
|
loading?: boolean;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Col xs={24} sm={12} lg={6}>
|
||||||
|
<Card size="small" loading={loading}>
|
||||||
|
<Statistic title={title} value={value} styles={{ content: color ? { color } : undefined }} />
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function DashboardPage() {
|
export default function DashboardPage() {
|
||||||
const { token } = theme.useToken();
|
const { token } = theme.useToken();
|
||||||
const [stats, setStats] = useState({ accounts: 0, tasks: 0, success: 0, failed: 0 });
|
const { can, canAny } = usePermissions();
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [stats, setStats] = useState<DashboardStats>(EMPTY_STATS);
|
||||||
|
|
||||||
|
const canViewDouyuAccounts = canAny(['account:view_all', 'account:view_assigned']);
|
||||||
|
const canViewDouyuTasks = canAny(['login:batch', 'login:view_all', 'login:view_assigned']);
|
||||||
|
const canViewCookies = can('cookie:view');
|
||||||
|
const canViewHuyaAccounts = can('huya:account');
|
||||||
|
const canViewHuyaTasks = can('huya:task');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Promise.all([accountApi.list(), loginApi.listTasks()])
|
let ignore = false;
|
||||||
.then(([accounts, tasks]) => {
|
|
||||||
setStats({
|
async function loadDashboard() {
|
||||||
accounts: accounts.length,
|
setLoading(true);
|
||||||
tasks: tasks.length,
|
const [
|
||||||
success: tasks.filter((t: LoginTaskItem) => t.status === 'success').length,
|
douyuAccountsResult,
|
||||||
failed: tasks.filter((t: LoginTaskItem) => ['failed', 'error'].includes(t.status)).length,
|
douyuTasksResult,
|
||||||
});
|
cookiesResult,
|
||||||
})
|
huyaAccountsResult,
|
||||||
.catch(() => {});
|
huyaTasksResult,
|
||||||
}, []);
|
huyaGoodsResult,
|
||||||
|
huyaRechargeGoodsResult,
|
||||||
|
] = await Promise.allSettled([
|
||||||
|
canViewDouyuAccounts ? accountApi.list() : Promise.resolve([]),
|
||||||
|
canViewDouyuTasks ? loginApi.listTasks() : Promise.resolve([]),
|
||||||
|
canViewCookies ? cookieApi.list() : Promise.resolve([]),
|
||||||
|
canViewHuyaAccounts ? huyaApi.listAccounts() : Promise.resolve([]),
|
||||||
|
canViewHuyaTasks ? huyaApi.listTasks() : Promise.resolve([]),
|
||||||
|
canViewHuyaTasks ? huyaApi.listGoods() : Promise.resolve([]),
|
||||||
|
canViewHuyaTasks ? huyaApi.listRechargeGoods() : Promise.resolve([]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (ignore) return;
|
||||||
|
|
||||||
|
const douyuAccounts = douyuAccountsResult.status === 'fulfilled' ? douyuAccountsResult.value : [];
|
||||||
|
const douyuTasks = douyuTasksResult.status === 'fulfilled' ? douyuTasksResult.value : [];
|
||||||
|
const cookies = cookiesResult.status === 'fulfilled' ? cookiesResult.value : [];
|
||||||
|
const huyaAccounts = huyaAccountsResult.status === 'fulfilled' ? huyaAccountsResult.value : [];
|
||||||
|
const huyaTasks = huyaTasksResult.status === 'fulfilled' ? huyaTasksResult.value : [];
|
||||||
|
const huyaGoods = huyaGoodsResult.status === 'fulfilled' ? huyaGoodsResult.value : [];
|
||||||
|
const huyaRechargeGoods = huyaRechargeGoodsResult.status === 'fulfilled' ? huyaRechargeGoodsResult.value : [];
|
||||||
|
|
||||||
|
setStats({
|
||||||
|
douyu: {
|
||||||
|
accounts: douyuAccounts.length,
|
||||||
|
tasks: douyuTasks.length,
|
||||||
|
success: douyuTasks.filter((task: LoginTaskItem) => task.status === 'success').length,
|
||||||
|
failed: countFailed(douyuTasks),
|
||||||
|
cookies: cookies.length,
|
||||||
|
},
|
||||||
|
huya: {
|
||||||
|
accounts: huyaAccounts.length,
|
||||||
|
tasks: huyaTasks.length,
|
||||||
|
success: huyaTasks.filter((task: HuyaTaskItem) => task.status === 'success').length,
|
||||||
|
failed: countFailed(huyaTasks),
|
||||||
|
goods: huyaGoods.length,
|
||||||
|
rechargeGoods: huyaRechargeGoods.length,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadDashboard();
|
||||||
|
return () => {
|
||||||
|
ignore = true;
|
||||||
|
};
|
||||||
|
}, [canViewCookies, canViewDouyuAccounts, canViewDouyuTasks, canViewHuyaAccounts, canViewHuyaTasks]);
|
||||||
|
|
||||||
|
const totalStats = useMemo(() => ({
|
||||||
|
accounts: stats.douyu.accounts + stats.huya.accounts,
|
||||||
|
tasks: stats.douyu.tasks + stats.huya.tasks,
|
||||||
|
success: stats.douyu.success + stats.huya.success,
|
||||||
|
failed: stats.douyu.failed + stats.huya.failed,
|
||||||
|
}), [stats]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2>概览</h2>
|
<div style={{ marginBottom: 16 }}>
|
||||||
<Row gutter={16}>
|
<Space align="center" wrap>
|
||||||
<Col span={6}>
|
<Title level={2} style={{ margin: 0 }}>概览</Title>
|
||||||
<Card>
|
<Tag color="blue">斗鱼</Tag>
|
||||||
<Statistic title="账号总数" value={stats.accounts} />
|
<Tag color="orange">虎牙</Tag>
|
||||||
</Card>
|
</Space>
|
||||||
</Col>
|
</div>
|
||||||
<Col span={6}>
|
|
||||||
<Card>
|
<Row gutter={[12, 12]} style={{ marginBottom: 20 }}>
|
||||||
<Statistic title="登录任务总数" value={stats.tasks} />
|
<StatCard title="账号总数" value={totalStats.accounts} loading={loading} />
|
||||||
</Card>
|
<StatCard title="任务总数" value={totalStats.tasks} loading={loading} />
|
||||||
</Col>
|
<StatCard title="成功任务" value={totalStats.success} color={token.colorSuccess} loading={loading} />
|
||||||
<Col span={6}>
|
<StatCard title="失败任务" value={totalStats.failed} color={token.colorError} loading={loading} />
|
||||||
<Card>
|
</Row>
|
||||||
<Statistic title="成功" value={stats.success} styles={{ content: { color: token.colorSuccess } }} />
|
|
||||||
</Card>
|
<div style={{ marginBottom: 10 }}>
|
||||||
</Col>
|
<Title level={4} style={{ margin: 0 }}>斗鱼</Title>
|
||||||
<Col span={6}>
|
<Text type="secondary">账号登录、Cookie 产出和批量登录任务</Text>
|
||||||
<Card>
|
</div>
|
||||||
<Statistic title="失败" value={stats.failed} styles={{ content: { color: token.colorError } }} />
|
<Row gutter={[12, 12]} style={{ marginBottom: 20 }}>
|
||||||
</Card>
|
<StatCard title="斗鱼账号" value={stats.douyu.accounts} loading={loading} />
|
||||||
</Col>
|
<StatCard title="登录任务" value={stats.douyu.tasks} loading={loading} />
|
||||||
|
<StatCard title="成功 Cookie" value={stats.douyu.cookies} color={token.colorSuccess} loading={loading} />
|
||||||
|
<StatCard title="失败/异常" value={stats.douyu.failed} color={token.colorError} loading={loading} />
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: 10 }}>
|
||||||
|
<Title level={4} style={{ margin: 0 }}>虎牙</Title>
|
||||||
|
<Text type="secondary">账号 Cookie、活动任务、兑换商品和充值商品</Text>
|
||||||
|
</div>
|
||||||
|
<Row gutter={[12, 12]}>
|
||||||
|
<StatCard title="虎牙账号" value={stats.huya.accounts} loading={loading} />
|
||||||
|
<StatCard title="虎牙任务" value={stats.huya.tasks} loading={loading} />
|
||||||
|
<StatCard title="兑换商品" value={stats.huya.goods} loading={loading} />
|
||||||
|
<StatCard title="充值商品" value={stats.huya.rechargeGoods} loading={loading} />
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -735,10 +735,31 @@ export default function HuyaTasksPage() {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const taskSummary = (
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 16, marginBottom: 8, color: token.colorTextSecondary, flexWrap: 'wrap' }}>
|
||||||
|
<span>共 <b>{tasks.length}</b> 个任务</span>
|
||||||
|
<span>已计划 <b>{plannedCount}</b></span>
|
||||||
|
<span>成功 <b style={{ color: token.colorSuccess }}>{successCount}</b></span>
|
||||||
|
<span>失败 <b style={{ color: token.colorError }}>{failedCount}</b></span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderTaskTable = (pageSize = 12) => (
|
||||||
|
<Table
|
||||||
|
columns={taskColumns}
|
||||||
|
dataSource={tasks}
|
||||||
|
rowKey="id"
|
||||||
|
loading={loading}
|
||||||
|
size="small"
|
||||||
|
pagination={{ pageSize, showTotal: (total) => `共 ${total} 条` }}
|
||||||
|
scroll={{ x: 920 }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ height: '100%', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
|
<div style={{ height: '100%', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
|
||||||
<div style={{ flexShrink: 0, marginBottom: 12, display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12 }}>
|
<div style={{ flexShrink: 0, marginBottom: 12, display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12 }}>
|
||||||
<h2 style={{ margin: 0 }}>虎牙兑换与充值</h2>
|
<h2 style={{ margin: 0 }}>虎牙任务操作台</h2>
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<Button icon={<ReloadOutlined />} onClick={loadAll} loading={loading}>
|
<Button icon={<ReloadOutlined />} onClick={loadAll} loading={loading}>
|
||||||
刷新
|
刷新
|
||||||
@@ -748,313 +769,327 @@ export default function HuyaTasksPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ flex: 1, minHeight: 0, overflowY: 'auto', overflowX: 'hidden', paddingRight: 2 }}>
|
<div style={{ flex: 1, minHeight: 0, overflowY: 'auto', overflowX: 'hidden', paddingRight: 2 }}>
|
||||||
<Row gutter={12}>
|
<Tabs
|
||||||
<Col xs={24} xl={10}>
|
defaultActiveKey="workbench"
|
||||||
<Card
|
items={[
|
||||||
size="small"
|
{
|
||||||
title={<Space><SettingOutlined />虎牙配置</Space>}
|
key: 'workbench',
|
||||||
extra={canConfig && (
|
label: <Space><PlayCircleOutlined />操作台</Space>,
|
||||||
<Button size="small" type="primary" onClick={saveConfig} loading={savingConfig}>
|
children: (
|
||||||
保存
|
<>
|
||||||
</Button>
|
<Card size="small" title="批量动作" style={{ marginBottom: 12 }}>
|
||||||
)}
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
|
||||||
style={{ marginBottom: 12 }}
|
|
||||||
>
|
|
||||||
<Form form={form} layout="vertical" disabled={!canConfig}>
|
|
||||||
<Row gutter={8}>
|
|
||||||
<Col span={12}>
|
|
||||||
<Form.Item label="直播间 ID" name="room_pid">
|
|
||||||
<Input placeholder="默认 1199650619883" />
|
|
||||||
</Form.Item>
|
|
||||||
</Col>
|
|
||||||
<Col span={12}>
|
|
||||||
<Form.Item label="SID" name="sid">
|
|
||||||
<Input placeholder="默认 2203" />
|
|
||||||
</Form.Item>
|
|
||||||
</Col>
|
|
||||||
<Col span={12}>
|
|
||||||
<Form.Item label="兑换活动 ID" name="outer_act_id">
|
|
||||||
<Input placeholder="默认 9504" />
|
|
||||||
</Form.Item>
|
|
||||||
</Col>
|
|
||||||
<Col span={12}>
|
|
||||||
<Form.Item label="绑定 bActId" name="bind_act_id">
|
|
||||||
<Input placeholder="默认 9271" />
|
|
||||||
</Form.Item>
|
|
||||||
</Col>
|
|
||||||
<Col span={12}>
|
|
||||||
<Form.Item label="支付渠道" name="pay_channel">
|
|
||||||
<Select
|
<Select
|
||||||
options={[
|
mode="multiple"
|
||||||
{ value: 'Weixin', label: '微信' },
|
showSearch
|
||||||
{ value: 'Zfb', label: '支付宝' },
|
placeholder="选择虎牙账号"
|
||||||
]}
|
value={selectedIds}
|
||||||
|
onChange={setSelectedIds}
|
||||||
|
options={accountOptions}
|
||||||
|
maxTagCount="responsive"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
filterOption={(input, option) => String(option?.label || '').toLowerCase().includes(input.toLowerCase())}
|
||||||
|
dropdownRender={(menu) => (
|
||||||
|
<>
|
||||||
|
<div style={{ padding: '4px 8px', borderBottom: `1px solid ${token.colorBorderSecondary}`, display: 'flex', gap: 8 }}>
|
||||||
|
<Button size="small" type="link" onClick={() => setSelectedIds(accounts.map((item) => item.id))}>
|
||||||
|
全选 ({accounts.length})
|
||||||
|
</Button>
|
||||||
|
<Button size="small" type="link" onClick={() => setSelectedIds([])}>
|
||||||
|
清空
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{menu}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
|
||||||
</Col>
|
<Select
|
||||||
</Row>
|
value={selectedTaskType}
|
||||||
</Form>
|
onChange={setSelectedTaskType}
|
||||||
</Card>
|
options={Object.entries(taskTypes).map(([value, label]) => ({ value, label }))}
|
||||||
|
style={{ flex: '1 1 220px', minWidth: 180 }}
|
||||||
<Card
|
/>
|
||||||
size="small"
|
<InputNumber
|
||||||
title={<Space><ShoppingOutlined />兑换商品列表</Space>}
|
min={1}
|
||||||
extra={(
|
max={10}
|
||||||
<Space size={6}>
|
value={concurrency}
|
||||||
<Button
|
onChange={(value) => setConcurrency(value || 1)}
|
||||||
size="small"
|
addonBefore="并发"
|
||||||
icon={<ReloadOutlined />}
|
style={{ width: 130, flexShrink: 0 }}
|
||||||
onClick={() => startTask('refresh_goods')}
|
/>
|
||||||
disabled={!canTask || selectedIds.length === 0 || wsConnected}
|
<Button
|
||||||
>
|
type="primary"
|
||||||
刷新
|
icon={<PlayCircleOutlined />}
|
||||||
</Button>
|
loading={starting}
|
||||||
<Button
|
disabled={!canTask || selectedIds.length === 0 || wsConnected}
|
||||||
size="small"
|
onClick={() => startTask()}
|
||||||
type="primary"
|
>
|
||||||
icon={<CheckCircleOutlined />}
|
创建任务
|
||||||
onClick={() => startTask('exchange_goods')}
|
|
||||||
disabled={!canTask || selectedIds.length === 0 || wsConnected}
|
|
||||||
>
|
|
||||||
兑换
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
)}
|
|
||||||
style={{ marginBottom: 12 }}
|
|
||||||
>
|
|
||||||
<Space style={{ marginBottom: 8 }} wrap>
|
|
||||||
<Select
|
|
||||||
showSearch
|
|
||||||
allowClear
|
|
||||||
placeholder="选择兑换商品"
|
|
||||||
value={selectedExchangeGoodsId || undefined}
|
|
||||||
onChange={(value) => setSelectedExchangeGoodsId(value || '')}
|
|
||||||
options={goodsOptions}
|
|
||||||
style={{ minWidth: 220 }}
|
|
||||||
filterOption={(input, option) => String(option?.label || '').toLowerCase().includes(input.toLowerCase())}
|
|
||||||
/>
|
|
||||||
<DatePicker
|
|
||||||
showTime
|
|
||||||
allowClear
|
|
||||||
value={exchangeAt}
|
|
||||||
onChange={setExchangeAt}
|
|
||||||
placeholder="立即兑换"
|
|
||||||
style={{ width: 190 }}
|
|
||||||
/>
|
|
||||||
</Space>
|
|
||||||
{goodsCategories.length > 0 && (
|
|
||||||
<Tabs
|
|
||||||
size="small"
|
|
||||||
activeKey={selectedGoodsCategory}
|
|
||||||
onChange={setSelectedGoodsCategory}
|
|
||||||
items={
|
|
||||||
goodsCategories.map((item) => ({
|
|
||||||
key: item.key,
|
|
||||||
label: `${item.label} (${item.count})`,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Table
|
|
||||||
columns={goodsColumns}
|
|
||||||
dataSource={filteredGoods}
|
|
||||||
rowKey="id"
|
|
||||||
size="small"
|
|
||||||
pagination={false}
|
|
||||||
scroll={{ y: 220 }}
|
|
||||||
locale={{ emptyText: '暂无兑换商品,请先刷新商品列表' }}
|
|
||||||
onRow={(record) => ({
|
|
||||||
onClick: () => setSelectedExchangeGoodsId(record.product_id),
|
|
||||||
})}
|
|
||||||
rowClassName={(record) => record.product_id === selectedExchangeGoodsId ? 'ant-table-row-selected' : ''}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card
|
|
||||||
size="small"
|
|
||||||
title={<Space><CreditCardOutlined />充值商品列表</Space>}
|
|
||||||
extra={(
|
|
||||||
<Space size={6}>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
icon={<ReloadOutlined />}
|
|
||||||
onClick={() => startTask('refresh_recharge_goods')}
|
|
||||||
disabled={!canTask || selectedIds.length === 0 || wsConnected}
|
|
||||||
>
|
|
||||||
刷新
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
type="primary"
|
|
||||||
icon={<QrcodeOutlined />}
|
|
||||||
onClick={() => startTask('create_recharge_order')}
|
|
||||||
disabled={!canTask || selectedIds.length === 0 || wsConnected}
|
|
||||||
>
|
|
||||||
支付码
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
)}
|
|
||||||
style={{ marginBottom: 12 }}
|
|
||||||
>
|
|
||||||
<Space style={{ marginBottom: 8 }} wrap>
|
|
||||||
<Select
|
|
||||||
showSearch
|
|
||||||
allowClear
|
|
||||||
placeholder="选择充值商品"
|
|
||||||
value={selectedRechargeGoodsId || undefined}
|
|
||||||
onChange={(value) => setSelectedRechargeGoodsId(value || '')}
|
|
||||||
options={rechargeGoodsOptions}
|
|
||||||
style={{ minWidth: 220 }}
|
|
||||||
filterOption={(input, option) => String(option?.label || '').toLowerCase().includes(input.toLowerCase())}
|
|
||||||
/>
|
|
||||||
<InputNumber
|
|
||||||
min={1}
|
|
||||||
max={999}
|
|
||||||
value={rechargeCount}
|
|
||||||
onChange={(value) => setRechargeCount(value || 1)}
|
|
||||||
addonAfter="份"
|
|
||||||
style={{ width: 120 }}
|
|
||||||
/>
|
|
||||||
<Select
|
|
||||||
value={rechargePayChannel}
|
|
||||||
onChange={setRechargePayChannel}
|
|
||||||
options={[
|
|
||||||
{ value: 'Weixin', label: '微信' },
|
|
||||||
{ value: 'Zfb', label: '支付宝' },
|
|
||||||
]}
|
|
||||||
style={{ width: 110 }}
|
|
||||||
/>
|
|
||||||
</Space>
|
|
||||||
<Table
|
|
||||||
columns={rechargeGoodsColumns}
|
|
||||||
dataSource={sortedRechargeGoods}
|
|
||||||
rowKey="id"
|
|
||||||
size="small"
|
|
||||||
pagination={false}
|
|
||||||
scroll={{ y: 220 }}
|
|
||||||
locale={{ emptyText: '暂无充值商品,请先刷新充值商品列表' }}
|
|
||||||
onRow={(record) => ({
|
|
||||||
onClick: () => setSelectedRechargeGoodsId(record.spu_id),
|
|
||||||
})}
|
|
||||||
rowClassName={(record) => record.spu_id === selectedRechargeGoodsId ? 'ant-table-row-selected' : ''}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
|
|
||||||
<Col xs={24} xl={14}>
|
|
||||||
<Card size="small" title="批量动作" style={{ marginBottom: 12 }}>
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
|
|
||||||
<Select
|
|
||||||
mode="multiple"
|
|
||||||
showSearch
|
|
||||||
placeholder="选择虎牙 CK"
|
|
||||||
value={selectedIds}
|
|
||||||
onChange={setSelectedIds}
|
|
||||||
options={accountOptions}
|
|
||||||
maxTagCount="responsive"
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
filterOption={(input, option) => String(option?.label || '').toLowerCase().includes(input.toLowerCase())}
|
|
||||||
dropdownRender={(menu) => (
|
|
||||||
<>
|
|
||||||
<div style={{ padding: '4px 8px', borderBottom: `1px solid ${token.colorBorderSecondary}`, display: 'flex', gap: 8 }}>
|
|
||||||
<Button size="small" type="link" onClick={() => setSelectedIds(accounts.map((item) => item.id))}>
|
|
||||||
全选 ({accounts.length})
|
|
||||||
</Button>
|
|
||||||
<Button size="small" type="link" onClick={() => setSelectedIds([])}>
|
|
||||||
清空
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{menu}
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
|
||||||
</>
|
{QUICK_ACTIONS.map((item) => (
|
||||||
)}
|
<Tooltip
|
||||||
/>
|
key={item.key}
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
|
title={
|
||||||
<Select
|
item.key === 'refresh_goods'
|
||||||
value={selectedTaskType}
|
? '使用一个选中的账号刷新当前 SID 兑换商品'
|
||||||
onChange={setSelectedTaskType}
|
: item.key === 'refresh_recharge_goods'
|
||||||
options={Object.entries(taskTypes).map(([value, label]) => ({ value, label }))}
|
? '使用一个选中的账号刷新充值商品列表'
|
||||||
style={{ flex: '1 1 220px', minWidth: 180 }}
|
: item.key === 'exchange_goods'
|
||||||
|
? '按商品页选择商品,支持立即或定时兑换'
|
||||||
|
: item.key === 'create_recharge_order'
|
||||||
|
? '按商品页选择生成扫码支付二维码'
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
icon={item.icon}
|
||||||
|
size="small"
|
||||||
|
onClick={() => startTask(item.key)}
|
||||||
|
disabled={!canTask || selectedIds.length === 0 || wsConnected}
|
||||||
|
>
|
||||||
|
{taskTypes[item.key] || item.key}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{taskSummary}
|
||||||
|
{renderTaskTable(12)}
|
||||||
|
<RealtimeLogPanel
|
||||||
|
logs={logs}
|
||||||
|
connected={wsConnected}
|
||||||
|
title="虎牙实时日志"
|
||||||
|
emptyText="暂无虎牙任务日志"
|
||||||
|
collapsible
|
||||||
|
spinWhenEmpty
|
||||||
|
style={{ marginTop: 8 }}
|
||||||
/>
|
/>
|
||||||
<InputNumber
|
</>
|
||||||
min={1}
|
),
|
||||||
max={10}
|
},
|
||||||
value={concurrency}
|
{
|
||||||
onChange={(value) => setConcurrency(value || 1)}
|
key: 'goods',
|
||||||
addonBefore="并发"
|
label: <Space><ShoppingOutlined />商品</Space>,
|
||||||
style={{ width: 130, flexShrink: 0 }}
|
children: (
|
||||||
/>
|
<Row gutter={12}>
|
||||||
<Button
|
<Col xs={24} xl={12}>
|
||||||
type="primary"
|
<Card
|
||||||
icon={<PlayCircleOutlined />}
|
size="small"
|
||||||
loading={starting}
|
title={<Space><ShoppingOutlined />兑换商品</Space>}
|
||||||
disabled={!canTask || selectedIds.length === 0 || wsConnected}
|
extra={(
|
||||||
onClick={() => startTask()}
|
<Space size={6}>
|
||||||
>
|
<Button
|
||||||
创建任务
|
size="small"
|
||||||
</Button>
|
icon={<ReloadOutlined />}
|
||||||
</div>
|
onClick={() => startTask('refresh_goods')}
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
|
disabled={!canTask || selectedIds.length === 0 || wsConnected}
|
||||||
{QUICK_ACTIONS.map((item) => (
|
>
|
||||||
<Tooltip
|
刷新
|
||||||
key={item.key}
|
</Button>
|
||||||
title={
|
<Button
|
||||||
item.key === 'refresh_goods'
|
size="small"
|
||||||
? '使用一个选中的 CK 刷新当前 SID 兑换商品'
|
type="primary"
|
||||||
: item.key === 'refresh_recharge_goods'
|
icon={<CheckCircleOutlined />}
|
||||||
? '使用一个选中的 CK 刷新充值商品列表'
|
onClick={() => startTask('exchange_goods')}
|
||||||
: item.key === 'exchange_goods'
|
disabled={!canTask || selectedIds.length === 0 || wsConnected}
|
||||||
? '按左侧选择商品,支持立即或定时兑换'
|
>
|
||||||
: item.key === 'create_recharge_order'
|
兑换
|
||||||
? '按左侧选择生成扫码支付二维码'
|
</Button>
|
||||||
: undefined
|
</Space>
|
||||||
}
|
)}
|
||||||
|
style={{ marginBottom: 12 }}
|
||||||
>
|
>
|
||||||
<Button
|
<Space style={{ marginBottom: 8 }} wrap>
|
||||||
icon={item.icon}
|
<Select
|
||||||
|
showSearch
|
||||||
|
allowClear
|
||||||
|
placeholder="选择兑换商品"
|
||||||
|
value={selectedExchangeGoodsId || undefined}
|
||||||
|
onChange={(value) => setSelectedExchangeGoodsId(value || '')}
|
||||||
|
options={goodsOptions}
|
||||||
|
style={{ minWidth: 220 }}
|
||||||
|
filterOption={(input, option) => String(option?.label || '').toLowerCase().includes(input.toLowerCase())}
|
||||||
|
/>
|
||||||
|
<DatePicker
|
||||||
|
showTime
|
||||||
|
allowClear
|
||||||
|
value={exchangeAt}
|
||||||
|
onChange={setExchangeAt}
|
||||||
|
placeholder="立即兑换"
|
||||||
|
style={{ width: 190 }}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
{goodsCategories.length > 0 && (
|
||||||
|
<Tabs
|
||||||
|
size="small"
|
||||||
|
activeKey={selectedGoodsCategory}
|
||||||
|
onChange={setSelectedGoodsCategory}
|
||||||
|
items={goodsCategories.map((item) => ({
|
||||||
|
key: item.key,
|
||||||
|
label: `${item.label} (${item.count})`,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Table
|
||||||
|
columns={goodsColumns}
|
||||||
|
dataSource={filteredGoods}
|
||||||
|
rowKey="id"
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => startTask(item.key)}
|
pagination={false}
|
||||||
disabled={!canTask || selectedIds.length === 0 || wsConnected}
|
scroll={{ y: 360 }}
|
||||||
>
|
locale={{ emptyText: '暂无兑换商品,请先刷新商品列表' }}
|
||||||
{taskTypes[item.key] || item.key}
|
onRow={(record) => ({
|
||||||
</Button>
|
onClick: () => setSelectedExchangeGoodsId(record.product_id),
|
||||||
</Tooltip>
|
})}
|
||||||
))}
|
rowClassName={(record) => record.product_id === selectedExchangeGoodsId ? 'ant-table-row-selected' : ''}
|
||||||
</div>
|
/>
|
||||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
</Card>
|
||||||
当前已接入查询积分、绑定、兑换记录、兑换商品列表、商品兑换、充值商品列表和扫码支付。
|
</Col>
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 16, marginBottom: 8, color: token.colorTextSecondary }}>
|
<Col xs={24} xl={12}>
|
||||||
<span>共 <b>{tasks.length}</b> 个任务</span>
|
<Card
|
||||||
<span>已计划 <b>{plannedCount}</b></span>
|
size="small"
|
||||||
<span>成功 <b style={{ color: token.colorSuccess }}>{successCount}</b></span>
|
title={<Space><CreditCardOutlined />充值商品</Space>}
|
||||||
<span>失败 <b style={{ color: token.colorError }}>{failedCount}</b></span>
|
extra={(
|
||||||
</div>
|
<Space size={6}>
|
||||||
<Table
|
<Button
|
||||||
columns={taskColumns}
|
size="small"
|
||||||
dataSource={tasks}
|
icon={<ReloadOutlined />}
|
||||||
rowKey="id"
|
onClick={() => startTask('refresh_recharge_goods')}
|
||||||
loading={loading}
|
disabled={!canTask || selectedIds.length === 0 || wsConnected}
|
||||||
size="small"
|
>
|
||||||
pagination={{ pageSize: 12, showTotal: (total) => `共 ${total} 条` }}
|
刷新
|
||||||
scroll={{ x: 920 }}
|
</Button>
|
||||||
/>
|
<Button
|
||||||
</Col>
|
size="small"
|
||||||
</Row>
|
type="primary"
|
||||||
|
icon={<QrcodeOutlined />}
|
||||||
|
onClick={() => startTask('create_recharge_order')}
|
||||||
|
disabled={!canTask || selectedIds.length === 0 || wsConnected}
|
||||||
|
>
|
||||||
|
支付码
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
style={{ marginBottom: 12 }}
|
||||||
|
>
|
||||||
|
<Space style={{ marginBottom: 8 }} wrap>
|
||||||
|
<Select
|
||||||
|
showSearch
|
||||||
|
allowClear
|
||||||
|
placeholder="选择充值商品"
|
||||||
|
value={selectedRechargeGoodsId || undefined}
|
||||||
|
onChange={(value) => setSelectedRechargeGoodsId(value || '')}
|
||||||
|
options={rechargeGoodsOptions}
|
||||||
|
style={{ minWidth: 220 }}
|
||||||
|
filterOption={(input, option) => String(option?.label || '').toLowerCase().includes(input.toLowerCase())}
|
||||||
|
/>
|
||||||
|
<InputNumber
|
||||||
|
min={1}
|
||||||
|
max={999}
|
||||||
|
value={rechargeCount}
|
||||||
|
onChange={(value) => setRechargeCount(value || 1)}
|
||||||
|
addonAfter="份"
|
||||||
|
style={{ width: 120 }}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
value={rechargePayChannel}
|
||||||
|
onChange={setRechargePayChannel}
|
||||||
|
options={[
|
||||||
|
{ value: 'Weixin', label: '微信' },
|
||||||
|
{ value: 'Zfb', label: '支付宝' },
|
||||||
|
]}
|
||||||
|
style={{ width: 110 }}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
<Table
|
||||||
|
columns={rechargeGoodsColumns}
|
||||||
|
dataSource={sortedRechargeGoods}
|
||||||
|
rowKey="id"
|
||||||
|
size="small"
|
||||||
|
pagination={false}
|
||||||
|
scroll={{ y: 410 }}
|
||||||
|
locale={{ emptyText: '暂无充值商品,请先刷新充值商品列表' }}
|
||||||
|
onRow={(record) => ({
|
||||||
|
onClick: () => setSelectedRechargeGoodsId(record.spu_id),
|
||||||
|
})}
|
||||||
|
rowClassName={(record) => record.spu_id === selectedRechargeGoodsId ? 'ant-table-row-selected' : ''}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'config',
|
||||||
|
label: <Space><SettingOutlined />配置</Space>,
|
||||||
|
children: (
|
||||||
|
<Card
|
||||||
|
size="small"
|
||||||
|
title={<Space><SettingOutlined />虎牙配置</Space>}
|
||||||
|
extra={canConfig && (
|
||||||
|
<Button size="small" type="primary" onClick={saveConfig} loading={savingConfig}>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
style={{ maxWidth: 760 }}
|
||||||
|
>
|
||||||
|
<Form form={form} layout="vertical" disabled={!canConfig}>
|
||||||
|
<Row gutter={8}>
|
||||||
|
<Col xs={24} sm={12}>
|
||||||
|
<Form.Item label="直播间 ID" name="room_pid">
|
||||||
|
<Input placeholder="默认 1199650619883" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} sm={12}>
|
||||||
|
<Form.Item label="SID" name="sid">
|
||||||
|
<Input placeholder="默认 2203" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} sm={12}>
|
||||||
|
<Form.Item label="兑换活动 ID" name="outer_act_id">
|
||||||
|
<Input placeholder="默认 9504" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} sm={12}>
|
||||||
|
<Form.Item label="绑定 bActId" name="bind_act_id">
|
||||||
|
<Input placeholder="默认 9271" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} sm={12}>
|
||||||
|
<Form.Item label="支付渠道" name="pay_channel">
|
||||||
|
<Select
|
||||||
|
options={[
|
||||||
|
{ value: 'Weixin', label: '微信' },
|
||||||
|
{ value: 'Zfb', label: '支付宝' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'records',
|
||||||
|
label: <Space><FieldTimeOutlined />任务记录</Space>,
|
||||||
|
children: (
|
||||||
|
<>
|
||||||
|
{taskSummary}
|
||||||
|
{renderTaskTable(20)}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<RealtimeLogPanel
|
|
||||||
logs={logs}
|
|
||||||
connected={wsConnected}
|
|
||||||
title="虎牙实时日志"
|
|
||||||
emptyText="暂无虎牙任务日志"
|
|
||||||
collapsible
|
|
||||||
spinWhenEmpty
|
|
||||||
style={{ marginTop: 4 }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
title="兑换记录"
|
title="兑换记录"
|
||||||
open={!!exchangeRecordsTask}
|
open={!!exchangeRecordsTask}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export default function LoginPage({ onLogin }: { onLogin?: () => void }) {
|
|||||||
}}>
|
}}>
|
||||||
<Card style={{ width: 380, boxShadow: isDark ? '0 8px 24px rgba(0,0,0,0.4)' : '0 8px 24px rgba(0,0,0,0.15)' }}>
|
<Card style={{ width: 380, boxShadow: isDark ? '0 8px 24px rgba(0,0,0,0.4)' : '0 8px 24px rgba(0,0,0,0.15)' }}>
|
||||||
<Title level={3} style={{ textAlign: 'center', marginBottom: 32 }}>
|
<Title level={3} style={{ textAlign: 'center', marginBottom: 32 }}>
|
||||||
斗鱼批量登录后台
|
直播运营后台
|
||||||
</Title>
|
</Title>
|
||||||
<Text type="secondary" style={{
|
<Text type="secondary" style={{
|
||||||
display: 'block',
|
display: 'block',
|
||||||
|
|||||||
Reference in New Issue
Block a user