From 6d4e9ccb34e0292bfadba7bfd678231cb37ae8c0 Mon Sep 17 00:00:00 2001 From: qx <1084500556@qq.com> Date: 星期三, 02 七月 2025 15:45:16 +0800 Subject: [PATCH] :qx --- src/views/111111.vue | 335 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 335 insertions(+), 0 deletions(-) diff --git a/src/views/111111.vue b/src/views/111111.vue new file mode 100644 index 0000000..039340f --- /dev/null +++ b/src/views/111111.vue @@ -0,0 +1,335 @@ +<template> + <div> + <h3>鍓嶇 Word 缂栬緫鍣紙TinyMCE锛�</h3> + <div class="actions"> + <input type="file" accept=".docx" @change="importWord" ref="fileInput" style="margin-right: 10px;" /> + <button @click="exportToWord">瀵煎嚭 Word</button> + <button @click="exportToPDF">瀵煎嚭 PDF</button> + </div> + <editor v-model="content" :init="editorInit" :api-key="apiKey"></editor> + </div> +</template> +<script> +import Editor from '@tinymce/tinymce-vue'; +import { Document, Packer, Paragraph, TextRun, ImageRun, Table as DocxTable, TableRow as DocxTableRow, TableCell as DocxTableCell } from 'docx'; +import { saveAs } from 'file-saver'; +import jsPDF from 'jspdf'; +import html2canvas from 'html2canvas'; +import mammoth from 'mammoth'; + +export default { + components: { Editor }, + data() { + return { + apiKey: '3ceaxd5ckw4te35xj38vj3p5rmmeyv0x8pq2yrr92rwdiqzp', + content: '', + editorInit: { + height: 500, + menubar: true, + plugins: [ + 'advlist autolink lists link image charmap print preview anchor', + 'searchreplace visualblocks code fullscreen', + 'insertdatetime media table paste code help wordcount' + ], + toolbar: + 'undo redo | formatselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | image table | removeformat | help', + images_upload_handler: (blobInfo, success, failure) => { + const reader = new FileReader(); + reader.onload = () => success(reader.result); + reader.onerror = () => failure('鍥剧墖涓婁紶澶辫触'); + reader.readAsDataURL(blobInfo.blob()); + }, + content_style: 'body { font-family: SimSun, Arial; font-size: 14px; }', + table_default_attributes: { border: 1 }, + readonly: false, + language: 'zh-CN', + }, + }; + }, + methods: { + /** 瀵煎叆 Word 鏂囨。 */ + importWord(event) { + const file = event.target.files[0]; + if (!file) { + this.$message.error('璇烽�夋嫨涓�涓� .docx 鏂囦欢'); + return; + } + if (!file.name.endsWith('.docx')) { + this.$message.error('璇蜂笂浼� .docx 鏍煎紡鐨勬枃浠�'); + return; + } + + const reader = new FileReader(); + reader.onload = async (e) => { + try { + const arrayBuffer = e.target.result; + const result = await mammoth.convertToHtml({ arrayBuffer }); + this.content = result.value || '<p>鏃犲唴瀹�</p>'; + this.$message.success('Word 鏂囨。瀵煎叆鎴愬姛'); + this.$refs.fileInput.value = ''; + } catch (err) { + console.error('瀵煎叆 Word 澶辫触锛�', err); + this.$message.error('瀵煎叆 Word 鏂囨。澶辫触锛�' + err.message); + } + }; + reader.onerror = () => { + this.$message.error('璇诲彇鏂囦欢澶辫触'); + }; + reader.readAsArrayBuffer(file); + }, + + /** 瀵煎嚭涓� Word */ + exportToWord() { + if (!this.content || this.content.trim() === '') { + this.$message.error('缂栬緫鍣ㄥ唴瀹逛负绌猴紝鏃犳硶瀵煎嚭'); + return; + } + this.parseHtmlToDocx(this.content).then((elements) => { + const doc = new Document({ + sections: [{ properties: {}, children: elements }], + }); + + Packer.toBlob(doc).then((blob) => { + saveAs(blob, '鏂囨。.docx'); + this.$message.success('瀵煎嚭 Word 鎴愬姛'); + }).catch((err) => { + console.error('鐢熸垚 Word 澶辫触锛�', err); + this.$message.error('鐢熸垚 Word 澶辫触锛�' + err.message); + }); + }).catch((err) => { + console.error('瑙f瀽 HTML 澶辫触锛�', err); + this.$message.error('瑙f瀽 HTML 澶辫触锛�' + err.message); + }); + }, + + /** 瑙f瀽 HTML 涓� docx 鍏冪礌 */ + parseHtmlToDocx(html) { + return new Promise((resolve, reject) => { + if (!html || typeof html !== 'string') { + reject(new Error('鏃犳晥鐨� HTML 鍐呭')); + return; + } + + console.log('Input HTML:', html); // 璋冭瘯杈撳叆 HTML + + const parser = new DOMParser(); + const doc = parser.parseFromString(html, 'text/html'); + if (!doc || !doc.body) { + reject(new Error('HTML 瑙f瀽澶辫触锛氭枃妗g粨鏋勬棤鏁�')); + return; + } + + const elements = []; + const imagePromises = []; + + const processNode = (node) => { + if (node.nodeType !== Node.ELEMENT_NODE) { + return; + } + + console.log('Processing node:', node.tagName); // 璋冭瘯鑺傜偣 + + if (node.tagName === 'P') { + const childNodes = node.childNodes ? Array.from(node.childNodes) : []; + const textRuns = childNodes.map((child) => { + if (child.nodeType === Node.TEXT_NODE) { + return new TextRun({ text: child.textContent || '' }); + } else if (child.tagName === 'B') { + return new TextRun({ text: child.textContent || '', bold: true }); + } else if (child.tagName === 'I') { + return new TextRun({ text: child.textContent || '', italics: true }); + } else if (child.tagName === 'IMG') { + const promise = this.getImageBase64FromUrl(child.src).then(({ base64, width, height }) => { + return new ImageRun({ + data: base64, + transformation: { width, height }, + }); + }).catch((err) => { + console.error('鍥剧墖鍔犺浇澶辫触锛�', err, 'URL:', child.src); + return new TextRun(`[鍥剧墖: ${child.alt || '鍥惧儚'}]`); + }); + imagePromises.push(promise); + return promise; + } + return new TextRun({ text: child.textContent || '' }); + }).filter(Boolean); + if (textRuns.length) { + elements.push(new Paragraph({ children: textRuns })); + } + } else if (node.tagName === 'H1') { + elements.push(new Paragraph({ text: node.textContent || '', heading: 'Heading1' })); + } else if (node.tagName === 'UL' || node.tagName === 'OL') { + const children = node.children ? Array.from(node.children) : []; + children.forEach((li) => { + elements.push( + new Paragraph({ + text: li.textContent || '', + bullet: node.tagName === 'UL' ? { level: 0 } : { level: 0, number: true }, + }) + ); + }); + } else if (node.tagName === 'TABLE') { + const rows = node.querySelectorAll('tr') ? Array.from(node.querySelectorAll('tr')) : []; + const tableRows = rows.map((tr) => { + const cells = tr.querySelectorAll('td, th') ? Array.from(tr.querySelectorAll('td, th')) : []; + const tableCells = cells.map((cell) => { + return new DocxTableCell({ + children: [new Paragraph(cell.textContent || '')], + }); + }); + return new DocxTableRow({ children: tableCells }); + }); + if (tableRows.length) { + elements.push(new DocxTable({ rows: tableRows })); + } + } + const children = node.children ? Array.from(node.children) : []; + children.forEach(processNode); + }; + + const nodes = doc.body.childNodes ? Array.from(doc.body.childNodes) : []; + if (nodes.length === 0) { + console.warn('HTML 鏂囨。鏃犳湁鏁堣妭鐐�'); + resolve(elements); + return; + } + + nodes.forEach(processNode); + + Promise.all(imagePromises).then((imageRuns) => { + let imageIndex = 0; + elements.forEach((element, index) => { + if (!(element instanceof Paragraph)) { + console.log('Skipping non-Paragraph element at index', index, 'type:', element.constructor.name); + return; // 璺宠繃闈� Paragraph 鍏冪礌 + } + if (!Array.isArray(element.children)) { + console.warn('Element at index', index, 'has invalid children:', element.children); + element.children = []; // 鍒濆鍖栦负绌烘暟缁� + return; + } + console.log('Processing element at index', index, 'children:', element.children); + element.children = element.children.map((child, childIndex) => { + if (child instanceof Promise) { + const imageRun = imageRuns[imageIndex++] || new TextRun('[鍥剧墖鍔犺浇澶辫触]'); + console.log('Replacing Promise at child index', childIndex, 'with:', imageRun); + return imageRun; + } + return child; + }); + }); + resolve(elements); + }).catch((err) => { + console.error('鍥剧墖澶勭悊澶辫触锛�', err); + reject(new Error('鍥剧墖澶勭悊澶辫触锛�' + err.message)); + }); + }); + }, + + /** 鑾峰彇鍥剧墖鐨� Base64 鏁版嵁鍜屽昂瀵� */ + getImageBase64FromUrl(url) { + return new Promise((resolve, reject) => { + if (!url) { + reject(new Error('鍥剧墖 URL 涓虹┖')); + return; + } + + console.log('Fetching image:', url); + + if (url.startsWith('data:image/')) { + const base64 = url.split(',')[1]; + const img = new Image(); + img.onload = () => { + const maxWidth = 200; + const maxHeight = 200; + let { width, height } = img; + if (width > maxWidth || height > maxHeight) { + const ratio = Math.min(maxWidth / width, maxHeight / height); + width = Math.round(width * ratio); + height = Math.round(height * ratio); + } + resolve({ base64, width, height }); + }; + img.onerror = () => reject(new Error('Base64 鍥剧墖鍔犺浇澶辫触')); + img.src = url; + return; + } + + fetch(url, { mode: 'cors' }) + .then((response) => { + if (!response.ok) throw new Error('缃戠粶鍥剧墖鍔犺浇澶辫触'); + return response.blob(); + }) + .then((blob) => { + const reader = new FileReader(); + reader.onloadend = () => { + const base64 = reader.result.split(',')[1]; + const img = new Image(); + img.onload = () => { + const maxWidth = 200; + const maxHeight = 200; + let { width, height } = img; + if (width > maxWidth || height > maxHeight) { + const ratio = Math.min(maxWidth / width, maxHeight / height); + width = Math.round(width * ratio); + height = Math.round(height * ratio); + } + resolve({ base64, width, height }); + }; + img.onerror = () => reject(new Error('鍥剧墖璇诲彇澶辫触')); + img.src = reader.result; + }; + reader.onerror = () => reject(new Error('鍥剧墖璇诲彇澶辫触')); + reader.readAsDataURL(blob); + }) + .catch((err) => reject(err)); + }); + }, + + /** 瀵煎嚭涓� PDF */ + exportToPDF() { + if (!this.content || this.content.trim() === '') { + this.$message.error('缂栬緫鍣ㄥ唴瀹逛负绌猴紝鏃犳硶瀵煎嚭'); + return; + } + const contentElement = document.createElement('div'); + contentElement.innerHTML = this.content; + contentElement.style.padding = '10px'; + document.body.appendChild(contentElement); + + html2canvas(contentElement, { scale: 2 }) + .then((canvas) => { + const doc = new jsPDF(); + const imgData = canvas.toDataURL('image/png'); + doc.addImage(imgData, 'PNG', 10, 10, 190, 0); + doc.save('鏂囨。.pdf'); + this.$message.success('瀵煎嚭 PDF 鎴愬姛'); + document.body.removeChild(contentElement); + }) + .catch((err) => { + console.error('鐢熸垚 PDF 澶辫触锛�', err); + this.$message.error('鐢熸垚 PDF 澶辫触锛�' + err.message); + document.body.removeChild(contentElement); + }); + }, + }, +}; +</script> +<style> +.actions { + margin-top: 10px; + margin-bottom: 10px; +} +.actions button { + padding: 8px 16px; + margin-right: 10px; + background-color: #409eff; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; +} +.actions button:hover { + background-color: #66b1ff; +} +</style> \ No newline at end of file -- Gitblit v1.8.0