ofd文件

ofd文件处理预览

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

markdown 复制代码
::: 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文件是什么

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

javascript 复制代码
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 压缩包,包含以下结构

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

相关库的认识

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

javascript 复制代码
jszip 
JSZipUtils 

这两个库存的作用和认识

jszip的认识使用

认识

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

主要功能:
  • 创建新的 ZIP 文件
  • 读取现有的 ZIP 文件
  • 向 ZIP 文件中添加或删除文件
  • 生成 ZIP 文件并下载
使用示例
plain 复制代码
// 创建一个新的 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 的一个辅助工具库,主要用于处理二进制数据,特别是在获取远程文件时非常有用。

主要功能:
  • 获取二进制数据
  • 处理跨域请求
  • 提供便捷的文件获取方法
基本使用示例:
plain 复制代码
// 获取远程二进制数据
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 写法:
plain 复制代码
JSZipUtils.getBinaryContent("path/to/file.zip", function(err, data) {
    if(err) {
        throw err;
    }
    JSZip.loadAsync(data)
    .then(function(zip) {
        // 处理 zip 文件
    });
});
使用 fetch API 的现代写法:
plain 复制代码
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. 更好的错误处理:
plain 复制代码
fetch(url)
    .then(response => {
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        return response.arrayBuffer();
    })
  1. 支持更多数据类型:
plain 复制代码
// 获取不同类型的数据
fetch(url)
    .then(response => response.arrayBuffer())  // 二进制数据
    .then(response => response.blob())        // Blob 对象
    .then(response => response.text())        // 文本数据
    .then(response => response.json())        // JSON 数据
  1. 支持请求配置:
plain 复制代码
fetch(url, {
    method: 'GET',
    headers: {
        'Authorization': 'Bearer token',
        'Content-Type': 'application/octet-stream'
    },
    mode: 'cors',
    credentials: 'include'
})
  1. 支持异步/等待语法:
plain 复制代码
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 文件,解压后获取其中的文件内容:

plain 复制代码
// 使用 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:

plain 复制代码
// 添加 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的配合使用

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

plain 复制代码
// 下载远程文件并创建新的 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文件是什么样子的

javascript 复制代码
{
    "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的方式预览

推荐的预览方式

安装相关依赖
javascript 复制代码
pnpm i jszip x2js jb2 opentype.js easyofd
预览文件
javascript 复制代码
<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的方式预览

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

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

javascript 复制代码
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之中设置一下全局

javascript 复制代码
// 设置全局变量
if (typeof window !== 'undefined') {
  window.global = window;
}
文件之中使用
javascript 复制代码
<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上已经需要付费了,所以我果断放弃了

javascript 复制代码
// npm地址
https://www.npmjs.com/package/ofd.js

// 安装
pnpm install ofd.js
相关推荐
冴羽1 小时前
CSS 新特性!瀑布流布局的终极解决方案
前端·javascript·css
满天星辰1 小时前
Vue 响应式原理深度解析
前端·vue.js
YDS8291 小时前
SpringCloud —— MQ的可靠性保障和延迟消息
后端·spring·spring cloud·rabbitmq
怪可爱的地球人2 小时前
em,rem,px,rpx单位换算,你弄懂了吗?
前端
无限大62 小时前
为什么"区块链"不只是比特币?——从加密货币到分布式应用
后端
码途潇潇2 小时前
JavaScript有哪些数据类型?如何判断一个变量的数据类型?
前端·javascript
洛神么么哒2 小时前
freeswitch-初级-01-日志分割
后端
满天星辰2 小时前
Vue真的是单向数据流?
前端·vue.js
细心细心再细心2 小时前
Nice-modal-react的使用
前端
蝎子莱莱爱打怪2 小时前
我的2025年年终总结
java·后端·面试