Files
Yi.Framework/Yi.Ai.Vue3/src/pages/chat/index.vue
2025-12-28 22:45:23 +08:00

305 lines
5.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { Expand, Fold } from '@element-plus/icons-vue';
import { computed, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
const route = useRoute();
const router = useRouter();
// 控制侧边栏折叠状态
const isCollapsed = ref(false);
// 菜单项配置
const navItems = [
{ name: 'conversation', label: '对话', icon: 'ChatDotRound', path: '/chat/conversation' },
{ name: 'image', label: '图片生成', icon: 'Picture', path: '/chat/image' },
{ name: 'video', label: '视频生成', icon: 'VideoCamera', path: '/chat/video' },
];
// 当前激活的菜单
const activeNav = computed(() => {
const path = route.path;
const item = navItems.find(item => item.path === path);
return item?.name || 'user';
});
// 切换菜单
function handleNavSelect(menu: typeof navItems[0]) {
router.push(menu.path);
}
// 在移动端默认折叠侧边栏
const isMobile = ref(false);
function checkIsMobile() {
isMobile.value = window.innerWidth <= 768;
// 移动端默认折叠
if (isMobile.value) {
isCollapsed.value = true;
}
}
// 初始检查和监听窗口大小变化
checkIsMobile();
window.addEventListener('resize', checkIsMobile);
</script>
<template>
<div class="console-page" :class="{ 'is-collapsed': isCollapsed }">
<!-- 侧边栏导航 -->
<div class="nav-sidebar" :class="{ 'is-collapsed': isCollapsed }">
<div class="nav-header">
<h2 v-show="!isCollapsed" class="nav-title">
AI聊天
</h2>
<div class="collapse-btn" @click="isCollapsed = !isCollapsed">
<el-icon>
<Expand v-if="isCollapsed" />
<Fold v-else />
</el-icon>
</div>
</div>
<el-menu
:default-active="activeNav"
class="nav-menu"
:collapse="isCollapsed"
:collapse-transition="false"
>
<el-menu-item
v-for="item in navItems"
:key="item.name"
:index="item.name"
@click="handleNavSelect(item)"
>
<el-icon>
<component :is="item.icon" />
</el-icon>
<template #title>
<span>{{ item.label }}</span>
</template>
</el-menu-item>
</el-menu>
</div>
<!-- 折叠遮罩层移动端 -->
<div
v-if="isMobile && !isCollapsed"
class="sidebar-overlay"
@click="isCollapsed = true"
/>
<!-- 主内容区 -->
<div class="content-main">
<div v-if="isMobile" class="content-header">
<div class="mobile-toggle" @click="isCollapsed = false">
<el-icon><i-ep-expand /></el-icon>
<span>菜单</span>
</div>
</div>
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" />
</keep-alive>
</router-view>
</div>
</div>
</template>
<style scoped lang="scss">
.console-page {
display: flex;
width: 100%;
overflow: auto;
&.is-collapsed {
.content-main {
margin-left: 0;
}
}
}
.nav-sidebar {
width: 240px;
height: 100%;
border-right: var(--header-border);
flex-shrink: 0;
display: flex;
flex-direction: column;
transition: all 0.3s ease;
position: relative;
z-index: 1001;
&.is-collapsed {
width: 64px;
.nav-title {
opacity: 0;
width: 0;
overflow: hidden;
}
}
}
.nav-header {
padding: 20px;
display: flex;
align-items: center;
justify-content: space-between;
min-height: 70px;
box-sizing: border-box;
}
.nav-title {
margin: 0;
font-size: 20px;
font-weight: 600;
color: var(--el-text-color-primary);
transition: all 0.3s ease;
white-space: nowrap;
}
.collapse-btn {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 4px;
cursor: pointer;
color: var(--el-text-color-secondary);
transition: all 0.2s;
flex-shrink: 0;
&:hover {
background-color: var(--el-fill-color-light);
color: var(--el-text-color-primary);
}
.el-icon {
font-size: 18px;
}
}
.nav-menu {
flex: 1;
border-right: none;
overflow-y: auto;
overflow-x: hidden;
:deep(.el-menu-item) {
&.is-active {
background-color: var(--el-color-primary-light-9);
color: var(--el-color-primary);
}
span {
transition: opacity 0.3s ease;
}
}
&:not(.el-menu--collapse) {
:deep(.el-menu-item) {
justify-content: flex-start;
}
}
}
.sidebar-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
transition: opacity 0.3s ease;
}
.content-main {
flex: 1;
//padding: 20px;
position: relative;
}
.content-header {
display: none;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid var(--el-border-color);
}
.mobile-toggle {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
border-radius: 4px;
background-color: var(--el-fill-color-light);
cursor: pointer;
color: var(--el-text-color-primary);
transition: background-color 0.2s;
&:hover {
background-color: var(--el-fill-color);
}
.el-icon {
font-size: 18px;
}
}
// 移动端适配
@media (max-width: 768px) {
.console-page {
position: relative;
}
.nav-sidebar {
position: fixed;
left: 0;
top: 0;
bottom: 0;
z-index: 1001;
transform: translateX(-100%);
transition: transform 0.3s ease;
&.is-collapsed {
transform: translateX(-100%);
width: 240px;
}
&:not(.is-collapsed) {
transform: translateX(0);
}
.collapse-btn {
display: none;
}
}
.content-header {
display: block;
}
.content-main {
//padding: 15px;
margin-left: 0 !important;
}
}
// 平板适配
@media (min-width: 769px) and (max-width: 1024px) {
.nav-sidebar {
width: 200px;
&.is-collapsed {
width: 64px;
}
}
.content-main {
padding: 15px;
}
}
</style>