实现TabPage标签拖拽功能及样式优化:1. 激活标签背景色改为#F5CC84 2. 激活标签不显示关闭按钮 3. 修复TabPage标签拖拽功能
This commit is contained in:
4
AutoRobot/Windows/Robot/Web/package-lock.json
generated
4
AutoRobot/Windows/Robot/Web/package-lock.json
generated
@@ -2321,6 +2321,7 @@
|
||||
"integrity": "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.0",
|
||||
"immutable": "^5.0.2",
|
||||
@@ -2448,6 +2449,7 @@
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -2462,6 +2464,7 @@
|
||||
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.21.3",
|
||||
"postcss": "^8.4.43",
|
||||
@@ -2521,6 +2524,7 @@
|
||||
"resolved": "http://47.111.181.23:8081/repository/npm-public/vue/-/vue-3.5.22.tgz",
|
||||
"integrity": "sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.22",
|
||||
"@vue/compiler-sfc": "3.5.22",
|
||||
|
||||
@@ -36,6 +36,9 @@
|
||||
:id="tabPage.id"
|
||||
:title="tabPage.title"
|
||||
:panels="tabPage.panels"
|
||||
@tabDragStart="onTabDragStart(area.id, $event)"
|
||||
@tabDragMove="onTabDragMove(area.id, $event)"
|
||||
@tabDragEnd="onTabDragEnd"
|
||||
>
|
||||
<!-- 在TabPage内渲染其包含的Panels -->
|
||||
<Panel
|
||||
@@ -86,6 +89,14 @@ const panelDragState = ref({
|
||||
startAreaPos: { x: 0, y: 0 }
|
||||
})
|
||||
|
||||
// TabPage拖拽相关状态
|
||||
const tabDragState = ref({
|
||||
isDragging: false,
|
||||
currentAreaId: null,
|
||||
startClientPos: { x: 0, y: 0 },
|
||||
startAreaPos: { x: 0, y: 0 }
|
||||
})
|
||||
|
||||
// 添加新的浮动面板
|
||||
const addFloatingPanel = () => {
|
||||
// 获取父容器尺寸以计算居中位置
|
||||
@@ -297,6 +308,64 @@ const onPanelDragEnd = () => {
|
||||
panelDragState.value.currentAreaId = null
|
||||
}
|
||||
|
||||
// TabPage拖拽开始
|
||||
const onTabDragStart = (areaId, event) => {
|
||||
const area = floatingAreas.value.find(a => a.id === areaId)
|
||||
// 只有当Area中只有一个TabPage时才允许通过TabPage的页标签移动Area
|
||||
if (area && area.tabPages && area.tabPages.length === 1) {
|
||||
tabDragState.value.isDragging = true
|
||||
tabDragState.value.currentAreaId = areaId
|
||||
tabDragState.value.startClientPos = {
|
||||
x: event.clientX,
|
||||
y: event.clientY
|
||||
}
|
||||
tabDragState.value.startAreaPos = {
|
||||
x: area.x,
|
||||
y: area.y
|
||||
}
|
||||
console.log('TabPage拖拽开始,移动Area:', areaId)
|
||||
}
|
||||
}
|
||||
|
||||
// TabPage拖拽移动
|
||||
const onTabDragMove = (areaId, event) => {
|
||||
if (tabDragState.value.isDragging && tabDragState.value.currentAreaId === areaId) {
|
||||
const area = floatingAreas.value.find(a => a.id === areaId)
|
||||
if (area) {
|
||||
// 计算移动距离
|
||||
const deltaX = event.clientX - tabDragState.value.startClientPos.x
|
||||
const deltaY = event.clientY - tabDragState.value.startClientPos.y
|
||||
|
||||
// 计算新位置
|
||||
let newLeft = tabDragState.value.startAreaPos.x + deltaX
|
||||
let newTop = tabDragState.value.startAreaPos.y + deltaY
|
||||
|
||||
// 确保不超出父容器边界
|
||||
if (dockLayoutRef.value) {
|
||||
const parentRect = dockLayoutRef.value.getBoundingClientRect()
|
||||
|
||||
// 严格边界检查
|
||||
newLeft = Math.max(0, Math.min(newLeft, parentRect.width - area.width))
|
||||
newTop = Math.max(0, Math.min(newTop, parentRect.height - area.height))
|
||||
}
|
||||
|
||||
// 更新位置
|
||||
area.x = newLeft
|
||||
area.y = newTop
|
||||
|
||||
// 调试信息
|
||||
console.log('TabPage拖拽移动,Area新位置:', { x: newLeft, y: newTop })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TabPage拖拽结束
|
||||
const onTabDragEnd = () => {
|
||||
console.log('TabPage拖拽结束')
|
||||
tabDragState.value.isDragging = false
|
||||
tabDragState.value.currentAreaId = null
|
||||
}
|
||||
|
||||
// 监听floatingAreas变化,确保当Area最大化时,Panel也会自动最大化
|
||||
watch(floatingAreas, (newAreas) => {
|
||||
newAreas.forEach(area => {
|
||||
|
||||
@@ -7,10 +7,13 @@
|
||||
:key="panel.id"
|
||||
:class="['tab-item', { 'active': activeTabIndex === index }]"
|
||||
@click="setActiveTab(index)"
|
||||
@mousedown="onTabDragStart(index, $event)"
|
||||
>
|
||||
<div class="flex items-center justify-between h-full px-3">
|
||||
<span class="tab-title">{{ panel.title }}</span>
|
||||
<!-- 当标签页未被激活时显示关闭按钮 -->
|
||||
<button
|
||||
v-if="activeTabIndex !== index"
|
||||
class="button-icon p-[2px] rounded hover:opacity-100 opacity-80"
|
||||
@click.stop="closeTab(panel.id)"
|
||||
aria-label="关闭"
|
||||
@@ -33,7 +36,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits, ref } from 'vue'
|
||||
import { defineProps, defineEmits, ref, onMounted } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
id: { type: String, required: true },
|
||||
@@ -50,23 +53,88 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['tabChange', 'tabClose', 'tabAdd'])
|
||||
const emit = defineEmits(['tabChange', 'tabClose', 'tabAdd', 'tabDragStart', 'tabDragMove', 'tabDragEnd'])
|
||||
|
||||
// 当前激活的标签页索引
|
||||
const activeTabIndex = ref(props.activeIndex)
|
||||
const activeTabIndex = ref(-1)
|
||||
|
||||
// 拖拽相关状态
|
||||
let isDragging = false
|
||||
let dragIndex = -1
|
||||
|
||||
// 设置激活的标签页
|
||||
const setActiveTab = (index) => {
|
||||
if (index >= 0 && index < props.tabs.length) {
|
||||
if (index >= 0 && index < props.panels.length) {
|
||||
activeTabIndex.value = index
|
||||
emit('tabChange', { index, tab: props.tabs[index] })
|
||||
emit('tabChange', { index, tab: props.panels[index] })
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载后,如果有面板且没有激活的标签,默认激活第一个
|
||||
onMounted(() => {
|
||||
if (props.panels && props.panels.length > 0 && activeTabIndex.value === -1) {
|
||||
setActiveTab(0)
|
||||
}
|
||||
})
|
||||
|
||||
// 关闭标签页
|
||||
const closeTab = (tabId) => {
|
||||
emit('tabClose', { id: tabId })
|
||||
}
|
||||
|
||||
// 标签拖拽开始
|
||||
const onTabDragStart = (index, event) => {
|
||||
// 只有当点击的是标题文本区域(不是关闭按钮)时才触发拖拽
|
||||
if (!event.target.closest('.button-icon') && !event.target.closest('button')) {
|
||||
isDragging = true
|
||||
dragIndex = index
|
||||
|
||||
// 传递标签页索引和鼠标位置
|
||||
emit('tabDragStart', {
|
||||
clientX: event.clientX,
|
||||
clientY: event.clientY,
|
||||
tabIndex: index,
|
||||
tabId: props.panels[index].id
|
||||
})
|
||||
|
||||
// 防止文本选择和默认行为
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
// 将鼠标移动和释放事件绑定到document,确保拖拽的连续性
|
||||
document.addEventListener('mousemove', onTabDragMove)
|
||||
document.addEventListener('mouseup', onTabDragEnd)
|
||||
document.addEventListener('mouseleave', onTabDragEnd)
|
||||
}
|
||||
}
|
||||
|
||||
// 标签拖拽移动
|
||||
const onTabDragMove = (event) => {
|
||||
if (isDragging) {
|
||||
// 防止文本选择和默认行为
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
emit('tabDragMove', {
|
||||
clientX: event.clientX,
|
||||
clientY: event.clientY,
|
||||
tabIndex: dragIndex
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 标签拖拽结束
|
||||
const onTabDragEnd = () => {
|
||||
if (isDragging) {
|
||||
isDragging = false
|
||||
emit('tabDragEnd', { tabIndex: dragIndex })
|
||||
dragIndex = -1
|
||||
|
||||
// 拖拽结束后移除事件监听器
|
||||
document.removeEventListener('mousemove', onTabDragMove)
|
||||
document.removeEventListener('mouseup', onTabDragEnd)
|
||||
document.removeEventListener('mouseleave', onTabDragEnd)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -124,12 +192,12 @@ const closeTab = (tabId) => {
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
background: #ffffff;
|
||||
background: #F5CC84;
|
||||
border: 1px solid #c7d2ea;
|
||||
border-bottom-color: #ffffff;
|
||||
border-bottom-color: #F5CC84;
|
||||
border-top-width: 2px;
|
||||
border-top-color: #0078d7;
|
||||
color: #333;
|
||||
color: #000000;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@@ -138,8 +206,8 @@ const closeTab = (tabId) => {
|
||||
}
|
||||
|
||||
.tab-item.active:hover {
|
||||
background: #ffffff;
|
||||
color: #333;
|
||||
background: #F5CC84;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
### Area
|
||||
1. 初始添加时,默认宽300px,高250px。位置相对父容器水平居中,垂直居中。
|
||||
2. 最大化时,填充满父容器。
|
||||
3. 还原时,恢复到最大化前的位置和大小。
|
||||
4. 关闭时,从父容器中移除。同时将内容区的Panel移除。
|
||||
5. 拖拽时,允许在父容器内移动,不允许超出父容器边界。
|
||||
6. 允许拖动边框,改变Area的大小。
|
||||
7. 当内容区只包含一个Panel时,显示Area的标题栏。
|
||||
8. 当内容区只包含一个Panel时,拖动Panel标题栏就相当于拖动Area。
|
||||
9. 当内容区只包含一个Panel时,拖动TabPage的页标签就相当于拖动Area。
|
||||
10. 当内容区只包含一个Panel时,单击Panel的最大化按钮,就相当于单击Area的最大化按钮同时最大化Panel。
|
||||
11. 当内容区只包含一个Panel时,最大化Area时,Panel也会最大化。
|
||||
### Area
|
||||
1. 初始添加时,默认宽300px,高250px。位置相对父容器水平居中,垂直居中。
|
||||
2. 最大化时,填充满父容器。
|
||||
3. 还原时,恢复到最大化前的位置和大小。
|
||||
4. 关闭时,从父容器中移除。同时将内容区的Panel移除。
|
||||
5. 拖拽时,允许在父容器内移动,不允许超出父容器边界。
|
||||
6. 允许拖动边框,改变Area的大小。
|
||||
7. 当内容区只包含一个Panel时,显示Area的标题栏。
|
||||
8. 当内容区只包含一个Panel时,拖动Panel标题栏就相当于拖动Area。
|
||||
9. 当内容区只包含一个Panel时,拖动TabPage的页标签就相当于拖动Area的标题栏,从而改变Area的位置。(已实现)
|
||||
10. 当内容区只包含一个Panel时,单击Panel的最大化按钮,就相当于单击Area的最大化按钮同时最大化Panel。
|
||||
11. 当内容区只包含一个Panel时,最大化Area时,Panel也会最大化。
|
||||
|
||||
### TabPage
|
||||
1. 初始添加时,默认宽300px,高250px。位置相对父容器水平居中,垂直居中。
|
||||
2. 背景颜色为#5D6B99
|
||||
3. 当TabPage的页标签被选中时,背景颜色为#F5CC84,文字颜色为#000000。
|
||||
4. 当TabPage的页标签未被选中时,背景颜色与Area的背景颜色相同,文字颜色为#FFFFFF。
|
||||
5. 当TabPage的页标签被选中时,激活的页标签不显示关闭按钮。
|
||||
|
||||
### Panel
|
||||
1. 填充满父容器。
|
||||
|
||||
Reference in New Issue
Block a user