前端功能问题系列文章,点击上方合集↑
序言
大家好,我是大澈!
本文约3100+
字,整篇阅读大约需要5分钟。
本文主要内容分三部分,第一部分是需求分析,第二部分是实现步骤,第三部分是问题详解。
如果您只需要解决问题,请阅读第一、二部分即可。
如果您有更多时间,进一步学习问题相关知识点,请阅读至第三部分。
1. 需求分析
实现级联菜单效果,点击一级菜单某一项,就会加载出对应的二级菜单内容,点击二级菜单某一项,就会加载出对应的三级菜单内容,以此类推,最后一级为五级菜单。
对于加载
而言,必须是懒加载效果,即点击菜单某一项时,才会真正去请求加载对应的下一级菜单内容。
对于菜单最后一级内容,要插入一个输入框
,在输入框内,用户可以任意输入版本号,并且要求输入框不可被选中。
当然,我们的正题是数据的懒加载,级联菜单中插入不可被选中的输入框实现算是附加内容了。
2. 实现步骤
2.1 为什么要使用数据懒加载
在项目中,我们使用数据的懒加载,会有如下好处
:
-
提升用户体验
-
减少初始加载时间
-
减轻网络资源的消耗
了解了这些好处之后,再就是要了解数据懒加载的应用场景。
这部分比较有用,所以多叙述一些。
数据懒加载在以下场景
中特别有用:
-
长列表或分页数据
:当你有一个包含大量数据的列表或分页功能时,可以使用数据懒加载来延迟加载列表项或分页数据。只有当用户滚动到可见范围时才加载新的数据,从而提高初始加载速度和响应性能。
-
图片或媒体资源
:对于包含大量图片或媒体资源的页面,可以使用数据懒加载来延迟加载这些资源。只有当图片或媒体元素进入视口时才进行加载,从而减少初始加载时间和网络资源的消耗。
-
条件性加载
:当一些数据或组件只在特定条件下才需要显示或使用时,可以使用数据懒加载。根据条件动态加载数据或组件,避免不必要的加载和资源占用。
-
路由级别的懒加载
:在Vue的路由中,可以使用懒加载来按需加载路由组件。只有当用户访问某个路由时才进行组件的加载,提高初始加载速度和路由切换的性能。
-
异步加载模块
:对于大型的Vue项目,可以使用数据懒加载来异步加载模块,减少初始加载时间。将模块分割成小块,并在需要时按需加载,提高应用的性能和可维护性。
上述场景在此部分只做简单描述,如果你有空余时间,各个场景具体代码
实例请见第三部分总结。
我们这次的需求实现,就是符合第1
条场景。
2.2 数据懒加载代码实例
当选中某一级时,动态加载该级下的选项。
通过lazy
开启动态加载,并通过lazyload
方法来设置加载数据源。
-
lazyload
方法有两个参数,第一个参数node
为当前点击的节点,第二个resolve
为数据加载完成的回调(必须调用)。 -
node
可以解构出level
参数,它从0
开始计数,指明当前点击节点的层级。 -
为数据对象添加
leaf
属性,是为了指明哪一级的对象是叶子节点。
模板代码:
<template>
<!-- 级联面板 -->
<el-cascader-panel :props="props"></el-cascader-panel>
</template>
逻辑代码:
<script setup>
// 级联面板配置项
const props = reactive({
// 开启懒加载
lazy: true,
// 懒加载加载数据源方法
async lazyLoad(node, resolve) {
const { level } = node;
// 已经有数据了,不需要重复请求了
if (node.children && node.children.length > 0) return;
// 模拟掉接口
setTimeout(() => {
let result;
switch (level) {
case 0: // 一级目录
result = [
{
value: "0",
label: "行内转账-手机号",
children: []
},
{
value: "1",
label: "行内转账-银行账号",
children: []
},
{
value: "2",
label: "汇款到当地银行",
children: []
},
{
value: "3",
label: "汇款到钱包",
children: []
},
{
value: "4",
label: "转账到CUPD",
children: []
}
];
break;
case 1: //二级目录
// 省略此处代码...
break;
case 2: //三级目录
// 省略此处代码...
break;
case 3: //四级目录
// 省略此处代码...
break;
case 4: // 五级目录
result = [
{
value: "最低系统版本",
label: "最低系统版本要求"
},
{
value: "0",
label: "不启用"
},
{
value: "1",
label: "内测"
},
{
value: "2",
label: "上线"
}
];
// 设置五级菜单为叶子节点,无子节点
result.forEach((item) => {
item.leaf = level >= 4;
});
break;
default:
result = [];
break;
}
resolve(result);
}, 1000);
}
});
</script>
2.3 插入输入框代码实例
通过插槽和条件判断,在级联面板中插入输入框,其中data.label
为节点对象的名称。
通过@click.stop
阻止事件冒泡、宽度为父元素的100%
、以及清除级联面板组件一些默认样式,实现了输入框不可被选中的效果。
模版代码:
<template>
<!-- 级联面板 -->
<el-cascader-panel :props="props">
<template #default="{ data }">
<!-- 输入框节点对象 -->
<div v-if="data.label == '最低系统版本要求'" @click.stop style="width: 100%">
<div>{{ data.label }}</div>
<div class="searchText">
<el-input v-model="searchText" :placeholder="'最低系统版本要求'"></el-input>
</div>
</div>
<div v-else>
<div>{{ data.label }}</div>
</div>
</template>
</el-cascader-panel>
</template>
样式代码:
<style lang="scss" scope>
.el-cascader-panel.is-bordered {
border: none;
}
.el-cascader-menu:last-child .el-cascader-node:nth-of-type(1):has(.searchText) {
margin-bottom: 30px;
padding: 0;
&:hover,
&:focus {
background: #fff;
}
}
.el-cascader-menu__list {
padding: 20px 0;
}
</style>
3. 问题详解
3.1 关于CascaderProps配置项的参数一览
3.2 懒加载各个场景代码实例总结
长列表或分页数据懒加载:
<template>
<div>
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
<li v-if="loading">Loading...</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
items: [],
loading: false,
};
},
mounted() {
this.loadMore(); // 初始加载
window.addEventListener('scroll', this.handleScroll);
},
methods: {
loadMore() {
this.loading true;
// 模拟异步加载数据
setTimeout(() => {
const newItems = /* 请求新数据 */;
this.items = this.items.concat(newItems);
this.loading = false;
}, 1000);
},
handleScroll() {
const scrollPosition = window.innerHeight + window.pageYOffset;
const contentHeight = document.documentElement.scrollHeight;
if (scrollPosition >= contentHeight && !this.loading) {
this.loadMore(); // 滚动到底部时加载更多数据
}
},
},
beforeDestroy() {
window.removeEventListener('scroll', this.handleScroll);
},
};
</script>
图片懒加载:
<template>
<div>
<img :src="placeholder" ref="image" style="display: none;">
</div>
</template>
<script>
export {
data() {
return {
placeholder: require('@/assets/placeholder.png'), // 占位图
};
},
mounted() {
window.addEventListener('scroll', this.handleScroll);
},
methods: {
handleScroll() {
const imageElement = this.$refs.image;
const rect = imageElement.getBoundingClientRect();
if (rect.top < window.innerHeight) {
const src = /* 获取图片真实地址 */;
imageElement.src = src; // 加载图片
window.removeEventListener('scroll', this.handleScroll); // 图片加载后移除滚动监听
}
},
},
beforeDestroy() {
window.removeEventListener('scroll', this.handle);
},
};
</script>
条件性数据懒加载:
<template>
<div>
<button @click="showData = true">显示数据</button>
<div v-if="showData">
<!-- 显示数据的组件或内容 -->
</div>
</div>
</template>
<script>
export default {
data() {
return {
showData: false,
};
},
};
</script>
路由懒加载(基于 Vue Router
):
const Home = () => import('./components/Home.vue');
const About = () => import('./components/About.vue');
const Contact = () => import('./components/Contact.vue');
const routes = [
{ path: '/home', component: Home },
{ path: '/about', component: About },
{ path: '/contact', component: Contact },
];
const router = new VueRouter({
routes,
});
异步加载模块(基于 import()
动态导入语法):
<template>
<div>
<button @click="loadModule">加载模块</button>
<div v-if="moduleLoaded">
<!-- 显示已加载的模块 -->
</div>
</div>
</template>
<script>
export default {
data() {
return {
moduleLoaded: false,
};
},
methods: {
loadModule() {
import('./path/to/module.js')
.then((module) => {
// 模块加载成功
this.moduleLoaded = true;
})
.catch((error) => {
console.error(error);
});
},
},
};
</script>
结语
建立这个平台的初衷:
-
打造一个仅包含前端问题的问答平台,让大家高效搜索处理同样问题。
-
通过不断积累问题,一起练习逻辑思维,并顺便学习相关的知识点。
-
遇到难题,遇到有共鸣的问题,一起讨论,一起沉淀,一起成长。
感谢关注微信公众号:“程序员大澈”,然后加入问答群
,让我们一起解决实现所有BUG!