优化交互,开放文档支持目录导航
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<a-layout class="api-menu-trigger">
|
||||
<a-layout-sider theme="light" :trigger="null" collapsible v-model:collapsed="appMenuCollapsed" :width="rightAsideWidth" style="height: 100vh;overflow: auto;">
|
||||
<a-layout-sider theme="light" :trigger="null" collapsible v-model:collapsed="appMenuCollapsed" :width="leftAsideWidth" style="height: 100vh;overflow: auto;">
|
||||
<div class="logo">
|
||||
<img src="../../assets/api-logo.png">
|
||||
<h1>API开放文档</h1>
|
||||
@@ -14,8 +14,8 @@
|
||||
<a-layout-header style="border-bottom: 2px solid #eee;background: #fff; padding: 0; box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);-webkit-box-shadow:0 1px 4px rgba(0, 21, 41, 0.08);">
|
||||
<a-row type="flex">
|
||||
<a-col flex="auto">
|
||||
<MenuUnfoldOutlined class="trigger" v-if="appMenuCollapsed" @click="appMenuCollapsed = !appMenuCollapsed"/>
|
||||
<MenuFoldOutlined class="trigger" v-else @click="appMenuCollapsed = !appMenuCollapsed"/>
|
||||
<MenuUnfoldOutlined class="trigger" v-if="appMenuCollapsed" @click="turnLeftCollapse"/>
|
||||
<MenuFoldOutlined class="trigger" v-else @click="turnLeftCollapse"/>
|
||||
</a-col>
|
||||
<a-col flex="400px" style="text-align: right;padding-right: 20px;">
|
||||
</a-col>
|
||||
@@ -39,15 +39,28 @@
|
||||
return {
|
||||
minHeight: minHeight + 'px',
|
||||
appMenuCollapsed: false,
|
||||
rightAsideWidth: 300
|
||||
leftAsideWidth: 300
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
mounted() {
|
||||
this.dragChangeRightAsideWidth();
|
||||
this.dragChangeLeftAsideWidth();
|
||||
},
|
||||
methods: {
|
||||
dragChangeRightAsideWidth: function() {
|
||||
turnLeftCollapse() {
|
||||
this.appMenuCollapsed = !this.appMenuCollapsed;
|
||||
setTimeout(() => {
|
||||
if (this.appMenuCollapsed) {
|
||||
this.leftAsideWidthChange(this.leftAsideWidth + 1);
|
||||
} else {
|
||||
this.leftAsideWidthChange(1);
|
||||
}
|
||||
}, 100);
|
||||
},
|
||||
leftAsideWidthChange(width) {
|
||||
this.$store.commit('setLeftAsideWidth', width);
|
||||
},
|
||||
dragChangeLeftAsideWidth: function() {
|
||||
// 保留this引用
|
||||
let resize = this.$refs.rightResize;
|
||||
let resizeBar = this.$refs.rightResizeBar;
|
||||
@@ -61,12 +74,13 @@
|
||||
// 计算并应用位移量
|
||||
let endX = e2.clientX;
|
||||
let moveLen = startX - endX;
|
||||
if ((moveLen < 0 && this.rightAsideWidth < 600) || (moveLen > 0 && this.rightAsideWidth > 280)) {
|
||||
if ((moveLen < 0 && this.leftAsideWidth < 600) || (moveLen > 0 && this.leftAsideWidth > 280)) {
|
||||
startX = endX;
|
||||
this.rightAsideWidth -= moveLen;
|
||||
if (this.rightAsideWidth < 280) {
|
||||
this.rightAsideWidth = 280;
|
||||
this.leftAsideWidth -= moveLen;
|
||||
if (this.leftAsideWidth < 280) {
|
||||
this.leftAsideWidth = 280;
|
||||
}
|
||||
this.leftAsideWidthChange(this.leftAsideWidth);
|
||||
}
|
||||
};
|
||||
document.onmouseup = () => {
|
||||
|
||||
@@ -13,6 +13,8 @@ export default createStore({
|
||||
apiDoc: {},
|
||||
// 全局参数
|
||||
globalParam: [],
|
||||
// 左侧菜单栏宽度
|
||||
leftAsideWidth: 0,
|
||||
|
||||
// swagger原始文档
|
||||
swaggerDoc: {},
|
||||
@@ -36,7 +38,13 @@ export default createStore({
|
||||
getters: {
|
||||
getDocChangedNum: (state) => () => {
|
||||
return state.docChangedNum;
|
||||
}
|
||||
},
|
||||
getLeftAsideWidth: (state) => () => {
|
||||
return state.leftAsideWidth;
|
||||
},
|
||||
getApiDoc: (state) => () => {
|
||||
return state.apiDoc;
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
setUserInfo(state, userInfo) {
|
||||
@@ -51,6 +59,9 @@ export default createStore({
|
||||
addDocChangedNum(state) {
|
||||
state.docChangedNum++;
|
||||
},
|
||||
setLeftAsideWidth(state, leftAsideWidth) {
|
||||
state.leftAsideWidth = leftAsideWidth;
|
||||
},
|
||||
// swagger
|
||||
setSwaggerDoc(state, swaggerDoc) {
|
||||
state.swaggerDoc = swaggerDoc;
|
||||
|
||||
@@ -90,9 +90,9 @@
|
||||
</template>
|
||||
</a-form-item>
|
||||
<a-form-item label="文档内容" required name="jsonContent" v-else-if="docEdit.docType === 2">
|
||||
<!-- 尝试使用ace编辑器,但感觉除了卡顿意义不大,不会在这里编辑json内容,暂时用textarea代替-->
|
||||
<!-- <ace-editor v-model:value="docEdit.jsonContent" lang="json" theme="monokai" width="100%" height="100" :options="aceEditorConfig"></ace-editor>-->
|
||||
<a-textarea placeholder="请输入JSON格式的Swagger文档内容" v-model:value="docEdit.jsonContent" :auto-size="{ minRows: 5, maxRows: 10 }"></a-textarea>
|
||||
<!-- textarea在内容很多的时候(>300KB)会卡顿,ace不会-->
|
||||
<ace-editor v-model:value="docEdit.jsonContent" lang="json" theme="monokai" width="100%" height="100" :options="aceEditorConfig"></ace-editor>
|
||||
<!-- <a-textarea placeholder="请输入JSON格式的Swagger文档内容" v-model:value="docEdit.jsonContent" :auto-size="{ minRows: 5, maxRows: 10 }"></a-textarea>-->
|
||||
<template #extra>
|
||||
查看文档内容
|
||||
<a-popover title="文档内容说明">
|
||||
@@ -119,8 +119,8 @@
|
||||
</template>
|
||||
</a-form-item>
|
||||
<a-form-item label="文档内容" required name="jsonContent" v-else-if="docEdit.docType === 4">
|
||||
<!-- <ace-editor v-model:value="docEdit.jsonContent" lang="json" theme="monokai" width="100%" height="100" :options="aceEditorConfig"></ace-editor>-->
|
||||
<a-textarea placeholder="请输入JSON格式的OpenApi文档内容" v-model:value="docEdit.jsonContent" :auto-size="{ minRows: 5, maxRows: 10 }"></a-textarea>
|
||||
<ace-editor v-model:value="docEdit.jsonContent" lang="json" theme="monokai" width="100%" height="100" :options="aceEditorConfig"></ace-editor>
|
||||
<!-- <a-textarea placeholder="请输入JSON格式的OpenApi文档内容" v-model:value="docEdit.jsonContent" :auto-size="{ minRows: 5, maxRows: 10 }"></a-textarea>-->
|
||||
<template #extra>
|
||||
查看文档内容
|
||||
<a-popover title="文档内容说明">
|
||||
@@ -150,10 +150,10 @@
|
||||
<a-form-item label="开放访问" required name="openVisit">
|
||||
<a-radio-group v-model:value="docEdit.openVisit">
|
||||
<a-radio :value="0">否</a-radio>
|
||||
<a-radio :value="1">开放</a-radio>
|
||||
<a-radio :value="1">开放访问</a-radio>
|
||||
</a-radio-group>
|
||||
<template #extra>
|
||||
开放访问后无需登录即可通过<a @click="openShareViewWindow(docEdit)">开放文档URL</a>访问改文档信息
|
||||
开放访问后无需登录即可通过<a @click="openShareViewWindow(docEdit)">开放文档URL</a>访问该文档信息
|
||||
</template>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态" required name="docStatus">
|
||||
@@ -240,17 +240,16 @@
|
||||
});
|
||||
};
|
||||
const deleteDoc = async (row) => updateDoc(row.id, null, 0);
|
||||
|
||||
// 打开开放文档新窗口
|
||||
const openShareViewWindow = record => {
|
||||
if (!record.shareUuid) {
|
||||
message.warning('请先保存文档后再试');
|
||||
} else if (record.openVisit === 1) {
|
||||
window.open(getZyplayerApiBaseUrl() + '/doc-api#/share/home?uuid=' + record.shareUuid);
|
||||
} else if (record.openVisit !== 1) {
|
||||
message.warning('该文档尚未开启开放访问功能,请在编辑页选择开放后再试');
|
||||
} else {
|
||||
message.warning('改文档尚未开启开放访问功能,请在编辑页选择开放后再试');
|
||||
window.open(getZyplayerApiBaseUrl() + '/doc-api#/share/home?uuid=' + record.shareUuid);
|
||||
}
|
||||
};
|
||||
|
||||
const handleActionMenuClick = (item, record) => {
|
||||
if (item.key === 'shareView') {
|
||||
openShareViewWindow(record);
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<mavon-editor ref="mavonEditor" v-model="shareInstruction" :toolbars="toolbars"
|
||||
:externalLink="false"
|
||||
:externalLink="false" @imgAdd="addMarkdownImage" :imageFilter="imageFilter"
|
||||
style="height: 100%;"
|
||||
placeholder="请录入文档内容"/>
|
||||
placeholder="请录入开放文档说明"/>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
}
|
||||
zyplayerApi.apiDocUpdate({id: docEdit.value.id, shareInstruction: shareInstruction.value}).then(res => {
|
||||
editShareInstructionVisible.value = false;
|
||||
message.success('保存成功!');
|
||||
});
|
||||
};
|
||||
const editDoc = async (id) => {
|
||||
@@ -48,6 +49,12 @@
|
||||
shareInstruction.value = res.data.shareInstruction;
|
||||
});
|
||||
};
|
||||
const addMarkdownImage = (pos, file) => {
|
||||
};
|
||||
const imageFilter = (pos, file) => {
|
||||
message.error('暂不支持图片上传');
|
||||
return false;
|
||||
};
|
||||
onMounted(() => {
|
||||
});
|
||||
return {
|
||||
@@ -56,6 +63,8 @@
|
||||
shareInstruction,
|
||||
handleNewDocOk,
|
||||
editDoc,
|
||||
imageFilter,
|
||||
addMarkdownImage,
|
||||
toolbars: {
|
||||
bold: true, // 粗体
|
||||
italic: true, // 斜体
|
||||
@@ -69,7 +78,7 @@
|
||||
ol: true, // 有序列表
|
||||
ul: true, // 无序列表
|
||||
link: true, // 链接
|
||||
imagelink: true, // 图片链接
|
||||
imagelink: false, // 图片链接
|
||||
code: true, // code
|
||||
table: true, // 表格
|
||||
fullscreen: true, // 全屏编辑
|
||||
|
||||
@@ -1,34 +1,70 @@
|
||||
<template>
|
||||
<div v-if="apiDoc.shareInstruction">
|
||||
<div class="markdown-body" v-html="markdownToHtml(apiDoc.shareInstruction)" style="margin: 0 auto;max-width: 1000px;"></div>
|
||||
<a-row>
|
||||
<a-col :xs="0" :sm="4" :md="4" :lg="6" :xl="6" v-if="navigationList.length > 0">
|
||||
<Navigation ref="navigationRef" :heading="navigationList"></Navigation>
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="navigationList.length > 0?20:24" :md="navigationList.length > 0?20:24" :lg="navigationList.length > 0?18:24" :xl="navigationList.length > 0?18:24">
|
||||
<div class="markdown-body share-instruction" v-html="markdownToHtml(apiDoc.shareInstruction)" style="margin: 0 auto;max-width: 1000px;"></div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<div v-else style="text-align: center;">欢迎访问开放API文档</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {toRefs, ref, reactive, onMounted, watch, computed} from 'vue';
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
import {computed, onMounted, ref, watch} from 'vue';
|
||||
import {useStore} from 'vuex';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
|
||||
import {markdownIt} from 'mavon-editor'
|
||||
import 'mavon-editor/dist/markdown/github-markdown.min.css'
|
||||
import 'mavon-editor/dist/css/index.css'
|
||||
import swaggerAnalysis from "../../assets/core/SwaggerAnalysis";
|
||||
import Navigation from './components/Navigation.vue'
|
||||
|
||||
export default {
|
||||
components: {Navigation},
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const apiDoc = computed(() => store.state.apiDoc);
|
||||
onMounted(() => {
|
||||
let navigationRef = ref();
|
||||
watch(store.getters.getApiDoc, () => {
|
||||
setTimeout(() => {
|
||||
createNavigationHeading('.share-instruction');
|
||||
}, 100);
|
||||
});
|
||||
const markdownToHtml = desc => {
|
||||
return markdownIt.render(desc || '');
|
||||
}
|
||||
let navigationList = ref([]);
|
||||
const createNavigationHeading = (domClass) => {
|
||||
if (!document.querySelector(domClass)) {
|
||||
return [];
|
||||
}
|
||||
let headNodeArr = document.querySelector(domClass).querySelectorAll('h1,h2,h3,h4,h5,h6');
|
||||
if (headNodeArr.length <= 0) {
|
||||
return [];
|
||||
}
|
||||
let headArr = [];
|
||||
headNodeArr.forEach(node => {
|
||||
let text = node.innerHTML.replace(/^\s+/g, '').replace(/\s+$/g, '').replace(/<\/?[^>]+(>|$)/g, '');
|
||||
headArr.push({
|
||||
node: node,
|
||||
level: parseInt(node.tagName.replace(/[h]/i, ''), 10),
|
||||
text: text
|
||||
});
|
||||
});
|
||||
navigationList.value = headArr;
|
||||
};
|
||||
onMounted(() => {
|
||||
});
|
||||
return {
|
||||
apiDoc,
|
||||
navigationRef,
|
||||
navigationList,
|
||||
markdownToHtml,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.share-instruction{padding: 0 10px;}
|
||||
</style>
|
||||
|
||||
106
zyplayer-doc-ui/api-ui/src/views/share/components/Navigation.vue
Normal file
106
zyplayer-doc-ui/api-ui/src/views/share/components/Navigation.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<div class="navigation">
|
||||
<div ref="navigationRef" style="display: inline-block;width: 100%;height: 1px;"></div>
|
||||
<div class="navigation-heading" :style="{width: navigationWidth}">
|
||||
<div v-for="item in heading" :class="'heading-item heading-'+item.level" @click="headingItemClick(item)">
|
||||
{{item.text}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {computed, onMounted, ref, watch} from 'vue';
|
||||
import {useStore} from 'vuex';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
heading: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore();
|
||||
let navigationWidth = ref('100px');
|
||||
onMounted(() => {
|
||||
window.onresize = () => {
|
||||
computeNavigationWidth();
|
||||
}
|
||||
setTimeout(() => {
|
||||
computeNavigationWidth();
|
||||
}, 100);
|
||||
});
|
||||
watch(store.getters.getLeftAsideWidth, () => {
|
||||
computeNavigationWidth();
|
||||
});
|
||||
let navigationRef = ref();
|
||||
const computeNavigationWidth = () => {
|
||||
navigationWidth.value = window.getComputedStyle(navigationRef.value, null).width;
|
||||
};
|
||||
// 滚动到指定节点
|
||||
const headingItemClick = (item) => {
|
||||
item.node.scrollIntoView({behavior: "smooth", block: "start", inline: "nearest"});
|
||||
};
|
||||
return {
|
||||
navigationRef,
|
||||
navigationWidth,
|
||||
headingItemClick,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.navigation {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.navigation-heading {
|
||||
position: fixed;
|
||||
z-index: 4;
|
||||
top: 150px;
|
||||
height: calc(100vh - 250px);
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
padding-left: 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.navigation-heading .heading-item {
|
||||
padding: 5px 0;
|
||||
cursor: pointer;
|
||||
color: #646a73;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.navigation-heading .heading-item:hover {
|
||||
color: #3370ff;
|
||||
}
|
||||
|
||||
.navigation-heading .heading-1 {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.navigation-heading .heading-2 {
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.navigation-heading .heading-3 {
|
||||
padding-left: 32px;
|
||||
}
|
||||
|
||||
.navigation-heading .heading-4 {
|
||||
padding-left: 48px;
|
||||
}
|
||||
|
||||
.navigation-heading .heading-5 {
|
||||
padding-left: 64px;
|
||||
}
|
||||
|
||||
.navigation-heading .heading-6 {
|
||||
padding-left: 80px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user