mirror of
				https://github.com/pure-admin/vue-pure-admin.git
				synced 2025-11-03 13:44:47 +08:00 
			
		
		
		
	feat: add tags
This commit is contained in:
		
							parent
							
								
									651ac333ee
								
							
						
					
					
						commit
						1eed20ebce
					
				@ -60,11 +60,11 @@ export default defineComponent({
 | 
			
		||||
      () => getBreadcrumb()
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const pathCompile = (path: string): string | Object => {
 | 
			
		||||
      const { params } = route;
 | 
			
		||||
      var toPath = pathToRegexp.compile(path);
 | 
			
		||||
      return toPath(params);
 | 
			
		||||
    };
 | 
			
		||||
    // const pathCompile = (path: string): string | Object => {
 | 
			
		||||
      // const { params } = route;
 | 
			
		||||
      // var toPath = pathToRegexp.compile(path);
 | 
			
		||||
      // return toPath(params);
 | 
			
		||||
    // };
 | 
			
		||||
 | 
			
		||||
    const handleLink = (item: RouteLocationMatched): any => {
 | 
			
		||||
      const { redirect, path } = item;
 | 
			
		||||
@ -72,7 +72,7 @@ export default defineComponent({
 | 
			
		||||
        router.push(redirect.toString());
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      router.push(pathCompile(path));
 | 
			
		||||
      router.push(path);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return { levelList, handleLink };
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,6 @@
 | 
			
		||||
<script>
 | 
			
		||||
import { computed, defineComponent } from "vue";
 | 
			
		||||
import { useRoute } from "vue-router";
 | 
			
		||||
import { deviceDetection } from "../../utils/deviceDetection";
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
  name: "AppMain",
 | 
			
		||||
  setup() {
 | 
			
		||||
 | 
			
		||||
@ -2,3 +2,4 @@ export { default as Navbar } from './Navbar.vue'
 | 
			
		||||
export { default as Sidebar } from './sidebar/index.vue'
 | 
			
		||||
export { default as AppMain } from './AppMain.vue'
 | 
			
		||||
export { default as setting } from './setting/index.vue'
 | 
			
		||||
export { default as tag } from './tag/index.vue'
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,7 @@
 | 
			
		||||
      active-text-color="#409EFF"
 | 
			
		||||
      :collapse-transition="false"
 | 
			
		||||
      mode="vertical"
 | 
			
		||||
      @select="menuSelect"
 | 
			
		||||
    >
 | 
			
		||||
      <sidebar-item
 | 
			
		||||
        v-for="route in routes"
 | 
			
		||||
@ -26,6 +27,7 @@ import { useRoute, useRouter } from "vue-router";
 | 
			
		||||
import { useStore } from "vuex";
 | 
			
		||||
import SidebarItem from "./SidebarItem.vue";
 | 
			
		||||
import { algorithm } from "../../../utils/algorithm";
 | 
			
		||||
import { useDynamicRoutesHook } from "../tag/tagsHook";
 | 
			
		||||
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
  name: "sidebar",
 | 
			
		||||
@ -45,10 +47,22 @@ export default defineComponent({
 | 
			
		||||
      return path;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const { dynamicRouteTags } = useDynamicRoutesHook();
 | 
			
		||||
 | 
			
		||||
    const menuSelect = (indexPath: string): void => {
 | 
			
		||||
      let parentPath = "";
 | 
			
		||||
      let parentPathIndex = indexPath.lastIndexOf("/");
 | 
			
		||||
      if (parentPathIndex > 0) {
 | 
			
		||||
        parentPath = indexPath.slice(0, parentPathIndex);
 | 
			
		||||
      }
 | 
			
		||||
      dynamicRouteTags(indexPath, parentPath);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      routes: computed(() => algorithm.increaseIndexes(router)),
 | 
			
		||||
      activeMenu,
 | 
			
		||||
      isCollapse: computed(() => !store.getters.sidebar.opened),
 | 
			
		||||
      menuSelect,
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -1,51 +1,117 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="tags">
 | 
			
		||||
    <el-tag
 | 
			
		||||
      size="medium"
 | 
			
		||||
      v-for="tag in tags"
 | 
			
		||||
      :key="tag.name"
 | 
			
		||||
      closable
 | 
			
		||||
      :type="tag.type"
 | 
			
		||||
      >{{ tag.name }}</el-tag
 | 
			
		||||
    >
 | 
			
		||||
  <div class="tags-view">
 | 
			
		||||
    <el-scrollbar :vertical="false" class="scroll-container">
 | 
			
		||||
      <div
 | 
			
		||||
        v-for="(item, index) in dynamicTagList"
 | 
			
		||||
        :key="index"
 | 
			
		||||
        :class="['scroll-item', $route.path === item.path ? 'active' : '']"
 | 
			
		||||
      >
 | 
			
		||||
        <router-link :to="item.path">{{ $t(item.meta.title) }}</router-link>
 | 
			
		||||
        <span v-if="index !== 0 " class="el-icon-close" @click="deleteMenu(item)"></span>
 | 
			
		||||
      </div>
 | 
			
		||||
    </el-scrollbar>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang='ts'>
 | 
			
		||||
import { ref, defineComponent, onUnmounted, onMounted } from "vue";
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
  name: "tag",
 | 
			
		||||
<script>
 | 
			
		||||
import { useDynamicRoutesHook } from "./tagsHook";
 | 
			
		||||
import { useRoute } from "vue-router";
 | 
			
		||||
import { ref, watchEffect } from "vue";
 | 
			
		||||
export default {
 | 
			
		||||
  setup() {
 | 
			
		||||
    let flag = ref(true);
 | 
			
		||||
    const route = useRoute();
 | 
			
		||||
    const { deleteDynamicTag, dRoutes } = ref(useDynamicRoutesHook()).value;
 | 
			
		||||
 | 
			
		||||
    const tags = ref([
 | 
			
		||||
      { name: "首页", type: "info" },
 | 
			
		||||
      { name: "基础管理", type: "info" },
 | 
			
		||||
    ]);
 | 
			
		||||
    function deleteMenu(item) {
 | 
			
		||||
      deleteDynamicTag(item, route.path);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const { dynamicRouteTags } = useDynamicRoutesHook();
 | 
			
		||||
 | 
			
		||||
    // 初始化页面刷新保证当前路由tabview存在
 | 
			
		||||
    let stop = watchEffect(() => {
 | 
			
		||||
      let parentPath = route.path.slice(0, route.path.lastIndexOf("/"));
 | 
			
		||||
      dynamicRouteTags(route.path, parentPath);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      // 监听只执行一次,但获取不到当前路由,需要下一个事件轮询中取消监听
 | 
			
		||||
      stop();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      tags,
 | 
			
		||||
      flag,
 | 
			
		||||
      dynamicTagList: dRoutes,
 | 
			
		||||
      deleteMenu,
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.tags {
 | 
			
		||||
  height: 32px;
 | 
			
		||||
  float: right;
 | 
			
		||||
  border: 1px solid #f0f0f0;
 | 
			
		||||
.tags-view {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  transition: 0.18s;
 | 
			
		||||
}
 | 
			
		||||
:deep(.el-tag) {
 | 
			
		||||
  background-color: #fff;
 | 
			
		||||
  border: 1px solid #d0d7e7;
 | 
			
		||||
  margin-left: 4px;
 | 
			
		||||
  &:first-child {
 | 
			
		||||
    margin-left: 8px;
 | 
			
		||||
  justify-content: flex-start;
 | 
			
		||||
  .scroll-item {
 | 
			
		||||
    border: 1px solid #eee;
 | 
			
		||||
    border-radius: 3px;
 | 
			
		||||
    padding: 2px 8px;
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    margin-right: 2px;
 | 
			
		||||
  }
 | 
			
		||||
  a {
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    color: #666;
 | 
			
		||||
    padding: 0 10px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
.el-icon-close {
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  border-radius: 50%;
 | 
			
		||||
  padding: 1px;
 | 
			
		||||
  transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
 | 
			
		||||
}
 | 
			
		||||
.el-icon-close:hover {
 | 
			
		||||
  background: #b4bccc;
 | 
			
		||||
}
 | 
			
		||||
.scroll-container {
 | 
			
		||||
  text-align: left;
 | 
			
		||||
  padding: 5px 0;
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  :deep(.el-scrollbar__bar) {
 | 
			
		||||
    bottom: 0px;
 | 
			
		||||
  }
 | 
			
		||||
  :deep(.el-scrollbar__wrap) {
 | 
			
		||||
    height: 49px;
 | 
			
		||||
  }
 | 
			
		||||
  :deep(.el-scrollbar__wrap::-webkit-scrollbar) {
 | 
			
		||||
    display: none;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
.active {
 | 
			
		||||
  background: #409EFF;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  color: #fff;
 | 
			
		||||
  a {
 | 
			
		||||
    color: #fff;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
.active::before {
 | 
			
		||||
  content: "";
 | 
			
		||||
  background: #fff;
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  width: 8px;
 | 
			
		||||
  height: 8px;
 | 
			
		||||
  border-radius: 50%;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 50%;
 | 
			
		||||
  left: 5px;
 | 
			
		||||
  margin-top: -4px;
 | 
			
		||||
  margin-right: 2px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										74
									
								
								src/layout/components/tag/tagsHook.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/layout/components/tag/tagsHook.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,74 @@
 | 
			
		||||
import { reactive, toRefs, nextTick } from "vue"
 | 
			
		||||
import { useRouter } from "vue-router"
 | 
			
		||||
 | 
			
		||||
interface InterDynamic {
 | 
			
		||||
  dRoutes: object[],
 | 
			
		||||
  [propName: string]: any
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 默认显示首页tag
 | 
			
		||||
let dynamic: InterDynamic = reactive({
 | 
			
		||||
  dRoutes: [
 | 
			
		||||
    {
 | 
			
		||||
      path: "/welcome", meta: {
 | 
			
		||||
        title: "home",
 | 
			
		||||
        icon: 'el-icon-s-home',
 | 
			
		||||
        showLink: true,
 | 
			
		||||
        savedPosition: false,
 | 
			
		||||
      }
 | 
			
		||||
    }]
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
export function useDynamicRoutesHook() {
 | 
			
		||||
  const router = useRouter()
 | 
			
		||||
  /**
 | 
			
		||||
   * @param value string 当前menu对应的路由path
 | 
			
		||||
   * @param parentPath string 当前路由中父级路由
 | 
			
		||||
   */
 | 
			
		||||
  function dynamicRouteTags(value: string, parentPath: string): void {
 | 
			
		||||
    const hasValue = dynamic.dRoutes.some((item: any) => {
 | 
			
		||||
      return item.path === value
 | 
			
		||||
    })
 | 
			
		||||
    function concatPath(arr: object[], value: string, parentPath: string) {
 | 
			
		||||
      if (!hasValue) {
 | 
			
		||||
        arr.forEach((arrItem: any) => {
 | 
			
		||||
          let pathConcat = parentPath + '/' + arrItem.path
 | 
			
		||||
          if (arrItem.path === value || pathConcat === value) {
 | 
			
		||||
            dynamic.dRoutes.push({ path: value, meta: arrItem.meta })
 | 
			
		||||
            console.log(dynamic.dRoutes)
 | 
			
		||||
          } else {
 | 
			
		||||
            if (arrItem.children && arrItem.children.length > 0) {
 | 
			
		||||
              concatPath(arrItem.children, value, parentPath)
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    concatPath(router.options.routes, value, parentPath)
 | 
			
		||||
  }
 | 
			
		||||
  /**
 | 
			
		||||
   * @param value any 当前删除tag路由
 | 
			
		||||
   * @param current objct 当前激活路由对象
 | 
			
		||||
   */
 | 
			
		||||
  const deleteDynamicTag = async (obj: any, current: object): Promise<any> => {
 | 
			
		||||
    let valueIndex: number = dynamic.dRoutes.findIndex((item: any) => {
 | 
			
		||||
      return item.path === obj.path
 | 
			
		||||
    })
 | 
			
		||||
    // 从当前匹配到的路径中删除
 | 
			
		||||
    await dynamic.dRoutes.splice(valueIndex, 1)
 | 
			
		||||
    if (current === obj.path) { // 如果删除当前激活tag就自动切换到最后一个tag
 | 
			
		||||
      let newRoute: any = dynamic.dRoutes.slice(-1)
 | 
			
		||||
      nextTick(() => {
 | 
			
		||||
        router.push({
 | 
			
		||||
          path: newRoute[0].path
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    ...toRefs(dynamic),
 | 
			
		||||
    dynamicRouteTags,
 | 
			
		||||
    deleteDynamicTag
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -11,6 +11,8 @@
 | 
			
		||||
      <div :class="{ 'fixed-header': fixedHeader }">
 | 
			
		||||
        <!-- 顶部导航栏 -->
 | 
			
		||||
        <navbar />
 | 
			
		||||
        <!-- tabs标签页 -->
 | 
			
		||||
        <tag /> 
 | 
			
		||||
      </div>
 | 
			
		||||
      <!-- 主体内容 -->
 | 
			
		||||
      <app-main />
 | 
			
		||||
@ -21,7 +23,7 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { Navbar, Sidebar, AppMain, setting } from "./components";
 | 
			
		||||
import { Navbar, Sidebar, AppMain, setting, tag } from "./components";
 | 
			
		||||
import {
 | 
			
		||||
  ref,
 | 
			
		||||
  reactive,
 | 
			
		||||
@ -48,6 +50,7 @@ export default {
 | 
			
		||||
    Sidebar,
 | 
			
		||||
    AppMain,
 | 
			
		||||
    setting,
 | 
			
		||||
    tag
 | 
			
		||||
  },
 | 
			
		||||
  setup() {
 | 
			
		||||
    const store = useStore();
 | 
			
		||||
 | 
			
		||||
@ -220,6 +220,7 @@ const whiteList = ["/login", "/register"]
 | 
			
		||||
 | 
			
		||||
router.beforeEach((to, _from, next) => {
 | 
			
		||||
  NProgress.start()
 | 
			
		||||
  // @ts-ignore
 | 
			
		||||
  document.title = to.meta.title // 动态title
 | 
			
		||||
  whiteList.indexOf(to.path) !== -1 || storageSession.getItem("info") ? next() : next("/login") // 全部重定向到登录页
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user