|
@@ -0,0 +1,195 @@
|
|
|
+<template>
|
|
|
+ <div
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ :class="[ns.e('header')]"
|
|
|
+ ref="headerRef"
|
|
|
+ >
|
|
|
+ <template v-for="(value, key) in childList">
|
|
|
+ <div
|
|
|
+ v-if="value.vif"
|
|
|
+ :id="`pane_${value.props.name}`"
|
|
|
+ :class="[
|
|
|
+ ns.e('header-item'),
|
|
|
+ ns.is('activation', isActivation(value.props.name))
|
|
|
+ ]"
|
|
|
+ @click="headerChildrenClick(value)"
|
|
|
+ >
|
|
|
+ {{ value.props.label }}
|
|
|
+ <el-icon v-if="showClose"
|
|
|
+ style="margin-left: 5px"
|
|
|
+ @click.stop="handelClose(value)">
|
|
|
+ <Close/>
|
|
|
+ </el-icon>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <div
|
|
|
+ :class="[ns.e('active_box')]"
|
|
|
+ :style="activeBoxStyle"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ :class="[ns.e('content')]"
|
|
|
+ ref="contentRef"
|
|
|
+ :style="contentStyle"
|
|
|
+ >
|
|
|
+ <Component :is="renderSlot(slots , 'default')"/>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import {useCyNamespace} from "@/utils/xiaochan-element-plus";
|
|
|
+import {renderSlot, ref, nextTick, watch, onMounted} from "vue";
|
|
|
+import TabsProps from "@/components/cy/tabs/src/TabsProps";
|
|
|
+import {TabsPaneContext, tabsRootContextKey} from "./index";
|
|
|
+import '../style/index.scss'
|
|
|
+import {Close} from "@element-plus/icons-vue";
|
|
|
+import {ElIcon} from 'element-plus'
|
|
|
+import {useResizeObserver} from "@vueuse/core";
|
|
|
+
|
|
|
+const props = defineProps(TabsProps)
|
|
|
+const emits = defineEmits([
|
|
|
+ 'update:modelValue',
|
|
|
+ 'change'
|
|
|
+])
|
|
|
+const slots = useSlots()
|
|
|
+
|
|
|
+const ns = useCyNamespace('tabs')
|
|
|
+const headerRef = ref<HTMLDivElement | null>(null)
|
|
|
+const contentRef = ref<HTMLDivElement | null>(null)
|
|
|
+
|
|
|
+const isActivation = (name: string) => {
|
|
|
+ return props.modelValue === name
|
|
|
+}
|
|
|
+
|
|
|
+const headerChildrenClick = (item: TabsPaneContext) => {
|
|
|
+ emits('update:modelValue', item.props.name)
|
|
|
+}
|
|
|
+
|
|
|
+const activeBoxStyle = ref({
|
|
|
+ width: '0px',
|
|
|
+ transform: 'translateX(0px)',
|
|
|
+ height: '0px'
|
|
|
+})
|
|
|
+
|
|
|
+function handleScrolling() {
|
|
|
+ nextTick().then(() => {
|
|
|
+ const current = props.modelValue
|
|
|
+ const currentId = `pane_${current}`
|
|
|
+
|
|
|
+ let scroll = 0;
|
|
|
+ let width = 0;
|
|
|
+ let height = 0;
|
|
|
+ const itemList = headerRef.value?.querySelectorAll('.cy-tabs__header-item')
|
|
|
+
|
|
|
+ for (let i = 0; i < itemList.length; i++) {
|
|
|
+ const item = itemList[i]
|
|
|
+ const id = item.id
|
|
|
+ if (currentId === id) {
|
|
|
+ width = item.clientWidth
|
|
|
+ height = item.clientHeight
|
|
|
+ break
|
|
|
+ }
|
|
|
+ scroll += item.scrollWidth
|
|
|
+ }
|
|
|
+ activeBoxStyle.value = {
|
|
|
+ width: width + 'px',
|
|
|
+ transform: `translateX(${scroll}px)`,
|
|
|
+ height: height + 'px'
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+const showClose = computed(() => {
|
|
|
+ return props.closable
|
|
|
+})
|
|
|
+
|
|
|
+const nameList = computed(() => {
|
|
|
+ const list = []
|
|
|
+
|
|
|
+ for (let key in childList.value) {
|
|
|
+ const item = childList.value[key]
|
|
|
+ if (item.vif) {
|
|
|
+ list.push(item.props.name)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return list
|
|
|
+})
|
|
|
+
|
|
|
+const contentStyle = ref({
|
|
|
+ height: 'max-content',
|
|
|
+})
|
|
|
+const handelClose = (data: TabsPaneContext) => {
|
|
|
+ const name = data.props.name
|
|
|
+
|
|
|
+ function update() {
|
|
|
+ if (props.modelValue !== name) return
|
|
|
+ const index = nameList.value.indexOf(name)
|
|
|
+
|
|
|
+ if (index > 0) {
|
|
|
+ emits('update:modelValue', nameList.value[index - 1])
|
|
|
+ } else if (index === 0) {
|
|
|
+ emits('update:modelValue', nameList.value[index + 1])
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function next() {
|
|
|
+ update()
|
|
|
+ data.setVif(false)
|
|
|
+ }
|
|
|
+
|
|
|
+ if (props.closeEvent != null) {
|
|
|
+ if (props.closeEvent(name)) {
|
|
|
+ next()
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
+ next()
|
|
|
+}
|
|
|
+
|
|
|
+watch(() => props.modelValue, () => {
|
|
|
+ handleScrolling()
|
|
|
+ emits('change', props.modelValue)
|
|
|
+}, {flush: 'post', immediate: true})
|
|
|
+
|
|
|
+watch(() => props.closable, () => {
|
|
|
+ handleScrolling()
|
|
|
+}, {flush: 'post'})
|
|
|
+
|
|
|
+const childList = ref<any>({})
|
|
|
+
|
|
|
+const registerPane = (data: TabsPaneContext) => {
|
|
|
+ childList.value[data.uid] = data
|
|
|
+}
|
|
|
+
|
|
|
+const unregisterPane = (id) => {
|
|
|
+ delete childList.value[id]
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ if (!props.maxContent) {
|
|
|
+ useResizeObserver(contentRef, (entries) => {
|
|
|
+ const entry = entries[0]
|
|
|
+ const marginTop = 3;
|
|
|
+ const padding = 8 + 3;
|
|
|
+ const height = window.innerHeight - entry.target.getBoundingClientRect().top - marginTop - padding
|
|
|
+ contentStyle.value = {
|
|
|
+ height: `${height}px`
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+provide(tabsRootContextKey, {
|
|
|
+ props: props,
|
|
|
+ registerPane: registerPane,
|
|
|
+ unregisterPane: unregisterPane,
|
|
|
+ childList: childList
|
|
|
+})
|
|
|
+</script>
|