mirror of
https://github.com/JOYCEQL/magic-resume.git
synced 2026-07-03 14:07:11 +02:00
feat: basic custom fields
This commit is contained in:
@@ -20,6 +20,7 @@ import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Field from "./Field";
|
||||
import ProjectItem from "./project/ProjectItem";
|
||||
import BasicPanel from "./basic/BasicPanel";
|
||||
|
||||
interface Project {
|
||||
id: string;
|
||||
@@ -66,60 +67,7 @@ export function EditPanel() {
|
||||
const renderFields = () => {
|
||||
switch (activeSection) {
|
||||
case "basic":
|
||||
return (
|
||||
<div className="space-y-5 p-4">
|
||||
<Field
|
||||
label="姓名"
|
||||
value={basic.name}
|
||||
onChange={(value) => updateBasicInfo({ name: value })}
|
||||
placeholder="请输入你的姓名"
|
||||
required
|
||||
/>
|
||||
<Field
|
||||
label="职位"
|
||||
value={basic.title}
|
||||
onChange={(value) => updateBasicInfo({ title: value })}
|
||||
placeholder="期望职位"
|
||||
required
|
||||
/>
|
||||
<Field
|
||||
label="出生日期"
|
||||
value={basic.birthDate}
|
||||
onChange={(value) => updateBasicInfo({ birthDate: value })}
|
||||
type="date"
|
||||
/>
|
||||
<div className="grid grid-cols-2 gap-5">
|
||||
<Field
|
||||
label="电子邮箱"
|
||||
value={basic.email}
|
||||
onChange={(value) => updateBasicInfo({ email: value })}
|
||||
placeholder="your@email.com"
|
||||
required
|
||||
/>
|
||||
<Field
|
||||
label="电话"
|
||||
value={basic.phone}
|
||||
onChange={(value) => updateBasicInfo({ phone: value })}
|
||||
placeholder="手机号码"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<Field
|
||||
label="所在地"
|
||||
value={basic.location}
|
||||
onChange={(value) => updateBasicInfo({ location: value })}
|
||||
placeholder="城市"
|
||||
required
|
||||
/>
|
||||
<Field
|
||||
label="个人简介"
|
||||
value={basic.summary}
|
||||
onChange={(value) => updateBasicInfo({ summary: value })}
|
||||
type="textarea"
|
||||
placeholder="简单介绍一下自己..."
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return <BasicPanel />;
|
||||
|
||||
case "projects":
|
||||
return (
|
||||
|
||||
@@ -525,7 +525,8 @@ export function PreviewPanel() {
|
||||
basic.location,
|
||||
basic.birthDate
|
||||
? new Date(basic.birthDate).toLocaleDateString()
|
||||
: ""
|
||||
: "",
|
||||
...(basic.customFields?.map((field) => field.value) || []) // 添加自定义字段的值
|
||||
]
|
||||
.filter(Boolean)
|
||||
.map((item, index, array) => (
|
||||
|
||||
@@ -133,7 +133,7 @@ export function SidePanel() {
|
||||
<div className="flex items-center p-3 space-x-3">
|
||||
<GripVertical
|
||||
className={cn(
|
||||
"w-5 h-5 opacity-0 group-hover:opacity-100 transition-opacity",
|
||||
"w-5 h-5 transition-opacity",
|
||||
theme === "dark" ? "text-neutral-400" : "text-gray-400"
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
import React, { useState } from "react";
|
||||
import { PlusCircle, X } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Field from "../Field";
|
||||
import { useResumeStore } from "@/store/useResumeStore";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { platform } from "os";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const BasicPanel = () => {
|
||||
const { basic, updateBasicInfo, theme } = useResumeStore();
|
||||
const [fields, setFields] = useState(basic.customFields);
|
||||
const [newLabel, setNewLabel] = useState("");
|
||||
const [newValue, setNewValue] = useState("");
|
||||
|
||||
const addCustomField = () => {
|
||||
const newField = {
|
||||
id: crypto.randomUUID(),
|
||||
label: newLabel,
|
||||
value: newValue,
|
||||
required: false,
|
||||
placeholder: ""
|
||||
};
|
||||
setFields([...fields, newField]);
|
||||
updateBasicInfo({
|
||||
...basic,
|
||||
customFields: [...fields, newField]
|
||||
});
|
||||
};
|
||||
|
||||
const updateField = (id: string, updates: { value: string }) => {
|
||||
setFields(
|
||||
fields.map((field) =>
|
||||
field.id === id ? { ...field, ...updates } : field
|
||||
)
|
||||
);
|
||||
updateBasicInfo({ customFields: fields });
|
||||
};
|
||||
|
||||
const deleteField = (id) => {
|
||||
setFields(fields.filter((field) => field.id !== id));
|
||||
updateBasicInfo({
|
||||
customFields: fields.filter((field) => field.id !== id)
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-5 p-4">
|
||||
<Field
|
||||
label="姓名"
|
||||
value={basic.name}
|
||||
onChange={(value) => updateBasicInfo({ name: value })}
|
||||
placeholder="请输入你的姓名"
|
||||
required
|
||||
/>
|
||||
<Field
|
||||
label="职位"
|
||||
value={basic.title}
|
||||
onChange={(value) => updateBasicInfo({ title: value })}
|
||||
placeholder="期望职位"
|
||||
required
|
||||
/>
|
||||
<Field
|
||||
label="出生日期"
|
||||
value={basic.birthDate}
|
||||
onChange={(value) => updateBasicInfo({ birthDate: value })}
|
||||
type="date"
|
||||
/>
|
||||
<div className="grid grid-cols-2 gap-5">
|
||||
<Field
|
||||
label="电子邮箱"
|
||||
value={basic.email}
|
||||
onChange={(value) => updateBasicInfo({ email: value })}
|
||||
placeholder="your@email.com"
|
||||
required
|
||||
/>
|
||||
<Field
|
||||
label="电话"
|
||||
value={basic.phone}
|
||||
onChange={(value) => updateBasicInfo({ phone: value })}
|
||||
placeholder="手机号码"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<Field
|
||||
label="所在地"
|
||||
value={basic.location}
|
||||
onChange={(value) => updateBasicInfo({ location: value })}
|
||||
placeholder="城市"
|
||||
required
|
||||
/>
|
||||
<Field
|
||||
label="个人简介"
|
||||
value={basic.summary}
|
||||
onChange={(value) => updateBasicInfo({ summary: value })}
|
||||
type="textarea"
|
||||
placeholder="简单介绍一下自己..."
|
||||
/>
|
||||
|
||||
{/* 自定义字段 */}
|
||||
{fields.map((field) => (
|
||||
<div key={field.id} className="relative group">
|
||||
<Field
|
||||
label={field.label}
|
||||
value={field.value}
|
||||
onChange={(value) => updateField(field.id, { value })}
|
||||
placeholder={field.placeholder}
|
||||
/>
|
||||
<button
|
||||
onClick={() => deleteField(field.id)}
|
||||
className="absolute -right-2 -top-2 p-1 rounded-full bg-red-500 text-white opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
>
|
||||
<X className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* 新字段输入区 */}
|
||||
<div className="flex gap-3">
|
||||
<Input
|
||||
value={newLabel}
|
||||
onChange={(e) => 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"
|
||||
)}
|
||||
/>
|
||||
<Input
|
||||
value={newValue}
|
||||
onChange={(e) => 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"
|
||||
)}
|
||||
/>
|
||||
<Button
|
||||
onClick={addCustomField}
|
||||
disabled={!newLabel}
|
||||
className={cn(
|
||||
theme === "dark"
|
||||
? "bg-indigo-600 hover:bg-indigo-700 text-white "
|
||||
: "bg-black hover:bg-neutral-800 text-white "
|
||||
)}
|
||||
>
|
||||
<PlusCircle className="w-4 h-4 mr-2" />
|
||||
添加
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BasicPanel;
|
||||
@@ -65,7 +65,8 @@ const initialState = {
|
||||
phone: "13800138000",
|
||||
location: "北京市",
|
||||
summary: "5年前端开发经验...",
|
||||
birthDate: "1995-01-01"
|
||||
birthDate: "1995-01-01",
|
||||
customFields: []
|
||||
},
|
||||
education: [
|
||||
{
|
||||
|
||||
@@ -6,6 +6,13 @@ export interface BasicInfo {
|
||||
phone: string;
|
||||
location: string;
|
||||
summary: string;
|
||||
customFields: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
value: string;
|
||||
required: boolean;
|
||||
placeholder: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface Education {
|
||||
|
||||
Reference in New Issue
Block a user