feat: 拖拽侧边栏宽度
This commit is contained in:
71
src/common/hooks/useResizeHandle.ts
Normal file
71
src/common/hooks/useResizeHandle.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { type Ref, onUnmounted } from 'vue'
|
||||
|
||||
/**
|
||||
* 拖拽调整宽度 Hook 的参数
|
||||
*/
|
||||
interface UseResizeHandleOptions {
|
||||
/** 当前宽度响应式引用 */
|
||||
width: Ref<number>
|
||||
/** 宽度变更回调,传入新宽度 */
|
||||
onWidthChange: (width: number) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* 将侧边栏拖拽调整宽度的逻辑封装为可复用的 composable
|
||||
*
|
||||
* 在 mousedown 时绑定 document 级 mousemove/mouseup 监听,
|
||||
* 拖拽过程中通过 onWidthChange 回调通知外部更新宽度(由外部负责约束范围),
|
||||
* 拖拽结束后自动清理事件监听和全局样式。
|
||||
*
|
||||
* @param options - 配置项,包含宽度 ref 和变更回调
|
||||
* @returns 返回 mousedown 事件处理器,绑定到拖拽手柄元素
|
||||
*/
|
||||
export function useResizeHandle(options: UseResizeHandleOptions): {
|
||||
onMouseDown: (event: MouseEvent) => void
|
||||
} {
|
||||
const { onWidthChange } = options
|
||||
|
||||
/** 是否正在拖拽 */
|
||||
let isResizing = false
|
||||
|
||||
/**
|
||||
* 处理 mousemove,计算新宽度并通知外部
|
||||
*/
|
||||
const onMouseMove = (event: MouseEvent): void => {
|
||||
if (!isResizing) return
|
||||
onWidthChange(event.clientX)
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 mouseup,结束拖拽并清理
|
||||
*/
|
||||
const onMouseUp = (): void => {
|
||||
isResizing = false
|
||||
document.removeEventListener('mousemove', onMouseMove)
|
||||
document.removeEventListener('mouseup', onMouseUp)
|
||||
document.body.style.userSelect = ''
|
||||
document.body.style.cursor = ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 mousedown,开始拖拽
|
||||
*/
|
||||
const onMouseDown = (event: MouseEvent): void => {
|
||||
event.preventDefault()
|
||||
isResizing = true
|
||||
document.addEventListener('mousemove', onMouseMove)
|
||||
document.addEventListener('mouseup', onMouseUp)
|
||||
document.body.style.userSelect = 'none'
|
||||
document.body.style.cursor = 'ew-resize'
|
||||
}
|
||||
|
||||
/** 组件卸载时兜底清理 */
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('mousemove', onMouseMove)
|
||||
document.removeEventListener('mouseup', onMouseUp)
|
||||
document.body.style.userSelect = ''
|
||||
document.body.style.cursor = ''
|
||||
})
|
||||
|
||||
return { onMouseDown }
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
<!-- 主体区域 -->
|
||||
<div class="base-layout__main">
|
||||
<!-- 侧边栏 -->
|
||||
<div class="base-layout__sidebar">
|
||||
<div class="base-layout__sidebar" :style="{ width: sidebarWidth + 'px' }">
|
||||
<!-- 侧边栏内容 -->
|
||||
<div class="base-layout__sidebar-content">
|
||||
<GameList />
|
||||
@@ -23,7 +23,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- 拖拽调整宽度的手柄 -->
|
||||
<div class="base-layout__resize-handle"></div>
|
||||
<div class="base-layout__resize-handle" @mousedown="onResizeMouseDown"></div>
|
||||
</div>
|
||||
|
||||
<!-- 主内容区域 -->
|
||||
@@ -40,9 +40,16 @@ import GameList from '@/components/baseLayouts/GameList.vue'
|
||||
import Lobby from '@/components/baseLayouts/Lobby.vue'
|
||||
import { useBaseLayoutStore } from '@/stores'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useResizeHandle } from '@/common/hooks/useResizeHandle'
|
||||
|
||||
const baseLayoutStore = useBaseLayoutStore()
|
||||
const { activeGameIcon, activeGameName } = storeToRefs(baseLayoutStore)
|
||||
const { activeGameIcon, activeGameName, sidebarWidth } = storeToRefs(baseLayoutStore)
|
||||
|
||||
/** 拖拽手柄事件处理器 */
|
||||
const { onMouseDown: onResizeMouseDown } = useResizeHandle({
|
||||
width: sidebarWidth,
|
||||
onWidthChange: (width) => baseLayoutStore.setSidebarWidth(width)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
/** 侧边栏最小宽度 */
|
||||
export const MIN_SIDEBAR_WIDTH = 200
|
||||
/** 侧边栏最大宽度 */
|
||||
export const MAX_SIDEBAR_WIDTH = 650
|
||||
/** 侧边栏默认宽度 */
|
||||
export const DEFAULT_SIDEBAR_WIDTH = 400
|
||||
|
||||
export const useBaseLayoutStore = defineStore(
|
||||
'baseLayout',
|
||||
() => {
|
||||
@@ -12,6 +19,8 @@ export const useBaseLayoutStore = defineStore(
|
||||
const activeGameIcon = ref<string>('🐍')
|
||||
/** 当前选中的游戏名称 */
|
||||
const activeGameName = ref<string>('贪吃蛇')
|
||||
/** 侧边栏当前宽度 */
|
||||
const sidebarWidth = ref<number>(DEFAULT_SIDEBAR_WIDTH)
|
||||
|
||||
/**
|
||||
* 设置顶部导航栏标题
|
||||
@@ -33,13 +42,23 @@ export const useBaseLayoutStore = defineStore(
|
||||
activeGameName.value = name
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置侧边栏宽度,自动约束在最小和最大值之间
|
||||
* @param width - 新宽度(px)
|
||||
*/
|
||||
const setSidebarWidth = (width: number): void => {
|
||||
sidebarWidth.value = Math.min(Math.max(width, MIN_SIDEBAR_WIDTH), MAX_SIDEBAR_WIDTH)
|
||||
}
|
||||
|
||||
return {
|
||||
topbarTitle,
|
||||
activeGameId,
|
||||
activeGameIcon,
|
||||
activeGameName,
|
||||
sidebarWidth,
|
||||
setTopbarTitle,
|
||||
setActiveGame
|
||||
setActiveGame,
|
||||
setSidebarWidth
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user