532 lines
22 KiB
Vue
532 lines
22 KiB
Vue
<template>
|
||
<div class="tinymce-container editor-container">
|
||
<textarea class="tinymce-textarea" :id="tinymceId"></textarea>
|
||
</div>
|
||
</template>
|
||
<script>
|
||
import { string } from 'html-docx-js/dist/html-docx';
|
||
import htmlDocx from 'html-docx-js/dist/html-docx.js';
|
||
import { Document, Packer, PageOrientation, Paragraph, TextRun } from 'docx'; // 引入 docx.js
|
||
import html2canvas from 'html2canvas';
|
||
|
||
const tableStyle = ` b span{
|
||
font-weight: bold !important;
|
||
}
|
||
i span{
|
||
font-style: italic !important; ;
|
||
}
|
||
sub span{
|
||
vertical-align: sub;
|
||
}
|
||
sup span{
|
||
vertical-align: sup;
|
||
}
|
||
sub {
|
||
vertical-align: sub!important;
|
||
}
|
||
sup {
|
||
vertical-align: sup !important;
|
||
}
|
||
span[style*="vertical-align: super"] {
|
||
vertical-align: super !important;
|
||
}
|
||
span[style*="vertical-align: sub"] {
|
||
vertical-align: sub !important;
|
||
}
|
||
table {
|
||
border:0px !important;
|
||
border-collapse: collapse; /* 去除单元格间隙 */
|
||
width: auto;
|
||
margin : 0 auto !important;
|
||
table-layout: auto; /* 自动调整列宽 */
|
||
text-align:left;
|
||
font-family:'Charis SIL' !important;
|
||
font-size: 14px !important;
|
||
mso-font-kerning: 1.0000pt !important;
|
||
line-height: 20px !important;
|
||
mos-line-height: 20px !important;
|
||
}
|
||
table td, table th {
|
||
padding: 5px;
|
||
text-align:left !important;
|
||
white-space: pre-wrap; /* 保留换行符并换行 */
|
||
word-wrap: break-word; /* 长单词自动换行 */
|
||
word-break: break-word;
|
||
font-family:'Charis SIL' !important;
|
||
font-size: 14px !important;
|
||
mso-font-kerning: 1.0000pt !important;
|
||
line-height: 20px !important;
|
||
mos-line-height: 20px !important;
|
||
}
|
||
table tbody tr td{
|
||
|
||
text-align:left !important;
|
||
border-left:none !important;
|
||
mso-border-left-alt:none !important;
|
||
border-right:none !important;
|
||
mso-border-right-alt:none !important;
|
||
border-top:none;mso-border-top-alt:none !important;
|
||
border-bottom:none !important;
|
||
mso-border-bottom-alt:none !important;
|
||
|
||
word-break: keep-all !important;
|
||
text-align: justify !important; // 设置两端对齐
|
||
|
||
|
||
}
|
||
table tr td p{
|
||
|
||
text-align:left !important;
|
||
|
||
margin:0;
|
||
font-family:'Charis SIL' !important;
|
||
font-size: 14px !important;
|
||
mso-font-kerning: 1.0000pt !important;
|
||
line-height: 20px !important;
|
||
mos-line-height: 20px !important;
|
||
}
|
||
table span{
|
||
|
||
color:#000000;text-align:left !important;
|
||
font-family:'Charis SIL' !important;
|
||
font-size: 14px !important;
|
||
mso-font-kerning: 1.0000pt !important;
|
||
line-height: 20px !important;
|
||
mos-line-height: 20px !important;
|
||
}
|
||
table .color-highlight{
|
||
color:rgb(0,130,170) !important;
|
||
font-family:'Charis SIL' !important;
|
||
font-size: 14px !important;
|
||
mso-font-kerning: 1.0000pt !important;
|
||
line-height: 20px !important;
|
||
mos-line-height: 20px !important;
|
||
}
|
||
table tr:first-child td {
|
||
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;
|
||
}
|
||
table tr:last-of-type td {
|
||
border-bottom:1.0000pt solid #000 !important;mso-border-bottom-alt:0.5000pt solid #000 !important;;
|
||
}
|
||
|
||
|
||
`;
|
||
export default {
|
||
name: 'tinymce',
|
||
components: {},
|
||
props: {
|
||
id: {
|
||
type: String
|
||
},
|
||
value: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
isEdit: {},
|
||
toolbar: {
|
||
type: Array,
|
||
required: false,
|
||
default() {
|
||
return [];
|
||
}
|
||
},
|
||
menubar: {
|
||
default: 'file edit insert view format table '
|
||
},
|
||
height: {
|
||
type: Number,
|
||
required: false,
|
||
default: 360
|
||
},
|
||
width: {
|
||
type: String,
|
||
required: false,
|
||
default: '100%'
|
||
},
|
||
isShowArtWorkButton: {
|
||
default: false
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
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,
|
||
dialogVisible: false,
|
||
form: {
|
||
name: '',
|
||
region: '',
|
||
date1: '',
|
||
date2: '',
|
||
delivery: false,
|
||
type: [],
|
||
resource: '',
|
||
desc: ''
|
||
},
|
||
formLabelWidth: '120px',
|
||
hasChange: false,
|
||
hasInit: false,
|
||
|
||
tinymceId: this.id || 'vue-tinymce-' + +new Date()
|
||
};
|
||
},
|
||
watch: {
|
||
value(val) {
|
||
if (!this.hasChange && this.hasInit) {
|
||
this.$nextTick(() => window.tinymce.get(this.tinymceId).setContent(val));
|
||
}
|
||
}
|
||
},
|
||
mounted() {
|
||
this.typesettingType = 1;
|
||
this.initTinymce();
|
||
},
|
||
activated() {
|
||
this.typesettingType = 1;
|
||
this.initTinymce();
|
||
},
|
||
deactivated() {
|
||
this.destroyTinymce();
|
||
},
|
||
methods: {
|
||
handleSubmit() {
|
||
this.$refs.uploadImage.handleSubmit();
|
||
},
|
||
getDetail(val) {
|
||
if (this.hasInit == true) {
|
||
this.$nextTick(() => window.tinymce.get(this.tinymceId).setContent(val));
|
||
}
|
||
},
|
||
//将字符串添加到富文本编辑器中
|
||
addArtWork(str) {
|
||
window.tinymce.get(this.tinymceId).insertContent(str);
|
||
},
|
||
onClick(e) {
|
||
this.$emit('onClick', e, tinymce);
|
||
},
|
||
|
||
changeTable() {
|
||
// 获取所有表格
|
||
const tables = window.tinymce.get(this.tinymceId).getBody().querySelectorAll('table');
|
||
console.log('tables at line 110:', tables);
|
||
|
||
// 遍历并设置样式
|
||
tables.forEach((table) => {
|
||
const editor = window.tinymce.get(this.tinymceId);
|
||
editor.dom.setStyles(table, {
|
||
width: this.typesettingType == 1 ? '17.18cm' : '25.88cm'
|
||
});
|
||
});
|
||
|
||
this.$forceUpdate();
|
||
},
|
||
initTinymce() {
|
||
const _this = this;
|
||
window.tinymce.init({
|
||
selector: `#${this.tinymceId}`,
|
||
content_css: false, // 禁用默认样式
|
||
table_resize_bars: true, // 启用拖动调整功能
|
||
valid_elements: '*[*]', // 允许所有 HTML 标签
|
||
|
||
paste_preprocess: function (plugin, args) {
|
||
let content = args.content;
|
||
const container = document.createElement('div');
|
||
container.innerHTML = content;
|
||
_this.updateTableStyles(container);
|
||
args.content = container.innerHTML; // 更新内容
|
||
},
|
||
content_style: `${tableStyle}`,
|
||
formats: {
|
||
bold: { inline: 'b' },
|
||
italic: { inline: 'i' }
|
||
},
|
||
body_class: 'panel-body ',
|
||
object_resizing: false,
|
||
toolbar: this.toolbar.length > 0 ? this.toolbar : toolbar,
|
||
menubar: false, // 启用菜单栏并保持必要的项目
|
||
statusbar: false, // 关闭底部状态栏
|
||
custom_colors: false,
|
||
color_map: ['000000', 'Black', '0082AA', 'TMR Blue'],
|
||
plugins: 'forecolor code paste table image', // 启用 forecolor 和 code 插件
|
||
end_container_on_empty_block: true,
|
||
content_css: 'default', // 加载 TinyMCE 默认样式表
|
||
//设置自定义按钮 myCustomToolbarButton
|
||
|
||
setup(ed) {
|
||
ed.ui.registry.addButton('uploadWord', {
|
||
text: 'Word',
|
||
icon: 'import-word', // 使用自定义图标
|
||
onAction: function () {
|
||
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.$commonJS.extractTablesFromWord(arrayBuffer, function (tablesHtml) {
|
||
console.log('tablesHtml at line 279:', 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 () {
|
||
const editorBody = ed.getBody();
|
||
// 创建 MutationObserver 监听内容变化
|
||
const observer = new MutationObserver(() => {
|
||
console.log('editorBody at line 313:', editorBody);
|
||
// _this.updateTableStyles(editorBody, _this.typesettingType);
|
||
// const hasHorizontalScrollbar = editorBody.scrollWidth > editorBody.clientWidth;
|
||
// if (hasHorizontalScrollbar) {
|
||
// console.log('TinyMCE 出现横向滚动条');
|
||
// } else {
|
||
// console.log('没有横向滚动条');
|
||
// }
|
||
});
|
||
|
||
// 监听子节点和内容的变化
|
||
observer.observe(editorBody, { childList: true, subtree: true, characterData: true });
|
||
});
|
||
// 定义自定义按钮
|
||
ed.ui.registry.addButton('clearButton', {
|
||
text: 'Empty',
|
||
|
||
onAction: () => {
|
||
// 插入自定义表格到编辑器中
|
||
ed.setContent('');
|
||
}
|
||
});
|
||
|
||
// 定义自定义按钮
|
||
ed.ui.registry.addButton('customButtonExportWord', {
|
||
text: _this.$t('commonTable.exportWord'),
|
||
onAction: () => {
|
||
// 插入自定义表格到编辑器中
|
||
let content = ed.getContent(); // 获取内容
|
||
content = content.replace(/<strong>/g, '<b>').replace(/<\/strong>/g, '</b>');
|
||
content = content.replace(/<em>/g, '<i>').replace(/<\/strong>/g, '</i>');
|
||
const container = document.createElement('div');
|
||
container.innerHTML = content;
|
||
|
||
_this.export('table', _this.$commonJS.updateTableStyles(container, _this.typesettingType, 1));
|
||
}
|
||
});
|
||
// 定义自定义按钮
|
||
ed.ui.registry.addButton('customButtonExportImg', {
|
||
text: _this.$t('commonTable.exportImg'),
|
||
onAction: () => {
|
||
// 插入自定义表格到编辑器中
|
||
_this.export('image', ed.getContent());
|
||
}
|
||
});
|
||
ed.ui.registry.addContextToolbar('spacer', {
|
||
predicate: () => false, // 保持静态
|
||
items: '',
|
||
scope: 'node'
|
||
});
|
||
ed.on('paste', (event) => {});
|
||
ed.on('SetContent', function (e) {
|
||
e.content = e.content.replace(/<strong>/g, '<b>').replace(/<\/strong>/g, '</b>');
|
||
e.content = e.content.replace(/<em>/g, '<i>').replace(/<\/em>/g, '</i>');
|
||
});
|
||
ed.on('GetContent', function (e) {
|
||
e.content = e.content.replace(/<b>/g, '<strong>').replace(/<\/b>/g, '</strong>');
|
||
e.content = e.content.replace(/<i>/g, '<em>').replace(/<\/i>/g, '</em>');
|
||
});
|
||
},
|
||
init_instance_callback: (editor) => {
|
||
if (_this.value) {
|
||
editor.setContent(_this.value);
|
||
}
|
||
_this.hasInit = true;
|
||
editor.on('NodeChange Change KeyUp SetContent', () => {
|
||
this.hasChange = true;
|
||
this.$emit('input', editor.getContent());
|
||
});
|
||
}
|
||
});
|
||
},
|
||
// 提取 Word 文件中的表格
|
||
|
||
updateTableStyles(container) {
|
||
var html = this.$commonJS.updateTableStyles(container, this.typesettingType);
|
||
var editor = window.tinymce.activeEditor; // 将外部 DOM 内容更新到编辑器
|
||
const container1 = document.createElement('div');
|
||
container1.innerHTML = html; // html 是更新后的 HTML 内容
|
||
editor.setContent(container1.innerHTML); // 更新编辑器内容
|
||
editor.focus(); // 聚焦到编辑器// 触发编辑器内容变化后,如果需要,可能还要设置编辑器的样式
|
||
},
|
||
//销毁富文本
|
||
destroyTinymce() {
|
||
if (window.tinymce.get(this.tinymceId)) {
|
||
window.tinymce.get(this.tinymceId).destroy();
|
||
}
|
||
},
|
||
//设置内容
|
||
setContent(value) {
|
||
window.tinymce.get(this.tinymceId).setContent(value);
|
||
},
|
||
//获取内容
|
||
getContent(type) {
|
||
this.$emit('getContent', type, window.tinymce.get(this.tinymceId).getContent());
|
||
console.log('window.tinymce.get(this.tinymceId).getContent() at line 431:', window.tinymce.get(this.tinymceId).getContent());
|
||
},
|
||
|
||
async export(type, data) {
|
||
if (type == 'table') {
|
||
var tableHtml = `<html xmlns:w="urn:schemas-microsoft-com:office:word">
|
||
<head>
|
||
<style>
|
||
|
||
${tableStyle}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
${data}
|
||
</body>
|
||
</html>`;
|
||
|
||
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');
|
||
link.href = URL.createObjectURL(converted);
|
||
link.download = 'table.docx';
|
||
link.click();
|
||
} else if (type == 'image') {
|
||
const hiddenContainer = document.createElement('div');
|
||
hiddenContainer.style.position = 'absolute';
|
||
hiddenContainer.style.left = '-9999ppx';
|
||
hiddenContainer.style.top = '0';
|
||
hiddenContainer.innerHTML = data;
|
||
const style = document.createElement('style');
|
||
style.innerHTML = `${tableStyle}`;
|
||
document.head.appendChild(style);
|
||
document.body.appendChild(hiddenContainer);
|
||
// 使用 html2canvas 捕获表格内容
|
||
const table = hiddenContainer.querySelector('table'); // 找到表格
|
||
table.style.border = 'none';
|
||
// 使用 html2canvas 将内容转为图片
|
||
html2canvas(hiddenContainer, {
|
||
scale: 2, // 提高图片的分辨率,默认为1,设置为2可以使图片更清晰
|
||
logging: false, // 禁用日志输出
|
||
useCORS: true, // 允许跨域图像
|
||
allowTaint: true // 允许污染 canvas,解决图片链接不可用问题
|
||
})
|
||
// 清空现有内容,显示图片
|
||
.then((canvas) => {
|
||
const imgData = canvas.toDataURL('image/png'); // 创建一个图片对象
|
||
const link = document.createElement('a'); // 创建一个链接并下载图片
|
||
link.href = imgData;
|
||
link.download = 'image.png';
|
||
link.click();
|
||
});
|
||
}
|
||
},
|
||
inlineStyles(element) {
|
||
const styles = window.getComputedStyle(element);
|
||
for (let style in styles) {
|
||
if (styles.hasOwnProperty(style)) {
|
||
element.style[style] = styles[style];
|
||
}
|
||
}
|
||
},
|
||
|
||
//获取上传图片后的地址,并设置图片样式
|
||
tableSuccessCBK(arr) {
|
||
console.log(arr, '222');
|
||
const _this = this;
|
||
window.tinymce.get(_this.tinymceId).insertContent(arr);
|
||
}
|
||
},
|
||
destroyed() {
|
||
this.destroyTinymce();
|
||
}
|
||
};
|
||
</script>
|
||
<style scoped>
|
||
::v-deep .tox-tinymce-aux {
|
||
z-index: 9999 !important;
|
||
}
|
||
::v-deep .tox .tox-menu {
|
||
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>
|