普通视图

发现新文章,点击刷新页面。
昨天以前首页

ofd文件

作者 林太白
2026年1月7日 15:24

ofd文件处理预览

文章内容所涉及的源码地址,求朋友们给个star啊

::: tip

个人网站 ([https://nexuslin.github.io/](https://nexuslin.github.io/))

文章内容所涉及的源码地址,求朋友们给个star啊


同路之人,幸得君顾,盼得君之一赞!

与君同行,愿得青眼相加!

你的star

如春风化雨,润物无声;

如山间清泉,滋润心田;

如长河落日,映照初心;

亦如暗夜明灯,照亮前路;

是吾辈前行之明灯,亦是我坚持的动力!

愿君前程似锦,代码如诗,人生如画!

【GIthub地址】([https://github.com/lintaibai/TG](https://gitee.com/lintaibai/TG))

【Gitee地址】([https://gitee.com/lintaibai/TG](https://gitee.com/lintaibai/TG))


:::

OFD文件是什么

本质上就是一种国产的压缩文件

2016年成为国家标准(GB/T 33190-2016// 国家标准网站
https://openstd.samr.gov.cn/bzgk/gb/newGbInfo?hcno=3AF6682D939116B6F5EED53D01A9DB5D

OFD(Open Fixed-layout Document)开放式版式文件

中国自主研发的一种电子文档格式‌

中国国家标准版的电子文件格式,类似于 PDF

具有以下特点:

  • 基于XML和ZIP技术
  • 支持数字签名
  • 支持版式固定
  • 支持国产密码算法
  • 支持长期保存格式

OFD 文件结构

OFD 文件本质上是一个 ZIP 压缩包,包含以下结构

document.ofd
├── OFD.xml          // 文档根文件
├── Doc_0/
│   ├── Doc_0/       // 文档目录
│   │   ├── Document.xml
│   │   └── Pages/
│   │       └── Page_0/
│   │           ├── Page.xml
│   │           └── Res/        // 资源目录
│   │               ├── image/
│   │               └── font/
└── Public.xml       // 公共资源

相关库的认识

想要解决ofd文件的预览,我们必须认识相关的两个依赖库

jszip 
JSZipUtils 

这两个库存的作用和认识

jszip的认识使用

认识

JSZip 是一个用于创建、读取和编辑 .zip 文件的JavaScript库。它可以在浏览器和Node.js环境中使用。

主要功能:
  • 创建新的 ZIP 文件
  • 读取现有的 ZIP 文件
  • 向 ZIP 文件中添加或删除文件
  • 生成 ZIP 文件并下载
使用示例
// 创建一个新的 ZIP 文件
var zip = new JSZip();

// 添加一个文本文件
zip.file("hello.txt", "Hello World\n");

// 添加一个文件夹和其中的文件
zip.folder("images").file("smile.gif", "base64数据", {base64: true});

// 生成 ZIP 文件
zip.generateAsync({type:"blob"})
.then(function(content) {
    // 在浏览器中下载
    saveAs(content, "example.zip");
});
常见应用场景:
  • 在前端打包多个文件供用户下载
  • 动态生成包含多个文件的压缩包
  • 处理上传的 ZIP 文件内容

JSZipUtils 的认识使用

认识

JSZipUtils 是 JSZip 的一个辅助工具库,主要用于处理二进制数据,特别是在获取远程文件时非常有用。

主要功能:
  • 获取二进制数据
  • 处理跨域请求
  • 提供便捷的文件获取方法
基本使用示例:
// 获取远程二进制数据
JSZipUtils.getBinaryContent("path/to/file.zip", function(err, data) {
    if(err) {
        throw err;
    }
    
    // 使用获取到的数据创建 JSZip 对象
    JSZip.loadAsync(data)
    .then(function(zip) {
        // 处理 zip 文件内容
        return zip.file("hello.txt").async("string");
    })
    .then(function(text) {
        console.log(text);
    });
});
常见应用场景:
  • 下载并处理远程 ZIP 文件
  • 获取二进制文件内容
  • 处理跨域的二进制数据请求

为什么 JSZipUtils 被废弃

JSZipUtils 被废弃的主要原因有:

  1. 现代浏览器原生支持更好:fetch API 已经成为现代浏览器的标准,提供了更强大和灵活的功能
  2. 维护问题:JSZipUtils 已经很久没有更新,可能存在安全漏洞
  3. 功能冗余:fetch API 可以完全覆盖 JSZipUtils 的功能,而且提供更多特性
使用 fetch API 替代的示例
原来的 JSZipUtils 写法:
JSZipUtils.getBinaryContent("path/to/file.zip", function(err, data) {
    if(err) {
        throw err;
    }
    JSZip.loadAsync(data)
    .then(function(zip) {
        // 处理 zip 文件
    });
});
使用 fetch API 的现代写法:
fetch("path/to/file.zip")
    .then(response => {
        if (!response.ok) {
            throw new Error("Network response was not ok");
        }
        return response.arrayBuffer();
    })
    .then(data => {
        return JSZip.loadAsync(data);
    })
    .then(zip => {
        // 处理 zip 文件
    })
    .catch(error => {
        console.error("Error:", error);
    });
fetch API 的优势
  1. 更好的错误处理:
fetch(url)
    .then(response => {
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        return response.arrayBuffer();
    })
  1. 支持更多数据类型:
// 获取不同类型的数据
fetch(url)
    .then(response => response.arrayBuffer())  // 二进制数据
    .then(response => response.blob())        // Blob 对象
    .then(response => response.text())        // 文本数据
    .then(response => response.json())        // JSON 数据
  1. 支持请求配置:
fetch(url, {
    method: 'GET',
    headers: {
        'Authorization': 'Bearer token',
        'Content-Type': 'application/octet-stream'
    },
    mode: 'cors',
    credentials: 'include'
})
  1. 支持异步/等待语法:
async function loadZip(url) {
    try {
        const response = await fetch(url);
        if (!response.ok) {
            throw new Error('Network response was not ok');
        }
        const data = await response.arrayBuffer();
        const zip = await JSZip.loadAsync(data);
        return zip;
    } catch (error) {
        console.error('Error:', error);
        throw error;
    }
}
完整的迁移示例

假设我们要下载一个 ZIP 文件,解压后获取其中的文件内容:

// 使用 async/await 的现代写法
async function processZipFile(url) {
    try {
        // 1. 获取文件
        const response = await fetch(url);
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        // 2. 转换为二进制数据
        const arrayBuffer = await response.arrayBuffer();
        
        // 3. 加载到 JSZip
        const zip = await JSZip.loadAsync(arrayBuffer);
        
        // 4. 处理文件内容
        const files = Object.keys(zip.files);
        for (const filename of files) {
            if (!zip.files[filename].dir) {
                const content = await zip.file(filename).async('string');
                console.log(`${filename}: ${content}`);
            }
        }
        
        return zip;
    } catch (error) {
        console.error('Error processing zip file:', error);
        throw error;
    }
}

// 使用示例
processZipFile('path/to/file.zip')
    .then(zip => {
        console.log('Zip file processed successfully');
    })
    .catch(error => {
        console.error('Failed to process zip file:', error);
    });
兼容性考虑

如果需要支持较老的浏览器,可以添加 polyfill:

// 添加 fetch polyfill(如果需要)
import 'whatwg-fetch';

// 或者使用条件加载
if (!window.fetch) {
    // 加载 fetch polyfill
}
总结

迁移到 fetch API 的好处:

  1. 更现代的 API 设计
  2. 更好的错误处理机制
  3. 更灵活的数据处理能力
  4. 更好的性能和浏览器支持
  5. 更少的依赖,减少包大小
  6. 更好的 TypeScript 支持

建议在新的项目中直接使用 fetch API,在现有项目中逐步将 JSZipUtils 的调用替换为 fetch API 的实现。

JSZipUtils和jszip的配合使用

通常在实际应用中,这两个库会配合使用:

// 下载远程文件并创建新的 ZIP 包
JSZipUtils.getBinaryContent("path/to/image.jpg", function(err, data) {
    if(err) throw err;
    
    var zip = new JSZip();
    zip.file("image.jpg", data, {binary: true});
    
    zip.generateAsync({type:"blob"})
    .then(function(content) {
        saveAs(content, "new.zip");
    });
});
注意事项
  1. 在浏览器中使用时,如果处理大文件,要注意内存使用情况
  2. 处理远程文件时要注意跨域问题
  3. JSZipUtils 已经被标记为废弃,推荐使用原生的 fetch API 替代
  4. 现代项目中,也可以考虑使用更新的替代方案,如 JSZip 的最新版本配合 fetch API

这两个库在前端处理文件压缩和解压缩任务时非常实用,特别是在需要动态处理文件内容的场景中。

vue3之中预览ofd.js文件

接下来我们就简单实现一下ofd.js文件的预览,我们的想法是在vue3之中替代掉老旧的JSZipUtils库,当然,和之前一样我们还是以实现功能然后进行优化为主

因为ofd文件的本质上是一种压缩包,所以我们需要先解压 OFD 文件,然后解析其中的内容。

先来看看标准的ofd文件是什么样子的

{
    "name": "OFD.xml",
    "dir": false,
    "date": "2020-08-22T16:21:20.000Z",
    "comment": null,
    "unixPermissions": null,
    "dosPermissions": 32,
    "_data": {
        "compressedSize": 446,
        "uncompressedSize": 1269,
        "crc32": -2125441896,
        "compression": {
            "magic": "\b\u0000"
        },
        "compressedContent": {
            "0": 157,
            "1": 84,
            "2": 209,
            "3": 110,
             xxx
        }
    },
    "_dataBinary": true,
    "options": {
        "compression": null,
        "compressionOptions": null
    },
    "unsafeOriginalName": "OFD.xml"
}

方式1-使用easyofd的方式预览

推荐的预览方式

安装相关依赖
pnpm i jszip x2js jb2 opentype.js easyofd
预览文件
<script setup>
import EasyOFD from "easyofd";
import { onMounted } from 'vue'

onMounted(() => {
  let yourElement=document.getElementById("1111111");
  let ofd=new EasyOFD('myofdID', yourElement);
})

</script>

<template>
      <div id="1111111"> </div>

</template>

<style >
 .OfdButton{
      padding: 10px 20px;
      background-color: #007bff;
      color: #fff;
      border: none;
      border-radius: 5px;
      cursor: pointer;
      margin-right: 10px;
    }
</style>

方式2-使用ofd的方式预览

相关源码地址
https://github.com/DLTech21/ofd.js
安装依赖

这里我们先安装必须的依赖,后面剔除

pnpm install jszip
pnpm install jszip-utils(后面剔除)
pnpm i @lapo/asn1js
pnpm i js-sha1
pnpm i ofd-xml-parser js-md5 jsrsasign jsrsasign-util sm-crypto

安装成功以后我们首要的就是将ofd之前的版本兼容到vue3

这里需要我们main.TS之中设置一下全局

// 设置全局变量
if (typeof window !== 'undefined') {
  window.global = window;
}
文件之中使用
<template>
  <el-container style="width:100vw; height: 100vh;">
    <el-header style="background:#F5F5F5;display: flex; height: 40px; border: 1px solid #e8e8e8; align-items: center;">
      <div class="upload-icon" @click="uploadFile">
        <div class="upload-icon">打开OFD</div>
        <font-awesome-icon icon="cloud-upload-alt"/>
        <input type="file" ref="fileRef" class="hidden" accept=".ofd" @change="fileChanged">
      </div>

      <div class="upload-icon" @click="uploadPdfFile">
        <div class="upload-icon">PDF2OFD</div>
        <font-awesome-icon icon="cloud-upload-alt"/>
        <input type="file" ref="pdfFileRef" class="hidden" accept=".pdf" @change="pdfFileChanged">
      </div>

      <div style="display: flex;align-items: center" v-if="ofdObj">
        <div class="upload-icon" style="margin-left: 10px" @click="downPdf" v-if="ofdBase64">
          下载PDF
          <font-awesome-icon icon="download"/>
        </div>

        <div class="scale-icon" style="margin-left: 10px" @click="plus">
          <font-awesome-icon icon="search-plus"/>
        </div>

        <div class="scale-icon" @click="minus">
          <font-awesome-icon icon="search-minus" />
        </div>
        <div class="scale-icon">
          <font-awesome-icon icon="step-backward" @click="firstPage"/>
        </div>

        <div class="scale-icon" style="font-size: 18px" @click="prePage">
          <font-awesome-icon icon="caret-left"/>
        </div>

        <div class="scale-icon">
          {{pageIndex}}/{{pageCount}}
        </div>

        <div class="scale-icon" style="font-size: 18px" @click="nextPage">
          <font-awesome-icon icon="caret-right"/>
        </div>
        <div class="scale-icon" @click="lastPage">
          <font-awesome-icon icon="step-forward"/>
        </div>
      </div>
    </el-header>

    <el-main style="height: auto;background: #808080;;padding: 0" v-loading="loading">
      <div id="leftMenu" class="left-section">
        <div class="text-icon" @click="demo(1)">
          <p>电子发票</p>
        </div>
        <div class="text-icon" @click="demo(2)">
          <p>电子公文</p>
        </div>
        <div class="text-icon" @click="demo(3)">
          <p>骑缝章</p>
        </div>
        <div class="text-icon" @click="demo(4)">
          <p>多页文档</p>
        </div>
      </div>
      <div class="main-section" id="content" ref="contentDivRef" @mousewheel="scrool"></div>
    </el-main>

    <div class="SealContainer" id="sealInfoDiv" hidden="hidden" ref="sealInfoDivRef">
      <div class="SealContainer mask" @click="closeSealInfoDialog"></div>
      <div class="SealContainer-layout">
        <div class="SealContainer-content">
          <p class="content-title">签章信息</p>
          <div class="subcontent">
            <span class="title">签章人</span>
            <span class="value" id="spSigner">[无效的签章结构]</span>
          </div>
          <div class="subcontent">
            <span class="title">签章提供者</span>
            <span class="value" id="spProvider">[无效的签章结构]</span>
          </div>
          <div class="subcontent">
            <span class="title">原文摘要值</span>
            <span class="value" id="spHashedValue" @click="showMore('原文摘要值', 'spHashedValue')" style="cursor: pointer">[无效的签章结构]</span>
          </div>
          <div class="subcontent">
            <span class="title">签名值</span>
            <span class="value" id="spSignedValue" @click="showMore('签名值', 'spSignedValue')" style="cursor: pointer">[无效的签章结构]</span>
          </div>
          <div class="subcontent">
            <span class="title">签名算法</span>
            <span class="value" id="spSignMethod">[无效的签章结构]</span>
          </div>
          <div class="subcontent">
            <span class="title">版本号</span>
            <span class="value" id="spVersion">[无效的签章结构]</span>
          </div>
          <div class="subcontent">
            <span class="title">验签结果</span>
            <span class="value" id="VerifyRet">[无效的签章结构]</span>
          </div>

          <p class="content-title">印章信息</p>
          <div class="subcontent">
            <span class="title">印章标识</span>
            <span class="value" id="spSealID">[无效的签章结构]</span>
          </div>
          <div class="subcontent">
            <span class="title">印章名称</span>
            <span class="value" id="spSealName">[无效的签章结构]</span>
          </div>
          <div class="subcontent">
            <span class="title">印章类型</span>
            <span class="value" id="spSealType">[无效的签章结构]</span>
          </div>
          <div class="subcontent">
            <span class="title">有效时间</span>
            <span class="value" id="spSealAuthTime">[无效的签章结构]</span>
          </div>
          <div class="subcontent">
            <span class="title">制章日期</span>
            <span class="value" id="spSealMakeTime">[无效的签章结构]</span>
          </div>
          <div class="subcontent">
            <span class="title">印章版本</span>
            <span class="value" id="spSealVersion">[无效的签章结构]</span>
          </div>
        </div>
        <input style="position:absolute;right:1%;top:1%;" type="button" name="" id="" value="X" @click="closeSealInfoDialog()"/>
      </div>
    </div>

    <el-dialog :title="dialogTitle" :visible.sync="dialogVisible">
      <span style="text-align: left">{{dialogValue}}</span>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
      </div>
    </el-dialog>
  </el-container>
</template>

<script setup>
import { ref, onMounted, reactive } from 'vue'
import { parseOfdDocument, renderOfd, renderOfdByScale, digestCheck, getPageScale, setPageScale } from "@/utils/ofd/ofd.js"
import JSZipUtils from "jszip-utils"
import { ElMessage, ElMessageBox } from 'element-plus'
// 响应式数据
const fileRef = ref(null)
const pdfFileRef = ref(null)
const contentDivRef = ref(null)
const sealInfoDivRef = ref(null)

const state = reactive({
  pdfFile: null,
  ofdBase64: null,
  loading: false,
  pageIndex: 1,
  pageCount: 0,
  scale: 0,
  dialogTitle: null,
  dialogValue: null,
  dialogVisible: false,
  ofdObj: null,
  screenWidth: document.body.clientWidth
})

// 方法
const uploadFile = () => {
  state.pdfFile = null
  fileRef.value.click()
}

const fileChanged = (e) => {
  const file = e.target.files[0]
  const ext = file.name.replace(/.+\./, "")
  
  if (["ofd"].indexOf(ext) === -1) {
    ElMessageBox.alert('仅支持ofd类型', 'error', {
      confirmButtonText: '确定',
      callback: action => {
        ElMessage({
          type: 'info',
          message: `action: ${action}`
        })
      }
    })
    return
  }
  
  if (file.size > 100 * 1024 * 1024) {
    ElMessageBox.alert('文件大小需 < 100M', 'error', {
      confirmButtonText: '确定',
      callback: action => {
        ElMessage({
          type: 'info',
          message: `action: ${action}`
        })
      }
    })
    return
  }
  
  const reader = new FileReader()
  reader.readAsDataURL(file)
  reader.onload = (e) => {
    state.ofdBase64 = e.target.result.split(',')[1]
  }
  
  getOfdDocumentObj(file, state.screenWidth)
  fileRef.value.value = null
}

const uploadPdfFile = () => {
  state.pdfFile = null
  pdfFileRef.value.click()
}

const pdfFileChanged = (e) => {
  const file = e.target.files[0]
  const ext = file.name.replace(/.+\./, "")
  
  if (["pdf"].indexOf(ext) === -1) {
    ElMessageBox.alert('仅支持pdf类型', 'error', {
      confirmButtonText: '确定',
      callback: action => {
        ElMessage({
          type: 'info',
          message: `action: ${action}`
        })
      }
    })
    return
  }
  
  if (file.size > 100 * 1024 * 1024) {
    ElMessageBox.alert('文件大小需 < 100M', 'error', {
      confirmButtonText: '确定',
      callback: action => {
        ElMessage({
          type: 'info',
          message: `action: ${action}`
        })
      }
    })
    return
  }
  
  const reader = new FileReader()
  reader.readAsDataURL(file)
  reader.onload = (e) => {
    const pdfBase64 = e.target.result.split(',')[1]
    downOfd(pdfBase64)
  }
  
  pdfFileRef.value.value = null
}

const downOfd = (pdfBase64) => {
  state.loading = true
  axios({
    method: "post",
    url: "https://51shouzu.xyz/api/ofd/convertOfd",
    data: {
      pdfBase64,
    }
  }).then(response => {
    state.loading = false
    const binary = atob(response.data.data.replace(/\s/g, ''))
    const len = binary.length
    const buffer = new ArrayBuffer(len)
    const view = new Uint8Array(buffer)
    for (let i = 0; i < len; i++) {
      view[i] = binary.charCodeAt(i)
    }
    const blob = new Blob([view], null)
    const url = URL.createObjectURL(blob)
    const link = document.createElement('a')
    link.style.display = 'none'
    link.href = url
    link.setAttribute('download', 'ofd.ofd')
    document.body.appendChild(link)
    link.click()
  }).catch(error => {
    console.log(error, "error")
    ElMessageBox.alert('PDF打开失败', error, {
      confirmButtonText: '确定',
      callback: action => {
        ElMessage({
          type: 'info',
          message: `action: ${action}`
        })
      }
    })
  })
}

const downPdf = () => {
  state.loading = true
  axios({
    method: "post",
    url: "https://51shouzu.xyz/api/ofd/convertPdf",
    data: {
      ofdBase64: state.ofdBase64
    }
  }).then(response => {
    state.loading = false
    const binary = atob(response.data.data.replace(/\s/g, ''))
    const len = binary.length
    const buffer = new ArrayBuffer(len)
    const view = new Uint8Array(buffer)
    for (let i = 0; i < len; i++) {
      view[i] = binary.charCodeAt(i)
    }
    const blob = new Blob([view], { type: "application/pdf" })
    const url = URL.createObjectURL(blob)
    const link = document.createElement('a')
    link.style.display = 'none'
    link.href = url
    link.setAttribute('download', 'ofd.pdf')
    document.body.appendChild(link)
    link.click()
  }).catch(error => {
    console.log(error, "error")
    ElMessageBox.alert('OFD打开失败', error, {
      confirmButtonText: '确定',
      callback: action => {
        ElMessage({
          type: 'info',
          message: `action: ${action}`
        })
      }
    })
  })
}

const plus = () => {
  setPageScale(++state.scale)
  const divs = renderOfdByScale(state.ofdObj)
  displayOfdDiv(divs)
}

const minus = () => {
  setPageScale(--state.scale)
  const divs = renderOfdByScale(state.ofdObj)
  displayOfdDiv(divs)
}

const prePage = () => {
  const contentDiv = document.getElementById('content')
  const ele = contentDiv.children.item(state.pageIndex - 2)
  ele?.scrollIntoView(true)
  if (ele) state.pageIndex = state.pageIndex - 1
}

const firstPage = () => {
  const contentDiv = document.getElementById('content')
  const ele = contentDiv.firstElementChild
  ele?.scrollIntoView(true)
  if (ele) state.pageIndex = 1
}

const nextPage = () => {
  const contentDiv = document.getElementById('content')
  const ele = contentDiv.children.item(state.pageIndex)
  ele?.scrollIntoView(true)
  if (ele) ++state.pageIndex
}

const lastPage = () => {
  const contentDiv = document.getElementById('content')
  const ele = contentDiv.lastElementChild
  ele?.scrollIntoView(true)
  if (ele) state.pageIndex = contentDiv.childElementCount
}

const demo = (value) => {
  let ofdFile = null
  switch (value) {
    case 1:
      ofdFile = '999.ofd'
      break
    case 2:
      ofdFile = 'n.ofd'
      break
    case 3:
      ofdFile = 'h.ofd'
      break
    case 4:
      ofdFile = '2.ofd'
      break
  }
  JSZipUtils.getBinaryContent(ofdFile, (err, data) => {
    if (err) {
      console.log("JSZipUtils===1");
      console.log(err)
    } else {
      const base64String = btoa(String.fromCharCode.apply(null, new Uint8Array(data)))
      console.log("JSZipUtils===2");
      state.ofdBase64 = base64String
    }
  })
  getOfdDocumentObj(ofdFile, state.screenWidth)
}

const getOfdDocumentObj = (file, screenWidth) => {
  const t = new Date().getTime()
  state.loading = true
  
  parseOfdDocument({
    ofd: file,
    success(res) {
      console.log(res)
      const t1 = new Date().getTime()
      console.log('解析ofd', t1 - t)
      state.ofdObj = res[0]
      state.pageCount = res[0].pages.length
      const divs = renderOfd(screenWidth, res[0])
      const t2 = new Date().getTime()
      console.log('xml转svg', t2 - t1)
      displayOfdDiv(divs)
      const t3 = new Date().getTime()
      console.log('svg渲染到页面', t3 - t2)
      state.loading = false
    },
    fail(error) {
      console.log(error)
      state.loading = false
      ElMessageBox.alert('OFD打开失败', error, {
        confirmButtonText: '确定',
        callback: action => {
          ElMessage({
            type: 'info',
            message: `action: ${action}`
          })
        }
      })
    }
  })
}

const displayOfdDiv = (divs) => {
  state.scale = getPageScale()
  const contentDiv = document.getElementById('content')
  contentDiv.innerHTML = ''
  
  for (const div of divs) {
    contentDiv.appendChild(div)
  }
  
  for (const ele of document.getElementsByName('seal_img_div')) {
    addEventOnSealDiv(ele, JSON.parse(ele.dataset.sesSignature), JSON.parse(ele.dataset.signedInfo))
  }
}

const addEventOnSealDiv = (div, SES_Signature, signedInfo) => {
  try {
    global.HashRet = null
    global.VerifyRet = signedInfo.VerifyRet
    
    div.addEventListener("click", () => {
      document.getElementById('sealInfoDiv').hidden = false
      document.getElementById('sealInfoDiv').setAttribute('style', 'display:flex;align-items: center;justify-content: center;')
      
      if (SES_Signature.realVersion < 4) {
        document.getElementById('spSigner').innerText = SES_Signature.toSign.cert['commonName']
        document.getElementById('spProvider').innerText = signedInfo.Provider['@_ProviderName']
        document.getElementById('spHashedValue').innerText = SES_Signature.toSign.dataHash.replace(/\n/g, '')
        document.getElementById('spSignedValue').innerText = SES_Signature.signature.replace(/\n/g, '')
        document.getElementById('spSignMethod').innerText = SES_Signature.toSign.signatureAlgorithm.replace(/\n/g, '')
        document.getElementById('spSealID').innerText = SES_Signature.toSign.eseal.esealInfo.esID
        document.getElementById('spSealName').innerText = SES_Signature.toSign.eseal.esealInfo.property.name
        document.getElementById('spSealType').innerText = SES_Signature.toSign.eseal.esealInfo.property.type
        document.getElementById('spSealAuthTime').innerText = "从 " + SES_Signature.toSign.eseal.esealInfo.property.validStart + " 到 " + SES_Signature.toSign.eseal.esealInfo.property.validEnd
        document.getElementById('spSealMakeTime').innerText = SES_Signature.toSign.eseal.esealInfo.property.createDate
        document.getElementById('spSealVersion').innerText = SES_Signature.toSign.eseal.esealInfo.header.version
      } else {
        document.getElementById('spSigner').innerText = SES_Signature.cert['commonName']
        document.getElementById('spProvider').innerText = signedInfo.Provider['@_ProviderName']
        document.getElementById('spHashedValue').innerText = SES_Signature.toSign.dataHash.replace(/\n/g, '')
        document.getElementById('spSignedValue').innerText = SES_Signature.signature.replace(/\n/g, '')
        document.getElementById('spSignMethod').innerText = SES_Signature.signatureAlgID.replace(/\n/g, '')
        document.getElementById('spSealID').innerText = SES_Signature.toSign.eseal.esealInfo.esID
        document.getElementById('spSealName').innerText = SES_Signature.toSign.eseal.esealInfo.property.name
        document.getElementById('spSealType').innerText = SES_Signature.toSign.eseal.esealInfo.property.type
        document.getElementById('spSealAuthTime').innerText = "从 " + SES_Signature.toSign.eseal.esealInfo.property.validStart + " 到 " + SES_Signature.toSign.eseal.esealInfo.property.validEnd
        document.getElementById('spSealMakeTime').innerText = SES_Signature.toSign.eseal.esealInfo.property.createDate
        document.getElementById('spSealVersion').innerText = SES_Signature.toSign.eseal.esealInfo.header.version
      }
      
      document.getElementById('spVersion').innerText = SES_Signature.toSign.version
      document.getElementById('VerifyRet').innerText = "文件摘要值后台验证中,请稍等... " + (global.VerifyRet ? "签名值验证成功" : "签名值验证失败")
      
      if (global.HashRet == null || global.HashRet == undefined || Object.keys(global.HashRet).length <= 0) {
        setTimeout(() => {
          const signRetStr = global.VerifyRet ? "签名值验证成功" : "签名值验证失败"
          global.HashRet = digestCheck(global.toBeChecked.get(signedInfo.signatureID))
          const hashRetStr = global.HashRet ? "文件摘要值验证成功" : "文件摘要值验证失败"
          document.getElementById('VerifyRet').innerText = hashRetStr + " " + signRetStr
        }, 1000)
      }
    })
  } catch (e) {
    console.log(e)
  }
  
  if (!global.VerifyRet) {
    div.setAttribute('class', 'gray')
  }
}

const closeSealInfoDialog = () => {
  sealInfoDivRef.value.setAttribute('style', 'display: none')
  document.getElementById('spSigner').innerText = "[无效的签章结构]"
  document.getElementById('spProvider').innerText = "[无效的签章结构]"
  document.getElementById('spHashedValue').innerText = "[无效的签章结构]"
  document.getElementById('spSignedValue').innerText = "[无效的签章结构]"
  document.getElementById('spSignMethod').innerText = "[无效的签章结构]"
  document.getElementById('spSealID').innerText = "[无效的签章结构]"
  document.getElementById('spSealName').innerText = "[无效的签章结构]"
  document.getElementById('spSealType').innerText = "[无效的签章结构]"
  document.getElementById('spSealAuthTime').innerText = "[无效的签章结构]"
  document.getElementById('spSealMakeTime').innerText = "[无效的签章结构]"
  document.getElementById('spSealVersion').innerText = "[无效的签章结构]"
  document.getElementById('spVersion').innerText = "[无效的签章结构]"
  document.getElementById('VerifyRet').innerText = "[无效的签章结构]"
}

const showMore = (title, id) => {
  state.dialogVisible = true
  state.dialogValue = document.getElementById(id).innerText
  state.dialogTitle = title
}

const scrool = () => {
  const scrolled = contentDivRef.value.firstElementChild?.getBoundingClientRect()?.top - 60
  let top = 0
  let index = 0
  
  for (let i = 0; i < contentDivRef.value.childElementCount; i++) {
    top += (Math.abs(contentDivRef.value.children.item(i)?.style.height.replace('px', '')) + Math.abs(contentDivRef.value.children.item(i)?.style.marginBottom.replace('px', '')))
    if (Math.abs(scrolled) < top) {
      index = i
      break
    }
  }
  
  state.pageIndex = index + 1
}

// 生命周期钩子
onMounted(() => {
  state.screenWidth = document.body.clientWidth - document.getElementById('leftMenu').getBoundingClientRect().width
  
  contentDivRef.value.addEventListener('scroll', scrool)
  
  window.onresize = () => {
    state.screenWidth = (document.body.clientWidth - 88)
    const divs = renderOfd(state.screenWidth, state.ofdObj)
    displayOfdDiv(divs)
  }
})
</script>

<style scoped>
/* 保持原有的样式不变 */
.upload-icon {
  display: flex;
  cursor: pointer;
  justify-content: center;
  align-items: center;
  height: 28px;
  padding-left: 10px;
  padding-right: 10px;
  background-color: rgb(59, 95, 232);
  border-radius: 1px;
  border-color: #5867dd;
  font-weight: 500;
  font-size: 12px;
  color: white;
  margin: 1px;
}

.scale-icon {
  display: flex;
  cursor: pointer;
  justify-content: center;
  align-items: center;
  width: 33px;
  height: 28px;
  background-color: #F5F5F5;;
  border-radius: 1px;
  font-weight: 500;
  font-size: 12px;
  color: #333333;
  text-align: center;
  padding: 2px;
}

.scale-icon :active {
  color: rgb(59, 95, 232);
}

.scale-icon :hover {
  color: rgb(59, 95, 232);
}

.text-icon {
  display: flex;
  cursor: pointer;
  justify-content: center;
  align-items: center;
  height: 28px;
  width: 90%;
  background-color: rgb(59, 95, 232);
  border-radius: 1px;
  border-color: #5867dd;
  font-weight: 500;
  font-size: 10px;
  color: white;
  margin-top: 20px;
}

.hidden {
  display: none !important;
}

.SealContainer {
  z-index: 99999;
  position: fixed;
  left: 0;
  top: 0;
  width: 100vw;
  height: 100vh;
}

.SealContainer .mask {
  background: #000000;
  opacity: 0.3;
}

.content-title {
  font-size: 16px;
  text-align: center;
  border-bottom: 1px solid rgb(59, 95, 232);
  color: rgb(59, 95, 232);
  margin-top: 10px;
}

.SealContainer-content {
  position: relative;
  width: 100%;
  height: 100%;
  overflow-y: auto;
  background: white;
  display: flex;
  flex-direction: column;
  padding: 10px;
  align-items: center;
}

.SealContainer-layout {
  position: relative;
  width: 60%;
  height: 80vh;
  overflow-y: auto;
  background: white;
  z-index: 100;
  display: flex;
  flex-direction: column;
  padding: 10px;
  align-items: center;
}

.subcontent {
  width: 80%;
  display: flex;
  flex-direction: column;
  text-align: left;
  margin-bottom: 10px;
  font-family: simsun;
}

.subcontent .title {
  font-weight: 600;
}

.subcontent .value {
  font-weight: 400;
  -webkit-line-clamp: 1;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.left-section {
  position: fixed;
  width: 88px;
  height: 100%;
  background:#F5F5F5;
  border: 1px solid #e8e8e8;
  align-items: center;
  display: flex;
  flex-direction: column
}

.main-section {
  padding-top: 20px;
  margin-left:88px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: #808080;
  overflow: hidden
}

@media (max-width: 767px) {
  .SealContainer-layout {
    position: relative;
    width: 90%;
    height: 90vh;
    overflow-y: auto;
    background: white;
    z-index: 100;
    display: flex;
    flex-direction: column;
    padding: 10px;
    align-items: center;
  }

  .subcontent {
    width: 95%;
    display: flex;
    flex-direction: column;
    text-align: left;
    margin-bottom: 10px;
    font-family: simsun;
  }

  .left-section {
    position: fixed;
    width: 0px;
    height: 100%;
    background:#F5F5F5;
    border: 1px solid #e8e8e8;
    align-items: center;
    display: none;
    flex-direction: column;
  }

  .main-section {
    padding-top: 20px;
    margin-left:0px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background: #808080;
    overflow: hidden
  }
}
</style>

方式3-ofd.js文件预览以及使用(废弃)

查看了一下npm上已经需要付费了,所以我果断放弃了

// npm地址
https://www.npmjs.com/package/ofd.js

// 安装
pnpm install ofd.js
❌
❌