智能排版小工具

站长可乐
2月24日发布

mm0nfh3e.png
本工具旨在帮助您快速整理老师们提交的德育案例、校刊稿件、教学手册等 Word 文档,自动识别标题层级,并允许您对文字样式进行局部调整,最终导出格式规范的 Word 或 PDF 文件。
✨ 主要功能:
支持上传 ZIP 压缩包(内含 .docx 文件或嵌套 ZIP),自动解压并提取所有文档。自动识别常见的标题序号(如“一、”“(一)”“1.”“1.1”等)及无序号标题(如“背景:”“总结”),并按首次出现顺序分配层级。在预览区可以直接修改文字内容(点击文字即可编辑)。用鼠标选中文字后,可通过操作台单独修改选中部分的字体、字号、颜色。将当前预览内容(含图片、样式)导出为 .doc 文件,可用 Microsoft Word 打开。通过浏览器打印功能生成 PDF 文件,保留所有排版和图片。

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>智能校刊排版 · 选中编辑版</title>
    <style>
        * { box-sizing: border-box; }
        body {
            font-family: 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
            background-color: #f5f3f0;
            margin: 0;
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 20px;
        }
        .card {
            max-width: 1300px;
            width: 100%;
            background: white;
            border-radius: 28px;
            box-shadow: 0 25px 50px -12px rgba(0,0,0,0.3);
            padding: 35px;
        }
        h1 {
            margin-top: 0;
            color: #3a2c1b;
            font-weight: 400;
            border-left: 6px solid #b78c5a;
            padding-left: 20px;
            font-size: 2.2rem;
        }
        .sub {
            color: #6b5a48;
            margin-bottom: 30px;
            font-style: italic;
        }
        .upload-area {
            border: 2px dashed #c9b69c;
            border-radius: 32px;
            padding: 40px 20px;
            text-align: center;
            background: #fefcf9;
            transition: background 0.2s;
            cursor: pointer;
        }
        .upload-area:hover { background: #f9f3ea; }
        .upload-icon { font-size: 48px; margin-bottom: 10px; color: #b78c5a; }
        /* 操作台样式 */
        .toolbar {
            background: #f0e9e0;
            border-radius: 50px;
            padding: 15px 25px;
            margin: 20px 0;
            display: flex;
            flex-wrap: wrap;
            gap: 15px;
            align-items: center;
            justify-content: center;
        }
        .tool-item {
            display: flex;
            align-items: center;
            gap: 8px;
        }
        .tool-item label {
            font-weight: 500;
            color: #5e4b36;
            font-size: 0.9rem;
        }
        .tool-item select, .tool-item input[type="number"], .tool-item input[type="color"] {
            padding: 6px 10px;
            border: 1px solid #dcd0c0;
            border-radius: 30px;
            background: white;
            font-size: 0.9rem;
        }
        .tool-item input[type="number"] {
            width: 70px;
        }
        .style-btn {
            background: #b78c5a;
            color: white;
            border: none;
            padding: 6px 18px;
            border-radius: 30px;
            cursor: pointer;
            font-weight: 500;
            font-size: 0.9rem;
            margin-left: 5px;
        }
        .style-btn:hover {
            background: #a07344;
        }
        .btn-group {
            display: flex;
            gap: 15px;
            flex-wrap: wrap;
            margin: 20px 0 20px;
            justify-content: center;
        }
        button {
            background: #b78c5a;
            border: none;
            color: white;
            padding: 12px 30px;
            border-radius: 40px;
            font-size: 1.1rem;
            font-weight: 500;
            cursor: pointer;
            box-shadow: 0 8px 16px -4px rgba(140,90,40,0.3);
            transition: all 0.2s;
            border: 1px solid transparent;
            min-width: 140px;
        }
        button:disabled {
            opacity: 0.4;
            pointer-events: none;
            filter: grayscale(0.5);
        }
        button.secondary {
            background: #ece3d9;
            color: #5e4b36;
            box-shadow: none;
        }
        button.secondary:hover { background: #dfd2c2; }
        button:hover { background: #a07344; transform: translateY(-2px); }

        .preview {
            background: #fefcf9;
            border-radius: 20px;
            border: 1px solid #e7dccc;
            padding: 25px;
            margin-top: 20px;
        }
        .preview h3 {
            margin-top: 0;
            color: #4e3e2b;
            font-weight: 400;
            display: flex;
            align-items: center;
            gap: 8px;
        }
        /* 预览容器 */
        .preview-content {
            background: white;
            border-radius: 16px;
            padding: 2cm 2cm 2cm 2cm;
            max-height: 600px;
            overflow-y: auto;
            box-shadow: inset 0 0 0 1px #eee7de;
            font-family: '宋体', 'SimSun', serif;
            font-size: 10.5pt;
            line-height: 1.5;
            outline: none;
        }
        /* 标题样式(基础结构,字体大小固定) */
        .preview-content .chapter-title {
            font-family: '黑体', 'Hei', sans-serif;
            font-size: 18pt;
            font-weight: bold;
            text-align: center;
            margin: 24pt 0 18pt 0;
        }
        .preview-content .h1 {
            font-family: '黑体', 'Hei', sans-serif;
            font-size: 16pt;
            font-weight: bold;
            margin: 18pt 0 12pt 0;
        }
        .preview-content .h2 {
            font-family: '黑体', 'Hei', sans-serif;
            font-size: 14pt;
            font-weight: bold;
            margin: 12pt 0 6pt 0;
        }
        .preview-content .h3 {
            font-family: '黑体', 'Hei', sans-serif;
            font-size: 12pt;
            font-weight: bold;
            margin: 6pt 0 3pt 0;
        }
        .preview-content .h4 {
            font-family: '黑体', 'Hei', sans-serif;
            font-size: 11pt;
            font-weight: bold;
            margin: 3pt 0 3pt 0;
        }
        .preview-content .h5 {
            font-family: '黑体', 'Hei', sans-serif;
            font-size: 10.5pt;
            font-weight: bold;
            margin: 3pt 0 3pt 0;
        }
        .preview-content p {
            font-family: '宋体', 'SimSun', serif;
            font-size: 10.5pt;
            line-height: 1.5;
            margin: 0 0 0 0;
            text-indent: 2em;
            text-align: justify;
        }
        .preview-content .image-container {
            text-align: center;
            margin: 12pt 0;
        }
        .preview-content .image-container img {
            max-width: 100%;
            height: auto;
            display: block;
            margin: 0 auto;
        }
        .status {
            margin: 15px 0;
            padding: 12px;
            border-radius: 40px;
            background: #f0e9e0;
            color: #3f352b;
            text-align: center;
            font-weight: 500;
            min-height: 60px;
            display: flex;
            align-items: center;
            justify-content: center;
            word-break: break-word;
        }
        .footer-note {
            margin-top: 20px;
            font-size: 0.8rem;
            color: #9b8c7a;
            text-align: center;
        }
        hr { border: none; border-top: 1px solid #e2d7cb; margin: 20px 0; }
        @media print {
            body * { visibility: hidden; }
            .printable-area, .printable-area * { visibility: visible; }
            .printable-area {
                position: absolute;
                left: 0;
                top: 0;
                width: 100%;
                background: white;
                padding: 2cm 2cm 2cm 2cm !important;
                margin: 0;
                box-shadow: none;
                max-height: none;
                overflow: visible;
            }
            .toolbar, .btn-group, .upload-area, .preview h3, .status, .footer-note, hr {
                display: none !important;
            }
        }
    </style>
    <!-- 核心库 -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
    <script>if (typeof JSZip === 'undefined') document.write('<script src="https://unpkg.com/jszip@3.10.1/dist/jszip.min.js"><\/script>');</script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.6.0/mammoth.browser.min.js"></script>
    <script>if (typeof mammoth === 'undefined') document.write('<script src="https://unpkg.com/mammoth@1.6.0/mammoth.browser.min.js"><\/script>');</script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
    <script>if (typeof saveAs === 'undefined') document.write('<script src="https://unpkg.com/file-saver@2.0.5/dist/FileSaver.min.js"><\/script>');</script>
</head>
<body>
<div class="card">
    <h1>📚 智能校刊排版 · 选中编辑版</h1>
    <div class="sub">先选中文字,再点击操作台按钮修改选中内容的样式</div>
    <!-- 上传区域 -->
    <div class="upload-area" id="dropZone">
        <div class="upload-icon">📂</div>
        <div><strong>点击选择 或 拖拽ZIP到此</strong></div>
        <div style="font-size:0.9rem; margin-top:8px; color:#7f6e5b;">支持多级标题,图片自动居中</div>
        <input type="file" id="fileInput" accept=".zip,application/zip" style="display:none;">
    </div>
    <!-- 操作台(选中编辑版) -->
    <div class="toolbar" id="toolbar">
        <div class="tool-item">
            <label>字体</label>
            <select id="fontSelect">
                <option value="宋体">宋体</option>
                <option value="黑体">黑体</option>
                <option value="楷体">楷体</option>
                <option value="仿宋">仿宋</option>
                <option value="微软雅黑">微软雅黑</option>
            </select>
            <button class="style-btn" id="applyFontBtn">应用</button>
        </div>
        <div class="tool-item">
            <label>字号</label>
            <select id="fontSizeSelect">
                <option value="1">8pt (极小)</option>
                <option value="2">10pt (小)</option>
                <option value="3" selected>12pt (正常)</option>
                <option value="4">14pt (中)</option>
                <option value="5">18pt (大)</option>
                <option value="6">24pt (很大)</option>
                <option value="7">36pt (极大)</option>
            </select>
            <button class="style-btn" id="applySizeBtn">应用</button>
        </div>
        <div class="tool-item">
            <label>字色</label>
            <input type="color" id="colorInput" value="#000000">
            <button class="style-btn" id="applyColorBtn">应用</button>
        </div>
    </div>
    <!-- 状态和按钮组 -->
    <div class="status" id="statusMsg">⏳ 等待上传压缩包</div>

    <div class="btn-group">
        <button id="processBtn" disabled>🧠 智能顺序定级</button>
        <button id="downloadWordBtn" disabled class="secondary">📄 导出 Word (.doc)</button>
        <button id="printPdfBtn" disabled class="secondary">🖨️ 打印 / PDF</button>
    </div>
    <!-- 预览区域 (可编辑) -->
    <div class="preview">
        <h3>🔍 可编辑预览 (选中文字后修改样式)</h3>
        <div class="preview-content" id="previewContent" contenteditable="true">
            <p style="color:#b7a48e; text-align:center;">上传并解析后点击「智能顺序定级」生成预览</p>
        </div>
    </div>
    <div class="footer-note">※ 选中文字后点击操作台按钮,可单独修改选中部分的样式</div>
</div>
<script>
(function() {
    // DOM 元素
    const dropZone = document.getElementById('dropZone');
    const fileInput = document.getElementById('fileInput');
    const statusMsg = document.getElementById('statusMsg');
    const processBtn = document.getElementById('processBtn');
    const downloadWordBtn = document.getElementById('downloadWordBtn');
    const printPdfBtn = document.getElementById('printPdfBtn');
    const previewContent = document.getElementById('previewContent');
    // 操作台按钮
    const applyFontBtn = document.getElementById('applyFontBtn');
    const applySizeBtn = document.getElementById('applySizeBtn');
    const applyColorBtn = document.getElementById('applyColorBtn');
    const fontSelect = document.getElementById('fontSelect');
    const fontSizeSelect = document.getElementById('fontSizeSelect');
    const colorInput = document.getElementById('colorInput');
    // 全局数据
    let chapters = [];
    let hasError = false;
    // ---------- 选中编辑功能 ----------
    // 使用 mousedown 而不是 click,防止按钮点击导致选区丢失
    applyFontBtn.addEventListener('mousedown', (e) => {
        e.preventDefault(); // 阻止默认行为,避免失去焦点
        const font = fontSelect.value;
        document.execCommand('fontName', false, font);
    });
    applySizeBtn.addEventListener('mousedown', (e) => {
        e.preventDefault();
        const size = fontSizeSelect.value; // 1-7 的数字
        document.execCommand('fontSize', false, size);
    });
    applyColorBtn.addEventListener('mousedown', (e) => {
        e.preventDefault();
        const color = colorInput.value;
        document.execCommand('foreColor', false, color);
    });
    // ---------- 状态更新 ----------
    function setStatus(msg, isError = false, isWarning = false) {
        statusMsg.innerText = msg;
        if (isError) {
            statusMsg.style.background = '#ffded9';
            statusMsg.style.color = '#b33c2f';
        } else if (isWarning) {
            statusMsg.style.background = '#fff3d6';
            statusMsg.style.color = '#8a6d3b';
        } else {
            statusMsg.style.background = '#f0e9e0';
            statusMsg.style.color = '#3f352b';
        }
    }
    function escapeHtml(unsafe) {
        return unsafe.replace(/[&<>"']/g, function(m) {
            if(m === '&') return '&amp;'; if(m === '<') return '&lt;'; if(m === '>') return '&gt;';
            if(m === '"') return '&quot;'; return '&#039;';
        });
    }
    function extractTitleFromFilename(path) {
        let fileName = path.split('/').pop();
        fileName = fileName.replace(/\.docx$/i, '').replace(/[_-]/g, ' ');
        return fileName || '未命名案例';
    }
    // ---------- 递归解压ZIP ----------
    async function extractAllFiles(zipFile) {
        let fileEntries = [];
        try {
            const rootZip = await JSZip.loadAsync(zipFile);
            const queue = [{ zip: rootZip, pathPrefix: '' }];
            while (queue.length > 0) {
                const { zip, pathPrefix } = queue.shift();
                const entries = Object.keys(zip.files);
                for (const relPath of entries) {
                    const zipEntry = zip.files[relPath];
                    if (zipEntry.dir) continue;
                    const fullPath = pathPrefix ? `${pathPrefix}/${relPath}` : relPath;
                    try {
                        if (relPath.toLowerCase().endsWith('.zip')) {
                            const data = await zipEntry.async('arraybuffer');
                            const nestedZip = await JSZip.loadAsync(data);
                            queue.push({ zip: nestedZip, pathPrefix: fullPath.replace(/\.zip$/i, '') });
                        } else {
                            const data = await zipEntry.async('arraybuffer');
                            fileEntries.push({ path: fullPath, data: data });
                        }
                    } catch (innerErr) {
                        console.warn('跳过文件:', fullPath, innerErr);
                        setStatus(`⚠️ 文件 ${fullPath} 解压失败,已跳过`, false, true);
                    }
                }
            }
        } catch (err) {
            throw new Error('解压失败: ' + err.message);
        }
        return fileEntries;
    }
    // 提取 HTML(含图片)
    async function extractHtmlFromDocx(arrayBuffer) {
        const result = await mammoth.convertToHtml({ arrayBuffer });
        return result.value;
    }
    // 上传处理
    async function handleFileUpload(file) {
        setStatus('📦 正在解压...');
        hasError = false;
        try {
            const allFiles = await extractAllFiles(file);
            const docxFiles = allFiles.filter(f => f.path.toLowerCase().endsWith('.docx'));
            if (docxFiles.length === 0) {
                setStatus('❌ 未找到 .docx 文件', true);
                return false;
            }
            setStatus(`🔍 找到 ${docxFiles.length} 个文档,提取中...`);
            docxFiles.sort((a, b) => a.path.localeCompare(b.path, 'zh'));
            const chapterPromises = docxFiles.map(async (file) => {
                try {
                    const html = await extractHtmlFromDocx(file.data);
                    const plainText = html.replace(/<[^>]*>/g, '').replace(/\s+/g, ' ').trim();
                    const paragraphs = plainText.split(/\n/).filter(p => p.trim().length > 0);
                    return {
                        title: extractTitleFromFilename(file.path),
                        html: html,
                        paragraphs: paragraphs
                    };
                } catch (err) {
                    console.error('解析失败:', file.path, err);
                    hasError = true;
                    return {
                        title: extractTitleFromFilename(file.path),
                        html: '<p>文档解析错误</p>',
                        paragraphs: ['[文档解析错误]']
                    };
                }
            });
            chapters = await Promise.all(chapterPromises);
            if (hasError) {
                setStatus('✅ 解析完成(部分错误),可点击「智能顺序定级」', false, true);
            } else {
                setStatus(`✅ 解析完成,共 ${chapters.length} 章`);
            }
            return true;
        } catch (err) {
            setStatus('❌ 解压失败:' + err.message, true);
            return false;
        }
    }
    // ---------- 标题识别 ----------
    const headingKeywords = [
        '摘要','引言','前言','背景','目的','意义','对象','方法','过程','结果',
        '分析','讨论','结论','建议','反思','总结','附录','参考文献','注','说明',
        '关键词','概述','缘由','思路','举措','成效','思考','问题','对策','缘由',
        '基本情况','主要做法','取得成效','存在不足','下一步打算','案例描述',
        '案例反思','案例背景','案例过程','案例分析','案例启示','教育建议',
        '教育对策','教育反思','教育叙事','教育案例','德育案例','管理案例'
    ];
    function detectTitlePattern(text) {
        const trimmed = text.trim();
        if (!trimmed || trimmed.length > 80 || trimmed.length < 2) return null;
        const patterns = [
            { name: 'chinese_num', regex: /^\s*[一二三四五六七八九十百千]+[、..]/ },
            { name: 'paren_chinese', regex: /^\s*[((][一二三四五六七八九十百千]+[))]/ },
            { name: 'arabic_num', regex: /^\s*\d+[、..]/ },
            { name: 'paren_arabic', regex: /^\s*[((]\d+[))]/ },
            { name: 'multi_arabic', regex: /^\s*\d+(\.\d+)+[\s\.::]?/ }
        ];
        for (let p of patterns) if (p.regex.test(trimmed)) return p.name;
        if (/[。!?;]$/.test(trimmed)) return null;
        if (/[::]/.test(trimmed)) {
            let parts = trimmed.split(/[::]/);
            if (parts[0].length < 25) return 'plain_heading';
        }
        if (headingKeywords.includes(trimmed)) return 'plain_heading';
        for (let kw of headingKeywords) if (trimmed.startsWith(kw) && trimmed.length < 30) return 'plain_heading';
        return null;
    }
    function enhanceHeadingDetection(paragraphs) {
        let candidates = [];
        paragraphs.forEach((para, idx) => {
            let pattern = detectTitlePattern(para);
            if (pattern) candidates.push({ idx, pattern });
        });
        let order = [];
        let seen = new Set();
        candidates.forEach(c => {
            if (!seen.has(c.pattern)) {
                seen.add(c.pattern);
                order.push(c.pattern);
            }
        });
        const levelMap = {};
        order.forEach((p, i) => {
            if (i === 0) levelMap[p] = 'h1';
            else if (i === 1) levelMap[p] = 'h2';
            else if (i === 2) levelMap[p] = 'h3';
            else if (i === 3) levelMap[p] = 'h4';
            else levelMap[p] = 'h5';
        });
        let classified = paragraphs.map((text, idx) => {
            let cand = candidates.find(c => c.idx === idx);
            return { text, level: cand ? levelMap[cand.pattern] : 'body' };
        });
        for (let i = 0; i < classified.length; i++) {
            if (classified[i].level === 'body') {
                let t = classified[i].text;
                if (t.length < 40 && !/[。!?;]$/.test(t)) {
                    let prevLevel = i > 0 ? classified[i-1].level : null;
                    let nextLevel = i < classified.length-1 ? classified[i+1].level : null;
                    if (prevLevel && prevLevel.startsWith('h')) {
                        classified[i].level = prevLevel;
                    } else if (nextLevel && nextLevel.startsWith('h')) {
                        classified[i].level = nextLevel;
                    }
                }
            }
        }
        return classified;
    }
    // 构建预览HTML
    function buildPreviewHtmlFromChapters() {
        if (!chapters.length) return '<p style="color:#b7a48e;">请先上传并解析文档</p>';
        let html = '';
        for (const ch of chapters) {
            if (!ch.classified) continue;
            html += `<div class="chapter-title">${escapeHtml(ch.title)}</div>`;
            const tempDiv = document.createElement('div');
            tempDiv.innerHTML = ch.html;
            const children = Array.from(tempDiv.children);
            if (children.length === ch.classified.length) {
                for (let i = 0; i < children.length; i++) {
                    const elem = children[i];
                    const level = ch.classified[i].level;
                    if (level.startsWith('h')) {
                        const wrapper = document.createElement('div');
                        wrapper.className = level;
                        wrapper.innerHTML = elem.innerHTML;
                        html += wrapper.outerHTML;
                    } else {
                        html += elem.outerHTML;
                    }
                }
            } else {
                let content = ch.html
                    .replace(/<h1>/g, '<div class="h1">').replace(/<\/h1>/g, '</div>')
                    .replace(/<h2>/g, '<div class="h2">').replace(/<\/h2>/g, '</div>')
                    .replace(/<h3>/g, '<div class="h3">').replace(/<\/h3>/g, '</div>')
                    .replace(/<h4>/g, '<div class="h4">').replace(/<\/h4>/g, '</div>')
                    .replace(/<h5>/g, '<div class="h5">').replace(/<\/h5>/g, '</div>')
                    .replace(/<h6>/g, '<div class="h5">').replace(/<\/h6>/g, '</div>')
                    .replace(/<img([^>]+)>/g, '<div class="image-container"><img$1></div>');
                html += content;
            }
            html += `<div style="page-break-before: always; margin: 0;"></div>`;
        }
        return html;
    }
    function updatePreview() {
        let html = buildPreviewHtmlFromChapters();
        previewContent.innerHTML = html;
    }
    // 下载函数
    function downloadBlob(blob, filename) {
        return new Promise((resolve, reject) => {
            try {
                if (typeof window.saveAs === 'function') {
                    window.saveAs(blob, filename);
                    resolve();
                } else {
                    const url = window.URL.createObjectURL(blob);
                    const a = document.createElement('a');
                    a.href = url;
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                    setTimeout(() => {
                        document.body.removeChild(a);
                        window.URL.revokeObjectURL(url);
                        resolve();
                    }, 100);
                }
            } catch (err) {
                reject(err);
            }
        });
    }
    // 事件绑定
    dropZone.addEventListener('click', () => fileInput.click());
    dropZone.addEventListener('dragover', (e) => e.preventDefault());
    dropZone.addEventListener('drop', async (e) => {
        e.preventDefault();
        const file = e.dataTransfer.files[0];
        if (file && (file.type === 'application/zip' || file.name.endsWith('.zip'))) {
            const success = await handleFileUpload(file);
            if (success) {
                processBtn.disabled = false;
                downloadWordBtn.disabled = true;
                printPdfBtn.disabled = true;
            }
        } else {
            setStatus('请上传ZIP压缩包', true);
        }
    });
    fileInput.addEventListener('change', async (e) => {
        const file = e.target.files[0];
        if (file) {
            const success = await handleFileUpload(file);
            if (success) {
                processBtn.disabled = false;
                downloadWordBtn.disabled = true;
                printPdfBtn.disabled = true;
            }
        }
    });
    processBtn.addEventListener('click', () => {
        if (!chapters.length) {
            setStatus('请先上传有效压缩包', true);
            return;
        }
        try {
            for (let i = 0; i < chapters.length; i++) {
                chapters[i].classified = enhanceHeadingDetection(chapters[i].paragraphs);
            }
            updatePreview();
            downloadWordBtn.disabled = false;
            printPdfBtn.disabled = false;
            setStatus('🎯 标题识别完成,可选中文字修改样式');
        } catch (err) {
            setStatus('❌ 处理失败: ' + err.message, true);
        }
    });
    // 导出 Word
    downloadWordBtn.addEventListener('click', async () => {
        if (!chapters.length) { setStatus('没有可导出的内容', true); return; }
        setStatus('📄 正在准备导出 Word 文件...');
        try {
            const fullHtml = previewContent.innerHTML;
            const styles = document.querySelector('style').innerHTML;
            const fullDocument = `<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <style>
        ${styles}
        .preview-content { padding: 2cm; max-height: none; overflow: visible; }
        body { margin: 0; background: white; }
    </style>
</head>
<body>
    <div class="preview-content">
        ${fullHtml}
    </div>
</body>
</html>`;
            const blob = new Blob([fullDocument], { type: 'application/msword' });
            await downloadBlob(blob, '校刊排版.doc');
            setStatus('✅ 文档已导出为 .doc 格式');
        } catch (err) {
            console.error(err);
            setStatus('❌ 导出失败: ' + err.message, true);
        }
    });
    // 打印 PDF
    printPdfBtn.addEventListener('click', () => {
        if (!chapters.length) { setStatus('没有可打印的内容', true); return; }
        previewContent.classList.add('printable-area');
        setTimeout(() => window.print(), 50);
        window.addEventListener('afterprint', function onAfterPrint() {
            previewContent.classList.remove('printable-area');
            window.removeEventListener('afterprint', onAfterPrint);
        });
    });
    // 初始预览
    previewContent.innerHTML = '<p style="color:#b7a48e; text-align:center;">上传并解析后点击「智能顺序定级」预览</p>';
})();
</script>
</body>
</html>
© 版权声明
THE END
喜欢就支持一下吧
点赞 0 分享 收藏
评论 抢沙发
OωO
取消