瀏覽代碼

perf: 优化导航样式以及菜单折叠动画 (#408)

* chore: `4.0.0` 版本,正在开发中

* chore: update `element-plus`

* chore: update

* chore: update dependencies

* chore: update

* style: update

* chore: update `dependencies`

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update
RealityBoy 2 年之前
父節點
當前提交
3e93618015

+ 1 - 1
LICENSE

@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2022 啝裳
+Copyright (c) 2023 啝裳
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal

+ 1 - 1
mock/asyncRoutes.ts

@@ -159,7 +159,7 @@ const frameRouter = {
 const tabsRouter = {
   path: "/tabs",
   meta: {
-    icon: "IF-team-icontabs",
+    icon: "IF-pure-iconfont-tabs",
     title: "menus.hstabs",
     rank: tabs
   },

+ 6 - 6
package.json

@@ -34,9 +34,9 @@
     "@logicflow/core": "^1.1.30",
     "@logicflow/extension": "^1.1.30",
     "@pureadmin/descriptions": "^1.1.0",
-    "@pureadmin/table": "^1.9.0",
+    "@pureadmin/table": "^2.0.0",
     "@pureadmin/utils": "^1.8.5",
-    "@vueuse/core": "^9.10.0",
+    "@vueuse/core": "^9.12.0",
     "@vueuse/motion": "2.0.0-beta.12",
     "@wangeditor/editor": "^5.1.21",
     "@wangeditor/editor-for-vue": "^5.1.12",
@@ -47,7 +47,7 @@
     "dayjs": "^1.11.7",
     "echarts": "^5.4.1",
     "el-table-infinite-scroll": "^3.0.1",
-    "element-plus": "^2.2.28",
+    "element-plus": "2.2.28",
     "element-resize-detector": "^1.2.4",
     "intro.js": "^6.0.0",
     "js-cookie": "^3.0.1",
@@ -57,12 +57,12 @@
     "mockjs": "^1.1.0",
     "nprogress": "^0.2.0",
     "path": "^0.12.7",
-    "pinia": "^2.0.28",
+    "pinia": "^2.0.30",
     "qrcode": "^1.5.1",
     "qs": "^6.11.0",
     "responsive-storage": "^2.1.0",
     "sortablejs": "^1.15.0",
-    "swiper": "^8.4.5",
+    "swiper": "^9.0.3",
     "typeit": "^8.7.1",
     "v-contextmenu": "3.0.0",
     "vue": "^3.2.45",
@@ -130,7 +130,7 @@
     "terser": "^5.16.1",
     "typescript": "^4.9.4",
     "unplugin-vue-define-options": "^1.0.0",
-    "vite": "^4.0.4",
+    "vite": "^4.1.1",
     "vite-plugin-cdn-import": "^0.3.5",
     "vite-plugin-compression": "^0.5.1",
     "vite-plugin-mock": "^2.9.6",

File diff suppressed because it is too large
+ 214 - 174
pnpm-lock.yaml


+ 6 - 18
src/assets/iconfont/iconfont.css

@@ -1,8 +1,8 @@
 @font-face {
   font-family: "iconfont"; /* Project id 2208059 */
-  src: url("iconfont.woff2?t=1638023560828") format("woff2"),
-    url("iconfont.woff?t=1638023560828") format("woff"),
-    url("iconfont.ttf?t=1638023560828") format("truetype");
+  src: url("iconfont.woff2?t=1671895108120") format("woff2"),
+    url("iconfont.woff?t=1671895108120") format("woff"),
+    url("iconfont.ttf?t=1671895108120") format("truetype");
 }
 
 .iconfont {
@@ -13,26 +13,14 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
-.team-icontabs::before {
+.pure-iconfont-tabs:before {
   content: "\e63e";
 }
 
-.team-iconlogo::before {
+.pure-iconfont-logo:before {
   content: "\e620";
 }
 
-.team-iconxinpin::before {
-  content: "\e614";
-}
-
-.team-iconxinpinrenqiwang::before {
+.pure-iconfont-new:before {
   content: "\e615";
 }
-
-.team-iconexit-fullscreen::before {
-  content: "\e62a";
-}
-
-.team-iconfullscreen::before {
-  content: "\e62b";
-}

File diff suppressed because it is too large
+ 0 - 7
src/assets/iconfont/iconfont.js


+ 6 - 27
src/assets/iconfont/iconfont.json

@@ -2,50 +2,29 @@
   "id": "2208059",
   "name": "pure-admin",
   "font_family": "iconfont",
-  "css_prefix_text": "team-icon",
-  "description": "pure-admin",
+  "css_prefix_text": "pure-iconfont-",
+  "description": "pure-admin-iconfont",
   "glyphs": [
     {
       "icon_id": "20594647",
-      "name": "标签页",
+      "name": "Tabs",
       "font_class": "tabs",
       "unicode": "e63e",
       "unicode_decimal": 58942
     },
     {
       "icon_id": "22129506",
-      "name": "水能",
+      "name": "PureLogo",
       "font_class": "logo",
       "unicode": "e620",
       "unicode_decimal": 58912
     },
-    {
-      "icon_id": "7795613",
-      "name": "新品",
-      "font_class": "xinpin",
-      "unicode": "e614",
-      "unicode_decimal": 58900
-    },
     {
       "icon_id": "7795615",
-      "name": "新品人气王",
-      "font_class": "xinpinrenqiwang",
+      "name": "New",
+      "font_class": "new",
       "unicode": "e615",
       "unicode_decimal": 58901
-    },
-    {
-      "icon_id": "5698509",
-      "name": "全屏缩小",
-      "font_class": "exit-fullscreen",
-      "unicode": "e62a",
-      "unicode_decimal": 58922
-    },
-    {
-      "icon_id": "5698510",
-      "name": "全屏显示",
-      "font_class": "fullscreen",
-      "unicode": "e62b",
-      "unicode_decimal": 58923
     }
   ]
 }

二進制
src/assets/iconfont/iconfont.ttf


二進制
src/assets/iconfont/iconfont.woff


二進制
src/assets/iconfont/iconfont.woff2


+ 0 - 21
src/layout/components/screenfull/index.vue

@@ -1,21 +0,0 @@
-<script setup lang="ts">
-import { useI18n } from "vue-i18n";
-import { useFullscreen } from "@vueuse/core";
-
-const { t } = useI18n();
-const { isFullscreen, toggle } = useFullscreen();
-</script>
-
-<template>
-  <div
-    class="screen-full w-[36px] h-[48px] flex-ac cursor-pointer navbar-bg-hover"
-    @click="toggle"
-  >
-    <FontIcon
-      :title="
-        isFullscreen ? t('buttons.hsexitfullscreen') : t('buttons.hsfullscreen')
-      "
-      :icon="isFullscreen ? 'team-iconexit-fullscreen' : 'team-iconfullscreen'"
-    />
-  </div>
-</template>

+ 5 - 1
src/layout/components/sidebar/horizontal.vue

@@ -46,7 +46,11 @@ watch(
     class="horizontal-header"
   >
     <div class="horizontal-header-left" @click="backHome">
-      <FontIcon icon="team-iconlogo" svg style="width: 35px; height: 35px" />
+      <FontIcon
+        icon="pure-iconfont-logo"
+        svg
+        style="width: 35px; height: 35px"
+      />
       <h4>{{ title }}</h4>
     </div>
     <el-menu

+ 1 - 1
src/layout/components/sidebar/leftCollapse.vue

@@ -24,7 +24,7 @@ const iconClass = computed(() => {
     "align-middle",
     "text-primary",
     "cursor-pointer",
-    "duration-[360ms]",
+    "duration-[100ms]",
     "hover:text-primary",
     "dark:hover:!text-white"
   ];

+ 10 - 2
src/layout/components/sidebar/logo.vue

@@ -18,7 +18,11 @@ const { title } = useNav();
         class="sidebar-logo-link"
         to="/"
       >
-        <FontIcon icon="team-iconlogo" svg style="width: 35px; height: 35px" />
+        <FontIcon
+          icon="pure-iconfont-logo"
+          svg
+          style="width: 35px; height: 35px"
+        />
         <span class="sidebar-title">{{ title }}</span>
       </router-link>
       <router-link
@@ -28,7 +32,11 @@ const { title } = useNav();
         class="sidebar-logo-link"
         to="/"
       >
-        <FontIcon icon="team-iconlogo" svg style="width: 35px; height: 35px" />
+        <FontIcon
+          icon="pure-iconfont-logo"
+          svg
+          style="width: 35px; height: 35px"
+        />
         <span class="sidebar-title">{{ title }}</span>
       </router-link>
     </transition>

+ 132 - 103
src/layout/components/sidebar/sidebarItem.vue

@@ -28,33 +28,17 @@ const props = defineProps({
   }
 });
 
-const getExtraIconStyle = computed((): CSSProperties => {
-  if (!isCollapse.value) {
-    return {
-      position: "absolute",
-      right: "10px"
-    };
-  } else {
-    return {
-      position: "static"
-    };
-  }
-});
-
-const getNoDropdownStyle = computed((): CSSProperties => {
+const getSpanStyle = computed((): CSSProperties => {
   return {
-    display: "flex",
-    alignItems: "center"
+    width: "100%",
+    textAlign: "center"
   };
 });
 
-const getDivStyle = computed((): CSSProperties => {
+const getNoDropdownStyle = computed((): CSSProperties => {
   return {
-    width: !isCollapse.value ? "" : "100%",
     display: "flex",
-    alignItems: "center",
-    justifyContent: "space-between",
-    overflow: "hidden"
+    alignItems: "center"
   };
 });
 
@@ -66,22 +50,45 @@ const getMenuTextStyle = computed(() => {
   };
 });
 
-const getSubTextStyle = computed((): CSSProperties => {
+const getDivStyle = computed((): CSSProperties => {
   return {
-    width: !isCollapse.value ? "210px" : "",
-    display: "inline-block",
-    overflow: "hidden",
-    textOverflow: "ellipsis"
+    width: "100%",
+    display: "flex",
+    alignItems: "center",
+    justifyContent: "space-between",
+    overflow: "hidden"
   };
 });
 
-const getSpanStyle = computed(() => {
+const getsubMenuIconStyle = computed((): CSSProperties => {
   return {
-    overflow: "hidden",
-    textOverflow: "ellipsis"
+    display: "flex",
+    justifyContent: "center",
+    alignItems: "center",
+    margin:
+      layout.value === "horizontal"
+        ? "0 5px 0 0"
+        : isCollapse.value
+        ? "0 auto"
+        : "0 5px 0 0"
   };
 });
 
+const getSubTextStyle = computed((): CSSProperties => {
+  if (!isCollapse.value) {
+    return {
+      width: "210px",
+      display: "inline-block",
+      overflow: "hidden",
+      textOverflow: "ellipsis"
+    };
+  } else {
+    return {
+      width: ""
+    };
+  }
+});
+
 const expandCloseIcon = computed(() => {
   if (!getConfig()?.MenuArrowIconNoTransition) return "";
   return {
@@ -115,6 +122,20 @@ function hoverMenu(key) {
   });
 }
 
+// 左侧菜单折叠后,当菜单没有图标时只显示第一个文字并加上省略号
+function overflowSlice(text, item?: any) {
+  const newText =
+    (text?.length > 1 ? text.toString().slice(0, 1) : text) + "...";
+  if (item && !(isCollapse.value && item?.parentId === null)) {
+    return layout.value === "mix" &&
+      item?.pathList?.length === 2 &&
+      isCollapse.value
+      ? newText
+      : text;
+  }
+  return newText;
+}
+
 function hasOneShowingChild(
   children: childrenType[] = [],
   parent: childrenType
@@ -151,84 +172,84 @@ function resolvePath(routePath) {
 </script>
 
 <template>
-  <template
+  <el-menu-item
     v-if="
       hasOneShowingChild(props.item.children, props.item) &&
       (!onlyOneChild.children || onlyOneChild.noShowingChildren)
     "
+    :index="resolvePath(onlyOneChild.path)"
+    :class="{ 'submenu-title-noDropdown': !isNest }"
+    :style="getNoDropdownStyle"
   >
-    <el-menu-item
-      :index="resolvePath(onlyOneChild.path)"
-      :class="{ 'submenu-title-noDropdown': !isNest }"
-      :style="getNoDropdownStyle"
+    <div
+      v-if="toRaw(props.item.meta.icon)"
+      class="sub-menu-icon"
+      :style="getsubMenuIconStyle"
     >
-      <div class="sub-menu-icon" v-if="toRaw(props.item.meta.icon)">
-        <component
-          :is="
-            useRenderIcon(
-              toRaw(onlyOneChild.meta.icon) ||
-                (props.item.meta && toRaw(props.item.meta.icon))
-            )
-          "
-        />
-      </div>
-      <div
-        v-if="
-          isCollapse &&
-          layout === 'vertical' &&
-          props.item?.pathList?.length === 1
+      <component
+        :is="
+          useRenderIcon(
+            toRaw(onlyOneChild.meta.icon) ||
+              (props.item.meta && toRaw(props.item.meta.icon))
+          )
         "
-        :style="getDivStyle"
-      >
-        <span :style="getMenuTextStyle">
-          {{ transformI18n(onlyOneChild.meta.title) }}
-        </span>
-      </div>
-      <div
-        v-if="
-          isCollapse && layout === 'mix' && props.item?.pathList?.length === 2
-        "
-        :style="getDivStyle"
-      >
-        <span :style="getMenuTextStyle">
+      />
+    </div>
+    <span
+      v-if="
+        !props.item?.meta.icon &&
+        isCollapse &&
+        layout === 'vertical' &&
+        props.item?.pathList?.length === 1
+      "
+      :style="getSpanStyle"
+    >
+      {{ overflowSlice(transformI18n(onlyOneChild.meta.title)) }}
+    </span>
+    <span
+      v-if="
+        !onlyOneChild.meta.icon &&
+        isCollapse &&
+        layout === 'mix' &&
+        props.item?.pathList?.length === 2
+      "
+      :style="getSpanStyle"
+    >
+      {{ overflowSlice(transformI18n(onlyOneChild.meta.title)) }}
+    </span>
+    <template #title>
+      <div :style="getDivStyle">
+        <span v-if="layout === 'horizontal'">
           {{ transformI18n(onlyOneChild.meta.title) }}
         </span>
-      </div>
-      <template #title>
-        <div :style="getDivStyle">
-          <span v-if="layout === 'horizontal'">
+        <el-tooltip
+          v-else
+          placement="top"
+          :effect="tooltipEffect"
+          :offset="-10"
+          :disabled="!onlyOneChild.showTooltip"
+        >
+          <template #content>
             {{ transformI18n(onlyOneChild.meta.title) }}
-          </span>
-          <el-tooltip
-            v-else
-            placement="top"
-            :effect="tooltipEffect"
-            :offset="-10"
-            :disabled="!onlyOneChild.showTooltip"
+          </template>
+          <span
+            ref="menuTextRef"
+            :style="getMenuTextStyle"
+            @mouseover="hoverMenu(onlyOneChild)"
           >
-            <template #content>
-              {{ transformI18n(onlyOneChild.meta.title) }}
-            </template>
-            <span
-              ref="menuTextRef"
-              :style="getMenuTextStyle"
-              @mouseover="hoverMenu(onlyOneChild)"
-            >
-              {{ transformI18n(onlyOneChild.meta.title) }}
-            </span>
-          </el-tooltip>
-          <FontIcon
-            v-if="onlyOneChild.meta.extraIcon"
-            width="30px"
-            height="30px"
-            :style="getExtraIconStyle"
-            :icon="onlyOneChild.meta.extraIcon.name"
-            :svg="onlyOneChild.meta.extraIcon.svg ? true : false"
-          />
-        </div>
-      </template>
-    </el-menu-item>
-  </template>
+            {{ transformI18n(onlyOneChild.meta.title) }}
+          </span>
+        </el-tooltip>
+        <FontIcon
+          v-if="onlyOneChild.meta.extraIcon"
+          width="30px"
+          height="30px"
+          :icon="onlyOneChild.meta.extraIcon.name"
+          :svg="onlyOneChild.meta.extraIcon.svg ? true : false"
+        />
+      </div>
+    </template>
+  </el-menu-item>
 
   <el-sub-menu
     v-else
@@ -237,7 +258,11 @@ function resolvePath(routePath) {
     :index="resolvePath(props.item.path)"
   >
     <template #title>
-      <div v-if="toRaw(props.item.meta.icon)" class="sub-menu-icon">
+      <div
+        v-if="toRaw(props.item.meta.icon)"
+        :style="getsubMenuIconStyle"
+        class="sub-menu-icon"
+      >
         <component
           :is="useRenderIcon(props.item.meta && toRaw(props.item.meta.icon))"
         />
@@ -255,21 +280,25 @@ function resolvePath(routePath) {
         <template #content>
           {{ transformI18n(props.item.meta.title) }}
         </template>
-        <div
+        <span
+          v-if="
+            !(
+              isCollapse &&
+              toRaw(props.item.meta.icon) &&
+              props.item.parentId === null
+            )
+          "
           ref="menuTextRef"
           :style="getSubTextStyle"
           @mouseover="hoverMenu(props.item)"
         >
-          <span :style="getSpanStyle">
-            {{ transformI18n(props.item.meta.title) }}
-          </span>
-        </div>
+          {{ overflowSlice(transformI18n(props.item.meta.title), props.item) }}
+        </span>
       </el-tooltip>
       <FontIcon
         v-if="props.item.meta.extraIcon"
         width="30px"
         height="30px"
-        style="position: absolute; right: 10px"
         :icon="props.item.meta.extraIcon.name"
         :svg="props.item.meta.extraIcon.svg ? true : false"
       />

+ 2 - 2
src/layout/components/tag/index.scss

@@ -281,7 +281,7 @@
   left: 0;
   bottom: 0;
   background: var(--el-color-primary);
-  animation: scheduleInWidth 400ms ease-in;
+  animation: scheduleInWidth 200ms ease-in;
 }
 
 /* 灵动模式下鼠标移出隐藏蓝色进度条 */
@@ -292,5 +292,5 @@
   left: 0;
   bottom: 0;
   background: var(--el-color-primary);
-  animation: scheduleOutWidth 400ms ease-in;
+  animation: scheduleOutWidth 200ms ease-in;
 }

+ 8 - 8
src/router/enums.ts

@@ -13,13 +13,13 @@ const home = 0, // 平台规定只有 home 路由的 rank 才能为 0 ,所以
   list = 10,
   permission = 11,
   system = 12,
-  menuoverflow = 13,
-  tabs = 14,
-  formdesign = 15,
-  flowchart = 16,
-  ppt = 17,
-  editor = 18,
-  guide = 19,
+  tabs = 13,
+  formdesign = 14,
+  flowchart = 15,
+  ppt = 16,
+  editor = 17,
+  guide = 18,
+  menuoverflow = 19,
   about = 20;
 
 export {
@@ -36,12 +36,12 @@ export {
   list,
   permission,
   system,
-  menuoverflow,
   tabs,
   formdesign,
   flowchart,
   ppt,
   editor,
   guide,
+  menuoverflow,
   about
 };

+ 1 - 1
src/router/modules/components.ts

@@ -18,7 +18,7 @@ export default {
         title: $t("menus.hsmessage"),
         extraIcon: {
           svg: true,
-          name: "team-iconxinpinrenqiwang"
+          name: "pure-iconfont-new"
         },
         transition: {
           enterTransition: "animate__fadeInLeft",

+ 1 - 1
src/router/modules/nested.ts

@@ -55,7 +55,7 @@ export default {
                 keepAlive: true,
                 extraIcon: {
                   svg: true,
-                  name: "team-iconxinpinrenqiwang"
+                  name: "pure-iconfont-new"
                 }
               }
             }

+ 2 - 1
src/style/index.scss

@@ -6,7 +6,8 @@
 
 /* 自定义全局 CssVar */
 :root {
-  --pure-transition-duration: 0.016s;
+  /* 左侧菜单展开、收起动画时长 */
+  --pure-transition-duration: 0.3s;
 }
 
 /* 灰色模式 */

+ 31 - 32
src/style/sidebar.scss

@@ -14,12 +14,13 @@
   }
 
   .sub-menu-icon {
-    vertical-align: middle;
     font-size: 18px;
-    display: inline-flex;
-    justify-content: center;
-    align-items: center;
     margin-right: 5px;
+
+    svg {
+      width: 18px;
+      height: 18px;
+    }
   }
 
   .set-icon {
@@ -195,7 +196,7 @@
       align-items: center;
       padding-left: 10px;
       cursor: pointer;
-      transition: all 0.125s ease;
+      transition: all var(--pure-transition-duration) ease;
 
       i {
         font-size: 30px;
@@ -284,6 +285,7 @@
 
     .el-menu-item,
     .el-sub-menu__title {
+      padding-right: var(--el-menu-base-level-padding);
       color: $menuText;
 
       &:hover {
@@ -374,19 +376,13 @@
 
     .el-menu-item,
     .el-sub-menu {
-      // i {
-      //   width: 20px;
-      //   text-align: center;
-      //   font-size: 16px;
-      // }
-
-      // i.fa {
-      //   margin-right: 5px;
-      //   font-size: 16px;
-      // }
+      .iconfont {
+        font-size: 18px;
+      }
+
       .el-menu-tooltip__trigger {
         width: 54px;
-        padding: 18px !important;
+        padding: 0;
       }
     }
   }
@@ -471,13 +467,13 @@
   .el-menu--collapse .is-active.submenu-title-noDropdown.outer-most::before {
     position: absolute;
     top: 0;
-    left: 2px;
+    left: 0;
     width: 2px;
     height: 100%;
     background-color: $menuActiveBefore;
     content: "";
     clear: both;
-    transition: all 0.125s ease-in-out;
+    transition: all var(--pure-transition-duration) ease-in-out;
     transform: translateY(0);
   }
 
@@ -537,7 +533,7 @@ body[layout="vertical"] {
     }
 
     .sidebar-container {
-      transition: width 0.125s;
+      transition: width var(--pure-transition-duration);
       width: 54px !important;
 
       .is-active.submenu-title-noDropdown.outer-most {
@@ -554,11 +550,10 @@ body[layout="vertical"] {
       .el-sub-menu {
         & > .el-sub-menu__title {
           & > span {
-            height: 0;
-            width: 0;
-            overflow: hidden;
-            visibility: hidden;
-            display: inline-block;
+            height: 100%;
+            width: 100%;
+            text-align: center;
+            visibility: visible;
           }
         }
       }
@@ -568,7 +563,7 @@ body[layout="vertical"] {
       }
 
       .el-sub-menu__title {
-        padding: 0 18px !important;
+        padding: 0;
       }
     }
 
@@ -597,9 +592,13 @@ body[layout="horizontal"] {
   $sideBarWidth: 0;
   @include merge-style($sideBarWidth);
 
+  .fixed-header,
+  .main-container {
+    transition: none !important;
+  }
+
   .fixed-header {
     width: 100%;
-    transition: none !important;
   }
 }
 
@@ -622,7 +621,7 @@ body[layout="mix"] {
     }
 
     .sidebar-container {
-      transition: width 0.125s;
+      transition: width var(--pure-transition-duration);
       width: 54px !important;
 
       .is-active.submenu-title-noDropdown.outer-most {
@@ -638,12 +637,12 @@ body[layout="mix"] {
     .el-menu--collapse {
       .el-sub-menu {
         & > .el-sub-menu__title {
+          padding: 0;
           & > span {
-            height: 0;
-            width: 0;
-            overflow: hidden;
-            visibility: hidden;
-            display: inline-block;
+            height: 100%;
+            width: 100%;
+            text-align: center;
+            visibility: visible;
           }
         }
       }

Some files were not shown because too many files changed in this diff