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()
 | 
					      () => getBreadcrumb()
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const pathCompile = (path: string): string | Object => {
 | 
					    // const pathCompile = (path: string): string | Object => {
 | 
				
			||||||
      const { params } = route;
 | 
					      // const { params } = route;
 | 
				
			||||||
      var toPath = pathToRegexp.compile(path);
 | 
					      // var toPath = pathToRegexp.compile(path);
 | 
				
			||||||
      return toPath(params);
 | 
					      // return toPath(params);
 | 
				
			||||||
    };
 | 
					    // };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const handleLink = (item: RouteLocationMatched): any => {
 | 
					    const handleLink = (item: RouteLocationMatched): any => {
 | 
				
			||||||
      const { redirect, path } = item;
 | 
					      const { redirect, path } = item;
 | 
				
			||||||
@ -72,7 +72,7 @@ export default defineComponent({
 | 
				
			|||||||
        router.push(redirect.toString());
 | 
					        router.push(redirect.toString());
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      router.push(pathCompile(path));
 | 
					      router.push(path);
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return { levelList, handleLink };
 | 
					    return { levelList, handleLink };
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,6 @@
 | 
				
			|||||||
<script>
 | 
					<script>
 | 
				
			||||||
import { computed, defineComponent } from "vue";
 | 
					import { computed, defineComponent } from "vue";
 | 
				
			||||||
import { useRoute } from "vue-router";
 | 
					import { useRoute } from "vue-router";
 | 
				
			||||||
import { deviceDetection } from "../../utils/deviceDetection";
 | 
					 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
  name: "AppMain",
 | 
					  name: "AppMain",
 | 
				
			||||||
  setup() {
 | 
					  setup() {
 | 
				
			||||||
 | 
				
			|||||||
@ -2,3 +2,4 @@ export { default as Navbar } from './Navbar.vue'
 | 
				
			|||||||
export { default as Sidebar } from './sidebar/index.vue'
 | 
					export { default as Sidebar } from './sidebar/index.vue'
 | 
				
			||||||
export { default as AppMain } from './AppMain.vue'
 | 
					export { default as AppMain } from './AppMain.vue'
 | 
				
			||||||
export { default as setting } from './setting/index.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"
 | 
					      active-text-color="#409EFF"
 | 
				
			||||||
      :collapse-transition="false"
 | 
					      :collapse-transition="false"
 | 
				
			||||||
      mode="vertical"
 | 
					      mode="vertical"
 | 
				
			||||||
 | 
					      @select="menuSelect"
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <sidebar-item
 | 
					      <sidebar-item
 | 
				
			||||||
        v-for="route in routes"
 | 
					        v-for="route in routes"
 | 
				
			||||||
@ -26,6 +27,7 @@ import { useRoute, useRouter } from "vue-router";
 | 
				
			|||||||
import { useStore } from "vuex";
 | 
					import { useStore } from "vuex";
 | 
				
			||||||
import SidebarItem from "./SidebarItem.vue";
 | 
					import SidebarItem from "./SidebarItem.vue";
 | 
				
			||||||
import { algorithm } from "../../../utils/algorithm";
 | 
					import { algorithm } from "../../../utils/algorithm";
 | 
				
			||||||
 | 
					import { useDynamicRoutesHook } from "../tag/tagsHook";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
  name: "sidebar",
 | 
					  name: "sidebar",
 | 
				
			||||||
@ -45,10 +47,22 @@ export default defineComponent({
 | 
				
			|||||||
      return path;
 | 
					      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 {
 | 
					    return {
 | 
				
			||||||
      routes: computed(() => algorithm.increaseIndexes(router)),
 | 
					      routes: computed(() => algorithm.increaseIndexes(router)),
 | 
				
			||||||
      activeMenu,
 | 
					      activeMenu,
 | 
				
			||||||
      isCollapse: computed(() => !store.getters.sidebar.opened),
 | 
					      isCollapse: computed(() => !store.getters.sidebar.opened),
 | 
				
			||||||
 | 
					      menuSelect,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -1,51 +1,117 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div class="tags">
 | 
					  <div class="tags-view">
 | 
				
			||||||
    <el-tag
 | 
					    <el-scrollbar :vertical="false" class="scroll-container">
 | 
				
			||||||
      size="medium"
 | 
					      <div
 | 
				
			||||||
      v-for="tag in tags"
 | 
					        v-for="(item, index) in dynamicTagList"
 | 
				
			||||||
      :key="tag.name"
 | 
					        :key="index"
 | 
				
			||||||
      closable
 | 
					        :class="['scroll-item', $route.path === item.path ? 'active' : '']"
 | 
				
			||||||
      :type="tag.type"
 | 
					 | 
				
			||||||
      >{{ tag.name }}</el-tag
 | 
					 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
 | 
					        <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>
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang='ts'>
 | 
					<script>
 | 
				
			||||||
import { ref, defineComponent, onUnmounted, onMounted } from "vue";
 | 
					import { useDynamicRoutesHook } from "./tagsHook";
 | 
				
			||||||
export default defineComponent({
 | 
					import { useRoute } from "vue-router";
 | 
				
			||||||
  name: "tag",
 | 
					import { ref, watchEffect } from "vue";
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
  setup() {
 | 
					  setup() {
 | 
				
			||||||
    let flag = ref(true);
 | 
					    const route = useRoute();
 | 
				
			||||||
 | 
					    const { deleteDynamicTag, dRoutes } = ref(useDynamicRoutesHook()).value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const tags = ref([
 | 
					    function deleteMenu(item) {
 | 
				
			||||||
      { name: "首页", type: "info" },
 | 
					      deleteDynamicTag(item, route.path);
 | 
				
			||||||
      { name: "基础管理", type: "info" },
 | 
					    }
 | 
				
			||||||
    ]);
 | 
					
 | 
				
			||||||
 | 
					    const { dynamicRouteTags } = useDynamicRoutesHook();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 初始化页面刷新保证当前路由tabview存在
 | 
				
			||||||
 | 
					    let stop = watchEffect(() => {
 | 
				
			||||||
 | 
					      let parentPath = route.path.slice(0, route.path.lastIndexOf("/"));
 | 
				
			||||||
 | 
					      dynamicRouteTags(route.path, parentPath);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setTimeout(() => {
 | 
				
			||||||
 | 
					      // 监听只执行一次,但获取不到当前路由,需要下一个事件轮询中取消监听
 | 
				
			||||||
 | 
					      stop();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      tags,
 | 
					      dynamicTagList: dRoutes,
 | 
				
			||||||
      flag,
 | 
					      deleteMenu,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
});
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="scss" scoped>
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
.tags {
 | 
					.tags-view {
 | 
				
			||||||
  height: 32px;
 | 
					  width: 100%;
 | 
				
			||||||
  float: right;
 | 
					  font-size: 14px;
 | 
				
			||||||
  border: 1px solid #f0f0f0;
 | 
					 | 
				
			||||||
  display: flex;
 | 
					  display: flex;
 | 
				
			||||||
  align-items: center;
 | 
					  align-items: center;
 | 
				
			||||||
  transition: 0.18s;
 | 
					  justify-content: flex-start;
 | 
				
			||||||
}
 | 
					  .scroll-item {
 | 
				
			||||||
:deep(.el-tag) {
 | 
					    border: 1px solid #eee;
 | 
				
			||||||
  background-color: #fff;
 | 
					    border-radius: 3px;
 | 
				
			||||||
  border: 1px solid #d0d7e7;
 | 
					    padding: 2px 8px;
 | 
				
			||||||
  margin-left: 4px;
 | 
					    display: inline-block;
 | 
				
			||||||
  &:first-child {
 | 
					    margin-right: 2px;
 | 
				
			||||||
    margin-left: 8px;
 | 
					  }
 | 
				
			||||||
 | 
					  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>
 | 
					</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 }">
 | 
					      <div :class="{ 'fixed-header': fixedHeader }">
 | 
				
			||||||
        <!-- 顶部导航栏 -->
 | 
					        <!-- 顶部导航栏 -->
 | 
				
			||||||
        <navbar />
 | 
					        <navbar />
 | 
				
			||||||
 | 
					        <!-- tabs标签页 -->
 | 
				
			||||||
 | 
					        <tag /> 
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <!-- 主体内容 -->
 | 
					      <!-- 主体内容 -->
 | 
				
			||||||
      <app-main />
 | 
					      <app-main />
 | 
				
			||||||
@ -21,7 +23,7 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import { Navbar, Sidebar, AppMain, setting } from "./components";
 | 
					import { Navbar, Sidebar, AppMain, setting, tag } from "./components";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  ref,
 | 
					  ref,
 | 
				
			||||||
  reactive,
 | 
					  reactive,
 | 
				
			||||||
@ -48,6 +50,7 @@ export default {
 | 
				
			|||||||
    Sidebar,
 | 
					    Sidebar,
 | 
				
			||||||
    AppMain,
 | 
					    AppMain,
 | 
				
			||||||
    setting,
 | 
					    setting,
 | 
				
			||||||
 | 
					    tag
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  setup() {
 | 
					  setup() {
 | 
				
			||||||
    const store = useStore();
 | 
					    const store = useStore();
 | 
				
			||||||
 | 
				
			|||||||
@ -220,6 +220,7 @@ const whiteList = ["/login", "/register"]
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
router.beforeEach((to, _from, next) => {
 | 
					router.beforeEach((to, _from, next) => {
 | 
				
			||||||
  NProgress.start()
 | 
					  NProgress.start()
 | 
				
			||||||
 | 
					  // @ts-ignore
 | 
				
			||||||
  document.title = to.meta.title // 动态title
 | 
					  document.title = to.meta.title // 动态title
 | 
				
			||||||
  whiteList.indexOf(to.path) !== -1 || storageSession.getItem("info") ? next() : next("/login") // 全部重定向到登录页
 | 
					  whiteList.indexOf(to.path) !== -1 || storageSession.getItem("info") ? next() : next("/login") // 全部重定向到登录页
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user