swagger文档树展示开发
This commit is contained in:
@@ -27,6 +27,7 @@ public class SwaggerDocServiceImpl extends ServiceImpl<SwaggerDocMapper, Swagger
|
||||
queryWrapper.eq(swaggerDoc.getDocType() != null, "doc_type", swaggerDoc.getDocType());
|
||||
queryWrapper.eq(swaggerDoc.getOpenVisit() != null, "open_visit", swaggerDoc.getOpenVisit());
|
||||
queryWrapper.eq(swaggerDoc.getDocStatus() != null, "doc_status", swaggerDoc.getDocStatus());
|
||||
queryWrapper.orderByAsc("id");
|
||||
return this.list(queryWrapper);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,15 +80,20 @@ public class SwaggerDocumentController {
|
||||
if (resourceList == null || resourceList.isEmpty()) {
|
||||
return DocResponseJson.warn("该地址未找到文档");
|
||||
}
|
||||
// 删除原有文档
|
||||
if (swaggerDoc.getId() != null) {
|
||||
swaggerDocService.removeById(swaggerDoc.getId());
|
||||
}
|
||||
// 存明细地址
|
||||
String swaggerDomain = SwaggerDocUtil.getSwaggerResourceDomain(docUrl);
|
||||
for (SwaggerResource resource : resourceList) {
|
||||
swaggerDoc.setId(null);
|
||||
swaggerDoc.setDocUrl(resource.getUrl());
|
||||
swaggerDoc.setDocUrl(swaggerDomain + resource.getUrl());
|
||||
swaggerDoc.setName(resource.getName());
|
||||
swaggerDocService.save(swaggerDoc);
|
||||
}
|
||||
} else if (SwaggerDocUtil.isSwaggerLocation(docUrl)) {
|
||||
swaggerDocService.save(swaggerDoc);
|
||||
swaggerDocService.saveOrUpdate(swaggerDoc);
|
||||
} else {
|
||||
return DocResponseJson.warn("不支持的地址:" + docUrl);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,33 @@
|
||||
package com.zyplayer.doc.swaggerplus.controller;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.gson.Gson;
|
||||
import com.zyplayer.doc.core.annotation.AuthMan;
|
||||
import com.zyplayer.doc.core.exception.ConfirmException;
|
||||
import com.zyplayer.doc.core.json.DocResponseJson;
|
||||
import com.zyplayer.doc.core.json.ResponseJson;
|
||||
import com.zyplayer.doc.data.repository.manage.entity.SwaggerDoc;
|
||||
import com.zyplayer.doc.data.service.manage.SwaggerDocService;
|
||||
import com.zyplayer.doc.swaggerplus.service.SwaggerHttpRequestService;
|
||||
import io.swagger.models.Swagger;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import springfox.documentation.spring.web.json.Json;
|
||||
import springfox.documentation.swagger.web.ApiKeyVehicle;
|
||||
import springfox.documentation.swagger.web.SecurityConfiguration;
|
||||
import springfox.documentation.swagger.web.SwaggerResource;
|
||||
import springfox.documentation.swagger.web.UiConfiguration;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.*;
|
||||
|
||||
import static org.springframework.util.MimeTypeUtils.APPLICATION_JSON_VALUE;
|
||||
|
||||
/**
|
||||
* 承接了所有的ApiResourceController的接口
|
||||
@@ -29,21 +39,39 @@ import java.util.Set;
|
||||
@RestController
|
||||
public class SwaggerProxyController {
|
||||
|
||||
private static final String HAL_MEDIA_TYPE = "application/hal+json";
|
||||
|
||||
@Resource
|
||||
private SwaggerDocService swaggerDocService;
|
||||
@Resource
|
||||
private SwaggerHttpRequestService swaggerHttpRequestService;
|
||||
|
||||
@RequestMapping("/swagger-resources")
|
||||
public List<SwaggerResource> swaggerResources() {
|
||||
Set<SwaggerResource> resourceList = new HashSet<>();
|
||||
List<SwaggerResource> resourceList = new LinkedList<>();
|
||||
List<SwaggerDoc> docList = swaggerDocService.getSwaggerDocList(new SwaggerDoc());
|
||||
for (SwaggerDoc swaggerDoc : docList) {
|
||||
SwaggerResource resource = new SwaggerResource();
|
||||
resource.setUrl("/doc-swagger/doc/content?id=" + swaggerDoc.getId());
|
||||
resource.setUrl("/v2/api-docs?id=" + swaggerDoc.getId());
|
||||
resource.setName(swaggerDoc.getName());
|
||||
resource.setSwaggerVersion("2.0");
|
||||
resourceList.add(resource);
|
||||
}
|
||||
return new LinkedList<>(resourceList);
|
||||
return resourceList;
|
||||
}
|
||||
|
||||
@ResponseBody
|
||||
@RequestMapping(value = "/v2/api-docs", produces = {MimeTypeUtils.APPLICATION_JSON_VALUE, HAL_MEDIA_TYPE})
|
||||
public ResponseEntity<Json> content(HttpServletRequest request, Long id) {
|
||||
SwaggerDoc swaggerDoc = swaggerDocService.getById(id);
|
||||
if (swaggerDoc == null) {
|
||||
throw new ConfirmException("文档不存在");
|
||||
}
|
||||
if (Objects.equals(swaggerDoc.getDocType(), 1)) {
|
||||
String contentStr = swaggerHttpRequestService.requestUrl(request, swaggerDoc.getDocUrl());
|
||||
return new ResponseEntity<>(new Json(contentStr), HttpStatus.OK);
|
||||
}
|
||||
return new ResponseEntity<>(new Json(swaggerDoc.getJsonContent()), HttpStatus.OK);
|
||||
}
|
||||
|
||||
@ResponseBody
|
||||
|
||||
@@ -14,6 +14,14 @@ public class SwaggerDocUtil {
|
||||
return docUrl.contains("/swagger-resources");
|
||||
}
|
||||
|
||||
public static String getSwaggerResourceDomain(String docUrl) {
|
||||
int index = docUrl.indexOf("/swagger-resources");
|
||||
if (index >= 0) {
|
||||
return docUrl.substring(0, index);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static boolean isSwaggerLocation(String docUrl) {
|
||||
return docUrl.contains("/v2/api-docs");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import apiClient from './request/custom.js'
|
||||
|
||||
export const customApi = {
|
||||
get: (url, data) => apiClient({url: url, method: 'get', data: data}),
|
||||
post: (url, data) => apiClient({url: url, method: 'post', data: data}),
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Axios from 'axios'
|
||||
import interceptors from './interceptors'
|
||||
import interceptors from './interceptorsCustom'
|
||||
import {getCustomApiBaseUrl} from "./utils";
|
||||
|
||||
const apiClient = Axios.create({
|
||||
|
||||
@@ -5,7 +5,7 @@ import {getZyplayerApiBaseUrl} from "./utils";
|
||||
// 增加不需要验证结果的标记
|
||||
const noValidate = {
|
||||
"./swagger-resources": true,
|
||||
"/zyplayer-doc-db/datasource/test": true,
|
||||
"/v2/api-docs": true,
|
||||
};
|
||||
|
||||
export default function (axios) {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import qs from 'qs'
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
export default function (axios) {
|
||||
axios.interceptors.request.use(
|
||||
config => {
|
||||
if (config.method === 'get') {
|
||||
config.params = config.params || {};
|
||||
config.params = {...config.params, _: (new Date()).getTime()}
|
||||
} else if (config.method === 'post') {
|
||||
config.data = config.data || {};
|
||||
if (config.data instanceof FormData) {
|
||||
// 表单,无需特殊处理
|
||||
} else if (config.data instanceof Object) {
|
||||
config.data = qs.stringify(config.data);
|
||||
}
|
||||
}
|
||||
return config;
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
axios.interceptors.response.use(
|
||||
response => {
|
||||
if (!!response.message) {
|
||||
vue.$message.error('请求错误:' + response.message);
|
||||
} else {
|
||||
return response.data;
|
||||
}
|
||||
return Promise.reject('请求错误');
|
||||
},
|
||||
error => {
|
||||
console.log('err' + error);
|
||||
message.error('请求错误:' + error.message);
|
||||
return Promise.reject(error)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<a-layout-sider theme="light" :trigger="null" collapsible v-model:collapsed="appMenuCollapsed" :width="rightAsideWidth" style="height: 100vh;overflow: auto;">
|
||||
<div class="logo">
|
||||
<img src="../../assets/logo.png">
|
||||
<h1>swagger文档管理</h1>
|
||||
<h1>Swagger文档管理</h1>
|
||||
</div>
|
||||
<menu-layout :collapsed="appMenuCollapsed"></menu-layout>
|
||||
</a-layout-sider>
|
||||
@@ -13,13 +13,10 @@
|
||||
<a-layout>
|
||||
<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="60px">
|
||||
<a-col flex="auto">
|
||||
<MenuUnfoldOutlined class="trigger" v-if="appMenuCollapsed" @click="appMenuCollapsed = !appMenuCollapsed"/>
|
||||
<MenuFoldOutlined class="trigger" v-else @click="appMenuCollapsed = !appMenuCollapsed"/>
|
||||
</a-col>
|
||||
<a-col flex="auto" style="text-align: center;">
|
||||
<span v-if="initialEnv === 'newGray'" class="initial-env">当前环境:灰度</span>
|
||||
</a-col>
|
||||
<a-col flex="400px" style="text-align: right;padding-right: 20px;">
|
||||
<header-avatar></header-avatar>
|
||||
</a-col>
|
||||
@@ -46,14 +43,10 @@
|
||||
return {
|
||||
minHeight: minHeight + 'px',
|
||||
appMenuCollapsed: false,
|
||||
rightAsideWidth: 250
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
initialEnv () {
|
||||
return this.$store.state.initialEnv;
|
||||
rightAsideWidth: 300
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
mounted() {
|
||||
this.dragChangeRightAsideWidth();
|
||||
},
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<template v-if="menuItem.meta">
|
||||
<DashboardOutlined v-if="menuItem.meta.icon === 'DashboardOutlined'"/>
|
||||
<FileTextOutlined v-if="menuItem.meta.icon === 'FileTextOutlined'"/>
|
||||
<InfoCircleOutlined v-if="menuItem.meta.icon === 'InfoCircleOutlined'"/>
|
||||
</template>
|
||||
<span>{{menuItem.name}}</span>
|
||||
</router-link>
|
||||
@@ -30,7 +31,8 @@
|
||||
SettingOutlined,
|
||||
CarryOutOutlined,
|
||||
FileTextOutlined,
|
||||
DashboardOutlined
|
||||
DashboardOutlined,
|
||||
InfoCircleOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
|
||||
export default {
|
||||
@@ -43,7 +45,7 @@
|
||||
},
|
||||
components: {
|
||||
StarOutlined, SettingOutlined, CarryOutOutlined, FileTextOutlined,
|
||||
DashboardOutlined
|
||||
DashboardOutlined, InfoCircleOutlined
|
||||
},
|
||||
methods: {
|
||||
haveShowChildren(children) {
|
||||
|
||||
@@ -4,13 +4,27 @@
|
||||
<menu-children-layout :menuItem="menuItem" v-for="menuItem in menuData"></menu-children-layout>
|
||||
</a-menu>
|
||||
<a-divider style="margin: 6px 0;"/>
|
||||
<div v-show="!collapsed">
|
||||
<div v-show="!collapsed" class="doc-tree">
|
||||
<div style="padding: 10px 5px;">
|
||||
<a-select placeholder="请选择分组" v-model:value="swaggerDocChoice" style="width: 100%;">
|
||||
<a-select placeholder="请选择分组" v-model:value="swaggerDocChoice" @change="swaggerDocChoiceChange" style="width: 100%;">
|
||||
<a-select-option :value="item.url" v-for="item in swaggerResourceList">{{item.name}}</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
<a-directory-tree :tree-data="treeData" v-model:expandedKeys="expandedKeys" @select="docChecked"></a-directory-tree>
|
||||
<a-directory-tree :showIcon="false" :tree-data="treeData" v-model:expandedKeys="expandedKeys" @select="docChecked">
|
||||
<template #title="{ title, isLeaf, method }">
|
||||
<template v-if="isLeaf">
|
||||
<a-tag color="pink" v-if="method === 'get'">get</a-tag>
|
||||
<a-tag color="red" v-else-if="method === 'post'">post</a-tag>
|
||||
<a-tag color="orange" v-else-if="method === 'put'">put</a-tag>
|
||||
<a-tag color="green" v-else-if="method === 'head'">head</a-tag>
|
||||
<a-tag color="cyan" v-else-if="method === 'patch'">patch</a-tag>
|
||||
<a-tag color="blue" v-else-if="method === 'delete'">delete</a-tag>
|
||||
<a-tag color="purple" v-else-if="method === 'options'">options</a-tag>
|
||||
<a-tag color="purple" v-else-if="method === 'trace'">trace</a-tag>
|
||||
</template>
|
||||
{{title}}
|
||||
</template>
|
||||
</a-directory-tree>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -18,6 +32,7 @@
|
||||
<script>
|
||||
import MenuChildrenLayout from './MenuChildrenLayout.vue'
|
||||
import {customApi} from '../../api'
|
||||
import {createTreeViewByTag, getTreeDataForTag} from '../../store/SwaggerDocUtil'
|
||||
|
||||
export default {
|
||||
name: 'MenuLayout',
|
||||
@@ -42,9 +57,9 @@
|
||||
title: '用户信息管理',
|
||||
key: '0-0-0',
|
||||
children: [
|
||||
{title: '/getUserInfo', key: '0-0-0-0', isLeaf: true, path: '/doc/view', query: {path: '/getUserInfo'}},
|
||||
{title: '/deleteUserInfo', key: '0-0-0-1', isLeaf: true, path: '/doc/view', query: {path: '/deleteUserInfo'}},
|
||||
{title: '/updateUserInfo', key: '0-0-0-2', isLeaf: true, path: '/doc/view', query: {path: '/updateUserInfo'}},
|
||||
{title: '/getUserInfo', key: '0-0-0-0', isLeaf: true, query: {path: '/getUserInfo'}},
|
||||
{title: '/deleteUserInfo', key: '0-0-0-1', isLeaf: true, query: {path: '/deleteUserInfo'}},
|
||||
{title: '/updateUserInfo', key: '0-0-0-2', isLeaf: true, query: {path: '/updateUserInfo'}},
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -81,22 +96,59 @@
|
||||
docChecked(val, node) {
|
||||
if (node.node.isLeaf) {
|
||||
let dataRef = node.node.dataRef;
|
||||
this.$router.push({path: dataRef.path, query: dataRef.query});
|
||||
this.$router.push({path: '/doc/view', query: dataRef.query});
|
||||
}
|
||||
},
|
||||
getSwaggerResourceList() {
|
||||
customApi.post('./swagger-resources').then(res => {
|
||||
customApi.get('./swagger-resources').then(res => {
|
||||
if (res instanceof Array) {
|
||||
this.swaggerResourceList = res || [];
|
||||
if (this.swaggerResourceList.length > 0) {
|
||||
this.swaggerDocChoice = this.swaggerResourceList[0].url;
|
||||
this.swaggerDocChoiceChange();
|
||||
}
|
||||
} else {
|
||||
this.$message.error('获取文档列表请求失败');
|
||||
}
|
||||
});
|
||||
},
|
||||
swaggerDocChoiceChange() {
|
||||
this.loadV2Doc(this.swaggerDocChoice);
|
||||
},
|
||||
loadV2Doc(url) {
|
||||
customApi.get(url).then(res => {
|
||||
let v2Doc = this.toJsonObj(res);
|
||||
if (typeof v2Doc !== 'object') {
|
||||
this.$message.error('获取文档数据请求失败');
|
||||
}
|
||||
this.$store.commit('setSwaggerDoc', v2Doc);
|
||||
let metaInfo = {url};
|
||||
let treeData = createTreeViewByTag(v2Doc);
|
||||
this.treeData = getTreeDataForTag(v2Doc, treeData.pathIndex, metaInfo);
|
||||
this.$store.commit('setSwaggerTreePathMap', treeData.treePathDataMap);
|
||||
});
|
||||
},
|
||||
toJsonObj(value) {
|
||||
if (typeof value !== 'string') {
|
||||
return value;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (e) {
|
||||
try {
|
||||
return eval('(' + value + ')');// 处理变态的单双引号共存字符串
|
||||
} catch (e) {
|
||||
return value || undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.doc-tree{padding: 10px 4px;}
|
||||
.doc-tree .ant-tree-switcher{width: 15px;}
|
||||
.doc-tree .ant-tree-switcher-noop{width: 0;}
|
||||
.doc-tree .ant-tag{margin-right: 0;}
|
||||
</style>
|
||||
|
||||
@@ -21,6 +21,14 @@ let routers = [
|
||||
},
|
||||
component: () => import('./views/common/Console.vue')
|
||||
},
|
||||
{
|
||||
path: '/doc/info',
|
||||
name: '文档信息',
|
||||
meta: {
|
||||
icon: 'InfoCircleOutlined',
|
||||
},
|
||||
component: () => import('./views/doc/DocInfo.vue')
|
||||
},
|
||||
{
|
||||
path: '/doc/manage',
|
||||
name: '文档管理',
|
||||
|
||||
144
zyplayer-doc-ui/swagger-ui/src/store/SwaggerDocUtil.js
Normal file
144
zyplayer-doc-ui/swagger-ui/src/store/SwaggerDocUtil.js
Normal file
@@ -0,0 +1,144 @@
|
||||
export function getDefinitions(definitions) {
|
||||
if (!definitions) {
|
||||
return {};
|
||||
}
|
||||
let swaggerDefinitions = {};
|
||||
Object.keys(definitions).forEach((key) => {
|
||||
swaggerDefinitions["#/definitions/" + key] = definitions[key];
|
||||
});
|
||||
return swaggerDefinitions;
|
||||
}
|
||||
|
||||
export function createTreeViewByTag(swagger, keywords) {
|
||||
let pathIndex = {}, treePathDataMap = {};
|
||||
let paths = swagger.paths;
|
||||
let domain = swagger.domainUrl;// 服务器代理会返回此属性
|
||||
let rewriteDomainUrl = swagger.rewriteDomainUrl;// 服务器代理会返回此属性
|
||||
if (!paths) {
|
||||
return;
|
||||
}
|
||||
if (!domain) {
|
||||
domain = "http://" + swagger.host + swagger.basePath;
|
||||
}
|
||||
if (domain.substring(domain.length - 1) === "/") {
|
||||
domain = domain.substring(0, domain.length - 1);
|
||||
}
|
||||
//console.log(paths);
|
||||
Object.keys(paths).forEach(key => {
|
||||
//console.log(key, paths[key]);
|
||||
setRequestMethodForTag(rewriteDomainUrl, domain, paths[key], pathIndex, key, "get", treePathDataMap);
|
||||
setRequestMethodForTag(rewriteDomainUrl, domain, paths[key], pathIndex, key, "head", treePathDataMap);
|
||||
setRequestMethodForTag(rewriteDomainUrl, domain, paths[key], pathIndex, key, "post", treePathDataMap);
|
||||
setRequestMethodForTag(rewriteDomainUrl, domain, paths[key], pathIndex, key, "put", treePathDataMap);
|
||||
setRequestMethodForTag(rewriteDomainUrl, domain, paths[key], pathIndex, key, "patch", treePathDataMap);
|
||||
setRequestMethodForTag(rewriteDomainUrl, domain, paths[key], pathIndex, key, "delete", treePathDataMap);
|
||||
setRequestMethodForTag(rewriteDomainUrl, domain, paths[key], pathIndex, key, "options", treePathDataMap);
|
||||
setRequestMethodForTag(rewriteDomainUrl, domain, paths[key], pathIndex, key, "trace", treePathDataMap);
|
||||
});
|
||||
// console.log(pathIndex);
|
||||
// console.log(treePathDataMap);
|
||||
// console.log(treeData);
|
||||
return {pathIndex: pathIndex, treePathDataMap: treePathDataMap};
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置对象的各种请求方式,存在则复制
|
||||
* @param source 资源,原始json的paths的指定对象
|
||||
* @param pathObj 当前的待赋值对象
|
||||
* @param url url绝对路径
|
||||
* @param method 请求方式,post、get...
|
||||
* @returns
|
||||
*/
|
||||
function setRequestMethodForTag(rewriteDomainUrl, domain, source, pathObj, url, method, treePathDataMap) {
|
||||
if (!source[method] || !source[method].tags) {
|
||||
return;
|
||||
}
|
||||
source[method].tags.forEach(function(val, index) {
|
||||
let tempObj = pathObj[val];
|
||||
if(!tempObj) {
|
||||
tempObj = pathObj[val] = {};
|
||||
}
|
||||
let tempUrlObj = tempObj[url];
|
||||
if(!tempUrlObj) {
|
||||
tempUrlObj = tempObj[url] = {};
|
||||
}
|
||||
let tempPath = url + "." + method;
|
||||
tempUrlObj[method] = source[method];
|
||||
tempUrlObj[method].path = tempPath;
|
||||
tempUrlObj[method].url = url;
|
||||
tempUrlObj[method].method = method;
|
||||
tempUrlObj[method].domain = domain;
|
||||
tempUrlObj[method].rewriteDomainUrl = rewriteDomainUrl;
|
||||
treePathDataMap[tempPath] = source[method];
|
||||
});
|
||||
}
|
||||
|
||||
export function getTreeDataForTag(swagger, pathData, metaInfo) {
|
||||
return [
|
||||
{
|
||||
title: swagger.title || 'Swagger接口文档',
|
||||
key: '0-0',
|
||||
children: getTreeHtmlForTag(pathData, 0, metaInfo)
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
function getTreeHtmlForTag(pathData, treeId, metaInfo) {
|
||||
let treeData = [];
|
||||
let indexNow = 1;
|
||||
// get, head, post, put, patch, delete, options, trace
|
||||
let actionArrays = ["get", "head", "post", "put", "patch", "delete", "options", "trace"];
|
||||
Object.keys(pathData).forEach(key => {
|
||||
let tempNode = pathData[key];
|
||||
let tempTreeId = treeId + "_" + indexNow;
|
||||
// 只有一个子元素,而且有method元素,说明是只有一个节点
|
||||
let nodeSub = getObjectFirstAttributeIfOnly(tempNode);
|
||||
if (nodeSub && nodeSub.method) {
|
||||
nodeSub.treeId = tempTreeId;
|
||||
let title = nodeSub.summary || nodeSub.path;
|
||||
treeData.push({
|
||||
title: title,
|
||||
key: tempTreeId,
|
||||
isLeaf: true,
|
||||
method: nodeSub.method,
|
||||
query: {
|
||||
path: nodeSub.path,
|
||||
...metaInfo
|
||||
}
|
||||
});
|
||||
} else if (actionArrays.indexOf(key) >= 0) {
|
||||
tempNode.treeId = tempTreeId;
|
||||
let title = tempNode.summary || tempNode.path;
|
||||
treeData.push({
|
||||
title: title,
|
||||
key: tempTreeId,
|
||||
isLeaf: true,
|
||||
method: tempNode.method,
|
||||
query: {
|
||||
path: tempNode.path,
|
||||
...metaInfo
|
||||
}
|
||||
});
|
||||
} else {
|
||||
treeData.push({title: key, key: key, children: getTreeHtmlForTag(tempNode, tempTreeId, metaInfo)});
|
||||
}
|
||||
indexNow++;
|
||||
});
|
||||
return treeData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果对象只有一个属性则返回第一个属性,否则返回null
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
function getObjectFirstAttributeIfOnly(data) {
|
||||
let len = 0, value = "";
|
||||
for (let key in data) {
|
||||
if (++len > 1) {
|
||||
return undefined;
|
||||
}
|
||||
value = data[key];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import {createStore} from 'vuex'
|
||||
import {getDefinitions, createTreeViewByTag} from './SwaggerDocUtil'
|
||||
|
||||
export default createStore({
|
||||
state() {
|
||||
@@ -16,12 +17,22 @@ export default createStore({
|
||||
name: '修改用户信息'
|
||||
},
|
||||
},
|
||||
swaggerDoc: {},
|
||||
swaggerDefinitions: {},
|
||||
swaggerTreePathMap: [],
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
setUserInfo(state, userInfo) {
|
||||
state.userInfo = userInfo;
|
||||
},
|
||||
setSwaggerDoc(state, swaggerDoc) {
|
||||
state.swaggerDoc = swaggerDoc;
|
||||
state.swaggerDefinitions = getDefinitions(swaggerDoc.definitions);
|
||||
},
|
||||
setSwaggerTreePathMap(state, swaggerTreePathMap) {
|
||||
state.swaggerTreePathMap = swaggerTreePathMap;
|
||||
},
|
||||
addTableName(state, item) {
|
||||
let sameObj = Object.assign({}, state.pageTabNameMap);
|
||||
sameObj[item.key] = item.val;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
控制台
|
||||
控制台{{swaggerDoc.info}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -11,7 +11,11 @@
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {},
|
||||
computed: {
|
||||
swaggerDoc () {
|
||||
return this.$store.state.swaggerDoc;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {}
|
||||
|
||||
40
zyplayer-doc-ui/swagger-ui/src/views/doc/DocInfo.vue
Normal file
40
zyplayer-doc-ui/swagger-ui/src/views/doc/DocInfo.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<a-card style="width: 600px">
|
||||
<a-form :label-col="{span: 4}" :wrapper-col="{span: 20}">
|
||||
<a-form-item label="标题">{{swaggerDocInfo.title}}</a-form-item>
|
||||
<a-form-item label="版本">{{swaggerDocInfo.version}}</a-form-item>
|
||||
<a-form-item label="作者" v-if="swaggerDocInfo.contact">
|
||||
{{swaggerDocInfo.contact.name}} {{swaggerDocInfo.contact.email}}
|
||||
<a :href="swaggerDocInfo.contact.url" target="_blank" v-if="swaggerDocInfo.contact.url">{{swaggerDocInfo.contact.url}}</a>
|
||||
</a-form-item>
|
||||
<a-form-item label="host">{{swaggerDoc.host}}</a-form-item>
|
||||
<a-form-item label="许可证" v-if="swaggerDocInfo.license">
|
||||
<a :href="swaggerDocInfo.license.url" target="_blank">{{swaggerDocInfo.license.name}}</a>
|
||||
</a-form-item>
|
||||
<a-form-item label="文档说明">
|
||||
<span v-html="swaggerDocInfo.description"></span>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'docInfo',
|
||||
components: {},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
swaggerDoc () {
|
||||
return this.$store.state.swaggerDoc || {};
|
||||
},
|
||||
swaggerDocInfo () {
|
||||
return this.$store.state.swaggerDoc.info || {};
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
||||
@@ -29,11 +29,13 @@
|
||||
</a-form>
|
||||
<a-table :dataSource="docList" :columns="docListColumns" size="middle"
|
||||
:loading="docListLoading"
|
||||
:scroll="{ x: 1200, y: 'calc(100vh - 340px)' }">
|
||||
:scroll="{ x: 1400, y: 'calc(100vh - 340px)' }">
|
||||
<template #bodyCell="{ column, text, record }">
|
||||
<template v-if="column.dataIndex === 'operation'">
|
||||
<a-button type="link">编辑</a-button>
|
||||
<a-button type="link" danger @click="deleteDoc(record)">删除</a-button>
|
||||
<a-button type="link" @click="editDoc(record)">编辑</a-button>
|
||||
<a-popconfirm title="确定要删除吗?" @confirm="deleteDoc(record)">
|
||||
<a-button type="link" danger>删除</a-button>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'docType'">
|
||||
<a-tag color="red" v-if="text === 1">URL添加</a-tag>
|
||||
@@ -84,7 +86,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { toRefs, ref, onMounted } from 'vue';
|
||||
import { toRefs, ref, reactive, onMounted } from 'vue';
|
||||
import {zyplayerApi} from '../../api';
|
||||
|
||||
export default {
|
||||
@@ -118,6 +120,10 @@
|
||||
docType: 1, openVisit: 0, docStatus: 1,
|
||||
};
|
||||
};
|
||||
const editDoc = (record) => {
|
||||
docEdit.value = {...record};
|
||||
newDocVisible.value = true;
|
||||
};
|
||||
const updateDoc = async (id, docStatus, yn) => {
|
||||
zyplayerApi.swaggerDocUpdate({id, docStatus, yn}).then(res => {
|
||||
searchDocList();
|
||||
@@ -138,6 +144,7 @@
|
||||
openNewDoc,
|
||||
handleNewDocOk,
|
||||
deleteDoc,
|
||||
editDoc,
|
||||
newDocRules: {
|
||||
name: [{required: true, message: '请输入文档名称', trigger: 'change'}],
|
||||
docUrl: [{required: true, message: '请输入文档地址', trigger: 'change'}],
|
||||
@@ -154,16 +161,11 @@
|
||||
}, {
|
||||
title: '文档名称',
|
||||
dataIndex: 'name',
|
||||
width: 150,
|
||||
}, {
|
||||
title: '文档类型',
|
||||
dataIndex: 'docType',
|
||||
width: 90,
|
||||
}, {
|
||||
title: '文档地址',
|
||||
dataIndex: 'docUrl',
|
||||
}, {
|
||||
title: '目标域名',
|
||||
dataIndex: 'rewriteDomain',
|
||||
}, {
|
||||
title: '开放访问',
|
||||
dataIndex: 'openVisit',
|
||||
@@ -172,6 +174,13 @@
|
||||
title: '状态',
|
||||
dataIndex: 'docStatus',
|
||||
width: 90,
|
||||
}, {
|
||||
title: '文档地址',
|
||||
dataIndex: 'docUrl',
|
||||
}, {
|
||||
title: '目标域名',
|
||||
dataIndex: 'rewriteDomain',
|
||||
width: 250,
|
||||
}, {
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
|
||||
@@ -1,33 +1,39 @@
|
||||
<template>
|
||||
<a-tabs v-model:activeKey="activePage" closable @tab-click="changePage" style="padding: 5px 10px 0;">
|
||||
<a-tab-pane tab="接口说明" key="doc"/>
|
||||
<a-tab-pane tab="在线调试" key="debug"/>
|
||||
</a-tabs>
|
||||
<a-tabs v-model:activeKey="activePage" closable @tab-click="changePage" style="padding: 5px 10px 0;">
|
||||
<a-tab-pane tab="接口说明" key="doc"/>
|
||||
<a-tab-pane tab="在线调试" key="debug"/>
|
||||
</a-tabs>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'About',
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
activePage: 'doc'
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
mounted() {
|
||||
let path = this.$route.query.path;
|
||||
let docInfo = this.$store.state.docMap[path];
|
||||
if(!docInfo) {
|
||||
this.$message.error('没有找到对应的文档');
|
||||
return;
|
||||
}
|
||||
this.$store.commit('addTableName', {key: this.$route.fullPath, val: docInfo.name});
|
||||
},
|
||||
methods: {
|
||||
changePage(){
|
||||
import {toRefs, ref, reactive, onMounted} from 'vue';
|
||||
import { useRouter } from "vue-router";
|
||||
import {useStore} from 'vuex';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
export default {
|
||||
setup() {
|
||||
const router = useRouter();
|
||||
const store = useStore()
|
||||
let activePage = ref('doc');
|
||||
onBeforeRouteUpdate(async (to, from) => {
|
||||
let path = to.query.path;
|
||||
let docInfo = store.state.swaggerTreePathMap[path];
|
||||
if (!docInfo) {
|
||||
message.error('没有找到对应的文档');
|
||||
return;
|
||||
}
|
||||
store.commit('addTableName', {key: to.fullPath, val: docInfo.summary});
|
||||
});
|
||||
const changePage = () => {
|
||||
|
||||
}
|
||||
return {
|
||||
activePage,
|
||||
changePage,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user