feat: 完美复刻豆包收起展开动画

This commit is contained in:
何嘉悦 2025-05-21 03:14:09 +08:00
parent 7535bbb591
commit e19b4ff765
8 changed files with 84 additions and 58 deletions

View File

@ -24,8 +24,10 @@ export interface DesignConfigState {
collapseType: CollapseType;
// 是否折叠菜单
isCollapse: boolean;
// 头部折叠按钮是否被悬停
isCollapseHover: boolean;
// 安全区是否被悬停
isSafeAreaHover: boolean;
// 跟踪是否首次激活悬停
hasActivatedHover: boolean;
}
export const themeColorList: string[] = [
@ -67,8 +69,10 @@ const design: DesignConfigState = {
collapseType: 'followSystem',
// 是否折叠菜单
isCollapse: false,
// 头部折叠按钮是否被悬停
isCollapseHover: false,
// 安全区是否被悬停
isSafeAreaHover: false,
// 跟踪是否首次激活悬停
hasActivatedHover: false,
};
export default design;

View File

@ -14,7 +14,7 @@ export function useCollapseToggle(threshold: number = COLLAPSE_THRESHOLD) {
/** 核心折叠切换方法 */
const changeCollapse = () => {
// 切换最终折叠状态
designStore.setCollapseFinal(!designStore.isCollapse);
designStore.setCollapse(!designStore.isCollapse);
if (isAboveThreshold.value) {
// 宽屏逻辑

View File

@ -25,13 +25,13 @@ export function useWindowWidthObserver(
// 判断当前的折叠状态
switch (designStore.collapseType) {
case 'alwaysCollapsed':
designStore.setCollapseFinal(true);
designStore.setCollapse(true);
break;
case 'followSystem':
designStore.setCollapseFinal(!isAbove);
designStore.setCollapse(!isAbove);
break;
case 'alwaysExpanded':
designStore.setCollapseFinal(false);
designStore.setCollapse(false);
if (isAbove) {
// 大于的时候执行关闭动画
console.log('执行关闭动画');
@ -42,7 +42,7 @@ export function useWindowWidthObserver(
}
break;
case 'narrowExpandWideCollapse':
designStore.setCollapseFinal(isAbove);
designStore.setCollapse(isAbove);
}
console.log('最终的折叠状态:', designStore.isCollapse);

View File

@ -16,9 +16,8 @@ useSafeArea({
direction: 'left',
size: 50,
onChange(isInSafeArea) {
// console.log('', isInSafeArea, isCollapse.value);
// true
designStore.isCollapseHover = isInSafeArea;
designStore.isSafeAreaHover = isInSafeArea;
},
enabled: isCollapse, //
});
@ -58,6 +57,7 @@ useWindowWidthObserver();
.layout-container-main {
margin-left: var(--sidebar-left-container-default-width, 0px);
transition: margin-left 0.3s ease;
}
}

View File

@ -101,35 +101,17 @@ function handleChange() {
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>
<template>
<div
class="aside-container"
:class="{
'aside-container-suspended': designStore.isCollapseHover,
'aside-container-suspended': designStore.isSafeAreaHover,
'aside-container-collapse': designStore.isCollapse,
// no-delay
'no-delay': designStore.isCollapse && !designStore.hasActivatedHover,
}"
@mouseenter.stop="handleChangeMouse('enter')"
@mouseleave.stop="handleChangeMouse('leave')"
>
<div class="aside-wrapper">
<div v-if="!designStore.isCollapse" class="aside-header">
@ -301,33 +283,51 @@ function handleChangeMouse(type: string) {
//
transition-property: opacity, transform;
transition-duration: 0.2s, 0.2s;
transition-duration: 0.3s, 0.3s;
transition-timing-function: ease, ease;
transition-delay: 0.2s, 0s;
}
transition-delay: 0.3s, 0.3s;
//
.hover-delay {
transition-delay: 0.2s;
/* 新增:未激活悬停时覆盖延迟 */
&.no-delay {
transition-delay: 0s, 0s;
}
/* 禁用悬停事件 */
pointer-events: none;
&:hover,
&.aside-container-suspended {
pointer-events: auto;
/* 悬停激活后恢复事件响应 */
}
}
//
.aside-container-suspended {
position: absolute;
.aside-container-collapse:hover,
.aside-container-collapse.aside-container-suspended {
// aside-container-suspended
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;
// 沿
transition-property: opacity, transform;
transition-duration: 0.3s, 0.3s;
transition-timing-function: ease, ease;
transition-delay: 0s, 0s;
// -
.conversations-wrap {
height: calc(100vh - 155px) !important;
}
}
// 穿

View File

@ -10,7 +10,10 @@ const designStore = useDesignStore();
function handleChangeCollapse() {
changeCollapse();
designStore.isCollapseHover = false;
//
designStore.isSafeAreaHover = false;
//
designStore.hasActivatedHover = false;
if (!designStore.isCollapse) {
document.documentElement.style.setProperty(
`--sidebar-left-container-default-width`,

View File

@ -11,7 +11,8 @@ const {
layout: reLayout,
collapseType: reCollapseType,
isCollapse: reisCollapse,
isCollapseHover: reisCollapseHover,
isSafeAreaHover: reisSafeAreaHover,
hasActivatedHover: rehasActivatedHover,
} = designSetting;
export const useDesignStore = defineStore(
@ -44,17 +45,39 @@ export const useDesignStore = defineStore(
// 最终是否展开左侧菜单
const isCollapse = ref<boolean>(reisCollapse);
const setCollapseFinal = (collapseFinal: boolean) => {
const setCollapse = (collapseFinal: boolean) => {
isCollapse.value = collapseFinal;
};
// 折叠按钮是否被悬停
const isCollapseHover = ref<boolean>(reisCollapseHover);
const isSafeAreaHover = ref<boolean>(reisSafeAreaHover);
const setCollapseHover = (hover: boolean) => {
isCollapseHover.value = hover;
const setSafeAreaHover = (hover: boolean) => {
isSafeAreaHover.value = hover;
};
// 跟踪是否首次激活悬停
const hasActivatedHover = ref<boolean>(rehasActivatedHover);
// 两个监听不要合并
watch(
() => isCollapse.value,
(newValue) => {
if (newValue) {
hasActivatedHover.value = false;
}
},
{ deep: true },
);
watch(
() => isSafeAreaHover.value,
() => {
hasActivatedHover.value = true;
},
{ deep: true },
);
return {
darkMode,
setDarkMode,
@ -67,9 +90,10 @@ export const useDesignStore = defineStore(
collapseType,
setCollapseType,
isCollapse,
setCollapseFinal,
isCollapseHover,
setCollapseHover,
setCollapse,
isSafeAreaHover,
setSafeAreaHover,
hasActivatedHover,
};
},
{

View File

@ -3,23 +3,18 @@
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
// biome-ignore lint: disable
export {}
export {};
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
ElAside: typeof import('element-plus/es')['ElAside']
ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElImage: typeof import('element-plus/es')['ElImage']
ElInput: typeof import('element-plus/es')['ElInput']
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']
Popover: typeof import('./../src/components/Popover/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']