第 1 天 实操步骤。
准备工作
- 一个现代浏览器(Chrome/Edge)
- 一个代码编辑器(VS Code 或记事本均可)
- 几张设备图标图片(PNG/SVG),暂时可以用占位图代替
为了方便测试,我假设你的图标文件名为
ddj.png和ssx2.png,放在与 HTML 同级的images/文件夹下。如果你手头没有对应图片,可以先使用任意 50x40 尺寸的纯色图片替代。
步骤 1:创建 HTML 文件并引入 Konva
新建一个 index.html,用 CDN 引入 Konva(你也可以下载到本地)。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>WCS 设备布局 - Day1</title>
<style>
body {
margin: 0;
padding: 20px;
background: #f0f2f5;
font-family: sans-serif;
}
#container {
border: 1px solid #ccc;
background: #fff;
width: 800px;
height: 600px;
}
</style>
</head>
<body>
<h2>仓库设备布局 (静态渲染)</h2>
<div id="container"></div>
<!-- 引入 Konva -->
<script src="https://unpkg.com/konva@9/konva.min.js"></script>
<script>
// 我们的代码将写在这里
</script>
</body>
</html>
步骤 2:准备设备布局数据
把我们之前看到的 JSON 整理成一个 JavaScript 对象,放在 <script> 里。
为了让数据更纯粹,我移除了第二个元素里嵌套的 value 业务字段,只保留一个 selected 标志(如果你需要它)。
javascript
const layoutData = {
description: null,
name: null,
layout: [
{
id: "1782803001807",
deviceCode: "stacker",
imgName: "ddj",
left: 480,
top: 275,
width: 50,
height: 40,
angle: 0,
moveLength: 200,
plcMax: null,
plcMin: null,
selected: false
},
{
id: "1782803143726",
deviceCode: "conveyor", // 假设这是一个输送线设备
imgName: "ssx2",
left: 540,
top: 240,
width: 50,
height: 40,
angle: 0,
moveLength: null,
plcMax: null,
plcMin: null,
selected: false
}
]
};
步骤 3:创建 Konva 画布和图层
javascript
// 创建舞台
const stage = new Konva.Stage({
container: 'container', // 对应 div 的 id
width: 800,
height: 600
});
// 创建一个图层
const layer = new Konva.Layer();
stage.add(layer);
步骤 4:编写图片加载与节点创建函数
由于图片加载是异步的,我们需要等待所有图片准备好后再统一绘制。
javascript
// 辅助函数:根据设备配置创建 Konva.Image 节点
function createDeviceNode(device) {
return new Promise((resolve, reject) => {
const img = new window.Image();
img.crossOrigin = "anonymous"; // 如果图片在别的域,根据需要设置
img.onload = () => {
const imageNode = new Konva.Image({
id: device.id,
image: img,
x: device.left,
y: device.top,
width: device.width,
height: device.height,
rotation: device.angle, // Konva 默认旋转中心是图片左上角
// 如果需要绕中心旋转,可设置 offsetX/offsetY,但这里角度为0,暂不需要
// 把业务数据也挂到自定义属性上,供后续使用
deviceCode: device.deviceCode,
moveLength: device.moveLength,
selected: device.selected
});
resolve(imageNode);
};
img.onerror = () => {
// 如果图片不存在,用一个矩形占位
console.warn(`图片 ${device.imgName}.png 加载失败,使用占位矩形`);
const rectNode = new Konva.Rect({
id: device.id,
x: device.left,
y: device.top,
width: device.width,
height: device.height,
fill: '#cccccc',
stroke: '#333',
strokeWidth: 1,
rotation: device.angle
});
resolve(rectNode);
};
// 假设图片放在 images 文件夹下
img.src = `images/${device.imgName}.png`;
});
}
步骤 5:遍历数据并批量渲染
javascript
async function renderLayout() {
// 并发创建所有节点
const nodes = await Promise.all(
layoutData.layout.map(device => createDeviceNode(device))
);
// 将所有节点添加到图层
nodes.forEach(node => layer.add(node));
// 一次性绘制
layer.batchDraw();
}
// 启动渲染
renderLayout().then(() => {
console.log('设备布局渲染完成');
console.log('可通过 stage.findOne("#id") 查找节点');
});
步骤 6:测试与验证
- 在项目根目录创建
images文件夹,放入ddj.png和ssx2.png(你可以先随便找两张图片,大小尽量接近 50x40)。 - 用浏览器打开
index.html。 - 你应该能看到两个设备图标出现在画布右侧(对应 JSON 中的坐标)。
- 按 F12 打开控制台,如果图片加载失败,会看到警告,并且画布上会出现灰色矩形作为占位符。
完整代码(可直接运行)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>WCS 设备布局 - Day1 静态渲染</title>
<style>
body {
margin: 0;
padding: 20px;
background: #f0f2f5;
font-family: sans-serif;
}
#container {
border: 1px solid #ccc;
background: #fff;
width: 800px;
height: 600px;
}
.info {
margin-top: 10px;
font-size: 14px;
color: #666;
}
</style>
</head>
<body>
<h2>仓库设备布局</h2>
<div id="container"></div>
<div class="info">静态渲染完成。打开控制台可查看节点信息。</div>
<script src="https://unpkg.com/konva@9/konva.min.js"></script>
<script>
// 设备布局数据(来自你的 JSON)
const layoutData = {
description: null,
name: null,
layout: [
{
id: "1782803001807",
deviceCode: "stacker",
imgName: "ddj",
left: 480,
top: 275,
width: 50,
height: 40,
angle: 0,
moveLength: 200,
plcMax: null,
plcMin: null,
selected: false
},
{
id: "1782803143726",
deviceCode: "conveyor",
imgName: "ssx2",
left: 540,
top: 240,
width: 50,
height: 40,
angle: 0,
moveLength: null,
plcMax: null,
plcMin: null,
selected: false
}
]
};
// 创建舞台和图层
const stage = new Konva.Stage({
container: 'container',
width: 800,
height: 600
});
const layer = new Konva.Layer();
stage.add(layer);
// 根据设备配置创建图像节点的异步函数
function createDeviceNode(device) {
return new Promise((resolve) => {
const img = new window.Image();
img.onload = () => {
const imageNode = new Konva.Image({
id: device.id,
image: img,
x: device.left,
y: device.top,
width: device.width,
height: device.height,
rotation: device.angle,
// 附加自定义属性
deviceCode: device.deviceCode,
moveLength: device.moveLength,
selected: device.selected
});
resolve(imageNode);
};
img.onerror = () => {
console.warn(`图片 ${device.imgName}.png 加载失败,使用占位矩形`);
const rectNode = new Konva.Rect({
id: device.id,
x: device.left,
y: device.top,
width: device.width,
height: device.height,
fill: '#cccccc',
stroke: '#333',
strokeWidth: 1,
rotation: device.angle,
deviceCode: device.deviceCode,
moveLength: device.moveLength,
selected: device.selected
});
resolve(rectNode);
};
img.src = `images/${device.imgName}.png`;
});
}
// 渲染所有设备
async function renderLayout() {
const nodes = await Promise.all(
layoutData.layout.map(device => createDeviceNode(device))
);
nodes.forEach(node => layer.add(node));
layer.batchDraw();
console.log('所有设备节点已添加到图层并绘制');
console.log('示例:查找堆垛机节点', stage.findOne('#1782803001807'));
}
renderLayout();
</script>
</body>
</html>
第 1 天总结
完成以上步骤后,你已经掌握了:
- Konva 的 Stage → Layer → Node 结构
- 如何从 JSON 数据动态创建 图片节点
- 如何处理异步图片加载
x, y, rotation属性的直接映射- 为节点附加自定义业务属性(如
deviceCode、moveLength),为后面的交互做准备
明天我们将在这些节点上添加拖拽和选中高亮 ,让画面可编辑。
如果你在运行过程中遇到任何报错,直接把错误信息发来,我帮你定位。