vue3左右布局,左右两边宽度不固定,可以拖动中间的分割线实现拖动改变宽度 电脑版发表于:2025/5/28 9:22 ### 第一个版本如下 ``` <template> <div class="split-panel-container"> <div class="panel-left" :style="{ width: `${leftWidth}%` }"> <div class="panel-content" v-bind:style="isResizing ? noSelectStyle : {}"> <slot name="left"> <div class="default-content">左侧面板内容</div> </slot> </div> </div> <div class="resize-handle" @mousedown="startResize"> <div class="handle-icon"></div> </div> <div class="panel-right" :style="{ width: `${rightWidth}%` }"> <div class="panel-content" v-bind:style="isResizing ? noSelectStyle : {}"> <slot name="right"> <div class="default-content">右侧面板内容</div> </slot> </div> </div> </div> </template> <script setup lang="ts"> import { ref, computed, onMounted, onUnmounted } from 'vue' const props = defineProps({ defaultLeftWidth: { type: Number, default: 70 }, minLeftWidth: { type: Number, default: 30 }, minRightWidth: { type: Number, default: 20 } }) const leftWidth = ref(props.defaultLeftWidth) const rightWidth = ref(100 - props.defaultLeftWidth) const isResizing = ref(false) // 禁止选择样式 const noSelectStyle = computed(() => ({ 'user-select': 'none', '-webkit-user-select': 'none', 'pointer-events': 'none' })) const startResize = (e: MouseEvent) => { isResizing.value = true document.body.style.cursor = 'col-resize' document.addEventListener('mousemove', handleResize) document.addEventListener('mouseup', stopResize) e.preventDefault() // 阻止默认行为,防止选中内容 } const handleResize = (e: MouseEvent) => { if (!isResizing.value) return const container = document.querySelector('.split-panel-container') as HTMLElement const containerRect = container.getBoundingClientRect() const containerWidth = containerRect.width // 计算鼠标在容器中的相对位置百分比 let mouseX = e.clientX - containerRect.left let newLeftWidth = (mouseX / containerWidth) * 100 // 应用最小宽度限制 if (newLeftWidth < props.minLeftWidth) newLeftWidth = props.minLeftWidth if (newLeftWidth > 100 - props.minRightWidth) newLeftWidth = 100 - props.minRightWidth leftWidth.value = newLeftWidth rightWidth.value = 100 - newLeftWidth } const stopResize = () => { isResizing.value = false document.body.style.cursor = '' document.removeEventListener('mousemove', handleResize) document.removeEventListener('mouseup', stopResize) } onMounted(() => { // 初始化宽度 leftWidth.value = props.defaultLeftWidth rightWidth.value = 100 - props.defaultLeftWidth }) onUnmounted(() => { document.removeEventListener('mousemove', handleResize) document.removeEventListener('mouseup', stopResize) }) </script> <style scoped lang="scss"> .split-panel-container { display: flex; width: 100%; height: 100%; min-height: 300px; overflow: hidden; .panel-left, .panel-right { position: relative; overflow: auto; transition: width 0.1s ease; /* 自定义滚动条 */ &::-webkit-scrollbar { width: 8px; height: 8px; } &::-webkit-scrollbar-track { background: transparent; } &::-webkit-scrollbar-thumb { background: rgba(150, 150, 150, 0.5); border-radius: 4px; transition: background 0.2s; } &::-webkit-scrollbar-thumb:hover { background: rgba(100, 100, 100, 0.7); } } .panel-left { /* 关键调整:将左侧面板的右侧padding设置为负数,使内容可以扩展到滚动条区域 */ padding-right: -8px; margin-right: 8px; /* 补偿padding,保持整体宽度不变 */ /* 让左侧面板的滚动条与分割线重叠 */ &::-webkit-scrollbar { position: absolute; right: 0; width: 8px; /* 滚动条宽度 */ } } .resize-handle { position: relative; width: 6px; background-color: #f0f0f0; cursor: col-resize; border-left: 1px solid #ddd; border-right: 1px solid #ddd; z-index: 20; /* 确保分割线在最上层 */ /* 鼠标悬停和拖动时的样式 */ &:hover, &:active { background-color: #e0e0e0; } .handle-icon { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 2px; height: 30px; background-color: #ccc; &::before, &::after { content: ''; position: absolute; width: 2px; height: 30px; background-color: #ccc; } &::before { left: -6px; } &::after { left: 6px; } } } .panel-content { padding: 16px; min-height: 100%; } .default-content { padding: 20px; background-color: #f9f9f9; border-radius: 4px; margin: 10px; } } </style> ```