fix: 前端页面架构重构初版
This commit is contained in:
@@ -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>
|
||||
918
Yi.Ai.Vue3/src/layouts/components/ChatAside/index.vue
Normal file
918
Yi.Ai.Vue3/src/layouts/components/ChatAside/index.vue
Normal 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>
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -1,94 +1,50 @@
|
||||
<!-- Header 头部 -->
|
||||
<!--
|
||||
<!– Header 头部 –>
|
||||
<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">
|
||||
<!– 左侧logo和品牌区域 –>
|
||||
<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>
|
||||
<!– 右侧功能按钮区域 –>
|
||||
<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(--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(--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(--brand-color, #000000);
|
||||
white-space: nowrap;
|
||||
letter-spacing: -0.5px;
|
||||
transition: color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
//color: var(--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>
|
||||
|
||||
Reference in New Issue
Block a user