From 3a50e7e33bd13cd9a6e103de33a6a368c03be85a Mon Sep 17 00:00:00 2001 From: iqudoo Date: Tue, 16 Jun 2026 23:41:27 +0800 Subject: [PATCH] fix --- src/App.tsx | 40 +++++++++++++++---------- src/components/ElementMaskDialog.tsx | 4 +-- src/components/PropSidebar.tsx | 19 +++++++----- src/components/VisibilityRuleDialog.tsx | 2 +- src/elementMask.ts | 3 +- src/index.css | 25 ++++++++++++++++ src/labelRenderer.ts | 23 +++++++------- 7 files changed, 78 insertions(+), 38 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 1d19a7d..0b58f27 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -169,8 +169,8 @@ export default function App() { // Modal / Inline input values for template creations const [showAddTmplForm, setShowAddTmplForm] = useState(false); const [newTmplName, setNewTmplName] = useState(''); - const [newTmplW, setNewTmplW] = useState(75); - const [newTmplH, setNewTmplH] = useState(45); + const [newTmplW, setNewTmplW] = useState('75'); + const [newTmplH, setNewTmplH] = useState('45'); const [editingTemplateId, setEditingTemplateId] = useState(null); const [editTmplName, setEditTmplName] = useState(''); const [templateMenuId, setTemplateMenuId] = useState(null); @@ -405,12 +405,22 @@ export default function App() { await showAlert('请先输入合法的模板名', { variant: 'warning' }); return; } + const width = Number(newTmplW); + const height = Number(newTmplH); + if (!newTmplW.trim() || Number.isNaN(width) || width < 10 || width > 150) { + await showAlert('请输入合法的标签宽度(10–150 mm)', { variant: 'warning' }); + return; + } + if (!newTmplH.trim() || Number.isNaN(height) || height < 10 || height > 150) { + await showAlert('请输入合法的标签高度(10–150 mm)', { variant: 'warning' }); + return; + } const newId = `custom_tmpl_${Date.now()}`; const newTmpl: LabelTemplate = { id: newId, name: `${newTmplName}`, - width: Math.max(10, Number(newTmplW) || 70), - height: Math.max(10, Number(newTmplH) || 40), + width, + height, elements: [], paper: DEFAULT_PAPER_CONFIG, cutLine: DEFAULT_CUT_LINE_CONFIG, @@ -427,8 +437,8 @@ export default function App() { const closeAddTemplateDialog = () => { setShowAddTmplForm(false); setNewTmplName(''); - setNewTmplW(75); - setNewTmplH(45); + setNewTmplW('75'); + setNewTmplH('45'); }; const openEditTemplateDialog = (tmpl: LabelTemplate) => { @@ -1621,7 +1631,6 @@ export default function App() { {editingTemplateId && (
setNewTmplW(Number(e.target.value))} + onChange={(e) => setNewTmplW(e.target.value)} className="w-full px-3 py-2 bg-gray-50 border border-gray-200 rounded-lg text-sm font-mono focus:border-indigo-400 focus:outline-none" />
@@ -1730,12 +1738,12 @@ export default function App() { 标签高度 (mm) setNewTmplH(Number(e.target.value))} + onChange={(e) => setNewTmplH(e.target.value)} className="w-full px-3 py-2 bg-gray-50 border border-gray-200 rounded-lg text-sm font-mono focus:border-indigo-400 focus:outline-none" />
diff --git a/src/components/ElementMaskDialog.tsx b/src/components/ElementMaskDialog.tsx index 826e40b..62043dd 100644 --- a/src/components/ElementMaskDialog.tsx +++ b/src/components/ElementMaskDialog.tsx @@ -110,7 +110,7 @@ export const ElementMaskDialog: React.FC = ({ }; return createPortal( -
+
e.stopPropagation()} @@ -129,7 +129,7 @@ export const ElementMaskDialog: React.FC = ({

- 蒙版仅作用于当前图层(背景与内容);先在离屏画布合成,再按蒙版裁剪后绘制到画布。边框不受蒙版影响。 + 蒙版作用于当前图层(背景、内容与边框);先在离屏画布合成,再按蒙版裁剪后绘制到画布。

diff --git a/src/components/PropSidebar.tsx b/src/components/PropSidebar.tsx index f4c29d9..e49404e 100644 --- a/src/components/PropSidebar.tsx +++ b/src/components/PropSidebar.tsx @@ -511,10 +511,10 @@ export const PropSidebar: React.FC = ({

暂无变量

) : ( -
+
变量列表
-

- 可编辑变量预览值,未被内容或显示条件引用的变量可删除。 +

+ 未被内容或显示条件引用的变量可删除

{templateVariables.map((v) => { const inUse = isTemplateVariableInUse(template, v); @@ -2289,10 +2289,15 @@ export const PropSidebar: React.FC = ({
{!isLocked && ( <> diff --git a/src/components/VisibilityRuleDialog.tsx b/src/components/VisibilityRuleDialog.tsx index 977f875..921ff14 100644 --- a/src/components/VisibilityRuleDialog.tsx +++ b/src/components/VisibilityRuleDialog.tsx @@ -124,7 +124,7 @@ export const VisibilityRuleDialog: React.FC = ({ }; return createPortal( -
+
e.stopPropagation()} diff --git a/src/elementMask.ts b/src/elementMask.ts index f24213c..19664e3 100644 --- a/src/elementMask.ts +++ b/src/elementMask.ts @@ -241,8 +241,7 @@ type DrawLayerFn = ( ) => void | Promise; /** - * 绘制元素图层:无蒙版时直接绘制;有蒙版时先在离屏画布绘制整层,应用蒙版后再合成到主画布。 - * 仅对当前元素的 layer 回调内容生效,边框等装饰应在合成后单独绘制。 + * 绘制元素图层:无蒙版时直接绘制;有蒙版时先在离屏画布绘制整层(背景、内容、边框),应用蒙版后再合成到主画布。 */ export async function composeElementLayer( mainCtx: CanvasRenderingContext2D, diff --git a/src/index.css b/src/index.css index 8ac977e..3c5dcae 100644 --- a/src/index.css +++ b/src/index.css @@ -753,6 +753,7 @@ html.app-dark-shell body { } .ps-layer-item { + position: relative; display: flex; align-items: center; justify-content: space-between; @@ -810,6 +811,30 @@ html.app-dark-shell body { flex-shrink: 0; } +/* PC:未 hover 时不占位,悬停时浮于右侧 */ +.ps-layer-item-actions--float { + position: absolute; + right: 4px; + top: 50%; + z-index: 1; + transform: translateY(-50%); + opacity: 0; + pointer-events: none; + padding-left: 16px; + background: linear-gradient(to left, #2d2d2d 180px, transparent); + transition: opacity 0.12s ease; +} + +.ps-layer-item:hover .ps-layer-item-actions--float { + opacity: 1; + pointer-events: auto; + background: linear-gradient(to left, #383838 180px, transparent); +} + +.ps-layer-item.selected:hover .ps-layer-item-actions--float { + background: linear-gradient(to left, #3d4f5f 180px, transparent); +} + .ps-layer-item-actions button { display: inline-flex; align-items: center; diff --git a/src/labelRenderer.ts b/src/labelRenderer.ts index f6a5492..da5573e 100644 --- a/src/labelRenderer.ts +++ b/src/labelRenderer.ts @@ -606,9 +606,9 @@ async function drawTableElement( await composeElementLayer(ctx, elem, ex, ey, ew, eh, scale, async (layerCtx, ox, oy, ow, oh) => { drawElementFill(layerCtx, ox, oy, ow, oh, elem, radii); await drawCellContent(layerCtx, ox, oy); + drawElementBorders(layerCtx, ox, oy, ow, oh, radii, sides, elem, scale); }); - drawElementBorders(ctx, ex, ey, ew, eh, radii, sides, elem, scale); drawTableGridLines(ctx, elem, ex, ey, scale); } @@ -871,9 +871,8 @@ export async function renderLabelToDataUrl(options: RenderLabelOptions): Promise await composeElementLayer(ctx, elem, ex, ey, ew, eh, scale, async (layerCtx, ox, oy, ow, oh) => { drawElementFill(layerCtx, ox, oy, ow, oh, elem, radii); drawTextContentInBox(layerCtx, elem, ox, oy, ow, oh, String(value), scale); + drawElementBorders(layerCtx, ox, oy, ow, oh, radii, sides, elem, scale); }); - - drawElementBorders(ctx, ex, ey, ew, eh, radii, sides, elem, scale); } else if (elem.type === 'barcode') { try { const radii = resolveCornerRadiiPx(elem, scale, ew, eh); @@ -882,7 +881,10 @@ export async function renderLabelToDataUrl(options: RenderLabelOptions): Promise await composeElementLayer(ctx, elem, ex, ey, ew, eh, scale, async (layerCtx, ox, oy, ow, oh) => { drawElementFill(layerCtx, ox, oy, ow, oh, elem, radii); - if (!barcodeValue) return; + if (!barcodeValue) { + drawElementBorders(layerCtx, ox, oy, ow, oh, radii, sides, elem, scale); + return; + } const inner = getPaddedRectPx(ox, oy, ow, oh, elem, scale); const tempCanvas = document.createElement('canvas'); JsBarcode(tempCanvas, barcodeValue, { @@ -895,9 +897,8 @@ export async function renderLabelToDataUrl(options: RenderLabelOptions): Promise background: GENERATOR_TRANSPARENT_BG, }); layerCtx.drawImage(tempCanvas, inner.x, inner.y, inner.w, inner.h); + drawElementBorders(layerCtx, ox, oy, ow, oh, radii, sides, elem, scale); }); - - drawElementBorders(ctx, ex, ey, ew, eh, radii, sides, elem, scale); } catch (error) { console.warn('Barcode drawing failed', error); if (mode === 'design') { @@ -921,7 +922,10 @@ export async function renderLabelToDataUrl(options: RenderLabelOptions): Promise await composeElementLayer(ctx, elem, ex, ey, ew, eh, scale, async (layerCtx, ox, oy, ow, oh) => { drawElementFill(layerCtx, ox, oy, ow, oh, elem, radii); - if (!qrValue) return; + if (!qrValue) { + drawElementBorders(layerCtx, ox, oy, ow, oh, radii, sides, elem, scale); + return; + } const inner = getPaddedRectPx(ox, oy, ow, oh, elem, scale); const tempCanvas = document.createElement('canvas'); await QRCode.toCanvas(tempCanvas, qrValue, { @@ -933,9 +937,8 @@ export async function renderLabelToDataUrl(options: RenderLabelOptions): Promise }, }); layerCtx.drawImage(tempCanvas, inner.x, inner.y, inner.w, inner.h); + drawElementBorders(layerCtx, ox, oy, ow, oh, radii, sides, elem, scale); }); - - drawElementBorders(ctx, ex, ey, ew, eh, radii, sides, elem, scale); } catch (error) { console.warn('QR code drawing failed', error); if (mode === 'design') { @@ -977,8 +980,8 @@ export async function renderLabelToDataUrl(options: RenderLabelOptions): Promise } else { drawImagePlaceholder(layerCtx, inner.x, inner.y, inner.w, inner.h, '图片'); } + drawElementBorders(layerCtx, ox, oy, ow, oh, radii, sides, elem, scale); }); - drawElementBorders(ctx, ex, ey, ew, eh, radii, sides, elem, scale); } else { drawElementChrome(ctx, ex, ey, ew, eh, elem, scale); const inner = getPaddedRectPx(ex, ey, ew, eh, elem, scale);