mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
feat(usage): 响应头 Tab 增加并排对比模式
泛化 RequestHeadersContent 组件支持任意 header 对, 响应头 Tab 复用同一组件实现客户端/提供商响应头 Diff 对比。
This commit is contained in:
@@ -465,8 +465,8 @@
|
|||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 请求头专用:对比模式 -->
|
<!-- 请求头/响应头:对比模式 -->
|
||||||
<template v-if="activeTab === 'request-headers' && hasProviderHeaders">
|
<template v-if="canCompare">
|
||||||
<button
|
<button
|
||||||
title="对比"
|
title="对比"
|
||||||
class="p-1 rounded transition-colors"
|
class="p-1 rounded transition-colors"
|
||||||
@@ -580,7 +580,26 @@
|
|||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="response-headers">
|
<TabsContent value="response-headers">
|
||||||
|
<RequestHeadersContent
|
||||||
|
v-if="viewMode === 'compare'"
|
||||||
|
:detail="detail"
|
||||||
|
:view-mode="viewMode"
|
||||||
|
:data-source="dataSource"
|
||||||
|
:current-header-data="currentResponseHeaderData"
|
||||||
|
:current-expand-depth="currentExpandDepth"
|
||||||
|
:has-provider-headers="hasProviderResponseHeaders"
|
||||||
|
:client-headers-with-diff="[]"
|
||||||
|
:provider-headers-with-diff="[]"
|
||||||
|
:header-stats="responseHeaderStats"
|
||||||
|
:is-dark="isDark"
|
||||||
|
:client-headers="detail.client_response_headers"
|
||||||
|
:provider-headers="detail.response_headers"
|
||||||
|
client-label="客户端响应头"
|
||||||
|
provider-label="提供商响应头"
|
||||||
|
empty-message="无响应头信息"
|
||||||
|
/>
|
||||||
<JsonContent
|
<JsonContent
|
||||||
|
v-else
|
||||||
:data="currentResponseHeaderData"
|
:data="currentResponseHeaderData"
|
||||||
:view-mode="viewMode"
|
:view-mode="viewMode"
|
||||||
:expand-depth="currentExpandDepth"
|
:expand-depth="currentExpandDepth"
|
||||||
@@ -703,7 +722,7 @@ const AUTO_REFRESH_INTERVAL_MS = 1000
|
|||||||
|
|
||||||
// 监听标签页切换
|
// 监听标签页切换
|
||||||
watch(activeTab, (newTab) => {
|
watch(activeTab, (newTab) => {
|
||||||
if (newTab !== 'request-headers' && viewMode.value === 'compare') {
|
if (!['request-headers', 'response-headers'].includes(newTab) && viewMode.value === 'compare') {
|
||||||
viewMode.value = 'formatted'
|
viewMode.value = 'formatted'
|
||||||
}
|
}
|
||||||
// 切换到不支持对话视图的 Tab 时,重置为 JSON 视图
|
// 切换到不支持对话视图的 Tab 时,重置为 JSON 视图
|
||||||
@@ -742,6 +761,36 @@ const hasProviderResponseHeaders = computed(() => {
|
|||||||
Object.keys(detail.value.client_response_headers).length > 0)
|
Object.keys(detail.value.client_response_headers).length > 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 当前 Tab 是否支持对比模式
|
||||||
|
const canCompare = computed(() => {
|
||||||
|
if (activeTab.value === 'request-headers') return hasProviderHeaders.value
|
||||||
|
if (activeTab.value === 'response-headers') return hasProviderResponseHeaders.value
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
// 响应头 diff 统计(用于对比模式标题栏)
|
||||||
|
const responseHeaderStats = computed(() => {
|
||||||
|
const counts = { added: 0, modified: 0, removed: 0, unchanged: 0 }
|
||||||
|
if (!detail.value?.client_response_headers || !detail.value?.response_headers) return counts
|
||||||
|
|
||||||
|
const clientHeaders = detail.value.client_response_headers as Record<string, unknown>
|
||||||
|
const providerHeaders = detail.value.response_headers as Record<string, unknown>
|
||||||
|
const clientKeys = new Set(Object.keys(clientHeaders))
|
||||||
|
const providerKeys = new Set(Object.keys(providerHeaders))
|
||||||
|
const allKeys = new Set([...clientKeys, ...providerKeys])
|
||||||
|
|
||||||
|
for (const key of allKeys) {
|
||||||
|
if (clientKeys.has(key) && providerKeys.has(key)) {
|
||||||
|
counts[clientHeaders[key] === providerHeaders[key] ? 'unchanged' : 'modified']++
|
||||||
|
} else if (clientKeys.has(key)) {
|
||||||
|
counts.removed++
|
||||||
|
} else {
|
||||||
|
counts.added++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return counts
|
||||||
|
})
|
||||||
|
|
||||||
// 是否显示客户端/提供商切换按钮
|
// 是否显示客户端/提供商切换按钮
|
||||||
const showDataSourceToggle = computed(() => {
|
const showDataSourceToggle = computed(() => {
|
||||||
if (activeTab.value === 'request-headers') return hasProviderHeaders.value
|
if (activeTab.value === 'request-headers') return hasProviderHeaders.value
|
||||||
@@ -751,16 +800,16 @@ const showDataSourceToggle = computed(() => {
|
|||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
// 当前高亮的数据源(请求头对比模式下两个都不高亮)
|
// 当前高亮的数据源(对比模式下两个都不高亮)
|
||||||
const activeDataSource = computed(() => {
|
const activeDataSource = computed(() => {
|
||||||
if (activeTab.value === 'request-headers' && viewMode.value === 'compare') return null
|
if (['request-headers', 'response-headers'].includes(activeTab.value) && viewMode.value === 'compare') return null
|
||||||
return dataSource.value
|
return dataSource.value
|
||||||
})
|
})
|
||||||
|
|
||||||
// 设置数据源(请求头需要同时退出对比模式)
|
// 设置数据源(对比模式下切换数据源需退出对比)
|
||||||
function setDataSource(source: 'client' | 'provider') {
|
function setDataSource(source: 'client' | 'provider') {
|
||||||
dataSource.value = source
|
dataSource.value = source
|
||||||
if (activeTab.value === 'request-headers' && viewMode.value === 'compare') {
|
if (['request-headers', 'response-headers'].includes(activeTab.value) && viewMode.value === 'compare') {
|
||||||
viewMode.value = 'formatted'
|
viewMode.value = 'formatted'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
<!-- 对比模式 - 并排 Diff -->
|
<!-- 对比模式 - 并排 Diff -->
|
||||||
<div v-show="viewMode === 'compare'">
|
<div v-show="viewMode === 'compare'">
|
||||||
<div
|
<div
|
||||||
v-if="!detail.request_headers && !detail.provider_request_headers"
|
v-if="!resolvedClientHeaders || !resolvedProviderHeaders"
|
||||||
class="text-sm text-muted-foreground"
|
class="text-sm text-muted-foreground"
|
||||||
>
|
>
|
||||||
无请求头信息
|
{{ emptyMessage }}
|
||||||
</div>
|
</div>
|
||||||
<Card
|
<Card
|
||||||
v-else
|
v-else
|
||||||
@@ -15,11 +15,11 @@
|
|||||||
<!-- Diff 头部 -->
|
<!-- Diff 头部 -->
|
||||||
<div class="flex border-b bg-muted/50">
|
<div class="flex border-b bg-muted/50">
|
||||||
<div class="flex-1 px-3 py-2 text-xs text-muted-foreground border-r flex items-center justify-between">
|
<div class="flex-1 px-3 py-2 text-xs text-muted-foreground border-r flex items-center justify-between">
|
||||||
<span class="font-medium">客户端请求头</span>
|
<span class="font-medium">{{ clientLabel }}</span>
|
||||||
<span class="text-destructive">-{{ headerStats.removed + headerStats.modified }}</span>
|
<span class="text-destructive">-{{ headerStats.removed + headerStats.modified }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 px-3 py-2 text-xs text-muted-foreground flex items-center justify-between">
|
<div class="flex-1 px-3 py-2 text-xs text-muted-foreground flex items-center justify-between">
|
||||||
<span class="font-medium">提供商请求头</span>
|
<span class="font-medium">{{ providerLabel }}</span>
|
||||||
<span class="text-green-600 dark:text-green-400">+{{ headerStats.added + headerStats.modified }}</span>
|
<span class="text-green-600 dark:text-green-400">+{{ headerStats.added + headerStats.modified }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -161,7 +161,7 @@ import Card from '@/components/ui/card.vue'
|
|||||||
import JsonContent from './JsonContent.vue'
|
import JsonContent from './JsonContent.vue'
|
||||||
import type { RequestDetail } from '@/api/dashboard'
|
import type { RequestDetail } from '@/api/dashboard'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
detail: RequestDetail
|
detail: RequestDetail
|
||||||
viewMode: 'compare' | 'formatted' | 'raw'
|
viewMode: 'compare' | 'formatted' | 'raw'
|
||||||
dataSource: 'client' | 'provider'
|
dataSource: 'client' | 'provider'
|
||||||
@@ -172,7 +172,25 @@ const props = defineProps<{
|
|||||||
providerHeadersWithDiff: Array<{ key: string; value: unknown; status: string }>
|
providerHeadersWithDiff: Array<{ key: string; value: unknown; status: string }>
|
||||||
headerStats: { added: number; modified: number; removed: number; unchanged: number }
|
headerStats: { added: number; modified: number; removed: number; unchanged: number }
|
||||||
isDark: boolean
|
isDark: boolean
|
||||||
}>()
|
// 泛化 props:允许传入任意 header 对和标签,用于复用为响应头对比
|
||||||
|
clientHeaders?: Record<string, unknown>
|
||||||
|
providerHeaders?: Record<string, unknown>
|
||||||
|
clientLabel?: string
|
||||||
|
providerLabel?: string
|
||||||
|
emptyMessage?: string
|
||||||
|
}>(), {
|
||||||
|
clientLabel: '客户端请求头',
|
||||||
|
providerLabel: '提供商请求头',
|
||||||
|
emptyMessage: '无请求头信息',
|
||||||
|
})
|
||||||
|
|
||||||
|
// 解析实际使用的 header 数据
|
||||||
|
const resolvedClientHeaders = computed(() =>
|
||||||
|
props.clientHeaders ?? props.detail.request_headers ?? {}
|
||||||
|
)
|
||||||
|
const resolvedProviderHeaders = computed(() =>
|
||||||
|
props.providerHeaders ?? props.detail.provider_request_headers ?? {}
|
||||||
|
)
|
||||||
|
|
||||||
const leftPanelRef = ref<HTMLElement | null>(null)
|
const leftPanelRef = ref<HTMLElement | null>(null)
|
||||||
const rightPanelRef = ref<HTMLElement | null>(null)
|
const rightPanelRef = ref<HTMLElement | null>(null)
|
||||||
@@ -198,8 +216,8 @@ function onRightScroll() {
|
|||||||
|
|
||||||
// 合并并排序的条目(用于并排显示)
|
// 合并并排序的条目(用于并排显示)
|
||||||
const sortedEntries = computed(() => {
|
const sortedEntries = computed(() => {
|
||||||
const clientHeaders = props.detail.request_headers || {}
|
const clientHeaders = resolvedClientHeaders.value
|
||||||
const providerHeaders = props.detail.provider_request_headers || {}
|
const providerHeaders = resolvedProviderHeaders.value
|
||||||
|
|
||||||
const clientKeys = new Set(Object.keys(clientHeaders))
|
const clientKeys = new Set(Object.keys(clientHeaders))
|
||||||
const providerKeys = new Set(Object.keys(providerHeaders))
|
const providerKeys = new Set(Object.keys(providerHeaders))
|
||||||
|
|||||||
Reference in New Issue
Block a user