fix:tab some bug

This commit is contained in:
秦圆圆 2022-01-24 19:51:36 +08:00
parent 521896a919
commit 6a22e79508
22 changed files with 381 additions and 310 deletions

View File

@ -1,4 +1,4 @@
import { Component, Input, Output, EventEmitter, OnChanges, AfterViewInit, ViewChild } from '@angular/core';
import { Component, Input, Output, EventEmitter, OnChanges, AfterViewInit, ViewChild, OnInit } from '@angular/core';
import { NzMessageService } from 'ng-zorro-antd/message';
import { AceConfigInterface, AceComponent, AceDirective } from 'ngx-ace-wrapper';
import { whatTextType } from '../../../utils';
@ -44,7 +44,7 @@ const eventHash = new Map()
templateUrl: './eo-editor.component.html',
styleUrls: ['./eo-editor.component.scss'],
})
export class EoEditorComponent implements AfterViewInit, OnChanges {
export class EoEditorComponent implements AfterViewInit, OnInit, OnChanges {
@Input() eventList: EventType[] = [];
@Input() hiddenList: string[] = [];
@Input() code: string;
@ -82,15 +82,7 @@ export class EoEditorComponent implements AfterViewInit, OnChanges {
constructor(private message: NzMessageService) {}
ngAfterViewInit(): void {
// To get the Ace instance:
this.buttonList = this.eventList
.filter((it) => it !== 'type')
.map((it) => ({
event: it,
...eventHash.get(it),
}));
}
ngAfterViewInit(): void {}
ngOnChanges() {
// * update root type
if (this.eventList.includes('type') && !this.hiddenList.includes('type')) {
@ -101,6 +93,15 @@ export class EoEditorComponent implements AfterViewInit, OnChanges {
}
}
}
ngOnInit() {
// To get the Ace instance:
this.buttonList = this.eventList
.filter((it) => it !== 'type')
.map((it) => ({
event: it,
...eventHash.get(it),
}));
}
log(event, txt) {
console.log('ace event', event, txt);
}

View File

@ -20,15 +20,16 @@
</div>
</div>
<div class="content_container {{ this.id ? 'has_tab_page' : '' }}">
<nz-tabset class="inside_page_tab" *ngIf="this.id" nzLinkRouter>
<nz-tab>
<a *nzTabLink nz-tab-link [routerLink]="['detail']" queryParamsHandling="merge"> 文档 </a>
</nz-tab>
<nz-tab>
<a *nzTabLink nz-tab-link [routerLink]="['edit']" queryParamsHandling="merge"> 编辑 </a>
</nz-tab>
<nz-tab>
<a *nzTabLink nz-tab-link [routerLink]="['test']" queryParamsHandling="merge"> 测试 </a>
<nz-tabset class="inside_page_tab" *ngIf="this.id" nzLinkRouter>
<nz-tab *ngFor="let tab of TABS">
<a
*nzTabLink
nz-tab-link
(click)="clickContentMenu(tab)"
[routerLink]="[tab.routerLink]"
queryParamsHandling="merge"
>{{ tab.title }}</a
>
</nz-tab>
</nz-tabset>
<router-outlet></router-outlet>

View File

@ -1,9 +1,8 @@
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { NzFormatEmitEvent, NzTreeNode } from 'ng-zorro-antd/tree';
import { Message, MessageService } from '../../shared/services/message';
import { ApiTabComponent } from './tab/api-tab.component';
import { GroupService } from '../../shared/services/group/group.service';
import { NzModalService } from 'ng-zorro-antd/modal';
import { ApiDataService } from '../../shared/services/api-data/api-data.service';
@ -13,15 +12,15 @@ import { GroupApiDataModel, GroupTreeItem } from '../../shared/models';
import { ApiData } from '../../shared/services/api-data/api-data.model';
import { Group } from '../../shared/services/group/group.model';
import { Subject } from 'rxjs';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { ApiTabService } from './tab/api-tab.service';
@Component({
selector: 'eo-api',
templateUrl: './api.component.html',
styleUrls: ['./api.component.scss'],
})
export class ApiComponent implements OnInit, OnDestroy {
@ViewChild(ApiTabComponent) apiTabComponent: ApiTabComponent;
/**
* Default projectID.
*/
@ -49,13 +48,28 @@ export class ApiComponent implements OnInit, OnDestroy {
*/
id: number;
TABS = [
{
routerLink: 'detail',
title: '文档',
},
{
routerLink: 'edit',
title: '编辑',
},
{
routerLink: 'test',
title: '测试',
},
];
private destroy$: Subject<void> = new Subject<void>();
constructor(
private route: ActivatedRoute,
private groupService: GroupService,
private apiDataService: ApiDataService,
private messageService: MessageService,
private modalService: NzModalService
private modalService: NzModalService,
private tabSerive: ApiTabService
) {}
ngOnInit(): void {
@ -93,7 +107,7 @@ export class ApiComponent implements OnInit, OnDestroy {
}
getApis() {
this.apiDataService.loadAllByProjectID(this.projectID).subscribe((items: Array<ApiData>) => {
let apiItems={};
let apiItems = {};
items.forEach((item) => {
delete item.updatedAt;
apiItems[item.uuid] = item;
@ -106,7 +120,7 @@ export class ApiComponent implements OnInit, OnDestroy {
isLeaf: true,
});
});
this.apiDataItems=apiItems;
this.apiDataItems = apiItems;
this.generateGroupTreeData();
});
}
@ -119,6 +133,9 @@ export class ApiComponent implements OnInit, OnDestroy {
this.id = Number(params.get('uuid'));
});
}
clickContentMenu(data) {
this.tabSerive.apiEvent$.next({ action: 'beforeChangeRouter', data: data });
}
/**
* Event emit from group tree component.
*
@ -132,27 +149,16 @@ export class ApiComponent implements OnInit, OnDestroy {
case 'loadAllGroup':
this.buildGroupTreeData();
break;
case 'testApi':
this.testApi(event.node);
break;
case 'detailApi':
this.detailApi(event.node);
break;
case 'editApi':
this.editApi(event.node);
break;
case 'copyApi':
this.copyApi(event.node);
break;
case 'deleteApi':
this.deleteApi(event.node);
break;
case 'newApi':
this.newApi(event.node);
break;
case 'newApiTest':
this.newApiTest();
default: {
this.tabSerive.apiEvent$.next({ action: event.eventName, data: event.node });
break;
}
}
}
@ -165,42 +171,29 @@ export class ApiComponent implements OnInit, OnDestroy {
.pipe(takeUntil(this.destroy$))
.subscribe((data: Message) => {
switch (data.type) {
case 'apiAdd':
case 'editApi':
case 'addApi':
case 'editApi':
{
this.tabSerive.apiEvent$.next({ action: `${data.type}Finish`,data:data.data});
this.buildGroupTreeData();
break;
}
case 'groupAdd':
case 'groupEdit':
case 'groupDelete':
this.buildGroupTreeData();
break;
case 'apiDelete':
this.watchApiDelete(data.data);
let tmpApi = data.data;
this.tabSerive.apiEvent$.next({ action: 'removeApiDataTabs', data: [tmpApi.uuid] });
this.buildGroupTreeData();
break;
case 'apiBatchDelete':
this.watchApiBatchDelete(data.data);
this.tabSerive.apiEvent$.next({ action: 'removeApiDataTabs', data: data.data.uuids });
break;
}
});
}
/**
* Delete api data tabs after item removed.
*
* @param data object
*/
watchApiDelete(data) {
this.apiTabComponent.removeApiDataTabs([data.uuid]);
this.buildGroupTreeData();
}
/**
* Delete api data tabs after items removed.
*
* @param data object
*/
watchApiBatchDelete(data) {
this.apiTabComponent.removeApiDataTabs(data.uuids);
}
/**
* Generate group tree nodes.
*/
@ -209,51 +202,6 @@ export class ApiComponent implements OnInit, OnDestroy {
this.treeNodes = [];
listToTree(this.treeItems, this.treeNodes, 0);
}
/**
* Create a new tab of add new api data.
*
* @param item GroupTreeItem
*/
newApi(node?: NzTreeNode): void {
console.log('newApi',node)
this.apiTabComponent.appendTab('edit', node ? { groupID: node.key } : {});
}
/**
* Create a new tab of add new api test.
*/
newApiTest(): void {
this.apiTabComponent.appendTab('test');
}
/**
* Test api data.
*
* @param node NzTreeNode
*/
testApi(node: NzTreeNode): void {
this.apiTabComponent.appendTab('test', node.origin);
}
/**
* View api data.
*
* @param node NzTreeNode
*/
detailApi(node: NzTreeNode): void {
this.apiTabComponent.appendTab('detail', node.origin);
}
/**
* Edit api data.
*
* @param node NzTreeNode
*/
editApi(node: NzTreeNode): void {
this.apiTabComponent.appendTab('edit', node.origin);
}
/**
* Copy api data.
*
@ -264,13 +212,9 @@ export class ApiComponent implements OnInit, OnDestroy {
delete data.uuid;
delete data.createdAt;
data.name += ' Copy';
this.apiDataService.create(data).subscribe((result: ApiData) => {
this.buildGroupTreeData();
this.apiTabComponent.appendTab('edit', {
title: result.name,
key: result.uuid,
method: result.method,
});
window.sessionStorage.setItem('apiDataWillbeSave', JSON.stringify(data));
this.tabSerive.apiEvent$.next({
action: 'newApi',
});
}

View File

@ -1 +0,0 @@

View File

@ -17,7 +17,7 @@
<p class="api_line">Rest 参数</p>
<eo-api-detail-rest [model]="apiData.restParams"></eo-api-detail-rest>
</div>
<div *ngIf="apiData.requestBody && apiData.requestBody.length">
<div *ngIf="apiData.requestBody?.length">
<div class="api_line">
Body 请求参数<nz-tag class="ml10" nzColor="default">{{ CONST.BODY_TYPE[apiData.requestBodyType] }}</nz-tag>
<nz-tag *ngIf="apiData.requestBodyType==='json'" nzColor="default">最外层结构为:{{ CONST.JSON_ROOT_TYPE[apiData.requestBodyJsonType] }}</nz-tag>

View File

@ -51,7 +51,9 @@
<nz-tab [nzTitle]="headerTitleTmp" [nzForceRender]="true">
<ng-template #headerTitleTmp>
请求头部
<span class="eo-tab-icon">{{ apiData.requestHeaders | apiParamsNum }}</span>
<span class="eo-tab-icon" *ngIf="bindGetApiParamNum(apiData.requestHeaders)">{{
apiData.requestHeaders | apiParamsNum
}}</span>
</ng-template>
<eo-api-edit-header class="eo_theme_iblock bbd bld brd" [model]="apiData.requestHeaders"></eo-api-edit-header>
</nz-tab>
@ -59,7 +61,14 @@
<nz-tab [nzTitle]="bodyTitleTmp" [nzForceRender]="true">
<ng-template #bodyTitleTmp>
请求体
<span class="eo-tab-icon">{{ apiData.requestBody | apiParamsNum }}</span>
<span
class="iconfont icon-circle eo-tab-theme-icon"
*ngIf="
['formData', 'json', 'xml'].includes(apiData.requestBodyType)
? bindGetApiParamNum(apiData.requestBody)
: apiData.requestBody?.length
"
></span>
</ng-template>
<eo-api-edit-body
class="eo_theme_iblock bbd bld brd"
@ -72,32 +81,38 @@
<nz-tab [nzTitle]="queryTitleTmp" [nzForceRender]="true">
<ng-template #queryTitleTmp>
Query 参数
<span class="eo-tab-icon">{{ apiData.queryParams | apiParamsNum }}</span>
<span class="eo-tab-icon" *ngIf="bindGetApiParamNum(apiData.queryParams)">{{
apiData.queryParams | apiParamsNum
}}</span>
</ng-template>
<eo-api-edit-query class="eo_theme_iblock bbd bld brd" [model]="apiData.queryParams"></eo-api-edit-query>
</nz-tab>
<nz-tab [nzTitle]="restTitleTmp" [nzForceRender]="true">
<ng-template #restTitleTmp>
REST 参数
<span class="eo-tab-icon">{{ apiData.restParams | apiParamsNum }}</span>
<span class="eo-tab-icon" *ngIf="bindGetApiParamNum(apiData.restParams)">{{
apiData.restParams | apiParamsNum
}}</span>
</ng-template>
<eo-api-edit-rest class="eo_theme_iblock bbd bld brd" [model]="apiData.restParams"></eo-api-edit-rest>
</nz-tab>
</nz-tabset>
</nz-collapse-panel>
</nz-collapse>
<!-- 响应参数 -->
<!-- 响应内容 -->
<nz-collapse class="eo_collapse mt40" [nzGhost]="true">
<nz-collapse-panel #panel [nzActive]="true" nzHeader="响应内容" nzShowArrow="false" [nzExtra]="extraTpl">
<ng-template #extraTpl>
{{ panel.nzActive ? '收缩' : '展开' }}
<span class="iconfont icon-chevron-{{ panel.nzActive ? 'up' : 'down' }}"></span>
<nz-collapse-panel #panelRes [nzActive]="true" nzHeader="响应内容" nzShowArrow="false" [nzExtra]="extraTplRes">
<ng-template #extraTplRes>
{{ panelRes.nzActive ? '收缩' : '展开' }}
<span class="iconfont icon-chevron-{{ panelRes.nzActive ? 'up' : 'down' }}"></span>
</ng-template>
<nz-tabset [nzAnimated]="false" [nzSelectedIndex]="1" class="mt10">
<nz-tab [nzTitle]="responseHeaderTitleTmp" [nzForceRender]="true">
<ng-template #responseHeaderTitleTmp>
返回头部
<span class="eo-tab-icon">{{ apiData.responseHeaders | apiParamsNum }}</span>
<span class="eo-tab-icon" *ngIf="bindGetApiParamNum(apiData.responseHeaders)">{{
apiData.responseHeaders | apiParamsNum
}}</span>
</ng-template>
<eo-api-edit-header
class="eo_theme_iblock bbd bld brd"
@ -107,7 +122,14 @@
<nz-tab [nzTitle]="responseTitleTmp" [nzForceRender]="true">
<ng-template #responseTitleTmp>
返回结果
<span class="eo-tab-icon">{{ apiData.responseBody | apiParamsNum }}</span>
<span
class="iconfont icon-circle eo-tab-theme-icon"
*ngIf="
['formData', 'json', 'xml'].includes(apiData.responseBodyType)
? bindGetApiParamNum(apiData.responseBody)
: apiData.responseBody?.length
"
></span>
</ng-template>
<eo-api-edit-body
class="eo_theme_iblock bbd bld brd"

View File

@ -1,12 +1,12 @@
import { Component, OnInit, ViewChild, OnDestroy } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ActivatedRoute } from '@angular/router';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { NzMessageService } from 'ng-zorro-antd/message';
import { NzTreeSelectComponent } from 'ng-zorro-antd/tree-select';
import { Observable, of, Subject } from 'rxjs';
import { switchMap, debounceTime, take, takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { debounceTime, take, takeUntil, pairwise, filter } from 'rxjs/operators';
import { ApiEditRest } from '../../../shared/services/api-data/api-edit-params.model';
import { ApiData, RequestProtocol, RequestMethod } from '../../../shared/services/api-data/api-data.model';
@ -15,10 +15,12 @@ import { MessageService } from '../../../shared/services/message';
import { Group } from '../../../shared/services/group/group.model';
import { GroupService } from '../../../shared/services/group/group.service';
import { ApiTabService } from '../tab/api-tab.service';
import { objectToArray,getRest } from '../../../utils';
import { objectToArray } from '../../../utils';
import { getRest } from '../../../utils/api';
import { treeToListHasLevel, listToTree, listToTreeHasLevel } from '../../../utils/tree';
import { ApiParamsNumPipe } from '../../../shared/pipes/api-param-num.pipe';
@Component({
selector: 'eo-api-edit-edit',
templateUrl: './api-edit.component.html',
@ -33,7 +35,6 @@ export class ApiEditComponent implements OnInit, OnDestroy {
REQUEST_METHOD = objectToArray(RequestMethod);
REQUEST_PROTOCOL = objectToArray(RequestProtocol);
private api$: Observable<object>;
private destroy$: Subject<void> = new Subject<void>();
private changeGroupID$: Subject<string | number> = new Subject();
@ -41,10 +42,10 @@ export class ApiEditComponent implements OnInit, OnDestroy {
private storage: ApiDataService,
private route: ActivatedRoute,
private fb: FormBuilder,
private router: Router,
private message: NzMessageService,
private messageService: MessageService,
private groupService: GroupService
private groupService: GroupService,
private apiTab: ApiTabService
) {}
getApiGroup() {
this.groups = [];
@ -115,22 +116,37 @@ export class ApiEditComponent implements OnInit, OnDestroy {
this.editApi(formData);
}
bindGetApiParamNum(params) {
return new ApiParamsNumPipe().transform(params);
}
ngOnInit(): void {
this.getApiGroup();
this.resetApi();
this.initApi(Number(this.route.snapshot.queryParams.uuid));
this.watchTabChange();
this.watchGroupIDChange();
this.watchUri();
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
private initApi(id) {
this.resetForm();
this.initBasicForm();
if (this.route.snapshot.queryParams.uuid) {
//Edit
this.watchQueryChange();
} else {
let testData = window.sessionStorage.getItem('testDataToAPI');
if (testData) {
//Add From Test
Object.assign(
this.apiData,
JSON.parse(testData)
);
//recovery from tab
if (this.apiTab.currentTab && this.apiTab.tabCache[this.apiTab.tabID]) {
let tabData = this.apiTab.tabCache[this.apiTab.tabID];
this.apiData = tabData.apiData;
return;
}
if (!id) {
let tmpApiData = window.sessionStorage.getItem('apiDataWillbeSave');
if (tmpApiData) {
//Add From Test|Copy Api
window.sessionStorage.removeItem('apiDataWillbeSave');
Object.assign(this.apiData, JSON.parse(tmpApiData));
this.validateForm.patchValue(this.apiData);
} else {
//Add directly
Object.assign(this.apiData, {
@ -146,70 +162,56 @@ export class ApiEditComponent implements OnInit, OnDestroy {
responseBody: [],
});
}
} else {
this.getApi(id);
}
}
private watchTabChange() {
this.apiTab.tabChange$
.pipe(
pairwise(),
//actually change tab,not init tab
filter((data) => data[0].uuid !== data[1].uuid),
takeUntil(this.destroy$)
)
.subscribe(([nowTab, nextTab]) => {
this.apiTab.saveTabData$.next({
tab: nowTab,
data: {
apiData: this.apiData,
},
});
this.initApi(nextTab.key);
});
}
private watchGroupIDChange() {
this.changeGroupID$.pipe(debounceTime(500), take(1)).subscribe((id) => {
this.apiData.groupID = (this.apiData.groupID === 0 ? -1 : this.apiData.groupID).toString();
this.expandGroup();
});
this.validateForm
.get('uri')
.valueChanges.pipe(debounceTime(500), takeUntil(this.destroy$))
.subscribe((url) => {
this.changeUri(url);
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
private watchQueryChange() {
this.api$ = this.route.queryParamMap.pipe(
switchMap((params) => {
const id = Number(params.get('uuid'));
if (!id) {
const groupID = params.get('groupID');
if (groupID) {
return of({
groupID,
});
}
return of();
}
return of({ id });
}),
takeUntil(this.destroy$)
);
this.api$.subscribe({
next: (inArg: any = {}) => {
if (inArg.id) {
this.getApi(inArg.id);
}
if (inArg.groupID) {
this.apiData.groupID = inArg.groupID;
this.changeGroupID$.next(this.apiData.groupID);
}
},
});
}
/**
* Generate Rest Param From Url
*/
private changeUri(url) {
const rests = getRest(url);
rests.forEach((newRest) => {
if (this.apiData.restParams.find((val: ApiEditRest) => val.name === newRest)) {
return;
}
const restItem: ApiEditRest = {
name: newRest,
required: true,
example: '',
description: '',
};
this.apiData.restParams.splice(this.apiData.restParams.length - 1, 0, restItem);
});
private watchUri() {
this.validateForm
.get('uri')
.valueChanges.pipe(debounceTime(500), takeUntil(this.destroy$))
.subscribe((url) => {
const rests = getRest(url);
rests.forEach((newRest) => {
if (this.apiData.restParams.find((val: ApiEditRest) => val.name === newRest)) {
return;
}
const restItem: ApiEditRest = {
name: newRest,
required: true,
example: '',
description: '',
};
this.apiData.restParams.splice(this.apiData.restParams.length - 1, 0, restItem);
});
});
}
/**
* Reset Group ID after group list load
@ -250,25 +252,24 @@ export class ApiEditComponent implements OnInit, OnDestroy {
/**
* Init API data structure
*/
private resetApi() {
private resetForm() {
this.apiData = {
name: '',
projectID: 1,
uri: '/',
groupID: this.route.snapshot.queryParams.groupID||'-1',
groupID: this.route.snapshot.queryParams.groupID || '-1',
protocol: RequestProtocol.HTTP,
method: RequestMethod.POST,
};
}
private editApi(formData) {
const busEvent = formData.uuid ? 'editApi' : 'apiAdd';
const busEvent = formData.uuid ? 'editApi' : 'addApi';
const title = busEvent === 'editApi' ? '编辑成功' : '新增成功';
this.storage[busEvent === 'editApi' ? 'update' : 'create'](formData, this.apiData.uuid).subscribe(
(result: ApiData) => {
this.message.success(title);
this.messageService.send({ type: busEvent, data: result });
this.router.navigate(['/home/api/detail'], { queryParams: { uuid: result.uuid } });
}
);
}

View File

@ -213,11 +213,12 @@ export class ApiEditService {
{
key: '插入',
operateName: 'insert',
itemExpression: `ng-if="!($ctrl.mainObject.setting.munalHideOperateColumn&&$first)"`,
itemExpression: `ng-if="!($ctrl.mainObject.setting.munalHideOperateColumn&&$first)"`
},
{
key: '删除',
operateName: 'delete',
itemExpression: 'ng-if="!($ctrl.mainObject.setting.munalHideOperateColumn&&$first)"'
},
],
},

View File

@ -22,7 +22,7 @@
<nz-dropdown-menu #menu="nzDropdownMenu">
<ul nz-menu>
<li nz-menu-item (click)="onClick({ event: $event, eventName: 'newApi' })"><a>新建API</a></li>
<li nz-menu-item (click)="onClick({ event: $event, eventName: 'newApiTest' })"><a>新建测试</a></li>
<!-- <li nz-menu-item (click)="onClick({ event: $event, eventName: 'newApiTest' })"><a>新建测试</a></li> -->
<li nz-menu-item (click)="newGroup()"><a>新建分组</a></li>
</ul>
</nz-dropdown-menu>
@ -48,7 +48,7 @@
<div class="tree_node f_row f_js_ac" *ngIf="!node.isLeaf">
<div class="f_row_ac">
<span class="iconfont icon-folder-outline fs16 mr5"></span>
<span>{{ node.title }}</span>
<span class="text_omit node_title">{{ node.title }}</span>
</div>
<span class="tree_node_operate">
<button nz-dropdown [nzDropdownMenu]="groupMenu">
@ -75,8 +75,10 @@
<!-- Leaf -->
<div class="tree_node f_row f_js_ac" *ngIf="node.isLeaf">
<div class="f_row_ac">
<b class="method_text method_text_{{ node.origin.method }} mr5" *ngIf="node.origin.method">{{node.origin.method.slice(0, 4)}}</b>
{{ node.title }}
<b class="method_text method_text_{{ node.origin.method }} mr5" *ngIf="node.origin.method">{{
node.origin.method.slice(0, 4)
}}</b>
<span class="text_omit node_title">{{ node.title }}</span>
</div>
<span class="tree_node_operate">
<button>

View File

@ -9,6 +9,9 @@
.method_text{
width: 32px;
}
.node_title{
max-width: 145px;
}
.tree_node {
font-size: 12px;
.tree_node_operate {

View File

@ -6,7 +6,6 @@
(nzSelectChange)="switchTab()"
[nzTabBarExtraContent]="extraTemplate"
>
<nz-tab *ngFor="let tab of tabs; let i = index" nzClosable [nzTitle]="titleTemplate">
<ng-template #titleTemplate>
<span class="mr5 method_text_{{ tab.method }}" *ngIf="tab.method">{{ tab.method.slice(0, 4) }}</span>

View File

@ -22,7 +22,7 @@
}
}
.ant-tabs-nav-wrap {
width: calc(100% - 135px);
max-width: calc(100% - 135px);
flex: none;
}
.ant-tabs-nav-add {

View File

@ -1,16 +1,16 @@
import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core';
import { Component, OnInit, Input, OnChanges, SimpleChanges, OnDestroy } from '@angular/core';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { TabItem } from './tab.model';
import { ApiTabService } from './api-tab.service';
import { filter } from 'rxjs';
import { filter, Subject, takeUntil } from 'rxjs';
@Component({
selector: 'eo-api-tab',
templateUrl: './api-tab.component.html',
styleUrls: ['./api-tab.component.scss'],
})
export class ApiTabComponent implements OnInit, OnChanges {
export class ApiTabComponent implements OnInit, OnChanges, OnDestroy {
@Input() apiDataItems;
id: number;
/**
@ -32,30 +32,20 @@ export class ApiTabComponent implements OnInit, OnChanges {
};
MAX_LIMIT = 15;
private destroy$: Subject<void> = new Subject<void>();
constructor(private router: Router, private route: ActivatedRoute, private tabSerive: ApiTabService) {}
ngOnInit(): void {
this.watchChangeRouter();
this.watchApiAction();
}
ngOnChanges(changes: SimpleChanges): void {
if (changes.apiDataItems && changes.apiDataItems.currentValue) {
if (changes.apiDataItems && !changes.apiDataItems.previousValue && changes.apiDataItems.currentValue) {
this.initTab();
}
}
/**
* path change
*/
watchChangeRouter() {
this.router.events.pipe(filter((event) => event instanceof NavigationEnd)).subscribe((event: NavigationEnd) => {
this.id = Number(this.route.snapshot.queryParams.uuid);
if (!this.id) return;
this.tabs[this.selectedIndex] = Object.assign(
{
uuid: this.tabs[this.selectedIndex].uuid,
},
this.getTabInfoByID(this.id)
);
});
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
/**
* Create a new tab.
@ -67,14 +57,16 @@ export class ApiTabComponent implements OnInit, OnChanges {
* Init tab data after load or update.
*/
initTab() {
this.id = Number(this.route.snapshot.queryParams.uuid);
if (this.id) {
let apiHasDelete = !this.apiDataItems[this.id];
if (apiHasDelete) {
let apiID = Number(this.route.snapshot.queryParams.uuid);
if (apiID) {
let hasApiExist = this.apiDataItems[apiID];
if (!hasApiExist) {
this.closeTab({ index: this.selectedIndex });
return;
}
const tab = this.getTabInfoByID(this.id);
const tab = this.getTabInfo({
id: apiID,
});
this.appendTab('unset', tab);
} else {
let module = Object.keys(this.defaultTabs).find((keyName) =>
@ -88,7 +80,7 @@ export class ApiTabComponent implements OnInit, OnChanges {
*
* @param tab TabItem
*/
appendTab(which = 'test', apiData = {}): void {
appendTab(which = 'test', apiData: any = {}): void {
if (this.tabs.length >= this.MAX_LIMIT) return;
let tab: TabItem = Object.assign(
{
@ -100,15 +92,20 @@ export class ApiTabComponent implements OnInit, OnChanges {
let existTabIndex = this.tabs.findIndex((val) => val.key === tab.key);
if (tab.key && existTabIndex !== -1) {
this.selectedIndex = existTabIndex;
if (this.tabs[existTabIndex].path !== tab.path) {
//exist api(same tab) change page
this.tabs[existTabIndex].path = tab.path;
this.switchTab();
}
return;
}
let hasTab = this.tabs.length > 0;
this.tabs.push(tab);
if (hasTab) {
this.selectedIndex = this.tabs.length - 1;
} else {
// if index no change,manual change reflesh content
if (this.selectedIndex === this.tabs.length - 1) {
this.switchTab();
return;
}
this.selectedIndex = this.tabs.length - 1;
}
/**
* Remove api data tabs.
@ -118,7 +115,7 @@ export class ApiTabComponent implements OnInit, OnChanges {
removeApiDataTabs(uuids: Array<string | number>): void {
const items = [];
this.tabs.forEach((tab: TabItem, index: number) => {
if (uuids.indexOf(tab.key)) {
if (uuids.includes(tab.key)) {
items.push({ index });
}
});
@ -127,19 +124,27 @@ export class ApiTabComponent implements OnInit, OnChanges {
});
}
/**
* Close current tab.
* Close Tab and keep tab status
*
* @param index number
*/
closeTab({ index }: { index: number }): void {
this.tabSerive.removeData(this.tabs[index].uuid);
if (this.tabs[index]) {
this.tabSerive.removeData(this.tabs[index].uuid);
}
this.tabs.splice(index, 1);
//no tab left
if (0 === this.tabs.length) {
this.newTab();
return;
}
//selectedIndex no change
if (this.selectedIndex < this.tabs.length) {
this.switchTab();
}
}
/**
* Switch the tab.
* router change after switch the tab or tab content
* @param {TabItem} inArg.tab
* @param inArg.index
*/
@ -148,7 +153,61 @@ export class ApiTabComponent implements OnInit, OnChanges {
this.tabSerive.tabChange$.next(tab);
this.activeRoute(tab);
}
/**
* Api Operation triggle tab change
*/
private watchApiAction() {
this.tabSerive.apiEvent$.pipe(takeUntil(this.destroy$)).subscribe((inArg) => {
console.log('watchApiAction', inArg);
switch (inArg.action) {
case 'newApiTest':
case 'testApi':
this.appendTab('test', inArg.data.origin);
break;
case 'detailApi':
this.appendTab('detail', inArg.data.origin);
break;
case 'editApi':
this.appendTab('edit', inArg.data.origin);
break;
case 'newApi':
this.appendTab('edit', inArg.data ? { groupID: inArg.data.key } : {});
break;
case 'addApiFromTest': {
this.changeCurrentTab(
this.getTabInfo({
path: this.defaultTabs['edit'].path,
apiData: inArg.data,
})
);
break;
}
case 'addApiFinish':
case 'editApiFinish': {
this.changeCurrentTab(
this.getTabInfo({
path: this.defaultTabs['detail'].path,
apiData: inArg.data,
})
);
this.switchTab();
break;
}
case 'removeApiDataTabs': {
this.removeApiDataTabs(inArg.data);
break;
}
case 'beforeChangeRouter': {
this.changeCurrentTab(
this.getTabInfo({
id: Number(this.route.snapshot.queryParams.uuid),
})
);
break;
}
}
});
}
/**
* Action new tab route.
*
@ -161,13 +220,22 @@ export class ApiTabComponent implements OnInit, OnChanges {
})
.finally();
}
private getTabInfoByID(id) {
private changeCurrentTab(tabInfo) {
this.tabs[this.selectedIndex] = Object.assign(this.tabs[this.selectedIndex], tabInfo);
}
/**
* Get tab info by api id or api data
* @param inArg.id exist api id
* @param apiData tab content api data
* @returns {TabItem}
*/
private getTabInfo(inArg: { id?: number; apiData?: any; path?: string }) {
let apiData = inArg.apiData || this.apiDataItems[inArg.id];
const result = {
path: this.router.url.split('?')[0],
title: this.apiDataItems[id].name,
method: this.apiDataItems[id].method,
key: this.apiDataItems[id].uuid,
path: inArg.path || this.router.url.split('?')[0],
title: apiData.name,
method: apiData.method,
key: apiData.uuid,
};
return result;
}

View File

@ -4,9 +4,12 @@ import { TabItem } from './tab.model';
export class ApiTabService {
currentTab: TabItem;
tabCache = {};
/**
* Tab Or Tab Content Change
*/
tabChange$: ReplaySubject<TabItem> = new ReplaySubject(1);
saveTabData$: Subject<{ tab: TabItem; data: any }> = new Subject();
apiEvent$: Subject<{ action: string; data?: any }> = new Subject();
get tabID(): number {
return this.currentTab.uuid;
}

View File

@ -23,7 +23,6 @@
<input
type="text"
name="uri"
id="uri"
nz-input
formControlName="uri"
[(ngModel)]="apiData.uri"
@ -33,7 +32,16 @@
{{ status === 'testing' ? '终止' : '发送' }}
<span *ngIf="status === 'testing' && waitSeconds" class="ml5">{{ waitSeconds }}</span>
</button>
<button type="button" *ngIf="!apiData.uuid" nz-button nzType="default" (click)="saveTestDataToApi()" class="ml10">保存为新 API</button>
<button
type="button"
*ngIf="!apiData.uuid"
nz-button
nzType="default"
(click)="saveTestDataToApi()"
class="ml10"
>
保存为新 API
</button>
</nz-input-group>
</nz-form-control>
</nz-form-item>
@ -45,7 +53,9 @@
<nz-tab [nzTitle]="headerTitleTmp" [nzForceRender]="true">
<ng-template #headerTitleTmp>
请求头部
<span class="eo-tab-icon">{{ apiData.requestHeaders | apiParamsNum }}</span>
<span class="eo-tab-icon" *ngIf="bindGetApiParamNum(apiData.requestHeaders)">{{
apiData.requestHeaders | apiParamsNum
}}</span>
</ng-template>
<eo-api-test-header class="eo_theme_iblock bbd" [model]="apiData.requestHeaders"></eo-api-test-header>
</nz-tab>
@ -53,7 +63,14 @@
<nz-tab [nzTitle]="bodyTitleTmp" [nzForceRender]="true">
<ng-template #bodyTitleTmp>
请求体
<span class="eo-tab-icon">{{ apiData.requestBody | apiParamsNum }}</span>
<span
class="iconfont icon-circle eo-tab-theme-icon"
*ngIf="
['formData', 'json', 'xml'].includes(apiData.requestBodyType)
? bindGetApiParamNum(apiData.requestBody)
: apiData.requestBody?.length
"
></span>
</ng-template>
<eo-api-test-body
class="eo_theme_iblock bbd"
@ -66,7 +83,9 @@
<nz-tab [nzTitle]="queryTitleTmp" [nzForceRender]="true">
<ng-template #queryTitleTmp>
Query 参数
<span class="eo-tab-icon">{{ apiData.queryParams | apiParamsNum }}</span>
<span class="eo-tab-icon" *ngIf="bindGetApiParamNum(apiData.queryParams)">{{
apiData.queryParams | apiParamsNum
}}</span>
</ng-template>
<eo-api-test-query
class="eo_theme_iblock bbd"
@ -77,7 +96,9 @@
<nz-tab [nzTitle]="restTitleTmp" [nzForceRender]="true">
<ng-template #restTitleTmp>
REST 参数
<span class="eo-tab-icon">{{ apiData.restParams | apiParamsNum }}</span>
<span class="eo-tab-icon" *ngIf="bindGetApiParamNum(apiData.restParams)">{{
apiData.restParams | apiParamsNum
}}</span>
</ng-template>
<eo-api-test-rest class="eo_theme_iblock bbd" [model]="apiData.restParams"></eo-api-test-rest>
</nz-tab>
@ -85,7 +106,7 @@
<!-- 响应信息 -->
<nz-tabset
[nzTabBarStyle]="{ 'padding-left': '10px' }"
[(nzSelectedIndex)]="this.tabIndexRes"
[(nzSelectedIndex)]="tabIndexRes"
[nzAnimated]="false"
class="mt10"
>

View File

@ -1,5 +1,5 @@
import { Component, OnInit, OnDestroy, ChangeDetectorRef, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ActivatedRoute } from '@angular/router';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Select } from '@ngxs/store';
@ -8,7 +8,7 @@ import { ApiData, RequestMethod, RequestProtocol } from '../../../shared/service
import { MessageService } from '../../../shared/services/message';
import { interval, Subscription, Observable, of, Subject } from 'rxjs';
import { take, takeUntil, distinctUntilChanged, pairwise } from 'rxjs/operators';
import { take, takeUntil, distinctUntilChanged, pairwise, filter } from 'rxjs/operators';
import { ApiTestHistoryComponent } from './history/api-test-history.component';
@ -19,6 +19,7 @@ import { ApiTabService } from '../tab/api-tab.service';
import { objectToArray } from '../../../utils';
import { EnvState } from '../../../shared/store/env.state';
import { ApiParamsNumPipe } from '../../../shared/pipes/api-param-num.pipe';
@Component({
selector: 'eo-api-test',
@ -47,18 +48,16 @@ export class ApiTestComponent implements OnInit, OnDestroy {
private status$: Subject<string> = new Subject<string>();
private timer$: Subscription;
private api$: Observable<object>;
private destroy$: Subject<void> = new Subject<void>();
constructor(
private fb: FormBuilder,
private router: Router,
private testServerService: TestServerService,
private storage: ApiDataService,
private route: ActivatedRoute,
private messageService: MessageService,
private ref: ChangeDetectorRef,
private apiTest: ApiTestService,
private apiTab: ApiTabService
private apiTab: ApiTabService,
private testServerService: TestServerService,
private messageService: MessageService
) {
this.testServer = this.testServerService.getService();
this.testServer.init((message) => {
@ -105,8 +104,8 @@ export class ApiTestComponent implements OnInit, OnDestroy {
history: this.testResult,
testData: this.apiData,
});
window.sessionStorage.setItem('testDataToAPI', JSON.stringify(apiData));
this.router.navigate(['/home/api/edit']);
window.sessionStorage.setItem('apiDataWillbeSave', JSON.stringify(apiData));
this.apiTab.apiEvent$.next({action:'addApiFromTest',data:apiData})
}
changeQuery() {
this.apiData.uri = this.apiTest.transferUrlAndQuery(this.apiData.uri, this.apiData.queryParams, {
@ -120,6 +119,9 @@ export class ApiTestComponent implements OnInit, OnDestroy {
replaceType: 'replace',
}).query;
}
bindGetApiParamNum(params) {
return new ApiParamsNumPipe().transform(params);
}
ngOnInit(): void {
this.initApi(Number(this.route.snapshot.queryParams.uuid));
this.watchTabChange();
@ -215,16 +217,23 @@ export class ApiTestComponent implements OnInit, OnDestroy {
});
}
private watchTabChange() {
this.apiTab.tabChange$.pipe(pairwise(), takeUntil(this.destroy$)).subscribe(([nowTab, nextTab]) => {
this.apiTab.saveTabData$.next({
tab: nowTab,
data: {
apiData: this.apiData,
testResult: this.testResult,
},
this.apiTab.tabChange$
.pipe(
pairwise(),
//actually change tab,not init tab
filter((data) => data[0].uuid !== data[1].uuid),
takeUntil(this.destroy$)
)
.subscribe(([nowTab, nextTab]) => {
this.apiTab.saveTabData$.next({
tab: nowTab,
data: {
apiData: this.apiData,
testResult: this.testResult,
},
});
this.initApi(nextTab.key);
});
this.initApi(nextTab.key);
});
}
/**
* Init API data structure

View File

@ -146,7 +146,7 @@ export class ApiTestService {
* @description Add query to URL and read query form url
* @param {string} url - whole url include query
* @param {object} query - ui query param
* @param {string} opts.priority - who's priority higher,url or query
* @param {string} opts.priority - which as priority higher,url or query
* @param {string} opts.replaceType - replace means only keep replace array,merge means union
* @returns {object} - {url:"",query:[]}
*/
@ -252,7 +252,6 @@ export class ApiTestService {
* @returns {ApiData}
*/
getApiFromTestData(inData) {
console.log('getApiFromTestData=>', inData);
let testToEditParams = (arr) => {
let result = [];
arr.forEach((val) => {
@ -265,7 +264,7 @@ export class ApiTestService {
};
let result = {
...inData.testData,
responseHeaders: [],
responseHeaders: inData.history.response.headers,
responseBodyType: 'json',
responseBodyJsonType: 'object',
responseBody: [],
@ -281,7 +280,6 @@ export class ApiTestService {
result.responseBodyType=bodyInfo.textType;
result.responseBodyJsonType=bodyInfo.rootType;
}
console.log('getApiFromTestData=>', result);
return result;
}
getTestDataFromApi(inData) {

View File

@ -1,5 +1,4 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'apiParamsNum',
pure: false,

View File

@ -154,7 +154,6 @@ export class TestServerAPIKitService implements TestServer {
env: formatEnv(opts.env),
testTime: formatDate(new Date(), 'YYYY-MM-dd HH:mm:ss', this.locale),
};
console.log(data,result)
return result;
}
/**

21
src/app/utils/api.ts Normal file
View File

@ -0,0 +1,21 @@
/**
* get rest param from url,format like {restName}
* @param url
* @returns {Array[string]}
*/
export const getRest: (url: string) => string[] = (url) => {
return [...url.replace(/{{(.*?)}}/g, '').matchAll(/{(.*?)}/g)].map((val) => val[1]);
};
export const addEnvPrefix = (prefix, uri) => {
// * 需要先判断uri是否已经包含 http:// 前缀
if (prefix == null) {
return uri;
}
const hasPrefix = /(http|https):\/{2}.+/.test(uri);
if (hasPrefix) {
return uri;
}
// * 添加前缀
return prefix + uri;
};

View File

@ -1,25 +1,5 @@
export const uuid = (): string => Math.random().toString(36).slice(-8);
/**
* get rest param from url,format like {restName}
* @param url
* @returns {Array[string]}
*/
export const getRest: (url: string) => string[] = (url) => {
return [...url.replace(/{{(.*?)}}/g, '').matchAll(/{(.*?)}/g)].map((val) => val[1]);
};
export const addEnvPrefix = (prefix, uri) => {
// * 需要先判断uri是否已经包含 http:// 前缀
if (prefix == null) {
return uri;
}
const hasPrefix = /(http|https):\/{2}.+/.test(uri);
if (hasPrefix) {
return uri;
}
// * 添加前缀
return prefix + uri;
};
// const DOMAIN_REGEX =
// '(^((http|wss|ws|ftp|https)://))|(^(((http|wss|ws|ftp|https)://)|)(([\\w\\-_]+([\\w\\-\\.]*)?(\\.(' +

View File

@ -47,7 +47,7 @@ export const treeToListHasLevel = (tree, opts: { listDepth: number; mapItem?: (v
val = opts.mapItem(val);
}
result.push(val);
if (val.children && val.children.length) {
if (val.children?.length) {
result = result.concat(
treeToListHasLevel(val.children, {
listDepth: opts.listDepth + 1,