├── static └── .gitkeep ├── .eslintignore ├── src ├── views │ └── pages │ │ ├── layout │ │ ├── empty.layout.vue │ │ ├── index.vue │ │ ├── app-main.vue │ │ ├── sidebar │ │ │ ├── item.vue │ │ │ ├── link.vue │ │ │ ├── index.vue │ │ │ └── sidebar-item.vue │ │ ├── swaggerInfo.vue │ │ ├── commonSetting.vue │ │ └── topbar.vue │ │ ├── simpleMain │ │ └── index.vue │ │ ├── simpleSwagger │ │ └── index.vue │ │ ├── main │ │ └── index.vue │ │ ├── swagger │ │ ├── reqJsonView.vue │ │ ├── mdShow.vue │ │ ├── jsonedit.vue │ │ ├── assist │ │ │ ├── buildmd.js │ │ │ └── swagger.helper.js │ │ └── index.vue │ │ ├── simpleLayout │ │ ├── app-main.vue │ │ └── index.vue │ │ └── login │ │ └── index.vue ├── assets │ ├── logo.png │ └── vue.png ├── styles │ ├── index.scss │ ├── tennetcn-ui │ │ └── index.scss │ └── main │ │ ├── sidebar-simple.scss │ │ └── sidebar.scss ├── store │ ├── getters.js │ ├── main.js │ ├── index.js │ └── swagger.js ├── router │ ├── route.load.js │ ├── index.js │ ├── route.config.js │ └── menu.load.js ├── main.js ├── components │ └── util │ │ ├── file │ │ └── down.js │ │ └── http │ │ └── request.js ├── App.vue └── api │ └── swagger.js ├── config ├── prod.env.js ├── dev.env.js └── index.js ├── .editorconfig ├── .gitignore ├── .babelrc ├── index.html ├── .eslintrc ├── README.md └── package.json /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | /test/ 6 | -------------------------------------------------------------------------------- /src/views/pages/layout/empty.layout.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfree/think-swagger-ui-vuele/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/vue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfree/think-swagger-ui-vuele/HEAD/src/assets/vue.png -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /src/views/pages/simpleMain/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | .vscode 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | 16 | publish_local.sh -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import './main/sidebar.scss'; 2 | @import './main/sidebar-simple.scss'; 3 | @import './tennetcn-ui//index.scss'; 4 | 5 | a, 6 | a:focus, 7 | a:hover { 8 | cursor: pointer; 9 | color: inherit; 10 | outline: none; 11 | text-decoration: none; 12 | } 13 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-vue-jsx", "transform-runtime"] 12 | } 13 | -------------------------------------------------------------------------------- /src/views/pages/simpleSwagger/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /src/store/getters.js: -------------------------------------------------------------------------------- 1 | const getters = { 2 | swaggerPath: state => state.swagger.path, 3 | swaggerInfo: state => state.swagger.info, 4 | menus: state => state.swagger.menus, 5 | headers: state => state.swagger.headers, 6 | theme: state => state.main.theme 7 | 8 | } 9 | export default getters 10 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | swagger-ui 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/store/main.js: -------------------------------------------------------------------------------- 1 | const main = { 2 | state: { 3 | theme: null 4 | }, 5 | mutations: { 6 | theme: function(state, result) { 7 | state.theme = result 8 | } 9 | }, 10 | actions: { 11 | path({ commit }, param) { 12 | commit('theme', param) 13 | } 14 | } 15 | } 16 | 17 | export default main 18 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import swagger from './swagger' 4 | import getters from './getters' 5 | import main from './main' 6 | 7 | Vue.use(Vuex) 8 | 9 | const store = new Vuex.Store({ 10 | modules: { 11 | swagger, 12 | main 13 | }, 14 | getters 15 | }) 16 | 17 | export default store 18 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "ga": true, 4 | "Promise": true, 5 | "cc": true 6 | }, 7 | "plugins": ["html", "json"], 8 | "extends": "elemefe", 9 | "rules": { 10 | "no-restricted-globals": ["error", "event", "fdescribe"], 11 | "semi": [2, "never"] 12 | }, 13 | "parserOptions": { 14 | "ecmaVersion": 6, 15 | "ecmaFeatures": { 16 | "jsx": true 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/views/pages/main/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 28 | 29 | -------------------------------------------------------------------------------- /src/router/route.load.js: -------------------------------------------------------------------------------- 1 | // function LOAD_PAGES_MAP(name) { 2 | // return require.ensure([], () => require(`../views/pages/${name}.vue`)) 3 | // } 4 | 5 | // export const loadPage = function(path) { 6 | // return LOAD_PAGES_MAP(path) 7 | // } 8 | 9 | const LOAD_PAGES_MAP = { 10 | 'zh-CN': name => { 11 | return r => require.ensure([], () => 12 | r(require(`../views/pages/${name}.vue`)), 13 | 'zh-CN') 14 | } 15 | } 16 | 17 | export const loadPage = function(path) { 18 | return LOAD_PAGES_MAP['zh-CN'](path) 19 | } 20 | -------------------------------------------------------------------------------- /src/views/pages/layout/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 23 | 24 | -------------------------------------------------------------------------------- /src/views/pages/swagger/reqJsonView.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 21 | 22 | -------------------------------------------------------------------------------- /src/views/pages/simpleLayout/app-main.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 18 | 19 | 29 | -------------------------------------------------------------------------------- /src/views/pages/layout/app-main.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 18 | 19 | 29 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import App from './App' 5 | import router from './router' 6 | import TennetcnUI from 'tennetcn-ui' 7 | import 'element-ui/lib/theme-chalk/index.css' 8 | import 'tennetcn-ui/lib/styles/index.css' 9 | import '@/styles/index.scss' 10 | Vue.config.productionTip = false 11 | 12 | Vue.use(TennetcnUI) 13 | 14 | import store from './store' 15 | 16 | /* eslint-disable no-new */ 17 | new Vue({ 18 | el: '#app', 19 | store, 20 | router, 21 | render: h => h(App) 22 | }) 23 | -------------------------------------------------------------------------------- /src/views/pages/swagger/mdShow.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 29 | 30 | -------------------------------------------------------------------------------- /src/components/util/file/down.js: -------------------------------------------------------------------------------- 1 | export function writeFileDown(content, fileName, option = {}) { 2 | var a = document.createElement('a') 3 | var url = window.URL.createObjectURL(new Blob([content], 4 | { type: (option.type || mimeType.txt) + ';charset=' + (option.encoding || 'utf-8') })) 5 | a.href = url 6 | a.target = '_blank' 7 | a.download = fileName || 'file.txt' 8 | a.click() 9 | window.URL.revokeObjectURL(url) 10 | } 11 | 12 | export const mimeType = { 13 | txt: 'text/plain', 14 | sql: 'text/plain', 15 | md: 'text/markdown', 16 | pdf: 'application/pdf', 17 | xls: 'application/vnd.ms-excel', 18 | js: 'application/x-javascript', 19 | html: 'text/html' 20 | } 21 | -------------------------------------------------------------------------------- /src/views/pages/layout/sidebar/item.vue: -------------------------------------------------------------------------------- 1 | 31 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 23 | 24 | 36 | -------------------------------------------------------------------------------- /src/views/pages/simpleLayout/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 21 | 22 | -------------------------------------------------------------------------------- /src/views/pages/layout/sidebar/link.vue: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 36 | -------------------------------------------------------------------------------- /src/store/swagger.js: -------------------------------------------------------------------------------- 1 | const swagger = { 2 | state: { 3 | info: null, 4 | path: null, 5 | menus: null, 6 | headers: null 7 | }, 8 | mutations: { 9 | swaggerPath: function(state, result) { 10 | state.path = result 11 | }, 12 | swaggerInfo: function(state, result) { 13 | state.info = result 14 | }, 15 | menus: function(state, result) { 16 | state.menus = result 17 | }, 18 | headers: function(state, result) { 19 | state.headers = result 20 | } 21 | }, 22 | actions: { 23 | path({ commit }, param) { 24 | commit('swaggerPath', param) 25 | }, 26 | info({ commit }, param) { 27 | commit('swaggerInfo', param) 28 | }, 29 | menus({ commit }, param) { 30 | commit('menus', param) 31 | }, 32 | headers({ commit }, param) { 33 | commit('headers', param) 34 | } 35 | } 36 | } 37 | 38 | export default swagger 39 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import routeConfig from './route.config' 4 | import { isEmpty } from 'tennetcn-ui/lib/utils' 5 | import swaggerService from '@/api/swagger' 6 | import store from '@/store' 7 | 8 | Vue.use(Router) 9 | 10 | const createRouter = () => new Router({ 11 | scrollBehavior: () => ({ y: 0 }), 12 | routes: routeConfig 13 | }) 14 | 15 | const router = createRouter() 16 | 17 | var isFirstLoad = true 18 | 19 | router.afterEach((to, from) => { 20 | if (isFirstLoad) { 21 | firstLoad() 22 | isFirstLoad = false 23 | } 24 | }) 25 | 26 | function firstLoad() { 27 | if (router.currentRoute.path !== '/') { 28 | const swaggerPath = window.sessionStorage.swaggerPath 29 | const theme = window.sessionStorage.theme 30 | store.commit('theme', theme) 31 | if (isEmpty(swaggerPath)) { 32 | router.push({ path: '/' }) 33 | } else { 34 | swaggerService.reqAndResolveSwagger(swaggerPath).then(result => { 35 | }) 36 | } 37 | } 38 | } 39 | 40 | export default router 41 | -------------------------------------------------------------------------------- /src/views/pages/swagger/jsonedit.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 48 | 49 | -------------------------------------------------------------------------------- /src/router/route.config.js: -------------------------------------------------------------------------------- 1 | import login from '@/views/pages/login' 2 | import layoutMain from '@/views/pages/layout' 3 | import simpleLayout from '@/views/pages/simpleLayout' 4 | import main from '@/views/pages/main' 5 | import simpleMain from '@/views/pages/simpleMain' 6 | import swagger from '@/views/pages/swagger' 7 | import simpleSwagger from '@/views/pages/simpleSwagger' 8 | 9 | const mainRoute = [ 10 | { 11 | path: '/', 12 | name: 'login', 13 | component: login 14 | }, 15 | { 16 | path: '/main', 17 | name: 'main', 18 | component: layoutMain, 19 | children: [ 20 | { 21 | path: 'index', 22 | name: 'mainIndex', 23 | component: main 24 | } 25 | ] 26 | }, 27 | { 28 | path: '/simpleMain', 29 | name: 'simpleMain', 30 | component: simpleLayout, 31 | children: [ 32 | { 33 | path: 'index', 34 | name: 'simpleMainIndex', 35 | component: simpleMain 36 | } 37 | ] 38 | }, 39 | { 40 | path: '/swagger', 41 | name: 'swagger', 42 | component: layoutMain, 43 | children: [ 44 | { 45 | path: 'index', 46 | name: 'swaggerIndex', 47 | component: swagger 48 | } 49 | ] 50 | }, 51 | { 52 | path: '/simpleSwagger', 53 | name: 'simpleSwagger', 54 | component: simpleLayout, 55 | children: [ 56 | { 57 | path: 'index', 58 | name: 'simpleSwaggerIndex', 59 | component: simpleSwagger 60 | } 61 | ] 62 | } 63 | ] 64 | 65 | const megreRoute = function() { 66 | let route = [] 67 | route = route.concat(mainRoute) 68 | return route 69 | } 70 | 71 | export default megreRoute() 72 | -------------------------------------------------------------------------------- /src/api/swagger.js: -------------------------------------------------------------------------------- 1 | import request from '@/components/util/http/request' 2 | import store from '@/store' 3 | import { resolveMenu } from '@/router/menu.load' 4 | 5 | function reqSwagger(url) { 6 | return new Promise(function(resolve, reject) { 7 | request.get(url, {}, response => { 8 | const result = response.data 9 | resolve(result) 10 | }) 11 | }) 12 | } 13 | 14 | function reqAndResolveSwagger(url) { 15 | return new Promise(function(resolve, reject) { 16 | reqSwagger(url).then(result => { 17 | if (result.swagger !== undefined) { 18 | window.sessionStorage.swaggerPath = url 19 | store.commit('swaggerPath', url) 20 | store.commit('swaggerInfo', result) 21 | 22 | const menus = resolveMenu() 23 | result['menus'] = menus 24 | } 25 | resolve(result) 26 | }) 27 | }) 28 | } 29 | 30 | function resolveSwagger(swaggerJson) { 31 | store.commit('swaggerPath', null) 32 | window.sessionStorage.swaggerPath = null 33 | const result = JSON.parse(swaggerJson) 34 | return new Promise(function(resolve, reject) { 35 | if (result.swagger !== undefined) { 36 | store.commit('swaggerInfo', result) 37 | 38 | const menus = resolveMenu() 39 | result['menus'] = menus 40 | } 41 | resolve(result) 42 | }) 43 | } 44 | 45 | function sendRequest(method, url, requestData) { 46 | return new Promise(function(resolve, reject) { 47 | request[method](url, requestData, response => { 48 | resolve(response) 49 | }) 50 | }) 51 | } 52 | 53 | const swaggerService = { 54 | reqSwagger, 55 | resolveSwagger, 56 | reqAndResolveSwagger, 57 | sendRequest 58 | } 59 | 60 | export default swaggerService 61 | -------------------------------------------------------------------------------- /src/styles/tennetcn-ui/index.scss: -------------------------------------------------------------------------------- 1 | .tc-table{ 2 | .el-table__body tr.current-row{ 3 | background-color: #bad4ee !important; 4 | } 5 | .el-table__body tr.current-row:hover{ 6 | background-color: #bad4ee !important; 7 | } 8 | .el-table--striped .el-table__body tr.el-table__row--striped.current-row td, 9 | .el-table__body tr.current-row>td{ 10 | background-color: #bad4ee !important; 11 | } 12 | .el-table__empty-block { 13 | min-height: 35px; 14 | } 15 | td { 16 | padding: 6px 0px !important; 17 | } 18 | thead{ 19 | th{ 20 | background-color: #E4E7ED; 21 | color: #303133; 22 | padding: 5px 0px !important; 23 | } 24 | } 25 | } 26 | 27 | .el-button--think{ 28 | background-color: #394A5f; 29 | border-color: #394A5f; 30 | color: #fff; 31 | } 32 | .el-button--think:hover{ 33 | background-color: #465B76; 34 | border-color: #6482a9; 35 | color: #fff; 36 | } 37 | .el-button--think:focus{ 38 | background-color: #465B76; 39 | border-color: #6482a9; 40 | color:#fff; 41 | } 42 | 43 | .el-row{ 44 | margin-left: 0px !important; 45 | margin-right: 0px !important; 46 | } 47 | /******* 48 | * 日期选择变小一点 49 | ********/ 50 | .el-date-picker__header{ 51 | margin: 6px; 52 | } 53 | .el-picker-panel__content{ 54 | margin: 8px; 55 | width: auto !important; 56 | } 57 | .el-date-table{ 58 | th { 59 | padding: 3px !important; 60 | } 61 | td { 62 | padding: 3px 0px !important; 63 | } 64 | } 65 | 66 | /** 67 | ** input-tag 修复样式 0.0.14版本修复后,去掉此样式 68 | **/ 69 | .vue-input-tag-wrapper{ 70 | padding-top: 1px; 71 | .input-tag{ 72 | height: 26px; 73 | line-height: 26px; 74 | padding: 0px 4px; 75 | margin-bottom: 1px; 76 | } 77 | } -------------------------------------------------------------------------------- /src/router/menu.load.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | import { isEmpty } from 'tennetcn-ui/lib/utils' 3 | /* 4 | *{name:name,desc:desc,children:[{path:'/login',reqMethod:[]}]} 5 | */ 6 | 7 | const resolveMenu = function() { 8 | var menus = [] 9 | const swaggerInfo = store.getters.swaggerInfo 10 | console.log(swaggerInfo, 'swaggerInfo') 11 | const rootPath = getRootPath() 12 | const paths = swaggerInfo.paths 13 | var tagMap = {} 14 | Array.from(Object.keys(paths)).forEach(path => { 15 | const reqMethod = paths[path] 16 | const firstMethod = reqMethod[Object.keys(reqMethod)[0]] 17 | const firstTag = firstMethod.tags[0] 18 | const title = isEmpty(firstMethod.summary) ? path : firstMethod.summary 19 | var children = [] 20 | children.push({path: path.substr(1, path.length - 1), key: path, reqMethod: reqMethod, meta: {icon: '', title: title}, routeParam: {firstTag: firstTag, path: path}}) 21 | 22 | tagMap[firstTag] = (tagMap[firstTag] || []).concat(children) 23 | }) 24 | 25 | Array.from(swaggerInfo.tags).forEach((tag, pindex) => { 26 | const hidden = tag.name === 'basic-error-controller' 27 | const children = tagMap[tag.name] 28 | children.forEach((child, index) => { 29 | child.routeParam.pindex = pindex 30 | child.routeParam.index = index 31 | }) 32 | console.log(rootPath, 'rootPath') 33 | const menu = Object.assign({meta: {icon: '', title: tag.name }, path: rootPath, key: tag.name, hidden: hidden}, tag, {children: children, routeParam: {}}) 34 | menus.push(menu) 35 | }) 36 | store.commit('menus', menus) 37 | 38 | return menus 39 | } 40 | 41 | function getRootPath() { 42 | const theme = store.getters.theme 43 | if (theme === 'admin') { 44 | return '/swagger' 45 | } else if (theme === 'simple') { 46 | return '/simpleSwagger' 47 | } 48 | } 49 | 50 | export { resolveMenu } 51 | -------------------------------------------------------------------------------- /src/views/pages/layout/sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 56 | 74 | -------------------------------------------------------------------------------- /src/views/pages/layout/sidebar/sidebar-item.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 58 | -------------------------------------------------------------------------------- /src/views/pages/layout/swaggerInfo.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 57 | 58 | -------------------------------------------------------------------------------- /src/views/pages/swagger/assist/buildmd.js: -------------------------------------------------------------------------------- 1 | import { isEmptyObject } from 'tennetcn-ui/lib/utils' 2 | export default { 3 | data() { 4 | return { 5 | } 6 | }, 7 | methods: { 8 | buildMd: function() { 9 | let arrMds = [] 10 | const basePath = this.swaggerInfo.basePath === '/' ? '' : this.swaggerInfo.basePath 11 | const requestUrl = this.methodForm.requestProtocol + this.swaggerInfo.host + basePath 12 | const reqMethod = this.menuInfo.reqMethod[this.activeName] || {} 13 | arrMds.push('# ' + reqMethod.summary) 14 | arrMds.push('## 请求头') 15 | arrMds.push('| 名称 | 描述 |') 16 | arrMds.push('| --- | --- |') 17 | arrMds.push('| Host | `' + requestUrl + '` |') 18 | arrMds.push('| 请求地址 | `' + this.methodForm.requestPath + '` |') 19 | arrMds.push('| 请求方式 | `' + this.activeName + '` |') 20 | arrMds.push('| 响应Content-Type | `' + this.methodForm.contentType + '` |\r\n') 21 | arrMds.push('## 请求参数') 22 | if (this.isPostJson) { 23 | arrMds.push('```') 24 | console.log(this.parameters[0], 'xxxx') 25 | arrMds.push(JSON.stringify(this.parameters[0].schemaDescription, null, '\t')) 26 | arrMds.push('```') 27 | } else { 28 | arrMds.push('| 名称 | 描述 | 是否必填 | 参数类型 | 数据类型 |') 29 | arrMds.push('| --- | --- | --- | --- | --- |') 30 | this.parameters.forEach(item => { 31 | arrMds.push('| ' + item.name + ' | ' + (item.description || '') + ' | ' + item.required + ' | ' + item.in + ' | ' + item.type + ' |') 32 | }) 33 | } 34 | 35 | arrMds.push('\r\n## 响应数据') 36 | const respJson = this.calcComplexParamResp() 37 | if (isEmptyObject(respJson)) { 38 | try { 39 | arrMds.push('```') 40 | arrMds.push(JSON.stringify(this.responseResult, null, ' ')) 41 | arrMds.push('```') 42 | } catch (err) { 43 | console.log(err) 44 | } 45 | } else { 46 | try { 47 | arrMds.push('```') 48 | arrMds.push(JSON.stringify(respJson, null, ' ')) 49 | arrMds.push('```') 50 | } catch (err) { 51 | console.log(err) 52 | } 53 | } 54 | return arrMds.join('\r\n') 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.3.1 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | proxyTable: { 14 | }, 15 | 16 | // Various Dev Server settings 17 | host: 'localhost', // can be overwritten by process.env.HOST 18 | port: 8092, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 19 | autoOpenBrowser: false, 20 | errorOverlay: true, 21 | notifyOnErrors: true, 22 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 23 | 24 | // Use Eslint Loader? 25 | // If true, your code will be linted during bundling and 26 | // linting errors and warnings will be shown in the console. 27 | useEslint: true, 28 | // If true, eslint errors and warnings will also be shown in the error overlay 29 | // in the browser. 30 | showEslintErrorsInOverlay: false, 31 | 32 | /** 33 | * Source Maps 34 | */ 35 | 36 | // https://webpack.js.org/configuration/devtool/#development 37 | devtool: 'cheap-module-eval-source-map', 38 | 39 | // If you have problems debugging vue-files in devtools, 40 | // set this to false - it *may* help 41 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 42 | cacheBusting: true, 43 | 44 | cssSourceMap: true 45 | }, 46 | 47 | build: { 48 | // Template for index.html 49 | index: path.resolve(__dirname, '../dist/index.html'), 50 | 51 | // Paths 52 | assetsRoot: path.resolve(__dirname, '../dist'), 53 | assetsSubDirectory: 'static', 54 | assetsPublicPath: './', 55 | 56 | /** 57 | * Source Maps 58 | */ 59 | 60 | productionSourceMap: true, 61 | // https://webpack.js.org/configuration/devtool/#production 62 | devtool: '#source-map', 63 | 64 | // Gzip off by default as many popular static hosts such as 65 | // Surge or Netlify already gzip all static assets for you. 66 | // Before setting to `true`, make sure to: 67 | // npm install --save-dev compression-webpack-plugin 68 | productionGzip: false, 69 | productionGzipExtensions: ['js', 'css'], 70 | 71 | // Run the build command with an extra argument to 72 | // View the bundle analyzer report after build finishes: 73 | // `npm run build --report` 74 | // Set to `true` or `false` to always turn it on or off 75 | bundleAnalyzerReport: process.env.npm_config_report 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/views/pages/layout/commonSetting.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 77 | 78 | -------------------------------------------------------------------------------- /src/components/util/http/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import store from '@/store' 3 | import { isEmpty } from 'tennetcn-ui/lib/utils' 4 | 5 | axios.defaults.baseURL = '' 6 | axios.defaults.headers.common['Authorization'] = window.sessionStorage.getItem('token') 7 | axios.defaults.headers.common['Access-Control-Allow-Origin'] = '*' 8 | axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8' 9 | axios.defaults.withCredentials = false 10 | 11 | const http = axios.create({ 12 | transformRequest: [function(data) { 13 | let newData = '' 14 | for (const k in data) { 15 | if (data.hasOwnProperty(k) === true && data[k] !== null && data[k] !== undefined) { 16 | newData += encodeURIComponent(k) + '=' + encodeURIComponent(data[k]) + '&' 17 | } 18 | } 19 | return newData 20 | }] 21 | }) 22 | http.interceptors.request.use(setConfig) 23 | const httpJson = axios.create({ 24 | headers: { 25 | 'Content-Type': 'application/json;charset=utf-8' 26 | } 27 | }) 28 | 29 | httpJson.interceptors.request.use(setConfig) 30 | 31 | function setConfig(config) { 32 | const headers = [].concat(store.state.swagger.headers || []) 33 | headers.forEach(item => { 34 | if (item.use && !isEmpty(item.headerInfo)) { 35 | config.headers[item.headerName] = item.headerInfo 36 | } 37 | }) 38 | return config 39 | } 40 | 41 | function apiAxios(method, url, params, success, error) { 42 | execRequest(http({ 43 | method: method, 44 | url: url, 45 | data: method === 'POST' || method === 'PUT' ? params : null, 46 | params: method === 'GET' || method === 'DELETE' ? params : null 47 | }), success, error) 48 | } 49 | 50 | function apiJsonAxios(method, url, params, success, error) { 51 | execRequest(httpJson({ 52 | method: method, 53 | url: url, 54 | data: params 55 | }), success, error) 56 | } 57 | 58 | function execRequest(httpRequest, success, error) { 59 | httpRequest.then(function(res) { 60 | success(res) 61 | }).catch(function(err) { 62 | console.log(err, 'err') 63 | if (error || error === undefined) { 64 | success(err.response) 65 | } else { 66 | error(err.response) 67 | } 68 | }) 69 | } 70 | 71 | export default { 72 | get: function(url, params, response) { 73 | return apiAxios('GET', url, params, response) 74 | }, 75 | post: function(url, params, response) { 76 | return apiAxios('POST', url, params, response) 77 | }, 78 | put: function(url, params, response) { 79 | return apiAxios('PUT', url, params, response) 80 | }, 81 | delete: function(url, params, response) { 82 | return apiAxios('DELETE', url, params, response) 83 | }, 84 | postJson: function(url, params, response) { 85 | return apiJsonAxios('POST', url, params, response) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/views/pages/layout/topbar.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 60 | 61 | 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # think-swagger-ui-vuele 2 | `swagger-ui`有非常多的版本,觉得不太好用,用`postman`,每个接口都要自己进行录入。所以在基于`think-vuele`进行了`swagger`格式`json`的解析,自己实现了一套swaggerui界面。 3 | 4 | swagger分为后端数据提供方方和前端页面展示请求方。从一定角度来看,swagger是一种标准的数据格式的定义,对于不同语言进行实现一些注解API式的东西,能快速生成这种描述`restful`格式的`api`信息的`json`串. 5 | 6 | 此项目模块依赖于[`think-vuele`](http://vuele.tennetcn.com) 7 | 8 | demo:[http://sw.tennetcn.com](http://sw.tennetcn.com) 9 | 10 | github:[https://github.com/chfree/think-swagger-ui-vuele](https://github.com/chfree/think-swagger-ui-vuele) 11 | 12 | ## 使用方式 13 | ### 自行下载编译 14 | ```shell 15 | // 下载代码 16 | git clone https://github.com/chfree/think-swagger-ui-vuele 17 | 18 | // 安装依赖 19 | npm install 20 | 21 | // 直接运行 22 | npm run dev 23 | 24 | // 打包 25 | npm run build 26 | ``` 27 | 28 | ### java项目 maven直接依赖 29 | ```xml 30 | 31 | com.tennetcn.free 32 | think-swagger-ui-starter 33 | 0.0.5 34 | 35 | ``` 36 | 此jar包的开源项目为[`think-free-base`](https://github.com/chfree/think-free-base/tree/master/think-swagger-ui-starter)中的子项目模块 37 | 38 | # V1.1.1 39 | ## 更新功能点 40 | - 显示`application/json`模式请求的参数描述 41 | - 显示了响应示例描述 42 | 43 | # V1.1.0 44 | ## 更新功能点 45 | - 加入了请求响应的时间差 46 | - 可以将某个api的请求响应信息以md的模式查看和导出 47 | - 优化了部分显示内容 48 | 49 | 50 | 51 | # V1.0.0 52 | 53 | ## 登陆 54 | 登陆界面分为`json`模式和`swagger`请求地址访问,没多大区别,只有拿到标准的`swagger`的`json`数据即可。 55 | 56 | 支持两种主题,一种是后端管理系统模式的主题。另外一种也是类似,中间1024px进行居中,两边留白。 57 | 58 | ![swagger_login](http://bedimage.tennetcn.com/tennetcn.com/project/swagger/swagger_login.png) 59 | 60 | ## 主页 61 | 对于我使用过的一个版本的`swagger`来说,当接口数量在`1000+`以上,会等的时间非常长,原因是他一次将所有接口数据进行解析渲染,这个就是慢的原因。 62 | 63 | 所以我将此进行优化,改为先解析出`api`摘要信息,然后在点击摘要的时候进行请求头、请求体的渲染;基本可以做到秒开 64 | 65 | 可以自动填充非`json`请求体的数据,采用的是`mock.Random`。 66 | 67 | 对于json请求体的数据,可以进行`json`格式化编辑,也是非常方便。`json`在线格式化编辑使用的是`josdejong`大神的[`jsoneditor`](https://github.com/josdejong/jsoneditor) 68 | 69 | 对于响应数据直接采用`json`格式化组件进行格式化展示,支持展开层级。再也不用将返回的数据在去找相关的`json`格式化工具进行格式化了。格式化控件采用的是`chenfengjw163`大神的[`vue-json-viewer`](https://github.com/chenfengjw163/vue-json-viewer) 70 | 71 | ![swagger_simple](http://bedimage.tennetcn.com/tennetcn.com/project/swagger/swagger_simple.png) 72 | 73 | ![swagger_edit_json](http://bedimage.tennetcn.com/tennetcn.com/project/swagger/swagger_edit_json.png) 74 | 75 | ![swagger_custom_field](http://bedimage.tennetcn.com/tennetcn.com/project/swagger/swagger_custom_field.png) 76 | 77 | ![swagger_admin](http://bedimage.tennetcn.com/tennetcn.com/project/swagger/swagger_admin.png) 78 | 79 | ## 设置 80 | 在后端api请求的时候,一般都会在请求头中带一些token的验证,来进行用户标识,所以在设置中,进行了自定义请求头的设置,可以方便的设置相关的请求头,在任何一个请求都会自动带上设置的请求信息。 81 | 82 | ![swagger_common_setting](http://bedimage.tennetcn.com/tennetcn.com/project/swagger/swagger_common_setting.png) 83 | 84 | ## swagger 信息展示 85 | 来源于后端swagger配置的相关信息在此处进行展示 86 | ![swagger_info](http://bedimage.tennetcn.com/tennetcn.com/project/swagger/swagger_info.png) 87 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "think-swagger-ui", 3 | "version": "1.1.0", 4 | "description": "A Vue.js project", 5 | "author": "zfree <79763939@qq.com>", 6 | "private": true, 7 | "scripts": { 8 | "bootstrap": "yarn || npm i", 9 | "ndev": "npm run bootstrap && webpack-dev-server --mode development --inline --progress --config build/webpack.dev.conf.js", 10 | "dev": "webpack-dev-server --mode development --inline --progress --config build/webpack.dev.conf.js", 11 | "start": "npm run dev", 12 | "lint": "eslint --ext .js,.vue src", 13 | "nbuild": "rimraf dist && npm run bootstrap && node build/build.js", 14 | "build": "rimraf dist && node build/build.js" 15 | }, 16 | "dependencies": { 17 | "axios": "0.18.0", 18 | "tennetcn-ui": "0.0.70", 19 | "vue": "2.6.10", 20 | "vue-json-viewer": "^2.2.1", 21 | "vue-meditor": "^2.1.1", 22 | "vue-router": "^3.0.1", 23 | "vuex": "^3.1.1" 24 | }, 25 | "devDependencies": { 26 | "autoprefixer": "8.5.0", 27 | "babel-core": "6.26.0", 28 | "babel-eslint": "^10.0.1", 29 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 30 | "babel-loader": "7.1.5", 31 | "babel-plugin-syntax-jsx": "^6.18.0", 32 | "babel-plugin-transform-runtime": "6.23.0", 33 | "babel-plugin-transform-vue-jsx": "3.7.0", 34 | "babel-preset-env": "1.7.0", 35 | "babel-preset-stage-2": "6.24.1", 36 | "chalk": "2.4.1", 37 | "copy-webpack-plugin": "4.5.2", 38 | "css-loader": "^2.1.0", 39 | "echarts": "^4.2.1", 40 | "eslint": "4.19.1", 41 | "eslint-config-elemefe": "0.1.1", 42 | "eslint-friendly-formatter": "4.0.1", 43 | "eslint-loader": "2.0.0", 44 | "eslint-plugin-html": "^4.0.1", 45 | "eslint-plugin-json": "^1.2.0", 46 | "eslint-plugin-vue": "4.7.1", 47 | "extract-text-webpack-plugin": "^3.0.2", 48 | "file-loader": "1.1.11", 49 | "friendly-errors-webpack-plugin": "1.7.0", 50 | "html-webpack-plugin": "4.0.0-alpha", 51 | "jsoneditor": "^7.0.4", 52 | "mini-css-extract-plugin": "0.4.1", 53 | "mockjs": "^1.0.1-beta3", 54 | "node-notifier": "5.2.1", 55 | "node-sass": "^4.14.1", 56 | "optimize-css-assets-webpack-plugin": "5.0.0", 57 | "ora": "3.0.0", 58 | "portfinder": "1.0.16", 59 | "rimraf": "2.6.2", 60 | "sass-loader": "7.0.3", 61 | "semver": "5.5.0", 62 | "shelljs": "0.8.2", 63 | "style-loader": "^0.23.1", 64 | "transliteration": "^1.1.11", 65 | "uglifyjs-webpack-plugin": "^1.1.1", 66 | "url-loader": "1.0.1", 67 | "vue-loader": "15.4.2", 68 | "vue-template-compiler": "2.6.10", 69 | "webpack": "4.16.5", 70 | "webpack-cli": "3.1.0", 71 | "webpack-dev-server": "^3.1.10", 72 | "webpack-merge": "^4.2.1", 73 | "webpack-node-externals": "^1.7.2" 74 | }, 75 | "engines": { 76 | "node": ">= 6.0.0", 77 | "npm": ">= 3.0.0" 78 | }, 79 | "browserslist": [ 80 | "> 1%", 81 | "last 2 versions", 82 | "not ie <= 8" 83 | ] 84 | } 85 | -------------------------------------------------------------------------------- /src/styles/main/sidebar-simple.scss: -------------------------------------------------------------------------------- 1 | #app.simple { 2 | $top-title-bar-height: 51px; 3 | $left-container-width: 200px; 4 | $menuBg:#fff; 5 | $subMenuBg:#fff; 6 | $menuHover:#ecf5ff; 7 | 8 | // 主体区域 9 | .main-container { 10 | min-height: calc(100% - var(80px)); 11 | transition: margin-left .28s; 12 | margin-left: $left-container-width; 13 | position: relative; 14 | } 15 | // 侧边栏 16 | .sidebar-container { 17 | transition: width 0.28s; 18 | width: $left-container-width !important; 19 | height: calc(100% - var($top-title-bar-height)); 20 | position: fixed; 21 | font-size: 0px; 22 | top: $top-title-bar-height; 23 | bottom: 0; 24 | left: 0; 25 | z-index: 1001; 26 | overflow: hidden; 27 | //reset element-ui css 28 | .horizontal-collapse-transition { 29 | transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out; 30 | } 31 | .el-scrollbar__bar.is-vertical{ 32 | right: 0px; 33 | } 34 | .scrollbar-wrapper { 35 | overflow-x: hidden!important; 36 | .el-scrollbar__view { 37 | height: 100%; 38 | } 39 | } 40 | .is-horizontal { 41 | display: none; 42 | } 43 | a { 44 | display: inline-block; 45 | width: 100%; 46 | overflow: hidden; 47 | } 48 | .svg-icon { 49 | margin-right: 16px; 50 | } 51 | .el-menu { 52 | border: none; 53 | height: 100%; 54 | width: 100% !important; 55 | } 56 | } 57 | .hideSidebar { 58 | .sidebar-container { 59 | width: 36px !important; 60 | } 61 | .main-container { 62 | margin-left: 36px; 63 | } 64 | .submenu-title-noDropdown { 65 | padding-left: 10px !important; 66 | position: relative; 67 | .el-tooltip { 68 | padding: 0 10px !important; 69 | } 70 | } 71 | .el-submenu { 72 | overflow: hidden; 73 | &>.el-submenu__title { 74 | padding-left: 10px !important; 75 | .el-submenu__icon-arrow { 76 | display: none; 77 | } 78 | } 79 | } 80 | .el-menu--collapse { 81 | .el-submenu { 82 | &>.el-submenu__title { 83 | &>span { 84 | height: 0; 85 | width: 0; 86 | overflow: hidden; 87 | visibility: hidden; 88 | display: inline-block; 89 | } 90 | } 91 | } 92 | } 93 | } 94 | .sidebar-container .nest-menu .el-submenu>.el-submenu__title, 95 | .sidebar-container .el-submenu .el-menu-item:not(.is-active) { 96 | min-width: $left-container-width !important; 97 | background-color: $subMenuBg !important; 98 | &:hover { 99 | background-color: $menuHover !important; 100 | } 101 | } 102 | .el-menu--collapse .el-menu .el-submenu { 103 | min-width: $left-container-width !important; 104 | } 105 | .sidebar-container { 106 | .el-submenu__title,.el-menu-item{ 107 | height: 50px !important; 108 | line-height: 50px !important; 109 | font-size: 14px; 110 | font-weight: 600; 111 | } 112 | .el-submenu .el-menu-item{ 113 | height: 48px !important; 114 | line-height: 48px !important; 115 | font-size: 14px; 116 | font-weight: 600; 117 | } 118 | } 119 | 120 | //适配移动端 121 | .mobile { 122 | .main-container { 123 | margin-left: 0px; 124 | } 125 | .sidebar-container { 126 | transition: transform .28s; 127 | width: 180px !important; 128 | } 129 | &.hideSidebar { 130 | .sidebar-container { 131 | transition-duration: 0.3s; 132 | transform: translate3d(-180px, 0, 0); 133 | } 134 | } 135 | .logo-img{ 136 | display: none; 137 | } 138 | } 139 | .withoutAnimation { 140 | .main-container, 141 | .sidebar-container { 142 | transition: none; 143 | } 144 | } 145 | } 146 | 147 | .el-menu--vertical{ 148 | & >.el-menu{ 149 | .svg-icon{ 150 | margin-right: 16px; 151 | } 152 | } 153 | 154 | 155 | } 156 | -------------------------------------------------------------------------------- /src/styles/main/sidebar.scss: -------------------------------------------------------------------------------- 1 | #app.main { 2 | $top-title-bar-height: 50px; 3 | $left-container-width: 200px; 4 | $menuBg:#394A60; 5 | $subMenuBg:#394A60; 6 | $menuHover:#1d61a0; 7 | 8 | // 主体区域 9 | .main-container { 10 | min-height: calc(100% - var(80px)); 11 | transition: margin-left .28s; 12 | margin-left: $left-container-width; 13 | position: relative; 14 | } 15 | // 侧边栏 16 | .sidebar-container { 17 | transition: width 0.28s; 18 | width: $left-container-width !important; 19 | height: calc(100% - var($top-title-bar-height)); 20 | position: fixed; 21 | font-size: 0px; 22 | top: $top-title-bar-height; 23 | bottom: 0; 24 | left: 0; 25 | z-index: 1001; 26 | overflow: hidden; 27 | //reset element-ui css 28 | .horizontal-collapse-transition { 29 | transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out; 30 | } 31 | .el-scrollbar__bar.is-vertical{ 32 | right: 0px; 33 | } 34 | .scrollbar-wrapper { 35 | overflow-x: hidden!important; 36 | .el-scrollbar__view { 37 | height: 100%; 38 | } 39 | } 40 | .is-horizontal { 41 | display: none; 42 | } 43 | a { 44 | display: inline-block; 45 | width: 100%; 46 | overflow: hidden; 47 | } 48 | .svg-icon { 49 | margin-right: 16px; 50 | } 51 | .el-menu { 52 | border: none; 53 | height: 100%; 54 | width: 100% !important; 55 | } 56 | } 57 | .hideSidebar { 58 | .sidebar-container { 59 | width: 36px !important; 60 | } 61 | .main-container { 62 | margin-left: 36px; 63 | } 64 | .submenu-title-noDropdown { 65 | padding-left: 10px !important; 66 | position: relative; 67 | .el-tooltip { 68 | padding: 0 10px !important; 69 | } 70 | } 71 | .el-submenu { 72 | overflow: hidden; 73 | &>.el-submenu__title { 74 | padding-left: 10px !important; 75 | .el-submenu__icon-arrow { 76 | display: none; 77 | } 78 | } 79 | } 80 | .el-menu--collapse { 81 | .el-submenu { 82 | &>.el-submenu__title { 83 | &>span { 84 | height: 0; 85 | width: 0; 86 | overflow: hidden; 87 | visibility: hidden; 88 | display: inline-block; 89 | } 90 | } 91 | } 92 | } 93 | } 94 | .sidebar-container .nest-menu .el-submenu>.el-submenu__title, 95 | .sidebar-container .el-submenu .el-menu-item:not(.is-active) { 96 | min-width: $left-container-width !important; 97 | background-color: $subMenuBg !important; 98 | &:hover { 99 | background-color: $menuHover !important; 100 | } 101 | } 102 | .el-menu--collapse .el-menu .el-submenu { 103 | min-width: $left-container-width !important; 104 | } 105 | .sidebar-container { 106 | .el-submenu__title,.el-menu-item{ 107 | height: 50px !important; 108 | line-height: 50px !important; 109 | font-size: 14px; 110 | font-weight: 600; 111 | } 112 | .el-submenu .el-menu-item{ 113 | height: 48px !important; 114 | line-height: 48px !important; 115 | font-size: 14px; 116 | font-weight: 600; 117 | } 118 | } 119 | 120 | //适配移动端 121 | .mobile { 122 | .main-container { 123 | margin-left: 0px; 124 | } 125 | .sidebar-container { 126 | transition: transform .28s; 127 | width: 180px !important; 128 | } 129 | &.hideSidebar { 130 | .sidebar-container { 131 | transition-duration: 0.3s; 132 | transform: translate3d(-180px, 0, 0); 133 | } 134 | } 135 | .logo-img{ 136 | display: none; 137 | } 138 | } 139 | .withoutAnimation { 140 | .main-container, 141 | .sidebar-container { 142 | transition: none; 143 | } 144 | } 145 | } 146 | 147 | .el-menu--vertical{ 148 | & >.el-menu{ 149 | .svg-icon{ 150 | margin-right: 16px; 151 | } 152 | } 153 | 154 | 155 | } 156 | -------------------------------------------------------------------------------- /src/views/pages/login/index.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 102 | 103 | -------------------------------------------------------------------------------- /src/views/pages/swagger/assist/swagger.helper.js: -------------------------------------------------------------------------------- 1 | import { isEmpty, isEmptyObject } from 'tennetcn-ui/lib/utils' 2 | export default { 3 | data() { 4 | return { 5 | methodForm: { 6 | requestProtocol: 'http://', 7 | contentType: null, 8 | requestPath: this.$route.query.path, 9 | requestMethod: this.activeName 10 | }, 11 | responseResult: {}, 12 | paramColumn: [ 13 | { text: '启用', name: 'open', width: '60', editable: true, type: 'checkbox' }, 14 | { text: '参数', name: 'name', width: '180', editable: true }, 15 | { text: '值', name: 'value', editable: true, type: 'input' }, 16 | { text: '描述', name: 'description', width: '180' }, 17 | { text: '是否必填', name: 'required', width: '80' }, 18 | { text: '参数类型', name: 'in', width: '120' }, 19 | { text: '数据类型', name: 'type', width: '120' } 20 | ], 21 | simpleParamColumn: [ 22 | { text: '启用', name: 'open', width: '50', editable: true, type: 'checkbox' }, 23 | { text: '参数', name: 'name', width: '120', editable: true }, 24 | { text: '值', name: 'value', editable: true, type: 'input' }, 25 | { text: '描述', name: 'description', width: '120' }, 26 | { text: '必填', name: 'required', width: '50' }, 27 | { text: '参数类型', name: 'in', width: '80' }, 28 | { text: '数据类型', name: 'type', width: '80' } 29 | ], 30 | customParamItem: { 31 | category: 'custom', 32 | disabled: null, 33 | in: 'query', 34 | name: '', 35 | editable: true, 36 | open: true, 37 | required: false, 38 | type: 'string' 39 | }, 40 | customParam: [], 41 | responseTimeInfo: { 42 | requestTime: null, 43 | responseTime: null, 44 | diffTime: null 45 | } 46 | } 47 | }, 48 | computed: { 49 | columns: function() { 50 | if (this.theme === 'admin') { 51 | return this.paramColumn 52 | } else if (this.theme === 'simple') { 53 | return this.simpleParamColumn 54 | } 55 | }, 56 | menuInfo() { 57 | if (this.menus === null) { 58 | return null 59 | } 60 | const parentMenu = this.menus[this.$route.query.pindex] 61 | if (parentMenu === null) { 62 | return null 63 | } 64 | return parentMenu.children[this.$route.query.index] 65 | }, 66 | tabs() { 67 | if (this.menuInfo === null) { 68 | return [] 69 | } 70 | const reqMethod = this.menuInfo.reqMethod 71 | const tabs = Object.keys(reqMethod) 72 | 73 | this.activeName = tabs[0] 74 | return tabs 75 | }, 76 | reqMethod() { 77 | if (this.menuInfo === null) { 78 | return {} 79 | } 80 | return this.menuInfo.reqMethod[this.activeName] || {} 81 | }, 82 | producesProviders() { 83 | const produces = this.reqMethod.produces 84 | if (isEmpty(produces)) { 85 | return [] 86 | } 87 | 88 | return produces.map((item, index) => { 89 | if (index === 0) { 90 | this.methodForm.contentType = item 91 | } 92 | return { text: item, value: item, id: index } 93 | }) 94 | }, 95 | parameters() { 96 | let params = (this.reqMethod.parameters || []) 97 | this.isPostJson = false 98 | params.forEach(item => { 99 | this.$set(item, 'open', true) 100 | this.$set(item, 'editable', false) 101 | if (!isEmpty(item.schema) && !isEmpty(item.schema.$ref)) { 102 | item.type = 'json' 103 | this.isPostJson = true 104 | const value = JSON.stringify(this.calcComplexParam(item), null, ' ') 105 | this.$set(item, 'value', value) 106 | this.$set(item, 'defaultValue', value) 107 | // schema description 108 | item.schemaDescription = this.calcComplexParamDescription(item) 109 | } 110 | }) 111 | return params.concat(this.customParam) 112 | } 113 | }, 114 | methods: { 115 | calcComplexParamDescription(item) { 116 | return this.calcComplexParam(item, true) 117 | }, 118 | calcComplexParamResp() { 119 | const resps = this.menuInfo.reqMethod[this.activeName].responses 120 | return this.calcComplexParam(resps['200'], true) 121 | }, 122 | calcComplexParam(item, isDesc) { 123 | var result = {} 124 | // 不是复杂属性 125 | if (isEmpty(item.schema) || isEmpty(item.schema.$ref)) { 126 | return result 127 | } 128 | this.loopCalcComplexParam(item.schema, result, isDesc) 129 | return result 130 | }, 131 | loopCalcComplexParam(parentRefProperty, parentObj, isDesc) { 132 | if (!isEmpty(parentRefProperty.$ref)) { 133 | const ref = this.getDefinName(parentRefProperty.$ref) 134 | const refDefin = this.swaggerInfo.definitions[ref] 135 | 136 | for (var key in refDefin.properties) { 137 | const refProperty = refDefin.properties[key] 138 | if (refProperty.type === 'array') { 139 | var childArr = [] 140 | this.loopCalcComplexParamArr(refProperty, childArr, isDesc) 141 | if (isDesc) { 142 | if (isEmpty(refProperty.items.$ref)) { 143 | parentObj[key] = [refProperty.description + '(' + refProperty.items.type + ')'] 144 | } else { 145 | parentObj[key] = childArr 146 | } 147 | } else { 148 | parentObj[key] = childArr 149 | } 150 | } else { 151 | var childObj = {} 152 | // 继续计算子级 153 | this.loopCalcComplexParam(refProperty, childObj, isDesc) 154 | if (isDesc) { 155 | if (isEmpty(refProperty.type)) { 156 | parentObj[key] = childObj 157 | } else { 158 | parentObj[key] = refProperty.description + '(' + refProperty.type + ')' 159 | } 160 | } else { 161 | parentObj[key] = isEmptyObject(childObj) ? (refProperty.type === 'integer' ? 0 : '') : childObj 162 | } 163 | 164 | } 165 | } 166 | } 167 | }, 168 | loopCalcComplexParamArr(parentRefProperty, parentArr, isDesc) { 169 | if (isEmpty(parentRefProperty.items.$ref)) { 170 | if (parentRefProperty.items.type === 'string') { 171 | parentArr.push('') 172 | } else { 173 | parentArr.push([]) 174 | } 175 | } else { 176 | var childObj = {} 177 | // 继续计算子级 178 | this.loopCalcComplexParam(parentRefProperty.items, childObj, isDesc) 179 | 180 | parentArr.push(childObj) 181 | } 182 | }, 183 | getDefinName(refFull) { 184 | return refFull.replace('#/definitions/', '') 185 | }, 186 | tabName(name) { 187 | const reqMethod = this.menuInfo.reqMethod[name] || {} 188 | return reqMethod.summary + '-' + name 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/views/pages/swagger/index.vue: -------------------------------------------------------------------------------- 1 | 128 | 129 | 279 | 280 | --------------------------------------------------------------------------------