You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
534 lines
27 KiB
534 lines
27 KiB
<html lang="zh"> |
|
<head> |
|
<title>Api 文档</title> |
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> |
|
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.8"></script> |
|
<!-- 引入样式 --> |
|
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"> |
|
<!-- 引入组件库 --> |
|
<script src="https://unpkg.com/element-ui/lib/index.js"></script> |
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jsoneditor@9.10.4/dist/jsoneditor.min.css"> |
|
<script src="https://cdn.jsdelivr.net/npm/jsoneditor@9.10.4/dist/jsoneditor.min.js" ></script> |
|
<script src="https://unpkg.com/axios/dist/axios.min.js"></script> |
|
<link rel="stylesheet" href="https://unpkg.com/codemirror@5.65.8/lib/codemirror.css"> |
|
<link rel="stylesheet" href="https://unpkg.com/codemirror@5.65.8/theme/material.css"> |
|
<script src="https://unpkg.com/codemirror@5.65.8/lib/codemirror.js"></script> |
|
<script src="https://unpkg.com/codemirror@5.65.8/mode/javascript/javascript.js"></script> |
|
<style> |
|
html,body,#app,.el-container{ |
|
height: 100%; |
|
margin: 0; |
|
} |
|
.el-aside{ |
|
background-color: #545c64; |
|
} |
|
|
|
#url .el-link{ |
|
font-size: 18px; |
|
line-height: 30px; |
|
} |
|
.el-alert{ |
|
margin-bottom: 10px; |
|
} |
|
.el-form--label-top .el-form-item__label{ |
|
line-height: 35px; |
|
padding: 0; |
|
} |
|
.el-form-item{ |
|
margin-bottom: 15px; |
|
} |
|
#send{ |
|
font-size: 25px;position: relative;color: #67C23A; |
|
} |
|
#send:hover{ |
|
color: #67C23A99; |
|
} |
|
.CodeMirror { |
|
width: 100%; height: calc(100% - 173px); |
|
} |
|
.upload-demo{ |
|
position: absolute; |
|
top: 50%; |
|
left: 50%; |
|
margin-left: -30px; |
|
margin-top: -90px; |
|
} |
|
.el-dialog__header{ |
|
background-color: #909399; |
|
padding: 15px 20px; |
|
} |
|
.el-dialog__title,.el-dialog__headerbtn .el-dialog__close{ |
|
color: white; |
|
} |
|
.t-add .cell, .re-add { |
|
color: #67C23A !important; |
|
} |
|
.t-del .cell, .re-del { |
|
text-decoration: line-through; |
|
color: #F56C6C !important; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div id="app"> |
|
<el-container> |
|
<el-aside width="200px" style="overflow: hidden"> |
|
<el-menu |
|
:default-active="apiActive" |
|
class="el-menu-vertical-demo" |
|
@select="handleSelect" |
|
background-color="#545c64" |
|
text-color="#fff" |
|
active-text-color="#ffd04b"> |
|
<el-submenu v-for="(item, index) in ApiInfo.apiLists" v-if="searchP(item)" :index="index"> |
|
<template slot="title"> |
|
<i :class="item.children.filter(a => myMark.includes(a.url)).length > 0 ? 'el-icon-star-on' : 'el-icon-star-off'" style="color: #E6A23C"></i> |
|
<span>{{item.name}}</span> |
|
</template> |
|
<el-menu-item v-for="(api, Aindex) in item.children" v-if="search(api)" :index="index + '-' + Aindex"> |
|
<i :class="myMark.includes(api.url) ? 'el-icon-star-on' : 'el-icon-star-off'" style="color: #E6A23C"></i> |
|
<span slot="title">{{api.name}}</span> |
|
</el-menu-item> |
|
</el-submenu> |
|
</el-menu> |
|
</el-aside> |
|
<el-container> |
|
<el-header> |
|
<el-button style="margin-top: 8px" type="primary" plain @click="docDesVisible = true">文档说明</el-button> |
|
<el-button style="margin-top: 8px" type="primary" plain @click="showMark = !showMark">我的标记</el-button> |
|
检索:<el-input v-model="searchText" style="width: 200px" placeholder="搜索:地址|名字" clearable></el-input> |
|
版本检索:<el-select v-model="version" style="width: 200px" placeholder="版本号"> |
|
<el-option v-for="vv in ApiInfo.version" :value="vv" :label="'版本:' + vv"></el-option> |
|
</el-select> |
|
</el-header> |
|
<el-main> |
|
<el-row v-if="currentApiInfo.name" :gutter="10"> |
|
<el-col :span="12"> |
|
<el-card class="box-card"> |
|
<div slot="header" class="clearfix"> |
|
<i :class="myMark.includes(currentApiInfo.url) ? 'el-icon-star-on' : 'el-icon-star-off'" @click="mark(currentApiInfo.url)" style="color: #E6A23C;font-size: 25px;position: relative;top: 3px"></i> |
|
<el-tag>{{currentApiInfo.method}}</el-tag> |
|
<el-link type="primary">{{ApiInfo.host}}{{currentApiInfo.url}}</el-link> |
|
<el-link :icon="sendIcon" type="success" @click="send" style="font-size:18px"></el-link> |
|
</div> |
|
<el-tabs v-model="paramShowType" @tab-click="paramShowTypeToggle"> |
|
<el-tab-pane label="表单" name="form"> |
|
<el-form ref="form" :model="form" label-position="top" label-width="80px"> |
|
<template v-for="request in currentApiInfo.requestParams"> |
|
<template v-if="!request.children || request.children.length === 0"> |
|
<el-form-item > |
|
<template slot="label"> |
|
<b style="color:#E6A23C" v-if="request.version">V{{Math.abs(request.version)}}</b> |
|
<el-link type="primary" :class="{'re-del':request.version < 0, 're-add':request.version > 0}" :underline="false">[{{ request.name }}]</el-link> |
|
<i style="color: #cccccc"><{{request.type}}></i>{{request.describe}} |
|
</template> |
|
<el-input v-if="request.type !== 'Array'" v-model="form[request.name]"></el-input> |
|
<el-select style="width: 100%" v-else multiple allow-create filterable default-first-option v-model="form[request.name]"></el-select> |
|
</el-form-item> |
|
</template> |
|
<template v-else-if="request.children.length > 0"> |
|
<template v-for="requestChildren in request.children"> |
|
<el-form-item> |
|
<template slot="label"> |
|
<b style="color:#E6A23C" v-if="request.version">V{{Math.abs(request.version)}}</b> |
|
<el-link type="primary" :class="{'re-del':request.version < 0, 're-add':request.version > 0}" :underline="false">[{{ request.name }}.{{requestChildren.name}}]</el-link> |
|
<i style="color: #cccccc"><{{requestChildren.type}}></i>{{request.describe}}.{{requestChildren.describe}} |
|
</template> |
|
<el-input v-if="request.type === 'Object'" v-model="form[request.name][requestChildren.name]"></el-input> |
|
<el-input v-else value="请使用json组件模拟数据" readonly=""></el-input> |
|
</el-form-item> |
|
</template> |
|
</template> |
|
</template> |
|
</el-form> |
|
</el-tab-pane> |
|
<el-tab-pane label="JSON" name="json"> |
|
<div id="paramShowJson" style="width: 100%; height: calc(100% - 173px);"></div> |
|
</el-tab-pane> |
|
<el-tab-pane label="请求前脚本" name="scriptBefore"> |
|
<textarea id="requestBefore" ></textarea> |
|
</el-tab-pane> |
|
<el-tab-pane label="请求后脚本" name="scriptAfter"> |
|
<textarea id="requestAfter" ></textarea> |
|
</el-tab-pane> |
|
<el-tab-pane label="请求响应" name="response"> |
|
<div v-if="typeof this.response === 'object'"> |
|
<div id="responseJson" style="width: 100%; height: calc(100% - 173px);"></div> |
|
</div> |
|
<div v-else style="width: 100%; height: calc(100% - 173px);" v-html="response"></div> |
|
</el-tab-pane> |
|
</el-tabs> |
|
</el-card> |
|
</el-col> |
|
<el-col :span="12"> |
|
<el-alert type="info" :closable="false"> |
|
<template slot="title"> |
|
<i v-if="currentApiInfo.auth === 'strength'" style="color:#F56C6C;font-size: 20px;position: relative;right: 5px;top: 2px;" class="el-icon-s-check"></i> |
|
<i v-if="currentApiInfo.auth === 'weak'" style="color:#67C23A;font-size: 20px;position: relative;right: 5px;top: 2px;" class="el-icon-s-check"></i> |
|
<i v-if="!currentApiInfo.auth" style="font-size: 20px;position: relative;right: 5px;top: 2px;" class="el-icon-s-check"></i> |
|
<el-tag type="danger" v-if="currentApiInfo.method.toUpperCase() === 'GET'" effect="dark">GET</el-tag> |
|
<el-tag type="warning" v-if="currentApiInfo.method.toUpperCase() === 'POST'" effect="dark">POST</el-tag> |
|
<span style="font-size: 18px;color: #606266">{{currentApiInfo.name}}</span> |
|
</template> |
|
<?php if(\App\Common\Util\AdminAuth::isSuper()) { ?> |
|
<span> |
|
<el-link>请求位置:</el-link> <el-link type="primary">{{currentApiInfo.call}}</el-link> |
|
</span> |
|
<?php } ?> |
|
</el-alert> |
|
<el-alert id="url" type="success" :closable="false"> |
|
<template slot="title" > |
|
<div> |
|
<el-link><b>请求地址:</b></el-link> |
|
</div> |
|
<div> |
|
<el-link type="primary"><b>URL:</b> <i>{{ApiInfo.host + currentApiInfo.url}}</i></el-link> |
|
<el-link> <i class="el-icon-document-copy" @click="copy(ApiInfo.host + currentApiInfo.url)"></i></el-link> |
|
</div> |
|
<div> |
|
<el-link type="primary"><b>Path:</b> <i>{{ currentApiInfo.url }}</i> </el-link> |
|
<el-link> <i class="el-icon-document-copy" @click="copy(currentApiInfo.url)"></i></el-link> |
|
</div> |
|
</template> |
|
</el-alert> |
|
|
|
<el-tabs v-model="paramInfo" @tab-click="paramToggle"> |
|
<el-tab-pane label="请求参数" name="request"> |
|
|
|
<el-table |
|
:data="currentApiInfo.requestParams" |
|
style="width: 100%;margin-bottom: 20px;" |
|
row-key="name" |
|
border |
|
:row-class-name="tableVersionClass" |
|
default-expand-all> |
|
<el-table-column prop="name" label="字段" ></el-table-column> |
|
<el-table-column prop="type" align="center" label="字段类型" ></el-table-column> |
|
<el-table-column prop="version" align="center" label="版本" ></el-table-column> |
|
<el-table-column prop="describe" label="描述" ></el-table-column> |
|
</el-table> |
|
</el-tab-pane> |
|
<el-tab-pane label="响应参数" name="response"> |
|
<el-table |
|
:data="currentApiInfo.responseParams" |
|
style="width: 100%;margin-bottom: 20px;" |
|
row-key="name" |
|
border |
|
:row-class-name="tableVersionClass" |
|
default-expand-all> |
|
<el-table-column prop="name" label="字段"></el-table-column> |
|
<el-table-column prop="type" align="center" label="类型" ></el-table-column> |
|
<el-table-column prop="version" align="center" label="版本" ></el-table-column> |
|
<el-table-column prop="describe" label="描述" ></el-table-column> |
|
</el-table> |
|
</el-tab-pane> |
|
</el-tabs> |
|
|
|
</el-col> |
|
</el-row> |
|
</el-main> |
|
</el-container> |
|
</el-container> |
|
<el-dialog title="文档说明" :visible.sync="docDesVisible"> |
|
<el-alert title="页面介绍" type="success" :closable="false"> |
|
<div><i class="el-icon-star-on" style="color: #E6A23C"></i> 已做标记,可认为是收藏,接口详情点击可切换标记状态</div> |
|
<div><i class="el-icon-star-off" style="color: #E6A23C"></i> 未做标记</div> |
|
<div><i style="color:#F56C6C;" class="el-icon-s-check"></i>必须使用Token验证</div> |
|
<div><i style="color:#67C23A;" class="el-icon-s-check"></i>使用Token数据, 不验证</div> |
|
<div><i style="color:#909399;" class="el-icon-s-check"></i> 无需token</div> |
|
<div><i class="el-icon-s-promotion"></i> 发送模拟请求</div> |
|
</el-alert> |
|
<el-alert title="响应介绍" type="warning" :closable="false"> |
|
<div><b>code:</b> 状态码</div> |
|
<div><b>data:</b> 数据</div> |
|
<div><b>msg:</b> 响应提示语</div> |
|
<div><b>ts:</b> 当前时间戳(秒)</div> |
|
<hr> |
|
<div><b>code 值说明:</b></div> |
|
<div><b style="margin-left: 10px">200:</b>成功</div> |
|
<div><b style="margin-left: 10px">202:</b>失败</div> |
|
<div><b style="margin-left: 10px">1001:</b>刷新token,此时响应数据会带上新的token</div> |
|
<div><b style="margin-left: 10px">1002:</b>token无效,应要求用户重新登录</div> |
|
</el-alert> |
|
<el-alert title="认证说明" type="error" :closable="false"> |
|
<div>认证方式采用JWT方式, 传输方式使用Header参数: Authenticate</div> |
|
<div><b>例:</b>Authenticate: Bearer G0peyJhbGciF7hOiJyaXB.G0peyJhbGciF7hOiJyaXB.G0peyJhbGciF7hOiJyaXB</div> |
|
</el-alert> |
|
</el-dialog> |
|
</div> |
|
</body> |
|
|
|
<script> |
|
const APP = new Vue({ |
|
el:'#app', |
|
data: { |
|
docDesVisible:true, |
|
paramInfo: 'request', |
|
paramShowType: 'form', |
|
requestUrl: 'https://vuejs.org/guide/introduction.html', |
|
sendIcon: 'el-icon-s-promotion', |
|
form: {}, |
|
paramShowJsonEl:'', |
|
responseJsonEl:'', |
|
scriptBeforeRequestEl:'', |
|
scriptAfterRequestEl:'', |
|
scriptBeforeRequest:`// 请求之前会调用此段代码\n// requestData 对象包含请求参数,可在此更改,异步请求库为:axios \n// ApiInfo 对象包含接口信息\n// console.log(requestData);\n// console.log(ApiInfo);`, |
|
scriptAfterRequest:`// 请求之后调用此段代码\n// ApiInfo 对象包含接口信息\n// response 对象包含响应的数据\n// console.log(ApiInfo);\n// console.log(response);`, |
|
response:{}, |
|
currentApiInfo:{}, |
|
ApiInfo:{}, |
|
myMark:[], |
|
showMark:false, |
|
searchText:"", |
|
apiActive:-1, |
|
version: 1 |
|
}, |
|
created(){ |
|
if (localStorage.getItem("ScBefore")){ |
|
this.scriptBeforeRequest = localStorage.getItem("ScBefore"); |
|
} |
|
if (!/requestData\.headers/.test(this.scriptBeforeRequest)) { |
|
this.scriptBeforeRequest += `\nif(ApiInfo.auth){\n`; |
|
this.scriptBeforeRequest += ` requestData.headers = { Authenticate: 'Bearer ' + localStorage.getItem("token")};\n`; |
|
this.scriptBeforeRequest += `}`; |
|
} |
|
if (localStorage.getItem("ScAfter")){ |
|
this.scriptAfterRequest = localStorage.getItem("ScAfter"); |
|
} |
|
if (!/response\.data\.token/.test(this.scriptAfterRequest)) { |
|
this.scriptAfterRequest += `\nif(response.data.token){\n` |
|
+ ` localStorage.setItem("token", response.data.token);\n` |
|
+ `}`; |
|
} |
|
|
|
this.getApiInfo(); |
|
if (localStorage.getItem("myMark")) { |
|
this.myMark = localStorage.getItem("myMark").split(','); |
|
} |
|
}, |
|
mounted(){ |
|
|
|
}, |
|
methods: { |
|
mark(url){ |
|
let index = this.myMark.indexOf(url); |
|
if(index >= 0){ |
|
this.myMark.splice(index, 1); |
|
}else{ |
|
this.myMark.push(url); |
|
} |
|
localStorage.setItem("myMark", this.myMark); |
|
}, |
|
searchP(item){ |
|
let show = true; |
|
if(this.showMark && item.children.filter(a => this.myMark.includes(a.url)).length === 0){ |
|
show = false; |
|
} |
|
if(this.searchText){ |
|
let length = item.children.filter(a => { |
|
return a.url.indexOf(this.searchText) < 0 && a.name.indexOf(this.searchText) < 0 |
|
}); |
|
if (length > 0) show = false; |
|
} |
|
if (this.version) { |
|
let length = item.children.filter(a => { |
|
return !a.version.includes(this.version * 1) |
|
}); |
|
if (length > 0) show = false; |
|
} |
|
return show; |
|
}, |
|
search(a){ |
|
let show = true; |
|
if(this.showMark && !this.myMark.includes(a.url)){ |
|
show = false; |
|
} |
|
if(this.searchText && (a.url.indexOf(this.searchText) < 0 && a.name.indexOf(this.searchText) < 0)){ |
|
show = false; |
|
} |
|
if(this.version && !a.version.includes(this.version * 1)) { |
|
show = false; |
|
} |
|
return show; |
|
}, |
|
handleSelect(key, keyPath) { |
|
let s = key.split('-') |
|
let currentApiInfo = this.ApiInfo.apiLists[s[0]].children[s[1]]; |
|
this.currentApiInfo = JSON.parse(JSON.stringify(currentApiInfo)); |
|
this.form = this.requestParamsResolve(this.currentApiInfo.requestParams); |
|
this.paramShowJsonEl && this.paramShowJsonEl.set(this.form); |
|
}, |
|
requestParamsResolve(params){ |
|
let forms = {}; |
|
for (let i = 0; i < params.length; i++) { |
|
switch (params[i].type) { |
|
case 'Object': |
|
forms[params[i].name] = this.requestParamsResolve(params[i].children ? params[i].children : []); |
|
break; |
|
case 'Array': |
|
forms[params[i].name] = []; |
|
if(params[i].children && params[i].children.length > 0){ |
|
forms[params[i].name].push(this.requestParamsResolve(params[i].children)); |
|
} |
|
break; |
|
case 'Integer': |
|
case 'float': |
|
forms[params[i].name] = 1; |
|
break; |
|
default: |
|
forms[params[i].name] = ''; |
|
} |
|
} |
|
|
|
return forms; |
|
}, |
|
handleClose(key, keyPath) { |
|
console.log(key, keyPath); |
|
}, |
|
paramToggle(el){ |
|
console.log(el) |
|
}, |
|
paramShowTypeToggle(el){ |
|
if (el.name === 'json') { |
|
setTimeout(() => { |
|
if (!this.paramShowJsonEl) { |
|
this.paramShowJsonEl = new JSONEditor( |
|
document.getElementById("paramShowJson"), |
|
this.jsonEditorInitOptions('code', (jsonString) => { |
|
this.form = JSON.parse(jsonString); |
|
}) |
|
); |
|
} |
|
|
|
setTimeout(() => this.paramShowJsonEl.set(this.form), 5); |
|
}, 10); |
|
} else if (el.name === 'scriptBefore') { |
|
if (!this.scriptBeforeRequestEl) { |
|
let myTextArea = document.getElementById('requestBefore'); |
|
this.scriptBeforeRequestEl = CodeMirror.fromTextArea(myTextArea, { |
|
lineNumbers: true, |
|
mode: 'text/javascript', |
|
theme: 'material', |
|
styleActiveLine: true, |
|
lineWrapping: true |
|
}); |
|
|
|
this.scriptBeforeRequestEl.on('blur', (codemirror) => { |
|
localStorage.setItem("ScBefore", codemirror.getValue()); |
|
this.scriptBeforeRequest = codemirror.getValue(); |
|
}); |
|
|
|
setTimeout(() => { |
|
this.scriptBeforeRequestEl.setValue(this.scriptBeforeRequest); |
|
this.scriptBeforeRequestEl.refresh(); |
|
}, 10) |
|
} |
|
} else if (el.name === 'scriptAfter') { |
|
if (!this.scriptAfterRequestEl) { |
|
let myTextArea = document.getElementById('requestAfter'); |
|
this.scriptAfterRequestEl = CodeMirror.fromTextArea(myTextArea, { |
|
lineNumbers: true, |
|
mode: 'text/javascript', |
|
theme: 'material', |
|
styleActiveLine: true, |
|
lineWrapping: true, |
|
}); |
|
this.scriptAfterRequestEl.on('blur', (codemirror) => { |
|
localStorage.setItem("ScAfter", codemirror.getValue()); |
|
this.scriptBeforeRequest = codemirror.getValue(); |
|
}); |
|
|
|
setTimeout(() => { |
|
this.scriptAfterRequestEl.setValue(this.scriptAfterRequest); |
|
this.scriptAfterRequestEl.refresh(); |
|
}, 10) |
|
} |
|
} |
|
}, |
|
send(){ |
|
this.sendIcon = 'el-icon-loading'; |
|
this.paramShowType = 'response'; |
|
let requestData = { |
|
url: this.currentApiInfo.url, |
|
method:this.currentApiInfo.method |
|
}; |
|
if (this.form){ |
|
this.currentApiInfo.method.toUpperCase() === 'GET' |
|
? requestData.params = this.form |
|
: requestData.data = this.form; |
|
} |
|
|
|
let ApiInfo = JSON.parse(JSON.stringify(this.currentApiInfo)); |
|
{ |
|
eval(this.scriptBeforeRequest); |
|
} |
|
|
|
axios(requestData).then(res => { |
|
this.response = res.data; |
|
this.sendIcon = 'el-icon-s-promotion'; |
|
this.responseJsonHandle(); |
|
{ |
|
let response = JSON.parse(JSON.stringify(res.data)); |
|
eval(this.scriptAfterRequest); |
|
} |
|
}).catch((error) => { |
|
this.response = error; |
|
this.sendIcon = 'el-icon-s-promotion'; |
|
this.responseJsonHandle(); |
|
}); |
|
}, |
|
responseJsonHandle(){ |
|
setTimeout(() => { |
|
if (typeof this.response === 'object') { |
|
if (!this.responseJsonEl) { |
|
this.responseJsonEl = new JSONEditor( |
|
document.getElementById("responseJson"), |
|
this.jsonEditorInitOptions('code') |
|
); |
|
} |
|
this.responseJsonEl.set(this.response); |
|
} else { |
|
this.responseJsonEl = null; |
|
} |
|
}, 10); |
|
}, |
|
jsonEditorInitOptions(defaultMode = 'tree', change){ |
|
return { |
|
mode: defaultMode, |
|
modes: ['code', 'form', 'text', 'tree', 'view', 'preview'], // allowed modes |
|
onChangeText: change, |
|
}; |
|
}, |
|
copy(text){ |
|
navigator.clipboard.writeText(text); |
|
this.$message.success('复制成功'); |
|
}, |
|
exportApi(file){ |
|
let reader = new FileReader() |
|
reader.readAsText(file.raw,'UTF-8'); |
|
reader.onload = function (e) { |
|
try { |
|
let urlData = JSON.parse(this.result); |
|
}catch(e){ |
|
APP.$message.error('识别失败'); |
|
} |
|
}; |
|
}, |
|
getApiInfo(){ |
|
axios({ |
|
url:"<?= \App\Common\Util\TP::route()->to([\Plugins\ApiDoc\Admin\Controller\DocumentController::class, 'doc']) ?>" |
|
}).then(res => { |
|
this.ApiInfo = res.data.data; |
|
}) |
|
}, |
|
tableVersionClass({row, rowIndex}){ |
|
if(!row.version) return ''; |
|
return row.version > 0 ? 't-add' : 't-del'; |
|
} |
|
} |
|
}); |
|
|
|
|
|
|
|
</script> |
|
</html>
|