移动端适配
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user