提示信息
# 同伴X 交互组件整理报告 v2.0
> 本报告由 v1.0(弹窗 + 空状态)扩展为 **完整 Widget 组件库说明文档**。
> 开发时必须使用规范组件,禁止自行拼装原生 Widget。
> 组件演示:登录后进入 `/showcase`(仅 Debug 模式)。
---
## 一、按钮组件
### 1.1 主操作按钮 `PrimaryActionButton`
**用途**:页面主要 CTA(提交、登录、确认等)
**规范**:
- 高度固定 **48dp**,圆角 **24dp**(全胶囊)
- 内置 Loading 防抖:点击后自动禁用,`onPressed` 异步完成后自动恢复
- 颜色:主色 `AppColors.primary`;危险操作 `AppColors.error`(`isDestructive: true`)
- 禁用:`onPressed: null`,背景自动变 `AppColors.divider`
```dart
// 标准用法(必须传 async 闭包)
PrimaryActionButton(
label: '立即登录',
onPressed: () async {
await ref.read(authController.notifier).login(phone, code);
},
)
// 危险操作
PrimaryActionButton(
label: '确认删除',
isDestructive: true,
onPressed: () async { ... },
)
// 全宽
PrimaryActionButton(...).wFull(context)
```
---
### 1.2 社交胶囊按钮 `AppCapsuleButton`
**用途**:用户关系操作(关注 / 已关注 / 回关 / 互相关注)
| variant | 样式 | 场景 |
|---|---|---|
| `primaryOutline` | 主色边框,透明背景 | + 关注 |
| `primaryFilled` | 主色填充 | 回关 |
| `secondaryOutline` | 灰色边框 | 已关注 |
| `surfaceVariant` | 灰背景 | 互相关注 |
```dart
AppCapsuleButton(
variant: AppCapsuleVariant.primaryOutline,
onTap: () => ref.read(followCtrl.notifier).follow(userId),
)
```
---
## 二、导航栏组件
### 2.1 `AppNavBar` — 通用导航栏
**规范**:高度 44pt(iOS HIG),`showBack: false` 用于 Tab 根页面
```dart
AppNavBar(
title: '我的主页',
showBack: true, // 默认 true
actions: [Icon(AppIcons.more, ...).p8().onTap(...)],
)
```
---
### 2.2 `AppTransparentNavBar` — 透明渐变导航栏
**规范**:**必须**传入 `scrollController`,否则透明度永不变化
```dart
AppTransparentNavBar(
title: '活动详情',
scrollController: scrollController,
titleThreshold: 200,
)
```
---
### 2.3 `AppSearchNavBar` — 搜索导航栏
```dart
AppSearchNavBar(
hintText: '搜索用户',
controller: textCtrl,
onSubmitted: (query) => ref.read(searchCtrl.notifier).search(query),
autofocus: true,
)
```
---
### 2.4 `AppChatNavBar` — 聊天导航栏
```dart
AppChatNavBar(
avatarUrl: user.avatar,
name: user.nickname,
isOnline: user.isOnline,
actions: [Icon(AppIcons.more).p8().onTap(...)],
)
```
---
### 2.5 `AppBottomNavBar` — 底部导航栏
**规范**:基于 Material 3 `NavigationBar`,高度 64dp,已配置选中指示器颜色。
**使用**:挂载在 `MainShellPage` 的 `bottomNavigationBar` 中,不要单独使用。
```dart
// 在 MainShellPage 中自动挂载,无需手动使用
AppBottomNavBar(
currentIndex: currentIndex.value,
onTap: (i) => currentIndex.value = i,
unreadMessageCount: unreadCount, // 来自消息 Provider
)
```
---
## 三、弹窗 & 底部表单
### 3.1 确认弹窗 `AppDialog.show`
**规范**:所有二次确认弹窗必须使用此方法,禁止自行构建 `AlertDialog`
```dart
final result = await AppDialog.show(
context: context,
title: '确认删除?',
content: '删除后将无法恢复,确定要删除这条信号吗?',
confirmLabel: '确认删除',
cancelLabel: '再想想',
isDestructive: true, // 确认按钮变红
);
if (result == true) { /* 用户点了确认 */ }
```
**返回值**:`true` = 确认,`false` = 取消,`null` = 点击背景关闭
---
### 3.2 底部表单 `AppDialog.showSheet`
**规范**:所有 `showModalBottomSheet` 必须用此封装
```dart
await AppDialog.showSheet<void>(
context: context,
title: '选择操作', // 可选,不传则无标题行
showDragHandle: true, // 默认显示拖拽指示条
child: [...].vStack(),
);
```
---
### 3.3 历史弹窗规范(二次确认弹窗清单)
| 功能 | 标题 | 按钮类型 |
|---|---|---|
| 退出登录 | 退出登录 | 危险 (红) |
| 注销账号 | 确认注销? | 危险 (红),带5秒倒计时 |
| 清空所有消息 | 清空所有消息? | 危险 (红) |
| 删除信号 | 确认删除 | 危险 (红) |
| 拉黑用户 | 确定拉黑此用户? | 危险 (红) |
| 取消关注 | 取消关注 | 普通 |
| 提交举报 | 确认提交 | 普通 |
| 清空访客 | 清空访客? | 危险 (红) |
| 清空缓存 | 确认清空 | 危险 (红) |
---
## 四、Toast 通知
### 4.1 `AppToast` — 覆盖层 Toast
**规范**:Overlay 式实现,从顶部带动画滑入,2.5s 后自动消失,**不阻断交互**。
左侧色块指示类型,支持 4 种:
| 方法 | 颜色 | 场景 |
|---|---|---|
| `AppToast.showInfo(msg)` | 主色蓝紫 | 普通提示 |
| `AppToast.showSuccess(msg)` | 绿色 | 操作成功 |
| `AppToast.showWarning(msg)` | 橙色 | 警告提醒 |
| `AppToast.showError(msg)` | 红色 | 错误失败 |
| `AppToast.show(msg)` | 主色 | 默认 info |
```dart
// 业务层错误处理
try {
await api.submit();
AppToast.showSuccess('提交成功');
} catch (e) {
AppToast.showError(e is AppApiError ? e.msg : '操作失败,请重试');
}
```
---
## 五、头像组件
### 5.1 `CustomAvatar`
**规范**:禁止使用 `CircleAvatar`,统一使用此组件处理 OSS 裁剪、加载失败占位
| AvatarSize | 像素 | 场景 |
|---|---|---|
| `sm` | 32×32 | 评论列表 |
| `df` | 48×48 | 普通列表(默认) |
| `lg` | 64×64 | 聊天消息 |
| `xl` | 80×80 | 个人主页 |
| `xxl` | 120×120 | 设置大头像 |
```dart
CustomAvatar(
url: user.avatarUrl,
name: user.nickname, // 加载失败显示首字母
size: AvatarSize.df,
)
```
---
## 六、骨架屏 & 加载
**规范**:禁止裸用 `Shimmer.fromColors`;骨架效果统一通过 `AppSkeleton` 输出,shimmer 颜色由配置统一管理。
### 6.1 ✅ 推荐:`AppSkeleton.wrap` / `Skeletonizer`(自动生成骨架)
**优点**:复用真实 Widget 结构,无需手写骨架布局,loading/正常状态一键切换。
**依赖包**:`skeletonizer: ^1.4.2`(已集成至 `shared.dart`)
```dart
// 方式一:enabled 开关(最推荐)
AppSkeleton.wrap(
enabled: isLoading,
child: UserCard(user: isLoading ? User.empty() : user),
)
// 方式二:AsyncValue loading 分支
ref.watch(usersProvider).when(
data: (users) => _buildList(users),
loading: () => AppSkeleton.fromWidget(
child: _buildList(List.generate(5, (_) => User.empty())),
),
error: (e, _) => AppEmptyState(type: AppEmptyType.serverError),
)
// 方式三:直接用 Skeletonizer(需走 AppSkeleton.wrap 保证颜色一致)
// ❌ 错误:Skeletonizer(child: ...) 直接用
// ✅ 正确:AppSkeleton.wrap(enabled: true, child: ...)
```
---
### 6.2 兜底:`AppSkeleton` 内置工厂(无法复用真实 Widget 时使用)
```dart
AppSkeleton.listTile() // 列表项骨架(头像+两行文字)
AppSkeleton.card(height: 160) // 卡片骨架
AppSkeleton.circle(size: 48) // 圆形骨架(头像占位)
AppSkeleton.page() // 页面级骨架(顶部大图+列表)
```
---
### 6.2 `AppLoading`
**规范**:禁止裸用 `CircularProgressIndicator`
```dart
const AppLoading() // 局部加载(居中转圈)
AppLoading(fullScreen: true) // 全屏蒙层加载
```
---
## 七、空状态
### 7.1 `AppEmptyState`
**规范**:禁止手拼空状态 UI,统一使用此组件
| type | 场景 |
|---|---|
| `noData` | 通用暂无数据 |
| `networkError` | 网络失败 |
| `searchEmpty` | 搜索无结果 |
| `noMessage` | 消息列表为空 |
| `noNotification` | 通知为空 |
| `permissionDenied` | 无权限 |
| `serverError` | 服务异常 |
| `custom` | 自定义文案 |
```dart
// 带操作按钮
AppEmptyState(
type: AppEmptyType.networkError,
actionLabel: '重新加载',
onAction: () => ref.refresh(myProvider),
)
// 自定义
AppEmptyState(
type: AppEmptyType.custom,
title: '还没有信号',
subtitle: '快去发布第一条信号吧!',
actionLabel: '立即发布',
onAction: () => context.push(AppRoutes.publishSignal),
)
```
---
## 八、列表 & 刷新
### 8.1 `AppListView<T>`
**规范**:禁止手拼 `EasyRefresh` + 分页逻辑,使用封装组件一站式处理
```dart
AppListView<SignalModel>(
items: signals,
itemBuilder: (ctx, index, item) => SignalCard(signal: item),
onRefresh: () async => ref.refresh(signalListProvider),
onLoadMore: () async => ref.read(signalListCtrl.notifier).loadMore(),
emptyType: AppEmptyType.noData,
)
```
---
## 九、TabBar
### 9.1 `AppTabBar`
**规范**:选中 12sp/粗体,未选中 12sp/普通,指示器宽度跟随文字
```dart
AppTabBar(
tabs: const ['热门', '动态', '关注'],
controller: tabController,
onTap: (index) { ... },
)
```
---
## 十、空状态文案规范(历史清单)
| 场景 | 主标语 | 描述 |
|---|---|---|
| 关注列表为空 | 还没有关注任何人 | 去发现一些有趣的人 |
| 粉丝列表为空 | 暂时还没有粉丝 | 多发布高质量信号 |
| 黑名单为空 | 黑名单为空 | 世界很和平 |
| 信号收件箱为空 | 暂无信号 | 附近暂无新信号 |
| 信号记录为空 | 暂无信号记录 | 去发布第一条信号 |
| 消息列表为空 | 暂无消息 | 去发射信号,寻找志同道合的伙伴 |
| 访客记录为空 | 访客空空如也 | 主动出击去别人主页 |
---
**更新说明**:
- v1.0:整理弹窗 + 空状态
- v2.0:扩展为完整 Widget 组件库,新增 AppToast、AppDialog、AppBottomNavBar、各尺寸头像、按钮规范等