- FastAPI backend with SQLAlchemy ORM and SQLite - AI chatbot with OpenAI-compatible LLM integration (SSE streaming) - Admin panel for content management, LLM config, token management - Anonymous access with 3-question limit, token-based access control - Recruiter intent detection with admin notification - Resume generator (JD-based, Markdown to Word export) - Chinese localized public interface Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
136 lines
5.1 KiB
JavaScript
136 lines
5.1 KiB
JavaScript
// ── Main Page JavaScript ──
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
loadProfile();
|
|
loadSkills();
|
|
loadEducation();
|
|
loadExperience();
|
|
});
|
|
|
|
async function apiFetch(url) {
|
|
const resp = await fetch(url);
|
|
if (resp.status === 401) {
|
|
window.location.href = '/login';
|
|
return null;
|
|
}
|
|
return resp.json();
|
|
}
|
|
|
|
async function loadProfile() {
|
|
const data = await apiFetch('/api/profile');
|
|
if (!data || !data.name) return;
|
|
|
|
document.getElementById('profile-name').textContent = data.name;
|
|
|
|
const photoEl = document.getElementById('profile-photo');
|
|
if (data.photo_url) {
|
|
photoEl.innerHTML = `<img src="${data.photo_url}" alt="${data.name}">`;
|
|
} else {
|
|
photoEl.textContent = data.name.charAt(0);
|
|
}
|
|
|
|
const items = [
|
|
{ label: '学历', value: data.education_level },
|
|
{ label: '所在地', value: data.location },
|
|
{ label: '邮箱', value: data.email },
|
|
{ label: '电话', value: data.phone },
|
|
];
|
|
const grid = document.getElementById('profile-info-grid');
|
|
grid.innerHTML = items.filter(i => i.value).map(i =>
|
|
`<div class="info-item">${i.label}: <span>${i.value}</span></div>`
|
|
).join('');
|
|
|
|
const summaryEl = document.getElementById('summary-text');
|
|
if (data.self_summary) {
|
|
summaryEl.textContent = data.self_summary;
|
|
}
|
|
}
|
|
|
|
async function loadSkills() {
|
|
const data = await apiFetch('/api/skills');
|
|
if (!data || !data.length) return;
|
|
|
|
const container = document.getElementById('skills-container');
|
|
container.innerHTML = data.map(s => `
|
|
<div class="skill-card">
|
|
<div class="skill-category">${s.category}</div>
|
|
<div class="skill-content">${s.content}</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
async function loadEducation() {
|
|
const data = await apiFetch('/api/education');
|
|
if (!data || !data.length) return;
|
|
|
|
const container = document.getElementById('education-container');
|
|
container.innerHTML = data.map(e => `
|
|
<div class="accordion-item">
|
|
<div class="accordion-header" onclick="toggleAccordion(this)">
|
|
<div class="header-info">
|
|
<div class="time-range">${e.start_date} - ${e.end_date}</div>
|
|
<div class="main-info">${e.school}</div>
|
|
<div class="sub-info">${e.major} | ${e.degree}</div>
|
|
</div>
|
|
<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<polyline points="6 9 12 15 18 9"></polyline>
|
|
</svg>
|
|
</div>
|
|
<div class="accordion-content">
|
|
<div class="accordion-body">
|
|
${e.details ? `<div class="detail-section"><div class="detail-text">${e.details}</div></div>` : '<div class="detail-section"><div class="detail-text" style="color:var(--text-light)">暂无详细信息</div></div>'}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
async function loadExperience() {
|
|
const data = await apiFetch('/api/experience');
|
|
if (!data || !data.length) return;
|
|
|
|
const container = document.getElementById('experience-container');
|
|
container.innerHTML = data.map(exp => {
|
|
let details = '';
|
|
if (exp.company_intro) {
|
|
details += `<div class="detail-section"><div class="detail-label">公司简介</div><div class="detail-text">${exp.company_intro}</div></div>`;
|
|
}
|
|
if (exp.responsibilities) {
|
|
details += `<div class="detail-section"><div class="detail-label">工作职责</div><div class="detail-text">${exp.responsibilities}</div></div>`;
|
|
}
|
|
if (exp.achievements) {
|
|
details += `<div class="detail-section"><div class="detail-label">工作成就</div><div class="detail-text">${exp.achievements}</div></div>`;
|
|
}
|
|
return `
|
|
<div class="accordion-item">
|
|
<div class="accordion-header" onclick="toggleAccordion(this)">
|
|
<div class="header-info">
|
|
<div class="time-range">${exp.start_date} - ${exp.end_date}</div>
|
|
<div class="main-info">${exp.company}</div>
|
|
<div class="sub-info">${exp.position}</div>
|
|
</div>
|
|
<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<polyline points="6 9 12 15 18 9"></polyline>
|
|
</svg>
|
|
</div>
|
|
<div class="accordion-content">
|
|
<div class="accordion-body">${details || '<div class="detail-section"><div class="detail-text" style="color:var(--text-light)">暂无详细信息</div></div>'}</div>
|
|
</div>
|
|
</div>`;
|
|
}).join('');
|
|
}
|
|
|
|
function toggleAccordion(header) {
|
|
const item = header.parentElement;
|
|
const content = item.querySelector('.accordion-content');
|
|
const isActive = item.classList.contains('active');
|
|
|
|
if (isActive) {
|
|
content.style.maxHeight = null;
|
|
item.classList.remove('active');
|
|
} else {
|
|
content.style.maxHeight = content.scrollHeight + 'px';
|
|
item.classList.add('active');
|
|
}
|
|
}
|