最近菜鸟刚搞完签字,结果需求就加了,如果合同有附件(.doc.docx),签名就是签到附件里面,没有附件才是签到那个html里面!
这里附件签名过后就不能像html那样可以修改原html了,毕竟这个要写入word文档,实现不了,所以我们公司退而求其次只需要记录附件和签字的对应关系,以及何时签字就行!
实现难点在于:前端只能展示docx;后端也不好把 doc、docx 转换为html !(只能说 doc 是什么天杀的格式?)
后端 doc 转 docx
但是好在后端可以将 doc 转成 docx,菜鸟是前端不知道后端咋搞的,只要了点代码过来,各位可以参考一下:
public void docToDocx(String batchId, HttpServletResponse response) throws IOException {
// FileEntity data = adminFileService.queryOne(batchId).getData();
// String path = data.getPath();
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
response.setHeader("Content-Disposition", "attachment; filename=\"converted.docx\"");
ServletOutputStream outputStream = response.getOutputStream();
File inputWord = new File("E:\\03 项目沟通文档\\1820994860746969088-(7.31第二次修改)农大三代建库测序技术委托合同.doc");
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
InputStream docxInputStream = new FileInputStream(inputWord)) {
IConverter converter = LocalConverter.builder().build();
boolean flag = false;
flag = converter.convert(docxInputStream).as(DocumentType.DOC).to(byteArrayOutputStream).as(DocumentType.DOCX).execute();
if (flag) {
converter.shutDown();
}
byteArrayOutputStream.writeTo(outputStream);
System.out.println("转换成功");
} catch (Exception e) {
e.printStackTrace();
}
}
后端下载的插件名称:
<dependency>
<groupId>com.documents4j</groupId>
<artifactId>documents4j-local</artifactId>
<version>1.0.3</version>
</dependency>
<dependency>
<groupId>com.documents4j</groupId>
<artifactId>documents4j-transformer-msoffice-word</artifactId>
<version>1.0.3</version>
</dependency>
好了,后端菜鸟就不多废话了,接下来是重头戏前端代码!
2025/8/8补充:
更加推荐:LibreOffice + documents4j + JODConverter
dcox - preview
都转化成了 docx 了,那我前端展示也是分分钟的事情!需要使用插件:dcox - preview !
安装
npm i docx-preview --save
导入
import { renderAsync } from 'docx-preview';
使用
js
// 请求后端
getPrintApi(route.query.batchId)
.then(async (res) => {
await nextTick();
let reportContainer = document.getElementById("reportContainer");
renderAsync(
res,
reportContainer, // HTMLElement 渲染文档内容的元素,
null // HTMLElement, 用于呈现文档样式、数字、字体的元素。如果为 null,则将使用 reportContainer。
)
.then((res) => {
console.log("res---->", res);
let bigBox = document.querySelector(".bigBox");
let contractBox = document.getElementById("reportContainer");
let st = window.getComputedStyle(contractBox, null);
var tr = st.getPropertyValue("transform");
if (tr === "none") {
isScale.value = false;
bigBox.style.height = "auto";
const height = bigBox.offsetHeight;
bigBoxHeight.value = height;
} else {
isScale.value = true;
bigBox.style.height = "auto";
const height = bigBox.offsetHeight * 0.5;
bigBoxmargin.value = (window.innerWidth - 700 * 0.5) / 2;
bigBoxHeight.value = height;
}
})
.catch((err) => {
console.log(err);
// eslint-disable-next-line
ElMessage({
message: "网络问题,请刷新界面!",
type: "error",
});
});
})
.catch((err) => {
console.log(err);
});
template
<template>
<div class="bigBox" :style="{ height: bigBoxHeight + 'px' }">
<div id="reportContainer" :style="{ marginLeft: isScale ? bigBoxmargin + 'px' : 'auto' }"></div>
</div>
<div class="btnBox">
<el-button type="primary" @click="showSign">前往签字</el-button>
</div>
<!-- 签字弹窗 -->
<sign v-if="signshow" :dialogVisible="signshow" @closeEvent="hideSign"></sign>
</template>
注意
1、这里的请求里一定要加上 responseType: "arraybuffer",
2、这里 bigBoxHeight 、bigBoxmargin 可以见我上一篇文章:前端实现签字效果+合同展示
加载本地文件
<script setup>
import { onMounted } from "vue";
import { renderAsync } from "docx-preview";
// 在 Vite 里,直接 `import` 一个非代码文件会返回 URL,但加上 `?url` 可以确保它返回的是资源路径字符串而不是尝试解析它。
import docxUrl from "@/assets/templateWord/WordReportA.docx?url";
onMounted(async () => {
const container = document.getElementById("reportContainer");
// 用 fetch 把 docx 转成 ArrayBuffer
const response = await fetch(docxUrl);
const arrayBuffer = await response.arrayBuffer();
// 渲染
await renderAsync(arrayBuffer, container);
});
</script>
<template>
<div id="reportContainer"></div>
</template>
<style lang="scss" scoped>
:deep(.docx-wrapper) {
background-color: transparent;
.docx {
padding: 20px !important;
}
}
</style>
vue-office
vue-office使用简单,文档也清晰,推荐使用,官网:501351981.github.io/vue-office/…
注意
vue-office 不支持 pnpm 引入,只能 npm !!!
使用
<script setup>
//引入VueOfficeDocx组件
import VueOfficeDocx from '@vue-office/docx'
//引入相关样式
import '@vue-office/docx/lib/index.css'
import { downloadWordApi } from '@/network/reportDownloadApi'
const props = defineProps({
dialogVisible: {
type: Boolean,
default: false
},
reportId: {
type: Number,
default: null
}
})
const emit = defineEmits(['closeEvent'])
// 关闭弹窗
function handleClose() {
emit('closeEvent', false)
}
const dialogBox = ref()
function closeDialog() {
dialogBox.value.resetFields()
}
// 获取报告详情
let src = ref('')
let loading = ref(true)
const getpreviewReport = async () => {
const res = await downloadWordApi(props.reportId)
if (res?.byteLength <= 200) {
const jsonString = new TextDecoder('utf-8').decode(res)
const jsonObject = JSON.parse(jsonString)
ElMessage({
message: jsonObject.message,
type: 'error'
})
loading.value = false
return
}
// 将二进制流转为blob对象
let blob = new Blob([res], {
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
})
// 生成文件URL
src.value = URL.createObjectURL(blob)
loading.value = false
}
getpreviewReport()
// docx渲染
const renderedHandler = () => {
console.log('渲染完成')
}
const errorHandler = () => {
console.log('渲染失败')
}
</script>
<template>
<div>
<el-dialog
title="详情"
ref="dialogBox"
width="80%"
top="2%"
:modelValue="dialogVisible"
:before-close="handleClose"
@close="closeDialog"
:close-on-click-modal="false"
:destroy-on-close="true"
>
<div style="height: 75vh; overflow-y: auto" v-if="src != '' && !loading">
<vue-office-docx :src="src" @rendered="renderedHandler" @error="errorHandler" />
</div>
<p style="text-align: center" v-else-if="src == '' && loading">加载中……</p>
<p style="text-align: center" v-else>暂无数据!</p>
<template #footer>
<div>
<el-button @click="handleClose">关闭</el-button>
</div>
</template>
</el-dialog>
</div>
</template>