From 2727cfda86253b608dcb520149c843980d21c172 Mon Sep 17 00:00:00 2001 From: fengpu <18691763612@163.com> Date: Fri, 5 Jun 2026 16:54:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=BB=BC=E5=90=88=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=90=8E=E5=8F=B0=E6=89=81=E5=B9=B3=E5=8C=96=20+=20=E5=8E=86?= =?UTF-8?q?=E5=8F=B2=E6=8C=89=20portal+app=20=E9=9A=94=E7=A6=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - admin-portal 从多应用形态重构为单应用形态:3个一级菜单(系统设置/运营中心/设备中心)直接挂在 portal 下,URL=/admin-portal/
/ - TopBar 移除应用切换下拉,应用名改为纯文本展示 - PageHistory 隔离粒度从 portal 级改为 portal+app 级(scopeKey 形如 admin-portal__admin / tenant-portal__nutrition) - Home 原型首页区分 admin 一级菜单卡片与 tenant 应用卡片布局,tenant 新增"应用主页"入口卡片 - nav.ts 用 adminSections + getAdminVirtualApp() 替代原 adminApps 占位应用数组 Co-Authored-By: Claude Opus 4.7 --- CLAUDE.md | 19 +-- src/composables/usePageHistory.ts | 55 ++++--- src/docs/business-architecture.md | 31 ++-- src/layouts/PortalLayout.vue | 90 ++++++----- src/layouts/components/PageHistory.vue | 1 + src/layouts/components/TopBar.vue | 32 ++-- src/meta/nav.ts | 91 +++++++++-- src/pages/console/VisibilityConsole.vue | 7 +- src/pages/home/Home.vue | 201 +++++++++++++++++++++--- src/router/routes.ts | 59 ++++++- 10 files changed, 445 insertions(+), 141 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 4462ee0..f761a0e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,22 +9,23 @@ --- ## 四个终端 - | 终端 | key | UI 库 | 主要用户 | 业务定位 | |------|-----|-------|---------|---------| -| 综合管理后台 | `admin-portal` | Ant Design Vue | 平台管理员 | 平台级管理(系统设置/运营中心/设备中心 等独立应用) | -| 租户运营后台 | `tenant-portal` | Ant Design Vue | 租户运营人员 | **17 个独立可部署应用的统一入口**(营养管理/健康监测/...) | +| 综合管理后台 | `admin-portal` | Ant Design Vue | 平台管理员 | 平台级管理(系统设置/运营中心/设备中心 为顶部一级菜单,**无子应用、无应用切换、无应用网格**) | +| 租户运营后台 | `tenant-portal` | Ant Design Vue | 租户运营人员 | **17 个独立可部署应用的统一入口**(营养管理/健康监测/...),有应用网格首页 | | C 端微信小程序 | `miniprogram` | Vant 4(375×812) | 终端用户 | 用户自助查询、上报、咨询 | | 硬件终端屏 | `hardware` | 自定义(动态 viewport) | 现场操作员 | 净菜柜屏 / 自助查询屏 / 大屏 等 | -> **重要架构说明**(对齐生产 vue 项目 `D:\Work\platform-vue-tenant` 的 qiankun 微前端形态): -> - `admin-portal` / `tenant-portal` 只是登录门户外壳。下挂多个**独立可部署的应用(NavApp)**,每个应用对应生产 vue 项目中的一个 `subXxx` 子应用。 -> - 进入某个应用后,顶部显示**该应用内的一级菜单 tabs**(NavSection),左侧显示**当前一级菜单下的二/三级菜单**(NavGroup → NavLeaf)。 -> - URL 结构:`///
/`,例:`/tenant-portal/nutrition/nutrition-farm/monitor/crop` -> - 应用首页(`/`):展示应用网格卡片(参考 vue 项目 main 的 `home.vue`)。 +> **重要架构说明**(admin / tenant 形态差异): +> - **综合管理后台为单应用形态**:3 个一级菜单(系统设置 / 运营中心 / 设备中心)直接挂在 portal 下,URL = `/admin-portal/
/`,无应用切换概念。 +> - **租户运营后台为多应用形态**(对齐生产 vue 项目 `D:\Work\platform-vue-tenant` 的 qiankun 微前端):下挂多个**独立可部署的应用(NavApp)**,每个应用对应生产 vue 项目中的一个 `subXxx` 子应用。 +> - 进入某个应用后,顶部显示**该应用内的一级菜单 tabs**(NavSection),左侧显示**当前一级菜单下的二/三级菜单**(NavGroup → NavLeaf) +> - URL:`/tenant-portal//
/`,例:`/tenant-portal/nutrition/nutrition-farm/monitor/crop` +> - 应用首页(`/tenant-portal`):展示应用网格卡片(参考 vue 项目 main 的 `home.vue`) +> - 不支持在 TopBar 切换应用;切换应用通过"应用首页"或"返回原型首页"完成 +> - **最近打开菜单(PageHistory)按 portal + app 隔离**:scope key 形如 `admin-portal__admin` / `tenant-portal__nutrition`;切换应用后只显示当前应用打开过的菜单。 > 详细业务边界与各应用模块清单见 **`src/docs/business-architecture.md`**。AI 在生成原型前**必须先读这份文档**。 - --- ## 技术栈 diff --git a/src/composables/usePageHistory.ts b/src/composables/usePageHistory.ts index 0a9ff12..a5a867f 100644 --- a/src/composables/usePageHistory.ts +++ b/src/composables/usePageHistory.ts @@ -2,7 +2,10 @@ * 页面访问历史(最近打开的菜单 Tabs) * - 对齐生产 vue 项目 D:\Work\platform-vue-tenant\apps\subNutritionManagement\src\store\pageHistory * - 实现方式:composable + localStorage(项目不引入 Pinia) - * - 作用域:每个 portal 独立维护一份历史(key: pageHistory.) + * - 作用域:按 __ 隔离一份历史 + * · admin-portal__admin + * · tenant-portal__nutrition / tenant-portal__monitor / ... + * 切换应用后只显示当前应用打开过的菜单 */ import type { Router } from 'vue-router' @@ -23,44 +26,44 @@ export interface PageHistoryItem { const STORAGE_KEY_PREFIX = 'pageHistory.' -const readFromStorage = (portalKey: string): PageHistoryItem[] => { +const readFromStorage = (scopeKey: string): PageHistoryItem[] => { try { - const raw = localStorage.getItem(STORAGE_KEY_PREFIX + portalKey) + const raw = localStorage.getItem(STORAGE_KEY_PREFIX + scopeKey) return raw ? (JSON.parse(raw) as PageHistoryItem[]) : [] } catch { return [] } } -const writeToStorage = (portalKey: string, list: PageHistoryItem[]): void => { - localStorage.setItem(STORAGE_KEY_PREFIX + portalKey, JSON.stringify(list)) +const writeToStorage = (scopeKey: string, list: PageHistoryItem[]): void => { + localStorage.setItem(STORAGE_KEY_PREFIX + scopeKey, JSON.stringify(list)) } -/** 各 portal 的响应式历史列表 */ +/** 各 scope 的响应式历史列表 */ const stores = new Map>>() -const getStore = (portalKey: string) => { - let s = stores.get(portalKey) +const getStore = (scopeKey: string) => { + let s = stores.get(scopeKey) if (!s) { - s = ref(readFromStorage(portalKey)) - stores.set(portalKey, s) + s = ref(readFromStorage(scopeKey)) + stores.set(scopeKey, s) } return s } /** * 使用页面历史(无数量上限,对齐 vue 项目) - * @param portalKey 'admin-portal' | 'tenant-portal' 等 + * @param scopeKey 形如 'admin-portal__admin' / 'tenant-portal__nutrition' */ -export const usePageHistory = (portalKey: string) => { - const list = getStore(portalKey) +export const usePageHistory = (scopeKey: string) => { + const list = getStore(scopeKey) /** 新增页面(已存在则前移到末尾) */ const addPage = (item: PageHistoryItem): void => { const arr = (list.value ?? []).filter(i => i.path !== item.path) arr.push(item) list.value = arr - writeToStorage(portalKey, arr) + writeToStorage(scopeKey, arr) } /** 移除指定路径 */ @@ -70,7 +73,7 @@ export const usePageHistory = (portalKey: string) => { if (idx === -1) return null arr.splice(idx, 1) list.value = [...arr] - writeToStorage(portalKey, list.value) + writeToStorage(scopeKey, list.value) // 返回下一个应跳转的路径(关闭当前页时用) return list.value[list.value.length - 1]?.path ?? null } @@ -78,21 +81,29 @@ export const usePageHistory = (portalKey: string) => { /** 关闭除指定路径外的所有 */ const closeOthers = (path: string): void => { list.value = (list.value ?? []).filter(i => i.path === path) - writeToStorage(portalKey, list.value) + writeToStorage(scopeKey, list.value) } /** 清空 */ const clear = (): void => { list.value = [] - writeToStorage(portalKey, []) + writeToStorage(scopeKey, []) } return { list, addPage, removePage, closeOthers, clear } } /** - * 在 router.afterEach 钩子里自动追踪某个 portal 的访问 - * - 仅当路由 meta.portal 匹配时记录 + * 计算路由对应的历史 scope key + * - admin 路由 meta.app 固定为 'admin' + * - tenant 路由 meta.app 为具体应用 key(nutrition / monitor / ...) + */ +const resolveScopeKey = (portal: string, app: string | undefined): string => + `${portal}__${app ?? 'default'}` + +/** + * 在 router.afterEach 钩子里自动追踪访问 + * - 仅当路由 meta.portal 存在时记录 * - 忽略应用网格首页 / 重定向路由 */ export const installPageHistoryTracker = (router: Router): void => { @@ -101,11 +112,13 @@ export const installPageHistoryTracker = (router: Router): void => { if (!portal) return if (to.meta?.isAppGrid) return if (!to.name) return - const { addPage } = usePageHistory(portal) + const app = to.meta?.app as string | undefined + const scopeKey = resolveScopeKey(portal, app) + const { addPage } = usePageHistory(scopeKey) addPage({ path: to.path, title: String(to.meta?.label ?? to.name ?? to.path), - app: to.meta?.app as string | undefined, + app, section: to.meta?.section as string | undefined, leafKey: String(to.name), }) diff --git a/src/docs/business-architecture.md b/src/docs/business-architecture.md index de9b9f1..5912d39 100644 --- a/src/docs/business-architecture.md +++ b/src/docs/business-architecture.md @@ -2,7 +2,7 @@ > **作用**:这份文档供 AI 在生成原型时参考,确保理解业务上下文、不串端、不重复造功能。 > **维护规则**:用户在规划每个端的具体功能模块时填写本文档,AI 在 new-page / gen-prd / cross-role-doc-builder 等任务中**必须先读这份文档**。 -> _Last updated: 2026-06-04 / 阶段 16(用户提供四端粗粒度功能清单)_ +> _Last updated: 2026-06-05 / 阶段 17(综合后台扁平化、租户后台去应用切换、历史按 portal+app 隔离)_ --- @@ -10,21 +10,32 @@ ### 1.1 综合管理后台 `admin-portal` -| 子端 | 一级模块 | 说明 | -|------|---------|------| -| 系统设置 | (待规划) | 平台层基础设置:组织、账号、权限、字典、参数 | -| 运营中心 | (待规划) | 平台运营视角:租户管理、应用配置、计费、统计 | -| 设备中心 | (待规划) | 跨租户硬件接入与运维:设备注册、固件版本、在线状态 | +> **形态**:单应用形态。**没有子应用概念**,3 个一级菜单(系统设置 / 运营中心 / 设备中心)直接挂在 portal 下。 +> **URL 模式**:`/admin-portal/
/` +> **UI 表现**:左上角显示固定标题"综合管理后台",**无应用切换下拉**、**无应用网格首页**、**无"返回应用首页"快捷入口**;顶部一级菜单 tabs 即三大模块。 + +| 一级模块 | 说明 | +|---------|------| +| 系统设置 | 平台层基础设置:组织、账号、权限、字典、参数 | +| 运营中心 | 平台运营视角:租户管理、应用配置、计费、统计 | +| 设备中心 | 跨租户硬件接入与运维:设备注册、固件版本、在线状态 | > 综合后台是**跨租户**视角;与租户运营后台的边界:凡是只能在租户内部生效的业务(如某租户的食堂菜单),归属租户后台。 ### 1.2 租户运营后台 `tenant-portal` -| 一级模块 | 说明 | +> **形态**:多应用形态(对齐生产 vue 项目 qiankun 微前端,每个应用对应一个独立可部署的 subXxx)。 +> **URL 模式**:`/tenant-portal//
/`;`/tenant-portal` 为应用网格首页。 +> **UI 表现**: +> - `/tenant-portal` 为应用网格首页(无侧栏 / 无顶部一级菜单),展示 17 个应用卡片 +> - 进入应用后,左上角显示当前应用名(纯文本,**不可点击切换应用**),顶部一级菜单 tabs = 当前应用内的 sections +> - 右上角提供"应用首页"快捷入口回到应用网格、"返回原型首页"回到全局导航 +> - 切换应用只能通过"应用首页"或"返回原型首页" + +| 一级模块(应用) | 说明 | |---------|------| -| 租户首页 | 单租户运营视角的首页:核心 KPI、待办、快捷入口 | -| 控制台 | 租户运营的控制中心:组织架构、岗位、人员、流程 | -| 系统管理 | 租户级系统管理:账号、角色、字典、日志、消息模板 | +| 控制台 | 租户运营驾驶舱:核心 KPI、待办、快捷入口 | +| 系统管理 | 租户级系统管理:组织、账号、角色、字典、日志、消息模板 | | 营养管理 | 食堂全链路:种植、采购、加工、菜品、食谱、订餐、出餐、质检 | | 健康体检 | 体检计划、机构、项目、报告、统计 | | 体重管理 | 体重档案、采集、目标、干预、提醒 | diff --git a/src/layouts/PortalLayout.vue b/src/layouts/PortalLayout.vue index 58f51bf..acf34af 100644 --- a/src/layouts/PortalLayout.vue +++ b/src/layouts/PortalLayout.vue @@ -6,11 +6,10 @@ :current-app="isAppGrid ? undefined : currentApp" :sections="isAppGrid ? [] : currentAppSections" :current-section="currentSectionKey" - :show-app-grid="!isAppGrid" + :show-app-grid="!isAppGrid && variant === 'tenant'" :app-grid-path="portalPath" :portal-title="portalTitle" @section-change="onSectionChange" - @app-change="onAppChange" @search="onSearch" >