feat: improve recording automation and task workflows

This commit is contained in:
2026-04-23 23:18:11 +08:00
parent 1c892259a9
commit 23ead56781
88 changed files with 9579 additions and 1581 deletions
+202 -35
View File
@@ -1,22 +1,43 @@
<script setup lang="ts">
import { computed } from "vue";
import { computed, ref, watch } from "vue";
import { useRouter, useRoute } from "vue-router";
import { useAuthStore } from "@/stores/auth";
import { useBackendStatus } from "@/composables/useBackendStatus";
import { useViewport } from "@/composables/useViewport";
import {
House,
VideoCamera,
Tickets,
Setting,
SwitchButton
SwitchButton,
Operation
} from "@element-plus/icons-vue";
const router = useRouter();
const route = useRoute();
const authStore = useAuthStore();
const { isMobile } = useViewport();
const { backendUnavailable, backendMessage } = useBackendStatus();
const mobileNavVisible = ref(false);
const displayName = computed(() => authStore.user?.displayName ?? "Operator");
const menuItems = [
{ index: "/live-rooms", label: "直播间管理", icon: House },
{ index: "/record-tasks", label: "录制任务", icon: VideoCamera },
{ index: "/logs", label: "系统日志", icon: Tickets },
{ index: "/settings", label: "系统设置", icon: Setting }
];
watch(
() => route.path,
() => {
mobileNavVisible.value = false;
}
);
async function handleLogout() {
mobileNavVisible.value = false;
await authStore.logout();
await router.push({ name: "login" });
}
@@ -24,31 +45,19 @@ async function handleLogout() {
<template>
<el-container class="shell">
<el-aside width="240px" class="sidebar">
<el-aside v-if="!isMobile" width="240px" class="sidebar">
<div class="brand">
<div class="brand__mark">LR</div>
<div>
<div class="brand__title">Live Recorder</div>
<div class="brand__subtitle">录制控制台</div>
<div class="brand__subtitle">直播录制控制台</div>
</div>
</div>
<el-menu :default-active="route.path" router class="menu">
<el-menu-item index="/live-rooms">
<el-icon><House /></el-icon>
<span>直播间管理</span>
</el-menu-item>
<el-menu-item index="/record-tasks">
<el-icon><VideoCamera /></el-icon>
<span>录制任务</span>
</el-menu-item>
<el-menu-item index="/logs">
<el-icon><Tickets /></el-icon>
<span>系统日志</span>
</el-menu-item>
<el-menu-item index="/settings">
<el-icon><Setting /></el-icon>
<span>系统设置</span>
<el-menu-item v-for="item in menuItems" :key="item.index" :index="item.index">
<el-icon><component :is="item.icon" /></el-icon>
<span>{{ item.label }}</span>
</el-menu-item>
</el-menu>
@@ -59,15 +68,72 @@ async function handleLogout() {
</div>
</el-aside>
<el-drawer
v-model="mobileNavVisible"
class="mobile-nav-drawer"
direction="ltr"
size="292px"
:with-header="false"
>
<div class="mobile-drawer">
<div class="brand brand--drawer">
<div class="brand__mark">LR</div>
<div>
<div class="brand__title">Live Recorder</div>
<div class="brand__subtitle">直播录制控制台</div>
</div>
</div>
<el-menu :default-active="route.path" router class="menu menu--drawer">
<el-menu-item v-for="item in menuItems" :key="item.index" :index="item.index">
<el-icon><component :is="item.icon" /></el-icon>
<span>{{ item.label }}</span>
</el-menu-item>
</el-menu>
<div class="sidebar-footer sidebar-footer--drawer">
<div class="sidebar-footer__label">当前账户</div>
<div class="sidebar-footer__name">{{ displayName }}</div>
<el-button :icon="SwitchButton" text @click="handleLogout">退出登录</el-button>
</div>
</div>
</el-drawer>
<el-container>
<el-header class="header">
<div>
<div class="header__title">直播录制控制台</div>
<div class="header__subtitle">平台适配巡检录制和结果统一管理</div>
<div class="header__shell">
<div class="header__lead">
<el-button
v-if="isMobile"
class="mobile-nav-trigger"
:icon="Operation"
@click="mobileNavVisible = true"
>
菜单
</el-button>
<div>
<div class="header__title">直播录制控制台</div>
<div class="header__subtitle">平台适配巡检录制和结果统一管理</div>
</div>
</div>
<div v-if="isMobile" class="header__account">
{{ displayName }}
</div>
</div>
</el-header>
<el-main class="main">
<el-alert
v-if="backendUnavailable"
class="backend-alert"
type="error"
:closable="false"
show-icon
title="后端服务不可用"
:description="backendMessage"
/>
<router-view />
</el-main>
</el-container>
@@ -87,6 +153,15 @@ async function handleLogout() {
background: transparent;
}
.mobile-drawer {
display: flex;
min-height: 100%;
flex-direction: column;
padding: 20px 16px 20px 20px;
background:
linear-gradient(180deg, rgba(250, 248, 243, 0.98), rgba(246, 244, 239, 0.98));
}
.brand {
display: flex;
align-items: center;
@@ -96,6 +171,11 @@ async function handleLogout() {
border-bottom: 1px solid rgba(82, 74, 63, 0.08);
}
.brand--drawer {
padding-left: 4px;
padding-right: 4px;
}
.brand__mark {
width: 42px;
height: 42px;
@@ -132,6 +212,10 @@ async function handleLogout() {
box-shadow: 0 14px 28px rgba(31, 24, 18, 0.05);
}
.menu--drawer {
flex: 1;
}
.menu :deep(.el-menu-item) {
height: 46px;
margin-bottom: 6px;
@@ -164,6 +248,10 @@ async function handleLogout() {
box-shadow: 0 12px 24px rgba(31, 24, 18, 0.04);
}
.sidebar-footer--drawer {
margin-top: 18px;
}
.sidebar-footer__label {
font-size: 12px;
letter-spacing: 0.08em;
@@ -179,10 +267,12 @@ async function handleLogout() {
}
.header {
height: auto;
flex: 0 0 auto;
padding: 22px 32px 0;
}
.header > div {
.header__shell {
display: flex;
align-items: center;
justify-content: space-between;
@@ -194,6 +284,17 @@ async function handleLogout() {
box-shadow: 0 16px 34px rgba(31, 24, 18, 0.05);
}
.header__lead {
display: flex;
align-items: center;
gap: 14px;
min-width: 0;
}
.header__lead > div:last-child {
min-width: 0;
}
.header__title {
font-size: 22px;
font-weight: 700;
@@ -207,31 +308,97 @@ async function handleLogout() {
font-size: 13px;
}
.header__account {
flex-shrink: 0;
max-width: 120px;
color: #6f675f;
font-size: 12px;
text-align: right;
word-break: break-word;
}
.mobile-nav-trigger {
flex-shrink: 0;
}
.main {
min-height: 0;
padding: 28px 32px 36px;
}
@media (max-width: 960px) {
.shell {
flex-direction: column;
}
.backend-alert {
margin-bottom: 18px;
border-radius: 14px;
}
.sidebar {
width: 100% !important;
padding: 18px;
}
:deep(.mobile-nav-drawer .el-drawer__body) {
padding: 0;
}
@media (max-width: 768px) {
.header {
padding: 8px 18px 0;
padding: 12px 14px 0;
}
.header > div {
.header__shell {
align-items: center;
gap: 12px;
padding: 14px 16px;
}
.header__lead {
width: 100%;
align-items: flex-start;
flex-direction: column;
justify-content: space-between;
flex-direction: row;
gap: 12px;
}
.header__title {
font-size: 18px;
line-height: 1.12;
}
.header__subtitle {
max-width: 240px;
font-size: 12px;
line-height: 1.45;
}
.header__account {
display: none;
}
.mobile-nav-trigger {
min-width: 84px;
padding-inline: 14px;
}
.main {
padding: 22px 18px 24px;
padding: 18px 14px 24px;
}
.backend-alert {
margin-bottom: 14px;
}
}
@media (max-width: 480px) {
.header__shell {
padding: 12px 14px;
}
.header__title {
font-size: 17px;
}
.header__subtitle {
display: none;
}
.mobile-nav-trigger {
min-width: 72px;
padding-inline: 12px;
}
}
</style>