特性:
- 可以自定义拖拽过表格
- 可以点击某个表格,拖拽右下角小正方形进行任意方向选取单元格
- 支持选中某一行、列
- 支持监听@selectedGrids、@selectedDatas事件获取选中项的DOM对象和数据数组
- 支持props自定义显示label字段别名
sgExcelGrid源码
<template> <div :class="$options.name"> <div class="ruler-corner"></div> <div class="horizontal-ruler" :style="{ left: `${-rulerPosition.x}px` }"> <div class="tick" :hoverGrid="hoverGrid.x === A_Z[i]" @click="(hoverGrid = { x: A_Z[i] }), (mousedownGrid = {})" v-for="(a, i) in A_Z.slice(0, colCount_)" :key="i" > {{ a }} </div> </div> <div class="vertical-ruler" :style="{ top: `${-rulerPosition.y}px` }"> <div class="tick" :hoverGrid="hoverGrid.y === i" @click="(hoverGrid = { y: i }), (mousedownGrid = {})" v-for="(a, i) in Math.ceil(pageSize / colCount_)" :key="i" > {{ i + 1 }} </div> </div> <div class="grids-scroll" ref="scrollContainer"> <div class="grids" ref="dragContainer" :selectedGrids="selectedGrids.length > 0"> <div class="grid" :hoverGridX="hoverGrid.x === gridsData[i].x" :hoverGridY="hoverGrid.y === gridsData[i].y" :mousedownGrid=" mousedownGrid.x === gridsData[i].x && mousedownGrid.y === gridsData[i].y " :dragMove="isMouseDragMove" :type="a.strong ? 'primary' : ''" v-for="(a, i) in data" :key="i" @mouseover="hoverGrid = gridsData[i]" @click="clickGrid(i)" @mouseout="hoverGrid = {}" > <span :title="a[label]">{{ a[label] }}</span ><i class="el-icon-close" @click="del(a)" /> <div class="position-text" :title="`点击复制`" @click="$g.copy($g.stripHTML(getPositionText(gridsData[i])), true)" v-html="getPositionText(gridsData[i])" ></div> <div class="drag-select-btn" @mousedown.stop="clickResizeHandle"></div> </div> </div> </div> <!-- 拖拽 --> <sgDragMoveTile :data="dragMoveTileData" @scroll="scroll" /> </div> </template> <script> import sgDragMoveTile from "@/vue/components/admin/sgDragMoveTile"; export default { name: "sgExcelGrid", components: { sgDragMoveTile, }, data() { return { A_Z: [...Array(26)].map((v, i) => String.fromCharCode(i + 65)), hoverGrid: {}, //移入的宫格标记 mousedownGrid: {}, //点击的宫格标记 gridsData: [], //记录网格宫格状态 selectedGrids: [], //被选中的网格宫格DOM selectedDatas: [], //被选中的网格宫格数据 rulerPosition: { x: 0, y: 0 }, dragMoveTileData: {}, gridWidth: 200, gridHeight: 100, colCount_: 8, pageSize_: 100, isMouseDragMove: false, //鼠标拖拽选中移动 label: `label`, //显示文本字段名 }; }, props: [ "value", "props", "data", "pageSize", //每页显示多少个宫格 "colCount", //列数 ], computed: {}, watch: { props: { handler(newValue, oldValue) { if (newValue && Object.keys(newValue).length) { newValue.label && (this.label = newValue.label); } }, deep: true, //深度监听 immediate: true, //立即执行 }, pageSize: { handler(newValue, oldValue) { newValue && (this.pageSize_ = newValue); }, deep: true, //深度监听 immediate: true, //立即执行 }, data: { handler(newValue, oldValue) { this.init_gridsData(); }, deep: true, //深度监听 immediate: true, //立即执行 }, colCount: { handler(newValue, oldValue) { newValue && (this.colCount_ = newValue); this.$nextTick(() => { this.$el.style.setProperty("--gridWidth", `${this.gridWidth}px`); //js往css传递局部参数 this.$el.style.setProperty("--gridHeight", `${this.gridHeight}px`); //js往css传递局部参数 this.$el.style.setProperty( "--gridsWidth", `${this.colCount_ * this.gridWidth}px` ); //js往css传递局部参数 }); }, deep: true, //深度监听 immediate: true, //立即执行 }, selectedGrids: { handler(newValue, oldValue) { this.$emit(`selectedGrids`, newValue || []); }, deep: true, //深度监听 // immediate: true, //立即执行 }, selectedDatas: { handler(newValue, oldValue) { this.$emit(`selectedDatas`, newValue || []); }, deep: true, //深度监听 // immediate: true, //立即执行 }, }, created() {}, mounted() { this.init_grid_view(); this.addEvents(); }, destroyed() { this.removeEvents(); }, methods: { clickGrid(i) { (this.mousedownGrid = this.gridsData[i]), (this.hoverGrid = {}), this.resetSelectGrid(); this.selectedGrids = [this.gridsData[i]]; this.selectedDatas = [this.data[i]]; }, clickResizeHandle(e) { this.originRect = e.target.parentNode.getBoundingClientRect(); this.originRect.bottomRightX = this.originRect.x + this.originRect.width; //右下角坐标.x this.originRect.bottomRightY = this.originRect.y + this.originRect.height; //右下角坐标.y this.__addWindowEvents(); }, __addWindowEvents() { this.__removeWindowEvents(); addEventListener("mousemove", this.mousemove_window); addEventListener("mouseup", this.mouseup_window); }, __removeWindowEvents() { removeEventListener("mousemove", this.mousemove_window); removeEventListener("mouseup", this.mouseup_window); }, mousemove_window(e) { this.isMouseDragMove = true; let { x, y } = e; let minWidth = 0, minHeight = 0, maxWidth = innerWidth, maxHeight = innerHeight; x < 0 && (x = 0), y < 0 && (y = 0), x > maxWidth && (x = maxWidth), y > maxHeight && (y = maxHeight); let style = {}; style.x = this.originRect.x; style.y = this.originRect.y; style.width = x - this.originRect.x; style.width <= minWidth && ((style.width = Math.abs(style.width)), ((style.x = this.originRect.x - style.width), (style.width = style.width + this.originRect.width))); style.height = y - this.originRect.y; style.height <= minHeight && ((style.height = Math.abs(style.height)), ((style.y = this.originRect.y - style.height), (style.height = style.height + this.originRect.height))); style.width > maxWidth && (style.width = maxWidth); style.height > maxHeight && (style.height = maxHeight); this.calcRectGrid(style); }, mouseup_window(e) { this.isMouseDragMove = false; this.__removeWindowEvents(); }, resetAllGridStatus() { this.resetSelectGrid(); this.mousedownGrid = {}; }, resetSelectGrid(d) { this.selectedGrids = []; this.selectedDatas = []; let grids = this.$refs.dragContainer.querySelectorAll(`.grid`); grids.forEach((v) => { v.removeAttribute("selected-left"); v.removeAttribute("selected-top"); v.removeAttribute("selected-right"); v.removeAttribute("selected-bottom"); v.removeAttribute("selected"); }); }, // 计算是否选中格子 calcRectGrid(rect) { this.resetSelectGrid(); this.selectedGrids = this.getSelectedDoms({ targetDoms: this.$refs.dragContainer.querySelectorAll(`.grid`), rect, }); this.selectedGrids.forEach((grid) => { let grid_rect = grid.getBoundingClientRect(); let gridRectScreenWidth = grid_rect.x + grid_rect.width; let gridRectScreenHeight = grid_rect.y + grid_rect.height; grid_rect.x <= rect.x && rect.x < gridRectScreenWidth && grid.setAttribute("selected-left", true); grid_rect.y <= rect.y && rect.y < gridRectScreenHeight && grid.setAttribute("selected-top", true); let rectScreenWidth = rect.x + rect.width; let rectScreenHeight = rect.y + rect.height; grid_rect.x < rectScreenWidth && rectScreenWidth <= grid_rect.x + grid_rect.width && grid.setAttribute("selected-right", true); grid_rect.y < rectScreenHeight && rectScreenHeight <= grid_rect.y + grid_rect.height && grid.setAttribute("selected-bottom", true); grid.setAttribute("selected", true); }); }, // 获取被选中的DOM getSelectedDoms({ targetDoms, rect } = {}) { this.selectedDatas = []; return [...targetDoms].filter((targetDom, i) => { if (this.$g.isCrash(targetDom, rect)) { this.selectedDatas.push(this.data[i]); return targetDom; } }); // 获取被圈选的内容 }, // ---------------------------------------- del(d) { this.$emit(`del`, d); }, addEvents(d) { this.removeEvents(); this.__removeWindowEvents(); addEventListener("resize", this.resize); }, removeEvents(d) { removeEventListener("resize", this.resize); }, getPositionText(gridData) { return `<span>第${gridData.y + 1}行</span> <span>第${gridData.x}列</span>`; }, init_grid_view() { this.resize(); this.$nextTick(() => { this.init_sgDragMoveTile(); }); }, init_gridsData(d) { this.gridsData = [...Array(this.pageSize_)].map((v, i) => ({ x: this.A_Z[i % this.colCount_], y: Math.floor(i / this.colCount_), })); this.$nextTick(() => { this.resetAllGridStatus(); }); }, init_sgDragMoveTile() { this.dragMoveTileData = { scrollContainer: this.$refs.scrollContainer, dragContainer: this.$refs.dragContainer, }; }, resize(d) { let scrollContainer = this.$refs.scrollContainer; scrollContainer && this.scroll({ target: scrollContainer }); }, scroll(e) { this.rulerPosition = { x: e.target.scrollLeft, y: e.target.scrollTop, }; }, }, }; </script> <style lang="scss" scoped> .sgExcelGrid { /*禁止选中文本*/ user-select: none; overflow: hidden; $tickDis: 40px; $gridWidth: var(--gridWidth); $gridHeight: var(--gridHeight); $gridsWidth: var(--gridsWidth); $scrollbarWidth: 14px; width: 100%; height: 100%; position: relative; .ruler-corner { position: absolute; z-index: 2; left: 0; top: 0; width: $tickDis; height: $tickDis; box-sizing: border-box; border: 1px solid #ebeef5; border-right: none; border-bottom: none; background-color: #eff2f755; /*遮罩模糊*/ backdrop-filter: blur(5px); } .horizontal-ruler { position: absolute; z-index: 1; left: 0; top: 0; margin-left: $tickDis; display: flex; flex-wrap: nowrap; border-top: 1px solid #ebeef5; border-left: 1px solid #ebeef5; /*遮罩模糊*/ backdrop-filter: blur(5px); // box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); .tick { display: flex; justify-content: center; align-items: center; flex-shrink: 0; width: $gridWidth; height: $tickDis; box-sizing: border-box; border-top: 1px solid transparent; border-left: 1px solid transparent; border-bottom: 1px solid #ebeef5; border-right: 1px solid #ebeef5; font-family: DIN-Black; background-color: #eff2f755; &[hoverGrid] { border-left: 1px solid #409eff; border-right: 1px solid #409eff; background-color: #b3d8ff99; color: #409eff; } } } .vertical-ruler { position: absolute; z-index: 1; left: 0; top: 0; margin-top: $tickDis; display: flex; flex-wrap: wrap; flex-direction: column; border-top: 1px solid #ebeef5; border-left: 1px solid #ebeef5; /*遮罩模糊*/ backdrop-filter: blur(5px); // box-shadow: 2px 0 12px 0 rgba(0, 0, 0, 0.1); .tick { display: flex; justify-content: center; align-items: center; flex-shrink: 0; width: $tickDis; height: $gridHeight; box-sizing: border-box; border-top: 1px solid transparent; border-left: 1px solid transparent; border-bottom: 1px solid #ebeef5; border-right: 1px solid #ebeef5; font-family: DIN-Black; background-color: #eff2f7; background-color: #eff2f755; &[hoverGrid] { border-top: 1px solid #409eff; border-bottom: 1px solid #409eff; background-color: #b3d8ff99; color: #409eff; } } } .grids-scroll { width: calc(100% - #{$tickDis}); height: calc(100vh - 310px); box-sizing: border-box; overflow: auto; position: relative; margin: $tickDis 0 0 $tickDis; .grids { width: calc(#{$gridsWidth} + #{$scrollbarWidth}); min-height: calc(#{$gridHeight} + #{$scrollbarWidth}); overflow: auto; display: flex; flex-wrap: wrap; align-content: flex-start; box-sizing: border-box; border-top: 1px solid #ebeef5; border-left: 1px solid #ebeef5; .grid { display: flex; justify-content: center; align-items: center; width: $gridWidth; height: $gridHeight; padding: 20px; box-sizing: border-box; border-top: 1px solid transparent; border-left: 1px solid transparent; border-bottom: 1px solid #ebeef5; border-right: 1px solid #ebeef5; word-wrap: break-word; word-break: break-all; white-space: break-spaces; position: relative; span { /*多行省略号*/ overflow: hidden; word-break: break-all; white-space: break-spaces; display: -webkit-box; -webkit-box-orient: vertical; max-height: min-content; -webkit-line-clamp: 3; line-height: 1.2; } // 坐标文本 .position-text { position: absolute; height: 22px; z-index: 1; left: 0px; top: 0px; display: none; flex-wrap: nowrap; white-space: nowrap; align-items: center; color: white; background-color: #00000055; box-sizing: border-box; padding: 0 5px; border-radius: 0 0 8px 0; >>> span { font-size: 12px !important; } cursor: cell; &:hover { background-color: #409eff; color: white; } } // 删除 i.el-icon-close { z-index: 1; display: none; position: absolute; right: 0; top: 0; font-size: 12px !important; justify-content: center; align-items: center; color: white; background-color: #409eff; box-sizing: border-box; padding: 5px; border-radius: 0 0 0 8px; cursor: pointer; &:hover { background-color: #f56c6c; } } // 拖拽选区 .drag-select-btn { position: absolute; height: 9px; width: 9px; z-index: 1; right: -4.5px; bottom: -4.5px; display: none; box-sizing: border-box; border: 2px solid white; background-color: #f56c6c; cursor: crosshair; } &:nth-of-type(2n) { background-color: #eff2f755; } &[hoverGridX] { border-left: 1px solid #409eff; border-right: 1px solid #409eff; background-color: #f2f8fe; } &[hoverGridY] { border-top: 1px solid #409eff; border-bottom: 1px solid #409eff; background-color: #f2f8fe; } &[mousedownGrid] { z-index: 2; //让drag-select-btn在顶端 border: 1px solid #f56c6c; background-color: #f56c6c22; .position-text { background-color: #f56c6c66; color: white; &:hover { background-color: #f56c6c; } } i.el-icon-close { background-color: #f56c6c66; &:hover { background-color: #f56c6c; } } .drag-select-btn { display: block; } &:hover:not([dragMove]) { background-color: #f56c6c66; i, .position-text { display: flex; } } } &[selected] { z-index: 2; //让drag-select-btn在顶端 border-top: 1px solid transparent; border-left: 1px solid transparent; border-right: 1px solid #f56c6c22; border-bottom: 1px solid #f56c6c22; background-color: #f56c6c22; .position-text { background-color: #f56c6c66; color: white; &:hover { background-color: #f56c6c; } } i.el-icon-close { background-color: #f56c6c66; &:hover { background-color: #f56c6c; } } .drag-select-btn { display: none; } &:hover:not([dragMove]) { border: 1px solid #f56c6c; background-color: #f56c6c66 !important; } } &[selected-left] { border-left: 1px solid #f56c6c; } &[selected-top] { border-top: 1px solid #f56c6c; } &[selected-right] { border-right: 1px solid #f56c6c; } &[selected-bottom] { border-bottom: 1px solid #f56c6c; } &[selected-right][selected-bottom] { .drag-select-btn { display: block; } } &[mousedownGridX] { border-left: 1px solid #f56c6c; border-right: 1px solid #f56c6c; background-color: #f56c6c22; } &[mousedownGridY] { border-top: 1px solid #f56c6c; border-bottom: 1px solid #f56c6c; background-color: #f56c6c22; } &[dragMove] { } &:hover:not([mousedownGrid]):not([dragMove]) { background-color: #b3d8ff99; i, .position-text { display: flex; } } } &[selectedGrids] { .grid { &:hover:not([mousedownGrid]):not([dragMove]):not([selected]) { border: 1px solid #409eff; } } } } } } </style>
应用
<template> <sgExcelGrid :props="{ label: `MC` }" :data="gridDatas" :pageSize="pageSize" @del="delGrid" @selectedDatas="selectedDatas" /> </template> <script> import sgExcelGrid from "@/vue/components/admin/sgExcelGrid"; export default { components: { sgExcelGrid, }, data() { return { gridDatas: [ { ID: 1, value: 1, MC: "显示文本1" }, { ID: 2, value: 2, MC: "显示文本2" }, { ID: 3, value: 3, MC: "显示文本3" }, { ID: 4, value: 4, MC: "显示文本4" }, { ID: 5, value: 5, MC: "显示文本5" }, ], pageSize: 100, //每页显示多少个单元格 rectSelectIDS: [], //选中的ID数组 }; }, props: ["value"], computed: {}, watch: {}, created() {}, mounted() {}, destroyed() {}, methods: { selectedDatas(d) { this.rectSelectIDS = d.map((v) => v.ID); //获取选中项ID数组 }, delGrid(d) { //删除单元格 }, }, }; </script>
基于