This commit is contained in:
399
src/views/audit/integrations/detail.vue
Normal file
399
src/views/audit/integrations/detail.vue
Normal file
@@ -0,0 +1,399 @@
|
||||
<template>
|
||||
<div class="integration-detail-page">
|
||||
<ElCard shadow="never">
|
||||
<div class="detail-header">
|
||||
<ElButton @click="router.back()">
|
||||
<template #icon>
|
||||
<ElIcon><ArrowLeft /></ElIcon>
|
||||
</template>
|
||||
返回
|
||||
</ElButton>
|
||||
<h2 class="detail-title">外部集成交互详情</h2>
|
||||
<ElTag
|
||||
v-if="detail"
|
||||
class="result-tag"
|
||||
:type="integrationCategoryMeta[detail.result.category]?.type || 'info'"
|
||||
>
|
||||
{{ auditResultDisplay(detail.result.code, detail.result.name) }}
|
||||
</ElTag>
|
||||
</div>
|
||||
|
||||
<div v-loading="loading">
|
||||
<template v-if="detail">
|
||||
<ElCard shadow="never" class="section-card">
|
||||
<template #header>交互概览</template>
|
||||
<ElDescriptions :column="3" border>
|
||||
<ElDescriptionsItem label="提供方">
|
||||
{{ detail.identity.provider_name || detail.identity.provider }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="方向">
|
||||
{{ detail.identity.direction_name || detail.identity.direction }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="操作">
|
||||
{{ detail.identity.operation_name || detail.identity.operation }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="结果">
|
||||
<ElTag :type="integrationCategoryMeta[detail.result.category]?.type || 'info'">
|
||||
{{ auditResultDisplay(detail.result.code, detail.result.name) }}
|
||||
</ElTag>
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="结果类别">
|
||||
{{
|
||||
integrationCategoryMeta[detail.result.category]?.label || detail.result.category
|
||||
}}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="耗时"
|
||||
>{{ detail.result.duration_ms }} ms</ElDescriptionsItem
|
||||
>
|
||||
<ElDescriptionsItem label="HTTP 状态">
|
||||
{{ detail.result.http_status ?? '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="外部结果码">
|
||||
{{ detail.result.provider_code || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="状态变更">
|
||||
{{ booleanText(detail.result.state_changed) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem v-if="detail.identity.external_id" label="外部业务标识" :span="3">
|
||||
{{ detail.identity.external_id }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem
|
||||
v-if="detail.result.provider_message"
|
||||
label="外部结果摘要"
|
||||
:span="3"
|
||||
>
|
||||
{{ detail.result.provider_message }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem
|
||||
v-if="detail.result.recovery_strategy"
|
||||
label="恢复策略说明"
|
||||
:span="3"
|
||||
>
|
||||
{{ detail.result.recovery_strategy }}
|
||||
</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
</ElCard>
|
||||
|
||||
<div class="detail-grid section-card">
|
||||
<ElCard v-if="hasResource" shadow="never">
|
||||
<template #header>关联资源</template>
|
||||
<ElDescriptions :column="1">
|
||||
<ElDescriptionsItem label="资源类型">
|
||||
{{
|
||||
auditResourceTypeLabels[detail.resource?.type || ''] ||
|
||||
detail.resource?.type ||
|
||||
'-'
|
||||
}}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="业务标识">
|
||||
{{ detail.resource?.key || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
</ElCard>
|
||||
<ElCard shadow="never">
|
||||
<template #header>调查入口</template>
|
||||
<div v-if="hasAssociations" class="link-actions">
|
||||
<ElButton
|
||||
v-if="detail.linkage.request_id"
|
||||
type="primary"
|
||||
plain
|
||||
@click="
|
||||
openAuditInvestigation({ mode: 'request', id: detail.linkage.request_id })
|
||||
"
|
||||
>
|
||||
请求链路
|
||||
</ElButton>
|
||||
<ElButton
|
||||
v-if="detail.linkage.correlation_id"
|
||||
type="primary"
|
||||
plain
|
||||
@click="
|
||||
openAuditInvestigation({
|
||||
mode: 'correlation',
|
||||
id: detail.linkage.correlation_id
|
||||
})
|
||||
"
|
||||
>
|
||||
业务关联链路
|
||||
</ElButton>
|
||||
<ElButton
|
||||
v-if="detail.resource?.type && detail.resource?.id"
|
||||
type="primary"
|
||||
plain
|
||||
@click="openResourceTimeline"
|
||||
>
|
||||
资源审计时间线
|
||||
</ElButton>
|
||||
</div>
|
||||
<span v-else class="muted">无可用调查引用</span>
|
||||
<ElAlert
|
||||
v-if="hasUnavailableFidelity"
|
||||
class="fidelity-alert"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
title="部分关联缺少可靠标识,已隐藏相应入口。"
|
||||
/>
|
||||
</ElCard>
|
||||
</div>
|
||||
|
||||
<div class="detail-grid section-card">
|
||||
<ElCard shadow="never">
|
||||
<template #header>触发信息</template>
|
||||
<ElDescriptions :column="1">
|
||||
<ElDescriptionsItem label="触发来源">
|
||||
{{ detail.trigger.source || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="业务场景">
|
||||
{{ detail.trigger.scene || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="尝试序号">
|
||||
{{ detail.trigger.attempt }}
|
||||
</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
</ElCard>
|
||||
<ElCard shadow="never">
|
||||
<template #header>时间信息</template>
|
||||
<ElDescriptions :column="1">
|
||||
<ElDescriptionsItem label="创建时间">
|
||||
{{ formatDateTime(detail.timestamps.created_at) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="计划时间">
|
||||
{{ formatOptionalDate(detail.timestamps.scheduled_at) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="开始时间">
|
||||
{{ formatOptionalDate(detail.timestamps.started_at) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="更新时间">
|
||||
{{ formatDateTime(detail.timestamps.updated_at) }}
|
||||
</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
</ElCard>
|
||||
</div>
|
||||
|
||||
<ElCard shadow="never" class="section-card">
|
||||
<template #header>关联可靠性</template>
|
||||
<ElDescriptions :column="3" border>
|
||||
<ElDescriptionsItem label="尝试序列可靠">
|
||||
{{ booleanText(detail.fidelity.attempt_sequence_reliable) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="业务链路可用">
|
||||
{{ booleanText(detail.fidelity.correlation_available) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="资源引用可用">
|
||||
{{ booleanText(detail.fidelity.resource_id_available) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="重试序列可用">
|
||||
{{ booleanText(detail.fidelity.trigger_series_available) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="外部消息保真度" :span="2">
|
||||
{{ detail.fidelity.provider_message_fidelity || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
</ElCard>
|
||||
|
||||
<div v-if="hasContentSummary" class="content-grid section-card">
|
||||
<ElCard v-if="hasJsonContent(detail.content.request_summary)" shadow="never">
|
||||
<template #header>请求摘要</template>
|
||||
<pre>{{ auditJson(detail.content.request_summary) }}</pre>
|
||||
</ElCard>
|
||||
<ElCard v-if="hasJsonContent(detail.content.response_summary)" shadow="never">
|
||||
<template #header>响应摘要</template>
|
||||
<pre>{{ auditJson(detail.content.response_summary) }}</pre>
|
||||
</ElCard>
|
||||
<ElCard v-if="hasJsonContent(detail.content.metadata)" shadow="never">
|
||||
<template #header>扩展信息</template>
|
||||
<pre>{{ auditJson(detail.content.metadata) }}</pre>
|
||||
</ElCard>
|
||||
</div>
|
||||
|
||||
<ElCard v-if="detail.attempts?.length" shadow="never">
|
||||
<template #header>尝试记录</template>
|
||||
<ArtTable :data="detail.attempts" :pagination="false" :margin-top="0">
|
||||
<template #default>
|
||||
<ElTableColumn prop="attempt" label="序号" width="80" />
|
||||
<ElTableColumn label="创建时间" width="180">
|
||||
<template #default="{ row }">{{ formatDateTime(row.created_at) }}</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="操作" min-width="180" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.operation_name || row.operation }}</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="结果" width="110">
|
||||
<template #default="{ row }">
|
||||
<ElTag :type="integrationCategoryType(row.result_category)">
|
||||
{{ auditResultDisplay(row.result, row.result_name) }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="耗时" width="100">
|
||||
<template #default="{ row }">{{ row.duration_ms }} ms</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="已发送" width="90">
|
||||
<template #default="{ row }">{{ booleanText(row.sent) }}</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="状态变更" width="100">
|
||||
<template #default="{ row }">{{ booleanText(row.state_changed) }}</template>
|
||||
</ElTableColumn>
|
||||
</template>
|
||||
</ArtTable>
|
||||
</ElCard>
|
||||
</template>
|
||||
<ElEmpty v-else-if="!loading" description="未找到外部集成交互记录" />
|
||||
</div>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ArrowLeft } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { AuditService } from '@/api/modules'
|
||||
import type { IntegrationDetailResponse } from '@/types/api'
|
||||
import {
|
||||
auditJson,
|
||||
auditResourceTypeLabels,
|
||||
auditResultDisplay,
|
||||
integrationCategoryMeta
|
||||
} from '@/utils/business/audit'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import { openAuditInvestigation } from '@/components/business/audit/investigationController'
|
||||
|
||||
defineOptions({ name: 'AuditIntegrationDetail' })
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const loading = ref(false)
|
||||
const detail = ref<IntegrationDetailResponse>()
|
||||
|
||||
const hasUnavailableFidelity = computed(() =>
|
||||
Object.values(detail.value?.fidelity || {}).some((value) => value === false)
|
||||
)
|
||||
const hasResource = computed(() =>
|
||||
Boolean(detail.value?.resource?.type || detail.value?.resource?.key)
|
||||
)
|
||||
const hasAssociations = computed(() =>
|
||||
Boolean(
|
||||
detail.value?.linkage.request_id ||
|
||||
detail.value?.linkage.correlation_id ||
|
||||
(detail.value?.resource?.type && detail.value?.resource?.id)
|
||||
)
|
||||
)
|
||||
const hasJsonContent = (value?: Record<string, unknown> | null) =>
|
||||
Boolean(value && Object.keys(value).length)
|
||||
const hasContentSummary = computed(() =>
|
||||
Boolean(
|
||||
hasJsonContent(detail.value?.content.request_summary) ||
|
||||
hasJsonContent(detail.value?.content.response_summary) ||
|
||||
hasJsonContent(detail.value?.content.metadata)
|
||||
)
|
||||
)
|
||||
|
||||
const booleanText = (value: boolean) => (value ? '是' : '否')
|
||||
const integrationCategoryType = (category: unknown) =>
|
||||
integrationCategoryMeta[category as keyof typeof integrationCategoryMeta]?.type || 'info'
|
||||
const formatOptionalDate = (value?: string | null) => (value ? formatDateTime(value) : '-')
|
||||
const openResourceTimeline = () => {
|
||||
if (!detail.value?.resource?.type || !detail.value.resource.id) return
|
||||
openAuditInvestigation({
|
||||
mode: 'resource',
|
||||
resourceType: detail.value.resource.type,
|
||||
id: detail.value.resource.id
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const id = String(route.params.integrationId || '')
|
||||
if (!id) return
|
||||
loading.value = true
|
||||
try {
|
||||
detail.value = (await AuditService.getIntegrationDetail(id)).data
|
||||
} catch {
|
||||
ElMessage.error('外部集成交互详情加载失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.integration-detail-page {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.result-tag {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.section-card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.detail-grid,
|
||||
.content-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.content-grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.link-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.fidelity-alert {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
pre {
|
||||
max-height: 360px;
|
||||
padding: 12px;
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
|
||||
line-height: 1.6;
|
||||
overflow-wrap: anywhere;
|
||||
white-space: pre-wrap;
|
||||
background: var(--el-fill-color-light);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@media (width <= 992px) {
|
||||
.content-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (width <= 768px) {
|
||||
.integration-detail-page {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.detail-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user