common.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. import { pxCheck } from '@/packages/utils/pxCheck';
  2. import { onMounted, provide, VNode, ref, Ref, computed, onActivated, watch, PropType } 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 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. background: {
  42. type: String,
  43. default: ''
  44. },
  45. animatedTime: {
  46. type: [Number, String],
  47. default: 300
  48. },
  49. titleGutter: {
  50. type: [Number, String],
  51. default: 0
  52. }
  53. },
  54. components: {},
  55. emits: ['update:modelValue', 'click', 'change'],
  56. setup(props: any, { emit, slots }: any) {
  57. provide('activeKey', { activeKey: computed(() => props.modelValue) });
  58. const titles: Ref<Title[]> = ref([]);
  59. const renderTitles = (vnodes: VNode[]) => {
  60. vnodes.forEach((vnode: VNode, index: number) => {
  61. let type = vnode.type;
  62. type = (type as any).name || type;
  63. if (type == 'nut-tabpane') {
  64. let title = new Title();
  65. if (vnode.props?.title || vnode.props?.['pane-key']) {
  66. title.title = vnode.props?.title;
  67. title.paneKey = vnode.props?.['pane-key'] || index;
  68. title.disabled = vnode.props?.disabled;
  69. } else {
  70. // title.titleSlot = vnode.children?.title() as VNode[];
  71. }
  72. titles.value.push(title);
  73. } else {
  74. renderTitles(vnode.children as VNode[]);
  75. }
  76. });
  77. };
  78. const currentIndex = ref((props.modelValue as number) || 0);
  79. const findTabsIndex = (value: string | number) => {
  80. let index = titles.value.findIndex((item) => item.paneKey == value);
  81. if (index == -1) {
  82. console.error('[NutUI] <Tabs> 请检查 v-model 值是否为 paneKey ,如 paneKey 未设置,请采用下标控制 .');
  83. } else {
  84. currentIndex.value = index;
  85. }
  86. };
  87. const init = (vnodes: VNode[] = slots.default?.()) => {
  88. titles.value = [];
  89. if (vnodes && vnodes.length) {
  90. renderTitles(vnodes);
  91. }
  92. findTabsIndex(props.modelValue);
  93. };
  94. watch(
  95. () => slots.default?.(),
  96. (vnodes: VNode[]) => {
  97. init(vnodes);
  98. }
  99. );
  100. watch(
  101. () => props.modelValue,
  102. (value: string | number) => {
  103. findTabsIndex(value);
  104. }
  105. );
  106. onMounted(init);
  107. onActivated(init);
  108. const contentStyle = computed(() => {
  109. return {
  110. transform:
  111. props.direction == 'horizontal'
  112. ? `translate3d(-${currentIndex.value * 100}%, 0, 0)`
  113. : `translate3d( 0,-${currentIndex.value * 100}%, 0)`,
  114. transitionDuration: `${props.animatedTime}ms`
  115. };
  116. });
  117. const tabsNavStyle = computed(() => {
  118. return {
  119. background: props.background
  120. };
  121. });
  122. const tabsActiveStyle = computed(() => {
  123. return {
  124. color: props.type == 'smile' ? props.color : '',
  125. background: props.type == 'line' ? props.color : ''
  126. };
  127. });
  128. const titleStyle = computed(() => {
  129. return {
  130. marginLeft: pxCheck(props.titleGutter),
  131. marginRight: pxCheck(props.titleGutter)
  132. };
  133. });
  134. const methods = {
  135. tabChange: (item: Title, index: number) => {
  136. emit('click', item);
  137. if (item.disabled) {
  138. return;
  139. }
  140. currentIndex.value = index;
  141. emit('update:modelValue', item.paneKey);
  142. emit('change', item);
  143. }
  144. };
  145. return {
  146. titles,
  147. contentStyle,
  148. tabsNavStyle,
  149. titleStyle,
  150. tabsActiveStyle,
  151. ...methods
  152. };
  153. }
  154. };