前言
快捷导航不仅能够显著提升系统的灵活性和用户交互性,还极大地增强了用户的操作体验。本文将展示如何在 vue 中实现一个既可自定义又具备响应式特性的快捷导航菜单。
一、实现思路
列表页
-
结构设计
定义页面结构,包含一个导航卡片和一个对话框组件;
导航卡片中包含一个标题、添加按钮和导航项列表;
使用
v-for
指令遍历routeData
数组,动态生成每个导航项。 -
数据管理
在
data
中定义routeData
数组,存储导航项的信息(如图标和标题);定义
dialogChild
变量,用于控制对话框的显隐状态。 -
事件处理
handleCellClick(item)
:处理点击导航项的事件,输出所点击的项的信息;
deleteItem(index)
:处理删除导航项的事件,通过splice
方法从routeData
中移除指定项;
navigationAdd()
:打开添加导航项的对话框,将dialogChild
设置为true
。
1.1 列表组件
html
<template>
<div class="navigation">
<div class="cardContent">
<el-card class="box-card">
<div slot="header">
<span />
<span class="cardName">快捷导航</span>
<el-link style="float: right" type="primary" @click="navigationAdd">添加</el-link>
</div>
<div v-if="routeData && routeData.length" class="gridContainer">
<!-- 遍历路由数据,生成每个导航项 -->
<div v-for="(item, index) in routeData" :key="index" class="gridContent">
<div class="content" :title="item.title" @click.stop="handleCellClick(item)">
<p><img :src="item.imgUrl" alt="" /></p>
<p>{{ item.title }}</p>
</div>
<div class="overlay">
<i title="删除" class="el-icon-circle-close" @click.stop="deleteItem(index)"></i>
</div>
</div>
</div>
<div v-else><el-empty description="暂无数据"></el-empty></div>
</el-card>
</div>
<navigation-dialog :dialogChild.sync="dialogChild" />
</div>
</template>
<script>
import NavigationDialog from "./navigationDialog.vue";
export default {
components: { NavigationDialog },
data() {
return {
dialogChild: false, // 控制对话框显隐状态
routeData: [
//模拟数据
{
imgUrl: require("../assets/demo.png"),
title: "数据统计",
},
{
imgUrl: require("../assets/demo.png"),
title: "订单管理",
},
{
imgUrl: require("../assets/demo.png"),
title: "汇总信息",
},
{
imgUrl: require("../assets/demo.png"),
title: "客服管理",
},
{
imgUrl: require("../assets/demo.png"),
title: "财物中心",
},
{
imgUrl: require("../assets/demo.png"),
title: "系统管理",
},
{
imgUrl: require("../assets/demo.png"),
title: "系统监控",
},
{
imgUrl: require("../assets/demo.png"),
title: "系统工具",
},
],
};
},
methods: {
// 点击导航项的处理函数
handleCellClick(item) {
console.log("item:", item);
},
// 删除导航项的处理函数
deleteItem(index) {
this.routeData.splice(index, 1);
},
// 打开添加导航项对话框的处理函数
navigationAdd() {
this.dialogChild = true;
},
},
};
</script>
<style lang="less" scoped>
.navigation {
width: 36%;
padding: 16px;
.cardContent {
span:first-child {
display: inline-block;
width: 10px;
height: 10px;
background: #409eff;
margin-right: 10px;
}
.cardName,
.el-link {
font-weight: bold;
}
.gridContainer {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
gap: 16px;
padding: 16px;
.gridContent {
width: 100px;
height: 100px;
border-radius: 4px;
position: relative;
box-sizing: border-box;
overflow: hidden;
background: linear-gradient(to bottom, rgba(106, 183, 255, 0.7), rgba(106, 183, 255, 0.2));
display: flex;
align-items: center;
justify-content: center;
text-align: center;
.content {
cursor: pointer;
color: rgb(60, 60, 60);
img {
width: 50px;
height: 50px;
}
p:nth-child(2) {
margin-top: 2px;
width: 92px;
padding: 0px 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
.overlay {
position: absolute;
top: 3px;
right: 3px;
}
.el-icon-circle-close {
cursor: pointer;
font-size: 18px;
color: rgb(255, 255, 255);
}
.el-icon-circle-close:hover {
color: rgba(255, 255, 255, 0.5);
}
}
}
}
::v-deep {
.el-checkbox-button__inner {
margin: 0 8px 10px 8px;
border: 1px solid #d3d3d3;
border-radius: 4px !important;
}
}
</style>
弹框页
-
结构设计
使用
<el-dialog>
组件创建一个对话框,包含标题和内容区域;在内容区域中,使用
v-for
遍历cities
数组,生成每个菜单的选项和复选框。 -
数据管理
在
data
中定义cities
数组,模拟系统管理和监控等菜单项;定义
checkStates
对象,用于存储每个菜单的选中状态(全选、部分选中和选中的城市)。 -
事件处理
initializeCheckStates()
:初始化每个菜单的选中状态,设置全选状态和选中的菜单;
handleCheckAllChange(item)
:处理全选复选框的变化,更新选中状态;
handleCheckedCitiesChange(item)
:处理菜单复选框的变化,更新全选和部分选中状态;
submit()
:提交选中的信息,过滤出选中的路由项,并输出到控制台;
cancel()
:取消操作,重置选中状态并关闭对话框。
2.1 弹框组件
html
<template>
<el-dialog :close-on-click-modal="false" title="添加快捷导航" width="60%" :visible.sync="dialogVisible" @close="cancel">
<el-card class="box-card">
<!-- 遍历菜单数组,生成每个菜单的选项 -->
<div v-for="(item, index) in cities" :key="index" class="checkboxContent">
<span class="navTit">{{ item.label }}</span>
<!-- 全选复选框 -->
<el-checkbox v-model="checkStates[item.label].checkAll" :indeterminate="checkStates[item.label].isIndeterminate" @change="handleCheckAllChange(item)">全选</el-checkbox>
<!-- 菜单的复选框组 -->
<el-checkbox-group v-model="checkStates[item.label].checkedCities" size="medium" @change="handleCheckedCitiesChange(item)">
<el-checkbox-button v-for="(e, isx) in item.router" :key="isx" :label="e.title" border>{{ e.title }}</el-checkbox-button>
</el-checkbox-group>
</div>
<div class="btns">
<el-button size="medium" type="primary" @click="submit">保 存</el-button>
<el-button size="medium" @click="cancel">取 消</el-button>
</div>
</el-card>
</el-dialog>
</template>
<script>
export default {
props: {
dialogChild: {
type: Boolean,
default: false,
},
},
data() {
return {
dialogVisible: false, // 控制对话框显隐状态
cities: [
//模拟数据
{
label: "系统管理",
router: [
{ title: "用户管理", id: 0 },
{ title: "角色管理", id: 1 },
{ title: "菜单管理", id: 2 },
{ title: "部门管理", id: 3 },
{ title: "日志管理", id: 4 },
{ title: "字典管理", id: 5 },
],
},
{
label: "系统监控",
router: [
{ title: "在线用户", id: 6 },
{ title: "定时任务", id: 7 },
],
},
{
label: "系统工具",
router: [{ title: "表单构建", id: 8 }],
},
],
checkStates: {}, // 存储每个菜单的选中状态
};
},
watch: {
dialogChild: {
handler(newName) {
this.dialogVisible = newName;
},
deep: true,
},
},
mounted() {
this.initializeCheckStates();
},
methods: {
// 初始化每个菜单的选中状态
initializeCheckStates() {
this.cities.forEach((item) => {
this.$set(this.checkStates, item.label, {
checkAll: false, // 全选状态
checkedCities: [], // 选中的菜单
isIndeterminate: false, // 是否为部分选中状态
});
});
},
// 处理全选复选框的变化
handleCheckAllChange(item) {
this.checkStates[item.label].checkedCities = this.checkStates[item.label].checkAll ? item.router.map((e) => e.title) : [];
this.checkStates[item.label].isIndeterminate = false; // 重置部分选中状态
},
// 处理菜单复选框的变化
handleCheckedCitiesChange(item) {
const checkedCount = this.checkStates[item.label].checkedCities.length; // 选中的菜单数量
this.checkStates[item.label].checkAll = checkedCount === item.router.length; // 更新全选状态
this.checkStates[item.label].isIndeterminate = checkedCount > 0 && checkedCount < item.router.length; // 更新部分选中状态
},
// 提交选中的信息
submit() {
const selectedInfo = this.cities.map((item) => {
const { checkedCities } = this.checkStates[item.label]; // 获取当前导航项的选中状态
const selectedRoutes = item.router.filter((route) => checkedCities.includes(route.title)); // 过滤出选中的路由项
return {
label: item.label, // 导航项标签
selectedRoutes: selectedRoutes.map((route) => ({
title: route.title,
id: route.id,
})), // 选中的路由项
};
});
console.log("选中的信息:", selectedInfo); // 输出选中的信息
},
// 取消操作
cancel() {
this.initializeCheckStates(); // 重置选中状态
this.$emit("update:dialogChild", false);
},
},
};
</script>
<style lang="less" scoped>
.checkboxContent {
margin-bottom: 16px;
.navTit {
margin-right: 10px;
color: rgb(41, 107, 255);
font-weight: bold;
font-size: 16px;
}
.el-checkbox {
margin: 16px 0;
}
}
.btns {
margin-top: 16px;
display: flex;
justify-content: right;
}
::v-deep {
.el-checkbox-button__inner:nth-child(n + 2) {
margin: 0 !important;
margin: 0 10px 10px 0 !important;
}
}
</style>
1.3 组件通信
使用 props
和 $emit
实现父子组件之间的通信。A
页面通过 dialogChild
属性控制 B
页面的对话框显隐状态,并通过 update:dialogChild
事件通知 B
页面关闭对话框。