- 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)
183 lines
6.9 KiB
Vue
183 lines
6.9 KiB
Vue
<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>
|