跳到主要内容

页面、路由与权限开发

BBC 8.0 的三个应用使用不同的路由模型:买家 PC 端由前端静态声明完整路由;平台管理端和商家端由“本地页面能力 + 后端当前用户菜单树”共同生成。新增页面前必须先确认所属应用,不能把三个端的步骤混用。

开发前判断

问题处理方式
页面只属于买家 PC放入 apps/pc,在 PC 静态路由中注册。
页面只属于平台管理端放入 apps/admin,同时维护页面注册和组件映射。
页面只属于商家端放入 apps/seller,同时维护页面注册和组件映射。
组件只被一个端使用放入该应用的 src/components
组件已被两个以上应用真实复用评估提升到 packages/components
接口只属于一个端放入该应用的 src/api
多端只复用后端数据结构使用 packages/api/models 的生成类型。

应用之间不得直接导入内部页面、组件或 API。跨端复用能力应通过合适的 packages/* 公共入口提供。

新增买家 PC 页面

PC 路由集中在 apps/pc/src/router/index.ts,页面通常放在 apps/pc/src/views

1. 创建页面

apps/pc/src/views/custom-page.vue
<script setup lang="ts">
import { useTranslate } from "@shop-tnt/components/useTranslate";

const t = useTranslate();
</script>

<template>
<main class="custom-page">
<h1>{{ t("自定义页面") }}</h1>
</main>
</template>

2. 注册静态路由

apps/pc/src/router/index.tsroutes 中增加唯一的 pathname

{
path: '/custom-page',
name: 'customPage',
...migratedPage(() => import('@/views/custom-page.vue'), '自定义页面')
}

需要登录的页面明确设置 requiresAuth

{
path: '/member/custom-page',
name: 'memberCustomPage',
...migratedPage(() => import('@/views/member/custom-page.vue'), '会员自定义页面', {
requiresAuth: true,
backName: 'member'
})
}

loginMode: 'dialog' 只用于购物车、客服等需要保留当前上下文的前台体验;账户和会员中心页面通常使用默认的登录页跳转。新增业务访问条件时,应在 PC 路由守卫中同时保护直接输入 URL 的场景,不能只隐藏页面入口。

新增平台或商家菜单页

Admin 和 Seller 的可见菜单结构来自登录后的 current 菜单接口:

  • Admin:admin/systems/roles/current/menus
  • Seller:seller/shops/roles/current/menus

前端只登记可以渲染的页面。菜单层级、同级顺序、标题和后端非空图标均以后端返回为准。

以下以 Admin 页面为例;Seller 使用对应的 apps/seller 文件。

1. 创建页面组件

apps/admin/src/views/example/example-list.vue

2. 注册页面能力

apps/admin/src/router/page-routes.ts 增加扁平记录,后台 path 必须是绝对路径:

page({
path: "/example/example-list",
name: "exampleList",
title: "exampleList",
});

name 是前后端权限契约的一部分,必须唯一。不要为了展示标题而修改既有 name,也不要在这里构造菜单父子树。

3. 绑定实际组件

apps/admin/src/router/route-components.ts 增加同名映射:

export const routeComponents = {
exampleList: () => import("@/views/example/example-list.vue"),
};

页面注册存在但组件映射缺失属于本地契约错误,应在开发阶段直接修复,不能生成空白兼容页。

4. 维护后端菜单

在平台菜单管理中创建对应菜单节点:

  • identifier 必须等于前端 route name,这里是 exampleList
  • 父子层级、顺序、标题和图标在后端维护。
  • 纯分组节点只需要后端提供 children,前端权限路由会用 RouterView 承载,不需要创建占位 Vue 页面。
  • 修改角色授权后,确认 current 菜单接口实际返回该 identifier。

不要直接按照旧文档手工向菜单表插入自增 ID。8.0 菜单 ID 可能是 19 位 Long,直接当 JavaScript Number 或人工推算 ID 都存在精度和数据完整性风险。

5. 维护标题和语言包

页面内用户可见文案使用 useTranslate()。新增路由标题或国际化 key 后检查 packages/locales,并执行:

pnpm locale:check

可见菜单标题仍以后端菜单为准,不能从前端语言包反向推导后端菜单。

新增隐藏详情或编辑页

详情、编辑、审核页通常不出现在菜单中,但需要跟随入口页面授权。页面注册必须声明 hidden 和返回来源:

page({
path: "/example/detail/:id",
name: "exampleDetail",
title: "exampleDetail",
backName: "exampleList",
hidden: true,
});

并在 route-components.ts 中绑定:

export const routeComponents = {
exampleDetail: () => import("@/views/example/example-detail.vue"),
};

一个隐藏页可以从多个已授权入口进入时使用 backNames。店铺认证、支付进件等登录后固定系统流程才使用 authFlow,普通详情页不能用它绕过角色权限。

统一二级页返回

Admin 和 Seller 的表单、审核、详情和长内容页优先使用 PageActionBarusePageBack()

<script setup lang="ts">
import { PageActionBar } from "@shop-tnt/components";
import { usePageBack } from "@shop-tnt/core";

const { goBack } = usePageBack();
</script>

<template>
<section class="example-detail-page">
<!-- 页面内容 -->
<PageActionBar :show-save="false" @back="goBack" />
</section>
</template>

列表跳转到二级页且需要保留搜索、筛选和分页时,使用 withReturnTo()

const { withReturnTo } = usePageBack();

router.push(withReturnTo({ name: "exampleDetail", params: { id: row.id } }));

不要在各页面散落 router.back()、硬编码返回 URL 或为固定操作栏手工增加底部占位。

页面接口与组件

功能开关与权限

功能是否在当前构建中出现,会受到 VITE_DISTRIBUTIONVITE_I18NVITE_IMVITE_LIVEVIDEO 影响。

本地页面文件存在不代表当前环境一定开放。验收必须同时检查:

  1. 功能开关允许该路由。
  2. 后端 current 菜单返回对应 identifier。
  3. 普通角色确实获得授权。

验证清单

只改一个应用页面时执行对应开发构建:

pnpm build:dev:pc
pnpm build:dev:seller
pnpm build:dev:admin

路由、共享包或多个应用一起变化时执行:

pnpm typecheck
pnpm build:dev

完成前还应验证:

  • 页面 URL 直接打开和刷新均可访问。
  • Admin/Seller 的页面注册、组件映射、后端 identifier 完全一致。
  • 普通角色、超级管理员或店主的菜单结果符合授权预期。
  • 隐藏页只能跟随已授权入口访问,返回目标正确。
  • 功能关闭后,直接输入 URL 不能绕过限制。
  • 浏览器控制台没有缺少组件、未知路由或权限映射错误。