├── .browserslistrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── LICENSE ├── README.md ├── babel.config.js ├── cypress.json ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── assets │ ├── example.png │ ├── example2.png │ ├── example3.png │ ├── example4.png │ ├── example5.png │ └── logo.png ├── base │ └── BaseComponent.ts ├── components │ ├── GeneratorCodeDialog │ │ ├── components │ │ │ ├── ResultCode.vue │ │ │ └── TemplateConfig.vue │ │ └── index.vue │ ├── GeneratorCodeFileDialog │ │ ├── index.vue │ │ └── util │ │ │ └── FileCodeUtil.ts │ └── VersionView │ │ ├── dialog.vue │ │ └── index.vue ├── config │ ├── CodeResolve.ts │ ├── CodeTemplate.ts │ └── Color.ts ├── data │ └── Version.ts ├── entity │ ├── ApiDocs.ts │ ├── Path.ts │ └── Propertie.ts ├── main.ts ├── shims-tsx.d.ts ├── shims-vue.d.ts ├── store │ └── index.ts ├── util │ ├── FinalValue.ts │ ├── HttpUtil.ts │ ├── Marked.ts │ └── index.ts └── views │ └── index │ ├── components │ ├── RequestCode │ │ └── index.vue │ ├── ResponseCode │ │ └── index.vue │ └── SearchHeader │ │ ├── components │ │ ├── SelectPath.vue │ │ └── ServiceConfigDialog.vue │ │ ├── entity │ │ └── Project.ts │ │ └── index.vue │ └── index.vue ├── tests └── e2e │ ├── .eslintrc.js │ ├── plugins │ └── index.js │ ├── specs │ └── test.js │ └── support │ ├── commands.js │ └── index.js ├── tsconfig.json └── vue.config.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | README.md 2 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'eslint:recommended', 8 | 'plugin:vue/base', 9 | 'plugin:vue/essential', 10 | 'plugin:vue/strongly-recommended', 11 | 'plugin:vue/recommended', 12 | '@vue/standard', 13 | '@vue/typescript' 14 | ], 15 | rules: { 16 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 17 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 18 | 'vue/max-attributes-per-line': ['error', { 19 | 'singleline': 5, 20 | 'multiline': { 21 | 'max': 1, 22 | 'allowFirstLine': false 23 | } 24 | }], 25 | 'no-unused-vars': 'off', 26 | 'vue/no-v-html': 'off', 27 | "vue/singleline-html-element-content-newline": "off" 28 | }, 29 | parserOptions: { 30 | parser: '@typescript-eslint/parser' 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | /tests/e2e/videos/ 6 | /tests/e2e/screenshots/ 7 | 8 | 9 | # local env files 10 | .env.local 11 | .env.*.local 12 | 13 | # Log files 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | pnpm-debug.log* 18 | 19 | # Editor directories and files 20 | .idea 21 | .vscode 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 木兰宽松许可证, 第2版 2 | 3 | 木兰宽松许可证, 第2版 4 | 2020年1月 http://license.coscl.org.cn/MulanPSL2 5 | 6 | 7 | 您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束: 8 | 9 | 0. 定义 10 | 11 | “软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。 12 | 13 | “贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。 14 | 15 | “贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 16 | 17 | “法人实体”是指提交贡献的机构及其“关联实体”。 18 | 19 | “关联实体”是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 20 | 21 | 1. 授予版权许可 22 | 23 | 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。 24 | 25 | 2. 授予专利许可 26 | 27 | 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。 28 | 29 | 3. 无商标许可 30 | 31 | “本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。 32 | 33 | 4. 分发限制 34 | 35 | 您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。 36 | 37 | 5. 免责声明与责任限制 38 | 39 | “软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。 40 | 41 | 6. 语言 42 | “本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。 43 | 44 | 条款结束 45 | 46 | 如何将木兰宽松许可证,第2版,应用到您的软件 47 | 48 | 如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步: 49 | 50 | 1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; 51 | 52 | 2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; 53 | 54 | 3, 请将如下声明文本放入每个源文件的头部注释中。 55 | 56 | Copyright (c) [Year] [name of copyright holder] 57 | [Software Name] is licensed under Mulan PSL v2. 58 | You can use this software according to the terms and conditions of the Mulan PSL v2. 59 | You may obtain a copy of Mulan PSL v2 at: 60 | http://license.coscl.org.cn/MulanPSL2 61 | THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 62 | See the Mulan PSL v2 for more details. 63 | 64 | 65 | Mulan Permissive Software License,Version 2 66 | 67 | Mulan Permissive Software License,Version 2 (Mulan PSL v2) 68 | January 2020 http://license.coscl.org.cn/MulanPSL2 69 | 70 | Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions: 71 | 72 | 0. Definition 73 | 74 | Software means the program and related documents which are licensed under this License and comprise all Contribution(s). 75 | 76 | Contribution means the copyrightable work licensed by a particular Contributor under this License. 77 | 78 | Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License. 79 | 80 | Legal Entity means the entity making a Contribution and all its Affiliates. 81 | 82 | Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity. 83 | 84 | 1. Grant of Copyright License 85 | 86 | Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not. 87 | 88 | 2. Grant of Patent License 89 | 90 | Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken. 91 | 92 | 3. No Trademark License 93 | 94 | No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4. 95 | 96 | 4. Distribution Restriction 97 | 98 | You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software. 99 | 100 | 5. Disclaimer of Warranty and Limitation of Liability 101 | 102 | THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 103 | 104 | 6. Language 105 | 106 | THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL. 107 | 108 | END OF THE TERMS AND CONDITIONS 109 | 110 | How to Apply the Mulan Permissive Software License,Version 2 (Mulan PSL v2) to Your Software 111 | 112 | To apply the Mulan PSL v2 to your work, for easy identification by recipients, you are suggested to complete following three steps: 113 | 114 | i Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner; 115 | 116 | ii Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package; 117 | 118 | iii Attach the statement to the appropriate annotated syntax at the beginning of each source file. 119 | 120 | 121 | Copyright (c) [Year] [name of copyright holder] 122 | [Software Name] is licensed under Mulan PSL v2. 123 | You can use this software according to the terms and conditions of the Mulan PSL v2. 124 | You may obtain a copy of Mulan PSL v2 at: 125 | http://license.coscl.org.cn/MulanPSL2 126 | THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 127 | See the Mulan PSL v2 for more details. 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swagger前端替换工具 2 | 3 | - 工具在线使用地址:[http://xietiansheng.cn/swager-replace-tools](http://xietiansheng.cn/swagger-replace-tools) 4 | - gitee:[https://gitee.com/XieTS/swagger-ui-replace](https://gitee.com/XieTS/swagger-ui-replace) 5 | - github:[https://github.com/xietiansheng/swagger-replace-tools](https://github.com/xietiansheng/swagger-replace-tools) 6 | 7 | (访问公司内部局域网Swagger时,请检查Network中请求是否跨域,若请求跨域请<拉取代码在本地配置跨域解决>或<让后端开发人员配置允许跨域>) 8 | 9 | ### 效果展示 10 | > 数据展示 11 | 12 | ![](./src/assets/example.png) 13 | 14 | > 接口搜索 15 | 16 | ![](./src/assets/example4.png) 17 | 18 | > JavaScript 代码生成 19 | 20 | ![](./src/assets/example2.png) 21 | 22 | > Vue 代码生成 23 | 24 | ![](./src/assets/example3.png) 25 | 26 | > Dart 代码生成 27 | 28 | ![](./src/assets/example5.png) 29 | 30 | * 任何语言代码均可通过模板语法自由配置 31 | 32 | ### 开发背景 33 | 34 | *原生Swagger页面对于前端开发者来说并不是很友好,页面交互能力主要存在如下问题:* 35 | 36 | 1. 后端修改了某个接口,前端需要先找对该接口对应的controller层,接着在该controller下挨个查找,耗时耗力. 37 | 2. 请求参数请求结果众多字段复制粘贴,大量重复操作. 38 | 3. 新的页面开发,大量代码反复书写,劳心费力. 39 | 40 | 41 | ### 功能汇总 42 | 43 | 1. 接口搜索:支持当前项目下所有接口一键搜索. 44 | 2. 参数预览:请求参数,返回参数类型注释预览. 45 | 3. 代码生成:目前支持请求,返回参数一键生成JavaScript、TypeScript、Vue代码生成. 46 | 4. 代码模板:支持代码模板高度可配置,满足所有代码生成需求. 47 | 5. 代码文件生成(内测中):支持一键生成文件代码,完美解决新项目重复书写代码问题. 48 | 49 | ### 使用环境 50 | 51 | > 线上项目能够覆盖绝大多数使用场景,解析完Swagger地址之后会优先使用本地请求去请求swagger内容,在本地请求跨域的情况下(例如公网测试环境swagger跨域)则会使用node服务器代理当前请求来解决跨域问题,以下是两种情况的使用方式: 52 | 53 | * 本地使用: 54 | ``` 55 | 1. npm run install 56 | 2. 配置 vue.config.js proxy的target代理地址解决跨域问题 57 | 3. npm run serve 58 | ``` 59 | 60 | - 线上使用: 61 | - [http://xietiansheng.cn/swagger-replace-tools](http://xietiansheng.cn/swagger-replace-tools) 62 | 63 | ### 操作手册 64 | 65 | - 项目选择,参数预览 66 | 67 | - 输入任意swagger地址(域名 + 端口),可以打开任意swagger页面量复制地址,系统会自动解析出标准的swagger地址。 68 | - 选择对应的项目。 69 | - 选择对应的接口,支持搜索。 70 | - 选择需要生成的层级代码点击生成代码按钮。 71 | 72 | *系统会将所有选择缓存至本地,下次打开自动读取* 73 | 74 | 75 | - 代码模板配置 76 | 77 | - 找到对应的参数层级,点击生成代码。 78 | - 配置左侧代码模板,模板语法如下: 79 | - $a:属性key 80 | - $b:属性注释 81 | - $c:属性默认值 82 | - $d: 属性的类型(目前仅支持int,String,Array,后期会开放配置功能) 83 | - !!!for:循环语句,$a,$b,$c,$d需要包裹在!!!for中才会生效 84 | - 所有模板配置系统会自动缓存到本地 85 | - 模板内支持多个!!!for语法嵌套(参考dart语法) 86 | 87 | #### 模板语法示例 88 | 89 | ```javascript 90 | 91 | // 后端接口请求参数 92 | key - 注释 - 值 93 | skuName - 商品名称 - T恤 94 | skuCode - 商品码 - SKU20210331 95 | skuPrice - 商品价格 - 200 96 | 97 | // 代码模板配置 98 | const data = { 99 | !!!for 100 | // $b 101 | $a = $c 102 | !!!for 103 | 104 | // 这里输入任意代码,都会被复制到结果当中 105 | constructor(){ 106 | super() 107 | } 108 | } 109 | 110 | // 替换结果 111 | const params = { 112 | // 商品名称 113 | skuName: '', 114 | // 商品码 115 | skuCode: '', 116 | // 商品价格 117 | skuPrice: '', 118 | 119 | // 这里输入任意代码,都会被复制到结果当中 120 | constructor (data = {}) { 121 | super() 122 | this.setData(data) 123 | } 124 | } 125 | ``` 126 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginsFile": "tests/e2e/plugins/index.js" 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "swagger-ui-replace", 3 | "version": "2.1.6", 4 | "private": false, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "test:e2e": "vue-cli-service test:e2e", 9 | "lint": "vue-cli-service lint" 10 | }, 11 | "dependencies": { 12 | "axios": "^0.21.1", 13 | "core-js": "^3.6.5", 14 | "element-ui": "^2.15.1", 15 | "file-saver": "^2.0.5", 16 | "highlight.js": "^10.3.2", 17 | "marked": "^1.2.3", 18 | "vue": "^2.6.11", 19 | "vue-class-component": "^7.2.3", 20 | "vue-property-decorator": "^9.1.2", 21 | "vuex": "^3.6.2" 22 | }, 23 | "devDependencies": { 24 | "@types/file-saver": "^2.0.1", 25 | "@types/highlight.js": "^10.1.0", 26 | "@types/marked": "^1.1.0", 27 | "@typescript-eslint/eslint-plugin": "^2.33.0", 28 | "@typescript-eslint/parser": "^2.33.0", 29 | "@vue/cli-plugin-babel": "~4.5.0", 30 | "@vue/cli-plugin-e2e-cypress": "~4.5.0", 31 | "@vue/cli-plugin-eslint": "~4.5.0", 32 | "@vue/cli-plugin-typescript": "~4.5.0", 33 | "@vue/cli-service": "~4.5.0", 34 | "@vue/eslint-config-standard": "^5.1.2", 35 | "@vue/eslint-config-typescript": "^5.0.2", 36 | "eslint": "^6.7.2", 37 | "eslint-plugin-import": "^2.20.2", 38 | "eslint-plugin-node": "^11.1.0", 39 | "eslint-plugin-promise": "^4.2.1", 40 | "eslint-plugin-standard": "^4.0.0", 41 | "eslint-plugin-vue": "^6.2.2", 42 | "node-sass": "^4.12.0", 43 | "sass-loader": "^8.0.2", 44 | "typescript": "~3.9.3", 45 | "vue-template-compiler": "^2.6.11" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xietiansheng/swagger-ui-replace/4449edc6f8e11af777cedb41b0d42edf82019e62/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 19 | 20 | 21 | 24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 48 | 49 | 118 | -------------------------------------------------------------------------------- /src/assets/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xietiansheng/swagger-ui-replace/4449edc6f8e11af777cedb41b0d42edf82019e62/src/assets/example.png -------------------------------------------------------------------------------- /src/assets/example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xietiansheng/swagger-ui-replace/4449edc6f8e11af777cedb41b0d42edf82019e62/src/assets/example2.png -------------------------------------------------------------------------------- /src/assets/example3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xietiansheng/swagger-ui-replace/4449edc6f8e11af777cedb41b0d42edf82019e62/src/assets/example3.png -------------------------------------------------------------------------------- /src/assets/example4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xietiansheng/swagger-ui-replace/4449edc6f8e11af777cedb41b0d42edf82019e62/src/assets/example4.png -------------------------------------------------------------------------------- /src/assets/example5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xietiansheng/swagger-ui-replace/4449edc6f8e11af777cedb41b0d42edf82019e62/src/assets/example5.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xietiansheng/swagger-ui-replace/4449edc6f8e11af777cedb41b0d42edf82019e62/src/assets/logo.png -------------------------------------------------------------------------------- /src/base/BaseComponent.ts: -------------------------------------------------------------------------------- 1 | import { Vue } from 'vue-property-decorator' 2 | 3 | export class DialogComponent extends Vue { 4 | visible: boolean = false 5 | 6 | open (options?: object) { 7 | this.visible = true 8 | } 9 | 10 | close () { 11 | this.visible = false 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/GeneratorCodeDialog/components/ResultCode.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 39 | 40 | 43 | -------------------------------------------------------------------------------- /src/components/GeneratorCodeDialog/components/TemplateConfig.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 95 | 96 | 106 | -------------------------------------------------------------------------------- /src/components/GeneratorCodeDialog/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 57 | 58 | 72 | -------------------------------------------------------------------------------- /src/components/GeneratorCodeFileDialog/index.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 63 | 64 | 66 | -------------------------------------------------------------------------------- /src/components/GeneratorCodeFileDialog/util/FileCodeUtil.ts: -------------------------------------------------------------------------------- 1 | import CodeResolve from '@/config/CodeResolve' 2 | import CodeTemplate from '@/config/CodeTemplate' 3 | 4 | export default class FileCodeUtil { 5 | static generator (config: any) { 6 | const tsCode = '`' + CodeResolve.JavaScript(config.properties, CodeTemplate.TypeScriptFile) + 7 | '\n\n' + CodeResolve.JavaScript(config.parameters, CodeTemplate.QueryParams) + '' + 8 | '\n\n' + CodeTemplate.ListModel + '' + 9 | '`' 10 | const vueCode = '`' + CodeResolve.Vue(config.properties, CodeTemplate.VueFile) + '`' 11 | let text = ` 12 | const fs = require('fs') 13 | fs.mkdir('./${config.folderName}', err => { 14 | if (err) { 15 | return console.error(err) 16 | } 17 | // 植入ts代码 18 | fs.mkdirSync('./${config.folderName}/model') 19 | fs.writeFileSync('./${config.folderName}/model/index.ts', ${tsCode}) 20 | fs.writeFileSync('./${config.folderName}/index.vue', ${vueCode}) 21 | }) 22 | ` 23 | // 替换modelName listModelName 24 | text = text.replaceAll('$name:class:model', config.modelName) 25 | text = text.replaceAll('$name:class:listModel', config.listModelName) 26 | text = text.replaceAll('$name:class:paramsModel', config.paramsModelName) 27 | const str = new Blob([text], { type: 'text/plain;charset=utf-8' }) 28 | saveAs(str, 'code.js') 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/components/VersionView/dialog.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 23 | 24 | 27 | -------------------------------------------------------------------------------- /src/components/VersionView/index.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 41 | 42 | 57 | -------------------------------------------------------------------------------- /src/config/CodeResolve.ts: -------------------------------------------------------------------------------- 1 | import { Propertie } from '@/entity/Propertie' 2 | import FinalValue from '@/util/FinalValue' 3 | 4 | export default class CodeResolve { 5 | /** 6 | * JavaScript代码解析 7 | * @param properties 8 | * @param val 9 | * @constructor 10 | */ 11 | public static JavaScript = (properties: Propertie[], val: string) => { 12 | const { header, props, footer } = CodeResolve.transformTemplate(val) 13 | const body = CodeResolve.transformPropToText({ 14 | props, 15 | properties, 16 | callbackFn: (data, propertie) => { 17 | return data.replace(/\$a|\$b|\$c|\$d/g, (a, b) => { 18 | // @ts-ignore 19 | const typeValue = FinalValue.TYPE_TO_VALUE[propertie.type] 20 | // @ts-ignore 21 | const typeToDartType = FinalValue.TYPE_TO_DARTTYPE[propertie.type] 22 | return a.replace('$a', propertie.name) 23 | .replace('$b', propertie.description) 24 | .replace('$c', typeValue) 25 | .replace('$d', typeToDartType) 26 | }) 27 | } 28 | }) 29 | return header + body + footer 30 | } 31 | 32 | public static Vue = (properties: Propertie[], val: string) => { 33 | // 找到模板中需要渲染的参数 34 | const { header, props, footer } = CodeResolve.transformTemplate(val) 35 | const body = CodeResolve.transformPropToText({ 36 | props, 37 | properties, 38 | callbackFn: (data, propertie) => { 39 | return data.replace(/\$prop|\$label/g, (a, b) => { 40 | return a.replace('$prop', propertie.name) 41 | .replace('$label', propertie.description) 42 | }) 43 | } 44 | }) 45 | return header + body + footer 46 | } 47 | 48 | private static transformPropToText (options: { properties: Propertie[], props: string, callbackFn: (data: string, propertie: Propertie) => string }): string { 49 | let resultCode = '' 50 | // 循环props内部的for语法 51 | const forStrList: MatchStr[] = CodeResolve.findForStrList(options.props) 52 | forStrList.forEach((matchStr: MatchStr) => { 53 | if (matchStr.type === 'for') { 54 | // 替换变量 55 | options.properties.forEach((propertie: Propertie) => { 56 | resultCode += options.callbackFn(matchStr.data, propertie) 57 | }) 58 | } else { 59 | resultCode += matchStr.data 60 | } 61 | }) 62 | return resultCode 63 | } 64 | 65 | private static transformTemplate (val: string): { header: string, props: string, footer: string } { 66 | const header = val.substring(0, val.indexOf(FinalValue.FOR_KEY)) 67 | const props = val.substring(val.indexOf(FinalValue.FOR_KEY), val.lastIndexOf(FinalValue.FOR_KEY) + FinalValue.FOR_KEY.length) 68 | const footer = val.substring(val.lastIndexOf(FinalValue.FOR_KEY) + FinalValue.FOR_KEY.length, val.length) 69 | return { 70 | header, 71 | props, 72 | footer 73 | } 74 | } 75 | 76 | /** 77 | * 寻找所有for语句 78 | * @param targetText 目标文本 79 | * @private 80 | * @return string[] 已移除的结果数组 81 | */ 82 | private static findForStrList (targetText: string): MatchStr[] { 83 | const forKeyLength = FinalValue.FOR_KEY.length 84 | const forKey = FinalValue.FOR_KEY 85 | const tempTargetText = targetText 86 | const resultArr: MatchStr[] = [] 87 | let startIndex = -1 88 | const recursiveFunc = function (text: string) { 89 | const position = text.indexOf(forKey) 90 | if (position < 0) return 91 | // 还没有找到过for语句,当前是第一个 92 | if (startIndex === -1) { 93 | // 去掉第一个回车 94 | startIndex = 0 95 | // 有别的语句插在了两个for中间,需要做特殊处理,把这个特殊语句复制下来 96 | if (position !== 0) { 97 | resultArr.push({ type: 'string', data: text.substring(0, position) }) 98 | } 99 | } else { 100 | resultArr.push({ type: 'for', data: text.substring(startIndex, position) }) 101 | startIndex = -1 102 | } 103 | // 把第一个回车去掉 104 | recursiveFunc(text.substring(position + forKeyLength + 1)) 105 | } 106 | recursiveFunc(tempTargetText) 107 | return resultArr 108 | } 109 | } 110 | 111 | interface MatchStr { 112 | type: 'for' | 'string', 113 | data: string 114 | } 115 | -------------------------------------------------------------------------------- /src/config/CodeTemplate.ts: -------------------------------------------------------------------------------- 1 | export default class CodeTemplate { 2 | public static Dart = 3 | 'class Model extends BaseModel {\n' + 4 | ' !!!for\n' + 5 | '/// $b\n' + 6 | ' $d $a = $c;\n' + 7 | ' !!!for\n' + 8 | '\n' + 9 | ' @override\n' + 10 | ' setJson(Map json) {\n' + 11 | '!!!for\n' + 12 | ' $a = json[\'attachExtName\'] ?? this.$a;\n' + 13 | '!!!for\n' + 14 | ' super.setJson(json);\n' + 15 | ' }\n' + 16 | '\n' + 17 | ' @override\n' + 18 | ' Model fromJson(Map json) {\n' + 19 | ' return Model()..setJson(json);\n' + 20 | ' }\n' + 21 | '}' 22 | 23 | public static JavaScript = 24 | 'const params = {' + 25 | '\n!!!for' + 26 | '\n // $b' + 27 | '\n $a: $c,' + 28 | '\n!!!for' + 29 | '\n}' 30 | 31 | public static TypeScript = 32 | 'class Model extends BaseModel {' + 33 | '\n!!!for' + 34 | '\n // $b' + 35 | '\n $a = $c' + 36 | '\n!!!for' + 37 | '\n}' 38 | 39 | public static TypeScriptFile = 40 | 'import { BaseModel, ListModel } from \'jsy-model\'' + 41 | '\nimport { ApiService } from \'@/api\'' + 42 | '\nexport class $name:class:model extends BaseModel {' + 43 | '\n!!!for' + 44 | '\n // $b' + 45 | '\n $a = $c' + 46 | '\n!!!for' + 47 | '\n constructor (data = {}) {' + 48 | '\n super()' + 49 | '\n this.setData(data)' + 50 | '\n }' + 51 | '\n}' 52 | 53 | public static QueryParams = 54 | 'export class $name:class:paramsModel {' + 55 | '\n!!!for' + 56 | '\n // $b' + 57 | '\n $a = $c' + 58 | '\n!!!for' + 59 | '\n }' 60 | 61 | public static ListModel = 62 | 'export class $name:class:listModel extends ListModel<$name:class:paramsModel, $name:class:model> {' + 63 | '\ngetList (params: $name:class:paramsModel): Promise {' + 64 | '\n return Promise.resolve(undefined)' + 65 | '\n}' + 66 | '\n\nmodelClass = $name:class:model' + 67 | '\n}' 68 | 69 | public static VueFile = ` 86 | 87 | 109 | 110 | 113 | ` 114 | 115 | public static Vue = ` 124 | 125 | 132 | 133 | 136 | ` 137 | } 138 | -------------------------------------------------------------------------------- /src/config/Color.ts: -------------------------------------------------------------------------------- 1 | export class Color { 2 | public static readonly METHOD_COLOR_MAP = { 3 | get: '#61affe', 4 | put: '#fca130', 5 | delete: '#f93e3e', 6 | post: '#49cc90' 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/data/Version.ts: -------------------------------------------------------------------------------- 1 | import { Util } from '@/util' 2 | import FinalValue from '@/util/FinalValue' 3 | // @ts-ignore 4 | import config from '../../package.json' 5 | 6 | export interface VersionHistory { 7 | version: string, 8 | func: { id: string, message: string }[], 9 | issue: { id: string, message: string }[], 10 | timestamp: string 11 | } 12 | 13 | export class Version { 14 | public static getVersion (): string { 15 | return config.version 16 | } 17 | 18 | public static getOldVersion (): string { 19 | return Util.getStorage(FinalValue.APP_VERSION) 20 | } 21 | 22 | public static readonly history: VersionHistory[] = [ 23 | { 24 | version: '2.1.6', 25 | issue: [ 26 | { id: '216i1', message: '修复模板语法中多个!!!for语法同时存在时,代码生成错误问题!' } 27 | ], 28 | func: [ 29 | { id: '216f1', message: '增加对flutter dart语法的默认支持' }, 30 | { id: '216f2', message: '增加$d配置项,显示属性对应的数据类型,详情参考README.md文档' }, 31 | { id: '216f3', message: '保存用户模板类型选择,下次生成代码自动打开上一次的模板' } 32 | ], 33 | timestamp: '2021/06/04' 34 | }, 35 | { 36 | version: '2.1.5', 37 | issue: [], 38 | func: [ 39 | { id: '215f1', message: '字符串默认值由单引号改为双引号(后期增加默认值配置项)' } 40 | ], 41 | timestamp: '2021/05/21' 42 | }, 43 | { 44 | version: '2.1.4', 45 | issue: [], 46 | func: [ 47 | { id: '214f1', message: '接口选择框增加清空操作' } 48 | ], 49 | timestamp: '2021/04/08' 50 | }, 51 | { 52 | version: '2.1.3', 53 | issue: [ 54 | { id: '213i1', message: '修复部分接口返回参数数据错误问题。' } 55 | ], 56 | func: [ 57 | { id: '213f1', message: '接口搜索支持路径大小写模糊搜索' }, 58 | { id: '213f2', message: '优化接口选择时请求方式的显示样式' } 59 | ], 60 | timestamp: '2021/04/06' 61 | }, 62 | { 63 | version: '2.1.2', 64 | issue: [ 65 | { id: '212i1', message: '修复部分接口生成参数错误问题。' } 66 | ], 67 | func: [ 68 | { id: '212f1', message: '新增Gitee跳转(源码+文档)。' }, 69 | { id: '212f2', message: '新增<复制请求路径>按钮,支持一键复制请求路径。' }, 70 | { id: '212f3', message: '新增请求方式显示' } 71 | ], 72 | timestamp: '2021/04/02' 73 | }, 74 | { 75 | version: '2.1.1', 76 | issue: [ 77 | { id: '211i1', message: '修复部分接口无法正常生成代码问题。' }, 78 | { id: '211i2', message: '修复部分swagger地址无法正常解析问题。' } 79 | ], 80 | func: [ 81 | { id: '211f1', message: '增加更多功能按钮,用户可配置存储多个服务器地址。' }, 82 | { id: '211f2', message: '增加版本更新日志提示。' } 83 | ], 84 | timestamp: '2021/03/30' 85 | }, 86 | { 87 | version: '2.1.0', 88 | issue: [], 89 | func: [ 90 | { id: '210f1', message: '自动保存用户输入的模板,下次使用生成代码直接使用上次的模板。' }, 91 | { id: '210f2', message: '自动保存用户输入的Swagger地址。' }, 92 | { id: '210f3', message: '优化接口选择器样式。' } 93 | ], 94 | timestamp: '2021/03/25' 95 | }, 96 | { 97 | version: '2.0.0', 98 | issue: [], 99 | func: [ 100 | { id: '200f1', message: '全新Swagger替代工具2.0版本发布!' } 101 | ], 102 | timestamp: '2021/03/20' 103 | } 104 | ] 105 | } 106 | -------------------------------------------------------------------------------- /src/entity/ApiDocs.ts: -------------------------------------------------------------------------------- 1 | import { Propertie } from '@/entity/Propertie' 2 | import { Path } from '@/entity/Path' 3 | 4 | export const METHOD_TYPE = [ 5 | 'get', 6 | 'put', 7 | 'post', 8 | 'delete', 9 | 'options', 10 | 'head', 11 | 'patch', 12 | 'trace' 13 | ] 14 | 15 | export class Definition { 16 | properties: Propertie[] | object = [] 17 | title = '' 18 | type = '' 19 | } 20 | 21 | export class Tag { 22 | name: string = '' 23 | description = '' 24 | children: Path[] = [] 25 | } 26 | 27 | /** 28 | * @author xietiansheng 29 | */ 30 | export class ApiDocs { 31 | fullUrl = '' 32 | basePath = '' 33 | definitions: { [key: string]: Definition } = {} 34 | host = '' 35 | info: object = {} 36 | paths: Path[] = [] 37 | securityDefinitions: object = {} 38 | tags: Tag[] = [] 39 | // 这个只用来选择路径,防止污染源数据导致级联选择器报错 40 | pathOptions: Tag[] = [] 41 | } 42 | -------------------------------------------------------------------------------- /src/entity/Path.ts: -------------------------------------------------------------------------------- 1 | import { Propertie } from '@/entity/Propertie' 2 | import { Tag } from '@/entity/ApiDocs' 3 | 4 | export class Path { 5 | consumes: string[] = [] 6 | operationId: string = '' 7 | responses: {} = {} 8 | parameters: Propertie[] = [] 9 | properties: Propertie[] = [] 10 | summary: string = '' 11 | tags: Tag[] = [] 12 | method: string = '' 13 | url: string = '' 14 | 15 | constructor (props?: {}) { 16 | Object.assign(this, props) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/entity/Propertie.ts: -------------------------------------------------------------------------------- 1 | import { ApiDocs } from '@/entity/ApiDocs' 2 | import { Path } from '@/entity/Path' 3 | import { Util } from '@/util' 4 | import store from '@/store' 5 | 6 | export class Propertie { 7 | name = '' 8 | type = '' 9 | format = '' 10 | description = '' 11 | required = false 12 | level = '' 13 | $ref = '' 14 | items: { format: string, type: string, $ref: string } | undefined 15 | children: Propertie[] = [] 16 | childDefinition: Path | undefined 17 | schema: { $ref: '' } | undefined 18 | 19 | get apiDocs (): ApiDocs { 20 | return store.state.apiDocs 21 | } 22 | 23 | constructor (props?: any) { 24 | Object.assign(this, props) 25 | let refName = props.$ref || (props.items && props.items.$ref) || (props.schema && (props.schema.$ref || (props.schema.items && props.schema.items.$ref))) 26 | // 如果当前这个属性是个链接,则直接查找子级类数据 27 | if (refName) { 28 | refName = Util.transformRefName(refName) 29 | // 不污染原始数据 30 | const definition = JSON.parse(JSON.stringify(this.apiDocs.definitions[refName])) 31 | if (definition.properties && !Array.isArray(definition.properties)) { 32 | const properties: Propertie[] = [] 33 | for (const propKey in definition.properties) { 34 | // @ts-ignore 35 | const propertie = definition.properties[propKey] 36 | // 将所有自己数据转为参数对象,向下递归所有数据 37 | properties.push(new Propertie({ 38 | ...propertie, 39 | name: propKey 40 | })) 41 | } 42 | definition.properties = properties 43 | } 44 | this.childDefinition = new Path({ ...definition }) 45 | this.children = (definition.properties as Propertie[]) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import ElementUI from 'element-ui' 4 | import 'element-ui/lib/theme-chalk/index.css' 5 | import store from './store' 6 | 7 | Vue.config.productionTip = false 8 | Vue.use(ElementUI, { 9 | size: 'small' 10 | }) 11 | 12 | new Vue({ 13 | store, 14 | render: h => h(App) 15 | }).$mount('#app') 16 | -------------------------------------------------------------------------------- /src/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue' 2 | 3 | declare global { 4 | namespace JSX { 5 | // tslint:disable no-empty-interface 6 | interface Element extends VNode {} 7 | // tslint:disable no-empty-interface 8 | interface ElementClass extends Vue {} 9 | interface IntrinsicElements { 10 | [elem: string]: any; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue' 3 | export default Vue 4 | } 5 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import { Path } from '@/entity/Path' 4 | import { ApiDocs } from '@/entity/ApiDocs' 5 | import HttpUtil from '@/util/HttpUtil' 6 | 7 | Vue.use(Vuex) 8 | 9 | export default new Vuex.Store({ 10 | state: { 11 | apiDocs: new ApiDocs(), 12 | curPath: new Path(), 13 | fullUrl: '', 14 | mainLoading: false 15 | }, 16 | mutations: { 17 | SET_CUR_PATH (state: any, curPath: Path) { 18 | state.curPath = curPath 19 | }, 20 | SET_API_DOCS (state: any, apiDocs: ApiDocs) { 21 | state.apiDocs = apiDocs 22 | }, 23 | SET_FULL_URL (state: any, url: ApiDocs) { 24 | state.fullUrl = url 25 | }, 26 | CHANGE_MAIN_LOADING (state: any, loading: boolean) { 27 | state.mainLoading = loading 28 | } 29 | }, 30 | actions: { 31 | async queryApiDocs ({ commit }, url) { 32 | if (url) { 33 | commit('SET_FULL_URL', url) 34 | } 35 | commit('CHANGE_MAIN_LOADING', true) 36 | const data = await HttpUtil.get(url || this.state.fullUrl) 37 | if (data.errMsg || !data.info) { 38 | commit('SET_API_DOCS', new ApiDocs()) 39 | commit('CHANGE_MAIN_LOADING', false) 40 | return 41 | } 42 | // 处理一下path数据,方便后期做处理 43 | const newPaths: Path[] = [] 44 | for (const pathsKey in data.paths) { 45 | const path = data.paths[pathsKey] 46 | for (const pathKey in path) { 47 | const pathElement = path[pathKey] 48 | pathElement.method = pathKey 49 | pathElement.name = `${path[pathKey].summary} ${pathsKey}` 50 | pathElement.url = pathsKey 51 | newPaths.push(new Path(pathElement)) 52 | } 53 | } 54 | data.paths = newPaths 55 | data.tags.forEach((item: any) => { 56 | item.children = data.paths.filter((path: Path) => path.tags.indexOf(item.name) !== -1) 57 | }) 58 | // 提前组装好所有tags数据,方便用户进行搜索 59 | data.pathOptions = JSON.parse(JSON.stringify(data.tags)) 60 | Object.assign(this, data) 61 | commit('SET_API_DOCS', data) 62 | commit('CHANGE_MAIN_LOADING', false) 63 | } 64 | } 65 | 66 | }) 67 | -------------------------------------------------------------------------------- /src/util/FinalValue.ts: -------------------------------------------------------------------------------- 1 | export default class FinalValue { 2 | // 请求相关数据 3 | public static readonly API_URL = '/api/manage/v2/api-docs' 4 | public static readonly API_PROJECT_URL = '/swagger-resources' 5 | 6 | // 颜色 7 | public static readonly PRIMARY_COLOR = '#409EFF' 8 | 9 | // 缓存相关 10 | public static readonly APP_VERSION = 'app_version' 11 | public static readonly STORAGE_SERVICE_URL = 'service_value' 12 | public static readonly STORAGE_SERVICE_LIST = 'service_list' 13 | public static readonly STORAGE_PROJECT_URL = 'project_value' 14 | 15 | // 代码模板相关 16 | public static readonly FOR_KEY = '!!!for' 17 | public static readonly TYPE_TO_VALUE = { 18 | number: 0, 19 | integer: 0, 20 | string: "''", 21 | boolean: false, 22 | array: '[]' 23 | } 24 | 25 | public static readonly TYPE_TO_DARTTYPE = { 26 | number: 'int', 27 | integer: 'int', 28 | string: 'String', 29 | boolean: 'boolean', 30 | array: 'Array' 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/util/HttpUtil.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | export default class HttpUtil { 4 | public static async get (url: string): Promise { 5 | let rspData = '' 6 | try { 7 | const res = await this.service.get(url) 8 | rspData = res.data 9 | } catch (e) { 10 | // 可能出现跨域跨域,使用node服务中转请求 11 | try { 12 | const res = await this.service.get('/swagger/query?url=' + url) 13 | rspData = res.data 14 | } catch (e) { 15 | throw new Error(e) 16 | } 17 | } 18 | return rspData 19 | } 20 | 21 | private static service = axios.create({ 22 | timeout: 10000 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /src/util/Marked.ts: -------------------------------------------------------------------------------- 1 | import marked from 'marked' 2 | import highlight from 'highlight.js' 3 | 4 | export default class Marked { 5 | public static _initFlag = false 6 | public static marked (markedCode: string, language:string): string { 7 | if (!this._initFlag) { this.init() } 8 | const resultCode = 9 | '```' + language + 10 | `\n${markedCode}\n` + 11 | '```' 12 | return marked(resultCode, { sanitize: true }) 13 | } 14 | 15 | static init () { 16 | marked.setOptions({ 17 | pedantic: false, 18 | gfm: true, 19 | breaks: false, 20 | sanitize: false, 21 | smartLists: true, 22 | smartypants: false, 23 | xhtml: false, 24 | highlight (code, lang) { 25 | if (lang && highlight.getLanguage(lang)) { 26 | return highlight.highlight(lang, code, true).value 27 | } else { 28 | return highlight.highlightAuto(code).value 29 | } 30 | } 31 | }) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/util/index.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import { saveAs } from 'file-saver' 3 | 4 | export class Util { 5 | /** 6 | * 解析用户输入的swagger地址 7 | * @param url 8 | */ 9 | public static parseSwaggerUrl (url: string): string { 10 | if (!url) { 11 | return '' 12 | } 13 | if (!url.startsWith('http://') && !url.startsWith('https://')) { 14 | url = 'http://' + url 15 | } 16 | if (url.substring(url.length - 1) === '/') { 17 | url = url.substring(0, url.length - 1) 18 | } 19 | const position = url.indexOf('/swagger-ui.html') 20 | if (position !== -1) { 21 | return url.substring(0, position) 22 | } 23 | return url 24 | } 25 | 26 | /** 27 | * 转换名称 28 | * @param refName 29 | */ 30 | public static transformRefName (refName: string): string { 31 | return refName.replace('#/definitions/', '') 32 | } 33 | 34 | /** 35 | * 复制内容到剪贴板 36 | */ 37 | public static copyTextToSystem (text: string) { 38 | const input = document.createElement('textarea') 39 | input.value = text 40 | document.body.appendChild(input) 41 | input.select() 42 | document.execCommand('copy') 43 | document.body.removeChild(input) 44 | } 45 | 46 | public static generatorFile (text: string, codeType: string) { 47 | const codeTypeToSuffix = { 48 | vue: 'vue', 49 | typescript: 'ts' 50 | } 51 | const str = new Blob([text], { type: 'text/plain;charset=utf-8' }) 52 | // @ts-ignore 53 | saveAs(str, `code.${codeTypeToSuffix[codeType]}`) 54 | } 55 | 56 | public static setStorage (key: string, value: string): void { 57 | localStorage.setItem(key, value) 58 | } 59 | 60 | public static getStorage (key: string): string { 61 | return localStorage.getItem(key) || '' 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/views/index/components/RequestCode/index.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | 112 | 113 | 175 | -------------------------------------------------------------------------------- /src/views/index/components/ResponseCode/index.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 85 | 86 | 140 | -------------------------------------------------------------------------------- /src/views/index/components/SearchHeader/components/SelectPath.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 101 | 102 | 107 | -------------------------------------------------------------------------------- /src/views/index/components/SearchHeader/components/ServiceConfigDialog.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 94 | 95 | 106 | -------------------------------------------------------------------------------- /src/views/index/components/SearchHeader/entity/Project.ts: -------------------------------------------------------------------------------- 1 | export interface Project { 2 | location: string, 3 | name: string, 4 | swaggerVersion: string, 5 | url: string 6 | } 7 | -------------------------------------------------------------------------------- /src/views/index/components/SearchHeader/index.vue: -------------------------------------------------------------------------------- 1 | 84 | 85 | 217 | 218 | 220 | -------------------------------------------------------------------------------- /src/views/index/index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 41 | 42 | 57 | -------------------------------------------------------------------------------- /tests/e2e/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | 'cypress' 4 | ], 5 | env: { 6 | mocha: true, 7 | 'cypress/globals': true 8 | }, 9 | rules: { 10 | strict: 'off' 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/e2e/plugins/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable arrow-body-style */ 2 | // https://docs.cypress.io/guides/guides/plugins-guide.html 3 | 4 | // if you need a custom webpack configuration you can uncomment the following import 5 | // and then use the `file:preprocessor` event 6 | // as explained in the cypress docs 7 | // https://docs.cypress.io/api/plugins/preprocessors-api.html#Examples 8 | 9 | // /* eslint-disable import/no-extraneous-dependencies, global-require */ 10 | // const webpack = require('@cypress/webpack-preprocessor') 11 | 12 | module.exports = (on, config) => { 13 | // on('file:preprocessor', webpack({ 14 | // webpackOptions: require('@vue/cli-service/webpack.config'), 15 | // watchOptions: {} 16 | // })) 17 | 18 | return Object.assign({}, config, { 19 | fixturesFolder: 'tests/e2e/fixtures', 20 | integrationFolder: 'tests/e2e/specs', 21 | screenshotsFolder: 'tests/e2e/screenshots', 22 | videosFolder: 'tests/e2e/videos', 23 | supportFile: 'tests/e2e/support/index.js' 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /tests/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // https://docs.cypress.io/api/introduction/api.html 2 | 3 | describe('My First Test', () => { 4 | it('Visits the app root url', () => { 5 | cy.visit('/') 6 | cy.contains('h1', 'Welcome to Your Vue.js + TypeScript App') 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /tests/e2e/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /tests/e2e/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "sourceMap": true, 14 | "baseUrl": ".", 15 | "types": [ 16 | "webpack-env", 17 | "node_modules/element-ui/types" 18 | ], 19 | "paths": { 20 | "@/*": [ 21 | "src/*" 22 | ] 23 | }, 24 | "lib": [ 25 | "esnext", 26 | "dom", 27 | "dom.iterable", 28 | "scripthost" 29 | ] 30 | }, 31 | "include": [ 32 | "src/**/*.ts", 33 | "src/**/*.tsx", 34 | "src/**/*.vue", 35 | "tests/**/*.ts", 36 | "tests/**/*.tsx" 37 | ], 38 | "exclude": [ 39 | "node_modules" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | publicPath: '/swagger-replace-tools/', 3 | devServer: { 4 | open: true, // 是否自动弹出浏览器页面 5 | host: 'localhost', 6 | port: '8080', 7 | https: false, 8 | hotOnly: false, 9 | proxy: { 10 | '/swagger': { 11 | target: 'https://xietiansheng.cn', 12 | ws: true, 13 | changeOrigin: true 14 | } 15 | } 16 | } 17 | } 18 | --------------------------------------------------------------------------------