您的位置:首页 > 财经 > 产业 > 建设通官网首页_西安学校网站建设费用_体球网足球世界杯_网站片区

建设通官网首页_西安学校网站建设费用_体球网足球世界杯_网站片区

2025/5/22 19:34:34 来源:https://blog.csdn.net/sen_shan/article/details/147272041  浏览:    关键词:建设通官网首页_西安学校网站建设费用_体球网足球世界杯_网站片区
建设通官网首页_西安学校网站建设费用_体球网足球世界杯_网站片区

 系列文档目录

Vue3+Vite+TypeScript安装

Element Plus安装与配置

主页设计与router配置

静态菜单设计

Pinia引入

Header响应式菜单缩展

Mockjs引用与Axios封装

登录设计

登录成功跳转主页

多用户动态加载菜单

Pinia持久化

动态路由 -动态增加路由

动态路由-动态删除路由 

路由守卫-无路由跳转404

 路由守卫-未登录跳转登录界面

 登录退出

Tags-组件构建

Tags-与菜单联动 

Pinia持久化优化

按钮权限

客制按钮组件

客制Table组件

客制Form组件

国际化

配置文件


 文章目录

目录

 系列文档目录

 文章目录

前言 

Tags设定

菜单单击

 演示效果

tab切换

Tag关闭

后续



前言 

        上一章节已完成了 Tags 组件的初步构建。本章节中,将继续完善 Tags 组件,使其与菜单实现完整联动。 


Tags设定

在存储管理中进行了调整,新增了   setTabsData   方法,用于更新 Tabs 数据。

 // tabssetTabsData(val: any)    {console.log('val',val)if(val.name==='home'){this.currentMenu=null;}else{let index=this.tabs.findIndex((item:any)=>item.index===val.index);console.log(index)index===-1?  this.tabs.push(val):"";console.log('tabs:',this.tabs)}},

 优化后完整代码:

// src/stores/index.tsimport { defineStore } from 'pinia';
import router from '../router';
import type { Component } from 'vue';
import { get } from 'http';type Modules = Record<string, () => Promise<{ default: Component }>>;// 定义公共 store
export const useAllDataStore = defineStore('useAllData', {// 定义状态state: () => ({isCollapse: false, // 定义初始状态username: '',token_key: '',menuData:[],tabs:[{path:"/home"     ,index:"Home",label:"home",icon:"home"}],currentMenu:null,}),// 定义 actionsactions: {// 设置用户名setUsername(username: string) {this.username = username;},// 获取用户名getUsername(): string {return this.username;},// 设置 token_keysetTokenKey(token_key: string) {// sessionStorage.setItem('useAllData-session-store', JSON.stringify({ token_key: token_key, menuData: this.menuData}));this.token_key = token_key;},// 获取 token_keygetTokenKey(): string {/*const sessionData = sessionStorage.getItem('useAllData-session-store');console.log(sessionData)if (sessionData) {const data = JSON.parse(sessionData);this.token_key = data.token_key;this.menuData = data.menuData;}else{this.token_key = ''}*/return this.token_key;},// 设置菜单数据setMenuData(menuData: any){// sessionStorage.setItem('useAllData-session-store', JSON.stringify({ token_key: this.token_key, menuData: menuData}));addRouter(menuData)this.menuData = menuData},// 获取菜单数据getMenuData(): [] {return this.menuData;},// tabssetTabsData(val: any)    {console.log('val',val)if(val.name==='home'){this.currentMenu=null;}else{let index=this.tabs.findIndex((item:any)=>item.index===val.index);console.log(index)index===-1?  this.tabs.push(val):"";console.log('tabs:',this.tabs)}},getTabsData(): [] {return this.tabs;},// 登出方法logout() {sessionStorage.removeItem('useAllData-store'); // 清除 sessionStorage 中的数据/*this.username = '';this.token_key = '';this.menuData = [];*/router.push({ name: 'login' }); // 重定向到登录页面},},persist: {enabled: true,key: 'useAllData-store',storage: sessionStorage, // // localStorage  sessionStoragepaths: ['token_key'], // 指定持久化的字段},/*persist: {enabled: true,strategies: [{key: 'useAllData-store',storage: sessionStorage,// localStorage  sessionStoragepaths: ['token_key','menuData'], // 指定需要持久化的字段},],},*/});function addRouter(menuData: any){const routerList=router.getRoutes()const modules: Modules = import.meta.glob('../views/**/*.vue') as Modules;const routerArr=[]menuData.forEach((item:any) => {// console.log(item) if(item.children){item.children.forEach((child:any) => {const componentPath = `../${child.path}.vue`;const module = modules[componentPath];if (module) {/*module().then(({ default: component }) => {child.component = component;});*/child.component = module;routerArr.push(child)}});}else{const componentPath = `../${item.path}.vue`;const module = modules[componentPath];if(module){item.component = module;routerArr.push(item)}}});// 增加删除路由routerList.forEach((item:any) => {if (item.name === 'main' || item.name === 'home' || item.name === '404' || item.name === 'login'|| item.name === 'error'|| item.name === 'undefined'|| item.path === '/'|| item.path === '/main') returnrouter.removeRoute(item.name)});routerArr.forEach((item:any) => {router.addRoute('main',{path: item.index,name: item.label,component: item.component,});})
const routerListLast=router.getRoutes()
console.log(routerListLast)}export function ReloadData() {const store = useAllDataStore();const menuData = store.getMenuData();addRouter(menuData);}

菜单单击

        在   components/MainAsideCont.vue   文件中,调整了菜单的单击事件处理逻辑,通过 Pinia 状态管理新增了 Tags 数据 的处理。

const handlemenu = (item: MenuItem) => {router.push(item.index);console.log('item:', item);store.setTabsData(item)};const handlemenuchild = (item: MenuItem, subItem: MenuItem) => {router.push(subItem.index);console.log('subItem:', subItem);store.setTabsData(subItem)
};

 调整后完整语句:

<template><el-menu:default-active="activeIndex"class="el-menu-vertical-demo":collapse="isCollapse"><h3 :key="TitleText">{{ TitleText }}</h3><!-- 渲染没有子菜单的项 --><el-menu-itemv-for="item in noChilden":key="item.index":index="item.index"@click="handlemenu(item)"><component v-if="item.icon" class="icon" :is="item.icon.name"></component><span>{{ item.label }}</span></el-menu-item><!-- 渲染有子菜单的项 --><el-sub-menuv-for="item in hasChilden":key="item.index":index="item.index"><template #title><component v-if="item.icon" class="icon" :is="item.icon.name"></component><span>{{ item.label }}</span></template><el-menu-itemv-for="subItem in item.children":key="subItem.index":index="subItem.index"@click="handlemenuchild(item, subItem)"><span>{{ subItem.label }}</span></el-menu-item></el-sub-menu></el-menu>
</template><script lang="ts" setup>
import { ref, computed, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { useAllDataStore } from '@/stores';const store = useAllDataStore();interface MenuItem {index: string;label: string;icon?: { name: string; __name: string };children?: MenuItem[];
}// 确保 menuAPI 是一个数组,并赋值给 menuData
const menuData = ref<MenuItem[]>([]); // 初始化为空数组// 封装数据获取和处理逻辑
const fetchMenuData = () => {try {const result = store.getMenuData(); // 调用 store 获取数据console.log('main menuAPI 返回的数据:', store.getMenuData());console.error('main menuAPI :', result);if (Array.isArray(result)) {menuData.value = result as MenuItem[];} else {console.error('menuAPI 返回的数据不是数组:', result);}} catch (error) {console.error('获取菜单数据失败:', error);}
};onMounted(() => {if (!store.getMenuData().length) {console.warn('菜单数据为空,尝试重新获取');fetchMenuData();} else {console.log('菜单数据已存在,无需重新获取');menuData.value = store.getMenuData() as MenuItem[];console.log('menuData.value:', menuData.value);}
});const hasChilden = computed(() => menuData.value.filter(item => item.children && item.children.length > 0));
const noChilden = computed(() => menuData.value.filter(item => !item.children || item.children.length === 0));const activeIndex = ref('Home');
const router = useRouter();const handlemenu = (item: MenuItem) => {router.push(item.index);console.log('item:', item);store.setTabsData(item)};const handlemenuchild = (item: MenuItem, subItem: MenuItem) => {router.push(subItem.index);console.log('subItem:', subItem);store.setTabsData(subItem)
};const TitleText = computed(() => {return store.isCollapse ? '平台' : '测试平台管理';
});const isCollapse = computed(() => store.isCollapse);</script><style>
.el-menu {height: 100%; /* 设置整个布局的高度为 100%,确保布局占满整个视口 */border-right: none; /* 去掉右边框 */
}
.el-menu-vertical-demo:not(.el-menu--collapse) {width: 180px;min-height: 400px;
}
.el-menu-vertical-demo.el-menu--collapse {width: 60px; /* 收缩时的宽度 */
}.icon {margin-right: 8px; /* 图标与文字之间的间距 */font-size: 18px; /* 图标的大小 */width: 18px;height: 18px;size: 8px;color: #606266; /* 图标的默认颜色 */vertical-align: middle; /* 垂直居中对齐 */
}/* 鼠标悬停时的样式 */
.icon:hover {color: #409eff; /* 鼠标悬停时图标的颜色 */
}
</style>

 演示效果

点击菜单时,内容将以 Tabs 的形式在   Main   区域呈现。

tab切换

在   components/MainTagsCont.vue   中增加了 Tabs 的单击事件,目的是在点击 Tabs 时切换对应的内容。

@click="handleTagClick(item)"
const handleTagClick = (item) => {router.push(item.index)store.setTabsData(item);};

修改后完整代码:

<template><div class="tabs"><el-tag v-for="(item,index) in tabs":key="index":label="item.label":name="item.index":icon="item.icon":path="item.path":closable="item.index !== 'Home'":effect="route.name===item.label?'dark':'plain'"@click="handleTagClick(item)"type="primary"round>{{ item.label }}</el-tag></div>
</template>
<script lang="ts" setup>
import{ effect, ref ,computed} from "vue"
import { useRoute ,useRouter } from "vue-router";
import { useAllDataStore } from '@/stores';const store = useAllDataStore();const tabs=computed(() => store.getTabsData()); const route=useRoute()const router=useRouter()
const handleTagClick = (item) => {router.push(item.index)store.setTabsData(item);};</script>
<style scoped>.tabs{margin: 20px 0 0 20px;
}
.el-tag{margin-right: 10px;
}
</style>

测试了 Tab 点击事件,效果如下:

Tag关闭

1.在   stores/index.ts   文件中,为 Pinia 状态管理 新增了   removeTagsData   方法,用于移除指定的 Tags 数据

    removeTagsData(val: any){let index=this.tabs.findIndex((item:any)=>item.index===val.index);this.tabs.splice(index,1)},

优化后完整语句:

// src/stores/index.tsimport { defineStore } from 'pinia';
import router from '../router';
import type { Component } from 'vue';
import { get } from 'http';type Modules = Record<string, () => Promise<{ default: Component }>>;// 定义公共 store
export const useAllDataStore = defineStore('useAllData', {// 定义状态state: () => ({isCollapse: false, // 定义初始状态username: '',token_key: '',menuData:[],tabs:[{path:"/home"     ,index:"Home",label:"home",icon:"home"}],currentMenu:null,}),// 定义 actionsactions: {// 设置用户名setUsername(username: string) {this.username = username;},// 获取用户名getUsername(): string {return this.username;},// 设置 token_keysetTokenKey(token_key: string) {// sessionStorage.setItem('useAllData-session-store', JSON.stringify({ token_key: token_key, menuData: this.menuData}));this.token_key = token_key;},// 获取 token_keygetTokenKey(): string {/*const sessionData = sessionStorage.getItem('useAllData-session-store');console.log(sessionData)if (sessionData) {const data = JSON.parse(sessionData);this.token_key = data.token_key;this.menuData = data.menuData;}else{this.token_key = ''}*/return this.token_key;},// 设置菜单数据setMenuData(menuData: any){// sessionStorage.setItem('useAllData-session-store', JSON.stringify({ token_key: this.token_key, menuData: menuData}));addRouter(menuData)this.menuData = menuData},// 获取菜单数据getMenuData(): [] {return this.menuData;},// tabssetTabsData(val: any)    {console.log('val',val)if(val.name==='home'){this.currentMenu=null;}else{let index=this.tabs.findIndex((item:any)=>item.index===val.index);console.log(index)index===-1?  this.tabs.push(val):"";console.log('tabs:',this.tabs)}},getTabsData(): [] {return this.tabs;},removeTagsData(val: any){let index=this.tabs.findIndex((item:any)=>item.index===val.index);this.tabs.splice(index,1)},// 登出方法logout() {sessionStorage.removeItem('useAllData-store'); // 清除 sessionStorage 中的数据/*this.username = '';this.token_key = '';this.menuData = [];*/router.push({ name: 'login' }); // 重定向到登录页面},},persist: {enabled: true,key: 'useAllData-store',storage: sessionStorage, // // localStorage  sessionStoragepaths: ['token_key'], // 指定持久化的字段},/*persist: {enabled: true,strategies: [{key: 'useAllData-store',storage: sessionStorage,// localStorage  sessionStoragepaths: ['token_key','menuData'], // 指定需要持久化的字段},],},*/});function addRouter(menuData: any){const routerList=router.getRoutes()const modules: Modules = import.meta.glob('../views/**/*.vue') as Modules;const routerArr=[]menuData.forEach((item:any) => {// console.log(item) if(item.children){item.children.forEach((child:any) => {const componentPath = `../${child.path}.vue`;const module = modules[componentPath];if (module) {/*module().then(({ default: component }) => {child.component = component;});*/child.component = module;routerArr.push(child)}});}else{const componentPath = `../${item.path}.vue`;const module = modules[componentPath];if(module){item.component = module;routerArr.push(item)}}});// 增加删除路由routerList.forEach((item:any) => {if (item.name === 'main' || item.name === 'home' || item.name === '404' || item.name === 'login'|| item.name === 'error'|| item.name === 'undefined'|| item.path === '/'|| item.path === '/main') returnrouter.removeRoute(item.name)});routerArr.forEach((item:any) => {router.addRoute('main',{path: item.index,name: item.label,component: item.component,});})
const routerListLast=router.getRoutes()
console.log(routerListLast)}export function ReloadData() {const store = useAllDataStore();const menuData = store.getMenuData();addRouter(menuData);}

 2.在   components/MainTagsCont.vue   文件中,新增了 关闭事件 的处理逻辑,用于实现 Tab 的关闭功能。

const handleTagClose = (item,index) => {// pinia 删除数据store.removeTagsData(item);// 如果点击关闭不是当前标签,则不执行路由跳转if(route.name !==item.label){return}if(index===store.getTabsData().length){router.push(store.getTabsData()[index-1].index)}else{router.push(store.getTabsData()[index].index)}}

 修改后完整语句:

<template><div class="tabs"><el-tag v-for="(item,index) in tabs":key="index":label="item.label":name="item.index":icon="item.icon":path="item.path":closable="item.index !== 'Home'":effect="route.name===item.label?'dark':'plain'"@click="handleTagClick(item)"@close="handleTagClose(item,index)"round>{{ item.label }}</el-tag></div>
</template>
<script lang="ts" setup>
import{ effect, ref ,computed} from "vue"
import { useRoute ,useRouter } from "vue-router";
import { useAllDataStore } from '@/stores';const store = useAllDataStore();const tabs=computed(() => store.getTabsData()); const route=useRoute()const router=useRouter()
const handleTagClick = (item) => {router.push(item.index)store.setTabsData(item);};
const handleTagClose = (item,index) => {// pinia 删除数据store.removeTagsData(item);// 如果点击关闭不是当前标签,则不执行路由跳转if(route.name !==item.label){return}if(index===store.getTabsData().length){router.push(store.getTabsData()[index-1].index)}else{router.push(store.getTabsData()[index].index)}}</script>
<style scoped>.tabs{margin: 20px 0 0 20px;
}
.el-tag{margin-right: 10px;
}
</style>

3.测试Tabs关闭

运行程序登录后并打开多个菜单项。

 测试1-关闭最后一个

测试2-关闭中间一个

 


后续

        本章节完善了 Tags 组件,使其与菜单实现了完整的联动功能。然而,当前的实现仍存在一些与 Pinia 相关的问题。下一章节将重点优化 Pinia 的持久化,以提升数据管理的稳定性和用户体验。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com