mini-ui-小程序UI开发.md 13 KB


description: "小程序ui开发指令"

按小程序ui规范,小程序表单开发规范(.roo/commands/mini-form.md)

小程序UI开发规范 (Tailwind CSS v4)

概述

本规范定义了基于Taro框架的小程序UI开发标准,采用Tailwind CSS v4原子化样式和Heroicons图标库,遵循shadcn/ui组件设计模式。

技术栈

  • Taro 4 - 跨端小程序框架
  • React 18 - 前端框架
  • Tailwind CSS v4 - 原子化CSS框架
  • @egoist/tailwindcss-icons - 图标库集成
  • @weapp-tailwindcss/merge - Tailwind类名合并工具(小程序版tailwind-merge)
  • clsx - 条件样式类名管理

目录结构

mini/
├── src/
│   ├── components/
│   │   └── ui/           # UI组件库
│   │       ├── button.tsx
│   │       ├── input.tsx
│   │       ├── card.tsx
│   │       └── ...
│   ├── pages/
│   ├── utils/
│   └── app.css           # Tailwind样式入口
├── tailwind.config.js    # Tailwind配置
└── postcss.config.js     # PostCSS配置

样式规范

1. Tailwind CSS v4 使用规范

1.1 基础类名使用

// ✅ 正确使用原子类
<View className="flex items-center justify-between p-4 bg-white rounded-lg shadow-sm">
  <Text className="text-lg font-semibold text-gray-900">标题</Text>
</View>

// ❌ 避免使用内联样式
<View style={{ display: 'flex', alignItems: 'center', padding: 16 }}>
  <Text style={{ fontSize: 18, fontWeight: '600' }}>标题</Text>
</View>

1.2 类名合并规范

// ✅ 使用twMerge处理动态类名冲突
import { twMerge } from '@weapp-tailwindcss/merge'

// 处理静态和动态类名的冲突
<View className={twMerge('px-4 py-2', isActive ? 'bg-blue-500' : 'bg-gray-200')}>
  <Text>按钮</Text>
</View>

// 处理多个条件类名的合并
<View className={twMerge(
  'flex items-center',
  isActive && 'bg-blue-500 text-white',
  isDisabled && 'opacity-50 cursor-not-allowed',
  customClassName
)}>
  <Text>复杂组件</Text>
</View>

// ❌ 避免手动拼接类名导致冲突
<View className={`px-4 py-2 ${isActive ? 'bg-blue-500' : 'bg-gray-200'} ${customClassName}`}>
  <Text>按钮</Text>
</View>

1.2 响应式设计

// 使用Tailwind的响应式前缀
<View className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  <View className="w-full sm:w-1/2 md:w-1/3" />
</View>

1.3 状态样式

// 悬停和焦点状态
<Button className="bg-blue-500 hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
  点击按钮
</Button>

// 禁用状态
<Button className="disabled:opacity-50 disabled:cursor-not-allowed">
  禁用按钮
</Button>

2. 图标使用规范

2.1 图标

使用@egoist/tailwindcss-icons提供的图标类名: "mdi", "lucide", "heroicons", "heroicons-outline", "heroicons-solid"

// 基础用法
<View className="i-heroicons-user-20-solid text-gray-600" />
<Button className="flex items-center gap-2">
  <View className="i-heroicons-plus-20-solid" />
  <Text>添加</Text>
</Button>

// 图标大小和颜色
<View className="i-heroicons-home-16-solid w-6 h-6 text-blue-500" />
<View className="i-heroicons-cog-8-tooth-20-solid w-8 h-8 text-gray-400" />

// 图标变体
// solid - 实心图标
// outline - 轮廓图标
// mini - 迷你图标 (20x20)
// micro - 微型图标 (16x16)
<View className="i-heroicons-heart-20-solid text-red-500" />
<View className="i-heroicons-heart-20-outline text-red-500" />

2.2 图标命名规则

i-heroicons-[图标名]-[大小]-[变体]
  • 大小: 16 | 20 | 24
  • 变体: solid | outline

3. UI组件规范

3.1 组件文件结构

每个UI组件应包含:

// mini/src/components/ui/button.tsx
import { Button as TaroButton, ButtonProps } from '@tarojs/components'
import { cn } from '@/utils/cn'
import { cva, type VariantProps } from 'class-variance-authority'

const buttonVariants = cva(
  'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background',
  {
    variants: {
      variant: {
        default: 'bg-primary text-primary-foreground hover:bg-primary/90',
        destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
        outline: 'border border-input hover:bg-accent hover:text-accent-foreground',
        secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
        ghost: 'hover:bg-accent hover:text-accent-foreground',
        link: 'underline-offset-4 hover:underline text-primary',
      },
      size: {
        default: 'h-10 py-2 px-4',
        sm: 'h-9 px-3 rounded-md',
        lg: 'h-11 px-8 rounded-md',
        icon: 'h-10 w-10',
      },
    },
    defaultVariants: {
      variant: 'default',
      size: 'default',
    },
  }
)

interface ButtonProps extends ButtonProps, VariantProps<typeof buttonVariants> {}

export function Button({ className, variant, size, ...props }: ButtonProps) {
  return (
    <TaroButton
      className={cn(buttonVariants({ variant, size, className }))}
      {...props}
    />
  )
}

3.2 常用组件示例

按钮组件 (Button)

// 使用示例
<Button variant="primary" size="lg" onClick={handleClick}>
  <View className="i-heroicons-plus-20-solid mr-2" />
  创建用户
</Button>

<Button variant="outline" size="sm" disabled={loading}>
  {loading && <View className="i-heroicons-arrow-path-20-solid animate-spin mr-2" />}
  加载中...
</Button>

卡片组件 (Card)

<Card className="p-6 bg-white rounded-lg shadow-sm">
  <CardHeader>
    <View className="flex items-center justify-between">
      <Text className="text-lg font-semibold">用户信息</Text>
      <View className="i-heroicons-user-circle-20-solid text-gray-400" />
    </View>
  </CardHeader>
  <CardContent>
    <Text className="text-gray-600">用户详情内容</Text>
  </CardContent>
</Card>

输入框组件 (Input)

<View className="space-y-2">
  <Label htmlFor="username">用户名</Label>
  <View className="relative">
    <View className="absolute left-3 top-1/2 -translate-y-1/2">
      <View className="i-heroicons-user-20-solid text-gray-400 w-5 h-5" />
    </View>
    <Input
      id="username"
      className="pl-10"
      placeholder="请输入用户名"
      value={username}
      onInput={handleInput}
    />
  </View>
</View>

4. 页面布局规范

4.1 页面容器

// 主容器
<View className="min-h-screen bg-gray-50">
  <View className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
    {/* 页面内容 */}
  </View>
</View>

// 卡片布局
<View className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
  {/* 卡片内容 */}
</View>

4.2 响应式断点

  • sm: 640px
  • md: 768px
  • lg: 1024px
  • xl: 1280px
  • 2xl: 1536px

5. 主题配置

5.1 颜色系统

/* 在 app.css 中定义 */
:root {
  --primary: 59 130 246;
  --primary-foreground: 255 255 255;
  --secondary: 107 114 128;
  --secondary-foreground: 255 255 255;
  --accent: 243 244 246;
  --accent-foreground: 17 24 39;
  --destructive: 239 68 68;
  --destructive-foreground: 255 255 255;
  --muted: 249 250 251;
  --muted-foreground: 107 114 128;
  --border: 229 231 235;
  --input: 255 255 255;
  --ring: 59 130 246;
  --background: 255 255 255;
  --foreground: 17 24 39;
}

5.2 Tailwind配置

// tailwind.config.js
module.exports = {
  content: [
    './src/**/*.{js,ts,jsx,tsx}',
  ],
  theme: {
    extend: {
      colors: {
        primary: 'rgb(var(--primary))',
        'primary-foreground': 'rgb(var(--primary-foreground))',
        secondary: 'rgb(var(--secondary))',
        'secondary-foreground': 'rgb(var(--secondary-foreground))',
        accent: 'rgb(var(--accent))',
        'accent-foreground': 'rgb(var(--accent-foreground))',
        destructive: 'rgb(var(--destructive))',
        'destructive-foreground': 'rgb(var(--destructive-foreground))',
        muted: 'rgb(var(--muted))',
        'muted-foreground': 'rgb(var(--muted-foreground))',
        border: 'rgb(var(--border))',
        input: 'rgb(var(--input))',
        ring: 'rgb(var(--ring))',
        background: 'rgb(var(--background))',
        foreground: 'rgb(var(--foreground))',
      },
    },
  },
  plugins: [
    require('@egoist/tailwindcss-icons')({
      // 配置Heroicons
      collections: {
        heroicons: {
          solid: true,
          outline: true,
          mini: true,
        },
      },
    }),
  ],
}

6. 工具函数

6.1 类名合并工具

// mini/src/utils/cn.ts
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from '@weapp-tailwindcss/merge'

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

6.2 小程序专用类名处理

// 小程序环境下的类名合并
import { twMerge } from '@weapp-tailwindcss/merge'

// 标准用法(自动处理小程序转义)
const classes = twMerge('px-2 py-1 bg-red hover:bg-dark-red', 'p-3 bg-[#B91C1C]')
// → 'hovercbg-dark-red p-3 bg-_hB91C1C_'

// 手动指定版本(如果需要)
import { twMerge as twMergeV4 } from '@weapp-tailwindcss/merge/v4'
import { twMerge as twMergeV3 } from '@weapp-tailwindcss/merge/v3'

// 使用cva进行组件变体管理
import { cva } from 'class-variance-authority'

const buttonVariants = cva(
  'inline-flex items-center justify-center rounded-md text-sm font-medium',
  {
    variants: {
      variant: {
        default: 'bg-blue-500 text-white hover:bg-blue-600',
        destructive: 'bg-red-500 text-white hover:bg-red-600',
      },
      size: {
        sm: 'h-8 px-3 text-xs',
        lg: 'h-12 px-6 text-base',
      },
    },
  }
)

7. 最佳实践

7.1 状态管理

// 使用React Hook进行状态管理
const [loading, setLoading] = useState(false)
const [data, setData] = useState<User[]>([])

// 加载状态显示
{loading ? (
  <View className="flex justify-center py-8">
    <View className="i-heroicons-arrow-path-20-solid animate-spin w-8 h-8 text-blue-500" />
  </View>
) : (
  <View className="grid grid-cols-1 gap-4">
    {data.map(item => <Card key={item.id} {...item} />)}
  </View>
)}

7.2 错误处理

// 错误状态展示
<View className="text-center py-8">
  <View className="i-heroicons-exclamation-triangle-20-solid w-12 h-12 text-red-500 mx-auto mb-4" />
  <Text className="text-gray-600">加载失败,请稍后重试</Text>
  <Button variant="outline" size="sm" onClick={retry} className="mt-4">
    重新加载
  </Button>
</View>

8. 性能优化

8.1 样式优化

  • 使用Tailwind的JIT模式,只生成用到的类名
  • 避免内联样式,全部使用类名
  • 合理使用@apply提取重复样式

8.2 图标优化

  • 使用CSS图标而非图片图标
  • 图标按需加载,不使用的图标不会被打包
  • 合理使用图标大小,避免过大图标

9. 调试工具

9.1 开发调试

// 添加调试样式类
<View className="border border-red-500 debug">
  <Text>调试内容</Text>
</View>

// 使用Tailwind的调试工具
// 在开发环境中添加
// <View className="outline outline-1 outline-red-500" />

10. tailwind-merge使用规范

10.1 基本用法

// 单类名合并
const result = twMerge('px-2 py-1 bg-red hover:bg-dark-red', 'p-3 bg-[#B91C1C]')
// → 'hovercbg-dark-red p-3 bg-_hB91C1C_'

// 处理冲突类名
twMerge('px-4', 'px-2') // → 'px-2'
twMerge('text-red-500', 'text-blue-500') // → 'text-blue-500'

10.2 条件类名处理

// 使用cn工具函数处理条件类名
import { cn } from '@/utils/cn'

const Button = ({ variant, size, disabled, className }) => {
  return (
    <Button
      className={cn(
        'inline-flex items-center justify-center rounded-md',
        variant === 'primary' && 'bg-blue-500 text-white',
        variant === 'secondary' && 'bg-gray-200 text-gray-800',
        size === 'sm' && 'px-3 py-1 text-sm',
        size === 'lg' && 'px-6 py-3 text-lg',
        disabled && 'opacity-50 cursor-not-allowed',
        className // 允许外部覆盖
      )}
    >
      按钮
    </Button>
  )
}

10.3 小程序特殊处理

// 跨端使用
import { create } from '@weapp-tailwindcss/merge'

const { twMerge } = create({
  // 在当前环境为小程序时启用转义
  disableEscape: true
})

// 版本选择
import { twMerge as twMergeV4 } from '@weapp-tailwindcss/merge/v4' // Tailwind v4
import { twMerge as twMergeV3 } from '@weapp-tailwindcss/merge/v3' // Tailwind v3

注意事项

  1. 兼容性:确保所有类名在小程序环境中有效
  2. 性能:避免过度嵌套和复杂选择器
  3. 可维护性:保持组件结构清晰,样式统一
  4. 可读性:合理使用空格和换行,提高代码可读性
  5. tailwind-merge:始终使用twMerge或cn工具函数处理动态类名,避免类名冲突
  6. 版本兼容:根据Tailwind CSS版本选择正确的tailwind-merge版本