Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
322 lines
6.5 KiB
Vue
322 lines
6.5 KiB
Vue
<template>
|
|
<aside class="rail" aria-label="主导航">
|
|
<div class="rail-brand">
|
|
<div class="brand-mark" aria-hidden="true">
|
|
<svg viewBox="0 0 36 36">
|
|
<path d="M19.8 4.5c5.7 1.1 9.9 5.7 10.5 11.6-2.8-.9-5.5-.7-7.9.6-2.8 1.5-4.5 4.3-5.2 8.2-4.4-2.8-6.5-6.5-6.3-11.1.2-4.2 3.5-7.8 8.9-9.3Z" />
|
|
<path d="M9 7.6c-3 3.5-4 7.3-2.9 11.2 1.2 4.2 4.6 7 10.1 8.5-2 1.8-4.6 2.6-7.6 2.3C5.1 26.7 3.5 23.1 3.7 19 4 14.4 5.7 10.6 9 7.6Z" />
|
|
</svg>
|
|
</div>
|
|
<strong class="brand-name">星海科技</strong>
|
|
<button class="brand-toggle" type="button" aria-label="打开 AI 助手" @click="emit('openChat')">
|
|
<i class="mdi mdi-chevron-double-left"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<nav class="rail-nav" aria-label="功能导航">
|
|
<button
|
|
v-for="item in decoratedNavItems"
|
|
:key="item.id"
|
|
class="nav-btn"
|
|
:class="{ active: activeView === item.id }"
|
|
type="button"
|
|
@click="emit('navigate', item.id)"
|
|
>
|
|
<span class="nav-icon" v-html="item.icon"></span>
|
|
<span class="nav-label">{{ item.displayLabel }}</span>
|
|
<span v-if="item.badge" class="nav-badge">{{ item.badge }}</span>
|
|
</button>
|
|
</nav>
|
|
|
|
<button class="rail-user" type="button" aria-label="打开用户菜单">
|
|
<span class="user-avatar">张</span>
|
|
<span class="user-copy">
|
|
<strong>张晓明</strong>
|
|
<span>财务管理员</span>
|
|
</span>
|
|
<i class="mdi mdi-chevron-down"></i>
|
|
</button>
|
|
</aside>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed } from 'vue'
|
|
|
|
const props = defineProps({
|
|
navItems: { type: Array, required: true },
|
|
activeView: { type: String, required: true }
|
|
})
|
|
|
|
const emit = defineEmits(['navigate', 'openChat'])
|
|
|
|
const sidebarMeta = {
|
|
overview: { label: '总览' },
|
|
requests: { label: '差旅申请/报销' },
|
|
approval: { label: '审批中心', badge: '12' },
|
|
chat: { label: 'AI助手' },
|
|
policies: { label: '知识管理' },
|
|
audit: { label: '审计追踪' }
|
|
}
|
|
|
|
const decoratedNavItems = computed(() =>
|
|
props.navItems.map((item) => ({
|
|
...item,
|
|
displayLabel: sidebarMeta[item.id]?.label ?? item.label,
|
|
badge: sidebarMeta[item.id]?.badge
|
|
}))
|
|
)
|
|
</script>
|
|
|
|
<style scoped>
|
|
.rail {
|
|
position: sticky;
|
|
top: 0;
|
|
height: 100dvh;
|
|
display: grid;
|
|
grid-template-rows: auto 1fr auto;
|
|
background:
|
|
linear-gradient(180deg, rgba(255,255,255,.98), rgba(248,251,250,.96)),
|
|
#fff;
|
|
border-right: 1px solid #dbe4ee;
|
|
box-shadow: 1px 0 0 rgba(15,23,42,.02);
|
|
z-index: 20;
|
|
}
|
|
|
|
.rail-brand {
|
|
min-height: 86px;
|
|
display: grid;
|
|
grid-template-columns: 32px minmax(0, 1fr) 28px;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 22px 20px 18px;
|
|
}
|
|
|
|
.brand-mark {
|
|
width: 30px;
|
|
height: 30px;
|
|
display: grid;
|
|
place-items: center;
|
|
color: #07936f;
|
|
}
|
|
|
|
.brand-mark svg {
|
|
width: 30px;
|
|
height: 30px;
|
|
fill: currentColor;
|
|
}
|
|
|
|
.brand-name {
|
|
min-width: 0;
|
|
color: #0f172a;
|
|
font-size: 16px;
|
|
font-weight: 800;
|
|
letter-spacing: 0;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.brand-toggle {
|
|
width: 28px;
|
|
height: 28px;
|
|
display: grid;
|
|
place-items: center;
|
|
border: 0;
|
|
border-radius: 7px;
|
|
background: transparent;
|
|
color: #718096;
|
|
transition: background 180ms var(--ease), color 180ms var(--ease);
|
|
}
|
|
|
|
.brand-toggle:hover {
|
|
background: #eef7f4;
|
|
color: #07936f;
|
|
}
|
|
|
|
.rail-nav {
|
|
display: grid;
|
|
align-content: start;
|
|
gap: 14px;
|
|
padding: 16px 20px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.nav-btn {
|
|
width: 100%;
|
|
min-height: 48px;
|
|
display: grid;
|
|
grid-template-columns: 28px minmax(0, 1fr) auto;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 0 12px;
|
|
border: 1px solid transparent;
|
|
border-radius: 8px;
|
|
background: transparent;
|
|
color: #64748b;
|
|
text-align: left;
|
|
transition:
|
|
background 180ms var(--ease),
|
|
border-color 180ms var(--ease),
|
|
color 180ms var(--ease),
|
|
box-shadow 180ms var(--ease);
|
|
}
|
|
|
|
.nav-btn:hover {
|
|
background: rgba(16,185,129,.07);
|
|
color: #0f9f78;
|
|
}
|
|
|
|
.nav-btn.active {
|
|
background: linear-gradient(90deg, rgba(16,185,129,.16), rgba(16,185,129,.08));
|
|
border-color: rgba(16,185,129,.10);
|
|
color: #059669;
|
|
box-shadow: inset 3px 0 0 #10b981;
|
|
}
|
|
|
|
.nav-icon {
|
|
width: 28px;
|
|
height: 28px;
|
|
display: grid;
|
|
place-items: center;
|
|
border-radius: 7px;
|
|
color: currentColor;
|
|
}
|
|
|
|
.nav-btn :deep(svg) {
|
|
width: 19px;
|
|
height: 19px;
|
|
stroke: currentColor;
|
|
stroke-width: 2;
|
|
fill: none;
|
|
stroke-linecap: round;
|
|
stroke-linejoin: round;
|
|
}
|
|
|
|
.nav-label {
|
|
min-width: 0;
|
|
color: currentColor;
|
|
font-size: 14px;
|
|
font-weight: 750;
|
|
line-height: 1.2;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.nav-badge {
|
|
min-width: 34px;
|
|
height: 22px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 0 8px;
|
|
border-radius: 999px;
|
|
background: #ff5b67;
|
|
color: #fff;
|
|
font-size: 12px;
|
|
font-weight: 800;
|
|
line-height: 1;
|
|
}
|
|
|
|
.rail-user {
|
|
min-width: 0;
|
|
min-height: 74px;
|
|
display: grid;
|
|
grid-template-columns: 38px minmax(0, 1fr) 22px;
|
|
align-items: center;
|
|
gap: 10px;
|
|
margin: 0;
|
|
padding: 16px 20px 18px;
|
|
border: 0;
|
|
border-top: 1px solid transparent;
|
|
background: transparent;
|
|
color: #64748b;
|
|
text-align: left;
|
|
transition: background 180ms var(--ease), border-color 180ms var(--ease);
|
|
}
|
|
|
|
.rail-user:hover {
|
|
border-top-color: #e2e8f0;
|
|
background: rgba(255,255,255,.72);
|
|
}
|
|
|
|
.user-avatar {
|
|
width: 36px;
|
|
height: 36px;
|
|
display: grid;
|
|
place-items: center;
|
|
border: 2px solid #fff;
|
|
border-radius: 999px;
|
|
background: linear-gradient(135deg, #0f9f78, #65d6b4);
|
|
box-shadow: 0 6px 14px rgba(15,159,120,.18);
|
|
color: #fff;
|
|
font-size: 14px;
|
|
font-weight: 800;
|
|
}
|
|
|
|
.user-copy {
|
|
min-width: 0;
|
|
display: grid;
|
|
gap: 2px;
|
|
}
|
|
|
|
.user-copy strong {
|
|
color: #334155;
|
|
font-size: 14px;
|
|
font-weight: 750;
|
|
line-height: 1.2;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.user-copy span {
|
|
color: #64748b;
|
|
font-size: 12px;
|
|
line-height: 1.2;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.rail-user .mdi {
|
|
justify-self: end;
|
|
color: #718096;
|
|
font-size: 13px;
|
|
}
|
|
|
|
@media (max-width: 980px) {
|
|
.rail {
|
|
position: relative;
|
|
height: auto;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 760px) {
|
|
.rail {
|
|
border-right: 0;
|
|
border-bottom: 1px solid #dbe4ee;
|
|
}
|
|
|
|
.rail-brand {
|
|
min-height: 68px;
|
|
padding: 16px;
|
|
}
|
|
|
|
.rail-nav {
|
|
display: flex;
|
|
gap: 10px;
|
|
padding: 8px 16px 16px;
|
|
overflow-x: auto;
|
|
}
|
|
|
|
.nav-btn {
|
|
min-width: 148px;
|
|
}
|
|
|
|
.rail-user {
|
|
display: none;
|
|
}
|
|
}
|
|
</style>
|