🔄 fix(tables): keep current page after edits & auto-fallback when page becomes empty
Includes ChannelsTable, RedemptionsTable and UsersTable: • Refactor `refresh(page = activePage)` in all three tables so data reloads the requested (or current) page instead of forcing page 1. • On single-row deletion (and bulk deletion in ChannelsTable): – Refresh current page immediately. – If the refreshed page has no data and `activePage > 1`, automatically load the previous page to avoid blank views. • RedemptionsTable: corrected prior bug where `refresh` used `activePage - 1`. • Misc: removed outdated inline comments and aligned search / reset flows. Result: smoother UX—users stay on their working page, and pagination gracefully adjusts after deletions.
This commit is contained in:
@@ -547,9 +547,15 @@ const ChannelsTable = () => {
|
|||||||
title: t('确定是否要删除此渠道?'),
|
title: t('确定是否要删除此渠道?'),
|
||||||
content: t('此修改将不可逆'),
|
content: t('此修改将不可逆'),
|
||||||
onOk: () => {
|
onOk: () => {
|
||||||
manageChannel(record.id, 'delete', record).then(() => {
|
(async () => {
|
||||||
removeRecord(record);
|
await manageChannel(record.id, 'delete', record);
|
||||||
});
|
await refresh();
|
||||||
|
setTimeout(() => {
|
||||||
|
if (channels.length === 0 && activePage > 1) {
|
||||||
|
refresh(activePage - 1);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
})();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -1004,12 +1010,12 @@ const ChannelsTable = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const refresh = async () => {
|
const refresh = async (page = activePage) => {
|
||||||
const { searchKeyword, searchGroup, searchModel } = getFormValues();
|
const { searchKeyword, searchGroup, searchModel } = getFormValues();
|
||||||
if (searchKeyword === '' && searchGroup === '' && searchModel === '') {
|
if (searchKeyword === '' && searchGroup === '' && searchModel === '') {
|
||||||
await loadChannels(activePage, pageSize, idSort, enableTagMode);
|
await loadChannels(page, pageSize, idSort, enableTagMode);
|
||||||
} else {
|
} else {
|
||||||
await searchChannels(enableTagMode, activeTypeKey, statusFilter, activePage, pageSize, idSort);
|
await searchChannels(enableTagMode, activeTypeKey, statusFilter, page, pageSize, idSort);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1569,6 +1575,11 @@ const ChannelsTable = () => {
|
|||||||
if (success) {
|
if (success) {
|
||||||
showSuccess(t('已删除 ${data} 个通道!').replace('${data}', data));
|
showSuccess(t('已删除 ${data} 个通道!').replace('${data}', data));
|
||||||
await refresh();
|
await refresh();
|
||||||
|
setTimeout(() => {
|
||||||
|
if (channels.length === 0 && activePage > 1) {
|
||||||
|
refresh(activePage - 1);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
} else {
|
} else {
|
||||||
showError(message);
|
showError(message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,9 +151,15 @@ const RedemptionsTable = () => {
|
|||||||
title: t('确定是否要删除此兑换码?'),
|
title: t('确定是否要删除此兑换码?'),
|
||||||
content: t('此修改将不可逆'),
|
content: t('此修改将不可逆'),
|
||||||
onOk: () => {
|
onOk: () => {
|
||||||
manageRedemption(record.id, 'delete', record).then(() => {
|
(async () => {
|
||||||
removeRecord(record.key);
|
await manageRedemption(record.id, 'delete', record);
|
||||||
});
|
await refresh();
|
||||||
|
setTimeout(() => {
|
||||||
|
if (redemptions.length === 0 && activePage > 1) {
|
||||||
|
refresh(activePage - 1);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
})();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -320,8 +326,13 @@ const RedemptionsTable = () => {
|
|||||||
});
|
});
|
||||||
}, [pageSize]);
|
}, [pageSize]);
|
||||||
|
|
||||||
const refresh = async () => {
|
const refresh = async (page = activePage) => {
|
||||||
await loadRedemptions(activePage - 1, pageSize);
|
const { searchKeyword } = getFormValues();
|
||||||
|
if (searchKeyword === '') {
|
||||||
|
await loadRedemptions(page, pageSize);
|
||||||
|
} else {
|
||||||
|
await searchRedemptions(searchKeyword, page, pageSize);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const manageRedemption = async (id, action, record) => {
|
const manageRedemption = async (id, action, record) => {
|
||||||
@@ -541,7 +552,6 @@ const RedemptionsTable = () => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (formApi) {
|
if (formApi) {
|
||||||
formApi.reset();
|
formApi.reset();
|
||||||
// 重置后立即查询,使用setTimeout确保表单重置完成
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setActivePage(1);
|
setActivePage(1);
|
||||||
loadRedemptions(1, pageSize);
|
loadRedemptions(1, pageSize);
|
||||||
|
|||||||
@@ -61,6 +61,52 @@ const TokensTable = () => {
|
|||||||
title: t('名称'),
|
title: t('名称'),
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: t('剩余'),
|
||||||
|
key: 'quota',
|
||||||
|
render: (text, record) => {
|
||||||
|
if (record.unlimited_quota) {
|
||||||
|
return <Tag color='white' shape='circle'>{t('无限制')}</Tag>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const used = parseInt(record.used_quota) || 0;
|
||||||
|
const remain = parseInt(record.remain_quota) || 0;
|
||||||
|
const total = used + remain;
|
||||||
|
// 计算剩余额度百分比,100% 表示额度未使用
|
||||||
|
const percent = total > 0 ? (remain / total) * 100 : 0;
|
||||||
|
|
||||||
|
// 根据剩余百分比动态设置颜色,100% 绿色,<=10% 红色,<=30% 黄色,其余默认
|
||||||
|
const getProgressColor = (pct) => {
|
||||||
|
if (pct === 100) return 'var(--semi-color-success)';
|
||||||
|
if (pct <= 10) return 'var(--semi-color-danger)';
|
||||||
|
if (pct <= 30) return 'var(--semi-color-warning)';
|
||||||
|
return undefined; // 默认颜色
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
content={
|
||||||
|
<div className='text-xs'>
|
||||||
|
<div>{t('已用额度')}: {renderQuota(used)}</div>
|
||||||
|
<div>{t('剩余额度')}: {renderQuota(remain)} ({percent.toFixed(0)}%)</div>
|
||||||
|
<div>{t('总额度')}: {renderQuota(total)}</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className='w-[30px]'>
|
||||||
|
<Progress
|
||||||
|
percent={percent}
|
||||||
|
stroke={getProgressColor(percent)}
|
||||||
|
showInfo={false}
|
||||||
|
aria-label='quota usage'
|
||||||
|
type="circle"
|
||||||
|
size='small'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: t('状态'),
|
title: t('状态'),
|
||||||
dataIndex: 'status',
|
dataIndex: 'status',
|
||||||
@@ -172,52 +218,6 @@ const TokensTable = () => {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: t('剩余额度'),
|
|
||||||
key: 'quota',
|
|
||||||
render: (text, record) => {
|
|
||||||
if (record.unlimited_quota) {
|
|
||||||
return <Tag color='white' shape='circle'>{t('无限制')}</Tag>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const used = parseInt(record.used_quota) || 0;
|
|
||||||
const remain = parseInt(record.remain_quota) || 0;
|
|
||||||
const total = used + remain;
|
|
||||||
// 计算剩余额度百分比,100% 表示额度未使用
|
|
||||||
const percent = total > 0 ? (remain / total) * 100 : 0;
|
|
||||||
|
|
||||||
// 根据剩余百分比动态设置颜色,100% 绿色,<=10% 红色,<=30% 黄色,其余默认
|
|
||||||
const getProgressColor = (pct) => {
|
|
||||||
if (pct === 100) return 'var(--semi-color-success)';
|
|
||||||
if (pct <= 10) return 'var(--semi-color-danger)';
|
|
||||||
if (pct <= 30) return 'var(--semi-color-warning)';
|
|
||||||
return undefined; // 默认颜色
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip
|
|
||||||
content={
|
|
||||||
<div className='text-xs'>
|
|
||||||
<div>{t('已用额度')}: {renderQuota(used)}</div>
|
|
||||||
<div>{t('剩余额度')}: {renderQuota(remain)} ({percent.toFixed(0)}%)</div>
|
|
||||||
<div>{t('总额度')}: {renderQuota(total)}</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className='w-[30px]'>
|
|
||||||
<Progress
|
|
||||||
percent={percent}
|
|
||||||
stroke={getProgressColor(percent)}
|
|
||||||
showInfo={false}
|
|
||||||
aria-label='quota usage'
|
|
||||||
type="circle"
|
|
||||||
size='small'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: t('可用模型'),
|
title: t('可用模型'),
|
||||||
dataIndex: 'model_limits',
|
dataIndex: 'model_limits',
|
||||||
@@ -427,9 +427,10 @@ const TokensTable = () => {
|
|||||||
title: t('确定是否要删除此令牌?'),
|
title: t('确定是否要删除此令牌?'),
|
||||||
content: t('此修改将不可逆'),
|
content: t('此修改将不可逆'),
|
||||||
onOk: () => {
|
onOk: () => {
|
||||||
manageToken(record.id, 'delete', record).then(() => {
|
(async () => {
|
||||||
removeRecord(record.key);
|
await manageToken(record.id, 'delete', record);
|
||||||
});
|
await refresh();
|
||||||
|
})();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@@ -503,8 +504,8 @@ const TokensTable = () => {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const refresh = async () => {
|
const refresh = async (page = activePage) => {
|
||||||
await loadTokens(1);
|
await loadTokens(page);
|
||||||
setSelectedKeys([]);
|
setSelectedKeys([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -680,6 +681,11 @@ const TokensTable = () => {
|
|||||||
const count = res.data.data || 0;
|
const count = res.data.data || 0;
|
||||||
showSuccess(t('已删除 {{count}} 个令牌!', { count }));
|
showSuccess(t('已删除 {{count}} 个令牌!', { count }));
|
||||||
await refresh();
|
await refresh();
|
||||||
|
setTimeout(() => {
|
||||||
|
if (tokens.length === 0 && activePage > 1) {
|
||||||
|
refresh(activePage - 1);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
} else {
|
} else {
|
||||||
showError(res?.data?.message || t('删除失败'));
|
showError(res?.data?.message || t('删除失败'));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -247,9 +247,15 @@ const UsersTable = () => {
|
|||||||
title: t('确定是否要注销此用户?'),
|
title: t('确定是否要注销此用户?'),
|
||||||
content: t('相当于删除用户,此修改将不可逆'),
|
content: t('相当于删除用户,此修改将不可逆'),
|
||||||
onOk: () => {
|
onOk: () => {
|
||||||
manageUser(record.id, 'delete', record).then(() => {
|
(async () => {
|
||||||
removeRecord(record.id);
|
await manageUser(record.id, 'delete', record);
|
||||||
});
|
await refresh();
|
||||||
|
setTimeout(() => {
|
||||||
|
if (users.length === 0 && activePage > 1) {
|
||||||
|
refresh(activePage - 1);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
})();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -459,13 +465,12 @@ const UsersTable = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const refresh = async () => {
|
const refresh = async (page = activePage) => {
|
||||||
setActivePage(1);
|
|
||||||
const { searchKeyword, searchGroup } = getFormValues();
|
const { searchKeyword, searchGroup } = getFormValues();
|
||||||
if (searchKeyword === '' && searchGroup === '') {
|
if (searchKeyword === '' && searchGroup === '') {
|
||||||
await loadUsers(1, pageSize);
|
await loadUsers(page, pageSize);
|
||||||
} else {
|
} else {
|
||||||
await searchUsers(1, pageSize, searchKeyword, searchGroup);
|
await searchUsers(page, pageSize, searchKeyword, searchGroup);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -606,7 +611,6 @@ const UsersTable = () => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (formApi) {
|
if (formApi) {
|
||||||
formApi.reset();
|
formApi.reset();
|
||||||
// 重置后立即查询,使用setTimeout确保表单重置完成
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setActivePage(1);
|
setActivePage(1);
|
||||||
loadUsers(1, pageSize);
|
loadUsers(1, pageSize);
|
||||||
|
|||||||
Reference in New Issue
Block a user