424 lines
18 KiB
HTML
424 lines
18 KiB
HTML
{{ define "component-avatar" }}
|
|
{{ end }}
|
|
|
|
{{ define "component-basic-info" }}
|
|
<div class="basic-info">
|
|
<div class="info-header">
|
|
<h1 class="name">{{.Name}}</h1>
|
|
<h2 class="title">{{.Title}}</h2>
|
|
</div>
|
|
<div class="contact-row">
|
|
{{ if .Email }}<div class="contact-tag email-protected" onclick="revealEmail(this)" data-email="{{.Email}}"><i class="fas fa-envelope"></i> {{.Email}}</div>{{ end }}
|
|
{{ if .Phone }}<div class="contact-tag"><i class="fas fa-phone"></i> {{.Phone}}</div>{{ end }}
|
|
{{ if .Location }}<div class="contact-tag"><i class="fas fa-map-marker-alt"></i> {{.Location}}</div>{{ end }}
|
|
{{ if .Website }}<div class="contact-tag"><i class="fas fa-globe"></i> <a href="{{.Website}}" target="_blank">{{.Website}}</a></div>{{ end }}
|
|
</div>
|
|
<div class="meta-row">
|
|
{{ if .ExperienceYears }}<div class="meta-tag"><i class="fas fa-clock"></i> {{.ExperienceYears}}</div>{{ end }}
|
|
{{ if .JobTarget }}<div class="meta-tag"><i class="fas fa-bullseye"></i> {{.JobTarget}}</div>{{ end }}
|
|
{{ if .Industry }}<div class="meta-tag"><i class="fas fa-building"></i> {{.Industry}}</div>{{ end }}
|
|
</div>
|
|
{{ if .Summary }}
|
|
<div class="summary">{{.Summary}}</div>
|
|
{{ end }}
|
|
</div>
|
|
{{ end }}
|
|
|
|
{{ define "section-core-advantages" }}
|
|
{{ if .BasicInfo.CoreAdvantages }}
|
|
<section>
|
|
<h2 class="section-title"><i class="fas fa-star"></i> 核心优势</h2>
|
|
<div class="section-content">
|
|
<ul class="advantage-list">
|
|
{{ range .BasicInfo.CoreAdvantages }}
|
|
<li><i class="fas fa-check-circle"></i> {{.}}</li>
|
|
{{ end }}
|
|
</ul>
|
|
</div>
|
|
</section>
|
|
{{ end }}
|
|
{{ end }}
|
|
|
|
{{ define "section-experience" }}
|
|
{{ if .Experience }}
|
|
<section>
|
|
<h2 class="section-title"><i class="fas fa-briefcase"></i> 工作经验</h2>
|
|
<div class="section-content">
|
|
{{ range .Experience }}
|
|
<article class="item">
|
|
<div class="item-header">
|
|
<span class="company">{{.Company}}</span>
|
|
<span class="period">{{.StartDate}} - {{.EndDate}}</span>
|
|
</div>
|
|
<div class="position">{{.Position}}</div>
|
|
{{ if .Platforms }}
|
|
<div class="platforms">
|
|
{{ range .Platforms }}
|
|
<span class="platform-tag">{{.}}</span>
|
|
{{ end }}
|
|
</div>
|
|
{{ end }}
|
|
{{ if .Description }}
|
|
<div class="description">{{.Description}}</div>
|
|
{{ end }}
|
|
{{ if .Highlights }}
|
|
<ul class="highlights">
|
|
{{ range .Highlights }}
|
|
<li><i class="fas fa-circle"></i> {{.}}</li>
|
|
{{ end }}
|
|
</ul>
|
|
{{ end }}
|
|
</article>
|
|
{{ end }}
|
|
</div>
|
|
</section>
|
|
{{ end }}
|
|
{{ end }}
|
|
|
|
{{ define "section-education" }}
|
|
{{ if .Education }}
|
|
<section>
|
|
<h2 class="section-title"><i class="fas fa-graduation-cap"></i> 教育背景</h2>
|
|
<div class="section-content">
|
|
{{ range .Education }}
|
|
<article class="item">
|
|
<div class="item-header">
|
|
<span class="school">{{.School}}</span>
|
|
<span class="period">{{.StartDate}} - {{.EndDate}}</span>
|
|
</div>
|
|
<div class="degree">{{.Degree}} - {{.Major}}</div>
|
|
{{ if .GPA }}
|
|
<div class="gpa">GPA: {{.GPA}}</div>
|
|
{{ end }}
|
|
</article>
|
|
{{ end }}
|
|
</div>
|
|
</section>
|
|
{{ end }}
|
|
{{ end }}
|
|
|
|
{{ define "section-skills" }}
|
|
{{ if .Skills }}
|
|
<section>
|
|
<h2 class="section-title"><i class="fas fa-code"></i> 技术栈</h2>
|
|
<div class="section-content">
|
|
{{ $categories := dict }}
|
|
{{ range .Skills }}
|
|
{{ $cat := .Category }}
|
|
{{ if not (index $categories $cat) }}
|
|
{{ $_ := set $categories $cat slice }}
|
|
{{ end }}
|
|
{{ $categories = merge $categories (dict $cat (append (index $categories $cat) .)) }}
|
|
{{ end }}
|
|
|
|
<div class="skill-section">
|
|
{{ range $cat, $skills := $categories }}
|
|
<div class="skill-category">
|
|
<div class="skill-category-title">{{ $cat }}</div>
|
|
<div class="skill-items">
|
|
{{ range $skills }}
|
|
<span class="skill-tag">{{ .Name }}</span>
|
|
{{ end }}
|
|
</div>
|
|
</div>
|
|
{{ end }}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
{{ end }}
|
|
{{ end }}
|
|
|
|
{{ define "section-skills-progress" }}
|
|
{{ if .Skills }}
|
|
<section>
|
|
<h2 class="section-title"><i class="fas fa-code"></i> 技术栈</h2>
|
|
<div class="section-content">
|
|
{{ $levelMap := dict "beginner" "25%" "intermediate" "50%" "advanced" "75%" "expert" "100%" }}
|
|
{{ $levelText := dict "beginner" "入门" "intermediate" "中级" "advanced" "高级" "expert" "专家" }}
|
|
|
|
{{ $categories := dict }}
|
|
{{ range .Skills }}
|
|
{{ $cat := .Category }}
|
|
{{ if not (index $categories $cat) }}
|
|
{{ $_ := set $categories $cat slice }}
|
|
{{ end }}
|
|
{{ $categories = merge $categories (dict $cat (append (index $categories $cat) .)) }}
|
|
{{ end }}
|
|
|
|
<div class="skill-section">
|
|
{{ range $cat, $skills := $categories }}
|
|
<div class="skill-category">
|
|
<div class="skill-category-title">{{ $cat }}</div>
|
|
{{ range $skills }}
|
|
{{ $width := index $levelMap .Level }}
|
|
{{ if not $width }}{{ $width = "50%" }}{{ end }}
|
|
{{ $text := index $levelText .Level }}
|
|
{{ if not $text }}{{ $text = .Level }}{{ end }}
|
|
<div class="skill-progress-item">
|
|
<div class="skill-progress-header">
|
|
<span class="skill-progress-name">{{ .Name }}</span>
|
|
<span class="skill-progress-level">{{ $text }}</span>
|
|
</div>
|
|
<div class="skill-progress-bar">
|
|
<div class="skill-progress-fill" style="width: {{ $width }}"></div>
|
|
</div>
|
|
</div>
|
|
{{ end }}
|
|
</div>
|
|
{{ end }}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
{{ end }}
|
|
{{ end }}
|
|
|
|
{{ define "section-projects" }}
|
|
{{ if .Projects }}
|
|
<section>
|
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.12rem;">
|
|
<h2 class="section-title"><i class="fas fa-project-diagram"></i> 项目经验</h2>
|
|
<a href="/portfolio" style="color: #3498db; text-decoration: none; font-size: 0.9em; display: flex; align-items: center; gap: 0.31rem; transition: all 0.3s ease;">
|
|
<i class="fas fa-folder-open"></i> 查看作品集
|
|
</a>
|
|
</div>
|
|
<div class="section-content">
|
|
{{ range $index, $project := .Projects }}
|
|
<article class="item">
|
|
<div class="item-header">
|
|
<span class="project-name">{{$project.Name}}</span>
|
|
<span class="period">{{$project.StartDate}} - {{$project.EndDate}}</span>
|
|
</div>
|
|
{{ if $project.URL }}
|
|
<a href="{{$project.URL}}" target="_blank"><i class="fas fa-external-link-alt"></i> 查看项目</a>
|
|
{{ end }}
|
|
{{ if $project.Description }}
|
|
<div class="description">{{$project.Description}}</div>
|
|
{{ end }}
|
|
{{ if $project.TechStack }}
|
|
<div class="tech-stack">
|
|
{{ range $project.TechStack }}
|
|
<span class="tech-tag">{{.}}</span>
|
|
{{ end }}
|
|
</div>
|
|
{{ end }}
|
|
{{ if $project.Highlights }}
|
|
<ul class="highlights">
|
|
{{ range $project.Highlights }}
|
|
<li><i class="fas fa-star"></i> {{.}}</li>
|
|
{{ end }}
|
|
</ul>
|
|
{{ end }}
|
|
{{ if $project.Achievements }}
|
|
<div class="achievements">
|
|
<h4><i class="fas fa-trophy"></i> 项目成果</h4>
|
|
<ul>
|
|
{{ range $project.Achievements }}
|
|
<li><i class="fas fa-award"></i> {{.}}</li>
|
|
{{ end }}
|
|
</ul>
|
|
</div>
|
|
{{ end }}
|
|
</article>
|
|
{{ end }}
|
|
</div>
|
|
</section>
|
|
{{ end }}
|
|
{{ end }}
|
|
|
|
{{ define "section-summary" }}
|
|
{{ if .BasicInfo.Summary }}
|
|
<section>
|
|
<h2 class="section-title"><i class="fas fa-user"></i> 个人简介</h2>
|
|
<div class="section-content">
|
|
<div class="sidebar-summary">{{.BasicInfo.Summary}}</div>
|
|
</div>
|
|
</section>
|
|
{{ end }}
|
|
{{ end }}
|
|
|
|
{{ define "theme-switch" }}
|
|
<style>
|
|
.theme-switch {
|
|
position: fixed !important;
|
|
top: 1.25rem !important;
|
|
right: 1.25rem !important;
|
|
z-index: 2147483647 !important;
|
|
pointer-events: auto !important;
|
|
}
|
|
.theme-switch * {
|
|
pointer-events: auto !important;
|
|
}
|
|
.theme-switch button {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
|
|
color: white !important;
|
|
border: none !important;
|
|
padding: 0.62rem 1.12rem !important;
|
|
border-radius: 1.56rem !important;
|
|
cursor: pointer !important;
|
|
font-size: 0.81rem !important;
|
|
font-weight: 500 !important;
|
|
display: flex !important;
|
|
align-items: center !important;
|
|
gap: 0.50rem !important;
|
|
box-shadow: 0 0.25rem 0.94rem rgba(102, 126, 234, 0.4) !important;
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
|
white-space: nowrap !important;
|
|
line-height: 1.4 !important;
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
|
|
pointer-events: auto !important;
|
|
opacity: 1 !important;
|
|
visibility: visible !important;
|
|
}
|
|
.theme-switch button:hover {
|
|
transform: translateY(-0.12rem) !important;
|
|
box-shadow: 0 0.38rem 1.25rem rgba(102, 126, 234, 0.5) !important;
|
|
background: linear-gradient(135deg, #7c8eea 0%, #8b5bb3 100%) !important;
|
|
}
|
|
.theme-switch button:active {
|
|
transform: translateY(0) !important;
|
|
}
|
|
.theme-switch button i {
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
}
|
|
@media print {
|
|
.theme-switch { display: none !important; }
|
|
}
|
|
@media (max-width: 40rem) {
|
|
.theme-switch {
|
|
top: 0.75rem !important;
|
|
right: 0.75rem !important;
|
|
}
|
|
.theme-switch button {
|
|
padding: 0.50rem 0.88rem !important;
|
|
font-size: 0.75rem !important;
|
|
}
|
|
.theme-switch button span {
|
|
display: none !important;
|
|
}
|
|
}
|
|
</style>
|
|
<div class="theme-switch">
|
|
<button id="themeToggle" type="button" title="点击切换简历主题" onclick="(function(){
|
|
var themes = ['tech', 'modern', 'classic', 'minimalist', 'fresh', 'professional', 'sidebar', 'elegant'];
|
|
var themeNames = {'tech':'科技极客','modern':'现代商务','classic':'传统经典','minimalist':'极简风格','fresh':'清新自然','professional':'专业商务','sidebar':'经典侧边栏','elegant':'优雅轻奢'};
|
|
var currentTheme = '{{.Template}}';
|
|
var currentRoute = '{{.Route}}' || 'default';
|
|
var idx = themes.indexOf(currentTheme);
|
|
if (idx < 0) idx = 0;
|
|
var nextIdx = (idx + 1) % themes.length;
|
|
var newTheme = themes[nextIdx];
|
|
var btn = document.getElementById('themeToggle');
|
|
if (btn) { btn.style.opacity = '0.6'; btn.style.pointerEvents = 'none'; }
|
|
var toast = document.createElement('div');
|
|
toast.style.cssText = 'position:fixed;top:1.25rem;left:50%;transform:translateX(-50%);padding:0.75rem 1.50rem;border-radius:0.50rem;color:white;font-size:0.88rem;z-index:2147483647;box-shadow:0 0.25rem 0.75rem rgba(0,0,0,0.15);transition:opacity 0.3s;';
|
|
fetch('/api/resume?route=' + encodeURIComponent(currentRoute), {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ template: newTheme })
|
|
}).then(function(resp) {
|
|
if (resp.ok) {
|
|
toast.style.background = '#10b981';
|
|
toast.textContent = '切换到' + themeNames[newTheme] + '主题';
|
|
document.body.appendChild(toast);
|
|
setTimeout(function(){ toast.style.opacity = '0'; }, 1500);
|
|
setTimeout(function(){ window.location.reload(); }, 500);
|
|
} else {
|
|
toast.style.background = '#ef4444';
|
|
toast.textContent = '切换失败';
|
|
document.body.appendChild(toast);
|
|
setTimeout(function(){ toast.style.opacity = '0'; }, 2000);
|
|
if (btn) { btn.style.opacity = '1'; btn.style.pointerEvents = 'auto'; }
|
|
}
|
|
}).catch(function(err) {
|
|
toast.style.background = '#ef4444';
|
|
toast.textContent = '切换失败,请检查网络';
|
|
document.body.appendChild(toast);
|
|
setTimeout(function(){ toast.style.opacity = '0'; }, 2000);
|
|
if (btn) { btn.style.opacity = '1'; btn.style.pointerEvents = 'auto'; }
|
|
});
|
|
})()">
|
|
<i class="fas fa-palette"></i>
|
|
<span>切换主题</span>
|
|
</button>
|
|
</div>
|
|
{{ end }}
|
|
|
|
{{ define "theme-script" }}
|
|
<script>
|
|
(function() {
|
|
const themes = ['tech', 'modern', 'classic', 'minimalist', 'fresh', 'professional', 'sidebar', 'elegant'];
|
|
const themeNames = {
|
|
'tech': '科技极客',
|
|
'modern': '现代商务',
|
|
'classic': '传统经典',
|
|
'minimalist': '极简风格',
|
|
'fresh': '清新自然',
|
|
'professional': '专业商务',
|
|
'sidebar': '经典侧边栏',
|
|
'elegant': '优雅轻奢'
|
|
};
|
|
const currentTheme = '{{.Template}}';
|
|
const currentRoute = '{{.Route}}';
|
|
let currentThemeIndex = themes.indexOf(currentTheme) !== -1 ? themes.indexOf(currentTheme) : 0;
|
|
let isSwitching = false;
|
|
|
|
function showToast(message, type) {
|
|
const toast = document.createElement('div');
|
|
toast.style.cssText = 'position:fixed;top:1.25rem;left:50%;transform:translateX(-50%);padding:0.75rem 1.50rem;border-radius:0.50rem;color:white;font-size:0.88rem;z-index:2147483647;box-shadow:0 0.25rem 0.75rem rgba(0,0,0,0.15);transition:opacity 0.3s;';
|
|
if (type === 'success') toast.style.background = '#10b981';
|
|
else if (type === 'error') toast.style.background = '#ef4444';
|
|
else toast.style.background = '#3b82f6';
|
|
toast.textContent = message;
|
|
document.body.appendChild(toast);
|
|
setTimeout(function() { toast.style.opacity = '0'; }, 2000);
|
|
setTimeout(function() { toast.remove(); }, 2500);
|
|
}
|
|
|
|
async function switchTheme() {
|
|
if (isSwitching) return;
|
|
isSwitching = true;
|
|
|
|
const nextIndex = (currentThemeIndex + 1) % themes.length;
|
|
const newTheme = themes[nextIndex];
|
|
const btn = document.getElementById('themeToggle');
|
|
|
|
btn.style.opacity = '0.6';
|
|
btn.style.pointerEvents = 'none';
|
|
|
|
try {
|
|
const response = await fetch('/api/resume?route=' + encodeURIComponent(currentRoute || 'default'), {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ template: newTheme })
|
|
});
|
|
|
|
if (response.ok) {
|
|
showToast('切换到' + themeNames[newTheme] + '主题', 'success');
|
|
currentThemeIndex = nextIndex;
|
|
setTimeout(function() { window.location.reload(); }, 500);
|
|
} else {
|
|
const data = await response.json().catch(function() { return {}; });
|
|
showToast('切换失败:' + (data.message || '未知错误'), 'error');
|
|
btn.style.opacity = '1';
|
|
btn.style.pointerEvents = 'auto';
|
|
isSwitching = false;
|
|
}
|
|
} catch (err) {
|
|
console.error('Failed to switch theme:', err);
|
|
showToast('切换失败,请检查网络连接', 'error');
|
|
btn.style.opacity = '1';
|
|
btn.style.pointerEvents = 'auto';
|
|
isSwitching = false;
|
|
}
|
|
}
|
|
|
|
window.__switchTheme = switchTheme;
|
|
|
|
const toggleBtn = document.getElementById('themeToggle');
|
|
if (toggleBtn) {
|
|
toggleBtn.addEventListener('click', switchTheme);
|
|
}
|
|
})();
|
|
</script>
|
|
{{ end }} |