feat(huya): 增加协作单礼包字典与名称编辑
This commit is contained in:
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user