按小程序ui规范,小程序表单开发规范(.roo/commands/mini-form.md)
本规范定义了基于Taro框架的小程序UI开发标准,采用Tailwind CSS v4原子化样式和Heroicons图标库,遵循shadcn/ui组件设计模式。
mini/
├── src/
│ ├── components/
│ │ └── ui/ # UI组件库
│ │ ├── button.tsx
│ │ ├── input.tsx
│ │ ├── card.tsx
│ │ └── ...
│ ├── pages/
│ ├── utils/
│ └── app.css # Tailwind样式入口
├── tailwind.config.js # Tailwind配置
└── postcss.config.js # PostCSS配置
// ✅ 正确使用原子类
<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>
// ✅ 使用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>
// 使用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>
// 悬停和焦点状态
<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>
使用@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" />
i-heroicons-[图标名]-[大小]-[变体]
每个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}
/>
)
}
按钮组件 (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>
// 主容器
<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>
sm: 640pxmd: 768pxlg: 1024pxxl: 1280px2xl: 1536px/* 在 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;
}
// 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,
},
},
}),
],
}
// 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))
}
// 小程序环境下的类名合并
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',
},
},
}
)
// 使用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>
)}
// 错误状态展示
<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>
@apply提取重复样式// 添加调试样式类
<View className="border border-red-500 debug">
<Text>调试内容</Text>
</View>
// 使用Tailwind的调试工具
// 在开发环境中添加
// <View className="outline outline-1 outline-red-500" />
// 单类名合并
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'
// 使用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>
)
}
// 跨端使用
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