el-table动态渲染列、可编辑单元格、虚拟无缝滚动的实现
作者:heroboyluck
针对日常开发的组件二次封装、方案设计实现。包括对el-table的动态渲染、单元格编辑;对于无缝滚动的实现,优化大数据量下的页面卡顿问题。
1. el-table实现动态渲染列
常规使用el-table
<template> <el-table ref="multipleTable" :data="data" > <el-table-column prop="family_name" label="姓名" align="center"> </el-table-column> <el-table-column label="操作" align="center"> <template slot-scope="scope"> <el-button @click="handleEdit(scope.row)" type="text" size="small" >用户信息</el-button > </template> </el-table-column> </el-table> </template> <script> export default { data(){ return { data:[] } }, methods:{ handleEdit(){} } } </script>
表格比较长时,需要些好多的el-table-column
;所以想通过动态渲染的方式循环渲染出列项。
官方给出的formatter
格式化列项输出的方法只能格式化文本。无法渲染VNode。
尝试通过v-html
绑定,报错h is not a function
// ... <el-table-column label="操作" align="center"> <template slot-scope="scope"> <div v-html="item.render(scope)"></div> </template> </el-table-column>
解决办法,通过render
方法提供CreateElement
函数。新创建一个组件RenderColumn
RenderColumn.vue
<script> export default { props:{ render:{ type:Function, default:()=>()=>{} }, scope:{ type:Object, default:()=>{} } }, render(h){ const { row, column, $index } = this.scope return this.render(h,row,column,$index) } } </script>
在渲染表格时调用,主要在于需要给render
方法传入CreateElement
方法。
<template> <el-table ref="multipleTable" :data="data" > <el-table-column v-for="item in columns" :label="item.lable" :prop="item.prop"> <template slot-scope="scope"> <render-column v-if="item.render" :render="item.render" :scope="scope" /> <span v-else>{{scope.row[item.prop]}}</span> </template> </el-table-column> </el-table> </template> <script> export default { data(){ let $this = this return { data:[], columns:[ { label:'姓名', prop:'name' }, { label:'操作', render(h,row,column){ return <el-button onClick={$this.handleEdit(row)} type="text" size="small" >用户信息</el-button> } } ] } }, methods:{ handleEdit(){} } } </script>
vue-cli
脚手架已经继承了JSX的语法,可以直接书写。
2. el-table实现单元格的编辑
实现单元格的编辑,实现编辑组件EditCell.vue
逻辑的核心点:
非编辑状态下,展示当前列项值,通过点击事件,单元格进入可编辑状态。并可通过
this.$refs.input.focus()
聚焦数据
el-input
主要在于处理完成输入、enter键后完成编辑状态。当完成编辑时,如果传入了校验函数。则需调用函数进行校验。并通过
el-popover
展示。
<template> <div class="edit-cell"> <el-popover :value="validateMsg !== ''" trigger="manual"> <div slot="reference"> <span v-if="!editable" @click="handleEditable" >{{ editValue }} <i class="el-icon-edit"></i> </span> <el-input ref="input" autofocus v-else v-model="editValue" @change="handleEditable" @blur="handleEditable" /> </div> <span style="color: #f5222d">{{ validateMsg }}</span> </el-popover> </div> </template> <script> export default { name: "edit-cell", props: { value: String, validator: { type: Function, default: () => null } }, data() { return { editValue: "", editable: false, validateMsg: "" }; }, mounted() { this.editValue = this.value; }, methods: { handleEditable() { if (this.editable && typeof this.validator === "function") { const err = this.validator(this.editValue); if (err) { this.validateMsg = err; return; } this.validateMsg = ""; } this.editable = !this.editable; if (this.editable) { this.$nextTick(() => { this.$refs.input.focus(); }); } this.$emit("change", this.editValue); } } }; </script>
如果要实现整行row
的编辑,可给每一行数据追加属性editable
,整合编辑时更改属性,切换为编辑状态。
切入编辑状态el-input
本来想通过autofocus
获取焦点的。但没有用,使用了ref组件内部的方法。
到此这篇关于el-table动态渲染列、可编辑单元格、虚拟无缝滚动的实现的文章就介绍到这了,更多相关el-table动态渲染列、可编辑单元格、虚拟无缝滚动内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
3. 实现虚拟无缝滚动seamlessScroll
使用过vue-seamless-scroll
,可实现数据的无缝滚动。但当数据量超过大几千时,页面就会变的很卡。通过看源代码实现,加入5000的数据量,需要渲染10000个DOM节点。
通过之前虚拟列表的思想,实现一个虚拟无缝滚动组件
实现滚动的主要API
transform:translate(0px,0px)
,在水平、垂直方向上进行平移数据列表window.requestAnimationFrame(()=>{})
在浏览器下次重绘时调用回调函数,通常为60次/s
实现的主要逻辑:
组件挂载或者数据
data
变化时进行数据初始化init()
init
方法用于调用数据切割滚动方法。其中一个参数virtual
用于显示控制如果数据量不大时,就没必要虚拟滚动了。在
move
方法中,通过每一帧的渲染更新,回调函数处理this.translateY -= this.speed
平移数据列表。在
splitData
中则处理数据切割,判断如果不需要虚拟滚动时,则加载展示所有的数据。随后监听了
translateY
的变化,用于处理虚拟列表的滚动分页逻辑/** * 如果平移的距离大于分页*每项的长度,进行数据滚动重置 **/ handleDataScorll() { if ( Math.abs(this.translateY) < this.pageOptions.pageSize * this.itemWidth ) { return; } // this.stop(); // 第一页已滚动完成 if (this.virtual) { this.splitData(); } this.translateY = 0; },
核心的JS逻辑,实现的相关方法。
export default { // ... mounted() { // 复制数据,数据仓 this.copyData = [...this.data]; // 切割数据 this.init(); }, computed: { boxStyle() { return { transform: `translate(0, ${this.translateY}px )`, }; }, total() { return this.data.length; }, }, watch: { data(newData) { this.copyData = [...newData]; this.init(); }, translateY() { this.handleDataScorll(); }, }, methods: { init() { if (!this.virtual) { // 非虚拟列表管滚动,则直接展示所有数据 this.pageOptions.pageSize = this.total; } if (this.total > 0) { this.splitData(); this.move(); } }, move() { this.stop(); this.animationFrame = requestAnimationFrame(() => { if (this.total > 0) { this.translateY -= this.speed; } this.move(); }); }, splitData() { if (!this.virtual) { this.preData = [...this.copyData]; this.nextData = [...this.copyData]; return; } // 只有在虚拟列表时,才调换数据位置 this.copyData = [...this.copyData, ...this.preData]; // pre this.preData = this.copyData.splice(0, this.pageOptions.pageSize); // next this.nextData = this.copyData.slice(0, this.pageOptions.pageSize); }, /** * 监听滚动的距离 */ handleDataScorll() { if ( Math.abs(this.translateY) < this.pageOptions.pageSize * this.itemWidth ) { return; } // this.stop(); // 第一页已滚动完成 if (this.virtual) { this.splitData(); } this.translateY = 0; }, stop() { if (this.animationFrame) { cancelAnimationFrame(this.animationFrame); } } }, };
示例中仅实现竖向滚动,横向滚动后续会追加props属性mode
进行逻辑处理。
4. 通过el-select实现联级选择
Element提供的Cascader,但设计师可能需要的是并排的多个下拉,进行控制。
主要的实现逻辑:
通过level指定联动选择的层级数量。通过循环渲染出
el-select
,还有最关键的实现分级数据, 从data中分级出每一级level数据。视图中则通过
optionsData[index]
获取数据
optionsData: function () { let arr = [[...this.data]] for (let id of this.selectedData) { if (!id) { arr.push([]) break } let data = arr[arr.length - 1].find((item) => item.id === id) if (!data) { arr.push([]) break } arr.push(data.children || []) } return arr }
最重要的是保证
selectedData
为层级深度长度的数组,这样才能渲染出正确数量的el-select每一层级的事件
change
通过index来更新选中的数据selelctData
<template> <div class="cascade-select-city"> <el-select placeholder="请选择" v-for="(val, index) in selectedData" :key="index" :value="selectedData[index]" @change="handleSelect($event, index)" > <el-option value="">请选择</el-option> <el-option v-for="item in optionsData[index]" :key="item.id" :label="item.name" :value="item.name" /> </el-select> </div> </template> <script> export default { name: 'cascade-select', props: { /** * 用于自定义级联数据 */ data: { type: Array, default: () => [] }, /** * 联动层级数量 */ level: { type: Number, default: 1 }, /** * 绑定数据 */ value: { type: Array, default: () => [] } }, data () { return { selectedData: new Array(this.level).fill('') } }, mounted () { }, watch: { value (val, oldVal) { if (JSON.stringify([val]) !== JSON.stringify([this.selectedData])) { // this.selectedData = [...val] } } }, computed: { /** * 处理层级数据 */ optionsData: function () { let arr = [[...this.data]] for (let id of this.selectedData) { if (!id) { arr.push([]) break } let data = arr[arr.length - 1].find((item) => item.AreaId === id) if (!data) { arr.push([]) break } arr.push(data.children || []) } return arr } }, methods: { /** * 处理联动的select */ handleSelect (selected, level) { // 更新值 this.selectedData = this.selectedData.map((val, index) => { if (index < level) { return val } return index === level ? selected : '' }) this.$emit('change', this.selectedData) } } } </script>
组件仅实现了data为静态数据时的逻辑处理,如果数据是动态的呢,比如异步加载联动数据。
到此这篇关于el-table动态渲染列、可编辑单元格、虚拟无缝滚动的实现的文章就介绍到这了,更多相关el-table动态渲染列、可编辑单元格、虚拟无缝滚动内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!