feat(mobile,account-service): 统一发送服务 + 员工目录接口 + 好友申请五态/验证消息(B-52 施工 2/3)

- message_send_service:所有消息走统一门禁(登录/连接/空接收方/好友校验),
  失败打印 SDK 原始 code 并映射中文提示,不再只吞异常
- chat_screen 接入统一发送服务,失败提示真实原因
- account-service 新增 GET /api/directory(登录用户可读启用员工,用于添加同事)
- add_friend_screen 增加可编辑验证消息(视觉规范 4.2)
- new_friends_screen 五态 + 窄屏(<360)按钮组换行不挤压姓名(视觉规范 5.3)

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
编码工程师
2026-08-16 02:16:00 +08:00
co-authored by multica-agent
parent d6ef6eb9a4
commit 12b0a1c943
5 changed files with 232 additions and 41 deletions
+73 -33
View File
@@ -98,43 +98,83 @@ class _NewFriendsScreenState extends State<NewFriendsScreen> {
Widget _tile(FriendApplicationInfo a) {
final name = a.fromNickname?.isNotEmpty == true ? a.fromNickname! : (a.fromUserID ?? '');
Widget trailing;
final isPending = a.handleResult == 0;
final showButtons = isPending;
// 窄屏(<360)时按钮组换到第二行右对齐,避免挤压姓名区
final narrow = MediaQuery.sizeOf(context).width < 360;
// 「接受/拒绝」按钮组:总宽固定 132,任何情况下不压缩、不换行
final buttonGroup = Row(
mainAxisSize: MainAxisSize.min,
children: [
FilledButton(
onPressed: () => _handle(a, true),
style: FilledButton.styleFrom(
backgroundColor: AppColors.primary,
minimumSize: const Size(60, 32),
padding: const EdgeInsets.symmetric(horizontal: AppGap.x3),
),
child: const Text('接受', style: TextStyle(fontSize: AppFont.sub)),
),
const SizedBox(width: AppGap.x2),
OutlinedButton(
onPressed: () => _handle(a, false),
style: OutlinedButton.styleFrom(
minimumSize: const Size(60, 32),
padding: const EdgeInsets.symmetric(horizontal: AppGap.x3),
),
child: const Text('拒绝', style: TextStyle(fontSize: AppFont.sub)),
),
],
);
Widget statusText;
if (a.handleResult == 1) {
trailing = const Text('已添加', style: TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary));
statusText = const Text('已添加', style: TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary));
} else if (a.handleResult == -1) {
trailing = const Text('已拒绝', style: TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary));
statusText = const Text('已拒绝', style: TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary));
} else {
trailing = Row(
mainAxisSize: MainAxisSize.min,
children: [
FilledButton(
onPressed: () => _handle(a, true),
style: FilledButton.styleFrom(
backgroundColor: AppColors.primary,
minimumSize: const Size(0, 32),
padding: const EdgeInsets.symmetric(horizontal: AppGap.x3),
),
child: const Text('接受', style: TextStyle(fontSize: AppFont.sub)),
),
const SizedBox(width: AppGap.x2),
OutlinedButton(
onPressed: () => _handle(a, false),
style: OutlinedButton.styleFrom(
minimumSize: const Size(0, 32),
padding: const EdgeInsets.symmetric(horizontal: AppGap.x3),
),
child: const Text('拒绝', style: TextStyle(fontSize: AppFont.sub)),
),
],
);
statusText = const Text('等待对方通过', style: TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary));
}
return ListTile(
leading: NameAvatar(name: name),
title: Text(name, style: const TextStyle(fontSize: AppFont.body)),
subtitle: a.reqMsg?.isNotEmpty == true
? Text(a.reqMsg!, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary))
: null,
trailing: trailing,
return Padding(
padding: const EdgeInsets.symmetric(horizontal: AppGap.x4, vertical: AppGap.x3),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
NameAvatar(name: name),
const SizedBox(width: AppGap.x3),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: AppFont.body, color: AppColors.textPrimary),
),
if (a.reqMsg?.isNotEmpty == true) ...[
const SizedBox(height: AppGap.x1),
Text(
a.reqMsg!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary),
),
],
if (narrow && showButtons) ...[
const SizedBox(height: AppGap.x2),
Align(alignment: Alignment.centerRight, child: buttonGroup),
],
],
),
),
if (!narrow)
SizedBox(width: 132, child: Align(alignment: Alignment.centerRight, child: showButtons ? buttonGroup : statusText)),
],
),
);
}
}