react中封装一个预览.doc和.docx文件的组件

主要用到了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文档内容要求的展示内容,图片+文字说明的形式,代码可鞥有些冗余,还有需要优化的地方.

相关推荐
肠胃炎8 分钟前
CSS 内容超出显示省略号
前端·css
哟哟耶耶1 小时前
react-10样式模块化(./index.module.css, <div className={welcome.title}>Welcome</div>)
前端·javascript·react.js
Lysun0011 小时前
tailwindcss如何改变antd子组件的样式
前端·javascript·tailwindcss·antdesign
kooboo china.1 小时前
Tailwind CSS 实战:基于 Kooboo 构建企业官网页面(三)
前端·javascript·css·编辑器·html
生产队队长1 小时前
CSS:选择器-基本选择器
前端·css
神秘代码行者1 小时前
HTML Picture标签详细教程
前端·html
爱编程的鱼1 小时前
如何用CSS实现HTML元素的旋转效果
前端·css
HBR666_2 小时前
vue3定义全局防抖指令
前端·javascript·vue.js
前端老实人灬3 小时前
vue使用docx 生成 导出word文件。(包含:页眉、页脚、生成目录、页码、图片、表格、合并表格)
前端·vue.js·word
MyhEhud3 小时前
kotlin 过滤 filter 函数的作用和使用场景
android·java·服务器·前端·kotlin