CareerBot/app/static/js/main.js
ln0422 96997daed0 Initial commit: CareerBot full-stack career showcase with AI chatbot
- 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>
2026-04-07 20:36:38 +08:00

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');
}
}