fix: 前端页面架构重构初版

This commit is contained in:
Gsh
2025-12-28 22:42:17 +08:00
parent c649ad31c2
commit e4621d9049
53 changed files with 6098 additions and 845 deletions

View File

@@ -1,392 +0,0 @@
<!-- Aside 侧边栏 -->
<script setup lang="ts">
import type { ConversationItem } from 'vue-element-plus-x/types/Conversations';
import type { ChatSessionVo } from '@/api/session/types';
import { useRoute, useRouter } from 'vue-router';
import { get_session } from '@/api';
import logo from '@/assets/images/logo.png';
import SvgIcon from '@/components/SvgIcon/index.vue';
import Collapse from '@/layouts/components/Header/components/Collapse.vue';
import { useDesignStore } from '@/stores';
import { useSessionStore } from '@/stores/modules/session';
const route = useRoute();
const router = useRouter();
const designStore = useDesignStore();
const sessionStore = useSessionStore();
const sessionId = computed(() => route.params?.id);
const conversationsList = computed(() => sessionStore.sessionList);
const loadMoreLoading = computed(() => sessionStore.isLoadingMore);
const active = ref<string | undefined>();
onMounted(async () => {
// 获取会话列表
await sessionStore.requestSessionList();
// 高亮最新会话
if (conversationsList.value.length > 0 && sessionId.value) {
const currentSessionRes = await get_session(`${sessionId.value}`);
// 通过 ID 查询详情,设置当前会话 (因为有分页)
sessionStore.setCurrentSession(currentSessionRes.data);
}
});
watch(
() => sessionStore.currentSession,
(newValue) => {
active.value = newValue ? `${newValue.id}` : undefined;
},
);
// 创建会话
function handleCreatChat() {
// 创建会话, 跳转到默认聊天
sessionStore.createSessionBtn();
}
// 切换会话
function handleChange(item: ConversationItem<ChatSessionVo>) {
sessionStore.setCurrentSession(item);
router.replace({
name: 'chatWithId',
params: {
id: item.id,
},
});
}
// 处理组件触发的加载更多事件
async function handleLoadMore() {
if (!sessionStore.hasMore)
return; // 无更多数据时不加载
await sessionStore.loadMoreSessions();
}
// 右键菜单
function handleMenuCommand(command: string, item: ConversationItem<ChatSessionVo>) {
switch (command) {
case 'delete':
ElMessageBox.confirm('删除后,聊天记录将不可恢复。', '确定删除对话?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
confirmButtonClass: 'el-button--danger',
cancelButtonClass: 'el-button--info',
roundButton: true,
autofocus: false,
})
.then(() => {
// 删除会话
sessionStore.deleteSessions([item.id!]);
nextTick(() => {
if (item.id === active.value) {
// 如果删除当前会话 返回到默认页
sessionStore.createSessionBtn();
}
});
})
.catch(() => {
// 取消删除
});
break;
case 'rename':
ElMessageBox.prompt('', '编辑对话名称', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputErrorMessage: '请输入对话名称',
confirmButtonClass: 'el-button--primary',
cancelButtonClass: 'el-button--info',
roundButton: true,
inputValue: item.sessionTitle, // 设置默认值
autofocus: false,
inputValidator: (value) => {
if (!value) {
return false;
}
return true;
},
}).then(({ value }) => {
sessionStore
.updateSession({
id: item.id!,
sessionTitle: value,
sessionContent: item.sessionContent,
})
.then(() => {
ElMessage({
type: 'success',
message: '修改成功',
});
nextTick(() => {
// 如果是当前会话,则更新当前选中会话信息
if (sessionStore.currentSession?.id === item.id) {
sessionStore.setCurrentSession({
...item,
sessionTitle: value,
});
}
});
});
});
break;
default:
break;
}
}
</script>
<template>
<div
class="aside-container"
:class="{
'aside-container-suspended': designStore.isSafeAreaHover,
'aside-container-collapse': designStore.isCollapse,
// 折叠且未激活悬停时添加 no-delay 类
'no-delay': designStore.isCollapse && !designStore.hasActivatedHover,
}"
>
<div class="aside-wrapper">
<div v-if="!designStore.isCollapse" class="aside-header">
<div class="flex items-center gap-8px hover:cursor-pointer" @click="handleCreatChat">
<el-image :src="logo" alt="logo" fit="cover" class="logo-img" />
<span class="logo-text max-w-150px text-overflow">意心AI</span>
</div>
<Collapse class="ml-auto" />
</div>
<div class="aside-body">
<div class="creat-chat-btn-wrapper">
<div class="creat-chat-btn" @click="handleCreatChat">
<el-icon class="add-icon">
<Plus />
</el-icon>
<span class="creat-chat-text">新对话</span>
<SvgIcon name="ctrl+k" size="37" />
</div>
</div>
<div class="aside-content">
<div v-if="conversationsList.length > 0" class="conversations-wrap overflow-hidden">
<Conversations
v-model:active="active"
:items="conversationsList"
:label-max-width="200"
:show-tooltip="true"
:tooltip-offset="60"
show-built-in-menu
groupable
row-key="id"
label-key="sessionTitle"
tooltip-placement="right"
:load-more="handleLoadMore"
:load-more-loading="loadMoreLoading"
:items-style="{
marginLeft: '8px',
userSelect: 'none',
borderRadius: '10px',
padding: '8px 12px',
}"
:items-active-style="{
backgroundColor: '#fff',
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)',
color: 'rgba(0, 0, 0, 0.85)',
}"
:items-hover-style="{
backgroundColor: 'rgba(0, 0, 0, 0.04)',
}"
@menu-command="handleMenuCommand"
@change="handleChange"
/>
</div>
<el-empty v-else class="h-full flex-center" description="暂无对话记录" />
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
// 基础样式
.aside-container {
position: absolute;
top: 0;
left: 0;
// z-index: 11;
width: var(--sidebar-default-width);
height: 100%;
pointer-events: auto;
background-color: var(--sidebar-background-color);
border-right: 0.5px solid var(--s-color-border-tertiary, rgb(0 0 0 / 8%));
.aside-wrapper {
display: flex;
flex-direction: column;
height: 100%;
// 侧边栏头部样式
.aside-header {
display: flex;
align-items: center;
height: 36px;
margin: 10px 12px 0;
.logo-img {
box-sizing: border-box;
width: 36px;
height: 36px;
padding: 4px;
overflow: hidden;
background-color: #ffffff;
border-radius: 50%;
img {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
}
.logo-text {
font-size: 16px;
font-weight: 700;
color: rgb(0 0 0 / 85%);
transform: skewX(-2deg);
}
}
// 侧边栏内容样式
.aside-body {
.creat-chat-btn-wrapper {
padding: 0 12px;
.creat-chat-btn {
display: flex;
gap: 6px;
align-items: center;
padding: 8px 6px;
margin-top: 16px;
margin-bottom: 6px;
color: #0057ff;
cursor: pointer;
user-select: none;
background-color: rgb(0 87 255 / 6%);
border: 1px solid rgb(0 102 255 / 15%);
border-radius: 12px;
&:hover {
background-color: rgb(0 87 255 / 12%);
}
.creat-chat-text {
font-size: 14px;
font-weight: 700;
line-height: 22px;
}
.add-icon {
width: 24px;
height: 24px;
font-size: 16px;
}
.svg-icon {
height: 24px;
margin-left: auto;
color: rgb(0 87 255 / 30%);
}
}
}
.aside-content {
display: flex;
flex: 1;
flex-direction: column;
height: 100%;
min-height: 0;
// 会话列表高度-基础样式
.conversations-wrap {
height: calc(100vh - 110px);
.label {
display: flex;
align-items: center;
height: 100%;
}
}
}
}
}
}
// 折叠样式
.aside-container-collapse {
position: absolute;
top: 54px;
// z-index: 22;
height: auto;
max-height: calc(100% - 110px);
padding-bottom: 12px;
overflow: hidden;
/* 禁用悬停事件 */
pointer-events: none;
border: 1px solid rgb(0 0 0 / 8%);
border-radius: 15px;
box-shadow:
0 10px 20px 0 rgb(0 0 0 / 10%),
0 0 1px 0 rgb(0 0 0 / 15%);
opacity: 0;
transition: opacity 0.3s ease 0.3s, transform 0.3s ease 0.3s;
// 指定样式过渡
// 向左偏移一个宽度
transform: translateX(-100%);
/* 新增:未激活悬停时覆盖延迟 */
&.no-delay {
transition-delay: 0s, 0s;
}
}
// 悬停样式
.aside-container-collapse:hover,
.aside-container-collapse.aside-container-suspended {
height: auto;
max-height: calc(100% - 110px);
padding-bottom: 12px;
overflow: hidden;
pointer-events: auto;
border: 1px solid rgb(0 0 0 / 8%);
border-radius: 15px;
box-shadow:
0 10px 20px 0 rgb(0 0 0 / 10%),
0 0 1px 0 rgb(0 0 0 / 15%);
// 直接在这里写悬停时的样式(与 aside-container-suspended 一致)
opacity: 1;
transition: opacity 0.3s ease 0s, transform 0.3s ease 0s;
// 过渡动画沿用原有设置
transform: translateX(15px);
// 会话列表高度-悬停样式
.conversations-wrap {
height: calc(100vh - 155px) !important;
}
}
// 样式穿透
:deep() {
// 会话列表背景色
.conversations-list {
background-color: transparent !important;
}
// 群组标题样式 和 侧边栏菜单背景色一致
.conversation-group-title {
padding-left: 12px !important;
background-color: var(--sidebar-background-color) !important;
}
.conversation-group .active-sticky
{
z-index: 0 ;
}
.conversation-group .sticky-title{
z-index: 0 ;
}
}
</style>

View File

@@ -0,0 +1,918 @@
<script setup lang="ts">
import type { ConversationItem } from 'vue-element-plus-x/types/Conversations';
import type { ChatSessionVo } from '@/api/session/types';
import { ChatLineSquare, Expand, Fold, MoreFilled, Plus } from '@element-plus/icons-vue';
import { useRoute, useRouter } from 'vue-router';
import { get_session } from '@/api';
import { useDesignStore } from '@/stores';
import { useSessionStore } from '@/stores/modules/session';
const route = useRoute();
const router = useRouter();
const designStore = useDesignStore();
const sessionStore = useSessionStore();
const sessionId = computed(() => route.params?.id);
const conversationsList = computed(() => sessionStore.sessionList);
const loadMoreLoading = computed(() => sessionStore.isLoadingMore);
const active = ref<string | undefined>();
const isCollapsed = computed(() => designStore.isCollapseConversationList);
onMounted(async () => {
await sessionStore.requestSessionList();
if (conversationsList.value.length > 0 && sessionId.value) {
const currentSessionRes = await get_session(`${sessionId.value}`);
sessionStore.setCurrentSession(currentSessionRes.data);
}
});
watch(
() => sessionStore.currentSession,
(newValue) => {
active.value = newValue ? `${newValue.id}` : undefined;
},
);
// 创建会话
function handleCreatChat() {
sessionStore.createSessionBtn();
}
// 切换会话
function handleChange(item: ConversationItem<ChatSessionVo>) {
sessionStore.setCurrentSession(item);
router.replace({
name: 'chatConversationWithId',
params: {
id: item.id,
},
});
}
// 处理组件触发的加载更多事件
async function handleLoadMore() {
if (!sessionStore.hasMore)
return;
await sessionStore.loadMoreSessions();
}
// 右键菜单
function handleMenuCommand(command: string, item: ConversationItem<ChatSessionVo>) {
switch (command) {
case 'delete':
ElMessageBox.confirm('删除后,聊天记录将不可恢复。', '确定删除对话?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
confirmButtonClass: 'el-button--danger',
cancelButtonClass: 'el-button--info',
roundButton: true,
autofocus: false,
})
.then(() => {
sessionStore.deleteSessions([item.id!]);
nextTick(() => {
if (item.id === active.value) {
sessionStore.createSessionBtn();
}
});
})
.catch(() => {
// 取消删除
});
break;
case 'rename':
ElMessageBox.prompt('', '编辑对话名称', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputErrorMessage: '请输入对话名称',
confirmButtonClass: 'el-button--primary',
cancelButtonClass: 'el-button--info',
roundButton: true,
inputValue: item.sessionTitle,
autofocus: false,
inputValidator: (value) => {
return !!value;
},
}).then(({ value }) => {
sessionStore
.updateSession({
id: item.id!,
sessionTitle: value,
sessionContent: item.sessionContent,
})
.then(() => {
ElMessage({
type: 'success',
message: '修改成功',
});
nextTick(() => {
if (sessionStore.currentSession?.id === item.id) {
sessionStore.setCurrentSession({
...item,
sessionTitle: value,
});
}
});
});
});
break;
default:
break;
}
}
// 折叠/展开侧边栏
function toggleSidebar() {
// designStore.setIsCollapseConversationList(!designStore.isCollapseConversationList);
}
// 点击logo创建新会话仅在折叠状态
function handleLogoClick() {
if (isCollapsed.value) {
handleCreatChat();
}
}
// 处理右键菜单(折叠状态下使用)
function handleContextMenu(event: MouseEvent, item: ConversationItem<ChatSessionVo>) {
event.preventDefault();
// 在折叠状态下触发删除确认
ElMessageBox.confirm('删除后,聊天记录将不可恢复。', '确定删除对话?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
confirmButtonClass: 'el-button--danger',
cancelButtonClass: 'el-button--info',
roundButton: true,
autofocus: false,
})
.then(() => {
sessionStore.deleteSessions([item.id!]);
nextTick(() => {
if (item.id === active.value) {
sessionStore.createSessionBtn();
}
});
})
.catch(() => {
// 取消删除
});
}
// 折叠状态下点击更多按钮
function handleCollapsedMenuClick(event: MouseEvent, item: ConversationItem<ChatSessionVo>) {
event.stopPropagation();
// 创建一个简单的菜单
ElMessageBox({
title: '对话操作',
message: `
<div style="padding: 8px 0;">
<div class="menu-item" data-action="rename" style="padding: 8px 12px; cursor: pointer; border-radius: 4px; margin: 4px 0;">
<span style="font-size: 14px;">重命名</span>
</div>
<div class="menu-item" data-action="delete" style="padding: 8px 12px; cursor: pointer; border-radius: 4px; margin: 4px 0; color: #f56c6c;">
<span style="font-size: 14px;">删除对话</span>
</div>
</div>
`,
showConfirmButton: false,
showCancelButton: false,
dangerouslyUseHTMLString: true,
customClass: 'collapsed-menu-dialog',
closeOnClickModal: true,
closeOnPressEscape: true,
}).then(() => {
// 对话框关闭
}).catch(() => {
// 对话框关闭
});
// 添加菜单项点击事件
nextTick(() => {
const menuItems = document.querySelectorAll('.menu-item');
menuItems.forEach((itemEl) => {
itemEl.addEventListener('click', (e) => {
const action = (e.currentTarget as HTMLElement).dataset.action;
if (action === 'delete') {
ElMessageBox.confirm('删除后,聊天记录将不可恢复。', '确定删除对话?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
confirmButtonClass: 'el-button--danger',
cancelButtonClass: 'el-button--info',
roundButton: true,
autofocus: false,
})
.then(() => {
sessionStore.deleteSessions([item.id!]);
nextTick(() => {
if (item.id === active.value) {
sessionStore.createSessionBtn();
}
});
})
.catch(() => {
// 取消删除
});
}
else if (action === 'rename') {
ElMessageBox.prompt('', '编辑对话名称', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputErrorMessage: '请输入对话名称',
confirmButtonClass: 'el-button--primary',
cancelButtonClass: 'el-button--info',
roundButton: true,
inputValue: item.sessionTitle,
autofocus: false,
inputValidator: (value) => {
return !!value;
},
}).then(({ value }) => {
sessionStore
.updateSession({
id: item.id!,
sessionTitle: value,
sessionContent: item.sessionContent,
})
.then(() => {
ElMessage({
type: 'success',
message: '修改成功',
});
nextTick(() => {
if (sessionStore.currentSession?.id === item.id) {
sessionStore.setCurrentSession({
...item,
sessionTitle: value,
});
}
});
});
});
}
// 关闭菜单对话框
document.querySelector('.collapsed-menu-dialog .el-message-box__headerbtn')?.dispatchEvent(new Event('click'));
});
});
});
}
</script>
<template>
<div
class="aside-container"
:class="{ 'aside-collapsed': isCollapsed }"
>
<div class="aside-wrapper">
<!-- 头部 -->
<div class="aside-header">
<!-- 展开状态显示logo和标题 -->
<div
v-if="!isCollapsed"
class="header-content-expanded flex items-center gap-8px hover:cursor-pointer"
@click="handleCreatChat"
>
<span class="logo-text max-w-150px text-overflow">会话</span>
</div>
<!-- 折叠状态只显示logo -->
<div
v-else
class="header-content-collapsed flex items-center justify-center hover:cursor-pointer"
@click="handleLogoClick"
>
<el-icon size="20">
<ChatLineSquare />
</el-icon>
</div>
<!-- 折叠按钮 -->
<el-tooltip
:content="isCollapsed ? '展开侧边栏' : '折叠侧边栏'"
placement="bottom"
>
<el-button
class="collapse-btn"
type="text"
@click="toggleSidebar"
>
<el-icon v-if="isCollapsed">
<Expand />
</el-icon>
<el-icon v-else>
<Fold />
</el-icon>
</el-button>
</el-tooltip>
</div>
<!-- 内容区域 -->
<div class="aside-body">
<!-- 创建会话按钮 -->
<div class="creat-chat-btn-wrapper">
<div
class="creat-chat-btn"
:class="{ 'creat-chat-btn-collapsed': isCollapsed }"
@click="handleCreatChat"
>
<el-icon class="add-icon">
<Plus />
</el-icon>
<span v-if="!isCollapsed" class="creat-chat-text">
新对话
</span>
</div>
</div>
<!-- 会话列表 -->
<div class="aside-content">
<div v-if="conversationsList.length > 0" class="conversations-wrap">
<Conversations
v-model:active="active"
:items="conversationsList"
:label-max-width="isCollapsed ? 0 : 140"
:show-tooltip="!isCollapsed"
:tooltip-offset="60"
show-built-in-menu
groupable
row-key="id"
label-key="sessionTitle"
:tooltip-placement="isCollapsed ? 'right-start' : 'right'"
:load-more="handleLoadMore"
:load-more-loading="loadMoreLoading"
:items-style="{
marginLeft: '8px',
marginRight: '8px',
userSelect: 'none',
borderRadius: isCollapsed ? '12px' : '10px',
padding: isCollapsed ? '12px 8px' : '8px 12px',
justifyContent: isCollapsed ? 'center' : 'space-between',
width: isCollapsed ? '64px' : 'auto',
height: isCollapsed ? '64px' : 'auto',
minHeight: '48px',
flexDirection: isCollapsed ? 'column' : 'row',
position: 'relative',
overflow: 'hidden',
}"
:items-active-style="{
backgroundColor: '#fff',
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)',
color: 'rgba(0, 0, 0, 0.85)',
}"
:items-hover-style="{
backgroundColor: 'rgba(0, 0, 0, 0.04)',
}"
@menu-command="handleMenuCommand"
@change="handleChange"
@contextmenu="handleContextMenu"
>
<!-- 自定义折叠状态下的会话项内容 -->
<template #default="{ item }">
<div class="conversation-item-content">
<div v-if="isCollapsed" class="collapsed-item">
<div
class="avatar-circle"
@click="handleChange(item)"
@contextmenu="(e) => handleContextMenu(e, item)"
>
{{ item.sessionTitle?.charAt(0) || 'A' }}
</div>
<div v-if="item.unreadCount" class="unread-indicator">
{{ item.unreadCount > 99 ? '99+' : item.unreadCount }}
</div>
<!-- 折叠状态下的更多操作按钮 -->
<div
class="collapsed-menu-trigger"
@click.stop="(e) => handleCollapsedMenuClick(e, item)"
@contextmenu.stop="(e) => handleContextMenu(e, item)"
>
<el-icon size="14">
<MoreFilled />
</el-icon>
</div>
</div>
<div v-else class="expanded-item">
<div class="conversation-info">
<div class="conversation-title">
{{ item.sessionTitle }}
</div>
<div v-if="item.sessionContent" class="conversation-preview">
{{ item.sessionContent.substring(0, 30) }}{{ item.sessionContent.length > 30 ? '...' : '' }}
</div>
</div>
</div>
</div>
</template>
</Conversations>
</div>
<el-empty
v-else
class="h-full flex-center"
:description="isCollapsed ? '' : '暂无对话记录'"
>
<template #description>
<span v-if="!isCollapsed">暂无对话记录</span>
</template>
</el-empty>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
// 基础样式
.aside-container {
height: 100%;
border-right: 0.5px solid var(--s-color-border-tertiary, rgb(0 0 0 / 8%));
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
overflow: hidden;
flex-shrink: 0;
background-color: var(--sidebar-background-color, #f9fafb);
// 展开状态 - 240px
&:not(.aside-collapsed) {
width: 240px;
.aside-wrapper {
width: 240px;
}
}
// 折叠状态 - 100px
&.aside-collapsed {
width: 100px;
.aside-wrapper {
width: 100px;
}
}
}
.aside-wrapper {
display: flex;
flex-direction: column;
height: 100%;
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
// 头部样式
.aside-header {
display: flex;
align-items: center;
justify-content: space-between;
height: 56px;
padding: 0 12px;
border-bottom: 1px solid var(--s-color-border-tertiary, rgb(0 0 0 / 8%));
//background-color: var(--sidebar-header-bg, #ffffff);
.header-content-expanded {
flex: 1;
}
.header-content-collapsed {
width: 36px;
height: 36px;
border-radius: 8px;
transition: background-color 0.2s ease;
&:hover {
background-color: rgba(0, 0, 0, 0.04);
}
.el-icon {
color: var(--el-text-color-secondary);
}
}
.logo-text {
font-size: 16px;
font-weight: 700;
color: rgb(0 0 0 / 85%);
transform: skewX(-2deg);
}
.collapse-btn {
width: 32px;
height: 32px;
padding: 0;
color: var(--el-text-color-secondary);
transition: transform 0.2s ease;
&:hover {
color: var(--el-text-color-primary);
background-color: var(--el-fill-color-light);
transform: scale(1.1);
}
.el-icon {
font-size: 18px;
}
}
}
// 内容区域
.aside-body {
flex: 1;
display: flex;
flex-direction: column;
padding: 0 4px;
overflow: hidden;
.creat-chat-btn-wrapper {
padding: 12px 8px 4px;
.creat-chat-btn {
display: flex;
align-items: center;
justify-content: center;
height: 40px;
color: #0057ff;
cursor: pointer;
user-select: none;
background-color: rgb(0 87 255 / 6%);
border: 1px solid rgb(0 102 255 / 15%);
border-radius: 12px;
transition: all 0.2s ease;
&:hover {
background-color: rgb(0 87 255 / 12%);
transform: translateY(-1px);
}
&.creat-chat-btn-collapsed {
width: 40px;
height: 40px;
border-radius: 50%;
margin: 0 auto;
}
.add-icon {
width: 24px;
height: 24px;
font-size: 16px;
}
.creat-chat-text {
font-size: 14px;
font-weight: 700;
line-height: 22px;
margin-left: 6px;
transition: opacity 0.2s ease;
}
}
}
.aside-content {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
overflow: hidden;
.conversations-wrap {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding: 0 4px;
&::-webkit-scrollbar {
width: 4px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.1);
border-radius: 2px;
}
&:hover::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.2);
}
.conversation-item-content {
width: 100%;
height: 100%;
.collapsed-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
width: 100%;
height: 100%;
.avatar-circle {
width: 36px;
height: 36px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s ease;
&:hover {
transform: scale(1.05);
}
}
.unread-indicator {
position: absolute;
top: 2px;
right: 2px;
min-width: 16px;
height: 16px;
padding: 0 4px;
background-color: #ff4d4f;
color: white;
border-radius: 8px;
font-size: 10px;
display: flex;
align-items: center;
justify-content: center;
z-index: 1;
font-weight: 600;
}
.collapsed-menu-trigger {
position: absolute;
bottom: 4px;
right: 4px;
width: 20px;
height: 20px;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.05);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
opacity: 0;
transition: all 0.2s ease;
z-index: 2;
.el-icon {
color: var(--el-text-color-secondary);
font-size: 12px;
}
&:hover {
background-color: rgba(0, 0, 0, 0.1);
opacity: 1;
}
}
&:hover .collapsed-menu-trigger {
opacity: 0.7;
}
}
.expanded-item {
display: flex;
align-items: center;
width: 100%;
min-width: 0;
.conversation-info {
flex: 1;
min-width: 0;
margin-right: 8px;
overflow: hidden;
.conversation-title {
font-size: 14px;
font-weight: 500;
color: var(--el-text-color-primary);
line-height: 1.4;
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 100%;
}
.conversation-preview {
font-size: 12px;
color: var(--el-text-color-secondary);
line-height: 1.4;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 100%;
}
}
}
}
}
}
}
// 样式穿透 - 重点修复溢出问题
:deep() {
.conversations-list {
background-color: transparent !important;
width: 100% !important;
}
.conversation-group-title {
padding-left: 12px !important;
background-color: transparent !important;
transition: all 0.3s ease;
.title-text {
opacity: 0.6;
font-size: 12px;
transition: opacity 0.2s ease;
}
}
.conversation-item {
transition: all 0.3s ease;
width: 100% !important;
max-width: 100% !important;
box-sizing: border-box !important;
&-inner {
width: 100% !important;
max-width: 100% !important;
box-sizing: border-box !important;
display: flex !important;
justify-content: space-between !important;
align-items: center !important;
}
&-content {
flex: 1 !important;
min-width: 0 !important;
max-width: calc(100% - 32px) !important;
overflow: hidden !important;
box-sizing: border-box !important;
}
// 确保操作按钮区域在展开状态下正常显示
&-actions {
flex-shrink: 0 !important;
display: flex !important;
align-items: center !important;
opacity: 1 !important;
visibility: visible !important;
transition: all 0.3s ease;
.el-button {
transition: all 0.2s ease;
width: 24px !important;
height: 24px !important;
padding: 0 !important;
margin-left: 4px !important;
flex-shrink: 0 !important;
&:hover {
transform: scale(1.1);
}
.el-icon {
font-size: 16px !important;
}
}
}
}
// 折叠状态样式
.aside-collapsed {
.conversation-group-title {
display: none !important;
}
.conversation-item {
justify-content: center !important;
padding: 12px 8px !important;
height: 64px !important;
min-height: 64px !important;
&-label {
display: none !important;
}
&-actions {
// 折叠状态下隐藏默认操作按钮,使用自定义的
display: none !important;
opacity: 0 !important;
visibility: hidden !important;
}
&-content {
max-width: 100% !important;
}
}
}
// 展开状态样式
&:not(.aside-collapsed) {
.conversation-item {
&-actions {
display: flex !important;
opacity: 1 !important;
visibility: visible !important;
}
}
}
}
// 自定义对话框样式
:deep(.collapsed-menu-dialog) {
.el-message-box {
width: 160px !important;
padding: 12px !important;
&__header {
padding: 0 0 8px 0 !important;
border-bottom: none !important;
}
&__title {
font-size: 14px !important;
font-weight: 600 !important;
}
&__content {
padding: 0 !important;
}
&__headerbtn {
top: 8px !important;
right: 8px !important;
.el-icon {
font-size: 14px !important;
}
}
}
}
// 移动端适配
@media (max-width: 768px) {
.aside-container {
position: fixed;
left: 0;
top: 0;
bottom: 0;
z-index: 1000;
width: 280px !important;
transform: translateX(-100%);
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15);
&.aside-collapsed {
transform: translateX(-100%);
width: 100px !important;
}
&:not(.aside-collapsed) {
transform: translateX(0);
}
}
.aside-wrapper {
width: 280px !important;
.aside-collapsed & {
width: 100px !important;
}
}
// 移动端遮罩层
.aside-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
animation: fadeIn 0.3s ease;
}
}
// 动画
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
</style>

View File

@@ -30,40 +30,40 @@ const popoverRef = ref();
// 弹出面板内容
const popoverList = ref([
{
key: '5',
title: '控制台',
icon: 'settings-4-fill',
},
{
key: '3',
divider: true,
},
{
key: '7',
title: '公告',
icon: 'notification-fill',
},
{
key: '8',
title: '模型库',
icon: 'apps-fill',
},
{
key: '9',
title: '文档',
icon: 'book-fill',
},
{
key: '6',
title: '新手引导',
icon: 'dashboard-fill',
},
{
key: '3',
divider: true,
},
// {
// key: '5',
// title: '控制台',
// icon: 'settings-4-fill',
// },
// {
// key: '3',
// divider: true,
// },
// {
// key: '7',
// title: '公告',
// icon: 'notification-fill',
// },
// {
// key: '8',
// title: '模型库',
// icon: 'apps-fill',
// },
// {
// key: '9',
// title: '文档',
// icon: 'book-fill',
// },
//
// {
// key: '6',
// title: '新手引导',
// icon: 'dashboard-fill',
// },
// {
// key: '3',
// divider: true,
// },
{
key: '4',
title: '退出登录',
@@ -130,7 +130,9 @@ function handleClick(item: any) {
ElMessage.warning('暂未开放');
break;
case '5':
openDialog();
// 打开控制台
popoverRef.value?.hide?.();
router.push('/console');
break;
case '6':
handleStartTutorial();

View File

@@ -0,0 +1,84 @@
<script setup lang="ts">
import { useRouter, useRoute } from 'vue-router';
const router = useRouter();
const route = useRoute();
// 检查是否在聊天页面
const isOnChatPage = computed(() => {
return route.path.startsWith('/chat');
});
function goToChat() {
router.push('/chat/conversation');
}
</script>
<template>
<div v-if="!isOnChatPage" class="start-chat-btn-container" data-tour="start-chat-btn">
<div
class="start-chat-btn"
title="开始聊天"
@click="goToChat"
>
<el-icon class="chat-icon">
<i-ep-chat-dot-round />
</el-icon>
<span class="btn-text">开始聊天</span>
</div>
</div>
</template>
<style scoped lang="scss">
.start-chat-btn-container {
display: flex;
align-items: center;
margin-right: 12px;
.start-chat-btn {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
padding: 8px 16px;
border-radius: 8px;
background: linear-gradient(135deg, var(--el-color-primary) 0%, var(--el-color-primary-light-3) 100%);
color: #fff;
font-weight: 600;
transition: all 0.3s;
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
}
&:active {
transform: translateY(0);
}
.chat-icon {
font-size: 18px;
}
.btn-text {
font-size: 14px;
}
}
}
// 移动端隐藏文字
@media (max-width: 768px) {
.start-chat-btn-container {
margin-right: 8px;
.start-chat-btn {
padding: 8px;
.btn-text {
display: none;
}
}
}
}
</style>

View File

@@ -0,0 +1,95 @@
<script setup lang="ts">
import { useColorMode } from '@vueuse/core';
// 使用 VueUse 的 useColorMode
const mode = useColorMode({
attribute: 'class',
modes: {
light: 'light',
dark: 'dark',
},
});
// 切换主题
function toggleTheme() {
mode.value = mode.value === 'dark' ? 'light' : 'dark';
}
// 主题图标
const themeIcon = computed(() => {
return mode.value === 'dark' ? 'Sunny' : 'Moon';
});
// 主题标题
const themeTitle = computed(() => {
return mode.value === 'dark' ? '切换到浅色模式' : '切换到深色模式';
});
</script>
<template>
<div class="theme-btn-container" data-tour="theme-btn">
<div
class="theme-btn"
:title="themeTitle"
@click="toggleTheme"
>
<!-- PC端显示文字 + 图标 -->
<el-icon class="theme-icon">
<component :is="`i-ep-${themeIcon}`" />
</el-icon>
<span class="pc-text">主题</span>
</div>
</div>
</template>
<style scoped lang="scss">
.theme-btn-container {
display: flex;
align-items: center;
.theme-btn {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
padding: 8px 12px;
border-radius: 6px;
transition: all 0.2s;
color: var(--el-text-color-regular);
&:hover {
background-color: var(--el-fill-color-light);
color: var(--el-color-primary);
}
.theme-icon {
font-size: 18px;
transition: transform 0.3s;
}
&:hover .theme-icon {
transform: rotate(20deg);
}
// PC端显示文字
.pc-text {
display: inline;
font-size: 14px;
font-weight: 500;
}
}
}
// 移动端隐藏文字
@media (max-width: 768px) {
.theme-btn-container {
.theme-btn {
padding: 8px;
.pc-text {
display: none;
}
}
}
}
</style>

View File

@@ -1,94 +1,50 @@
<!-- Header 头部 -->
<!--
&lt;!&ndash; Header 头部 &ndash;&gt;
<script setup lang="ts">
import { onKeyStroke } from '@vueuse/core';
import { SIDE_BAR_WIDTH } from '@/config/index';
import { useDesignStore, useUserStore } from '@/stores';
import { useSessionStore } from '@/stores/modules/session';
import { useRouter } from 'vue-router';
import logo from '@/assets/images/logo.png';
import { useUserStore } from '@/stores';
import AiTutorialBtn from './components/AiTutorialBtn.vue';
import AnnouncementBtn from './components/AnnouncementBtn.vue';
import Avatar from './components/Avatar.vue';
import BuyBtn from './components/BuyBtn.vue';
import Collapse from './components/Collapse.vue';
import ConsoleBtn from './components/ConsoleBtn.vue';
import CreateChat from './components/CreateChat.vue';
import LoginBtn from './components/LoginBtn.vue';
import ModelLibraryBtn from './components/ModelLibraryBtn.vue';
import TitleEditing from './components/TitleEditing.vue';
import StartChatBtn from './components/StartChatBtn.vue';
import ThemeBtn from './components/ThemeBtn.vue';
const router = useRouter();
const userStore = useUserStore();
const designStore = useDesignStore();
const sessionStore = useSessionStore();
const avatarRef = ref();
const currentSession = computed(() => sessionStore.currentSession);
onMounted(() => {
// 全局设置侧边栏默认宽度 (这个是不变的,一开始就设置)
document.documentElement.style.setProperty(`--sidebar-default-width`, `${SIDE_BAR_WIDTH}px`);
if (designStore.isCollapse) {
document.documentElement.style.setProperty(`--sidebar-left-container-default-width`, ``);
}
else {
document.documentElement.style.setProperty(
`--sidebar-left-container-default-width`,
`${SIDE_BAR_WIDTH}px`,
);
}
});
// 定义 Ctrl+K 的处理函数
function handleCtrlK(event: KeyboardEvent) {
event.preventDefault(); // 防止默认行为
sessionStore.createSessionBtn();
}
// 设置全局的键盘按键监听
onKeyStroke(event => event.ctrlKey && event.key.toLowerCase() === 'k', handleCtrlK, {
passive: false,
});
// 打开控制台
function handleOpenConsole() {
avatarRef.value?.openDialog?.();
router.push('/console');
}
</script>
<template>
<div class="header-container">
<div class="header-box relative z-10 top-0 left-0 right-0">
<div class="absolute left-0 right-0 top-0 bottom-0 flex items-center flex-row">
<div
class="overflow-hidden flex h-full items-center flex-row flex-1 w-fit flex-shrink-0 min-w-0"
>
<div class="w-full flex items-center flex-row">
<!-- 左边 -->
<div
v-if="designStore.isCollapse"
class="left-box flex h-full items-center pl-20px gap-12px flex-shrink-0 flex-row"
>
<Collapse />
<CreateChat />
<div v-if="currentSession" class="w-0.5px h-30px bg-[rgba(217,217,217)]" />
</div>
<!-- 中间 -->
<div class="middle-box flex-1 min-w-0 ml-12px">
<TitleEditing />
</div>
</div>
<div class="header-box">
&lt;!&ndash; 左侧logo和品牌区域 &ndash;&gt;
<div class="left-section">
<div class="brand-container">
<el-image :src="logo" alt="logo" fit="contain" class="logo-img" />
<span class="brand-text">意心AI</span>
</div>
</div>
<!-- 右边 -->
<div class="right-box flex h-full items-center pr-20px flex-shrink-0 mr-auto flex-row">
<AnnouncementBtn />
<ModelLibraryBtn />
<AiTutorialBtn />
<ConsoleBtn @open-console="handleOpenConsole" />
<BuyBtn v-show="userStore.userInfo" />
<Avatar v-show="userStore.userInfo" ref="avatarRef" />
<LoginBtn v-show="!userStore.userInfo" />
</div>
&lt;!&ndash; 右侧功能按钮区域 &ndash;&gt;
<div class="right-section">
<StartChatBtn />
<AnnouncementBtn />
<ModelLibraryBtn />
<AiTutorialBtn />
<ConsoleBtn @open-console="handleOpenConsole" />
<BuyBtn v-show="userStore.userInfo" />
<ThemeBtn />
<LoginBtn v-show="!userStore.userInfo" />
<Avatar v-show="userStore.userInfo" />
</div>
</div>
</div>
@@ -98,20 +54,483 @@ function handleOpenConsole() {
.header-container {
display: flex;
flex-shrink: 0;
flex-direction: column;
width: 100%;
height: fit-content;
height: var(&#45;&#45;header-container-default-height, 60px);
.header-box {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
width: calc(
100% - var(--sidebar-left-container-default-width, 0px) - var(
--sidebar-right-container-default-width,
0px
)
);
height: var(--header-container-default-heigth);
margin: 0 var(--sidebar-right-container-default-width, 0) 0
var(--sidebar-left-container-default-width, 0);
height: 100%;
padding: 0 16px;
background: var(&#45;&#45;header-bg-color, #ffffff);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
}
// 左侧品牌区域
.left-section {
display: flex;
align-items: center;
min-width: fit-content;
flex-shrink: 0;
.brand-container {
display: flex;
align-items: center;
gap: 8px;
.logo-img {
width: 36px; // 优化为更合适的大小
height: 36px;
flex-shrink: 0;
transition: transform 0.2s ease;
&:hover {
transform: scale(1.05);
}
}
.brand-text {
font-size: 22px; // 减小字体大小
font-weight: bold;
color: var(&#45;&#45;brand-color, #000000);
white-space: nowrap;
letter-spacing: -0.5px;
transition: color 0.2s ease;
&:hover {
//color: var(&#45;&#45;brand-hover-color, #40a9ff);
}
}
}
}
// 右侧功能区域
.right-section {
display: flex;
align-items: center;
gap: 12px; // 优化按钮间距
height: 100%;
flex-shrink: 0;
// 统一按钮样式
:deep(.menu-button) {
height: 36px;
display: flex;
align-items: center;
justify-content: center;
}
}
}
</style>
-->
<!-- Header 头部 -->
<script setup lang="ts">
import { computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import logo from '@/assets/images/logo.png';
import ConsoleBtn from '@/layouts/components0/Header/components/ConsoleBtn.vue';
import { useUserStore } from '@/stores';
import AiTutorialBtn from './components/AiTutorialBtn.vue';
import AnnouncementBtn from './components/AnnouncementBtn.vue';
import Avatar from './components/Avatar.vue';
import BuyBtn from './components/BuyBtn.vue';
import LoginBtn from './components/LoginBtn.vue';
import ModelLibraryBtn from './components/ModelLibraryBtn.vue';
import ThemeBtn from './components/ThemeBtn.vue';
const router = useRouter();
const route = useRoute();
const userStore = useUserStore();
// 当前激活的菜单项
const activeIndex = computed(() => {
if (route.path.startsWith('/console'))
return 'console';
if (route.path.startsWith('/model-library'))
return 'model-library';
if (route.path.includes('/chat/'))
return 'chat';
return '';
});
// 导航处理
function handleSelect(key: string) {
if (key && key !== 'no-route') {
router.push(key);
}
}
</script>
<template>
<div class="header-container">
<el-menu
:default-active="activeIndex"
class="header-menu"
mode="horizontal"
:ellipsis="false"
:router="false"
@select="handleSelect"
>
<!-- 左侧品牌区域 -->
<div class="menu-left">
<div class="brand-container" @click="router.push('/')">
<el-image :src="logo" alt="logo" fit="contain" class="logo-img" />
<span class="brand-text">意心AI</span>
</div>
</div>
<!-- 右侧功能区域 -->
<div class="menu-right">
<!-- AI聊天菜单 -->
<el-sub-menu index="chat" class="chat-submenu" popper-class="custom-popover">
<template #title>
<span class="menu-title">AI聊天</span>
</template>
<el-menu-item index="/chat/conversation">
AI对话
</el-menu-item>
<el-menu-item index="/chat/image">
图片生成
</el-menu-item>
<el-menu-item index="/chat/video">
视频生成
</el-menu-item>
</el-sub-menu>
<!-- 公告按钮 -->
<el-menu-item class="custom-menu-item" index="no-route">
<AnnouncementBtn :is-menu-item="true" />
</el-menu-item>
<!-- 模型库 -->
<el-menu-item index="/model-library" class="custom-menu-item">
<ModelLibraryBtn :is-menu-item="true" />
</el-menu-item>
<!-- AI教程 -->
<el-menu-item class="custom-menu-item" index="no-route">
<AiTutorialBtn />
</el-menu-item>
<!-- 控制台菜单 -->
<el-sub-menu index="console" class="console-submenu" popper-class="custom-popover">
<template #title>
<ConsoleBtn />
</template>
<el-menu-item index="/console/user">
用户信息
</el-menu-item>
<el-menu-item index="/console/apikey">
API密钥
</el-menu-item>
<el-menu-item index="/console/recharge-log">
充值记录
</el-menu-item>
<el-menu-item index="/console/usage">
用量统计
</el-menu-item>
<el-menu-item index="/console/premium">
尊享服务
</el-menu-item>
<el-menu-item index="/console/daily-task">
每日任务
</el-menu-item>
<el-menu-item index="/console/invite">
每周邀请
</el-menu-item>
<el-menu-item index="/console/activation">
激活码兑换
</el-menu-item>
</el-sub-menu>
<!-- 购买按钮 -->
<el-menu-item v-if="userStore.userInfo" class="custom-menu-item" index="no-route">
<BuyBtn :is-menu-item="true" />
</el-menu-item>
<!-- 主题切换暂不显示 -->
<el-menu-item v-if="false" class="custom-menu-item" index="no-route">
<ThemeBtn :is-menu-item="true" />
</el-menu-item>
<!-- 用户头像 -->
<div v-if="userStore.userInfo" class="avatar-container">
<Avatar />
</div>
<!-- 登录按钮 -->
<el-menu-item v-if="!userStore.userInfo" class="login-menu-item" index="no-route">
<LoginBtn :is-menu-item="true" />
</el-menu-item>
</div>
</el-menu>
</div>
</template>
<style scoped lang="scss">
.header-container {
--menu-hover-bg: #f5f5f5;
--menu-active-color: var(--el-color-primary);
--menu-transition: all 0.2s ease;
width: 100%;
height: var(--header-container-default-height, 64px);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
user-select: none;
}
.header-menu {
display: flex;
align-items: center;
justify-content: space-between;
height: 100%;
border-bottom: none !important;
}
// 左侧品牌区域
.menu-left {
flex-shrink: 0;
margin-left: 20px;
}
.brand-container {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
padding: 8px 12px;
border-radius: 6px;
transition: background-color var(--menu-transition);
&:hover {
background-color: var(--menu-hover-bg);
}
}
.logo-img {
width: 34px;
height: 34px;
flex-shrink: 0;
transition: transform var(--menu-transition);
&:hover {
transform: scale(1.05);
}
}
.brand-text {
font-size: 20px;
font-weight: 600;
color: var(--brand-color, #000000);
white-space: nowrap;
letter-spacing: -0.5px;
}
// 右侧功能区域
.menu-right {
display: flex;
align-items: center;
height: 100%;
margin-right: 16px;
gap: 4px;
}
// 公共菜单项样式
:deep(.el-menu-item),
:deep(.el-sub-menu__title) {
height: 100% !important;
border-bottom: none !important;
padding: 0 12px !important;
color: inherit !important;
&:hover {
background-color: transparent !important;
color: var(--menu-active-color) !important;
}
&.is-active {
background-color: transparent !important;
color: var(--menu-active-color) !important;
.menu-title {
color: var(--menu-active-color) !important;
}
}
}
// 聊天和控制台子菜单
.chat-submenu,
.console-submenu {
:deep(.el-sub-menu__title) {
display: flex;
align-items: center;
justify-content: center;
min-width: 80px;
}
}
.menu-title {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
font-size: 1.2rem;
font-weight: bold;
color: #606266;
transition: all 0.2s;
}
// 自定义按钮菜单项
.custom-menu-item,
.login-menu-item {
:deep(.el-menu-item-content) {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 0 !important;
}
}
// Avatar 容器
.avatar-container {
height: 100%;
display: flex;
align-items: center;
padding: 0 12px;
margin-left: 4px;
}
// 响应式设计
@media (max-width: 1280px) {
.brand-text {
font-size: 18px;
}
.menu-left {
margin-left: 16px;
}
.menu-right {
margin-right: 12px;
gap: 2px;
}
:deep(.el-menu-item),
:deep(.el-sub-menu__title) {
padding: 0 10px !important;
}
}
@media (max-width: 1024px) {
.brand-container {
gap: 8px;
padding: 6px 10px;
}
.logo-img {
width: 30px;
height: 30px;
}
.brand-text {
font-size: 16px;
}
.menu-title {
font-size: 13px;
}
.avatar-container {
padding: 0 8px;
}
}
@media (max-width: 768px) {
.brand-text {
display: none;
}
.logo-img {
width: 32px;
height: 32px;
}
.menu-left {
margin-left: 12px;
}
.menu-right {
margin-right: 8px;
// 隐藏按钮文字
:deep(.button-text) {
display: none;
}
.menu-title {
display: none;
}
// 显示图标
:deep(.el-icon) {
font-size: 18px;
}
}
:deep(.el-menu-item),
:deep(.el-sub-menu__title) {
padding: 0 8px !important;
min-width: auto !important;
}
}
@media (max-width: 480px) {
.menu-right {
gap: 0;
:deep(.el-menu-item),
:deep(.el-sub-menu__title) {
padding: 0 6px !important;
}
}
}
</style>
<style lang="scss">
// 自定义弹出框样式
.custom-popover {
.el-menu {
border: none;
border-radius: 8px;
padding: 6px 0;
min-width: 160px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
.el-menu-item {
height: 40px;
line-height: 40px;
padding: 0 20px;
margin: 2px 8px;
border-radius: 6px;
font-size: 14px;
transition: all 0.2s ease;
&:hover {
background-color: var(--el-color-primary-light-9);
color: var(--el-color-primary);
}
&.is-active {
color: var(--el-color-primary);
background-color: var(--el-color-primary-light-9);
font-weight: 500;
}
}
}
}
</style>