0


Vue2虚拟列表,umy-ui封装

一、起因

1、需求: 由于业务需求在页面一次性展示较多数据,不低于上千,但是每条数据涉及样式较多,数据渲染过多就会导致页面卡顿
2、满足: 大量数据加载;表格功能:列显隐、列顺序调整、固定、筛选、排序;表格调整存储本地
3、技术框架: 若依、Element UI、vue2

二、umy-ui

1、umy-ui库中的table表格组件,它不造轮子。它改造了element-ui等等库的表格组件。只为了免费解决前端小伙伴的问题。

2、用前须知(这是关于表格的须知,你应该认真读完下面的内容)

 1. 表格解决卡顿问题,那么虚拟表格原理呢大概就是: 减少对DOM节点的渲染,通过滚动函数节流实现滚动后事件来动态渲染数据

 2. 基础表格其实就是element的表格的升级版,修改了ele的表格bug(如果你想使用个普通表格你无需安装其他库,就使用这个表格即可),你可以发现基础表格里面的示例没有配置:use-virtual 这个属性。

 3 基础表格没有使用use-virtual属性,代表表格数据不多,只想要一个普通的表格。如果你表格卡。请你关注下虚拟表格部分。

 4. 使用u-table 开启use-virtual虚拟可以支持微小的合并行|列 如2列 2行,支持多级头, 超过2行2列可能布局错乱,因为虚拟滚动的原理导致某些节点并未渲染。
 
     4.5 使用u-table 开启use-virtual不支持开展行,如果需要展开行,你是要虚拟表格部分的ux展开行!

 5. u-table不支持展开行,需要展开行使用ux-grid
 
 6. ux-grid解决列多 行多导致卡的情况, u-table解决行多的情况,不解决列多的情况(如你的列超过70+,你可能就需要使用ux-grid了,因为此时你需要把列也虚拟)

 7. 重点:虚拟表格集成了基础表格的东西(如属性/方法/事件)!

 8. 虚拟表格在本文档中呢, 意思就是解决了数据量多导致卡顿的情况! 基础表格在文档中呢,意思就是升级版的el-table(但是没解决数据多卡的情况)!

 9. 编辑型表格呢,是解决那种表格单元带有输入框或者选择时间等等的情况,而导致卡顿的场景!意思就是表格单元格具有一定的操作,单元格有自定义组件或者UI库组件等等

 10. 有了表格,怎么导出表格数据为excel并且带样式呢?,[请点击](https://github.com/livelyPeng/pl-export-excel)

三、安装引入

1.安装
推荐使用 npm 的方式安装,它能更好地和 webpack 打包工具配合使用

npminstall umy-ui

2.引入
main.js

// 引入umy-uiimport UmyUi from'umy-ui'

Vue.use(UmyUi);

三、封装

以下代码是基于若依框架封装的主代码,其余见附带资源中,对应表格中输入或展示形式可自行封装:

<script>exportdefault{name:"SuperUxTable",props:{// 数据value:{type:[Array],require:true,},// 字典dict:{type:[Object],require:true,},// 分页page:{type:[Object],require:false,},// 模板columns:{type:[Array],require:true,},// 是否显示序号index:{type: Boolean,default:false,},// 是否显示单选radio:{type: Boolean,default:false,},// 是否显示多选checkbox:{type: Boolean,default:false,},// 是否显示分页pagination:{type: Boolean,default:false,},// 是否列操作convenitentOperation:{type: Boolean,default:false,},// 是否禁止选择selectable:{type: Function,default:()=>{},},//storageKey:{type: String,},showSummary:{type: Boolean,default:false,},height:{type:[String, Number],require:false,},firstSummary:{type: Boolean,default:false,},},components:{ElDictTag:()=>import("@/components/DictTag/index.vue"),ElDraggable:()=>import("@/components/draggable/index.vue"),ElFilePreview:()=>import("@/components/file-preview/index.vue"),ElComputedInput:()=>import("@/components/computed-input/index.vue"),ElPopoverSelectV2:()=>import("@/components/popover-select-v2/index.vue"),ElPopoverMultipleSelectV2:()=>import("@/components/popover-select-v2/multiple.vue"),ElComputedInputV2:()=>import("@/components/computed-input-v2/index.vue"),ElPopoverTreeSelect:()=>import("@/components/popover-tree-select/index.vue"),ButtonHide:()=>import("./hide.vue"),ButtonFreeze:()=>import("./freeze.vue"),IconHide:()=>import("./once/hide.vue"),IconSort:()=>import("./once/sort.vue"),IconFreeze:()=>import("./once/freeze.vue"),IconFilter:()=>import("./once/filters.vue"),},data(){const{ columns, storageKey }=this.$props;const localColumns = localStorage.getItem(storageKey);const innerColumns =
      storageKey && localColumns
        ?JSON.parse(localColumns): columns.map(({ item, attr })=>({
            attr,item:{hidden:true,...item },}));return{innerColumns: innerColumns,rowKey:"id",// 选择selectData:[],selectState:false,// 过滤filterData:[],filterState:false,count:0,scrollTop:0,resizeHeight:0,};},computed:{innerValue:{get(){if(this.filterState){returnthis.filterData;}elseif(this.selectState){returnthis.selectData;}else{returnthis.$props.value;}},set(value){this.$emit("input", value);},},showColumns:{get(){returnthis.innerColumns.filter(({ item })=> item.hidden);},set(){},},filterRules:{get(){return Object.fromEntries(this.innerColumns
            .filter(({ item })=> item.filter &&!!item.filter.length).map(({ item })=>[item.key, item.filter]));},set(){},},tableHeight:{get(){let{ height }=this.$props;return height ? height :this.resizeHeight;},set(){},},},watch:{filterRules:{handler:function(newValue){functionmultiFilter(array, filters){const filterKeys = Object.keys(filters);// filters all elements passing the criteriareturn array.filter((item)=>{// dynamically validate all filter criteriareturn filterKeys.every((key)=>{//ignore when the filter is empty Anneif(!filters[key].length)returntrue;return!!~filters[key].indexOf(item[key]);});});}this.filterState =JSON.stringify(newValue)!=="{}";this.filterData =multiFilter(this.$props.value, newValue);},},value:{handler:function(newValue){if(this.value.length >0){this.$refs.superUxTable &&this.$refs.superUxTable.clearSelection();}},immediate:true,deep:true,},},directives:{// 使用局部注册指令的方式resize:{// 指令的名称bind(el, binding){// el为绑定的元素,binding为绑定给指令的对象let width ="",
          height ="";functionisReize(){const style = document.defaultView.getComputedStyle(el);if(width !== style.width || height !== style.height){
            binding.value();// 关键}
          width = style.width;
          height = style.height;}
        el.__vueSetInterval__ =setInterval(isReize,300);},unbind(el){clearInterval(el.__vueSetInterval__);},},},methods:{resize(){this.resizeHeight =
        document.getElementsByClassName("el-super-ux-table")[0].offsetHeight -55;},//onSelectionChange(value){this.selectData = value;this.$emit("row-select",this.selectData);},//onRowClick(row, column, event){const{ radio, checkbox }=this.$props;// 单选if(radio){this.$emit("row-select",[row]);}// 多选if(checkbox){this.$refs.superUxTable.toggleRowSelection([this.innerValue.find((item)=> item.id === row.id),]);}},// 宽度onWidth({ column }){this.innerColumns =this.innerColumns.map(({ item, attr })=>({
        attr,item:{...item,width: item.key === column.property ? column.resizeWidth : item.width,},}));if(this.$props.storageKey){
        localStorage.setItem(this.$props.storageKey,JSON.stringify(this.innerColumns));}},// 隐藏onHide(prop){this.$nextTick(()=>{this.$refs.superUxTable.doLayout();if(this.$props.storageKey){
          localStorage.setItem(this.$props.storageKey,JSON.stringify(this.innerColumns));}});},// 排序onSort(prop){const{ key, sort }= prop;
      console.log(key,"key", sort,"sort");this.$nextTick(()=>{this.$refs.superUxTable.sort(key, sort);this.$refs.superUxTable.doLayout();if(this.$props.storageKey){
          localStorage.setItem(this.$props.storageKey,JSON.stringify(this.innerColumns));}});},// 冻结onFreeze(){this.$nextTick(()=>{this.$refs.superUxTable.doLayout();if(this.$props.storageKey){
          localStorage.setItem(this.$props.storageKey,JSON.stringify(this.innerColumns));}this.count++;});},// 过滤onFilter(){this.$nextTick(()=>{this.$refs.superUxTable.doLayout();if(this.$props.storageKey){
          localStorage.setItem(this.$props.storageKey,JSON.stringify(this.innerColumns));}});},onFilters(value){const{item:{ key },attr:{ dictName },}= value;let dataList =[];const dict =this.dict.type[dictName];
      dataList = Array.from(newSet(this.innerValue.map((item)=> item[key]).filter((item)=> item))).map((item)=>({text: dictName
          ?(dict.find((dictItem)=> dictItem.value == item)||{}).label
          : item,value: item,}));return dataList;},// 继承el-table的MethodextendMethod(){const refMethod = Object.entries(this.$refs["superUxTable"]);for(const[key, value]of refMethod){if(!(key.includes("$")|| key.includes("_"))){this[key]= value;}}},getSummaries({ columns, data }){const means =[];// 合计let{ firstSummary }=this.$props;

      columns.forEach((column, columnIndex)=>{if(!firstSummary && columnIndex ===0){
          means.push("合计");}else{const values = data.map((item)=>Number(item[column.property]));let sumColumn =this.showColumns.filter(({ item, attr })=> attr.isSummary && item.key === column.property
          );// 合计// if (!values.every(value => isNaN(value))) {if(sumColumn.length){
            means[columnIndex]= values.reduce((prev, curr)=>{const value =Number(curr);if(!isNaN(value)){return prev + curr;}else{return prev;}},0);

            means[columnIndex]= means[columnIndex].toFixed(2);}else{
            means[columnIndex]="";}}});// sums[index] = sums[index] && sums[index].toFixed(2); // 保留2位小数,解决小数合计列return[means];},},created(){},mounted(){this.extendMethod();},updated(){this.$nextTick(()=>{this.$refs.superUxTable.doLayout();});},destroyed(){},};</script><template><divclass="el-super-ux-table":key="count"v-resize="resize"><ux-gridborderrow-keyuse-virtualkeep-sourceshow-overflowbeautify-tableref="superUxTable"v-bind="$attrs":height="tableHeight"v-on="$listeners":data="innerValue":show-summary="showSummary":summary-method="getSummaries"@row-click="onRowClick"@header-dragend="onWidth"@selection-change="onSelectionChange":header-row-style="{
        color: '#515a6e',
      }"style="flex: 1"><!-- 多选 --><ux-table-columnv-if="checkbox"fixed="left"width="50"align="center"type="checkbox"resizablereserve-selection:column-key="rowKey"></ux-table-column><!-- 序号 --><ux-table-columnv-if="index"fixed="left"width="50"title="序号"type="index"align="center"class="is-index"resizable></ux-table-column><ux-table-columnv-for="({ item, attr }, index) in showColumns":key="item.key + index":field="item.key":title="item.title":fixed="item.fixed ? 'left' : undefined":width="item.width || 180":sortable="item.sortabled"resizableshow-overflow><templateslot="header"slot-scope="scope"><template><spanv-if="item.require"style="color: #ff4949">*</span><span:style="{
                color:
                  item.sort ||
                  item.fixed ||
                  (item.filter && !!item.filter.length)
                    ? '#1890ff'
                    : '',
              }">
              {{ item.title }}
            </span><template><!-- <icon-sort
                v-if="item.sortabled"
                v-model="item.sort"
                @sort="onSort(item)"
              ></icon-sort> --><icon-freezev-if="item.fixedabled"v-model="item.fixed"@freeze="onFreeze"></icon-freeze><icon-filterv-if="item.filterabled"v-model="item.filter":filters="onFilters({ item, attr })"@filter="onFilter"></icon-filter><icon-hidev-if="item.hiddenabled"v-model="item.hidden"@hide="onHide"></icon-hide></template></template></template><templateslot-scope="scope"><slot:name="item.key"v-bind="scope":item="item":attr="attr"><templatev-if="attr.is"><componentv-if="attr.is === 'el-dict-tag'"v-bind="attr":size="$attrs.size":value="scope.row[item.key]":options="dict.type[attr.dictName]"></component><componentv-else-if="attr.is === 'el-popover-select-v2'"v-bind="attr"v-model="scope.row[item.key]":title="item.title":size="$attrs.size":source.sync="scope.row"></component><componentv-else-if="attr.is === 'el-popover-multiple-select-v2'"v-bind="attr"v-model="scope.row[item.key]":title="item.title":size="$attrs.size":source.sync="scope.row"></component><componentv-else-if="attr.is === 'el-select'"v-bind="attr"v-model="scope.row[item.key]":size="$attrs.size"><template><el-optionv-for="item in dict.type[attr.dictName]":key="item.value":label="item.label":value="item.value"></el-option></template></component><componentv-elsev-bind="attr"v-model="scope.row[item.key]":size="$attrs.size"style="width: 100%"></component></template><templatev-else><componentv-if="attr.formatter"is="span">{{
                attr.formatter(scope.row)
              }}</component><componentv-elseis="span">{{
                scope.row[item.key] || "--"
              }}</component></template></slot></template></ux-table-column><slot></slot><!-- </el-table> --></ux-grid><divstyle="
        height: 50px;
        display: flex;
        justify-content: space-between;
        align-items: center;
      ":style="{
        height: checkbox || pagination ? '50px' : '0px',
      }"><divclass="mr-4"><templatev-if="convenitentOperation"><button-hidev-model="innerColumns"@change="onHide"></button-hide></template></div><paginationv-if="pagination"v-show="!selectState":total="page.total":page.sync="page.pageNum":limit.sync="page.pageSize"@pagination="$emit('pagination', { ...$event })"style="height: 32px;padding: 0 !important;flex: 1;overflow-x: auto"/></div></div></template><stylelang="scss"scoped>.el-super-ux-table{position: relative;display: flex;flex: 1;flex-direction: column;overflow: auto;}::v-deep.el-super-ux-table .elx-cell{word-break: keep-all;white-space: nowrap;.icon-sort{display: none;}&:hover .icon-sort{display: inline-block;}.icon-freeze{display: none;}&:hover .icon-freeze{display: inline-block;}.icon-filter{display: none;}&:hover .icon-filter{display: inline-block;}.icon-hide{display: none;}&:hover .icon-hide{display: inline-block;}.elx-cell--sort{display: none;}&:hover .elx-cell--sort{display: inline-block;}}::v-deep.uxbeautifyTableClass
  .elx-header--column
  .elx-resizable.is--line:before{height: 100%;background-color: #dfe6ec;}</style>

四、实例

<el-super-ux-tableindexv-model="materialInfo[item.key]":dict="dict":ref="tabName":columns="columns":size="$attrs.size":height="420"><!-- 判断是否禁用 --><templateslot="drug"slot-scope="scope"><componentv-bind="scope.attr"v-model="scope.row[scope.item.key]":size="$attrs.size":source.sync="scope.row":disabled="!(scope.row.medicineMaterial === '0')"><el-optionv-for="item in dict.type[scope.attr.dictName]":key="item.value":label="item.label":value="item.value"></el-option></component></template><templateslot="registrationNo"slot-scope="scope"><componentv-bind="scope.attr"v-model="scope.row[scope.item.key]":size="$attrs.size":source.sync="scope.row":disabled="!(scope.row.medicineMaterial === '0')"></component></template><ux-table-columnfixed="right"title="操作"width="120"align="center"><templateslot="header"slot-scope="scope"><el-buttontype="text":size="$attrs.size"@click="useRowAdd(tabName)">
         增行
       </el-button></template><templateslot-scope="scope"><el-buttontype="text":size="$attrs.size"@click.native.prevent="useRowRemove(tabName, scope)">
         删除
       </el-button><AmendantRecordv-if="
           tabName === 'materialBasic' &&
           addType === 'edit' &&
           scope.row.id
         "v-model="scope.row"></AmendantRecord></template></ux-table-column></el-super-ux-table>
标签: vue umy-ui 虚拟列表

本文转载自: https://blog.csdn.net/LuoYi_ly_/article/details/134325285
版权归原作者 汪叽家的兔子羡 所有, 如有侵权,请联系我们删除。

“Vue2虚拟列表,umy-ui封装”的评论:

还没有评论