效果:
编辑
编辑
编辑
编辑
config.mjs文件
编辑
import {defineConfig} from 'vitepress' import hljs from 'highlight.js/lib/core' import javascript from 'highlight.js/lib/languages/javascript' import xml from 'highlight.js/lib/languages/xml' import {ref} from "./cache/deps/vue.js"; // 注册语言 hljs.registerLanguage('javascript', javascript) hljs.registerLanguage('html', xml) export default defineConfig(async () => { return { markdown: { theme: 'material-theme-palenight', lineNumbers: true, math: true, container: { tipLabel: '提示', warningLabel: '警告', dangerLabel: '危险', infoLabel: "信息", detailsLabel: "详情" }, }, enhanceApp: { setup: (ctx) => { ctx.app.component('AdBanner', AdBanner) } }, title: "平台基础管理系统PBM帮助文档", description: "系统使用说明", themeConfig: { outline: { level: [2, 3], label: '页面导航' }, siteTitle: "帮助文档", logo: "../pubilc/assets/logo.png", nav: [ { text: '首页', link: '/' }, { text: '数据采集', items: [ { text: '直报', link: 'DFS' }, { text: '爬虫', link: 'DAS' }, { text: '爬虫2', link: 'das2' }, { text: '爬虫2', link: 'das2' }, ] }, ] , sidebar: { 'DFS': { items: [ { text: '表单管理平行测试', link: 'null' } ] }, 'formManage': { items: [ { text: '表单管理', collapsible: true, collapsed: false, items: [ { text: '表单管理文档', link: 'testMD' }, { text: '表单路径测试', link: 'testForm' }, { text: '测试文档2', link: 'test2' }, { text: '测试3', link: 'test3' } ] } ] }, }, footer: { message: "平台基础管理系统PBM帮助文档", copyright: '2025.5.20 @langtao' }, styl: { } } } })
前端代码:
列表:
<template> <div class="app-container" style="width:100%;height:100%;"> <el-aside style="padding-right: 10px; padding-left: 15px; padding-top: 0px;float:left;width:20%;background:none;"> <div class="block" style="width:100%;height:100%;"> <!-- <el-select v-model="xiangmuId" class="filter-item" placeholder="项目" style="width: 100%" clearable @change="handChange"> <el-option v-for="item in xmList" :key="item.id" :label="item.xiangmumingcheng" :value="item.id" /> </el-select> --> <el-cascader ref="cascaderHandle" v-model="xiangmuId" style="width:100%;" clearable :options="xmList" :props="optionProps" :show-all-levels="false" @change="change"> <template slot-scope="{ data }"> <span @click="clickNode">{{ data.xiangmumingcheng }}</span> </template> </el-cascader> <el-tree id="el-tree" ref="tree" class="filter-tree" :data="data2" node-key="id" :highlight-current="true" :props="defaultProps" :default-expanded-keys="TreeArr" :expand-on-click-node="false" @node-click="handleNodeClick" @node-contextmenu="rightClick" /> <div v-show="menu1Visible"> <ul id="menu1" class="menu1" style="font-size:14px;"> <li class="menu_item" @click="handleAdd">新增下级目录</li> <li class="menu_item" @click="download()">生成md文件</li> </ul> </div> <div v-show="menu2Visible"> <ul id="menu2" class="menu2" style="font-size:14px;"> <li class="menu_item" @click="handleAdd">新增下级目录</li> </ul> </div> <div v-show="menu3Visible"> <ul id="menu3" class="menu3" style="font-size:14px;"> <li class="menu_item" @click="handleAdd">新增下级目录</li> <li class="menu_item" @click="handleEdit">编辑目录</li> <li class="menu_item" @click="add()">新增文档</li> <li class="menu_item" @click="handleDisable">删除目录</li> </ul> </div> <div v-show="menu4Visible"> <ul id="menu4" class="menu4" style="font-size:14px;"> <li class="menu_item" @click="handleEdit">编辑目录</li> <li class="menu_item" @click="add()">新增文档</li> <li class="menu_item" @click="handleDisable">删除目录</li> </ul> </div> <div v-show="menu5Visible"> <ul id="menu5" class="menu5" style="font-size:14px;"> <li class="menu_item" @click="edit()">编辑文档</li> <li class="menu_item" @click="remove()">删除文档</li> </ul> </div> </div> </el-aside> <el-aside style="padding-right: 5px; padding-left: 5px; padding-top: 0px;float:left;width:80%;background:none;"> <div class="filter-container" style="text-align:center;margin-top:0px;padding-bottom: 0px;"> <el-button class="filter-item" icon="el-icon-s-data" type="info" @click="history()">文档历史版本</el-button> <el-button class="filter-item" icon="el-icon-warning-outline" type="primary" @click="yulan()">预览</el-button> </div> <el-table v-show="displayVisible" v-loading="listLoading" :data="numList" :height="tableHeight4" style="width: 100%; overflow:auto;" border> <el-table-column type="index" header-align="center" align="center" label="序号" width="80px" /> <el-table-column header-align="center" align="left" label="操作" width="320px"> <template slot-scope="scope"> <el-button type="text" size="small" @click="hlfp(scope)">查看</el-button> </template> </el-table-column> <el-table-column header-align="center" align="left" label="项目名称"> <template slot-scope="scope"> <span>{{ scope.row.xmName }}</span> </template> </el-table-column> <el-table-column header-align="center" align="right" label="文档数量"> <template slot-scope="scope"> <span>{{ scope.row.num }}</span> </template> </el-table-column> </el-table> <div v-show="displayVisible1" style="width:100%; margin:0 auto;"> <div> <table class="table"> <tr> <td class="title-name"> <strong>项目:</strong> </td> <td class="table-content"> <span>{{ from.xmName }}</span> </td> <td class="title-name"> <strong>文档名称:</strong> </td> <td class="table-content"> <span>{{ from.tableName }}</span> </td> </tr> <tr> <td class="title-name"> <strong>备注:</strong> </td> <td colspan="3" style="100%"> <span>{{ from.tableDetailExplain }}</span> </td> </tr> <tr> <td class="title-name"> <strong>内容:</strong> </td> </tr> <tr> <td colspan="4" style="98%"> <span v-html="from.htmlData" /> </td> </tr> </table> </div> </div> <el-dialog :visible.sync="dialogAddVisible" width="600px" :show-close="false" append-to-body :title="'新增下级目录'"> <dialog-add v-if="dialogAddVisible" ref="dialogAdd" :xiangmu="xiangmuId" :classify="classifyId" :level="treeLevel" :visible.sync="dialogAddVisible" /> </el-dialog> <el-dialog :visible.sync="dialogEditVisible" width="600px" :show-close="false" append-to-body :title="'编辑目录'"> <dialog-edit v-if="dialogEditVisible" ref="dialogEdit" :proid="code" :level="treeLevel" :visible.sync="dialogEditVisible" /> </el-dialog> <el-dialog :visible.sync="addApiVisible" width="1200px" append-to-body :show-close="false" :title="'新增文档'"> <add-api v-if="addApiVisible" ref="addApi" :classify="classifyId" :xiangmu="xiangmuId" :visible.sync="addApiVisible" /> </el-dialog> <el-dialog :visible.sync="editApiVisible" width="1200px" append-to-body :show-close="false" :title="'编辑文档'"> <edit-api v-if="editApiVisible" ref="editApi" :proid="classifyId" :visible.sync="editApiVisible" /> </el-dialog> <el-dialog :visible.sync="historyVisible" width="1000px" append-to-body :title="'文档历史记录'"> <history v-if="historyVisible" ref="history" :history="batchCode" :visible.sync="historyVisible" /> </el-dialog> <el-dialog :visible.sync="downloadVisible" width="400px" append-to-body :show-close="false" :title="'生成MD文件'"> <download v-if="downloadVisible" ref="download" :xiangmu="xiangmuId" :classify="classifyId" :level="treeLevel2" :visible.sync="downloadVisible" /> </el-dialog> <el-dialog :visible.sync="yulanVisible" width="1200px" append-to-body :title="'预览'"> <yulan v-if="yulanVisible" ref="yulan" :proid="xiangmuId" :visible.sync="yulanVisible" /> </el-dialog> </el-aside> </div> </template> <script> import { getXiangMuList } from '@/api/projectInfo' import { getInfo, getFileList, remove } from '@/api/usersManualFile' import { getInitialLoading, getAgainLoading, makeMD } from '@/api/configMenu' import { disabled} from '@/api/usersManualCatalog' import { getDictListByCode } from '@/api/dict' import { Message, MessageBox } from 'element-ui' import { tableHeight4 } from '@/utils/tableHeight' import DialogAdd from './add' // eslint-disable-line no-unused-vars import DialogEdit from './edit' // eslint-disable-line no-unused-vars import AddApi from '../manual/addApi' // eslint-disable-line no-unused-vars import EditApi from '../manual/editApi' // eslint-disable-line no-unused-vars import history from '../manual/history' // eslint-disable-line no-unused-vars import download from '../manual/downloadByXm' // eslint-disable-line no-unused-vars import yulan from '../manual/yulan' // eslint-disable-line no-unused-vars export default { name: 'Zzjg', components: { AddApi, EditApi,history, download, yulan, DialogAdd, DialogEdit, }, mixins: [tableHeight4], provide() { return { getTreeList: this.getTreeList } }, data() { return { TreeArr: [], optionProps: { value: 'id', children: 'children', label: 'xiangmumingcheng', checkStrictly: false, expandTrigger: 'hover' }, IdArr: [], displayVisible: false, displayVisible1: false, listLoading: false, total: 0, xiangmuId: '', numList: [], xmList: [], userList: [], classifyId: '', batchCode: '', flag: '', DATA: null, NODE: null, objectID: null, menu1Visible: false, menu2Visible: false, menu3Visible: false, menu4Visible: false, menu4Visible2: false, menu5Visible:false, treeLevel:'', treeLevel2: '', downloadId: '', dialogAddVisible: false, dialogEditVisible: false, addApiVisible: false, editApiVisible: false, historyVisible: false, downloadVisible: false, yulanVisible: false, stateOptions: [], data: [], data2: [], from: {}, queryPage: {}, datas: '', img1: require('../../../assets/images/Folder-01.png'), defaultProps: { children: 'children', label: 'menuName' } } }, created() { this.getList() this.getXiangMuList() this.getDictList('YW_BASE_STATUS') }, methods: { getList() { this.displayVisible = true this.displayVisible1 = false getFileList().then(response => { this.numList = response.data this.listLoading = false }).catch(response => { this.listLoading = false }) }, // 提示框 renderContent: function(h, { node, data, store }) { var text = data.name if (data.flag === 'ML') { if (text.length > 15) { return ( < span > < i > <img src={this.img1} style='width: 18px;height: 18px;margin-right:5px;padding-top:1px' /></i> <el-tooltip class='item' id='tool' effect='light' popper-class='draw' visible-arrow='false' content={data.name} placement='bottom-start' > <span class='style-demo' >{data.name}</span > </el-tooltip> </span>) } else { return ( < span > < i > <img src={this.img1} style='width: 18px;height: 18px;margin-right:5px;padding-top:1px' /></i> <span class='style-demo' >{data.name}</span > </span> ) } } else { if (text.length > 15) { return ( < span > <el-tooltip class='item' id='tool' effect='light' popper-class='draw' visible-arrow='false' content={data.name} placement='bottom-start' > <span class='style-demo' >{data.name}</span > </el-tooltip> </span>) } else { return ( < span > <span class='style-demo' >{data.name}</span > </span> ) } } }, hlfp(scope) { this.displayVisible = false this.displayVisible1 = true this.xiangmuId = scope.row.xmId // 加载列表 getInitialLoading({ xmId: scope.row.projectId }).then(response => { this.data = response.data this.data2 = this.buildTree2(this.data) this.data2.forEach(m => { this.TreeArr.push(m.id) }) this.listLoading = false }).catch(response => { this.listLoading = false }) }, getXiangMuList() { // 加载列表 this.xmList = [] getXiangMuList(this.queryPage).then(response => { this.xmList = this.buildTree(response.data) this.listLoading = false }).catch(response => { this.listLoading = false }) }, buildTree(data) { var rdata = [] for (let i = 0; i < data.length; i++) { var e1 = data[i] if (e1.parentId === '-1') { rdata.push(e1) } for (let j = 0; j < data.length; j++) { var e2 = data[j] if (e1.parentId === e2.id) { if (!e2.children) { e2.children = [] } e2.children.push(e1) } } } return rdata }, clickNode($event) { $event.target.parentElement.parentElement.firstElementChild.click() this.$refs.cascaderHandle.dropDownVisible = false }, change(val) { const nodesObj = this.$refs['cascaderHandle'].getCheckedNodes() this.data = [] this.data2 = [] if (val.length === 2) { this.displayVisible = false this.xiangmuId = nodesObj[0].data.id this.getTreeList(nodesObj[0].data.id) this.displayVisible1 = true } else { this.displayVisible = true this.displayVisible1 = false } }, // handChange(e) { // this.displayVisible = false // this.data = [] // this.data2 = [] // if (e !== null && e !== '' && e !== 'null') { // this.getTreeList(e) // this.displayVisible1 = true // } // }, getTreeList(code) { // 加载列表 // 加载列表 getInitialLoading({ xmId: code }).then(response => { this.data = response.data console.log(code,'123213') this.data2 = this.buildTree2(this.data) this.data2.forEach(m => { this.TreeArr.push(m.id) }) this.listLoading = false }).catch(response => { this.listLoading = false }) }, buildTree2(data) { var rdata = [] for (let i = 0; i < data.length; i++) { var e1 = data[i] if (e1.parentId === '-1') { rdata.push(e1) } for (let j = 0; j < data.length; j++) { var e2 = data[j] if (e1.parentId === e2.id) { if (!e2.children) { e2.children = [] } e2.children.push(e1) } } } return rdata }, getDictList(code) { getDictListByCode(code).then(response => { if (code === 'YW_BASE_STATUS') { this.stateOptions = response.data } }) }, getDicName(code, flag) { var dict = [] if (flag === 'YW_BASE_STATUS') { dict = this.stateOptions } for (var i in dict) { if (dict[i].code === code) { return dict[i].name } } }, // 右键点击 rightClick(MouseEvent, object, Node, element) { // 鼠标右击触发事件 this.DATA = object this.NODE = Node console.log(this.NODE,this.DATA,'123',this.NODE.level) if (this.NODE.level === 1) { this.menu1Visible = true // 显示模态窗口,跳出自定义菜单栏 this.menu2Visible = false // 显示模态窗口,跳出自定义菜单栏 this.menu3Visible = false var menu1 = document.querySelector('#menu1') document.addEventListener('click', this.foo) // 给整个document添加监听鼠标事件,点击任何位置执行foo方法 menu1.style.display = 'block' menu1.style.left = MouseEvent.clientX - 0 + 'px' menu1.style.top = MouseEvent.clientY - 20 + 'px' } else if (this.NODE.level > 1 && this.NODE.level < 3) { if (this.NODE.data.flag === 'ML') { this.menu1Visible = false // 显示模态窗口,跳出自定义菜单栏 this.menu2Visible = true // 显示模态窗口,跳出自定义菜单栏 var menu2 = document.querySelector('#menu2') document.addEventListener('click', this.foo) // 给整个document添加监听鼠标事件,点击任何位置执行foo方法 menu2.style.display = 'block' menu2.style.left = MouseEvent.clientX - 0 + 'px' menu2.style.top = MouseEvent.clientY - 20 + 'px' } } else if(this.NODE.level === 3){ this.menu1Visible = false this.menu2Visible = false this.menu3Visible = true this.menu4Visible = false this.menu4Visible2 = false this.menu5Visible = false var menu3 = document.querySelector('#menu3') document.addEventListener('click', this.foo) // 给整个document添加监听鼠标事件,点击任何位置执行foo方法 menu3.style.display = 'block' menu3.style.left = MouseEvent.clientX - 0 + 'px' menu3.style.top = MouseEvent.clientY - 20 + 'px' } else if(this.NODE.level ===4){ this.menu1Visible = false this.menu2Visible = false this.menu3Visible = false if (this.NODE.data.flag === 'ML') { this.menu4Visible = true var menu4 = document.querySelector('#menu4') document.addEventListener('click', this.foo) // 给整个document添加监听鼠标事件,点击任何位置执行foo方法 menu4.style.display = 'block' menu4.style.left = MouseEvent.clientX - 0 + 'px' menu4.style.top = MouseEvent.clientY - 20 + 'px' }else{ this.menu4Visible2 = true var menu42 = document.querySelector('#menu42') document.addEventListener('click', this.foo) // 给整个document添加监听鼠标事件,点击任何位置执行foo方法 menu42.style.display = 'block' menu42.style.left = MouseEvent.clientX - 0 + 'px' menu42.style.top = MouseEvent.clientY - 20 + 'px' } } else if(this.NODE.level === 5){ this.menu1Visible = false this.menu2Visible = false this.menu3Visible = false this.menu4Visible = false this.menu4Visible2 = false this.menu5Visible = true var menu5 = document.querySelector('#menu5') document.addEventListener('click', this.foo) // 给整个document添加监听鼠标事件,点击任何位置执行foo方法 menu5.style.display = 'block' menu5.style.left = MouseEvent.clientX - 0 + 'px' menu5.style.top = MouseEvent.clientY - 20 + 'px' } this.classifyId = this.NODE.data.id this.treeLevel = this.NODE.level+1 this.treeLevel2 = this.NODE.level this.downloadId = this.NODE.data.id console.log(this.downloadId,'this.downloadId') this.xiangmuId = this.NODE.data.projectId }, foo() { // 取消鼠标监听事件 菜单栏 this.menu1Visible = false this.menu2Visible = false this.menu3Visible = false this.menu4Visible = false this.menu4Visible2 = false this.menu5Visible = false document.removeEventListener('click', this.foo) // 要及时关掉监听,不关掉的是一个坑,不信你试试,虽然前台显示的时候没有啥毛病,加一个alert你就知道了 }, handleNodeClick(data) { this.from = {} this.displayVisible1 = true this.classifyId = data.id console.log(this.classifyId,'this.classifyId',this.optionProps.label) this.xiangmuId = data.projectId this.flag = data.flag this.batchCode = data.code this.parent = data.parentId if (this.flag === 'WD') { getInfo({ code: data.id }).then(response => { this.from = response.data[0] }).catch(function() { }) } else { var dataA = data if (!this.IdArr.includes(dataA.id)) { // 树 getAgainLoading({ parentId: data.id, xmId: data.projectId }).then(response => { this.data = response.data for (let i = 0; i < this.data.length; i++) { const newChild = this.data[i] if (!dataA.children) { this.$set(dataA, 'children', []) } dataA.children.push(newChild) } this.dataId = dataA.id this.IdArr.push(dataA.id) this.listLoading = false }).catch(response => { this.listLoading = false }) } } }, handleAdd() { this.dialogAddVisible = true this.$nextTick(() => { this.$refs.dialogAdd }) }, handleEdit() { this.dialogEditVisible = true this.code = this.classifyId this.$nextTick(() => { this.$refs.dialogEdit }) }, add() { if (this.parent === '-1') { Message({ message: '请选择子节点!', type: 'error', duration: 5 * 1000 }) } else if (this.parent !== '-1' && this.flag !== 'WD') { if (this.xiangmuId !== null && this.xiangmuId !== '') { if (this.classifyId !== null && this.classifyId !== '') { this.addApiVisible = true this.$nextTick(() => { this.$refs.addApi }) } else { Message({ message: '请选择节点!', type: 'error', duration: 5 * 1000 }) } } else { Message({ message: '请选择项目!', type: 'error', duration: 5 * 1000 }) } } }, edit() { if (this.parent !== '-1' && this.flag === 'WD') { if (this.xiangmuId !== null && this.xiangmuId !== '') { if (this.classifyId !== null && this.classifyId !== '') { this.editApiVisible = true this.$nextTick(() => { this.$refs.editApi }) } else { Message({ message: '请选择子节点!', type: 'error', duration: 5 * 1000 }) } } else { Message({ message: '请选择项目!', type: 'error', duration: 5 * 1000 }) } } else { Message({ message: '请选择文档节点!', type: 'error', duration: 5 * 1000 }) } }, history() { if (this.parent !== '-1' && this.flag === 'WD') { if (this.xiangmuId !== null && this.xiangmuId !== '') { if (this.classifyId !== null && this.classifyId !== '') { this.historyVisible = true this.$nextTick(() => { this.$refs.history }) } else { Message({ message: '请选择子节点!', type: 'error', duration: 5 * 1000 }) } } else { Message({ message: '请选择项目!', type: 'error', duration: 5 * 1000 }) } } else { Message({ message: '请选择文档节点!', type: 'error', duration: 5 * 1000 }) } }, download() { if (this.xiangmuId !== null && this.xiangmuId !== '') { this.downloadVisible = true this.$nextTick(() => { this.$refs.download }) } else { Message({ message: '请选择项目!', type: 'error', duration: 5 * 1000 }) } }, handleDisable() { MessageBox.confirm('确认删除节点', '确定', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { disabled(this.NODE.id).then(response => { Message({ message: '删除节点成功', type: 'success', duration: 5 * 1000 }) // 重新加载表格 this.getTreeList(this.xiangmuId) }) }) }, remove() { if (this.parent !== '-1' && this.flag === 'WD') { if (this.xiangmuId !== null && this.xiangmuId !== '') { if (this.classifyId !== null && this.classifyId !== '') { MessageBox.confirm('确认删除', '确定', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { remove({ code: this.classifyId }).then(response => { Message({ message: '删除成功', type: 'success', duration: 5 * 1000 }) // 重新加载表格 this.getTreeList(this.xiangmuId) // 重新加载表格 this.handleNodeClick() }) }) } else { Message({ message: '请选择子节点!', type: 'error', duration: 5 * 1000 }) } } else { Message({ message: '请选择项目!', type: 'error', duration: 5 * 1000 }) } } else { Message({ message: '请选择文档节点!', type: 'error', duration: 5 * 1000 }) } }, yulan() { if (this.xiangmuId !== null && this.xiangmuId !== '') { this.yulanVisible = true this.$nextTick(() => { this.$refs.yulan }) } else { Message({ message: '请选择项目!', type: 'error', duration: 5 * 1000 }) } } } } </script> <style scoped> .app-container{ height:calc(100% - 20px) !important; width:calc(100% - 20px) !important; padding:10px 0; } #app-contain{ height:100%; width:100% } .custom-tree-node { flex: 1; display: flex; align-items: center; justify-content: space-between; font-size: 14px; padding-right: 8px; } /* 右键会选中文字,为了美观将它禁用*/ #el-tree{ user-select:none; } #el-tree >>> .style-demo { color: #474a4d; font-size: 14px; font-family: "黑体"; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; } .menu1 { height: 60px; width: 120px; position: fixed; border: 1px solid #ccc; background-color: white; list-style: none; padding-left: 10px; } .menu2 { height: 40px; width: 120px; position: fixed; border: 1px solid #ccc; background-color: white; list-style: none; padding-left: 10px; } .menu3 { height: 120px; width: 120px; position: fixed; border: 1px solid #ccc; background-color: white; list-style: none; padding-left: 10px; } .menu4 { height: 90px; width: 120px; position: fixed; border: 1px solid #ccc; background-color: white; list-style: none; padding-left: 10px; } .menu42 { height: 80px; width: 120px; position: fixed; border: 1px solid #ccc; background-color: white; list-style: none; padding-left: 10px; } .menu5 { height: 60px; width: 120px; position: fixed; border: 1px solid #ccc; background-color: white; list-style: none; padding-left: 10px; } .menu_item { line-height: 20px; text-align: left; margin-top: 10px; } .collapse-title{ font-size: 16px; color:#1196EE; } .table{ border-collapse: separate; border-spacing: 10px 10px; } .title-name{ width: 100px; text-align: left; vertical-align: middle; } .table-content{ width: 450px; } </style>
生成md文件:
<template> <div class="app-container" style="width:100%;height:100%;"> <div class="block" style="width:100%;height:100%;"> 是否要按照目录生成MD文件 </div> <span slot="footer" class="dialog-footer"> <div style="text-align:center;margin-top:20px;"> <el-button type="primary" @click="onSave">确定</el-button> <el-button type="danger" class="quxiao_btn" @click="closePage()">取消</el-button> </div> </span> </div> </template> <script> import { getTreeList, makeMD } from '@/api/configMenu' import { Message } from 'element-ui' export default { name: 'Zzjg', props: { // 第二种方式 xiangmu: { type: String, required: true }, classify: { type: String, required: true }, level: { type: String, required: true } }, data() { return { data: [], data2: [], ids: '', defaultProps: { children: 'children', label: 'name' } } }, created() { this.getTreeList() }, methods: { getTreeList() { // 加载列表 getTreeList({ xiangmuId: this.xiangmu }).then(response => { this.data = response.data this.data2 = this.buildTree2(this.data) }).catch(response => { }) }, buildTree2(data) { var rdata = [] for (let i = 0; i < data.length; i++) { var e1 = data[i] if (e1.parentId === '-1') { rdata.push(e1) } for (let j = 0; j < data.length; j++) { var e2 = data[j] if (e1.parentId === e2.id) { if (!e2.children) { e2.children = [] } e2.children.push(e1) } } } return rdata }, onSave() { var ids = '' var flag = '' var level = '' ids = this.classify console.log(this.level,'level') const loading = this.$loading({ lock: true, text: 'Loading', spinner: 'el-icon-loading', background: 'rgba(0, 0, 0, 0.7)' }) makeMD({ id: ids, flag: flag, level:this.level }).then(response => { var fileName = 'download.zip' // const contentDisposition = response.headers['content-disposition'] // if (contentDisposition) { // fileName = window.decodeURI(response.headers['content-disposition'].split('=')[1], 'UTF-8') // } const blob = new Blob([response], { type: `application/zip` // word文档为msword,pdf文档为pdf }) const objectUrl = URL.createObjectURL(blob) const link = document.createElement('a') link.href = objectUrl console.log(objectUrl,'objectUrl') link.setAttribute('download', fileName) document.body.appendChild(link) link.click() // 释放内存 window.URL.revokeObjectURL(link.href) loading.close() this.closePage() if (response.code === 20000) { Message({ message: '生成成功', type: 'success', duration: 5 * 1000 }) loading.close() this.closePage() } }).catch(response => { loading.close() }) }, closePage() { this.$emit('update:visible', false) } } } </script>
新增方法:
<template> <div class="markdown"> <div style="width:100%;height:auto; margin:0 auto;"> <el-form ref="form" :model="form" :rules="rules" label-width="80px" style="margin: 0 auto;width:100%;"> <el-row> <el-col :span="12"> <el-form-item label="手册名称" prop="tableName"> <el-input v-model="form.tableName" placeholder="手册名称" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="排序号" prop="sortNum"> <el-input-number v-model="form.sortNum" :step="1" style="width:100%;text-align:left;" /> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="12"> <el-form-item label="路径" prop="link"> <el-input v-model="form.link" placeholder="路径" /> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="24"> <el-form-item label="内容" prop="sortNum"> <mavon-editor ref="md" v-model="content" style="height: 400px" @change="change" @imgAdd="imgAdd" /> </el-form-item> </el-col> </el-row> </el-form> <div style="text-align:center; padding-top:10px"> <el-button type="primary" @click="save">保存</el-button> <el-button type="danger" @click="closePage">取消</el-button> </div> </div> </div> </template> <script> import { Message } from 'element-ui' import { save, getMdTemplate } from '@/api/usersManualFile' import { upload } from '@/api/projectFile' import { mavonEditor } from 'mavon-editor' import 'mavon-editor/dist/css/index.css' export default { name: '', components: { mavonEditor }, inject: ['getTreeList'], props: { // 第二种方式 xiangmu: { type: String, required: true }, classify: { type: String, required: true } }, data() { return { form: { sortNum: 1 }, rules: { sortNum: [ { required: true, message: '排序号不能为空', trigger: 'blur' } ], tableName: [ { required: true, message: 'api名称不能为空', trigger: 'blur' } ] }, content: '', html: '', configs: {} } }, created() { this.getDetailed() }, methods: { getDetailed() { const loading = this.$loading({ lock: true, text: 'Loading', spinner: 'el-icon-loading', background: 'rgba(0, 0, 0, 0.7)' }) getMdTemplate().then(response => { this.content = response.data loading.close() }).catch(function() { loading.close() }) }, // 将图片上传到服务器,返回地址替换到md中 imgAdd(pos, $file) { const formdata = new FormData() formdata.append('image', $file) // 访问后台服务器方法 upload(formdata).then(res => { if (res.code === 20000) { this.$refs.md.$img2Url(pos, res.data.abstractPath) } else { this.$message.error(res.message) } }).catch(err => { console.log(err) }) }, // 所有操作都会被解析重新渲染 change(value, render) { this.form.htmlData = render this.form.markdownData = value }, save() { // 新增 this.$refs.form.validate(valid => { if (valid) { const loading = this.$loading({ lock: true, text: 'Loading', spinner: 'el-icon-loading', background: 'rgba(0, 0, 0, 0.7)' }) this.form.xmId = this.xiangmu this.form.classifyId = this.classify save(this.form).then(response => { Message({ message: '新增成功', type: 'success', duration: 5 * 1000 }) this.$emit('update:visible', false) // this.getList() this.getTreeList() loading.close() }).catch(response => { loading.close() }) } else { return false } }) }, closePage() { this.$emit('update:visible', false) } } } </script>
后端生成方法:
/** * 生成md文件 * * @Title: makeMD * @Description: * @author sxy * @param flag * @param ids * @return * @throws IOException */ @RequestMapping(value = "/makeMD", method = RequestMethod.POST, produces = "application/json") // @GetMapping(value="/makeMD", method = RequestMethod.POST, produces = "application/json") @ResponseBody public void makeMD(HttpServletRequest request, HttpServletResponse response, String flag, String id,Integer level) throws IOException { if (id == null || StringUtils.isBlank(id)) { // return ResultData.error(ResultData.PARAM_ERROR_CODE, "参数错误"); } // 创建存放生成的MD文件的根目录 String rootDir = uploadPath + File.separator + "md_files"; File rootDirFile = new File(rootDir); if (!rootDirFile.exists()) { rootDirFile.mkdirs(); } // 生成日期子目录 // 生成日期时间子目录,格式为:年-月-日_时-分-秒 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"); String dateSubDir = LocalDateTime.now().format(formatter); String fullDirPath = rootDir + File.separator + dateSubDir; File dateDir = new File(fullDirPath); if (!dateDir.exists()) { dateDir.mkdirs(); } //md文件列表 List<UsersManualFile> list = new ArrayList<>(); //如果不是根节点的话,则去查找UsersManualFile表里的parent_id的数据 if (level != 1) { QueryWrapper<UsersManualFile> queryWrapper = new QueryWrapper<UsersManualFile>(); queryWrapper.eq("parent_id", id); list = usersManualFileMapper.selectList(queryWrapper); }else { QueryWrapper<UsersManualFile> queryWrapper = new QueryWrapper<UsersManualFile>(); queryWrapper.eq("umf_xm_id", id); list = usersManualFileMapper.selectList(queryWrapper); //mjs文件列表 QueryWrapper<ConfigMenu> queryWrapper2 = new QueryWrapper<ConfigMenu>(); queryWrapper2.eq("project_id", id); List<ConfigMenu> configMenuList = configMenuMapper.selectList(queryWrapper2); List<ConfigMenu> navMenuList = configMenuList; if (configMenuList == null || configMenuList.size() == 0) { // return ResultData.error(ResultData.PARAM_ERROR_CODE, "无数据"); } //动态nav StringBuilder mjsTemplateNav = new StringBuilder(); mjsTemplateNav.append( " nav: [\n" + " {\n" + " text: '首页',\n" + " link: '/'\n" + " },\n" ); //动态sidebar StringBuilder mjsTemplateSidebar = new StringBuilder(); mjsTemplateSidebar.append("sidebar: {\n"); //动态nav for (ConfigMenu configMenu : configMenuList) { // 二级节点为横向菜单 if ("2".equals(configMenu.getLevel())) { // 判断该菜单是否存在下拉子项 boolean hasSubItems = navMenuList.stream() .anyMatch(menu -> menu.getParentId().equals(configMenu.getId())); if (hasSubItems) { // 如果有下拉子项,则使用 items 格式 mjsTemplateNav.append(" {\n"); mjsTemplateNav.append(" text: '").append(configMenu.getMenuName()).append("',\n"); mjsTemplateNav.append(" items: [\n"); for (ConfigMenu subMenu : navMenuList) { if (subMenu.getParentId().equals(configMenu.getId())) { mjsTemplateNav.append(" {\n"); mjsTemplateNav.append(" text: '").append(subMenu.getMenuName()).append("',\n"); mjsTemplateNav.append(" link: '").append(subMenu.getLink()).append("'\n"); mjsTemplateNav.append(" },\n"); } } mjsTemplateNav.append(" ]\n"); mjsTemplateNav.append(" },\n"); } else { // 如果没有下拉子项,则使用简单格式 mjsTemplateNav.append(" {\n"); mjsTemplateNav.append(" text: '").append(configMenu.getMenuName()).append("',\n"); mjsTemplateNav.append(" link: '").append(configMenu.getLink()).append("'\n"); mjsTemplateNav.append(" },\n"); } } } //动态生成sidebar // 创建一个映射,用于存储每个路径对应的菜单项 Map<String, List<ConfigMenu>> pathToMenus = new HashMap<>(); for (ConfigMenu configMenu : configMenuList) { if ("3".equals(configMenu.getLevel()) || "4".equals(configMenu.getLevel())) { // 如果是三级或四级菜单,检查其是否有对应的文档文件 for (UsersManualFile usersManualFile : list) { if (usersManualFile.getParentId().equals(configMenu.getId())) { // 根据菜单的路径分组 String path = configMenu.getLink(); pathToMenus.computeIfAbsent(path, k -> new ArrayList<>()).add(configMenu); } } } } // 生成每个路径的配置 for (Map.Entry<String, List<ConfigMenu>> entry : pathToMenus.entrySet()) { String path = entry.getKey(); List<ConfigMenu> menus = entry.getValue(); // 对菜单项进行去重 List<ConfigMenu> uniqueMenus = new ArrayList<>(); for (ConfigMenu menu : menus) { if (!uniqueMenus.contains(menu)) { uniqueMenus.add(menu); } } menus = uniqueMenus; mjsTemplateSidebar.append(" '").append(path).append("': {\n"); mjsTemplateSidebar.append(" items: [\n"); for (ConfigMenu configMenu : menus) { if ("3".equals(configMenu.getLevel())) { // 如果是三级菜单,直接添加文档项 for (UsersManualFile usersManualFile : list) { if (usersManualFile.getParentId().equals(configMenu.getId())) { mjsTemplateSidebar.append(" {\n"); mjsTemplateSidebar.append(" text: '").append(usersManualFile.getTableName()).append("',\n"); mjsTemplateSidebar.append(" link: '").append(usersManualFile.getLink()).append("'\n"); mjsTemplateSidebar.append(" },\n"); } } } else if ("4".equals(configMenu.getLevel())) { // 如果是四级菜单,创建一个可折叠的项 mjsTemplateSidebar.append(" {\n"); mjsTemplateSidebar.append(" text: '").append(configMenu.getMenuName()).append("',\n"); mjsTemplateSidebar.append(" collapsible: true,\n"); mjsTemplateSidebar.append(" collapsed: false,\n"); mjsTemplateSidebar.append(" items: [\n"); // 查找对应的文档文件 boolean hasItems = false; for (UsersManualFile usersManualFile : list) { if (usersManualFile.getParentId().equals(configMenu.getId())) { if (!hasItems) { hasItems = true; } mjsTemplateSidebar.append(" {\n"); mjsTemplateSidebar.append(" text: '").append(usersManualFile.getTableName()).append("',\n"); mjsTemplateSidebar.append(" link: '").append(usersManualFile.getLink()).append("'\n"); mjsTemplateSidebar.append(" },\n"); } } if (hasItems) { mjsTemplateSidebar.setLength(mjsTemplateSidebar.length() - 2); // 去掉最后一个逗号 mjsTemplateSidebar.append("\n ]\n"); } else { mjsTemplateSidebar.append(" ]\n"); } mjsTemplateSidebar.append(" },\n"); } } mjsTemplateSidebar.setLength(mjsTemplateSidebar.length() - 2); // 去掉最后一个逗号 mjsTemplateSidebar.append("\n ]\n"); mjsTemplateSidebar.append(" },\n"); } mjsTemplateSidebar.append(" },\n"); mjsTemplateNav.append(" ]\n" + " ,\n"); //根据id获取项目名称 ProjectInfo projectInfo = projectInfoMapper.selectById(id); String projectName = ""; if (projectInfo != null){ projectName = projectInfo.getXiangmumingcheng(); } //配置mjs文件内容 // 固定模板头部 String mjsTemplateHeder = "import {defineConfig} from 'vitepress'\n" + "import hljs from 'highlight.js/lib/core'\n" + "import javascript from 'highlight.js/lib/languages/javascript'\n" + "import xml from 'highlight.js/lib/languages/xml'\n" + "import {ref} from \"./cache/deps/vue.js\";\n" + "// 注册语言\n" + "hljs.registerLanguage('javascript', javascript)\n" + "hljs.registerLanguage('html', xml)\n" + "\n" + "export default defineConfig(async () => {\n" + " return {\n" + " markdown: {\n" + " theme: 'material-theme-palenight',\n" + " lineNumbers: true,\n" + " math: true,\n" + " container: {\n" + " tipLabel: '提示',\n" + " warningLabel: '警告',\n" + " dangerLabel: '危险',\n" + " infoLabel: \"信息\",\n" + " detailsLabel: \"详情\"\n" + " },\n" + "\n" + " },\n" + " enhanceApp: {\n" + " setup: (ctx) => {\n" + " ctx.app.component('AdBanner', AdBanner)\n" + " }\n" + " },\n" + "\n" + //动态修改项目名称 " title: \""+projectName+"帮助文档\",\n" + " description: \"系统使用说明\",\n" + " themeConfig: {\n" + " outline: {\n" + " level: [2, 3],\n" + " label: '页面导航'\n" + " },\n" + " siteTitle: \"帮助文档\",\n" + " logo: \"../pubilc/assets/logo.png\",\n"; //固定模板尾部 String mjsTemplateTail = " footer: {\n" + " message: \""+projectName+"帮助文档\",\n" + " copyright: '2025.5.20 @langtao'\n" + " },\n" + " styl: {\n" + "\n" + " }\n" + "\n" + " }\n" + " }\n" + "})"; // 写入动态生成的 .mjs 文件 String mjsContent = mjsTemplateHeder+mjsTemplateNav+mjsTemplateSidebar+mjsTemplateTail; String fileName = "config" + ".mjs"; String filePath = fullDirPath + File.separator + fileName; // 写入文件 try (FileWriter writer = new FileWriter(filePath)) { writer.write(mjsContent); } } if (list == null || list.size() == 0) { // return ResultData.error(ResultData.PARAM_ERROR_CODE, "无数据"); } // 存储生成的md文件的相对路径 List<String> mdFilePaths = new ArrayList<>(); for (UsersManualFile usersManualFile : list) { // 使用文件ID作为文件名前缀,避免文件名重复 String fileNamePrefix = usersManualFile.getId().toString().substring(usersManualFile.getId().toString().length() - 8); String fileName = fileNamePrefix + "-" + usersManualFile.getTableName() + ".md"; String filePath = fullDirPath + File.separator + fileName; // 写入MD内容 try (FileWriter writer = new FileWriter(filePath)) { writer.write(usersManualFile.getMarkdownData()); } // 添加相对路径到结果列表 mdFilePaths.add("md_files/" + dateSubDir + fileName); } // 在生成MD文件和MJS文件后,将文件夹压缩成ZIP String zipPath = fullDirPath + ".zip"; FileOutputStream fos = new FileOutputStream(zipPath); ZipOutputStream zos = new ZipOutputStream(fos); // 添加文件夹到ZIP addFolderToZip(fullDirPath, fullDirPath, zos); // 关闭流 zos.close(); fos.close(); // 设置响应头,以便前端下载 response.setHeader("Content-Disposition", "attachment; filename=" + dateSubDir + ".zip"); response.setHeader("Content-Type", "application/octet-stream"); // 将ZIP文件发送给前端 FileInputStream fis = new FileInputStream(zipPath); IOUtils.copy(fis, response.getOutputStream()); response.getOutputStream().flush(); fis.close(); // 删除临时文件 // deleteDir(new File(fullDirPath)); // new File(zipPath).delete(); // return ResultData.success("ok", response); } /** * 将文件夹添加到ZIP压缩包中 */ private void addFolderToZip(String srcFolder, String baseFolder, ZipOutputStream zos) throws IOException { File folder = new File(srcFolder); for (File file : folder.listFiles()) { if (file.isFile()) { FileInputStream fis = new FileInputStream(file); String entryName = file.getAbsolutePath().substring(baseFolder.length() + 1); ZipEntry entry = new ZipEntry(entryName); zos.putNextEntry(entry); IOUtils.copy(fis, zos); zos.closeEntry(); fis.close(); } else if (file.isDirectory()) { addFolderToZip(file.getAbsolutePath(), baseFolder, zos); } } } /** * 删除文件夹及其内容 */ private void deleteDir(File dir) { if (dir.isDirectory()) { for (File child : dir.listFiles()) { deleteDir(child); } } dir.delete(); }