提交
This commit is contained in:
@@ -15,5 +15,8 @@
|
|||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<!-- built files will be auto injected -->
|
<!-- built files will be auto injected -->
|
||||||
</body><script src="<%=BASE_URL %>/tinymce/tinymce.min.js"></script>
|
</body><script src="<%=BASE_URL %>/tinymce/tinymce.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/docxtemplater/3.29.4/docxtemplater.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/docxtemplater/jszip-utils.min.js"></script>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -363,6 +363,7 @@ const en = {
|
|||||||
typesettingType1: 'Vertical A4',
|
typesettingType1: 'Vertical A4',
|
||||||
exportWord: 'Export Word',
|
exportWord: 'Export Word',
|
||||||
exportImg: 'Export PNG',
|
exportImg: 'Export PNG',
|
||||||
|
PaperRotation: 'Paper Rotation',
|
||||||
},
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -356,6 +356,7 @@ const zh = {
|
|||||||
typesettingType1: '竖向 A4',
|
typesettingType1: '竖向 A4',
|
||||||
exportWord: '导出 Word',
|
exportWord: '导出 Word',
|
||||||
exportImg: '导出 图片',
|
exportImg: '导出 图片',
|
||||||
|
PaperRotation: '纸张方向',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -107,6 +107,9 @@
|
|||||||
<i class="el-icon-document-add"></i>
|
<i class="el-icon-document-add"></i>
|
||||||
</b>
|
</b>
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
<common-drag-word></common-drag-word>
|
||||||
|
</p>
|
||||||
<div class="unfetteredBox">
|
<div class="unfetteredBox">
|
||||||
<div
|
<div
|
||||||
:style="item.isHidden ? 'opacity:0.2' : 'opacity:1'"
|
:style="item.isHidden ? 'opacity:0.2' : 'opacity:1'"
|
||||||
|
|||||||
BIN
src/components/page/components/Tinymce/img/bit_bug.png
Normal file
BIN
src/components/page/components/Tinymce/img/bit_bug.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
@@ -6,10 +6,10 @@
|
|||||||
<script>
|
<script>
|
||||||
import { string } from 'html-docx-js/dist/html-docx';
|
import { string } from 'html-docx-js/dist/html-docx';
|
||||||
import htmlDocx from 'html-docx-js/dist/html-docx.js';
|
import htmlDocx from 'html-docx-js/dist/html-docx.js';
|
||||||
import { Document, Packer, PageOrientation, Paragraph, TextRun } from "docx"; // 引入 docx.js
|
import { Document, Packer, PageOrientation, Paragraph, TextRun } from 'docx'; // 引入 docx.js
|
||||||
import html2canvas from 'html2canvas';
|
import html2canvas from 'html2canvas';
|
||||||
const toolbar =
|
const toolbar =
|
||||||
'undo redo | formatselect | bold italic | forecolor |subscript superscript|table tabledelete | |spacer customButton |customButtonExportWord |customButtonExportImg | pageOrientation';
|
'uploadWord|undo redo | formatselect | bold italic | forecolor |subscript superscript|table tabledelete |customButtonExportWord |customButtonExportImg |customDropdown | clearButton';
|
||||||
const tableStyle = ` b span{
|
const tableStyle = ` b span{
|
||||||
font-weight: bold !important;
|
font-weight: bold !important;
|
||||||
}
|
}
|
||||||
@@ -128,6 +128,39 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
typesettingType: 1,
|
typesettingType: 1,
|
||||||
|
typesettingTypeOptions: [
|
||||||
|
{
|
||||||
|
label: this.$t('commonTable.typesettingType1'),
|
||||||
|
value: 1,
|
||||||
|
orientation: 'portrait',
|
||||||
|
pageSize: {
|
||||||
|
width: 11906,
|
||||||
|
height: 16976
|
||||||
|
},
|
||||||
|
pageMargins: {
|
||||||
|
top: 1440,
|
||||||
|
bottom: 1440,
|
||||||
|
left: 1084,
|
||||||
|
right: 1084
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 1 cm = 144 Twip 上边距(2.54 cm) = 2.54 × 144 = 365.76 Twip
|
||||||
|
{
|
||||||
|
label: this.$t('commonTable.typesettingType2'),
|
||||||
|
value: 2,
|
||||||
|
orientation: 'landscape',
|
||||||
|
pageSize: {
|
||||||
|
width: 16976,
|
||||||
|
height: 11906
|
||||||
|
},
|
||||||
|
pageMargins: {
|
||||||
|
top: 1440,
|
||||||
|
bottom: 1440,
|
||||||
|
left: 1084,
|
||||||
|
right: 1084
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
uploadReset: false,
|
uploadReset: false,
|
||||||
dialogVisible: false,
|
dialogVisible: false,
|
||||||
form: {
|
form: {
|
||||||
@@ -191,7 +224,7 @@ export default {
|
|||||||
tables.forEach((table) => {
|
tables.forEach((table) => {
|
||||||
const editor = window.tinymce.get(this.tinymceId);
|
const editor = window.tinymce.get(this.tinymceId);
|
||||||
editor.dom.setStyles(table, {
|
editor.dom.setStyles(table, {
|
||||||
width: this.typesettingType == 1 ? '171.5mm' : '258.6mm'
|
width: this.typesettingType == 1 ? '17.18cm' : '25.88cm'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -224,25 +257,54 @@ export default {
|
|||||||
statusbar: false, // 关闭底部状态栏
|
statusbar: false, // 关闭底部状态栏
|
||||||
custom_colors: false,
|
custom_colors: false,
|
||||||
color_map: ['000000', 'Black', '0082AA', 'TMR Blue'],
|
color_map: ['000000', 'Black', '0082AA', 'TMR Blue'],
|
||||||
plugins: 'forecolor code paste table', // 启用 forecolor 和 code 插件
|
plugins: 'forecolor code paste table image', // 启用 forecolor 和 code 插件
|
||||||
end_container_on_empty_block: true,
|
end_container_on_empty_block: true,
|
||||||
content_css: 'default', // 加载 TinyMCE 默认样式表
|
content_css: 'default', // 加载 TinyMCE 默认样式表
|
||||||
//设置自定义按钮 myCustomToolbarButton
|
//设置自定义按钮 myCustomToolbarButton
|
||||||
|
|
||||||
setup(ed) {
|
setup(ed) {
|
||||||
ed.ui.registry.addButton('pageOrientation', {
|
ed.ui.registry.addButton('uploadWord', {
|
||||||
text: '切换方向',
|
text: 'Word',
|
||||||
|
icon: 'import-word', // 使用自定义图标
|
||||||
onAction: function () {
|
onAction: function () {
|
||||||
_this.typesettingType = _this.typesettingType == 1 ? 2 : 1;
|
const input = document.createElement('input');
|
||||||
_this.changeTable();
|
input.type = 'file';
|
||||||
// if (ed.dom.hasClass(ed.getBody(), 'a4-portrait')) {
|
input.accept = '.docx'; // 限制为 Word 文件
|
||||||
// ed.dom.removeClass(ed.getBody(), 'a4-portrait');
|
input.addEventListener('change', function () {
|
||||||
// ed.dom.addClass(ed.getBody(), 'a4-landscape');
|
const file = input.files[0];
|
||||||
// } else {
|
if (file) {
|
||||||
// ed.dom.removeClass(ed.getBody(), 'a4-landscape');
|
const reader = new FileReader();
|
||||||
// ed.dom.addClass(ed.getBody(), 'a4-portrait');
|
reader.onload = function (e) {
|
||||||
// }
|
const arrayBuffer = e.target.result;
|
||||||
|
_this.extractTablesFromWord(arrayBuffer, function (tablesHtml) {
|
||||||
|
ed.setContent(tablesHtml);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
input.click();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ed.ui.registry.addMenuButton('customDropdown', {
|
||||||
|
text: _this.$t('commonTable.PaperRotation'), // 下拉框标题
|
||||||
|
fetch: function (callback) {
|
||||||
|
// 定义下拉框的内容
|
||||||
|
const items = [..._this.typesettingTypeOptions];
|
||||||
|
const menuItems = items.map((item) => ({
|
||||||
|
type: 'menuitem',
|
||||||
|
text: item.label,
|
||||||
|
onAction: function () {
|
||||||
|
_this.typesettingType = item.value;
|
||||||
|
_this.changeTable();
|
||||||
|
// ed.execCommand(item.value); // 执行命令
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
callback(menuItems);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
ed.on('init', function () {
|
ed.on('init', function () {
|
||||||
const editorBody = ed.getBody();
|
const editorBody = ed.getBody();
|
||||||
// 创建 MutationObserver 监听内容变化
|
// 创建 MutationObserver 监听内容变化
|
||||||
@@ -259,13 +321,15 @@ export default {
|
|||||||
observer.observe(editorBody, { childList: true, subtree: true, characterData: true });
|
observer.observe(editorBody, { childList: true, subtree: true, characterData: true });
|
||||||
});
|
});
|
||||||
// 定义自定义按钮
|
// 定义自定义按钮
|
||||||
ed.ui.registry.addButton('customButton', {
|
ed.ui.registry.addButton('clearButton', {
|
||||||
text: 'Empty',
|
text: 'Empty',
|
||||||
|
|
||||||
onAction: () => {
|
onAction: () => {
|
||||||
// 插入自定义表格到编辑器中
|
// 插入自定义表格到编辑器中
|
||||||
ed.setContent('');
|
ed.setContent('');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 定义自定义按钮
|
// 定义自定义按钮
|
||||||
ed.ui.registry.addButton('customButtonExportWord', {
|
ed.ui.registry.addButton('customButtonExportWord', {
|
||||||
text: _this.$t('commonTable.exportWord'),
|
text: _this.$t('commonTable.exportWord'),
|
||||||
@@ -312,6 +376,107 @@ export default {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// 提取 Word 文件中的表格
|
||||||
|
extractTablesFromWord(arrayBuffer, callback) {
|
||||||
|
const zip = new JSZip();
|
||||||
|
var that = this;
|
||||||
|
zip.loadAsync(arrayBuffer)
|
||||||
|
.then(function (zip) {
|
||||||
|
const docXmlPath = 'word/document.xml'; // Word 主文档的 XML 路径
|
||||||
|
return zip.file(docXmlPath).async('string');
|
||||||
|
})
|
||||||
|
.then(function (docXml) {
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const xmlDoc = parser.parseFromString(docXml, 'text/xml');
|
||||||
|
const tables = xmlDoc.getElementsByTagName('w:tbl'); // 查找 Word 表格标签
|
||||||
|
|
||||||
|
let html = '';
|
||||||
|
for (let table of tables) {
|
||||||
|
html += that.convertTableToHtml(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!html) {
|
||||||
|
html = '<p>未检测到表格内容。</p>';
|
||||||
|
}
|
||||||
|
callback(html);
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.innerHTML = html;
|
||||||
|
that.updateTableStyles(container);
|
||||||
|
|
||||||
|
console.log('html at line 400:', html);
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
console.error('解析 Word 文件出错:', err);
|
||||||
|
callback('<p>文件解析失败,请检查文件格式。</p>');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 将 XML 表格转换为 HTML
|
||||||
|
convertTableToHtml(tableNode) {
|
||||||
|
const rows = tableNode.getElementsByTagName('w:tr');
|
||||||
|
let html = '<table border="1" style="border-collapse: collapse;">';
|
||||||
|
for (let row of rows) {
|
||||||
|
html += '<tr>';
|
||||||
|
const cells = row.getElementsByTagName('w:tc');
|
||||||
|
for (let cell of cells) {
|
||||||
|
let cellHtml = '';
|
||||||
|
const paragraphs = cell.getElementsByTagName('w:p'); // 获取单元格内段落
|
||||||
|
|
||||||
|
for (let paragraph of paragraphs) {
|
||||||
|
const texts = paragraph.getElementsByTagName('w:r'); // 获取段落内的文本和样式
|
||||||
|
for (let run of texts) {
|
||||||
|
const textNode = run.getElementsByTagName('w:t')[0];
|
||||||
|
if (textNode) {
|
||||||
|
const style = this.getStyleFromRun(run); // 提取样式
|
||||||
|
cellHtml += `<span style="${style}">${textNode.textContent}</span>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cellHtml += '<br>'; // 段落换行
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `<td>${cellHtml}</td>`;
|
||||||
|
}
|
||||||
|
html += '</tr>';
|
||||||
|
}
|
||||||
|
html += '</table>';
|
||||||
|
|
||||||
|
return html;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 提取 w:r 节点中的样式并转换为 CSS
|
||||||
|
getStyleFromRun(run) {
|
||||||
|
const styleNode = run.getElementsByTagName('w:rPr')[0];
|
||||||
|
let style = '';
|
||||||
|
|
||||||
|
if (styleNode) {
|
||||||
|
// 加粗
|
||||||
|
if (styleNode.getElementsByTagName('w:b').length > 0) {
|
||||||
|
style += 'font-weight: bold;';
|
||||||
|
}
|
||||||
|
// 斜体
|
||||||
|
if (styleNode.getElementsByTagName('w:i').length > 0) {
|
||||||
|
style += 'font-style: italic;';
|
||||||
|
}
|
||||||
|
// 上标或下标
|
||||||
|
const vertAlign = styleNode.getElementsByTagName('w:vertAlign')[0];
|
||||||
|
if (vertAlign) {
|
||||||
|
const alignVal = vertAlign.getAttribute('w:val');
|
||||||
|
if (alignVal === 'superscript') {
|
||||||
|
style += 'vertical-align: super; font-size: smaller;';
|
||||||
|
} else if (alignVal === 'subscript') {
|
||||||
|
style += 'vertical-align: sub; font-size: smaller;';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 字体颜色
|
||||||
|
const colorNode = styleNode.getElementsByTagName('w:color')[0];
|
||||||
|
if (colorNode) {
|
||||||
|
const colorVal = colorNode.getAttribute('w:val');
|
||||||
|
style += `color: #${colorVal};`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return style;
|
||||||
|
},
|
||||||
replaceNegativeSign(node) {
|
replaceNegativeSign(node) {
|
||||||
if (node.nodeType === Node.TEXT_NODE) {
|
if (node.nodeType === Node.TEXT_NODE) {
|
||||||
// 如果是文本节点,替换负号
|
// 如果是文本节点,替换负号
|
||||||
@@ -336,11 +501,12 @@ export default {
|
|||||||
var _this = this;
|
var _this = this;
|
||||||
// 更新表格样式
|
// 更新表格样式
|
||||||
const tables = container.querySelectorAll('table');
|
const tables = container.querySelectorAll('table');
|
||||||
|
|
||||||
tables.forEach((table) => {
|
tables.forEach((table) => {
|
||||||
table.setAttribute(
|
table.setAttribute(
|
||||||
'style',
|
'style',
|
||||||
`width: ${
|
`width: ${
|
||||||
this.typesettingType == 1 ? '171.5mm' : '258.6mm'
|
this.typesettingType == 1 ? '17.18cm' : '25.88cm'
|
||||||
};border: none; margin: 0 auto !important;border-collapse: collapse; `
|
};border: none; margin: 0 auto !important;border-collapse: collapse; `
|
||||||
);
|
);
|
||||||
const cells = table.querySelectorAll('td');
|
const cells = table.querySelectorAll('td');
|
||||||
@@ -369,6 +535,7 @@ export default {
|
|||||||
console.log('匹配到带有数字的方括号内容');
|
console.log('匹配到带有数字的方括号内容');
|
||||||
// 为匹配的元素添加样式类
|
// 为匹配的元素添加样式类
|
||||||
element.classList.add('color-highlight');
|
element.classList.add('color-highlight');
|
||||||
|
element.style.color = 'rgb(0,130,170)';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -409,6 +576,23 @@ export default {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
var editor = window.tinymce.activeEditor;
|
||||||
|
var html = '';
|
||||||
|
tables.forEach((e) => {
|
||||||
|
console.log('e at line 579:', e);
|
||||||
|
|
||||||
|
html += e.outerHTML;
|
||||||
|
console.log('html at line 582:', html);
|
||||||
|
});
|
||||||
|
// 将外部 DOM 内容更新到编辑器
|
||||||
|
const container1 = document.createElement('div');
|
||||||
|
container1.innerHTML = html; // html 是更新后的 HTML 内容
|
||||||
|
|
||||||
|
// 更新编辑器内容
|
||||||
|
editor.setContent(container1.innerHTML);
|
||||||
|
|
||||||
|
// 触发编辑器内容变化后,如果需要,可能还要设置编辑器的样式
|
||||||
|
editor.focus(); // 聚焦到编辑器
|
||||||
},
|
},
|
||||||
//销毁富文本
|
//销毁富文本
|
||||||
destroyTinymce() {
|
destroyTinymce() {
|
||||||
@@ -424,16 +608,13 @@ export default {
|
|||||||
getContent(type) {
|
getContent(type) {
|
||||||
this.$emit('getContent', type, window.tinymce.get(this.tinymceId).getContent());
|
this.$emit('getContent', type, window.tinymce.get(this.tinymceId).getContent());
|
||||||
},
|
},
|
||||||
|
|
||||||
export(type, data) {
|
async export(type, data) {
|
||||||
if (type == 'table') {
|
if (type == 'table') {
|
||||||
var tableHtml = `<html xmlns:w="urn:schemas-microsoft-com:office:word">
|
var tableHtml = `<html xmlns:w="urn:schemas-microsoft-com:office:word">
|
||||||
<head>
|
<head>
|
||||||
<style>
|
<style>
|
||||||
@page {
|
|
||||||
size: A4; /* 或者 A3, Letter 等纸张大小 */
|
|
||||||
margin: 20mm;
|
|
||||||
}
|
|
||||||
${tableStyle}
|
${tableStyle}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
@@ -441,8 +622,17 @@ export default {
|
|||||||
${data}
|
${data}
|
||||||
</body>
|
</body>
|
||||||
</html>`;
|
</html>`;
|
||||||
console.log('tableHtml at line 438:', tableHtml);
|
|
||||||
const converted = htmlDocx.asBlob(tableHtml); // 将 HTML 转换为 Word Blob
|
const converted = htmlDocx.asBlob(tableHtml, {
|
||||||
|
orientation: this.typesettingTypeOptions[this.typesettingType - 1].orientation,
|
||||||
|
pageSize: {
|
||||||
|
...this.typesettingTypeOptions[this.typesettingType - 1].pageSize
|
||||||
|
},
|
||||||
|
pageMargins: {
|
||||||
|
...this.typesettingTypeOptions[this.typesettingType - 1].pageMargins
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// const converted = htmlDocx.asBlob(tableHtml); // 将 HTML 转换为 Word Blob
|
||||||
// 触发文件下载
|
// 触发文件下载
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = URL.createObjectURL(converted);
|
link.href = URL.createObjectURL(converted);
|
||||||
@@ -519,4 +709,15 @@ export default {
|
|||||||
::v-deep .tox .tox-menu {
|
::v-deep .tox .tox-menu {
|
||||||
z-index: 9999 !important;
|
z-index: 9999 !important;
|
||||||
}
|
}
|
||||||
|
/* 自定义按钮样式 */
|
||||||
|
.custom-btn {
|
||||||
|
background-color: #28a745 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.custom-btn:hover {
|
||||||
|
background-color: #218838 !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
393
src/components/page/components/table/dragWord.vue
Normal file
393
src/components/page/components/table/dragWord.vue
Normal file
@@ -0,0 +1,393 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<!-- 拖拽区域 -->
|
||||||
|
<div class="drag-drop-area" @dragover.prevent="onDragOver" @dragleave="onDragLeave" @drop.prevent="onDrop" @click="clickUpload">
|
||||||
|
<p>将 Word 文件拖拽到此处</p>
|
||||||
|
</div>
|
||||||
|
<el-dialog append-to-body
|
||||||
|
title="Add Academic Integrity Committee"
|
||||||
|
:visible.sync="addVisible"
|
||||||
|
width="660px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
|
||||||
|
>
|
||||||
|
<div v-if="tables.length > 0" class="preview-area">
|
||||||
|
<div v-for="(table, index) in tables" :key="index" class="table-wrapper">
|
||||||
|
<h3>Table {{ index + 1 }}</h3>
|
||||||
|
<div v-html="table.html" class="table-preview"></div>
|
||||||
|
|
||||||
|
<div class="table-options">
|
||||||
|
<label>
|
||||||
|
页面方向:
|
||||||
|
<select v-model="table.orientation">
|
||||||
|
<option value="portrait">纵向</option>
|
||||||
|
<option value="landscape">横向</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<button @click="confirmTable(index)">确认表格</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span slot="footer" class="dialog-footer">
|
||||||
|
<el-button @click="addVisCancle">Cancel</el-button>
|
||||||
|
<el-button type="primary" @click="saveAdd()" v-if="dis_able">OK</el-button>
|
||||||
|
</span>
|
||||||
|
</el-dialog>
|
||||||
|
<!-- 表格预览区 -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import JSZip from 'jszip';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tables: [], // 保存解析后的表格数据
|
||||||
|
addVisible:false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
clickUpload(){
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.type = 'file';
|
||||||
|
input.accept = '.docx'; // 限制为 Word 文件
|
||||||
|
input.addEventListener('change', function () {
|
||||||
|
const file = input.files[0];
|
||||||
|
if (file) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = function (e) {
|
||||||
|
const arrayBuffer = e.target.result;
|
||||||
|
_this.extractTablesFromWord(arrayBuffer, function (tablesHtml) {
|
||||||
|
ed.setContent(tablesHtml);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
input.click();
|
||||||
|
},
|
||||||
|
// 拖拽事件处理
|
||||||
|
onDragOver(event) {
|
||||||
|
event.currentTarget.style.borderColor = '#007bff';
|
||||||
|
},
|
||||||
|
onDragLeave(event) {
|
||||||
|
event.currentTarget.style.borderColor = '#ccc';
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// 提取 Word 文件中的表格
|
||||||
|
extractTablesFromWord(arrayBuffer, callback) {
|
||||||
|
const zip = new JSZip();
|
||||||
|
var that = this;
|
||||||
|
zip.loadAsync(arrayBuffer)
|
||||||
|
.then(function (zip) {
|
||||||
|
const docXmlPath = 'word/document.xml'; // Word 主文档的 XML 路径
|
||||||
|
return zip.file(docXmlPath).async('string');
|
||||||
|
})
|
||||||
|
.then(function (docXml) {
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const xmlDoc = parser.parseFromString(docXml, 'text/xml');
|
||||||
|
const tables = xmlDoc.getElementsByTagName('w:tbl'); // 查找 Word 表格标签
|
||||||
|
|
||||||
|
let html = '';
|
||||||
|
for (let table of tables) {
|
||||||
|
html += that.convertTableToHtml(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!html) {
|
||||||
|
html = '<p>未检测到表格内容。</p>';
|
||||||
|
}
|
||||||
|
callback(html);
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.innerHTML = html;
|
||||||
|
that.updateTableStyles(container);
|
||||||
|
|
||||||
|
console.log('html at line 400:', html);
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
console.error('解析 Word 文件出错:', err);
|
||||||
|
callback('<p>文件解析失败,请检查文件格式。</p>');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async onDrop(event) {
|
||||||
|
const file = event.dataTransfer.files[0];
|
||||||
|
if (!file || !file.name.endsWith('.docx')) {
|
||||||
|
alert('请拖拽一个 Word 文件 (.docx)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const tables = await this.parseWordFile(file);
|
||||||
|
if (tables.length === 0) {
|
||||||
|
alert('未检测到表格内容');
|
||||||
|
} else {
|
||||||
|
this.tables = tables.map((table) => ({
|
||||||
|
html: this.convertTableToHtml(table),
|
||||||
|
orientation: 'portrait' // 默认纵向
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.addVisible=true
|
||||||
|
var html = '';
|
||||||
|
for (let table of this.tables) {
|
||||||
|
html += this.convertTableToHtml(table.html);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!html) {
|
||||||
|
html = '<p>未检测到表格内容。</p>';
|
||||||
|
}
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.innerHTML = html;
|
||||||
|
this.updateTableStyles(container);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('文件解析失败:', err);
|
||||||
|
alert('文件解析失败,请检查文件格式。');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 解析 Word 文件
|
||||||
|
async parseWordFile(file) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
reader.onload = async (e) => {
|
||||||
|
try {
|
||||||
|
const arrayBuffer = e.target.result;
|
||||||
|
|
||||||
|
const zip = new JSZip();
|
||||||
|
const zipContent = await zip.loadAsync(arrayBuffer);
|
||||||
|
const docXmlPath = 'word/document.xml';
|
||||||
|
const docXml = await zipContent.file(docXmlPath).async('string');
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const xmlDoc = parser.parseFromString(docXml, 'text/xml');
|
||||||
|
resolve(Array.from(xmlDoc.getElementsByTagName('w:tbl')));
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 将 XML 表格转换为 HTML
|
||||||
|
convertTableToHtml(tableNode) {
|
||||||
|
const rows = tableNode.getElementsByTagName('w:tr');
|
||||||
|
let html = '<table border="1" style="border-collapse: collapse;">';
|
||||||
|
for (let row of rows) {
|
||||||
|
html += '<tr>';
|
||||||
|
const cells = row.getElementsByTagName('w:tc');
|
||||||
|
for (let cell of cells) {
|
||||||
|
let cellHtml = '';
|
||||||
|
const paragraphs = cell.getElementsByTagName('w:p'); // 获取单元格内段落
|
||||||
|
|
||||||
|
for (let paragraph of paragraphs) {
|
||||||
|
const texts = paragraph.getElementsByTagName('w:r'); // 获取段落内的文本和样式
|
||||||
|
for (let run of texts) {
|
||||||
|
const textNode = run.getElementsByTagName('w:t')[0];
|
||||||
|
if (textNode) {
|
||||||
|
const style = this.getStyleFromRun(run); // 提取样式
|
||||||
|
cellHtml += `<span style="${style}">${textNode.textContent}</span>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cellHtml += '<br>'; // 段落换行
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `<td>${cellHtml}</td>`;
|
||||||
|
}
|
||||||
|
html += '</tr>';
|
||||||
|
}
|
||||||
|
html += '</table>';
|
||||||
|
|
||||||
|
return html;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 提取 w:r 节点中的样式并转换为 CSS
|
||||||
|
getStyleFromRun(run) {
|
||||||
|
const styleNode = run.getElementsByTagName('w:rPr')[0];
|
||||||
|
let style = '';
|
||||||
|
|
||||||
|
if (styleNode) {
|
||||||
|
// 加粗
|
||||||
|
if (styleNode.getElementsByTagName('w:b').length > 0) {
|
||||||
|
style += 'font-weight: bold;';
|
||||||
|
}
|
||||||
|
// 斜体
|
||||||
|
if (styleNode.getElementsByTagName('w:i').length > 0) {
|
||||||
|
style += 'font-style: italic;';
|
||||||
|
}
|
||||||
|
// 上标或下标
|
||||||
|
const vertAlign = styleNode.getElementsByTagName('w:vertAlign')[0];
|
||||||
|
if (vertAlign) {
|
||||||
|
const alignVal = vertAlign.getAttribute('w:val');
|
||||||
|
if (alignVal === 'superscript') {
|
||||||
|
style += 'vertical-align: super; font-size: smaller;';
|
||||||
|
} else if (alignVal === 'subscript') {
|
||||||
|
style += 'vertical-align: sub; font-size: smaller;';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 字体颜色
|
||||||
|
const colorNode = styleNode.getElementsByTagName('w:color')[0];
|
||||||
|
if (colorNode) {
|
||||||
|
const colorVal = colorNode.getAttribute('w:val');
|
||||||
|
style += `color: #${colorVal};`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return style;
|
||||||
|
},
|
||||||
|
replaceNegativeSign(node) {
|
||||||
|
if (node.nodeType === Node.TEXT_NODE) {
|
||||||
|
// 如果是文本节点,替换负号
|
||||||
|
node.nodeValue = node.nodeValue.replace(/^-(?=\d)/, '−');
|
||||||
|
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
||||||
|
// 如果是元素节点,递归处理子节点
|
||||||
|
node.childNodes.forEach(this.replaceNegativeSign);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
capitalizeFirstLetter(node) {
|
||||||
|
if (node.nodeType === Node.TEXT_NODE) {
|
||||||
|
// 如果是文本节点,只处理第一个非空字符
|
||||||
|
node.nodeValue = node.nodeValue.replace(/^\s*([a-zA-Z])/, (match, firstLetter) => {
|
||||||
|
return firstLetter.toUpperCase();
|
||||||
|
});
|
||||||
|
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
||||||
|
// 递归处理子节点
|
||||||
|
node.childNodes.forEach(this.capitalizeFirstLetter);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateTableStyles(container) {
|
||||||
|
var _this = this;
|
||||||
|
// 更新表格样式
|
||||||
|
const tables = container.querySelectorAll('table');
|
||||||
|
|
||||||
|
tables.forEach((table) => {
|
||||||
|
table.setAttribute(
|
||||||
|
'style',
|
||||||
|
`width: ${
|
||||||
|
this.typesettingType == 1 ? '17.18cm' : '25.88cm'
|
||||||
|
};border: none; margin: 0 auto !important;border-collapse: collapse; `
|
||||||
|
);
|
||||||
|
const cells = table.querySelectorAll('td');
|
||||||
|
cells.forEach((td) => {
|
||||||
|
if (/^-?\d+(\.\d+)?$/.test(td.textContent.trim())) {
|
||||||
|
_this.replaceNegativeSign(td);
|
||||||
|
}
|
||||||
|
const hasSupOrSub = _this.containsSupOrSub(td); // 检查当前 td 是否包含上下标
|
||||||
|
if (!hasSupOrSub) {
|
||||||
|
// 递归处理单元格内的所有子节点
|
||||||
|
td.childNodes.forEach(_this.capitalizeFirstLetter);
|
||||||
|
// 替换 <a> 标签为其内部文本
|
||||||
|
td.querySelectorAll('a').forEach((a) => {
|
||||||
|
const textNode = document.createTextNode(a.textContent); // 创建文本节点
|
||||||
|
a.replaceWith(textNode); // 用文本节点替换 <a> 标签
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 td 元素中的所有子元素
|
||||||
|
const childElements = td.querySelectorAll('*');
|
||||||
|
|
||||||
|
// 遍历每个子元素
|
||||||
|
childElements.forEach((element) => {
|
||||||
|
// 如果元素的文本内容匹配正则表达式
|
||||||
|
if (/\[\d+(?:,\d+)*\]/g.test(element.textContent)) {
|
||||||
|
console.log('匹配到带有数字的方括号内容');
|
||||||
|
// 为匹配的元素添加样式类
|
||||||
|
element.classList.add('color-highlight');
|
||||||
|
element.style.color = 'rgb(0,130,170)';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const firstRowTdElements = container.querySelectorAll('tr:first-child td'); // 获取第一个 <tr> 中的所有 <td> 元素
|
||||||
|
// 遍历所有 <td> 元素,添加上下边框样式
|
||||||
|
firstRowTdElements.forEach((td) => {
|
||||||
|
const currentStyle = td.getAttribute('style');
|
||||||
|
if (currentStyle) {
|
||||||
|
td.setAttribute(
|
||||||
|
'style',
|
||||||
|
currentStyle +
|
||||||
|
';border-top:1.0000pt solid #000 !important;mso-border-top-alt:0.5000pt solid #000 !important;border-bottom:1.0000pt solid #000 !important;mso-border-bottom-alt:0.5000pt solid #000 !important;'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
td.setAttribute(
|
||||||
|
'style',
|
||||||
|
'border-top:1.0000pt solid #000 !important;mso-border-top-alt:0.5000pt solid #000 !important;border-bottom:1.0000pt solid #000 !important;mso-border-bottom-alt:0.5000pt solid #000 !important;'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const firstRowTdElementsLast = container.querySelectorAll('tr:last-of-type td');
|
||||||
|
// 遍历所有 <td> 元素,添加上下边框样式
|
||||||
|
firstRowTdElementsLast.forEach((td) => {
|
||||||
|
// 获取当前的 style 属性(如果有)
|
||||||
|
const currentStyle = td.getAttribute('style');
|
||||||
|
// 如果已有 style 属性,则追加边框样式;如果没有 style 属性,则设置新的 style
|
||||||
|
if (currentStyle) {
|
||||||
|
td.setAttribute(
|
||||||
|
'style',
|
||||||
|
currentStyle +
|
||||||
|
';border-bottom:1.0000pt solid #000 !important;mso-border-bottom-alt:0.5000pt solid #000 !important;'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
td.setAttribute(
|
||||||
|
'style',
|
||||||
|
'border-bottom:1.0000pt solid #000 !important;mso-border-bottom-alt:0.5000pt solid #000 !important;'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
var editor = window.tinymce.activeEditor;
|
||||||
|
var html = '';
|
||||||
|
tables.forEach((e) => {
|
||||||
|
console.log('e at line 579:', e);
|
||||||
|
|
||||||
|
html += e.outerHTML;
|
||||||
|
console.log('html at line 582:', html);
|
||||||
|
});
|
||||||
|
// 将外部 DOM 内容更新到编辑器
|
||||||
|
const container1 = document.createElement('div');
|
||||||
|
container1.innerHTML = html; // html 是更新后的 HTML 内容
|
||||||
|
|
||||||
|
// 更新编辑器内容
|
||||||
|
editor.setContent(container1.innerHTML);
|
||||||
|
|
||||||
|
// 触发编辑器内容变化后,如果需要,可能还要设置编辑器的样式
|
||||||
|
editor.focus(); // 聚焦到编辑器
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// 确认表格
|
||||||
|
confirmTable(index) {
|
||||||
|
const table = this.tables[index];
|
||||||
|
alert(`表格 ${index + 1} 已确认,页面方向为 ${table.orientation === 'portrait' ? '纵向' : '横向'}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.drag-drop-area {
|
||||||
|
border: 2px dashed #ccc;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
width: 300px;
|
||||||
|
margin: 0 auto;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.drag-drop-area:hover {
|
||||||
|
border-color: #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-area {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.table-wrapper {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.table-preview {
|
||||||
|
border: 1px solid #000;
|
||||||
|
padding: 10px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
.table-options {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<tinymce
|
<tinymce
|
||||||
|
|
||||||
ref="tinymceChild"
|
ref="tinymceChild"
|
||||||
:value="updatedHtml"
|
:value="updatedHtml"
|
||||||
:typesettingType="typesettingType"
|
:typesettingType="typesettingType"
|
||||||
@@ -71,10 +70,12 @@ export default {
|
|||||||
selectionRange: null, // 保存选区范围
|
selectionRange: null, // 保存选区范围
|
||||||
updatedHtml: '',
|
updatedHtml: '',
|
||||||
imgHtml: '',
|
imgHtml: '',
|
||||||
typesettingTypeOptions: [
|
orientation: this.typesettingType == 1 ? 'portrait' : 'landscape',
|
||||||
{ label: this.$t('commonTable.typesettingType1'), value: 1, pageWidth: 210 },
|
pageSize: {
|
||||||
{ label: this.$t('commonTable.typesettingType2'), value: 2, pageWidth: 297 }
|
width: 11906,
|
||||||
],
|
height: 16976
|
||||||
|
},
|
||||||
|
|
||||||
transform: null,
|
transform: null,
|
||||||
typesettingType: 1
|
typesettingType: 1
|
||||||
};
|
};
|
||||||
@@ -145,10 +146,9 @@ export default {
|
|||||||
handlePaste(event) {
|
handlePaste(event) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// 解析内容并替换表格
|
// 解析内容并替换表格
|
||||||
console.log('event.currentTarget.innerHTML at line 146:', event)
|
console.log('event.currentTarget.innerHTML at line 146:', event);
|
||||||
let replacedContent = this.setHtmlWord(event);
|
let replacedContent = this.setHtmlWord(event);
|
||||||
|
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$refs.tinymceChild.tableSuccessCBK(replacedContent);
|
this.$refs.tinymceChild.tableSuccessCBK(replacedContent);
|
||||||
});
|
});
|
||||||
@@ -652,6 +652,6 @@ td input ::placeholder {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.modal {
|
.modal {
|
||||||
z-index: 1000; /* 保证模态框 z-index 低于颜色选择框 */
|
z-index: 1000; /* 保证模态框 z-index 低于颜色选择框 */
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ Vue.component("Editor", Editor);
|
|||||||
|
|
||||||
import commonTable from '@/components/page/components/table/table.vue'
|
import commonTable from '@/components/page/components/table/table.vue'
|
||||||
Vue.component('common-table', commonTable);
|
Vue.component('common-table', commonTable);
|
||||||
|
import commonDragWord from '@/components/page/components/table/dragWord.vue'
|
||||||
|
Vue.component('common-drag-word', commonDragWord);
|
||||||
Vue.use(VueI18n);
|
Vue.use(VueI18n);
|
||||||
Vue.use(ElementUI, {
|
Vue.use(ElementUI, {
|
||||||
size: 'small',
|
size: 'small',
|
||||||
|
|||||||
Reference in New Issue
Block a user