UNPKG

vxe-pc-ui

Version:
941 lines (874 loc) 34.7 kB
import { ref, h, reactive, inject, PropType, provide, computed, onUnmounted, watch, nextTick, onMounted } from 'vue' import { defineVxeComponent } from '../../ui/src/comp' import { createEvent, getConfig, getIcon, globalEvents, permission, useSize, renderEmptyElement } from '../../ui' import { getSlotVNs } from '../../ui/src/vn' import { toCssUnit, addClass, removeClass } from '../../ui/src/dom' import { isEnableConf } from '../../ui/src/utils' import { warnLog, errLog } from '../../ui/src/log' import XEUtils from 'xe-utils' import VxeLoadingComponent from '../../loading/src/loading' import type { VxeTabsPropTypes, VxeTabPaneProps, VxeTabsEmits, TabsInternalData, TabsReactData, TabsPrivateRef, VxeTabsPrivateComputed, VxeTabsConstructor, VxeTabsPrivateMethods, VxeTabPaneDefines, ValueOf, TabsMethods, TabsPrivateMethods, VxeComponentStyleType } from '../../../types' const scrollbarOffsetSize = 20 export default defineVxeComponent({ name: 'VxeTabs', props: { modelValue: [String, Number, Boolean] as PropType<VxeTabsPropTypes.ModelValue>, options: Array as PropType<VxeTabsPropTypes.Options>, width: [String, Number] as PropType<VxeTabsPropTypes.Width>, height: [String, Number] as PropType<VxeTabsPropTypes.Height>, destroyOnClose: { type: Boolean as PropType<VxeTabsPropTypes.DestroyOnClose>, default: () => getConfig().tabs.destroyOnClose }, titleWidth: [String, Number] as PropType<VxeTabsPropTypes.TitleWidth>, titleAlign: [String, Number] as PropType<VxeTabsPropTypes.TitleAlign>, type: { type: String as PropType<VxeTabsPropTypes.Type>, default: () => getConfig().tabs.type }, position: { type: String as PropType<VxeTabsPropTypes.Position>, default: () => getConfig().tabs.position }, showClose: Boolean as PropType<VxeTabsPropTypes.ShowClose>, showBody: { type: Boolean as PropType<VxeTabsPropTypes.ShowBody>, default: true }, padding: { type: Boolean as PropType<VxeTabsPropTypes.Padding>, default: () => getConfig().tabs.padding }, trigger: String as PropType<VxeTabsPropTypes.Trigger>, beforeChangeMethod: Function as PropType<VxeTabsPropTypes.BeforeChangeMethod>, closeConfig: Object as PropType<VxeTabsPropTypes.CloseConfig>, refreshConfig: Object as PropType<VxeTabsPropTypes.RefreshConfig>, size: { type: String as PropType<VxeTabsPropTypes.Size>, default: () => getConfig().tabs.size || getConfig().size }, // 已废弃 beforeCloseMethod: Function as PropType<VxeTabsPropTypes.BeforeCloseMethod> }, emits: [ 'update:modelValue', 'change', 'tab-change', 'tab-change-fail', 'tab-close', 'tab-close-fail', 'tab-click', 'tab-load' ] as VxeTabsEmits, setup (props, context) { const { slots, emit } = context const xID = XEUtils.uniqueId() const $xeParentTabs = inject<(VxeTabsConstructor & VxeTabsPrivateMethods) | null>('$xeTabs', null) const { computeSize } = useSize(props) const refElem = ref<HTMLDivElement>() const refHeadWrapperElem = ref<HTMLDivElement>() const refHeadPrevElem = ref<HTMLDivElement>() const refHeadNextElem = ref<HTMLDivElement>() const reactData = reactive<TabsReactData>({ staticTabs: [], activeName: null, initNames: [], lintLeft: 0, lintTop: 0, lintWidth: 0, lintHeight: 0, scrollbarWidth: 0, scrollbarHeight: 0, isTabOver: false, resizeFlag: 1, cacheTabMaps: {} }) const internalData: TabsInternalData = { slTimeout: undefined } const refMaps: TabsPrivateRef = { refElem } const computeTabType = computed(() => { const { type } = props return type || 'default' }) const computeTabPosition = computed(() => { const { position } = props return position || 'top' }) const computeLrPosition = computed(() => { const tabPosition = computeTabPosition.value return tabPosition === 'left' || tabPosition === 'right' }) const computeLineStyle = computed(() => { const { lintLeft, lintTop, lintWidth, lintHeight } = reactData const lrPosition = computeLrPosition.value return lrPosition ? { top: `${lintTop}px`, height: `${lintHeight}px` } : { left: `${lintLeft}px`, width: `${lintWidth}px` } }) const computeWrapperStyle = computed(() => { const { width } = props const stys: VxeComponentStyleType = {} if (width) { stys.width = toCssUnit(width) } return stys }) const computeCloseOpts = computed(() => { return Object.assign({}, getConfig().tabs.closeConfig, props.closeConfig) }) const computeRefreshOpts = computed(() => { return Object.assign({}, getConfig().tabs.refreshConfig, props.refreshConfig) }) const computeTabOptions = computed(() => { const { options } = props return (options || []).filter((item) => handleFilterTab(item)) }) const computeTabStaticOptions = computed(() => { const { staticTabs } = reactData return staticTabs.filter((item) => handleFilterTab(item)) }) const computeParentTabsResizeFlag = computed(() => { return $xeParentTabs ? $xeParentTabs.reactData.resizeFlag : null }) const computeMaps: VxeTabsPrivateComputed = { } const $xeTabs = { xID, props, context, reactData, getRefMaps: () => refMaps, getComputeMaps: () => computeMaps } as unknown as VxeTabsConstructor & VxeTabsPrivateMethods const handleFilterTab = (item: VxeTabPaneProps | VxeTabPaneDefines.TabConfig) => { const { permissionCode } = item if (permissionCode) { if (!permission.checkVisible(permissionCode)) { return false } } return true } const callSlot = (slotFunc: any, params: any) => { if (slotFunc) { if (XEUtils.isString(slotFunc)) { slotFunc = slots[slotFunc] || null } if (XEUtils.isFunction(slotFunc)) { return getSlotVNs(slotFunc(params)) } } return [] } const checkScrolling = () => { const lrPosition = computeLrPosition.value const headerWrapperEl = refHeadWrapperElem.value const headPrevEl = refHeadPrevElem.value const headNextEl = refHeadNextElem.value if (headerWrapperEl) { const { scrollLeft, scrollTop, clientWidth, clientHeight, scrollWidth, scrollHeight } = headerWrapperEl if (headPrevEl) { if ((lrPosition ? scrollTop : scrollLeft) > 0) { addClass(headPrevEl, 'scrolling--middle') } else { removeClass(headPrevEl, 'scrolling--middle') } } if (headNextEl) { if (lrPosition ? (clientHeight < scrollHeight - Math.ceil(scrollTop)) : (clientWidth < scrollWidth - Math.ceil(scrollLeft))) { addClass(headNextEl, 'scrolling--middle') } else { removeClass(headNextEl, 'scrolling--middle') } } } } const updateTabStyle = () => { const handleStyle = () => { const { activeName } = reactData const tabType = computeTabType.value const tabOptions = computeTabOptions.value const tabStaticOptions = computeTabStaticOptions.value const headerWrapperEl = refHeadWrapperElem.value const lrPosition = computeLrPosition.value let lintWidth = 0 let lintHeight = 0 let lintLeft = 0 let lintTop = 0 let sBarWidth = 0 let sBarHeight = 0 let isOver = false if (headerWrapperEl) { const index = XEUtils.findIndexOf(tabStaticOptions.length ? tabStaticOptions : tabOptions, item => item.name === activeName) const { children, offsetWidth, scrollWidth, offsetHeight, scrollHeight, clientWidth, clientHeight } = headerWrapperEl sBarWidth = offsetWidth - clientWidth sBarHeight = offsetHeight - clientHeight if (lrPosition) { isOver = scrollHeight !== clientHeight if (index > -1) { const tabEl = children[index] as HTMLDivElement if (tabEl) { const tabHeight = tabEl.clientHeight const tabWidth = tabEl.clientWidth if (tabType === 'card') { lintWidth = tabWidth lintHeight = tabHeight lintTop = tabEl.offsetTop } else if (tabType === 'border-card') { lintWidth = tabWidth lintHeight = tabHeight lintTop = tabEl.offsetTop - 1 } else { lintHeight = Math.max(4, Math.floor(tabHeight * 0.6)) lintTop = tabEl.offsetTop + Math.floor((tabHeight - lintHeight) / 2) } } } } else { isOver = scrollWidth !== clientWidth if (index > -1) { const tabEl = children[index] as HTMLDivElement if (tabEl) { const tabWidth = tabEl.clientWidth if (tabType === 'card') { lintWidth = tabWidth + 1 lintLeft = tabEl.offsetLeft } else if (tabType === 'border-card') { lintWidth = tabWidth lintLeft = tabEl.offsetLeft - 1 } else { lintWidth = Math.max(4, Math.floor(tabWidth * 0.6)) lintLeft = tabEl.offsetLeft + Math.floor((tabWidth - lintWidth) / 2) } } } } } reactData.scrollbarWidth = sBarWidth reactData.scrollbarHeight = sBarHeight reactData.lintLeft = lintLeft reactData.lintTop = lintTop reactData.lintWidth = lintWidth reactData.lintHeight = lintHeight reactData.isTabOver = isOver checkScrolling() } handleStyle() nextTick(handleStyle) } const dispatchEvent = (type: ValueOf<VxeTabsEmits>, params: Record<string, any>, evnt: Event | null) => { emit(type, createEvent(evnt, { $tabs: $xeTabs }, params)) } const emitModel = (value: any) => { emit('update:modelValue', value) } const addInitName = (name: VxeTabsPropTypes.ModelValue, evnt: Event | null) => { const { initNames } = reactData if (name && !initNames.includes(name)) { dispatchEvent('tab-load', { name }, evnt) initNames.push(name) return true } return false } const initDefaultName = (list?: VxeTabsPropTypes.Options | VxeTabPaneDefines.TabConfig[]) => { let activeName: VxeTabsPropTypes.ModelValue = null const nameMaps: Record<string, { loading: boolean }> = {} if (list && list.length) { let validVal = false activeName = props.modelValue list.forEach((item) => { const { name, preload } = item || {} if (name) { nameMaps[`${name}`] = { loading: false } if (activeName === name) { validVal = true } if (preload) { addInitName(name, null) } } }) if (!validVal) { activeName = list[0].name addInitName(activeName, null) emitModel(activeName) } } reactData.activeName = activeName reactData.cacheTabMaps = nameMaps } const clickEvent = (evnt: KeyboardEvent, item: VxeTabPaneProps | VxeTabPaneDefines.TabConfig) => { const { trigger } = props const beforeMethod = props.beforeChangeMethod || getConfig().tabs.beforeChangeMethod const { activeName } = reactData const { name } = item const value = name dispatchEvent('tab-click', { name }, evnt) if (trigger === 'manual') { return } if (name !== activeName) { Promise.resolve( !beforeMethod || beforeMethod({ $tabs: $xeTabs, name, oldName: activeName, newName: name, option: item }) ).then((status) => { if (status) { reactData.activeName = name emitModel(value) addInitName(name, evnt) dispatchEvent('change', { value, name, oldName: activeName, newName: name, option: item }, evnt) dispatchEvent('tab-change', { value, name, oldName: activeName, newName: name, option: item }, evnt) } else { dispatchEvent('tab-change-fail', { value, name, oldName: activeName, newName: name, option: item }, evnt) } }).catch(() => { dispatchEvent('tab-change-fail', { value, name, oldName: activeName, newName: name, option: item }, evnt) }) } } const handleRefreshTabEvent = (evnt: KeyboardEvent, item: VxeTabPaneDefines.TabConfig | VxeTabPaneProps) => { evnt.stopPropagation() const { activeName, cacheTabMaps } = reactData const { name } = item const refreshOpts = computeRefreshOpts.value const { queryMethod } = refreshOpts const cacheItem = name ? cacheTabMaps[`${name}`] : null if (cacheItem) { if (queryMethod) { if (cacheItem.loading) { return } cacheItem.loading = true Promise.resolve( queryMethod({ $tabs: $xeTabs, value: activeName, name, option: item }) ).finally(() => { cacheItem.loading = false }) } else { errLog('vxe.error.notFunc', ['refresh-config.queryMethod']) } } } const handleCloseTabEvent = (evnt: KeyboardEvent, item: VxeTabPaneDefines.TabConfig | VxeTabPaneProps, index: number, list: VxeTabsPropTypes.Options | VxeTabPaneDefines.TabConfig[]) => { evnt.stopPropagation() const { activeName } = reactData const closeOpts = computeCloseOpts.value const beforeMethod = closeOpts.beforeMethod || props.beforeCloseMethod || getConfig().tabs.beforeCloseMethod const { name } = item const value = activeName let nextName = value if (activeName === name) { const nextItem = index < list.length - 1 ? list[index + 1] : list[index - 1] nextName = nextItem ? nextItem.name : null } Promise.resolve( !beforeMethod || beforeMethod({ $tabs: $xeTabs, value, name, nextName, option: item }) ).then(status => { if (status) { dispatchEvent('tab-close', { value, name, nextName }, evnt) } else { dispatchEvent('tab-close-fail', { value, name, nextName }, evnt) } }).catch(() => { dispatchEvent('tab-close-fail', { value, name, nextName }, evnt) }) } const startScrollAnimation = (offsetPos: number, offsetSize: number) => { const { slTimeout } = internalData const lrPosition = computeLrPosition.value let offsetLeft = lrPosition ? 0 : offsetSize let offsetTop = lrPosition ? offsetSize : 0 let scrollCount = 6 let delayNum = 35 if (slTimeout) { clearTimeout(slTimeout) internalData.slTimeout = undefined } const scrollAnimate = () => { const headerWrapperEl = refHeadWrapperElem.value if (scrollCount > 0) { scrollCount-- if (headerWrapperEl) { const { clientWidth, clientHeight, scrollWidth, scrollHeight, scrollLeft, scrollTop } = headerWrapperEl if (lrPosition) { offsetTop = Math.floor(offsetTop / 2) if (offsetPos > 0) { if (clientHeight + scrollTop < scrollHeight) { headerWrapperEl.scrollTop += offsetTop delayNum -= 4 internalData.slTimeout = setTimeout(scrollAnimate, delayNum) } } else { if (scrollTop > 0) { headerWrapperEl.scrollTop -= offsetTop delayNum -= 4 internalData.slTimeout = setTimeout(scrollAnimate, delayNum) } } } else { offsetLeft = Math.floor(offsetLeft / 2) if (offsetPos > 0) { if (clientWidth + scrollLeft < scrollWidth) { headerWrapperEl.scrollLeft += offsetLeft delayNum -= 4 internalData.slTimeout = setTimeout(scrollAnimate, delayNum) } } else { if (scrollLeft > 0) { headerWrapperEl.scrollLeft -= offsetLeft delayNum -= 4 internalData.slTimeout = setTimeout(scrollAnimate, delayNum) } } } updateTabStyle() } } } scrollAnimate() } const handleScrollToLeft = (offsetPos: number) => { const lrPosition = computeLrPosition.value const headerWrapperEl = refHeadWrapperElem.value if (headerWrapperEl) { const { clientWidth, clientHeight } = headerWrapperEl const offsetSize = Math.floor((lrPosition ? clientHeight : clientWidth) * 0.75) startScrollAnimation(offsetPos, offsetSize) } } const scrollLeftEvent = () => { handleScrollToLeft(-1) } const scrollRightEvent = () => { handleScrollToLeft(1) } const scrollToTab = (name: VxeTabsPropTypes.ModelValue) => { const tabOptions = computeTabOptions.value const tabStaticOptions = computeTabStaticOptions.value const lrPosition = computeLrPosition.value return nextTick().then(() => { const headerWrapperEl = refHeadWrapperElem.value if (headerWrapperEl) { const index = XEUtils.findIndexOf(tabStaticOptions.length ? tabStaticOptions : tabOptions, item => item.name === name) if (index > -1) { const { scrollLeft, scrollTop, clientWidth, clientHeight, children } = headerWrapperEl const tabEl = children[index] as HTMLDivElement if (tabEl) { if (lrPosition) { const tabOffsetTop = tabEl.offsetTop const tabClientHeight = tabEl.clientHeight // 如果顶部被挡 const overSize = (tabOffsetTop + tabClientHeight) - (scrollTop + clientHeight) if (overSize > 0) { headerWrapperEl.scrollTop += overSize } // 如果底部被挡,优先 if (tabOffsetTop < scrollTop) { headerWrapperEl.scrollTop = tabOffsetTop } } else { const tabOffsetLeft = tabEl.offsetLeft const tabClientWidth = tabEl.clientWidth // 如果右侧被挡 const overSize = (tabOffsetLeft + tabClientWidth) - (scrollLeft + clientWidth) if (overSize > 0) { headerWrapperEl.scrollLeft += overSize } // 如果左侧被挡,优先 if (tabOffsetLeft < scrollLeft) { headerWrapperEl.scrollLeft = tabOffsetLeft } } } } updateTabStyle() } }) } const handlePrevNext = (isNext: boolean) => { const { activeName } = reactData const tabOptions = computeTabOptions.value const tabStaticOptions = computeTabStaticOptions.value const list = tabStaticOptions.length ? tabStaticOptions : tabOptions const index = XEUtils.findIndexOf(list, item => item.name === activeName) if (index > -1) { let item: VxeTabPaneProps | null = null if (isNext) { if (index < list.length - 1) { item = list[index + 1] } } else { if (index > 0) { item = list[index - 1] } } if (item) { const name = item.name const value = name reactData.activeName = name emitModel(value) addInitName(name, null) } } return nextTick() } const tabsMethods: TabsMethods = { dispatchEvent, scrollToTab, prev () { return handlePrevNext(false) }, next () { return handlePrevNext(true) }, prevTab () { warnLog('vxe.error.delFunc', ['[tabs] prevTab', 'prev']) return tabsMethods.prev() }, nextTab () { warnLog('vxe.error.delFunc', ['[tabs] nextTab', 'next']) return tabsMethods.next() } } const tabsPrivateMethods: TabsPrivateMethods = { } Object.assign($xeTabs, tabsMethods, tabsPrivateMethods) const renderTabHeader = (tabList: VxeTabsPropTypes.Options | VxeTabPaneDefines.TabConfig[]) => { const { titleWidth: allTitleWidth, titleAlign: allTitleAlign, showClose, closeConfig, refreshConfig } = props const { activeName, scrollbarWidth, scrollbarHeight, isTabOver, cacheTabMaps } = reactData const tabType = computeTabType.value const tabPosition = computeTabPosition.value const lrPosition = computeLrPosition.value const lineStyle = computeLineStyle.value const tabPrefixSlot = slots.tabPrefix || slots['tab-prefix'] || slots.prefix const tabSuffixSlot = slots.tabSuffix || slots['tab-suffix'] || slots.suffix || slots.extra const closeOpts = computeCloseOpts.value const closeVisibleMethod = closeOpts.visibleMethod const refreshOpts = computeRefreshOpts.value const refreshVisibleMethod = refreshOpts.visibleMethod return h('div', { key: 'th', class: ['vxe-tabs-header', `type--${tabType}`, `pos--${tabPosition}`] }, [ tabPrefixSlot ? h('div', { class: ['vxe-tabs-header--prefix', `type--${tabType}`, `pos--${tabPosition}`] }, callSlot(tabPrefixSlot, { name: activeName })) : renderEmptyElement($xeTabs), isTabOver ? h('div', { ref: refHeadPrevElem, class: ['vxe-tabs-header--bar vxe-tabs-header--prev-bar', `type--${tabType}`, `pos--${tabPosition}`], onClick: scrollLeftEvent }, [ h('span', { class: lrPosition ? getIcon().TABS_TAB_BUTTON_TOP : getIcon().TABS_TAB_BUTTON_LEFT }) ]) : renderEmptyElement($xeTabs), h('div', { class: ['vxe-tabs-header--wrapper', `type--${tabType}`, `pos--${tabPosition}`] }, [ h('div', { ref: refHeadWrapperElem, class: 'vxe-tabs-header--item-wrapper', style: lrPosition ? { marginRight: `-${scrollbarWidth + scrollbarOffsetSize}px`, paddingRight: `${scrollbarOffsetSize}px` } : { marginBottom: `-${scrollbarHeight + scrollbarOffsetSize}px`, paddingBottom: `${scrollbarOffsetSize}px` }, onScroll: checkScrolling }, tabList.map((item, index) => { const { title, titleWidth, titleAlign, icon, name } = item const itemSlots = item.slots || {} const titleSlot = itemSlots.title || itemSlots.tab const titlePrefixSlot = itemSlots.titlePrefix || (itemSlots as any)['title-prefix'] const titleSuffixSlot = itemSlots.titleSuffix || (itemSlots as any)['title-suffix'] const itemWidth = titleWidth || allTitleWidth const itemAlign = titleAlign || allTitleAlign const params = { $tabs: $xeTabs, value: activeName, name, option: item } const isActive = activeName === name const cacheItem = name ? cacheTabMaps[`${name}`] : null const isLoading = cacheItem ? cacheItem.loading : false return h('div', { key: `${name}`, class: ['vxe-tabs-header--item', `type--${tabType}`, `pos--${tabPosition}`, itemAlign ? `align--${itemAlign}` : '', { 'is--active': isActive }], style: itemWidth ? { width: toCssUnit(itemWidth) } : undefined, onClick (evnt: KeyboardEvent) { clickEvent(evnt, item) } }, [ h('div', { class: 'vxe-tabs-header--item-inner' }, [ h('div', { class: 'vxe-tabs-header--item-content' }, [ icon ? h('span', { class: 'vxe-tabs-header--item-icon' }, [ h('i', { class: icon }) ]) : renderEmptyElement($xeTabs), titlePrefixSlot ? h('span', { class: 'vxe-tabs-header--item-prefix' }, callSlot(titlePrefixSlot, { name, title })) : renderEmptyElement($xeTabs), h('span', { class: 'vxe-tabs-header--item-name' }, titleSlot ? callSlot(titleSlot, { name, title }) : `${title}`), titleSuffixSlot ? h('span', { class: 'vxe-tabs-header--item-suffix' }, callSlot(titleSuffixSlot, { name, title })) : renderEmptyElement($xeTabs) ]), (isEnableConf(refreshConfig) || refreshOpts.enabled) && (refreshVisibleMethod ? refreshVisibleMethod(params) : true) ? h('div', { class: ['vxe-tabs-header--refresh-btn', { 'is--active': isActive, 'is--loading': isLoading, 'is--disabled': isLoading }], onClick (evnt: KeyboardEvent) { handleRefreshTabEvent(evnt, item) } }, [ h('i', { class: isLoading ? getIcon().TABS_TAB_REFRESH_LOADING : getIcon().TABS_TAB_REFRESH }) ]) : renderEmptyElement($xeTabs), (showClose || (isEnableConf(closeConfig) || closeOpts.enabled)) && (!closeVisibleMethod || closeVisibleMethod(params)) ? h('div', { class: ['vxe-tabs-header--close-btn', { 'is--active': isActive }], onClick (evnt: KeyboardEvent) { handleCloseTabEvent(evnt, item, index, tabList) } }, [ h('i', { class: getIcon().TABS_TAB_CLOSE }) ]) : renderEmptyElement($xeTabs) ]) ]) }).concat([ h('span', { key: 'line', class: ['vxe-tabs-header--active-line', `type--${tabType}`, `pos--${tabPosition}`], style: lineStyle }) ])) ]), isTabOver ? h('div', { ref: refHeadNextElem, class: ['vxe-tabs-header--bar vxe-tabs-header--next-bar', `type--${tabType}`, `pos--${tabPosition}`], onClick: scrollRightEvent }, [ h('span', { class: lrPosition ? getIcon().TABS_TAB_BUTTON_BOTTOM : getIcon().TABS_TAB_BUTTON_RIGHT }) ]) : renderEmptyElement($xeTabs), tabSuffixSlot ? h('div', { class: ['vxe-tabs-header--suffix', `type--${tabType}`, `pos--${tabPosition}`] }, callSlot(tabSuffixSlot, { name: activeName })) : renderEmptyElement($xeTabs) ]) } const renderTabPane = (item: VxeTabPaneProps | VxeTabPaneDefines.TabConfig) => { const { initNames, activeName } = reactData const { name, slots } = item const defaultSlot = slots ? slots.default : null return name && initNames.includes(name) ? h('div', { key: `${name}`, class: ['vxe-tabs-pane--item', { 'is--visible': activeName === name }] }, defaultSlot ? callSlot(defaultSlot, { name }) : []) : renderEmptyElement($xeTabs) } const renderTabContent = (tabList: VxeTabsPropTypes.Options | VxeTabPaneDefines.TabConfig[]) => { const { destroyOnClose } = props const { activeName } = reactData if (destroyOnClose) { const activeTab = tabList.find(item => item.name === activeName) return [activeTab ? renderTabPane(activeTab) : renderEmptyElement($xeTabs)] } return tabList.map((item) => renderTabPane(item)) } const rendetTabBody = (tabList: VxeTabsPropTypes.Options | VxeTabPaneDefines.TabConfig[]) => { const { height, padding, showBody } = props const { activeName, cacheTabMaps } = reactData const vSize = computeSize.value const tabType = computeTabType.value const tabPosition = computeTabPosition.value const refreshOpts = computeRefreshOpts.value const { showLoading } = refreshOpts const headerpSlot = slots.header const footerSlot = slots.footer if (!showBody) { return renderEmptyElement($xeTabs) } const cacheItem = activeName ? cacheTabMaps[`${activeName}`] : null const isLoading = cacheItem ? cacheItem.loading : false const defParams = { name: activeName } return h('div', { key: 'tb', class: ['vxe-tabs-pane--wrapper', `type--${tabType}`, `pos--${tabPosition}`, { 'is--content': showBody }] }, [ headerpSlot ? h('div', { class: 'vxe-tabs-pane--header' }, callSlot(headerpSlot, defParams)) : renderEmptyElement($xeTabs), h('div', { class: ['vxe-tabs-pane--body', `type--${tabType}`, `pos--${tabPosition}`, { [`size--${vSize}`]: vSize, 'is--padding': padding, 'is--height': height }] }, renderTabContent(tabList)), footerSlot ? h('div', { class: 'vxe-tabs-pane--footer' }, callSlot(footerSlot, defParams)) : renderEmptyElement($xeTabs), showLoading && isLoading ? renderEmptyElement($xeTabs) : h(VxeLoadingComponent, { class: 'vxe-tabs--loading', modelValue: isLoading }) ]) } const renderVN = () => { const { height, padding, trigger } = props const { activeName } = reactData const vSize = computeSize.value const tabOptions = computeTabOptions.value const tabStaticOptions = computeTabStaticOptions.value const tabType = computeTabType.value const tabPosition = computeTabPosition.value const wrapperStyle = computeWrapperStyle.value const defaultSlot = slots.default const tabList = defaultSlot ? tabStaticOptions : tabOptions const vns = [ h('div', { key: 'ts', class: 'vxe-tabs-slots' }, defaultSlot ? defaultSlot({ name: activeName }) : []) ] if (tabPosition === 'right' || tabPosition === 'bottom') { vns.push( rendetTabBody(tabList), renderTabHeader(tabList) ) } else { vns.push( renderTabHeader(tabList), rendetTabBody(tabList) ) } return h('div', { ref: refElem, class: ['vxe-tabs', `pos--${tabPosition}`, `vxe-tabs--${tabType}`, `trigger--${trigger === 'manual' ? 'trigger' : 'default'}`, { [`size--${vSize}`]: vSize, 'is--padding': padding, 'is--height': height }], style: wrapperStyle }, vns) } watch(() => props.position, () => { reactData.resizeFlag++ }) watch(() => props.modelValue, (val) => { addInitName(val, null) reactData.activeName = val }) watch(() => reactData.activeName, (val) => { scrollToTab(val) }) const optsFlag = ref(0) watch(() => props.options ? props.options.length : -1, () => { optsFlag.value++ }) watch(() => props.options, () => { optsFlag.value++ }) watch(optsFlag, () => { initDefaultName(props.options) reactData.resizeFlag++ }) const stFlag = ref(0) watch(() => reactData.staticTabs ? reactData.staticTabs.length : -1, () => { stFlag.value++ }) watch(() => reactData.staticTabs, () => { stFlag.value++ }) watch(stFlag, () => { initDefaultName(reactData.staticTabs) reactData.resizeFlag++ }) watch(computeParentTabsResizeFlag, () => { reactData.resizeFlag++ }) watch(() => reactData.resizeFlag, () => { nextTick(() => { updateTabStyle() }) }) onMounted(() => { updateTabStyle() globalEvents.on($xeTabs, 'resize', updateTabStyle) }) onUnmounted(() => { globalEvents.off($xeTabs, 'resize') }) provide('$xeTabs', $xeTabs) addInitName(props.modelValue, null) initDefaultName(reactData.staticTabs.length ? reactData.staticTabs : props.options) $xeTabs.renderVN = renderVN return $xeTabs }, render () { return this.renderVN() } })