专注于 Vue2 传统路由配置到 Vue3 约定式路由的迁移。 触发条件(满足任意一项即触发): - 任务包含"路由迁移"、"pages.json"、"约定式路由"、"路由配置"等关键词 - 需要从 pages.json 迁移到文件系统路由 - 需要添加 definePage 页面配置 - 需要配置强类型路由系统(TypedRouter) - 需要更新路由跳转代码(uni.navigateTo → TypedRouter) - 需要处理多平台路由适配 - 需要查阅路由迁移映射表(docs/prompts/route-migration-map.yml) - 从 Vue2 项目迁移页面路由 必须协同的技能: - code-migration(代码迁移)- Vue2 → Vue3 代码写法 - component-migration(组件迁移)- ColorUI → wot-design-uni - style-migration(样式迁移)- ColorUI 类名 → UnoCSS 原子类 禁止事项: - 禁止自行决定路由路径(必须查阅映射表) - 禁止使用 uni.navigateTo 字符串拼接(必须使用 TypedRouter) - 禁止在 definePage 中添加 name 字段(只使用 style) - 禁止跳过 definePage 配置(会导致标题显示为 "unibest") - 禁止不更新映射表状态(完成后必须添加 ✅ 标记) 覆盖场景:几乎所有从 Vue2 迁移到 Vue3 的页面都需要此技能,包括路由配置、页面跳转、参数传递等。
从 Vue2 项目的 传统 pages.json 路由配置 迁移到 Vue3 项目的 约定式路由系统 + 自动路由生成 现代化路由管理模式。
完整页面迁移:
code-migration + component-migration + style-migration参阅 .claude/skills/check-trigger.md 了解完整的技能触发检查流程。
必读文件:
docs/prompts/route-migration-map.yml - 路由映射表(强制查阅)src/router/index.ts - 强类型路由工具函数关键要求:
| ❌ 错误写法 | ✅ 正确写法 | 说明 |
|---|---|---|
| 自行决定路由路径 | 查阅映射表执行 | 必须严格按照映射表 |
uni.navigateTo({ url: '/pages/...' }) | TypedRouter.toXxx() | 使用强类型路由 |
| 不看映射表直接迁移 | 先读映射表再迁移 | 映射表是唯一标准 |
| 不标记完成状态 | 完成后添加 ✅ | 必须追踪进度 |
| 页面缺少 definePage 配置 | 所有页面必须添加 definePage | 页面标题和配置必需 |
| 页面标题显示 "unibest" 或空 | 使用 definePage 设置标题 | 影响用户体验 |
definePage({ name: ... }) | 删除 name 字段 | name 字段是非法配置 |
必须严格遵照 Vue2 到 Vue3 uni-app 路由迁移映射表 执行所有路由迁移任务
docs\prompts\route-migration-map.yml
# 1. 首先读取映射表文件
Read: docs\prompts\route-migration-map.yml
# 2. 在 route_mappings 中查找对应的路径映射
# 例如:gitee-example/pages/repairOrder/repairOrder.vue → src/pages-sub/repair/order-list.vue
# 3. 严格按照映射表执行迁移
# 4. 完成后在映射表相应模块添加 ✅ 标记
传统路由配置模式 (pages.json)
├── pages.json # 手动维护的集中式路由配置
│ ├── pages[] # 主包页面配置数组
│ ├── subPackages[] # 分包配置
│ ├── globalStyle{} # 全局样式配置
│ ├── tabBar{} # 底部导航配置
│ └── networkTimeout{} # 网络超时配置
├── 页面文件 # 页面文件与路由配置分离
└── 手动同步 # 需要手动保持文件与配置同步
特点:
约定式路由系统 (文件系统路由)
├── pages.config.ts # 全局配置和组件自动导入
├── src/pages/ # 页面目录结构即路由结构
│ ├── index/ # /pages/index/index
│ │ └── index.vue # 页面文件
│ ├── login/ # /pages/login/
│ │ ├── login.vue # 登录页面
│ │ └── register.vue # 注册页面
│ └── about/ # /pages/about/
│ ├── about.vue # 关于页面
│ └── components/ # 页面级组件
├── src/pages-sub/ # 分包页面 (自动识别为分包)
├── src/tabbar/ # 底部导航配置
│ └── config.ts # TabBar 配置
└── 自动生成 # 路由配置自动生成到 pages.json
特点:
Vue2 项目 - 集中式配置:
// pages.json
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页"
}
},
{
"path": "pages/login/login",
"style": {
"navigationBarTitleText": "登录",
"navigationStyle": "custom"
}
}
]
}
Vue3 项目 - 约定式路由:
<!-- src/pages/index/index.vue -->
<script setup lang="ts">
// 使用 definePage API
definePage({
style: {
navigationBarTitleText: "首页",
},
});
</script>
<template>
<view>首页内容</view>
</template>
Vue2 项目 - 手动分包配置:
// pages.json
{
"subPackages": [
{
"root": "pages-sub/maintenance",
"pages": [
{
"path": "maintainance",
"style": {
"navigationBarTitleText": "设备保养"
}
}
]
}
]
}
Vue3 项目 - 自动分包识别:
src/pages-sub/ # 自动识别为分包目录
├── maintenance/ # 分包名称
│ ├── maintainance.vue # 自动生成路径: pages-sub/maintenance/maintainance
│ └── excuteMaintainance.vue # 自动生成路径: pages-sub/maintenance/excuteMaintainance
└── complaint/ # 其他分包
├── complaint.vue
└── detail.vue
当接到路由迁移任务时,必须按照以下完整流程执行:
# 必须首先执行
Read: docs\prompts\route-migration-map.yml
在开始迁移页面之前,必须先搜索旧项目中所有跳转到该页面的代码:
# 示例:搜索所有跳转到 repairOrder 页面的代码
Grep: pattern="pages/repairOrder/repairOrder" path="gitee-example/" output_mode="content"
# 常见的跳转方式:
# - uni.navigateTo({ url: '/pages/repairOrder/repairOrder' })
# - uni.redirectTo({ url: '/pages/repairOrder/repairOrder' })
# - uni.switchTab({ url: '/pages/repairOrder/repairOrder' })
# - 字符串拼接:`/pages/repairOrder/repairOrder?id=${id}`
记录以下信息:
# 根据映射表创建新文件
# 旧路径: gitee-example/pages/repairOrder/repairOrder.vue
# 新路径: src/pages-sub/repair/order-list.vue
Write: src/pages-sub/repair/order-list.vue
按照其他技能的要求迁移:
code-migration 技能迁移 Vue 代码component-migration 技能迁移组件style-migration 技能迁移样式api-migration 技能迁移接口调用重要性: definePage 是页面配置的核心,缺少它会导致页面标题显示为 "unibest" 或空白,严重影响用户体验。
执行时机: 在创建页面文件后,必须立即添加 definePage 配置,这是不可跳过的强制步骤。
最小配置示例:
<script setup lang="ts">
// ✅ 所有页面必须添加 definePage,至少包含页面标题
definePage({
style: {
navigationBarTitleText: "维修工单池", // 导航栏标题(必需)
enablePullDownRefresh: false, // 是否启用下拉刷新
},
});
</script>
常见页面配置:
<script setup lang="ts">
definePage({
style: {
navigationBarTitleText: "维修工单池", // 导航栏标题
enablePullDownRefresh: true, // 启用下拉刷新
onReachBottomDistance: 50, // 触底距离
backgroundColor: "#f5f5f5", // 背景色
navigationBarBackgroundColor: "#368CFE", // 导航栏背景色
navigationBarTextStyle: "white", // 导航栏文字颜色
},
});
</script>
在 src/types/routes.ts 中添加:
// 1. 添加到 PageRoute 类型
export type PageRoute = "/pages/index/index" | "/pages-sub/repair/order-list"; // ✅ 新增
// 2. 添加参数类型
export interface PageParams {
"/pages-sub/repair/order-list": {
status?: string;
page?: number;
};
}
在 src/router/helpers.ts 中添加:
export class TypedRouter {
/** 跳转到维修工单池 */
static toRepairList(params?: PageParams["/pages-sub/repair/order-list"]) {
return navigateToTyped("/pages-sub/repair/order-list", params);
}
}
在 src/router/index.ts 中导出:
export const {
toRepairList, // ✅ 导出新方法
// ... 其他方法
} = TypedRouter;
在 src/router/helpers.ts 的 isValidRoute 中添加:
const validRoutes: PageRoute[] = [
"/pages-sub/repair/order-list", // ✅ 添加
// ...
];
根据阶段 2 记录的跳转点,逐一更新:
# 搜索新项目中是否有使用旧路径的跳转(理论上不应该有)
Grep: pattern="pages/repairOrder/repairOrder" path="src/" output_mode="content"
# 搜索是否有使用新路径但未使用 TypedRouter 的跳转
Grep: pattern="pages-sub/repair/order-list" path="src/" output_mode="content"
迁移前(Vue2 旧代码):
// ❌ 旧代码
uni.navigateTo({
url: `/pages/repairOrder/repairOrder?status=${status}&page=${page}`,
});
迁移后(Vue3 新代码):
// ✅ 新代码
import { TypedRouter } from "@/router";
TypedRouter.toRepairList({ status, page });
// navigateTo → TypedRouter.toXxx()
TypedRouter.toRepairList(params);
// redirectTo → redirectToTyped()
redirectToTyped("/pages-sub/repair/order-list", params);
// switchTab → switchTabTyped()(仅用于 Tab 页面)
switchTabTyped("/pages/index/index");
# 检查类型错误
pnpm type-check
# 确保新项目中没有旧路径的引用
Grep: pattern="pages/repairOrder" path="src/" output_mode="files_with_matches"
# 确保所有新路径跳转都使用了 TypedRouter
Grep: pattern="pages-sub/repair/order-list" path="src/" glob="*.vue" output_mode="content"
完成迁移后,更新映射表:
route_mappings:
repair_module:
- old: gitee-example/pages/repairOrder/repairOrder.vue
new: src/pages-sub/repair/order-list.vue
status: ✅ # 标记为已完成
Grep: pattern="pages/repairDispatch/repairDispatch" path="gitee-example/" output_mode="content"
搜索结果:
gitee-example/pages/repairOrder/repairOrder.vue:120: uni.navigateTo({ url: '/pages/repairDispatch/repairDispatch?id=' + item.id })
gitee-example/pages/index/index.vue:45: uni.navigateTo({ url: '/pages/repairDispatch/repairDispatch' })
从搜索结果分析:
id 参数(有时没有参数)navigateTo 跳转// src/types/routes.ts
export interface PageParams {
"/pages-sub/repair/dispatch": {
id?: string; // 可选参数
};
}
// src/router/helpers.ts
static toRepairDispatch(id?: string) {
return navigateToTyped('/pages-sub/repair/dispatch', { id })
}
在新项目中找到对应位置,替换为:
// 原来:uni.navigateTo({ url: '/pages/repairDispatch/repairDispatch?id=' + item.id })
// 现在:
TypedRouter.toRepairDispatch(item.id);
❌ 错误做法:直接按照自己的理解创建路径
✅ 正确做法:必须先读取映射表,严格按照映射表执行
❌ 错误做法:只创建了新页面,但忘记更新其他页面中的跳转代码
✅ 正确做法:迁移前先搜索所有跳转点,迁移后逐一更新
❌ 错误做法:直接使用 uni.navigateTo 跳转到新路径
✅ 正确做法:完整配置强类型路由系统(类型定义 + TypedRouter 方法)
❌ 错误做法:所有参数都定义为可选
✅ 正确做法:根据实际使用情况,正确区分必填和可选参数
❌ 错误做法:迁移完就认为完成了
✅ 正确做法:搜索验证,确保没有遗漏的旧路径引用
根据映射表,在新项目中创建对应的页面文件:
# 旧路径: pages/repairOrder/repairOrder.vue
# 新路径: src/pages-sub/repair/order-list.vue
# 创建文件
Write: src/pages-sub/repair/order-list.vue
在页面文件中使用 definePage 定义页面配置:
<script setup lang="ts">
definePage({
style: {
navigationBarTitleText: "维修工单池",
enablePullDownRefresh: true,
onReachBottomDistance: 50,
},
});
</script>
迁移页面的模板、脚本和样式:
更新所有跳转到该页面的路由路径:
// 旧代码
uni.navigateTo({
url: "/pages/repairOrder/repairOrder",
});
// 新代码
uni.navigateTo({
url: "/pages-sub/repair/order-list",
});
definePage 不支持 name 字段。使用 name 字段会导致类型错误或运行时警告。
❌ 错误示例:
definePage({
name: "test-z-paging-loading", // ❌ 错误:name 是非法字段
style: {
navigationBarTitleText: "z-paging-loading 组件测试",
},
});
✅ 有效配置项:
style: 页面样式配置 (对象)
navigationBarTitleText: 导航栏标题enablePullDownRefresh: 是否启用下拉刷新onReachBottomDistance: 触底距离navigationBarBackgroundColor: 导航栏背景色navigationBarTextStyle: 导航栏文字颜色 (black/white)navigationStyle: 导航栏样式 (default/custom)backgroundColor: 背景色middlewares: 中间件 (数组)<script setup lang="ts">
definePage({
style: {
// 导航栏标题(必需)
navigationBarTitleText: "维修工单",
// 下拉刷新
enablePullDownRefresh: true,
// 触底距离
onReachBottomDistance: 50,
// 导航栏背景色
navigationBarBackgroundColor: "#368CFE",
// 导航栏标题颜色
navigationBarTextStyle: "white",
// 自定义导航栏(谨慎使用)
navigationStyle: "custom",
// 背景色
backgroundColor: "#f5f5f5",
},
});
</script>
Vue3 项目采用了完整的强类型路由跳转系统,通过 TypeScript 提供编译时类型检查,避免路由错误和参数错误。
src/
├── types/
│ └── routes.ts # 路由类型定义文件
│ ├── PageRoute # 所有页面路由的联合类型
│ ├── TabRoute # Tab页面路由类型
│ └── PageParams # 页面参数类型映射
├── router/
│ ├── index.ts # 路由管理中心(导出所有工具)
│ ├── helpers.ts # 强类型路由跳转工具实现
│ │ ├── navigateToTyped() # 类型安全的页面跳转
│ │ ├── redirectToTyped() # 类型安全的重定向
│ │ ├── switchTabTyped() # 类型安全的Tab切换
│ │ └── TypedRouter # 封装业务逻辑的路由类
│ ├── examples.ts # 路由使用示例代码
│ ├── guards.ts # 路由守卫
│ └── interceptor.ts # 路由拦截器
src/types/routes.ts)/** 页面路由类型(完整版本,包含主包和分包) */
export type PageRoute =
/** 主包页面 */
| "/pages/index/index"
| "/pages/about/about"
| "/pages/me/me"
/** 维修管理模块 (10个页面) */
| "/pages-sub/repair/order-list" // 维修工单池
| "/pages-sub/repair/dispatch" // 维修待办单
| "/pages-sub/repair/finish" // 维修已办
| "/pages-sub/repair/order-detail" // 维修详情
| "/pages-sub/repair/add-order" // 添加维修记录
| "/pages-sub/repair/handle"; // 订单处理
// ... 更多路由
/** Tab页面路由类型 */
export type TabRoute = "/pages/index/index" | "/pages/address/list" | "/pages/me/me";
/** 页面参数类型映射(强类型约束) */
export interface PageParams {
"/pages/index/index": {};
"/pages/login/login": {
redirect?: string;
};
/** 维修工单详情页参数 */
"/pages-sub/repair/order-detail": {
repairId: string; // 必填参数
storeId: string; // 必填参数
};
/** 维修工单列表页参数 */
"/pages-sub/repair/order-list": {
status?: string; // 可选参数
page?: number;
row?: number;
repairName?: string;
state?: string;
};
/** 订单处理页面参数 */
"/pages-sub/repair/handle": {
/** 操作类型: DISPATCH-派单, TRANSFER-转单, BACK-退单, FINISH-办结 */
action: "DISPATCH" | "TRANSFER" | "BACK" | "FINISH"; // 联合类型约束
repairId: string;
repairType: string;
preStaffId?: string;
preStaffName?: string;
repairObjType?: string;
publicArea?: string;
repairChannel?: string;
};
}
TypedRouter 是封装了业务逻辑的路由工具类,提供语义化的跳转方法:
import { TypedRouter } from "@/router";
// ✅ 推荐:使用 TypedRouter 进行跳转
// 跳转到维修工单详情(自动类型检查)
TypedRouter.toRepairDetail("repair123", "store456");
// 跳转到维修工单列表(可选参数)
TypedRouter.toRepairList({ status: "pending", page: 1 });
// 跳转到订单处理页面(复杂参数对象)
TypedRouter.toRepairHandle({
action: "DISPATCH", // TypeScript 会检查值必须是允许的类型
repairId: "R001",
repairType: "TYPE_01",
preStaffId: "STAFF_123",
});
// 跳转到首页(Tab切换)
TypedRouter.toHome();
// 跳转到选择器页面(级联跳转)
TypedRouter.toSelectFloor(); // 第一步:选择楼栋
TypedRouter.toSelectUnit("F001"); // 第二步:选择单元
TypedRouter.toSelectRoom("F001", "U001"); // 第三步:选择房屋
import { navigateToTyped, redirectToTyped, switchTabTyped } from "@/router";
// 类型安全的页面跳转
navigateToTyped("/pages-sub/repair/order-detail", {
repairId: "repair123", // TypeScript 会检查参数类型是否匹配
storeId: "store456",
});
// 类型安全的重定向
redirectToTyped("/pages/login/login", {
redirect: "/pages/me/me",
});
// 类型安全的Tab切换
switchTabTyped("/pages/index/index");
Vue2 旧代码:
// ❌ 旧代码:字符串拼接,无类型检查,容易出错
uni.navigateTo({
url: `/pages/repairOrder/repairOrder?repairId=${repairId}&status=${status}`,
});
Vue3 新代码:
// ✅ 新代码:使用 TypedRouter,类型安全
import { TypedRouter } from "@/router";
TypedRouter.toRepairDetail(repairId, storeId);
Vue2 旧代码:
// ❌ 旧代码:手动拼接URL,参数类型不安全
const url = `/pages/repairDispatch/repairDispatch?action=DISPATCH&repairId=${id}`;
uni.navigateTo({ url });
Vue3 新代码:
// ✅ 新代码:参数对象,TypeScript 自动检查
TypedRouter.toRepairHandle({
action: "DISPATCH",
repairId: id,
repairType: type,
});
当迁移完成一个新页面后,需要将其加入强类型路由系统。
在 src/types/routes.ts 中添加页面路由和参数类型:
// 1. 在 PageRoute 类型中添加新路由
export type PageRoute = "/pages/index/index" | "/pages-sub/repair/new-page"; // ✅ 新增路由
// 2. 在 PageParams 接口中定义参数类型
export interface PageParams {
"/pages-sub/repair/new-page": {
repairId: string; // 必填参数
status?: string; // 可选参数
};
}
在 src/router/helpers.ts 的 TypedRouter 类中添加对应方法:
export class TypedRouter {
/** 跳转到新页面 */
static toNewPage(repairId: string, status?: string) {
return navigateToTyped("/pages-sub/repair/new-page", { repairId, status });
}
}
在 src/router/index.ts 中导出新方法(便于外部使用):
export const {
toRepairList,
toRepairDetail,
toNewPage, // ✅ 导出新方法
// ... 其他方法
} = TypedRouter;
isValidRoute 验证函数在 src/router/helpers.ts 的 isValidRoute 函数中添加新路由:
export function isValidRoute(path: string): path is PageRoute {
const validRoutes: PageRoute[] = [
"/pages/index/index",
"/pages-sub/repair/new-page", // ✅ 添加到验证列表
// ... 其他路由
];
return validRoutes.includes(path as PageRoute);
}
<script setup lang="ts">
import { TypedRouter } from "@/router";
import type { RepairOrder } from "@/types/repair";
/** 查看工单详情 */
function handleViewDetail(item: RepairOrder) {
// ✅ 类型安全的跳转,参数自动推断和检查
TypedRouter.toRepairDetail(item.repairId!, userInfo.storeId);
}
/** 派单 */
function handleDispatch(item: RepairOrder) {
TypedRouter.toRepairHandle({
action: "DISPATCH", // 类型约束为 'DISPATCH' | 'TRANSFER' | 'BACK' | 'FINISH'
repairId: item.repairId!,
repairType: item.repairType,
});
}
/** 结束工单 */
function handleEndOrder(item: RepairOrder) {
TypedRouter.toEndRepair(item.repairId!, item.communityId);
}
</script>
<script setup lang="ts">
import { TypedRouter } from "@/router";
import { useSelectorStore } from "@/stores/useSelectorStore";
const selectorStore = useSelectorStore();
/** 选择楼栋 */
function handleSelectFloor() {
TypedRouter.toSelectFloor(); // 无参数
}
/** 选择单元 */
function handleSelectUnit() {
// ✅ 参数类型自动检查
TypedRouter.toSelectUnit(selectorStore.selectedFloor.floorId);
}
/** 选择房屋 */
function handleSelectRoom() {
// ✅ 多个参数的类型安全
TypedRouter.toSelectRoom(selectorStore.selectedFloor.floorId, selectorStore.selectedUnit.unitId);
}
</script>
// ❌ 错误:路由路径拼写错误,TypeScript 会报错
TypedRouter.toRepairDetial("R001", "S001"); // 方法名拼写错误
// ❌ 错误:参数类型不匹配
TypedRouter.toRepairDetail("R001", 123); // storeId 应该是 string,不是 number
// ❌ 错误:缺少必填参数
TypedRouter.toRepairHandle({
action: "DISPATCH",
// repairId 是必填的,会报错
});
// ❌ 错误:参数值不在允许范围内
TypedRouter.toRepairHandle({
action: "INVALID_ACTION", // action 必须是 'DISPATCH' | 'TRANSFER' | 'BACK' | 'FINISH'
repairId: "R001",
repairType: "TYPE_01",
});
// ✅ IDE 会自动提示所有可用的跳转方法
TypedRouter.to; // 输入 "to" 后会显示所有 toXxx 方法
// ✅ 参数对象的智能提示
TypedRouter.toRepairHandle({
action: "", // 输入时会提示 'DISPATCH' | 'TRANSFER' | 'BACK' | 'FINISH'
// 光标移到这里会提示所有可用参数
});
完成页面迁移后,必须完成以下强类型路由相关的配置:
类型定义
PageRoute 中添加了新路由路径PageParams 中定义了参数类型TypedRouter 方法
TypedRouter 类中添加了对应的跳转方法toXxxXxx 格式)路由验证
isValidRoute 函数中添加了新路由代码迁移
uni.navigateTo 改为使用 TypedRouter测试验证
解决方案:按照"步骤 5"依次完成四步配置即可。
解决方案:使用参数对象形式:
// ❌ 不推荐:参数太多
static toRepairHandle(action, repairId, repairType, preStaffId, preStaffName, ...)
// ✅ 推荐:使用参数对象
static toRepairHandle(params: PageParams['/pages-sub/repair/handle'])
解决方案:在类型定义中使用可选参数:
export interface PageParams {
"/pages-sub/repair/order-list": {
status?: string; // 可选参数
page?: number;
};
}
// ⚠️ 不推荐:直接使用 uni.navigateTo(无类型检查)
uni.navigateTo({
url: "/pages-sub/repair/order-list",
});
// ✅ 推荐:使用 TypedRouter(类型安全)
TypedRouter.toRepairList();
// 带参数跳转
TypedRouter.toRepairDetail("repair123", "store456");
<script setup lang="ts">
import { onLoad } from "@dcloudio/uni-app";
const orderId = ref("");
const status = ref("");
onLoad((options) => {
orderId.value = options.orderId as string;
status.value = options.status as string;
});
</script>
映射表检查
文件创建
definePage 配置
路由跳转
功能验证
通过约定式路由系统,实现路由配置的自动化管理,提升开发效率和代码可维护性!