wechat-article / components /api /DebugModal.vue
Antigravity
Initialize wechat-article without git history and binary assets
70e483f
Raw
History Blame Contribute Delete
6.06 kB
<script setup lang="ts">
import type { FormError } from '#ui/types';
import CodeSegment from '~/components/api/CodeSegment.vue';
import { apis } from '~/config/public-apis';
interface Props {
initialSelected: string;
}
const props = defineProps<Props>();
const isOpen = ref(false);
const selectedApi = ref(apis[0]);
const payload: Ref<Record<string, any>> = ref({});
const host = window.location.protocol + '//' + window.location.host;
function onOpen() {
isOpen.value = true;
selectedApi.value = apis.find(api => api.name === props.initialSelected)!;
}
function apiChange() {
payload.value = {};
}
function isEmpty(key: string, obj: Record<string, any>): boolean {
if (!obj.hasOwnProperty(key)) {
return true;
}
let value = obj[key];
if (typeof value === 'string') {
value = value.trim();
}
return value === undefined || value === null || value === '';
}
function validate(state: Record<string, any>): FormError[] {
const errors: FormError[] = [];
selectedApi.value.params.forEach(param => {
if (param.required && isEmpty(param.name, state)) {
errors.push({ path: param.name, message: param.name + '不能为空' });
}
if (!isEmpty(param.name, state) && param.type === 'Int') {
if (Number.isNaN(parseInt(state[param.name]))) {
errors.push({ path: param.name, message: param.name + '格式不正确' });
} else if (parseInt(state[param.name]) < 0) {
errors.push({ path: param.name, message: param.name + '不得小于0' });
}
}
});
return errors;
}
const resp = ref<Record<string, any> | null | string>(null);
const hasResponse = computed(() => {
return typeof resp.value === 'string' || (typeof resp.value === 'object' && resp.value);
});
const btnLoading = ref(false);
function submit() {
const params = toRaw(payload.value);
let url = selectedApi.value.url;
if (selectedApi.value.method === 'GET') {
url += '?' + new URLSearchParams(params).toString();
}
btnLoading.value = true;
resp.value = null;
fetch(url, {
method: selectedApi.value.method,
})
.then(resp => {
if (resp.headers.get('content-type') === 'application/json') {
return resp.json();
} else {
return resp.text();
}
})
.then(data => {
resp.value = data;
})
.finally(() => {
btnLoading.value = false;
});
}
</script>
<template>
<div>
<UTooltip text="在线调试" :popper="{ placement: 'top' }">
<UButton color="blue" variant="ghost" square @click="onOpen" icon="i-lucide:bug-play"></UButton>
</UTooltip>
<USlideover v-model="isOpen" :ui="{ width: 'max-w-[800px]' }">
<UCard
class="flex flex-col flex-1"
:ui="{
body: { base: 'overflow-y-scroll h-[calc(100vh-72px)]' },
ring: '',
divide: 'divide-y divide-gray-100 dark:divide-gray-800',
}"
>
<template #header>
<h1 class="font-semibold text-2xl">在线接口调试工具</h1>
</template>
<div class="space-y-5">
<div class="flex items-center gap-3">
<span>选择接口: </span>
<USelectMenu
class="flex-1"
v-model="selectedApi"
:options="apis"
option-attribute="name"
@change="apiChange"
>
<template #label>
<span
:class="[
selectedApi.method === 'GET' ? 'bg-green-400' : 'bg-fuchsia-400',
'inline-block size-2 flex-shrink-0 rounded-full',
]"
aria-hidden="true"
/>
<span class="font-medium">{{ selectedApi.name }}</span>
</template>
<template #option="{ option: api }">
<div>
<p class="font-medium">
<span
:class="[
api.method === 'GET' ? 'bg-green-400' : 'bg-fuchsia-400',
'inline-block size-2 mr-2 flex-shrink-0 rounded-full',
]"
aria-hidden="true"
/>
<span>{{ api.name }}</span>
</p>
<p class="text-gray-400 font-mono">{{ api.description }}</p>
</div>
</template>
</USelectMenu>
</div>
<div class="space-y-5">
<div>
<p class="font-semibold mb-2">请求URL:</p>
<p class="font-mono border p-2 rounded-md">
<span class="text-gray-400">{{ host }}</span>
<span class="font-semibold">{{ selectedApi.url }}</span>
</p>
</div>
<div>
<p class="font-semibold mb-2">请求方式:</p>
<p class="font-mono border p-2 rounded-md">{{ selectedApi.method }}</p>
</div>
<div>
<p class="font-semibold mb-2">参数:</p>
<UForm :state="payload" :validate="validate" @submit="submit" class="space-y-3">
<UFormGroup
v-for="p in selectedApi.params"
:key="p.name"
:label="p.name"
:name="p.name"
:required="p.required"
>
<UInput
v-model="payload[p.name]"
:type="p.type === 'Int' ? 'number' : 'text'"
:placeholder="p.label + (p.remark ? ',' + p.remark : '')"
/>
</UFormGroup>
<UButton type="submit" color="black" class="px-5" :loading="btnLoading">提交</UButton>
</UForm>
</div>
</div>
<div v-if="hasResponse">
<h3 class="font-bold text-2xl">响应</h3>
<CodeSegment :code="resp!" :lang="typeof resp === 'object' ? 'json' : 'xml'" />
</div>
</div>
</UCard>
</USlideover>
</div>
</template>