feat: improve layout shell, navigation composables and add UI assets
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -1,18 +1,21 @@
|
||||
<template>
|
||||
<aside class="rail" aria-label="主导航">
|
||||
<div class="rail-brand">
|
||||
<div class="brand-mark">星</div>
|
||||
<div class="brand-copy">
|
||||
<strong>星海科技</strong>
|
||||
<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>
|
||||
<button class="brand-toggle" type="button" aria-label="打开合规对话" @click="emit('openChat')">
|
||||
<i class="pi pi-angle-left"></i>
|
||||
<strong class="brand-name">星海科技</strong>
|
||||
<button class="brand-toggle" type="button" aria-label="打开 AI 助手" @click="emit('openChat')">
|
||||
<i class="pi pi-angle-double-left"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<nav class="rail-nav">
|
||||
<nav class="rail-nav" aria-label="功能导航">
|
||||
<button
|
||||
v-for="item in navItems"
|
||||
v-for="item in decoratedNavItems"
|
||||
:key="item.id"
|
||||
class="nav-btn"
|
||||
:class="{ active: activeView === item.id }"
|
||||
@@ -20,30 +23,47 @@
|
||||
@click="emit('navigate', item.id)"
|
||||
>
|
||||
<span class="nav-icon" v-html="item.icon"></span>
|
||||
<span class="nav-copy">
|
||||
<strong>{{ item.label }}</strong>
|
||||
</span>
|
||||
<span class="nav-label">{{ item.displayLabel }}</span>
|
||||
<span v-if="item.badge" class="nav-badge">{{ item.badge }}</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div class="rail-user">
|
||||
<div class="user-avatar">张</div>
|
||||
<div class="user-copy">
|
||||
<button class="rail-user" type="button" aria-label="打开用户菜单">
|
||||
<span class="user-avatar">张</span>
|
||||
<span class="user-copy">
|
||||
<strong>张晓明</strong>
|
||||
<span>财务管理员</span>
|
||||
</div>
|
||||
</span>
|
||||
<i class="pi pi-angle-down"></i>
|
||||
</div>
|
||||
</button>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
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: '差旅申请/报销' },
|
||||
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>
|
||||
@@ -53,120 +73,117 @@ const emit = defineEmits(['navigate', 'openChat'])
|
||||
height: 100dvh;
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
gap: 0;
|
||||
padding: 0;
|
||||
background: #fff;
|
||||
border-right: 1px solid var(--line);
|
||||
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,
|
||||
.rail-user {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.rail-brand {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
min-height: 86px;
|
||||
display: grid;
|
||||
grid-template-columns: 32px minmax(0, 1fr) 28px;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 22px 20px 18px;
|
||||
}
|
||||
|
||||
.brand-mark,
|
||||
.user-avatar {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
.brand-mark {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 6px;
|
||||
background: var(--primary);
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #07936f;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 999px;
|
||||
background: #e2f6ef;
|
||||
color: #047857;
|
||||
.brand-mark svg {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.brand-copy,
|
||||
.user-copy,
|
||||
.nav-copy {
|
||||
.brand-name {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.brand-copy strong,
|
||||
.user-copy strong,
|
||||
.nav-copy strong {
|
||||
color: var(--ink);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.user-copy span,
|
||||
.nav-copy small {
|
||||
margin-top: 2px;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
line-height: 1.3;
|
||||
color: #0f172a;
|
||||
font-size: 16px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.brand-toggle {
|
||||
width: 28px;
|
||||
min-height: 28px;
|
||||
height: 28px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
border-radius: 7px;
|
||||
background: transparent;
|
||||
color: #94a3b8;
|
||||
color: #718096;
|
||||
transition: background 180ms var(--ease), color 180ms var(--ease);
|
||||
}
|
||||
|
||||
.brand-toggle:hover {
|
||||
background: #eef7f4;
|
||||
color: #07936f;
|
||||
}
|
||||
|
||||
.rail-nav {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
align-content: start;
|
||||
padding: 16px 12px;
|
||||
gap: 14px;
|
||||
padding: 16px 20px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.nav-btn {
|
||||
width: 100%;
|
||||
min-height: 42px;
|
||||
min-height: 48px;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-columns: 28px minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 6px;
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
color: #475569;
|
||||
color: #64748b;
|
||||
text-align: left;
|
||||
transition: background 160ms var(--ease), color 160ms var(--ease);
|
||||
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:hover,
|
||||
.nav-btn.active {
|
||||
background: rgba(16,185,129,.10);
|
||||
color: var(--primary);
|
||||
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: 20px;
|
||||
height: 20px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 7px;
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
.nav-btn :deep(svg) {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
width: 19px;
|
||||
height: 19px;
|
||||
stroke: currentColor;
|
||||
stroke-width: 2;
|
||||
fill: none;
|
||||
@@ -174,43 +191,130 @@ const emit = defineEmits(['navigate', 'openChat'])
|
||||
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 {
|
||||
padding: 16px;
|
||||
border-top: 1px solid var(--line);
|
||||
background: #fff;
|
||||
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 .pi {
|
||||
color: var(--muted);
|
||||
justify-self: end;
|
||||
color: #718096;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.rail {
|
||||
position: relative;
|
||||
height: auto;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.rail-nav {
|
||||
grid-auto-flow: row;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.rail {
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid var(--line);
|
||||
padding: 14px;
|
||||
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;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
.nav-btn {
|
||||
min-width: 190px;
|
||||
min-width: 148px;
|
||||
}
|
||||
|
||||
.rail-user {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user