当前位置:   article > 正文

HOW - Canvas 入门系列之基于vue-konva的多维表格(四)

HOW - Canvas 入门系列之基于vue-konva的多维表格(四)

一、背景

HOW - Canvas 入门系列之表格绘制工具(二) 中我们介绍过基于原生 Canvas 实现表格绘制,并支持如下能力:

  1. 绘制表格数据并填充单元格内容
  2. 单元格点击触发事件
  3. 支持动态调整列宽

今天我们将将基于 vue-konva 实现的一个表格渲染的组件。

二、涉及 konva 组件

  • v-stage:用于场景一个画布,它是Konva应用程序的根容器,所有的图形都是在画布上绘制的。画布定义了Canvas的大小以及应用程序的全局属性。
  • v-layer:用于创建一个图层,它是Konva中用于组织和管理可视元素的容器。通常情况下,每个图形都被添加到一个图层上,然后图层再添加到舞台上。使用图层可以实现对不同部分的图形进行分组和独立管理。
  • v-group:用于创建一个群组,它是Konva中用于将多个形状或群组组合在一起的容器。群组可以包含任意数量的形状、群组或其他Konva节点,并可以一起移动、旋转和缩放。
  • v-shape:用于创建任意形状的图形,比如矩形、圆形、线条等。通过指定不同的type属性,可以创建不同类型的形状。除了预定义的形状外,还可以通过sceneFunc属性自定义绘制函数来创建特定形状的图形。
  • v-rect: 用于在 Konva 画布上绘制矩形,可以通过设置属性来定义矩形的位置、大小、填充颜色、边框颜色、边框宽度等属性。
  • v-text:用于在 Konva 画布上显示文本,并且可以通过设置 属性来定义文本的位置、内容、字体、字体大小、字体颜色等属性。

三、具体实现

回顾一下我们要实现的能力:

  1. 绘制表格数据并填充单元格内容
  2. 单元格点击触发事件
  3. 支持动态调整列宽

下面是一个简单的示例,演示如何使用Vue Konva来实现一个多维表格渲染,支持绘制表格数据、填充单元格内容、单元格点击事件:

<template>
  <v-stage :config="stageConfig" @click="handleStageClick">
    <v-layer>
      <!-- 绘制表格 -->
      <v-group>
        <!-- 绘制表头 -->
        <v-rect
          v-for="(column, columnIndex) in columns"
          :key="'header-' + columnIndex"
          :config="getHeaderRectConfig(columnIndex)"
        />
        <v-text
          v-for="(column, columnIndex) in columns"
          :key="'header-text-' + columnIndex"
          :config="getHeaderTextConfig(columnIndex)"
        />
        <!-- 绘制表格内容 -->
        <v-rect
          v-for="(row, rowIndex) in rows"
          :key="'row-' + rowIndex"
          :config="getRowRectConfig(rowIndex)"
          :fill="highlightedCell && highlightedCell.row === rowIndex ? (highlightedCell.column === null ? 'yellow' : 'lightyellow') : (rowIndex % 2 === 0 ? 'lightblue' : 'lightcyan')"
        />
        <v-text
          v-for="(row, rowIndex) in rows"
          :key="'row-text-' + rowIndex"
          :config="getRowTextConfig(rowIndex)"
        />
      </v-group>
    </v-layer>
  </v-stage>
</template>

<script>
import { Vue as VueKonva, Stage, Layer, Group, Rect, Text } from 'vue-konva';

export default {
  components: {
    VueKonva,
    Stage,
    Layer,
    Group,
    Rect,
    Text,
  },
  data() {
    return {
      stageConfig: {
        width: 800,
        height: 600,
      },
      columns: ['Column 1', 'Column 2', 'Column 3'], // 列名
      rows: ['Row 1', 'Row 2', 'Row 3'], // 行名
      cellWidth: 100,
      cellHeight: 50,
      highlightedCell: null, // 记录当前高亮的单元格
    };
  },
  methods: {
    // 处理舞台点击事件
    handleStageClick(event) {
      // 点击事件处理逻辑
      const clickedX = event.evt.offsetX;
      const clickedY = event.evt.offsetY;
      const columnIndex = Math.floor(clickedX / this.cellWidth);
      const rowIndex = Math.floor(clickedY / this.cellHeight);
      this.highlightedCell = { row: rowIndex, column: columnIndex };
    },
    // 获取表头矩形配置
    getHeaderRectConfig(columnIndex) {
      return {
        x: columnIndex * this.cellWidth,
        y: 0,
        width: this.cellWidth,
        height: this.cellHeight,
        fill: 'lightgray',
        stroke: 'black',
        strokeWidth: 1,
      };
    },
    // 获取表头文本配置
    getHeaderTextConfig(columnIndex) {
      return {
        x: columnIndex * this.cellWidth + 5,
        y: 5,
        text: this.columns[columnIndex],
      };
    },
    // 获取行矩形配置
    getRowRectConfig(rowIndex) {
      return {
        x: 0,
        y: rowIndex * this.cellHeight,
        width: this.cellWidth * this.columns.length,
        height: this.cellHeight,
        fill: rowIndex % 2 === 0 ? 'lightblue' : 'lightcyan',
        stroke: 'black',
        strokeWidth: 1,
      };
    },
    // 获取行文本配置
    getRowTextConfig(rowIndex) {
      return {
        x: 5,
        y: rowIndex * this.cellHeight + 5,
        text: this.rows[rowIndex],
      };
    },
  },
};
</script>

<style scoped>
/* Add your styles here */
</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115

在这个示例中,我们使用了Vue Konva的各个组件来绘制多维表格。其中:

  • <v-stage>用于创建舞台。
  • <v-layer>用于创建图层。
  • <v-group>用于组织表格中的各个元素。
  • <v-rect>用于绘制矩形,代表表格的单元格。
  • <v-text>用于绘制文本,显示表格的行名和列名。

这个示例中还包含了一些方法,用于动态计算单元格的位置和尺寸,并根据表格的行数和列数来绘制相应的内容。同时,还提供了一个舞台点击事件的处理方法handleStageClick,你可以在其中添加你需要的点击事件处理逻辑。

你可以根据需要扩展这个示例,实现更复杂的表格功能,比如动态调整列宽等:

<template>
  <v-stage :config="stageConfig" @mousedown="handleMouseDown" @mouseup="handleMouseUp" @mousemove="handleMouseMove">
    <v-layer>
      <!-- 绘制表格 -->
      <v-group>
        <!-- 绘制表头 -->
        <v-rect
          v-for="(column, columnIndex) in columns"
          :key="'header-' + columnIndex"
          :config="getHeaderRectConfig(columnIndex)"
        />
        <v-text
          v-for="(column, columnIndex) in columns"
          :key="'header-text-' + columnIndex"
          :config="getHeaderTextConfig(columnIndex)"
        />
        <!-- 绘制表格内容 -->
        <v-rect
          v-for="(row, rowIndex) in rows"
          :key="'row-' + rowIndex"
          :config="getRowRectConfig(rowIndex)"
        />
        <v-text
          v-for="(row, rowIndex) in rows"
          :key="'row-text-' + rowIndex"
          :config="getRowTextConfig(rowIndex)"
        />
      </v-group>
      <!-- 拖拽调整列宽的辅助线 -->
      <v-line v-if="isResizing" :config="resizerLineConfig"/>
    </v-layer>
  </v-stage>
</template>

<script>
import { Vue as VueKonva, Stage, Layer, Group, Rect, Text, Line } from 'vue-konva';

export default {
  components: {
    VueKonva,
    Stage,
    Layer,
    Group,
    Rect,
    Text,
    Line,
  },
  data() {
    return {
      stageConfig: {
        width: 800,
        height: 600,
      },
      columns: ['Column 1', 'Column 2', 'Column 3'], // 列名
      rows: ['Row 1', 'Row 2', 'Row 3'], // 行名
      cellHeight: 50,
      isResizing: false, // 是否正在调整列宽
      resizeStartX: 0, // 调整列宽的起始坐标
      resizingColumnIndex: -1, // 正在调整宽度的列索引
      columnWidths: [100, 100, 100], // 列宽度数组
    };
  },
  methods: {
    // 处理鼠标按下事件
    handleMouseDown(event) {
      if (event.target.name() === 'resizer') {
        this.isResizing = true;
        this.resizeStartX = event.evt.clientX;
        this.resizingColumnIndex = parseInt(event.target.attrs.columnIndex);
      }
    },
    // 处理鼠标移动事件
    handleMouseMove(event) {
      if (this.isResizing) {
        const movementX = event.evt.clientX - this.resizeStartX;
        const newWidth = Math.max(30, this.columnWidths[this.resizingColumnIndex] + movementX);
        this.$set(this.columnWidths, this.resizingColumnIndex, newWidth);
      }
    },
    // 处理鼠标释放事件
    handleMouseUp() {
      if (this.isResizing) {
        this.isResizing = false;
        this.resizeStartX = 0;
        this.resizingColumnIndex = -1;
      }
    },
    // 获取表头矩形配置
    getHeaderRectConfig(columnIndex) {
      return {
        x: this.getCellX(columnIndex),
        y: 0,
        width: this.columnWidths[columnIndex],
        height: this.cellHeight,
        fill: 'lightgray',
        stroke: 'black',
        strokeWidth: 1,
        name: 'resizer', // 设置名称以便于识别
        draggable: true, // 允许拖拽
        columnIndex: columnIndex, // 保存列索引
      };
    },
    // 获取表头文本配置
    getHeaderTextConfig(columnIndex) {
      return {
        x: this.getCellX(columnIndex) + 5,
        y: 5,
        text: this.columns[columnIndex],
      };
    },
    // 获取行矩形配置
    getRowRectConfig(rowIndex) {
      return {
        x: 0,
        y: rowIndex * this.cellHeight,
        width: this.columnWidths.reduce((acc, cur) => acc + cur, 0),
        height: this.cellHeight,
        stroke: 'black',
        strokeWidth: 1,
      };
    },
    // 获取行文本配置
    getRowTextConfig(rowIndex) {
      return {
        x: 5,
        y: rowIndex * this.cellHeight + 5,
        text: this.rows[rowIndex],
      };
    },
    // 获取单元格的 x 坐标
    getCellX(columnIndex) {
      let x = 0;
      for (let i = 0; i < columnIndex; i++) {
        x += this.columnWidths[i];
      }
      return x;
    },
  },
  computed: {
    // 获取调整列宽时的辅助线配置
    resizerLineConfig() {
      return {
        points: [this.resizeStartX, 0, this.resizeStartX, this.stageConfig.height],
        stroke: 'black',
        strokeWidth: 1,
      };
    },
  },
};
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150

可以发现我们对于 cell 的宽度将通过一个 columnWidths 来维护,并根据鼠标事件来动态变更当前列每个cell渲染宽度和辅助线的位置。

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

闽ICP备14008679号