refactor(account): 一键查询不该重复展示账号字段 —— 改成刷新它们,只留账号表里没有的

用户反馈:「一键查询也太重复了,直接更新账号信息不行吗?为啥很多都要再展示一次?
那建立账号信息表有啥用」——**说得对,是我设计错了**。

## 问题

面板把鱼翅 / 积分 / 钱包余额 / 精英宝典 / 电竞手册又原样列了一遍,
而这些账号表(`accounts`)本来就有对应列,账号详情也在显示同一批值 ——
同一屏出现两份,既冗余又让人分不清哪份是准的。

## 改法:查询 = 刷新,不是另开副本

| 内容 | 处理 |
| --- | --- |
| 鱼翅 / 积分 / 交易余额 / 开通状态 | 写回**账号表**(`_refresh_*` 本来就在写),由账号详情已有界面展示 —— **面板不再重复** |
| **积分到期时间** | 有 `accounts.points_expire_at` 列,但**查询漏了没写、界面也没显示** → 现在写回,并加到账号信息区展示 |
| **高价值套装是否已获得** | 账号表没有 → **只有这个该单独展示** |
| **官方兑换记录** | 账号表没有 → 单独展示 |

面板因此瘦身成只含「🎯 高价值套装」+「兑换记录」(+ 错误提示 + 查询时间),
标题也改成「高价值套装与兑换记录(一键查询)」,不再叫"账号信息"。

## 修了一个连带 bug

积分到期时间一直没被查询写回账号(只在快照里躺着),界面也没展示 ——
现在 `_parse_expire_at` 后写入 `accounts.points_expire_at`,
且斗鱼返回 `0` 时写 `None` 而不是 `0`(否则又显示 1970/1/1)。

## 测试(+3,共 41)

`TestOverviewRefreshesAccountFields`:资产/开通状态确实写回账号列、
**积分到期时间写回**、`willExpireTime="0"` 时清成 None 而非 0。

## 验证

`TZ=Asia/Shanghai` **593 passed / 6 skipped**;ruff 全绿;前端 build 通过、lint 0 errors。
This commit is contained in:
yml2213
2026-10-04 19:22:34 +08:00
parent c2520613ca
commit aea05952af
6 changed files with 137 additions and 47 deletions
+2
View File
@@ -155,6 +155,8 @@ export interface AccountOverview {
points: number | null;
gold_balance: number | null;
exchange_balance: number | null;
/** 积分到期时间(一键查询会刷新它) */
points_expire_at: string | null;
game_name: string;
game_channel: string;
/** 三列各管一个作用域:unknown / unbound / pending / bound */
+10 -3
View File
@@ -380,7 +380,7 @@ export default function AccountDetailPage() {
title={
<Space>
<ThunderboltOutlined />
<span>一键查询(斗鱼实时)</span>
<span>高价值套装与兑换记录(一键查询)</span>
</Space>
}
extra={
@@ -406,8 +406,9 @@ export default function AccountDetailPage() {
/>
) : (
<Text type="secondary">
实时向斗鱼查询:鱼翅 / 积分(含到期时间)/ 精英宝典 / 电竞手册 / 官方兑换记录,
并重点标出「兔兔白日梦」「狂战萌兔」是否已获得。结果会**存在这个账号上**。
实时向斗鱼查一次这个账号:鱼翅 / 积分 / 开通状态会**就地刷新到下面的账号信息**,
这里只单独展示账号表里没有的两项 —— 「兔兔白日梦」「狂战萌兔」是否已获得,
以及官方兑换记录。
</Text>
)}
</Card>
@@ -574,6 +575,12 @@ export default function AccountDetailPage() {
<div style={{ display: 'flex', gap: 32, marginTop: 16, flexWrap: 'wrap' }}>
<Statistic title="当前积分" value={overview?.points ?? '-'} />
<Statistic
title="积分到期"
// 斗鱼用 0 表示「没有到期时间」;不处理会显示成 1970/1/1
value={overview?.points_expire_at ? formatTime(overview.points_expire_at) : '-'}
valueStyle={{ fontSize: 20 }}
/>
<Statistic title="鱼翅余额" value={overview?.gold_balance ?? '-'} />
<Statistic title="兑换余额" value={overview?.exchange_balance ?? '-'} />
<Statistic
@@ -82,11 +82,6 @@ function formatTime(value: string | number | null | undefined): string {
return Number.isNaN(d.getTime()) ? text : d.toLocaleString('zh-CN');
}
function yesNo(value: boolean | null | undefined): string {
if (value === true) return '已开通';
if (value === false) return '未开通';
return '未知';
}
/**
* 高价值套装卡片。
@@ -145,6 +140,16 @@ function HighValueCard({ goods }: { goods: HighValueGoods }) {
);
}
/**
* 一键查询结果面板。
*
* ⚠️ **只展示账号表里没有的东西**(高价值套装是否已获得、官方兑换记录)。
*
* 鱼翅 / 积分 / 钱包余额 / 开通状态这些,账号表本来就有对应字段
* (`gold_balance` / `points` / `exchange_balance` / `elite_opened` / `esports_opened`),
* 查询的作用是**刷新它们**,然后由账号详情里已有的界面直接反映出来 ——
* 在这里再复制一份展示是多余的,还会让人分不清哪份才是准的。
*/
export default function AccountOverviewPanel({
result,
queriedAt,
@@ -154,7 +159,6 @@ export default function AccountOverviewPanel({
queriedAt?: string | null;
}) {
if (!result) return <Empty description="暂无结果" />;
const assets = result.assets || {};
const errors = result.errors || {};
const goods = result.high_value_goods || [];
const records = result.exchange_records || [];
@@ -195,42 +199,6 @@ export default function AccountOverviewPanel({
/>
)}
<Card size="small" title="资产">
<Descriptions size="small" column={2}>
<Descriptions.Item label="鱼翅">
{assets.gold_balance ?? '-'}
</Descriptions.Item>
<Descriptions.Item label="钱包兑换余额">
{assets.exchange_balance ?? '-'}
</Descriptions.Item>
<Descriptions.Item label="积分">{assets.points ?? '-'}</Descriptions.Item>
<Descriptions.Item label="积分到期">
{formatTime(assets.points_expire_at)}
</Descriptions.Item>
</Descriptions>
</Card>
<Card size="small" title="开通状态">
<Descriptions size="small" column={2}>
<Descriptions.Item label="精英宝典">
<Tag color={result.elite?.opened ? 'green' : 'default'}>
{yesNo(result.elite?.opened)}
</Tag>
</Descriptions.Item>
<Descriptions.Item label="宝典到期">
{formatTime(result.elite?.expire_time)}
</Descriptions.Item>
<Descriptions.Item label="电竞手册">
<Tag color={result.esports?.opened ? 'green' : 'default'}>
{yesNo(result.esports?.opened)}
</Tag>
</Descriptions.Item>
<Descriptions.Item label="电竞积分">
{result.esports?.points ?? '-'}
</Descriptions.Item>
</Descriptions>
</Card>
<Card size="small" title="🎯 高价值套装">
{goods.length === 0 ? (
<Text type="secondary">没有配置关注的高价值套装,或本次没取到</Text>