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__main">
|
||||||
<!-- 侧边栏 -->
|
<!-- 侧边栏 -->
|
||||||
<div class="base-layout__sidebar">
|
<div class="base-layout__sidebar" :style="{ width: sidebarWidth + 'px' }">
|
||||||
<!-- 侧边栏内容 -->
|
<!-- 侧边栏内容 -->
|
||||||
<div class="base-layout__sidebar-content">
|
<div class="base-layout__sidebar-content">
|
||||||
<GameList />
|
<GameList />
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 拖拽调整宽度的手柄 -->
|
<!-- 拖拽调整宽度的手柄 -->
|
||||||
<div class="base-layout__resize-handle"></div>
|
<div class="base-layout__resize-handle" @mousedown="onResizeMouseDown"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 主内容区域 -->
|
<!-- 主内容区域 -->
|
||||||
@@ -40,9 +40,16 @@ import GameList from '@/components/baseLayouts/GameList.vue'
|
|||||||
import Lobby from '@/components/baseLayouts/Lobby.vue'
|
import Lobby from '@/components/baseLayouts/Lobby.vue'
|
||||||
import { useBaseLayoutStore } from '@/stores'
|
import { useBaseLayoutStore } from '@/stores'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { useResizeHandle } from '@/common/hooks/useResizeHandle'
|
||||||
|
|
||||||
const baseLayoutStore = useBaseLayoutStore()
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref } from 'vue'
|
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(
|
export const useBaseLayoutStore = defineStore(
|
||||||
'baseLayout',
|
'baseLayout',
|
||||||
() => {
|
() => {
|
||||||
@@ -12,6 +19,8 @@ export const useBaseLayoutStore = defineStore(
|
|||||||
const activeGameIcon = ref<string>('🐍')
|
const activeGameIcon = ref<string>('🐍')
|
||||||
/** 当前选中的游戏名称 */
|
/** 当前选中的游戏名称 */
|
||||||
const activeGameName = ref<string>('贪吃蛇')
|
const activeGameName = ref<string>('贪吃蛇')
|
||||||
|
/** 侧边栏当前宽度 */
|
||||||
|
const sidebarWidth = ref<number>(DEFAULT_SIDEBAR_WIDTH)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置顶部导航栏标题
|
* 设置顶部导航栏标题
|
||||||
@@ -33,13 +42,23 @@ export const useBaseLayoutStore = defineStore(
|
|||||||
activeGameName.value = name
|
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 {
|
return {
|
||||||
topbarTitle,
|
topbarTitle,
|
||||||
activeGameId,
|
activeGameId,
|
||||||
activeGameIcon,
|
activeGameIcon,
|
||||||
activeGameName,
|
activeGameName,
|
||||||
|
sidebarWidth,
|
||||||
setTopbarTitle,
|
setTopbarTitle,
|
||||||
setActiveGame
|
setActiveGame,
|
||||||
|
setSidebarWidth
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user