diff --git a/apps/fronted/package.json b/apps/fronted/package.json
index 3f2bb41..4d1b06e 100644
--- a/apps/fronted/package.json
+++ b/apps/fronted/package.json
@@ -19,6 +19,7 @@
"@tiptap/extension-list-item": "^2.4.0",
"@tiptap/extension-text-align": "^2.4.0",
"@tiptap/extension-text-style": "^2.4.0",
+ "@tiptap/extension-underline": "^2.9.1",
"@tiptap/pm": "^2.4.0",
"@tiptap/react": "^2.4.0",
"@tiptap/starter-kit": "^2.4.0",
diff --git a/apps/fronted/src/app/workbench/compoents/Editor/RichText.tsx b/apps/fronted/src/app/workbench/compoents/Editor/RichText.tsx
new file mode 100644
index 0000000..a5aabc7
--- /dev/null
+++ b/apps/fronted/src/app/workbench/compoents/Editor/RichText.tsx
@@ -0,0 +1,249 @@
+import React from "react";
+import { useEditor, EditorContent, BubbleMenu } from "@tiptap/react";
+import StarterKit from "@tiptap/starter-kit";
+import TextAlign from "@tiptap/extension-text-align";
+import TextStyle from "@tiptap/extension-text-style";
+import Underline from "@tiptap/extension-underline";
+import Color from "@tiptap/extension-color";
+import { Button } from "@/components/ui/button";
+import { Separator } from "@/components/ui/separator";
+import {
+ Bold,
+ Italic,
+ Underline as UnderlineIcon,
+ Strikethrough,
+ AlignLeft,
+ AlignCenter,
+ AlignRight,
+ AlignJustify,
+ List,
+ ListOrdered,
+ Quote,
+ Undo,
+ Redo
+} from "lucide-react";
+
+interface RichTextEditorProps {
+ content: string;
+ onChange: (content: string) => void;
+ placeholder?: string;
+}
+
+const MenuButton = ({ onClick, isActive = false, children }) => (
+
+);
+
+const RichTextEditor = ({
+ content,
+ onChange,
+ placeholder
+}: RichTextEditorProps) => {
+ const editor = useEditor({
+ extensions: [
+ StarterKit.configure({
+ heading: {
+ levels: [1, 2, 3]
+ }
+ }),
+ TextAlign.configure({
+ types: ["heading", "paragraph"],
+ alignments: ["left", "center", "right", "justify"]
+ }),
+ TextStyle,
+ Underline,
+ Color
+ ],
+ content,
+ onUpdate: ({ editor }) => {
+ onChange(editor.getHTML());
+ },
+ editorProps: {
+ attributes: {
+ class:
+ "prose prose-sm sm:prose max-w-none focus:outline-none min-h-[150px] p-4",
+ placeholder: placeholder
+ }
+ }
+ });
+
+ if (!editor) {
+ return null;
+ }
+
+ return (
+
+ {/* 工具栏 */}
+
+
+ editor.chain().focus().toggleBold().run()}
+ isActive={editor.isActive("bold")}
+ >
+
+
+ editor.chain().focus().toggleItalic().run()}
+ isActive={editor.isActive("italic")}
+ >
+
+
+ editor.chain().focus().toggleUnderline().run()}
+ isActive={editor.isActive("underline")}
+ >
+
+
+ editor.chain().focus().toggleStrike().run()}
+ isActive={editor.isActive("strike")}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
editor.chain().focus().setTextAlign("left").run()}
+ isActive={editor.isActive({ textAlign: "left" })}
+ >
+
+
+
editor.chain().focus().setTextAlign("center").run()}
+ isActive={editor.isActive({ textAlign: "center" })}
+ >
+
+
+
editor.chain().focus().setTextAlign("right").run()}
+ isActive={editor.isActive({ textAlign: "right" })}
+ >
+
+
+
editor.chain().focus().setTextAlign("justify").run()}
+ isActive={editor.isActive({ textAlign: "justify" })}
+ >
+
+
+
+
+
+
+
+ editor.chain().focus().toggleBulletList().run()}
+ isActive={editor.isActive("bulletList")}
+ >
+
+
+ editor.chain().focus().toggleOrderedList().run()}
+ isActive={editor.isActive("orderedList")}
+ >
+
+
+ editor.chain().focus().toggleBlockquote().run()}
+ isActive={editor.isActive("blockquote")}
+ >
+
+
+
+
+
+
+
+ editor.chain().focus().undo().run()}
+ disabled={!editor.can().undo()}
+ >
+
+
+ editor.chain().focus().redo().run()}
+ disabled={!editor.can().redo()}
+ >
+
+
+
+
+
+ {/* 编辑区域 */}
+
+
+ {/* 气泡菜单 */}
+ {editor && (
+
+ editor.chain().focus().toggleBold().run()}
+ isActive={editor.isActive("bold")}
+ >
+
+
+ editor.chain().focus().toggleItalic().run()}
+ isActive={editor.isActive("italic")}
+ >
+
+
+ editor.chain().focus().toggleUnderline().run()}
+ isActive={editor.isActive("underline")}
+ >
+
+
+
+ )}
+
+ );
+};
+
+export default RichTextEditor;
diff --git a/apps/fronted/src/app/workbench/page.tsx b/apps/fronted/src/app/workbench/page.tsx
index 6f8ed3e..9641e43 100644
--- a/apps/fronted/src/app/workbench/page.tsx
+++ b/apps/fronted/src/app/workbench/page.tsx
@@ -1,32 +1,659 @@
"use client";
-import Editor from "./compoents/Editor";
+import React, { useState } from "react";
+import { Button } from "@/components/ui/button";
+import { Card } from "@/components/ui/card";
+import { Input } from "@/components/ui/input";
+import { Textarea } from "@/components/ui/textarea";
+import { Label } from "@/components/ui/label";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
- ResizableHandle,
- ResizablePanel,
- ResizablePanelGroup
-} from "@/components/ui/resizable";
-import Preview from "./compoents/Preview";
-import styles from "./index.module.scss";
-const WorkBench = () => {
+ Type,
+ Layout,
+ Phone,
+ Mail,
+ Globe,
+ MapPin,
+ GraduationCap,
+ Briefcase,
+ Award,
+ Plus,
+ Trash,
+ Download,
+ User,
+ Cpu,
+ Calendar,
+ Trash2
+} from "lucide-react";
+import RichTextEditor from "./compoents/Editor/RichText";
+
+const TabIcon = ({ icon: Icon, label }) => (
+
+
+ {label}
+
+);
+
+const FormField = ({ label, children }) => (
+
+
+ {children}
+
+);
+
+const Section = ({ title, children, className = "" }) => (
+
+
{title}
+ {children}
+
+);
+
+// 简历数据结构
+interface ResumeData {
+ basics: {
+ name: string;
+ label: string;
+ email: string;
+ phone: string;
+ website: string;
+ location: string;
+ summary: string;
+ };
+ education: Array<{
+ institution: string;
+ area: string;
+ studyType: string;
+ startDate: string;
+ endDate: string;
+ description: string;
+ }>;
+ work: Array<{
+ company: string;
+ position: string;
+ startDate: string;
+ endDate: string;
+ description: string;
+ }>;
+ skills: Array<{
+ name: string;
+ level: string;
+ keywords: string[];
+ }>;
+ projects: Array<{
+ name: string;
+ description: string;
+ highlights: string[];
+ url: string;
+ }>;
+}
+
+// 简历预览组件
+const ResumePreview = ({ data }: { data: ResumeData }) => {
+ const handleDownloadPDF = () => {
+ window.print();
+ };
+
return (
-
-
+ {/* 固定的下载按钮 */}
+
+
+ {/* 简历预览内容 */}
+
-
-
-
-
-
-
-
-
+ {/* 个人信息头部 */}
+
+
+ {data.basics.name}
+
+ {data.basics.label && (
+
+ {data.basics.label}
+
+ )}
+
+ {data.basics.email && (
+
+
+ {data.basics.email}
+
+ )}
+ {data.basics.phone && (
+
+
+
{data.basics.phone}
+
+ )}
+ {data.basics.location && (
+
+
+ {data.basics.location}
+
+ )}
+ {data.basics.website && (
+
+ )}
+
+
+
+ {/* 主要内容区域 */}
+
+ {/* 个人简介 */}
+ {data.basics.summary && (
+
+
+
+ 个人简介
+
+
+ {data.basics.summary}
+
+
+ )}
+
+ {/* 工作经历 */}
+ {data.work.length > 0 && (
+
+
+
+ 工作经历
+
+
+ {data.work.map((work, index) => (
+
+
+
+ {work.company}
+
+
+ {work.startDate} - {work.endDate}
+
+
+
+ {work.position}
+
+
+
+ ))}
+
+
+ )}
+
+ {/* 教育经历 */}
+ {data.education.length > 0 && (
+
+
+
+ 教育经历
+
+
+ {data.education.map((edu, index) => (
+
+
+
+ {edu.institution}
+
+
+ {edu.startDate} - {edu.endDate}
+
+
+
+ {edu.studyType} · {edu.area}
+
+
+
+ ))}
+
+
+ )}
+
+ {/* 技能专长 */}
+ {data.skills.length > 0 && (
+
+
+
+ 技能专长
+
+
+ {data.skills.map((skill, index) => (
+
+
+ {skill.name}
+
+
+ {skill.keywords.join(" · ")}
+
+
+ ))}
+
+
+ )}
+
+ {/* 项目经历 */}
+ {data.projects.length > 0 && (
+
+
+
+ 项目经历
+
+
+ {data.projects.map((project, index) => (
+
+
+
+ {project.name}
+
+ {project.url && (
+
+ 项目链接
+
+ )}
+
+
+ {project.highlights.length > 0 && (
+
+ {project.highlights.map((highlight, i) => (
+ -
+ {highlight}
+
+ ))}
+
+ )}
+
+ ))}
+
+
+ )}
+
+
);
};
-export default WorkBench;
+// 主编辑器组件
+const ResumeEditor = () => {
+ const [resumeData, setResumeData] = useState({
+ basics: {
+ name: "",
+ label: "",
+ email: "",
+ phone: "",
+ website: "",
+ location: "",
+ summary: ""
+ },
+ education: [],
+ work: [],
+ skills: [],
+ projects: []
+ });
+
+ // 更新基本信息
+ const updateBasics = (field: keyof ResumeData["basics"], value: string) => {
+ setResumeData((prev) => ({
+ ...prev,
+ basics: {
+ ...prev.basics,
+ [field]: value
+ }
+ }));
+ };
+
+ // 添加教育经历
+ const addEducation = () => {
+ setResumeData((prev) => ({
+ ...prev,
+ education: [
+ ...prev.education,
+ {
+ institution: "",
+ area: "",
+ studyType: "",
+ startDate: "",
+ endDate: "",
+ description: ""
+ }
+ ]
+ }));
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ updateBasics("name", e.target.value)}
+ placeholder="你的名字"
+ className="bg-white"
+ />
+
+
+ updateBasics("label", e.target.value)}
+ placeholder="期望职位"
+ className="bg-white"
+ />
+
+
+
+
+
+
+
+
+ updateBasics("email", e.target.value)
+ }
+ placeholder="邮箱地址"
+ className="pl-10 bg-white"
+ />
+
+
+
+
+
+
+ updateBasics("phone", e.target.value)
+ }
+ placeholder="联系电话"
+ className="pl-10 bg-white"
+ />
+
+
+
+
+
+
+
+ updateBasics("summary", value)}
+ placeholder="简单介绍自己"
+ />
+
+
+
+
+
+
+
+
+
+
+ {resumeData.education.map((edu, index) => (
+
+
+
+ ))}
+
+
+
+
+ {/* 其他 TabsContent 内容类似 */}
+
+
+
+
+
+
+
+ );
+};
+
+export default ResumeEditor;
diff --git a/apps/fronted/src/components/ui/textarea.tsx b/apps/fronted/src/components/ui/textarea.tsx
new file mode 100644
index 0000000..9f9a6dc
--- /dev/null
+++ b/apps/fronted/src/components/ui/textarea.tsx
@@ -0,0 +1,24 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+export interface TextareaProps
+ extends React.TextareaHTMLAttributes {}
+
+const Textarea = React.forwardRef(
+ ({ className, ...props }, ref) => {
+ return (
+
+ )
+ }
+)
+Textarea.displayName = "Textarea"
+
+export { Textarea }
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 698a1c2..fd34a8d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -122,6 +122,9 @@ importers:
'@tiptap/extension-text-style':
specifier: ^2.4.0
version: 2.9.1(@tiptap/core@2.9.1)
+ '@tiptap/extension-underline':
+ specifier: ^2.9.1
+ version: 2.9.1(@tiptap/core@2.9.1)
'@tiptap/pm':
specifier: ^2.4.0
version: 2.9.1
@@ -2227,6 +2230,14 @@ packages:
'@tiptap/core': 2.9.1(@tiptap/pm@2.9.1)
dev: false
+ /@tiptap/extension-underline@2.9.1(@tiptap/core@2.9.1):
+ resolution: {integrity: sha512-IrUsIqKPgD7GcAjr4D+RC0WvLHUDBTMkD8uPNEoeD1uH9t9zFyDfMRPnx/z3/6Gf6fTh3HzLcHGibiW2HiMi2A==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ dependencies:
+ '@tiptap/core': 2.9.1(@tiptap/pm@2.9.1)
+ dev: false
+
/@tiptap/pm@2.9.1:
resolution: {integrity: sha512-mvV86fr7kEuDYEApQ2uMPCKL2uagUE0BsXiyyz3KOkY1zifyVm1fzdkscb24Qy1GmLzWAIIihA+3UHNRgYdOlQ==}
dependencies: