特性:
1、支持任意深度的chm文件解析
2、解析后内容结构转换为tree数据呈现
3、点击树节点可以在html实时查看数据
4、不依赖任何浏览器端插件,兼容性较好
nodejs端核心代码
javascript
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前端核心代码
html
<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://rp.wedoyun.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: '',
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 = {
[p0.getAttribute('name')]: p0.getAttribute('value'),//文件别名
[p1.getAttribute('name')]: p1.getAttribute('value'),
filePath: `${this.htmPath}${p1.getAttribute('value')}`,//文件路径
}
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;
console.log(JSON.stringify(treeData, null, 2));
});
},
// 上传失败
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>