文章目录

SmartOA 前端是从 SmartAdmin V3 二次开发的,早期代码处于「能跑就行」的状态:157 个 vue-tsc 类型错误、大面积 any、API 返回类型全靠猜。2026 年 5 月底,我决定一次性把类型体系立起来——升级 TypeScript 5→6、vue-tsc 2→3,把 any 全部清零,让 vue-tsc --noEmit 从 157 个错误走到 0。这篇记录整个迁移的拆解方法和关键决策。

一、为什么必须做这次迁移

前端规模:254 个 Vue 文件 + 217 个 TS 文件,约 7.2 万行代码。在业务持续叠加的情况下,类型债只会越滚越大:

💡 决策:未上线代码宁可一次性大规模迁移(长痛不如短痛),也不打补丁式修复。这次迁移是「先升级工具链,再修类型错误,最后清 any」三步走。

二、第一步:工具链升级 TS 5→6 + vue-tsc 2→3

先升级再修错,避免在旧编译器下重复劳动。升级内容:

// package.json 关键变更
"typescript": "^6.0",          // 5 → 6
"vue-tsc": "^3.0",            // 2 → 3
"@types/node": "^25.0",        // 20 → 25
"node": ">=22"                // 运行时要求同步提升

升级后立刻跑 vue-tsc --noEmit,错误数从 0(旧版根本没检查出来)直接飙升到 157——这正是想要的效果:新编译器把以前藏着的类型问题全部暴露了。

三、157 → 0 的拆解过程(checkpoint 记录)

没有一口气修完,而是按「错误聚类」分批推进,每批留下 checkpoint:

阶段错误数修复内容
起点157升级后全量暴露
第 1 批157 → 62API 类型修复:PageResult 等通用返回结构
第 2 批157 → 42login / center / help-doc / role-tree / role-menu 的 API 类型
第 3 批157 → 39flow tasks VO casts + help-doc-form-drawer
第 4 批157 → 0剩余 VO 转换与组件类型级联

关键技巧:先修公共类型(PageResult、分页参数),再修业务页面。公共类型一改,几十个页面的错误会同时消失,效率远高于逐个页面硬啃。

四、定义 VO 类型体系:不再靠猜

此前前端没有员工/角色/部门的类型定义,接口返回什么全靠 res.data 链式取值。这次补全了核心 VO:

// types/api/employee.ts — 与后端 EmployeeVO 一一对应
export interface EmployeeVO {
    employeeId: number;
    username: string;
    nickname?: string;
    departmentId?: number;
    jobNumber?: string;
    status: number;          // 1 在职 / 0 离职
    createTime?: string;
}

export interface RoleVO {
    roleId: number;
    roleName: string;
    roleKey: string;
}

export interface DepartmentVO {
    departmentId: number;
    departmentName: string;
    parentId?: number;
}

export interface PositionVO {
    positionId: number;
    positionName: string;
}
✅ 效果:VO 定义后,组件里的类型级联更新一次到位(commit: T-2/T-3),API 返回类型修正不再需要每个页面单独猜。

五、any 清零:三条硬规则

清 any 不是「把 any 改成 unknown 就完事」,而是建立规则:

  1. 禁用 any:unknown 必须收窄(类型守卫 / 断言 / 泛型),禁止 as any 和 as unknown as X 绕过检查
  2. catch 语句:用 e as {message?: string} 或 e instanceof Error,不吞异常类型
  3. 根源修复:API 返回类型与后端 DTO 对齐(PageResult<T> 泛型),而不是在调用处打补丁
// ❌ 旧写法:any 随手用
const list = res.data as any;

// ✅ 新写法:泛型 + 类型守卫
const list = res.data as PageResult<EmployeeVO>;
if (!list || !Array.isArray(list.records)) throw new Error('数据格式异常');

六、顺带完成的周边清理

迁移过程中把同类问题一并处理:

七、成果与数据

指标迁移前迁移后
vue-tsc --noEmit157 errors0 errors
any 残留lib/plugins/directives/types 多处0 处
构建耗时—3.12s
ESLint10 error + 9 warn0 问题
✅ 最终状态:vue-tsc 零错误、any 清零、ESLint 清零,类型体系成为后续所有前端改动的第一道防线。此后每次提交前 npx vue-tsc --noEmit + ESLint + Prettier 成为强制检查(六项检查中的前三项)。

八、踩坑清单(可直接复用)

  1. 先升级再修错:新编译器暴露的问题才是真问题,别在旧版本下浪费时间
  2. 先公共后业务:PageResult 这类公共类型一改,几十个页面错误同时消失
  3. checkpoint 留痕:每批修复记录错误数变化(157→62→42→39→0),便于回溯定位
  4. any 清零是规则不是动作:unknown + 类型守卫 + 泛型,禁止 as any 逃生
  5. VO 对齐后端:前端类型与后端 DTO 一一对应,字段名驼峰一致,减少转换层

评 论