Files
cma-management/frontend/src/layouts/MainLayout.vue
T
Hermes CI Fix 1b73dbb272 feat(ui): remove in-app entity switcher, entity fixed at login
- MainLayout/ReportCenter: drop switch-entity switcher, keep read-only entity tag
- KPIList/PredictDashboard/GrowthQuality: entity selectors locked to session entity
- api/index.ts: drop switchEntity/myEntities wrappers (endpoint kept for compat)
- sidebar brand now shows logged-in entity name

(cma-remove-inapp-entity-switcher-20260914)
2026-09-14 10:15:23 +08:00

183 lines
6.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<el-container>
<el-aside :width="isCollapse ? '64px' : '220px'" :class="{ 'mobile-show': mobileMenuOpen }">
<div class="aside-mask" v-if="mobileMenuOpen" @click="mobileMenuOpen = false"></div>
<div class="logo">
<span v-show="!isCollapse">{{ logoText }}</span>
</div>
<el-menu :default-active="activeMenu" :collapse="isCollapse" router background-color="#304156" text-color="#bfcbd9" active-text-color="#409EFF" @select="onMenuSelect">
<template v-for="group in menuGroups" :key="group.label">
<el-sub-menu :index="'group-' + group.label" v-if="group.items.length > 0">
<template #title>
<el-icon><component :is="groupIcon(group.label)" /></el-icon>
<span>{{ group.label }}</span>
</template>
<el-menu-item v-for="item in group.items" :key="item.path" :index="item.path">
<el-icon><component :is="iconMap[item.icon]" /></el-icon>
<span>{{ item.label }}</span>
</el-menu-item>
</el-sub-menu>
</template>
</el-menu>
</el-aside>
<el-container>
<el-header height="60px">
<div class="header-left">
<el-button :icon="isCollapse ? 'Expand' : 'Fold'" text @click="isCollapse = !isCollapse" class="hide-mobile" />
<el-button text class="show-mobile" @click="mobileMenuOpen = !mobileMenuOpen"><el-icon><Menu /></el-icon></el-button>
<span class="page-title">{{ pageTitle }}</span>
</div>
<div class="header-right">
<!-- 企业只读标识(不可点):账套由登录窗口选定,应用内无切换入口(换企业须退出重登) -->
<el-tag size="small" type="primary" effect="plain" class="entity-tag">{{ currentEntityName }}</el-tag>
<el-tag size="small" :type="roleTagType">{{ roleLabel }}</el-tag>
<el-dropdown trigger="click">
<span class="user-info"><el-icon><User /></el-icon>{{ userName }}</span>
<template #dropdown>
<el-dropdown-item @click="logout">退出登录</el-dropdown-item>
</template>
</el-dropdown>
</div>
</el-header>
<el-main><router-view /></el-main>
</el-container>
</el-container>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import {
User, Monitor, Document, TrendCharts, WarningFilled, Connection,
Menu, Bell, Setting, Coin, DataAnalysis, Money, DataLine,
DataBoard, Tools, Collection, Edit, Reading, Opportunity, PieChart,
} from '@element-plus/icons-vue'
import { MENU_ITEMS, ROLE_LABELS } from '../permission'
const route = useRoute()
const router = useRouter()
const isCollapse = ref(false)
const mobileMenuOpen = ref(false)
// 侧边栏品牌:带当前登录企业名(如「陕西酣客 · 管理会计OS」),账套由登录窗口选定
const logoText = computed(() => {
if (currentEntityName.value) return `${currentEntityName.value} · 管理会计OS`
return '管理会计OS'
})
// ── 当前企业(只读标识):2026-09-14 需求——取消应用内企业切换,
// 账套由登录窗口选定(localStorage.cma_entity_id 登录时写入),本页不再加载授权企业列表、不再调 switch-entity。
const currentEntityName = ref(localStorage.getItem('cma_entity_name') || '酣客')
// 图标名称 → 组件实例映射
const iconMap: Record<string, any> = {
Monitor, Document, TrendCharts, WarningFilled, Connection,
User, Bell, Setting, Coin, DataAnalysis, Money, DataLine,
DataBoard, Collection, Edit, Reading, Opportunity, PieChart,
}
// 分组图标映射 — PDCA五组
const groupIcons: Record<string, any> = {
'基础数据与知识': Document,
'规划与目标': DataBoard,
'执行与控制': Edit,
'监控与评价': TrendCharts,
'复盘与改进': DataAnalysis,
'系统管理': Tools,
}
function groupIcon(label: string) {
return groupIcons[label] || Collection
}
// 从 localStorage 获取用户信息
const userStr = localStorage.getItem('cma_user') || '{}'
const user = JSON.parse(userStr)
const userRole: string = user.role || ''
// 按角色过滤菜单
const filteredMenus = computed(() => {
return MENU_ITEMS.filter(item => item.roles.includes(userRole))
})
// 按分组组织菜单
const menuGroups = computed(() => {
const groups: { label: string; items: typeof MENU_ITEMS }[] = []
const groupMap = new Map<string, typeof MENU_ITEMS>()
for (const item of filteredMenus.value) {
const label = item.group || ''
if (!label) continue
if (!groupMap.has(label)) {
groupMap.set(label, [])
}
groupMap.get(label)!.push(item)
}
// 按出现顺序输出
const seen = new Set<string>()
for (const item of filteredMenus.value) {
const label = item.group || ''
if (!label || seen.has(label)) continue
seen.add(label)
groups.push({ label, items: groupMap.get(label)! })
}
return groups
})
const activeMenu = computed(() => route.path)
const pageTitle = computed(() => (route.meta?.title as string) || '管理会计OS')
const roleLabel = computed(() => ROLE_LABELS[userRole] || userRole)
const roleTagType = computed(() => {
const types: Record<string, string> = { ceo: 'danger', finance: 'warning', business: 'primary', it: 'info' }
return types[userRole] || ''
})
const userName = computed(() => user.name || user.username || '')
function logout() {
localStorage.removeItem('cma_token')
localStorage.removeItem('cma_user')
router.push('/login')
}
function onMenuSelect() {
mobileMenuOpen.value = false
}
</script>
<style scoped>
.logo { height: 60px; display: flex; align-items: center; justify-content: center; color: #fff; font-size: 16px; font-weight: bold; background: #2b3a4a; }
.el-aside { background: #304156; }
.el-header { display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #e6e6e6; background: #fff; padding: 0 20px; }
.header-left { display: flex; align-items: center; gap: 12px; }
.header-right { display: flex; align-items: center; gap: 16px; }
.user-info { cursor: pointer; display: flex; align-items: center; gap: 4px; }
.el-main { background: #f0f2f5; min-height: calc(100vh - 60px); }
/* 折叠菜单样式覆盖:子菜单项缩进 */
.el-menu :deep(.el-sub-menu .el-menu-item) { padding-left: 54px !important; }
/* 移动端 */
@media screen and (max-width: 768px) {
.hide-mobile { display: none !important; }
.show-mobile { display: inline-flex !important; }
.el-aside {
position: fixed; top: 0; bottom: 0; left: 0; z-index: 1001;
transform: translateX(-100%);
transition: transform .3s ease;
}
.el-aside.mobile-show { transform: translateX(0); }
.aside-mask {
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.5);
z-index: -1;
}
.el-header { padding: 0 12px; }
.header-right .el-tag { display: none; }
.el-main { min-height: calc(100vh - 60px); padding: 10px; }
}
.show-mobile { display: none; }
</style>