浏览代码

Merge branch 'next' of https://github.com/jdf2e/nutui into next

Drjnigfubo 3 年之前
父节点
当前提交
729b64efe1

+ 16 - 1
src/packages/__VUE/collapse/demo.vue

@@ -29,7 +29,6 @@
       </nut-collapse-item>
     </nut-collapse>
     <h2>自定义折叠图标</h2>
-
     <nut-collapse v-model:active="active3" :accordion="true" icon="arrow-right2" rotate="90">
       <nut-collapse-item :title="title1" :name="1">
         <template v-slot:sTitle> 文本测试 </template>
@@ -54,6 +53,16 @@
         使用 Teleport 新特性重构挂载类组件
       </nut-collapse-item>
     </nut-collapse>
+    <h2>设置固定内容(不折叠)</h2>
+    <nut-collapse v-model:active="active6" icon="down-arrow" :accordion="true">
+      <nut-collapse-item :title="title1" :name="1">
+        <template v-slot:extraRender>固定内容</template>
+        NutUI是一套拥有京东风格的轻量级的 Vue 组件库
+      </nut-collapse-item>
+      <nut-collapse-item :title="title2" :name="2">
+        在产品的功能、体验、易用性和灵活性等各个方面做了全面的升级!
+      </nut-collapse-item>
+    </nut-collapse>
   </div>
 </template>
 <script lang="ts">
@@ -68,6 +77,7 @@ export default createDemo({
       active3: 1,
       active4: 1,
       active5: 1,
+      active6: 1,
       title1: '标题1',
       title2: '标题2',
       title3: '标题3',
@@ -83,3 +93,8 @@ export default createDemo({
   }
 });
 </script>
+<style lang="scss">
+.nut-collapse-item .collapse-extraWrapper .collapse-extraRender {
+  color: red;
+}
+</style>

+ 36 - 0
src/packages/__VUE/collapse/doc.md

@@ -221,6 +221,41 @@ export default {
 </script>
 ```
 :::
+### 设置固定内容(不折叠)
+
+通过 slot:extraRender 设置内容
+:::demo
+
+```html
+<template>
+  <nut-collapse v-model:active="activeName" icon="down-arrow" :accordion="true">
+    <nut-collapse-item :title="title1" :name="1">
+      <template v-slot:extraRender>固定内容</template>
+      NutUI是一套拥有京东风格的轻量级的 Vue 组件库
+    </nut-collapse-item>
+    <nut-collapse-item :title="title2" :name="2">
+      在产品的功能、体验、易用性和灵活性等各个方面做了全面的升级!
+    </nut-collapse-item>
+  </nut-collapse>
+</template>
+<script>
+import { reactive, ref, toRefs } from 'vue';
+export default {
+  setup() {
+    const activeName = ref(1);
+    const title = reactive({
+      title1: '标题1',
+      title2: '标题2',
+    })
+    return {
+      activeName,
+      ...toRefs(title)
+    };
+  }
+}
+</script>
+```
+:::
 ## Collapse Prop
 
 | 字段 | 说明 | 类型 | 默认值
@@ -244,6 +279,7 @@ export default {
 | title | 标题栏左侧内容,支持插槽传入(props传入的优先级更高) | string | - |
 | sub-title | 标题栏副标题,支持插槽传入(props传入的优先级更高) | string | - |
 | disabled | 标题栏是否禁用 | boolean | false |
+| slot:extraRender | 设置标题下固定内容(不折叠) | - | ’‘ |
 
 
 ### Events

+ 12 - 2
src/packages/__VUE/collapseitem/index.scss

@@ -68,13 +68,15 @@
   // .extraRender {
   //   display: block;
   // }
-  .collapse-wrapper {
+  .collapse-wrapper,
+  .collapse-extraWrapper {
     display: block;
     position: relative;
     height: 0;
     overflow: hidden;
     transition: height 0.3s ease-in-out;
-    .collapse-content {
+    .collapse-content,
+    .collapse-extraRender {
       display: block;
       padding: $collapse-wrapper-content-padding;
       color: $collapse-wrapper-content-color;
@@ -86,6 +88,14 @@
       padding: $collapse-wrapper-empty-content-padding;
     }
   }
+  .collapse-extraWrapper {
+    height: auto;
+    .collapse-extraRender {
+      word-wrap: break-word;
+      word-break: break-all;
+      overflow: hidden;
+    }
+  }
   .open-style {
     will-change: height;
     height: auto;

+ 5 - 0
src/packages/__VUE/collapseitem/index.taro.vue

@@ -36,6 +36,11 @@
         :style="iconStyle"
       ></nut-icon>
     </view>
+    <view v-if="$slots.extraRender" class="collapse-extraWrapper">
+      <div class="collapse-extraRender">
+        <slot name="extraRender"></slot>
+      </div>
+    </view>
     <view
       :class="['collapse-wrapper', openExpanded ? 'open-style' : 'close-style']"
       ref="wrapperRef"

+ 5 - 0
src/packages/__VUE/collapseitem/index.vue

@@ -34,6 +34,11 @@
         :style="iconStyle"
       ></nut-icon>
     </view>
+    <view v-if="$slots.extraRender" class="collapse-extraWrapper">
+      <div class="collapse-extraRender">
+        <slot name="extraRender"></slot>
+      </div>
+    </view>
     <view class="collapse-wrapper" ref="wrapperRef">
       <view :class="['collapse-content', emptyContent]" ref="contentRef">
         <slot></slot>

+ 10 - 3
src/packages/__VUE/datepicker/__tests__/datepicker.spec.ts

@@ -2,6 +2,9 @@ import { config, mount } from '@vue/test-utils';
 import { nextTick, ref, toRefs, reactive, onMounted } from 'vue';
 import NutIcon from '../../icon/index.vue';
 import NutRange from '../../range/index.vue';
+import NutPicker from '../../picker/index.vue';
+import NutPopup from '../../popup/index.vue';
+import NutPickerColumn from '../../picker/Column.vue';
 import DatePicker from '../../datepicker/index.vue';
 
 function sleep(delay = 0): Promise<void> {
@@ -13,7 +16,10 @@ function sleep(delay = 0): Promise<void> {
 beforeAll(() => {
   config.global.components = {
     NutIcon,
-    NutRange
+    NutRange,
+    NutPicker,
+    NutPopup,
+    NutPickerColumn
   };
 });
 
@@ -25,6 +31,7 @@ test('Do not display Chinese', async () => {
   const wrapper = mount(DatePicker, {
     props: {
       modelValue: new Date(2020, 0, 1),
+      type: 'year-month',
       visible: true,
       isWrapTeleport: false,
       isShowChinese: false
@@ -36,10 +43,9 @@ test('Do not display Chinese', async () => {
   confirm.trigger('click');
   expect(wrapper.emitted().confirm[0]).toEqual([
     {
-      selectedValue: ['2020', '01', '01'],
+      selectedValue: ['2020', '01'],
       selectedOptions: [
         { text: '2020', value: '2020' },
-        { text: '01', value: '01' },
         { text: '01', value: '01' }
       ]
     }
@@ -49,6 +55,7 @@ test('Do not display Chinese', async () => {
 test('min date & max date', async () => {
   const wrapper = mount(DatePicker, {
     props: {
+      type: 'year-month',
       minDate: new Date(2020, 0, 1),
       maxDate: new Date(2022, 10, 1),
       visible: true,

+ 11 - 2
src/packages/__VUE/datepicker/index.vue

@@ -17,7 +17,7 @@
 <script lang="ts">
 import { toRefs, watch, computed, reactive, onBeforeMount } from 'vue';
 import type { PropType } from 'vue';
-import picker from '../picker/index.vue';
+import Picker from '../picker/index.vue';
 import { popupProps } from '../popup/index.vue';
 import { PickerOption } from '../picker/types';
 import { createComponent } from '@/packages/utils/create';
@@ -44,7 +44,7 @@ const zhCNType: {
 };
 export default create({
   components: {
-    [picker.name]: picker
+    [Picker.name]: Picker
   },
   props: {
     ...popupProps,
@@ -137,6 +137,15 @@ export default create({
             }
           }
         }
+      } else {
+        return {
+          [`${type}Year`]: year,
+          [`${type}Month`]: month,
+          [`${type}Day`]: day,
+          [`${type}Hour`]: hour,
+          [`${type}Minute`]: minute,
+          [`${type}Seconds`]: seconds
+        };
       }
 
       return {

+ 4 - 4
src/packages/__VUE/infiniteloading/index.vue

@@ -225,10 +225,10 @@ export default create({
       }
     };
 
-    // // 滚动监听对象
-    // const getParentElement = (el: HTMLElement) => {
-    //   return !!props.containerId ? document.querySelector(`#${props.containerId}`) : el && el.parentNode;
-    // };
+    // 滚动监听对象
+    const getParentElement = (el: HTMLElement) => {
+      return !!props.containerId ? document.querySelector(`#${props.containerId}`) : el && el.parentNode;
+    };
 
     onMounted(() => {
       const parentElement = getParentElement(state.scroller as HTMLElement) as Node & ParentNode;

+ 3 - 2
src/packages/__VUE/list/doc.md

@@ -70,8 +70,9 @@ export default {
 
 | 参数         | 说明                             | 类型   | 默认值           |
 |--------------|----------------------------------|--------|------------------|
-| height         | 列表项的高度               | Number | 50                |
-| listData         | 列表数据               | any[] | []                |
+| height         | 列表项的高度               | Number | `50`                |
+| list-data         | 列表数据               | any[] | `[]`                |
+| container-height `v3.1.19`        | 容器高度              | Number | `可视区高度`                |
 
 ### Slot
 

+ 6 - 3
src/packages/__VUE/list/index.taro.vue

@@ -2,7 +2,7 @@
   <scroll-view
     :class="classes"
     :scroll-y="true"
-    :style="{ height: screenHeight + 'px' }"
+    :style="{ height: containerHeight + 'px' }"
     scroll-top="0"
     @scroll="handleScrollEvent"
     ref="list"
@@ -31,6 +31,10 @@ export default create({
       default: () => {
         return [];
       }
+    },
+    containerHeight: {
+      type: [Number],
+      default: Taro.getSystemInfoSync().windowHeight || 667
     }
   },
   emits: ['scroll'],
@@ -38,14 +42,13 @@ export default create({
   setup(props, { emit }) {
     const list = ref(null) as Ref;
     const state = reactive({
-      screenHeight: Taro.getSystemInfoSync().windowHeight,
       startOffset: 0,
       start: 0,
       list: props.listData.slice()
     });
 
     const visibleCount = computed(() => {
-      return Math.ceil(state.screenHeight / props.height);
+      return Math.ceil(props.containerHeight / props.height);
     });
 
     const end = computed(() => {

+ 5 - 2
src/packages/__VUE/list/index.vue

@@ -23,6 +23,10 @@ export default create({
       default: () => {
         return [];
       }
+    },
+    containerHeight: {
+      type: [Number],
+      default: document.documentElement.clientHeight || document.body.clientHeight || 667
     }
   },
   emits: ['scroll'],
@@ -30,14 +34,13 @@ export default create({
   setup(props, { emit }) {
     const list = ref(null) as Ref;
     const state = reactive({
-      screenHeight: document.documentElement.clientHeight || document.body.clientHeight || 667,
       startOffset: 0,
       start: 0,
       list: props.listData.slice()
     });
 
     const visibleCount = computed(() => {
-      return Math.ceil(state.screenHeight / props.height);
+      return Math.ceil(props.containerHeight / props.height);
     });
 
     const end = computed(() => {

+ 5 - 5
src/packages/__VUE/notify/__test__/function.spec.ts

@@ -13,7 +13,7 @@ describe('function notify', () => {
       color: '#ad0000',
       background: '#ffe1e1'
     });
-    let textNotify = document.querySelector('.nut-notify') as HTMLElement;
+    const textNotify = document.querySelector('.nut-notify') as HTMLElement;
     expect(textNotify.innerHTML).toContain('基础用法');
     expect(textNotify.style.color).toEqual('rgb(173, 0, 0)');
     expect(textNotify.style.background).toEqual('rgb(255, 225, 225)');
@@ -24,7 +24,7 @@ describe('function notify', () => {
     NotifyFunction.primary('主要通知', {
       duration: 500
     });
-    let textNotify1 = document.querySelector('.nut-notify--primary') as HTMLElement;
+    const textNotify1 = document.querySelector('.nut-notify--primary') as HTMLElement;
     expect(textNotify1.innerHTML).toContain('主要通知');
     await sleep(500);
     expect(textNotify1.style.display).toEqual('');
@@ -33,7 +33,7 @@ describe('function notify', () => {
     NotifyFunction.success('成功通知', {
       duration: 500
     });
-    let textNotify1 = document.querySelector('.nut-notify--success') as HTMLElement;
+    const textNotify1 = document.querySelector('.nut-notify--success') as HTMLElement;
     expect(textNotify1.innerHTML).toContain('成功通知');
     await sleep(500);
     expect(textNotify1.style.display).toEqual('');
@@ -42,7 +42,7 @@ describe('function notify', () => {
     NotifyFunction.danger('危险通知', {
       duration: 500
     });
-    let textNotify1 = document.querySelector('.nut-notify--danger') as HTMLElement;
+    const textNotify1 = document.querySelector('.nut-notify--danger') as HTMLElement;
     expect(textNotify1.innerHTML).toContain('危险通知');
     await sleep(500);
     expect(textNotify1.style.display).toEqual('');
@@ -51,7 +51,7 @@ describe('function notify', () => {
     NotifyFunction.warn('警告通知', {
       duration: 500
     });
-    let textNotify1 = document.querySelector('.nut-notify--warning') as HTMLElement;
+    const textNotify1 = document.querySelector('.nut-notify--warning') as HTMLElement;
     expect(textNotify1.innerHTML).toContain('警告通知');
     await sleep(500);
     expect(textNotify1.style.display).toEqual('');

+ 12 - 4
src/packages/__VUE/notify/__test__/notify.spec.ts

@@ -1,15 +1,21 @@
 import { mount } from '@vue/test-utils';
 import Notify from '../index.vue';
+import { nextTick } from 'vue';
 
 describe('Notify', () => {
   test('base notify', () => {
-    const wrapper = mount(Notify);
-    const rate = wrapper.find('.nut-notify');
+    const wrapper = mount(Notify, {
+      props: {
+        isWrapTeleport: false
+      }
+    });
+    const rate = wrapper.find('.nut-popup').find('.nut-notify');
     expect(rate.exists()).toBe(true);
   });
   test('base notify message', async () => {
     const wrapper = mount(Notify, {
       props: {
+        isWrapTeleport: false,
         message: '测试文案'
       }
     });
@@ -18,6 +24,7 @@ describe('Notify', () => {
   test('should be displayed after setting the type', async () => {
     const wrapper = mount(Notify, {
       props: {
+        isWrapTeleport: false,
         type: 'warning'
       }
     });
@@ -28,17 +35,18 @@ describe('Notify', () => {
   test('should be displayed after setting the color and background', async () => {
     const wrapper = mount(Notify, {
       props: {
+        isWrapTeleport: false,
         color: 'rgb(173, 0, 0)',
         background: 'rgb(255, 225, 225)'
       }
     });
-    const notify = wrapper.find('.nut-notify');
+    const notify = wrapper.find('.nut-popup').find('.nut-notify');
     expect((notify.element as HTMLElement).style.color).toBe('rgb(173, 0, 0)');
     expect((notify.element as HTMLElement).style.background).toBe('rgb(255, 225, 225)');
   });
 
   test('should be displayed after setting the color and class-name', () => {
-    const wrapper = mount(Notify, { props: { 'class-name': 'xxx' } });
+    const wrapper = mount(Notify, { props: { isWrapTeleport: false, 'class-name': 'xxx' } });
     const rate = wrapper.findAll('.xxx');
     expect(rate.length).toBe(1);
   });

+ 28 - 12
src/packages/__VUE/notify/__test__/notify.ts

@@ -1,4 +1,4 @@
-import { createVNode, defineComponent, render, App } from 'vue';
+import { createVNode, defineComponent, render, h, onMounted } from 'vue';
 import Notify from '../index.vue';
 const defaultOptions = {
   type: 'base',
@@ -11,7 +11,7 @@ const defaultOptions = {
   onClosed: null,
   onClick: null,
   onOpened: null,
-  textTimer: null,
+  // textTimer: null,
   unmount: null
 };
 
@@ -67,15 +67,30 @@ const mountNotify = (opts: any) => {
   opts.id = _id;
   idsMap.push(opts.id);
   optsMap.push(opts);
-  const container = document.createElement('view');
-  container.id = opts.id;
-  const instance: any = createVNode(Notify, opts);
-  render(instance, container);
-  document.body.appendChild(container);
-  setTimeout(() => {
-    instance.showPopup = true;
-  }, 0);
-  return instance.component.ctx;
+  const root = document.createElement('view');
+  root.id = 'notify-' + opts.id;
+  const Wrapper = {
+    setup() {
+      // opts.onUpdate = (val: boolean) => {
+      //   console.log(val);
+      //   if (val == false) {
+      //     document.body.removeChild(root);
+      //   }
+      // };
+      opts.teleport = `#notify-${opts.id}`;
+      onMounted(() => {
+        setTimeout(() => {
+          document.body.removeChild(root);
+        }, opts.duration);
+      });
+      return () => {
+        return h(Notify, opts);
+      };
+    }
+  };
+  const instance: any = createVNode(Wrapper);
+  document.body.appendChild(root);
+  render(instance, root);
 };
 
 const errorMsg = (msg: string) => {
@@ -109,7 +124,8 @@ export const NotifyFunction = {
   hide() {
     clearNotify();
   },
-  install(app: App): void {
+  install(app: any): void {
+    app.use(Notify);
     app.config.globalProperties.$notify = NotifyFunction;
   }
 };

+ 1 - 1
src/packages/__VUE/notify/demo.vue

@@ -43,7 +43,7 @@ export default createDemo({
       });
     };
     const primaryNotify = (msg: string) => {
-      Notify.primary(msg, { duration: 10000 });
+      Notify.primary(msg, { duration: 1000 });
     };
     const successNotify = (msg: string) => {
       Notify.success(msg);

+ 5 - 1
src/packages/__VUE/notify/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <nut-popup v-model:visible="showPopup" :position="position" :overlay="false">
+  <nut-popup v-model:visible="showPopup" :position="position" :overlay="false" :isWrapTeleport="isWrapTeleport">
     <div
       :class="['nut-notify', `nut-notify--${type}`, className]"
       :style="{ color: color, background: background }"
@@ -48,6 +48,10 @@ export default create({
       type: String,
       default: 'top'
     },
+    isWrapTeleport: {
+      type: Boolean,
+      default: true
+    },
     onClose: Function,
     onClick: Function,
     unmount: Function

+ 62 - 22
src/packages/__VUE/swipe/demo.vue

@@ -1,54 +1,59 @@
 <template>
   <div class="demo full">
-    <h2>基础用法</h2>
+    <h2>{{ translate('basic') }}</h2>
     <nut-swipe>
-      <nut-cell round-radius="0" desc="左滑删除" />
+      <nut-cell round-radius="0" :desc="translate('leftDel')" />
       <template #right>
-        <nut-button shape="square" style="height: 100%" type="danger">删除</nut-button>
+        <nut-button shape="square" style="height: 100%" type="danger">{{ translate('delete') }}</nut-button>
       </template>
     </nut-swipe>
-    <h2>禁止滑动</h2>
+    <h2>{{ translate('disable') }}</h2>
     <nut-swipe disabled>
-      <nut-cell round-radius="0" desc="禁止滑动" />
+      <nut-cell round-radius="0" :desc="translate('disable')" />
       <template #right>
-        <nut-button shape="square" style="height: 100%" type="danger">删除</nut-button>
+        <nut-button shape="square" style="height: 100%" type="danger">{{ translate('delete') }}</nut-button>
       </template>
     </nut-swipe>
-    <h2>左右滑动</h2>
+    <h2>{{ translate('swipeLR') }}</h2>
     <nut-swipe>
       <template #left>
-        <nut-button shape="square" style="height: 100%" type="success">选择</nut-button>
+        <nut-button shape="square" style="height: 100%" type="success">{{ translate('select') }}</nut-button>
       </template>
-      <nut-cell round-radius="0" desc="左滑右滑都可以哦" />
+      <nut-cell round-radius="0" :desc="translate('swipeLRDesc')" />
       <template #right>
-        <nut-button shape="square" style="height: 100%" type="danger">删除</nut-button>
-        <nut-button shape="square" style="height: 100%" type="info">收藏</nut-button>
+        <nut-button shape="square" style="height: 100%" type="danger">{{ translate('delete') }}</nut-button>
+        <nut-button shape="square" style="height: 100%" type="info">{{ translate('collect') }}</nut-button>
       </template>
     </nut-swipe>
-    <h2>异步控制</h2>
+    <h2>{{ translate('async') }}</h2>
     <nut-swipe ref="refSwipe" @open="open" @close="close">
-      <nut-cell title="异步打开关闭">
+      <nut-cell :title="translate('asyncDesc')">
         <template v-slot:link>
-          <nut-switch v-model="checked" @change="changSwitch" active-text="开" inactive-text="关" />
+          <nut-switch
+            v-model="checked"
+            @change="changSwitch"
+            :active-text="translate('open')"
+            :inactive-text="translate('close')"
+          />
         </template>
       </nut-cell>
       <template #right>
-        <nut-button shape="square" style="height: 100%" type="danger">删除</nut-button>
+        <nut-button shape="square" style="height: 100%" type="danger">{{ translate('delete') }}</nut-button>
       </template>
     </nut-swipe>
-    <h2>自定义</h2>
+    <h2>{{ translate('custom') }}</h2>
     <nut-swipe>
       <template #left>
-        <nut-button shape="square" style="height: 100%" type="success">选择</nut-button>
+        <nut-button shape="square" style="height: 100%" type="success">{{ translate('select') }}</nut-button>
       </template>
-      <nut-cell title="商品描述">
+      <nut-cell :title="translate('desc')">
         <template v-slot:link>
           <nut-inputnumber v-model="number" />
         </template>
       </nut-cell>
       <template #right>
-        <nut-button shape="square" style="height: 100%" type="danger">删除</nut-button>
-        <nut-button shape="square" style="height: 100%" type="info">收藏</nut-button>
+        <nut-button shape="square" style="height: 100%" type="danger">{{ translate('delete') }}</nut-button>
+        <nut-button shape="square" style="height: 100%" type="info">{{ translate('collect') }}</nut-button>
       </template>
     </nut-swipe>
   </div>
@@ -57,7 +62,42 @@
 <script lang="ts">
 import { ref } from 'vue';
 import { createComponent } from '@/packages/utils/create';
-const { createDemo } = createComponent('swipe');
+const { createDemo, translate } = createComponent('swipe');
+import { useTranslate } from '@/sites/assets/util/useTranslate';
+useTranslate({
+  'zh-CN': {
+    basic: '基本用法',
+    leftDel: '左滑删除',
+    disable: '禁用滑动',
+    swipeLR: '左右滑动',
+    swipeLRDesc: '左滑右滑都可以哦',
+    async: '异步控制',
+    asyncDesc: '异步打开关闭',
+    open: '开',
+    close: '关',
+    custom: '自定义',
+    desc: '商品描述',
+    delete: '删除',
+    select: '选择',
+    collect: '收藏'
+  },
+  'en-US': {
+    basic: 'Basic Usage',
+    leftDel: 'Swipe left to delete',
+    disable: 'Disable swipe',
+    swipeLR: 'Swipe left and right',
+    swipeLRDesc: 'You can swipe left and right',
+    async: 'Async control swipe',
+    asyncDesc: 'Async on and off',
+    open: 'on',
+    close: 'off',
+    custom: 'Custom',
+    desc: 'product description',
+    delete: 'Delelte',
+    select: 'Select',
+    collect: 'Collect'
+  }
+});
 export default createDemo({
   props: {},
   setup() {
@@ -78,7 +118,7 @@ export default createDemo({
     const close = () => {
       checked.value = false;
     };
-    return { checked, number, changSwitch, refSwipe, open, close };
+    return { checked, number, changSwitch, refSwipe, open, close, translate };
   }
 });
 </script>

+ 184 - 0
src/packages/__VUE/swipe/doc.en-US.md

@@ -0,0 +1,184 @@
+#  Swipe
+
+### Intro
+
+Used for cell components that can slide left and right to display operation buttons.
+
+### Install
+
+```javascript
+import { createApp } from 'vue';
+//vue
+import { Swipe,Cell,Button } from '@nutui/nutui';
+//taro
+import { Swipe,Cell,Button } from '@nutui/nutui-taro';
+
+const app = createApp();
+app.use(Swipe);
+app.use(Cell);
+app.use(Button);
+```
+
+### Basic Usage
+
+:::demo
+```html
+<template>
+<nut-swipe>
+    <nut-cell round-radius="0" desc="Swipe left to delete" />
+    <template #right>
+        <nut-button shape="square" style="height:100%" type="danger">Delelte</nut-button>
+    </template>
+</nut-swipe>
+</template>
+```
+:::
+
+
+### Disable swipe
+
+
+:::demo
+```html
+<template>
+<nut-swipe disabled>
+    <nut-cell round-radius="0" desc="Disable swipe" />
+    <template #right>
+        <nut-button shape="square" style="height:100%" type="danger">Delelte</nut-button>
+    </template>
+</nut-swipe>
+</template>
+```
+:::
+
+### Swipe left and right
+
+
+:::demo
+```html
+<template>
+<nut-swipe>
+    <template #left>
+        <nut-button shape="square" style="height:100%" type="success">Select</nut-button>
+    </template>
+    <nut-cell round-radius="0" desc="You can swipe left and right" />
+    <template #right>
+        <nut-button shape="square" style="height:100%" type="danger">Delelte</nut-button>
+        <nut-button shape="square" style="height:100%" type="info">Collect</nut-button>
+    </template>
+</nut-swipe>
+</template>
+```
+:::
+
+### Async control swipe
+
+Need to introduce the `switch` component separately
+
+:::demo
+```html
+<template>
+<nut-swipe ref="refSwipe" @open="open" @close="close">
+    <nut-cell title="Async on and off">
+    <template v-slot:link>
+        <nut-switch v-model="checked" @change="changSwitch" active-text="on" inactive-text="off" />
+    </template>
+    </nut-cell>
+    <template #right>
+        <nut-button shape="square" style="height:100%" type="danger">Delelte</nut-button>
+    </template>
+</nut-swipe>
+</template>
+<script lang="ts">
+import { ref } from 'vue';
+export default {
+    setup() {
+        const refSwipe = ref<HTMLElement>();
+        const checked = ref(false);
+        const changSwitch = (value: boolean) => {
+            if (value) {
+                refSwipe.value?.open('left');
+            } else {
+                refSwipe.value?.close();
+            }
+        };
+         const open = (obj: any) => {
+            console.log(obj);
+            checked.value = true;
+        };
+        const close = () => {
+            checked.value = false;
+        };
+        return { checked, changSwitch, refSwipe, open, close };
+    }
+}
+</script>
+```
+:::
+
+### Custom
+
+Need to introduce the `inputnumber` component separately
+
+:::demo
+```html
+<template>
+<nut-swipe>
+    <template #left>
+        <nut-button shape="square" style="height:100%" type="success">Select</nut-button>
+    </template>
+    <nut-cell title="product description">
+    <template v-slot:link>
+        <nut-inputnumber v-model="number" />
+    </template>
+    </nut-cell>
+    <template #right>
+        <nut-button shape="square" style="height:100%" type="danger">Delelte</nut-button>
+        <nut-button shape="square" style="height:100%" type="info">Collect</nut-button>
+    </template>
+</nut-swipe>
+</template>
+<script lang="ts">
+import { ref } from 'vue';
+export default {
+    setup() {
+        const number = ref(0);
+        return { number };
+    }
+}
+</script>
+```
+:::
+
+
+### Props
+
+| Attribute                   | Description               | Type    | Default |
+|-----------------------------|---------------------------|---------|---------|
+| name                        | identifies                | String  | -       |
+| disabled                    | Whether to disabled swipe | String  | false   |
+| touch-move-prevent-default  | Whether to stop touchmove event preventdefault       | boolean | false   |
+| touch-move-stop-propagation | Whether to stop touchmove event propagation      | boolean | false   |
+### Events
+
+| Event | Description                  | Arguments              |
+|-------|------------------------------|------------------------|
+| open  | Emitted when Swipe is opened | {type:'left or right'} |
+| close | Emitted when Swipe is closed | {type:'left or right'} |
+    
+
+### Slots
+| Name    | Description    |
+|---------|----------------|
+| left    | Custom left    |
+| default | Custom default |
+| right   | Custom right   |
+
+### Methods
+
+Use [ref](https://vuejs.org/guide/essentials/template-refs.html) to get Swipe instance and call instance methods.
+
+| Name  | Description | Arguments     |
+|-------|-------------|---------------|
+| open  | open swipe  | left or right |
+| close | close swipe |               |

+ 2 - 2
src/packages/__VUE/swipe/doc.md

@@ -2,7 +2,7 @@
 
 ### 介绍
 
-常用于单元格左滑删除等手势操作
+常用于单元格左滑删除等手势操作
 
 ### 安装
 
@@ -177,7 +177,7 @@ export default {
 | right   | 右侧滑动内容 |
 
 ### 方法
-通过 ref 可以获取到 Swipe 实例并调用实例方法。
+通过 [ref](https://vuejs.org/guide/essentials/template-refs.html) 可以获取到 Swipe 实例并调用实例方法。
 
 | 方法名 | 说明             | 参数          |
 |--------|------------------|---------------|

+ 1 - 1
src/packages/__VUE/tabs/__tests__/index.spec.ts

@@ -23,7 +23,7 @@ test('base Tabs', () => {
 test('base tabs props', async () => {
   const wrapper = mount(Tabs, {
     props: {
-      'v-model': 0,
+      modelValue: '0',
       background: '#f5f5f5',
       color: '#f5f5f5',
       direction: 'horizontal',

+ 3 - 1
src/packages/__VUE/tabs/common.ts

@@ -82,7 +82,9 @@ export const component = {
     const currentIndex = ref((props.modelValue as number) || 0);
     const findTabsIndex = (value: string | number) => {
       let index = titles.value.findIndex((item) => item.paneKey == value);
-      if (index == -1) {
+      if (titles.value.length == 0) {
+        console.error('[NutUI] <Tabs> 当前未找到 TabPane 组件元素 , 请检查 .');
+      } else if (index == -1) {
         console.error('[NutUI] <Tabs> 请检查 v-model 值是否为 paneKey ,如 paneKey 未设置,请采用下标控制 .');
       } else {
         currentIndex.value = index;

+ 11 - 0
src/sites/mobile-taro/vue/src/exhibition/pages/collapse/index.vue

@@ -46,6 +46,16 @@
         快看漫画与全球潮玩集合店X11达成战略合作
       </nut-collapse-item>
     </nut-collapse>
+    <h2>设置固定内容(不折叠部分)</h2>
+    <nut-collapse v-model:active="active6" icon="down-arrow" :accordion="true">
+      <nut-collapse-item :title="title1" :name="1">
+        <template v-slot:extraRender>固定内容</template>
+        NutUI是一套拥有京东风格的轻量级的 Vue 组件库
+      </nut-collapse-item>
+      <nut-collapse-item :title="title2" :name="2">
+        在产品的功能、体验、易用性和灵活性等各个方面做了全面的升级!
+      </nut-collapse-item>
+    </nut-collapse>
   </div>
 </template>
 <script lang="ts">
@@ -58,6 +68,7 @@ export default {
       active3: 1,
       active4: 1,
       active5: 1,
+      active6: 1,
       title1: '标题1',
       title2: '标题2',
       title3: '标题3',