feat(huya): 增加协作单礼包字典与名称编辑
This commit is contained in:
@@ -43,6 +43,9 @@ import type {
|
||||
HuyaCollabCkRequest,
|
||||
HuyaCollabCkStatusRequest,
|
||||
HuyaCollabFrequentGift,
|
||||
HuyaCollabGiftCatalog,
|
||||
HuyaCollabGiftCatalogCreate,
|
||||
HuyaCollabGiftCatalogUpdate,
|
||||
HuyaCollabImagesRequest,
|
||||
HuyaCollabImportResult,
|
||||
HuyaCollabMetric,
|
||||
@@ -243,6 +246,19 @@ export const huyaApi = {
|
||||
api.delete<{ success: boolean; name: string }, { success: boolean; name: string }>(
|
||||
`/huya/collab-shops/${id}`,
|
||||
),
|
||||
|
||||
// ---- 礼包字典(标准名称 + 所需积分;名称编辑会级联历史协作单)----
|
||||
listCollabGiftCatalog: () =>
|
||||
api.get<HuyaCollabGiftCatalog[], HuyaCollabGiftCatalog[]>('/huya/collab-gifts'),
|
||||
createCollabGift: (data: HuyaCollabGiftCatalogCreate) =>
|
||||
api.post<HuyaCollabGiftCatalog, HuyaCollabGiftCatalog>('/huya/collab-gifts', data),
|
||||
updateCollabGift: (id: number, data: HuyaCollabGiftCatalogUpdate) =>
|
||||
api.put<HuyaCollabGiftCatalog, HuyaCollabGiftCatalog>(`/huya/collab-gifts/${id}`, data),
|
||||
deleteCollabGift: (id: number) =>
|
||||
api.delete<
|
||||
{ success: boolean; id: number; name: string; points: number },
|
||||
{ success: boolean; id: number; name: string; points: number }
|
||||
>(`/huya/collab-gifts/${id}`),
|
||||
getCollabOrder: (id: number) =>
|
||||
api.get<HuyaCollabOrder, HuyaCollabOrder>(`/huya/external-orders/${id}`),
|
||||
createCollabOrder: (data: HuyaCollabOrderCreate) =>
|
||||
|
||||
@@ -1824,3 +1824,33 @@ export interface HuyaCollabShopUpdate {
|
||||
/** 改名时是否级联更新已有订单里的店铺名(默认 true) */
|
||||
rename_orders?: boolean;
|
||||
}
|
||||
|
||||
/** 协作台账的礼包字典项(与实时活动礼包 HuyaCollabGift 分开) */
|
||||
export interface HuyaCollabGiftCatalog {
|
||||
id: number;
|
||||
name: string;
|
||||
points: number;
|
||||
sort_order: number;
|
||||
/** false = 停用(不再出现在可选项里;已有订单仍显示原值) */
|
||||
is_active: boolean;
|
||||
remark: string;
|
||||
created_by_name: string;
|
||||
updated_by_name: string;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
export interface HuyaCollabGiftCatalogCreate {
|
||||
name: string;
|
||||
points: number;
|
||||
sort_order?: number;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
export interface HuyaCollabGiftCatalogUpdate {
|
||||
name?: string;
|
||||
points?: number;
|
||||
sort_order?: number;
|
||||
is_active?: boolean;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,368 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
Empty,
|
||||
Input,
|
||||
InputNumber,
|
||||
Modal,
|
||||
Popconfirm,
|
||||
Row,
|
||||
Space,
|
||||
Switch,
|
||||
Table,
|
||||
Tag,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import type { TableProps } from 'antd';
|
||||
import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import { huyaApi, type HuyaCollabGiftCatalog } from '../api/modules';
|
||||
import { message } from '../utils/antdMessage';
|
||||
import { getErrorMessage } from '../utils/error';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
/**
|
||||
* 虎牙协作礼包字典:管理员维护标准名称和所需积分,客服只能选择。
|
||||
*
|
||||
* - 停用后不再出现在新建下拉里,历史订单继续显示原值;
|
||||
* - 改名会同步所有历史订单上的礼包名;
|
||||
* - 改积分只影响之后新建 / 换礼包时的默认值,不回填历史积分快照;
|
||||
* - 删除只删字典行,仍有订单使用时会拒绝。
|
||||
*/
|
||||
export default function HuyaCollabGiftModal({
|
||||
open,
|
||||
gifts,
|
||||
canManage,
|
||||
onClose,
|
||||
onChanged,
|
||||
}: {
|
||||
open: boolean;
|
||||
gifts: HuyaCollabGiftCatalog[];
|
||||
canManage: boolean;
|
||||
onClose: () => void;
|
||||
onChanged: () => void;
|
||||
}) {
|
||||
const [newName, setNewName] = useState('');
|
||||
const [newPoints, setNewPoints] = useState<number | null>(null);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [editTarget, setEditTarget] = useState<HuyaCollabGiftCatalog | null>(null);
|
||||
const [editName, setEditName] = useState('');
|
||||
const [editPoints, setEditPoints] = useState<number | null>(null);
|
||||
const [editRemark, setEditRemark] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
setNewName('');
|
||||
setNewPoints(null);
|
||||
setEditTarget(null);
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const create = async () => {
|
||||
const name = newName.trim();
|
||||
if (!name) {
|
||||
message.warning('请先输入礼包名');
|
||||
return;
|
||||
}
|
||||
if (newPoints === null) {
|
||||
message.warning('请填写礼包所需积分');
|
||||
return;
|
||||
}
|
||||
setBusy(true);
|
||||
try {
|
||||
await huyaApi.createCollabGift({ name, points: newPoints });
|
||||
message.success(`已添加礼包「${name}」`);
|
||||
setNewName('');
|
||||
setNewPoints(null);
|
||||
onChanged();
|
||||
} catch (error) {
|
||||
message.error(getErrorMessage(error));
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
};
|
||||
|
||||
const patch = async (
|
||||
gift: HuyaCollabGiftCatalog,
|
||||
payload: { sort_order?: number; is_active?: boolean; remark?: string },
|
||||
) => {
|
||||
setBusy(true);
|
||||
try {
|
||||
await huyaApi.updateCollabGift(gift.id, payload);
|
||||
onChanged();
|
||||
} catch (error) {
|
||||
message.error(getErrorMessage(error));
|
||||
onChanged();
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
};
|
||||
|
||||
const openEdit = (gift: HuyaCollabGiftCatalog) => {
|
||||
setEditTarget(gift);
|
||||
setEditName(gift.name);
|
||||
setEditPoints(gift.points);
|
||||
setEditRemark(gift.remark || '');
|
||||
};
|
||||
|
||||
const submitEdit = async () => {
|
||||
if (!editTarget) return;
|
||||
const name = editName.trim();
|
||||
if (!name) {
|
||||
message.warning('礼包名不能为空');
|
||||
return;
|
||||
}
|
||||
if (editPoints === null) {
|
||||
message.warning('请填写礼包所需积分');
|
||||
return;
|
||||
}
|
||||
setBusy(true);
|
||||
try {
|
||||
await huyaApi.updateCollabGift(editTarget.id, {
|
||||
name,
|
||||
points: editPoints,
|
||||
remark: editRemark.trim(),
|
||||
});
|
||||
message.success(
|
||||
name === editTarget.name
|
||||
? `已保存礼包「${name}」`
|
||||
: `已改名为「${name}」,历史协作单里的礼包名也已同步`,
|
||||
);
|
||||
setEditTarget(null);
|
||||
onChanged();
|
||||
} catch (error) {
|
||||
message.error(getErrorMessage(error));
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
};
|
||||
|
||||
const remove = async (gift: HuyaCollabGiftCatalog) => {
|
||||
setBusy(true);
|
||||
try {
|
||||
await huyaApi.deleteCollabGift(gift.id);
|
||||
message.success(`已删除礼包「${gift.name}」`);
|
||||
onChanged();
|
||||
} catch (error) {
|
||||
message.error(getErrorMessage(error));
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
};
|
||||
|
||||
const columns: TableProps<HuyaCollabGiftCatalog>['columns'] = [
|
||||
{
|
||||
title: '礼包名',
|
||||
dataIndex: 'name',
|
||||
width: 280,
|
||||
render: (value: string, row) => (
|
||||
<Space size={4}>
|
||||
<Text strong>{value}</Text>
|
||||
{!row.is_active && <Tag>已停用</Tag>}
|
||||
{canManage && (
|
||||
<Tooltip title="编辑名称、积分和备注">
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
icon={<EditOutlined />}
|
||||
disabled={busy}
|
||||
onClick={() => openEdit(row)}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '所需积分',
|
||||
dataIndex: 'points',
|
||||
width: 110,
|
||||
render: (value: number) => <Text strong>{value}</Text>,
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
dataIndex: 'sort_order',
|
||||
width: 100,
|
||||
render: (value: number, row) => (
|
||||
<InputNumber
|
||||
size="small"
|
||||
value={value}
|
||||
disabled={!canManage || busy}
|
||||
onChange={(next) => {
|
||||
if (next === null || next === value) return;
|
||||
void patch(row, { sort_order: Number(next) });
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '新建时可选',
|
||||
dataIndex: 'is_active',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
render: (value: boolean, row) => (
|
||||
<Switch
|
||||
size="small"
|
||||
checked={value}
|
||||
disabled={!canManage || busy}
|
||||
checkedChildren="启用"
|
||||
unCheckedChildren="停用"
|
||||
onChange={(checked) => void patch(row, { is_active: checked })}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'remark',
|
||||
render: (value: string, row) => (
|
||||
<Input
|
||||
size="small"
|
||||
defaultValue={value}
|
||||
disabled={!canManage || busy}
|
||||
placeholder="备注(失焦保存)"
|
||||
onBlur={(event) => {
|
||||
const next = event.target.value.trim();
|
||||
if (next !== value) void patch(row, { remark: next });
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '最后改动',
|
||||
width: 150,
|
||||
render: (_, row) => (
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
{row.updated_by_name || '-'}
|
||||
{row.updated_at ? ` · ${new Date(row.updated_at).toLocaleString()}` : ''}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
render: (_, row) =>
|
||||
canManage ? (
|
||||
<Popconfirm
|
||||
title={`删除礼包「${row.name}」?`}
|
||||
description="仍有协作单使用该礼包时会被拒绝,可改用停用"
|
||||
onConfirm={() => void remove(row)}
|
||||
disabled={busy}
|
||||
>
|
||||
<Button size="small" danger type="text" icon={<DeleteOutlined />} disabled={busy} />
|
||||
</Popconfirm>
|
||||
) : (
|
||||
<Text type="secondary">-</Text>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
title="礼包管理"
|
||||
open={open}
|
||||
onCancel={onClose}
|
||||
footer={<Button onClick={onClose}>关闭</Button>}
|
||||
width={940}
|
||||
destroyOnHidden
|
||||
>
|
||||
<Space direction="vertical" size={10} style={{ width: '100%' }}>
|
||||
<Text type="secondary">
|
||||
先在这里维护标准礼包名和所需积分,新建 / 补资料时可以直接选择。礼包名请按兑换记录里的名称填写;
|
||||
停用只是不再出现在可选列表里,历史订单不受影响;改名会同步已有订单,改积分只影响之后新建或换礼包的单。
|
||||
</Text>
|
||||
{canManage && (
|
||||
<Row gutter={8}>
|
||||
<Col flex="auto">
|
||||
<Input
|
||||
placeholder="礼包名(按兑换记录标准名填写),回车添加"
|
||||
value={newName}
|
||||
maxLength={128}
|
||||
onChange={(event) => setNewName(event.target.value)}
|
||||
onPressEnter={() => void create()}
|
||||
/>
|
||||
</Col>
|
||||
<Col>
|
||||
<InputNumber
|
||||
style={{ width: 140 }}
|
||||
min={0}
|
||||
precision={0}
|
||||
placeholder="所需积分"
|
||||
value={newPoints}
|
||||
onChange={(value) => setNewPoints(value ?? null)}
|
||||
/>
|
||||
</Col>
|
||||
<Col>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
loading={busy}
|
||||
onClick={() => void create()}
|
||||
>
|
||||
添加
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
<Table<HuyaCollabGiftCatalog>
|
||||
rowKey="id"
|
||||
size="small"
|
||||
columns={columns}
|
||||
dataSource={gifts}
|
||||
pagination={false}
|
||||
scroll={{ y: 340 }}
|
||||
locale={{
|
||||
emptyText: (
|
||||
<Empty
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
description="还没有礼包,先加一个"
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Space>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title={editTarget ? `编辑礼包「${editTarget.name}」` : '编辑礼包'}
|
||||
open={!!editTarget}
|
||||
onCancel={() => setEditTarget(null)}
|
||||
onOk={() => void submitEdit()}
|
||||
confirmLoading={busy}
|
||||
okText="保存"
|
||||
destroyOnHidden
|
||||
>
|
||||
<Space direction="vertical" size={10} style={{ width: '100%' }}>
|
||||
<Input
|
||||
value={editName}
|
||||
maxLength={128}
|
||||
autoFocus
|
||||
placeholder="礼包名"
|
||||
onChange={(event) => setEditName(event.target.value)}
|
||||
onPressEnter={() => void submitEdit()}
|
||||
/>
|
||||
<InputNumber
|
||||
style={{ width: '100%' }}
|
||||
min={0}
|
||||
precision={0}
|
||||
value={editPoints}
|
||||
placeholder="所需积分"
|
||||
onChange={(value) => setEditPoints(value ?? null)}
|
||||
/>
|
||||
<Input
|
||||
value={editRemark}
|
||||
maxLength={255}
|
||||
placeholder="备注(可选)"
|
||||
onChange={(event) => setEditRemark(event.target.value)}
|
||||
/>
|
||||
<Text type="secondary">
|
||||
改名会同步所有已有协作单上的礼包名;修改积分只影响之后新建或换礼包的单,不会改变历史订单。
|
||||
</Text>
|
||||
</Space>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
type HuyaCollabCkPreview,
|
||||
type HuyaCollabFrequentGift,
|
||||
type HuyaCollabGift,
|
||||
type HuyaCollabGiftCatalog,
|
||||
type HuyaCollabImageSlot,
|
||||
type HuyaCollabMetric,
|
||||
type HuyaCollabOrder,
|
||||
@@ -40,6 +41,7 @@ import {
|
||||
import HuyaCollabShopModal from '../components/HuyaCollabShopModal';
|
||||
import HuyaCollabShopPicker from '../components/HuyaCollabShopPicker';
|
||||
import HuyaCollabHomeImageUploader from '../components/HuyaCollabHomeImageUploader';
|
||||
import HuyaCollabGiftModal from '../components/HuyaCollabGiftModal';
|
||||
import { message } from '../utils/antdMessage';
|
||||
import { getErrorMessage } from '../utils/error';
|
||||
import { copyTextToClipboard } from '../utils/clipboard';
|
||||
@@ -513,10 +515,12 @@ export default function HuyaCollabPage() {
|
||||
const [summary, setSummary] = useState<HuyaCollabSummary | null>(null);
|
||||
const [owners, setOwners] = useState<HuyaCollabOwner[]>([]);
|
||||
const [shops, setShops] = useState<HuyaCollabShop[]>([]);
|
||||
const [giftCatalog, setGiftCatalog] = useState<HuyaCollabGiftCatalog[]>([]);
|
||||
const [giftNames, setGiftNames] = useState<string[]>([]);
|
||||
// 「常用礼包」:台账里用得最多的几个(新建/补资料的礼包名快捷按钮,见 2026-10-18 需求)
|
||||
const [frequentGifts, setFrequentGifts] = useState<HuyaCollabFrequentGift[]>([]);
|
||||
const [shopModalOpen, setShopModalOpen] = useState(false);
|
||||
const [giftModalOpen, setGiftModalOpen] = useState(false);
|
||||
const [filters, setFilters] = useState<HuyaCollabOrderQuery>({
|
||||
include_void: false,
|
||||
archive_scope: 'active',
|
||||
@@ -616,6 +620,7 @@ export default function HuyaCollabPage() {
|
||||
nextSummary,
|
||||
nextOwners,
|
||||
nextShops,
|
||||
nextGiftCatalog,
|
||||
nextGiftNames,
|
||||
nextFrequentGifts,
|
||||
] = await Promise.all([
|
||||
@@ -632,6 +637,7 @@ export default function HuyaCollabPage() {
|
||||
}),
|
||||
withMeta ? huyaApi.collabOrderOwners() : Promise.resolve(null),
|
||||
withMeta ? huyaApi.listCollabShops() : Promise.resolve(null),
|
||||
withMeta ? huyaApi.listCollabGiftCatalog() : Promise.resolve(null),
|
||||
withMeta ? huyaApi.collabOrderGifts() : Promise.resolve(null),
|
||||
withMeta ? huyaApi.collabFrequentGifts() : Promise.resolve(null),
|
||||
]);
|
||||
@@ -640,6 +646,7 @@ export default function HuyaCollabPage() {
|
||||
setSummary(nextSummary);
|
||||
if (nextOwners) setOwners(nextOwners);
|
||||
if (nextShops) setShops(nextShops);
|
||||
if (nextGiftCatalog) setGiftCatalog(nextGiftCatalog);
|
||||
if (nextGiftNames) setGiftNames(nextGiftNames);
|
||||
if (nextFrequentGifts) setFrequentGifts(nextFrequentGifts);
|
||||
// 选中是「当前页」的概念:刷新后剔除已经不在这页上的行,避免点到别人的脏 id。
|
||||
@@ -657,7 +664,7 @@ export default function HuyaCollabPage() {
|
||||
// 暂停后连 WebSocket 也不建:既不自动重拉,也不做断线兜底轮询。
|
||||
const realtimeSyncEnabled = realtimeEnabled && !realtimePaused;
|
||||
const collabModalOpen = Boolean(
|
||||
createOpen || supplementTarget || detail || statusAction || batchStatusOpen || rechargeOpen || archiveOpen || ckTarget || ckExportOpen || shopModalOpen,
|
||||
createOpen || supplementTarget || detail || statusAction || batchStatusOpen || rechargeOpen || archiveOpen || ckTarget || ckExportOpen || shopModalOpen || giftModalOpen,
|
||||
);
|
||||
|
||||
const toggleRealtimeSync = (nextEnabled: boolean) => {
|
||||
@@ -761,12 +768,16 @@ export default function HuyaCollabPage() {
|
||||
}
|
||||
if (preview.ok && preview.has_role) {
|
||||
message.success(`CK 有效:绑定角色 ${preview.bind_game_role}`);
|
||||
// 「记住的礼包」如果不在当前列表里就清掉重选;列表为空时不动它(允许手填)
|
||||
// 「记住的礼包」如果不在实时列表、也不在标准字典里就清掉重选。
|
||||
const options = preview.gifts.length > 0 ? preview.gifts : giftsRef.current;
|
||||
const available = new Set([
|
||||
...options.map((gift) => gift.name),
|
||||
...giftCatalog.filter((gift) => gift.is_active).map((gift) => gift.name),
|
||||
]);
|
||||
const picked = String(createForm.getFieldValue('gift_name') ?? '').trim();
|
||||
if (picked && options.length > 0 && !options.some((gift) => gift.name === picked)) {
|
||||
if (picked && available.size > 0 && !available.has(picked)) {
|
||||
createForm.setFieldValue('gift_name', undefined);
|
||||
setGiftHint(`上次选的「${picked}」不在当前的礼包列表里,已清空,请重新点选`);
|
||||
setGiftHint(`上次选的「${picked}」不在实时礼包或标准字典里,已清空,请重新点选`);
|
||||
} else {
|
||||
setGiftHint('');
|
||||
}
|
||||
@@ -778,7 +789,7 @@ export default function HuyaCollabPage() {
|
||||
if (seq === previewSeq.current) setPreviewing(false);
|
||||
}
|
||||
},
|
||||
[emptyPreview, createForm],
|
||||
[emptyPreview, createForm, giftCatalog],
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -1825,33 +1836,49 @@ export default function HuyaCollabPage() {
|
||||
: giftOptions.length > 0
|
||||
? `礼包用本地缓存(${giftOptions.length} 个${giftCacheLabel ? ` · ${giftCacheLabel}` : ''}),要更新点「强制刷新礼包」`
|
||||
: (ckPreview?.gifts_message || '');
|
||||
const hasSelectableGifts = giftOptions.length > 0
|
||||
|| giftCatalog.some((gift) => gift.is_active);
|
||||
|
||||
/**
|
||||
* 礼包下拉选项:只允许点选缓存里的礼包,不接受手输。
|
||||
* 当前值是历史/已下线礼包时补一个只读选项,保证界面能显示,但依然只能从下拉里选。
|
||||
*/
|
||||
/** 礼包下拉:实时活动礼包 + 管理员维护的标准字典,二者按名称去重。 */
|
||||
const giftSelectOptions = (current?: string | null) => {
|
||||
const value = String(current ?? '').trim();
|
||||
const options = giftOptions.map((gift) => ({
|
||||
value: gift.name,
|
||||
label: [
|
||||
gift.category_name ? `[${gift.category_name}]` : '',
|
||||
gift.name,
|
||||
gift.price ? `· ${gift.price} 积分` : '',
|
||||
gift.remain_text ? `· 剩 ${gift.remain_text}` : '',
|
||||
].filter(Boolean).join(' '),
|
||||
}));
|
||||
if (value && !giftOptions.some((gift) => gift.name === value)) {
|
||||
const seen = new Set<string>();
|
||||
const options = giftOptions
|
||||
.filter((gift) => {
|
||||
if (!gift.name || seen.has(gift.name)) return false;
|
||||
seen.add(gift.name);
|
||||
return true;
|
||||
})
|
||||
.map((gift) => ({
|
||||
value: gift.name,
|
||||
label: [
|
||||
gift.category_name ? `[${gift.category_name}]` : '',
|
||||
gift.name,
|
||||
gift.price ? `· ${gift.price} 积分` : '',
|
||||
gift.remain_text ? `· 剩 ${gift.remain_text}` : '',
|
||||
].filter(Boolean).join(' '),
|
||||
}));
|
||||
for (const gift of giftCatalog) {
|
||||
if (!gift.is_active || !gift.name || seen.has(gift.name)) continue;
|
||||
seen.add(gift.name);
|
||||
options.push({
|
||||
value: gift.name,
|
||||
label: `${gift.name} · ${gift.points} 积分 · 标准字典`,
|
||||
});
|
||||
}
|
||||
if (value && !seen.has(value)) {
|
||||
options.unshift({ value, label: `${value}(历史值)` });
|
||||
}
|
||||
return options;
|
||||
};
|
||||
|
||||
/** 礼包名对应的积分单价;缓存里没有 / 没价格时返回 null(后端核账时还会按名称补一次)。 */
|
||||
/** 礼包名对应的积分:实时价格优先,其次标准字典;都没有时返回 null。 */
|
||||
const giftRequiredPoints = (name?: string | null): number | null => {
|
||||
const value = String(name ?? '').trim();
|
||||
if (!value) return null;
|
||||
return giftOptions.find((gift) => gift.name === value)?.price ?? null;
|
||||
return giftOptions.find((gift) => gift.name === value)?.price
|
||||
?? giftCatalog.find((gift) => gift.name === value)?.points
|
||||
?? null;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1863,8 +1890,11 @@ export default function HuyaCollabPage() {
|
||||
* 点一个拉不到的名字等于白点(见设计文档 §11.4)。缓存为空时整行不显示。
|
||||
*/
|
||||
const frequentGiftChoices = (): HuyaCollabFrequentGift[] => {
|
||||
if (giftOptions.length === 0) return [];
|
||||
const available = new Set(giftOptions.map((gift) => gift.name));
|
||||
const available = new Set([
|
||||
...giftOptions.map((gift) => gift.name),
|
||||
...giftCatalog.filter((gift) => gift.is_active).map((gift) => gift.name),
|
||||
]);
|
||||
if (available.size === 0) return [];
|
||||
return frequentGifts.filter((item) => available.has(item.name)).slice(0, 6);
|
||||
};
|
||||
|
||||
@@ -2107,7 +2137,7 @@ export default function HuyaCollabPage() {
|
||||
// 前缀单独一行、礼包名完整换行,别让单行省略号把名字吃掉
|
||||
labelRender={() => renderGiftSelectedLabel(row.gift_name)}
|
||||
styles={{ content: { whiteSpace: 'normal', textOverflow: 'clip', lineHeight: 1.3 } }}
|
||||
notFoundContent={giftOptions.length === 0 ? '礼包缓存为空:先在「新建」里贴 CK 拉一次' : '没有匹配的礼包'}
|
||||
notFoundContent={hasSelectableGifts ? '没有匹配的礼包' : '实时礼包为空,标准字典也还没有配置'}
|
||||
onChange={(next: string) => void patchOrder(row, {
|
||||
gift_name: next,
|
||||
required_points: giftRequiredPoints(next),
|
||||
@@ -2528,6 +2558,9 @@ export default function HuyaCollabPage() {
|
||||
{canEdit && !isArchiveView && (
|
||||
<Button icon={<ShopOutlined />} onClick={() => setShopModalOpen(true)}>店铺管理</Button>
|
||||
)}
|
||||
{canManage && !isArchiveView && (
|
||||
<Button icon={<GiftOutlined />} onClick={() => setGiftModalOpen(true)}>礼包管理</Button>
|
||||
)}
|
||||
{canEdit && !isArchiveView && (
|
||||
<Button
|
||||
type="primary"
|
||||
@@ -2776,16 +2809,16 @@ export default function HuyaCollabPage() {
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item name="gift_name" label="礼包名"
|
||||
tooltip="只接受礼包缓存里的点选结果,可输入关键字搜索;不接受手工输入">
|
||||
tooltip="从实时活动礼包或管理员维护的标准字典里点选,可输入关键字搜索">
|
||||
<Select
|
||||
allowClear
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
placeholder="输入关键字搜索,点选礼包"
|
||||
options={giftSelectOptions(detail.gift_name)}
|
||||
notFoundContent={giftOptions.length === 0
|
||||
? '礼包缓存为空:先在「新建」里贴 CK 拉一次'
|
||||
: '没有匹配的礼包'}
|
||||
notFoundContent={hasSelectableGifts
|
||||
? '没有匹配的礼包'
|
||||
: '实时礼包为空,标准字典也还没有配置'}
|
||||
onChange={(value?: string) => {
|
||||
detailForm.setFieldValue('required_points', giftRequiredPoints(value));
|
||||
}}
|
||||
@@ -3117,20 +3150,27 @@ export default function HuyaCollabPage() {
|
||||
<Form.Item
|
||||
name="gift_name"
|
||||
label="礼包名(用户购买的物品,可稍后补)"
|
||||
tooltip="默认空白,不会自动带上次选的礼包(2026-10-18)。只接受礼包缓存里的点选结果,可输入关键字搜索;不选也能先建单,之后点「补资料」再选。"
|
||||
extra={giftHint || (giftsCachedAt && giftOptions.length > 0
|
||||
? `礼包列表缓存于 ${giftCacheLabel}(${giftOptions.length} 个)`
|
||||
: undefined)}
|
||||
tooltip="默认空白,不会自动带上次选的礼包。可从实时活动礼包或管理员维护的标准字典里点选;不选也能先建单,之后点「补资料」再选。"
|
||||
extra={giftHint || [
|
||||
giftsCachedAt && giftOptions.length > 0
|
||||
? `实时礼包缓存于 ${giftCacheLabel}(${giftOptions.length} 个)`
|
||||
: '',
|
||||
giftCatalog.length > 0
|
||||
? `标准字典 ${giftCatalog.filter((gift) => gift.is_active).length} 个可用`
|
||||
: '',
|
||||
].filter(Boolean).join(';') || undefined}
|
||||
validateStatus={giftHint ? 'warning' : undefined}
|
||||
>
|
||||
<Select
|
||||
allowClear
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
placeholder={giftOptions.length > 0 ? '输入关键字搜索,点选礼包' : '礼包缓存为空:先贴 CK 拉取'}
|
||||
placeholder={hasSelectableGifts ? '输入关键字搜索,点选礼包' : '请先在上方「礼包管理」配置标准礼包'}
|
||||
options={giftSelectOptions(createGiftValue)}
|
||||
disabled={giftOptions.length === 0 && !createGiftValue}
|
||||
notFoundContent={giftOptions.length === 0 ? '先在左侧贴一份有效 CK 拉取礼包列表' : '没有匹配的礼包'}
|
||||
disabled={!hasSelectableGifts && !createGiftValue}
|
||||
notFoundContent={hasSelectableGifts
|
||||
? '没有匹配的礼包'
|
||||
: '实时礼包为空,标准字典也还没有配置'}
|
||||
onChange={(value?: string) => {
|
||||
// 选了就清掉「上次那个礼包不在本次列表里」的提示,并带出积分价格
|
||||
setGiftHint('');
|
||||
@@ -3256,17 +3296,17 @@ export default function HuyaCollabPage() {
|
||||
}),
|
||||
)}
|
||||
<Form.Item name="gift_name" label="礼包名"
|
||||
tooltip="只接受礼包缓存里的点选结果,可输入关键字搜索;不接受手工输入">
|
||||
tooltip="从实时活动礼包或管理员维护的标准字典里点选,可输入关键字搜索">
|
||||
<Select
|
||||
allowClear
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
placeholder={giftOptions.length > 0 ? '输入关键字搜索,点选礼包' : '礼包缓存为空:先在「新建」里贴 CK 拉一次'}
|
||||
placeholder={hasSelectableGifts ? '输入关键字搜索,点选礼包' : '请先让管理员配置标准礼包'}
|
||||
options={giftSelectOptions(supplementGiftValue ?? supplementTarget.gift_name)}
|
||||
disabled={giftOptions.length === 0 && !supplementGiftValue}
|
||||
notFoundContent={giftOptions.length === 0
|
||||
? '礼包缓存为空:先在「新建」里贴 CK 拉一次'
|
||||
: '没有匹配的礼包'}
|
||||
disabled={!hasSelectableGifts && !supplementGiftValue}
|
||||
notFoundContent={hasSelectableGifts
|
||||
? '没有匹配的礼包'
|
||||
: '实时礼包为空,标准字典也还没有配置'}
|
||||
onChange={(value?: string) => {
|
||||
supplementForm.setFieldValue('required_points', giftRequiredPoints(value));
|
||||
}}
|
||||
@@ -3587,6 +3627,13 @@ export default function HuyaCollabPage() {
|
||||
onClose={() => setShopModalOpen(false)}
|
||||
onChanged={() => void load()}
|
||||
/>
|
||||
<HuyaCollabGiftModal
|
||||
open={giftModalOpen}
|
||||
gifts={giftCatalog}
|
||||
canManage={canManage}
|
||||
onClose={() => setGiftModalOpen(false)}
|
||||
onChanged={() => void load()}
|
||||
/>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user