- 移除预检查算力点步骤,改为请求前预扣算力点 - 添加请求失败时自动返还算力点的功能 - 修改copyAIResponse函数参数以支持失败回调处理 - 在响应状态异常时也执行算力点返还操作 - 从auth服务中移除EnsureUserCredits方法 - 新增RefundUserCredits方法用于返还用户算力点 - 添加AI_REFUND算力点日志类型常量 - 更新算力点日志类型说明及展示标签
222 lines
9.9 KiB
TypeScript
222 lines
9.9 KiB
TypeScript
"use client";
|
|
|
|
import { DeleteOutlined, EditOutlined, PlusOutlined, ReloadOutlined, SearchOutlined } from "@ant-design/icons";
|
|
import { ProTable, type ProColumns } from "@ant-design/pro-components";
|
|
import { Button, Card, Col, Form, Input, InputNumber, Modal, Row, Space, Tag, Tooltip, Typography } from "antd";
|
|
import dayjs from "dayjs";
|
|
import { useEffect, useState } from "react";
|
|
|
|
import type { AdminCreditLog } from "@/services/api/admin";
|
|
import { useAdminCreditLogs } from "./use-admin-credit-logs";
|
|
|
|
type CreditLogFormValues = Partial<AdminCreditLog>;
|
|
|
|
const creditLogTypeLabels: Record<string, string> = {
|
|
admin_adjust: "后台调整",
|
|
ai_consume: "模型消费",
|
|
ai_refund: "失败返还",
|
|
};
|
|
|
|
export default function AdminCreditLogsPage() {
|
|
const { logs, keyword, page, pageSize, total, isLoading, searchLogs, changePage, changePageSize, resetFilters, refreshLogs, saveLog: saveAdminLog, deleteLog } = useAdminCreditLogs();
|
|
const [form] = Form.useForm<CreditLogFormValues>();
|
|
const [keywordText, setKeywordText] = useState(keyword);
|
|
const [editingLog, setEditingLog] = useState<Partial<AdminCreditLog> | null>(null);
|
|
const [deletingLog, setDeletingLog] = useState<AdminCreditLog | null>(null);
|
|
|
|
useEffect(() => setKeywordText(keyword), [keyword]);
|
|
|
|
useEffect(() => {
|
|
if (editingLog) form.setFieldsValue({ type: "admin_adjust", amount: 0, balance: 0, ...editingLog });
|
|
}, [editingLog, form]);
|
|
|
|
const saveLog = async () => {
|
|
const value = await form.validateFields();
|
|
await saveAdminLog({ ...editingLog, ...value });
|
|
setEditingLog(null);
|
|
};
|
|
|
|
const columns: ProColumns<AdminCreditLog>[] = [
|
|
{
|
|
title: "用户 ID",
|
|
dataIndex: "userId",
|
|
width: 220,
|
|
render: (_, item) => <Typography.Text copyable>{item.userId}</Typography.Text>,
|
|
},
|
|
{
|
|
title: "类型",
|
|
dataIndex: "type",
|
|
width: 140,
|
|
render: (_, item) => <Tag>{creditLogTypeLabels[item.type] || item.type || "-"}</Tag>,
|
|
},
|
|
{
|
|
title: "变动",
|
|
dataIndex: "amount",
|
|
width: 100,
|
|
render: (_, item) => <Typography.Text type={item.amount >= 0 ? "success" : "danger"}>{item.amount}</Typography.Text>,
|
|
},
|
|
{
|
|
title: "余额",
|
|
dataIndex: "balance",
|
|
width: 100,
|
|
},
|
|
{
|
|
title: "备注",
|
|
dataIndex: "remark",
|
|
ellipsis: true,
|
|
render: (_, item) => <Typography.Text type="secondary">{item.remark || "-"}</Typography.Text>,
|
|
},
|
|
{
|
|
title: "创建时间",
|
|
dataIndex: "createdAt",
|
|
width: 180,
|
|
render: (_, item) => <Typography.Text type="secondary">{item.createdAt ? dayjs(item.createdAt).format("YYYY-MM-DD HH:mm:ss") : "-"}</Typography.Text>,
|
|
},
|
|
{
|
|
title: "操作",
|
|
key: "actions",
|
|
width: 96,
|
|
align: "right",
|
|
render: (_, item) => (
|
|
<Space size={4}>
|
|
<Tooltip title="编辑">
|
|
<Button type="text" size="small" icon={<EditOutlined />} onClick={() => setEditingLog(item)} />
|
|
</Tooltip>
|
|
<Tooltip title="删除">
|
|
<Button danger type="text" size="small" icon={<DeleteOutlined />} onClick={() => setDeletingLog(item)} />
|
|
</Tooltip>
|
|
</Space>
|
|
),
|
|
},
|
|
];
|
|
|
|
return (
|
|
<main style={{ padding: 24 }}>
|
|
<Space direction="vertical" size={16} style={{ width: "100%" }}>
|
|
<Card variant="borderless">
|
|
<Form layout="vertical">
|
|
<Row gutter={16} align="bottom">
|
|
<Col flex="360px">
|
|
<Form.Item label="关键词">
|
|
<Input.Search value={keywordText} placeholder="搜索用户 ID、类型、备注或关联 ID" allowClear enterButton={<SearchOutlined />} onSearch={() => searchLogs(keywordText)} onChange={(event) => setKeywordText(event.target.value)} />
|
|
</Form.Item>
|
|
</Col>
|
|
<Col flex="none">
|
|
<Form.Item>
|
|
<Space>
|
|
<Button
|
|
onClick={() => {
|
|
setKeywordText("");
|
|
resetFilters();
|
|
}}
|
|
>
|
|
重置
|
|
</Button>
|
|
<Button type="primary" icon={<ReloadOutlined />} onClick={() => searchLogs(keywordText)}>
|
|
查询
|
|
</Button>
|
|
</Space>
|
|
</Form.Item>
|
|
</Col>
|
|
</Row>
|
|
</Form>
|
|
</Card>
|
|
<ProTable<AdminCreditLog>
|
|
rowKey="id"
|
|
columns={columns}
|
|
dataSource={logs}
|
|
loading={isLoading}
|
|
search={false}
|
|
defaultSize="middle"
|
|
tableLayout="fixed"
|
|
cardProps={{ variant: "borderless" }}
|
|
headerTitle={
|
|
<Space>
|
|
<Typography.Text strong>算力点日志</Typography.Text>
|
|
<Tag>{total} 条</Tag>
|
|
</Space>
|
|
}
|
|
options={{ density: true, setting: true, reload: () => void refreshLogs() }}
|
|
toolBarRender={() => [
|
|
<Button key="add" type="primary" icon={<PlusOutlined />} onClick={() => setEditingLog({ type: "admin_adjust", amount: 0, balance: 0 })}>
|
|
新增
|
|
</Button>,
|
|
]}
|
|
pagination={{
|
|
current: page,
|
|
pageSize,
|
|
total,
|
|
showSizeChanger: true,
|
|
pageSizeOptions: [10, 20, 50, 100],
|
|
showTotal: (value) => `共 ${value} 条`,
|
|
onChange: (nextPage, nextPageSize) => (nextPageSize !== pageSize ? changePageSize(nextPageSize) : changePage(nextPage)),
|
|
}}
|
|
/>
|
|
</Space>
|
|
|
|
<Modal title={editingLog?.id ? "编辑日志" : "新增日志"} open={Boolean(editingLog)} width={680} onCancel={() => setEditingLog(null)} onOk={() => void saveLog()} okText="保存" cancelText="取消" destroyOnHidden>
|
|
<Form form={form} layout="vertical" requiredMark={false}>
|
|
<Row gutter={14}>
|
|
<Col span={12}>
|
|
<Form.Item name="userId" label="用户 ID" rules={[{ required: true, message: "请输入用户 ID" }]}>
|
|
<Input />
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={12}>
|
|
<Form.Item name="type" label="类型" rules={[{ required: true, message: "请输入类型" }]}>
|
|
<Input />
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={12}>
|
|
<Form.Item name="amount" label="变动数量" rules={[{ required: true, message: "请输入变动数量" }]}>
|
|
<InputNumber precision={0} style={{ width: "100%" }} />
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={12}>
|
|
<Form.Item name="balance" label="变动后余额" rules={[{ required: true, message: "请输入变动后余额" }]}>
|
|
<InputNumber min={0} precision={0} style={{ width: "100%" }} />
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={12}>
|
|
<Form.Item name="relatedId" label="关联 ID">
|
|
<Input />
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={12}>
|
|
<Form.Item name="createdAt" label="创建时间">
|
|
<Input placeholder="不填则新增时自动生成" />
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={24}>
|
|
<Form.Item name="remark" label="备注">
|
|
<Input.TextArea rows={3} />
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={24}>
|
|
<Form.Item name="extra" label="扩展信息">
|
|
<Input.TextArea rows={3} />
|
|
</Form.Item>
|
|
</Col>
|
|
</Row>
|
|
</Form>
|
|
</Modal>
|
|
|
|
<Modal
|
|
title="删除日志"
|
|
open={Boolean(deletingLog)}
|
|
onCancel={() => setDeletingLog(null)}
|
|
onOk={async () => {
|
|
if (!deletingLog) return;
|
|
await deleteLog(deletingLog.id);
|
|
setDeletingLog(null);
|
|
}}
|
|
okText="删除"
|
|
okButtonProps={{ danger: true }}
|
|
cancelText="取消"
|
|
>
|
|
确定删除这条算力点日志吗?
|
|
</Modal>
|
|
</main>
|
|
);
|
|
}
|