基于Canvas+React的高性能Table表格

4 篇文章 0 订阅
3 篇文章 0 订阅

        本人之前擅长客户端GDI+,疫情期间偶然了解到Web前端的Canvas画布。突发奇想,遂一发不可收拾,于是就有了这么个产物--基于Canvas+React的高性能Table表格,可咸可甜

        语言:JavaScript、TypeScript、ES6语法;

        框架:React;

        技术:Canvas画布;

        性能:以装载并渲染18列、N行为标准,压测有以下性能指标(附,测试硬盘状态不是太好,08年的老盘)。

1如需自适应列宽,30万行(30万,耗时24S)
2若无需自适应列宽,可达百万级(30万,耗时5S)
3自适应列宽模式,14500行,耗时1.5S
4无自适应列宽,14500行,耗时忽略不计

        特色:高性能、渲染流畅、操控顺滑、高扩展性(自定义派生列、单元格)、装载灵活、ES6语法、全程面向对象(class)、样式全部属性化(颜色、字体、尺寸、高度……抛弃繁杂的CSS样式表,降低入手门槛、维护复杂度);

目前已经支持的功能:

1、序号列、颜色(矩形、圆形)、进度、复选框、复选框集、按钮集、金额线、普通单元格;

2、右键显示隐藏列;

3、拖拽列索引顺序;

4、拖拽列宽;

5、自适应列宽;

6、数据结构(列表结构、树结构);

7、自定义行、单元格背景色、前景色;

8、自定义选中行、单元格的背景色;

9、焦点行背景色;

10、展开、折叠父子行;

11、表格数据排序;

12、表格内数据查找、定位;

13、数据选择模式(单选、多选--Shift辅助、鼠标滑动拖拽);

14、快捷键:↑(向上选择单元格或行)、↓(向下选择单元格或行)、←(向左选择单元格)、→(向右选择单元格)、PageUp(上翻一个显示范围)、PageDown(下翻一个显示范围)、Home(定位首行)、End(定位末行)、Ctrl+C(复制选中内容)、Ctrl+F(表格内数据查找、定位);

15、表格数据打印(目前针对页边距,还有点小瑕疵,需要手工调整一次页边距。大数据打印,尚且存有性能缺陷);

16、表格数据导出(时间紧迫,目前仅支持Excel);

17、多表头(支持表头合并);

18、单元格合并;

19、表头数据筛选、过滤(类似Excel数据筛选);

20、表格内容动态编辑(目前仅支持文本内容编辑,后续补充支持下拉框、日期框、数字框……);

21、表内,行数据拖拽交换(同级交换、父子级交换);

22、表外,行数据拖拽交换(不同表格之间,拖拽移除、或插入、或交换);

23、自定义右键菜单;

24、行定位(定位可视范围外、未渲染区域的选中行,并主动更新滚动条,使目标行处于可视范围内);

25、丰富的事件行为,总有一款适合您。单元格单击事件、行单击事件、行选择改变事件、表格勾选事件(行、列、单元格)、表格失焦事件、右键上下文选项单击事件……;

列表结构:

树结构:

右键菜单:

显示、隐藏列:

 列头数据筛选:

 

案例一:绑定Column、Row、Cell对象

import React, { useEffect, useState } from 'react';
import ColumnCollection from '@/EniacComponents/Table/Collections/ColumnCollection';
import RowCollection from '@/EniacComponents/Table/Collections/RowCollection';
import TableCore, { IFieldData } from '@/EniacComponents/Table/TableCore';
import Table from '@/EniacComponents/Table/Index';

const PageTableBindColumns = () => {
  const [columns, setColumns] = useState(new ColumnCollection());
  const [rows, setRows] = useState(new RowCollection());

  const BuildColumns = () => {
    let loColumns = columns;
    if (!loColumns || loColumns.Count === 0) {
      let loFieldList: IFieldData[] = [
        { Name: 'colSequenceColumn', Text: '', Type: 'SequenceColumn' },
        { Name: 'colCheckBoxColumn', Text: '', Type: 'CheckBoxColumn' },
        { Name: 'name', Text: '姓名', Type: 'Column' },
        { Name: 'age', Text: '年龄', Type: 'Column' },
        { Name: 'sex', Text: '性别', Type: 'Column' },
        { Name: 'items', Text: '功能清单', Type: 'CheckBoxListColumn' },
        { Name: 'address', Text: '地址', Type: 'Column' },
        { Name: 'assets_b', Text: '资产-本币', Type: 'AmountColumn' },
        { Name: 'assets_y', Text: '资产-原币', Type: 'AmountColumn' },
        { Name: 'controls', Text: '操作列', Type: 'ButtonColumn' },
      ];

      loColumns = new ColumnCollection();
      for (let liIndex = 0; liIndex < loFieldList.length; liIndex++) {
        let loCulumnData = loFieldList[liIndex];
        let loColumn = TableCore.CreateColumn(loCulumnData);
        loColumns.Add(loColumn);
      }

      setColumns(loColumns);
    }

    return loColumns;
  };

  const BuildRows = (poColumns: ColumnCollection) => {
    if (!rows || rows.length === 0) {
      let loDataList = [
        {
          name: '辛弃疾',
          age: 37,
          items: '喝酒,true;文臣,true;武将,true',
          sex: '男',
          address: '南宋',
          assets_b: 56832.215,
          assets_y: 56832.215,
          controls: '新增;修改;复制;审核;反审核;删除',
        },
        {
          name: '苏洵',
          age: 62,
          items: '喝酒,true;文臣,true;武将,false',
          sex: '男',
          address: '北宋',
          assets_b: 65912.32,
          assets_y: 65912.32,
          controls: '新增;修改;复制;审核;反审核;删除',
        },
        {
          name: '苏轼',
          age: 46,
          items: '喝酒,true;文臣,true;武将,true',
          sex: '男',
          address: '北宋',
          assets_b: 92615.68,
          assets_y: 92615.68,
          controls: '新增;修改;复制;审核;反审核;删除',
        },
        {
          name: '苏辙',
          age: 43,
          items: '喝酒,true;文臣,true;武将,true',
          sex: '男',
          address: '北宋',
          assets_b: 4689.36,
          assets_y: 4689.36,
          controls: '新增;修改;复制;审核;反审核;删除',
        },
        {
          name: '欧阳修',
          age: 78,
          items: '喝酒,true;文臣,true;武将,false',
          sex: '男',
          address: '北宋',
          assets_b: 9956592.36,
          assets_y: 9956592.36,
          controls: '新增;修改;复制;审核;反审核;删除',
        },
        {
          name: '王维',
          age: 66,
          items: '喝酒,true;文臣,true;武将,false',
          sex: '男',
          address: '大唐',
          assets_b: 6588.66,
          assets_y: 6588.66,
          controls: '新增;修改;复制;审核;反审核;删除',
        },
        {
          name: '杜甫',
          age: 48,
          items: '喝酒,true;文臣,true;武将,false',
          sex: '男',
          address: '大唐',
          assets_b: 6523698.99,
          assets_y: 6523698.99,
          controls: '新增;修改;复制;审核;反审核;删除',
        },
        {
          name: '李白',
          age: 63,
          items: '喝酒,true;文臣,true;武将,true',
          sex: '男',
          address: '大唐',
          assets_b: 98217365.922,
          assets_y: 98217365.922,
          controls: '新增;修改;复制;审核;反审核;删除',
        },
        {
          name: '李清照',
          age: 53,
          items: '喝酒,true;文臣,true;武将,true',
          sex: '女',
          address: '大宋',
          assets_b: 16591.52,
          assets_y: 16591.52,
          controls: '新增;修改;复制;审核;反审核;删除',
        },
        {
          name: '施耐庵',
          age: 56,
          items: '喝酒,true;文臣,true;武将,true',
          sex: '男',
          address: '大明',
          assets_b: 32336.12,
          assets_y: 32336.12,
          controls: '新增;修改;复制;审核;反审核;删除',
        },
        {
          name: '曹雪芹',
          age: 36,
          items: '喝酒,true;文臣,true;武将,true',
          sex: '男',
          address: '大清',
          assets_b: 615292.77,
          assets_y: 615292.77,
          controls: '新增;修改;复制;审核;反审核;删除',
        },
        {
          name: '罗贯中',
          age: 68,
          items: '喝酒,true;文臣,true;武将,true',
          sex: '男',
          address: '元末明初',
          assets_b: 6668214.53,
          assets_y: 6668214.53,
          controls: '新增;修改;复制;审核;反审核;删除',
        },
      ];

      let loRows = new RowCollection();
      loDataList.forEach((loRowData: object) => {
        let loRow = TableCore.CreateRow(poColumns, loRowData);
        if (loRow) {
          loRows.Add(loRow);
        }
      });

      setRows(loRows);
    }
  };

  const BuildTableData = () => {
    let loColumns = BuildColumns();
    BuildRows(loColumns);
  };

  useEffect(() => {
    BuildTableData();
  }, [columns, rows]);

  return (
    <Table
      Width={'100%'}
      Height={'100%'}
      Properties={{
        Draggable: true,
        ShowCheckBoxColumn: true,
        ShowSequenceColumn: true,
        ContextMenuVisible: true,
        ColumnHeight: 40,
      }}
      DataSource={null}
      Items={{ Columns: columns, Rows: rows }}
    ></Table>
  );
};

export default PageTableBindColumns;

案例二:绑定数据源、事件的使用

import React, { useEffect, useState } from 'react';
import Table from '@/EniacComponents/Table/Index';
import ToolStripView from '../Components/ToolStrip/ToolStripView';
import { IFieldData } from '@/EniacComponents/Table/TableCore';

const PageTableBindFields = () => {
  const [fieldList, setFieldList] = useState([] as IFieldData[]);
  const [dataList, setDataList] = useState([] as object[]);

  const BuildFieldList = () => {
    if (!fieldList || fieldList.length === 0) {
      let loFieldList: IFieldData[] = [
        { Name: 'name', Text: '姓名', Type: 'Column' },
        { Name: 'age', Text: '年龄', Type: 'Column' },
        { Name: 'items', Text: '功能清单', Type: 'CheckBoxListColumn' },
        { Name: 'sex', Text: '性别', Type: 'Column' },
        { Name: 'address', Text: '地址', Type: 'Column' },
      ];

      setFieldList(loFieldList);
    }
  };

  const BuildDataList = () => {
    if (!dataList || dataList.length === 0) {
      let loDataList: any[] = [
        { name: '辛弃疾', age: 37, items: '新增;修改;复制;删除;', sex: '男', address: '南宋' },
        { name: '苏洵', age: 62, items: '新增;修改;复制;删除;', sex: '男', address: '北宋' },
        { name: '苏轼', age: 46, items: '新增;修改;复制;删除;', sex: '男', address: '北宋' },
        { name: '苏辙', age: 43, items: '新增;修改;复制;删除;', sex: '男', address: '北宋' },
        { name: '欧阳修', age: 78, items: '新增;修改;复制;删除;', sex: '男', address: '北宋' },
        { name: '王维', age: 66, items: '新增;修改;复制;删除;', sex: '男', address: '大唐' },
        { name: '杜甫', age: 48, items: '新增;修改;复制;删除;', sex: '男', address: '大唐' },
        { name: '李白', age: 63, items: '新增;修改;复制;删除;', sex: '男', address: '大唐' },
        { name: '李清照', age: 53, items: '新增;修改;复制;删除;', sex: '女', address: '大宋' },
        { name: '施耐庵', age: 56, items: '新增;修改;复制;删除;', sex: '男', address: '大明' },
        { name: '曹雪芹', age: 36, items: '新增;修改;复制;删除;', sex: '男', address: '大清' },
        { name: '罗贯中', age: 68, items: '新增;修改;复制;删除;', sex: '男', address: '元末明初' },
        { name: '辛弃疾', age: 37, items: '新增;修改;复制;删除;', sex: '男', address: '南宋' },
        { name: '苏洵', age: 62, items: '新增;修改;复制;删除;', sex: '男', address: '北宋' },
        { name: '苏轼', age: 46, items: '新增;修改;复制;删除;', sex: '男', address: '北宋' },
        { name: '苏辙', age: 43, items: '新增;修改;复制;删除;', sex: '男', address: '北宋' },
        { name: '欧阳修', age: 78, items: '新增;修改;复制;删除;', sex: '男', address: '北宋' },
        { name: '王维', age: 66, items: '新增;修改;复制;删除;', sex: '男', address: '大唐' },
        { name: '杜甫', age: 48, items: '新增;修改;复制;删除;', sex: '男', address: '大唐' },
        { name: '李白', age: 63, items: '新增;修改;复制;删除;', sex: '男', address: '大唐' },
        { name: '李清照', age: 53, items: '新增;修改;复制;删除;', sex: '女', address: '大宋' },
        { name: '施耐庵', age: 56, items: '新增;修改;复制;删除;', sex: '男', address: '大明' },
        { name: '曹雪芹', age: 36, items: '新增;修改;复制;删除;', sex: '男', address: '大清' },
        { name: '罗贯中', age: 68, items: '新增;修改;复制;删除;', sex: '男', address: '元末明初' },
        { name: '辛弃疾', age: 37, items: '新增;修改;复制;删除;', sex: '男', address: '南宋' },
        { name: '苏洵', age: 62, items: '新增;修改;复制;删除;', sex: '男', address: '北宋' },
        { name: '苏轼', age: 46, items: '新增;修改;复制;删除;', sex: '男', address: '北宋' },
        { name: '苏辙', age: 43, items: '新增;修改;复制;删除;', sex: '男', address: '北宋' },
        { name: '欧阳修', age: 78, items: '新增;修改;复制;删除;', sex: '男', address: '北宋' },
        { name: '王维', age: 66, items: '新增;修改;复制;删除;', sex: '男', address: '大唐' },
        { name: '杜甫', age: 48, items: '新增;修改;复制;删除;', sex: '男', address: '大唐' },
        { name: '李白', age: 63, items: '新增;修改;复制;删除;', sex: '男', address: '大唐' },
        { name: '李清照', age: 53, items: '新增;修改;复制;删除;', sex: '女', address: '大宋' },
        { name: '施耐庵', age: 56, items: '新增;修改;复制;删除;', sex: '男', address: '大明' },
        { name: '曹雪芹', age: 36, items: '新增;修改;复制;删除;', sex: '男', address: '大清' },
        { name: '罗贯中', age: 68, items: '新增;修改;复制;删除;', sex: '男', address: '元末明初' },
        { name: '辛弃疾', age: 37, items: '新增;修改;复制;删除;', sex: '男', address: '南宋' },
        { name: '苏洵', age: 62, items: '新增;修改;复制;删除;', sex: '男', address: '北宋' },
        { name: '苏轼', age: 46, items: '新增;修改;复制;删除;', sex: '男', address: '北宋' },
        { name: '苏辙', age: 43, items: '新增;修改;复制;删除;', sex: '男', address: '北宋' },
        { name: '欧阳修', age: 78, items: '新增;修改;复制;删除;', sex: '男', address: '北宋' },
        { name: '王维', age: 66, items: '新增;修改;复制;删除;', sex: '男', address: '大唐' },
        { name: '杜甫', age: 48, items: '新增;修改;复制;删除;', sex: '男', address: '大唐' },
        { name: '李白', age: 63, items: '新增;修改;复制;删除;', sex: '男', address: '大唐' },
        { name: '李清照', age: 53, items: '新增;修改;复制;删除;', sex: '女', address: '大宋' },
        { name: '施耐庵', age: 56, items: '新增;修改;复制;删除;', sex: '男', address: '大明' },
        { name: '曹雪芹', age: 36, items: '新增;修改;复制;删除;', sex: '男', address: '大清' },
        { name: '罗贯中', age: 68, items: '新增;修改;复制;删除;', sex: '男', address: '元末明初' },
        { name: '辛弃疾', age: 37, items: '新增;修改;复制;删除;', sex: '男', address: '南宋' },
        { name: '苏洵', age: 62, items: '新增;修改;复制;删除;', sex: '男', address: '北宋' },
        { name: '苏轼', age: 46, items: '新增;修改;复制;删除;', sex: '男', address: '北宋' },
        { name: '苏辙', age: 43, items: '新增;修改;复制;删除;', sex: '男', address: '北宋' },
        { name: '欧阳修', age: 78, items: '新增;修改;复制;删除;', sex: '男', address: '北宋' },
        { name: '王维', age: 66, items: '新增;修改;复制;删除;', sex: '男', address: '大唐' },
        { name: '杜甫', age: 48, items: '新增;修改;复制;删除;', sex: '男', address: '大唐' },
        { name: '李白', age: 63, items: '新增;修改;复制;删除;', sex: '男', address: '大唐' },
        { name: '李清照', age: 53, items: '新增;修改;复制;删除;', sex: '女', address: '大宋' },
        { name: '施耐庵', age: 56, items: '新增;修改;复制;删除;', sex: '男', address: '大明' },
        { name: '曹雪芹', age: 36, items: '新增;修改;复制;删除;', sex: '男', address: '大清' },
        { name: '罗贯中', age: 68, items: '新增;修改;复制;删除;', sex: '男', address: '元末明初' },
      ];

      setDataList(loDataList);
    }
  };

  useEffect(() => {
    BuildFieldList();
    BuildDataList();
  }, [fieldList, dataList]);

  const table_CellClick = (e: any) => {
    console.log('table_CellClick', e.Cell.Text);
  };

  const table_RowClick = (e: any) => {
    console.log('table_RowClick', e.Row);
  };

  const table_SelectionChanged = (e: any) => {
    console.log('table_SelectionChanged', e.Row);
  };

  const table_CheckStateChanged = (e: any) => {
    if (e.Cell) {
      console.log('勾选单元格', e.Cell);
    } else if (e.Row) {
      console.log('勾选行', e.Row);
    } else if (e.Column) {
      console.log('勾选列', e.Column);
    }
  };

  const table_ContextItemClick = (e: any) => {
    console.log('table_ContextMenuItemClick', e.Item);
  };

  const table_GotFocus = (e: any) => {
    console.log('table_GotFocus', '获得焦点');
  };

  const table_LostFocus = (e: any) => {
    console.log('table_LostFocus', '失去焦点');
  };

  return (
    <div style={{ width: '100%', height: '100%' }}>
      <div>
        <ToolStripView ItemStyle={'ImageBeforeText'}></ToolStripView>
      </div>
      <div style={{ width: '100%', height: 'calc(100% - 43px)' }}>
        <Table
          Width={'100%'}
          Height={'100%'}
          Properties={{
            AllowDragRow: true,
            ShowCheckBoxColumn: true,
            ShowSequenceColumn: true,
            ContextMenuVisible: true,
            CustomContextItems: [
              { Name: 'CustomItem1', Text: '自定义选项1号' },
              { Name: 'CustomItem2', Text: '自定义选项2号' },
            ],
          }}
          DataSource={{ FieldList: fieldList, DataList: dataList }}
          Items={null}
          OnCellClick={table_CellClick}
          OnRowClick={table_RowClick}
          OnSelectionChanged={table_SelectionChanged}
          OnCheckStateChanged={table_CheckStateChanged}
          OnContextItemClick={table_ContextItemClick}
          OnGotFocus={table_GotFocus}
          OnLostFocus={table_LostFocus}
        ></Table>
      </div>
    </div>
  );
};

export default PageTableBindFields;

本人对GDI+、Canvas相当痴迷,并小有成就。欢迎咨询、洽谈,相互学习、互相进步。 

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
要导出Excel表格包含图片,可以使用以下步骤: 1. 安装 `xlsx` 和 `file-saver` 模块。 ``` npm install xlsx file-saver --save ``` 2. 编写导出Excel表格的函数。 ```javascript import XLSX from 'xlsx'; import FileSaver from 'file-saver'; const exportExcel = (data) => { const wb = XLSX.utils.book_new(); const ws = XLSX.utils.json_to_sheet(data); wb.Sheets['Sheet1'] = ws; // 添加图片 const imgData = 'base64图片数据'; // 替换为实际的图片数据 const img = new Image(); img.src = imgData; const ctx = document.createElement('canvas').getContext('2d'); ctx.drawImage(img, 0, 0); const imageData = ctx.canvas.toDataURL('image/jpeg'); const imageBlob = dataURItoBlob(imageData); const imageFile = new File([imageBlob], 'image.jpg', { type: 'image/jpeg' }); const imageId = XLSX.utils.generate_sheet_image_id(); wb.SheetNames.push('Images'); wb.Sheets['Images'] = {}; wb.Sheets['Images'][imageId] = { '!type': 'jpeg', '!data': imageFile, }; ws['!images'] = [{ id: imageId, start: { row: 0, column: 0 }, end: { row: 0, column: 0 } }]; const wbout = XLSX.write(wb, { bookType: 'xlsx', type: 'binary' }); FileSaver.saveAs(new Blob([s2ab(wbout)], { type: 'application/octet-stream' }), 'data.xlsx'); }; // 将Base64图片数据转换为Blob对象 const dataURItoBlob = (dataURI) => { const byteString = atob(dataURI.split(',')[1]); const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; const ab = new ArrayBuffer(byteString.length); const ia = new Uint8Array(ab); for (let i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i); } return new Blob([ab], { type: mimeString }); }; // 将字符串转换为ArrayBuffer const s2ab = (s) => { const buf = new ArrayBuffer(s.length); const view = new Uint8Array(buf); for (let i = 0; i < s.length; i++) { view[i] = s.charCodeAt(i) & 0xff; } return buf; }; ``` 上述代码中,`data` 是要导出的数据数组,`imgData` 是要导出的图片数据,可以替换为实际的图片数据。`exportExcel` 函数将数据和图片添加到Excel工作簿中,并将工作簿下载到本地。 3. 调用导出Excel表格的函数。 ```javascript const data = [ { name: '张三', age: 18 }, { name: '李四', age: 20 }, ]; exportExcel(data); ``` 上述代码中,`data` 是要导出的数据数组,调用 `exportExcel` 函数将数据和图片添加到Excel工作簿中,并将工作簿下载到本地。 以上就是Ant Design Pro + React导出Excel表格包含图片的实现方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值