mirror of
https://github.com/pure-admin/vue-pure-admin.git
synced 2025-06-07 08:57:19 +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