二维码

第一章 绘制基本图形

本章摘要

本章节将带您从基础图形绘制出发,深入Canvas 2D API的世界。您将学习绘制矩形、折线、多边形、圆及椭圆的方法,理解Canvas渲染上下文的工作机制。通过实践案例和练习,您将掌握关键绘图技巧,激发创造力和想象力。无论您是新手还是资深开发者,本章节都是您图形开发之路上的宝贵资源。

图形系统开发实战课程 - 基础篇 绘制基本图形

第一章 绘制基本图形

本章的内容包括:

  • 绘制矩形
  • 绘制折线和多边形
  • 绘制圆和圆弧
  • 绘制椭圆和椭圆弧

1. 矩形

  矩形是日常生活和工作中最常见到的图形,Canvas提供了非常简单的方式,只需一行代码就能够绘制矩形,其语法如下:

绘制矩形的边框:

1
ctx.strokeRect(x, y, width, height)

绘制填充的矩形:

1
ctx.fillRect(x, y, width, height)

  在这两个方法中只需指定矩形的起始坐标(x,y)和矩形的宽和高,就能够绘制矩形。先看一个示例的运行效果图:
运行效果

源代码如下:

{.line-numbers}
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
<!DOCTYPE html>
<html>

<head>
<title>绘制基本图形</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
</head>

<body style="overflow: hidden; margin:0px;">
<canvas id="canvas" width="1200" height="800"></canvas>
</body>
<script>
// 从页面中获取画布对象
let canvas = document.getElementById('canvas');
// 从画布中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');

// 绘制左侧矩形(描边)
ctx.strokeRect(50, 50, 200, 100);

// 绘制右侧填充矩形
ctx.fillRect(350, 50, 200, 100);
</script>

</html>

  这个示例中:

  • let canvas = document.getElementById('canvas')可取得页面中的canvas对象;
  • let ctx = canvas.getContext('2d');可取得Canvas对象的渲染上下文对象
  • ctx.strokeRect(50, 50, 200, 100)ctx.fillRect(350, 50, 200, 100)分别绘制了一个非填充的矩形和一个填充的矩形,其各参数分的含义如下:
    • 第一个参数表示矩形的起点横坐标为(x),左侧矩形的x=50, 右侧矩形的x=350
    • 第二个参数表示矩形的起点纵坐标为(y),两个矩形的y均为50
    • 第三个参数表示矩形的宽度(width),两个矩形的width均为200
    • 第四个参数表示矩形的高度(height),两个矩形的height均为100

  坐标的原点在左上角,水平方向为x轴,垂直方向为y轴,各个坐标在图中的位置如下图所示:
运行效果

  Canvas还提供了其他的一些api,可以调整矩形效果,例如使用不用颜色绘制矩形,指定矩形的边框大小等等,均是通过Canvas的渲染上下文对象进行赋值,这些api如下所示:

1
2
3
4
5
6
// 改变线宽
ctx.lineWidth = value;
// 画笔颜色
ctx.strokeStyle = color;
// 填充颜色
ctx.fillStyle = color;

其运行效果如下图所示:
运行效果
源代码如下:

{.line-numbers}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- 略过html部分的内容-->
<script>
// 从页面中获取画布对象
let canvas = document.getElementById('canvas');
// 从画布中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');

// 绘制左侧矩形
ctx.lineWidth = 10;
ctx.strokeRect(100, 300, 200, 100);

// 绘制右侧矩形
ctx.lineWidth = 5;
ctx.fillStyle = "#0000FF"
ctx.fillRect(400, 300, 200, 100);
ctx.strokeStyle = "#FF0000"
ctx.strokeRect(400, 300, 200, 100);
</script>

  此外,Canvas的渲染上下文还提供了在路径中绘制矩形rect()清空一个矩形区域clearRect(),其参数与strokeRect()fillRect()完全一样,后续文章将会讲解路径的使用方法。清空一个矩形区域,将会实现让清除部分完全透明,通常用于清除Canvas原有内容,以下代码可清除整个Canvas中的内容:

1
ctx.clearRect(0, 0, canvas.width, canvas.height);

2. 折线和多边形

  线段的应用非常广泛,不仅包含了普通的直线,还包括了折线,虚线等等,先看一个例子:
运行效果
  这个例子中我们使用Canvas绘制了四条直线,其中第一条最简单的直线,第二条直线增加了线宽属性,第三条直线增加了颜色属性,第四条直线为虚线,代码如下:

{.line-numbers}
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
<script>
// 从页面中获取画布对象
let canvas = document.getElementById('canvas');
// 从画布中获取“渲染上下文”对象
let ctx = canvas.getContext('2d');

// 绘制简单直线
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(450, 50);
ctx.stroke();

// 绘制加粗的直线
ctx.beginPath();
ctx.moveTo(50, 110);
ctx.lineTo(450, 110);
ctx.lineWidth=5;
ctx.stroke();

// 绘制蓝色直线
ctx.beginPath();
ctx.moveTo(50, 170);
ctx.lineTo(450, 170);
ctx.strokeStyle="blue";
ctx.stroke();

// 绘制虚线
ctx.beginPath();
ctx.moveTo(50, 230);
ctx.lineTo(450, 230);
ctx.setLineDash([20, 15]);
ctx.stroke();
</script>

这个示例使用了Canvas的渲染上下文的以下几个方法:

1
2
3
4
ctx.beginPath()    // 开始路径
ctx.moveTo(x,y) // 将路径起点移动到起始点(x,y)坐标
ctx.lineTo(x,y) // 使用直线连接路径至终点(x,y)坐标
ctx.stroke() // 绘制当前路径

  绘制折线其实是由绘制“路径”这个功能实现的,其过程为:

  1. 开始绘制路径:ctx.beginPath()
  2. 将路径移动到起始点:ctx.moveTo(x, y)
  3. 使用直线连接路径至某个点:ctx.lineTo(x, y),如果需要可以继续使用直线连接路径至某个点
  4. 设置样式:
    指定线宽:ctx.lineWidth=5
    指定边框颜色:ctx.strokeColor=“red”
    指定填充颜色:ctx.fillColor=“#CCCCCC”
    使用虚线模式:ctx.setLineDash([10,10])
  5. 描边或填充
    描边:ctx.stroke()
    填充:ctx.fill();

下图标识了上述这四条直线的坐标值,可以更好的理解绘制直线的过程:

运行效果

整个绘制的过程如下:

  • 1、将画笔移动到坐标(50,50),绘制直线至坐标(450,50);
  • 2、将画笔移动到坐标(50,110),绘制直线至坐标(450,110);
  • 3、将画笔移动到坐标(50,170),绘制直线至坐标(450,170);
  • 4、将画笔移动到坐标(50,230),绘制直线至坐标(450,230);
    在绘制过程中,可根据需要改变线宽或颜色等属性。

  上述的几张效果图中均包含了灰色的网格线,这个网格线也是通过循环绘制直线功能来实现的,其代码如下:

{.line-numbers}
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
/**
* 绘制网格线
* @param color 颜色
* @param stepX x轴网格间距
* @param stepY y轴网格间距
*/
function drawGrid(color, stepX, stepY) {
ctx.save();
ctx.beginPath();

// 水平线
for (var i = stepX; i < canvas.width; i += stepX) {
ctx.moveTo(i, 0);
ctx.lineTo(i, canvas.height)
}

// 垂直线
for (var i = stepY; i < canvas.height; i += stepY) {
ctx.moveTo(0, i);
ctx.lineTo(canvas.width, i);
}

// 绘制
ctx.lineWidth = 0.5;
ctx.strokeStyle = color;
ctx.stroke();
ctx.restore();
}

我们在来看一个绘制折线图的例子:
运行效果

实现的逻辑也很简单,其代码如下:

{.line-numbers}
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
<script>
// 从页面中获取画布对象
let canvas = document.getElementById('canvas');
// 从画布中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');

// 绘制坐标系
ctx.lineWidth = 6;
drawLine([[76, 45], [76, 285], [316, 285]]);
ctx.stroke();
drawLine([[90, 51], [76, 23], [62, 51], [90, 51]]);
ctx.fill();
drawLine([[310, 271], [338, 285], [310, 299], [310, 271]]);
ctx.fill();

// 绘制数据走向
drawLine([[105, 256], [192, 169], [222, 227], [309, 140]]);
ctx.strokeStyle = "blue";
ctx.stroke();
drawLine([[105, 215], [192, 110], [222, 157], [309, 52]]);
ctx.strokeStyle = "red";
ctx.stroke();

/**
* 绘制折线
*/
function drawLine(pixel) {
ctx.beginPath();
ctx.moveTo(pixel[0][0], pixel[0][1]);
for (let m = 1; m < pixel.length; m += 1) {
ctx.lineTo(pixel[m][0], pixel[m][1]);
}
}
</script>

  接下来我们讲解一下如何绘制虚线,绘制虚线是在设置样式时由ctx.setLineDash([])方法指定的,这个方法接受一个整数数组作为参数,其含义为:“虚线长度 空格长度 虚线长度 空格长度”,画布在绘制虚线时就会按照这个数组的长度信息重复绘制,使用不同的参数可得到不同的选线效果,我们看看以下示例:

运行效果
完整的代码如下:

{.line-numbers}
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
<script>
// 从页面中获取画布对象
let canvas = document.getElementById('canvas');
// 从画布中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');

// 绘制虚线n
ctx.lineWidth=2;
let y = 100;
drawDashedLine([2, 2]);
drawDashedLine([10, 10]);
drawDashedLine([20, 5]);
drawDashedLine([15, 4, 4, 4]);
drawDashedLine([20, 4, 4, 4, 4, 4, 4, 4]);
drawDashedLine([12, 4, 4]);

function drawDashedLine(pattern) {
ctx.beginPath();
ctx.setLineDash(pattern);
ctx.moveTo(500, y);
ctx.lineTo(800, y);
ctx.stroke();
y += 40;
}
</script>

  多边形的绘制和绘制折线的过程非常相似,其区别是最后一个点需和第一个点的坐标重合,也可使用closePath()实现闭合折线。接下来我们看一个绘制多边形(五角星)的例子:

运行效果

其源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script>
// 从页面中获取画布对象
let canvas = document.getElementById('canvas');
// 从画布中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');

// 建立五角星的绘制路径
ctx.beginPath();
ctx.moveTo(100, 20);
ctx.lineTo(53, 164);
ctx.lineTo(176, 76);
ctx.lineTo(24, 76);
ctx.lineTo(147, 164)
ctx.closePath();

// 绘制
ctx.fillStyle = "red";
ctx.fill();
</script>

  在这个例子中,可以看到在moveTo()之后,接着lineTo()lineTo()……,就实现了多边形的绘制。
  如果没有执行moveTo(),则表示从当前位置开始绘制,初始位置是坐标原点(0,0)。
  这个例子中还执行了一个closePath()方法,表示从最后一个点至第一个点之间绘制一条直线,闭合了这段折线,首尾进行了相连,实现了多边形的绘制。

在建立路径后,需执行stroke()fill()命令,表示将路径绘制出来。

  • stroke()将会对路径进行描边,
  • fill()将会对路径进行填充。

3. 圆和圆弧

Canvas 2D API 提供了arc()命令,用于绘制圆或圆弧,其语法如下:

1
ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);
参数名 说明
x 圆弧中心(圆心)的 x 轴坐标
y 圆弧中心(圆心)的 y 轴坐标
radius 圆弧的半径
startAngle 圆弧的起始点,x 轴方向开始计算,单位以弧度表示
endAngle 圆弧的终点,单位以弧度表示。
anticlockwise 可选的Boolean值(默认值为: false),表示顺时针绘制圆弧,为 true表示逆时针绘制圆弧

  圆的绘制也是通过建立路径开始的,我们通过几个例子,来了解各个参数的含义,首先看看绘制圆的示例,其运行效果如下图所示:
运行效果

这个例子中绘制了一个带边框的圆和一个填充圆,其代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script>
// 从页面中获取画布对象
let canvas = document.getElementById('canvas');
// 从画布中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');
// 绘制背景网格线
drawGrid('lightgray', 10, 10);

// 绘制一个带边框的圆
ctx.beginPath();
ctx.arc(150, 150, 100, 0, 2 * Math.PI);
ctx.lineWidth = 4;
ctx.stroke();

// 绘制一个蓝色填充的圆
ctx.beginPath();
ctx.arc(400, 150, 100, 0, 2 * Math.PI);
ctx.fillStyle = "blue";
ctx.fill();
</script>

我们挑选出关键的几条语句:

1
2
3
4
5
6
7
// 绘制一个带边框的圆
ctx.arc(150, 150, 100, 0, 2 * Math.PI);
ctx.stroke();

// 绘制一个蓝色填充的圆
ctx.arc(400, 150, 100, 0, 2 * Math.PI);
ctx.fill();

我们在回顾一下arc()的语法:

1
ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);

  前两个参数为圆心的坐标(x,y),第3个参数为圆的半径,下图显示圆心坐标和半径:
Alt text

  第4和第5个参数是圆的起始角度终止角度,上面这个例子中,指定开始角度为0,结束角度为2π,因此绘制的是一个完整的圆,而当开始角度至结束角度不到2π时,绘制的就是一个圆弧。需要特别提醒的是:我们之前在学校学习的时候,用到的角度单位通常是,例如30°/45°/60°/90°/180°/360°等等,而acr()中需使用弧度作为单位,我们知道一周的弧度数是2π(2πr/r),因此可以通过以下代码将角度转为弧度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 将弧度转换为度
* @param {number} angleInRadians 以弧度为单位的角度
* @return {number} 角度(以度为单位)
*/
function toDegrees(angleInRadians) {
return (angleInRadians * 180) / Math.PI;
}

/**
* 将度数转换为弧度
* @param {number} angleInDegrees 以度为单位的角度
* @return {number} 角度(弧度)
*/
function toRadians(angleInDegrees) {
return (angleInDegrees * Math.PI) / 180;
}

  接来下看一下圆弧的例子,运行效果如下图:
Alt text

  在这里例子中绘制了3段圆弧,绿色的半圆,红色的顺时针135°圆弧,蓝色的逆时针135°圆弧,下图中标注了坐标信息:
Alt text
  这三段弧线都是从0°开始,可以看出0°位于圆心右侧的x坐标轴上,默认时按顺时针方向绘制圆弧,如果anticlockwise=true则逆时针绘制圆弧。绘制这三段圆弧的源代码如下:

{.line-numbers}
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
<script>
// 从页面中获取画布对象
let canvas = document.getElementById('canvas');
// 从画布中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');
// 绘制背景网格线
drawGrid('lightgray', 10, 10);

// 绘制一个绿色半圆
ctx.lineWidth = 8;
ctx.beginPath();
ctx.arc(150, 150, 100, 0, Math.PI);
ctx.strokeStyle = "green";
ctx.stroke();

// 绘制一个红色圆弧
ctx.beginPath();
ctx.arc(400, 150, 100, 0, 135 * Math.PI / 180, true);
ctx.strokeStyle = "blue";
ctx.stroke();

// 绘制另一个蓝色圆弧
ctx.beginPath();
ctx.arc(400, 150, 100, 0, 135 * Math.PI / 180, false);
ctx.strokeStyle = "red";
ctx.stroke();
</script>

  接下来我们来看一个有趣的例子,绘制一张笑脸,其效果和代码分别如下:
运行效果

{.line-numbers}
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
<script>
// 从页面中获取画布对象
let canvas = document.getElementById('canvas');
// 从画布中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');
// 绘制背景网格线
drawGrid('lightgray', 10, 10);

// 绘制笑脸
drawFace(140, 140, 10);

// 笑脸绘制函数
function drawFace(x, y, size) {
ctx.save();
ctx.translate(x, y);
ctx.beginPath();
ctx.arc(0, 0, 10 * size, 0, Math.PI * 2, true); // Outer circle
ctx.fillStyle="yellow";
ctx.fill();

ctx.beginPath();
ctx.arc(0, 0, 7 * size, 0, Math.PI, false); // Mouth
ctx.lineWidth = 4;
ctx.strokeStyle = "rgb(0,0,0)";
ctx.stroke();

ctx.beginPath();
ctx.arc(0 - 3 * size, 0 - 2.5 * size, size, 0, Math.PI * 2, true); // Left eye
ctx.moveTo(0 + 4 * size, 0 - 2.5 * size);
ctx.arc(0 + 3 * size, 0 - 2.5 * size, size, 0, Math.PI * 2, true); // Right eye
ctx.fillStyle="black";
ctx.fill();
ctx.restore();
}
</script>

4. 椭圆和椭圆弧

  绘制椭圆和绘制圆的方法非常相似,也是从建立路径开始的,通过ellipse()命令来绘制椭圆或椭圆弧,其语法如下:

1
ctx.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise);
参数名 说明
x 椭圆圆心的 x 轴坐标
y 椭圆圆心的 y 轴坐标
radiusX 椭圆长轴的半径
radiusY 椭圆短轴的半径
rotation 椭圆的旋转角度,单位以弧度表示
startAngle 将要绘制的起始点角度,从 x 轴测量,以弧度表示
endAngle 椭圆将要绘制的结束点角度,以弧度表示
anticlockwise 可选的Boolean值,如果为 true,逆时针绘制圆弧,反之,顺时针绘制

  我们通过一个例子,来了解各个参数的含义,其运行效果如下图所示:
Alt text

{.line-numbers}
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
<script>
// 从页面中获取画布对象
let canvas = document.getElementById('canvas');
// 从画布中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');

// 绘制左侧椭圆
ctx.beginPath();
ctx.ellipse(100, 100, 75, 50, 0, 0, 2 * Math.PI);
ctx.stroke();

// 绘制右侧椭圆
ctx.beginPath();
ctx.ellipse(300, 100, 50, 70, 0, 0, 2 * Math.PI);
ctx.stroke();

// 绘制辅助线
ctx.beginPath()
ctx.moveTo(0, 100)
ctx.lineTo(850, 100);
ctx.moveTo(100, 0);
ctx.lineTo(100, 200)
ctx.moveTo(300, 0);
ctx.lineTo(300, 200)
ctx.setLineDash([4, 4]);
ctx.stroke();
</script>

  从这个例子可以看出,绘制椭圆ctx.ellipse()由中心点坐标、x方向半径、y方向半径、起始角度、终止角度等几个参数绘制而成,其参数和圆的绘制非常相似。
比较特殊的是绘制椭圆多了一个旋转角度参数:

ctx.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise);

  通过一个示例了解一下这个参数,其运行效果和源代码分别如下:
运行效果

{.line-numbers}
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
<script>
// 从页面中获取画布对象
let canvas = document.getElementById('canvas');
// 从画布中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');
// 绘制背景网格线
drawGrid('lightgray', 10, 10);

// 绘制红色椭圆
ctx.lineWidth = 4;
ctx.beginPath();
ctx.ellipse(120, 120, 110, 50, 0, 0, 2 * Math.PI);
ctx.strokeStyle = "red";
ctx.stroke();

// 绘制蓝色椭圆
ctx.beginPath();
ctx.ellipse(120, 120, 110, 50, 60 * Math.PI / 180, 0, 2 * Math.PI);
ctx.strokeStyle = "blue";
ctx.stroke();

// 绘制绿色椭圆
ctx.beginPath();
ctx.ellipse(120, 120, 110, 50, 120 * Math.PI / 180, 0, 2 * Math.PI);
ctx.strokeStyle = "green";
ctx.stroke();
</script>

  在这里示例中,三个椭圆除旋转角度外,其他参数均相同,可以看出椭圆以圆心为中心点进行了旋转。

5 本章小结

  本节讲解了如何在Canvas绘制矩形、线、多边形、圆、椭圆等内容,并讲解了绘制边和填充、设置线宽、设置颜色等内容,本节内容使用了Canvas 2D API以下方法:

参数名 说明
strokeRect() 绘制一个矩形的边框
fillRect() 绘制一个填充的矩形
rect() 在路径中绘制矩形
clearRect() 清除指定矩形区域,让清除部分完全透明
beginPath() 开始一个新的路径
closePath() 将笔点返回到当前子路径起始点
moveTo() 将一个新的子路径的起始点移动到 (x,y) 坐标
lineTo() 绘制直线(使用直线连接子路径的终点到 x,y 坐标)
arc() 绘制圆弧路径
ellipse() 绘制椭圆路径
stroke() 绘制当前或已经存在的路径
fill() 填充当前或已存在的路径

同时也使用了Canvas 2D API修改了绘图时的以下样式:

参数名 说明
strokeStyle 描述画笔(绘制图形)颜色或者样式的属性
fillStyle 描述填充时颜色和样式的属性
lineWidth 设置线段宽度的属性
setLineDash() 填充线时使用虚线模式

练习一下

基本形状

  • 练习1:绘制一个绿色的(green),填充的,大小为整个画布的矩形;
  • 练习2:绘制一个金色的(gold),填充的,大小为100的圆;
  • 练习3:绘制一条蓝色的(blue),长度为200, 粗细为6的带箭头的线;

七巧板

  七巧板是由五个三角形、一个平行四边形和一个正方形组成,以下是七巧板中各块板的坐标,使用这些数据可以绘制出下面这个图形,你也动手试一试吧。

1
2
3
4
5
6
7
8
9
[
{ "coords": [[100, 50], [340, 50], [220, 170]], "fillColor": "#caff67" },
{ "coords": [[100, 50], [220, 170], [100, 290]], "fillColor": "#67becf" },
{ "coords": [[340, 50], [340, 170], [280, 230], [280, 110]], "fillColor": "#ef3d61" },
{ "coords": [[280, 110], [280, 230], [220, 170]], "fillColor": "#f9f51a" },
{ "coords": [[220, 170], [280, 230], [220, 290], [160, 230]], "fillColor": "#a54c09" },
{ "coords": [[160, 230], [220, 290], [100, 290]], "fillColor": "#fa8ccc" },
{ "coords": [[340, 170], [340, 290], [220, 290]], "fillColor": "#f6ca29" }
]

运行效果


本文为“图形开发学院”(www.graphanywhere.com)网站原创文章,遵循CC BY-NC-ND 4.0版权协议,商业转载请联系作者获得授权,非商业转载请附上原文出处链接及本声明。

历史发布版本

第1版发布时间:2023-11-01
第2版发布时间:2024-06-01