feat(billing): add feature pricing configuration

This commit is contained in:
Jesse_Chen
2026-08-31 03:37:44 +08:00
parent da4c03a857
commit aaaa5fb73c
10 changed files with 518 additions and 1 deletions
@@ -111,6 +111,8 @@ export function AdminApp({ children }: { children: ReactNode }) {
{ name: "consultations", list: "/admin/consultations", meta: { label: "咨询请求", icon: <MessageOutlined /> } },
{ name: "usage", list: "/admin/usage", meta: { label: "用量与成本", icon: <ExperimentOutlined /> } },
{ name: "models", list: "/admin/models", meta: { label: "模型配置", icon: <ApiOutlined /> } },
{ name: "feature-pricing", list: "/admin/feature-pricing", meta: { label: "功能定价", icon: <CreditCardOutlined /> } },
{ name: "pricing-simulator", list: "/admin/pricing-simulator", meta: { label: "定价测算", icon: <ExperimentOutlined /> } },
{ name: "model-releases", list: "/admin/model-releases", meta: { label: "模型发布", icon: <ControlOutlined /> } },
{ name: "feature-flags", list: "/admin/feature-flags", meta: { label: "功能开关", icon: <ControlOutlined /> } },
{ name: "security", list: "/admin/security", meta: { label: "安全验证", icon: <SafetyCertificateOutlined /> } },
@@ -0,0 +1,107 @@
"use client";
import { EditOutlined, PlusOutlined, UploadOutlined } from "@ant-design/icons";
import { useGetIdentity } from "@refinedev/core";
import { Button, Card, Form, Input, InputNumber, Modal, Select, Space, Table, Tag, Typography } from "antd";
import { useEffect, useState } from "react";
import { adminRequestJson } from "@/lib/admin/providers";
type Row = {
id: string;
featureKey: string;
modelTier: string;
creditCost: number;
version: number;
status: string;
enabled: boolean;
effectiveFrom: string | null;
effectiveTo: string | null;
};
const featureOptions = ["chat.standard", "chat.premium", "rectification", "report.full", "report.export", "profile.extra"];
const tierOptions = ["standard", "premium", "internal"];
export function FeaturePricingManagement() {
const { data: identity } = useGetIdentity<{ permissions?: string[] }>();
const [rows, setRows] = useState<Row[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [refresh, setRefresh] = useState(0);
const [editing, setEditing] = useState<Row | null | undefined>();
const [form] = Form.useForm();
const canWrite = Boolean(identity?.permissions?.includes("billing.products.write"));
const canPublish = Boolean(identity?.permissions?.includes("billing.products.publish"));
useEffect(() => {
let active = true;
setIsLoading(true);
adminRequestJson<{ data: Row[] }>("/api/admin/feature-pricing")
.then((payload) => { if (active) setRows(payload.data); })
.finally(() => { if (active) setIsLoading(false); });
return () => { active = false; };
}, [refresh]);
const refetch = async () => setRefresh((value) => value + 1);
async function save() {
const values = await form.validateFields();
await adminRequestJson("/api/admin/feature-pricing", {
method: "POST",
body: JSON.stringify({ action: "saveDraft", ...values, id: editing?.id ?? null, reason: values.reason }),
});
setEditing(undefined);
form.resetFields();
await refetch();
}
async function publish(row: Row) {
await adminRequestJson("/api/admin/feature-pricing", {
method: "POST",
body: JSON.stringify({ action: "publish", id: row.id, reason: "后台发布功能定价草稿" }),
});
await refetch();
}
return (
<Card
title="功能级定价"
extra={canWrite ? <Button icon={<PlusOutlined />} onClick={() => setEditing(null)}>稿</Button> : null}
>
<Typography.Paragraph type="secondary">
fail-closed退
</Typography.Paragraph>
<Table<Row>
rowKey="id"
loading={isLoading}
dataSource={rows}
columns={[
{ title: "功能", dataIndex: "featureKey" },
{ title: "模型档位", dataIndex: "modelTier" },
{ title: "积分", dataIndex: "creditCost" },
{ title: "版本", dataIndex: "version" },
{ title: "状态", dataIndex: "status", render: (value: string) => <Tag color={value === "published" ? "green" : "default"}>{value}</Tag> },
{
title: "操作",
render: (_: unknown, row: Row) => (
<Space>
{row.status === "draft" && canWrite ? <Button icon={<EditOutlined />} onClick={() => {
form.setFieldsValue({ featureKey: row.featureKey, modelTier: row.modelTier, creditCost: row.creditCost });
setEditing(row);
}}></Button> : null}
{row.status === "draft" && canPublish ? <Button icon={<UploadOutlined />} onClick={() => publish(row)}></Button> : null}
</Space>
),
},
]}
/>
<Modal open={editing !== undefined} title={editing ? "编辑定价草稿" : "新建定价草稿"} onCancel={() => { setEditing(undefined); form.resetFields(); }} onOk={save} okButtonProps={{ disabled: !canWrite }}>
<Form form={form} layout="vertical">
<Form.Item name="featureKey" label="功能" rules={[{ required: true }]}><Select options={featureOptions.map((value) => ({ value, label: value }))} disabled={Boolean(editing)} /></Form.Item>
<Form.Item name="modelTier" label="模型档位" rules={[{ required: true }]}><Select options={tierOptions.map((value) => ({ value, label: value }))} disabled={Boolean(editing)} /></Form.Item>
<Form.Item name="creditCost" label="积分" rules={[{ required: true, type: "number", min: 1 }]}><InputNumber precision={0} min={1} style={{ width: "100%" }} /></Form.Item>
<Form.Item name="reason" label="变更原因" rules={[{ required: true, min: 1, max: 500 }]}><Input /></Form.Item>
</Form>
</Modal>
</Card>
);
}