vue-api-request-builder 0.1.0 → 0.2.1
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/dist/index.css +1 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.es.js +427 -302
- package/dist/index.umd.js +5 -5
- package/package.json +4 -4
- package/dist/vue-api-request-builder.css +0 -1
- package/lib/components/KeyValueInput.vue +0 -144
- package/lib/components/RequestForm.vue +0 -388
- package/lib/components/ResponseSection.vue +0 -144
- package/lib/index.ts +0 -17
- package/lib/types/request.ts +0 -55
- package/lib/utils/request.ts +0 -194
|
@@ -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,17 +0,0 @@
|
|
|
1
|
-
import RequestForm from './components/RequestForm.vue';
|
|
2
|
-
import KeyValueInput from './components/KeyValueInput.vue';
|
|
3
|
-
import type { RequestSchema, KeyValuePair, ResponseData } from './types/request';
|
|
4
|
-
import { defaultRequestSchema } from './types/request';
|
|
5
|
-
import { executeRequest } from './utils/request';
|
|
6
|
-
|
|
7
|
-
export {
|
|
8
|
-
RequestForm,
|
|
9
|
-
KeyValueInput,
|
|
10
|
-
type RequestSchema,
|
|
11
|
-
type KeyValuePair,
|
|
12
|
-
type ResponseData,
|
|
13
|
-
defaultRequestSchema,
|
|
14
|
-
executeRequest
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export default RequestForm;
|
package/lib/types/request.ts
DELETED
|
@@ -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
|
-
};
|
package/lib/utils/request.ts
DELETED
|
@@ -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
|
-
}
|