<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; 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="60" 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" 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; } } ::v-deep.uxbeautifyTableClass .elx-header--column .elx-resizable.is--line:before { height: 100%; background-color: #dfe6ec; } </style>