index.vue 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. <script setup lang="ts">
  2. import {
  3. h,
  4. reactive,
  5. computed,
  6. onMounted,
  7. defineComponent,
  8. getCurrentInstance
  9. } from "vue";
  10. import { setType } from "./types";
  11. import { useI18n } from "vue-i18n";
  12. import { routerArrays } from "./types";
  13. import { emitter } from "/@/utils/mitt";
  14. import backTop from "/@/assets/svg/back_top.svg";
  15. import { useAppStoreHook } from "/@/store/modules/app";
  16. import fullScreen from "/@/assets/svg/full_screen.svg";
  17. import exitScreen from "/@/assets/svg/exit_screen.svg";
  18. import { deviceDetection } from "/@/utils/deviceDetection";
  19. import { useSettingStoreHook } from "/@/store/modules/settings";
  20. import { useMultiTagsStore } from "/@/store/modules/multiTags";
  21. import navbar from "./components/navbar.vue";
  22. import tag from "./components/tag/index.vue";
  23. import appMain from "./components/appMain.vue";
  24. import setting from "./components/setting/index.vue";
  25. import Vertical from "./components/sidebar/vertical.vue";
  26. import Horizontal from "./components/sidebar/horizontal.vue";
  27. const isMobile = deviceDetection();
  28. const pureSetting = useSettingStoreHook();
  29. const instance = getCurrentInstance().appContext.app.config.globalProperties;
  30. // 清空缓存后从serverConfig.json读取默认配置并赋值到storage中
  31. const layout = computed(() => {
  32. // 路由
  33. if (
  34. useMultiTagsStore().multiTagsCache &&
  35. (!instance.$storage.tags || instance.$storage.tags.length === 0)
  36. ) {
  37. // eslint-disable-next-line vue/no-side-effects-in-computed-properties
  38. instance.$storage.tags = routerArrays;
  39. }
  40. // 国际化
  41. if (!instance.$storage.locale) {
  42. // eslint-disable-next-line
  43. instance.$storage.locale = { locale: instance.$config?.Locale ?? "zh" };
  44. useI18n().locale.value = instance.$config?.Locale ?? "zh";
  45. }
  46. // 导航
  47. if (!instance.$storage.layout) {
  48. // eslint-disable-next-line vue/no-side-effects-in-computed-properties
  49. instance.$storage.layout = {
  50. layout: instance.$config?.Layout ?? "vertical",
  51. theme: instance.$config?.Theme ?? "default"
  52. };
  53. }
  54. // 灰色模式、色弱模式、隐藏标签页
  55. if (!instance.$storage.sets) {
  56. // eslint-disable-next-line
  57. instance.$storage.sets = {
  58. grey: instance.$config?.Grey ?? false,
  59. weak: instance.$config?.Weak ?? false,
  60. hideTabs: instance.$config?.HideTabs ?? false,
  61. multiTagsCache: instance.$config?.MultiTagsCache ?? false
  62. };
  63. }
  64. return instance.$storage?.layout.layout;
  65. });
  66. const set: setType = reactive({
  67. sidebar: computed(() => {
  68. return useAppStoreHook().sidebar;
  69. }),
  70. device: computed(() => {
  71. return useAppStoreHook().device;
  72. }),
  73. fixedHeader: computed(() => {
  74. return pureSetting.fixedHeader;
  75. }),
  76. classes: computed(() => {
  77. return {
  78. hideSidebar: !set.sidebar.opened,
  79. openSidebar: set.sidebar.opened,
  80. withoutAnimation: set.sidebar.withoutAnimation,
  81. mobile: set.device === "mobile"
  82. };
  83. }),
  84. hideTabs: computed(() => {
  85. return instance.$storage?.sets.hideTabs;
  86. })
  87. });
  88. function setTheme(layoutModel: string) {
  89. window.document.body.setAttribute("layout", layoutModel);
  90. instance.$storage.layout = {
  91. layout: `${layoutModel}`,
  92. theme: instance.$storage.layout?.theme
  93. };
  94. }
  95. function toggle(device: string, bool: boolean) {
  96. useAppStoreHook().toggleDevice(device);
  97. useAppStoreHook().toggleSideBar(bool, "resize");
  98. }
  99. // 判断是否可自动关闭菜单栏
  100. let isAutoCloseSidebar = true;
  101. // 监听容器
  102. emitter.on("resize", ({ detail }) => {
  103. if (isMobile) return;
  104. let { width } = detail;
  105. width <= 670 ? setTheme("vertical") : setTheme(useAppStoreHook().layout);
  106. /** width app-wrapper类容器宽度
  107. * 0 < width <= 760 隐藏侧边栏
  108. * 760 < width <= 990 折叠侧边栏
  109. * width > 990 展开侧边栏
  110. */
  111. if (width > 0 && width <= 760) {
  112. toggle("mobile", false);
  113. isAutoCloseSidebar = true;
  114. } else if (width > 760 && width <= 990) {
  115. if (isAutoCloseSidebar) {
  116. toggle("desktop", false);
  117. isAutoCloseSidebar = false;
  118. }
  119. } else if (width > 990) {
  120. if (!set.sidebar.isClickHamburger) {
  121. toggle("desktop", true);
  122. isAutoCloseSidebar = true;
  123. }
  124. }
  125. });
  126. onMounted(() => {
  127. if (isMobile) {
  128. toggle("mobile", false);
  129. }
  130. });
  131. function onFullScreen() {
  132. pureSetting.hiddenSideBar
  133. ? pureSetting.changeSetting({ key: "hiddenSideBar", value: false })
  134. : pureSetting.changeSetting({ key: "hiddenSideBar", value: true });
  135. }
  136. const layoutHeader = defineComponent({
  137. render() {
  138. return h(
  139. "div",
  140. {
  141. class: { "fixed-header": set.fixedHeader },
  142. style: [
  143. set.hideTabs && layout.value.includes("horizontal")
  144. ? "box-shadow: 0 1px 4px rgb(0 21 41 / 8%);"
  145. : ""
  146. ]
  147. },
  148. {
  149. default: () => [
  150. !pureSetting.hiddenSideBar && layout.value.includes("vertical")
  151. ? h(navbar)
  152. : h("div"),
  153. !pureSetting.hiddenSideBar && layout.value.includes("horizontal")
  154. ? h(Horizontal)
  155. : h("div"),
  156. h(
  157. tag,
  158. {},
  159. {
  160. default: () => [
  161. h(
  162. "span",
  163. { onClick: onFullScreen },
  164. {
  165. default: () => [
  166. !pureSetting.hiddenSideBar ? h(fullScreen) : h(exitScreen)
  167. ]
  168. }
  169. )
  170. ]
  171. }
  172. )
  173. ]
  174. }
  175. );
  176. }
  177. });
  178. </script>
  179. <template>
  180. <div :class="['app-wrapper', set.classes]" v-resize>
  181. <div
  182. v-show="
  183. set.device === 'mobile' &&
  184. set.sidebar.opened &&
  185. layout.includes('vertical')
  186. "
  187. class="app-mask"
  188. @click="useAppStoreHook().toggleSideBar()"
  189. />
  190. <Vertical
  191. v-show="!pureSetting.hiddenSideBar && layout.includes('vertical')"
  192. />
  193. <div
  194. :class="[
  195. 'main-container',
  196. pureSetting.hiddenSideBar ? 'main-hidden' : ''
  197. ]"
  198. >
  199. <div v-if="set.fixedHeader">
  200. <layout-header />
  201. <!-- 主体内容 -->
  202. <app-main :fixed-header="set.fixedHeader" />
  203. </div>
  204. <el-scrollbar v-else>
  205. <el-backtop
  206. title="回到顶部"
  207. target=".main-container .el-scrollbar__wrap"
  208. ><backTop />
  209. </el-backtop>
  210. <layout-header />
  211. <!-- 主体内容 -->
  212. <app-main :fixed-header="set.fixedHeader" />
  213. </el-scrollbar>
  214. </div>
  215. <!-- 系统设置 -->
  216. <setting />
  217. </div>
  218. </template>
  219. <style lang="scss" scoped>
  220. @mixin clearfix {
  221. &::after {
  222. content: "";
  223. display: table;
  224. clear: both;
  225. }
  226. }
  227. .app-wrapper {
  228. @include clearfix;
  229. position: relative;
  230. height: 100%;
  231. width: 100%;
  232. &.mobile.openSidebar {
  233. position: fixed;
  234. top: 0;
  235. }
  236. }
  237. .main-hidden {
  238. margin-left: 0 !important;
  239. }
  240. .app-mask {
  241. background: #000;
  242. opacity: 0.3;
  243. width: 100%;
  244. top: 0;
  245. height: 100%;
  246. position: absolute;
  247. z-index: 999;
  248. }
  249. .re-screen {
  250. margin-top: 12px;
  251. }
  252. </style>