Files
Jyotisha/frontend/src/components/admin/feature-pricing-management.tsx
T
Jesse_Chen afb8305ca1
Independent Staging Quality Gate / validate (push) Failing after 26m23s
Independent Staging Quality Gate / publish (push) Has been skipped
fix admin feature pricing visibility
2026-08-31 19:57:08 +08:00

116 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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 {
FEATURE_PRICING_OPTIONS,
MODEL_TIER_OPTIONS,
featurePricingLabel,
modelTierLabel,
} from "@/lib/admin/feature-pricing-labels";
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 statusLabels: Record<string, string> = {
draft: "草稿(draft)",
published: "已发布(published)",
retired: "已停用(retired)",
};
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;
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", render: featurePricingLabel },
{ title: "模型档位", dataIndex: "modelTier", render: modelTierLabel },
{ title: "积分", dataIndex: "creditCost" },
{ title: "版本", dataIndex: "version" },
{ title: "状态", dataIndex: "status", render: (value: string) => <Tag color={value === "published" ? "green" : "default"}>{statusLabels[value] ?? 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={[...FEATURE_PRICING_OPTIONS]} disabled={Boolean(editing)} /></Form.Item>
<Form.Item name="modelTier" label="模型档位" rules={[{ required: true }]}><Select options={[...MODEL_TIER_OPTIONS]} 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>
);
}