Parcourir la source

Update 分割拖拽面板组件优化,支持外部传入面板样式及拖拽宽度

Yue il y a 1 semaine
Parent
commit
fa66e3e64a
1 fichiers modifiés avec 92 ajouts et 60 suppressions
  1. 92 60
      UI/VAP_V3.VUE/src/components/split-panel/VbSplitPanel.vue

+ 92 - 60
UI/VAP_V3.VUE/src/components/split-panel/VbSplitPanel.vue

@@ -6,13 +6,25 @@ const props = withDefaults(
 		minSize?: number | string // 最小尺寸,支持像素或百分比(如 '10%')
 		maxSize?: number | string // 最大尺寸,支持像素或百分比(如 '80%')
 		containerSize?: string | number // 容器尺寸
+		panelStyle?: object // 面板样式,支持内联样式
+		panelItemStyle?: object // 面板项样式,支持内联样式
+		firstPanelItemStyle?: object // 第1个面板项样式,支持内联样式
+		secondPanelItemStyle?: object // 第2个面板项样式,支持内联样式
+		handlerBg?: string // 拖拽条背景色
+		handlerHoverBg?: string // 拖拽条悬停背景色
+		handlerWidth?: number // 拖拽条宽度
+		handlerMargin?: number // 拖拽条边距
 	}>(),
 	{
 		mode: "h",
 		size: 200,
 		minSize: 100,
 		maxSize: 2000,
-		containerSize: "100%"
+		containerSize: "100%",
+		handlerBg: "#e5e7eb",
+		handlerHoverBg: "#409eff",
+		handlerWidth: 6,
+		handlerMargin: 2
 	}
 )
 const emits = defineEmits<{
@@ -21,10 +33,16 @@ const emits = defineEmits<{
 
 const panelRef = ref(null)
 const isDragging = ref(false)
-const currentSize = ref<number>(
+const parentSize = ref(0)
+const firstPanelSize = ref<number>(
 	typeof props.size === "number" ? props.size : parseFloat(props.size as string) || 200
 )
-const parentSize = ref(0)
+
+const secondPanelSize = computed(() =>
+	Math.floor(
+		parentSize.value - firstPanelSize.value - (props.handlerWidth || 6) - (props.handlerMargin || 2)
+	)
+)
 
 const validatePositive = (value, name) => {
 	const num = Number(value)
@@ -46,12 +64,12 @@ const parseSizeValue = (value: number | string, parentSize: number): number => {
 		if (!isNaN(percentage)) {
 			if (percentage > 100) {
 				console.warn(`VbSplitPanel:${value} 超出范围,已设置为 90%`)
-				return (parentSize * 90) / 100
+				return Math.floor((parentSize * 90) / 100)
 			} else if (percentage < 0) {
 				console.warn(`VbSplitPanel:${value} 不能小于 0,已设置为 10%`)
-				return (parentSize * 10) / 100
+				return Math.floor((parentSize * 10) / 100)
 			} else {
-				return (parentSize * percentage) / 100
+				return Math.floor((parentSize * percentage) / 100)
 			}
 		}
 	}
@@ -66,7 +84,7 @@ const getParentSize = () => {
 	const size = realMode.value === "horizontal" ? parent.offsetWidth : parent.offsetHeight
 
 	validatePositive(size, `拖拽父元素${realMode.value === "horizontal" ? "水平" : "垂直"}方向尺寸`)
-	parentSize.value = size
+	parentSize.value = Math.floor(size)
 }
 
 // 容器样式
@@ -91,6 +109,11 @@ const containerStyle = computed(() => {
 	}
 
 	return {
+		...(props.panelStyle || {}),
+		"--split-handler-bg": props.handlerBg || "#e5e7eb",
+		"--split-handler-hover-bg": props.handlerHoverBg || "#409eff",
+		"--split-handler-width": (props.handlerWidth || 6) + "px",
+		"--split-handler-margin": (props.handlerMargin || 2) + "px",
 		[sizeProp]: finalSize,
 		[crossProp]: "100%",
 		display: "flex",
@@ -102,13 +125,24 @@ const modeClass = computed(() => realMode.value)
 const handlerClass = computed(() => (realMode.value === "horizontal" ? "h-handler" : "v-handler"))
 
 const firstPanelStyle = computed(() => {
-	const pixelSize = parseSizeValue(currentSize.value, parentSize.value)
+	const pixelSize = parseSizeValue(firstPanelSize.value, parentSize.value)
 	return {
+		...(props.panelItemStyle || {}),
+		...(props.firstPanelItemStyle || {}),
 		...(realMode.value === "horizontal"
 			? { width: `${pixelSize}px` }
 			: { height: `${pixelSize}px` }),
-		flex: "none",
-		overflow: "auto"
+		flex: "none"
+	}
+})
+const secondPanelStyle = computed(() => {
+	return {
+		...(props.panelItemStyle || {}),
+		...(props.secondPanelItemStyle || {}),
+		...(realMode.value === "horizontal"
+			? { width: `${secondPanelSize.value}px` }
+			: { height: `${secondPanelSize.value}px` }),
+		flex: 1
 	}
 })
 
@@ -119,9 +153,9 @@ const startDrag = (e) => {
 	const startPos = realMode.value === "horizontal" ? e.clientX : e.clientY
 	// 确保 start 是数值类型
 	const start =
-		typeof currentSize.value === "number"
-			? currentSize.value
-			: parseFloat(currentSize.value as string) || 200
+		typeof firstPanelSize.value === "number"
+			? firstPanelSize.value
+			: parseFloat(firstPanelSize.value as string) || 200
 	const onMove = (e) => {
 		if (!isDragging.value) return
 		const pos = realMode.value === "horizontal" ? e.clientX : e.clientY
@@ -132,22 +166,17 @@ const startDrag = (e) => {
 		const maxPixelSize = parseSizeValue(props.maxSize, parentSize.value)
 
 		size = Math.max(minPixelSize, Math.min(maxPixelSize, size))
-		size = Math.min(size, parentSize.value - minPixelSize)
-		currentSize.value = size
+		size = Math.floor(Math.min(size, parentSize.value - minPixelSize))
+		firstPanelSize.value = size
 	}
 
 	const stop = () => {
 		isDragging.value = false
 		document.removeEventListener("mousemove", onMove)
 		document.removeEventListener("mouseup", stop)
-		// 确保发送的数值是正确的数字类型
-		const finalSize =
-			typeof currentSize.value === "number"
-				? currentSize.value
-				: parseFloat(currentSize.value as string) || 200
-		const secondPanelSize = parentSize.value - finalSize - 6
+
 		nextTick(() => {
-			emits("dragComplete", finalSize, secondPanelSize)
+			emits("dragComplete", firstPanelSize.value, secondPanelSize.value)
 		})
 	}
 
@@ -159,7 +188,7 @@ onMounted(() => {
 	getParentSize()
 	// 在父元素尺寸获取后,重新计算初始尺寸
 	if (parentSize.value > 0) {
-		currentSize.value = parseSizeValue(props.size, parentSize.value)
+		firstPanelSize.value = parseSizeValue(props.size, parentSize.value)
 	}
 })
 
@@ -170,7 +199,7 @@ watch(
 	() => props.size,
 	(newSize) => {
 		if (parentSize.value > 0) {
-			currentSize.value = parseSizeValue(newSize, parentSize.value)
+			firstPanelSize.value = parseSizeValue(newSize, parentSize.value)
 		}
 	}
 )
@@ -178,54 +207,57 @@ watch(
 
 <template>
 	<div class="split-panel" ref="panelRef" :class="modeClass" :style="containerStyle">
-		<div class="panel-item first-panel" :style="firstPanelStyle">
+		<div class="panel-item" :style="firstPanelStyle">
 			<slot name="first"></slot>
 		</div>
 
 		<div class="split-handler" :class="handlerClass" @mousedown="startDrag"></div>
 
-		<div class="panel-item second-panel">
+		<div class="panel-item" :style="secondPanelStyle">
 			<slot name="second"></slot>
 		</div>
 	</div>
 </template>
 
-<style scoped>
+<style scoped lang="scss">
 .split-panel {
 	position: relative;
-}
-.horizontal {
-	flex-direction: row;
-}
-.vertical {
-	flex-direction: column;
-}
+	background: transparent;
+	--split-handler-bg: #e5e7eb;
+	--split-handler-hover-bg: #409eff;
+	--split-handler-width: 6px;
+	--split-handler-margin: 2px;
 
-.panel-item {
-	background: #f9fafb;
-}
-.second-panel {
-	flex: 1;
-	background: #fff;
-}
-
-.split-handler {
-	background: #e5e7eb;
-	transition: background 0.2s;
-	z-index: 10;
-	flex-shrink: 0;
-}
-.h-handler {
-	width: 6px;
-	height: 100%;
-	cursor: col-resize;
-}
-.v-handler {
-	height: 6px;
-	width: 100%;
-	cursor: row-resize;
-}
-.split-handler:hover {
-	background: #409eff;
+	&.horizontal {
+		flex-direction: row;
+	}
+	&.vertical {
+		flex-direction: column;
+	}
+	.panel-item {
+		background: transparent;
+		overflow: auto;
+	}
+	.split-handler {
+		background: var(--split-handler-bg);
+		transition: background 0.2s;
+		z-index: 10;
+		flex-shrink: 0;
+		&.h-handler {
+			width: var(--split-handler-width);
+			height: 100%;
+			cursor: col-resize;
+			margin: 0 var(--split-handler-margin);
+		}
+		&.v-handler {
+			height: var(--split-handler-width);
+			width: 100%;
+			cursor: row-resize;
+			margin: var(--split-handler-margin) 0;
+		}
+		&:hover {
+			background: var(--split-handler-hover-bg);
+		}
+	}
 }
 </style>