{"id":232,"date":"2025-08-14T23:33:37","date_gmt":"2025-08-14T15:33:37","guid":{"rendered":"https:\/\/www.guanchengyu.com\/?page_id=232"},"modified":"2025-08-15T00:58:27","modified_gmt":"2025-08-14T16:58:27","slug":"titlegenerater","status":"publish","type":"page","link":"https:\/\/www.guanchengyu.com\/index.php\/homepage\/titlegenerater\/","title":{"rendered":"\u79d1\u6280\u62a5\u544a\u5904\u7406&#8211;\u6d4b\u8bd5V2"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>\ud83d\udcc4\u79d1\u6280\u62a5\u544a\u5904\u7406\u5de5\u5177\u7bb1\ud83d\udd27   \u2014\u2014\u90d1\u76db\u56fd\u8bbe\u8ba1\uff0c\u6d4b\u8bd5\u7248\u4e0d\u4ee3\u8868\u6700\u7ec8\u54c1\u8d28\uff1a\uff09<\/title>\n    <style>\n        * {\n            margin: 0;\n            padding: 0;\n            box-sizing: border-box;\n            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n        }\n\n        body {\n            background-color: #f5f7fa;\n            min-height: 100vh;\n            padding: 2rem 1rem;\n            color: #333;\n            line-height: 1.6;\n        }\n\n        .container {\n            max-width: 700px;\n            margin: 0 auto;\n            background: white;\n            border-radius: 12px;\n            box-shadow: 0 10px 25px rgba(0, 0, 0, 0.05), 0 6px 12px rgba(0, 0, 0, 0.03);\n            overflow: hidden;\n        }\n\n        .header {\n            background: linear-gradient(135deg, #007cba 0%, #005a87 100%);\n            color: white;\n            padding: 1.5rem 2rem;\n        }\n\n        .header h1 {\n            font-size: 1.5rem;\n            font-weight: 600;\n            margin-bottom: 0.5rem;\n        }\n\n        .header p {\n            opacity: 0.9;\n            font-size: 0.9rem;\n        }\n\n        \/* \u6807\u7b7e\u9875\u6837\u5f0f *\/\n        .tabs {\n            display: flex;\n            border-bottom: 1px solid #eee;\n        }\n\n        .tab {\n            padding: 1rem 1.5rem;\n            cursor: pointer;\n            transition: all 0.3s ease;\n            font-weight: 500;\n            color: #555;\n            border-bottom: 3px solid transparent;\n        }\n\n        .tab.active {\n            color: #007cba;\n            border-bottom-color: #007cba;\n        }\n\n        .tab:hover:not(.active) {\n            background-color: #f8fafc;\n            color: #333;\n        }\n\n        .tab-content {\n            padding: 2rem;\n            display: none;\n        }\n\n        .tab-content.active {\n            display: block;\n            animation: fadeIn 0.3s ease;\n        }\n\n        @keyframes fadeIn {\n            from { opacity: 0; transform: translateY(10px); }\n            to { opacity: 1; transform: translateY(0); }\n        }\n\n        \/* \u8868\u5355\u901a\u7528\u6837\u5f0f *\/\n        .form-group {\n            margin-bottom: 1.2rem;\n            position: relative;\n        }\n\n        label {\n            display: block;\n            margin-bottom: 0.5rem;\n            font-weight: 500;\n            color: #555;\n            font-size: 0.95rem;\n        }\n\n        label .step-number {\n            display: inline-flex;\n            align-items: center;\n            justify-content: center;\n            width: 22px;\n            height: 22px;\n            background: #007cba;\n            color: white;\n            border-radius: 50%;\n            margin-right: 8px;\n            font-size: 0.8rem;\n            font-weight: 600;\n        }\n\n        input[type=text],\n        input[type=file] {\n            width: 100%;\n            padding: 0.9rem;\n            border: 1px solid #ddd;\n            border-radius: 8px;\n            font-size: 1rem;\n            transition: all 0.3s ease;\n        }\n\n        input[type=file] {\n            padding: 0.8rem;\n            cursor: pointer;\n        }\n\n        input[type=file]::file-selector-button {\n            background: #f8f9fa;\n            border: 1px solid #ddd;\n            padding: 6px 12px;\n            border-radius: 4px;\n            margin-right: 10px;\n            cursor: pointer;\n            transition: all 0.2s ease;\n        }\n\n        input[type=file]::file-selector-button:hover {\n            background: #f1f3f4;\n        }\n\n        input:focus {\n            outline: none;\n            border-color: #007cba;\n            box-shadow: 0 0 0 3px rgba(0, 124, 186, 0.1);\n        }\n\n        input::placeholder {\n            color: #bbb;\n        }\n\n        button {\n            margin-top: 1.5rem;\n            width: 100%;\n            padding: 1rem;\n            font-size: 1rem;\n            font-weight: 500;\n            background: #007cba;\n            color: white;\n            border: none;\n            border-radius: 8px;\n            cursor: pointer;\n            transition: all 0.3s ease;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            gap: 8px;\n        }\n\n        button:hover {\n            background: #005a87;\n            transform: translateY(-2px);\n            box-shadow: 0 4px 12px rgba(0, 90, 135, 0.2);\n        }\n\n        button:active {\n            transform: translateY(0);\n        }\n\n        \/* \u8f93\u51fa\u533a\u57df\u6837\u5f0f *\/\n        .output-box {\n            margin-top: 1.5rem;\n            padding: 1rem;\n            background: #f8fafc;\n            border-radius: 8px;\n            border: 1px solid #eee;\n            font-weight: 500;\n            color: #2d3748;\n            word-break: break-all;\n            transition: all 0.3s ease;\n        }\n\n        .tooltip {\n            margin-top: 0.5rem;\n            font-size: 0.85rem;\n            color: #64748b;\n            font-weight: normal;\n        }\n\n        .required-mark {\n            color: #e53e3e;\n        }\n\n        .input-focus-effect {\n            position: relative;\n        }\n\n        .input-focus-effect::after {\n            content: '';\n            position: absolute;\n            left: 0;\n            bottom: 0;\n            width: 0;\n            height: 2px;\n            background-color: #007cba;\n            transition: width 0.3s ease;\n        }\n\n        .input-focus-effect:focus-within::after {\n            width: 100%;\n        }\n\n        \/* \u65e5\u5fd7\u533a\u57df\u6837\u5f0f *\/\n        #log {\n            margin-top: 1.5rem;\n            font-size: 0.9em;\n            color: #555;\n            white-space: pre-wrap;\n            max-height: 300px;\n            overflow-y: auto;\n            padding: 1rem;\n            background: #f8fafc;\n            border-radius: 8px;\n            border: 1px solid #eee;\n        }\n\n        .file-info {\n            margin-top: 8px;\n            font-size: 0.85rem;\n            color: #34a853;\n            padding-left: 30px;\n        }\n\n        .form-hint {\n            font-size: 0.8rem;\n            color: #80868b;\n            margin-top: 5px;\n            padding-left: 30px;\n        }\n\n        .progress-container {\n            margin-top: 1.5rem;\n            height: 6px;\n            background-color: #f1f3f4;\n            border-radius: 3px;\n            overflow: hidden;\n            display: none;\n        }\n\n        .progress-bar {\n            height: 100%;\n            background-color: #34a853;\n            width: 0%;\n            transition: width 0.3s ease;\n        }\n\n        \/* \u901a\u77e5\u63d0\u793a\u6837\u5f0f *\/\n        .notification {\n            position: fixed;\n            bottom: 20px;\n            left: 50%;\n            transform: translateX(-50%);\n            padding: 10px 20px;\n            border-radius: 4px;\n            color: white;\n            z-index: 1000;\n            opacity: 0;\n            transition: opacity 0.3s ease, transform 0.3s ease;\n            transform: translate(-50%, 20px);\n        }\n\n        .notification.success {\n            background-color: #48bb78;\n        }\n\n        .notification.error {\n            background-color: #e53e3e;\n        }\n\n        @media (max-width: 576px) {\n            .container {\n                max-width: 100%;\n            }\n            \n            .tab {\n                padding: 0.8rem 1rem;\n                font-size: 0.9rem;\n            }\n            \n            .tab-content {\n                padding: 1.5rem;\n            }\n            \n            input, button {\n                font-size: 0.95rem;\n            }\n        }\n    <\/style>\n<\/head>\n<body>\n    <div class=\"container\">\n        <div class=\"header\">\n            <h1>\u5de5\u5177\u7bb1\ud83d\udd27<\/h1>\n            <p>\u540c\u6d4e\u5927\u5b66\u6d4b\u7ed8\u4e0e\u5730\u7406\u4fe1\u606f\u5b66\u9662\u672c\u79d1\u751f\u79d1\u6280\u62a5\u544a\u8bc1\u660e\u6279\u91cf\u5236\u4f5c<\/p>\n        <\/div>\n        \n        <!-- \u6807\u7b7e\u9875\u5bfc\u822a -->\n        <div class=\"tabs\">\n            <div class=\"tab active\" data-tab=\"report-id\">\u62a5\u544aID\u751f\u6210\u5de5\u5177<\/div>\n            <div class=\"tab\" data-tab=\"certificate\">\u542c\u8bfe\u8bc1\u660e\u751f\u6210\u5206\u88c5\u5de5\u5177<\/div>\n        <\/div>\n        \n        <!-- \u62a5\u544aID\u751f\u6210\u5de5\u5177\u5185\u5bb9 -->\n        <div class=\"tab-content active\" id=\"report-id-content\">\n            <form id=\"report-form\" onsubmit=\"return false;\">\n                <div class=\"form-group input-focus-effect\">\n                    <label for=\"reportName\">\u62a5\u544a\u540d\u79f0 <span class=\"required-mark\">*<\/span><\/label>\n                    <input type=\"text\" id=\"reportName\" placeholder=\"\u62a5\u544a\u540d\u79f0\">\n                <\/div>\n\n                <div class=\"form-group input-focus-effect\">\n                    <label for=\"reporter\">\u62a5\u544a\u4eba <span class=\"required-mark\">*<\/span><\/label>\n                    <input type=\"text\" id=\"reporter\" placeholder=\"\u62a5\u544a\u4eba\u59d3\u540d\">\n                <\/div>\n\n                <div class=\"form-group input-focus-effect\">\n                    <label for=\"date\">\u65e5\u671f\u53f7 <span class=\"required-mark\">*<\/span><\/label>\n                    <input type=\"text\" id=\"date\" placeholder=\"\u4f8b\u5982\uff1a20250815\">\n                <\/div>\n\n                <div class=\"form-group input-focus-effect\">\n                    <label for=\"time\">\u5177\u4f53\u65f6\u95f4 <span class=\"required-mark\">*<\/span><\/label>\n                    <input type=\"text\" id=\"time\" placeholder=\"\">\n                <\/div>\n\n                <div class=\"form-group input-focus-effect\">\n                    <label for=\"location\">\u5730\u70b9 <span class=\"required-mark\">*<\/span><\/label>\n                    <input type=\"text\" id=\"location\" placeholder=\"\u62a5\u544a\u5730\u70b9\">\n                <\/div>\n\n                <div class=\"form-group input-focus-effect\">\n                    <label for=\"reportNumber\">\u62a5\u544a\u671f\u53f7 <span class=\"required-mark\">*<\/span><\/label>\n                    <input type=\"text\" id=\"reportNumber\" placeholder=\"\u62a5\u544a\u671f\u53f7\">\n                <\/div>\n\n                <button onclick=\"generateReportId()\">\n                    <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n                        <path d=\"M12 6V12L16 14\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/>\n                        <path d=\"M20 12C20 16.4183 16.4183 20 12 20C7.58172 20 4 16.4183 4 12C4 7.58172 7.58172 4 12 4C16.4183 4 20 7.58172 20 12Z\" stroke=\"currentColor\" stroke-width=\"2\"\/>\n                    <\/svg>\n                    \u751f\u6210\u62a5\u544a ID\n                <\/button>\n\n                <div class=\"output-box\" id=\"report-output\">\n                    \u62a5\u544a\ud83c\udd94\u5c06\u663e\u793a\u5728\u8fd9\u91cc\u2b05\ufe0f\n                    <div class=\"tooltip\">\u751f\u6210\u540e\u5c06\u81ea\u52a8\u590d\u5236\u5230\u526a\u8d34\u677f<\/div>\n                <\/div>\n            <\/form>\n        <\/div>\n        \n        <!-- \u542c\u8bfe\u8bc1\u660e\u751f\u6210\u5206\u88c5\u5de5\u5177 -->\n        <div class=\"tab-content\" id=\"certificate-content\">\n            <h3 style=\"margin-bottom: 1.5rem; color: #333; font-size: 1.2rem;\">\u542c\u8bfe\u8bc1\u660e\u751f\u6210\u5206\u88c5\u5de5\u5177<\/h3>\n            \n            <div class=\"form-group\">\n                <label>\n                    <span class=\"step-number\">\u2460<\/span>\n                    Excel \u95ee\u5377\u6587\u4ef6\n                <\/label>\n                <input type=\"file\" id=\"excelFile\" accept=\".xlsx,.xls\"\/>\n                <p class=\"form-hint\">\u652f\u6301 .xlsx \u548c .xls \u683c\u5f0f\uff0c\u9700\u5305\u542b\u62a5\u544a\u540d\u79f0\u3001\u59d3\u540d\u7b49\u5fc5\u8981\u5217<\/p>\n                <div id=\"excelFileInfo\" class=\"file-info\"><\/div>\n            <\/div>\n\n            <div class=\"form-group\">\n                <label>\n                    <span class=\"step-number\">\u2461<\/span>\n                    Word \u6a21\u677f\u6587\u4ef6\n                <\/label>\n                <input type=\"file\" id=\"wordFile\" accept=\".docx\"\/>\n                <p class=\"form-hint\">\u652f\u6301 .docx \u683c\u5f0f\u7684\u6a21\u677f\u6587\u4ef6<\/p>\n                <div id=\"wordFileInfo\" class=\"file-info\"><\/div>\n            <\/div>\n\n            <div class=\"form-group\">\n                <label>\n                    <span class=\"step-number\">\u2462<\/span>\n                    \u65e5\u671f\uff08\u53ef\u4e3a\u7a7a\uff0c\u9ed8\u8ba4\u4eca\u5929\uff09\n                <\/label>\n                <input type=\"text\" id=\"certDate\" placeholder=\"\u7559\u7a7a\u5219\u4f7f\u7528\u4eca\u5929\"\/>\n                <p class=\"form-hint\">\u5982\u586b\u5199\u8bf7\u4f7f\u75288\u4f4d\u683c\u5f0f\uff0c\u4f8b\u5982\uff1a20241020<\/p>\n            <\/div>\n\n            <div class=\"form-group\">\n                <label>\n                    <span class=\"step-number\">\u2463<\/span>\n                    \u671f\u53f7\uff08\u53ef\u4e3a\u7a7a\uff0c\u9ed8\u8ba4 1\uff09\n                <\/label>\n                <input type=\"text\" id=\"certPeriod\" placeholder=\"\u7559\u7a7a\u5219\u4e3a 1\"\/>\n            <\/div>\n\n            <button onclick=\"generateCertificates()\">\n                <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n                    <path d=\"M19 9H15V3H9V9H5L12 16L19 9Z\" fill=\"currentColor\"\/>\n                    <path d=\"M5 18V20H19V18H5Z\" fill=\"currentColor\"\/>\n                <\/svg>\n                \u4e00\u952e\u751f\u6210\u5e76\u6253\u5305\u4e0b\u8f7d\n            <\/button>\n\n            <div class=\"progress-container\">\n                <div class=\"progress-bar\" id=\"progressBar\"><\/div>\n            <\/div>\n\n            <pre id=\"log\">\u7b49\u5f85\u4e0a\u4f20\u6587\u4ef6\u4e2d\u2026<\/pre>\n        <\/div>\n    <\/div>\n\n    <!-- \u4f9d\u8d56\u5e93 -->\n    <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/xlsx@0.18.5\/dist\/xlsx.full.min.js\"><\/script>\n    <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/docxtemplater@3.37.12\/build\/docxtemplater.js\"><\/script>\n    <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/pizzip@3.1.4\/dist\/pizzip.min.js\"><\/script>\n    <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/pizzip@3.1.4\/dist\/pizzip-utils.min.js\"><\/script>\n    <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/file-saver@2.0.5\/dist\/FileSaver.min.js\"><\/script>\n    <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/jszip@3.10.1\/dist\/jszip.min.js\"><\/script>\n\n    <script>\n        \/\/ \u6807\u7b7e\u9875\u5207\u6362\u529f\u80fd\n        document.querySelectorAll('.tab').forEach(tab => {\n            tab.addEventListener('click', () => {\n                \/\/ \u79fb\u9664\u6240\u6709\u6fc0\u6d3b\u72b6\u6001\n                document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));\n                document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));\n                \n                \/\/ \u6fc0\u6d3b\u5f53\u524d\u6807\u7b7e\n                tab.classList.add('active');\n                const tabId = tab.getAttribute('data-tab');\n                document.getElementById(`${tabId}-content`).classList.add('active');\n            });\n        });\n\n        \/\/ \u62a5\u544aID\u751f\u6210\u5de5\u5177\u76f8\u5173\u51fd\u6570\n        function generateReportId() {\n            \/\/ \u53d6\u503c\n            const fields = ['reportName','reporter','date','time','location','reportNumber'];\n            const values = fields.map(id => document.getElementById(id).value.trim());\n            \n            \/\/ \u9a8c\u8bc1\u6240\u6709\u5b57\u6bb5\u662f\u5426\u586b\u5199\n            if (values.some(v => v === '')) {\n                \/\/ \u9ad8\u4eae\u663e\u793a\u672a\u586b\u5199\u7684\u5b57\u6bb5\n                fields.forEach((id, index) => {\n                    const input = document.getElementById(id);\n                    if (values[index] === '') {\n                        input.style.borderColor = '#e53e3e';\n                        input.style.boxShadow = '0 0 0 3px rgba(229, 62, 62, 0.1)';\n                        \n                        \/\/ 3\u79d2\u540e\u79fb\u9664\u9ad8\u4eae\n                        setTimeout(() => {\n                            input.style.borderColor = '#ddd';\n                            input.style.boxShadow = 'none';\n                        }, 3000);\n                    }\n                });\n                \n                showNotification('\u8bf7\u586b\u5199\u6240\u6709\u5fc5\u586b\u4fe1\u606f', 'error');\n                return;\n            }\n            \n            \/\/ \u751f\u6210\u62a5\u544aID\n            const reportId = values.join('_');\n\n            \/\/ \u663e\u793a\u7ed3\u679c\u5e76\u6dfb\u52a0\u52a8\u753b\u6548\u679c\n            const box = document.getElementById('report-output');\n            box.textContent = '\u62a5\u544aID\uff1a' + reportId;\n            box.style.borderColor = '#48bb78';\n            box.style.backgroundColor = 'rgba(72, 187, 120, 0.05)';\n            \n            \/\/ \u590d\u5236\u5230\u526a\u8d34\u677f\n            navigator.clipboard.writeText(reportId).then(() => {\n                showNotification('\u62a5\u544aID\u5df2\u590d\u5236\u5230\u526a\u8d34\u677f\uff01\ud83e\udee1', 'success');\n                \n                \/\/ 3\u79d2\u540e\u6062\u590d\u6837\u5f0f\n                setTimeout(() => {\n                    box.style.borderColor = '#eee';\n                    box.style.backgroundColor = '#f8fafc';\n                }, 3000);\n            }).catch(() => {\n                showNotification('\u590d\u5236\u5931\u8d25\uff0c\u8bf7\u624b\u52a8\u9009\u4e2d\u590d\u5236\u2639\ufe0f', 'error');\n            });\n        }\n\n        \/\/ \u542c\u8bfe\u8bc1\u660e\u6279\u91cf\u751f\u6210\u5668\u76f8\u5173\u51fd\u6570\n        function log(msg) {\n            const logElement = document.getElementById('log');\n            logElement.textContent += '\\n' + new Date().toLocaleTimeString() + ' - ' + msg;\n            logElement.scrollTop = logElement.scrollHeight; \/\/ \u81ea\u52a8\u6eda\u52a8\u5230\u5e95\u90e8\n        }\n\n        function updateProgress(percent) {\n            const progressBar = document.getElementById('progressBar');\n            const progressContainer = progressBar.parentElement;\n            \n            progressContainer.style.display = 'block';\n            progressBar.style.width = percent + '%';\n        }\n\n        async function readFile(file) {\n            return new Promise((resolve, reject) => {\n                const reader = new FileReader();\n                reader.onload = e => resolve(e.target.result);\n                reader.onerror = reject;\n                reader.readAsArrayBuffer(file);\n            });\n        }\n\n        \/* \u5bbd\u677e\u65e5\u671f\uff1a\u7559\u7a7a=\u4eca\u5929\uff1b\u975e\u6cd5=\u515c\u5e95 *\/\n        function normalizeDate(str){\n            const s = String(str).replace(\/\\D\/g,'');\n            if(!s){\n                const d = new Date();\n                return d.getFullYear()*10000 + (d.getMonth()+1)*100 + d.getDate();\n            }\n            const pad = (s+'00000000').slice(0,8);\n            return pad;\n        }\n\n        \/* \u5bbd\u677e\u671f\u53f7\uff1a\u7559\u7a7a=1\uff1b\u5176\u4f59\u8f6c\u5b57\u7b26\u4e32 *\/\n        function normalizePeriod(str){\n            return String(str).trim() || '1';\n        }\n\n        \/* \u89e3\u6790 Excel *\/\n        function parseExcel(buffer){\n            const wb = XLSX.read(buffer,{type:'array'});\n            const ws = wb.Sheets[wb.SheetNames[0]];\n            const data = XLSX.utils.sheet_to_json(ws,{header:1});\n            const header = data[0];\n            const idx = {\n                Title  : header.findIndex(h=>\/\u62a5\u544a\u540d\u79f0\/.test(h)),\n                Name   : header.findIndex(h=>\/\u59d3\u540d\/.test(h)),\n                ID     : header.findIndex(h=>\/\u5b66\u53f7\/.test(h)),\n                Grade  : header.findIndex(h=>\/\u5e74\u7ea7\/.test(h)),\n                Content: header.findIndex(h=>\/\u4e3b\u8981\u4f53\u4f1a\/.test(h))\n            };\n            if(Object.values(idx).some(i=>i===-1)) throw new Error('Excel \u7f3a\u5c11\u5fc5\u8981\u5217\uff01\u8bf7\u68c0\u67e5\u662f\u5426\u5305\u542b\u62a5\u544a\u540d\u79f0\u3001\u59d3\u540d\u3001\u5b66\u53f7\u3001\u5e74\u7ea7\u548c\u4e3b\u8981\u4f53\u4f1a\u5217');\n            return data.slice(1).map(r=>({\n                Title   : r[idx.Title],\n                Name    : r[idx.Name],\n                ID      : r[idx.ID],\n                Grade   : r[idx.Grade],\n                Content : r[idx.Content] || ''\n            }));\n        }\n\n        \/* \u89e3\u6790 Title *\/\n        function parseTitle(title){\n            const [topic,author,date,time,location,period_number] = String(title).split('_');\n            const y=date.slice(0,4), m=date.slice(4,6), d=date.slice(6,8);\n            return {\n                topic, author,\n                date:`${y}\u5e74${m}\u6708${d}\u65e5`,\n                time, location, period_number\n            };\n        }\n\n        \/* \u9996\u884c\u7a7a 4 \u7a7a\u683c & \u6bb5\u843d\u5904\u7406 *\/\n        function preprocessContent(str){\n            if(!str) return '';\n            return str.split('\\n').map(p=>p.trim()?'    '+p:p).join('\\n');\n        }\n\n        \/* \u4e3b\u6d41\u7a0b *\/\n        async function generateCertificates() {\n            try{\n                const excelFile = document.getElementById('excelFile').files[0];\n                const wordFile  = document.getElementById('wordFile').files[0];\n                \n                if(!excelFile || !wordFile){ \n                    showNotification('\u8bf7\u4e0a\u4f20 Excel \u548c Word \u6a21\u677f\u6587\u4ef6', 'error');\n                    return; \n                }\n\n                const dateVal   = normalizeDate(document.getElementById('certDate').value);\n                const periodVal = normalizePeriod(document.getElementById('certPeriod').value);\n\n                log('\u8bfb\u53d6 Excel \u6587\u4ef6\u4e2d\uff0c\u8bf7\u7a0d\u7b49...');\n                updateProgress(10);\n                const excelBuf = await readFile(excelFile);\n                const rows = parseExcel(excelBuf);\n                log(`Excel \u89e3\u6790\u5b8c\u6210\uff0c\u5171 ${rows.length} \u6761\u8bb0\u5f55`);\n                updateProgress(25);\n\n                log('\u8bfb\u53d6 Word \u6a21\u677f...');\n                const wordBuf = await readFile(wordFile);\n                updateProgress(30);\n\n                const zip = new JSZip();\n                const folderName = `${dateVal}-${periodVal}`;\n                let done = 0;\n                const total = rows.length;\n\n                for(const row of rows){\n                    try {\n                        const t = parseTitle(row.Title);\n                        const data = {\n                            topic   : t.topic || '\u672a\u77e5\u4e3b\u9898',\n                            author  : t.author || '\u672a\u77e5\u62a5\u544a\u4eba',\n                            date    : t.date || '\u672a\u77e5\u65e5\u671f',\n                            time    : t.time || '\u672a\u77e5\u65f6\u95f4',\n                            location: t.location || '\u672a\u77e5\u5730\u70b9',\n                            name    : row.Name || '\u672a\u77e5\u59d3\u540d',\n                            grade   : row.Grade || '\u672a\u77e5\u5e74\u7ea7',\n                            ID      : row.ID || '\u672a\u77e5\u5b66\u53f7',\n                            content : preprocessContent(row.Content)\n                        };\n\n                        const pz = new PizZip(wordBuf);\n                        const doc = new window.docxtemplater(pz,{paragraphLoop:true,linebreaks:true});\n                        doc.setData(data);\n                        doc.render();\n                        const blob = doc.getZip().generate({type:'blob'});\n\n                        const gradeFolder = row.Grade || '\u5176\u4ed6\u5e74\u7ea7';\n                        const fileName = `${dateVal}-${periodVal} ${row.ID} ${row.Name} \u79d1\u6280\u62a5\u544a\u542c\u8bfe\u8bc1\u660e.docx`;\n                        const path = `${folderName}\/${gradeFolder}\/${fileName}`;\n                        zip.file(path, blob);\n                        \n                        done++;\n                        log(`\u5df2\u5904\u7406 ${done}\/${total}\uff1a${row.Name}`);\n                        \n                        \/\/ \u66f4\u65b0\u8fdb\u5ea6\n                        const progress = 30 + Math.round((done \/ total) * 60);\n                        updateProgress(progress);\n                    } catch (e) {\n                        log(`\u5904\u7406 ${row.Name} \u65f6\u51fa\u9519: ${e.message}`);\n                    }\n                }\n\n                log('\u6b63\u5728\u6253\u5305\u6587\u4ef6...');\n                updateProgress(95);\n                const zipBlob = await zip.generateAsync({type:'blob'});\n                \n                saveAs(zipBlob, `${folderName}.zip`);\n                updateProgress(100);\n                showNotification('\u6587\u4ef6\u751f\u6210\u6210\u529f\uff0c\u5df2\u5f00\u59cb\u4e0b\u8f7d', 'success');\n                log(`\u5168\u90e8\u5b8c\u6210\uff01\u270c\ufe0f\u5171\u5904\u7406 ${done} \u4e2a\u6587\u4ef6\u3002`);\n            }catch(e){\n                console.error(e);\n                log('\u51fa\u9519\u4e86\u3002\u3002\u3002 ' + e.message);\n                showNotification('\u5904\u7406\u51fa\u9519: ' + e.message, 'error');\n                updateProgress(0);\n            }\n        }\n\n        \/\/ \u6587\u4ef6\u9009\u62e9\u53cd\u9988\n        document.getElementById('excelFile').addEventListener('change', function(e) {\n            const infoElement = document.getElementById('excelFileInfo');\n            if (e.target.files.length > 0) {\n                infoElement.textContent = `\u5df2\u9009\u62e9: ${e.target.files[0].name}`;\n            } else {\n                infoElement.textContent = '';\n            }\n        });\n\n        document.getElementById('wordFile').addEventListener('change', function(e) {\n            const infoElement = document.getElementById('wordFileInfo');\n            if (e.target.files.length > 0) {\n                infoElement.textContent = `\u5df2\u9009\u62e9: ${e.target.files[0].name}`;\n            } else {\n                infoElement.textContent = '';\n            }\n        });\n\n        \/\/ \u901a\u77e5\u63d0\u793a\u51fd\u6570\n        function showNotification(message, type) {\n            \/\/ \u79fb\u9664\u5df2\u5b58\u5728\u7684\u901a\u77e5\n            const existing = document.querySelector('.notification');\n            if (existing) existing.remove();\n            \n            \/\/ \u521b\u5efa\u901a\u77e5\u5143\u7d20\n            const notification = document.createElement('div');\n            notification.className = `notification ${type}`;\n            notification.textContent = message;\n            \n            \/\/ \u6dfb\u52a0\u5230\u9875\u9762\n            document.body.appendChild(notification);\n            \n            \/\/ \u663e\u793a\u52a8\u753b\n            setTimeout(() => {\n                notification.style.opacity = '1';\n                notification.style.transform = 'translate(-50%, 0)';\n            }, 10);\n            \n            \/\/ 3\u79d2\u540e\u9690\u85cf\n            setTimeout(() => {\n                notification.style.opacity = '0';\n                notification.style.transform = 'translate(-50%, 20px)';\n                \n                \/\/ \u79fb\u9664\u5143\u7d20\n                setTimeout(() => {\n                    if (notification.parentNode) {\n                        notification.parentNode.removeChild(notification);\n                    }\n                }, 300);\n            }, 3000);\n        }\n\n        \/\/ \u4e3a\u8f93\u5165\u6846\u6dfb\u52a0\u805a\u7126\u6548\u679c\n        document.querySelectorAll('input').forEach(input => {\n            input.addEventListener('focus', () => {\n                if (input.parentElement.classList.contains('input-focus-effect')) {\n                    input.parentElement.classList.add('focused');\n                }\n            });\n            \n            input.addEventListener('blur', () => {\n                if (input.parentElement.classList.contains('input-focus-effect')) {\n                    input.parentElement.classList.remove('focused');\n                }\n            });\n        });\n    <\/script>\n<\/body>\n<\/html>\n","protected":false},"excerpt":{"rendered":"<p>\ud83d\udcc4\u79d1\u6280\u62a5\u544a\u5904\u7406\u5de5\u5177\u7bb1\ud83d\udd27 \u2014\u2014\u90d1\u76db\u56fd\u8bbe\u8ba1\uff0c\u6d4b\u8bd5\u7248\u4e0d\u4ee3\u8868\u6700\u7ec8\u54c1\u8d28\uff1a\uff09 \u5de5\u5177\u7bb1\ud83d\udd27 \u540c\u6d4e\u5927\u5b66\u6d4b\u7ed8\u4e0e\u5730\u7406\u4fe1\u606f\u5b66\u9662\u672c\u79d1\u751f\u79d1 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":62,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-232","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/www.guanchengyu.com\/index.php\/wp-json\/wp\/v2\/pages\/232","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.guanchengyu.com\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.guanchengyu.com\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.guanchengyu.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.guanchengyu.com\/index.php\/wp-json\/wp\/v2\/comments?post=232"}],"version-history":[{"count":23,"href":"https:\/\/www.guanchengyu.com\/index.php\/wp-json\/wp\/v2\/pages\/232\/revisions"}],"predecessor-version":[{"id":269,"href":"https:\/\/www.guanchengyu.com\/index.php\/wp-json\/wp\/v2\/pages\/232\/revisions\/269"}],"up":[{"embeddable":true,"href":"https:\/\/www.guanchengyu.com\/index.php\/wp-json\/wp\/v2\/pages\/62"}],"wp:attachment":[{"href":"https:\/\/www.guanchengyu.com\/index.php\/wp-json\/wp\/v2\/media?parent=232"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}