1130 lines
66 KiB
HTML
1130 lines
66 KiB
HTML
<div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||
<div class="border-b border-slate-200 px-6 py-4 bg-slate-50">
|
||
<div>
|
||
<h3 class="font-semibold text-slate-800 flex items-center gap-2">
|
||
<i class="fas fa-file-alt text-purple-500"></i> {{ if .IsNew }}新建简历{{ else }}编辑简历 - {{.Resume.BasicInfo.Name}}{{ end }}
|
||
</h3>
|
||
<p class="text-sm text-slate-500 mt-1">{{.User.Name}} 的简历管理</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex">
|
||
<div class="w-64 border-r border-slate-200 bg-slate-50/50">
|
||
<nav class="p-4 space-y-1">
|
||
<button onclick="switchTab('basic')" class="tab-btn w-full flex items-center gap-3 px-4 py-3 rounded-xl text-left text-sm font-medium transition-all bg-blue-600 text-white shadow-md shadow-blue-500/30" data-tab="basic">
|
||
<i class="fas fa-user text-lg"></i> 基本信息
|
||
</button>
|
||
<button onclick="switchTab('experience')" class="tab-btn w-full flex items-center gap-3 px-4 py-3 rounded-xl text-left text-sm font-medium transition-all text-slate-600 hover:bg-slate-100 hover:text-slate-800" data-tab="experience">
|
||
<i class="fas fa-briefcase text-lg"></i> 工作经历
|
||
</button>
|
||
<button onclick="switchTab('education')" class="tab-btn w-full flex items-center gap-3 px-4 py-3 rounded-xl text-left text-sm font-medium transition-all text-slate-600 hover:bg-slate-100 hover:text-slate-800" data-tab="education">
|
||
<i class="fas fa-graduation-cap text-lg"></i> 教育背景
|
||
</button>
|
||
<button onclick="switchTab('skills')" class="tab-btn w-full flex items-center gap-3 px-4 py-3 rounded-xl text-left text-sm font-medium transition-all text-slate-600 hover:bg-slate-100 hover:text-slate-800" data-tab="skills">
|
||
<i class="fas fa-code text-lg"></i> 技能特长
|
||
</button>
|
||
<button onclick="switchTab('projects')" class="tab-btn w-full flex items-center gap-3 px-4 py-3 rounded-xl text-left text-sm font-medium transition-all text-slate-600 hover:bg-slate-100 hover:text-slate-800" data-tab="projects">
|
||
<i class="fas fa-folder-open text-lg"></i> 项目经验
|
||
</button>
|
||
</nav>
|
||
</div>
|
||
|
||
<div class="flex-1 p-6">
|
||
<div id="tab-basic" class="tab-content">
|
||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1.5">访问路由</label>
|
||
<div class="relative">
|
||
<div class="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400">
|
||
<i class="fas fa-link"></i>
|
||
</div>
|
||
<input type="text" id="basic-route" class="w-full pl-10 pr-4 py-2.5 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all" value="{{.Resume.Route}}" placeholder="如 zhangsan">
|
||
</div>
|
||
<p class="text-xs text-slate-500 mt-1">访问地址: /[路由]</p>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1.5">简历模板</label>
|
||
<div class="relative">
|
||
<div class="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400">
|
||
<i class="fas fa-palette"></i>
|
||
</div>
|
||
<select id="basic-template" class="w-full pl-10 pr-4 py-2.5 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all appearance-none bg-white">
|
||
<option value="modern" {{ if eq .Resume.Template "modern" }}selected{{ end }}>现代商务</option>
|
||
<option value="classic" {{ if eq .Resume.Template "classic" }}selected{{ end }}>传统经典</option>
|
||
<option value="tech" {{ if eq .Resume.Template "tech" }}selected{{ end }}>科技极客</option>
|
||
<option value="sidebar" {{ if eq .Resume.Template "sidebar" }}selected{{ end }}>经典侧边栏</option>
|
||
<option value="elegant" {{ if eq .Resume.Template "elegant" }}selected{{ end }}>优雅轻奢</option>
|
||
<option value="fresh" {{ if eq .Resume.Template "fresh" }}selected{{ end }}>清新自然</option>
|
||
<option value="minimalist" {{ if eq .Resume.Template "minimalist" }}selected{{ end }}>极简风格</option>
|
||
<option value="professional" {{ if eq .Resume.Template "professional" }}selected{{ end }}>专业商务</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="flex items-end">
|
||
<label class="flex items-center gap-2 cursor-pointer">
|
||
<input type="checkbox" id="basic-showInHome" class="w-4 h-4 text-blue-600 rounded focus:ring-blue-500" {{ if .Resume.ShowInHome }}checked{{ end }}>
|
||
<span class="text-sm font-medium text-slate-700">在首页展示</span>
|
||
</label>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1.5">姓名</label>
|
||
<div class="relative">
|
||
<div class="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400">
|
||
<i class="fas fa-user"></i>
|
||
</div>
|
||
<input type="text" id="basic-name" class="w-full pl-10 pr-4 py-2.5 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all" value="{{.Resume.BasicInfo.Name}}" required>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1.5">职位</label>
|
||
<div class="relative">
|
||
<div class="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400">
|
||
<i class="fas fa-briefcase"></i>
|
||
</div>
|
||
<input type="text" id="basic-title" class="w-full pl-10 pr-4 py-2.5 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all" value="{{.Resume.BasicInfo.Title}}" placeholder="如 高级后端工程师">
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1.5">邮箱</label>
|
||
<div class="relative">
|
||
<div class="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400">
|
||
<i class="fas fa-envelope"></i>
|
||
</div>
|
||
<input type="email" id="basic-email" class="w-full pl-10 pr-4 py-2.5 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all" value="{{.Resume.BasicInfo.Email}}">
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1.5">手机号</label>
|
||
<div class="relative">
|
||
<div class="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400">
|
||
<i class="fas fa-phone"></i>
|
||
</div>
|
||
<input type="tel" id="basic-phone" class="w-full pl-10 pr-4 py-2.5 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all" value="{{.Resume.BasicInfo.Phone}}">
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1.5">所在地</label>
|
||
<div class="relative">
|
||
<div class="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400">
|
||
<i class="fas fa-map-marker-alt"></i>
|
||
</div>
|
||
<input type="text" id="basic-location" class="w-full pl-10 pr-4 py-2.5 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all" value="{{.Resume.BasicInfo.Location}}" placeholder="如 北京市">
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1.5">个人网站</label>
|
||
<div class="relative">
|
||
<div class="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400">
|
||
<i class="fas fa-globe"></i>
|
||
</div>
|
||
<input type="url" id="basic-website" class="w-full pl-10 pr-4 py-2.5 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all" value="{{.Resume.BasicInfo.Website}}" placeholder="https://">
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1.5">工作年限</label>
|
||
<div class="relative">
|
||
<div class="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400">
|
||
<i class="fas fa-calendar"></i>
|
||
</div>
|
||
<input type="text" id="basic-experienceYears" class="w-full pl-10 pr-4 py-2.5 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all" value="{{.Resume.BasicInfo.ExperienceYears}}" placeholder="如 10年">
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1.5">求职目标</label>
|
||
<div class="relative">
|
||
<div class="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400">
|
||
<i class="fas fa-target"></i>
|
||
</div>
|
||
<input type="text" id="basic-jobTarget" class="w-full pl-10 pr-4 py-2.5 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all" value="{{.Resume.BasicInfo.JobTarget}}" placeholder="如 高级后端工程师">
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1.5">所属行业</label>
|
||
<div class="relative">
|
||
<div class="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400">
|
||
<i class="fas fa-building"></i>
|
||
</div>
|
||
<input type="text" id="basic-industry" class="w-full pl-10 pr-4 py-2.5 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all" value="{{.Resume.BasicInfo.Industry}}" placeholder="如 互联网、医疗">
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1.5">头像URL</label>
|
||
<div class="relative">
|
||
<div class="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400">
|
||
<i class="fas fa-image"></i>
|
||
</div>
|
||
<input type="url" id="basic-avatar" class="w-full pl-10 pr-4 py-2.5 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all" value="{{.Resume.BasicInfo.Avatar}}">
|
||
</div>
|
||
</div>
|
||
<div class="md:col-span-2">
|
||
<label class="block text-sm font-medium text-slate-700 mb-1.5">个人简介</label>
|
||
<textarea id="basic-summary" rows="6" class="w-full px-4 py-3 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all resize-none" placeholder="简要介绍自己的核心竞争力和职业发展方向...">{{.Resume.BasicInfo.Summary}}</textarea>
|
||
</div>
|
||
<div class="md:col-span-2">
|
||
<label class="block text-sm font-medium text-slate-700 mb-1.5">核心优势</label>
|
||
<div class="flex items-center gap-2 mb-2">
|
||
<input type="text" id="coreAdvantageInput" class="flex-1 px-4 py-2.5 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all" placeholder="输入核心优势,按回车添加">
|
||
<button onclick="addCoreAdvantage()" class="btn btn-primary">
|
||
<i class="fas fa-plus"></i> 添加
|
||
</button>
|
||
</div>
|
||
<div id="coreAdvantagesList" class="flex flex-wrap gap-2">
|
||
{{ range .Resume.BasicInfo.CoreAdvantages }}
|
||
<span class="inline-flex items-center gap-1 px-3 py-1.5 bg-blue-50 text-blue-600 rounded-full text-sm">
|
||
{{.}}
|
||
<button onclick="removeCoreAdvantage(this)" class="hover:text-blue-800"><i class="fas fa-times"></i></button>
|
||
</span>
|
||
{{ end }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="tab-experience" class="tab-content hidden relative">
|
||
<div class="flex items-center justify-between mb-6">
|
||
<h4 class="font-semibold text-slate-800">工作经历列表</h4>
|
||
<button onclick="addExperience()" class="btn btn-primary">
|
||
<i class="fas fa-plus"></i> 添加工作经历
|
||
</button>
|
||
</div>
|
||
<div id="experienceList" class="space-y-4">
|
||
{{ range .Resume.Experience }}
|
||
<div class="experience-item bg-slate-50 rounded-xl p-5 border border-slate-200 cursor-move" draggable="true" ondragstart="dragStart(event, 'experience')">
|
||
<div class="flex items-start justify-between mb-4">
|
||
<div class="flex items-center gap-2">
|
||
<span class="cursor-grab text-slate-400 hover:text-slate-600"><i class="fas fa-grip-vertical"></i></span>
|
||
<span class="px-3 py-1 bg-blue-100 text-blue-700 rounded-full text-xs font-medium exp-company">{{.Company}}</span>
|
||
<span class="px-3 py-1 bg-slate-200 text-slate-700 rounded-full text-xs font-medium exp-position">{{.Position}}</span>
|
||
</div>
|
||
<button onclick="removeExperience(this)" class="text-slate-400 hover:text-red-500 transition-colors">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-4">
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">公司名称</label>
|
||
<input type="text" class="exp-company-input w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" value="{{.Company}}" placeholder="输入公司名称" onchange="updateExpCompany(this)">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">职位</label>
|
||
<input type="text" class="exp-position-input w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" value="{{.Position}}" placeholder="输入职位" onchange="updateExpPosition(this)">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">开始日期</label>
|
||
<input type="text" class="exp-start-date w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" value="{{.StartDate}}" placeholder="YYYY-MM">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">结束日期</label>
|
||
<input type="text" class="exp-end-date w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" value="{{.EndDate}}" placeholder="YYYY-MM 或 至今">
|
||
</div>
|
||
</div>
|
||
<div class="mb-4">
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">工作描述</label>
|
||
<textarea class="exp-description w-full px-3 py-2 border border-slate-200 rounded-lg text-sm resize-none" rows="3" placeholder="描述你的工作职责和主要内容...">{{.Description}}</textarea>
|
||
</div>
|
||
<div class="mb-4">
|
||
<label class="block text-xs font-medium text-slate-500 mb-2">工作亮点</label>
|
||
<div class="flex items-center gap-2 mb-2">
|
||
<input type="text" class="exp-highlight-input flex-1 px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="输入亮点,按回车添加">
|
||
<button onclick="addExpHighlight(this)" class="btn btn-secondary btn-xs">添加</button>
|
||
</div>
|
||
<div class="exp-highlights-list flex flex-wrap gap-2">
|
||
{{ range .Highlights }}
|
||
<span class="inline-flex items-center gap-1 px-2 py-1 bg-amber-50 text-amber-700 rounded-full text-xs">
|
||
{{.}}
|
||
<button onclick="removeExpHighlight(this)" class="hover:text-amber-900"><i class="fas fa-times"></i></button>
|
||
</span>
|
||
{{ end }}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-2">负责平台</label>
|
||
<div class="flex items-center gap-2 mb-2">
|
||
<input type="text" class="exp-platform-input flex-1 px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="输入平台名称,按回车添加">
|
||
<button onclick="addExpPlatform(this)" class="btn btn-secondary btn-xs">添加</button>
|
||
</div>
|
||
<div class="exp-platforms-list flex flex-wrap gap-2">
|
||
{{ range .Platforms }}
|
||
<span class="inline-flex items-center gap-1 px-2 py-1 bg-green-50 text-green-700 rounded-full text-xs">
|
||
{{.}}
|
||
<button onclick="removeExpPlatform(this)" class="hover:text-green-900"><i class="fas fa-times"></i></button>
|
||
</span>
|
||
{{ end }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{{ else }}
|
||
<div id="experienceEmpty" class="text-center py-12 text-slate-500">
|
||
<div class="w-16 h-16 mx-auto mb-4 rounded-full bg-slate-100 flex items-center justify-center">
|
||
<i class="fas fa-briefcase text-slate-400 text-2xl"></i>
|
||
</div>
|
||
<p>暂无工作经历</p>
|
||
</div>
|
||
{{ end }}
|
||
</div>
|
||
</div>
|
||
|
||
<div id="tab-education" class="tab-content hidden relative">
|
||
<div class="flex items-center justify-between mb-6">
|
||
<h4 class="font-semibold text-slate-800">教育背景列表</h4>
|
||
<button onclick="addEducation()" class="btn btn-primary">
|
||
<i class="fas fa-plus"></i> 添加教育背景
|
||
</button>
|
||
</div>
|
||
<div id="educationList" class="space-y-4">
|
||
{{ range .Resume.Education }}
|
||
<div class="education-item bg-slate-50 rounded-xl p-5 border border-slate-200 cursor-move" draggable="true" ondragstart="dragStart(event, 'education')">
|
||
<div class="flex items-start justify-between mb-4">
|
||
<div class="flex items-center gap-2">
|
||
<span class="cursor-grab text-slate-400 hover:text-slate-600"><i class="fas fa-grip-vertical"></i></span>
|
||
<span class="px-3 py-1 bg-green-100 text-green-700 rounded-full text-xs font-medium edu-school">{{.School}}</span>
|
||
<span class="px-3 py-1 bg-slate-200 text-slate-700 rounded-full text-xs font-medium edu-degree">{{.Degree}}</span>
|
||
<span class="px-3 py-1 bg-blue-50 text-blue-700 rounded-full text-xs font-medium edu-major">{{.Major}}</span>
|
||
</div>
|
||
<button onclick="removeEducation(this)" class="text-slate-400 hover:text-red-500 transition-colors">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">学校名称</label>
|
||
<input type="text" class="edu-school-input w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" value="{{.School}}" placeholder="输入学校名称" onchange="updateEduSchool(this)">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">学位</label>
|
||
<input type="text" class="edu-degree-input w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" value="{{.Degree}}" placeholder="如 本科、硕士" onchange="updateEduDegree(this)">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">专业</label>
|
||
<input type="text" class="edu-major-input w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" value="{{.Major}}" placeholder="输入专业" onchange="updateEduMajor(this)">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">入学日期</label>
|
||
<input type="text" class="edu-start-date w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" value="{{.StartDate}}" placeholder="YYYY-MM">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">毕业日期</label>
|
||
<input type="text" class="edu-end-date w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" value="{{.EndDate}}" placeholder="YYYY-MM">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">GPA</label>
|
||
<input type="text" class="edu-gpa w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" value="{{.GPA}}" placeholder="如 3.8/4.0">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{{ else }}
|
||
<div id="educationEmpty" class="text-center py-12 text-slate-500">
|
||
<div class="w-16 h-16 mx-auto mb-4 rounded-full bg-slate-100 flex items-center justify-center">
|
||
<i class="fas fa-graduation-cap text-slate-400 text-2xl"></i>
|
||
</div>
|
||
<p>暂无教育背景</p>
|
||
</div>
|
||
{{ end }}
|
||
</div>
|
||
</div>
|
||
|
||
<div id="tab-skills" class="tab-content hidden relative">
|
||
<div class="flex items-center justify-between mb-6">
|
||
<h4 class="font-semibold text-slate-800">技能特长列表</h4>
|
||
<button onclick="addSkill()" class="btn btn-primary">
|
||
<i class="fas fa-plus"></i> 添加技能
|
||
</button>
|
||
</div>
|
||
<div id="skillsList" class="space-y-4">
|
||
{{ range .Resume.Skills }}
|
||
<div class="skill-item bg-slate-50 rounded-xl p-5 border border-slate-200 cursor-move" draggable="true" ondragstart="dragStart(event, 'skills')">
|
||
<div class="flex items-start justify-between mb-4">
|
||
<div class="flex items-center gap-2">
|
||
<span class="cursor-grab text-slate-400 hover:text-slate-600"><i class="fas fa-grip-vertical"></i></span>
|
||
<span class="px-3 py-1 bg-purple-100 text-purple-700 rounded-full text-xs font-medium">{{.Name}}</span>
|
||
<span class="px-3 py-1 bg-slate-200 text-slate-700 rounded-full text-xs font-medium">{{.Level}}</span>
|
||
<span class="px-3 py-1 bg-orange-50 text-orange-700 rounded-full text-xs font-medium">{{.Category}}</span>
|
||
</div>
|
||
<button onclick="removeSkill(this)" class="text-slate-400 hover:text-red-500 transition-colors">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">技能名称</label>
|
||
<input type="text" class="skill-name w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" value="{{.Name}}" placeholder="如 Go、React">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">熟练程度</label>
|
||
<select class="skill-level w-full px-3 py-2 border border-slate-200 rounded-lg text-sm">
|
||
<option value="beginner" {{ if eq .Level "beginner" }}selected{{ end }}>入门 (Beginner)</option>
|
||
<option value="intermediate" {{ if eq .Level "intermediate" }}selected{{ end }}>进阶 (Intermediate)</option>
|
||
<option value="advanced" {{ if eq .Level "advanced" }}selected{{ end }}>高级 (Advanced)</option>
|
||
<option value="expert" {{ if eq .Level "expert" }}selected{{ end }}>专家 (Expert)</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">技能分类</label>
|
||
<input type="text" class="skill-category w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" value="{{.Category}}" placeholder="如 后端、前端、数据库">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{{ else }}
|
||
<div id="skillsEmpty" class="text-center py-12 text-slate-500">
|
||
<div class="w-16 h-16 mx-auto mb-4 rounded-full bg-slate-100 flex items-center justify-center">
|
||
<i class="fas fa-code text-slate-400 text-2xl"></i>
|
||
</div>
|
||
<p>暂无技能特长</p>
|
||
</div>
|
||
{{ end }}
|
||
</div>
|
||
</div>
|
||
|
||
<div id="tab-projects" class="tab-content hidden relative">
|
||
<div class="flex items-center justify-between mb-6">
|
||
<h4 class="font-semibold text-slate-800">项目经验列表</h4>
|
||
<button onclick="addProject()" class="btn btn-primary">
|
||
<i class="fas fa-plus"></i> 添加项目
|
||
</button>
|
||
</div>
|
||
<div id="projectsList" class="space-y-4">
|
||
{{ range .Resume.Projects }}
|
||
<div class="project-item bg-slate-50 rounded-xl p-5 border border-slate-200 cursor-move" draggable="true" ondragstart="dragStart(event, 'projects')">
|
||
<div class="flex items-start justify-between mb-4">
|
||
<div class="flex items-center gap-2">
|
||
<span class="cursor-grab text-slate-400 hover:text-slate-600"><i class="fas fa-grip-vertical"></i></span>
|
||
<span class="px-3 py-1 bg-indigo-100 text-indigo-700 rounded-full text-xs font-medium proj-name">{{.Name}}</span>
|
||
<a href="{{.URL}}" target="_blank" class="px-3 py-1 bg-blue-50 text-blue-700 rounded-full text-xs font-medium hover:bg-blue-100 transition-colors proj-url">
|
||
<i class="fas fa-external-link-alt"></i> 链接
|
||
</a>
|
||
</div>
|
||
<button onclick="removeProject(this)" class="text-slate-400 hover:text-red-500 transition-colors">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-4">
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">项目名称</label>
|
||
<input type="text" class="proj-name-input w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" value="{{.Name}}" placeholder="输入项目名称" onchange="updateProjName(this)">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">项目链接</label>
|
||
<input type="url" class="proj-url-input w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" value="{{.URL}}" placeholder="https://" onchange="updateProjUrl(this)">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">开始日期</label>
|
||
<input type="text" class="proj-start-date w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" value="{{.StartDate}}" placeholder="YYYY-MM">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">结束日期</label>
|
||
<input type="text" class="proj-end-date w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" value="{{.EndDate}}" placeholder="YYYY-MM 或 至今">
|
||
</div>
|
||
</div>
|
||
<div class="mb-4">
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">项目描述</label>
|
||
<textarea class="proj-description w-full px-3 py-2 border border-slate-200 rounded-lg text-sm resize-none" rows="3" placeholder="描述项目的背景、目标和你的角色...">{{.Description}}</textarea>
|
||
</div>
|
||
<div class="mb-4">
|
||
<label class="block text-xs font-medium text-slate-500 mb-2">技术栈</label>
|
||
<div class="flex items-center gap-2 mb-2">
|
||
<input type="text" class="proj-tech-input flex-1 px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="输入技术栈,按回车添加">
|
||
<button onclick="addProjTech(this)" class="btn btn-secondary btn-xs">添加</button>
|
||
</div>
|
||
<div class="proj-tech-list flex flex-wrap gap-2">
|
||
{{ range .TechStack }}
|
||
<span class="inline-flex items-center gap-1 px-2 py-1 bg-cyan-50 text-cyan-700 rounded-full text-xs">
|
||
{{.}}
|
||
<button onclick="removeProjTech(this)" class="hover:text-cyan-900"><i class="fas fa-times"></i></button>
|
||
</span>
|
||
{{ end }}
|
||
</div>
|
||
</div>
|
||
<div class="mb-4">
|
||
<label class="block text-xs font-medium text-slate-500 mb-2">项目亮点</label>
|
||
<div class="flex items-center gap-2 mb-2">
|
||
<input type="text" class="proj-highlight-input flex-1 px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="输入亮点,按回车添加">
|
||
<button onclick="addProjHighlight(this)" class="btn btn-secondary btn-xs">添加</button>
|
||
</div>
|
||
<div class="proj-highlights-list flex flex-wrap gap-2">
|
||
{{ range .Highlights }}
|
||
<span class="inline-flex items-center gap-1 px-2 py-1 bg-amber-50 text-amber-700 rounded-full text-xs">
|
||
{{.}}
|
||
<button onclick="removeProjHighlight(this)" class="hover:text-amber-900"><i class="fas fa-times"></i></button>
|
||
</span>
|
||
{{ end }}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-2">项目成果</label>
|
||
<div class="flex items-center gap-2 mb-2">
|
||
<input type="text" class="proj-achievement-input flex-1 px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="输入成果,按回车添加">
|
||
<button onclick="addProjAchievement(this)" class="btn btn-secondary btn-xs">添加</button>
|
||
</div>
|
||
<div class="proj-achievements-list flex flex-wrap gap-2">
|
||
{{ range .Achievements }}
|
||
<span class="inline-flex items-center gap-1 px-2 py-1 bg-green-50 text-green-700 rounded-full text-xs">
|
||
{{.}}
|
||
<button onclick="removeProjAchievement(this)" class="hover:text-green-900"><i class="fas fa-times"></i></button>
|
||
</span>
|
||
{{ end }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{{ else }}
|
||
<div id="projectsEmpty" class="text-center py-12 text-slate-500">
|
||
<div class="w-16 h-16 mx-auto mb-4 rounded-full bg-slate-100 flex items-center justify-center">
|
||
<i class="fas fa-folder-open text-slate-400 text-2xl"></i>
|
||
</div>
|
||
<p>暂无项目经验</p>
|
||
</div>
|
||
{{ end }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<input type="hidden" id="resume-id" value="{{.Resume.ID}}">
|
||
<input type="hidden" id="user-id" value="{{.User.ID}}">
|
||
|
||
<div class="fixed bottom-0 left-0 right-0 bg-white border-t border-slate-200 px-6 py-4 shadow-lg z-50" style="padding-left: 17.50rem;">
|
||
<div class="flex items-center justify-center gap-3">
|
||
<button onclick="goBack()" class="btn btn-secondary">
|
||
<i class="fas fa-arrow-left"></i> 返回
|
||
</button>
|
||
<button onclick="previewResume()" class="btn btn-info">
|
||
<i class="fas fa-eye"></i> 预览
|
||
</button>
|
||
<button onclick="saveResume()" class="btn btn-primary">
|
||
<i class="fas fa-save"></i> 保存简历
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="height: 5rem;"></div>
|
||
|
||
<script>
|
||
function switchTab(tabName) {
|
||
document.querySelectorAll('.tab-content').forEach(el => el.classList.add('hidden'));
|
||
document.querySelectorAll('.tab-btn').forEach(el => {
|
||
el.classList.remove('bg-blue-600', 'text-white', 'shadow-md', 'shadow-blue-500/30');
|
||
el.classList.add('text-slate-600', 'hover:bg-slate-100', 'hover:text-slate-800');
|
||
});
|
||
|
||
document.getElementById('tab-' + tabName).classList.remove('hidden');
|
||
const activeBtn = document.querySelector('[data-tab="' + tabName + '"]');
|
||
activeBtn.classList.remove('text-slate-600', 'hover:bg-slate-100', 'hover:text-slate-800');
|
||
activeBtn.classList.add('bg-blue-600', 'text-white', 'shadow-md', 'shadow-blue-500/30');
|
||
}
|
||
|
||
function getCoreAdvantages() {
|
||
const list = document.getElementById('coreAdvantagesList');
|
||
const items = list.querySelectorAll('span');
|
||
return Array.from(items).map(item => item.textContent.trim().replace(/×$/, ''));
|
||
}
|
||
|
||
function addCoreAdvantage() {
|
||
const input = document.getElementById('coreAdvantageInput');
|
||
const value = input.value.trim();
|
||
if (!value) return;
|
||
|
||
const list = document.getElementById('coreAdvantagesList');
|
||
const span = document.createElement('span');
|
||
span.className = 'inline-flex items-center gap-1 px-3 py-1.5 bg-blue-50 text-blue-600 rounded-full text-sm';
|
||
span.innerHTML = value + ' <button onclick="removeCoreAdvantage(this)" class="hover:text-blue-800"><i class="fas fa-times"></i></button>';
|
||
list.appendChild(span);
|
||
input.value = '';
|
||
}
|
||
|
||
function removeCoreAdvantage(el) {
|
||
el.parentElement.remove();
|
||
}
|
||
|
||
document.getElementById('coreAdvantageInput').addEventListener('keypress', function(e) {
|
||
if (e.key === 'Enter') {
|
||
addCoreAdvantage();
|
||
}
|
||
});
|
||
|
||
function addExperience() {
|
||
const list = document.getElementById('experienceList');
|
||
const empty = document.getElementById('experienceEmpty');
|
||
if (empty) empty.classList.add('hidden');
|
||
|
||
const item = document.createElement('div');
|
||
item.className = 'experience-item bg-slate-50 rounded-xl p-5 border border-slate-200 cursor-move';
|
||
item.draggable = true;
|
||
item.ondragstart = function(e) { dragStart(e, 'experience'); };
|
||
item.ondragover = function(e) { dragOver(e); };
|
||
item.ondrop = function(e) { dragDrop(e, 'experience'); };
|
||
item.innerHTML = `
|
||
<div class="flex items-start justify-between mb-4">
|
||
<div class="flex items-center gap-2">
|
||
<span class="cursor-grab text-slate-400 hover:text-slate-600"><i class="fas fa-grip-vertical"></i></span>
|
||
<span class="px-3 py-1 bg-blue-100 text-blue-700 rounded-full text-xs font-medium exp-company">公司名称</span>
|
||
<span class="px-3 py-1 bg-slate-200 text-slate-700 rounded-full text-xs font-medium exp-position">职位</span>
|
||
</div>
|
||
<button onclick="removeExperience(this)" class="text-slate-400 hover:text-red-500 transition-colors">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-4">
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">公司名称</label>
|
||
<input type="text" class="exp-company-input w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="输入公司名称" onchange="updateExpCompany(this)">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">职位</label>
|
||
<input type="text" class="exp-position-input w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="输入职位" onchange="updateExpPosition(this)">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">开始日期</label>
|
||
<input type="text" class="exp-start-date w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="YYYY-MM">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">结束日期</label>
|
||
<input type="text" class="exp-end-date w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="YYYY-MM 或 至今">
|
||
</div>
|
||
</div>
|
||
<div class="mb-4">
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">工作描述</label>
|
||
<textarea class="exp-description w-full px-3 py-2 border border-slate-200 rounded-lg text-sm resize-none" rows="3" placeholder="描述你的工作职责和主要内容..."></textarea>
|
||
</div>
|
||
<div class="mb-4">
|
||
<label class="block text-xs font-medium text-slate-500 mb-2">工作亮点</label>
|
||
<div class="flex items-center gap-2 mb-2">
|
||
<input type="text" class="exp-highlight-input flex-1 px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="输入亮点,按回车添加" onkeypress="handleEnterKey(event, addExpHighlight)">
|
||
<button onclick="addExpHighlight(this)" class="btn btn-secondary btn-xs">添加</button>
|
||
</div>
|
||
<div class="exp-highlights-list flex flex-wrap gap-2"></div>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-2">负责平台</label>
|
||
<div class="flex items-center gap-2 mb-2">
|
||
<input type="text" class="exp-platform-input flex-1 px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="输入平台名称,按回车添加" onkeypress="handleEnterKey(event, addExpPlatform)">
|
||
<button onclick="addExpPlatform(this)" class="btn btn-secondary btn-xs">添加</button>
|
||
</div>
|
||
<div class="exp-platforms-list flex flex-wrap gap-2"></div>
|
||
</div>
|
||
`;
|
||
list.appendChild(item);
|
||
}
|
||
|
||
function removeExperience(el) {
|
||
el.closest('.experience-item').remove();
|
||
checkEmptyState('experience');
|
||
}
|
||
|
||
function updateExpCompany(el) {
|
||
const item = el.closest('.experience-item');
|
||
item.querySelector('.exp-company').textContent = el.value || '公司名称';
|
||
}
|
||
|
||
function updateExpPosition(el) {
|
||
const item = el.closest('.experience-item');
|
||
item.querySelector('.exp-position').textContent = el.value || '职位';
|
||
}
|
||
|
||
function addExpHighlight(el) {
|
||
const input = el.previousElementSibling;
|
||
const value = input.value.trim();
|
||
if (!value) return;
|
||
|
||
const list = el.closest('.experience-item').querySelector('.exp-highlights-list');
|
||
const span = document.createElement('span');
|
||
span.className = 'inline-flex items-center gap-1 px-2 py-1 bg-amber-50 text-amber-700 rounded-full text-xs';
|
||
span.innerHTML = value + ' <button onclick="removeExpHighlight(this)" class="hover:text-amber-900"><i class="fas fa-times"></i></button>';
|
||
list.appendChild(span);
|
||
input.value = '';
|
||
}
|
||
|
||
function removeExpHighlight(el) {
|
||
el.parentElement.remove();
|
||
}
|
||
|
||
function addExpPlatform(el) {
|
||
const input = el.previousElementSibling;
|
||
const value = input.value.trim();
|
||
if (!value) return;
|
||
|
||
const list = el.closest('.experience-item').querySelector('.exp-platforms-list');
|
||
const span = document.createElement('span');
|
||
span.className = 'inline-flex items-center gap-1 px-2 py-1 bg-green-50 text-green-700 rounded-full text-xs';
|
||
span.innerHTML = value + ' <button onclick="removeExpPlatform(this)" class="hover:text-green-900"><i class="fas fa-times"></i></button>';
|
||
list.appendChild(span);
|
||
input.value = '';
|
||
}
|
||
|
||
function removeExpPlatform(el) {
|
||
el.parentElement.remove();
|
||
}
|
||
|
||
function addEducation() {
|
||
const list = document.getElementById('educationList');
|
||
const empty = document.getElementById('educationEmpty');
|
||
if (empty) empty.classList.add('hidden');
|
||
|
||
const item = document.createElement('div');
|
||
item.className = 'education-item bg-slate-50 rounded-xl p-5 border border-slate-200 cursor-move';
|
||
item.draggable = true;
|
||
item.ondragstart = function(e) { dragStart(e, 'education'); };
|
||
item.ondragover = function(e) { dragOver(e); };
|
||
item.ondrop = function(e) { dragDrop(e, 'education'); };
|
||
item.innerHTML = `
|
||
<div class="flex items-start justify-between mb-4">
|
||
<div class="flex items-center gap-2">
|
||
<span class="cursor-grab text-slate-400 hover:text-slate-600"><i class="fas fa-grip-vertical"></i></span>
|
||
<span class="px-3 py-1 bg-green-100 text-green-700 rounded-full text-xs font-medium edu-school">学校名称</span>
|
||
<span class="px-3 py-1 bg-slate-200 text-slate-700 rounded-full text-xs font-medium edu-degree">学位</span>
|
||
<span class="px-3 py-1 bg-blue-50 text-blue-700 rounded-full text-xs font-medium edu-major">专业</span>
|
||
</div>
|
||
<button onclick="removeEducation(this)" class="text-slate-400 hover:text-red-500 transition-colors">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">学校名称</label>
|
||
<input type="text" class="edu-school-input w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="输入学校名称" onchange="updateEduSchool(this)">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">学位</label>
|
||
<input type="text" class="edu-degree-input w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="如 本科、硕士" onchange="updateEduDegree(this)">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">专业</label>
|
||
<input type="text" class="edu-major-input w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="输入专业" onchange="updateEduMajor(this)">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">入学日期</label>
|
||
<input type="text" class="edu-start-date w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="YYYY-MM">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">毕业日期</label>
|
||
<input type="text" class="edu-end-date w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="YYYY-MM">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">GPA</label>
|
||
<input type="text" class="edu-gpa w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="如 3.8/4.0">
|
||
</div>
|
||
</div>
|
||
`;
|
||
list.appendChild(item);
|
||
}
|
||
|
||
function removeEducation(el) {
|
||
el.closest('.education-item').remove();
|
||
checkEmptyState('education');
|
||
}
|
||
|
||
function updateEduSchool(el) {
|
||
const item = el.closest('.education-item');
|
||
item.querySelector('.edu-school').textContent = el.value || '学校名称';
|
||
}
|
||
|
||
function updateEduDegree(el) {
|
||
const item = el.closest('.education-item');
|
||
item.querySelector('.edu-degree').textContent = el.value || '学位';
|
||
}
|
||
|
||
function updateEduMajor(el) {
|
||
const item = el.closest('.education-item');
|
||
item.querySelector('.edu-major').textContent = el.value || '专业';
|
||
}
|
||
|
||
function addSkill() {
|
||
const list = document.getElementById('skillsList');
|
||
const empty = document.getElementById('skillsEmpty');
|
||
if (empty) empty.classList.add('hidden');
|
||
|
||
const item = document.createElement('div');
|
||
item.className = 'skill-item bg-slate-50 rounded-xl p-5 border border-slate-200 cursor-move';
|
||
item.draggable = true;
|
||
item.ondragstart = function(e) { dragStart(e, 'skills'); };
|
||
item.ondragover = function(e) { dragOver(e); };
|
||
item.ondrop = function(e) { dragDrop(e, 'skills'); };
|
||
item.innerHTML = `
|
||
<div class="flex items-start justify-between mb-4">
|
||
<div class="flex items-center gap-2">
|
||
<span class="cursor-grab text-slate-400 hover:text-slate-600"><i class="fas fa-grip-vertical"></i></span>
|
||
<span class="px-3 py-1 bg-purple-100 text-purple-700 rounded-full text-xs font-medium skill-name-label">技能名称</span>
|
||
<span class="px-3 py-1 bg-slate-200 text-slate-700 rounded-full text-xs font-medium skill-level-label">入门</span>
|
||
<span class="px-3 py-1 bg-orange-50 text-orange-700 rounded-full text-xs font-medium skill-category-label">分类</span>
|
||
</div>
|
||
<button onclick="removeSkill(this)" class="text-slate-400 hover:text-red-500 transition-colors">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">技能名称</label>
|
||
<input type="text" class="skill-name w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="如 Go、React" onchange="updateSkillName(this)">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">熟练程度</label>
|
||
<select class="skill-level w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" onchange="updateSkillLevel(this)">
|
||
<option value="beginner">入门 (Beginner)</option>
|
||
<option value="intermediate">进阶 (Intermediate)</option>
|
||
<option value="advanced">高级 (Advanced)</option>
|
||
<option value="expert">专家 (Expert)</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">技能分类</label>
|
||
<input type="text" class="skill-category w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="如 后端、前端、数据库" onchange="updateSkillCategory(this)">
|
||
</div>
|
||
</div>
|
||
`;
|
||
list.appendChild(item);
|
||
}
|
||
|
||
function removeSkill(el) {
|
||
el.closest('.skill-item').remove();
|
||
checkEmptyState('skills');
|
||
}
|
||
|
||
function updateSkillName(el) {
|
||
const item = el.closest('.skill-item');
|
||
item.querySelector('.skill-name-label').textContent = el.value || '技能名称';
|
||
}
|
||
|
||
function updateSkillLevel(el) {
|
||
const item = el.closest('.skill-item');
|
||
const map = {
|
||
'beginner': '入门',
|
||
'intermediate': '进阶',
|
||
'advanced': '高级',
|
||
'expert': '专家'
|
||
};
|
||
item.querySelector('.skill-level-label').textContent = map[el.value] || el.value;
|
||
}
|
||
|
||
function updateSkillCategory(el) {
|
||
const item = el.closest('.skill-item');
|
||
item.querySelector('.skill-category-label').textContent = el.value || '分类';
|
||
}
|
||
|
||
function addProject() {
|
||
const list = document.getElementById('projectsList');
|
||
const empty = document.getElementById('projectsEmpty');
|
||
if (empty) empty.classList.add('hidden');
|
||
|
||
const item = document.createElement('div');
|
||
item.className = 'project-item bg-slate-50 rounded-xl p-5 border border-slate-200 cursor-move';
|
||
item.draggable = true;
|
||
item.ondragstart = function(e) { dragStart(e, 'projects'); };
|
||
item.ondragover = function(e) { dragOver(e); };
|
||
item.ondrop = function(e) { dragDrop(e, 'projects'); };
|
||
item.innerHTML = `
|
||
<div class="flex items-start justify-between mb-4">
|
||
<div class="flex items-center gap-2">
|
||
<span class="cursor-grab text-slate-400 hover:text-slate-600"><i class="fas fa-grip-vertical"></i></span>
|
||
<span class="px-3 py-1 bg-indigo-100 text-indigo-700 rounded-full text-xs font-medium proj-name">项目名称</span>
|
||
<a href="#" target="_blank" class="px-3 py-1 bg-blue-50 text-blue-700 rounded-full text-xs font-medium hover:bg-blue-100 transition-colors proj-url">
|
||
<i class="fas fa-external-link-alt"></i> 链接
|
||
</a>
|
||
</div>
|
||
<button onclick="removeProject(this)" class="text-slate-400 hover:text-red-500 transition-colors">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-4">
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">项目名称</label>
|
||
<input type="text" class="proj-name-input w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="输入项目名称" onchange="updateProjName(this)">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">项目链接</label>
|
||
<input type="url" class="proj-url-input w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="https://" onchange="updateProjUrl(this)">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">开始日期</label>
|
||
<input type="text" class="proj-start-date w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="YYYY-MM">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">结束日期</label>
|
||
<input type="text" class="proj-end-date w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="YYYY-MM 或 至今">
|
||
</div>
|
||
</div>
|
||
<div class="mb-4">
|
||
<label class="block text-xs font-medium text-slate-500 mb-1">项目描述</label>
|
||
<textarea class="proj-description w-full px-3 py-2 border border-slate-200 rounded-lg text-sm resize-none" rows="3" placeholder="描述项目的背景、目标和你的角色..."></textarea>
|
||
</div>
|
||
<div class="mb-4">
|
||
<label class="block text-xs font-medium text-slate-500 mb-2">技术栈</label>
|
||
<div class="flex items-center gap-2 mb-2">
|
||
<input type="text" class="proj-tech-input flex-1 px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="输入技术栈,按回车添加" onkeypress="handleEnterKey(event, addProjTech)">
|
||
<button onclick="addProjTech(this)" class="btn btn-secondary btn-xs">添加</button>
|
||
</div>
|
||
<div class="proj-tech-list flex flex-wrap gap-2"></div>
|
||
</div>
|
||
<div class="mb-4">
|
||
<label class="block text-xs font-medium text-slate-500 mb-2">项目亮点</label>
|
||
<div class="flex items-center gap-2 mb-2">
|
||
<input type="text" class="proj-highlight-input flex-1 px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="输入亮点,按回车添加" onkeypress="handleEnterKey(event, addProjHighlight)">
|
||
<button onclick="addProjHighlight(this)" class="btn btn-secondary btn-xs">添加</button>
|
||
</div>
|
||
<div class="proj-highlights-list flex flex-wrap gap-2"></div>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-slate-500 mb-2">项目成果</label>
|
||
<div class="flex items-center gap-2 mb-2">
|
||
<input type="text" class="proj-achievement-input flex-1 px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="输入成果,按回车添加" onkeypress="handleEnterKey(event, addProjAchievement)">
|
||
<button onclick="addProjAchievement(this)" class="btn btn-secondary btn-xs">添加</button>
|
||
</div>
|
||
<div class="proj-achievements-list flex flex-wrap gap-2"></div>
|
||
</div>
|
||
`;
|
||
list.appendChild(item);
|
||
}
|
||
|
||
function removeProject(el) {
|
||
el.closest('.project-item').remove();
|
||
checkEmptyState('projects');
|
||
}
|
||
|
||
function updateProjName(el) {
|
||
const item = el.closest('.project-item');
|
||
item.querySelector('.proj-name').textContent = el.value || '项目名称';
|
||
}
|
||
|
||
function updateProjUrl(el) {
|
||
const item = el.closest('.project-item');
|
||
const urlEl = item.querySelector('.proj-url');
|
||
urlEl.href = el.value || '#';
|
||
}
|
||
|
||
function addProjTech(el) {
|
||
const input = el.previousElementSibling;
|
||
const value = input.value.trim();
|
||
if (!value) return;
|
||
|
||
const list = el.closest('.project-item').querySelector('.proj-tech-list');
|
||
const span = document.createElement('span');
|
||
span.className = 'inline-flex items-center gap-1 px-2 py-1 bg-cyan-50 text-cyan-700 rounded-full text-xs';
|
||
span.innerHTML = value + ' <button onclick="removeProjTech(this)" class="hover:text-cyan-900"><i class="fas fa-times"></i></button>';
|
||
list.appendChild(span);
|
||
input.value = '';
|
||
}
|
||
|
||
function removeProjTech(el) {
|
||
el.parentElement.remove();
|
||
}
|
||
|
||
function addProjHighlight(el) {
|
||
const input = el.previousElementSibling;
|
||
const value = input.value.trim();
|
||
if (!value) return;
|
||
|
||
const list = el.closest('.project-item').querySelector('.proj-highlights-list');
|
||
const span = document.createElement('span');
|
||
span.className = 'inline-flex items-center gap-1 px-2 py-1 bg-amber-50 text-amber-700 rounded-full text-xs';
|
||
span.innerHTML = value + ' <button onclick="removeProjHighlight(this)" class="hover:text-amber-900"><i class="fas fa-times"></i></button>';
|
||
list.appendChild(span);
|
||
input.value = '';
|
||
}
|
||
|
||
function removeProjHighlight(el) {
|
||
el.parentElement.remove();
|
||
}
|
||
|
||
function addProjAchievement(el) {
|
||
const input = el.previousElementSibling;
|
||
const value = input.value.trim();
|
||
if (!value) return;
|
||
|
||
const list = el.closest('.project-item').querySelector('.proj-achievements-list');
|
||
const span = document.createElement('span');
|
||
span.className = 'inline-flex items-center gap-1 px-2 py-1 bg-green-50 text-green-700 rounded-full text-xs';
|
||
span.innerHTML = value + ' <button onclick="removeProjAchievement(this)" class="hover:text-green-900"><i class="fas fa-times"></i></button>';
|
||
list.appendChild(span);
|
||
input.value = '';
|
||
}
|
||
|
||
function removeProjAchievement(el) {
|
||
el.parentElement.remove();
|
||
}
|
||
|
||
function collectExperience() {
|
||
const items = document.querySelectorAll('.experience-item');
|
||
return Array.from(items).map(item => ({
|
||
company: item.querySelector('.exp-company-input')?.value || item.querySelector('.exp-company')?.textContent || '',
|
||
position: item.querySelector('.exp-position-input')?.value || item.querySelector('.exp-position')?.textContent || '',
|
||
start_date: item.querySelector('.exp-start-date')?.value || '',
|
||
end_date: item.querySelector('.exp-end-date')?.value || '',
|
||
description: item.querySelector('.exp-description')?.value || '',
|
||
highlights: Array.from(item.querySelectorAll('.exp-highlights-list span')).map(s => s.textContent.trim().replace(/×$/, '')),
|
||
platforms: Array.from(item.querySelectorAll('.exp-platforms-list span')).map(s => s.textContent.trim().replace(/×$/, ''))
|
||
}));
|
||
}
|
||
|
||
function collectEducation() {
|
||
const items = document.querySelectorAll('.education-item');
|
||
return Array.from(items).map(item => ({
|
||
school: item.querySelector('.edu-school-input')?.value || item.querySelector('.edu-school')?.textContent || '',
|
||
degree: item.querySelector('.edu-degree-input')?.value || item.querySelector('.edu-degree')?.textContent || '',
|
||
major: item.querySelector('.edu-major-input')?.value || item.querySelector('.edu-major')?.textContent || '',
|
||
start_date: item.querySelector('.edu-start-date')?.value || '',
|
||
end_date: item.querySelector('.edu-end-date')?.value || '',
|
||
gpa: item.querySelector('.edu-gpa')?.value || ''
|
||
}));
|
||
}
|
||
|
||
function collectSkills() {
|
||
const items = document.querySelectorAll('.skill-item');
|
||
return Array.from(items).map(item => ({
|
||
name: item.querySelector('.skill-name')?.value || item.querySelector('.skill-name-label')?.textContent || '',
|
||
level: item.querySelector('.skill-level')?.value || 'beginner',
|
||
category: item.querySelector('.skill-category')?.value || item.querySelector('.skill-category-label')?.textContent || ''
|
||
}));
|
||
}
|
||
|
||
function collectProjects() {
|
||
const items = document.querySelectorAll('.project-item');
|
||
return Array.from(items).map(item => ({
|
||
name: item.querySelector('.proj-name-input')?.value || item.querySelector('.proj-name')?.textContent || '',
|
||
description: item.querySelector('.proj-description')?.value || '',
|
||
tech_stack: Array.from(item.querySelectorAll('.proj-tech-list span')).map(s => s.textContent.trim().replace(/×$/, '')),
|
||
url: item.querySelector('.proj-url-input')?.value || '',
|
||
highlights: Array.from(item.querySelectorAll('.proj-highlights-list span')).map(s => s.textContent.trim().replace(/×$/, '')),
|
||
achievements: Array.from(item.querySelectorAll('.proj-achievements-list span')).map(s => s.textContent.trim().replace(/×$/, '')),
|
||
start_date: item.querySelector('.proj-start-date')?.value || '',
|
||
end_date: item.querySelector('.proj-end-date')?.value || '',
|
||
show_in_resume: true
|
||
}));
|
||
}
|
||
|
||
function saveResume() {
|
||
const resumeId = document.getElementById('resume-id').value;
|
||
const userId = document.getElementById('user-id').value;
|
||
|
||
const data = {
|
||
user_id: userId,
|
||
route: document.getElementById('basic-route').value,
|
||
template: document.getElementById('basic-template').value,
|
||
show_in_home: document.getElementById('basic-showInHome').checked,
|
||
basic_info: {
|
||
name: document.getElementById('basic-name').value,
|
||
title: document.getElementById('basic-title').value,
|
||
email: document.getElementById('basic-email').value,
|
||
phone: document.getElementById('basic-phone').value,
|
||
location: document.getElementById('basic-location').value,
|
||
website: document.getElementById('basic-website').value,
|
||
summary: document.getElementById('basic-summary').value,
|
||
avatar: document.getElementById('basic-avatar').value,
|
||
experience_years: document.getElementById('basic-experienceYears').value,
|
||
job_target: document.getElementById('basic-jobTarget').value,
|
||
industry: document.getElementById('basic-industry').value,
|
||
core_advantages: getCoreAdvantages()
|
||
},
|
||
experience: collectExperience(),
|
||
education: collectEducation(),
|
||
skills: collectSkills(),
|
||
projects: collectProjects()
|
||
};
|
||
|
||
let url, method;
|
||
if (resumeId) {
|
||
url = '/dashboard/api/resume/' + resumeId;
|
||
method = 'PUT';
|
||
} else {
|
||
url = '/dashboard/api/resume/create';
|
||
method = 'POST';
|
||
}
|
||
|
||
fetch(url, {
|
||
method: method,
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(data)
|
||
})
|
||
.then(response => response.json())
|
||
.then(result => {
|
||
if (result.success) {
|
||
showCustomAlert('success', '成功', '简历保存成功');
|
||
setTimeout(() => {
|
||
window.location.href = '/dashboard/user/' + userId + '/resume';
|
||
}, 1500);
|
||
} else {
|
||
showCustomAlert('error', '错误', '保存失败:' + (result.message || '未知错误'));
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Error:', error);
|
||
showCustomAlert('error', '错误', '保存失败');
|
||
});
|
||
}
|
||
|
||
function previewResume() {
|
||
const route = document.getElementById('basic-route').value;
|
||
if (!route) {
|
||
showCustomAlert('info', '提示', '请先填写访问路由');
|
||
return;
|
||
}
|
||
window.open('/' + route, '_blank');
|
||
}
|
||
|
||
function goBack() {
|
||
const userId = document.getElementById('user-id').value;
|
||
window.location.href = '/dashboard/user/' + userId + '/resume';
|
||
}
|
||
|
||
function handleEnterKey(event, callback) {
|
||
if (event.key === 'Enter') {
|
||
event.preventDefault();
|
||
callback(event.target.nextElementSibling);
|
||
}
|
||
}
|
||
|
||
function checkEmptyState(type) {
|
||
const list = document.getElementById(type + 'List');
|
||
const empty = document.getElementById(type + 'Empty');
|
||
if (empty && list && list.querySelectorAll('.' + type + '-item').length === 0) {
|
||
empty.classList.remove('hidden');
|
||
}
|
||
}
|
||
|
||
let draggedItem = null;
|
||
let draggedType = '';
|
||
|
||
function dragStart(event, type) {
|
||
draggedItem = event.target;
|
||
draggedType = type;
|
||
event.target.style.opacity = '0.5';
|
||
event.dataTransfer.effectAllowed = 'move';
|
||
}
|
||
|
||
function dragOver(event) {
|
||
event.preventDefault();
|
||
event.dataTransfer.dropEffect = 'move';
|
||
const target = event.target.closest('.' + draggedType + '-item');
|
||
if (target && target !== draggedItem) {
|
||
target.style.borderColor = '#3b82f6';
|
||
target.style.borderWidth = '0.12rem';
|
||
}
|
||
}
|
||
|
||
function dragDrop(event, type) {
|
||
event.preventDefault();
|
||
const target = event.target.closest('.' + type + '-item');
|
||
if (target && target !== draggedItem) {
|
||
const list = document.getElementById(type + 'List');
|
||
const children = Array.from(list.children);
|
||
const fromIndex = children.indexOf(draggedItem);
|
||
const toIndex = children.indexOf(target);
|
||
|
||
if (fromIndex < toIndex) {
|
||
list.insertBefore(draggedItem, target.nextSibling);
|
||
} else {
|
||
list.insertBefore(draggedItem, target);
|
||
}
|
||
}
|
||
|
||
draggedItem.style.opacity = '1';
|
||
document.querySelectorAll('.' + type + '-item').forEach(item => {
|
||
item.style.borderColor = '';
|
||
item.style.borderWidth = '';
|
||
});
|
||
|
||
draggedItem = null;
|
||
draggedType = '';
|
||
}
|
||
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
switchTab('basic');
|
||
});
|
||
</script> |