交易流水对账明细 | AI引擎计价规则
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Card, Drawer, Input, message, Modal, Popconfirm, Select, Space, Table, Tag, Typography } from 'antd';
|
||||
import { CopyOutlined, EyeOutlined, PlusOutlined, ReloadOutlined } from '@ant-design/icons';
|
||||
import dayjs from 'dayjs';
|
||||
import {
|
||||
createModelPricingRule,
|
||||
disableModelPricingRule,
|
||||
getModelPricingRules,
|
||||
publishModelPricingRule,
|
||||
updateModelPricingRule,
|
||||
} from '../api';
|
||||
import type { ModelPricingRule, ModelPricingRulePayload } from '../types';
|
||||
import PricingRuleForm from '../components/modelPricing/PricingRuleForm';
|
||||
import PricingRulePreview from '../components/modelPricing/PricingRulePreview';
|
||||
import { formatDate } from '../utils/formatDate';
|
||||
|
||||
const statusMap: Record<string, { color: string; text: string }> = {
|
||||
draft: { color: 'default', text: '草稿' },
|
||||
published: { color: 'green', text: '已发布' },
|
||||
disabled: { color: 'red', text: '已停用' },
|
||||
};
|
||||
|
||||
const modeMap: Record<string, string> = {
|
||||
text_token_tiered: '文本分档 Token',
|
||||
image_per_output: '按成功输出图片',
|
||||
image_input_output_tiered: '输入图 + 输出像素',
|
||||
video_token_rate: '视频 Token',
|
||||
};
|
||||
|
||||
const AdminModelPricingRules: React.FC = () => {
|
||||
const [rows, setRows] = useState<ModelPricingRule[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [page, setPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(50);
|
||||
const [modelName, setModelName] = useState('');
|
||||
const [status, setStatus] = useState('');
|
||||
const [category, setCategory] = useState('');
|
||||
const [editing, setEditing] = useState<ModelPricingRule | null>(null);
|
||||
const [formOpen, setFormOpen] = useState(false);
|
||||
const [detail, setDetail] = useState<ModelPricingRule | null>(null);
|
||||
|
||||
const load = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await getModelPricingRules({
|
||||
page,
|
||||
pageSize,
|
||||
modelName: modelName || undefined,
|
||||
publishStatus: status || undefined,
|
||||
modelCategory: category || undefined,
|
||||
});
|
||||
setRows(res.items || []);
|
||||
setTotal(res.total || 0);
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '加载模型计价规则失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => { load(); }, [page, pageSize, modelName, status, category]);
|
||||
|
||||
const submit = async (payload: ModelPricingRulePayload) => {
|
||||
setSaving(true);
|
||||
try {
|
||||
if (editing?.id && editing.publishStatus === 'draft') {
|
||||
await updateModelPricingRule(editing.id, payload);
|
||||
} else {
|
||||
await createModelPricingRule(payload);
|
||||
}
|
||||
message.success('价格草稿已保存');
|
||||
setFormOpen(false);
|
||||
setEditing(null);
|
||||
await load();
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '保存失败');
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const cloneRule = (rule: ModelPricingRule) => {
|
||||
setEditing({
|
||||
...rule,
|
||||
id: '',
|
||||
publishStatus: 'draft',
|
||||
versionCode: `${rule.versionCode}_copy_${dayjs().format('YYYYMMDDHHmm')}`,
|
||||
effectiveFrom: dayjs().add(1, 'minute').toISOString(),
|
||||
effectiveTo: null,
|
||||
referencedCount: 0,
|
||||
});
|
||||
setFormOpen(true);
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{ title: '模型', dataIndex: 'modelName', width: 280, fixed: 'left' as const, render: (v: string, r: ModelPricingRule) => <div><Typography.Text strong>{v}</Typography.Text><div style={{ color: '#94a3b8', fontSize: 12 }}>{r.provider} / {r.modelCategory}</div></div> },
|
||||
{ title: '价格版本', dataIndex: 'versionCode', width: 180 },
|
||||
{ title: '计价模式/计算器', key: 'calculator', width: 230, render: (_: any, r: ModelPricingRule) => <div>{modeMap[r.billingMode] || r.billingMode}<div style={{ color: '#94a3b8', fontSize: 12 }}>{r.calculatorVersion}</div></div> },
|
||||
{ title: '生效时间', key: 'effective', width: 290, render: (_: any, r: ModelPricingRule) => <div>{formatDate(r.effectiveFrom)}<div style={{ color: '#94a3b8', fontSize: 12 }}>至 {r.effectiveTo ? formatDate(r.effectiveTo) : '长期有效'}</div></div> },
|
||||
{ title: '状态', dataIndex: 'publishStatus', width: 100, render: (v: string) => <Tag color={(statusMap[v] || {}).color}>{(statusMap[v] || {}).text || v}</Tag> },
|
||||
{ title: '规则Hash/引用', key: 'hash', width: 190, render: (_: any, r: ModelPricingRule) => <div>{r.ruleContentHash ? `${r.ruleContentHash.slice(0, 12)}…` : '-'}<div style={{ color: '#94a3b8', fontSize: 12 }}>{r.referencedCount || 0} 条引用</div></div> },
|
||||
{ title: '来源更新时间', dataIndex: 'sourceUpdatedAt', width: 170, render: (v: string) => v ? formatDate(v) : '-' },
|
||||
{ title: '操作', key: 'action', width: 310, fixed: 'right' as const, render: (_: any, r: ModelPricingRule) => <Space>
|
||||
<Button size="small" icon={<EyeOutlined />} onClick={() => setDetail(r)}>详情/试算</Button>
|
||||
{r.publishStatus === 'draft' && <Button size="small" onClick={() => { setEditing(r); setFormOpen(true); }}>编辑</Button>}
|
||||
<Button size="small" icon={<CopyOutlined />} onClick={() => cloneRule(r)}>克隆新版本</Button>
|
||||
{r.publishStatus === 'draft' && <Popconfirm title="发布后价格正文不可修改,确认发布?" onConfirm={async () => { await publishModelPricingRule(r.id); message.success('已发布'); load(); }}><Button size="small" type="primary">发布</Button></Popconfirm>}
|
||||
{r.publishStatus === 'published' && <Popconfirm title="停用后不再匹配新消费,历史快照不受影响。确认?" onConfirm={async () => { await disableModelPricingRule(r.id); message.success('已停用'); load(); }}><Button size="small" danger>停用</Button></Popconfirm>}
|
||||
</Space> },
|
||||
];
|
||||
|
||||
return <div>
|
||||
<Card bordered={false} style={{ borderRadius: 12 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 12, marginBottom: 16, flexWrap: 'wrap' }}>
|
||||
<Space wrap>
|
||||
<Input allowClear placeholder="模型名称" value={modelName} onChange={e => { setPage(1); setModelName(e.target.value); }} style={{ width: 260 }} />
|
||||
<Select value={category} onChange={v => { setPage(1); setCategory(v); }} style={{ width: 130 }} options={[{ value: '', label: '全部类型' }, { value: 'text', label: '文本' }, { value: 'image', label: '图片' }, { value: 'video', label: '视频' }]} />
|
||||
<Select value={status} onChange={v => { setPage(1); setStatus(v); }} style={{ width: 130 }} options={[{ value: '', label: '全部状态' }, { value: 'draft', label: '草稿' }, { value: 'published', label: '已发布' }, { value: 'disabled', label: '已停用' }]} />
|
||||
</Space>
|
||||
<Space>
|
||||
<Button icon={<ReloadOutlined />} onClick={load}>刷新</Button>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => { setEditing(null); setFormOpen(true); }}>新增价格版本</Button>
|
||||
</Space>
|
||||
</div>
|
||||
<Table rowKey="id" columns={columns} dataSource={rows} loading={loading} scroll={{ x: 1500 }} pagination={{ current: page, pageSize, total, showSizeChanger: true, onChange: (p, ps) => { setPage(p); setPageSize(ps); } }} />
|
||||
</Card>
|
||||
|
||||
<Modal open={formOpen} title={editing?.id ? '编辑价格草稿' : editing ? '克隆价格版本' : '新增价格版本'} width={1100} footer={null} destroyOnClose onCancel={() => { setFormOpen(false); setEditing(null); }}>
|
||||
<PricingRuleForm initial={editing} loading={saving} onSubmit={submit} onCancel={() => { setFormOpen(false); setEditing(null); }} />
|
||||
</Modal>
|
||||
|
||||
<Drawer open={!!detail} width={760} title={detail ? `${detail.modelName} / ${detail.versionCode}` : '计价详情'} onClose={() => setDetail(null)}>
|
||||
{detail && <>
|
||||
<Space wrap style={{ marginBottom: 12 }}><Tag>{detail.provider}</Tag><Tag>{detail.modelCategory}</Tag><Tag color="blue">{modeMap[detail.billingMode] || detail.billingMode}</Tag><Tag color={(statusMap[detail.publishStatus] || {}).color}>{(statusMap[detail.publishStatus] || {}).text}</Tag></Space>
|
||||
<Typography.Paragraph>生效:{formatDate(detail.effectiveFrom)} ~ {detail.effectiveTo ? formatDate(detail.effectiveTo) : '长期有效'}<br />计算器:{detail.calculatorVersion}<br />规则 Hash:{detail.ruleContentHash || '-'}</Typography.Paragraph>
|
||||
<Typography.Paragraph>来源:{detail.sourceUrl || '-'}<br />官方更新时间:{detail.sourceUpdatedAt ? formatDate(detail.sourceUpdatedAt) : '-'}</Typography.Paragraph>
|
||||
<pre style={{ background: '#f7f8fa', borderRadius: 8, padding: 12, overflow: 'auto' }}>{JSON.stringify(detail.ruleJson, null, 2)}</pre>
|
||||
<PricingRulePreview billingMode={detail.billingMode} calculatorVersion={detail.calculatorVersion} ruleJson={detail.ruleJson} />
|
||||
</>}
|
||||
</Drawer>
|
||||
</div>;
|
||||
};
|
||||
|
||||
export default AdminModelPricingRules;
|
||||
Reference in New Issue
Block a user