增加Splitter
This commit is contained in:
@@ -22,15 +22,217 @@ yarn add joyd.web.vue.cubelib
|
|||||||
|
|
||||||
## 使用
|
## 使用
|
||||||
|
|
||||||
```javascript
|
### 全局注册
|
||||||
import { CubeButton } from 'joyd.web.vue.cubelib'
|
|
||||||
|
|
||||||
app.use(CubeButton)
|
```javascript
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
import CubeLib from 'joyd.web.vue.cubelib'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
app.use(CubeLib)
|
||||||
|
app.mount('#app')
|
||||||
```
|
```
|
||||||
|
|
||||||
## 文档
|
### 按需导入
|
||||||
|
|
||||||
查看 [在线文档](https://docs.joyd.com) 获取详细的使用说明和示例。
|
```javascript
|
||||||
|
import { CubeButton, CubeSplitter } from 'joyd.web.vue.cubelib'
|
||||||
|
|
||||||
|
// 在组件中使用
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
CubeButton,
|
||||||
|
CubeSplitter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 组件文档
|
||||||
|
|
||||||
|
### CubeButton
|
||||||
|
|
||||||
|
按钮组件,提供基本的按钮样式和交互功能。
|
||||||
|
|
||||||
|
#### Props
|
||||||
|
|
||||||
|
- `variant`: 按钮变体,可选值:`primary`, `secondary`, `outline`, `ghost`
|
||||||
|
- `size`: 按钮尺寸,可选值:`sm`, `md`, `lg`
|
||||||
|
- `disabled`: 是否禁用按钮
|
||||||
|
|
||||||
|
#### Events
|
||||||
|
|
||||||
|
- `click`: 点击按钮时触发
|
||||||
|
|
||||||
|
### CubeSplitter
|
||||||
|
|
||||||
|
可调整大小的分隔条组件,用于在布局中创建可调整大小的面板。
|
||||||
|
|
||||||
|
#### Props
|
||||||
|
|
||||||
|
- `direction`: 分隔条方向,可选值:`vertical` (垂直), `horizontal` (水平)
|
||||||
|
- `position`: 分隔条位置,可选值:`left`, `right`, `top`, `bottom`
|
||||||
|
- `initialSize`: 初始尺寸
|
||||||
|
- `minSize`: 最小尺寸
|
||||||
|
- `maxSize`: 最大尺寸
|
||||||
|
|
||||||
|
#### Events
|
||||||
|
|
||||||
|
- `resize`: 调整大小时触发
|
||||||
|
- `resizeEnd`: 调整大小结束时触发
|
||||||
|
- `collapse`: 折叠面板时触发
|
||||||
|
- `expand`: 展开面板时触发
|
||||||
|
|
||||||
|
#### 使用示例
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<div class="left-panel" :style="{ width: leftPanelWidth + 'px' }"></div>
|
||||||
|
<CubeSplitter
|
||||||
|
direction="vertical"
|
||||||
|
position="left"
|
||||||
|
:min-size="50"
|
||||||
|
:max-size="500"
|
||||||
|
:initial-size="200"
|
||||||
|
@resize="onLeftResize"
|
||||||
|
@resize-end="onLeftResizeEnd"
|
||||||
|
@collapse="onLeftCollapse"
|
||||||
|
@expand="onLeftExpand"
|
||||||
|
/>
|
||||||
|
<div class="middle-panel"></div>
|
||||||
|
<CubeSplitter
|
||||||
|
direction="vertical"
|
||||||
|
position="right"
|
||||||
|
:min-size="0"
|
||||||
|
:max-size="500"
|
||||||
|
:initial-size="200"
|
||||||
|
@resize="onRightResize"
|
||||||
|
@resize-end="onRightResizeEnd"
|
||||||
|
@collapse="onRightCollapse"
|
||||||
|
@expand="onRightExpand"
|
||||||
|
/>
|
||||||
|
<div class="right-panel" :style="{ width: rightPanelWidth + 'px' }"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
|
||||||
|
const leftPanelWidth = ref(200)
|
||||||
|
const rightPanelWidth = ref(200)
|
||||||
|
|
||||||
|
// 从 localStorage 加载保存的尺寸
|
||||||
|
onMounted(() => {
|
||||||
|
const savedLeftWidth = localStorage.getItem('leftPanelWidth')
|
||||||
|
const savedRightWidth = localStorage.getItem('rightPanelWidth')
|
||||||
|
|
||||||
|
if (savedLeftWidth) {
|
||||||
|
leftPanelWidth.value = parseInt(savedLeftWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (savedRightWidth) {
|
||||||
|
rightPanelWidth.value = parseInt(savedRightWidth)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 左侧分隔条调整
|
||||||
|
const onLeftResize = (newWidth) => {
|
||||||
|
leftPanelWidth.value = newWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
const onLeftResizeEnd = (finalWidth) => {
|
||||||
|
// 只保存正常尺寸,不保存折叠状态
|
||||||
|
if (finalWidth > 50) {
|
||||||
|
localStorage.setItem('leftPanelWidth', finalWidth.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onLeftCollapse = (newSize) => {
|
||||||
|
leftPanelWidth.value = newSize
|
||||||
|
}
|
||||||
|
|
||||||
|
const onLeftExpand = (newSize) => {
|
||||||
|
leftPanelWidth.value = newSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// 右侧分隔条调整
|
||||||
|
const onRightResize = (newWidth) => {
|
||||||
|
rightPanelWidth.value = newWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
const onRightResizeEnd = (finalWidth) => {
|
||||||
|
// 只保存正常尺寸,不保存折叠状态
|
||||||
|
if (finalWidth > 0) {
|
||||||
|
localStorage.setItem('rightPanelWidth', finalWidth.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onRightCollapse = (newSize) => {
|
||||||
|
rightPanelWidth.value = newSize
|
||||||
|
}
|
||||||
|
|
||||||
|
const onRightExpand = (newSize) => {
|
||||||
|
rightPanelWidth.value = newSize
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-panel {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
min-width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.middle-panel {
|
||||||
|
flex: 1;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
min-width: 0;
|
||||||
|
border-left: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### CSS 变量
|
||||||
|
|
||||||
|
CubeSplitter 组件支持通过 CSS 变量进行定制:
|
||||||
|
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
--cube-splitter-color: #e0e0e0;
|
||||||
|
--cube-splitter-hover-color: #667eea;
|
||||||
|
--cube-splitter-active-color: #5568d3;
|
||||||
|
--cube-splitter-handle-color: #fff;
|
||||||
|
--cube-splitter-handle-shadow: 0 0 4px rgba(0, 0, 0, 0.2);
|
||||||
|
--cube-splitter-collapsing-color: #667eea;
|
||||||
|
--cube-splitter-collapsing-shadow: 0 0 10px rgba(102, 126, 234, 0.5);
|
||||||
|
--cube-splitter-transition-speed: 0.3s;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题解答
|
||||||
|
|
||||||
|
### Q: 如何保存分隔条的位置?
|
||||||
|
|
||||||
|
A: 可以使用 `@resize-end` 事件来保存最终的尺寸到 localStorage 或其他存储中,然后在组件挂载时从存储中加载。
|
||||||
|
|
||||||
|
### Q: 如何在最小尺寸和当前尺寸之间切换?
|
||||||
|
|
||||||
|
A: 单击分隔条的句柄即可在最小尺寸和当前尺寸之间切换。
|
||||||
|
|
||||||
|
### Q: 为什么右侧分隔条的拖拽方向与左侧不同?
|
||||||
|
|
||||||
|
A: CubeSplitter 组件统一了拖拽方向逻辑:向左/上拖动减小尺寸,向右/下拖动增大尺寸,无论分隔条位置是左还是右。
|
||||||
|
|
||||||
## 贡献
|
## 贡献
|
||||||
|
|
||||||
|
|||||||
426
Web/Vue/CubeLib/src/components/CubeSplitter.vue
Normal file
426
Web/Vue/CubeLib/src/components/CubeSplitter.vue
Normal file
@@ -0,0 +1,426 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
|
|
||||||
|
// 修复props和emit的使用方式
|
||||||
|
const props = defineProps({
|
||||||
|
direction: {
|
||||||
|
type: String,
|
||||||
|
default: 'vertical', // 'vertical' 或 'horizontal'
|
||||||
|
validator: (value) => ['vertical', 'horizontal'].includes(value)
|
||||||
|
},
|
||||||
|
position: {
|
||||||
|
type: String,
|
||||||
|
default: 'left', // 'left', 'right', 'top' 或 'bottom'
|
||||||
|
validator: (value) => ['left', 'right', 'top', 'bottom'].includes(value)
|
||||||
|
},
|
||||||
|
initialSize: {
|
||||||
|
type: Number,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
minSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 200
|
||||||
|
},
|
||||||
|
maxSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 1000
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['resize', 'resizeEnd', 'handleClick', 'collapse', 'expand']) // 添加collapse和expand事件
|
||||||
|
|
||||||
|
const dividerRef = ref(null)
|
||||||
|
const isResizing = ref(false)
|
||||||
|
let startX = 0
|
||||||
|
let startY = 0
|
||||||
|
let startWidth = 0
|
||||||
|
let startHeight = 0
|
||||||
|
// 使用 ref 管理 currentSize 状态
|
||||||
|
// 注意:具体初始化值将在 onMounted 中设置,确保能正确访问 props
|
||||||
|
const currentSize = ref(0)
|
||||||
|
// 使用 ref 管理 isCollapsed 状态,与 isResizing 保持一致
|
||||||
|
const isCollapsed = ref(false) // 用于跟踪面板是否处于折叠状态
|
||||||
|
|
||||||
|
// 添加统一的边界检查函数
|
||||||
|
const clampSize = (size, min, max) => {
|
||||||
|
return Math.max(min, Math.min(max, size))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算是否处于有效折叠状态(考虑右侧面板的边框影响)
|
||||||
|
const isPanelEffectivelyCollapsed = (elementSize, minSize) => {
|
||||||
|
// 对于右侧分隔条,需要特殊处理最小尺寸的判断
|
||||||
|
// 因为右侧面板有 border-left: 1px,即使宽度为 0,offsetWidth 也可能返回 1
|
||||||
|
return elementSize <= minSize + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算鼠标移动的 delta 值
|
||||||
|
const calculateDelta = (e) => {
|
||||||
|
const deltaX = e.clientX - startX
|
||||||
|
const deltaY = e.clientY - startY
|
||||||
|
return { deltaX, deltaY }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取目标元素
|
||||||
|
const getTargetElement = () => {
|
||||||
|
// 边界检查:确保dividerRef.value存在
|
||||||
|
if (!dividerRef.value) {
|
||||||
|
console.warn('可调整分隔条:dividerRef 不可用')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据position属性获取正确的相邻元素
|
||||||
|
let targetElement = null
|
||||||
|
if (props.position === 'left' || props.position === 'top') {
|
||||||
|
targetElement = dividerRef.value.previousElementSibling
|
||||||
|
} else if (props.position === 'right' || props.position === 'bottom') {
|
||||||
|
targetElement = dividerRef.value.nextElementSibling
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetElement
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算新的尺寸
|
||||||
|
const calculateNewSize = (startSize, delta, minSize, maxSize, position) => {
|
||||||
|
let newSize = startSize
|
||||||
|
if (position === 'left' || position === 'top' || position === 'right' || position === 'bottom') {
|
||||||
|
// 统一逻辑:向左/上拖减小尺寸,向右/下拖增大尺寸
|
||||||
|
// 当向左拖动时,delta 为负数,startSize + delta 会减小
|
||||||
|
// 当向右拖动时,delta 为正数,startSize + delta 会增大
|
||||||
|
newSize = startSize + delta
|
||||||
|
}
|
||||||
|
return Math.max(minSize, Math.min(maxSize, newSize))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const onMouseDown = (e) => {
|
||||||
|
// 边界检查:确保dividerRef.value存在
|
||||||
|
if (!dividerRef.value) {
|
||||||
|
console.warn('可调整分隔条:dividerRef 不可用')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isResizing.value = true
|
||||||
|
startX = e.clientX
|
||||||
|
startY = e.clientY
|
||||||
|
|
||||||
|
// 获取目标元素
|
||||||
|
const targetElement = getTargetElement()
|
||||||
|
|
||||||
|
// 边界检查:确保targetElement存在
|
||||||
|
if (!targetElement) {
|
||||||
|
console.warn('可调整分隔条:未找到目标元素')
|
||||||
|
isResizing.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取相邻元素的初始尺寸
|
||||||
|
startWidth = targetElement.offsetWidth
|
||||||
|
startHeight = targetElement.offsetHeight
|
||||||
|
// 注意:不再重新初始化 currentSize,避免覆盖拖拽时的更新值
|
||||||
|
|
||||||
|
// 阻止默认行为,防止选中文本
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
// 添加事件监听器到document
|
||||||
|
document.addEventListener('mousemove', onMouseMove)
|
||||||
|
document.addEventListener('mouseup', onMouseUp)
|
||||||
|
document.body.style.cursor = props.direction === 'vertical' ? 'col-resize' : 'row-resize'
|
||||||
|
|
||||||
|
// 添加拖拽样式
|
||||||
|
dividerRef.value.classList.add('resizing')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const onHandleClick = (e) => {
|
||||||
|
try {
|
||||||
|
// 阻止事件冒泡
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
// 边界检查:确保dividerRef.value存在
|
||||||
|
if (!dividerRef.value) {
|
||||||
|
console.warn('可调整分隔条:dividerRef 不可用')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加简单的视觉反馈
|
||||||
|
dividerRef.value.classList.add('collapsing')
|
||||||
|
setTimeout(() => {
|
||||||
|
dividerRef.value?.classList.remove('collapsing')
|
||||||
|
}, 300)
|
||||||
|
|
||||||
|
// 获取目标元素
|
||||||
|
const targetElement = getTargetElement()
|
||||||
|
|
||||||
|
// 边界检查:确保targetElement存在
|
||||||
|
if (!targetElement) {
|
||||||
|
console.warn('可调整分隔条:未找到目标元素')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取目标元素的当前尺寸
|
||||||
|
const currentElementSize = props.direction === 'vertical' ? targetElement.offsetWidth : targetElement.offsetHeight
|
||||||
|
|
||||||
|
// 使用统一的尺寸判断逻辑
|
||||||
|
const effectivelyCollapsed = isPanelEffectivelyCollapsed(currentElementSize, props.minSize)
|
||||||
|
|
||||||
|
// 计算切换后的尺寸
|
||||||
|
let newSize = 0
|
||||||
|
|
||||||
|
if (!effectivelyCollapsed) {
|
||||||
|
// 当前尺寸大于最小尺寸,切换到最小尺寸(折叠)
|
||||||
|
currentSize.value = clampSize(currentElementSize, props.minSize, props.maxSize) // 保存当前尺寸,以便稍后恢复
|
||||||
|
newSize = props.minSize
|
||||||
|
isCollapsed.value = true
|
||||||
|
// 只触发折叠事件,让父组件根据需要处理尺寸更新
|
||||||
|
emit('collapse', newSize)
|
||||||
|
} else {
|
||||||
|
// 当前尺寸等于最小尺寸,切换到之前保存的尺寸(展开)
|
||||||
|
// 确保 restoreSize 有合理的值
|
||||||
|
const restoreSize = clampSize(
|
||||||
|
currentSize.value > props.minSize ? currentSize.value :
|
||||||
|
(props.initialSize && props.initialSize > props.minSize ? props.initialSize : 200),
|
||||||
|
props.minSize, props.maxSize
|
||||||
|
)
|
||||||
|
newSize = restoreSize
|
||||||
|
isCollapsed.value = false
|
||||||
|
// 只触发展开事件,让父组件根据需要处理尺寸更新
|
||||||
|
emit('expand', newSize)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('可调整分隔条:处理单击事件时出错:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onMouseMove = (e) => {
|
||||||
|
if (!isResizing.value) return
|
||||||
|
|
||||||
|
// 计算鼠标移动的 delta 值
|
||||||
|
const { deltaX, deltaY } = calculateDelta(e)
|
||||||
|
|
||||||
|
let newSize = 0
|
||||||
|
|
||||||
|
if (props.direction === 'vertical') {
|
||||||
|
// 垂直分隔条:计算新的宽度
|
||||||
|
newSize = calculateNewSize(startWidth, deltaX, props.minSize, props.maxSize, props.position)
|
||||||
|
|
||||||
|
// 获取目标元素的实际尺寸
|
||||||
|
const targetElement = getTargetElement()
|
||||||
|
const currentElementSize = targetElement ? targetElement.offsetWidth : newSize
|
||||||
|
|
||||||
|
// 使用统一的尺寸判断逻辑
|
||||||
|
const effectivelyCollapsed = isPanelEffectivelyCollapsed(currentElementSize, props.minSize)
|
||||||
|
|
||||||
|
// 如果不是折叠状态,更新 currentSize
|
||||||
|
if (!effectivelyCollapsed) {
|
||||||
|
currentSize.value = clampSize(newSize, props.minSize, props.maxSize)
|
||||||
|
isCollapsed.value = false
|
||||||
|
} else {
|
||||||
|
isCollapsed.value = true
|
||||||
|
}
|
||||||
|
emit('resize', newSize)
|
||||||
|
} else {
|
||||||
|
// 水平分隔条:计算新的高度
|
||||||
|
newSize = calculateNewSize(startHeight, deltaY, props.minSize, props.maxSize, props.position)
|
||||||
|
|
||||||
|
// 获取目标元素的实际尺寸
|
||||||
|
const targetElement = getTargetElement()
|
||||||
|
const currentElementSize = targetElement ? targetElement.offsetHeight : newSize
|
||||||
|
|
||||||
|
// 使用统一的尺寸判断逻辑
|
||||||
|
const effectivelyCollapsed = isPanelEffectivelyCollapsed(currentElementSize, props.minSize)
|
||||||
|
|
||||||
|
// 如果不是折叠状态,更新 currentSize
|
||||||
|
if (!effectivelyCollapsed) {
|
||||||
|
currentSize.value = clampSize(newSize, props.minSize, props.maxSize)
|
||||||
|
isCollapsed.value = false
|
||||||
|
} else {
|
||||||
|
isCollapsed.value = true
|
||||||
|
}
|
||||||
|
emit('resize', newSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onMouseUp = (e) => {
|
||||||
|
isResizing.value = false
|
||||||
|
|
||||||
|
// 计算鼠标移动的 delta 值
|
||||||
|
const deltaX = e.clientX - startX
|
||||||
|
const deltaY = e.clientY - startY
|
||||||
|
|
||||||
|
// 计算最终尺寸
|
||||||
|
const finalSize = props.direction === 'vertical' ?
|
||||||
|
calculateNewSize(startWidth, deltaX, props.minSize, props.maxSize, props.position) :
|
||||||
|
calculateNewSize(startHeight, deltaY, props.minSize, props.maxSize, props.position)
|
||||||
|
|
||||||
|
// 获取目标元素的实际尺寸
|
||||||
|
const targetElement = getTargetElement()
|
||||||
|
const currentElementSize = targetElement ?
|
||||||
|
(props.direction === 'vertical' ? targetElement.offsetWidth : targetElement.offsetHeight) :
|
||||||
|
finalSize
|
||||||
|
|
||||||
|
// 使用统一的尺寸判断逻辑
|
||||||
|
const effectivelyCollapsed = isPanelEffectivelyCollapsed(currentElementSize, props.minSize)
|
||||||
|
|
||||||
|
// 如果不是折叠状态,更新 currentSize
|
||||||
|
if (!effectivelyCollapsed) {
|
||||||
|
currentSize.value = clampSize(finalSize, props.minSize, props.maxSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 触发拖拽结束事件
|
||||||
|
emit('resizeEnd', finalSize)
|
||||||
|
|
||||||
|
// 移除事件监听器
|
||||||
|
document.removeEventListener('mousemove', onMouseMove)
|
||||||
|
document.removeEventListener('mouseup', onMouseUp)
|
||||||
|
document.body.style.cursor = ''
|
||||||
|
|
||||||
|
// 移除拖拽样式
|
||||||
|
if (dividerRef.value) {
|
||||||
|
dividerRef.value.classList.remove('resizing')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 初始化 currentSize 为合理的默认值
|
||||||
|
if (props.initialSize && props.initialSize > props.minSize) {
|
||||||
|
currentSize.value = clampSize(props.initialSize, props.minSize, props.maxSize)
|
||||||
|
isCollapsed.value = false
|
||||||
|
} else {
|
||||||
|
currentSize.value = 200 // 默认值
|
||||||
|
isCollapsed.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化时设置初始尺寸
|
||||||
|
if (props.initialSize) {
|
||||||
|
emit('resize', props.initialSize)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
// 清理全局事件监听器,避免内存泄漏
|
||||||
|
document.removeEventListener('mousemove', onMouseMove)
|
||||||
|
document.removeEventListener('mouseup', onMouseUp)
|
||||||
|
|
||||||
|
// 清理DOM相关资源
|
||||||
|
if (dividerRef.value) {
|
||||||
|
// 移除拖拽和折叠样式
|
||||||
|
dividerRef.value.classList.remove('resizing', 'collapsing')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理全局样式
|
||||||
|
document.body.style.cursor = ''
|
||||||
|
|
||||||
|
// 重置组件状态
|
||||||
|
isResizing.value = false
|
||||||
|
isCollapsed.value = false
|
||||||
|
|
||||||
|
// Vue的响应式数据会自动清理,无需额外处理
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="dividerRef"
|
||||||
|
class="cube-splitter"
|
||||||
|
:class="direction"
|
||||||
|
@mousedown="onMouseDown"
|
||||||
|
>
|
||||||
|
<div class="splitter-handle" @click.stop.prevent="onHandleClick"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* CSS 变量定义 */
|
||||||
|
:root {
|
||||||
|
--cube-splitter-color: #e0e0e0;
|
||||||
|
--cube-splitter-hover-color: #667eea;
|
||||||
|
--cube-splitter-active-color: #5568d3;
|
||||||
|
--cube-splitter-handle-color: #fff;
|
||||||
|
--cube-splitter-handle-shadow: 0 0 4px rgba(0, 0, 0, 0.2);
|
||||||
|
--cube-splitter-collapsing-color: #667eea;
|
||||||
|
--cube-splitter-collapsing-shadow: 0 0 10px rgba(102, 126, 234, 0.5);
|
||||||
|
--cube-splitter-transition-speed: 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cube-splitter {
|
||||||
|
position: relative;
|
||||||
|
background-color: var(--cube-splitter-color);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
/* 确保可以接收鼠标事件 */
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
flex-shrink: 0;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cube-splitter:hover {
|
||||||
|
background-color: var(--cube-splitter-hover-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cube-splitter.vertical {
|
||||||
|
width: 6px;
|
||||||
|
height: 100%;
|
||||||
|
cursor: col-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cube-splitter.horizontal {
|
||||||
|
width: 100%;
|
||||||
|
height: 6px;
|
||||||
|
cursor: row-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splitter-handle {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cube-splitter.vertical .splitter-handle {
|
||||||
|
width: 4px;
|
||||||
|
height: 40px;
|
||||||
|
background-color: var(--cube-splitter-handle-color);
|
||||||
|
box-shadow: var(--cube-splitter-handle-shadow);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cube-splitter.horizontal .splitter-handle {
|
||||||
|
width: 40px;
|
||||||
|
height: 4px;
|
||||||
|
background-color: var(--cube-splitter-handle-color);
|
||||||
|
box-shadow: var(--cube-splitter-handle-shadow);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cube-splitter:hover .splitter-handle {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 拖拽时的样式 */
|
||||||
|
.cube-splitter:active,
|
||||||
|
.cube-splitter.resizing {
|
||||||
|
background-color: var(--cube-splitter-active-color);
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cube-splitter:active .splitter-handle,
|
||||||
|
.cube-splitter.resizing .splitter-handle {
|
||||||
|
opacity: 1;
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 折叠/展开时的视觉反馈 */
|
||||||
|
.cube-splitter.collapsing {
|
||||||
|
background-color: var(--cube-splitter-collapsing-color);
|
||||||
|
transition: background-color var(--cube-splitter-transition-speed) ease;
|
||||||
|
box-shadow: var(--cube-splitter-collapsing-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cube-splitter.collapsing .splitter-handle {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1.1);
|
||||||
|
transition: transform var(--cube-splitter-transition-speed) ease;
|
||||||
|
box-shadow: 0 0 8px rgba(102, 126, 234, 0.8);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
import { App, defineComponent } from 'vue'
|
import { App, defineComponent } from 'vue'
|
||||||
import CubeButton from './components/CubeButton.vue'
|
import CubeButton from './components/CubeButton.vue'
|
||||||
|
import CubeSplitter from './components/CubeSplitter.vue'
|
||||||
|
|
||||||
const components = {
|
const components = {
|
||||||
CubeButton
|
CubeButton,
|
||||||
|
CubeSplitter
|
||||||
}
|
}
|
||||||
|
|
||||||
const install = (app: App) => {
|
const install = (app: App) => {
|
||||||
@@ -17,4 +19,4 @@ const CubeLib = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default CubeLib
|
export default CubeLib
|
||||||
export { CubeButton }
|
export { CubeButton, CubeSplitter }
|
||||||
Reference in New Issue
Block a user