feat: add desktop app release packaging

This commit is contained in:
lutc5
2026-04-29 18:45:25 +08:00
parent 74bbd8e6d2
commit 92c8735bfc
73 changed files with 8934 additions and 757 deletions

View File

@@ -0,0 +1,181 @@
<script setup>
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { ClearRequests, GetRequests } from '../../wailsjs/go/main/App.js'
import { ClipboardSetText, EventsOff, EventsOn } from '../../wailsjs/runtime'
const emit = defineEmits(['notice'])
const requests = ref([])
const selected = ref(null)
const query = ref('')
const activeStatus = ref('all')
const filtered = computed(() => {
const q = query.value.trim().toLowerCase()
return requests.value.filter((request) => {
const matchesQuery = !q || `${request.method} ${request.path} ${request.statusCode}`.toLowerCase().includes(q)
const code = Number(request.statusCode)
const matchesStatus =
activeStatus.value === 'all' ||
(activeStatus.value === 'ok' && code >= 200 && code < 300) ||
(activeStatus.value === 'err' && code >= 400) ||
(activeStatus.value === 'warn' && code >= 300 && code < 400)
return matchesQuery && matchesStatus
})
})
async function refresh() {
try {
requests.value = await GetRequests()
} catch (e) {
console.debug('Wails GetRequests unavailable in browser preview')
}
}
async function clear() {
try {
await ClearRequests()
} catch (e) {
console.debug('Wails ClearRequests unavailable in browser preview')
}
requests.value = []
selected.value = null
}
function statusClass(code) {
if (code >= 200 && code < 300) return 'ok'
if (code >= 400) return 'err'
return 'warn'
}
function selectRow(index) {
selected.value = selected.value === index ? null : index
}
async function writeClipboard(text) {
const value = text || ''
try {
await ClipboardSetText(value)
return true
} catch (e) {
await navigator.clipboard?.writeText(value)
return true
}
}
async function copyText(text, label) {
try {
await writeClipboard(text)
emit('notice', `已复制${label}`)
} catch (e) {
console.debug('Copy failed:', e)
emit('notice', `${label}复制失败`)
}
}
function safeEventsOn(name, handler) {
try {
EventsOn(name, handler)
} catch (e) {
console.debug('Wails runtime event unavailable:', name)
}
}
function safeEventsOff(name) {
try {
EventsOff(name)
} catch (e) {
console.debug('Wails runtime event unavailable:', name)
}
}
onMounted(() => {
refresh()
safeEventsOn('requests:updated', (data) => {
requests.value = data || []
})
})
onUnmounted(() => {
safeEventsOff('requests:updated')
})
</script>
<template>
<div class="page requests-page">
<div class="page-title">
<div>
<h1>请求流</h1>
<p>查看客户端调用 OpenAI / Anthropic 兼容接口的请求与响应</p>
</div>
<div class="toolbar">
<button class="secondary-button" type="button" @click="refresh">刷新</button>
<button class="danger-button" type="button" @click="clear">清空</button>
</div>
</div>
<section class="table-panel requests-panel">
<div class="table-toolbar">
<input v-model="query" class="search-input" type="search" placeholder="搜索路径、方法或状态码" />
<div class="segmented">
<button :class="{ active: activeStatus === 'all' }" type="button" @click="activeStatus = 'all'">全部</button>
<button :class="{ active: activeStatus === 'ok' }" type="button" @click="activeStatus = 'ok'">成功</button>
<button :class="{ active: activeStatus === 'warn' }" type="button" @click="activeStatus = 'warn'">跳转</button>
<button :class="{ active: activeStatus === 'err' }" type="button" @click="activeStatus = 'err'">错误</button>
</div>
</div>
<div v-if="filtered.length > 0" class="table-scroll hidden-scrollbar">
<table class="data-table">
<thead>
<tr>
<th>时间</th>
<th>方法</th>
<th>路径</th>
<th>状态</th>
<th>耗时</th>
</tr>
</thead>
<tbody>
<tr v-for="(request, index) in filtered" :key="index" @click="selectRow(index)">
<td>{{ request.time }}</td>
<td><span class="method-chip">{{ request.method }}</span></td>
<td>
<div class="cell-main">{{ request.path }}</div>
<div class="cell-sub">{{ request.reqBody ? '包含请求体' : '无请求体' }}</div>
</td>
<td><span class="status-chip" :class="statusClass(request.statusCode)">{{ request.statusCode }}</span></td>
<td>{{ request.duration }}</td>
</tr>
</tbody>
</table>
</div>
<div v-else class="empty-state">暂无匹配请求</div>
<div v-if="selected !== null && filtered[selected]" class="detail-panel hidden-scrollbar">
<div class="detail-section">
<div class="detail-toolbar">
<h3>请求内容</h3>
<div class="detail-actions">
<button type="button" class="ghost-button" @click="copyText(filtered[selected].reqBody, '请求内容')">
复制
</button>
</div>
</div>
<pre>{{ filtered[selected].reqBody || '空请求体' }}</pre>
</div>
<div class="detail-section">
<div class="detail-toolbar">
<h3>响应内容</h3>
<div class="detail-actions">
<button type="button" class="ghost-button" @click="copyText(filtered[selected].respBody, '响应内容')">
复制
</button>
</div>
</div>
<pre>{{ filtered[selected].respBody || '空响应体' }}</pre>
</div>
</div>
</section>
</div>
</template>