feat: redesign responsive control room ui
This commit is contained in:
@@ -4,13 +4,21 @@ import { useRouter, useRoute } from "vue-router";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { useBackendStatus } from "@/composables/useBackendStatus";
|
||||
import { useViewport } from "@/composables/useViewport";
|
||||
import { useUiPreferences } from "@/composables/useUiPreferences";
|
||||
import {
|
||||
House,
|
||||
VideoCamera,
|
||||
Document,
|
||||
Tickets,
|
||||
RefreshRight,
|
||||
Setting,
|
||||
SwitchButton,
|
||||
Operation
|
||||
Operation,
|
||||
Fold,
|
||||
Expand,
|
||||
Sunny,
|
||||
Moon,
|
||||
Monitor
|
||||
} from "@element-plus/icons-vue";
|
||||
|
||||
const router = useRouter();
|
||||
@@ -18,19 +26,115 @@ const route = useRoute();
|
||||
const authStore = useAuthStore();
|
||||
const { isMobile } = useViewport();
|
||||
const { backendUnavailable, backendMessage } = useBackendStatus();
|
||||
const { themeMode, density, resolvedTheme, sidebarCollapsed, cycleThemeMode, toggleSidebarCollapsed } =
|
||||
useUiPreferences();
|
||||
|
||||
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 }
|
||||
|
||||
const navigationGroups = [
|
||||
{
|
||||
key: "monitor",
|
||||
title: "直播监控",
|
||||
items: [
|
||||
{ index: "/live-rooms", label: "直播间", icon: House },
|
||||
{ index: "/record-tasks", label: "录制任务", icon: VideoCamera },
|
||||
{ index: "/recovery", label: "恢复中心", icon: RefreshRight }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "review",
|
||||
title: "回顾分析",
|
||||
items: [
|
||||
{ index: "/daily-reviews", label: "回顾日报", icon: Document },
|
||||
{ index: "/logs", label: "系统日志", icon: Tickets }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "system",
|
||||
title: "系统管理",
|
||||
items: [{ index: "/settings", label: "系统设置", icon: Setting }]
|
||||
}
|
||||
];
|
||||
|
||||
const pageMeta = computed(() => {
|
||||
if (route.name === "live-rooms") {
|
||||
return {
|
||||
title: "直播监控控制台",
|
||||
subtitle: "统一管理直播间、自动开录、实时状态和异常处理。"
|
||||
};
|
||||
}
|
||||
|
||||
if (route.name === "record-tasks") {
|
||||
return {
|
||||
title: "录制任务工作区",
|
||||
subtitle: "按会话观察录制进度、分片状态、转码后处理和删除操作。"
|
||||
};
|
||||
}
|
||||
|
||||
if (route.name === "record-session-detail") {
|
||||
return {
|
||||
title: "会话事件时间轴",
|
||||
subtitle: "用整场视角查看分片、脚本、Webhook 与弹幕热度。"
|
||||
};
|
||||
}
|
||||
|
||||
if (route.name === "record-task-detail") {
|
||||
return {
|
||||
title: "分片详情",
|
||||
subtitle: "查看单个分片的产物、状态、输出结果和原始日志。"
|
||||
};
|
||||
}
|
||||
|
||||
if (route.name === "daily-reviews") {
|
||||
return {
|
||||
title: "回顾日报",
|
||||
subtitle: "按天回看录制规模、热点时刻、异常分布与重点会话。"
|
||||
};
|
||||
}
|
||||
|
||||
if (route.name === "logs") {
|
||||
return {
|
||||
title: "系统日志",
|
||||
subtitle: "面向排障的原始事件流,支持快速缩小范围和定位异常。"
|
||||
};
|
||||
}
|
||||
|
||||
if (route.name === "recovery") {
|
||||
return {
|
||||
title: "恢复中心",
|
||||
subtitle: "集中处理未自动拉起的直播间和等待恢复的 MP4 转码任务。"
|
||||
};
|
||||
}
|
||||
|
||||
if (route.name === "settings") {
|
||||
return {
|
||||
title: "系统设置",
|
||||
subtitle: "录制、存储、通知、脚本和平台策略统一在这里收口。"
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
title: "Live Recorder",
|
||||
subtitle: "专业直播录制运维控制台。"
|
||||
};
|
||||
});
|
||||
|
||||
const currentThemeIcon = computed(() => {
|
||||
if (themeMode.value === "light") {
|
||||
return Sunny;
|
||||
}
|
||||
|
||||
if (themeMode.value === "dark") {
|
||||
return Moon;
|
||||
}
|
||||
|
||||
return Monitor;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
() => route.fullPath,
|
||||
() => {
|
||||
mobileNavVisible.value = false;
|
||||
}
|
||||
@@ -44,94 +148,157 @@ async function handleLogout() {
|
||||
</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>
|
||||
<el-container class="app-shell">
|
||||
<el-aside
|
||||
v-if="!isMobile"
|
||||
class="app-sidebar"
|
||||
:class="{ 'app-sidebar--collapsed': sidebarCollapsed }"
|
||||
:width="sidebarCollapsed ? '92px' : '276px'"
|
||||
>
|
||||
<div class="app-brand">
|
||||
<div class="app-brand__mark">LR</div>
|
||||
<div v-if="!sidebarCollapsed" class="app-brand__copy">
|
||||
<div class="app-brand__title">Live Recorder</div>
|
||||
<div class="app-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="app-sidebar__scroll">
|
||||
<section v-for="group in navigationGroups" :key="group.key" class="app-nav-group">
|
||||
<div v-if="!sidebarCollapsed" class="app-nav-group__title">{{ group.title }}</div>
|
||||
<el-menu :default-active="route.path" router class="app-nav-menu" :collapse="sidebarCollapsed">
|
||||
<el-menu-item v-for="item in group.items" :key="item.index" :index="item.index">
|
||||
<el-icon><component :is="item.icon" /></el-icon>
|
||||
<template #title>{{ item.label }}</template>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<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 class="app-sidebar__footer">
|
||||
<div class="app-sidebar__status">
|
||||
<span class="app-status-dot" :class="`app-status-dot--${resolvedTheme}`"></span>
|
||||
<span v-if="!sidebarCollapsed">{{ resolvedTheme === "dark" ? "深色控制台" : "浅色控制台" }}</span>
|
||||
</div>
|
||||
<div v-if="!sidebarCollapsed" class="app-account">
|
||||
<div class="app-account__label">当前账户</div>
|
||||
<div class="app-account__name">{{ displayName }}</div>
|
||||
</div>
|
||||
<el-button
|
||||
class="app-sidebar__logout"
|
||||
:icon="SwitchButton"
|
||||
text
|
||||
@click="handleLogout"
|
||||
>
|
||||
<span v-if="!sidebarCollapsed">退出登录</span>
|
||||
</el-button>
|
||||
</div>
|
||||
</el-aside>
|
||||
|
||||
<el-drawer
|
||||
v-model="mobileNavVisible"
|
||||
class="mobile-nav-drawer"
|
||||
class="app-mobile-drawer"
|
||||
direction="ltr"
|
||||
size="292px"
|
||||
size="88vw"
|
||||
: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 class="app-brand app-brand--drawer">
|
||||
<div class="app-brand__mark">LR</div>
|
||||
<div class="app-brand__copy">
|
||||
<div class="app-brand__title">Live Recorder</div>
|
||||
<div class="app-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="mobile-drawer__controls">
|
||||
<el-select v-model="themeMode" size="small">
|
||||
<el-option label="跟随系统" value="system" />
|
||||
<el-option label="浅色" value="light" />
|
||||
<el-option label="深色" value="dark" />
|
||||
</el-select>
|
||||
<el-select v-model="density" size="small">
|
||||
<el-option label="舒适密度" value="comfortable" />
|
||||
<el-option label="紧凑密度" value="compact" />
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<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 class="app-sidebar__scroll">
|
||||
<section v-for="group in navigationGroups" :key="group.key" class="app-nav-group">
|
||||
<div class="app-nav-group__title">{{ group.title }}</div>
|
||||
<el-menu :default-active="route.path" router class="app-nav-menu app-nav-menu--mobile">
|
||||
<el-menu-item v-for="item in group.items" :key="item.index" :index="item.index">
|
||||
<el-icon><component :is="item.icon" /></el-icon>
|
||||
<template #title>{{ item.label }}</template>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="mobile-drawer__footer">
|
||||
<div class="app-account">
|
||||
<div class="app-account__label">当前账户</div>
|
||||
<div class="app-account__name">{{ displayName }}</div>
|
||||
</div>
|
||||
<el-button class="mobile-drawer__logout" :icon="SwitchButton" @click="handleLogout">退出登录</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-drawer>
|
||||
|
||||
<el-container>
|
||||
<el-header class="header">
|
||||
<div class="header__shell">
|
||||
<div class="header__lead">
|
||||
<el-container class="app-shell__main">
|
||||
<el-header class="app-topbar">
|
||||
<div class="app-topbar__main">
|
||||
<div class="app-topbar__lead">
|
||||
<el-button
|
||||
v-if="isMobile"
|
||||
class="mobile-nav-trigger"
|
||||
class="app-topbar__menu-button"
|
||||
:icon="Operation"
|
||||
@click="mobileNavVisible = true"
|
||||
>
|
||||
菜单
|
||||
</el-button>
|
||||
|
||||
<div>
|
||||
<div class="header__title">直播录制控制台</div>
|
||||
<div class="header__subtitle">平台适配、巡检、录制和结果统一管理</div>
|
||||
/>
|
||||
<el-button
|
||||
v-else
|
||||
class="app-topbar__collapse-button"
|
||||
:icon="sidebarCollapsed ? Expand : Fold"
|
||||
@click="toggleSidebarCollapsed"
|
||||
/>
|
||||
<div class="app-topbar__copy">
|
||||
<div class="app-topbar__eyebrow">Control Room</div>
|
||||
<div class="app-topbar__title">{{ pageMeta.title }}</div>
|
||||
<div class="app-topbar__subtitle">{{ pageMeta.subtitle }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="isMobile" class="header__account">
|
||||
{{ displayName }}
|
||||
<div class="app-topbar__controls desktop-only">
|
||||
<el-select v-model="themeMode" size="small" class="app-topbar__select">
|
||||
<el-option label="跟随系统" value="system" />
|
||||
<el-option label="浅色" value="light" />
|
||||
<el-option label="深色" value="dark" />
|
||||
</el-select>
|
||||
<el-select v-model="density" size="small" class="app-topbar__select">
|
||||
<el-option label="舒适密度" value="comfortable" />
|
||||
<el-option label="紧凑密度" value="compact" />
|
||||
</el-select>
|
||||
<div class="app-topbar__account">
|
||||
<span class="app-topbar__account-label">当前账户</span>
|
||||
<strong>{{ displayName }}</strong>
|
||||
</div>
|
||||
<el-button :icon="SwitchButton" @click="handleLogout">退出</el-button>
|
||||
</div>
|
||||
|
||||
<div class="app-topbar__controls mobile-only">
|
||||
<el-button class="app-topbar__theme-button" :icon="currentThemeIcon" @click="cycleThemeMode" />
|
||||
</div>
|
||||
</div>
|
||||
</el-header>
|
||||
|
||||
<el-main class="main">
|
||||
<el-main class="app-main">
|
||||
<el-alert
|
||||
v-if="backendUnavailable"
|
||||
class="backend-alert"
|
||||
type="error"
|
||||
:closable="false"
|
||||
show-icon
|
||||
title="后端服务不可用"
|
||||
title="后端服务暂时不可用"
|
||||
:description="backendMessage"
|
||||
/>
|
||||
<router-view />
|
||||
@@ -141,264 +308,336 @@ async function handleLogout() {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.shell {
|
||||
.app-shell {
|
||||
min-height: 100vh;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
.app-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px 16px 20px 20px;
|
||||
gap: 18px;
|
||||
padding: 20px 16px;
|
||||
border-right: 1px solid var(--border-subtle);
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.16));
|
||||
backdrop-filter: blur(18px);
|
||||
}
|
||||
|
||||
:global(html[data-theme="dark"]) .app-sidebar {
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0));
|
||||
}
|
||||
|
||||
.app-sidebar--collapsed {
|
||||
padding-inline: 12px;
|
||||
}
|
||||
|
||||
.app-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
min-height: 56px;
|
||||
}
|
||||
|
||||
.app-brand--drawer {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.app-brand__mark {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(180deg, var(--accent), var(--accent-strong));
|
||||
color: #f5fbff;
|
||||
font-size: 16px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.04em;
|
||||
box-shadow: 0 10px 24px rgba(47, 111, 180, 0.22);
|
||||
}
|
||||
|
||||
.app-brand__copy {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.app-brand__title {
|
||||
color: var(--text-primary);
|
||||
font-size: 16px;
|
||||
font-weight: 750;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
|
||||
.app-brand__subtitle {
|
||||
margin-top: 4px;
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.app-sidebar__scroll {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.app-nav-group {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.app-nav-group__title {
|
||||
padding-inline: 10px;
|
||||
color: var(--text-muted);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.app-nav-menu {
|
||||
border-right: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.app-nav-menu :deep(.el-menu-item) {
|
||||
height: 44px;
|
||||
margin-bottom: 4px;
|
||||
border-radius: 8px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.app-nav-menu :deep(.el-menu-item:hover) {
|
||||
background: var(--accent-soft);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.app-nav-menu :deep(.el-menu-item.is-active) {
|
||||
background: linear-gradient(180deg, rgba(47, 111, 180, 0.16), rgba(47, 111, 180, 0.08));
|
||||
color: var(--accent);
|
||||
box-shadow: inset 0 0 0 1px rgba(47, 111, 180, 0.18);
|
||||
}
|
||||
|
||||
.app-sidebar__footer {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
margin-top: auto;
|
||||
padding: 14px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--border-subtle);
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.app-sidebar--collapsed .app-sidebar__footer {
|
||||
padding-inline: 10px;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
.app-sidebar__status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.app-status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.app-status-dot--light {
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.app-status-dot--dark {
|
||||
background: #9ad0ff;
|
||||
}
|
||||
|
||||
.app-account__label {
|
||||
color: var(--text-muted);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.app-account__name {
|
||||
margin-top: 6px;
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.app-sidebar__logout {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.app-sidebar--collapsed .app-sidebar__logout {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.app-shell__main {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.app-topbar {
|
||||
height: auto;
|
||||
padding: 18px var(--content-padding) 0;
|
||||
}
|
||||
|
||||
.app-topbar__main {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
min-height: var(--header-row-height);
|
||||
padding: 18px 20px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--border-subtle);
|
||||
background: rgba(255, 255, 255, 0.56);
|
||||
backdrop-filter: blur(16px);
|
||||
box-shadow: var(--shadow-soft);
|
||||
}
|
||||
|
||||
:global(html[data-theme="dark"]) .app-topbar__main {
|
||||
background: rgba(15, 27, 41, 0.78);
|
||||
}
|
||||
|
||||
.app-topbar__lead {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 14px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.app-topbar__copy {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.app-topbar__eyebrow {
|
||||
margin-bottom: 8px;
|
||||
color: var(--accent);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.app-topbar__title {
|
||||
color: var(--text-primary);
|
||||
font-size: clamp(20px, 2vw, 28px);
|
||||
font-weight: 760;
|
||||
letter-spacing: -0.04em;
|
||||
line-height: 1.05;
|
||||
}
|
||||
|
||||
.app-topbar__subtitle {
|
||||
margin-top: 8px;
|
||||
max-width: 68ch;
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.app-topbar__controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.app-topbar__select {
|
||||
width: 122px;
|
||||
}
|
||||
|
||||
.app-topbar__account {
|
||||
display: grid;
|
||||
gap: 3px;
|
||||
min-width: 96px;
|
||||
text-align: right;
|
||||
color: var(--text-primary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.app-topbar__account-label {
|
||||
color: var(--text-muted);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.app-topbar__collapse-button,
|
||||
.app-topbar__menu-button,
|
||||
.app-topbar__theme-button {
|
||||
width: 40px;
|
||||
min-width: 40px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.app-main {
|
||||
padding: var(--content-padding);
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.backend-alert {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.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));
|
||||
gap: 18px;
|
||||
padding: 20px;
|
||||
background: var(--bg-subtle);
|
||||
}
|
||||
|
||||
.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;
|
||||
.mobile-drawer__controls {
|
||||
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);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.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;
|
||||
.mobile-drawer__footer {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
min-width: 0;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.header__lead > div:last-child {
|
||||
min-width: 0;
|
||||
.mobile-drawer__logout {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.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) {
|
||||
:deep(.app-mobile-drawer .el-drawer__body) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.header {
|
||||
padding: 12px 14px 0;
|
||||
@media (max-width: 1023px) {
|
||||
.app-topbar {
|
||||
padding: 14px var(--content-padding-mobile) 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;
|
||||
.app-main {
|
||||
padding: var(--content-padding-mobile);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.header__shell {
|
||||
padding: 12px 14px;
|
||||
@media (max-width: 767px) {
|
||||
.app-topbar__main {
|
||||
padding: 14px 14px 16px;
|
||||
}
|
||||
|
||||
.header__title {
|
||||
font-size: 17px;
|
||||
.app-topbar__title {
|
||||
font-size: clamp(18px, 6vw, 24px);
|
||||
}
|
||||
|
||||
.header__subtitle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile-nav-trigger {
|
||||
min-width: 72px;
|
||||
padding-inline: 12px;
|
||||
.app-topbar__subtitle {
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user