feat: improve recording recovery and upload workflow

This commit is contained in:
2026-08-10 11:14:34 +08:00
parent e0d3969e46
commit 19d358b312
58 changed files with 5979 additions and 586 deletions
+24 -4
View File
@@ -1,9 +1,11 @@
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from "vue";
import { ElMessage } from "element-plus";
import { useRoute } from "vue-router";
import apiClient, { getApiErrorMessage } from "@/api/client";
import EmptyState from "@/components/ui/EmptyState.vue";
import MetricCard from "@/components/ui/MetricCard.vue";
import PlatformMark from "@/components/ui/PlatformMark.vue";
import RightDrawer from "@/components/ui/RightDrawer.vue";
import SafeAvatar from "@/components/ui/SafeAvatar.vue";
import StatusBadge from "@/components/ui/StatusBadge.vue";
@@ -25,6 +27,7 @@ import { House, RefreshRight, SwitchButton, VideoCamera } from "@element-plus/ic
const inheritValue = "__inherit__";
const AUTO_REFRESH_INTERVAL_MS = 15000;
const route = useRoute();
const loading = ref(false);
const submitLoading = ref(false);
@@ -687,6 +690,7 @@ function nullableStringFromSelect(value: string | number) {
onMounted(async () => {
await loadRooms();
if (route.query.action === "create") openCreateDialog();
startAutoRefresh();
document.addEventListener("visibilitychange", handleVisibilityChange);
});
@@ -787,7 +791,7 @@ onBeforeUnmount(() => {
:status="row.currentRecordingState"
context="recording"
/>
<StatusBadge :label="row.platformName || '--'" :status="row.platformName || 'unknown'" />
<PlatformMark :name="row.platformName" />
<StatusBadge v-if="row.isPinned" label="置顶" status="completed" size="sm" />
<StatusBadge v-if="row.isPriority" label="重点" status="retrying" size="sm" />
</div>
@@ -892,7 +896,7 @@ onBeforeUnmount(() => {
<div class="cell-title">{{ row.title || row.anchorName || row.roomId }}</div>
<div class="cell-subtitle">{{ row.anchorName || "未知主播" }}</div>
<div class="config-summary">
<span>{{ row.platformName || "--" }}</span>
<PlatformMark :name="row.platformName" />
<span>{{ row.roomId || "--" }}</span>
<span>{{ formatPollingInterval(row.pollingIntervalSecondsOverride) }}</span>
</div>
@@ -1025,7 +1029,7 @@ onBeforeUnmount(() => {
:status="activeRoom.currentRecordingState"
context="recording"
/>
<StatusBadge :label="activeRoom.platformName || '--'" :status="activeRoom.platformName || 'unknown'" />
<PlatformMark :name="activeRoom.platformName" />
</div>
<el-descriptions :column="1" border class="detail-panel__descriptions">
@@ -1033,7 +1037,7 @@ onBeforeUnmount(() => {
{{ activeRoom.title || activeRoom.anchorName || activeRoom.roomId || "--" }}
</el-descriptions-item>
<el-descriptions-item label="平台 + Room ID">
{{ activeRoom.platformName || "--" }} · {{ activeRoom.roomId || "--" }}
<span class="platform-room-id"><PlatformMark :name="activeRoom.platformName" /><span>· {{ activeRoom.roomId || "--" }}</span></span>
</el-descriptions-item>
<el-descriptions-item label="直播状态">
{{ roomAvailabilityLabel(activeRoom) }}
@@ -1453,6 +1457,7 @@ onBeforeUnmount(() => {
</template>
<style scoped>
.platform-room-id { display: inline-flex; align-items: center; gap: 6px; }
.page-stack {
display: grid;
gap: 24px;
@@ -1691,6 +1696,10 @@ onBeforeUnmount(() => {
background: var(--surface-muted);
}
.detail-panel__hero > div {
min-width: 0;
}
.detail-panel__title {
color: var(--text-primary);
font-size: 18px;
@@ -1922,6 +1931,17 @@ onBeforeUnmount(() => {
}
@media (max-width: 640px) {
.detail-panel__hero {
align-items: flex-start;
gap: 12px;
padding: 12px;
}
.detail-panel__descriptions :deep(.el-descriptions__label) {
width: 104px;
min-width: 104px;
}
.header-actions :deep(.el-space__item) {
width: 100%;
}