|
|
@@ -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>
|