adspecs 0.1.18 → 0.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.adspecs/extensions/git/commands/adspecs.git.initialize.md +4 -4
- package/.adspecs/extensions/git/extension.yml +1 -1
- package/.adspecs/extensions/git/git-config.yml +17 -2
- package/.adspecs/extensions.yml +32 -0
- package/.adspecs/paths.json +17 -0
- package/.adspecs/templates/02-/344/272/247/345/223/201/351/234/200/346/261/202/350/257/264/346/230/216/344/271/246/346/250/241/346/235/277.md +4 -0
- package/.adspecs/workflows/adspecs/workflow.yml +5 -4
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.qoder-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/references/antd-front-demo/.env +4 -4
- package/references/antd-front-demo/.env.develop +12 -0
- package/references/antd-front-demo/.env.production +5 -0
- package/references/antd-front-demo/package-lock.json +2 -2
- package/references/antd-front-demo/package.json +1 -1
- package/skills/adspecs-front-prototype/SKILL.md +405 -0
- package/skills/adspecs-git-commit/SKILL.md +23 -8
- package/skills/adspecs-git-initialize/SKILL.md +54 -0
- package/src/lib/prompts.js +23 -21
- package/references/antd-front-demo/src/pages/aps/coil/components/CoilDrawer.tsx +0 -141
- package/references/antd-front-demo/src/pages/aps/coil/index.tsx +0 -209
- package/references/antd-front-demo/src/pages/aps/optimization/index.tsx +0 -192
- package/references/antd-front-demo/src/pages/aps/order/components/OrderDrawer.tsx +0 -251
- package/references/antd-front-demo/src/pages/aps/order/index.tsx +0 -273
- package/references/antd-front-demo/src/pages/aps/param/index.tsx +0 -153
- package/references/antd-front-demo/src/pages/aps/plan/components/CuttingPlanVisualization.tsx +0 -821
- package/references/antd-front-demo/src/pages/aps/plan/index.tsx +0 -254
- package/references/antd-front-demo/src/pages/aps/task/index.tsx +0 -190
package/references/antd-front-demo/src/pages/aps/plan/components/CuttingPlanVisualization.tsx
DELETED
|
@@ -1,821 +0,0 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
2
|
-
import { Card, Tag, Space, Typography, Row, Col, Statistic } from 'antd';
|
|
3
|
-
import { useTranslation } from 'react-i18next';
|
|
4
|
-
import type { PlanDetailVO, SmallCoilVO } from '@/types/aps/plan';
|
|
5
|
-
|
|
6
|
-
const { Text } = Typography;
|
|
7
|
-
|
|
8
|
-
interface CuttingPlanVisualizationProps {
|
|
9
|
-
plan: PlanDetailVO;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface Region {
|
|
13
|
-
index: number;
|
|
14
|
-
startY: number; // 沿母卷长度方向(Y 轴)的起始位置
|
|
15
|
-
endY: number; // 沿母卷长度方向(Y 轴)的结束位置
|
|
16
|
-
length: number;
|
|
17
|
-
verticalCuts: number[];
|
|
18
|
-
strips: Strip[];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface Strip {
|
|
22
|
-
x: number; // 沿母卷宽度方向(X 轴)的起始位置
|
|
23
|
-
width: number; // 窄条宽度(沿 X 轴)
|
|
24
|
-
smallCoil?: SmallCoilVO;
|
|
25
|
-
isWasteEdge: boolean;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// 订单颜色映射(全局缓存)
|
|
29
|
-
const ORDER_COLORS: Record<string, string> = {};
|
|
30
|
-
const COLOR_PALETTE = [
|
|
31
|
-
'#1890ff', '#52c41a', '#faad14', '#f5222d', '#722ed1',
|
|
32
|
-
'#13c2c2', '#eb2f96', '#fa8c16', '#a0d911', '#2f54eb',
|
|
33
|
-
];
|
|
34
|
-
let colorIndex = 0;
|
|
35
|
-
|
|
36
|
-
function getOrderColor(orderId: string | undefined): string {
|
|
37
|
-
if (!orderId) return '#bfbfbf';
|
|
38
|
-
if (!ORDER_COLORS[orderId]) {
|
|
39
|
-
ORDER_COLORS[orderId] = COLOR_PALETTE[colorIndex % COLOR_PALETTE.length];
|
|
40
|
-
colorIndex++;
|
|
41
|
-
}
|
|
42
|
-
return ORDER_COLORS[orderId];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// 区域背景色(浅色)
|
|
46
|
-
const REGION_BG_COLORS = [
|
|
47
|
-
'rgba(24, 144, 255, 0.06)',
|
|
48
|
-
'rgba(82, 196, 26, 0.06)',
|
|
49
|
-
'rgba(250, 173, 20, 0.06)',
|
|
50
|
-
'rgba(114, 46, 209, 0.06)',
|
|
51
|
-
'rgba(19, 194, 194, 0.06)',
|
|
52
|
-
];
|
|
53
|
-
|
|
54
|
-
const CuttingPlanVisualization: React.FC<CuttingPlanVisualizationProps> = ({ plan }) => {
|
|
55
|
-
const { t } = useTranslation('aps/plan');
|
|
56
|
-
|
|
57
|
-
// 必须在 useMemo 之前声明,因为 useMemo 内部引用了 coilWidth
|
|
58
|
-
const coilWidth = plan.coilWidth || 1500;
|
|
59
|
-
|
|
60
|
-
const visualizationData = useMemo(() => {
|
|
61
|
-
// 解析竖切切点(每个区域一组)—— 竖切沿母卷宽度方向切割
|
|
62
|
-
const verticalCutsByRegion = plan.verticalCuts.split(';').map(s =>
|
|
63
|
-
s.split(',').map(n => parseFloat(n.trim())).filter(n => !isNaN(n))
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
// 解析横切切点 —— 横切沿母卷长度方向切割,将母卷分为多个区域
|
|
67
|
-
const horizontalCuts = plan.horizontalCuts
|
|
68
|
-
? plan.horizontalCuts.split(',').map(n => parseFloat(n.trim())).filter(n => !isNaN(n))
|
|
69
|
-
: [];
|
|
70
|
-
|
|
71
|
-
// 工艺参数(假设值)
|
|
72
|
-
const headWaste = 100; // 废头 mm
|
|
73
|
-
const tailWaste = 100; // 废尾 mm
|
|
74
|
-
const cutHeadLength = 200; // 切头 mm
|
|
75
|
-
|
|
76
|
-
// 构建区域数据(竖向布局:区域沿 Y 轴排列)
|
|
77
|
-
const regions: Region[] = [];
|
|
78
|
-
let currentY = headWaste; // 从废头之后开始
|
|
79
|
-
|
|
80
|
-
for (let i = 0; i < verticalCutsByRegion.length; i++) {
|
|
81
|
-
const verticalCuts = verticalCutsByRegion[i];
|
|
82
|
-
|
|
83
|
-
// 计算区域结束位置(长度方向)
|
|
84
|
-
let regionEndY: number;
|
|
85
|
-
if (i < horizontalCuts.length) {
|
|
86
|
-
regionEndY = horizontalCuts[i];
|
|
87
|
-
} else {
|
|
88
|
-
// 最后一个区域,根据小卷长度估算
|
|
89
|
-
const regionCoils = plan.smallCoils.filter(sc => sc.region === i + 1);
|
|
90
|
-
const avgLength = regionCoils.length > 0
|
|
91
|
-
? regionCoils.reduce((sum, sc) => sum + sc.length, 0) / regionCoils.length
|
|
92
|
-
: 100000;
|
|
93
|
-
regionEndY = currentY + avgLength;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const regionLength = regionEndY - currentY;
|
|
97
|
-
|
|
98
|
-
// 构建窄条(竖切沿宽度方向,窄条在 X 轴上排列)
|
|
99
|
-
const strips: Strip[] = [];
|
|
100
|
-
|
|
101
|
-
// 左废边:从母卷左边缘(0)到第一个竖切切点
|
|
102
|
-
const leftWasteWidth = verticalCuts[0];
|
|
103
|
-
strips.push({
|
|
104
|
-
x: 0,
|
|
105
|
-
width: leftWasteWidth,
|
|
106
|
-
isWasteEdge: true,
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
// 有效窄条:相邻竖切切点之间的所有窄条
|
|
110
|
-
for (let j = 0; j < verticalCuts.length - 1; j++) {
|
|
111
|
-
const x = verticalCuts[j];
|
|
112
|
-
const stripWidth = verticalCuts[j + 1] - x;
|
|
113
|
-
|
|
114
|
-
// 查找对应的小卷
|
|
115
|
-
const smallCoil = plan.smallCoils.find(sc =>
|
|
116
|
-
sc.region === i + 1 &&
|
|
117
|
-
Math.abs(sc.width - stripWidth) < 1
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
strips.push({ x, width: stripWidth, smallCoil, isWasteEdge: false });
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// 右侧区域:从最后一个竖切切点到母卷右边缘
|
|
124
|
-
const lastCutPos = verticalCuts[verticalCuts.length - 1];
|
|
125
|
-
const rightAreaWidth = coilWidth - lastCutPos;
|
|
126
|
-
|
|
127
|
-
// 判断右侧是废边还是未分配区域
|
|
128
|
-
// 如果宽度 <= 参数A(假设 10mm),则认为是右废边
|
|
129
|
-
const EDGE_WASTE_THRESHOLD = 15; // 废边宽度阈值
|
|
130
|
-
const isRightWaste = rightAreaWidth <= EDGE_WASTE_THRESHOLD;
|
|
131
|
-
|
|
132
|
-
if (isRightWaste) {
|
|
133
|
-
// 右废边
|
|
134
|
-
strips.push({
|
|
135
|
-
x: lastCutPos,
|
|
136
|
-
width: rightAreaWidth,
|
|
137
|
-
isWasteEdge: true,
|
|
138
|
-
});
|
|
139
|
-
} else {
|
|
140
|
-
// 未分配区域(右侧剩余宽度)
|
|
141
|
-
strips.push({
|
|
142
|
-
x: lastCutPos,
|
|
143
|
-
width: rightAreaWidth,
|
|
144
|
-
smallCoil: undefined,
|
|
145
|
-
isWasteEdge: false,
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
regions.push({
|
|
150
|
-
index: i,
|
|
151
|
-
startY: currentY,
|
|
152
|
-
endY: regionEndY,
|
|
153
|
-
length: regionLength,
|
|
154
|
-
verticalCuts,
|
|
155
|
-
strips,
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
currentY = regionEndY;
|
|
159
|
-
|
|
160
|
-
// 添加切头(除最后一个区域外)
|
|
161
|
-
if (i < verticalCutsByRegion.length - 1) {
|
|
162
|
-
currentY += cutHeadLength;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const totalLength = currentY + tailWaste;
|
|
167
|
-
|
|
168
|
-
return {
|
|
169
|
-
regions,
|
|
170
|
-
horizontalCuts,
|
|
171
|
-
headWaste,
|
|
172
|
-
tailWaste,
|
|
173
|
-
cutHeadLength,
|
|
174
|
-
totalLength,
|
|
175
|
-
};
|
|
176
|
-
}, [plan, coilWidth]);
|
|
177
|
-
|
|
178
|
-
// SVG 参数 —— 竖向布局
|
|
179
|
-
|
|
180
|
-
// 竖向布局:X 轴 = 母卷宽度方向,Y 轴 = 母卷长度方向
|
|
181
|
-
const containerWidth = 800; // viewBox 宽度
|
|
182
|
-
const padding = { top: 50, right: 80, bottom: 50, left: 50 };
|
|
183
|
-
|
|
184
|
-
// X 方向缩放(宽度方向)
|
|
185
|
-
const availableWidth = containerWidth - padding.left - padding.right;
|
|
186
|
-
const scaleX = availableWidth / coilWidth;
|
|
187
|
-
const drawingWidth = coilWidth * scaleX;
|
|
188
|
-
|
|
189
|
-
// Y 方向缩放(长度方向)—— 使用较小的缩放比,保持合理高度
|
|
190
|
-
// 目标:总长度映射到约 500px
|
|
191
|
-
const targetDrawingHeight = 500;
|
|
192
|
-
const scaleY = targetDrawingHeight / visualizationData.totalLength;
|
|
193
|
-
const drawingHeight = visualizationData.totalLength * scaleY;
|
|
194
|
-
|
|
195
|
-
const svgHeight = drawingHeight + padding.top + padding.bottom;
|
|
196
|
-
const offsetX = padding.left;
|
|
197
|
-
const offsetY = padding.top;
|
|
198
|
-
|
|
199
|
-
// 获取订单列表(用于图例)
|
|
200
|
-
const orderList = useMemo(() => {
|
|
201
|
-
const orders = new Set<string>();
|
|
202
|
-
plan.smallCoils.forEach(sc => {
|
|
203
|
-
if (sc.orderId) orders.add(sc.orderId);
|
|
204
|
-
});
|
|
205
|
-
return Array.from(orders).sort();
|
|
206
|
-
}, [plan.smallCoils]);
|
|
207
|
-
|
|
208
|
-
// 统计信息
|
|
209
|
-
const stats = useMemo(() => {
|
|
210
|
-
const assignedCoils = plan.smallCoils.filter(sc => sc.orderId);
|
|
211
|
-
const unassignedCoils = plan.smallCoils.filter(sc => !sc.orderId);
|
|
212
|
-
const totalWeight = plan.smallCoils.reduce((sum, sc) => sum + sc.weight, 0);
|
|
213
|
-
const assignedWeight = assignedCoils.reduce((sum, sc) => sum + sc.weight, 0);
|
|
214
|
-
|
|
215
|
-
return {
|
|
216
|
-
totalCoils: plan.smallCoils.length,
|
|
217
|
-
assignedCoils: assignedCoils.length,
|
|
218
|
-
unassignedCoils: unassignedCoils.length,
|
|
219
|
-
totalWeight,
|
|
220
|
-
assignedWeight,
|
|
221
|
-
assignmentRate: totalWeight > 0 ? (assignedWeight / totalWeight * 100) : 0,
|
|
222
|
-
};
|
|
223
|
-
}, [plan.smallCoils]);
|
|
224
|
-
|
|
225
|
-
return (
|
|
226
|
-
<Card title={t('visualization')} style={{ marginTop: 16 }}>
|
|
227
|
-
<Space direction="vertical" style={{ width: '100%' }} size="large">
|
|
228
|
-
{/* 统计信息 */}
|
|
229
|
-
<Row gutter={16}>
|
|
230
|
-
<Col span={4}>
|
|
231
|
-
<Statistic title={t('coilId')} value={plan.coilId} styles={{ content: { fontSize: 16 } }} />
|
|
232
|
-
</Col>
|
|
233
|
-
<Col span={4}>
|
|
234
|
-
<Statistic title={t('coilWidth')} value={coilWidth} suffix="mm" styles={{ content: { fontSize: 16 } }} />
|
|
235
|
-
</Col>
|
|
236
|
-
<Col span={4}>
|
|
237
|
-
<Statistic title={t('coilWeight')} value={plan.coilWeight} suffix="吨" styles={{ content: { fontSize: 16 } }} />
|
|
238
|
-
</Col>
|
|
239
|
-
<Col span={3}>
|
|
240
|
-
<Statistic title={t('totalCuts')} value={plan.totalCuts} suffix="刀" styles={{ content: { fontSize: 16 } }} />
|
|
241
|
-
</Col>
|
|
242
|
-
<Col span={3}>
|
|
243
|
-
<Statistic
|
|
244
|
-
title={t('theoreticalUtilization')}
|
|
245
|
-
value={plan.theoreticalUtilization}
|
|
246
|
-
suffix="%"
|
|
247
|
-
precision={2}
|
|
248
|
-
styles={{ content: { fontSize: 16, color: '#52c41a' } }}
|
|
249
|
-
/>
|
|
250
|
-
</Col>
|
|
251
|
-
<Col span={3}>
|
|
252
|
-
<Statistic
|
|
253
|
-
title="分配率"
|
|
254
|
-
value={stats.assignmentRate}
|
|
255
|
-
suffix="%"
|
|
256
|
-
precision={1}
|
|
257
|
-
styles={{ content: { fontSize: 16, color: stats.assignmentRate >= 90 ? '#52c41a' : '#faad14' } }}
|
|
258
|
-
/>
|
|
259
|
-
</Col>
|
|
260
|
-
<Col span={3}>
|
|
261
|
-
<Statistic
|
|
262
|
-
title="小卷数"
|
|
263
|
-
value={stats.totalCoils}
|
|
264
|
-
suffix={`(${stats.assignedCoils}/${stats.unassignedCoils})`}
|
|
265
|
-
styles={{ content: { fontSize: 16 } }}
|
|
266
|
-
/>
|
|
267
|
-
</Col>
|
|
268
|
-
</Row>
|
|
269
|
-
|
|
270
|
-
{/* SVG 竖向平面图 */}
|
|
271
|
-
<div style={{
|
|
272
|
-
width: '96%',
|
|
273
|
-
margin: '0 auto',
|
|
274
|
-
overflowY: 'auto',
|
|
275
|
-
background: '#fafafa',
|
|
276
|
-
padding: 16,
|
|
277
|
-
borderRadius: 4,
|
|
278
|
-
border: '1px solid #f0f0f0',
|
|
279
|
-
}}>
|
|
280
|
-
<svg
|
|
281
|
-
viewBox={`0 0 ${containerWidth} ${svgHeight}`}
|
|
282
|
-
width="100%"
|
|
283
|
-
style={{ display: 'block' }}
|
|
284
|
-
>
|
|
285
|
-
{/* 定义箭头标记 */}
|
|
286
|
-
<defs>
|
|
287
|
-
<marker id="arrowL" markerWidth="8" markerHeight="8" refX="0" refY="3" orient="auto">
|
|
288
|
-
<path d="M8,0 L0,3 L8,6" fill="#666" />
|
|
289
|
-
</marker>
|
|
290
|
-
<marker id="arrowR" markerWidth="8" markerHeight="8" refX="8" refY="3" orient="auto">
|
|
291
|
-
<path d="M0,0 L8,3 L0,6" fill="#666" />
|
|
292
|
-
</marker>
|
|
293
|
-
<marker id="arrowU" markerWidth="8" markerHeight="8" refX="3" refY="0" orient="auto">
|
|
294
|
-
<path d="M0,8 L3,0 L6,8" fill="#666" />
|
|
295
|
-
</marker>
|
|
296
|
-
<marker id="arrowD" markerWidth="8" markerHeight="8" refX="3" refY="8" orient="auto">
|
|
297
|
-
<path d="M0,0 L3,8 L6,0" fill="#666" />
|
|
298
|
-
</marker>
|
|
299
|
-
</defs>
|
|
300
|
-
|
|
301
|
-
{/* 母卷轮廓 */}
|
|
302
|
-
<rect
|
|
303
|
-
x={offsetX}
|
|
304
|
-
y={offsetY}
|
|
305
|
-
width={drawingWidth}
|
|
306
|
-
height={drawingHeight}
|
|
307
|
-
fill="#fff"
|
|
308
|
-
stroke="#333"
|
|
309
|
-
strokeWidth={2}
|
|
310
|
-
/>
|
|
311
|
-
|
|
312
|
-
{/* ===== 废头区域(顶部) ===== */}
|
|
313
|
-
<rect
|
|
314
|
-
x={offsetX}
|
|
315
|
-
y={offsetY}
|
|
316
|
-
width={drawingWidth}
|
|
317
|
-
height={visualizationData.headWaste * scaleY}
|
|
318
|
-
fill="#f0f0f0"
|
|
319
|
-
stroke="#999"
|
|
320
|
-
strokeWidth={1}
|
|
321
|
-
strokeDasharray="5,5"
|
|
322
|
-
/>
|
|
323
|
-
<text
|
|
324
|
-
x={offsetX + drawingWidth / 2}
|
|
325
|
-
y={offsetY + (visualizationData.headWaste * scaleY) / 2 - 5}
|
|
326
|
-
textAnchor="middle"
|
|
327
|
-
dominantBaseline="middle"
|
|
328
|
-
fontSize={10}
|
|
329
|
-
fill="#666"
|
|
330
|
-
fontWeight="bold"
|
|
331
|
-
>
|
|
332
|
-
废头
|
|
333
|
-
</text>
|
|
334
|
-
<text
|
|
335
|
-
x={offsetX + drawingWidth / 2}
|
|
336
|
-
y={offsetY + (visualizationData.headWaste * scaleY) / 2 + 8}
|
|
337
|
-
textAnchor="middle"
|
|
338
|
-
dominantBaseline="middle"
|
|
339
|
-
fontSize={9}
|
|
340
|
-
fill="#999"
|
|
341
|
-
>
|
|
342
|
-
{visualizationData.headWaste}mm
|
|
343
|
-
</text>
|
|
344
|
-
|
|
345
|
-
{/* ===== 废尾区域(底部) ===== */}
|
|
346
|
-
<rect
|
|
347
|
-
x={offsetX}
|
|
348
|
-
y={offsetY + drawingHeight - visualizationData.tailWaste * scaleY}
|
|
349
|
-
width={drawingWidth}
|
|
350
|
-
height={visualizationData.tailWaste * scaleY}
|
|
351
|
-
fill="#f0f0f0"
|
|
352
|
-
stroke="#999"
|
|
353
|
-
strokeWidth={1}
|
|
354
|
-
strokeDasharray="5,5"
|
|
355
|
-
/>
|
|
356
|
-
<text
|
|
357
|
-
x={offsetX + drawingWidth / 2}
|
|
358
|
-
y={offsetY + drawingHeight - (visualizationData.tailWaste * scaleY) / 2 - 5}
|
|
359
|
-
textAnchor="middle"
|
|
360
|
-
dominantBaseline="middle"
|
|
361
|
-
fontSize={10}
|
|
362
|
-
fill="#666"
|
|
363
|
-
fontWeight="bold"
|
|
364
|
-
>
|
|
365
|
-
废尾
|
|
366
|
-
</text>
|
|
367
|
-
<text
|
|
368
|
-
x={offsetX + drawingWidth / 2}
|
|
369
|
-
y={offsetY + drawingHeight - (visualizationData.tailWaste * scaleY) / 2 + 8}
|
|
370
|
-
textAnchor="middle"
|
|
371
|
-
dominantBaseline="middle"
|
|
372
|
-
fontSize={9}
|
|
373
|
-
fill="#999"
|
|
374
|
-
>
|
|
375
|
-
{visualizationData.tailWaste}mm
|
|
376
|
-
</text>
|
|
377
|
-
|
|
378
|
-
{/* ===== 绘制各区域 ===== */}
|
|
379
|
-
{visualizationData.regions.map((region) => {
|
|
380
|
-
const regionY = offsetY + region.startY * scaleY;
|
|
381
|
-
const regionH = region.length * scaleY;
|
|
382
|
-
const bgColor = REGION_BG_COLORS[region.index % REGION_BG_COLORS.length];
|
|
383
|
-
|
|
384
|
-
return (
|
|
385
|
-
<g key={region.index}>
|
|
386
|
-
{/* 区域背景 */}
|
|
387
|
-
<rect
|
|
388
|
-
x={offsetX}
|
|
389
|
-
y={regionY}
|
|
390
|
-
width={drawingWidth}
|
|
391
|
-
height={regionH}
|
|
392
|
-
fill={bgColor}
|
|
393
|
-
stroke="none"
|
|
394
|
-
/>
|
|
395
|
-
|
|
396
|
-
{/* 区域标签(左侧) */}
|
|
397
|
-
<rect
|
|
398
|
-
x={offsetX - 36}
|
|
399
|
-
y={regionY + regionH / 2 - 10}
|
|
400
|
-
width={30}
|
|
401
|
-
height={20}
|
|
402
|
-
fill="#1890ff"
|
|
403
|
-
rx={3}
|
|
404
|
-
/>
|
|
405
|
-
<text
|
|
406
|
-
x={offsetX - 21}
|
|
407
|
-
y={regionY + regionH / 2}
|
|
408
|
-
textAnchor="middle"
|
|
409
|
-
dominantBaseline="middle"
|
|
410
|
-
fontSize={11}
|
|
411
|
-
fill="#fff"
|
|
412
|
-
fontWeight="bold"
|
|
413
|
-
>
|
|
414
|
-
R{region.index + 1}
|
|
415
|
-
</text>
|
|
416
|
-
|
|
417
|
-
{/* 区域长度标注(右侧) */}
|
|
418
|
-
<text
|
|
419
|
-
x={offsetX + drawingWidth + 8}
|
|
420
|
-
y={regionY + regionH / 2}
|
|
421
|
-
textAnchor="start"
|
|
422
|
-
dominantBaseline="middle"
|
|
423
|
-
fontSize={9}
|
|
424
|
-
fill="#666"
|
|
425
|
-
>
|
|
426
|
-
{Math.round(region.length)}mm
|
|
427
|
-
</text>
|
|
428
|
-
|
|
429
|
-
{/* ===== 绘制窄条(沿 X 轴排列) ===== */}
|
|
430
|
-
{region.strips.map((strip, stripIndex) => {
|
|
431
|
-
const stripX = offsetX + strip.x * scaleX;
|
|
432
|
-
const stripW = strip.width * scaleX;
|
|
433
|
-
|
|
434
|
-
if (strip.isWasteEdge) {
|
|
435
|
-
// 废边(左/右两侧)
|
|
436
|
-
return (
|
|
437
|
-
<g key={`waste-${stripIndex}`}>
|
|
438
|
-
<rect
|
|
439
|
-
x={stripX}
|
|
440
|
-
y={regionY}
|
|
441
|
-
width={stripW}
|
|
442
|
-
height={regionH}
|
|
443
|
-
fill="#e8e8e8"
|
|
444
|
-
stroke="#bbb"
|
|
445
|
-
strokeWidth={0.5}
|
|
446
|
-
strokeDasharray="3,3"
|
|
447
|
-
/>
|
|
448
|
-
{stripW > 12 && (
|
|
449
|
-
<text
|
|
450
|
-
x={stripX + stripW / 2}
|
|
451
|
-
y={regionY + regionH / 2}
|
|
452
|
-
textAnchor="middle"
|
|
453
|
-
dominantBaseline="middle"
|
|
454
|
-
fontSize={8}
|
|
455
|
-
fill="#999"
|
|
456
|
-
transform={`rotate(-90, ${stripX + stripW / 2}, ${regionY + regionH / 2})`}
|
|
457
|
-
>
|
|
458
|
-
废边 {strip.width}mm
|
|
459
|
-
</text>
|
|
460
|
-
)}
|
|
461
|
-
</g>
|
|
462
|
-
);
|
|
463
|
-
} else {
|
|
464
|
-
// 有效窄条
|
|
465
|
-
const hasOrder = !!strip.smallCoil?.orderId;
|
|
466
|
-
const color = hasOrder ? getOrderColor(strip.smallCoil!.orderId) : '#91caff';
|
|
467
|
-
const showLabel = stripW > 20;
|
|
468
|
-
const showDetail = stripW > 40 && regionH > 30;
|
|
469
|
-
|
|
470
|
-
return (
|
|
471
|
-
<g key={`strip-${stripIndex}`}>
|
|
472
|
-
<rect
|
|
473
|
-
x={stripX}
|
|
474
|
-
y={regionY}
|
|
475
|
-
width={stripW}
|
|
476
|
-
height={regionH}
|
|
477
|
-
fill={color}
|
|
478
|
-
fillOpacity={hasOrder ? 0.35 : 0.25}
|
|
479
|
-
stroke={color}
|
|
480
|
-
strokeWidth={hasOrder ? 1.5 : 1}
|
|
481
|
-
strokeDasharray={hasOrder ? 'none' : '4,2'}
|
|
482
|
-
/>
|
|
483
|
-
{/* 窄条宽度标注 */}
|
|
484
|
-
{showLabel && (
|
|
485
|
-
<text
|
|
486
|
-
x={stripX + stripW / 2}
|
|
487
|
-
y={regionY + regionH / 2 - (showDetail ? 6 : 0)}
|
|
488
|
-
textAnchor="middle"
|
|
489
|
-
dominantBaseline="middle"
|
|
490
|
-
fontSize={9}
|
|
491
|
-
fill="#333"
|
|
492
|
-
fontWeight="bold"
|
|
493
|
-
>
|
|
494
|
-
{strip.width}mm
|
|
495
|
-
</text>
|
|
496
|
-
)}
|
|
497
|
-
{/* 订单标注 */}
|
|
498
|
-
{hasOrder && showDetail && (
|
|
499
|
-
<text
|
|
500
|
-
x={stripX + stripW / 2}
|
|
501
|
-
y={regionY + regionH / 2 + 8}
|
|
502
|
-
textAnchor="middle"
|
|
503
|
-
dominantBaseline="middle"
|
|
504
|
-
fontSize={8}
|
|
505
|
-
fill="#333"
|
|
506
|
-
>
|
|
507
|
-
{strip.smallCoil!.orderId}
|
|
508
|
-
</text>
|
|
509
|
-
)}
|
|
510
|
-
</g>
|
|
511
|
-
);
|
|
512
|
-
}
|
|
513
|
-
})}
|
|
514
|
-
|
|
515
|
-
{/* ===== 竖切切点标注(沿 X 轴,垂直线) ===== */}
|
|
516
|
-
{region.verticalCuts.map((cutPos, cutIndex) => {
|
|
517
|
-
const cutX = offsetX + cutPos * scaleX;
|
|
518
|
-
return (
|
|
519
|
-
<g key={`vcut-${cutIndex}`}>
|
|
520
|
-
{/* 竖切线(垂直线) */}
|
|
521
|
-
<line
|
|
522
|
-
x1={cutX}
|
|
523
|
-
y1={regionY}
|
|
524
|
-
x2={cutX}
|
|
525
|
-
y2={regionY + regionH}
|
|
526
|
-
stroke="#1890ff"
|
|
527
|
-
strokeWidth={1}
|
|
528
|
-
strokeDasharray="4,2"
|
|
529
|
-
opacity={0.6}
|
|
530
|
-
/>
|
|
531
|
-
</g>
|
|
532
|
-
);
|
|
533
|
-
})}
|
|
534
|
-
|
|
535
|
-
{/* ===== 横切线(区域分隔线,水平线) ===== */}
|
|
536
|
-
{region.index > 0 && (
|
|
537
|
-
<g>
|
|
538
|
-
<line
|
|
539
|
-
x1={offsetX - 6}
|
|
540
|
-
y1={regionY}
|
|
541
|
-
x2={offsetX + drawingWidth + 6}
|
|
542
|
-
y2={regionY}
|
|
543
|
-
stroke="#f5222d"
|
|
544
|
-
strokeWidth={2.5}
|
|
545
|
-
strokeDasharray="6,3"
|
|
546
|
-
/>
|
|
547
|
-
{/* 横切标记 */}
|
|
548
|
-
<circle cx={offsetX - 6} cy={regionY} r={3} fill="#f5222d" />
|
|
549
|
-
<circle cx={offsetX + drawingWidth + 6} cy={regionY} r={3} fill="#f5222d" />
|
|
550
|
-
{/* 横切位置标注 */}
|
|
551
|
-
{visualizationData.horizontalCuts[region.index - 1] !== undefined && (
|
|
552
|
-
<text
|
|
553
|
-
x={offsetX + drawingWidth + 45}
|
|
554
|
-
y={regionY}
|
|
555
|
-
textAnchor="start"
|
|
556
|
-
dominantBaseline="middle"
|
|
557
|
-
fontSize={8}
|
|
558
|
-
fill="#f5222d"
|
|
559
|
-
fontWeight="bold"
|
|
560
|
-
>
|
|
561
|
-
H{region.index}:{visualizationData.horizontalCuts[region.index - 1]}
|
|
562
|
-
</text>
|
|
563
|
-
)}
|
|
564
|
-
{/* 切头标注 */}
|
|
565
|
-
{visualizationData.cutHeadLength > 0 && (
|
|
566
|
-
<>
|
|
567
|
-
<rect
|
|
568
|
-
x={offsetX}
|
|
569
|
-
y={regionY - visualizationData.cutHeadLength * scaleY}
|
|
570
|
-
width={drawingWidth}
|
|
571
|
-
height={visualizationData.cutHeadLength * scaleY}
|
|
572
|
-
fill="#fff1f0"
|
|
573
|
-
stroke="#ffa39e"
|
|
574
|
-
strokeWidth={0.5}
|
|
575
|
-
strokeDasharray="3,3"
|
|
576
|
-
/>
|
|
577
|
-
{visualizationData.cutHeadLength * scaleY > 12 && (
|
|
578
|
-
<text
|
|
579
|
-
x={offsetX + drawingWidth / 2}
|
|
580
|
-
y={regionY - (visualizationData.cutHeadLength * scaleY) / 2}
|
|
581
|
-
textAnchor="middle"
|
|
582
|
-
dominantBaseline="middle"
|
|
583
|
-
fontSize={8}
|
|
584
|
-
fill="#f5222d"
|
|
585
|
-
>
|
|
586
|
-
切头 {visualizationData.cutHeadLength}mm
|
|
587
|
-
</text>
|
|
588
|
-
)}
|
|
589
|
-
</>
|
|
590
|
-
)}
|
|
591
|
-
</g>
|
|
592
|
-
)}
|
|
593
|
-
</g>
|
|
594
|
-
);
|
|
595
|
-
})}
|
|
596
|
-
|
|
597
|
-
{/* ===== 宽度尺寸标注(X 轴方向,底部) ===== */}
|
|
598
|
-
<line
|
|
599
|
-
x1={offsetX}
|
|
600
|
-
y1={offsetY + drawingHeight + 25}
|
|
601
|
-
x2={offsetX + drawingWidth}
|
|
602
|
-
y2={offsetY + drawingHeight + 25}
|
|
603
|
-
stroke="#666"
|
|
604
|
-
strokeWidth={1.5}
|
|
605
|
-
markerStart="url(#arrowL)"
|
|
606
|
-
markerEnd="url(#arrowR)"
|
|
607
|
-
/>
|
|
608
|
-
<line
|
|
609
|
-
x1={offsetX}
|
|
610
|
-
y1={offsetY + drawingHeight + 20}
|
|
611
|
-
x2={offsetX}
|
|
612
|
-
y2={offsetY + drawingHeight + 30}
|
|
613
|
-
stroke="#666"
|
|
614
|
-
strokeWidth={1.5}
|
|
615
|
-
/>
|
|
616
|
-
<line
|
|
617
|
-
x1={offsetX + drawingWidth}
|
|
618
|
-
y1={offsetY + drawingHeight + 20}
|
|
619
|
-
x2={offsetX + drawingWidth}
|
|
620
|
-
y2={offsetY + drawingHeight + 30}
|
|
621
|
-
stroke="#666"
|
|
622
|
-
strokeWidth={1.5}
|
|
623
|
-
/>
|
|
624
|
-
<text
|
|
625
|
-
x={offsetX + drawingWidth / 2}
|
|
626
|
-
y={offsetY + drawingHeight + 38}
|
|
627
|
-
textAnchor="middle"
|
|
628
|
-
fontSize={11}
|
|
629
|
-
fill="#333"
|
|
630
|
-
fontWeight="bold"
|
|
631
|
-
>
|
|
632
|
-
宽度 {coilWidth} mm
|
|
633
|
-
</text>
|
|
634
|
-
|
|
635
|
-
{/* ===== 长度尺寸标注(Y 轴方向,右侧) ===== */}
|
|
636
|
-
<line
|
|
637
|
-
x1={offsetX + drawingWidth + 20}
|
|
638
|
-
y1={offsetY}
|
|
639
|
-
x2={offsetX + drawingWidth + 20}
|
|
640
|
-
y2={offsetY + drawingHeight}
|
|
641
|
-
stroke="#666"
|
|
642
|
-
strokeWidth={1.5}
|
|
643
|
-
markerStart="url(#arrowU)"
|
|
644
|
-
markerEnd="url(#arrowD)"
|
|
645
|
-
/>
|
|
646
|
-
<line
|
|
647
|
-
x1={offsetX + drawingWidth + 15}
|
|
648
|
-
y1={offsetY}
|
|
649
|
-
x2={offsetX + drawingWidth + 25}
|
|
650
|
-
y2={offsetY}
|
|
651
|
-
stroke="#666"
|
|
652
|
-
strokeWidth={1.5}
|
|
653
|
-
/>
|
|
654
|
-
<line
|
|
655
|
-
x1={offsetX + drawingWidth + 15}
|
|
656
|
-
y1={offsetY + drawingHeight}
|
|
657
|
-
x2={offsetX + drawingWidth + 25}
|
|
658
|
-
y2={offsetY + drawingHeight}
|
|
659
|
-
stroke="#666"
|
|
660
|
-
strokeWidth={1.5}
|
|
661
|
-
/>
|
|
662
|
-
<text
|
|
663
|
-
x={offsetX + drawingWidth + 35}
|
|
664
|
-
y={offsetY + drawingHeight / 2}
|
|
665
|
-
textAnchor="middle"
|
|
666
|
-
dominantBaseline="middle"
|
|
667
|
-
fontSize={11}
|
|
668
|
-
fill="#333"
|
|
669
|
-
fontWeight="bold"
|
|
670
|
-
transform={`rotate(90, ${offsetX + drawingWidth + 35}, ${offsetY + drawingHeight / 2})`}
|
|
671
|
-
>
|
|
672
|
-
长度 {Math.round(visualizationData.totalLength)} mm
|
|
673
|
-
</text>
|
|
674
|
-
|
|
675
|
-
{/* ===== 方向标注 ===== */}
|
|
676
|
-
<text
|
|
677
|
-
x={offsetX + drawingWidth / 2}
|
|
678
|
-
y={offsetY - 15}
|
|
679
|
-
textAnchor="middle"
|
|
680
|
-
fontSize={10}
|
|
681
|
-
fill="#999"
|
|
682
|
-
>
|
|
683
|
-
← 母卷宽度方向 →
|
|
684
|
-
</text>
|
|
685
|
-
<text
|
|
686
|
-
x={offsetX + drawingWidth + 55}
|
|
687
|
-
y={offsetY + drawingHeight / 2}
|
|
688
|
-
textAnchor="middle"
|
|
689
|
-
fontSize={10}
|
|
690
|
-
fill="#999"
|
|
691
|
-
transform={`rotate(90, ${offsetX + drawingWidth + 55}, ${offsetY + drawingHeight / 2})`}
|
|
692
|
-
>
|
|
693
|
-
← 母卷长度方向 →
|
|
694
|
-
</text>
|
|
695
|
-
</svg>
|
|
696
|
-
</div>
|
|
697
|
-
|
|
698
|
-
{/* 图例 */}
|
|
699
|
-
<Card size="small" title="图例">
|
|
700
|
-
<Row gutter={[16, 8]}>
|
|
701
|
-
<Col span={6}>
|
|
702
|
-
<Space size={4}>
|
|
703
|
-
<svg width={24} height={14}>
|
|
704
|
-
<line x1={0} y1={7} x2={24} y2={7} stroke="#f5222d" strokeWidth={2.5} strokeDasharray="6,3" />
|
|
705
|
-
</svg>
|
|
706
|
-
<Text>横切线(区域分隔)</Text>
|
|
707
|
-
</Space>
|
|
708
|
-
</Col>
|
|
709
|
-
<Col span={6}>
|
|
710
|
-
<Space size={4}>
|
|
711
|
-
<svg width={24} height={14}>
|
|
712
|
-
<line x1={12} y1={0} x2={12} y2={14} stroke="#1890ff" strokeWidth={1} strokeDasharray="4,2" />
|
|
713
|
-
</svg>
|
|
714
|
-
<Text>竖切线(窄条分隔)</Text>
|
|
715
|
-
</Space>
|
|
716
|
-
</Col>
|
|
717
|
-
<Col span={6}>
|
|
718
|
-
<Space size={4}>
|
|
719
|
-
<div style={{ width: 24, height: 14, background: '#e8e8e8', border: '1px dashed #999' }} />
|
|
720
|
-
<Text>废边</Text>
|
|
721
|
-
</Space>
|
|
722
|
-
</Col>
|
|
723
|
-
<Col span={6}>
|
|
724
|
-
<Space size={4}>
|
|
725
|
-
<div style={{ width: 24, height: 14, background: '#f0f0f0', border: '1px dashed #999' }} />
|
|
726
|
-
<Text>废头 / 废尾</Text>
|
|
727
|
-
</Space>
|
|
728
|
-
</Col>
|
|
729
|
-
<Col span={6}>
|
|
730
|
-
<Space size={4}>
|
|
731
|
-
<div style={{ width: 24, height: 14, background: '#fff1f0', border: '1px dashed #ffa39e' }} />
|
|
732
|
-
<Text>切头</Text>
|
|
733
|
-
</Space>
|
|
734
|
-
</Col>
|
|
735
|
-
{orderList.map(orderId => (
|
|
736
|
-
<Col span={6} key={orderId}>
|
|
737
|
-
<Space size={4}>
|
|
738
|
-
<div
|
|
739
|
-
style={{
|
|
740
|
-
width: 24,
|
|
741
|
-
height: 14,
|
|
742
|
-
background: getOrderColor(orderId),
|
|
743
|
-
opacity: 0.35,
|
|
744
|
-
border: `1.5px solid ${getOrderColor(orderId)}`,
|
|
745
|
-
}}
|
|
746
|
-
/>
|
|
747
|
-
<Text>{orderId}</Text>
|
|
748
|
-
</Space>
|
|
749
|
-
</Col>
|
|
750
|
-
))}
|
|
751
|
-
</Row>
|
|
752
|
-
</Card>
|
|
753
|
-
|
|
754
|
-
{/* 订单分配详情 */}
|
|
755
|
-
<Card size="small" title="订单分配详情">
|
|
756
|
-
<Space wrap>
|
|
757
|
-
{orderList.map(orderId => {
|
|
758
|
-
const orderCoils = plan.smallCoils.filter(sc => sc.orderId === orderId);
|
|
759
|
-
const totalWeight = orderCoils.reduce((sum, sc) => sum + sc.weight, 0);
|
|
760
|
-
const regions = Array.from(new Set(orderCoils.map(sc => sc.region))).sort();
|
|
761
|
-
return (
|
|
762
|
-
<Tag key={orderId} color={getOrderColor(orderId)} style={{ padding: '4px 8px' }}>
|
|
763
|
-
<Space size={4}>
|
|
764
|
-
<span style={{ fontWeight: 'bold' }}>{orderId}</span>
|
|
765
|
-
<span>{orderCoils.length}条</span>
|
|
766
|
-
<span>{totalWeight.toFixed(2)}吨</span>
|
|
767
|
-
<span style={{ fontSize: 11, color: '#666' }}>
|
|
768
|
-
(R{regions.join(',R')})
|
|
769
|
-
</span>
|
|
770
|
-
</Space>
|
|
771
|
-
</Tag>
|
|
772
|
-
);
|
|
773
|
-
})}
|
|
774
|
-
{stats.unassignedCoils > 0 && (
|
|
775
|
-
<Tag style={{ padding: '4px 8px' }}>
|
|
776
|
-
<Space size={4}>
|
|
777
|
-
<span style={{ fontWeight: 'bold' }}>未分配</span>
|
|
778
|
-
<span>{stats.unassignedCoils}条</span>
|
|
779
|
-
<span>
|
|
780
|
-
{plan.smallCoils.filter(sc => !sc.orderId).reduce((sum, sc) => sum + sc.weight, 0).toFixed(2)}吨
|
|
781
|
-
</span>
|
|
782
|
-
</Space>
|
|
783
|
-
</Tag>
|
|
784
|
-
)}
|
|
785
|
-
</Space>
|
|
786
|
-
</Card>
|
|
787
|
-
|
|
788
|
-
{/* 切点汇总 */}
|
|
789
|
-
<Row gutter={16}>
|
|
790
|
-
<Col span={12}>
|
|
791
|
-
<Card size="small" title={`竖切切点 (共 ${plan.verticalCuts.split(';').reduce((sum, s) => sum + s.split(',').length, 0)} 个)`}>
|
|
792
|
-
<Space wrap size={[8, 8]}>
|
|
793
|
-
{visualizationData.regions.map((region, idx) => (
|
|
794
|
-
<Tag key={idx} color="blue">
|
|
795
|
-
R{idx + 1}: [{region.verticalCuts.join(', ')}]
|
|
796
|
-
</Tag>
|
|
797
|
-
))}
|
|
798
|
-
</Space>
|
|
799
|
-
</Card>
|
|
800
|
-
</Col>
|
|
801
|
-
<Col span={12}>
|
|
802
|
-
<Card size="small" title={`横切切点 (共 ${visualizationData.horizontalCuts.length} 个)`}>
|
|
803
|
-
<Space wrap size={[8, 8]}>
|
|
804
|
-
{visualizationData.horizontalCuts.map((cut, idx) => (
|
|
805
|
-
<Tag key={idx} color="red">
|
|
806
|
-
H{idx + 1}: {cut}mm
|
|
807
|
-
</Tag>
|
|
808
|
-
))}
|
|
809
|
-
{visualizationData.horizontalCuts.length === 0 && (
|
|
810
|
-
<Text type="secondary">无横切</Text>
|
|
811
|
-
)}
|
|
812
|
-
</Space>
|
|
813
|
-
</Card>
|
|
814
|
-
</Col>
|
|
815
|
-
</Row>
|
|
816
|
-
</Space>
|
|
817
|
-
</Card>
|
|
818
|
-
);
|
|
819
|
-
};
|
|
820
|
-
|
|
821
|
-
export default CuttingPlanVisualization;
|