问题:TenantRoutingProperties 定义了配置但未被使用 解决方案: 1. TenantAwareLoadBalancer 注入 TenantRoutingProperties - 使用配置的 tenantHeader 名称 - 使用配置的 buildTenantGroup 方法 - 使用配置的 isSharedService 判断 - 使用配置的 isFallbackToShared 策略 2. 新增功能 - 支持 enabled=false 禁用租户路由 - 共享服务跳过租户过滤 - 可配置是否回退到共享实例 3. 更新测试适配新构造函数
341 lines
8.9 KiB
Vue
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>
|