TagsView
流程
注意
- 监听路由变化,组成用于渲染
tags
的数据源 - 创建
tags
组件,根据数据源渲染tag
,渲染出来的tags
需要同时具备 2.1. 国际化title
2.2 路由跳转
数据源
tags
的数据我们最好把它保存到pinia
中。
bash
1. 保存数据:`appmain` 组件中进行
2. 展示数据:`tags` 组件中进行
步骤
在 constant 中新建常量
- src/constant/index.js
js
// tags
export const TAGS_VIEW = "tagsView";
stores 创建 useTags
- stores/tagsView.js
js
/**
* @Author: jsopy
* @Date: 2025-01-18 17:05:38
* @LastEditTime: 2025-01-18 17:43:49
* @FilePath: /admin/src/stores/tagsView.js
* @Description: tagsView store
* @
*/
import { TAGS_VIEW } from "@/constant/index.js";
import { getItem, setItem } from "@/utils/storage";
import { defineStore } from "pinia";
export const useTags = defineStore("tagsView", () => {
// 设置tags源
const tagsViewList = ref(getItem(TAGS_VIEW) || []);
// 添加tags
const addTagsViewList = (tag) => {
const isFind = tagsViewList.value.find((item) => {
return item.path === tag.path;
});
// 处理重复
if (!isFind) {
tagsViewList.value.push(tag);
setItem(TAGS_VIEW, tagsViewList.value);
}
};
// 为指定的tag修改title
const changeTagsView = ({ index, tag }) => {
tagsViewList.value[index] = tag;
setItem(TAGS_VIEW, tagsViewList.value);
};
// 删除tagsview
/**
* 删除 tag
* @param {type: 'other'||'right'||'index', index: index} payload
*/
const removeTagsView = (payload) => {
if (payload.type === "index") {
tagsViewList.value.splice(payload.index, 1);
return;
} else if (payload.type === "other") {
tagsViewList.value.splice(
payload.index + 1,
tagsViewList.value.length - payload.index + 1
);
tagsViewList.value.splice(0, payload.index);
} else if (payload.type === "right") {
tagsViewList.value.splice(
payload.index + 1,
tagsViewList.value.length - payload.index + 1
);
}
setItem(TAGS_VIEW, tagsViewList.value);
};
return {
tagsViewList,
addTagsViewList,
changeTagsView,
removeTagsView,
};
});
创建白名单
- utils/tags.js
js
/**
* @Author: jsopy
* @Date: 2025-01-18 17:13:28
* @LastEditTime: 2025-01-18 17:15:37
* @FilePath: /admin/src/utils/tags.js
* @Description:
* @
*/
const whiteList = ["/login", "/import", "/404", "/401"];
/**
* path 是否需要被缓存
* @param {*} path
* @returns
*/
export function isTags(path) {
return !whiteList.includes(path);
}
Appmain 监听路由变化
- appmain/index.vue
vue
<!--
* @Author: jsopy
* @Date: 2025-01-12 09:45:02
* @LastEditTime: 2025-01-18 17:44:18
* @FilePath: /admin/src/components/AppMain/index.vue
* @Description:
*
-->
<template>
<div class="app-main">
<router-view></router-view>
</div>
</template>
<script setup>
import { isTags } from "@/utils/tags";
import { generateTitle } from "@/utils/i18n";
import { useTags } from "@/stores/tagsView";
import { useI18n } from "vue-i18n";
const { tagsViewList, addTagsViewList, changeTagsView, removeTagsView } =
useTags();
const route = useRoute();
const i18n = useI18n();
/* 生成title */
const getTitle = (route) => {
let title = "";
if (!route.meta) {
// 处理无 meta 的路由
const pathArr = route.path.split("/");
title = pathArr[pathArr.length - 1];
} else {
title = generateTitle(route.meta.title);
}
return title;
};
/* 监听路由变化 */
watch(
route,
(to, from) => {
if (!isTags(to.path)) return;
const { fullPath, meta, name, params, path, query } = to;
addTagsViewList({
fullPath,
meta,
name,
params,
path,
query,
title: getTitle(to),
});
},
{
immediate: true,
}
);
/* 监听 国际化 */
watch(
() => {
return i18n.locale.value;
},
(newval, oldval) => {
console.log("改变了");
console.log(newval);
tagsViewList.forEach((route, index) => {
changeTagsView({
index,
tag: {
...route,
title: getTitle(route),
},
});
});
}
);
</script>
<style lang="scss" scoped></style>
创建组件
- components/TagsView/index.vue
vue
<!--
* @Author: jsopy
* @Date: 2025-01-18 17:25:39
* @LastEditTime: 2025-01-18 17:45:23
* @FilePath: /admin/src/components/TagsView/index.vue
* @Description:
*
-->
<template>
<div class="tags-view-container" v-if="tagsViewList != []">
<router-link
class="tags-view-item"
:class="isActive(tag) ? 'active' : ''"
:style="{
backgroundColor: isActive(tag) ? bgcolor : '',
borderColor: isActive(tag) ? bgcolor : '',
}"
v-for="(tag, index) in useTags().tagsViewList"
:key="tag.fullPath"
:to="{ path: tag.fullPath }"
>
<span
:style="{
color: isActive(tag) ? activetextcolor : defaultextcolor,
}"
>
{{ tag.title }}</span
>
<span
@click.prevent.stop="onCloseClick(index)"
v-show="!isActive(tag)"
style="color: black; font-size: 12px"
>
x
</span>
</router-link>
</div>
</template>
<script setup>
import { useTags } from "@/stores/tagsView";
import { useRoute } from "vue-router";
import { generateTitle } from "@/utils/i18n";
import { useZhuTiStore } from "@/stores/ZhuTi";
const { tagsViewList, addTagsViewList, changeTagsView, removeTagsView } =
useTags();
/* 获取 变量 */
const ZhuTistore = useZhuTiStore();
const bgcolor = ref(ZhuTistore.bgcolor);
const defaultextcolor = ref(ZhuTistore.defaultTextColor);
const activetextcolor = ref(ZhuTistore.activeTextColor);
watch(
() => {
return ZhuTistore.bgcolor;
},
(newval, oldval) => {
bgcolor.value = newval;
}
);
watch(
() => {
return ZhuTistore.defaultTextColor;
},
(newval, old) => {
defaultextcolor.value = newval;
}
);
watch(
() => {
return ZhuTistore.activeTextColor;
},
(newval, oldval) => {
activetextcolor.value = newval;
}
);
const route = useRoute();
/**
* 是否被选中
*/
const isActive = (tag) => {
return tag.path === route.path;
};
/**
* 关闭 tag 的点击事件
*/
const onCloseClick = (index) => {
removeTagsView({ type: "index", index });
};
</script>
<style lang="scss" scoped>
.tags-view-container {
height: 34px;
width: 100%;
background: #fff;
border-bottom: 1px solid #d8dce5;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04);
.tags-view-item {
display: inline-block;
position: relative;
cursor: pointer;
height: 26px;
line-height: 26px;
border: 1px solid #d8dce5;
color: #495060;
background: #fff;
padding: 0 8px;
font-size: 12px;
margin-left: 5px;
margin-top: 4px;
&:first-of-type {
margin-left: 15px;
}
&:last-of-type {
margin-right: 15px;
}
&.active {
color: #fff;
&::before {
content: "";
background: #fff;
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
position: relative;
margin-right: 4px;
}
}
// close 按钮
.el-icon-close {
width: 16px;
height: 16px;
line-height: 10px;
vertical-align: 2px;
border-radius: 50%;
text-align: center;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
transform-origin: 100% 50%;
&:before {
transform: scale(0.6);
display: inline-block;
vertical-align: -3px;
}
&:hover {
background-color: #b4bccc;
color: #fff;
}
}
}
}
</style>
引入组件
- src/layouts/default.vue
vue
<!--
* @Author: jsopy
* @Date: 2025-01-08 10:29:01
* @LastEditTime: 2025-01-18 17:44:52
* @FilePath: /admin/src/layouts/default.vue
* @Description:
*
-->
<template>
<div class="app-wrapper">
<!--左侧-->
<SideBar id="guide-sidebar" class="sidebarleft"></SideBar>
<!--左侧-->
<!--右侧-->
<div class="main-containerall">
<div class="fixed-header">
<!--顶部的navbar-->
<NavBar></NavBar>
<!--tags-->
<TagsView></TagsView>
<!--tags-->
</div>
<!--内容区域-->
<div class="content">
<AppMain></AppMain>
</div>
<!--内容区域-->
</div>
<!--右侧-->
</div>
</template>
<script setup>
import { useCollapse } from "@/stores/sidebaropen";
const sidebarwidth = computed(() => {
return useCollapse().sidebarOpened ? "64px" : "210px";
});
</script>
<style lang="scss" scoped>
.app-wrapper {
display: flex;
min-height: 100vh;
width: 100%;
flex-flow: row nowrap;
.sidebarleft {
flex: 0 0 v-bind(sidebarwidth);
min-height: 100vh;
background: #{$menuBg};
}
.main-containerall {
flex: 1;
display: flex;
min-height: 100vh;
flex-flow: column nowrap;
.fixed-header {
position: sticky;
top: 0px;
z-index: 10;
flex: 0 0 105px;
width: 100%;
}
.content {
flex: 1;
width: 100%;
}
}
}
</style>