zhangjf 8e4afcd1a5 feat: TenantAwareLoadBalancer 整合 TenantRoutingProperties 配置
问题:TenantRoutingProperties 定义了配置但未被使用

解决方案:
1. TenantAwareLoadBalancer 注入 TenantRoutingProperties
   - 使用配置的 tenantHeader 名称
   - 使用配置的 buildTenantGroup 方法
   - 使用配置的 isSharedService 判断
   - 使用配置的 isFallbackToShared 策略

2. 新增功能
   - 支持 enabled=false 禁用租户路由
   - 共享服务跳过租户过滤
   - 可配置是否回退到共享实例

3. 更新测试适配新构造函数
2026-02-19 21:02:25 +08:00

341 lines
8.9 KiB
Vue

<template>
<el-container class="main-layout">
<!-- 侧边栏 -->
<el-aside :width="appStore.sidebarCollapsed ? '64px' : '220px'" class="sidebar">
<div class="logo">
<img src="/vite.svg" alt="Logo" />
<span v-show="!appStore.sidebarCollapsed">资金服务平台</span>
</div>
<el-menu
:default-active="activeMenu"
:collapse="appStore.sidebarCollapsed"
:collapse-transition="false"
router
class="sidebar-menu"
>
<el-menu-item index="/dashboard">
<el-icon><HomeFilled /></el-icon>
<span>首页</span>
</el-menu-item>
<el-sub-menu index="system">
<template #title>
<el-icon><Setting /></el-icon>
<span>系统管理</span>
</template>
<el-menu-item index="/system/user">
<el-icon><User /></el-icon>
<span>用户管理</span>
</el-menu-item>
<el-menu-item index="/system/role">
<el-icon><UserFilled /></el-icon>
<span>角色管理</span>
</el-menu-item>
<el-menu-item index="/system/dept">
<el-icon><OfficeBuilding /></el-icon>
<span>部门管理</span>
</el-menu-item>
<el-menu-item index="/system/menu">
<el-icon><Menu /></el-icon>
<span>菜单管理</span>
</el-menu-item>
<el-menu-item index="/system/config">
<el-icon><Tools /></el-icon>
<span>参数设置</span>
</el-menu-item>
<el-menu-item index="/system/tenant">
<el-icon><OfficeBuilding /></el-icon>
<span>租户管理</span>
</el-menu-item>
<el-menu-item index="/system/file">
<el-icon><FolderOpened /></el-icon>
<span>文件管理</span>
</el-menu-item>
</el-sub-menu>
<el-menu-item index="/customer/list">
<el-icon><UserFilled /></el-icon>
<span>客户管理</span>
</el-menu-item>
<el-sub-menu index="project">
<template #title>
<el-icon><Folder /></el-icon>
<span>项目管理</span>
</template>
<el-menu-item index="/project/list">
<el-icon><FolderOpened /></el-icon>
<span>项目列表</span>
</el-menu-item>
<el-menu-item index="/project/requirement">
<el-icon><Document /></el-icon>
<span>需求工单</span>
</el-menu-item>
</el-sub-menu>
<el-sub-menu index="expense">
<template #title>
<el-icon><Money /></el-icon>
<span>支出管理</span>
</template>
<el-menu-item index="/expense/type">
<el-icon><PriceTag /></el-icon>
<span>支出类型</span>
</el-menu-item>
<el-menu-item index="/expense/list">
<el-icon><Wallet /></el-icon>
<span>支出列表</span>
</el-menu-item>
</el-sub-menu>
<el-menu-item index="/receivable/list">
<el-icon><Wallet /></el-icon>
<span>应收款管理</span>
</el-menu-item>
<el-sub-menu index="report">
<template #title>
<el-icon><DataAnalysis /></el-icon>
<span>报表中心</span>
</template>
<el-menu-item index="/report/project-finance">
<el-icon><TrendCharts /></el-icon>
<span>项目收支报表</span>
</el-menu-item>
</el-sub-menu>
</el-menu>
</el-aside>
<!-- 主内容区 -->
<el-container>
<!-- 顶部导航 -->
<el-header class="header">
<div class="left">
<el-icon class="collapse-btn" @click="appStore.toggleSidebar">
<Fold v-if="!appStore.sidebarCollapsed" />
<Expand v-else />
</el-icon>
<el-breadcrumb separator="/">
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item v-if="currentRoute.meta.parent">
{{ currentRoute.meta.parent }}
</el-breadcrumb-item>
<el-breadcrumb-item v-if="currentRoute.meta.title !== '首页'">
{{ currentRoute.meta.title }}
</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="right">
<el-dropdown @command="handleCommand">
<span class="user-info">
<el-avatar :size="32" :src="userStore.userInfo?.avatar">
{{ userStore.userInfo?.realName?.charAt(0) || 'U' }}
</el-avatar>
<span class="username">{{ userStore.userInfo?.realName || '用户' }}</span>
<el-icon><ArrowDown /></el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="profile">个人中心</el-dropdown-item>
<el-dropdown-item command="settings">系统设置</el-dropdown-item>
<el-dropdown-item divided command="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</el-header>
<!-- 内容区 -->
<el-main class="main-content">
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</el-main>
</el-container>
</el-container>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessageBox } from 'element-plus'
import {
HomeFilled, Setting, User, UserFilled, OfficeBuilding, Menu, Tools,
Folder, FolderOpened, Document, Money, PriceTag, Wallet,
Fold, Expand, ArrowDown, DataAnalysis, TrendCharts
} from '@element-plus/icons-vue'
import { useUserStore } from '@/stores/user'
import { useAppStore } from '@/stores/app'
const route = useRoute()
const router = useRouter()
const userStore = useUserStore()
const appStore = useAppStore()
const activeMenu = computed(() => route.path)
const currentRoute = computed(() => route)
const handleCommand = async (command: string) => {
switch (command) {
case 'profile':
router.push('/profile')
break
case 'settings':
router.push('/system/config')
break
case 'logout':
try {
await ElMessageBox.confirm('确定要退出登录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
await userStore.logoutAction()
router.push('/login')
} catch (e) {
// 取消退出
}
break
}
}
</script>
<style scoped>
.main-layout {
height: 100vh;
}
.sidebar {
background-color: #f5f7fa;
transition: width 0.3s;
border-right: 1px solid #e4e7ed;
}
.logo {
height: 60px;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
color: #303133;
font-size: 18px;
font-weight: bold;
border-bottom: 1px solid #e4e7ed;
background-color: #fff;
}
.logo img {
width: 32px;
height: 32px;
}
.sidebar-menu {
border-right: none;
background-color: #f5f7fa;
height: calc(100% - 60px);
}
.sidebar-menu:not(.el-menu--collapse) {
width: 220px;
}
/* 菜单项样式 */
.sidebar-menu :deep(.el-menu-item),
.sidebar-menu :deep(.el-sub-menu__title) {
color: #606266;
}
.sidebar-menu :deep(.el-menu-item:hover),
.sidebar-menu :deep(.el-sub-menu__title:hover) {
background-color: #ecf5ff;
}
.sidebar-menu :deep(.el-menu-item.is-active) {
color: #409eff;
background-color: #ecf5ff;
}
/* 子菜单展开时一级菜单样式 */
.sidebar-menu :deep(.el-sub-menu.is-opened > .el-sub-menu__title) {
color: #409eff;
background-color: #e6f1fc;
}
.sidebar-menu :deep(.el-sub-menu.is-opened > .el-sub-menu__title .el-sub-menu__icon-arrow) {
color: #409eff;
}
/* 子菜单容器样式 */
.sidebar-menu :deep(.el-sub-menu .el-menu) {
background-color: #fafafa;
}
.sidebar-menu :deep(.el-sub-menu .el-menu-item) {
background-color: #fafafa;
min-width: auto;
}
.sidebar-menu :deep(.el-sub-menu .el-menu-item:hover) {
background-color: #ecf5ff;
}
.sidebar-menu :deep(.el-sub-menu .el-menu-item.is-active) {
color: #409eff;
background-color: #ecf5ff;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
background-color: #fff;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
padding: 0 20px;
}
.left {
display: flex;
align-items: center;
gap: 15px;
}
.collapse-btn {
font-size: 20px;
cursor: pointer;
}
.right {
display: flex;
align-items: center;
}
.user-info {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
}
.username {
font-size: 14px;
}
.main-content {
background-color: #f0f2f5;
padding: 20px;
overflow-y: auto;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.2s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>