特性:
1、支持任意深度的chm文件解析
2、解析后内容结构转换为tree数据呈现
3、点击树节点可以在html实时查看数据
4、不依赖任何浏览器端插件,兼容性较好
nodejs端核心代码
const $g = global.SG.$g, fs = global.SG.fs, router = global.SG.router, xlsx = global.SG.xlsx; module.exports = global.SG.router; let webRootPath = 'http://127.0.0.1:9999/chm/';//测试环境chm文件根目录 //上传单个文件(all方法支持POST、GET、PUT、PATCH、DELETE传参方式) let uploadFileName = '';//获取上传后的文件名 router.all( "/chm/upload",//接口路径 $g.dir.upload( "./upload",//存储临时上传文件的路径 ({ fileName, } = {}) => { uploadFileName = fileName; }).single("file"),//上传单个文件 (req, res) => { // 开始解压上传的upload文件---------------------------------------- let cp = require('child_process'); cp.exec("reg query HKEY_CLASSES_ROOT\\360zip\\shell\\open\\command /ve", function (e, stdout, stderr) { let rootPath = `${__dirname.split('\\').slice(0, -3).join('\\')}`; let uploadFolderPath = `${rootPath}\\upload\\${uploadFileName}`; let targetFolderPath = `${rootPath}\\chm\\${uploadFileName}`; let str = stdout.match(/\"([^\"]+)\"/)[0]; if (str) { // console.log('已经找到360zip程序,详细地址为:'+str); cp.exec(`${str} -x ${uploadFolderPath} ${targetFolderPath}`, { encoding: 'binary' }, function (e, stdout, stderr) { // 遍历读取目录里面的文件---------------------------------------- let files = []; let walker = require('walk').walk(targetFolderPath, { followLinks: false }); walker.on('file', function (roots, stat, next) { if (stat.name.includes(`.hhc`)) { let hhcFilePath = `${roots}/${stat.name}`; files.push(hhcFilePath); fs.readFile(hhcFilePath, 'utf-8', (err, data) => $g.json.res(req, res, "chm文件解析成功", { htmPath: `${webRootPath}${uploadFileName}/`, hhcFilePath: `${webRootPath}${uploadFileName}/${stat.name}`, hhcData: data, }, true)); } else next(); }); walker.on('end', function () { files.length === 0 && $g.json.res(req, res, "没有找到hhc文件,请仔细检查chm文件是否正确!", { targetFolderPath }, false); }); }); } else { console.log('没有找到360zip程序,无法完成解压缩功能,请在服务器端安装360zip软件!'); } }); } );
vue前端核心代码
<template> <div :class="$options.name"> <div class="sg-left " v-loading="loading"> <!-- 树节点 --> <div class="tree-header"> <!-- 树节点 --> <div class="tree-header"> <div class="sg-left "> <el-tooltip popper-class="sg-el-tooltip" :enterable="false" effect="dark" :content="`支持拖拽到树上传文件`" placement="top-start"> <el-button type="text" icon="el-icon-upload" size="mini" @click="d => $refs.sgUpload.triggerUploadFile()"> 上传chm文件 </el-button> </el-tooltip> </div> <div class="sg-right "> </div> </div> </div> <div class="tree-body" @click="treeData.length === 0 ? $refs.sgUpload.triggerUploadFile() : ''"> <el-tree ref="tree" @current-change="current_change" :data="treeData" :props="{ label: 'Name', children: 'children' }" :icon-class="'folder-tree-node'" :indent="25" @node-click="nodeClick" node-key="id" :filter-node-method="filterNode" default-expand-all highlight-current :default-expanded-keys="default_expanded_keys"> <div slot="reference" class="node-label" slot-scope="{ node, data }"> <label class="left" :title="node.label"> {{ node.label }} </label> </div> </el-tree> <sgUpload drag ref="sgUpload" :data="{ accept: `.${['chm'].join(',.')}`, // actionUrl: `http://127.0.0.1:9999/api/chm/upload`, actionUrl: `http://xxx.xxxxxx.cn:33/api/chm/upload`, headers: {}, }" @beforeUpload="beforeUpload" @uploadSuccess="uploadSuccess" @error="uploadError" hideUploadTray /> </div> </div> <div class="sg-right "> <iframe id="iframe" ref="iframe" :src="src" frameborder="no" style="width:100%;height:100%;"></iframe> </div> <div class="hhcHTML" ref="hhcHTML" style="display: none;"> </div> </div> </template> <script> import sgUpload from "@/vue/components/admin/sgUpload"; export default { name: 'chmDecode', components: { sgUpload, }, data() { return { loading: false, htmPath: '', src: '', current_node: null, default_expanded_keys: [], treeData: [], } }, created() { }, methods: { // 解析hhc文件 decodeHhcData(doms) { let r = []; let _recursion = (doms, d) => { [].slice.call(doms).forEach(v => { let OBJECT = v.querySelector(`OBJECT`); let p0 = OBJECT.querySelectorAll(`param`)[0]; let p1 = OBJECT.querySelectorAll(`param`)[1]; let obj = { id: this.$g.UUID(), [p0.getAttribute('name')]: p0.getAttribute('value'),//文件别名 [p1.getAttribute('name')]: p1.getAttribute('value'), filePath: `${this.htmPath}${p1.getAttribute('value')}`,//文件路径 } this.current_node || (this.current_node = obj); d.push(obj) if (OBJECT.nextElementSibling) { obj.children = [] _recursion(OBJECT.nextElementSibling.children, obj.children) } }); } _recursion(doms, r); return r; }, // 开始上传 beforeUpload(d) { this.loading = true; }, // 上传成功 uploadSuccess(d, f) { this.htmPath = d.data.htmPath; this.$refs.hhcHTML.innerHTML = d.data.hhcData; this.$nextTick(() => { let treeData = this.decodeHhcData(this.$refs.hhcHTML.querySelectorAll(`.hhcHTML>ul>li`)) this.treeData = treeData; this.loading = false; this.$nextTick(() => { this.$refs.tree.setCurrentKey(this.current_node.id) this.src = this.current_node.filePath; }); }); }, // 上传失败 uploadError(d, f) { this.loading = false; }, //点击节点 nodeClick(data) { }, //过滤节点 filterNode(value, data) { }, // 树节点修改 current_change(d) { this.src = d.filePath; }, } }; </script> <style lang="scss" scoped> .chmDecode { width: 100%; display: flex; flex-wrap: nowrap; $treeWidth: 610px; $treeControlWidth: 100px; &>.sg-left { width: $treeWidth; flex-wrap: nowrap; white-space: nowrap; flex-shrink: 0; .tree-header { display: flex; justify-content: space-between; align-items: center; &>.sg-left {} &>.sg-right {} } .tree-body { height: calc(100vh - 200px); } } &>.sg-right { margin-left: 20px; flex-grow: 1; height: calc(100vh - 170px); .baseinfo { width: 100%; height: 100%; overflow-x: hidden; overflow-y: auto; position: relative; .form-body { height: calc(100% - 60px); overflow-y: auto; overflow-x: hidden; } .form-footer { position: absolute; height: 70px; box-sizing: border-box; padding-top: 20px; width: 100%; display: flex; justify-content: space-between; bottom: 0; &>* { width: 100%; flex-grow: 1; } } } } } </style>