对接素材列表解决
This commit is contained in:
@@ -7,7 +7,7 @@ import AdminAuthoriz from './pages/AdminAuthoriz';
|
||||
import AdminConsume from './pages/AdminConsume';
|
||||
import AdminLoginPage from './pages/AdminLoginPage';
|
||||
import AdminDashboard from './pages/AdminDashboard';
|
||||
import AdminPlatform from './pages/AdminPlatform';
|
||||
import AdminPlatform from './pages/Adminplatform';
|
||||
import AdminUsers from './pages/AdminUsers';
|
||||
import AdminModels from './pages/AdminModels';
|
||||
import AdminSettings from './pages/AdminSettings';
|
||||
|
||||
@@ -403,6 +403,49 @@ export async function deleteOauthApp(id: string): Promise<void> {
|
||||
await api.get(`/admin/user-oauth-apps/delete/${id}`);
|
||||
}
|
||||
|
||||
// ── Open Type ───────────────────────────────────────────────
|
||||
|
||||
export async function getOpenTypeList(params?: {
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
type_name?: string;
|
||||
open_type?: number;
|
||||
}): Promise<{ total: number; items: any[] }> {
|
||||
const q = new URLSearchParams();
|
||||
if (params?.page) q.set('page', String(params.page));
|
||||
if (params?.page_size) q.set('page_size', String(params.page_size));
|
||||
if (params?.type_name) q.set('type_name', params.type_name);
|
||||
if (params?.open_type) q.set('open_type', String(params.open_type));
|
||||
const qs = q.toString();
|
||||
return api.get(`/open-type/list${qs ? `?${qs}` : ''}`);
|
||||
}
|
||||
|
||||
export async function getOpenType(id: string): Promise<any> {
|
||||
return api.get(`/open-type/select/${id}`);
|
||||
}
|
||||
|
||||
export async function createOpenType(data: {
|
||||
open_type: number;
|
||||
description: string;
|
||||
type_name: string;
|
||||
thumb?: string;
|
||||
}): Promise<any> {
|
||||
return api.post('/open-type/create', data);
|
||||
}
|
||||
|
||||
export async function updateOpenType(id: string, data: {
|
||||
open_type?: number;
|
||||
description?: string;
|
||||
type_name?: string;
|
||||
thumb?: string;
|
||||
}): Promise<any> {
|
||||
return api.put(`/open-type/update/${id}`, data);
|
||||
}
|
||||
|
||||
export async function deleteOpenType(id: string): Promise<void> {
|
||||
await api.delete(`/open-type/delete/${id}`);
|
||||
}
|
||||
|
||||
// ── Generation Records (Admin) ─────────────────────────────
|
||||
|
||||
export async function getAdminGenerationRecords(params?: {
|
||||
@@ -566,3 +609,17 @@ export async function getOAuthList(params: OAuthListParams): Promise<any> {
|
||||
if (params.page_size !== undefined) query.set('page_size', String(params.page_size));
|
||||
return api.get(`/user-oauth/oauth_list?${query.toString()}`);
|
||||
}
|
||||
|
||||
export async function uploadImage(file: File): Promise<{ url: string; filename: string }> {
|
||||
const form = new FormData();
|
||||
form.append('file', file);
|
||||
const token = localStorage.getItem('auth_token');
|
||||
const res = await fetch(`${import.meta.env.VITE_API_BASE || 'http://localhost:8000'}/api/generation-records/upload-image`, {
|
||||
method: 'POST',
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
body: form,
|
||||
});
|
||||
if (!res.ok) throw new Error('图片上传失败');
|
||||
const data = await res.json();
|
||||
return { url: data.url, filename: data.filename };
|
||||
}
|
||||
@@ -1,43 +1,49 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
Button, Card, Space, Table, Tag, Typography, message, Modal, Input, Upload,
|
||||
Button, Card, Space, Table, Tag, Typography, message, Modal, Form, Input, InputNumber, Upload,
|
||||
} from 'antd';
|
||||
import {
|
||||
HistoryOutlined, ReloadOutlined, PlusOutlined, UploadOutlined,
|
||||
HistoryOutlined, ReloadOutlined, PlusOutlined, UploadOutlined, EyeOutlined, EditOutlined, DeleteOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { getOperationLogs } from '../api';
|
||||
import { getOpenTypeList, getOpenType, createOpenType, updateOpenType, deleteOpenType, uploadImage } from '../api';
|
||||
import { formatDate } from '../utils/formatDate';
|
||||
|
||||
interface OperationLog {
|
||||
interface OpenType {
|
||||
id: string;
|
||||
userId: string;
|
||||
username: string;
|
||||
action: string;
|
||||
method: string;
|
||||
path: string;
|
||||
detail?: string;
|
||||
ip?: string;
|
||||
open_type: number;
|
||||
type_name: string;
|
||||
description: string;
|
||||
thumb?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
const METHOD_COLORS: Record<string, string> = { POST: 'green', PUT: 'blue', DELETE: 'red' };
|
||||
|
||||
const AdminPlatform: React.FC = () => {
|
||||
const [logs, setLogs] = useState<OperationLog[]>([]);
|
||||
const [openTypes, setOpenTypes] = useState<OpenType[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [page, setPage] = useState(1);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [formData, setFormData] = useState({ title: '', description: '', image: '' });
|
||||
const [pageSize, setPageSize] = useState(20);
|
||||
|
||||
const load = async (p?: number) => {
|
||||
const [createModalVisible, setCreateModalVisible] = useState(false);
|
||||
const [detailModalVisible, setDetailModalVisible] = useState(false);
|
||||
const [updateModalVisible, setUpdateModalVisible] = useState(false);
|
||||
const [currentOpenType, setCurrentOpenType] = useState<OpenType | null>(null);
|
||||
|
||||
const [createForm] = Form.useForm();
|
||||
const [updateForm] = Form.useForm();
|
||||
|
||||
const load = async (p?: number, ps?: number) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await getOperationLogs(p || page);
|
||||
setLogs(res.items || []);
|
||||
const res = await getOpenTypeList({
|
||||
page: p || page,
|
||||
page_size: ps || pageSize,
|
||||
});
|
||||
setOpenTypes(res.items || []);
|
||||
setTotal(res.total || 0);
|
||||
} catch {
|
||||
message.error('加载平台管理失败');
|
||||
message.error('加载开户方式列表失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -45,100 +51,227 @@ const AdminPlatform: React.FC = () => {
|
||||
|
||||
useEffect(() => { load(); }, []);
|
||||
|
||||
const handleCreate = async () => {
|
||||
try {
|
||||
const values = await createForm.validateFields();
|
||||
console.log(values);
|
||||
await createOpenType({
|
||||
open_type: values.open_type,
|
||||
type_name: values.type_name,
|
||||
description: values.description,
|
||||
thumb: values.thumb,
|
||||
});
|
||||
message.success('创建成功');
|
||||
setCreateModalVisible(false);
|
||||
createForm.resetFields();
|
||||
load();
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '创建失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDetail = async (id: string) => {
|
||||
try {
|
||||
const openType = await getOpenType(id);
|
||||
setCurrentOpenType(openType);
|
||||
setDetailModalVisible(true);
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '获取详情失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdate = async (id: string) => {
|
||||
try {
|
||||
const openType = await getOpenType(id);
|
||||
setCurrentOpenType(openType);
|
||||
updateForm.setFieldsValue({
|
||||
open_type: openType.open_type,
|
||||
type_name: openType.type_name,
|
||||
description: openType.description,
|
||||
thumb: openType.thumb,
|
||||
});
|
||||
setUpdateModalVisible(true);
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '获取详情失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveUpdate = async () => {
|
||||
if (!currentOpenType) return;
|
||||
try {
|
||||
const values = await updateForm.validateFields();
|
||||
await updateOpenType(currentOpenType.id, {
|
||||
open_type: values.open_type,
|
||||
type_name: values.type_name,
|
||||
description: values.description,
|
||||
thumb: values.thumb,
|
||||
});
|
||||
message.success('更新成功');
|
||||
setUpdateModalVisible(false);
|
||||
updateForm.resetFields();
|
||||
setCurrentOpenType(null);
|
||||
load();
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '更新失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '确定要删除这个开户方式吗?',
|
||||
okText: '删除',
|
||||
okType: 'danger',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
try {
|
||||
await deleteOpenType(id);
|
||||
message.success('删除成功');
|
||||
load();
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '删除失败');
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '操作人', dataIndex: 'username', width: 120,
|
||||
render: (v: string) => <Typography.Text strong>{v}</Typography.Text>,
|
||||
},
|
||||
{
|
||||
title: '操作', dataIndex: 'action', width: 160,
|
||||
title: 'ID', dataIndex: 'id', width: 100,
|
||||
render: (v: string) => <Typography.Text>{v}</Typography.Text>,
|
||||
},
|
||||
{
|
||||
title: '方法', dataIndex: 'method', width: 80,
|
||||
render: (v: string) => <Tag color={METHOD_COLORS[v] || 'default'}>{v}</Tag>,
|
||||
title: '开户类型', dataIndex: 'open_type', width: 100,
|
||||
render: (v: number) => <Typography.Text>{v}</Typography.Text>,
|
||||
},
|
||||
{
|
||||
title: '路径', dataIndex: 'path', width: 220, ellipsis: true,
|
||||
title: '类型名称', dataIndex: 'type_name', width: 150,
|
||||
render: (v: string) => <Typography.Text strong>{v}</Typography.Text>,
|
||||
},
|
||||
{
|
||||
title: '描述', dataIndex: 'description', width: 250, ellipsis: true,
|
||||
render: (v: string) => <Typography.Text type="secondary" style={{ fontSize: 12 }}>{v}</Typography.Text>,
|
||||
},
|
||||
{
|
||||
title: '时间', dataIndex: 'createdAt', width: 160,
|
||||
title: '缩略图', dataIndex: 'thumb', width: 120,
|
||||
render: (v: string) => v ? <img src={v} alt="thumb" style={{ width: 80, height: 60, objectFit: 'cover' }} /> : '-',
|
||||
},
|
||||
{
|
||||
title: '创建时间', dataIndex: 'createdAt', width: 160,
|
||||
render: (v: string) => <Typography.Text type="secondary" style={{ fontSize: 12 }}>{formatDate(v)}</Typography.Text>,
|
||||
},
|
||||
{
|
||||
title: '更新时间', dataIndex: 'updatedAt', width: 160,
|
||||
render: (v: string) => <Typography.Text type="secondary" style={{ fontSize: 12 }}>{formatDate(v)}</Typography.Text>,
|
||||
},
|
||||
{
|
||||
title: '操作', width: 200,
|
||||
render: (_: any, record: OpenType) => (
|
||||
<Space>
|
||||
<Button
|
||||
icon={<EyeOutlined />}
|
||||
size="small"
|
||||
onClick={() => handleDetail(record.id)}
|
||||
>详情</Button>
|
||||
<Button
|
||||
icon={<EditOutlined />}
|
||||
size="small"
|
||||
onClick={() => handleUpdate(record.id)}
|
||||
>更新</Button>
|
||||
<Button
|
||||
icon={<DeleteOutlined />}
|
||||
size="small"
|
||||
danger
|
||||
onClick={() => handleDelete(record.id)}
|
||||
>删除</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Card bordered={false} style={{ borderRadius: 12, border: '1px solid #f0f0f5' }}>
|
||||
<Card variant="outlined" style={{ borderRadius: 12, border: '1px solid #f0f0f5' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
|
||||
<Space>
|
||||
<HistoryOutlined style={{ fontSize: 18, color: '#6366f1' }} />
|
||||
<Typography.Text strong style={{ fontSize: 16 }}>平台管理</Typography.Text>
|
||||
<Typography.Text strong style={{ fontSize: 16 }}>平台开户方式管理</Typography.Text>
|
||||
</Space>
|
||||
<Space>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => setShowModal(true)}>新增</Button>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => setCreateModalVisible(true)}>新增</Button>
|
||||
<Button icon={<ReloadOutlined />} onClick={() => load()}>刷新</Button>
|
||||
</Space>
|
||||
</div>
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={logs}
|
||||
dataSource={openTypes}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
pagination={{
|
||||
current: page,
|
||||
pageSize: 20,
|
||||
pageSize,
|
||||
total,
|
||||
showTotal: (t) => `共 ${t} 条记录`,
|
||||
onChange: (p) => { setPage(p); load(p); },
|
||||
onChange: (p, ps) => { setPage(p); setPageSize(ps); load(p, ps); },
|
||||
}}
|
||||
scroll={{ x: 800 }}
|
||||
scroll={{ x: 1000 }}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Modal
|
||||
title="新增平台"
|
||||
open={showModal}
|
||||
onOk={() => {
|
||||
message.success('新增成功');
|
||||
setShowModal(false);
|
||||
setFormData({ title: '', description: '', image: '' });
|
||||
load();
|
||||
}}
|
||||
title="新增开户方式"
|
||||
open={createModalVisible}
|
||||
onOk={handleCreate}
|
||||
onCancel={() => {
|
||||
setShowModal(false);
|
||||
setFormData({ title: '', description: '', image: '' });
|
||||
setCreateModalVisible(false);
|
||||
createForm.resetFields();
|
||||
}}
|
||||
okText="确认"
|
||||
okText="创建"
|
||||
cancelText="取消"
|
||||
width={600}
|
||||
>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
<div>
|
||||
<Typography.Text strong style={{ marginBottom: 8, display: 'block' }}>标题</Typography.Text>
|
||||
<Input
|
||||
placeholder="请输入标题"
|
||||
value={formData.title}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, title: e.target.value }))}
|
||||
/>
|
||||
<Form form={createForm} layout="vertical">
|
||||
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<Form.Item
|
||||
name="open_type"
|
||||
label="开户方式Id"
|
||||
style={{ flex: 1 }}
|
||||
rules={[{ required: true, message: '请输入开户方式Id' }]}
|
||||
>
|
||||
<InputNumber style={{ width: '100%' }} min={1} placeholder="请输入开户方式Id" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="type_name"
|
||||
label="类型名称"
|
||||
style={{ flex: 1 }}
|
||||
rules={[{ required: true, message: '请输入类型名称' }, { max: 100, message: '类型名称不能超过100个字符' }]}
|
||||
>
|
||||
<Input style={{ width: '100%' }} placeholder="请输入类型名称" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
<div>
|
||||
<Typography.Text strong style={{ marginBottom: 8, display: 'block' }}>描述</Typography.Text>
|
||||
<Input.TextArea
|
||||
placeholder="请输入描述"
|
||||
value={formData.description}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, description: e.target.value }))}
|
||||
rows={4}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Typography.Text strong style={{ marginBottom: 8, display: 'block' }}>封面图片</Typography.Text>
|
||||
|
||||
<Form.Item
|
||||
name="description"
|
||||
label="描述"
|
||||
rules={[{ required: true, message: '请输入描述' }]}
|
||||
>
|
||||
<Input.TextArea placeholder="请输入描述" rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="thumb"
|
||||
label="缩略图"
|
||||
>
|
||||
<Upload
|
||||
action="/api/upload"
|
||||
listType="picture-card"
|
||||
onChange={(info) => {
|
||||
if (info.file.status === 'done') {
|
||||
setFormData(prev => ({ ...prev, image: info.file.response?.url || '' }));
|
||||
customRequest={async ({ file, onSuccess, onError }) => {
|
||||
try {
|
||||
const res = await uploadImage(file as File);
|
||||
console.log(res);
|
||||
createForm.setFieldsValue({ thumb: res.url });
|
||||
onSuccess(res);
|
||||
} catch (e: any) {
|
||||
onError(e);
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -147,8 +280,95 @@ const AdminPlatform: React.FC = () => {
|
||||
<div style={{ marginTop: 8 }}>上传图片</div>
|
||||
</div>
|
||||
</Upload>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title="开户方式详情"
|
||||
open={detailModalVisible}
|
||||
onCancel={() => {
|
||||
setDetailModalVisible(false);
|
||||
setCurrentOpenType(null);
|
||||
}}
|
||||
okText="关闭"
|
||||
cancelText="取消"
|
||||
width={600}
|
||||
>
|
||||
{currentOpenType && (
|
||||
<div style={{ lineHeight: '2' }}>
|
||||
<p><strong>ID:</strong> {currentOpenType.id}</p>
|
||||
<p><strong>开户类型:</strong> {currentOpenType.open_type}</p>
|
||||
<p><strong>类型名称:</strong> {currentOpenType.type_name}</p>
|
||||
<p><strong>描述:</strong> {currentOpenType.description}</p>
|
||||
<p><strong>缩略图:</strong> {currentOpenType.thumb ? <img src={currentOpenType.thumb} alt="thumb" style={{ width: 120, height: 80, objectFit: 'cover' }} /> : '-'}</p>
|
||||
<p><strong>创建时间:</strong> {formatDate(currentOpenType.createdAt)}</p>
|
||||
<p><strong>更新时间:</strong> {formatDate(currentOpenType.updatedAt)}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title="更新开户方式"
|
||||
open={updateModalVisible}
|
||||
onOk={handleSaveUpdate}
|
||||
onCancel={() => {
|
||||
setUpdateModalVisible(false);
|
||||
updateForm.resetFields();
|
||||
setCurrentOpenType(null);
|
||||
}}
|
||||
okText="更新"
|
||||
cancelText="取消"
|
||||
width={600}
|
||||
>
|
||||
<Form form={updateForm} layout="vertical">
|
||||
<Form.Item
|
||||
name="open_type"
|
||||
label="开户类型"
|
||||
rules={[{ required: true, message: '请输入开户类型' }]}
|
||||
>
|
||||
<InputNumber min={1} placeholder="请输入开户类型编号" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="type_name"
|
||||
label="类型名称"
|
||||
rules={[{ required: true, message: '请输入类型名称' }, { max: 100, message: '类型名称不能超过100个字符' }]}
|
||||
>
|
||||
<Input placeholder="请输入类型名称" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="description"
|
||||
label="描述"
|
||||
rules={[{ required: true, message: '请输入描述' }]}
|
||||
>
|
||||
<Input.TextArea placeholder="请输入描述" rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="thumb"
|
||||
label="缩略图"
|
||||
>
|
||||
<Upload
|
||||
listType="picture-card"
|
||||
defaultFileList={currentOpenType?.thumb ? [{ uid: '1', name: 'thumb', status: 'done', url: currentOpenType.thumb }] : []}
|
||||
customRequest={async ({ file, onSuccess, onError }) => {
|
||||
try {
|
||||
const res = await uploadImage(file);
|
||||
updateForm.setFieldsValue({ thumb: res.url });
|
||||
onSuccess(res);
|
||||
} catch (e: any) {
|
||||
onError(e);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{!updateForm.getFieldValue('thumb') && (
|
||||
<div>
|
||||
<UploadOutlined style={{ fontSize: 24, color: '#999' }} />
|
||||
<div style={{ marginTop: 8 }}>上传图片</div>
|
||||
</div>
|
||||
)}
|
||||
</Upload>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user