index.jsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. import { ref, onMounted, nextTick, getCurrentInstance, inject, watch } from 'vue'
  2. import { ElTabs, ElIcon, ElDropdownMenu, ElDropdownItem } from 'element-plus'
  3. import { MoreFilled } from '@element-plus/icons-vue'
  4. import CipDropdown from '@cip/components/cip-dropdown'
  5. import { isArray } from '@cip/utils/util'
  6. import { generateProps } from '../helper/component-util'
  7. import { componentScheme } from './tabs.scheme'
  8. import useIntersectionObserver from './hooks/useIntersectionObserver'
  9. import Tab from './tab'
  10. import './index.less'
  11. const Tabs = {
  12. name: 'CipTabsPlus',
  13. props: generateProps(componentScheme),
  14. emits: ['update:modelValue'],
  15. setup (props, { emit, slots, attrs }) {
  16. const onUpdate = (name) => {
  17. emit('update:modelValue', name)
  18. }
  19. // 从 slots 中获取标签页信息
  20. const tabPanes = ref([])
  21. const getTabPane = () => {
  22. let children = slots.default?.()
  23. if (isArray(children) && typeof children[0]?.type === 'symbol') {
  24. children = children[0].children
  25. }
  26. tabPanes.value = children.filter(child => ['ElTabPane', 'CipTabPane'].includes(child.type?.name))
  27. if (!props.modelValue) {
  28. onUpdate(getDefaultTabName(tabPanes.value))
  29. }
  30. }
  31. const getDefaultTabName = (tabs) => {
  32. return tabs.filter(tab => !tab?.props?.disabled)[0]?.props?.name
  33. }
  34. getTabPane()
  35. // 获取隐藏的tab页
  36. const { namespace } = inject('cip-config')
  37. const ins = getCurrentInstance()
  38. const id = `cip-tabs-plus_${ins.uid}` // 元素id
  39. const canUseIntersectionApi = !!IntersectionObserver
  40. const canScroll = ref(true)
  41. const hiddenTabs = ref([])
  42. if (canUseIntersectionApi) { // 如果不兼容IntersectionObserver api,则恢复element默认ui行为
  43. const handler = (entries) => entries.map(entry => {
  44. const id = entry.target.attributes.id.value
  45. if (id.indexOf('cip-tabs-plus') > 0) {
  46. return {}
  47. }
  48. return { name: id.substring(4), isIntersecting: entry.isIntersecting }
  49. })
  50. const { entries } = useIntersectionObserver(`.${namespace}-tabs__item`, {
  51. multiple: true,
  52. scope: `#${id}`,
  53. handler,
  54. options: { threshold: 0.75 }
  55. })
  56. const hiddenEntries = ref([])
  57. watch(() => entries.value, (val) => {
  58. const hidden = val.filter(item => item.name && !item.isIntersecting).map(v => v.name)
  59. const show = val.filter(item => item.name && item.isIntersecting).map(v => v.name)
  60. hiddenEntries.value = Array.from(new Set([...hiddenEntries.value, ...hidden])).filter(name => !show.includes(name))
  61. hiddenTabs.value = tabPanes.value.filter(tab => hiddenEntries.value.includes(tab?.props?.name?.toString?.())).map(item => item?.props)
  62. })
  63. // 根据 ui进行样式调整
  64. onMounted(() => {
  65. nextTick(() => { // tabPane可能未挂载完成
  66. const cipTabsEl = document.querySelector(`#${id}`)
  67. const scroller = cipTabsEl.querySelector(`.${namespace}-tabs__nav-wrap`)
  68. canScroll.value = scroller.className.indexOf?.('is-scrollable') > -1 // 有这个类说明溢出需要更多展示
  69. if (canScroll.value) {
  70. const el = cipTabsEl.querySelector('.cip-tabs-plus__more')
  71. const width = scroller.offsetWidth
  72. // const tabsHeader = cipTabsEl.querySelector(`.${namespace}-tabs__header`)
  73. if (props.tabPosition === 'left') {
  74. el.style.left = `${width / 2 - 7}px`
  75. }
  76. if (props.tabPosition === 'right') {
  77. el.style.right = `${width / 2 - 7}px`
  78. }
  79. }
  80. })
  81. })
  82. }
  83. return () =>
  84. <div class={`cip-tabs-plus ${canUseIntersectionApi ? 'use-intersection' : ''}`} id={id}>
  85. <ElTabs
  86. {...props}
  87. onUpdate:modelValue={onUpdate}
  88. class={props.underline ? '' : 'no-underline'}
  89. style={{ height: props.height }}
  90. >
  91. {slots.default?.()}
  92. </ElTabs>
  93. {/* ...更多按钮 ↓↓↓ */}
  94. {
  95. canUseIntersectionApi && canScroll.value &&
  96. <div class={`cip-tabs-plus__more ${props.tabPosition}`} id={`${id}__more`}>
  97. <CipDropdown onCommand={onUpdate}>
  98. {{
  99. default: () => <ElIcon><MoreFilled/></ElIcon>,
  100. dropdown: () =>
  101. <ElDropdownMenu>
  102. {
  103. hiddenTabs.value.length && hiddenTabs.value.map((tab, index) =>
  104. <ElDropdownItem key={tab.name} command={tab.name} disabled={tab.disabled}>{tab.label ?? tab.name}</ElDropdownItem>
  105. )
  106. }
  107. </ElDropdownMenu>
  108. }}
  109. </CipDropdown>
  110. </div>
  111. }
  112. </div>
  113. }
  114. }
  115. Tabs.Tab = Tab
  116. export default Tabs