操作日志中文化与功能优化

- 后端操作描述统一使用中文冒号(:)替换英文冒号(:)
- 前端资源名称列 ID 显示改为中文冒号
- 清理日志改为弹窗内独立日期选择器,不再依赖筛选条件
- 侧边栏操作记录移至渠道管理上方
- RPM/TPM 卡片标签改为中文显示
This commit is contained in:
wangxiaoji 2026-07-01 16:03:03 +08:00
parent 006e723ac5
commit 01900f1a3a
10 changed files with 63 additions and 34 deletions

View File

@ -1348,7 +1348,7 @@ func AddChannel(c *gin.Context) {
if addChannelRequest.Channel != nil { if addChannelRequest.Channel != nil {
channelName = addChannelRequest.Channel.Name channelName = addChannelRequest.Channel.Name
} }
service.RecordCreateOperation(c, "channel", 0, channelName, "创建渠道: "+channelName, "") service.RecordCreateOperation(c, "channel", 0, channelName, "创建渠道"+channelName, "")
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": true, "success": true,
"message": "", "message": "",
@ -1366,7 +1366,7 @@ func DeleteChannel(c *gin.Context) {
} }
model.InitChannelCache() model.InitChannelCache()
// 记录操作日志 // 记录操作日志
service.RecordDeleteOperation(c, "channel", id, "", fmt.Sprintf("删除渠道 (ID: %d)", id), "") service.RecordDeleteOperation(c, "channel", id, "", fmt.Sprintf("删除渠道 (ID%d)", id), "")
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": true, "success": true,
"message": "", "message": "",
@ -1721,7 +1721,7 @@ func UpdateChannel(c *gin.Context) {
channel.Key = "" channel.Key = ""
clearChannelInfo(&channel.Channel) clearChannelInfo(&channel.Channel)
// 记录操作日志 // 记录操作日志
service.RecordUpdateOperation(c, "channel", channel.Id, channel.Name, "更新渠道: "+channel.Name, "") service.RecordUpdateOperation(c, "channel", channel.Id, channel.Name, "更新渠道"+channel.Name, "")
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": true, "success": true,
"message": "", "message": "",

View File

@ -270,7 +270,7 @@ func CreateModelMeta(c *gin.Context) {
return return
} }
model.RefreshPricing() model.RefreshPricing()
service.RecordCreateOperation(c, "model", m.Id, m.ModelName, "创建模型: "+m.ModelName, "") service.RecordCreateOperation(c, "model", m.Id, m.ModelName, "创建模型"+m.ModelName, "")
common.ApiSuccess(c, &m) common.ApiSuccess(c, &m)
} }
@ -319,7 +319,7 @@ func UpdateModelMeta(c *gin.Context) {
} }
} }
model.RefreshPricing() model.RefreshPricing()
service.RecordUpdateOperation(c, "model", m.Id, m.ModelName, "更新模型: "+m.ModelName, "") service.RecordUpdateOperation(c, "model", m.Id, m.ModelName, "更新模型"+m.ModelName, "")
common.ApiSuccess(c, &m) common.ApiSuccess(c, &m)
} }
@ -336,7 +336,7 @@ func DeleteModelMeta(c *gin.Context) {
return return
} }
model.RefreshPricing() model.RefreshPricing()
service.RecordDeleteOperation(c, "model", id, "", fmt.Sprintf("删除模型 (ID: %d)", id), "") service.RecordDeleteOperation(c, "model", id, "", fmt.Sprintf("删除模型 (ID%d)", id), "")
common.ApiSuccess(c, nil) common.ApiSuccess(c, nil)
} }

View File

@ -690,10 +690,10 @@ func UpdateOption(c *gin.Context) {
if modelPricingOptionKeys[option.Key] { if modelPricingOptionKeys[option.Key] {
changes := computeModelPricingDiff(oldVal, valStr) changes := computeModelPricingDiff(oldVal, valStr)
if len(changes) > 0 { if len(changes) > 0 {
service.RecordUpdateWithDiff(c, "setting", 0, option.Key, "更新系统设置: "+option.Key, changes, "") service.RecordUpdateWithDiff(c, "setting", 0, option.Key, "更新系统设置"+option.Key, changes, "")
} }
} else { } else {
service.RecordUpdateOperation(c, "setting", 0, option.Key, "更新系统设置: "+option.Key, "") service.RecordUpdateOperation(c, "setting", 0, option.Key, "更新系统设置"+option.Key, "")
} }
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{

View File

@ -106,7 +106,7 @@ func AddRedemption(c *gin.Context) {
} }
keys = append(keys, key) keys = append(keys, key)
} }
service.RecordCreateOperation(c, "redemption", 0, redemption.Name, "创建兑换码: "+redemption.Name, "") service.RecordCreateOperation(c, "redemption", 0, redemption.Name, "创建兑换码"+redemption.Name, "")
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": true, "success": true,
"message": "", "message": "",
@ -122,7 +122,7 @@ func DeleteRedemption(c *gin.Context) {
common.ApiError(c, err) common.ApiError(c, err)
return return
} }
service.RecordDeleteOperation(c, "redemption", id, "", fmt.Sprintf("删除兑换码 (ID: %d)", id), "") service.RecordDeleteOperation(c, "redemption", id, "", fmt.Sprintf("删除兑换码 (ID%d)", id), "")
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": true, "success": true,
"message": "", "message": "",
@ -161,7 +161,7 @@ func UpdateRedemption(c *gin.Context) {
common.ApiError(c, err) common.ApiError(c, err)
return return
} }
service.RecordUpdateOperation(c, "redemption", cleanRedemption.Id, cleanRedemption.Name, "更新兑换码: "+cleanRedemption.Name, "") service.RecordUpdateOperation(c, "redemption", cleanRedemption.Id, cleanRedemption.Name, "更新兑换码"+cleanRedemption.Name, "")
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": true, "success": true,
"message": "", "message": "",

View File

@ -228,7 +228,7 @@ func AddToken(c *gin.Context) {
common.ApiError(c, err) common.ApiError(c, err)
return return
} }
service.RecordCreateOperation(c, "token", cleanToken.Id, cleanToken.Name, "创建令牌: "+cleanToken.Name, "") service.RecordCreateOperation(c, "token", cleanToken.Id, cleanToken.Name, "创建令牌"+cleanToken.Name, "")
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": true, "success": true,
"message": "", "message": "",
@ -243,7 +243,7 @@ func DeleteToken(c *gin.Context) {
common.ApiError(c, err) common.ApiError(c, err)
return return
} }
service.RecordDeleteOperation(c, "token", id, "", fmt.Sprintf("删除令牌 (ID: %d)", id), "") service.RecordDeleteOperation(c, "token", id, "", fmt.Sprintf("删除令牌 (ID%d)", id), "")
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": true, "success": true,
"message": "", "message": "",
@ -308,7 +308,7 @@ func UpdateToken(c *gin.Context) {
common.ApiError(c, err) common.ApiError(c, err)
return return
} }
service.RecordUpdateOperation(c, "token", cleanToken.Id, cleanToken.Name, "更新令牌: "+cleanToken.Name, "") service.RecordUpdateOperation(c, "token", cleanToken.Id, cleanToken.Name, "更新令牌"+cleanToken.Name, "")
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": true, "success": true,
"message": "", "message": "",

View File

@ -1206,7 +1206,7 @@ func UpdateUser(c *gin.Context) {
if originUser.DistributorCommissionBps != updatedUser.DistributorCommissionBps { if originUser.DistributorCommissionBps != updatedUser.DistributorCommissionBps {
changes = append(changes, service.FieldChange{Field: "分销佣金比例", OldValue: fmt.Sprintf("%d‱", originUser.DistributorCommissionBps), NewValue: fmt.Sprintf("%d‱", updatedUser.DistributorCommissionBps)}) changes = append(changes, service.FieldChange{Field: "分销佣金比例", OldValue: fmt.Sprintf("%d‱", originUser.DistributorCommissionBps), NewValue: fmt.Sprintf("%d‱", updatedUser.DistributorCommissionBps)})
} }
service.RecordUpdateWithDiff(c, "user", updatedUser.Id, updatedUser.Username, "更新用户: "+updatedUser.Username, changes, "") service.RecordUpdateWithDiff(c, "user", updatedUser.Id, updatedUser.Username, "更新用户"+updatedUser.Username, changes, "")
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": true, "success": true,
@ -1246,7 +1246,7 @@ func AdminClearUserBinding(c *gin.Context) {
} }
model.RecordLog(user.Id, model.LogTypeManage, fmt.Sprintf("admin cleared %s binding for user %s", bindingType, user.Username)) model.RecordLog(user.Id, model.LogTypeManage, fmt.Sprintf("admin cleared %s binding for user %s", bindingType, user.Username))
service.RecordDeleteOperation(c, "user_binding", user.Id, user.Username, "清除用户绑定["+bindingType+"]: "+user.Username, "") service.RecordDeleteOperation(c, "user_binding", user.Id, user.Username, "清除用户绑定["+bindingType+"]"+user.Username, "")
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": true, "success": true,
@ -1421,7 +1421,7 @@ func DeleteUser(c *gin.Context) {
}) })
return return
} }
service.RecordDeleteOperation(c, "user", id, originUser.Username, "删除用户: "+originUser.Username, "") service.RecordDeleteOperation(c, "user", id, originUser.Username, "删除用户"+originUser.Username, "")
} }
func DeleteSelf(c *gin.Context) { func DeleteSelf(c *gin.Context) {
@ -1501,7 +1501,7 @@ func CreateUser(c *gin.Context) {
common.ApiError(c, err) common.ApiError(c, err)
return return
} }
service.RecordCreateOperation(c, "user", cleanUser.Id, cleanUser.Username, "创建用户: "+cleanUser.Username, "") service.RecordCreateOperation(c, "user", cleanUser.Id, cleanUser.Username, "创建用户"+cleanUser.Username, "")
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": true, "success": true,
@ -1781,7 +1781,7 @@ func ManageUser(c *gin.Context) {
} }
changes = append(changes, service.FieldChange{Field: "学员", OldValue: oldLabel, NewValue: newLabel}) changes = append(changes, service.FieldChange{Field: "学员", OldValue: oldLabel, NewValue: newLabel})
} }
service.RecordUpdateWithDiff(c, "user", user.Id, user.Username, label+": "+user.Username, changes, "") service.RecordUpdateWithDiff(c, "user", user.Id, user.Username, label+""+user.Username, changes, "")
} }
switch req.Action { switch req.Action {

View File

@ -222,7 +222,7 @@ func commonActionLabel(action string, targetType string, targetName string) stri
} }
if targetName != "" { if targetName != "" {
return actionLabel + typeLabel + ": " + targetName return actionLabel + typeLabel + "" + targetName
} }
return actionLabel + typeLabel return actionLabel + typeLabel
} }

View File

@ -207,6 +207,12 @@ const SiderBar = ({ onNavigate = () => {} }) => {
const adminItems = useMemo(() => { const adminItems = useMemo(() => {
const items = [ const items = [
{
text: t('操作记录'),
itemKey: 'operation-log',
to: '/console/operation-log',
className: isAdmin() ? '' : 'tableHiddle',
},
{ {
text: t('渠道管理'), text: t('渠道管理'),
itemKey: 'channel', itemKey: 'channel',
@ -249,12 +255,6 @@ const SiderBar = ({ onNavigate = () => {} }) => {
to: '/user', to: '/user',
className: isAdmin() ? '' : 'tableHiddle', className: isAdmin() ? '' : 'tableHiddle',
}, },
{
text: t('操作记录'),
itemKey: 'operation-log',
to: '/console/operation-log',
className: isAdmin() ? '' : 'tableHiddle',
},
{ {
text: t('代理管理'), text: t('代理管理'),
itemKey: 'distributor', itemKey: 'distributor',

View File

@ -202,7 +202,7 @@ const RechargeCard = ({
{ {
key: 'rpm', key: 'rpm',
icon: Gauge, icon: Gauge,
label: t('RPM'), label: t('每分钟请求'),
value: rpmTpmData?.rpm, value: rpmTpmData?.rpm,
sublabel: t('最近1分钟'), sublabel: t('最近1分钟'),
raw: true, raw: true,
@ -211,7 +211,7 @@ const RechargeCard = ({
{ {
key: 'tpm', key: 'tpm',
icon: TrendingUp, icon: TrendingUp,
label: t('TPM'), label: t('每分钟Token'),
value: rpmTpmData?.tpm, value: rpmTpmData?.tpm,
sublabel: t('最近1分钟'), sublabel: t('最近1分钟'),
raw: true, raw: true,

View File

@ -85,6 +85,10 @@ const OperationLog = () => {
const [detailVisible, setDetailVisible] = useState(false); const [detailVisible, setDetailVisible] = useState(false);
const [selectedLog, setSelectedLog] = useState(null); const [selectedLog, setSelectedLog] = useState(null);
// Cleanup modal
const [deleteModalVisible, setDeleteModalVisible] = useState(false);
const [deleteDate, setDeleteDate] = useState(null);
const fetchLogs = async () => { const fetchLogs = async () => {
setLoading(true); setLoading(true);
try { try {
@ -132,14 +136,17 @@ const OperationLog = () => {
fetchLogs(); fetchLogs();
}; };
const handleDeleteOldLogs = async () => { const handleDeleteOldLogs = () => {
if (!dateRange || !dateRange[1]) { setDeleteDate(null);
setDeleteModalVisible(true);
};
const handleConfirmDelete = () => {
if (!deleteDate) {
showError('请选择截止日期'); showError('请选择截止日期');
return; return;
} }
const targetTimestamp = Math.floor( const targetTimestamp = Math.floor(new Date(deleteDate).getTime() / 1000);
new Date(dateRange[1]).getTime() / 1000
);
Modal.confirm({ Modal.confirm({
title: '确认清理', title: '确认清理',
content: `确定要清理 ${timestamp2string(targetTimestamp)} 之前的操作日志吗?此操作不可逆。`, content: `确定要清理 ${timestamp2string(targetTimestamp)} 之前的操作日志吗?此操作不可逆。`,
@ -151,6 +158,7 @@ const OperationLog = () => {
const { success, data, message } = res.data; const { success, data, message } = res.data;
if (success) { if (success) {
showSuccess(`成功清理 ${data} 条操作日志`); showSuccess(`成功清理 ${data} 条操作日志`);
setDeleteModalVisible(false);
fetchLogs(); fetchLogs();
} else { } else {
showError(message); showError(message);
@ -206,7 +214,7 @@ const OperationLog = () => {
dataIndex: 'target_name', dataIndex: 'target_name',
key: 'target_name', key: 'target_name',
width: 160, width: 160,
render: (text, record) => text || `ID: ${record.target_id}`, render: (text, record) => text || `ID${record.target_id}`,
}, },
{ {
title: t('操作描述'), title: t('操作描述'),
@ -339,6 +347,27 @@ const OperationLog = () => {
/> />
</Card> </Card>
<Modal
title={t('清理日志')}
visible={deleteModalVisible}
onCancel={() => setDeleteModalVisible(false)}
onOk={handleConfirmDelete}
okText={t('确认清理')}
cancelText={t('取消')}
width={420}
>
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
<Text>{t('选择截止日期,将清理该日期之前的所有操作日志。此操作不可逆。')}</Text>
<DatePicker
type='dateTime'
placeholder={t('选择截止日期')}
value={deleteDate}
onChange={setDeleteDate}
style={{ width: '100%' }}
/>
</div>
</Modal>
<Modal <Modal
title={t('操作详情')} title={t('操作详情')}
visible={detailVisible} visible={detailVisible}