← API | 列表 | HTML转Flutter模板规范
提示信息
# HTML → Flutter 模板转换规范

> 本文档说明如何编写「可高效转换为同伴 App Flutter 代码」的 HTML 原型,以及 AI 将其翻译成 Flutter 时必须遵守的映射规则。

---

## 一、写 HTML 原型时推荐使用的工具栈

| HTML 端                    | 对应 Flutter 端               | 说明                         |
|---------------------------|-------------------------------|------------------------------|
| **Tailwind CSS v3**        | VelocityX + AppSpacing/AppColors | 语义对齐最佳,间距/颜色命名一致 |
| **RemixIcon 4.8(Line/Fill)** | AppIcons(remixicon 包)   | 图标名称直接映射               |
| **CSS 变量 `--color-*`**   | AppColors 常量                 | 建议统一颜色变量命名            |
| **Google Fonts: Inter**    | 系统字体(项目未自定义字体)    | HTML 原型加载即可              |

---

## 二、颜色变量对照表

在 HTML `<style>` 块中声明如下 CSS 变量,AI 转 Flutter 时直接替换为 `AppColors.*`:

```css
:root {
  /* 品牌色 */
  --color-primary:        #6366F1;   /* AppColors.primary */
  --color-primary-light:  #EEF2FF;   /* AppColors.primaryLight */
  --color-secondary:      #8B5CF6;   /* AppColors.secondary */

  /* 状态色 */
  --color-error:          #EF4444;   /* AppColors.error */
  --color-warning:        #F59E0B;   /* AppColors.warning */
  --color-success:        #10B981;   /* AppColors.success */

  /* 文字色 */
  --color-text-primary:   #111827;   /* AppColors.textPrimary  / context.themeTextPrimary */
  --color-text-secondary: #6B7280;   /* AppColors.textSecondary / context.themeTextSecondary */
  --color-text-hint:      #9CA3AF;   /* AppColors.textHint */

  /* 背景 / 边界 */
  --color-bg-primary:     #FFFFFF;   /* AppColors.bgPrimary  / context.themeBackground */
  --color-bg-secondary:   #F9FAFB;   /* AppColors.bgSecondary / context.themeInputBg */
  --color-divider:        #E5E7EB;   /* AppColors.divider    / context.themeDivider */
  --color-surface:        #FFFFFF;   /* context.themeSurface */

  /* 渐变(主题色渐变,转为 AppGradientTheme.primaryGradient) */
  --gradient-primary: linear-gradient(to right, #6366F1, #A78BFA);
}
```

---

## 三、间距 / 尺寸对照表

使用 Tailwind 的间距类时,映射到 `AppSpacing` 常量:

| Tailwind 类        | px 值 | AppSpacing / AppRadius |
|--------------------|-------|------------------------|
| `p-1` / `gap-1`    | 4     | `AppSpacing.xs`        |
| `p-2` / `gap-2`    | 8     | `AppSpacing.sm`        |
| `p-3` / `gap-3`    | 12    | `AppSpacing.md`        |
| `p-4` / `gap-4`    | 16    | `AppSpacing.lg`(pageMargin)|
| `p-6` / `gap-6`    | 24    | `AppSpacing.xl`        |
| `p-8` / `gap-8`    | 32    | `AppSpacing.xxl`       |
| `p-12` / `gap-12`  | 48    | `AppSpacing.max`       |
| `rounded-sm`       | 4     | `AppRadius.xs`         |
| `rounded`          | 8     | `AppRadius.sm`         |
| `rounded-md`       | 12    | `AppRadius.md`         |
| `rounded-lg`       | 16    | `AppRadius.lg`         |
| `rounded-xl`       | 24    | `AppRadius.xl`         |
| `rounded-2xl`      | 32    | `AppRadius.xxl`        |
| `rounded-full`     | ∞     | `AppRadius.max`        |

---

## 四、字体排版对照表

| 角色          | HTML(Tailwind)                        | Flutter(AppTextStyles)        |
|--------------|----------------------------------------|--------------------------------|
| 大标题        | `text-3xl font-extrabold tracking-tight` | `AppTextStyles.h1Style`(32, w800)|
| 标题二        | `text-2xl font-bold tracking-tight`     | `AppTextStyles.h2Style`(24, w700)|
| 标题三        | `text-xl font-semibold`                 | `AppTextStyles.h3Style`(20, w600)|
| 正文大        | `text-lg font-medium`                   | `AppTextStyles.bodyLg`(18, w500)|
| 正文          | `text-base`                             | `AppTextStyles.bodyMd`(16, w400)|
| 正文小        | `text-sm`                               | `AppTextStyles.bodySm`(14, w400)|
| 辅助文字      | `text-xs`                               | `AppTextStyles.caption`(12, w400)|
| 导航标题      | `text-[17px] font-semibold`             | `AppTextStyles.navTitle`       |
| 按钮文字      | `text-base font-semibold`               | `AppTextStyles.button`         |

---

## 五、布局规则(HTML → VelocityX)

### 5.1 禁止直接翻译的标签

| HTML 写法                  | ❌ 错误 Flutter 翻译         | ✅ 正确 Flutter(VelocityX)         |
|--------------------------|------------------------------|--------------------------------------|
| `<div class="flex">`     | `Row(...)`                   | `[...].hStack()`                     |
| `<div class="flex-col">` | `Column(...)`                | `[...].vStack()`                     |
| `<div class="p-4">`      | `Padding(EdgeInsets.all(16))`| `widget.p16()`                       |
| `<div class="bg-white rounded-xl shadow">` | `Container(...)` | `widget.box.white.roundedXl.shadow.make()` |
| `<button onclick>`       | `ElevatedButton`             | `PrimaryActionButton` 或 `AppCapsuleButton` |

### 5.2 页面级布局

```dart
// HTML:
// <div class="px-4 py-6 flex flex-col gap-4">...</div>
// Flutter:
[
  widget1,
  widget2,
].vStack(spacing: AppSpacing.lg)
 .scrollVertical()
 .pSymmetric(h: AppSpacing.lg, v: AppSpacing.xl)
```

---

## 六、组件对照速查

### 6.1 按钮

```html
<!-- HTML 原型写法 → Flutter PrimaryActionButton -->
<button class="w-full h-13 rounded-full text-white font-semibold
               bg-gradient-to-r from-[#6366F1] to-[#A78BFA]
               shadow-lg shadow-indigo-500/25">
  立即登录
</button>
```
```dart
// Flutter 翻译
PrimaryActionButton(label: '立即登录', onPressed: () async { ... })
  .wFull(context)
```

```html
<!-- 社交胶囊按钮(出线/填充/次要) -->
<span class="px-3 py-1.5 rounded-full border border-indigo-500 text-indigo-500 text-sm font-medium">
  + 关注
</span>
```
```dart
AppCapsuleButton(variant: AppCapsuleVariant.primaryOutline, onTap: () {})
// variants: primaryOutline / primaryFilled / secondaryOutline / surfaceVariant
```

### 6.2 头像

```html
<!-- HTML 原型 -->
<div class="w-12 h-12 rounded-full bg-gradient-to-br from-indigo-500 to-violet-500
            flex items-center justify-center text-white font-bold text-lg">
  U
</div>
<!-- 或含图片 -->
<img class="w-12 h-12 rounded-full object-cover" src="..." />
```
```dart
// 规格:sm=32 / df=48 / lg=64 / xl=80 / xxl=120
CustomAvatar(url: user.avatar, name: user.nickname, size: AvatarSize.df)
```

### 6.3 列表单元格(AppCell)

```html
<!-- 带箭头的设置项 -->
<div class="flex items-center min-h-[52px] px-4">
  <span class="flex-1 text-base text-gray-900">通知设置</span>
  <span class="text-sm text-gray-500">已开启</span>
  <i class="ri-arrow-right-s-line text-gray-400 ml-1"></i>
</div>
```
```dart
AppCell(title: '通知设置', value: '已开启', onTap: () {})

// 分组容器
AppCellGroup(title: '账户', children: [
  AppCell(title: '昵称', value: '同伴用户', onTap: () {}),
  AppCell(title: '手机号', value: '138****8888', onTap: () {}),
])
```

### 6.4 Toast 通知

```html
<!-- HTML 原型 - 顶部 pill 样式 Toast -->
<div class="fixed top-safe left-4 right-4 flex items-center gap-2
            px-5 py-3 rounded-full backdrop-blur-md bg-white/85
            border border-gray-200/50 shadow-xl">
  <i class="ri-checkbox-circle-line text-green-500"></i>
  <span class="text-base font-semibold">保存成功</span>
</div>
```
```dart
AppToast.showSuccess('保存成功');
AppToast.showError('网络连接失败,请重试');
AppToast.showWarning('此操作不可恢复');
AppToast.showInfo('功能开发中');
```

### 6.5 空状态页(AppEmptyState)

```html
<!-- 空状态 -->
<div class="flex flex-col items-center gap-4 py-16">
  <div class="w-24 h-24 rounded-full bg-indigo-50 flex items-center justify-center">
    <i class="ri-inbox-line text-5xl text-indigo-400"></i>
  </div>
  <p class="text-base font-semibold text-gray-900">暂无数据</p>
  <p class="text-sm text-gray-500">这里空空如也</p>
</div>
```
```dart
// 预设类型
AppEmptyState(type: AppEmptyType.noData)
AppEmptyState(type: AppEmptyType.networkError, actionLabel: '重新加载',
    onAction: () => ref.refresh(myProvider))
// 自定义
AppEmptyState(type: AppEmptyType.custom,
    title: '还没有信号', subtitle: '快去发布第一条吧',
    actionLabel: '去发布', onAction: () => context.push(AppRoutes.xxx))
```

### 6.6 骨架屏

```html
<!-- 骨架屏:统一用 pulse 动画 + rounded bg-gray-200 -->
<div class="animate-pulse space-y-3">
  <div class="h-4 bg-gray-200 rounded-full w-3/4"></div>
  <div class="h-4 bg-gray-200 rounded-full w-1/2"></div>
</div>
```
```dart
// Flutter 翻译:用 AppSkeleton 三种写法
AppSkeleton.wrap(enabled: isLoading, child: RealWidget(...))
AppSkeleton.listTile()   // 列表骨架
AppSkeleton.card()       // 卡片骨架
```

### 6.7 徽章(AppBadge)

```html
<!-- 消息角标 -->
<div class="relative inline-flex">
  <i class="ri-notification-3-line text-2xl"></i>
  <!-- 纯红点 -->
  <span class="absolute -top-1 -right-1 w-2 h-2 rounded-full bg-red-500
               border-2 border-white"></span>
  <!-- 数字 -->
  <span class="absolute -top-1 -right-1 px-1.5 py-0.5 rounded-full bg-red-500
               border-2 border-white text-white text-[9px] font-bold">5</span>
</div>
```
```dart
AppBadge(child: Icon(AppIcons.notification))           // 纯红点
AppBadge(count: 5, child: Icon(AppIcons.message))      // 数字
AppBadge(count: 100, child: someWidget)                // 99+
```

### 6.8 对话框 / 底部表单

```html
<!-- 确认弹窗 -->
<div class="fixed inset-0 bg-black/40 flex items-center justify-center">
  <div class="bg-white rounded-3xl p-6 mx-6 w-full max-w-sm">
    <h3 class="text-xl font-semibold text-center">确认删除?</h3>
    <p class="text-sm text-gray-500 text-center mt-3">删除后将无法恢复</p>
    <div class="flex gap-3 mt-6">
      <button class="flex-1 h-11 rounded-full bg-gray-100 text-gray-500 font-semibold">取消</button>
      <button class="flex-1 h-11 rounded-full bg-red-500 text-white font-semibold">确认删除</button>
    </div>
  </div>
</div>
```
```dart
final ok = await AppDialog.show(
  context: context,
  title: '确认删除?',
  content: '删除后将无法恢复',
  confirmLabel: '确认删除',
  isDestructive: true,
);
```

---

## 七、图标使用规范(RemixIcon 4.8)

HTML 原型中使用 RemixIcon CDN:
```html
<link href="https://cdn.jsdelivr.net/npm/remixicon@4.8.0/fonts/remixicon.css" rel="stylesheet">
```

| 使用场景      | HTML 类名                  | Flutter                   |
|-------------|---------------------------|---------------------------|
| 默认图标      | `ri-xxx-line`              | `AppIcons.xxxLine` (line) |
| 强调/激活图标 | `ri-xxx-fill`              | `AppIcons.xxx` (fill)     |
| 禁止          | 任何系统图标 / `ri-xxx-2-line` | 禁止 `Icons.xxx`         |

---

## 八、动画规范

```html
<!-- HTML 动画(参考入场效果) -->
<div class="animate-fadeInUp">...</div>
```
```dart
// Flutter:flutter_animate
widget.animate().fadeIn(500.ms).slideY(begin: 0.2)
// 列表动画
[...items].animate(interval: 100.ms).fade().slideX()
// 禁止手动 AnimationController
```

---

## 九、AsyncValue 三态模板

HTML 原型中要同时画出三个状态:

```html
<!-- ① Loading(骨架屏) -->
<div class="animate-pulse space-y-3">…</div>

<!-- ② Data(真实列表) -->
<ul>…</ul>

<!-- ③ Error(空状态) -->
<div class="empty-state" data-type="networkError">…</div>
```
```dart
// Flutter 翻译
ref.watch(signalProvider).when(
  data:    (data)       => ListView(...),
  loading: ()           => AppSkeleton.listTile(),
  error:   (err, stack) => AppEmptyState(
    type: AppEmptyType.networkError,
    actionLabel: '重新加载',
    onAction: () => ref.refresh(signalProvider),
  ),
)
```

---

## 十、页面级 HTML 模板(完整示例)

把下面的模板贴给 AI,它就知道用哪套规范来写页面:

```html
<!DOCTYPE html>
<html lang="zh-CN" class="bg-[#F9FAFB]">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
  <title>页面名称 — 同伴App原型</title>
  <!-- RemixIcon 4.8 -->
  <link href="https://cdn.jsdelivr.net/npm/remixicon@4.8.0/fonts/remixicon.css" rel="stylesheet">
  <!-- Tailwind CSS v3 -->
  <script src="https://cdn.tailwindcss.com"></script>
  <style>
    /* ── 同伴 App 设计 Token ── */
    :root {
      --color-primary: #6366F1;
      --color-primary-light: #EEF2FF;
      --color-secondary: #8B5CF6;
      --color-error: #EF4444;
      --color-warning: #F59E0B;
      --color-success: #10B981;
      --color-text-primary: #111827;
      --color-text-secondary: #6B7280;
      --color-text-hint: #9CA3AF;
      --color-bg-primary: #FFFFFF;
      --color-bg-secondary: #F9FAFB;
      --color-divider: #E5E7EB;
      --color-surface: #FFFFFF;
      --gradient-primary: linear-gradient(to right, #6366F1, #A78BFA);
    }
    /* 模拟手机安全区 */
    body { max-width: 390px; margin: 0 auto; font-family: 'Inter', sans-serif; }
    /* 状态栏占位 */
    .status-bar { height: 44px; }
    /* 导航栏 */
    .nav-bar { height: 44px; }
    /* Tab Bar */
    .tab-bar { height: 83px; padding-bottom: 28px; }
  </style>
  <script>
    // Tailwind 颜色扩展
    tailwind.config = {
      theme: { extend: { colors: {
        primary: '#6366F1',
        secondary: '#8B5CF6',
        'primary-light': '#EEF2FF',
      }}}
    }
  </script>
</head>
<body class="bg-[var(--color-bg-secondary)] min-h-screen">

  <!-- ① 状态栏(安全区占位) -->
  <div class="status-bar bg-white"></div>

  <!-- ② 导航栏 -->
  <header class="nav-bar bg-white flex items-center px-4 border-b border-[var(--color-divider)]">
    <button class="p-2 -ml-2"><i class="ri-arrow-left-s-line text-2xl text-gray-700"></i></button>
    <h1 class="flex-1 text-center text-[17px] font-semibold text-gray-900">页面标题</h1>
    <div class="w-10"></div><!-- 占位 -->
  </header>

  <!-- ③ 页面内容区(可滚动) -->
  <main class="flex-1 overflow-y-auto px-4 py-6 flex flex-col gap-4">

    <!-- 卡片容器:对应 content.p16().box.white.roundedXl.shadow.make() -->
    <div class="bg-white rounded-2xl shadow-sm p-4 flex flex-col gap-4">

      <!-- 文字层级示例 -->
      <h2 class="text-xl font-semibold text-gray-900">卡片标题</h2>
      <p class="text-sm text-gray-500">辅助说明文字,不超过两行</p>

      <!-- 头像 + 用户信息 -->
      <div class="flex items-center gap-3">
        <div class="w-12 h-12 rounded-full bg-gradient-to-br from-indigo-500 to-violet-500
                    flex items-center justify-center text-white font-bold text-lg flex-shrink-0">U</div>
        <div class="flex flex-col">
          <span class="text-base font-medium text-gray-900">用户昵称</span>
          <span class="text-xs text-gray-400">@username</span>
        </div>
      </div>

    </div>

    <!-- 设置列表:对应 AppCellGroup -->
    <div class="bg-white rounded-2xl overflow-hidden shadow-sm">
      <!-- AppCell:对应 AppCell(title:'', value:'', onTap:()) -->
      <div class="flex items-center min-h-[52px] px-4 border-b border-gray-100">
        <i class="ri-user-3-line text-xl text-indigo-500 mr-3"></i>
        <span class="flex-1 text-base text-gray-900">账号设置</span>
        <i class="ri-arrow-right-s-line text-gray-400"></i>
      </div>
      <div class="flex items-center min-h-[52px] px-4">
        <i class="ri-notification-3-line text-xl text-indigo-500 mr-3"></i>
        <span class="flex-1 text-base text-gray-900">消息通知</span>
        <!-- 开关:对应 Switch -->
        <div class="w-11 h-6 bg-indigo-500 rounded-full relative">
          <div class="absolute right-0.5 top-0.5 w-5 h-5 bg-white rounded-full shadow"></div>
        </div>
      </div>
    </div>

    <!-- 主操作按钮:对应 PrimaryActionButton -->
    <button class="w-full h-[52px] rounded-full text-white font-semibold text-base
                   bg-gradient-to-r from-indigo-500 to-violet-400
                   shadow-lg shadow-indigo-500/25">
      立即确认
    </button>

    <!-- 危险操作按钮:isDestructive: true -->
    <button class="w-full h-[52px] rounded-full text-white font-semibold text-base bg-red-500
                   shadow-lg shadow-red-500/25">
      退出登录
    </button>

    <!-- 胶囊按钮组:对应 AppCapsuleButton -->
    <div class="flex gap-2 flex-wrap">
      <span class="px-3 py-1.5 rounded-full border border-indigo-500 text-indigo-500 text-sm font-medium">+ 关注</span>
      <span class="px-3 py-1.5 rounded-full bg-indigo-500 text-white text-sm font-medium">回关</span>
      <span class="px-3 py-1.5 rounded-full border border-gray-200 text-gray-500 text-sm font-medium">已关注</span>
      <span class="px-3 py-1.5 rounded-full bg-gray-100 text-gray-500 text-sm font-medium">互相关注</span>
    </div>

  </main>

  <!-- ④ Tab Bar(如需要) -->
  <nav class="tab-bar bg-white border-t border-gray-100 flex items-start justify-around px-4 pt-2 fixed bottom-0 left-0 right-0 max-w-[390px] mx-auto">
    <button class="flex flex-col items-center gap-1">
      <i class="ri-home-5-fill text-2xl text-indigo-500"></i>
      <span class="text-[10px] font-medium text-indigo-500">首页</span>
    </button>
    <button class="flex flex-col items-center gap-1">
      <i class="ri-compass-3-line text-2xl text-gray-400"></i>
      <span class="text-[10px] text-gray-400">发现</span>
    </button>
    <button class="flex flex-col items-center gap-1 relative">
      <i class="ri-chat-3-line text-2xl text-gray-400"></i>
      <!-- 角标:对应 AppBadge(count:5) -->
      <span class="absolute -top-1 right-0 px-1.5 py-0.5 rounded-full bg-red-500 border-2
                   border-white text-white text-[9px] font-bold">5</span>
      <span class="text-[10px] text-gray-400">消息</span>
    </button>
    <button class="flex flex-col items-center gap-1">
      <i class="ri-user-3-line text-2xl text-gray-400"></i>
      <span class="text-[10px] text-gray-400">我的</span>
    </button>
  </nav>

</body>
</html>
```

---

## 十一、给 AI 的转换指令模板(粘贴使用)

当你想让 AI 把 HTML 原型转成 Flutter 时,在对话开头加上:

```
请根据同伴App前端规范将以下HTML原型转为Flutter代码:
- 使用 VelocityX 链式语法替代所有原生布局(Column→.vStack(),Row→.hStack(),Padding→.p16()等)
- 颜色使用 AppColors.*,间距使用 AppSpacing.*,圆角使用 AppRadius.*,字体使用 AppTextStyles.*
- 图标使用 AppIcons.*(RemixIcon),禁止 Icons.*
- 按钮使用 PrimaryActionButton 或 AppCapsuleButton,禁止 ElevatedButton/TextButton
- 动画使用 flutter_animate,禁止手动 AnimationController
- 统一 import 'package:tongban_app/shared.dart',禁止单独 import theme/ 或 widgets/ 下的文件
- AsyncValue 必须处理 data / loading / error 三态
- 每个页面文件 ≤ 300 行,每个 Widget ≤ 150 行

HTML原型如下:
[粘贴HTML]
```