移动端适配

This commit is contained in:
iqudoo
2026-06-13 02:23:11 +08:00
parent f2689520bd
commit 6c314f9808
10 changed files with 1058 additions and 533 deletions

View File

@@ -19,6 +19,7 @@ import {
} from '../elementAlign';
import { CollapsiblePanelSection } from './CollapsiblePanelSection';
import { CommitInput } from './CommitInput';
import { useIsNarrowScreen } from '../hooks/useIsMobile';
import {
Sliders,
LayoutGrid,
@@ -146,6 +147,8 @@ const PropTextarea: React.FC<PropTextareaProps> = ({ value, onCommit, ...props }
);
};
type MobilePropModule = 'content' | 'properties' | 'layers';
interface PropSidebarProps {
template: LabelTemplate;
onChangeTemplate: (updated: LabelTemplate) => void;
@@ -154,8 +157,15 @@ interface PropSidebarProps {
onChangeTab: (tab: 'element' | 'paper') => void;
paper: PaperConfig;
onSelectElements?: (ids: string[]) => void;
variant?: 'desktop' | 'mobile';
}
const MOBILE_PROP_MODULES: { id: MobilePropModule; label: string; icon: React.ReactNode }[] = [
{ id: 'properties', label: '属性', icon: <Sliders className="w-4 h-4" /> },
{ id: 'content', label: '内容', icon: <Link2 className="w-4 h-4" /> },
{ id: 'layers', label: '图层', icon: <Layers className="w-4 h-4" /> },
];
export const PropSidebar: React.FC<PropSidebarProps> = ({
template,
onChangeTemplate,
@@ -164,7 +174,9 @@ export const PropSidebar: React.FC<PropSidebarProps> = ({
onChangeTab,
paper,
onSelectElements,
variant = 'desktop',
}) => {
const isNarrowScreen = useIsNarrowScreen();
const selectedElementId = selectedElementIds[0] || null;
const selectedElem = template.elements.find((el) => el.id === selectedElementId);
const [newVarName, setNewVarName] = useState('');
@@ -172,6 +184,7 @@ export const PropSidebar: React.FC<PropSidebarProps> = ({
{ mode: 'add' } | { mode: 'edit'; index: number } | null
>(null);
const [panelCollapsed, setPanelCollapsed] = useState<PanelCollapseState>(loadPanelCollapseState);
const [mobileModule, setMobileModule] = useState<MobilePropModule>('properties');
const togglePanel = (key: keyof PanelCollapseState) => {
setPanelCollapsed((prev) => {
@@ -653,43 +666,8 @@ export const PropSidebar: React.FC<PropSidebarProps> = ({
);
};
return (
<div className="ps-sidebar-stack">
<VisibilityRuleDialog
open={visibilityRuleDialog !== null}
mode={visibilityRuleDialog?.mode ?? 'add'}
initialRule={editingVisibilityRule}
variableOptions={visibilityRuleDialogVariableOptions}
onClose={() => setVisibilityRuleDialog(null)}
onConfirm={handleConfirmVisibilityRule}
/>
<CollapsiblePanelSection
sectionClassName="content-panel"
collapsed={panelCollapsed.content}
onToggle={() => togglePanel('content')}
icon={<Link2 className="w-3.5 h-3.5 text-[#31a8ff] shrink-0" />}
title="内容"
bodyClassName="ps-panel-body p-3 min-h-0"
>
{renderContentPanelBody()}
</CollapsiblePanelSection>
<CollapsiblePanelSection
sectionClassName="properties-panel flex flex-col"
collapsed={panelCollapsed.properties}
onToggle={() => togglePanel('properties')}
icon={<Sliders className="w-3.5 h-3.5 text-[#31a8ff] shrink-0" />}
title="属性"
headerExtra={
selectedElem ? (
<span className="text-[10px] text-[#999] font-normal truncate max-w-[100px]">
{selectedElem.name}
</span>
) : null
}
bodyClassName="ps-panel-body p-3 space-y-4 min-h-0"
>
<>
const renderPropertiesPanelBody = () => (
<>
{/* Label Canvas Dimensions */}
{!selectedElem && (
<div className="space-y-4">
@@ -1674,7 +1652,7 @@ export const PropSidebar: React.FC<PropSidebarProps> = ({
updated[key] = e.target.value;
handleElemChange(updated);
}}
className="color-input shrink-0 scale-90"
className="color-input shrink-0"
title={`${label}边边框颜色`}
/>
<PropInput
@@ -1737,20 +1715,11 @@ export const PropSidebar: React.FC<PropSidebarProps> = ({
</div>
)}
</>
</CollapsiblePanelSection>
</>
);
<CollapsiblePanelSection
sectionClassName="layers-panel-section"
collapsed={panelCollapsed.layers}
onToggle={() => togglePanel('layers')}
icon={<Layers className="w-3.5 h-3.5 shrink-0 text-[#31a8ff]" />}
title="图层"
headerExtra={
<span className="text-[10px] text-[#666] font-mono">{template.elements.length}</span>
}
bodyClassName="ps-panel-body min-h-0"
>
const renderLayersPanelBody = () => (
<>
{template.elements.length === 0 ? (
<div className="text-[11px] text-[#666] text-center py-6"></div>
) : (
@@ -1789,7 +1758,13 @@ export const PropSidebar: React.FC<PropSidebarProps> = ({
</span>
</div>
<div className="flex items-center gap-0.5 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity">
<div
className={`flex items-center gap-0.5 shrink-0 ${
isNarrowScreen || variant === 'mobile'
? 'opacity-100'
: 'opacity-0 group-hover:opacity-100 transition-opacity'
}`}
>
<button
type="button"
onClick={(e) => { e.stopPropagation(); moveLayer(actualIdx, 'up'); }}
@@ -1830,6 +1805,96 @@ export const PropSidebar: React.FC<PropSidebarProps> = ({
);
})
)}
</>
);
const visibilityRuleDialogNode = (
<VisibilityRuleDialog
open={visibilityRuleDialog !== null}
mode={visibilityRuleDialog?.mode ?? 'add'}
initialRule={editingVisibilityRule}
variableOptions={visibilityRuleDialogVariableOptions}
onClose={() => setVisibilityRuleDialog(null)}
onConfirm={handleConfirmVisibilityRule}
/>
);
if (variant === 'mobile') {
return (
<div className="mobile-design-props">
{visibilityRuleDialogNode}
<nav className="mobile-design-props-nav" aria-label="属性模块">
{MOBILE_PROP_MODULES.map((item) => (
<button
key={item.id}
type="button"
onClick={() => setMobileModule(item.id)}
className={`mobile-design-props-nav-btn ${mobileModule === item.id ? 'active' : ''}`}
>
{item.icon}
<span>{item.label}</span>
</button>
))}
</nav>
<div className="mobile-design-props-panel scrollbar-hide">
{mobileModule === 'content' && (
<div className="ps-panel-body p-3 min-h-0">{renderContentPanelBody()}</div>
)}
{mobileModule === 'properties' && (
<div className="ps-panel-body p-3 space-y-4 min-h-0">{renderPropertiesPanelBody()}</div>
)}
{mobileModule === 'layers' && (
<div className="ps-panel-body min-h-0 mobile-layers-panel">{renderLayersPanelBody()}</div>
)}
</div>
</div>
);
}
return (
<div className="ps-sidebar-stack">
{visibilityRuleDialogNode}
<CollapsiblePanelSection
sectionClassName="properties-panel flex flex-col"
collapsed={panelCollapsed.properties}
onToggle={() => togglePanel('properties')}
icon={<Sliders className="w-3.5 h-3.5 text-[#31a8ff] shrink-0" />}
title="属性"
headerExtra={
selectedElem ? (
<span className="text-[10px] text-[#999] font-normal truncate max-w-[100px]">
{selectedElem.name}
</span>
) : null
}
bodyClassName="ps-panel-body p-3 space-y-4 min-h-0"
>
{renderPropertiesPanelBody()}
</CollapsiblePanelSection>
<CollapsiblePanelSection
sectionClassName="content-panel"
collapsed={panelCollapsed.content}
onToggle={() => togglePanel('content')}
icon={<Link2 className="w-3.5 h-3.5 text-[#31a8ff] shrink-0" />}
title="内容"
bodyClassName="ps-panel-body p-3 min-h-0"
>
{renderContentPanelBody()}
</CollapsiblePanelSection>
<CollapsiblePanelSection
sectionClassName="layers-panel-section"
collapsed={panelCollapsed.layers}
onToggle={() => togglePanel('layers')}
icon={<Layers className="w-3.5 h-3.5 shrink-0 text-[#31a8ff]" />}
title="图层"
headerExtra={
<span className="text-[10px] text-[#666] font-mono">{template.elements.length}</span>
}
bodyClassName="ps-panel-body min-h-0"
>
{renderLayersPanelBody()}
</CollapsiblePanelSection>
</div>
);