feat(huya): 礼包名默认空白,改用「常用礼包」备选按钮

新建协作单以前会把「上次选的礼包」自动填进礼包名(localStorage
huya.collab.lastGift)。连开几单同一个礼包是省事,但每开一单都顶着上一次的
选择,稍不留神就按错礼包建单;而且这个自动值会被草稿自动保存吃进去,
下次打开又「恢复」出来。

- 新建弹窗**不再预填礼包名**:删掉 lastGift 的读写,字段默认空白。
- 草稿恢复只带订单号 / 店铺 / CK / 截图 / 备注,礼包名留空;
  hasDraftContent() 也不再认「只有礼包名」的草稿(否则被旧逻辑自动填过的
  那份草稿会一直弹「已恢复上次未提交的草稿」)。
- 新增 GET /external-orders/gifts/frequent:按台账里的使用次数排序
  (次数相同看最近一次使用),新建与补资料弹窗在字段上方渲染最多 6 个按钮,
  只显示**当前礼包缓存里还有的**(活动换 SID 后老礼包会整批下架)。
- 新增 5 例测试,并做了 3 次反向验证(去掉次数排序 / 改成升序 / 去掉空名过滤)。

补资料弹窗的初值仍是「这一单自己的礼包」,详情抽屉编辑也不动 —— 那不是
「上次选的」。
This commit is contained in:
yml2213
2026-10-18 10:53:04 +08:00
parent c441b6c663
commit 21f7ee3f3f
8 changed files with 244 additions and 28 deletions
+11
View File
@@ -39,6 +39,7 @@ import type {
HuyaCollabCkPreview,
HuyaCollabCkRequest,
HuyaCollabCkStatusRequest,
HuyaCollabFrequentGift,
HuyaCollabImagesRequest,
HuyaCollabImportResult,
HuyaCollabMetric,
@@ -212,6 +213,16 @@ export const huyaApi = {
collabOrderShops: () => api.get<string[], string[]>('/huya/external-orders/shops'),
/** 已用过的礼包/皮肤名(筛选下拉) */
collabOrderGifts: () => api.get<string[], string[]>('/huya/external-orders/gifts'),
/**
* 常用礼包(台账里用得最多的几个):新建/补资料的礼包名快捷按钮。
*
* 只来自历史记录,**是不是当前活动还在卖的**由页面拿礼包缓存比对后决定要不要显示。
*/
collabFrequentGifts: (limit = 8) =>
api.get<HuyaCollabFrequentGift[], HuyaCollabFrequentGift[]>(
'/huya/external-orders/gifts/frequent',
{ params: { limit } },
),
// ---- 店铺字典(新建协作单时点选;见业务规则 docs/设计-虎牙外部CK协作台账.md)----
/** 全部店铺(含停用的:管理弹窗要看到;新建下拉由页面按 is_active 过滤) */
+11
View File
@@ -1597,6 +1597,17 @@ export interface HuyaCollabGift {
icon: string;
}
/**
* 「常用礼包」:台账里用得最多的礼包名 + 用过几次。
*
* 只来自历史记录;**当前活动还有没有它**要拿 `HuyaCollabGift` 缓存比对
* (活动换 SID 后整批礼包会变,见设计文档 §11.4)。
*/
export interface HuyaCollabFrequentGift {
name: string;
used: number;
}
/** 粘贴 CK 后的即时预览:ok 且 has_role 才允许提交新建 */
export interface HuyaCollabCkPreview {
ok: boolean;
+78 -28
View File
@@ -25,6 +25,7 @@ import {
type HuyaCollabAuditItem,
type HuyaCollabCkExportResult,
type HuyaCollabCkPreview,
type HuyaCollabFrequentGift,
type HuyaCollabGift,
type HuyaCollabImageSlot,
type HuyaCollabMetric,
@@ -150,8 +151,6 @@ const SLOT_META: Record<HuyaCollabImageSlot, { label: string; keysKey: 'bind_ima
};
const IMPORT_PREVIEW_ROWS = 20;
// 「记住最近选的礼包」的本地存储键(同一个礼包连开几单时不用反复选)
const LAST_GIFT_KEY = 'huya.collab.lastGift';
// 「礼包列表」的本地缓存键:一个活动期间就那一批礼包,没必要每贴一份 CK 都去虎牙拉一次;
// 要更新就点「强制刷新礼包」(活动换 SID 后必点一次)。
const GIFTS_CACHE_KEY = 'huya.collab.gifts';
@@ -258,11 +257,12 @@ function markDraftHintShown(): void {
}
function hasDraftContent(draft: Omit<CreateDraft, 'saved_at'>): boolean {
// 礼包名不算「填过东西」:它现在默认空白、也不从草稿恢复(见下面 createForm 那段的说明),
// 否则一份「只被上次自动填了礼包」的老草稿会一直弹「已恢复上次未提交的草稿」。
return Boolean(
draft.order_no.trim()
|| draft.shop.trim()
|| draft.ck_text.trim()
|| draft.gift_name.trim()
|| draft.remark.trim()
|| draft.home_images.length,
);
@@ -486,6 +486,8 @@ export default function HuyaCollabPage() {
const [owners, setOwners] = useState<HuyaCollabOwner[]>([]);
const [shops, setShops] = useState<HuyaCollabShop[]>([]);
const [giftNames, setGiftNames] = useState<string[]>([]);
// 「常用礼包」:台账里用得最多的几个(新建/补资料的礼包名快捷按钮,见 2026-10-18 需求)
const [frequentGifts, setFrequentGifts] = useState<HuyaCollabFrequentGift[]>([]);
const [shopModalOpen, setShopModalOpen] = useState(false);
const [filters, setFilters] = useState<HuyaCollabOrderQuery>({ include_void: false });
// 默认按固定创建序号正序:老单在上、新单永远追加在最后。
@@ -564,7 +566,14 @@ export default function HuyaCollabPage() {
const withMeta = options?.withMeta !== false;
if (!silent) setLoading(true);
try {
const [list, nextSummary, nextOwners, nextShops, nextGiftNames] = await Promise.all([
const [
list,
nextSummary,
nextOwners,
nextShops,
nextGiftNames,
nextFrequentGifts,
] = await Promise.all([
huyaApi.listCollabOrders({
...filters,
sort_by: sortKey,
@@ -576,6 +585,7 @@ export default function HuyaCollabPage() {
withMeta ? huyaApi.collabOrderOwners() : Promise.resolve(null),
withMeta ? huyaApi.listCollabShops() : Promise.resolve(null),
withMeta ? huyaApi.collabOrderGifts() : Promise.resolve(null),
withMeta ? huyaApi.collabFrequentGifts() : Promise.resolve(null),
]);
setRows(list.items);
setTotal(list.total);
@@ -583,6 +593,7 @@ export default function HuyaCollabPage() {
if (nextOwners) setOwners(nextOwners);
if (nextShops) setShops(nextShops);
if (nextGiftNames) setGiftNames(nextGiftNames);
if (nextFrequentGifts) setFrequentGifts(nextFrequentGifts);
// 选中是「当前页」的概念:刷新后剔除已经不在这页上的行,避免点到别人的脏 id。
setSelectedIds((ids) => ids.filter((id) => list.items.some((row) => row.id === id)));
} catch (e: unknown) {
@@ -1446,7 +1457,6 @@ export default function HuyaCollabPage() {
draft,
require_verified: !draft, // 草稿单不跑探针;完整建单仍要求 CK 读到绑定角色
});
if (created.gift_name) window.localStorage.setItem(LAST_GIFT_KEY, created.gift_name);
if (draft) {
const reason = missing.length > 0
? `还缺 ${missing.join('、')}`
@@ -1543,6 +1553,50 @@ export default function HuyaCollabPage() {
return options;
};
/**
* 「常用礼包」快捷按钮(2026-10-18 用户要求):
* 礼包名**默认空白**(不再自动带上次选的那个,避免没注意时建错单),
* 想少点两下就点这里的备选。
*
* 只列**当前礼包缓存里还有的**那几个:活动换 SID 之后老礼包基本都下架了,
* 点一个拉不到的名字等于白点(见设计文档 §11.4)。缓存为空时整行不显示。
*/
const frequentGiftChoices = (): HuyaCollabFrequentGift[] => {
if (giftOptions.length === 0) return [];
const available = new Set(giftOptions.map((gift) => gift.name));
return frequentGifts.filter((item) => available.has(item.name)).slice(0, 6);
};
/** 按钮上的短标签:礼包名很长(「[限量返场] 怪诞乔克服装礼包」),去掉类目前缀再截断 */
const giftChipLabel = (name: string): string => {
const short = name.replace(/^\[[^\]]*\]\s*/, '').trim() || name;
return short.length > 10 ? `${short.slice(0, 10)}…` : short;
};
const renderFrequentGiftChips = (
current: string | undefined,
pick: (name: string) => void,
) => {
const choices = frequentGiftChoices();
if (choices.length === 0) return null;
return (
<Space size={6} wrap style={{ marginBottom: 6 }}>
<Text type="secondary" style={{ fontSize: 12 }}>常用礼包:</Text>
{choices.map((item) => (
<Tooltip key={item.name} title={`${item.name}(用过 ${item.used} 次)`}>
<Button
size="small"
type={current === item.name ? 'primary' : 'default'}
onClick={() => pick(item.name)}
>
{giftChipLabel(item.name)}
</Button>
</Tooltip>
))}
</Space>
);
};
// 表头排序:一次正序、再点反序(Table 上限制 sortDirections,不会出现「第三次取消」)。
const sortProps = (key: CollabSortKey) => ({
key,
@@ -2069,31 +2123,20 @@ export default function HuyaCollabPage() {
createForm.resetFields();
const draft = readCreateDraft(user?.id);
if (draft && hasDraftContent(draft)) {
// 上次填了一半:原样恢复(CK / 礼包 / 截图 / 备注都在),并重新验证暂存的 CK
const restored = { ...draft };
const restoredGift = String(restored.gift_name ?? '').trim();
if (
restoredGift
&& giftOptions.length > 0
&& !giftOptions.some((gift) => gift.name === restoredGift)
) {
restored.gift_name = '';
setGiftHint(`草稿里的「${restoredGift}」不在当前礼包缓存里,已清空,请重新点选`);
} else {
setGiftHint('');
}
createForm.setFieldsValue({ ...restored });
// 上次填了一半:订单号 / 店铺 / CK / 截图 / 备注都原样恢复,并重新验证暂存的 CK。
// **礼包名一律留空**(2026-10-18 用户要求):礼包默认空白,不再带上次选的那个;
// 反正上面有「常用礼包」备选,点一下就有,不值得为它记住上一次的选择。
createForm.setFieldsValue({ ...draft, gift_name: '' });
setGiftHint('');
setDraftRestoredAt(draft.saved_at || Date.now());
setCreateOpen(true);
if (draft.ck_text.trim()) scheduleCkPreview(draft.ck_text, true);
return;
}
setDraftRestoredAt(null);
// 记住最近选的礼包:只在当前缓存里还有它时才自动带上(礼包名不接受手输)
const lastGift = window.localStorage.getItem(LAST_GIFT_KEY);
if (lastGift && giftOptions.some((gift) => gift.name === lastGift)) {
createForm.setFieldsValue({ gift_name: lastGift });
}
// 礼包名**不预填**(2026-10-18 用户要求):以前会自动带上"上次选的",
// 每开一单都顶着上一次的礼包,容易在没注意时建错单。
// 想少点两下就用上面的「常用礼包」快捷按钮,点了才算选了。
setGiftHint('');
setCreateOpen(true);
}}
@@ -2587,10 +2630,14 @@ export default function HuyaCollabPage() {
)}
<Row gutter={16}>
<Col span={14}>
{renderFrequentGiftChips(createGiftValue, (name) => {
createForm.setFieldsValue({ gift_name: name });
setGiftHint('');
})}
<Form.Item
name="gift_name"
label="礼包名(用户购买的物品,可稍后补)"
tooltip="只接受礼包缓存里的点选结果,可输入关键字搜索。先贴有效 CK 拉一次礼包;不选也能先建单,之后点「补资料」再选。"
tooltip="默认空白,不会自动带上次选的礼包(2026-10-18)。只接受礼包缓存里的点选结果,可输入关键字搜索;不选也能先建单,之后点「补资料」再选。"
extra={giftHint || (giftsCachedAt && giftOptions.length > 0
? `礼包列表缓存于 ${giftCacheLabel}(${giftOptions.length} 个)`
: undefined)}
@@ -2604,10 +2651,9 @@ export default function HuyaCollabPage() {
options={giftSelectOptions(createGiftValue)}
disabled={giftOptions.length === 0 && !createGiftValue}
notFoundContent={giftOptions.length === 0 ? '先在左侧贴一份有效 CK 拉取礼包列表' : '没有匹配的礼包'}
onChange={(value: string) => {
// 记住最近选的礼包:连开几单同一个礼包就不用反复点
onChange={() => {
// 选了就清掉「上次那个礼包不在本次列表里」的提示
setGiftHint('');
if (value) window.localStorage.setItem(LAST_GIFT_KEY, value);
}}
/>
</Form.Item>
@@ -2709,6 +2755,10 @@ export default function HuyaCollabPage() {
</Form.Item>
</Col>
<Col span={12}>
{renderFrequentGiftChips(
supplementGiftValue ?? supplementTarget.gift_name,
(name) => supplementForm.setFieldsValue({ gift_name: name }),
)}
<Form.Item name="gift_name" label="礼包名"
tooltip="只接受礼包缓存里的点选结果,可输入关键字搜索;不接受手工输入">
<Select