管理后台页面开发,包括列表页、详情页、表单页等后台功能页面的实现流程。
src/client/admin/pages/[EntityName]List.tsx 和 src/client/admin/pages/[EntityName]Detail.tsx列表页组件示例:
import React, { useEffect, useState } from 'react';
import { Table, Button, Space, Tag, message } from 'antd';
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
import type { InferResponseType } from 'hono/client';
import { yourEntityClient } from '@/client/api';
// 类型定义
type EntityItem = InferResponseType<typeof yourEntityClient[':id']['$get'], 200>;
type EntityListResponse = InferResponseType<typeof yourEntityClient.$get, 200>;
const YourEntityList: React.FC = () => {
const navigate = useNavigate();
const [data, setData] = useState<EntityItem[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [pagination, setPagination] = useState({
current: 1,
pageSize: 10,
total: 0
});
// 获取数据列表
const fetchData = async () => {
try {
setLoading(true);
const res = await yourEntityClient.$get({
query: {
page: pagination.current,
pageSize: pagination.pageSize
}
});
if (!res.ok) throw new Error('获取数据失败');
const result: EntityListResponse = await res.json();
setData(result.data);
setPagination({
...pagination,
total: result.pagination.total
});
} catch (error) {
message.error(error instanceof Error ? error.message : '获取数据失败');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, [pagination.current, pagination.pageSize]);
// 分页变化处理
const handleTableChange = (pagination: any) => {
setPagination({
...pagination,
current: pagination.current,
pageSize: pagination.pageSize
});
};
// 操作按钮处理
const handleAdd = () => {
navigate('/your-entities/new');
};
const handleEdit = (id: number) => {
navigate(`/your-entities/${id}`);
};
const handleDelete = async (id: number) => {
try {
const res = await yourEntityClient[':id'].$delete({
param: { id }
});
if (!res.ok) throw new Error('删除失败');
message.success('删除成功');
fetchData();
} catch (error) {
message.error(error instanceof Error ? error.message : '删除失败');
}
};
// 表格列定义
const columns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
width: 80
},
{
title: '名称',
dataIndex: 'name',
key: 'name'
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status: number) => (
<Tag color={status === 1 ? 'green' : 'red'}>
{status === 1 ? '启用' : '禁用'}
</Tag>
)
},
{
title: '操作',
key: 'action',
render: (_: any, record: EntityItem) => (
<Space size="middle">
<Button
type="text"
icon={<EditOutlined />}
onClick={() => handleEdit(record.id)}
>
编辑
</Button>
<Button
type="text"
danger
icon={<DeleteOutlined />}
onClick={() => handleDelete(record.id)}
>
删除
</Button>
</Space>
)
}
];
return (
<div className="page-container">
<div className="page-header">
<h2>实体管理</h2>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={handleAdd}
>
添加实体
</Button>
</div>
<Table
columns={columns}
dataSource={data.map(item => ({ ...item, key: item.id }))}
loading={loading}
pagination={{
current: pagination.current,
pageSize: pagination.pageSize,
total: pagination.total,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `共 ${total} 条记录`
}}
onChange={handleTableChange}
bordered
/>
</div>
);
};
export default YourEntityList;
src/client/admin/routes.tsx添加路由配置示例:
import YourEntityList from './pages/YourEntityList';
import YourEntityDetail from './pages/YourEntityDetail';
export const routes = [
// ...其他路由
{
path: '/your-entities',
element: <YourEntityList />
},
{
path: '/your-entities/:id',
element: <YourEntityDetail />
},
{
path: '/your-entities/new',
element: <YourEntityDetail isNew={true} />
}
];
src/client/admin/menu.tsx添加菜单配置示例:
import { TableOutlined } from '@ant-design/icons';
export const menuItems = [
// ...其他菜单项
{
key: 'your-entities',
icon: <TableOutlined />,
label: '实体管理',
path: '/your-entities'
}
];
src/client/admin/components/[EntityName]Form.tsx表单组件示例:
import React from 'react';
import { Form, Input, Select, message } from 'antd';
import type { CreateRequest, UpdateRequest } from '@/client/api/your-entity';
interface YourEntityFormProps {
initialValues?: Partial<CreateRequest>;
onFinish: (values: CreateRequest | UpdateRequest) => Promise<void>;
loading: boolean;
}
const { Option } = Select;
const YourEntityForm: React.FC<YourEntityFormProps> = ({
initialValues,
onFinish,
loading
}) => {
const [form] = Form.useForm();
// 初始化表单值
React.useEffect(() => {
if (initialValues) {
form.setFieldsValue(initialValues);
} else {
form.resetFields();
}
}, [form, initialValues]);
// 表单提交处理
const handleSubmit = async () => {
try {
const values = await form.validateFields();
await onFinish(values);
} catch (error) {
message.error('表单验证失败,请检查输入内容');
}
};
return (
<Form
form={form}
layout="vertical"
onFinish={handleSubmit}
initialValues={initialValues}
>
<Form.Item
name="name"
label="名称"
rules={[
{ required: true, message: '请输入名称' },
{ max: 20, message: '名称不能超过20个字符' }
]}
>
<Input placeholder="请输入名称" />
</Form.Item>
<Form.Item
name="status"
label="状态"
rules={[{ required: true, message: '请选择状态' }]}
>
<Select placeholder="请选择状态">
<Option value={1}>启用</Option>
<Option value={0}>禁用</Option>
</Select>
</Form.Item>
<Form.Item>
<button
type="submit"
className="ant-btn ant-btn-primary"
disabled={loading}
>
{loading ? '提交中...' : '提交'}
</button>
</Form.Item>
</Form>
);
};
export default YourEntityForm;
src/client/admin/pages/[EntityName]Detail.tsx详情页组件示例:
import React, { useEffect, useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Card, Spin, message } from 'antd';
import type { InferResponseType, InferRequestType } from 'hono/client';
import { yourEntityClient } from '@/client/api';
import YourEntityForm from '../components/YourEntityForm';
// 类型定义
type EntityDetail = InferResponseType<typeof yourEntityClient[':id']['$get'], 200>;
type CreateRequest = InferRequestType<typeof yourEntityClient.$post>['json'];
type UpdateRequest = InferRequestType<typeof yourEntityClient[':id']['$put']>['json'];
interface YourEntityDetailProps {
isNew?: boolean;
}
const YourEntityDetail: React.FC<YourEntityDetailProps> = ({ isNew = false }) => {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const [loading, setLoading] = useState<boolean>(!isNew);
const [initialValues, setInitialValues] = useState<Partial<CreateRequest>>({});
// 获取详情数据
const fetchDetail = async () => {
if (!id || isNew) return;
try {
setLoading(true);
const res = await yourEntityClient[':id'].$get({
param: { id: Number(id) }
});
if (!res.ok) throw new Error('获取详情失败');
const data: EntityDetail = await res.json();
setInitialValues(data);
} catch (error) {
message.error(error instanceof Error ? error.message : '获取详情失败');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchDetail();
}, [id, isNew]);
// 表单提交处理
const handleFinish = async (values: CreateRequest | UpdateRequest) => {
try {
setLoading(true);
if (isNew) {
// 创建新实体
const res = await yourEntityClient.$post({
json: values as CreateRequest
});
if (!res.ok) throw new Error('创建失败');
message.success('创建成功');
} else if (id) {
// 更新实体
const res = await yourEntityClient[':id'].$put({
param: { id: Number(id) },
json: values as UpdateRequest
});
if (!res.ok) throw new Error('更新失败');
message.success('更新成功');
}
navigate('/your-entities');
} catch (error) {
message.error(error instanceof Error ? error.message : '操作失败');
} finally {
setLoading(false);
}
};
return (
<div className="page-container">
<div className="page-header">
<h2>{isNew ? '添加实体' : '编辑实体'}</h2>
</div>
<Card>
<Spin spinning={loading}>
<YourEntityForm
initialValues={initialValues}
onFinish={handleFinish}
loading={loading}
/>
</Spin>
</Card>
</div>
);
};
export default YourEntityDetail;
页面容器样式:
.page-container {
padding: 24px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}