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

View File

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

View File

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

View File

@ -16,9 +16,8 @@ useSafeArea({
direction: 'left', direction: 'left',
size: 50, size: 50,
onChange(isInSafeArea) { onChange(isInSafeArea) {
// console.log('', isInSafeArea, isCollapse.value);
// true // true
designStore.isCollapseHover = isInSafeArea; designStore.isSafeAreaHover = isInSafeArea;
}, },
enabled: isCollapse, // enabled: isCollapse, //
}); });
@ -58,6 +57,7 @@ useWindowWidthObserver();
.layout-container-main { .layout-container-main {
margin-left: var(--sidebar-left-container-default-width, 0px); 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('点击了会话'); 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 <div
class="aside-container" class="aside-container"
:class="{ :class="{
'aside-container-suspended': designStore.isCollapseHover, 'aside-container-suspended': designStore.isSafeAreaHover,
'aside-container-collapse': designStore.isCollapse, '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 class="aside-wrapper">
<div v-if="!designStore.isCollapse" class="aside-header"> <div v-if="!designStore.isCollapse" class="aside-header">
@ -301,33 +283,51 @@ function handleChangeMouse(type: string) {
// //
transition-property: opacity, transform; transition-property: opacity, transform;
transition-duration: 0.2s, 0.2s; transition-duration: 0.3s, 0.3s;
transition-timing-function: ease, ease; transition-timing-function: ease, ease;
transition-delay: 0.2s, 0s; transition-delay: 0.3s, 0.3s;
}
// /* 新增:未激活悬停时覆盖延迟 */
.hover-delay { &.no-delay {
transition-delay: 0.2s; transition-delay: 0s, 0s;
}
/* 禁用悬停事件 */
pointer-events: none;
&:hover,
&.aside-container-suspended {
pointer-events: auto;
/* 悬停激活后恢复事件响应 */
}
} }
// //
.aside-container-suspended { .aside-container-collapse:hover,
position: absolute; .aside-container-collapse.aside-container-suspended {
// aside-container-suspended
opacity: 1; opacity: 1;
transform: translateX(15px); transform: translateX(15px);
top: 54px;
border-radius: 15px; border-radius: 15px;
border: 1px solid rgba(0, 0, 0, 0.08); border: 1px solid rgba(0, 0, 0, 0.08);
box-shadow: box-shadow:
0px 10px 20px 0px rgba(0, 0, 0, 0.1), 0px 10px 20px 0px rgba(0, 0, 0, 0.1),
0px 0px 1px 0px rgba(0, 0, 0, 0.15); 0px 0px 1px 0px rgba(0, 0, 0, 0.15);
height: auto; height: auto;
z-index: 22;
overflow: hidden; overflow: hidden;
max-height: calc(100% - 110px); max-height: calc(100% - 110px);
padding-bottom: 12px; 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() { function handleChangeCollapse() {
changeCollapse(); changeCollapse();
designStore.isCollapseHover = false; //
designStore.isSafeAreaHover = false;
//
designStore.hasActivatedHover = 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`,

View File

@ -11,7 +11,8 @@ const {
layout: reLayout, layout: reLayout,
collapseType: reCollapseType, collapseType: reCollapseType,
isCollapse: reisCollapse, isCollapse: reisCollapse,
isCollapseHover: reisCollapseHover, isSafeAreaHover: reisSafeAreaHover,
hasActivatedHover: rehasActivatedHover,
} = designSetting; } = designSetting;
export const useDesignStore = defineStore( export const useDesignStore = defineStore(
@ -44,17 +45,39 @@ export const useDesignStore = defineStore(
// 最终是否展开左侧菜单 // 最终是否展开左侧菜单
const isCollapse = ref<boolean>(reisCollapse); const isCollapse = ref<boolean>(reisCollapse);
const setCollapseFinal = (collapseFinal: boolean) => { const setCollapse = (collapseFinal: boolean) => {
isCollapse.value = collapseFinal; isCollapse.value = collapseFinal;
}; };
// 折叠按钮是否被悬停 // 折叠按钮是否被悬停
const isCollapseHover = ref<boolean>(reisCollapseHover); const isSafeAreaHover = ref<boolean>(reisSafeAreaHover);
const setCollapseHover = (hover: boolean) => { const setSafeAreaHover = (hover: boolean) => {
isCollapseHover.value = hover; 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 { return {
darkMode, darkMode,
setDarkMode, setDarkMode,
@ -67,9 +90,10 @@ export const useDesignStore = defineStore(
collapseType, collapseType,
setCollapseType, setCollapseType,
isCollapse, isCollapse,
setCollapseFinal, setCollapse,
isCollapseHover, isSafeAreaHover,
setCollapseHover, setSafeAreaHover,
hasActivatedHover,
}; };
}, },
{ {

View File

@ -3,23 +3,18 @@
// 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']