7. TabPage的页标签可以定义在上、右、下、左四个边缘显示,通过对外提供的属性设置。
This commit is contained in:
@@ -62,6 +62,7 @@
|
||||
:id="tabPage.id"
|
||||
:title="tabPage.title"
|
||||
:panels="tabPage.panels"
|
||||
:tabPosition="'bottom'"
|
||||
@tabDragStart="onTabDragStart(area.id, $event)"
|
||||
@tabDragMove="onTabDragMove(area.id, $event)"
|
||||
@tabDragEnd="onTabDragEnd"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="tab-page">
|
||||
<!-- 使用panels数组渲染标签栏 -->
|
||||
<div v-if="showTabs && panels && panels.length > 0" class="tab-header">
|
||||
<div class="tab-page" :class="[`tab-page-${tabPosition}`]">
|
||||
<!-- 顶部标签栏 -->
|
||||
<div v-if="tabPosition === 'top' && showTabs && panels && panels.length > 0" class="tab-header tab-header-horizontal">
|
||||
<div
|
||||
v-for="(panel, index) in panels"
|
||||
:key="panel.id"
|
||||
@@ -27,11 +27,96 @@
|
||||
</div>
|
||||
<div class="tab-placeholder"></div>
|
||||
</div>
|
||||
|
||||
<!-- 左侧标签栏 -->
|
||||
<div v-if="tabPosition === 'left' && showTabs && panels && panels.length > 0" class="tab-header tab-header-vertical tab-header-left">
|
||||
<div
|
||||
v-for="(panel, index) in panels"
|
||||
:key="panel.id"
|
||||
:class="['tab-item-vertical', { 'active': activeTabIndex === index }]"
|
||||
@click="setActiveTab(index)"
|
||||
@mousedown="onTabDragStart(index, $event)"
|
||||
>
|
||||
<div class="flex flex-col items-center justify-center w-full h-full py-2">
|
||||
<span class="tab-title-vertical">{{ panel.title }}</span>
|
||||
<!-- 当标签页未被激活时显示关闭按钮 -->
|
||||
<button
|
||||
v-if="activeTabIndex !== index"
|
||||
class="button-icon p-[2px] rounded hover:opacity-100 opacity-80 mt-1"
|
||||
@click.stop="closeTab(panel.id)"
|
||||
aria-label="关闭"
|
||||
>
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" aria-hidden="true">
|
||||
<line x1="2" y1="2" x2="9" y2="9" stroke="#e6efff" stroke-width="1" />
|
||||
<line x1="2" y1="9" x2="9" y2="2" stroke="#e6efff" stroke-width="1" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-placeholder"></div>
|
||||
</div>
|
||||
|
||||
<!-- Tab页内容区域 -->
|
||||
<div class="tab-content">
|
||||
<!-- 渲染slot内容(Panel组件) -->
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
<!-- 右侧标签栏 -->
|
||||
<div v-if="tabPosition === 'right' && showTabs && panels && panels.length > 0" class="tab-header tab-header-vertical tab-header-right">
|
||||
<div
|
||||
v-for="(panel, index) in panels"
|
||||
:key="panel.id"
|
||||
:class="['tab-item-vertical', { 'active': activeTabIndex === index }]"
|
||||
@click="setActiveTab(index)"
|
||||
@mousedown="onTabDragStart(index, $event)"
|
||||
>
|
||||
<div class="flex flex-col items-center justify-center w-full h-full py-2">
|
||||
<span class="tab-title-vertical">{{ panel.title }}</span>
|
||||
<!-- 当标签页未被激活时显示关闭按钮 -->
|
||||
<button
|
||||
v-if="activeTabIndex !== index"
|
||||
class="button-icon p-[2px] rounded hover:opacity-100 opacity-80 mt-1"
|
||||
@click.stop="closeTab(panel.id)"
|
||||
aria-label="关闭"
|
||||
>
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" aria-hidden="true">
|
||||
<line x1="2" y1="2" x2="9" y2="9" stroke="#e6efff" stroke-width="1" />
|
||||
<line x1="2" y1="9" x2="9" y2="2" stroke="#e6efff" stroke-width="1" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-placeholder"></div>
|
||||
</div>
|
||||
|
||||
<!-- 底部标签栏 -->
|
||||
<div v-if="tabPosition === 'bottom' && showTabs && panels && panels.length > 0" class="tab-header tab-header-horizontal tab-header-bottom">
|
||||
<div
|
||||
v-for="(panel, index) in panels"
|
||||
: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="关闭"
|
||||
>
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" aria-hidden="true">
|
||||
<line x1="2" y1="2" x2="9" y2="9" stroke="#e6efff" stroke-width="1" />
|
||||
<line x1="2" y1="9" x2="9" y2="2" stroke="#e6efff" stroke-width="1" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-placeholder"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -50,6 +135,12 @@ const props = defineProps({
|
||||
showTabs: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 标签页位置:top(顶部), right(右侧), bottom(底部), left(左侧)
|
||||
tabPosition: {
|
||||
type: String,
|
||||
default: 'top',
|
||||
validator: (value) => ['top', 'right', 'bottom', 'left'].includes(value)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -138,9 +229,9 @@ const onTabDragEnd = () => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 主容器样式 */
|
||||
.tab-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #5D6B99;
|
||||
@@ -150,28 +241,63 @@ const onTabDragEnd = () => {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
/* 顶部和底部位置:水平布局 */
|
||||
.tab-page-top,
|
||||
.tab-page-bottom {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 左侧和右侧位置:水平布局但标签栏垂直 */
|
||||
.tab-page-left,
|
||||
.tab-page-right {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
:root {
|
||||
--vs-blue-top: #4f72b3;
|
||||
--vs-blue-bottom: #3c5a99;
|
||||
}
|
||||
|
||||
/* 标签栏样式 - 模仿Windows Forms */
|
||||
.tab-header {
|
||||
/* 水平标签栏样式 */
|
||||
.tab-header-horizontal {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
height: 28px;
|
||||
background: transparent;
|
||||
border-bottom: none;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.tab-header-bottom {
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
padding-top: 0;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
/* 垂直标签栏样式 */
|
||||
.tab-header-vertical {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
width: 28px;
|
||||
background: transparent;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
.tab-header-right {
|
||||
flex-direction: column;
|
||||
padding-left: 0;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
/* 水平标签项样式 */
|
||||
.tab-item {
|
||||
height: 26px;
|
||||
margin-left: 1px;
|
||||
background: linear-gradient(to bottom, var(--vs-blue-top), var(--vs-blue-bottom));
|
||||
border-bottom-color: #c7d2ea;
|
||||
border-radius: 3px 3px 0 0;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
@@ -179,6 +305,13 @@ const onTabDragEnd = () => {
|
||||
font-size: 12px;
|
||||
user-select: none;
|
||||
min-width: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 底部位置的特殊样式 */
|
||||
.tab-page-bottom .tab-item {
|
||||
border-radius: 0 0 3px 3px;
|
||||
}
|
||||
|
||||
.tab-title {
|
||||
@@ -191,28 +324,95 @@ const onTabDragEnd = () => {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
/* 垂直标签项样式 */
|
||||
.tab-item-vertical {
|
||||
width: 26px;
|
||||
height: 60px;
|
||||
margin-top: 1px;
|
||||
background: linear-gradient(to right, var(--vs-blue-top), var(--vs-blue-bottom));
|
||||
border-radius: 3px 0 0 3px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 右侧位置的特殊样式 */
|
||||
.tab-page-right .tab-item-vertical {
|
||||
border-radius: 0 3px 3px 0;
|
||||
background: linear-gradient(to left, var(--vs-blue-top), var(--vs-blue-bottom));
|
||||
}
|
||||
|
||||
.tab-title-vertical {
|
||||
text-align: center;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin: 0 4px;
|
||||
writing-mode: vertical-rl;
|
||||
text-orientation: mixed;
|
||||
}
|
||||
|
||||
/* 活动标签样式 */
|
||||
.tab-item.active,
|
||||
.tab-item-vertical.active {
|
||||
background: #F5CC84;
|
||||
border: 1px solid #c7d2ea;
|
||||
border-bottom-color: #F5CC84;
|
||||
border-top-width: 2px;
|
||||
border-top-color: #0078d7;
|
||||
color: #000000;
|
||||
font-weight: 500;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.tab-item:hover {
|
||||
/* 顶部位置活动标签 */
|
||||
.tab-page-top .tab-item.active {
|
||||
border-bottom-color: #F5CC84;
|
||||
border-top-width: 2px;
|
||||
border-top-color: #0078d7;
|
||||
}
|
||||
|
||||
/* 底部位置活动标签 */
|
||||
.tab-page-bottom .tab-item.active {
|
||||
border-top-color: #F5CC84;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: #0078d7;
|
||||
}
|
||||
|
||||
/* 左侧位置活动标签 */
|
||||
.tab-page-left .tab-item-vertical.active {
|
||||
border-right-color: #F5CC84;
|
||||
border-left-width: 2px;
|
||||
border-left-color: #0078d7;
|
||||
}
|
||||
|
||||
/* 右侧位置活动标签 */
|
||||
.tab-page-right .tab-item-vertical.active {
|
||||
border-left-color: #F5CC84;
|
||||
border-right-width: 2px;
|
||||
border-right-color: #0078d7;
|
||||
}
|
||||
|
||||
/* 悬停样式 */
|
||||
.tab-item:hover,
|
||||
.tab-item-vertical:hover {
|
||||
background: linear-gradient(to bottom, #5a7db8, #4667a4);
|
||||
}
|
||||
|
||||
.tab-item.active:hover {
|
||||
.tab-page-right .tab-item-vertical:hover {
|
||||
background: linear-gradient(to left, #5a7db8, #4667a4);
|
||||
}
|
||||
|
||||
.tab-item.active:hover,
|
||||
.tab-item-vertical.active:hover {
|
||||
background: #F5CC84;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 按钮样式 */
|
||||
.button-icon {
|
||||
background: transparent;
|
||||
border: none;
|
||||
@@ -223,28 +423,31 @@ const onTabDragEnd = () => {
|
||||
}
|
||||
|
||||
/* 确保在活动标签页中的按钮样式正确 */
|
||||
.tab-item.active .button-icon {
|
||||
.tab-item.active .button-icon,
|
||||
.tab-item-vertical.active .button-icon {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.tab-item.active .button-icon:hover {
|
||||
.tab-item.active .button-icon:hover,
|
||||
.tab-item-vertical.active .button-icon:hover {
|
||||
opacity: 1;
|
||||
background: #f3f4f6;
|
||||
}
|
||||
|
||||
/* 确保在非活动标签页中的按钮样式正确 */
|
||||
.tab-item:not(.active) .button-icon svg line {
|
||||
.tab-item:not(.active) .button-icon svg line,
|
||||
.tab-item-vertical:not(.active) .button-icon svg line {
|
||||
stroke: #e6efff;
|
||||
}
|
||||
|
||||
.tab-item:not(.active) .button-icon:hover svg line {
|
||||
.tab-item:not(.active) .button-icon:hover svg line,
|
||||
.tab-item-vertical:not(.active) .button-icon:hover svg line {
|
||||
stroke: white;
|
||||
}
|
||||
|
||||
.tab-placeholder {
|
||||
background: transparent;
|
||||
border: transparent;
|
||||
border-bottom-color: #c7d2ea;
|
||||
}
|
||||
|
||||
/* 内容区域样式 */
|
||||
@@ -280,20 +483,35 @@ const onTabDragEnd = () => {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
.tab-header::-webkit-scrollbar {
|
||||
/* 滚动条样式 - 水平标签栏 */
|
||||
.tab-header-horizontal::-webkit-scrollbar {
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.tab-header::-webkit-scrollbar-track {
|
||||
.tab-header-horizontal::-webkit-scrollbar-track {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.tab-header::-webkit-scrollbar-thumb {
|
||||
.tab-header-horizontal::-webkit-scrollbar-thumb {
|
||||
background: #c7d2ea;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* 滚动条样式 - 垂直标签栏 */
|
||||
.tab-header-vertical::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.tab-header-vertical::-webkit-scrollbar-track {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.tab-header-vertical::-webkit-scrollbar-thumb {
|
||||
background: #c7d2ea;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* 内容区域滚动条 */
|
||||
.tab-panel::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
@@ -301,7 +519,6 @@ const onTabDragEnd = () => {
|
||||
|
||||
.tab-panel::-webkit-scrollbar-track {
|
||||
background: #f5f7fb;
|
||||
border-left: 1px solid #c7d2ea;
|
||||
}
|
||||
|
||||
.tab-panel::-webkit-scrollbar-thumb {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
4. 当TabPage的页标签未被选中时,背景颜色与Area的背景颜色相同,文字颜色为#FFFFFF。
|
||||
5. 当TabPage的页标签被选中时,激活的页标签不显示关闭按钮。
|
||||
6. 当TabPage的页标签被选中时,鼠标显示为移动,否则显示为手型。
|
||||
7. TabPage的页标签可以定义在上、右、下、左四个边缘显示,通过对外提供的属性设置。
|
||||
|
||||
### Panel
|
||||
1. 填充满父容器。
|
||||
@@ -34,3 +35,5 @@
|
||||
1. 当一个Panel被拖动时,显示停靠指示器。
|
||||
2. 当拖动Panel到指示器时,显示停靠区。
|
||||
3. 当主区域内没有其他Area时,隐藏外部边缘指示器、中心区域容器,只显示中心指示器。
|
||||
4. 当将TabPage的页标签拖动到中心指示器时
|
||||
4.1. 如果TabPage只有这一个标签页,则将TabPage停靠到中心区域。
|
||||
Reference in New Issue
Block a user