flash-im-ui-style
闪讯 IM 的 UI 风格与样式规范。在编写 Flutter 页面时激活,确保视觉一致性。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
闪讯 IM 的 UI 风格与样式规范。在编写 Flutter 页面时激活,确保视觉一致性。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
GitHub Release 发布操作规范。在需要发布版本、上传安装包到 GitHub Release 时激活,确保使用正确的命令和流程。
确认/删除等交互弹框统一使用 showTolyPopPicker 底部弹出样式。在需要弹出确认框、删除确认、操作选择时激活,确保交互风格一致。
后端服务启动与数据库操作规范。在需要启动后端服务、运行 API 测试、执行数据库迁移或遇到连接错误时激活,确保使用正确的命令和流程。
功能归档规范。在归档功能版本、更新功能网、创建存档快照时激活,确保节点编号正确、网络图完整。
使用 tolyui_mediax 实现媒体预览。适用于图片九宫格展示、全屏预览、手势缩放、视频播放、Hero 动画等场景。
Flutter Widget/Page 组件代码评审技能。在需要审查组件代码质量、发现设计问题时激活,确保输出结构化的问题清单和改进建议。
| name | flash-im-ui-style |
| description | 闪讯 IM 的 UI 风格与样式规范。在编写 Flutter 页面时激活,确保视觉一致性。 |
| metadata | {"model":"manual","last_modified":"Sat, 19 Apr 2026 00:00:00 GMT"} |
| 用途 | 色值 | 说明 |
|---|---|---|
| 主色 | #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(Tab 页面、搜索页):
AppBar(
title: const Text('标题'),
backgroundColor: const Color(0xFFEDEDED),
elevation: 0,
scrolledUnderElevation: 0,
)
白色 AppBar(详情页、设置页):
AppBar(
title: const Text('标题'),
backgroundColor: Colors.white,
foregroundColor: const Color(0xFF333333),
elevation: 0,
scrolledUnderElevation: 0,
)
Scaffold(backgroundColor: const Color(0xFFF5F5F5))
白色内容区域用 Container(color: Colors.white) 包裹,区域之间用 SizedBox(height: 10) 灰色间隔分组。
标准列表项(好友列表、申请列表):
EdgeInsets.symmetric(horizontal: 16, vertical: 12)Padding(left: 68) + Divider(height: 0.5, thickness: 0.5, color: Color(0xFFF0F0F0))设置项(群详情、聊天设置):
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),
],
),
),
)
设置项之间的分割线:
Container(
color: Colors.white,
padding: const EdgeInsets.only(left: 16),
child: const Divider(height: 0.5, thickness: 0.5),
)
操作按钮(好友申请/群通知的同意/拒绝):
拒绝(灰色文字):
GestureDetector(
onTap: onReject,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
child: Text('拒绝', style: TextStyle(fontSize: 13, color: Color(0xFF999999))),
),
)
同意/接受(蓝色填充):
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
[toly+] iOS 风格 CupertinoSwitch,主题蓝激活色:
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';
通用头像(AvatarWidget):
identicon:seed 格式(像素风头像)http://... 网络图片群头像(GroupAvatarWidget):
grid:url1,url2,... 格式// 群主角标
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)),
),
)
占位模式(点击跳转搜索页):
FlashSearchBar(hintText: '搜索', onTap: () => ...)
输入模式(可编辑):
FlashSearchBar(hintText: '搜索', editable: true, controller: ..., onChanged: ...)
[toly+] 自定义搜索框(带右侧 loading 指示器):
#EDEDED,输入框白色圆角 6px通讯录顶部固定入口(新的朋友、群通知、群聊):
// 图标容器
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
入群确认/申请对话框:
AlertDialog + RoundedRectangleBorder(borderRadius: 12)#F8F8F8,圆角 8#999999FilledButton + 圆角 6统一用 ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(...)))
常见文案:
已成功加入群聊申请已发送,等待群主审批已同意 / 已拒绝已开启入群验证 / 已关闭入群验证操作失败:$e红点角标(通讯录 Tab、群通知入口):
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)),
)
闪讯的公共组件分布在 flash_shared 和 flash_session 两个模块中。
位置: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(
avatar: 'identicon:user123', // 或 'http://...' 或 null
size: 44,
borderRadius: 6,
)
路由规则:
identicon:xxx → IdenticonAvatar(像素风)http(s)://... → Image.network(含错误降级)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: 格式:
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(
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(hintText: '搜索', onTap: () => Navigator.push(...))
// 编辑模式(可输入)
FlashSearchBar(editable: true, controller: ctrl, onChanged: (v) => ...)
高度 36px,背景色 #EDEDED,输入框白色圆角 6px。
位置: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(user: currentUser, size: 64, borderRadius: 8)
和 AvatarWidget 的区别:UserAvatar 直接接收 User 对象,根据 hasCustomAvatar 自动选择 identicon 或网络图片。AvatarWidget 接收字符串。
头像 URL 可能是相对路径,需要拼接 baseUrl。统一的解析方法:
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。