Files
FruitServer/docs/plans/INBOX.md
T

292 KiB
Raw Blame History

[已处理] 2026-10-20 | K31-D 首页频道行滚动时固定吸顶(仅 App 端)

用户指令(2026-10-20,原文):

「上滑的时候 上方的tab要固定」

现状:lib/pages/home/home_browse_view.dart 中频道行(热门/免费/新剧/榜单)作为 _heroWithChannels() 的一部分放在 ListView 内,随列表一起滚动,上滑后会滚出屏幕。

口径(只改 Flutter,不动后端、不改接口)

  1. 频道行移出滚动内容,固定在列表可视区顶部:
    • _body() 的 Expanded 内改为 Stack:底层保持 RefreshIndicator + ListView(home-browse-page),顶层 Positioned(top: 0, left: 0, right: 0) 放频道行;
    • 轮播仍从可视区顶部开始,因此初始状态下频道行依旧悬浮在轮播图上。
  2. 吸顶样式切换:给 ListView 绑定 ScrollController,监听滚动偏移:
    • 偏移 < AppDimens.heroBannerHeight - 频道行高(44) 时:保持现状(透明背景、图上白字/未选中 white70、选中主题色);
    • 偏移 >= 该阈值后(轮播已滚出频道行下方):频道行改为不透明背景 AppColors.ink、底部 1px AppColors.border 分隔线,未选中文字 AppColors.muted、选中仍为 AppColors.blue,去图上白色;
    • 阈值不要硬编码 154,直接用 AppDimens.heroBannerHeight - _channelRowHeight。
  3. 无 banner 场景:频道行同样固定吸顶,始终使用非叠加配色(onSurface/muted + 主题色选中 + AppColors.ink 背景)。
  4. HomeBrowseHeader、RefreshIndicator 下拉刷新、Key(home-channels、home-channel-*、home-channel-underline-*、home-browse-page、home-hero-banner)全部保持不变。
  5. 频道行自身仍可横向滚动;纵向手势不被频道行拦截,保证列表可正常上滑。

测试/门禁

  • 新增用例:向上滚动 300px 后,home-channel-hot 的屏幕 y 坐标与滚动前一致(吸顶),且出现吸顶背景状态(可用新增 Key home-channels-pinned 标识);
  • 既有频道切换、下拉刷新、发现页轮播用例保持通过;
  • dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug。

流程:从 develop 拉 feature/flutter-k31-sticky-channels;作者 Zhou Weidong <502333560@qq.com>;--no-ff 合回 develop 并 push;PROGRESS 留痕;真机验收由用户执行。


[已处理] 2026-10-20 | K31-C 首页频道选中态改用主题色(仅 App 端)

用户指令(2026-10-20,原文):

「选中的颜色用主题色啊」

现状:lib/pages/home/home_browse_view.dart 的 _channelItem() 中,叠加在轮播图上的选中项文字与下划线都是白色(Colors.white / Colors.white),未选中为 Colors.white70。

口径(只改 Flutter,不动后端、不改接口)

  1. 选中项文字颜色改为主题色 AppColors.blue(AppColors.primary 同值),叠加在图上与非叠加场景都用主题色;保留文字阴影保证可读性。
  2. 选中项下划线颜色同样改为 AppColors.blue。
  3. 未选中项保持现状:图上 Colors.white70、非叠加 AppColors.muted;字重 w600、字号 15 不变。
  4. 横线尺寸 18×2、圆角 1、间距 4 不变;Key 不变。
  5. 同步更新 test/home/k28_a_navigation_test.dart 中选中态颜色断言为主题色。

测试/门禁:dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug。

流程:从 develop 拉 fix/flutter-k31-channel-accent;作者 Zhou Weidong <502333560@qq.com>;--no-ff 合回 develop 并 push;PROGRESS 留痕;真机验收由用户执行。


[已处理] 2026-10-20 | K31-B 首页频道悬浮轮播图 + 轮播全宽直角 + 频道下划线选中(仅 App 端)

用户指令(2026-10-20,原文):

「现在改首页 上方的热门 免费 新剧 榜单 是悬浮在轮播图上的 轮播图宽度占满 不要圆角 而且上面的也不是圆角展示 选中的下方带横线标识即可」

现状

  • lib/pages/home/home_browse_view.dart 的 _sections() 顺序为:_channels()(ChoiceChip 胶囊行)→ _hero()(带左右 16 padding)→ 继续观看 → 频道标题 → 网格。
  • _channels() 用 ChoiceChip + StadiumBorder + selectedColor: AppColors.blue,是圆角胶囊选中态。
  • _hero() 外层有 Padding(pageHorizontal),FeedBannerCarousel 的 _bannerCard 用 ClipRRect 圆角(hero 用 AppDimens.heroBannerRadius),点击层 Material 也带同款圆角。

口径(只改 Flutter,不动后端、不改接口、不影响发现页)

  1. 结构调整:把「频道行」叠加到轮播图上。新增 _heroWithChannels():
    • SizedBox(height: AppDimens.heroBannerHeight) 内用 Stack;
    • 底层 Positioned.fill(child: FeedBannerCarousel(...));
    • 顶层 Positioned(top: 8, left: 0, right: 0, child: _channels(l10n, overImage: true))。
    • 无 banner 时(banners.isEmpty)退化为普通频道行,放在网格上方,不叠加。
  2. 轮播图全宽直角(仅首页 hero):
    • FeedBannerCarousel 增加可选参数 flush(默认 false,发现页行为不变);
    • flush: true 时 _bannerCard 的外层 ClipRRect 圆角为 0、点击层 Material 圆角为 0;
    • 首页 _hero() 去掉左右 Padding,让轮播贴屏幕左右边缘;
    • 发现页的 0.94 视口与圆角保持现状。
  3. 频道行改为下划线选中,不再用 ChoiceChip:
    • 结构:横向可滚动 Row/ListView,项间距 20,左右内边距 16,整行高 44;
    • 文字:选中 fontSize 15 / w800,未选中 w600;悬浮在图上时颜色为白色(未选中 Colors.white70,带播放器同款文字阴影),非叠加场景用 AppColors.onSurface / AppColors.muted;
    • 选中项文字下方 4px 处一条 width 18 / height 2 / 圆角 1 的横线,悬浮时白色、非叠加时 AppColors.blue;未选中项用同尺寸透明横线占位,避免切换时跳动;
    • Key 保持不变:整行 home-channels,每项 home-channel-<hot|free|newReleases|ranking>;横线新增 home-channel-underline-<name>。
  4. 交互与状态不变:点击仍然调用 logic.selectChannel(...),选中频道影响下方网格与频道标题。
  5. _sections() 中 _channels() 从原位置移除,改为由 _heroWithChannels() 承载;无 banner 时才单独出现在网格上方。

测试/门禁

  • 同步改写 test/home/k28_a_navigation_test.dart 中基于 ChoiceChip 的断言:改为断言选中项横线可见/非选中项横线透明、颜色与字重符合上述口径,点击仍能切换频道;
  • 新增断言:home-hero-banner 宽度等于屏幕宽度(无左右 padding)、圆角为 0;频道行在 Stack 中位于轮播之上(可通过层级/位置断言);
  • 发现页轮播测试保持通过(flush 默认 false);
  • dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug。

流程:从 develop 拉 feature/flutter-k31-home-channels;作者 Zhou Weidong <502333560@qq.com>;--no-ff 合回 develop 并 push;PROGRESS 留痕;真机验收由用户执行。


[已处理] 2026-10-20 | K31-A 播放页长按手势:中间更多 / 两侧清屏+2 倍速(仅 App 端)

用户指令(2026-10-20,原文):

「接下来做 长按屏幕中间弹出更多弹窗 长按屏幕两侧清屏开启2倍速 但是要保留进度条 先不要上滑锁定倍速」

参考图:用户提供截图(20261020-123247.jpg)——长按两侧时画面清屏,仅保留底部进度条,进度条上方居中显示橙色「▸ 2.0 倍速快进中」;截图中的「上滑锁定倍速」本次明确不做。

现状

  • lib/pages/player/widgets/player_overlay.dart 的 player-video-tap 为全屏 GestureDetector,只有 onTap 切播放/暂停,没有长按手势。
  • _chromeVisible 控制清屏;清屏时只隐藏右侧 rail 与底部文案,进度条始终显示(符合「保留进度条」)。
  • 倍速由 _PlayerPageState._playbackSpeed + logic.setPlaybackSpeed(double) 驱动,顶部倍速入口与更多弹窗都走这两个入口。

口径(只改 Flutter,不动后端、不改接口)

  1. 手势分区(以屏幕宽度为基准,全屏浮层 player-video-tap 内判定):
    • 中间 1/3(width/3 ~ 2*width/3)长按:弹出「更多」弹窗,复用现有 _openMore(context, episode);不得改变播放/暂停状态。
    • 左侧 1/3 与右侧 1/3 长按:进入「加速态」。
  2. 加速态行为:
    • 立即切到 2.0 倍速(logic.setPlaybackSpeed(2.0) 并同步 _playbackSpeed);
    • 进入清屏(隐藏右侧 rail 与底部文案),进度条保留可见;
    • 进度条上方居中显示提示 Key('player-speed-boost-hint'):橙色(AppColors.blue)小三角图标 + 文案「2.0 倍速快进中」,字号 13、w700、带播放器文字阴影;
    • 松手或手势取消后:恢复长按前的倍速与清屏状态(本来清屏则保持清屏,本来非清屏则恢复控件),并隐藏提示。
  3. 不做「上滑锁定倍速」,也不做左右分区差异化倍速;首页沉浸流(homeMode)不加此手势。
  4. 需要新增 l10n 文案 key:speedBoosting,占位符 speed(如 2.0):
    • zh:「{speed} 倍速快进中」
    • en:「{speed}x speed」
    • id:「{speed}x kecepatan」 同步三份 arb 并执行 flutter gen-l10n 生成,不手改生成文件以外的方式绕过。
  5. 模型扩展:
    • PlayerOverlayData 增加 required bool boosting;
    • PlayerOverlayActions 增加 required VoidCallback onLongPressCenter、onBoostStart、onBoostEnd。

测试/门禁

  • 新增用例:中间长按弹出更多弹窗;左侧长按出现 player-speed-boost-hint、进度条仍可见、倍速变 2.0;松手后提示消失、倍速与清屏状态恢复;长按不改播放状态。
  • dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug。

流程:从 develop 拉 feature/flutter-k31-longpress;作者 Zhou Weidong <502333560@qq.com>;--no-ff 合回 develop 并 push;PROGRESS 留痕;真机验收由用户执行。


[已处理] 2026-10-20 | K30-F 简介文字与集数同规格 + 暂停按钮透明(仅 App 端)

用户指令(2026-10-20,原文):

「播放页第n 集右侧的简介文字大小与第n集大小保持一致颜色保持一致 为灰色 暂停按钮改为透明背景」

现状

  1. lib/pages/player/widgets/player_overlay_parts.dart 的 _EpisodeSynopsisState:_labelStyle 为 12 / AppColors.onPrimary(白)/ w700,_synopsisStyle 为 12 / AppColors.playerMuted(灰)/ 常规;分隔符 | 为 Colors.white54。白色加粗的「第 n 集」与灰色简介观感差异大。
  2. lib/pages/player/widgets/player_center_toggle.dart 的暂停按钮为 52 圆形、实底 AppColors.blue。

口径(只改 Flutter,不动后端、不改接口)

  1. 简推行统一为一套文字规格:fontSize 12、颜色 AppColors.playerMuted(灰)、fontWeight w500;「第 n 集」、分隔符 |、简介正文三者共用同一 TextStyle(分隔符可沿用同色、适度降低不透明度不算违约)。
  2. 「展开 / 收起」按钮保持主色可点击态,字号 12 不变。
  3. 展开后的作者备注字号 10、颜色灰色保持现状。
  4. 暂停按钮:去掉 AppColors.blue 实底圆形容器,改为透明背景;图标保持白色 AppColors.onPrimary、尺寸 26,按钮点击区域仍为 52×52(player-play-toggle Key 不变)。
  5. 不动机能逻辑,不动播放/清屏/选集等其它控件。

测试/门禁:如有断言涉及简介样式或暂停按钮装饰需同步;dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug。

流程:从 develop 拉 fix/flutter-k30-synopsis-pause;作者 Zhou Weidong <502333560@qq.com>;--no-ff 合回 develop 并 push;PROGRESS 留痕;真机验收由用户执行。


[已处理] 2026-10-20 | K30-E 播放页底部选集条箭头靠右(仅 App 端)

用户指令(2026-10-20,原文):

「修改 播放页底部选集 上箭头应该在右侧 而不是紧挨着」

现状:lib/pages/player/widgets/player_overlay.dart 的 _bottomActions() 中,选集胶囊内部是 Flexible(文本) + SizedBox(width: 3) + Icon(keyboard_arrow_up_rounded);Flexible 只收缩不占满,导致文字后的剩余空白留在箭头右边,箭头紧贴文字,违反 K29.9 之后用户新口径。

口径(只改 Flutter,不动后端、不改接口)

  1. 选集胶囊文本由 Flexible 改为 Expanded,让箭头被推到胶囊内最右侧。
  2. 文本与箭头间距由 3 提到 8;胶囊左右内边距保持 14,垂直 8 不变。
  3. 箭头尺寸 18、颜色 AppColors.onPrimary、阴影不变;胶囊背景、圆角 7、点击行为不变。
  4. 右侧清屏按钮与胶囊间距 8 不变;其余底部布局、进度条、标题行一律不动。

测试/门禁:如有断言涉及选集条布局需同步;dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug。

流程:从 develop 拉 fix/flutter-k30-episodes-arrow;作者 Zhou Weidong <502333560@qq.com>;--no-ff 合回 develop 并 push;PROGRESS 留痕;真机验收由用户执行。


[已处理] 2026-10-20 | K30-D 评论输入框尺寸与圆角修正(仅 App 端)

用户指令(2026-10-20,原文):

「评论面板的输入框高度还是不对啊 很小 圆角太大」

现状:lib/pages/player/widgets/drama_comments_sheet.dart 中 _composerHeight = 44、_composerRadius = 22,且 _composerDecoration 使用 isDense: true 且 contentPadding 只有水平 14,视觉上被压扁;22 的圆角在 44 高度下等于胶囊,观感「圆角太大」。

口径(只改 Flutter,不动后端、不改接口)

  1. 常量:_composerHeight 44 → 52;_composerRadius 22 → 12;_sendButtonSize 36 → 40。
  2. _composerDecoration:contentPadding 改为 EdgeInsets.symmetric(horizontal: 16, vertical: 14);保留 isDense: true 以防溢出但不得再压扁;背景 AppColors.commentInput、无描边不变。
  3. 发送按钮 40×40,与输入框垂直居中;图标 18 不变。
  4. TextField 保持 maxLines: 1、textAlignVertical: center;输入文字与 hint 字号 14 不变。
  5. 「回复 XX」提示条与输入框间距 6 不变;发送成功收起键盘行为不变。
  6. 不新增后端字段、不改接口、不动评论列表样式。

测试/门禁:更新既有用例断言为输入框高度 52、发送按钮 40;dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug。

流程:从 develop 拉 fix/flutter-k30-comment-input;作者 Zhou Weidong <502333560@qq.com>;--no-ff 合回 develop 并 push;PROGRESS 留痕;真机验收由用户执行。


[已处理] 2026-10-20 | K30-C 评论面板视觉重做(仅 App 端)

用户指令(2026-10-20,原文):

「先不写后端 只改app端 现在的评论ui 还是不好看 而且评论的输入框变小了 不好看」

口径(只改 Flutter,不动后端)

  1. 输入框恢复并加大:高度 44、圆角 22、左右 14、背景 #F5EFE8、输入文字 14;发送按钮 36 圆形(主色底 + 白色图标),与输入框垂直居中;底部留出安全区。
  2. 评论列表精致化:
    • 头像 32、昵称 13 加粗、时间 11 灰色、正文 14(行高 1.4)、点赞数字 11;
    • 列表项上下 padding 12、头像与内容间距 10、项之间浅色分割线(左侧缩进与正文对齐);
    • 「展开 n 条回复」「回复」字号 12,颜色主色;整体不拥挤也不松散。
  3. 弹窗顶部:拖拽条 + 「n 条评论」标题 15 加粗 + 关闭按钮,标题区与列表间距 8。
  4. 发送成功后收起键盘(保留 K30-B 行为);失败保留输入与焦点。
  5. 不新增后端字段、不改接口。

测试/门禁:输入框高度/发送按钮尺寸断言;发送成功收起键盘;dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug。

流程:从 develop 拉 fix/flutter-k30-comments-polish;作者 Zhou Weidong <502333560@qq.com>;--no-ff 合回 develop 并 push;PROGRESS 留痕;真机验收由用户执行。


[已完成·待用户验收] 2026-10-20 | K30-A 后端补互动数量字段(详情 VO)

用户指令(2026-10-20,原文):

「现在改右侧的收藏 评论喜欢 都是显示数量 然后改评论面板 ui 不好看 间距太大 点击发送后要收回键盘」

背景:客户端播放页右侧 rail 需要显示收藏数、评论数、点赞数;当前 /app 详情 VO 没有 favoriteCount / commentCount,客户端只能显示文字标签。

口径

  1. AppDramaDetailVO 增加 likeCount、favoriteCount、commentCount(Long),JSON 字段同名。
  2. 数据来源:drama.like_count、drama.favorite_count;评论数按 content_comment 中该剧未删除评论计数。
  3. 同步更新 docs/api-phase1.md 详情接口响应示例与字段说明(契约先行)。
  4. 更新/新增契约测试;mvn test 全过;mvn -pl fruitshorts-app-api -am package -DskipTests 成功。
  5. 不改已有字段语义,不动管理端。

流程:从 develop 拉 feature/backend-drama-counts;作者 Zhou Weidong <502333560@qq.com>;--no-ff 合回 develop 并 push;PROGRESS 留痕。


[已处理] 2026-10-20 | K30-B 播放页右侧数量 + 评论面板收紧

用户指令(2026-10-20,原文):

「现在改右侧的收藏 评论喜欢 都是显示数量 然后改评论面板 ui 不好看 间距太大 点击发送后要收回键盘」

口径(依赖 K30-A 的接口字段)

  1. 播放页右侧 rail:收藏、评论、喜欢三项都显示数量(格式:<1 万显示原数,≥1 万显示 x.x万);分享不显示数量。数据来自详情 VO 的 favoriteCount / commentCount / likeCount,点击后数量随接口结果更新。
  2. 评论面板 UI 收紧:
    • 列表项上下 padding 由 12 减到 8,头像 28,昵称 12、时间 10、正文 13、行距 2;
    • 底部输入框高度 40,左右 12,发送按钮 32;
    • 面板顶部标题与列表间距缩小,整体更紧凑。
  3. 点击发送成功后调用 FocusScope.of(context).unfocus() 收起键盘;发送失败保留输入与焦点。
  4. 其余播放与评论逻辑不变。

测试/门禁:rail 三项数量显示用例;发送成功后焦点丢失用例;dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug。

流程:从 develop 拉 feature/flutter-k30-counts-comments;作者 Zhou Weidong <502333560@qq.com>;--no-ff 合回 develop 并 push;PROGRESS 留痕;真机验收由用户执行。


[已处理] 2026-10-20 | K29.9 播放页底部细节调整

用户指令(2026-10-20,原文):

「现在改播放页底部 标题右边的箭头紧挨着不在最右侧 集数和简介上下对齐大小一致 紧挨着展开而不是下箭头 进度条宽度一致 下面的选集字体大小加大 圆角减小 清屏按钮整体大一些 注意益处隐藏」

口径

  1. 标题行:剧名后面的 > 紧贴标题文字,不再用 Expanded/Spacer 推到最右侧;整行仍可点击进详情。
  2. 集数与简介:第 n 集 和简介同一行、同一字号(12)、基线对齐;简介单行省略。
  3. 「展开/收起」改为紧贴简介末尾的文字按钮(不是右侧下箭头图标);展开后显示全文与作者声明,再次点击收起。
  4. 进度条与上方文字左右对齐(同一水平内边距,宽度一致),不做额外缩进。
  5. 底部「选集 · 全 n 集 · 免费观看」字号加大(建议 13–14、加粗),胶囊圆角减小(建议 6–8),仍保持深色半透明底 + 白字。
  6. 右下角「清屏」按钮整体加大(建议 40×40、图标 22),与选集条垂直居中。
  7. 所有文本 maxLines: 1 + TextOverflow.ellipsis,行内用 Flexible,确保长剧名/长简介不溢出。
  8. 其余口径不变(无整块背景、只有选集条有底、清屏范围、倍速、进度时间)。

测试/门禁:长标题/长简介场景无溢出;展开/收起可点;清屏按钮尺寸断言;dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug。

流程:从 develop 拉 fix/flutter-k29-bottom-detail;作者 Zhou Weidong <502333560@qq.com>;--no-ff 合回 develop 并 push;PROGRESS 留痕;真机验收由用户执行。


[已处理] 2026-10-20 | K29.8 「更多」弹窗增加小窗播放

用户指令(2026-10-20,原文):

「再加上小窗播放」

口径

  1. 在播放页「更多」弹窗中增加「小窗播放」项(Key player-more-pip),位于「清屏播放」「离线缓存」同组。
  2. 仅当 actions.onPip != null(平台支持)时显示;不支持时隐藏,不置灰造假。
  3. 点击进入 PiP(复用现有 onPip),关闭弹窗;底栏右侧仍保持「清屏」,不重复放 PiP。
  4. 其余弹窗项与播放逻辑不变。

测试/门禁:支持 PiP 时弹窗出现 player-more-pip 且可点击触发;不支持时不出现;dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug。

流程:从 develop 拉 fix/flutter-k29-more-pip;作者 Zhou Weidong <502333560@qq.com>;--no-ff 合回 develop 并 push;PROGRESS 留痕;真机验收由用户执行。


[已处理] 2026-10-20 | K29.7 「更多」弹窗倍速/清晰度改为单行

用户指令(2026-10-20,原文):

「倍速 清晰度这些一行就可以放下 不用竖向排列」

口径

  1. 倍速、清晰度每组只占一行:左侧图标 + 名称(固定宽度),右侧分段选项横向排列,不再把选项换行/竖向堆叠。
  2. 选项可横向压缩或横向滚动,保证 0.75/1/1.25/1.5/2/3 与清晰度档位在一行内可见或可横滑。
  3. 与 K29.6 的小尺寸分段样式一致(字号 12、高 28、圆角 8、段间距 6)。
  4. 其余弹窗内容与播放逻辑不变。

测试/门禁:倍速/清晰度各只有一行;选项可点;dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug。

流程:从 develop 拉 fix/flutter-k29-sheet-single-row;作者 Zhou Weidong <502333560@qq.com>;--no-ff 合回 develop 并 push;PROGRESS 留痕;真机验收由用户执行。


[待处理] 2026-10-20 | K29.6 播放页「更多」弹窗精简

用户指令(2026-10-20,原文):

「现在改右上角的更多弹出框 不感兴趣 不要 弹幕不要文字大小不要 投屏不要 定时关闭不要 倍速和清晰度小一点 整体UI精致一些」

口径

  1. 删除:不感兴趣、弹幕开关、弹幕设置、文字大小、投屏、定时关闭。
  2. 保留:倍速、清晰度、清屏播放、离线缓存(其余未提及项也不新增)。
  3. 倍速与清晰度改为更小、更紧凑的分段控件:字号 12、高度 28、圆角 8、左右 padding 10、段间距 6;选中为主色实心白字,未选中为浅底深字。
  4. 整体精致化:弹窗顶部拖拽条、标题字号 15、行高 44、分组间距 12、底部安全区留白;不使用粗描边。
  5. 不可用项(如清晰度仅 AUTO)保持禁用态说明,不伪造。
  6. 不改弹窗外的播放逻辑。

测试/门禁:删除项的 Key 断言移除;倍速/清晰度/清屏/离线缓存仍可用;dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug。

流程:从 develop 拉 fix/flutter-k29-more-sheet;作者 Zhou Weidong <502333560@qq.com>;--no-ff 合回 develop 并 push;PROGRESS 留痕;真机验收由用户执行。


[待处理] 2026-10-20 | K29.5 清屏范围与退出位置调整

用户指令(2026-10-20,原文):

「清屏只清楚右侧的收藏 评论喜欢分享 和底部上面的文案 清屏后 恢复还在右下角」 「进度条也不清」

口径

  1. 清屏只隐藏两部分:
    • 右侧互动 rail(收藏/评论/喜欢/分享);
    • 底部文案区(剧名行、第 n 集 | 简介)。 进度条不隐藏,清屏后仍显示。
  2. 清屏后仍保留:顶栏(返回/第 n 集/倍速/更多)、底部「选集 · 全 n 集 · 免费观看」条、进度条、右下角按钮。
  3. 右下角按钮在普通态为「清屏」,清屏态变为「退出清屏」;点击恢复右侧 rail 与底部文案,不改变播放状态。
  4. 移除原先位于右上角的退出清屏按钮。
  5. 清屏态点画面仍为播放/暂停切换;其余口径不变(暂停才显示中心按钮、跨集清屏保持、倍速、拖动时间)。

测试/门禁:清屏后 rail 与底部文案消失、顶栏/选集条/进度条/右下按钮仍在;点右下按钮恢复且播放态不变;dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug。

流程:从 develop 拉 fix/flutter-k29-clear-scope;作者 Zhou Weidong <502333560@qq.com>;--no-ff 合回 develop 并 push;PROGRESS 留痕;真机验收由用户执行。


[待处理] 2026-10-20 | K29.4 播放页去背景:仅选集条保留底色

用户指令(2026-10-20,原文):

「底部的不要黑色背景 就选集那里要背景 整个播放页也不要背景」

口径(覆盖 K29.3 的深色面板)

  1. 播放页不绘制整页底色:Scaffold/根容器透明,视频铺满;仅视频未就绪时允许黑色兜底。
  2. 底部信息区不再有整块背景:剧名、第 n 集 | 简介、进度条直接叠在视频上,白色文字 + 阴影;去掉面板底色、分割线、圆角卡片。
  3. 只有「选集」条保留背景:选集 · 全 n 集 · 免费观看 用深色半透明胶囊(#B3000000)+ 白字;底栏右侧「清屏」按钮保留自己的圆形底。
  4. 顶栏去掉整条渐变,改为文字/图标阴影保证可读;右侧互动 rail 的圆形按钮底色保留。
  5. 移除覆盖整页的四段式黑色渐变。
  6. 其余口径不变(暂停才显示中心按钮、清屏跨集保持、倍速、拖动时间、底栏右侧清屏)。

测试/门禁:更新页面/底栏背景相关断言;dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug。

流程:从 develop 拉 fix/flutter-k29-transparent-player;作者 Zhou Weidong <502333560@qq.com>;--no-ff 合回 develop 并 push;PROGRESS 留痕;真机验收由用户执行。


[已处理] 2026-10-20 | K29.3 播放页恢复深色外观(与参考图一致)

用户指令(2026-10-20,原文):

「现在播放页底部怎么是白色的 和我发你的图片不一致 而且 整个播放页仍然有黑色背景」

现状(真机截图核对):视频铺满全屏,但底部信息面板是白色,与参考图的深色播放器不一致。

口径(以参考图为准,播放页为深色)

  1. 页面底色:#0D0D10;仅播放器页使用深色,不影响其他浅色页面。
  2. 底部信息面板:深色 #0D0D10(或 0xED000000 叠在视频上),白色主文字、#BBBBBB 次文字、深色分割线;进度条白色/橙色;底栏「选集 · 全 n 集 · 免费观看」使用 #1C1C1E 胶囊 + 白字。
  3. 顶栏保留黑色渐变;右侧 rail 保持深色半透明圆底 + 白字。
  4. 移除覆盖整页的四段式黑色渐变(0x66000000 → 0xE6000000),避免把视频整体压黑;只在顶栏与底部面板处做必要渐变。
  5. 底栏右侧仍为「清屏」按钮(K29.2)。
  6. 其余口径不变(暂停才显示中心按钮、清屏跨集保持、倍速、拖动时间)。

测试/门禁:更新底栏颜色相关断言(如有);dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug。

流程:从 develop 拉 fix/flutter-k29-dark-player;作者 Zhou Weidong <502333560@qq.com>;--no-ff 合回 develop 并 push;PROGRESS 留痕;真机验收由用户执行。


[已处理] 2026-10-20 | K29.2 播放页底栏右侧改为「清屏」

用户指令(2026-10-20,原文):

「底部的右边是清屏」

口径

  1. 播放页底栏最右侧按钮由 PiP/小窗改为「清屏」:
    • Key player-bottom-clear-screen,图标 Icons.fullscreen_rounded,tooltip 用 l10n.clearScreen;
    • 点击执行 actions.onClear(进入清屏态,保留右上退出清屏按钮)。
    • 删除 player-pip 底栏按钮;PiP 若仍可用,保留在「更多」面板内,不重复放底栏。
  2. 复核整页无多余黑底:底栏为白色卡片,页面底色 AppColors.ink;只有视频画面与顶栏渐变可为深色。
  3. 测试:新增底栏清屏按钮存在且点击进入清屏态;删除 player-pip 断言;既有播放测试全过。

门禁:dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug。

流程:从 develop 拉 fix/flutter-k29-bottom-clear;作者 Zhou Weidong <502333560@qq.com>;--no-ff 合回 develop 并 push;PROGRESS 留痕;真机验收由用户执行。


[已完成·待用户验收] 2026-10-20 | K29.1 播放页视觉收紧:底部压缩、去掉全屏观看、右栏下移

用户指令(2026-10-20,原文):

「视频播放页底部的区域太大了 而且不要显示全屏观看 还有右侧的收藏评论喜欢 分享 位置太靠上 整体ui 不好看」

口径

  1. 删除视频下方「全屏观看」胶囊及相关状态/Key/测试;清屏仍由更多面板的「清屏播放」和右上退出按钮负责。
  2. 去掉整页黑色底:
    • player_view.dart 的 ColoredBox(color: AppColors.onSurface)(浅色主题下是深色)改为 AppColors.ink;
    • 底部信息面板 Color(0xED000000) 改为白色 AppColors.card + 深色文字/浅色分割线;顶栏保留深色渐变以保证视频上的可读性;
    • 右侧 rail 保持深色半透明圆底(视频上可读),不做黑色大底。
  3. 底部信息区整体压缩约 30–35%:
    • 只保留:剧名行(含 >)、第 n 集 | 简介(默认单行,点击展开)、细进度条、底栏「选集 · 全 n 集 · 免费观看」+ 小窗按钮。
    • 作者声明默认不显示,展开简介时再显示;收紧行高与上下 padding(建议整体高度 ≤ 200px,按 390×844 基准)。
    • 不遮挡视频主体,面板上沿不超过屏幕高度的 78%。
  4. 右侧互动 rail 下移:与底部面板上沿对齐(约屏高 55–60% 起),按钮间距 18、图标 24、数字 11px;不得与底部面板重叠。
  5. 其余播放口径不变(暂停才显示中心按钮、清屏跨集保持、倍速、拖动时间)。

测试/门禁:删除全屏观看相关断言;新增底部面板高度/rail 位置约束断言(宽松阈值);dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug。

流程:从 develop 拉 fix/flutter-k29-player-polish;作者 Zhou Weidong <502333560@qq.com>;--no-ff 合回 develop 并 push;PROGRESS 留痕;真机验收由用户执行。


[已完成·待用户验收] 2026-10-20 | K29 播放页按新参考重构 + 语言入口移到「我的」

用户指令:「播放页面按照这个来修改 剧场的语言不展示 在我的页面切换」(附播放页/更多面板/评论面板三张参考图)。

口径

A. 播放页(严格按参考图)

  1. 顶栏:左侧返回 + 第 n 集;右侧「倍速」入口(图标 + 文字,显示当前倍速)+ ⋮ 更多。移除顶栏大标题/副标题/题材胶囊/分页点与清屏按钮。
  2. 右侧互动 rail:收藏(含数)、评论(含数)、点赞(含数)、分享;无数据的数量隐藏,不伪造。
  3. 视频下方居中「全屏观看」胶囊(进入沉浸/清屏态)。
  4. 底部黑色信息面板:剧名 + >、第 n 集 | 简介(可展开/收起)、作者声明(有则显示)、细进度条、底栏「选集 · 全 n 集 · 免费观看 ⌃」+ 右侧小窗按钮。
  5. 更多面板(白色):倍速分段 0.75/1/1.25/1.5/2/3;清晰度分段(接口只有 AUTO 时仅显示 AUTO,其余置灰);不感兴趣;清屏播放;弹幕开关(无后端,置灰并标注暂未开放);弹幕设置;离线缓存;字体大小;投屏;定时关闭。不可用项禁用,不做假功能。
  6. 评论面板(白色):n 条评论 标题、评论列表(头像/昵称/内容/时间/回复/点赞)、底部输入框。
  7. 既有口径不得回退:暂停才显示中心播放按钮、清屏/退出清屏、清屏跨集保持、倍速即时生效、拖动进度显示时间。

B. 剧场移除语言

  • 删除 theater-language-chips 及 TheaterLanguage 过滤逻辑/测试;分类 chips、搜索、下拉刷新保留。

C. 我的页新增语言切换

  • 菜单新增「语言」项(Key mine-language),弹窗三选一:简体中文 / English / Bahasa Indonesia(Key mine-language-zh|en|id)。
  • 选中后立即切换并持久化;首次启动仍跟随系统(resolveAppLocale)。
  • 需要 LocaleController(ChangeNotifier)+ Provider + MaterialApp 的 locale 覆盖;不得破坏现有 l10n。

测试/门禁:播放页顶栏/底栏/更多/评论结构 Key 断言;剧场语言相关用例删除或改为「不存在」;语言切换用例(选中后文案变化 + 持久化);dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug。

流程:从 develop 拉 feature/flutter-k29-player-language;作者 Zhou Weidong <502333560@qq.com>;--no-ff 合回 develop 并 push;禁 git add -A;PROGRESS 留痕;真机验收由用户执行。


[已完成·待用户验收] 2026-10-20 | K28-R 按简图重设计剩余 UI(A2 → B → C 连续执行)

用户指令:「UI 不是完全按照这个设计的 重新设计ui」(再次附 5 屏 + 4 面板简图)。

背景:K28-A1(4 Tab + 首页骨架)与 K28-S(浅色暖橙换肤)已合并;剧场、创作、我的、播放页仍是旧结构,首页 Hero 也需要按简图重做。

要求:严格按简图还原,样式用「二·五 视觉风格 v2」,底部保持 4 槽(不做中央 +)。顺序执行、每步单独分支与合并:

A2 剧场(feature/flutter-k28-a2)

  • 顶栏:搜索框 + 头像(与首页一致)。
  • 分类 chips:全部 + 服务端分类,橙色实心选中。
  • 语言 chips:全部语言/中文/印尼语/马来语/英语(纯 UI 选中态,不做过滤)。
  • 两列剧卡:大封面(原图比例)、左上粉色胶囊、右上锁数量、右下「全 n 集/大结局」米色胶囊、标题粗体、副行「更新至 n 集 · 日期」灰字、右下橙色圆形 +;保留搜索与下拉刷新。

B 创作 + 我的(feature/flutter-k28-b)

  • 创作:「创作」大标题 + 「让故事成为漫剧」副标题;两张白卡:AI 制作(从故事或想法开始)、上传发布(发布已完成的漫剧),右侧箭头;点击进入诚实占位页。
  • 我的:头像 + 昵称 + 签名;三宫格快捷入口(收藏/追剧、观看历史、本地下载);金币余额卡 + 充值;会员卡;菜单(我的作品、创作者中心、消息通知、设置、帮助与反馈)。

C 播放 + 四面板(feature/flutter-k28-c)

  • 播放页:大标题 + 橙点、副标题、右上「…」与圆形按钮;Hero 大图 + 题材胶囊 + 分页点;右侧互动 rail(点赞数/评论数/收藏/分享);底部剧名、第 n 集、标签 + 关注、简介、进度条 + 时间、选集/更多。
  • 面板:选集(6 列、锁定态)、评论(列表 + 输入)、更多操作(倍速/清晰度/下载/文字大小/投诉)、内容解锁(金币/广告/会员 + 「具体方式以作品而定」)。
  • 既有口径禁止回退:清屏/退出清屏、暂停才显示中心播放按钮、清屏跨集保持、倍速弹窗、拖动显示时间。

测试/门禁(每批):dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug;更新受影响的旧断言;新增结构 Key 断言。 流程:作者 Zhou Weidong <502333560@qq.com>;每批 --no-ff 合回 develop 并 push;PROGRESS 留痕;真机验收由用户执行。


[待处理] 2026-10-20 | K28 客户端 UI 重构(按 5 屏 + 4 面板简图)

用户指令:「先读懂这个ui 简图 然后根据这个简图先出ui 在划分任务」

计划文件:docs/plans/2026-10-20-K28-ui-wireframe.md(先读,按其中分屏口径执行)。

范围:只做 UI 与信息架构,不改后端契约、不新增依赖、不做假接口;功能缺口在计划文件第五节登记。

分批

  • K28-A1(立即执行):底部 4 Tab(首页/剧场/创作/我的)+ 首页浏览化改造;剧场本批只改名/保留现状。
  • K28-S(A1 合并后立即执行):浅色暖橙视觉主题——按用户 2026-10-20 参考图,改全局 token、剧卡/chips/角标样式;底部导航保持 4 槽(不做五槽/中央 +);口径见计划文件「二·五 视觉风格 v2」。
  • K28-A2(A1 验收后):剧场页按简图改造(分类 + 语言 chips + 两列剧卡)。
  • K28-B:创作页 + 我的页 UI。
  • K28-C:竖屏播放页 UI + 选集/评论/更多操作/内容解锁四面板。
  • K28-D:l10n 三语补齐、回归测试、门禁、PROGRESS 留痕。

约束

  • 每批从 develop 拉 feature/flutter-k28-*,完成后 --no-ff 合回 develop 并 push。
  • 门禁:dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug。
  • 提交作者 Zhou Weidong <502333560@qq.com>;禁 git add -A;真机验收由用户执行。
  • 播放页现有「清屏/退出清屏、暂停才显示播放按钮、清屏跨集保持、倍速弹窗」口径不得回退。

【待 Trae 协助】2026-10-19 | 客户端 P0→P2 契约缺口(不阻塞其余项)

以下能力当前后端契约/配置不具备,客户端不做假按钮、不造接口;已落地的同类入口保持真实可用。请确认后端排期或补充凭据。

  1. 看广告解锁:/app/** 无激励广告相关接口,Flutter 工程也未集成广告 SDK;当前只实现金币解锁。需提供广告平台账号、广告位 ID 与发奖校验接口。
  2. Google Play 充值:POST /app/orders/verify 需要真实 purchaseToken 与商店商品配置;服务端默认 verifier=disabled,客户端无 Google Play Billing 商品/凭据。已按「暂不接则隐藏入口」处理,钱包页不放假充值按钮。
  3. 短信验证码通道:POST /app/auth/otp 已对接,但服务端当前是 LogOtpSender,只写日志不发短信;UI 可发起请求,真机收码需替换短信供应商后才能验收。
  4. 创作 tab:契约明确「创作者中心由 Web 端承载,App 不提供创作者中心」,C 端只有 POST /app/creator/apply,没有 AI 制作、上传、发布接口。无法做真实创作 tab,需产品确认是否改为「入驻申请」或开放 C 端接口。
  5. 会员购买:无 C 端会员商品/订阅接口,后台商品契约也禁止启用 VIP 商品;无法做真实购买流程。
  6. 搜索接后端:/app/** 无搜索接口;当前只能对已加载目录本地过滤,无法做服务端搜索。
  7. 剧场语言筛选:剧目有 language 字段,但 /app/home/feed、/app/discover/feed、卡片 VO、详情 VO 均未下发语言;客户端无真实数据可筛。
  8. 播放清晰度切换:/app/episodes/{id}/play 当前只下发 qualities: ["AUTO"],没有多清晰度地址;仅保留 AUTO,不伪造切换。
  9. Google 登录:需要 Google OAuth 客户端 ID/服务器配置;当前登录页已移除该假入口。

补充指令(2026-10-19 23:30 周卫东)

  1. UI/UX 参考 Google 设计体系:遵循 Material Design 3 规范 + Google Stitch 设计理念,界面现代简洁、层次分明,做成精品,不要粗糙界面。
  2. 收尾提交 develop:所有工作做完、门禁全过后,把 feature 分支合并回 develop 并 git push origin develop。用户明天拉 develop 打包验证。

INBOX — 给 Codex 的指令队列(FruitServer 仓库内)

Codex 每次开始任务前必须先读本文件(2026-10-16 起指令通道从 specs 仓库迁到本仓库)。 按条目顺序执行,处理完的条目标记 [已处理]。指令来源:项目经理(Trae)。有异议在条目下留言,不要静默跳过。

规范以 specs 仓库为准(AGENTS.md、CODING_STANDARDS.md、GIT_WORKFLOW.md、docs/01~20、docs/plans/codex-研发任务清单.md)—— specs 现在只拉取、由项目负责人更新,我们不再往它提交。工作留痕写在本仓库 docs/plans/PROGRESS.md。

⚠️ 双 Agent 协作红线(Trae + Codex 共守)

  1. 单一写者:同一时刻只有一个 agent 在改代码/写 PROGRESS/跑门禁。
  2. 留痕:谁干活谁写 docs/plans/PROGRESS.md(命令 + 原始输出 + 解读,逐动作追加)。Trae 接手时用「【Trae 接手】」标记。
  3. git 不冲突:提交前先 git status/git log;只 git add 自己的文件,禁止 git add -A/.;不夹带对方改动。
  4. 文件不覆盖:写 INBOX/PROGRESS 前先读最新,追加不覆盖。
  5. 求助通道:遇到阻塞/不确定,在本文件顶部写「【待 Trae 协助】+ 问题描述」。
  6. 禅道同步:任务「开始/完成」时同步更新禅道任务状态。
  7. 验收归用户:每批做完停在干净交接点,由用户亲自验证;回执必须含「用户可照做的验证步骤 + 每步预期结果」。
  8. 门禁(不达标不提交):后端 = mvn test 全过(核心接口须有契约测试);Web = pnpm build:pro 通过 + eslint 零错误;Flutter = dart analyze lib test 零告警 + flutter test 全过。
  9. 分支(按 GIT_WORKFLOW.md):从 develop 拉 feature/<模块>-<简述>,小步提交;完成后 git checkout develop && git merge --no-ff feature/... 再 git push origin develop;绝不动 main(dev→main 走 PR,由用户操作)。
  10. 设备验证必须「设备侧取证」(2026-10-16 新增,血的教训):真机 / 模拟器 / 小程序等目标设备的验证,证据必须产自设备侧(设备侧集成测试、设备日志、设备截图、设备侧抓到的连接)。禁止用"在开发机上 curl 某地址返回 200"证明设备可达 —— 本机 curl 本机网卡地址必然 200,这是假阳性(已实际误导过一次:10.157.242.1 是开发机 VPN 网卡地址,手机根本不可达,导致真机「剧目列表加载失败」)。拿不到设备侧证据的,一律标「未验证」,不得写成"已验证"。

🚨🚨 立即开工(最高优先 · 不要等巡检 · 不要做任何收口动作)—— 2026-10-19 周卫东拍板

读到这里就直接开始做下面 P0 → P1 → P2,逐条落地并跑门禁,每条完成写 PROGRESS 留痕。

背景:周卫东对照「漫剧 App 功能规划图」盘点 ReelShort(Flutter)现状,结论:「看剧」链路真实可用,但「商业化」和「创作」大面积是假功能/缺失(金币写死 0、充值弹「示例数据」、解锁弹「即将开放」、下载不落盘、Google/手机登录弹「即将开放」、创作 tab 整个没有)。

拍板:全量补齐 P0→P1→P2。原则——不贪多,但每个都要能用、要精品。后端接口已就绪,全部是 App 端对接活,不重做后端。

工作目录:C:\work\ReelShort(Flutter,分支 develop)。禅道任务:29/30/31。

P0 — 消灭假功能(做出来的必须能用,最优先)

  1. 内容解锁闭环:对接 POST /app/episodes/{id}/unlock,金币解锁 + 看广告解锁跑通,不再「即将开放」
  2. 金币余额:对接 GET /app/wallet,不再写死 0
  3. 充值流程:对接订单接口;若支付暂不接则先隐藏入口,不给假按钮
  4. 本地下载落盘:对接 GET /app/downloads/{episodeId}/url,真下载 + 离线播放
  5. 手机号登录:对接 POST /app/auth/otp,不再「即将开放」

P1 — 补齐规划核心功能

  1. 「创作」tab(底部导航 3→4):AI 制作入口 + 上传发布入口,对接 creator-center 接口
  2. 开通会员购买流程
  3. 首页「榜单」tab
  4. 剧场「语言筛选」

P2 — UX 打磨(精品感)

  1. 搜索接后端搜索接口(不再本地过滤)
  2. 点赞/评论显示计数
  3. 设置页完善(移除 debug「服务器地址」)
  4. 首页轮播 banner 真正用起来
  5. 播放清晰度切换

门禁:每条做完 dart analyze lib test 零告警 + flutter test 全过,再下一条。测试命名 should_<期望>_when_<条件>。

执行状态(2026-10-19 收尾)

  • P0:金币解锁、钱包余额、本地下载落盘、手机号登录已完成;充值入口按契约隐藏;看广告解锁缺广告接口,见顶部第 1 条。
  • P1:榜单 tab 已完成;创作 tab、会员购买、剧场语言筛选缺契约,见顶部第 4/5/7 条。
  • P2:点赞/评论计数、设置页清理、首页轮播 banner 已完成;服务端搜索、播放清晰度切换缺契约,见顶部第 6/8 条。
  • 门禁:dart analyze lib test 零告警;flutter test 全过(+385 ~1);flutter build apk --debug 成功。
  • 交付:feature/flutter-p0-commerce 已以 --no-ff 合并 develop(ec2db68)并推送 origin/develop。

[待处理] 2026-10-19 | K27.16 客户端 develop 合并进 release/v1.0.0

用户指令(2026-10-19,原文):

「然后把 dev 相关改动合并到release/v1.0.0」

前置状态

  • ReelShort release/v1.0.0 = 814b2c0(含 3d0e080 明文 HTTP/双网络修复 + 公网地址 http://39.162.50.99:8081)。
  • develop = 190e4f9,领先 release 55 个提交(K22–K27 全部 UI/播放器/发现页改动)。

口径

  1. 在 release/v1.0.0 上执行 git merge --no-ff develop,冲突按以下原则解决:
    • lib/core/config/app_config.dart:地址取 release(devHostBaseUrl 与 lanBaseUrl 均为 http://39.162.50.99:8081),保留 release 的 probeAndApplyBaseUrl();结构/其余方法取 develop。
    • lib/main.dart:保留 await AppConfig.probeAndApplyBaseUrl();。
    • android/app/src/main/AndroidManifest.xml:保留 android:usesCleartextTraffic="true"。
    • lib/core/config/server_endpoint.dart:采用 develop 的 SharedPreferencesAsync 版本。
    • pubspec.yaml / pubspec.lock:采用 develop 的依赖集合(shared_preferences ^2.5.5、shared_preferences_platform_interface dev 依赖、foundation override),不要保留 release 的低版本降级。
    • lib/core/data/services/auth_session_store.dart:采用 develop 版本。
    • 其余 K22–K27 的 UI/播放器/发现页/测试改动全部保留 develop 版本。
  2. 解决冲突后执行 flutter pub get,同步把 test/config/server_endpoint_test.dart 的地址断言改为 http://39.162.50.99:8081(保留 develop 的异步 SharedPreferences mock)。
  3. 门禁:dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug。
  4. 合并提交后 push release/v1.0.0;作者 Zhou Weidong <502333560@qq.com>;禁 git add -A;PROGRESS 留痕(含冲突清单与门禁原始输出)。
  5. 真机验证由用户执行。

[待处理] 2026-10-19 | K27.15 客户端默认 API 地址改回 192.168.31.187:8081

用户指令(2026-10-19,原文):

「http://192.168.31.187:8081 改成这个」

口径

  1. lib/core/config/app_config.dart 的 devHostBaseUrl 改为 http://192.168.31.187:8081。
  2. 地址优先级与模拟器/桌面环回逻辑不变。
  3. 同步更新 test/config/server_endpoint_test.dart 中断言旧地址的用例。

测试:dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug。

流程:从最新 develop 拉 chore/flutter-api-host-3;--no-ff 合回 develop 并 push;作者 Zhou Weidong <502333560@qq.com>;PROGRESS 留痕;真机验收由用户执行。


[待处理] 2026-10-19 | K27.14 客户端默认 API 地址改为 172.18.0.7:8081

用户指令(2026-10-19,原文):

「改成这个 172.18.0.7:8081」

口径

  1. lib/core/config/app_config.dart 的 devHostBaseUrl 改为 http://172.18.0.7:8081。
  2. 地址优先级与模拟器/桌面环回逻辑不变。
  3. 同步更新 test/config/server_endpoint_test.dart 中断言旧地址的用例。

测试:dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug。

流程:从最新 develop 拉 chore/flutter-api-host-2;--no-ff 合回 develop 并 push;作者 Zhou Weidong <502333560@qq.com>;PROGRESS 留痕;真机验收由用户执行。


[待处理] 2026-10-19 | K27.13 客户端默认 API 地址改为 192.168.31.250:8081

用户指令(2026-10-19,原文):

「现在把客户端的api 地址改为 192.168.31250:8081」

说明:原文 192.168.31250 不是合法 IPv4,按 192.168.31.250 处理。

口径

  1. lib/core/config/app_config.dart 的 devHostBaseUrl 改为 http://192.168.31.250:8081,注释保持原文。
  2. 其余优先级不变:运行时设置 > --dart-define=API_BASE_URL > 平台默认值;模拟器/桌面仍走环回。
  3. 不改其他代码;如有测试断言旧地址,一并更新。

测试:dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug。

流程:从最新 develop 拉 chore/flutter-api-host;--no-ff 合回 develop 并 push;作者 Zhou Weidong <502333560@qq.com>;PROGRESS 留痕;真机验收由用户执行。


[待处理] 2026-10-19 | K27.12 发现页封面按原图比例铺满 + 下拉刷新

用户指令(2026-10-19,原文):

「不是这样 根据封面尺寸全遮住啊 然后发现页加一个下拉刷新」

口径

  1. 封面按原图比例铺满(K27.10 的 contain 留白不算):
    • 现有封面源图比例不一(约 1220×2712、1520×2720、1080×1413),固定 120 高无法既铺满又不裁剪。
    • 改为两列瀑布流:每张卡宽度固定为列宽,封面高度由图片原始宽高比决定(fitWidth,不裁剪、不留黑边),剧名/题材仍在封面下方。
    • 封面加载中/失败用 3:4 占位,避免高度为 0;「免费」角标仍叠加在封面左上角;封面不再叠加剧名与渐变。
    • 保留 home-all-dramas-section / home-all-dramas-grid / home-drama-card-<id> / home-drama-cover-<id> 等既有 Key 语义(容器 Key 不变)。
    • 两列分配按奇偶分列实现(无第三方依赖);列间距 10、页面左右 14、卡片间距 10 不变。
  2. 下拉刷新:
    • 发现页 CustomScrollView 外包 RefreshIndicator(AlwaysScrollableScrollPhysics),主色指示器。
    • onRefresh 同时执行 categories.load() 与 logic.refreshResume(),完成后收起;失败仍保留现有错误态/空态。
  3. 不改搜索、分类选择、快捷入口与底部导航逻辑。

测试

  1. 封面图片节点不再使用裁剪模式(fitWidth 或等比铺满),且没有固定高度导致留白。
  2. 下拉手势触发 onRefresh:CategoryController.load 与 refreshResume 被调用(可用假 repository 统计次数),指示器收起。
  3. 卡片 Key 与点击进入详情行为不变。
  4. 既有测试全过。

流程:从最新 develop 拉 fix/flutter-discover-masonry-refresh;门禁 dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug;--no-ff 合回 develop 并 push;作者 Zhou Weidong <502333560@qq.com>;PROGRESS 留痕;真机验收由用户执行。


[待处理] 2026-10-19 | K27.11 发现页搜索框:点击页面其他区域失焦

用户指令(2026-10-19,原文):

「点击页面搜索输入框失去焦点」

口径

  1. discover_view.dart 的搜索 TextField(Key discover-search)增加 onTapOutside:点击输入框以外的任意页面区域时调用 logic.searchFocus.unfocus(),收起键盘、取消焦点。
  2. 点击输入框本身仍保持/获取焦点,不影响输入与搜索过滤;点击分类 chips、卡片、按钮等仍执行原有动作(失焦不吞点击)。
  3. 不改搜索逻辑、不清空输入内容、不改变界面布局。

测试

  1. 点击 discover-search 使其获得焦点;
  2. 点击页面其他区域(如顶栏标题或分类 chip)后,logic.searchFocus.hasFocus == false(或输入框不再处于 focused 状态);
  3. 输入内容与过滤结果不受影响;
  4. 既有测试全过。

流程:从最新 develop 拉 fix/flutter-discover-search-unfocus;门禁 dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug;--no-ff 合回 develop 并 push;作者 Zhou Weidong <502333560@qq.com>;PROGRESS 留痕;真机验收由用户执行。


[待处理] 2026-10-19 | K27.10 发现页短剧卡片:封面不裁剪、图上不显示剧名

用户指令(2026-10-19,原文):

「现在改发现页面的短剧列表 要求图片不做切割 图片上不显示剧名」

口径

  1. discover_view.dart 的 _HomeDramaCard 封面:
    • RemoteImage 的 fit 从 BoxFit.cover 改为 BoxFit.contain,图片完整显示不裁剪;封面区域加中性底色(如 AppColors.card),避免留白突兀。
    • 封面尺寸(高 120、圆角 10)与卡片网格尺寸保持不变。
  2. 删除封面上的剧名:移除盖在图片上的 Positioned 标题 Text,以及只为该标题服务的底部黑色渐变遮罩。
  3. 保留左上角「免费」角标;图片下方的剧名/题材两行照旧显示(home-drama-title-<id> 仍存在且只有一处)。
  4. 不改变卡片点击、网格列数与间距。

测试

  1. 封面内不再出现剧名文本(find.descendant(of: cover, matching: find.text(title)) 为空)。
  2. 封面 RemoteImage.fit == BoxFit.contain。
  3. 图片下方 home-drama-title-<id> 仍存在且只有一个;「免费」角标逻辑不变。
  4. 既有测试全过。

流程:从最新 develop 拉 fix/flutter-discover-cover-contain;门禁 dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug;--no-ff 合回 develop 并 push;作者 Zhou Weidong <502333560@qq.com>;PROGRESS 留痕;真机验收由用户执行。


[待处理] 2026-10-19 | K27.9 发现页顶栏固定(与我的页面一致)

用户指令(2026-10-19,原文):

「发现页上方的怎么和我的页面不一样 不是固定的」

问题定位(Trae 勘察)

  • discover_view.dart 的 _header 被放在 CustomScrollView 的 SliverToBoxAdapter 里,滚动时会跟随内容消失。
  • mine_view.dart 的 _topBar 在 List(ListView) 之外,固定不滚动。

口径

  1. 把发现页 _header(logo + 「发现」标题 + 搜索按钮 + 头像)从 CustomScrollView 的 slivers 中移出,放在 _page 的 Column 顶部、Expanded(_feed(...)) 之前,实现固定顶栏。
  2. 顶栏尺寸与我的页面统一:SizedBox(height: 46) + Padding(horizontal: 14),内部布局与样式沿用现有 _header。
  3. 搜索框、分类 chips、继续观看、快捷入口等内容仍可滚动,不改变顺序与间距。
  4. 底部导航行为不变。

测试

  1. 发现页初始 discover-search-button 的位置;
  2. 向上拖动内容列表后,discover-search-button 的 dy 不变,而可滚动内容(如分类 chips / 正在追卡片)位置发生变化;
  3. 既有测试全过。

流程:从最新 develop 拉 fix/flutter-discover-fixed-header;门禁 dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug;--no-ff 合回 develop 并 push;作者 Zhou Weidong <502333560@qq.com>;PROGRESS 留痕;真机验收由用户执行。


[待处理] 2026-10-19 | K27.8 首页右侧互动下移 + 倍速移入右上角弹窗 + 拖动进度显示时间

用户指令(2026-10-19,原文):

「现在把首页右侧的 喜欢 评论 分享 收藏位置再往下调整下 而且点剧集进去的播放页 倍速是放在 右上角点击的弹窗内 滑动进度条的时候要有时间」

口径

  1. 首页右侧互动栏(喜欢/评论/分享/收藏)整体下移:HomeFeedOverlay 中该栏的 Align 从 Alignment.centerRight 调整为约 Alignment(1.0, 0.45)(或等效的下移),右侧间距保持 12,不得与底部信息卡重叠。
  2. 倍速入口从画面右下角移除(删除 player-speed 那个 1.0x 文本按钮),改放到右上角「更多」弹窗内:
    • _openMore 弹窗新增 ListTile,Key player-more-speed,leading Icons.speed_rounded,title 为 l10n.playbackSpeed,trailing 显示当前倍速(如 1.0x)。
    • 点击后关闭更多弹窗并打开现有倍速弹窗 player-speed-sheet;档位与选中逻辑保持不变,选择后立即生效并更新 trailing。
  3. 拖动进度条时显示时间:播放页 SeekProgressBar 改为 showTimes: true,拖动过程中显示 当前时间 / 总时长(复用组件已有的 mm:ss / mm:ss 预览),松手后按现有逻辑 seek。
  4. 其余已确认规则不变:中心按钮仅暂停时显示、清屏跨集共享、退出清屏按钮、无定时器。

测试

  1. 首页 rail-like 的垂直中心比修改前更低(断言其 center.dy 大于播放器可视区中线上方阈值,或与底部信息卡顶部保持间距)。
  2. 播放页不再存在 player-speed 主画面入口;player-more → player-more-speed → player-speed-sheet 可打开;选 1.5x 后关闭弹窗,再次打开更多弹窗 trailing 显示 1.5x。
  3. 拖动 player-progress 过程中出现形如 mm:ss / mm:ss 的时间文本;松手后消失。
  4. 既有测试全过。

流程:从最新 develop 拉 fix/flutter-feed-rail-speed-time;门禁 dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug;--no-ff 合回 develop 并 push;作者 Zhou Weidong <502333560@qq.com>;PROGRESS 留痕;真机验收由用户执行。


[待处理] 2026-10-19 | K27.7 倍速选择改为弹窗

用户指令(2026-10-19,原文):

「倍速的要写在弹窗内」

口径

  1. _PlaybackOverlay 画面右下角常驻的静态 1.0x 文本改为可点击的倍速入口(Key player-speed,文案显示当前倍速,如 1.0x)。
  2. 点击弹出倍速弹窗(底部 sheet,Key player-speed-sheet),选项:0.5x / 0.75x / 1.0x / 1.25x / 1.5x / 2.0x,每项 Key player-speed-<值>(如 player-speed-1.5);当前项用主色高亮。
  3. 选择后立即 controller.setPlaybackSpeed(value) 并关闭弹窗,入口文案同步更新。
  4. 倍速状态由 _PlayerPageState 统一持有:切换剧集后新控制器自动沿用当前倍速;离开播放页后恢复默认 1.0x。
  5. 清屏态不显示倍速入口(与顶栏/进度条一起隐藏);普通态与暂停态显示。
  6. l10n 新增 playbackSpeed:en Playback speed / zh 倍速 / id Kecepatan putar;三语齐全。

测试

  1. 点 player-speed 出现 player-speed-sheet,包含 6 个档位。
  2. 选 player-speed-1.5 后弹窗关闭,入口显示 1.5x。
  3. 清屏后倍速入口隐藏。
  4. 切集后倍速入口仍显示所选值(若实现层可测)。
  5. 既有测试全过。

流程:从最新 develop 拉 fix/flutter-speed-sheet;门禁 dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug;--no-ff 合回 develop 并 push;作者 Zhou Weidong <502333560@qq.com>;PROGRESS 留痕;真机验收由用户执行。


[待处理] 2026-10-19 | K27.6 首页集数文案左贴 + 清屏状态跨集共享

用户指令(2026-10-19,原文):

「首页的底部的卡片 第n集/共n集 要紧挨着左侧啊 还有 选集进去播放页面还是 点击清屏后 再次滑动 还不是清屏状态」

问题定位(Trae 勘察)

  1. home_feed_overlay.dart 的 _infoCard 用 Row + Expanded + 右对齐 Flexible,把「第 n 集 / 共 n 集」顶到了卡片最右侧。
  2. 选集进入的播放页(homeMode=false)是 PlaybackQueue.series,上下滑动按集切换;每集各自渲染一个 _PlaybackOverlay,而 _controlsVisible 是 _PlaybackOverlayState 的局部状态,所以清屏只作用于当前集,滑到下一集又恢复控件。

口径

  1. 首页底部卡片:剧集类型标签与「第 n 集 / 共 n 集」放在同一个 Wrap(spacing 6、runSpacing 4、交叉轴居中),集数文案紧跟标签左侧排列,不再右对齐/推到最右;标签过长时允许整体换行,不得溢出。
  2. 清屏状态提升为播放页级别:把 _controlsVisible 从 _PlaybackOverlayState 移到 _PlayerPageState,与首页 _feedChromeVisible 合并为同一个 _chromeVisible(或等价命名)。
    • _PlaybackOverlay 改为无内部显隐状态,接收 controlsVisible、onClear、onTogglePlay 等参数。
    • 播放页清屏后,上下滑动切集 / 切剧仍保持清屏;退出清屏按钮恢复后所有页面同步显示控件。
    • 首页与选集两种入口共用同一状态来源。
  3. 已确认的规则不变:播放中不显示中心按钮、暂停时显示播放按钮、清屏点画面切播放、右上角退出清屏、无定时器。

测试

  1. 首页 home-feed-episode 的左边缘紧贴标签区(与 home-feed-info-card 左内边距接近,不得落在卡片右半区)。
  2. 选集进入播放页 → 点 player-clear-screen → 上滑切到下一集 → 仍无 player-controls-top / player-controls-bottom,player-exit-clear-screen 仍在;点退出后恢复。
  3. 首页清屏后滑动切剧仍保持清屏(回归)。
  4. 中心按钮仅暂停时显示(回归)。
  5. 既有测试全过。

流程:从最新 develop 拉 fix/flutter-clear-screen-shared;门禁 dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug;--no-ff 合回 develop 并 push;作者 Zhou Weidong <502333560@qq.com>;PROGRESS 留痕;真机验收由用户执行。


[待处理] 2026-10-19 | K27.5 中心按钮只在暂停时显示(播放状态一律不显示)

用户指令(2026-10-19,原文):

「都说了播放状态不显示啊」

口径(最终,覆盖 K27.4 第 2 条)

  1. 播放中:不显示任何中心按钮(无论是否清屏、无论首页还是播放页)。
  2. 暂停中:显示中心播放按钮(PlayerCenterToggle(playing: false),图标为播放);点击它或点画面恢复播放,播放后按钮立即消失。
  3. 播放页:中心按钮单独渲染在 _controlsVisible 控制层块之外,保证清屏 + 暂停时仍能看到播放按钮;清屏播放中不显示。
  4. 首页:home-feed-center-play 仅在 !userWantsPlay 时渲染,播放中不显示;清屏不影响该规则(清屏 + 暂停 → 显示播放按钮)。
  5. K27.4 的其他结论保留:清屏状态跨滑动保持、退出清屏按钮、清屏态点画面切播放、无定时器。

测试

  1. 播放中(普通态与清屏态):中心按钮都不存在。
  2. 点画面暂停后:普通态与清屏态都出现播放按钮;点它恢复播放且按钮消失。
  3. 清屏后滑动切剧:仍保持清屏。
  4. 退出按钮恢复控制层且不改变播放态。
  5. 更新 K27.4 里「两种播放状态都显示中心按钮」的断言;既有测试全过。

流程:从最新 develop 拉 fix/flutter-center-toggle-paused-only;门禁 dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug;--no-ff 合回 develop 并 push;作者 Zhou Weidong <502333560@qq.com>;PROGRESS 留痕;真机验收由用户执行。


[待处理] 2026-10-19 | K27.4 清屏状态跨滑动保持 + 中心按钮常驻(暂停/播放双态)

用户指令(2026-10-19,原文):

「我点了清屏后 再次滑动 还是没有清屏 而且清屏后点击播放没显示暂停按钮」

口径(覆盖 K27.1 / K27.3 的冲突部分)

  1. 清屏状态跨滑动保持:撤销 K27.3 在 onPageChanged 里复位 _feedChromeVisible 的逻辑;首页清屏后上下滑动切剧,新页面仍保持清屏(只有顶栏/右侧 rail/底部卡片/遮罩隐藏,见第 2 条中心按钮除外)。
  2. 中心按钮常驻双态:
    • 播放中显示暂停图标(playing: true);暂停中显示播放图标(playing: false);点击即切换播放/暂停。
    • 播放页:中心按钮移出 if (_controlsVisible) 控制层块,清屏时仍显示;清屏仍隐藏顶栏、进度条、三按钮、评论卡、倍速角标。
    • 首页:中心按钮不再依赖 _feedChromeVisible,清屏时同样显示;chromeHidden(PiP)时不显示。
    • 清屏态下点画面仍是播放/暂停切换;退出清屏仍只由右上角退出按钮触发。
  3. 不引入定时器;顶栏/底部控件仍常驻、不自动隐藏。

测试

  1. 清屏后滑动切换下一页:仍无 home-feed-title / home-feed-info-card,退出按钮仍在。
  2. 清屏后中心按钮存在:播放中显示暂停图标;点一下 → 暂停并变播放图标;再点 → 播放并变暂停图标。
  3. 清屏后点画面:播放/暂停切换且保持清屏。
  4. 点退出按钮:除中心按钮外的控制层恢复,播放态不变。
  5. 既有测试全过(更新原「滑动复位」「清屏无中心按钮」的断言)。

流程:从最新 develop 拉 fix/flutter-clear-screen-state;门禁 dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug;--no-ff 合回 develop 并 push;作者 Zhou Weidong <502333560@qq.com>;PROGRESS 留痕;真机验收由用户执行。


[待处理] 2026-10-19 | K27.3 清屏态点击画面切播放、退出按钮位置与滑动重置

用户指令(2026-10-19,原文):

「不是啊 点击完清屏 我在点击屏幕如果是播放中应该是暂停啊 而且首页点完清屏 退出清屏的位置不对 如果点击了清屏 在滑动切换也是清屏状态」

口径

  1. 清屏态点击画面 = 播放/暂停切换,保持清屏:
    • 播放页 _onVideoTap:无论控制层是否可见,都调用 widget.onTogglePlay();删除「清屏态点画面只恢复控制层」的分支。
    • 首页清屏态:home-feed-video-tap 改为播放/暂停切换,保持清屏。
    • 退出清屏只由右上角 player-exit-clear-screen / home-feed-exit-clear-screen 触发,点击退出按钮不改变播放态。
  2. 首页退出清屏按钮位置修正:放入 SafeArea(顶部安全区内 8、右侧 12),36×36,与首页顶栏按钮视觉对齐;不得压在状态栏/刘海下面。
  3. 首页滑动切换剧目时重置清屏:PageView.onPageChanged 中把 _feedChromeVisible 复位为 true(仅 homeMode),新一页进入时展示控制层。
  4. 不引入定时器;控制层仍常驻;已完成的中心按钮口径(仅暂停时显示播放按钮)不变。

测试

  1. 清屏后点击画面:播放 → 暂停,顶栏/底部仍隐藏,退出按钮仍在;再点 → 播放,仍清屏。
  2. 点退出按钮:控制层恢复,播放态不变。
  3. 首页清屏后用 PageView 切换下一页:home-feed-title / home-feed-info-card 恢复显示。
  4. 既有测试全过(更新原先「点画面恢复控制层」的断言)。

流程:从最新 develop 拉 fix/flutter-clear-screen-tap;门禁 dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug;--no-ff 合回 develop 并 push;作者 Zhou Weidong <502333560@qq.com>;PROGRESS 留痕;真机验收由用户执行。


[待处理] 2026-10-19 | K27.2 清屏态增加「退出清屏」浮动按钮

用户指令(2026-10-19,原文):

「而且有了清屏自然要有退出清屏按钮啊」

口径

  • 播放页与首页 feed 进入清屏态后,画面右上角保留一个「退出清屏」浮动按钮,点击恢复控制层。
  • 按钮规格:36×36 圆形,底色 Color(0x80000000),图标白色 Icons.fullscreen_exit_rounded(size 18),距顶部安全区 8、右侧 12。
  • Key:播放页 player-exit-clear-screen,首页 home-feed-exit-clear-screen。
  • tooltip 新增 l10n key exitClearScreen,三语齐全:en Exit clear screen / zh 退出清屏 / id Keluar dari layar bersih。
  • 点击只恢复控制层,不改变播放/暂停状态;恢复后按钮消失。
  • 保留「清屏态点击画面恢复控制层」的现有手势。
  • 按钮不自动隐藏,与控制层显示状态严格互斥。

测试

  1. 点 player-clear-screen 后 player-exit-clear-screen 出现,控件区仍隐藏。
  2. 点 player-exit-clear-screen 后顶栏/底部/中心按钮恢复,退出按钮消失,播放态不变。
  3. 首页 home-feed-clear-screen → home-feed-exit-clear-screen 出现;点击后 overlay 恢复、按钮消失。
  4. 既有测试全过。

流程:可从 develop 拉 fix/flutter-exit-clear-screen;门禁 dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug;--no-ff 合回 develop 并 push;作者 Zhou Weidong <502333560@qq.com>;PROGRESS 留痕;真机验收由用户执行。


[待处理] 2026-10-19 | K27.1 中心按钮改口径:播放中不显示,仅暂停时显示播放按钮

用户指令(2026-10-19,原文):

「播放按钮不用显示啊 就点击暂停的时候显示」

口径(覆盖 K27 第三节,其余不变)

  • 播放中:不显示任何中心按钮;点击画面 = 暂停(点一下暂停)。
  • 暂停后:中心显示播放按钮(Key 沿用 player-play-toggle,playing: false),点击或再点画面 = 播放;播放开始后按钮立即消失。
  • 首页沉浸 feed 同步:暂停时显示中心播放按钮 home-feed-center-play,播放中不显示;点画面仍是播放/暂停切换。
  • 清屏状态下中心按钮一并隐藏;点画面先恢复控制层,不改变播放态。
  • 不引入任何定时器;控制层仍然常驻。

实现要点

  1. _PlaybackOverlay:把 if (widget.playing) PlayerCenterToggle(playing: true) 改为 if (!widget.playing) PlayerCenterToggle(playing: false, onTap: widget.onTogglePlay)。
  2. _onVideoTap:控制层可见时直接 widget.onTogglePlay();清屏时只恢复控制层。
  3. player_view.dart 首页分支:恢复暂停态中心播放按钮(home-feed-center-play → setUserWantsPlay(true)),播放态不渲染;PiP/清屏时不渲染。
  4. 测试更新:
    • 播放中 player-play-toggle 不存在;点画面后视频暂停且按钮出现;点按钮后恢复播放且按钮消失。
    • 首页暂停后 home-feed-center-play 出现,播放后消失。
    • 清屏隐藏中心按钮,点画面恢复控制层。
    • pump 5 秒控件仍在。
  5. 门禁/分支/提交规范同 K27:从 develop 拉 fix/flutter-paused-play-toggle,dart analyze lib test 零告警 + flutter test 全过 + flutter build apk --debug,--no-ff 合回 develop 并 push,作者 Zhou Weidong <502333560@qq.com>,PROGRESS 留痕;真机验收由用户执行。

[已完成·待用户验收] 2026-10-19 | K27 播放器控制层交互重做:清屏按钮 + 取消自动隐藏 + 暂停按钮即时消失

用户指令(2026-10-19,原文):

「现在播放页不对啊 不是点击播放或暂停 等一会所有的按钮都消失了 而是要要清屏按钮 点了清屏其他的才消失 点击暂停是点击一下 暂停 在点击下播放 暂停按钮消失这样」

现状问题(Trae 勘察)

  • player_view.dart 的 _PlaybackOverlayState 有 Timer? _hideTimer + _scheduleHide(),2.5 秒后把 _controlsVisible 置 false,属于「等一会全部消失」。
  • 首页 player_view.dart 中 shouldShowFeedPauseIndicator 分支会在暂停后常驻一个中心播放按钮(home-feed-center-play)。
  • 播放页中心按钮 player-play-toggle 常驻,暂停后也不消失。
  • 首页 HomeFeedOverlay 没有清屏按钮;播放页顶栏只有返回 / 标题 / 更多。

一、控制层常驻(删除一切定时隐藏)

  1. _PlaybackOverlayState 删除 Timer? _hideTimer、_scheduleHide()、initState/dispose 中的定时器逻辑;import 'dart:async' 若无其它用途一并删除。
  2. _controlsVisible 只由用户显式动作改变:点「清屏」→ 隐藏;清屏状态下点画面 → 恢复。不得有任何延时自动隐藏。
  3. 进入播放页、切集、选集返回、PiP 返回时,控制层默认可见。

二、清屏按钮

播放页(_PlaybackOverlay)

  1. 顶栏右侧、player-more 左侧新增清屏按钮,复用 _TopAction(36×36),Key player-clear-screen,图标 Icons.fullscreen_rounded。
  2. 点击后隐藏:顶栏(player-controls-top)、中心按钮、进度条、三按钮、评论卡、1.0x 角标;画面上只剩视频。
  3. 清屏状态下点击画面任意处 → 恢复控制层,不切换播放/暂停。
  4. tooltip 走 l10n 新增 key clearScreen,en/zh/id 三语必须齐全(en Clear screen / zh 清屏 / id Bersihkan layar)。

首页沉浸 feed

  1. HomeFeedOverlay 顶栏在「搜索」与「更多」之间新增清屏按钮,Key home-feed-clear-screen,36×36 圆钮,样式复用 _RoundIcon。
  2. 点击后隐藏整个 HomeFeedOverlay(剧名行、右侧 rail、底部玻璃卡)、首页暂停提示与渐变遮罩,只留视频。
  3. 该显隐状态由 player_view.dart 持有(例如 _feedChromeVisible),清屏后点画面恢复,且不切换播放状态。

三、中心播放/暂停按钮

  1. 播放中且控制层可见 → 中心显示暂停按钮(PlayerCenterToggle(playing: true),Key player-play-toggle)。
  2. 点击中心暂停按钮 → 执行暂停,并且该按钮立即消失;不显示常驻的播放按钮。
  3. 暂停中点击画面 → 播放;恢复播放后中心暂停按钮随控制层恢复显示。
  4. 删除首页暂停后的常驻播放按钮分支(shouldShowFeedPauseIndicator / home-feed-center-play)。若 helper 及测试因此失效,一并删除并更新测试,不留死代码。
  5. 首页与播放页必须共用 PlayerCenterToggle,样式不得分叉。

四、保持不变

  • 票据三态、进度条 seek、选集、评论、下一集、下载授权、PiP、倍数角标等既有逻辑不改。
  • 不新增第三方依赖;不改后端、不改 Web。

五、测试(先红后绿)

更新/新增 widget test,至少覆盖:

  1. 播放页初始控制层可见;pump 5 秒后 player-controls-top / player-controls-bottom 仍然存在(证明无自动隐藏)。
  2. 点 player-clear-screen → 顶栏、底部控制区、中心按钮都消失;点画面恢复,且播放态不变。
  3. 播放中中心暂停按钮存在;点击后播放态变为暂停且中心按钮消失;再点画面恢复播放。
  4. 首页点 home-feed-clear-screen → home-feed-title、home-feed-info-card 消失;点画面恢复。
  5. 既有全部测试通过(含 player_fullscreen_test.dart、player_center_toggle_test.dart)。

六、门禁与流程

  • 从 develop 拉 fix/flutter-player-clear-screen。
  • 门禁:dart analyze lib test 零告警、flutter test 全过、flutter build apk --debug。
  • 完成后 git checkout develop && git merge --no-ff 并 push;提交身份 Zhou Weidong <502333560@qq.com>;禁用 git add -A。
  • 本批只做代码改造与本地门禁;真机验证由用户执行,Codex 不得自行真机验证,回执里给出用户可照做的验证步骤与每步预期结果。
  • 在 docs/plans/PROGRESS.md 追加留痕(命令 + 原始输出 + 解读)。

[已完成·待用户验收] 2026-10-19 | K26 客户端按「移动端设计预览.html」校正六屏

用户指令(2026-10-19):「现在看 移动端设计预览.html 按这个来」。

新增权威设计文件(本地已同步): /Users/jianghaifeng/Desktop/ReelShort/docs/移动端设计预览.html 该文件优先于之前的 Stitch screens;颜色/布局/组件尺寸以它为准。 保留 K24 已接入的 Outfit/Plus Jakarta Sans 字体,其余按预览校正。

一、全局 Token(按预览)

  • 主色 #FF5B3D、强调 #9278EA、背景 #0B0C10、底栏 #0D0D11、 卡片 #1B1C21 / #1C1D21、输入 #25252A、正文 #E6E1E3、次要 #D2CDD0、浅橙 #FFA490。
  • 圆角:卡片 10–14、按钮 9、chips 20、封面 10;不再统一全圆。
  • 字体沿用 Outfit(标题)/ Plus Jakarta Sans(正文);字号按预览 9/10/11/12/13/14/15/18/20。
  • 更新 AppColors/AppDimens/GlassPanel 默认值;保留现有语义名。

二、首页(预览 01)

  1. 顶部:剧名 + 右侧搜索/菜单 28px 圆钮;不再显示 HOT/Season/剧集头像 Follow。
  2. 右侧互动:Like / Comment / Share / Favorite 四项,22px 图标 + 9px 文案/计数; 下载/PiP 继续收进“更多”。
  3. 底部玻璃卡(rgba(15,15,20,.72)、blur 6、radius 14、padding 11/12):
    • 题材 tags(都市/甜宠,8px)
    • 第 x 集 / 共 y 集(9px)
    • 剧集标题(13px 800)
    • 两个等宽 34px 按钮:▶ 追剧(橙色)/ 选集(#2a2a30)
  4. 底部 3 Tab 文案与图标按预览(首页/发现/我的)。

三、发现页(预览 02)

  1. 顶部:Logo + 发现 + 搜索圆钮 + 头像。
  2. 搜索框 36px、#25252A、全圆、左侧搜索图标。
  3. 分类 chips:推荐/悬疑/恋爱/动作,选中橙底白字。
  4. Continue Watching 卡:#1C1D21、radius 12、52×66 封面、标题/集数/3px 进度、右侧 56×28 橙色“继续”。
  5. 4 个快捷入口(筛选/排行榜/新剧/收藏):#202126、radius 12、高 62、16px 图标 + 9px 文案。
  6. 热门短剧 标题带 3×18 橙色竖条 + 右侧 全部 ›;2 列网格,封面高 120、radius 10、 左上“免费”角标、底部渐变标题、下方 11px 标题 + 9px 题材。
  7. 移除预览中没有的额外区块(轮播/多余 banner)。

四、详情页(预览 03)

  1. Hero 高 190、圆角 0、左上 28px 圆形返回;不再放分享/收藏浮层。
  2. 内容:N 集 · 已完结(紫色 9px,完结无数据时只显示 N 集)、标题 18px、简介 11px / 1.6。
  3. ▶ 开始观看 40px 橙色按钮。
  4. 选集 标题 + 横向/换行集数 chips(1 起,选中橙底白字,锁定紫色)。
  5. 简化 K25 的统计/CTA 行/剧集预览卡;保留播放、收藏、下载逻辑(收藏/下载可放“更多”)。
  6. 不加底部 Tab。

五、播放器(预览 04 + 用户要求全屏)

  1. 全屏播放(用户已明确);顶部 46px:28px 返回 + 居中 剧名 · 第 x 集 + 28px 更多。
  2. 视频区:中心 52px 播放圆钮;右下 1.0x 倍速文字;无右侧互动栏。
  3. 进度条 3px、#FF5B3D 播放段、#474248 轨道。
  4. 三按钮行:选集 / 评论 / 下一集,高 32、radius 9,下一集橙色,末集置灰。
  5. 一条精选评论卡:#1B1C21、radius 10、头像/昵称/时间/正文;无评论隐藏。
  6. 下载/PiP 收进更多;保留票据、自动连播、进度、互动逻辑。

六、我的页(预览 05)

  1. 顶部:Logo + 我的 + 搜索圆钮 + 头像。
  2. 用户行:48px 头像、昵称、欢迎语、右侧“登录/注册”chip(已登录显示账号信息)。
  3. 金币卡:#201B1E、radius 13、金币图标、我的金币、0 金币 · 去充值、右侧 ›。
  4. Continue Watching 卡:#1B1C21、radius 12、50×64 封面、标题、第 x 集 · 时间、橙色“继续”。
  5. Tabs:历史(橙色)/ 收藏 / 喜欢;一条历史卡(48×60 封面、标题、集数/时间、播放圆钮)。
  6. 语言行:#1B1C21、radius 12、语言 + 当前值(橙色)。
  7. 底部 3 Tab;原 My List 区块保留在 Me 页内部。

七、登录页(预览 06)

  1. 居中 64px 渐变 Logo(#FF5B3D → #9278EA,radius 18)+ 水果短剧 20px + 副标题 11px。
  2. 三个 44px 入口:Google(白底)、手机号(#25252A)、邮箱(#25252A); Google/手机号后端未就绪时置灰并提示“即将开放”;邮箱进入现有邮箱表单。
  3. 底部 9px 法律文案:继续即代表同意《用户协议》与《隐私政策》。
  4. 保留 K21 邮箱表单作为二级页,不改认证逻辑。

八、数据与测试

  • 预览中的 12.4k、3.2k、已完结、VIP 等:有真实数据才显示,否则隐藏,不伪造。
  • 更新首页/发现/详情/播放器/我的/登录相关测试;保留播放、进度、收藏、下载、PiP 行为断言。
  • 新增:预览布局关键 Key、3 Tab、搜索、Continue Watching、选集 chips、全屏播放器三按钮、登录入口。
  • dart analyze lib test 零告警;flutter test 全过;flutter build apk --debug 通过。

九、Git

  • 从干净 develop(基线 eefabb6)拉 feature/flutter-k26-mobile-preview,小步提交; --no-ff 合并回 develop 并 push;作者 Zhou Weidong <502333560@qq.com>;不动 main。
  • 计划文档 FruitServer/docs/plans/2026-10-19-K26.md;留痕写 PROGRESS.md。
  • 回执:六屏改动、token、测试条数与门禁原始输出、数据缺口、真机验收步骤。

[待处理] 2026-10-19 | K25 客户端详情页 + 播放器对齐设计稿

用户指令(2026-10-19):「开始实现详情页和播放器」。

设计权威:docs/design/screens/03-detail.html、04-player.html(origin/main 或 /tmp/specs-design/docs/design/screens/)。 设计系统沿用 K24 的 Obsidian Flare token、Outfit/Plus Jakarta Sans、GlassPanel;不改后端接口/模型字段,缺失数据不伪造。

一、详情页(03-detail.html)

  1. Hero:16:9 横版头图(优先 drama.coverUrl,无则 posterUrl),底部渐变; 左上玻璃返回、右上玻璃分享/收藏;图上叠加 Trailer 0:45、4K UHD 玻璃 pill(无数据时只显示 4K/隐藏)。
  2. 元数据:集数 pill(N Episodes)、完结/榜单名次(模型无字段则隐藏)、标题 24px、 Season 1 • DramaPulse Original Release、播放量(viewCount)、题材标签;评分无字段则隐藏。
  3. 简介卡:#12141A/GlassPanel 圆角 16、正文 14px、默认 2 行 + More 展开;保留现有 l10n 文案。
  4. CTA 行:Start Watching(或继续观看集数)橙色全宽胶囊 + 48×48 收藏 + 48×48 下载; 举报入口收进右上“更多”;保留现有 Favorite/Download/Report 逻辑。
  5. Episodes 区:标题 Episodes (N) + 范围 chips(如 1-30,无数据可只显示全部); 横向集数 chips(46×44、选中橙色、锁定紫色);下方 2 列剧集预览卡(96×64 缩略图、Free/Coin、 时长、锁态);保留现有选集跳转与下载入口。
  6. 详情页为 push 页面,不加底部 Tab;加载/失败/空态保留返回按钮(现状已有)。

二、播放器(04-player.html)

  1. 模式拆分:
    • homeMode=true(首页沉浸 feed)保持现状全屏竖滑;
    • homeMode=false(从详情进入)改为设计稿的“卡片式播放器”: 顶部导航(返回、居中剧名 + Ep.x、更多)、9:10 圆角 16 视频框、下方进度与操作区、评论卡。
  2. 顶部/视频框:Auto-Next On 紫色 pill、1.0x 倍速 pill、中心橙色播放按钮、 右侧 36px 点赞/全屏;场景标签(无数据隐藏)。
  3. 进度条:1.5px 轨、缓冲段 white/30、播放段 #FF5B3D、白色 thumb; 左侧当前时间、中间 Ep x / N、右侧总时长;保留拖动 seek。
  4. 三按钮行:Episodes 玻璃胶囊、Comments (count) 玻璃胶囊、Next Ep 橙色胶囊; 分别打开现有选集、评论弹窗、下一集;没有下一集时置灰。
  5. 精选评论卡:#14161E 16px 圆角、VIP 标记(有数据才显示)、点赞数、View all N; 复用 CommentController;无评论时隐藏。
  6. 保留现有播放票据、自动连播、PiP、下载、互动逻辑;下载/PiP 收进顶部“更多”。
  7. 不要改 playback_session/ticket 协议;只改 UI 组织与控件。

三、数据缺口(不得伪造)

  • 评分、完结状态、Top 排名、VIP、剧级点赞/评论计数、场景标签:模型/接口没有就隐藏;
  • viewCount、集数、题材、Episode.locked、freeEpisodes/unlockCoin 已有,按现有字段渲染。

四、测试与门禁

  • 更新详情/播放器相关 widget 测试;保留 PiP、进度、票据错误、收藏/下载等既有行为断言;
  • 新增:16:9 hero、CTA 行、剧集 chips、播放器卡片模式、三按钮行、进度条颜色;
  • dart analyze lib test 零告警;flutter test 全过;flutter build apk --debug 通过。
  • 真机验收由用户执行;不伪造设备侧证据。

五、Git 与回执

  • 从干净 develop(基线 4553606)拉 feature/flutter-k25-detail-player,小步提交; 完成后 --no-ff 合并回 develop 并 push;作者 Zhou Weidong <502333560@qq.com>;不动 main。
  • 计划文档 FruitServer/docs/plans/2026-10-19-K25.md;留痕写 PROGRESS.md。
  • 回执:详情/播放器结构、保留/新增的 Key、测试条数与门禁原始输出、数据缺口、真机验收步骤。

[待处理] 2026-10-19 | K24 客户端 UI 对齐设计稿(设计系统 + 首页 + 发现)

用户指令(2026-10-19):「修改客户端 UI,读 specs 下 docs 下的 screens」。

设计权威文件(已全部读取): docs/design/DESIGN.md、docs/design/README.md、docs/design/screens/01-home.html、02-discover.html (以及 03-detail / 04-player / 05-me / 06-login,后三屏留到 K25/K26)。 口径冲突以 README + screens HTML 为准:#0B0C10 / #16181F / #FF5B3D / #9278EA; DESIGN.md 顶部 YAML 的 #ffb4a5/#cdbdff 是自动生成色板,不采用。

一、本批范围

只做「设计系统 + 底部导航 + 首页 + 发现页」。详情页、播放器、我的页、登录页留到 K25/K26。 不改后端接口、Web、specs;不改业务数据模型字段;缺失的评分/完结/榜单名次不要伪造。

二、设计 Token(全局)

  1. lib/core/theme/app_colors.dart 改为 Obsidian Flare:
    • background #0B0C10、card #16181F、surface-elevated #1D1E28、bar #0B0C10(95% + blur)
    • primary #FF5B3D、pressed #FF4321、secondary/lavender #9278EA
    • gold #FBBF24、danger/free #FF5B3D、success #34D399
    • on-surface #FFFFFF / #E3E2E8、muted #A1A1AA、subtle #71717A
    • border rgba(255,255,255,0.08)、glass 高亮 rgba(255,255,255,0.12-0.14)
    • 保留现有语义命名(AppColors.blue 等)以免大面积改名,但值统一到新色板;新增 secondary、lavender、onSurface。
  2. app_theme.dart:主色/次色/背景/卡片/描边/进度条统一;固定深色;按钮/chip 使用全圆角; 进度条改为 1–2px;补充 Outfit(标题)与 Plus Jakarta Sans(正文)字体。
  3. 字体:下载 Outfit + Plus Jakarta Sans 的 OFL TTF 放入 assets/fonts/,pubspec.yaml 注册; 禁止运行时依赖网络字体;若下载失败,使用系统字体并在回执标注未完成项。
  4. 新增 lib/core/widgets/glass_panel.dart(BackdropFilter 16/24px、半透明渐变、白 8–14% 描边、 可选圆角 16/24/26),供首页底部卡、顶部按钮等复用。
  5. 间距/圆角:页面 gutter 16、卡片圆角 16–24、按钮/chip 全圆、卡片间距 12–16;补充到 app_dimens.dart。

三、底部导航(3 Tab)

  • 改为 Home / Discover / Me 三项,Keys 保留 tab-home、tab-discover、tab-my; 删除 tab-my-list 独立 Tab(原 My List 内容并入 Me 页)。
  • 选中态:橙色图标 + 橙色文案 + 下方 1px 橙色圆点;未选中 white/45。
  • 背景 #0B0C10/95 + 顶部 white/5 分隔 + backdrop blur;高度与安全区按设计。
  • 修正现有 tab 映射:Home 进沉浸 feed,Discover 进发现,Me 进个人页; My List 入口保留在 Me 页内部(History/Watchlist/Liked)。
  • 更新受影响的 widget 测试,不再断言 4 Tab;保留 Home/Discover/Me 的行为断言。

四、首页(Home)

按 01-home.html 重做首页沉浸层(保留 PlayerPage(homeMode: true) 视频 feed):

  1. 顶部:状态栏安全区 + 剧名、HOT 徽章、Season 1 • Vertical Cinema 副标题;右侧搜索/更多 36px 玻璃圆钮。
  2. 右侧互动栏:剧集头像 48px + 橙色 Follow +;Like / Comment / Bookmark / Share, 44–46px 玻璃圆形按钮 + 11px 计数/文案;保留下载/PiP 时收进“更多”菜单,避免超出设计动作集。
  3. 底部玻璃信息卡(26px 圆角、blur 24):
    • 题材 chips(#9278EA/15 与 #FF5B3D/15 半透明底)
    • Ep. x / y pill
    • 2 行简介
    • 1px 进度条(播放段 #FF5B3D)+ 当前/总时长
    • Follow Series 橙色胶囊主 CTA + Episodes 玻璃胶囊
  4. 顶部/右侧/底部渐变 scrim 按设计稿比例;文字加 0 2px 8px rgba(0,0,0,.75) 或置于玻璃 pill。
  5. 复用现有 Player 逻辑(进度、选集、点赞/收藏/评论),不要重写播放链路。

五、发现页(Discover)

按 02-discover.html 重做:

  1. 顶部品牌行(橙点 + DramaPulse/FruitShorts + 副标题)+ 右侧头像入口。
  2. 44px 搜索框(左侧搜索图标、右侧语音占位),接通现有 DiscoverLogic.search 过滤; searchRequest 时聚焦。
  3. 全圆分类 chips,选中橙色实心(未选中 white/8)。
  4. Continue Watching 卡:海报 64×88、Ep. x / y • xx min left、65% 橙色进度、橙色 Play 按钮; 数据用现有观看记录;没有记录时隐藏该卡。
  5. 4 个快捷入口:Filter / Top Charts / New / Watchlist(玻璃卡 + 图标 + 文案)。
  6. Trending Dramas:标题 + See All;2 列网格,海报比例约 3/4.2、16px 圆角; 封面叠加 Free/VIP/HOT 徽章(有数据才显示)、评分(模型无字段则隐藏)、播放量(用 viewCount)。
  7. 移除发现页顶层 3D 轮播(设计稿没有);轮播组件保留但不再挂在 Discover;若删除组件需确认无测试引用。
  8. 标题 12px、副标题 11px,卡片底色 #15161E;与设计一致。

六、测试与门禁

  • 更新受影响的既有测试(4 Tab → 3 Tab;Discover 结构变化),保留播放/进度/收藏等行为断言。
  • 新增测试:3 Tab 映射、玻璃卡渲染、Discover 搜索过滤、2 列网格。
  • dart analyze lib test 零告警;flutter test 全过;flutter build apk --debug 通过。
  • 真机验收由用户执行;不伪造评分/完结/榜单数据。

七、Git 与回执

  • 从干净 develop 拉 feature/flutter-k24-design-system,小步提交;完成后 --no-ff 合并回 develop 并 push; 作者 Zhou Weidong <502333560@qq.com>;不动 main。
  • 计划文档 FruitServer/docs/plans/2026-10-19-K24.md;留痕写 PROGRESS.md。
  • 回执:token/字体落地、3 Tab、Home/Discover 结构、测试条数与门禁原始输出、已知数据缺口、真机验收步骤。

[已处理·待用户验收] 2026-10-19 | K23.1 后台修正:品牌文字、删除按钮红色、布局回归

用户反馈(2026-10-19,附截图):

  1. 「图片的文字没有改过来」——浅色侧边栏里 FruitShorts 仍是蓝色,应改为正常深色品牌字;
  2. 「删除按钮统一用红色背景」——当前多处删除仍是文字/次要样式;
  3. 「有的布局乱了」——K23 后菜单、弹窗或顶栏存在对齐/溢出问题。

只改 ReelShortWeb 视觉/交互;不改接口、后端业务、客户端、main。

一、品牌文字颜色

  • src/store/modules/app.ts:
    • setMenuTheme 浅色分支 logoTitleTextColor: 'inherit' → '#303133'(或 var(--el-text-color-primary));
    • setHeaderTheme 浅色分支 topHeaderTextColor: 'inherit' → '#303133';
    • 深色分支保持 #fff。
  • 检查 Logo.vue、ToolHeader.vue、Breadcrumb.vue,确保浅色菜单/顶栏不再出现蓝色品牌字或蓝色继承文字。
  • 登录页品牌色保持现状。

二、删除按钮统一红色背景

  1. 全仓扫描所有「删除 / 移除 / 删除菜单」按钮,统一为 type="danger" 实心红底、白字, 表格内 size="small";禁止 link、plain、text、class="danger-action" 的浅色文字样式。
  2. 至少覆盖:
    • views/contentManagement/DramaManagementView.vue
    • views/contentManagement/EpisodeManagementView.vue
    • views/contentManagement/BannerManagementView.vue
    • views/contentManagement/CategoryManagementView.vue
    • views/operation/NoticeManagementView.vue
    • views/commerce/ProductManagementView.vue
    • views/governance/ContentGovernanceView.vue(评论删除、敏感词删除)
    • views/userManagement/AdminUsersView.vue
    • views/permission/RoleManagementView.vue(当前 link)
    • views/permission/RoleCategoriesView.vue(补 size="small")
    • views/permission/MenuManagementView.vue(当前 plain)
    • views/creator-center/DramaEditView.vue 与 views/creator-center/WorkspaceView.vue 的删除/下线
    • views/commerce/OrderManagementView.vue、views/creator/CreatorAuditView.vue 的 danger-action 危险操作(退款/回收/冻结等)统一实心红底白字。
  3. 删除确认弹窗的确认按钮也改为红色背景(confirmButtonClass: 'el-button--danger' 并确认样式生效)。
  4. 调整操作列宽度,确保红底按钮不换行、不挤出单元格;同一列按钮高度一致。

三、布局回归修正

逐项用浏览器检查并修复:

  1. 浅色/深色侧边栏:品牌图与文字垂直居中,品牌字不换行、不溢出;折叠/展开正常。
  2. 菜单展开:一级与二级文字左边缘对齐(42px 基线),行高 44/40,无重叠/多余空白;popper 同样。
  3. 短剧编辑弹窗:680px 与 375px 宽度下无横向滚动;封面预览/文件名/操作按钮不挤压; 数字输入两列对齐;底部“取消/保存修改”固定可见。
  4. 锁定屏幕弹窗:440px 下标题/说明/表单/底部按钮不裁切,取消与锁定按钮高度一致。
  5. 表格页:红色删除按钮加入后,操作列不换行、不出现横向溢出;1024 与 375 宽度验证。
  6. 顶栏/标签页/面包屑:图标与文字基线一致,无错位。

四、验收与门禁

  • pnpm lint:eslint 零错误;pnpm build:pro 通过。
  • 浏览器自查:1440×900、1024×768、375×812;浅色/深色;登录页、工作台、短剧管理编辑弹窗、 锁定屏幕、菜单展开;控制台 0 error、0 404;scrollWidth <= clientWidth。
  • 回执附:品牌色改动、删除按钮清单、布局修复项、门禁原始输出、浏览器自查结论。

五、Git

  • 从干净 develop(基线 fe1ae95)拉 feature/web-k23-fixes,小步提交, --no-ff 合并回 develop 并 push;作者 Zhou Weidong <502333560@qq.com>。
  • 计划文档 FruitServer/docs/plans/2026-10-19-K23.1.md;留痕写 PROGRESS.md。
  • 用户负责最终验证;本批不再扩大范围。

[已处理·待用户验收] 2026-10-19 | K23 后台视觉深化:主色、菜单、编辑弹窗与锁屏

用户指令(2026-10-19,附三张截图):「现在着重优化 UI 及交互」。 截图问题:

  1. 左侧菜单子项无图标、缩进/行高/选中态不统一;
  2. 短剧编辑弹窗里封面直接暴露超长 URL,数字输入框布局松散;
  3. 锁定屏幕弹窗标题被分隔线穿过、右上角图标错位、缺少取消入口。

只改 ReelShortWeb 视觉/交互 + FruitServer 菜单图标种子 SQL;不改接口契约、后端业务、客户端、main。

一、主色统一为品牌辣椒红

  • src/styles/business/theme.css 的 --el-color-primary 由 #409eff 改为 #c4322a, 同步 light-3/5/7/8/9、dark-2(参考值:#d3615b / #e19995 / #f0c2c0 / #f5d9d8 / #faeeed / #9d2822)。
  • 按钮、开关、链接、选中菜单、标签页激活态、焦点环统一走 var(--el-color-primary); 检查白字/红底、红字/白底对比度 ≥ 4.5:1;成功/警告/危险语义色保持不变。
  • 登录页品牌色已用 --app-brand,保持不变;全站不再出现默认 Element 蓝。

二、左侧菜单(口径更正:子菜单统一不用图标)

用户 2026-10-19 更正:不是子菜单图标为空,而是子菜单统一不要图标;当前问题是没对齐、视觉不好看。 不要给子菜单加图标,也不要在子菜单预留图标位置。

  1. 子菜单图标一律清空:
    • sys_menu 中所有 type='MENU' 的子菜单 icon 置为空串(含 id 4/5/7/8/9/10/11/12/13/14/16/18/19/21/23/25/27/29/30/31/33);
    • 同步 sql/data.sql 与 sql/data-phase1.sql,并实际执行到开发库;
    • 菜单管理页保存子菜单时也不落图标(或保存后清空),避免以后又出现图标;
    • 渲染层对嵌套菜单项强制不渲染图标(即使库里有历史值也只显示文字)。
  2. 对齐与视觉(新增 src/styles/business/menu.css,作用域 #v-menu):
    • 一级菜单:行高 44px,左侧 padding 16px,图标 18px,图标与文字间距 8px;
    • 二级菜单:行高 40px,文字左边缘与一级菜单文字左边缘对齐(即一级 padding + 图标 18 + 间距 8 = 42px), 不出现“子项文字比父项文字更靠左/更靠右”的错位;
    • 二级菜单不渲染图标,也不保留空白图标位;选中/悬浮背景从文字左边缘开始,圆角 8px;
    • 一级/二级的选中态、悬浮态、展开箭头、折叠态、popper 子菜单保持一致;
    • 深色模式选中态用深主色底 + 白字,浅色模式浅主色底 + 主色文字;行距紧凑,不出现大块空白。

三、短剧编辑弹窗(DramaManagementView.vue)

  1. 弹窗宽 680px,close-on-click-modal=false,内容区可滚动、底部按钮固定; 表单分三块:基础信息(名称/类型/简介)、付费设置(免费集数/单集解锁金币/是否热门)、封面。
  2. 数字输入:el-input-number 固定宽 180、controls-position="right",两列等宽对齐。
  3. 封面编辑器重做:
    • 左侧 2:3 预览(约 96×128),无封面时显示虚线占位;
    • 右侧显示状态 chip(已上传/未上传)、文件名(只显示 basename,不显示完整路径)、 更换封面 / 移除 操作;上传中显示进度遮罩;
    • 不再展示原始 URL 输入框;如需查看,提供“查看原图”链接在新窗口打开;
    • 上传前校验类型(jpg/png/webp)与大小(≤5MB),失败在弹窗内提示并可重试;
    • 移除 只清空本地表单,保存后生效。
  4. 表单加 :model + :rules(名称必填、类型必选),保存时 validate(),错误显示在字段下方; 保留现有保存接口与字段,不改 payload。
  5. 保存按钮 loading/disabled 防重复(已有 saving,补齐禁用与关闭保护)。

四、锁定屏幕弹窗(LockDialog.vue)

  • 宽度 440,max-height 调整到 320–360,去掉全屏按钮(该弹窗不需要全屏)。
  • 重做头部:标题左对齐、无穿过的分隔线、关闭按钮右对齐;标题下加一行说明“输入密码解锁当前会话”。
  • 内容居中:头像 64、姓名 14、密码框 100% 宽;密码框支持回车提交。
  • 底部:取消(关闭)+ 锁定(primary,宽度不小于 96)。
  • 删除 console.log('set: ', val),不改变锁定逻辑。

五、交互与一致性

  • 弹窗统一:标题/内容/底部间距、圆角、按钮高度(36/40)、暗色模式可读。
  • 破坏性操作不新增逻辑;本批只做视觉与上述表单交互。
  • 不修改接口、不新增权限点、不改业务流程。

六、验收与门禁

  • pnpm lint:eslint 零错误;pnpm build:pro 通过。
  • 开发库子菜单 icon 已全部置空;GET /admin/auth/menus 自检:一级菜单有图标,二级菜单 icon 为空。
  • 浏览器自查(不作为用户验收):登录页/工作台/短剧管理/锁定屏幕;1024 与 375 宽度; 暗色/浅色;控制台 0 error、0 404。
  • 用户浏览器验收:菜单图标与行距、红色主色、短剧编辑弹窗封面、锁屏弹窗。

七、Git 与回执

  • ReelShortWeb 从干净 develop(基线 3e8312e)拉 feature/web-k23-admin-polish; FruitServer 只改菜单图标 SQL 种子,单独提交并 push。
  • 完成后各自 --no-ff 合并回 develop 并 push;作者 Zhou Weidong <502333560@qq.com>。
  • 计划文档 FruitServer/docs/plans/2026-10-19-K23.md;留痕写 PROGRESS.md。
  • 回执:主色变量、子菜单图标落库结果、弹窗改动、门禁原始输出、浏览器自查结论。

[已处理·待用户验收] 2026-10-19 | K22.1 后台收尾:登录文案、图标残留与文件清理

用户反馈(2026-10-19,附登录页截图):

  1. 「这个下面的文字可以不要」——登录页标题下方的说明段落与三个标签(短剧与剧集 / 发布状态 / 角色权限)删掉。
  2. 「还有图标没有改过来」——仍有旧品牌图/旧图标来源:浏览器 favicon、首屏 loading 的 /logo.png、mock 路由里的旧图标、在线图标开关。
  3. 「有些文件该删除的删除掉」——删除已确认无引用的文件与 mock 体系。

只改 ReelShortWeb;不改接口契约、后端业务、客户端、main。用户直接在浏览器验收。

一、登录页文案

  • src/views/Login/Login.vue 删除 <div class="hero-copy"> 中的说明段落 <p>{{ t('login.message') }}</p> 与 <ul> 三个标签;保留 hero-kicker 与 h1。
  • 删除随之不再使用的 l10n key(login.message / login.pointDrama / login.pointPublish / login.pointAccess) 与对应样式;若 key 仍被其他页面引用则保留并说明。

二、图标与品牌残留(重点)

  1. favicon:用客户端品牌图替换 public/favicon.ico:
    • 复制 src/assets/imgs/brand-mark.png 为 public/brand-mark.png;
    • index.html 改 <link rel="icon" type="image/png" href="/brand-mark.png" />;
    • 删除 public/favicon.ico。
  2. 首屏 loading:index.html 的 <img src="/logo.png"> 改为 /brand-mark.png,加圆角; 首屏背景改为客户端深色 #100C0A、标题文字浅色,避免闪白。 删除 public/logo.png。
  3. 在线图标开关:.env.base、.env.dev、.env.pro、.env.test 的 VITE_USE_ONLINE_ICON 全部改为 false,由 PurgeIcons 本地打包 Material Symbols,避免在线 API 不可达导致图标空白/旧缓存。 构建后必须确认菜单/顶栏/表格工具栏图标真实渲染。
  4. mock 旧图标:.env.dev、.env.pro、.env.test 的 VITE_USE_MOCK 全部改为 false; 删除整个 mock/ 目录;vite.config.mts 移除 viteMockServe 的 import 与条件配置。 目的:不再把 mock/role/index.mock.ts 里的 ant-design: / ep: 旧图标带进任何构建。

三、文件清理(删除前先全仓 grep 确认零引用)

必须删除:

  • src/assets/imgs/logo.png(已无引用)
  • src/assets/imgs/avatar.jpg(UserInfo 已改读 store,无引用)
  • public/logo.png、public/favicon.ico(被 brand-mark 替代)
  • src/views/permission/CustomMenuView.vue(无路由/菜单/组件引用)
  • src/views/Login/components/RegisterForm.vue(后台无注册接口,假提交)
    • 同时移除 src/views/Login/components/index.ts 的导出、Login.vue 的注册切换状态与 LoginForm.vue 的「还没有账号?注册」入口及 to-register 事件;相关 l10n key 若不再使用一并删除。

可选删除(仅在 grep 证明零引用后):

  • 其他未挂路由、未被 import 的 demo 页面/组件/资源。

禁止删除仍被路由、菜单、组件或构建配置引用的文件;删除清单与 grep 证据写入回执。

四、验收

  • 全仓扫描结果:旧图标前缀 0、<el-icon> 0、@element-plus/icons-vue 0、/logo.png 0、mock 引用 0。
  • pnpm lint:eslint 零错误;pnpm build:pro 通过。
  • 用 pnpm dev(base 模式)打开登录页与后台:
    • 登录 hero 只剩 kicker + 标题;
    • 浏览器标签页图标、首屏 loading、侧边栏品牌图三者一致;
    • 菜单/顶栏/表格工具栏图标全部为 Material Symbols Rounded 且无空白;
    • 控制台无 404(尤其无 /logo.png、mock 请求)。

五、Git 与回执

  • 从干净 develop(ReelShortWeb 当前 5494190)拉 feature/web-k22-cleanup,小步提交, --no-ff 合并回 develop 并 push;作者 Zhou Weidong <502333560@qq.com>。
  • 计划文档 FruitServer/docs/plans/2026-10-19-K22.1.md;留痕写 PROGRESS.md。
  • 回执:删除文件清单 + grep 证据、图标/品牌修复、env 变更、门禁原始输出、浏览器自查结论。

[已处理·待用户验收] 2026-10-19 | K22 后台管理视觉与交互统一(图标对齐客户端)

用户指令(2026-10-19):「先优化后台管理 UI 及交互;后台管理图标统一成客户端的;严格执行 specs 文档规范」。

只改 ReelShortWeb 的视觉/交互 + FruitServer 的菜单图标种子 SQL;不改接口契约、业务逻辑、客户端、main。 用户会直接在浏览器验收结果。

一、必读规范(严格执行)

按顺序读:AGENTS.md → CODING_STANDARDS.md → GIT_WORKFLOW.md → FruitServer/docs/api-phase1.md §2 → docs/plans/codex-研发任务清单.md → 本 INBOX。

Vue/TS 硬约束:沿用 vue-element-plus-admin 结构;eslint 零错误;pnpm build:pro 通过; 新增手写源码首行 // Author: fruit.com.cn;函数 ≤50 行、参数 ≤5、类/文件规模红线、禁止魔法值; 提交作者统一 Zhou Weidong <502333560@qq.com>。

二、图标统一(核心)

目标:后台所有图标统一为客户端同款 Material Symbols Rounded(客户端使用 Material *_rounded), 不再混用 ep:、ant-design:、mdi:、zmdi:、ion:、bi:、radix-icons: 与 Element Plus 图标组件。

  1. 新增 src/constants/icons.ts(语义名 → Iconify 全名)与 src/components/AppIcon:
    • AppIcon 基于现有 Icon 组件,默认 material-symbols:*,统一尺寸/颜色/描边风格;
    • 图标名禁止散落在页面里,统一从 AppIcons 取。
  2. 菜单图标(sys_menu.icon)按以下映射落库,并同步 FruitServer/sql/data.sql 与 sql/data-phase1.sql:
    菜单 id 新图标
    工作台 1 / 2 material-symbols:dashboard-rounded
    内容管理 3 material-symbols:video-library-rounded
    后台管理 6 material-symbols:settings-rounded
    用户管理 15 material-symbols:group-rounded
    创作者管理 17 material-symbols:movie-edit-rounded
    内容治理 20 material-symbols:policy-rounded
    订单与权益 22 material-symbols:payments-rounded
    数据看板 24 material-symbols:monitoring-rounded
    创作者中心 26 material-symbols:video-camera-front-rounded
    运营管理 32 material-symbols:campaign-rounded
  3. 更新完 SQL 后实际执行到开发库(psql -f sql/data.sql、psql -f sql/data-phase1.sql,脚本必须幂等), 并用管理员登录后的 GET /admin/auth/menus 自检返回的 icon 全部为 material-symbols: 前缀。
  4. 菜单管理页图标候选改为上述合法 Iconify 名;保存时统一存全名;旧的短名(Document 等)渲染时做映射兜底。
  5. 顶栏、标签页、表格工具栏、弹窗等共享组件 以及 src/views/** 页面内所有图标 (含 <el-icon><Plus/></el-icon>)统一替换为 AppIcon;移除页面内对 @element-plus/icons-vue 的直接引用。 语义映射至少包含:menu-rounded / menu-open-rounded、fullscreen-rounded / fullscreen-exit-rounded、 format-size-rounded、language-rounded、dark-mode-rounded / light-mode-rounded、 refresh-rounded、close-rounded、chevron-left-rounded / chevron-right-rounded、 search-rounded、restart-alt-rounded、view-column-rounded、help-rounded、add-rounded、 edit-rounded、delete-rounded、upload-rounded、download-rounded、visibility-rounded、 arrow-upward-rounded / arrow-downward-rounded、check-rounded、cancel-rounded。
  6. 品牌图标:把客户端 ReelShort/assets/icon/splash_mark.png 复制为 ReelShortWeb/src/assets/imgs/brand-mark.png,侧边栏(展开/收起)与登录页统一使用该图; 不再使用框架默认 logo 或自绘图标。保留原 logo.png 文件但不再引用。

三、登录页(src/views/Login/**)

  1. 删除默认账号/密码占位提示(admin / admin123),改走 i18n 占位文案。
  2. “记住我”默认关闭;只记住用户名,不把密码写入 localStorage;同步清理 user store 中登录信息的密码字段。
  3. 表单支持回车提交;登录/注册按钮在请求与动态路由生成完成前保持 loading,防止重复提交。
  4. 窄屏(≤960px)压缩品牌装饰区,保证登录卡在首屏完整可见。
  5. 登录页品牌名/副标题与侧边栏共用同一来源(环境变量 + i18n),不再写死 FruitShorts Studio。
  6. 视觉与客户端一致:深色暖底、辣椒红强调只用于按钮/焦点(不整体改后台主题色),品牌 mark 与侧边栏同尺寸同圆角。

四、全局壳与主题

  1. 修复深色模式:菜单/头部浅色主题下的文字对比度、标签页关闭图标、主题开关轨道/边框、 面包屑当前项颜色;所有颜色走 var(--el-*) 或业务 token。
  2. 去掉 body 的 min-width: 980px 强约束,补 1200/768 断点;保证 1024、800、375 宽度下顶栏右侧与内容区不被裁切。
  3. 顶栏、标签页、表格工具栏、弹窗图标全部换成 Material Symbols Rounded;同一语义只用一个图标。
  4. 清理全局 CSS 冲突:
    • 只保留一处 :root 字体声明(--font-display / --font-body);
    • 圆角收敛为 --app-radius-sm/md/lg(4/6/12),登录卡可单独 18;
    • 把硬编码 #409eff !important 改为 var(--el-color-primary);
    • 删除/改名未使用的 .app-shell/.sidebar/.main-area/.topbar/.brand/... 死选择器,避免“改了不生效”。
  5. 用户信息区头像/姓名从 userStore 读取,不再写死 avatar.jpg 与 Archer。
  6. “清除缓存并重置”增加二次确认。

五、明确不做(留给 K23)

业务页的表格/筛选/分页统一、拦截器 reject、删除/上下架二次确认、行内防重复、表单 rules、 权限按钮补齐、上传校验、状态标签/日期格式统一。本批不碰这些业务交互逻辑,避免与视觉批次混在一起。

六、验收与门禁

  • pnpm build:pro 通过;pnpm lint:eslint 零错误(只跑改动文件也可,但最终全量通过)。
  • 开发库菜单 icon 已更新;GET /admin/auth/menus 自检全部为 material-symbols:。
  • 浏览器自查(Codex 自检,不作为用户验收):登录页、侧边栏折叠/展开、顶栏、标签页、 深色/浅色、1024/800/375 宽度;控制台无报错。
  • 用户浏览器验收:图标风格、登录页、全局壳、深色模式、窄屏。

七、Git 与回执

  • ReelShortWeb 从干净 develop(基线 5c087f4)拉 feature/web-k22-admin-visual; FruitServer 只改 SQL 种子,单独提交并 push。
  • 完成后各自 --no-ff 合并回 develop 并 push;不动 main。
  • 计划文档 FruitServer/docs/plans/2026-10-19-K22.md;留痕写 PROGRESS.md。
  • 回执:图标映射与落库结果、登录页/全局壳改动、扫描前后对比、门禁原始输出、浏览器自查结论。

[已处理·待用户验收] 2026-10-19 | K21 登录页 UI 优化

用户指令(2026-10-19):「现在主要做登录页面 ui 优化」。

只改客户端 ReelShort 登录/注册页(lib/pages/auth/**)及其 l10n;不改接口、后端、Web、specs、main。 主题继续使用辣椒红暖色与 K20 的 AppDimens token。

一、现状问题

  • 品牌区是左上角小横排,缺少登录页应有的品牌感;页面整体顶对齐,大屏下方留白过多。
  • 返回按钮独占一行,推高内容且与品牌区挤在一起。
  • 登录/注册共用一句 authSubtitle,注册态缺少针对性说明。
  • 注册态没有密码规则提示(文案已有 authPasswordHint,但只作为输入框 hint)。
  • 本地校验失败只有一个错误胶囊,出错字段没有红框定位。
  • _submit() 没有 busy 短路,密码框 IME 回车可绕过禁用态重复提交;失败分支 setState 缺 mounted 判断。

二、改造项

  1. 布局重排(auth_view.dart)
    • 页面改为「可垂直居中、键盘弹起可滚动」:用 LayoutBuilder + SingleChildScrollView(保留 Key auth-scroll)
      • ConstrainedBox(minHeight: 可视高度 - padding) + 居中内容,最大宽度仍 440。
    • 返回按钮改为顶部浮层(Positioned + SafeArea,Key auth-back 保留),不再占据内容流; 品牌区与卡片整体垂直居中。
    • 保留 auth-background-glow 主光晕(每个颜色 alpha ≤ 0.18),新增一层金色/暖色极淡光晕(alpha ≤ 0.06,独立 Key)。
  2. 品牌区重做(auth_widgets.dart)
    • _AuthBrand 改为居中竖排:品牌图标 + appName(26/w800)+ authBrandTagline(14,blueSoft)。
    • 品牌图标改用真实资源 assets/icon/splash_mark.png(ClipRRect 圆角 14 + 辣椒红柔光阴影), Key auth-brand-mark 必须保留且尺寸仍为 48×48(既有测试断言)。
  3. 卡片与文案
    • 卡片结构保持:模式切换 → 标题(25/w800)→ 副标题 → 字段 → 错误胶囊 → 提交按钮。
    • 副标题按模式区分:登录用现有 authSubtitle;注册新增 authSignUpSubtitle(三语):
      • en: Create an account to keep your list, history, and progress.
      • zh: 创建账号,保存你的片单、观看记录和进度。
      • id: Buat akun untuk menyimpan daftar, riwayat, dan progresmu.
    • 注册态在密码框下增加一行 12px 的 authPasswordHint 辅助说明;登录态不显示。
  4. 字段级错误定位(auth_logic.dart + auth_form_widgets.dart)
    • AuthLogic 增加 AuthFieldError? fieldError;validate() 失败时记录具体字段错误,onChanged 时清除。
    • 邮箱/密码 InputDecoration 增加 errorBorder / focusedErrorBorder(colorScheme.error,宽度 1.5), 出错字段显示红框;错误胶囊 auth-error-capsule / auth-error 及其文案、圆角 12、alpha 0.1 全部保留。
  5. 交互健壮性(auth_view.dart)
    • _submit() 开头 if (_logic.busy) return;;catch 中 setState 前判断 mounted; onSubmitted 在 busy 时忽略。其余提交与导航行为不变。
  6. 视觉细节
    • 输入框聚焦时前缀图标保持 blueSoft(现状),提交按钮增加极轻的辣椒红阴影(不改背景色断言)。
    • 页面底部保留 28 + viewInsets.bottom 的内边距,确保小屏 + 键盘下提交按钮可达。
    • 不引入新依赖、不新增图片资源。

三、硬约束(既有测试不得改断言)

test/auth/auth_visual_test.dart 与 test/auth/auth_page_test.dart 必须原样通过,重点保留:

  • auth-brand、auth-brand-mark 尺寸 48×48、品牌名与 tagline 文案;
  • auth-background-glow 渐变每个颜色 alpha ≤ 0.18;
  • auth-card 圆角 20、边框色 outlineVariantDark、宽度 1;
  • 输入框高度 52、圆角 14、背景 AppColors.bar、聚焦边框 blue / 1.5;
  • 提交按钮高度 52、圆角 26、背景 blue / 按下 bluePressed;
  • auth-enter-animation 时长 ≤220ms、tween 0→1;
  • 模式切换 Key、选中色 blue、标题文案 Welcome back / Create your account;
  • 隐藏兼容文案 New here? Create an account / Already have an account? Sign in 必须仍可被 find.text 找到;
  • 错误胶囊 Key、圆角 12、alpha 0.1、错误图标与语义色;
  • 小屏 320×568 + 键盘 260 时 auth-submit 可滚动到并可点击。

测试失败时改实现,不改测试。 允许新增测试覆盖注册副标题、密码提示与字段红框。

四、门禁

dart analyze lib test 零告警;flutter test 全过;flutter build apk --debug 通过。 真机验收由用户执行,Codex 不做设备侧取证。

五、Git 与回执

  • 从干净 develop(基线 e44fbdf)拉 feature/flutter-k21-auth-ui,小步提交,完成后 --no-ff 合并回 develop 并 push;不动 main;作者统一 Zhou Weidong <502333560@qq.com>。
  • 计划文档 FruitServer/docs/plans/2026-10-19-K21.md;留痕写 PROGRESS.md 并单独提交 FruitServer develop。
  • 回执:布局前后差异、三语文案、保留的 Key/尺寸清单、测试条数与门禁原始输出、真机验收步骤。

六、K21 完成回执

  • 客户端分支:feature/flutter-k21-auth-ui
  • 功能提交:8a96f34
  • 合并回 develop:b1c69e2
  • 已推送:e44fbdf..b1c69e2 develop
  • author/committer:Zhou Weidong <502333560@qq.com>

已完成:居中可滚动布局、顶部浮层返回、图片品牌 hero、模式独立副标题、 注册密码规则提示、字段级红框、busy/mounted 保护、双层光晕和提交阴影。

$ dart analyze lib test
No issues found!

$ flutter test
00:24 +343: All tests passed!

$ flutter build apk --debug
Running Gradle task 'assembleDebug'... 7.5s
✓ Built build/app/outputs/flutter-apk/app-debug.apk

[已处理·待用户验收] 2026-10-19 | K20 客户端视觉统一(辣椒红主题)

用户指令(2026-10-19):「保留辣椒红,先做 K20 视觉统一」。

只改客户端 ReelShort 的视觉与布局;不改业务逻辑、接口调用、后端、Web、specs、main。 主题口径已拍板:保留现有辣椒红暖色(AppColors.blue = #C4322A),不恢复克莱因蓝。

一、目标

消除跨页面视觉不一致与明显不合理的紧凑布局,统一颜色、字号、间距、圆角、导航、卡片、按钮与进度条; 不改任何接口行为,不改既有测试断言。

二、统一 token(新增 lib/core/theme/app_dimens.dart)

  • 页面左右边距:16(详情页 24 → 16;Auth 20 → 16)。
  • 卡片:外层圆角 16、内边距 16;列表内嵌小块圆角 12、内边距 12;胶囊圆角 20。
  • 字号档位:caption 12、body 14、列表标题 15、区块标题 16、页面标题 20(w800); 导航标签 11。禁止再出现 9.5 / 10px 的业务文案。
  • 主按钮最小高度 48(整页主操作 52);点击区不小于 44×44。
  • 主色只用 AppColors;新增 AppColors.favorite(收藏红心)如需保留粉色。

三、改造项(按文件)

  1. 底部导航合一:新增 lib/core/widgets/app_bottom_nav.dart,参数化 tab(图标/文案/选中); 高 64、图标 22、标签 11、选中态用主色胶囊(以现有 home_view.dart 为基准)。 替换 home_view.dart、discover_view.dart、mine_view.dart 三处导航; player_view.dart 的 _fallbackNav 若保留也必须复用该组件。保留现有 Key(tab-home 等)与文案。
  2. 账号入口卡重做:account_entry_card.dart 高度 48 → 64,图标 16 → 18,标签 9.5 → 11, 允许 2 行;四个入口点击区高度 ≥44;保留 mine-account-card、mine-account-profile/devices/export/deactivate 五个 Key。
  3. 字号下限:把 home_drama_grid.dart:146、mine_view.dart:144/648/912、discover_view.dart:369、 home_view.dart:364、comment_tile.dart:114、watch_records_page.dart:282/296、 notices_page.dart:244/260、mine_view.dart:591、account_devices_page.dart:204 提升到 ≥12(导航标签 11)。
  4. 卡片/间距收敛:downloads_page.dart:305(10)、library_drama_tile.dart:26、 notices_page.dart:217、watch_records_page.dart:230、downloads_page.dart:200、 mine_view.dart:614/802 统一到 12;home_drama_grid.dart:104/109、mine_view.dart:377、 account_devices_page.dart:115、account_deactivate_page.dart:93 统一到 16; 卡片内边距 9/14 收敛为 12/16。drama_detail_view.dart:147/233 的 24 → 16。
  5. 颜色收敛:替换 downloads_page.dart:201/222/224/370、watch_records_page.dart:295、 mine_view.dart:143/590/637/717/743/828/835、favorites_page.dart:117、 home_quick_drawer.dart:54/77/118/262、player_view.dart:792/872/879 中的直接 Color(0x...) 为最接近的 AppColors 常量(必要时新增 favorite);account_devices_page.dart:196-203 的 scheme.tertiary 改 AppColors.gold 或 blueSoft;drama_detail_view.dart:169 的 onSurfaceVariant 改 AppColors.muted。
  6. 进度条统一:详情续看 drama_detail_view.dart:366-373、观看记录 watch_records_page.dart:267-272、 下载 downloads_page.dart:357-362 统一为高 4、圆角 4、前景 AppColors.blueSoft、背景 Colors.white24; Player 主拖动条保留金色。
  7. 按钮与对比度:app_theme.dart 增加 filledButtonTheme.minimumSize = Size(0, 48) 与 outlinedButtonTheme(深色前景用 AppColors.blueSoft);mine_view.dart:390-401 去掉 VisualDensity.compact;empty_state.dart:150-158 的重试色由 scheme.primary 改 AppColors.blueSoft。
  8. 标题层级:页面标题统一 20/w800,区块 16/w700,列表 15/w600-700; drama_detail_view.dart:159-164 的 headlineLarge 显式改 24。
  9. 深色唯一口径:app.dart:221-223 改 themeMode: ThemeMode.dark;移除页面级 Theme(data: AppTheme.dark()) 的重复包裹(保留根主题),确保日期选择器、弹窗也是深色。 若短期内删除页面级包裹风险大,可保留包裹,但 themeMode 必须为 dark。
  10. 状态栏与安全区:根 Scaffold 统一 AnnotatedRegion<SystemUiOverlayStyle>(light), appBarTheme.systemOverlayStyle = light、systemNavigationBarColor = AppColors.bar; favorites_page.dart:102、watch_records_page.dart:118、notices_page.dart:118、 downloads_page.dart:96、account_profile_page.dart:108、account_devices_page.dart:93、 account_export_page.dart:89、account_deactivate_page.dart:73 的底部 padding 增加 MediaQuery.paddingOf(context).bottom(或 SafeArea(top: false))。
  11. 详情页返回与封面压暗:drama_detail_view.dart:80-107 的加载/失败分支补带返回按钮的 AppBar; SliverAppBar 封面顶部加黑色渐变,保证返回/更多图标在浅色封面上可读。
  12. 选中态可见性:player_view.dart:834-845 点赞/收藏选中时改实心图标 (Icons.favorite / Icons.bookmark);discover_view.dart:262-296 与 mine_view.dart:537-564 的选中项补 Semantics(selected: true)。
  13. 点击区补足:discover_view.dart 的 _HeaderPill(403-447)、_LoginButton(449-469) 最小高度 44;分类 Chip(262-296)点击区 44(视觉高度可保持 36,用透明 padding 扩大)。
  14. 列表占位对齐:watch_records_page.dart:208-222 占位高度与真实卡片一致(114),并复用相同 padding。

四、明确不做(留给 K21)

搜索 UI、Android 返回键行为、播放器切集票据失败黑屏、登录后继续原操作、下拉刷新、注销状态闭环、 时长/日期格式统一、语言上送后端。本批不碰这些逻辑。

五、约束与门禁

  • 不改既有测试断言;视觉改动导致测试失败时改实现,不改测试(保留所有现有 Key 与文案)。
  • dart analyze lib test 零告警;flutter test 全过;flutter build apk --debug 通过。
  • 代码规模红线、文档注释、// Author: fruit.com.cn 与既有规范不变。
  • 真机验收由用户执行,Codex 不做设备侧取证。

六、Git 与回执

  • 从干净 develop(基线 42ce1d1)拉 feature/flutter-k20-visual,小步提交,完成后 --no-ff 合并回 develop 并 push;不动 main;作者统一 Zhou Weidong <502333560@qq.com>。
  • 计划文档 FruitServer/docs/plans/2026-10-19-K20.md;留痕写 PROGRESS.md 并单独提交 FruitServer develop。
  • 回执:改动清单、统一后的 token 值、测试条数与门禁原始输出、真机验收步骤(逐屏对照色/字/间距)。

七、K20 完成回执

  • 客户端分支:feature/flutter-k20-visual
  • 功能末提交:5f1df40
  • 合并回 develop:e44fbdf
  • 已推送:42ce1d1..e44fbdf develop
  • author/committer:Zhou Weidong <502333560@qq.com>

已完成 14 项改造:统一 token、四处底部导航、账号入口卡 64 高、 字号下限、卡片/间距、颜色、进度条、按钮高度、标题层级、深色主题、 状态栏/安全区、详情返回与封面渐变、选中态、点击区和观看占位高度。

门禁:

$ dart analyze lib test
No issues found!

$ flutter test
00:24 +341: All tests passed!

$ flutter build apk --debug
Running Gradle task 'assembleDebug'... 7.8s
✓ Built build/app/outputs/flutter-apk/app-debug.apk

既有测试要求的两项例外:Auth 卡圆角保持 20;EmptyState 副标题/按钮保持 12.5/13。两者均提取为 token,未修改测试断言。


[已处理·待用户验收] 2026-10-19 | K19 客户端账号中心:资料 / 设备 / 数据导出 / 注销

用户指令(2026-10-19):「先写 K19 任务书并派单」。

只改客户端 ReelShort。后端、ReelShortWeb、specs、main 一律不动。

一、背景与缺口

后端 /app 共 45 个接口,客户端已接 26 个。本轮对接账号中心 6 个(api-phase1.md §1.2,第 157–162 行;实现见 fruitshorts-app-api/.../AppMeController.java):

方法 路径 客户端现状
GET /app/me 未对接
PUT /app/me 未对接
GET /app/me/devices 未对接
GET /app/me/export 未对接
POST /app/me/deactivate 未对接
POST /app/me/deactivate/cancel 未对接

二、接口与字段口径(以后端代码为准确认,勿臆造)

  1. GET /app/me → AppUserRespVO:uid, nickname, avatarUrl, gender, countryCode, regionCode, language, timezone, currency, status, maskedTarget, createdAt, lastLoginAt, vipLevel(Integer), vipExpireAt。vipLevel 是数字,其余时间字段是 LocalDateTime 字符串。
  2. PUT /app/me → 请求 ProfileUpdateReqVO,全部字段可选,只传本次改动的字段: nickname, avatarUrl, gender, birthday(yyyy-MM-dd), language, timezone, regionCode。 nickname 为空串时后端忽略;gender 取值按 schema-phase1.sql 注释用小写 male / female / unknown;birthday 后端只能设置、不能清空。响应同 AppUserRespVO。
  3. GET /app/me/devices → 直接返回 AppUserDevice 实体数组(不是专用 VO): id, userId, deviceId, platform, deviceModel, firstSeenAt, lastSeenAt, status, createdAt, updatedAt, deleted。 按契约 §0,Long 字段以字符串下发。客户端只展示 deviceId/platform/deviceModel/firstSeenAt/lastSeenAt/status, userId、deleted 一律忽略、不落日志、不上屏。
  4. GET /app/me/export → {profile: AppUserRespVO, credentials: [{type, masked, verified}], consents: [...], devices: [...]} (以 AppUserService.exportAccount 实际结构为准)。
  5. POST /app/me/deactivate → 请求 {reason}(password 后端当前不校验,客户端不采集、不上送); 成功返回 scheduledAt(15 天冷静期截止时间,LocalDateTime)。后端同时吊销全部会话。 已有申请时返回业务错误,文案「注销申请已在处理中」。
  6. POST /app/me/deactivate/cancel → 成功 data = null;无待处理申请时错误文案「没有待处理的注销申请」。

契约缺口(本批如实标注,不得自行补后端):

  • GET /app/me 不返回「注销申请中」状态 → UI 不能做状态判断,只能把后端错误文案原样透传; 「撤销注销」入口常驻,无申请时展示后端返回的文案。
  • 无「设备下线 / 踢出」接口 → 设备页只读;TC-ACCOUNT-10 中「单点下线」记为缺口,留待后续批次。
  • 无头像上传接口 → 头像只展示(avatarUrl 为空用现有占位图标),本批不做上传、不做 URL 输入框。

三、客户端改造范围

  1. remote:新增 lib/core/data/remote/account_api.dart,六个方法,路径与请求体严格按上表; 错误一律抛现有 ApiException(禁止吞异常、禁止空 catch)。
  2. models:新增 account_profile.dart、account_device.dart、account_export.dart。 数值/ID 解析用 json_numbers.dart 的 jsonToId / jsonToInt,兼容字符串与数字两种下发形式。
  3. repository:新增 account_repository.dart(接口 + HttpAccountRepository); 在 app.dart 的 Provider 树注册,供页面与测试注入(FakeAccountRepository 放 test/support/)。
  4. 页面(建议 lib/pages/account/,沿用 provider binding/logic/view 三层):
    • 个人资料:进入时 GET /app/me;可改 nickname(去空格后非空、≤30 字符,客户端校验)、 gender(male/female/unknown,本地化文案)、birthday(日期选择,yyyy-MM-dd); uid / maskedTarget / countryCode / currency / status 只读展示;language / timezone / regionCode 只读。 保存调 PUT /app/me,成功后用响应刷新页面,并同步本地登录态(见第 6 条)。
    • 登录设备:GET /app/me/devices,列表展示平台、机型、设备号(脱敏显示,保留首尾各 4 位)、 首次/最近活跃时间、状态;空态用现有 EmptyState;本页只读。
    • 数据导出:GET /app/me/export,展示资料摘要、凭据(type/masked/verified)、授权记录数、设备数; 提供「复制完整 JSON」到剪贴板(Clipboard.setData)+ SnackBar 提示;不写本地文件。
    • 注销账号:15 天冷静期与「申请后立即登出全部设备」的显著提示;可选注销原因(≤200 字); 「申请注销」必须二次确认弹窗 → 成功后展示 scheduledAt 并清空本地会话(服务端已吊销全部会话, 用 AuthController.clear(),不要再调 logout),回到未登录态; 「撤销注销申请」常驻,成功/失败均以后端文案提示。
  5. 入口:lib/pages/mine/mine_view.dart 在已登录态新增「账号」卡(置于钱包卡之后、菜单卡之前): 个人资料 / 登录设备 / 数据导出 / 注销账号,四项带图标与分隔线;未登录不显示。样式沿用现有 _menuCard。
  6. 登录态同步:AuthController 增加公开方法(如 applyProfile(AuthUser)),资料保存成功后更新 内存 session 的 uid/nickname/avatarUrl 并持久化,使「我的」页昵称即时生效;不改令牌与刷新逻辑。
  7. l10n:新增文案必须同时写入 app_en.arb / app_zh.arb / app_id.arb 并重新生成; 印尼语为印尼市场主语言,禁止留英文占位。
  8. 测试(TDD,先红后绿):
    • API/模型:六接口路径与请求体、Long 字符串/数字两种解析、缺字段容错、错误文案透传;
    • logic:资料保存成功后回调登录态、注销成功后清会话、撤销失败展示后端文案、导出复制内容;
    • widget:我的页四个入口仅登录态可见、资料页字段回填与保存、设备空态、注销二次确认、 数据导出页加载/失败/重试。
    • 既有测试不得改断言;如需适配只改注入的 Fake。

四、约束与门禁

  • 代码规模红线:函数 ≤50 行、参数 ≤5、类 ≤300 行、文件 ≤500 行、嵌套 ≤4、圈复杂度 ≤10;禁止魔法值。
  • public 类/方法必须有文档注释,文件首行统一 // Author: fruit.com.cn;禁止裸 then()、空 catch。
  • 门禁(不达标不提交):dart analyze lib test 零告警;flutter test 全过; flutter build apk --debug 构建通过。
  • 真机验证由用户执行,Codex 不得自行做设备侧验证,也不得用本机 curl 充当设备证据。

五、Git 与交接

  • 从干净 develop(基线 7d68699)拉 feature/flutter-k19-account;小步提交, type(scope): 描述(≤50 字符),只 git add 本次文件。
  • 完成后 git checkout develop && git merge --no-ff feature/flutter-k19-account && git push origin develop; 不动 main;提交作者与提交者统一 Zhou Weidong <502333560@qq.com>。
  • 任务书与留痕属于 FruitServer 仓库:docs/plans/PROGRESS.md 追加命令 + 原始输出 + 解读, 单独提交到 FruitServer develop(docs(plans): ...),不夹带客户端代码。

六、回执要求

完成后停住并回执:分支与提交号、六个接口的调用位置、测试条数与结果、dart analyze / flutter test / flutter build apk --debug 原始结论、已知缺口(设备下线 / 头像上传 / 注销状态查询), 以及「用户可照做的真机验证步骤 + 每步预期结果」。

七、K19 完成回执

  • 客户端分支:feature/flutter-k19-account
  • 功能末提交:7151bc9
  • 合并回 develop:9b3ab9d
  • 已推送:7d68699..9b3ab9d develop
  • author/committer:Zhou Weidong <502333560@qq.com>

六个接口调用集中在 ReelShort/lib/core/data/remote/account_api.dart,分别由:

  • AccountProfilePage 调 GET/PUT /app/me
  • AccountDevicesPage 调 GET /app/me/devices
  • AccountExportPage 调 GET /app/me/export
  • AccountDeactivatePage 调 POST /app/me/deactivate 与 /deactivate/cancel

门禁原始结论:

$ dart analyze lib test
No issues found!

$ flutter test
00:26 +335: All tests passed!

$ flutter build apk --debug
Running Gradle task 'assembleDebug'... 10.0s
✓ Built build/app/outputs/flutter-apk/app-debug.apk

已知缺口:

  • GET /app/me 不返回注销申请状态,UI 不判断状态;撤销失败展示后端文案。
  • 没有设备下线接口,登录设备页只读;TC-ACCOUNT-10 的“单点下线”未实现。
  • 没有头像上传接口,头像只展示;空值使用占位图标。
  • 资料响应不含生日,当前只支持设置生日,不能回填或清空。

用户真机验证步骤(设备侧取证由用户执行):

  1. 登录后进入「我的」。 预期:钱包卡之后、菜单卡之前显示「账号」卡,包含个人资料 / 登录设备 / 数据导出 / 注销账号四项。
  2. 打开「个人资料」。 预期:昵称、性别、UID、脱敏账号、国家、币种、状态、语言、时区、地区回填; 修改昵称与性别后保存,返回「我的」后昵称立即更新。
  3. 打开「登录设备」。 预期:设备号脱敏,展示平台、机型、首次/最近活跃时间和状态;无设备时显示空态。
  4. 打开「数据导出」。 预期:展示资料摘要、凭据、授权记录数和设备数;点「复制完整 JSON」出现成功提示。
  5. 打开「注销账号」。 预期:显示 15 天冷静期与立即登出提示;输入原因后必须二次确认。
  6. 确认注销并重新打开 App。 预期:成功页展示预计注销时间,本地会话已清空,App 回到未登录态。
  7. 重新登录后再次进入「注销账号」,点击「撤销注销申请」。 预期:有申请时显示撤销成功;无申请时原样展示后端“没有待处理的注销申请”。

[已处理·待用户验收] 2026-10-17 | K16 注册限流改为可配置(支持不限制)

用户指令(2026-10-17):「注册 429 限流做成可配置的,不限制或限制数量」。

只改后端注册风控配置化,不改注册业务逻辑与接口形态。

一、现状(实测)

  • fruitshorts-module-user/.../AppAuthService.java:305-307:常量硬编码 MAX_REGISTER_PER_IP_PER_DAY = 5、MAX_REGISTER_PER_DEVICE_PER_DAY = 3。
  • guardRegisterRisk(...) 在数值达到阈值时抛 429;无注解注入,无法配置。
  • fruitshorts-app-api/src/main/resources/application.yml 已有 fruitshorts.interaction.* 等配置段。

二、本批范围(后台可配置,走 sys_config)

  1. 读取方式:AppAuthService 注入 ConfigService,读取:
    • app.register.max-per-ip-per-day
    • app.register.max-per-device-per-day
    • 值 ≤0 = 不限制(跳过对应 guard);键缺失或非数字时回退默认 5 / 3;错误码与文案不变。
  2. 种子配置:sql/data-phase1.sql 增加两键的幂等种子(INSERT ... ON CONFLICT (config_key) DO NOTHING):
    • 开发默认值 0(不限制),remark 写明「0=不限制;生产建议 IP 5 / 设备 3」。
  3. 后台配置:无需新建页面——已有 ReelShortWeb/src/views/systemManagement/SettingsView.vue 使用 /admin/configs;只需确认菜单可进入「系统配置」并能看到这两个键。
  4. 契约说明:docs/api-phase1.md §1.1 注册风控段补充:两键可在后台「系统配置」修改、0=不限制、缺省回退 5/3。
  5. 测试:ConfigService 注入 mock 覆盖 0 / 缺省 / 自定义三种;mvn test 全绿。
  6. 打包并重启 8081:
    • mvn -pl fruitshorts-app-api -am package -DskipTests;
    • 停旧 8081 进程并使用新 jar 启动,/healthz 通过;
    • 验证:确认库中两键为 0 后,用新设备号连续注册 ≥4 次不再 429(接口自检,不作为设备侧证据)。
  7. 不使用 application.yml 固化限流值(已下发口径更正)。

三、约束

  • 不改客户端;不改注册接口路径与返回结构;不动 main。
  • 提交作者与提交者统一 Zhou Weidong <502333560@qq.com>;从干净 develop 拉 feature/backend-register-rate-config,完成后 git merge --no-ff 回 develop 并 push。
  • 留痕写入 FruitServer/docs/plans/PROGRESS.md。

四、K16 首版回执(历史,已被返工回执替代)

结论:注册 IP/设备限流已改为读取后台 sys_config,开发两键为 0/0, 支持 0=不限制;后台系统设置页可修改。注册接口与返回结构未变。

  • 最终后端实现 f1aaaee,缺省回退测试 f4003ed,最终合并 5b7be2e,已推送 78fb995..5b7be2e develop。
  • AppAuthService 注入 ConfigService,读取 app.register.max-per-ip-per-day 和 app.register.max-per-device-per-day;非法/缺失回退 5/3。
  • sql/data-phase1.sql 已增加幂等种子(0/0);当前开发库已写入并确认 ID 3/4。
  • 新增 Web 提交 54db082,合并 5c087f4;系统设置页可编辑两项, pnpm build:pro 通过。
  • AppAuthServiceTest 18 项通过,全量 mvn test BUILD SUCCESS。
  • 新 jar 已启动,/healthz 返回 200;当前 8081 PID 80642。
  • 同一新设备号连续注册 4 次均 HTTP 200、code=0,不再出现 429。

命令、日志和服务自检原始输出见 docs/plans/PROGRESS.md「32. K16 注册限流后台配置化」。

五、K16 返工回执(2026-10-17)

结论:已按最新口径改为“后台 sys_config 优先,@Value fallback”, 后台「系统配置」可按 IP/设备修改;开发库两键均为 0。

  • 返工提交 35d92c0 fix(backend): 后台注册限流配置优先并保留注入兜底。
  • AppAuthService 读取 app.register.max-per-ip-per-day / app.register.max-per-device-per-day;缺失或非数字时回退 fruitshorts.auth.register-max-* 的 @Value 注入值。
  • sql/data-phase1.sql 幂等种子已执行;查询结果为 IP 0、设备 0。
  • /admin/configs 实测返回两键,现有系统设置页可修改。
  • 临时把 IP 上限改为 2:前两次注册成功,第三次返回 code=429; 恢复 0 后再次注册成功。
  • AppAuthServiceTest 18 项通过,全量 mvn test BUILD SUCCESS。
  • 新 jar 已重启,8081 PID 94657,/healthz 返回 HTTP 200。

原始输出与解读见 docs/plans/PROGRESS.md「33. K16 返工」。


[待处理] 2026-10-17 | K14 客户端文档:README + 任务清单 + 文件说明

用户指令(2026-10-17):「现在更新客户端 readme 任务清单 及文件说明」。

只改 ReelShort 仓库文档,不改代码、不改 specs、不改后端/Web。 内容必须基于当前实际代码与已交付事实,禁止编造;不写任何密钥/账号。

一、现状(实测)

  • ReelShort/README.md 仅 50 行,是原型阶段说明,已明显过时:
    • 仍写“本地演示评论”“互动数据保存在本机”,实际 K3 已接后端评论/点赞/举报;
    • 未提分类、通知、下载授权、克莱因蓝主题、印尼语默认、运行时服务器地址设置;
    • 未写门禁命令、iOS SPM 兼容注意事项、USB 调试方式。
  • ReelShort 无 docs/ 目录,没有任务清单与文件说明。
  • lib 现有 105 个 Dart 文件,目录:core/{config,data/{models,remote,repositories,services},formatting,platform,theme,widgets}、l10n、pages/{auth,discover,downloads,drama_detail,favorites,home,interaction,mine,notices,player,watch_records}。

二、本批范围(3 份文档)

K14-A 重写 ReelShort/README.md

至少包含:

  1. 项目定位:面向印尼市场的短剧 App(Flutter)。
  2. 技术栈:Flutter 3.47 / Dart 3.13、Provider、dio、video_player、shared_preferences、flutter_localizations(en/zh/id)。
  3. 目录结构概览(core 三层 + pages + l10n + test)。
  4. 已接后端接口清单(按契约分组,以代码实际调用为准):账号、首页/发现/分类/详情、播放、观看记录/收藏、下载授权 8 接口、互动(评论/点赞/举报)、公告。
  5. 运行方式:
    • Android USB:adb reverse tcp:8081 tcp:8081 + flutter run -d <device> --dart-define=API_BASE_URL=http://127.0.0.1:8081;
    • iOS:flutter run -d <udid> --dart-define=API_BASE_URL=http://<MacIP>:8081;
    • debug 下可在「我的 → 设置 → 服务器地址」运行时修改(K13),换网络不必重编。
  6. 环境注意:
    • Xcode 16.2 / Swift 6.0.3 与 shared_preferences_foundation 2.5.6 的固定原因(2.6.0 需 Swift 6.1);
    • iOS SPM 报 Missing package product 时的恢复步骤(清 DerivedData、flutter pub get、xcodebuild -resolvePackageDependencies);
    • Android libdartjni.so 加载失败时卸载重装。
  7. 门禁命令:dart analyze lib test、flutter test、flutter build apk --debug;测试命名约定。
  8. 当前边界:下载不落盘/不续传/不离线播放;无搜索接口;通知无已读态。

K14-B 新增 ReelShort/docs/TASKS.md

表格形式,列:批次 / 范围 / 状态 / 关键提交或契约章节 / 备注。至少覆盖:

  • K1 详情+播放+进度+收藏;K2 分类/记录/收藏页/下载授权;K3 评论/点赞/举报/分享;K5 克莱因蓝主题+印尼语;K6 登录页+底部导航+震动;K7 轮播+站内通知;K8 登录页视觉;K9 首页网格;K10 空态;K11 3 列+全部首位;K12 去标题+空态重做;K13 运行时服务器地址。
  • 待办:K4 账号剩余(Google 登录需凭据)、B1 支付/金币、搜索(契约缺失)、下载落盘(另立批次)。
  • 状态以 develop 实际合并情况为准,不确定的标注「需核对」,不要编造提交号。

K14-C 新增 ReelShort/docs/FILES.md

  • 目录树(到两级),每目录一句话职责。
  • 关键文件表:lib/core/config/app_config.dart、server_endpoint(如已实现)、core/data/remote/*、repositories/*、services/*、core/theme/*、core/widgets/empty_state.dart、pages/*/、l10n/*.arb。
  • test/ 结构说明(按模块目录)。
  • 只写实际存在的文件;生成物(*.g.dart、app_localizations*.dart)标注为生成、勿手改。

三、约束

  • 中文书写,简洁准确;不使用 AI 腔(“赋能/一站式/旨在”等)。
  • 不泄露任何密钥、账号、服务器凭据。
  • 只新增/修改 README.md、docs/TASKS.md、docs/FILES.md;不删既有内容以外的文件。
  • 提交作者与提交者统一 Zhou Weidong <502333560@qq.com>。

四、门禁与 Git

  • 文档改动仍需 dart analyze lib test 与 flutter test 通过(确认未误改代码)。
  • 从干净 develop 拉 docs/k14-client-docs;完成后 git merge --no-ff 回 develop 并 push;不动 main。
  • 在 FruitServer/docs/plans/PROGRESS.md 追加「叙述 + 命令 + 原始输出 + 解读」,完成后停住写回执。

五、K14 完成回执(2026-10-17)

结论:客户端 README 已重写,并新增任务清单与文件说明。只改 ReelShort 的 三份文档,未改代码、specs、后端或 Web。

  • 文档分支 docs/k14-client-docs 从 f95325d 完成并 --no-ff 合并; 主提交 5a28845,接口统计修正 213c86e,最终合并 9fe62d1,已推送 f95325d..9fe62d1 develop。
  • README.md 已覆盖印尼市场定位、Flutter 3.47.7/Dart 3.13.5 技术栈、 实际调用的 26 个 /app/** 路径(31 个方法级调用)、Android USB/iOS/运行时地址、iOS SPM、 shared_preferences 固定、Android libdartjni、门禁和当前边界。
  • 新增 docs/TASKS.md:K1-K13 状态表、K4/支付/搜索/下载落盘待办; 未写不确定提交号,未确认项标「需核对」。
  • 新增 docs/FILES.md:目录树、关键文件职责、test 结构和生成物说明。
  • 仓库 .gitignore 排除了 /docs/,本次仅对 TASKS/FILES 使用 git add -f,没有修改 .gitignore。
  • dart analyze lib test 零告警;flutter test 为 00:20 +309 ~1: All tests passed!;本轮仅文档,不需要 APK 构建。

原始命令、输出与内容核对见 docs/plans/PROGRESS.md 「30. K14 客户端文档」。


[待处理] 2026-10-17 | K13 运行时服务器地址设置(仅 debug)

用户反馈(2026-10-17,已重复 3 次):每次 Codex 门禁构建出的 APK 不带 --dart-define,App 就回退到 http://jiangdeMacBook-Air.local:8081,真机不可用。

这是开发效率问题,不是业务功能。只做 debug 工具,不改 release 行为、不改接口。

一、现状(实测)

  • lib/core/config/app_config.dart:apiBaseUrl = dart-define(API_BASE_URL) 非空 ? 它 : 默认值;默认值在 iOS/Android 真机上是 http://jiangdeMacBook-Air.local:8081(常量)。
  • 构建参数由 --dart-define 决定,编译期固化;Xcode/门禁构建不带参数时必然回退。
  • 客户端已有 SharedPreferencesAsync 使用先例(LocaleController)。

二、本批范围

  1. 新增运行时配置 lib/core/config/server_endpoint.dart(命名可调整):
    • 读取 SharedPreferencesAsync 的 dev.api_base_url;
    • 优先级:运行时设置 > --dart-define(API_BASE_URL) > 现有默认值;
    • 启动时在 main() 里 await 加载完成后再创建 ApiClient(若现有结构不允许,可在 AppConfig 初始化时同步设置);
    • URL 规范化:去首尾空格、去末尾 /;仅接受 http:// 或 https:// 开头,非法值忽略并保留原值。
  2. 设置入口(仅 kDebugMode):
    • 位置:我的页 → 设置(现有 showLanguage 弹窗内增加「服务器地址」一行),或登录页底部一行小字入口;二选一,保持克制;
    • 交互:弹窗显示当前实际 baseUrl,可编辑并保存;保存后提示「重启 App 生效」(文案走 l10n,三语);
    • 提供「恢复默认」操作,清除 dev.api_base_url。
  3. release 行为不变:kDebugMode == false 时完全不显示入口,也不读取本地覆盖(仍用编译期配置)。
  4. 测试:
    • 优先级:本地设置 > dart-define > 默认;
    • URL 规范化与非法输入拒绝;
    • debug 下入口可见、release 下不可见(可用 debugShowSetting 之类的可注入开关测试);
    • 不得删改既有测试致绿。

三、明确不做

  • 不做多环境切换 UI(dev/staging/prod 列表)、不做远程配置下发。
  • 不改后端、不改接口、不改其他页面业务逻辑。
  • 不在 release 包暴露任何服务器地址修改能力。

四、门禁与 Git

  • dart analyze lib test 零告警;flutter test 全过;flutter build apk --debug 通过。
  • 从干净 develop 拉 feature/flutter-k13-dev-server-setting;完成后 git merge --no-ff 回 develop 并 push;不动 main。
  • 先写 FruitServer/docs/plans/2026-10-17-K13.md;每步「叙述 + 命令 + 原始输出 + 解读」追加到 PROGRESS.md;完成后停住写回执。
  • 提交作者与提交者统一 Zhou Weidong <502333560@qq.com>。

五、K13 完成回执(2026-10-17)

结论:新增 debug-only 运行时服务器地址覆盖。启动优先级为本地设置 > --dart-define(API_BASE_URL) > 现有默认;release 不读取本地覆盖且不显示入口。 未改接口、后端或其他页面业务逻辑;未跑真机。

  • 客户端分支 feature/flutter-k13-dev-server-setting 从 b762289 完成并 --no-ff 合并;功能提交 907562a,合并提交 f95325d,已推送 b762289..f95325d develop。
  • 新增 ServerEndpoint,使用 SharedPreferencesAsync key dev.api_base_url;URL 会 trim、校验 http(s) 和 host,并去掉末尾 /。
  • main() 在 runApp 前 debug 加载覆盖;ApiClient 创建时读取已解析地址。
  • 我的页设置弹窗新增服务器地址行、编辑/保存、非法值提示、重启生效提示和 恢复默认;入口可通过 debugShowSetting 注入,默认 kDebugMode。
  • 新增三语文案;既有语言设置流程保留。
  • dart analyze lib test 零告警;flutter test 为 00:19 +309 ~1: All tests passed!;不带 dart-define 的 Android debug APK 在隔离副本中构建成功。

用户真机验证步骤与逐步预期、原始输出见 docs/plans/PROGRESS.md「29. K13 运行时服务器地址设置」。 设备侧修改地址、重启和网络连通性验收由用户执行。


[待处理] 2026-10-17 | K12 首页去标题 + 空态视觉重做

用户指令(2026-10-17):「有了全部分类后 全部剧目不要显示了 而且现在空状态UI太丑了」。

在 K9/K10/K11 基础上做视觉修订,不改接口、数据与业务逻辑。

一、现状(实测)

  • 首页 lib/pages/discover/discover_view.dart:分类 chips 下方有 Text(l10n.allDramas)(Key('home-all-dramas-title')),再往下才是 _HomeDramaGrid。
  • 空态组件 lib/core/widgets/empty_state.dart:96×96 圆角 28 实心方块 + 42px 图标 + 16px 标题 + 13px 副标题 + TextButton。用户反馈“太丑”。

二、本批范围

K12-A 去掉首页「全部剧目」标题

  • 删除 Key('home-all-dramas-title') 的 Text 及其下间距;分类 chips 与网格间距调整为 12–14px。
  • 网格、数据、空态逻辑不变;相关测试中“标题存在”的断言改为“不存在”,不得删测试文件。

K12-B 空态视觉重做(empty_state.dart)

目标:轻盈、精致、留白多,去掉“大色块 + 图标”的模板感。

图形结构改为双层同心圆:

  • 外层圆:直径 84(compact 60),填充 blue 透明度 0.05–0.06,并加 1px 描边 blue 透明度 0.10–0.12;
  • 内层圆:直径 56(compact 40),填充 blue 透明度 0.10–0.12;
  • 中央图标:26–28px(compact 20–22),颜色深色主题 blueSoft、浅色主题 blue;
  • 外层与内层同心居中,用 Stack 或两层 Container 嵌套实现。

文字与间距:

  • 标题 15px / w600 / onSurface,与图形间距 14(compact 10);
  • 副标题 12.5px / muted / 行高 1.45,与标题间距 6;
  • 操作按钮:保留 TextButton,字号 13、主色、点击区 ≥44pt,与副标题间距 8;
  • 整体 Padding 上下 28(compact 16),最大宽度 260;
  • 不新增图片资源、不加动画。

K12-C 测试

  • 更新 empty_state 相关断言:存在 empty-state-art 与内层圆(可用新 Key empty-state-art-inner)、图标尺寸、标题/副标题/按钮;
  • 首页测试:home-all-dramas-title 不存在;chips 后直接出现 home-all-dramas-grid;
  • 既有测试不得删改致绿。

三、门禁与 Git

  • dart analyze lib test 零告警;flutter test 全过;flutter build apk --debug 通过(构建前确认无并发 flutter run/build)。
  • 从干净 develop 拉 feature/flutter-k12-home-empty-polish;完成后 git merge --no-ff 回 develop 并 push;不动 main。
  • 先写 FruitServer/docs/plans/2026-10-17-K12.md;每步「叙述 + 命令 + 原始输出 + 解读」追加到 PROGRESS.md;完成后停住写回执。
  • 提交作者与提交者统一 Zhou Weidong <502333560@qq.com>。

五、K12 完成回执(2026-10-17)

结论:首页「全部剧目」标题已删除,chips 与网格间距为 14px;空态改为 双层同心圆视觉。未改接口、数据、轮播、三态或其他页面;未跑真机。

  • 客户端分支 feature/flutter-k12-home-empty-polish 从 b5a8656 完成并 --no-ff 合并;功能提交 2dd4627,合并提交 b762289,已推送 b5a8656..b762289 develop。
  • 外层圆 84(compact 60),blue 5.5% + 11% 描边;内层圆 56(compact 40), blue 11%;图标 27(compact 21)。
  • 标题 15/w600、副标题 12.5、按钮 13;上下 padding 28(compact 16), 最大宽 260。
  • 新增 empty-state-art-inner、empty-state-padding Key;外层 empty-state-art 及图标/标题/按钮 Key 保留。
  • 首页删除 home-all-dramas-title,chips 后直接进入网格;既有壳层中的 All Dramas 锚点改为稳定网格 Key,测试流程和用例均保留。
  • dart analyze lib test 零告警;flutter test 为 00:17 +302 ~1: All tests passed!;flutter build apk --debug 成功。

用户真机验证步骤与逐步预期、原始输出见 docs/plans/PROGRESS.md「28. K12 首页去标题与空态重做」。 设备侧视觉、深浅主题与小屏验收由用户执行。


[待处理] 2026-10-17 | K11 首页网格 3 列 + 分类首位「全部」

用户指令(2026-10-17):「现在改首页,一行显示 3 个,而且我要的全部是显示在分类的第一个」。

在 K9 已实现的 2 列网格基础上做最小修订,不改接口、不改业务逻辑、不动后端。

〇、开工前必读

  1. K9 计划与实现(lib/pages/discover/widgets/home_drama_grid.dart、discover_view.dart)
  2. K5 主题 token
  3. 本 INBOX 条目

一、PM 已勘察的现状(实测)

  • lib/pages/discover/widgets/home_drama_grid.dart:SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, mainAxisSpacing: 16, crossAxisSpacing: 12, childAspectRatio: 2/3)。
  • lib/pages/discover/discover_view.dart:273:分类 chips 第一项为 (code: CategoryController.allCode, label: l10n.forYou)(文案「为你推荐」)。
  • 其余(轮播、空态、三态)保持 K9/K10 现状。

二、本批范围

  1. 网格改 3 列:crossAxisCount: 3;mainAxisSpacing: 14、crossAxisSpacing: 10,childAspectRatio 保持 2/3;3 列下标题最多 2 行、分类 1 行,避免溢出。
  2. 分类首位改「全部」:chips 第一项仍使用 allCode,文案改为新增 l10n all(中文「全部」/ English「All」/ Bahasa Indonesia「Semua」);保留「热门 / 最新 / 服务端分类」顺序不变。
  3. 同步测试:
    • 更新/新增断言 crossAxisCount == 3;
    • 断言分类 chips 第一项文案为「全部」(按 locale);
    • 既有 K9/K10 测试不得删改致绿。
  4. 不做:不改轮播、空态、三态、数据源、接口、其他页面;不新增分页。

三、门禁与 Git

  • dart analyze lib test 零告警;flutter test 全过;flutter build apk --debug 通过(当前有 Android 真机 flutter run 在跑,构建前先确认没有并发构建占用 build/)。
  • 从干净 develop 拉 feature/flutter-k11-home-grid-3col;完成后 git merge --no-ff 回 develop 并 push;不动 main。
  • 先写 FruitServer/docs/plans/2026-10-17-K11.md;每步「叙述 + 命令 + 原始输出 + 解读」追加到 PROGRESS.md;完成后停住写回执。
  • 提交作者与提交者统一 Zhou Weidong <502333560@qq.com>。

五、K11 完成回执(2026-10-17)

结论:首页网格已改为 3 列,分类首项仍绑定 CategoryController.allCode, 文案改为「全部 / All / Semua」。未改轮播、空态、三态、数据源、接口或其他页面; 未跑真机。

  • 客户端分支 feature/flutter-k11-home-grid-3col 从 0466b4b 完成并 --no-ff 合并;功能提交 f0db631,合并提交 b5a8656,已推送 0466b4b..b5a8656 develop。
  • crossAxisCount: 3、mainAxisSpacing: 14、crossAxisSpacing: 10, childAspectRatio: 2 / 3 保持不变。
  • 三份 ARB 原本已有 all key,分别为 All / 全部 / Semua,本轮直接复用, 未插入重复 JSON key。
  • K9 网格测试更新为 3 列并断言「全部」位于「热门」之前;两个既有首页壳层 用例只把旧分类文案断言改为「All/全部」,导航流程和用例均保留。
  • dart analyze lib test 零告警;flutter test 为 00:18 +302 ~1: All tests passed!;Android debug APK 在独立副本中构建成功。

用户真机验证步骤与逐步预期、原始输出见 docs/plans/PROGRESS.md「27. K11 首页 3 列与全部分类」。 设备侧 3 列布局、分类切换与详情跳转验收由用户执行。


[待处理] 2026-10-17 | K10 统一空列表占位(EmptyState)

用户指令(2026-10-17):「做空列表的占位图,所有的列表,要求简单大方、符合审美」。

依赖 K5 主题 token;不引入图片资源(用内置 Icons + 柔和圆形底),保证离线、体积与风格统一。 只改空态呈现,不改列表数据、接口与业务逻辑。

〇、开工前必读

  1. AGENTS.md、CODING_STANDARDS.md、GIT_WORKFLOW.md
  2. K5 主题 token(AppColors / ColorScheme)
  3. 本 INBOX 条目

一、PM 已勘察的现状(实测,10 处空态)

页面/组件 代码位置 现有文案
首页 lib/pages/home/home_view.dart:56 noStories
首页剧目网格 lib/pages/discover/widgets/home_drama_grid.dart:52-53 noCategoryDramas / noSearchResults
收藏 lib/pages/favorites/favorites_page.dart:107 noFavorites
通知 lib/pages/notices/notices_page.dart:115 noNotices
详情剧集 lib/pages/drama_detail/drama_detail_view.dart:130 noEpisodesYet
我的-片单 lib/pages/mine/mine_view.dart:676 noSavedShows
我的-剧目 lib/pages/mine/mine_view.dart:736 noStories
下载 lib/pages/downloads/downloads_page.dart:151 noDownloads
观看记录 lib/pages/watch_records/watch_records_page.dart:97 noWatchRecords
评论弹窗 lib/pages/player/widgets/drama_comments_sheet.dart:225 noComments

现状均为纯文字(部分带按钮),风格不统一。

二、本批范围

K10-A 新建统一组件 lib/core/widgets/empty_state.dart

规格(简单大方):

  • 布局:垂直居中,Padding 24;默认最大宽度 280,避免宽屏拉伸。
  • 图形占位:96×96 圆角 28 的容器,背景 AppColors.blue 透明度 0.10–0.12;中央 Icons 40–44px,颜色 AppColors.blueSoft(深色主题)/ AppColors.blue(浅色主题按 Theme.brightness 取)。
  • 标题:15–16px、w600、onSurface;居中,最多 2 行。
  • 副标题(可选):13px、muted、居中,最多 2 行。
  • 操作按钮(可选):TextButton/OutlinedButton,主色文字,最小点击区域 44pt;用于「重试」「去发现」等。
  • compact 变体:图形 64×64、图标 28、字号缩小,用于评论弹窗等内嵌区域。
  • 语义:外层 Semantics(label: title);纯装饰图形 excludeFromSemantics。
  • 参数建议:icon、title、subtitle?、actionLabel?、onAction?、compact=false、key。

K10-B 替换 10 处空态

  • 按上表逐一替换为 EmptyState,图标对应语义:
    • 首页/剧目:movie_outlined;分类:grid_view_rounded;搜索:search_off_rounded;
    • 收藏:favorite_border_rounded;下载:download_outlined;记录:history_rounded;
    • 通知:notifications_none_rounded;评论:chat_bubble_outline_rounded;剧集:video_library_outlined;
    • 首页加载失败仍用错误态(error_outline_rounded),不要用空态冒充失败。
  • 保留现有空态文案 key;如需副标题,优先复用已有 l10n,新增文案必须 en/zh/id 三份齐全(例如通用 emptyHint)。
  • 保留现有按钮行为(重试/去发现等);评论弹窗使用 compact。
  • 空态出现在原有滚动容器内时,保持可滚动与居中,不破坏三态切换。

K10-C 测试

  • 组件单测:标题/副标题/按钮渲染、compact 尺寸、图标存在、Semantics。
  • 页面测试:至少覆盖 收藏/下载/记录/通知/评论 五处空态渲染为 EmptyState(可通过 find.byType(EmptyState) + 文案);既有测试不得删改致绿。
  • 深色/浅色、小屏不溢出。

三、明确不做

  • 不引入图片/SVG/字体资源;不做动画插画。
  • 不改列表数据、接口、三态逻辑、页面结构。
  • 不改错误态与加载态,只统一空态视觉。

四、门禁与 Git

  • dart analyze lib test 零告警;flutter test 全过;flutter build apk --debug 通过。
  • 从干净 develop 拉 feature/flutter-k10-empty-states;完成后 git merge --no-ff 回 develop 并 push;不动 main。
  • 先写 FruitServer/docs/plans/2026-10-17-K10.md;每步「叙述 + 命令 + 原始输出 + 解读」追加到 PROGRESS.md;完成后停住写回执。
  • 提交作者与提交者统一 Zhou Weidong <502333560@qq.com>。

五、K10 完成回执(2026-10-17)

结论:新增统一 EmptyState 组件,任务书表格中的 10 处空态已全部替换; 错误/加载态保持原逻辑,未引入图片、SVG、字体或接口改动。未跑真机。

  • 客户端分支 feature/flutter-k10-empty-states 从 19babb8 完成并 --no-ff 合并;功能提交 6d08330,合并提交 0466b4b,已推送 19babb8..0466b4b develop。
  • 默认图形 96x96、圆角 28,blue 11% 背景,图标 42;compact 为 64x64、 图标 28;标题 16/w600,副标题 13,操作按钮至少 44pt。
  • 深色主题图标 blueSoft,浅色主题图标 blue;外层 Semantics 只读标题, 图形和文本不重复朗读,操作按钮保持可访问。
  • 10 处替换完成:首页、首页分类/搜索、收藏、通知、详情剧集、我的两处、 下载、观看记录、评论 compact。
  • 新增三语 emptyHint;原有空态 l10n key、页面文案和操作行为保留。
  • 新增 test/empty_state/empty_state_test.dart 4 个组件用例;收藏、下载、 记录、通知、评论五处页面测试已断言 EmptyState,评论还校验 compact。
  • dart analyze lib test 零告警;flutter test 为 00:18 +302 ~1: All tests passed!;Android debug APK 在独立副本中构建成功。

用户真机验证步骤与逐步预期、原始输出见 docs/plans/PROGRESS.md「26. K10 统一空列表占位」。 设备侧深色/浅色、小屏与各页面空态验收由用户执行。


[待处理] 2026-10-17 | K9 首页剧目列表改造(2 列全部剧目)

用户指令(2026-10-17):「优化首页,要有全部剧目的列表,一行 2 个,不用显示继续观看的」。

只改首页(发现页)信息流布局与卡片,不改接口、不改业务逻辑、不动后端。 依赖 K5 主题 token 与现有 RemoteImage / 卡片视觉。

〇、开工前必读

  1. AGENTS.md、CODING_STANDARDS.md、GIT_WORKFLOW.md
  2. 当前首页 lib/pages/discover/discover_view.dart(649 行)
  3. K5 主题 token、K7 轮播实现
  4. 本 INBOX 条目

一、PM 已勘察的现状(实测)

  • 首页结构(discover_view.dart):顶部 header → 轮播(home feed banners)→ 分类 chips(为你推荐/热门/最新/服务端分类)→「继续观看」标题 + 横向卡片列表(高 196、卡片宽 132,_dramaContent / _continueHeader / _continueTile)。
  • 数据源:CategoryController.visibleDramas(当前选中分类的剧目);_cardsFor(...) 生成展示卡片;分类切换已有过滤逻辑。
  • 「继续观看」区块使用 l10n continueWatching;横向列表不展示播放进度,本质是分类剧目横滑。

二、本批范围(首页单页改造)

  1. 移除「继续观看」区块:删除 _continueHeader 调用与其标题;_continueTile 若不再被使用则一并删除(不留死代码)。
  2. 新增「全部剧目」2 列网格:
    • 位置:分类 chips 下方,占据原「继续观看」位置;
    • 标题:新增 l10n allDramas(中文「全部剧目」/ English「All Dramas」/ Bahasa Indonesia「Semua Drama」);
    • 网格:每行 2 个,左右边距 16、列间距 12、行间距 16;封面竖版比例 2:3,圆角 14;
    • 卡片内容:封面、标题(最多 2 行)、分类/类型(最多 1 行),沿用现有深色渐变与文字层级;
    • 数据:当前选中分类下的全部剧目(沿用 visibleDramas,去重后全量渲染);「为你推荐」显示全部;
    • 点击:沿用 logic.openCard(...) 进详情;
    • 长列表性能:外层改为 CustomScrollView + SliverGrid(或等价的懒构建方案),不要用 GridView(shrinkWrap: true) 嵌套整页;图片沿用 RemoteImage 懒加载。
  3. 保留:header、轮播、分类 chips、底部导航、现有三态(加载/失败重试/空态)与空态文案(无分类剧目/无搜索结果)。
  4. 明确不做:不动接口与数据模型、不改观看记录/收藏/下载、不改轮播与通知、不新增分页(feed 一次返回全量)、不改底部导航。

三、测试与门禁

  • 新增/更新 widget 测试:首页不再出现「继续观看」;出现 allDramas 标题;网格为 2 列(断言 SliverGridDelegateWithFixedCrossAxisCount.crossAxisCount == 2);分类切换后网格内容更新;空态与失败态保持。
  • dart analyze lib test 零告警;flutter test 全过;flutter build apk --debug 通过;构建前确认无并发 flutter run/build 占用 build/。
  • 真机验收由用户执行:首页滚动、2 列布局、分类切换、点击进详情。

四、Git 与留痕

  • 从干净 develop 拉 feature/flutter-k9-home-grid;完成后 git merge --no-ff 回 develop 并 push;不动 main。
  • 先写 FruitServer/docs/plans/2026-10-17-K9.md;每步「叙述 + 命令 + 原始输出 + 解读」追加到 PROGRESS.md;完成后停住写回执。
  • 提交作者与提交者统一 Zhou Weidong <502333560@qq.com>。

五、K9 完成回执(2026-10-17)

结论:首页「继续观看」已移除,分类下方改为 CustomScrollView + SliverGrid 的 2 列「全部剧目」列表;分类切换、空态、失败态、全量测试与 APK 构建均完成。 未改接口、数据模型、轮播、通知或其他页面;未跑真机。

  • 客户端分支 feature/flutter-k9-home-grid 从 99f64ab 完成并 --no-ff 合并;功能提交 cf03178,合并提交 2c381be,已推送 99f64ab..2c381be develop。
  • discover_view.dart 从 649 行降到 487 行;新增 widgets/home_drama_grid.dart 167 行。已删除 _continueHeader、 _dramaContent、_continueTile、_ShowcaseCard 与 _cardsFor。
  • SliverGridDelegateWithFixedCrossAxisCount 为 2 列、列距 12、行距 16; 左右边距 16;封面 2:3、圆角 14,标题最多 2 行、类型最多 1 行。
  • 新增三语 allDramas:全部剧目 / All Dramas / Semua Drama。
  • 新增 test/discover/home_all_dramas_grid_test.dart 5 个用例,覆盖标题、 无继续观看、2 列参数、2:3 封面、分类切换、空态、失败态和 320px 小屏。
  • 既有 test/widget_test.dart 的 5 处「Continue Watching」可见文案断言原位 更新为「All Dramas」,所有既有用例与导航流程保留,未删除测试用例。
  • dart analyze lib test 零告警;flutter test 为 00:18 +297 ~1: All tests passed!;flutter build apk --debug 成功。

用户真机验证步骤与逐步预期、原始输出见 docs/plans/PROGRESS.md「24. K9 首页 2 列全部剧目」。 设备侧滚动、布局、分类切换和详情跳转验收由用户执行。


[已处理·待用户验收] 2026-10-17 | K8 登录页视觉重设计

用户指令(2026-10-17):「接下来做登录页面 ui 优化,现在这个 ui 不好看」。

K6 已完成功能层优化(品牌区、autofill、密码显隐、键盘流转),本批只做视觉重设计,不改业务逻辑与接口。 依赖 K5 主题 token(blue #002FA7 / bluePressed #00227A / blueSoft #5B7BE0 / onPrimary #FFFFFF)。 specs 无设计稿;以下为 PM 按「深色 + 克莱因蓝 + 印尼市场」给出的可执行视觉规格,按此实现。

〇、开工前必读

  1. AGENTS.md、CODING_STANDARDS.md、GIT_WORKFLOW.md
  2. K5 主题 token(lib/core/theme/app_colors.dart、app_theme.dart)
  3. 当前登录页 lib/pages/auth/auth_view.dart(356 行,K6 后版本)
  4. 本 INBOX 条目

一、本批范围(登录页单页视觉重设计)

目标:从「表单堆叠」升级为有品牌感的深色登录页,保持现有逻辑与 Key 不变。

布局结构(自上而下)

  1. 全屏背景:AppColors.ink,顶部叠加一层克莱因蓝径向/线性渐变光晕(透明度 ≤0.18,不抢文字对比度)。
  2. 品牌区:44–52px 克莱因蓝圆角方块(内含白色播放/剧集图形图标,用 Icons. 即可,不引入图片资源)+ App 名 + 印尼市场 tagline(现有 authBrandTagline)。
  3. 表单卡片:AppColors.card 背景、圆角 20、1px 边框(用 K5 outlineVariant)、内边距 20;卡片内为标题、副标题、输入框、错误提示、主按钮。
  4. 主按钮:全宽、高 52、圆角 26、AppColors.blue、onPrimary 文字 16/w700;按下态 bluePressed;loading 时 18px 白色环且禁用输入。
  5. 登录/注册切换:改为分段控件(Segmented)或 pill 双段(登录 | 注册),选中段主色底/白字,未选中透明/muted;点击切换现有 toggleMode(),不再用文字链接。
  6. 底部辅助:保留当前模式说明或语言提示一行小字(muted,可选);不新增协议勾选。

输入框规格

  • 填充式背景 AppColors.bar,圆角 14,高 52,无外边框;聚焦时 1.5px AppColors.blue 边框 + 前缀图标变主色;
  • 前缀图标:邮箱 mail_outline_rounded、密码 lock_outline_rounded;密码后缀显隐按钮保留;
  • 标签与错误文案沿用现有 l10n;错误提示改为浅红底胶囊(红 10% 透明度 + 圆角 12 + 图标),不再裸文本。

动效与可用性

  • 首帧淡入 + 上滑(≤220ms,TweenAnimationBuilder 即可,不引第三方库);
  • 小屏(iPhone SE)+ 键盘弹出不遮挡:沿用 auth-scroll 滚动与 viewInsets padding;
  • 所有点击区域 ≥44pt;对比度:正文 ≥4.5:1;
  • 保留 Key:auth-scroll、auth-email、auth-password;如新增控件需有稳定 Key 便于测试。

明确不做

  • 不改 AuthLogic、校验规则、AuthController、接口调用、登录/注册流程;
  • 不做忘记密码、Google/Apple 登录、协议勾选、第三方 SDK;
  • 不新增图片/字体资源;不改其他页面。

二、测试与门禁

  • 新增/更新 widget 测试:分段切换仍改变模式、邮箱→密码→提交焦点流转、密码显隐、busy 禁用、错误胶囊展示、品牌区文案存在;既有 test/auth/auth_page_test.dart 不得删改用例。
  • dart analyze lib test 零告警;flutter test 全过;flutter build apk --debug 通过(注意当前有真机构建在跑,避免并发写 build/,先确认无构建进程)。
  • 真机验收由用户执行:视觉、键盘、小屏、深色对比度。

三、Git 与留痕

  • 从当前干净 develop(6cc5bfc)拉 feature/flutter-k8-auth-visual;完成后 git merge --no-ff 回 develop 并 push;不动 main。
  • 先写 FruitServer/docs/plans/2026-10-17-K8.md;每步「叙述 + 命令 + 原始输出 + 解读」追加到 PROGRESS.md;完成后停住写回执。
  • 提交作者与提交者统一 Zhou Weidong <502333560@qq.com>。

四、K8 完成回执(2026-10-17)

结论:登录页视觉重设计、交互回归、全量测试和 Android debug 构建均完成。 未改 AuthLogic、校验、接口、登录/注册流程或其他页面;未跑真机。

  • 客户端分支 feature/flutter-k8-auth-visual 已从 6cc5bfc 完成并 --no-ff 合并;修复提交 45c3c64,合并提交 99f64ab,已推送 6cc5bfc..99f64ab develop。
  • 深色背景、≤0.18 克莱因蓝光晕、48px 品牌方块、20px 圆角卡片、52px 输入框与主按钮、pill 分段切换、错误胶囊、220ms 淡入上滑均按规格实现。
  • 既有 auth-scroll/auth-email/auth-password/auth-switch-mode Key、autofill、 next→done→提交、busy 禁用、密码显隐与 Haptics 保留;新增视觉控件 Key。
  • 新增 test/auth/auth_visual_test.dart 6 个用例;既有 test/auth/auth_page_test.dart 9 个用例未修改且全过。
  • dart analyze lib test 零告警;flutter test 为 00:19 +292 ~1: All tests passed!;flutter build apk --debug 成功。

用户真机验证步骤与逐步预期、原始命令输出见 docs/plans/PROGRESS.md「23. K8 登录页视觉重设计」。 设备侧截图、键盘与小屏验收由用户执行。


[待处理] 2026-10-17 | K7 首页轮播图 + 站内信通知

用户指令(2026-10-17):「现在做首页轮播图及站内信通知」。

依赖 K5/K6:轮播与通知页的配色、控件、震动反馈沿用 K5 主题与 K6 Haptics。 接口只认 FruitServer/docs/api-phase1.md §1.3 / §1.9,契约里没有的接口一律不许自造。

〇、开工前必读

  1. AGENTS.md、CODING_STANDARDS.md、GIT_WORKFLOW.md
  2. FruitServer/docs/api-phase1.md §1.3(feed banners)/ §1.9.2(notices)
  3. K5 主题 token、K6 Haptics 封装
  4. 本 INBOX 条目

一、PM 已勘察的现状(实测)

  • 轮播图已有基础实现:发现页 lib/pages/discover/discover_view.dart 的 _carousel 使用 lib/pages/discover/widgets/cover_carousel.dart(3D 翻转风格,手动滑动)。
    • 数据来自 GET /app/discover/feed 的 banners[];契约无独立 banner 接口。
    • 现状缺口:无自动轮播、无页码/圆点指示器、单张时无特殊处理、图片失败无占位。
  • 站内信完全未接:客户端没有 notice_api / 通知页;GET /app/notices 契约已实现。
    • 契约:GET /app/notices?pageIndex=1&pageSize=20(匿名),返回 {total, list:[{id,title,content,type,publishAt,expireAt}]},pageSize 最大 50,空数据返回 total=0,list=[]。
    • 契约没有已读/未读接口,本批不做未读红点。
  • 我的页菜单 lib/pages/mine/mine_view.dart 的 _menuCard 已有「观看历史 / 我的收藏 / 下载 / 设置」列表,可新增「通知」入口。
  • 客户端尚未接 GET /app/config,本批不接 noticeEnabled 开关(登记为后续项)。

二、本批范围(2 块 + 测试验收,按顺序做)

K7-A 首页轮播图增强

  • 数据源保持不变:feed.banners,不新增接口、不改后端。
  • 在现有 CoverCarousel 上做最小增强,保留 3D 翻转风格:
    • 自动轮播:间隔 4.5s;用户拖拽/触摸期间暂停,松手后恢复;items.length <= 1 时不启动定时器;
    • 页面不可见/组件 dispose 时取消定时器(避免后台耗电与 setState after dispose);
    • 指示器:底部圆点或 当前/总数(二选一,克制);颜色用 K5 主题 token,未选中用 muted 低透明度;
    • 图片加载失败显示占位底色 + 图标(复用 RemoteImage 的错误处理,不白屏);
    • 点击行为沿用现有 onOpen(跳详情),不改变跳转逻辑;
    • 无障碍:指示器与轮播容器提供语义标签(走 l10n)。
  • items 为空时保持现状 SizedBox.shrink(),不占位。

K7-B 站内信通知

  • 新增数据层:notice.dart(model)、notice_api.dart、notice_repository.dart、notices_controller.dart,ID 按字符串解析。
  • 新增独立页 lib/pages/notices/notices_page.dart:
    • 分页:首屏 pageSize=20,list.length < total 时展示「加载更多」并用 pageIndex+1 续拉;
    • 三态:加载 / 空(「暂无通知」)/ 失败(可重试);支持下拉刷新;
    • 列表项:标题、类型(type 原样展示或统一「公告」,不自造枚举映射)、publishAt 按 locale 格式化、内容摘要(最多 2 行);
    • 点击打开详情弹窗(标题 + 发布时间 + 完整 content,保留换行);关闭按钮无障碍标签走 l10n。
  • 入口:我的页 _menuCard 新增「通知」ListTile(图标 notifications_none_rounded,主题色),点击打开 NoticesPage;不新增首页铃铛,避免改首页信息架构。
  • 匿名可访问,不要求登录;不做未读态、不做通知设置。
  • 文案全部进 en/zh/id 三份 arb。

K7-C 测试与验收

  • 轮播:pump 时间推进后自动切页、单张不启动定时器、指示器数量与当前页、触摸暂停、点击回调。
  • 通知:首屏加载、分页追加、空态、失败重试、详情展示、时间格式化(id locale)。
  • 深色主题、小屏、长标题/长内容不溢出。
  • 真机验收由用户执行:轮播自动切换与指示器、通知列表/详情、三态与下拉刷新。

三、明确不做

  • 不新增 banner 接口、不改 /app/notices 契约、不改后端/Web/specs。
  • 不做已读/未读、通知红点、推送(FCM)、通知设置。
  • 不接 /app/config 的 noticeEnabled(登记后续)。
  • 不新增首页铃铛入口、不改首页信息架构。

四、规范约束

  • 颜色只用 K5 token;震动仅用于关键操作(K6 Haptics),轮播滑动不加震动。
  • 新增文案三语齐全;新文件首行 // Author: fruit.com.cn;public 类/方法文档注释;无 AI 痕迹。
  • 代码规模红线:函数 ≤50 行、参数 ≤5、类 ≤300 行、单文件 ≤500 行、嵌套 ≤4 层、圈复杂度 ≤10。

五、门禁

  1. dart analyze lib test 零告警;flutter test 全过。
  2. flutter build apk --debug 通过。
  3. 新增测试必须真红后绿;不得删改既有测试来「变绿」。
  4. 真机/设备侧验证由用户执行(红线 7、10),回执给「用户可照做步骤 + 每步预期」。

六、Git 与留痕

  • 从当前干净 develop 拉 feature/flutter-k7-carousel-notices;完成后 git merge --no-ff 回 develop 并 push;不动 main。
  • 先写 FruitServer/docs/plans/2026-10-17-K7.md(范围、影响文件、契约缺口、风险、待确认)。
  • 每步「叙述 + 命令 + 原始输出 + 解读」追加到 docs/plans/PROGRESS.md;完成后停住写回执。
  • 提交作者与提交者统一 Zhou Weidong <502333560@qq.com>。

七、K7 完成回执(2026-10-17)

结论:K7-A 轮播增强、K7-B 站内通知、K7-C 测试验收均完成并停止。 未新增接口,未改后端/Web/specs。

7.1 首页轮播

  • 保持 feed.banners 数据源和 CoverCarousel 3D 翻转风格。
  • 4.5s 自动轮播;单张不启动 Timer。
  • 触摸/拖拽期间暂停,松手恢复。
  • active=false 或 TickerMode 不可见时暂停;dispose 取消 Timer。
  • 底部圆点指示器使用 onPrimary / muted K5 token,带三语语义标签。
  • 图片失败复用 RemoteImage 并显示图标占位。
  • 点击继续调用原有 onOpen。

7.2 站内通知

  • 新增 notice.dart、notice_api.dart、notice_repository.dart、 notices_controller.dart 和 NoticesPage。
  • GET /app/notices?pageIndex&pageSize=20,匿名访问。
  • ID 按字符串解析,total 按数字解析。
  • 首屏、加载更多、空态、失败重试、下拉刷新均已接入; 已加载数量 < total 时才展示加载更多。
  • 列表展示标题、原始 type、locale 时间、两行摘要。
  • 详情弹窗展示完整 content,保留换行并支持长内容滚动。
  • 我的页新增“通知”入口;匿名可进入,无登录闸。
  • noticeEnabled 属 /app/config,本批未接,登记后续。

真实 8081 匿名接口验证:

$ curl -sS 'http://127.0.0.1:8081/app/notices?pageIndex=1&pageSize=20'
{"code":0,"message":"ok","data":{"total":0,"list":[]}}

7.3 门禁原始结果

$ dart analyze lib test
Analyzing lib, test...
No issues found!

$ flutter test test/discover/cover_carousel_test.dart test/notice
All tests passed!

$ flutter test
00:14 +284 ~1: All tests passed!

$ flutter build apk --debug
Running Gradle task 'assembleDebug'...                              6.9s
✓ Built build/app/outputs/flutter-apk/app-debug.apk

~1 是既有 live watch 用例在 8081 未启动时按机制跳过。

新增覆盖:

  • 自动轮播、单张无 Timer、交互暂停、active/TickerMode、圆点、图片占位、onOpen。
  • 通知 API 字符串 ID、controller 分页/空/失败/刷新。
  • NoticesPage 首屏、加载更多、加载/空/失败三态、详情换行、印尼时间。
  • 320×568 长标题/长内容不溢出;我的页匿名通知入口。

7.4 Git

  • 功能提交:b870956665f5268070c691533cc2a334f2aa7f59 feat(flutter): 增加轮播自动播放与站内通知
  • --no-ff 合并:3e8703b,已推送 b7b1c50..3e8703b develop -> develop
  • author/committer 均为 Zhou Weidong <502333560@qq.com>;未操作 main。

7.5 用户真机验证步骤与预期

  1. 打开发现页顶部轮播,等待 4.5 秒。

预期:自动翻到下一张,圆点同步变化;单张 banner 不会自动循环。

  1. 按住/拖动轮播超过 4.5 秒后松手。

预期:按住期间不自动翻页;松手后恢复自动轮播;拖拽仍保持 3D 翻转。

  1. 暂时断开图片源或使用失败封面。

预期:显示深色占位和图片失败图标,不白屏。

  1. 点击当前 banner。

预期:按原有 DRAMA 跳转逻辑进入详情。

  1. 进入“我的 → 通知”,未登录也可访问。

预期:显示通知列表;空数据时显示“暂无通知”;断网时显示失败及重试。

  1. 有超过 20 条通知时点击“加载更多”。

预期:下一页追加到列表,无重复;已加载数量达到 total 后按钮消失。

  1. 下拉刷新,再点击一条长通知。

预期:刷新后列表回到第一页;详情显示标题、发布时间和完整正文; 正文换行保留且长内容可滚动。

  1. 切换 Bahasa Indonesia 查看通知时间。

预期:时间按印尼 Material 格式显示。

设备侧截图与真机结果由用户执行。

7.6 已知边界

  • 无已读/未读、红点、FCM、通知设置。
  • 不接 /app/config noticeEnabled,通知入口当前常驻。
  • 无独立 banner 接口,未新增首页铃铛。

[已处理·待用户验收] 2026-10-17 | K6 客户端 UI:登录页 + 底部导航优化

用户指令(2026-10-17):「接下来优化登录页面 UI 及底部导航 UI」。

依赖 K5 完成:必须基于 K5 的克莱因蓝主题(blue #002FA7 / bluePressed #00227A / blueSoft #5B7BE0 / onPrimary #FFFFFF)与统一 ColorScheme 再做,不得沿用旧 #2F7BFF。 specs 无 UI 设计稿(已确认),本批以「视觉统一 + 可用性」为目标,不自行改变信息架构。

〇、开工前必读

  1. AGENTS.md、CODING_STANDARDS.md、GIT_WORKFLOW.md
  2. K5 条目与 K5 计划文档(色值 token)
  3. specs docs/02-技术架构.md(印尼首发)、docs/19-术语表.md
  4. 本 INBOX 条目

一、PM 已勘察的现状(实测)

  • 登录页 lib/pages/auth/auth_view.dart(246 行):AppBar + ListView,两个 TextField、FilledButton、错误行、登录/注册切换链接。
    • 密码无显隐切换;无 autofillHints;无键盘 next/done 流转;错误提示写死 #FF6B6B。
    • 主色仍直接引用 AppColors.blue,无品牌区;小屏键盘遮挡只靠 ListView 自然滚动。
  • 底部导航在 lib/pages/home/home_view.dart 的 _HomeTabs(约 182–260 行):自定义 Container + Row + 4 个 _TabButton。
    • 高度 63,背景 #0D0D11、上边框 #242329 均为硬编码;只有选中/未选中图标,无指示条;无统一主题 token。
    • 现有 Key:home-tabs-bar、tab-discover、tab-home、tab-my-list、tab-my,测试依赖,不得改名。
    • 播放沉浸模式(videoImmersive)下隐藏导航,需保留。

二、本批范围(3 块 + 测试验收,按顺序做)

K6-A 登录页 UI 优化

  • 品牌区:登录页顶部增加 App 名与一句印尼市场定位副标题(文案走 l10n 三语),使用 K5 主题色;不引入图片资源。
  • 表单体验:
    • 邮箱 autofillHints: [AutofillHints.email]、密码 [AutofillHints.password];
    • 密码框增加显隐切换(图标按钮,语义标签走 l10n);
    • 键盘动作:邮箱 next → 密码 done → 提交;提交中禁用输入与按钮;
    • 错误提示统一样式,颜色取主题语义错误色,不再硬编码 #FF6B6B。
  • 布局与可用性:小屏 + 键盘弹出不遮挡输入与提交按钮(resizeToAvoidBottomInset + 滚动/SafeArea);所有点击区域 ≥44pt;主按钮用 blue,按下态用 bluePressed。
  • 不改登录/注册业务逻辑、接口调用、字段校验规则与切换逻辑;不做忘记密码(契约无接口)、Google/Apple 登录(属 K4)、隐私协议勾选(无契约要求)。

K6-B 底部导航 UI 优化

  • 统一使用 K5 主题 token:背景、边框、选中/未选中色全部来自 AppColors / ColorScheme,移除 #0D0D11、#242329 等硬编码。
  • 视觉:选中项为主色图标 + 主色标签 + 明确的指示条/背景胶囊(二选一,保持克制);未选中为 muted;图标 22–24、标签 10–11,间距一致。
  • 可用性:整体高度与底部安全区适配(含 iPhone home indicator),每个 tab 点击区域 ≥48pt;切换动效 ≤200ms,不引入第三方动画库。
  • 保持:4 个 tab 的数量、顺序、导航逻辑与全部 Key 不变;播放沉浸模式仍隐藏导航。
  • 不做:新增 tab、改信息架构、改页面内容。

K6-C 点击震动反馈

specs 无触觉反馈规范;此为用户 2026-10-17 明确指令,按下方口径执行。客户端当前 HapticFeedback 调用数为 0。

  • 使用 Flutter 内置 HapticFeedback(package:flutter/services.dart),不引入第三方插件。
  • 统一封装 lib/core/platform/haptics.dart(如 Haptics 抽象 + 默认实现),页面通过依赖注入调用,便于 widget 测试替换为 fake;禁止在页面里散落直接调用静态 HapticFeedback.*。
  • 触发范围(克制,只做关键操作):
    • 底部导航 tab 切换:selectionClick();
    • 主操作按钮成功触发时:登录/注册提交、收藏/取消收藏、点赞/取消点赞、评论发送、举报提交、下载授权申请:lightImpact();
    • 危险操作确认(清空观看记录、清空下载、删除记录/授权):mediumImpact()。
  • 不在普通列表项点击、滚动、页面切换上震动;不新增「震动开关」设置项(避免超出本批)。
  • 调用不阻塞 UI:unawaited(...);异常不得影响业务(触觉不可用时静默忽略,禁止空 catch 之外的吞错写法)。
  • 真机验收:iPhone 上逐项可感知;iOS 低电量模式会自动禁用触觉,验收时确认未处于低电量模式。

K6-D 测试与验收

  • widget 测试至少覆盖:
    • 底部导航 4 个 Key 存在、选中态随 tab 切换、沉浸模式下隐藏;
    • 登录页密码显隐切换、提交中按钮禁用、错误提示展示、键盘 next/done 焦点流转(可用 tester.testTextInput);
    • 震动反馈:用 fake Haptics 断言 tab 切换触发 selectionClick、主操作触发 lightImpact、危险操作确认触发 mediumImpact,且普通列表点击不触发;
    • 无新增硬编码色值(沿用 K5 的扫描测试口径)。
  • 深色主题、小屏(iPhone SE 级别)、键盘弹出三种场景不溢出。
  • 真机验收由用户执行:登录页视觉与交互、底部导航选中态与安全区。

三、规范约束

  • 颜色只允许来自 K5 的 AppColors / ColorScheme;页面禁止硬编码色值。
  • 新增文案必须 en/zh/id 三份 arb 齐全并重新生成;禁止硬编码文案。
  • 保持现有 Key 与业务逻辑;函数 ≤50 行、参数 ≤5、类 ≤300 行、单文件 ≤500 行、嵌套 ≤4 层。
  • 新文件首行 // Author: fruit.com.cn;public 类/方法文档注释;无 AI 痕迹。

四、门禁

  1. dart analyze lib test 零告警;flutter test 全过。
  2. flutter build apk --debug 通过。
  3. 新增测试必须真红后绿;不得删改既有测试来「变绿」。
  4. 真机/设备侧验证由用户执行(红线 7、10),回执给「用户可照做步骤 + 每步预期」。

五、Git 与留痕

  • 从 K5 合并后的干净 develop 拉 feature/flutter-k6-auth-nav-ui;完成后 git merge --no-ff 回 develop 并 push;不动 main。
  • 先写 FruitServer/docs/plans/2026-10-17-K6.md(范围、影响文件、风险、待确认)。
  • 每步「叙述 + 命令 + 原始输出 + 解读」追加到 docs/plans/PROGRESS.md;完成后停住写回执。
  • 提交作者与提交者统一 Zhou Weidong <502333560@qq.com>。

六、K6 完成回执(2026-10-17)

结论:K6-A 登录页、K6-B 底部导航、K6-C 点击震动反馈与 K6-D 测试验收均已完成 并停止。未做 K4,未接新接口,未改后端/Web/specs,未跑真机。

6.1 登录页

  • 新增 App 品牌区、印尼市场副标题与管理入口一致的三语文案。
  • 邮箱/密码 autofill;密码显隐按钮与三语语义标签。
  • 键盘 next → done → 提交,提交中禁用输入、显隐、提交和模式切换。
  • 错误提示使用 ColorScheme.error,不再硬编码错误红。
  • submit、显隐按钮、模式切换点击区均 ≥44pt;小屏键盘场景可滚动到 submit。
  • 登录/注册接口、校验规则和模式切换逻辑未改。

6.2 底部导航

  • 背景 AppColors.bar、边框 ColorScheme.outlineVariant,移除旧深色边框/背景硬编码。
  • 选中项使用 bluePressed 胶囊、onPrimary 图标、blueSoft 标签; 未选中使用 muted。
  • 160ms 内置动画;64pt 容器 + SafeArea 底部适配;每个 tab 点击区 ≥48pt。
  • 4 个 tab 数量、顺序、回调和 Key 不变: home-tabs-bar、tab-discover、tab-home、tab-my-list、tab-my。
  • videoImmersive 为 true 时导航继续隐藏。

6.3 点击震动反馈

  • 新增 lib/core/platform/haptics.dart:Haptics 抽象 + FlutterHaptics 默认实现;通过 LumaDramaApp Provider 注入,测试使用 fake。
  • 页面没有直接调用静态 HapticFeedback.*;静态扫描限定封装文件内部调用。
  • 触发范围:
    • tab 真正切换:selectionClick();重复点击当前 tab 不触发。
    • 登录/注册提交、收藏/取消收藏、剧集/评论点赞、评论发送、举报提交、 下载授权申请:lightImpact()。
    • 清空观看记录、清空下载、删除记录、删除下载授权:mediumImpact()。
  • 所有页面调用均为 unawaited;平台异常由默认实现静默忽略,不影响业务。
  • 普通列表点击、滚动、页面跳转不触发。

6.4 门禁原始结果

$ cd /Users/jianghaifeng/Desktop/ReelShort/ReelShort
$ dart analyze lib test
Analyzing lib, test...
No issues found!

$ flutter test
00:14 +263 ~1: All tests passed!

$ flutter build apk --debug
Running Gradle task 'assembleDebug'...                              6.6s
✓ Built build/app/outputs/flutter-apk/app-debug.apk

~1 是既有 live watch 用例在 8081 未启动时按机制跳过,不是失败。

新增测试覆盖:

  • 登录品牌、autofill、密码显隐、next/done、busy 禁用、语义错误色、 44pt 点击区和小屏键盘滚动。
  • 底栏 4 Key、选中态切换、主题背景/边框、48pt 点击区、160ms 动效和沉浸隐藏。
  • Fake Haptics 的 selection/light/medium 触发、重复 tab 不重复触发、 普通列表点击不触发。
  • 静态调用扫描与平台触觉异常静默测试。
  • Klein Blue 旧色值扫描继续为零。

6.5 Git

  • 登录页/底部导航提交:e609e3a feat(flutter): 优化登录页与底部导航
  • 震动反馈提交:8a481d5 feat(flutter): 增加关键操作震动反馈
  • --no-ff 合并提交:0cedaab merge: 合并 K6 登录导航与震动反馈
  • 已推送:e52d818..0cedaab develop -> develop
  • author/committer 均为 Zhou Weidong <502333560@qq.com>;未操作 main。

6.6 用户真机验证步骤与预期

前置:iPhone 关闭低电量模式并确认系统触觉正常;安装最新 debug APK。 以下证据必须由用户在设备侧取得,Codex 未执行真机验证。

  1. 打开登录页,检查品牌名、印尼市场副标题、输入框和主按钮。

预期:三语显示正确;按钮为克莱因蓝白字;密码眼睛按钮可切换显隐; 错误提示为系统语义红色;所有点击区易按。

  1. 小屏手机打开键盘,使用邮箱键盘 next,密码键盘 done。

预期:焦点从邮箱进入密码;done 触发登录提交;键盘弹出时 submit 可滚动到可见区域。

  1. 使用延迟网络或错误账号观察提交过程。

预期:提交时两个输入框、眼睛、submit 和模式切换全部禁用;加载指示可见; 完成后恢复或展示后端错误,不崩溃。

  1. 在浅色/深色系统主题与小屏下检查底部 4 个 tab。

预期:4 个 Key 对应入口均存在且顺序不变;选中胶囊和指示清晰; 底部有安全区留白;无文字/图标重叠。

  1. 逐一点击非当前 tab。

预期:每次真正切换触发轻微 selection 触觉;重复点击当前 tab 不再触发。

  1. 登录提交、收藏/取消、剧集或评论点赞、发送评论、提交举报、申请下载授权。

预期:关键操作触发 lightImpact;触觉不影响请求速度或业务结果。

  1. 清空观看记录、清空下载、删除记录或下载授权并确认。

预期:确认后触发更明显的 mediumImpact;取消确认不触发。

  1. 点击普通列表项(如“我的”中的语言入口)并滚动列表。

预期:不产生震动;页面切换和滚动保持无触觉。

  1. 临时开启 iOS 低电量模式后重复上述操作。

预期:系统可能禁用触觉,但业务、页面和无异常行为保持正常; 正式验收请关闭低电量模式。

设备侧截图、触觉感知结果与系统低电量测试由用户执行。

6.7 iOS Xcode SPM 构建修复

  • Runner.xcscheme 新增 “Bootstrap Flutter SPM Package” PreAction: 按 FLUTTER_ROOT → PATH → $HOME/flutter → /opt/homebrew 兜底解析 Flutter,执行 flutter pub get,找不到时明确报错并非 0 退出。
  • 原有 “Run Prepare Flutter Framework Script” 保留,并改为同一兜底链解析 Flutter root 后执行 xcode_backend.sh prepare。
  • xmllint --noout 通过;xcodebuild -list 可识别 Runner scheme 与全部 SPM source packages。
  • 在已有 SPM 包时完整执行: xcodebuild -project ios/Runner.xcodeproj -scheme Runner -configuration Debug -sdk iphoneos build 结果为 BUILD SUCCEEDED;日志确认执行 PreAction,输出 Resolving dependencies...、Got dependencies!。
  • 删除 ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage 后立即运行同一 xcodebuild,Xcode 在 “Resolve Package Graph” 阶段先失败, 尚未运行 PreActions: Could not resolve package dependencies ... folder doesn’t exist。 因此 scheme-only 修复无法覆盖“包从未生成”的首次冷启动; 该冷启动自动生成场景标记为未通过验证。
  • 当前可靠口径:新克隆、flutter clean 或手动删除 ephemeral 后,先执行一次 flutter pub get,再从 Xcode GUI 运行;正常构建期间 PreAction 会继续刷新包。
  • 修复提交:cc037a2956848128c4899df1f5092fea44d3620b fix(ios): 刷新 Flutter SPM 包后再准备框架
  • 合并:ac706a4,已推送 ReelShort develop。

6.8 导航白线与观看历史缺陷修复

  • 底部导航白线:M3 默认深色 outlineVariant 实测亮度为 1.0。 已新增显式 token: outlineDark=#3A3C45、outlineVariantDark=#242329、 outlineLight=#8A8E99、outlineVariantLight=#D6D9E2; light/dark ColorScheme 均显式赋值,底栏边框使用主题 outlineVariant。
  • 新增测试断言导航边框不是白色、深色亮度 <0.2, 并覆盖 light/dark ColorScheme token。
  • 观看历史诊断:
    • 只读查询 UKZBA9IBE 得到两条正常记录:剧 1/集 1、剧 5/集 7;
    • 真实 8081 recent 返回结构正常,客户端诊断账号解析出 2 条记录并成功补详情;
    • 未复现用户设备上的 401/100000、JSON 异常或详情失败;
    • 当前代码缺陷是 WatchRecordsController 把 recent 异常统一折叠为 progressError,后端原文无法定位。
  • 修复:controller 对 recent/详情异常打印真实类型、异常与堆栈; ApiException.message 透传到页面错误态,页面新增稳定 retry Key。
  • 临时真实联调诊断文件已删除,不进入提交。
  • 用户设备上的具体错误仍待设备侧日志确认,未宣称根因已完全复现。

定向门禁:

$ flutter test test/theme test/home/home_nav_ui_test.dart test/watch/watch_records_controller_test.dart test/watch/watch_records_page_test.dart
00:01 +21: All tests passed!

全量门禁:

$ dart analyze lib test
Analyzing lib, test...
No issues found!

$ flutter test
00:13 +266 ~1: All tests passed!

$ flutter build apk --debug
✓ Built build/app/outputs/flutter-apk/app-debug.apk

Git:

  • 修复提交:732483465ced2b28438585a4b2f1573e9a0529cb fix(flutter): 修复导航白线与观看记录错误透传
  • --no-ff 合并:b7b1c50,已推送 ac706a4..b7b1c50 develop -> develop

6.9 已知边界

  • 未增加震动开关;触觉遵循系统设置。
  • 不做忘记密码、Google/Apple 登录、隐私勾选或新 tab。
  • Xcode 首次包图解析早于 scheme PreActions;完全无 SPM 包时仍需先运行 flutter pub get,本轮未通过 scheme 实现冷启动自动生成。
  • 观看历史缺陷未在用户真机上复现;需用户更新后抓 [WatchRecordsController] recent failed / detail failed 日志与页面原文。
  • K4 未开始。

[已处理·待用户验收] 2026-10-17 | K5 客户端视觉:克莱因蓝主题 + 印尼市场适配

用户指令(2026-10-17):「改 UI 及 App 整体颜色,克莱因蓝,针对印尼市场,认真阅读文档严格执行」。

本批只做 K5 视觉与本地化,不做 K4,不接新接口。 颜色口径:specs 全文没有成文色板或设计稿(已检索 AGENTS.md、CODING_STANDARDS.md、GIT_WORKFLOW.md、docs/01~20、docs/plans/*、FruitServer/docs/*); 颜色值来自用户 2026-10-17 明确裁定的 方案 A,按下方色值执行,不得自行另选。

〇、开工前必读(按顺序)

  1. AGENTS.md(目标市场:首发印尼)
  2. CODING_STANDARDS.md(Dart/Flutter 红线、门禁)
  3. GIT_WORKFLOW.md(Git Flow)
  4. specs docs/02-技术架构.md(印尼首发)、docs/15-成本清单与预算.md(印尼语/Android 主导)、docs/19-术语表.md、docs/16-测试用例库.md
  5. 本 INBOX 条目

一、PM 已勘察的现状(实测,直接用)

  • 主题集中在 lib/core/theme/app_colors.dart:blue #2F7BFF、blueSoft #8FB4FF、ink/bar/card/muted。
  • lib/app.dart 浅色与深色两套 ColorScheme.fromSeed 都硬编码 seedColor: 0xFF2F7BFF,另有 scaffoldBackgroundColor。
  • 42 处硬编码蓝色散落 14 个文件,分布:mine_view.dart 13、discover_view.dart 5、player_view.dart 4、comment_tile.dart 3、drama_detail_view.dart 3、home_view.dart 2、auth_view.dart 2、app.dart 2,其余 7 个文件各 1。
  • lib/core/config/locale_controller.dart 只支持 en / zh,选 id 会落成 null(跟随系统)——印尼首发缺陷。
  • app.dart 的 localeResolutionCallback 在系统语言不支持时回退 supported.first(当前为 en),与「首发印尼」不符。
  • 三语 arb 齐全:app_en.arb / app_zh.arb / app_id.arb 各 243 个 key。

二、本批范围(4 块,按顺序做)

K5-A 主题色系统(克莱因蓝)

色值(用户裁定,方案 A):

Token 值 用途
blue #002FA7 主色(Klein Blue / IKB)
bluePressed #00227A 按下/激活态
blueSoft #5B7BE0 深色背景上的高亮/次级文字与图标
onPrimary #FFFFFF 主色上的文字/图标
ink/bar/card/muted 保持现值 深色底不改成蓝色,不破坏现有暗色风格
  • app_colors.dart 增加上述 token,并把 blue 改为 #002FA7;blueSoft 若与现有语义冲突,明确注释用途。
  • app.dart 两套主题改为显式 ColorScheme(或 fromSeed + 覆盖 primary/onPrimary),确保浅色与深色下主色一致为 #002FA7;seedColor 不得再出现其他蓝色字面量。
  • 对比度硬要求:正文文字 ≥ 4.5:1、大字号/图标 ≥ 3:1;主色大面积背景使用 bluePressed,主色上的文字用 onPrimary。
  • 新增单测:至少断言 blue == Color(0xFF002FA7)、onPrimary 在白底/主色上的对比度达标。

K5-B 全量 UI 替换

  • 把 42 处硬编码蓝色全部改为 AppColors.* 或 Theme.of(context).colorScheme.*,页面层禁止再出现 0xFF2F7BFF / Colors.blue。
  • 覆盖页面:首页、发现、详情、播放(右栏/进度条)、我的、评论弹窗与评论项、观看记录、收藏、下载、登录、AuthGate、快捷抽屉。
  • 统一控件:FilledButton/TextButton、Tab 指示器、Switch、进度条、选中态、链接、SnackBar 主色。
  • 不改语义色:错误红、成功绿、点赞红(如有)保持原语义,不因主题替换改成蓝色。

K5-C 印尼市场适配

  • LocaleController 支持 id:'id' => Locale('id'),读写 ui.locale 持久化。
  • 默认语言规则:用户未显式选择时跟随系统;系统语言不是 en/zh/id 时回退 id(首发印尼),不得回退 en。
  • 语言选择入口增加 Bahasa Indonesia 选项(现有入口位置沿用,不新造页面)。
  • 校验三语 arb:新增文案必须 en/zh/id 三份齐全;禁止硬编码文案。
  • 日期/数字本地化:检查现有格式化是否按 locale(intl),id 下日期/数字格式正确。
  • 不接 Google 登录、不做支付(属 K4/后续)。

K5-D 回归与验收

  • 深色/浅色两套主题、加载/空/失败三态、小屏(iPhone SE 级别)均不破版。
  • 新增 widget 测试:主题主色生效、页面无硬编码蓝色(可用静态扫描测试)、LocaleController 支持 id 与回退 id。
  • 真机验收由用户执行:主题色、按钮/进度条/选中态、语言切换与印尼语显示。

三、明确不做

  • 不改业务逻辑、接口调用、后端、ReelShortWeb、specs。
  • 不做 Google 登录、支付、下载落盘。
  • 不改 App 图标/启动图(无设计资源;如后续提供 Stitch 出稿再做)。
  • 不替换信息架构与页面布局,只做颜色、主题与本地化;布局仅在换色后出现对比度/溢出问题时做最小修正。

四、规范约束

  • 颜色只允许在 app_colors.dart 与主题层定义;页面禁止硬编码色值(测试断言除外)。
  • l10n 三语齐全并重新生成;所有新增文案进三份 arb。
  • 新文件首行 // Author: fruit.com.cn;public 类/方法文档注释;无 AI 痕迹。
  • 代码规模红线:函数 ≤50 行、参数 ≤5、类 ≤300 行、单文件 ≤500 行、嵌套 ≤4 层、圈复杂度 ≤10。

五、门禁

  1. dart analyze lib test 零告警;flutter test 全过。
  2. flutter build apk --debug 通过。
  3. 新增测试覆盖:主题 token 与对比度、无硬编码蓝色扫描、locale id 支持与回退、语言切换。
  4. 真机/设备侧验证由用户执行(红线 7、10),回执给「用户可照做步骤 + 每步预期」,禁止用开发机 curl 冒充设备证据。

六、Git 与留痕

  • 开工前先处理上一条诊断分支 feature/flutter-ios-home-diagnostics:按实际情况提交为诊断增强或清理回滚,确保 K5 从干净的 develop 拉分支(当前 develop 为 e1147cf)。
  • 分支:feature/flutter-k5-klein-blue;完成后 git merge --no-ff 回 develop 并 push;不动 main。
  • 先写 FruitServer/docs/plans/2026-10-17-K5.md(范围、影响文件、色值表、风险、待确认)。
  • 每步「叙述 + 命令 + 原始输出 + 解读」追加到 docs/plans/PROGRESS.md;完成后停住写回执(含用户验证步骤与预期)。
  • 提交作者与提交者统一 Zhou Weidong <502333560@qq.com>。

七、K5 完成回执(2026-10-17)

结论:K5-A 主题 token/ColorScheme、K5-B 蓝色收敛、K5-C 印尼语适配、 K5-D 回归均已完成并停止,未开始 K4,未接新接口,未跑真机。

7.1 诊断分支收口

  • iOS 首页诊断插桩已保留在未合并分支 feature/flutter-ios-home-diagnostics,提交 7efbed97587dd3ed02967dcbbaa6fb85693c1883。
  • K5 从干净的 ReelShort develop e1147cf 拉出,未夹带诊断插桩。

7.2 色值、主题与蓝色收敛

  • AppColors.blue = #002FA7、bluePressed = #00227A、 blueSoft = #5B7BE0、onPrimary = #FFFFFF。
  • AppTheme.light() / AppTheme.dark() 使用显式 ColorScheme,两套主题 primary 均为 #002FA7、onPrimary 均为白色;SnackBar 使用深蓝背景白字。
  • 深色表面的小图标、链接和轨道使用 blueSoft;大面积选中背景使用 bluePressed;主按钮使用 blue + onPrimary。
  • 页面层旧蓝 0xFF2F7BFF、0xFF8FB4FF、Colors.blue 扫描结果为空。
  • 错误红、成功绿、点赞红等语义色未改;App 图标/启动图未改。

7.3 印尼市场适配

  • LocaleController 支持选择、持久化和恢复 id。
  • 未显式选择时跟随系统;系统语言非 en/zh/id 时回退 id。
  • “我的”语言弹层与首页快捷抽屉均增加 Bahasa Indonesia; 快捷抽屉改为 Wrap,避免 320px 小屏溢出。
  • 新增 bahasaIndonesia 三语文案并重新生成 l10n。
  • 观看记录与下载授权的日期/时间改由 MaterialLocalizations 按当前 locale 和系统 12/24 小时设置格式化。

7.4 门禁原始结果

$ cd /Users/jianghaifeng/Desktop/ReelShort/ReelShort
$ dart analyze lib test
Analyzing lib, test...
No issues found!

$ flutter test
00:12 +251 ~1: All tests passed!

$ flutter build apk --debug
Running Gradle task 'assembleDebug'...                             26.6s
✓ Built build/app/outputs/flutter-apk/app-debug.apk

~1 是既有 live watch 用例在 8081 未启动时按机制跳过,不是失败。

新增测试覆盖:

  • Klein Blue token、白字/主色对比度、图标/深底对比度。
  • 浅色/深色 ColorScheme 与 widget 实际主题值。
  • lib 旧蓝字面量静态扫描。
  • locale id 选择、持久化、系统语言跟随与未知语言回退 id。
  • Bahasa Indonesia 选项、语言切换、印尼语日期/时间格式。
  • 320×568 深色小屏首页与“我的”页不溢出。

7.5 Git

  • 功能提交:10d448c8ba93aa2762e14babdb528805897b3992 feat(flutter): 应用克莱因蓝主题与印尼语适配
  • --no-ff 合并提交:e52d81881c19202c7adc8d13a6f78f78e4f6182f merge: 合并 K5 克莱因蓝主题与印尼语适配
  • 已推送:e1147cf..e52d818 develop -> develop
  • author/committer 均为 Zhou Weidong <502333560@qq.com>;未操作 main。

7.6 用户真机验证步骤与预期

前置:安装最新 debug APK,分别准备一套 Android 与 iOS 设备,保留系统深色/浅色 切换条件。以下证据必须由用户在设备侧取得,Codex 未执行真机验证。

  1. 浅色与深色系统下分别打开首页、发现、详情、播放、我的、收藏、记录和下载。

预期:页面可读、无溢出;主按钮为克莱因蓝白字;选中态/进度条可见; 深色底上的链接和图标使用较亮的蓝紫高亮;旧亮蓝 #2F7BFF 不再出现。

  1. 对主按钮、进度条、选中 Tab 截图并用取色工具检查。

预期:主色接近 #002FA7;按下/激活大面积背景接近 #00227A; 主色按钮文字为 #FFFFFF。

  1. 在 iPhone SE 级小屏打开首页与“我的”,展开语言入口。

预期:三个语言选项自动换行,不挤出屏幕;底栏文字和图标不重叠。

  1. 从“我的 → 语言”选择 Bahasa Indonesia,再冷启动 App。

预期:界面切为印尼语,重启后仍保持印尼语。

  1. 从首页快捷抽屉选择 Bahasa Indonesia。

预期:同样切为印尼语,两个入口行为一致。

  1. 清除 App 数据/重新安装,把系统语言设为法语,且不手动选择语言。

预期:App 自动使用印尼语,不回退英语。

  1. 在印尼语下查看观看记录与下载授权时间。

预期:日期/时间使用印尼地区 Material 格式;系统 12/24 小时设置变化时格式跟随。

  1. 未登录点评论发布/点赞/举报,登录页按钮与错误/成功提示仍清晰可辨。

预期:业务流程不变;Klein Blue 按钮、蓝色 SnackBar 与红色错误提示对比度正常。

设备侧截图、系统语言切换日志与真机结果由用户执行;开发机检查不能替代这些证据。

7.7 已知边界

  • 现有主体页面保持 dark-first 设计,并显式使用 AppTheme.dark(); 根部同时提供浅色/深色 ColorScheme,未重做信息架构或页面布局。
  • App 图标、启动图、Google 登录与支付不在本批范围。
  • K4 未开始。

[已处理·待用户验收] 2026-10-17 | K3 客户端第三批:互动(评论 + 点赞 + 举报 + 分享)

用户指令(2026-10-17):客户端继续对接接口及 UI,严格执行 specs。

本批只做 K3,做完停下写回执,不要连做 K4。 规范以 specs 为准(docs/plans/codex-研发任务清单.md 阶段 A6、docs/16-测试用例库.md INTERACT、docs/09-测试计划.md); 接口只认 FruitServer/docs/api-phase1.md §1.7,契约里没有的接口一律不许自造。

〇、开工前必读(按 specs 顺序,缺一不可)

  1. AGENTS.md(项目上下文、强制约束、自主执行与沟通节奏)
  2. CODING_STANDARDS.md(三端红线、Dart/Flutter 专章、门禁总表)
  3. GIT_WORKFLOW.md(Git Flow、提交格式、审查门槛)
  4. FruitServer/docs/api-phase1.md §0 / §1.7(接口唯一权威)
  5. docs/plans/codex-研发任务清单.md(A6)、docs/16-测试用例库.md(INTERACT-01~09)、docs/09-测试计划.md
  6. 本 INBOX 条目

一、PM 已勘察的现状(实测,直接用)

  • ReelShort develop 45f5f25(K2 已合并推送),互动现状全部是本地演示数据:
    • engagement_controller.dart 注释明确 Local demo interactions ... No public/social backend,内部维护 liked / comments 两个本地集合;
    • 播放页右栏已有三个入口:点赞 rail-like、评论 rail-comments、分享 rail-share(player_view.dart);
    • 评论弹窗 drama_comments_sheet.dart 直接 addComment 到本地 List<String>,无分页、无回复、无删除、无举报;
    • 分享走 platform_share_service.dart 的系统分享,不需要后端接口;
    • 全工程没有举报入口。
  • 后端 §1.7 五个接口均已实现(AppCommentController、AppInteractionController),本批不改后端。

契约字段(实测自后端 VO,直接用)

  • CommentRespVO:id,dramaId,episodeId,parentId,rootId,userId,uid,nickname,avatarUrl,content,status,auditReason,likeCount,replyCount,reportCount,createdAt,mine,liked(ID 按字符串解析)
  • CommentPublishReqVO:dramaId(必填)、episodeId、content(必填)、parentId(回复时传)
  • 列表 GET /app/comments?dramaId=&episodeId=&rootId=&cursor=&size= → CursorResult{list,nextCursor,hasMore};匿名可读
  • POST /app/likes/toggle:{targetType: DRAMA|COMMENT, targetId} → {liked, count}
  • POST /app/reports:{targetType: COMMENT|USER|DRAMA, targetId, reasonCode(必填), description?} → 工单 id
  • 敏感词三档:BLOCK 直接报错、REPLACE 替换后放行、REVIEW 转待审(status=pending,仅自己可见)
  • 限流:同一剧集每分钟 5 条评论(后端判定,客户端只展示返回的 message)

二、本批范围(4 块,按顺序做)

K3-A 评论

  • 评论列表弹窗改为服务端数据:首屏 size=20,hasMore 时展示“加载更多”,用 nextCursor 续拉;三态(加载/空/失败可重试)。
  • 发布顶层评论:dramaId + content;发布回复:额外传 parentId(只做一层楼层回复,回复列表用 rootId 拉取,不做无限嵌套)。
  • 敏感词三档处理:
    • BLOCK:原样展示后端 message,不清空输入框;
    • REPLACE:展示后端替换后的内容(以响应为准);
    • REVIEW:该条以 status=pending 出现在自己的列表里,并显示“审核中”标识(文案走 l10n)。
  • 删除自己的评论:仅 mine=true 显示删除入口,二次确认后调 DELETE /app/comments/{id};失败回滚并提示。
  • 未登录:列表照常可看(匿名),发布/点赞/举报前拦截并弹现有 AuthGate 登录提示,不发业务请求。
  • 限流/重复提交:直接展示后端 message,前端不自造判断。

K3-B 点赞

  • 剧集点赞:播放页 rail-like 接 POST /app/likes/toggle(targetType=DRAMA),乐观更新,失败回滚并提示;未登录先走 AuthGate。
  • 评论点赞:评论列表项内接 targetType=COMMENT,用返回 {liked, count} 更新该项。
  • 契约缺口(按此处理,不许改后端):AppDramaDetailVO / AppDramaCardVO 没有 likeCount / liked,客户端拿不到剧集点赞初始值。 本批不展示剧集点赞数字(避免假数据),只展示/切换当前会话内的 liked 状态;并在计划文档登记 「建议后端详情与卡片补 likeCount / liked(需 specs 负责人改契约)」。

K3-C 举报

  • 入口:评论项“更多”菜单(举报评论)、详情页更多菜单(举报短剧)。不做举报用户(本批无用户主页入口)。
  • 弹窗:原因单选 + 可选描述,提交 POST /app/reports;成功后提示并关闭。
  • 失败透传:重复举报 你已举报过该内容,我们正在处理、举报自己 不能举报自己发布的内容,一律展示后端 message。
  • 未登录先走 AuthGate。
  • 契约缺口(登记并按下述降级):§1.7 未定义 reasonCode 枚举,后端不校验。本批前端固定 5 项: SPAM(垃圾广告)、ABUSE(辱骂骚扰)、PORN(色情低俗)、AD(引流广告)、OTHER(其他);文案进三语 arb。 计划文档登记「建议后端补 reasonCode 枚举与后台映射」。

K3-D 分享

  • 保持现有系统分享(PlatformShareService),不新增后端接口;只做回归验收(标题/简介/footer 走 l10n)。

三、明确不做(避免误判“没做完”)

  • 拉黑(TC-INTERACT-06):§1.7 无接口,本批不做。
  • 审核联动闭环(TC-INTERACT-09):属后台 2.5 内容治理,已实现,本批不做。
  • 后台敏感词管理、评论审核、举报工单处理:后台已有,本批不动。
  • 评论图片/表情/@提及/无限嵌套、我的评论列表:本批不做。
  • 并发点赞(TC-INTERACT-07):由后端幂等保证,客户端不压测,只做防连点。

四、UI 与规范约束(严格执行 specs)

  • l10n:所有新增文案(评论/回复/审核中/举报原因/失败提示)进 lib/l10n/app_{en,zh,id}.arb 三份齐全并重新生成;禁止硬编码。
  • 视觉:沿用现有深色主题、AppColors、播放页右栏与底部弹窗样式;不自行改主题。
  • 三态:评论列表必须有加载、空、失败(可重试);禁止白屏或假装成功。
  • 分层:新增 comment_api / interaction_api(放 lib/core/data/remote/)、comment_repository、comment_controller;页面只依赖 controller/repository 抽象;沿用 K2 的分层与注入方式。
  • ID:全部按字符串解析(json_numbers.dart),禁止 as int。
  • 不要动:K2 的分类/记录/收藏/下载逻辑、登录注册、播放进度核心;不改 ReelShortWeb、FruitServer 后端、specs。
  • 新文件首行 // Author: fruit.com.cn;public 类/方法必须有文档注释;无 AI 痕迹;测试命名 should_<期望>_when_<条件>。
  • 代码规模红线:函数 ≤50 行、参数 ≤5、类 ≤300 行、单文件 ≤500 行、嵌套 ≤4 层、圈复杂度 ≤10。

五、门禁(不达标不提交)

  1. dart analyze lib test 零告警;flutter test 全过(存量不得跑红)。
  2. flutter build apk --debug 通过;若缺 SDK,贴原始输出并标「未验证」。
  3. 新增测试至少覆盖:评论游标翻页与 hasMore、发布成功/敏感词 BLOCK/REPLACE/REVIEW、删除自己的评论与失败回滚、点赞乐观更新与回滚、举报成功/重复举报 message 透传、未登录 AuthGate、评论点赞计数更新。
  4. 真机/设备侧验证由用户执行(红线 7、10),回执给“用户可照做步骤 + 每步预期”,禁止用开发机 curl 冒充设备证据。
  5. Git:从 develop 拉 feature/flutter-k3-interaction,完成后 git merge --no-ff 回 develop 并 push;不动 main;只 git add 自己的文件。

六、节奏与留痕

  • 先写 FruitServer/docs/plans/2026-10-17-K3.md:范围、影响文件、上述 3 个契约缺口、风险、待确认。
  • TDD:每块先红灯 → 实现 → 绿灯 → 重构;每完成一块跑一次 dart analyze lib test + 定向测试。
  • 每步「叙述 + 命令 + 原始输出 + 解读」追加到 docs/plans/PROGRESS.md。
  • 做完停下写回执:真数据/本地数据页面清单、用户验证步骤与预期、门禁原始输出、提交号。

七、K3 完成回执(2026-10-17)

结论:K3-A 评论、K3-B 点赞、K3-C 举报、K3-D 分享回归均已完成并停止, 未开始 K4。后端、ReelShortWeb、specs 均未修改。

7.1 真数据 / 本地数据页面清单

真数据(走契约 §1.7):

  • 播放页评论弹窗:匿名评论列表、游标加载更多、顶层评论、一层回复、 BLOCK/REPLACE/REVIEW 结果、本人删除、评论点赞。
  • 播放页剧集点赞:POST /app/likes/toggle 切换,乐观更新与服务端状态校准; 不展示剧集点赞数字。
  • 评论更多菜单与详情页更多菜单:评论/短剧举报,固定五项原因,可选描述。

本地/系统能力:

  • 剧集 liked 仅保存在当前 App 会话内。因详情/卡片契约没有 likeCount/liked, App 重启或换账号后不展示服务端初始点赞状态。
  • 分享继续走 PlatformShareService 系统分享,不新增后端接口。
  • “我的 → 喜欢”只过滤当前 feed 的会话内 liked,没有契约外加“我的点赞列表”接口。

按降级方案未做:拉黑、举报用户、举报后台处理、无限嵌套、图片/表情/@、我的评论列表。

7.2 门禁原始结果

$ cd /Users/jianghaifeng/Desktop/ReelShort/ReelShort
$ dart analyze lib test
Analyzing lib, test...
No issues found!

$ flutter test
00:12 +242 ~1: All tests passed!

$ flutter build apk --debug
Running Gradle task 'assembleDebug'...                              6.8s
✓ Built build/app/outputs/flutter-apk/app-debug.apk

~1 是既有 live_watch_flow_test 在 8081 未启动时按测试机制跳过,不是失败。

7.3 Git

  • 功能提交:d361034226ec2bc431548d45068dc60046e5e473 feat(flutter): 接入互动评论点赞举报
  • --no-ff 合并提交:9db6935308e65342be46c81419c2992ed2df1df0 merge: 合并 K3 客户端互动能力
  • 举报本人错误透传修复:1488ee8f85671de96c70347c1b17fb9c4556bd88 fix(flutter): 透传举报本人评论错误
  • 修复合并提交:e1147cfcbabcdf457e427bbd118c32d37f8d9b15 merge: 合并 K3 举报错误透传修复
  • 已推送:45f5f25..9db6935、9db6935..e1147cf develop -> develop
  • author 与 committer 均为 Zhou Weidong <502333560@qq.com>;未操作 main。

7.4 用户真机验证步骤与预期

前置:启动 8081 客户端接口服务,准备可登录测试账号;在 Android 设备安装 build/app/outputs/flutter-apk/app-debug.apk,确认 App 使用设备可访问的后端地址。 以下证据必须由用户在设备侧取得,Codex 未执行真机验证。

  1. 未登录打开任意有评论的剧集,点播放页评论入口。

预期:评论列表正常加载;空列表显示“还没有评论”;断网后显示失败与重试,不白屏。

  1. 未登录点发布、评论点赞或举报,再点剧集点赞。

预期:均先出现登录提示;关闭提示后不发业务请求;登录后可重试。

  1. 登录后发表顶层评论,再输入命中 REPLACE 或 REVIEW 策略的词。

预期:顶层评论出现在列表;REPLACE 展示服务端替换后的内容;REVIEW 显示“审核中”; BLOCK 原样显示后端 message 且输入框内容保留。

  1. 在顶层评论点“回复”并发布。

预期:请求带 parentId;回复归入该楼层,只做一层回复;点“查看回复”可展开。

  1. 连续加载超过首屏 20 条的评论。

预期:按 nextCursor 续拉,不重复、不跳页;没有下一页时加载更多消失。

  1. 点赞一条评论和当前剧集,再点一次取消。

预期:图标立即切换;评论计数在服务端响应后校准;断网时状态回滚并提示; 剧集点赞不显示数字。

  1. 删除自己的评论:更多 → 删除评论 → 二次确认。

预期:成功后从列表移除;制造断网/服务失败时恢复原位置并提示。其他人的评论不显示删除项。

  1. 举报评论与短剧:更多 → 举报;分别选择 SPAM/ABUSE/PORN/AD/OTHER 和可选描述。

预期:成功后提示“举报已提交,感谢反馈。”;重复举报原样显示后端 “你已举报过该内容,我们正在处理”;举报自己发布内容原样显示后端 message。

  1. 点播放页分享。

预期:打开系统分享面板;文本顺序为标题、简介、FruitShorts footer; 切换 en/zh/id 后三类文案同步切换。

  1. 在 Android logcat 保存评论/点赞/举报请求与响应,并保留关键页面截图。

预期:可见 /app/comments*、/app/likes/toggle、/app/reports 的真实响应; 设备侧取证由用户执行,开发机 curl 或本机返回 200 不替代本步骤。

7.5 未验证与已知边界

  • 真机、模拟器、设备日志、设备截图均未由 Codex 执行,状态为“设备侧取证待用户执行”。
  • 剧集点赞初始状态和数字因契约缺口无法展示,App 重启后当前会话状态丢失。
  • 敏感词具体词表、限流阈值、后台审核联动由后端/运营环境决定,本轮只做客户端透传与状态展示。
  • 拉黑与 K4 不在本轮范围。

[已处理·待用户验收] 2026-10-17 | K2 客户端第二批:分类 + 观看记录 + 收藏列表 + 下载授权(A3/A5 剩余)+ UI

用户指令(2026-10-17):「现在先做客户端 对接相关接口 及ui 严格执行specs 内的要求」。

本批只做 K2,做完停下写回执,不要连做 K3/K4。 规范以 specs 为准(docs/plans/codex-研发任务清单.md 阶段 A3/A5、docs/16-测试用例库.md CONTENT/RECORD、docs/09-测试计划.md); 接口只认 FruitServer/docs/api-phase1.md §1.3 / §1.5 / §1.6,契约里没有的接口一律不许自造。

〇、开工前必读(按 specs 顺序,缺一不可)

  1. AGENTS.md(项目上下文、强制约束、自主执行与沟通节奏)
  2. CODING_STANDARDS.md(三端红线、Dart/Flutter 专章、门禁总表)
  3. GIT_WORKFLOW.md(Git Flow、提交格式、审查门槛)
  4. FruitServer/docs/api-phase1.md §0 / §1.3 / §1.5 / §1.6(接口唯一权威)
  5. docs/plans/codex-研发任务清单.md(A3/A5)、docs/16-测试用例库.md(CONTENT/RECORD)、docs/09-测试计划.md
  6. 本 INBOX 条目

硬性执行口径(摘自上述规范,违反即打回)

  • Git:从 develop 拉 feature/flutter-k2-library 开发;提交信息 type(scope): 描述(type ∈ feat/fix/docs/test/refactor/style/perf/chore,≤50 字符,一个 commit 只做一件事);完成后 git merge --no-ff 回 develop 再 push;禁止直接改 main;只 git add 自己的文件,禁止 git add -A/.。
  • TDD:先写测试(红灯)→ 实现(绿灯)→ 重构;测试命名 should_<期望>_when_<条件>;核心类(repository/service/logic)必配 *_test.dart。
  • Dart:provider binding/logic/view 三层;dio;全程 async/await,禁裸 then();IO 失败用结果对象/控制器错误字段承接,不把异常直接抛到 UI;禁止空 catch。
  • 代码规模:函数 ≤50 行、参数 ≤5、类 ≤300 行、单文件 ≤500 行、嵌套 ≤4 层、圈复杂度 ≤10;除 0/1/-1/null/空串/布尔 外禁止魔法值。
  • 注释:每个 public 类/方法必须有文档注释;文件头写一行中文职责;新文件首行 // Author: fruit.com.cn;代码/注释/提交信息不得出现任何 AI 痕迹。
  • 留痕:每步「叙述 + 工具命令 + 原始输出 + 解读」,追加到 docs/plans/PROGRESS.md;做前先写 docs/plans/2026-10-17-K2.md(步骤/影响文件/风险/待确认)。
  • l10n 生成物(lib/l10n/app_localizations*.dart)豁免作者头,但 .arb 必须手写三份。

一、PM 已勘察的现状(实测,直接用)

  • ReelShort(develop 00280ca)已接:/app/home/feed、/app/discover/feed、/app/dramas/{id}、/app/episodes/{id}/play、/app/watch/progress(POST/GET)、/app/watch/recent、DELETE /app/watch/progress/{dramaId}、/app/favorites(toggle/list/delete)、账号 4 个接口。
  • 未接:GET /app/categories、观看记录删除/清空、下载全部 8 个接口、/app/me*(属 K4)、互动(属 K3)。
  • 现状缺陷:
    • 发现页分类是本地按 sections 下标硬编码(discover_view.dart),不是 GET /app/categories;
    • "我的-记录"只渲染能在已加载首页/发现目录里匹配到的剧(mine_view.dart),匹配不到就消失;
    • 收藏列表嵌在"我的"页签里,不能单独刷新/取消收藏;
    • 下载完全没有。

二、本批范围(4 块,按顺序做)

K2-A 分类(A3 剩余)

  • 接口:GET /app/categories → [{ id: string, name: string }](J1:id 是字符串)。
  • UI:发现页分类条改为服务端分类;点分类展示该分类的剧。
    • 内容来源只允许 GET /app/discover/feed 的 sections[] 中 code == "category-{id}" 的分区(后端已按分类生成,见 AppCatalogService#discoverFeed)。
    • 某分类在 discover 里没有对应分区 → 显示空态,不要自己拼请求/自造接口。
    • 保留现有 hot / new 分区的入口与样式。

K2-B 观看记录页(A5 剩余)

  • 接口:
    • GET /app/watch/recent?limit=20 → [{ dramaId, episodeId, positionMs, durationMs, finished, playCount, updatedAt }](全部 ID 为字符串)
    • DELETE /app/watch/progress/{dramaId}、DELETE /app/watch/progress(清空)
  • 契约缺口(必须按下面方式处理,不许改后端):recent 不返回剧名/封面。做法:
    • 先去重 dramaId,再对每个 id 调 GET /app/dramas/{id} 补卡片;
    • 并发上限 4,结果按 dramaId 缓存(同页内不重复请求);
    • 单条详情失败不能拖垮整页:该条显示占位(剧名"加载失败"+重试),其余正常渲染;
    • 在计划文档里记为「契约缺口:建议后续在 ProgressRespVO 补卡片字段(需 specs 负责人改契约)」。
  • UI:独立"观看记录"页(入口:我的 → 观看记录)
    • 每条:封面、剧名、第 N 集、positionMs/durationMs 进度、更新时间;点击进详情并续看;
    • 单条删除、清空(二次确认);删除乐观更新,失败回滚并提示;
    • 未登录 → 复用现有 AuthGate 登录引导(接口需要登录,见 SecurityConfig);
    • 三态:加载中 / 空("还没有观看记录")/ 失败(可重试)。

K2-C 收藏列表页(A5 剩余)

  • 接口:GET /app/favorites(返回 AppDramaCardVO[],与首页卡片同结构)、DELETE /app/favorites/{dramaId}、POST /app/favorites/toggle。
  • UI:独立"我的收藏"页(我的 → 收藏),复用首页卡片渲染:
    • 取消收藏(按钮或长按/左滑,二选一但要能发现)→ 乐观更新 + 失败回滚;
    • 点击进详情;空/错/载三态;未登录 → AuthGate。
  • 契约缺口:TC-RECORD-04 写"分页正确",但 GET /app/favorites 没有分页参数、返回全量数组 → 本批按全量实现,计划文档登记该缺口,不许自造 page/size。

K2-D 下载授权与管理(A5 最后一块,不做实际落盘)

  • 接口(§1.6,全部需登录;设备号优先请求体、其次 X-Device-Id,现有 ApiClient 已带):
    方法 路径 本批用途
    GET /app/downloads/policy 配额/授权天数/wifiOnly/清晰度/已用/剩余/expirePolicyText
    PUT /app/downloads/settings 仅 Wi-Fi、最高清晰度
    POST /app/downloads 申请授权(body episodeId,quality 可省=AUTO)
    GET /app/downloads 我的下载授权列表
    GET /app/downloads/{episodeId}/url 续传前刷新短时效地址(本批只提供 API 方法)
    POST /app/downloads/{episodeId}/progress 进度上报(本批只提供 API 方法)
    DELETE /app/downloads/{episodeId} 删除单条
    DELETE /app/downloads 清空,返回条数
  • UI:
    • 详情页/播放页加"下载"入口:调 POST /app/downloads;付费/未解锁/配额满时直接展示后端 message,不要自造判断(判定归服务端);
    • 我的 → 下载:策略卡(剩余配额、授权天数、expirePolicyText)、授权列表(剧名、集号、status、taskStatus、progressPercent、到期时间)、单条删除、清空、仅 Wi-Fi/清晰度设置;
    • 状态枚举全部后端返回值驱动(ACTIVE/EXPIRED/REVOKED、QUEUED/DOWNLOADING/PAUSED/COMPLETED/FAILED/CANCELED),前端不新增枚举;
    • 空/错/载三态 + 未登录 AuthGate。
  • 明确不做(写进计划文档,避免误判"没做完"):文件分片落盘、断点续传、离线播放、licenseKey 本地校验 —— 需要存储/权限/加密播放方案,另立批次。本批只交付"授权与状态管理"。

三、UI 与规范约束(严格执行 specs)

  • 语言:所有新增文案必须进 lib/l10n/app_{en,zh,id}.arb 三份齐全并重新生成;禁止硬编码中文/英文。印尼语为首发语言。
  • 视觉:沿用现有深色主题与 AppColors/组件风格(specs 里未提供 Stitch 出稿;若用户后续给出稿,再单独做视觉替换,本批不自行改主题)。
  • 三态:每个新页面/列表都要有加载、空、失败(可重试);失败文案走 errorText + l10n,禁止白屏/假装成功。
  • 分层:新增 category_api / download_api(放 lib/core/data/remote/),页面只依赖 repository/controller 抽象;沿用 K1 的分层与依赖注入方式。
  • ID:19 位雪花 ID 一律按字符串解析(json_numbers.dart),禁止 as int;计数/时长同理按契约类型处理。
  • 不要动:首页/发现已接 feed 逻辑(分类来源替换除外)、登录注册、播放/进度/收藏已有逻辑(除必要接线)、ReelShortWeb、FruitServer 后端。
  • 新文件首行 // Author: fruit.com.cn;测试命名 should_<期望>_when_<条件>。

四、门禁(不达标不提交)

  1. dart analyze lib test 零告警;flutter test 全过(存量 33 个测试文件不得跑红)。
  2. flutter build apk --debug 通过(CODING_STANDARDS §6 门禁总表含构建);若本机缺 Android SDK,回执里贴失败原始输出并标「未验证」,不得静默跳过。
  3. 新增测试至少覆盖:分类→section 映射(含无分区空态)、记录页 N+1 详情请求去重/并发上限/单条失败降级、删除与清空回滚、收藏取消回滚、下载策略/授权列表渲染、未登录 AuthGate;核心类覆盖率 ≥80%。
  4. 真机/设备侧验证由用户执行(红线 7、10):回执里给"用户可照做的步骤 + 每步预期结果",禁止用开发机 curl 冒充设备可达;拿不到设备证据就标「未验证」。
  5. Git:feature/flutter-k2-library 开发 → git merge --no-ff 回 develop → git push origin develop;不动 main;合并/推送前再跑一次门禁。

五、节奏与留痕

  • 先写 FruitServer/docs/plans/2026-10-17-K2.md:范围、影响文件清单、上述 3 个契约缺口、风险、待确认。
  • TDD 节奏:每块先写测试(红灯)→ 实现(绿灯)→ 重构;每完成一个模块跑一次门禁,零告警才继续。
  • 每步在公司 PROGRESS.md 追加「叙述 + 命令 + 原始输出 + 解读」(PM 验收口径,缺一即打回)。
  • 做完停下写回执(追加到 docs/plans/PROGRESS.md + 本条下),回执必须含:
    • 哪些页面已走真数据、哪些仍是本地/未做;
    • 用户可照做的验证步骤与每步预期;
    • 测试与 analyze 的原始输出;
    • 提交号。
  • 禅道:开工/完成各同步一次任务状态。

六、PM 登记:specs 契约缺口(Codex 不要动,反馈给项目负责人)

  1. 搜索:任务清单 A3 写了"搜索"、TC-CONTENT-05 有搜索用例,但 api-phase1.md 没有 /app/search 接口 → 本批不做搜索。
  2. GET /app/favorites 无分页参数,与 TC-RECORD-04"分页正确"不一致。
  3. GET /app/watch/recent 不返回剧名/封面,记录页需 N+1 详情请求(建议后端补卡片字段)。

用户裁定(2026-10-17):先按现状契约执行,上述缺口后期再补。本批不得因缺口停工,也不得临时改后端或自造接口;按第二节的降级方案实现,缺口留到后续批次。

七、Codex 回执(2026-10-17)

状态:K2-A/B/C/D 代码、自动化测试、Android debug 构建与 develop 推送已完成; 真机/模拟器侧业务验证未验证,按红线交用户执行。用户照做清单与测试跳过口径见 docs/plans/PROGRESS.md「动作 17:用户真机验收清单」。K3/K4 未开始。

提交

feature: cc4d7c5a9dc6a4f6f0565a32503cce5973af703c
merge:   45f5f25640a274edb51bd90b3b5e52c43ba9d9cd
branch:  feature/flutter-k2-library -> develop(--no-ff,已 push,feature 已删除)
main:    未改动

已接真实契约数据

  • 发现页分类:GET /app/categories + GET /app/discover/feed; 服务端分类只匹配 category-{id},无分区显示空态。
  • 独立观看记录页:GET /app/watch/recent?limit=20、按 dramaId 去重并发上限 4 补详情、 DELETE /app/watch/progress/{dramaId}、DELETE /app/watch/progress。
  • 独立我的收藏页:GET /app/favorites、POST /app/favorites/toggle、 DELETE /app/favorites/{dramaId}。
  • 我的下载页:§1.6 全部 8 个 /app/downloads* 接口;详情页/播放页下载入口调用 POST /app/downloads,拒绝时直接展示后端 message。
  • 三页面均为加载 / 空 / 失败可重试三态;未登录显示登录引导。

仍本地或未做

  • 搜索未做:契约没有搜索接口。
  • 文件分片落盘、断点续传、离线播放、licenseKey 本地校验未做;本批只交付授权与状态管理。
  • 点赞/评论仍沿用既有本地状态,属 K3,不在本批。
  • 发现 feed 当前只下发 category-{id};若后端没有 hot/new 分区,保留的入口显示空态。
  • 收藏接口仍返回全量数组,本批未发送 page/size。

门禁原始输出

$ dart analyze lib test
Analyzing lib, test...
No issues found!

$ flutter test
00:11 +220 ~1: All tests passed!

$ flutter build apk --debug
Running Gradle task 'assembleDebug'... 2,377ms
✓ Built build/app/outputs/flutter-apk/app-debug.apk

~1 是既有真实后端联调用例因本机 8081 服务未启动而按设计跳过,不是失败。 首次冷构建也成功,耗时 705.7s;最终修复后缓存构建 2,377ms。

用户验证步骤与预期

  1. 在 ReelShort 执行 git checkout develop && git pull origin develop,确认 HEAD 为 45f5f25;后端 8081 已启动,手机与开发机在同一局域网。
  2. 取局域网地址并运行:IP=$(ipconfig getifaddr en0), flutter run -d <device-id> --dart-define=API_BASE_URL=http://$IP:8081。 预期:App 启动、首页/发现能加载;地址必须按当前网络重新获取。
  3. 打开发现页:分类条应出现 /app/categories 返回的分类。选择一个有内容分类, 预期显示 discover feed 对应 code=category-{id} 的剧;选择无分区分类显示空态。
  4. 播放任意免费集约 10 秒后返回,进入“我的 → 观看记录”。 预期:出现该剧封面/剧名/集号/进度/更新时间;即使该剧不在首页分区也能显示。 单条删除先立即消失;断网重试删除时,记录应恢复并提示失败。清空应先弹二次确认。
  5. 在详情或播放页收藏一部剧,进入“我的 → 我的收藏”。 预期:卡片来自服务端;点粉色收藏按钮取消后立即消失;断网取消应恢复卡片并提示失败。
  6. 登录后在详情页/播放页点下载入口。 预期:成功时提示“下载授权已就绪”;付费、未解锁或配额满时,直接出现后端 message。 进入“我的 → 下载”,预期策略卡显示剩余配额/授权天数/expirePolicyText; 列表显示后端 status、taskStatus、progressPercent、到期时间; 仅 Wi-Fi、清晰度、单删、清空可操作。
  7. 退出登录后分别进入观看记录、我的收藏、我的下载。 预期:三页都显示登录引导,不显示网络错误或白屏。

证据口径

  • 以上设备侧步骤尚未执行,因此真机/模拟器验证统一标记「未验证」。
  • 没有用开发机 curl 冒充设备可达证据。
  • 本批不包含实际文件下载,因此不能据此验收“离线播放”。

[已处理] 2026-10-17 | 三库全量拉取 + main → develop 同步(Trae 执行,无代码改动)

用户指令(2026-10-17):「先拉最新全量代码,三个库全拉,相关规范已经备齐、接口文档完备、INBOX更新、环境地址清单在docs下21-环境地址清单.md 同步main 分支到开发分支」。

一、执行结果(实测)

仓库 分支 HEAD 落后 origin/develop 领先 origin/main 未并入 工作区
FruitServer develop 2d70858 0 0 0 干净
ReelShort develop 00280ca 0 0 0 干净
ReelShortWeb develop 3ec9ea6 0 0 0 干净
  • 三库 git fetch --all --prune 后均无新提交,无需 pull。
  • ReelShort 是唯一有 main 未同步的库:eaf8fb4 style: 全量补充作者标识 与 develop 改名(nontonki→fruitshorts)冲突 15 个文件。 解法:保留 develop 口径(package:fruitshorts)+ 按文件类型补回署名头(.dart → //,.yaml → #,.xml → <!-- -->); main 版复活了 develop 已删除的 5 个文件(test/catalog/demo_media_test.dart、test/catalog/home_feed_category_test.dart、 lib/pages/home/home_feed_sections.dart、lib/pages/home/widgets/home_feed_header.dart、lib/pages/home/widgets/category_swipe_region.dart)→ 已 git rm; lib/l10n/app_localizations*.dart(生成文件)不加署名头,已还原。 门禁:dart analyze lib test → No issues found!;提交 00280ca(作者 jianghaifeng <haifeng_hello@163.com>),已推 origin/develop。

二、⚠️ 待 PM / 用户确认

  1. docs/21-环境地址清单.md 在 specs 仓库中不存在。已核对:specs docs/(仅 01~20 + README + plans + 参考规范材料清单 + 代码现状盘点)、三个代码仓 git ls-files、specs 全部远端分支(只有 origin/main)—— 均无此文件。请确认是尚未推送,还是在别的路径/仓库。
  2. specs 本地仍有 22 笔提交未推送到远端(无写权限,origin/main 停在 c528345)。若以远端为准,需要决定是否把本地回退到 origin/main。

三、用户裁定(2026-10-17)

  1. specs 只有 main,只需同步 → 已将本地 specs 强制对齐到 origin/main(c528345),本地领先的 22 笔未推送提交已丢弃;此后 specs 只拉取,不提交。
  2. specs 是唯一权威 → 规范、接口契约、任务清单一律以 specs 为准;与本仓库文档冲突时以 specs 为准。
  3. 执行前置动作 → Codex 每次开始任务前必须先读本文件(INBOX),按条目顺序执行;新条目由 PM 追加,不覆盖历史。

[已交接 · 未验证(真机验证改由用户执行)] 2026-10-16 20:30 | K1-verify:用正确地址重做真机验证(设备侧取证)

用户指令(2026-10-16 20:20):「让 Codex 用正确地址重做一次真机验证」。

背景(为什么必须重做):你 1-3/1-4 那批的真机验证用的是 --dart-define=API_BASE_URL=http://10.157.242.1:8081 —— 该地址是开发机某块 VPN 网卡(utun)的地址, 现已不存在,手机侧没有这块网卡,所以按它构建、安装到手机上的 App 在真机上必然请求失败 (用户实测提示「剧目列表加载失败」,报错里就显示着这个地址)。 更关键的是:当时那条"证据"是在开发机上 curl 本机网卡地址拿到 200 —— 本机 curl 本机必然 200, 不构成真机可达的证据(假阳性)。PM 已把 specs 里 4 处记录修正并作废该结论(481d4bb)。

一、重做验证(必须设备侧取证,见红线第 10 条)

  1. 先取真实地址,不要硬编码:ipconfig getifaddr en0 拿开发机局域网 IP(PM 实测当前 192.168.31.187;换网络会变)。
  2. 前置条件:手机与开发机在同一局域网;iPhone 已授权「本地网络」权限(设置 → 隐私与安全性 → 本地网络)。
  3. 真机跑:flutter run -d 00008101-000875813E42001E --dart-define=API_BASE_URL=http://<步骤1得到的IP>:8081
  4. 证据至少一条来自设备侧(优先级从高到低):
    • 首选:加设备侧集成测试(integration_test 依赖 + integration_test/ 目录), 用 flutter test integration_test -d <device> --dart-define=API_BASE_URL=http://<IP>:8081 在手机进程里 真实请求 /app/home/feed 与 /app/dramas/{id},断言 code=0 且列表非空。 —— 这是可重复、可入库的最硬证据,也顺带把"设备验证"变成可回归的。
    • 或:App 运行时抓从手机 IP 到 8081 的已建立连接(lsof -nP -iTCP:8081 | grep <手机IP>), 并同时给出后端侧确实收到该请求的证据。
    • 或:手机侧截图(device 截图不支持无线 iPhone —— 那就用设备侧测试或连接证据替代)。
  5. 若都拿不到:如实标「未验证」,并说明卡在哪一步(同网段?权限未授权?),不许含糊。
  6. 顺带确认原故障已消失:首页「剧目列表」能出内容(或由步骤 4 的测试断言覆盖)。

二、留痕要求(只写本仓库,别动 specs)

  • docs/plans/PROGRESS.md(本仓库 FruitServer 这份)追加逐动作留痕:命令 + 原始输出 + 解读。
  • 本条下写回执,含用户可照做的验证步骤 + 每步预期结果,并明确标注"证据来自设备侧"。
  • 不要去改 specs 仓库(它只拉取;那 4 处 PM 已修正,别重复改)。

三、门禁与 git

  • dart analyze lib test 零告警 + flutter test 全过(新增的 integration_test 一起跑;若它依赖真机、无法在纯本机跑,就在回执里说明并给出真机执行命令与输出)。
  • 在 ReelShort develop 提交并 git push origin develop;不动 main;新文件首行 // Author: fruit.com.cn。

四、节奏

做完停下写回执,不要连做下一批。


[已处理·待用户验收] 2026-10-16 19:40 | K1 客户端对接:详情 + 播放 + 进度续看 + 收藏(看剧闭环)+ UI 调整

用户指令(2026-10-16 19:30):「现在你要做的事对接客户端接口及 UI 调整,仍然严格按照 specs 执行」→ 转到 客户端 ReelShort(Flutter),按 specs 阶段 A 推进。

一、PM 已勘察的现状(实测,直接用)

客户端目前只调了 6 个 C 端接口(契约里已实现 44 个):

/app/home/feed   /app/discover/feed
/app/auth/register   /app/auth/login   /app/auth/refresh   /app/auth/logout
  • lib/core/data/remote/ 下只有 api_client / api_exception / api_interceptors / auth_api / catalog_api —— 没有 playback / watch / library / interaction / me 的 api 类。
  • WatchHistoryRepository 是 SharedPreferencesWatchHistoryRepository(本地存储),mine_logic 还在用 demoProfileId 假数据 —— 观看进度/续看位置全在本机,没接 /app/watch/*。
  • EngagementRepository 是 SharedPreferences / InMemory —— 点赞/收藏状态也是本地假数据。
  • 已有:dio 网络层 + 拦截器(A1 ✅)、改名 + 印尼语(✅)、json_numbers.dart 兼容字符串 id(J1 ✅)。

二、本批范围(K1:一条完整看剧闭环)

目标:从首页点进一部剧 → 详情正确 → 能起播 → 进度能续看 → 能收藏,全链路走真接口。

  1. 详情(A3 的一块):新增 catalog_api 方法对接 GET /app/dramas/{id}(含剧集列表;免费集带已签名播放地址)。
  2. 播放(A4):新增 lib/core/data/remote/playback_api.dart
    • GET /app/episodes/{id}/play → 票据 + 签名地址。签名地址原样使用,不要拼接/改写(文档硬要求)。
    • 起播前取票据;票据/签名地址有有效期,过期要重新取,不要缓存到失效后还用。
  3. 进度与续看(A5 的一块):
    • POST /app/watch/progress(每 10s 一次,切集/退出/切后台各补一次)
    • GET /app/watch/progress?dramaId=(进入详情/播放时定位续看位置)
    • GET /app/watch/recent?limit=20("我的"页最近观看)
    • 把 WatchHistoryRepository 的本地实现换成远程实现(保留抽象接口,测试注入内存假实现);mine_logic 去掉 demoProfileId。
  4. 收藏(A5 的一块):POST /app/favorites/toggle、GET /app/favorites、DELETE /app/favorites/{dramaId};收藏状态以服务端为准(不要再用本地 EngagementRepository 存收藏)。
  5. UI 调整(本批必须一起做):
    • 详情页 / 播放页 / "我的"页:加载中 / 空 / 失败三态齐备(失败要能重试,不要白屏或假装成功)
    • 进度条与续看定位、收藏按钮的真实状态与乐观更新回滚
    • 断网/超时的提示文案走 l10n(en / zh / id 三份都要有)

三、口径与约束(照 specs)

  • ID 是字符串(J1 刚统一):解析走 json_numbers.dart,不要直接 as int。计数类(viewCount/expiresIn)也是字符串。
  • accessType 等枚举用后端返回值驱动 UI(免费/付费/试看),不要在前端写死;付费集解锁不在本批(属 B2)。
  • 接口层与业务层分离:新增 api 类、controller/logic 只依赖 repository 抽象(沿用现有 catalog_api + catalog_controller 的分层做法)。
  • 不要动已对接的首页/发现/登录注册逻辑(除必要的最小接线)。
  • 新文件首行 // Author: fruit.com.cn;测试命名 should_<期望>_when_<条件>。

四、门禁

  • dart analyze lib test 零告警 + flutter test 全过(存量 27 个测试文件不得跑红;新增测试要覆盖:票据过期重取、进度上报节流、续看定位、收藏切换回滚、失败态)
  • 真实后端联调(8081):跑一次端到端并留证据 —— 首页 → 详情 → 播放 → 上报进度 → 重新进入看到续看位置 → 收藏出现在收藏列表
  • 在 ReelShort develop 提交并 git push origin develop;不动 main

五、节奏

先写 docs/plans/2026-10-16-K1.md(计划 + 影响面 + 风险)→ 做 → 停下写回执(含用户可照做的验证步骤 + 每步预期结果,以及"哪几个页面现在走真数据、哪几个还是假的"的清单)。不要连做 K2。

后续批次(预告,不用现在做):K2 = 记录/最近观看页 + 收藏列表页 + 分类/搜索 + 下载授权(A5/A3 剩余);K3 = 互动(评论/点赞/举报/分享,A6);K4 = 账号剩余(资料/设备/注销;Google 登录需 Google OAuth 凭据,属待你提供的付费/密钥类)。


[已处理 · 待用户验收] 2026-10-16 18:30 | J1 全局 ID 字符串化(Long → String)+ 契约统一

PM 独立复核(Trae 2026-10-16 19:05)—— 交付 FruitServer f35db2c / ReelShortWeb 3ec9ea6 / ReelShort 94ff9f1(三仓均在 develop 且与远端同步),验证通过。

  • 门禁复跑:mvn test 110 → 115 条全过(commerce 33 / admin-api 16→21 / app-api 49 / worker 12)+ BUILD SUCCESS ✅
  • 全局配置落位核实:fruitshorts-framework/.../web/JacksonLongToStringConfig.java 存在(一处生效两端,未改 yml)✅
  • 端到端真实验证(PM 亲自跑,非采信回执):新注册用户雪花 id 2111018722574602241 → ① /admin/app-users/page 返回 "id":"2111018722574602241"(字符串)、无裸数字大 id、"total":2 仍是数字(符合约定); ② 用该 id 调 /admin/app-users/{id} → code=0(修复前此场景即 200002); ③ 真 node 逐位比对:typeof parsed.id === 'string'、String(parsed.id) 与库值逐位相等 = true;测试数据已清理(残留 0)。
  • 按红线第 7 条,最终验收权仍归用户。

⚠️ 三处口径待用户点头(PM 意见附后):

  1. 唯一例外 PageResult.total 保持数字 —— PM 意见:接受(它是条数不是 id,前端分页组件要数字);但需在契约 §0「ID 一律字符串」处显式写出这个例外,否则前端会困惑。待 Codex 补一句。
  2. 客户端改动比"只改 id 解析"略宽(物理上必须) —— Codex 查明 Flutter 的 id 本来就是 toString() 不会崩,真正会崩的是被全局规则扫到的 Long 计数,故只改了 expiresIn、viewCount 等 3 处 + 新增 json_numbers.dart。PM 认可这个判断与最小改动口径。⚠️ 另需用户知晓:ReelShort 原先只有 main,Codex 新建了 develop 并推送;若客户端同事仍在 main 上开发,这笔需要带过去。
  3. Codex 自我纠正了一处统计错误 —— 它第一版清理脚本用 psql ... returning 1 | wc -l 数行数,数到的是 psql 命令标签(删 0 行也输出 1),那轮"删除 1 行"是假数字;随后用全库扫描复核(uid/邮箱/雪花 id 零命中)确认无残留。PM 认可其如实披露。

📌 仍挂着:禅道里同样没有 J1 任务(项目 2 下只有 S0/S1、A1–A6、B1/B2、C1–C3),无可同步状态。

⚠️ PM 顺手说明:为做端到端验证,8080(PID 80375)/ 8081(80383)/ 3005 均为 Codex 重启后的新 jar,接口与页面正常。

用户裁定(2026-10-16 18:10):雪花 id 精度问题 「按方案 (a) 全局修改,统一契约」。

背景(两方独立复现同一根因,故现在铺全局):

  • 你在 I1 里实测到:UI 点「下架」后端回 200002 公告不存在 —— 库里 2111011206851596290,浏览器 JSON.parse 后变 2111011206851596300,回查自然找不到。你把修复限定在本轮两个模块并留了待裁定。
  • PM 独立复核同样确认:新注册用户 id 2111012753262120962 由 /admin/app-users/page 以裸 JSON 数字下发;用真 JS 引擎验证 JSON.parse('{"id":2111012753262120962}').id → 2111012753262121000(不相等)。根因是 JS Number.MAX_SAFE_INTEGER = 9007199254740991,19 位雪花 id 必然超出;而服务端没有全局 Long→String 序列化配置。
  • 种子数据都是小 id(4/5/25)所以老页面一直正常 —— 这是只要"新建后立刻拿 id 操作"就必然踩的坑。

一、后端:全局序列化(放 fruitshorts-framework,两个入口都生效)

  1. Long.class 与 Long.TYPE 一律用 ToStringSerializer。
  2. ⚠️ 不要动 Integer / int(分页 pageIndex/pageSize 等保持数字)。
  3. 必须覆盖:普通字段、嵌套对象、List/Map 的值、分页 list。
  4. 反序列化方向也要验:请求体 / 路径变量 / 查询参数里 id 传字符串要能正确绑定成 Long(前端回传的就是字符串)。

二、契约统一(docs/api-phase1.md §0 通用约定)

  • 写明「所有 ID 字段一律以字符串下发」(含主键与外键 id),并给示例。
  • 把 I1 里逐模块的描述(商品/公告 VO 各自加 ToStringSerializer)收拢到这条总口径,不要各写一套。
  • 顺手把 §2.11/§2.12 里与总口径重复的表述改成引用 §0。

三、两个前端适配

  1. ReelShortWeb:审计现有页面有没有把 id 当数字用 —— parseInt(id) / Number(id) / id + 1 / 拿 id 当数组下标 / === 与数字比较 / sort((a,b)=>a.id-b.id),逐处改成字符串语义。本轮新加的两个页面已按 string 写,别回退。
  2. ReelShort(Flutter):fromJson 里 json['id'] as int 这类写法在字符串下发时会直接抛异常,必须审计并修。策略二选一(推荐前者,改动小且向前兼容):
    • 兼容解析:统一按"接受字符串或数字"的方式解析(如 int.tryParse(v.toString())),或把模型里的 id 保留为 String;
    • 或全量改成 String(改动更大,要与客户端同事对齐)。
    • ⚠️ 客户端现由另一同事在推进:只做"ID 解析"这一类最小改动,不要顺手改别的;有分歧或改不动就写「【待 Trae 协助】」,并在回执里给出逐文件清单。

四、验证(必须有证据,不接受"应该没问题")

  1. 后端契约测试:Long 字段确实以字符串输出(含嵌套 / 分页 list / Map)。
  2. 端到端复现 I1 那个 bug 并证明已消失:admin 新建一条记录 → 用响应里的 id 调详情 / 编辑 / 上下架 → 成功(全局范围内,不再只限商品/公告)。
  3. JS 侧一致性证据:拿真实响应喂 node -e 'const d=JSON.parse(...); console.log(String(d.id))',证明与库里的值逐位相等。
  4. C 端:注册 / 播放票据 / 订单等返回的 id 也是字符串,且客户端解析不崩(Flutter 侧跑测试)。

五、门禁与 git

  • mvn test 存量 110 条不得跑红 + 新增测试;Web pnpm ts:check + eslint 零错误 + pnpm build:pro 通过;若改了客户端则 dart analyze lib test 零告警 + flutter test 全过。
  • FruitServer / ReelShortWeb / ReelShort 各自在 develop 提交并推送;绝不动 main。
  • 新写文件首行 // Author: fruit.com.cn(Vue/TS 同;Dart 用 // Author: fruit.com.cn)。

六、节奏

先写 FruitServer/docs/plans/2026-10-16-J1.md(方案 + 影响面清单 + 风险)→ 做 → 跑门禁 → 停在干净交接点写回执,回执必须含:

  • 每个仓库改了哪些文件(客户端逐文件清单要单独列)
  • 「用户可照做的验证步骤 + 每步预期结果」
  • 若客户端有未改完/有分歧的部分,如实列出(不许含糊)

[已处理·待用户验收] 2026-10-16 17:30 | I1 后台管理端:商品(金币包)配置 + 系统公告管理(后端接口 + Web 页面)

用户指令(2026-10-16 17:10):「现在做后台管理端,规范还是按照 specs」+「ReelShortWeb 就是这个项目」→ 后台管理端 = ReelShortWeb(Vue3 + Element Plus),本批后端接口与页面一起做。

一、PM 已勘察的两个缺口(实测证据,直接用)

# 缺口 证据
1 商品/金币包配置:后台完全没有入口 product 表 16 个字段已建(code/name/type/platform/store_product_id/coin_amount/bonus_coin/price_amount/currency/display_price/region_codes/vip_days/sort/status/effective_at/expire_at)、已有 3 行种子数据、C 端 /app/orders/verify 依赖 store_product_id 对齐商店商品(错误码 200002 该商品未在后台配置 就是为它留的);但 admin-api 29 个 Controller 里没有任何商品 CRUD 接口(grep 只命中 WalletAdminController /coins 的发币补偿)→ 运营改商品只能改库。PRD §09 明确要求「商品配置与价格分层」
2 系统公告:有读没写 ops_notice 表 0 行;E1 已交付 C 端 GET /app/notices(匿名只读);契约 §2.7 原话「系统公告 ops_notice 目前没有产出方,等有公告功能再接」→ 后台没有公告管理接口,运营发不了公告

除这两项外,admin-api 对照契约 §2.1–§2.10 全部已实现,不要重做。

二、后端(FruitServer / admin-api)

  1. 契约先行(依据 12-上线检查清单 §一):先在 docs/api-phase1.md 写清两个模块的接口、字段、错误码、权限点(放在 §2.6 订单与权益之后,或新增一节),再动代码。
  2. 商品配置 /admin/products:
    • GET /admin/products/page(筛选:type / status / platform / keyword,分页沿用既有口径)、GET /admin/products/{id}
    • POST /admin/products、PUT /admin/products/{id}、PUT /admin/products/{id}/status(上下架)、DELETE /admin/products/{id}
    • 字段按 product 表现有结构,不要改表
    • 必须有校验:code 在全局唯一、store_product_id 在同一 platform 下唯一(否则同一商店商品被重复配置 → 发币口径混乱);type=COIN 时 coin_amount > 0;type=VIP 时 vip_days > 0
    • ⚠️ VIP 商品口径:B1-1 已定「VIP 订阅本轮不做」→ 允许建但不允许启用(启用报明确错误)或直接拒绝创建,二选一由你定,写进契约并在回执里说明;拿不准就写「【待 Trae 协助】」
    • 权限点命名沿用现有习惯(参考 commerce:*),并落 sys_permission + sys_menu
  3. 系统公告 /admin/notices:
    • GET /admin/notices/page、GET /admin/notices/{id}、POST、PUT /admin/notices/{id}、PUT /admin/notices/{id}/status(发布/下架)、DELETE /admin/notices/{id}
    • 字段按 ops_notice 表现有结构,不要改表
    • 与 C 端 GET /app/notices 的筛选口径必须严格对齐(E1 已定:status=1 且 target ∈ {ALL, USER}、publish_at <= now、expire_at 为空或未过期、按 publish_at desc, id desc)—— 后台发布后 C 端能查到、过期/下架后查不到
    • 权限点 + 菜单同上
  4. 菜单与权限点落库:写进 sql/data-phase1.sql(幂等),注意沿用既有保留段、不要与现有 id 冲突(现有菜单到 id 30)。建议:商品配置挂 订单与权益(id 22) 下;系统公告可挂新的运营分组,具体由你按现有约定定。超管必须可见且可授权。
  5. Swagger 中文注解按 S1 规范补齐(@Tag / @Operation)。
  6. 门禁:mvn test 存量 91 条不得跑红 + 新增契约测试,至少覆盖:store_product_id 同平台唯一、code 唯一、type 校验、公告发布后 C 端可见 / 过期或下架后不可见、越权(无权限点被拒)。

三、后台页面(ReelShortWeb)

挂载机制(PM 已勘察):路由是静态注册在 src/router/index.ts(component: () => import('@/views/...')),而菜单树来自 DB sys_menu,其中 component 字段必须与视图路径一致(例:views/contentManagement/DramaManagementView)。两边都要加。

  1. 新增两个页面(放在与现有页面同级的目录,命名沿用 XxxManagementView.vue 风格):
    • 商品配置:列表(分页/筛选)+ 新建/编辑弹窗 + 上下架 + 删除;表单字段按 product 表;金额/金币用数字输入并做前端校验
    • 系统公告:列表 + 新建/编辑(标题/正文/类型/目标/发布时间/失效时间)+ 发布/下架 + 删除
  2. 按现有约定写:API 封装放 src/api/admin/(新增如 product.ts / notice.ts);权限码走现有的 can(code);表格状态列用接口返回的 status / statusText,不要在前端写死中文;大数值用 formatCount。
  3. src/router/index.ts 静态注册路由(route_name 与 DB sys_menu.route_name 一致,注意不要与既有 route_name 重名 —— 之前踩过 CreatorCenter 重名导致白屏/404 的坑)。
  4. 不要改动已对接的既有页面(避免与其他人正在做的改动冲突)。
  5. 门禁:pnpm build:pro 通过 + eslint 零错误。

四、git

  • FruitServer:在 develop 提交并推送;不动 main。
  • ReelShortWeb:现在是 main 分支且与远端分叉(本地 4564aa8 领先 1、远端 1fd0b8b 全量补充作者标识 领先 1)。请先 git fetch 并把远端合进来,然后按 specs 的 GIT_WORKFLOW 新建 develop 分支,在 develop 上提交并 git push origin develop;不动 main(合并走 PR、由用户操作)。远端是 jianghaifeng/ReelShortWeb.git(有写权限)。
  • 作者的署名规范:// Author: fruit.com.cn(Vue/TS 文件首行)。

五、节奏(红线第 7 条)

  1. 先写计划文件 docs/plans/2026-10-16-I1.md(后端 + 前端两部分、步骤、风险、待确认)。
  2. 做,跑门禁。
  3. 停在干净交接点写回执:逐项列出改了什么;给你和用户可照做的验证步骤 + 每步预期结果(含「后台登录 → 菜单里能看到这两个新页面 → 建一个商品 → 用该商品在 C 端 verify 能对上」这样的端到端链路)。
  4. 不要连着抢做下一批。

回执(Codex,2026-10-16):I1 完成,停在干净交接点

提交:FruitServer 8d19a0c(develop,已推) + ReelShortWeb db313f4(develop,已推,含 husky 门禁)。 留痕:docs/plans/PROGRESS.md(本轮新建,逐动作命令 + 原始输出 + 解读);计划 docs/plans/2026-10-16-I1.md。 没碰 main;01-08 冻结模块未改(改动只在 module-commerce / module-system / admin-api / app-api 的 AppNoticeService)。

门禁数字(原始输出见 PROGRESS 动作 3/6/10)

门禁 结果
mvn test BUILD SUCCESS,commerce 33 / admin-api 16 / app-api 49 / worker 12 = 110 条全过(存量 91 + 新增 19,无跑红)
mvn -pl fruitshorts-app-api,fruitshorts-admin-api -am package -DskipTests BUILD SUCCESS
sql/data-phase1.sql 幂等 连跑三遍,sys_role_menu/permission/menu/role_permission 行数 54/52/33/79 前后一致
pnpm ts:check 0 错误
eslint --ext .js,.ts,.vue ./src 0 error(首遍 1 个 prettier 折行,改后复跑为空)
pnpm build:pro Build successful
:3005 真浏览器 两页渲染正常;1440/1280/1024 三档 overflowX=false;截图见下面步骤

交付内容

  1. 后端:/admin/products 6 个接口(page/{id}/POST/PUT/{id}/status/DELETE)、/admin/notices 6 个接口;字段严格按 product / ops_notice 表现有结构,未改表。
  2. 校验:code 全局唯一、storeProductId 同 platform 唯一、COIN_PACK 需 coinAmount>0、VIP 需 vipDays>0、新建默认下架。
  3. 公告可见性与 C 端共用一份实现:OpsNoticeService.pageForApp(...),后台与 /app/notices 不再各写一套条件。
  4. 权限点 product:list/product:update/notice:list/notice:update + 菜单 31(商品配置,挂 22「订单与权益」)/ 32(运营管理)/ 33(系统公告),超管已授权,幂等。
  5. 前端:src/api/admin/product.ts、src/api/admin/notice.ts、ProductManagementView.vue、NoticeManagementView.vue;状态列只用接口 statusText,不写死中文。

两个自己拍的口径(请复核)

  1. VIP 商品:允许建档、不允许启用 —— status=1 时回 400 会员商品在订阅功能上线前不能启用(可先建档,上线后再上下架)。理由:上线日可以直接上下架,不必临时补商品数据;已写进契约 §2.11。
  2. ⚠️ 雪花 id 以字符串下发(本轮实测抓到的真缺陷) —— UI 上点「下架」后端回 200002 公告不存在,根因是 S0 换雪花主键后 id 超过 JS 安全整数范围:DB 里 2111011206851596290,浏览器 JSON.parse 后变成 2111011206851596300,回查自然找不到。 修复限定在本轮两个模块(ProductRespVO / NoticeRespVO 的 id 加 ToStringSerializer,新建接口返回 String,前端 id: string),没动全局序列化、没动 app-api,避免影响 Flutter 客户端;并补了两条防回归测试。
    • 遗留:admin-api 其它模块(订单/权益/短剧…)只要"新建后立刻用 id 操作",同样会中招。是否统一做"admin-api 的 Long → String"、app-api 要不要一起动(Flutter 解析 id 的地方会受影响)——这是平台级改动,请裁定,我没自行铺开。
  3. 菜单挂载机制与条目描述有出入(如实报出):实测菜单来自 DB sys_menu,前端用 import.meta.glob 按 component 字符串动态解析;src/router/index.ts 的 asyncRouterMap 只静态声明 dashboard/content/user/system,commerce/governance/board 都不在里面。所以本轮没有改 src/router/index.ts,只建页面 + 落菜单(component 与视图路径一致)。若你要求"两边都登记",我补一版。

用户可照做的验证步骤(每步都有预期结果)

准备(后端):cd FruitServer && mvn -pl fruitshorts-app-api,fruitshorts-admin-api -am package -DskipTests,然后

java -jar fruitshorts-admin-api/target/fruitshorts-admin-api-1.0.0-SNAPSHOT.jar   # 8080
java -jar fruitshorts-app-api/target/fruitshorts-app-api-1.0.0-SNAPSHOT.jar       # 8081
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8080/v3/api-docs        # 预期 200

数据库若在干净机器上:psql -f sql/data-phase1.sql(可重复执行)。

  1. 菜单:pnpm dev → 打开 http://127.0.0.1:3005/#/login → admin / admin123 登录。 预期:左侧出现「订单与权益 → 商品配置」与「运营管理 → 系统公告」,其余菜单不变。
  2. 商品页:进 #/commerce/products。预期:3 行种子(coin_100/550/1200),列 = 商品编码/名称/类型/平台/商店商品 id/到账金币/价格/排序/状态/创建时间/操作;状态显示「已上架」。
  3. 新建商品(正常路径):点「新建商品」→ 编码 uitest_coin_500、名称随意、平台选 Google Play、商店商品 id UITEST_COIN_500、金币 500、赠送 50、价格 9900 → 保存。 预期:toast「商品已创建(默认下架,请确认后再上架)」,列表新增一行且状态「已下架」(id 是雪花 id,注意这是本轮修过的点)。
  4. 异常路径:再新建一个同编码商品 → 预期 toast「商品编码已存在:uitest_coin_500」;把类型改成「会员(第二阶段)」、天数 30 → 保存成功(允许建档),再点它的状态想上架 → 预期 toast「会员商品在订阅功能上线前不能启用(可先建档,上线后再上下架)」,状态不变。
  5. C 端对齐(命令):把第 3 步的商品点上架,然后
    psql -c "select id, code, platform, store_product_id, status from product where store_product_id='UITEST_COIN_500';"
    
    预期:查到 1 行、status=1 —— 这正是 /app/orders/verify 找商品的同一条件。 (POST /app/orders/verify 本机默认 STORE_VERIFIER=disabled,会先回 200003 商店校验尚未配置,这是 B1-1 的既定行为,不是商品没配上。)
  6. 公告页:进 #/operation/notices → 「新建公告」填标题/正文(默认「全部用户/系统」、不填时间=立即发布)→ 保存。 预期:toast「公告已发布」,行内状态「已发布」、失效时间显示「-」。
    curl -s "http://127.0.0.1:8081/app/notices?pageIndex=1&pageSize=5"    # 预期 total 1,能看到刚发的标题
    
  7. 下架(异常路径的对照组):点该行状态「已发布」→ 预期 toast「公告已下架」、行内变「已下架」;再跑上面的 curl → 预期 total 0。
  8. 越权:用 content.editor01 / admin123(内容运营,无 product:*/notice:*)登录后直接敲 #/commerce/products。 预期:页面 403/无权限(后端 {"code":403,"message":"无权限"});不带 token 请求 curl -s "http://127.0.0.1:8080/admin/products/page" → {"code":401,...}。
  9. 清理:把第 3/4 步的商品、第 6 步的公告删掉;核对 select count(*) from product; = 3、select count(*) from ops_notice; = 0。 (本轮自测数据我已清干净:product 3 行种子、ops_notice 0 行。)

门禁/流程说明:本轮所有子步骤都在同一批内完成,未抢做下一批;mvn test 与 package 都是先停服务再打包、打包后重新起服务、对着活着的服务做的 curl 自测(不是对停掉的服务打)。 服务当前状态:8080/8081 我这边是已起的(自测完成时仍在监听);若你这边连不上就是被环境回收了,按上面「准备」两条命令起回来即可。

禅道同步(红线第 6 条,如实说明):我把 水果短剧APP(项目 2)的 4 个执行下的任务都拉了一遍, 任务只有 S0/S1/S0-16、A1–A6、B1/B2、C1–C3 —— 没有 I1(后台管理端 商品配置 + 系统公告)这一条, 所以本轮没有可同步的任务号,我没有动任何既有任务的状态(避免误改 B1/C1/A* 这些别的批次)。 下一步请给 I1 建任务并告知任务号,我补一次状态同步(codex01 的 token 已可用)。


回执(Codex,2026-10-16):J1 完成,停在干净交接点

提交(三个仓库各自 develop,main 未动)

  • FruitServer f35db2c(develop,已推):全局 Long→String + 契约 §0 + 测试
  • ReelShortWeb 3ec9ea6(develop,已推):4 处 id 数字语义适配
  • ReelShort 94ff9f1(新建 develop,已推):客户端 Long 计数兼容解析 留痕:docs/plans/PROGRESS.md(本轮追加 J1 段,含原始输出);计划 docs/plans/2026-10-16-J1.md。

门禁数字

门禁 结果
mvn test BUILD SUCCESS — commerce 33 / admin-api 21 / app-api 49 / worker 12 = 115 条全过(存量 110 未跑红,新增 5)
mvn … package -DskipTests BUILD SUCCESS
pnpm ts:check / eslint / pnpm build:pro 0 错误 / 0 error / Build successful
dart analyze lib test / flutter test No issues found! / All tests passed!(+167)

每个仓库改了什么

FruitServer(后端)

  • 新增 fruitshorts-framework/.../web/JacksonLongToStringConfig.java(Long/long → 字符串; PageResult.total 用 mixin 保持数字;PlainLongSerializer 供 mixin 使用)
  • docs/api-phase1.md:§0 新增「ID 字段一律以字符串下发」总口径(含示例、边界、total 例外), §2.11/§2.12 改为引用 §0
  • I1 的特例回退:ProductRespVO / NoticeRespVO 去掉 @JsonSerialize, ProductAdminController / NoticeAdminController 的 create 回到 Result<Long>
  • 测试:新增 admin-api/.../web/LongToStringSerializationTest.java(5 条); ControllerContractSupport(admin + app 两处)挂上模块,避免契约测试假绿; C 端 4 条 id 断言改成 value("…") + isString()

ReelShortWeb(4 个文件)

  • src/utils/creatorImpersonation.ts — 代看创作者 id 全程字符串(原来 Number(raw),19 位会被四舍五入)
  • src/views/contentManagement/EpisodeManagementView.vue — Number(route.query.drama) → 字符串比较
  • src/views/creator-center/composables/useDramaEdit.js — Number(route.params.id) → 字符串比较
  • src/views/permission/composables/useMenuManagement.js — 顶级菜单哨兵 0 → '0'(与接口的 "0" 对齐)

ReelShort(Flutter)逐文件清单(只有这些,没夹带别的)

  • lib/core/data/models/json_numbers.dart(新增)— jsonToInt:接受数字或数字字符串
  • lib/core/data/models/auth_session.dart — expiresIn(后端 long)改用 jsonToInt
  • lib/core/data/models/drama.dart — viewCount(后端 Long)两处改用 jsonToInt
  • test/core/json_id_parsing_test.dart(新增)— 8 条回归用例

三个需要你确认的口径

  1. PageResult.total 保持数字(唯一例外) —— 它是条数不是 id,前端分页组件(el-pagination / Flutter 分页) 都要数字。实测 total = 2 int。若你要求"Long 一律字符串",删掉 JacksonLongToStringConfig 里那一行 mixin 即可。
  2. 反向(请求方向)未做限制:请求体 "id":"…"、路径变量、查询参数都接受字符串并转 Long(有测试)。 Integer/int(pageIndex/pageSize/Result.code/totalEpisodes/durationSec…)保持数字 —— 实测已确认。
  3. 客户端适配范围比"只改 id 解析"宽了一点点,原因是物理上必须:客户端 id 本来就是 (json['id'] ?? '').toString(),不会崩;真正会崩的是被全局规则扫到的 Long 计数, 所以改了 expiresIn 与 viewCount 共 3 处(json_numbers.dart 是新增工具)。 其余 8 处 as num? 对应的 Java 字段都是 Integer/int,按最小改动原则没动。 ⚠️ ReelShort 仓库原来只有 main(无 develop),我按条目要求新建了 develop 并在上面提交。 客户端由另一位同事推进,若他继续在 main 上走,需要把这笔 94ff9f1 带过去。

用户可照做的验证步骤(每步都有预期结果)

准备:cd FruitServer && mvn -pl fruitshorts-app-api,fruitshorts-admin-api -am package -DskipTests,起两个 jar; curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8080/v3/api-docs → 200。

  1. 复现 PM 那条(最直观):注册一个 C 端账号
    curl -s -X POST http://127.0.0.1:8081/app/auth/register -H 'Content-Type: application/json' \
      -d '{"targetType":"EMAIL","target":"你的邮箱@example.com","password":"Passw0rd!23","nickname":"手测","deviceId":"manual-1"}'
    
    预期:"expiresIn": "7200"(字符串)。
  2. 用响应里的 user id 调后台:curl -s "http://127.0.0.1:8080/admin/app-users/page?pageSize=50" -H "Authorization: Bearer <admin token>", 拿新用户的 id(预期是 "2111…" 这种带引号的字符串、total 是不带引号的数字),再 curl -s "http://127.0.0.1:8080/admin/app-users/<id>" -H "Authorization: Bearer <token>" → 预期 code=0、data.id 与列表一致 (修复前这里返回 200002 数据不存在)。
  3. JS 逐位一致:把第 2 步的响应存成 r.json,然后
    node -e 'const d=JSON.parse(require("fs").readFileSync("r.json","utf8"));console.log(String(d.data.id))'
    
    预期与 psql -c "select id::text from app_user where uid='<uid>'" 逐位相同。 对照:node -e 'console.log(String(JSON.parse("2111015748376858626")))' → 2111015748376858620 附近(会被改写),说明精度问题真实存在。
  4. 后台界面(:3005):admin/admin123 登录 → 商品配置新建一个商品 → 点它那一行的状态/编辑, 预期正常(toast 成功,不再"商品不存在");菜单管理编辑一个顶级菜单,预期父级下拉显示「顶级菜单」而不是 0。
  5. 短剧管理 → 剧集管理带参跳转:从短剧管理点某剧的「剧集」,URL 带 ?drama=<id>, 预期剧集页自动选中该剧(改成字符串比较前这里会选不中)。
  6. 客户端:cd ReelShort && flutter test(预期 All tests passed);有设备时 flutter run 打开首页看播放量/详情 不报解析异常(后端 viewCount 现在下发字符串)。
  7. 异常路径:请求体里 id 传数字(老客户端行为)也应被接受:PUT /admin/products/<id>/status?status=1 用数字 id 路径同样 200。

禅道:J1 在禅道里同样没有对应任务(项目 2 下仍是 S0/S1/S0-16、A1–A6、B1/B2、C1–C3),未动任何既有任务状态。 服务现状:8080/8081 是我刚重建起回的新 jar(自测时在跑);:3005 的 dev server 也在。若被环境回收,按上面「准备」起回来即可。


回执(Codex,2026-10-16):K1 完成,停在干净交接点

提交:ReelShort d7eff4c(develop,已推;main 未动)。留痕:docs/plans/PROGRESS.md;计划:docs/plans/2026-10-16-K1.md。

门禁:dart analyze lib test → No issues found!;flutter test → All tests passed!(188 条,存量 27 个测试文件未跑红);真实后端联调(8081)见下方原始输出。

先纠正条目里的一处勘察:客户端详情接口早已接上(CatalogApi.dramaDetail + CatalogController.requireDetail,详情页在用), 票据调用 CatalogApi.playTicket 也存在但没有任何页面调用。所以本轮真正做的是:把票据接进播放链路、进度从本地换服务端、 收藏换服务端、补三态 UI。

改了什么(ReelShort,只列这一批) 新增:remote/playback_api.dart、remote/watch_api.dart、remote/library_api.dart、models/watch_progress.dart、 repositories/playback_repository.dart、repositories/favorite_repository.dart、services/favorite_controller.dart、 formatting/error_text.dart。 改:app.dart(注入三个仓库 + 收藏控制器 + 登录/登出联动)、remote/api_client.dart(补 delete)、 remote/catalog_api.dart 与 repositories/catalog_repository.dart、services/catalog_controller.dart(票据职责搬走)、 models/play_ticket.dart(补 dramaId/number/title/previewSeconds)、repositories/watch_history_repository.dart(换服务端模型)、 pages/{discover,drama_detail,mine,player}/*、l10n/app_{en,zh,id}.arb + 4 个生成文件。 测试:新增 5 个(含 live 联调),改 8 个(含 fake 与 widget_test 的离线仓库注入)。

怎么改的(要点)

  1. 起播前取票:PlaybackRepository.ticketFor() 带 TTL 缓存(默认 10 分钟 < 后端 15 分钟签名),过期或 forceRefresh 时重取; 回前台发现票据过期会重新取。签名地址原样交给播放器(AppConfig.mediaUrl 只补 host,path 与 ?e=&s= 不动,有测试钉住)。
  2. accessType 驱动 UI:COIN_REQUIRED/VIP_REQUIRED → 锁定提示 + "金币解锁即将开放";NO_MEDIA → 暂无片源; 前端不推断能不能播(契约 §1.4 的硬要求)。付费解锁仍属 B2。
  3. 进度:服务端每剧一条记录,POST /app/watch/progress 节流从 5 秒改成10 秒,切集/退出/切后台各补一次,播完带 finished; 续看位置只在"记录里的那一集"生效(其它集从头播);demoProfileId 与本地 SharedPreferences 实现已删除。
  4. 收藏:FavoriteController 以服务端返回为准,乐观更新 + 失败回滚 + 失败提示;退出登录清空(不跨账号串联)。
  5. 三态:详情页(加载 / 失败可重试 / 空剧集);播放页(票据加载中 / 失败可重试 / 后端判定不可播); 我的页(续看读取失败可重试 / 收藏列表加载中·失败·空 / 最近观看加载中·失败·空)。断网与超时文案走 l10n(en/zh/id 各 8 个新键)。

原始联调输出(8081)

① /app/home/feed                       → dramaId=5
② /app/dramas/5                        → episodes=3;第一集 id='7'
     playUrl=/files/episode/202610/cec39d8e….mp4?e=1792144372&s=8791826a…
③ /app/episodes/7/play                 → episodeId='7' dramaId='5' accessType=FREE accessible=True
④ POST /app/watch/progress (42s)       → positionMs="42000" durationMs="228000" playCount=1
⑤ GET  /app/watch/progress?dramaId=5   → episodeId='7' positionMs='42000'   ← 续看位置读得回来
⑥ GET  /app/watch/recent?limit=20      → 条数=1(dramaId='5' episodeId='7')
⑦ POST /app/favorites/toggle           → true;GET /app/favorites → 1 条;DELETE → 0 条
$ flutter test test/integration/live_watch_flow_test.dart → All tests passed!

自测数据已清干净:app_user 只剩 PM 那条(id=28)、watch_progress 0、user_favorite 0。

哪些页面现在走真数据、哪些还是假的

页面/能力 数据来源 状态
首页 / 发现页 /app/home/feed、/app/discover/feed ✅ 真数据(本批未动)
登录 / 注册 / 刷新 / 登出 /app/auth/* ✅ 真数据(本批未动)
详情页(剧集列表、可播地址) /app/dramas/{id} ✅ 真数据
详情页"继续观看"与剧集进度条 /app/watch/progress?dramaId= ✅ 本批接上(原来读本机)
播放页(票据/签名地址/起播判定) /app/episodes/{id}/play + accessType ✅ 本批接上
播放页进度上报(10s 节流) POST /app/watch/progress ✅ 本批接上(原来存本机)
我的页:收藏列表 + 收藏按钮 /app/favorites* ✅ 本批接上(原来存本机)
我的页:最近观看 /app/watch/recent ✅ 接上;只渲染能在已加载目录里按 id 匹配到的剧,匹配不到先不显示(见待确认 2)
我的页:点赞 本地 EngagementController ❌ 仍是本地假状态(属 K3 互动)
播放页:评论 本地 DramaCommentsSheet ❌ 仍是本地(属 K3)
播放页:解锁付费集 — ❌ 本批不做(属 B2,票据已按 accessType 提示)
记录页 / 收藏列表页 / 分类搜索 / 下载 — ❌ 属 K2

用户可照做的验证步骤(每步预期结果) 准备:后端两个 jar 起着(admin=200、app=200);cd ReelShort && flutter run(模拟器用 --dart-define=API_BASE_URL=http://10.0.2.2:8081;iOS/桌面用默认 127.0.0.1)。

  1. 匿名可看:不登录进首页 → 点任一剧 → 详情正常出剧集列表;点某集能起播(免费集)。 预期:起播前有一瞬"加载票据",无白屏;付费集显示"第 N 集已锁定 + 金币解锁即将开放"。
  2. 登录后进度:用「我的 → 登录」注册/登录一个邮箱账号 → 进一部剧 → 看 20~30 秒 → 返回详情。 预期:详情页的"继续观看"按钮下方出现进度条与"续看至 mm:ss / mm:ss",剧集行上也有一条进度条。
  3. 跨进程续看(真数据验证):杀进程重开 → 进同一部剧 → 直接点"继续观看"。 预期:从上次的位置继续播(不是从头)。也可以直接查库: psql -c "select drama_id, episode_id, position_ms from watch_progress order by updated_at desc limit 3;" 预期有该剧记录。
  4. 收藏:播放页右侧点收藏(或详情页右上角)→ 打开「我的 → Watchlist」。 预期:该剧出现在收藏列表;再点一次取消 → 从列表消失。断网时点收藏 → 提示"收藏操作失败,请重试"且图标回到原状态。
  5. 异常路径:把后端停掉(lsof -tiTCP:8081 -sTCP:LSLISTEN | xargs kill)后进详情/播放。 预期:详情页给"网络异常,请检查网络后重试"+ 重试按钮;播放页给"此视频暂时无法播放"+ 重试; 我的页收藏区给失败文案 + 重试;都不白屏、也不假装成功。恢复后端后点重试即可继续。
  6. 一套命令验证(不用模拟器): cd ReelShort && flutter test test/integration/live_watch_flow_test.dart → 预期 All tests passed(服务没起会 skip)。

待确认 / 如实说明

  1. 票据过期用的是客户端 TTL(10 分钟),因为契约里 play 响应没有 expiresAt 字段。后端若补上过期时间,改成按字段判断即可(一行)。
  2. /app/watch/recent 只返回 dramaId/episodeId/位置,没有剧名封面,所以"我的页最近观看"目前按 id 去已加载的目录卡片里匹配; 匹配不到的条目先不显示。K2 的记录页建议要么后端补卡片字段,要么做成独立请求(已在计划里记为风险 4)。
  3. WatchHistoryRepository 的接口签名被改了(服务端是"每剧一条续看记录",没有 profile 概念),连带改了 4 个 logic 与 6 个测试文件—— 这是接口模型决定的,不是顺手重构;clampResumePosition 等本地工具函数保留。
  4. 未登录时进度与收藏接口一律不发(匿名浏览/试看不受影响),收藏按钮沿用"先引导登录"。
  5. 禅道里仍没有 K1 任务(项目 2 下只有 S0/S1、A1–A6、B1/B2、C1–C3),未动任何既有任务状态。

回执(Codex,2026-10-16):K1-verify —— 未验证(真机验证由用户执行,Codex 未执行)

结论先说:按用户 20:45 指令「codex 不用跑测试 我来执行」,本批的真机验证状态 = 未验证, 由用户执行后以其结果为准。我没有执行任何最终真机验证,也没有用任何"开发机 curl"当作设备可达证据。

交付物(ReelShort develop,已推)

  • pubspec.yaml:dev_dependencies 加 integration_test(sdk)—— 唯一的新依赖
  • integration_test/app_backend_test.dart(新增,首行 // Author: fruit.com.cn):两个设备侧用例
    1. 在手机进程里请求 /app/home/feed 与 /app/dramas/{id},断言列表非空、详情 id 与卡片一致;
    2. 用真 App(真网络、真目录控制器)启动,轮询到首页底部标签栏出现 —— 即"剧目列表加载成功", 也就是用户在真机上遇到的「剧目列表加载失败」是否消失。
  • 该文件按用户要求保持原样,不再改动。

当前机器状态(交接前已清理)

  • 我起的 flutter run / xcodebuild / lsof 轮询进程已全部终止(pgrep -fl "flutter_tools|xcodebuild" 只剩 IDE 常驻 daemon,非构建)。
  • build/ 里留着我被叫停那次构建的半成品 → 用户从 flutter clean && flutter pub get 起跑即可清掉。
  • 后端 8080 / 8081 仍在跑(admin=200 / app=200)。

用户可直接复制执行的命令(⚠️ 本项目 iOS 用 SPM,没有 Podfile,不要跑 pod install)

# 0) 起点:清掉残留构建产物(我被打断的那次留下的)
cd /Users/jianghaifeng/Desktop/ReelShort/ReelShort
flutter clean && flutter pub get

# 1) 取开发机真实局域网地址(不要硬编码;换网络会变)
IP=$(ipconfig getifaddr en0) && echo "API_BASE_URL=http://$IP:8081"

# 2) 设备侧集成测试(最硬的证据,跑在手机进程里)
flutter test integration_test -d 00008101-000875813E42001E --dart-define=API_BASE_URL=http://$IP:8081

# 2b) 只想跑网络那一条
flutter test integration_test/app_backend_test.dart -d 00008101-000875813E42001E \
  --plain-name "should_fetch_home_feed_and_drama_detail_when_device_reaches_backend" \
  --dart-define=API_BASE_URL=http://$IP:8081

# 3) 真机手动看 App(可选,验证首页列表与交互)
flutter run -d 00008101-000875813E42001E --dart-define=API_BASE_URL=http://$IP:8081

(本次实测 ipconfig getifaddr en0 = 192.168.31.187;若换网络请以命令输出为准。)

前置条件

  1. 手机与开发机在同一局域网(例如都在 192.168.31.x)。
  2. iPhone 已授权「本地网络」权限:设置 → 隐私与安全性 → 本地网络 → 打开本项目(App 名 FruitShorts)。 没授权时 App 请求局域网会失败;Info.plist 里 NSLocalNetworkUsageDescription 与 NSAllowsLocalNetworking 都已配好(上一批加的),所以系统应当会弹权限询问。
  3. 同一时刻只跑一个 flutter 构建(并发写同一个 build/ 会互相破坏产物 —— 这正是本次 video_player_avfoundation…bundle: No such file or directory 的疑似成因)。

设备侧证据看什么(红线第 10 条)

  • 测试输出里必须出现由手机进程打印的:
    • [device] API_BASE_URL=http://192.168.31.187:8081(证明手机上用的是局域网地址,不是开发机环回/VPN 地址)
    • [device] 首页分区=4 剧目=3 / [device] 详情 id=5 剧集=3(证明设备真的拿到了数据)
    • [device] 首页剧目列表已渲染:API_BASE_URL=http://192.168.31.187:8081(证明真 App 首页列表出内容)
    • 最后一行 All tests passed!
  • 若失败:reason 里会带上 API_BASE_URL=…,据此区分"地址不对"还是"设备连不上/权限未授权"。

如实说明(失败与历史记录,详见 PROGRESS 的 K1-verify 段)

  • 我在 17:56 跑过一次,那次是设备侧输出且全过(上面那几行 [device] 就是它的原始输出)。
  • 随后两次执行失败:17:59 那次 The Dart VM Service was not discovered after 60 seconds(app 没 attach,我也没抓到任何手机→8081 的连接); 18:03 那次 flutter run 构建完成后 PM 报产物缺失(video_player_avfoundation…bundle: No such file or directory)并叫停。
  • 因此在用户重跑之前,本批一律按「未验证」计;上面那次的历史输出只作参考,不作为结论。