style(ui): optimize sidebar density and enhance dark mode contrast for tab selectors

This commit is contained in:
digua authored and digua committed 2026-05-26 23:19:47 +08:00
1 parent 3565cad5a9
commit 8a54568cd4
5 files changed
+74 -51

No files matched your search

@@ -153,7 +153,7 @@ onUnmounted(() => {
>
<!-- 滑动选中焦点层:基于 CSS Transform 平滑移动 -->
<div
class="absolute left-0 inset-y-0 -z-10 rounded-full bg-white ring-[1.5px] ring-primary-500 shadow-sm transition-all duration-350 ease-out dark:bg-primary-500/15 dark:ring-primary-400"
class="absolute left-0 inset-y-0 -z-10 rounded-full bg-white ring-[1.5px] ring-primary-500 shadow-sm transition-all duration-350 ease-out dark:bg-primary-700/15 dark:ring-primary-500"
:style="ringStyle"
></div>
@@ -168,7 +168,7 @@ onUnmounted(() => {
class="relative z-10 flex h-[32px] items-center justify-center whitespace-nowrap rounded-full px-4 text-[13px] font-medium transition-colors duration-200"
:class="[
selectedId === assistant.id
? 'text-primary-600 dark:text-primary-300'
? 'text-primary-600 dark:text-primary-400'
: 'text-gray-500 hover:bg-gray-200/50 hover:text-gray-800 dark:text-gray-400 dark:hover:bg-gray-700/50 dark:hover:text-gray-200',
]"
@click="emit('select', assistant.id)"
+60 -40
View File
@@ -187,16 +187,16 @@ function isPrivateChat(session: AnalysisSession): boolean {
return session.type === 'private'
}
// 获取会话头像显示文字:私聊取最后两字,群聊取前两字
// 获取会话头像显示文字:私聊取最后一字,群聊取前一字
function getSessionAvatarText(session: AnalysisSession): string {
const name = session.name || ''
if (!name) return '?'
if (isPrivateChat(session)) {
// 私聊:取最后两个字
return name.length <= 2 ? name : name.slice(-2)
// 私聊:取最后一个字
return name.slice(-1)
} else {
// 群聊:取前两个字
return name.length <= 2 ? name : name.slice(0, 2)
// 群聊:取第一个字
return name.slice(0, 1)
}
}
@@ -207,6 +207,32 @@ function getSessionAvatar(session: AnalysisSession): string | null {
}
return session.groupAvatar || null
}
// 根据会话 ID 生成雅致的莫兰迪色系头像背景和文字颜色(中文注释)
function getAvatarColorClass(session: AnalysisSession, isActive: boolean) {
if (isActive) {
return isPrivateChat(session)
? 'bg-pink-500 text-white dark:bg-pink-600'
: 'bg-primary-600 text-white dark:bg-primary-500'
}
// 雅致的低饱和度配色方案,提升侧边栏的简洁感与品质感(中文注释)
const palettes = [
{ bg: 'bg-pink-50 dark:bg-pink-950/20', text: 'text-pink-600 dark:text-pink-400' },
{ bg: 'bg-blue-50 dark:bg-blue-950/20', text: 'text-blue-600 dark:text-blue-400' },
{ bg: 'bg-emerald-50 dark:bg-emerald-950/20', text: 'text-emerald-600 dark:text-emerald-400' },
{ bg: 'bg-purple-50 dark:bg-purple-950/20', text: 'text-purple-600 dark:text-purple-400' },
{ bg: 'bg-amber-50 dark:bg-amber-950/20', text: 'text-amber-600 dark:text-amber-400' },
]
const idStr = session.id || ''
let hash = 0
for (let i = 0; i < idStr.length; i++) {
hash = idStr.charCodeAt(i) + ((hash << 5) - hash)
}
const index = Math.abs(hash) % palettes.length
return `${palettes[index].bg} ${palettes[index].text}`
}
</script>
<template>
@@ -228,7 +254,8 @@ function getSessionAvatar(session: AnalysisSession): string | null {
</div>
<span class="ml-2 text-xs text-gray-400">v{{ version }}</span>
</div>
<div v-else
<div
v-else
class="group relative flex h-12 w-12 cursor-pointer items-center justify-center rounded-full hover:bg-gray-200/60 dark:hover:bg-gray-800"
style="-webkit-app-region: no-drag"
@click="toggleSidebar"
@@ -314,7 +341,7 @@ function getSessionAvatar(session: AnalysisSession): string | null {
{{ t('layout.noSearchResult') }}
</div>
<div class="space-y-1 pb-8" :class="[isCollapsed ? '' : 'px-4']">
<div class="space-y-1 pb-8" :class="[isCollapsed ? 'px-3' : 'px-4']">
<UContextMenu
v-for="session in filteredSortedSessions"
:key="session.id"
@@ -323,66 +350,59 @@ function getSessionAvatar(session: AnalysisSession): string | null {
<!-- 侧边栏折叠时,hover 显示完整会话名称(Tooltip 需绑定到真实 DOM) -->
<UTooltip :text="session.name" :disabled="!isCollapsed || !session.name" :popper="{ placement: 'right' }">
<div
class="group relative flex items-center p-2 text-left transition-colors"
class="group relative flex items-center text-left transition-all duration-200 cursor-pointer"
:class="[
route.params.id === session.id && !isCollapsed
? 'bg-primary-100 text-gray-900 dark:bg-primary-900/30 dark:text-primary-100'
: 'text-gray-700 dark:text-gray-200 hover:bg-gray-200/60 dark:hover:bg-gray-800',
isCollapsed
? 'justify-center cursor-pointer h-13 w-13 rounded-full ml-3.5'
: 'cursor-pointer w-full rounded-full',
route.params.id === session.id
? 'bg-gray-200/50 dark:bg-gray-800/80 text-gray-900 dark:text-white font-medium'
: 'text-gray-600 dark:text-gray-300 hover:bg-gray-200/30 dark:hover:bg-gray-800/30',
isCollapsed ? 'justify-center h-10 w-10 rounded-xl mx-auto' : 'w-full rounded-xl p-1.5 px-2.5 pl-1.5',
]"
@click="
router.push({ name: getSessionRouteName(session), params: { id: session.id }, query: route.query })
"
>
<!-- 激活指示器:在展开和折叠下,都优雅地贴在侧边栏最左侧边缘 -->
<div
class="absolute top-1/2 -translate-y-1/2 w-[3px] rounded-r-full bg-pink-500 dark:bg-pink-400 transition-all duration-200"
:class="[
isCollapsed ? '-left-3' : '-left-4',
route.params.id === session.id
? 'h-4.5 opacity-100'
: 'h-0 opacity-0 group-hover:h-2.5 group-hover:opacity-40',
]"
/>
<!-- 会话头像 -->
<!-- 有头像图片时显示图片 -->
<img
v-if="getSessionAvatar(session)"
:src="getSessionAvatar(session)!"
:alt="session.name"
class="h-9 w-9 min-w-9 shrink-0 rounded-full object-cover"
:class="[isCollapsed ? '' : 'mr-3']"
class="h-7 w-7 min-w-7 shrink-0 rounded-lg object-cover"
:class="[isCollapsed ? '' : 'mr-2.5']"
/>
<!-- 无头像时显示图标/文字 -->
<!-- 无头像时显示精致的首字母/缩写字头像 -->
<div
v-else
class="flex h-9 w-9 shrink-0 items-center justify-center rounded-full text-[10px] font-bold"
:class="[
route.params.id === session.id
? isPrivateChat(session)
? 'bg-pink-600 text-white dark:bg-pink-500 dark:text-white'
: 'bg-primary-600 text-white dark:bg-primary-500 dark:text-white'
: 'bg-gray-400 text-white dark:bg-gray-600 dark:text-white',
isCollapsed ? '' : 'mr-3',
]"
class="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg text-xs font-semibold select-none"
:class="[getAvatarColorClass(session, route.params.id === session.id), isCollapsed ? '' : 'mr-2.5']"
>
<!-- 折叠时显示缩略名字,不折叠时显示图标 -->
<template v-if="isCollapsed">
{{ getSessionAvatarText(session) }}
</template>
<template v-else>
<UIcon
:name="isPrivateChat(session) ? 'i-heroicons-user' : 'i-heroicons-chat-bubble-left-right'"
class="h-4 w-4"
/>
</template>
{{ getSessionAvatarText(session) }}
</div>
<!-- Session Info -->
<div v-if="!isCollapsed" class="min-w-0 flex-1">
<div class="flex items-center justify-between gap-2">
<p class="truncate text-sm font-medium">
<div class="flex items-center justify-between gap-1.5">
<p class="truncate text-xs font-medium">
{{ session.name }}
</p>
<UIcon
v-if="sessionStore.isPinned(session.id)"
name="i-lucide-pin"
class="h-3.5 w-3.5 shrink-0 text-gray-400 rotate-45"
class="h-3 w-3 shrink-0 text-gray-400/80 rotate-45"
/>
</div>
<p class="truncate text-xs text-gray-500 dark:text-gray-400">
<p class="truncate text-[10px] text-gray-400 dark:text-gray-500 mt-0.5 leading-none">
{{ t('layout.sessionInfo', { count: session.messageCount }) }}
</p>
</div>
@@ -22,16 +22,18 @@ const { isSidebarCollapsed: isCollapsed } = storeToRefs(layoutStore)
<UTooltip :text="isCollapsed ? tooltip || title : ''" :popper="{ placement: 'right' }">
<UButton
:block="!isCollapsed"
class="transition-all rounded-full hover:bg-gray-200/60 dark:hover:bg-gray-800 h-12 cursor-pointer"
class="transition-all duration-200 rounded-xl hover:bg-gray-200/40 dark:hover:bg-gray-800/40 h-10 cursor-pointer"
:class="[
isCollapsed ? 'flex w-12 items-center justify-center px-0' : 'justify-start pl-4',
active ? 'bg-primary-100 text-primary-600 dark:bg-primary-900/30 dark:text-primary-400' : '',
isCollapsed ? 'flex w-10 items-center justify-center px-0 mx-auto' : 'justify-start pl-1.5',
active
? 'bg-gray-200/50 dark:bg-gray-800/80 text-gray-900 dark:text-white font-medium'
: 'text-gray-600 dark:text-gray-300',
]"
color="gray"
variant="ghost"
>
<UIcon :name="icon" class="h-5 w-5 shrink-0" :class="[isCollapsed ? '' : 'mr-2']" />
<span v-if="!isCollapsed" class="truncate">{{ title }}</span>
<UIcon :name="icon" class="h-5 w-5 shrink-0" :class="[isCollapsed ? '' : 'mr-2.5']" />
<span v-if="!isCollapsed" class="truncate text-xs font-medium">{{ title }}</span>
</UButton>
</UTooltip>
</template>
@@ -6,16 +6,17 @@ import SidebarButton from './SidebarButton.vue'
const { t } = useI18n()
const layoutStore = useLayoutStore()
const { showSettings } = storeToRefs(layoutStore)
const { showSettings, isSidebarCollapsed: isCollapsed } = storeToRefs(layoutStore)
</script>
<template>
<div class="px-4 py-2 dark:border-gray-800 space-y-2 mb-2">
<div class="py-2 dark:border-gray-800 space-y-2 mb-2" :class="[isCollapsed ? 'px-3' : 'px-4']">
<!-- 设置 -->
<SidebarButton
icon="i-heroicons-cog-6-tooth"
:title="t('layout.footer.settings')"
:active="showSettings"
:tooltip="t('layout.footer.settings')"
@click="layoutStore.openSettings()"
/>
</div>
@@ -84,7 +84,7 @@ onUnmounted(() => {
>
<!-- 滑动选中焦点层:基于 CSS Transform 平滑移动 -->
<div
class="absolute left-0 inset-y-0 -z-10 rounded-full bg-white ring-[1.5px] ring-primary-500 shadow-sm transition-all duration-350 ease-out dark:bg-primary-500/15 dark:ring-primary-400"
class="absolute left-0 inset-y-0 -z-10 rounded-full bg-white ring-[1.5px] ring-primary-500 shadow-sm transition-all duration-350 ease-out dark:bg-primary-700/15 dark:ring-primary-500"
:style="ringStyle"
></div>
@@ -100,7 +100,7 @@ onUnmounted(() => {
class="relative z-10 flex h-[32px] items-center justify-center whitespace-nowrap rounded-full px-5 text-[13px] font-medium transition-colors duration-200 cursor-pointer"
:class="[
modelValue === tab.id
? 'text-primary-600 dark:text-primary-300'
? 'text-primary-600 dark:text-primary-400'
: 'text-gray-500 hover:bg-gray-200/50 hover:text-gray-800 dark:text-gray-400 dark:hover:bg-gray-700/50 dark:hover:text-gray-200',
]"
@click="emit('update:modelValue', tab.id)"