diff --git a/apps/fronted/src/components/editor/basic/BasicPanel.tsx b/apps/fronted/src/components/editor/basic/BasicPanel.tsx index b3e42d0..20ff539 100644 --- a/apps/fronted/src/components/editor/basic/BasicPanel.tsx +++ b/apps/fronted/src/components/editor/basic/BasicPanel.tsx @@ -11,21 +11,66 @@ import { FileText, Smartphone, Globe, - Github + Github, + GripVertical, + Trash2, + LucideIcon } from "lucide-react"; import { Button } from "@/components/ui/button"; +import { Switch } from "@/components/ui/switch"; +import { Label } from "@/components/ui/label"; import Field from "../Field"; import { useResumeStore } from "@/store/useResumeStore"; -import { Input } from "@/components/ui/input"; import { cn } from "@/lib/utils"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { motion, Reorder } from "framer-motion"; -// 预定义图标选项 -const iconOptions = [ +type IconOption = { + label: string; + value: string; + icon: LucideIcon; +}; + +type CustomFieldType = { + id: string; + label: string; + value: string; + icon: string; +}; + +type Theme = "dark" | "light"; + +interface IconSelectorProps { + value: string; + onChange: (value: string) => void; + theme: Theme; +} + +interface CustomFieldProps { + field: CustomFieldType; + onUpdate: (field: CustomFieldType) => void; + onDelete: (id: string) => void; + theme: Theme; +} + +interface BasicInfo { + name?: string; + title?: string; + birthDate?: string; + email?: string; + phone?: string; + location?: string; + summary?: string; + employed?: boolean; + customFields?: CustomFieldType[]; + icons?: Record; +} + +const iconOptions: IconOption[] = [ { label: "用户", value: "User", icon: User }, { label: "工作", value: "Briefcase", icon: Briefcase }, { label: "日期", value: "Calendar", icon: Calendar }, @@ -38,10 +83,15 @@ const iconOptions = [ { label: "Github", value: "Github", icon: Github } ]; -const IconSelector = ({ value, onChange, theme }) => { +const IconSelector: React.FC = ({ + value, + onChange, + theme +}) => { const [open, setOpen] = React.useState(false); const selectedIcon = iconOptions.find((i) => i.value === value) || iconOptions[0]; + const Icon = selectedIcon.icon; const handleSelect = (iconValue: string) => { onChange(iconValue); @@ -51,14 +101,7 @@ const IconSelector = ({ value, onChange, theme }) => { return ( - + { )} > {iconOptions.map(({ value: iconValue, icon: Icon, label }) => ( - + ))} ); }; -const BasicPanel = () => { +const CustomField: React.FC = ({ + field, + onUpdate, + onDelete, + theme +}) => { + return ( + +
+ + onUpdate({ ...field, icon: value })} + theme={theme} + /> + onUpdate({ ...field, label: value })} + placeholder="字段名称" + /> + onUpdate({ ...field, value })} + placeholder="字段内容" + /> + +
+
+ ); +}; + +const BasicPanel: React.FC = () => { const { basic, updateBasicInfo, theme } = useResumeStore(); - const [fields, setFields] = useState(basic?.customFields || []); - const [newLabel, setNewLabel] = useState(""); - const [newValue, setNewValue] = useState(""); + const [fields, setFields] = useState( + basic?.customFields || [] + ); const addCustomField = () => { - const newField = { + const fieldToAdd: CustomFieldType = { id: crypto.randomUUID(), - label: newLabel, - value: newValue, - icon: "User", - required: false, - placeholder: "" + label: "", + value: "", + icon: "User" }; - const updatedFields = [...fields, newField]; + const updatedFields = [...fields, fieldToAdd]; setFields(updatedFields); updateBasicInfo({ ...basic, customFields: updatedFields }); - setNewLabel(""); - setNewValue(""); }; - const updateField = (id: string, updates: any) => { + const updateField = (updatedField: CustomFieldType) => { const updatedFields = fields.map((field) => - field.id === id ? { ...field, ...updates } : field + field.id === updatedField.id ? updatedField : field ); setFields(updatedFields); updateBasicInfo({ @@ -124,16 +217,6 @@ const BasicPanel = () => { }); }; - const updateIcon = (key: string, iconName: string) => { - updateBasicInfo({ - ...basic, - icons: { - ...(basic?.icons || {}), - [key]: iconName - } - }); - }; - const deleteField = (id: string) => { const updatedFields = fields.filter((field) => field.id !== id); setFields(updatedFields); @@ -143,13 +226,24 @@ const BasicPanel = () => { }); }; - const renderField = (key: string, label: string, options = {}) => { - const { - required = false, - type = "text", - isCustom = false, - field = null - } = options; + const handleReorder = (newOrder: CustomFieldType[]) => { + setFields(newOrder); + updateBasicInfo({ + ...basic, + customFields: newOrder + }); + }; + + const renderField = ( + key: keyof BasicInfo, + label: string, + options: { + type?: "date" | "textarea" | "text" | "editor" | undefined; + isCustom?: boolean; + field?: CustomFieldType | null; + } = {} + ) => { + const { type = "text", isCustom = false, field = null } = options; const selectedIcon = basic?.icons?.[key] || "User"; @@ -158,19 +252,27 @@ const BasicPanel = () => {
updateIcon(key, value)} + onChange={(value) => { + updateBasicInfo({ + ...basic, + icons: { + ...(basic?.icons || {}), + [key]: value + } + }); + }} theme={theme} /> {label}
- isCustom - ? updateField(field.id, { value }) - : updateBasicInfo({ [key]: value }) + isCustom && field + ? updateField({ ...field, value }) + : updateBasicInfo({ ...basic, [key]: value }) } placeholder={`请输入${label}`} type={type} @@ -181,65 +283,81 @@ const BasicPanel = () => { return (
- {renderField("name", "姓名", { required: true })} - {renderField("title", "职位", { required: true })} +
+
{renderField("name", "姓名")}
+
+ + updateBasicInfo({ + ...basic, + employed: checked + }) + } + className={cn( + theme === "dark" ? "bg-neutral-700" : "bg-neutral-200", + "data-[state=checked]:bg-green-500" + )} + /> + +
+
+ {renderField("title", "职位")} {renderField("birthDate", "出生日期", { type: "date" })}
- {renderField("email", "电子邮箱", { required: true })} - {renderField("phone", "电话", { required: true })} + {renderField("email", "电子邮箱")} + {renderField("phone", "电话")}
- {renderField("location", "所在地", { required: true })} + {renderField("location", "所在地")} {renderField("summary", "个人简介", { type: "textarea" })} {/* 自定义字段 */} - {Array.isArray(fields) && - fields.map((field) => ( -
- {renderField(field.id, field.label, { isCustom: true, field })} - -
- ))} +
+ + {Array.isArray(fields) && + fields.map((field) => ( + + ))} + - {/* 新字段输入区 */} -
- setNewLabel(e.target.value)} - placeholder="字段名称" - className={cn( - "w-1/3", - theme === "dark" - ? "bg-neutral-900 border-neutral-700 text-neutral-200 placeholder:text-neutral-500" - : "bg-white border-gray-200 text-gray-900 placeholder:text-gray-500" - )} - /> - setNewValue(e.target.value)} - placeholder="字段值" - className={cn( - "w-1/3", - theme === "dark" - ? "bg-neutral-900 border-neutral-700 text-neutral-200 placeholder:text-neutral-500" - : "bg-white border-gray-200 text-gray-900 placeholder:text-gray-500" - )} - /> + {/* 添加按钮 */}
diff --git a/apps/fronted/src/store/useResumeStore.ts b/apps/fronted/src/store/useResumeStore.ts index 5a71e07..11ff9f9 100644 --- a/apps/fronted/src/store/useResumeStore.ts +++ b/apps/fronted/src/store/useResumeStore.ts @@ -66,6 +66,7 @@ const initialState = { location: "北京市", summary: "5年前端开发经验...", birthDate: "", + employed: false, icons: {}, customFields: [] }, diff --git a/apps/fronted/src/types/resume.ts b/apps/fronted/src/types/resume.ts index 7e6e9e9..5d00c2a 100644 --- a/apps/fronted/src/types/resume.ts +++ b/apps/fronted/src/types/resume.ts @@ -7,13 +7,12 @@ export interface BasicInfo { location: string; summary: string; icons: Record; + employed: boolean; customFields: Array<{ id: string; label: string; value: string; icon: string; - required: boolean; - placeholder: string; }>; } @@ -102,20 +101,19 @@ export interface ResumeTheme { } export const THEME_COLORS = [ - "#2563eb", // 经典蓝 - "#059669", // 翡翠绿 - "#7c3aed", // 优雅紫 - "#e11d48", // 玫瑰红 - "#d97706", // 琥珀金 - "#0891b2", // 青碧蓝 - "#4f46e5", // 靛青蓝 - "#0d9488", // 青蓝绿 - "#0284c7", // 天际蓝 - "#6d28d9", // 雅致紫 - "#c026d3", // 绯紫红 - "#db2777", // 粉红色 - "#ea580c", // 活力橙 - "#65a30d", // 青柠绿 - "#475569", // 岩石灰 - "#dc2626" // 中国红 + "#2563eb", + "#059669", + "#7c3aed", + "#e11d48", + "#d97706", + "#0891b2", + "#4f46e5", + "#0d9488", + "#0284c7", + "#6d28d9", + "#c026d3", + "#ea580c", + "#65a30d", + "#475569", + "#dc2626" ];