feat: initial commit for HookPilot

This commit is contained in:
吴璨
2026-09-27 20:09:10 +08:00
commit e998d4defc
73 changed files with 10782 additions and 0 deletions
+87
View File
@@ -0,0 +1,87 @@
<script setup lang="ts">
import { computed } from 'vue';
import type { HookDetail, HookForm } from '../../api/types';
import { toast } from '../../composables/toast';
import { copyText, formatTime, webhookUrl } from '../../utils/format';
const props = defineProps<{ form: HookForm; hook: HookDetail | null; groups: string[] }>();
const emit = defineEmits<{ regenerateToken: [] }>();
const url = computed(() => webhookUrl(props.form.slug));
const slugChanged = computed(() => props.hook !== null && props.hook.slug !== props.form.slug);
const hasBodyParams = computed(() => props.form.params.some((p) => p.source === 'json' || p.source === 'form'));
async function copyUrl() {
if (await copyText(url.value)) toast.success('调用地址已复制');
}
</script>
<template>
<div class="form-grid">
<div class="field">
<label class="required">接口名称</label>
<input v-model="form.name" maxlength="100" placeholder="如:更新家庭 IP" />
</div>
<div class="field">
<label>分组</label>
<input v-model="form.group" list="hook-groups" maxlength="50" placeholder="可选,用于在列表中归类" />
<datalist id="hook-groups">
<option v-for="g in groups" :key="g" :value="g" />
</datalist>
</div>
<div class="field">
<label class="required">接口标识</label>
<input v-model="form.slug" maxlength="64" placeholder="如:home-ip" class="mono" />
<div class="hint">小写字母、数字、下划线和连字符,以字母或数字开头。</div>
</div>
<div class="field">
<label class="required">请求方式</label>
<select v-model="form.method">
<option value="POST">POST</option>
<option value="GET">GET</option>
</select>
<div v-if="form.method === 'GET' && hasBodyParams" class="hint" style="color: var(--danger)">
GET 接口只能从 Query 或 Header 提取参数,请调整参数配置。
</div>
</div>
<div class="field full">
<label>调用地址</label>
<div class="row">
<div class="input-group" style="flex: 1">
<span class="addon">{{ form.method }}</span>
<input :value="url" readonly class="mono" />
</div>
<button class="btn" :disabled="!form.slug" @click="copyUrl">复制</button>
</div>
<div v-if="slugChanged" class="hint" style="color: var(--warning)">
修改标识后原地址 /hooks/{{ hook!.slug }} 将失效,调用方需同步更新。
</div>
</div>
<div class="field full">
<label>描述</label>
<textarea v-model="form.description" rows="3" maxlength="1000" placeholder="接口用途、调用方等说明" />
</div>
<div class="field full">
<label>接口 Token</label>
<template v-if="hook">
<div class="row">
<input :value="`hp_••••••••••••••••••••••••${hook.tokenHint}`" readonly class="mono" style="max-width: 360px" />
<button class="btn btn-danger-outline" @click="emit('regenerateToken')">重新生成</button>
</div>
<div class="hint">
Token 仅在创建或重新生成时完整显示一次。调用时通过 <code>Authorization: Bearer &lt;token&gt;</code> 或
<code>X-Hook-Token</code> 请求头传递。
</div>
</template>
<div v-else class="hint">创建接口后自动生成,届时只显示一次,请妥善保存。</div>
</div>
<div v-if="hook" class="field full">
<div class="small faint">创建于 {{ formatTime(hook.createdAt) }} · 最后修改 {{ formatTime(hook.updatedAt) }}</div>
</div>
</div>
</template>