From 5c6dc7fee25b1688e8795b96f60953a8e31603d0 Mon Sep 17 00:00:00 2001
From: JOYCEQL <1449239013@qq.com>
Date: Sun, 1 Mar 2026 15:08:19 +0800
Subject: [PATCH] feat: Implement blank resume creation and enhance section
management with standard modules and a new section addition UI
---
.../dashboard/resumes/CreateResumeModal.tsx | 2 +-
src/app/app/dashboard/resumes/page.tsx | 3 +-
src/components/editor/SidePanel.tsx | 82 ++++++++++++++++---
src/components/templates/classic/config.ts | 1 +
src/components/templates/creative/config.ts | 1 +
src/components/templates/elegant/config.ts | 1 +
src/components/templates/left-right/config.ts | 1 +
src/components/templates/minimalist/config.ts | 1 +
src/components/templates/modern/config.ts | 1 +
src/components/templates/timeline/config.ts | 1 +
src/config/initialResumeData.ts | 46 ++++++++++-
src/config/modules.ts | 12 +++
src/i18n/locales/en.json | 9 +-
src/i18n/locales/zh.json | 9 +-
src/store/useResumeStore.ts | 16 +++-
src/types/template.ts | 1 +
16 files changed, 168 insertions(+), 19 deletions(-)
create mode 100644 src/config/modules.ts
diff --git a/src/app/app/dashboard/resumes/CreateResumeModal.tsx b/src/app/app/dashboard/resumes/CreateResumeModal.tsx
index b74f5fd..9262256 100644
--- a/src/app/app/dashboard/resumes/CreateResumeModal.tsx
+++ b/src/app/app/dashboard/resumes/CreateResumeModal.tsx
@@ -114,7 +114,7 @@ const TemplateThumbnail = ({
},
basic: {
...initialResumeState.basic,
- layout: template.basic?.layout || "classic",
+ layout: (template.basic?.layout as any) || "left",
},
// Feed richer mock content in large preview.
experience: sampleExperience,
diff --git a/src/app/app/dashboard/resumes/page.tsx b/src/app/app/dashboard/resumes/page.tsx
index dbe6ece..3bb7f61 100644
--- a/src/app/app/dashboard/resumes/page.tsx
+++ b/src/app/app/dashboard/resumes/page.tsx
@@ -253,7 +253,8 @@ const ResumeWorkbench = () => {
}, []);
const handleCreateFromModal = (templateId: string | null) => {
- const newId = createResume(templateId);
+ const isBlank = !templateId;
+ const newId = createResume(templateId, isBlank);
if (templateId) {
const template = DEFAULT_TEMPLATES.find((t) => t.id === templateId);
diff --git a/src/components/editor/SidePanel.tsx b/src/components/editor/SidePanel.tsx
index 19505b3..ac6d773 100644
--- a/src/components/editor/SidePanel.tsx
+++ b/src/components/editor/SidePanel.tsx
@@ -18,8 +18,16 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import LayoutSetting from "./layout/LayoutSetting";
import { useResumeStore } from "@/store/useResumeStore";
import { cn } from "@/lib/utils";
-import { THEME_COLORS } from "@/types/resume";
+import { THEME_COLORS, MenuSection } from "@/types/resume";
import { ColorPicker } from "@/components/ui/color-picker";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover";
+import { Plus } from "lucide-react";
+import { STANDARD_MODULES } from "@/config/modules";
+import { DEFAULT_TEMPLATES } from "@/config";
const fontOptions = [
{ value: "sans", label: "无衬线体" },
@@ -84,6 +92,18 @@ export function SidePanel() {
const { themeColor = THEME_COLORS[0] } = globalSettings;
const t = useTranslations("workbench.sidePanel");
+ const currentTemplate = DEFAULT_TEMPLATES.find(
+ (t) => t.id === activeResume?.templateId
+ );
+
+ const availableModules = useMemo(() => {
+ return (
+ currentTemplate?.availableSections
+ ?.map((id) => STANDARD_MODULES[id])
+ .filter(Boolean) || []
+ );
+ }, [currentTemplate]);
+
const fontOptions = [
{ value: "sans", label: t("typography.font.sans") },
{ value: "serif", label: t("typography.font.serif") },
@@ -145,15 +165,57 @@ export function SidePanel() {
reorderSections={reorderSections}
/>
-
-
- {t("layout.addCustomSection")}
-
+
+
+
+
+
+ {t("layout.addCustomSection")}
+
+
+
+
+ {/* Standard Sections Library */}
+ {availableModules.map((section) => (
+
+ ))}
+
+ {/* Divider for Custom Section */}
+ {availableModules.length > 0 && (
+
+ )}
+
+ {/* Add Custom Section */}
+
+
+
+
diff --git a/src/components/templates/classic/config.ts b/src/components/templates/classic/config.ts
index 0c903df..1622f04 100644
--- a/src/components/templates/classic/config.ts
+++ b/src/components/templates/classic/config.ts
@@ -20,4 +20,5 @@ export const classicConfig: ResumeTemplate = {
basic: {
layout: "left",
},
+ availableSections: ["skills", "experience", "projects", "education"],
};
diff --git a/src/components/templates/creative/config.ts b/src/components/templates/creative/config.ts
index bbeffa4..d67e95b 100644
--- a/src/components/templates/creative/config.ts
+++ b/src/components/templates/creative/config.ts
@@ -20,4 +20,5 @@ export const creativeConfig: ResumeTemplate = {
basic: {
layout: "left",
},
+ availableSections: ["skills", "experience", "projects", "education"],
};
diff --git a/src/components/templates/elegant/config.ts b/src/components/templates/elegant/config.ts
index 7a8fe4d..f7f818b 100644
--- a/src/components/templates/elegant/config.ts
+++ b/src/components/templates/elegant/config.ts
@@ -20,4 +20,5 @@ export const elegantConfig: ResumeTemplate = {
basic: {
layout: "center",
},
+ availableSections: ["skills", "experience", "projects", "education"],
};
diff --git a/src/components/templates/left-right/config.ts b/src/components/templates/left-right/config.ts
index 1a451f5..99706ea 100644
--- a/src/components/templates/left-right/config.ts
+++ b/src/components/templates/left-right/config.ts
@@ -20,4 +20,5 @@ export const leftRightConfig: ResumeTemplate = {
basic: {
layout: "left",
},
+ availableSections: ["skills", "experience", "projects", "education"],
};
diff --git a/src/components/templates/minimalist/config.ts b/src/components/templates/minimalist/config.ts
index c025eee..9e71255 100644
--- a/src/components/templates/minimalist/config.ts
+++ b/src/components/templates/minimalist/config.ts
@@ -20,4 +20,5 @@ export const minimalistConfig: ResumeTemplate = {
basic: {
layout: "center",
},
+ availableSections: ["skills", "experience", "projects", "education"],
};
diff --git a/src/components/templates/modern/config.ts b/src/components/templates/modern/config.ts
index 42f349c..17ff037 100644
--- a/src/components/templates/modern/config.ts
+++ b/src/components/templates/modern/config.ts
@@ -20,4 +20,5 @@ export const modernConfig: ResumeTemplate = {
basic: {
layout: "center",
},
+ availableSections: ["skills", "experience", "projects", "education"],
};
diff --git a/src/components/templates/timeline/config.ts b/src/components/templates/timeline/config.ts
index 364c9f2..5e46cca 100644
--- a/src/components/templates/timeline/config.ts
+++ b/src/components/templates/timeline/config.ts
@@ -20,4 +20,5 @@ export const timelineConfig: ResumeTemplate = {
basic: {
layout: "right",
},
+ availableSections: ["skills", "experience", "projects", "education"],
};
diff --git a/src/config/initialResumeData.ts b/src/config/initialResumeData.ts
index bcc3e60..4fd98db 100644
--- a/src/config/initialResumeData.ts
+++ b/src/config/initialResumeData.ts
@@ -50,7 +50,7 @@ export const initialResumeState = {
id: "1",
school: "北京大学",
major: "计算机科学与技术",
- degree: "本科",
+ degree: "",
startDate: "2013-09",
endDate: "2017-06",
visible: true,
@@ -326,3 +326,47 @@ export const initialResumeStateEn = {
activeSection: "basic",
globalSettings: initialGlobalSettings,
};
+
+export const blankResumeState = {
+ ...initialResumeState,
+ title: "新建简历",
+ basic: {
+ ...initialResumeState.basic,
+ name: "",
+ title: "",
+ email: "",
+ phone: "",
+ location: "",
+ birthDate: "",
+ employementStatus: "",
+ photo: "",
+ customFields: [],
+ },
+ education: [],
+ skillContent: "",
+ experience: [],
+ projects: [],
+ menuSections: [initialResumeState.menuSections[0]],
+};
+
+export const blankResumeStateEn = {
+ ...initialResumeStateEn,
+ title: "New Resume",
+ basic: {
+ ...initialResumeStateEn.basic,
+ name: "",
+ title: "",
+ email: "",
+ phone: "",
+ location: "",
+ birthDate: "",
+ employementStatus: "",
+ photo: "",
+ customFields: [],
+ },
+ education: [],
+ skillContent: "",
+ experience: [],
+ projects: [],
+ menuSections: [initialResumeStateEn.menuSections[0]],
+};
diff --git a/src/config/modules.ts b/src/config/modules.ts
new file mode 100644
index 0000000..4309544
--- /dev/null
+++ b/src/config/modules.ts
@@ -0,0 +1,12 @@
+export interface ResumeModule {
+ id: string;
+ titleKey: string;
+ icon: string;
+}
+
+export const STANDARD_MODULES: Record
= {
+ skills: { id: "skills", titleKey: "skills", icon: "⚡" },
+ experience: { id: "experience", titleKey: "experience", icon: "💼" },
+ projects: { id: "projects", titleKey: "projects", icon: "🚀" },
+ education: { id: "education", titleKey: "education", icon: "🎓" },
+};
diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json
index d9daee1..7409851 100644
--- a/src/i18n/locales/en.json
+++ b/src/i18n/locales/en.json
@@ -291,7 +291,14 @@
"sidePanel": {
"layout": {
"title": "Layout",
- "addCustomSection": "Add Custom Section"
+ "addCustomSection": "Add Module",
+ "addCustomSectionOption": "Add Custom Section",
+ "standardSections": {
+ "skills": "Skills",
+ "experience": "Experience",
+ "projects": "Projects",
+ "education": "Education"
+ }
},
"theme": {
"title": "Theme Color",
diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json
index 4e2da60..d4f17a1 100644
--- a/src/i18n/locales/zh.json
+++ b/src/i18n/locales/zh.json
@@ -251,7 +251,14 @@
"sidePanel": {
"layout": {
"title": "布局",
- "addCustomSection": "添加自定义模块"
+ "addCustomSection": "添加模块",
+ "addCustomSectionOption": "添加自定义模块",
+ "standardSections": {
+ "skills": "专业技能",
+ "experience": "工作经验",
+ "projects": "项目经历",
+ "education": "教育经历"
+ }
},
"theme": {
"title": "主题色",
diff --git a/src/store/useResumeStore.ts b/src/store/useResumeStore.ts
index 1d94360..1c1f4eb 100644
--- a/src/store/useResumeStore.ts
+++ b/src/store/useResumeStore.ts
@@ -15,6 +15,8 @@ import { DEFAULT_TEMPLATES } from "@/config";
import {
initialResumeState,
initialResumeStateEn,
+ blankResumeState,
+ blankResumeStateEn,
} from "@/config/initialResumeData";
import { generateUUID } from "@/utils/uuid";
interface ResumeStore {
@@ -22,7 +24,7 @@ interface ResumeStore {
activeResumeId: string | null;
activeResume: ResumeData | null;
- createResume: (templateId: string | null) => string;
+ createResume: (templateId: string | null, isBlank?: boolean) => string;
deleteResume: (resume: ResumeData) => void;
duplicateResume: (resumeId: string) => string;
updateResume: (resumeId: string, data: Partial) => void;
@@ -113,7 +115,7 @@ export const useResumeStore = create(
activeResumeId: null,
activeResume: null,
- createResume: (templateId = null) => {
+ createResume: (templateId = null, isBlank = false) => {
const locale =
typeof document !== "undefined"
? document.cookie
@@ -122,8 +124,14 @@ export const useResumeStore = create(
?.split("=")[1] || "zh"
: "zh";
- const initialResumeData =
- locale === "en" ? initialResumeStateEn : initialResumeState;
+ let initialResumeData: any;
+ if (isBlank) {
+ initialResumeData =
+ locale === "en" ? blankResumeStateEn : blankResumeState;
+ } else {
+ initialResumeData =
+ locale === "en" ? initialResumeStateEn : initialResumeState;
+ }
const id = generateUUID();
const template = templateId
diff --git a/src/types/template.ts b/src/types/template.ts
index a735bec..7b41705 100644
--- a/src/types/template.ts
+++ b/src/types/template.ts
@@ -20,6 +20,7 @@ export interface ResumeTemplate {
basic: {
layout?: "left" | "center" | "right";
};
+ availableSections?: string[];
}
export interface TemplateConfig {