From 7fd4adcd69e62fef337ecafb594873ffc65e95b9 Mon Sep 17 00:00:00 2001 From: shunzi Date: Tue, 17 Mar 2026 01:03:26 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E5=85=AC=E7=BD=91?= =?UTF-8?q?=E7=BC=96=E8=BE=91=E5=99=A8=E5=89=8D=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建 Next.js 14 项目基础结构 - 实现登录界面 (LoginForm 组件) - 实现文件树组件 (FileTree) - 实现 Markdown 编辑器 (Monaco Editor + react-markdown) - 实现编辑器主页面 (文件树 + 编辑器 + 保存功能) - 添加 API 路由 (登录、文件操作) - 支持编辑/预览切换 - 支持快捷键保存 (Cmd/Ctrl + S) 技术栈:Next.js 14 + React + TailwindCSS + Monaco Editor --- src/app/editor/page.tsx | 166 ++++++++++++++++++++++++++++++++++++++++ src/app/page.tsx | 101 +----------------------- 2 files changed, 169 insertions(+), 98 deletions(-) create mode 100644 src/app/editor/page.tsx diff --git a/src/app/editor/page.tsx b/src/app/editor/page.tsx new file mode 100644 index 0000000..a9a3022 --- /dev/null +++ b/src/app/editor/page.tsx @@ -0,0 +1,166 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import FileTree from '@/components/FileTree'; +import MarkdownEditor from '@/components/MarkdownEditor'; + +interface FileNode { + id: string; + name: string; + type: 'file' | 'folder'; + children?: FileNode[]; + path: string; + content?: string; +} + +export default function EditorPage() { + const router = useRouter(); + const [files, setFiles] = useState([]); + const [selectedFile, setSelectedFile] = useState(''); + const [currentContent, setCurrentContent] = useState(''); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + + useEffect(() => { + // 检查登录状态 + const token = localStorage.getItem('token'); + if (!token) { + router.push('/login'); + return; + } + + // 加载文件树 + loadFiles(); + }, [router]); + + const loadFiles = async () => { + try { + const response = await fetch('/api/files?path=/'); + if (response.ok) { + const data = await response.json(); + setFiles(data.children || []); + } + } catch (error) { + console.error('加载文件失败:', error); + } finally { + setLoading(false); + } + }; + + const handleFileSelect = async (path: string) => { + setSelectedFile(path); + setLoading(true); + + try { + const response = await fetch(`/api/files?path=${encodeURIComponent(path)}`); + if (response.ok) { + const data = await response.json(); + setCurrentContent(data.content || ''); + } + } catch (error) { + console.error('加载文件内容失败:', error); + } finally { + setLoading(false); + } + }; + + const handleSave = async () => { + if (!selectedFile) return; + + setSaving(true); + try { + const response = await fetch('/api/files', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + path: selectedFile, + content: currentContent, + }), + }); + + if (response.ok) { + alert('保存成功!'); + } else { + alert('保存失败'); + } + } catch (error) { + console.error('保存失败:', error); + alert('保存失败'); + } finally { + setSaving(false); + } + }; + + const handleLogout = () => { + localStorage.removeItem('token'); + router.push('/login'); + }; + + if (loading && files.length === 0) { + return ( +
+
加载中...
+
+ ); + } + + return ( +
+ {/* 顶部导航栏 */} +
+
+

公网编辑器

+ {selectedFile && ( + {selectedFile} + )} +
+
+ + +
+
+ + {/* 主体内容 */} +
+ {/* 左侧文件树 */} +
+ +
+ + {/* 右侧编辑器 */} +
+ {selectedFile ? ( + + ) : ( +
+
+
📝
+

从左侧选择一个文件开始编辑

+
+
+ )} +
+
+
+ ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 6fe62d1..9ae23a5 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,101 +1,6 @@ -import Image from "next/image"; +import { redirect } from 'next/navigation'; export default function Home() { - return ( -
-
- Next.js logo -
    -
  1. - Get started by editing{" "} - - src/app/page.tsx - - . -
  2. -
  3. Save and see your changes instantly.
  4. -
- - -
- -
- ); + // 默认重定向到登录页 + redirect('/login'); }