From d96e94b7c5302276cd36ef6c7b9bb32c511306e4 Mon Sep 17 00:00:00 2001 From: cmm-va <867884660@qq.com> Date: Fri, 19 Jul 2024 14:34:52 +0800 Subject: [PATCH] feat(grid): fix highlight bind #WIK-16060 --- packages/grid/src/core/types/core.ts | 3 +- packages/grid/src/core/utils/common.ts | 8 +- packages/grid/src/grid.component.html | 15 +-- packages/grid/src/grid.component.ts | 61 +++++---- packages/grid/src/services/event.service.ts | 119 +++++++++++++++++- .../grid/src/services/selection.servive.ts | 41 +++++- packages/grid/src/styles.scss | 6 +- packages/grid/src/types/grid.ts | 6 + 8 files changed, 203 insertions(+), 56 deletions(-) diff --git a/packages/grid/src/core/types/core.ts b/packages/grid/src/core/types/core.ts index d184b039..9e0fc0a5 100644 --- a/packages/grid/src/core/types/core.ts +++ b/packages/grid/src/core/types/core.ts @@ -1,5 +1,6 @@ import { WritableSignal } from '@angular/core'; import { AITableAction } from './action'; +import { AITableSelection } from '../../types'; export enum AITableFieldType { // NotSupport = 0, @@ -74,7 +75,7 @@ export interface AITable { records: WritableSignal; fields: WritableSignal; actions: AITableAction[]; - selection: Map; + selection: WritableSignal; onChange: () => void; apply: (action: AITableAction) => void; } diff --git a/packages/grid/src/core/utils/common.ts b/packages/grid/src/core/utils/common.ts index f39807b3..3c14408d 100644 --- a/packages/grid/src/core/utils/common.ts +++ b/packages/grid/src/core/utils/common.ts @@ -1,14 +1,18 @@ import { Actions } from '../action'; import { AITable, AITableAction, AITableFields, AITableRecords } from '../types'; import { FLUSHING } from './weak-map'; -import { WritableSignal } from '@angular/core'; +import { WritableSignal, signal } from '@angular/core'; export function createAITable(records: WritableSignal, fields: WritableSignal): AITable { const aiTable: AITable = { records, fields, actions: [], - selection: new Map(), + selection: signal({ + selectedRecords: new Map(), + selectedFields: new Map(), + selectedCells: new Map() + }), onChange: () => {}, apply: (action: AITableAction) => { aiTable.actions.push(action); diff --git a/packages/grid/src/grid.component.html b/packages/grid/src/grid.component.html index d3e1e978..bef2dc79 100644 --- a/packages/grid/src/grid.component.html +++ b/packages/grid/src/grid.component.html @@ -3,7 +3,7 @@ @for (field of gridData().fields; track field.id) { -
+
{{ field.name }}
} @@ -12,28 +12,19 @@
@for (record of gridData().records; track record.id; let index = $index) {
-
+
{{ index + 1 }}
@for (field of gridData().fields; track $index) {
(); @@ -76,9 +78,9 @@ export class AITableGridComponent implements OnInit { isSelectedAll = false; - selection = new Map(); - - selectedHeader = new Set(); + get selection() { + return this.aiTableGridSelectionService.selection(); + } onChange = output(); @@ -89,8 +91,17 @@ export class AITableGridComponent implements OnInit { constructor( private elementRef: ElementRef, private aiTableGridEventService: AITableGridEventService, - private thyPopover: ThyPopover - ) {} + private thyPopover: ThyPopover, + public aiTableGridSelectionService: AITableGridSelectionService + ) { + effect( + () => { + this.aiTable.selection.set(this.aiTableGridSelectionService.selection()); + console.log('跟新啦', this.aiTable.selection()); + }, + { allowSignalWrites: true } + ); + } ngOnInit(): void { this.initAITable(); @@ -120,43 +131,29 @@ export class AITableGridComponent implements OnInit { this.gridData().records = data; } - clearSelection() { - this.selection.clear(); - this.selectedHeader.clear(); - } - selectCell(recordId: string, fieldId: string) { - this.clearSelection(); this.toggleAllCheckbox(false); - this.selection.set(recordId, { [fieldId]: '' }); + this.isSelectedAll = false; + this.aiTableGridSelectionService.selectCell(recordId, fieldId); } selectCol(field: any) { - this.clearSelection(); this.toggleAllCheckbox(false); - // 选择表头 - this.selectedHeader.add(field.name); - this.aiRecords().forEach((item) => { - const value = item.value[field.id]; - this.selection.set(item.id, { [field.id]: value }); - }); + this.isSelectedAll = false; + this.aiTableGridSelectionService.selectCol(field.id); } - selectRow() { - this.clearSelection(); - this.gridData().records.forEach((record) => { - if (record.checked) { - this.selection.set(record.id, record.value); - } - }); + selectRow(record: AITableRecord) { + this.aiTableGridSelectionService.selectRow(record.id); + this.isSelectedAll = this.selection.selectedRecords.size === this.aiRecords().length; } toggleSelectAll() { - this.clearSelection(); + this.aiTableGridSelectionService.clearSelection(); if (this.isSelectedAll) { this.toggleAllCheckbox(true); this.aiRecords().forEach((item) => { - this.selection.set(item.id, item.value); + this.selectRow(item); }); } else { this.toggleAllCheckbox(false); diff --git a/packages/grid/src/services/event.service.ts b/packages/grid/src/services/event.service.ts index 79b49988..1e85691a 100644 --- a/packages/grid/src/services/event.service.ts +++ b/packages/grid/src/services/event.service.ts @@ -1,4 +1,4 @@ -import { Injectable, signal, Signal } from '@angular/core'; +import { Injectable, Renderer2, signal, Signal } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { fromEvent } from 'rxjs'; import { DBL_CLICK_EDIT_TYPE } from '../constants'; @@ -16,7 +16,12 @@ export class AITableGridEventService { takeUntilDestroyed = takeUntilDestroyed(); - constructor(private thyPopover: ThyPopover) {} + lastClickCellElement?: HTMLElement; + + constructor( + private thyPopover: ThyPopover, + private renderer: Renderer2 + ) {} initialize(aiTable: AITable, aiFieldRenderers?: Partial>) { this.aiTable = aiTable; @@ -29,6 +34,116 @@ export class AITableGridEventService { .subscribe((event) => { this.dblClick(event as MouseEvent); }); + + fromEvent(element, 'click') + .pipe(this.takeUntilDestroyed) + .subscribe((event) => { + this.click(event as MouseEvent); + }); + } + + updateCellClass(dom: Element, operation: string) { + const rowDom = dom.closest('.grid-row'); + const highlightCells = rowDom?.querySelectorAll('.grid-cell'); + highlightCells?.forEach((cell) => { + operation === 'remove' ? this.renderer.removeClass(cell, 'highlight') : this.renderer.addClass(cell, 'highlight'); + }); + operation === 'remove' ? this.renderer.removeClass(dom, 'isSelected') : this.renderer.addClass(dom, 'isSelected'); + } + + updateColClass(dom: Element, operation: string) { + const fieldId = dom.getAttribute('fieldid'); + const tableElement = dom.closest('ai-table-grid'); + const cells = tableElement?.querySelectorAll(`[fieldid="${fieldId}"]`); + cells?.forEach((cell) => { + operation === 'add' ? this.renderer.addClass(cell, 'highlight') : this.renderer.removeClass(cell, 'highlight'); + }); + } + + updateAllClass(dom: Element, operation: string, checked: boolean) { + const tableElement = dom.closest('ai-table-grid'); + const rows = tableElement?.querySelectorAll('.grid-row'); + if (checked && operation === 'add') { + rows?.forEach((row) => { + this.renderer.addClass(row, 'highlight'); + }); + } + if (operation === 'remove') { + rows?.forEach((row) => { + this.renderer.removeClass(row, 'highlight'); + }); + } + } + + private click(event: MouseEvent) { + const cell = this.cellType(event.target as HTMLElement); + if (this.lastClickCellElement) { + const lastCell = this.cellType(this.lastClickCellElement); + if (lastCell?.type === 'cell') { + this.updateCellClass(this.lastClickCellElement, 'remove'); + } + + if (lastCell?.type === 'row' && cell?.type !== 'row' && cell.type) { + const tableElement = lastCell.element.closest('ai-table-grid'); + const checkboxes = tableElement?.querySelectorAll('.checked-box'); + checkboxes?.forEach((box) => { + const row = box.closest('.grid-row'); + this.renderer.removeClass(row, 'highlight'); + }); + } + + if (lastCell.type === 'col') { + this.updateColClass(lastCell.element, 'remove'); + } + + if (lastCell?.type === 'all' && cell?.type !== 'row' && cell.type) { + this.updateAllClass(cell.element, 'remove', false); + } + } + + if (cell?.type === 'cell') { + this.updateCellClass(cell.element, 'add'); + } + + if (cell?.type === 'row') { + const rowDom = cell.element.closest('.grid-row'); + if ((event.target as HTMLInputElement).checked) { + this.renderer.addClass(rowDom, 'highlight'); + } else { + this.renderer.removeClass(rowDom, 'highlight'); + } + } + + if (cell?.type === 'col') { + this.updateColClass(cell.element, 'add'); + } + + if (cell?.type === 'all') { + this.updateAllClass(cell.element, 'add', (event.target as HTMLInputElement).checked); + } + + this.lastClickCellElement = event.target as HTMLElement; + } + + private cellType(cell: HTMLElement) { + const cellDom = cell.closest('.grid-cell'); + const rowDom = cell.closest('.grid-row-index'); + const colDom = cell.closest('.grid-column-field'); + const checkAllDom = cell.closest('.grid-column-checkbox'); + if (cellDom && cellDom.getAttribute('fieldid') && cellDom.getAttribute('recordid')) { + return { type: 'cell', element: cellDom }; + } + if (rowDom && cell.tagName === 'INPUT') { + return { type: 'row', element: rowDom }; + } + if (colDom) { + return { type: 'col', element: colDom }; + } + + if (checkAllDom && cell.tagName === 'INPUT') { + return { type: 'all', element: checkAllDom }; + } + return {}; } private dblClick(event: MouseEvent) { diff --git a/packages/grid/src/services/selection.servive.ts b/packages/grid/src/services/selection.servive.ts index 9bd4dd4e..eeab16b3 100644 --- a/packages/grid/src/services/selection.servive.ts +++ b/packages/grid/src/services/selection.servive.ts @@ -1,9 +1,42 @@ -import { Injectable } from '@angular/core'; -import { AITable } from '../core'; +import { Injectable, WritableSignal, signal } from '@angular/core'; +import { AITableSelection } from '../types'; @Injectable() export class AITableGridSelectionService { - aiTable!: AITable; + selection: WritableSignal = signal({ + selectedRecords: new Map(), + selectedFields: new Map(), + selectedCells: new Map() + }); - setState() {} + clearSelection() { + this.selection.set({ + selectedRecords: new Map(), + selectedFields: new Map(), + selectedCells: new Map() + }); + } + + selectCell(recordId: string, fieldId: string) { + this.clearSelection(); + this.selection().selectedCells.set(recordId, { [fieldId]: true }); + } + + selectCol(fieldId: string) { + this.clearSelection(); + this.selection().selectedFields.set(fieldId, true); + } + + selectRow(recordId: string) { + if (this.selection().selectedRecords.has(recordId)) { + this.selection().selectedRecords.delete(recordId); + } else { + this.selection().selectedRecords.set(recordId, true); + } + this.selection.set({ + selectedRecords: this.selection().selectedRecords, + selectedFields: new Map(), + selectedCells: new Map() + }); + } } diff --git a/packages/grid/src/styles.scss b/packages/grid/src/styles.scss index 6de01bec..fb78395d 100644 --- a/packages/grid/src/styles.scss +++ b/packages/grid/src/styles.scss @@ -32,6 +32,9 @@ display: none; } } + &.highlight { + background: variables.$secondary-item-active; + } } .grid-cell { @@ -84,9 +87,6 @@ } } .grid-row-index { - &.highlight { - background: rgba(102, 152, 255, 0.1); - } .checked-box { display: block; } diff --git a/packages/grid/src/types/grid.ts b/packages/grid/src/types/grid.ts index 9c95af0a..330488a7 100644 --- a/packages/grid/src/types/grid.ts +++ b/packages/grid/src/types/grid.ts @@ -18,3 +18,9 @@ export interface AITableGridData { } export type GridCellPath = [RecordPath, FieldPath]; + +export interface AITableSelection { + selectedRecords: Map; + selectedFields: Map; + selectedCells: Map; +}