feat: 优化库位栅格绘制逻辑,支持自适应布局和透明背景,提升视觉效果
This commit is contained in:
parent
e44b53f5c0
commit
1e86108745
@ -19,7 +19,7 @@ function roundedRectPath(ctx: CanvasRenderingContext2D, x: number, y: number, w:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在点位右上角绘制 2x3 库位栅格(静态,来自 associatedStorageLocations)。
|
* 在点位右上角绘制自适应库位栅格(静态,来自 associatedStorageLocations)。
|
||||||
* 随画布缩放和平移,由 Meta2D 世界坐标系统自然处理。
|
* 随画布缩放和平移,由 Meta2D 世界坐标系统自然处理。
|
||||||
*/
|
*/
|
||||||
export function drawStorageGrid(
|
export function drawStorageGrid(
|
||||||
@ -36,54 +36,115 @@ export function drawStorageGrid(
|
|||||||
|
|
||||||
const fontFamily = opts?.fontFamily ?? pen.calculative?.fontFamily;
|
const fontFamily = opts?.fontFamily ?? pen.calculative?.fontFamily;
|
||||||
|
|
||||||
// 尺寸与间距(世界坐标)
|
// 尺寸与间距(世界坐标)- 放大库位方框
|
||||||
const base = Math.min(w, h);
|
const base = Math.min(w, h);
|
||||||
const cell = Math.max(12, Math.min(24, base * 0.60));
|
// 单个库位时使用更大的方框,多个库位时使用正常大小
|
||||||
const gap = Math.max(2, Math.min(6, base * 0.08));
|
const cell = Math.max(16, Math.min(32, base * 0.70)); // 基础库位方框大小
|
||||||
const gridCols = 3;
|
const gap = Math.max(1, Math.min(2, base * 0.03)); // 进一步减少间距
|
||||||
const gridRows = 2;
|
|
||||||
const gridW = gridCols * cell + (gridCols - 1) * gap;
|
|
||||||
const gridH = gridRows * cell + (gridRows - 1) * gap;
|
|
||||||
|
|
||||||
// 右上角位置
|
// 自适应网格布局:根据库位数量动态调整行列数,确保正方形背景
|
||||||
const outer = gap;
|
const totalSlots = assoc.length;
|
||||||
|
let gridCols: number;
|
||||||
|
let gridRows: number;
|
||||||
|
let isSingleSlot = false;
|
||||||
|
|
||||||
|
if (totalSlots === 1) {
|
||||||
|
gridCols = 1;
|
||||||
|
gridRows = 1; // 单个库位使用1x1布局,突出显示
|
||||||
|
isSingleSlot = true;
|
||||||
|
} else if (totalSlots <= 2) {
|
||||||
|
gridCols = 2;
|
||||||
|
gridRows = 2; // 2个库位时使用2x2正方形
|
||||||
|
} else if (totalSlots <= 4) {
|
||||||
|
gridCols = 2;
|
||||||
|
gridRows = 2; // 3-4个库位时使用2x2正方形
|
||||||
|
} else if (totalSlots <= 9) {
|
||||||
|
gridCols = 3;
|
||||||
|
gridRows = 3; // 5-9个库位时使用3x3正方形
|
||||||
|
} else if (totalSlots <= 16) {
|
||||||
|
gridCols = 4;
|
||||||
|
gridRows = 4; // 10-16个库位时使用4x4正方形
|
||||||
|
} else {
|
||||||
|
gridCols = 5;
|
||||||
|
gridRows = 5; // 17+个库位时使用5x5正方形
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据库位数量调整方框大小和间距
|
||||||
|
let finalCell = cell;
|
||||||
|
let finalGap = gap;
|
||||||
|
|
||||||
|
if (isSingleSlot) {
|
||||||
|
// 单个库位时方框更大
|
||||||
|
finalCell = Math.max(20, Math.min(40, base * 0.85));
|
||||||
|
} else {
|
||||||
|
// 多个库位时边距更大,确保库位之间有明显间距
|
||||||
|
finalGap = Math.max(6, Math.min(12, base * 0.12));
|
||||||
|
}
|
||||||
|
|
||||||
|
const gridW = gridCols * finalCell + (gridCols - 1) * finalGap;
|
||||||
|
const gridH = gridRows * finalCell + (gridRows - 1) * finalGap;
|
||||||
|
|
||||||
|
// 确保背景是正方形,取较大的尺寸
|
||||||
|
// 单个库位时使用更小的正方形来突出显示
|
||||||
|
const squareSize = isSingleSlot ? Math.max(gridW, gridH) * 0.8 : Math.max(gridW, gridH);
|
||||||
|
const gridW_final = squareSize;
|
||||||
|
const gridH_final = squareSize;
|
||||||
|
|
||||||
|
// 右上角位置 - 进一步减少外边距
|
||||||
|
const outer = Math.max(0.5, gap * 0.3);
|
||||||
const gx = x + w + outer;
|
const gx = x + w + outer;
|
||||||
const gy = y - gridH - outer;
|
const gy = y - gridH_final - outer;
|
||||||
|
|
||||||
ctx.save();
|
ctx.save();
|
||||||
// 背景底
|
// 背景底 - 透明背景,使用正方形尺寸
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
roundedRectPath(ctx, gx - gap * 0.5, gy - gap * 0.5, gridW + gap, gridH + gap, Math.min(6, cell * 0.4));
|
roundedRectPath(
|
||||||
ctx.fillStyle = '#00000022';
|
ctx,
|
||||||
|
gx - finalGap * 0.5,
|
||||||
|
gy - finalGap * 0.5,
|
||||||
|
gridW_final + finalGap,
|
||||||
|
gridH_final + finalGap,
|
||||||
|
Math.min(6, finalCell * 0.4)
|
||||||
|
);
|
||||||
|
ctx.fillStyle = 'transparent'; // 透明背景
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
|
ctx.strokeStyle = 'transparent'; // 透明边框
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
// 前 5 个格子 + 最后一格可能显示 +N
|
// 计算库位在正方形背景中的居中位置
|
||||||
|
const actualGridW = gridCols * finalCell + (gridCols - 1) * finalGap;
|
||||||
|
const actualGridH = gridRows * finalCell + (gridRows - 1) * finalGap;
|
||||||
|
const offsetX = (gridW_final - actualGridW) / 2;
|
||||||
|
const offsetY = (gridH_final - actualGridH) / 2;
|
||||||
|
|
||||||
|
// 显示所有库位格子,最后一格可能显示 +N
|
||||||
const slots = assoc.slice(0, gridCols * gridRows);
|
const slots = assoc.slice(0, gridCols * gridRows);
|
||||||
slots.forEach((layerName, i) => {
|
slots.forEach((layerName, i) => {
|
||||||
const r = Math.floor(i / gridCols);
|
const r = Math.floor(i / gridCols);
|
||||||
const c = i % gridCols;
|
const c = i % gridCols;
|
||||||
const cx = gx + c * (cell + gap);
|
// 库位位置应该是左上角坐标,不是中心点
|
||||||
const cy = gy + r * (cell + gap);
|
const cx = gx + offsetX + c * (finalCell + finalGap);
|
||||||
|
const cy = gy + offsetY + r * (finalCell + finalGap);
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
roundedRectPath(ctx, cx, cy, cell, cell, Math.min(4, cell * 0.3));
|
roundedRectPath(ctx, cx, cy, finalCell, finalCell, Math.min(4, finalCell * 0.3));
|
||||||
// 解析状态
|
// 解析状态
|
||||||
const state = opts?.stateResolver?.(pen.id!, layerName) ?? {};
|
const state = opts?.stateResolver?.(pen.id!, layerName) ?? {};
|
||||||
|
|
||||||
const occupied = !!state.occupied;
|
const occupied = !!state.occupied;
|
||||||
const locked = !!state.locked;
|
const locked = !!state.locked;
|
||||||
|
|
||||||
// 填充颜色:占用为红色,否则默认灰底
|
// 填充颜色:占用为红色,否则透明
|
||||||
ctx.fillStyle = occupied ? '#ff4d4f' : '#f5f5f5';
|
ctx.fillStyle = occupied ? '#ff4d4f' : 'transparent';
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
ctx.strokeStyle = '#999999';
|
ctx.strokeStyle = 'transparent';
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
// 若锁定,绘制锁图标角标(尽量不阻塞:未加载完成则跳过)
|
// 若锁定,绘制锁图标角标(尽量不阻塞:未加载完成则跳过)
|
||||||
if (locked && lockedIcon.complete && lockedIcon.naturalWidth > 0) {
|
if (locked && lockedIcon.complete && lockedIcon.naturalWidth > 0) {
|
||||||
const pad = Math.max(1, Math.floor(cell * 0.08));
|
const pad = Math.max(1, Math.floor(finalCell * 0.08));
|
||||||
// 图标尺寸不超过 cell 内可用空间,且有最小可见阈值
|
// 图标尺寸不超过 cell 内可用空间,且有最小可见阈值
|
||||||
const maxIcon = Math.max(0, cell - 2 * pad);
|
const maxIcon = Math.max(0, finalCell - 2 * pad);
|
||||||
const iconSize = Math.min(14, Math.floor(cell * 0.7), maxIcon);
|
const iconSize = Math.min(14, Math.floor(finalCell * 0.7), maxIcon);
|
||||||
|
|
||||||
// 将绘制范围裁剪到当前格子,确保不会溢出
|
// 将绘制范围裁剪到当前格子,确保不会溢出
|
||||||
ctx.save();
|
ctx.save();
|
||||||
@ -101,20 +162,21 @@ export function drawStorageGrid(
|
|||||||
const i = gridCols * gridRows - 1;
|
const i = gridCols * gridRows - 1;
|
||||||
const r = Math.floor(i / gridCols);
|
const r = Math.floor(i / gridCols);
|
||||||
const c = i % gridCols;
|
const c = i % gridCols;
|
||||||
const cx = gx + c * (cell + gap);
|
// 更多按钮位置也应该是左上角坐标
|
||||||
const cy = gy + r * (cell + gap);
|
const cx = gx + offsetX + c * (finalCell + finalGap);
|
||||||
|
const cy = gy + offsetY + r * (finalCell + finalGap);
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
roundedRectPath(ctx, cx, cy, cell, cell, Math.min(4, cell * 0.3));
|
roundedRectPath(ctx, cx, cy, finalCell, finalCell, Math.min(4, finalCell * 0.3));
|
||||||
ctx.fillStyle = '#e6f4ff';
|
ctx.fillStyle = '#e6f4ff';
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
ctx.strokeStyle = '#1677ff';
|
ctx.strokeStyle = '#1677ff';
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
ctx.fillStyle = '#1677ff';
|
ctx.fillStyle = '#1677ff';
|
||||||
ctx.font = `${Math.floor(cell * 0.6)}px/${1} ${fontFamily ?? 'sans-serif'}`;
|
ctx.font = `${Math.floor(finalCell * 0.6)}px/${1} ${fontFamily ?? 'sans-serif'}`;
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
ctx.textBaseline = 'middle';
|
ctx.textBaseline = 'middle';
|
||||||
ctx.fillText('+' + overflow, cx + cell / 2, cy + cell / 2);
|
ctx.fillText('+' + overflow, cx + finalCell / 2, cy + finalCell / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
@ -247,10 +309,10 @@ export function drawStorageBackground(ctx: CanvasRenderingContext2D, pen: MapPen
|
|||||||
ctx.arcTo(x, y, x + r, y, r);
|
ctx.arcTo(x, y, x + r, y, r);
|
||||||
ctx.closePath();
|
ctx.closePath();
|
||||||
|
|
||||||
// 填充半透明背景
|
// 填充半透明背景 - 调整为更偏白色
|
||||||
ctx.fillStyle = '#00000022';
|
ctx.fillStyle = 'transparent';
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
ctx.strokeStyle = '#999999';
|
ctx.strokeStyle = 'transparent';
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
@ -272,48 +334,100 @@ export function createStorageLocationPens(
|
|||||||
): MapPen[] {
|
): MapPen[] {
|
||||||
const { x = 0, y = 0, width: w = 0, height: h = 0 } = pointRect;
|
const { x = 0, y = 0, width: w = 0, height: h = 0 } = pointRect;
|
||||||
|
|
||||||
// 库位栅格参数
|
// 库位栅格参数 - 与drawStorageGrid保持一致
|
||||||
const base = Math.min(w, h);
|
const base = Math.min(w, h);
|
||||||
const cell = Math.max(12, Math.min(24, base * 0.60));
|
const cell = Math.max(16, Math.min(32, base * 0.70)); // 放大库位方框
|
||||||
const gap = Math.max(2, Math.min(6, base * 0.08));
|
const gap = Math.max(1, Math.min(2, base * 0.03)); // 进一步减少间距
|
||||||
const gridCols = 3;
|
|
||||||
const gridRows = 2;
|
|
||||||
const gridH = gridRows * cell + (gridRows - 1) * gap;
|
|
||||||
|
|
||||||
// 右上角位置 - getPointRect返回的是中心点坐标,需要转换为左上角坐标
|
// 自适应网格布局:根据库位数量动态调整行列数,确保正方形背景
|
||||||
const outer = gap;
|
const totalSlots = storageLocations.length;
|
||||||
|
let gridCols: number;
|
||||||
|
let gridRows: number;
|
||||||
|
let isSingleSlot = false;
|
||||||
|
|
||||||
|
if (totalSlots === 1) {
|
||||||
|
gridCols = 1;
|
||||||
|
gridRows = 1; // 单个库位使用1x1布局,突出显示
|
||||||
|
isSingleSlot = true;
|
||||||
|
} else if (totalSlots <= 2) {
|
||||||
|
gridCols = 2;
|
||||||
|
gridRows = 2; // 2个库位时使用2x2正方形
|
||||||
|
} else if (totalSlots <= 4) {
|
||||||
|
gridCols = 2;
|
||||||
|
gridRows = 2; // 3-4个库位时使用2x2正方形
|
||||||
|
} else if (totalSlots <= 9) {
|
||||||
|
gridCols = 3;
|
||||||
|
gridRows = 3; // 5-9个库位时使用3x3正方形
|
||||||
|
} else if (totalSlots <= 16) {
|
||||||
|
gridCols = 4;
|
||||||
|
gridRows = 4; // 10-16个库位时使用4x4正方形
|
||||||
|
} else {
|
||||||
|
gridCols = 5;
|
||||||
|
gridRows = 5; // 17+个库位时使用5x5正方形
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据库位数量调整方框大小和间距
|
||||||
|
let finalCell = cell;
|
||||||
|
let finalGap = gap;
|
||||||
|
|
||||||
|
if (isSingleSlot) {
|
||||||
|
// 单个库位时方框更大
|
||||||
|
finalCell = Math.max(20, Math.min(40, base * 0.85));
|
||||||
|
} else {
|
||||||
|
// 多个库位时边距更大,确保库位之间有明显间距
|
||||||
|
finalGap = Math.max(6, Math.min(12, base * 0.12));
|
||||||
|
}
|
||||||
|
|
||||||
|
const gridW = gridCols * finalCell + (gridCols - 1) * finalGap;
|
||||||
|
const gridH = gridRows * finalCell + (gridRows - 1) * finalGap;
|
||||||
|
|
||||||
|
// 确保背景是正方形,取较大的尺寸
|
||||||
|
// 单个库位时使用更小的正方形来突出显示
|
||||||
|
const squareSize = isSingleSlot ? Math.max(gridW, gridH) * 0.8 : Math.max(gridW, gridH);
|
||||||
|
const gridW_final = squareSize;
|
||||||
|
const gridH_final = squareSize;
|
||||||
|
|
||||||
|
// 右上角位置 - getPointRect返回的是中心点坐标,需要转换为左上角坐标,进一步减少外边距
|
||||||
|
const outer = Math.max(0.5, gap * 0.3);
|
||||||
const gx = x + w / 2 + outer;
|
const gx = x + w / 2 + outer;
|
||||||
const gy = y - h / 2 - gridH - outer;
|
const gy = y - h / 2 - gridH_final - outer;
|
||||||
|
|
||||||
const pens: MapPen[] = [];
|
const pens: MapPen[] = [];
|
||||||
|
|
||||||
// 创建库位区域背景矩形
|
// 创建库位区域背景矩形 - 使用正方形尺寸,透明背景
|
||||||
const backgroundPen: MapPen = {
|
const backgroundPen: MapPen = {
|
||||||
id: `storage-bg-${pointId}`,
|
id: `storage-bg-${pointId}`,
|
||||||
name: 'storage-background',
|
name: 'storage-background',
|
||||||
tags: ['storage-background', `point-${pointId}`],
|
tags: ['storage-background', `point-${pointId}`],
|
||||||
x: gx - gap * 0.5,
|
x: gx - finalGap * 0.5,
|
||||||
y: gy - gap * 0.5,
|
y: gy - finalGap * 0.5,
|
||||||
width: gridCols * cell + (gridCols - 1) * gap + gap,
|
width: gridW_final + finalGap,
|
||||||
height: gridRows * cell + (gridRows - 1) * gap + gap,
|
height: gridH_final + finalGap,
|
||||||
lineWidth: 1,
|
lineWidth: 1,
|
||||||
background: '#00000022',
|
background: 'transparent', // 透明背景
|
||||||
color: '#999999',
|
color: 'transparent', // 透明边框
|
||||||
locked: LockState.DisableEdit,
|
locked: LockState.DisableEdit,
|
||||||
visible: true,
|
visible: true,
|
||||||
borderRadius: Math.min(6, cell * 0.4),
|
borderRadius: Math.min(6, finalCell * 0.4),
|
||||||
};
|
};
|
||||||
pens.push(backgroundPen);
|
pens.push(backgroundPen);
|
||||||
|
|
||||||
|
// 计算库位在正方形背景中的居中位置
|
||||||
|
const actualGridW = gridCols * finalCell + (gridCols - 1) * finalGap;
|
||||||
|
const actualGridH = gridRows * finalCell + (gridRows - 1) * finalGap;
|
||||||
|
const offsetX = (gridW_final - actualGridW) / 2;
|
||||||
|
const offsetY = (gridH_final - actualGridH) / 2;
|
||||||
|
|
||||||
// 创建库位pen对象
|
// 创建库位pen对象
|
||||||
storageLocations.slice(0, gridCols * gridRows).forEach((locationName, i) => {
|
storageLocations.slice(0, gridCols * gridRows).forEach((locationName, i) => {
|
||||||
const r = Math.floor(i / gridCols);
|
const r = Math.floor(i / gridCols);
|
||||||
const c = i % gridCols;
|
const c = i % gridCols;
|
||||||
// 计算库位在背景矩形内的相对位置,然后加上背景矩形的起始位置
|
// 计算库位在背景矩形内的相对位置,然后加上背景矩形的起始位置和居中偏移
|
||||||
const relativeX = c * (cell + gap) + cell / 2;
|
// 库位位置应该是左上角坐标,不是中心点
|
||||||
const relativeY = r * (cell + gap) + cell / 2;
|
const relativeX = c * (finalCell + finalGap);
|
||||||
const cx = gx - gap * 0.5 + relativeX;
|
const relativeY = r * (finalCell + finalGap);
|
||||||
const cy = gy - gap * 0.5 + relativeY;
|
const cx = gx - finalGap * 0.5 + offsetX + relativeX;
|
||||||
|
const cy = gy - finalGap * 0.5 + offsetY + relativeY;
|
||||||
|
|
||||||
// 从storageStateMap获取库位状态
|
// 从storageStateMap获取库位状态
|
||||||
const inner = storageStateMap.get(pointId);
|
const inner = storageStateMap.get(pointId);
|
||||||
@ -327,14 +441,14 @@ export function createStorageLocationPens(
|
|||||||
tags: ['storage-location', `point-${pointId}`],
|
tags: ['storage-location', `point-${pointId}`],
|
||||||
x: cx,
|
x: cx,
|
||||||
y: cy,
|
y: cy,
|
||||||
width: cell,
|
width: finalCell,
|
||||||
height: cell,
|
height: finalCell,
|
||||||
lineWidth: 1,
|
lineWidth: 1,
|
||||||
background: occupied ? '#ff4d4f' : '#f5f5f5',
|
background: occupied ? '#ff4d4f' : 'transparent',
|
||||||
color: '#999999',
|
color: 'transparent',
|
||||||
locked: LockState.DisableEdit,
|
locked: LockState.DisableEdit,
|
||||||
visible: true,
|
visible: true,
|
||||||
borderRadius: Math.min(4, cell * 0.3),
|
borderRadius: Math.min(4, finalCell * 0.3),
|
||||||
storageLocation: {
|
storageLocation: {
|
||||||
pointId,
|
pointId,
|
||||||
locationName,
|
locationName,
|
||||||
@ -353,11 +467,12 @@ export function createStorageLocationPens(
|
|||||||
const i = gridCols * gridRows - 1;
|
const i = gridCols * gridRows - 1;
|
||||||
const r = Math.floor(i / gridCols);
|
const r = Math.floor(i / gridCols);
|
||||||
const c = i % gridCols;
|
const c = i % gridCols;
|
||||||
// 计算库位在背景矩形内的相对位置,然后加上背景矩形的起始位置
|
// 计算库位在背景矩形内的相对位置,然后加上背景矩形的起始位置和居中偏移
|
||||||
const relativeX = c * (cell + gap) + cell / 2;
|
// 更多按钮位置也应该是左上角坐标
|
||||||
const relativeY = r * (cell + gap) + cell / 2;
|
const relativeX = c * (finalCell + finalGap);
|
||||||
const cx = gx - gap * 0.5 + relativeX;
|
const relativeY = r * (finalCell + finalGap);
|
||||||
const cy = gy - gap * 0.5 + relativeY;
|
const cx = gx - finalGap * 0.5 + offsetX + relativeX;
|
||||||
|
const cy = gy - finalGap * 0.5 + offsetY + relativeY;
|
||||||
|
|
||||||
const morePen: MapPen = {
|
const morePen: MapPen = {
|
||||||
id: `storage-more-${pointId}`,
|
id: `storage-more-${pointId}`,
|
||||||
@ -365,15 +480,15 @@ export function createStorageLocationPens(
|
|||||||
tags: ['storage-more', `point-${pointId}`],
|
tags: ['storage-more', `point-${pointId}`],
|
||||||
x: cx,
|
x: cx,
|
||||||
y: cy,
|
y: cy,
|
||||||
width: cell,
|
width: finalCell,
|
||||||
height: cell,
|
height: finalCell,
|
||||||
lineWidth: 1,
|
lineWidth: 1,
|
||||||
background: '#e6f4ff',
|
background: '#e6f4ff',
|
||||||
color: '#1677ff',
|
color: '#1677ff',
|
||||||
locked: LockState.DisableEdit,
|
locked: LockState.DisableEdit,
|
||||||
visible: true,
|
visible: true,
|
||||||
text: `+${overflow}`,
|
text: `+${overflow}`,
|
||||||
fontSize: Math.floor(cell * 0.6),
|
fontSize: Math.floor(finalCell * 0.6),
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
textBaseline: 'middle',
|
textBaseline: 'middle',
|
||||||
storageLocation: {
|
storageLocation: {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user