fix: 【Api配置】修复api响应参数类型展示错误,接口文档添加注释

This commit is contained in:
XieYongHong 2024-10-17 15:22:34 +08:00
parent 08c8eda133
commit bda3e6a4c7
7 changed files with 456 additions and 228 deletions

View File

@ -24,7 +24,12 @@
</div> </div>
<div class="api-card" v-if="requestCard.codeText !== undefined"> <div class="api-card" v-if="requestCard.codeText !== undefined">
<h5>请求示例</h5> <h5>请求示例</h5>
<JsonViewer :value="requestCard.codeText" copyable /> <Monaco
:tips="requestCard.tips"
:codeText="requestCard.codeText"
:loading="loading"
/>
</div> </div>
<div class="api-card" v-if="requestCard.tableData.length"> <div class="api-card" v-if="requestCard.tableData.length">
<h5>请求参数</h5> <h5>请求参数</h5>
@ -37,10 +42,7 @@
size="small" size="small"
> >
<template #required="slotProps"> <template #required="slotProps">
<span>{{ Boolean(slotProps.required) + '' }}</span> <span :style="{ color: Boolean(slotProps.required) ? '#f81d22' : ''}">{{ Boolean(slotProps.required) + '' }}</span>
</template>
<template #type="slotProps">
<span>{{ slotProps?.schema.type }}</span>
</template> </template>
</j-pro-table> </j-pro-table>
</div> </div>
@ -80,18 +82,23 @@
</j-pro-table> </j-pro-table>
</div> </div>
<JsonViewer :value="respParamsCard.codeText" copyable /> <!-- <JsonViewer :value="respParamsCard.codeText" copyable />-->
<Monaco
:tips="respParamsCard.tips"
:codeText="respParamsCard.codeText"
:loading="loading"
/>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts" name="APIDoes">
import { JsonViewer } from 'vue3-json-viewer';
import 'vue3-json-viewer/dist/index.css';
import type { apiDetailsType } from '../typing'; import type { apiDetailsType } from '../typing';
import InputCard from './InputCard.vue'; import InputCard from './InputCard.vue';
import { PropType } from 'vue'; import type { PropType } from 'vue';
import { findData, getCodeText, dealNoRef } from '../utils'; import { findData, getCodeText, dealNoRef } from '../utils';
import {randomString} from "@/utils/utils";
import Monaco from './monaco.vue'
type cardType = { type cardType = {
columns: object[]; columns: object[];
@ -112,38 +119,49 @@ const props = defineProps({
}, },
}); });
const { selectApi } = toRefs(props); const { selectApi } = toRefs(props);
const loading = ref(false)
const requestCard = reactive<cardType>({ const requestCard = reactive<cardType>({
columns: [ columns: [
{ {
title: '参数名', title: '参数名',
dataIndex: 'name', dataIndex: 'paramsName',
key: 'name', key: 'paramsName',
width: 320
}, },
{ {
title: '参数说明', title: '参数说明',
dataIndex: 'description', dataIndex: 'desc',
key: 'description', key: 'desc',
}, },
{ {
title: '请求类型', title: '请求类型',
dataIndex: 'in', dataIndex: 'in',
key: 'in', key: 'in',
width: 80
}, },
{ {
title: '是否必须', title: '是否必须',
dataIndex: 'required', dataIndex: 'required',
key: 'required', key: 'required',
scopedSlots: true, scopedSlots: true,
width: 80
}, },
{ {
title: '参数类型', title: '参数类型',
dataIndex: 'paramsType',
key: 'paramsType',
width: 200
},
{
title: 'schema',
dataIndex: 'type', dataIndex: 'type',
key: 'type', key: 'type',
scopedSlots: true, width: 200
}, },
], ],
tableData: [], tableData: [],
tips: [],
codeText: undefined, codeText: undefined,
getData: () => { getData: () => {
if (!props.selectApi.requestBody) if (!props.selectApi.requestBody)
@ -159,27 +177,30 @@ const requestCard = reactive<cardType>({
const schemaName = _ref?.split('/').pop(); const schemaName = _ref?.split('/').pop();
const type = schema.type || ''; const type = schema.type || '';
const tableData = findData(props.schemas, schemaName); const tableData = findData(props.schemas, schemaName);
requestCard.codeText = // requestCard.codeText =
type === 'array' // type === 'array'
? [getCodeText(props.schemas, tableData, 3)] // ? [getCodeText(props.schemas, tableData, 3)]
: getCodeText(props.schemas, tableData, 3); // : getCodeText(props.schemas, tableData, 3);
const { codeText, codeTips } = getCodeText(props.schemas, tableData, 3)
requestCard.codeText = JSON.stringify(codeText)
requestCard.tips = codeTips
requestCard.tableData = [ requestCard.tableData = [
{ {
name: schemaName[0].toLowerCase() + schemaName.substring(1), paramsName: schemaName[0].toLowerCase() + schemaName.substring(1),
description: schemaName, desc: schemaName,
in: 'body', in: 'body',
id: randomString(),
required: true, required: true,
schema: { type: type || schemaName }, paramsType: type || schemaName,
children: tableData.map((item) => ({ type: type || schemaName,
name: item.paramsName, children: tableData,
description: item.desc,
required: false,
schema: { type: item.paramsType },
})),
}, },
]; ];
// console.log(requestCard,'requestCard') // console.log(requestCard,'requestCard')
} }
setTimeout(() => {
loading.value = true
}, 1000)
}, },
}); });
const responseStatusCard = reactive<cardType>({ const responseStatusCard = reactive<cardType>({
@ -189,6 +210,7 @@ const responseStatusCard = reactive<cardType>({
title: '状态码', title: '状态码',
dataIndex: 'code', dataIndex: 'code',
key: 'code', key: 'code',
width: 200
}, },
{ {
title: '说明', title: '说明',
@ -199,6 +221,7 @@ const responseStatusCard = reactive<cardType>({
title: 'schema', title: 'schema',
dataIndex: 'schema', dataIndex: 'schema',
key: 'schema', key: 'schema',
width: 200
}, },
], ],
tableData: [], tableData: [],
@ -231,6 +254,7 @@ const respParamsCard = reactive<cardType>({
{ {
title: '参数名称', title: '参数名称',
dataIndex: 'paramsName', dataIndex: 'paramsName',
width: 320
}, },
{ {
title: '参数说明', title: '参数说明',
@ -239,9 +263,16 @@ const respParamsCard = reactive<cardType>({
{ {
title: '类型', title: '类型',
dataIndex: 'paramsType', dataIndex: 'paramsType',
width: 200
},
{
title: 'schema',
dataIndex: 'schema',
width: 200
}, },
], ],
tableData: [], tableData: [],
tips: [],
codeText: '', codeText: '',
getData: (code: string) => { getData: (code: string) => {
const schemaName = responseStatusCard.tableData.find( const schemaName = responseStatusCard.tableData.find(
@ -249,11 +280,22 @@ const respParamsCard = reactive<cardType>({
)?.schema; )?.schema;
const tableData = findData(props.schemas, schemaName); const tableData = findData(props.schemas, schemaName);
respParamsCard.codeText = getCodeText(props.schemas, tableData, 3); const { codeText, codeTips } = getCodeText(props.schemas, tableData, 3)
respParamsCard.codeText = JSON.stringify(codeText)
respParamsCard.tips = codeTips
respParamsCard.tableData = tableData; respParamsCard.tableData = tableData;
setTimeout(() => {
loading.value = true
}, 1000)
}, },
}); });
const options = {
minimap: {
enabled: false
}
}
const getContent = (data: any) => { const getContent = (data: any) => {
if (data && data.content) { if (data && data.content) {
return Object.keys(data.content || {})[0]; return Object.keys(data.content || {})[0];
@ -263,17 +305,18 @@ const getContent = (data: any) => {
onMounted(() => { onMounted(() => {
requestCard.getData(); requestCard.getData();
responseStatusCard.getData(); responseStatusCard.getData();
watch( });
watch(
() => props.selectApi, () => props.selectApi,
() => { () => {
requestCard.getData(); requestCard.getData();
responseStatusCard.getData(); responseStatusCard.getData();
}, },
); );
watch([() => responseStatusCard.activeKey, () => props.selectApi], (n) => { watch([() => responseStatusCard.activeKey, () => props.selectApi], (n) => {
n[0] && respParamsCard.getData(n[0]); n[0] && respParamsCard.getData(n[0]);
});
}); });
</script> </script>

View File

@ -5,7 +5,7 @@
<div class="input"> <div class="input">
<InputCard :value="props.selectApi.method" /> <InputCard :value="props.selectApi.method" />
<j-input :value="props.selectApi?.url" disabled /> <j-input :value="props.selectApi?.url" disabled />
<span class="send" @click="send">发送</span> <j-button type="primary" @click="send" :loading="loading">发送</j-button>
</div> </div>
</div> </div>
@ -107,14 +107,31 @@
<AIcon type="PlusOutlined" />新增 <AIcon type="PlusOutlined" />新增
</j-button> </j-button>
</div> </div>
<j-monaco-editor <a-select v-model:value="bodyType" @change="handleChangeBodyType">
<a-select-option value="json">Json</a-select-option>
<a-select-option value="text">Text</a-select-option>
</a-select>
<template
v-if="showRequestBody" v-if="showRequestBody"
>
<j-monaco-editor
v-if="bodyType === 'json'"
ref="editorRef" ref="editorRef"
language="json" language="json"
style="height: 100% ; min-height: 200px;" style="height: 100% ; min-height: 200px;"
theme="vs" theme="vs"
v-model:modelValue="requestBody.code" v-model:modelValue="requestBody.code"
/> />
<j-monaco-editor
v-else
ref="editorRef"
language="text"
style="height: 100% ; min-height: 200px;"
theme="vs"
v-model:modelValue="requestBody.code"
/>
</template>
</div> </div>
</div> </div>
<div class="api-card"> <div class="api-card">
@ -145,6 +162,8 @@ const editorRef = ref();
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>();
const method = ref() const method = ref()
const showRequestBody = ref(!!props.selectApi?.requestBody) const showRequestBody = ref(!!props.selectApi?.requestBody)
const bodyType = ref('text');
const loading = ref(false);
const requestBody = reactive({ const requestBody = reactive({
tableColumns: [ tableColumns: [
{ {
@ -208,6 +227,11 @@ const init = () => {
}; };
init(); init();
const handleChangeBodyType = () => {
console.log(editorRef.value)
editorRef.value?.setModelLanguage(editorRef.value.getModel(), bodyType.value);
}
const send = () => { const send = () => {
if (paramsTable.value.length) if (paramsTable.value.length)
formRef.value && formRef.value &&
@ -240,16 +264,22 @@ const _send = () => {
...urlParams, ...urlParams,
}; };
}else{ }else{
if(bodyType.value == 'text') {
params = requestBody.code
} else {
params = JSON.parse(requestBody.code || '{}') params = JSON.parse(requestBody.code || '{}')
} }
}
server[methodObj[methodName]](url, params).then((resp: any) => { loading.value = true;
server[methodObj[methodName]](url, params, {}, bodyType.value === 'text' ? {headers: {'Content-Type': 'text/plain'}} : {}).then((resp: any) => {
// body // body
if (Object.keys(params).length === 0 && refStr.value) { if (Object.keys(params).length === 0 && refStr.value) {
requestBody.code = JSON.stringify(getDefaultParams()); requestBody.code = JSON.stringify(getDefaultParams());
editorRef.value?.editorFormat(); editorRef.value?.editorFormat();
} }
responsesContent.value = resp; responsesContent.value = resp;
}).finally(() => {
loading.value = false;
}); });
}; };

View File

@ -5,6 +5,7 @@
:columns="columns" :columns="columns"
:dataSource="_tableData" :dataSource="_tableData"
:rowSelection="props.mode !== 'home' ? rowSelection : undefined" :rowSelection="props.mode !== 'home' ? rowSelection : undefined"
:style="{padding: 0}"
noPagination noPagination
model="TABLE" model="TABLE"
> >

View File

@ -0,0 +1,63 @@
<template>
<div class="api-example">
<j-monaco-editor
v-if="loading"
language="json"
:model-value="codeText"
:init="init"
/>
<div class="api-example-tips" :style="{ transform: `translateY(${scrollTop}px)`}" v-if="loading">
<div class="tips-line" v-for="tip in tips">
<span v-if="tip">//{{tip}}</span>
</div>
</div>
</div>
</template>
<script setup name="ApiMonaco">
defineProps({
tips: {
type: Array,
default: () => []
},
codeText: {
type: String,
default: ''
},
loading: {
type: Boolean,
default: false
}
})
const scrollTop = ref(0)
const init = (editor) => {
editor.onDidScrollChange(e => {
scrollTop.value = 0 - e.scrollTop
})
}
</script>
<style scoped lang="less">
.api-example {
height: 350px;
position: relative;
overflow: hidden;
.api-example-tips {
position: absolute;
left: 50%;
top: 19px;
bottom: 0;
right: 0;
pointer-events: none;
color: #608b4e;
.tips-line {
height: 19px;
}
}
}
</style>

View File

@ -3,23 +3,23 @@
<div class="top"> <div class="top">
<slot name="top" /> <slot name="top" />
</div> </div>
<j-row class="content" :style="{padding:'24px'}" > <div class="api-page-content" :style="styles">
<j-col <div
:span="24"
v-if="props.showTitle" v-if="props.showTitle"
style="font-size: 16px;margin-bottom: 48px;" style="font-size: 16px;margin-bottom: 48px;"
> >
API文档 API文档
</j-col> </div>
<j-col :span="5" class="tree-content"> <div class="api-page-body">
<div class="tree-content">
<LeftTree <LeftTree
@select="treeSelect" @select="treeSelect"
:mode="props.mode" :mode="props.mode"
:has-home="props.hasHome" :has-home="props.hasHome"
:code="props.code" :code="props.code"
/> />
</j-col> </div>
<j-col :span="19"> <div class="api-page-detail">
<HomePage v-show="showHome" /> <HomePage v-show="showHome" />
<div class="url-page" v-show="!showHome"> <div class="url-page" v-show="!showHome">
<ChooseApi <ChooseApi
@ -39,9 +39,8 @@
> >
<j-button <j-button
@click="selectedApi = initSelectedApi" @click="selectedApi = initSelectedApi"
style="margin-bottom: 24px" style="margin-bottom: 24px; width: 80px">返回</j-button>
>返回</j-button <div class="api-details-tabs">
>
<j-tabs v-model:activeKey="activeKey" type="card"> <j-tabs v-model:activeKey="activeKey" type="card">
<j-tab-pane key="does" tab="文档"> <j-tab-pane key="does" tab="文档">
<ApiDoes <ApiDoes
@ -58,8 +57,10 @@
</j-tabs> </j-tabs>
</div> </div>
</div> </div>
</j-col> </div>
</j-row> </div>
</div>
</div>
</div> </div>
</template> </template>
@ -88,6 +89,12 @@ const props = defineProps<{
}>(); }>();
const showHome = ref<boolean>(Boolean(props.hasHome)); // home const showHome = ref<boolean>(Boolean(props.hasHome)); // home
const tableData = ref([]); const tableData = ref([]);
const styles = computed(() => {
return {
padding: props.mode === 'api' ? 0 : '24px'
}
})
const treeSelect = (node: treeNodeTpye, nodeSchemas: object = {}) => { const treeSelect = (node: treeNodeTpye, nodeSchemas: object = {}) => {
if (node.key === 'home') return (showHome.value = true); if (node.key === 'home') return (showHome.value = true);
schemas.value = nodeSchemas; schemas.value = nodeSchemas;
@ -178,15 +185,48 @@ watch(
<style lang="less" scoped> <style lang="less" scoped>
.api-page-container { .api-page-container {
.content { height: 100%;
.api-page-content {
background-color: #fff; background-color: #fff;
margin: 0 !important; margin: 0 !important;
.api-page-body {
position: relative;
display: flex;
gap: 16px;
.tree-content { .tree-content {
padding-bottom: 30px;
height: calc(100vh - 230px); height: calc(100vh - 230px);
width: 280px;
overflow-y: auto; overflow-y: auto;
border-right: 1px solid #e9e9e9; border-right: 1px solid #e9e9e9;
} }
.api-page-detail {
position: absolute;
left: 296px;
top: 0;
bottom: 0;
right: 0;
.url-page {
height: 100%;
} }
.api-details {
max-height: 100%;
display: flex;
flex-direction: column;
.api-details-tabs {
overflow-y: auto;
}
}
}
}
}
} }
</style> </style>

View File

@ -34,6 +34,11 @@ export type modeType = 'api'| 'appManger' | 'home'
export type schemaObjType = { export type schemaObjType = {
paramsName: string; paramsName: string;
paramsType: string; paramsType: string;
id: string;
type?: string;
schema?: string;
required?: boolean
desc?: string; desc?: string;
children?: schemaObjType[]; children?: schemaObjType[];
}; };

View File

@ -1,4 +1,5 @@
import { schemaObjType } from "./typing"; import { schemaObjType } from "./typing";
import {randomString} from "@/utils/utils";
/** /**
@ -14,22 +15,32 @@ export function findData(schemas: object, schemaName: string , paths:string[]=[]
} }
const result: schemaObjType[] = []; const result: schemaObjType[] = [];
const schema = schemas[schemaName]; const schema = schemas[schemaName];
const required = schema.required || []
Object.entries(schema.properties).forEach((item: [string, any]) => { Object.entries(schema.properties).forEach((item: [string, any]) => {
const [paramsName, extra] = item
const paramsType = const paramsType =
(item[1].$ref && item[1].$ref.split('/').pop()) || (extra.$ref && extra.$ref.split('/').pop()) ||
(item[1].items?.$ref && item[1].items.$ref.split('/').pop()) || (extra.items?.$ref && extra.items.$ref.split('/').pop()) ||
item[1].item?.type || extra.item?.type ||
item[1].type || extra.type ||
''; '';
const schema = extra.format ? `${paramsType}(${extra.format})` : ''
const schemaObj: schemaObjType = { const schemaObj: schemaObjType = {
paramsName: item[0], paramsName: paramsName,
paramsType, paramsType,
desc: item[1].description || '', required: required.includes(paramsName),
desc: extra.description || '',
schema: schema,
type: !basicType.includes(paramsType) ? paramsType : '',
id: randomString()
}; };
if (!basicType.includes(paramsType) && paths.filter(path=>path === schemaName).length >=2 ){ if (!basicType.includes(paramsType) && (paths.filter(path=>path === schemaName).length <=2) ){
paths.push(schemaName) paths.push(schemaName)
schemaObj.children = findData(schemas, paramsType); schemaObj.children = findData(schemas, paramsType, paths);
} }
result.push(schemaObj); result.push(schemaObj);
}); });
@ -47,54 +58,89 @@ export function getCodeText(
schemas: object, schemas: object,
arr: schemaObjType[], arr: schemaObjType[],
level: number, level: number,
): object { paths: string[] = []
const result = {}; ): any {
const tips: Array<string | undefined> = []
let result = {}
arr.forEach((item) => { arr.forEach((item) => {
let value: any = ""
tips.push(item.desc)
switch (item.paramsType) { switch (item.paramsType) {
case 'string': case 'string':
result[item.paramsName] = ''; value = ''
break; break;
case 'integer': case 'integer':
result[item.paramsName] = 0; case 'number':
value = 0
break; break;
case 'boolean': case 'boolean':
result[item.paramsName] = true; value = true
break; break;
case 'array': case 'array':
result[item.paramsName] = []; value = []
break; break;
case 'object': case 'object':
result[item.paramsName] = ''; value = {}
break;
case 'number':
result[item.paramsName] = 0;
break; break;
default: { default: {
if (item.children) {
if (paths.filter(path=> path === item.paramsName).length >=2) {
break
}
paths.push(item.paramsName)
const _result = getCodeText(
schemas,
item.children,
level - 1,
paths
)
value = _result.codeText
tips.push(..._result.codeTips)
tips.push(undefined)
} else {
if (paths.filter(path=> path === item.paramsName).length >=2) {
break
}
paths.push(item.paramsName)
const properties = schemas[item.paramsType]?.properties as object || {}; const properties = schemas[item.paramsType]?.properties as object || {};
const newArr = Object.entries(properties).map( const newArr = Object.entries(properties).map(
(item: [string, any]) => { (item: [string, any]) => {
const [paramsName, extra] = item
const paramsType =
(extra.$ref && extra.$ref.split('/').pop()) ||
(extra.items?.$ref && extra.items.$ref.split('/').pop()) ||
extra.item?.type ||
extra.type ||
'';
return{ return{
paramsName: item[0], paramsName: paramsName,
paramsType: level desc: extra.description || '',
? (item[1].$ref && item[1].$ref.split('/').pop()) || paramsType: paramsType,
(item[1].items?.$ref && schema: extra.format ? `${paramsType}(${extra.format})` : ''
item[1].items.$ref.split('/').pop()) ||
item[1].item?.type ||
item[1].type ||
''
: item[1].type,
}}, }},
); );
result[item.paramsName] = getCodeText(
const _result = getCodeText(
schemas, schemas,
newArr, newArr,
level - 1, level - 1,
); paths
)
value = _result.codeText
tips.push(..._result.codeTips)
tips.push(undefined)
} }
} }
}
result[item.paramsName] = value
}); });
return {
codeText: result,
codeTips: tips
return result; };
} }
/** /**