主要用到了mammoth这个插件,mammoth.js是一个JavaScript库,主要用于将Microsoft Word文档(.docx格式)转换为HTML。它可以通过Node.js环境使用,也可以直接在浏览器中使用。
关键代码:
import mammoth from 'mammoth';
import { useEffect, useState, useRef } from 'react';
import { Slider, Spin, Space } from 'antd';
import './index.global.less';
import { type } from 'os';
import { parse } from 'path';
interface Props {
filePath: string;
type: string;
screenFlag: string;
isReadingMode: boolean;
}
// react中封装一个预览.doc和.docx文件的组件
const DocView: (props: Props) => JSX.Element = (props: Props) => {
const [docDom, setDocDom] = useState(<Space size="large" className="icon-loading">
<Spin size="large" />
</Space>);
const [textDom, setTextDom] = useState('');
const [scale, setScale] = useState(180);
const [warpClassName, setWarpclassName] = useState(['view-warper']);
const textInput = useRef(null);
const { filePath, type, screenFlag, isReadingMode } = props;
const mOptions = {
includeDefaultStyleMap: true,
convertImage: mammoth.images.imgElement(function (image) {
return image.read('base64').then(function (imageBuffer) {
if (image.contentType === 'image/x-wmf') {
return {
}
}
return {
src: 'data' + image.contentType + ';base64,' + imageBuffer,
}
});
}),
};
useEffect(() => {
return () => {
setDocDom('');
<Space size="large" className="icon-loading">
<Spin size="large" />
</Space>
}
}, []);
// 引入全局样式类名(具体样式我就不展示了)
useEffect(() => {
const name = ['view-warper'];
if (type === 'doc' || type === 'docx') {
name.push('docx-warp');
} else {
name.push('text-warp');
}
if (screenFlag) {
name.push('big-screen');
}
else {
name.push('small-screen');
}
if (isReadingMode) {
name.push('reading-mode');
}
else {
name.push('normal-mode');
}
setWarpclassName(name);
if (!screenFlag) {
setScale(100);
}
}, [screenFlag, type, isReadingMode]);
useEffect(() => {
if (type === 'txt' && !textDom) {
return false;
}
if (scale / 100 < 1) {
textInput.current.style.transformOrigin = 'top center';
} else {
textInput.current.style.transformOrigin = 'left top';
}
}, [scale]);
const handleScale = value => {
setScale(value);
};
useEffect(() => {
setDocDom(<Space size="large" className="icon-loading">
<Spin size="large" />
</Space>);
if (!filePath) {
return
}
if (type === 'txt') {
// 以自己项目实际接口为准
api.getDocumentDetailAfter(filePath).then((res: any) => {
if (res) {
setTextDom(res);
}
else {
setTextDom('该文档没有任何内容');
}
textInput.current.style.transformOrigin = 'left top';
}).catch((error: any) => {
throw new Error(error);
})
}
if (type === 'doc' || type === 'docx') {
const jsonFile = new XMLHttpRequest();
jsonFile.open('POST', '/xxx/xxx', true);
jsonFile.setRequestHeader('Content-Type', 'application/json');
jsonFile.send(filePath);
jsonFile.responseType = 'arraybuffer';
jsonFile.onreadystatechange = () => {
if (jsonFile.readyState === 4 && jsonFile.status === 200) {
mammoth.convertToHtml(
{ arrayBuffer: jsonFile.response },
mOptions
).then((result: any) => {
setDocDom(parse(result.value));
textInput.current.style.transformOrigin = 'left top';
}).catch(a => {
throw new Error(a);
setDocDom(<div className="res-error">
<p>无法查看此文档</p>
<p>请检查重新上传docx文件</p>
</div>);
})
} else if (jsonFile.status !== 200) {
setDocDom(<div className="res-error">
<p>网络超时,请稍后再试</p>
</div>);
}
}
}
}, [filePath]);
return (
<div className={warpClassName.join(' ')}>
{(type === 'doc' || type === 'docx')
&& <div
id="docx"
style={{ transform: `scale(${scale / 100})` }}
className='docx-content'
ref={textInput}
>
{docDom}
</div>
}
{(type === 'txt')
&& (textDom ? <textarea
id="txt"
className="txt-content"
style={{ transform: `scale(${scale / 100})` }}
ref={textInput}
value={textDom}
>
</textarea> : docDom)
}
{(type !== 'txt' && screenFlag)
&& <div className="docx-footer">
<div className="slider-warp">
<a
className="slider-icon more-icon"
onClick={() => setScale(setScale(~~scale + 5))}
/>
<Slider
className="slider"
min={0}
max={100}
defaultValue={100}
onChange={handleScale}
value={scale}
tooltipVisible={false}
/>
<a className="slider-icon less-icon" onClick={() => setScale(setScale(~~scale - 5))}></a>
</div>
</div>
}
</div>
)
}
index.global.less代码如下:
.view-warper {
.icon-loading {
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
.docx-warp {
height: calc(100% - 40px);
width: 100%;
overflow: auto;
}
.res-error {
padding: 108px 32px;
height: 250px;
transform: translate(0, 100%);
position: relative;
text-align-last: center;
background: url(./error.png) top center no-repeat; // 注意项目图片路径
p {
text-indent: 0;
margin-bottom: 0;
}
}
#docx {
height: 100%;
padding: 10px;
flex: 1;
.res-error {
padding: 108px 32px;
height: 250px;
transform: translate(0, 100%);
position: relative;
text-align-last: center;
background: url(./error.png) top center no-repeat; // 注意项目图片路径
p {
text-indent: 0;
margin-bottom: 0;
}
}
img {
max-width: 80%;
display: flex;
margin: 0 auto;
}
html {
overflow-y: scroll;
font-family: helvetica, arial, sans-serif;
}
body {
margin: 0;
padding: 0;
}
h3+p {
margin-left: 40px;
}
h3+p+ul,
h3+ol {
margin-left: 40px;
}
h2+ul {
margin-left: 60px;
}
h3+ol {
margin-left: 60px;
}
.container {
overflow: auto;
max-width: 940px;
margin: 0 auto;
}
.banner {
overflow: auto;
margin-bottom: 20px;
background-color: #555;
color: #fff;
}
.banner a {
color: #fff;
}
.banner h1 {
font-size: 20px;
line-height: 2;
margin: .5em 0;
}
.span8,
.span4 {
float: left;
}
.span8 {
width: 620px;
}
.span4 {
margin: 0 0 0 20px;
}
.well {
background-color: #f2f2f2;
border: 1px solid #ccc;
padding: 1em;
min-height: 200px;
}
.messages .warning {
color: #c60;
}
li {
list-style: decimal;
margin-bottom: 10px;
}
p {
margin-bottom: 10px;
line-height: 25;
text-indent: 60px;
}
p+ol {
margin-left: 50px;
}
h1 {
text-align: center;
font-weight: bolder;
font-size: 26px;
margin: 20px 0;
}
::marker {
color: blue;
font-size: 1.2em;
display: none;
content: '';
}
ul {
list-style: none;
}
strong {
font-size: 24px;
}
h2 {
padding-left: 30px;
}
table {
border-collapse: collapse;
margin: 24px auto;
font-size: 0.9em;
font-family: sans-serif;
box-shadow: 0 0 20px rgba(0, 0, 0, .15);
table-layout: fixed;
width: 90%;
strong {
font-size: 12px;
}
}
table td p {
border-bottom: none;
}
table thead tr {
background-color: #1ab394;
color: #fff;
text-align: left;
}
table li,
table p {
margin-bottom: 10px;
margin-left: 0;
border-bottom: 1px solid #e7e7e7;
padding-bottom: 10px;
padding-top: 15px;
}
table th,
.styled-table td {
padding: 12px 15px;
}
table tbody tr {
border-bottom: 1px solid #ddd;
}
table tbody tr:last-of-type {
border-bottom: 2px solid #1ab394;
}
table tbody tr.active-row {
font-weight: bolder;
color: #1ab394;
}
}
.docx-footer {
height: 40px;
border-top: 1px solid gray;
position: fixed;
width: inherit;
right: 424px;
background: #fff;
bottom: 0;
.slider-warp {
display: flex;
align-items: center;
margin-right: 25px;
height: 40px;
flex-direction: row-reverse;
.slider {
width: 80px;
}
.slider-icon {
display: inline-block;
width: 18px;
height: 18px;
background-position: top center;
background-size: 100%;
cursor: pointer;
}
.more-icon {
background-image: url('./more.png');
}
.less-icon {
background-image: url('./less.png');
}
}
}
.ant-slider-handle,
.ant-slider-handle.ant-tooltip-open {
border: 1px solid #2468f2;
}
.ant-slider-track {
background-color: #2468f2;
border-radius: 5px;
z-index: 111;
}
.ant-slider-handle {
z-index: 112;
}
.ant-slider-step {
background-color: #91d5ff;
z-index: 1;
border-radius: 5px;
}
}
.text-warp {
height: calc(100% - 60px);
margin: 12px auto;
text-align: center;
.txt-content {
padding: 10px;
}
textarea {
width: 90%;
height: 100%;
border: none;
resize: none;
outline: none !important;
overflow: auto;
&:focus {
border: none;
outline: none !important;
}
}
}
.docx-warp {
height: calc(~'100% - 45px');
background: #fff;
margin: 12px auto 13px;
overflow: auto;
width: 93%;
#docx {
&>p:nth-child(2)>strong {
font-size: 18px;
}
&>p:nth-child(3) {
margin-top: 100px;
text-align: center;
}
&>p:nth-child(4) {
margin-bottom: 100px;
text-align: center;
}
&>p:nth-child(5) {
margin-bottom: 100px;
text-align: center;
}
&>p>a {
color: #333;
text-align: center;
}
&>ul {
li {
margin-left: 0;
}
}
&>h3 {
margin: 20px 60px 20px;
}
}
}
.normal-mode {
width: 93%;
.docx-footer {
width: inherit;
right: 424px;
}
}
.reading-mode {
width: 98%;
.docx-footer {
left: 0;
right: 0;
width: 100%;
}
}
基本上实现了查看word文档内容要求的展示内容,图片+文字说明的形式,代码可鞥有些冗余,还有需要优化的地方.