mini-ui-package-standards.md 22 KB

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小程序中,<View> 组件内的子元素默认是横向布局flex-row),这与Web开发的div默认垂直布局行为完全不同。

1.1 垂直布局规范

问题: View容器默认横向布局,导致子元素横向排列

解决方案: 必须显式添加 flex flex-col 类才能实现垂直布局

正确示例:

import { View, Text } from '@tarojs/components'

// ✅ 正确: 使用 flex flex-col 实现垂直布局
<View className="flex flex-col">
  <Text>姓名: 张三</Text>
  <Text>性别: 男</Text>
  <Text>年龄: 35</Text>
</View>

// ❌ 错误: 缺少 flex flex-col,子元素会横向排列
<View>
  <Text>姓名: 张三</Text>
  <Text>性别: 男</Text>
  <Text>年龄: 35</Text>
</View>

1.2 信息卡片布局模式

来源: 史诗011.003经验总结

标准模式:

import { View, Text } from '@tarojs/components'

export function PersonalBasicInfo({ personalInfo }: { personalInfo: PersonalInfoResponse }) {
  return (
    <View className="bg-white rounded-lg p-4">
      <Text className="text-lg font-semibold mb-4">个人基本信息</Text>

      {/* 垂直布局的信息列表 - 必须使用 flex flex-col */}
      <View className="flex flex-col space-y-2">
        <View className="flex justify-between">
          <Text className="text-gray-600">姓名</Text>
          <Text>{personalInfo.name}</Text>
        </View>

        <View className="flex justify-between">
          <Text className="text-gray-600">性别</Text>
          <Text>{personalInfo.gender}</Text>
        </View>

        <View className="flex justify-between">
          <Text className="text-gray-600">年龄</Text>
          <Text>{personalInfo.age}</Text>
        </View>

        {/* 更多字段... */}
      </View>
    </View>
  )
}

关键点:

  1. 信息列表容器必须使用 flex flex-col 实现垂直布局
  2. 每个信息项使用 flex justify-between 实现标签和值的左右分布
  3. 使用 space-y-2space-y-3 添加垂直间距
  4. 重要: 记住在所有需要垂直排列的 View 上添加 flex flex-col

2. Text组件的默认内联行为

问题: Text组件默认是内联显示(类似Web中的<span>),不会自动换行

影响: 导致多个Text组件在同一行显示,即使它们在不同的代码行上

解决方案: 使用flex flex-col强制Text组件垂直排列

实际案例 (来源: 史诗011.003):

// ❌ 错误: Text组件会内联显示在同一行
<View>
  <Text>统计1</Text>
  <Text>统计2</Text>
  <Text>统计3</Text>
</View>

// ✅ 正确: 使用 flex flex-col 强制垂直排列
<View className="flex flex-col">
  <Text>统计1</Text>
  <Text>统计2</Text>
  <Text>统计3</Text>
</View>

3. 常见布局模式

3.1 卡片容器布局

<View className="bg-white rounded-lg p-4 shadow-sm">
  <Text className="text-lg font-semibold mb-4">卡片标题</Text>

  {/* 内容区域 - 垂直布局 */}
  <View className="flex flex-col space-y-3">
    {items.map(item => (
      <View key={item.id} className="flex justify-between border-b pb-2">
        <Text className="text-gray-600">{item.label}</Text>
        <Text>{item.value}</Text>
      </View>
    ))}
  </View>
</View>

3.2 列表项布局

<View className="flex flex-col space-y-2">
  {list.map(item => (
    <View key={item.id} className="bg-white rounded-lg p-3">
      {/* 列表项标题 */}
      <Text className="font-semibold mb-2">{item.title}</Text>

      {/* 列表项内容 - 垂直布局 */}
      <View className="flex flex-col space-y-1">
        <Text className="text-sm text-gray-600">{item.description}</Text>
        <Text className="text-xs text-gray-400">{item.date}</Text>
      </View>
    </View>
  ))}
</View>

3.3 网格布局

{/* 2列网格 */}
<View className="grid grid-cols-2 gap-3">
  {items.map(item => (
    <View key={item.id} className="bg-white rounded-lg p-3">
      <View className="flex flex-col">
        <Text className="font-semibold">{item.title}</Text>
        <Text className="text-sm text-gray-600">{item.value}</Text>
      </View>
    </View>
  ))}
</View>

图标使用规范

3.1 图标系统概述

项目使用的图标库: Heroicons (UnoCSS图标集)

图标类命名规范: i-heroicons-{icon-name}-{size}-{style}

重要: 不要使用emoji,必须使用Heroicons图标类。

3.2 图标类使用规范

3.2.1 基础图标使用

正确示例:

import { View } from '@tarojs/components'

// ✅ 正确: 使用Heroicons图标类
<View className="i-heroicons-chevron-left-20-solid w-5 h-5 text-gray-600" />
<View className="i-heroicons-user-20-solid w-6 h-6 text-blue-500" />
<View className="i-heroicons-bell-20-solid w-4 h-4 text-gray-400" />

// ❌ 错误: 使用emoji
<Text>🔔</Text>
<Text>👤</Text>
<View>←</View>

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 图标尺寸和颜色

尺寸类:

<View className="i-heroicons-user-20-solid w-4 h-4" />   // 16px
<View className="i-heroicons-user-20-solid w-5 h-5" />   // 20px
<View className="i-heroicons-user-20-solid w-6 h-6" />   // 24px
<View className="i-heroicons-user-20-solid w-8 h-8" />   // 32px
<View className="i-heroicons-user-20-solid text-xl" />  // 使用Tailwind文本尺寸
<View className="i-heroicons-user-20-solid text-2xl" /> // 使用Tailwind文本尺寸

颜色类:

<View className="i-heroicons-user-20-solid text-gray-400" />  // 灰色
<View className="i-heroicons-user-20-solid text-blue-500" />  // 蓝色
<View className="i-heroicons-user-20-solid text-green-500" /> // 绿色
<View className="i-heroicons-user-20-solid text-red-500" />   // 红色
<View className="i-heroicons-user-20-solid text-white" />    // 白色
<View className="i-heroicons-user-20-solid text-yellow-600" /> // 黄色
<View className="i-heroicons-user-20-solid text-purple-500" /> // 紫色

3.2.4 常见使用场景

导航栏返回按钮:

<View className="i-heroicons-chevron-left-20-solid w-5 h-5" />

功能入口图标:

<View className="flex flex-col items-center">
  <View className="i-heroicons-user-20-solid text-blue-500 text-lg mb-1" />
  <Text className="text-sm">个人信息</Text>
</View>

<View className="flex flex-col items-center">
  <View className="i-heroicons-document-text-20-solid text-green-500 text-lg mb-1" />
  <Text className="text-sm">考勤记录</Text>
</View>

<View className="flex flex-col items-center">
  <View className="i-heroicons-chart-bar-20-solid text-purple-500 text-lg mb-1" />
  <Text className="text-sm">薪资查询</Text>
</View>

<View className="flex flex-col items-center">
  <View className="i-heroicons-building-office-2-20-solid text-yellow-600 text-lg mb-1" />
  <Text className="text-sm">企业信息</Text>
</View>

状态指示器:

// 加载中
<View className="i-heroicons-arrow-path-20-solid animate-spin w-5 h-5" />

// 成功/提示
<View className="i-heroicons-check-circle-20-solid text-green-500 w-6 h-6" />

// 警告
<View className="i-heroicons-exclamation-triangle-20-solid text-orange-500 w-6 h-6" />

// 错误
<View className="i-heroicons-x-circle-20-solid text-red-500 w-6 h-6" />

// 信息
<View className="i-heroicons-information-circle-20-solid text-blue-500 w-6 h-6" />

输入框图标:

<View className="flex items-center">
  <View className="i-heroicons-phone-20-solid text-gray-400 mr-3 w-5 h-5" />
  <Input placeholder="请输入手机号" />
</View>

<View className="flex items-center">
  <View className="i-heroicons-lock-closed-20-solid text-gray-400 mr-3 w-5 h-5" />
  <Input placeholder="请输入密码" type="password" />
</View>

二维码按钮:

<View className="flex items-center">
  <Text>张三</Text>
  <View className="i-heroicons-qr-code-20-solid text-white text-lg ml-2" />
</View>

3.2.5 动画效果

旋转动画:

<View className="i-heroicons-arrow-path-20-solid animate-spin w-5 h-5" />

脉冲动画:

<View className="i-heroicons-bell-20-solid animate-pulse w-6 h-6" />

3.3 Navbar图标规范

返回按钮图标:

leftIcon = 'i-heroicons-chevron-left-20-solid'

示例:

<Navbar
  title="页面标题"
  leftIcon="i-heroicons-chevron-left-20-solid"
  leftText="返回"
  onClickLeft={() => Taro.navigateBack()}
/>

3.4 TabBar图标规范

使用iconClass属性(推荐):

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 常见错误避免

错误示例:

// ❌ 错误1: 使用emoji
<Text>🔔 通知</Text>
<View>👤</View>

// ❌ 错误2: 使用文本符号
<Text>← 返回</Text>
<View>→</View>

// ❌ 错误3: 使用其他图标库
<View className="fa fa-user" />
<View className="material-icons">person</View>

// ❌ 错误4: 忘记添加尺寸类
<View className="i-heroicons-user-20-solid" />  {/* 没有尺寸,可能不显示 */}

// ❌ 错误5: 图标类名拼写错误
<View className="i-heroicon-user-20-solid" />   {/* 缺少s */}
<View className="i-heroicons-user-20-solid" />   {/* ✅ 正确 */}

正确示例:

// ✅ 正确1: 使用Heroicons图标类
<View className="i-heroicons-bell-20-solid w-5 h-5" />
<View className="i-heroicons-user-20-solid w-6 h-6" />

// ✅ 正确2: 添加尺寸和颜色
<View className="i-heroicons-chevron-left-20-solid w-5 h-5 text-gray-600" />

// ✅ 正确3: 图标+文本组合
<View className="flex items-center">
  <View className="i-heroicons-phone-20-solid text-blue-500 w-5 h-5 mr-2" />
  <Text>联系电话</Text>
</View>

Taro组件使用规范

4.1 基础组件导入

import { View, Text, Image, Button, ScrollView } from '@tarojs/components'
import Taro from '@tarojs/taro'

4.2 Image组件规范

<Image
  src={imageUrl}
  mode="aspectFill"  // 或 aspectFit, widthFix
  className="w-full h-32 rounded-lg"
  lazyLoad          // 懒加载
  onClick={handleClick}
/>

mode模式说明:

  • aspectFill: 保持纵横比缩放图片,确保图片填充整个容器(可能裁剪)
  • aspectFit: 保持纵横比缩放图片,确保图片完全显示(可能有空白)
  • widthFix: 宽度不变,高度自动变化,保持原图宽高比

4.3 ScrollView组件规范

<ScrollView
  scrollY           // 垂直滚动
  className="h-full"
  onScrollToLower={handleLoadMore}
  lowerThreshold={100}
>
  <View className="flex flex-col">
    {items.map(item => (
      <View key={item.id}>{item.content}</View>
    ))}
  </View>
</ScrollView>

4.4 Button组件规范

注意: Taro的Button组件有默认样式,如需自定义样式建议使用View

// ✅ 推荐: 使用View实现自定义按钮
<View
  className="bg-blue-500 text-white py-2 px-4 rounded text-center"
  onClick={handleClick}
>
  <Text>确定</Text>
</View>

// ⚠️ 谨慎使用: Taro Button组件有平台默认样式
<Button onClick={handleClick}>确定</Button>

Navbar导航栏集成规范

5.1 Navbar组件来源

import { Navbar } from '@d8d/mini-shared-ui-components/components/navbar'

5.2 页面层级划分

TabBar页面(一级,无返回按钮):

  • 首页/个人主页
  • 列表页
  • 个人信息页
  • 设置页

非TabBar页面(二级,带返回按钮):

  • 详情页
  • 编辑页
  • 从其他页面跳转来的页面

5.3 Navbar配置规范

TabBar页面(无返回按钮):

<Navbar
  title="页面标题"
  leftIcon=""
  leftText=""
  onClickLeft={() => {}}
  placeholder
  fixed
/>

非TabBar页面(带返回按钮):

import Taro from '@tarojs/taro'

<Navbar
  title="页面标题"
  leftIcon="i-heroicons-chevron-left-20-solid"
  leftText="返回"
  onClickLeft={() => Taro.navigateBack()}
  placeholder
  fixed
/>

5.4 完整页面结构示例

import { View, ScrollView } from '@tarojs/components'
import { Navbar } from '@d8d/mini-shared-ui-components/components/navbar'

export function MyPage() {
  return (
    <View className="h-screen bg-gray-100">
      {/* Navbar导航栏 */}
      <Navbar
        title="页面标题"
        leftIcon=""
        leftText=""
        onClickLeft={() => {}}
        placeholder
        fixed
      />

      {/* 页面内容 */}
      <ScrollView scrollY className="h-full">
        <View className="flex flex-col space-y-3 p-4">
          {/* 页面内容 */}
        </View>
      </ScrollView>
    </View>
  )
}

照片预览功能实现

6.1 使用Taro.previewImage

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 (
    <View onClick={handlePreview} className="flex flex-col">
      <Image
        src={url}
        mode="aspectFill"
        className="w-full h-32 rounded-lg"
      />
      <Text>{type}</Text>
    </View>
  )
}

多图片预览:

const handlePreview = (currentIndex: number) => {
  Taro.previewImage({
    current: images[currentIndex].url,
    urls: images.map(img => img.url)
  })
}

数据脱敏规范

7.1 银行卡号脱敏

/**
 * 脱敏银行卡号
 * @param cardNumber 完整银行卡号
 * @returns 脱敏后的银行卡号(如:**** **** **** 1234)
 */
export function maskCardNumber(cardNumber: string): string {
  if (!cardNumber || cardNumber.length < 4) {
    return '****'
  }
  const last4 = cardNumber.slice(-4)
  return `**** **** **** ${last4}`
}

// 使用示例
<View className="flex justify-between">
  <Text className="text-gray-600">银行卡号</Text>
  <Text>{maskCardNumber(bankCard.cardNumber)}</Text>
</View>

7.2 身份证号脱敏

/**
 * 脱敏身份证号
 * @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}`
}

// 使用示例
<View className="flex justify-between">
  <Text className="text-gray-600">身份证号</Text>
  <Text>{maskIdCard(personalInfo.idCard)}</Text>
</View>

7.3 手机号脱敏

/**
 * 脱敏手机号
 * @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 标准目录结构

mini-ui-packages/<package-name>/
├── 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配置

{
  "name": "@d8d/<package-name>",
  "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/<PageName>/<PageName>": {
      "types": "./src/pages/<PageName>/<PageName>.tsx",
      "import": "./src/pages/<PageName>/<PageName>.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 Jest配置

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['@d8d/mini-testing-utils/setup'],
  moduleNameMapper: {
    '^@tarojs/taro$': '@d8d/mini-testing-utils/testing/taro-api-mock.ts',
    '\\.(css|less|scss)$': '@d8d/mini-testing-utils/testing/style-mock.js'
  },
  testMatch: [
    '<rootDir>/tests/**/*.test.{ts,tsx}'
  ],
  transform: {
    '^.+\\.(ts|tsx)$': 'ts-jest'
  }
}

9.2 组件测试示例

import { render, screen } from '@testing-library/react'
import { View, Text } from '@tarojs/components'
import { MyComponent } from '../MyComponent'

describe('MyComponent', () => {
  it('渲染组件并验证垂直布局', () => {
    render(<MyComponent />)

    // 验证组件包含 flex flex-col 类
    const container = screen.getByTestId('my-container')
    expect(container.className).toContain('flex flex-col')
  })
})

常见问题和解决方案

10.1 布局问题

问题: 元素横向排列而不是垂直排列

  • 原因: View容器默认是flex-row
  • 解决: 添加flex flex-col

问题: Text组件在同一行显示

  • 原因: Text组件默认是内联显示
  • 解决: 父容器添加flex flex-col

10.2 样式问题

问题: Tailwind样式不生效

  • 原因: 类名冲突或拼写错误
  • 解决: 检查类名拼写,确保使用正确的Tailwind类名

问题: 样式在不同平台表现不一致

  • 原因: 不同小程序平台的样式引擎差异
  • 解决: 使用Taro提供的跨平台样式方案,避免使用平台特有样式

10.3 API问题

问题: RPC客户端类型错误

  • 原因: API路径映射错误或类型推断不正确
  • 解决: 验证后端路由定义,使用RPC推断类型

最佳实践

11.1 组件开发

  1. 始终使用flex flex-col实现垂直布局
  2. 为每个View添加语义化的className
  3. 使用data-testid属性便于测试
  4. 组件props使用TypeScript接口定义
  5. 使用相对路径导入包内模块

11.2 性能优化

  1. 使用Image组件的lazyLoad属性
  2. 列表数据使用虚拟滚动
  3. 避免不必要的重渲染
  4. 使用React.memo优化组件性能

11.3 代码质量

  1. 遵循项目编码标准
  2. 编写单元测试和集成测试
  3. 使用TypeScript严格模式
  4. 运行pnpm typecheck确保类型正确
  5. 使用ESLint进行代码检查

参考实现

12.1 用人方小程序UI包

  • mini-ui-packages/yongren-dashboard-ui
  • mini-ui-packages/yongren-order-management-ui
  • mini-ui-packages/yongren-talent-management-ui

12.2 人才小程序UI包

  • mini-ui-packages/rencai-dashboard-ui
  • mini-ui-packages/rencai-personal-info-ui
  • mini-ui-packages/rencai-auth-ui

12.3 共享组件

  • mini-ui-packages/mini-shared-ui-components

版本历史

版本 日期 变更说明 作者
1.0 2025-12-26 初始版本,基于史诗011和017经验总结 Bob (Scrum Master)

重要提醒:

  1. 本规范专门针对Taro小程序UI包开发,与Web UI包开发规范(ui-package-standards.md)不同
  2. flex flex-col是Taro小程序中最常用的布局类,请务必牢记
  3. 所有Mini UI包的开发都应遵循本规范