浏览代码

Merge pull request #135 from xiaoxian521/feat/expandTag

feat: expand tag
啝裳 3 年之前
父节点
当前提交
6c75296a02

+ 7 - 3
src/assets/iconfont/iconfont.css

@@ -1,8 +1,8 @@
 @font-face {
   font-family: "iconfont"; /* Project id 2208059 */
-  src: url("iconfont.woff2?t=1636197082361") format("woff2"),
-    url("iconfont.woff?t=1636197082361") format("woff"),
-    url("iconfont.ttf?t=1636197082361") format("truetype");
+  src: url("iconfont.woff2?t=1638023560828") format("woff2"),
+    url("iconfont.woff?t=1638023560828") format("woff"),
+    url("iconfont.ttf?t=1638023560828") format("truetype");
 }
 
 .iconfont {
@@ -13,6 +13,10 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.team-icontabs::before {
+  content: "\e63e";
+}
+
 .team-iconlogo::before {
   content: "\e620";
 }

文件差异内容过多而无法显示
+ 1 - 2
src/assets/iconfont/iconfont.js


+ 7 - 0
src/assets/iconfont/iconfont.json

@@ -5,6 +5,13 @@
   "css_prefix_text": "team-icon",
   "description": "pure-admin",
   "glyphs": [
+    {
+      "icon_id": "20594647",
+      "name": "标签页",
+      "font_class": "tabs",
+      "unicode": "e63e",
+      "unicode_decimal": 58942
+    },
     {
       "icon_id": "22129506",
       "name": "水能",

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


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


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


+ 44 - 18
src/components/ReIcon/index.ts

@@ -3,39 +3,53 @@ import icon from "./src/Icon.vue";
 import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
 import { iconComponents } from "/@/plugins/element-plus";
 
-export const Icon = Object.assign(icon, {
-  install(app: App) {
-    app.component(icon.name, icon);
-  }
-});
-
-export default {
-  Icon
-};
 /**
  * find icon component
  * @param icon icon图标
  * @returns component
  */
 export function findIconReg(icon: string) {
-  const faReg = /^fa-/;
+  // fontawesome
+  const faReg = /^FA-/;
+  // iconfont
+  const iFReg = /^IF-/;
+  // typeof icon === "function" 属于SVG
   if (faReg.test(icon)) {
-    return findIcon(icon.split(faReg)[1]);
+    const text = icon.split(faReg)[1];
+    return findIcon(
+      text.slice(0, text.indexOf(" ")),
+      "FA",
+      text.slice(text.indexOf(" ") + 1, text.length)
+    );
+  } else if (iFReg.test(icon)) {
+    return findIcon(icon.split(iFReg)[1], "IF");
+  } else if (typeof icon === "function") {
+    return findIcon(icon, "SVG");
   } else {
-    return findIcon(icon, false);
+    return findIcon(icon, "EL");
   }
 }
-export function findIcon(icon: String, isFa: Boolean = true) {
-  if (isFa) {
+
+// 支持fontawesome、iconfont、element-plus/icons、自定义svg
+export function findIcon(icon: String, type = "EL", property?: string) {
+  if (type === "FA") {
     return defineComponent({
       name: "FaIcon",
-      data() {
-        return { icon: icon };
+      setup() {
+        return { icon, property };
       },
       components: { FontAwesomeIcon },
-      template: `<font-awesome-icon :icon="icon" />`
+      template: `<font-awesome-icon :icon="icon" v-bind:[property]="true" />`
     });
-  } else {
+  } else if (type === "IF") {
+    return defineComponent({
+      name: "IfIcon",
+      data() {
+        return { icon: `iconfont ${icon}` };
+      },
+      template: `<i :class="icon" />`
+    });
+  } else if (type === "EL") {
     const components = iconComponents.filter(
       component => component.name === icon
     );
@@ -44,5 +58,17 @@ export function findIcon(icon: String, isFa: Boolean = true) {
     } else {
       return null;
     }
+  } else if (type === "SVG") {
+    return icon;
   }
 }
+
+export const Icon = Object.assign(icon, {
+  install(app: App) {
+    app.component(icon.name, icon);
+  }
+});
+
+export default {
+  Icon
+};

+ 5 - 1
src/plugins/element-plus/index.ts

@@ -41,6 +41,8 @@ import {
   ElTabPane,
   ElAvatar,
   ElEmpty,
+  ElCollapse,
+  ElCollapseItem,
   // 指令
   ElLoading,
   ElInfiniteScroll
@@ -106,7 +108,9 @@ const components = [
   ElTabs,
   ElTabPane,
   ElAvatar,
-  ElEmpty
+  ElEmpty,
+  ElCollapse,
+  ElCollapseItem
 ];
 // icon
 export const iconComponents = [

+ 6 - 2
src/plugins/fontawesome/index.ts

@@ -6,12 +6,16 @@
 import { App } from "vue";
 import "font-awesome/css/font-awesome.css";
 import { library } from "@fortawesome/fontawesome-svg-core";
-import { faUserSecret } from "@fortawesome/free-solid-svg-icons";
+import {
+  faUserSecret,
+  faCoffee,
+  faSpinner
+} from "@fortawesome/free-solid-svg-icons";
 import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
 // github.com/Remix-Design/RemixIcon/blob/master/README_CN.md#%E5%AE%89%E8%A3%85%E5%BC%95%E5%85%A5
 import "remixicon/fonts/remixicon.css";
 
 export function useFontawesome(app: App) {
-  library.add(faUserSecret);
+  library.add(faUserSecret, faCoffee, faSpinner);
   app.component("font-awesome-icon", FontAwesomeIcon);
 }

+ 2 - 0
src/plugins/i18n/config.ts

@@ -42,6 +42,7 @@ export const menusConfig = {
       permission: "权限管理",
       permissionPage: "页面权限",
       permissionButton: "按钮权限",
+      hstabs: "标签页操作",
       externalLink: "外链"
     }
   },
@@ -78,6 +79,7 @@ export const menusConfig = {
       permission: "Permission Manage",
       permissionPage: "Page Permission",
       permissionButton: "Button Permission",
+      hstabs: "Tabs Operate",
       externalLink: "External Link"
     }
   }

+ 2 - 0
src/router/index.ts

@@ -15,6 +15,7 @@ import { usePermissionStoreHook } from "/@/store/modules/permission";
 import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
 
 // 静态路由
+import tabsRouter from "./modules/tabs";
 import homeRouter from "./modules/home";
 import Layout from "/@/layout/index.vue";
 import errorRouter from "./modules/error";
@@ -32,6 +33,7 @@ import { transformI18n } from "../utils/i18n";
 const modulesRoutes = import.meta.glob("/src/views/*/*/*.vue");
 
 const constantRoutes: Array<RouteComponent> = [
+  tabsRouter,
   homeRouter,
   flowChartRouter,
   editorRouter,

+ 41 - 0
src/router/modules/tabs.ts

@@ -0,0 +1,41 @@
+import Layout from "/@/layout/index.vue";
+
+const tabsRouter = {
+  path: "/tabs",
+  name: "reTabs",
+  component: Layout,
+  redirect: "/tags/index",
+  meta: {
+    icon: "IF-team-icontabs",
+    title: "message.hstabs",
+    i18n: true,
+    showLink: true,
+    rank: 8
+  },
+  children: [
+    {
+      path: "/tabs/index",
+      name: "reTabs",
+      component: () => import("/@/views/tabs/index.vue"),
+      meta: {
+        title: "message.hstabs",
+        showLink: true,
+        i18n: true
+      }
+    },
+    {
+      path: "/tabs/detail/:id",
+      name: "tabDetail",
+      component: () => import("/@/views/tabs/tabDetail.vue"),
+      meta: {
+        title: "",
+        showLink: false,
+        i18n: false,
+        dynamicLevel: 3,
+        realPath: "/tabs/detail"
+      }
+    }
+  ]
+};
+
+export default tabsRouter;

+ 40 - 3
src/store/modules/multiTags.ts

@@ -4,6 +4,13 @@ import { getConfig } from "/@/config";
 import { positionType } from "./types";
 import { storageLocal } from "/@/utils/storage";
 
+interface Itag {
+  path: string;
+  parentPath: string;
+  name: string;
+  meta: any;
+}
+
 export const useMultiTagsStore = defineStore({
   id: "pure-multiTags",
   state: () => ({
@@ -34,14 +41,44 @@ export const useMultiTagsStore = defineStore({
       this.getMultiTagsCache &&
         storageLocal.setItem("responsive-tags", multiTags);
     },
-    handleTags<T>(mode: string, value?: T, position?: positionType): any {
+    handleTags<T>(
+      mode: string,
+      value?: T | Itag,
+      position?: positionType
+    ): any {
       switch (mode) {
         case "equal":
           this.multiTags = value;
           break;
         case "push":
-          this.multiTags.push(value);
-          this.tagsCache(this.multiTags);
+          {
+            const tagVal = value as Itag;
+            // 判断tag是否已存在:
+            const tagHasExits = this.multiTags.some(tag => {
+              return tag.path === tagVal?.path;
+            });
+
+            if (tagHasExits) return;
+            const meta = tagVal?.meta;
+            const dynamicLevel = meta?.dynamicLevel ?? -1;
+            if (dynamicLevel > 0) {
+              // dynamicLevel动态路由可打开的数量
+              const realPath = meta?.realPath ?? "";
+              // 获取到已经打开的动态路由数, 判断是否大于dynamicLevel
+              if (
+                this.multiTags.filter(e => e.meta?.realPath ?? "" === realPath)
+                  .length >= dynamicLevel
+              ) {
+                // 关闭第一个
+                const index = this.multiTags.findIndex(
+                  item => item.meta?.realPath === realPath
+                );
+                index !== -1 && this.multiTags.splice(index, 1);
+              }
+            }
+            this.multiTags.push(value);
+            this.tagsCache(this.multiTags);
+          }
           break;
         case "splice":
           this.multiTags.splice(position?.startIndex, position?.length);

+ 0 - 6
src/style/sidebar.scss

@@ -444,12 +444,6 @@
       }
     }
 
-    & > .el-menu {
-      i {
-        margin-right: 16px;
-      }
-    }
-
     .is-active > .el-sub-menu__title,
     .is-active.submenu-title-noDropdown {
       color: $subMenuActiveText !important;

+ 50 - 0
src/views/tabs/index.vue

@@ -0,0 +1,50 @@
+<script setup lang="ts">
+import { ref } from "vue";
+import { useRouter, useRoute } from "vue-router";
+import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
+
+const router = useRouter();
+const route = useRoute();
+const activeName = ref("tag");
+
+function toDetail(index: number) {
+  useMultiTagsStoreHook().handleTags("push", {
+    path: `/tabs/detail/${index}`,
+    parentPath: route.matched[0].path,
+    name: "tabDetail",
+    meta: {
+      title: `No.${index} - 详情信息`,
+      showLink: false,
+      i18n: false,
+      dynamicLevel: 3,
+      realPath: "/tabs/detail"
+    }
+  });
+  router.push(`/tabs/detail/${index}`);
+}
+</script>
+
+<template>
+  <el-collapse v-model="activeName" class="tabs-container">
+    <el-collapse-item
+      title="标签页复用超出限制自动关闭(使用场景: 动态路由)"
+      name="tag"
+    >
+      <el-button
+        v-for="index in 6"
+        :key="index"
+        size="medium"
+        @click="toDetail(index)"
+      >
+        打开{{ index }}详情页
+      </el-button>
+    </el-collapse-item>
+  </el-collapse>
+</template>
+
+<style lang="scss" scoped>
+.tabs-container {
+  padding: 10px;
+  background: #fff;
+}
+</style>

+ 11 - 0
src/views/tabs/tabDetail.vue

@@ -0,0 +1,11 @@
+<script setup lang="ts">
+import { useRoute } from "vue-router";
+const route = useRoute();
+const index = route.params?.id ?? -1;
+</script>
+
+<template>
+  <div>{{ index }} - 详情页内容在此</div>
+</template>
+
+<style lang="scss" scoped></style>

部分文件因为文件数量过多而无法显示