ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

CodeCV 简历导出只出最后一页?一个油猴脚本搞定多页无水印 PDF(附完整源码)

CodeCV 简历导出只出最后一页?一个油猴脚本搞定多页无水印 PDF(附完整源码) 用 CodeCV 写完简历兴冲冲点导出结果两页只出来了第二页 —— 前面全白写。这篇给出一个可直接用的油猴脚本并说清原因、安装步骤和几个必须知道的坑。脚本基于原作者的 MIT 开源项目修改已托管到 GitHub点链接即可安装文末附完整源码。一、问题长什么样CodeCV 导出 PDF 时多页简历只会导出最后一页简历实际页数导出结果1 页1 页正常2 页1 页内容是第 2 页3 页1 页内容是第 3 页单页永远正常所以很多人以为是自己简历太长的问题 —— 其实是导出脚本的锅。二、原因只有一行 CSSCodeCV 的简历预览是三层绝对定位结构.re-render ← 最外层 └── .jufe-wrapper-page ← 页面容器 ├── .jufe-wrapper-page-item ← 第 1 页 ├── .jufe-wrapper-page-item ← 第 2 页 └── .jufe-wrapper-page-item ← 第原脚本的打印样式里有这么一段.re-render, .jufe-wrapper-page, .jufe-wrapper-page-item { position: absolute !important; top: 0 !important; left: 0 !important; /* ← 元凶 */.jufe-wrapper-page-item就是每一页。它们本该依次往下排被强制top: 0之后全部叠在同一个坐标上—— 后一页盖住前一页所以只剩最后一页。这里还有第二个坑同一段里的height: 297mm !important会把超出一页的内容裁掉。这个后面第六节单独讲。三、修复思路只改一个属性分页思路本身没问题问题在于不该动站点赖以定位的容器层。正确做法是保留absolute只把每页的top从「都是 0」改成「第 n 页 (n-1) × 297mm」第 1 页 top: 0mm 第 2 页 top: 297mm 第 3 页 top: 594一页一张纸内容不重叠站点的定位体系一根毫毛都不动。为什么不直接用更标准的break-after: page我试过。CodeCV 的预览依赖三层absolute共用同一个坐标系一旦把容器改成static走文档流整块布局就散了 —— 内容重复、顺序错乱、页与页之间还露出深色底。在第三方站点上做适配优先「最少改动」而不是「最标准写法」。这是踩了三次坑换来的结论。四、安装点一下就行第 1 步浏览器先装TampermonkeyChrome / Edge 应用商店搜一下装一次就够。第 2 步点下面的链接 一键安装脚本Tampermonkey 会自动弹出安装页面点「安装」即可。装好之后脚本跟随仓库自动更新。装过旧版本先在 Tampermonkey 仪表盘里删掉旧版再点上面的链接否则新旧两版会同时运行、页面上出现两个按钮。第 3 步打开 CodeCV 简历页 →按 F5 刷新。刷新后页面左侧会出现两个按钮按钮作用️ 导出完美简历主按钮一键导出诊断导出有问题时点它会把页面结构信息自动复制到剪贴板按钮只在页面加载时挂载所以装完必须刷新一次CodeCV 页面。备选安装方式方式地址jsDelivr国内推荐https://cdn.jsdelivr.net/gh/XXWen0201/codecv-exportmain/codecv-export.user.jsGitHub 仓库XXWen0201/codecv-exportGitHub Rawhttps://raw.githubusercontent.com/XXWen0201/codecv-export/main/codecv-export.user.js手动粘贴复制文末完整源码→ Tampermonkey 新建脚本 → 全选删除模板 → 粘贴 →Ctrl S五、怎么用点「导出完美简历」在弹出的打印对话框里按下表设置选项值目标 / 打印机另存为 PDF纸张A4边距无背景图形勾选不勾的话色块、分隔线会丢直接按Ctrl P也照样生效不必先点按钮。如果导出结果不对打开F12控制台还有几个排错用的命令命令作用__codecvExport.dump()打印并复制完整结构诊断__codecvExport.detectPages()看看脚本把哪些元素识别成了「页」__codecvExport.checkOverflow(pages)返回内容超出一页 A4 的页码__codecvExport.restore()手动还原打印排版六、必须知道的三个坑1. 某一页内容超过一页 A4超出的部分会静默丢失打印样式给每页写死了height: 297mm超出部分会被裁掉。页数还是对的肉眼很难发现。我做过压力测试一页里灌入 46 段文字A4 内只装得下约 29 段第 3046 段从生成的 PDF 里彻底消失不是画面上裁掉是根本没输出。这个修不掉 —— 把高度放开或改成overflow: visible只会让内容盖到下一页上更糟。所以脚本内置了告警导出时若检测到某页超长会在页面下方弹红条写明具体页码控制台也会console.warn提示。看到红条就去 CodeCV 里把那一页精简到一页之内。2. 只在浏览器渲染层生效脚本处理的是你本机已经渲染出来的页面内容不构造请求、不绕过付费墙、不批量抓取。如果站点是把水印烧进服务端生成的文件里脚本无能为力 —— 它只能在浏览器渲染层隐藏页面上已有的元素。3. 请只用于导出你自己的简历脚本去水印靠的是隐藏页面上已有的水印元素不是破解。请勿用于他人简历或绕过付费。七、常见问题Q安装链接点不开 / 打开是一堆代码说明 Tampermonkey 没装或没启用。先确认地址栏右侧有 Tampermonkey 图标再重新点链接。若网络访问不到 jsDelivr改用文末的完整源码手动安装。Q导出后是空白纸张先按 F5 刷新页面。脚本只改内联样式和类名刷新即可恢复。若刷新后仍空白点「诊断」按钮把输出贴出来。Q页数对了但内容重复 / 错位大概率是 CodeCV 改版了。点「诊断」按钮 —— 它会把每页的类名、position/top/height、祖先链等自动复制到剪贴板改CONFIG.pageSelector即可适配也欢迎到 Issues 反馈。Q页面左侧没有按钮① 确认油猴脚本是启用状态② 确认已经按 F5 刷新过③ 确认地址是www.codecvcv.com/cv/...脚本按这个路径匹配。Q水印还在脚本按class/id里含watermark的元素来隐藏水印。如果 CodeCV 换了命名方式把「诊断」结果发出来即可。Q不想装 Tampermonkey可以改造成 Chrome 扩展Manifest V3代价是多一步加载已解压的扩展程序。有需要再补。八、我怎么确认它真的修好了不是看着像好了是用本机 Chrome 无头模式真实打印成 PDF再用 pypdf 逐页数页数、提取文本比对。把脚本原文注入模拟页面而不是手抄一份 CSS 进去由脚本自己去注入样式、自己跑布局逻辑再打印模拟简历导出页数内容完整误报1 页1 页是无2 页2 页是无3 页3 页是无5 页5 页是无超长内容2 页正确检出超长并向用户告警—同时还验证了「导出后是否干净还原」内联样式清空、标记类摘除、水印元素数量不变证明是隐藏而不是删除。一个容易忽略的细节页数对不等于脚本生效 —— 不注入打印样式时页数也可能碰巧对上。所以模拟页里埋了三个「诱饵」元素干扰层、校验徽标、水印只有脚本真的执行了它们才会从 PDF 里消失。校验脚本就在仓库的test/verify_e2e.py可以自己跑。九、更新日志版本改动v4.1.0水印从直接删 DOM改为可恢复的隐藏避免误删内容还原top时补回!important优先级新增「内容超出一页 A4」告警v4.0.0核心修复每页top逐页错开原版Campus 的单页导出脚本早期两版方案改成文档流 break-after: page、按position批量隐藏元素会破坏站点布局或导致内容消失已废弃不再提供。十、完整源码如果安装链接用不了复制下面全部内容粘贴进 Tampermonkey 新建脚本里Ctrl S保存即可。// UserScript // name CodeCV 简历一键无水印导出多页修复版 // name:en CodeCV Resume One-Click PDF Export (multi-page fix) // namespace https://github.com/XXWen0201/codecv-export // version 4.1.0 // description 一键把 CodeCV 简历导出成 A4 高清 PDF修复原版「多页简历只导出最后一页」的问题打印后自动还原页面水印只隐藏不删除。 // author Campus原版· 多页修复 by XXWen0201 // match *://www.codecvcv.com/cv/* // match *://codecvcv.com/cv/* // grant none // license MIT // run-at document-idle // homepageURL https://github.com/XXWen0201/codecv-export // supportURL https://github.com/XXWen0201/codecv-export/issues // icon https://cdn.jsdelivr.net/gh/XXWen0201/codecv-exportmain/icons/icon128.png // downloadURL https://cdn.jsdelivr.net/gh/XXWen0201/codecv-exportmain/codecv-export.user.js // updateURL https://cdn.jsdelivr.net/gh/XXWen0201/codecv-exportmain/codecv-export.user.js // /UserScript /* * 【v4 设计原则能不动就不动】 * ---------------------------------------------------------------------------- * 前两版翻车原因都是改动超出必要范围 * v2 把 CodeCV 依赖的 absolute 定位容器改成 static → 内容重复、错位、露黑底。 * v3 加了「隐藏所有 position:fixed/sticky 元素」的规则去清浮层 * 结果把简历正文容器一起隐藏了 → 纸张还在、内容全空。 * * v4 只做一件事把每一页的 top 从「全部 0」改成「第 n 页 (n-1) × 297mm」。 * 其余所有规则**逐字沿用原脚本**一个字都不多加。 * 浮层改成按文字特征精准匹配且限定在小尺寸徽标上绝不再批量隐藏元素。 * * 【根因】原脚本的这一段把每一页都压在同一个点 (0,0) 上 * 后一页盖住前一页所以两页简历只剩最后一页 * .re-render, .jufe-wrapper-page, .jufe-wrapper-page-item { * position: absolute !important; * top: 0 !important; left: 0 !important; * } * 只需让每页的 top 依次为 0 / 297mm / 594mm……即恢复成一页一张纸。 * * 已用无头 Chrome 实测原版 CSS 输出 1 页本方案输出页数与实际一致。 * * v4.1 只修「潜伏的雷」不动任何已跑通的逻辑 * a) 水印原来用 el.remove() 直接删 DOM —— 不可逆。一旦误命中包裹简历的容器 * 内容会被永久删掉和 v3 空白 bug 同源。改为「打印时隐藏 打印后恢复」 * 并且明确跳过「包裹着页元素的祖先」宁可漏隐藏也不动简历。 * b) 还原 top 时丢了 !important 优先级 —— 改为连优先级一起存、一起还原。 * c) 新增「某页内容超出一页 A4」告警 —— 这是原脚本就有的静默丢失路径 * 打印样式给每页写死 height:297mm超出部分会从 PDF 里彻底消失且毫无提示。 * 修不掉放开 height 只会盖到下一页所以至少明确报警。 * 另__codecvExport 上多挂了 checkOverflow / layout / restore方便手动排错。 * * 【合规边界】 * 只处理你自己本机已渲染出来的简历内容。不构造请求、不绕过付费墙、不批量抓取。 * 站点若在服务端往导出文件里烧水印本脚本无能为力。 * */ (function () { use strict; /* * CONFIG —— 唯一适配层站点改版只改这里 * */ const CONFIG { pageSelector: .jufe-wrapper-page-item, pageFallbacks: [ .jufe-wrapper-page *, [class*page-item], .re-render *, [class*wrapper-page] ], singlePageSelector: .markdown-transform-html, pageHeightMM: 297, // 一页的高度A4 hideWatermark: true, // 打印时隐藏类名含 watermark 的元素原脚本是直接删 DOM这里改为可恢复的隐藏 hideBadges: true, // 打印前隐藏「正在校验」这类小徽标精准匹配不批量隐藏 debug: false }; /* */ const BTN_ID codecv-export-btn; const DIAG_ID codecv-diag-btn; const TOAST_ID codecv-toast; const PAGE_MARK codecv-print-page; const BADGE_CLASS codecv-hide-badge; const TOP_FLAG data-codecv-top; const TOP_PRIO_FLAG data-codecv-top-prio; // 「校验 / 防伪」类小徽标的文字特征 const BADGE_RE /正在校验|校验中|校验第|防伪|仅供预览|禁止转载|下载后|请勿外传/; /* --------------------------------------------------------------------- * 1. 样式 * —— 打印部分严格沿用原脚本只在标注「★」的两处做了必要调整 * ------------------------------------------------------------------- */ const CSS /* 屏幕态按钮 */ #${BTN_ID} { position: fixed; top: 50%; left: 0; transform: translateY(-50%); z-index: 2147483647; width: 35px; padding: 15px 5px; background: #333; color: #fff; border: none; outline: none; border-radius: 0 5px 5px 0; cursor: pointer; writing-mode: vertical-lr; font-size: 12px; letter-spacing: 1px; font-family: system-ui, -apple-system, Microsoft YaHei, sans-serif; opacity: .5; transition: .3s; } #${BTN_ID}:hover { opacity: 1; background: #ff7449; } #${DIAG_ID} { position: fixed; top: calc(50% 80px); left: 0; z-index: 2147483647; width: 35px; padding: 8px 5px; background: #777; color: #fff; border: none; outline: none; border-radius: 0 5px 5px 0; cursor: pointer; writing-mode: vertical-lr; font-size: 11px; letter-spacing: 1px; font-family: system-ui, -apple-system, Microsoft YaHei, sans-serif; opacity: .35; transition: .3s; } #${DIAG_ID}:hover { opacity: 1; background: #ff7449; } #${TOAST_ID} { position: fixed; left: 50%; bottom: 40px; transform: translateX(-50%); z-index: 2147483647; max-width: 78vw; padding: 10px 18px; background: rgba(30,30,30,.92); color: #fff; border-radius: 8px; font-size: 13px; line-height: 1.6; font-family: system-ui, -apple-system, Microsoft YaHei, sans-serif; opacity: 0; pointer-events: none; transition: opacity .25s; white-space: pre-wrap; text-align: center; } #${TOAST_ID}.show { opacity: 1; } #${TOAST_ID}.warn { background: rgba(200,60,40,.94); } /* 打印态 */ media print { /* --- 原脚本的隐藏列表仅追加我们自己的三个元素 --- */ #${BTN_ID}, #${DIAG_ID}, #${TOAST_ID}, .tamper-print-btn, .operator, #header-nav, .markdown-edit, .shadow, .history, .crisp-client, #ark-float-btn, .el-scrollbar__bar, .toggle-edit-mode, .preview-cv::before, .preview-cv::after, .${BADGE_CLASS} { display: none !important; } /* --- 原脚本的容器重置逐字不动 --- */ html, body, #__nuxt, #main, #editor, .flex, .preview-cv, .el-scrollbar__wrap, .el-scrollbar__view { margin: 0 !important; padding: 0 !important; height: auto !important; width: 100% !important; display: block !important; position: static !important; overflow: visible !important; background: white !important; } /* --- 两层容器定位方式原样保留absolute top:0--- ★调整放开 height 与 overflow否则第 2 页之后会被裁掉 背景置透明避免页与页之间露出深色底。 --- */ .re-render, .jufe-wrapper-page { position: absolute !important; top: 0 !important; left: 0 !important; width: 100% !important; height: auto !important; max-height: none !important; overflow: visible !important; margin: 0 !important; padding: 0 !important; border: none !important; box-shadow: none !important; transform: none !important; background: transparent !important; } /* --- ★核心修复★ 每一页仍然 absolute与站点一致 只是 top 不再写死 0 —— 由 JS 写成 0 / 297mm / 594mm … --- */ .jufe-wrapper-page-item, .${PAGE_MARK} { position: absolute !important; left: 0 !important; width: 100% !important; height: 297mm !important; margin: 0 !important; padding: 0 !important; border: none !important; box-shadow: none !important; transform: none !important; background: #fff !important; } /* JS 万一没跑到时的兜底按序号排开最多 10 页 */ .jufe-wrapper-page-item:nth-of-type(1), .${PAGE_MARK}:nth-of-type(1) { top: 0mm !important; } .jufe-wrapper-page-item:nth-of-type(2), .${PAGE_MARK}:nth-of-type(2) { top: 297mm !important; } .jufe-wrapper-page-item:nth-of-type(3), .${PAGE_MARK}:nth-of-type(3) { top: 594mm !important; } .jufe-wrapper-page-item:nth-of-type(4), .${PAGE_MARK}:nth-of-type(4) { top: 891mm !important; } .jufe-wrapper-page-item:nth-of-type(5), .${PAGE_MARK}:nth-of-type(5) { top: 1188mm !important; } .jufe-wrapper-page-item:nth-of-type(6), .${PAGE_MARK}:nth-of-type(6) { top: 1485mm !important; } .jufe-wrapper-page-item:nth-of-type(7), .${PAGE_MARK}:nth-of-type(7) { top: 1782mm !important; } .jufe-wrapper-page-item:nth-of-type(8), .${PAGE_MARK}:nth-of-type(8) { top: 2079mm !important; } .jufe-wrapper-page-item:nth-of-type(9), .${PAGE_MARK}:nth-of-type(9) { top: 2376mm !important; } .jufe-wrapper-page-item:nth-of-type(10), .${PAGE_MARK}:nth-of-type(10) { top: 2673mm !important; } /* --- 原脚本的正文内边距 --- */ .markdown-transform-html { padding: 8mm 12mm !important; } /* --- 原脚本的页面设置 --- */ page { size: A4 portrait; margin: 0; } * { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; } } ; const styleEl document.createElement(style); styleEl.textContent CSS; (document.head || document.documentElement).appendChild(styleEl); /* --------------------------------------------------------------------- * 2. 页元素探测 * ------------------------------------------------------------------- */ function isPageLike(el) { return el el.nodeType 1 el.getBoundingClientRect().height 400; } function detectPages() { let pages Array.from(document.querySelectorAll(CONFIG.pageSelector)); if (pages.length) return pages; for (const sel of CONFIG.pageFallbacks) { pages Array.from(document.querySelectorAll(sel)).filter(isPageLike); if (pages.length) return pages; } const md document.querySelector(CONFIG.singlePageSelector); if (md) return [md]; return []; } /* --------------------------------------------------------------------- * 3. 打印布局只做「逐页错开 top」这一件事 * ------------------------------------------------------------------- */ let lastOverflow []; // 上一次检查中「内容超出一页」的页码 /** * 检查有没有哪一页内容超过了一页 A4。 * * 背景打印样式给每页写死 height: 297mm沿用原脚本而站点在页元素上 * 有 overflow: hidden —— 一旦某页内容超过 297mm**超出部分会被静默裁掉 * 页数还是对的肉眼很难发现**。这是原脚本就存在的设计改不掉改成 * overflow: visible 会让它盖到下一页上更糟所以至少给个明确告警。 * * 必须在「套用打印样式之前」量此时还是屏幕态clientHeight 是一页高度 * scrollHeight 是内容真实高度。clientHeight 太小元素被隐藏时跳过避免误报。 */ function checkOverflow(pages) { const out []; pages.forEach((el, i) { if (el.clientHeight 100 el.scrollHeight el.clientHeight 2) { out.push(i 1); } }); return out; } function applyPrintLayout() { const pages detectPages(); if (!pages.length) return null; // 先量再改样式 —— 顺序反了就量不准那时打印样式已生效 lastOverflow checkOverflow(pages); pages.forEach((el, i) { if (!el.hasAttribute(TOP_FLAG)) { // 连同「优先级」一起存站点若把 top 写成 !important还原时不能丢 el.setAttribute(TOP_FLAG, el.style.getPropertyValue(top) || ); el.setAttribute(TOP_PRIO_FLAG, el.style.getPropertyPriority(top) || ); } el.classList.add(PAGE_MARK); el.style.setProperty(top, (i * CONFIG.pageHeightMM) mm, important); }); if (CONFIG.hideBadges) hideBadges(pages); hideWatermarks(pages); return pages; } function restorePrintLayout() { document.querySelectorAll([ TOP_FLAG ]).forEach(el { const original el.getAttribute(TOP_FLAG); const prio el.getAttribute(TOP_PRIO_FLAG) || ; el.style.removeProperty(top); if (original) el.style.setProperty(top, original, prio); el.removeAttribute(TOP_FLAG); el.removeAttribute(TOP_PRIO_FLAG); el.classList.remove(PAGE_MARK); }); document.querySelectorAll(. BADGE_CLASS).forEach(el { el.classList.remove(BADGE_CLASS); }); } /** * 隐藏「正在校验第 N 页」这类小徽标。 * 严格限定文字匹配 尺寸很小 不在简历页的祖先链上。 * 绝不像 v3 那样按 position 批量隐藏元素那会连正文容器一起干掉。 */ function hideBadges(pages) { if (!document.body) return; const protect new Set(); pages.forEach(p { let e p; while (e) { protect.add(e); e e.parentElement; } }); const candidates document.body.querySelectorAll(div,span,p,section,aside,button,label); for (const el of candidates) { if (protect.has(el) || el.id BTN_ID || el.id DIAG_ID || el.id TOAST_ID) continue; const t (el.textContent || ).trim(); if (!t || t.length 60 || !BADGE_RE.test(t)) continue; const r el.getBoundingClientRect(); if (r.width 0 r.width 340 r.height 140) { el.classList.add(BADGE_CLASS); } } } /** * 打印时隐藏水印。 * * 注意两点都是踩过的雷 * 1. 绝不用 el.remove()。那是不可逆的 —— 一旦某天误命中「包裹着简历的容器」 * 内容会被永久删掉刷新都救不回来v3 的空白 bug 就是这么来的。 * 这里只挂一个「仅打印态生效」的隐藏类打印结束由 restorePrintLayout() 摘掉。 * 2. 明确跳过「祖先」若某元素内部**包含**页元素说明它是简历的外壳一律不碰。 * 宁可漏隐藏一个水印也不能动简历本身。 */ function hideWatermarks(pages) { if (!CONFIG.hideWatermark) return 0; let n 0; document.querySelectorAll([class*watermark], [id*watermark]).forEach(el { if (pages.some(p el ! p el.contains(p))) return; // 是简历祖先跳过 el.classList.add(BADGE_CLASS); n; }); return n; } /* --------------------------------------------------------------------- * 4. 打印主流程 * ------------------------------------------------------------------- */ let printing false; function runPrint() { const pages applyPrintLayout(); if (!pages) { toast(没找到简历内容。\n请先进入简历预览页能看到白色纸张再点导出。, true); return; } let msg 检测到 ${pages.length} 页\n正在唤起打印…; if (lastOverflow.length) { msg \n⚠️ 第 ${lastOverflow.join( / )} 页内容超出一页 A4 超出部分会被裁掉请先在 CodeCV 里精简该页; console.warn([CodeCV] 内容超出一页 A4 的页码, lastOverflow); } toast(msg, lastOverflow.length 0); requestAnimationFrame(() { setTimeout(() { if (printing) return; printing true; window.print(); setTimeout(() { printing false; restorePrintLayout(); }, 1500); }, 60); }); } /* --------------------------------------------------------------------- * 5. UI * ------------------------------------------------------------------- */ function toast(msg, warn) { let el document.getElementById(TOAST_ID); if (!el) { el document.createElement(div); el.id TOAST_ID; document.body.appendChild(el); } el.textContent msg; el.className show (warn ? warn : ); clearTimeout(el._t); el._t setTimeout(() { el.className warn ? warn : ; }, 3200); } function mountButtons() { if (document.body !document.getElementById(BTN_ID)) { const btn document.createElement(button); btn.id BTN_ID; btn.type button; btn.textContent ️ 导出完美简历; btn.title 一键导出 A4 无水印 PDF支持多页; btn.addEventListener(click, runPrint); document.body.appendChild(btn); } if (document.body !document.getElementById(DIAG_ID)) { const d document.createElement(button); d.id DIAG_ID; d.type button; d.textContent 诊断; d.title 点一下把页面结构信息复制到剪贴板导出有问题时用; d.addEventListener(click, dump); document.body.appendChild(d); } } /* --------------------------------------------------------------------- * 6. 钩子直接按 CtrlP 也生效 * ------------------------------------------------------------------- */ window.addEventListener(beforeprint, () { applyPrintLayout(); }); window.addEventListener(afterprint, () { printing false; restorePrintLayout(); }); /* --------------------------------------------------------------------- * 7. 诊断 dump —— 点「诊断」按钮即可会把结构复制到剪贴板 * ------------------------------------------------------------------- */ function describe(el) { if (!el) return null; let cls ; try { cls (typeof el.className string ? el.className : ).trim().replace(/\s/g, .); } catch (e) { cls ; } return el.tagName.toLowerCase() (cls ? . cls.slice(0, 90) : ); } function dump() { const pages detectPages(); const L []; const push s L.push(s); push( CodeCV 导出脚本 结构诊断 (v4.1) ); push(URL: location.href); push(窗口: window.innerWidth x window.innerHeight dpr window.devicePixelRatio); push(探测到页数: pages.length); if (pages.length) { const of checkOverflow(pages); push(内容超出一页 A4 的页码: (of.length ? of.join(, ) ← 这些页会被裁掉 : 无)); } push(); if (!pages.length) push(!! 没探测到页元素请检查 CONFIG.pageSelector); pages.forEach((el, i) { const cs getComputedStyle(el); const r el.getBoundingClientRect(); push(--- 第 ${i 1} 页 ---); push( 选择器: describe(el)); push( rect: top${r.top.toFixed(0)} left${r.left.toFixed(0)} w${r.width.toFixed(0)} h${r.height.toFixed(0)}); push( offsetTop${el.offsetTop} offsetLeft${el.offsetLeft}); push( position${cs.position} top${cs.top} left${cs.left} height${cs.height} overflow${cs.overflow}); push( transform${cs.transform}); push( margin${cs.margin}); push( padding${cs.padding}); push( offsetParent: ${describe(el.offsetParent)}); push( 父节点: ${describe(el.parentElement)}); push( 文本开头: ${(el.innerText || ).replace(/\s/g, ).trim().slice(0, 45)}); }); if (pages.length) { push(); push(--- 第 1 页的祖先链往上到 body---); let el pages[0].parentElement; let d 0; while (el el ! document.body d 12) { const cs getComputedStyle(el); push( ${..repeat(d)}${describe(el)} position${cs.position} height${cs.height} overflow${cs.overflow} transform${cs.transform} bg${cs.backgroundColor} margin${cs.margin}); el el.parentElement; d; } const bcs getComputedStyle(document.body); push( body margin${bcs.margin} bg${bcs.backgroundColor} scrollHeight${document.body.scrollHeight}); } push(); push(--- 深色大块背景排查页间黑条---); let dark 0; if (document.body) { for (const el of document.body.querySelectorAll(*)) { const cs getComputedStyle(el); const m cs.backgroundColor.match(/rgba?\((\d),\s*(\d),\s*(\d)/); if (!m) continue; const lum 0.299 * m[1] 0.587 * m[2] 0.114 * m[3]; const r el.getBoundingClientRect(); if (lum 130 r.width 300 r.height 20) { push( ${describe(el)} bg${cs.backgroundColor} size${r.width.toFixed(0)}x${r.height.toFixed(0)}); if (dark 12) break; } } } if (!dark) push( 无); push(); push(--- position:fixed / sticky 元素仅列出供排查浮层---); let fx 0; if (document.body) { for (const el of document.body.querySelectorAll(*)) { const cs getComputedStyle(el); if (cs.position fixed || cs.position sticky) { const t (el.innerText || ).replace(/\s/g, ).trim().slice(0, 28); const r el.getBoundingClientRect(); push( ${describe(el)} ${cs.position} ${r.width.toFixed(0)}x${r.height.toFixed(0)} 文本${t}); if (fx 20) break; } } } if (!fx) push( 无); push(); push( 诊断结束 ); const text L.join(\n); console.log(text); try { navigator.clipboard.writeText(text).then( () toast(诊断信息已复制到剪贴板直接粘贴发我即可), () toast(已输出到控制台请手动复制F12 → Console, true) ); } catch (e) { toast(已输出到控制台请手动复制F12 → Console, true); } return text; } window.__codecvExport { print: runPrint, dump: dump, detectPages: detectPages, checkOverflow: checkOverflow, // 返回「内容超出一页 A4」的页码数组 layout: applyPrintLayout, // 手动套用打印布局不弹打印框十一、协议与致谢原脚本CodeCV 简历一键无水印导出byCampusMIT License 发布地址https://greasyfork.org/scripts/575852本版本基于原脚本修改同样以MIT License发布并保留原作者版权声明修复版仓库XXWen0201/codecv-export问题反馈走 Issues本文为原创内容代码部分为原项目的衍生作品依据 MIT 协议修改与再分发MIT 协议允许修改和再发布但要求保留原作者的版权声明与许可证。如果你是二次修改后再发布请同样保留这一段。
返回列表