Files
SEONexusAdmin/src/views/home/index.vue

241 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="home card">
<el-card shadow="never" class="home-metadata-console">
<template #header>
<div class="home-metadata-console__header">
<div>
<div class="home-metadata-console__title">视频缺字段控制台</div>
<div class="home-metadata-console__subtitle">后台打开先看这里工作台任务池和计划任务状态都汇总到首屏</div>
</div>
<div class="home-metadata-console__actions">
<el-button :loading="metadataLoading" @click="loadMetadataConsole">刷新状态</el-button>
<el-button type="primary" :loading="workbenchRunning" @click="handleRunWorkbench">刷新工作台</el-button>
<el-button type="warning" plain :loading="taskPoolRunning" @click="handleRunTaskPool">刷新任务池</el-button>
</div>
</div>
</template>
<el-alert
:type="metadataAlertType"
:closable="false"
show-icon
:title="metadataAlertTitle"
:description="metadataAlertDescription"
/>
<div class="home-metadata-console__stats">
<div class="home-metadata-stat">
<div class="home-metadata-stat__label">缺字段视频总量</div>
<div class="home-metadata-stat__value">{{ metadataSummary.workbench?.videos_with_any_missing_metadata || 0 }}</div>
<div class="home-metadata-stat__tip">越大越需要先看工作台再按批次派给 Codex</div>
</div>
<div class="home-metadata-stat">
<div class="home-metadata-stat__label">重采优先队列</div>
<div class="home-metadata-stat__value">{{ metadataSummary.workbench?.queue_size || 0 }}</div>
<div class="home-metadata-stat__tip">这是最适合优先处理的一批适合先做首轮修复</div>
</div>
<div class="home-metadata-stat">
<div class="home-metadata-stat__label">任务池批次数</div>
<div class="home-metadata-stat__value">{{ metadataSummary.task_pool?.batch_count || 0 }}</div>
<div class="home-metadata-stat__tip">批次越清晰后续技术和 Codex 的协作越顺</div>
</div>
</div>
<div class="home-metadata-console__hint">
<strong>当前建议</strong>{{ metadataSummary.recommended_action || "先刷新状态,再看工作台和任务池。" }}
<br />
<strong>首条提示</strong>{{ metadataFirstHint }}
</div>
<div class="home-metadata-console__links">
<el-button text type="primary" @click="openMetadataRoute('/video/metadata/missing/task-pool/status')">打开状态总览</el-button>
<el-button text type="primary" @click="openMetadataRoute('/video/metadata/missing/task-pool/ops')">打开运维总览</el-button>
<el-button text type="primary" @click="openMetadataRoute('/video/metadata/missing/task-pool')">打开任务池</el-button>
<el-button text type="primary" @click="openMetadataRoute('/video/metadata/missing/workbench')">打开工作台</el-button>
</div>
</el-card>
<div class="home-bootstrap-actions">
<el-button type="primary" @click="openBootstrapCenter('overview')">打开 Bootstrap 总控台</el-button>
<el-button type="warning" plain @click="openBootstrapCenter({ tab: 'ops', opsFocus: { panel: 'oncall', stage: 'all', clearHostFocus: true, hostConsolePanel: 'oncall' } })">值班待办</el-button>
<el-button type="danger" plain @click="openBootstrapCenter({ tab: 'ops', opsFocus: { panel: 'attention', stage: 'attention', clearHostFocus: true, hostConsolePanel: 'runs' } })">异常聚焦</el-button>
<el-button type="info" plain @click="openBootstrapCenter('env')">查看环境模板</el-button>
</div>
<BootstrapOpsOverview />
<BootstrapEnvOverview action-text="前往站点页查看环境模板" @open-dialog="goSitePage" />
<BootstrapAdminCenter ref="bootstrapAdminCenterRef" />
</div>
</template>
<script setup lang="ts" name="home">
import { computed, onMounted, ref } from "vue";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import BootstrapOpsOverview from "@/views/site/bootstrapOpsOverview.vue";
import BootstrapEnvOverview from "@/views/site/bootstrapEnvOverview.vue";
import BootstrapAdminCenter from "@/views/site/bootstrapAdminCenter.vue";
import {
getMetadataMissingTaskPoolStatus,
runMetadataMissingTaskPool,
runMetadataMissingWorkbench
} from "@/api/modules/video/list";
import type { VideoList } from "@/api/interface/video/list";
const router = useRouter();
const bootstrapAdminCenterRef = ref<InstanceType<typeof BootstrapAdminCenter> | null>(null);
const metadataLoading = ref(false);
const workbenchRunning = ref(false);
const taskPoolRunning = ref(false);
const metadataSummary = ref<VideoList.MetadataMissingTaskPoolStatusData>({});
type BootstrapFocusMode = "all" | "task" | "oncall" | "pipeline" | "import" | "release";
type BootstrapFocusPayload = BootstrapFocusMode | BootstrapFocusMode[];
type BootstrapComparePreset =
| "latest_two"
| "latest_applied_vs_dry_run"
| "latest_template_two"
| "latest_template_apply_vs_preview"
| "latest_restore_two"
| "latest_restore_apply_vs_preview";
type BootstrapRestorePreset = "latest_restore_after_preview" | "latest_restore_before_preview";
type BootstrapHistoryActionPreset = "template_family" | "restore_family" | "restore_preview_only" | "restore_apply_only";
type BootstrapDialogPayload =
| BootstrapFocusPayload
| {
focusMode?: BootstrapFocusPayload;
comparePreset?: BootstrapComparePreset;
restorePreset?: BootstrapRestorePreset;
historyActionPreset?: BootstrapHistoryActionPreset;
};
type OpsPanelKey = "queue" | "oncall" | "tasks" | "runs" | "attention" | "completed";
type BootstrapStageKey =
| "all"
| "ready_to_register"
| "ready_to_apply"
| "ready_for_prepare"
| "ready_to_import"
| "ready_to_release_dry_run"
| "ready_to_publish"
| "attention"
| "published";
type HostConsolePanelKey = "bundle" | "oncall" | "runs" | "completed";
type OpsInspectorKind = "bundle" | "oncall" | "task" | "run" | "attention_bundle" | "failed_run" | "completed";
type BootstrapOpsFocusPayload = {
panel?: OpsPanelKey;
stage?: BootstrapStageKey;
host?: string;
keyword?: string;
clearHostFocus?: boolean;
hostConsolePanel?: HostConsolePanelKey;
inspectorKind?: OpsInspectorKind | "default";
};
type BootstrapAdminOpenPayload =
| "overview"
| "ops"
| "env"
| {
tab?: "overview" | "ops" | "env";
opsFocus?: BootstrapOpsFocusPayload;
envDialog?: BootstrapDialogPayload;
refresh?: boolean;
};
const openBootstrapCenter = (payload: BootstrapAdminOpenPayload = "overview") => {
bootstrapAdminCenterRef.value?.openDialog(payload);
};
const loadMetadataConsole = async () => {
metadataLoading.value = true;
try {
const response = await getMetadataMissingTaskPoolStatus();
metadataSummary.value = response.data || {};
} finally {
metadataLoading.value = false;
}
};
const handleRunWorkbench = async () => {
workbenchRunning.value = true;
try {
const response = await runMetadataMissingWorkbench();
ElMessage.success(response.msg || "工作台刷新已触发");
await loadMetadataConsole();
} finally {
workbenchRunning.value = false;
}
};
const handleRunTaskPool = async () => {
taskPoolRunning.value = true;
try {
const response = await runMetadataMissingTaskPool();
ElMessage.success(response.msg || "任务池刷新已触发");
await loadMetadataConsole();
} finally {
taskPoolRunning.value = false;
}
};
const openMetadataRoute = (path: string) => {
router.push(path);
};
const metadataFirstHint = computed(() => metadataSummary.value.operator_hint?.[0] || "当前还没有额外提示,建议先看状态总览。");
const metadataAlertType = computed(() => {
const status = metadataSummary.value.plan_task?.status_label || "missing";
if (status === "enabled") return "success";
if (status === "disabled") return "warning";
if (status === "db_blocked") return "error";
return "info";
});
const metadataAlertTitle = computed(() => {
const status = metadataSummary.value.plan_task?.status_label || "missing";
if (status === "enabled") return "视频缺字段计划任务已启用";
if (status === "disabled") return "视频缺字段计划任务已存在但未启用";
if (status === "db_blocked") return "当前环境暂时无法读取视频缺字段计划任务";
return "视频缺字段计划任务尚未补齐";
});
const metadataAlertDescription = computed(() => {
const status = metadataSummary.value.plan_task?.status_label || "missing";
const action = metadataSummary.value.recommended_action || "先刷新状态,再决定下一步。";
if (status === "enabled") return `当前工作台和任务池已经可持续刷新。${action}`;
if (status === "disabled") return `当前后台已经有这条计划任务,但还没有启用。${action}`;
if (status === "db_blocked") return `这通常说明当前后台环境没读到完整数据库上下文。${action}`;
return `当前还没补齐这条计划任务,建议先补齐再观察。${action}`;
});
const goSitePage = (payload: BootstrapDialogPayload = "all") => {
const normalizedFocus = typeof payload === "string" || Array.isArray(payload) ? payload : payload.focusMode ?? "all";
const comparePreset = typeof payload === "string" || Array.isArray(payload) ? undefined : payload.comparePreset;
const restorePreset = typeof payload === "string" || Array.isArray(payload) ? undefined : payload.restorePreset;
const historyActionPreset = typeof payload === "string" || Array.isArray(payload) ? undefined : payload.historyActionPreset;
const queryFocus = Array.isArray(normalizedFocus) ? normalizedFocus.join(",") : normalizedFocus;
router.push({
path: "/site/index",
query: {
...(queryFocus === "all" ? {} : { bootstrapFocus: queryFocus }),
...(comparePreset ? { bootstrapComparePreset: comparePreset } : {}),
...(restorePreset ? { bootstrapRestorePreset: restorePreset } : {}),
...(historyActionPreset ? { bootstrapHistoryActionPreset: historyActionPreset } : {})
}
});
};
onMounted(() => {
loadMetadataConsole();
});
</script>
<style scoped lang="scss">
@use "./index.scss" as *;
.home-bootstrap-actions {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin: 0 0 16px;
}
</style>