Browse Source

fix(tabs): h5 add sticky props (#1707)

Drjingfubo 3 years ago
parent
commit
0d096a4fc8

+ 28 - 2
src/packages/__VUE/tabs/common.ts

@@ -1,5 +1,6 @@
 import { pxCheck } from '@/packages/utils/pxCheck';
 import { pxCheck } from '@/packages/utils/pxCheck';
 import { TypeOfFun } from '@/packages/utils/util';
 import { TypeOfFun } from '@/packages/utils/util';
+import { useRect } from '@/packages/utils/useRect';
 import { onMounted, provide, VNode, ref, Ref, computed, onActivated, watch } from 'vue';
 import { onMounted, provide, VNode, ref, Ref, computed, onActivated, watch } from 'vue';
 export class Title {
 export class Title {
   title: string = '';
   title: string = '';
@@ -54,6 +55,14 @@ export const component = {
     titleGutter: {
     titleGutter: {
       type: [Number, String],
       type: [Number, String],
       default: 0
       default: 0
+    },
+    sticky: {
+      type: Boolean,
+      default: false
+    },
+    top: {
+      type: Number,
+      default: 0
     }
     }
   },
   },
 
 
@@ -61,6 +70,8 @@ export const component = {
   emits: ['update:modelValue', 'click', 'change'],
   emits: ['update:modelValue', 'click', 'change'],
 
 
   setup(props: any, { emit, slots }: any) {
   setup(props: any, { emit, slots }: any) {
+    const container = ref(null);
+    let stickyFixed: boolean;
     provide('activeKey', { activeKey: computed(() => props.modelValue) });
     provide('activeKey', { activeKey: computed(() => props.modelValue) });
     provide('autoHeight', { autoHeight: computed(() => props.autoHeight) });
     provide('autoHeight', { autoHeight: computed(() => props.autoHeight) });
     const titles: Ref<Title[]> = ref([]);
     const titles: Ref<Title[]> = ref([]);
@@ -112,17 +123,31 @@ export const component = {
       }
       }
       findTabsIndex(props.modelValue);
       findTabsIndex(props.modelValue);
     };
     };
+    const onStickyScroll = (params: { top: number; fixed: boolean }) => {
+      stickyFixed = params.fixed;
+    };
+
     watch(
     watch(
       () => slots.default?.(),
       () => slots.default?.(),
       (vnodes: VNode[]) => {
       (vnodes: VNode[]) => {
         init(vnodes);
         init(vnodes);
       }
       }
     );
     );
-
+    const getScrollTopRoot = () => {
+      return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
+    };
     watch(
     watch(
       () => props.modelValue,
       () => props.modelValue,
       (value: string | number) => {
       (value: string | number) => {
         findTabsIndex(value);
         findTabsIndex(value);
+        if (stickyFixed) {
+          let top = useRect(container.value!).top + getScrollTopRoot();
+          let value = Math.ceil(top - props.top);
+          window.scrollTo({
+            top: value,
+            behavior: 'smooth'
+          });
+        }
       }
       }
     );
     );
     onMounted(init);
     onMounted(init);
@@ -164,13 +189,14 @@ export const component = {
         emit('change', item);
         emit('change', item);
       }
       }
     };
     };
-
     return {
     return {
       titles,
       titles,
       contentStyle,
       contentStyle,
       tabsNavStyle,
       tabsNavStyle,
       titleStyle,
       titleStyle,
       tabsActiveStyle,
       tabsActiveStyle,
+      container,
+      onStickyScroll,
       ...methods
       ...methods
     };
     };
   }
   }

+ 2 - 0
src/packages/__VUE/tabs/doc.en-US.md

@@ -348,6 +348,8 @@ export default {
 | title-gutter         | Label gap                                                                                                                          | number,string | 0          |
 | title-gutter         | Label gap                                                                                                                          | number,string | 0          |
 | size`v3.1.13`        | Tab bar font size optional value  `large` `normal` `small`                                                                         | string        | normal     |
 | size`v3.1.13`        | Tab bar font size optional value  `large` `normal` `small`                                                                         | string        | normal     |
 | auto-height`v3.1.21` | Automatic height. When set to `true`, `nut-tabs` and `nut-tabs__content` will change with the height of the current `nut-tabpane`. | boolean       | false      |
 | auto-height`v3.1.21` | Automatic height. When set to `true`, `nut-tabs` and `nut-tabs__content` will change with the height of the current `nut-tabpane`. | boolean       | false      |
+| sticky`v3.1.22` `applet not supported` | Whether to use sticky mode| boolean       | false      |
+| top`v3.1.22` `applet not supported` | Sticky offset top | number       | 0      |
 
 
 ## Tabs Slots
 ## Tabs Slots
 
 

+ 2 - 0
src/packages/__VUE/tabs/doc.md

@@ -348,6 +348,8 @@ export default {
 | title-gutter         | 标签间隙                                                                                          | number,string | 0          |
 | title-gutter         | 标签间隙                                                                                          | number,string | 0          |
 | size`v3.1.13`        | 标签栏字体尺寸大小 可选值  large normal small                                                     | string        | normal     |
 | size`v3.1.13`        | 标签栏字体尺寸大小 可选值  large normal small                                                     | string        | normal     |
 | auto-height`v3.1.21` | 自动高度。设置为 true 时,nut-tabs 和 nut-tabs__content 会随着当前 nut-tabpane 的高度而发生变化。 | boolean       | false      |
 | auto-height`v3.1.21` | 自动高度。设置为 true 时,nut-tabs 和 nut-tabs__content 会随着当前 nut-tabpane 的高度而发生变化。 | boolean       | false      |
+| sticky`v3.1.22` `小程序不支持` | 是否使用粘性布局 | boolean       | false      |
+| top`v3.1.22` `小程序不支持` | 粘性布局下的吸顶距离 | number       | 0      |
 
 
 ## Tabs Slots
 ## Tabs Slots
 
 

+ 36 - 15
src/packages/__VUE/tabs/template.html

@@ -1,22 +1,43 @@
-<view class="nut-tabs" :class="[direction]">
-    <view class="nut-tabs__titles" :class="{ [type]: type, scrollable: titleScroll, [size]: size }"
+<view class="nut-tabs" :class="[direction]" ref="container" id="container">
+  <template v-if="sticky">
+    <nut-sticky :top="top" :container="container" @scroll="onStickyScroll">
+      <view class="nut-tabs__titles" :class="{ [type]: type, scrollable: titleScroll, [size]: size }"
         :style="tabsNavStyle">
         :style="tabsNavStyle">
         <slot v-if="$slots.titles" name="titles"></slot>
         <slot v-if="$slots.titles" name="titles"></slot>
         <template v-else>
         <template v-else>
-            <view class="nut-tabs__titles-item" :style="titleStyle" @click="tabChange(item, index)"
-                :class="{ active: item.paneKey == modelValue, disabled: item.disabled }" v-for="(item, index) in titles"
-                :key="item.paneKey">
-                <view class="nut-tabs__titles-item__line" :style="tabsActiveStyle" v-if="type == 'line'"></view>
-                <view class="nut-tabs__titles-item__smile" :style="tabsActiveStyle" v-if="type == 'smile'">
-                    <nut-icon :color="color" name="joy-smile" />
-                </view>
-                <view class="nut-tabs__titles-item__text"
-                    :class="{ ellipsis: ellipsis }">{{ item.title }}
-                </view>
+          <view class="nut-tabs__titles-item" :style="titleStyle" @click="tabChange(item, index)"
+            :class="{ active: item.paneKey == modelValue, disabled: item.disabled }" v-for="(item, index) in titles"
+            :key="item.paneKey">
+            <view class="nut-tabs__titles-item__line" :style="tabsActiveStyle" v-if="type == 'line'"></view>
+            <view class="nut-tabs__titles-item__smile" :style="tabsActiveStyle" v-if="type == 'smile'">
+              <nut-icon :color="color" name="joy-smile" />
+            </view>
+            <view class="nut-tabs__titles-item__text" :class="{ ellipsis: ellipsis }">{{ item.title }}
             </view>
             </view>
+          </view>
         </template>
         </template>
+      </view>
+    </nut-sticky>
+  </template>
+  <template v-else>
+    <view class="nut-tabs__titles" :class="{ [type]: type, scrollable: titleScroll, [size]: size }"
+      :style="tabsNavStyle">
+      <slot v-if="$slots.titles" name="titles"></slot>
+      <template v-else>
+        <view class="nut-tabs__titles-item" :style="titleStyle" @click="tabChange(item, index)"
+          :class="{ active: item.paneKey == modelValue, disabled: item.disabled }" v-for="(item, index) in titles"
+          :key="item.paneKey">
+          <view class="nut-tabs__titles-item__line" :style="tabsActiveStyle" v-if="type == 'line'"></view>
+          <view class="nut-tabs__titles-item__smile" :style="tabsActiveStyle" v-if="type == 'smile'">
+            <nut-icon :color="color" name="joy-smile" />
+          </view>
+          <view class="nut-tabs__titles-item__text" :class="{ ellipsis: ellipsis }">{{ item.title }}
+          </view>
+        </view>
+      </template>
     </view>
     </view>
-    <view class="nut-tabs__content" :style="contentStyle">
-        <slot name="default"></slot>
-    </view>
+  </template>
+  <view class="nut-tabs__content" :style="contentStyle">
+    <slot name="default"></slot>
+  </view>
 </view>
 </view>