赞
踩
1、需求: 由于业务需求在页面一次性展示较多数据,不低于上千,但是每条数据涉及样式较多,数据渲染过多就会导致页面卡顿
2、满足: 大量数据加载;表格功能:列显隐、列顺序调整、固定、筛选、排序;表格调整存储本地
3、技术框架: 若依、Element UI、vue2
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 打包工具配合使用
npm install umy-ui
2.引入
main.js
// 引入umy-ui
import UmyUi from 'umy-ui'
Vue.use(UmyUi);
以下代码是基于若依框架封装的主代码,其余见附带资源中,对应表格中输入或展示形式可自行封装:
<script> export default { 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) { return this.filterData; } else if (this.selectState) { return this.selectData; } else { return this.$props.value; } }, set(value) { this.$emit("input", value); }, }, showColumns: { get() { return this.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) { function multiFilter(array, filters) { const filterKeys = Object.keys(filters); // filters all elements passing the criteria return array.filter((item) => { // dynamically validate all filter criteria return filterKeys.every((key) => { //ignore when the filter is empty Anne if (!filters[key].length) return true; 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 = ""; function isReize() { 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( new Set(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的Method extendMethod() { 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> <div class="el-super-ux-table" :key="count" v-resize="resize"> <ux-grid border row-key use-virtual keep-source show-overflow beautify-table ref="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-column v-if="checkbox" fixed="left" width="50" align="center" type="checkbox" resizable reserve-selection :column-key="rowKey" ></ux-table-column> <!-- 序号 --> <ux-table-column v-if="index" fixed="left" width="50" title="序号" type="index" align="center" class="is-index" resizable ></ux-table-column> <ux-table-column v-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" resizable show-overflow > <template slot="header" slot-scope="scope"> <template> <span v-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-freeze v-if="item.fixedabled" v-model="item.fixed" @freeze="onFreeze" ></icon-freeze> <icon-filter v-if="item.filterabled" v-model="item.filter" :filters="onFilters({ item, attr })" @filter="onFilter" ></icon-filter> <icon-hide v-if="item.hiddenabled" v-model="item.hidden" @hide="onHide" ></icon-hide> </template> </template> </template> <template slot-scope="scope"> <slot :name="item.key" v-bind="scope" :item="item" :attr="attr"> <template v-if="attr.is"> <component v-if="attr.is === 'el-dict-tag'" v-bind="attr" :size="$attrs.size" :value="scope.row[item.key]" :options="dict.type[attr.dictName]" ></component> <component v-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> <component v-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> <component v-else-if="attr.is === 'el-select'" v-bind="attr" v-model="scope.row[item.key]" :size="$attrs.size" > <template> <el-option v-for="item in dict.type[attr.dictName]" :key="item.value" :label="item.label" :value="item.value" > </el-option> </template> </component> <component v-else v-bind="attr" v-model="scope.row[item.key]" :size="$attrs.size" style="width: 100%" > </component ></template> <template v-else> <component v-if="attr.formatter" is="span">{{ attr.formatter(scope.row) }}</component> <component v-else is="span">{{ scope.row[item.key] || "--" }}</component> </template> </slot> </template> </ux-table-column> <slot></slot> <!-- </el-table> --> </ux-grid> <div style=" height: 50px; display: flex; justify-content: space-between; align-items: center; " :style="{ height: checkbox || pagination ? '50px' : '0px', }" > <div class="mr-4"> <template v-if="convenitentOperation"> <button-hide v-model="innerColumns" @change="onHide"></button-hide> </template> </div> <pagination v-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> <style lang="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-table index v-model="materialInfo[item.key]" :dict="dict" :ref="tabName" :columns="columns" :size="$attrs.size" :height="420" > <!-- 判断是否禁用 --> <template slot="drug" slot-scope="scope"> <component v-bind="scope.attr" v-model="scope.row[scope.item.key]" :size="$attrs.size" :source.sync="scope.row" :disabled="!(scope.row.medicineMaterial === '0')" > <el-option v-for="item in dict.type[scope.attr.dictName]" :key="item.value" :label="item.label" :value="item.value" > </el-option> </component> </template> <template slot="registrationNo" slot-scope="scope"> <component v-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-column fixed="right" title="操作" width="120" align="center" > <template slot="header" slot-scope="scope"> <el-button type="text" :size="$attrs.size" @click="useRowAdd(tabName)" > 增行 </el-button> </template> <template slot-scope="scope"> <el-button type="text" :size="$attrs.size" @click.native.prevent="useRowRemove(tabName, scope)" > 删除 </el-button> <AmendantRecord v-if=" tabName === 'materialBasic' && addType === 'edit' && scope.row.id " v-model="scope.row" ></AmendantRecord> </template> </ux-table-column> </el-super-ux-table>
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。