feat(douyu): 精英宝典工作台支持上下/左右布局切换并记忆偏好
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Button, Card, Input, InputNumber, Modal, QRCode, Select, Space, Table, Tag, Typography,
|
Button, Card, Input, InputNumber, Modal, QRCode, Select, Space, Table, Tag, Tooltip, Typography,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import type { TableProps } from 'antd';
|
import type { TableProps } from 'antd';
|
||||||
import {
|
import {
|
||||||
CheckCircleOutlined, CopyOutlined, CreditCardOutlined, ExclamationCircleOutlined, FieldTimeOutlined, GiftOutlined,
|
CheckCircleOutlined, ColumnWidthOutlined, CopyOutlined, CreditCardOutlined, ExclamationCircleOutlined, FieldTimeOutlined, GiftOutlined,
|
||||||
ImportOutlined, QrcodeOutlined, ReloadOutlined,
|
ImportOutlined, QrcodeOutlined, ReloadOutlined,
|
||||||
SearchOutlined, SettingOutlined, ShoppingOutlined, StopOutlined,
|
SearchOutlined, SettingOutlined, ShoppingOutlined, StopOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
@@ -116,6 +116,7 @@ const ESPORTS_TASK_TYPES = new Set([
|
|||||||
]);
|
]);
|
||||||
const DOUYU_GOLD_AMOUNT_STORAGE_KEY = 'douyu_task_gold_amount';
|
const DOUYU_GOLD_AMOUNT_STORAGE_KEY = 'douyu_task_gold_amount';
|
||||||
const DOUYU_GIFT_COUNT_STORAGE_KEY = 'douyu_task_gift_count';
|
const DOUYU_GIFT_COUNT_STORAGE_KEY = 'douyu_task_gift_count';
|
||||||
|
const DOUYU_LAYOUT_MODE_STORAGE_KEY = 'douyu_task_layout_mode';
|
||||||
const CONFIRM_FAIL_TIP_SECONDS = 6;
|
const CONFIRM_FAIL_TIP_SECONDS = 6;
|
||||||
|
|
||||||
function resultText(result: Record<string, unknown> | null | undefined, key: string): string {
|
function resultText(result: Record<string, unknown> | null | undefined, key: string): string {
|
||||||
@@ -284,6 +285,10 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
|||||||
const [giftCount, setGiftCount] = useState(() => savedPositiveInteger(DOUYU_GIFT_COUNT_STORAGE_KEY));
|
const [giftCount, setGiftCount] = useState(() => savedPositiveInteger(DOUYU_GIFT_COUNT_STORAGE_KEY));
|
||||||
const [selectedGoodsId, setSelectedGoodsId] = useState('');
|
const [selectedGoodsId, setSelectedGoodsId] = useState('');
|
||||||
const [accountSearch, setAccountSearch] = useState('');
|
const [accountSearch, setAccountSearch] = useState('');
|
||||||
|
const [layoutMode, setLayoutMode] = useState<'stack' | 'split'>(() => {
|
||||||
|
const v = localStorage.getItem(DOUYU_LAYOUT_MODE_STORAGE_KEY);
|
||||||
|
return v === 'split' ? 'split' : 'stack';
|
||||||
|
});
|
||||||
const accountTableAreaRef = useRef<HTMLDivElement | null>(null);
|
const accountTableAreaRef = useRef<HTMLDivElement | null>(null);
|
||||||
const [accountTableAreaHeight, setAccountTableAreaHeight] = useState(360);
|
const [accountTableAreaHeight, setAccountTableAreaHeight] = useState(360);
|
||||||
const [accountPageSize, setAccountPageSize] = useState<number>(() => {
|
const [accountPageSize, setAccountPageSize] = useState<number>(() => {
|
||||||
@@ -459,6 +464,10 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
|||||||
|
|
||||||
useEffect(() => { loadData(); }, [loadData]);
|
useEffect(() => { loadData(); }, [loadData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem(DOUYU_LAYOUT_MODE_STORAGE_KEY, layoutMode);
|
||||||
|
}, [layoutMode]);
|
||||||
|
|
||||||
// 监听账号表格区域高度变化,动态计算 scroll.y 实现表体内部滚动
|
// 监听账号表格区域高度变化,动态计算 scroll.y 实现表体内部滚动
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const node = accountTableAreaRef.current;
|
const node = accountTableAreaRef.current;
|
||||||
@@ -472,7 +481,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
|||||||
const observer = new ResizeObserver(update);
|
const observer = new ResizeObserver(update);
|
||||||
observer.observe(node);
|
observer.observe(node);
|
||||||
return () => observer.disconnect();
|
return () => observer.disconnect();
|
||||||
}, []);
|
}, [layoutMode]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
localStorage.setItem(DOUYU_GOLD_AMOUNT_STORAGE_KEY, String(goldAmount));
|
localStorage.setItem(DOUYU_GOLD_AMOUNT_STORAGE_KEY, String(goldAmount));
|
||||||
@@ -1236,6 +1245,222 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
|||||||
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
|
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
|
||||||
gap: 8,
|
gap: 8,
|
||||||
};
|
};
|
||||||
|
const operationExtra = (
|
||||||
|
<Space>
|
||||||
|
<Tag color={selectedIds.length ? 'blue' : 'default'}>{selectedText}</Tag>
|
||||||
|
<Space.Compact>
|
||||||
|
<Button size="small" disabled>并发</Button>
|
||||||
|
<InputNumber
|
||||||
|
size="small"
|
||||||
|
min={1}
|
||||||
|
max={10}
|
||||||
|
value={concurrency}
|
||||||
|
onChange={(v) => setConcurrency(v || 1)}
|
||||||
|
style={{ width: 70 }}
|
||||||
|
/>
|
||||||
|
</Space.Compact>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
const operationBody = (
|
||||||
|
<>
|
||||||
|
{isEsportsHandbook ? (
|
||||||
|
<div style={compactOperationGridStyle}>
|
||||||
|
<div style={sectionStyle}>
|
||||||
|
<div style={sectionTitleStyle}>
|
||||||
|
<CreditCardOutlined />
|
||||||
|
<span>开通手册</span>
|
||||||
|
</div>
|
||||||
|
{renderActionButton('create_esports_qr', 'primary')}
|
||||||
|
</div>
|
||||||
|
<div style={sectionStyle}>
|
||||||
|
<div style={sectionTitleStyle}>
|
||||||
|
<ShoppingOutlined />
|
||||||
|
<span>皮肤兑换</span>
|
||||||
|
</div>
|
||||||
|
<Space direction="vertical" style={{ width: '100%' }} size={6}>
|
||||||
|
<Select
|
||||||
|
size="small"
|
||||||
|
value={selectedGoodsId || undefined}
|
||||||
|
onChange={setSelectedGoodsId}
|
||||||
|
options={goods.map((item) => ({ value: item.commodity_id, label: goodsLabel(item) }))}
|
||||||
|
placeholder="选择电竞皮肤"
|
||||||
|
showSearch
|
||||||
|
optionFilterProp="label"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
<div style={actionGridStyle}>
|
||||||
|
{renderActionButton('refresh_esports_goods')}
|
||||||
|
{renderActionButton('exchange_esports_goods', 'primary')}
|
||||||
|
</div>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
<div style={sectionStyle}>
|
||||||
|
<div style={sectionTitleStyle}>
|
||||||
|
<CreditCardOutlined />
|
||||||
|
<span>充值与送礼</span>
|
||||||
|
</div>
|
||||||
|
<Space direction="vertical" style={{ width: '100%' }} size={6}>
|
||||||
|
<Space.Compact style={{ width: '100%' }}>
|
||||||
|
<InputNumber
|
||||||
|
size="small"
|
||||||
|
min={1}
|
||||||
|
value={goldAmount}
|
||||||
|
onChange={(v) => setGoldAmount(v || 1)}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
icon={<CreditCardOutlined />}
|
||||||
|
onClick={() => startTask('create_gold_qr')}
|
||||||
|
disabled={actionDisabled('create_gold_qr')}
|
||||||
|
>
|
||||||
|
充值鱼翅
|
||||||
|
</Button>
|
||||||
|
</Space.Compact>
|
||||||
|
<InputNumber
|
||||||
|
size="small"
|
||||||
|
min={1}
|
||||||
|
value={giftCount}
|
||||||
|
onChange={(v) => setGiftCount(v || 1)}
|
||||||
|
addonAfter="赠送数量"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
<div style={actionGridStyle}>
|
||||||
|
{renderActionButton('donate_esports_chicken_gift', 'primary')}
|
||||||
|
{renderActionButton('donate_esports_firework_gift', 'primary')}
|
||||||
|
</div>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div style={compactOperationGridStyle}>
|
||||||
|
<div style={sectionStyle}>
|
||||||
|
<div style={sectionTitleStyle}>
|
||||||
|
<ShoppingOutlined />
|
||||||
|
<span>兑换</span>
|
||||||
|
</div>
|
||||||
|
<Space direction="vertical" style={{ width: '100%' }} size={6}>
|
||||||
|
<Select
|
||||||
|
size="small"
|
||||||
|
value={selectedGoodsId || undefined}
|
||||||
|
onChange={setSelectedGoodsId}
|
||||||
|
options={goods.map((item) => ({ value: item.commodity_id, label: goodsLabel(item) }))}
|
||||||
|
placeholder="选择兑换商品"
|
||||||
|
showSearch
|
||||||
|
optionFilterProp="label"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
<div style={actionGridStyle}>
|
||||||
|
{renderActionButton('refresh_goods')}
|
||||||
|
{renderActionButton('exchange_goods', 'primary')}
|
||||||
|
</div>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={sectionStyle}>
|
||||||
|
<div style={sectionTitleStyle}>
|
||||||
|
<CreditCardOutlined />
|
||||||
|
<span>开通、充值与送礼</span>
|
||||||
|
</div>
|
||||||
|
<Space direction="vertical" style={{ width: '100%' }} size={6}>
|
||||||
|
{renderActionButton('create_elite_qr', 'primary')}
|
||||||
|
<Space.Compact style={{ width: '100%' }}>
|
||||||
|
<InputNumber
|
||||||
|
size="small"
|
||||||
|
min={1}
|
||||||
|
value={goldAmount}
|
||||||
|
onChange={(v) => setGoldAmount(v || 1)}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
icon={<CreditCardOutlined />}
|
||||||
|
onClick={() => startTask('create_gold_qr')}
|
||||||
|
disabled={actionDisabled('create_gold_qr')}
|
||||||
|
>
|
||||||
|
充值鱼翅
|
||||||
|
</Button>
|
||||||
|
</Space.Compact>
|
||||||
|
<Space.Compact style={{ width: '100%' }}>
|
||||||
|
<InputNumber
|
||||||
|
size="small"
|
||||||
|
min={1}
|
||||||
|
value={giftCount}
|
||||||
|
onChange={(v) => setGiftCount(v || 1)}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
icon={<GiftOutlined />}
|
||||||
|
onClick={() => startTask('donate_elite_gift')}
|
||||||
|
disabled={actionDisabled('donate_elite_gift')}
|
||||||
|
>
|
||||||
|
赠送精英令
|
||||||
|
</Button>
|
||||||
|
</Space.Compact>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||||
|
勾选账号后可批量执行;账号行右键可执行绑定与查询动作。
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
const accountsBody = (
|
||||||
|
<>
|
||||||
|
<Space style={{ marginBottom: 8, width: '100%', justifyContent: 'space-between', flexShrink: 0 }} wrap>
|
||||||
|
<Space>
|
||||||
|
<Input
|
||||||
|
size="small" placeholder="搜索账号/昵称/游戏名" prefix={<SearchOutlined />}
|
||||||
|
value={accountSearch} onChange={(e) => setAccountSearch(e.target.value)}
|
||||||
|
style={{ width: 200 }} allowClear
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
<Space>
|
||||||
|
<Button size="small" icon={<ImportOutlined />} onClick={() => setImportOpen(true)}>导入账号</Button>
|
||||||
|
{selectedIds.length > 0 && (
|
||||||
|
<Button size="small" danger onClick={removeSelected}>移出选中</Button>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
<div ref={accountTableAreaRef} style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }}>
|
||||||
|
<Table
|
||||||
|
rowKey="id"
|
||||||
|
size="small"
|
||||||
|
className="douyu-task-record-table"
|
||||||
|
loading={loading}
|
||||||
|
rowSelection={{
|
||||||
|
selectedRowKeys: selectedIds,
|
||||||
|
onChange: (keys) => setSelectedIds(keys.map(Number)),
|
||||||
|
}}
|
||||||
|
columns={accountColumns}
|
||||||
|
dataSource={filteredAccounts}
|
||||||
|
pagination={{
|
||||||
|
pageSize: accountPageSize,
|
||||||
|
showSizeChanger: true,
|
||||||
|
pageSizeOptions: [10, 20, 50, 100],
|
||||||
|
size: 'small',
|
||||||
|
showLessItems: true,
|
||||||
|
showTotal: (total) => `共 ${total} 条`,
|
||||||
|
onChange: (_page, size) => {
|
||||||
|
if (size !== accountPageSize) {
|
||||||
|
setAccountPageSize(size);
|
||||||
|
localStorage.setItem('douyu_task_account_page_size', String(size));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
tableLayout="fixed"
|
||||||
|
scroll={{ x: 1180, y: Math.max(120, accountTableAreaHeight - 80) }}
|
||||||
|
onRow={(record) => ({
|
||||||
|
onContextMenu: (e) => handleRowContextMenu(record, e),
|
||||||
|
style: { cursor: 'context-menu' },
|
||||||
|
title: '右键打开账号动作',
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
// 任务记录表:表头固定、表体内部滚动、分页器固定显示在底部
|
// 任务记录表:表头固定、表体内部滚动、分页器固定显示在底部
|
||||||
const taskRecordTableStyle = `
|
const taskRecordTableStyle = `
|
||||||
.douyu-task-record-table {
|
.douyu-task-record-table {
|
||||||
@@ -1304,237 +1529,60 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
|||||||
<Text type="secondary">{handbookDescription}</Text>
|
<Text type="secondary">{handbookDescription}</Text>
|
||||||
</div>
|
</div>
|
||||||
<Space>
|
<Space>
|
||||||
|
<Tooltip title={layoutMode === 'split' ? '切换为上下布局' : '切换为左右布局'}>
|
||||||
|
<Button
|
||||||
|
icon={<ColumnWidthOutlined />}
|
||||||
|
onClick={() => setLayoutMode((m) => (m === 'split' ? 'stack' : 'split'))}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
<Button icon={<ReloadOutlined />} onClick={loadData} loading={loading}>刷新</Button>
|
<Button icon={<ReloadOutlined />} onClick={loadData} loading={loading}>刷新</Button>
|
||||||
{canConfig && <Button icon={<SettingOutlined />} onClick={() => setConfigOpen(true)}>配置</Button>}
|
{canConfig && <Button icon={<SettingOutlined />} onClick={() => setConfigOpen(true)}>配置</Button>}
|
||||||
{(activeBatchIds.size > 0 || runningBatchIds.size > 0) && <Button danger icon={<StopOutlined />} onClick={stopBatch}>停止</Button>}
|
{(activeBatchIds.size > 0 || runningBatchIds.size > 0) && <Button danger icon={<StopOutlined />} onClick={stopBatch}>停止</Button>}
|
||||||
</Space>
|
</Space>
|
||||||
</Space>
|
</Space>
|
||||||
|
|
||||||
{/* 操作工具栏 */}
|
{/* 操作工具栏 + 账号表格(支持上下/左右布局切换) */}
|
||||||
<Card
|
{layoutMode === 'split' ? (
|
||||||
size="small"
|
<div style={{ flex: 1, minHeight: 0, display: 'flex', gap: 12, overflow: 'hidden' }}>
|
||||||
title={isEsportsHandbook ? '电竞手册操作' : '精英宝典操作'}
|
<Card
|
||||||
extra={
|
size="small" title="账号"
|
||||||
<Space>
|
extra={<Tag color="blue">已选 {selectedIds.length}/{accounts.length}</Tag>}
|
||||||
<Tag color={selectedIds.length ? 'blue' : 'default'}>{selectedText}</Tag>
|
style={{ flex: 1, minWidth: 0, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}
|
||||||
<Space.Compact>
|
styles={{ body: { flex: 1, minHeight: 0, padding: '4px 6px', overflow: 'hidden', display: 'flex', flexDirection: 'column' } }}
|
||||||
<Button size="small" disabled>并发</Button>
|
>
|
||||||
<InputNumber
|
{accountsBody}
|
||||||
size="small"
|
</Card>
|
||||||
min={1}
|
<Card
|
||||||
max={10}
|
|
||||||
value={concurrency}
|
|
||||||
onChange={(v) => setConcurrency(v || 1)}
|
|
||||||
style={{ width: 70 }}
|
|
||||||
/>
|
|
||||||
</Space.Compact>
|
|
||||||
</Space>
|
|
||||||
}
|
|
||||||
style={{ flexShrink: 0, marginBottom: 12 }}
|
|
||||||
styles={{ body: { padding: 8 } }}
|
|
||||||
>
|
|
||||||
{isEsportsHandbook ? (
|
|
||||||
<div style={compactOperationGridStyle}>
|
|
||||||
<div style={sectionStyle}>
|
|
||||||
<div style={sectionTitleStyle}>
|
|
||||||
<CreditCardOutlined />
|
|
||||||
<span>开通手册</span>
|
|
||||||
</div>
|
|
||||||
{renderActionButton('create_esports_qr', 'primary')}
|
|
||||||
</div>
|
|
||||||
<div style={sectionStyle}>
|
|
||||||
<div style={sectionTitleStyle}>
|
|
||||||
<ShoppingOutlined />
|
|
||||||
<span>皮肤兑换</span>
|
|
||||||
</div>
|
|
||||||
<Space direction="vertical" style={{ width: '100%' }} size={6}>
|
|
||||||
<Select
|
|
||||||
size="small"
|
|
||||||
value={selectedGoodsId || undefined}
|
|
||||||
onChange={setSelectedGoodsId}
|
|
||||||
options={goods.map((item) => ({ value: item.commodity_id, label: goodsLabel(item) }))}
|
|
||||||
placeholder="选择电竞皮肤"
|
|
||||||
showSearch
|
|
||||||
optionFilterProp="label"
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
/>
|
|
||||||
<div style={actionGridStyle}>
|
|
||||||
{renderActionButton('refresh_esports_goods')}
|
|
||||||
{renderActionButton('exchange_esports_goods', 'primary')}
|
|
||||||
</div>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
<div style={sectionStyle}>
|
|
||||||
<div style={sectionTitleStyle}>
|
|
||||||
<CreditCardOutlined />
|
|
||||||
<span>充值与送礼</span>
|
|
||||||
</div>
|
|
||||||
<Space direction="vertical" style={{ width: '100%' }} size={6}>
|
|
||||||
<Space.Compact style={{ width: '100%' }}>
|
|
||||||
<InputNumber
|
|
||||||
size="small"
|
|
||||||
min={1}
|
|
||||||
value={goldAmount}
|
|
||||||
onChange={(v) => setGoldAmount(v || 1)}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
icon={<CreditCardOutlined />}
|
|
||||||
onClick={() => startTask('create_gold_qr')}
|
|
||||||
disabled={actionDisabled('create_gold_qr')}
|
|
||||||
>
|
|
||||||
充值鱼翅
|
|
||||||
</Button>
|
|
||||||
</Space.Compact>
|
|
||||||
<InputNumber
|
|
||||||
size="small"
|
|
||||||
min={1}
|
|
||||||
value={giftCount}
|
|
||||||
onChange={(v) => setGiftCount(v || 1)}
|
|
||||||
addonAfter="赠送数量"
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
/>
|
|
||||||
<div style={actionGridStyle}>
|
|
||||||
{renderActionButton('donate_esports_chicken_gift', 'primary')}
|
|
||||||
{renderActionButton('donate_esports_firework_gift', 'primary')}
|
|
||||||
</div>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div style={compactOperationGridStyle}>
|
|
||||||
<div style={sectionStyle}>
|
|
||||||
<div style={sectionTitleStyle}>
|
|
||||||
<ShoppingOutlined />
|
|
||||||
<span>兑换</span>
|
|
||||||
</div>
|
|
||||||
<Space direction="vertical" style={{ width: '100%' }} size={6}>
|
|
||||||
<Select
|
|
||||||
size="small"
|
|
||||||
value={selectedGoodsId || undefined}
|
|
||||||
onChange={setSelectedGoodsId}
|
|
||||||
options={goods.map((item) => ({ value: item.commodity_id, label: goodsLabel(item) }))}
|
|
||||||
placeholder="选择兑换商品"
|
|
||||||
showSearch
|
|
||||||
optionFilterProp="label"
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
/>
|
|
||||||
<div style={actionGridStyle}>
|
|
||||||
{renderActionButton('refresh_goods')}
|
|
||||||
{renderActionButton('exchange_goods', 'primary')}
|
|
||||||
</div>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style={sectionStyle}>
|
|
||||||
<div style={sectionTitleStyle}>
|
|
||||||
<CreditCardOutlined />
|
|
||||||
<span>开通、充值与送礼</span>
|
|
||||||
</div>
|
|
||||||
<Space direction="vertical" style={{ width: '100%' }} size={6}>
|
|
||||||
{renderActionButton('create_elite_qr', 'primary')}
|
|
||||||
<Space.Compact style={{ width: '100%' }}>
|
|
||||||
<InputNumber
|
|
||||||
size="small"
|
|
||||||
min={1}
|
|
||||||
value={goldAmount}
|
|
||||||
onChange={(v) => setGoldAmount(v || 1)}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
icon={<CreditCardOutlined />}
|
|
||||||
onClick={() => startTask('create_gold_qr')}
|
|
||||||
disabled={actionDisabled('create_gold_qr')}
|
|
||||||
>
|
|
||||||
充值鱼翅
|
|
||||||
</Button>
|
|
||||||
</Space.Compact>
|
|
||||||
<Space.Compact style={{ width: '100%' }}>
|
|
||||||
<InputNumber
|
|
||||||
size="small"
|
|
||||||
min={1}
|
|
||||||
value={giftCount}
|
|
||||||
onChange={(v) => setGiftCount(v || 1)}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
icon={<GiftOutlined />}
|
|
||||||
onClick={() => startTask('donate_elite_gift')}
|
|
||||||
disabled={actionDisabled('donate_elite_gift')}
|
|
||||||
>
|
|
||||||
赠送精英令
|
|
||||||
</Button>
|
|
||||||
</Space.Compact>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
|
||||||
勾选账号后可批量执行;账号行右键可执行绑定与查询动作。
|
|
||||||
</Text>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* 账号 */}
|
|
||||||
<Card
|
|
||||||
size="small" title="账号"
|
|
||||||
extra={<Tag color="blue">已选 {selectedIds.length}/{accounts.length}</Tag>}
|
|
||||||
style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}
|
|
||||||
styles={{ body: { flex: 1, minHeight: 0, padding: '4px 6px', overflow: 'hidden', display: 'flex', flexDirection: 'column' } }}
|
|
||||||
>
|
|
||||||
<Space style={{ marginBottom: 8, width: '100%', justifyContent: 'space-between', flexShrink: 0 }} wrap>
|
|
||||||
<Space>
|
|
||||||
<Input
|
|
||||||
size="small" placeholder="搜索账号/昵称/游戏名" prefix={<SearchOutlined />}
|
|
||||||
value={accountSearch} onChange={(e) => setAccountSearch(e.target.value)}
|
|
||||||
style={{ width: 200 }} allowClear
|
|
||||||
/>
|
|
||||||
</Space>
|
|
||||||
<Space>
|
|
||||||
<Button size="small" icon={<ImportOutlined />} onClick={() => setImportOpen(true)}>导入账号</Button>
|
|
||||||
{selectedIds.length > 0 && (
|
|
||||||
<Button size="small" danger onClick={removeSelected}>移出选中</Button>
|
|
||||||
)}
|
|
||||||
</Space>
|
|
||||||
</Space>
|
|
||||||
<div ref={accountTableAreaRef} style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }}>
|
|
||||||
<Table
|
|
||||||
rowKey="id"
|
|
||||||
size="small"
|
size="small"
|
||||||
className="douyu-task-record-table"
|
title={isEsportsHandbook ? '电竞手册操作' : '精英宝典操作'}
|
||||||
loading={loading}
|
extra={operationExtra}
|
||||||
rowSelection={{
|
style={{ width: 340, flexShrink: 0, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}
|
||||||
selectedRowKeys: selectedIds,
|
styles={{ body: { padding: 8, flex: 1, minHeight: 0, overflowY: 'auto' } }}
|
||||||
onChange: (keys) => setSelectedIds(keys.map(Number)),
|
>
|
||||||
}}
|
{operationBody}
|
||||||
columns={accountColumns}
|
</Card>
|
||||||
dataSource={filteredAccounts}
|
|
||||||
pagination={{
|
|
||||||
pageSize: accountPageSize,
|
|
||||||
showSizeChanger: true,
|
|
||||||
pageSizeOptions: [10, 20, 50, 100],
|
|
||||||
size: 'small',
|
|
||||||
showLessItems: true,
|
|
||||||
showTotal: (total) => `共 ${total} 条`,
|
|
||||||
onChange: (_page, size) => {
|
|
||||||
if (size !== accountPageSize) {
|
|
||||||
setAccountPageSize(size);
|
|
||||||
localStorage.setItem('douyu_task_account_page_size', String(size));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
tableLayout="fixed"
|
|
||||||
scroll={{ x: 1180, y: Math.max(120, accountTableAreaHeight - 80) }}
|
|
||||||
onRow={(record) => ({
|
|
||||||
onContextMenu: (e) => handleRowContextMenu(record, e),
|
|
||||||
style: { cursor: 'context-menu' },
|
|
||||||
title: '右键打开账号动作',
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
) : (
|
||||||
|
<>
|
||||||
|
<Card
|
||||||
|
size="small"
|
||||||
|
title={isEsportsHandbook ? '电竞手册操作' : '精英宝典操作'}
|
||||||
|
extra={operationExtra}
|
||||||
|
style={{ flexShrink: 0, marginBottom: 12 }}
|
||||||
|
styles={{ body: { padding: 8 } }}
|
||||||
|
>
|
||||||
|
{operationBody}
|
||||||
|
</Card>
|
||||||
|
<Card
|
||||||
|
size="small" title="账号"
|
||||||
|
extra={<Tag color="blue">已选 {selectedIds.length}/{accounts.length}</Tag>}
|
||||||
|
style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}
|
||||||
|
styles={{ body: { flex: 1, minHeight: 0, padding: '4px 6px', overflow: 'hidden', display: 'flex', flexDirection: 'column' } }}
|
||||||
|
>
|
||||||
|
{accountsBody}
|
||||||
|
</Card>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 右键菜单 */}
|
{/* 右键菜单 */}
|
||||||
{contextMenu && (
|
{contextMenu && (
|
||||||
|
|||||||
Reference in New Issue
Block a user