简单接口工具(ApiCraft-Web)

时间:2025-04-18 09:14:32
<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'); // 请求URL const url = ref(''); // 当前选中的参数类型标签 const activeTab = ref('params'); // params, headers, body // 请求参数 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 { // 尝试解析字符串为 JSON return JSON.stringify(JSON.parse(data), null, 2); } catch { // 如果不是 JSON 字符串,直接返回原始字符串,去掉多余的引号 return data.replace(/^"|"$/g, ''); } } // 如果是对象,格式化为 JSON 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>