feat: 接口页面增加文档功能

接口页面增加文档功能
This commit is contained in:
song.tianyang 2021-02-17 11:50:25 +08:00
parent 51ca71531e
commit 7b671d3620
15 changed files with 1112 additions and 12 deletions

View File

@ -0,0 +1,48 @@
package io.metersphere.api.controller;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.ApiDocumentInfoDTO;
import io.metersphere.api.dto.ApiDocumentRequest;
import io.metersphere.api.dto.ApiDocumentSimpleInfoDTO;
import io.metersphere.api.service.ApiDefinitionService;
import io.metersphere.api.service.ApiDocumentService;
import io.metersphere.base.domain.ApiDefinition;
import io.metersphere.base.domain.ApiDefinitionWithBLOBs;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import org.python.antlr.ast.Str;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author song.tianyang
* @Date 2021/2/5 6:25 下午
* @Description
*/
@RestController
@RequestMapping(value = "/api/document")
public class ApiDocumentController {
@Resource
ApiDocumentService apiDocumentService;
@Resource
ApiDefinitionService apiDefinitionService;
@PostMapping("/selectApiSimpleInfo")
public List<ApiDocumentSimpleInfoDTO> list(@RequestBody ApiDocumentRequest request) {
List<ApiDocumentSimpleInfoDTO> returnList = apiDocumentService.findApiDocumentSimpleInfoByRequest(request);
return returnList;
}
@GetMapping("/selectApiInfoById/{id}")
public ApiDocumentInfoDTO selectApiInfoById(@PathVariable String id) {
ApiDefinitionWithBLOBs apiModel = apiDefinitionService.getBLOBs(id);
ApiDocumentInfoDTO returnDTO = apiDocumentService.conversionModelToDTO(apiModel);
return returnDTO;
}
}

View File

@ -0,0 +1,34 @@
package io.metersphere.api.dto;
import lombok.Getter;
import lombok.Setter;
/**
* @author song.tianyang
* @Date 2021/2/8 4:37 下午
* @Description
*/
@Getter
@Setter
public class ApiDocumentInfoDTO {
private String id;
private String method;
private String uri;
private String name;
private String status;
private String requestHead;
private String urlParams;
private String requestBodyParamType;
private String requestBodyFormData;
private String requestBodyStrutureData;
private String responseHead;
private String responseBody;
private String responseBodyParamType;
private String responseBodyFormData;
private String responseBodyStrutureData;
private String responseCode;
}

View File

@ -0,0 +1,19 @@
package io.metersphere.api.dto;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
/**
* @author song.tianyang
* @Date 2021/2/7 10:32 上午
* @Description
*/
@Getter
@Setter
public class ApiDocumentRequest {
private String projectId;
private List<String> moduleIds;
private String shareId;
}

View File

@ -0,0 +1,17 @@
package io.metersphere.api.dto;
import lombok.Getter;
import lombok.Setter;
/**
* 接口定义的简易信息
* @author song.tianyang
* @Date 2021/2/7 10:15 上午
* @Description
*/
@Getter
@Setter
public class ApiDocumentSimpleInfoDTO {
private String id;
private String name;
}

View File

@ -0,0 +1,222 @@
package io.metersphere.api.service;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.ApiDocumentInfoDTO;
import io.metersphere.api.dto.ApiDocumentRequest;
import io.metersphere.api.dto.ApiDocumentSimpleInfoDTO;
import io.metersphere.base.domain.ApiDefinitionWithBLOBs;
import io.metersphere.base.mapper.ext.ExtApiDocumentMapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author song.tianyang
* @Date 2021/2/7 10:37 上午
* @Description
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class ApiDocumentService {
@Resource
ExtApiDocumentMapper extApiDocumentMapper;
public List<ApiDocumentSimpleInfoDTO> findApiDocumentSimpleInfoByRequest(ApiDocumentRequest request) {
return extApiDocumentMapper.findApiDocumentSimpleInfoByRequest(request);
}
public ApiDocumentInfoDTO conversionModelToDTO(ApiDefinitionWithBLOBs apiModel) {
ApiDocumentInfoDTO apiInfoDTO = new ApiDocumentInfoDTO();
if (apiModel != null) {
apiInfoDTO.setId(apiModel.getId());
apiInfoDTO.setName(apiModel.getName());
apiInfoDTO.setMethod(apiModel.getMethod());
apiInfoDTO.setUri(apiModel.getPath());
apiInfoDTO.setStatus(apiModel.getStatus());
JSONObject requestJsonObj = JSONObject.parseObject(apiModel.getRequest());
//head赋值
if (requestJsonObj.containsKey("headers")) {
JSONArray requestHeadDataArr = new JSONArray();
//head赋值
JSONArray headArr = requestJsonObj.getJSONArray("headers");
for (int index = 0; index < headArr.size(); index++) {
JSONObject headObj = headArr.getJSONObject(index);
if (headObj.containsKey("name") && headObj.containsKey("value")) {
requestHeadDataArr.add(headObj);
}
}
apiInfoDTO.setRequestHead(requestHeadDataArr.toJSONString());
}
//url参数赋值
JSONArray urlParamArr = new JSONArray();
if (requestJsonObj.containsKey("arguments")) {
//urlParam -- query赋值
JSONArray headArr = requestJsonObj.getJSONArray("arguments");
for (int index = 0; index < headArr.size(); index++) {
JSONObject headObj = headArr.getJSONObject(index);
if (headObj.containsKey("name") && headObj.containsKey("value")) {
urlParamArr.add(headObj);
}
}
}
if (requestJsonObj.containsKey("rest")) {
//urlParam -- rest赋值
JSONArray headArr = requestJsonObj.getJSONArray("rest");
for (int index = 0; index < headArr.size(); index++) {
JSONObject headObj = headArr.getJSONObject(index);
if (headObj.containsKey("name") && headObj.containsKey("value")) {
urlParamArr.add(headObj);
}
}
}
apiInfoDTO.setUrlParams(urlParamArr.toJSONString());
//请求体参数类型
if (requestJsonObj.containsKey("body")) {
JSONObject bodyObj = requestJsonObj.getJSONObject("body");
if (bodyObj.containsKey("type")) {
String type = bodyObj.getString("type");
apiInfoDTO.setRequestBodyParamType(type);
if (StringUtils.equalsAny(type, "JSON", "XML", "Raw")) {
if (bodyObj.containsKey("raw")) {
String raw = bodyObj.getString("raw");
apiInfoDTO.setRequestBodyStrutureData(raw);
}
} else if (StringUtils.equalsAny(type, "Form Data", "WWW_FORM")) {
if (bodyObj.containsKey("kvs")) {
JSONArray bodyParamArr = new JSONArray();
JSONArray kvsArr = bodyObj.getJSONArray("kvs");
for (int i = 0; i < kvsArr.size(); i++) {
JSONObject kv = kvsArr.getJSONObject(i);
if (kv.containsKey("name")) {
bodyParamArr.add(kv);
}
}
apiInfoDTO.setRequestBodyFormData(bodyParamArr.toJSONString());
}
} else if (StringUtils.equals(type, "BINARY")) {
if (bodyObj.containsKey("binary")) {
List<Map<String, String>> bodyParamList = new ArrayList<>();
JSONArray kvsArr = bodyObj.getJSONArray("kvs");
for (int i = 0; i < kvsArr.size(); i++) {
JSONObject kv = kvsArr.getJSONObject(i);
if (kv.containsKey("description") && kv.containsKey("files")) {
Map<String, String> bodyMap = new HashMap<>();
String name = kv.getString("description");
JSONArray fileArr = kv.getJSONArray("files");
String value = "";
for (int j = 0; j < fileArr.size(); j++) {
JSONObject fileObj = fileArr.getJSONObject(j);
if (fileObj.containsKey("name")) {
value += fileObj.getString("name") + " ;";
}
}
bodyMap.put("name", name);
bodyMap.put("value", value);
bodyParamList.add(bodyMap);
}
}
apiInfoDTO.setRequestBodyFormData(JSONArray.toJSONString(bodyParamList));
}
}
}
}
JSONObject responseJsonObj = JSONObject.parseObject(apiModel.getResponse());
//赋值响应头
if (responseJsonObj.containsKey("headers")) {
JSONArray responseHeadDataArr = new JSONArray();
JSONArray headArr = responseJsonObj.getJSONArray("headers");
for (int index = 0; index < headArr.size(); index++) {
JSONObject headObj = headArr.getJSONObject(index);
if (headObj.containsKey("name") && headObj.containsKey("value")) {
responseHeadDataArr.add(headObj);
}
}
apiInfoDTO.setResponseHead(responseHeadDataArr.toJSONString());
}
// 赋值响应体
if (responseJsonObj.containsKey("body")) {
JSONObject bodyObj = responseJsonObj.getJSONObject("body");
if (bodyObj.containsKey("type")) {
String type = bodyObj.getString("type");
apiInfoDTO.setResponseBodyParamType(type);
if (StringUtils.equalsAny(type, "JSON", "XML", "Raw")) {
if (bodyObj.containsKey("raw")) {
String raw = bodyObj.getString("raw");
apiInfoDTO.setResponseBodyStrutureData(raw);
}
} else if (StringUtils.equalsAny(type, "Form Data", "WWW_FORM")) {
if (bodyObj.containsKey("kvs")) {
JSONArray bodyParamArr = new JSONArray();
JSONArray kvsArr = bodyObj.getJSONArray("kvs");
for (int i = 0; i < kvsArr.size(); i++) {
JSONObject kv = kvsArr.getJSONObject(i);
if (kv.containsKey("name")) {
bodyParamArr.add(kv);
}
}
apiInfoDTO.setResponseBodyFormData(bodyParamArr.toJSONString());
}
} else if (StringUtils.equals(type, "BINARY")) {
if (bodyObj.containsKey("binary")) {
List<Map<String, String>> bodyParamList = new ArrayList<>();
JSONArray kvsArr = bodyObj.getJSONArray("kvs");
for (int i = 0; i < kvsArr.size(); i++) {
JSONObject kv = kvsArr.getJSONObject(i);
if (kv.containsKey("description") && kv.containsKey("files")) {
Map<String, String> bodyMap = new HashMap<>();
String name = kv.getString("description");
JSONArray fileArr = kv.getJSONArray("files");
String value = "";
for (int j = 0; j < fileArr.size(); j++) {
JSONObject fileObj = fileArr.getJSONObject(j);
if (fileObj.containsKey("name")) {
value += fileObj.getString("name") + " ;";
}
}
bodyMap.put("name", name);
bodyMap.put("value", value);
bodyParamList.add(bodyMap);
}
}
apiInfoDTO.setResponseBodyFormData(JSONArray.toJSONString(bodyParamList));
}
}
}
}
// 赋值响应码
if (responseJsonObj.containsKey("statusCode")) {
JSONArray responseStatusDataArr = new JSONArray();
JSONArray statusArr = responseJsonObj.getJSONArray("statusCode");
for (int index = 0; index < statusArr.size(); index++) {
JSONObject statusObj = statusArr.getJSONObject(index);
if (statusObj.containsKey("name") && statusObj.containsKey("value")) {
responseStatusDataArr.add(statusObj);
}
}
apiInfoDTO.setResponseCode(responseStatusDataArr.toJSONString());
}
}
return apiInfoDTO;
}
}

View File

@ -0,0 +1,11 @@
package io.metersphere.base.mapper.ext;
import io.metersphere.api.dto.ApiDocumentRequest;
import io.metersphere.api.dto.ApiDocumentSimpleInfoDTO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface ExtApiDocumentMapper {
List<ApiDocumentSimpleInfoDTO> findApiDocumentSimpleInfoByRequest(@Param("request") ApiDocumentRequest request);
}

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.metersphere.base.mapper.ext.ExtApiDocumentMapper">
<select id="findApiDocumentSimpleInfoByRequest" resultType="io.metersphere.api.dto.ApiDocumentSimpleInfoDTO">
SELECT api.id,api.name FROM Api_definition api
<where>
<if test="request.projectId != null">
api.project_Id = #{request.projectId}
</if>
<if test="request.moduleIds != null and request.moduleIds.size() > 0">
AND api.module_id in
<foreach collection="request.moduleIds" item="nodeId" separator="," open="(" close=")">
#{nodeId}
</foreach>
</if>
</where>
ORDER BY api.create_time DESC
</select>
</mapper>

View File

@ -26,7 +26,7 @@
:name="item.name">
<!-- 列表集合 -->
<ms-api-list
v-if="item.type === 'list' && isApiListEnable"
v-if="item.type === 'list' && activeDom==='api' "
:module-tree="nodeTree"
:module-options="moduleOptions"
:current-protocol="currentProtocol"
@ -35,29 +35,42 @@
:select-node-ids="selectNodeIds"
:trash-enable="trashEnable"
:is-api-list-enable="isApiListEnable"
:active-dom="activeDom"
:queryDataType="queryDataType"
:selectDataRange="selectDataRange"
@changeSelectDataRangeAll="changeSelectDataRangeAll"
@editApi="editApi"
@handleCase="handleCase"
@showExecResult="showExecResult"
@activeDomChange="activeDomChange"
@isApiListEnableChange="isApiListEnableChange"
ref="apiList"/>
<!--测试用例列表-->
<api-case-simple-list
v-if="item.type === 'list' && !isApiListEnable"
v-if="item.type === 'list' && activeDom==='testCase' "
:current-protocol="currentProtocol"
:visible="visible"
:currentRow="currentRow"
:select-node-ids="selectNodeIds"
:trash-enable="trashEnable"
:is-api-list-enable="isApiListEnable"
:active-dom="activeDom"
:queryDataType="queryDataType"
@changeSelectDataRangeAll="changeSelectDataRangeAll"
@isApiListEnableChange="isApiListEnableChange"
@activeDomChange="activeDomChange"
@handleCase="handleCase"
@showExecResult="showExecResult"
ref="apiList"/>
<api-documents-page class="api-doc-page"
v-if="item.type === 'list' && activeDom==='doc' "
:is-api-list-enable="isApiListEnable"
:active-dom="activeDom"
:project-id="projectId"
:module-ids="selectNodeIds"
@activeDomChange="activeDomChange"
@isApiListEnableChange="isApiListEnableChange"
/>
<!-- 添加/编辑测试窗口-->
<div v-else-if="item.type=== 'ADD'" class="ms-api-div">
@ -132,7 +145,9 @@ import {getCurrentProjectID, getCurrentUser, getUUID} from "@/common/js/utils";
import MsApiModule from "./components/module/ApiModule";
import ApiCaseSimpleList from "./components/list/ApiCaseSimpleList";
export default {
import ApiDocumentsPage from "@/business/components/api/definition/components/list/ApiDocumentsPage";
export default {
name: "ApiDefinition",
computed: {
queryDataType: function () {
@ -141,8 +156,10 @@ export default {
this.changeRedirectParam(redirectIDParam);
if (routeParam === 'apiTestCase') {
this.isApiListEnableChange(false);
this.activeDomChange("testCase");
} else {
this.isApiListEnableChange(true);
this.activeDomChange("api");
}
return routeParam;
},
@ -162,7 +179,8 @@ export default {
MsDebugDubboPage,
MsRunTestTcpPage,
MsRunTestSqlPage,
MsRunTestDubboPage
MsRunTestDubboPage,
ApiDocumentsPage
},
props: {
visible: {
@ -193,6 +211,7 @@ export default {
closable: false
}],
isApiListEnable: true,
activeDom: "testCase",
syncTabs: [],
projectId: "",
nodeTree: []
@ -230,6 +249,9 @@ export default {
isApiListEnableChange(data) {
this.isApiListEnable = data;
},
activeDomChange(tabType){
this.activeDom = tabType;
},
addTab(tab) {
if (tab.name === 'add') {
this.handleTabsEdit(this.$t('api_test.definition.request.fast_debug'), "debug");

View File

@ -0,0 +1,119 @@
<template>
<div class="container">
<div class="wrapper">
<div class="section" style="width:80%;margin-left: 8px" v-for="(item, index) in list" :key="index">
<div class="border" style="width:100%;height:200px;font-size:30px;text-align:center;color:black;">
<el-divider><i class="el-icon-mobile-phone">111</i></el-divider>
{{item.name}}
</div>
</div>
</div>
<div id="nac" style="height:500px;"></div>
<nav style="position:fixed;right:30px;top:100px;">
<span class="nav1 hand" v-for="(item, index) in navList" :key="index" @click="jump(index)"
:class="index==0?'current':''">{{item}}</span>
</nav>
</div>
</template>
<script>
// import $ from 'jquery';
export default {
name:"MsAnchor",
data() {
return {
scroll: '',
list: [{
name: "第一条wwwwwwww",
backgroundcolor: "#90B2A3"
}, {
name: "第二条eeeeeeee",
backgroundcolor: "#A593B2"
}, {
name: "第三条",
backgroundcolor: "#A7B293"
}, {
name: "第四条",
backgroundcolor: "#0F2798"
}, {
name: "第五条",
backgroundcolor: "#0A464D"
}],
navList: [11111111, 2111111111111, 31111, 4111111, 5111111111]
}
},
methods: {
dataScroll: function () {
this.scroll = document.documentElement.scrollTop || document.body.scrollTop;
},
jump(index) {
let jump = document.getElementsByClassName('section');
//
let total = jump[index].offsetTop;
// Chrome
document.body.scrollTop = total;
// Firefox
document.documentElement.scrollTop = total;
// Safari
window.pageYOffset = total;
// $('html, body').animate({
// 'scrollTop': total
// }, 400);
},
loadSroll: function () {
// var self = this;
// var $navs = $(".nav1");
// var sections = document.getElementsByClassName('section');
// for (var i = sections.length - 1; i >= 0; i--) {
// if (self.scroll >= sections[i].offsetTop - 100) {
// $navs.eq(i).addClass("current").siblings().removeClass("current")
// break;
// }
// }
}
},
watch: {
scroll: function () {
this.loadSroll()
}
},
mounted() {
window.addEventListener('scroll', this.dataScroll);
}
}
</script>
<style>
* {
padding: 0;
margin: 0;
}
.nav1 {
display: block;
height: 40px;
text-align: center;
line-height: 40px;
background: #eee;
}
.navs1 .active {
color: #847ec3;
background-color: #e2e2e2;
}
.hand {
cursor: pointer;
}
.current {
color: #fff;
background: #847ec3;
}
.border {
border: 1px solid #eee;
border-top: 0
}
</style>

View File

@ -0,0 +1,422 @@
<template>
<el-container>
<el-main>
<div ref="apiDocInfoDiv">
<div style="margin-bottom: 50px">
<div style="font-size: 17px">
{{apiInfo.name}}
<span class="apiStatusTag">
<api-status :value="apiInfo.status"/>
</span>
</div>
<!--api请求信息-->
<el-row class="apiInfoRow">
<div class="tip">请求信息</div>
</el-row>
<el-row class="apiInfoRow">
<div class="simpleFontClass">
<el-tag size="medium"
:style="{'background-color': getColor(apiInfo.method), border: getColor(apiInfo.method),borderRadius:'0px', marginRight:'20px'}">
{{ apiInfo.method }}
</el-tag>
{{apiInfo.uri}}
</div>
</el-row>
<!--api请求头-->
<el-row class="apiInfoRow">
<div class="blackFontClass">
请求头
<div v-if="getJsonArr(apiInfo.requestHead).length==0">
</div>
<div v-else>
<el-table border :show-header="false"
:data="getJsonArr(apiInfo.requestHead)" row-key="name" class="test-content adjust-table">
<el-table-column prop="name"
label="名称"
show-overflow-tooltip/>
<el-table-column prop="value"
label="值"
show-overflow-tooltip/>
</el-table>
</div>
</div>
</el-row>
<!--URL参数-->
<el-row class="apiInfoRow">
<div class="blackFontClass">
URL参数
<div v-if="getJsonArr(apiInfo.urlParams).length==0">
</div>
<div v-else>
<el-table border
:data="getJsonArr(apiInfo.urlParams)" row-key="name" class="test-content adjust-table">
<el-table-column prop="name"
label="名称"
min-width="120px"
show-overflow-tooltip/>
<el-table-column prop="isEnable"
label="是否必填"
min-width="80px"
show-overflow-tooltip/>
<el-table-column prop="value"
label="值"
min-width="120px"
show-overflow-tooltip/>
<el-table-column prop="description"
label="描述"
min-width="450px"
show-overflow-tooltip/>
</el-table>
</div>
</div>
</el-row>
<!--api请求体 以及表格-->
<el-row class="apiInfoRow">
<div class="blackFontClass">
请求体
</div>
<div class="smallFontClass">
类型:{{apiInfo.requestBodyParamType}}
</div>
<div>
<el-table border v-if="apiInfo.requestBodyParamType=='kv'"
:data="getJsonArr(apiInfo.requestBodyFormData)" row-key="id" class="test-content adjust-table">
<el-table-column prop="name"
label="名称"
min-width="120px"
show-overflow-tooltip/>
<el-table-column prop="contentType"
label="类型"
min-width="80px"
show-overflow-tooltip/>
<el-table-column prop="description"
label="描述"
min-width="450px"
show-overflow-tooltip/>
<el-table-column label="必需"
min-width="80px"
show-overflow-tooltip>
<template v-slot:default="scope">
<div v-if="scope.enable"></div>
<div v-else-if="!scope.enable"></div>
</template>
</el-table-column>
<el-table-column prop="value"
label="默认值"
min-width="120px"
show-overflow-tooltip/>
</el-table>
</div>
</el-row>
<!--范例展示-->
<el-row class="apiInfoRow">
<div class="blackFontClass">
范例展示
</div>
<div class="showDataDiv">
<br/>
<p style="margin: 0px 20px;">
{{ apiInfo.requestBodyStrutureData }}
</p>
<br/>
</div>
</el-row>
<!--响应信息-->
<el-row class="apiInfoRow">
<div class="tip">响应信息</div>
</el-row>
<el-row class="apiInfoRow">
</el-row>
<!--响应头-->
<el-row class="apiInfoRow">
<div class="blackFontClass">
响应头:
<el-table border :show-header="false"
:data="getJsonArr(apiInfo.responseHead)" row-key="name" class="test-content adjust-table">
<el-table-column prop="name"
label="名称"
show-overflow-tooltip/>
<el-table-column prop="value"
label="值"
show-overflow-tooltip/>
</el-table>
</div>
</el-row>
<!--响应体-->
<el-row class="apiInfoRow">
<div class="blackFontClass">
响应体
</div>
<div class="smallFontClass">
类型:{{apiInfo.responseBodyParamType}}
</div>
<div>
<el-table border v-if="apiInfo.responseBodyParamType=='kv'"
:data="getJsonArr(apiInfo.responseBodyFormData)" row-key="id" class="test-content adjust-table">
<el-table-column prop="name"
label="名称"
min-width="120px"
show-overflow-tooltip/>
<el-table-column prop="contentType"
label="类型"
min-width="80px"
show-overflow-tooltip/>
<el-table-column prop="description"
label="描述"
min-width="450px"
show-overflow-tooltip/>
<el-table-column label="必需"
min-width="80px"
show-overflow-tooltip>
<template v-slot:default="scope">
<div v-if="scope.enable"></div>
<div v-else-if="!scope.enable"></div>
</template>
</el-table-column>
<el-table-column prop="value"
label="默认值"
min-width="120px"
show-overflow-tooltip/>
</el-table>
</div>
</el-row>
<!--响应状态码-->
<el-row class="apiInfoRow">
<div class="blackFontClass">
响应状态码:
<el-table border :show-header="false"
:data="getJsonArr(apiInfo.responseCode)" row-key="name" class="test-content adjust-table">
<el-table-column prop="name"
label="名称"
show-overflow-tooltip/>
<el-table-column prop="value"
label="值"
show-overflow-tooltip/>
</el-table>
</div>
</el-row>
</div>
</div>
</el-main>
<!-- 右侧列表 -->
<el-aside width="200px">
<div ref="apiDocList">
<el-steps style="height: 40%" direction="vertical" :active="apiStepIndex">
<el-step v-for="(apiInfo) in apiSimpleInfoArray" :key="apiInfo.id" @click.native="clickStep(apiInfo.id)">
<el-link slot="title">{{apiInfo.name}}</el-link>
</el-step>
</el-steps>
</div>
</el-aside>
</el-container>
</template>
<script>
import MsAnchor from "./Anchor";
import {API_METHOD_COLOUR} from "@/business/components/api/definition/model/JsonData";
import {jsonToMap} from "@/common/js/utils";
import ApiStatus from "@/business/components/api/definition/components/list/ApiStatus";
import {buildNodePath} from "@/business/components/api/definition/model/NodeTree";
export default {
name: "ApiDocumentItem",
components: {
MsAnchor,ApiStatus,
},
data() {
return {
apiStepIndex:0,
apiSimpleInfoArray:[],
apiInfo:{
method:"无",
uri:"无",
name:"无",
id:"",
requestHead:"无",
urlParams:"无",
requestBodyParamType:"无",
requestBodyFormData:'[]',
requestBodyStrutureData:"",
responseHead:"无",
responseBody:"",
responseBodyParamType:"无",
responseBodyFormData:"无",
responseBodyStrutureData:"无",
responseCode:"无",
},
methodColorMap: new Map(API_METHOD_COLOUR),
clientHeight:'',//
}
},
props: {
projectId:String,
moduleIds:Array,
},
activated() {
this.initApiDocSimpleList();
this.clientHeight = `${document.documentElement.clientHeight}`;//
let that = this;
window.onresize = function(){
this.clientHeight = `${document.documentElement.clientHeight}`;
this.changeFixed(this.clientHeight);
}
},
created: function () {
this.initApiDocSimpleList();
this.clientHeight = `${document.documentElement.clientHeight}`;//
let that = this;
window.onresize = function(){
this.clientHeight = `${document.documentElement.clientHeight}`;
this.changeFixed(this.clientHeight);
}
},
mounted() {
let that = this;
window.onresize = function(){
this.clientHeight = `${document.documentElement.clientHeight}`;
if(that.$refs.apiDocInfoDiv){
that.$refs.apiDocInfoDiv.style.minHeight = this.clientHeight - 300 + 'px';
that.$refs.apiDocList.style.minHeight = this.clientHeight - 300 + 'px';
}
}
},
computed: {
documentId: function () {
return this.$route.params.documentId;
}
},
watch: {
'$route.params.documentId'() {
},
moduleIds(){
this.initApiDocSimpleList();
},
clientHeight(){ //clientHeight
this.changeFixed(this.clientHeight);
}
},
methods: {
changeFixed(clientHeight){
if(this.$refs.apiDocInfoDiv){
this.$refs.apiDocInfoDiv.style.height = clientHeight -300 + 'px';
this.$refs.apiDocInfoDiv.style.overflow = 'auto';
this.$refs.apiDocList.style.height = clientHeight -300 + 'px';
}
},
initApiDocSimpleList(){
let simpleRequest = {};
if(this.projectId!=null && this.projectId!= ""){
simpleRequest.projectId=this.projectId;
}
if(this.documentId!=null && this.documentId!= ""){
simpleRequest.documentId=this.documentId;
}
if(this.moduleIds.length>0){
simpleRequest.moduleIds=this.moduleIds;
}
let simpleInfoUrl = "/api/document/selectApiSimpleInfo";
this.$post(simpleInfoUrl, simpleRequest, response => {
this.apiSimpleInfoArray = response.data;
this.apiStepIndex = 0;
if(this.apiSimpleInfoArray.length>0){
this.selectApiInfo(this.apiSimpleInfoArray[0].id);
}
});
},
selectApiInfo(apiId){
let simpleInfoUrl = "/api/document/selectApiInfoById/"+apiId;
this.$get(simpleInfoUrl, response => {
this.apiInfo = response.data;
});
},
clickStep(apiId){
for (let index = 0; index < this.apiSimpleInfoArray.length; index++) {
if(apiId == this.apiSimpleInfoArray[index].id){
this.apiStepIndex = index;
break;
}
}
this.selectApiInfo(apiId);
},
stepClick(stepIndex){
this.apiStepIndex = stepIndex;
},
getColor(enable, method) {
return this.methodColorMap.get(method);
},
getJsonArr(jsonString){
let returnJsonArr = [];
if(jsonString == '无'){
return returnJsonArr;
}
let jsonArr = JSON.parse(jsonString);
//
for(var index = 0;index < jsonArr.length;index++){
var item = jsonArr[index];
if(item.name!="" && item.name!=null){
returnJsonArr.push(item);
}
}
return returnJsonArr;
}
},
}
</script>
<style scoped>
.simpleFontClass{
font-size: 14px;
}
.blackFontClass{
font-weight: bold;
font-size: 14px;
}
.smallFontClass{
font-size: 13px;
margin: 20px 0px;
}
.tip {
padding: 3px 5px;
font-size: 14px;
border-radius: 4px;
border-left: 4px solid #783887;
}
.apiInfoRow{
margin: 20px 10px;
}
.apiStatusTag{
margin: 20px 5px;
}
.showDataDiv{
background-color: #F5F7F9;
margin: 20px 0px;
}
/*
步骤条中已经完成后的节点样式和里面a标签的样式
*/
/deep/ .el-step__head.is-finish {
color: #C0C4CC;
border-color: #C0C4CC;
}
/deep/ .el-step__title.is-finish /deep/ .el-link.el-link--default{
color: #C0C4CC;
}
/*
步骤条中当前节点样式和当前a标签的样式
*/
/deep/ .el-step__head.is-process {
color: #783887;
border-color: #783887;
}
/deep/ .el-step__title.is-process /deep/ .el-link.el-link--default {
color: #783887;
}
</style>

View File

@ -1,7 +1,9 @@
<template>
<div>
<api-list-container
<api-list-container-with-doc
:is-api-list-enable="isApiListEnable"
:active-dom="activeDom"
@activeDomChange="activeDomChange"
@isApiListEnableChange="isApiListEnableChange">
<el-link type="primary" style="float:right;margin-top: 5px" @click="open">{{$t('commons.adv_search.title')}}</el-link>
@ -99,7 +101,7 @@
</el-table>
<ms-table-pagination :change="initTable" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/>
</api-list-container>
</api-list-container-with-doc>
<api-case-list @showExecResult="showExecResult" @refresh="initTable" :currentApi="selectCase" ref="caseList"/>
<!--批量编辑-->
@ -130,7 +132,8 @@ import MsBatchEdit from "../basis/BatchEdit";
import {API_METHOD_COLOUR, CASE_PRIORITY, DUBBO_METHOD, REQ_METHOD, SQL_METHOD, TCP_METHOD} from "../../model/JsonData";
import {getBodyUploadFiles, getCurrentProjectID} from "@/common/js/utils";
import ApiListContainer from "./ApiListContainer";
// import ApiListContainer from "./ApiListContainer";
import ApiListContainerWithDoc from "@/business/components/api/definition/components/list/ApiListContainerWithDoc";
import PriorityTableItem from "../../../../track/common/tableItems/planview/PriorityTableItem";
import MsApiCaseTableExtendBtns from "../reference/ApiCaseTableExtendBtns";
import MsReferenceView from "../reference/ReferenceView";
@ -150,7 +153,7 @@ export default {
MsSetEnvironment,
ApiCaseList,
PriorityTableItem,
ApiListContainer,
ApiListContainerWithDoc,
MsTableOperatorButton,
MsTableOperator,
MsTablePagination,
@ -211,6 +214,7 @@ export default {
props: {
currentProtocol: String,
selectNodeIds: Array,
activeDom:String,
visible: {
type: Boolean,
default: false,
@ -270,6 +274,9 @@ export default {
isApiListEnableChange(data) {
this.$emit('isApiListEnableChange', data);
},
activeDomChange(tabType){
this.$emit("activeDomChange",tabType);
},
initTable() {
this.selectRows = new Set();
this.condition.status = "";

View File

@ -0,0 +1,60 @@
<template>
<div>
<api-list-container-with-doc
:is-api-list-enable="isApiListEnable"
:active-dom="activeDom"
@activeDomChange="activeDomChange"
@isApiListEnableChange="isApiListEnableChange">
<api-document-item :project-id="projectId" :module-ids="moduleIds"/>
</api-list-container-with-doc>
</div>
</template>
<script>
import ApiListContainerWithDoc from "@/business/components/api/definition/components/list/ApiListContainerWithDoc";
import ApiDocumentItem from "@/business/components/api/definition/components/document/ApiDocumentItem";
export default {
name: "ApiDocumentsPage",
components: {
ApiListContainerWithDoc,
ApiDocumentItem,
},
data() {
return {
}
},
props: {
projectId:String,
moduleIds:Array,
activeDom:String,
isApiListEnable: {
type: Boolean,
default: false,
},
},
created: function () {
},
watch: {
},
computed: {
},
methods: {
isApiListEnableChange(data){
this.$emit("isApiListEnableChange",data);
},
activeDomChange(data){
this.$emit("activeDomChange",data);
}
},
}
</script>
<style scoped>
</style>

View File

@ -1,7 +1,9 @@
<template>
<div>
<api-list-container
<api-list-container-with-doc
:is-api-list-enable="isApiListEnable"
:active-dom="activeDom"
@activeDomChange="activeDomChange"
@isApiListEnableChange="isApiListEnableChange">
<el-link type="primary" @click="open" style="float: right;margin-top: 5px">{{ $t('commons.adv_search.title') }}
@ -163,7 +165,7 @@
</el-table>
<ms-table-pagination :change="initTable" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/>
</api-list-container>
</api-list-container-with-doc>
<ms-api-case-list @refresh="initTable" @showExecResult="showExecResult" :currentApi="selectApi" ref="caseList"/>
<!--批量编辑-->
<ms-batch-edit ref="batchEdit" @batchEdit="batchEdit" :typeArr="typeArr" :value-arr="valueArr"/>
@ -197,6 +199,7 @@ import MsTableAdvSearchBar from "@/business/components/common/components/search/
import {API_DEFINITION_CONFIGS} from "@/business/components/common/components/search/search-components";
import MsTipButton from "@/business/components/common/components/MsTipButton";
import CaseBatchMove from "@/business/components/api/definition/components/basis/BatchMove";
import ApiListContainerWithDoc from "@/business/components/api/definition/components/list/ApiListContainerWithDoc";
import {
_filter,
_handleSelect,
@ -213,7 +216,7 @@ export default {
CaseBatchMove,
ApiStatus,
MsTableHeaderSelectPopover,
ApiListContainer,
ApiListContainerWithDoc,
MsTableButton,
MsTableOperatorButton,
MsTableOperator,
@ -291,6 +294,7 @@ export default {
currentProtocol: String,
selectNodeIds: Array,
isSelectThisWeek: String,
activeDom:String,
visible: {
type: Boolean,
default: false,
@ -351,10 +355,12 @@ export default {
handleBatchMove() {
this.$refs.testCaseBatchMove.open(this.moduleTree, [], this.moduleOptions);
},
isApiListEnableChange(data) {
this.$emit('isApiListEnableChange', data);
},
activeDomChange(tabType){
this.$emit("activeDomChange",tabType);
},
initTable() {
this.selectRows = new Set();
initCondition(this.condition);

View File

@ -0,0 +1,89 @@
<template>
<el-card class="card-content" v-if="isShow">
<el-button-group v-if="isShowChangeButton">
<el-tooltip class="item" effect="dark" :content="$t('api_test.definition.api_title')" placement="left">
<el-button plain style="width: 44px;height: 32px;padding: 5px 8px;" :class="{active: showApiList}" @click="changeTab('api')">API</el-button>
</el-tooltip>
<el-tooltip class="item" effect="dark" :content="$t('api_test.definition.case_title')" placement="top">
<el-button plain style="width: 44px;height: 32px;padding: 1px;" :class="{active: showTestCaseList}" @click="changeTab('testCase')">CASE</el-button>
</el-tooltip>
<el-tooltip class="item" effect="dark" :content="$t('api_test.definition.doc_title')" placement="right">
<el-button plain style="width: 44px;height: 32px;padding: 1px;" :class="{active: showDocList}" @click="changeTab('doc')">
{{ $t('api_test.definition.doc_title') }}</el-button>
</el-tooltip>
</el-button-group>
<template v-slot:header>
<slot name="header"></slot>
</template>
<slot></slot>
</el-card>
</template>
<script>
export default {
name: "ApiListContainerWithDoc",
data() {
return {
isShow: true,
showApiList:false,
showTestCaseList:false,
showDocList:true,
}
},
props: {
activeDom: String,
isShowChangeButton: {
type: Boolean,
default: true
}
},
created() {
this.refreshButtonActiveClass(this.activeDom);
},
methods: {
changeTab(tabType){
this.refreshButtonActiveClass(tabType);
this.$emit("activeDomChange",tabType);
},
refreshButtonActiveClass(tabType){
if(tabType === "testCase"){
this.showApiList = false;
this.showTestCaseList = true;
this.showDocList = false;
}else if(tabType === "doc"){
this.showApiList = false;
this.showTestCaseList = false;
this.showDocList = true;
}else{
this.showApiList = true;
this.showTestCaseList = false;
this.showDocList = false;
}
}
},
}
</script>
<style scoped>
.active {
border: solid 1px #6d317c;
background-color: #7C3985;
color: #FFFFFF;
}
.case-button {
border-left: solid 1px #6d317c;
}
.item{
border: solid 1px #6d317c;
}
</style>

View File

@ -48,6 +48,11 @@ export default {
name: "ApiDefinition",
component: () => import('@/business/components/api/definition/ApiDefinition'),
},
{
path: "definition/document/:documentId",
name: "ApiDefinitionDocument",
component: () => import('@/business/components/api/definition/components/document/ApiDocumentItem'),
},
{
path: "automation",
name: "ApiAutomation",