From add4237b1fe0377ded3507b8b956b056c993c889 Mon Sep 17 00:00:00 2001 From: "Ray.Hao" <1490493387@qq.com> Date: Fri, 12 Dec 2025 08:17:32 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E5=A4=9A?= =?UTF-8?q?=E7=A7=9F=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.development | 9 ++ .env.production | 6 ++ src/components/TenantSelector/index.vue | 124 ++++++++++++++++++++++++ src/constants/index.ts | 10 ++ src/plugins/permission.ts | 39 +++++++- src/store/modules/tenant-store.ts | 85 ++++++++-------- 6 files changed, 226 insertions(+), 47 deletions(-) create mode 100644 src/components/TenantSelector/index.vue diff --git a/.env.development b/.env.development index be875675..7218a9e4 100644 --- a/.env.development +++ b/.env.development @@ -14,3 +14,12 @@ VITE_APP_WS_ENDPOINT= # 启用 Mock 服务 VITE_MOCK_DEV_SERVER=false + +# ============================================ +# 多租户功能开关 +# ============================================ +# 是否启用多租户功能(默认:false) +# true: 启用多租户,显示租户切换器,发送 tenant-id 请求头 +# false: 禁用多租户,隐藏租户相关UI,不发送 tenant-id 请求头 +# 注意:前端开关需要与后端配置(youlai.tenant.enabled)保持一致 +VITE_APP_TENANT_ENABLED=false diff --git a/.env.production b/.env.production index cf3bf9a2..65908200 100644 --- a/.env.production +++ b/.env.production @@ -4,3 +4,9 @@ VITE_APP_BASE_API = '/prod-api' VITE_APP_TITLE=vue3-element-admin # WebSocket端点(可选) #VITE_APP_WS_ENDPOINT=wss://api.youlai.tech/ws + +# ============================================ +# 多租户功能开关 +# ============================================ +# 是否启用多租户功能(默认:false) +VITE_APP_TENANT_ENABLED=false diff --git a/src/components/TenantSelector/index.vue b/src/components/TenantSelector/index.vue new file mode 100644 index 00000000..2aa545c5 --- /dev/null +++ b/src/components/TenantSelector/index.vue @@ -0,0 +1,124 @@ + + + + + {{ currentTenantName }} + + + + + + + {{ tenant.name }} + + + + + + + + + + + + + diff --git a/src/constants/index.ts b/src/constants/index.ts index ef122d45..34a64764 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -11,6 +11,10 @@ export const STORAGE_KEYS = { REFRESH_TOKEN: `${APP_PREFIX}:auth:refresh_token`, // JWT刷新令牌 REMEMBER_ME: `${APP_PREFIX}:auth:remember_me`, // 记住登录状态 + // 租户相关 + TENANT_ID: `${APP_PREFIX}:tenant:id`, // 当前租户ID + TENANT_INFO: `${APP_PREFIX}:tenant:info`, // 当前租户信息 + // 系统核心相关 DICT_CACHE: `${APP_PREFIX}:system:dict_cache`, // 字典数据缓存 @@ -41,6 +45,11 @@ export const AUTH_KEYS = { REMEMBER_ME: STORAGE_KEYS.REMEMBER_ME, } as const; +export const TENANT_KEYS = { + TENANT_ID: STORAGE_KEYS.TENANT_ID, + TENANT_INFO: STORAGE_KEYS.TENANT_INFO, +} as const; + export const SYSTEM_KEYS = { DICT_CACHE: STORAGE_KEYS.DICT_CACHE, } as const; @@ -66,6 +75,7 @@ export const APP_KEYS = { export const ALL_STORAGE_KEYS = { ...AUTH_KEYS, + ...TENANT_KEYS, ...SYSTEM_KEYS, ...SETTINGS_KEYS, ...APP_KEYS, diff --git a/src/plugins/permission.ts b/src/plugins/permission.ts index fa9ad4f1..43235e8b 100644 --- a/src/plugins/permission.ts +++ b/src/plugins/permission.ts @@ -4,6 +4,35 @@ import router from "@/router"; import { usePermissionStore, useUserStore } from "@/store"; import { useTenantStoreHook } from "@/store/modules/tenant-store"; +/** + * 多租户功能是否启用 + * 通过环境变量控制,实现零侵入的可插拔设计 + */ +const TENANT_ENABLED = import.meta.env.VITE_APP_TENANT_ENABLED === "true"; + +/** + * 初始化多租户上下文(插件式设计) + * - 仅在启用多租户时执行 + * - 失败不影响主流程(优雅降级) + * - 完全解耦,可随时移除 + */ +async function initTenantContextIfEnabled(): Promise { + if (!TENANT_ENABLED) { + console.debug("[Tenant] 多租户功能未启用,跳过初始化"); + return; + } + + try { + console.debug("[Tenant] 开始加载租户..."); + const tenantStore = useTenantStoreHook(); + await tenantStore.loadTenant(); + console.debug("[Tenant] 租户加载成功"); + } catch (error) { + // 优雅降级:后端未启用多租户或接口不存在时,不影响正常流程 + console.debug("[Tenant] 租户上下文初始化失败(可能后端未启用多租户):", error); + } +} + export function setupPermission() { const whiteList = ["/login"]; @@ -39,11 +68,11 @@ export function setupPermission() { await userStore.getUserInfo(); } - // 登录成功后,尝试获取租户列表和当前租户信息(如果启用多租户) - // 最小侵入:如果接口失败,不影响正常流程(可能是单租户模式) - const tenantStore = useTenantStoreHook(); - // 由 tenantStore 内部自行判断前端多租户开关(VITE_APP_TENANT_ENABLED) - await tenantStore.prepareTenantContextAfterLogin(); + // 【多租户插件】初始化租户上下文(零侵入设计) + // - 通过 VITE_APP_TENANT_ENABLED 环境变量控制 + // - 失败不影响主流程,优雅降级 + // - 可通过设置环境变量为 false 完全移除此功能 + await initTenantContextIfEnabled(); const dynamicRoutes = await permissionStore.generateRoutes(); dynamicRoutes.forEach((route: RouteRecordRaw) => { diff --git a/src/store/modules/tenant-store.ts b/src/store/modules/tenant-store.ts index a8a69db2..b06bc834 100644 --- a/src/store/modules/tenant-store.ts +++ b/src/store/modules/tenant-store.ts @@ -1,11 +1,6 @@ import { store } from "@/store"; import TenantAPI, { type TenantInfo } from "@/api/system/tenant-api"; - -// 前端多租户开关;默认开启,若后端未启用多租户可在 .env 设置 VITE_APP_TENANT_ENABLED=false -const TENANT_ENABLED = import.meta.env.VITE_APP_TENANT_ENABLED !== "false"; - -const TENANT_ID_KEY = "current_tenant_id"; -const TENANT_INFO_KEY = "current_tenant_info"; +import { STORAGE_KEYS } from "@/constants"; /** * 租户 Store @@ -19,12 +14,12 @@ export const useTenantStore = defineStore("tenant", () => { const tenantList = ref([]); /** - * 初始化租户信息 + * 恢复租户信息 * 从 localStorage 恢复上次使用的租户 */ - function initTenant() { - const savedTenantId = localStorage.getItem(TENANT_ID_KEY); - const savedTenantInfo = localStorage.getItem(TENANT_INFO_KEY); + function restoreTenant() { + const savedTenantId = localStorage.getItem(STORAGE_KEYS.TENANT_ID); + const savedTenantInfo = localStorage.getItem(STORAGE_KEYS.TENANT_INFO); if (savedTenantId) { currentTenantId.value = Number(savedTenantId); @@ -56,35 +51,41 @@ export const useTenantStore = defineStore("tenant", () => { } /** - * 登录后初始化租户:获取列表并尽量确定当前租户 - * - 忽略错误,以便单租户模式不受影响 + * 加载租户 + * + * 执行流程: + * 1. 获取用户可访问的租户列表 + * 2. 尝试获取后端当前租户 + * 3. 如果只有一个租户,自动选中 + * 4. 否则等待用户手动选择 + * + * @remarks + * 此方法由路由守卫调用,仅在启用多租户时执行 */ - // 登录后准备租户上下文:先取租户列表,再用后端返回的当前租户;若单租户则自动选中 - async function prepareTenantContextAfterLogin() { - if (!TENANT_ENABLED) { - return; - } + async function loadTenant() { + // 1. 获取租户列表 + await fetchTenantList(); - try { - await fetchTenantList(); - - if (tenantList.value.length > 0 && !currentTenantId.value) { - try { - const currentTenantInfo = await TenantAPI.getCurrentTenant(); - if (currentTenantInfo) { - setCurrentTenant(currentTenantInfo); - } else if (tenantList.value.length === 1) { - setCurrentTenant(tenantList.value[0]); - } - } catch (error) { - if (tenantList.value.length === 1) { - setCurrentTenant(tenantList.value[0]); - } - console.debug("获取当前租户信息失败(可能是单租户模式):", error); + // 2. 如果已有租户列表且未设置当前租户 + if (tenantList.value.length > 0 && !currentTenantId.value) { + try { + // 尝试从后端获取当前租户 + const currentTenantInfo = await TenantAPI.getCurrentTenant(); + if (currentTenantInfo) { + setCurrentTenant(currentTenantInfo); + return; } + } catch (error) { + console.debug("[Tenant] 获取当前租户失败,尝试自动选择:", error); + } + + // 3. 如果只有一个租户,自动选中 + if (tenantList.value.length === 1) { + setCurrentTenant(tenantList.value[0]); + console.debug("[Tenant] 自动选中唯一租户:", tenantList.value[0].name); + } else { + console.debug("[Tenant] 多个租户可用,等待用户选择"); } - } catch (error) { - console.debug("获取租户列表失败(可能是单租户模式):", error); } } @@ -98,8 +99,8 @@ export const useTenantStore = defineStore("tenant", () => { currentTenant.value = tenant; // 保存到 localStorage - localStorage.setItem(TENANT_ID_KEY, String(tenant.id)); - localStorage.setItem(TENANT_INFO_KEY, JSON.stringify(tenant)); + localStorage.setItem(STORAGE_KEYS.TENANT_ID, String(tenant.id)); + localStorage.setItem(STORAGE_KEYS.TENANT_INFO, JSON.stringify(tenant)); } /** @@ -145,18 +146,18 @@ export const useTenantStore = defineStore("tenant", () => { currentTenantId.value = null; currentTenant.value = null; tenantList.value = []; - localStorage.removeItem(TENANT_ID_KEY); - localStorage.removeItem(TENANT_INFO_KEY); + localStorage.removeItem(STORAGE_KEYS.TENANT_ID); + localStorage.removeItem(STORAGE_KEYS.TENANT_INFO); } - // 初始化 - initTenant(); + // 恢复本地租户信息 + restoreTenant(); return { currentTenantId, currentTenant, tenantList, - prepareTenantContextAfterLogin, + loadTenant, fetchTenantList, setCurrentTenant, switchTenant,