api-render-ui 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,15 @@
1
1
  import { GLOBAL_STYLES } from './inlined-styles';
2
2
  import { OpenAPIV3 } from 'openapi-types';
3
3
 
4
- type OpenAPIV3CustomDoc = OpenAPIV3.Document & { swagger: string }
4
+ type OpenAPIV3CustomDoc = OpenAPIV3.Document & { swagger: string; definitions: any } //Compatible with version 2.0
5
5
 
6
6
  export class ApiRenderer {
7
7
  options: any;
8
8
  container: any;
9
+
10
+ // 静态标志:记录是否已注入全局样式
11
+ private static globalStyleInjected = false;
12
+
9
13
  constructor(options: any) {
10
14
  this.options = Object.assign({
11
15
  mountPoint: document?.body, // 默认挂载到body
@@ -19,10 +23,13 @@ export class ApiRenderer {
19
23
  render(apiSpec: OpenAPIV3CustomDoc, renderUnit: boolean = false) {
20
24
 
21
25
  const appendInlineStyle = (text: string) => {
22
- // 动态创建 <style> 标签并插入全局样式
23
- const styleEl = document.createElement('style');
24
- styleEl.textContent = text;
25
- document.head.appendChild(styleEl);
26
+ if (!ApiRenderer.globalStyleInjected) {
27
+ // 动态创建 <style> 标签并插入全局样式
28
+ const styleEl = document.createElement('style');
29
+ styleEl.textContent = text;
30
+ document.head.appendChild(styleEl);
31
+ ApiRenderer.globalStyleInjected = true; // 标记已注入
32
+ }
26
33
  };
27
34
  // 使用
28
35
  appendInlineStyle(GLOBAL_STYLES);
@@ -131,14 +138,53 @@ export class ApiRenderer {
131
138
  }
132
139
 
133
140
 
141
+ const auths = [
142
+ {
143
+ value: "No Auth"
144
+ },
145
+ {
146
+ value: "Basic Auth",
147
+ parameters: [
148
+ {
149
+ name: "userName",
150
+ value: ""
151
+ },
152
+ {
153
+ name: "password",
154
+ value: ""
155
+ }
156
+ ]
157
+ },
158
+ {
159
+ value: "OAuth 2.0",
160
+ }
161
+ ]
162
+
163
+ const grantTypes = [
164
+ { value: "authorization_code" },
165
+ { value: "Implicit" },
166
+ { value: "Resource Owner Password Credentials" },
167
+ { value: "client_credentials" }
168
+ ]
169
+
170
+ const clientAuthenticationOpts = [
171
+ { value: "Headers", displayName: "Send credentials in headers" },
172
+ { value: "Body", displayName: "Send credentials in body" }
173
+ ]
174
+
175
+ // ========== 数据定义 ==========
176
+ const consumeOpts = [
177
+ { value: "None" },
178
+ { value: "application/json" },
179
+ { value: "application/xml" },
180
+ { value: "application/x-www-form-urlencoded" },
181
+ { value: "multipart/form-data" },
182
+ { value: "text/plain" }
183
+ ];
184
+
134
185
 
135
186
  function renderApiUnit(apiOperator: any, containerRef: any, elementMap: any) {
136
187
  return function (evt: any) {
137
- console.log('点击的API操作:');
138
- console.log('方法:', apiOperator.method);
139
- console.log('URL:', apiOperator.url);
140
- console.log('------------------------');
141
-
142
188
  const currentTarget = evt.currentTarget;
143
189
  // 检查是否已存在对应的新元素
144
190
  if (elementMap.has(currentTarget)) {
@@ -186,7 +232,7 @@ function createApiUnit(apiOperator: any) {
186
232
  // 授权部分
187
233
  const authSection = createSectionAuth(apiOperator);
188
234
  // 请求体部分
189
- const bodySection = createSectionRequestBody(apiOperator.requestBody);
235
+ const bodySection = createSectionRequestBody(apiOperator);
190
236
 
191
237
  reqContent.append(paramSection, headerSection, authSection, bodySection);
192
238
  apiContainer.appendChild(reqContent);
@@ -226,7 +272,6 @@ function createReqOperator(apiOperator: any) {
226
272
  reqUrl.value = apiOperator.url; // 绑定初始值
227
273
  // 可选:添加输入事件监听(根据需求)
228
274
  reqUrl.addEventListener('input', (e: any) => {
229
- console.log('当前值:', e.target.value);
230
275
  // 这里可以添加保存逻辑(如更新状态/发送请求
231
276
  apiOperator.url = e.target.value
232
277
  });
@@ -242,7 +287,6 @@ function createReqOperator(apiOperator: any) {
242
287
  sendButton.appendChild(sendText);
243
288
 
244
289
  sendButton.addEventListener('click', (e: any) => {
245
- console.log('当前值:', e.target.value);
246
290
  // 这里可以添加保存逻辑(如更新状态/发送请求
247
291
  // responseSectionRef 在渲染时会被挂载到 apiOperator._responseSectionRef 上
248
292
  const respRef = apiOperator._responseSectionRef || null;
@@ -324,19 +368,191 @@ function createSectionAuth(apiOperator: any) {
324
368
  authValues.setAttribute('data-layer', 'paraKeyValues');
325
369
  authValues.className = 'Parakeyvalues codigma-apiunit-parakeyvalues';
326
370
 
327
- let auths = apiOperator.auths || []
328
- const authTypeRow = createSelectRow(auths, authValues)
329
- if (auths.length > 0) {
330
- let parameters = apiOperator.auths[0].parameters || [];
331
- const authRows = parameters.map((parameter: any) => createRow(parameter));
332
- authValues.append(authTypeRow, ...authRows);
333
- }
371
+ const authTypeRow = createSelectRow('Authorization Type', auths)
372
+ // 添加选择事件监听
373
+ authTypeRow.children[1].addEventListener('change', function (event: any) {
374
+ //切换前先移除掉原来的元素
375
+ authValues && Array.from(authValues.children).slice(1).forEach((el: any) => el.remove());
376
+ const auth = auths[event.target.selectedIndex];
377
+ if (auth.value == 'No Auth') {
378
+
379
+ } else if (auth.value == 'Basic Auth') {
380
+ apiOperator.auth = [
381
+ {
382
+ name: "userName",
383
+ value: ""
384
+ },
385
+ {
386
+ name: "password",
387
+ value: ""
388
+ }
389
+ ]
390
+ const authRows = apiOperator.auth.map((parameter: any) => createRow(parameter));
391
+ authValues.append(...authRows);
392
+ } else if (auth.value == 'OAuth 2.0') {
393
+ apiOperator.auth = {}
394
+ renderAuthForm(apiOperator, authValues, authTypeRow)
395
+ }
396
+ });
334
397
 
398
+ authValues.append(authTypeRow);
335
399
  authSection.append(authCnr, authValues);
336
400
  return authSection;
337
401
  }
338
402
 
339
- function createSectionRequestBody(requestBody: any) {
403
+ // 工具函数:创建带 class 和属性的元素
404
+ function createElement(tag: string, props: Record<string, string> = {}, classes: string[] = []): HTMLElement {
405
+ const el = document.createElement(tag);
406
+ Object.entries(props).forEach(([key, value]) => el.setAttribute(key, value));
407
+ if (classes.length) el.classList.add(...classes);
408
+ return el;
409
+ }
410
+
411
+ // 工具函数:创建 label + input 结构
412
+ function createLabeledInput(apiOperator: any,
413
+ labelText: string,
414
+ inputId: string,
415
+ inputName: string,
416
+ modelKey: string,
417
+ required: boolean = false
418
+ ): HTMLElement {
419
+ const wrapper = createElement('div', {}, ['Keyvalue', 'codigma-apiunit-keyvalue']);
420
+ const label = createElement('label', { for: inputId }, ['type', 'codigma-apiunit-send']);
421
+ label.textContent = labelText + ':';
422
+ const input = createElement('input', {
423
+ type: 'text',
424
+ id: inputId,
425
+ name: inputName,
426
+ autocomplete: 'off',
427
+ ...(required ? { required: 'true' } : {})
428
+ }, ['Valuetext', 'codigma-apiunit-valuetext']) as HTMLInputElement;
429
+
430
+ // 双向绑定模拟:初始化值 + 监听输入
431
+ input.value = apiOperator.auth[modelKey] || '';
432
+ input.addEventListener('input', (e) => {
433
+ apiOperator.auth[modelKey] = (e.target as HTMLInputElement).value;
434
+ });
435
+
436
+ wrapper.appendChild(label);
437
+ wrapper.appendChild(input);
438
+ return wrapper;
439
+ }
440
+
441
+ // 创建下拉选择框(替代 ast-select)
442
+ function createSelect(apiOperator: any,
443
+ labelText: string,
444
+ selectId: string,
445
+ options: { value: string; displayName?: string }[],
446
+ modelKey: string,
447
+ changeHandler: (value: string) => void
448
+ ): HTMLElement {
449
+ const wrapper = createElement('div', {}, ['Keyvalue', 'codigma-apiunit-keyvalue']);
450
+ const label = createElement('label', { for: selectId }, ['type', 'codigma-apiunit-send']);
451
+ label.textContent = labelText + ':';
452
+
453
+ const select = createElement('select', {
454
+ id: selectId,
455
+ name: selectId
456
+ }, []) as HTMLSelectElement;
457
+
458
+ options.forEach(opt => {
459
+ const option = createElement('option', { value: opt.value }) as HTMLOptionElement;
460
+ option.textContent = opt.displayName || opt.value;
461
+ select.appendChild(option);
462
+ });
463
+
464
+ // 初始化选中值
465
+ select.value = apiOperator.auth[modelKey] || options[0]?.value || '';
466
+ // changeHandler(select.value); // 触发初始设置
467
+
468
+ select.addEventListener('change', (e) => {
469
+ const val = (e.target as HTMLSelectElement).value;
470
+ changeHandler(val);
471
+ });
472
+
473
+ wrapper.appendChild(label);
474
+ wrapper.appendChild(select);
475
+ return wrapper;
476
+ }
477
+
478
+ // 重新渲染整个表单(基于当前 apiOperator.auth.grantType)
479
+ function renderAuthForm(apiOperator: any, authValues: any, authTypeRow: any) {
480
+ authValues.innerHTML = ''; // 清空
481
+
482
+ authValues.append(authTypeRow);
483
+ // Token
484
+ authValues.appendChild(createLabeledInput(apiOperator, 'Token', 'token', 'token', 'token', true));
485
+
486
+ // Grant Type
487
+ authValues.appendChild(
488
+ createSelect(apiOperator,
489
+ 'Grant Type',
490
+ 'grantType',
491
+ grantTypes,
492
+ 'grantType',
493
+ (value: string) => {
494
+ apiOperator.auth.grantType = value;
495
+ renderAuthForm(apiOperator, authValues, authTypeRow); // 重新渲染以反映条件字段
496
+ }
497
+ )
498
+ );
499
+
500
+ const gt = apiOperator.auth.grantType || 'authorization_code';
501
+
502
+ // Conditional: Authorization Code or Implicit
503
+ if (gt === 'authorization_code' || gt === 'Implicit') {
504
+ authValues.appendChild(createLabeledInput(apiOperator, 'Authorization Endpoint', 'authorizationEndpoint', 'authorizationEndpoint', 'authorizationEndpoint', true));
505
+ authValues.appendChild(createLabeledInput(apiOperator, 'Redirect URL', 'RedirectURI', 'RedirectURI', 'redirectURI', true));
506
+ }
507
+
508
+ // Conditional: Token Endpoint needed
509
+ if (gt === 'authorization_code' || gt === 'client_credentials' || gt === 'Resource Owner Password Credentials') {
510
+ authValues.appendChild(createLabeledInput(apiOperator, 'Token Endpoint', 'TokenEndpoint', 'TokenEndpoint', 'tokenEndpoint', true));
511
+ }
512
+
513
+ // Client ID (always shown)
514
+ authValues.appendChild(createLabeledInput(apiOperator, 'Client ID', 'ClientID', 'ClientID', 'clientId', true));
515
+
516
+ // Conditional: Client Secret
517
+ if (gt === 'authorization_code' || gt === 'client_credentials' || gt === 'Resource Owner Password Credentials') {
518
+ authValues.appendChild(createLabeledInput(apiOperator, 'Client Secret', 'ClientSecret', 'ClientSecret', 'clientSecret', true));
519
+ }
520
+
521
+ // Conditional: Username & Password
522
+ if (gt === 'Resource Owner Password Credentials') {
523
+ authValues.appendChild(createLabeledInput(apiOperator, 'Username', 'Username', 'Username', 'username', true));
524
+ authValues.appendChild(createLabeledInput(apiOperator, 'Password', 'Password', 'Password', 'password', true));
525
+ }
526
+
527
+ // Scopes (always shown)
528
+ authValues.appendChild(createLabeledInput(apiOperator, 'Scopes', 'Scopes', 'Scopes', 'scopes', true));
529
+
530
+ // Client Authentication
531
+ authValues.appendChild(
532
+ createSelect(apiOperator,
533
+ 'Client Authentication',
534
+ 'clientAuthentication',
535
+ clientAuthenticationOpts,
536
+ 'clientAuthentication',
537
+ (value: string) => {
538
+ apiOperator.auth.clientAuthentication = value;
539
+ // 不需要重新渲染整个表单,除非 UI 依赖此值
540
+ }
541
+ )
542
+ );
543
+
544
+ // --- 新增:Generate Token 按钮 ---
545
+ const buttonWrapper = createElement('div', {}, ['parameter-item']);
546
+ const generateBtn = createElement('button', {}, ['parameter-button']) as HTMLButtonElement;
547
+ generateBtn.type = 'button';
548
+ generateBtn.textContent = 'Generate Token';
549
+ generateBtn.addEventListener('click', () => generateOAuth2Token(apiOperator)); // 绑定点击事件
550
+
551
+ buttonWrapper.appendChild(generateBtn);
552
+ authValues.appendChild(buttonWrapper);
553
+ }
554
+
555
+ function createSectionRequestBody(apiOperator: any) {
340
556
  const bodySection = document.createElement('div');
341
557
  bodySection.setAttribute('data-layer', 'request-body-section');
342
558
  bodySection.className = 'RequestBodySection codigma-apiunit-request-body-section';
@@ -351,14 +567,170 @@ function createSectionRequestBody(requestBody: any) {
351
567
  bodyCnr.appendChild(bodyText);
352
568
  bodySection.appendChild(bodyCnr);
353
569
  // 请求体内容
354
- const bodyValue = document.createElement('textarea');
355
- bodyValue.setAttribute('data-layer', 'bodyTextValue');
356
- bodyValue.className = 'Id0CategoryId0NameNamePhotourlsTagsId0NameStatusAvailable codigma-apiunit-parakeyvalues';
357
- bodyValue.value = JSON.stringify(requestBody);
358
- bodySection.appendChild(bodyValue);
570
+ renderConsumeSection(apiOperator, bodySection, bodyCnr);
359
571
  return bodySection;
360
572
  }
361
573
 
574
+
575
+
576
+
577
+ // ========== 创建下拉选择框 ==========
578
+ function createConsumeSelect(apiOperator: any, container: HTMLElement, bodyCnr: any) {
579
+ const wrapper = createElement('div');
580
+ const label = createElement('label');
581
+ label.style.margin = '0 1rem';
582
+ label.textContent = 'Content-Type';
583
+
584
+ const select = createElement('select') as HTMLSelectElement;
585
+ select.style.width = '18rem'; // 模拟 [width]="'18rem'"
586
+
587
+ consumeOpts.forEach(opt => {
588
+ const option = document.createElement('option');
589
+ option.value = opt.value;
590
+ option.textContent = opt.value;
591
+ select.appendChild(option);
592
+ });
593
+
594
+ // 设置当前值
595
+ select.value = apiOperator.currentConsume || getCurrentConsume(apiOperator);
596
+
597
+ // 监听 change
598
+ select.addEventListener('change', (e) => {
599
+ const newValue = (e.target as HTMLSelectElement).value;
600
+ if (apiOperator.currentConsume !== newValue) {
601
+ apiOperator.currentConsume = newValue;
602
+ renderConsumeSection(apiOperator, container, bodyCnr); // 重新渲染整个区域
603
+ }
604
+ });
605
+
606
+ wrapper.appendChild(label);
607
+ wrapper.appendChild(select);
608
+ return wrapper;
609
+ }
610
+
611
+ // ========== 渲染请求体到指定容器(无 ID 依赖) ==========
612
+ function renderRequestBodyInto(apiOperator: any, targetDiv: HTMLElement) {
613
+ targetDiv.innerHTML = ''; // 清空目标区域
614
+
615
+ // 确保 requestBody 和 content 存在
616
+ const ct = apiOperator.currentConsume || getCurrentConsume(apiOperator);
617
+ // ✅ 情况 1: 使用 textarea(JSON/XML/None/text/plain)
618
+ if (
619
+ ct === null ||
620
+ ct === 'None' ||
621
+ ct === 'application/json' ||
622
+ ct === 'application/xml' ||
623
+ ct === 'text/plain'
624
+ ) {
625
+ // 决定使用哪个 key:'None' 也视为一种 content 类型(或可映射为 '',但这里直接用 'None')
626
+ const mimeType = ct || 'None';
627
+
628
+ // 从 content[mimeType] 读取,若无则默认空字符串
629
+ let rawValue = apiOperator.requestBody.content[mimeType];
630
+ if (rawValue == null) {
631
+ rawValue = '';
632
+ apiOperator.requestBody.content[mimeType] = rawValue; // 初始化
633
+ }
634
+
635
+ const textarea = createElement('textarea', {}, []) as HTMLTextAreaElement;
636
+ textarea.style.width = '100%';
637
+ textarea.style.height = '200px';
638
+ textarea.style.fontFamily = 'monospace';
639
+ textarea.style.fontSize = '14px';
640
+ textarea.placeholder = `Enter ${mimeType} body...`;
641
+
642
+ textarea.value = rawValue;
643
+
644
+ // 监听输入 → 写回 content[mimeType]
645
+ textarea.addEventListener('input', () => {
646
+ apiOperator.requestBody.content[mimeType] = textarea.value;
647
+ console.log(`Content updated for ${mimeType}:`, textarea.value);
648
+ });
649
+
650
+ targetDiv.appendChild(textarea);
651
+
652
+ }
653
+ // ✅ 情况 2: 表单参数
654
+ else if (
655
+ (ct === 'application/x-www-form-urlencoded' || ct === 'multipart/form-data') &&
656
+ apiOperator.requestBody.content &&
657
+ Array.isArray(apiOperator.requestBody.content[ct])
658
+ ) {
659
+ const params = apiOperator.requestBody.content[ct];
660
+ params.forEach((param: any, index: number) => {
661
+ const item = createElement('div', {}, ['Keyvalue', 'codigma-apiunit-keyvalue']);
662
+
663
+ const label = createElement('label', { for: `param-${Date.now()}-${index}` }, ['petId', 'codigma-apiunit-send']);
664
+ label.textContent = `${param.name}:`;
665
+
666
+ const input = createElement('input', {
667
+ type: param.uiType || 'text',
668
+ // 使用时间戳+索引避免 id 冲突(仅用于 label for)
669
+ id: `param-${Date.now()}-${index}`,
670
+ name: 'name'
671
+ }, ['Valuetext', 'codigma-apiunit-valuetext']) as HTMLInputElement;
672
+
673
+ input.value = param.value || '';
674
+ input.autocomplete = 'off';
675
+ input.required = true;
676
+
677
+ input.addEventListener('keydown', (e) => {
678
+ setEditStatus(e, true);
679
+ });
680
+
681
+ input.addEventListener('input', () => {
682
+ param.value = input.value;
683
+ });
684
+
685
+ item.appendChild(label);
686
+ item.appendChild(input);
687
+ targetDiv.appendChild(item);
688
+ });
689
+ }
690
+ }
691
+
692
+ function getCurrentConsume(apiOperator: any) {
693
+ if (!apiOperator.requestBody) {
694
+ apiOperator.requestBody = { content: {} };
695
+ }
696
+ if (!apiOperator.requestBody.content) {
697
+ apiOperator.requestBody.content = {};
698
+ }
699
+
700
+ const keys = Object.keys(apiOperator.requestBody.content);
701
+ const ct = apiOperator.currentConsume = keys.length > 0 ? keys[0] : 'None';
702
+ return ct;
703
+ }
704
+
705
+ // ========== 模拟 setEditStatus(你原有逻辑) ==========
706
+ function setEditStatus(event: KeyboardEvent, status: boolean) {
707
+ // TODO: 根据你的业务逻辑实现
708
+ console.log('setEditStatus called:', event.key, status);
709
+ }
710
+
711
+ // ========== 主渲染函数 ==========
712
+ function renderConsumeSection(apiOperator: any, container: HTMLElement, bodyCnr: any) {
713
+ container.innerHTML = '';
714
+
715
+ container.appendChild(bodyCnr);
716
+
717
+ // 创建外层包裹 div
718
+ const wrapperDiv = createElement('div', {}, ['codigma-apiunit-parakeyvalues']);
719
+
720
+ // 1. Content-Type 选择器
721
+ const selectWrapper = createConsumeSelect(apiOperator, container, bodyCnr);
722
+ wrapperDiv.appendChild(selectWrapper);
723
+
724
+ // 2. 请求体区域(不再使用固定 ID)
725
+ const reqBodyContainer = createElement('div', {}, ['codigma-apiunit-request-body-info']); // 无 id,避免冲突
726
+ renderRequestBodyInto(apiOperator, reqBodyContainer); // 改为传入目标容器
727
+ wrapperDiv.appendChild(reqBodyContainer);
728
+
729
+ // 将整个包裹 div 添加到外部容器
730
+ container.appendChild(wrapperDiv);
731
+ }
732
+
733
+
362
734
  function createSectionResponse(apiOperator: any) {
363
735
  const responseSection = document.createElement('div');
364
736
  responseSection.setAttribute('data-layer', 'reqresponse');
@@ -398,6 +770,11 @@ function createTimeStatusElement(apiOperator: any) {
398
770
  return timeStatus;
399
771
  }
400
772
 
773
+ function updateTimeStatus(timeStatus: any, apiOperator: any) {
774
+ timeStatus.textContent
775
+ = `Status: ${apiOperator.response.status || ""} ${apiOperator.response.statusText || ""} Time: ${apiOperator.requestDuration || ""}`;
776
+ }
777
+
401
778
  function createRow(parameter: any) {
402
779
  const petIdRow = document.createElement('div');
403
780
  petIdRow.setAttribute('data-layer', 'keyValue');
@@ -413,7 +790,6 @@ function createRow(parameter: any) {
413
790
 
414
791
  // 可选:添加输入事件监听(根据需求)
415
792
  petIdValue.addEventListener('input', (e: any) => {
416
- console.log('当前值:', e.target.value);
417
793
  // 这里可以添加保存逻辑(如更新状态/发送请求
418
794
  parameter["value"] = e.target.value
419
795
  });
@@ -430,7 +806,7 @@ function createInputElement() {
430
806
  return inputText;
431
807
  }
432
808
 
433
- function createSelectRow(auths: any, authValues: any) {
809
+ function createSelectRow(name: string, args: any) {
434
810
  // 创建外层容器div
435
811
  const container = document.createElement('div');
436
812
  container.setAttribute('data-layer', 'keyValue');
@@ -438,9 +814,9 @@ function createSelectRow(auths: any, authValues: any) {
438
814
 
439
815
  // 创建type显示div
440
816
  const typeLabel = document.createElement('div');
441
- typeLabel.setAttribute('data-layer', "type");
817
+ typeLabel.setAttribute('data-layer', name);
442
818
  typeLabel.className = 'type codigma-apiunit-send';
443
- typeLabel.textContent = 'Type:';
819
+ typeLabel.textContent = name + ":";
444
820
 
445
821
  // 创建select元素
446
822
  const selectElement: any = document.createElement('select');
@@ -449,22 +825,13 @@ function createSelectRow(auths: any, authValues: any) {
449
825
  selectElement.setAttribute('data-layer', 'valueText');
450
826
 
451
827
  // 示例选项(可根据实际需求添加
452
- auths.forEach((auth: any) => {
828
+ args.forEach((auth: any) => {
453
829
  const option1 = document.createElement('option');
454
- option1.value = auth["type"];
455
- option1.textContent = auth["type"];
830
+ option1.value = auth["value"];
831
+ option1.textContent = (auth['displayName'] != null || auth['label'] != null) ? (auth["displayName"] || auth['label']): auth["value"];
456
832
  selectElement.appendChild(option1);
457
833
  })
458
834
 
459
- // 添加选择事件监听
460
- selectElement.addEventListener('change', function (event: any) {
461
- //切换前先移除掉原来的元素
462
- authValues && Array.from(authValues.children).slice(1).forEach((el: any) => el.remove());
463
- const auth = auths[event.target.selectedIndex];
464
- let parameters = auth.parameters || [];
465
- const authRows = parameters.map((parameter: any) => createRow(parameter));
466
- authValues.append(...authRows);
467
- });
468
835
  // 组装DOM结构
469
836
  container.appendChild(typeLabel);
470
837
  container.appendChild(selectElement);
@@ -479,6 +846,179 @@ function isHeaderParam(param: any) {
479
846
  return param.in === 'header';
480
847
  }
481
848
 
849
+
850
+ async function generateOAuth2Token(apiInfo: any) {
851
+ // TODO 生成OAuth2.0 Token
852
+ // 1. 获取访问令牌
853
+ const accessToken = await getAccessToken(apiInfo);
854
+ apiInfo.auth.token = accessToken
855
+ }
856
+
857
+
858
+ /**
859
+ * 获取 OAuth 2.0 访问令牌
860
+ * @returns {Promise<string>} 访问令牌
861
+ */
862
+ async function getAccessToken(apiInfo: any) {
863
+ const auth = apiInfo.auth;
864
+ if (auth == null) {
865
+ throw new Error('OAuth2.0认证信息未配置');
866
+ }
867
+ const CLIENT_ID = auth.clientId;
868
+ const CLIENT_SECRET = auth.clientSecret;
869
+ const SCOPE = auth.scopes;
870
+
871
+ if (apiInfo.auth.grantType == 'client_credentials') {
872
+ try {
873
+ console.log('正在获取访问令牌...');
874
+
875
+ // 构造请求头
876
+ const headerParams: any = {
877
+ 'Content-Type': 'application/x-www-form-urlencoded'
878
+ };
879
+ // 构造请求体 (使用 client_credentials 流)
880
+ const bodyParams = new URLSearchParams();
881
+ bodyParams.append('grant_type', 'client_credentials');
882
+ if (SCOPE) {
883
+ bodyParams.append('scope', SCOPE);
884
+ }
885
+
886
+ if (apiInfo.auth.clientAuthentication == 'Body') {
887
+ bodyParams.append('client_id', CLIENT_ID);
888
+ bodyParams.append('client_secret', CLIENT_SECRET);
889
+ } else {
890
+ // 如果是 Headers 方式,则在后续的 fetch 请求中添加 Authorization 头
891
+ headerParams['Authorization'] = 'Basic ' + btoa(CLIENT_ID + ':' + CLIENT_SECRET);
892
+ }
893
+
894
+ const response = await fetch(auth.tokenEndpoint, {
895
+ method: 'POST',
896
+ headers: headerParams,
897
+ body: bodyParams.toString()
898
+ })
899
+
900
+ if (!response.ok) {
901
+ const errorData = await response.json().catch(() => ({}));
902
+ throw new Error(`获取令牌失败: ${response.status} ${response.statusText}. ${JSON.stringify(errorData)}`);
903
+ }
904
+
905
+ const tokenData = await response.json();
906
+ console.log('令牌获取成功:', tokenData);
907
+ return tokenData.access_token;
908
+ } catch (error) {
909
+ console.error('获取访问令牌时出错:', error);
910
+ throw error;
911
+ }
912
+ } else if (apiInfo.auth.grantType == 'Resource Owner Password Credentials') {
913
+
914
+ } else if (apiInfo.auth.grantType == 'authorization_code') {
915
+ // ========== 配置:替换为你在本地 OAuth 服务注册的客户端信息 ==========
916
+ const config = {
917
+ clientId: apiInfo.auth.clientId, // 替换为你的 client_id
918
+ clientSecret: apiInfo.auth.clientSecret, // 替换为你的 client_secret
919
+ redirectUri: apiInfo.auth.redirectURI, // 必须与注册的 redirect_uri 一致
920
+ authUrl: apiInfo.auth.authorizationEndpoint,
921
+ tokenUrl: apiInfo.auth.tokenEndpoint,
922
+ scope: apiInfo.auth.scopes // 可选: 'openid profile email' 如果服务支持
923
+ };
924
+
925
+ // 从 URL 获取参数
926
+ function getUrlParams() {
927
+ const params = new URLSearchParams(window.location.search);
928
+ return Object.fromEntries(params.entries());
929
+ }
930
+
931
+ // 构建授权 URL 并跳转
932
+ function redirectToAuth() {
933
+ const state = Math.random().toString(36).substring(2);
934
+ const nonce = Math.random().toString(36).substring(2); // OpenID Connect 推荐使用 nonce
935
+ const authUrl = new URL(config.authUrl);
936
+ authUrl.searchParams.append('client_id', config.clientId);
937
+ authUrl.searchParams.append('redirect_uri', config.redirectUri);
938
+ authUrl.searchParams.append('response_type', 'code');
939
+ authUrl.searchParams.append('scope', config.scope);
940
+ authUrl.searchParams.append('state', state);
941
+ authUrl.searchParams.append('nonce', nonce); // 用于 ID Token 验证
942
+ authUrl.searchParams.append('access_type', 'offline'); // 请求 refresh_token(如果支持)
943
+
944
+ const newTab = window.open(authUrl.toString(), '_blank');
945
+ }
946
+
947
+ // // 使用授权码换取 Token
948
+ // async function exchangeCodeForToken(code: any) {
949
+ // try {
950
+ // const response = await fetch(config.tokenUrl, {
951
+ // method: 'POST',
952
+ // headers: {
953
+ // 'Content-Type': 'application/x-www-form-urlencoded',
954
+ // 'Authorization': 'Basic ' + btoa(config.clientId + ':' + config.clientSecret)
955
+ // },
956
+ // body: new URLSearchParams({
957
+ // // 'client_id': config.clientId,
958
+ // // 'client_secret': config.clientSecret,
959
+ // 'code': code,
960
+ // 'redirect_uri': config.redirectUri,
961
+ // 'grant_type': 'authorization_code'
962
+ // })
963
+ // });
964
+
965
+ // if (!response.ok) {
966
+ // const errorText = await response.text();
967
+ // throw new Error(`HTTP ${response.status}: ${errorText}`);
968
+ // }
969
+
970
+ // const tokenData = await response.json();
971
+ // console.log('Token Response:', tokenData);
972
+
973
+ // // 可选:清除 URL 参数
974
+ // // history.replaceState({}, document.title, window.location.pathname);
975
+ // } catch (error) {
976
+ // console.error('Token Exchange Error:', error);
977
+ // }
978
+ // }
979
+
980
+ // // 页面初始化
981
+ // document.addEventListener('DOMContentLoaded', () => {
982
+ // const loginBtn = document.getElementById('loginBtn');
983
+ // const urlParams = getUrlParams();
984
+
985
+ // if (urlParams.code) {
986
+ // // 已收到授权码
987
+ // loginBtn.style.display = 'none';
988
+ // exchangeCodeForToken(urlParams.code);
989
+ // } else {
990
+
991
+ // }
992
+ // });
993
+
994
+ redirectToAuth();
995
+ } else if (apiInfo.auth.grantType == 'Implicit') {
996
+
997
+ } else {
998
+
999
+ }
1000
+ }
1001
+
1002
+
1003
+ function getBodyEditorContent(apiOperator: any): string {
1004
+ const ct = apiOperator.currentConsume || 'None';
1005
+ return apiOperator.requestBody.content[ct];
1006
+ }
1007
+
1008
+
1009
+
1010
+ let gotResponse = false;
1011
+
1012
+ let ifSendingRequest = false;
1013
+ let requestDuration: string = "0";
1014
+ let responseObj: {
1015
+ status ?: number | string;
1016
+ statusText ?: string;
1017
+ body ?: string;//响应体统一转换成字符串
1018
+ } = { };
1019
+ // 创建一个新的 AbortController 实例
1020
+ let controller: AbortController | undefined;
1021
+
482
1022
  /**
483
1023
  *
484
1024
  *
@@ -493,43 +1033,45 @@ function isHeaderParam(param: any) {
493
1033
  * @param {*} apiOperator
494
1034
  * @param {*} apiInfo
495
1035
  */
496
-
497
1036
  function sendRequest(apiOperator: any, responseSectionRef: any) {
498
- // 防御性检查:确保传入了 responseSectionRef,避免后续调用 querySelector 时抛出错误
499
- if (!responseSectionRef) {
500
- console.error('sendRequest: missing responseSectionRef. Aborting request to avoid runtime errors.');
501
- return;
502
- }
503
- let reuqestUrl = getRequestUrl(apiOperator);
504
- const result = checkIfParameter(apiOperator);
1037
+ const apiInfo = apiOperator
1038
+ let reuqestUrl = getRequestUrl(apiInfo);
1039
+
1040
+ //TODO 根据参数类型构造请求头和请求体,apiInfo['parameterHasFormDataVer2']表示有formData参数,暂时未区分具体的content-type,待改造
1041
+ let header = apiInfo['parameterHasFormDataVer2'] ? 'application/x-www-form-urlencoded' :
1042
+ ((apiInfo['parameterHasBody']) ? apiInfo['currentConsume'] : 'application/json');
505
1043
 
506
- let header = result.hasRequestBody ? 'application/json' :
507
- (result.hasRequestFormData ? 'application/x-www-form-urlencoded' : 'application/json');
508
1044
  let headers: any = {
509
1045
  'Content-Type': header
510
1046
  }
511
-
512
- //TODO
513
- if (apiOperator.custom) {
514
- for (let index = 0; index < apiOperator.customHeaderparameters.length; index++) {
515
- const paras = apiOperator.customHeaderparameters[index];
516
- if (paras.name != '' && paras.value != '' && paras.name != null && paras.value != null) {
517
- headers[paras.name] = paras.value
1047
+ const headerParas = getHeadersParams(apiInfo, apiInfo.rawApiInfo.parameters || []);
1048
+ // 合并自定义头参数
1049
+ Object.assign(headers, headerParas);
1050
+ // 构造 Basic Auth
1051
+ addAuthHeader(apiInfo, headers);
1052
+
1053
+ let body;
1054
+ if (apiInfo.method.toUpperCase() == "POST" || apiInfo.method.toUpperCase() == "PUT") {
1055
+ if (apiInfo['currentConsume'] == 'application/json' && apiInfo.requestBody != null) {
1056
+ body = getBodyEditorContent(apiOperator);
1057
+ } else if (apiInfo['parameterHasFormDataVer2']) {
1058
+ body = getRequestFormData(apiInfo.rawApiInfo);
1059
+ } else if (apiInfo['currentConsume'] == 'application/x-www-form-urlencoded') {
1060
+ for (const key of apiInfo["requestBody"]["content"][apiInfo['currentConsume']]) {
1061
+ if (body == null || body == '') {
1062
+ body = key.name + "=" + key.value;
1063
+ } else {
1064
+ body = body + "&" + key.name + "=" + key.value;
1065
+ }
518
1066
  }
1067
+ } else {
1068
+ body = "";
519
1069
  }
1070
+ } else {
1071
+ body = "";
520
1072
  }
521
1073
 
522
- let body = result.hasRequestBody ? apiOperator.requestBody :
523
- (result.hasRequestFormData ? getRequestFormData(apiOperator.rawApiInfo) : null);
524
-
525
- //TODO
526
- if (apiOperator.custom) {
527
- if (apiOperator.method.toUpperCase() == "POST" || apiOperator.method.toUpperCase() == "PUT") {
528
- body = apiOperator.requestBody;
529
- }
530
- }
531
-
532
- apiOperator.ifSendingRequest = true;
1074
+ ifSendingRequest = true;
533
1075
  const startTime = Date.now(); // 记录开始时间
534
1076
 
535
1077
  apiOperator.controller = new AbortController();
@@ -540,32 +1082,32 @@ function sendRequest(apiOperator: any, responseSectionRef: any) {
540
1082
 
541
1083
  // 使用 fetch 发送请求,并传递 signal
542
1084
  fetch(reuqestUrl, {
543
- method: apiOperator.method.toUpperCase(),
1085
+ method: apiInfo.method.toUpperCase(),
544
1086
  headers: headers,
545
- body: body != null ? JSON.stringify(body) : null,
1087
+ body: body,
546
1088
  signal: signal
547
1089
  })
548
1090
  .then(response => {
549
1091
  if (!response.ok) {
550
- apiOperator.response = {
1092
+ responseObj = {
551
1093
  status: response.status,
552
- statusText: response.statusText
1094
+ statusText: response.statusText,
1095
+ body: ""
553
1096
  }
554
1097
 
555
1098
  const endTime = Date.now(); // 即使在错误的情况下也记录结束时间
556
- apiOperator.requestDuration = formatDuration(endTime - startTime);
557
- apiOperator.ifSendingRequest = false;
558
- // apiOperator.responseJsoneditor.value = "";
1099
+ requestDuration = formatDuration(endTime - startTime);
1100
+ ifSendingRequest = false;
559
1101
  throw new Error('Network response was not ok.');
560
1102
  }
561
1103
  const endTime = Date.now(); // 记录结束时间
562
- apiOperator.requestDuration = formatDuration(endTime - startTime); // 计算耗时
1104
+ requestDuration = formatDuration(endTime - startTime); // 计算耗时
563
1105
 
564
1106
  const responsebodyElement = responseSectionRef.querySelector('[data-layer="responsebody"]');
565
1107
  responsebodyElement.removeChild(overlayLayerContainer);
566
1108
 
567
- apiOperator.ifSendingRequest = false;
568
- apiOperator.response = {
1109
+ ifSendingRequest = false;
1110
+ responseObj = {
569
1111
  status: response.status,
570
1112
  statusText: response.statusText
571
1113
  }
@@ -586,18 +1128,19 @@ function sendRequest(apiOperator: any, responseSectionRef: any) {
586
1128
  });
587
1129
  })
588
1130
  .then(data => {
589
- apiOperator.gotResponse = true;
1131
+ gotResponse = true;
590
1132
  const responsebodyElement = responseSectionRef.querySelector('[data-layer="responsebody"]');
591
1133
  // 此时 data 可能是 JSON 对象,也可能是文本字符串
592
1134
  if (typeof data === 'object') {
593
1135
  // 假设 data 是 JSON 对象,你可以在这里处理它
594
1136
  console.log('Received JSON:', data);
595
- responsebodyElement.textContent = JSON.stringify(data, null, 4)
1137
+ responseObj.body = JSON.stringify(data, null, 4);
596
1138
  } else {
597
1139
  // 假设 data 是文本字符串,你可以在这里处理它
598
1140
  console.log('Received text:', data);
599
- responsebodyElement.textContent = data;
1141
+ responseObj.body = data;
600
1142
  }
1143
+ responsebodyElement.textContent = responseObj.body;
601
1144
  })
602
1145
  .catch(error => {
603
1146
  // 错误处理
@@ -605,11 +1148,6 @@ function sendRequest(apiOperator: any, responseSectionRef: any) {
605
1148
  });
606
1149
  }
607
1150
 
608
- function updateTimeStatus(timeStatus: any, apiOperator: any) {
609
- timeStatus.textContent
610
- = `Status: ${apiOperator.response.status || ""} ${apiOperator.response.statusText || ""} Time: ${apiOperator.requestDuration || ""}`;
611
- }
612
-
613
1151
  function createRequestOverlayLayer(apiOperator: any, responseSectionRef: any) {
614
1152
  // 创建主容器
615
1153
  const container = document.createElement('div');
@@ -666,40 +1204,15 @@ function createRequestOverlayLayer(apiOperator: any, responseSectionRef: any) {
666
1204
  return container
667
1205
  }
668
1206
 
669
- function checkIfParameter(apiOperator: any) {
670
- let hasRequestBody = false;
671
- let hasRequestFormData = false;
672
- const parameters = apiOperator.rawApiInfo.parameters;
673
- if (parameters) {
674
- for (let index = 0; index < parameters.length; index++) {
675
- const parameter = parameters[index];
676
- if (parameter.in == "query" || parameter.in == "path") {
677
- } else if (parameter.in == "body") {
678
- hasRequestBody = true;
679
- parameter.name = parameter.name.charAt(0).toUpperCase() + parameter.name.slice(1);
680
- } else if (parameter.in == "formData") {
681
- hasRequestFormData = true;
682
- }
683
- }
684
- }
685
-
686
- //support openapi 3.0
687
- const requestBody = apiOperator.rawApiInfo.requestBody;
688
- if (requestBody) {
689
- hasRequestBody = true;
690
- }
691
- // 返回包含两个状态的对象
692
- return { hasRequestBody, hasRequestFormData };
693
- }
694
1207
 
695
1208
  function formatDuration(milliseconds: number) {
696
- let totalSeconds: string | number = Math.floor(milliseconds / 1000);
697
- let seconds: string | number = totalSeconds % 60;
698
- let minutes: string | number = Math.floor(totalSeconds / 60) % 60;
699
- let hours: string | number = Math.floor(totalSeconds / (60 * 60));
1209
+ let totalSeconds = Math.floor(milliseconds / 1000);
1210
+ let seconds: any = totalSeconds % 60;
1211
+ let minutes: any = Math.floor(totalSeconds / 60) % 60;
1212
+ let hours: any = Math.floor(totalSeconds / (60 * 60));
700
1213
 
701
1214
  // 毫秒部分
702
- let millisecondsPart: string | number = Math.floor(milliseconds % 1000);
1215
+ let millisecondsPart: any = Math.floor(milliseconds % 1000);
703
1216
  // 毫秒不足三位时前面补0
704
1217
  millisecondsPart = millisecondsPart.toString().padStart(3, '0');
705
1218
 
@@ -712,10 +1225,18 @@ function formatDuration(milliseconds: number) {
712
1225
  return `${hours}h${minutes}m${seconds}s${millisecondsPart}ms`;
713
1226
  }
714
1227
 
715
- function getRequestUrl(apiOperator: any) {
716
- let reuqestUrl = apiOperator.url;
1228
+ function clickAbortRequest() {
1229
+ // 如果你想取消请求,调用 controller 的 abort 方法
1230
+ if (controller) {
1231
+ controller.abort();
1232
+ ifSendingRequest = false;
1233
+ }
1234
+ }
1235
+
1236
+ function getRequestUrl(apiInfo: any) {
1237
+ let reuqestUrl = apiInfo.url;
717
1238
 
718
- const requestParameters = apiOperator.rawApiInfo.parameters || [];
1239
+ const requestParameters = apiInfo.rawApiInfo.parameters || [];
719
1240
  if (requestParameters == null) {
720
1241
  return reuqestUrl;
721
1242
  }
@@ -726,13 +1247,51 @@ function getRequestUrl(apiOperator: any) {
726
1247
  }
727
1248
  }
728
1249
 
729
- let queryParams = getQueryParams(apiOperator, requestParameters);
1250
+ let queryParams = getQueryParams(apiInfo, requestParameters);
730
1251
  reuqestUrl = queryParams.length > 0 ? (reuqestUrl + "?" + queryParams.join("&")) : reuqestUrl;
731
1252
 
732
1253
  return reuqestUrl;
733
1254
  }
734
1255
 
735
- function getQueryParams(apiOperator: any, requestParameters: any) {
1256
+ function addAuthHeader(apiInfo: any, headers: any) {
1257
+ const env = isBrowserEnvironment();
1258
+ if (apiInfo.auth.authType == "Basic Auth" && apiInfo.auth != null) {
1259
+ if (env) {
1260
+ const credentials = btoa(`${apiInfo.auth.username}:${apiInfo.auth.passWord}`); // btoa 是浏览器内置的 Base64 编码函数
1261
+ headers['Authorization'] = `Basic ${credentials}`;
1262
+ } else {
1263
+ const buffer = Buffer.from(`${apiInfo.auth.username}:${apiInfo.auth.passWord}`);
1264
+ const credentials = buffer.toString('base64');
1265
+ headers['Authorization'] = `Basic ${credentials}`;
1266
+ }
1267
+ } else if (apiInfo.auth.authType == "OAuth 2.0" && apiInfo.auth != null) {
1268
+ if (apiInfo.auth.token != null || apiInfo.auth.token != '') {
1269
+ headers['Authorization'] = `Bearer ${apiInfo.auth.token}`;
1270
+ }
1271
+ }
1272
+ }
1273
+
1274
+ function getHeadersParams(apiInfo: any, requestParameters: any) {
1275
+ let headersParams = [];
1276
+ for (const element of requestParameters) {
1277
+ if (element.in == "header") {
1278
+ headersParams[element.name] = element.value
1279
+ }
1280
+ }
1281
+
1282
+ if (apiInfo.customHeaderparameters) {
1283
+ for (let index = 0; index < apiInfo.customHeaderparameters.length; index++) {
1284
+ const paras = apiInfo.customHeaderparameters[index];
1285
+ if (paras.name != '' && paras.value != '' && paras.name != null && paras.value != null) {
1286
+ headersParams[paras.name] = paras.value
1287
+ }
1288
+ }
1289
+ }
1290
+
1291
+ return headersParams;
1292
+ }
1293
+
1294
+ function getQueryParams(apiInfo: any, requestParameters: any) {
736
1295
  let queryParams = [];
737
1296
  for (const element of requestParameters) {
738
1297
  if (element.in == "query") {
@@ -748,9 +1307,9 @@ function getQueryParams(apiOperator: any, requestParameters: any) {
748
1307
  }
749
1308
  }
750
1309
 
751
- if (apiOperator.custom) {
752
- for (let index = 0; index < apiOperator.customQueryparameters.length; index++) {
753
- const paras = apiOperator.customQueryparameters[index];
1310
+ if (apiInfo.customQueryparameters) {
1311
+ for (let index = 0; index < apiInfo.customQueryparameters.length; index++) {
1312
+ const paras = apiInfo.customQueryparameters[index];
754
1313
  if (paras.name != '' && paras.value != '' && paras.name != null && paras.value != null) {
755
1314
  queryParams.push(paras.name + "=" + paras.value)
756
1315
  }
@@ -781,6 +1340,16 @@ function getRequestFormData(rawApiInfo: any) {
781
1340
  return formData;
782
1341
  }
783
1342
 
1343
+ function isBrowserEnvironment() {
1344
+ if (typeof window !== 'undefined' && typeof document !== 'undefined') {
1345
+ return true; // 浏览器环境
1346
+ } else if (typeof process !== 'undefined' && process.versions && process.versions.node) {
1347
+ return false; // Node.js 环境
1348
+ } else {
1349
+ return false;
1350
+ }
1351
+ }
1352
+
784
1353
 
785
1354
  function createSvg() {
786
1355
  const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
@@ -803,20 +1372,17 @@ function createSvg() {
803
1372
 
804
1373
  function parseParaModel(parameterObj: any, dataDef: any) {
805
1374
  let bodyModel;
806
- if (parameterObj && parameterObj.schema) {
807
- if (parameterObj.schema['$ref']) { // 对象类型
808
- bodyModel = parseModel(parameterObj.schema['$ref'], dataDef);
809
- } else if (parameterObj.schema['type'] == 'array') { // 数组类型
810
- const itemObj = parameterObj.schema['items'];
811
- if (itemObj['$ref']) {
812
- bodyModel = parseModel(itemObj['$ref'], dataDef);
813
- } else if (itemObj['type']) {
814
- bodyModel = parseElement(itemObj);
815
- }
816
- bodyModel = [bodyModel];
1375
+ if (parameterObj.schema['$ref']) { // 对象类型
1376
+ bodyModel = parseModel(parameterObj.schema['$ref'], dataDef);
1377
+ } else if (parameterObj.schema['type'] == 'array') { // 数组类型
1378
+ const itemObj = parameterObj.schema['items'];
1379
+ if (itemObj['$ref']) {
1380
+ bodyModel = parseModel(itemObj['$ref'], dataDef);
1381
+ } else if (itemObj['type']) {
1382
+ bodyModel = parseElement(itemObj);
817
1383
  }
1384
+ bodyModel = [bodyModel];
818
1385
  }
819
-
820
1386
  return bodyModel;
821
1387
  }
822
1388
 
@@ -849,10 +1415,69 @@ function parseModel(modelDef: any, apiDef: any) {
849
1415
  return model;
850
1416
  }
851
1417
 
1418
+ function parseFormDataModel(modelDef: any, apiDef: any) {
1419
+ const model: any = [];
1420
+ const bodyName = modelDef.substring(modelDef.lastIndexOf('/') + 1);
1421
+ const def = apiDef[bodyName];
1422
+ const props = def['properties'];
1423
+ if (props) {
1424
+ for (const key in props) {
1425
+ if (Object.prototype.hasOwnProperty.call(props, key)) {
1426
+ const element = props[key];
1427
+ let modelEle: any;
1428
+ if (element.hasOwnProperty('items') && element['type'] == 'array') {
1429
+ if (element["items"]['$ref']) {
1430
+ modelEle = [parseModel(element["items"]['$ref'], apiDef)]
1431
+ } else if (element["items"]['type']) {
1432
+ modelEle = [parseElement(element["items"])];
1433
+ }
1434
+ if (modelEle) {
1435
+ model.push({
1436
+ type: 'string',
1437
+ uiType: 'text',
1438
+ name: key,
1439
+ value: JSON.stringify(modelEle)
1440
+ });
1441
+ }
1442
+ } else if (element['type']) {
1443
+ model.push({
1444
+ type: element['type'],
1445
+ uiType: ((element['type'] == 'integer' || element['type'] == 'number') ? 'number' : 'text'),
1446
+ name: key,
1447
+ value: parseElement(element)
1448
+ });
1449
+ } else if (element['$ref']) {
1450
+ const bodyModel = parseModel(element['$ref'], apiDef);
1451
+ model.push({
1452
+ type: 'string',
1453
+ uiType: 'text',
1454
+ name: key,
1455
+ value: JSON.stringify(bodyModel)
1456
+ });
1457
+ }
1458
+ }
1459
+ }
1460
+ }
1461
+
1462
+ return model;
1463
+ }
1464
+
1465
+ function parseFormDataParaModel(parameterObj: any, dataDef: any) {
1466
+ let bodyModel: any = [];
1467
+ if (parameterObj.schema['$ref']) { // 对象类型
1468
+ bodyModel = parseFormDataModel(parameterObj.schema['$ref'], dataDef);
1469
+ }
1470
+ return bodyModel;
1471
+ }
1472
+
852
1473
  function parseElement(element: any) {
853
1474
  let elementValue;
854
1475
  if (element['type'].includes('integer')) {
855
- elementValue = 0;
1476
+ if (element['enum']) {
1477
+ elementValue = element['enum'][0];
1478
+ } else {
1479
+ elementValue = 0;
1480
+ }
856
1481
  } else if (element['type'].includes('boolean')) {
857
1482
  elementValue = false;
858
1483
  } else if (element['type'].includes('string')) {
@@ -863,6 +1488,12 @@ function parseElement(element: any) {
863
1488
  }
864
1489
  }
865
1490
 
1491
+ // 如果有default则用default的值
1492
+ if (element['default']) {
1493
+ elementValue = element['default'];
1494
+ }
1495
+
1496
+ //如果有example则用example的值
866
1497
  if (element['example']) {
867
1498
  elementValue = element['example'];
868
1499
  }
@@ -870,6 +1501,18 @@ function parseElement(element: any) {
870
1501
  return elementValue;
871
1502
  }
872
1503
 
1504
+ function parseElementType(element: any) {
1505
+ if (element['type'].includes('integer')) {
1506
+ return 'integer';
1507
+ } else if (element['type'].includes('boolean')) {
1508
+ return 'boolean';
1509
+ } else if (element['type'].includes('string')) {
1510
+ return 'string';
1511
+ }
1512
+ return 'string';
1513
+ }
1514
+
1515
+
873
1516
  const HTTP_METHODS = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'] as const;
874
1517
  type HttpMethod = typeof HTTP_METHODS[number]; // 等价于你的 HttpMethods 的值类型
875
1518
 
@@ -909,56 +1552,123 @@ function parseOpenAPI(openapiSpec: OpenAPIV3CustomDoc) {
909
1552
  const apiOperator: any = {
910
1553
  method: method.toUpperCase(),
911
1554
  url: path,
912
- rawApiInfo: null,
1555
+ rawApiInfo: operation,
913
1556
  requestBody: null,
914
1557
  response: {},
915
- auths: [
916
- {
917
- type: "No Auth"
918
- },
919
- {
920
- type: "Basic Auth",
921
- parameters: [
922
- {
923
- name: "userName",
924
- value: ""
925
- },
926
- {
927
- name: "password",
928
- value: ""
929
- }
930
- ]
931
- }
932
- ]
1558
+ auth: {}
933
1559
  };
934
1560
 
935
1561
  if (operation) {
936
1562
  // requestBody
937
1563
  // support openapi 2.0
938
- if (operation.parameters) {
939
- const parameter = operation.parameters.filter((val: any) => val.in == 'body');
940
- if (parameter && parameter.length > 0) {
941
- // let requestBody = parseParaModel(parameter[0], data['definitions']);
942
- // apiOperator.requestBody = requestBody;
1564
+ // if (operation.parameters) {
1565
+ // const parameter = operation.parameters.filter((val: any) => val.in == 'body');
1566
+ // if (parameter && parameter.length > 0) {
1567
+ // // let requestBody = parseParaModel(parameter[0], data['definitions']);
1568
+ // // apiOperator.requestBody = requestBody;
1569
+ // }
1570
+ // }
1571
+
1572
+ // //support openapi 3.0
1573
+ // if (operation.requestBody) {
1574
+ // const requestBodyObject = operation.requestBody as OpenAPIV3.RequestBodyObject //目前只支持对象类型 TODO 引用类型待支持
1575
+ // const content = requestBodyObject.content;
1576
+ // for (const key in content) {
1577
+ // if (Object.prototype.hasOwnProperty.call(content, key)) {
1578
+ // const element: OpenAPIV3.MediaTypeObject = content[key];
1579
+ // if (element) {
1580
+ // let requestBody = parseParaModel(element, openapiSpec["components"]!["schemas"]);
1581
+ // apiOperator.requestBody = requestBody;
1582
+ // }
1583
+ // }
1584
+ // }
1585
+ // }
1586
+
1587
+
1588
+ if (apiOperator.rawApiInfo.parameters) {
1589
+ const parameterBody = apiOperator.rawApiInfo.parameters.filter((val: any) => val.in == 'body');
1590
+ if (parameterBody && parameterBody.length > 0) {
1591
+ apiOperator["requestBody"] = {
1592
+ content: {},
1593
+ }
1594
+ apiOperator['consumes'] = apiOperator.rawApiInfo['consumes']
1595
+ const currentConsume = apiOperator.rawApiInfo['consumes'].length > 0 ? apiOperator.rawApiInfo['consumes'][0] : "application/json" //TODO 简化处理暂时取第一个
1596
+ apiOperator['currentConsume'] = currentConsume
1597
+ let requestBody: any = parseParaModel(parameterBody[0], openapiSpec['definitions']); // support openapi 2.0
1598
+ apiOperator["requestBody"]["content"][currentConsume] = JSON.stringify(requestBody, null, 4);
1599
+ apiOperator["parameterHasBody"] = true;//标记该接口有body参数,仅适用于openapi 2.0
1600
+ } else {
1601
+ for (const parameter of apiOperator.rawApiInfo.parameters) {
1602
+ if (parameter.in == 'query' || parameter.in == 'header' || parameter.in == 'path' || parameter.in == 'formData') {
1603
+ if (parameter.type == 'integer' || (parameter.schema != null && parameter.schema.type == 'integer')
1604
+ || parameter.type == 'number' || (parameter.schema != null && parameter.schema.type == 'number')) {
1605
+ parameter.uiType = 'number';
1606
+ }
1607
+ if (parameter.type == 'string' || (parameter.schema != null && parameter.schema.type == 'string')) {
1608
+ parameter.uiType = 'text';
1609
+ }
1610
+ if (parameter.type == 'boolean' || (parameter.schema != null && parameter.schema.type == 'boolean')) {
1611
+ parameter.uiType = 'text';// TODO 未来可以改成checkbox
1612
+ }
1613
+ }
1614
+ }
1615
+ const parameterFormData = apiOperator.rawApiInfo.parameters.filter((val: any) => val.in == 'formData');
1616
+ if (parameterFormData && parameterFormData.length > 0) {
1617
+ apiOperator["parameterHasFormDataVer2"] = true;//标记该接口有formData参数,仅适用于openapi 2.0 body参数和formData参数互斥
1618
+ }
1619
+ // support openapi 3.0 enum parameter parsing
1620
+ for (const param of apiOperator.rawApiInfo.parameters) {
1621
+ const paramSchema = param.schema;
1622
+ if (paramSchema && paramSchema['$ref']) {
1623
+ const bodyName = paramSchema['$ref'].substring(paramSchema['$ref'].lastIndexOf('/') + 1);
1624
+ if (openapiSpec && openapiSpec["components"] != null && openapiSpec["components"]["schemas"]!=null) {
1625
+ const def = openapiSpec["components"] != null ? openapiSpec["components"]["schemas"][bodyName] : {type: ''};
1626
+ param.type = parseElementType(def);
1627
+ param.uiType = (param.type == 'integer' || param.type == 'number') ? 'number' : 'text';
1628
+ param.value = parseElement(def);
1629
+ }
1630
+ }
1631
+ }
943
1632
  }
944
1633
  }
945
1634
 
946
1635
  //support openapi 3.0
947
- if (operation.requestBody) {
948
- const requestBodyObject = operation.requestBody as OpenAPIV3.RequestBodyObject //目前只支持对象类型 TODO 引用类型待支持
949
- const content = requestBodyObject.content;
1636
+ if (apiOperator.rawApiInfo.requestBody) {
1637
+ const content = apiOperator.rawApiInfo.requestBody.content;
1638
+ apiOperator["requestBody"] = {
1639
+ content: {}
1640
+ }
1641
+
950
1642
  for (const key in content) {
951
1643
  if (Object.prototype.hasOwnProperty.call(content, key)) {
952
- const element: OpenAPIV3.MediaTypeObject = content[key];
1644
+ const element = content[key];
1645
+ apiOperator['consumes']?.push(key);
953
1646
  if (element) {
954
- let requestBody = parseParaModel(element, openapiSpec["components"]!["schemas"]);
955
- apiOperator.requestBody = requestBody;
1647
+ if (element.schema == undefined) {
1648
+ console.log("pathKey is:", path);
1649
+ console.log("key is:", key);
1650
+ console.log("content is:", content);
1651
+ console.log("Unsupported requestBody schema format:", element);
1652
+ continue;
1653
+ }
1654
+ if (key != 'application/x-www-form-urlencoded' && key != 'multipart/form-data') {//TODO 支持更多的content-type
1655
+ let requestBody: any = parseParaModel(element, openapiSpec["components"]!["schemas"]);
1656
+ apiOperator["requestBody"]["content"][key] = JSON.stringify(requestBody, null, 4)
1657
+ } else {
1658
+ let requestFormDataBody: any = parseFormDataParaModel(element, openapiSpec["components"]!["schemas"]);
1659
+ apiOperator["requestBody"]["content"][key] = requestFormDataBody;
1660
+ }
1661
+ apiOperator["parameterHasBody"] = true;//标记该接口有body参数,仅适用于openapi 3.0
956
1662
  }
957
1663
  }
958
1664
  }
1665
+ if (apiOperator.consumes) {
1666
+ if (apiOperator.consumes.length > 0) {
1667
+ apiOperator['currentConsume'] = apiOperator['consumes'][0]
1668
+ }
1669
+ }
959
1670
  }
960
1671
 
961
- apiOperator.rawApiInfo = operation;
962
1672
  // 添加到结果列表
963
1673
  apiOperatorList.push(apiOperator);
964
1674
  }