zhangwencui
2 小时以前 5f3dea3fd930afde720677fba10b6bfb2e836a18
src/views/im/home/components/resizable-aside.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,111 @@
<script lang="ts" setup>
import { onBeforeUnmount, onMounted, ref } from 'vue'
defineOptions({ name: 'ImResizableAside' })
const props = withDefaults(
  defineProps<{
    defaultWidth?: number // é»˜è®¤å®½åº¦
    maxWidth?: number // æœ€å¤§å®½åº¦
    minWidth?: number // æœ€å°å®½åº¦
    storageKey: string // localStorage å­˜å‚¨ key,必填;调用方传 StorageKeys.localStorage.asideWidth
  }>(),
  {
    defaultWidth: 260,
    minWidth: 200,
    maxWidth: 500
  }
)
const asideWidth = ref<number>(props.defaultWidth)
const isResizing = ref(false)
let startX = 0
let startWidth = 0
onMounted(() => {
  const saved = localStorage.getItem(props.storageKey)
  if (saved) {
    const w = Number.parseInt(saved, 10)
    if (!Number.isNaN(w)) {
      asideWidth.value = clamp(w)
    }
  }
  document.addEventListener('mousemove', handleResize)
  document.addEventListener('mouseup', stopResize)
})
onBeforeUnmount(() => {
  // æ‹–拽中卸载:复用 stopResize å¤ä½ body cursor/userSelect å¹¶å†™å›žå½“前宽度,避免全局状态泄漏
  if (isResizing.value) {
    stopResize()
  }
  document.removeEventListener('mousemove', handleResize)
  document.removeEventListener('mouseup', stopResize)
})
/** æŠŠå®½åº¦å¤¹åˆ° [minWidth, maxWidth] åŒºé—´ï¼Œæ¢å¤ / æ‹–拽路径都走它兜底 */
function clamp(w: number) {
  return Math.max(props.minWidth, Math.min(props.maxWidth, w))
}
/** æŒ‰ä¸‹æ‹–拽手柄:记录起始位置 + é”å®š body cursor/userSelect,避免拖拽中误选文本 */
function startResize(e: MouseEvent) {
  isResizing.value = true
  startX = e.clientX
  startWidth = asideWidth.value
  document.body.style.cursor = 'col-resize'
  document.body.style.userSelect = 'none'
  e.preventDefault()
}
/** æ‹–拽中:按鼠标位移计算新宽度并 clamp åˆ°å…è®¸åŒºé—´ */
function handleResize(e: MouseEvent) {
  if (!isResizing.value) {
    return
  }
  const deltaX = e.clientX - startX
  asideWidth.value = clamp(startWidth + deltaX)
}
/** æ¾å¼€é¼ æ ‡ï¼šè§£é” body å…¨å±€æ€å¹¶æŠŠå½“前宽度写入 localStorage æŒä¹…化 */
function stopResize() {
  if (!isResizing.value) {
    return
  }
  isResizing.value = false
  document.body.style.cursor = ''
  document.body.style.userSelect = ''
  localStorage.setItem(props.storageKey, String(asideWidth.value))
}
</script>
<template>
  <!--
    å¯æ‹–拽宽度的左侧 Aside
    - ä½¿ç”¨ localStorage è®°ä½ç”¨æˆ·ä¸Šæ¬¡è°ƒæ•´çš„宽度(storageKey å¿…填)
    - æ‹–拽区在右边缘,鼠标变 col-resize
  -->
  <aside
    class="relative flex flex-col shrink-0 bg-[var(--ant-color-fill-secondary)] border-r border-r-solid border-[var(--im-border-color-lighter)] shadow-[2px_0_8px_rgba(0,0,0,0.05)]"
    :style="{ width: `${asideWidth }px` }"
  >
    <slot></slot>
    <div
      class="im-resizable-aside__handle absolute top-0 right--0.75 z-10 flex items-center justify-center w-1.5 h-full cursor-col-resize transition-colors"
      :class="{ 'is-resizing': isResizing }"
      title="拖拽调整宽度"
      @mousedown="startResize"
    >
      <div class="im-resizable-aside__line w-0.5 h-full rounded-0.5 bg-transparent transition-all"></div>
    </div>
  </aside>
</template>
<style scoped>
/* hover / æ‹–拽中 æŠŠå†…部 line åŠ ç²—å˜æ·±ï¼Œæç¤ºæ‰‹æŸ„å¯æ‹–ï¼›çŠ¶æ€åœ¨çˆ¶ handle ä¸Š â†’ é€šè¿‡åŽä»£é€‰æ‹©å™¨è”动子 line */
.im-resizable-aside__handle:hover .im-resizable-aside__line,
.im-resizable-aside__handle.is-resizing .im-resizable-aside__line {
  width: 3px;
  background-color: var(--im-resize-line-color);
}
</style>