PayrollManagementSystem/frontend/src/components/layout/BasicLayout.vue
liuwenxin d7fbec1177 feat(system): 登录页重做、布局白化与首页 Bento 仪表盘(视觉体系统一落地)
- 登录页:雾灰点阵底 + 右上涟漪同心圆 + 顶部靛蓝天光;居中纸白卡片(品牌区/欢迎回来/表单);逻辑不变
- BasicLayout:侧栏深色面板改为中性灰微渐变 + 白底浮起当前项(左 2px 靛蓝条);logo 区水线;内容区雾灰点阵
- 首页:Bento Grid 仪表盘(参数 16 项/岗位 3 个为真实数据;绩效/回款/待办为业务上线前的精致空态)
- AppMenu:激活项白底浮起样式,菜单变量随浅色侧栏调整

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-13 00:09:41 +08:00

237 lines
5.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import AppMenu from '@/components/layout/AppMenu.vue'
import { useUserStore } from '@/store/user'
/** 基础布局:左侧品牌导航(窄屏收为抽屉)+ 顶栏(面包屑/用户/退出)+ 内容区。 */
const route = useRoute()
const router = useRouter()
const userStore = useUserStore()
const drawerVisible = ref(false)
/** 面包屑:首页 + 分组(如有)+ 当前页标题(首页自身不重复) */
const breadcrumbs = computed<string[]>(() => {
const items: string[] = []
if (route.meta.group) items.push(route.meta.group)
if (route.meta.title && route.path !== '/dashboard') items.push(route.meta.title)
return items
})
// 移动端在抽屉里点完菜单即收起(路由变化意味着导航已发生)
watch(
() => route.path,
() => {
drawerVisible.value = false
},
)
async function handleLogout(): Promise<void> {
await userStore.logout()
ElMessage.success('已退出登录')
await router.replace('/login')
}
</script>
<template>
<el-container class="app-layout">
<el-aside class="app-aside" width="220px">
<div class="app-brand">
<span class="app-brand__name">衍水集团</span>
<span class="app-brand__sub">营销人员工资管理系统</span>
<span class="app-brand__line"></span>
</div>
<AppMenu class="app-aside__menu" />
</el-aside>
<el-container class="app-body">
<el-header class="app-header">
<div class="app-header__left">
<button
type="button"
class="app-header__menu-toggle"
aria-label="打开导航菜单"
@click="drawerVisible = true"
>
<svg viewBox="0 0 20 20" width="18" height="18" aria-hidden="true">
<path
d="M3 5h14M3 10h14M3 15h14"
stroke="currentColor"
stroke-width="1.6"
stroke-linecap="round"
/>
</svg>
</button>
<el-breadcrumb separator="/">
<el-breadcrumb-item :to="{ path: '/dashboard' }">首页</el-breadcrumb-item>
<el-breadcrumb-item v-for="item in breadcrumbs" :key="item">
{{ item }}
</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="app-header__user">
<el-tag v-for="role in userStore.roles" :key="role" size="small" type="info">
{{ role }}
</el-tag>
<span class="app-header__username">{{ userStore.displayName }}</span>
<el-button link type="primary" @click="handleLogout">退出登录</el-button>
</div>
</el-header>
<el-main class="app-main">
<router-view />
</el-main>
</el-container>
<el-drawer
v-model="drawerVisible"
class="app-drawer"
direction="ltr"
size="240px"
:with-header="false"
>
<div class="app-brand">
<span class="app-brand__name">衍水集团</span>
<span class="app-brand__sub">营销人员工资管理系统</span>
<span class="app-brand__line"></span>
</div>
<AppMenu />
</el-drawer>
</el-container>
</template>
<style scoped>
.app-layout {
height: 100%;
}
.app-aside {
display: flex;
flex-direction: column;
/* 中性灰侧栏:比页面底沉一档,与纸白卡片拉开层次 */
background: linear-gradient(180deg, #f2f4f8 0%, #ecedf3 100%);
border-right: 1px solid var(--ys-line);
}
.app-brand {
display: flex;
flex-direction: column;
gap: 3px;
padding: 18px 20px 12px;
}
.app-brand__name {
font-family: var(--ys-font-brand);
font-size: 16px;
font-weight: 500;
letter-spacing: 4px;
color: var(--ys-ink);
}
.app-brand__sub {
font-size: 11px;
letter-spacing: 0.06em;
color: var(--ys-ink-3);
}
/* 品牌签名:logo 下的渐隐水线(全站唯一装饰元素,页面标题处同款) */
.app-brand__line {
width: 24px;
height: 2px;
border-radius: 1px;
margin-top: 8px;
background: linear-gradient(90deg, var(--ys-indigo), rgba(46, 75, 168, 0));
}
.app-aside__menu {
flex: 1;
overflow-y: auto;
}
.app-header {
display: flex;
align-items: center;
justify-content: space-between;
height: 56px;
background: linear-gradient(180deg, #ffffff, #fdfdfe);
border-bottom: 1px solid var(--ys-line);
}
.app-header__left {
display: flex;
align-items: center;
gap: 12px;
}
/* 窄屏才出现的汉堡按钮;不引入图标库,用内联 SVG */
.app-header__menu-toggle {
display: none;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
padding: 0;
color: var(--ys-text);
cursor: pointer;
background: none;
border: none;
border-radius: 6px;
}
.app-header__menu-toggle:hover {
background-color: #f2f3f5;
}
.app-header__menu-toggle:focus-visible {
outline: 2px solid var(--ys-indigo);
outline-offset: 2px;
}
.app-header__user {
display: flex;
align-items: center;
gap: 10px;
}
.app-header__username {
font-size: 14px;
color: var(--ys-text);
}
.app-main {
padding: 20px;
/* 雾灰底 + 点阵网格:内容区的秩序与密度 */
background-color: var(--ys-mist);
background-image: radial-gradient(rgba(28, 35, 51, 0.05) 1px, transparent 1px);
background-size: 24px 24px;
}
@media (max-width: 768px) {
.app-aside {
display: none;
}
.app-header__menu-toggle {
display: inline-flex;
}
}
@media (prefers-reduced-motion: reduce) {
.app-header__menu-toggle {
transition: none;
}
}
</style>
<style>
/* 抽屉内容铺满侧栏同款灰(非 scoped:目标元素由 el-drawer 渲染在组件根之外) */
.app-drawer .el-drawer__body {
padding: 0;
background: linear-gradient(180deg, #f2f4f8 0%, #ecedf3 100%);
}
</style>