11-admin-frontend.md 13 KB

管理后台前端页面开发流程规范

适用场景

管理后台页面开发,包括列表页、详情页、表单页等后台功能页面的实现流程。

开发流程

1. 创建页面组件

  • 位置: src/client/admin/pages/[EntityName]List.tsxsrc/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;
    

2. 注册路由配置

  • 位置: 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} />
       }
     ];
    

3. 添加菜单配置

  • 位置: src/client/admin/menu.tsx
  • 添加菜单配置示例:

     import { TableOutlined } from '@ant-design/icons';
         
     export const menuItems = [
       // ...其他菜单项
       {
         key: 'your-entities',
         icon: <TableOutlined />,
         label: '实体管理',
         path: '/your-entities'
       }
     ];
    

4. 创建表单组件

  • 位置: 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;
    

5. 实现详情页

  • 位置: 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;
    

6. 样式规范

  • 使用CSS Modules或Styled Components进行样式隔离
  • 页面容器样式:

     .page-container {
       padding: 24px;
     }
         
     .page-header {
       display: flex;
       justify-content: space-between;
       align-items: center;
       margin-bottom: 24px;
     }
    

7. 权限控制

  • 使用ProtectedRoute组件包装需要权限控制的页面: ```tsx import { ProtectedRoute } from '@/client/admin/components/ProtectedRoute';
    export const routes = [ // ...其他路由 { path: '/your-entities', element: ( ) } ];