超级简历模板内容下载静态页
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>简历数据源精准解析工具 (完美适配一页)</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
<style>
* {
box-sizing: border-box;
}
body {
font-family: 'Microsoft YaHei', sans-serif;
margin: 0;
padding: 20px;
background-color: #f0f2f5;
display: flex;
gap: 20px;
height: 100vh;
overflow: hidden;
}
.editor-panel {
width: 420px;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
overflow-y: auto;
flex-shrink: 0;
}
.editor-panel h2 {
margin-top: 0;
color: #333;
font-size: 18px;
border-bottom: 2px solid #1a5fb4;
padding-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
.parse-section {
background: #f8f9fa;
border: 1px dashed #adb5bd;
border-radius: 6px;
padding: 15px;
margin-bottom: 20px;
}
.parse-section h3 {
margin: 0 0 10px 0;
font-size: 15px;
color: #495057;
display: flex;
align-items: center;
gap: 6px;
}
.parse-section textarea {
width: 100%;
height: 120px;
padding: 8px;
border: 1px solid #ced4da;
border-radius: 4px;
font-size: 12px;
resize: vertical;
margin-bottom: 10px;
font-family: monospace;
}
.btn-parse {
width: 100%;
background-color: #28a745;
color: white;
border: none;
padding: 8px;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
transition: 0.2s;
}
.btn-parse:hover {
background-color: #218838;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
font-size: 13px;
color: #666;
margin-bottom: 5px;
font-weight: bold;
}
.form-group input,
.form-group textarea {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 13px;
}
.form-group textarea {
height: 80px;
resize: vertical;
}
.btn-group {
display: flex;
flex-direction: column;
gap: 10px;
margin-top: 20px;
padding-bottom: 20px;
}
button {
padding: 10px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: 0.2s;
}
.btn-primary {
background-color: #1a5fb4;
color: white;
}
.btn-primary:hover {
background-color: #134a8e;
}
.btn-success {
background-color: #28a745;
color: white;
}
.btn-success:hover {
background-color: #218838;
}
.btn-warning {
background-color: #ffc107;
color: #333;
}
.btn-danger {
background-color: #dc3545;
color: white;
font-size: 12px;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
border: none;
}
.btn-danger:hover {
background-color: #c82333;
}
.preview-panel {
flex: 1;
display: flex;
flex-direction: column;
overflow-y: auto;
align-items: center;
padding-bottom: 40px;
}
.toolbar {
position: sticky;
top: 0;
z-index: 10;
margin-bottom: 20px;
display: flex;
gap: 10px;
background: white;
padding: 10px 20px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
/* 🔥 关键修改:移除 overflow:hidden,允许内容自然撑开以获取真实高度 */
#resume-page {
width: 210mm;
min-height: 297mm;
background: white;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.15);
padding: 15mm 20mm;
color: #333;
transition: transform 0.3s ease;
flex-shrink: 0;
transform-origin: top center; /* 缩放锚点设为顶部居中 */
}
/* 模板样式 */
.template-classic .header {
border-bottom: 2px solid #333;
padding-bottom: 15px;
margin-bottom: 20px;
}
.template-classic .name {
font-size: 26px;
font-weight: bold;
margin-bottom: 8px;
}
.template-classic .contact {
font-size: 13px;
color: #555;
display: flex;
flex-wrap: wrap;
gap: 15px;
}
.template-classic .section {
margin-bottom: 18px;
}
.template-classic .section-title {
font-size: 17px;
font-weight: bold;
border-bottom: 1px solid #ddd;
padding-bottom: 5px;
margin-bottom: 12px;
}
.template-classic .item-desc {
font-size: 12px;
line-height: 1.6;
text-align: justify;
white-space: pre-wrap;
}
</style>
</head>
<body>
<div class="editor-panel">
<h2>
📝 简历信息填写
<button class="btn-danger" onclick="clearCache()" title="清空所有已保存的数据">🗑️ 清空缓存</button>
</h2>
<div class="parse-section">
<h3>📥 精准解析 WonderCV HTML 源码</h3>
<textarea id="raw-data-input" placeholder="在此粘贴完整的 WonderCV HTML 源码..."></textarea>
<button class="btn-parse" onclick="parseWonderCV()">🔍 一键精准解析</button>
</div>
<div class="form-group"><label>姓名</label><input type="text" id="input-name" value="" /></div>
<div class="form-group"><label>电话</label><input type="text" id="input-phone" value="" /></div>
<div class="form-group"><label>邮箱</label><input type="text" id="input-email" value="" /></div>
<div class="form-group"><label>求职意向</label><input type="text" id="input-target" value="" /></div>
<div class="form-group"><label>城市 / 生日 / 状态</label><input type="text" id="input-other" value="" /></div>
<div class="form-group"><label>专业技能 (换行分隔)</label><textarea id="input-skills"></textarea></div>
<div class="form-group">
<label>工作经历 (换行分隔每段经历)</label><textarea id="input-work" style="height: 150px"></textarea>
</div>
<div class="form-group"><label>教育经历</label><textarea id="input-edu"></textarea></div>
<div class="btn-group">
<button class="btn-warning" onclick="smartFillOnePage()">✨ 智能适配一页</button>
<button class="btn-primary" onclick="resetScale()">↩️ 重置缩放</button>
<button class="btn-success" onclick="exportPDF()">⬇️ 导出 PDF</button>
</div>
</div>
<div class="preview-panel">
<div class="toolbar">
<span style="align-self: center; font-weight: bold">切换模板:</span>
<button class="btn-primary" onclick="switchTemplate('classic')">经典简约</button>
</div>
<div id="resume-page" class="template-classic"></div>
</div>
<script>
let currentTemplate = 'classic'
const CACHE_KEY = 'wondercv_resume_data_cache'
// A4 纸在 96dpi 下的像素高度约为 1123px,减去上下 padding (15mm*2 ≈ 113px)
const A4_CONTENT_HEIGHT_PX = 1010
// ====================== 缓存管理模块 ======================
function saveToCache() {
const data = getData()
localStorage.setItem(CACHE_KEY, JSON.stringify(data))
}
function loadFromCache() {
const cached = localStorage.getItem(CACHE_KEY)
if (cached) {
try {
const data = JSON.parse(cached)
document.getElementById('input-name').value = data.name || ''
document.getElementById('input-phone').value = data.phone || ''
document.getElementById('input-email').value = data.email || ''
document.getElementById('input-target').value = data.target || ''
document.getElementById('input-other').value = data.other || ''
document.getElementById('input-skills').value = data.skills || ''
document.getElementById('input-work').value = data.work || ''
document.getElementById('input-edu').value = data.edu || ''
} catch (e) {
console.error('缓存数据损坏', e)
}
}
}
function clearCache() {
if (confirm('确定要清空所有已缓存的简历数据吗?')) {
localStorage.removeItem(CACHE_KEY)
document.querySelectorAll('.editor-panel input, .editor-panel textarea').forEach((el) => (el.value = ''))
resetScale()
renderResume()
alert('🗑️ 缓存已清空!')
}
}
// ====================== 核心:预处理 + 深度适配 WonderCV DOM ======================
function parseWonderCV() {
let rawHtml = document.getElementById('raw-data-input').value.trim()
if (!rawHtml) {
alert('❌ 请先粘贴 HTML 源码!')
return
}
try {
rawHtml = rawHtml
.replace(/\\n/g, '\n')
.replace(/\\t/g, '\t')
.replace(/\\x3C!--.*?-->/g, '')
.replace(/ /g, ' ')
const parser = new DOMParser()
const doc = parser.parseFromString(rawHtml, 'text/html')
const cleanText = (str) => (str ? str.replace(/\s+/g, ' ').trim() : '')
const name = cleanText(doc.querySelector('.user-name')?.textContent)
let phone = '',
email = '',
target = ''
const otherArr = []
doc.querySelectorAll('.profile-item span').forEach((span) => {
const label = span.querySelector('.attr-name')?.textContent || ''
const text = cleanText(span.textContent.replace(label, ''))
if (label.includes('电话')) phone = text
else if (label.includes('邮箱')) email = text
else if (label.includes('求职意向')) target = text
else if (label.includes('现居城市') || label.includes('生日') || label.includes('当前状态')) {
if (text) otherArr.push(text)
}
})
const other = otherArr.join(' | ')
const skillP = doc.querySelector('#skill_list p')
const skills = skillP
? skillP.innerHTML
.replace(/<br\s*\/?>/gi, '\n')
.replace(/<[^>]+>/g, '')
.trim()
: ''
const workLines = []
doc.querySelectorAll('#works > .info-content > .menu').forEach((block) => {
const title1 = block.querySelector('.menu-title:nth-child(1)')
const title2 = block.querySelector('.menu-title:nth-child(2)')
const company = cleanText(title1?.querySelector('.font_w_b')?.textContent)
const time = cleanText(title1?.querySelector('.right')?.textContent)
const position = cleanText(title2?.querySelector('.left')?.textContent)
const contentP = block.querySelector('.menu-content p, .menu-content')
const content = contentP
? contentP.innerHTML
.replace(/<br\s*\/?>/gi, '\n')
.replace(/<\/p><p>/gi, '\n')
.replace(/<[^>]+>/g, '')
.trim()
: ''
if (company) workLines.push(`${company} | ${position} | ${time}\n${content}`)
})
const work = workLines.join('\n\n')
const eduLines = []
doc.querySelectorAll('#edus > .info-content > .menu').forEach((block) => {
const title1 = block.querySelector('.menu-title:nth-child(1)')
const title2 = block.querySelector('.menu-title:nth-child(2)')
const school = cleanText(title1?.querySelector('.font_w_b')?.textContent)
const time = cleanText(title1?.querySelector('.right')?.textContent)
const majorInfo = cleanText(title2?.querySelector('.left')?.textContent)
if (school) eduLines.push(`${school} | ${majorInfo} | ${time}`)
})
const edu = eduLines.join('\n')
document.getElementById('input-name').value = name
document.getElementById('input-phone').value = phone
document.getElementById('input-email').value = email
document.getElementById('input-target').value = target
document.getElementById('input-other').value = other
document.getElementById('input-skills').value = skills
document.getElementById('input-work').value = work
document.getElementById('input-edu').value = edu
saveToCache()
renderResume()
alert(`✅ 解析成功并已自动保存!\n已提取: ${name} | ${phone}`)
} catch (e) {
console.error(e)
alert(`❌ 解析失败: ${e.message}`)
}
}
// ====================== 🔥 核心修复:整体缩放适配一页 ======================
function smartFillOnePage() {
// 先重置缩放,获取简历内容的真实自然高度
const page = document.getElementById('resume-page')
page.style.transform = 'scale(1)'
// 强制浏览器重排,确保获取到最新的 scrollHeight
void page.offsetHeight
const actualHeight = page.scrollHeight
if (actualHeight <= 10) {
alert('⚠️ 简历内容为空,请先填写或解析数据!')
return
}
// 计算缩放比例:目标高度 / 实际高度
// 0.98 是为了留出微小的安全边距,防止导出 PDF 时被裁切
let scale = (A4_CONTENT_HEIGHT_PX / actualHeight) * 0.98
// 限制最大缩放比例为 1.2,避免字数极少时字体过大导致排版失真
if (scale > 1.2) scale = 1.2
// 限制最小缩放比例为 0.5,避免字数极多时字体过小无法阅读
if (scale < 0.5) {
alert('⚠️ 内容严重超出一页,已按最小可读比例(0.5)缩放。\n建议精简工作经历或项目描述!')
scale = 0.5
}
page.style.transform = `scale(${scale})`
console.log(`📏 实际高度: ${actualHeight}px, 应用缩放: ${scale.toFixed(3)}`)
}
function resetScale() {
document.getElementById('resume-page').style.transform = 'scale(1)'
}
// ====================== 渲染与导出逻辑 ======================
function getData() {
return {
name: document.getElementById('input-name').value,
phone: document.getElementById('input-phone').value,
email: document.getElementById('input-email').value,
target: document.getElementById('input-target').value,
other: document.getElementById('input-other').value,
skills: document.getElementById('input-skills').value,
work: document.getElementById('input-work').value,
edu: document.getElementById('input-edu').value,
}
}
function renderResume() {
const data = getData()
const page = document.getElementById('resume-page')
page.className = `template-${currentTemplate}`
page.innerHTML = `
<div class="header">
<div class="name">${data.name}</div>
<div class="contact">
<span>📞 ${data.phone}</span>
<span>📧 ${data.email}</span>
<span>🎯 ${data.target}</span>
<span>📍 ${data.other}</span>
</div>
</div>
<div class="section"><div class="section-title">专业技能</div><div class="item-desc">${data.skills}</div></div>
<div class="section"><div class="section-title">工作经历</div><div class="item-desc">${data.work}</div></div>
<div class="section"><div class="section-title">教育经历</div><div class="item-desc">${data.edu}</div></div>
`
}
function switchTemplate(template) {
currentTemplate = template
resetScale() // 切换模板时重置缩放,避免残留
renderResume()
}
function exportPDF() {
// 导出前暂时重置缩放,让 html2pdf 按照原始尺寸渲染,保证清晰度
const page = document.getElementById('resume-page')
const currentTransform = page.style.transform
page.style.transform = 'scale(1)'
const element = document.getElementById('resume-page')
html2pdf()
.set({
margin: 0,
filename: `${getData().name || '简历'}_简历.pdf`,
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 2, useCORS: true },
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' },
})
.from(element)
.save()
.then(() => {
// 导出完成后恢复预览区的缩放状态
page.style.transform = currentTransform
})
}
// ====================== 初始化与事件绑定 ======================
document.querySelectorAll('.editor-panel input, .editor-panel textarea').forEach((el) => {
el.addEventListener('input', () => {
saveToCache()
renderResume()
})
})
window.addEventListener('DOMContentLoaded', () => {
loadFromCache()
renderResume()
})
</script>
</body>
</html>
有什么不同见解可以在评论区共同讨论

浙公网安备 33010602011771号