From 27cd042df9aca0383a49f3514bc21958dd890912 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期一, 29 六月 2026 15:42:23 +0800
Subject: [PATCH] 银川 1.联调产品维护页面 2.添加IM即时通讯模块

---
 src/views/im/home/components/resizable-aside.vue |  111 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 111 insertions(+), 0 deletions(-)

diff --git a/src/views/im/home/components/resizable-aside.vue b/src/views/im/home/components/resizable-aside.vue
new file mode 100644
index 0000000..e2785c1
--- /dev/null
+++ b/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>

--
Gitblit v1.9.3