Api文档
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.

556 lines
29 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-x: 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: item.children.length > item.children.filter(a => myMark.includes(a.url)).length ? '#E6A23C' : 'red' }"></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">
<span v-if="api.version.filter(v => v < 0).length <= 0">{{api.name}}</span>
<span v-else class="re-del">{{api.name}}</span>
</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-button style="margin-top: 8px" type="primary" plain @click="getApiInfo">更新</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.map(vs => Math.abs(vs)).filter((v1, i1, s1) => s1.indexOf(v1) === i1)" :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 v-if="currentApiInfo.version.filter(v => v < 0).length <= 0" :icon="sendIcon" type="success" @click="send" style="font-size:18px"></el-link>
<el-link v-else disabled> 版本 {{Math.abs(ApiInfo.version.filter(v => v <= 0)[0])}} 已废弃</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 !== 1">V{{Math.abs(request.version)}}</b>
<el-link type="primary" :class="{'re-del':request.version < 0, 're-add':request.version > 1}" :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 !== 1">V{{Math.abs(request.version)}}</b>
<el-link type="primary" :class="{'re-del':request.version < 0, 're-add':request.version > 1}" :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"
size="mini"
default-expand-all>
<el-table-column prop="name" label="字段" ></el-table-column>
<el-table-column prop="type" align="center" label="类型" :width="80" ></el-table-column>
<el-table-column prop="required" align="center" :width="60" label="必填" >
<template #default="scope">{{ scope.row.required ? "是" : "否" }}</template>
</el-table-column>
<el-table-column prop="version" align="center" :width="60" 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
size="mini"
:row-class-name="tableVersionClass"
default-expand-all>
<el-table-column prop="name" label="字段"></el-table-column>
<el-table-column prop="type" align="center" label="类型" :width="80" ></el-table-column>
<el-table-column prop="required" align="center" :width="60" label="必填" >
<template #default="scope">{{ scope.row.required ? "是" : "否" }}</template>
</el-table-column>
<el-table-column prop="version" align="center" :width="60" 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" @close="docDesVisibleClose">
<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:!localStorage.getItem("docDesVisible"),
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 = { Authorization: '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.token){`
+ `\n localStorage.setItem("token", response.data.token.token);`
+ `\n}else{`
+ `\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
}).length;
if (length <= 0) show = false;
}
if (this.version && !item.version.includes(this.version * 1) && !item.version.includes(-this.version)) {
show = false;
}
return show;
},
docDesVisibleClose(){
localStorage.setItem('docDesVisible', "1");
},
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) && !a.version.includes(-this.version)) {
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.scriptAfterRequest = 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));
try{
eval(this.scriptBeforeRequest);
}catch (e) {
console.log(e)
}
axios(requestData).then(res => {
this.response = res.data;
this.sendIcon = 'el-icon-s-promotion';
this.responseJsonHandle();
try{
let response = JSON.parse(JSON.stringify(res.data));
eval(this.scriptAfterRequest);
}catch (e) {
console.log(e)
}
}).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')
);
}
console.log(this.response);
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;
this.$message.success("已获取最新文档信息,当前最新版本:" + Math.max(...res.data.data.version))
})
},
tableVersionClass({row, rowIndex}){
if(row.version === 1) return '';
return row.version > 1 ? 't-add' : 't-del';
}
}
});
</script>
</html>