Browse Source

feat: 添加picker组件

yangkaixuan 4 years ago
parent
commit
9073885c73

+ 57 - 95
src/packages/picker/Column.vue

@@ -8,32 +8,31 @@
     @touchcancel="onTouchEnd"
     @transitionend="stopMomentum"
   >
-    <view class="nut-picker__hairline" :style="{ top: ` ${top}px` }"></view>
-    <view
-      class="nut-picker__mask"
-      :style="{ backgroundSize: `100% ${top}px` }"
-    ></view>
     <view class="nut-picker__wrapper" ref="wrapper" :style="wrapperStyle">
       <view
         class="nut-picker__item"
         :key="index"
-        v-for="(item, index) in columns"
-        >{{ item }}</view
+        v-for="(item, index) in state.options"
+        >{{ dataType === 'cascade' ? item.text : item }}</view
       >
     </view>
   </view>
 </template>
 <script lang="ts">
-import { toRefs, reactive, ref, watch, computed } from 'vue';
+import { reactive, ref, watch, computed } from 'vue';
 import { createComponent } from '@/utils/create';
-import { useTouch } from './use-touch';
+import { useTouch } from '@/utils/useTouch';
+import { commonProps } from './commonProps';
 const MOMENTUM_LIMIT_DISTANCE = 15;
 const MOMENTUM_LIMIT_TIME = 300;
 const DEFAULT_DURATION = 200;
-const { componentName, create } = createComponent('picker');
+const { create } = createComponent('picker');
 function range(num: number, min: number, max: number): number {
   return Math.min(Math.max(num, min), max);
 }
+function stopPropagation(event: Event) {
+  event.stopPropagation();
+}
 function preventDefault(event: Event, isStopPropagation?: boolean) {
   /* istanbul ignore else */
   if (typeof event.cancelable !== 'boolean' || event.cancelable) {
@@ -45,9 +44,6 @@ function preventDefault(event: Event, isStopPropagation?: boolean) {
   }
 }
 
-function stopPropagation(event: Event) {
-  event.stopPropagation();
-}
 function getElementTranslateY(element) {
   const style = window.getComputedStyle(element);
   const transform = style.transform || style.webkitTransform;
@@ -61,95 +57,51 @@ export function isObject(val: unknown): val is Record<any, any> {
 function isOptionDisabled(option) {
   return isObject(option) && option.disabled;
 }
+
 export default create({
   props: {
-    show: {
-      type: Boolean,
-      default: false
-    },
-    readonly: {
-      type: Boolean,
-      default: false
-    },
-    txt: {
-      type: String,
-      default: ''
-    },
-    visibleItemCount: {
-      type: [Number],
-      default: 7
-    },
-    defaultIndex: {
-      type: [Number, String],
-      default: 0
-    },
-    itemHeight: {
-      type: [Number],
-      default: 35
-    },
-    initialOptions: {
-      type: Array,
-      default: () => [
-        1,
-        2,
-        3,
-        4,
-        5,
-        6,
-        7,
-        8,
-        9,
-        11,
-        22,
-        33,
-        44,
-        55,
-        66,
-        77,
-        8,
-        9
-      ]
-    }
+    dataType: String,
+    ...commonProps
   },
   components: {},
-  emits: ['click', 'close'],
-
+  emits: ['click', 'change'],
   setup(props, { emit }) {
     let moving;
     let startOffset, touchStartTime, momentumOffset, transitionEndTrigger;
-
-    const _show = ref(false);
-
     const state = reactive({
       index: props.defaultIndex,
       offset: 0,
       duration: 0,
-      options: props.initialOptions
+      options: props.listData
     });
-    const baseOffset = () =>
-      (props.itemHeight * (props.visibleItemCount - 1)) / 2;
-    const count = () => state.options.length;
-    const momentum = (distance, duration) => {
-      const speed = Math.abs(distance / duration);
-
-      distance = state.offset + (speed / 0.003) * (distance < 0 ? -1 : 1);
-
-      const index = getIndexByOffset(distance);
-
-      setIndex(index, true);
-    };
     watch(
-      () => props.show,
+      () => props.listData,
       val => {
-        _show.value = val;
+        if (val) {
+          state.options = val;
+        }
       }
     );
+
+    const wrapper = ref();
+    const touch = useTouch();
+    const count = () => state.options.length;
+    const _show = ref(false);
+    const getIndexByOffset = offset =>
+      range(Math.round(-offset / props.itemHeight), 0, count() - 1);
+
+    const baseOffset = () =>
+      (props.itemHeight * (props.visibleItemCount - 1)) / 2;
+
     const stopMomentum = () => {
       moving = false;
       state.duration = 0;
+      if (transitionEndTrigger) {
+        transitionEndTrigger();
+        transitionEndTrigger = null;
+      }
     };
-    const wrapper = ref();
-    const touch = useTouch();
+
     const adjustIndex = index => {
       index = range(index, 0, count());
 
@@ -160,7 +112,8 @@ export default create({
         if (!isOptionDisabled(state.options[i])) return i;
       }
     };
-    const setIndex = (index, emitChange) => {
+
+    const setIndex = (index, emitChange = false) => {
       index = adjustIndex(index) || 0;
 
       const offset = -index * props.itemHeight;
@@ -169,12 +122,11 @@ export default create({
           state.index = index;
 
           if (emitChange) {
-            // emit('change', index);
+            emit('change', index);
           }
         }
       };
 
-      // trigger the change event after transitionend when moving
       if (moving && offset !== state.offset) {
         transitionEndTrigger = trigger;
       } else {
@@ -183,8 +135,22 @@ export default create({
 
       state.offset = offset;
     };
-    const getIndexByOffset = offset =>
-      range(Math.round(-offset / props.itemHeight), 0, count() - 1);
+    watch(
+      () => props.defaultIndex,
+      val => {
+        setIndex(val);
+      }
+    );
+    setIndex(props.defaultIndex);
+    const momentum = (distance, duration) => {
+      const speed = Math.abs(distance / duration);
+
+      distance = state.offset + (speed / 0.003) * (distance < 0 ? -1 : 1);
+
+      const index = getIndexByOffset(distance);
+
+      setIndex(index, true);
+    };
     const onTouchStart = event => {
       if (props.readonly) {
         return;
@@ -213,7 +179,7 @@ export default create({
 
       if (touch.isVertical()) {
         moving = true;
-        // preventDefault(event, true);
+        preventDefault(event, true);
       }
 
       const moveOffset = startOffset + touch.deltaY.value;
@@ -254,8 +220,8 @@ export default create({
       transitionDuration: `${state.duration}ms`,
       transitionProperty: state.duration ? 'all' : 'none'
     }));
+
     return {
-      show: _show,
       wrapper,
       onTouchStart,
       onTouchMove,
@@ -263,12 +229,8 @@ export default create({
       wrapperStyle,
       state,
       stopMomentum,
-      columns: props.initialOptions,
-      top: (Number(props.visibleItemCount - 1) / 2) * props.itemHeight,
-      height: Number(props.visibleItemCount) * props.itemHeight,
-      close: () => {
-        emit('close');
-      }
+      columns: state.options,
+      height: Number(props.visibleItemCount) * props.itemHeight
     };
   }
 });

+ 22 - 0
src/packages/picker/commonProps.ts

@@ -0,0 +1,22 @@
+export const commonProps = {
+  listData: {
+    type: Array,
+    default: []
+  },
+  readonly: {
+    type: Boolean,
+    default: false
+  },
+  visibleItemCount: {
+    type: [Number],
+    default: 7
+  },
+  defaultIndex: {
+    type: [Number, String],
+    default: 0
+  },
+  itemHeight: {
+    type: [Number],
+    default: 35
+  }
+};

+ 105 - 15
src/packages/picker/demo.vue

@@ -1,14 +1,32 @@
 <template>
   <div class="demo">
     <h2>基础用法</h2>
-    <nut-cell title="请选择城市" desc="北京" @click="sss"></nut-cell>
+    <nut-cell title="请选择城市" :desc="desc" @click="open(1)"></nut-cell>
     <h2>多列样式</h2>
-    <nut-cell title="请选择时间" desc="周三 下午" @click="sss"></nut-cell>
+    <nut-cell title="请选择时间" :desc="desc2" @click="open(2)"></nut-cell>
     <h2>多级联动</h2>
-    <nut-cell title="请选择时间" desc="北京 朝阳" @click="sss"></nut-cell>
-    <h2>带标题的样式</h2>
-    <nut-cell title="请选择时间" desc="请选择城市" @click="sss"></nut-cell>
-    <nut-picker :show="show" @close="close"></nut-picker>
+    <nut-cell title="请选择地址" :desc="desc3" @click="open(3)"></nut-cell>
+
+    <nut-picker
+      v-model:is-visible="show"
+      :list-data="listData1"
+      title="城市选择"
+      @confirm="confirm"
+      @close="close"
+    ></nut-picker>
+    <nut-picker
+      v-model:is-visible="show2"
+      :list-data="listData2"
+      title="多列选择"
+      @confirm="confirm2"
+      @close="close"
+    ></nut-picker>
+    <nut-picker
+      v-model:is-visible="show3"
+      :list-data="listData3"
+      title="地址选择"
+      @confirm="confirm3"
+    ></nut-picker>
   </div>
 </template>
 <script lang="ts">
@@ -18,21 +36,93 @@ const { createDemo } = createComponent('picker');
 export default createDemo({
   props: {},
   setup() {
+    const listData1 = [
+      '南京市',
+      '无锡市',
+      '海北藏族自治区',
+      '北京市',
+      '连云港市',
+      '浙江市',
+      '江苏市'
+    ];
+    const listData2 = [
+      {
+        values: ['周一', '周二', '周三', '周四', '周五'],
+        defaultIndex: 2
+      },
+      // 第二列
+      {
+        values: ['上午', '下午', '晚上'],
+        defaultIndex: 1
+      }
+    ];
+    const listData3 = [
+      {
+        text: '浙江',
+        children: [
+          {
+            text: '杭州',
+            children: [{ text: '西湖区' }, { text: '余杭区' }]
+          },
+          {
+            text: '温州',
+            children: [{ text: '鹿城区' }, { text: '瓯海区' }]
+          }
+        ]
+      },
+      {
+        text: '福建',
+        children: [
+          {
+            text: '福州',
+            children: [{ text: '鼓楼区' }, { text: '台江区' }]
+          },
+          {
+            text: '厦门',
+            children: [{ text: '思明区' }, { text: '海沧区' }]
+          }
+        ]
+      }
+    ];
     const show = ref(false);
+    const show2 = ref(false);
+    const show3 = ref(false);
+    const showList = [show, show2, show3];
+    const desc = ref(listData1[0]);
+    const desc2 = ref(
+      `${listData2[0].values[listData2[0].defaultIndex]} ${
+        listData2[1].values[listData2[1].defaultIndex]
+      }`
+    );
+    const desc3 = ref(
+      `${listData3[0].text} 
+      ${listData3[0].children[0].text} 
+      ${listData3[0].children[0].children[0].text}`
+    );
+    const descList = [desc, desc2, desc3];
     return {
+      listData1,
+      listData2,
+      listData3,
       show,
-      close: () => {
-        show.value = false;
+      show2,
+      show3,
+      desc,
+      desc2,
+      desc3,
+      open: index => {
+        showList[index - 1].value = true;
+      },
+      confirm: res => {
+        desc.value = res;
       },
-      sss: () => {
-        show.value = true;
+      confirm2: res => {
+        desc2.value = res.join(' ');
+      },
+      confirm3: res => {
+        desc3.value = res.join(' ');
       }
     };
   }
 });
 </script>
-
-<style lang="scss" scoped>
-.demo {
-}
-</style>

+ 154 - 10
src/packages/picker/doc.md

@@ -2,31 +2,175 @@
 
 ### 介绍
     
-基于 xxxxxxx
+提供多个选项集合供用户选择其中一项。
     
 ## 安装
-    
-    
+```javascript
+import { createApp } from 'vue';
+import { Picker } from '@nutui/nutui';
+
+const app = createApp();
+app.use(Picker);
+```
     
 ## 代码演示
-    
-### 基础用法1
-    
 
     
+### 基础用法
+```html
+<nut-cell title="请选择城市" :desc="desc" @click="open"></nut-cell>
+<nut-picker
+    v-model:is-visible="show"
+    :list-data="listData"
+    title="城市选择"
+    @confirm="confirm" 
+></nut-picker>
+```
+```javascript
+<script>
+export default createDemo({
+  setup(props, { emit }) {
+    const show = ref(false);
+    const listData = [
+      '南京市',
+      '无锡市',
+      '海北藏族自治区',
+      '北京市',
+      '连云港市',
+      '浙江市',
+      '江苏市'
+    ];
+
+    return {
+      listData,
+      open: (index) => {
+        show.value = true;
+      },
+      confirm: (res) => {
+        desc.value = res;
+      }
+    };
+  }
+});
+</script>
+```
+### 多列样式
+
+```html
+<nut-cell title="请选择时间" :desc="desc" @click="open"></nut-cell>
+<nut-picker
+    v-model:is-visible="show"
+    :list-data="listData"
+    title="多列选择"
+    @confirm="confirm"
+    @close="close"
+></nut-picker>
+```
+```javascript
+<script>
+export default createDemo({
+  setup(props, { emit }) {
+    const show = ref(false);
+    const listData = [
+      {
+        values: ['周一', '周二', '周三', '周四', '周五'],
+        defaultIndex: 2
+      },
+      // 第二列
+      {
+        values: ['上午', '下午', '晚上'],
+        defaultIndex: 1
+      }
+    ];
+
+    return {
+      listData,
+      open: (index) => {
+        show.value = true;
+      },
+      confirm: (res) => {
+        desc.value = res.join(' ');
+      }
+    };
+  }
+});
+</script>
+```
+### 多级联动
+```html
+<nut-cell title="地址" :desc="desc" @click="open"></nut-cell>
+<nut-picker
+    v-model:is-visible="show"
+    :list-data="listData"
+    title="地址选择"
+    @confirm="confirm" 
+></nut-picker>
+```
+```javascript
+<script>
+export default createDemo({
+  setup(props, { emit }) {
+    const show = ref(false);
+    const listData = [
+      {
+        text: '浙江',
+        children: [
+          {
+            text: '杭州',
+            children: [{ text: '西湖区' }, { text: '余杭区' }]
+          },
+          {
+            text: '温州',
+            children: [{ text: '鹿城区' }, { text: '瓯海区' }]
+          }
+        ]
+      },
+      {
+        text: '福建',
+        children: [
+          {
+            text: '福州',
+            children: [{ text: '鼓楼区' }, { text: '台江区' }]
+          },
+          {
+            text: '厦门',
+            children: [{ text: '思明区' }, { text: '海沧区' }]
+          }
+        ]
+      }
+    ];
+
+    return {
+      listData,
+      open: (index) => {
+        show.value = true;
+      },
+      confirm: (res) => {
+        desc.value = res.join(' ');
+      }
+    };
+  }
+});
+</script>
+``` 
+
+
+
 ## API
     
 ### Props
     
 | 参数         | 说明                             | 类型   | 默认值           |
 |--------------|----------------------------------|--------|------------------|
-| name         | 图标名称或图片链接               | String | -                |
-| color        | 图标颜色                         | String | -                |
-| size         | 图标大小,如 '20px' '2em' '2rem' | String | -                |
+| is-visible   | 是否可见               | Boolean | false             |
+| title        | 设置标题                         | String | -                |
+| list-data         | 列表数据 | Array | -                |
+| default-value-index         | 初始选中项的索引,默认为 0 | number | 0                |
    
 ### Events
     
 | 事件名 | 说明           | 回调参数     |
 |--------|----------------|--------------|
-| click  | 点击图标时触发 | event: Event |
+| close  | 关闭弹窗时触发  | event: Event |
+| confirm  | 点击确认时候触发  | event: Event |
     

+ 5 - 1
src/packages/picker/index.scss

@@ -3,7 +3,6 @@
     position: relative;
     text-align: center;
     overflow-y: hidden;
-    flex-grow: 1;
     &:hover {
       cursor: grab;
     }
@@ -36,6 +35,11 @@
   }
   &__column {
     display: flex;
+    position: relative;
+  }
+  &__columnitem {
+    flex-grow: 1;
+    height: 100%;
   }
   &__left {
     color: #fa2c19;

+ 140 - 216
src/packages/picker/index.vue

@@ -7,270 +7,194 @@
       @close="close"
     >
       <view class="nut-picker__bar">
-        <view class="nut-picker__left"> 取消</view>
-        <view> 城市选择</view>
-        <view> 确定</view>
+        <view class="nut-picker__left" @click="close()"> 取消</view>
+        <view> {{ title }}</view>
+        <view @click="confirm()"> 确定</view>
       </view>
+
       <view class="nut-picker__column">
-        <column></column>
-        <column></column>
+        <view
+          class="nut-picker__mask"
+          :style="{ backgroundSize: `100% ${top}px` }"
+        ></view>
+        <view class="nut-picker__hairline" :style="{ top: ` ${top}px` }"></view>
+        <view
+          class="nut-picker__columnitem"
+          v-for="(item, columnIndex) in columnList"
+          :key="columnIndex"
+        >
+          <column
+            :listData="item.values"
+            :readonly="readonly"
+            :defaultIndex="item.defaultIndex"
+            :visibleItemCount="visibleItemCount"
+            :itemHeight="itemHeight"
+            :dataType="dataType"
+            @change="
+              dataIndex => {
+                changeHandler(columnIndex, dataIndex);
+              }
+            "
+          ></column>
+        </view>
       </view>
     </nut-popup>
   </view>
 </template>
 <script lang="ts">
-import { toRefs, reactive, ref, watch, computed } from 'vue';
+import { reactive, ref, watch, computed, toRaw } from 'vue';
 import { createComponent } from '@/utils/create';
-import { useTouch } from './use-touch';
 import column from './Column.vue';
-const MOMENTUM_LIMIT_DISTANCE = 15;
-const MOMENTUM_LIMIT_TIME = 300;
-const DEFAULT_DURATION = 200;
-const { componentName, create } = createComponent('picker');
-function range(num: number, min: number, max: number): number {
-  return Math.min(Math.max(num, min), max);
-}
-function preventDefault(event: Event, isStopPropagation?: boolean) {
-  /* istanbul ignore else */
-  if (typeof event.cancelable !== 'boolean' || event.cancelable) {
-    event.preventDefault();
-  }
-
-  if (isStopPropagation) {
-    stopPropagation(event);
-  }
-}
-
-function stopPropagation(event: Event) {
-  event.stopPropagation();
-}
-function getElementTranslateY(element) {
-  const style = window.getComputedStyle(element);
-  const transform = style.transform || style.webkitTransform;
-  const translateY = transform.slice(7, transform.length - 1).split(', ')[5];
-  return Number(translateY);
-}
-export function isObject(val: unknown): val is Record<any, any> {
-  return val !== null && typeof val === 'object';
-}
+import { commonProps } from './commonProps';
+const { create } = createComponent('picker');
 
-function isOptionDisabled(option) {
-  return isObject(option) && option.disabled;
-}
 export default create({
   props: {
-    show: {
-      type: Boolean,
-      default: false
-    },
-    readonly: {
+    isVisible: {
       type: Boolean,
       default: false
     },
-    txt: {
+    title: {
       type: String,
       default: ''
     },
-    visibleItemCount: {
-      type: [Number],
-      default: 7
-    },
-    defaultIndex: {
-      type: [Number, String],
-      default: 0
-    },
-    itemHeight: {
-      type: [Number],
-      default: 35
-    },
-    initialOptions: {
-      type: Array,
-      default: () => [
-        1,
-        2,
-        3,
-        4,
-        5,
-        6,
-        7,
-        8,
-        9,
-        11,
-        22,
-        33,
-        44,
-        55,
-        66,
-        77,
-        8,
-        9
-      ]
-    }
+    ...commonProps
   },
   components: { column },
-  emits: ['click', 'close'],
+  emits: ['close', 'confirm', 'update:isVisible'],
 
   setup(props, { emit }) {
-    let moving;
-    let startOffset, touchStartTime, momentumOffset, transitionEndTrigger;
-
-    const _show = ref(false);
+    const show = ref(false);
+    const defaultIndex = ref(props.defaultIndex);
+    const listData = reactive(props.listData);
+    //临时变量,当点击确定时候赋值
+    let _defaultIndex = props.defaultIndex;
+    const childrenKey = 'children';
+    const valuesKey = 'values';
+    let defaultIndexList = [];
 
-    const state = reactive({
-      index: props.defaultIndex,
-      offset: 0,
-      duration: 0,
-      options: props.initialOptions
-    });
-    const baseOffset = () =>
-      (props.itemHeight * (props.visibleItemCount - 1)) / 2;
-    const count = () => state.options.length;
-    const momentum = (distance, duration) => {
-      const speed = Math.abs(distance / duration);
-
-      distance = state.offset + (speed / 0.003) * (distance < 0 ? -1 : 1);
-
-      const index = getIndexByOffset(distance);
-
-      setIndex(index, true);
-    };
     watch(
-      () => props.show,
+      () => props.isVisible,
       val => {
-        _show.value = val;
+        show.value = val;
       }
     );
-    const stopMomentum = () => {
-      moving = false;
-      state.duration = 0;
-    };
-    const wrapper = ref();
-    const touch = useTouch();
-    const adjustIndex = index => {
-      index = range(index, 0, count());
 
-      for (let i = index; i < count(); i++) {
-        if (!isOptionDisabled(state.options[i])) return i;
-      }
-      for (let i = index - 1; i >= 0; i--) {
-        if (!isOptionDisabled(state.options[i])) return i;
-      }
+    const addDefaultIndexList = listData => {
+      defaultIndexList = [];
+      listData.forEach(res => {
+        defaultIndexList.push(res.defaultIndex || 0);
+      });
     };
-    const setIndex = (index, emitChange) => {
-      index = adjustIndex(index) || 0;
-
-      const offset = -index * props.itemHeight;
-      const trigger = () => {
-        if (index !== state.index) {
-          state.index = index;
-
-          if (emitChange) {
-            // emit('change', index);
-          }
+    const dataType = computed(() => {
+      const firstColumn = listData[0] || {};
+
+      if (typeof firstColumn === 'object') {
+        if (firstColumn?.[childrenKey]) {
+          return 'cascade';
+        } else if (firstColumn?.[valuesKey]) {
+          addDefaultIndexList(props.listData);
+          //多列
+          return 'multipleColumns';
         }
-      };
-
-      // trigger the change event after transitionend when moving
-      if (moving && offset !== state.offset) {
-        transitionEndTrigger = trigger;
-      } else {
-        trigger();
       }
-
-      state.offset = offset;
-    };
-    const getIndexByOffset = offset =>
-      range(Math.round(-offset / props.itemHeight), 0, count() - 1);
-    const onTouchStart = event => {
-      if (props.readonly) {
-        return;
-      }
-      touch.start(event);
-
-      if (moving) {
-        const translateY = getElementTranslateY(wrapper.value);
-        state.offset = Math.min(0, translateY - baseOffset());
-        startOffset = state.offset;
-      } else {
-        startOffset = state.offset;
+      return 'text';
+    });
+    const formatCascade = (listData, defaultIndex) => {
+      const formatted = [];
+      let children = listData;
+      children.defaultIndex = defaultIndex;
+      while (children) {
+        formatted.push({
+          values: children,
+          defaultIndex: children.defaultIndex
+        });
+        children = children?.[children.defaultIndex || 0].children;
       }
-
-      state.duration = 0;
-      touchStartTime = Date.now();
-      momentumOffset = startOffset;
-      transitionEndTrigger = null;
+      addDefaultIndexList(formatted);
+      return formatted;
     };
-    const onTouchMove = event => {
-      if (props.readonly) {
-        return;
-      }
-      moving = true;
-      touch.move(event);
-
-      if (touch.isVertical()) {
-        moving = true;
-        // preventDefault(event, true);
-      }
-
-      const moveOffset = startOffset + touch.deltaY.value;
-      if (moveOffset > props.itemHeight) {
-        state.offset = props.itemHeight;
-      } else {
-        state.offset = startOffset + touch.deltaY.value;
-      }
-
-      const now = Date.now();
 
-      if (now - touchStartTime > MOMENTUM_LIMIT_TIME) {
-        touchStartTime = now;
-        momentumOffset = state.offset;
+    const columnList = computed(() => {
+      if (dataType.value === 'text') {
+        return [{ values: listData, defaultIndex: defaultIndex.value }];
+      } else if (dataType.value === 'multipleColumns') {
+        return listData;
+      } else if (dataType.value === 'cascade') {
+        return formatCascade(listData, defaultIndex.value);
       }
-    };
-    const onTouchEnd = () => {
-      const index = getIndexByOffset(state.offset);
-      state.duration = DEFAULT_DURATION;
-      setIndex(index, true);
-      const distance = state.offset - momentumOffset;
-      const duration = Date.now() - touchStartTime;
-
-      const allowMomentum =
-        duration < MOMENTUM_LIMIT_TIME &&
-        Math.abs(distance) > MOMENTUM_LIMIT_DISTANCE;
-
-      if (allowMomentum) {
-        momentum(distance, duration);
-        return;
+      return listData;
+    });
+    const getCascadeData = (listData, defaultIndex) => {
+      let arr = listData;
+      arr.defaultIndex = defaultIndex;
+      const dataList = [];
+
+      while (arr) {
+        const item = arr[arr.defaultIndex ?? 0];
+        dataList.push(item.text);
+        arr = item.children;
       }
+      return dataList;
     };
-    const handleClick = (event: Event) => {
-      emit('click', event);
-    };
-    const wrapperStyle = computed(() => ({
-      transform: `translate3d(0, ${state.offset + baseOffset()}px, 0)`,
-      transitionDuration: `${state.duration}ms`,
-      transitionProperty: state.duration ? 'all' : 'none'
-    }));
     return {
-      show: _show,
-      wrapper,
-      onTouchStart,
-      onTouchMove,
-      onTouchEnd,
-      wrapperStyle,
-      state,
+      show,
       column,
-      stopMomentum,
-      columns: props.initialOptions,
+      title: props.title,
+      dataType,
+      columnList,
       top: (Number(props.visibleItemCount - 1) / 2) * props.itemHeight,
       height: Number(props.visibleItemCount) * props.itemHeight,
       close: () => {
         emit('close');
+        emit('update:isVisible', false);
+      },
+      changeHandler: (columnIndex, dataIndex) => {
+        if (dataType.value === 'cascade') {
+          let cursor = listData;
+          //最外层使用props.defaultIndex作为初始index
+          if (columnIndex === 0) {
+            defaultIndex.value = dataIndex;
+          } else {
+            let i = 0;
+            while (cursor) {
+              if (i === columnIndex) {
+                cursor.defaultIndex = dataIndex;
+              } else if (i > columnIndex) {
+                cursor.defaultIndex = 0;
+              }
+              cursor = cursor[cursor.defaultIndex || 0].children;
+              i++;
+            }
+          }
+        } else if (dataType.value === 'text') {
+          _defaultIndex = dataIndex;
+        } else if (dataType.value === 'multipleColumns') {
+          defaultIndexList[columnIndex] = dataIndex;
+        }
+      },
+      confirm: () => {
+        if (dataType.value === 'text') {
+          defaultIndex.value = _defaultIndex;
+          emit('confirm', listData[_defaultIndex]);
+        } else if (dataType.value === 'multipleColumns') {
+          for (let i = 0; i < defaultIndexList.length; i++) {
+            listData[i].defaultIndex = defaultIndexList[i];
+          }
+          const checkedArr = toRaw(listData).map(
+            res => res.values[res.defaultIndex]
+          );
+          emit('confirm', checkedArr);
+        } else if (dataType.value === 'cascade') {
+          emit('confirm', getCascadeData(toRaw(listData), defaultIndex.value));
+        }
+
+        emit('update:isVisible', false);
       }
     };
   }
 });
 </script>
-
 <style lang="scss">
 @import 'index.scss';
 </style>

+ 0 - 69
src/packages/picker/use-touch.ts

@@ -1,69 +0,0 @@
-import { ref } from 'vue';
-
-const MIN_DISTANCE = 10;
-
-type Direction = '' | 'vertical' | 'horizontal';
-
-function getDirection(x: number, y: number) {
-  if (x > y && x > MIN_DISTANCE) {
-    return 'horizontal';
-  }
-  if (y > x && y > MIN_DISTANCE) {
-    return 'vertical';
-  }
-  return '';
-}
-
-export function useTouch() {
-  const startX = ref(0);
-  const startY = ref(0);
-  const deltaX = ref(0);
-  const deltaY = ref(0);
-  const offsetX = ref(0);
-  const offsetY = ref(0);
-  const direction = ref<Direction>('');
-
-  const isVertical = () => direction.value === 'vertical';
-  const isHorizontal = () => direction.value === 'horizontal';
-
-  const reset = () => {
-    deltaX.value = 0;
-    deltaY.value = 0;
-    offsetX.value = 0;
-    offsetY.value = 0;
-    direction.value = '';
-  };
-
-  const start = ((event: TouchEvent) => {
-    reset();
-    startX.value = event.touches[0].clientX;
-    startY.value = event.touches[0].clientY;
-  }) as EventListener;
-
-  const move = ((event: TouchEvent) => {
-    const touch = event.touches[0];
-    deltaX.value = touch.clientX - startX.value;
-    deltaY.value = touch.clientY - startY.value;
-    offsetX.value = Math.abs(deltaX.value);
-    offsetY.value = Math.abs(deltaY.value);
-
-    if (!direction.value) {
-      direction.value = getDirection(offsetX.value, offsetY.value);
-    }
-  }) as EventListener;
-
-  return {
-    move,
-    start,
-    reset,
-    startX,
-    startY,
-    deltaX,
-    deltaY,
-    offsetX,
-    offsetY,
-    direction,
-    isVertical,
-    isHorizontal
-  };
-}