UNPKG

vxe-table-select-area

Version:

一个基于 vxe-table 的可区域选中复制、粘贴的组件

295 lines (293 loc) 8.92 kB
import XEUtils from 'xe-utils' import GlobalConfig from '../../v-x-e-table/src/conf' import vSize from '../../mixins/size' import { createResizeEvent } from '../../tools/resize' import { GlobalEvent } from '../../tools/event' import { browse } from '../../tools/dom' import VxeLoading from '../../loading/index' export default { name: 'VxeList', mixins: [vSize], props: { data: Array, height: [Number, String], maxHeight: [Number, String], loading: Boolean, className: [String, Function], size: { type: String, default: () => GlobalConfig.list.size || GlobalConfig.size }, autoResize: { type: Boolean, default: () => GlobalConfig.list.autoResize }, syncResize: [Boolean, String, Number], scrollY: Object }, data () { return { scrollYLoad: false, bodyHeight: 0, topSpaceHeight: 0, items: [] } }, computed: { sYOpts () { return Object.assign({}, GlobalConfig.list.scrollY, this.scrollY) }, styles () { const { height, maxHeight } = this const style = {} if (height) { style.height = isNaN(height) ? height : `${height}px` } else if (maxHeight) { style.height = 'auto' style.maxHeight = isNaN(maxHeight) ? maxHeight : `${maxHeight}px` } return style } }, watch: { data (value) { this.loadData(value) }, syncResize (value) { if (value) { this.recalculate() this.$nextTick(() => setTimeout(() => this.recalculate())) } } }, created () { Object.assign(this, { fullData: [], lastScrollLeft: 0, lastScrollTop: 0, scrollYStore: { startIndex: 0, endIndex: 0, visibleSize: 0 } }) this.loadData(this.data) GlobalEvent.on(this, 'resize', this.handleGlobalResizeEvent) }, mounted () { if (this.autoResize) { const resizeObserver = createResizeEvent(() => this.recalculate()) resizeObserver.observe(this.$el) this.$resize = resizeObserver } }, activated () { this.recalculate().then(() => this.refreshScroll()) }, beforeDestroy () { if (this.$resize) { this.$resize.disconnect() } }, destroyed () { GlobalEvent.off(this, 'resize') }, render (h) { const { $scopedSlots, styles, bodyHeight, topSpaceHeight, items, className, loading } = this return h('div', { class: ['vxe-list', className ? (XEUtils.isFunction(className) ? className({ $list: this }) : className) : '', { 'is--loading': loading }] }, [ h('div', { ref: 'virtualWrapper', class: 'vxe-list--virtual-wrapper', style: styles, on: { scroll: this.scrollEvent } }, [ h('div', { ref: 'ySpace', class: 'vxe-list--y-space', style: { height: bodyHeight ? `${bodyHeight}px` : '' } }), h('div', { ref: 'virtualBody', class: 'vxe-list--body', style: { marginTop: topSpaceHeight ? `${topSpaceHeight}px` : '' } }, $scopedSlots.default ? $scopedSlots.default.call(this, { items, $list: this }, h) : []) ]), /** * 加载中 */ h(VxeLoading, { class: 'vxe-list--loading', props: { value: loading } }) ]) }, methods: { getParentElem () { return this.$el.parentNode }, /** * 加载数据 * @param {Array} datas 数据 */ loadData (datas) { const { sYOpts, scrollYStore } = this const fullData = datas || [] Object.assign(scrollYStore, { startIndex: 0, endIndex: 1, visibleSize: 0 }) this.fullData = fullData // 如果gt为0,则总是启用 this.scrollYLoad = sYOpts.enabled && sYOpts.gt > -1 && (sYOpts.gt === 0 || sYOpts.gt <= fullData.length) this.handleData() return this.computeScrollLoad().then(() => { this.refreshScroll() }) }, /** * 重新加载数据 * @param {Array} datas 数据 */ reloadData (datas) { this.clearScroll() return this.loadData(datas) }, handleData () { const { fullData, scrollYLoad, scrollYStore } = this this.items = scrollYLoad ? fullData.slice(scrollYStore.startIndex, scrollYStore.endIndex) : fullData.slice(0) return this.$nextTick() }, /** * 重新计算列表 */ recalculate () { const { $el } = this if ($el.clientWidth && $el.clientHeight) { return this.computeScrollLoad() } return Promise.resolve() }, /** * 清除滚动条 */ clearScroll () { const scrollBodyElem = this.$refs.virtualWrapper if (scrollBodyElem) { scrollBodyElem.scrollTop = 0 } return this.$nextTick() }, /** * 刷新滚动条 */ refreshScroll () { const { lastScrollLeft, lastScrollTop } = this return this.clearScroll().then(() => { if (lastScrollLeft || lastScrollTop) { this.lastScrollLeft = 0 this.lastScrollTop = 0 return this.scrollTo(lastScrollLeft, lastScrollTop) } }) }, /** * 如果有滚动条,则滚动到对应的位置 * @param {Number} scrollLeft 左距离 * @param {Number} scrollTop 上距离 */ scrollTo (scrollLeft, scrollTop) { const scrollBodyElem = this.$refs.virtualWrapper if (XEUtils.isNumber(scrollLeft)) { scrollBodyElem.scrollLeft = scrollLeft } if (XEUtils.isNumber(scrollTop)) { scrollBodyElem.scrollTop = scrollTop } if (this.scrollYLoad) { return new Promise(resolve => setTimeout(() => resolve(this.$nextTick()), 50)) } return this.$nextTick() }, computeScrollLoad () { return this.$nextTick().then(() => { const { $refs, sYOpts, scrollYLoad, scrollYStore } = this const { virtualWrapper: virtualWrapperElem, virtualBody: virtualBodyElem } = $refs let rowHeight = 0 let firstItemElem if (virtualBodyElem) { if (sYOpts.sItem) { firstItemElem = virtualBodyElem.querySelector(sYOpts.sItem) } if (!firstItemElem) { firstItemElem = virtualBodyElem.children[0] } } if (firstItemElem) { rowHeight = firstItemElem.offsetHeight } rowHeight = Math.max(20, rowHeight) scrollYStore.rowHeight = rowHeight // 计算 Y 逻辑 if (scrollYLoad) { const visibleYSize = Math.max(8, Math.ceil(virtualWrapperElem.clientHeight / rowHeight)) const offsetYSize = sYOpts.oSize ? XEUtils.toNumber(sYOpts.oSize) : browse.msie ? 20 : (browse.edge ? 10 : 0) scrollYStore.offsetSize = offsetYSize scrollYStore.visibleSize = visibleYSize scrollYStore.endIndex = Math.max(scrollYStore.startIndex, visibleYSize + offsetYSize, scrollYStore.endIndex) this.updateYData() } else { this.updateYSpace() } this.rowHeight = rowHeight }) }, scrollEvent (evnt) { const scrollBodyElem = evnt.target const scrollTop = scrollBodyElem.scrollTop const scrollLeft = scrollBodyElem.scrollLeft const isX = scrollLeft !== this.lastScrollLeft const isY = scrollTop !== this.lastScrollTop this.lastScrollTop = scrollTop this.lastScrollLeft = scrollLeft if (this.scrollYLoad) { this.loadYData(evnt) } this.$emit('scroll', { scrollLeft, scrollTop, isX, isY, $event: evnt }) }, loadYData (evnt) { const { scrollYStore } = this const { startIndex, endIndex, visibleSize, offsetSize, rowHeight } = scrollYStore const scrollBodyElem = evnt.target const scrollTop = scrollBodyElem.scrollTop const toVisibleIndex = Math.floor(scrollTop / rowHeight) const offsetStartIndex = Math.max(0, toVisibleIndex - 1 - offsetSize) const offsetEndIndex = toVisibleIndex + visibleSize + offsetSize if (toVisibleIndex <= startIndex || toVisibleIndex >= endIndex - visibleSize - 1) { if (startIndex !== offsetStartIndex || endIndex !== offsetEndIndex) { scrollYStore.startIndex = offsetStartIndex scrollYStore.endIndex = offsetEndIndex this.updateYData() } } }, updateYData () { this.handleData() this.updateYSpace() }, updateYSpace () { const { scrollYStore, scrollYLoad, fullData } = this this.bodyHeight = scrollYLoad ? fullData.length * scrollYStore.rowHeight : 0 this.topSpaceHeight = scrollYLoad ? Math.max(scrollYStore.startIndex * scrollYStore.rowHeight, 0) : 0 }, handleGlobalResizeEvent () { this.recalculate() } } }