ag-grid-vue在排班组件中的使用
在很多医务系统中,会有医生护士排班的业务,比如下面这种:
可是实现排班数据的上移,下移,并且能够通过右键菜单实现排班等操作;
直接上源码:
首先安装依赖:
npm install ag-grid-vue
npm install ag-grid-community
npm install vue-property-decorator
接下来组件封装:
目录结构如下:
jhe-grid-table.vue内容如下:
1 <template> 2 <div class="JheGridTable"> 3 <div @contextmenu.prevent="onOpenContextMenu"> 4 <ag-grid-vue 5 style="width: 100%; height: 100%" 6 class="ag-theme-balham" 7 :defaultColDef="defaultColDef" 8 :columnDefs="columnDefs" 9 :rowData="rowData" 10 :enableColResize="true" 11 :suppressCellFocus="false" 12 :suppressMultiRangeSelection="false" 13 :enableCellTextSelection="false" 14 :enableRangeSelection="false" 15 :rowMultiSelectWithClick="false" 16 :suppressRowClickSelection="false" 17 :floatingFilter="false" 18 :suppressMenuHide="false" 19 :suppressMovableColumns="true" 20 :suppressDragLeaveHidesColumns="false" 21 :suppressMovable="false" 22 :context="ctx" 23 :allowContextMenuWithControlKey="false" 24 :headerHeight="headerHeight" 25 :getRowHeight="getRowHeight" 26 @grid-ready="onGridReady" 27 @first-data-rendered="onFirstDataRendered" 28 @rowDataChanged="onRowDataChanged" 29 @cellClicked="onCellClicked" 30 @sort-changed="onSortChanged" 31 @cellMouseDown="onCellMouseDown" 32 @mouseup.native="onCellMouseUp" 33 @cellMouseOver="onCellMouseOver" 34 @cellMouseOut="onCellMouseOut" 35 @cellFocused="onCellFocused" 36 @dragStarted="onDragStarted" 37 @dragStopped="onDragStopped" 38 ></ag-grid-vue> 39 </div> 40 41 <context-menu 42 id="testingctx" 43 ref="ctx" 44 @ctx-open="onCtxOpened" 45 @ctx-cancel="resetCtxMenu" 46 @ctx-close="onCtxClosed" 47 > 48 <div class="ctx-menu-body" :style="ctxMenuBodyStyle"> 49 <div class="ctx-group" :style="{ width: ctxMenuBodyWidth }"> 50 <div 51 class="ctx-item" 52 :key="'ctxmi_' + index" 53 v-for="(item, index) in ctxMenuItems" 54 @click="onCtxMenuItemClick(item)" 55 @mouseenter="onCtxMenuMouseEnter(item)" 56 @mouseleave="onCtxMenuMouseLeave(item)" 57 > 58 <div> 59 {{ item.title }} 60 </div> 61 <div v-if="item.children && item.children.length > 0"> 62 <i class="el-icon-arrow-right"></i> 63 </div> 64 </div> 65 </div> 66 <div 67 v-if="showChildCtxMenu" 68 class="ctx-group" 69 style="border-left: 1px solid #ccc; overflow: auto" 70 > 71 <div 72 class="ctx-item" 73 :key="'sub_' + index" 74 v-for="(item, index) in subMenuItems" 75 @click="onCtxMenuItemClick(item)" 76 > 77 {{ item.title }} 78 </div> 79 </div> 80 </div> 81 </context-menu> 82 </div> 83 </template> 84 <script> 85 import { getOneWeekDate } from "../utils/tools"; 86 import $ from "jquery"; 87 import _ from "lodash"; 88 import SsCellRender from "../components/ss-cell-render"; 89 import SsHeaderRender from "../components/ss-header-render"; 90 import SsRowHeaderRender from "../components/ss-row-header-render"; 91 import contextMenu from "../components/ctx-menu"; 92 //引入样式文件 93 import "ag-grid-community/dist/styles/ag-grid.css"; 94 import "ag-grid-community/dist/styles/ag-theme-balham.css"; 95 //引入ag-grid-vue 96 import { AgGridVue } from "ag-grid-vue"; 97 import { Message } from "element-ui"; 98 99 export default { 100 name: "JheGridTable", 101 props: { 102 startDate: { 103 type: Date, 104 }, 105 //列定义 106 columns: { 107 type: Array, 108 default: () => { 109 return []; 110 }, 111 }, 112 //rowData数据 113 rowData: { 114 type: Array, 115 default: () => { 116 return []; 117 }, 118 }, 119 headerHeight: { 120 type: Number, 121 default: 30, 122 }, 123 ctxMenuItems: { 124 type: Array, 125 default: () => { 126 return []; 127 }, 128 }, 129 }, 130 data() { 131 let self = this; 132 return { 133 gridCtrl: null, 134 gridApi: null, 135 columnApi: null, 136 isDraging: false, 137 selectedRanges: [], 138 ctx: { 139 isDragCopying: false, 140 }, 141 ctxMenuBodyWidth: 0, 142 ctxMenuBodyStyle: null, 143 subMenuItems: [], 144 showChildCtxMenu: false, 145 mouseEnterCtxMenuItem: null, 146 jheGridApi: { 147 selectRow(rowId) { 148 self.selectRow(rowId); 149 }, 150 selectColumn(colId) { 151 self.selectColumn(colId); 152 }, 153 selectCells(range) { 154 self.selectCells(range); 155 }, 156 moveRowUp() { 157 self.moveRowUp(); 158 }, 159 moveRowDown() { 160 self.moveRowDown(); 161 }, 162 moveDaysUp() { 163 self.moveDaysUp(); 164 }, 165 moveDaysDown() { 166 self.moveDaysDown(); 167 }, 168 exchangeDays() { 169 self.exchangeDays(); 170 }, 171 getCellData(rowId, colId) { 172 return self.getCellData(rowId, colId); 173 }, 174 setCellData(rowId, colId, value) { 175 self.setCellData(rowId, colId, value); 176 }, 177 getRowData(rowId) { 178 return self.getRowData(rowId); 179 }, 180 setRangeValue(range, data) { 181 self.setRangeValue(range, data); 182 }, 183 }, 184 }; 185 }, 186 computed: { 187 defaultColDef() { 188 return { 189 cellStyle: { 190 "text-align": "center", 191 }, 192 }; 193 }, 194 columnDefs() { 195 let oneWeekDate = getOneWeekDate(this.startDate); 196 let colDefs = this.columns.map((c, index) => { 197 let columnDef = _.cloneDeep(c); 198 if (typeof columnDef.colId === "undefined") { 199 columnDef.colId = index; 200 } 201 if (columnDef.selectable) { 202 delete columnDef.selectable; 203 columnDef.cellRenderer = "SsCellRender"; 204 columnDef.headerComponent = "SsHeaderRender"; 205 let date = oneWeekDate[columnDef.field]; 206 207 columnDef.headerComponentParams = { 208 subtitle: 209 date && 210 date.toLocaleDateString("cn", { 211 month: "numeric", 212 day: "numeric", 213 }), 214 onColumnClick: this.onSelectWholeColumn, 215 }; 216 } 217 return columnDef; 218 }); 219 colDefs.unshift({ 220 headerName: "#", 221 colId: "rowNum", 222 valueGetter: "node.rowIndex", 223 minWidth: 40, 224 maxWidth: 40, 225 pinned: "left", 226 cellRenderer: "SsRowHeaderRender", 227 cellStyle: { 228 "text-align": "center", 229 "background-color": "rgb(245, 247, 247)", 230 "border-right": "1px solid #d9dcde", 231 }, 232 }); 233 return colDefs; 234 }, 235 }, 236 watch: { 237 "ctx.isDragCopying": { 238 handler(newVal, oldVal) { 239 if (this.$el) { 240 if (newVal) { 241 this.$el.classList.add("drag-copying"); 242 } else { 243 this.$el.classList.remove("drag-copying"); 244 } 245 } 246 }, 247 }, 248 columnDefs: { 249 handler(newV) { 250 //列定义改变后,表格需要重新渲染 251 this.$nextTick(() => { 252 this.gridApi.setColumnDefs(newV); 253 this.gridApi.sizeColumnsToFit(); 254 }); 255 }, 256 deep: true, 257 }, 258 }, 259 created() { 260 this.iniGrid(); 261 }, 262 methods: { 263 iniGrid() { 264 this.ctxMenuBodyWidth = 160; 265 this.ctxMenuBodyStyle = { 266 width: this.ctxMenuBodyWidth + "px", 267 }; 268 }, 269 getRowHeight(params) { 270 return 35; 271 }, 272 onGridReady(params) { 273 this.gridCtrl = params; 274 this.gridApi = params.api; 275 this.columnApi = params.columnApi; 276 this.gridApi.sizeColumnsToFit(); 277 this.$emit("init", this.jheGridApi); 278 }, 279 onFirstDataRendered(params) { 280 this.$emit("loaded", params); 281 }, 282 onRowDataChanged(event) { 283 this.$emit("changed", event); 284 }, 285 //单元格点击事件 286 onCellClicked(event) { 287 let field = event.column.colDef.field; 288 let cellRendererName = event.column.colDef.cellRenderer; 289 let position = { rowId: event.node.id, colId: event.column.colId }; 290 if (cellRendererName === "SsCellRender") { 291 this.gridCtrl.api.deselectAll(); 292 this.$emit("cellClicked", position, event.data[field]); 293 } 294 295 // const colIndex = this._getColumnIndex(event.column.colId); 296 // if (colIndex == 1) { 297 // //行点击 298 // this._selectRow(event.node.id); 299 // } 300 }, 301 onSortChanged(params) { 302 let { api } = params; 303 api.refreshCells({ 304 columns: [this.columnDefs[0].colId], 305 }); 306 }, 307 308 onCellMouseDown(event) { 309 if (event.event.button != 0) { 310 // 不是鼠标左键 311 return; 312 } 313 314 this.isDraging = true; 315 console.log("onCellMouseDown", arguments); 316 317 if (!event.event.ctrlKey) { 318 this.selectedRanges = []; 319 this._clearSelectedCells(); 320 } 321 322 this._clearDropCopyHolder(); 323 324 const start = this._getCellPos(event); 325 this.selectedRanges.push([start]); 326 this._showDropCopyHolder(start.rowId, start.colId); 327 }, 328 onCellMouseUp(event) { 329 this.isDraging = false; 330 331 if (event.button != 0) { 332 // 不是鼠标左键 333 return; 334 } 335 336 console.log("onCellMouseUp", arguments, this.gridApi); 337 const end = this._getCellPos(event.target); 338 if (end == null) { 339 // 不是单元格 340 return; 341 } 342 343 const range = this.selectedRanges.pop(); 344 if (!range) { 345 return; 346 } 347 range[1] = end; 348 349 this.selectedRanges.push(range); 350 this._selectRange(range); 351 this.gridApi.clearFocusedCell(); 352 353 if (this.ctx.isDragCopying) { 354 const data = this._getCellData(range[0].rowId, range[0].colId); 355 this._setRangValue(range, data); 356 console.log( 357 "onCellMouseUp", 358 this.rowData, 359 this._getRowData(range[0].rowId) 360 ); 361 // this.gridApi.refreshCells(); 362 } 363 364 this.ctx.isDragCopying = false; 365 }, 366 onCellMouseOver(event) { 367 if (this.isDraging) { 368 console.log("cellMouseOver", arguments); 369 const end = this._getCellPos(event); 370 const range = this.selectedRanges.pop(); 371 range[1] = end; 372 this.selectedRanges.push(range); 373 374 this._selectAllRanges(this.selectedRanges); 375 } 376 }, 377 onCellMouseOut() { 378 if (this.isDraging) console.log("onCellMouseOut", arguments); 379 }, 380 onCellFocused() { 381 console.log("onCellFocused", arguments); 382 // setTimeout(()=>{ 383 // this.gridApi.clearFocusedCell(); 384 // },1000); 385 }, 386 onDragStarted() { 387 console.log("onDragStarted", arguments); 388 }, 389 onDragStopped() { 390 console.log("onDragStopped", arguments); 391 }, 392 393 _swapRanges(range1, range2) { 394 if (range1 == null || range2 == null) { 395 console.error("只能对两个尺寸相等的区域进行对换", arguments); 396 return null; 397 } 398 399 const fX1 = Math.min(range1[0].colIndex, range1[1].colIndex) || 0; 400 const fY1 = Math.min(range1[0].rowIndex, range1[1].rowIndex) || 0; 401 const fX2 = Math.max(range1[0].colIndex, range1[1].colIndex) || 0; 402 const fY2 = Math.max(range1[0].rowIndex, range1[1].rowIndex) || 0; 403 404 const sX1 = Math.min(range2[0].colIndex, range2[1].colIndex) || 0; 405 const sY1 = Math.min(range2[0].rowIndex, range2[1].rowIndex) || 0; 406 const sX2 = Math.max(range2[0].colIndex, range2[1].colIndex) || 0; 407 const sY2 = Math.max(range2[0].rowIndex, range2[1].rowIndex) || 0; 408 409 if (fX2 - fX1 != sX2 - sX1 || fY2 - fY1 != sY2 - sY1) { 410 console.error("只能对两个尺寸相等的区域进行对换", arguments); 411 return null; 412 } 413 414 let rowIdList = []; 415 let changedData = []; 416 const selectable = this._getColumns() 417 .filter((c) => c.colDef.cellRenderer === "SsCellRender") 418 .map((c) => c.colId); 419 420 for (let i = 0; i + fY1 <= fY2; i++) { 421 const fRowId = this._getRowId(i + fY1); 422 const sRowId = this._getRowId(i + sY1); 423 424 for (let j = 0; j + fX1 <= fX2; j++) { 425 const fColId = this._getColId(j + fX1); 426 const sColId = this._getColId(j + sX1); 427 if (selectable.includes(fColId) && selectable.includes(sColId)) { 428 const fData = this._getCellData(fRowId, fColId); 429 const sData = this._getCellData(sRowId, sColId); 430 this._setCellData(fRowId, fColId, sData); 431 this._setCellData(sRowId, sColId, fData); 432 rowIdList.push(fRowId); 433 rowIdList.push(sRowId); 434 } 435 } 436 } 437 let noRepeatRowIdList = Array.from(new Set(rowIdList)); 438 noRepeatRowIdList.forEach((rowId) => { 439 changedData.push(this._getRowData(rowId)); 440 }); 441 this.$emit("changed", changedData); 442 }, 443 /** 444 * 修改一个区域的数据 445 */ 446 _setRangValue(range, data) { 447 const selectable = this._getColumns() 448 .filter((c) => c.colDef.cellRenderer === "SsCellRender") 449 .map((c) => c.colId); 450 const x1 = Math.min(range[0].colIndex, range[1].colIndex) || 0; 451 const y1 = Math.min(range[0].rowIndex, range[1].rowIndex) || 0; 452 const x2 = Math.max(range[0].colIndex, range[1].colIndex) || 0; 453 const y2 = Math.max(range[0].rowIndex, range[1].rowIndex) || 0; 454 455 let rowIdList = []; 456 let changedData = []; 457 this.$el.querySelectorAll("[row-index]").forEach((row) => { 458 const rowIndex = parseInt(row.getAttribute("row-index")); 459 const rowId = row.getAttribute("row-id"); 460 461 if (rowIndex < y1 || rowIndex > y2) { 462 } else { 463 row.querySelectorAll("[aria-colindex]").forEach((col) => { 464 const colIndex = parseInt(col.getAttribute("aria-colindex")); 465 const colId = col.getAttribute("col-id"); 466 467 if ( 468 colIndex >= x1 && 469 colIndex <= x2 && 470 selectable.includes(colId) 471 ) { 472 this._setCellData(rowId, colId, _.cloneDeep(data)); 473 rowIdList.push(rowId); 474 } 475 }); 476 } 477 }); 478 let noRepeatRowIdList = Array.from(new Set(rowIdList)); 479 noRepeatRowIdList.forEach((rowId) => { 480 changedData.push(this._getRowData(rowId)); 481 }); 482 this.$emit("changed", changedData); 483 }, 484 _showDropCopyHolder(rowId, colId) { 485 const params1 = { 486 rowNodes: [this._getRowNodeByID(rowId)], 487 columns: [colId], 488 }; 489 const instances1 = this.gridApi.getCellRendererInstances(params1); 490 instances1.forEach((instance) => { 491 instance.isShowDropCopyHolder = true; 492 }); 493 }, 494 _clearDropCopyHolder() { 495 const instances = this.gridApi.getCellRendererInstances(); 496 instances.forEach((instance) => { 497 instance.isShowDropCopyHolder = false; 498 }); 499 }, 500 _clearSelectedCells(range) { 501 if (!range) { 502 this.$el 503 .querySelectorAll(".ag-center-cols-container [row-index]") 504 .forEach((row) => { 505 row.querySelectorAll("[aria-colindex]").forEach((col) => { 506 col.classList.remove( 507 "ag-cell-range-selected", 508 "ag-cell-range-selected-1", 509 "ag-cell-range-top", 510 "ag-cell-range-bottom", 511 "ag-cell-range-left", 512 "ag-cell-range-right" 513 ); 514 }); 515 }); 516 return; 517 } 518 519 const x1 = Math.min(range[0].colIndex, range[1].colIndex) || 0; 520 const y1 = Math.min(range[0].rowIndex, range[1].rowIndex) || 0; 521 const x2 = Math.max(range[0].colIndex, range[1].colIndex) || 0; 522 const y2 = Math.max(range[0].rowIndex, range[1].rowIndex) || 0; 523 524 const selectable = this._getColumns() 525 .filter((c) => c.colDef.cellRenderer === "SsCellRender") 526 .map((c) => c.colId); 527 let isStartPOS = (r, c) => { 528 return range[0].rowId == r && range[0].colId == c; 529 }; 530 531 for (let i = 0; i + y1 <= y2; i++) { 532 const rowIndex = i + y1; 533 const rowId = this._getRowId(rowIndex); 534 const row = this.$el.querySelector( 535 `.ag-center-cols-container [row-index="${rowIndex}"]` 536 ); 537 for (let j = 0; j + x1 <= x2; j++) { 538 const colIndex = j + x1; 539 const colId = this._getColId(colIndex); 540 if (selectable.includes(colId)) { 541 const col = row.querySelector(`[aria-colindex="${colIndex}"]`); 542 col.classList.remove( 543 "ag-cell-range-selected", 544 "ag-cell-range-selected-1", 545 "ag-cell-range-top", 546 "ag-cell-range-bottom", 547 "ag-cell-range-left", 548 "ag-cell-range-right" 549 ); 550 } 551 } 552 } 553 }, 554 /** 555 * 选中一个区域 556 */ 557 _selectRange(range) { 558 const x1 = Math.min(range[0].colIndex, range[1].colIndex) || 0; 559 const y1 = Math.min(range[0].rowIndex, range[1].rowIndex) || 0; 560 const x2 = Math.max(range[0].colIndex, range[1].colIndex) || 0; 561 const y2 = Math.max(range[0].rowIndex, range[1].rowIndex) || 0; 562 const selectable = this._getColumns() 563 .filter((c) => c.colDef.cellRenderer === "SsCellRender") 564 .map((c) => c.colId); 565 let isStartPOS = (r, c) => { 566 return range[0].rowId == r && range[0].colId == c; 567 }; 568 569 for (let i = 0; i + y1 <= y2; i++) { 570 const rowIndex = i + y1; 571 const rowId = this._getRowId(rowIndex); 572 const row = this.$el.querySelector( 573 `.ag-center-cols-container [row-index="${rowIndex}"]` 574 ); 575 for (let j = 0; j + x1 <= x2; j++) { 576 const colIndex = j + x1; 577 const colId = this._getColId(colIndex); 578 if (selectable.includes(colId)) { 579 const col = row.querySelector(`[aria-colindex="${colIndex}"]`); 580 //排除掉分组行 581 if (col) { 582 if (rowIndex == y1) { 583 col.classList.add("ag-cell-range-top"); 584 } 585 586 if (rowIndex == y2) { 587 col.classList.add("ag-cell-range-bottom"); 588 } 589 590 if (colIndex == x1) { 591 col.classList.add("ag-cell-range-left"); 592 } 593 594 if (colIndex == x2) { 595 col.classList.add("ag-cell-range-right"); 596 } 597 598 col.classList.add( 599 "ag-cell-range-selected", 600 "ag-cell-range-selected-1" 601 ); 602 } 603 } 604 } 605 } 606 607 // this.$el.querySelectorAll("[row-index]").forEach((row) => { 608 // const rowIndex = parseInt(row.getAttribute("row-index")); 609 // const rowId = row.getAttribute("row-id"); 610 // selecting = {}; 611 612 // if (rowIndex < y1 || rowIndex > y2) { 613 // row.querySelectorAll("[aria-colindex]").forEach((col) => { 614 // const colId = col.getAttribute("col-id"); 615 // col.classList.remove( 616 // "ag-cell-range-selected", 617 // "ag-cell-range-selected-1", 618 // "ag-cell-range-top", 619 // "ag-cell-range-bottom", 620 // "ag-cell-range-left", 621 // "ag-cell-range-right" 622 // ); 623 624 // const params1 = { 625 // rowNodes: [this._getRowNodeByID(rowId)], 626 // columns: [colId], 627 // }; 628 // const instances1 = this.gridApi.getCellRendererInstances(params1); 629 // instances1.forEach((instance) => { 630 // instance.isShowDropCopyHolder = false; 631 // }); 632 // }); 633 // } else { 634 // row.querySelectorAll("[aria-colindex]").forEach((col) => { 635 // const colIndex = parseInt(col.getAttribute("aria-colindex")); 636 // const colId = col.getAttribute("col-id"); 637 // col.classList.remove( 638 // "ag-cell-range-selected", 639 // "ag-cell-range-selected-1", 640 // "ag-cell-range-top", 641 // "ag-cell-range-bottom", 642 // "ag-cell-range-left", 643 // "ag-cell-range-right" 644 // ); 645 // if ( 646 // colIndex >= x1 && 647 // colIndex <= x2 && 648 // selectable.includes(colId) 649 // ) { 650 // const params1 = { 651 // rowNodes: [this._getRowNodeByID(rowId)], 652 // columns: [colId], 653 // }; 654 // const instances1 = this.gridApi.getCellRendererInstances(params1); 655 // instances1.forEach((instance) => { 656 // instance.isShowDropCopyHolder = isStartPOS(rowId, colId); 657 // }); 658 659 // if (rowIndex == y1) { 660 // col.classList.add("ag-cell-range-top"); 661 // } 662 663 // if (rowIndex == y2) { 664 // col.classList.add("ag-cell-range-bottom"); 665 // } 666 667 // if (colIndex == x1) { 668 // col.classList.add("ag-cell-range-left"); 669 // } 670 671 // if (colIndex == x2) { 672 // col.classList.add("ag-cell-range-right"); 673 // } 674 675 // col.classList.add( 676 // "ag-cell-range-selected", 677 // "ag-cell-range-selected-1" 678 // ); 679 // } else { 680 // const params1 = { 681 // rowNodes: [this._getRowNodeByID(rowId)], 682 // columns: [colId], 683 // }; 684 // const instances1 = this.gridApi.getCellRendererInstances(params1); 685 // instances1.forEach((instance) => { 686 // instance.isShowDropCopyHolder = false; 687 // }); 688 // } 689 // }); 690 // } 691 // }); 692 693 this.gridApi.refreshCells(); 694 }, 695 _selectAllRanges(ranges) { 696 this._clearSelectedCells(); 697 // this._clearDropCopyHolder(); 698 ranges.forEach((range) => { 699 this._selectRange(range); 700 }); 701 }, 702 _selectCol(colId) { 703 this.gridCtrl.api.deselectAll(); 704 const colIndex = this._getColumnIndex(colId); 705 const firstRowId = this._getRowData("0").isRowGroup ? "1" : "0"; 706 const firstRowIndex = this._getRowIndex(firstRowId); 707 const lastRowId = this.rowData.length - 1 + ""; 708 const lastRowIndex = this._getRowIndex(lastRowId); 709 710 const range = [ 711 { colId, colIndex, rowId: firstRowId, rowIndex: firstRowIndex }, 712 { colId, colIndex, rowId: lastRowId, rowIndex: lastRowIndex }, 713 ]; 714 715 this._selectRange(range); 716 }, 717 _selectRow(rowId) { 718 const columns = this._getColumns(); 719 const rowIndex = this._getRowIndex(rowId); 720 const firstColId = columns[0].colId; 721 const firstColIndex = 1; 722 const lastColId = columns[columns.length - 1].colId; 723 const lastColIndex = columns.length; 724 725 const range = [ 726 { rowId, rowIndex, colId: firstColId, colIndex: firstColIndex }, 727 { rowId, rowIndex, colId: lastColId, colIndex: lastColIndex }, 728 ]; 729 730 this._selectRange(range); 731 }, 732 /** 733 * 获取一个元素的位置 734 * rowIndex,colIndex,rowId,colId 735 */ 736 _getCellPos(element) { 737 if (!element) { 738 return null; 739 } 740 741 if (element instanceof HTMLElement) { 742 let $agCell = $(element).is(".ag-cell") 743 ? $(element) 744 : $(element).closest(".ag-cell"); 745 746 if (!$agCell.length) { 747 return null; 748 } 749 750 let $agRow = $agCell.closest(".ag-row"); 751 return { 752 rowIndex: parseInt($agRow.attr("row-index")), 753 rowId: $agRow.attr("row-id"), 754 colIndex: $agCell.attr("aria-colindex"), 755 colId: $agCell.attr("col-id"), 756 }; 757 } else { 758 return { 759 rowIndex: element.rowIndex, 760 rowId: element.node.id, 761 colIndex: this._getColumnIndex(element.column.colId), 762 colId: element.column.colId, 763 }; 764 } 765 }, 766 /** 767 * 获取Row节点 768 */ 769 _getRowNodeByID(id) { 770 return this.gridApi.getRowNode(id); 771 }, 772 /** 773 * 获取Row数据 774 */ 775 _getRowData(rowId) { 776 return this._getRowNodeByID(rowId).data; 777 }, 778 /** 779 * 获取Cell数据 780 */ 781 _getCellData(rowId, colId) { 782 const data = this._getRowData(rowId); 783 let field = this.columnDefs.find( 784 (colDef) => colId === colDef.colId 785 ).field; 786 const shifts = data[field].shifts; 787 return shifts; 788 }, 789 /** 790 * 设置Row数据 791 */ 792 _setRowData(rowId, data) { 793 this._getRowNodeByID(rowId).setData(data); 794 }, 795 /** 796 * 设置Cell数据 797 */ 798 _setCellData(rowId, colId, value) { 799 const data = this._getRowData(rowId); 800 let field = this.columnDefs.find( 801 (colDef) => colId === colDef.colId 802 ).field; 803 804 data[field].shifts = value; 805 this._setRowData(rowId, data); 806 }, 807 /** 808 * 获取所有列定义 809 */ 810 _getColumns() { 811 return this.columnApi.getColumns() || []; 812 }, 813 /** 814 * 获取colIndex 815 */ 816 _getColumnIndex(colId) { 817 return this._getColumns().findIndex((c) => c.colId == colId) + 1; 818 }, 819 _getRowIndex(rowId) { 820 const row = this.gridApi.getModel().getRowNode(rowId); 821 return row && row.rowIndex; 822 }, 823 _getColId(colIndex) { 824 const col = this._getColumns()[colIndex - 1]; 825 return col && col.colId; 826 }, 827 _getRowId(rowIndex) { 828 const row = this.$el.querySelector(`[row-index="${rowIndex}"]`); 829 return row && row.getAttribute("row-id"); 830 }, 831 832 /** 833 * 当点击列头时全选一整列 834 * @param {*} event 点击事件对象 835 * @param {*} params column 控制器 836 */ 837 onSelectWholeColumn(event, params) { 838 let { column } = params; 839 this._selectCol(column.colId); 840 }, 841 842 /** 843 * 响应 contextmenu 事件 844 * @param {*} event contextmenu 事件 845 */ 846 onOpenContextMenu(event) { 847 console.log("this.ctxMenuItems", this.ctxMenuItems); 848 this.$refs.ctx.close(); 849 let row = this.getGridRow(event.target); 850 let column = this.getGridColumn(event.target); 851 if (!row || !column) { 852 return; 853 } 854 let rowIndex = row.rowIndex; 855 let colIndex = this._getColumnIndex(column.colId); 856 857 if (row.data.isRowGroup || this.selectedRanges.length === 0) { 858 return; 859 } else { 860 let range = this.selectedRanges[0]; 861 if (!range[0] || !range[1]) { 862 return; 863 } 864 const x1 = Math.min(range[0].colIndex, range[1].colIndex) || 0; 865 const y1 = Math.min(range[0].rowIndex, range[1].rowIndex) || 0; 866 const x2 = Math.max(range[0].colIndex, range[1].colIndex) || 0; 867 const y2 = Math.max(range[0].rowIndex, range[1].rowIndex) || 0; 868 if ( 869 rowIndex >= y1 && 870 rowIndex <= y2 && 871 colIndex >= x1 && 872 colIndex <= x2 873 ) { 874 //可编辑区域显示右键菜单并且event.target必须在selecteRange选中区域内 875 if (this.ctxMenuItems.length > 0) { 876 this.$nextTick(() => { 877 this.$refs.ctx.open(this.$event, this.ctxMenuItems, this.$el); 878 }); 879 } 880 } 881 } 882 }, 883 /** 884 * 获取当前单元格所在的行 885 * @param {*} el 单元格 886 */ 887 getGridRow(el) { 888 let agRow = el.closest(".ag-row") || el; 889 if (!agRow.classList.contains("ag-row")) { 890 return null; 891 } 892 let rowId = agRow.attributes["row-id"].value; 893 let row = this.gridCtrl.api.getRowNode(+rowId); 894 return row; 895 }, 896 /** 897 * 获取当前单元格所在的列 898 * @param {*} el 单元格 899 */ 900 getGridColumn(el) { 901 let agCell = el.closest(".ag-cell") || el; 902 if (!agCell.classList.contains("ag-cell")) { 903 return null; 904 } 905 let colId = agCell.attributes["col-id"].value; 906 let column = this.gridCtrl.columnApi.getColumn(colId); 907 return column; 908 }, 909 910 /** 911 * contextmenu 打开后事件 912 * @param {*} ctxMenu 913 */ 914 onCtxOpened(ctxMenu) { 915 this.resetCtxMenu(); 916 }, 917 /** 918 * contextmenu 关闭后事件 919 * @param {*} ctxMenu 920 */ 921 onCtxClosed(ctxMenu) { 922 // console.log("close"); 923 }, 924 /** 925 * 重置 contextmenu 926 */ 927 resetCtxMenu() { 928 this.selectedCtxMenu = null; 929 this.subMenuItems = []; 930 this.showChildCtxMenu = false; 931 this.ctxMenuBodyStyle.width = this.ctxMenuBodyWidth + "px"; 932 }, 933 /** 934 * 点击菜单项操作 935 */ 936 onCtxMenuItemClick(menuItem) { 937 console.log("onCtxMenuItemClick", menuItem); 938 this.selectedCtxMenu = menuItem; 939 this.$emit("command", menuItem, this.selectedRanges); 940 }, 941 /** 942 * 鼠标停留菜单项,需显示二级菜单 943 * @param {*} item 当前菜单项 944 */ 945 onCtxMenuMouseEnter(item) { 946 if (this.mouseEnterCtxMenuItem) { 947 clearTimeout(this.mouseEnterCtxMenuItem); 948 this.mouseEnterCtxMenuItem = null; 949 } 950 this.mouseEnterCtxMenuItem = setTimeout(() => { 951 this.showChildCtxMenu = !!(item.children && item.children.length); 952 if (this.showChildCtxMenu) { 953 this.selectedCtxMenu = item; 954 this.subMenuItems = item.children; 955 this.ctxMenuBodyStyle.width = this.ctxMenuBodyWidth * 2 + "px"; 956 } else { 957 this.resetCtxMenu(); 958 } 959 this.mouseEnterCtxMenuItem = null; 960 }, 500); 961 }, 962 onCtxMenuMouseLeave(item) {}, 963 964 //提供以下几种api 965 //选中一行 966 selectRow(rowId) { 967 console.log("selectRow", arguments); 968 this._getRowNodeByID(rowId).setSelected(true, true); 969 }, 970 //选中一列 971 selectColumn(colId) { 972 console.log("selectColumn", arguments); 973 this._selectCol(colId); 974 }, 975 //选中单元格 976 selectCells(range) { 977 console.log("selectCells", arguments); 978 this._selectRange(range); 979 }, 980 //行上移 981 moveRowUp() { 982 console.log("moveRowUp", arguments); 983 let row = this.gridCtrl.api.getSelectedNodes(); 984 if (!row || !row.length) { 985 Message.error("请选择一条数据"); 986 return; 987 } 988 row = row[0]; 989 if (row.data.isRowGroup) { 990 return; 991 } 992 if (row.rowIndex === 0) { 993 Message.error("已是最顶层"); 994 return; 995 } else { 996 let currentRowData = this._getRowData(row.id); 997 let targetRowData = this._getRowData(+row.id - 1); 998 if (targetRowData.isRowGroup) { 999 Message.error("已是组内最顶层"); 1000 } else { 1001 this._setRowData(+row.id - 1, currentRowData); 1002 this._setRowData(row.id, targetRowData); 1003 this._getRowNodeByID(+row.id - 1).setSelected(true, true); 1004 this.$emit("changed", [currentRowData, targetRowData]); 1005 } 1006 } 1007 }, 1008 //行下移 1009 moveRowDown() { 1010 console.log("moveRowDown", arguments); 1011 let row = this.gridCtrl.api.getSelectedNodes(); 1012 if (!row || !row.length) { 1013 Message.error("请选择一条数据"); 1014 return; 1015 } 1016 row = row[0]; 1017 if (row.data.isRowGroup) { 1018 return; 1019 } 1020 if (row.rowIndex === this.rowData.length - 1) { 1021 Message.error("已是最底层"); 1022 return; 1023 } else { 1024 let currentRowData = this._getRowData(row.id); 1025 let targetRowData = this._getRowData(+row.id + 1); 1026 if (targetRowData.isRowGroup) { 1027 Message.error("已是组内最底层"); 1028 } else { 1029 this._setRowData(+row.id + 1, currentRowData); 1030 this._setRowData(row.id, targetRowData); 1031 this._getRowNodeByID(+row.id + 1).setSelected(true, true); 1032 this.$emit("changed", [targetRowData, currentRowData]); 1033 } 1034 } 1035 }, 1036 // 排班数据上移 1037 moveDaysUp() { 1038 console.log("moveDaysUp", arguments); 1039 let row = this.gridCtrl.api.getSelectedNodes(); 1040 if (!row || !row.length) { 1041 Message.error("请选择一条数据"); 1042 return; 1043 } 1044 row = row[0]; 1045 if (row.data.isRowGroup) { 1046 return; 1047 } 1048 if (row.rowIndex === 0) { 1049 Message.error("已是最顶层"); 1050 return; 1051 } else { 1052 let currentRowData = this._getRowData(row.id); 1053 let targetRowId = +row.id - 1; 1054 let targetRowData = this._getRowData(targetRowId); 1055 //分组行为第一行 1056 if (targetRowData.isRowGroup) { 1057 let targetIndex = this.rowData.findIndex((d) => { 1058 return d.groupNO === targetRowData.groupNO; 1059 }); 1060 if (targetIndex === 0) { 1061 return; 1062 } else { 1063 targetRowId = +row.id - 2; 1064 targetRowData = this._getRowData(targetRowId); 1065 } 1066 } 1067 const fields = [ 1068 "monday", 1069 "tuesday", 1070 "wednesday", 1071 "thursday", 1072 "friday", 1073 "saturday", 1074 "sunday", 1075 ]; 1076 fields.forEach((field) => { 1077 let value = currentRowData[field]; 1078 currentRowData[field] = targetRowData[field]; 1079 targetRowData[field] = value; 1080 }); 1081 this._setRowData(targetRowId, targetRowData); 1082 this._setRowData(row.id, currentRowData); 1083 this._getRowNodeByID(targetRowId).setSelected(true, true); 1084 this.$emit("changed", [targetRowData, currentRowData]); 1085 } 1086 }, 1087 // 排班数据下移 1088 moveDaysDown() { 1089 console.log("moveRowDown", arguments); 1090 let row = this.gridCtrl.api.getSelectedNodes(); 1091 if (!row || !row.length) { 1092 Message.error("请选择一条数据"); 1093 return; 1094 } 1095 row = row[0]; 1096 if (row.data.isRowGroup) { 1097 return; 1098 } 1099 if (row.rowIndex === this.rowData.length - 1) { 1100 Message.error("已是最底层"); 1101 return; 1102 } else { 1103 let currentRowData = this._getRowData(row.id); 1104 let targetRowId = +row.id + 1; 1105 let targetRowData = this._getRowData(targetRowId); 1106 if (targetRowData.isRowGroup) { 1107 targetRowId = +row.id + 2; 1108 targetRowData = this._getRowData(targetRowId); 1109 } 1110 1111 const fields = [ 1112 "monday", 1113 "tuesday", 1114 "wednesday", 1115 "thursday", 1116 "friday", 1117 "saturday", 1118 "sunday", 1119 ]; 1120 fields.forEach((field) => { 1121 let value = currentRowData[field]; 1122 currentRowData[field] = targetRowData[field]; 1123 targetRowData[field] = value; 1124 }); 1125 this._setRowData(targetRowId, targetRowData); 1126 this._setRowData(row.id, currentRowData); 1127 this._getRowNodeByID(targetRowId).setSelected(true, true); 1128 this.$emit("changed", [currentRowData, targetRowData]); 1129 } 1130 }, 1131 //交换两行排班数据 1132 exchangeDays() { 1133 console.log("exchangeDays", arguments); 1134 if (this.selectedRanges.length === 2) { 1135 this._swapRanges(this.selectedRanges[0], this.selectedRanges[1]); 1136 } else { 1137 return "必须选中两个相同大小的区域,且不能重叠。"; 1138 } 1139 }, 1140 // 获取单元格Cell数据 1141 getCellData(rowId, colId) { 1142 return this._getCellData(rowId, colId); 1143 }, 1144 //设置单元格Cell数据 1145 setCellData(rowId, colId, value) { 1146 this._setCellData(rowId, colId, value); 1147 }, 1148 //获取行Row数据 1149 getRowData(rowId) { 1150 return this._getRowData(rowId); 1151 }, 1152 //设置区域为某个值 1153 setRangeValue(range, data) { 1154 this._setRangValue(range, data); 1155 }, 1156 }, 1157 components: { 1158 AgGridVue, 1159 SsCellRender, 1160 SsHeaderRender, 1161 SsRowHeaderRender, 1162 contextMenu, 1163 }, 1164 }; 1165 </script> 1166 <style lang="scss"> 1167 .drag-copying { 1168 cursor: copy; 1169 .ag-cell.ag-cell-range-selected { 1170 background: lightpink !important; 1171 } 1172 } 1173 </style> 1174 <style lang="scss" scoped> 1175 .JheGridTable { 1176 width: 100%; 1177 height: 100%; 1178 background: #fff; 1179 ::v-deep .group-cell { 1180 background-color: #d7e7f8; 1181 font-weight: bold; 1182 } 1183 ::v-deep .ag-cell { 1184 padding: 3px 11px; 1185 font-size: 14px; 1186 } 1187 1188 ::v-deep .ag-pinned-left-cols-container { 1189 .ag-cell { 1190 border-right: 1px solid #d9dcde !important; 1191 } 1192 .ag-cell-focus { 1193 border-right: 1px solid #0091ea !important; 1194 } 1195 } 1196 ::v-deep .ag-center-cols-container { 1197 .ag-cell { 1198 border-right: 1px solid #d9dcde; 1199 } 1200 } 1201 ::v-deep .ag-center-cols-container { 1202 .ag-theme-balham .ag-ltr .ag-cell { 1203 border-right: 1px solid #d9dcde !important; 1204 } 1205 .ag-cell-focus { 1206 border-right: 1px solid #0091ea !important; 1207 } 1208 } 1209 ::v-deep .ag-pinned-right-cols-container { 1210 .ag-cell { 1211 border-right: 1px solid #d9dcde !important; 1212 } 1213 .ag-cell-focus { 1214 border-right: 1px solid #0091ea !important; 1215 } 1216 } 1217 1218 .ag-theme-balham .ag-cell-range-selected:not(.ag-cell-focus) { 1219 //border-right: 1px solid #d9dcde!important; 1220 } 1221 //.ag-cell-range-selected{border-right: 1px solid #0091ea} 1222 ::v-deep .ag-selection-checkbox:not(.ag-hidden) ~ .ag-cell-value:not(:empty) { 1223 margin: 0; 1224 } 1225 1226 ::v-deep .ag-theme-balham .ag-header-cell::after, 1227 ::v-deep .ag-theme-balham .ag-header-group-cell::after { 1228 border-right: 1px solid rgba(189, 195, 199, 0.5); 1229 content: " "; 1230 height: 100%; 1231 margin-top: 0; 1232 position: absolute; 1233 text-indent: -2000px; 1234 top: 0; 1235 } 1236 1237 ::v-deep .ag-header-cell-label { 1238 justify-content: center; 1239 text-align: center; 1240 } 1241 ::v-deep .ag-root-wrapper-body.ag-layout-normal { 1242 height: 100% !important; 1243 } 1244 ::v-deep .ctx-menu-body { 1245 display: flex; 1246 flex-flow: row nowrap; 1247 align-items: stretch; 1248 min-width: 160px; 1249 } 1250 } 1251 </style>
ss-cell-render.vue 内容如下:
1 <!-- 2 Component: SsCellRender 3 Description: 电子表格单元格渲染组建 4 --> 5 6 <template> 7 <div :class="['SsCellRender', selectedClass]"> 8 <el-row v-if="shifts.length > 0"> 9 <el-col 10 v-for="(s, i) in shifts" 11 :key="i" 12 :span="Math.floor(24 / shifts.length)" 13 :style="s" 14 ><!--{{cellName(s.id)}}-->{{ s.shiftName }}</el-col 15 > 16 </el-row> 17 <el-row v-else> 18 <el-col></el-col> 19 </el-row> 20 <div class="ss-cell-mask"></div> 21 <div 22 v-show="isShowDropCopyHolder" 23 class="DragCopyHolder" 24 @mousedown="onMouseHold" 25 > 26 <!-- <Icon type="md-add" color="white" /> --> 27 <i class="el-icon-plus" style="color: white"></i> 28 </div> 29 </div> 30 </template> 31 <script> 32 export default { 33 name: "SsCellRender", 34 data() { 35 return { 36 data: {}, 37 rowIndex: null, 38 colId: null, 39 rowId: null, 40 context: null, 41 isShowDropCopyHolder: false, 42 value: "", 43 insideParams: null, 44 shiftName: "", 45 }; 46 }, 47 computed: { 48 // 班次背景色方法 49 styles() { 50 return this.shiftIdInfo; 51 }, 52 53 selectedClass() { 54 return { 55 // "ag-cell-range-selected": this.isSelecting, 56 "is-drag-copying": this.isDragCopying, 57 }; 58 }, 59 shifts() { 60 return this.value.shifts; 61 }, 62 }, 63 watch: {}, 64 methods: { 65 initialize(params) { 66 this.value = params.value; 67 this.data = params.data; 68 // this.data.rowHeight = this.shifts.length * 26; 69 this.rowIndex = params.rowIndex; 70 this.rowId = params.node.id; 71 this.colId = params.column.colId; 72 this.params.eGridCell.style["padding"] = 0; 73 // this.params.eGridCell.style["padding-right"] = 0; 74 this.context = params.context; 75 // console.log(this.params, this); 76 }, 77 cellName(id) { 78 let shiftName = ""; 79 this.shifts.forEach((e) => { 80 if (this.shiftIdInfo[id]) { 81 if (e.days !== null && e.days !== undefined) { 82 shiftName = this.shiftIdInfo[id].name + "-" + e.days; 83 } else { 84 shiftName = this.shiftIdInfo[id].name; 85 } 86 } 87 }); 88 return shiftName; 89 }, 90 onMouseHold() { 91 this.context.isDragCopying = true; 92 }, 93 }, 94 beforeUpdate() { 95 this.initialize(this.params); 96 }, 97 created() { 98 this.initialize(this.params); 99 }, 100 mounted() {}, 101 beforeDestroy() {}, 102 }; 103 </script> 104 105 <style lang="scss" scoped> 106 .SsCellRender { 107 padding: 5px 11px; 108 //height: 100%; 109 position: relative; 110 cursor: cell; 111 112 .ss-cell-mask { 113 position: absolute; 114 top: 0; 115 left: 0; 116 right: 0; 117 bottom: 0; 118 background: #0091ea; 119 opacity: 0; 120 } 121 ::v-deep .ag-cell-range-selected .ss-cell-mask { 122 opacity: 0.3; 123 } 124 125 .DragCopyHolder { 126 position: absolute; 127 right: 0px; 128 bottom: 0px; 129 font-size: 10px; 130 background: red; 131 width: 12px; 132 height: 12px; 133 border: 0; 134 cursor: copy; 135 } 136 .DragCopyHolder:hover { 137 border: 1px solid whitesmoke; 138 width: 14px; 139 height: 14px; 140 } 141 .DragCopyHolder i { 142 position: absolute; 143 left: 1px; 144 top: 1px; 145 } 146 147 ::v-deep .el-col { 148 height: 22px; 149 overflow: hidden; 150 line-height: 22px; 151 } 152 ::v-deep .el-col:first-child { 153 border-radius: 11px 0 0 11px; 154 } 155 ::v-deep .el-col:last-child { 156 border-radius: 0 11px 11px 0; 157 } 158 ::v-deep .el-col:first-child:last-child { 159 border-radius: 11px; 160 } 161 } 162 </style>
ss-header-render.vue内容如下:
1 <!-- 2 Component: SsHeaderRender 3 Description: 电子表格的自定义Header 4 --> 5 <template> 6 <div 7 class="SsHeaderRender ag-cell-label-container" 8 role="presentation" 9 :style="style" 10 > 11 <!-- <span ref="eMenu" class="ag-header-icon ag-header-cell-menu-button"></span> --> 12 13 <div 14 ref="eLabel" 15 class="ag-header-cell-label" 16 role="presentation" 17 style="flex-wrap: wrap" 18 @click="onSortChanged" 19 > 20 <!-- <div 21 ref="eText" 22 class="ag-header-cell-text" 23 role="columnheader" 24 style="width:100%;border-bottom:1px solid #d9dcde" 25 >{{title}}</div> 26 <div ref="eText" class="ag-header-cell-text" role="columnheader">{{subtitle}}</div>--> 27 <div ref="eText" class="ag-header-cell-text" role="columnheader"> 28 {{ title }} {{ subtitle }} 29 </div> 30 31 <!-- <span ref="eFilter" class="ag-header-icon ag-filter-icon"></span> --> 32 <!-- <span v-if="isSortAscending" ref="eSortOrder" class="ag-header-icon ag-sort-order"></span> --> 33 <span 34 v-if="isSortAscending" 35 ref="eSortAsc" 36 class="ag-header-icon ag-sort-ascending-icon" 37 > 38 <span class="ag-icon ag-icon-asc" unselectable="on"></span> 39 </span> 40 <span 41 v-if="isSortDescending" 42 ref="eSortDesc" 43 class="ag-header-icon ag-sort-descending-icon" 44 > 45 <span class="ag-icon ag-icon-desc" unselectable="on"></span> 46 </span> 47 <span 48 v-if="params.enableMenu" 49 ref="eMenu" 50 class="ag-header-icon ag-header-cell-menu-button" 51 aria-hidden="true" 52 @click.stop="onMenuClicked" 53 > 54 <span class="ag-icon ag-icon-menu" unselectable="on"></span> 55 </span> 56 </div> 57 </div> 58 </template> 59 <script> 60 export default { 61 name: "SsHeaderRender", 62 // components: {}, 63 // directives: {}, 64 // filters: {}, 65 data() { 66 return { 67 style: {}, 68 }; 69 }, 70 // props: [], 71 computed: { 72 title() { 73 if ( 74 this.params.displayName === "周六" || 75 this.params.displayName === "周日" 76 ) { 77 this.style = { 78 color: "#CC0001", 79 }; 80 } else { 81 this.style = {}; 82 } 83 return this.params.displayName; 84 }, 85 subtitle() { 86 return this.params.subtitle; 87 }, 88 isSortAscending() { 89 return this.params.enableSorting && this.params.column.isSortAscending(); 90 }, 91 isSortDescending() { 92 return this.params.enableSorting && this.params.column.isSortDescending(); 93 }, 94 }, 95 // watch: {}, 96 methods: { 97 onMenuClicked() { 98 this.params.showColumnMenu(this.$refs.eMenu); 99 }, 100 onSortChanged(event) { 101 this.params.enableSorting && this.params.progressSort(); 102 this.params.onColumnClick && 103 this.params.onColumnClick(event, this.params); 104 // console.log( 105 // "onSortChanged", 106 // this.params, 107 // this.params.api.getDisplayedRowCount() 108 // ); 109 }, 110 }, 111 }; 112 </script> 113 <style lang="scss" scoped> 114 .ag-header-cell .SsHeaderRender:hover { 115 .ag-header-icon.ag-header-cell-menu-button { 116 display: inline; 117 opacity: 1 !important; 118 transition: opacity 0.2s ease 0s, border 0.2s ease 0s; 119 } 120 } 121 .SsHeaderRender { 122 .ag-header-icon.ag-header-cell-menu-button { 123 display: none; 124 opacity: 0 !important; 125 transition: opacity 0.2s ease 0s, border 0.2s ease 0s; 126 } 127 } 128 </style>
ss-row-header-render.vue内容如下:
1 <!-- 2 Component: SsRowHeaderRender 3 Description: 电子表格行头的Render 4 --> 5 <template> 6 <div :class="['SsRowHeaderRender']" @mousedown="selectRow"> 7 <span>{{ rowIndex + 1 }}</span> 8 </div> 9 </template> 10 <script> 11 export default { 12 name: "SsRowHeaderRender", 13 data() { 14 return { 15 columnDefines: null, 16 }; 17 }, 18 computed: { 19 rowIndex() { 20 return this.params.rowIndex; 21 }, 22 23 value() { 24 return +this.params.value; 25 }, 26 }, 27 watch: {}, 28 methods: { 29 initialize(params) { 30 this.columnDefines = params.columnApi.getColumns(); 31 this.params.eGridCell.style["padding-left"] = 0; 32 this.params.eGridCell.style["padding-right"] = 0; 33 }, 34 selectRow() { 35 this.params.node.setSelected(true, true); 36 }, 37 }, 38 beforeUpdate() { 39 this.initialize(this.params); 40 }, 41 created() { 42 this.initialize(this.params); 43 }, 44 mounted() {}, 45 beforeDestroy() {}, 46 }; 47 </script> 48 49 <style lang="scss" scoped> 50 .SsRowHeaderRender { 51 & { 52 padding-left: 11px; 53 padding-right: 11px; 54 height: 100%; 55 } 56 } 57 58 .ag-selection-checkbox { 59 padding-left: 11px; 60 } 61 </style>
ctx-menu.vue内容如下:
1 <template> 2 <div 3 :id="id" 4 ref="contextMenu" 5 :style="ctxStyle" 6 class="ctx-menu-container" 7 @click.stop 8 @contextmenu.stop 9 > 10 <div style="background-color: transparent" class="ctx open"> 11 <ul 12 role="menu" 13 class="ctx-menu" 14 :class="{ 15 'ctx-menu-right': align === 'right', 16 'ctx-menu-left': align === 'left', 17 }" 18 > 19 <slot /> 20 </ul> 21 </div> 22 </div> 23 </template> 24 25 <script> 26 import { createBodyClickListener } from "./body-click-listener"; 27 28 // const EVENT_LIST = ['click', 'contextmenu', 'keydown'] 29 30 export default { 31 name: "ContextMenu", 32 props: { 33 id: { 34 type: String, 35 default: "default-ctx", 36 }, 37 }, 38 data() { 39 return { 40 locals: {}, 41 align: "left", 42 ctxTop: 0, 43 ctxLeft: 0, 44 ctxVisible: false, 45 bodyClickListener: createBodyClickListener((e) => { 46 const isOpen = !!this.ctxVisible; 47 const outsideClick = isOpen && !this.$el.contains(e.target); 48 49 if (outsideClick) { 50 if (e.which !== 1) { 51 e.preventDefault(); 52 e.stopPropagation(); 53 return false; 54 } else { 55 this.ctxVisible = false; 56 this.$emit("ctx-cancel", this.locals); 57 e.stopPropagation(); 58 } 59 } else { 60 this.ctxVisible = false; 61 this.$emit("ctx-close", this.locals); 62 } 63 }), 64 }; 65 }, 66 computed: { 67 ctxStyle() { 68 return { 69 display: this.ctxVisible ? "block" : "none", 70 top: (this.ctxTop || 0) + "px", 71 left: (this.ctxLeft || 0) + "px", 72 }; 73 }, 74 }, 75 watch: { 76 ctxVisible(newVal, oldVal) { 77 if (oldVal === true && newVal === false) { 78 this.bodyClickListener.stop((e) => { 79 // this.locals = {} 80 }); 81 } 82 }, 83 }, 84 methods: { 85 /* 86 * this function handles some cross-browser compat issues 87 * thanks to https://github.com/callmenick/Custom-Context-Menu 88 */ 89 setPositionFromEvent(e, data, parentPosition) { 90 e = e || window.event; 91 92 const scrollingElement = 93 document.scrollingElement || document.documentElement; 94 95 if (e.pageX || e.pageY) { 96 this.ctxLeft = e.pageX; 97 this.ctxTop = e.pageY - scrollingElement.scrollTop; 98 } else if (e.clientX || e.clientY) { 99 this.ctxLeft = e.clientX + scrollingElement.scrollLeft; 100 this.ctxTop = e.clientY + scrollingElement.scrollTop; 101 } 102 this.ctxTop = this.ctxTop - parentPosition.top; 103 this.ctxLeft = this.ctxLeft - parentPosition.left; 104 105 this.$nextTick(() => { 106 const menu = this.$el; 107 const minHeight = 108 (menu.style.minHeight || menu.style.height).replace("px", "") || 32; 109 const minWidth = 110 (menu.style.minWidth || menu.style.width).replace("px", "") || 32; 111 const scrollHeight = menu.scrollHeight || minHeight; 112 let scrollWidth = menu.scrollWidth || minWidth; 113 const findSubItem = data.find((item) => { 114 return item.children && item.children.length > 0; 115 }); // 存在二级菜单 116 if (findSubItem) { 117 scrollWidth = menu.scrollWidth * 2 || minWidth; 118 } 119 const largestHeight = 120 window.innerHeight - scrollHeight - parentPosition.top - 25; 121 const largestWidth = 122 window.innerWidth - scrollWidth - parentPosition.left - 25; 123 if (this.ctxTop > largestHeight) this.ctxTop = largestHeight; 124 if (this.ctxLeft > largestWidth) this.ctxLeft = largestWidth; 125 }); 126 return e; 127 }, 128 getElementLeftAndTop(element) { 129 var left = element.offsetLeft; // 当前元素左边距 130 var top = element.offsetTop; // 当前元素上边距 131 var parent = element.offsetParent; // 当前元素的父级元素 132 while (parent !== null) { 133 left += parent.offsetLeft; // 累加左边距 134 top += parent.offsetTop; // 累加上边距 135 parent = parent.offsetParent; // 依次获取父元素 136 } 137 return { left, top }; 138 }, 139 140 open(e, data, parentElement) { 141 if (this.ctxVisible) this.ctxVisible = false; 142 this.ctxVisible = true; 143 this.$emit("ctx-open", (this.locals = data || {})); 144 const parentPosition = this.getElementLeftAndTop(parentElement); 145 this.setPositionFromEvent(e, data, parentPosition); 146 this.$el.setAttribute("tab-index", -1); 147 this.bodyClickListener.start(); 148 return this; 149 }, 150 151 close() { 152 this.ctxVisible = false; 153 }, 154 }, 155 }; 156 </script> 157 158 <style lang="scss" scoped> 159 .ctx { 160 position: relative; 161 } 162 163 .ctx-menu { 164 position: absolute; 165 top: 100%; 166 left: 0; 167 z-index: 1000; 168 display: none; 169 float: left; 170 min-width: 160px; 171 padding: 5px 0; 172 margin: 2px 0 0; 173 font-size: 0.9rem; 174 color: #373a3c; 175 text-align: left; 176 list-style: none; 177 background-color: #fff; 178 -webkit-background-clip: padding-box; 179 background-clip: padding-box; 180 border: 1px solid rgba(0, 0, 0, 0.15); 181 border-radius: 0.25rem; 182 -moz-box-shadow: 0 0 5px #ccc; 183 -webkit-box-shadow: 0 0 5px #ccc; 184 box-shadow: 0 0 5px #ccc; 185 } 186 187 .ctx-divider { 188 height: 1px; 189 margin: 0.5rem 0; 190 overflow: hidden; 191 background-color: #e5e5e5; 192 } 193 194 .ctx-group { 195 width: 100%; 196 } 197 .ctx-item { 198 display: block; 199 padding: 7px 16px; 200 clear: both; 201 font-weight: 14px; 202 line-height: normal; 203 color: #373a3c; 204 text-align: inherit; 205 white-space: nowrap; 206 background: none; 207 border: 0; 208 cursor: pointer; 209 display: flex; 210 justify-content: space-between; 211 } 212 213 .ctx-item:focus, 214 .ctx-item:hover { 215 color: #2b2d2f; 216 text-decoration: none; 217 background-color: #f5f5f5; 218 cursor: normal; 219 } 220 221 .ctx-item.active, 222 .ctx-item.active:focus, 223 .ctx-item.active:hover { 224 color: #fff; 225 text-decoration: none; 226 background-color: #0275d8; 227 outline: 0; 228 } 229 230 .ctx-item.disabled, 231 .ctx-item.disabled:focus, 232 .ctx-item.disabled:hover { 233 color: #818a91; 234 } 235 236 .ctx-item.disabled:focus, 237 .ctx-item.disabled:hover { 238 text-decoration: none; 239 cursor: not-allowed; 240 background-color: transparent; 241 background-image: none; 242 filter: "progid:DXImageTransform.Microsoft.gradient(enabled = false)"; 243 } 244 245 .open > .ctx-menu { 246 display: block; 247 } 248 249 .open > a { 250 outline: 0; 251 } 252 253 .ctx-menu-right { 254 right: 0; 255 left: auto; 256 } 257 258 .ctx-menu-left { 259 right: auto; 260 left: 0; 261 } 262 263 .ctx-header { 264 display: block; 265 padding: 3px 20px; 266 font-size: 0.9rem; 267 line-height: 1.5; 268 color: #818a91; 269 white-space: nowrap; 270 } 271 272 .ctx-backdrop { 273 position: fixed; 274 top: 0; 275 right: 0; 276 bottom: 0; 277 left: 0; 278 z-index: 990; 279 } 280 281 .pull-right > .ctx-menu { 282 right: 0; 283 left: auto; 284 } 285 286 .ctx-menu-container { 287 position: fixed; 288 padding: 0; 289 border: 1px solid #bbb; 290 background-color: whitesmoke; 291 z-index: 99999; 292 box-shadow: 0 5px 11px 0 rgba(0, 0, 0, 0.18), 0 4px 15px 0 rgba(0, 0, 0, 0.15); 293 } 294 </style>
body-click-listener.js内容如下:
1 /** 2 * When listening for an outside click, we set useCapture = true. 3 * This way, we can prevent other click listeners from firing when performing the 'click-out'. 4 * If useCapture is set to false, the handlers fire backwards 5 */ 6 export const createBodyClickListener = function(fn) { 7 let isListening = false; 8 9 /* === public api ========================================== */ 10 return { 11 get isListening() { 12 return isListening; 13 }, 14 15 start(cb) { 16 window.addEventListener("click", _onclick, true); 17 window.addEventListener("keyup", _onescape, true); 18 isListening = true; 19 if (typeof cb === "function") cb(); 20 }, 21 22 stop(cb) { 23 window.removeEventListener("click", _onclick, true); 24 window.removeEventListener("keyup", _onescape, true); 25 isListening = false; 26 if (typeof cb === "function") cb(); 27 } 28 }; 29 30 /* === private helpers ===================================== */ 31 function _onclick(e) { 32 e.preventDefault(); 33 if (typeof fn === "function") fn(e); 34 } 35 36 function _onescape(e) { 37 if (e.keyCode === 27) _onclick(e); 38 } 39 };
utils下tools.js内容如下:
1 /** 2 * 获得原时间前后几天的日期 3 * @param {*} src 原时间 4 * @param {*} offset 偏移量,正负天数 5 */ 6 export const offsetDate = (src, offset) => { 7 let dest = new Date(src); 8 9 let timespan = dest.setDate(dest.getDate() + offset); 10 dest = new Date(timespan); 11 12 return dest; 13 }; 14 15 /** 16 * 获得原日期所在周的所有日期 17 * @param {*} src 原日期 18 */ 19 export const getOneWeekDate = (src) => { 20 let source = new Date(src); 21 let keys = [ 22 "sunday", 23 "monday", 24 "tuesday", 25 "wednesday", 26 "thursday", 27 "friday", 28 "saturday", 29 "sunday", 30 ]; 31 let mapWeekDays = {}; 32 33 let date = source; 34 let wd = date.getDay(); 35 mapWeekDays[keys[wd]] = date; 36 37 for (wd = date.getDay() - 1; wd >= 1; wd--) { 38 date = offsetDate(date, -1); 39 mapWeekDays[keys[wd]] = date; 40 } 41 date = source; 42 for (wd = source.getDay() + 1; wd <= 8; wd++) { 43 date = offsetDate(date, +1); 44 mapWeekDays[keys[wd]] = date; 45 } 46 47 return mapWeekDays; 48 };
父组件的用法示例:
1 <template> 2 <div style="width: 100%; height: 500px"> 3 <div> 4 <jhe-button @click="initData">加载(2022-12-25)周数据</jhe-button> 5 <jhe-button @click="handleSelectRow">选中第二行</jhe-button> 6 <jhe-button @click="handleSelectColumn">选中周一列</jhe-button> 7 <jhe-button @click="handleSelectCells">选中单元格</jhe-button> 8 <jhe-button @click="handleMoveRowUp">行上移</jhe-button> 9 <jhe-button @click="handleMoveRowDown">行下移</jhe-button> 10 <jhe-button @click="handleMoveDaysUp">排班数据上移</jhe-button> 11 <jhe-button @click="handleMoveDaysDown">排班数据下移</jhe-button> 12 <jhe-button @click="handleExchangeDays">班次对换</jhe-button> 13 <jhe-button @click="handleGetCellData" 14 >获取单元格数据(第二行,周二列)</jhe-button 15 > 16 <jhe-button @click="handleSetCellData" 17 >设置单元格数据(第三行,周四列)</jhe-button 18 > 19 <jhe-button @click="handleGetRowData">获取行数据(第五行)</jhe-button> 20 <jhe-button @click="handleSetRangeValue">设置区域值</jhe-button> 21 </div> 22 <jhe-grid-table 23 :startDate="startDate" 24 :columns="columns" 25 :rowData="rowData" 26 :headerHeight="headerHeight" 27 :ctxMenuItems="ctxMenuItems" 28 @init="onInit" 29 @loaded="onLoaded" 30 @changed="onChanged" 31 @cellClicked="onCellClicked" 32 @command="onCommand" 33 ></jhe-grid-table> 34 </div> 35 </template> 36 <script> 37 export default { 38 name: "DemoJheGridTable", 39 data() { 40 return { 41 //需要显示的数据 42 rowData: [], 43 headerHeight: 30, 44 columns: [], 45 jheGridApi: null, 46 startDate: null, 47 targetDate: new Date("2022-12-05"), 48 ctxMenuItems: [], //右键菜单项 49 cellClickedPositon: null, 50 cellClickdSchedule: null, 51 changedRowData: null, 52 selectedRanges: null, 53 }; 54 }, 55 created() { 56 this.startDate = this.getStartDateByTargetDate(this.targetDate); 57 this.columns = [ 58 { 59 colId: 1, 60 headerName: "组号", 61 field: "groupNO", 62 minWidth: 60, 63 //pinned: "left", 64 sortable: false, 65 filter: false, 66 colSpan: (params) => { 67 if (params.data.isRowGroup === 1) { 68 return this.columns.length; 69 } else { 70 return 1; 71 } 72 }, 73 cellClassRules: { 74 "group-cell": "data.isRowGroup === 1", 75 }, 76 }, 77 { 78 colId: 2, 79 headerName: "姓名", 80 field: "userName", 81 minWidth: 80, 82 //pinned: "left", 83 sortable: false, 84 filter: false, 85 }, 86 { 87 colId: 3, 88 headerName: "层级", 89 field: "level", 90 minWidth: 60, 91 //pinned: "left", 92 sortable: false, 93 filter: false, 94 }, 95 { 96 colId: 4, 97 headerName: "岗位", 98 field: "post", 99 minWidth: 80, 100 //pinned: "left", 101 sortable: false, 102 filter: false, 103 }, 104 { 105 colId: 5, 106 headerName: "职称", 107 field: "titleName", 108 minWidth: 90, 109 //pinned: "left", 110 sortable: false, 111 filter: false, 112 }, 113 { 114 colId: "w1", 115 headerName: "周一", 116 field: "monday", 117 minWidth: 90, 118 sortable: false, 119 filter: false, 120 selectable: true, 121 }, 122 { 123 colId: "w2", 124 headerName: "周二", 125 field: "tuesday", 126 minWidth: 90, 127 sortable: false, 128 filter: false, 129 selectable: true, 130 }, 131 { 132 colId: "w3", 133 headerName: "周三", 134 field: "wednesday", 135 minWidth: 90, 136 sortable: false, 137 filter: false, 138 selectable: true, 139 }, 140 { 141 colId: "w4", 142 headerName: "周四", 143 field: "thursday", 144 minWidth: 90, 145 sortable: false, 146 filter: false, 147 selectable: true, 148 }, 149 { 150 colId: "w5", 151 headerName: "周五", 152 field: "friday", 153 minWidth: 90, 154 sortable: false, 155 filter: false, 156 selectable: true, 157 }, 158 { 159 colId: "w6", 160 headerName: "周六", 161 field: "saturday", 162 minWidth: 90, 163 sortable: false, 164 filter: false, 165 selectable: true, 166 }, 167 { 168 colId: "w7", 169 headerName: "周日", 170 field: "sunday", 171 minWidth: 90, 172 sortable: false, 173 filter: false, 174 selectable: true, 175 }, 176 { 177 colId: 13, 178 headerName: "公休", 179 //field: "dayOff", 180 minWidth: 65, 181 //pinned: "right", 182 sortable: false, 183 filter: false, 184 hide: false, 185 valueGetter: (params) => { 186 let keys = [ 187 "monday", 188 "tuesday", 189 "wednesday", 190 "thursday", 191 "friday", 192 "saturday", 193 "sunday", 194 ]; 195 let allDays = 0; 196 keys.forEach((key) => { 197 params.data[key].shifts.forEach((s) => { 198 allDays = allDays + s.days; 199 }); 200 }); 201 return allDays; 202 }, 203 }, 204 { 205 colId: 14, 206 headerName: "管床", 207 field: "beds", 208 minWidth: 70, 209 sortable: false, 210 //pinned: "right", 211 filter: false, 212 hide: true, 213 }, 214 ]; 215 let data = [ 216 { 217 isRowGroup: 1, 218 groupNO: "一组", 219 }, 220 { 221 id: "220", 222 userName: "孙丽", 223 employeeNO: "220", 224 beds: "4,5,6", 225 level: "0", 226 memo: null, 227 displayOrder: 1000, 228 groupNO: "一组", 229 durations: 0, 230 dayOff: 15, 231 monday: { 232 id: "73098", 233 date: "2022-12-05", 234 shifts: [], 235 }, 236 tuesday: { 237 id: "73099", 238 date: "2022-12-06", 239 shifts: [], 240 }, 241 wednesday: { 242 id: "73100", 243 date: "2022-12-07", 244 shifts: [], 245 }, 246 thursday: { 247 id: "73101", 248 date: "2022-12-08", 249 shifts: [], 250 }, 251 friday: { 252 id: "73102", 253 date: "2022-12-09", 254 shifts: [], 255 }, 256 saturday: { 257 id: "73103", 258 date: "2022-12-10", 259 shifts: [], 260 }, 261 sunday: { 262 id: "73104", 263 date: "2022-12-11", 264 shifts: [], 265 }, 266 post: "5", 267 title: "", 268 titleName: "", 269 }, 270 { 271 id: "21", 272 userName: "刘世卿", 273 employeeNO: "21", 274 beds: null, 275 level: "0", 276 memo: null, 277 displayOrder: 1001, 278 groupNO: "一组", 279 durations: 46800000, 280 dayOff: 15, 281 monday: { 282 id: "73091", 283 date: "2022-12-05", 284 shifts: [], 285 }, 286 tuesday: { 287 id: "73092", 288 date: "2022-12-06", 289 shifts: [ 290 { 291 id: 2860, 292 shiftName: "观P2", 293 background: "#2D8CF0", 294 color: "#FFFFFF", 295 days: 1, 296 totalDays: null, 297 }, 298 ], 299 }, 300 wednesday: { 301 id: "73093", 302 date: "2022-12-07", 303 shifts: [ 304 { 305 id: 2860, 306 shiftName: "观P2", 307 background: "#2D8CF0", 308 color: "#FFFFFF", 309 days: 1, 310 totalDays: null, 311 }, 312 ], 313 }, 314 thursday: { 315 id: "73094", 316 date: "2022-12-08", 317 shifts: [], 318 }, 319 friday: { 320 id: "73095", 321 date: "2022-12-09", 322 shifts: [], 323 }, 324 saturday: { 325 id: "73096", 326 date: "2022-12-10", 327 shifts: [], 328 }, 329 sunday: { 330 id: "73097", 331 date: "2022-12-11", 332 shifts: [], 333 }, 334 post: "0", 335 title: "", 336 titleName: "", 337 }, 338 { 339 id: "354", 340 userName: "孙娉", 341 employeeNO: "354", 342 beds: null, 343 level: "0", 344 memo: null, 345 displayOrder: 1002, 346 groupNO: "一组", 347 durations: 0, 348 dayOff: 15, 349 monday: { 350 id: "73105", 351 date: "2022-12-05", 352 shifts: [], 353 }, 354 tuesday: { 355 id: "73106", 356 date: "2022-12-06", 357 shifts: [], 358 }, 359 wednesday: { 360 id: "73107", 361 date: "2022-12-07", 362 shifts: [], 363 }, 364 thursday: { 365 id: "73108", 366 date: "2022-12-08", 367 shifts: [], 368 }, 369 friday: { 370 id: "73109", 371 date: "2022-12-09", 372 shifts: [], 373 }, 374 saturday: { 375 id: "73110", 376 date: "2022-12-10", 377 shifts: [], 378 }, 379 sunday: { 380 id: "73111", 381 date: "2022-12-11", 382 shifts: [], 383 }, 384 post: "0", 385 title: "1", 386 titleName: "初级(护士)", 387 }, 388 { 389 id: "695", 390 userName: "陈伟萍", 391 employeeNO: "695", 392 beds: null, 393 level: "0", 394 memo: null, 395 displayOrder: 1003, 396 groupNO: "一组", 397 durations: 23400000, 398 dayOff: 15, 399 monday: { 400 id: "73084", 401 date: "2022-12-05", 402 shifts: [], 403 }, 404 tuesday: { 405 id: "73085", 406 date: "2022-12-06", 407 shifts: [ 408 { 409 id: 2860, 410 shiftName: "观P2", 411 background: "#2D8CF0", 412 color: "#FFFFFF", 413 days: 1, 414 totalDays: null, 415 }, 416 ], 417 }, 418 wednesday: { 419 id: "73086", 420 date: "2022-12-07", 421 shifts: [], 422 }, 423 thursday: { 424 id: "73087", 425 date: "2022-12-08", 426 shifts: [], 427 }, 428 friday: { 429 id: "73088", 430 date: "2022-12-09", 431 shifts: [], 432 }, 433 saturday: { 434 id: "73089", 435 date: "2022-12-10", 436 shifts: [], 437 }, 438 sunday: { 439 id: "73090", 440 date: "2022-12-11", 441 shifts: [], 442 }, 443 post: "0", 444 title: "", 445 titleName: "", 446 }, 447 { 448 id: "133", 449 userName: "纪丽鑫", 450 employeeNO: "133", 451 beds: "1111", 452 level: "3", 453 memo: null, 454 displayOrder: 1005, 455 groupNO: "一组", 456 durations: 46800000, 457 dayOff: 15, 458 monday: { 459 id: "73147", 460 date: "2022-12-05", 461 shifts: [], 462 }, 463 tuesday: { 464 id: "73148", 465 date: "2022-12-06", 466 shifts: [ 467 { 468 id: 1000, 469 shiftName: "抢N", 470 background: "#EA1A1A", 471 color: "black", 472 days: 2, 473 totalDays: null, 474 }, 475 ], 476 }, 477 wednesday: { 478 id: "73149", 479 date: "2022-12-07", 480 shifts: [ 481 { 482 id: 2860, 483 shiftName: "观P2", 484 background: "#2D8CF0", 485 color: "#FFFFFF", 486 days: 1, 487 totalDays: null, 488 }, 489 ], 490 }, 491 thursday: { 492 id: "73150", 493 date: "2022-12-08", 494 shifts: [], 495 }, 496 friday: { 497 id: "73151", 498 date: "2022-12-09", 499 shifts: [], 500 }, 501 saturday: { 502 id: "73152", 503 date: "2022-12-10", 504 shifts: [], 505 }, 506 sunday: { 507 id: "73153", 508 date: "2022-12-11", 509 shifts: [], 510 }, 511 post: "4", 512 title: "", 513 titleName: "", 514 }, 515 { 516 id: "110110", 517 userName: "童谣", 518 employeeNO: "110110", 519 beds: null, 520 level: "1", 521 memo: null, 522 displayOrder: 1007, 523 groupNO: "一组", 524 durations: 46800000, 525 dayOff: 15, 526 monday: { 527 id: "73182", 528 date: "2022-12-05", 529 shifts: [], 530 }, 531 tuesday: { 532 id: "73183", 533 date: "2022-12-06", 534 shifts: [ 535 { 536 id: 2860, 537 shiftName: "观P2", 538 background: "#2D8CF0", 539 color: "#FFFFFF", 540 days: 1, 541 totalDays: null, 542 }, 543 ], 544 }, 545 wednesday: { 546 id: "73184", 547 date: "2022-12-07", 548 shifts: [ 549 { 550 id: 2860, 551 shiftName: "观P2", 552 background: "#2D8CF0", 553 color: "#FFFFFF", 554 days: 1, 555 totalDays: null, 556 }, 557 ], 558 }, 559 thursday: { 560 id: "73185", 561 date: "2022-12-08", 562 shifts: [], 563 }, 564 friday: { 565 id: "73186", 566 date: "2022-12-09", 567 shifts: [], 568 }, 569 saturday: { 570 id: "73187", 571 date: "2022-12-10", 572 shifts: [], 573 }, 574 sunday: { 575 id: "73188", 576 date: "2022-12-11", 577 shifts: [], 578 }, 579 post: "6", 580 title: "1", 581 titleName: "初级(护士)", 582 }, 583 { 584 id: "134", 585 userName: "辛琤琤", 586 employeeNO: "134", 587 beds: "7,8,9", 588 level: "2", 589 memo: null, 590 displayOrder: 1008, 591 groupNO: "一组", 592 durations: 0, 593 dayOff: 15, 594 monday: { 595 id: "73077", 596 date: "2022-12-05", 597 shifts: [], 598 }, 599 tuesday: { 600 id: "73078", 601 date: "2022-12-06", 602 shifts: [], 603 }, 604 wednesday: { 605 id: "73079", 606 date: "2022-12-07", 607 shifts: [], 608 }, 609 thursday: { 610 id: "73080", 611 date: "2022-12-08", 612 shifts: [], 613 }, 614 friday: { 615 id: "73081", 616 date: "2022-12-09", 617 shifts: [], 618 }, 619 saturday: { 620 id: "73082", 621 date: "2022-12-10", 622 shifts: [], 623 }, 624 sunday: { 625 id: "73083", 626 date: "2022-12-11", 627 shifts: [], 628 }, 629 post: "3", 630 title: "", 631 titleName: "", 632 }, 633 { 634 isRowGroup: 1, 635 groupNO: "二组", 636 }, 637 { 638 id: "389", 639 userName: "张小婧", 640 employeeNO: "389", 641 beds: null, 642 level: "0", 643 memo: null, 644 displayOrder: 2005, 645 groupNO: "二组", 646 durations: 0, 647 dayOff: 15, 648 monday: { 649 id: "73035", 650 date: "2022-12-05", 651 shifts: [], 652 }, 653 tuesday: { 654 id: "73036", 655 date: "2022-12-06", 656 shifts: [], 657 }, 658 wednesday: { 659 id: "73037", 660 date: "2022-12-07", 661 shifts: [], 662 }, 663 thursday: { 664 id: "73038", 665 date: "2022-12-08", 666 shifts: [], 667 }, 668 friday: { 669 id: "73039", 670 date: "2022-12-09", 671 shifts: [], 672 }, 673 saturday: { 674 id: "73040", 675 date: "2022-12-10", 676 shifts: [], 677 }, 678 sunday: { 679 id: "73041", 680 date: "2022-12-11", 681 shifts: [], 682 }, 683 post: "0", 684 title: "", 685 titleName: "", 686 }, 687 { 688 id: "394", 689 userName: "刘利慧", 690 employeeNO: "394", 691 beds: null, 692 level: "0", 693 memo: null, 694 displayOrder: 2010, 695 groupNO: "二组", 696 durations: 0, 697 dayOff: 15, 698 monday: { 699 id: "73042", 700 date: "2022-12-05", 701 shifts: [], 702 }, 703 tuesday: { 704 id: "73043", 705 date: "2022-12-06", 706 shifts: [], 707 }, 708 wednesday: { 709 id: "73044", 710 date: "2022-12-07", 711 shifts: [], 712 }, 713 thursday: { 714 id: "73045", 715 date: "2022-12-08", 716 shifts: [], 717 }, 718 friday: { 719 id: "73046", 720 date: "2022-12-09", 721 shifts: [], 722 }, 723 saturday: { 724 id: "73047", 725 date: "2022-12-10", 726 shifts: [], 727 }, 728 sunday: { 729 id: "73048", 730 date: "2022-12-11", 731 shifts: [], 732 }, 733 post: "0", 734 title: "", 735 titleName: "", 736 }, 737 { 738 id: "437", 739 userName: "闫丽平", 740 employeeNO: "437", 741 beds: null, 742 level: "0", 743 memo: null, 744 displayOrder: 2012, 745 groupNO: "二组", 746 durations: 0, 747 dayOff: 15, 748 monday: { 749 id: "73063", 750 date: "2022-12-05", 751 shifts: [], 752 }, 753 tuesday: { 754 id: "73064", 755 date: "2022-12-06", 756 shifts: [], 757 }, 758 wednesday: { 759 id: "73065", 760 date: "2022-12-07", 761 shifts: [], 762 }, 763 thursday: { 764 id: "73066", 765 date: "2022-12-08", 766 shifts: [], 767 }, 768 friday: { 769 id: "73067", 770 date: "2022-12-09", 771 shifts: [], 772 }, 773 saturday: { 774 id: "73068", 775 date: "2022-12-10", 776 shifts: [], 777 }, 778 sunday: { 779 id: "73069", 780 date: "2022-12-11", 781 shifts: [], 782 }, 783 post: "0", 784 title: "", 785 titleName: "", 786 }, 787 ]; 788 this.rowData = [...data]; 789 this.ctxMenuItems = [ 790 { 791 id: "copy", 792 title: "复制", 793 }, 794 { 795 id: "paste", 796 title: "粘贴", 797 }, 798 { 799 id: "clear", 800 title: "清除", 801 }, 802 { 803 id: "exchange", 804 title: "对换", 805 }, 806 { 807 id: "P", 808 title: "P", 809 children: [ 810 { 811 id: "观P1", 812 title: "观P1", 813 background: "#f27979", 814 color: "#000", 815 }, 816 { 817 id: "观P2", 818 title: "观P2", 819 background: "#f27979", 820 color: "#83e4ec", 821 }, 822 ], 823 }, 824 { 825 id: "X", 826 title: "X", 827 background: "#98F279", 828 color: "#FFD8D7", 829 }, 830 { 831 id: "其他", 832 title: "其他", 833 children: [ 834 { 835 id: "抢N", 836 title: "抢N", 837 background: "#EA1A1A", 838 color: "black", 839 }, 840 ], 841 }, 842 ]; 843 }, 844 computed: {}, 845 methods: { 846 initData() { 847 this.startDate = this.getStartDateByTargetDate(new Date("2022-12-25")); 848 let data = [ 849 { 850 isRowGroup: 1, 851 groupNO: "一组", 852 }, 853 { 854 id: "220", 855 userName: "孙丽11", 856 employeeNO: "220", 857 beds: "4,5,6", 858 level: "0", 859 memo: null, 860 displayOrder: 1000, 861 groupNO: "一组", 862 durations: 0, 863 dayOff: 15, 864 monday: { 865 id: "73098", 866 date: "2022-12-19", 867 shifts: [ 868 { 869 id: 1000, 870 shiftName: "抢N", 871 background: "#EA1A1A", 872 color: "black", 873 days: 2, 874 totalDays: null, 875 }, 876 ], 877 }, 878 tuesday: { 879 id: "73099", 880 date: "2022-12-20", 881 shifts: [], 882 }, 883 wednesday: { 884 id: "73100", 885 date: "2022-12-21", 886 shifts: [], 887 }, 888 thursday: { 889 id: "73101", 890 date: "2022-12-22", 891 shifts: [], 892 }, 893 friday: { 894 id: "73102", 895 date: "2022-12-23", 896 shifts: [], 897 }, 898 saturday: { 899 id: "73103", 900 date: "2022-12-24", 901 shifts: [], 902 }, 903 sunday: { 904 id: "73104", 905 date: "2022-12-25", 906 shifts: [], 907 }, 908 post: "5", 909 title: "", 910 titleName: "", 911 }, 912 { 913 id: "21", 914 userName: "刘世卿", 915 employeeNO: "21", 916 beds: null, 917 level: "0", 918 memo: null, 919 displayOrder: 1001, 920 groupNO: "一组", 921 durations: 46800000, 922 dayOff: 15, 923 monday: { 924 id: "73091", 925 date: "2022-12-19", 926 shifts: [], 927 }, 928 tuesday: { 929 id: "73092", 930 date: "2022-12-20", 931 shifts: [ 932 { 933 id: 2860, 934 shiftName: "观P2", 935 background: "#2D8CF0", 936 color: "#FFFFFF", 937 days: 1, 938 totalDays: null, 939 }, 940 ], 941 }, 942 wednesday: { 943 id: "73093", 944 date: "2022-12-21", 945 shifts: [ 946 { 947 id: 2860, 948 shiftName: "观P2", 949 background: "#2D8CF0", 950 color: "#FFFFFF", 951 days: 1, 952 totalDays: null, 953 }, 954 ], 955 }, 956 thursday: { 957 id: "73094", 958 date: "2022-12-22", 959 shifts: [], 960 }, 961 friday: { 962 id: "73095", 963 date: "2022-12-23", 964 shifts: [], 965 }, 966 saturday: { 967 id: "73096", 968 date: "2022-12-24", 969 shifts: [], 970 }, 971 sunday: { 972 id: "73097", 973 date: "2022-12-25", 974 shifts: [], 975 }, 976 post: "0", 977 title: "", 978 titleName: "", 979 }, 980 { 981 id: "354", 982 userName: "孙娉", 983 employeeNO: "354", 984 beds: null, 985 level: "0", 986 memo: null, 987 displayOrder: 1002, 988 groupNO: "一组", 989 durations: 0, 990 dayOff: 15, 991 monday: { 992 id: "73105", 993 date: "2022-12-19", 994 shifts: [], 995 }, 996 tuesday: { 997 id: "73106", 998 date: "2022-12-20", 999 shifts: [], 1000 }, 1001 wednesday: { 1002 id: "73107", 1003 date: "2022-12-21", 1004 shifts: [], 1005 }, 1006 thursday: { 1007 id: "73108", 1008 date: "2022-12-22", 1009 shifts: [], 1010 }, 1011 friday: { 1012 id: "73109", 1013 date: "2022-12-23", 1014 shifts: [], 1015 }, 1016 saturday: { 1017 id: "73110", 1018 date: "2022-12-24", 1019 shifts: [], 1020 }, 1021 sunday: { 1022 id: "73111", 1023 date: "2022-12-25", 1024 shifts: [], 1025 }, 1026 post: "0", 1027 title: "1", 1028 titleName: "初级(护士)", 1029 }, 1030 { 1031 id: "695", 1032 userName: "陈伟萍", 1033 employeeNO: "695", 1034 beds: null, 1035 level: "0", 1036 memo: null, 1037 displayOrder: 1003, 1038 groupNO: "一组", 1039 durations: 23400000, 1040 dayOff: 15, 1041 monday: { 1042 id: "73084", 1043 date: "2022-12-19", 1044 shifts: [], 1045 }, 1046 tuesday: { 1047 id: "73085", 1048 date: "2022-12-20", 1049 shifts: [ 1050 { 1051 id: 2860, 1052 shiftName: "观P2", 1053 background: "#2D8CF0", 1054 color: "#FFFFFF", 1055 days: 1, 1056 totalDays: null, 1057 }, 1058 ], 1059 }, 1060 wednesday: { 1061 id: "73086", 1062 date: "2022-12-21", 1063 shifts: [], 1064 }, 1065 thursday: { 1066 id: "73087", 1067 date: "2022-12-22", 1068 shifts: [], 1069 }, 1070 friday: { 1071 id: "73088", 1072 date: "2022-12-23", 1073 shifts: [], 1074 }, 1075 saturday: { 1076 id: "73089", 1077 date: "2022-12-24", 1078 shifts: [], 1079 }, 1080 sunday: { 1081 id: "73090", 1082 date: "2022-12-25", 1083 shifts: [], 1084 }, 1085 post: "0", 1086 title: "", 1087 titleName: "", 1088 }, 1089 { 1090 id: "133", 1091 userName: "纪丽鑫", 1092 employeeNO: "133", 1093 beds: "1111", 1094 level: "3", 1095 memo: null, 1096 displayOrder: 1005, 1097 groupNO: "一组", 1098 durations: 46800000, 1099 dayOff: 15, 1100 monday: { 1101 id: "73147", 1102 date: "2022-12-19", 1103 shifts: [], 1104 }, 1105 tuesday: { 1106 id: "73148", 1107 date: "2022-12-20", 1108 shifts: [ 1109 { 1110 id: 1000, 1111 shiftName: "抢N", 1112 background: "#EA1A1A", 1113 color: "black", 1114 days: 2, 1115 totalDays: null, 1116 }, 1117 ], 1118 }, 1119 wednesday: { 1120 id: "73149", 1121 date: "2022-12-21", 1122 shifts: [ 1123 { 1124 id: 2860, 1125 shiftName: "观P2", 1126 background: "#2D8CF0", 1127 color: "#FFFFFF", 1128 days: 1, 1129 totalDays: null, 1130 }, 1131 ], 1132 }, 1133 thursday: { 1134 id: "73150", 1135 date: "2022-12-22", 1136 shifts: [], 1137 }, 1138 friday: { 1139 id: "73151", 1140 date: "2022-12-23", 1141 shifts: [], 1142 }, 1143 saturday: { 1144 id: "73152", 1145 date: "2022-12-24", 1146 shifts: [], 1147 }, 1148 sunday: { 1149 id: "73153", 1150 date: "2022-12-25", 1151 shifts: [], 1152 }, 1153 post: "4", 1154 title: "", 1155 titleName: "", 1156 }, 1157 { 1158 id: "110110", 1159 userName: "童谣", 1160 employeeNO: "110110", 1161 beds: null, 1162 level: "1", 1163 memo: null, 1164 displayOrder: 1007, 1165 groupNO: "一组", 1166 durations: 46800000, 1167 dayOff: 15, 1168 monday: { 1169 id: "73182", 1170 date: "2022-12-19", 1171 shifts: [], 1172 }, 1173 tuesday: { 1174 id: "73183", 1175 date: "2022-12-20", 1176 shifts: [ 1177 { 1178 id: 2860, 1179 shiftName: "观P2", 1180 background: "#2D8CF0", 1181 color: "#FFFFFF", 1182 days: 1, 1183 totalDays: null, 1184 }, 1185 ], 1186 }, 1187 wednesday: { 1188 id: "73184", 1189 date: "2022-12-21", 1190 shifts: [ 1191 { 1192 id: 2860, 1193 shiftName: "观P2", 1194 background: "#2D8CF0", 1195 color: "#FFFFFF", 1196 days: 1, 1197 totalDays: null, 1198 }, 1199 ], 1200 }, 1201 thursday: { 1202 id: "73185", 1203 date: "2022-12-22", 1204 shifts: [], 1205 }, 1206 friday: { 1207 id: "73186", 1208 date: "2022-12-23", 1209 shifts: [], 1210 }, 1211 saturday: { 1212 id: "73187", 1213 date: "2022-12-24", 1214 shifts: [], 1215 }, 1216 sunday: { 1217 id: "73188", 1218 date: "2022-12-25", 1219 shifts: [], 1220 }, 1221 post: "6", 1222 title: "1", 1223 titleName: "初级(护士)", 1224 }, 1225 { 1226 id: "134", 1227 userName: "辛琤琤", 1228 employeeNO: "134", 1229 beds: "7,8,9", 1230 level: "2", 1231 memo: null, 1232 displayOrder: 1008, 1233 groupNO: "一组", 1234 durations: 0, 1235 dayOff: 15, 1236 monday: { 1237 id: "73077", 1238 date: "2022-12-19", 1239 shifts: [], 1240 }, 1241 tuesday: { 1242 id: "73078", 1243 date: "2022-12-20", 1244 shifts: [], 1245 }, 1246 wednesday: { 1247 id: "73079", 1248 date: "2022-12-21", 1249 shifts: [], 1250 }, 1251 thursday: { 1252 id: "73080", 1253 date: "2022-12-22", 1254 shifts: [], 1255 }, 1256 friday: { 1257 id: "73081", 1258 date: "2022-12-23", 1259 shifts: [], 1260 }, 1261 saturday: { 1262 id: "73082", 1263 date: "2022-12-24", 1264 shifts: [], 1265 }, 1266 sunday: { 1267 id: "73083", 1268 date: "2022-12-25", 1269 shifts: [], 1270 }, 1271 post: "3", 1272 title: "", 1273 titleName: "", 1274 }, 1275 { 1276 isRowGroup: 1, 1277 groupNO: "二组", 1278 }, 1279 { 1280 id: "389", 1281 userName: "张小婧", 1282 employeeNO: "389", 1283 beds: null, 1284 level: "0", 1285 memo: null, 1286 displayOrder: 2005, 1287 groupNO: "二组", 1288 durations: 0, 1289 dayOff: 15, 1290 monday: { 1291 id: "73035", 1292 date: "2022-12-19", 1293 shifts: [], 1294 }, 1295 tuesday: { 1296 id: "73036", 1297 date: "2022-12-20", 1298 shifts: [], 1299 }, 1300 wednesday: { 1301 id: "73037", 1302 date: "2022-12-21", 1303 shifts: [], 1304 }, 1305 thursday: { 1306 id: "73038", 1307 date: "2022-12-22", 1308 shifts: [], 1309 }, 1310 friday: { 1311 id: "73039", 1312 date: "2022-12-23", 1313 shifts: [], 1314 }, 1315 saturday: { 1316 id: "73040", 1317 date: "2022-12-24", 1318 shifts: [], 1319 }, 1320 sunday: { 1321 id: "73041", 1322 date: "2022-12-25", 1323 shifts: [], 1324 }, 1325 post: "0", 1326 title: "", 1327 titleName: "", 1328 }, 1329 { 1330 id: "394", 1331 userName: "刘利慧", 1332 employeeNO: "394", 1333 beds: null, 1334 level: "0", 1335 memo: null, 1336 displayOrder: 2010, 1337 groupNO: "二组", 1338 durations: 0, 1339 dayOff: 15, 1340 monday: { 1341 id: "73042", 1342 date: "2022-12-19", 1343 shifts: [], 1344 }, 1345 tuesday: { 1346 id: "73043", 1347 date: "2022-12-20", 1348 shifts: [], 1349 }, 1350 wednesday: { 1351 id: "73044", 1352 date: "2022-12-21", 1353 shifts: [], 1354 }, 1355 thursday: { 1356 id: "73045", 1357 date: "2022-12-22", 1358 shifts: [], 1359 }, 1360 friday: { 1361 id: "73046", 1362 date: "2022-12-23", 1363 shifts: [], 1364 }, 1365 saturday: { 1366 id: "73047", 1367 date: "2022-12-24", 1368 shifts: [], 1369 }, 1370 sunday: { 1371 id: "73048", 1372 date: "2022-12-25", 1373 shifts: [], 1374 }, 1375 post: "0", 1376 title: "", 1377 titleName: "", 1378 }, 1379 { 1380 id: "437", 1381 userName: "闫丽平", 1382 employeeNO: "437", 1383 beds: null, 1384 level: "0", 1385 memo: null, 1386 displayOrder: 2012, 1387 groupNO: "二组", 1388 durations: 0, 1389 dayOff: 15, 1390 monday: { 1391 id: "73063", 1392 date: "2022-12-19", 1393 shifts: [], 1394 }, 1395 tuesday: { 1396 id: "73064", 1397 date: "2022-12-20", 1398 shifts: [], 1399 }, 1400 wednesday: { 1401 id: "73065", 1402 date: "2022-12-21", 1403 shifts: [], 1404 }, 1405 thursday: { 1406 id: "73066", 1407 date: "2022-12-22", 1408 shifts: [], 1409 }, 1410 friday: { 1411 id: "73067", 1412 date: "2022-12-23", 1413 shifts: [], 1414 }, 1415 saturday: { 1416 id: "73068", 1417 date: "2022-12-24", 1418 shifts: [], 1419 }, 1420 sunday: { 1421 id: "73069", 1422 date: "2022-12-25", 1423 shifts: [], 1424 }, 1425 post: "0", 1426 title: "", 1427 titleName: "", 1428 }, 1429 ]; 1430 this.rowData = [...data]; 1431 }, 1432 getStartDateByTargetDate(targetDate) { 1433 let dayList = [1, 2, 3, 4, 5, 6, 0]; 1434 let day = targetDate.getDay(); 1435 let index = dayList.findIndex((d) => d === day); //代表当前天距离周一有几天 1436 let startDate = new Date( 1437 targetDate.getTime() - index * 24 * 60 * 60 * 1000 1438 ); //计算出周一是哪一天 1439 return startDate; 1440 }, 1441 //ag-grid创建完成后执行的事件 1442 onInit(api) { 1443 this.jheGridApi = api; 1444 }, 1445 onLoaded(params) { 1446 console.log("onLoaded", params); 1447 }, 1448 onChanged(data) { 1449 console.log("onChanged", data); 1450 this.changedRowData = data; 1451 }, 1452 onCellClicked(positon, data) { 1453 console.log("onCellClicked", positon, data); 1454 this.cellClickedPositon = positon; 1455 this.cellClickdSchedule = data; 1456 }, 1457 //点击菜单项触发 1458 onCommand(menuItem, selectedRanges) { 1459 console.log("onCommand", menuItem, selectedRanges); 1460 this.selectedRanges = selectedRanges; 1461 switch (menuItem.id) { 1462 // 复制选中的数据 1463 case "copy": 1464 //to do something... 1465 break; 1466 // 粘贴复制的数据到指定区域 1467 case "paste": 1468 //to do something... 1469 break; 1470 // 清除选中区域的数据 1471 case "clear": 1472 //to do something... 1473 break; 1474 case "exchange": 1475 //to do something... 1476 break; 1477 default: 1478 //to do something... 1479 break; 1480 } 1481 }, 1482 //提供的api有如下几种: 1483 //选中一行 1484 handleSelectRow() { 1485 this.jheGridApi.selectRow("1"); 1486 }, 1487 //选中一列 1488 handleSelectColumn() { 1489 this.jheGridApi.selectColumn("w1"); 1490 }, 1491 //选中单元格 1492 handleSelectCells() { 1493 let range = [ 1494 { rowId: "1", rowIndex: 1, colId: "w1", colIndex: 7 }, 1495 { rowId: "2", rowIndex: 2, colId: "w2", colIndex: 8 }, 1496 ]; 1497 this.jheGridApi.selectCells(range); 1498 }, 1499 //行上移 1500 handleMoveRowUp() { 1501 this.jheGridApi.moveRowUp(); 1502 }, 1503 //行下移 1504 handleMoveRowDown() { 1505 this.jheGridApi.moveRowDown(); 1506 }, 1507 //排班数据上移 1508 handleMoveDaysUp() { 1509 this.jheGridApi.moveDaysUp(); 1510 }, 1511 //排班数据下移 1512 handleMoveDaysDown() { 1513 this.jheGridApi.moveDaysDown(); 1514 }, 1515 //班次对换 1516 handleExchangeDays() { 1517 this.jheGridApi.exchangeDays(); 1518 }, 1519 //获取单元格数据 1520 handleGetCellData() { 1521 let data = this.jheGridApi.getCellData("1", "w2"); 1522 console.log("handleGetCellData", data); 1523 }, 1524 //设置单元格数据 1525 handleSetCellData() { 1526 let data = this.jheGridApi.getCellData("4", "w2"); 1527 this.jheGridApi.setCellData("2", "w4", data); 1528 }, 1529 //获取行数据 1530 handleGetRowData() { 1531 let data = this.jheGridApi.getRowData("4"); 1532 console.log("handleGetRowData", data); 1533 }, 1534 //设置区域为某个值 1535 handleSetRangeValue() { 1536 let range = [ 1537 { rowId: "0", rowIndex: 0, colId: "w1", colIndex: 7 }, 1538 { rowId: "1", rowIndex: 1, colId: "w2", colIndex: 8 }, 1539 ]; 1540 let data = this.jheGridApi.getCellData("4", "w2"); 1541 this.jheGridApi.setRangeValue(range, data); 1542 }, 1543 }, 1544 }; 1545 </script> 1546 <style lang="scss"> 1547 .group-cell { 1548 background-color: #d7e7f8 !important; 1549 font-weight: bold !important; 1550 text-align: left !important; 1551 padding-left: 36px !important; 1552 } 1553 </style>
效果图如下: