feat: ✨ 新增关闭右侧菜单动画
This commit is contained in:
parent
1f5eeb2146
commit
7535bbb591
@ -24,6 +24,8 @@ export interface DesignConfigState {
|
|||||||
collapseType: CollapseType;
|
collapseType: CollapseType;
|
||||||
// 是否折叠菜单
|
// 是否折叠菜单
|
||||||
isCollapse: boolean;
|
isCollapse: boolean;
|
||||||
|
// 头部折叠按钮是否被悬停
|
||||||
|
isCollapseHover: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const themeColorList: string[] = [
|
export const themeColorList: string[] = [
|
||||||
@ -65,6 +67,8 @@ const design: DesignConfigState = {
|
|||||||
collapseType: 'followSystem',
|
collapseType: 'followSystem',
|
||||||
// 是否折叠菜单
|
// 是否折叠菜单
|
||||||
isCollapse: false,
|
isCollapse: false,
|
||||||
|
// 头部折叠按钮是否被悬停
|
||||||
|
isCollapseHover: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default design;
|
export default design;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { COLLAPSE_THRESHOLD } from '@/config/index';
|
import { COLLAPSE_THRESHOLD } from '@/config/index';
|
||||||
import { useWindowWidthObserver } from '@/hooks/useWindowWidthObserver';
|
import { useWindowWidthObserver } from '@/hooks/useWindowWidthObserver';
|
||||||
import { useDesignStore } from '@/store/modules/design';
|
import { useDesignStore } from '@/store';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 侧边栏折叠切换逻辑组合式函数 方便多个页面调用
|
* 侧边栏折叠切换逻辑组合式函数 方便多个页面调用
|
||||||
|
|||||||
86
src/hooks/useSafeArea.ts
Normal file
86
src/hooks/useSafeArea.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import type { Ref } from 'vue';
|
||||||
|
|
||||||
|
type SafeAreaDirection = 'left' | 'right' | 'top' | 'bottom';
|
||||||
|
|
||||||
|
interface SafeAreaOptions {
|
||||||
|
direction: SafeAreaDirection; // 必选方向
|
||||||
|
size: number; // 安全区域尺寸(像素)
|
||||||
|
onChange?: (isInArea: boolean, e: MouseEvent) => void; // 状态变化回调
|
||||||
|
enabled?: Ref<boolean>; // 改为接收响应式变量(Ref 类型)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSafeArea(options: SafeAreaOptions): { isInSafeArea: Ref<boolean> } {
|
||||||
|
const { direction, size, onChange, enabled = ref(true) } = options; // 默认值改为 ref(true)
|
||||||
|
const isInSafeArea = ref(false);
|
||||||
|
let isThrottling = false;
|
||||||
|
|
||||||
|
// 方向检测逻辑(未修改)
|
||||||
|
const checkInArea = (e: MouseEvent) => {
|
||||||
|
const { clientX, clientY } = e;
|
||||||
|
const w = window.innerWidth;
|
||||||
|
const h = window.innerHeight;
|
||||||
|
return direction === 'left'
|
||||||
|
? clientX <= size
|
||||||
|
: direction === 'right'
|
||||||
|
? clientX >= w - size
|
||||||
|
: direction === 'top'
|
||||||
|
? clientY <= size
|
||||||
|
: direction === 'bottom'
|
||||||
|
? clientY >= h - size
|
||||||
|
: false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 鼠标移动处理函数(未修改)
|
||||||
|
const handleMouseMove = (e: MouseEvent) => {
|
||||||
|
if (!enabled.value || isThrottling)
|
||||||
|
return; // 直接使用 enabled.value 判断
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const current = checkInArea(e);
|
||||||
|
if (current !== isInSafeArea.value) {
|
||||||
|
isInSafeArea.value = current;
|
||||||
|
onChange?.(current, e);
|
||||||
|
}
|
||||||
|
isThrottling = false;
|
||||||
|
});
|
||||||
|
isThrottling = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 动态绑定/解绑事件(简化逻辑)
|
||||||
|
const toggleListener = (enable: boolean) => {
|
||||||
|
if (enable) {
|
||||||
|
window.addEventListener('mousemove', handleMouseMove);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
window.removeEventListener('mousemove', handleMouseMove);
|
||||||
|
isInSafeArea.value = false; // 关闭时重置状态
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听 enabled 的变化(直接监听传入的 ref)
|
||||||
|
watch(
|
||||||
|
enabled,
|
||||||
|
(newVal) => {
|
||||||
|
toggleListener(newVal);
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
); // 立即执行一次,处理初始状态
|
||||||
|
|
||||||
|
// 组件卸载时强制解绑
|
||||||
|
onUnmounted(() => {
|
||||||
|
toggleListener(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
return { isInSafeArea };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用示例
|
||||||
|
// 外部组件中
|
||||||
|
// const isListening = ref(false); // 响应式开关
|
||||||
|
|
||||||
|
// const { isInSafeArea } = useSafeArea({
|
||||||
|
// direction: 'left',
|
||||||
|
// size: 50,
|
||||||
|
// onChange: (isIn, e) => console.log('状态变化:', isIn),
|
||||||
|
// enabled: isListening // 直接传入响应式变量(无需 .value)
|
||||||
|
// });
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import type { MaybeRef } from 'vue';
|
import type { MaybeRef } from 'vue';
|
||||||
import { onBeforeUnmount, ref, unref, watch } from 'vue';
|
import { onBeforeUnmount, ref, unref, watch } from 'vue';
|
||||||
import { COLLAPSE_THRESHOLD, SIDE_BAR_WIDTH } from '@/config/index';
|
import { COLLAPSE_THRESHOLD, SIDE_BAR_WIDTH } from '@/config/index';
|
||||||
import { useDesignStore } from '@/store/modules/design';
|
import { useDesignStore } from '@/store';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 这里逻辑是研究豆包的折叠逻辑后,设计的折叠方法
|
* 这里逻辑是研究豆包的折叠逻辑后,设计的折叠方法
|
||||||
@ -28,7 +28,6 @@ export function useWindowWidthObserver(
|
|||||||
designStore.setCollapseFinal(true);
|
designStore.setCollapseFinal(true);
|
||||||
break;
|
break;
|
||||||
case 'followSystem':
|
case 'followSystem':
|
||||||
designStore.setCollapseFinal(!isAbove);
|
|
||||||
designStore.setCollapseFinal(!isAbove);
|
designStore.setCollapseFinal(!isAbove);
|
||||||
break;
|
break;
|
||||||
case 'alwaysExpanded':
|
case 'alwaysExpanded':
|
||||||
@ -44,7 +43,6 @@ export function useWindowWidthObserver(
|
|||||||
break;
|
break;
|
||||||
case 'narrowExpandWideCollapse':
|
case 'narrowExpandWideCollapse':
|
||||||
designStore.setCollapseFinal(isAbove);
|
designStore.setCollapseFinal(isAbove);
|
||||||
designStore.setCollapseFinal(isAbove);
|
|
||||||
}
|
}
|
||||||
console.log('最终的折叠状态:', designStore.isCollapse);
|
console.log('最终的折叠状态:', designStore.isCollapse);
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,27 @@
|
|||||||
<!-- 纵向布局作为基础布局 -->
|
<!-- 纵向布局作为基础布局 -->
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useSafeArea } from '@/hooks/useSafeArea';
|
||||||
import { useWindowWidthObserver } from '@/hooks/useWindowWidthObserver';
|
import { useWindowWidthObserver } from '@/hooks/useWindowWidthObserver';
|
||||||
import Aside from '@/layouts/components/Aside/index.vue';
|
import Aside from '@/layouts/components/Aside/index.vue';
|
||||||
import Header from '@/layouts/components/Header/index.vue';
|
import Header from '@/layouts/components/Header/index.vue';
|
||||||
import Main from '@/layouts/components/Main/index.vue';
|
import Main from '@/layouts/components/Main/index.vue';
|
||||||
import { useDesignStore } from '@/store/modules/design';
|
import { useDesignStore } from '@/store';
|
||||||
|
|
||||||
const designStore = useDesignStore();
|
const designStore = useDesignStore();
|
||||||
|
|
||||||
console.log('每次加载全局的折叠状态', designStore.collapseType);
|
const isCollapse = computed(() => designStore.isCollapse);
|
||||||
|
|
||||||
|
/* 是否移入了安全区 */
|
||||||
|
useSafeArea({
|
||||||
|
direction: 'left',
|
||||||
|
size: 50,
|
||||||
|
onChange(isInSafeArea) {
|
||||||
|
// console.log('是否移入了安全区', isInSafeArea, isCollapse.value);
|
||||||
|
// 设置悬停为 true
|
||||||
|
designStore.isCollapseHover = isInSafeArea;
|
||||||
|
},
|
||||||
|
enabled: isCollapse, // 折叠才开启监听
|
||||||
|
});
|
||||||
|
|
||||||
/** 监听窗口大小变化,折叠侧边栏 */
|
/** 监听窗口大小变化,折叠侧边栏 */
|
||||||
useWindowWidthObserver();
|
useWindowWidthObserver();
|
||||||
@ -20,9 +33,7 @@ useWindowWidthObserver();
|
|||||||
<Header />
|
<Header />
|
||||||
</el-header>
|
</el-header>
|
||||||
<el-container class="layout-container-main">
|
<el-container class="layout-container-main">
|
||||||
<Transition :class="designStore.pageAnimateType">
|
<Aside />
|
||||||
<Aside v-if="!designStore.isCollapse" class="layout-aside transition-all" />
|
|
||||||
</Transition>
|
|
||||||
<el-main class="layout-main">
|
<el-main class="layout-main">
|
||||||
<!-- 路由页面 -->
|
<!-- 路由页面 -->
|
||||||
<Main />
|
<Main />
|
||||||
@ -35,21 +46,12 @@ useWindowWidthObserver();
|
|||||||
.layout-container {
|
.layout-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
.layout-header {
|
.layout-header {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-aside {
|
|
||||||
overflow: hidden;
|
|
||||||
width: var(--sidebar-left-container-default-width, 0px);
|
|
||||||
height: 100%;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 10;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layout-main {
|
.layout-main {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
<!-- Aside 侧边栏 -->
|
<!-- Aside 侧边栏 -->
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { GroupableOptions } from 'vue-element-plus-x/types/Conversations';
|
||||||
import logo from '@/assets/images/logo.png';
|
import logo from '@/assets/images/logo.png';
|
||||||
import Collapse from '@/layouts/components/Header/components/Collapse.vue';
|
import Collapse from '@/layouts/components/Header/components/Collapse.vue';
|
||||||
|
import { useDesignStore } from '@/store';
|
||||||
|
|
||||||
|
const designStore = useDesignStore();
|
||||||
|
|
||||||
/* 创建会话 开始 */
|
/* 创建会话 开始 */
|
||||||
function handleCreatChat() {
|
function handleCreatChat() {
|
||||||
@ -10,19 +14,125 @@ function handleCreatChat() {
|
|||||||
/* 创建会话 结束 */
|
/* 创建会话 结束 */
|
||||||
|
|
||||||
/* 会话组件 开始 */
|
/* 会话组件 开始 */
|
||||||
const active = ref();
|
const active = ref('m1');
|
||||||
const conversationsList = ref([]);
|
const conversationsList = ref([
|
||||||
|
{
|
||||||
|
key: 'm1',
|
||||||
|
label: '菜单测试项目 1',
|
||||||
|
group: '工作',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'm2',
|
||||||
|
label: '菜单测试项目 2',
|
||||||
|
disabled: true,
|
||||||
|
group: '工作',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'm3',
|
||||||
|
label: '菜单测试项目 3',
|
||||||
|
group: '工作',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'm4',
|
||||||
|
label: '菜单测试项目 4',
|
||||||
|
group: '学习',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'm5',
|
||||||
|
label: '菜单测试项目 5',
|
||||||
|
group: '学习',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'm6',
|
||||||
|
label: '菜单测试项目 6',
|
||||||
|
group: '学习',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'm7',
|
||||||
|
label: '菜单测试项目 7',
|
||||||
|
group: '学习',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'm8',
|
||||||
|
label: '菜单测试项目 8',
|
||||||
|
group: '个人',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'm9',
|
||||||
|
label: '菜单测试项目 9',
|
||||||
|
group: '个人',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'm10',
|
||||||
|
label: '菜单测试项目 10',
|
||||||
|
group: '个人',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'm11',
|
||||||
|
label: '菜单测试项目 11',
|
||||||
|
group: '个人',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'm12',
|
||||||
|
label: '菜单测试项目 12',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'm13',
|
||||||
|
label: '菜单测试项目 13',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'm14',
|
||||||
|
label: '菜单测试项目 14',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 自定义分组选项
|
||||||
|
const customGroupOptions: GroupableOptions = {
|
||||||
|
// 自定义分组排序,学习 > 工作 > 个人 > 未分组
|
||||||
|
sort: (a: any, b: any) => {
|
||||||
|
const order: Record<string, number> = { 学习: 0, 工作: 1, 个人: 2, 未分组: 3 };
|
||||||
|
const orderA = order[a] !== undefined ? order[a] : 999;
|
||||||
|
const orderB = order[b] !== undefined ? order[b] : 999;
|
||||||
|
return orderA - orderB;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
function handleChange() {
|
function handleChange() {
|
||||||
console.log('点击了会话');
|
console.log('点击了会话');
|
||||||
}
|
}
|
||||||
/* 会话组件 结束 */
|
/* 会话组件 结束 */
|
||||||
|
|
||||||
|
/* 鼠标事件 开始 */
|
||||||
|
// 添加悬停和移除的类名自己控制
|
||||||
|
const isHoverSelf = ref(false);
|
||||||
|
function handleChangeMouse(type: string) {
|
||||||
|
if (designStore.isCollapse) {
|
||||||
|
if (type === 'enter') {
|
||||||
|
isHoverSelf.value = !!designStore.isCollapse;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
isHoverSelf.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
isHoverSelf.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* 鼠标事件 结束 */
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="aside-container">
|
<div
|
||||||
|
class="aside-container"
|
||||||
|
:class="{
|
||||||
|
'aside-container-suspended': designStore.isCollapseHover,
|
||||||
|
'aside-container-collapse': designStore.isCollapse,
|
||||||
|
}"
|
||||||
|
@mouseenter.stop="handleChangeMouse('enter')"
|
||||||
|
@mouseleave.stop="handleChangeMouse('leave')"
|
||||||
|
>
|
||||||
<div class="aside-wrapper">
|
<div class="aside-wrapper">
|
||||||
<div class="aside-header">
|
<div v-if="!designStore.isCollapse" class="aside-header">
|
||||||
<div class="flex items-center gap-8px hover:cursor-pointer" @click="handleCreatChat">
|
<div class="flex items-center gap-8px hover:cursor-pointer" @click="handleCreatChat">
|
||||||
<el-image :src="logo" alt="logo" fit="cover" class="logo-img" />
|
<el-image :src="logo" alt="logo" fit="cover" class="logo-img" />
|
||||||
<span class="logo-text max-w-150px text-overflow">Elemennt-Plus-X</span>
|
<span class="logo-text max-w-150px text-overflow">Elemennt-Plus-X</span>
|
||||||
@ -30,47 +140,58 @@ function handleChange() {
|
|||||||
<Collapse class="ml-auto" />
|
<Collapse class="ml-auto" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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-body">
|
<div class="aside-body">
|
||||||
<div v-if="conversationsList.length > 0" class="flex h-full">
|
<div class="creat-chat-btn-wrapper">
|
||||||
<Conversations
|
<div class="creat-chat-btn" @click="handleCreatChat">
|
||||||
:active="active"
|
<el-icon class="add-icon">
|
||||||
:items="conversationsList"
|
<Plus />
|
||||||
row-key="id"
|
</el-icon>
|
||||||
label-key="sessionTitle"
|
<span class="creat-chat-text">新对话</span>
|
||||||
@change="handleChange"
|
<SvgIcon name="ctrl+k" size="37" />
|
||||||
/>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-empty v-else class="h-full flex-center" description="暂无对话记录" />
|
<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="35"
|
||||||
|
show-built-in-menu
|
||||||
|
:groupable="customGroupOptions"
|
||||||
|
row-key="key"
|
||||||
|
@change="handleChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-empty v-else class="h-full flex-center" description="暂无对话记录" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
// 基础样式
|
||||||
.aside-container {
|
.aside-container {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
left: 0;
|
||||||
user-select: none;
|
top: 0;
|
||||||
box-sizing: border-box;
|
position: absolute;
|
||||||
|
width: var(--sidebar-default-width);
|
||||||
|
pointer-events: auto;
|
||||||
background-color: var(--sidebar-background-color);
|
background-color: var(--sidebar-background-color);
|
||||||
border-right: 1px solid rgba(0, 0, 0, 0.08);
|
border-right: 0.5px solid var(--s-color-border-tertiary, rgba(0, 0, 0, 0.08));
|
||||||
|
z-index: 11;
|
||||||
|
|
||||||
.aside-wrapper {
|
.aside-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
|
// 侧边栏头部样式
|
||||||
.aside-header {
|
.aside-header {
|
||||||
height: 36px;
|
height: 36px;
|
||||||
margin: 10px 12px 0px;
|
margin: 10px 12px 0px;
|
||||||
@ -103,53 +224,122 @@ function handleChange() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.creat-chat-btn-wrapper {
|
// 侧边栏内容样式
|
||||||
padding: 0 12px;
|
.aside-body {
|
||||||
|
.creat-chat-btn-wrapper {
|
||||||
|
padding: 0 12px;
|
||||||
|
|
||||||
.creat-chat-btn {
|
.creat-chat-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 6px;
|
||||||
|
margin-top: 16px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
background-color: rgba(0, 87, 255, 0.06);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid rgba(0, 102, 255, 0.15);
|
||||||
|
cursor: pointer;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(0, 87, 255, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.creat-chat-text {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-icon {
|
||||||
|
margin-left: auto;
|
||||||
|
height: 24px;
|
||||||
|
color: rgba(0, 87, 255, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.aside-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
padding: 8px 6px;
|
height: 100%;
|
||||||
margin-top: 16px;
|
flex: 1;
|
||||||
margin-bottom: 6px;
|
min-height: 0;
|
||||||
color: #0057ff;
|
|
||||||
background-color: rgba(0, 87, 255, 0.06);
|
|
||||||
border-radius: 12px;
|
|
||||||
border: 1px solid rgba(0, 102, 255, 0.15);
|
|
||||||
cursor: pointer;
|
|
||||||
gap: 6px;
|
|
||||||
|
|
||||||
&:hover {
|
// 会话列表高度-基础样式
|
||||||
background-color: rgba(0, 87, 255, 0.12);
|
.conversations-wrap {
|
||||||
}
|
height: calc(100vh - 110px);
|
||||||
|
|
||||||
.creat-chat-text {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 700;
|
|
||||||
line-height: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-icon {
|
|
||||||
font-size: 16px;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.svg-icon {
|
|
||||||
margin-left: auto;
|
|
||||||
height: 24px;
|
|
||||||
color: rgba(0, 87, 255, 0.3);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.aside-body {
|
// 折叠样式
|
||||||
display: flex;
|
.aside-container-collapse {
|
||||||
flex-direction: column;
|
position: absolute;
|
||||||
height: 100%;
|
opacity: 0;
|
||||||
flex: 1;
|
// 向左偏移一个宽度
|
||||||
min-height: 0;
|
transform: translateX(-100%);
|
||||||
}
|
top: 54px;
|
||||||
|
border-radius: 15px;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||||
|
box-shadow:
|
||||||
|
0px 10px 20px 0px rgba(0, 0, 0, 0.1),
|
||||||
|
0px 0px 1px 0px rgba(0, 0, 0, 0.15);
|
||||||
|
height: auto;
|
||||||
|
z-index: 22;
|
||||||
|
overflow: hidden;
|
||||||
|
max-height: calc(100% - 110px);
|
||||||
|
padding-bottom: 12px;
|
||||||
|
|
||||||
|
// 指定样式过渡
|
||||||
|
transition-property: opacity, transform;
|
||||||
|
transition-duration: 0.2s, 0.2s;
|
||||||
|
transition-timing-function: ease, ease;
|
||||||
|
transition-delay: 0.2s, 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 悬停延迟样式
|
||||||
|
.hover-delay {
|
||||||
|
transition-delay: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 悬停样式
|
||||||
|
.aside-container-suspended {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(15px);
|
||||||
|
top: 54px;
|
||||||
|
border-radius: 15px;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||||
|
box-shadow:
|
||||||
|
0px 10px 20px 0px rgba(0, 0, 0, 0.1),
|
||||||
|
0px 0px 1px 0px rgba(0, 0, 0, 0.15);
|
||||||
|
height: auto;
|
||||||
|
z-index: 22;
|
||||||
|
overflow: hidden;
|
||||||
|
max-height: calc(100% - 110px);
|
||||||
|
padding-bottom: 12px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 样式穿透
|
||||||
|
:deep() {
|
||||||
|
// 会话列表背景色
|
||||||
|
.conversations-list {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 群组标题样式 和 侧边栏菜单背景色一致
|
||||||
|
.conversation-group-title {
|
||||||
|
background-color: var(--sidebar-background-color) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -3,13 +3,14 @@
|
|||||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||||||
import { SIDE_BAR_WIDTH } from '@/config/index';
|
import { SIDE_BAR_WIDTH } from '@/config/index';
|
||||||
import { useCollapseToggle } from '@/hooks/useCollapseToggle';
|
import { useCollapseToggle } from '@/hooks/useCollapseToggle';
|
||||||
import { useDesignStore } from '@/store/modules/design';
|
import { useDesignStore } from '@/store';
|
||||||
|
|
||||||
const { changeCollapse } = useCollapseToggle();
|
const { changeCollapse } = useCollapseToggle();
|
||||||
const designStore = useDesignStore();
|
const designStore = useDesignStore();
|
||||||
|
|
||||||
function handleChangeCollapse() {
|
function handleChangeCollapse() {
|
||||||
changeCollapse();
|
changeCollapse();
|
||||||
|
designStore.isCollapseHover = false;
|
||||||
if (!designStore.isCollapse) {
|
if (!designStore.isCollapse) {
|
||||||
document.documentElement.style.setProperty(
|
document.documentElement.style.setProperty(
|
||||||
`--sidebar-left-container-default-width`,
|
`--sidebar-left-container-default-width`,
|
||||||
|
|||||||
@ -30,24 +30,34 @@ function handleClickTitle() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div class="w-full h-full flex flex-col justify-center">
|
||||||
class="title-editing-container hover:bg-[rgba(0,0,0,.04)] cursor-pointer rounded-md p-4px flex items-center"
|
<div class="box-border mr-20px">
|
||||||
@click="handleClickTitle"
|
<div
|
||||||
>
|
class="title-editing-container p-4px w-fit max-w-full flex items-center justify-start cursor-pointer select-none hover:bg-[rgba(0,0,0,.04)] cursor-pointer rounded-md font-size-14px"
|
||||||
<span class="font-size-14px text-overflow max-w-320px">标题编辑标题编辑标题编辑标题编辑标题编辑标题编辑标题编辑标题编辑</span>
|
@click="handleClickTitle"
|
||||||
<SvgIcon name="draft-line" size="14" />
|
>
|
||||||
|
<div class="text-overflow select-none pr-8px">
|
||||||
|
标题编辑标题编辑标题编辑标题编辑标题编辑标题编辑标题编辑标题编辑
|
||||||
|
</div>
|
||||||
|
<SvgIcon name="draft-line" size="14" class="flex-none c-gray-500" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.title-editing-container {
|
.title-editing-container {
|
||||||
|
transition: all 0.3s ease;
|
||||||
&:hover {
|
&:hover {
|
||||||
.svg-icon {
|
.svg-icon {
|
||||||
|
opacity: 1;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.svg-icon {
|
.svg-icon {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
opacity: 0.5;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<!-- Header 头部 -->
|
<!-- Header 头部 -->
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { SIDE_BAR_WIDTH } from '@/config/index';
|
import { SIDE_BAR_WIDTH } from '@/config/index';
|
||||||
import { useUserStore } from '@/store';
|
import { useDesignStore, useUserStore } from '@/store';
|
||||||
import Avatar from './components/Avatar.vue';
|
import Avatar from './components/Avatar.vue';
|
||||||
import Collapse from './components/Collapse.vue';
|
import Collapse from './components/Collapse.vue';
|
||||||
import CreateChat from './components/CreateChat.vue';
|
import CreateChat from './components/CreateChat.vue';
|
||||||
@ -9,13 +9,21 @@ import LoginBtn from './components/LoginBtn.vue';
|
|||||||
import TitleEditing from './components/TitleEditing.vue';
|
import TitleEditing from './components/TitleEditing.vue';
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
const designStore = useDesignStore();
|
||||||
console.log('userStore', userStore.token);
|
console.log('userStore', userStore.token);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.documentElement.style.setProperty(
|
// 全局设置侧边栏默认宽度 (这个是不变的,一开始就设置)
|
||||||
`--sidebar-left-container-default-width`,
|
document.documentElement.style.setProperty(`--sidebar-default-width`, `${SIDE_BAR_WIDTH}px`);
|
||||||
`${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`,
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -23,16 +31,25 @@ onMounted(() => {
|
|||||||
<div class="header-container">
|
<div class="header-container">
|
||||||
<div class="header-box relative z-10 top-0 left-0 right-0">
|
<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="absolute left-0 right-0 top-0 bottom-0 flex items-center flex-row">
|
||||||
<!-- 左边 -->
|
<div
|
||||||
<div class="left-box flex h-full items-center pl-20px gap-12px flex-shrink-0 flex-row">
|
class="overflow-hidden flex h-full items-center flex-row flex-1 w-fit flex-shrink-0 min-w-0"
|
||||||
<Collapse />
|
>
|
||||||
<CreateChat />
|
<div class="w-full flex items-center flex-row">
|
||||||
</div>
|
<!-- 左边 -->
|
||||||
|
<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 class="w-0.5px h-30px bg-[rgba(217,217,217)]" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 中间 -->
|
<!-- 中间 -->
|
||||||
<div class="middle-box flex h-full items-center gap-12px flex-1 pl-12px">
|
<div class="middle-box flex-1 min-w-0 ml-12px">
|
||||||
<div class="w-0.5px h-30px bg-[rgba(217,217,217)]" />
|
<TitleEditing />
|
||||||
<TitleEditing />
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 右边 -->
|
<!-- 右边 -->
|
||||||
@ -54,6 +71,7 @@ onMounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
.header-box {
|
.header-box {
|
||||||
height: var(--header-container-default-heigth);
|
height: var(--header-container-default-heigth);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<!-- Main -->
|
<!-- Main -->
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useDesignStore } from '@/store/modules/design';
|
import { useDesignStore } from '@/store';
|
||||||
import { useKeepAliveStore } from '@/store/modules/keepAlive';
|
import { useKeepAliveStore } from '@/store/modules/keepAlive';
|
||||||
|
|
||||||
const designStore = useDesignStore();
|
const designStore = useDesignStore();
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
import type { LayoutType } from '@/config/design';
|
import type { LayoutType } from '@/config/design';
|
||||||
// import { useScreenStore } from '@/hooks/useScreen';
|
// import { useScreenStore } from '@/hooks/useScreen';
|
||||||
import LayoutVertical from '@/layouts/LayoutVertical/index.vue';
|
import LayoutVertical from '@/layouts/LayoutVertical/index.vue';
|
||||||
import { useDesignStore } from '@/store/modules/design';
|
import { useDesignStore } from '@/store';
|
||||||
|
|
||||||
// 这里添加布局类型
|
// 这里添加布局类型
|
||||||
const LayoutComponent: Record<LayoutType, Component> = {
|
const LayoutComponent: Record<LayoutType, Component> = {
|
||||||
|
|||||||
@ -30,7 +30,7 @@ function handleSend() {
|
|||||||
@submit="handleSend"
|
@submit="handleSend"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<div class="flex items-center gap-8px">
|
<div class="flex-1 flex items-center gap-8px flex-none w-fit overflow-hidden">
|
||||||
<div
|
<div
|
||||||
class="flex items-center gap-4px px-12px py-8px rounded-15px cursor-pointer font-size-12px border-1px border-gray border-solid hover:bg-[rgba(0,0,0,.04)]"
|
class="flex items-center gap-4px px-12px py-8px rounded-15px cursor-pointer font-size-12px border-1px border-gray border-solid hover:bg-[rgba(0,0,0,.04)]"
|
||||||
>
|
>
|
||||||
@ -58,7 +58,8 @@ function handleSend() {
|
|||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.chat-home-container {
|
.chat-home-container {
|
||||||
width: 100%;
|
padding: 0 16px;
|
||||||
|
width: calc(100% - 32px);
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@ -7,4 +7,5 @@ store.use(piniaPluginPersistedstate);
|
|||||||
|
|
||||||
export default store;
|
export default store;
|
||||||
|
|
||||||
|
export * from './modules/design';
|
||||||
export * from './modules/user';
|
export * from './modules/user';
|
||||||
|
|||||||
@ -11,6 +11,7 @@ const {
|
|||||||
layout: reLayout,
|
layout: reLayout,
|
||||||
collapseType: reCollapseType,
|
collapseType: reCollapseType,
|
||||||
isCollapse: reisCollapse,
|
isCollapse: reisCollapse,
|
||||||
|
isCollapseHover: reisCollapseHover,
|
||||||
} = designSetting;
|
} = designSetting;
|
||||||
|
|
||||||
export const useDesignStore = defineStore(
|
export const useDesignStore = defineStore(
|
||||||
@ -47,6 +48,13 @@ export const useDesignStore = defineStore(
|
|||||||
isCollapse.value = collapseFinal;
|
isCollapse.value = collapseFinal;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 折叠按钮是否被悬停
|
||||||
|
const isCollapseHover = ref<boolean>(reisCollapseHover);
|
||||||
|
|
||||||
|
const setCollapseHover = (hover: boolean) => {
|
||||||
|
isCollapseHover.value = hover;
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
darkMode,
|
darkMode,
|
||||||
setDarkMode,
|
setDarkMode,
|
||||||
@ -60,6 +68,8 @@ export const useDesignStore = defineStore(
|
|||||||
setCollapseType,
|
setCollapseType,
|
||||||
isCollapse,
|
isCollapse,
|
||||||
setCollapseFinal,
|
setCollapseFinal,
|
||||||
|
isCollapseHover,
|
||||||
|
setCollapseHover,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
7
types/components.d.ts
vendored
7
types/components.d.ts
vendored
@ -3,18 +3,23 @@
|
|||||||
// Generated by unplugin-vue-components
|
// Generated by unplugin-vue-components
|
||||||
// Read more: https://github.com/vuejs/core/pull/3399
|
// Read more: https://github.com/vuejs/core/pull/3399
|
||||||
// biome-ignore lint: disable
|
// biome-ignore lint: disable
|
||||||
export {};
|
export {}
|
||||||
|
|
||||||
/* prettier-ignore */
|
/* prettier-ignore */
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
|
ElAside: typeof import('element-plus/es')['ElAside']
|
||||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||||
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
||||||
ElHeader: typeof import('element-plus/es')['ElHeader']
|
ElHeader: typeof import('element-plus/es')['ElHeader']
|
||||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||||
ElImage: typeof import('element-plus/es')['ElImage']
|
ElImage: typeof import('element-plus/es')['ElImage']
|
||||||
|
ElInput: typeof import('element-plus/es')['ElInput']
|
||||||
ElMain: typeof import('element-plus/es')['ElMain']
|
ElMain: typeof import('element-plus/es')['ElMain']
|
||||||
|
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||||
|
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
||||||
|
ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||||
IconSelect: typeof import('./../src/components/IconSelect/index.vue')['default']
|
IconSelect: typeof import('./../src/components/IconSelect/index.vue')['default']
|
||||||
Popover: typeof import('./../src/components/Popover/index.vue')['default']
|
Popover: typeof import('./../src/components/Popover/index.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user