Skip to content

33 · uni-app 对照查证:迁移项目怎么找"正确答案" ​

这是迁移期最高频的动作:写一个页面 / 接一个接口之前,先去 uni-app 老项目里确认"原来到底是怎么干的"。 本篇给的是可复制的查证路径,不是迁移方法论。配套读 32-规格驱动开发与AI协作。


开篇:概念对照 ​

uni-app 老项目Flutter 落点对齐程度
pages/**/*.vue(template + script)lib/ui/<feature>/views/*.dart结构照搬,实现重写
pages/api/*.js(接口定义)lib/data/services/api/**/*_api_service.dart(Retrofit)路径/字段 1:1
Vuex store/modules/*lib/config/providers/* + lib/ui/*/view_models/*不做 1:1,按 Riverpod 重设计
util/config.js(环境)lib/data/services/api/environment.dart1:1,含路由规则
static/language/zh-CN.jsonassets/i18n/zh-CN.json(远端覆盖、本地兜底)key 1:1
rpx / px 尺寸AppTokens + ScreenUtilInit(750×1624)不 1:1,走新 Figma 稿
static/general/** 位图assets/images/** + flutter_gen 强类型路径映射,走 Assets.images.*
字体图标AppIcon(iconfont,支持远程热更新)不 1:1,走 iconfont

关键认知:老项目是业务逻辑的唯一真源,但不是实现的真源。docs/uniapp-migration-workflow.md:5 原话:

业务逻辑以 uni-app 老项目为唯一真源。

而 docs/uniapp-migration-workflow.md:13 紧接着划了另一条线:

UI 视觉与组件交互按当前 Flutter 体系实现:优先复用 He 组件,视觉值走 AppTokens,文本走 RuntimeI18n,图片走 flutter_gen,图标走 AppIcon。

一句话概括:"做什么"对齐老项目,"怎么做"用 Flutter 体系。


一、老项目在哪 ​

不在本仓库。当前仓库 find . -name "*.vue" 结果为 0,老项目是独立仓库,代码里以路径引用的方式出现:

老项目路径(注释中的样子)内容
app_haier_energy_master/pages/agent/device/index.vue设备列表页(B/C 端混在一起)
pages/api/device.js设备相关接口定义
util/config.js环境与 baseUrl 路由
static/language/zh-CN.jsoni18n 文案真源
static/general/device/设备类型默认位图

所以第一步是把老项目仓库 clone 到本地另一个目录,后面所有查证都是跨仓库比对。


二、四条查证路径(按使用频率排序) ​

路径 1:路由常量注释 —— 最快(80% 情况够用) ​

lib/routing/routes.dart 每一条路由都写了对应的 vue 文件名和参数约定:

dart
// lib/routing/routes.dart:15-24
/// 设备加入电站入口选择页(addPlant.vue 等价)。
static const String plantSelect = '/plant-select';
/// 电站搜索列表页(selectDevice.vue 等价)。
static const String plantSelectList = '/plant-select-list';

/// 电站详情(powerDeatail,query: fatherId/systemStatus/plantStatus/plantCategory;
/// phase 电站详情)。
static const String plantDetail = '/plant-detail';

/// 电站基本信息页(baseInfo,extra: {plantId};phase 电站详情 · 概览 tab 信息卡入口)。
static const String plantBaseInfo = '/plant-base-info';

一条注释给了三样东西:原 vue 名、参数类型(query / extra)、入口位置。拿到 vue 名就能直接去老项目 pages/ 下搜。

注意 powerDeatail 是老项目的拼写错误(应为 Detail),迁移时原样保留在注释里。 这类"看起来是 typo 但要照抄"的地方,正是查证的价值所在。

路径 2:代码内 uni-app 注释 —— 1336 处 ​

lib/ 下有 1336 处 uni-app 字符串、287 处 .vue 引用。Repository 层最密集:

dart
// lib/data/repositories/plant_repository.dart:5
// 对齐 uni-app agent/home/index.vue getPlantList(GET /meta/app/plant,分页 +

// lib/data/repositories/plant_repository.dart:55
/// 电站选择列表(对齐 uni-app selectDevice.vue getPlantListNew)。

// lib/data/repositories/plant_repository.dart:64
/// 删除电站(对齐 uni-app deletePlant -> DELETE /meta/app/plant/{plantId})。

常用 grep:

bash
# 找某个功能的老实现出处
grep -rn "uni-app" lib/data/repositories/ | grep -i "device"

# 找所有引用了某个 vue 的地方
grep -rn "index.vue" lib/

# 找带 quirk 标记的刻意保留行为(最值得看)
grep -rn "quirk" lib/

路径 3:openspec change 的 Why 段 ​

openspec/changes/<name>/proposal.md 的 ## Why 第一段几乎总是写老实现在哪:

markdown
## Why
当前设备列表功能仅存在于 uniapp 旧项目
(app_haier_energy_master/pages/agent/device/index.vue),
Flutter 分中心 App 尚未实现该核心设备管理入口。
旧项目中 B 端(安装商)与 C 端(终端用户)设备页混在同一 vue 文件,
本变更只承接 B 端安装商设备列表,C 端内容拆分至独立变更 add-user-device-list。

这是信息密度最高的一处:不只告诉你去哪找,还告诉你老实现有什么历史包袱(比如 B/C 端混在一个 vue)。

路径 4:spec 的 Scenario —— 查"反直觉行为" ​

有些行为"看起来该优化、实际不能动",只在 spec 里写明了原因:

markdown
### Requirement: Session acquisition storage order
`_acquireSession(role)` SHALL preserve the uni-app order: save selected role,
save remembered login information, call login, save accessToken, save history
account, then call getAgentInfo. It SHALL NOT call getUserInfo during immediate
login because uni-app performs that check on next app launch.

(openspec/specs/auth-state/spec.md)

如果你在重构时看到一段"顺序很奇怪"的存储代码,先查 spec——大概率是刻意的。


三、迁移核对五项(docs/uniapp-migration-workflow.md:7-11) ​

开工前逐项核对,这是硬性要求:

#核对项真源常见翻车点
1文案和单位uni-app static/language/zh-CN.json;远端覆盖本地,本地仅兜底单位(kW vs kWh)在老项目里是硬编码字符串,Flutter 侧容易漏
2交互流程、API 顺序、校验、风控、加密、存储时机老项目源码 1:1存储先后顺序最容易错,见下面 quirk 清单
3渲染结构、顺序、文字有无照搬 template;冲突时保留业务行为老项目"某条件下不显示某个字段",Flutter 侧常被写成始终显示
4早期 rpx 过渡值仅缺新 Figma 稿时按 750-baseline-adaptation-guide.md 的迁移公式有新 Figma 稿就必须按 semantic token 重写,别沿用过渡值
5iconfont 缺失用现有 iconfont 占位,位置必须有,标 TODO(icon)图标没找到就整个不渲染,导致布局缺一块

第 5 条值得单独强调:图标可以错,位置不能缺。用占位图标 + TODO(icon),比留空白更容易被发现。


四、历史包袱(quirk)清单 ​

这些是"改了就会炸、看起来却像 bug"的地方,全部有注释或 spec 背书:

quirk位置为什么不能改
未登录时 userrole 请求头为空串lib/data/services/api/auth_interceptor.dart:57对齐 uni-app 未登录时 USER_ROLE 存储为空串;发 null 或不发,后端行为不同
isRegister 的 query 字段名恒为 phone(即使账号是邮箱)lib/data/services/api/api_service.dart:1101:1 复刻 uni-app,后端就认这个字段名
prod 环境 baseUrl 为 null,按 serviceZone 路由lib/data/services/api/environment.dart:5-12对齐 util/config.js:仅 betaVersion=false 时 zone.servicePath 才覆盖 baseUrl
dev/test 固定 baseUrl,serviceZone 不起作用同上对齐 request2.js,不是漏实现
登录存储顺序:角色 → 记住的登录信息 → login → token → 历史账号 → getAgentInfoopenspec/specs/auth-state/spec.mduni-app 原顺序,乱序会导致会话恢复取不到值
立即登录时不调 getUserInfo同上uni-app 在下次启动时才做这个检查

几条 quirk 的对应代码(方便直接对,都是当前 checkout 快照):

dart
// lib/data/services/api/environment.dart:20-27 —— prod 环境 baseUrl 为 null,按 zone 路由
enum Environment {
  dev('https://hemsdev.nahui-newenergy.com/api'),
  test('https://hemstest.nahui-newenergy.com/api'),
  prod(null); // 按 serviceZone 路由 hems*.haier-energy.com/api

  const Environment(this.baseUrl);
  final String? baseUrl;
}
dart
// lib/data/services/api/auth_interceptor.dart:139-141 —— userrole 头未登录时为空串
final role = await userRoleGetter?.call();
options.headers['userrole'] = role ?? '';
dart
// lib/data/services/api/api_service.dart:110-116 —— isRegister 的 query 字段名恒为 phone
@GET(ApiPaths.isRegister)
Future<ApiResponse<dynamic>> isRegister(
  @Query('phone') String phone, {
  @CancelRequest() CancelToken? cancelToken,
});

给 AI 的提醒:这些注释里普遍带 quirk / 对齐 uni-app / TODO 标记。动手重构前 grep -rn "quirk" lib/,能省掉一次线上事故。


五、不必对齐的部分 ​

明确边界,避免过度对齐导致 Flutter 侧写成"四不像":

维度处理
视觉尺寸、间距、色值不对齐老项目 rpx,走 AppTokens / 新 Figma 稿
状态管理形态不照搬 Vuex module 结构,按 Riverpod + ViewModel 重设计
路由实现用 GoRouter,不模拟 uni-app 的页面栈
图标实现走 iconfont(AppIcon),不复制位图
组件拆分粒度优先复用 he_components,不按 vue 的 template 结构硬切
老项目的 bug照搬——除非 change 里明确写了要修正。迁移期"行为一致"优先于"正确"

最后一条是迁移项目的铁律:先 1:1 落地,再单独提 change 修 bug。混在一起改,出问题分不清是迁移错了还是修 bug 引入的。


六、常见坑 ​

坑后果怎么避
只看 Figma 稿不看老项目视觉对、逻辑错(少了个校验、多了个字段)迁移期先看 vue,再看 Figma
把老项目的 typo 当错误"顺手修正"接口 404 / 字段名不匹配照抄,确需修正单独提 change
照搬 rpx 数值与新 750 基线冲突,check_figma_tokens.dart 报漂移有新稿走 token,无新稿走迁移公式
图标缺失就不渲染布局缺块,QA 才发现占位图标 + TODO(icon)
重构掉"奇怪的存储顺序"会话恢复失败,且只在特定路径复现改前先查该能力的 spec
老项目 i18n key 与 Flutter 侧 key 不一致线上显示 key 原文key 以 static/language/zh-CN.json 为准,1:1
不知道老项目要单独 clone查证卡住,靠猜实现开工第一件事:把老项目仓库拉到本地

七、自检清单 ​

  • [ ] 知道老项目不在本仓库(本仓库 0 个 .vue),已单独 clone
  • [ ] 能按 lib/routing/routes.dart 的注释,5 秒内定位任意页面对应的 vue 文件
  • [ ] 会用 grep -rn "uni-app" lib/ 与 grep -rn "quirk" lib/ 两条命令
  • [ ] 知道迁移核对五项,并能在提 change 前逐项过一遍
  • [ ] 能说出至少 3 条 quirk(userrole 空串、isRegister 的 phone 字段、prod baseUrl 为 null)
  • [ ] 清楚"必须对齐"和"不必对齐"的边界:逻辑对齐、视觉走 Flutter 体系
  • [ ] 知道迁移期发现老项目 bug 时,先 1:1 落地,再单独提 change 修

下一步 ​

  • 32-规格驱动开发与AI协作 —— 查证完怎么把结论固化成 spec 与 change
  • 09-数据层链路 —— 接接口时的 8 步清单,其中第 1 步就是查证老实现
  • 10-页面开发实战 —— 端到端做新页面的 7 步
  • 14-国际化 —— i18n key 的远端覆盖与本地兜底机制

相关文档 ​