欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

antd/fusion表格增加圈選復(fù)制功能思路詳解

 更新時(shí)間:2023年09月25日 09:32:01   作者:一顆冰淇淋  
我們存在著大量在PC頁面通過表格看數(shù)據(jù)業(yè)務(wù)場(chǎng)景,表格又分為兩種,一種是antd?/?fusion這種基于dom元素的表格,另一種是通過?canvas?繪制的類似?excel?的表格,這篇文章主要介紹了antd/fusion表格增加圈選復(fù)制功能,需要的朋友可以參考下

背景介紹

我們存在著大量在PC頁面通過表格看數(shù)據(jù)業(yè)務(wù)場(chǎng)景,表格又分為兩種,一種是 antd / fusion 這種基于 dom 元素的表格,另一種是通過 canvas 繪制的類似 excel 的表格。

基于 dom 的表格功能豐富較為美觀,能實(shí)現(xiàn)多表頭、合并單元格和各種自定義渲染(如表格中渲染圖形 / 按鈕 / 進(jìn)度條 / 單選框 / 輸入框),以展示為主,不提供圈選、整列復(fù)制等功能。

canvas 繪制的類 excel 外表樸素更為實(shí)用,大量數(shù)據(jù)渲染不卡頓,操作類似 excel,能行/列選中,圈選、復(fù)制等功能。

兩者使用場(chǎng)景有所差異,各有利弊,但業(yè)務(wù)方不希望一套系統(tǒng)中出現(xiàn)兩種類型的交互,期望能將兩種表格的優(yōu)缺點(diǎn)進(jìn)行融合,在美觀的dom表格中增加圈選、復(fù)制的功能。

圈選效果

業(yè)務(wù)方所期望的圈選效果和excel類似,鼠標(biāo)按下即選中元素,然后滑動(dòng)鼠標(biāo),鼠標(biāo)所經(jīng)過形成的四邊形就是選中區(qū)域,此時(shí)鼠標(biāo)右鍵點(diǎn)擊復(fù)制按鈕,或者鍵盤按下 ctrl + c 復(fù)制文本。

而dom表格經(jīng)過如上操作,會(huì)把一整行數(shù)據(jù)都選上,不符合業(yè)務(wù)同學(xué)的使用預(yù)期。

實(shí)現(xiàn)過程

去除默認(rèn)樣式

我們需要自行定義鼠標(biāo)事件、元素樣式,需要先將無用的默認(rèn)樣式清除,包括上圖中的 hover 和選中元素的背景色。

禁用表格本身的鼠標(biāo)點(diǎn)擊選擇功能,設(shè)置css,userSelect: none

<Table style={{ userSelect: 'none' }} ></Table>

去除 hover 樣式(這里使用的是 fusion 組件)

.next-table-row:hover {
  background-color: transparent !important;
}

鼠標(biāo)按下,記錄選中元素

為表格綁定鼠標(biāo)按鍵時(shí)觸發(fā)事件 mousedown 。

當(dāng)鼠標(biāo)按下時(shí),這個(gè)元素就是中心元素,無論是向哪個(gè)方向旋轉(zhuǎn),所形成的區(qū)域一定會(huì)包含初始選中的元素。

getBoundingClientRect() 用于獲得頁面中某個(gè)元素的上下左右分別相對(duì)瀏覽器視窗的位置。

const onMouseDown = (event) => {
  const rect = event.target.getBoundingClientRect();
  // funsion 判斷點(diǎn)擊是否為表頭元素,為否時(shí)才繼續(xù)后面的邏輯。antd 不需要判斷,因?yàn)辄c(diǎn)擊表頭不會(huì)觸發(fā)該事件
  const isHeaderNode = event.target?.parentNode?.getAttribute('class')?.indexOf('next-table-header-node') > -1;
  if (isHeaderNode) return;
  originDir = {
    top: rect.top,
    left: rect.left,
    right: rect.right,
    bottom: rect.bottom,
  };
  // 渲染
  renderNodes(originDir);
};
<Table style={{ userSelect: 'none' }} onMouseDown={onMouseDown}></Table>

鼠標(biāo)滑過

為表格綁定鼠標(biāo)滑過時(shí)觸發(fā)事件 mousemove

根據(jù)滑動(dòng)元素的上下左右距離與鼠標(biāo)按下時(shí)的位置進(jìn)行判斷,圈選元素存在四個(gè)方向,以第一次選中的元素為中心位置。滑動(dòng)時(shí)元素位于鼠標(biāo)按下的右下、左下、右上、左上方,根據(jù)不同的情況來設(shè)置四個(gè)角的方位。

const onMouseMove = (event) => {
  if (!originDir.top) return;
  const rect = event.target.getBoundingClientRect();
  let coordinates = {};
  // 鼠標(biāo)按下后往右下方拖動(dòng)
  if (
    rect.top <= originDir.top &&
    rect.left <= originDir.left &&
    rect.right <= originDir.left &&
    rect.bottom <= originDir.top
  ) {
    coordinates = {
      top: rect.top,
      left: rect.left,
      right: originDir.right,
      bottom: originDir.bottom,
    };
  }
  // 鼠標(biāo)按下后往左下方拖動(dòng)
  if (
    rect.top >= originDir.top &&
    rect.left <= originDir.left &&
    rect.right <= originDir.right &&
    rect.bottom >= originDir.bottom
  ) {
    coordinates = {
      top: originDir.top,
      left: rect.left,
      right: originDir.right,
      bottom: rect.bottom,
    };
  }
// 鼠標(biāo)按下后往右上方拖動(dòng)
   if (
    rect.top <= originDir.top &&
    rect.left >= originDir.left &&
    rect.right >= originDir.right &&
    rect.bottom <= originDir.bottom
    ) {
     coordinates = {
        top: rect.top,
        left: originDir.left,
        right: rect.right,
        bottom: originDir.bottom,
    };
}
  // 鼠標(biāo)按下后往左上方拖動(dòng)
  if (
    rect.top >= originDir.top &&
    rect.left >= originDir.left &&
    rect.right >= originDir.right &&
    rect.bottom >= originDir.bottom
  ) {
    coordinates = {
      top: originDir.top,
      left: originDir.left,
      right: rect.right,
      bottom: rect.bottom,
    };
  }
  renderNodes(coordinates);
};
<Table
    style={{ userSelect: 'none' }}
    onMouseDown={onMouseDown}
    onMouseMove={onMouseMove}
></Table>

渲染/清除樣式

遍歷表格中 dom 元素,如果該元素在圈選的區(qū)域內(nèi),為其添加選中的背景色,再為四邊形區(qū)域增加邊框。

這里無論是直接設(shè)置 style 還是添加 classname 都不是很好。直接添加 classname 時(shí),antd 會(huì)在 hover 操作時(shí)重置 classname,原來設(shè)置的 classname 會(huì)被覆蓋。直接設(shè)置 style 可能存在和其他設(shè)置沖突的情況,并且最后獲取所有圈選元素時(shí)比較麻煩。

以上兩種方法都嘗試過,最后選擇了直接往 dom 元素上面添加屬性,分別用5個(gè)屬性保存是否圈選,上下左右邊框,這里沒有進(jìn)行合并是因?yàn)橐粋€(gè)dom元素可能同時(shí)存在這五個(gè)屬性。

const renderNodes = (coordinates) => {
  const nodes = document.querySelectorAll('.next-table-cell-wrapper');
  nodes.forEach((item) => {
    const target = item?.getBoundingClientRect();
    clearStyle(item);
    if (
      target?.top >= coordinates.top &&
      target?.right <= coordinates.right &&
      target?.left >= coordinates.left &&
      target?.bottom <= coordinates.bottom
    ) {
      item.setAttribute('data-brush', 'true');
      if (target.top === coordinates.top) {
        item.setAttribute('brush-border-top', 'true');
      }
      if (target.right === coordinates.right) {
        item.setAttribute('brush-border-right', 'true');
      }
      if (target.left === coordinates.left) {
        item.setAttribute('brush-border-left', 'true');
      }
      if (target.bottom === coordinates.bottom) {
        item.setAttribute('brush-border-bottom', 'true');
      }
    }
  });
};
const clearStyle = (item) => {
  item.hasAttribute('data-brush') && item.removeAttribute('data-brush');
  item.hasAttribute('brush-border-top') && item.removeAttribute('brush-border-top');
  item.hasAttribute('brush-border-right') && item.removeAttribute('brush-border-right');
  item.hasAttribute('brush-border-left') && item.removeAttribute('brush-border-left');
  item.hasAttribute('brush-border-bottom') && item.removeAttribute('brush-border-bottom');
};

使用 fusion 的 table 需要為每一個(gè)元素添加上透明的邊框,不然會(huì)出現(xiàn)布局抖動(dòng)的情況。(antd 不用)

 /* 為解決設(shè)置樣式抖動(dòng)而設(shè)置 */
 .next-table td .next-table-cell-wrapper {
  border: 1px solid transparent;
 }
[brush-border-top="true"] {
  border-top: 1px solid #b93d06 !important;
}
[brush-border-right="true"] {
  border-right: 1px solid #b93d06 !important;
}
[brush-border-left="true"] {
  border-left: 1px solid #b93d06 !important;
}
[brush-border-bottom="true"] {
  border-bottom: 1px solid #b93d06 !important;
}
[data-brush="true"] {
  background-color: #f5f5f5 !important;
}
.next-table-row:hover {
  background-color: transparent !important;
}

鼠標(biāo)松開

為表格綁定鼠標(biāo)松開時(shí)觸發(fā)事件 mouseup 。

從鼠標(biāo)按下,到滑動(dòng),最后松開,是一整個(gè)圈選流程,在鼠標(biāo)按下時(shí)保存了初始的方位,滑動(dòng)時(shí)判斷是否存在方位再進(jìn)行計(jì)算,松開時(shí)將初始方位置空。

const onMouseUp = () => {
  originDir = {};
};
 <Table
    style={{ userSelect: 'none' }}
    onMouseDown={onMouseDown}
    onMouseMove={onMouseMove}
    onMouseUp={onMouseUp}
    ></Table>

到這一步,就已經(jīng)實(shí)現(xiàn)了鼠標(biāo)圈選功能。

復(fù)制功能

表格圈選的交互效果其實(shí)是為復(fù)制功能做準(zhǔn)備。

鼠標(biāo)右鍵復(fù)制

原表格在選中元素時(shí)鼠標(biāo)右鍵會(huì)出現(xiàn)【復(fù)制】按鈕,點(diǎn)擊后復(fù)制的效果是圖中圈選到的元素每一個(gè)都換行展示,圈選行為不能滿足使用需求,復(fù)制的內(nèi)容也無法按照頁面中展示的行列格式。

而當(dāng)我們實(shí)現(xiàn)圈選功能之后,因?yàn)槭褂?css 屬性 "user-select: none" 禁止用戶選擇文本,此時(shí)鼠標(biāo)右鍵已經(jīng)不會(huì)出現(xiàn)復(fù)制按鈕。

為了實(shí)現(xiàn)鼠標(biāo)右鍵出現(xiàn)復(fù)制按鈕,我們需要覆蓋原鼠標(biāo)右鍵事件,自定義復(fù)制功能。

1、為表格綁定鼠標(biāo)右鍵事件 contextMenu

<Table
    style={{ userSelect: 'none' }}
    onMouseDown={onMouseDown}
    onMouseMove={onMouseMove}
    onMouseUp={onMouseUp}
    onContextMenu={onContextMenu}
></Table>

2、創(chuàng)建一個(gè)包含復(fù)制按鈕的自定義上下文菜單

<div id="contextMenu" className="context-menu" style={{ cursor: 'pointer' }}>
<div onClick={onClickCopy}>復(fù)制</div>
</div>

3、阻止默認(rèn)的右鍵菜單彈出,將自定義上下文菜單添加到頁面中,并定位在鼠標(biāo)右鍵點(diǎn)擊的位置。

const onContextMenu = (event) => {
  event.preventDefault(); // 阻止默認(rèn)右鍵菜單彈出
  const contextMenu = document.getElementById('contextMenu');
  // 定位上下文菜單的位置
  contextMenu.style.left = `${event.clientX}px`;
  contextMenu.style.top = `${event.clientY}px`;
  // 顯示上下文菜單
  contextMenu.style.display = 'block';
};

這里復(fù)制按鈕沒有調(diào)整樣式,可根據(jù)自己項(xiàng)目情況進(jìn)行一些美化。

4、點(diǎn)擊復(fù)制按鈕時(shí),保存當(dāng)前行列格式執(zhí)行復(fù)制操作。

復(fù)制仍然保留表格的樣式,這里想了很久,一直在想通過保存dom元素的樣式來實(shí)現(xiàn),這種方案存在兩個(gè)問題,一是保存html樣式的api,document.execCommand('copy') 不被瀏覽器支持,二是表格元素都是行內(nèi)元素,即使復(fù)制了樣式,也和頁面上看到的布局不一樣。

最后采取的方案還是自己對(duì)是否換行進(jìn)行處理,遍歷元素時(shí)判斷當(dāng)前元素的 top 屬性和下一個(gè)點(diǎn)距離,如果相同則添加空字符串,不同則添加換行符 \n 。

const onClickCopy = () => {
    const contextMenu = document.getElementById('contextMenu');
    const copyableElements = document.querySelectorAll('[data-brush=true]');
    // 遍歷保存文本
    let copiedContent = '';
    copyableElements.forEach((element, index) => {
       let separator = ' ';
       if (index < copyableElements.length - 1) {
          const next = copyableElements?.[index + 1];
          if (next?.getBoundingClientRect().top !== element.getBoundingClientRect().top) {
              separator = '\n';
           }
        }
        copiedContent += `${element.innerHTML}${separator}`;
    });
    // 執(zhí)行復(fù)制操作
    navigator.clipboard.writeText(copiedContent).then(() => {
       console.log('已復(fù)制內(nèi)容:', copiedContent);
    }) .catch((error) => {
        console.error('復(fù)制失敗:', error);
    });
    // 隱藏上下文菜單
    contextMenu.style.display = 'none';
};

5、對(duì)鼠標(biāo)按下事件 onMouseDown 的處理

  • 鼠標(biāo)點(diǎn)擊右鍵也會(huì)觸發(fā) onMouseDown ,這時(shí)會(huì)造成選中區(qū)域錯(cuò)亂,需要通過 event.button 判斷當(dāng)前事件觸發(fā)的鼠標(biāo)位置。
  • 鼠標(biāo)右鍵后如果沒有點(diǎn)擊復(fù)制按鈕而是滑走或者使用鼠標(biāo)左鍵選中,這時(shí)候相當(dāng)于執(zhí)行取消復(fù)制操作,復(fù)制按鈕的上下文需要清除。
const onMouseDown = (event) => {
  //  0:表示鼠標(biāo)左鍵。2:表示鼠標(biāo)右鍵。1:表示鼠標(biāo)中鍵或滾輪按鈕
  if (event.button !== 0) return;
  // 隱藏復(fù)制按鈕
  const contextMenu = document.getElementById('contextMenu');
  contextMenu.style.display = 'none';
};

到這里,就已經(jīng)實(shí)現(xiàn)了圈選鼠標(biāo)右鍵復(fù)制的功能。

ctrl+s / command+s 復(fù)制

使用 event.ctrlKey 來檢查 Ctrl 鍵是否按下,使用 event.metaKey 來檢查 Command 鍵是否按下,并使用 event.key 來檢查按下的鍵是否是 c 鍵。

useEffect(() => {
    const clickSave = (event) => {
      if ((event.ctrlKey || event.metaKey) && event.key === 'c') {
        onClickCopy();
        event.preventDefault(); // 阻止默認(rèn)的保存操作
      }
    };
    document.addEventListener('keydown', clickSave);
    return () => {
      document.removeEventListener('keydown', clickSave);
    };
}, []);

antd 也可以使用

以上功能是在 fusion design 中實(shí)現(xiàn)的,在 antd 中也可以使用,語法稍有不同。

表格中鼠標(biāo)事件需要綁定在 onRow 函數(shù)中

 <Table
  style={{ userSelect: 'none' }}
  onRow={() => {
    return {
      onContextMenu,
      onMouseDown,
      onMouseMove,
      onMouseUp,
    };
  }}
>

獲取所有表格 dom 元素的類名替換一下

 const nodes = document.querySelectorAll('.ant-table-cell');

覆蓋表格 hover 時(shí)樣式

.ant-table-cell-row-hover {
    background: transparent;
  }
  .ant-table-wrapper .ant-table .ant-table-tbody > tr.ant-table-row:hover > td,
  .ant-table-wrapper .ant-table .ant-table-tbody > tr > td.ant-table-cell-row-hover {
    background: transparent;
  }

實(shí)現(xiàn)效果是這樣的

完整代碼

完整代碼在這里 table-brush-copy,包括 fusion design 和 ant design 兩個(gè)版本。

總結(jié)

表格圈選復(fù)制功能的實(shí)現(xiàn)主要是以下五步

  • mousedown 按下鼠標(biāo),記錄初始坐標(biāo)
  • mousemove 滑動(dòng)鼠標(biāo),計(jì)算所形成的四邊形區(qū)域
  • mouseup 松開鼠標(biāo),清空初始坐標(biāo)
  • contextmenu 自定義鼠標(biāo)右鍵事件,定位上下文事件
  • keydown 監(jiān)聽鍵盤按下位置,判斷是否為復(fù)制操作

集合了較多的鼠標(biāo)、鍵盤事件,以及 javascript 獲取屬性、元素。

到此這篇關(guān)于antd/fusion表格增加圈選復(fù)制功能的文章就介紹到這了,更多相關(guān)antd/fusion表格圈選復(fù)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論