127 lines
3.2 KiB
TypeScript
127 lines
3.2 KiB
TypeScript
import React, { useState, useEffect, useMemo } from 'react';
|
||
import { TemplateElement } from '../types';
|
||
import { ContentResolveMode } from '../utils';
|
||
import { renderLabelToDataUrl } from '../labelRenderer';
|
||
|
||
interface CanvasLabelImageProps {
|
||
widthMm: number;
|
||
heightMm: number;
|
||
elements: TemplateElement[];
|
||
rowData: any;
|
||
rowIndex: number;
|
||
showBorder?: boolean;
|
||
transparentBackground?: boolean;
|
||
variableDefaults?: Record<string, string> | null;
|
||
/** design=设计预览(空变量显示占位);output=排版/打印(空值隐藏图形元素) */
|
||
mode?: ContentResolveMode;
|
||
referenceBackground?: string;
|
||
referenceBackgroundOpacity?: number;
|
||
referenceBackgroundFit?: 'contain' | 'cover' | 'fill';
|
||
/** 渲染倍率,设计画布应传 MM_TO_PX * zoom 与显示尺寸 1:1 对齐 */
|
||
renderScale?: number;
|
||
}
|
||
|
||
export const CanvasLabelImage: React.FC<CanvasLabelImageProps> = ({
|
||
widthMm,
|
||
heightMm,
|
||
elements,
|
||
rowData,
|
||
rowIndex,
|
||
showBorder = false,
|
||
transparentBackground = false,
|
||
variableDefaults = null,
|
||
mode = 'design' as ContentResolveMode,
|
||
referenceBackground,
|
||
referenceBackgroundOpacity,
|
||
referenceBackgroundFit,
|
||
renderScale = 16,
|
||
}) => {
|
||
const [dataUrl, setDataUrl] = useState<string>('');
|
||
const [initialLoading, setInitialLoading] = useState<boolean>(true);
|
||
|
||
const elementsKey = useMemo(() => JSON.stringify(elements), [elements]);
|
||
const rowDataKey = useMemo(() => JSON.stringify(rowData), [rowData]);
|
||
const variableDefaultsKey = useMemo(
|
||
() => JSON.stringify(variableDefaults),
|
||
[variableDefaults]
|
||
);
|
||
|
||
useEffect(() => {
|
||
let active = true;
|
||
|
||
renderLabelToDataUrl({
|
||
widthMm,
|
||
heightMm,
|
||
elements,
|
||
rowData,
|
||
rowIndex,
|
||
showBorder,
|
||
transparentBackground,
|
||
variableDefaults,
|
||
mode,
|
||
scale: renderScale,
|
||
referenceBackground,
|
||
referenceBackgroundOpacity,
|
||
referenceBackgroundFit,
|
||
})
|
||
.then((url) => {
|
||
if (!active) return;
|
||
const img = new Image();
|
||
img.onload = () => {
|
||
if (active) {
|
||
setDataUrl(url);
|
||
setInitialLoading(false);
|
||
}
|
||
};
|
||
img.onerror = () => {
|
||
if (active) setInitialLoading(false);
|
||
};
|
||
img.src = url;
|
||
})
|
||
.catch((err) => {
|
||
console.error('Label graphics rendering error:', err);
|
||
if (active) setInitialLoading(false);
|
||
});
|
||
|
||
return () => {
|
||
active = false;
|
||
};
|
||
}, [
|
||
widthMm,
|
||
heightMm,
|
||
elementsKey,
|
||
rowDataKey,
|
||
variableDefaultsKey,
|
||
rowIndex,
|
||
showBorder,
|
||
transparentBackground,
|
||
mode,
|
||
renderScale,
|
||
referenceBackground,
|
||
referenceBackgroundOpacity,
|
||
referenceBackgroundFit,
|
||
]);
|
||
|
||
if (initialLoading && !dataUrl) {
|
||
return (
|
||
<div
|
||
className="flex items-center justify-center animate-pulse text-gray-400 font-medium text-[10px] bg-transparent"
|
||
style={{ width: '100%', height: '100%', minHeight: '30px' }}
|
||
>
|
||
<span>绘制中</span>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<img
|
||
src={dataUrl}
|
||
alt="Label Preview"
|
||
className="block w-full h-full pointer-events-none select-none"
|
||
style={{
|
||
imageRendering: 'auto',
|
||
}}
|
||
/>
|
||
);
|
||
};
|