main.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701
  1. <template>
  2. <el-container class="main-layout">
  3. <!-- 头部导航 -->
  4. <el-header class="layout-header">
  5. <div class="header-left">
  6. <div class="logo">
  7. <i class="el-icon-monitor logo-icon"></i>
  8. <span class="system-name">管理系统</span>
  9. </div>
  10. <!-- 移动端菜单切换按钮 -->
  11. <el-button
  12. class="mobile-menu-btn"
  13. icon="el-icon-menu"
  14. @click="toggleMobileMenu"
  15. v-show="isMobile"
  16. />
  17. </div>
  18. <div class="header-right">
  19. <el-dropdown @command="handleUserCommand">
  20. <div class="user-info">
  21. <el-avatar :size="32" :src="userInfo.avatar" />
  22. <span class="username">{{ userInfo.name }}</span>
  23. <i class="el-icon-arrow-down"></i>
  24. </div>
  25. <template #dropdown>
  26. <el-dropdown-menu>
  27. <!--<el-dropdown-item command="profile">-->
  28. <!-- <i class="el-icon-user"></i>个人信息-->
  29. <!--</el-dropdown-item>-->
  30. <!--<el-dropdown-item command="settings">-->
  31. <!-- <i class="el-icon-setting"></i>系统设置-->
  32. <!--</el-dropdown-item>-->
  33. <el-dropdown-item divided command="logout">
  34. <i class="el-icon-switch-button"></i>退出登录
  35. </el-dropdown-item>
  36. </el-dropdown-menu>
  37. </template>
  38. </el-dropdown>
  39. <el-tooltip content="全屏切换" placement="bottom">
  40. <el-button
  41. class="header-btn"
  42. :icon="isFullscreen ? 'el-icon-close' : 'el-icon-full-screen'"
  43. @click="toggleFullscreen"
  44. />
  45. </el-tooltip>
  46. <!--<el-tooltip content="消息通知" placement="bottom">-->
  47. <!-- <el-badge :value="5" class="header-btn">-->
  48. <!-- <el-button icon="el-icon-bell" @click="showNotifications" />-->
  49. <!-- </el-badge>-->
  50. <!--</el-tooltip>-->
  51. </div>
  52. </el-header>
  53. <el-container class="layout-body">
  54. <!-- 侧边栏菜单 -->
  55. <el-aside
  56. :width="isCollapse ? '64px' : '240px'"
  57. :class="['layout-aside', { 'mobile-open': mobileMenuOpen }]"
  58. >
  59. <div class="menu-toggle" @click="toggleCollapse" v-if="!isMobile">
  60. <i :class="isCollapse ? 'el-icon-s-unfold' : 'el-icon-s-fold'"></i>
  61. </div>
  62. <el-menu
  63. :default-active="activeMenu"
  64. :collapse="isCollapse && !isMobile"
  65. :collapse-transition="false"
  66. router
  67. class="sidebar-menu"
  68. background-color="#001529"
  69. text-color="#bfbfbf"
  70. active-text-color="#1890ff"
  71. @select="handleMenuSelect"
  72. >
  73. <template v-for="menu in menuList" :key="menu.path">
  74. <!-- 有子菜单的项 -->
  75. <el-sub-menu v-if="menu.children && menu.children.length" :index="menu.path">
  76. <template #title>
  77. <i :class="menu.icon"></i>
  78. <span>{{ menu.title }}</span>
  79. </template>
  80. <el-menu-item
  81. v-for="child in menu.children"
  82. :key="child.path"
  83. :index="child.path"
  84. >
  85. <i :class="child.icon"></i>
  86. <span>{{ child.title }}</span>
  87. </el-menu-item>
  88. </el-sub-menu>
  89. <!-- 没有子菜单的项 -->
  90. <el-menu-item v-else :index="menu.path">
  91. <i :class="menu.icon"></i>
  92. <span>{{ menu.title }}</span>
  93. </el-menu-item>
  94. </template>
  95. </el-menu>
  96. </el-aside>
  97. <!-- 主内容区域 -->
  98. <el-main class="layout-main">
  99. <div class="main-content">
  100. <router-view v-slot="{ Component }">
  101. <transition name="fade-transform" mode="out-in">
  102. <component :is="Component" />
  103. </transition>
  104. </router-view>
  105. </div>
  106. </el-main>
  107. </el-container>
  108. <!-- 移动端遮罩层 -->
  109. <div
  110. v-show="isMobile && mobileMenuOpen"
  111. class="mobile-mask"
  112. @click="closeMobileMenu"
  113. ></div>
  114. </el-container>
  115. </template>
  116. <script setup>
  117. import { ref, computed, onMounted, onUnmounted } from 'vue'
  118. import { useRouter, useRoute } from 'vue-router'
  119. import { ElMessage, ElMessageBox } from 'element-plus'
  120. import {getUserName, getUserAvatar, clearTokenAndUserInfo, getUserRole} from "/src/commom/utils/auth.js";
  121. const router = useRouter()
  122. const route = useRoute()
  123. // 响应式数据
  124. const isCollapse = ref(false)
  125. const isFullscreen = ref(false)
  126. const mobileMenuOpen = ref(false)
  127. const isMobile = ref(false)
  128. const name = computed(()=>getUserName())
  129. const avatar = 'https://www.loliapi.com/acg/pp/'
  130. // 用户信息
  131. const userInfo = ref({
  132. name: name?name:'管理员',
  133. avatar: avatar &&avatar !=='' ?avatar:'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png'
  134. })
  135. // 菜单数据
  136. const menuList = computed(()=>{
  137. //根据用户角色显示菜单
  138. const role = getUserRole();
  139. console.log(role)
  140. if(role === '管理员'){
  141. return [
  142. {
  143. path: '/interfaceInfo',
  144. title: '接口管理',
  145. icon: 'el-icon-document',
  146. children: [
  147. {
  148. path: '/manage/interfaceInfo',
  149. title: '接口管理',
  150. icon: 'el-icon-notebook-2'
  151. }
  152. ]
  153. },
  154. ]
  155. }else if (role === '用户'){
  156. return [
  157. {
  158. path: '/home',
  159. title: '主页',
  160. icon: 'el-icon-document',
  161. children: [
  162. {
  163. path: '/home/interfaceInfo',
  164. title: '接口信息',
  165. icon: 'el-icon-notebook-2'
  166. }
  167. ]
  168. },
  169. ]
  170. }else{
  171. return [
  172. {
  173. path: '/home',
  174. title: '主页',
  175. icon: 'el-icon-document',
  176. children: [
  177. {
  178. path: '/home/interfaceInfo',
  179. title: '接口信息',
  180. icon: 'el-icon-notebook-2'
  181. }
  182. ]
  183. },
  184. ]
  185. }
  186. })
  187. const menuList1 = ref([
  188. {
  189. path: '/dashboard',
  190. title: '仪表盘',
  191. icon: 'el-icon-data-analysis',
  192. children: [
  193. {
  194. path: '/dashboard/overview',
  195. title: '数据概览',
  196. icon: 'el-icon-trend-charts'
  197. },
  198. {
  199. path: '/dashboard/analytics',
  200. title: '数据分析',
  201. icon: 'el-icon-data-line'
  202. }
  203. ]
  204. },
  205. {
  206. path: '/user',
  207. title: '用户管理',
  208. icon: 'el-icon-user',
  209. children: [
  210. {
  211. path: '/user/list',
  212. title: '用户列表',
  213. icon: 'el-icon-user-solid'
  214. },
  215. {
  216. path: '/user/role',
  217. title: '角色管理',
  218. icon: 'el-icon-s-custom'
  219. },
  220. {
  221. path: '/user/permission',
  222. title: '权限管理',
  223. icon: 'el-icon-key'
  224. }
  225. ]
  226. },
  227. {
  228. path: '/interfaceInfo',
  229. title: '接口管理',
  230. icon: 'el-icon-document',
  231. children: [
  232. {
  233. path: '/manage/interfaceInfo',
  234. title: '接口管理',
  235. icon: 'el-icon-notebook-2'
  236. }
  237. ]
  238. },
  239. // {
  240. // path: '/system',
  241. // title: '系统设置',
  242. // icon: 'el-icon-setting',
  243. // children: [
  244. // {
  245. // path: '/system/basic',
  246. // title: '基础设置',
  247. // icon: 'el-icon-tools'
  248. // },
  249. // {
  250. // path: '/system/logs',
  251. // title: '系统日志',
  252. // icon: 'el-icon-document-checked'
  253. // }
  254. // ]
  255. // },
  256. // {
  257. // path: '/monitor',
  258. // title: '系统监控',
  259. // icon: 'el-icon-monitor',
  260. // children: [
  261. // {
  262. // path: '/monitor/server',
  263. // title: '服务器监控',
  264. // icon: 'el-icon-cpu'
  265. // },
  266. // {
  267. // path: '/monitor/performance',
  268. // title: '性能监控',
  269. // icon: 'el-icon-odometer'
  270. // }
  271. // ]
  272. // }
  273. ])
  274. // 计算属性
  275. const activeMenu = computed(() => route.path)
  276. // 方法
  277. const toggleCollapse = () => {
  278. isCollapse.value = !isCollapse.value
  279. }
  280. const toggleMobileMenu = () => {
  281. mobileMenuOpen.value = !mobileMenuOpen.value
  282. }
  283. const closeMobileMenu = () => {
  284. mobileMenuOpen.value = false
  285. }
  286. const handleMenuSelect = () => {
  287. if (isMobile.value) {
  288. closeMobileMenu()
  289. }
  290. }
  291. const toggleFullscreen = () => {
  292. if (!document.fullscreenElement) {
  293. document.documentElement.requestFullscreen()
  294. isFullscreen.value = true
  295. } else {
  296. if (document.exitFullscreen) {
  297. document.exitFullscreen()
  298. isFullscreen.value = false
  299. }
  300. }
  301. }
  302. const handleUserCommand = (command) => {
  303. switch (command) {
  304. // case 'profile':
  305. // router.push('/profile')
  306. // break
  307. // case 'settings':
  308. // router.push('/system/basic')
  309. // break
  310. case 'logout':
  311. handleLogout()
  312. break
  313. }
  314. }
  315. const handleLogout = async () => {
  316. try {
  317. await ElMessageBox.confirm('确定要退出登录吗?', '提示', {
  318. confirmButtonText: '确定',
  319. cancelButtonText: '取消',
  320. type: 'warning'
  321. })
  322. // 清除用户信息
  323. clearTokenAndUserInfo()
  324. ElMessage.success('退出成功')
  325. router.push('/login')
  326. } catch (error) {
  327. // 用户取消退出
  328. }
  329. }
  330. const showNotifications = () => {
  331. ElMessage.info('这是消息通知')
  332. }
  333. // 检测屏幕尺寸
  334. const checkMobile = () => {
  335. isMobile.value = window.innerWidth <= 768
  336. if (!isMobile.value) {
  337. mobileMenuOpen.value = false
  338. }
  339. }
  340. // 监听全屏状态变化
  341. const handleFullscreenChange = () => {
  342. isFullscreen.value = !!document.fullscreenElement
  343. }
  344. // 监听窗口大小变化
  345. const handleResize = () => {
  346. checkMobile()
  347. }
  348. // 生命周期
  349. onMounted(() => {
  350. document.addEventListener('fullscreenchange', handleFullscreenChange)
  351. window.addEventListener('resize', handleResize)
  352. checkMobile() // 初始化时检测一次
  353. })
  354. onUnmounted(() => {
  355. document.removeEventListener('fullscreenchange', handleFullscreenChange)
  356. window.removeEventListener('resize', handleResize)
  357. })
  358. </script>
  359. <style scoped>
  360. .main-layout {
  361. position: fixed;
  362. top: 0;
  363. left: 0;
  364. right: 0;
  365. bottom: 0;
  366. height: 100vh !important;
  367. width: 100vw !important;
  368. margin: 0;
  369. padding: 0;
  370. overflow: hidden;
  371. }
  372. /* 头部样式 */
  373. .layout-header {
  374. display: flex;
  375. align-items: center;
  376. justify-content: space-between;
  377. background: #fff;
  378. border-bottom: 1px solid #e6e6e6;
  379. padding: 0 20px;
  380. box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
  381. z-index: 1001;
  382. height: 60px !important;
  383. position: relative;
  384. }
  385. .header-left {
  386. display: flex;
  387. align-items: center;
  388. gap: 16px;
  389. }
  390. .logo {
  391. display: flex;
  392. align-items: center;
  393. gap: 12px;
  394. font-weight: 600;
  395. font-size: 18px;
  396. color: #1890ff;
  397. }
  398. .logo-icon {
  399. font-size: 24px;
  400. }
  401. .system-name {
  402. font-size: 18px;
  403. font-weight: 600;
  404. color: #333;
  405. }
  406. .mobile-menu-btn {
  407. display: none;
  408. border: none;
  409. background: transparent;
  410. font-size: 18px;
  411. color: #666;
  412. }
  413. .header-right {
  414. display: flex;
  415. align-items: center;
  416. gap: 16px;
  417. }
  418. .user-info {
  419. display: flex;
  420. align-items: center;
  421. gap: 8px;
  422. padding: 8px 12px;
  423. border-radius: 6px;
  424. cursor: pointer;
  425. transition: background-color 0.3s;
  426. }
  427. .user-info:hover {
  428. background-color: #f5f5f5;
  429. }
  430. .username {
  431. font-size: 14px;
  432. color: #333;
  433. }
  434. .header-btn {
  435. border: none;
  436. background: transparent;
  437. font-size: 16px;
  438. color: #666;
  439. cursor: pointer;
  440. padding: 8px;
  441. border-radius: 6px;
  442. transition: all 0.3s;
  443. }
  444. .header-btn:hover {
  445. background-color: #f5f5f5;
  446. color: #1890ff;
  447. }
  448. /* 主体样式 */
  449. .layout-body {
  450. height: calc(100vh - 60px) !important;
  451. width: 100%;
  452. position: relative;
  453. }
  454. /* 侧边栏样式 */
  455. .layout-aside {
  456. background: #001529;
  457. transition: all 0.3s;
  458. position: relative;
  459. overflow: hidden;
  460. height: 100%;
  461. }
  462. .menu-toggle {
  463. height: 48px;
  464. display: flex;
  465. align-items: center;
  466. justify-content: center;
  467. color: #fff;
  468. cursor: pointer;
  469. border-bottom: 1px solid #002140;
  470. transition: all 0.3s;
  471. }
  472. .menu-toggle:hover {
  473. background: #002140;
  474. }
  475. .menu-toggle i {
  476. font-size: 18px;
  477. }
  478. .sidebar-menu {
  479. border: none;
  480. height: calc(100% - 48px);
  481. overflow-y: auto;
  482. }
  483. .sidebar-menu:not(.el-menu--collapse) {
  484. width: 240px;
  485. }
  486. /* 主内容区域样式 */
  487. .layout-main {
  488. padding: 0;
  489. background: #f0f2f5;
  490. overflow: hidden;
  491. height: 100%;
  492. width: 100%;
  493. }
  494. .main-content {
  495. height: 100%;
  496. padding: 20px;
  497. overflow-y: auto;
  498. }
  499. /* 移动端遮罩层 */
  500. .mobile-mask {
  501. position: fixed;
  502. top: 60px;
  503. left: 0;
  504. right: 0;
  505. bottom: 0;
  506. background: rgba(0, 0, 0, 0.5);
  507. z-index: 999;
  508. }
  509. /* 过渡动画 */
  510. .fade-transform-leave-active,
  511. .fade-transform-enter-active {
  512. transition: all 0.3s;
  513. }
  514. .fade-transform-enter-from {
  515. opacity: 0;
  516. transform: translateX(-30px);
  517. }
  518. .fade-transform-leave-to {
  519. opacity: 0;
  520. transform: translateX(30px);
  521. }
  522. /* 响应式设计 */
  523. @media (max-width: 768px) {
  524. .layout-header {
  525. padding: 0 16px;
  526. }
  527. .system-name {
  528. font-size: 16px;
  529. }
  530. .username {
  531. display: none;
  532. }
  533. .mobile-menu-btn {
  534. display: block;
  535. }
  536. .layout-aside {
  537. position: fixed;
  538. top: 60px;
  539. left: 0;
  540. bottom: 0;
  541. z-index: 1000;
  542. transform: translateX(-100%);
  543. transition: transform 0.3s;
  544. width: 240px !important;
  545. }
  546. .layout-aside.mobile-open {
  547. transform: translateX(0);
  548. }
  549. .menu-toggle {
  550. display: none;
  551. }
  552. .sidebar-menu {
  553. height: 100%;
  554. }
  555. .main-content {
  556. padding: 16px;
  557. }
  558. }
  559. @media (max-width: 480px) {
  560. .layout-header {
  561. padding: 0 12px;
  562. }
  563. .header-right {
  564. gap: 8px;
  565. }
  566. .main-content {
  567. padding: 12px;
  568. }
  569. .logo {
  570. gap: 8px;
  571. }
  572. .logo-icon {
  573. font-size: 20px;
  574. }
  575. .system-name {
  576. font-size: 16px;
  577. }
  578. }
  579. /* 滚动条样式 */
  580. .main-content::-webkit-scrollbar {
  581. width: 6px;
  582. }
  583. .main-content::-webkit-scrollbar-track {
  584. background: #f1f1f1;
  585. border-radius: 3px;
  586. }
  587. .main-content::-webkit-scrollbar-thumb {
  588. background: #c1c1c1;
  589. border-radius: 3px;
  590. }
  591. .main-content::-webkit-scrollbar-thumb:hover {
  592. background: #a8a8a8;
  593. }
  594. .sidebar-menu::-webkit-scrollbar {
  595. width: 4px;
  596. }
  597. .sidebar-menu::-webkit-scrollbar-track {
  598. background: #001529;
  599. }
  600. .sidebar-menu::-webkit-scrollbar-thumb {
  601. background: #003366;
  602. border-radius: 2px;
  603. }
  604. .sidebar-menu::-webkit-scrollbar-thumb:hover {
  605. background: #004488;
  606. }
  607. /* 确保全局样式 */
  608. :global(html), :global(body) {
  609. margin: 0;
  610. padding: 0;
  611. height: 100%;
  612. width: 100%;
  613. overflow: hidden;
  614. }
  615. :global(#app) {
  616. height: 100vh;
  617. width: 100vw;
  618. overflow: hidden;
  619. }
  620. </style>