来源信息
- 仓库
- toly1994328/flash_im_by_ai
- 最近来源活动
- 2026年4月19日 23:38
- 检测到的 SKILL.md 语言
- 中文
- 星标
- 12
- 分支
- 2
安装方式
默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。
检查来源文件
决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。
正在显示 SKILL.md
SKILL.md
来源说明 · 只读预览- name
- flash-im-ui-style
- description
- 闪讯 IM 的 UI 风格与样式规范。在编写 Flutter 页面时激活,确保视觉一致性。
- metadata
- {"model":"manual","last_modified":"Sat, 19 Apr 2026 00:00:00 GMT"}
# 闪讯 UI 风格规范
## Contents
- [Color Palette](#color-palette)
- [AppBar Style](#appbar-style)
- [Page Background](#page-background)
- [List Items](#list-items)
- [Buttons](#buttons)
- [Switch Toggle](#switch-toggle)
- [Avatar](#avatar)
- [Search Bar](#search-bar)
- [Contact Header Items](#contact-header-items)
- [Dialog](#dialog)
- [Toast / SnackBar](#toast--snackbar)
- [Badge](#badge)
## Color Palette
| 用途 | 色值 | 说明 |
|------|------|------|
| 主色 | `#3B82F6` | 蓝色,按钮/链接/选中态 |
| 微信绿 | `#07C160` | Switch 激活色、在线状态 |
| 橙色 | `#FF9800` | 警告/需验证按钮 |
| 红色 | `#F44336` | 删除/退出/错误 |
| 文字主色 | `#333333` | 标题、正文 |
| 文字次色 | `#999999` | 副标题、描述、时间 |
| 文字提示色 | `#BBBBBB` | placeholder、hint |
| 分割线 | `#F0F0F0` | 列表分割线(0.5px) |
| 页面背景 | `#F5F5F5` | Scaffold 背景 |
| AppBar 背景 | `#EDEDED` | 灰色 AppBar(消息/通讯录 Tab) |
| AppBar 背景(白) | `#FFFFFF` | 白色 AppBar(详情页/设置页) |
| 卡片背景 | `#FFFFFF` | 列表项、设置项 |
| 输入框背景 | `#F8F8F8` | TextField 填充色 |
## AppBar Style
两种风格:
**灰色 AppBar**(Tab 页面、搜索页):
```dart
AppBar(
title: const Text('标题'),
backgroundColor: const Color(0xFFEDEDED),
elevation: 0,
scrolledUnderElevation: 0,
)
```
**白色 AppBar**(详情页、设置页):
```dart
AppBar(
title: const Text('标题'),
backgroundColor: Colors.white,
foregroundColor: const Color(0xFF333333),
elevation: 0,
scrolledUnderElevation: 0,
)
```
## Page Background
```dart
Scaffold(backgroundColor: const Color(0xFFF5F5F5))
```
白色内容区域用 `Container(color: Colors.white)` 包裹,区域之间用 `SizedBox(height: 10)` 灰色间隔分组。
## List Items
**标准列表项**(好友列表、申请列表):
- padding: `EdgeInsets.symmetric(horizontal: 16, vertical: 12)`
- 分割线: `Padding(left: 68)` + `Divider(height: 0.5, thickness: 0.5, color: Color(0xFFF0F0F0))`
- 左侧头像 44px,间距 12px
**设置项**(群详情、聊天设置):
```dart
Container(
color: Colors.white,
child: ListTile(
title: Text(title, style: TextStyle(fontSize: 16, color: Color(0xFF333333))),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(value, style: TextStyle(fontSize: 14, color: Color(0xFF999999))),
SizedBox(width: 4),
Icon(Icons.chevron_right, color: Color(0xFFCCCCCC), size: 20),
],
),
),
)
```
设置项之间的分割线:
```dart
Container(
color: Colors.white,
padding: const EdgeInsets.only(left: 16),
child: const Divider(height: 0.5, thickness: 0.5),
)
```
## Buttons
**操作按钮**(好友申请/群通知的同意/拒绝):
拒绝(灰色文字):
```dart
GestureDetector(
onTap: onReject,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
child: Text('拒绝', style: TextStyle(fontSize: 13, color: Color(0xFF999999))),
),
)
```
同意/接受(蓝色填充):
```dart
GestureDetector(
onTap: onAccept,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 6),
decoration: BoxDecoration(
color: Color(0xFF3B82F6),
borderRadius: BorderRadius.circular(4),
),
child: Text('同意', style: TextStyle(fontSize: 13, color: Colors.white)),
),
)
```
**搜索结果按钮**(四种状态):
| 状态 | 样式 |
|------|------|
| 已加入/已申请 | 灰色背景 `#F0F0F0` + 灰色文字 `#999999` |
| 加入 | 蓝色填充 `#3B82F6` + 白色文字 |
| 申请 | 橙色边框 `#FF9800` + 橙色文字 |
按钮 padding: `EdgeInsets.symmetric(horizontal: 10, vertical: 4)`,borderRadius: 4
## Switch Toggle
[toly+] iOS 风格 CupertinoSwitch,主题蓝激活色:
```dart
SizedBox(
width: 56,
height: 32,
child: FittedBox(
fit: BoxFit.contain,
child: CupertinoSwitch(
value: value,
onChanged: onChanged,
activeTrackColor: const Color(0xFF3B82F6),
),
),
)
```
需要 `import 'package:flutter/cupertino.dart';`
## Avatar
**通用头像**(AvatarWidget):
- 默认 size: 44, borderRadius: 6
- 支持 `identicon:seed` 格式(像素风头像)
- 支持 `http://...` 网络图片
- 空值显示灰色占位图标
**群头像**(GroupAvatarWidget):
- 解析 `grid:url1,url2,...` 格式
- 每行 5 个成员网格,LayoutBuilder 计算 tile 宽度
- 群主角标:橙色 "群主" 标签在头像右下角
```dart
// 群主角标
Positioned(
right: -2, bottom: -2,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 3, vertical: 1),
decoration: BoxDecoration(
color: Colors.orange,
borderRadius: BorderRadius.circular(4),
),
child: Text('群主', style: TextStyle(fontSize: 8, color: Colors.white, fontWeight: FontWeight.bold)),
),
)
```
## Search Bar
**占位模式**(点击跳转搜索页):
```dart
FlashSearchBar(hintText: '搜索', onTap: () => ...)
```
**输入模式**(可编辑):
```dart
FlashSearchBar(hintText: '搜索', editable: true, controller: ..., onChanged: ...)
```
[toly+] **自定义搜索框**(带右侧 loading 指示器):
- 搜索时保留已有结果不闪烁
- loading 只在搜索框右侧显示 16×16 的 CircularProgressIndicator
- 背景色 `#EDEDED`,输入框白色圆角 6px
## Contact Header Items
通讯录顶部固定入口(新的朋友、群通知、群聊):
```dart
// 图标容器
Container(
width: 40, height: 40,
decoration: BoxDecoration(
color: iconColor, // 橙色/蓝色/绿色
borderRadius: BorderRadius.circular(6),
),
child: Icon(icon, color: Colors.white, size: 22),
)
```
| 入口 | 图标 | 颜色 |
|------|------|------|
| 新的朋友 | `Icons.person_add` | `#F97D1C` 橙色 |
| 群通知 | `Icons.notifications` | `#3B82F6` 蓝色 |
| 群聊 | `Icons.group` | `#4CAF50` 绿色 |
红点角标:红色圆角背景 + 白色数字,padding `horizontal: 8, vertical: 2`
## Dialog
入群确认/申请对话框:
- `AlertDialog` + `RoundedRectangleBorder(borderRadius: 12)`
- 群信息预览卡片:灰色背景 `#F8F8F8`,圆角 8
- 需验证时显示留言 TextField(maxLines: 2, maxLength: 100)
- 取消按钮:灰色文字 `#999999`
- 确认按钮:`FilledButton` + 圆角 6
## Toast / SnackBar
统一用 `ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(...)))`
常见文案:
- 入群成功:`已成功加入群聊`
- 申请已发送:`申请已发送,等待群主审批`
- 审批结果:`已同意` / `已拒绝`
- 设置变更:`已开启入群验证` / `已关闭入群验证`
- 操作失败:`操作失败:$e`
## Badge
红点角标(通讯录 Tab、群通知入口):
```dart
Container(
padding: EdgeInsets.symmetric(horizontal: 4, vertical: 1),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(8),
),
child: Text(count > 99 ? '99+' : '$count',
style: TextStyle(color: Colors.white, fontSize: 10)),
)
```
## Shared Components
闪讯的公共组件分布在 `flash_shared` 和 `flash_session` 两个模块中。
### flash_shared(基础 UI 组件)
位置:`client/modules/flash_shared/lib/src/`
| 组件 | 文件 | 用途 |
|------|------|------|
| IdenticonAvatar | identicon_avatar.dart | 像素风头像,基于 seed 生成 5×5 对称方块图案 |
| AvatarWidget | avatar_widget.dart | 通用头像,自动路由 identicon / 网络图片 / 占位 |
| GroupAvatarWidget | group_avatar_widget.dart | 群组宫格头像,解析 grid: 格式,微信风格布局 |
| WxPopupMenuButton | popup_menu_button.dart | 微信风格右上角弹出菜单,深色气泡 + 尖角 + 动画 |
| FlashSearchBar | search_bar.dart | 搜索栏,占位模式 / 编辑模式两种 |
| FlashSearchInput | search_input.dart | 纯搜索输入框,白色圆角 + 搜索图标 |
#### AvatarWidget
```dart
AvatarWidget(
avatar: 'identicon:user123', // 或 'http://...' 或 null
size: 44,
borderRadius: 6,
)
```
路由规则:
- `identicon:xxx` → IdenticonAvatar(像素风)
- `http(s)://...` → Image.network(含错误降级)
- 空/null → 灰色占位图标
#### GroupAvatarWidget
```dart
GroupAvatarWidget(
members: [
GroupAvatarMember(id: '1', avatarUrl: 'identicon:1'),
GroupAvatarMember(id: '2', avatarUrl: 'identicon:2'),
],
size: 48,
borderRadius: 6,
)
```
布局规则:1 人铺满、2 人横排、3 人上 1 下 2、4 人 2×2、5-9 人 3 列网格。
解析 `grid:` 格式:
```dart
final avatar = 'grid:url1,url2,url3';
final avatarList = avatar.substring(5).split(',');
final members = avatarList.asMap().entries.map((e) =>
GroupAvatarMember(id: 'member_${e.key}', avatarUrl: e.value.trim().isNotEmpty ? e.value.trim() : null)
).toList();
```
#### WxPopupMenuButton
```dart
WxPopupMenuButton(
items: [
WxMenuItem(icon: Icons.group_add, text: '发起群聊', onTap: () => ...),
WxMenuItem(icon: Icons.person_add, text: '加好友/群', onTap: () => ...),
],
child: Icon(Icons.add_circle_outline, size: 22),
)
```
深色气泡菜单,缩放 0.8→1.0 + 淡入 200ms 动画,点击背景自动关闭。
#### FlashSearchBar
```dart
// 占位模式(点击跳转)
FlashSearchBar(hintText: '搜索', onTap: () => Navigator.push(...))
// 编辑模式(可输入)
FlashSearchBar(editable: true, controller: ctrl, onChanged: (v) => ...)
```
高度 36px,背景色 `#EDEDED`,输入框白色圆角 6px。
### flash_session(用户相关组件)
位置:`client/modules/flash_session/lib/src/view/`
| 组件 | 文件 | 用途 |
|------|------|------|
| UserAvatar | widget/user_card.dart | 用户头像,基于 User 对象自动选择渲染方式 |
| UserCard | widget/user_card.dart | 微信风格用户卡片(头像 + 昵称 + 闪讯号 + 签名) |
| SetPasswordPage | set_password_page.dart | 设置密码页面 |
| EditProfilePage | edit_profile_page.dart | 个人资料编辑页面 |
#### UserAvatar
```dart
UserAvatar(user: currentUser, size: 64, borderRadius: 8)
```
和 AvatarWidget 的区别:UserAvatar 直接接收 User 对象,根据 `hasCustomAvatar` 自动选择 identicon 或网络图片。AvatarWidget 接收字符串。
### URL 解析规范
头像 URL 可能是相对路径,需要拼接 baseUrl。统一的解析方法:
```dart
String? _resolveUrl(String? path) {
if (path == null || path.isEmpty) return null;
if (path.startsWith('http') || path.startsWith('identicon:') || path.startsWith('grid:')) return path;
if (baseUrl != null) return '$baseUrl$path';
return path;
}
```
注意:`identicon:` 和 `grid:` 前缀不能拼接 baseUrl,否则会变成无效 URL。
在 GitHub 查看