# Mini UI包开发规范 ## 版本信息 | 版本 | 日期 | 描述 | 作者 | |------|------|------|------| | 1.1 | 2025-12-26 | 添加图标使用规范(Heroicons) | Bob (Scrum Master) | | 1.0 | 2025-12-26 | 基于史诗011和017经验创建Mini UI包开发规范 | Bob (Scrum Master) | ## 概述 本文档专门针对Taro小程序UI包(mini-ui-packages)的开发规范,基于史诗011(用人方小程序)和史诗017(人才小程序)的实施经验总结。Mini UI包与Web UI包有显著的差异,特别是在布局、组件行为和平台特性方面。 **适用范围:** - `mini-ui-packages/` 目录下的所有UI包 - 使用Taro框架的小程序项目 - 所有基于`@tarojs/components`的组件开发 ## Taro小程序核心布局规范 ### 1. View组件的默认布局行为 **重要**: 在Taro小程序中,`` 组件内的子元素默认是**横向布局**(`flex-row`),这与Web开发的div默认垂直布局行为完全不同。 #### 1.1 垂直布局规范 **问题**: View容器默认横向布局,导致子元素横向排列 **解决方案**: 必须显式添加 `flex flex-col` 类才能实现垂直布局 **正确示例**: ```typescript import { View, Text } from '@tarojs/components' // ✅ 正确: 使用 flex flex-col 实现垂直布局 姓名: 张三 性别: 男 年龄: 35 // ❌ 错误: 缺少 flex flex-col,子元素会横向排列 姓名: 张三 性别: 男 年龄: 35 ``` #### 1.2 信息卡片布局模式 **来源**: 史诗011.003经验总结 **标准模式**: ```typescript import { View, Text } from '@tarojs/components' export function PersonalBasicInfo({ personalInfo }: { personalInfo: PersonalInfoResponse }) { return ( 个人基本信息 {/* 垂直布局的信息列表 - 必须使用 flex flex-col */} 姓名 {personalInfo.name} 性别 {personalInfo.gender} 年龄 {personalInfo.age} {/* 更多字段... */} ) } ``` **关键点**: 1. **信息列表容器**必须使用 `flex flex-col` 实现垂直布局 2. **每个信息项**使用 `flex justify-between` 实现标签和值的左右分布 3. 使用 `space-y-2` 或 `space-y-3` 添加垂直间距 4. **重要**: 记住在所有需要垂直排列的 View 上添加 `flex flex-col` ### 2. Text组件的默认内联行为 **问题**: Text组件默认是内联显示(类似Web中的``),不会自动换行 **影响**: 导致多个Text组件在同一行显示,即使它们在不同的代码行上 **解决方案**: 使用`flex flex-col`强制Text组件垂直排列 **实际案例** (来源: 史诗011.003): ```typescript // ❌ 错误: Text组件会内联显示在同一行 统计1 统计2 统计3 // ✅ 正确: 使用 flex flex-col 强制垂直排列 统计1 统计2 统计3 ``` ### 3. 常见布局模式 #### 3.1 卡片容器布局 ```typescript 卡片标题 {/* 内容区域 - 垂直布局 */} {items.map(item => ( {item.label} {item.value} ))} ``` #### 3.2 列表项布局 ```typescript {list.map(item => ( {/* 列表项标题 */} {item.title} {/* 列表项内容 - 垂直布局 */} {item.description} {item.date} ))} ``` #### 3.3 网格布局 ```typescript {/* 2列网格 */} {items.map(item => ( {item.title} {item.value} ))} ``` ## 图标使用规范 ### 3.1 图标系统概述 **项目使用的图标库**: Heroicons (UnoCSS图标集) **图标类命名规范**: `i-heroicons-{icon-name}-{size}-{style}` **重要**: **不要使用emoji**,必须使用Heroicons图标类。 ### 3.2 图标类使用规范 #### 3.2.1 基础图标使用 **正确示例**: ```typescript import { View } from '@tarojs/components' // ✅ 正确: 使用Heroicons图标类 // ❌ 错误: 使用emoji 🔔 👤 ``` #### 3.2.2 图标类命名格式 **格式**: `i-heroicons-{图标名称}-{尺寸}-{样式}` **常用图标名称**: - `chevron-left` - 左箭头 - `chevron-right` - 右箭头 - `user` - 用户 - `bell` - 通知铃 - `document-text` - 文档 - `chart-bar` - 图表 - `building-office` - 建筑/企业 - `calendar` - 日历 - `phone` - 电话 - `lock-closed` - 锁 - `camera` - 相机 - `qr-code` - 二维码 - `device-phone-mobile` - 手机 - `arrow-right-on-rectangle` - 登出/外跳 - `arrow-left-on-rectangle` - 登入/内跳 - `exclamation-triangle` - 警告 - `exclamation-circle` - 提示 - `photo` - 图片 - `arrow-path` - 加载中 **尺寸选项**: - `20` - 20x20 (推荐用于小程序) - `24` - 24x24 **样式选项**: - `solid` - 实心图标(推荐) - `outline` - 轮廓图标 #### 3.2.3 图标尺寸和颜色 **尺寸类**: ```typescript // 16px // 20px // 24px // 32px // 使用Tailwind文本尺寸 // 使用Tailwind文本尺寸 ``` **颜色类**: ```typescript // 灰色 // 蓝色 // 绿色 // 红色 // 白色 // 黄色 // 紫色 ``` #### 3.2.4 常见使用场景 **导航栏返回按钮**: ```typescript ``` **功能入口图标**: ```typescript 个人信息 考勤记录 薪资查询 企业信息 ``` **状态指示器**: ```typescript // 加载中 // 成功/提示 // 警告 // 错误 // 信息 ``` **输入框图标**: ```typescript ``` **二维码按钮**: ```typescript 张三 ``` #### 3.2.5 动画效果 **旋转动画**: ```typescript ``` **脉冲动画**: ```typescript ``` ### 3.3 Navbar图标规范 **返回按钮图标**: ```typescript leftIcon = 'i-heroicons-chevron-left-20-solid' ``` **示例**: ```typescript Taro.navigateBack()} /> ``` ### 3.4 TabBar图标规范 **使用iconClass属性**(推荐): ```typescript const tabList = [ { title: '首页', iconClass: 'i-heroicons-home-20-solid', selectedIconClass: 'i-heroicons-home-20-solid', pagePath: '/pages/index/index' }, { title: '考勤', iconClass: 'i-heroicons-calendar-20-solid', selectedIconClass: 'i-heroicons-calendar-20-solid', pagePath: '/pages/attendance/index' } ] ``` ### 3.5 图标查找参考 **Heroicons官方图标库**: https://heroicons.com/ **使用UnoCSS图标集**: 项目使用UnoCSS的Heroicons图标集,所有图标名称遵循Heroicons命名规范。 **查找图标的方法**: 1. 访问 Heroicons 官网查找所需图标 2. 记录图标名称(如 `user`, `chevron-left`) 3. 使用格式: `i-heroicons-{图标名称}-20-solid` 4. 添加尺寸和颜色类 ### 3.6 常见错误避免 **错误示例**: ```typescript // ❌ 错误1: 使用emoji 🔔 通知 👤 // ❌ 错误2: 使用文本符号 ← 返回 // ❌ 错误3: 使用其他图标库 person // ❌ 错误4: 忘记添加尺寸类 {/* 没有尺寸,可能不显示 */} // ❌ 错误5: 图标类名拼写错误 {/* 缺少s */} {/* ✅ 正确 */} ``` **正确示例**: ```typescript // ✅ 正确1: 使用Heroicons图标类 // ✅ 正确2: 添加尺寸和颜色 // ✅ 正确3: 图标+文本组合 联系电话 ``` ## Taro组件使用规范 ### 4.1 基础组件导入 ```typescript import { View, Text, Image, Button, ScrollView } from '@tarojs/components' import Taro from '@tarojs/taro' ``` ### 4.2 Image组件规范 ```typescript ``` **mode模式说明**: - `aspectFill`: 保持纵横比缩放图片,确保图片填充整个容器(可能裁剪) - `aspectFit`: 保持纵横比缩放图片,确保图片完全显示(可能有空白) - `widthFix`: 宽度不变,高度自动变化,保持原图宽高比 ### 4.3 ScrollView组件规范 ```typescript {items.map(item => ( {item.content} ))} ``` ### 4.4 Button组件规范 **注意**: Taro的Button组件有默认样式,如需自定义样式建议使用View ```typescript // ✅ 推荐: 使用View实现自定义按钮 确定 // ⚠️ 谨慎使用: Taro Button组件有平台默认样式 ``` ## Navbar导航栏集成规范 ### 5.1 Navbar组件来源 ```typescript import { Navbar } from '@d8d/mini-shared-ui-components/components/navbar' ``` ### 5.2 页面层级划分 **TabBar页面(一级,无返回按钮)**: - 首页/个人主页 - 列表页 - 个人信息页 - 设置页 **非TabBar页面(二级,带返回按钮)**: - 详情页 - 编辑页 - 从其他页面跳转来的页面 ### 5.3 Navbar配置规范 **TabBar页面(无返回按钮)**: ```typescript {}} placeholder fixed /> ``` **非TabBar页面(带返回按钮)**: ```typescript import Taro from '@tarojs/taro' Taro.navigateBack()} placeholder fixed /> ``` ### 5.4 完整页面结构示例 ```typescript import { View, ScrollView } from '@tarojs/components' import { Navbar } from '@d8d/mini-shared-ui-components/components/navbar' export function MyPage() { return ( {/* Navbar导航栏 */} {}} placeholder fixed /> {/* 页面内容 */} {/* 页面内容 */} ) } ``` ## 照片预览功能实现 ### 6.1 使用Taro.previewImage ```typescript import Taro from '@tarojs/taro' import { View, Image, Text } from '@tarojs/components' interface DocumentPhotoItemProps { type: string url: string } export function DocumentPhotoItem({ type, url }: DocumentPhotoItemProps) { const handlePreview = () => { Taro.previewImage({ current: url, // 当前显示图片的http链接 urls: [url] // 需要预览的图片http链接列表 }) } return ( {type} ) } ``` **多图片预览**: ```typescript const handlePreview = (currentIndex: number) => { Taro.previewImage({ current: images[currentIndex].url, urls: images.map(img => img.url) }) } ``` ## 数据脱敏规范 ### 7.1 银行卡号脱敏 ```typescript /** * 脱敏银行卡号 * @param cardNumber 完整银行卡号 * @returns 脱敏后的银行卡号(如:**** **** **** 1234) */ export function maskCardNumber(cardNumber: string): string { if (!cardNumber || cardNumber.length < 4) { return '****' } const last4 = cardNumber.slice(-4) return `**** **** **** ${last4}` } // 使用示例 银行卡号 {maskCardNumber(bankCard.cardNumber)} ``` ### 7.2 身份证号脱敏 ```typescript /** * 脱敏身份证号 * @param idCard 完整身份证号 * @returns 脱敏后的身份证号(如:3301**********1234) */ export function maskIdCard(idCard: string): string { if (!idCard || idCard.length < 8) { return '********' } const prefix = idCard.slice(0, 4) const suffix = idCard.slice(-4) return `${prefix}**********${suffix}` } // 使用示例 身份证号 {maskIdCard(personalInfo.idCard)} ``` ### 7.3 手机号脱敏 ```typescript /** * 脱敏手机号 * @param phone 完整手机号 * @returns 脱敏后的手机号(如:138****5678) */ export function maskPhone(phone: string): string { if (!phone || phone.length < 7) { return '****' } return `${phone.slice(0, 3)}****${phone.slice(-4)}` } ``` ## Mini UI包结构规范 ### 8.1 标准目录结构 ```text mini-ui-packages// ├── src/ │ ├── pages/ # 页面组件 │ │ └── PageName/ │ │ ├── PageName.tsx │ │ └── index.ts │ ├── components/ # UI组件 │ │ ├── ComponentName.tsx │ │ └── index.ts │ ├── api/ # API客户端 │ │ ├── client.ts │ │ └── index.ts │ ├── utils/ # 工具函数 │ │ ├── helpers.ts │ │ └── index.ts │ └── index.ts # 主入口 ├── tests/ # 测试文件 │ ├── pages/ │ │ └── PageName/ │ │ └── PageName.test.tsx │ └── components/ │ └── ComponentName.test.tsx ├── package.json ├── jest.config.cjs # Jest配置 └── tsconfig.json ``` ### 8.2 package.json配置 ```json { "name": "@d8d/", "version": "1.0.0", "type": "module", "main": "src/index.ts", "types": "src/index.ts", "exports": { ".": { "types": "./src/index.ts", "import": "./src/index.ts" }, "./api": { "types": "./src/api/index.ts", "import": "./src/api/index.ts" }, "./pages//": { "types": "./src/pages//.tsx", "import": "./src/pages//.tsx" } }, "dependencies": { "@d8d/mini-shared-ui-components": "workspace:*", "@tarojs/components": "^4.1.4", "@tarojs/taro": "^4.1.4", "react": "^19.1.0" }, "devDependencies": { "@testing-library/react": "^16.3.0", "jest": "^30.2.0", "ts-jest": "^29.4.5" } } ``` ## 数据获取规范 ### 9.1 使用React Query管理服务端状态 **重要**: Mini UI包必须使用React Query (`@tanstack/react-query`) 管理服务端状态,而不是手动使用`useState` + `useEffect`。 **原因**: - 符合项目技术栈要求(见`component-architecture.md`) - 自动处理加载状态、错误状态、缓存 - 更好的类型推断和RPC集成 - 统一的数据获取模式 #### 9.1.1 基本用法 ```typescript import { useQuery } from '@tanstack/react-query' import { apiClient } from '../api' const MyPage: React.FC = () => { // ✅ 正确: 使用React Query const { data, isLoading, error } = useQuery({ queryKey: ['resource-name'], queryFn: async () => { const res = await apiClient.resource.$get() if (!res.ok) { throw new Error('获取数据失败') } return await res.json() } }) // ❌ 错误: 不要使用useState + useEffect手动管理状态 // const [data, setData] = useState(null) // const [loading, setLoading] = useState(true) // useEffect(() => { // const fetchData = async () => { // setLoading(true) // const res = await apiClient.resource.$get() // const data = await res.json() // setData(data) // setLoading(false) // } // fetchData() // }, []) if (isLoading) return
加载中...
if (error) return
加载失败
return
{/* 渲染数据 */}
} ``` #### 9.1.2 多个独立查询 ```typescript const MyPage: React.FC = () => { // 多个独立的查询可以并行执行 const { data: statusData, isLoading: statusLoading } = useQuery({ queryKey: ['employment-status'], queryFn: async () => { const res = await apiClient.employment.status.$get() if (!res.ok) throw new Error('获取就业状态失败') return await res.json() } }) const { data: recordsData, isLoading: recordsLoading } = useQuery({ queryKey: ['salary-records'], queryFn: async () => { const res = await apiClient.employment['salary-records'].$get({ query: { take: 3 } }) if (!res.ok) throw new Error('获取薪资记录失败') const data = await res.json() return data.data || [] } }) const { data: historyData, isLoading: historyLoading } = useQuery({ queryKey: ['employment-history'], queryFn: async () => { const res = await apiClient.employment.history.$get({ query: { take: 20 } }) if (!res.ok) throw new Error('获取就业历史失败') const data = await res.json() return data.data || [] } }) // 每个查询有独立的loading状态 return ( ) } ``` #### 9.1.3 错误处理 ```typescript const MyPage: React.FC = () => { const { data, isLoading, error } = useQuery({ queryKey: ['resource'], queryFn: async () => { const res = await apiClient.resource.$get() if (!res.ok) { throw new Error('获取数据失败') } return await res.json() } }) // 使用useEffect处理错误 React.useEffect(() => { if (error) { Taro.showToast({ title: error.message, icon: 'none' }) } }, [error]) if (isLoading) return
加载中...
return
{/* 渲染数据 */}
} ``` #### 9.1.4 数据修改 (useMutation) 对于需要修改服务端数据的操作(POST、PUT、DELETE),使用`useMutation`: ```typescript import { useMutation, useQueryClient } from '@tanstack/react-query' import { apiClient } from '../api' const MyPage: React.FC = () => { const queryClient = useQueryClient() // 数据修改mutation const mutation = useMutation({ mutationFn: async (formData: MyFormData) => { const res = await apiClient.resource.$post({ json: formData }) if (!res.ok) { throw new Error('操作失败') } return await res.json() }, onSuccess: (data) => { // 成功后刷新相关查询 queryClient.invalidateQueries({ queryKey: ['resource-list'] }) Taro.showToast({ title: '操作成功', icon: 'success' }) }, onError: (error) => { Taro.showToast({ title: error.message, icon: 'none' }) } }) const handleSubmit = (formData: MyFormData) => { mutation.mutate(formData) } return ( ) } ``` **关键点**: - 使用`mutationFn`定义异步操作 - 使用`onSuccess`处理成功逻辑,通常需要`invalidateQueries`刷新数据 - 使用`onError`处理错误 - 使用`isPending`判断加载状态 #### 9.1.5 无限滚动查询 (useInfiniteQuery) 对于分页列表数据,使用`useInfiniteQuery`实现无限滚动: ```typescript import { useInfiniteQuery } from '@tanstack/react-query' import { apiClient } from '../api' const MyPage: React.FC = () => { // 无限滚动查询 const { data, isLoading, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({ queryKey: ['infinite-list'], queryFn: async ({ pageParam = 0 }) => { const res = await apiClient.items.$get({ query: { skip: pageParam * 20, take: 20 } }) if (!res.ok) { throw new Error('获取数据失败') } const data = await res.json() return { items: data.data || [], nextCursor: pageParam + 1 } }, initialPageParam: 0, getNextPageParam: (lastPage) => lastPage.nextCursor }) // 扁平化所有页的数据 const allItems = data?.pages.flatMap(page => page.items) || [] const handleLoadMore = () => { if (hasNextPage && !isFetchingNextPage) { fetchNextPage() } } return ( {allItems.map(item => ( {item.name} ))} {isFetchingNextPage && 加载更多...} {!hasNextPage && allItems.length > 0 && 没有更多数据了} ) } ``` **关键点**: - `pageParam`用于传递分页参数 - `getNextPageParam`决定是否有下一页 - 使用`pages.flatMap`合并所有页数据 - 使用`fetchNextPage`加载下一页 - 使用`hasNextPage`和`isFetchingNextPage`控制加载状态 ## 测试规范 ### 10.1 Jest配置 ```javascript module.exports = { preset: 'ts-jest', testEnvironment: 'jsdom', setupFilesAfterEnv: ['@d8d/mini-testing-utils/testing/setup'], moduleNameMapper: { '^@/(.*)$': '/src/$1', '^~/(.*)$': '/tests/$1', '^@tarojs/taro$': '@d8d/mini-testing-utils/testing/taro-api-mock.ts', '\\.(css|less|scss)$': '@d8d/mini-testing-utils/testing/style-mock.js' }, testMatch: [ '/tests/**/*.test.{ts,tsx}' ], transform: { '^.+\\.(ts|tsx)$': 'ts-jest' } } ``` ### 10.2 组件测试示例 ```typescript import { render, screen } from '@testing-library/react' import { View, Text } from '@tarojs/components' import { MyComponent } from '../MyComponent' describe('MyComponent', () => { it('渲染组件并验证垂直布局', () => { render() // 验证组件包含 flex flex-col 类 const container = screen.getByTestId('my-container') expect(container.className).toContain('flex flex-col') }) }) ``` ### 10.3 页面集成测试(使用React Query) **重要**: 页面集成测试必须使用真实的React Query,不要mock React Query。 ```typescript import React from 'react' import { render, screen, waitFor } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import MyPage from '../pages/MyPage' // Mock API client jest.mock('../api', () => ({ apiClient: { resource: { $get: jest.fn() } } })) const { apiClient } = require('../api') const createTestQueryClient = () => new QueryClient({ defaultOptions: { queries: { retry: false, staleTime: Infinity }, mutations: { retry: false } } }) const renderWithQueryClient = (component: React.ReactElement) => { const queryClient = createTestQueryClient() return render( {component} ) } describe('MyPage', () => { beforeEach(() => { jest.clearAllMocks() }) it('应该显示加载状态', async () => { // Mock API为pending状态 apiClient.resource.$get.mockImplementation(() => new Promise(() => {})) renderWithQueryClient() expect(screen.getByText('加载中...')).toBeInTheDocument() }) it('应该成功加载并显示数据', async () => { const mockData = { name: '测试数据' } apiClient.resource.$get.mockResolvedValue({ ok: true, json: async () => mockData }) renderWithQueryClient() await waitFor(() => { expect(screen.getByText('测试数据')).toBeInTheDocument() }) }) }) ``` ## 常见问题和解决方案 ### 11.1 布局问题 **问题**: 元素横向排列而不是垂直排列 - **原因**: View容器默认是flex-row - **解决**: 添加`flex flex-col`类 **问题**: Text组件在同一行显示 - **原因**: Text组件默认是内联显示 - **解决**: 父容器添加`flex flex-col`类 ### 11.2 样式问题 **问题**: Tailwind样式不生效 - **原因**: 类名冲突或拼写错误 - **解决**: 检查类名拼写,确保使用正确的Tailwind类名 **问题**: 样式在不同平台表现不一致 - **原因**: 不同小程序平台的样式引擎差异 - **解决**: 使用Taro提供的跨平台样式方案,避免使用平台特有样式 ### 11.3 API问题 **问题**: RPC客户端类型错误 - **原因**: API路径映射错误或类型推断不正确 - **解决**: 验证后端路由定义,使用RPC推断类型 ### 11.4 React Query问题 **问题**: 测试中React Query不工作 - **原因**: 忘记使用QueryClientProvider包裹 - **解决**: 使用renderWithQueryClient包装组件 **问题**: queryKey冲突导致数据混乱 - **原因**: 不同查询使用了相同的queryKey - **解决**: 为每个查询使用唯一的queryKey **问题**: mutation后数据没有更新 - **原因**: 忘记调用invalidateQueries - **解决**: 在onSuccess回调中刷新相关查询 **问题**: 无限滚动一直触发加载 - **原因**: getNextPageParam返回逻辑错误 - **解决**: 正确判断是否还有下一页,返回undefined或nextCursor ## 最佳实践 ### 12.1 组件开发 1. **始终使用flex flex-col实现垂直布局** 2. **为每个View添加语义化的className** 3. **使用data-testid属性便于测试** 4. **组件props使用TypeScript接口定义** 5. **使用相对路径导入包内模块** 6. **使用React Query管理服务端状态** 7. **为每个query使用唯一的queryKey** ### 12.2 数据获取 1. **使用useQuery获取数据,不要使用useState + useEffect** 2. **在queryFn中检查response.ok,失败时throw Error** 3. **使用useEffect处理错误,显示Toast提示** 4. **多个独立查询使用不同的queryKey** 5. **测试中使用真实的QueryClientProvider** ### 12.3 数据修改 1. **使用useMutation处理POST/PUT/DELETE操作** 2. **在mutationFn中检查response.ok,失败时throw Error** 3. **使用onSuccess刷新相关查询(invalidateQueries)** 4. **使用onError显示错误提示** 5. **使用isPending显示加载状态,避免重复提交** ### 12.4 无限滚动 1. **使用useInfiniteQuery处理分页列表** 2. **使用pages.flatMap合并所有页数据** 3. **正确实现getNextPageParam判断是否有下一页** 4. **使用hasNextPage和isFetchingNextPage控制加载状态** 5. **在ScrollView的onScrollToLower中触发fetchNextPage** ### 12.5 性能优化 1. **使用Image组件的lazyLoad属性** 2. **列表数据使用虚拟滚动** 3. **避免不必要的重渲染** 4. **使用React.memo优化组件性能** 5. **利用React Query的缓存机制减少重复请求** ### 12.6 代码质量 1. **遵循项目编码标准** 2. **编写单元测试和集成测试** 3. **使用TypeScript严格模式** 4. **运行pnpm typecheck确保类型正确** 5. **使用ESLint进行代码检查** ## 参考实现 ### 13.1 用人方小程序UI包 - `mini-ui-packages/yongren-dashboard-ui` - `mini-ui-packages/yongren-order-management-ui` - `mini-ui-packages/yongren-talent-management-ui` ### 13.2 人才小程序UI包 - `mini-ui-packages/rencai-dashboard-ui` - `mini-ui-packages/rencai-personal-info-ui` - `mini-ui-packages/rencai-employment-ui` - 使用React Query的参考实现 - `mini-ui-packages/rencai-auth-ui` ### 13.3 共享组件 - `mini-ui-packages/mini-shared-ui-components` ## 版本历史 | 版本 | 日期 | 变更说明 | 作者 | |------|------|----------|------| | 1.3 | 2025-12-28 | 添加useMutation和useInfiniteQuery规范,完善React Query最佳实践 | James (Claude Code) | | 1.2 | 2025-12-28 | 添加React Query数据获取规范,更新测试规范章节 | James (Claude Code) | | 1.1 | 2025-12-26 | 添加图标使用规范(Heroicons) | Bob (Scrum Master) | | 1.0 | 2025-12-26 | 基于史诗011和017经验创建Mini UI包开发规范 | Bob (Scrum Master) | --- **重要提醒**: 1. 本规范专门针对Taro小程序UI包开发,与Web UI包开发规范(`ui-package-standards.md`)不同 2. `flex flex-col`是Taro小程序中最常用的布局类,请务必牢记 3. **使用React Query管理服务端状态**,不要使用useState + useEffect手动管理 4. 所有Mini UI包的开发都应遵循本规范