feat(douyu): support elite handbook lock orders
This commit is contained in:
@@ -63,6 +63,7 @@ const ELITE_QUICK_ACTIONS = [
|
||||
{ key: 'query_change_bind_time', icon: <FieldTimeOutlined /> },
|
||||
{ key: 'query_limited_goods', icon: <SearchOutlined /> },
|
||||
{ key: 'query_gold_balance', icon: <SearchOutlined /> },
|
||||
{ key: 'lock_goods', icon: <ShoppingOutlined /> },
|
||||
{ key: 'exchange_goods', icon: <ShoppingOutlined /> },
|
||||
{ key: 'create_gold_qr', icon: <CreditCardOutlined /> },
|
||||
{ key: 'donate_elite_gift', icon: <GiftOutlined /> },
|
||||
@@ -102,6 +103,8 @@ const ELITE_TASK_TYPES = new Set([
|
||||
'create_gold_qr',
|
||||
'donate_elite_gift',
|
||||
'query_points',
|
||||
'lock_goods',
|
||||
'pay_locked_order',
|
||||
'exchange_goods',
|
||||
'query_game_name',
|
||||
'query_change_bind_time',
|
||||
@@ -150,6 +153,12 @@ function resultText(result: Record<string, unknown> | null | undefined, key: str
|
||||
return typeof value === 'string' ? value : '';
|
||||
}
|
||||
|
||||
function taskPayloadText(task: DouyuTaskItem, key: string): string {
|
||||
const payload = task.result?.payload;
|
||||
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return '';
|
||||
return resultText(payload as Record<string, unknown>, key);
|
||||
}
|
||||
|
||||
function resultFlag(result: Record<string, unknown> | null | undefined, key: string): boolean {
|
||||
const value = result?.[key];
|
||||
if (value === true) return true;
|
||||
@@ -419,6 +428,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
const [confirmFailCountdown, setConfirmFailCountdown] = useState(0);
|
||||
const [exchangePreview, setExchangePreview] = useState<{ task: DouyuTaskItem; url: string } | null>(null);
|
||||
const [xpdPurchaseRecordsAccountId, setXpdPurchaseRecordsAccountId] = useState<number | null>(null);
|
||||
const [nowSeconds, setNowSeconds] = useState(0);
|
||||
const locallyStartedBatchIds = useRef<Set<string>>(new Set());
|
||||
const autoOpenQrReady = useRef(false);
|
||||
const autoOpenEsportsBindReady = useRef(false);
|
||||
@@ -426,6 +436,12 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
|
||||
const logs = useWebSocketLogs();
|
||||
const canConfig = can('douyu:config');
|
||||
useEffect(() => {
|
||||
const updateNow = () => setNowSeconds(Math.floor(Date.now() / 1000));
|
||||
updateNow();
|
||||
const timer = window.setInterval(updateNow, 1000);
|
||||
return () => window.clearInterval(timer);
|
||||
}, []);
|
||||
const latestQueryGameTaskByAccount = useMemo(() => {
|
||||
const map = new Map<number, DouyuTaskItem>();
|
||||
for (const task of tasks) {
|
||||
@@ -568,6 +584,24 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
return map;
|
||||
}, [visibleTasks]);
|
||||
|
||||
const latestLockedTaskByAccount = useMemo(() => {
|
||||
const paidOrders = new Set(
|
||||
visibleTasks
|
||||
.filter((task) => task.task_type === 'pay_locked_order' && task.status === 'success')
|
||||
.map((task) => `${task.account_id}:${resultText(task.result, 'order_id')}`)
|
||||
.filter(Boolean),
|
||||
);
|
||||
const map = new Map<number, DouyuTaskItem>();
|
||||
for (const task of visibleTasks) {
|
||||
if (task.task_type !== 'lock_goods' || task.status !== 'success') continue;
|
||||
const orderId = resultText(task.result, 'order_id');
|
||||
if (!orderId || paidOrders.has(`${task.account_id}:${orderId}`)) continue;
|
||||
const previous = map.get(task.account_id);
|
||||
if (!previous || task.id > previous.id) map.set(task.account_id, task);
|
||||
}
|
||||
return map;
|
||||
}, [visibleTasks]);
|
||||
|
||||
const latestXpdPurchaseRecordsTaskByAccount = useMemo(() => {
|
||||
const map = new Map<number, DouyuTaskItem>();
|
||||
for (const task of tasks) {
|
||||
@@ -958,7 +992,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
return;
|
||||
}
|
||||
// 多批次并发:不再拦截,允许在已有批次运行时继续创建新批次
|
||||
if (['exchange_goods', 'exchange_esports_goods', 'exchange_xpd_goods'].includes(taskType) && !selectedGoodsId) {
|
||||
if (['lock_goods', 'exchange_goods', 'exchange_esports_goods', 'exchange_xpd_goods'].includes(taskType) && !selectedGoodsId) {
|
||||
message.warning('请先选择兑换商品');
|
||||
return;
|
||||
}
|
||||
@@ -973,7 +1007,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
}
|
||||
}
|
||||
const payload: Record<string, unknown> = { ...extraPayload };
|
||||
if (['exchange_goods', 'exchange_esports_goods', 'exchange_xpd_goods'].includes(taskType)) {
|
||||
if (['lock_goods', 'exchange_goods', 'exchange_esports_goods', 'exchange_xpd_goods'].includes(taskType)) {
|
||||
payload.commodity_id = selectedGoodsId;
|
||||
}
|
||||
if (taskType === 'exchange_xpd_goods') payload.pay_type = xpdPayType;
|
||||
@@ -1624,6 +1658,40 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
);
|
||||
},
|
||||
}] : []),
|
||||
...(!isPeaceHandbook && !isEsportsHandbook ? [{
|
||||
title: '锁单', width: 150, align: 'center' as const,
|
||||
render: (_: unknown, record: DouyuTaskAccountItem) => {
|
||||
const task = latestLockedTaskByAccount.get(record.id);
|
||||
if (!task) return <Text type="secondary">-</Text>;
|
||||
const orderId = resultText(task.result, 'order_id');
|
||||
const commodityId = resultText(task.result, 'commodity_id');
|
||||
const expiresAt = resultNumber(task.result, 'expires_at');
|
||||
const expired = expiresAt != null && nowSeconds > 0 && expiresAt <= nowSeconds;
|
||||
const paymentTask = visibleTasks.find((item) => (
|
||||
item.account_id === record.id
|
||||
&& item.task_type === 'pay_locked_order'
|
||||
&& taskPayloadText(item, 'order_id') === orderId
|
||||
&& ['planned', 'pending', 'running'].includes(item.status)
|
||||
));
|
||||
return (
|
||||
<Space direction="vertical" size={1}>
|
||||
<Text style={{ fontSize: 12 }}>{orderId}</Text>
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
disabled={expired || !orderId}
|
||||
loading={Boolean(paymentTask)}
|
||||
onClick={() => void startTask('pay_locked_order', [record.id], {
|
||||
order_id: orderId,
|
||||
commodity_id: commodityId,
|
||||
})}
|
||||
>
|
||||
{expired ? '已过期' : '支付锁单'}
|
||||
</Button>
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
}] : []),
|
||||
];
|
||||
|
||||
const [configFormValues, setConfigFormValues] = useState<DouyuConfig | null>(null);
|
||||
@@ -1653,7 +1721,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
|| selectedXpdGoodsSoldOut
|
||||
|| !xpdPaymentOptions.some((option) => option.value === xpdPayType);
|
||||
}
|
||||
if (['exchange_goods', 'exchange_esports_goods'].includes(taskType)) return !selectedGoodsId;
|
||||
if (['lock_goods', 'exchange_goods', 'exchange_esports_goods'].includes(taskType)) return !selectedGoodsId;
|
||||
// 选中账号中有正在执行的任务时禁用,避免同账号并发不同类型任务(并发写状态字段)
|
||||
return selectedHasRunning;
|
||||
};
|
||||
@@ -1854,6 +1922,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
/>
|
||||
<div style={actionGridStyle}>
|
||||
{renderActionButton('refresh_goods')}
|
||||
{renderActionButton('lock_goods')}
|
||||
{renderActionButton('exchange_goods', 'primary')}
|
||||
</div>
|
||||
</Space>
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import type { DouyuTaskItem } from '../api/types';
|
||||
|
||||
const EXCHANGE_TASK_TYPES = new Set(['exchange_goods', 'exchange_esports_goods', 'exchange_xpd_goods']);
|
||||
const EXCHANGE_TASK_TYPES = new Set([
|
||||
'exchange_goods',
|
||||
'pay_locked_order',
|
||||
'exchange_esports_goods',
|
||||
'exchange_xpd_goods',
|
||||
]);
|
||||
const CARD_W = 320;
|
||||
const CARD_H = 470;
|
||||
const FONT_FAMILY = '"PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif';
|
||||
@@ -29,11 +34,14 @@ function goodsOf(task: DouyuTaskItem): {
|
||||
const name = (rawString(goods, 'commodityName')
|
||||
|| rawString(goods, 'name')
|
||||
|| rawString(goods, 'sGoodsName')
|
||||
|| task.message.replace(/^兑换[^::]*[::]?\s*/, '').trim()
|
||||
|| task.message.replace(/^(?:兑换[^::]*|锁单支付成功)[::]?\s*/, '').trim()
|
||||
|| rawString(task.result, 'commodity_id'))
|
||||
.replace(/[((]\d+\s*(?:点券|碎片|积分)[))]\s*$/, '')
|
||||
.trim();
|
||||
const pic = rawString(goods, 'webPic') || rawString(goods, 'pic') || rawString(goods, 'sGoodsPic');
|
||||
const pic = rawString(goods, 'webPic')
|
||||
|| rawString(goods, 'pic')
|
||||
|| rawString(goods, 'sGoodsPic')
|
||||
|| rawString(task.result, 'commodity_image');
|
||||
const roleName = rawString(task.result, 'game_name');
|
||||
const channel = rawString(task.result, 'game_channel');
|
||||
const accountName = rawString(task.result, 'account_name')
|
||||
|
||||
Reference in New Issue
Block a user