当前位置:   article > 正文

【sgExcelGrid】自定义组件:简单模拟Excel表格拖拽、选中单元格、横行、纵列、拖拽圈选等操作

【sgExcelGrid】自定义组件:简单模拟Excel表格拖拽、选中单元格、横行、纵列、拖拽圈选等操作

特性:

  1. 可以自定义拖拽过表格
  2. 可以点击某个表格,拖拽右下角小正方形进行任意方向选取单元格
  3. 支持选中某一行、列
  4. 支持监听@selectedGrids、@selectedDatas事件获取选中项的DOM对象和数据数组
  5. 支持props自定义显示label字段别名

 sgExcelGrid源码

  1. <template>
  2. <div :class="$options.name">
  3. <div class="ruler-corner"></div>
  4. <div class="horizontal-ruler" :style="{ left: `${-rulerPosition.x}px` }">
  5. <div
  6. class="tick"
  7. :hoverGrid="hoverGrid.x === A_Z[i]"
  8. @click="(hoverGrid = { x: A_Z[i] }), (mousedownGrid = {})"
  9. v-for="(a, i) in A_Z.slice(0, colCount_)"
  10. :key="i"
  11. >
  12. {{ a }}
  13. </div>
  14. </div>
  15. <div class="vertical-ruler" :style="{ top: `${-rulerPosition.y}px` }">
  16. <div
  17. class="tick"
  18. :hoverGrid="hoverGrid.y === i"
  19. @click="(hoverGrid = { y: i }), (mousedownGrid = {})"
  20. v-for="(a, i) in Math.ceil(pageSize / colCount_)"
  21. :key="i"
  22. >
  23. {{ i + 1 }}
  24. </div>
  25. </div>
  26. <div class="grids-scroll" ref="scrollContainer">
  27. <div class="grids" ref="dragContainer" :selectedGrids="selectedGrids.length > 0">
  28. <div
  29. class="grid"
  30. :hoverGridX="hoverGrid.x === gridsData[i].x"
  31. :hoverGridY="hoverGrid.y === gridsData[i].y"
  32. :mousedownGrid="
  33. mousedownGrid.x === gridsData[i].x && mousedownGrid.y === gridsData[i].y
  34. "
  35. :dragMove="isMouseDragMove"
  36. :type="a.strong ? 'primary' : ''"
  37. v-for="(a, i) in data"
  38. :key="i"
  39. @mouseover="hoverGrid = gridsData[i]"
  40. @click="clickGrid(i)"
  41. @mouseout="hoverGrid = {}"
  42. >
  43. <span :title="a[label]">{{ a[label] }}</span
  44. ><i class="el-icon-close" @click="del(a)" />
  45. <div
  46. class="position-text"
  47. :title="`点击复制`"
  48. @click="$g.copy($g.stripHTML(getPositionText(gridsData[i])), true)"
  49. v-html="getPositionText(gridsData[i])"
  50. ></div>
  51. <div class="drag-select-btn" @mousedown.stop="clickResizeHandle"></div>
  52. </div>
  53. </div>
  54. </div>
  55. <!-- 拖拽 -->
  56. <sgDragMoveTile :data="dragMoveTileData" @scroll="scroll" />
  57. </div>
  58. </template>
  59. <script>
  60. import sgDragMoveTile from "@/vue/components/admin/sgDragMoveTile";
  61. export default {
  62. name: "sgExcelGrid",
  63. components: {
  64. sgDragMoveTile,
  65. },
  66. data() {
  67. return {
  68. A_Z: [...Array(26)].map((v, i) => String.fromCharCode(i + 65)),
  69. hoverGrid: {}, //移入的宫格标记
  70. mousedownGrid: {}, //点击的宫格标记
  71. gridsData: [], //记录网格宫格状态
  72. selectedGrids: [], //被选中的网格宫格DOM
  73. selectedDatas: [], //被选中的网格宫格数据
  74. rulerPosition: { x: 0, y: 0 },
  75. dragMoveTileData: {},
  76. gridWidth: 200,
  77. gridHeight: 100,
  78. colCount_: 8,
  79. pageSize_: 100,
  80. isMouseDragMove: false, //鼠标拖拽选中移动
  81. label: `label`, //显示文本字段名
  82. };
  83. },
  84. props: [
  85. "value",
  86. "props",
  87. "data",
  88. "pageSize", //每页显示多少个宫格
  89. "colCount", //列数
  90. ],
  91. computed: {},
  92. watch: {
  93. props: {
  94. handler(newValue, oldValue) {
  95. if (newValue && Object.keys(newValue).length) {
  96. newValue.label && (this.label = newValue.label);
  97. }
  98. },
  99. deep: true, //深度监听
  100. immediate: true, //立即执行
  101. },
  102. pageSize: {
  103. handler(newValue, oldValue) {
  104. newValue && (this.pageSize_ = newValue);
  105. },
  106. deep: true, //深度监听
  107. immediate: true, //立即执行
  108. },
  109. data: {
  110. handler(newValue, oldValue) {
  111. this.init_gridsData();
  112. },
  113. deep: true, //深度监听
  114. immediate: true, //立即执行
  115. },
  116. colCount: {
  117. handler(newValue, oldValue) {
  118. newValue && (this.colCount_ = newValue);
  119. this.$nextTick(() => {
  120. this.$el.style.setProperty("--gridWidth", `${this.gridWidth}px`); //js往css传递局部参数
  121. this.$el.style.setProperty("--gridHeight", `${this.gridHeight}px`); //js往css传递局部参数
  122. this.$el.style.setProperty(
  123. "--gridsWidth",
  124. `${this.colCount_ * this.gridWidth}px`
  125. ); //js往css传递局部参数
  126. });
  127. },
  128. deep: true, //深度监听
  129. immediate: true, //立即执行
  130. },
  131. selectedGrids: {
  132. handler(newValue, oldValue) {
  133. this.$emit(`selectedGrids`, newValue || []);
  134. },
  135. deep: true, //深度监听
  136. // immediate: true, //立即执行
  137. },
  138. selectedDatas: {
  139. handler(newValue, oldValue) {
  140. this.$emit(`selectedDatas`, newValue || []);
  141. },
  142. deep: true, //深度监听
  143. // immediate: true, //立即执行
  144. },
  145. },
  146. created() {},
  147. mounted() {
  148. this.init_grid_view();
  149. this.addEvents();
  150. },
  151. destroyed() {
  152. this.removeEvents();
  153. },
  154. methods: {
  155. clickGrid(i) {
  156. (this.mousedownGrid = this.gridsData[i]),
  157. (this.hoverGrid = {}),
  158. this.resetSelectGrid();
  159. this.selectedGrids = [this.gridsData[i]];
  160. this.selectedDatas = [this.data[i]];
  161. },
  162. clickResizeHandle(e) {
  163. this.originRect = e.target.parentNode.getBoundingClientRect();
  164. this.originRect.bottomRightX = this.originRect.x + this.originRect.width; //右下角坐标.x
  165. this.originRect.bottomRightY = this.originRect.y + this.originRect.height; //右下角坐标.y
  166. this.__addWindowEvents();
  167. },
  168. __addWindowEvents() {
  169. this.__removeWindowEvents();
  170. addEventListener("mousemove", this.mousemove_window);
  171. addEventListener("mouseup", this.mouseup_window);
  172. },
  173. __removeWindowEvents() {
  174. removeEventListener("mousemove", this.mousemove_window);
  175. removeEventListener("mouseup", this.mouseup_window);
  176. },
  177. mousemove_window(e) {
  178. this.isMouseDragMove = true;
  179. let { x, y } = e;
  180. let minWidth = 0,
  181. minHeight = 0,
  182. maxWidth = innerWidth,
  183. maxHeight = innerHeight;
  184. x < 0 && (x = 0),
  185. y < 0 && (y = 0),
  186. x > maxWidth && (x = maxWidth),
  187. y > maxHeight && (y = maxHeight);
  188. let style = {};
  189. style.x = this.originRect.x;
  190. style.y = this.originRect.y;
  191. style.width = x - this.originRect.x;
  192. style.width <= minWidth &&
  193. ((style.width = Math.abs(style.width)),
  194. ((style.x = this.originRect.x - style.width),
  195. (style.width = style.width + this.originRect.width)));
  196. style.height = y - this.originRect.y;
  197. style.height <= minHeight &&
  198. ((style.height = Math.abs(style.height)),
  199. ((style.y = this.originRect.y - style.height),
  200. (style.height = style.height + this.originRect.height)));
  201. style.width > maxWidth && (style.width = maxWidth);
  202. style.height > maxHeight && (style.height = maxHeight);
  203. this.calcRectGrid(style);
  204. },
  205. mouseup_window(e) {
  206. this.isMouseDragMove = false;
  207. this.__removeWindowEvents();
  208. },
  209. resetAllGridStatus() {
  210. this.resetSelectGrid();
  211. this.mousedownGrid = {};
  212. },
  213. resetSelectGrid(d) {
  214. this.selectedGrids = [];
  215. this.selectedDatas = [];
  216. let grids = this.$refs.dragContainer.querySelectorAll(`.grid`);
  217. grids.forEach((v) => {
  218. v.removeAttribute("selected-left");
  219. v.removeAttribute("selected-top");
  220. v.removeAttribute("selected-right");
  221. v.removeAttribute("selected-bottom");
  222. v.removeAttribute("selected");
  223. });
  224. },
  225. // 计算是否选中格子
  226. calcRectGrid(rect) {
  227. this.resetSelectGrid();
  228. this.selectedGrids = this.getSelectedDoms({
  229. targetDoms: this.$refs.dragContainer.querySelectorAll(`.grid`),
  230. rect,
  231. });
  232. this.selectedGrids.forEach((grid) => {
  233. let grid_rect = grid.getBoundingClientRect();
  234. let gridRectScreenWidth = grid_rect.x + grid_rect.width;
  235. let gridRectScreenHeight = grid_rect.y + grid_rect.height;
  236. grid_rect.x <= rect.x &&
  237. rect.x < gridRectScreenWidth &&
  238. grid.setAttribute("selected-left", true);
  239. grid_rect.y <= rect.y &&
  240. rect.y < gridRectScreenHeight &&
  241. grid.setAttribute("selected-top", true);
  242. let rectScreenWidth = rect.x + rect.width;
  243. let rectScreenHeight = rect.y + rect.height;
  244. grid_rect.x < rectScreenWidth &&
  245. rectScreenWidth <= grid_rect.x + grid_rect.width &&
  246. grid.setAttribute("selected-right", true);
  247. grid_rect.y < rectScreenHeight &&
  248. rectScreenHeight <= grid_rect.y + grid_rect.height &&
  249. grid.setAttribute("selected-bottom", true);
  250. grid.setAttribute("selected", true);
  251. });
  252. },
  253. // 获取被选中的DOM
  254. getSelectedDoms({ targetDoms, rect } = {}) {
  255. this.selectedDatas = [];
  256. return [...targetDoms].filter((targetDom, i) => {
  257. if (this.$g.isCrash(targetDom, rect)) {
  258. this.selectedDatas.push(this.data[i]);
  259. return targetDom;
  260. }
  261. }); // 获取被圈选的内容
  262. },
  263. // ----------------------------------------
  264. del(d) {
  265. this.$emit(`del`, d);
  266. },
  267. addEvents(d) {
  268. this.removeEvents();
  269. this.__removeWindowEvents();
  270. addEventListener("resize", this.resize);
  271. },
  272. removeEvents(d) {
  273. removeEventListener("resize", this.resize);
  274. },
  275. getPositionText(gridData) {
  276. return `<span>第${gridData.y + 1}行</span>&nbsp;<span>第${gridData.x}列</span>`;
  277. },
  278. init_grid_view() {
  279. this.resize();
  280. this.$nextTick(() => {
  281. this.init_sgDragMoveTile();
  282. });
  283. },
  284. init_gridsData(d) {
  285. this.gridsData = [...Array(this.pageSize_)].map((v, i) => ({
  286. x: this.A_Z[i % this.colCount_],
  287. y: Math.floor(i / this.colCount_),
  288. }));
  289. this.$nextTick(() => {
  290. this.resetAllGridStatus();
  291. });
  292. },
  293. init_sgDragMoveTile() {
  294. this.dragMoveTileData = {
  295. scrollContainer: this.$refs.scrollContainer,
  296. dragContainer: this.$refs.dragContainer,
  297. };
  298. },
  299. resize(d) {
  300. let scrollContainer = this.$refs.scrollContainer;
  301. scrollContainer && this.scroll({ target: scrollContainer });
  302. },
  303. scroll(e) {
  304. this.rulerPosition = {
  305. x: e.target.scrollLeft,
  306. y: e.target.scrollTop,
  307. };
  308. },
  309. },
  310. };
  311. </script>
  312. <style lang="scss" scoped>
  313. .sgExcelGrid {
  314. /*禁止选中文本*/
  315. user-select: none;
  316. overflow: hidden;
  317. $tickDis: 40px;
  318. $gridWidth: var(--gridWidth);
  319. $gridHeight: var(--gridHeight);
  320. $gridsWidth: var(--gridsWidth);
  321. $scrollbarWidth: 14px;
  322. width: 100%;
  323. height: 100%;
  324. position: relative;
  325. .ruler-corner {
  326. position: absolute;
  327. z-index: 2;
  328. left: 0;
  329. top: 0;
  330. width: $tickDis;
  331. height: $tickDis;
  332. box-sizing: border-box;
  333. border: 1px solid #ebeef5;
  334. border-right: none;
  335. border-bottom: none;
  336. background-color: #eff2f755;
  337. /*遮罩模糊*/
  338. backdrop-filter: blur(5px);
  339. }
  340. .horizontal-ruler {
  341. position: absolute;
  342. z-index: 1;
  343. left: 0;
  344. top: 0;
  345. margin-left: $tickDis;
  346. display: flex;
  347. flex-wrap: nowrap;
  348. border-top: 1px solid #ebeef5;
  349. border-left: 1px solid #ebeef5;
  350. /*遮罩模糊*/
  351. backdrop-filter: blur(5px);
  352. // box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  353. .tick {
  354. display: flex;
  355. justify-content: center;
  356. align-items: center;
  357. flex-shrink: 0;
  358. width: $gridWidth;
  359. height: $tickDis;
  360. box-sizing: border-box;
  361. border-top: 1px solid transparent;
  362. border-left: 1px solid transparent;
  363. border-bottom: 1px solid #ebeef5;
  364. border-right: 1px solid #ebeef5;
  365. font-family: DIN-Black;
  366. background-color: #eff2f755;
  367. &[hoverGrid] {
  368. border-left: 1px solid #409eff;
  369. border-right: 1px solid #409eff;
  370. background-color: #b3d8ff99;
  371. color: #409eff;
  372. }
  373. }
  374. }
  375. .vertical-ruler {
  376. position: absolute;
  377. z-index: 1;
  378. left: 0;
  379. top: 0;
  380. margin-top: $tickDis;
  381. display: flex;
  382. flex-wrap: wrap;
  383. flex-direction: column;
  384. border-top: 1px solid #ebeef5;
  385. border-left: 1px solid #ebeef5;
  386. /*遮罩模糊*/
  387. backdrop-filter: blur(5px);
  388. // box-shadow: 2px 0 12px 0 rgba(0, 0, 0, 0.1);
  389. .tick {
  390. display: flex;
  391. justify-content: center;
  392. align-items: center;
  393. flex-shrink: 0;
  394. width: $tickDis;
  395. height: $gridHeight;
  396. box-sizing: border-box;
  397. border-top: 1px solid transparent;
  398. border-left: 1px solid transparent;
  399. border-bottom: 1px solid #ebeef5;
  400. border-right: 1px solid #ebeef5;
  401. font-family: DIN-Black;
  402. background-color: #eff2f7;
  403. background-color: #eff2f755;
  404. &[hoverGrid] {
  405. border-top: 1px solid #409eff;
  406. border-bottom: 1px solid #409eff;
  407. background-color: #b3d8ff99;
  408. color: #409eff;
  409. }
  410. }
  411. }
  412. .grids-scroll {
  413. width: calc(100% - #{$tickDis});
  414. height: calc(100vh - 310px);
  415. box-sizing: border-box;
  416. overflow: auto;
  417. position: relative;
  418. margin: $tickDis 0 0 $tickDis;
  419. .grids {
  420. width: calc(#{$gridsWidth} + #{$scrollbarWidth});
  421. min-height: calc(#{$gridHeight} + #{$scrollbarWidth});
  422. overflow: auto;
  423. display: flex;
  424. flex-wrap: wrap;
  425. align-content: flex-start;
  426. box-sizing: border-box;
  427. border-top: 1px solid #ebeef5;
  428. border-left: 1px solid #ebeef5;
  429. .grid {
  430. display: flex;
  431. justify-content: center;
  432. align-items: center;
  433. width: $gridWidth;
  434. height: $gridHeight;
  435. padding: 20px;
  436. box-sizing: border-box;
  437. border-top: 1px solid transparent;
  438. border-left: 1px solid transparent;
  439. border-bottom: 1px solid #ebeef5;
  440. border-right: 1px solid #ebeef5;
  441. word-wrap: break-word;
  442. word-break: break-all;
  443. white-space: break-spaces;
  444. position: relative;
  445. span {
  446. /*多行省略号*/
  447. overflow: hidden;
  448. word-break: break-all;
  449. white-space: break-spaces;
  450. display: -webkit-box;
  451. -webkit-box-orient: vertical;
  452. max-height: min-content;
  453. -webkit-line-clamp: 3;
  454. line-height: 1.2;
  455. }
  456. // 坐标文本
  457. .position-text {
  458. position: absolute;
  459. height: 22px;
  460. z-index: 1;
  461. left: 0px;
  462. top: 0px;
  463. display: none;
  464. flex-wrap: nowrap;
  465. white-space: nowrap;
  466. align-items: center;
  467. color: white;
  468. background-color: #00000055;
  469. box-sizing: border-box;
  470. padding: 0 5px;
  471. border-radius: 0 0 8px 0;
  472. >>> span {
  473. font-size: 12px !important;
  474. }
  475. cursor: cell;
  476. &:hover {
  477. background-color: #409eff;
  478. color: white;
  479. }
  480. }
  481. // 删除
  482. i.el-icon-close {
  483. z-index: 1;
  484. display: none;
  485. position: absolute;
  486. right: 0;
  487. top: 0;
  488. font-size: 12px !important;
  489. justify-content: center;
  490. align-items: center;
  491. color: white;
  492. background-color: #409eff;
  493. box-sizing: border-box;
  494. padding: 5px;
  495. border-radius: 0 0 0 8px;
  496. cursor: pointer;
  497. &:hover {
  498. background-color: #f56c6c;
  499. }
  500. }
  501. // 拖拽选区
  502. .drag-select-btn {
  503. position: absolute;
  504. height: 9px;
  505. width: 9px;
  506. z-index: 1;
  507. right: -4.5px;
  508. bottom: -4.5px;
  509. display: none;
  510. box-sizing: border-box;
  511. border: 2px solid white;
  512. background-color: #f56c6c;
  513. cursor: crosshair;
  514. }
  515. &:nth-of-type(2n) {
  516. background-color: #eff2f755;
  517. }
  518. &[hoverGridX] {
  519. border-left: 1px solid #409eff;
  520. border-right: 1px solid #409eff;
  521. background-color: #f2f8fe;
  522. }
  523. &[hoverGridY] {
  524. border-top: 1px solid #409eff;
  525. border-bottom: 1px solid #409eff;
  526. background-color: #f2f8fe;
  527. }
  528. &[mousedownGrid] {
  529. z-index: 2; //让drag-select-btn在顶端
  530. border: 1px solid #f56c6c;
  531. background-color: #f56c6c22;
  532. .position-text {
  533. background-color: #f56c6c66;
  534. color: white;
  535. &:hover {
  536. background-color: #f56c6c;
  537. }
  538. }
  539. i.el-icon-close {
  540. background-color: #f56c6c66;
  541. &:hover {
  542. background-color: #f56c6c;
  543. }
  544. }
  545. .drag-select-btn {
  546. display: block;
  547. }
  548. &:hover:not([dragMove]) {
  549. background-color: #f56c6c66;
  550. i,
  551. .position-text {
  552. display: flex;
  553. }
  554. }
  555. }
  556. &[selected] {
  557. z-index: 2; //让drag-select-btn在顶端
  558. border-top: 1px solid transparent;
  559. border-left: 1px solid transparent;
  560. border-right: 1px solid #f56c6c22;
  561. border-bottom: 1px solid #f56c6c22;
  562. background-color: #f56c6c22;
  563. .position-text {
  564. background-color: #f56c6c66;
  565. color: white;
  566. &:hover {
  567. background-color: #f56c6c;
  568. }
  569. }
  570. i.el-icon-close {
  571. background-color: #f56c6c66;
  572. &:hover {
  573. background-color: #f56c6c;
  574. }
  575. }
  576. .drag-select-btn {
  577. display: none;
  578. }
  579. &:hover:not([dragMove]) {
  580. border: 1px solid #f56c6c;
  581. background-color: #f56c6c66 !important;
  582. }
  583. }
  584. &[selected-left] {
  585. border-left: 1px solid #f56c6c;
  586. }
  587. &[selected-top] {
  588. border-top: 1px solid #f56c6c;
  589. }
  590. &[selected-right] {
  591. border-right: 1px solid #f56c6c;
  592. }
  593. &[selected-bottom] {
  594. border-bottom: 1px solid #f56c6c;
  595. }
  596. &[selected-right][selected-bottom] {
  597. .drag-select-btn {
  598. display: block;
  599. }
  600. }
  601. &[mousedownGridX] {
  602. border-left: 1px solid #f56c6c;
  603. border-right: 1px solid #f56c6c;
  604. background-color: #f56c6c22;
  605. }
  606. &[mousedownGridY] {
  607. border-top: 1px solid #f56c6c;
  608. border-bottom: 1px solid #f56c6c;
  609. background-color: #f56c6c22;
  610. }
  611. &[dragMove] {
  612. }
  613. &:hover:not([mousedownGrid]):not([dragMove]) {
  614. background-color: #b3d8ff99;
  615. i,
  616. .position-text {
  617. display: flex;
  618. }
  619. }
  620. }
  621. &[selectedGrids] {
  622. .grid {
  623. &:hover:not([mousedownGrid]):not([dragMove]):not([selected]) {
  624. border: 1px solid #409eff;
  625. }
  626. }
  627. }
  628. }
  629. }
  630. }
  631. </style>

应用

  1. <template>
  2. <sgExcelGrid
  3. :props="{ label: `MC` }"
  4. :data="gridDatas"
  5. :pageSize="pageSize"
  6. @del="delGrid"
  7. @selectedDatas="selectedDatas"
  8. />
  9. </template>
  10. <script>
  11. import sgExcelGrid from "@/vue/components/admin/sgExcelGrid";
  12. export default {
  13. components: {
  14. sgExcelGrid,
  15. },
  16. data() {
  17. return {
  18. gridDatas: [
  19. { ID: 1, value: 1, MC: "显示文本1" },
  20. { ID: 2, value: 2, MC: "显示文本2" },
  21. { ID: 3, value: 3, MC: "显示文本3" },
  22. { ID: 4, value: 4, MC: "显示文本4" },
  23. { ID: 5, value: 5, MC: "显示文本5" },
  24. ],
  25. pageSize: 100, //每页显示多少个单元格
  26. rectSelectIDS: [], //选中的ID数组
  27. };
  28. },
  29. props: ["value"],
  30. computed: {},
  31. watch: {},
  32. created() {},
  33. mounted() {},
  34. destroyed() {},
  35. methods: {
  36. selectedDatas(d) {
  37. this.rectSelectIDS = d.map((v) => v.ID); //获取选中项ID数组
  38. },
  39. delGrid(d) {
  40. //删除单元格
  41. },
  42. },
  43. };
  44. </script>

基于【sgDragMoveTile】自定义组件:拖拽瓦片图、地图、大图,滚动条对应同步滚动_用鼠标拖拽(drag)内容div”,滚动条对应同步滚动 vue-CSDN博客文章浏览阅读140次。【代码】【sgDragMoveTile】自定义组件:拖拽瓦片图、地图、大图,滚动条对应同步滚动。_用鼠标拖拽(drag)内容div”,滚动条对应同步滚动 vuehttps://blog.csdn.net/qq_37860634/article/details/133292981

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/274385
推荐阅读
相关标签
  

闽ICP备14008679号