feat(huya): 增加协作单礼包字典与名称编辑

This commit is contained in:
yml2213
2026-11-07 16:54:01 +08:00
parent afcbff2207
commit efa8bf2a46
12 changed files with 1183 additions and 44 deletions
@@ -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>
</>
);
}