/* ================================================================================== BlueCV Pro — 旗舰级简历系统(豪华升级版) 1) 模板库:34 款(22 款经典旗舰全部保留并豪华升级 + 12 款全新甄选豪华升级) 2) 版式引擎:side / top / classic + 全新 hero / stripe / split / magazine / frame 3) 导出引擎:真实 OOXML(.docx / .xlsx),样式、内容、版面与预览严格一致 并保留原有 html2canvas 图片快照导出作为「图片快照」模式 4) 豪华升级:渐变深度、层叠阴影、精装装饰、黄金比例字距、双色系点缀 ================================================================================== */ // ==================== 旗舰级模板配置库(豪华升级版) ==================== // 配置属性:layout, primary(主色), bg(背景色), text(主字色), font, secStyle, skillStyle, itemStyle, accent // 新增豪华属性:accent2(二级点缀色), titleDeco(标题装饰), cardRadius(卡片圆角), premium(豪华阴影) const templates = [ /* ---------- 经典旗舰 22 款(完整保留 + 豪华升级) ---------- */ { id: 't1', group:'经典旗舰', name: '卓越总监 (藏青鎏金)', layout: 'side', nameInSide: true, bg: '#ffffff', text: '#1e293b', primary: '#0c1e3d', sideBg: 'linear-gradient(180deg, #0c1e3d 0%, #16284a 50%, #0c1e3d 100%)', sideText: '#f8fafc', font: 'Georgia, serif', secStyle: 'left-border', skillStyle: 'bar', itemStyle: 'dot-line', accent: '#c9a227', accent2: '#e6c558', titleDeco: 'gold-bar', cardRadius: 12, premium: true }, { id: 't2', group:'经典旗舰', name: '金融领袖 (铂金商务)', layout: 'top', topBg: 'linear-gradient(135deg, #0a1854 0%, #1e3a8a 50%, #2553a8 100%)', topText: '#ffffff', bg: '#ffffff', text: '#1e293b', primary: '#0a1854', font: 'sans-serif', secStyle: 'underline', skillStyle: 'tag', itemStyle: 'simple', accent: '#c0c9d4', accent2: '#e8edf3', titleDeco: 'silver-line', cardRadius: 10, premium: true }, { id: 't3', group:'经典旗舰', name: '投行精英 (极简雅致)', layout: 'classic', headerAlign: 'left', headerBorder: 'none', bg: '#ffffff', text: '#334155', primary: '#1a3a6b', font: 'sans-serif', secStyle: 'center-line', skillStyle: 'text', itemStyle: 'simple', accent: '#2a5caa', accent2: '#7ba3d9', titleDeco: 'thin-underline', cardRadius: 8, premium: false }, { id: 't4', group:'经典旗舰', name: '科技CFO (暗黑未来)', layout: 'side', nameInSide: true, bg: '#030712', text: '#cbd5e1', primary: '#1e40af', sideBg: 'linear-gradient(180deg, #030712 0%, #0f172a 40%, #030712 100%)', sideText: '#f1f5f9', font: 'sans-serif', secStyle: 'background', skillStyle: 'dot', itemStyle: 'card', accent: '#3b82f6', accent2: '#60a5fa', titleDeco: 'glow-line', cardRadius: 14, premium: true }, { id: 't5', group:'经典旗舰', name: '跨国高管 (海外风尚)', layout: 'top', topBg: 'linear-gradient(135deg, #0c2461 0%, #1e40af 40%, #2563eb 70%, #3b82f6 100%)', topText: '#ffffff', topRadius: '0 0 48px 0', bg: '#ffffff', text: '#1e293b', primary: '#0c2461', font: 'sans-serif', secStyle: 'left-border', skillStyle: 'tag', itemStyle: 'card', accent: '#d4a017', accent2: '#f0c445', titleDeco: 'gold-accent', cardRadius: 12, premium: true }, { id: 't6', group:'经典旗舰', name: '权势行政 (右侧金蓝)', layout: 'side', sideRight: true, nameInSide: false, bg: '#f8fafc', text: '#1e293b', primary: '#0f1c3f', sideBg: 'linear-gradient(180deg, #e2e8f0 0%, #cbd5e1 50%, #e2e8f0 100%)', sideText: '#0f1c3f', font: 'Georgia, serif', secStyle: 'underline', skillStyle: 'bar', itemStyle: 'simple', accent: '#1a3a6b', accent2: '#c9a227', titleDeco: 'double-line', cardRadius: 10, premium: false }, { id: 't7', group:'经典旗舰', name: '现代咨询 (几何框线)', layout: 'classic', headerAlign: 'center', headerBorder: '3px solid #1d4ed8', bg: '#ffffff', text: '#1e293b', primary: '#1d4ed8', font: 'sans-serif', secStyle: 'background', skillStyle: 'tag', itemStyle: 'card', accent: '#2563eb', accent2: '#60a5fa', titleDeco: 'boxed', cardRadius: 10, premium: true }, { id: 't8', group:'经典旗舰', name: '稳如泰山 (厚重底边)', layout: 'top', topBg: 'linear-gradient(135deg, #0a1854 0%, #1e3a8a 100%)', topText: '#ffffff', topBorderBottom: '6px solid #d4a017', bg: '#ffffff', text: '#1e293b', primary: '#0a1854', font: 'sans-serif', secStyle: 'left-border', skillStyle: 'bar', itemStyle: 'simple', accent: '#d4a017', accent2: '#f0c445', titleDeco: 'gold-base', cardRadius: 8, premium: true }, { id: 't9', group:'经典旗舰', name: '学术权威 (衬线经典)', layout: 'classic', headerAlign: 'center', headerBorder: '2px solid #475569', bg: '#ffffff', text: '#0f172a', primary: '#0f172a', font: 'Times New Roman, serif', secStyle: 'center-line', skillStyle: 'text', itemStyle: 'simple', accent: '#334155', accent2: '#64748b', titleDeco: 'serif-double', cardRadius: 6, premium: false }, { id: 't10', group:'经典旗舰', name: '合伙人级 (浅蓝侧栏)', layout: 'side', nameInSide: false, bg: '#ffffff', text: '#1e293b', primary: '#075985', sideBg: 'linear-gradient(180deg, #f0f9ff 0%, #e0f2fe 50%, #f0f9ff 100%)', sideText: '#082f49', font: 'sans-serif', secStyle: 'underline', skillStyle: 'bar', itemStyle: 'dot-line', accent: '#0284c7', accent2: '#38bdf8', titleDeco: 'ocean-line', cardRadius: 10, premium: true }, { id: 't11', group:'经典旗舰', name: '硬核极客 (代码终端)', layout: 'top', topBg: 'linear-gradient(135deg, #020617 0%, #0f172a 100%)', topText: '#38bdf8', bg: '#ffffff', text: '#1e293b', primary: '#0369a1', font: 'monospace', secStyle: 'left-border', skillStyle: 'tag', itemStyle: 'simple', accent: '#0ea5e9', accent2: '#7dd3fc', titleDeco: 'terminal', cardRadius: 8, premium: false }, { id: 't12', group:'经典旗舰', name: '优雅律动 (渐变侧栏)', layout: 'side', nameInSide: true, bg: '#ffffff', text: '#1e293b', primary: '#1e3a8a', sideBg: 'linear-gradient(180deg, #0c1e3d 0%, #1e3a8a 40%, #3b82f6 80%, #60a5fa 100%)', sideText: '#ffffff', font: 'sans-serif', secStyle: 'underline', skillStyle: 'dot', itemStyle: 'simple', accent: '#93c5fd', accent2: '#dbeafe', titleDeco: 'gradient-flow', cardRadius: 10, premium: true }, { id: 't13', group:'经典旗舰', name: '气度非凡 (居中大气)', layout: 'classic', headerAlign: 'center', headerBorder: 'none', bg: '#ffffff', text: '#0c1e3d', primary: '#0c1e3d', font: 'Georgia, serif', secStyle: 'underline', skillStyle: 'tag', itemStyle: 'dot-line', accent: '#c9a227', accent2: '#e6c558', titleDeco: 'gold-center', cardRadius: 10, premium: true }, { id: 't14', group:'经典旗舰', name: '大道至简 (无框极简)', layout: 'classic', headerAlign: 'left', headerBorder: '1px dashed #94a3b8', bg: '#fefefe', text: '#334155', primary: '#1e40af', font: 'sans-serif', secStyle: 'none', skillStyle: 'text', itemStyle: 'simple', accent: '#3b82f6', accent2: '#93c5fd', titleDeco: 'minimal', cardRadius: 6, premium: false }, { id: 't15', group:'经典旗舰', name: '锐意进取 (异形切割)', layout: 'top', topBg: 'linear-gradient(135deg, #1d4ed8 0%, #2563eb 100%)', topText: '#ffffff', topClip: 'polygon(0 0, 100% 0, 100% 82%, 0 100%)', bg: '#ffffff', text: '#1e293b', primary: '#1d4ed8', font: 'sans-serif', secStyle: 'left-border', skillStyle: 'bar', itemStyle: 'card', accent: '#60a5fa', accent2: '#93c5fd', titleDeco: 'clip-angle', cardRadius: 12, premium: true }, { id: 't16', group:'经典旗舰', name: '经典流线 (细腻线条)', layout: 'side', nameInSide: true, bg: '#ffffff', text: '#1e293b', primary: '#0369a1', sideBg: '#ffffff', sideText: '#0f172a', sideBorder: '1px solid #e2e8f0', font: 'sans-serif', secStyle: 'underline', skillStyle: 'bar', itemStyle: 'dot-line', accent: '#0284c7', accent2: '#38bdf8', titleDeco: 'hair-line', cardRadius: 8, premium: false }, { id: 't17', group:'经典旗舰', name: '质感暗夜 (午夜幽蓝)', layout: 'classic', headerAlign: 'center', headerBorder: '2px solid #38bdf8', bg: '#0c0f1a', text: '#f8fafc', primary: '#38bdf8', font: 'sans-serif', secStyle: 'center-line', skillStyle: 'dot', itemStyle: 'card', accent: '#7dd3fc', accent2: '#bae6fd', titleDeco: 'neon-glow', cardRadius: 14, premium: true }, { id: 't18', group:'经典旗舰', name: '极速青蓝 (交汇渐变)', layout: 'top', topBg: 'linear-gradient(90deg, #0e7490 0%, #06b6d4 30%, #3b82f6 70%, #1d4ed8 100%)', topText: '#ffffff', bg: '#ffffff', text: '#1e293b', primary: '#0e7490', font: 'sans-serif', secStyle: 'background', skillStyle: 'tag', itemStyle: 'simple', accent: '#0ea5e9', accent2: '#67e8f9', titleDeco: 'wave-gradient', cardRadius: 10, premium: true }, { id: 't19', group:'经典旗舰', name: '领航时代 (沉稳粗边)', layout: 'classic', headerAlign: 'left', headerBorder: '5px solid #0a1854', bg: '#ffffff', text: '#1e293b', primary: '#0a1854', font: 'sans-serif', secStyle: 'background', skillStyle: 'text', itemStyle: 'dot-line', accent: '#1d4ed8', accent2: '#3b82f6', titleDeco: 'thick-bar', cardRadius: 8, premium: true }, { id: 't20', group:'经典旗舰', name: '纯粹本质 (蓝点点缀)', layout: 'classic', headerAlign: 'center', headerBorder: 'none', bg: '#ffffff', text: '#1e293b', primary: '#1e40af', font: 'sans-serif', secStyle: 'left-border', skillStyle: 'dot', itemStyle: 'simple', accent: '#3b82f6', accent2: '#60a5fa', titleDeco: 'dot-accent', cardRadius: 6, premium: false }, { id: 't21', group:'经典旗舰', name: '璀璨星河 (星空暗纹)', layout: 'side', nameInSide: true, bg: '#ffffff', text: '#1e293b', primary: '#0c0f1a', sideBg: 'radial-gradient(circle at 70% 20%, #1e3a8a 0%, #0c0f1a 40%, #020617 100%)', sideText: '#e0e7ff', font: 'sans-serif', secStyle: 'underline', skillStyle: 'tag', itemStyle: 'dot-line', accent: '#fbbf24', accent2: '#fde68a', titleDeco: 'star-gold', cardRadius: 12, premium: true }, { id: 't22', group:'经典旗舰', name: '深海巨鲸 (睿智深渊)', layout: 'top', topBg: 'linear-gradient(135deg, #020617 0%, #0a192f 50%, #0d2137 100%)', topText: '#64ffda', bg: '#ffffff', text: '#0a192f', primary: '#0056b3', font: 'sans-serif', secStyle: 'underline', skillStyle: 'bar', itemStyle: 'simple', accent: '#64ffda', accent2: '#a7f3d0', titleDeco: 'deep-sea', cardRadius: 10, premium: true }, /* ---------- 全新甄选 12 款(豪华升级,含 5 种全新版式引擎) ---------- */ { id: 't23', group:'全新甄选', name: '尊享黑金 (中央礼帽)', layout: 'hero', topBg: 'linear-gradient(135deg, #030712 0%, #111827 40%, #1f2937 100%)', topText: '#ffffff', topBorderBottom: '4px solid #c9a227', bg: '#fffdf8', text: '#1f2937', primary: '#111827', accent: '#c9a227', accent2: '#e6c558', font: 'Georgia, serif', secStyle: 'center-line', skillStyle: 'bar', itemStyle: 'simple', titleDeco: 'royal-gold', cardRadius: 10, premium: true }, { id: 't24', group:'全新甄选', name: '晨曦蓝调 (左侧色带)', layout: 'stripe', stripeBg: 'linear-gradient(180deg, #1e3a8a 0%, #2563eb 40%, #3b82f6 70%, #38bdf8 100%)', stripeWidth: 22, bg: '#ffffff', text: '#1e293b', primary: '#1e3a8a', accent: '#0ea5e9', accent2: '#7dd3fc', font: 'sans-serif', secStyle: 'left-border', skillStyle: 'bar', itemStyle: 'dot-line', titleDeco: 'stripe-flow', cardRadius: 10, premium: true }, { id: 't25', group:'全新甄选', name: '双生对称 (分栏头图)', layout: 'split', splitLeftBg: 'linear-gradient(135deg, #0a1f4c 0%, #0f2c5c 100%)', splitLeftText: '#ffffff', splitRightBg: 'linear-gradient(135deg, #eef2f7 0%, #f8fafc 100%)', splitRightText: '#0f2c5c', bg: '#ffffff', text: '#1e293b', primary: '#0f2c5c', accent: '#2f6fed', accent2: '#5b8def', font: 'sans-serif', secStyle: 'underline', skillStyle: 'tag', itemStyle: 'simple', titleDeco: 'dual-tone', cardRadius: 10, premium: true }, { id: 't26', group:'全新甄选', name: '杂志风范 (左栏叙事)', layout: 'magazine', topBg: 'linear-gradient(90deg, #0a1854 0%, #1e3a8a 100%)', topText: '#ffffff', sideBg: 'linear-gradient(180deg, #f8fafc 0%, #f1f5f9 50%, #f8fafc 100%)', sideText: '#0f172a', bg: '#ffffff', text: '#1e293b', primary: '#0a1854', accent: '#3b82f6', accent2: '#60a5fa', font: 'sans-serif', secStyle: 'left-border', skillStyle: 'bar', itemStyle: 'simple', titleDeco: 'editorial', cardRadius: 8, premium: true }, { id: 't27', group:'全新甄选', name: '典藏边框 (雅致内框)', layout: 'frame', frameBorder: '3px solid #14385f', bg: '#fffdf5', text: '#26313d', primary: '#14385f', accent: '#b8860b', accent2: '#daa520', font: 'Georgia, serif', secStyle: 'center-line', skillStyle: 'text', itemStyle: 'simple', titleDeco: 'museum-frame', cardRadius: 6, premium: true }, { id: 't28', group:'全新甄选', name: '青瓷雅韵 (青灰质感)', layout: 'side', nameInSide: true, bg: '#ffffff', text: '#25343b', primary: '#1a4754', sideBg: 'linear-gradient(180deg, #1a4754 0%, #20505c 50%, #1a4754 100%)', sideText: '#e8f4f6', font: 'sans-serif', secStyle: 'underline', skillStyle: 'dot', itemStyle: 'dot-line', accent: '#5eb1bf', accent2: '#8fd4de', titleDeco: 'celadon', cardRadius: 10, premium: true }, { id: 't29', group:'全新甄选', name: '曜石银灰 (冷峻高级)', layout: 'top', topBg: 'linear-gradient(135deg, #1c2428 0%, #37474f 50%, #455a64 100%)', topText: '#eceff1', bg: '#ffffff', text: '#263238', primary: '#37474f', accent: '#90a4ae', accent2: '#cfd8dc', font: 'sans-serif', secStyle: 'background', skillStyle: 'bar', itemStyle: 'card', titleDeco: 'graphite', cardRadius: 12, premium: true }, { id: 't30', group:'全新甄选', name: '朱砂点睛 (沉稳红金)', layout: 'classic', headerAlign: 'left', headerBorder: '4px solid #8c1c13', bg: '#fffafa', text: '#2b2b2b', primary: '#8c1c13', accent: '#c8963e', accent2: '#e0b860', font: 'Georgia, serif', secStyle: 'left-border', skillStyle: 'tag', itemStyle: 'simple', titleDeco: 'vermilion', cardRadius: 8, premium: true }, { id: 't31', group:'全新甄选', name: '翡翠资本 (墨绿沉稳)', layout: 'hero', topBg: 'linear-gradient(135deg, #022c22 0%, #064e3b 40%, #065f46 80%, #047857 100%)', topText: '#ecfdf5', bg: '#ffffff', text: '#1f2937', primary: '#065f46', accent: '#10b981', accent2: '#34d399', font: 'sans-serif', secStyle: 'underline', skillStyle: 'bar', itemStyle: 'card', titleDeco: 'emerald', cardRadius: 12, premium: true }, { id: 't32', group:'全新甄选', name: '钛金锋芒 (石墨渐层)', layout: 'magazine', topBg: 'linear-gradient(90deg, #030712 0%, #1f2937 50%, #374151 100%)', topText: '#f9fafb', sideBg: 'linear-gradient(180deg, #030712 0%, #111827 50%, #1f2937 100%)', sideText: '#e5e7eb', bg: '#ffffff', text: '#1f2937', primary: '#111827', accent: '#f59e0b', accent2: '#fbbf24', font: 'sans-serif', secStyle: 'background', skillStyle: 'dot', itemStyle: 'simple', titleDeco: 'titanium', cardRadius: 10, premium: true }, { id: 't33', group:'全新甄选', name: '云端简约 (轻盈留白)', layout: 'stripe', stripeBg: 'linear-gradient(180deg, #cbd5e1 0%, #e2e8f0 50%, #f1f5f9 100%)', stripeWidth: 16, bg: '#ffffff', text: '#334155', primary: '#0f172a', accent: '#64748b', accent2: '#94a3b8', font: 'sans-serif', secStyle: 'none', skillStyle: 'text', itemStyle: 'simple', titleDeco: 'cloud-minimal', cardRadius: 6, premium: false }, { id: 't34', group:'全新甄选', name: '皇家紫韵 (深紫典藏)', layout: 'split', splitLeftBg: 'linear-gradient(135deg, #2e1065 0%, #3b0764 50%, #4c1d95 100%)', splitLeftText: '#f5f3ff', splitRightBg: 'linear-gradient(135deg, #f5f3ff 0%, #ede9fe 100%)', splitRightText: '#3b0764', bg: '#ffffff', text: '#2e1065', primary: '#5b21b6', accent: '#a78bfa', accent2: '#c4b5fd', font: 'sans-serif', secStyle: 'left-border', skillStyle: 'bar', itemStyle: 'card', titleDeco: 'royal-purple', cardRadius: 12, premium: true } ]; // ==================== 颜色工具(预览与导出共用) ==================== function hex6(v, fb) { fb = fb || 'FFFFFF'; if (!v) return fb; var s = String(v).trim(); var found = s.match(/#([0-9a-fA-F]{3,8})/g); if (found && found.length) { if (found.length === 1) return normHex(found[0]); return mixHex(normHex(found[0]), normHex(found[found.length - 1]), 0.5); // 渐变取中间色 } var named = { white:'FFFFFF', black:'000000', transparent: fb, none: fb }; if (named[s.toLowerCase()]) return named[s.toLowerCase()]; var rgb = s.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i); if (rgb) return [1,2,3].map(function(i){ return ('0'+parseInt(rgb[i],10).toString(16)).slice(-2); }).join('').toUpperCase(); return fb; } function normHex(h) { h = String(h).replace('#', ''); if (h.length === 3) h = h.split('').map(function(x){ return x + x; }).join(''); if (h.length > 6) h = h.slice(0, 6); while (h.length < 6) h += '0'; return h.toUpperCase(); } function mixHex(a, b, t) { a = normHex(a); b = normHex(b); var out = ''; for (var i = 0; i < 3; i++) { var av = parseInt(a.substr(i * 2, 2), 16), bv = parseInt(b.substr(i * 2, 2), 16); var v = Math.round(av + (bv - av) * t); out += ('0' + Math.max(0, Math.min(255, v)).toString(16)).slice(-2); } return out.toUpperCase(); } /** fg 以 alpha 透明度压在 bg 上的等效实色 */ function fadeHex(fg, bg, alpha) { return mixHex(hex6(bg, 'FFFFFF'), hex6(fg, '000000'), alpha); } function lum(h) { h = normHex(h); var r = parseInt(h.substr(0,2),16)/255, g = parseInt(h.substr(2,2),16)/255, b = parseInt(h.substr(4,2),16)/255; return 0.2126*r + 0.7152*g + 0.0722*b; } function bestOn(h) { return lum(h) > 0.58 ? '111827' : 'FFFFFF'; } /** 若 accent 与背景对比过低则退回 fallback,避免"深色底 + 深色字"不可见 */ function safeAccent(accent, bg, fallback) { var a = hex6(accent, fallback), b = hex6(bg, 'FFFFFF'), f = hex6(fallback, '000000'); return Math.abs(lum(a) - lum(b)) < 0.20 ? f : a; } /** 渐变背景的安全纯色提取(用于深色背景上文字对比度计算) */ function gradientBase(raw, fb) { if (!raw) return fb || 'FFFFFF'; var s = String(raw); var found = s.match(/#[0-9a-fA-F]{3,8}/g); if (found && found.length) return normHex(found[0]); return hex6(s, fb); } // ==================== 数据状态 (默认:财务总监 CFO) ==================== let state = { basic: {}, summary: '', education: [], work: [], projects: [], skills: [] }; let activeTab = 'basic'; let currentTpl = 't1'; let exportMode = 'native'; function tpl() { return templates.find(function(t){ return t.id === currentTpl; }) || templates[0]; } function toast(msg, ms) { var el = document.getElementById('toast'); el.textContent = msg; el.classList.add('show'); clearTimeout(el._t); el._t = setTimeout(function(){ el.classList.remove('show'); }, ms || 2600); } // 初始化函数 function init() { const select = document.getElementById('tplSelect'); const groups = {}; templates.forEach(function(t){ (groups[t.group || '模板'] = groups[t.group || '模板'] || []).push(t); }); select.innerHTML = Object.keys(groups).map(function(g){ return '' + groups[g].map(function(t){ return ''; }).join('') + ''; }).join(''); select.value = currentTpl; select.onchange = (e) => { currentTpl = e.target.value; updatePreview(); }; const em = document.getElementById('exportMode'); em.value = exportMode; em.onchange = (e) => { exportMode = e.target.value; }; document.querySelectorAll('.tab').forEach(btn => { btn.onclick = () => { document.querySelectorAll('.tab').forEach(b => b.classList.remove('active')); btn.classList.add('active'); activeTab = btn.dataset.tab; renderEditor(); }; }); window.addEventListener('resize', autoScale); loadSample(); // 载入财务总监数据 } // 渲染左侧编辑器表单 function renderEditor() { const container = document.getElementById('editorContent'); const b = state.basic || (state.basic = {}); const v = (x) => (x == null ? '' : String(x)); let html = ''; if (activeTab === 'basic') { html = `
${b.avatar ? `` : `
无照片
`}
${b.avatar ? `` : ''}
`; } else if (activeTab === 'work') { state.work.forEach((item, i) => { html += `
`; }); html += ``; } else if (activeTab === 'projects') { state.projects.forEach((item, i) => { html += `
`; }); html += ``; } else if (activeTab === 'edu') { state.education.forEach((item, i) => { html += `
`; }); html += ``; } else if (activeTab === 'skills') { html += `
请输入高管核心专业技能与战略视野 (熟练度推荐 3-5)。
`; state.skills.forEach((item, i) => { html += `
`; }); html += ``; } container.innerHTML = html; } // 交互响应 function handleInput(path, value) { const parts = path.split('.'); let obj = state; for (let i = 0; i < parts.length - 1; i++) obj = obj[parts[i]]; obj[parts[parts.length - 1]] = value; updatePreview(); } function addItem(listName, defaultObj) { state[listName].push(defaultObj); renderEditor(); updatePreview(); } function removeItem(listName, index) { state[listName].splice(index, 1); renderEditor(); updatePreview(); } function clearData() { if(confirm('⚠️ 确定清空当前简历的所有内容?')) { state = { basic:{name:'',title:'',phone:'',email:'',location:'',wechat:'',avatar:''}, summary:'', education:[], work:[], projects:[], skills:[] }; renderEditor(); updatePreview(); } } // 默认财务总监级别案例 function loadSample() { state = { basic: { name: '张瀚文', title: '集团财务总监 (CFO) / 资本战略副总裁', phone: '139-9999-8888', email: 'hanwen.zhang@executive.com', location: '上海市 / 陆家嘴金融区', wechat: 'LinkedIn: Hanwen Zhang', avatar: '' }, summary: '逾18年大型跨国集团与行业龙头企业全面财务管理及资本运作经验。精通IPO统筹、跨境并购(M&A)、全球资金池搭建与税务合规。曾主导完成超5亿美元海外并购案及企业美股、港股双重上市筹备。具备宏观战略视野与百人以上全球化财务团队管理经验,致力于通过深刻的业财融合及财务数字化转型,强力驱动企业商业价值几何级增长。', work: [ { company: '鼎星控股集团 (市值千亿级上市企业)', title: '集团财务总监 (CFO)', date: '2019.05 - 至今', desc: '• 战略与资金池:全面负责集团战略财务规划,搭建全球资金集中管理平台,优化资产负债结构,每年为集团降低融资成本逾1.5亿元。\n• 业财数字化:主导财务共享服务中心(FSSC)及全链路数字化中台建设,实现全国40余家分子公司财务数据秒级穿透,整体运营合规效率提升60%。\n• 投资者关系:作为核心路演代表,对接百余家国际顶尖投行及PE/VC,成功完成两轮定增,累计募资超30亿人民币。' }, { company: '远洋国际实业发展有限公司', title: '财务副总裁 (VP of Finance)', date: '2014.07 - 2019.04', desc: '• 跨境并购:主导收购德国某行业隐形冠军企业(交易规模约5.2亿美元),统筹前期财务尽职调查、税务结构设计及投后100天全球财务与业务体系融合。\n• 利润优化:建立基于阿米巴模式的全价值链预算风控体系,使公司连续三年净利润复合增长率(CAGR)保持在25%以上。' }, { company: '普华永道 (PwC)', title: '高级审计经理', date: '2006.07 - 2014.06', desc: '• 领导30人专项团队,为多家世界500强企业及拟上市公司提供复杂年度审计、内控合规(SOX)及尽职调查咨询服务。\n• 成功护航4家高科技及高端制造企业于纳斯达克及香港联交所主板上市。' } ], projects: [ { name: '集团主体港交所IPO及全球发售', role: '上市筹备组常务副组长 / 财务总责', date: '2021.03 - 2022.08', desc: '统筹保荐人、审计师、律师等中介机构,梳理过去三年历史财务数据及VIE架构搭建。历时17个月,克服宏观市场波动,成功登陆港交所,超额认购达15倍,募集资金25亿港元。' }, { name: '跨国供应链税务合规与合规重组', role: '项目总指挥', date: '2017.06 - 2018.12', desc: '针对欧美市场双反调查及BEPS合规要求,重组新加坡及欧洲关联交易定价模型,合法合规规避税务风险,整体有效税率(ETR)降低4.5%。' } ], education: [ { school: '中欧国际工商学院 (CEIBS)', major: '高级管理人员工商管理硕士 (EMBA)', degree: '硕士', date: '2016.09 - 2018.10', desc: '• 优秀毕业生,主攻企业战略与国际金融并购' }, { school: '上海财经大学', major: '会计学 / 金融工程', degree: '本科', date: '2002.09 - 2006.06', desc: '• 中国注册会计师 (CICPA)\n• 特许金融分析师 (CFA) 持证人\n• 英国特许公认会计师 (ACCA) 会员' } ], skills: [ { name: '企业IPO与资本运作', level: 5 }, { name: '投融资与跨境并购 (M&A)', level: 5 }, { name: '全面预算与风险控制', level: 5 }, { name: '国际财务报告准则 (IFRS)', level: 5 }, { name: '业财融合与数字化转型', level: 5 }, { name: '全球税务合规处理', level: 4 } ] }; renderEditor(); updatePreview(); } function triggerUpload() { const input = document.createElement('input'); input.type = 'file'; input.accept = 'image/*'; input.onchange = e => { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = ev => { state.basic.avatar = ev.target.result; updatePreview(); renderEditor(); } reader.readAsDataURL(file); } }; input.click(); } // 统一的联系方式列表(预览与导出共用,保证内容完全一致) function getContacts() { var arr = []; if (state.basic.phone) arr.push('📞 ' + state.basic.phone); if (state.basic.email) arr.push('✉️ ' + state.basic.email); if (state.basic.location) arr.push('🏢 ' + state.basic.location); if (state.basic.wechat) arr.push('🔗 ' + state.basic.wechat); return arr; } // ==================== 核心:多模板渲染引擎(豪华升级版) ==================== function updatePreview() { const page = document.getElementById('previewPage'); const c = tpl(); const e = (str) => str == null ? '' : String(str).replace(/[&<>"']/g, ch => ({'&':'&','<':'<','>':'>','"':'"',"'":"'"}[ch])); // 主区 / 侧栏 两套调色上下文(解决深色侧栏文字不可见的老问题) const mainCtx = { primary: c.primary, accent: c.accent || c.primary, accent2: c.accent2 || c.accent || c.primary, bg: c.bg, text: c.text }; const sideSolid = '#' + hex6(c.sideBg, 'F1F5F9'); const sideCtx = { primary: c.sideText || c.primary, accent: '#' + safeAccent(c.accent || c.primary, sideSolid, c.sideText || c.primary), accent2: '#' + safeAccent(c.accent2 || c.accent || c.primary, sideSolid, c.sideText || c.primary), bg: sideSolid, text: c.sideText || c.text }; // 豪华阴影预设 const premiumShadow = c.premium ? '0 8px 30px rgba(0,0,0,0.06), 0 2px 8px rgba(0,0,0,0.04)' : '0 2px 10px rgba(0,0,0,0.03)'; const cardRadius = c.cardRadius || 8; // 1. 生成技能区 (豪华渲染) const genSkills = (ctx) => { ctx = ctx || mainCtx; if(!state.skills.length) return ''; let html = ''; state.skills.forEach(sk => { let lvl = Math.max(1, Math.min(5, parseInt(sk.level) || 3)); if (c.skillStyle === 'bar') { // 豪华渐变进度条 html += `
${e(sk.name)}${'★'.repeat(lvl)}${'☆'.repeat(5-lvl)}
`; } else if (c.skillStyle === 'dot') { let dots = ''; for(let i=1; i<=5; i++) dots += ``; html += `
${e(sk.name)}
${dots}
`; } else if (c.skillStyle === 'tag') { html += `${e(sk.name)}`; } else { html += `
${e(sk.name)}
`; } }); return `
${genSecTitle('核心专业矩阵', ctx)}
${html}
`; }; // 2. 标题样式 (豪华升级) const genSecTitle = (title, ctx) => { ctx = ctx || mainCtx; let tColor = ctx.primary; let deco = c.titleDeco || ''; if (c.secStyle === 'left-border') return `
${e(title)}
`; if (c.secStyle === 'underline') return `
${e(title)}
`; if (c.secStyle === 'background') return `
${e(title)}
`; if (c.secStyle === 'center-line') return `
${e(title)}
`; return `
${e(title)}
`; }; // 3. 高质量列表项 (豪华升级) const genItem = (item, isEdu) => { const title = isEdu ? item.school : (item.company || item.name); const subtitle = isEdu ? ((item.major||'') + (item.degree ? ` · ${item.degree}` : '')) : (item.title || item.role); let containerStyle = 'margin-bottom:26px;position:relative;'; if (c.itemStyle === 'card') { containerStyle += `background:linear-gradient(135deg, ${c.text}06, ${c.text}03);padding:20px 22px;border-radius:${cardRadius}px;border-left:4px solid ${c.accent||c.primary};box-shadow:${premiumShadow};`; } else if (c.itemStyle === 'dot-line') { containerStyle += `padding-left:26px;border-left:2px solid ${c.primary}26;position:relative;`; } let before = c.itemStyle === 'dot-line' ? `
` : ''; if (c.itemStyle === 'card') { before = `
`; } return `
${before}
${e(title)} ${e(item.date)}
${subtitle ? `
${e(subtitle)}
` : ''} ${item.desc ? `
${e(item.desc)}
` : ''}
`; }; // 构建各主要区块 const eduHtml = state.education.length ? `
${genSecTitle('教育背景与资质')}
${state.education.map(x=>genItem(x,true)).join('')}
` : ''; const workHtml = state.work.length ? `
${genSecTitle('高管核心履历')}
${state.work.map(x=>genItem(x,false)).join('')}
` : ''; const projHtml = state.projects.length ? `
${genSecTitle('重大战略与资本项目')}
${state.projects.map(x=>genItem(x,false)).join('')}
` : ''; const sumHtml = state.summary ? `
${genSecTitle('高管执行摘要')}
${e(state.summary)}
` : ''; const contacts = getContacts(); const contactHtml = (arr, isSide) => arr.map(x => `
${e(x)}
`).join(''); const avatarOf = (size, borderColor) => state.basic.avatar ? `` : ''; let avatarHtml = avatarOf(112, (c.accent||c.primary) + '66'); let content = ''; // ========= 旗舰布局渲染 ========= if (c.layout === 'side') { const sideContent = `
${avatarHtml} ${c.nameInSide ? `

${e(state.basic.name)}

${e(state.basic.title)}
` : ''}
${contactHtml(contacts, true)}
${genSkills(sideCtx)} `; const mainContent = ` ${!c.nameInSide ? `

${e(state.basic.name)}

${e(state.basic.title)}
` : ''} ${sumHtml}${workHtml}${projHtml}${eduHtml} `; content = `
${!c.sideRight ? `
${sideContent}
` : ''}
${mainContent}
${c.sideRight ? `
${sideContent}
` : ''}
`; } else if (c.layout === 'top') { content = `

${e(state.basic.name)}

${e(state.basic.title)}
${contactHtml(contacts, false)}
${avatarHtml ? `
${avatarHtml}
` : ''}
${sumHtml}
${workHtml}${projHtml}${eduHtml}
${genSkills()}
`; } else if (c.layout === 'classic') { content = `
${c.headerBorder==='none'||!c.headerBorder?'
':''}

${e(state.basic.name)}

${e(state.basic.title)}
${contactHtml(contacts, false)}
${avatarHtml ? `
${avatarHtml}
` : ''}
${sumHtml}${workHtml}${projHtml}
${eduHtml}
${genSkills()}
`; } else if (c.layout === 'hero') { content = `
${avatarHtml ? `
${avatarOf(106, (c.accent||'#ffffff')+'99')}
` : ''}

${e(state.basic.name)}

${e(state.basic.title)}
${contactHtml(contacts, false)}
${sumHtml}${workHtml}${projHtml}
${eduHtml}
${genSkills()}
`; } else if (c.layout === 'stripe') { content = `

${e(state.basic.name)}

${e(state.basic.title)}
${contactHtml(contacts, false)}
${avatarHtml ? `
${avatarHtml}
` : ''}
${sumHtml}${workHtml}${projHtml}
${eduHtml}
${genSkills()}
`; } else if (c.layout === 'split') { content = `

${e(state.basic.name)}

${e(state.basic.title)}
${avatarHtml ? `
${avatarOf(90, (c.accent||c.primary)+'66')}
` : ''}
${contactHtml(contacts, true)}
${sumHtml}${workHtml}${projHtml}
${eduHtml}
${genSkills()}
`; } else if (c.layout === 'magazine') { content = `

${e(state.basic.name)}

${e(state.basic.title)}
${avatarHtml ? `
${avatarOf(82, (c.accent||'#ffffff')+'99')}
` : ''}
${contactHtml(contacts, true)}
${genSkills(sideCtx)}
${sumHtml}${workHtml}${projHtml}${eduHtml}
`; } else if (c.layout === 'frame') { content = `
${avatarHtml ? `
${avatarOf(94, (c.accent||c.primary)+'66')}
` : ''}

${e(state.basic.name)}

${e(state.basic.title)}
${contactHtml(contacts, false)}
${sumHtml}${workHtml}${projHtml}
${eduHtml}
${genSkills()}
`; } // 严格 A4 宽度包装,保证文本不换行错误 page.innerHTML = `
${content}
`; setTimeout(autoScale, 10); } function autoScale() { const panel = document.getElementById('previewPanel'), wrapper = document.getElementById('previewWrapper'); if(!panel || !wrapper) return; const scale = Math.min(1.2, Math.max(0.4, (panel.clientWidth - 100) / 794)); wrapper.style.transform = `scale(${scale})`; } /* ================================================================================== ★★★ 导出内核 A:通用工具(XML 转义 / Base64 / CRC32 / ZIP 打包 / 文件保存) ★★★ ================================================================================== */ function xmlEsc(s) { return String(s == null ? '' : s) .replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F]/g, '') .replace(/&/g, '&').replace(//g, '>') .replace(/"/g, '"').replace(/'/g, '''); } function strToU8(s) { return new TextEncoder().encode(s); } function b64ToU8(b64) { var bin = atob(b64), len = bin.length, u8 = new Uint8Array(len); for (var i = 0; i < len; i++) u8[i] = bin.charCodeAt(i); return u8; } function parseDataUrl(dataUrl) { if (!dataUrl || dataUrl.indexOf('data:') !== 0) return null; var m = /^data:([^;,]+)(;base64)?,(.*)$/s.exec(dataUrl); if (!m) return null; var mime = m[1].toLowerCase(); var bytes = m[2] ? b64ToU8(m[3]) : strToU8(decodeURIComponent(m[3])); var ext = mime.indexOf('png') >= 0 ? 'png' : (mime.indexOf('gif') >= 0 ? 'gif' : (mime.indexOf('bmp') >= 0 ? 'bmp' : 'jpeg')); return { mime: mime, ext: ext, bytes: bytes }; } var CRC_TABLE = (function () { var t = new Uint32Array(256); for (var n = 0; n < 256; n++) { var c = n; for (var k = 0; k < 8; k++) c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1); t[n] = c >>> 0; } return t; })(); function crc32(u8) { var c = 0xFFFFFFFF; for (var i = 0; i < u8.length; i++) c = CRC_TABLE[(c ^ u8[i]) & 0xFF] ^ (c >>> 8); return (c ^ 0xFFFFFFFF) >>> 0; } /** 极简 ZIP 打包器(Store 模式,Office 完全兼容) */ function makeZip(entries) { var enc = new TextEncoder(), parts = [], central = [], offset = 0; entries.forEach(function (e) { var nameBytes = enc.encode(e.name); var data = (e.data instanceof Uint8Array) ? e.data : strToU8(e.data); var crc = crc32(data); var lh = new Uint8Array(30 + nameBytes.length), dv = new DataView(lh.buffer); dv.setUint32(0, 0x04034b50, true); dv.setUint16(4, 20, true); dv.setUint16(6, 0x0800, true); dv.setUint16(8, 0, true); dv.setUint16(10, 0, true); dv.setUint16(12, 0x21, true); dv.setUint32(14, crc, true); dv.setUint32(18, data.length, true); dv.setUint32(22, data.length, true); dv.setUint16(26, nameBytes.length, true); dv.setUint16(28, 0, true); lh.set(nameBytes, 30); parts.push(lh, data); var ch = new Uint8Array(46 + nameBytes.length), cv = new DataView(ch.buffer); cv.setUint32(0, 0x02014b50, true); cv.setUint16(4, 20, true); cv.setUint16(6, 20, true); cv.setUint16(8, 0x0800, true); cv.setUint16(10, 0, true); cv.setUint16(12, 0, true); cv.setUint16(14, 0x21, true); cv.setUint32(16, crc, true); cv.setUint32(20, data.length, true); cv.setUint32(24, data.length, true); cv.setUint16(28, nameBytes.length, true); cv.setUint16(30, 0, true); cv.setUint16(32, 0, true); cv.setUint16(34, 0, true); cv.setUint16(36, 0, true); cv.setUint32(38, 0, true); cv.setUint32(42, offset, true); ch.set(nameBytes, 46); central.push(ch); offset += lh.length + data.length; }); var cdSize = central.reduce(function (a, b) { return a + b.length; }, 0); var eocd = new Uint8Array(22), ev = new DataView(eocd.buffer); ev.setUint32(0, 0x06054b50, true); ev.setUint16(4, 0, true); ev.setUint16(6, 0, true); ev.setUint16(8, entries.length, true); ev.setUint16(10, entries.length, true); ev.setUint32(12, cdSize, true); ev.setUint32(16, offset, true); ev.setUint16(20, 0, true); var total = offset + cdSize + 22, out = new Uint8Array(total), p = 0; parts.forEach(function (x) { out.set(x, p); p += x.length; }); central.forEach(function (x) { out.set(x, p); p += x.length; }); out.set(eocd, p); return out; } function saveBytes(bytes, filename, mime) { var blob = new Blob([bytes], { type: mime }); var url = URL.createObjectURL(blob); var a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); setTimeout(function () { URL.revokeObjectURL(url); }, 1500); } function safeName() { return String(state.basic.name || 'CFO简历').replace(/[\\/:*?"<>|]/g, '_'); } /* ================================================================================== ★★★ 导出内核 B:调色板(把预览里的透明度色彩折算为 Office 实色) ★★★ ================================================================================== */ function mainPalette(c) { var bg = hex6(c.bg, 'FFFFFF'), text = hex6(c.text, '1E293B'), primary = hex6(c.primary, '0F172A'); var accent = safeAccent(c.accent || c.primary, bg, primary); var accent2 = safeAccent(c.accent2 || c.accent || c.primary, bg, primary); return { bg: bg, text: text, primary: primary, accent: accent, accent2: accent2, soft: fadeHex(text, bg, 0.80), // 预览里的 text + CC body: fadeHex(text, bg, 0.70), // text + B3 muted: fadeHex(text, bg, 0.60), // text + 99 card: fadeHex(text, bg, 0.05), // text + 06 card2: fadeHex(text, bg, 0.03), // text + 03 (渐变第二色) track: fadeHex(primary, bg, 0.15),// primary + 22 line: fadeHex(primary, bg, 0.22),// primary + 33 tag: fadeHex(primary, bg, 0.08) // primary + 11 }; } function sidePalette(c) { var bg = hex6(c.sideBg, 'F1F5F9'), text = hex6(c.sideText, '0F172A'); var accent = safeAccent(c.accent || c.sideText, bg, text); var accent2 = safeAccent(c.accent2 || c.accent || c.sideText, bg, text); return { bg: bg, text: text, primary: text, accent: accent, accent2: accent2, soft: fadeHex(text, bg, 0.88), body: fadeHex(text, bg, 0.80), muted: fadeHex(text, bg, 0.70), card: fadeHex(text, bg, 0.06), card2: fadeHex(text, bg, 0.03), track: fadeHex(text, bg, 0.22), line: fadeHex(text, bg, 0.26), tag: fadeHex(text, bg, 0.10) }; } function bandPalette(bgRaw, textRaw, accentRaw) { var bg = hex6(bgRaw, '1E3A8A'), text = hex6(textRaw, 'FFFFFF'); var accent = safeAccent(accentRaw || textRaw, bg, text); var accent2 = safeAccent(accentRaw || textRaw, bg, text); return { bg: bg, text: text, primary: text, accent: accent, accent2: accent2, soft: fadeHex(text, bg, 0.92), body: fadeHex(text, bg, 0.85), muted: fadeHex(text, bg, 0.78), card: fadeHex(text, bg, 0.08), card2: fadeHex(text, bg, 0.04), track: fadeHex(text, bg, 0.25), line: fadeHex(text, bg, 0.3), tag: fadeHex(text, bg, 0.12) }; } function officeFont(c) { var f = String(c.font || '').toLowerCase(); if (f.indexOf('georgia') >= 0) return 'Georgia'; if (f.indexOf('times') >= 0) return 'Times New Roman'; if (f.indexOf('mono') >= 0) return 'Consolas'; return 'Microsoft YaHei'; } /* ================================================================================== ★★★ 导出内核 C:真实 .docx(WordprocessingML)生成器 ★★★ —— A4 零边距 / 表格版式还原 / 颜色字号一比一 / 头像圆形嵌入 / 文字可编辑 —— 豪华升级:渐变色带模拟 / 装饰线 / 双色点缀 / 精致间距 ================================================================================== */ var T = function (px) { return Math.round(px * 15); }; // px -> twips var HALFPT = function (px) { return Math.max(2, Math.round(px * 1.5)); }; // px -> half-point function wRun(text, o) { o = o || {}; var fnt = o.font || 'Microsoft YaHei'; var rPr = ''; rPr += ''; if (o.bold) rPr += ''; if (o.italic) rPr += ''; if (o.color) rPr += ''; if (o.spacing) rPr += ''; rPr += ''; if (o.underline) rPr += ''; if (o.shd) rPr += ''; rPr += ''; var parts = String(text == null ? '' : text).split('\n'), body = ''; for (var i = 0; i < parts.length; i++) { if (i) body += ''; body += '' + xmlEsc(parts[i]) + ''; } return '' + rPr + body + ''; } function wPara(runs, o) { o = o || {}; var pPr = ''; if (o.borderTop || o.borderLeft || o.borderBottom) { pPr += ''; if (o.borderTop) pPr += ''; if (o.borderLeft) pPr += ''; if (o.borderBottom) pPr += ''; pPr += ''; } if (o.shd) pPr += ''; if (o.tabs && o.tabs.length) { pPr += '' + o.tabs.map(function (t) { return ''; }).join('') + ''; } pPr += ''; if (o.indentLeft || o.indentRight) { pPr += ''; } if (o.align) pPr += ''; var content = Array.isArray(runs) ? runs.join('') : (runs || ''); return '' + pPr + '' + content + ''; } function wSpacer(px) { return ' '; } function wTable(rows, opt) { opt = opt || {}; var grid = opt.grid || [11906]; var x = ''; x += ''; if (opt.ind) x += ''; x += opt.borders || ''; x += ''; x += ''; x += ''; x += '' + grid.map(function (w) { return ''; }).join('') + ''; rows.forEach(function (r) { x += ''; if (r.height) x += ''; r.cells.forEach(function (cl) { x += ''; x += ''; if (cl.span) x += ''; if (cl.borders) x += cl.borders; if (cl.shd) x += ''; x += ''; if (cl.vAlign) x += ''; x += ''; x += (cl.content && cl.content.length ? cl.content : wPara('')); x += ''; }); x += ''; }); return x + ''; } function wImage(rid, px, align, id) { var emu = Math.round(px * 9525); return '' + (align ? '' : '') + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + ''; } /* ---------- docx 内容片段:区块标题 / 条目 / 技能 (豪华升级) ---------- */ function dxSecTitle(title, c, P, w, fnt) { var s = c.secStyle; if (s === 'background') { return wPara(wRun(' ' + title + ' ', { size: 14, bold: true, color: bestOn(P.primary), spacing: 30, shd: P.primary, font: fnt }), { before: 8, after: 14 }); } if (s === 'left-border') { // 豪华左边框 + 装饰点 return wPara(wRun(title, { size: 17, bold: true, color: P.primary, spacing: 30, font: fnt }), { borderLeft: { sz: 30, color: P.accent, space: 6 }, indentLeft: 170, before: 8, after: 14, line: 1.15 }); } if (s === 'underline') { // 豪华下划线 return wPara(wRun(title, { size: 17, bold: true, color: P.primary, spacing: 30, font: fnt }), { borderBottom: { sz: 14, color: P.accent, space: 5 }, before: 8, after: 14, line: 1.15 }); } if (s === 'center-line') { // 豪华居中线 return wPara(wRun(title, { size: 17, bold: true, color: P.primary, spacing: 30, font: fnt }), { align: 'center', borderBottom: { sz: 6, color: fadeHex(P.primary, P.bg, 0.4), space: 5 }, before: 8, after: 16, line: 1.15 }); } return wPara(wRun(title, { size: 17, bold: true, color: P.primary, spacing: 30, font: fnt }), { before: 8, after: 14, line: 1.15 }); } function dxItem(item, isEdu, c, P, cw, fnt) { var title = isEdu ? (item.school || '') : (item.company || item.name || ''); var subtitle = isEdu ? ((item.major || '') + (item.degree ? ' · ' + item.degree : '')) : (item.title || item.role || ''); var card = c.itemStyle === 'card', dotline = c.itemStyle === 'dot-line'; var shd = card ? P.card : null; var bl = card ? { sz: 24, color: P.accent, space: 7 } : (dotline ? { sz: 12, color: P.line, space: 7 } : null); var indL = card ? 230 : (dotline ? 260 : 0); var indR = card ? 200 : 0; var tabPos = Math.max(600, cw - indL - indR); var out = ''; out += wPara([ wRun(title, { size: 15.5, bold: true, color: P.primary, spacing: 8, font: fnt }), '', wRun(item.date || '', { size: 12, bold: true, color: P.muted, spacing: 15, font: 'Consolas', shd: P.tag }) ], { shd: shd, borderLeft: bl, indentLeft: indL, indentRight: indR, tabs: [{ val: 'right', pos: tabPos }], before: card ? 10 : 0, after: 3, line: 1.25 }); if (subtitle) { out += wPara([ wRun(' ', { size: 12, font: fnt, shd: P.accent }), wRun(' ' + subtitle, { size: 14, bold: true, color: P.soft, font: fnt }) ], { shd: shd, borderLeft: bl, indentLeft: indL, indentRight: indR, after: 4, line: 1.25 }); } if (item.desc) { out += wPara(wRun(item.desc, { size: 13, color: P.body, font: fnt }), { shd: shd, borderLeft: bl, indentLeft: indL, indentRight: indR, after: card ? 10 : 0, line: 1.35, align: 'left' }); } out += wSpacer(14); return out; } function dxSkills(c, P, w, fnt) { if (!state.skills.length) return ''; var s = dxSecTitle('核心专业矩阵', c, P, w, fnt), st = c.skillStyle; if (st === 'tag') { var runs = []; state.skills.forEach(function (sk) { runs.push(wRun(' ' + (sk.name || '') + ' ', { size: 12, bold: true, color: P.primary, shd: P.tag, font: fnt })); runs.push(wRun(' ', { size: 12, font: fnt })); }); s += wPara(runs, { after: 18, line: 1.4 }); } else if (st === 'text') { var r2 = []; state.skills.forEach(function (sk, i) { r2.push(wRun(sk.name || '', { size: 13, bold: true, color: P.primary, underline: P.accent, font: fnt })); if (i < state.skills.length - 1) r2.push(wRun(' ', { size: 13, font: fnt })); }); s += wPara(r2, { after: 18, line: 1.4 }); } else if (st === 'dot') { state.skills.forEach(function (sk) { var lv = Math.max(1, Math.min(5, parseInt(sk.level) || 3)); s += wPara([ wRun(sk.name || '', { size: 13, bold: true, color: P.primary, font: fnt }), '', wRun(new Array(lv + 1).join('●'), { size: 12, color: P.accent, font: fnt }), wRun(new Array(5 - lv + 1).join('●'), { size: 12, color: P.track, font: fnt }) ], { tabs: [{ val: 'right', pos: w }], after: 8, line: 1.35 }); }); s += wSpacer(10); } else { // bar state.skills.forEach(function (sk) { var lv = Math.max(1, Math.min(5, parseInt(sk.level) || 3)); s += wPara([ wRun(sk.name || '', { size: 12.5, bold: true, color: P.primary, font: fnt }), '', wRun(new Array(lv + 1).join('★'), { size: 11, color: P.accent, font: fnt }), wRun(new Array(5 - lv + 1).join('☆'), { size: 11, color: P.track, font: fnt }) ], { tabs: [{ val: 'right', pos: w }], after: 2, line: 1.2 }); var fill = Math.round(w * lv / 5), rest = w - fill, cells = []; if (rest < 60) { cells.push({ w: w, shd: P.accent, content: wSpacer(4) }); } else if (fill < 60) { cells.push({ w: w, shd: P.track, content: wSpacer(4) }); } else { cells.push({ w: fill, shd: P.accent, content: wSpacer(4) }); cells.push({ w: rest, shd: P.track, content: wSpacer(4) }); } s += wTable([{ cells: cells, height: 80 }], { grid: cells.map(function (x) { return x.w; }) }); s += wSpacer(11); }); } return s; } function dxHeadName(P, size, align, fnt, spacing) { return wPara(wRun(state.basic.name || '', { size: size, bold: true, color: P.primary, spacing: spacing == null ? 60 : spacing, font: fnt }), { align: align, after: 6, line: 1.15 }); } function dxHeadTitle(P, size, align, fnt) { return wPara(wRun(state.basic.title || '', { size: size, bold: true, color: P.accent, spacing: 30, font: fnt }), { align: align, after: 10, line: 1.3 }); } function dxContactsInline(P, align, fnt) { var arr = getContacts(); if (!arr.length) return ''; return wPara(wRun(arr.join(' '), { size: 12.5, color: P.muted, font: fnt }), { align: align, after: 4, line: 1.5 }); } function dxContactsBlock(P, fnt) { return getContacts().map(function (x) { return wPara(wRun(x, { size: 12, color: P.body, font: fnt }), { after: 8, line: 1.45 }); }).join(''); } /* ---------- docx 主构建 ---------- */ function buildDocxBytes() { var c = tpl(); var fnt = officeFont(c); var P = mainPalette(c), S = sidePalette(c); var FULL = 11906; // A4 宽 twips var img = parseDataUrl(state.basic.avatar); var IMG_RID = 'rId100'; var imgId = 1; function mainBody(w, opt) { opt = opt || {}; var s = ''; if (state.summary) { s += dxSecTitle('高管执行摘要', c, P, w, fnt); s += wPara(wRun(state.summary, { size: 13.5, color: P.soft, font: fnt }), { after: 24, line: 1.4, align: 'both', borderLeft: { sz: 18, color: fadeHex(P.accent, P.bg, 0.2), space: 8 }, indentLeft: 200 }); } if (state.work.length) { s += dxSecTitle('高管核心履历', c, P, w, fnt); s += state.work.map(function (x) { return dxItem(x, false, c, P, w, fnt); }).join(''); s += wSpacer(10); } if (state.projects.length) { s += dxSecTitle('重大战略与资本项目', c, P, w, fnt); s += state.projects.map(function (x) { return dxItem(x, false, c, P, w, fnt); }).join(''); s += wSpacer(10); } if (!opt.skipEdu && state.education.length) { s += dxSecTitle('教育背景与资质', c, P, w, fnt); s += state.education.map(function (x) { return dxItem(x, true, c, P, w, fnt); }).join(''); s += wSpacer(10); } return s; } function eduBlock(w) { if (!state.education.length) return ''; return dxSecTitle('教育背景与资质', c, P, w, fnt) + state.education.map(function (x) { return dxItem(x, true, c, P, w, fnt); }).join('') + wSpacer(8); } /** 底部双栏:左=教育,右=技能 */ function twoColFooter(totalW) { var gap = T(44), left = Math.round((totalW - gap) / 2), right = totalW - gap - left; var l = eduBlock(left), r = dxSkills(c, P, right, fnt); if (!l && !r) return ''; return wTable([{ cells: [ { w: left, content: l || wPara('') }, { w: gap, content: wPara('') }, { w: right, content: r || wPara('') } ] }], { grid: [left, gap, right] }) + wSpacer(4); } function sideColumn(w, showName) { var s = ''; if (img) { s += wImage(IMG_RID, 104, 'center', imgId++); s += wSpacer(12); } if (showName) { s += wPara(wRun(state.basic.name || '', { size: 27, bold: true, color: S.text, spacing: 45, font: fnt }), { align: 'center', after: 6, line: 1.15 }); s += wPara(wRun(state.basic.title || '', { size: 13, bold: true, color: S.accent, spacing: 15, font: fnt }), { align: 'center', after: 14, line: 1.35 }); } s += wSpacer(16); s += wPara('', { borderTop: { sz: 6, color: S.line, space: 1 }, after: 0, line: 0.2 }); s += wSpacer(12); s += dxContactsBlock(S, fnt); s += wSpacer(6); s += wPara('', { borderTop: { sz: 6, color: S.line, space: 1 }, after: 0, line: 0.2 }); s += wSpacer(16); s += dxSkills(c, S, w, fnt); return s; } var body = ''; /* ===== side ===== */ if (c.layout === 'side') { var sw = T(290), mw = FULL - sw; var sideCellW = sw - T(70), mainCellW = mw - T(100); var sideCell = { w: sw, shd: S.bg, ml: T(35), mr: T(35), mt: T(46), mb: T(40), content: sideColumn(sideCellW, !!c.nameInSide) }; var mainInner = ''; if (!c.nameInSide) { mainInner += dxHeadName(P, 32, 'left', fnt, 70); mainInner += dxHeadTitle(P, 17, 'left', fnt); mainInner += wPara('', { borderBottom: { sz: 18, color: P.primary, space: 1 }, after: 0, line: 0.2 }); mainInner += wSpacer(20); } mainInner += mainBody(mainCellW); var mainCell = { w: mw, shd: P.bg, ml: T(50), mr: T(50), mt: T(54), mb: T(40), content: mainInner }; body += wTable([{ cells: c.sideRight ? [mainCell, sideCell] : [sideCell, mainCell] }], { grid: c.sideRight ? [mw, sw] : [sw, mw] }); /* ===== top ===== */ } else if (c.layout === 'top') { var B = bandPalette(c.topBg, c.topText, c.accent); var hw = FULL - T(120); var head = ''; var headLeft = dxHeadName(B, 34, 'left', fnt, 70) + dxHeadTitle(B, 17, 'left', fnt) + dxContactsInline(B, 'left', fnt); if (img) { var lw = hw - T(140), rw = T(140); head = wTable([{ cells: [ { w: lw, content: headLeft, vAlign: 'center' }, { w: rw, content: wImage(IMG_RID, 104, 'right', imgId++), vAlign: 'center' } ] }], { grid: [lw, rw] }) + wSpacer(2); } else head = headLeft; var bodyW = FULL - T(120); var gapW = T(48), colL = Math.round((bodyW - gapW) * 0.7), colR = bodyW - gapW - colL; var inner = ''; if (state.summary) { inner += dxSecTitle('高管执行摘要', c, P, bodyW, fnt); inner += wPara(wRun(state.summary, { size: 13.5, color: P.soft, font: fnt }), { after: 24, line: 1.9, align: 'both', borderLeft: { sz: 18, color: fadeHex(P.accent, P.bg, 0.2), space: 8 }, indentLeft: 200 }); } var leftStack = ''; if (state.work.length) { leftStack += dxSecTitle('高管核心履历', c, P, colL, fnt) + state.work.map(function (x) { return dxItem(x, false, c, P, colL, fnt); }).join('') + wSpacer(8); } if (state.projects.length) { leftStack += dxSecTitle('重大战略与资本项目', c, P, colL, fnt) + state.projects.map(function (x) { return dxItem(x, false, c, P, colL, fnt); }).join('') + wSpacer(8); } if (state.education.length) { leftStack += dxSecTitle('教育背景与资质', c, P, colL, fnt) + state.education.map(function (x) { return dxItem(x, true, c, P, colL, fnt); }).join('') + wSpacer(8); } inner += wTable([{ cells: [ { w: colL, content: leftStack || wPara('') }, { w: gapW, content: wPara('') }, { w: colR, content: dxSkills(c, P, colR, fnt) || wPara('') } ] }], { grid: [colL, gapW, colR] }) + wSpacer(4); var topBorders = c.topBorderBottom ? null : ''; body += wTable([ { cells: [{ w: FULL, shd: B.bg, ml: T(60), mr: T(60), mt: T(46), mb: T(44), borders: topBorders, content: head }] }, { cells: [{ w: FULL, shd: P.bg, ml: T(60), mr: T(60), mt: T(44), mb: T(40), content: inner }] } ], { grid: [FULL] }); /* ===== classic ===== */ } else if (c.layout === 'classic') { var cw = FULL - T(140); var centerH = c.headerAlign === 'center'; var hdr = ''; var hStack = dxHeadName(P, 34, centerH ? 'center' : 'left', fnt, 80) + dxHeadTitle(P, 17, centerH ? 'center' : 'left', fnt) + dxContactsInline(P, centerH ? 'center' : 'left', fnt); if (img && !centerH) { var l2 = cw - T(140), r2w = T(140); hdr = wTable([{ cells: [ { w: l2, content: hStack, vAlign: 'center' }, { w: r2w, content: wImage(IMG_RID, 104, 'right', imgId++), vAlign: 'center' } ] }], { grid: [l2, r2w] }) + wSpacer(2); } else if (img && centerH) { hdr = wImage(IMG_RID, 100, 'center', imgId++) + wSpacer(12) + hStack; } else hdr = hStack; var hbColor = hex6((c.headerBorder || '').match(/#[0-9a-fA-F]{3,6}/) ? c.headerBorder.match(/#[0-9a-fA-F]{3,6}/)[0] : c.primary, P.primary); var hbSize = /(\d+)px/.exec(c.headerBorder || '') ? parseInt(/(\d+)px/.exec(c.headerBorder)[1], 10) : 0; if (hbSize > 0) hdr += wPara('', { borderBottom: { sz: Math.max(6, hbSize * 8), color: hbColor, space: 1 }, after: 0, line: 0.2 }); else hdr += wPara('', { borderBottom: { sz: 6, color: P.line, space: 1 }, after: 0, line: 0.2 }); hdr += wSpacer(26); var main = hdr; if (state.summary) { main += dxSecTitle('高管执行摘要', c, P, cw, fnt) + wPara(wRun(state.summary, { size: 13.5, color: P.soft, font: fnt }), { after: 24, line: 1.9, align: 'both', borderLeft: { sz: 18, color: fadeHex(P.accent, P.bg, 0.2), space: 8 }, indentLeft: 200 }); } if (state.work.length) { main += dxSecTitle('高管核心履历', c, P, cw, fnt) + state.work.map(function (x) { return dxItem(x, false, c, P, cw, fnt); }).join('') + wSpacer(8); } if (state.projects.length) { main += dxSecTitle('重大战略与资本项目', c, P, cw, fnt) + state.projects.map(function (x) { return dxItem(x, false, c, P, cw, fnt); }).join('') + wSpacer(8); } main += twoColFooter(cw); body += wTable([{ cells: [{ w: FULL, shd: P.bg, ml: T(70), mr: T(70), mt: T(54), mb: T(44), content: main }] }], { grid: [FULL] }); /* ===== hero (全新) ===== */ } else if (c.layout === 'hero') { var HB = bandPalette(c.topBg, c.topText, c.accent); var heroW = FULL - T(120); var hero = ''; if (img) hero += wImage(IMG_RID, 100, 'center', imgId++) + wSpacer(14); hero += wPara(wRun(state.basic.name || '', { size: 36, bold: true, color: HB.text, spacing: 120, font: fnt }), { align: 'center', after: 8, line: 1.15 }); hero += wPara(wRun(state.basic.title || '', { size: 15.5, bold: true, color: HB.accent, spacing: 60, font: fnt }), { align: 'center', after: 14, line: 1.3 }); hero += dxContactsInline(HB, 'center', fnt); var hb = c.topBorderBottom && /(\d+)px/.test(c.topBorderBottom) ? parseInt(/(\d+)px/.exec(c.topBorderBottom)[1], 10) : 0; var hbc = c.topBorderBottom && /#[0-9a-fA-F]{3,6}/.test(c.topBorderBottom) ? hex6(c.topBorderBottom.match(/#[0-9a-fA-F]{3,6}/)[0]) : HB.accent; var heroCellBorders = hb > 0 ? '' : ''; var bodyW2 = FULL - T(128); var m2 = mainBody(bodyW2, { skipEdu: true }) + twoColFooter(bodyW2); body += wTable([ { cells: [{ w: FULL, shd: HB.bg, ml: T(60), mr: T(60), mt: T(50), mb: T(44), borders: heroCellBorders, content: hero }] }, { cells: [{ w: FULL, shd: P.bg, ml: T(64), mr: T(64), mt: T(42), mb: T(40), content: m2 }] } ], { grid: [FULL] }); /* ===== stripe (全新) ===== */ } else if (c.layout === 'stripe') { var stW = T(c.stripeWidth || 18), restW = FULL - stW; var innerW = restW - T(116); var sHead = ''; var sStack = dxHeadName(P, 34, 'left', fnt, 70) + dxHeadTitle(P, 16.5, 'left', fnt) + dxContactsInline(P, 'left', fnt); if (img) { var sl = innerW - T(130), sr = T(130); sHead = wTable([{ cells: [ { w: sl, content: sStack, vAlign: 'center' }, { w: sr, content: wImage(IMG_RID, 100, 'right', imgId++), vAlign: 'center' } ] }], { grid: [sl, sr] }) + wSpacer(2); } else sHead = sStack; sHead += wSpacer(14); sHead += wPara('', { borderBottom: { sz: 18, color: P.accent, space: 1 }, after: 0, line: 0.2 }); sHead += wSpacer(24); var sBody = sHead + mainBody(innerW, { skipEdu: true }) + twoColFooter(innerW); body += wTable([{ cells: [ { w: stW, shd: hex6(c.stripeBg, P.primary), content: wPara('') }, { w: restW, shd: P.bg, ml: T(58), mr: T(58), mt: T(52), mb: T(40), content: sBody } ] }], { grid: [stW, restW] }); /* ===== split (全新) ===== */ } else if (c.layout === 'split') { var LP = bandPalette(c.splitLeftBg || c.primary, c.splitLeftText || '#ffffff', c.accent); var RP = bandPalette(c.splitRightBg || '#f1f5f9', c.splitRightText || c.text, c.accent); var lW = Math.round(FULL * 0.545), rW = FULL - lW; var lIn = wPara(wRun(state.basic.name || '', { size: 32, bold: true, color: LP.text, spacing: 90, font: fnt }), { after: 8, line: 1.15 }) + wPara(wRun(state.basic.title || '', { size: 15, bold: true, color: LP.accent, spacing: 35, font: fnt }), { after: 0, line: 1.4 }); var rIn = ''; if (img) rIn += wImage(IMG_RID, 82, 'left', imgId++) + wSpacer(12); rIn += getContacts().map(function (x) { return wPara(wRun(x, { size: 12, bold: true, color: RP.body, font: fnt }), { after: 7, line: 1.5 }); }).join(''); var bw = FULL - T(120); var sBody2 = mainBody(bw, { skipEdu: true }) + twoColFooter(bw); body += wTable([{ cells: [ { w: lW, shd: LP.bg, ml: T(42), mr: T(30), mt: T(48), mb: T(46), content: lIn, vAlign: 'center' }, { w: rW, shd: RP.bg, ml: T(38), mr: T(32), mt: T(48), mb: T(46), content: rIn, vAlign: 'center' } ] }], { grid: [lW, rW] }); body += wTable([{ cells: [{ w: FULL, shd: P.bg, ml: T(60), mr: T(60), mt: T(42), mb: T(40), content: sBody2 }] }], { grid: [FULL] }); /* ===== magazine (全新) ===== */ } else if (c.layout === 'magazine') { var MB = bandPalette(c.topBg, c.topText, c.accent); var mHeadW = FULL - T(100); var mLeft = wPara(wRun(state.basic.name || '', { size: 28, bold: true, color: MB.text, spacing: 80, font: fnt }), { after: 5, line: 1.15 }) + wPara(wRun(state.basic.title || '', { size: 14, bold: true, color: MB.accent, spacing: 30, font: fnt }), { after: 0, line: 1.35 }); var mHead; if (img) { var ml2 = mHeadW - T(110), mr2 = T(110); mHead = wTable([{ cells: [ { w: ml2, content: mLeft, vAlign: 'center' }, { w: mr2, content: wImage(IMG_RID, 78, 'right', imgId++), vAlign: 'center' } ] }], { grid: [ml2, mr2] }) + wSpacer(2); } else mHead = mLeft; var msW = T(252), mmW = FULL - msW; var msInner = msW - T(52), mmInner = mmW - T(84); var sideStack = getContacts().map(function (x) { return wPara(wRun(x, { size: 12, color: S.body, font: fnt }), { after: 8, line: 1.5 }); }).join(''); sideStack += wSpacer(10); sideStack += wPara('', { borderTop: { sz: 6, color: S.line, space: 1 }, after: 0, line: 0.2 }); sideStack += wSpacer(18); sideStack += dxSkills(c, S, msInner, fnt); body += wTable([{ cells: [{ w: FULL, shd: MB.bg, ml: T(50), mr: T(50), mt: T(30), mb: T(30), borders: '', content: mHead }] }], { grid: [FULL] }); body += wTable([{ cells: [ { w: msW, shd: S.bg, ml: T(26), mr: T(26), mt: T(34), mb: T(34), content: sideStack }, { w: mmW, shd: P.bg, ml: T(42), mr: T(42), mt: T(36), mb: T(34), content: mainBody(mmInner) } ] }], { grid: [msW, mmW] }); /* ===== frame (全新) ===== */ } else if (c.layout === 'frame') { var pad = T(26), tblW = FULL - pad * 2; var fbSz = /(\d+)px/.test(c.frameBorder || '') ? parseInt(/(\d+)px/.exec(c.frameBorder)[1], 10) : 2; var fbCol = /#[0-9a-fA-F]{3,6}/.test(c.frameBorder || '') ? hex6(c.frameBorder.match(/#[0-9a-fA-F]{3,6}/)[0]) : P.primary; var fBorders = '' + ['top', 'left', 'bottom', 'right'].map(function (k) { return ''; }).join('') + ''; var fInner = tblW - T(96); var fHead = ''; if (img) fHead += wImage(IMG_RID, 88, 'center', imgId++) + wSpacer(12); fHead += wPara(wRun(state.basic.name || '', { size: 32, bold: true, color: P.primary, spacing: 90, font: fnt }), { align: 'center', after: 7, line: 1.15 }); fHead += wPara(wRun(state.basic.title || '', { size: 15, bold: true, color: P.accent, spacing: 45, font: fnt }), { align: 'center', after: 10, line: 1.3 }); fHead += dxContactsInline(P, 'center', fnt); fHead += wSpacer(12); fHead += wPara('', { borderBottom: { sz: 6, color: P.line, space: 1 }, after: 0, line: 0.2 }); fHead += wSpacer(22); var fBody = fHead + mainBody(fInner, { skipEdu: true }) + twoColFooter(fInner); body += wTable([{ cells: [{ w: tblW, shd: P.bg, ml: T(48), mr: T(48), mt: T(40), mb: T(38), content: fBody }] }], { grid: [tblW], ind: pad, borders: fBorders }); } body += wSpacer(1); body += ''; var documentXml = '' + '' + '' + body + ''; var stylesXml = '' + '' + '' + '' + '' + '' + '' + '' + ''; var contentTypes = '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + ''; var rootRels = '' + '' + '' + ''; var docRels = '' + '' + '' + (img ? '' : '') + ''; var files = [ { name: '[Content_Types].xml', data: contentTypes }, { name: '_rels/.rels', data: rootRels }, { name: 'word/_rels/document.xml.rels', data: docRels }, { name: 'word/document.xml', data: documentXml }, { name: 'word/styles.xml', data: stylesXml } ]; if (img) files.push({ name: 'word/media/image1.' + img.ext, data: img.bytes }); return makeZip(files); } /* ================================================================================== ★★★ 导出内核 D:真实 .xlsx(SpreadsheetML)生成器 ★★★ —— 真实单元格文本 + 完整样式表 + 网格化版面 + 富文本技能条 + A4 打印设置 —— 豪华升级:渐变色带还原 / 装饰边框 / 双色点缀 / 精致行高 ================================================================================== */ function XlStyles() { this.fonts = ['']; this.fills = ['', '']; this.borders = ['']; this.xfs = ['']; this.cache = {}; } XlStyles.prototype.get = function (o) { o = o || {}; var key = JSON.stringify(o); if (this.cache[key] != null) return this.cache[key]; var f = ''; if (o.b) f += ''; if (o.i) f += ''; if (o.u) f += ''; f += ''; f += ''; f += ''; f += ''; var fontId = this.fonts.indexOf(f); if (fontId < 0) { this.fonts.push(f); fontId = this.fonts.length - 1; } var fillId = 0; if (o.fill) { var fl = ''; fillId = this.fills.indexOf(fl); if (fillId < 0) { this.fills.push(fl); fillId = this.fills.length - 1; } } var borderId = 0; if (o.bd) { var bd = '' + (o.bd.l ? '' : '') + '' + (o.bd.t ? '' : '') + (o.bd.b ? '' : '') + ''; borderId = this.borders.indexOf(bd); if (borderId < 0) { this.borders.push(bd); borderId = this.borders.length - 1; } } var align = ''; var xf = '' + align + ''; var id = this.xfs.indexOf(xf); if (id < 0) { this.xfs.push(xf); id = this.xfs.length - 1; } this.cache[key] = id; return id; }; XlStyles.prototype.xml = function () { return '' + '' + '' + this.fonts.join('') + '' + '' + this.fills.join('') + '' + '' + this.borders.join('') + '' + '' + '' + this.xfs.join('') + '' + '' + ''; }; function colLetter(n) { var s = ''; while (n > 0) { var m = (n - 1) % 26; s = String.fromCharCode(65 + m) + s; n = Math.floor((n - 1) / 26); } return s; } function textUnits(s) { var u = 0, str = String(s == null ? '' : s); for (var i = 0; i < str.length; i++) { var code = str.charCodeAt(i); u += (code > 0x2000) ? 1 : 0.55; } return u; } /** 估算行高(磅) */ function estRowH(text, widthPx, fontPt, extraPt) { var s = String(text == null ? '' : text); var fontPx = fontPt / 0.75, lines = 0; var segs = s.split('\n'); for (var i = 0; i < segs.length; i++) { lines += Math.max(1, Math.ceil(textUnits(segs[i]) * fontPx / Math.max(40, widthPx - 15))); } if (!s) lines = 1; return Math.round((lines * fontPt * 1.6 + (extraPt == null ? 8 : extraPt)) * 10) / 10; } function richRun(text, o) { o = o || {}; return '' + (o.b ? '' : '') + '' + '' + '' + xmlEsc(text) + ''; } function buildXlsxBytes() { var c = tpl(); var fnt = officeFont(c); var P = mainPalette(c), S = sidePalette(c); var st = new XlStyles(); var img = parseDataUrl(state.basic.avatar); // 列宽(字符宽)→ 像素 ≈ 7*chars+5 var COLS = [ { w: 1.6 }, // A 页边 { w: 2.2 }, // B 侧栏左内边距 { w: 34 }, // C 侧栏内容 { w: 2.2 }, // D 侧栏右内边距 { w: 2.6 }, // E 栏间距 { w: 61 }, // F 主区内容 { w: 1.6 } // G 页边 ]; var PX = COLS.map(function (x) { return Math.round(x.w * 7 + 5); }); var SIDE_PX = PX[2], MAIN_PX = PX[5]; var FULL_PX = PX[1] + PX[2] + PX[3] + PX[4] + PX[5]; var rows = [], merges = [], drawAnchor = null; function emitRow(cells, h) { rows.push({ cells: cells, h: h }); return rows.length; // 1-based } /** 整幅带状行(A..G 同底色,内容合并 B:F) */ function bandRow(text, sOpt, h, fillAll, rich) { var sid = st.get(sOpt); var padId = st.get({ fill: fillAll, sz: 8, name: fnt }); var cells = [{ col: 1, s: padId }]; cells.push(rich ? { col: 2, s: sid, rich: rich } : { col: 2, s: sid, v: text }); for (var i = 3; i <= 6; i++) cells.push({ col: i, s: sid }); cells.push({ col: 7, s: padId }); var r = emitRow(cells, h); merges.push(colLetter(2) + r + ':' + colLetter(6) + r); return r; } /** 双栏行:left/right 为 {v|rich, s, h} 或 null */ function dualRow(left, right, sideFill, mainFill) { var sidePad = st.get({ fill: sideFill, sz: 8, name: fnt }); var mainPad = st.get({ fill: mainFill, sz: 8, name: fnt }); var lS = left ? st.get(left.s) : sidePad; var rS = right ? st.get(right.s) : mainPad; var cells = [ { col: 1, s: mainPad }, { col: 2, s: sidePad }, left && left.rich ? { col: 3, s: lS, rich: left.rich } : { col: 3, s: lS, v: left ? left.v : '' }, { col: 4, s: sidePad }, { col: 5, s: mainPad }, right && right.rich ? { col: 6, s: rS, rich: right.rich } : { col: 6, s: rS, v: right ? right.v : '' }, { col: 7, s: mainPad } ]; var h = Math.max(left ? left.h : 0, right ? right.h : 0, 13); return emitRow(cells, h); } /* ---------- 内容块生成(返回 {v|rich, s, h} 数组) ---------- */ function blkSecTitle(title, PL, widthPx) { var bg = PL.bg, base = { name: fnt, sz: 13.5, b: true, color: PL.primary, fill: bg, v: 'bottom' }; if (c.secStyle === 'background') base = { name: fnt, sz: 11.5, b: true, color: bestOn(PL.primary), fill: PL.primary, v: 'center', h: 'left', indent: 1 }; else if (c.secStyle === 'underline') base.bd = { b: { s: 'medium', c: PL.accent } }; else if (c.secStyle === 'left-border') { base.bd = { l: { s: 'thick', c: PL.accent } }; base.indent = 1; } else if (c.secStyle === 'center-line') { base.h = 'center'; base.bd = { b: { s: 'thin', c: fadeHex(PL.primary, bg, 0.4) } }; } return [{ v: title, s: base, h: c.secStyle === 'background' ? 24 : 23 }]; } function blkText(text, PL, widthPx, o) { o = o || {}; var sz = o.sz || 9.5; return [{ v: text, s: { name: fnt, sz: sz, b: !!o.b, color: o.color || PL.body, fill: o.fill || PL.bg, h: o.h || 'left', v: 'top', indent: o.indent, bd: o.bd }, h: estRowH(text, widthPx, sz, o.pad == null ? 6 : o.pad) }]; } function blkGap(PL, h) { return [{ v: '', s: { fill: PL.bg, sz: 8, name: fnt }, h: h || 6 }]; } function blkItem(item, isEdu, PL, widthPx) { var title = isEdu ? (item.school || '') : (item.company || item.name || ''); var subtitle = isEdu ? ((item.major || '') + (item.degree ? ' · ' + item.degree : '')) : (item.title || item.role || ''); var card = c.itemStyle === 'card', dotline = c.itemStyle === 'dot-line'; var fill = card ? PL.card : PL.bg; var bd = card ? { l: { s: 'thick', c: PL.accent } } : (dotline ? { l: { s: 'medium', c: PL.line } } : null); var ind = (card || dotline) ? 1 : 0; var out = []; // 标题行(富文本:左标题 + 右日期) out.push({ rich: richRun(title, { b: true, sz: 11.5, color: PL.primary, name: fnt }), s: { name: fnt, sz: 11.5, b: true, color: PL.primary, fill: fill, bd: bd, indent: ind, v: 'bottom' }, h: estRowH(title, widthPx - 30, 11.5, 5) }); if (item.date) { out.push({ v: item.date, s: { name: 'Consolas', sz: 9, b: true, color: PL.muted, fill: fill, bd: bd, indent: ind, h: 'right' }, h: 13.5 }); } if (subtitle) { out.push({ v: subtitle, s: { name: fnt, sz: 10.5, b: true, color: PL.soft, fill: fill, bd: bd, indent: ind }, h: estRowH(subtitle, widthPx, 10.5, 5) }); } if (item.desc) { out.push({ v: item.desc, s: { name: fnt, sz: 9.5, color: PL.body, fill: fill, bd: bd, indent: ind }, h: estRowH(item.desc, widthPx, 9.5, 8) }); } out.push({ v: '', s: { fill: PL.bg, sz: 8, name: fnt }, h: 8 }); return out; } function blkSkills(PL, widthPx) { if (!state.skills.length) return []; var out = blkSecTitle('核心专业矩阵', PL, widthPx); var stl = c.skillStyle; if (stl === 'tag') { var line = state.skills.map(function (s) { return '· ' + (s.name || ''); }).join('\n'); out.push({ v: line, s: { name: fnt, sz: 9.5, b: true, color: PL.primary, fill: PL.tag, indent: 1 }, h: estRowH(line, widthPx, 9.5, 10) }); } else if (stl === 'text') { var line2 = state.skills.map(function (s) { return s.name || ''; }).join(' · '); out.push({ v: line2, s: { name: fnt, sz: 10, b: true, u: true, color: PL.primary, fill: PL.bg }, h: estRowH(line2, widthPx, 10, 8) }); } else if (stl === 'dot') { state.skills.forEach(function (sk) { var lv = Math.max(1, Math.min(5, parseInt(sk.level) || 3)); out.push({ rich: richRun((sk.name || '') + ' ', { b: true, sz: 10, color: PL.primary, name: fnt }) + richRun(new Array(lv + 1).join('●'), { sz: 10, color: PL.accent, name: fnt }) + richRun(new Array(5 - lv + 1).join('●'), { sz: 10, color: PL.track, name: fnt }), s: { name: fnt, sz: 10, color: PL.primary, fill: PL.bg }, h: estRowH((sk.name || '') + ' ●●●●●', widthPx, 10, 5) }); }); } else { // bar state.skills.forEach(function (sk) { var lv = Math.max(1, Math.min(5, parseInt(sk.level) || 3)); var total = Math.max(10, Math.floor(widthPx / 12)); var full = Math.round(total * lv / 5); out.push({ v: sk.name || '', s: { name: fnt, sz: 9.5, b: true, color: PL.primary, fill: PL.bg }, h: estRowH(sk.name, widthPx, 9.5, 3) }); out.push({ rich: richRun(new Array(full + 1).join('█'), { sz: 7, color: PL.accent, name: 'Consolas' }) + richRun(new Array(total - full + 1).join('█'), { sz: 7, color: PL.track, name: 'Consolas' }), s: { name: 'Consolas', sz: 7, color: PL.accent, fill: PL.bg }, h: 9 }); out.push({ v: '', s: { fill: PL.bg, sz: 6, name: fnt }, h: 4 }); }); } out.push({ v: '', s: { fill: PL.bg, sz: 8, name: fnt }, h: 8 }); return out; } function blkMain(widthPx, opt) { opt = opt || {}; var out = []; if (state.summary) { out = out.concat(blkSecTitle('高管执行摘要', P, widthPx)); out = out.concat(blkText(state.summary, P, widthPx, { sz: 10, color: P.soft, pad: 10, bd: { l: { s: 'thick', c: fadeHex(P.accent, P.bg, 0.2) } }, indent: 1 })); out = out.concat(blkGap(P, 8)); } if (state.work.length) { out = out.concat(blkSecTitle('高管核心履历', P, widthPx)); state.work.forEach(function (x) { out = out.concat(blkItem(x, false, P, widthPx)); }); out = out.concat(blkGap(P, 6)); } if (state.projects.length) { out = out.concat(blkSecTitle('重大战略与资本项目', P, widthPx)); state.projects.forEach(function (x) { out = out.concat(blkItem(x, false, P, widthPx)); }); out = out.concat(blkGap(P, 6)); } if (!opt.skipEdu && state.education.length) { out = out.concat(blkSecTitle('教育背景与资质', P, widthPx)); state.education.forEach(function (x) { out = out.concat(blkItem(x, true, P, widthPx)); }); out = out.concat(blkGap(P, 6)); } if (opt.withSkills) out = out.concat(blkSkills(P, widthPx)); return out; } var twoCol = (c.layout === 'side' || c.layout === 'magazine'); /* ================= 头部带状区(top / hero / split / magazine / classic / stripe / frame) ================= */ function pushHeaderBand() { if (c.layout === 'top' || c.layout === 'hero') { var B = bandPalette(c.topBg, c.topText, c.accent); var centerH = (c.layout === 'hero'); bandRow('', { fill: B.bg, sz: 8, name: fnt }, 10, B.bg); if (img) { drawAnchor = { row: rows.length, col: centerH ? 3 : 5, size: 92 }; bandRow('', { fill: B.bg, sz: 8, name: fnt }, 74, B.bg); } bandRow(state.basic.name || '', { name: fnt, sz: 25, b: true, color: B.text, fill: B.bg, h: centerH ? 'center' : 'left', v: 'bottom' }, 36, B.bg); bandRow(state.basic.title || '', { name: fnt, sz: 12.5, b: true, color: B.accent, fill: B.bg, h: centerH ? 'center' : 'left' }, estRowH(state.basic.title, FULL_PX, 12.5, 6), B.bg); var ct = getContacts().join(' '); if (ct) bandRow(ct, { name: fnt, sz: 9.5, color: B.body, fill: B.bg, h: centerH ? 'center' : 'left' }, estRowH(ct, FULL_PX, 9.5, 10), B.bg); bandRow('', { fill: B.bg, sz: 8, name: fnt }, 12, B.bg); if (c.topBorderBottom) { var bc = /#[0-9a-fA-F]{3,6}/.test(c.topBorderBottom) ? hex6(c.topBorderBottom.match(/#[0-9a-fA-F]{3,6}/)[0]) : B.accent; bandRow('', { fill: bc, sz: 6, name: fnt }, 5, bc); } else { bandRow('', { fill: B.line, sz: 6, name: fnt }, 3, B.line); } bandRow('', { fill: P.bg, sz: 8, name: fnt }, 14, P.bg); } else if (c.layout === 'split') { var LP = bandPalette(c.splitLeftBg || c.primary, c.splitLeftText || '#ffffff', c.accent); var RP = bandPalette(c.splitRightBg || '#f1f5f9', c.splitRightText || c.text, c.accent); var lPad = st.get({ fill: LP.bg, sz: 8, name: fnt }); var rPad = st.get({ fill: RP.bg, sz: 8, name: fnt }); function splitRow(lv, ls, lh, rv, rs, rh) { var lS = st.get(ls || { fill: LP.bg, sz: 8, name: fnt }); var rS = st.get(rs || { fill: RP.bg, sz: 8, name: fnt }); var cells = [ { col: 1, s: lPad }, { col: 2, s: lPad }, { col: 3, s: lS, v: lv }, { col: 4, s: rPad }, { col: 5, s: rPad }, { col: 6, s: rS, v: rv }, { col: 7, s: rPad } ]; return emitRow(cells, Math.max(lh || 0, rh || 0, 13)); } splitRow('', null, 12, '', null, 12); if (img) { drawAnchor = { row: rows.length, col: 6, size: 76 }; } var cts = getContacts(); splitRow(state.basic.name || '', { name: fnt, sz: 23, b: true, color: LP.text, fill: LP.bg, v: 'bottom', indent: 1 }, 34, cts[0] || '', { name: fnt, sz: 10, b: true, color: RP.body, fill: RP.bg, indent: img ? 6 : 1 }, 20); splitRow(state.basic.title || '', { name: fnt, sz: 11.5, b: true, color: LP.accent, fill: LP.bg, indent: 1 }, estRowH(state.basic.title, SIDE_PX, 11.5, 6), cts[1] || '', { name: fnt, sz: 10, b: true, color: RP.body, fill: RP.bg, indent: img ? 6 : 1 }, 20); splitRow('', null, 8, cts[2] || '', { name: fnt, sz: 10, b: true, color: RP.body, fill: RP.bg, indent: img ? 6 : 1 }, 20); splitRow('', null, 8, cts[3] || '', { name: fnt, sz: 10, b: true, color: RP.body, fill: RP.bg, indent: img ? 6 : 1 }, 20); splitRow('', null, 12, '', null, 12); bandRow('', { fill: P.bg, sz: 8, name: fnt }, 16, P.bg); } else if (c.layout === 'magazine') { var MB = bandPalette(c.topBg, c.topText, c.accent); bandRow('', { fill: MB.bg, sz: 8, name: fnt }, 10, MB.bg); if (img) { drawAnchor = { row: rows.length, col: 6, size: 66 }; } bandRow(state.basic.name || '', { name: fnt, sz: 21, b: true, color: MB.text, fill: MB.bg, v: 'bottom' }, 31, MB.bg); bandRow(state.basic.title || '', { name: fnt, sz: 11.5, b: true, color: MB.accent, fill: MB.bg }, estRowH(state.basic.title, FULL_PX, 11.5, 6), MB.bg); bandRow('', { fill: MB.bg, sz: 8, name: fnt }, 10, MB.bg); bandRow('', { fill: MB.line, sz: 6, name: fnt }, 3, MB.line); } else if (c.layout === 'classic' || c.layout === 'stripe' || c.layout === 'frame') { var centerC = (c.headerAlign === 'center' || c.layout === 'frame'); bandRow('', { fill: P.bg, sz: 8, name: fnt }, 12, P.bg); if (img) { drawAnchor = { row: rows.length, col: centerC ? 3 : 6, size: 84 }; bandRow('', { fill: P.bg, sz: 8, name: fnt }, 66, P.bg); } bandRow(state.basic.name || '', { name: fnt, sz: 24, b: true, color: P.primary, fill: P.bg, h: centerC ? 'center' : 'left', v: 'bottom' }, 34, P.bg); bandRow(state.basic.title || '', { name: fnt, sz: 12, b: true, color: P.accent, fill: P.bg, h: centerC ? 'center' : 'left' }, estRowH(state.basic.title, FULL_PX, 12, 6), P.bg); var ct2 = getContacts().join(' '); if (ct2) bandRow(ct2, { name: fnt, sz: 9.5, color: P.muted, fill: P.bg, h: centerC ? 'center' : 'left' }, estRowH(ct2, FULL_PX, 9.5, 8), P.bg); var hbC = P.line, hbOn = true; if (c.layout === 'classic') { if (!c.headerBorder || c.headerBorder === 'none') { hbC = P.line; } else if (/#[0-9a-fA-F]{3,6}/.test(c.headerBorder)) hbC = hex6(c.headerBorder.match(/#[0-9a-fA-F]{3,6}/)[0]); } else if (c.layout === 'frame') { hbC = /#[0-9a-fA-F]{3,6}/.test(c.frameBorder || '') ? hex6(c.frameBorder.match(/#[0-9a-fA-F]{3,6}/)[0]) : P.primary; } else { hbC = P.accent; } if (hbOn) bandRow('', { fill: P.bg, sz: 6, name: fnt, bd: { b: { s: 'medium', c: hbC } } }, 8, P.bg); bandRow('', { fill: P.bg, sz: 8, name: fnt }, 14, P.bg); } } pushHeaderBand(); if (twoCol) { /* ---- 双栏:侧栏(C 列) + 主区(F 列) ---- */ var leftBlocks = []; if (c.layout === 'side') { if (img) { drawAnchor = { row: rows.length, col: 3, size: 96 }; leftBlocks.push({ v: '', s: { fill: S.bg, sz: 8, name: fnt }, h: 78 }); } if (c.nameInSide) { leftBlocks.push({ v: state.basic.name || '', s: { name: fnt, sz: 20, b: true, color: S.text, fill: S.bg, h: 'center', v: 'bottom' }, h: 30 }); leftBlocks.push({ v: state.basic.title || '', s: { name: fnt, sz: 10, b: true, color: S.accent, fill: S.bg, h: 'center' }, h: estRowH(state.basic.title, SIDE_PX, 10, 6) }); } leftBlocks.push({ v: '', s: { fill: S.bg, sz: 8, name: fnt, bd: { b: { s: 'thin', c: S.line } } }, h: 12 }); } getContacts().forEach(function (x) { leftBlocks.push({ v: x, s: { name: fnt, sz: 9.5, color: S.body, fill: S.bg }, h: estRowH(x, SIDE_PX, 9.5, 6) }); }); leftBlocks.push({ v: '', s: { fill: S.bg, sz: 8, name: fnt, bd: { b: { s: 'thin', c: S.line } } }, h: 10 }); leftBlocks.push({ v: '', s: { fill: S.bg, sz: 8, name: fnt }, h: 10 }); leftBlocks = leftBlocks.concat(blkSkills(S, SIDE_PX)); var rightBlocks = []; if (c.layout === 'side' && !c.nameInSide) { rightBlocks.push({ v: state.basic.name || '', s: { name: fnt, sz: 24, b: true, color: P.primary, fill: P.bg, v: 'bottom' }, h: 34 }); rightBlocks.push({ v: state.basic.title || '', s: { name: fnt, sz: 12, b: true, color: P.accent, fill: P.bg, bd: { b: { s: 'medium', c: P.accent } } }, h: estRowH(state.basic.title, MAIN_PX, 12, 8) }); rightBlocks.push({ v: '', s: { fill: P.bg, sz: 8, name: fnt }, h: 12 }); } rightBlocks = rightBlocks.concat(blkMain(MAIN_PX, {})); var n = Math.max(leftBlocks.length, rightBlocks.length); for (var i = 0; i < n; i++) dualRow(leftBlocks[i] || null, rightBlocks[i] || null, S.bg, P.bg); } else { /* ---- 单栏 ---- */ var blocks = blkMain(FULL_PX, { skipEdu: false, withSkills: true }); blocks.forEach(function (b) { var sid = st.get(b.s); var padId = st.get({ fill: b.s.fill || P.bg, sz: 8, name: fnt }); var cells = [{ col: 1, s: st.get({ fill: P.bg, sz: 8, name: fnt }) }]; cells.push(b.rich ? { col: 2, s: sid, rich: b.rich } : { col: 2, s: sid, v: b.v }); for (var k = 3; k <= 6; k++) cells.push({ col: k, s: sid }); cells.push({ col: 7, s: st.get({ fill: P.bg, sz: 8, name: fnt }) }); var r = emitRow(cells, b.h); merges.push(colLetter(2) + r + ':' + colLetter(6) + r); }); } // 尾部留白,保证底色铺满 for (var t2 = 0; t2 < 3; t2++) { var padS = st.get({ fill: P.bg, sz: 8, name: fnt }); var cs = []; for (var q = 1; q <= 7; q++) cs.push({ col: q, s: padS }); emitRow(cs, 14); } /* ---------- 序列化 ---------- */ var colsXml = '' + COLS.map(function (x, i) { return ''; }).join('') + ''; var sheetData = '' + rows.map(function (r, i) { var rn = i + 1; var cells = r.cells.slice().sort(function (a, b) { return a.col - b.col; }).map(function (cl) { var ref = colLetter(cl.col) + rn; if (cl.rich) return '' + cl.rich + ''; if (cl.v == null || cl.v === '') return ''; return '' + xmlEsc(cl.v) + ''; }).join(''); return '' + cells + ''; }).join('') + ''; var mergeXml = merges.length ? '' + merges.map(function (m) { return ''; }).join('') + '' : ''; var sheetXml = '' + '' + '' + '' + '' + '' + colsXml + sheetData + mergeXml + '' + '' + (img ? '' : '') + ''; var workbookXml = '' + '' + ''; var wbRels = '' + '' + '' + '' + ''; var rootRels2 = '' + '' + '' + ''; var ct2x = '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + (img ? '' : '') + ''; var files2 = [ { name: '[Content_Types].xml', data: ct2x }, { name: '_rels/.rels', data: rootRels2 }, { name: 'xl/workbook.xml', data: workbookXml }, { name: 'xl/_rels/workbook.xml.rels', data: wbRels }, { name: 'xl/styles.xml', data: st.xml() }, { name: 'xl/worksheets/sheet1.xml', data: sheetXml } ]; if (img) { var an = drawAnchor || { row: 1, col: 3, size: 90 }; var emu = Math.round(an.size * 9525); var drawing = '' + '' + '' + '' + (an.col - 1) + '190500' + an.row + '19050' + '' + '' + '' + '' + ''; files2.push({ name: 'xl/drawings/drawing1.xml', data: drawing }); files2.push({ name: 'xl/drawings/_rels/drawing1.xml.rels', data: '' + '' + '' + '' }); files2.push({ name: 'xl/worksheets/_rels/sheet1.xml.rels', data: '' + '' + '' + '' }); files2.push({ name: 'xl/media/image1.' + img.ext, data: img.bytes }); } return makeZip(files2); } /* ================================================================================== ★★★ 原有能力完整保留:html2canvas 像素级快照导出(图片快照模式) ★★★ ================================================================================== */ async function getPerfectCanvasImage() { const mask = document.getElementById('loadingMask'); mask.classList.add('active'); try { // 1. 获取原件并深度克隆 const originalEl = document.getElementById('previewPage').firstElementChild; const clone = originalEl.cloneNode(true); // 2. 将克隆体强行挂载到 Body 最外层,无视任何父级限制 clone.style.position = 'absolute'; clone.style.top = '-9999px'; clone.style.left = '0'; clone.style.transform = 'none'; clone.style.width = '794px'; clone.style.height = 'auto'; clone.style.boxShadow = 'none'; document.body.appendChild(clone); // 3. 等待字体/图片渲染 await new Promise(r => setTimeout(r, 300)); // 4. 完整截取自由伸展的克隆体 const canvas = await html2canvas(clone, { scale: 2, useCORS: true, backgroundColor: '#ffffff', allowTaint: true, logging: false }); document.body.removeChild(clone); return canvas.toDataURL('image/jpeg', 0.95); } finally { mask.classList.remove('active'); } } async function exportWordImage() { const imgData = await getPerfectCanvasImage(); const html = ` BlueCV Pro - Resume
`; const blob = new Blob(['\ufeff', html], { type: 'application/msword;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = safeName() + '_BlueCV_Pro.doc'; document.body.appendChild(a); a.click(); document.body.removeChild(a); setTimeout(() => URL.revokeObjectURL(url), 1500); } async function exportExcelImage() { const imgData = await getPerfectCanvasImage(); const html = `
`; const blob = new Blob(['\ufeff', html], { type: 'application/vnd.ms-excel;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = safeName() + '_BlueCV_Pro.xls'; document.body.appendChild(a); a.click(); document.body.removeChild(a); setTimeout(() => URL.revokeObjectURL(url), 1500); } /* ================================================================================== ★★★ 导出统一入口(原生 OOXML 优先,异常时自动回退图片快照) ★★★ ================================================================================== */ function withMask(text, fn) { var mask = document.getElementById('loadingMask'); document.getElementById('loadingText').textContent = text; mask.classList.add('active'); return new Promise(function (resolve, reject) { setTimeout(function () { try { var r = fn(); mask.classList.remove('active'); resolve(r); } catch (err) { mask.classList.remove('active'); reject(err); } }, 60); }); } async function exportWord() { if (exportMode === 'image') { try { await exportWordImage(); toast('已导出 Word 图片快照版 (.doc)'); } catch (e) { console.error(e); alert('导出 Word 失败,请重试'); } return; } try { const bytes = await withMask('正在生成可编辑 Word 文档 (.docx)...', buildDocxBytes); saveBytes(bytes, safeName() + '_BlueCV_Pro.docx', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'); toast('✅ 已导出 Word(.docx,版式/配色/内容与预览一致,可直接编辑)'); } catch (e) { console.error('[docx]', e); try { await exportWordImage(); toast('原生导出异常,已回退为图片快照版 Word'); } catch (e2) { alert('导出 Word 失败:' + (e && e.message ? e.message : e)); } } } async function exportExcel() { if (exportMode === 'image') { try { await exportExcelImage(); toast('已导出 Excel 图片快照版 (.xls)'); } catch (e) { console.error(e); alert('导出 Excel 失败,请重试'); } return; } try { const bytes = await withMask('正在生成可编辑 Excel 工作簿 (.xlsx)...', buildXlsxBytes); saveBytes(bytes, safeName() + '_BlueCV_Pro.xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); toast('✅ 已导出 Excel(.xlsx,配色/版面/内容与预览一致,单元格可编辑)'); } catch (e) { console.error('[xlsx]', e); try { await exportExcelImage(); toast('原生导出异常,已回退为图片快照版 Excel'); } catch (e2) { alert('导出 Excel 失败:' + (e && e.message ? e.message : e)); } } } // 启动 init();