优化
This commit is contained in:
@@ -3,6 +3,12 @@ import { TemplateElement } from '../types';
|
|||||||
import { mmToPx } from '../utils';
|
import { mmToPx } from '../utils';
|
||||||
import { renderLabelToDataUrl } from '../labelRenderer';
|
import { renderLabelToDataUrl } from '../labelRenderer';
|
||||||
|
|
||||||
|
/** 离屏单元素渲染时预留描边空间,避免画布边缘裁切表格线 */
|
||||||
|
function getFloatPreviewPaddingMm(element: TemplateElement): number {
|
||||||
|
if (element.type !== 'table') return 0;
|
||||||
|
return (element.tableBorderWidth ?? 0.2) / 2 + 0.1;
|
||||||
|
}
|
||||||
|
|
||||||
interface ElementFloatPreviewProps {
|
interface ElementFloatPreviewProps {
|
||||||
element: TemplateElement;
|
element: TemplateElement;
|
||||||
renderScale: number;
|
renderScale: number;
|
||||||
@@ -17,10 +23,11 @@ export async function renderElementFloatPreview(
|
|||||||
renderScale: number,
|
renderScale: number,
|
||||||
variableDefaults?: Record<string, string> | null
|
variableDefaults?: Record<string, string> | null
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
|
const padMm = getFloatPreviewPaddingMm(element);
|
||||||
return renderLabelToDataUrl({
|
return renderLabelToDataUrl({
|
||||||
widthMm: element.width,
|
widthMm: element.width + padMm * 2,
|
||||||
heightMm: element.height,
|
heightMm: element.height + padMm * 2,
|
||||||
elements: [{ ...element, x: 0, y: 0 }],
|
elements: [{ ...element, x: padMm, y: padMm }],
|
||||||
rowData: null,
|
rowData: null,
|
||||||
rowIndex: 0,
|
rowIndex: 0,
|
||||||
showBorder: false,
|
showBorder: false,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { LabelTemplate, TemplateElement } from '../types';
|
import { LabelTemplate, TemplateElement } from '../types';
|
||||||
import { mmToPx, MM_TO_PX, shouldRenderElement } from '../utils';
|
import { mmToPx, MM_TO_PX, shouldRenderElement, centerElementOnLabel } from '../utils';
|
||||||
import type { ElementClipboardActions } from '../hooks/useElementClipboard';
|
import type { ElementClipboardActions } from '../hooks/useElementClipboard';
|
||||||
import {
|
import {
|
||||||
createDefaultTableElement,
|
createDefaultTableElement,
|
||||||
@@ -327,8 +327,7 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
|||||||
id,
|
id,
|
||||||
type: 'image',
|
type: 'image',
|
||||||
name: `图片 ${n}`,
|
name: `图片 ${n}`,
|
||||||
x: 5,
|
...centerElementOnLabel(template.width, template.height, 30, 30),
|
||||||
y: 5,
|
|
||||||
width: 30,
|
width: 30,
|
||||||
height: 30,
|
height: 30,
|
||||||
content: src,
|
content: src,
|
||||||
@@ -421,6 +420,14 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const centered = centerElementOnLabel(
|
||||||
|
template.width,
|
||||||
|
template.height,
|
||||||
|
newElement.width,
|
||||||
|
newElement.height
|
||||||
|
);
|
||||||
|
newElement = { ...newElement, ...centered };
|
||||||
|
|
||||||
onChange({
|
onChange({
|
||||||
...template,
|
...template,
|
||||||
elements: [...template.elements, newElement],
|
elements: [...template.elements, newElement],
|
||||||
|
|||||||
@@ -997,7 +997,9 @@ export const PropSidebar: React.FC<PropSidebarProps> = ({
|
|||||||
|
|
||||||
const renderPropertiesPanelBody = () => (
|
const renderPropertiesPanelBody = () => (
|
||||||
<>
|
<>
|
||||||
|
{!selectedElem && (
|
||||||
<div className="pb-3 border-b border-[#3a3a3a]">{renderPreviewReferenceBackgroundFields()}</div>
|
<div className="pb-3 border-b border-[#3a3a3a]">{renderPreviewReferenceBackgroundFields()}</div>
|
||||||
|
)}
|
||||||
{/* Label Canvas Dimensions */}
|
{/* Label Canvas Dimensions */}
|
||||||
{!selectedElem && (
|
{!selectedElem && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
@@ -1684,13 +1686,15 @@ export const PropSidebar: React.FC<PropSidebarProps> = ({
|
|||||||
value={selectedElem.width}
|
value={selectedElem.width}
|
||||||
onCommit={(val) => {
|
onCommit={(val) => {
|
||||||
const wVal = Math.max(2, Number(val) || 2);
|
const wVal = Math.max(2, Number(val) || 2);
|
||||||
let updated = { ...selectedElem, width: wVal };
|
|
||||||
if (selectedElem.type === 'qrcode') {
|
if (selectedElem.type === 'qrcode') {
|
||||||
updated.height = wVal;
|
handleElemChange({ ...selectedElem, width: wVal, height: wVal });
|
||||||
} else if (selectedElem.type === 'table') {
|
} else if (selectedElem.type === 'table') {
|
||||||
updated = scaleTableElementSize(updated, wVal, updated.height);
|
handleElemChange(
|
||||||
|
scaleTableElementSize(selectedElem, wVal, selectedElem.height)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
handleElemChange({ ...selectedElem, width: wVal });
|
||||||
}
|
}
|
||||||
handleElemChange(updated);
|
|
||||||
}}
|
}}
|
||||||
className="ps-field font-mono"
|
className="ps-field font-mono"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import {
|
|||||||
updateTableCells,
|
updateTableCells,
|
||||||
updateTableColWidth,
|
updateTableColWidth,
|
||||||
updateTableRowHeight,
|
updateTableRowHeight,
|
||||||
|
equalizeTableColWidths,
|
||||||
|
equalizeTableRowHeights,
|
||||||
getTableColWidths,
|
getTableColWidths,
|
||||||
getTableRowHeights,
|
getTableRowHeights,
|
||||||
getCellRawContent,
|
getCellRawContent,
|
||||||
@@ -90,7 +92,17 @@ export const TablePropertyFields: React.FC<TablePropertyFieldsProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
|
<div className="flex items-center justify-between gap-2">
|
||||||
<span className="text-[10px] font-semibold text-[#aaa]">行高 (mm)</span>
|
<span className="text-[10px] font-semibold text-[#aaa]">行高 (mm)</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => onChange(equalizeTableRowHeights(elem))}
|
||||||
|
className="text-[9px] text-[#31a8ff] hover:underline cursor-pointer shrink-0"
|
||||||
|
title="将各行高度恢复为相等"
|
||||||
|
>
|
||||||
|
等分行高
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div className="grid grid-cols-3 gap-1.5">
|
<div className="grid grid-cols-3 gap-1.5">
|
||||||
{rowHeights.map((h, i) => (
|
{rowHeights.map((h, i) => (
|
||||||
<div key={`rh-${i}`}>
|
<div key={`rh-${i}`}>
|
||||||
@@ -111,7 +123,17 @@ export const TablePropertyFields: React.FC<TablePropertyFieldsProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
|
<div className="flex items-center justify-between gap-2">
|
||||||
<span className="text-[10px] font-semibold text-[#aaa]">列宽 (mm)</span>
|
<span className="text-[10px] font-semibold text-[#aaa]">列宽 (mm)</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => onChange(equalizeTableColWidths(elem))}
|
||||||
|
className="text-[9px] text-[#31a8ff] hover:underline cursor-pointer shrink-0"
|
||||||
|
title="将各列宽度恢复为相等"
|
||||||
|
>
|
||||||
|
等分列宽
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div className="grid grid-cols-3 gap-1.5">
|
<div className="grid grid-cols-3 gap-1.5">
|
||||||
{colWidths.map((w, i) => (
|
{colWidths.map((w, i) => (
|
||||||
<div key={`cw-${i}`}>
|
<div key={`cw-${i}`}>
|
||||||
@@ -287,7 +309,9 @@ export const TableCellContentFields: React.FC<TableCellContentFieldsProps> = ({
|
|||||||
export function scaleTableElementSize(
|
export function scaleTableElementSize(
|
||||||
elem: TemplateElement,
|
elem: TemplateElement,
|
||||||
newWidth: number,
|
newWidth: number,
|
||||||
newHeight: number
|
newHeight: number,
|
||||||
|
startWidth = elem.width,
|
||||||
|
startHeight = elem.height
|
||||||
): TemplateElement {
|
): TemplateElement {
|
||||||
if (elem.type !== 'table') {
|
if (elem.type !== 'table') {
|
||||||
return { ...elem, width: newWidth, height: newHeight };
|
return { ...elem, width: newWidth, height: newHeight };
|
||||||
@@ -296,8 +320,8 @@ export function scaleTableElementSize(
|
|||||||
elem,
|
elem,
|
||||||
newWidth,
|
newWidth,
|
||||||
newHeight,
|
newHeight,
|
||||||
elem.width,
|
startWidth,
|
||||||
elem.height,
|
startHeight,
|
||||||
getTableRowHeights(elem),
|
getTableRowHeights(elem),
|
||||||
getTableColWidths(elem)
|
getTableColWidths(elem)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -422,12 +422,17 @@ function drawTableGridLines(
|
|||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
|
|
||||||
const inset = borderW / 2;
|
const inset = borderW / 2;
|
||||||
ctx.rect(
|
const right = left + width;
|
||||||
alignStrokeCoord(left + inset),
|
const bottom = top + height;
|
||||||
alignStrokeCoord(top + inset),
|
// 外框:描边完全落在网格区域内,避免 clip / 画布边缘裁切右、下边
|
||||||
Math.max(0, width - borderW),
|
ctx.moveTo(alignStrokeCoord(left + inset), alignStrokeCoord(top + inset));
|
||||||
Math.max(0, height - borderW)
|
ctx.lineTo(alignStrokeCoord(right - inset), alignStrokeCoord(top + inset));
|
||||||
);
|
ctx.moveTo(alignStrokeCoord(right - inset), alignStrokeCoord(top + inset));
|
||||||
|
ctx.lineTo(alignStrokeCoord(right - inset), alignStrokeCoord(bottom - inset));
|
||||||
|
ctx.moveTo(alignStrokeCoord(right - inset), alignStrokeCoord(bottom - inset));
|
||||||
|
ctx.lineTo(alignStrokeCoord(left + inset), alignStrokeCoord(bottom - inset));
|
||||||
|
ctx.moveTo(alignStrokeCoord(left + inset), alignStrokeCoord(bottom - inset));
|
||||||
|
ctx.lineTo(alignStrokeCoord(left + inset), alignStrokeCoord(top + inset));
|
||||||
|
|
||||||
for (let r = 1; r < rows; r++) {
|
for (let r = 1; r < rows; r++) {
|
||||||
const y = alignStrokeCoord(rowBounds[r]);
|
const y = alignStrokeCoord(rowBounds[r]);
|
||||||
@@ -532,8 +537,8 @@ async function drawTableElement(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
drawTableGridLines(ctx, elem, ex, ey, scale);
|
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
|
drawTableGridLines(ctx, elem, ex, ey, scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawImagePlaceholder(
|
function drawImagePlaceholder(
|
||||||
|
|||||||
@@ -56,6 +56,36 @@ export function getTableColWidths(elem: TemplateElement): number[] {
|
|||||||
return Array.from({ length: cols }, () => even);
|
return Array.from({ length: cols }, () => even);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 调整末项(必要时向前)使轨道总和精确等于 targetTotal */
|
||||||
|
function absorbTrackSumDiff(tracks: number[], targetTotal: number, min = 0.5): number[] {
|
||||||
|
const result = tracks.map((t) => round1(Math.max(min, t)));
|
||||||
|
let diff = round1(targetTotal - result.reduce((a, b) => a + b, 0));
|
||||||
|
for (let i = result.length - 1; i >= 0 && diff !== 0; i--) {
|
||||||
|
const adjusted = round1(Math.max(min, result[i] + diff));
|
||||||
|
const applied = round1(adjusted - result[i]);
|
||||||
|
result[i] = adjusted;
|
||||||
|
diff = round1(diff - applied);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 按比例缩放轨道,严格使总和等于 targetTotal(不放大目标总量) */
|
||||||
|
function scaleTracksToExactTotal(tracks: number[], targetTotal: number, min = 0.5): number[] {
|
||||||
|
if (tracks.length === 0) return [];
|
||||||
|
const count = tracks.length;
|
||||||
|
const floor = count * min;
|
||||||
|
if (targetTotal <= floor + 0.01) {
|
||||||
|
return absorbTrackSumDiff(Array.from({ length: count }, () => min), targetTotal, min);
|
||||||
|
}
|
||||||
|
const current = tracks.reduce((a, b) => a + b, 0);
|
||||||
|
if (current <= 0) {
|
||||||
|
const even = round1(targetTotal / count);
|
||||||
|
return absorbTrackSumDiff(Array.from({ length: count }, () => even), targetTotal, min);
|
||||||
|
}
|
||||||
|
const scaled = tracks.map((t) => round1(Math.max(min, (t / current) * targetTotal)));
|
||||||
|
return absorbTrackSumDiff(scaled, targetTotal, min);
|
||||||
|
}
|
||||||
|
|
||||||
/** 将轨道尺寸总和调整为 targetTotal(保持相对比例,末项吸收取整误差) */
|
/** 将轨道尺寸总和调整为 targetTotal(保持相对比例,末项吸收取整误差) */
|
||||||
function scaleTracksToTotal(tracks: number[], targetTotal: number, min = 0.5): number[] {
|
function scaleTracksToTotal(tracks: number[], targetTotal: number, min = 0.5): number[] {
|
||||||
if (tracks.length === 0) return [];
|
if (tracks.length === 0) return [];
|
||||||
@@ -73,6 +103,48 @@ function scaleTracksToTotal(tracks: number[], targetTotal: number, min = 0.5): n
|
|||||||
return scaled;
|
return scaled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 调整单条轨道尺寸,保持轨道总和不变;优先由 index 之后的轨道吸收差值 */
|
||||||
|
function resizeTrackKeepingTotal(
|
||||||
|
tracks: number[],
|
||||||
|
index: number,
|
||||||
|
sizeMm: number,
|
||||||
|
min = 0.5
|
||||||
|
): number[] {
|
||||||
|
if (index < 0 || index >= tracks.length) return tracks;
|
||||||
|
const total = round1(tracks.reduce((a, b) => a + b, 0));
|
||||||
|
if (tracks.length === 1) {
|
||||||
|
return [round1(Math.max(min, Math.min(total, sizeMm)))];
|
||||||
|
}
|
||||||
|
|
||||||
|
const beforeSum = round1(tracks.slice(0, index).reduce((a, b) => a + b, 0));
|
||||||
|
const afterCount = tracks.length - index - 1;
|
||||||
|
const beforeCount = index;
|
||||||
|
|
||||||
|
const maxForIndex =
|
||||||
|
afterCount > 0
|
||||||
|
? round1(total - beforeSum - afterCount * min)
|
||||||
|
: round1(total - beforeCount * min);
|
||||||
|
const newSize = round1(Math.max(min, Math.min(maxForIndex, sizeMm)));
|
||||||
|
const result = [...tracks];
|
||||||
|
result[index] = newSize;
|
||||||
|
|
||||||
|
if (afterCount > 0) {
|
||||||
|
const afterRemaining = round1(total - beforeSum - newSize);
|
||||||
|
const scaledAfter = scaleTracksToExactTotal(tracks.slice(index + 1), afterRemaining, min);
|
||||||
|
for (let i = 0; i < scaledAfter.length; i++) {
|
||||||
|
result[index + 1 + i] = scaledAfter[i];
|
||||||
|
}
|
||||||
|
} else if (beforeCount > 0) {
|
||||||
|
const beforeRemaining = round1(total - newSize);
|
||||||
|
const scaledBefore = scaleTracksToExactTotal(tracks.slice(0, index), beforeRemaining, min);
|
||||||
|
for (let i = 0; i < scaledBefore.length; i++) {
|
||||||
|
result[i] = scaledBefore[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return absorbTrackSumDiff(result, total, min);
|
||||||
|
}
|
||||||
|
|
||||||
/** 将 inner 区域均分给 count 条轨道(末项吸收取整误差) */
|
/** 将 inner 区域均分给 count 条轨道(末项吸收取整误差) */
|
||||||
function distributeTracks(total: number, count: number, min = 0.5): number[] {
|
function distributeTracks(total: number, count: number, min = 0.5): number[] {
|
||||||
if (count <= 0) return [];
|
if (count <= 0) return [];
|
||||||
@@ -132,8 +204,14 @@ export function scaleTableTracks(
|
|||||||
const scaleW = newInnerW / startInnerW;
|
const scaleW = newInnerW / startInnerW;
|
||||||
const scaleH = newInnerH / startInnerH;
|
const scaleH = newInnerH / startInnerH;
|
||||||
|
|
||||||
const tableRowHeights = startRowHeights.map((h) => round1(Math.max(0.5, h * scaleH)));
|
const tableRowHeights = scaleTracksToExactTotal(
|
||||||
const tableColWidths = startColWidths.map((w) => round1(Math.max(0.5, w * scaleW)));
|
startRowHeights.map((h) => h * scaleH),
|
||||||
|
newInnerH
|
||||||
|
);
|
||||||
|
const tableColWidths = scaleTracksToExactTotal(
|
||||||
|
startColWidths.map((w) => w * scaleW),
|
||||||
|
newInnerW
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...elem,
|
...elem,
|
||||||
@@ -456,14 +534,27 @@ export function updateTableRowHeight(
|
|||||||
rowIndex: number,
|
rowIndex: number,
|
||||||
heightMm: number
|
heightMm: number
|
||||||
): TemplateElement {
|
): TemplateElement {
|
||||||
const rowHeights = [...getTableRowHeights(elem)];
|
if (rowIndex < 0 || rowIndex >= getTableRows(elem)) return elem;
|
||||||
if (rowIndex < 0 || rowIndex >= rowHeights.length) return elem;
|
|
||||||
rowHeights[rowIndex] = Math.max(0.5, round1(heightMm));
|
|
||||||
const pad = resolvePaddingMm(elem);
|
const pad = resolvePaddingMm(elem);
|
||||||
|
const innerH = getTableInnerSizeMm(elem).height;
|
||||||
|
const rowHeights = scaleTracksToExactTotal(getTableRowHeights(elem), innerH);
|
||||||
|
const isLast = rowIndex === rowHeights.length - 1;
|
||||||
|
|
||||||
|
if (isLast) {
|
||||||
|
const nextHeights = [...rowHeights];
|
||||||
|
nextHeights[rowIndex] = round1(Math.max(0.5, heightMm));
|
||||||
|
const newInnerH = round1(nextHeights.reduce((a, b) => a + b, 0));
|
||||||
return {
|
return {
|
||||||
...elem,
|
...elem,
|
||||||
tableRowHeights: rowHeights,
|
tableRowHeights: nextHeights,
|
||||||
height: round1(rowHeights.reduce((a, b) => a + b, 0) + pad.top + pad.bottom),
|
height: round1(newInnerH + pad.top + pad.bottom),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...elem,
|
||||||
|
tableRowHeights: resizeTrackKeepingTotal(rowHeights, rowIndex, heightMm),
|
||||||
|
height: elem.height,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -472,14 +563,55 @@ export function updateTableColWidth(
|
|||||||
colIndex: number,
|
colIndex: number,
|
||||||
widthMm: number
|
widthMm: number
|
||||||
): TemplateElement {
|
): TemplateElement {
|
||||||
const colWidths = [...getTableColWidths(elem)];
|
if (colIndex < 0 || colIndex >= getTableCols(elem)) return elem;
|
||||||
if (colIndex < 0 || colIndex >= colWidths.length) return elem;
|
|
||||||
colWidths[colIndex] = Math.max(0.5, round1(widthMm));
|
|
||||||
const pad = resolvePaddingMm(elem);
|
const pad = resolvePaddingMm(elem);
|
||||||
|
const innerW = getTableInnerSizeMm(elem).width;
|
||||||
|
const colWidths = scaleTracksToExactTotal(getTableColWidths(elem), innerW);
|
||||||
|
const isLast = colIndex === colWidths.length - 1;
|
||||||
|
|
||||||
|
if (isLast) {
|
||||||
|
const nextWidths = [...colWidths];
|
||||||
|
nextWidths[colIndex] = round1(Math.max(0.5, widthMm));
|
||||||
|
const newInnerW = round1(nextWidths.reduce((a, b) => a + b, 0));
|
||||||
return {
|
return {
|
||||||
...elem,
|
...elem,
|
||||||
tableColWidths: colWidths,
|
tableColWidths: nextWidths,
|
||||||
width: round1(colWidths.reduce((a, b) => a + b, 0) + pad.left + pad.right),
|
width: round1(newInnerW + pad.left + pad.right),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...elem,
|
||||||
|
tableColWidths: resizeTrackKeepingTotal(colWidths, colIndex, widthMm),
|
||||||
|
width: elem.width,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 在现有元素高度内均分行高 */
|
||||||
|
export function equalizeTableRowHeights(elem: TemplateElement): TemplateElement {
|
||||||
|
const rows = getTableRows(elem);
|
||||||
|
const innerH = getTableInnerSizeMm(elem).height;
|
||||||
|
return {
|
||||||
|
...elem,
|
||||||
|
tableRowHeights: scaleTracksToExactTotal(
|
||||||
|
Array.from({ length: rows }, () => innerH / rows),
|
||||||
|
innerH
|
||||||
|
),
|
||||||
|
height: elem.height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 在现有元素宽度内均分列宽 */
|
||||||
|
export function equalizeTableColWidths(elem: TemplateElement): TemplateElement {
|
||||||
|
const cols = getTableCols(elem);
|
||||||
|
const innerW = getTableInnerSizeMm(elem).width;
|
||||||
|
return {
|
||||||
|
...elem,
|
||||||
|
tableColWidths: scaleTracksToExactTotal(
|
||||||
|
Array.from({ length: cols }, () => innerW / cols),
|
||||||
|
innerW
|
||||||
|
),
|
||||||
|
width: elem.width,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
15
src/utils.ts
15
src/utils.ts
@@ -30,6 +30,21 @@ export const VARIABLE_MAPPING_ROW_INDEX_LABEL = '数据序号';
|
|||||||
/** 粘贴副本时相对原位置的偏移 (mm) */
|
/** 粘贴副本时相对原位置的偏移 (mm) */
|
||||||
export const PASTE_ELEMENT_OFFSET_MM = 2;
|
export const PASTE_ELEMENT_OFFSET_MM = 2;
|
||||||
|
|
||||||
|
/** 在标签画布上居中放置元素(mm,保留 1 位小数) */
|
||||||
|
export function centerElementOnLabel(
|
||||||
|
labelWidth: number,
|
||||||
|
labelHeight: number,
|
||||||
|
elementWidth: number,
|
||||||
|
elementHeight: number
|
||||||
|
): { x: number; y: number } {
|
||||||
|
const x = Math.round(((labelWidth - elementWidth) / 2) * 10) / 10;
|
||||||
|
const y = Math.round(((labelHeight - elementHeight) / 2) * 10) / 10;
|
||||||
|
return {
|
||||||
|
x: Math.max(0, x),
|
||||||
|
y: Math.max(0, y),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/** 深拷贝元素并生成新 id,用于复制/粘贴 */
|
/** 深拷贝元素并生成新 id,用于复制/粘贴 */
|
||||||
export function cloneTemplateElementsForPaste(
|
export function cloneTemplateElementsForPaste(
|
||||||
elements: TemplateElement[],
|
elements: TemplateElement[],
|
||||||
|
|||||||
Reference in New Issue
Block a user