Browse Source

fix: 修复 Picker 在 Taro H5 下无法使用问题 #1911 #1921 (#1926)

* fix: 修复 ImagePreview 在Taro编译成H5后报错的问题

* fix: 地址关闭时, Close 事件触发两次问题解决

* feat: 组件DatePicker 添加双向绑定

* docs: 组件Picker文档修改

* feat: 组件Picker与DatePicker新增属性safe-area-inset-bottom

* feat: imagepreview

* fix: 组件imagepreview点击视频遮罩关闭(#1729)

* fix: 解决 Picker 在微信小程序中无法使用问题 (#1774)

* fix: 修改 Picker 组件 v-model 失效问题

* fix: 组件NoticeBar修改height之后,垂直轮播会卡顿

* fix: 删除Datepicker Demo演示多余内容

* fix: 组件Picker在JD小程序上适配

* fix: 组件Address京东小程序适配

* feat: 京东小程序适配

* fix: 删除空格

* feat: 删除console

* fix: 京东小程序imagepreview适配

* fix: 修复 imagepreview 动态设置 initNo 显示不正确问题

* fix: 组件 InfiniteLoading 某些情况下会错误触发下拉刷新#1819

* fix: 删除pullrefresh

* feat: 组件 imagepreview瘦身

* feat: 组件Picker 瘦身

* fix: address线上问题修改

* fix: 完善imagepreview

* feat: 公共函数提取

* feat: 函数式改用 createComponent

* fix: 文件回撤

* feat: 单元测试修改

* fix: 组件popover样式问题修改

* feat: 新增 clamp 函数

* fix: 组件Infiniteloading Review 内容修改

* fix: 组件Infiniteloading问题更新

* feat: 组件picker taro 更新

* feat: getScrollTopRoot函数修改

* fix: 组件Picker在Taro H5环境下无法使用 #1911 #1921
yangxiaolu1993 3 years ago
parent
commit
1c1d30db1b

+ 0 - 348
src/packages/__VUE/picker/ColumnTaro.vue

@@ -1,348 +0,0 @@
-<template>
-  <view class="nut-picker__list" @touchstart="onTouchStart" @touchmove="onTouchMove" @touchend="onTouchEnd">
-    <view
-      class="nut-picker-roller"
-      ref="roller"
-      :id="'roller' + refRandomId"
-      :style="threeDimensional ? touchRollerStyle : touchTileStyle"
-      @transitionend="stopMomentum"
-    >
-      <template v-for="(item, index) in column" :key="item.value ? item.value : index">
-        <!-- 3D 效果 -->
-        <view
-          class="nut-picker-roller-item"
-          :class="{ 'nut-picker-roller-item-hidden': isHidden(index + 1) }"
-          :style="setRollerStyle(index + 1)"
-          v-if="item && item.text && threeDimensional"
-        >
-          {{ item.text }}
-        </view>
-        <!-- 平铺 -->
-        <view class="nut-picker-roller-item-tile" v-if="item && item.text && !threeDimensional">
-          {{ item.text }}
-        </view>
-      </template>
-    </view>
-    <view class="nut-picker-roller-mask"></view>
-  </view>
-</template>
-<script lang="ts">
-import { reactive, ref, watch, computed, toRefs, onMounted, PropType } from 'vue';
-import { createComponent } from '@/packages/utils/create';
-import { PickerOption, TouchParams } from './types';
-import { useTaroRect } from '@/packages/utils/useTaroRect';
-import { clamp } from '@/packages/utils/util';
-import { useTouch } from '@/packages/utils/useTouch';
-const { create } = createComponent('picker-column');
-import Taro from '@tarojs/taro';
-
-export default create({
-  props: {
-    // 当前选中项
-    value: [String, Number],
-    columnsType: String,
-    lineSpacing: {
-      type: Number,
-      default: 36
-    },
-    itemShow: {
-      type: Boolean,
-      default: false
-    },
-    column: {
-      type: Array as PropType<PickerOption[]>,
-      default: () => []
-    },
-    readonly: {
-      type: Boolean,
-      default: false
-    },
-    // 是否开启3D效果
-    threeDimensional: {
-      type: Boolean,
-      default: true
-    },
-    // 惯性滚动 时长
-    swipeDuration: {
-      type: [Number, String],
-      default: 1000
-    }
-  },
-
-  emits: ['click', 'change'],
-  setup(props, { emit }) {
-    const touch: any = useTouch();
-    const wrapper = ref<HTMLElement>();
-    const itemref = ref();
-    const state = reactive({
-      touchParams: {
-        startY: 0,
-        endY: 0,
-        startTime: 0,
-        endTime: 0,
-        lastY: 0,
-        lastTime: 0
-      },
-      currIndex: 1,
-      transformY: 0,
-      scrollDistance: 0,
-      rotation: 20,
-      timer: null
-    });
-
-    const roller = ref(null);
-    const list = ref(null);
-    const listitem = ref(null);
-
-    const moving = ref(false); // 是否处于滚动中
-    const touchDeg = ref(0);
-    const touchTime = ref(0);
-    const touchTranslateY = ref(0);
-
-    const DEFAULT_DURATION = 200;
-
-    // 触发惯性滑动条件:
-    // 在手指离开屏幕时,如果和上一次 move 时的间隔小于 `MOMENTUM_TIME` 且 move
-    // 距离大于 `MOMENTUM_DISTANCE` 时,执行惯性滑动
-    const INERTIA_TIME = 300;
-    const INERTIA_DISTANCE = 15;
-
-    const touchTileStyle = computed(() => {
-      return {
-        transition: `transform ${touchTime.value}ms cubic-bezier(0.17, 0.89, 0.45, 1)`,
-        transform: `translate3d(0, ${state.scrollDistance}px, 0)`
-      };
-    });
-
-    const touchRollerStyle = computed(() => {
-      return {
-        transition: `transform ${touchTime.value}ms cubic-bezier(0.17, 0.89, 0.45, 1)`,
-        transform: `rotate3d(1, 0, 0, ${touchDeg.value})`
-      };
-    });
-    const setRollerStyle = (index: number) => {
-      return `transform: rotate3d(1, 0, 0, ${-state.rotation * index}deg) translate3d(0px, 0px, 104px)`;
-    };
-
-    const onTouchStart = (event: TouchEvent) => {
-      touch.start(event);
-      if (moving.value) {
-        let dom = list.value as any;
-        if (!props.threeDimensional) {
-          dom = roller.value as any;
-        }
-
-        const { transform } = window.getComputedStyle(dom);
-
-        state.scrollDistance = +transform.slice(7, transform.length - 1).split(', ')[5];
-      }
-
-      state.touchParams.startY = touch.deltaY.value;
-      state.touchParams.startTime = Date.now();
-      state.transformY = state.scrollDistance;
-    };
-
-    const onTouchMove = (event: TouchEvent) => {
-      touch.move(event);
-
-      if ((touch as any).isVertical) {
-        moving.value = true;
-        preventDefault(event, true);
-      }
-
-      (state.touchParams as TouchParams).lastY = touch.deltaY.value;
-      const now = Date.now();
-      let move = state.touchParams.lastY - state.touchParams.startY;
-
-      setMove(move);
-
-      // if (now - (state.touchParams as TouchParams).startTime > INERTIA_TIME) {
-      //   (state.touchParams as TouchParams).startTime = now;
-      //   state.touchParams.startY = (state.touchParams as TouchParams).lastY;
-      // }
-    };
-
-    const onTouchEnd = (event: TouchEvent) => {
-      state.touchParams.lastY = touch.deltaY.value;
-      state.touchParams.lastTime = Date.now();
-      let move = state.touchParams.lastY - state.touchParams.startY;
-
-      let moveTime = state.touchParams.lastTime - state.touchParams.startTime;
-
-      if (moveTime <= INERTIA_TIME && Math.abs(move) > INERTIA_DISTANCE) {
-        // 惯性滚动
-        const distance = momentum(move, moveTime);
-        setMove(distance, 'end', +props.swipeDuration);
-        return;
-      } else {
-        setMove(move, 'end');
-      }
-
-      setTimeout(() => {
-        touch.reset();
-        moving.value = false;
-      }, 0);
-    };
-
-    // 惯性滚动 距离
-    const momentum = (distance: number, duration: number) => {
-      // 惯性滚动的速度
-      const speed = Math.abs(distance / duration);
-      // 惯性滚动的距离
-      distance = (speed / 0.003) * (distance < 0 ? -1 : 1);
-      return distance;
-    };
-
-    const isHidden = (index: number) => {
-      if (index >= state.currIndex + 8 || index <= state.currIndex - 8) {
-        return true;
-      } else {
-        return false;
-      }
-    };
-
-    const setTransform = (translateY = 0, type: string | null, time = DEFAULT_DURATION, deg: string | number) => {
-      if (type === 'end') {
-        touchTime.value = time;
-      } else {
-        touchTime.value = 0;
-      }
-      touchDeg.value = deg as number;
-      touchTranslateY.value = translateY;
-      state.scrollDistance = translateY;
-    };
-
-    const setMove = (move: number, type?: string, time?: number) => {
-      let updateMove = move + state.transformY;
-      if (type === 'end') {
-        // 限定滚动距离
-        if (updateMove > 0) {
-          updateMove = 0;
-        }
-        if (updateMove < -(props.column.length - 1) * props.lineSpacing) {
-          updateMove = -(props.column.length - 1) * props.lineSpacing;
-        }
-
-        // 设置滚动距离为lineSpacing的倍数值
-        let endMove = Math.round(updateMove / props.lineSpacing) * props.lineSpacing;
-        let deg = `${(Math.abs(Math.round(endMove / props.lineSpacing)) + 1) * state.rotation}deg`;
-        setTransform(endMove, type, time, deg);
-
-        // let t = time ? time / 2 : 0;
-        // (state.timer as any) = setTimeout(() => {
-        //   setChooseValue();
-        // }, t);
-
-        state.currIndex = Math.abs(Math.round(endMove / props.lineSpacing)) + 1;
-      } else {
-        let deg = 0;
-        let currentDeg = (-updateMove / props.lineSpacing + 1) * state.rotation;
-
-        // picker 滚动的最大角度
-        const maxDeg = (props.column.length + 1) * state.rotation;
-        const minDeg = 0;
-
-        deg = clamp(currentDeg, minDeg, maxDeg);
-
-        if (minDeg < deg && deg < maxDeg) {
-          setTransform(updateMove, null, undefined, deg + 'deg');
-          state.currIndex = Math.abs(Math.round(updateMove / props.lineSpacing)) + 1;
-        }
-      }
-    };
-
-    const setChooseValue = () => {
-      emit('change', props.column[state.currIndex - 1]);
-    };
-
-    const modifyStatus = (type: boolean) => {
-      const { column } = props;
-      let index = column.findIndex((columnItem) => columnItem.value == props.value);
-
-      state.currIndex = index === -1 ? 1 : (index as number) + 1;
-      let move = index === -1 ? 0 : (index as number) * props.lineSpacing;
-      type && setChooseValue();
-      setMove(-move);
-    };
-
-    const preventDefault = (event: Event, isStopPropagation?: boolean) => {
-      /* istanbul ignore else */
-      if (typeof event.cancelable !== 'boolean' || event.cancelable) {
-        event.preventDefault();
-      }
-
-      if (isStopPropagation) {
-        event.stopPropagation();
-      }
-    };
-
-    // 惯性滚动结束
-    const stopMomentum = () => {
-      moving.value = false;
-      touchTime.value = 0;
-      setChooseValue();
-    };
-
-    watch(
-      () => props.column,
-      (val) => {
-        if (props.column && props.column.length > 0) {
-          state.transformY = 0;
-          modifyStatus(false);
-        }
-      },
-      {
-        deep: true
-      }
-    );
-
-    watch(
-      () => props.value,
-      (val) => {
-        state.transformY = 0;
-        modifyStatus(false);
-      },
-      {
-        deep: true
-      }
-    );
-
-    watch(
-      () => props.lineSpacing,
-      (val) => {
-        console.log('更新');
-        modifyStatus(false);
-      },
-      {
-        deep: true
-      }
-    );
-
-    onMounted(() => {
-      modifyStatus(false);
-    });
-
-    const refRandomId = Math.random().toString(36).slice(-8);
-
-    return {
-      ...toRefs(state),
-      ...toRefs(props),
-      wrapper,
-      itemref,
-      setRollerStyle,
-      isHidden,
-      roller,
-      list,
-      listitem,
-      onTouchStart,
-      onTouchMove,
-      onTouchEnd,
-      touchTileStyle,
-      touchRollerStyle,
-      setMove,
-      refRandomId,
-      stopMomentum
-    };
-  }
-});
-</script>

+ 297 - 0
src/packages/__VUE/picker/common.ts

@@ -0,0 +1,297 @@
+import { ref, onMounted, onBeforeUnmount, reactive, watch, computed, toRaw, toRefs, PropType } from 'vue';
+import { createComponent } from '@/packages/utils/create';
+const { componentName, create, translate } = createComponent('picker');
+import { usePicker } from './usePicker';
+import { popupProps } from '../popup/props';
+import column from './Column.vue';
+import Taro from '@tarojs/taro';
+
+export interface PickerOption {
+  text: string | number;
+  value: string | number;
+  disabled?: string;
+  children?: PickerOption[];
+  className?: string | number;
+}
+
+export const componentWeb = {
+  components: {
+    [column.name]: column
+  },
+  props: {
+    ...popupProps,
+    modelValue: {
+      type: Array as PropType<(string | number)[]>,
+      default: () => []
+    },
+    title: {
+      type: String,
+      default: ''
+    },
+    cancelText: {
+      type: String,
+      default: ''
+    },
+    okText: {
+      type: String,
+      default: ''
+    },
+    columns: {
+      type: Array as PropType<(PickerOption | PickerOption[])[]>,
+      default: () => {
+        return [];
+      }
+    },
+    readonly: {
+      type: Boolean,
+      default: false
+    },
+    threeDimensional: {
+      type: Boolean,
+      default: true
+    },
+    swipeDuration: {
+      type: [Number, String],
+      default: 1000
+    }
+  },
+  emits: ['close', 'change', 'confirm', 'update:visible', 'update:modelValue'],
+  setup(props: any, { emit }: any) {
+    const { changeHandler, confirm, defaultValues, columnsList, selectedOptions, columnsType, classes, close } =
+      usePicker(props, emit);
+
+    const state = reactive({
+      show: false,
+      ENV: Taro.getEnv(),
+      ENV_TYPE: Taro.ENV_TYPE
+    });
+
+    const pickerColumn = ref<any[]>([]);
+
+    const swipeRef = (el: any) => {
+      if (el && pickerColumn.value.length < columnsList.value.length) {
+        pickerColumn.value.push(el);
+      }
+    };
+
+    const confirmHandler = () => {
+      pickerColumn.value.length > 0 &&
+        pickerColumn.value.forEach((column) => {
+          column.stopMomentum();
+        });
+
+      confirm();
+    };
+
+    onMounted(() => {
+      if (props.visible) state.show = props.visible;
+    });
+
+    onBeforeUnmount(() => {
+      if (props.visible) state.show = false;
+    });
+
+    watch(
+      () => props.visible,
+      (val) => {
+        state.show = val;
+        if (val) {
+          pickerColumn.value = [];
+        }
+      }
+    );
+
+    return {
+      classes,
+      ...toRefs(state),
+      column,
+      columnsType,
+      columnsList,
+      close,
+      changeHandler,
+      confirmHandler,
+      defaultValues,
+      pickerColumn,
+      swipeRef,
+      translate
+    };
+  }
+};
+
+export const componentWeapp = {
+  components: {
+    [column.name]: column
+  },
+  props: {
+    ...popupProps,
+    modelValue: {
+      type: Array as PropType<(string | number)[]>,
+      default: []
+    },
+    title: {
+      type: String,
+      default: ''
+    },
+    cancelText: {
+      type: String,
+      default: ''
+    },
+    okText: {
+      type: String,
+      default: ''
+    },
+    columns: {
+      type: Array,
+      default: () => {
+        return [];
+      }
+    },
+    readonly: {
+      type: Boolean,
+      default: false
+    },
+    // 是否开启3D效果
+    threeDimensional: {
+      type: Boolean,
+      default: true
+    },
+    // 惯性滚动 时长
+    swipeDuration: {
+      type: [Number, String],
+      default: 1000
+    }
+  },
+  emits: ['close', 'change', 'confirm', 'update:visible', 'update:modelValue'],
+  setup(props: any, { emit }: any) {
+    const {
+      changeHandler,
+      confirm,
+      defaultValues,
+      columnsList,
+      selectedOptions,
+      isSameValue,
+      columnsType,
+      classes,
+      close
+    } = usePicker(props, emit);
+    const state = reactive({
+      show: false,
+      picking: false,
+      ENV: Taro.getEnv(),
+      ENV_TYPE: Taro.ENV_TYPE
+    });
+
+    // 选中项的位置  taro
+    let defaultIndexes = ref<number[]>([]);
+
+    const defaultValuesConvert = () => {
+      let defaultIndexs: number[] = [];
+      if (defaultValues.value.length > 0) {
+        defaultValues.value.forEach((value, index) => {
+          for (let i = 0; i < columnsList.value[index].length; i++) {
+            if (columnsList.value[index][i].value == value) {
+              defaultIndexs.push(i);
+              break;
+            }
+          }
+        });
+      } else {
+        columnsList.value.forEach((item) => {
+          defaultIndexs.push(0);
+          defaultValues.value.push(item[0].value);
+        });
+      }
+
+      return defaultIndexs;
+    };
+
+    // 平铺展示时,滚动选择
+    const tileChange = (data: any) => {
+      const prevDefaultValue = defaultIndexes.value;
+      let changeIndex = 0;
+      // 判断变化的是第几个
+      data.detail.value.forEach((col: number, index: number) => {
+        if (prevDefaultValue[index] != col) changeIndex = index;
+      });
+
+      if (state.show) {
+        defaultIndexes.value = data.detail.value;
+        // 选择的是哪个 option
+        changeHandler(changeIndex, columnsList.value[changeIndex][data.detail.value[changeIndex]]);
+      }
+    };
+
+    // 确定
+    const confirmHandler = () => {
+      if (state.picking) {
+        setTimeout(() => {
+          confirmHandlerAwit();
+        }, 0);
+      } else {
+        confirmHandlerAwit();
+      }
+    };
+
+    const confirmHandlerAwit = () => {
+      confirm();
+      state.show = false;
+    };
+
+    // 开始滚动
+    const handlePickstart = () => {
+      state.picking = true;
+    };
+    // 开始滚动
+    const handlePickend = () => {
+      state.picking = false;
+    };
+
+    onMounted(() => {
+      if (props.visible) {
+        defaultIndexes.value = defaultValuesConvert();
+        state.show = props.visible;
+      }
+    });
+
+    onBeforeUnmount(() => {
+      if (props.visible) state.show = false;
+    });
+
+    watch(
+      () => props.modelValue,
+      (newValues) => {
+        if (!isSameValue(newValues, defaultValues.value)) {
+          defaultIndexes.value = defaultValuesConvert();
+        }
+      },
+      { deep: true }
+    );
+
+    watch(
+      () => props.visible,
+      (val) => {
+        state.show = val;
+        if (val) {
+          defaultIndexes.value = defaultValuesConvert();
+        }
+      }
+    );
+
+    return {
+      classes,
+      ...toRefs(state),
+      column,
+      columnsType,
+      columnsList,
+      close,
+      changeHandler,
+      confirmHandler,
+      defaultValues,
+      defaultIndexes,
+      tileChange,
+      handlePickstart,
+      translate,
+      handlePickend
+    };
+  }
+};

+ 5 - 351
src/packages/__VUE/picker/index.taro.vue

@@ -43,9 +43,9 @@
           </view>
         </picker-view-column>
       </picker-view>
+
       <!-- Taro 下转换成 H5 -->
       <view class="nut-picker__column" v-if="ENV == ENV_TYPE.WEB">
-        <view class="nut-picker__hairline" ref="pickerline" :id="'pickerline' + refRandomId"></view>
         <view class="nut-picker__columnitem" v-for="(column, columnIndex) in columnsList" :key="columnIndex">
           <nut-picker-column
             :ref="swipeRef"
@@ -56,7 +56,6 @@
             :value="defaultValues[columnIndex]"
             :threeDimensional="false"
             :swipeDuration="swipeDuration"
-            :lineSpacing="lineSpacing"
             @change="
               (option) => {
                 changeHandler(columnIndex, option);
@@ -70,357 +69,12 @@
   </view>
 </template>
 <script lang="ts">
-import { ref, onMounted, onBeforeUnmount, reactive, watch, computed, toRaw, toRefs, PropType } from 'vue';
 import { createComponent } from '@/packages/utils/create';
-import { popupProps } from '../popup/props';
-import column from './ColumnTaro.vue';
-import { useTaroRect } from '@/packages/utils/useTaroRect';
+import { componentWeb, componentWeapp } from './common';
 import Taro from '@tarojs/taro';
-const { componentName, create, translate } = createComponent('picker');
-export default create({
-  components: {
-    [column.name]: column
-  },
-  props: {
-    ...popupProps,
-    modelValue: {
-      type: Array as PropType<(string | number)[]>,
-      default: []
-    },
-    title: {
-      type: String,
-      default: ''
-    },
-    cancelText: {
-      type: String,
-      default: ''
-    },
-    okText: {
-      type: String,
-      default: ''
-    },
-    columns: {
-      type: Array,
-      default: () => {
-        return [];
-      }
-    },
-    readonly: {
-      type: Boolean,
-      default: false
-    },
-    // 是否开启3D效果
-    threeDimensional: {
-      type: Boolean,
-      default: true
-    },
-    // 惯性滚动 时长
-    swipeDuration: {
-      type: [Number, String],
-      default: 1000
-    }
-  },
-  emits: ['close', 'change', 'confirm', 'update:visible', 'update:modelValue'],
-  setup(props, { emit }) {
-    const state = reactive({
-      show: false,
-      formattedColumns: props.columns as import('./types').PickerOption[],
-      lineSpacing: 36,
-      picking: false,
-      ENV: Taro.getEnv(),
-      ENV_TYPE: Taro.ENV_TYPE
-    });
-
-    const pickerline = ref(null);
-    // 选中项
-    let defaultValues = ref<(number | string)[]>(
-      Array.isArray(props.modelValue) && props.modelValue.length > 0 ? props.modelValue : []
-    );
-    // 选中项的位置
-    let defaultIndexes = ref<number[]>([]);
-
-    const pickerColumn = ref<any[]>([]);
-
-    const swipeRef = (el: any) => {
-      if (el && pickerColumn.value.length < columnsList.value.length) {
-        pickerColumn.value.push(el);
-      }
-    };
-
-    const classes = computed(() => {
-      const prefixCls = componentName;
-      return {
-        [prefixCls]: true
-      };
-    });
-
-    const selectedOptions = computed(() => {
-      let optins: import('./types').PickerOption[] = [];
-      (columnsList.value as import('./types').PickerOption[][]).map(
-        (column: import('./types').PickerOption[], index: number) => {
-          let currOptions = [];
-          currOptions = column.filter((item) => item.value == defaultValues.value[index]);
-          optins.push(currOptions[0]);
-        }
-      );
-
-      return optins;
-    });
-    // 当前类型
-    const columnsType = computed(() => {
-      const firstColumn: import('./types').PickerOption = state.formattedColumns[0];
-      if (firstColumn) {
-        if (Array.isArray(firstColumn)) {
-          return 'multiple';
-        }
-        if ('children' in firstColumn) {
-          return 'cascade';
-        }
-      }
-      return 'single';
-    });
-
-    // 将传入的 columns 格式化
-    const columnsList = computed(() => {
-      switch (columnsType.value) {
-        case 'multiple':
-          return state.formattedColumns;
-        case 'cascade':
-          // 级联数据处理
-          return formatCascade(state.formattedColumns, defaultValues.value);
-        default:
-          return [state.formattedColumns];
-      }
-    });
-
-    const formatCascade = (columns: import('./types').PickerOption[], defaultValues: (number | string)[]) => {
-      const formatted: import('./types').PickerOption[][] = [];
-      let cursor: import('./types').PickerOption = {
-        text: '',
-        value: '',
-        children: columns
-      };
-
-      let columnIndex = 0;
-
-      while (cursor && cursor.children) {
-        const options: import('./types').PickerOption[] = cursor.children;
-        const value = defaultValues[columnIndex];
-        let index = options.findIndex((columnItem) => columnItem.value == value);
-        if (index == -1) index = 0;
-        cursor = cursor.children[index];
-
-        columnIndex++;
-        formatted.push(options);
-      }
-
-      return formatted;
-    };
-
-    // 关闭
-    const close = () => {
-      emit('close', {
-        selectedValue: defaultValues.value,
-        selectedOptions: selectedOptions.value
-      });
-      emit('update:visible', false);
-    };
-
-    // 选择
-    const changeHandler = (columnIndex: number, option: import('./types').PickerOption) => {
-      if (option && Object.keys(option).length) {
-        if (columnsType.value === 'cascade') {
-          defaultValues.value[columnIndex] = option.value ? option.value : '';
-          let index = columnIndex;
-          let cursor = option;
-          while (cursor && cursor.children && cursor.children[0]) {
-            defaultValues.value[index + 1] = cursor.children[0].value;
-            index++;
-            cursor = cursor.children[0];
-          }
-
-          // 当前改变列 的 下一列 children 值为空
-          if (cursor && cursor.children && cursor.children.length == 0) {
-            defaultValues.value = defaultValues.value.slice(0, index + 1);
-          }
-        } else {
-          defaultValues.value[columnIndex] = option.hasOwnProperty('value') ? option.value : '';
-        }
-        emit('change', {
-          columnIndex: columnIndex,
-          selectedValue: defaultValues.value,
-          selectedOptions: selectedOptions.value
-        });
-      }
-    };
-
-    const defaultValuesConvert = () => {
-      let defaultIndexs = [];
-      if (defaultValues.value.length > 0) {
-        defaultValues.value.forEach((value, index) => {
-          for (let i = 0; i < columnsList.value[index].length; i++) {
-            if (columnsList.value[index][i].value == value) {
-              defaultIndexs.push(i);
-              break;
-            }
-          }
-        });
-      } else {
-        columnsList.value.forEach((item) => {
-          defaultIndexs.push(0);
-          defaultValues.value.push(item[0].value);
-        });
-      }
-
-      return defaultIndexs;
-    };
-
-    // 平铺展示时,滚动选择
-    const tileChange = ({ detail }) => {
-      const prevDefaultValue = defaultIndexes.value;
-      let changeIndex = 0;
-      // 判断变化的是第几个
-      detail.value.forEach((col, index) => {
-        if (prevDefaultValue[index] != col) changeIndex = index;
-      });
-
-      if (state.show) {
-        defaultIndexes.value = detail.value;
-        // 选择的是哪个 option
-        changeHandler(changeIndex, columnsList.value[changeIndex][detail.value[changeIndex]]);
-      }
-    };
-
-    // 确定
-    const confirmHandler = () => {
-      if (state.picking) {
-        setTimeout(() => {
-          confirmHandlerAwit();
-        }, 0);
-      } else {
-        confirmHandlerAwit();
-      }
-    };
-
-    const confirmHandlerAwit = () => {
-      if (defaultValues.value && !defaultValues.value.length) {
-        columnsList.value.forEach((columns) => {
-          defaultValues.value.push(columns[0].value);
-          selectedOptions.value.push(columns[0]);
-        });
-      }
-
-      emit('confirm', {
-        selectedValue: defaultValues.value,
-        selectedOptions: selectedOptions.value
-      });
-      emit('update:visible', false);
-
-      state.show = false;
-    };
-
-    // 开始滚动
-    const handlePickstart = () => {
-      state.picking = true;
-    };
-    // 开始滚动
-    const handlePickend = () => {
-      state.picking = false;
-    };
-
-    const refRandomId = Math.random().toString(36).slice(-8);
-
-    const getReference = async () => {
-      const refe = await useTaroRect(pickerline, Taro);
-      state.lineSpacing = refe.height ? refe.height : 36;
-    };
-
-    onMounted(() => {
-      if (props.visible) {
-        if (Taro.getEnv() == Taro.ENV_TYPE.WEB) {
-          setTimeout(() => {
-            getReference();
-          }, 200);
-        } else {
-          defaultIndexes.value = defaultValuesConvert();
-        }
-        state.show = props.visible;
-      }
-    });
-
-    onBeforeUnmount(() => {
-      if (props.visible) state.show = false;
-    });
-
-    watch(
-      () => props.modelValue,
-      (newValues) => {
-        const isSameValue = JSON.stringify(newValues) === JSON.stringify(defaultValues.value);
-        if (!isSameValue) {
-          defaultValues.value = newValues;
-          defaultIndexes.value = defaultValuesConvert();
-        }
-      },
-      { deep: true }
-    );
-
-    watch(
-      defaultValues,
-      (newValues) => {
-        const isSameValue = JSON.stringify(newValues) === JSON.stringify(props.modelValue);
-        if (!isSameValue) {
-          emit('update:modelValue', newValues);
-        }
-      },
-      { deep: true }
-    );
-
-    watch(
-      () => props.visible,
-      (val) => {
-        state.show = val;
-
-        if (val) {
-          if (Taro.getEnv() == Taro.ENV_TYPE.WEB) {
-            setTimeout(() => {
-              getReference();
-            }, 200);
-
-            pickerColumn.value = [];
-          } else {
-            defaultIndexes.value = defaultValuesConvert();
-          }
-        }
-      }
-    );
+const { create } = createComponent('picker');
 
-    watch(
-      () => props.columns,
-      (val) => {
-        if (val.length) state.formattedColumns = val as import('./types').PickerOption[];
-      }
-    );
+const component: any = Taro.getEnv() == Taro.ENV_TYPE.WEB ? componentWeb : componentWeapp;
 
-    return {
-      classes,
-      ...toRefs(state),
-      column,
-      columnsType,
-      columnsList,
-      close,
-      changeHandler,
-      confirmHandler,
-      defaultValues,
-      defaultIndexes,
-      translate,
-      pickerColumn,
-      swipeRef,
-      refRandomId,
-      pickerline,
-      tileChange,
-      handlePickstart,
-      handlePickend
-    };
-  }
-});
+export default create(component);
 </script>

+ 192 - 0
src/packages/__VUE/picker/usePicker.ts

@@ -0,0 +1,192 @@
+import { ref, reactive, watch, computed, toRaw, toRefs, PropType } from 'vue';
+interface PickerOption {
+  text: string | number;
+  value: string | number;
+  disabled?: string;
+  children?: PickerOption[];
+  className?: string | number;
+}
+
+export const usePicker = (props: any, emit: any) => {
+  const state = reactive({
+    formattedColumns: props.columns
+  });
+
+  // 选中项
+  let defaultValues = ref<(number | string)[]>(
+    Array.isArray(props.modelValue) && props.modelValue.length > 0 ? props.modelValue : []
+  );
+
+  const pickerColumn = ref<any[]>([]);
+
+  const swipeRef = (el: any) => {
+    if (el && pickerColumn.value.length < columnsList.value.length) {
+      pickerColumn.value.push(el);
+    }
+  };
+
+  const classes = computed(() => {
+    const prefixCls = 'nut-picker';
+    return {
+      [prefixCls]: true
+    };
+  });
+
+  const selectedOptions = computed(() => {
+    let optins: PickerOption[] = [];
+    (columnsList.value as PickerOption[][]).map((column: PickerOption[], index: number) => {
+      let currOptions = [];
+      currOptions = column.filter((item) => item.value == defaultValues.value[index]);
+      optins.push(currOptions[0]);
+    });
+
+    return optins;
+  });
+  // 当前类型
+  const columnsType = computed(() => {
+    const firstColumn: PickerOption | PickerOption[] = state.formattedColumns[0];
+    if (firstColumn) {
+      if (Array.isArray(firstColumn)) {
+        return 'multiple';
+      }
+      if ('children' in firstColumn) {
+        return 'cascade';
+      }
+    }
+    return 'single';
+  });
+  // 将传入的 columns 格式化
+  const columnsList = computed(() => {
+    switch (columnsType.value) {
+      case 'multiple':
+        return state.formattedColumns as PickerOption[][];
+      case 'cascade':
+        // 级联数据处理
+        return formatCascade(state.formattedColumns as PickerOption[], defaultValues.value ? defaultValues.value : []);
+      default:
+        return [state.formattedColumns] as PickerOption[][];
+    }
+  });
+
+  // 级联数据格式化
+  const formatCascade = (columns: PickerOption[], defaultValues: (number | string)[]) => {
+    const formatted: PickerOption[][] = [];
+    let cursor: PickerOption = {
+      text: '',
+      value: '',
+      children: columns
+    };
+
+    let columnIndex = 0;
+
+    while (cursor && cursor.children) {
+      const options: PickerOption[] = cursor.children;
+      const value = defaultValues[columnIndex];
+      let index = options.findIndex((columnItem) => columnItem.value == value);
+      if (index == -1) index = 0;
+      cursor = cursor.children[index];
+
+      columnIndex++;
+      formatted.push(options);
+    }
+
+    return formatted;
+  };
+
+  const close = () => {
+    emit('close', {
+      selectedValue: defaultValues.value,
+      selectedOptions: selectedOptions.value
+    });
+    emit('update:visible', false);
+  };
+
+  const changeHandler = (columnIndex: number, option: PickerOption) => {
+    if (option && Object.keys(option).length) {
+      defaultValues.value = defaultValues.value ? defaultValues.value : [];
+
+      if (columnsType.value === 'cascade') {
+        defaultValues.value[columnIndex] = option.value ? option.value : '';
+        let index = columnIndex;
+        let cursor = option;
+        while (cursor && cursor.children && cursor.children[0]) {
+          defaultValues.value[index + 1] = cursor.children[0].value;
+          index++;
+          cursor = cursor.children[0];
+        }
+
+        // 当前改变列 的 下一列 children 值为空
+        if (cursor && cursor.children && cursor.children.length == 0) {
+          defaultValues.value = defaultValues.value.slice(0, index + 1);
+        }
+      } else {
+        defaultValues.value[columnIndex] = option.hasOwnProperty('value') ? option.value : '';
+      }
+
+      emit('change', {
+        columnIndex: columnIndex,
+        selectedValue: defaultValues.value,
+        selectedOptions: selectedOptions.value
+      });
+    }
+  };
+
+  const confirm = () => {
+    if (defaultValues.value && !defaultValues.value.length) {
+      columnsList.value.forEach((columns) => {
+        defaultValues.value.push(columns[0].value);
+        selectedOptions.value.push(columns[0]);
+      });
+    }
+
+    emit('confirm', {
+      selectedValue: defaultValues.value,
+      selectedOptions: selectedOptions.value
+    });
+    emit('update:visible', false);
+  };
+
+  const isSameValue = (valA: any, valB: any) => JSON.stringify(valA) === JSON.stringify(valB);
+
+  watch(
+    () => props.modelValue,
+    (newValues) => {
+      if (!isSameValue(newValues, defaultValues.value)) {
+        defaultValues.value = newValues;
+      }
+    },
+    { deep: true }
+  );
+
+  watch(
+    defaultValues,
+    (newValues) => {
+      if (!isSameValue(newValues, props.modelValue)) {
+        emit('update:modelValue', newValues);
+      }
+    },
+    { deep: true }
+  );
+
+  watch(
+    () => props.columns,
+    (val) => {
+      if (val.length) state.formattedColumns = val as PickerOption[];
+    }
+  );
+
+  return {
+    classes,
+    ...toRefs(state),
+    columnsType,
+    columnsList,
+    close,
+    changeHandler,
+    confirm,
+    defaultValues,
+    pickerColumn,
+    swipeRef,
+    selectedOptions,
+    isSameValue
+  };
+};

+ 4 - 0
src/sites/mobile-taro/vue/config/index.js

@@ -55,6 +55,10 @@ const config = {
         enable: true,
         config: {}
       },
+      pxtransform: {
+        enable: true,
+        config: { selectorBlackList: ['nut-'] }
+      },
       cssModules: {
         enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
         config: {