中国裁判文书网一键转存

快速复制标题、内容,并支持一键生成 Word/PNG/PDF 文件

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         中国裁判文书网一键转存
// @version      1.3
// @description  快速复制标题、内容,并支持一键生成 Word/PNG/PDF 文件
// @author       旋转突进的蓝色枪兵
// @match        https://wenshu.court.gov.cn/website/wenshu/*/index.html*
// @grant        GM_setClipboard
// @grant        GM_download
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/html2canvas.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/jspdf.umd.min.js
// @license      MIT
// @namespace    https://greasyfork.org/users/1529400
// ==/UserScript==

(() => {
  'use strict';

  /* ---------- 通用函数 ---------- */
  const getContent = cls => document.querySelector(`.${cls}`)?.innerText.trim() ?? '';
  const sanitizeFileName = n => n.replace(/[\\/:*?"<>|]/g, '_');

  /* ---------- Word 生成 ---------- */
  async function createWordBlob(title, body) {
    const esc = t => t.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
    const paras = body.split('\n').filter(l => l.trim());
    const bodyXml = paras.map(p => `
      <w:p><w:pPr><w:jc w:val="left"/><w:spacing w:after="60" w:line="480" w:lineRule="exact"/>
        <w:ind w:firstLineChars="200"/><w:rPr><w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" w:eastAsia="仿宋"/>
        <w:sz w:val="28"/><w:szCs w:val="28"/></w:rPr></w:pPr>
        <w:r><w:rPr><w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" w:eastAsia="仿宋"/>
        <w:sz w:val="28"/><w:szCs w:val="28"/></w:rPr><w:t>${esc(p)}</w:t></w:r></w:p>`).join('');
    const titleXml = `
      <w:p><w:pPr><w:jc w:val="center"/><w:spacing w:after="200"/>
        <w:rPr><w:rFonts w:ascii="方正小标宋简体" w:eastAsia="方正小标宋简体"/><w:b/>
        <w:sz w:val="44"/><w:szCs w:val="44"/></w:rPr></w:pPr>
        <w:r><w:rPr><w:rFonts w:ascii="方正小标宋简体" w:eastAsia="方正小标宋简体"/><w:b/>
        <w:sz w:val="44"/><w:szCs w:val="44"/></w:rPr><w:t>${esc(title)}</w:t></w:r></w:p>`;
    const docXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
  <w:body>${titleXml}${bodyXml}<w:sectPr><w:pgSz w:w="11906" w:h="16838"/>
  <w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440"/></w:sectPr></w:body></w:document>`;
    if (typeof JSZip === 'undefined') await import('https://cdn.jsdelivr.net/npm/jszip@3/dist/jszip.min.js');
    const zip = new JSZip();
    zip.file('[Content_Types].xml', `<?xml version="1.0"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
  <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
  <Default Extension="xml" ContentType="application/xml"/>
  <Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
</Types>`);
    zip.file('_rels/.rels', `<?xml version="1.0"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
</Relationships>`);
    zip.file('word/_rels/document.xml.rels', `<?xml version="1.0"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"></Relationships>`);
    zip.file('word/document.xml', docXml);
    return zip.generateAsync({type: 'blob'});
  }

  /* ---------- 界面插入 ---------- */
  const hbox = document.querySelector('.header_box');
  if (!hbox) return;

  const addBtn = (text, parent) => {
    const div = document.createElement('div'); div.className = 'fl date';
    const btn = document.createElement('button'); btn.textContent = text;
    div.appendChild(btn); parent.appendChild(div); return btn;
  };
  const btnTitle = addBtn('复制标题', hbox);
  const btnContent = addBtn('复制内容', hbox);
  const btnWord = addBtn('创建文件', hbox);

  const lineShot = document.createElement('div'); lineShot.className = 'fl date';
  lineShot.style.display = 'flex'; lineShot.style.alignItems = 'center'; lineShot.style.gap = '6px';
  const btnShot = document.createElement('button'); btnShot.textContent = '创建截图';
  const selFmt = document.createElement('select');
  ['png', 'pdf'].forEach(f => { const o = document.createElement('option'); o.value = f; o.textContent = f.toUpperCase(); selFmt.appendChild(o); });
  selFmt.value = localStorage.getItem('userDefaultShotFmt') || 'png';
  lineShot.append(btnShot, selFmt); hbox.appendChild(lineShot);

  /* ---------- 事件 ---------- */
  btnTitle.onclick = () => { GM_setClipboard(getContent('PDF_title')); alert('标题已复制!'); };
  btnContent.onclick = () => { GM_setClipboard(getContent('PDF_pox')); alert('内容已复制!'); };
  btnWord.onclick = async () => {
    const title = sanitizeFileName(getContent('PDF_title')) || '未命名';
    const body = getContent('PDF_pox');
    if (!body) return alert('未获取到正文内容!');
    const blob = await createWordBlob(title, body);
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob); a.download = title + '.docx'; a.click();
    URL.revokeObjectURL(a.href);
  };

  btnShot.onclick = async () => {
    const fmt = selFmt.value;
    localStorage.setItem('userDefaultShotFmt', fmt);
    const node = document.querySelector('.PDF_box');
    if (!node) return alert('未找到 PDF_box 节点');
    try {
      const canvas = await html2canvas(node, {
        scale: 2,
        useCORS: true,
        backgroundColor: '#ffffff'
      });
      const title = sanitizeFileName(getContent('PDF_title')) || '截图';
      const fileName = `${title}.${fmt}`;

      if (fmt === 'png') {
        canvas.toBlob(blob => downBlob(blob, fileName), 'image/png');
      } else {
        const imgData = canvas.toDataURL('image/jpeg', 0.9);
        const imgWidth = 595.28; // A4 宽度(单位:pt)
        const imgHeight = (canvas.height * imgWidth) / canvas.width; // 等比缩放高度

        const pageHeight = 841.89; // A4 高度(单位:pt)
        let heightLeft = imgHeight;
        let position = 0;

        const pdf = new jspdf.jsPDF('p', 'pt', 'a4');

        pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
        heightLeft -= pageHeight;

        while (heightLeft >= 0) {
            position -= pageHeight;
            pdf.addPage();
            pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
            heightLeft -= pageHeight;
        }

        downBlob(pdf.output('blob'), fileName);
      }
    } catch (e) { alert('截图失败:' + e.message); }
  };

  const downBlob = (blob, fileName) => {
    if (typeof GM_download !== 'undefined') {
      const url = URL.createObjectURL(blob);
      GM_download({ url, name: fileName, saveAs: false, onload: () => URL.revokeObjectURL(url) });
    } else {
      const a = document.createElement('a');
      a.href = URL.createObjectURL(blob); a.download = fileName; a.click();
      URL.revokeObjectURL(a.href);
    }
  };
})();