账号作战表新增「兑换结果」列,移除已废弃的分析文档
- 账号作战表新增「兑换结果」列:明确展示 已抢到(含奖品名与订单号)/ 未成功(含原因与尝试次数)/ 已兑换(来自服务端兑换记录兜底)/ 抢购中 / 待抢购 / 已跳过重复兑换 - 抢到奖品的账号整行标绿并加左侧色条,一眼可见 - 复盘表时间列改为可读时间(秒级时间戳格式化) - 按用户确认删除已废弃的 purchase_logic_analysis.md
This commit is contained in:
+138
-3
@@ -210,6 +210,22 @@ type RecordRow = UserPrizeInfo & {
|
||||
uid: number
|
||||
}
|
||||
|
||||
// exchangeSummary 汇总某个账号本次运行里的兑换结果,供「兑换结果」列展示。
|
||||
type ExchangeSummary = {
|
||||
success?: MonitorResult
|
||||
failed?: MonitorResult
|
||||
attempts: number
|
||||
skipped: number
|
||||
}
|
||||
|
||||
function isExchangeAttempt(action?: string): boolean {
|
||||
return action === 'exchange' || action === 'exchange_error'
|
||||
}
|
||||
|
||||
function exchangeOrderID(exchange?: ExchangeResp): string {
|
||||
return exchange?.order_id || exchange?.orderId || ''
|
||||
}
|
||||
|
||||
type SavedConfig = Omit<UiConfig, 'cookieText' | 'selectedAccountLines'>
|
||||
|
||||
type ScheduleCountdown = {
|
||||
@@ -303,6 +319,14 @@ function summarizeInventory(items: PrizeInfo[]): string {
|
||||
return `剩余 ${leftKnown ? left : '-'} / 总量 ${totalKnown ? total : '-'}`
|
||||
}
|
||||
|
||||
function formatUnixTime(value?: number): string {
|
||||
const ts = Number(value || 0)
|
||||
if (!ts) return ''
|
||||
// 服务端返回秒级时间戳,兼容毫秒。
|
||||
const ms = ts > 1e12 ? ts : ts * 1000
|
||||
return new Date(ms).toLocaleString('zh-CN', { hour12: false })
|
||||
}
|
||||
|
||||
function statusTag(status?: number, error?: string): ReactNode {
|
||||
if (error) return <Tag color="error">失败</Tag>
|
||||
if (status === 200) return <Tag color="success">成功</Tag>
|
||||
@@ -615,6 +639,49 @@ export default function Dashboard() {
|
||||
return map
|
||||
}, [results])
|
||||
|
||||
// 本次运行里各账号的兑换结果(results 为最新在前)。
|
||||
const exchangeByUID = useMemo(() => {
|
||||
const map = new Map<number, ExchangeSummary>()
|
||||
results.forEach((item) => {
|
||||
if (item.action === 'skip_duplicate') {
|
||||
const entry = map.get(item.uid) ?? { attempts: 0, skipped: 0 }
|
||||
entry.skipped += 1
|
||||
map.set(item.uid, entry)
|
||||
return
|
||||
}
|
||||
if (!isExchangeAttempt(item.action)) return
|
||||
const entry = map.get(item.uid) ?? { attempts: 0, skipped: 0 }
|
||||
entry.attempts += 1
|
||||
if (item.category === 'success') {
|
||||
if (!entry.success) entry.success = item
|
||||
} else if (!entry.failed) {
|
||||
entry.failed = item
|
||||
}
|
||||
map.set(item.uid, entry)
|
||||
})
|
||||
return map
|
||||
}, [results])
|
||||
|
||||
// 用服务端兑换记录兜底,界面刷新后也能看出该账号是否已抢到当前奖品。
|
||||
const recordHitByUID = useMemo(() => {
|
||||
const map = new Map<number, UserPrizeInfo>()
|
||||
const pid = Number(watchedPID || 0)
|
||||
const name = String(watchedName || '').trim()
|
||||
if (pid <= 0 && !name) return map
|
||||
records.forEach((item) => {
|
||||
;(item.records || []).forEach((record) => {
|
||||
const hitPID = pid > 0 && Number(record.prize_id || 0) === pid
|
||||
const hitName = name !== '' && String(record.name || '').includes(name)
|
||||
if (!hitPID && !hitName) return
|
||||
const prev = map.get(item.uid)
|
||||
if (!prev || Number(record.create_time || 0) > Number(prev.create_time || 0)) {
|
||||
map.set(item.uid, record)
|
||||
}
|
||||
})
|
||||
})
|
||||
return map
|
||||
}, [records, watchedPID, watchedName])
|
||||
|
||||
const successCount = useMemo(
|
||||
() => new Set(results.filter((item) => item.category === 'success').map((item) => item.uid)).size,
|
||||
[results],
|
||||
@@ -983,6 +1050,63 @@ export default function Dashboard() {
|
||||
width: 84,
|
||||
render: (_, row) => scoreMap.get(row.uid)?.score ?? '-',
|
||||
},
|
||||
{
|
||||
title: '兑换结果',
|
||||
width: 190,
|
||||
render: (_, row) => {
|
||||
const exchange = exchangeByUID.get(row.uid)
|
||||
if (exchange?.success) {
|
||||
const orderID = exchangeOrderID(exchange.success.exchange)
|
||||
return (
|
||||
<div>
|
||||
<Tag color="success">已抢到</Tag>
|
||||
<div className="muted">{exchange.success.prize?.name || currentPrize?.name || ''}</div>
|
||||
{orderID ? (
|
||||
<div className="muted" title={orderID}>
|
||||
单号 {orderID}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (exchange?.failed) {
|
||||
return (
|
||||
<div>
|
||||
<Tag color="error">未成功</Tag>
|
||||
<div className="muted">
|
||||
{exchange.failed.error ||
|
||||
exchange.failed.message ||
|
||||
exchange.failed.exchange?.msg ||
|
||||
'兑换失败'}
|
||||
</div>
|
||||
{exchange.attempts > 1 ? (
|
||||
<div className="muted">已尝试 {exchange.attempts} 次</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const record = recordHitByUID.get(row.uid)
|
||||
if (record) {
|
||||
return (
|
||||
<div>
|
||||
<Tag color="blue">已兑换</Tag>
|
||||
<div className="muted">{record.name || ''}</div>
|
||||
{record.order_id ? (
|
||||
<div className="muted" title={record.order_id}>
|
||||
单号 {record.order_id}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Tag color={running ? 'processing' : 'default'}>{running ? '抢购中' : '待抢购'}</Tag>
|
||||
{exchange?.skipped ? <div className="muted">已跳过重复兑换</div> : null}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
width: 98,
|
||||
@@ -997,7 +1121,15 @@ export default function Dashboard() {
|
||||
},
|
||||
},
|
||||
],
|
||||
[currentPrize?.name, latestResultByUID, scoreMap, watchedName],
|
||||
[
|
||||
currentPrize?.name,
|
||||
exchangeByUID,
|
||||
latestResultByUID,
|
||||
recordHitByUID,
|
||||
running,
|
||||
scoreMap,
|
||||
watchedName,
|
||||
],
|
||||
)
|
||||
|
||||
const cookieColumns: ColumnsType<CookieEntry> = useMemo(
|
||||
@@ -1086,7 +1218,7 @@ export default function Dashboard() {
|
||||
{ title: '积分', width: 90, render: (_, row) => row.new_score || row.score || 0 },
|
||||
{ title: '状态', dataIndex: 'status', width: 84 },
|
||||
{ title: '订单号', dataIndex: 'order_id', minWidth: 200, ellipsis: true },
|
||||
{ title: '时间', dataIndex: 'create_time', width: 130 },
|
||||
{ title: '时间', width: 160, render: (_, row) => formatUnixTime(row.create_time) },
|
||||
],
|
||||
[],
|
||||
)
|
||||
@@ -1400,7 +1532,10 @@ export default function Dashboard() {
|
||||
columns={accountColumns}
|
||||
dataSource={accounts}
|
||||
pagination={false}
|
||||
scroll={{ x: 850, y: 560 }}
|
||||
rowClassName={(row) =>
|
||||
exchangeByUID.get(row.uid)?.success ? 'account-row-success' : ''
|
||||
}
|
||||
scroll={{ x: 1040, y: 560 }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -369,6 +369,15 @@ body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 抢到奖品的账号整行标绿,方便一眼看出结果。 */
|
||||
.account-panel .ant-table-tbody > tr.account-row-success > td {
|
||||
background: #f6ffed !important;
|
||||
}
|
||||
|
||||
.account-panel .ant-table-tbody > tr.account-row-success > td:first-child {
|
||||
box-shadow: inset 3px 0 0 #52c41a;
|
||||
}
|
||||
|
||||
.side-log-panel {
|
||||
padding: 0 12px 12px;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user