更新记录

1.0.1(2026-07-10) 下载此版本

更新日志

v1.0.0 (2026-07-10)

  • 首发:声明式路由中间件 + RBAC 权限引擎
  • 内置 auth 中间件(未登录自动跳登录页并携带 ?redirect= 回跳)
  • 导航包装 API:router.go / replace / relaunch / tab / back / toHome / toLogin / toFail
  • canAccess 复杂权限(与页面 roles 做 AND)、action: 指令、节流、afterEach 后置守卫
  • 函数式适配器 navigateTo / redirectTo / reLaunch / switchTab / navigateBack
  • 纯 JS/TS、零三方运行时依赖,H5 / 小程序 / App 多端通用

平台兼容性

uni-app(3.99)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟

ailingyun-router-guard(基于 uniapp-pages-router 的声明式路由中间件 + RBAC)

基于 uniapp-pages-router声明式路由中间件 + RBAC 引擎。 一切皆中间件:登录校验、企业认证、VIP 校验、埋点……同一种 (ctx, next) 写法,可同步可异步。 本插件是通用、可移植的,不依赖任何具体项目的 store / 常量 / UI;用到状态的地方都用「你的 store」抽象表示,复制到其他工程直接套用即可。

一句话原则:每次跳转都先过一遍中间件管线(全局 → 页面 → RBAC 闸门),通过才真正调用 uni 导航。


一、插件简介

  • 声明式守卫:在 pages.json 里用 middleware / roles 声明页面级权限,不在业务代码里写一堆 if (!login) uni.redirectTo(...)
  • 中间件管线:「全局中间件 → 页面中间件 → RBAC 自动闸门」统一契约 (ctx, next),同步异步皆可,可 redirect / abort
  • 内置 auth:页面加 "middleware": ["auth"] 即生效,未登录自动跳登录页并携带 ?redirect=回跳地址
  • RBAC + canAccessroles 命中其一即放行;复杂权限用 canAccess(path, roles) 做同步细粒度判断(与 roles 做 AND)。
  • 导航包装router.go / replace / relaunch / tab / back / toHome / toLogin / toFail,兼容原生 success/fail/animationType 等透传。
  • 纯 JS/TS:无三方运行时依赖,H5 / 小程序 / App 多端通用。

二、版本信息

内容
当前版本 1.0.0
更新日期 2026-07-10
兼容环境 uni-app(Vue2 / Vue3 均可),HBuilderX 3.1.0+
体积 轻量纯 JS/TS,无三方运行时依赖
前置依赖 uniapp-pages-router(开发依赖,>=1.2.0)

三、安装与引入

1. 前置依赖(uniapp-pages-router,开发依赖)

本插件依赖 uniapp-pages-router 在编译期把路由表注入为全局常量,并提供 uniRouter() Vite 插件。它是编译期 / 构建期依赖(只参与打包,不进运行时 bundle),按开发依赖安装:

npm install -D uniapp-pages-router      # 或 pnpm add -D / yarn add -D

它负责两件事:

  1. 编译期生成全局常量 UNI_ROUTE_MAP / UNI_ROUTES / UNI_TABBAR_ROUTES / UNI_TABBAR_MAP
  2. 提供 uniRouter({ pages, fields }) 插件,把 middleware / roles 等自定义字段注入路由表。

插件只在运行时读取这些常量。不安装 / 不配置它也能用:此时 UNI_ROUTE_MAP 为空,所有页当公开页,引擎退化为纯导航层(路径未知会 console.error)。

2. 导入本插件(市场导入 / 手动导入)

  • 市场导入:在 HBuilderX 插件市场点击「使用 HBuilderX 导入插件」,会自动放到 js_sdk/aly-router-guard/
  • 手动导入:把本目录整体放到项目根目录的 js_sdk/aly-router-guard/ 下即可,可改目录。

3. 编译期注入守卫字段(vite.config.ts

uniRoutermiddleware / roles 注入路由表:

import { uniRouter } from "uniapp-pages-router";

uniRouter({
  pages: "src/pages.json",
  fields: ["middleware", "roles"], // 关键:声明要注入的自定义字段
});

不配核心包也能用——此时所有页当公开页,引擎退化为纯导航层,路径未知会 console.error 提示。

4. 引入方式

import {
  initRouter,
  overrideUni,
  router,
  navigateTo,
  redirectTo,
  reLaunch,
  switchTab,
  navigateBack,
} from "@/js_sdk/aly-router-guard/index.js";

目录结构

js_sdk/aly-router-guard/
├── index.js            ← 装配入口:initRouter / overrideUni / router / 导航函数
├── index.d.ts          ← TS 类型入口
├── core/               ← 引擎核心(纯逻辑,无导航/UI 依赖)
│   ├── config.js       ← 配置单例(模块间唯一可变状态)
│   └── runner.js       ← 守卫管线 runOne / runPipeline
├── middlewares/        ← 中间件
│   ├── auth.js         ← 内置 auth 中间件
│   └── index.js        ← 内置中间件注册表 builtinMiddlewares
├── navigator/          ← 导航层
│   ├── index.js        ← Router 类 + 捕获原始 uni + 适配器
│   └── utils.js        ← parseTarget / buildQuery / currentPagePath
├── types/              ← TS 类型(JS 项目也可受益于 JSDoc)
├── examples/           ← 非运行时示例(middlewares.example.js)
├── package.json
└── README.md

目录名 ailingyun-router-guard 可按你的项目规划改名或移动到其他位置(详见上方说明),导入路径相应调整即可。移到其他目录也无影响。


四、快速上手

1. 30 秒上手(最小可用)

// 1) 初始化(App 启动一次)
initRouter({
  isLogin: () => !!userStore.token,
  loginUrl: '/pages/member/login/login',
  homeUrl: '/pages/index/index',
})

// 2) 保护页面(pages.json)
{ "path": "pages/profile/profile", "middleware": ["auth"] }

// 3) 跳转(任意页面)
import { router } from '@/js_sdk/aly-router-guard/index.js'
router.go('/pages/profile/profile')

就这三步:初始化 → 在 pages.json 声明守卫 → 用 router.go 跳转

2. 在 App 启动时初始化一次(initRouter

import {
  initRouter,
  overrideUni,
  router,
} from "@/js_sdk/aly-router-guard/index.js";
import { useUserStore } from "你的store路径";
const userStore = useUserStore();

initRouter({
  // 登录态 & 角色(RBAC 用,可异步)
  isLogin: () => !!userStore.token,
  getRoles: () => userStore.roles || [],

  // 系统页地址(按你的工程填)
  loginUrl: "/pages/member/login/login",
  homeUrl: "/pages/index/index",
  failUrl: "/pages/public/fail",
  fallback: "/pages/public/forbidden", // RBAC 无权限时的回退页(优先于 loginUrl)
  webviewUrl: "/pages/public/web", // 非 H5 打开外链用的 webview 页

  // 未登录自定义处理(配了就不强制跳登录页,自行决定 toast / 弹窗 / 跳转)
  onAuthFail: (ctx) => {
    uni.showToast({ title: "请先登录", icon: "none" });
  },

  // 后置守卫
  afterEach: (ctx) => console.log("跳转完成:", ctx.path, "<-", ctx.from),

  // 你的业务中间件注册表(key 可覆盖内置 auth)
  middlewares: {
    companyCheck: (ctx, next) => {
      if (userStore.companyVerified) return next();
      ctx.redirect("/pages/company/apply");
    },
  },

  // action: 指令型处理器(不导航,只触发回调)
  actions: {
    share: () => uni.showToast({ title: "分享" }),
  },
});

// 让三方库 / 隐式 uni.navigateTo 也走守卫
overrideUni();

// 可选:挂到全局方便任意地方调用
uni.$router = router;

3. 声明式守卫(pages.json

无需在页面代码里写 if (!login) uni.redirectTo(...),直接在路由表里声明:

{
  "pages": [
    { "path": "pages/index/index" }, // 公开页
    { "path": "pages/profile/profile", "middleware": ["auth"] }, // 需登录(内置 auth)
    {
      "path": "pages/admin/dashboard",
      "middleware": ["auth"],
      "roles": ["admin"],
    }, // 登录 + admin
    {
      "path": "pages/company/home",
      "middleware": ["auth", "companyCheck"],
      "roles": ["company"],
    },
  ],
}
  • middleware: [...] → 页面级中间件,按数组顺序执行。
  • roles: [...] → RBAC 自动闸门:用户角色命中其一即放行。

4. 导航 API(带示例)

import { router } from "@/js_sdk/aly-router-guard/index.js";

router.go("/pages/profile/profile"); // 普通跳转(自动选 navigateTo / switchTab)
router.go("/pages/profile/profile", { id: 1 }); // 带参数对象,自动拼成 query
router.go("UserCenter"); // 用别名(pages.json 的 alias)
router.replace("/pages/main/mine"); // 替换当前页(redirectTo)
router.relaunch("/pages/main/home"); // 重启(reLaunch)
router.tab("/pages/main/home"); // 跳 tabBar(switchTab)
router.back(); // 返回上一页
router.toHome(); // 回首页
router.toLogin(); // 去登录页,自动携带 ?redirect=当前页
router.toFail(404, "页面不存在"); // 去错误页

函数式适配器(直接替代 uni.*):

import {
  navigateTo,
  redirectTo,
  reLaunch,
  switchTab,
  navigateBack,
} from "@/js_sdk/aly-router-guard/index.js";

navigateTo("/pages/x/x"); // → router.go
redirectTo("/pages/x/x"); // → router.go(..., { redirect: true })
reLaunch("/pages/x/x"); // → router.go(..., { reLaunch: true })
switchTab("/pages/x/x"); // → router.go
navigateBack(1); // 数字 delta,或 { delta: 1 }

success / fail / animationType 等原生选项会原样透传给 uni,不会被吞。

target 形式:

形式 行为
/pages/x/x/pages/x/x?id=1 普通路径,query 与 params 合并
别名 命中 alias 字段,归一到真实 path
http(s)://... H5 直接打开;非 H5 跳 webviewUrl?url=<编码外链>
action:xxx 触发 config.actions['xxx']()不导航

gooptions 字段:

go / replace / relaunch 的第三个参数 options 会透传给底层 uni,并支持以下引擎保留字段:

字段 作用
skipGuard: true 跳过整个守卫管线,直接跳转(登录回跳等强制跳转用)
redirect: true redirectTo(也可直接调 router.replace
replace: true redirect: true,用于登录类重定向(替换当前页)
reLaunch: true reLaunch(也可直接调 router.relaunch
onError: (e) => void 跳转失败回调(同时会 console.error
其它(success / fail / animationType / events …) 原样透传给 uni

五、高级用法

1. 中间件系统

契约:

type MiddlewareFn = (
  ctx: MiddlewareCtx,
  next: () => void,
) => void | Promise<void>;

写一个中间件,本质上就是决定下面四种行为之一。以下只是示例模板,不是库内置函数(库目前只内置 auth 一个中间件,见下方「内置 auth 中间件」);把它们复制到你的 middlewares.js 改名即可用:

// 1) 放行(调用 next 继续管线)
export function pass(ctx, next) {
  next();
}

// 2) 重定向(默认 navigateTo 入栈,保留来源页,使「返回上一页」可用)
export function goLogin(ctx) {
  ctx.redirect("/pages/member/login/login");
}

// 3) 重定向且替换当前页(登录类重定向常用)
export function goLoginReplace(ctx) {
  ctx.redirect("/pages/member/login/login", { replace: true });
}

// 4) 静默中止(不跳转,停留在当前页)
export function silent(ctx) {
  ctx.abort();
} // 或不调 next 直接 return

你真实项目里的中间件长这样(参考库内 examples/middlewares.example.js):

export const vipOnly = async (ctx, next) => {
  if (useUserStore().roles.includes("vip")) return next(); // 行为 1:放行
  ctx.redirect("/pages/public/forbidden"); // 行为 2:重定向
};

ctx.redirect(target, opts?) 行为(重要)

  • 默认 navigateTo 入栈,保留来源页,使「返回上一页」可用;
  • { replace: true }redirectTo 替换当前页;
  • { reLaunch: true }reLaunch 重启。
  • 内置 auth 未登录重定向即使用 { replace: true };RBAC 无权限回退默认入栈。

ctx 字段path 目标 · route 路由记录 · from 来源页 · params 参数 · roles 当前页角色 · redirect · abort

2. 内置 auth 中间件

builtinMiddlewares.auth 已内置,页面加 "middleware": ["auth"] 即生效:

  1. isLogin() 为真 → 放行;
  2. 为假且配了 onAuthFail → 调用它(自行决定弹窗/跳转),不强制跳登录页
  3. 否则重定向 loginUrl?redirect=<原目标>,登录页 onLoad(query.redirect) 可回跳。

3. 自定义中间件

// middlewares.js(放在你的业务层)
import { useUserStore } from "你的store路径";

export const vipOnly = async (ctx, next) => {
  const roles = useUserStore().roles || [];
  if (roles.includes("vip")) return next();
  ctx.redirect("/pages/public/forbidden"); // 无权限 → 403 页(默认入栈)
};

注册方式(二选一):

  • 声明式(按名启用):在 initRouter({ middlewares: { vipOnly } }) 注册,pages.json"middleware": ["vipOnly"] 引用。
  • 运行时全局router.use(vipOnly) 对所有页面生效。

4. 执行顺序

全局中间件 → 页面声明式中间件 → RBAC 自动闸门,全部通过才真正跳转。

  • 任一环调用 ctx.redirect(t)立即终止管线并跳转到 t(后续中间件 / RBAC 不再执行);
  • 调用 ctx.abort() 或不调 next 直接 return静默中止,停留在当前页;
  • 未注册的 middleware key 会 console.warn 并跳过,不会崩溃。

5. RBAC 自动闸门与 canAccess

给页面声明 roles,引擎自动校验,无需写中间件:

{ "path": "pages/admin/dashboard", "roles": ["admin"] }
  • getRoles() 返回当前用户角色;命中其一即放行(some)。
  • 都不满足 → 跳 fallback(优先)或 loginUrl;两者都没配则静默中止。

canAccess 复杂权限:

当「角色命中仍不够、还要按路径 / 资源做更细判断」时,用 canAccess

initRouter({
  getRoles: () => userStore.roles || [],
  canAccess: (path, userRoles) => boolean,
});

它与页面 roles 的关系(AND 运算):

最终放行 = (页面 roles 命中其一)  AND  (canAccess(path, 用户角色) === true)

调用时机与范围(关键):

  • 只要配置了 canAccess,它会对每一次跳转都执行(不论该页是否声明 roles);
  • 页面声明了 roles → 先校验角色命中,再与 canAccess 结果做 AND;
  • 页面声明 roles → 角色部分视为通过,最终是否放行完全由 canAccess 决定(相当于用 canAccess 做整站权限矩阵)。

⚠️ canAccess 是同步调用(不被 await):必须直接 return boolean;需要异步(如调接口拉权限)时改用自定义中间件。 ⚠️ canAccess 只拿到 (path, 用户角色),拿不到本次跳转的 params;要基于参数判断权限请用自定义中间件(可访问 ctx.params)。

示例 1:基于路径的权限码矩阵

const permissionMap = {
  "/pages/admin/dashboard": ["admin.dashboard"],
  "/pages/admin/users": ["admin.users"],
  "/pages/finance/bill": ["finance.bill.read"],
};

initRouter({
  getRoles: () => userStore.roles || [],
  canAccess: (path, roles) => {
    const required = permissionMap[path];
    if (!required) return true; // 不在矩阵里的页,按 roles / 公开 处理
    const owned = userStore.permissions || []; // 你从接口/菜单拿到的权限码
    return required.every((p) => owned.includes(p)); // 所需权限码全拥有才放行
  },
});

示例 2:按路径前缀做整站级限制

initRouter({
  getRoles: () => userStore.roles || [],
  canAccess: (path) => {
    // canAccess 每次都跑:即使页面没声明 roles,也能在这里统一卡后台页
    if (path.startsWith("/pages/admin/"))
      return userStore.roles.includes("admin");
    return true;
  },
});

示例 3:整站「维护模式」一刀切

initRouter({
  canAccess: (path) => {
    if (path === "/pages/public/maintain") return true; // 维护页本身永远可进
    return !appStore.maintaining; // 维护期间其余页全部拦截
  },
});

与自定义中间件的取舍:

  • 简单「角色命中即放行」→ 页面 roles
  • 按路径 / 资源做同步细粒度判断 → canAccess
  • 需要异步接口、或要 ctx.redirect 到特定页、或要埋点日志 → 自定义中间件(router.usemiddlewares 注册)。

6. 其它高级

skipGuard(强制跳转,常用于登录回跳)

// 登录成功后强制回跳,跳过守卫避免被再次拦截
router.go(decodeURIComponent(query.redirect), {}, { skipGuard: true });

action: 指令(不导航,仅触发回调)

action: 是一种「伪跳转」:它复用 router.go 的统一入口,但目标以 action: 开头时不导航,而是执行你在 initRouter 里注册的同名回调函数,并立即返回、不走任何守卫管线

① 注册处理器

initRouter({
  // ...
  actions: {
    share: () => uni.showToast({ title: "分享" }),
    logout: () => {
      userStore.logout()   // 你的登出逻辑(读 store / 清 token 等)
      router.toLogin()      // 处理器内部可自行再调用 router 导航
    },
    contact: () => uni.makePhoneCall({ phoneNumber: "400-xxx" }),
  },
})

② 触发

router.go("action:share")     // 触发 actions.share(),不跳转
router.go("action:logout")    // 触发 actions.logout()

在模板里也可以直接绑:

<view @click="router.go('action:share')">分享</view>
<view @click="router.go('action:logout')">退出登录</view>

③ 关键行为(务必了解)

  • 不导航、不进守卫action: 在解析阶段就直接返回,因此不会经过全局中间件 / 页面中间件 / RBAC 闸门,也不会触发 afterEach。它本质上是「页面内的一条本地指令」,不是路由。
  • 处理器内部可再导航:处理器里是你自己的代码,可自由再调用 router.toLogin() / router.go(...);此时新的跳转会正常走守卫管线。
  • 未知 action 静默忽略:若 config.actions 里没有对应键(或根本没配 actions),router.go("action:xxx") 什么都不做、也不报错——适合「指令名可能动态来自后端」的场景。
  • 不解析参数action: 只携带指令名,不解析 query,处理器拿不到任何入参;需要数据请在闭包里捕获,或读全局状态 / store。
  • 统一入口的价值:因为和真实路径共用 router.go,你可以把「跳转」与「本地指令」混在同一个数据驱动列表里,用同一个处理函数,无需区分分支:
const items = [
  { label: "个人资料", target: "/pages/member/profile" },
  { label: "分享",     target: "action:share" },
  { label: "退出登录", target: "action:logout" },
]
// 统一处理:onTap(item) { router.go(item.target) }

节流(防连点)

router.setThrottleDelay(0); // 关闭;默认 300ms,只对“短时间重复点同一目标”生效

全局中间件管理

router.use(mw); // 注册全局中间件
router.clearMiddlewares(); // 清空所有运行时注册的全局中间件(调试用)

7. 配置项一览(initRouter(cfg)

字段 类型 说明
middlewares Record<string, fn> 命名中间件注册表,key 可覆盖内置 auth
getRoles () => string[] \| Promise<string[]> 当前用户角色(供 RBAC / canAccess
isLogin () => boolean \| Promise<boolean> 登录态(内置 auth 用,可异步校验 token)
loginUrl string 登录页;auth 未登录重定向目标;toLogin() 默认目标
homeUrl string 首页;toHome() 目标
failUrl string 错误页;toFail(code, msg) 目标
fallback string RBAC 无权限重定向目标(优先于 loginUrl
onAuthFail (ctx) => void 未登录自定义处理,配了就不强制跳登录页
afterEach (ctx) => void 后置守卫,每次成功跳转后回调
canAccess (path, roles) => boolean 复杂权限 AND 兜底;同步调用、对每次跳转都执行,与 roles 命中做「与」运算(详见本节 5)
webviewUrl string 非 H5 打开外链的 webview 承载页
actions Record<string, () => void> action:xxx 指令处理器:不导航、不走守卫,执行同名回调;未知键静默忽略
throttleDelay number 节流阈值 ms,默认 300
globalMiddlewares fn[] 全局中间件,等价于逐个 router.use()

8. API 速查

方法 说明
go(target, params?, options?) 通用跳转,自动选 navigateTo / switchTab
replace / relaunch / tab redirectTo / reLaunch / switchTab
back(delta = 1) 返回(H5 走 history.go
toHome() / toLogin() / toFail(code, msg) 系统页快捷跳转
use(mw) / clearMiddlewares() 全局中间件注册 / 清空
setThrottleDelay(ms) 调节流阈值
getRoute(path) / isTabBar(path) 路由查询
hasHistory() / canGoBack(delta) 历史 / 可否返回
navigateTo / redirectTo / reLaunch / switchTab / navigateBack 函数式适配器

六、示例项目

目录结构见第三章。examples/middlewares.example.js 提供了完整可参考的中间件写法,覆盖:登录校验、企业认证、VIP 专属、异步 token 校验、全局埋点,以及它们与 initRouter 注册、pages.json 声明的对应关系(三处一一对应)。复制时按文件内注释的「① 定义 / ② 注册 / ③ 声明」落位即可。

// 见 js_sdk/aly-router-guard/examples/middlewares.example.js
// 关键片段:
export const vipOnly = (ctx, next) => {
  if (getIsVip()) return next();
  ctx.abort(); // 静默中止:停留在当前页,不跳转
};

七、更新日志

v1.0.0 (2026-07-10)

  • 首发:声明式中间件 + RBAC 引擎
  • 内置 auth 中间件(未登录跳登录页并回跳)
  • 导航包装 API:router.go / replace / relaunch / tab / back / toHome / toLogin / toFail
  • canAccess 复杂权限(与 roles 做 AND)、action: 指令、节流、afterEach 后置守卫
  • 函数式适配器 navigateTo / redirectTo / reLaunch / switchTab / navigateBack

八、常见问题(FAQ)

  1. 系统页不要加 auth / roles(登录页、失败页等),否则可能自我重定向;确需进入用 { skipGuard: true }
  2. <navigator> 组件拦不到:本引擎与 uni.addInterceptor 都拦不住原生组件跳转,请统一用 router.go / navigateTo
  3. tabBar 页不接收参数uni.switchTab 平台限制,tab / switchTab 带参目标页也拿不到,需传参请用全局状态或 uni.$emit
  4. overrideUni() 只改写 4 个 APInavigateTo / redirectTo / reLaunch / switchTabnavigateBack 未改写。
  5. action:xxx 不导航:仅触发回调,适合「分享 / 退出登录 / 客服」等指令。
  6. 对象 / 数组参数自动 JSON 序列化进 query,接收端按需 JSON.parse

九、贡献与反馈

欢迎在插件市场该页面下提评论 / 反馈,或提交 Issue / PR。 插件 ID:ailingyun-router-guard


十、许可证

MIT


隐私与权限声明

  • 本插件需要申请的系统权限列表:无
  • 本插件采集的数据、发送的服务器地址、以及数据用途说明:插件不采集任何数据
  • 本插件是否包含广告:无

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

许可协议

MIT License

Copyright (c) 2026 ailingyun

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

暂无用户评论。