debug
This commit is contained in:
371
src/views/site/domainExternalSeoSnapshotValidateRunDialog.vue
Normal file
371
src/views/site/domainExternalSeoSnapshotValidateRunDialog.vue
Normal file
@@ -0,0 +1,371 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" title="最近站外快照校验记录" width="1080px" draggable>
|
||||
<div class="snapshot-validate-run-dialog">
|
||||
<div class="snapshot-validate-run-dialog__toolbar">
|
||||
<el-button type="primary" plain @click="loadRuns">刷新</el-button>
|
||||
</div>
|
||||
<div v-if="summary" class="snapshot-validate-run-dialog__summary">
|
||||
<el-alert
|
||||
:title="buildSummaryTitle(summary)"
|
||||
:type="summary.alert_buckets?.high ? 'error' : summary.alert_buckets?.medium ? 'warning' : 'success'"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="summary" class="snapshot-validate-run-dialog__hero">
|
||||
<div class="snapshot-validate-run-dialog__hero-main">
|
||||
<div class="snapshot-validate-run-dialog__hero-label">最近 dry-run 总览</div>
|
||||
<div class="snapshot-validate-run-dialog__hero-value">{{ buildValidateHealth(summary) }}</div>
|
||||
<div class="snapshot-validate-run-dialog__hero-note">
|
||||
先看高优先级 dry-run 数量,再看最新一笔字段校验是否已经恢复通过。
|
||||
</div>
|
||||
</div>
|
||||
<div class="snapshot-validate-run-dialog__hero-metrics">
|
||||
<div v-for="item in buildHeroMetrics(summary)" :key="item.label" class="snapshot-validate-run-dialog__metric">
|
||||
<div class="snapshot-validate-run-dialog__metric-label">{{ item.label }}</div>
|
||||
<div class="snapshot-validate-run-dialog__metric-value">{{ item.value }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="summary" class="snapshot-validate-run-dialog__attention">
|
||||
<div class="snapshot-validate-run-dialog__attention-box">
|
||||
<div class="snapshot-validate-run-dialog__attention-title">优先关注的 dry-run</div>
|
||||
<div v-if="!(summary.top_attention_runs || []).length" class="snapshot-validate-run-dialog__attention-empty">当前没有高优先级 dry-run 异常</div>
|
||||
<div v-else class="snapshot-validate-run-dialog__attention-list">
|
||||
<div v-for="item in (summary.top_attention_runs || []).slice(0, 3)" :key="item.run_id" class="snapshot-validate-run-dialog__attention-item">
|
||||
<div class="snapshot-validate-run-dialog__attention-run">{{ item.run_id }}</div>
|
||||
<div class="snapshot-validate-run-dialog__attention-reason">{{ item.alert_reason || "-" }}</div>
|
||||
<div class="snapshot-validate-run-dialog__attention-meta">
|
||||
通过率 {{ Number(item.success_rate || 0).toFixed(0) }}% / 错误 {{ Number(item.error_count || 0) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snapshot-validate-run-dialog__attention-box">
|
||||
<div class="snapshot-validate-run-dialog__attention-title">最新一笔字段校验</div>
|
||||
<div class="snapshot-validate-run-dialog__latest-grid">
|
||||
<div class="snapshot-validate-run-dialog__mini">
|
||||
<div class="snapshot-validate-run-dialog__mini-label">Run</div>
|
||||
<div class="snapshot-validate-run-dialog__mini-value snapshot-validate-run-dialog__mini-value--break">
|
||||
{{ summary.latest_run?.run_id || "-" }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="snapshot-validate-run-dialog__mini">
|
||||
<div class="snapshot-validate-run-dialog__mini-label">状态</div>
|
||||
<div class="snapshot-validate-run-dialog__mini-value">{{ summary.latest_run?.passed ? "passed" : "failed" }}</div>
|
||||
</div>
|
||||
<div class="snapshot-validate-run-dialog__mini">
|
||||
<div class="snapshot-validate-run-dialog__mini-label">通过率</div>
|
||||
<div class="snapshot-validate-run-dialog__mini-value">{{ Number(summary.latest_run?.success_rate || 0).toFixed(0) }}%</div>
|
||||
</div>
|
||||
<div class="snapshot-validate-run-dialog__mini">
|
||||
<div class="snapshot-validate-run-dialog__mini-label">主要原因</div>
|
||||
<div class="snapshot-validate-run-dialog__mini-value">{{ summary.latest_run?.alert_reason || "-" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="rows" border>
|
||||
<el-table-column prop="run_id" label="Run ID" min-width="180" />
|
||||
<el-table-column prop="received_count" label="接收" width="90" />
|
||||
<el-table-column prop="valid_count" label="通过" width="90" />
|
||||
<el-table-column prop="error_count" label="错误" width="90" />
|
||||
<el-table-column prop="warning_count" label="警告" width="90" />
|
||||
<el-table-column label="通过率" width="100">
|
||||
<template #default="{ row }">
|
||||
<span>{{ Number(row.success_rate || 0).toFixed(0) }}%</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="告警" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.alert_level && row.alert_level !== 'none'" :type="row.alert_level === 'high' ? 'danger' : row.alert_level === 'medium' ? 'warning' : 'info'" size="small">
|
||||
{{ row.alert_level }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.passed ? 'success' : 'danger'" size="small">
|
||||
{{ row.passed ? "passed" : "failed" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="错误聚合" min-width="240">
|
||||
<template #default="{ row }">
|
||||
<span v-if="!(row.error_buckets || []).length">-</span>
|
||||
<span v-else>
|
||||
{{
|
||||
(row.error_buckets || [])
|
||||
.slice(0, 3)
|
||||
.map((item: any) => `${item.message} x${item.count}`)
|
||||
.join(" ; ")
|
||||
}}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="警告聚合" min-width="220">
|
||||
<template #default="{ row }">
|
||||
<span v-if="!(row.warning_buckets || []).length">-</span>
|
||||
<span v-else>
|
||||
{{
|
||||
(row.warning_buckets || [])
|
||||
.slice(0, 2)
|
||||
.map((item: any) => `${item.message} x${item.count}`)
|
||||
.join(" ; ")
|
||||
}}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="updated_at" label="更新时间" min-width="180" />
|
||||
<el-table-column label="操作" width="320" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button v-if="row.summary_html_path" link type="primary" @click="openPublicPath(row.summary_html_path)">HTML摘要</el-button>
|
||||
<el-button v-if="row.summary_json_path" link type="info" @click="openPublicPath(row.summary_json_path)">JSON摘要</el-button>
|
||||
<el-button v-if="row.payload_json_path" link type="warning" @click="openPublicPath(row.payload_json_path)">原始快照</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { SiteList } from "@/api/interface/site/list";
|
||||
import { getDomainExternalSeoSnapshotValidateRuns } from "@/api/modules/site/list";
|
||||
|
||||
const BASE_URL = import.meta.env.VITE_API_URL as string;
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const rows = ref<SiteList.DomainExternalSeoSnapshotValidateRunItem[]>([]);
|
||||
const summary = ref<SiteList.DomainExternalSeoSnapshotValidateRunData | null>(null);
|
||||
|
||||
const openPublicPath = (path?: string) => {
|
||||
if (!path) return;
|
||||
window.open(`${BASE_URL}/${String(path).replace(/^\/+/, "")}`, "_blank", "noopener");
|
||||
};
|
||||
|
||||
const loadRuns = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getDomainExternalSeoSnapshotValidateRuns({ limit: 20 });
|
||||
summary.value = res.data ?? null;
|
||||
rows.value = res.data.items ?? [];
|
||||
} catch (_error) {
|
||||
ElMessage.error("读取站外快照校验记录失败");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const buildSummaryTitle = (data?: SiteList.DomainExternalSeoSnapshotValidateRunData | null) => {
|
||||
const high = Number(data?.alert_buckets?.high || 0);
|
||||
const medium = Number(data?.alert_buckets?.medium || 0);
|
||||
if (high > 0) {
|
||||
const item = (data?.top_attention_runs || [])[0];
|
||||
return `最近 dry-run 里有 ${high} 笔高优先级校验失败,先看 ${item?.run_id || "最新异常 run"}。`;
|
||||
}
|
||||
if (medium > 0) {
|
||||
const item = (data?.top_attention_runs || [])[0];
|
||||
return `最近 dry-run 里有 ${medium} 笔需要关注的字段问题,建议先看 ${item?.run_id || "异常 run"}。`;
|
||||
}
|
||||
return "最近 dry-run 整体稳定,当前没有高优先级字段校验问题。";
|
||||
};
|
||||
|
||||
const buildValidateHealth = (data?: SiteList.DomainExternalSeoSnapshotValidateRunData | null) => {
|
||||
if (Number(data?.alert_buckets?.high || 0) > 0) return "字段校验高风险";
|
||||
if (Number(data?.alert_buckets?.medium || 0) > 0) return "字段校验需关注";
|
||||
if (data?.latest_run?.passed) return "最近 dry-run 已恢复通过";
|
||||
return "等待新的 dry-run 数据";
|
||||
};
|
||||
|
||||
const buildHeroMetrics = (data?: SiteList.DomainExternalSeoSnapshotValidateRunData | null) => {
|
||||
return [
|
||||
{
|
||||
label: "总校验笔数",
|
||||
value: `${Number(data?.total || 0)}`,
|
||||
},
|
||||
{
|
||||
label: "高优先级 Run",
|
||||
value: `${Number(data?.alert_buckets?.high || 0)}`,
|
||||
},
|
||||
{
|
||||
label: "最新通过率",
|
||||
value: `${Number(data?.latest_run?.success_rate || 0).toFixed(0)}%`,
|
||||
},
|
||||
{
|
||||
label: "最新错误数",
|
||||
value: `${Number(data?.latest_run?.error_count || 0)}`,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const open = async () => {
|
||||
visible.value = true;
|
||||
await loadRuns();
|
||||
};
|
||||
|
||||
defineExpose({ open });
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.snapshot-validate-run-dialog__toolbar {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.snapshot-validate-run-dialog__summary {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.snapshot-validate-run-dialog__hero {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.snapshot-validate-run-dialog__hero-main {
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #9a3412 0%, #b91c1c 100%);
|
||||
}
|
||||
|
||||
.snapshot-validate-run-dialog__hero-label {
|
||||
font-size: 13px;
|
||||
opacity: 0.86;
|
||||
}
|
||||
|
||||
.snapshot-validate-run-dialog__hero-value {
|
||||
margin-top: 6px;
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.snapshot-validate-run-dialog__hero-note {
|
||||
margin-top: 8px;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
opacity: 0.92;
|
||||
}
|
||||
|
||||
.snapshot-validate-run-dialog__hero-metrics {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.snapshot-validate-run-dialog__metric {
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 12px;
|
||||
padding: 14px;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.snapshot-validate-run-dialog__metric-label {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.snapshot-validate-run-dialog__metric-value {
|
||||
margin-top: 8px;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.snapshot-validate-run-dialog__attention {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.snapshot-validate-run-dialog__attention-box {
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 12px;
|
||||
padding: 14px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.snapshot-validate-run-dialog__attention-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.snapshot-validate-run-dialog__attention-empty {
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.snapshot-validate-run-dialog__attention-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.snapshot-validate-run-dialog__attention-item {
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.snapshot-validate-run-dialog__attention-run {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.snapshot-validate-run-dialog__attention-reason {
|
||||
margin-top: 6px;
|
||||
color: #303133;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.snapshot-validate-run-dialog__attention-meta {
|
||||
margin-top: 6px;
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.snapshot-validate-run-dialog__latest-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.snapshot-validate-run-dialog__mini {
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.snapshot-validate-run-dialog__mini-label {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.snapshot-validate-run-dialog__mini-value {
|
||||
margin-top: 6px;
|
||||
color: #303133;
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.snapshot-validate-run-dialog__mini-value--break {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.snapshot-validate-run-dialog__hero,
|
||||
.snapshot-validate-run-dialog__attention,
|
||||
.snapshot-validate-run-dialog__hero-metrics,
|
||||
.snapshot-validate-run-dialog__latest-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user