405 lines
8.6 KiB
Vue
405 lines
8.6 KiB
Vue
<script setup lang="ts">
|
|
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,
|
|
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" });
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<el-container class="shell">
|
|
<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>
|
|
</div>
|
|
|
|
<el-menu :default-active="route.path" router class="menu">
|
|
<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">
|
|
<div class="sidebar-footer__label">当前账户</div>
|
|
<div class="sidebar-footer__name">{{ displayName }}</div>
|
|
<el-button :icon="SwitchButton" text @click="handleLogout">退出登录</el-button>
|
|
</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 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>
|
|
</el-container>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.shell {
|
|
min-height: 100vh;
|
|
background: transparent;
|
|
}
|
|
|
|
.sidebar {
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 20px 16px 20px 20px;
|
|
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;
|
|
gap: 12px;
|
|
margin-bottom: 20px;
|
|
padding: 8px 10px 16px;
|
|
border-bottom: 1px solid rgba(82, 74, 63, 0.08);
|
|
}
|
|
|
|
.brand--drawer {
|
|
padding-left: 4px;
|
|
padding-right: 4px;
|
|
}
|
|
|
|
.brand__mark {
|
|
width: 42px;
|
|
height: 42px;
|
|
display: grid;
|
|
place-items: center;
|
|
border-radius: 12px;
|
|
background:
|
|
linear-gradient(135deg, rgba(62, 95, 124, 0.96), rgba(47, 76, 102, 0.96));
|
|
color: #f8fcfb;
|
|
font-weight: 800;
|
|
letter-spacing: 0.02em;
|
|
box-shadow: 0 18px 30px rgba(62, 95, 124, 0.22);
|
|
}
|
|
|
|
.brand__title {
|
|
font-size: 17px;
|
|
font-weight: 700;
|
|
letter-spacing: -0.02em;
|
|
color: #171614;
|
|
}
|
|
|
|
.brand__subtitle {
|
|
margin-top: 4px;
|
|
font-size: 12px;
|
|
color: #8a8379;
|
|
}
|
|
|
|
.menu {
|
|
padding: 8px;
|
|
border-radius: 18px;
|
|
border-right: none;
|
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.74), rgba(251, 248, 243, 0.9));
|
|
border: 1px solid rgba(82, 74, 63, 0.08);
|
|
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;
|
|
border-radius: 12px;
|
|
color: #504941;
|
|
transition:
|
|
transform 0.18s ease,
|
|
background-color 0.18s ease,
|
|
color 0.18s ease,
|
|
box-shadow 0.18s ease;
|
|
}
|
|
|
|
.menu :deep(.el-menu-item:hover) {
|
|
background: rgba(62, 95, 124, 0.08);
|
|
transform: translateX(2px);
|
|
}
|
|
|
|
.menu :deep(.el-menu-item.is-active) {
|
|
background: linear-gradient(135deg, #4a6984, #2f4c66);
|
|
color: #f8fcfb;
|
|
box-shadow: 0 12px 22px rgba(62, 95, 124, 0.22);
|
|
}
|
|
|
|
.sidebar-footer {
|
|
margin-top: auto;
|
|
padding: 18px 14px 6px;
|
|
border-radius: 18px;
|
|
border: 1px solid rgba(82, 74, 63, 0.08);
|
|
background: rgba(255, 255, 255, 0.66);
|
|
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;
|
|
text-transform: uppercase;
|
|
color: #91897f;
|
|
}
|
|
|
|
.sidebar-footer__name {
|
|
margin: 10px 0 8px;
|
|
font-size: 15px;
|
|
font-weight: 700;
|
|
color: #171614;
|
|
}
|
|
|
|
.header {
|
|
height: auto;
|
|
flex: 0 0 auto;
|
|
padding: 22px 32px 0;
|
|
}
|
|
|
|
.header__shell {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 20px;
|
|
padding: 18px 22px;
|
|
border-radius: 14px;
|
|
border: 1px solid rgba(82, 74, 63, 0.08);
|
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.78), rgba(251, 248, 243, 0.86));
|
|
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;
|
|
letter-spacing: -0.03em;
|
|
color: #171614;
|
|
}
|
|
|
|
.header__subtitle {
|
|
margin-top: 4px;
|
|
color: #716b63;
|
|
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;
|
|
}
|
|
|
|
.backend-alert {
|
|
margin-bottom: 18px;
|
|
border-radius: 14px;
|
|
}
|
|
|
|
:deep(.mobile-nav-drawer .el-drawer__body) {
|
|
padding: 0;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.header {
|
|
padding: 12px 14px 0;
|
|
}
|
|
|
|
.header__shell {
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 14px 16px;
|
|
}
|
|
|
|
.header__lead {
|
|
width: 100%;
|
|
align-items: flex-start;
|
|
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: 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>
|