Selaa lähdekoodia

📝 docs(architecture): add admin dashboard development standards

- add new document for admin dashboard development standards
- update table of contents in architecture.md
- add reference to admin standards in coding-standards.md
- define technical stack, page structure, data handling, and UI component usage for admin interfaces
- specify permission control, responsive design, and code quality requirements for admin development
yourname 4 kuukautta sitten
vanhempi
sitoutus
bfa1aed752

+ 1 - 0
docs/architecture.md

@@ -22,6 +22,7 @@
 - [Taro小程序开发规范](./architecture/taro-mini-program-standards.md) - Taro小程序开发最佳实践
 - [Tailwind CSS样式规范](./architecture/tailwind-css-standards.md) - Tailwind CSS使用规范
 - [mini-demo迁移指导规范](./architecture/mini-demo-migration-guide.md) - 从原生小程序迁移到Taro的详细指导(包含精确样式迁移)
+- [管理后台开发规范](./architecture/admin-dashboard-standards.md) - 管理后台开发标准和最佳实践
 
 ### 文档范围
 全面定义出行服务项目的架构方案、技术栈选择、数据模型设计和集成策略

+ 528 - 0
docs/architecture/admin-dashboard-standards.md

@@ -0,0 +1,528 @@
+# 管理后台开发规范
+
+## 版本信息
+| 版本 | 日期 | 描述 | 作者 |
+|------|------|------|------|
+| 1.0 | 2025-10-16 | 初始版本,基于现有管理后台实现 | Winston |
+
+## 概述
+
+本文档定义了出行服务项目管理后台的开发标准和最佳实践,确保所有管理后台功能开发的一致性和可维护性。
+
+## 技术栈要求
+
+### 核心框架
+- **React**: 19.1.0+,使用函数组件和Hooks
+- **TypeScript**: 严格模式,类型安全优先
+- **React Router**: v7,声明式路由管理
+
+### 状态管理
+- **@tanstack/react-query**: 服务端状态管理
+- **React Context**: 本地状态和全局状态
+- **React Hook Form**: 表单状态管理
+
+### UI组件库
+- **shadcn/ui**: 基于Radix UI的组件库
+- **Tailwind CSS**: 4.1.11+,原子化样式
+- **Lucide React**: 图标库
+
+### 开发工具
+- **Vite**: 7.0.0+,构建工具
+- **Zod**: 表单验证和类型定义
+- **Sonner**: Toast通知
+
+## 页面开发规范
+
+### 页面结构标准
+
+每个管理后台页面应遵循以下结构:
+
+```typescript
+import React, { useState, useMemo } from 'react';
+import { useQuery, useMutation } from '@tanstack/react-query';
+import { useForm } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { toast } from 'sonner';
+
+// 1. 导入必要的组件和工具
+import { Button } from '@/client/components/ui/button';
+import { Card, CardContent, CardHeader, CardTitle } from '@/client/components/ui/card';
+import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/client/components/ui/table';
+
+// 2. 定义类型(使用后端Schema生成的类型)
+type EntityResponse = InferResponseType<typeof entityClient.$get, 200>['data'][0];
+
+// 3. 页面组件定义
+export const EntityPage = () => {
+  // 4. 状态管理
+  const [searchParams, setSearchParams] = useState({
+    page: 1,
+    limit: 10,
+    keyword: ''
+  });
+
+  // 5. 数据获取
+  const { data, isLoading, refetch } = useQuery({
+    queryKey: ['entities', searchParams],
+    queryFn: async () => {
+      const res = await entityClient.$get({
+        query: searchParams
+      });
+      if (res.status !== 200) throw new Error('获取数据失败');
+      return await res.json();
+    }
+  });
+
+  // 6. 渲染逻辑
+  return (
+    <div className="space-y-4">
+      {/* 页面标题和操作按钮 */}
+      <div className="flex justify-between items-center">
+        <h1 className="text-2xl font-bold">实体管理</h1>
+        <Button>创建实体</Button>
+      </div>
+
+      {/* 内容区域 */}
+      <Card>
+        <CardHeader>
+          <CardTitle>实体列表</CardTitle>
+        </CardHeader>
+        <CardContent>
+          {/* 表格或列表内容 */}
+        </CardContent>
+      </Card>
+    </div>
+  );
+};
+```
+
+### 数据表格规范
+
+#### 表格结构要求
+```typescript
+// 标准表格结构
+<Table>
+  <TableHeader>
+    <TableRow>
+      <TableHead>ID</TableHead>
+      <TableHead>名称</TableHead>
+      <TableHead>状态</TableHead>
+      <TableHead className="text-right">操作</TableHead>
+    </TableRow>
+  </TableHeader>
+  <TableBody>
+    {data?.data?.map((item) => (
+      <TableRow key={item.id}>
+        <TableCell className="font-medium">{item.id}</TableCell>
+        <TableCell>{item.name}</TableCell>
+        <TableCell>
+          <Badge variant={item.status === 'active' ? 'default' : 'secondary'}>
+            {item.status === 'active' ? '启用' : '禁用'}
+          </Badge>
+        </TableCell>
+        <TableCell className="text-right">
+          <div className="flex justify-end gap-2">
+            <Button variant="ghost" size="icon">
+              <Edit className="h-4 w-4" />
+            </Button>
+            <Button variant="ghost" size="icon" className="text-red-600">
+              <Trash2 className="h-4 w-4" />
+            </Button>
+          </div>
+        </TableCell>
+      </TableRow>
+    ))}
+  </TableBody>
+</Table>
+```
+
+#### 分页组件使用
+```typescript
+// 使用标准分页组件
+<DataTablePagination
+  currentPage={searchParams.page}
+  totalCount={data?.pagination?.total || 0}
+  pageSize={searchParams.limit}
+  onPageChange={(page, limit) => setSearchParams(prev => ({ ...prev, page, limit }))}
+/>
+```
+
+### 表单处理规范
+
+#### 表单验证
+```typescript
+// 使用Zod Schema进行表单验证
+const formSchema = z.object({
+  name: z.string().min(1, '名称不能为空'),
+  email: z.string().email('请输入有效的邮箱地址'),
+  phone: z.string().optional(),
+});
+
+const form = useForm({
+  resolver: zodResolver(formSchema),
+  defaultValues: {
+    name: '',
+    email: '',
+    phone: '',
+  },
+});
+```
+
+#### 表单提交
+```typescript
+// 标准表单提交处理
+const handleSubmit = async (data: FormData) => {
+  try {
+    const res = await entityClient.$post({ json: data });
+    if (res.status !== 201) throw new Error('创建失败');
+    toast.success('创建成功');
+    refetch(); // 重新获取数据
+  } catch {
+    toast.error('操作失败,请重试');
+  }
+};
+```
+
+## 搜索和筛选规范
+
+### 搜索功能
+```typescript
+// 防抖搜索实现
+const debounce = (func: Function, delay: number) => {
+  let timeoutId: NodeJS.Timeout;
+  return (...args: any[]) => {
+    clearTimeout(timeoutId);
+    timeoutId = setTimeout(() => func(...args), delay);
+  };
+};
+
+const debouncedSearch = useCallback(
+  debounce((keyword: string) => {
+    setSearchParams(prev => ({ ...prev, keyword, page: 1 }));
+  }, 300),
+  []
+);
+```
+
+### 高级筛选
+```typescript
+// 筛选状态管理
+const [filters, setFilters] = useState({
+  status: undefined as string | undefined,
+  category: [] as number[],
+  dateRange: undefined as { start?: string; end?: string } | undefined
+});
+
+// 筛选条件显示
+const hasActiveFilters = useMemo(() => {
+  return filters.status !== undefined ||
+         filters.category.length > 0 ||
+         filters.dateRange !== undefined;
+}, [filters]);
+```
+
+## 权限控制规范
+
+### 基于角色的访问控制
+```typescript
+// 权限检查Hook
+const usePermission = () => {
+  const { user } = useAuth();
+
+  const hasPermission = (permission: string) => {
+    return user?.roles?.some(role =>
+      role.permissions.includes(permission)
+    ) ?? false;
+  };
+
+  return { hasPermission };
+};
+
+// 页面级别权限控制
+const ProtectedPage = () => {
+  const { hasPermission } = usePermission();
+
+  if (!hasPermission('page:entity:view')) {
+    return <ErrorPage statusCode={403} />;
+  }
+
+  return <EntityPage />;
+};
+```
+
+### 操作级别权限控制
+```typescript
+// 条件渲染操作按钮
+{hasPermission('entity:create') && (
+  <Button onClick={handleCreate}>
+    创建实体
+  </Button>
+)}
+
+{hasPermission('entity:delete') && (
+  <Button variant="ghost" size="icon" onClick={handleDelete}>
+    <Trash2 className="h-4 w-4" />
+  </Button>
+)}
+```
+
+## 用户体验规范
+
+### 加载状态处理
+```typescript
+// 表格骨架屏
+const renderTableSkeleton = () => (
+  <div className="space-y-2">
+    {Array.from({ length: 5 }).map((_, index) => (
+      <div key={index} className="flex space-x-4">
+        <Skeleton className="h-4 flex-1" />
+        <Skeleton className="h-4 flex-1" />
+        <Skeleton className="h-4 flex-1" />
+      </div>
+    ))}
+  </div>
+);
+
+// 使用
+{isLoading ? renderTableSkeleton() : renderTableContent()}
+```
+
+### 错误处理
+```typescript
+// 统一错误处理
+const handleOperation = async (operation: () => Promise<any>) => {
+  try {
+    await operation();
+    toast.success('操作成功');
+  } catch (error) {
+    console.error('操作失败:', error);
+    toast.error('操作失败,请重试');
+  }
+};
+```
+
+### 确认对话框
+```typescript
+// 删除确认对话框
+const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
+const [itemToDelete, setItemToDelete] = useState<number | null>(null);
+
+const handleDelete = (id: number) => {
+  setItemToDelete(id);
+  setDeleteDialogOpen(true);
+};
+
+const confirmDelete = async () => {
+  if (!itemToDelete) return;
+
+  await handleOperation(async () => {
+    const res = await entityClient[':id'].$delete({ param: { id: itemToDelete } });
+    if (res.status !== 204) throw new Error('删除失败');
+  });
+
+  setDeleteDialogOpen(false);
+  setItemToDelete(null);
+};
+```
+
+## 组件使用规范
+
+### shadcn/ui组件使用
+
+#### 按钮组件
+```typescript
+// 标准按钮使用
+<Button variant="default" size="default">主要按钮</Button>
+<Button variant="outline" size="sm">次要按钮</Button>
+<Button variant="ghost" size="icon">图标按钮</Button>
+<Button variant="destructive">危险操作</Button>
+```
+
+#### 表单组件
+```typescript
+// 表单字段标准结构
+<FormField
+  control={form.control}
+  name="fieldName"
+  render={({ field }) => (
+    <FormItem>
+      <FormLabel>字段标签</FormLabel>
+      <FormControl>
+        <Input placeholder="请输入内容" {...field} />
+      </FormControl>
+      <FormMessage />
+    </FormItem>
+  )}
+/>
+```
+
+#### 对话框组件
+```typescript
+// 标准对话框使用
+<Dialog open={isOpen} onOpenChange={setIsOpen}>
+  <DialogContent className="sm:max-w-[500px]">
+    <DialogHeader>
+      <DialogTitle>对话框标题</DialogTitle>
+      <DialogDescription>对话框描述</DialogDescription>
+    </DialogHeader>
+    {/* 对话框内容 */}
+    <DialogFooter>
+      <Button variant="outline" onClick={() => setIsOpen(false)}>取消</Button>
+      <Button onClick={handleConfirm}>确认</Button>
+    </DialogFooter>
+  </DialogContent>
+</Dialog>
+```
+
+### 自定义组件开发
+
+#### 组件命名规范
+- 使用PascalCase命名
+- 文件名与组件名一致
+- 导出命名组件
+
+#### 组件Props定义
+```typescript
+interface CustomComponentProps {
+  value?: string;
+  onChange?: (value: string) => void;
+  placeholder?: string;
+  disabled?: boolean;
+  className?: string;
+}
+
+export const CustomComponent: React.FC<CustomComponentProps> = ({
+  value,
+  onChange,
+  placeholder,
+  disabled = false,
+  className
+}) => {
+  // 组件实现
+};
+```
+
+## 响应式设计规范
+
+### 移动端适配
+```typescript
+// 使用Tailwind响应式类
+<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
+  {/* 内容 */}
+</div>
+
+// 移动端菜单
+<Sheet open={isMobileMenuOpen} onOpenChange={setIsMobileMenuOpen}>
+  <SheetContent side="left" className="w-64 p-0">
+    {/* 移动端菜单内容 */}
+  </SheetContent>
+</Sheet>
+```
+
+### 布局规范
+- 桌面端:侧边栏 + 主内容区
+- 移动端:汉堡菜单 + 全屏内容
+- 断点:sm(640px), md(768px), lg(1024px), xl(1280px)
+
+## 性能优化规范
+
+### 代码分割
+```typescript
+// 使用React.lazy进行代码分割
+const LazyComponent = React.lazy(() => import('./LazyComponent'));
+
+// 在路由中使用
+<React.Suspense fallback={<div>加载中...</div>}>
+  <LazyComponent />
+</React.Suspense>
+```
+
+### 数据缓存
+```typescript
+// React Query缓存配置
+const { data } = useQuery({
+  queryKey: ['entities', searchParams],
+  queryFn: fetchEntities,
+  staleTime: 5 * 60 * 1000, // 5分钟
+  cacheTime: 10 * 60 * 1000, // 10分钟
+});
+```
+
+## 测试规范
+
+### 组件测试
+```typescript
+// 使用Testing Library进行组件测试
+import { render, screen, fireEvent } from '@testing-library/react';
+import { EntityPage } from './EntityPage';
+
+describe('EntityPage', () => {
+  it('should render entity list', () => {
+    render(<EntityPage />);
+    expect(screen.getByText('实体管理')).toBeInTheDocument();
+  });
+});
+```
+
+### E2E测试
+```typescript
+// 使用Playwright进行E2E测试
+test('should create new entity', async ({ page }) => {
+  await page.goto('/admin/entities');
+  await page.click('button:has-text("创建实体")');
+  await page.fill('input[name="name"]', '测试实体');
+  await page.click('button:has-text("创建")');
+  await expect(page.locator('text=创建成功')).toBeVisible();
+});
+```
+
+## 代码质量规范
+
+### 代码风格
+- 使用ESLint + Prettier进行代码格式化
+- 遵循TypeScript严格模式
+- 使用函数组件和Hooks
+- 避免使用any类型
+
+### 文件组织
+```text
+src/client/admin/
+├── components/          # 管理后台专用组件
+├── hooks/              # 管理后台Hooks
+├── layouts/            # 布局组件
+├── pages/              # 页面组件
+├── utils/              # 工具函数
+└── types/              # 类型定义
+```
+
+## 部署和构建规范
+
+### 环境变量
+```bash
+# 前端环境变量
+VITE_API_BASE_URL=http://localhost:3000/api
+VITE_APP_NAME=管理后台
+```
+
+### 构建配置
+```typescript
+// vite.config.ts
+export default defineConfig({
+  build: {
+    rollupOptions: {
+      output: {
+        manualChunks: {
+          vendor: ['react', 'react-dom'],
+          ui: ['@/client/components/ui']
+        }
+      }
+    }
+  }
+});
+```
+
+---
+
+**文档状态**: 正式版
+**下次评审**: 2025-11-16
+**维护者**: Winston 🏗️

+ 7 - 0
docs/architecture/coding-standards.md

@@ -48,3 +48,10 @@
 - **样式保留**: 确保百分百样式保留,保持用户体验一致性
 - **技术栈转换**: 原生小程序 → Taro + React + TypeScript
 - **数据集成**: 模拟数据 → 真实后端API
+
+## 管理后台开发规范
+- **管理后台开发**: 遵循 [管理后台开发规范](./admin-dashboard-standards.md) 进行开发
+- **页面结构**: 统一页面结构和布局标准
+- **数据表格**: 标准表格组件使用规范
+- **权限控制**: 基于角色的访问控制实现
+- **用户体验**: 统一的加载状态、错误处理和用户反馈机制