🎫 feat: Enhance redemption code expiry handling & improve UI responsiveness
Backend • Introduced `validateExpiredTime` helper in `controller/redemption.go`; reused in both Add & Update endpoints to enforce “expiry time must not be earlier than now”, eliminating duplicated checks • Removed `RedemptionCodeStatusExpired` constant and all related references – expiry is now determined exclusively by the `expired_time` field for simpler, safer state management • Simplified `DeleteInvalidRedemptions`: deletes codes that are `used` / `disabled` or `enabled` but already expired, without relying on extra status codes • Controller no longer mutates `status` when listing or fetching redemption codes; clients derive expiry status from timestamp Frontend • Added reusable `isExpired` helper in `RedemptionsTable.js`; leveraged for: – status rendering (orange “Expired” tag) – action-menu enable/disable logic – row styling • Removed duplicated inline expiry logic, improving readability and performance • Adjusted toolbar layout: on small screens the “Clear invalid codes” button now wraps onto its own line, while “Add” & “Copy” remain grouped Result The codebase is now more maintainable, secure, and performant with no redundant constants, centralized validation, and cleaner UI behaviour across devices.
This commit is contained in:
@@ -59,7 +59,16 @@ function renderTimestamp(timestamp) {
|
||||
const RedemptionsTable = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const renderStatus = (status) => {
|
||||
const isExpired = (rec) => {
|
||||
return rec.status === 1 && rec.expired_time !== 0 && rec.expired_time < Math.floor(Date.now() / 1000);
|
||||
};
|
||||
|
||||
const renderStatus = (status, record) => {
|
||||
if (isExpired(record)) {
|
||||
return (
|
||||
<Tag color='orange' size='large' shape='circle' prefixIcon={<Minus size={14} />}>{t('已过期')}</Tag>
|
||||
);
|
||||
}
|
||||
switch (status) {
|
||||
case 1:
|
||||
return (
|
||||
@@ -102,7 +111,7 @@ const RedemptionsTable = () => {
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
render: (text, record, index) => {
|
||||
return <div>{renderStatus(text)}</div>;
|
||||
return <div>{renderStatus(text, record)}</div>;
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -125,6 +134,13 @@ const RedemptionsTable = () => {
|
||||
return <div>{renderTimestamp(text)}</div>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('过期时间'),
|
||||
dataIndex: 'expired_time',
|
||||
render: (text) => {
|
||||
return <div>{text === 0 ? t('永不过期') : renderTimestamp(text)}</div>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('兑换人ID'),
|
||||
dataIndex: 'used_user_id',
|
||||
@@ -158,8 +174,7 @@ const RedemptionsTable = () => {
|
||||
}
|
||||
];
|
||||
|
||||
// 动态添加启用/禁用按钮
|
||||
if (record.status === 1) {
|
||||
if (record.status === 1 && !isExpired(record)) {
|
||||
moreMenuItems.push({
|
||||
node: 'item',
|
||||
name: t('禁用'),
|
||||
@@ -169,7 +184,7 @@ const RedemptionsTable = () => {
|
||||
manageRedemption(record.id, 'disable', record);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
} else if (!isExpired(record)) {
|
||||
moreMenuItems.push({
|
||||
node: 'item',
|
||||
name: t('启用'),
|
||||
@@ -436,7 +451,7 @@ const RedemptionsTable = () => {
|
||||
};
|
||||
|
||||
const handleRow = (record, index) => {
|
||||
if (record.status !== 1) {
|
||||
if (record.status !== 1 || isExpired(record)) {
|
||||
return {
|
||||
style: {
|
||||
background: 'var(--semi-color-disabled-border)',
|
||||
@@ -459,39 +474,66 @@ const RedemptionsTable = () => {
|
||||
<Divider margin="12px" />
|
||||
|
||||
<div className="flex flex-col md:flex-row justify-between items-center gap-4 w-full">
|
||||
<div className="flex gap-2 w-full md:w-auto order-2 md:order-1">
|
||||
<div className="flex flex-col sm:flex-row gap-2 w-full md:w-auto order-2 md:order-1">
|
||||
<div className="flex gap-2 w-full sm:w-auto">
|
||||
<Button
|
||||
theme='light'
|
||||
type='primary'
|
||||
icon={<IconPlus />}
|
||||
className="!rounded-full w-full sm:w-auto"
|
||||
onClick={() => {
|
||||
setEditingRedemption({
|
||||
id: undefined,
|
||||
});
|
||||
setShowEdit(true);
|
||||
}}
|
||||
>
|
||||
{t('添加兑换码')}
|
||||
</Button>
|
||||
<Button
|
||||
type='warning'
|
||||
icon={<IconCopy />}
|
||||
className="!rounded-full w-full sm:w-auto"
|
||||
onClick={async () => {
|
||||
if (selectedKeys.length === 0) {
|
||||
showError(t('请至少选择一个兑换码!'));
|
||||
return;
|
||||
}
|
||||
let keys = '';
|
||||
for (let i = 0; i < selectedKeys.length; i++) {
|
||||
keys +=
|
||||
selectedKeys[i].name + ' ' + selectedKeys[i].key + '\n';
|
||||
}
|
||||
await copyText(keys);
|
||||
}}
|
||||
>
|
||||
{t('复制所选兑换码到剪贴板')}
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
theme='light'
|
||||
type='primary'
|
||||
icon={<IconPlus />}
|
||||
className="!rounded-full w-full md:w-auto"
|
||||
type='danger'
|
||||
icon={<IconDelete />}
|
||||
className="!rounded-full w-full sm:w-auto"
|
||||
onClick={() => {
|
||||
setEditingRedemption({
|
||||
id: undefined,
|
||||
Modal.confirm({
|
||||
title: t('确定清除所有失效兑换码?'),
|
||||
content: t('将删除已使用、已禁用及过期的兑换码,此操作不可撤销。'),
|
||||
onOk: async () => {
|
||||
setLoading(true);
|
||||
const res = await API.delete('/api/redemption/invalid');
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
showSuccess(t('已删除 {{count}} 条失效兑换码', { count: data }));
|
||||
await refresh();
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
setLoading(false);
|
||||
},
|
||||
});
|
||||
setShowEdit(true);
|
||||
}}
|
||||
>
|
||||
{t('添加兑换码')}
|
||||
</Button>
|
||||
<Button
|
||||
type='warning'
|
||||
icon={<IconCopy />}
|
||||
className="!rounded-full w-full md:w-auto"
|
||||
onClick={async () => {
|
||||
if (selectedKeys.length === 0) {
|
||||
showError(t('请至少选择一个兑换码!'));
|
||||
return;
|
||||
}
|
||||
let keys = '';
|
||||
for (let i = 0; i < selectedKeys.length; i++) {
|
||||
keys +=
|
||||
selectedKeys[i].name + ' ' + selectedKeys[i].key + '\n';
|
||||
}
|
||||
await copyText(keys);
|
||||
}}
|
||||
>
|
||||
{t('复制所选兑换码到剪贴板')}
|
||||
{t('清除失效兑换码')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user