feat: ✨ 完美复刻豆包收起展开动画
This commit is contained in:
parent
7535bbb591
commit
e19b4ff765
@ -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;
|
||||||
|
|||||||
@ -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) {
|
||||||
// 宽屏逻辑
|
// 宽屏逻辑
|
||||||
|
|||||||
@ -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);
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 样式穿透
|
// 样式穿透
|
||||||
|
|||||||
@ -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`,
|
||||||
|
|||||||
@ -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,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
7
types/components.d.ts
vendored
7
types/components.d.ts
vendored
@ -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']
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user