|
|
|
|
@ -26,15 +26,7 @@
|
|
|
|
|
</div>
|
|
|
|
|
<div class="preview-container" v-if="formatType === 1" v-html="previewContent"></div>
|
|
|
|
|
<div class="preview-container" v-else>
|
|
|
|
|
<iframe
|
|
|
|
|
v-if="docxUrl"
|
|
|
|
|
:src="docxUrl"
|
|
|
|
|
frameborder="0"
|
|
|
|
|
style="width: 100%; height: 100%;"
|
|
|
|
|
></iframe>
|
|
|
|
|
<div v-else class="no-preview">
|
|
|
|
|
请上传文档进行预览
|
|
|
|
|
</div>
|
|
|
|
|
<div v-html="previewContent"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
@ -159,15 +151,7 @@
|
|
|
|
|
>
|
|
|
|
|
<div class="modal-preview-container" v-if="formatType === 1" v-html="previewContent"></div>
|
|
|
|
|
<div class="modal-preview-container" v-else>
|
|
|
|
|
<iframe
|
|
|
|
|
v-if="docxUrl"
|
|
|
|
|
:src="docxUrl"
|
|
|
|
|
frameborder="0"
|
|
|
|
|
style="width: 100%; height: 100%;"
|
|
|
|
|
></iframe>
|
|
|
|
|
<div v-else class="no-preview">
|
|
|
|
|
请上传文档进行预览
|
|
|
|
|
</div>
|
|
|
|
|
<div v-html="previewContent"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
</div>
|
|
|
|
|
@ -189,7 +173,7 @@ export default {
|
|
|
|
|
previewContent: '', // 预览内容
|
|
|
|
|
showEditDrawer: false,
|
|
|
|
|
showPreviewModal: false, // 添加预览模态窗口控制变量
|
|
|
|
|
currentTemplateIndex: 1,
|
|
|
|
|
currentTemplateIndex: 0,
|
|
|
|
|
editForm: {
|
|
|
|
|
field: '', // Renamed from name to field
|
|
|
|
|
name: '', // Renamed from label to name (Chinese name)
|
|
|
|
|
@ -201,6 +185,10 @@ export default {
|
|
|
|
|
docxUrl: null,
|
|
|
|
|
templateFile: null,
|
|
|
|
|
templates: [
|
|
|
|
|
{
|
|
|
|
|
name:'',
|
|
|
|
|
content:''
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: '资金划拨审批单',
|
|
|
|
|
content: `
|
|
|
|
|
@ -866,15 +854,21 @@ export default {
|
|
|
|
|
this.loadTemplate(newVal);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
formatType: {
|
|
|
|
|
immediate: true,
|
|
|
|
|
handler(val) {
|
|
|
|
|
if (val === 1) {
|
|
|
|
|
this.loadTemplate(this.currentTemplateIndex);
|
|
|
|
|
} else {
|
|
|
|
|
this.codeContent = '<?xml version="1.0" encoding="UTF-8"?>\n<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">\n <!-- 这里将显示生成的DOCX代码 -->\n</w:document>'
|
|
|
|
|
this.previewContent = ''
|
|
|
|
|
formatType(newVal) {
|
|
|
|
|
if (newVal === 1) {
|
|
|
|
|
// HTML 格式
|
|
|
|
|
if (this.$refs.codeEditor) {
|
|
|
|
|
this.$refs.codeEditor.setValue(this.codeContent)
|
|
|
|
|
}
|
|
|
|
|
this.previewContent = this.codeContent
|
|
|
|
|
this.handleRefresh()
|
|
|
|
|
} else if (newVal === 2) {
|
|
|
|
|
// DOCX 格式
|
|
|
|
|
if (this.$refs.codeEditor) {
|
|
|
|
|
this.$refs.codeEditor.setValue(this.codeContent)
|
|
|
|
|
}
|
|
|
|
|
this.previewContent = this.codeContent
|
|
|
|
|
this.handleRefresh()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
@ -916,68 +910,21 @@ export default {
|
|
|
|
|
this.previewContent = processedContent;
|
|
|
|
|
},
|
|
|
|
|
handleUpload() {
|
|
|
|
|
// 创建文件输入元素
|
|
|
|
|
const input = document.createElement('input');
|
|
|
|
|
input.type = 'file';
|
|
|
|
|
input.accept = '.docx';
|
|
|
|
|
|
|
|
|
|
// 监听文件选择事件
|
|
|
|
|
const input = document.createElement('input')
|
|
|
|
|
input.type = 'file'
|
|
|
|
|
input.accept = '.docx'
|
|
|
|
|
input.onchange = async (e) => {
|
|
|
|
|
const file = e.target.files[0];
|
|
|
|
|
e.preventDefault() // 阻止默认行为
|
|
|
|
|
const file = e.target.files[0]
|
|
|
|
|
if (file) {
|
|
|
|
|
// 检查文件类型
|
|
|
|
|
if (!file.name.endsWith('.docx')) {
|
|
|
|
|
this.$message.error('请选择docx格式的文件');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// 创建临时URL
|
|
|
|
|
const blobUrl = URL.createObjectURL(file);
|
|
|
|
|
// 使用 Microsoft Office Online Viewer
|
|
|
|
|
this.docxUrl = `https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(blobUrl)}`;
|
|
|
|
|
|
|
|
|
|
// 清空代码预览区
|
|
|
|
|
this.codeContent = '';
|
|
|
|
|
|
|
|
|
|
// 将文件转换为ArrayBuffer
|
|
|
|
|
const arrayBuffer = await this.readFileAsArrayBuffer(file);
|
|
|
|
|
|
|
|
|
|
// 使用mammoth转换docx为HTML,用于提取变量
|
|
|
|
|
const result = await mammoth.convertToHtml({
|
|
|
|
|
arrayBuffer,
|
|
|
|
|
transformDocument: (document) => {
|
|
|
|
|
return document;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 提取占位变量
|
|
|
|
|
const variables = this.extractVariables(result.value);
|
|
|
|
|
|
|
|
|
|
// 显示提取的HTML内容
|
|
|
|
|
this.codeContent = result.value;
|
|
|
|
|
|
|
|
|
|
// 更新字段元数据
|
|
|
|
|
this.updateFieldMetadata(variables);
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('处理文档失败:', error);
|
|
|
|
|
this.$message.error('处理文档失败,请重试');
|
|
|
|
|
this.$message.error('请选择docx格式的文件')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
await this.convertDocxToHtml(file)
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 触发文件选择对话框
|
|
|
|
|
input.click();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
readFileAsArrayBuffer(file) {
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
const reader = new FileReader();
|
|
|
|
|
reader.onload = (event) => resolve(event.target.result);
|
|
|
|
|
reader.onerror = (error) => reject(error);
|
|
|
|
|
reader.readAsArrayBuffer(file);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
input.click()
|
|
|
|
|
},
|
|
|
|
|
handleEditField(field) {
|
|
|
|
|
this.editForm = { ...field };
|
|
|
|
|
@ -1189,7 +1136,132 @@ export default {
|
|
|
|
|
extractAndUpdateFields() {
|
|
|
|
|
const variables = this.extractVariables(this.codeContent) // variables are field names
|
|
|
|
|
this.updateFieldMetadata(variables) // Pass field names
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
async convertDocxToHtml(file) {
|
|
|
|
|
try {
|
|
|
|
|
// 设置表格名称为文件名(去掉扩展名)
|
|
|
|
|
const fileName = file.name.replace(/\.[^/.]+$/, "")
|
|
|
|
|
this.formName = fileName
|
|
|
|
|
|
|
|
|
|
const reader = new FileReader()
|
|
|
|
|
const arrayBuffer = await new Promise((resolve, reject) => {
|
|
|
|
|
reader.onload = (e) => resolve(e.target.result)
|
|
|
|
|
reader.onerror = (e) => reject(e)
|
|
|
|
|
reader.readAsArrayBuffer(file)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const result = await mammoth.convertToHtml({
|
|
|
|
|
arrayBuffer,
|
|
|
|
|
convertImage: mammoth.images.imgElement(function(image) {
|
|
|
|
|
return image.read("base64").then(function(imageBuffer) {
|
|
|
|
|
return {
|
|
|
|
|
src: "data:" + image.contentType + ";base64," + imageBuffer
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}),
|
|
|
|
|
styleMap: [
|
|
|
|
|
"p[style-name='Normal'] => p:style='margin: 0; padding: 0;'",
|
|
|
|
|
"p[style-name='Heading 1'] => h1:style='font-size: 24px; font-weight: bold; margin: 0 0 20px 0;'",
|
|
|
|
|
"p[style-name='Heading 2'] => h2:style='font-size: 20px; font-weight: bold; margin: 0 0 16px 0;'",
|
|
|
|
|
"p[style-name='Heading 3'] => h3:style='font-size: 18px; font-weight: bold; margin: 0 0 14px 0;'",
|
|
|
|
|
"p[style-name='Heading 4'] => h4:style='font-size: 16px; font-weight: bold; margin: 0 0 12px 0;'",
|
|
|
|
|
"p[style-name='Heading 5'] => h5:style='font-size: 14px; font-weight: bold; margin: 0 0 10px 0;'",
|
|
|
|
|
"p[style-name='Heading 6'] => h6:style='font-size: 12px; font-weight: bold; margin: 0 0 8px 0;'",
|
|
|
|
|
"p[style-name='List Paragraph'] => p:style='margin: 0; padding: 0; list-style-type: disc;'",
|
|
|
|
|
"p[style-name='Quote'] => blockquote:style='margin: 0 0 16px 0; padding: 10px 20px; border-left: 4px solid #ccc;'",
|
|
|
|
|
"p[style-name='Intense Quote'] => blockquote:style='margin: 0 0 16px 0; padding: 10px 20px; border-left: 4px solid #666; background: #f5f5f5;'",
|
|
|
|
|
"r[style-name='Strong'] => strong:style='font-weight: bold;'",
|
|
|
|
|
"r[style-name='Emphasis'] => em:style='font-style: italic;'",
|
|
|
|
|
"r[style-name='Code'] => code:style='font-family: monospace; background: #f5f5f5; padding: 2px 4px;'",
|
|
|
|
|
"table => table:style='width: 100%; border-collapse: collapse; margin: 0 0 16px 0;'",
|
|
|
|
|
"tr => tr:style='border-bottom: 1px solid #ddd;'",
|
|
|
|
|
"td => td:style='padding: 8px; border: 1px solid #ddd; text-align: center; vertical-align: middle;'",
|
|
|
|
|
"th => th:style='padding: 8px; border: 1px solid #ddd; font-weight: bold; text-align: center; vertical-align: middle;'",
|
|
|
|
|
"table[style-name='Table Grid'] => table:style='width: 100%; border-collapse: collapse; margin: 0 0 16px 0; border: 1px solid #000;'",
|
|
|
|
|
"table[style-name='Table Grid Light'] => table:style='width: 100%; border-collapse: collapse; margin: 0 0 16px 0; border: 1px solid #ddd;'",
|
|
|
|
|
"table[style-name='Table Grid Dark'] => table:style='width: 100%; border-collapse: collapse; margin: 0 0 16px 0; border: 1px solid #000;'",
|
|
|
|
|
"table[style-name='Table Grid Medium'] => table:style='width: 100%; border-collapse: collapse; margin: 0 0 16px 0; border: 1px solid #666;'"
|
|
|
|
|
],
|
|
|
|
|
transformDocument: function(document) {
|
|
|
|
|
return document;
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 处理转换后的 HTML,确保所有样式都内嵌
|
|
|
|
|
let html = result.value
|
|
|
|
|
|
|
|
|
|
// 处理表格样式,保留原始边框样式
|
|
|
|
|
html = html.replace(/<table/g, (match) => {
|
|
|
|
|
if (match.includes('style-name="Table Grid"')) {
|
|
|
|
|
return '<table style="width: 100%; border-collapse: collapse; margin: 0 0 16px 0; border: 1px solid #000;"'
|
|
|
|
|
} else if (match.includes('style-name="Table Grid Light"')) {
|
|
|
|
|
return '<table style="width: 100%; border-collapse: collapse; margin: 0 0 16px 0; border: 1px solid #ddd;"'
|
|
|
|
|
} else if (match.includes('style-name="Table Grid Dark"')) {
|
|
|
|
|
return '<table style="width: 100%; border-collapse: collapse; margin: 0 0 16px 0; border: 1px solid #000;"'
|
|
|
|
|
} else if (match.includes('style-name="Table Grid Medium"')) {
|
|
|
|
|
return '<table style="width: 100%; border-collapse: collapse; margin: 0 0 16px 0; border: 1px solid #666;"'
|
|
|
|
|
}
|
|
|
|
|
return '<table style="width: 100%; border-collapse: collapse; margin: 0 0 16px 0;"'
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 处理表格单元格样式,统一设置为居中
|
|
|
|
|
html = html.replace(/<td/g, (match) => {
|
|
|
|
|
let style = 'padding: 8px; border: 1px solid #ddd; text-align: center; vertical-align: middle;'
|
|
|
|
|
if (match.includes('style-name="Table Grid"')) {
|
|
|
|
|
style = 'padding: 8px; border: 1px solid #000; text-align: center; vertical-align: middle;'
|
|
|
|
|
} else if (match.includes('style-name="Table Grid Light"')) {
|
|
|
|
|
style = 'padding: 8px; border: 1px solid #ddd; text-align: center; vertical-align: middle;'
|
|
|
|
|
} else if (match.includes('style-name="Table Grid Dark"')) {
|
|
|
|
|
style = 'padding: 8px; border: 1px solid #000; text-align: center; vertical-align: middle;'
|
|
|
|
|
} else if (match.includes('style-name="Table Grid Medium"')) {
|
|
|
|
|
style = 'padding: 8px; border: 1px solid #666; text-align: center; vertical-align: middle;'
|
|
|
|
|
}
|
|
|
|
|
return `<td style="${style}"`
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 处理表头单元格样式,统一设置为居中
|
|
|
|
|
html = html.replace(/<th/g, (match) => {
|
|
|
|
|
let style = 'padding: 8px; border: 1px solid #ddd; font-weight: bold; text-align: center; vertical-align: middle;'
|
|
|
|
|
if (match.includes('style-name="Table Grid"')) {
|
|
|
|
|
style = 'padding: 8px; border: 1px solid #000; font-weight: bold; text-align: center; vertical-align: middle;'
|
|
|
|
|
} else if (match.includes('style-name="Table Grid Light"')) {
|
|
|
|
|
style = 'padding: 8px; border: 1px solid #ddd; font-weight: bold; text-align: center; vertical-align: middle;'
|
|
|
|
|
} else if (match.includes('style-name="Table Grid Dark"')) {
|
|
|
|
|
style = 'padding: 8px; border: 1px solid #000; font-weight: bold; text-align: center; vertical-align: middle;'
|
|
|
|
|
} else if (match.includes('style-name="Table Grid Medium"')) {
|
|
|
|
|
style = 'padding: 8px; border: 1px solid #666; font-weight: bold; text-align: center; vertical-align: middle;'
|
|
|
|
|
}
|
|
|
|
|
return `<th style="${style}"`
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 处理段落样式
|
|
|
|
|
html = html.replace(/<p>/g, '<p style="margin: 0; padding: 0; display: flex; align-items: center; justify-content: center; height: 100%;">')
|
|
|
|
|
|
|
|
|
|
// 处理图片样式
|
|
|
|
|
html = html.replace(/<img/g, '<img style="max-width: 100%; height: auto;"')
|
|
|
|
|
|
|
|
|
|
// 更新代码内容和预览内容
|
|
|
|
|
this.codeContent = html
|
|
|
|
|
|
|
|
|
|
// 提取变量并更新字段元数据
|
|
|
|
|
const variables = this.extractVariables(html)
|
|
|
|
|
this.updateFieldMetadata(variables)
|
|
|
|
|
|
|
|
|
|
// 直接更新代码编辑器和预览
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
// 更新代码编辑器内容
|
|
|
|
|
if (this.$refs.codeEditor) {
|
|
|
|
|
this.$refs.codeEditor.setValue(html)
|
|
|
|
|
}
|
|
|
|
|
// 更新预览内容
|
|
|
|
|
this.previewContent = html
|
|
|
|
|
this.handleRefresh()
|
|
|
|
|
})
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('转换失败:', error)
|
|
|
|
|
this.$message.error('文件转换失败')
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
// 在组件销毁时释放URL
|
|
|
|
|
beforeDestroy() {
|
|
|
|
|
|