|
|
@@ -2,81 +2,145 @@
|
|
|
<div class="admin-layout">
|
|
|
<!-- 顶部导航栏 -->
|
|
|
<header class="top-nav">
|
|
|
- <div class="nav-inner">
|
|
|
- <div class="nav-left">
|
|
|
- <div class="nav-logo" @click="handleMenuClick('/admin/dashboard')">
|
|
|
- <div class="logo-icon">
|
|
|
- <i class="el-icon-s-platform"></i>
|
|
|
- </div>
|
|
|
- <span class="logo-text">总后台管理</span>
|
|
|
- </div>
|
|
|
+ <div class="nav-logo" @click="handleMenuClick('/admin/dashboard')" title="返回数据看板">
|
|
|
+ <div class="logo-icon">
|
|
|
+ <i class="el-icon-s-platform"></i>
|
|
|
+ </div>
|
|
|
+ <span class="logo-text">总后台管理</span>
|
|
|
+ <div class="logo-home-badge">
|
|
|
+ <i class="el-icon-s-home"></i>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="nav-divider"></div>
|
|
|
+
|
|
|
+ <nav class="nav-menu">
|
|
|
+ <div
|
|
|
+ v-for="(menu, index) in menuData"
|
|
|
+ :key="index"
|
|
|
+ class="nav-item"
|
|
|
+ :class="{ active: activeTopIndex === index && activeTopIndex !== -1 }"
|
|
|
+ @click="switchTopMenu(index)"
|
|
|
+ >
|
|
|
+ <i :class="menu.icon"></i>
|
|
|
+ <span>{{ menu.title }}</span>
|
|
|
</div>
|
|
|
- <div class="nav-right">
|
|
|
- <div class="user-info">
|
|
|
- <div class="user-avatar">
|
|
|
- {{ (nickName || 'A').charAt(0).toUpperCase() }}
|
|
|
- </div>
|
|
|
- <span class="user-role">管理员</span>
|
|
|
+ </nav>
|
|
|
+
|
|
|
+ <!-- 右侧用户区域 -->
|
|
|
+ <div class="user-area">
|
|
|
+ <div class="user-info">
|
|
|
+ <div class="user-avatar">
|
|
|
+ {{ (nickName || 'A').charAt(0).toUpperCase() }}
|
|
|
</div>
|
|
|
- <el-dropdown @command="handleCommand" trigger="click">
|
|
|
- <div class="nav-user">
|
|
|
- <span class="nav-username">{{ nickName }}</span>
|
|
|
- <i class="el-icon-arrow-down dropdown-arrow"></i>
|
|
|
- </div>
|
|
|
- <el-dropdown-menu slot="dropdown">
|
|
|
- <el-dropdown-item command="profile">
|
|
|
- <i class="el-icon-user"></i> 个人中心
|
|
|
- </el-dropdown-item>
|
|
|
- <el-dropdown-item command="logout" divided>
|
|
|
- <i class="el-icon-switch-button"></i> 退出登录
|
|
|
- </el-dropdown-item>
|
|
|
- </el-dropdown-menu>
|
|
|
- </el-dropdown>
|
|
|
+ <span class="user-role">管理员</span>
|
|
|
</div>
|
|
|
+ <el-dropdown @command="handleCommand" trigger="click">
|
|
|
+ <div class="nav-user">
|
|
|
+ <span class="nav-username">{{ nickName }}</span>
|
|
|
+ <i class="el-icon-arrow-down dropdown-arrow"></i>
|
|
|
+ </div>
|
|
|
+ <el-dropdown-menu slot="dropdown">
|
|
|
+ <el-dropdown-item command="profile">
|
|
|
+ <i class="el-icon-user"></i> 个人中心
|
|
|
+ </el-dropdown-item>
|
|
|
+ <el-dropdown-item command="logout" divided>
|
|
|
+ <i class="el-icon-switch-button"></i> 退出登录
|
|
|
+ </el-dropdown-item>
|
|
|
+ </el-dropdown-menu>
|
|
|
+ </el-dropdown>
|
|
|
</div>
|
|
|
</header>
|
|
|
|
|
|
- <!-- 下方:左侧菜单 + 内容区 -->
|
|
|
- <div class="admin-body">
|
|
|
- <aside class="admin-sidebar">
|
|
|
- <div class="sidebar-menu">
|
|
|
- <template v-for="item in menuList">
|
|
|
- <!-- 有子菜单的分组 -->
|
|
|
- <div v-if="item.children && item.children.length" :key="item.title" class="menu-group">
|
|
|
- <div class="menu-group-title" @click="toggleGroup(item.title)">
|
|
|
- <i :class="item.icon"></i>
|
|
|
+ <!-- 主体区域 -->
|
|
|
+ <div class="main-container">
|
|
|
+ <!-- 左侧侧边栏 -->
|
|
|
+ <aside class="sidebar" :class="{ 'sidebar-dashboard': isDashboard }">
|
|
|
+ <!-- Dashboard 快捷入口 -->
|
|
|
+ <div v-if="isDashboard" class="dashboard-shortcuts">
|
|
|
+ <div class="shortcuts-title">
|
|
|
+ <i class="el-icon-s-grid"></i>
|
|
|
+ <span>快捷入口</span>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ v-for="(menu, index) in menuData"
|
|
|
+ :key="index"
|
|
|
+ class="shortcut-item"
|
|
|
+ @click="switchTopMenu(index)"
|
|
|
+ >
|
|
|
+ <i :class="menu.icon"></i>
|
|
|
+ <span>{{ menu.title }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 普通二三级菜单 -->
|
|
|
+ <template v-else>
|
|
|
+ <div class="sidebar-header">
|
|
|
+ <i class="el-icon-menu"></i>
|
|
|
+ <span>菜单入口</span>
|
|
|
+ </div>
|
|
|
+ <el-menu
|
|
|
+ :default-active="activeMenu"
|
|
|
+ :unique-opened="true"
|
|
|
+ background-color="#fafbff"
|
|
|
+ text-color="#555"
|
|
|
+ active-text-color="#4f46e5"
|
|
|
+ >
|
|
|
+ <template v-for="item in currentSideMenu">
|
|
|
+ <!-- 有子菜单的分组(支持三级:el-submenu 内嵌 el-submenu 或 el-menu-item) -->
|
|
|
+ <el-submenu
|
|
|
+ v-if="item.children && item.children.length"
|
|
|
+ :index="item.title"
|
|
|
+ :key="item.title"
|
|
|
+ >
|
|
|
+ <template slot="title">
|
|
|
+ <i v-if="item.icon" :class="item.icon"></i>
|
|
|
<span>{{ item.title }}</span>
|
|
|
- <i class="el-icon-arrow-right group-arrow" :class="{ expanded: !collapsedGroups[item.title] }"></i>
|
|
|
- </div>
|
|
|
- <div v-show="!collapsedGroups[item.title]" class="menu-group-items">
|
|
|
- <div
|
|
|
- v-for="sub in item.children"
|
|
|
- :key="sub.path"
|
|
|
- :class="['menu-item', { active: $route.path === sub.path }]"
|
|
|
- @click="handleMenuClick(sub.path)"
|
|
|
+ </template>
|
|
|
+ <template v-for="child in item.children">
|
|
|
+ <!-- 三级分组(如外呼管理 > 通话接口管理等) -->
|
|
|
+ <el-submenu
|
|
|
+ v-if="child.children && child.children.length"
|
|
|
+ :index="child.title"
|
|
|
+ :key="child.title"
|
|
|
>
|
|
|
- <span>{{ sub.title }}</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <!-- 无子菜单的单项 -->
|
|
|
- <div
|
|
|
+ <template slot="title">
|
|
|
+ <i v-if="child.icon" :class="child.icon"></i>
|
|
|
+ <span>{{ child.title }}</span>
|
|
|
+ </template>
|
|
|
+ <el-menu-item
|
|
|
+ v-for="leaf in child.children"
|
|
|
+ :key="leaf.path"
|
|
|
+ :index="leaf.path"
|
|
|
+ @click="handleMenuClick(leaf.path)"
|
|
|
+ >{{ leaf.title }}</el-menu-item>
|
|
|
+ </el-submenu>
|
|
|
+ <!-- 普通二级菜单项 -->
|
|
|
+ <el-menu-item
|
|
|
+ v-else
|
|
|
+ :key="child.path"
|
|
|
+ :index="child.path"
|
|
|
+ @click="handleMenuClick(child.path)"
|
|
|
+ >{{ child.title }}</el-menu-item>
|
|
|
+ </template>
|
|
|
+ </el-submenu>
|
|
|
+ <!-- 无子菜单的直接链接项 -->
|
|
|
+ <el-menu-item
|
|
|
v-else
|
|
|
+ :index="item.path"
|
|
|
:key="item.path"
|
|
|
- :class="['menu-item', { active: $route.path === item.path }]"
|
|
|
@click="handleMenuClick(item.path)"
|
|
|
>
|
|
|
- <i :class="item.icon"></i>
|
|
|
- <span>{{ item.title }}</span>
|
|
|
- </div>
|
|
|
+ <i v-if="item.icon" :class="item.icon"></i>
|
|
|
+ <span slot="title">{{ item.title }}</span>
|
|
|
+ </el-menu-item>
|
|
|
</template>
|
|
|
- </div>
|
|
|
+ </el-menu>
|
|
|
+ </template>
|
|
|
</aside>
|
|
|
|
|
|
- <main class="admin-main">
|
|
|
- <div class="main-content">
|
|
|
- <router-view :key="viewKey" />
|
|
|
- </div>
|
|
|
+ <!-- 内容区域 -->
|
|
|
+ <main class="content-area">
|
|
|
+ <router-view :key="viewKey" />
|
|
|
</main>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -89,123 +153,164 @@ export default {
|
|
|
name: 'AdminLayout',
|
|
|
data() {
|
|
|
return {
|
|
|
- collapsedGroups: {},
|
|
|
+ activeTopIndex: -1,
|
|
|
viewKey: Date.now(),
|
|
|
- menuList: [
|
|
|
- {
|
|
|
- title: '数据看板',
|
|
|
- icon: 'el-icon-s-data',
|
|
|
- path: '/admin/dashboard'
|
|
|
- },
|
|
|
+ menuData: [
|
|
|
{
|
|
|
- title: '租户管理',
|
|
|
+ title: '租户代理',
|
|
|
icon: 'el-icon-office-building',
|
|
|
children: [
|
|
|
- { title: '租户列表', path: '/admin/company' },
|
|
|
- { title: '租户模块使用统计', path: '/admin/moduleUsage' }
|
|
|
+ { title: '代理管理', path: '/admin/proxy', icon: 'el-icon-user' },
|
|
|
+ { path:'/admin/serviceCost', title:'收费配置', icon: 'el-icon-price-tag' },
|
|
|
+ { path:'/admin/agentReport', title:'收益报表', icon: 'el-icon-data-analysis' },
|
|
|
+ { title: '租户列表', path: '/admin/company', icon: 'el-icon-notebook-2' },
|
|
|
+ { title: '租户模块使用统计', path: '/admin/moduleUsage', icon: 'el-icon-pie-chart' }
|
|
|
]
|
|
|
},
|
|
|
{
|
|
|
- title: '代理管理',
|
|
|
- icon: 'el-icon-user',
|
|
|
- path: '/admin/proxy'
|
|
|
- },
|
|
|
- {
|
|
|
- title: '员工与组织',
|
|
|
- icon: 'el-icon-s-custom',
|
|
|
+ title: '内容审计',
|
|
|
+ icon: 'el-icon-view',
|
|
|
children: [
|
|
|
- { title: '员工管理', path: '/admin/sysUser' },
|
|
|
- { title: '角色管理', path: '/admin/role' },
|
|
|
- { title: '菜单管理', path: '/admin/menu' },
|
|
|
- { title: '部门管理', path: '/admin/dept' },
|
|
|
- { title: '岗位管理', path: '/admin/post' }
|
|
|
+ { title: '视频资源', path: '/admin/videoResource', icon: 'el-icon-video-camera' },
|
|
|
+ { title: '公域课程管理', path: '/admin/course', icon: 'el-icon-reading' },
|
|
|
+ { title: '直播间', path: '/admin/live', icon: 'el-icon-video-camera-solid' },
|
|
|
+ { title: '直播视频', path: '/admin/liveVideo', icon: 'el-icon-film' },
|
|
|
+ { title: '商品管理', path: '/admin/product', icon: 'el-icon-goods' },
|
|
|
+ { title: '销售订单', path: '/admin/storeOrder', icon: 'el-icon-shopping-bag-1' },
|
|
|
+ { title: 'AI生成工作流', path: '/admin/workflowGenerate', icon: 'el-icon-cpu' },
|
|
|
+ { title: '销冠语料学习', path: '/admin/salesCorpus', icon: 'el-icon-trophy' },
|
|
|
+ { title: '文章管理', path: '/admin/article', icon: 'el-icon-document' }
|
|
|
]
|
|
|
},
|
|
|
{
|
|
|
- title: '系统管理',
|
|
|
- icon: 'el-icon-setting',
|
|
|
- children: [
|
|
|
- { title: '字典管理', path: '/admin/dict' },
|
|
|
- { title: '参数管理', path: '/admin/config' },
|
|
|
- { title: '通知公告', path: '/admin/notice' },
|
|
|
- { title: '违规词语', path: '/admin/keyword' }
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- title: '系统配置',
|
|
|
- icon: 'el-icon-s-tools',
|
|
|
+ title: '财务管理',
|
|
|
+ icon: 'el-icon-money',
|
|
|
children: [
|
|
|
- { title: 'CID配置', path: '/admin/cidConfig' },
|
|
|
- { title: '个微配置', path: '/admin/wxConfig' },
|
|
|
- { title: 'OSS配置', path: '/admin/ossConfig' },
|
|
|
- { title: '前端配置', path: '/admin/frontConfig' }
|
|
|
+ { title: '消费扣款记录', path: '/admin/consumeRecord', icon: 'el-icon-wallet' },
|
|
|
+ { title: '充值扣款记录', path: '/admin/rechargeRecord', icon: 'el-icon-bank-card' },
|
|
|
+ { title: '返佣记录', path: '/admin/commissionRecord', icon: 'el-icon-coin' },
|
|
|
+ { title: '代理提现管理', path: '/admin/withdrawal', icon: 'el-icon-money' }
|
|
|
]
|
|
|
},
|
|
|
{
|
|
|
- title: '外呼管理',
|
|
|
+ title: '通信管理',
|
|
|
icon: 'el-icon-phone-outline',
|
|
|
children: [
|
|
|
- { title: '外呼管理', path: '/admin/voice' },
|
|
|
- { title: '通话接口管理', path: '/admin/voiceApi' },
|
|
|
- { title: '号码管理', path: '/admin/voiceNumber' },
|
|
|
- { title: '通话套餐管理', path: '/admin/voicePackage' },
|
|
|
- { title: '坐席管理', path: '/admin/voiceSeat' },
|
|
|
- { title: '黑名单管理', path: '/admin/voiceBlacklist' },
|
|
|
- { title: '呼叫频率配置', path: '/admin/voiceFrequency' },
|
|
|
- { title: '通话套餐订单', path: '/admin/voiceOrder' }
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- title: '短信管理',
|
|
|
- icon: 'el-icon-message',
|
|
|
- children: [
|
|
|
- { title: '短信管理', path: '/admin/sms' },
|
|
|
- { title: '短信套餐', path: '/admin/smsPackage' },
|
|
|
- { title: '短信订单', path: '/admin/smsOrder' }
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- title: '财务管理',
|
|
|
- icon: 'el-icon-coin',
|
|
|
- children: [
|
|
|
- { title: '消费扣款记录', path: '/admin/consumeRecord' },
|
|
|
- { title: '充值扣款记录', path: '/admin/rechargeRecord' },
|
|
|
- { title: '返佣记录', path: '/admin/commissionRecord' },
|
|
|
- { title: '代理提现管理', path: '/admin/withdrawal' }
|
|
|
+ { title: '通话接口管理', icon: 'el-icon-connection', path: '/admin/voiceApi' },
|
|
|
+ { title: '通话套餐管理', icon: 'el-icon-box', path: '/admin/voicePackage' },
|
|
|
+ { title: '坐席管理', icon: 'el-icon-service', path: '/admin/voiceSeat' },
|
|
|
+ { title: '黑名单管理', icon: 'el-icon-warning', path: '/admin/voiceBlacklist' },
|
|
|
+ { title: '呼叫频率配置', icon: 'el-icon-timer', path: '/admin/voiceFrequency' },
|
|
|
+ { title: '通话套餐订单', icon: 'el-icon-tickets', path: '/admin/voiceOrder' },
|
|
|
+ { title: '短信管理', icon: 'el-icon-chat-dot-round', path: '/admin/sms' },
|
|
|
+ { title: '短信套餐', icon: 'el-icon-box', path: '/admin/smsPackage' },
|
|
|
+ { title: '短信订单', icon: 'el-icon-tickets', path: '/admin/smsOrder' }
|
|
|
]
|
|
|
},
|
|
|
{
|
|
|
- title: '日志管理',
|
|
|
- icon: 'el-icon-document',
|
|
|
+ title: '系统管理',
|
|
|
+ icon: 'el-icon-setting',
|
|
|
children: [
|
|
|
- { title: '操作日志', path: '/admin/operlog' },
|
|
|
- { title: '登录日志', path: '/admin/logininfor' },
|
|
|
- { title: '租户操作日志', path: '/admin/companyOperLog' },
|
|
|
- { title: '代理操作日志', path: '/admin/proxyOperLog' }
|
|
|
+ { title: '员工管理', icon: 'el-icon-user', path: '/admin/sysUser' },
|
|
|
+ { title: '角色管理', icon: 'el-icon-s-check', path: '/admin/role' },
|
|
|
+ { title: '菜单管理', icon: 'el-icon-menu', path: '/admin/menu' },
|
|
|
+ { title: '部门管理', icon: 'el-icon-s-cooperation', path: '/admin/dept' },
|
|
|
+ { title: '岗位管理', icon: 'el-icon-s-custom', path: '/admin/post' },
|
|
|
+ { title: '操作日志', icon: 'el-icon-document', path: '/admin/operLog' },
|
|
|
+ { title: '登录日志', icon: 'el-icon-key', path: '/admin/loginLog' },
|
|
|
+ { title: '租户操作日志', icon: 'el-icon-notebook-2', path: '/admin/companyOperLog' },
|
|
|
+ { title: '代理操作日志', icon: 'el-icon-document-copy', path: '/admin/proxyOperLog' }
|
|
|
]
|
|
|
},
|
|
|
{
|
|
|
- title: '其他管理',
|
|
|
- icon: 'el-icon-more',
|
|
|
+ title: '系统配置',
|
|
|
+ icon: 'el-icon-s-tools',
|
|
|
children: [
|
|
|
- { title: 'Ipad服务器', path: '/admin/ipadServer' },
|
|
|
- { title: '关键词管理', path: '/admin/keywordManage' },
|
|
|
- { title: '文本模型配置', path: '/admin/textModel' }
|
|
|
+ { title: '字典管理', icon: 'el-icon-collection', path: '/admin/dict' },
|
|
|
+ { title: '参数管理', icon: 'el-icon-edit-outline', path: '/admin/config' },
|
|
|
+ { title: '通知公告', icon: 'el-icon-bell', path: '/admin/notice' },
|
|
|
+ { title: '违规词语', icon: 'el-icon-warning-outline', path: '/admin/keyword' },
|
|
|
+ { title: 'CID配置', icon: 'el-icon-phone', path: '/admin/cidConfig' },
|
|
|
+ { title: '个微配置', icon: 'el-icon-chat-line-round', path: '/admin/wxConfig' },
|
|
|
+ { title: 'OSS配置', icon: 'el-icon-upload', path: '/admin/ossConfig' },
|
|
|
+ { title: '前端配置', icon: 'el-icon-monitor', path: '/admin/frontConfig' },
|
|
|
+ { title: 'Ipad服务器', icon: 'el-icon-mobile-phone', path: '/admin/ipadServer' },
|
|
|
+ { title: '关键词管理', icon: 'el-icon-search', path: '/admin/keywordManage' },
|
|
|
+ { title: '模型配置', icon: 'el-icon-cpu', path: '/admin/textModel' }
|
|
|
]
|
|
|
}
|
|
|
]
|
|
|
}
|
|
|
},
|
|
|
computed: {
|
|
|
- ...mapGetters(['nickName', 'avatar'])
|
|
|
+ ...mapGetters(['nickName', 'avatar']),
|
|
|
+ // 是否处于数据看板页面
|
|
|
+ isDashboard() {
|
|
|
+ return this.$route.path === '/admin/dashboard' || this.$route.path === '/admin'
|
|
|
+ },
|
|
|
+ // 当前顶部菜单对应的侧边栏二三级菜单(dashboard 时返回空)
|
|
|
+ currentSideMenu() {
|
|
|
+ if (this.activeTopIndex === -1) return []
|
|
|
+ return this.menuData[this.activeTopIndex]?.children || []
|
|
|
+ },
|
|
|
+ // 当前路由高亮
|
|
|
+ activeMenu() {
|
|
|
+ return this.$route.path
|
|
|
+ }
|
|
|
},
|
|
|
watch: {
|
|
|
- '$route'() {
|
|
|
- // 强制 router-view 重新渲染,解决路由切换后页面不刷新的问题
|
|
|
+ '$route.path'() {
|
|
|
this.viewKey = Date.now()
|
|
|
+ this.initActiveTop()
|
|
|
}
|
|
|
},
|
|
|
+ created() {
|
|
|
+ this.initActiveTop()
|
|
|
+ },
|
|
|
methods: {
|
|
|
+ switchTopMenu(index) {
|
|
|
+ this.activeTopIndex = index
|
|
|
+ // 自动跳转到该分组下第一个可点击的路由
|
|
|
+ const firstPath = this.getFirstPath(this.menuData[index].children)
|
|
|
+ if (firstPath && this.$route.path !== firstPath) {
|
|
|
+ this.$router.push(firstPath).catch(() => {})
|
|
|
+ }
|
|
|
+ },
|
|
|
+ getFirstPath(items) {
|
|
|
+ if (!items || !items.length) return null
|
|
|
+ for (let i = 0; i < items.length; i++) {
|
|
|
+ if (items[i].path) return items[i].path
|
|
|
+ if (items[i].children) {
|
|
|
+ const found = this.getFirstPath(items[i].children)
|
|
|
+ if (found) return found
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null
|
|
|
+ },
|
|
|
+ // 根据当前路由自动确定顶部选中项
|
|
|
+ initActiveTop() {
|
|
|
+ const currentPath = this.$route.path
|
|
|
+ // dashboard 是独立首页,不属于任何一级菜单,设为 -1
|
|
|
+ if (currentPath === '/admin/dashboard' || currentPath === '/admin') {
|
|
|
+ this.activeTopIndex = -1
|
|
|
+ return
|
|
|
+ }
|
|
|
+ for (let i = 0; i < this.menuData.length; i++) {
|
|
|
+ if (this.containsPath(this.menuData[i], currentPath)) {
|
|
|
+ this.activeTopIndex = i
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 不匹配任何分组时,回到 dashboard 状态
|
|
|
+ this.activeTopIndex = -1
|
|
|
+ },
|
|
|
+ containsPath(menu, path) {
|
|
|
+ if (menu.path === path) return true
|
|
|
+ if (menu.children) {
|
|
|
+ return menu.children.some(child => this.containsPath(child, path))
|
|
|
+ }
|
|
|
+ return false
|
|
|
+ },
|
|
|
handleMenuClick(path) {
|
|
|
if (this.$route.path === path) return
|
|
|
this.$router.push(path).catch(err => {
|
|
|
@@ -214,21 +319,6 @@ export default {
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
- toggleGroup(title) {
|
|
|
- const isCurrentlyOpen = !this.collapsedGroups[title]
|
|
|
- // 先关闭所有分组(unique-opened 效果)
|
|
|
- const newState = {}
|
|
|
- this.menuList.forEach(item => {
|
|
|
- if (item.children && item.children.length) {
|
|
|
- newState[item.title] = true // true = collapsed
|
|
|
- }
|
|
|
- })
|
|
|
- // 如果当前分组是关闭状态则打开它,否则保持关闭
|
|
|
- if (isCurrentlyOpen) {
|
|
|
- newState[title] = false // false = expanded
|
|
|
- }
|
|
|
- this.collapsedGroups = newState
|
|
|
- },
|
|
|
handleCommand(cmd) {
|
|
|
if (cmd === 'logout') {
|
|
|
this.$store.dispatch('LogOut').then(() => {
|
|
|
@@ -243,68 +333,147 @@ export default {
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
+/* ===== 整体布局 ===== */
|
|
|
.admin-layout {
|
|
|
- min-height: 100vh;
|
|
|
+ height: 100vh;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
- background: #f5f6fa;
|
|
|
+ overflow: hidden;
|
|
|
+ margin: 0 !important;
|
|
|
+ padding: 0 !important;
|
|
|
+ /* 抵消 sidebar.scss 给 #app 下 .main-container 注入的 margin-left */
|
|
|
+ position: relative;
|
|
|
}
|
|
|
|
|
|
/* ===== 顶部导航栏 ===== */
|
|
|
.top-nav {
|
|
|
- position: sticky;
|
|
|
- top: 0;
|
|
|
- z-index: 100;
|
|
|
+ height: 50px;
|
|
|
background: #fff;
|
|
|
- border-bottom: 1px solid #f0f0f0;
|
|
|
- box-shadow: 0 1px 4px rgba(0,0,0,0.04);
|
|
|
-}
|
|
|
-
|
|
|
-.nav-inner {
|
|
|
- margin: 0 auto;
|
|
|
- height: 56px;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: space-between;
|
|
|
- padding: 0 24px;
|
|
|
-}
|
|
|
-
|
|
|
-.nav-left {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- gap: 32px;
|
|
|
+ padding: 0 20px 0 0;
|
|
|
+ flex-shrink: 0;
|
|
|
+ z-index: 100;
|
|
|
+ border-bottom: 1px solid #e8e8e8;
|
|
|
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
|
|
}
|
|
|
|
|
|
+/* Logo */
|
|
|
.nav-logo {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 10px;
|
|
|
cursor: pointer;
|
|
|
+ margin-right: 32px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ text-decoration: none;
|
|
|
+ padding: 0 16px;
|
|
|
}
|
|
|
|
|
|
-.nav-logo .logo-icon {
|
|
|
+.logo-icon {
|
|
|
width: 32px;
|
|
|
height: 32px;
|
|
|
- background: linear-gradient(135deg, #4f46e5, #7c3aed);
|
|
|
- border-radius: 10px;
|
|
|
+ background: linear-gradient(135deg, #409EFF, #1890ff);
|
|
|
+ border-radius: 8px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
color: #fff;
|
|
|
- font-size: 18px;
|
|
|
+ font-size: 17px;
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
-.nav-logo .logo-text {
|
|
|
- font-size: 19px;
|
|
|
+.logo-text {
|
|
|
+ font-size: 17px;
|
|
|
font-weight: 600;
|
|
|
- color: #1a1a2e;
|
|
|
+ color: #303133;
|
|
|
white-space: nowrap;
|
|
|
}
|
|
|
|
|
|
-.nav-right {
|
|
|
+.logo-home-badge {
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ border-radius: 4px;
|
|
|
+ background: rgba(64, 158, 255, 0.1);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin-left: 4px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ transition: background 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.logo-home-badge i {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #409EFF;
|
|
|
+}
|
|
|
+
|
|
|
+.nav-logo:hover .logo-home-badge {
|
|
|
+ background: rgba(64, 158, 255, 0.2);
|
|
|
+}
|
|
|
+
|
|
|
+.nav-logo:hover .logo-home-badge i {
|
|
|
+ color: #1890ff;
|
|
|
+}
|
|
|
+
|
|
|
+/* Logo 与一级菜单之间的分隔线 */
|
|
|
+.nav-divider {
|
|
|
+ width: 1px;
|
|
|
+ height: 24px;
|
|
|
+ background: #e8e8e8;
|
|
|
+ margin-right: 8px;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* 顶部菜单 */
|
|
|
+.nav-menu {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ flex: 1;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.nav-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ padding: 0 18px;
|
|
|
+ height: 50px;
|
|
|
+ line-height: 50px;
|
|
|
+ cursor: pointer;
|
|
|
+ color: #606266;
|
|
|
+ font-size: 14px;
|
|
|
+ white-space: nowrap;
|
|
|
+ transition: all 0.3s;
|
|
|
+ position: relative;
|
|
|
+ flex-shrink: 0;
|
|
|
+ border-bottom: 2px solid transparent;
|
|
|
+}
|
|
|
+
|
|
|
+.nav-item:hover {
|
|
|
+ color: #409EFF;
|
|
|
+}
|
|
|
+
|
|
|
+.nav-item.active {
|
|
|
+ color: #409EFF;
|
|
|
+ border-bottom-color: #409EFF;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.nav-item i {
|
|
|
+ font-size: 16px;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* 右侧用户区域 */
|
|
|
+.user-area {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 12px;
|
|
|
+ margin-left: auto;
|
|
|
+ flex-shrink: 0;
|
|
|
+ padding-left: 20px;
|
|
|
+ color: #606266;
|
|
|
}
|
|
|
|
|
|
.user-info {
|
|
|
@@ -317,161 +486,221 @@ export default {
|
|
|
width: 30px;
|
|
|
height: 30px;
|
|
|
border-radius: 8px;
|
|
|
- background: linear-gradient(135deg, #4f46e5, #7c3aed);
|
|
|
+ background: linear-gradient(135deg, #409EFF, #1890ff);
|
|
|
color: #fff;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
- font-size: 14px;
|
|
|
+ font-size: 13px;
|
|
|
font-weight: 600;
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
.user-role {
|
|
|
font-size: 13px;
|
|
|
- color: #666;
|
|
|
+ color: #909399;
|
|
|
+ white-space: nowrap;
|
|
|
}
|
|
|
|
|
|
.nav-user {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- gap: 6px;
|
|
|
+ gap: 5px;
|
|
|
cursor: pointer;
|
|
|
- font-size: 15px;
|
|
|
- color: #333;
|
|
|
padding: 4px 8px;
|
|
|
- border-radius: 8px;
|
|
|
+ border-radius: 6px;
|
|
|
transition: background 0.2s;
|
|
|
}
|
|
|
|
|
|
.nav-user:hover {
|
|
|
- background: #f5f5f5;
|
|
|
+ background: rgba(64, 158, 255, 0.08);
|
|
|
}
|
|
|
|
|
|
.nav-username {
|
|
|
- font-size: 15px;
|
|
|
- color: #333;
|
|
|
- font-weight: 500;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #303133;
|
|
|
+ white-space: nowrap;
|
|
|
}
|
|
|
|
|
|
.dropdown-arrow {
|
|
|
- color: #999;
|
|
|
+ color: #909399;
|
|
|
font-size: 12px;
|
|
|
}
|
|
|
|
|
|
-/* ===== 下方布局 ===== */
|
|
|
-.admin-body {
|
|
|
+/* ===== 主体区域 ===== */
|
|
|
+/* 强制覆盖 sidebar.scss 中 #app .main-container { margin-left: 200px } 的全局规则 */
|
|
|
+.main-container {
|
|
|
flex: 1;
|
|
|
display: flex;
|
|
|
- width: 100%;
|
|
|
+ overflow: hidden;
|
|
|
+ margin: 0 !important;
|
|
|
+ padding: 0 !important;
|
|
|
}
|
|
|
|
|
|
-/* 左侧菜单 */
|
|
|
-.admin-sidebar {
|
|
|
+/* ===== 左侧侧边栏 ===== */
|
|
|
+.sidebar {
|
|
|
width: 200px;
|
|
|
flex-shrink: 0;
|
|
|
background: #fff;
|
|
|
- border-right: 1px solid #f0f0f0;
|
|
|
- padding: 16px 0;
|
|
|
+ border-right: 1px solid #e8e8e8;
|
|
|
overflow-y: auto;
|
|
|
+ overflow-x: hidden;
|
|
|
+ /* 覆盖全局 aside 样式 */
|
|
|
+ padding: 0 !important;
|
|
|
+ margin: 0 !important;
|
|
|
}
|
|
|
|
|
|
-.sidebar-menu {
|
|
|
+/* Dashboard 快捷入口侧边栏 */
|
|
|
+.sidebar-dashboard {
|
|
|
+ background: #fafbff;
|
|
|
+}
|
|
|
+
|
|
|
+.dashboard-shortcuts {
|
|
|
+ padding: 16px 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.shortcuts-title {
|
|
|
display: flex;
|
|
|
- flex-direction: column;
|
|
|
- gap: 2px;
|
|
|
- padding: 0 8px;
|
|
|
+ align-items: center;
|
|
|
+ gap: 7px;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #999;
|
|
|
+ text-transform: uppercase;
|
|
|
+ letter-spacing: 0.5px;
|
|
|
+ padding: 0 4px 10px;
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
+ margin-bottom: 8px;
|
|
|
}
|
|
|
|
|
|
-.menu-item {
|
|
|
+.shortcuts-title i {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #bbb;
|
|
|
+}
|
|
|
+
|
|
|
+.shortcut-item {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 10px;
|
|
|
- padding: 11px 16px;
|
|
|
+ padding: 10px 12px;
|
|
|
border-radius: 8px;
|
|
|
- font-size: 15px;
|
|
|
- color: #555;
|
|
|
cursor: pointer;
|
|
|
+ color: #555;
|
|
|
+ font-size: 14px;
|
|
|
transition: all 0.2s;
|
|
|
- user-select: none;
|
|
|
+ margin-bottom: 2px;
|
|
|
}
|
|
|
|
|
|
-.menu-item:hover {
|
|
|
- background: #f0f0ff;
|
|
|
- color: #4f46e5;
|
|
|
-}
|
|
|
-
|
|
|
-.menu-item.active {
|
|
|
+.shortcut-item:hover {
|
|
|
background: #ede9fe;
|
|
|
color: #4f46e5;
|
|
|
- font-weight: 600;
|
|
|
}
|
|
|
|
|
|
-.menu-item i {
|
|
|
- font-size: 18px;
|
|
|
+.shortcut-item i {
|
|
|
+ font-size: 16px;
|
|
|
flex-shrink: 0;
|
|
|
+ color: #8b5cf6;
|
|
|
}
|
|
|
|
|
|
-/* 分组菜单 */
|
|
|
-.menu-group {
|
|
|
- margin-bottom: 4px;
|
|
|
+/* 覆盖 el-menu 默认样式 */
|
|
|
+.sidebar >>> .el-menu {
|
|
|
+ border-right: none;
|
|
|
+ width: 200px;
|
|
|
+ padding: 8px 12px;
|
|
|
+ background-color: #fafbff !important;
|
|
|
}
|
|
|
|
|
|
-.menu-group-title {
|
|
|
+/* 菜单入口 header */
|
|
|
+.sidebar-header {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- gap: 10px;
|
|
|
- padding: 11px 16px;
|
|
|
+ gap: 7px;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #999;
|
|
|
+ text-transform: uppercase;
|
|
|
+ letter-spacing: 0.5px;
|
|
|
+ padding: 16px 16px 10px;
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
+ margin-bottom: 0;
|
|
|
+ background: #fafbff;
|
|
|
+}
|
|
|
+
|
|
|
+.sidebar-header i {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #bbb;
|
|
|
+}
|
|
|
+
|
|
|
+.sidebar >>> .el-menu-item {
|
|
|
+ height: 50px;
|
|
|
+ line-height: 50px;
|
|
|
+ font-size: 14px;
|
|
|
+ padding-left: 24px !important;
|
|
|
+ color: #555 !important;
|
|
|
border-radius: 8px;
|
|
|
- font-size: 15px;
|
|
|
- color: #555;
|
|
|
- cursor: pointer;
|
|
|
+ margin-bottom: 2px;
|
|
|
transition: all 0.2s;
|
|
|
- user-select: none;
|
|
|
+}
|
|
|
+
|
|
|
+.sidebar >>> .el-submenu__title {
|
|
|
+ height: 50px;
|
|
|
+ line-height: 50px;
|
|
|
+ font-size: 14px;
|
|
|
font-weight: 500;
|
|
|
+ color: #555 !important;
|
|
|
+ border-radius: 8px;
|
|
|
+ margin-bottom: 2px;
|
|
|
+ transition: all 0.2s;
|
|
|
}
|
|
|
|
|
|
-.menu-group-title:hover {
|
|
|
- background: #f0f0ff;
|
|
|
- color: #4f46e5;
|
|
|
+.sidebar >>> .el-menu-item i,
|
|
|
+.sidebar >>> .el-submenu__title i {
|
|
|
+ margin-right: 8px;
|
|
|
+ color: #8b5cf6;
|
|
|
}
|
|
|
|
|
|
-.menu-group-title i {
|
|
|
- font-size: 18px;
|
|
|
- flex-shrink: 0;
|
|
|
+.sidebar >>> .el-submenu .el-menu-item {
|
|
|
+ padding-left: 24px !important;
|
|
|
+ height: 46px;
|
|
|
+ line-height: 46px;
|
|
|
+ font-size: 13px;
|
|
|
+ min-width: 200px;
|
|
|
+ color: #555 !important;
|
|
|
+ border-radius: 8px;
|
|
|
+ margin-bottom: 2px;
|
|
|
+ transition: all 0.2s;
|
|
|
}
|
|
|
|
|
|
-.group-arrow {
|
|
|
- margin-left: auto;
|
|
|
- transition: transform 0.2s;
|
|
|
- color: #999;
|
|
|
- font-size: 12px;
|
|
|
+.sidebar >>> .el-submenu .el-menu-item i {
|
|
|
+ color: #8b5cf6;
|
|
|
}
|
|
|
|
|
|
-.group-arrow.expanded {
|
|
|
- transform: rotate(90deg);
|
|
|
+.sidebar >>> .el-menu-item.is-active {
|
|
|
+ background-color: #ede9fe !important;
|
|
|
+ color: #4f46e5 !important;
|
|
|
+ font-weight: 600;
|
|
|
+ border-right: none !important;
|
|
|
+ border-left: 3px solid #4f46e5 !important;
|
|
|
+ padding-left: 21px !important;
|
|
|
}
|
|
|
|
|
|
-.menu-group-items {
|
|
|
- padding-left: 8px;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- gap: 2px;
|
|
|
+.sidebar >>> .el-menu-item:hover,
|
|
|
+.sidebar >>> .el-submenu__title:hover {
|
|
|
+ background-color: #ede9fe !important;
|
|
|
+ color: #4f46e5 !important;
|
|
|
}
|
|
|
|
|
|
-.menu-group-items .menu-item {
|
|
|
- padding-left: 44px;
|
|
|
- font-size: 14px;
|
|
|
+.sidebar >>> .el-submenu .el-menu-item:hover {
|
|
|
+ background-color: #ede9fe !important;
|
|
|
+ color: #4f46e5 !important;
|
|
|
}
|
|
|
|
|
|
-/* 主内容区 */
|
|
|
-.admin-main {
|
|
|
+/* ===== 内容区域 ===== */
|
|
|
+.content-area {
|
|
|
flex: 1;
|
|
|
- min-width: 0;
|
|
|
overflow-y: auto;
|
|
|
background: #f5f6fa;
|
|
|
-}
|
|
|
-
|
|
|
-.main-content {
|
|
|
padding: 16px 24px 24px;
|
|
|
+ min-width: 0;
|
|
|
}
|
|
|
</style>
|