vue-api-request-builder 0.2.0 → 0.2.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.
package/README.md CHANGED
@@ -19,26 +19,6 @@ pnpm add vue-api-request-builder
19
19
 
20
20
  ### Basic Usage
21
21
 
22
- ```vue
23
- <template>
24
- <RequestForm v-model="requestSchema" @update:modelValue="handleSchemaChange" />
25
- <ResponseSection v-model="requestSchema" />
26
- </template>
27
-
28
- <script setup lang="ts">
29
- import { ref } from 'vue';
30
- import { RequestForm, ResponseSection, type RequestSchema, defaultRequestSchema } from 'vue-api-request-builder';
31
-
32
- const requestSchema = ref<RequestSchema>(defaultRequestSchema);
33
-
34
- const handleSchemaChange = (newSchema: RequestSchema) => {
35
- console.log("Schema changed:", newSchema);
36
- };
37
- </script>
38
- ```
39
-
40
- ### Advanced Usage with Preview
41
-
42
22
  ```vue
43
23
  <template>
44
24
  <div class="app-container">
@@ -48,7 +28,7 @@ const handleSchemaChange = (newSchema: RequestSchema) => {
48
28
  </template>
49
29
 
50
30
  <script setup lang="ts">
51
- import { ref, watch } from 'vue';
31
+ import { ref } from 'vue';
52
32
  import { RequestForm, ResponseSection, type RequestSchema, defaultRequestSchema } from 'vue-api-request-builder';
53
33
 
54
34
  const requestSchema = ref<RequestSchema>(defaultRequestSchema);
package/dist/index.css ADDED
@@ -0,0 +1 @@
1
+ *,:before,:after{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset: ;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / .5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: }::backdrop{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset: ;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / .5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: }[rows~="4"]{grid-template-rows:repeat(4,minmax(0,1fr))}[rows~="5"]{grid-template-rows:repeat(5,minmax(0,1fr))}[rows~="6"]{grid-template-rows:repeat(6,minmax(0,1fr))}.\!m-0{margin:0!important}.m-0{margin:0}.mb-1{margin-bottom:.25rem}.ms{margin-inline-start:1rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.h-6{height:1.5rem}.h-full{height:100%}.max-w-50\%{max-width:50%}.w-16{width:4rem}.w-40{width:10rem}.w-full{width:100%}.flex{display:flex}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.table{display:table}.items-center{align-items:center}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.border{border-width:1px}.border-gray-300{--un-border-opacity:1;border-color:rgb(209 213 219 / var(--un-border-opacity))}.border-solid{border-style:solid}.text-sm{font-size:.875rem;line-height:1.25rem}.font-bold{font-weight:700}.key-value-input[data-v-eef3e490]{width:100%}.empty-state[data-v-eef3e490]{color:#999;text-align:center;padding:8px 0}.key-value-input[data-v-eef3e490] .ant-typography pre{background-color:#f5f5f5;padding:4px 8px;border-radius:4px;margin:0;width:100%;overflow-x:auto}.key-value-input[data-v-eef3e490] .ant-space-item{flex:1}.key-value-input[data-v-eef3e490] .ant-space-item:first-child{flex:0 0 100px}.key-value-input[data-v-eef3e490] .ant-space-item:nth-child(2){flex:0 0 200px}.key-value-input[data-v-eef3e490] .ant-list-item{padding:4px 0}[data-v-eef3e490] .ant-typography pre{background-color:#f5f5f5;padding:8px;border-radius:4px;margin:0;width:100%;overflow-x:auto;max-height:60px;font-size:12px;line-height:1.5}.request-form[data-v-c9f65487]{margin:0;padding:0}.request-form[data-v-c9f65487] *{font-size:.9rem}.form-section[data-v-c9f65487]{margin-bottom:8px;width:100%}[data-v-c9f65487] .ant-card-head{background-color:#fafafa}[data-v-c9f65487] .ant-card-head-title{font-weight:500;padding:4px 0}[data-v-c9f65487] .ant-card-body{padding:8px 12px}[data-v-c9f65487] .ant-space{width:100%;display:flex;gap:8px;align-items:center}pre[data-v-c9f65487]{background-color:#f5f5f5;padding:8px;border-radius:4px;margin:0;width:100%;overflow-x:auto}.form-section[data-v-1dd2b288]{margin-bottom:8px}.response-section[data-v-1dd2b288]{margin-bottom:16px}.section-title[data-v-1dd2b288]{font-size:14px;font-weight:500;color:#1f1f1f;margin-bottom:8px;padding-left:4px;border-left:3px solid #1890ff}[data-v-1dd2b288] .ant-card-head{background-color:#fafafa}[data-v-1dd2b288] .ant-card-head-title{font-weight:500;padding:4px 0}[data-v-1dd2b288] .ant-card-body{padding:8px 12px}[data-v-1dd2b288] .ant-descriptions-item-label{font-weight:500}
package/dist/index.es.js CHANGED
@@ -1,4 +1,4 @@
1
- import { defineComponent as H, ref as w, watch as C, resolveComponent as c, createElementBlock as $, openBlock as _, createElementVNode as p, createCommentVNode as R, createBlock as O, createVNode as s, withCtx as r, createTextVNode as b, toDisplayString as z, Fragment as F, renderList as K, computed as J } from "vue";
1
+ import { defineComponent as H, ref as x, watch as C, resolveComponent as c, createElementBlock as $, openBlock as _, createElementVNode as p, createCommentVNode as R, createBlock as O, createVNode as s, withCtx as r, createTextVNode as b, toDisplayString as z, Fragment as F, renderList as K, computed as J } from "vue";
2
2
  const le = { class: "key-value-input flex flex-col gap-1" }, se = { class: "flex gap-2" }, ne = {
3
3
  key: 0,
4
4
  class: "flex flex-col gap-1"
@@ -12,7 +12,7 @@ const le = { class: "key-value-input flex flex-col gap-1" }, se = { class: "flex
12
12
  },
13
13
  emits: ["update:modelValue"],
14
14
  setup(a, { emit: g }) {
15
- const d = a, o = g, n = w(d.modelValue);
15
+ const d = a, o = g, n = x(d.modelValue);
16
16
  C(
17
17
  () => d.modelValue,
18
18
  (u) => {
@@ -33,13 +33,13 @@ const le = { class: "key-value-input flex flex-col gap-1" }, se = { class: "flex
33
33
  n.value = [];
34
34
  };
35
35
  return (u, t) => {
36
- const v = c("a-button"), x = c("a-popconfirm"), j = c("a-input"), B = c("a-typography-paragraph"), U = c("a-typography");
36
+ const v = c("a-button"), w = c("a-popconfirm"), j = c("a-input"), B = c("a-typography-paragraph"), U = c("a-typography");
37
37
  return _(), $("div", le, [
38
38
  p("div", se, [
39
39
  s(v, {
40
40
  type: "primary",
41
41
  onClick: y,
42
- class: "w-40 max-w-full",
42
+ class: "w-full max-w-50%",
43
43
  size: "small"
44
44
  }, {
45
45
  default: r(() => [
@@ -47,7 +47,8 @@ const le = { class: "key-value-input flex flex-col gap-1" }, se = { class: "flex
47
47
  ]),
48
48
  _: 1
49
49
  }),
50
- s(x, {
50
+ s(w, {
51
+ class: "w-full max-w-50%",
51
52
  title: "确认清空",
52
53
  description: "确定要清空所有参数吗?",
53
54
  "ok-text": "确认",
@@ -58,8 +59,8 @@ const le = { class: "key-value-input flex flex-col gap-1" }, se = { class: "flex
58
59
  s(v, {
59
60
  type: "primary",
60
61
  danger: "",
61
- class: "w-40 max-w-full",
62
62
  size: "small",
63
+ class: "w-full",
63
64
  disabled: n.value.length === 0
64
65
  }, {
65
66
  default: r(() => t[0] || (t[0] = [
@@ -128,7 +129,7 @@ const le = { class: "key-value-input flex flex-col gap-1" }, se = { class: "flex
128
129
  for (const [o, n] of g)
129
130
  d[o] = n;
130
131
  return d;
131
- }, L = /* @__PURE__ */ M(re, [["__scopeId", "data-v-1e5ef051"]]), de = {
132
+ }, L = /* @__PURE__ */ M(re, [["__scopeId", "data-v-eef3e490"]]), de = {
132
133
  method: "GET",
133
134
  url: "https://yesno.wtf",
134
135
  path: "/api",
@@ -146,14 +147,14 @@ const le = { class: "key-value-input flex flex-col gap-1" }, se = { class: "flex
146
147
  }, ye = { class: "flex flex-row gap-1 h-6" }, _e = { class: "flex flex-row gap-1" }, be = {
147
148
  key: 1,
148
149
  class: "flex flex-col gap-1 mt-1"
149
- }, xe = { class: "flex flex-row gap-1 items-center" }, we = /* @__PURE__ */ H({
150
+ }, we = { class: "flex flex-row gap-1 items-center" }, xe = /* @__PURE__ */ H({
150
151
  __name: "RequestForm",
151
152
  props: {
152
153
  modelValue: { default: () => de }
153
154
  },
154
155
  emits: ["update:modelValue"],
155
156
  setup(a, { emit: g }) {
156
- const d = a, o = g, n = w(d.modelValue.method), y = w(d.modelValue.url), l = w(d.modelValue.auth.type), f = w(d.modelValue.path), u = w(d.modelValue.auth.username || ""), t = w(d.modelValue.auth.password || ""), v = w(d.modelValue.auth.token || ""), x = w(d.modelValue.params), j = w(d.modelValue.headers), B = w(d.modelValue.body.formData || []), U = w(d.modelValue.body.type), k = w(d.modelValue.body.json || ""), S = w(d.modelValue.body.raw || ""), h = w("");
157
+ const d = a, o = g, n = x(d.modelValue.method), y = x(d.modelValue.url), l = x(d.modelValue.auth.type), f = x(d.modelValue.path), u = x(d.modelValue.auth.username || ""), t = x(d.modelValue.auth.password || ""), v = x(d.modelValue.auth.token || ""), w = x(d.modelValue.params), j = x(d.modelValue.headers), B = x(d.modelValue.body.formData || []), U = x(d.modelValue.body.type), k = x(d.modelValue.body.json || ""), S = x(d.modelValue.body.raw || ""), h = x("");
157
158
  function N(i, e) {
158
159
  let T;
159
160
  return function(...E) {
@@ -173,7 +174,7 @@ const le = { class: "key-value-input flex flex-col gap-1" }, se = { class: "flex
173
174
  token: v.value
174
175
  } : {}
175
176
  },
176
- params: x.value,
177
+ params: w.value,
177
178
  headers: j.value,
178
179
  body: {
179
180
  type: U.value,
@@ -191,7 +192,7 @@ const le = { class: "key-value-input flex flex-col gap-1" }, se = { class: "flex
191
192
  u,
192
193
  t,
193
194
  v,
194
- x,
195
+ w,
195
196
  j,
196
197
  U,
197
198
  B,
@@ -206,12 +207,12 @@ const le = { class: "key-value-input flex flex-col gap-1" }, se = { class: "flex
206
207
  () => d.modelValue,
207
208
  (i) => {
208
209
  const e = A();
209
- JSON.stringify(i) !== JSON.stringify(e) && (n.value = i.method, y.value = i.url, f.value = i.path, l.value = i.auth.type, u.value = i.auth.username || "", t.value = i.auth.password || "", v.value = i.auth.token || "", x.value = i.params, j.value = i.headers, U.value = i.body.type, B.value = i.body.formData || [], k.value = i.body.json || "", S.value = i.body.raw || "");
210
+ JSON.stringify(i) !== JSON.stringify(e) && (n.value = i.method, y.value = i.url, f.value = i.path, l.value = i.auth.type, u.value = i.auth.username || "", t.value = i.auth.password || "", v.value = i.auth.token || "", w.value = i.params, j.value = i.headers, U.value = i.body.type, B.value = i.body.formData || [], k.value = i.body.json || "", S.value = i.body.raw || "");
210
211
  },
211
212
  { deep: !0 }
212
213
  );
213
214
  const X = J(() => {
214
- const i = x.value.filter((e) => !!e.key).map((e) => e.key + "=" + encodeURIComponent(e.value)).join("&");
215
+ const i = w.value.filter((e) => !!e.key).map((e) => e.key + "=" + encodeURIComponent(e.value)).join("&");
215
216
  return i === "" ? "" : "?" + i;
216
217
  }), Q = J(() => {
217
218
  switch (U.value) {
@@ -249,7 +250,7 @@ ${T.value}\r
249
250
  const T = new URLSearchParams(e.search), E = [];
250
251
  T.forEach((V, D) => {
251
252
  E.push({ key: D, value: V });
252
- }), x.value = E;
253
+ }), w.value = E;
253
254
  } catch (i) {
254
255
  console.error("URL解析失败:", i);
255
256
  }
@@ -344,8 +345,8 @@ ${T.value}\r
344
345
  ]),
345
346
  p("div", fe, [
346
347
  s(L, {
347
- modelValue: x.value,
348
- "onUpdate:modelValue": e[3] || (e[3] = (m) => x.value = m),
348
+ modelValue: w.value,
349
+ "onUpdate:modelValue": e[3] || (e[3] = (m) => w.value = m),
349
350
  "add-button-text": "添加参数",
350
351
  "show-preview": !0,
351
352
  "preview-text": y.value + f.value + X.value
@@ -407,7 +408,7 @@ ${T.value}\r
407
408
  ])
408
409
  ])) : R("", !0),
409
410
  l.value === "Bearer" ? (_(), $("div", be, [
410
- p("div", xe, [
411
+ p("div", we, [
411
412
  e[24] || (e[24] = p("p", { class: "m-0 w-16 h-full" }, "Token", -1)),
412
413
  s(I, {
413
414
  value: v.value,
@@ -513,7 +514,7 @@ ${T.value}\r
513
514
  ]);
514
515
  };
515
516
  }
516
- }), Ee = /* @__PURE__ */ M(we, [["__scopeId", "data-v-c9f65487"]]);
517
+ }), Ee = /* @__PURE__ */ M(xe, [["__scopeId", "data-v-c9f65487"]]);
517
518
  async function ge(a, g = "xhr") {
518
519
  return g === "fetch" ? ke(a) : he(a);
519
520
  }
@@ -549,8 +550,8 @@ async function ke(a) {
549
550
  }
550
551
  try {
551
552
  const l = await fetch(d, n), f = {};
552
- l.headers.forEach((v, x) => {
553
- f[x.toLowerCase()] = v;
553
+ l.headers.forEach((v, w) => {
554
+ f[w.toLowerCase()] = v;
554
555
  });
555
556
  let u = "";
556
557
  if ((f["content-type"] || "").startsWith("application/json"))
@@ -585,8 +586,8 @@ async function he(a) {
585
586
  break;
586
587
  case "multipart/form-data":
587
588
  const v = new FormData();
588
- (u = a.body.formData) == null || u.forEach((x) => {
589
- x.key && v.append(x.key, x.value);
589
+ (u = a.body.formData) == null || u.forEach((w) => {
590
+ w.key && v.append(w.key, w.value);
590
591
  }), t = v;
591
592
  break;
592
593
  case "text/plain":
@@ -633,12 +634,12 @@ const Te = { style: { display: "flex", "align-items": "center", gap: "8px", "mar
633
634
  modelValue: {}
634
635
  },
635
636
  setup(a) {
636
- const g = a, d = w("xhr"), o = w({
637
+ const g = a, d = x("xhr"), o = x({
637
638
  status: "",
638
639
  headers: {},
639
640
  body: "",
640
641
  timing: 0
641
- }), n = w(""), y = (u) => {
642
+ }), n = x(""), y = (u) => {
642
643
  const t = Number(u);
643
644
  return t >= 200 && t < 300 ? "success" : t >= 300 && t < 400 ? "warning" : t >= 400 && t < 500 || t >= 500 ? "error" : "default";
644
645
  }, l = (u) => u instanceof Error ? u.message : "请求失败", f = async () => {
@@ -656,7 +657,7 @@ const Te = { style: { display: "flex", "align-items": "center", gap: "8px", "mar
656
657
  }
657
658
  };
658
659
  return (u, t) => {
659
- const v = c("a-radio-button"), x = c("a-radio-group"), j = c("a-button"), B = c("a-alert"), U = c("a-tag"), k = c("a-textarea"), S = c("a-card");
660
+ const v = c("a-radio-button"), w = c("a-radio-group"), j = c("a-button"), B = c("a-alert"), U = c("a-tag"), k = c("a-textarea"), S = c("a-card");
660
661
  return _(), O(S, {
661
662
  title: "响应",
662
663
  class: "form-section",
@@ -664,7 +665,7 @@ const Te = { style: { display: "flex", "align-items": "center", gap: "8px", "mar
664
665
  }, {
665
666
  default: r(() => [
666
667
  p("div", Te, [
667
- s(x, {
668
+ s(w, {
668
669
  value: d.value,
669
670
  "onUpdate:value": t[0] || (t[0] = (h) => d.value = h),
670
671
  "button-style": "solid",
package/dist/index.umd.js CHANGED
@@ -1,4 +1,4 @@
1
- (function(_,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],e):(_=typeof globalThis<"u"?globalThis:_||self,e(_.ApiRequestBuilder={},_.Vue))})(this,function(_,e){"use strict";const P={class:"key-value-input flex flex-col gap-1"},v={class:"flex gap-2"},I={key:0,class:"flex flex-col gap-1"},L={class:"flex w-full gap-1 items-center"},J=e.defineComponent({__name:"KeyValueInput",props:{modelValue:{},addButtonText:{default:"添加参数"},showPreview:{type:Boolean,default:!1},previewText:{default:""}},emits:["update:modelValue"],setup(a,{emit:y}){const d=a,l=y,r=e.ref(d.modelValue);e.watch(()=>d.modelValue,s=>{r.value=s}),e.watch(r,s=>{l("update:modelValue",s)},{deep:!0});const u=()=>{r.value.push({key:"",value:""})},n=s=>{r.value.splice(s,1)},m=()=>{r.value=[]};return(s,o)=>{const p=e.resolveComponent("a-button"),f=e.resolveComponent("a-popconfirm"),h=e.resolveComponent("a-input"),C=e.resolveComponent("a-typography-paragraph"),N=e.resolveComponent("a-typography");return e.openBlock(),e.createElementBlock("div",P,[e.createElementVNode("div",v,[e.createVNode(p,{type:"primary",onClick:u,class:"w-40 max-w-full",size:"small"},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(s.addButtonText),1)]),_:1}),e.createVNode(f,{title:"确认清空",description:"确定要清空所有参数吗?","ok-text":"确认","cancel-text":"取消",onConfirm:m},{default:e.withCtx(()=>[e.createVNode(p,{type:"primary",danger:"",class:"w-40 max-w-full",size:"small",disabled:r.value.length===0},{default:e.withCtx(()=>o[0]||(o[0]=[e.createTextVNode(" 清空 ")])),_:1},8,["disabled"])]),_:1})]),r.value.length>0?(e.openBlock(),e.createElementBlock("div",I,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(r.value,(V,k)=>(e.openBlock(),e.createElementBlock("div",{key:k,class:"flex"},[e.createElementVNode("div",L,[e.createVNode(p,{type:"primary",danger:"",onClick:x=>n(k),class:"w-16",size:"small"},{default:e.withCtx(()=>o[1]||(o[1]=[e.createTextVNode(" 删除 ")])),_:2},1032,["onClick"]),e.createVNode(h,{value:V.key,"onUpdate:value":x=>V.key=x,placeholder:"键",style:{width:"60%"},size:"small"},null,8,["value","onUpdate:value"]),e.createVNode(h,{value:V.value,"onUpdate:value":x=>V.value=x,placeholder:"值",style:{width:"100%"},size:"small"},null,8,["value","onUpdate:value"])])]))),128))])):e.createCommentVNode("",!0),s.showPreview?(e.openBlock(),e.createBlock(N,{key:1,class:"m-0"},{default:e.withCtx(()=>[e.createVNode(C,{class:"!m-0"},{default:e.withCtx(()=>[e.createElementVNode("pre",null,e.toDisplayString(s.previewText),1)]),_:1})]),_:1})):e.createCommentVNode("",!0)])}}}),S=(a,y)=>{const d=a.__vccOpts||a;for(const[l,r]of y)d[l]=r;return d},E=S(J,[["__scopeId","data-v-1e5ef051"]]),j={method:"GET",url:"https://yesno.wtf",path:"/api",auth:{type:"none"},params:[],headers:[],body:{type:"application/json"}},F={class:"request-form"},H={class:"flex flex-col gap-1"},M={class:"flex flex-row gap-1"},A={class:"flex flex-row gap-1"},K={class:"mt-1"},G={key:0,class:"flex flex-col gap-1 mt-2"},W={class:"flex flex-row gap-1 h-6"},X={class:"flex flex-row gap-1"},Q={key:1,class:"flex flex-col gap-1 mt-1"},Y={class:"flex flex-row gap-1 items-center"},q=S(e.defineComponent({__name:"RequestForm",props:{modelValue:{default:()=>j}},emits:["update:modelValue"],setup(a,{emit:y}){const d=a,l=y,r=e.ref(d.modelValue.method),u=e.ref(d.modelValue.url),n=e.ref(d.modelValue.auth.type),m=e.ref(d.modelValue.path),s=e.ref(d.modelValue.auth.username||""),o=e.ref(d.modelValue.auth.password||""),p=e.ref(d.modelValue.auth.token||""),f=e.ref(d.modelValue.params),h=e.ref(d.modelValue.headers),C=e.ref(d.modelValue.body.formData||[]),N=e.ref(d.modelValue.body.type),V=e.ref(d.modelValue.body.json||""),k=e.ref(d.modelValue.body.raw||""),x=e.ref("");function U(i,t){let w;return function(...g){clearTimeout(w),w=setTimeout(()=>i.apply(this,g),t)}}const z=()=>({method:r.value,url:u.value,path:m.value,auth:{type:n.value,...n.value==="Basic"?{username:s.value,password:o.value}:n.value==="Bearer"?{token:p.value}:{}},params:f.value,headers:h.value,body:{type:N.value,...N.value==="application/json"?{json:V.value}:N.value==="multipart/form-data"?{formData:C.value}:{raw:k.value}}}),pe=U(()=>{l("update:modelValue",z())},100);e.watch([r,u,m,n,s,o,p,f,h,N,C,V,k],()=>{pe()},{deep:!0}),e.watch(()=>d.modelValue,i=>{const t=z();JSON.stringify(i)!==JSON.stringify(t)&&(r.value=i.method,u.value=i.url,m.value=i.path,n.value=i.auth.type,s.value=i.auth.username||"",o.value=i.auth.password||"",p.value=i.auth.token||"",f.value=i.params,h.value=i.headers,N.value=i.body.type,C.value=i.body.formData||[],V.value=i.body.json||"",k.value=i.body.raw||"")},{deep:!0});const ce=e.computed(()=>{const i=f.value.filter(t=>!!t.key).map(t=>t.key+"="+encodeURIComponent(t.value)).join("&");return i===""?"":"?"+i}),me=e.computed(()=>{switch(N.value){case"application/json":try{return V.value?JSON.stringify(JSON.parse(V.value),null,2):"-空-"}catch{return"Invalid JSON"}case"multipart/form-data":const i="----WebKitFormBoundaryPreview";return C.value.filter(w=>!!w.key).map(w=>`--${i}\r
1
+ (function(_,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],e):(_=typeof globalThis<"u"?globalThis:_||self,e(_.ApiRequestBuilder={},_.Vue))})(this,function(_,e){"use strict";const P={class:"key-value-input flex flex-col gap-1"},v={class:"flex gap-2"},I={key:0,class:"flex flex-col gap-1"},L={class:"flex w-full gap-1 items-center"},J=e.defineComponent({__name:"KeyValueInput",props:{modelValue:{},addButtonText:{default:"添加参数"},showPreview:{type:Boolean,default:!1},previewText:{default:""}},emits:["update:modelValue"],setup(a,{emit:y}){const d=a,l=y,r=e.ref(d.modelValue);e.watch(()=>d.modelValue,s=>{r.value=s}),e.watch(r,s=>{l("update:modelValue",s)},{deep:!0});const u=()=>{r.value.push({key:"",value:""})},n=s=>{r.value.splice(s,1)},m=()=>{r.value=[]};return(s,o)=>{const p=e.resolveComponent("a-button"),f=e.resolveComponent("a-popconfirm"),h=e.resolveComponent("a-input"),C=e.resolveComponent("a-typography-paragraph"),N=e.resolveComponent("a-typography");return e.openBlock(),e.createElementBlock("div",P,[e.createElementVNode("div",v,[e.createVNode(p,{type:"primary",onClick:u,class:"w-full max-w-50%",size:"small"},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(s.addButtonText),1)]),_:1}),e.createVNode(f,{class:"w-full max-w-50%",title:"确认清空",description:"确定要清空所有参数吗?","ok-text":"确认","cancel-text":"取消",onConfirm:m},{default:e.withCtx(()=>[e.createVNode(p,{type:"primary",danger:"",size:"small",class:"w-full",disabled:r.value.length===0},{default:e.withCtx(()=>o[0]||(o[0]=[e.createTextVNode(" 清空 ")])),_:1},8,["disabled"])]),_:1})]),r.value.length>0?(e.openBlock(),e.createElementBlock("div",I,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(r.value,(V,k)=>(e.openBlock(),e.createElementBlock("div",{key:k,class:"flex"},[e.createElementVNode("div",L,[e.createVNode(p,{type:"primary",danger:"",onClick:x=>n(k),class:"w-16",size:"small"},{default:e.withCtx(()=>o[1]||(o[1]=[e.createTextVNode(" 删除 ")])),_:2},1032,["onClick"]),e.createVNode(h,{value:V.key,"onUpdate:value":x=>V.key=x,placeholder:"键",style:{width:"60%"},size:"small"},null,8,["value","onUpdate:value"]),e.createVNode(h,{value:V.value,"onUpdate:value":x=>V.value=x,placeholder:"值",style:{width:"100%"},size:"small"},null,8,["value","onUpdate:value"])])]))),128))])):e.createCommentVNode("",!0),s.showPreview?(e.openBlock(),e.createBlock(N,{key:1,class:"m-0"},{default:e.withCtx(()=>[e.createVNode(C,{class:"!m-0"},{default:e.withCtx(()=>[e.createElementVNode("pre",null,e.toDisplayString(s.previewText),1)]),_:1})]),_:1})):e.createCommentVNode("",!0)])}}}),S=(a,y)=>{const d=a.__vccOpts||a;for(const[l,r]of y)d[l]=r;return d},E=S(J,[["__scopeId","data-v-eef3e490"]]),j={method:"GET",url:"https://yesno.wtf",path:"/api",auth:{type:"none"},params:[],headers:[],body:{type:"application/json"}},F={class:"request-form"},H={class:"flex flex-col gap-1"},M={class:"flex flex-row gap-1"},A={class:"flex flex-row gap-1"},K={class:"mt-1"},G={key:0,class:"flex flex-col gap-1 mt-2"},W={class:"flex flex-row gap-1 h-6"},X={class:"flex flex-row gap-1"},Q={key:1,class:"flex flex-col gap-1 mt-1"},Y={class:"flex flex-row gap-1 items-center"},q=S(e.defineComponent({__name:"RequestForm",props:{modelValue:{default:()=>j}},emits:["update:modelValue"],setup(a,{emit:y}){const d=a,l=y,r=e.ref(d.modelValue.method),u=e.ref(d.modelValue.url),n=e.ref(d.modelValue.auth.type),m=e.ref(d.modelValue.path),s=e.ref(d.modelValue.auth.username||""),o=e.ref(d.modelValue.auth.password||""),p=e.ref(d.modelValue.auth.token||""),f=e.ref(d.modelValue.params),h=e.ref(d.modelValue.headers),C=e.ref(d.modelValue.body.formData||[]),N=e.ref(d.modelValue.body.type),V=e.ref(d.modelValue.body.json||""),k=e.ref(d.modelValue.body.raw||""),x=e.ref("");function U(i,t){let w;return function(...g){clearTimeout(w),w=setTimeout(()=>i.apply(this,g),t)}}const z=()=>({method:r.value,url:u.value,path:m.value,auth:{type:n.value,...n.value==="Basic"?{username:s.value,password:o.value}:n.value==="Bearer"?{token:p.value}:{}},params:f.value,headers:h.value,body:{type:N.value,...N.value==="application/json"?{json:V.value}:N.value==="multipart/form-data"?{formData:C.value}:{raw:k.value}}}),pe=U(()=>{l("update:modelValue",z())},100);e.watch([r,u,m,n,s,o,p,f,h,N,C,V,k],()=>{pe()},{deep:!0}),e.watch(()=>d.modelValue,i=>{const t=z();JSON.stringify(i)!==JSON.stringify(t)&&(r.value=i.method,u.value=i.url,m.value=i.path,n.value=i.auth.type,s.value=i.auth.username||"",o.value=i.auth.password||"",p.value=i.auth.token||"",f.value=i.params,h.value=i.headers,N.value=i.body.type,C.value=i.body.formData||[],V.value=i.body.json||"",k.value=i.body.raw||"")},{deep:!0});const ce=e.computed(()=>{const i=f.value.filter(t=>!!t.key).map(t=>t.key+"="+encodeURIComponent(t.value)).join("&");return i===""?"":"?"+i}),me=e.computed(()=>{switch(N.value){case"application/json":try{return V.value?JSON.stringify(JSON.parse(V.value),null,2):"-空-"}catch{return"Invalid JSON"}case"multipart/form-data":const i="----WebKitFormBoundaryPreview";return C.value.filter(w=>!!w.key).map(w=>`--${i}\r
2
2
  Content-Disposition: form-data; name="${w.key}"\r
3
3
  \r
4
4
  ${w.value}\r
package/package.json CHANGED
@@ -1,16 +1,17 @@
1
1
  {
2
2
  "name": "vue-api-request-builder",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "main": "dist/index.umd.js",
6
6
  "module": "dist/index.es.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "files": [
9
- "dist",
10
- "lib"
9
+ "dist"
11
10
  ],
12
11
  "scripts": {
13
12
  "dev": "vite",
13
+ "test": "vite --config vite.test.config.ts",
14
+ "build:all": "npm run build && npm run build:static",
14
15
  "build": "vue-tsc -b && vite build --config vite.lib.config.ts",
15
16
  "build:static": "vue-tsc -b && vite build",
16
17
  "preview": "vite preview"
@@ -22,7 +23,7 @@
22
23
  "dependencies": {
23
24
  "ant-design-vue": "4.x",
24
25
  "vue": "^3.5.13",
25
- "vue-api-request-builder": "^0.1.0"
26
+ "vue-api-request-builder": "^0.2.1"
26
27
  },
27
28
  "devDependencies": {
28
29
  "@types/node": "^20.0.0",
@@ -1 +0,0 @@
1
- .key-value-input[data-v-1e5ef051]{width:100%}.empty-state[data-v-1e5ef051]{color:#999;text-align:center;padding:8px 0}.key-value-input[data-v-1e5ef051] .ant-typography pre{background-color:#f5f5f5;padding:4px 8px;border-radius:4px;margin:0;width:100%;overflow-x:auto}.key-value-input[data-v-1e5ef051] .ant-space-item{flex:1}.key-value-input[data-v-1e5ef051] .ant-space-item:first-child{flex:0 0 100px}.key-value-input[data-v-1e5ef051] .ant-space-item:nth-child(2){flex:0 0 200px}.key-value-input[data-v-1e5ef051] .ant-list-item{padding:4px 0}[data-v-1e5ef051] .ant-typography pre{background-color:#f5f5f5;padding:8px;border-radius:4px;margin:0;width:100%;overflow-x:auto;max-height:60px;font-size:12px;line-height:1.5}.request-form[data-v-c9f65487]{margin:0;padding:0}.request-form[data-v-c9f65487] *{font-size:.9rem}.form-section[data-v-c9f65487]{margin-bottom:8px;width:100%}[data-v-c9f65487] .ant-card-head{background-color:#fafafa}[data-v-c9f65487] .ant-card-head-title{font-weight:500;padding:4px 0}[data-v-c9f65487] .ant-card-body{padding:8px 12px}[data-v-c9f65487] .ant-space{width:100%;display:flex;gap:8px;align-items:center}pre[data-v-c9f65487]{background-color:#f5f5f5;padding:8px;border-radius:4px;margin:0;width:100%;overflow-x:auto}.form-section[data-v-1dd2b288]{margin-bottom:8px}.response-section[data-v-1dd2b288]{margin-bottom:16px}.section-title[data-v-1dd2b288]{font-size:14px;font-weight:500;color:#1f1f1f;margin-bottom:8px;padding-left:4px;border-left:3px solid #1890ff}[data-v-1dd2b288] .ant-card-head{background-color:#fafafa}[data-v-1dd2b288] .ant-card-head-title{font-weight:500;padding:4px 0}[data-v-1dd2b288] .ant-card-body{padding:8px 12px}[data-v-1dd2b288] .ant-descriptions-item-label{font-weight:500}
@@ -1,144 +0,0 @@
1
- <template>
2
- <div class="key-value-input flex flex-col gap-1">
3
- <div class="flex gap-2">
4
- <a-button type="primary" @click="addItem" class="w-40 max-w-full" size="small">
5
- {{ addButtonText }}
6
- </a-button>
7
- <a-popconfirm
8
- title="确认清空"
9
- description="确定要清空所有参数吗?"
10
- ok-text="确认"
11
- cancel-text="取消"
12
- @confirm="clearItems"
13
- >
14
- <a-button type="primary" danger class="w-40 max-w-full" size="small" :disabled="items.length === 0">
15
- 清空
16
- </a-button>
17
- </a-popconfirm>
18
- </div>
19
-
20
- <template v-if="items.length > 0">
21
- <div class="flex flex-col gap-1">
22
- <div v-for="(item, index) in items" :key="index" class="flex">
23
- <div class="flex w-full gap-1 items-center">
24
- <a-button type="primary" danger @click="removeItem(index)" class="w-16" size="small">
25
- 删除
26
- </a-button>
27
- <a-input v-model:value="item.key" placeholder="键" style="width: 60%" size="small" />
28
- <a-input v-model:value="item.value" placeholder="值" style="width: 100%" size="small" />
29
- </div>
30
- </div>
31
- </div>
32
- </template>
33
-
34
- <a-typography v-if="showPreview" class="m-0">
35
- <a-typography-paragraph class="!m-0">
36
- <pre>{{ previewText }}</pre>
37
- </a-typography-paragraph>
38
- </a-typography>
39
- </div>
40
- </template>
41
-
42
- <script setup lang="ts">
43
- import { ref, watch } from "vue";
44
-
45
- interface KeyValueItem {
46
- key: string;
47
- value: string;
48
- }
49
-
50
- interface Props {
51
- modelValue: KeyValueItem[];
52
- addButtonText?: string;
53
- showPreview?: boolean;
54
- previewText?: string;
55
- }
56
-
57
- const props = withDefaults(defineProps<Props>(), {
58
- addButtonText: "添加参数",
59
- showPreview: false,
60
- previewText: "",
61
- });
62
-
63
- const emit = defineEmits<{
64
- (e: "update:modelValue", value: KeyValueItem[]): void;
65
- }>();
66
-
67
- const items = ref<KeyValueItem[]>(props.modelValue);
68
-
69
- watch(
70
- () => props.modelValue,
71
- (newValue) => {
72
- items.value = newValue;
73
- }
74
- );
75
-
76
- watch(
77
- items,
78
- (newValue) => {
79
- emit("update:modelValue", newValue);
80
- },
81
- { deep: true }
82
- );
83
-
84
- const addItem = () => {
85
- items.value.push({ key: "", value: "" });
86
- };
87
-
88
- const removeItem = (index: number) => {
89
- items.value.splice(index, 1);
90
- };
91
-
92
- const clearItems = () => {
93
- items.value = [];
94
- };
95
- </script>
96
-
97
- <style scoped>
98
- .key-value-input {
99
- width: 100%;
100
- }
101
-
102
- .empty-state {
103
- color: #999;
104
- text-align: center;
105
- padding: 8px 0;
106
- }
107
-
108
- .key-value-input :deep(.ant-typography pre) {
109
- background-color: #f5f5f5;
110
- padding: 4px 8px;
111
- border-radius: 4px;
112
- margin: 0;
113
- width: 100%;
114
- overflow-x: auto;
115
- }
116
-
117
- .key-value-input :deep(.ant-space-item) {
118
- flex: 1;
119
- }
120
-
121
- .key-value-input :deep(.ant-space-item:first-child) {
122
- flex: 0 0 100px;
123
- }
124
-
125
- .key-value-input :deep(.ant-space-item:nth-child(2)) {
126
- flex: 0 0 200px;
127
- }
128
-
129
- .key-value-input :deep(.ant-list-item) {
130
- padding: 4px 0;
131
- }
132
-
133
- :deep(.ant-typography pre) {
134
- background-color: #f5f5f5;
135
- padding: 8px;
136
- border-radius: 4px;
137
- margin: 0;
138
- width: 100%;
139
- overflow-x: auto;
140
- max-height: 60px;
141
- font-size: 12px;
142
- line-height: 1.5;
143
- }
144
- </style>
@@ -1,388 +0,0 @@
1
- <template>
2
- <div class="request-form">
3
- <a-form layout="vertical">
4
- <!-- 请求部分 -->
5
- <a-card title="URL配置" class="form-section" size="small">
6
- <div class="flex flex-col gap-1">
7
- <div class="flex flex-row gap-1">
8
- <a-input v-model:value="url" placeholder="基础URL" size="small" />
9
- <a-button v-if="canParseUrl" type="primary" size="small" @click="parseUrl"
10
- >拆解</a-button
11
- >
12
- </div>
13
- <div class="flex flex-row gap-1">
14
- <a-select v-model:value="method" class="w-40" size="small">
15
- <a-select-option value="GET">GET</a-select-option>
16
- <a-select-option value="POST">POST</a-select-option>
17
- <a-select-option value="PUT">PUT</a-select-option>
18
- <a-select-option value="DELETE">DELETE</a-select-option>
19
- <a-select-option value="OPTIONS">OPTIONS</a-select-option>
20
- </a-select>
21
- <a-input v-model:value="path" placeholder="路径" size="small" />
22
- </div>
23
- </div>
24
-
25
- <!-- URL参数拼接部分 -->
26
- <div class="mt-1">
27
- <KeyValueInput
28
- v-model="params"
29
- add-button-text="添加参数"
30
- :show-preview="true"
31
- :preview-text="url + path + queryString"
32
- />
33
- </div>
34
- </a-card>
35
-
36
- <!-- 认证部分 -->
37
- <a-card title="认证方案" class="form-section" size="small">
38
- <a-radio-group v-model:value="auth" button-style="solid" size="small">
39
- <a-radio-button value="none">无认证</a-radio-button>
40
- <a-radio-button value="Basic">Basic认证</a-radio-button>
41
- <a-radio-button value="Bearer">Bearer认证</a-radio-button>
42
- </a-radio-group>
43
-
44
- <template v-if="auth === 'Basic'">
45
- <div class="flex flex-col gap-1 mt-2">
46
- <div class="flex flex-row gap-1 h-6">
47
- <p class="m-0 w-16">用户名</p>
48
- <a-input v-model:value="httpUser" size="small" />
49
- </div>
50
- <div class="flex flex-row gap-1">
51
- <p class="m-0 w-16">密码</p>
52
- <a-input-password v-model:value="httpPassword" size="small" />
53
- </div>
54
- </div>
55
- </template>
56
- <template v-if="auth === 'Bearer'">
57
- <div class="flex flex-col gap-1 mt-1">
58
- <div class="flex flex-row gap-1 items-center">
59
- <p class="m-0 w-16 h-full">Token</p>
60
- <a-textarea
61
- v-model:value="httpToken"
62
- size="small"
63
- class="mt-1"
64
- rows="4"
65
- placeholder="请输入Token"
66
- />
67
- </div>
68
- </div>
69
- </template>
70
- </a-card>
71
-
72
- <!-- 请求头部分 -->
73
- <a-card title="请求头" class="form-section" size="small">
74
- <KeyValueInput v-model="headers" add-button-text="添加请求头" :show-preview="false" />
75
- </a-card>
76
-
77
- <!-- 请求体部分 -->
78
- <a-card
79
- v-if="method === 'POST' || method === 'PUT'"
80
- title="请求体"
81
- class="form-section"
82
- size="small"
83
- >
84
- <a-radio-group v-model:value="contentType" button-style="solid" size="small" class="mb-1">
85
- <a-radio-button value="application/json">JSON</a-radio-button>
86
- <a-radio-button value="multipart/form-data">Form Data</a-radio-button>
87
- <a-radio-button value="text/plain">Raw</a-radio-button>
88
- </a-radio-group>
89
-
90
- <!-- JSON 输入 -->
91
- <template v-if="contentType === 'application/json'">
92
- <a-textarea
93
- v-model:value="jsonBody"
94
- :rows="6"
95
- placeholder="请输入 JSON 数据"
96
- @input="handleJsonInput"
97
- />
98
- <a-alert
99
- v-if="jsonError"
100
- type="error"
101
- :message="jsonError"
102
- banner
103
- style="margin-bottom: 8px"
104
- />
105
- </template>
106
-
107
- <!-- Form Data 输入 -->
108
- <template v-if="contentType === 'multipart/form-data'">
109
- <KeyValueInput
110
- v-model="bodyParams"
111
- add-button-text="添加表单字段"
112
- :show-preview="false"
113
- />
114
- </template>
115
-
116
- <!-- Raw 输入 -->
117
- <template v-if="contentType === 'text/plain'">
118
- <a-textarea v-model:value="rawBody" :rows="6" placeholder="请输入原始数据" />
119
- </template>
120
-
121
- <!-- 预览部分 -->
122
- <pre>{{ requestBodyPreview }}</pre>
123
- </a-card>
124
- </a-form>
125
- </div>
126
- </template>
127
-
128
- <script setup lang="ts">
129
- import { ref, computed, watch } from "vue";
130
- import KeyValueInput from "./KeyValueInput.vue";
131
- import type { RequestSchema, KeyValuePair } from "../types/request";
132
- import { defaultRequestSchema } from "../types/request";
133
-
134
- interface Props {
135
- modelValue?: RequestSchema;
136
- }
137
-
138
- const props = withDefaults(defineProps<Props>(), {
139
- modelValue: () => defaultRequestSchema,
140
- });
141
-
142
- const emit = defineEmits<{
143
- (e: "update:modelValue", value: RequestSchema): void;
144
- }>();
145
-
146
- // 响应式状态
147
- const method = ref(props.modelValue.method);
148
- const url = ref(props.modelValue.url);
149
- const auth = ref(props.modelValue.auth.type);
150
- const path = ref(props.modelValue.path);
151
- const httpUser = ref(props.modelValue.auth.username || "");
152
- const httpPassword = ref(props.modelValue.auth.password || "");
153
- const httpToken = ref(props.modelValue.auth.token || "");
154
- const params = ref<KeyValuePair[]>(props.modelValue.params);
155
- const headers = ref<KeyValuePair[]>(props.modelValue.headers);
156
- const bodyParams = ref<KeyValuePair[]>(props.modelValue.body.formData || []);
157
- const contentType = ref(props.modelValue.body.type);
158
- const jsonBody = ref(props.modelValue.body.json || "");
159
- const rawBody = ref(props.modelValue.body.raw || "");
160
- const jsonError = ref("");
161
-
162
- // 创建一个防抖函数
163
- function debounce<T extends (...args: any[]) => any>(fn: T, delay: number) {
164
- let timeoutId: ReturnType<typeof setTimeout>;
165
- return function (this: any, ...args: Parameters<T>) {
166
- clearTimeout(timeoutId);
167
- timeoutId = setTimeout(() => fn.apply(this, args), delay);
168
- };
169
- }
170
-
171
- // 创建一个生成schema的函数
172
- const generateSchema = () => {
173
- return {
174
- method: method.value,
175
- url: url.value,
176
- path: path.value,
177
- auth: {
178
- type: auth.value,
179
- ...(auth.value === "Basic"
180
- ? {
181
- username: httpUser.value,
182
- password: httpPassword.value,
183
- }
184
- : auth.value === "Bearer"
185
- ? {
186
- token: httpToken.value,
187
- }
188
- : {}),
189
- },
190
- params: params.value,
191
- headers: headers.value,
192
- body: {
193
- type: contentType.value,
194
- ...(contentType.value === "application/json"
195
- ? { json: jsonBody.value }
196
- : contentType.value === "multipart/form-data"
197
- ? { formData: bodyParams.value }
198
- : { raw: rawBody.value }),
199
- },
200
- };
201
- };
202
-
203
- // 创建一个更新schema的函数
204
- const updateSchema = debounce(() => {
205
- emit("update:modelValue", generateSchema());
206
- }, 100);
207
-
208
- // 监听内部状态变化
209
- watch(
210
- [
211
- method,
212
- url,
213
- path,
214
- auth,
215
- httpUser,
216
- httpPassword,
217
- httpToken,
218
- params,
219
- headers,
220
- contentType,
221
- bodyParams,
222
- jsonBody,
223
- rawBody,
224
- ],
225
- () => {
226
- updateSchema();
227
- },
228
- { deep: true }
229
- );
230
-
231
- // 监听外部变化
232
- watch(
233
- () => props.modelValue,
234
- (newValue) => {
235
- const currentSchema = generateSchema();
236
- // 防止循环更新
237
- if (JSON.stringify(newValue) === JSON.stringify(currentSchema)) {
238
- return;
239
- }
240
-
241
- method.value = newValue.method;
242
- url.value = newValue.url;
243
- path.value = newValue.path;
244
- auth.value = newValue.auth.type;
245
- httpUser.value = newValue.auth.username || "";
246
- httpPassword.value = newValue.auth.password || "";
247
- httpToken.value = newValue.auth.token || "";
248
- params.value = newValue.params;
249
- headers.value = newValue.headers;
250
- contentType.value = newValue.body.type;
251
- bodyParams.value = newValue.body.formData || [];
252
- jsonBody.value = newValue.body.json || "";
253
- rawBody.value = newValue.body.raw || "";
254
- },
255
- { deep: true }
256
- );
257
-
258
- // 计算属性
259
- const queryString = computed(() => {
260
- const result = params.value
261
- .filter((p) => !!p.key)
262
- .map((p) => p.key + "=" + encodeURIComponent(p.value))
263
- .join("&");
264
- return result === "" ? "" : "?" + result;
265
- });
266
-
267
- const requestBodyPreview = computed(() => {
268
- switch (contentType.value) {
269
- case "application/json":
270
- try {
271
- if (!jsonBody.value) {
272
- return "-空-";
273
- }
274
- return JSON.stringify(JSON.parse(jsonBody.value), null, 2);
275
- } catch (e) {
276
- return "Invalid JSON";
277
- }
278
- case "multipart/form-data":
279
- const boundary = "----WebKitFormBoundaryPreview";
280
- const formData = bodyParams.value
281
- .filter((p) => !!p.key)
282
- .map(
283
- (p) =>
284
- `--${boundary}\r\nContent-Disposition: form-data; name="${p.key}"\r\n\r\n${p.value}\r\n`
285
- )
286
- .join("");
287
- return formData + `--${boundary}--\r\n`;
288
- case "text/plain":
289
- return rawBody.value || "-空-";
290
- default:
291
- return "-空-";
292
- }
293
- });
294
-
295
- const handleJsonInput = () => {
296
- try {
297
- if (jsonBody.value) {
298
- JSON.parse(jsonBody.value);
299
- jsonError.value = "";
300
- }
301
- } catch (e) {
302
- jsonError.value = "Invalid JSON format";
303
- }
304
- };
305
-
306
- const parseUrl = () => {
307
- try {
308
- const fullUrl = url.value;
309
- const urlObj = new URL(fullUrl);
310
-
311
- // 设置基础URL
312
- url.value = `${urlObj.protocol}//${urlObj.host}`;
313
-
314
- // 设置路径
315
- path.value = urlObj.pathname;
316
-
317
- // 解析查询参数
318
- const searchParams = new URLSearchParams(urlObj.search);
319
- const newParams: KeyValuePair[] = [];
320
- searchParams.forEach((value, key) => {
321
- newParams.push({ key, value });
322
- });
323
- params.value = newParams;
324
- } catch (error) {
325
- // 如果URL解析失败,显示错误提示
326
- console.error("URL解析失败:", error);
327
- }
328
- };
329
-
330
- const canParseUrl = computed(() => {
331
- if (!url.value) return false;
332
-
333
- try {
334
- const fullUrl = url.value;
335
- const urlObj = new URL(fullUrl);
336
-
337
- // 检查是否有查询参数或路径
338
- return urlObj.search !== "" || urlObj.pathname !== "/";
339
- } catch {
340
- return false;
341
- }
342
- });
343
- </script>
344
-
345
- <style scoped>
346
- .request-form {
347
- margin: 0;
348
- padding: 0px;
349
- }
350
-
351
- .request-form * {
352
- font-size: 0.9rem;
353
- }
354
-
355
- .form-section {
356
- margin-bottom: 8px;
357
- width: 100%;
358
- }
359
-
360
- :deep(.ant-card-head) {
361
- background-color: #fafafa;
362
- }
363
-
364
- :deep(.ant-card-head-title) {
365
- font-weight: 500;
366
- padding: 4px 0;
367
- }
368
-
369
- :deep(.ant-card-body) {
370
- padding: 8px 12px;
371
- }
372
-
373
- :deep(.ant-space) {
374
- width: 100%;
375
- display: flex;
376
- gap: 8px;
377
- align-items: center;
378
- }
379
-
380
- pre {
381
- background-color: #f5f5f5;
382
- padding: 8px;
383
- border-radius: 4px;
384
- margin: 0;
385
- width: 100%;
386
- overflow-x: auto;
387
- }
388
- </style>
@@ -1,144 +0,0 @@
1
- <template>
2
- <a-card title="响应" class="form-section" size="small">
3
- <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px">
4
- <a-radio-group v-model:value="requestMethod" button-style="solid" size="small">
5
- <a-radio-button value="xhr">XMLHttpRequest</a-radio-button>
6
- <a-radio-button value="fetch">Fetch</a-radio-button>
7
- </a-radio-group>
8
- <a-button type="primary" @click="sendRequest" size="small">发送</a-button>
9
- </div>
10
-
11
- <a-alert
12
- v-if="errorMessage"
13
- :message="errorMessage"
14
- type="error"
15
- show-icon
16
- style="margin-bottom: 8px"
17
- />
18
-
19
- <div class="flex flex-col gap-2">
20
- <div class="text-sm font-bold">基本信息</div>
21
- <div class="flex flex-col gap-1">
22
- <div>
23
- <span>状态码:</span>
24
- <a-tag :color="getStatusColor(response.status)">{{ response.status }}</a-tag>
25
- </div>
26
- <div>
27
- <span>耗时:</span>
28
- <span>{{ response.timing ? `${response.timing}ms` : "-" }}</span>
29
- </div>
30
- </div>
31
- <div class="text-sm font-bold">响应头</div>
32
- <template v-if="Object.keys(response.headers).length > 0">
33
- <table class="border border-solid border-gray-300 w-full">
34
- <tbody>
35
- <tr v-for="[key, value] in Object.entries(response.headers)" :key="key">
36
- <td class="border border-gray-300">{{ key }}</td>
37
- <td class="border border-gray-300">{{ value }}</td>
38
- </tr>
39
- </tbody>
40
- </table>
41
- </template>
42
- <p v-else>无响应头</p>
43
- <div class="text-sm font-bold">响应体</div>
44
- <a-textarea
45
- v-model:value="response.body"
46
- :rows="5"
47
- readonly
48
- style="width: 100%"
49
- size="small"
50
- />
51
- </div>
52
- </a-card>
53
- </template>
54
-
55
- <script setup lang="ts">
56
- import { ref } from "vue";
57
- import type { RequestSchema, ResponseData } from "../types/request";
58
- import { executeRequest, type RequestMethod } from "../utils/request";
59
-
60
- interface Props {
61
- modelValue: RequestSchema;
62
- }
63
-
64
- const props = defineProps<Props>();
65
-
66
- const requestMethod = ref<RequestMethod>("xhr");
67
- const response = ref<ResponseData>({
68
- status: "",
69
- headers: {},
70
- body: "",
71
- timing: 0,
72
- });
73
- const errorMessage = ref<string>("");
74
-
75
- const getStatusColor = (status: string | number): string => {
76
- const statusNum = Number(status);
77
- if (statusNum >= 200 && statusNum < 300) return "success";
78
- if (statusNum >= 300 && statusNum < 400) return "warning";
79
- if (statusNum >= 400 && statusNum < 500) return "error";
80
- if (statusNum >= 500) return "error";
81
- return "default";
82
- };
83
-
84
- const getErrorMessage = (error: unknown): string => {
85
- if (error instanceof Error) {
86
- return error.message;
87
- }
88
-
89
- return "请求失败";
90
- };
91
-
92
- const sendRequest = async () => {
93
- errorMessage.value = "";
94
- const startTime = Date.now();
95
- try {
96
- response.value = await executeRequest(props.modelValue, requestMethod.value);
97
- response.value.timing = Date.now() - startTime;
98
- } catch (error) {
99
- errorMessage.value = getErrorMessage(error);
100
- response.value = {
101
- status: "Error",
102
- headers: {},
103
- body: error instanceof Error ? error.message : "Request failed",
104
- timing: Date.now() - startTime,
105
- };
106
- }
107
- };
108
- </script>
109
-
110
- <style scoped>
111
- .form-section {
112
- margin-bottom: 8px;
113
- }
114
-
115
- .response-section {
116
- margin-bottom: 16px;
117
- }
118
-
119
- .section-title {
120
- font-size: 14px;
121
- font-weight: 500;
122
- color: #1f1f1f;
123
- margin-bottom: 8px;
124
- padding-left: 4px;
125
- border-left: 3px solid #1890ff;
126
- }
127
-
128
- :deep(.ant-card-head) {
129
- background-color: #fafafa;
130
- }
131
-
132
- :deep(.ant-card-head-title) {
133
- font-weight: 500;
134
- padding: 4px 0;
135
- }
136
-
137
- :deep(.ant-card-body) {
138
- padding: 8px 12px;
139
- }
140
-
141
- :deep(.ant-descriptions-item-label) {
142
- font-weight: 500;
143
- }
144
- </style>
package/lib/index.ts DELETED
@@ -1,19 +0,0 @@
1
- import RequestForm from './components/RequestForm.vue';
2
- import ResponseSection from './components/ResponseSection.vue';
3
- import KeyValueInput from './components/KeyValueInput.vue';
4
- import type { RequestSchema, KeyValuePair, ResponseData } from './types/request';
5
- import { defaultRequestSchema } from './types/request';
6
- import { executeRequest } from './utils/request';
7
-
8
- export {
9
- RequestForm,
10
- ResponseSection,
11
- KeyValueInput,
12
- type RequestSchema,
13
- type KeyValuePair,
14
- type ResponseData,
15
- defaultRequestSchema,
16
- executeRequest
17
- };
18
-
19
- export default RequestForm;
@@ -1,55 +0,0 @@
1
- // 基础键值对类型
2
- export interface KeyValuePair {
3
- key: string;
4
- value: string;
5
- }
6
-
7
- // 认证相关类型
8
- export interface AuthConfig {
9
- type: 'none' | 'Basic' | 'Bearer';
10
- username?: string;
11
- password?: string;
12
- token?: string;
13
- }
14
-
15
- // 请求体类型
16
- export interface RequestBody {
17
- type: 'application/json' | 'multipart/form-data' | 'text/plain';
18
- json?: string;
19
- formData?: KeyValuePair[];
20
- raw?: string;
21
- }
22
-
23
- // 响应类型
24
- export interface ResponseData {
25
- status: string;
26
- headers: Record<string, string>;
27
- body: string;
28
- timing?: number;
29
- }
30
-
31
- // 完整的请求Schema类型
32
- export interface RequestSchema {
33
- method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'OPTIONS';
34
- url: string;
35
- path: string;
36
- auth: AuthConfig;
37
- params: KeyValuePair[];
38
- headers: KeyValuePair[];
39
- body: RequestBody;
40
- }
41
-
42
- // 默认的请求Schema
43
- export const defaultRequestSchema: RequestSchema = {
44
- method: 'GET',
45
- url: 'https://yesno.wtf',
46
- path: '/api',
47
- auth: {
48
- type: 'none'
49
- },
50
- params: [],
51
- headers: [],
52
- body: {
53
- type: 'application/json'
54
- },
55
- };
@@ -1,194 +0,0 @@
1
- import type { RequestSchema, ResponseData } from '../types/request';
2
-
3
- export type RequestMethod = 'fetch' | 'xhr';
4
-
5
- export async function executeRequest(schema: RequestSchema, method: RequestMethod = 'xhr'): Promise<ResponseData> {
6
- if (method === 'fetch') {
7
- return executeFetchRequest(schema);
8
- } else {
9
- return executeXHRRequest(schema);
10
- }
11
- }
12
-
13
- async function executeFetchRequest(schema: RequestSchema): Promise<ResponseData> {
14
- // Build URL with query parameters
15
- const queryString = schema.params
16
- .filter(p => !!p.key)
17
- .map(p => `${p.key}=${encodeURIComponent(p.value)}`)
18
- .join('&');
19
- const fullUrl = `${schema.url}${schema.path}${queryString ? '?' + queryString : ''}`;
20
-
21
- // Prepare headers
22
- const headers = new Headers();
23
- schema.headers.forEach(header => {
24
- if (header.key) {
25
- headers.append(header.key, header.value);
26
- }
27
- });
28
-
29
- // Prepare request options
30
- const options: RequestInit = {
31
- method: schema.method,
32
- headers,
33
- credentials: 'omit'
34
- };
35
-
36
- // Handle authentication
37
- if (schema.auth.type === 'Basic' && schema.auth.username && schema.auth.password) {
38
- const auth = btoa(`${schema.auth.username}:${schema.auth.password}`);
39
- headers.append('Authorization', `Basic ${auth}`);
40
- } else if (schema.auth.type === 'Bearer' && schema.auth.token) {
41
- headers.append('Authorization', `Bearer ${schema.auth.token}`);
42
- }
43
-
44
- // Handle request body for POST/PUT methods
45
- if (schema.method === 'POST' || schema.method === 'PUT') {
46
- switch (schema.body.type) {
47
- case 'application/json':
48
- options.body = schema.body.json || '';
49
- headers.set('Content-Type', 'application/json; charset=utf-8');
50
- break;
51
- case 'multipart/form-data':
52
- const formData = new FormData();
53
- schema.body.formData?.forEach(param => {
54
- if (param.key) {
55
- formData.append(param.key, param.value);
56
- }
57
- });
58
- options.body = formData;
59
- break;
60
- case 'text/plain':
61
- options.body = schema.body.raw || '';
62
- headers.set('Content-Type', 'text/plain; charset=utf-8');
63
- break;
64
- }
65
- }
66
-
67
- try {
68
- const response = await fetch(fullUrl, options);
69
- const headers: Record<string, string> = {};
70
- response.headers.forEach((value, key) => {
71
- headers[key.toLowerCase()] = value;
72
- });
73
-
74
- let body = '';
75
- const contentType = headers['content-type'] || '';
76
- if (contentType.startsWith('application/json')) {
77
- try {
78
- const json = await response.json();
79
- body = JSON.stringify(json, null, 2);
80
- } catch (e) {
81
- body = await response.text();
82
- }
83
- } else {
84
- body = await response.text();
85
- }
86
-
87
- return {
88
- status: response.status.toString(),
89
- headers,
90
- body
91
- };
92
- } catch (error) {
93
- throw new Error('Request failed');
94
- }
95
- }
96
-
97
- async function executeXHRRequest(schema: RequestSchema): Promise<ResponseData> {
98
- return new Promise((resolve, reject) => {
99
- const xhr = new XMLHttpRequest();
100
- const user = schema.auth.type === 'Basic' ? schema.auth.username : null;
101
- const pswd = schema.auth.type === 'Basic' ? schema.auth.password : null;
102
-
103
- // Build URL with query parameters
104
- const queryString = schema.params
105
- .filter(p => !!p.key)
106
- .map(p => `${p.key}=${encodeURIComponent(p.value)}`)
107
- .join('&');
108
- const fullUrl = `${schema.url}${schema.path}${queryString ? '?' + queryString : ''}`;
109
-
110
- xhr.open(schema.method, fullUrl, true, user, pswd);
111
-
112
- // Add headers
113
- schema.headers.forEach(header => {
114
- if (header.key) {
115
- xhr.setRequestHeader(header.key, header.value);
116
- }
117
- });
118
-
119
- // Add Bearer token if present
120
- if (schema.auth.type === 'Bearer' && schema.auth.token) {
121
- xhr.setRequestHeader('Authorization', `Bearer ${schema.auth.token}`);
122
- }
123
-
124
- // Handle request body for POST/PUT methods
125
- if (schema.method === 'POST' || schema.method === 'PUT') {
126
- let requestBody = '';
127
-
128
- switch (schema.body.type) {
129
- case 'application/json':
130
- requestBody = schema.body.json || '';
131
- xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
132
- break;
133
- case 'multipart/form-data':
134
- const formData = new FormData();
135
- schema.body.formData?.forEach(param => {
136
- if (param.key) {
137
- formData.append(param.key, param.value);
138
- }
139
- });
140
- requestBody = formData as any;
141
- // Let browser handle multipart/form-data Content-Type and boundary
142
- break;
143
- case 'text/plain':
144
- requestBody = schema.body.raw || '';
145
- xhr.setRequestHeader('Content-Type', 'text/plain; charset=utf-8');
146
- break;
147
- }
148
-
149
- xhr.send(requestBody);
150
- } else {
151
- xhr.send();
152
- }
153
-
154
- xhr.onload = () => {
155
- const headers = parseHeaders(xhr);
156
- const response: ResponseData = {
157
- status: xhr.status.toString(),
158
- headers,
159
- body: ''
160
- };
161
-
162
- if ((headers['content-type'] || '').startsWith('application/json')) {
163
- try {
164
- response.body = JSON.stringify(JSON.parse(xhr.responseText), null, 2);
165
- } catch (e) {
166
- response.body = xhr.responseText;
167
- }
168
- } else {
169
- response.body = xhr.responseText;
170
- }
171
-
172
- resolve(response);
173
- };
174
-
175
- xhr.onerror = () => {
176
- reject(new Error('Request failed'));
177
- };
178
- });
179
- }
180
-
181
- function parseHeaders(xhr: XMLHttpRequest): Record<string, string> {
182
- const headers = xhr
183
- .getAllResponseHeaders()
184
- .trim()
185
- .split(/[\r\n]+/);
186
- const headerMap: Record<string, string> = {};
187
- headers.forEach(function (line) {
188
- const parts = line.split(': ');
189
- const header = parts.shift()?.toLowerCase() || '';
190
- const value = parts.join(': ');
191
- headerMap[header] = value;
192
- });
193
- return headerMap;
194
- }