修复限制兑换显示文案
This commit is contained in:
@@ -15,7 +15,6 @@ import {
|
||||
type DouyuTaskAccountItem,
|
||||
type DouyuTaskItem,
|
||||
} from '../api/modules';
|
||||
import RealtimeLogPanel from '../components/RealtimeLogPanel';
|
||||
import { usePermissions } from '../hooks/usePermissions';
|
||||
import { useWebSocketLogs } from '../hooks/useWebSocketLogs';
|
||||
import { formatTime } from '../utils/time';
|
||||
@@ -53,8 +52,6 @@ const ESPORTS_QUICK_ACTIONS = [
|
||||
{ key: 'donate_esports_firework_gift', icon: <GiftOutlined /> },
|
||||
];
|
||||
|
||||
const ELITE_BIND_ACTION_KEYS = ['get_bind_qr', 'confirm_bind', 'query_game_name', 'query_change_bind_time'];
|
||||
const ELITE_QUERY_ACTION_KEYS = ['query_points', 'query_limited_goods', 'query_gold_balance'];
|
||||
const ELITE_TASK_TYPES = new Set([
|
||||
'get_bind_qr',
|
||||
'confirm_bind',
|
||||
@@ -98,26 +95,6 @@ const STATUS_LABELS: Record<string, string> = {
|
||||
success: '成功', failed: '失败', error: '异常', stopped: '已停止',
|
||||
};
|
||||
|
||||
const BIND_STATUS_LABELS: Record<string, string> = {
|
||||
bind_qr_generated: '待扫码',
|
||||
game_queried: '已识别角色',
|
||||
game_not_bound: '未绑定',
|
||||
bind_confirmed: '已绑定',
|
||||
esports_bind_qr_generated: '电竞待绑定',
|
||||
esports_role_queried: '已查电竞角色',
|
||||
esports_bind_ready: '电竞待确认',
|
||||
esports_role_switching: '电竞换角色',
|
||||
esports_bind_confirming: '电竞绑定中',
|
||||
esports_bound: '电竞已绑定',
|
||||
esports_qr_created: '电竞待开通',
|
||||
esports_opened: '电竞已开通',
|
||||
esports_points_queried: '已查电竞积分',
|
||||
esports_gift_donated: '已赠电竞礼物',
|
||||
esports_goods_refreshed: '已刷电竞皮肤',
|
||||
esports_goods_exchanged: '已兑电竞皮肤',
|
||||
change_time_queried: '已查换绑',
|
||||
};
|
||||
|
||||
function resultText(result: Record<string, unknown> | null | undefined, key: string): string {
|
||||
const value = result?.[key];
|
||||
return typeof value === 'string' ? value : '';
|
||||
@@ -224,6 +201,26 @@ function resultNumber(result: Record<string, unknown> | null | undefined, key: s
|
||||
return null;
|
||||
}
|
||||
|
||||
function limitedGoodsName(item: unknown): string {
|
||||
if (!item || typeof item !== 'object') return '';
|
||||
const record = item as Record<string, unknown>;
|
||||
const value = record.commodityName ?? record.name;
|
||||
return typeof value === 'string' ? value : '';
|
||||
}
|
||||
|
||||
function limitedGoodsTaskText(task: DouyuTaskItem | null | undefined): string {
|
||||
if (!task) return '';
|
||||
if (task.message?.trim()) return task.message.trim();
|
||||
const count = resultNumber(task.result, 'limited_count');
|
||||
if (count == null) return '';
|
||||
if (count <= 0) return '无限制商品';
|
||||
const goods = task.result?.limited_goods;
|
||||
const names = Array.isArray(goods)
|
||||
? goods.map(limitedGoodsName).filter(Boolean).slice(0, 5)
|
||||
: [];
|
||||
return names.length ? `限兑 ${count} 个: ${names.join(', ')}` : `限兑 ${count} 个`;
|
||||
}
|
||||
|
||||
function savedPositiveInteger(key: string, fallback = 1): number {
|
||||
const value = Number(localStorage.getItem(key));
|
||||
return Number.isInteger(value) && value >= 1 ? value : fallback;
|
||||
@@ -322,6 +319,17 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
return map;
|
||||
}, [isEsportsHandbook, tasks]);
|
||||
|
||||
const latestLimitedGoodsTaskByAccount = useMemo(() => {
|
||||
const map = new Map<number, DouyuTaskItem>();
|
||||
for (const task of tasks) {
|
||||
if (task.task_type !== 'query_limited_goods') continue;
|
||||
if (!['success', 'failed', 'error'].includes(task.status)) continue;
|
||||
const previous = map.get(task.account_id);
|
||||
if (!previous || task.id > previous.id) map.set(task.account_id, task);
|
||||
}
|
||||
return map;
|
||||
}, [tasks]);
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
@@ -355,7 +363,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [canConfig]);
|
||||
}, [canConfig, isEsportsHandbook]);
|
||||
|
||||
const loadTasks = useCallback(async () => {
|
||||
try {
|
||||
@@ -779,6 +787,10 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
|
||||
// Account table
|
||||
const accountColumns: TableProps<DouyuTaskAccountItem>['columns'] = [
|
||||
{
|
||||
title: '#', width: 45,
|
||||
render: (_, __, index) => <Text type="secondary">{index + 1}</Text>,
|
||||
},
|
||||
{
|
||||
title: '账号', dataIndex: 'username', width: 150,
|
||||
render: (_, record) => (
|
||||
@@ -822,6 +834,20 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
title: '鱼翅', dataIndex: 'gold_balance', width: 70,
|
||||
render: (v: number | null) => v ?? <Text type="secondary">-</Text>,
|
||||
},
|
||||
{
|
||||
title: '限制兑换', dataIndex: 'exchange_balance', width: 150, ellipsis: true,
|
||||
render: (_, record) => {
|
||||
const latestLimitedTask = latestLimitedGoodsTaskByAccount.get(record.id);
|
||||
const text = limitedGoodsTaskText(latestLimitedTask);
|
||||
if (!text) return <Text type="secondary">未查</Text>;
|
||||
if (latestLimitedTask?.status !== 'success') {
|
||||
return <Text type="secondary">{text}</Text>;
|
||||
}
|
||||
const count = resultNumber(latestLimitedTask.result, 'limited_count');
|
||||
if (count === 0 || text === '无限制商品') return <Tag color="green">无限制商品</Tag>;
|
||||
return <Text title={text}>{text}</Text>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '换绑时间', dataIndex: 'change_role_wait_time', width: 130,
|
||||
render: (_, record) => {
|
||||
@@ -874,30 +900,16 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
return aw - bw;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态', dataIndex: 'bind_status', width: 100,
|
||||
render: (_, record) => {
|
||||
const value = isEsportsHandbook ? record.esports_bind_status : record.bind_status;
|
||||
return value
|
||||
? <Tag>{BIND_STATUS_LABELS[value] || value}</Tag>
|
||||
: <Text type="secondary">-</Text>;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// Task table
|
||||
// 任务记录表
|
||||
const taskColumns: TableProps<DouyuTaskItem>['columns'] = [
|
||||
{
|
||||
title: '账号', dataIndex: 'account_username', width: 120,
|
||||
title: '账号', dataIndex: 'account_username', width: 130,
|
||||
render: (_, r) => r.account_nickname || r.account_username || r.account_uid,
|
||||
},
|
||||
{ title: '任务', dataIndex: 'task_type', width: 140, render: (v) => taskTypes[v] || v },
|
||||
{
|
||||
title: '状态', dataIndex: 'status', width: 80,
|
||||
render: (v) => <Tag color={STATUS_COLORS[v]}>{STATUS_LABELS[v] || v}</Tag>,
|
||||
},
|
||||
{
|
||||
title: '结果', dataIndex: 'message', ellipsis: true,
|
||||
title: '结果', dataIndex: 'message', width: 300, ellipsis: true,
|
||||
render: (_, r) => {
|
||||
if (['prepare_esports_bind', 'get_esports_bind_qr'].includes(r.task_type)) {
|
||||
const roleName = resultText(r.result, 'role_name');
|
||||
@@ -990,8 +1002,13 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
);
|
||||
},
|
||||
},
|
||||
{ title: '任务', dataIndex: 'task_type', width: 170, render: (v) => taskTypes[v] || v },
|
||||
{
|
||||
title: '时间', dataIndex: 'finished_at', width: 150,
|
||||
title: '状态', dataIndex: 'status', width: 80,
|
||||
render: (v) => <Tag color={STATUS_COLORS[v]}>{STATUS_LABELS[v] || v}</Tag>,
|
||||
},
|
||||
{
|
||||
title: '时间', dataIndex: 'finished_at', width: 170,
|
||||
render: (_, r) => formatTime(r.finished_at || r.created_at),
|
||||
},
|
||||
];
|
||||
@@ -1059,10 +1076,73 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
gridTemplateColumns: '1fr 1fr',
|
||||
gap: 6,
|
||||
};
|
||||
const compactOperationGridStyle = {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
|
||||
gap: 8,
|
||||
};
|
||||
// 任务记录表:表头固定、表体内部滚动、分页器固定显示在底部
|
||||
const taskRecordTableStyle = `
|
||||
.douyu-task-record-table {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.douyu-task-record-table .ant-spin-nested-loading,
|
||||
.douyu-task-record-table .ant-spin-container {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.douyu-task-record-table .ant-table {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.douyu-task-record-table .ant-table-container {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.douyu-task-record-table .ant-table-header {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.douyu-task-record-table .ant-table-body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: auto !important;
|
||||
}
|
||||
.douyu-task-record-table .ant-table-thead > tr > th,
|
||||
.douyu-task-record-table .ant-table-tbody > tr > td {
|
||||
padding: 4px 8px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.douyu-task-record-table .ant-typography {
|
||||
font-size: 12px;
|
||||
}
|
||||
.douyu-task-record-table .ant-tag {
|
||||
font-size: 11px;
|
||||
line-height: 18px;
|
||||
margin-inline-end: 0;
|
||||
padding-inline: 5px;
|
||||
}
|
||||
.douyu-task-record-table .ant-pagination {
|
||||
flex-shrink: 0;
|
||||
margin: 0;
|
||||
padding: 6px 4px 2px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
`;
|
||||
|
||||
return (
|
||||
<div style={{ height: '100%', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
|
||||
{/* Header */}
|
||||
<style>{taskRecordTableStyle}</style>
|
||||
{/* 头部 */}
|
||||
<Space style={{ justifyContent: 'space-between', width: '100%', marginBottom: 8, flexShrink: 0 }}>
|
||||
<div>
|
||||
<h2 style={{ margin: 0 }}>{handbookTitle}</h2>
|
||||
@@ -1075,19 +1155,19 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
</Space>
|
||||
</Space>
|
||||
|
||||
{/* Main grid */}
|
||||
{/* 主布局 */}
|
||||
<div style={{
|
||||
flex: 1, minHeight: 0,
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'minmax(0, 1fr) minmax(320px, 380px)',
|
||||
gridTemplateColumns: 'minmax(0, 1fr) minmax(520px, 560px)',
|
||||
gap: 12, overflow: 'hidden',
|
||||
}}>
|
||||
{/* Left: accounts + tasks */}
|
||||
{/* 左侧账号 */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, minHeight: 0, overflow: 'hidden' }}>
|
||||
<Card
|
||||
size="small" title="账号"
|
||||
extra={<Tag color="blue">已选 {selectedIds.length}/{accounts.length}</Tag>}
|
||||
style={{ flex: '1 1 0', minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}
|
||||
style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}
|
||||
styles={{ body: { flex: 1, minHeight: 0, overflow: 'hidden', padding: 8, display: 'flex', flexDirection: 'column' } }}
|
||||
>
|
||||
<Space style={{ marginBottom: 8, width: '100%', justifyContent: 'space-between', flexShrink: 0 }} wrap>
|
||||
@@ -1108,7 +1188,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
<Table
|
||||
rowKey="id"
|
||||
size="small"
|
||||
style={{ flex: 1, minHeight: 0 }}
|
||||
className="douyu-task-record-table"
|
||||
loading={loading}
|
||||
rowSelection={{
|
||||
selectedRowKeys: selectedIds,
|
||||
@@ -1116,8 +1196,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
}}
|
||||
columns={accountColumns}
|
||||
dataSource={filteredAccounts}
|
||||
pagination={{ pageSize: 15, showSizeChanger: false }}
|
||||
scroll={{ y: 'max(180px, calc(100vh - 650px))' }}
|
||||
pagination={{ pageSize: 15, showSizeChanger: false, size: 'small', showLessItems: true }}
|
||||
onRow={(record) => ({
|
||||
onContextMenu: (e) => handleRowContextMenu(record, e),
|
||||
style: { cursor: 'context-menu' },
|
||||
@@ -1125,7 +1204,10 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
})}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 右侧:任务记录(弹性高度) + 操作区(自适应) */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, minHeight: 0, overflow: 'hidden' }}>
|
||||
<Card
|
||||
size="small" title="任务记录"
|
||||
extra={
|
||||
@@ -1134,25 +1216,31 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
<Tag color="error">失败 {visibleTasks.filter((t) => ['failed', 'error'].includes(t.status)).length}</Tag>
|
||||
</Space>
|
||||
}
|
||||
style={{ flex: '0 0 300px', minHeight: 260, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}
|
||||
styles={{ body: { flex: 1, minHeight: 0, padding: 8, overflow: 'hidden', display: 'flex', flexDirection: 'column' } }}
|
||||
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' } }}
|
||||
>
|
||||
<Table
|
||||
rowKey="id" size="small" columns={taskColumns} dataSource={visibleTasks}
|
||||
style={{ flex: 1, minHeight: 0 }}
|
||||
loading={loading} pagination={{ pageSize: 4, showSizeChanger: false, size: 'small' }}
|
||||
scroll={{ x: 700, y: 150 }}
|
||||
className="douyu-task-record-table"
|
||||
loading={loading}
|
||||
pagination={{
|
||||
pageSize: 15,
|
||||
showSizeChanger: false,
|
||||
size: 'small',
|
||||
showLessItems: true,
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
}}
|
||||
tableLayout="fixed"
|
||||
scroll={{ x: 850 }}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Right: operations + logs */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, overflow: 'auto' }}>
|
||||
<Card
|
||||
size="small"
|
||||
title={isEsportsHandbook ? '电竞手册操作' : '精英宝典操作'}
|
||||
extra={<Tag color={selectedIds.length ? 'blue' : 'default'}>{selectedText}</Tag>}
|
||||
styles={{ body: { padding: 8 } }}
|
||||
style={{ flexShrink: 0, display: 'flex', flexDirection: 'column' }}
|
||||
styles={{ body: { padding: 8, maxHeight: 260, overflow: 'auto' } }}
|
||||
>
|
||||
<Space direction="vertical" style={{ width: '100%' }} size={8}>
|
||||
<Space.Compact style={{ width: '100%' }}>
|
||||
@@ -1168,17 +1256,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
</Space.Compact>
|
||||
|
||||
{isEsportsHandbook ? (
|
||||
<>
|
||||
<div style={sectionStyle}>
|
||||
<div style={sectionTitleStyle}>
|
||||
<QrcodeOutlined />
|
||||
<span>角色绑定</span>
|
||||
</div>
|
||||
<div style={actionGridStyle}>
|
||||
{renderActionButton('prepare_esports_bind', 'primary')}
|
||||
{renderActionButton('query_esports_game_name')}
|
||||
</div>
|
||||
</div>
|
||||
<div style={compactOperationGridStyle}>
|
||||
<div style={sectionStyle}>
|
||||
<div style={sectionTitleStyle}>
|
||||
<CreditCardOutlined />
|
||||
@@ -1186,16 +1264,6 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
</div>
|
||||
{renderActionButton('create_esports_qr', 'primary')}
|
||||
</div>
|
||||
<div style={sectionStyle}>
|
||||
<div style={sectionTitleStyle}>
|
||||
<SearchOutlined />
|
||||
<span>查询</span>
|
||||
</div>
|
||||
<div style={actionGridStyle}>
|
||||
{renderActionButton('query_esports_points')}
|
||||
{renderActionButton('query_gold_balance')}
|
||||
</div>
|
||||
</div>
|
||||
<div style={sectionStyle}>
|
||||
<div style={sectionTitleStyle}>
|
||||
<ShoppingOutlined />
|
||||
@@ -1255,29 +1323,9 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
</div>
|
||||
</Space>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div style={sectionStyle}>
|
||||
<div style={sectionTitleStyle}>
|
||||
<QrcodeOutlined />
|
||||
<span>绑定与角色</span>
|
||||
</div>
|
||||
<div style={actionGridStyle}>
|
||||
{ELITE_BIND_ACTION_KEYS.map((key) => renderActionButton(key))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={sectionStyle}>
|
||||
<div style={sectionTitleStyle}>
|
||||
<SearchOutlined />
|
||||
<span>查询</span>
|
||||
</div>
|
||||
<div style={actionGridStyle}>
|
||||
{ELITE_QUERY_ACTION_KEYS.map((key) => renderActionButton(key))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={compactOperationGridStyle}>
|
||||
<div style={sectionStyle}>
|
||||
<div style={sectionTitleStyle}>
|
||||
<ShoppingOutlined />
|
||||
@@ -1344,19 +1392,17 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
</Space.Compact>
|
||||
</Space>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
勾选账号后可批量执行;右键账号行可快速执行单账号动作。
|
||||
勾选账号后可批量执行;账号行右键可执行绑定与查询动作。
|
||||
</Text>
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
<RealtimeLogPanel logs={logs.logs} connected={logs.connected} height={200} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Context menu */}
|
||||
{/* 右键菜单 */}
|
||||
{contextMenu && (
|
||||
<div style={contextMenuStyle} onClick={(e) => e.stopPropagation()}>
|
||||
<Card
|
||||
|
||||
Reference in New Issue
Block a user