common.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import { pxCheck } from '@/packages/utils/pxCheck';
  2. import { onMounted, provide, VNode, ref, Ref, computed, onActivated, watch } from 'vue';
  3. export class Title {
  4. title: string = '';
  5. titleSlot?: VNode[];
  6. paneKey: string = '';
  7. disabled: boolean = false;
  8. constructor() {}
  9. }
  10. export type TabsSize = 'large' | 'normal' | 'small';
  11. export const component = {
  12. props: {
  13. modelValue: {
  14. type: [String, Number],
  15. default: 0
  16. },
  17. color: {
  18. type: String,
  19. default: ''
  20. },
  21. direction: {
  22. type: String,
  23. default: 'horizontal' //vertical
  24. },
  25. size: {
  26. type: String as import('vue').PropType<TabsSize>,
  27. default: 'normal'
  28. },
  29. type: {
  30. type: String,
  31. default: 'line' //card、line、smile
  32. },
  33. titleScroll: {
  34. type: Boolean,
  35. default: false
  36. },
  37. ellipsis: {
  38. type: Boolean,
  39. default: true
  40. },
  41. autoHeight: {
  42. type: Boolean,
  43. default: false
  44. },
  45. background: {
  46. type: String,
  47. default: ''
  48. },
  49. animatedTime: {
  50. type: [Number, String],
  51. default: 300
  52. },
  53. titleGutter: {
  54. type: [Number, String],
  55. default: 0
  56. }
  57. },
  58. components: {},
  59. emits: ['update:modelValue', 'click', 'change'],
  60. setup(props: any, { emit, slots }: any) {
  61. provide('activeKey', { activeKey: computed(() => props.modelValue) });
  62. provide('autoHeight', { autoHeight: computed(() => props.autoHeight) });
  63. const titles: Ref<Title[]> = ref([]);
  64. const renderTitles = (vnodes: VNode[]) => {
  65. vnodes.forEach((vnode: VNode, index: number) => {
  66. let type = vnode.type;
  67. type = (type as any).name || type;
  68. if (type == 'nut-tabpane') {
  69. let title = new Title();
  70. if (vnode.props?.title || vnode.props?.['pane-key'] || vnode.props?.['paneKey']) {
  71. title.title = vnode.props?.title;
  72. title.paneKey = vnode.props?.['pane-key'] || vnode.props?.['paneKey'] || index;
  73. title.disabled = vnode.props?.disabled;
  74. } else {
  75. // title.titleSlot = vnode.children?.title() as VNode[];
  76. }
  77. titles.value.push(title);
  78. } else {
  79. if (vnode.children == ' ') {
  80. return;
  81. }
  82. renderTitles(vnode.children as VNode[]);
  83. }
  84. });
  85. };
  86. const currentIndex = ref((props.modelValue as number) || 0);
  87. const findTabsIndex = (value: string | number) => {
  88. let index = titles.value.findIndex((item) => item.paneKey == value);
  89. if (titles.value.length == 0) {
  90. console.error('[NutUI] <Tabs> 当前未找到 TabPane 组件元素 , 请检查 .');
  91. } else if (index == -1) {
  92. console.error('[NutUI] <Tabs> 请检查 v-model 值是否为 paneKey ,如 paneKey 未设置,请采用下标控制 .');
  93. } else {
  94. currentIndex.value = index;
  95. }
  96. };
  97. const init = (vnodes: VNode[] = slots.default?.()) => {
  98. titles.value = [];
  99. if (vnodes && vnodes.length) {
  100. renderTitles(vnodes);
  101. }
  102. findTabsIndex(props.modelValue);
  103. };
  104. watch(
  105. () => slots.default?.(),
  106. (vnodes: VNode[]) => {
  107. init(vnodes);
  108. }
  109. );
  110. watch(
  111. () => props.modelValue,
  112. (value: string | number) => {
  113. findTabsIndex(value);
  114. }
  115. );
  116. onMounted(init);
  117. onActivated(init);
  118. const contentStyle = computed(() => {
  119. return {
  120. transform:
  121. props.direction == 'horizontal'
  122. ? `translate3d(-${currentIndex.value * 100}%, 0, 0)`
  123. : `translate3d( 0,-${currentIndex.value * 100}%, 0)`,
  124. transitionDuration: `${props.animatedTime}ms`
  125. };
  126. });
  127. const tabsNavStyle = computed(() => {
  128. return {
  129. background: props.background
  130. };
  131. });
  132. const tabsActiveStyle = computed(() => {
  133. return {
  134. color: props.type == 'smile' ? props.color : '',
  135. background: props.type == 'line' ? props.color : ''
  136. };
  137. });
  138. const titleStyle = computed(() => {
  139. return {
  140. marginLeft: pxCheck(props.titleGutter),
  141. marginRight: pxCheck(props.titleGutter)
  142. };
  143. });
  144. const methods = {
  145. tabChange: (item: Title, index: number) => {
  146. emit('click', item);
  147. if (item.disabled) {
  148. return;
  149. }
  150. currentIndex.value = index;
  151. emit('update:modelValue', item.paneKey);
  152. emit('change', item);
  153. }
  154. };
  155. return {
  156. titles,
  157. contentStyle,
  158. tabsNavStyle,
  159. titleStyle,
  160. tabsActiveStyle,
  161. ...methods
  162. };
  163. }
  164. };