feat: Implement a dedicated mobile workbench with tabbed navigation and enhance overall workbench responsiveness

This commit is contained in:
JOYCEQL
2026-02-12 00:07:01 +08:00
parent 0917d678a3
commit 6c1954b763
3 changed files with 177 additions and 15 deletions
+28 -14
View File
@@ -7,6 +7,7 @@ import { EditorHeader } from "@/components/editor/EditorHeader";
import { SidePanel } from "@/components/editor/SidePanel";
import { EditPanel } from "@/components/editor/EditPanel";
import PreviewPanel from "@/components/preview";
import { MobileWorkbench } from "@/components/mobile/MobileWorkbench";
import {
ResizableHandle,
ResizablePanel,
@@ -178,6 +179,25 @@ export default function Home() {
setPanelSizes(sizes);
};
useEffect(() => {
// 初始化检查屏幕宽度
if (window.innerWidth < 1920) {
setSidePanelCollapsed(true);
}
// 监听 resize
const handleResize = () => {
if (window.innerWidth < 1920) {
setSidePanelCollapsed(true);
} else {
setSidePanelCollapsed(false);
}
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
useEffect(() => {
let newSizes = [];
@@ -189,7 +209,7 @@ export default function Home() {
newSizes.push(0);
} else {
if (sidePanelCollapsed) {
newSizes.push(50);
newSizes.push(36);
} else {
if (previewPanelCollapsed) {
newSizes.push(80);
@@ -209,7 +229,12 @@ export default function Home() {
if (editPanelCollapsed) {
newSizes.push(80);
} else {
newSizes.push(48);
// 如果侧边栏收起且编辑区展开,预览区占64,编辑区占36
if (sidePanelCollapsed) {
newSizes.push(64);
} else {
newSizes.push(48);
}
}
}
}
@@ -256,7 +281,6 @@ export default function Home() {
id="side-panel"
order={1}
defaultSize={panelSizes?.[0]}
minSize={20}
className={cn(
"bg-background border-r border-border"
)}
@@ -275,7 +299,6 @@ export default function Home() {
<ResizablePanel
id="edit-panel"
order={2}
minSize={32}
defaultSize={panelSizes?.[1]}
className={cn(
"bg-background border-r border-border"
@@ -295,7 +318,6 @@ export default function Home() {
order={3}
collapsible={false}
defaultSize={panelSizes?.[2]}
minSize={48}
className="bg-gray-100"
>
<div className="h-full overflow-y-auto">
@@ -314,15 +336,7 @@ export default function Home() {
{/* 移动端布局 */}
<div className="md:hidden h-[calc(100vh-64px)]">
<div className="h-full overflow-y-auto">
<PreviewPanel
sidePanelCollapsed={true}
editPanelCollapsed={true}
previewPanelCollapsed={false}
toggleSidePanel={toggleSidePanel}
toggleEditPanel={toggleEditPanel}
/>
</div>
<MobileWorkbench />
</div>
</main>
);
+1 -1
View File
@@ -30,7 +30,7 @@ export default async function LocaleLayout({ children }: Props) {
return (
<Document
locale={locale}
bodyClassName="overflow-y-hidden w-full md:min-w-[1600px]"
bodyClassName="overflow-y-hidden w-full"
>
<NextIntlClientProvider messages={messages}>
<Providers>{children}</Providers>
+148
View File
@@ -0,0 +1,148 @@
"use client";
import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { FileText, Palette, Eye } from "lucide-react";
import { cn } from "@/lib/utils";
import { useResumeStore } from "@/store/useResumeStore";
import { EditPanel } from "@/components/editor/EditPanel";
import { SidePanel } from "@/components/editor/SidePanel";
import PreviewPanel from "@/components/preview";
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
type TabType = "content" | "style" | "preview";
export function MobileWorkbench() {
const [activeTab, setActiveTab] = useState<TabType>("content");
const { activeResume, setActiveSection } = useResumeStore();
const { activeSection, menuSections } = activeResume || {};
// 渲染底部导航项
const renderNavItem = (tab: TabType, icon: React.ReactNode, label: string) => (
<button
onClick={() => setActiveTab(tab)}
className={cn(
"flex flex-col items-center justify-center py-2 px-4 flex-1 transition-colors",
activeTab === tab
? "text-primary"
: "text-muted-foreground hover:text-primary/80"
)}
>
<div className={cn("mb-1", activeTab === tab && "scale-110 duration-200")}>
{icon}
</div>
<span className="text-[10px] font-medium">{label}</span>
{activeTab === tab && (
<motion.div
layoutId="mobile-nav-indicator"
className="absolute bottom-0 w-12 h-1 bg-primary rounded-t-full"
/>
)}
</button>
);
return (
<div className="flex flex-col h-[calc(100vh-64px)] bg-background">
{/* 主要内容区域 */}
<div className="flex-1 overflow-hidden relative">
<AnimatePresence mode="wait">
{activeTab === "content" && (
<motion.div
key="content"
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 20 }}
transition={{ duration: 0.2 }}
className="h-full flex flex-col"
>
{/* 顶部模块选择器 */}
<div className="border-b bg-background/95 backdrop-blur z-10">
<ScrollArea className="w-full whitespace-nowrap">
<div className="flex p-2 space-x-2">
{/* 基础信息 */}
<button
onClick={() => setActiveSection("basic")}
className={cn(
"inline-flex items-center px-3 py-1.5 rounded-full text-sm font-medium transition-colors border",
activeSection === "basic"
? "bg-primary text-primary-foreground border-primary"
: "bg-background text-muted-foreground border-border hover:bg-muted"
)}
>
<span className="mr-1.5">👤</span>
</button>
{/* 其他模块 */}
{menuSections
?.filter((s) => s.id !== "basic" && s.enabled)
.map((section) => (
<button
key={section.id}
onClick={() => setActiveSection(section.id)}
className={cn(
"inline-flex items-center px-3 py-1.5 rounded-full text-sm font-medium transition-colors border",
activeSection === section.id
? "bg-primary text-primary-foreground border-primary"
: "bg-background text-muted-foreground border-border hover:bg-muted"
)}
>
<span className="mr-1.5">{section.icon}</span>
{section.title}
</button>
))}
</div>
<ScrollBar orientation="horizontal" className="invisible" />
</ScrollArea>
</div>
{/* 编辑区域 */}
<div className="flex-1 overflow-hidden">
<EditPanel />
</div>
</motion.div>
)}
{activeTab === "style" && (
<motion.div
key="style"
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 20 }}
transition={{ duration: 0.2 }}
className="h-full overflow-hidden"
>
<SidePanel />
</motion.div>
)}
{activeTab === "preview" && (
<motion.div
key="preview"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 1.05 }}
transition={{ duration: 0.2 }}
className="h-full overflow-y-auto bg-gray-100"
>
<PreviewPanel
sidePanelCollapsed={true}
editPanelCollapsed={true}
previewPanelCollapsed={false}
toggleSidePanel={() => {}}
toggleEditPanel={() => {}}
/>
</motion.div>
)}
</AnimatePresence>
</div>
{/* 底部导航栏 */}
<div className="h-16 border-t bg-background flex items-center justify-around relative shadow-[0_-1px_3px_rgba(0,0,0,0.05)] z-50">
{renderNavItem("content", <FileText className="w-5 h-5" />, "内容")}
{renderNavItem("style", <Palette className="w-5 h-5" />, "样式")}
{renderNavItem("preview", <Eye className="w-5 h-5" />, "预览")}
</div>
</div>
);
}