优化修复
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
removeUnusedTemplateVariable,
|
||||
resolveVisibilityRules,
|
||||
toggleVisibilityRuleEnabled,
|
||||
withContentAndSyncedName,
|
||||
} from '../utils';
|
||||
import { VisibilityRuleDialog } from './VisibilityRuleDialog';
|
||||
import { VisibilityRuleSummary } from './VisibilityRuleSummary';
|
||||
@@ -55,7 +56,6 @@ import {
|
||||
Layers,
|
||||
Trash2,
|
||||
Copy,
|
||||
ClipboardPaste,
|
||||
AlignHorizontalJustifyStart,
|
||||
AlignHorizontalJustifyCenter,
|
||||
AlignHorizontalJustifyEnd,
|
||||
@@ -209,6 +209,8 @@ export const PropSidebar: React.FC<PropSidebarProps> = ({
|
||||
elementClipboard,
|
||||
}) => {
|
||||
const { showConfirm } = useAppDialog();
|
||||
const templateRef = useRef(template);
|
||||
templateRef.current = template;
|
||||
const isNarrowScreen = useIsNarrowScreen();
|
||||
const selectedElementId = selectedElementIds[0] || null;
|
||||
const selectedElem = template.elements.find((el) => el.id === selectedElementId);
|
||||
@@ -278,9 +280,10 @@ export const PropSidebar: React.FC<PropSidebarProps> = ({
|
||||
const maxLabelH = Math.max(10, paperH - paper.marginTop - paper.marginBottom);
|
||||
|
||||
const handleElemChange = (updatedElem: TemplateElement) => {
|
||||
const current = templateRef.current;
|
||||
onChangeTemplate({
|
||||
...template,
|
||||
elements: template.elements.map((el) => (el.id === updatedElem.id ? updatedElem : el)),
|
||||
...current,
|
||||
elements: current.elements.map((el) => (el.id === updatedElem.id ? updatedElem : el)),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -292,6 +295,11 @@ export const PropSidebar: React.FC<PropSidebarProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleContentChange = (content: string) => {
|
||||
if (!selectedElem || selectedElem.type === 'table') return;
|
||||
handleElemChange(withContentAndSyncedName(selectedElem, content));
|
||||
};
|
||||
|
||||
const nudgeableElementIds = useMemo(() => {
|
||||
const idSet = new Set(selectedElementIds);
|
||||
return template.elements
|
||||
@@ -424,7 +432,7 @@ export const PropSidebar: React.FC<PropSidebarProps> = ({
|
||||
: selectedElem.content.trim()
|
||||
? `${selectedElem.content}${selectedElem.content.endsWith(' ') ? '' : ' '}${token}`
|
||||
: token;
|
||||
handleElemChange({ ...selectedElem, content });
|
||||
handleContentChange(content);
|
||||
};
|
||||
|
||||
const handleAddTemplateVariable = () => {
|
||||
@@ -565,6 +573,9 @@ export const PropSidebar: React.FC<PropSidebarProps> = ({
|
||||
};
|
||||
|
||||
const toggleLockLayer = (idx: number) => {
|
||||
const target = template.elements[idx];
|
||||
if (!target) return;
|
||||
const willLock = !target.locked;
|
||||
const list = template.elements.map((el, i) => {
|
||||
if (i === idx) {
|
||||
return { ...el, locked: !el.locked };
|
||||
@@ -572,6 +583,12 @@ export const PropSidebar: React.FC<PropSidebarProps> = ({
|
||||
return el;
|
||||
});
|
||||
onChangeTemplate({ ...template, elements: list });
|
||||
if (willLock && onSelectElements) {
|
||||
onSelectElements(selectedElementIds.filter((id) => id !== target.id));
|
||||
if (target.type === 'table') {
|
||||
onSelectTableCells?.([]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const deleteLayer = async (id: string) => {
|
||||
@@ -589,8 +606,8 @@ export const PropSidebar: React.FC<PropSidebarProps> = ({
|
||||
if (!confirmed) return;
|
||||
|
||||
onChangeTemplate({
|
||||
...template,
|
||||
elements: template.elements.filter((item) => item.id !== id),
|
||||
...templateRef.current,
|
||||
elements: templateRef.current.elements.filter((item) => item.id !== id),
|
||||
});
|
||||
if (onSelectElements && selectedElementIds.includes(id)) {
|
||||
onSelectElements(selectedElementIds.filter((selectedId) => selectedId !== id));
|
||||
@@ -621,9 +638,10 @@ export const PropSidebar: React.FC<PropSidebarProps> = ({
|
||||
});
|
||||
if (!confirmed) return;
|
||||
|
||||
const current = templateRef.current;
|
||||
const idSet = new Set(deletableSelectedIds);
|
||||
const remaining = template.elements.filter((el) => !idSet.has(el.id));
|
||||
onChangeTemplate({ ...template, elements: remaining });
|
||||
const remaining = current.elements.filter((el) => !idSet.has(el.id));
|
||||
onChangeTemplate({ ...current, elements: remaining });
|
||||
onSelectElements?.(selectedElementIds.filter((id) => remaining.some((el) => el.id === id)));
|
||||
};
|
||||
|
||||
@@ -656,8 +674,7 @@ export const PropSidebar: React.FC<PropSidebarProps> = ({
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
const reader = new FileReader();
|
||||
reader.onload = () =>
|
||||
handleElemChange({ ...selectedElem, content: reader.result as string });
|
||||
reader.onload = () => handleContentChange(reader.result as string);
|
||||
reader.readAsDataURL(file);
|
||||
e.target.value = '';
|
||||
}}
|
||||
@@ -672,7 +689,7 @@ export const PropSidebar: React.FC<PropSidebarProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
disabled={!selectedElem.content}
|
||||
onClick={() => handleElemChange({ ...selectedElem, content: '' })}
|
||||
onClick={() => handleContentChange('')}
|
||||
className="text-[9px] text-[#31a8ff] hover:underline cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed disabled:no-underline shrink-0"
|
||||
>
|
||||
清空内容
|
||||
@@ -680,7 +697,7 @@ export const PropSidebar: React.FC<PropSidebarProps> = ({
|
||||
</div>
|
||||
<PropTextarea
|
||||
value={selectedElem.content}
|
||||
onCommit={(val) => handleElemChange({ ...selectedElem, content: val })}
|
||||
onCommit={(val) => handleContentChange(val)}
|
||||
rows={selectedElem.type === 'image' ? 3 : 2}
|
||||
className="ps-field resize-none"
|
||||
placeholder={
|
||||
@@ -1123,7 +1140,13 @@ export const PropSidebar: React.FC<PropSidebarProps> = ({
|
||||
<PropInput
|
||||
type="text"
|
||||
value={selectedElem.name}
|
||||
onCommit={(val) => handleElemChange({ ...selectedElem, name: val || '未命名元素' })}
|
||||
onCommit={(val) =>
|
||||
handleElemChange({
|
||||
...selectedElem,
|
||||
name: val || '未命名元素',
|
||||
nameCustomized: true,
|
||||
})
|
||||
}
|
||||
className="ps-field mt-2"
|
||||
/>
|
||||
</div>
|
||||
@@ -2146,12 +2169,12 @@ export const PropSidebar: React.FC<PropSidebarProps> = ({
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 min-w-0 flex-1 pointer-events-none">
|
||||
<span className="shrink-0 w-3.5 flex items-center justify-center">
|
||||
{elem.type === 'text' && <Type className="w-3 h-3 text-[#666]" />}
|
||||
{elem.type === 'barcode' && <Barcode className="w-3 h-3 text-[#666]" />}
|
||||
{elem.type === 'qrcode' && <QrCode className="w-3 h-3 text-[#666]" />}
|
||||
{elem.type === 'image' && <ImageIcon className="w-3 h-3 text-[#666]" />}
|
||||
{elem.type === 'table' && <Table2 className="w-3 h-3 text-[#666]" />}
|
||||
<span className="ps-layer-item-icon">
|
||||
{elem.type === 'text' && <Type />}
|
||||
{elem.type === 'barcode' && <Barcode />}
|
||||
{elem.type === 'qrcode' && <QrCode />}
|
||||
{elem.type === 'image' && <ImageIcon />}
|
||||
{elem.type === 'table' && <Table2 />}
|
||||
</span>
|
||||
<span className="truncate" title={elem.name}>
|
||||
{elem.name}
|
||||
@@ -2159,7 +2182,7 @@ export const PropSidebar: React.FC<PropSidebarProps> = ({
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`flex items-center gap-0.5 shrink-0 pointer-events-auto ${isNarrowScreen || variant === 'mobile' || isLocked
|
||||
className={`ps-layer-item-actions pointer-events-auto ${isNarrowScreen || variant === 'mobile' || isLocked
|
||||
? 'opacity-100'
|
||||
: 'opacity-0 group-hover:opacity-100 transition-opacity'
|
||||
}`}
|
||||
@@ -2171,18 +2194,16 @@ export const PropSidebar: React.FC<PropSidebarProps> = ({
|
||||
onClick={(e) => { e.stopPropagation(); moveLayer(actualIdx, 'up'); }}
|
||||
disabled={actualIdx === template.elements.length - 1}
|
||||
title="上移"
|
||||
className="p-0.5 hover:bg-[#555] disabled:opacity-20 cursor-pointer text-[#aaa]"
|
||||
>
|
||||
<ArrowUp className="w-3 h-3" />
|
||||
<ArrowUp />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => { e.stopPropagation(); moveLayer(actualIdx, 'down'); }}
|
||||
disabled={actualIdx === 0}
|
||||
title="下移"
|
||||
className="p-0.5 hover:bg-[#555] disabled:opacity-20 cursor-pointer text-[#aaa]"
|
||||
>
|
||||
<ArrowDown className="w-3 h-3" />
|
||||
<ArrowDown />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
@@ -2190,18 +2211,18 @@ export const PropSidebar: React.FC<PropSidebarProps> = ({
|
||||
type="button"
|
||||
onClick={(e) => { e.stopPropagation(); toggleLockLayer(actualIdx); }}
|
||||
title={isLocked ? '解锁' : '锁定'}
|
||||
className={`p-0.5 cursor-pointer ${isLocked ? 'text-amber-400' : 'text-[#666] hover:text-[#ccc]'}`}
|
||||
className={isLocked ? 'text-amber-400' : 'text-[#666] hover:text-[#ccc]'}
|
||||
>
|
||||
{isLocked ? <Lock className="w-3 h-3" /> : <Unlock className="w-3 h-3" />}
|
||||
{isLocked ? <Lock /> : <Unlock />}
|
||||
</button>
|
||||
{!isLocked && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => { e.stopPropagation(); void deleteLayer(elem.id); }}
|
||||
title="删除"
|
||||
className="p-0.5 hover:text-red-400 text-[#666] cursor-pointer"
|
||||
className="hover:text-red-400 text-[#666]"
|
||||
>
|
||||
<Trash2 className="w-3 h-3" />
|
||||
<Trash2 />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -2316,25 +2337,13 @@ export const PropSidebar: React.FC<PropSidebarProps> = ({
|
||||
</button>
|
||||
))}
|
||||
<div className="mobile-design-props-nav-spacer" aria-hidden />
|
||||
{elementClipboard?.canPaste && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => elementClipboard.pasteClipboardElements()}
|
||||
className="mobile-design-props-nav-btn"
|
||||
aria-label="粘贴元素"
|
||||
title={`粘贴 ${elementClipboard.clipboardCount} 个元素`}
|
||||
>
|
||||
<ClipboardPaste className="w-4 h-4" />
|
||||
<span>粘贴</span>
|
||||
</button>
|
||||
)}
|
||||
{elementClipboard?.canCopy && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => elementClipboard.copySelectedElements()}
|
||||
onClick={() => elementClipboard.copyAndPasteSelectedElements()}
|
||||
className="mobile-design-props-nav-btn"
|
||||
aria-label="复制选中元素"
|
||||
title="复制选中元素"
|
||||
title="复制选中元素并创建副本"
|
||||
>
|
||||
<Copy className="w-4 h-4" />
|
||||
<span>复制</span>
|
||||
|
||||
Reference in New Issue
Block a user