效果:




config.mjs文件

javascript
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: {
}
}
}
})
前端代码:
列表:
javascript
<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文件:
javascript
<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>
新增方法:
javascript
<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>
后端生成方法:
java
/**
* 生成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();
}