Browse Source

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

suzigang 3 years ago
parent
commit
7eccd6e7ff

+ 5 - 0
src/packages/__VUE/address/index.scss

@@ -235,4 +235,9 @@
       color: $white;
     }
   }
+
+  &-select-icon {
+    margin-right: $address-item-margin-right;
+    color: $address-icon-color !important;
+  }
 }

+ 107 - 195
src/packages/__VUE/address/index.taro.vue

@@ -32,26 +32,29 @@
       </view>
 
       <!-- 请选择 -->
-      <view class="custom-address" v-if="privateType == 'custom'">
+      <view class="custom-address" v-if="['custom', 'custom2'].includes(privateType)">
         <view class="nut-address-region-tab" ref="tabRegion">
           <view
-            :class="{ 'tab-item': true, active: index == tabIndex, [tabName[index]]: true }"
-            v-for="(item, key, index) in selectedRegion"
+            :class="['tab-item', index == tabIndex ? 'active' : '']"
+            v-for="(item, index) in selectedRegion"
             :key="index"
-            @click="changeRegionTab(item, key, index)"
+            @click="changeRegionTab(item, index)"
           >
             <view>{{ getTabName(item, index) }} </view>
-
-            <view :class="{ 'region-tab-line-mini': true, active: index == tabIndex }" ref="regionLine"></view>
+            <view :class="{ 'region-tab-line-mini': true, active: index == tabIndex }"></view>
+          </view>
+          <view class="active tab-item" v-if="tabIndex == selectedRegion.length">
+            <view>{{ getTabName(null, selectedRegion.length) }} </view>
+            <view class="region-tab-line-mini active"></view>
           </view>
         </view>
 
-        <view class="region-con">
+        <view class="region-con" v-if="privateType == 'custom'">
           <ul class="region-group">
             <li
-              v-for="(item, index) in regionList[tabName[tabIndex]]"
+              v-for="(item, index) in regionList"
               :key="index"
-              :class="['region-item', selectedRegion[tabName[tabIndex]].id == item.id ? 'active' : '']"
+              :class="['region-item', selectedRegion[tabIndex]?.id == item.id ? 'active' : '']"
               @click="nextAreaList(item)"
             >
               <div>
@@ -61,32 +64,18 @@
                   v-bind="$attrs"
                   :name="selectedIcon"
                   size="13px"
-                  v-if="selectedRegion[tabName[tabIndex]].id == item.id"
+                  v-if="selectedRegion[tabIndex]?.id == item.id"
                 ></nut-icon
                 >{{ item.name }}
               </div>
             </li>
           </ul>
         </view>
-      </view>
 
-      <!-- 请选择 -->
-      <view class="custom-address" v-else-if="privateType == 'custom2'">
-        <view class="nut-address-region-tab" ref="tabRegion">
-          <view
-            :class="{ 'tab-item': true, active: index == tabIndex, [tabName[index]]: true }"
-            v-for="(item, key, index) in selectedRegion"
-            :key="index"
-            @click="changeRegionTab(item, key, index)"
-          >
-            <view>{{ getTabName(item, index) }}</view>
-            <view :class="{ 'region-tab-line-mini': true, active: index == tabIndex }"></view>
-          </view>
-        </view>
-        <view class="elevator-group">
+        <view class="elevator-group" v-else>
           <nut-elevator
             :height="height"
-            :index-list="regionList[tabName[tabIndex]]"
+            :index-list="transformData(regionList)"
             @click-item="handleElevatorItem"
           ></nut-elevator>
         </view>
@@ -135,7 +124,7 @@
   </nut-popup>
 </template>
 <script lang="ts">
-import { reactive, ref, toRefs, watch, computed, onMounted } from 'vue';
+import { reactive, ref, toRefs, watch, computed } from 'vue';
 import { popupProps } from '../popup/props';
 import { createComponent } from '@/packages/utils/create';
 
@@ -251,8 +240,6 @@ export default create({
       };
     });
 
-    const regionLine = ref<null | HTMLElement>(null);
-
     const tabItemRef = reactive({
       province: ref<null | HTMLElement>(null),
       city: ref<null | HTMLElement>(null),
@@ -267,6 +254,19 @@ export default create({
 
     const isCustom2 = computed(() => props.type === 'custom2');
 
+    const regionList = computed(() => {
+      switch (tabIndex.value) {
+        case 0:
+          return props.province;
+        case 1:
+          return props.city;
+        case 2:
+          return props.country;
+        default:
+          return props.town;
+      }
+    });
+
     const transformData = (data: RegionData[]) => {
       if (!Array.isArray(data)) throw new TypeError('params muse be array.');
 
@@ -293,29 +293,14 @@ export default create({
             list: [].concat(item)
           });
         } else {
-          newData[index] = {
-            title: item.title,
-            list: newData[index].list.concat(item)
-          };
+          newData[index].list.push(item);
         }
       });
 
       return newData;
     };
 
-    const regionList = reactive({
-      province: isCustom2.value ? transformData(props.province) : props.province,
-      city: isCustom2.value ? transformData(props.city) : props.city,
-      country: isCustom2.value ? transformData(props.country) : props.country,
-      town: isCustom2.value ? transformData(props.town) : props.town
-    });
-
-    const selectedRegion = reactive({
-      province: {} as RegionData,
-      city: {} as RegionData,
-      country: {} as RegionData,
-      town: {} as RegionData
-    }); //已选择的 省、市、县、镇
+    let selectedRegion = ref<RegionData[]>([]);
 
     let selectedExistAddress = reactive({}); // 当前选择的地址
 
@@ -323,64 +308,45 @@ export default create({
 
     const lineDistance = ref(20);
 
-    onMounted(() => {
-      customPlaceholder();
-    });
-
     // 设置选中省市县
     const initCustomSelected = () => {
-      // console.log(props.modelValue);
-      if (props.modelValue.length > 0) {
-        tabIndex.value = props.modelValue.length - 1;
-        for (let index = 0; index < props.modelValue.length; index++) {
-          if ((regionList as any)[tabName.value[index]].length == 0) {
-            tabIndex.value = index - 1;
-            break;
-          } else {
-            const val = props.modelValue[index];
-            const arr: [] = (regionList as any)[tabName.value[index]];
-            if (privateType.value == 'custom') {
-              (selectedRegion as any)[tabName.value[index]] = arr.filter((item: RegionData) => item.id == val)[0];
-            } else if (privateType.value == 'custom2') {
-              let sumArr: any = [];
-              arr.map((item) => {
-                sumArr.push(...(item as any).list);
-              });
-              (selectedRegion as any)[tabName.value[index]] = sumArr.filter((item: RegionData) => item.id == val)[0];
-            }
-          }
+      const defaultValue = props.modelValue;
+      const num = defaultValue.length;
+      if (num > 0) {
+        tabIndex.value = num - 1;
+        if (regionList.value.length == 0) {
+          tabIndex.value = 0;
+          return;
         }
-      }
-    };
-
-    // 自定义‘请选择’文案
-    const customPlaceholder = () => {
-      let selectStr = translate('select');
-      let typeD = Object.prototype.toString.call(props.columnsPlaceholder || selectStr);
-      if (typeD == '[object String]') {
-        tabNameDefault.value = new Array(4).fill(props.columnsPlaceholder || selectStr);
-      } else if (typeD == '[object Array]') {
-        tabNameDefault.value = new Array(4).fill('');
-        tabNameDefault.value.forEach((val, index) => {
-          if (props.columnsPlaceholder[index]) {
-            tabNameDefault.value[index] = props.columnsPlaceholder[index];
-          } else {
-            tabNameDefault.value[index] = selectStr;
+        for (let index = 0; index < num; index++) {
+          let arr: [] = [];
+          switch (index) {
+            case 0:
+              arr = props.province;
+              break;
+            case 1:
+              arr = props.city;
+              break;
+            case 2:
+              arr = props.country;
+              break;
+            default:
+              arr = props.town;
           }
-        });
+          selectedRegion.value[index] = arr.filter((item: RegionData) => item.id == defaultValue[index])[0];
+        }
       }
     };
 
-    //获取已选地区列表名称
-    const getTabName = (item: RegionData, index: number) => {
-      if (item.name) return item.name;
-
-      if (tabIndex.value < index) {
+    const getTabName = (item: RegionData | null, index: number) => {
+      if (item && item.name) return item.name;
+      if (tabIndex.value < index && item) {
         return item.name;
       } else {
-        return tabNameDefault.value[index];
+        return props.columnsPlaceholder[index] || translate('select');
       }
     };
+
     // 手动关闭 点击叉号(cross),或者蒙层(mask)
     const handClose = (type = 'self') => {
       if (!props.closeBtnIcon) return;
@@ -389,6 +355,7 @@ export default create({
 
       showPopup.value = false;
     };
+
     // 点击遮罩层关闭
     const clickOverlay = () => {
       closeWay.value = 'mask';
@@ -396,36 +363,35 @@ export default create({
 
     // 切换下一级列表
     const nextAreaList = (item: RegionData | string) => {
-      // onchange 接收的参数
-      const calBack = {
-        next: '',
-        value: '',
-        custom: tabName.value[tabIndex.value]
+      const tab = tabIndex.value;
+
+      const callBackParams: {
+        next?: string;
+        value?: RegionData;
+        custom: string;
+      } = {
+        custom: tabName.value[tab]
       };
 
-      (selectedRegion as any)[tabName.value[tabIndex.value]] = item;
+      selectedRegion.value[tab] = item;
 
-      // for (let i = tabIndex.value; i < tabIndex.value - 1; i++) {
-      //   (selectedRegion as any)[tabName.value[i + 1]] = {};
-      // }
-      for (let i = tabIndex.value; i < 4; i++) {
-        (selectedRegion as any)[tabName.value[i + 1]] = {};
+      for (let i = tab + 2; i < 4; i++) {
+        selectedRegion.value.splice(i, 1);
       }
+      if (tab < 3) {
+        tabIndex.value = tab + 1;
 
-      if (tabIndex.value < 3) {
-        tabIndex.value = tabIndex.value + 1;
+        callBackParams.next = tabName.value[tabIndex.value];
+        callBackParams.value = item;
 
-        // 切换下一个
-        calBack.next = tabName.value[tabIndex.value];
-        calBack.value = item as string;
-        emit('change', calBack);
+        emit('change', callBackParams);
       } else {
         handClose();
         emit('update:modelValue');
       }
     };
     //切换地区Tab
-    const changeRegionTab = (item: RegionData, key: number, index: number) => {
+    const changeRegionTab = (item: RegionData, index: number) => {
       if (getTabName(item, index)) {
         tabIndex.value = index;
       }
@@ -436,11 +402,9 @@ export default create({
       const copyExistAdd = props.existAddress as AddressList[];
       let prevExistAdd = {};
 
-      copyExistAdd.forEach((list, index) => {
-        if (list && (list as AddressList).selectedAddress) {
-          prevExistAdd = list;
-        }
-        (list as AddressList).selectedAddress = false;
+      copyExistAdd.forEach((list: AddressList) => {
+        if (list && list.selectedAddress) prevExistAdd = list;
+        list.selectedAddress = false;
       });
 
       item.selectedAddress = true;
@@ -453,67 +417,54 @@ export default create({
     };
     // 初始化
     const initAddress = () => {
-      for (let i = 0; i < tabName.value.length; i++) {
-        (selectedRegion as any)[tabName.value[i]] = {};
-      }
+      selectedRegion.value = [];
       tabIndex.value = 0;
     };
 
     // 关闭
     const close = () => {
-      const resCopy = Object.assign(
-        {
-          addressIdStr: '',
-          addressStr: ''
-        },
-        selectedRegion
-      );
-
-      const res = {
+      const data = {
+        addressIdStr: '',
+        addressStr: '',
+        province: selectedRegion.value[0],
+        city: selectedRegion.value[1],
+        country: selectedRegion.value[2],
+        town: selectedRegion.value[3]
+      };
+
+      const callBackParams = {
         data: {},
         type: privateType.value
       };
 
-      if (privateType.value == 'custom' || privateType.value == 'custom2') {
-        const { province, city, country, town } = resCopy;
-
-        resCopy.addressIdStr = [
-          (province as RegionData).id || 0,
-          (city as RegionData).id || 0,
-          (country as RegionData).id || 0,
-          (town as RegionData).id || 0
-        ].join('_');
-        resCopy.addressStr = [
-          (province as RegionData).name,
-          (city as RegionData).name,
-          (country as RegionData).name,
-          (town as RegionData).name
-        ].join('');
-        res.data = resCopy;
+      if (['custom', 'custom2'].includes(privateType.value)) {
+        [0, 1, 2, 3].forEach((i) => {
+          const item = selectedRegion.value[i];
+          data.addressIdStr += `${i ? '_' : ''}${(item && item.id) || 0}`;
+          data.addressStr += (item && item.name) || '';
+        });
+
+        callBackParams.data = data;
       } else {
-        res.data = selectedExistAddress;
+        callBackParams.data = selectedExistAddress;
       }
 
       initAddress();
 
       if (closeWay.value == 'self') {
-        emit('close', res);
+        emit('close', callBackParams);
       } else {
         emit('close-mask', { closeWay: closeWay });
       }
+
       emit('update:visible', false);
     };
 
     // 选择其他地址
     const switchModule = () => {
-      if (privateType.value == 'exist') {
-        privateType.value = 'custom';
-      } else {
-        privateType.value = 'exist';
-      }
-
+      const type = privateType.value;
+      privateType.value = type == 'exist' ? 'custom' : 'exist';
       initAddress();
-
       emit('switch-module', { type: privateType.value });
     };
 
@@ -531,51 +482,12 @@ export default create({
     watch(
       () => showPopup.value,
       (value) => {
-        if (value == false) {
-          close();
-        } else {
+        if (value) {
           initCustomSelected();
         }
       }
     );
 
-    watch(
-      () => props.province,
-      (value) => {
-        regionList.province = isCustom2.value ? transformData(value) : value;
-      }
-    );
-    watch(
-      () => props.city,
-      (value) => {
-        regionList.city = isCustom2.value ? transformData(value) : value;
-      }
-    );
-    watch(
-      () => props.country,
-      (value) => {
-        regionList.country = isCustom2.value ? transformData(value) : value;
-      }
-    );
-    watch(
-      () => props.town,
-      (value) => {
-        regionList.town = isCustom2.value ? transformData(value) : value;
-      }
-    );
-
-    watch(
-      () => props.existAddress,
-      (value) => {
-        //  existAddress.value = value;
-        value.forEach((item, index) => {
-          if ((item as AddressList).selectedAddress) {
-            selectedExistAddress = item as {};
-          }
-        });
-      }
-    );
-
     return {
       classes,
       showPopup,
@@ -590,7 +502,6 @@ export default create({
       close,
       getTabName,
       nextAreaList,
-      regionLine,
       lineDistance,
       changeRegionTab,
       selectedExist,
@@ -599,7 +510,8 @@ export default create({
       handleElevatorItem,
       ...toRefs(props),
       ...toRefs(tabItemRef),
-      translate
+      translate,
+      transformData
     };
   }
 });

+ 143 - 220
src/packages/__VUE/address/index.vue

@@ -38,12 +38,15 @@
         <view class="nut-address-region-tab" ref="tabRegion">
           <view
             :class="['tab-item', index == tabIndex ? 'active' : '']"
-            v-for="(item, key, index) in selectedRegion"
+            v-for="(item, index) in selectedRegion"
             :key="index"
-            @click="changeRegionTab(item, key, index)"
+            @click="changeRegionTab(item, index)"
           >
             <view>{{ getTabName(item, index) }} </view>
           </view>
+          <view class="active tab-item" v-if="tabIndex == selectedRegion.length">
+            <view>{{ getTabName(null, selectedRegion.length) }} </view>
+          </view>
 
           <view class="region-tab-line" ref="regionLine" :style="{ left: lineDistance + 'px' }"></view>
         </view>
@@ -51,22 +54,18 @@
         <view class="region-con" v-if="privateType == 'custom'">
           <ul class="region-group">
             <li
-              v-for="(item, index) in regionList[tabName[tabIndex]]"
+              v-for="(item, index) in regionList"
               :key="index"
-              class="region-item"
-              :class="[selectedRegion[tabName[tabIndex]].id == item.id ? 'active' : '']"
+              :class="['region-item', selectedRegion[tabIndex]?.id == item.id ? 'active' : '']"
               @click="nextAreaList(item)"
             >
               <div>
-                <nut-icon
-                  class="region-item-icon"
-                  type="self"
+                <component
+                  :is="renderIcon(selectedIcon)"
                   v-bind="$attrs"
-                  :name="selectedIcon"
-                  size="13px"
-                  v-if="selectedRegion[tabName[tabIndex]].id == item.id"
-                ></nut-icon
-                >{{ item.name }}
+                  v-if="selectedRegion[tabIndex]?.id == item.id"
+                ></component>
+                {{ item.name }}
               </div>
             </li>
           </ul>
@@ -75,35 +74,29 @@
         <view class="elevator-group" v-else>
           <nut-elevator
             :height="height"
-            :index-list="regionList[tabName[tabIndex]]"
+            :index-list="transformData(regionList)"
             @click-item="handleElevatorItem"
           ></nut-elevator>
         </view>
       </view>
 
       <!-- 配送至 -->
-      <view class="exist-address" v-else-if="privateType == 'exist'">
+      <view class="exist-address" v-else>
         <div class="exist-address-group">
           <ul class="exist-ul">
             <li
-              class="exist-item"
-              :class="[item.selectedAddress ? 'active' : '']"
+              :class="['exist-item', item.selectedAddress ? 'active' : '']"
               v-for="(item, index) in existAddress"
               :key="index"
               @click="selectedExist(item)"
             >
-              <nut-icon
-                class="exist-item-icon"
-                type="self"
+              <component
+                :is="renderIcon(item.selectedAddress ? selectedIcon : defaultIcon)"
                 v-bind="$attrs"
-                :name="item.selectedAddress ? selectedIcon : defaultIcon"
-                size="13px"
-              ></nut-icon>
+              ></component>
               <div class="exist-item-info">
-                <div class="exist-item-info-top" v-if="item.name && item.phone">
-                  <div class="exist-item-info-name">{{ item.name }}</div>
-                  <div class="exist-item-info-phone">{{ item.phone }}</div>
-                </div>
+                <div class="exist-item-info-name" v-if="item.name">{{ item.name }}</div>
+                <div class="exist-item-info-phone" v-if="item.phone">{{ item.phone }}</div>
                 <div class="exist-item-info-bottom">
                   <view>
                     {{ item.provinceName + item.cityName + item.countyName + item.townName + item.addressDetail }}
@@ -124,13 +117,14 @@
   </nut-popup>
 </template>
 <script lang="ts">
-import { reactive, ref, toRefs, watch, nextTick, computed, Ref, onMounted } from 'vue';
+import { reactive, ref, toRefs, watch, nextTick, computed, Ref, h } from 'vue';
 import { createComponent } from '@/packages/utils/create';
-import { isArray, isString, TypeOfFun } from '@/packages/utils/util';
 import { popupProps } from '../popup/props';
+import Icon from '../icon/index.vue';
 const { componentName, create, translate } = createComponent('address');
 interface RegionData {
   name: string;
+  id: number | string;
   [key: string]: any;
 }
 interface CustomRegionData {
@@ -182,11 +176,11 @@ export default create({
     isShowCustomAddress: {
       type: Boolean,
       default: true
-    }, // 是否显示‘选择其他地区’按钮 type=‘exist’ 生效
+    },
     existAddress: {
       type: Array,
       default: () => []
-    }, // 现存地址列表
+    },
     existAddressTitle: {
       type: String,
       default: ''
@@ -196,22 +190,18 @@ export default create({
       default: ''
     },
     defaultIcon: {
-      // 地址选择列表前 - 默认的图标
       type: String,
       default: 'location2'
     },
     selectedIcon: {
-      // 地址选择列表前 - 选中的图标
       type: String,
       default: 'Check'
     },
     closeBtnIcon: {
-      // 关闭弹框按钮 icon
       type: String,
       default: 'circle-close'
     },
     backBtnIcon: {
-      // 选择其他地址左上角返回 icon
       type: String,
       default: 'left'
     },
@@ -228,19 +218,36 @@ export default create({
 
   setup(props: any, { emit }) {
     const regionLine = ref<null | HTMLElement>(null);
-
     const tabRegion: Ref<any> = ref(null);
-
     const showPopup = ref(props.visible);
     const privateType = ref(props.type);
     const tabIndex = ref(0);
     const tabName = ref(['province', 'city', 'country', 'town']);
-    const tabNameDefault = ref(new Array(4).fill(translate('select')));
 
-    const isCustom2 = computed(() => props.type === 'custom2');
+    const regionList = computed(() => {
+      switch (tabIndex.value) {
+        case 0:
+          return props.province;
+        case 1:
+          return props.city;
+        case 2:
+          return props.country;
+        default:
+          return props.town;
+      }
+    });
+
+    const renderIcon = (n: string) => {
+      return h(Icon, {
+        class: `${componentName}-select-icon`,
+        type: 'self',
+        size: '13px',
+        name: n
+      });
+    };
 
     const transformData = (data: RegionData[]) => {
-      if (!isArray(data)) throw new TypeError('params muse be array.');
+      if (!Array.isArray(data)) throw new TypeError('params muse be array.');
 
       if (!data.length) return [];
 
@@ -253,9 +260,7 @@ export default create({
 
       const newData: CustomRegionData[] = [];
 
-      data = data.sort((a: RegionData, b: RegionData) => {
-        return a.title.localeCompare(b.title);
-      });
+      data = data.sort((a: RegionData, b: RegionData) => a.title.localeCompare(b.title));
 
       data.forEach((item: RegionData) => {
         const index = newData.findIndex((value: CustomRegionData) => value.title === item.title);
@@ -265,29 +270,15 @@ export default create({
             list: [].concat(item)
           });
         } else {
-          newData[index] = {
-            title: item.title,
-            list: newData[index].list.concat(item)
-          };
+          newData[index].list.push(item);
         }
       });
 
       return newData;
     };
 
-    const regionList = reactive({
-      province: isCustom2.value ? transformData(props.province) : props.province,
-      city: isCustom2.value ? transformData(props.city) : props.city,
-      country: isCustom2.value ? transformData(props.country) : props.country,
-      town: isCustom2.value ? transformData(props.town) : props.town
-    });
-
-    const selectedRegion = reactive({
-      province: {} as RegionData,
-      city: {} as RegionData,
-      country: {} as RegionData,
-      town: {} as RegionData
-    }); //已选择的 省、市、县、镇
+    //已选择的 省、市、县、镇
+    let selectedRegion = ref<RegionData[]>([]);
 
     let selectedExistAddress = reactive({}); // 当前选择的地址
 
@@ -297,123 +288,98 @@ export default create({
 
     // 设置选中省市县
     const initCustomSelected = () => {
-      if (props.modelValue.length > 0) {
-        tabIndex.value = props.modelValue.length - 1;
-        for (let index = 0; index < props.modelValue.length; index++) {
-          if ((regionList as any)[tabName.value[index]].length == 0) {
-            tabIndex.value = index - 1;
-            break;
-          } else {
-            const val = props.modelValue[index];
-            const arr: [] = (regionList as any)[tabName.value[index]];
-            if (privateType.value == 'custom') {
-              (selectedRegion as any)[tabName.value[index]] = arr.filter((item: RegionData) => item.id == val)[0];
-            } else if (privateType.value == 'custom2') {
-              let sumArr: any = [];
-              arr.map((item) => {
-                sumArr.push(...(item as any).list);
-              });
-              (selectedRegion as any)[tabName.value[index]] = sumArr.filter((item: RegionData) => item.id == val)[0];
-            }
+      const defaultValue = props.modelValue;
+      const num = defaultValue.length;
+      if (num > 0) {
+        tabIndex.value = num - 1;
+        if (regionList.value.length == 0) {
+          tabIndex.value = 0;
+          return;
+        }
+        for (let index = 0; index < num; index++) {
+          let arr: [] = [];
+          switch (index) {
+            case 0:
+              arr = props.province;
+              break;
+            case 1:
+              arr = props.city;
+              break;
+            case 2:
+              arr = props.country;
+              break;
+            default:
+              arr = props.town;
           }
+          selectedRegion.value[index] = arr.filter((item: RegionData) => item.id == defaultValue[index])[0];
         }
         lineAnimation();
       }
     };
-    // 自定义‘请选择’文案
-    const customPlaceholder = () => {
-      const { columnsPlaceholder } = props;
-      if (!columnsPlaceholder) return;
-      if (isString(columnsPlaceholder)) {
-        tabNameDefault.value = new Array(4).fill(columnsPlaceholder);
-      } else if (isArray(columnsPlaceholder) && columnsPlaceholder.length < 5) {
-        columnsPlaceholder.forEach((val, index) => {
-          tabNameDefault.value[index] = val;
-        });
-      }
-    };
-
-    //获取已选地区列表名称
-    const getTabName = (item: RegionData, index: number) => {
-      if (item.name) return item.name;
-
-      if (tabIndex.value < index) {
+    const getTabName = (item: RegionData | null, index: number) => {
+      if (item && item.name) return item.name;
+      if (tabIndex.value < index && item) {
         return item.name;
       } else {
-        return tabNameDefault.value[index];
+        return props.columnsPlaceholder[index] || translate('select');
       }
     };
-    // 手动关闭 点击叉号(cross),或者蒙层(mask)
-    const handClose = (type = 'self') => {
-      if (!props.closeBtnIcon) return;
-
-      closeWay.value = type == 'cross' ? 'cross' : 'self';
 
-      showPopup.value = false;
-    };
-    // 点击遮罩层关闭
-    const clickOverlay = () => {
-      closeWay.value = 'mask';
-    };
-    // 移动下面的红线
     const lineAnimation = () => {
       nextTick(() => {
         const name = tabRegion.value && tabRegion.value.getElementsByClassName('active')[0];
-
         if (name) {
           const distance = name.offsetLeft;
-
           lineDistance.value = distance ? distance : 20;
         }
       });
     };
-    // 切换下一级列表
-    const nextAreaList = (item: RegionData | string) => {
-      // onchange 接收的参数
-      const calBack = {
-        next: '',
-        value: '',
-        custom: tabName.value[tabIndex.value]
+
+    const nextAreaList = (item: RegionData) => {
+      const tab = tabIndex.value;
+
+      const callBackParams: {
+        next?: string;
+        value?: RegionData;
+        custom: string;
+      } = {
+        custom: tabName.value[tab]
       };
 
-      (selectedRegion as any)[tabName.value[tabIndex.value]] = item;
+      selectedRegion.value[tab] = item;
 
-      for (let i = tabIndex.value; i < 4; i++) {
-        (selectedRegion as any)[tabName.value[i + 1]] = {};
+      for (let i = tab + 2; i < 4; i++) {
+        selectedRegion.value.splice(i, 1);
       }
-
-      if (tabIndex.value < 3) {
-        tabIndex.value = tabIndex.value + 1;
+      if (tab < 3) {
+        tabIndex.value = tab + 1;
 
         lineAnimation();
 
-        // 切换下一个
-        calBack.next = tabName.value[tabIndex.value];
-        calBack.value = item as string;
-        emit('change', calBack);
+        callBackParams.next = tabName.value[tabIndex.value];
+        callBackParams.value = item;
+
+        emit('change', callBackParams);
       } else {
         handClose();
         emit('update:modelValue');
       }
     };
-    //切换地区Tab
-    const changeRegionTab = (item: RegionData, key: number, index: number) => {
+
+    const changeRegionTab = (item: RegionData, index: number) => {
       if (getTabName(item, index)) {
         tabIndex.value = index;
         lineAnimation();
       }
     };
 
-    // 选择现有地址
     const selectedExist = (item: RegionData) => {
       const copyExistAdd = props.existAddress as AddressList[];
       let prevExistAdd = {};
 
-      copyExistAdd.forEach((list, index) => {
-        if (list && (list as AddressList).selectedAddress) {
-          prevExistAdd = list;
-        }
-        (list as AddressList).selectedAddress = false;
+      copyExistAdd.forEach((list: AddressList) => {
+        if (list && list.selectedAddress) prevExistAdd = list;
+        list.selectedAddress = false;
       });
 
       item.selectedAddress = true;
@@ -424,81 +390,74 @@ export default create({
 
       handClose();
     };
-    // 初始化
+
     const initAddress = () => {
-      for (let i = 0; i < tabName.value.length; i++) {
-        (selectedRegion as any)[tabName.value[i]] = {};
-      }
+      selectedRegion.value = [];
       tabIndex.value = 0;
       lineAnimation();
     };
 
-    // 关闭
+    // 手动关闭 点击叉号(cross),或者蒙层(mask)
+    const handClose = (type = 'self') => {
+      if (!props.closeBtnIcon) return;
+      closeWay.value = type == 'cross' ? 'cross' : 'self';
+      showPopup.value = false;
+    };
+
+    const clickOverlay = () => {
+      closeWay.value = 'mask';
+    };
+
     const close = () => {
-      const resCopy = Object.assign(
-        {
-          addressIdStr: '',
-          addressStr: ''
-        },
-        selectedRegion
-      );
-
-      const res = {
+      const data = {
+        addressIdStr: '',
+        addressStr: '',
+        province: selectedRegion.value[0],
+        city: selectedRegion.value[1],
+        country: selectedRegion.value[2],
+        town: selectedRegion.value[3]
+      };
+
+      const callBackParams = {
         data: {},
         type: privateType.value
       };
 
-      if (privateType.value == 'custom' || privateType.value == 'custom2') {
-        const { province, city, country, town } = resCopy;
-
-        resCopy.addressIdStr = [
-          (province as RegionData).id || 0,
-          (city as RegionData).id || 0,
-          (country as RegionData).id || 0,
-          (town as RegionData).id || 0
-        ].join('_');
-        resCopy.addressStr = [
-          (province as RegionData).name,
-          (city as RegionData).name,
-          (country as RegionData).name,
-          (town as RegionData).name
-        ].join('');
-        res.data = resCopy;
+      if (['custom', 'custom2'].includes(privateType.value)) {
+        [0, 1, 2, 3].forEach((i) => {
+          const item = selectedRegion.value[i];
+          data.addressIdStr += `${i ? '_' : ''}${(item && item.id) || 0}`;
+          data.addressStr += (item && item.name) || '';
+        });
+
+        callBackParams.data = data;
       } else {
-        res.data = selectedExistAddress;
+        callBackParams.data = selectedExistAddress;
       }
 
       initAddress();
 
       if (closeWay.value == 'self') {
-        emit('close', res);
+        emit('close', callBackParams);
       } else {
         emit('close-mask', { closeWay: closeWay });
       }
+
       emit('update:visible', false);
     };
 
     // 选择其他地址
     const switchModule = () => {
-      if (privateType.value == 'exist') {
-        privateType.value = 'custom';
-      } else {
-        privateType.value = 'exist';
-      }
-
+      const type = privateType.value;
+      privateType.value = type == 'exist' ? 'custom' : 'exist';
       initAddress();
-
       emit('switch-module', { type: privateType.value });
     };
 
-    const handleElevatorItem = (key: string, item: RegionData | string) => {
+    const handleElevatorItem = (key: string, item: RegionData) => {
       nextAreaList(item);
     };
 
-    const updateRegion = (type: string, value: any) => {
-      regionList[type] = isCustom2.value ? transformData(value) : value;
-    };
-
     watch(
       () => props.visible,
       (value) => {
@@ -510,56 +469,17 @@ export default create({
       () => showPopup.value,
       (value) => {
         if (value) {
-          customPlaceholder();
           initCustomSelected();
         }
       }
     );
 
-    watch(
-      () => props.province,
-      (value) => {
-        updateRegion('province', value);
-      }
-    );
-    watch(
-      () => props.city,
-      (value) => {
-        updateRegion('city', value);
-      }
-    );
-    watch(
-      () => props.country,
-      (value) => {
-        updateRegion('country', value);
-      }
-    );
-    watch(
-      () => props.town,
-      (value) => {
-        updateRegion('town', value);
-      }
-    );
-
-    watch(
-      () => props.existAddress,
-      (value) => {
-        value.forEach((item) => {
-          if ((item as AddressList).selectedAddress) {
-            selectedExistAddress = item as {};
-          }
-        });
-      }
-    );
-
     return {
       showPopup,
       privateType,
       tabIndex,
       tabName,
-      regionList,
       selectedRegion,
-      selectedExistAddress,
       switchModule,
       closeWay,
       close,
@@ -575,7 +495,10 @@ export default create({
       handleElevatorItem,
       initCustomSelected,
       ...toRefs(props),
-      translate
+      translate,
+      regionList,
+      transformData,
+      renderIcon
     };
   }
 });

+ 1 - 1
src/packages/__VUE/countdown/__tests__/countdown.spec.ts

@@ -20,7 +20,7 @@ test('endTime props', async () => {
     }
   });
   expect(wrapper.emitted('on-end')).toBeFalsy();
-  await sleep(1000);
+  await sleep(1100);
   expect(wrapper.emitted('on-end')).toBeTruthy();
 });
 

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

@@ -5,6 +5,7 @@ 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 NutOverlay from '../../overlay/index.vue';
 import DatePicker from '../../datepicker/index.vue';
 
 function sleep(delay = 0): Promise<void> {
@@ -19,7 +20,8 @@ beforeAll(() => {
     NutRange,
     NutPicker,
     NutPopup,
-    NutPickerColumn
+    NutPickerColumn,
+    NutOverlay
   };
 });
 
@@ -38,8 +40,8 @@ test('Do not display Chinese', async () => {
     }
   });
   await nextTick();
-  expect(wrapper.find('.nut-picker__confirm').exists()).toBeTruthy();
-  const confirm = wrapper.find('.nut-picker__confirm');
+  expect(wrapper.find('.nut-picker__right').exists()).toBeTruthy();
+  const confirm = wrapper.find('.nut-picker__right');
   confirm.trigger('click');
   expect(wrapper.emitted().confirm[0]).toEqual([
     {

+ 3 - 3
src/packages/__VUE/dialog/__tests__/function.spec.ts

@@ -115,13 +115,13 @@ describe('function Dialog', () => {
       cancelText: '取消文案自定义',
       okText: '确定文案自定义'
     });
-    await sleep(500);
+    await sleep(1000);
     let cancelText = document.querySelector(
-      `#dialog-${dialog.options.id} .nut-dialog .nut-dialog__footer-cancel > view > view`
+      `#dialog-${dialog.options.id} .nut-dialog .nut-dialog__footer-cancel `
     ) as HTMLDivElement;
     expect(cancelText.innerHTML).toEqual('取消文案自定义');
     let okText = document.querySelector(
-      `#dialog-${dialog.options.id} .nut-dialog .nut-dialog__footer-ok > view > view`
+      `#dialog-${dialog.options.id} .nut-dialog .nut-dialog__footer-ok`
     ) as HTMLDivElement;
     expect(okText.innerHTML).toEqual('确定文案自定义');
   });

+ 48 - 38
src/packages/__VUE/form/common.ts

@@ -1,5 +1,5 @@
 import { getPropByPath, isObject, isPromise } from '@/packages/utils/util';
-import { computed, provide, reactive, VNode, watch } from 'vue';
+import { computed, PropType, provide, reactive, VNode, watch } from 'vue';
 import { FormItemRule } from '../formitem/types';
 import { ErrorMessage, FormRule, FormRules } from './types';
 
@@ -10,7 +10,7 @@ export const component = {
       default: {}
     },
     rules: {
-      type: Object,
+      type: Object as PropType<import('./types').FormRules>,
       default: {}
     }
   },
@@ -20,7 +20,7 @@ export const component = {
   setup(props: any, { emit, slots }: any) {
     const formErrorTip = computed(() => reactive<any>({}));
     provide('formErrorTip', formErrorTip);
-    const clearErrorTips = (value = props.modelValue) => {
+    const clearErrorTips = () => {
       Object.keys(formErrorTip.value).forEach((item) => {
         formErrorTip.value[item] = '';
       });
@@ -32,15 +32,15 @@ export const component = {
 
     watch(
       () => props.modelValue,
-      (value: any) => {
-        clearErrorTips(value);
+      () => {
+        clearErrorTips();
       },
       { immediate: true }
     );
 
     const findFormItem = (vnodes: VNode[]) => {
       let task: FormRule[] = [];
-      vnodes.forEach((vnode: VNode, index: number) => {
+      vnodes.forEach((vnode: VNode) => {
         let type = vnode.type;
         type = (type as any).name || type;
         if (type == 'nut-form-item' || type?.toString().endsWith('form-item')) {
@@ -73,8 +73,12 @@ export const component = {
 
       const _Promise = (errorMsg: ErrorMessage): Promise<ErrorMessage> => {
         return new Promise((resolve, reject) => {
-          tipMessage(errorMsg);
-          resolve(errorMsg);
+          try {
+            tipMessage(errorMsg);
+            resolve(errorMsg);
+          } catch (error) {
+            reject(error);
+          }
         });
       };
 
@@ -94,7 +98,7 @@ export const component = {
         const { required, regex, message } = ruleWithoutValidator;
         const errorMsg = { prop, message };
         if (required) {
-          if (value === '' || value === undefined || value === null) {
+          if (!value) {
             return _Promise(errorMsg);
           }
         }
@@ -105,14 +109,16 @@ export const component = {
           const result = validator(value, ruleWithoutValidator);
           if (isPromise(result)) {
             return new Promise((r, j) => {
-              result.then((res) => {
-                if (!res) {
-                  tipMessage(errorMsg);
-                  r(errorMsg);
-                } else {
-                  r(true);
-                }
-              });
+              result
+                .then((res) => {
+                  if (!res) {
+                    tipMessage(errorMsg);
+                    r(errorMsg);
+                  } else {
+                    r(true);
+                  }
+                })
+                .catch((e) => j(e));
             });
           } else {
             if (!result) {
@@ -131,35 +137,39 @@ export const component = {
      */
     const validate = (customProp = '') => {
       return new Promise((resolve, reject) => {
-        const task = findFormItem(slots.default());
+        try {
+          const task = findFormItem(slots.default());
 
-        const errors = task.map((item) => {
-          if (customProp) {
-            if (customProp == item.prop) {
-              return checkRule(item);
+          const errors = task.map((item) => {
+            if (customProp) {
+              if (customProp == item.prop) {
+                return checkRule(item);
+              } else {
+                return Promise.resolve(true);
+              }
             } else {
-              return Promise.resolve(true);
+              return checkRule(item);
             }
-          } else {
-            return checkRule(item);
-          }
-        });
+          });
 
-        Promise.all(errors).then((errorRes) => {
-          errorRes = errorRes.filter((item) => item != true);
-          const res = { valid: true, errors: [] };
-          if (errorRes.length) {
-            res.valid = false;
-            res.errors = errorRes as any;
-          }
-          resolve(res);
-        });
+          Promise.all(errors).then((errorRes) => {
+            errorRes = errorRes.filter((item) => item != true);
+            const res = { valid: true, errors: [] };
+            if (errorRes.length) {
+              res.valid = false;
+              res.errors = errorRes as any;
+            }
+            resolve(res);
+          });
+        } catch (error) {
+          reject(error);
+        }
       });
     };
-    const onSubmit = () => {
+    const submit = () => {
       validate();
       return false;
     };
-    return { validate, reset, onSubmit, formErrorTip };
+    return { validate, reset, submit, formErrorTip };
   }
 };

+ 1 - 0
src/packages/__VUE/form/demo.vue

@@ -295,6 +295,7 @@ export default createDemo({
             if (valid) {
               console.log('success', dynamicForm);
             } else {
+              Toast.warn(errors[0].message);
               console.log('error submit!!', errors);
             }
           });

+ 8 - 3
src/packages/__VUE/form/doc.en-US.md

@@ -75,6 +75,7 @@ app.use(CellGroup);
 </template>
 <script lang="ts">
 import { ref,reactive } from 'vue';
+import { Toast } from '@nutui/nutui';
 export default {
   setup(){
     const dynamicRefForm = ref<any>(null);
@@ -93,6 +94,7 @@ export default {
             if (valid) {
               console.log('success', dynamicForm);
             } else {
+              Toast.warn(errors[0].message);
               console.log('error submit!!', errors);
             }
           });
@@ -163,6 +165,7 @@ export default {
 </template>
 <script lang="ts">
 import { ref,reactive } from 'vue';
+import { Toast } from '@nutui/nutui';
 export default {
 setup(){
     const formData = reactive({
@@ -346,6 +349,7 @@ setup(){
 | Attribute   | Description                                              | Type   | Default |
 |-------------|----------------------------------------------------------|--------|---------|
 | model-value | Form data object (required when using form verification) | object |         |
+| rules`v3.2.3` | Unified configuration FormItem attr rules  | { prop: FormItemRule[] } |  {}      |
 
 ### Form Events
 
@@ -359,6 +363,7 @@ setup(){
 |---------------------|---------------------------------------------------------------------------------------------|------------------|---------|
 | required            | Whether to display the red asterisk next to the label of the required field                 | boolean          | `false` |
 | prop                | The v-model field of the form field is required when the form verification function is used | string           | -       |
+| rules               | Define validation rules                                                                     | FormItemRule []  | []      |
 | label-width         | The width of the form item label. The default unit is `px`                                  | number \| string | `90px`  |
 | label-align         | Form item label alignment. The optional values are `center` `right`                         | string           | `left`  |
 | body-align          | Default Solt box alignment. The optional values are `center` `right`                               | string           | `left`  |
@@ -366,7 +371,7 @@ setup(){
 | show-error-line     | Whether to mark the input box in red when the verification fails                            | boolean          | `true`  |
 | show-error-message  | Whether to display the error prompt under the input box when the verification fails         | boolean          | `true`  |
 
-### FormItem Rule data structure
+### FormItemRule data structure
 
 Use the `rules` attribute of FormItem to define verification rules. The optional attributes are as follows:
 
@@ -374,7 +379,7 @@ Use the `rules` attribute of FormItem to define verification rules. The optional
 |-----------|------------------------------------|-----------------------------------------|
 | required  | Is it a required field             | boolean                                 |
 | message   | Error prompt copy                  | string                                  |
-| validator | Verification by function           | (value) => boolean \| string \| Promise |
+| validator | Verification by function           | (value:string,rule?:FormItemRule) => boolean \| string \| Promise |
 | regex     | Verification by regular expression | RegExp                                  |
 
 ### FormItem Slots
@@ -398,6 +403,6 @@ Use [ref](https://vuejs.org/guide/essentials/template-refs.html#template-refs) t
 
 | Name              | Description                                                                                                       | Arguments                   | Return value |
 |-------------------|-------------------------------------------------------------------------------------------------------------------|-----------------------------|--------------|
-| submit            | Method of submitting form for verification                                                                        | -                           | Promise      |
+| submit`v3.2.8`            | Method of submitting form for verification                                                                        | -                           | -      |
 | reset             | Clear verification results                                                                                        | -                           | -            |
 | validate`v3.1.13` | Active trigger verification is used to trigger when the user customizes the scene, such as blur and change events | Same as FormItem prop value | -            |

+ 9 - 4
src/packages/__VUE/form/doc.md

@@ -75,6 +75,7 @@ app.use(CellGroup);
 </template>
 <script lang="ts">
 import { ref,reactive } from 'vue';
+import { Toast } from '@nutui/nutui';
 export default {
   setup(){
     const dynamicRefForm = ref<any>(null);
@@ -93,6 +94,7 @@ export default {
             if (valid) {
               console.log('success', dynamicForm);
             } else {
+              Toast.warn(errors[0].message);
               console.log('error submit!!', errors);
             }
           });
@@ -163,6 +165,7 @@ export default {
 </template>
 <script lang="ts">
 import { ref,reactive } from 'vue';
+import { Toast } from '@nutui/nutui';
 export default {
 setup(){
     const formData = reactive({
@@ -346,6 +349,7 @@ setup(){
 | 参数        | 说明                                 | 类型   | 默认值 |
 |-------------|--------------------------------------|--------|--------|
 | model-value | 表单数据对象(使用表单校验时,_必填_) | object |        |
+| rules`v3.2.3` | 统一配置每个 FormItem 的 rules  | { prop: FormItemRule[] } |  {}      |
 
 ### Form Events
 
@@ -359,6 +363,7 @@ setup(){
 |---------------------|------------------------------------------------------------------|------------------|---------|
 | required            | 是否显示必填字段的标签旁边的红色星号                             | boolean          | `false` |
 | prop                | 表单域 v-model 字段, 在使用表单校验功能的情况下,该属性是必填的 | string           | -       |
+| rules               | 定义校验规则                                  | FormItemRule []          | [] |
 | label-width         | 表单项 label 宽度,默认单位为`px`                                | number \| string | `90px`  |
 | label-align         | 表单项 label 对齐方式,可选值为 `center` `right`                 | string           | `left`  |
 | body-align          | 右侧插槽对齐方式,可选值为 `center` `right`                        | string           | `left`  |
@@ -366,7 +371,7 @@ setup(){
 | show-error-line     | 是否在校验不通过时标红输入框                                     | boolean          | `true`  |
 | show-error-message  | 是否在校验不通过时在输入框下方展示错误提示                       | boolean          | `true`  |
 
-### FormItem Rule 数据结构
+### FormItemRule 数据结构
 
 使用 FormItem 的`rules`属性可以定义校验规则,可选属性如下:
 
@@ -374,7 +379,7 @@ setup(){
 |-----------|------------------------|-----------------------------------------|
 | required  | 是否为必选字段         | boolean                                 |
 | message   | 错误提示文案           | string                                  |
-| validator | 通过函数进行校验       | (value) => boolean \| string \| Promise |
+| validator | 通过函数进行校验       | (value:string, rule?:FormItemRule ) => boolean \| string \| Promise |
 | regex     | 通过正则表达式进行校验 | RegExp                                  |
 
 ### FormItem Slots
@@ -398,6 +403,6 @@ setup(){
 
 | 方法名            | 说明                                                               | 参数                | 返回值  |
 |-------------------|--------------------------------------------------------------------|---------------------|---------|
-| submit            | 提交表单进行校验的方法                                             | -                   | Promise |
+| submit`v3.2.8`    | 提交表单进行校验的方法                                               | -                   | - |
 | reset             | 清空校验结果                                                       | -                   | -       |
-| validate`v3.1.13` | 用户主动触发校验,用于用户自定义场景时触发,例如 blur、change 事件 | 同 FormItem prop 值 | -       |
+| validate`v3.1.13` | 用户主动触发校验,用于用户自定义场景时触发,例如 blur、change 事件 | 同 FormItem prop 值,不传值会校验全部 Rule | -       |

+ 0 - 97
src/packages/__VUE/imagepreview/__tests__/__snapshots__/imagepreview.spec.ts.snap

@@ -1,97 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`init page No. 1`] = `"<view class=\\"nut-imagepreview-index\\">4 / 4</view>"`;
-
-exports[`video surported in H5 env 1`] = `
-"<view class=\\"nut-popup popup-center custom-pop\\" style=\\"z-index: 2005; transition-duration: 0.3s; width: 100%;\\">
-  <!-- @click.stop=\\"closeOnImg\\" @touchstart.capture=\\"onTouchStart\\" -->
-  <view class=\\"nut-imagepreview\\">
-    <view class=\\"nut-swiper nut-imagepreview-swiper\\">
-      <view class=\\"nut-swiper-inner\\">
-        <view class=\\"nut-swiper-item\\">
-          <!--v-if-->
-          <view class=\\"nut-imagepreview-box\\">
-            <div class=\\"nut-video\\"><video class=\\"nut-video-player\\" controls=\\"\\">
-                <source src=\\"https://storage.jd.com/about/big-final.mp4?Expires=3730193075&amp;AccessKey=3LoYX1dQWa6ZXzQl&amp;Signature=ViMFjz%2BOkBxS%2FY1rjtUVqbopbJI%3D\\" type=\\"video/mp4\\">
-              </video>
-              <!--v-if-->
-              <!--v-if-->
-              <div class=\\"nut-video-controller show-control\\" style=\\"display: none;\\">
-                <div class=\\"control-play-btn\\"></div>
-                <div class=\\"current-time\\">00:00</div>
-                <div class=\\"progress-container\\">
-                  <div class=\\"progress\\">
-                    <div class=\\"buffered\\" style=\\"width: 0%;\\"></div>
-                    <div class=\\"video-ball\\" style=\\"transform: translate3d(0px, -50%, 0);\\">
-                      <div class=\\"move-handle\\"></div>
-                    </div>
-                    <div class=\\"played\\"></div>
-                  </div>
-                </div>
-                <div class=\\"duration-time\\">00:00</div>
-                <div class=\\"volume muted\\"></div>
-                <div class=\\"fullscreen-icon\\"></div>
-              </div><!-- 错误弹窗 -->
-              <div class=\\"nut-video-error\\" style=\\"display: none;\\">
-                <p class=\\"lose\\">视频加载失败</p>
-                <p class=\\"retry\\">点击重试</p>
-              </div>
-            </div>
-          </view>
-        </view>
-        <view class=\\"nut-swiper-item\\">
-          <!--v-if-->
-          <view class=\\"nut-imagepreview-box\\">
-            <div class=\\"nut-video\\"><video class=\\"nut-video-player\\" controls=\\"\\">
-                <source src=\\"https://storage.jd.com/about/big-final.mp4?Expires=3730193075&amp;AccessKey=3LoYX1dQWa6ZXzQl&amp;Signature=ViMFjz%2BOkBxS%2FY1rjtUVqbopbJI%3D\\" type=\\"video/mp4\\">
-              </video>
-              <!--v-if-->
-              <!--v-if-->
-              <div class=\\"nut-video-controller show-control\\" style=\\"display: none;\\">
-                <div class=\\"control-play-btn\\"></div>
-                <div class=\\"current-time\\">00:00</div>
-                <div class=\\"progress-container\\">
-                  <div class=\\"progress\\">
-                    <div class=\\"buffered\\" style=\\"width: 0%;\\"></div>
-                    <div class=\\"video-ball\\" style=\\"transform: translate3d(0px, -50%, 0);\\">
-                      <div class=\\"move-handle\\"></div>
-                    </div>
-                    <div class=\\"played\\"></div>
-                  </div>
-                </div>
-                <div class=\\"duration-time\\">00:00</div>
-                <div class=\\"volume muted\\"></div>
-                <div class=\\"fullscreen-icon\\"></div>
-              </div><!-- 错误弹窗 -->
-              <div class=\\"nut-video-error\\" style=\\"display: none;\\">
-                <p class=\\"lose\\">视频加载失败</p>
-                <p class=\\"retry\\">点击重试</p>
-              </div>
-            </div>
-          </view>
-        </view>
-        <view class=\\"nut-swiper-item\\">
-          <view style=\\"transition-duration: .3s;\\" class=\\"nut-imagepreview-box\\"><img src=\\"//m.360buyimg.com/mobilecms/s750x366_jfs/t1/18629/34/3378/144318/5c263f64Ef0e2bff0/0d650e0aa2e852ee.jpg\\" class=\\"nut-imagepreview-img\\"></view>
-          <!--v-if-->
-        </view>
-        <view class=\\"nut-swiper-item\\">
-          <view style=\\"transition-duration: .3s;\\" class=\\"nut-imagepreview-box\\"><img src=\\"//m.360buyimg.com/mobilecms/s750x366_jfs/t1/26597/30/4870/174583/5c35c5d2Ed55eedc6/50e27870c25e7a82.png\\" class=\\"nut-imagepreview-img\\"></view>
-          <!--v-if-->
-        </view>
-        <view class=\\"nut-swiper-item\\">
-          <view style=\\"transition-duration: .3s;\\" class=\\"nut-imagepreview-box\\"><img src=\\"//m.360buyimg.com/mobilecms/s750x366_jfs/t1/9542/17/12873/201687/5c3c4362Ea9eb757d/60026b40a9d60d85.jpg\\" class=\\"nut-imagepreview-img\\"></view>
-          <!--v-if-->
-        </view>
-        <view class=\\"nut-swiper-item\\">
-          <view style=\\"transition-duration: .3s;\\" class=\\"nut-imagepreview-box\\"><img src=\\"//m.360buyimg.com/mobilecms/s750x366_jfs/t1/30042/36/427/82951/5c3bfdabE3faf2f66/9adca782661c988c.jpg\\" class=\\"nut-imagepreview-img\\"></view>
-          <!--v-if-->
-        </view>
-      </view>
-      <!--v-if-->
-    </view>
-  </view>
-  <view class=\\"nut-imagepreview-index\\">1 / 6</view>
-  <!--v-if-->
-  <!--v-if-->
-</view>"
-`;

+ 29 - 30
src/packages/__VUE/imagepreview/__tests__/imagepreview.spec.ts

@@ -1,7 +1,27 @@
-import { mount } from '@vue/test-utils';
+import { mount, config } from '@vue/test-utils';
 import ImagePreview from '../index.vue';
 import { nextTick, reactive, toRefs } from 'vue';
-// import { trigger } from '@/packages/utils/unit';
+import NutOverlay from '../../overlay/index.vue';
+import NutIcon from '../../icon/index.vue';
+import NutPopup from '../../popup/index.vue';
+import NutSwiper from '../../swiper/index.vue';
+import NutSwiperItem from '../../swiperitem/index.vue';
+import NutVideo from '../../video/index.vue';
+
+beforeAll(() => {
+  config.global.components = {
+    NutOverlay,
+    NutIcon,
+    NutPopup,
+    NutSwiper,
+    NutSwiperItem,
+    NutVideo
+  };
+});
+
+afterAll(() => {
+  config.global.components = {};
+});
 
 const images = [
   {
@@ -25,18 +45,9 @@ const videos = [
       type: 'video/mp4'
     },
     options: {
-      muted: true,
-      controls: true
-    }
-  },
-  {
-    source: {
-      src: 'https://storage.jd.com/about/big-final.mp4?Expires=3730193075&AccessKey=3LoYX1dQWa6ZXzQl&Signature=ViMFjz%2BOkBxS%2FY1rjtUVqbopbJI%3D',
-      type: 'video/mp4'
-    },
-    options: {
-      muted: true,
-      controls: true
+      autoplay: false,
+      muted: false,
+      controls: false
     }
   }
 ];
@@ -49,7 +60,7 @@ test('basic usage test', async () => {
     }
   });
   await nextTick();
-  expect((wrapper.find('.custom-pop').element as any).style.display).toEqual('');
+  expect((wrapper.find('.nut-imagepreview-custom-pop').element as any).style.display).toEqual('');
 });
 
 test('test autoplay', async () => {
@@ -63,10 +74,10 @@ test('test autoplay', async () => {
 
   await nextTick();
 
-  expect(wrapper.vm.active).toBe(1);
+  expect(wrapper.vm.active).toBe(0);
 
   setTimeout(() => {
-    expect(wrapper.vm.active).toBe(2);
+    expect(wrapper.vm.active).toBe(1);
   }, 3000);
 });
 
@@ -79,7 +90,7 @@ test('init page No.', async () => {
     }
   });
   await nextTick();
-  expect(wrapper.find('.nut-imagepreview-index').html()).toMatchSnapshot();
+  expect(wrapper.find('.nut-imagepreview-index').text()).toEqual('4 / 4');
 });
 
 test('customize pagination and color', async () => {
@@ -97,18 +108,6 @@ test('customize pagination and color', async () => {
   expect(swiperPagination.findAll('i')[0].element.style.backgroundColor).toEqual('red');
 });
 
-test('video surported in H5 env', async () => {
-  const wrapper = mount(ImagePreview, {
-    props: {
-      show: true,
-      images,
-      videos
-    }
-  });
-  await nextTick();
-  expect(wrapper.find('.custom-pop').html()).toMatchSnapshot();
-});
-
 function sleep(delay = 0): Promise<void> {
   return new Promise((resolve) => {
     setTimeout(resolve, delay);

+ 5 - 1
src/packages/__VUE/inputnumber/index.scss

@@ -50,10 +50,14 @@
   }
 
   input,
-  &__text--readonly {
+  &__text--readonly,
+  &__text--input {
     width: $inputnumber-input-width;
     height: 100%;
     text-align: center;
+    display: flex;
+    justify-content: center;
+    align-items: center;
     outline: none;
     border: $inputnumber-input-border;
     font-size: $inputnumber-input-font-size;

+ 1 - 0
src/packages/__VUE/inputnumber/index.taro.vue

@@ -15,6 +15,7 @@
     <input
       v-else
       type="number"
+      class="nut-inputnumber__text--input"
       :min="min"
       :max="max"
       :style="{ width: pxCheck(inputWidth) }"

+ 9 - 8
src/packages/__VUE/picker/__tests__/picker.spec.ts

@@ -2,15 +2,16 @@ import { config, mount } from '@vue/test-utils';
 import Picker from '../index.vue';
 import NutIcon from '../../icon/index.vue';
 import NutPupup from '../../popup/index.vue';
+import NutOverlay from '../../overlay/index.vue';
 import NutPickerColumn from '../Column.vue';
-import { nextTick, toRefs, reactive, ref, onMounted } from 'vue';
-import { triggerDrag } from '../../../utils/test/event';
+import { nextTick } from 'vue';
 
 beforeAll(() => {
   config.global.components = {
     NutIcon,
     NutPupup,
-    NutPickerColumn
+    NutPickerColumn,
+    NutOverlay
   };
 });
 
@@ -96,8 +97,8 @@ test('first render', async () => {
     }
   });
   await nextTick();
-  expect(wrapper.find('.nut-picker__cancel').exists()).toBeTruthy();
-  expect(wrapper.find('.nut-picker__confirm').exists()).toBeTruthy();
+  expect(wrapper.find('.nut-picker__left').exists()).toBeTruthy();
+  expect(wrapper.find('.nut-picker__right').exists()).toBeTruthy();
 });
 
 test('simple list-data confirm & close event', async () => {
@@ -109,8 +110,8 @@ test('simple list-data confirm & close event', async () => {
     }
   });
   await nextTick();
-  wrapper.find('.nut-picker__cancel').trigger('click');
-  wrapper.find('.nut-picker__confirm').trigger('click');
+  wrapper.find('.nut-picker__left').trigger('click');
+  wrapper.find('.nut-picker__right').trigger('click');
   expect(wrapper.emitted('confirm')![0]).toEqual([
     {
       selectedOptions: [{ text: '南京市', value: 'NanJing' }],
@@ -129,7 +130,7 @@ test('simple columns default checked item', async () => {
     }
   });
   await nextTick();
-  wrapper.find('.nut-picker__confirm').trigger('click');
+  wrapper.find('.nut-picker__right').trigger('click');
   expect(wrapper.emitted('confirm')![0]).toEqual([
     {
       selectedOptions: [{ text: '无锡市', value: 'WuXi' }],

+ 2 - 1
src/sites/doc/main.ts

@@ -10,7 +10,8 @@ import NutUI from '@/packages/nutui.vue';
 import { isMobile } from '@/sites/assets/util';
 
 if (isMobile) {
-  location.replace('demo.html' + location.hash);
+  let url = location.hash.replace('/zh-CN/', '').replace('/en-US/', '');
+  location.replace('demo.html' + url);
 }
 
 createApp(App).directive('hover', Hover).component('demo-block', DemoBlock).use(router).use(NutUI).mount('#doc');