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