<script setup>
import {ref, reactive} from 'vue';
import axios from 'axios';
import Message from '@/utils/message';
import JsonEditor from '@/components/JsonEditor.vue';
const method = ref('GET');
const url = ref('');
const activeTab = ref('params');
const requestData = reactive({
params: [{ key: '', value: '' }],
headers: [
{ key: 'Content-Type', value: 'application/json' },
],
body: ''
});
const response = reactive({
status: '',
time: '',
size: '',
data: null,
loading: false
});
const addParam = () => {
requestData.params.push({ key: '', value: '' });
};
const removeParam = (index) => {
requestData.params.splice(index, 1);
};
const addHeader = () => {
requestData.headers.push({ key: '', value: '' });
};
const removeHeader = (index) => {
requestData.headers.splice(index, 1);
};
const switchTab = (tab) => {
activeTab.value = tab;
};
const jsonEditor = ref();
const hasJsonError = ref(false);
const formatJsonBody = () => {
jsonEditor.value?.formatJson();
};
const handleJsonError = (error) => {
hasJsonError.value = !!error;
};
const sendRequest = async () => {
if (!url.value) {
Message.warning('请输入请求URL');
return;
}
if (hasJsonError.value) {
Message.error('请求体 JSON 格式错误');
return;
}
response.loading = true;
try {
const queryParams = {};
const headers = {};
requestData.params.forEach(param => {
if (param.key && param.value) {
queryParams[param.key] = param.value;
}
});
requestData.headers.forEach(header => {
if (header.key && header.value) {
headers[header.key] = header.value;
}
});
const requestBody = activeTab.value === 'body' ? requestData.body : null;
const result = await axios.post('http://localhost:8080/api/proxy', {
url: url.value,
method: method.value,
headers: headers,
queryParams: queryParams,
body: requestBody
});
response.status = `${result.data.status} ${result.data.status === 200 ? 'OK' : ''}`;
response.time = `${result.data.responseTime}ms`;
response.size = result.data.contentLength;
response.data = result.data.data;
Message.success('请求成功');
} catch (error) {
Message.error(error.message || '请求失败');
response.status = '500 Error';
response.data = error.message;
} finally {
response.loading = false;
}
};
const formatResponseData = (data) => {
if (typeof data === 'string') {
try {
return JSON.stringify(JSON.parse(data), null, 2);
} catch {
return data.replace(/^"|"$/g, '');
}
}
return JSON.stringify(data, null, 2);
};
</script>
<template>
<div class="bg-gray-50" style="width: 100%; height: 100%">
<div class="w-[1200px] mx-auto">
<nav class="h-16 bg-white shadow flex items-center justify-between px-8">
<div class="flex items-center space-x-2">
<span class="text-2xl font-['Pacifico'] text-primary">logo
</span>
<span class="text-lg font-medium">API工具
</span>
</div>
<button class="w-10 h-10 rounded-button flex items-center justify-center hover:bg-gray-100 transition-colors">
<i class="fas fa-sun text-gray-600"></i>
</button>
</nav>
<main class="py-12">
<div class="mx-auto">
<div class="bg-white rounded-lg shadow p-8">
<h2 class="text-lg font-semibold mb-4">接口测试
</h2>
<div class="space-y-4">
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">请求方法
</label>
<div class="relative">
<select v-model="method"
class="block w-full pl-3 pr-10 py-2 text-base border border-gray-300 focus:outline-none focus:ring-primary focus:border-primary rounded-button">
<option>GET
</option>
<option>POST
</option>
<option>PUT
</option>
<option>DELETE
</option>
</select>
</div>
</div>
<div class="md:col-span-3">
<label class="block text-sm font-medium text-gray-700 mb-1">请求URL
</label>
<div class="flex">
<input type="text" v-model="url"
class="flex-1 min-w-0 block w-full px-3 py-2 rounded-l-button border border-gray-300 focus:outline-none focus:ring-primary focus:border-primary">
<button
class="bg-primary hover:bg-blue-600 text-white px-4 py-2 rounded-r-button text-sm font-medium" @click="sendRequest">发送
</button>
</div>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">请求参数
</label>
<div class="overflow-hidden border border-gray-300 rounded-button">
<div class="bg-gray-50 px-4 py-2 border-b border-gray-300">
<div class="flex items-center space-x-4">
<button class="text-sm font-medium text-gray-500" :class="activeTab === 'params' ? ' text-primary ' : ''" @click="switchTab('params')">Query Params
</button>
<button class="text-sm font-medium text-gray-500" :class="activeTab === 'headers' ? ' text-primary ' : ''" @click="switchTab('headers')">Headers
</button>
<button class="text-sm font-medium text-gray-500" :class="activeTab === 'body' ? ' text-primary ' : ''" @click="switchTab('body')">Body
</button>
</div>
</div>
<div class="bg-white">
<div class="space-y-3 p-4" v-show="activeTab === 'params'">
<template v-for="(param, index) in requestData.params" :key="index">
<div class="grid grid-cols-12 gap-4 items-center">
<div class="col-span-3">
<input type="text" v-model="param.key" placeholder="参数名"
class="block w-full px-3 py-2 border border-gray-300 rounded-button focus:outline-none focus:ring-primary focus:border-primary">
</div>
<div class="col-span-8">
<input type="text" v-model="param.value" placeholder="参数值"
class="block w-full px-3 py-2 border border-gray-300 rounded-button focus:outline-none focus:ring-primary focus:border-primary">
</div>
<div class="col-span-1">
<button class="text-gray-500 hover:text-gray-700" @click="removeParam(index)">
<i class="fas fa-trash fa-icon"></i>
</button>
</div>
</div>
</template>
<button class="text-sm text-primary hover:text-blue-600 flex items-center space-x-1">
<i class="fas fa-plus fa-icon"></i>
<span @click="addParam">添加参数
</span>
</button>
</div>
<div class="space-y-3 p-4" v-show="activeTab === 'headers'">
<template v-for="(header, index) in requestData.headers" :key="index">
<div class="grid grid-cols-12 gap-4 items-center">
<div class="col-span-3">
<input type="text" v-model="header.key" placeholder="参数名"
class="block w-full px-3 py-2 border border-gray-300 rounded-button focus:outline-none focus:ring-primary focus:border-primary">
</div>
<div class="col-span-8">
<input type="text" v-model="header.value" placeholder="参数值"
class="block w-full px-3 py-2 border border-gray-300 rounded-button focus:outline-none focus:ring-primary focus:border-primary">
</div>
<div class="col-span-1">
<button class="text-gray-500 hover:text-gray-700" @click="removeHeader(index)">
<i class="fas fa-trash fa-icon"></i>
</button>
</div>
</div>
</template>
<button class="text-sm text-primary hover:text-blue-600 flex items-center space-x-1">
<i class="fas fa-plus fa-icon"></i>
<span @click="addHeader">添加请求头
</span>
</button>
</div>