Compare commits

...

3 Commits

5 changed files with 195 additions and 42 deletions

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="main-layout"> <div class="main-layout">
<Sidebar /> <Sidebar class="sidebar" />
<main class="content-area"> <main class="content-area">
<!-- 路由视图将渲染在这里 --> <!-- 路由视图将渲染在这里 -->
<router-view /> <router-view />
@ -16,7 +16,7 @@ import Sidebar from '../components/Sidebar.vue'
.main-layout { .main-layout {
display: flex; display: flex;
height: 100%; height: 100%;
min-width: 640px; /* min-width: 640px; */
overflow: hidden; /* 防止内部滚动影响布局 */ overflow: hidden; /* 防止内部滚动影响布局 */
} }
@ -28,6 +28,21 @@ import Sidebar from '../components/Sidebar.vue'
background-color: #ffffff; /* 示例背景色 */ background-color: #ffffff; /* 示例背景色 */
} }
.sidebar {
transition: all 0.3s ease;
}
/* 移动端样式 */
@media screen and (max-width: 768px) {
.sidebar {
display: none;
}
.content-area {
width: 100%;
}
}
/* 简单的侧边栏列表样式 */ /* 简单的侧边栏列表样式 */
.sidebar ul { .sidebar ul {
list-style: none; list-style: none;

View File

@ -26,6 +26,11 @@ export const useSettingsStore = defineStore('settings', {
} }
}), }),
persist: {
key: 'settings',
paths: ['sidebarCollapsed']
},
actions: { actions: {
// 切换侧边栏状态 // 切换侧边栏状态
toggleSidebar() { toggleSidebar() {

View File

@ -2,6 +2,13 @@
<div class="chat-header"> <div class="chat-header">
<div class="header-content"> <div class="header-content">
<div class="title"> <div class="title">
<el-button
class="mobile-sidebar-toggle"
:icon="Expand"
@click="settingsStore.toggleSidebar"
text
v-if="isMobile"
/>
<span class="title-text">{{ title }}</span> <span class="title-text">{{ title }}</span>
<el-tag v-if="chatStore.currentConversation?.conversationStatus === 'typing'" <el-tag v-if="chatStore.currentConversation?.conversationStatus === 'typing'"
size="small" size="small"
@ -49,10 +56,10 @@
</template> </template>
<script setup> <script setup>
import { ref, computed } from 'vue' import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useChatStore } from '@/store/chat' import { useChatStore } from '@/store/chat'
import { useSettingsStore } from '@/store/settings' import { useSettingsStore } from '@/store/settings'
import { Delete, Setting, User } from '@element-plus/icons-vue' import { Delete, Setting, User, Expand } from '@element-plus/icons-vue'
import { ElMessageBox } from 'element-plus' import { ElMessageBox } from 'element-plus'
import HistoryButton from '@/components/chat/HistoryButton.vue' import HistoryButton from '@/components/chat/HistoryButton.vue'
import CustomerBackground from '@/components/chat/CustomerBackground.vue' import CustomerBackground from '@/components/chat/CustomerBackground.vue'
@ -61,6 +68,7 @@ const chatStore = useChatStore()
const settingsStore = useSettingsStore() const settingsStore = useSettingsStore()
const showUserInfo = ref(false) const showUserInfo = ref(false)
const customerBackgroundRef = ref(null) const customerBackgroundRef = ref(null)
const isMobile = ref(false)
const title = computed(() => { const title = computed(() => {
const conversation = chatStore.currentConversation const conversation = chatStore.currentConversation
@ -101,6 +109,19 @@ const handleSettings = () => {
// TODO: // TODO:
console.log('打开设置') console.log('打开设置')
} }
const checkMobile = () => {
isMobile.value = window.innerWidth <= 768
}
onMounted(() => {
checkMobile()
window.addEventListener('resize', checkMobile)
})
onUnmounted(() => {
window.removeEventListener('resize', checkMobile)
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -121,7 +142,38 @@ const handleSettings = () => {
.title { .title {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px; gap: 4px;
height: 100%;
.mobile-sidebar-toggle {
height: 24px;
width: 24px;
padding: 0;
color: #666;
transition: all 0.3s ease;
border: none;
background: transparent !important;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
margin-left: -4px;
&:hover {
color: #07c160;
background: transparent !important;
}
&:focus {
outline: none;
box-shadow: none;
}
:deep(.el-icon) {
font-size: 18px;
margin-top: 1px;
}
}
.title-text { .title-text {
font-size: 16px; font-size: 16px;
@ -131,6 +183,8 @@ const handleSettings = () => {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
line-height: 24px;
padding-top: 1px;
} }
.status-tag { .status-tag {

View File

@ -1,41 +1,49 @@
<template> <template>
<div class="mode-selector" :class="{ 'collapsed': settingsStore.sidebarCollapsed }"> <div>
<div class="mode-header"> <div
<h3 class="mode-title">对话场景</h3> class="mode-selector-backdrop"
<div class="header-actions"> :class="{ visible: !settingsStore.sidebarCollapsed }"
<HistoryButton v-if="!settingsStore.sidebarCollapsed" :icon-only="true" /> @click="settingsStore.toggleSidebar"
<el-button v-if="isMobile"
class="collapse-btn" ></div>
:icon="settingsStore.sidebarCollapsed ? Expand : Fold" <div class="mode-selector" :class="{ 'collapsed': settingsStore.sidebarCollapsed }">
text <div class="mode-header">
bg <h3 class="mode-title">对话场景</h3>
@click="settingsStore.toggleSidebar" <div class="header-actions">
/> <HistoryButton v-if="!settingsStore.sidebarCollapsed" :icon-only="true" />
<el-button
class="collapse-btn"
:icon="settingsStore.sidebarCollapsed ? Expand : Fold"
text
bg
@click="settingsStore.toggleSidebar"
/>
</div>
</div> </div>
</div> <div class="mode-list">
<div class="mode-list"> <div
<div v-for="mode in chatModes"
v-for="mode in chatModes" :key="mode.id"
:key="mode.id" :class="['mode-item', { active: chatStore.currentMode === mode.id }]"
:class="['mode-item', { active: chatStore.currentMode === mode.id }]" @click="selectMode(mode.id)"
@click="selectMode(mode.id)" >
> <span class="emoji-icon">{{ mode.icon }}</span>
<span class="emoji-icon">{{ mode.icon }}</span> <span v-if="!settingsStore.sidebarCollapsed" class="mode-name">{{ mode.name }}</span>
<span v-if="!settingsStore.sidebarCollapsed" class="mode-name">{{ mode.name }}</span> <ModeSetting v-if="!settingsStore.sidebarCollapsed" @command="(cmd) => handleModeSettings(cmd, mode.id)">
<ModeSetting v-if="!settingsStore.sidebarCollapsed" @command="(cmd) => handleModeSettings(cmd, mode.id)"> <el-dropdown-item command="edit">
<el-dropdown-item command="edit"> <el-icon><EditPen /></el-icon>
<el-icon><EditPen /></el-icon> 编辑设置
编辑设置 </el-dropdown-item>
</el-dropdown-item> <el-dropdown-item command="reset">
<el-dropdown-item command="reset"> <el-icon><RefreshRight /></el-icon>
<el-icon><RefreshRight /></el-icon> 重置设置
重置设置 </el-dropdown-item>
</el-dropdown-item> <el-dropdown-item command="delete" divided>
<el-dropdown-item command="delete" divided> <el-icon><Delete /></el-icon>
<el-icon><Delete /></el-icon> 删除场景
删除场景 </el-dropdown-item>
</el-dropdown-item> </ModeSetting>
</ModeSetting> </div>
</div> </div>
</div> </div>
</div> </div>
@ -44,7 +52,7 @@
<script setup> <script setup>
import { useChatStore } from '@/store/chat' import { useChatStore } from '@/store/chat'
import { useSettingsStore } from '@/store/settings' import { useSettingsStore } from '@/store/settings'
import { computed } from 'vue' import { computed, ref, onMounted, onUnmounted } from 'vue'
import { Delete, EditPen, RefreshRight, Expand, Fold } from '@element-plus/icons-vue' import { Delete, EditPen, RefreshRight, Expand, Fold } from '@element-plus/icons-vue'
import { ElDropdown, ElDropdownMenu, ElDropdownItem, ElButton, ElMessageBox } from 'element-plus' import { ElDropdown, ElDropdownMenu, ElDropdownItem, ElButton, ElMessageBox } from 'element-plus'
import ModeSetting from '@/components/chat/ModeSetting.vue' import ModeSetting from '@/components/chat/ModeSetting.vue'
@ -117,6 +125,21 @@ const handleModeSettings = (command, modeId) => {
break break
} }
} }
const isMobile = ref(false)
const checkMobile = () => {
isMobile.value = window.innerWidth <= 768
}
onMounted(() => {
checkMobile()
window.addEventListener('resize', checkMobile)
})
onUnmounted(() => {
window.removeEventListener('resize', checkMobile)
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -139,6 +162,8 @@ $hover-color-dark: #2d2d2d;
padding: 0; padding: 0;
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1); box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease; transition: all 0.3s ease;
position: relative;
z-index: 1000;
&.collapsed { &.collapsed {
width: 64px; width: 64px;
@ -349,6 +374,44 @@ $hover-color-dark: #2d2d2d;
} }
} }
/* 移动端样式 */
@media screen and (max-width: 768px) {
.mode-selector {
position: fixed;
left: 0;
top: 0;
bottom: 0;
transform: translateX(-100%);
z-index: 1000;
&.collapsed {
transform: translateX(-240px);
}
&:not(.collapsed) {
transform: translateX(0);
}
}
.mode-selector-backdrop {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
&.visible {
opacity: 1;
visibility: visible;
}
}
}
// @media (prefers-color-scheme: dark) { // @media (prefers-color-scheme: dark) {
// .mode-selector { // .mode-selector {
// background: $background-color-dark; // background: $background-color-dark;

View File

@ -55,4 +55,20 @@ const handleModeChange = (mode) => {
background-color: #f5f5f5; background-color: #f5f5f5;
position: relative; position: relative;
} }
/* 移动端样式 */
@media screen and (max-width: 768px) {
.chat-container {
position: relative;
}
.chat-wrapper {
width: 100%;
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
}
}
</style> </style>