]> acesimba.cloud Git - StationBuilder.git/commitdiff
设计方案导出改造:Word(微软雅黑/黑边/页脚页码/目录域/240px) + PDF(html2pdf带页码)
authorwang.yu <wang.yu@advantech.com>
Wed, 19 Aug 2026 08:43:41 +0000 (16:43 +0800)
committerwang.yu <wang.yu@advantech.com>
Wed, 19 Aug 2026 08:43:41 +0000 (16:43 +0800)
public/design-doc.html

index ea0bfca4ebbbf4a9f60eed4ffd3309899e83acb5..e0574da3b4aacb648beec23efec87046b40d57dd 100644 (file)
@@ -4,7 +4,9 @@
   - 本文档是 StationBuilder 的产品/交互设计文档,单一 HTML 源,位于 public/design-doc.html,
     运行时通过顶栏「设计方案」链接(/design-doc.html)打开。
   - 每次功能改动后,请同步更新对应章节;新增/遗留的未实现能力用 <span class="todo">尚未实现</span> 标注。
-  - 顶部三个按钮可分别导出 PDF(浏览器打印)、Word(.doc)、Markdown(由本页 DOM 实时转换)。
+  - 顶部三个按钮分别导出:PDF(html2pdf.js 生成,带「第 X 页 / 共 Y 页」页脚页码,截图限 240px 宽)、
+    Word(.doc:微软雅黑、纯黑文字、黑色表格边框、1.5 倍行距、页脚页码域、默认 TOC 目录域、截图 240px 宽)、
+    Markdown(由本页 DOM 实时转换,不含图片)。
 -->
 <html lang="zh-CN">
 <head>
     main#doc{ padding:18px 14px 80px; }
   }
   @media print{ .doc-toolbar, .doc-nav{ display:none; } main#doc{ max-width:none; padding:0; width:100%; }
+    body, main#doc, main#doc *{ font-family:"Microsoft YaHei","微软雅黑",SimSun,sans-serif !important; color:#000 !important; line-height:1.5 !important; }
+    table{ border-collapse:collapse; border:1px solid #000 !important; }
+    th, td{ border:1px solid #000 !important; }
+    th{ background:#f2f2f2 !important; }
     .figure{ page-break-inside:avoid; break-inside:avoid; }
-    .figure img{ max-width:100%; width:auto; }
+    .figure img{ max-width:240px !important; width:auto !important; height:auto !important; }
     table{ page-break-inside:avoid; break-inside:avoid; }
     tr{ page-break-inside:avoid; break-inside:avoid; }
     h2,h3,h4{ page-break-after:avoid; break-after:avoid; }
   .changelog th{ background:#f6f7f8; color:#4b4f53; text-align:left; font-size:11px; font-weight:bold; border-bottom:1px solid var(--line); padding:8px 10px; }
   .changelog td{ border-bottom:1px solid #e3e5e7; padding:9px 10px; font-size:11px; vertical-align:top; }
   .changelog td:first-child{ white-space:nowrap; font-weight:bold; color:#008ed2; }
+  /* 导出 PDF 时作用于克隆容器,不影响屏幕 UI */
+  .export-pdf{ background:#fff; padding:20px 22px; width:780px; box-sizing:border-box; }
+  .export-pdf, .export-pdf *{ font-family:"Microsoft YaHei","微软雅黑",SimSun,sans-serif !important; color:#000 !important; line-height:1.5 !important; }
+  .export-pdf table{ border-collapse:collapse; border:1px solid #000; width:100%; margin:10px 0; }
+  .export-pdf th, .export-pdf td{ border:1px solid #000; padding:6px 9px; text-align:left; vertical-align:top; }
+  .export-pdf th{ font-weight:bold; background:#f2f2f2; }
+  .export-pdf .figure{ border:1px solid #000; background:#fff; }
+  .export-pdf .figure img{ max-width:240px; width:auto; height:auto; }
+  .export-pdf .doc-toc{ margin:0 0 18px; page-break-after:avoid; }
+  .export-pdf .doc-toc h2{ border-bottom:2px solid #000; }
+  .export-pdf .doc-toc ul{ list-style:none; padding-left:0; margin:8px 0; }
+  .export-pdf .doc-toc li{ margin:4px 0; }
+  .export-pdf .doc-toc li.sub{ padding-left:22px; font-size:12.5px; }
 </style>
 </head>
 <body>
 </main>
 </div>
 
+<!-- PDF 导出依赖 html2pdf(jsPDF + html2canvas);若加载失败,downloadPDF 会回退到浏览器打印 -->
+<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.2/html2pdf.bundle.min.js"></script>
 <script>
 // 自动生成左侧目录(TOC)并实现滚动高亮
 (function buildToc(){
@@ -801,7 +822,64 @@ function saveBlob(blob, filename){
   document.body.appendChild(a); a.click(); a.remove();
   setTimeout(()=>URL.revokeObjectURL(url), 60000);
 }
-function downloadPDF(){ window.print(); }
+function buildStaticToc(){
+  const main = document.getElementById('doc');
+  if(!main) return '';
+  const heads = main.querySelectorAll('h2, h3');
+  let html = '<div class="doc-toc"><h2>目录</h2><ul>';
+  heads.forEach(function(h){
+    const sub = h.tagName.toLowerCase()==='h3' ? ' class="sub"' : '';
+    const text = (h.textContent||'').replace(/</g,'&lt;');
+    html += '<li'+sub+'>'+text+'</li>';
+  });
+  html += '</ul></div>';
+  return html;
+}
+function downloadPDF(){
+  if(typeof html2pdf === 'undefined'){
+    alert('PDF 生成库未加载(可能无网络),已改用浏览器打印;如需自动页码,请在打印对话框勾选「页眉和页脚」。');
+    window.print();
+    return;
+  }
+  const src = document.getElementById('doc');
+  const clone = src.cloneNode(true);
+  clone.removeAttribute('id');
+  clone.classList.add('export-pdf');
+  clone.querySelectorAll('[id]').forEach(e=>e.removeAttribute('id'));
+  clone.querySelectorAll('img').forEach(function(img){
+    img.style.maxWidth='240px'; img.style.height='auto'; img.style.display='block'; img.style.margin='0 auto';
+  });
+  clone.insertAdjacentHTML('afterbegin', buildStaticToc());
+  const holder = document.createElement('div');
+  holder.style.position='fixed'; holder.style.left='-10000px'; holder.style.top='0';
+  holder.appendChild(clone);
+  document.body.appendChild(holder);
+
+  const opt = {
+    margin: [12, 10, 16, 10],
+    filename: 'StationBuilder-设计方案.pdf',
+    image: { type:'jpeg', quality:0.98 },
+    html2canvas: { scale:2, useCORS:true, backgroundColor:'#ffffff', logging:false },
+    jsPDF: { unit:'mm', format:'a4', orientation:'portrait' },
+    pagebreak: { mode:['avoid-all','css','legacy'] }
+  };
+  html2pdf().set(opt).from(clone).toPdf().get('pdf').then(function(pdf){
+    const total = pdf.internal.getNumberOfPages();
+    const w = pdf.internal.pageSize.getWidth();
+    const h = pdf.internal.pageSize.getHeight();
+    for(let i=1;i<=total;i++){
+      pdf.setPage(i);
+      pdf.setFontSize(9);
+      pdf.setTextColor(0,0,0);
+      pdf.text('第 '+i+' 页 / 共 '+total+' 页', w/2, h-8, { align:'center' });
+    }
+    pdf.save('StationBuilder-设计方案.pdf');
+  }).catch(function(e){
+    alert('PDF 生成失败:'+(e&&e.message||e));
+  }).finally(function(){
+    if(holder.parentNode) holder.parentNode.removeChild(holder);
+  });
+}
 function blobToDataURL(blob){
   return new Promise((resolve,reject)=>{
     const r=new FileReader();
@@ -822,11 +900,41 @@ async function downloadWord(){
       if(!resp.ok) return;
       const du = await blobToDataURL(await resp.blob());
       img.setAttribute('src', du);
-      img.style.maxWidth = '360px';
+      img.style.maxWidth = '240px';
+      img.style.height = 'auto';
+      img.style.display = 'block';
+      img.style.margin = '0 auto';
     }catch(e){ /* 失败则保留原相对路径,不影响导出其它内容 */ }
   }));
-  const head = '<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w="urn:schemas-microsoft-com:office:word" xmlns="http://www.w3.org/TR/REC-html40"><head><meta charset="utf-8"></head><body>';
-  const blob = new Blob([''+head+clone.innerHTML+'</body></html>'], {type:'application/msword;charset=utf-8'});
+  // 默认插入目录:Word TOC 域,打开后自动生成带页码、可点击跳转的目录
+  const tocField =
+    '<h2 style="border:none">目录</h2>' +
+    '<!--[if supportFields]><span style="mso-element:field-begin"></span>TOC \\o "1-3" \\h \\z \\u<span style="mso-element:field-separator"></span><![endif]-->' +
+    '(若目录为空,请右键「更新域」或按 F9 刷新)' +
+    '<!--[if supportFields]><span style="mso-element:field-end"></span><![endif]-->';
+  const styleCss = [
+    'body, div, p, span, td, th, li, h1, h2, h3, h4, a {',
+    '  font-family:"Microsoft YaHei","微软雅黑",SimSun,sans-serif;',
+    '  color:#000000; line-height:1.5;',
+    '}',
+    'body{ font-size:10.5pt; }',
+    'h1{ font-size:22pt; } h2{ font-size:15pt; } h3{ font-size:13pt; } h4{ font-size:11pt; }',
+    'table{ border-collapse:collapse; border:1px solid #000000; width:100%; }',
+    'td, th{ border:1px solid #000000; padding:4pt 6pt; text-align:left; vertical-align:top; }',
+    'th{ font-weight:bold; background:#f2f2f2; }',
+    'img{ max-width:240px; height:auto; }',
+    '@page Section1{ size:595.45pt 841.7pt; margin:36.0pt 36.0pt 40.0pt 36.0pt; mso-header-margin:35.4pt; mso-footer-margin:35.4pt; mso-paper-source:0; }',
+    'div.Section1{ page:Section1; }',
+    'p.MsoFooter{ text-align:center; font-size:9.0pt; color:#000000; }'
+  ].join('\n');
+  const mso = '<!--[if gte mso 9]><xml><w:WordDocument><w:View>Print</w:View><w:Zoom>90</w:Zoom><w:UpdateFields>true</w:UpdateFields></w:WordDocument></xml><![endif]-->';
+  const html =
+    '<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w="urn:schemas-microsoft-com:office:word" xmlns="http://www.w3.org/TR/REC-html40">' +
+    '<head><meta charset="utf-8"><style>' + styleCss + '</style>' + mso + '</head><body>' +
+    '<div style="mso-element:footer" class="MsoFooter"><p class="MsoFooter">第 <span style="mso-field-code:PAGE">1</span> 页 / 共 <span style="mso-field-code:NUMPAGES">1</span> 页</p></div>' +
+    '<div class="Section1">' + tocField + clone.innerHTML + '</div>' +
+    '</body></html>';
+  const blob = new Blob([html], {type:'application/msword;charset=utf-8'});
   saveBlob(blob, 'StationBuilder-设计方案.doc');
 }
 function inlineOf(node){