index.vue 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. <script lang="ts" setup name="Layout">
  2. import hotkeys from 'hotkeys-js'
  3. import IframeView from './components/views/iframe.vue'
  4. import LinkView from './components/views/link.vue'
  5. import Header from './components/Header/index.vue'
  6. import MainSidebar from './components/MainSidebar/index.vue'
  7. import SubSidebar from './components/SubSidebar/index.vue'
  8. import Topbar from './components/Topbar/index.vue'
  9. import Search from './components/Search/index.vue'
  10. import AppSetting from './components/AppSetting/index.vue'
  11. import HotkeysIntro from './components/HotkeysIntro/index.vue'
  12. import useSettingsStore from '@/store/modules/settings'
  13. import useKeepAliveStore from '@/store/modules/keepAlive'
  14. import useMenuStore from '@/store/modules/menu'
  15. const routeInfo = useRoute()
  16. const settingsStore = useSettingsStore()
  17. const keepAliveStore = useKeepAliveStore()
  18. const menuStore = useMenuStore()
  19. const isIframe = computed(() => !!routeInfo.meta.iframe)
  20. const isLink = computed(() => !!routeInfo.meta.link)
  21. useWatermark()
  22. watch(() => settingsStore.settings.menu.subMenuCollapse, (val) => {
  23. if (settingsStore.mode === 'mobile') {
  24. if (!val) {
  25. document.body.classList.add('hidden')
  26. }
  27. else {
  28. document.body.classList.remove('hidden')
  29. }
  30. }
  31. })
  32. watch(() => routeInfo.path, () => {
  33. if (settingsStore.mode === 'mobile') {
  34. settingsStore.$patch((state) => {
  35. state.settings.menu.subMenuCollapse = true
  36. })
  37. }
  38. })
  39. onMounted(() => {
  40. hotkeys('f5', (e) => {
  41. if (settingsStore.settings.toolbar.enablePageReload) {
  42. e.preventDefault()
  43. useMainPage().reload()
  44. }
  45. })
  46. hotkeys('alt+`', (e) => {
  47. if (settingsStore.settings.menu.enableHotkeys) {
  48. e.preventDefault()
  49. useMenu().switchTo(menuStore.actived + 1 < menuStore.allMenus.length ? menuStore.actived + 1 : 0)
  50. }
  51. })
  52. hotkeys('alt+up,alt+down', (e, handle) => {
  53. e.preventDefault()
  54. switch (handle.key) {
  55. case 'alt+up':
  56. useMainPage().maximize(true)
  57. break
  58. case 'alt+down':
  59. useMainPage().maximize(false)
  60. break
  61. }
  62. })
  63. })
  64. onUnmounted(() => {
  65. hotkeys.unbind('f5')
  66. hotkeys.unbind('alt+`')
  67. hotkeys.unbind('alt+up,alt+down')
  68. })
  69. </script>
  70. <template>
  71. <div class="layout">
  72. <div id="app-main" :class="{ 'main-page-maximize': settingsStore.mainPageMaximizeStatus }">
  73. <Header />
  74. <div class="wrapper">
  75. <div class="sidebar-container" :class="{ show: settingsStore.mode === 'mobile' && !settingsStore.settings.menu.subMenuCollapse }">
  76. <MainSidebar />
  77. <SubSidebar />
  78. </div>
  79. <div class="sidebar-mask" :class="{ show: settingsStore.mode === 'mobile' && !settingsStore.settings.menu.subMenuCollapse }" @click="settingsStore.toggleSidebarCollapse()" />
  80. <div class="main-container" :style="{ 'padding-bottom': routeInfo.meta.paddingBottom } as any">
  81. <Topbar />
  82. <div class="main">
  83. <div v-show="settingsStore.mainPageMaximizeStatus" class="exit-main-page-maximize" @click="settingsStore.setMainPageMaximize()">
  84. <el-icon>
  85. <svg-icon name="i-ri:logout-box-line" />
  86. </el-icon>
  87. </div>
  88. <router-view v-slot="{ Component, route }">
  89. <transition name="main" mode="out-in" appear>
  90. <keep-alive :include="keepAliveStore.list">
  91. <component :is="Component" v-show="!(isIframe || isLink)" :key="route.fullPath" />
  92. </keep-alive>
  93. </transition>
  94. </router-view>
  95. <IframeView v-show="isIframe && !isLink" />
  96. <LinkView v-if="isLink" />
  97. </div>
  98. <Copyright />
  99. </div>
  100. </div>
  101. <el-backtop :right="20" :bottom="20" title="回到顶部" />
  102. </div>
  103. <Search />
  104. <AppSetting />
  105. <HotkeysIntro />
  106. </div>
  107. </template>
  108. <style lang="scss" scoped>
  109. [data-app-width-mode="adaption"] {
  110. #app-main {
  111. width: 100%;
  112. }
  113. }
  114. [data-app-width-mode="adaption-min-width"] {
  115. #app-main {
  116. width: max(var(--g-app-width), 100%);
  117. }
  118. }
  119. [data-app-width-mode="center"] {
  120. #app-main {
  121. width: var(--g-app-width);
  122. }
  123. }
  124. [data-app-width-mode="center-max-width"] {
  125. #app-main {
  126. width: min(var(--g-app-width), 100%);
  127. }
  128. }
  129. [data-mode="mobile"] {
  130. #app-main {
  131. width: 100%;
  132. }
  133. .sidebar-container {
  134. transform: translateX(calc((var(--g-main-sidebar-width) + var(--g-sub-sidebar-width)) * -1));
  135. &.show {
  136. transform: translateX(0);
  137. }
  138. }
  139. .main-container {
  140. margin-left: 0 !important;
  141. }
  142. &[data-menu-mode="single"] {
  143. .sidebar-container {
  144. transform: translateX(calc(var(--g-sub-sidebar-width) * -1));
  145. &.show {
  146. transform: translateX(0);
  147. }
  148. }
  149. }
  150. }
  151. .layout {
  152. height: 100%;
  153. }
  154. #app-main {
  155. height: 100%;
  156. margin: 0 auto;
  157. transition: var(--el-transition-all);
  158. // 当前标签页全屏
  159. &.main-page-maximize {
  160. header,
  161. .sidebar-container {
  162. display: none;
  163. }
  164. .wrapper {
  165. padding-top: 0;
  166. .main-container {
  167. margin-left: 0;
  168. .topbar-container {
  169. display: none;
  170. }
  171. .main {
  172. margin: 0;
  173. }
  174. }
  175. }
  176. :deep([data-fixed-calc-width]) {
  177. width: 100%;
  178. transform: translateX(-50%);
  179. }
  180. }
  181. }
  182. .wrapper {
  183. position: relative;
  184. width: 100%;
  185. height: 100%;
  186. transition: padding-top 0.3s;
  187. .sidebar-container {
  188. position: fixed;
  189. z-index: 1010;
  190. top: 0;
  191. bottom: 0;
  192. display: flex;
  193. width: calc(var(--g-main-sidebar-actual-width) + var(--g-sub-sidebar-actual-width));
  194. box-shadow: -1px 0 0 0 var(--g-border-color);
  195. transition: transform 0.3s, var(--el-transition-box-shadow), top 0.3s;
  196. }
  197. .sidebar-mask {
  198. position: fixed;
  199. z-index: 1000;
  200. top: 0;
  201. left: 0;
  202. width: 100%;
  203. height: 100%;
  204. background-image: radial-gradient(transparent 1px, rgb(0 0 0 / 30%) 1px);
  205. background-size: 4px 4px;
  206. backdrop-filter: saturate(50%) blur(4px);
  207. transition: all 0.2s;
  208. opacity: 0;
  209. visibility: hidden;
  210. &.show {
  211. opacity: 1;
  212. visibility: visible;
  213. }
  214. }
  215. .main-sidebar-container:not(.main-sidebar-leave-active) + .sub-sidebar-container {
  216. left: var(--g-main-sidebar-width);
  217. }
  218. .main-container {
  219. display: flex;
  220. flex-direction: column;
  221. min-height: 100%;
  222. margin-left: calc(var(--g-main-sidebar-actual-width) + var(--g-sub-sidebar-actual-width));
  223. background-color: var(--g-main-bg);
  224. box-shadow: -1px 0 0 0 var(--g-border-color), 1px 0 0 0 var(--g-border-color);
  225. transition: margin-left 0.3s, background-color 0.3s, var(--el-transition-box-shadow);
  226. .main {
  227. height: 100%;
  228. flex: auto;
  229. position: relative;
  230. overflow: hidden;
  231. transition: 0.3s;
  232. .exit-main-page-maximize {
  233. position: fixed;
  234. z-index: 1000;
  235. right: -40px;
  236. top: -40px;
  237. width: 80px;
  238. height: 80px;
  239. border-radius: 50%;
  240. cursor: pointer;
  241. background-color: var(--el-overlay-color-lighter);
  242. color: #eee;
  243. transition: background-color 0.3s, var(--el-transition-color);
  244. &:hover {
  245. background-color: var(--el-overlay-color-light);
  246. color: #fff;
  247. }
  248. .el-icon {
  249. position: absolute;
  250. bottom: 16px;
  251. left: 16px;
  252. }
  253. }
  254. }
  255. .topbar-container.has-tabbar + .main {
  256. margin: var(--g-tabbar-height) 0 0;
  257. }
  258. .topbar-container.has-toolbar + .main {
  259. margin: var(--g-toolbar-height) 0 0;
  260. }
  261. .topbar-container.has-tabbar.has-toolbar + .main {
  262. margin: calc(var(--g-tabbar-height) + var(--g-toolbar-height)) 0 0;
  263. }
  264. }
  265. }
  266. header:not(.header-leave-active) + .wrapper {
  267. padding-top: var(--g-header-height);
  268. .sidebar-container {
  269. top: var(--g-header-height);
  270. :deep(.sidebar-logo) {
  271. display: none;
  272. }
  273. :deep(.el-menu) {
  274. padding-top: 0;
  275. }
  276. }
  277. .main-container {
  278. .topbar-container {
  279. top: var(--g-header-height);
  280. :deep(.tools) {
  281. display: none;
  282. }
  283. }
  284. }
  285. }
  286. // 主内容区动画
  287. .main-enter-active {
  288. transition: 0.2s;
  289. }
  290. .main-leave-active {
  291. transition: 0.15s;
  292. }
  293. .main-enter-from {
  294. opacity: 0;
  295. margin-left: -20px;
  296. }
  297. .main-leave-to {
  298. opacity: 0;
  299. margin-left: 20px;
  300. }
  301. </style>