1、增加消息推送的前台横幅显示

2、增加apikey的整体配额增加和修改记录显示
This commit is contained in:
2026-08-07 11:35:41 +08:00
parent f5d7cfadb4
commit 9538f6157d
16 changed files with 929 additions and 93 deletions
@@ -0,0 +1,192 @@
import React, { useState } from 'react';
import {
Modal, Radio, InputNumber, Input, Select, Space, Typography, Tag, Divider, message,
} from 'antd';
import { adjustApiKeyQuota } from '../api';
interface QuotaAdjustModalProps {
open: boolean;
keyId: string;
companyName: string;
quotaLimit: number | null;
quotaUsed: number;
quotaCycle: string | null;
onCancel: () => void;
onSuccess: () => void;
}
const QuotaAdjustModal: React.FC<QuotaAdjustModalProps> = ({
open, keyId, companyName, quotaLimit, quotaUsed, quotaCycle, onCancel, onSuccess,
}) => {
const [action, setAction] = useState<'adjust' | 'reset_usage' | 'set_limit' | 'change_cycle'>('adjust');
const [delta, setDelta] = useState<number>(0);
const [newLimit, setNewLimit] = useState<number | null>(quotaLimit);
const [newCycle, setNewCycle] = useState<string | null>(quotaCycle);
const [reason, setReason] = useState<string>('');
const [loading, setLoading] = useState(false);
const cycleLabel = (cycle: string | null) => {
const map: Record<string, string> = { daily: '每日', monthly: '每月', one_time: '一次性' };
return cycle ? map[cycle] || cycle : '无限';
};
const handleOk = async () => {
setLoading(true);
try {
const payload: any = { action, reason: reason || undefined };
if (action === 'adjust') payload.quotaLimitDelta = delta;
if (action === 'set_limit') payload.quotaLimit = newLimit;
if (action === 'change_cycle') payload.quotaCycle = newCycle;
await adjustApiKeyQuota(keyId, payload);
message.success('配额调整成功');
onSuccess();
} catch (e: any) {
message.error(e?.response?.data?.detail || '调整失败');
} finally {
setLoading(false);
}
};
const handleCancel = () => {
setAction('adjust');
setDelta(0);
setNewLimit(quotaLimit);
setNewCycle(quotaCycle);
setReason('');
onCancel();
};
// 预览计算
const previewLimit = action === 'adjust'
? round((quotaLimit || 0) + delta)
: action === 'set_limit'
? newLimit
: quotaLimit;
function round(n: number) {
return Math.round(n * 100) / 100;
}
return (
<Modal
title="调整配额"
open={open}
onOk={handleOk}
onCancel={handleCancel}
okText="确认调整"
cancelText="取消"
confirmLoading={loading}
width={480}
>
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
<div>
<Typography.Text type="secondary"></Typography.Text>
<Typography.Text strong>{companyName}</Typography.Text>
</div>
<div>
<Typography.Text type="secondary"></Typography.Text>
<Tag color="blue"> {quotaUsed.toFixed(2)} </Tag>
<Tag color="green"> {quotaLimit != null ? `${quotaLimit.toFixed(2)}` : '无限'}</Tag>
<Tag color="purple">{cycleLabel(quotaCycle)}</Tag>
</div>
<Divider style={{ margin: '8px 0' }} />
<Radio.Group value={action} onChange={e => setAction(e.target.value)} style={{ width: '100%' }}>
<Space direction="vertical" size={12} style={{ width: '100%' }}>
<Radio value="adjust">
<Space>
<Typography.Text></Typography.Text>
{action === 'adjust' && (
<InputNumber
min={0}
step={10}
value={delta}
onChange={v => setDelta(v || 0)}
addonAfter="元"
style={{ width: 160 }}
/>
)}
</Space>
</Radio>
<Radio value="reset_usage">
<Space>
<Typography.Text></Typography.Text>
{action === 'reset_usage' && (
<Typography.Text type="secondary">
{quotaUsed.toFixed(2)} 0.00
</Typography.Text>
)}
</Space>
</Radio>
<Radio value="set_limit">
<Space>
<Typography.Text></Typography.Text>
{action === 'set_limit' && (
<>
<InputNumber
min={0}
step={10}
value={newLimit}
onChange={setNewLimit}
addonAfter="元"
placeholder="留空=无限"
style={{ width: 160 }}
/>
<Typography.Text type="secondary">
{quotaLimit != null ? `${quotaLimit.toFixed(2)}` : '无限'}
</Typography.Text>
</>
)}
</Space>
</Radio>
<Radio value="change_cycle">
<Space>
<Typography.Text></Typography.Text>
{action === 'change_cycle' && (
<Select
value={newCycle}
onChange={setNewCycle}
allowClear
placeholder="选择周期"
style={{ width: 140 }}
options={[
{ label: '每日', value: 'daily' },
{ label: '每月', value: 'monthly' },
{ label: '一次性', value: 'one_time' },
{ label: '无限', value: null },
]}
/>
)}
</Space>
</Radio>
</Space>
</Radio.Group>
{action === 'adjust' && delta > 0 && (
<div style={{ padding: '8px 12px', background: '#f0f5ff', borderRadius: 6, fontSize: 13 }}>
<strong style={{ color: '#1677ff' }}>{previewLimit != null ? `${previewLimit.toFixed(2)}` : '无限'}</strong>
</div>
)}
<div>
<Typography.Text type="secondary" style={{ fontSize: 12 }}></Typography.Text>
<Input.TextArea
value={reason}
onChange={e => setReason(e.target.value)}
placeholder="请输入调整原因..."
rows={2}
maxLength={500}
style={{ marginTop: 4 }}
/>
</div>
</Space>
</Modal>
);
};
export default QuotaAdjustModal;