diff --git a/src/pages/Membership.tsx b/src/pages/Membership.tsx new file mode 100644 index 0000000..741666e --- /dev/null +++ b/src/pages/Membership.tsx @@ -0,0 +1,79 @@ +import { useState } from 'react' +import { useQuery, useQueryClient } from '@tanstack/react-query' +import { Table, Button, Typography, App, Modal, Input, InputNumber, Tag, Tabs, Space } from 'antd' +import { ReloadOutlined, PlusOutlined, DollarOutlined } from '@ant-design/icons' +import { api } from '@/services/http-client' +import dayjs from 'dayjs' + +const { Title } = Typography + +function MembershipPage() { + const { message } = App.useApp() + const qc = useQueryClient() + const [addOpen, setAddOpen] = useState(false) + const [newPlan, setNewPlan] = useState({ name: '', code: '', priceMonthly: 0, monthlyChatCount: 10 }) + + const { data: plans } = useQuery({ queryKey: ['quota', 'plans'], queryFn: (): Promise => api.get('/admin-api/quota/plans') }) + const { data: memberships } = useQuery({ queryKey: ['quota', 'memberships'], queryFn: (): Promise => api.get('/admin-api/quota/memberships') }) + const { data: costs } = useQuery({ queryKey: ['quota', 'costs'], queryFn: (): Promise => api.get('/admin-api/quota/costs') }) + + const addPlan = async () => { + await api.post('/admin-api/quota/plans', newPlan) + message.success('已添加') + setAddOpen(false) + qc.invalidateQueries({ queryKey: ['quota'] }) + } + + const planCols = [ + { title: '名称', dataIndex: 'name', width: 120 }, + { title: '代码', dataIndex: 'code', width: 100 }, + { title: '月费', dataIndex: 'priceMonthly', width: 80, render: (v: number) => `¥${v}` }, + { title: '年费', dataIndex: 'priceYearly', width: 80, render: (v: number) => `¥${v}` }, + { title: '月聊天', dataIndex: 'monthlyChatCount', width: 80 }, + { title: '月OCR', dataIndex: 'monthlyOcrPages', width: 80 }, + { title: '状态', dataIndex: 'isActive', width: 70, render: (v: boolean) => {v ? '启用' : '禁用'} }, + ] + + const memberCols = [ + { title: '用户', dataIndex: ['user', 'email'], width: 200 }, + { title: '计划', dataIndex: ['plan', 'name'], width: 120 }, + { title: '开始', dataIndex: 'startedAt', width: 100, render: (d: string) => dayjs(d).format('MM-DD') }, + { title: '状态', dataIndex: 'active', width: 70, render: (v: boolean) => {v ? '有效' : '失效'} }, + ] + + const costCols = [ + { title: '日期', dataIndex: 'date', width: 100, render: (d: string) => dayjs(d).format('MM-DD') }, + { title: '服务商', dataIndex: 'provider', width: 80 }, + { title: '模型', dataIndex: 'model', width: 120 }, + { title: '调用', dataIndex: 'calls', width: 70, align: 'center' as const }, + { title: 'Token', dataIndex: 'tokens', width: 90, align: 'center' as const }, + { title: '费用', dataIndex: 'cost', width: 80, render: (v: number) => `¥${v.toFixed(4)}` }, + ] + + return ( +
+
+ <DollarOutlined /> 会员与额度 + + + + +
+ + }, + { key: 'members', label: '用户会员', children: }, + { key: 'costs', label: '成本汇总', children:
}, + ]} /> + + setAddOpen(false)} okText="添加"> + setNewPlan({ ...newPlan, name: e.target.value })} style={{ marginBottom: 12 }} /> + setNewPlan({ ...newPlan, code: e.target.value })} style={{ marginBottom: 12 }} /> + setNewPlan({ ...newPlan, priceMonthly: v || 0 })} style={{ width: '100%', marginBottom: 12 }} prefix="¥" /> + setNewPlan({ ...newPlan, monthlyChatCount: v || 0 })} style={{ width: '100%' }} /> + + + ) +} + +export default MembershipPage