This commit is contained in:
张成
2026-03-24 16:07:02 +08:00
commit aa8eaa6ccd
121 changed files with 34042 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
<template>
<div v-show="isActive" class="tab-pane">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'CustomTabPane',
props: {
label: {
type: String,
required: true
},
name: {
type: String,
required: true
}
},
data() {
return {
isActive: false,
parentTabs: null
}
},
mounted() {
this.findParentTabs()
if (this.parentTabs) {
this.parentTabs.registerTab({
label: this.label,
name: this.name
})
this.updateActiveState(this.parentTabs.activeTab)
}
},
beforeDestroy() {
if (this.parentTabs) {
this.parentTabs.unregisterTab(this.name)
}
},
methods: {
findParentTabs() {
let parent = this.$parent
while (parent) {
if (parent.$options.name === 'CustomTabs') {
this.parentTabs = parent
break
}
parent = parent.$parent
}
},
updateActiveState(activeTab) {
this.isActive = activeTab === this.name
}
}
}
</script>
<style scoped>
.tab-pane {
width: 100%;
overflow-x: auto;
overflow-y: visible;
}
</style>

View File

@@ -0,0 +1,118 @@
<template>
<div class="custom-tabs">
<div class="tabs-header">
<div
v-for="tab in tabs"
:key="tab.name"
:class="['tab-item', { active: activeTab === tab.name }]"
@click="handleTabClick(tab.name)">
{{ tab.label }}
</div>
</div>
<div class="tabs-content">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'CustomTabs',
props: {
value: {
type: String,
default: ''
}
},
data() {
return {
activeTab: this.value || '',
tabs: []
}
},
watch: {
value(newVal) {
this.activeTab = newVal
this.updateChildren()
},
activeTab(newVal) {
this.updateChildren()
}
},
mounted() {
this.updateChildren()
},
methods: {
handleTabClick(name) {
this.activeTab = name
this.$emit('input', name)
this.$emit('change', name)
this.updateChildren()
},
registerTab(tab) {
if (!this.tabs.find(t => t.name === tab.name)) {
this.tabs.push(tab)
// 如果没有激活的tab设置第一个为激活
if (!this.activeTab && this.tabs.length > 0) {
this.activeTab = this.tabs[0].name
this.$emit('input', this.activeTab)
}
}
},
unregisterTab(tabName) {
const index = this.tabs.findIndex(t => t.name === tabName)
if (index > -1) {
this.tabs.splice(index, 1)
}
},
updateChildren() {
this.$children.forEach(child => {
if (child.$options.name === 'CustomTabPane') {
child.updateActiveState(this.activeTab)
}
})
}
}
}
</script>
<style scoped>
.custom-tabs {
width: 100%;
}
.tabs-header {
display: flex;
border-bottom: 1px solid #e8eaec;
background: #fff;
}
.tab-item {
padding: 12px 24px;
cursor: pointer;
color: #666;
font-size: 14px;
border-bottom: 2px solid transparent;
transition: all 0.3s;
user-select: none;
}
.tab-item:hover {
color: #333;
}
.tab-item.active {
color: #333;
border-bottom-color: #333;
font-weight: 500;
}
.tabs-content {
padding: 20px 0;
min-height: 400px;
overflow-x: auto;
overflow-y: visible;
width: 100%;
}
</style>