From f83a674b1f753d357fca492b2f4cab40c2fd7792 Mon Sep 17 00:00:00 2001 From: Scarqin Date: Fri, 17 Jun 2022 19:17:39 +0800 Subject: [PATCH] API test support send file and response preview file/image (#74) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: extension overflow page not show * feat: api test support send file * feat: response support file Co-authored-by: 夜鹰 <17kungfuboy@gmail.com> --- package.json | 4 +- .../editor/eo-editor/eo-editor.component.html | 1 - .../editor/eo-editor/eo-editor.component.ts | 3 + .../src/app/pages/api/api.component.scss | 12 -- .../group/edit/api-group-edit.component.ts | 1 - .../app/pages/api/test/api-test.component.ts | 1 - .../app/pages/api/test/api-test.service.ts | 10 +- .../api/test/body/api-test-body.component.ts | 37 ++++- .../api-test-result-response.component.html | 20 ++- .../api-test-result-response.component.ts | 24 ++- .../detail/extension-detail.component.scss | 63 -------- .../pages/extension/extension.component.html | 2 +- .../pages/extension/extension.component.scss | 3 +- .../list/extension-list.component.scss | 139 +----------------- .../src/app/pages/pages.component.scss | 23 ++- .../api-test/api-test-params.model.ts | 6 +- .../services/api-test/api-test.utils.ts | 4 +- .../local-node/api-server-data.model.ts | 3 + .../shared/services/storage/index.model.ts | 17 ++- src/workbench/browser/src/app/utils/index.ts | 35 +++++ .../browser/src/ng1/component/list-block.js | 4 +- .../src/ng1/component/select-default.js | 1 - 22 files changed, 172 insertions(+), 241 deletions(-) diff --git a/package.json b/package.json index d2dd3ac7..2106a0e3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eoapi", - "version": "1.1.1", + "version": "1.2.0-beta", "main": "out/app/electron-main/main.js", "description": "A lightweight, extensible API tool", "homepage": "https://github.com/eolinker/eoapi.git", @@ -68,4 +68,4 @@ "node-module-alias": { "eo": "./out" } -} +} \ No newline at end of file diff --git a/src/workbench/browser/src/app/eoui/editor/eo-editor/eo-editor.component.html b/src/workbench/browser/src/app/eoui/editor/eo-editor/eo-editor.component.html index 9f6822ab..67982ba8 100644 --- a/src/workbench/browser/src/app/eoui/editor/eo-editor/eo-editor.component.html +++ b/src/workbench/browser/src/app/eoui/editor/eo-editor/eo-editor.component.html @@ -12,7 +12,6 @@ { if (result.status === StorageResStatus.success) { - this.messageService.send({ type: 'updateGroupSuccess', data: {} }); //delete group api if (data.api.length > 0) { this.messageService.send({ type: 'gotoBulkDeleteApi', data: { uuids: data.api } }); diff --git a/src/workbench/browser/src/app/pages/api/test/api-test.component.ts b/src/workbench/browser/src/app/pages/api/test/api-test.component.ts index d3923fd4..15603f9b 100644 --- a/src/workbench/browser/src/app/pages/api/test/api-test.component.ts +++ b/src/workbench/browser/src/app/pages/api/test/api-test.component.ts @@ -4,7 +4,6 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Select } from '@ngxs/store'; import { - ApiData, RequestMethod, RequestProtocol, StorageRes, diff --git a/src/workbench/browser/src/app/pages/api/test/api-test.service.ts b/src/workbench/browser/src/app/pages/api/test/api-test.service.ts index 8fcb0f59..79652c56 100644 --- a/src/workbench/browser/src/app/pages/api/test/api-test.service.ts +++ b/src/workbench/browser/src/app/pages/api/test/api-test.service.ts @@ -81,6 +81,7 @@ export class ApiTestService { baseFun: { reduceItemWhenAddChildItem: reduceItemWhenIsOprDepth, watchCheckboxChange: opts.watchFormLastChange, + importFile: opts.importFile }, itemStructure: Object.assign({}, opts.itemStructure), tdList: [ @@ -114,8 +115,10 @@ export class ApiTestService { { thKey: '参数值', - type: 'input', + type: 'autoCompleteAndFile', modelKey: 'value', + switchVar: 'type', + swicthFile: 'file', placeholder: '参数值', width: 300, mark: 'value', @@ -273,7 +276,6 @@ export class ApiTestService { }); if (inData.history.response.responseType === 'text') { let bodyInfo = text2UiData(inData.history.response.body); - console.log(bodyInfo); result.responseBody = bodyInfo.data; result.responseBodyType = bodyInfo.textType; result.responseBodyJsonType = bodyInfo.rootType; @@ -282,7 +284,7 @@ export class ApiTestService { } getTestDataFromApi(inData) { let editToTestParams = (arr) => { - arr=arr||[]; + arr = arr || []; arr.forEach((val) => { val.value = val.example; delete val.example; @@ -334,7 +336,7 @@ export class ApiTestService { case 'formData': { inData.requestBody.forEach((val) => { val.value = val.example; - val.type = 'string'; + val.type = val.type === 'file' ? 'file' : 'string'; delete val.example; }); break; diff --git a/src/workbench/browser/src/app/pages/api/test/body/api-test-body.component.ts b/src/workbench/browser/src/app/pages/api/test/body/api-test-body.component.ts index 2a34c5fe..5f29f2cd 100644 --- a/src/workbench/browser/src/app/pages/api/test/body/api-test-body.component.ts +++ b/src/workbench/browser/src/app/pages/api/test/body/api-test-body.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, Input, Output, EventEmitter, OnChanges, OnDestroy } from '@angular/core'; +import { Component, OnInit, Input, Output, EventEmitter, OnChanges, OnDestroy, ChangeDetectorRef } from '@angular/core'; import { Subject } from 'rxjs'; import { pairwise, takeUntil, debounceTime } from 'rxjs/operators'; @@ -10,7 +10,7 @@ import { } from '../../../../shared/services/api-test/api-test-params.model'; import { ApiBodyType, JsonRootType } from '../../../../shared/services/storage/index.model'; import { ApiTestService } from '../api-test.service'; -import { Message, MessageService } from '../../../../shared/services/message'; +import { EoMessageService } from 'eo/workbench/browser/src/app/eoui/message/eo-message.service'; @Component({ selector: 'eo-api-test-body', @@ -40,7 +40,11 @@ export class ApiTestBodyComponent implements OnInit, OnChanges, OnDestroy { private bodyType$: Subject = new Subject(); private destroy$: Subject = new Subject(); private rawChange$: Subject = new Subject(); - constructor(private apiTest: ApiTestService, private messageService: MessageService) { + constructor( + private apiTest: ApiTestService, + private cdRef: ChangeDetectorRef, + private message: EoMessageService + ) { this.bodyType$.pipe(pairwise(), takeUntil(this.destroy$)).subscribe((val) => { this.beforeChangeBodyByType(val[0]); }); @@ -168,6 +172,33 @@ export class ApiTestBodyComponent implements OnInit, OnChanges, OnDestroy { watchFormLastChange: () => { this.modelChange.emit(this.model); }, + importFile: (inputArg) => { + if (inputArg.file.length === 0) return; + inputArg.item.value = ''; + inputArg.item.files = []; + for (var i = 0; i < inputArg.file.length; i++) { + var val = inputArg.file[i]; + if (val.size > 2 * 1024 * 1024) { + inputArg.item.value = ''; + this.message.error('文件大小均需小于2M'); + return; + } + } + for (var i = 0; i < inputArg.file.length; i++) { + var val = inputArg.file[i]; + inputArg.item.value = val.name + ',' + inputArg.item.value; + let tmpReader = new FileReader(); + tmpReader.readAsDataURL(val); + tmpReader.onload = function (_default) { + inputArg.item.files.splice(0, 0, { + name: val.name, + dataUrl: this.result, + }); + }; + } + inputArg.item.value = inputArg.item.value.slice(0, inputArg.item.value.length - 1); + this.modelChange.emit(this.model); + }, }); this.cache['listConfSetting'] = Object.assign({}, this.listConf.setting); } diff --git a/src/workbench/browser/src/app/pages/api/test/result-response/api-test-result-response.component.html b/src/workbench/browser/src/app/pages/api/test/result-response/api-test-result-response.component.html index c62acf2d..c760fffe 100644 --- a/src/workbench/browser/src/app/pages/api/test/result-response/api-test-result-response.component.html +++ b/src/workbench/browser/src/app/pages/api/test/result-response/api-test-result-response.component.html @@ -5,7 +5,7 @@ -
+
{{ model.statusCode || 'No Response' }}
@@ -13,7 +13,25 @@ Time: {{ model.testDeny }}ms
+
+
+ 无法预览非文本类型的数据,您可以 + + ,并用其他程序打开。 +
+ +
+
+ 响应结果超出可预览的大小,您可以 + + +
({{ extensionService.extensionIDs.length }})
-
+
diff --git a/src/workbench/browser/src/app/pages/extension/extension.component.scss b/src/workbench/browser/src/app/pages/extension/extension.component.scss index b604cd31..7c1536db 100644 --- a/src/workbench/browser/src/app/pages/extension/extension.component.scss +++ b/src/workbench/browser/src/app/pages/extension/extension.component.scss @@ -39,7 +39,6 @@ margin-right: 0.5rem; } } -.px-4 { +.pl-4 { padding-left: 1rem; - padding-right: 1rem; } \ No newline at end of file diff --git a/src/workbench/browser/src/app/pages/extension/list/extension-list.component.scss b/src/workbench/browser/src/app/pages/extension/list/extension-list.component.scss index b9cd7b84..565dc47f 100644 --- a/src/workbench/browser/src/app/pages/extension/list/extension-list.component.scss +++ b/src/workbench/browser/src/app/pages/extension/list/extension-list.component.scss @@ -18,147 +18,14 @@ .warn-color { color: var(--MAIN_THEME_COLOR); } -.px-3 { - padding-left: 0.75rem; - padding-right: 0.75rem; -} -.py-4 { - padding-top: 1rem; - padding-bottom: 1rem; -} -.px-4 { - padding-left: 1rem; - padding-right: 1rem; -} -.flex-1 { - flex: 1 1 0%; -} -.w-60 { - width: 15rem; -} -.grid-cols-4 { - grid-template-columns: repeat(4, minmax(0, 1fr)); -} -.gap-6 { - grid-gap: 1.5rem; - gap: 1.5rem; -} -.py-5 { - padding-top: 1.25rem; - padding-bottom: 1.25rem; -} -.px-3 { - padding-left: 0.75rem; - padding-right: 0.75rem; -} -.grid { - display: grid; -} - @media (max-width: 1279.9px) { .grid-cols-4 { grid-template-columns: repeat(3, minmax(0, 1fr)); } } - -.w-full { - width: 100%; +.w-60{ + width: 15rem; } -.px-3 { - padding-left: 0.75rem; - padding-right: 0.75rem; -} -.py-2 { - padding-top: 0.5rem; - padding-bottom: 0.5rem; -} -.h-76 { +.h-76{ height: 17rem; -} -.items-center { - align-items: center; -} -.flex-wrap { - flex-wrap: wrap; -} -.flex-col { - flex-direction: column; -} -.flex { - display: flex; -} -.border { - border-width: 1px; -} -.rounded-lg { - border-radius: 0.5rem; -} -.text-green-700 { - --tw-text-opacity: 1; - color: rgba(4, 120, 87, var(--tw-text-opacity)); -} -.p-1 { - padding: 0.25rem; -} -.text-xs { - font-size: 0.75rem; - line-height: 1rem; -} -.border { - border-width: 1px; -} -.rounded-sm { - border-radius: 0.125rem; -} -.border-green-700 { - --tw-border-opacity: 1; - border-color: rgba(4, 120, 87, var(--tw-border-opacity)); -} -.w-20 { - width: 5rem; -} -.my-3 { - margin-top: 0.75rem; - margin-bottom: 0.75rem; -} -.h-20 { - height: 5rem; -} -.block { - display: block; -} -.rounded-lg { - border-radius: 0.5rem; -} -.bg-cover { - background-size: cover; -} -.bg-no-repeat { - background-repeat: no-repeat; -} -.bg-center { - background-position: center; -} -.text-lg { - font-size: 1.125rem; - line-height: 1.75rem; -} -.font-bold { - font-weight: 700; -} -.text-gray-400 { - --tw-text-opacity: 1; - color: rgba(156, 163, 175, var(--tw-text-opacity)); -} -.my-2 { - margin-top: 0.5rem; - margin-bottom: 0.5rem; -} -.text-gray-500 { - --tw-text-opacity: 1; - color: rgba(107, 114, 128, var(--tw-text-opacity)); -} -.my-1 { - margin-top: 0.25rem; - margin-bottom: 0.25rem; } \ No newline at end of file diff --git a/src/workbench/browser/src/app/pages/pages.component.scss b/src/workbench/browser/src/app/pages/pages.component.scss index cd52ac76..eca7689f 100644 --- a/src/workbench/browser/src/app/pages/pages.component.scss +++ b/src/workbench/browser/src/app/pages/pages.component.scss @@ -1,4 +1,4 @@ -.home_container{ +.home_container { height: calc(100vh - var(--NAVBAR_HEIGHT) - var(--remote-notification-height, 0px) - 1px); } .home { @@ -14,3 +14,24 @@ background-color: rgb(255, 219, 7); overflow: hidden; } +::ng-deep { + eo-api { + router-outlet + * { + display: block; + height: calc(100vh - var(--NAVBAR_HEIGHT) - var(--FOOTER_HEIGHT) - var(--remote-notification-height) - 46px); + overflow: auto; + } + .has_tab_page router-outlet + * { + height: calc( + 100vh - var(--NAVBAR_HEIGHT) - var(--FOOTER_HEIGHT) - var(--remote-notification-height) - 45px - 47px + ); + } + } + eo-extension { + router-outlet + * { + display: block; + height: calc(100vh - var(--NAVBAR_HEIGHT) - var(--FOOTER_HEIGHT) - var(--remote-notification-height)); + overflow: auto; + } + } +} diff --git a/src/workbench/browser/src/app/shared/services/api-test/api-test-params.model.ts b/src/workbench/browser/src/app/shared/services/api-test/api-test-params.model.ts index 64986aea..47208530 100644 --- a/src/workbench/browser/src/app/shared/services/api-test/api-test-params.model.ts +++ b/src/workbench/browser/src/app/shared/services/api-test/api-test-params.model.ts @@ -1,6 +1,6 @@ export enum ApiTestParamsTypeFormData { text = 'string', - // file = 'file', + file = 'file', } export enum ApiTestParamsTypeJsonOrXml { string = 'string', @@ -32,6 +32,10 @@ export interface ApiTestBody extends BasiApiTestParams { * param type */ type: string; + /** + * If value is file,value is base64 string + */ + files?:string; /** * XML attribute */ diff --git a/src/workbench/browser/src/app/shared/services/api-test/api-test.utils.ts b/src/workbench/browser/src/app/shared/services/api-test/api-test.utils.ts index 39095966..8536e86c 100644 --- a/src/workbench/browser/src/app/shared/services/api-test/api-test.utils.ts +++ b/src/workbench/browser/src/app/shared/services/api-test/api-test.utils.ts @@ -58,6 +58,7 @@ export const eoFormatRequestData = (data, opts = { env: {} }, locale) => { checkbox: val.required, listDepth: val.listDepth || 0, paramKey: val.name, + files:val.files?.map(val=>val.dataUrl), paramType: typeMUI[val.type], paramInfo: val.value === undefined ? val.example : val.value, }); @@ -100,6 +101,7 @@ export const eoFormatRequestData = (data, opts = { env: {} }, locale) => { }; export const eoFormatResponseData = ({ report, history, id }) => { let { httpCode, ...response } = history.resultInfo; + console.log(report, history, id) response = { statusCode: httpCode, ...response, @@ -115,7 +117,7 @@ export const eoFormatResponseData = ({ report, history, id }) => { } = { id: id, general: report.general, - response: response, + response: {blobFileName:report.blobFileName,...response}, report: { request: { requestHeaders: report.request.headers.map((val) => ({ name: val.key, value: val.value })), diff --git a/src/workbench/browser/src/app/shared/services/api-test/local-node/api-server-data.model.ts b/src/workbench/browser/src/app/shared/services/api-test/local-node/api-server-data.model.ts index f558ba7b..fb6fa413 100644 --- a/src/workbench/browser/src/app/shared/services/api-test/local-node/api-server-data.model.ts +++ b/src/workbench/browser/src/app/shared/services/api-test/local-node/api-server-data.model.ts @@ -42,6 +42,9 @@ export interface TestLocalNodeData { checkbox: boolean; paramKey: string; paramType: string; + /** + * value + */ paramInfo: string; childList: object[]; }[]; diff --git a/src/workbench/browser/src/app/shared/services/storage/index.model.ts b/src/workbench/browser/src/app/shared/services/storage/index.model.ts index dbbbfb0e..ba50f9fc 100644 --- a/src/workbench/browser/src/app/shared/services/storage/index.model.ts +++ b/src/workbench/browser/src/app/shared/services/storage/index.model.ts @@ -110,6 +110,7 @@ export interface ApiTestHistoryResponse { body: string; contentType: string; responseType: 'text' | 'longText' | 'stream'; + blobFileName?:string; responseLength: number; testDeny: string; /** @@ -295,51 +296,51 @@ export enum RequestProtocol { } /** - * API数据对象接口 + * API Data */ export interface ApiData extends StorageModel { /** - * 名称 + * name * @type {string} */ name: string; /** - * 文档所属项目主键ID + * Belongs to which project * * @type {string|number} */ projectID?: string | number; /** - * 文档所属分组主键ID + * Belongs to which group * * @type {string|number} */ groupID: string | number; /** - * 请求地址 + * Request url,Usually value is path * * @type {string} */ uri: string; /** - * API协议 [http, https, ...] + * API protocol [http, https, ...] * * @type {RequestProtocol|string} */ protocol: RequestProtocol | string; /** - * 请求方法 [POST, GET, PUT, ...] + * Request method [POST, GET, PUT, ...] * * @type {RequestMethod|string} */ method: RequestMethod | string; /** - * 分组排序号 + * api show order * * @type {number} */ diff --git a/src/workbench/browser/src/app/utils/index.ts b/src/workbench/browser/src/app/utils/index.ts index fcf45b7d..aed9d65a 100644 --- a/src/workbench/browser/src/app/utils/index.ts +++ b/src/workbench/browser/src/app/utils/index.ts @@ -93,3 +93,38 @@ export const getDefaultValue = (list: any[], key) => { const [target] = list.filter((it) => it.default); return target[key] || ''; }; + +export const parserProperties = (properties) => Object.keys(properties).map((it) => ({ value: it, ...properties[it] })); +const base64ToUint8Array = (inputBase64String) => { + const tmpPadding = '='.repeat((4 - (inputBase64String.length % 4)) % 4); + const tmpBase64 = (inputBase64String + tmpPadding).replace(/\-/g, '+').replace(/_/g, '/'); + + const tmpRawData = window.atob(tmpBase64); + const tmpOutputArray = new Uint8Array(tmpRawData.length); + for (let i = 0; i < tmpRawData.length; ++i) { + tmpOutputArray[i] = tmpRawData.charCodeAt(i); + } + return tmpOutputArray; +}; +export const getBlobUrl = (inputStream, inputFileType) => { + let tmpBlob; + try { + inputStream = base64ToUint8Array(inputStream); + if (typeof window.Blob === 'function') { + tmpBlob = new Blob([inputStream], { + type: inputFileType, + }); + } else { + //@ts-ignore + const tmpBlobBuilder = + window.BlobBuilder || window.MozBlobBuilder || window.WebKitBlobBuilder || window.MSBlobBuilder; + const tmpBlobClass = new tmpBlobBuilder(); + tmpBlobClass.append(inputStream); + tmpBlob = tmpBlobClass.getBlob(inputFileType); + } + } catch (GET_BLOB_ERR) { + tmpBlob = inputStream; + } + const tmpUrlObj = window.URL || window.webkitURL; + return tmpUrlObj.createObjectURL(tmpBlob); +}; diff --git a/src/workbench/browser/src/ng1/component/list-block.js b/src/workbench/browser/src/ng1/component/list-block.js index 39c588b7..2c3b8a66 100644 --- a/src/workbench/browser/src/ng1/component/list-block.js +++ b/src/workbench/browser/src/ng1/component/list-block.js @@ -1252,8 +1252,10 @@ function listBlockController($rootScope, $element, $scope) { }; $scope.importFile = function (inputArg, inputEvent) { inputArg.$index = this.$parent.$index; + inputArg.item=vm.list[inputArg.$index]; vm.mainObject.baseFun.importFile(inputArg); - if (inputEvent) inputEvent.value = ''; + // if (inputEvent) inputEvent.value = ''; + ($scope.$root && $scope.$root.$$phase) || $scope.$apply(); }; /** * @desc 过滤列表函数 diff --git a/src/workbench/browser/src/ng1/component/select-default.js b/src/workbench/browser/src/ng1/component/select-default.js index d1d0650a..ad940d01 100644 --- a/src/workbench/browser/src/ng1/component/select-default.js +++ b/src/workbench/browser/src/ng1/component/select-default.js @@ -88,7 +88,6 @@ function selectDefaultController($scope, $element) { if (vm.mainObject && vm.mainObject.isNeedToResetPosition) { let tmpObj = vm.data.inputElem[0].getBoundingClientRect(); - console.log(tmpObj); vm.data.inputX = tmpObj.x; vm.data.inputY = tmpObj.y + 30; }