优化预览与导出排版
This commit is contained in:
@@ -4,9 +4,12 @@ import {
|
||||
DEFAULT_EXPORT_RANGE,
|
||||
DEFAULT_CUT_LINE_CONFIG,
|
||||
buildPrintablePages,
|
||||
collectGridCutLinePositions,
|
||||
getCutLineChannel,
|
||||
type PrintableLabel,
|
||||
} from '../utils';
|
||||
import { CanvasLabelImage } from './CanvasLabelImage';
|
||||
import { CommitInput } from './CommitInput';
|
||||
import { FileDown, RefreshCcw } from 'lucide-react';
|
||||
|
||||
interface PageInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'> {
|
||||
@@ -15,36 +18,14 @@ interface PageInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement
|
||||
inputClassName?: string;
|
||||
}
|
||||
|
||||
export const PageInput: React.FC<PageInputProps> = ({ value, onCommit, inputClassName = '', ...props }) => {
|
||||
const [localVal, setLocalVal] = useState<string>(String(value));
|
||||
|
||||
useEffect(() => {
|
||||
setLocalVal(String(value));
|
||||
}, [value]);
|
||||
|
||||
const handleBlur = () => {
|
||||
if (localVal !== String(value)) {
|
||||
onCommit(localVal);
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Enter') {
|
||||
(e.target as HTMLInputElement).blur();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<input
|
||||
{...props}
|
||||
value={localVal}
|
||||
onChange={(e) => setLocalVal(e.target.value)}
|
||||
onBlur={handleBlur}
|
||||
onKeyDown={handleKeyDown}
|
||||
className={inputClassName}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export const PageInput: React.FC<PageInputProps> = ({
|
||||
value,
|
||||
onCommit,
|
||||
inputClassName = '',
|
||||
...props
|
||||
}) => (
|
||||
<CommitInput value={value} onCommit={onCommit} className={inputClassName} {...props} />
|
||||
);
|
||||
|
||||
interface LayoutStats {
|
||||
gridCols: number;
|
||||
@@ -176,7 +157,7 @@ export const PaperSettingsPanel: React.FC<PaperSettingsPanelProps> = ({
|
||||
|
||||
const applyPaper = (next: PaperConfig) => {
|
||||
setDraftPaper(next);
|
||||
if (isMobile) onChangePaper(next);
|
||||
onChangePaper(next);
|
||||
};
|
||||
|
||||
const handlePaperTypeChange = (type: PaperType) => {
|
||||
@@ -213,7 +194,7 @@ export const PaperSettingsPanel: React.FC<PaperSettingsPanelProps> = ({
|
||||
)}
|
||||
{isPs && (
|
||||
<p className="text-[10px] text-[#666] leading-relaxed">
|
||||
修改后点击面板外区域或切换焦点,预览将自动更新。
|
||||
排版参数修改后将自动更新预览。
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -465,6 +446,31 @@ export const LayoutPreview: React.FC<LayoutPreviewProps> = ({
|
||||
const currentPaperH = paper.orientation === 'portrait' ? paper.height : paper.width;
|
||||
const colGapPx = Math.max(0, paper.columnGap * previewScale);
|
||||
const rowGapPx = Math.max(0, paper.rowGap * previewScale);
|
||||
const labelWPx = template.width * previewScale;
|
||||
const labelHPx = template.height * previewScale;
|
||||
const cutStrokeWidth = Math.max(0.5, cutLine.width * previewScale);
|
||||
const cutHalfStroke = cutStrokeWidth / 2;
|
||||
const cutChannel = getCutLineChannel(paper.columnGap, paper.rowGap, cutLine);
|
||||
const cutChannelXPx = cutChannel.x * previewScale;
|
||||
const cutChannelYPx = cutChannel.y * previewScale;
|
||||
const cutDash =
|
||||
cutLine.style === 'dashed'
|
||||
? `${Math.max(2, cutStrokeWidth * 4)} ${Math.max(2, cutStrokeWidth * 4)}`
|
||||
: undefined;
|
||||
const pageCutLines = useMemo(
|
||||
() =>
|
||||
collectGridCutLinePositions(
|
||||
0,
|
||||
0,
|
||||
labelWPx,
|
||||
labelHPx,
|
||||
gridInfo.cols,
|
||||
gridInfo.rows,
|
||||
colGapPx,
|
||||
rowGapPx
|
||||
),
|
||||
[labelWPx, labelHPx, gridInfo.cols, gridInfo.rows, colGapPx, rowGapPx]
|
||||
);
|
||||
const visiblePages = showAllPages ? printablePages : printablePages.slice(0, MAX_PREVIEW_PAGES);
|
||||
const hasMorePages = printablePages.length > MAX_PREVIEW_PAGES;
|
||||
|
||||
@@ -587,15 +593,58 @@ export const LayoutPreview: React.FC<LayoutPreviewProps> = ({
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="grid bg-white"
|
||||
className="grid bg-white relative"
|
||||
style={{
|
||||
gridTemplateColumns: `repeat(${gridInfo.cols}, ${template.width * previewScale}px)`,
|
||||
gridAutoRows: `${template.height * previewScale}px`,
|
||||
gridTemplateColumns: `repeat(${gridInfo.cols}, ${labelWPx}px)`,
|
||||
gridAutoRows: `${labelHPx}px`,
|
||||
columnGap: `${colGapPx}px`,
|
||||
rowGap: `${rowGapPx}px`,
|
||||
width: 'max-content',
|
||||
}}
|
||||
>
|
||||
{cutLine.enabled && (
|
||||
<svg
|
||||
className="absolute pointer-events-none z-20"
|
||||
style={{
|
||||
left: -cutHalfStroke,
|
||||
top: -cutHalfStroke,
|
||||
width: pageCutLines.gridW + cutStrokeWidth,
|
||||
height: pageCutLines.gridH + cutStrokeWidth,
|
||||
overflow: 'visible',
|
||||
}}
|
||||
viewBox={`${-cutHalfStroke} ${-cutHalfStroke} ${pageCutLines.gridW + cutStrokeWidth} ${pageCutLines.gridH + cutStrokeWidth}`}
|
||||
aria-hidden
|
||||
>
|
||||
{pageCutLines.ys.map((y) => (
|
||||
<line
|
||||
key={`h-${y}`}
|
||||
x1={0}
|
||||
y1={y}
|
||||
x2={pageCutLines.gridW}
|
||||
y2={y}
|
||||
stroke={cutLine.color}
|
||||
strokeWidth={cutStrokeWidth}
|
||||
strokeDasharray={cutDash}
|
||||
strokeLinecap="butt"
|
||||
shapeRendering="crispEdges"
|
||||
/>
|
||||
))}
|
||||
{pageCutLines.xs.map((x) => (
|
||||
<line
|
||||
key={`v-${x}`}
|
||||
x1={x}
|
||||
y1={0}
|
||||
x2={x}
|
||||
y2={pageCutLines.gridH}
|
||||
stroke={cutLine.color}
|
||||
strokeWidth={cutStrokeWidth}
|
||||
strokeDasharray={cutDash}
|
||||
strokeLinecap="butt"
|
||||
shapeRendering="crispEdges"
|
||||
/>
|
||||
))}
|
||||
</svg>
|
||||
)}
|
||||
{pageRows.map((item, cellIdx) => {
|
||||
if (!item) {
|
||||
return (
|
||||
@@ -616,30 +665,26 @@ export const LayoutPreview: React.FC<LayoutPreviewProps> = ({
|
||||
<div
|
||||
key={cellIdx}
|
||||
onClick={() => onSelectRowIndex(item.sourceIndex)}
|
||||
className={`bg-white text-center relative overflow-hidden flex flex-col justify-center cursor-pointer box-border ${isActive ? 'ring-2 ring-inset ring-[#31a8ff] z-10' : ''
|
||||
className={`bg-white text-center relative flex flex-col justify-center cursor-pointer box-border ${isActive ? 'ring-2 ring-inset ring-[#31a8ff] z-10' : ''
|
||||
}`}
|
||||
style={{
|
||||
width: `${template.width * previewScale}px`,
|
||||
height: `${template.height * previewScale}px`,
|
||||
width: `${labelWPx}px`,
|
||||
height: `${labelHPx}px`,
|
||||
padding: `${cutChannelYPx}px ${cutChannelXPx}px`,
|
||||
boxSizing: 'border-box',
|
||||
}}
|
||||
>
|
||||
<CanvasLabelImage
|
||||
widthMm={template.width}
|
||||
heightMm={template.height}
|
||||
elements={template.elements}
|
||||
rowData={item.row}
|
||||
rowIndex={item.sourceIndex}
|
||||
showBorder={false}
|
||||
mode="output"
|
||||
/>
|
||||
{cutLine.enabled && (
|
||||
<div
|
||||
className="absolute inset-0 pointer-events-none box-border"
|
||||
style={{
|
||||
border: `${Math.max(1, cutLine.width * previewScale)}px ${cutLine.style} ${cutLine.color}`,
|
||||
}}
|
||||
<div className="w-full h-full overflow-hidden relative">
|
||||
<CanvasLabelImage
|
||||
widthMm={template.width}
|
||||
heightMm={template.height}
|
||||
elements={template.elements}
|
||||
rowData={item.row}
|
||||
rowIndex={item.sourceIndex}
|
||||
showBorder={false}
|
||||
mode="output"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user