feat(ui): redirect tenant switch away from tenant-scoped routes

Switching the active tenant triggers window.location.reload() to
re-key every cached store / open SSE stream / in-flight request.
The catch: when the user is currently sitting on a route whose URL
embeds a tenant-scoped resource id, the new tenant lands on a
404/403 and the user sees a blank screen.

Three risky route shapes today:

  /platform/knowledge-bases/:kbId
  /platform/knowledge-bases/:kbId/creatChat
  /platform/chat/:chatid

Add a tiny tenantSwitch utility (navigateAfterTenantSwitch) that
matches the current path against this list and redirects to
/platform/knowledge-bases — the platform default landing, always
renderable regardless of role. Anything else (settings, agent
list, KB list, etc.) continues to reload its current path because
those views handle empty/forbidden data gracefully.

Both tenant-switch call sites (TenantSelector and UserMenu) use
the helper so the matrix lives in one place; future tenant-scoped
routes only need to add a regex to the list.
This commit is contained in:
wizardchen
2026-05-15 12:16:49 +08:00
committed by lyingbug
parent 8c0510f2e1
commit 854cb0c2a1
3 changed files with 54 additions and 5 deletions
+4 -2
View File
@@ -82,6 +82,7 @@ import { useAuthStore } from '@/stores/auth'
import { searchTenants, type TenantInfo } from '@/api/tenant'
import { useI18n } from 'vue-i18n'
import { MessagePlugin } from 'tdesign-vue-next'
import { navigateAfterTenantSwitch } from '@/utils/tenantSwitch'
const { t } = useI18n()
const authStore = useAuthStore()
@@ -161,7 +162,7 @@ const clearSearch = () => {
const selectTenant = (tenantId: number) => {
// 找到选中的租户信息
const selectedTenant = tenants.value.find(t => t.id === tenantId)
if (tenantId === defaultTenantId.value) {
authStore.setSelectedTenant(null, null)
} else {
@@ -169,8 +170,9 @@ const selectTenant = (tenantId: number) => {
}
closeDropdown()
MessagePlugin.success(t('tenant.switchSuccess'))
// 切换租户后跳转到新租户下安全的入口(详见 tenantSwitch.ts 注释)。
setTimeout(() => {
window.location.reload()
navigateAfterTenantSwitch()
}, 500)
}
+6 -3
View File
@@ -214,6 +214,7 @@ import { getCurrentUser, logout as logoutApi } from '@/api/auth'
import { useI18n } from 'vue-i18n'
import IMChannelsOverviewPanel from '@/components/IMChannelsOverviewPanel.vue'
import { listAllIMChannels, type IMChannelOverview } from '@/api/agent'
import { navigateAfterTenantSwitch } from '@/utils/tenantSwitch'
const { t } = useI18n()
@@ -372,10 +373,12 @@ const switchToTenant = (m: Membership) => {
closeAll()
MessagePlugin.success(t('tenant.switchSuccess'))
// Hard reload so every cached store / open SSE stream / in-flight
// request gets re-keyed under the new tenant. Same approach as
// TenantSelector.vue.
// request gets re-keyed under the new tenant. If the current path
// embeds a tenant-scoped resource id, reload would white-screen the
// user; navigateAfterTenantSwitch redirects to the platform home in
// that case. Same helper as TenantSelector.vue.
setTimeout(() => {
window.location.reload()
navigateAfterTenantSwitch()
}, 400)
}
+44
View File
@@ -0,0 +1,44 @@
// Tenant-switch navigation helper.
//
// Switching the active tenant triggers a full page reload so every cached
// store / open SSE stream / in-flight request gets re-keyed under the new
// tenant. The catch: if the user is currently sitting on a route whose URL
// embeds a tenant-scoped resource id (KB id, session id), reloading lands
// them on a 403/empty page in the new tenant — visible to the user as a
// blank screen. For those routes we redirect to the platform default
// landing page instead, which is always safe regardless of role.
//
// Anything not on this list keeps its current path on reload (settings,
// agent list, KB list, etc. — they re-fetch under the new tenant
// naturally).
const TENANT_SCOPED_ROUTE_PATTERNS: RegExp[] = [
/^\/platform\/knowledge-bases\/[^/]+$/, // /platform/knowledge-bases/:kbId
/^\/platform\/knowledge-bases\/[^/]+\/creatChat$/, // /platform/knowledge-bases/:kbId/creatChat
/^\/platform\/chat\/[^/]+$/, // /platform/chat/:chatid
]
const SAFE_FALLBACK_PATH = '/platform/knowledge-bases'
/**
* Return the URL to navigate to after a tenant switch. If the current path
* embeds a tenant-scoped id, returns the safe fallback; otherwise returns
* null to signal "reload current path".
*/
export function tenantSwitchTargetPath(currentPath: string): string | null {
const isScoped = TENANT_SCOPED_ROUTE_PATTERNS.some(re => re.test(currentPath))
return isScoped ? SAFE_FALLBACK_PATH : null
}
/**
* Perform the post-switch navigation. Either redirect to the safe fallback
* (if the current page is tenant-scoped) or hard-reload the current page.
*/
export function navigateAfterTenantSwitch(): void {
const target = tenantSwitchTargetPath(window.location.pathname)
if (target !== null) {
window.location.href = target
} else {
window.location.reload()
}
}