feat: redesign responsive control room ui

This commit is contained in:
2026-04-26 02:18:03 +08:00
parent ff3e1fae70
commit 94f6e08dea
16 changed files with 4189 additions and 926 deletions
+78 -48
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { onMounted, reactive, ref } from "vue";
import { computed, onMounted, reactive, ref } from "vue";
import apiClient, { getApiErrorMessage } from "@/api/client";
import { useViewport } from "@/composables/useViewport";
import type { SystemLog } from "@/types";
@@ -22,6 +22,10 @@ const logLevelOptions = Object.entries(logLevelLabelMap).map(([value, label]) =>
label
}));
const totalWarnings = computed(() => logs.value.filter((item) => item.level === 2).length);
const totalErrors = computed(() => logs.value.filter((item) => item.level === 3).length);
const newestLogTime = computed(() => (logs.value[0] ? formatDate(logs.value[0].createdAt) : "-"));
async function loadLogs() {
loading.value = true;
loadError.value = "";
@@ -67,18 +71,50 @@ onMounted(loadLogs);
<div class="page-stack">
<div class="page-header">
<div>
<div class="page-kicker">Diagnostics</div>
<h1 class="page-title">系统日志</h1>
<p class="page-subtitle">
支持按直播间任务和日志级别筛选便于快速定位解析失败巡检异常和 ffmpeg 输出问题
面向排障的原始事件流这里保留最近发生的关键日志可以按直播间任务和级别快速收缩范围
</p>
</div>
<div class="page-toolbar">
<el-button @click="loadLogs">刷新日志</el-button>
</div>
</div>
<el-alert v-if="loadError" class="page-error-alert" type="error" :closable="false" show-icon :title="loadError" />
<div class="stats-grid">
<div class="stat-card">
<div class="stat-card__label">当前加载</div>
<div class="stat-card__value">{{ logs.length }}</div>
<div class="stat-card__hint">按当前筛选条件拉取到的日志条目</div>
</div>
<div class="stat-card">
<div class="stat-card__label">Warning</div>
<div class="stat-card__value">{{ totalWarnings }}</div>
<div class="stat-card__hint">用于快速观察近期是否出现成批预警</div>
</div>
<div class="stat-card">
<div class="stat-card__label">Error</div>
<div class="stat-card__value">{{ totalErrors }}</div>
<div class="stat-card__hint">优先定位对录制流程有破坏性的异常事件</div>
</div>
<div class="stat-card">
<div class="stat-card__label">最新事件</div>
<div class="stat-card__value" style="font-size: 18px; letter-spacing: -0.03em;">{{ newestLogTime }}</div>
<div class="stat-card__hint">默认按时间倒序优先展示最近发生的事件</div>
</div>
</div>
<el-card class="surface-card filter-card" shadow="never">
<h3 class="section-title">日志过滤</h3>
<p class="section-subtitle">默认拉取最近 200 条日志可按直播间 ID任务 ID 和级别缩小范围</p>
<div class="toolbar-row">
<div>
<h3 class="section-title">日志过滤</h3>
<p class="section-subtitle">默认拉取最近 200 条日志移动端也保留同一套过滤维度</p>
</div>
</div>
<el-form class="filter-form">
<el-form-item label="直播间 ID">
@@ -101,20 +137,51 @@ onMounted(loadLogs);
<el-input-number v-model="filters.take" :min="1" :max="500" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadLogs">查询</el-button>
<el-button type="primary" @click="loadLogs">应用筛选</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="surface-card table-card" shadow="never">
<h3 class="section-title">日志列表</h3>
<p class="section-subtitle">按时间倒序排列优先展示最近发生的事件</p>
<div class="toolbar-row">
<div>
<h3 class="section-title">日志列表</h3>
<p class="section-subtitle">桌面端保留高密度表格移动端切成事件卡片便于单手排查</p>
</div>
</div>
<div class="table-scroll-shell logs-table-shell">
<div v-if="isMobile" class="data-card-list">
<article v-for="row in logs" :key="row.id" class="data-card">
<div class="data-card__header">
<div>
<div class="data-card__title">{{ row.message }}</div>
<div class="data-card__subtitle">{{ row.category }}</div>
</div>
<el-tag :type="levelTagType(row.level)">{{ logLevelLabelMap[row.level] }}</el-tag>
</div>
<div class="data-card__grid">
<div>
<dt>时间</dt>
<dd>{{ formatDate(row.createdAt) }}</dd>
</div>
<div>
<dt>任务</dt>
<dd class="monospace">{{ row.recordTaskId || "-" }}</dd>
</div>
<div style="grid-column: 1 / -1;">
<dt>详情</dt>
<dd class="log-detail monospace">{{ row.detail || "-" }}</dd>
</div>
</div>
</article>
</div>
<div v-else class="table-scroll-shell logs-table-shell">
<el-table
:data="logs"
v-loading="loading"
:height="isMobile ? undefined : 720"
:height="720"
class="premium-table logs-table"
table-layout="auto"
>
@@ -135,7 +202,7 @@ onMounted(loadLogs);
</el-table-column>
<el-table-column label="时间" width="180">
<template #default="{ row }">
<span class="log-date-text">{{ formatDate(row.createdAt) }}</span>
<span class="table-date-text">{{ formatDate(row.createdAt) }}</span>
</template>
</el-table-column>
</el-table>
@@ -145,20 +212,6 @@ onMounted(loadLogs);
</template>
<style scoped>
.page-stack {
display: grid;
gap: 24px;
}
.page-error-alert {
border-radius: 14px;
}
.filter-card :deep(.el-card__body),
.table-card :deep(.el-card__body) {
padding-top: 20px;
}
.filter-form {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
@@ -166,30 +219,11 @@ onMounted(loadLogs);
align-items: end;
}
.filter-form :deep(.el-form-item:last-child) {
align-self: end;
}
.log-detail {
font-size: 12px;
color: #6d665b;
line-height: 1.6;
}
.log-date-text {
white-space: nowrap;
font-variant-numeric: tabular-nums;
}
.logs-table-shell {
margin-inline: -4px;
padding-inline: 4px;
}
.logs-table {
min-width: 980px;
}
.logs-table :deep(.el-table__cell) {
vertical-align: top;
}
@@ -208,13 +242,9 @@ onMounted(loadLogs);
.filter-form {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.filter-form :deep(.el-form-item:last-child) {
margin-left: 0;
}
}
@media (max-width: 640px) {
@media (max-width: 767px) {
.filter-form {
grid-template-columns: 1fr;
}