Browse Source

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

Drjnigfubo 4 years ago
parent
commit
0c16f0cbc7

+ 17 - 0
CHANGELOG.md

@@ -1,3 +1,20 @@
+## v3.1.8~9
+
+`2021-10-17`
+
+* :zap: feat: 新增 timeselect 配送时间组件 @szg2008
+* :zap: feat: 新增 tabs 选项卡组件 @richard1015
+* :zap: feat(swipe): add props prevent-default、stop-propagation @richard1015
+* :zap: feat(uploader): add props auto-upload 支持手动上传 #688 @richard1015
+* :zap: upd(popover): 位置计算逻辑优化 (#710) @liqiong-lab
+* :bug: fix(overlay): modify overlay background style @szg2008
+* :bug: fix(popup): taro env touchmove content disable @richard1015
+* :bug: fix: child component taro export bug #707  @Ymm0008
+* :bug: fix(address): 组件红线位置错乱问题 (#732) @yangxiaolu1993
+* :bug: fix(address & elevator): remove pinyin dependence & 支持cdn使用 @szg2008
+* :zap: upd(elevator): add name props support html #691 @szg2008
+* :zap: upd: create 函数添加 ts 类型 (#704) @qqjay2017
+* :zap: 官网优化(文章时间分类、贡献者指南、FAQ常见问题模块) @richard1015 @Drjingfubo @lzzwoniu
 
 
 ## v3.1.7
 ## v3.1.7
 
 

+ 4 - 2
jd/generate-nutui-taro-vue.js

@@ -7,9 +7,11 @@ let importScssStr = `\n`;
 const packages = [];
 const packages = [];
 config.nav.map((item) => {
 config.nav.map((item) => {
   item.packages.forEach((element) => {
   item.packages.forEach((element) => {
-    let { name, show, type, taro, exportEmpty } = element;
+    let { name, show, type, taro, exportEmpty, exportEmptyTaro } = element;
     if (taro && (show || exportEmpty)) {
     if (taro && (show || exportEmpty)) {
-      importStr += `import ${name} from './__VUE/${name.toLowerCase()}/index${exportEmpty ? '' : '.taro'}.vue';\n`;
+      importStr += `import ${name} from './__VUE/${name.toLowerCase()}/index${
+        exportEmpty && !exportEmptyTaro ? '' : '.taro'
+      }.vue';\n`;
       importScssStr += `import './__VUE/${name.toLowerCase()}/index.scss';\n`;
       importScssStr += `import './__VUE/${name.toLowerCase()}/index.scss';\n`;
       packages.push(name);
       packages.push(name);
     }
     }

+ 2 - 1
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@nutui/nutui",
   "name": "@nutui/nutui",
-  "version": "3.1.8",
+  "version": "3.1.9",
   "description": "京东风格的轻量级移动端 Vue2、Vue3 组件库(支持小程序开发)",
   "description": "京东风格的轻量级移动端 Vue2、Vue3 组件库(支持小程序开发)",
   "main": "dist/nutui.umd.js",
   "main": "dist/nutui.umd.js",
   "module": "dist/nutui.es.js",
   "module": "dist/nutui.es.js",
@@ -46,6 +46,7 @@
     "serve": "vite preview",
     "serve": "vite preview",
     "upload": "yarn build:site:oss && node ./jd/upload.js",
     "upload": "yarn build:site:oss && node ./jd/upload.js",
     "add": "node jd/createComponentMode.js",
     "add": "node jd/createComponentMode.js",
+    "publish:beta": "npm publish --tag=beta",
     "publish:next": "npm publish --tag=next",
     "publish:next": "npm publish --tag=next",
     "publish:taro": "npm publish",
     "publish:taro": "npm publish",
     "generate:file": "node jd/generate-nutui.js",
     "generate:file": "node jd/generate-nutui.js",

+ 7 - 0
src/config.json

@@ -97,6 +97,11 @@
         "cName": "更新日志",
         "cName": "更新日志",
         "show": true,
         "show": true,
         "isLink": true
         "isLink": true
+      },
+      {
+        "name": "notice",
+        "cName": "常见问题",
+        "show": true
       }
       }
     ]
     ]
   },
   },
@@ -306,6 +311,7 @@
           "type": "component",
           "type": "component",
           "show": false,
           "show": false,
           "exportEmpty": true,
           "exportEmpty": true,
+          "exportEmptyTaro": true,
           "desc": "折叠面板-item",
           "desc": "折叠面板-item",
           "author": "Ymm0008"
           "author": "Ymm0008"
         },
         },
@@ -445,6 +451,7 @@
           "show": false,
           "show": false,
           "taro": true,
           "taro": true,
           "exportEmpty": true,
           "exportEmpty": true,
+          "exportEmptyTaro": true,
           "desc": "轮播图子组件",
           "desc": "轮播图子组件",
           "author": "suzigang"
           "author": "suzigang"
         },
         },

+ 31 - 23
src/packages/__VUE/address/index.taro.vue

@@ -7,29 +7,29 @@
     v-model:visible="showPopup"
     v-model:visible="showPopup"
     :class="classes"
     :class="classes"
   >
   >
-    <view-block class="nut-address">
-      <view-block class="nut-address__header">
-        <view-block class="arrow-back" @click="switchModule">
+    <view class="nut-address">
+      <view class="nut-address__header">
+        <view class="arrow-back" @click="switchModule">
           <nut-icon
           <nut-icon
             :name="backBtnIcon"
             :name="backBtnIcon"
             color="#cccccc"
             color="#cccccc"
             v-if="privateType == 'custom' && type == 'exist' && backBtnIcon"
             v-if="privateType == 'custom' && type == 'exist' && backBtnIcon"
           ></nut-icon>
           ></nut-icon>
-        </view-block>
+        </view>
 
 
-        <view-block class="nut-address__header__title">
+        <view class="nut-address__header__title">
           {{ privateType == 'custom' ? customAddressTitle : existAddressTitle }}
           {{ privateType == 'custom' ? customAddressTitle : existAddressTitle }}
-        </view-block>
+        </view>
 
 
-        <view-block class="arrow-close" @click="handClose('cross')">
+        <view class="arrow-close" @click="handClose('cross')">
           <nut-icon v-if="closeBtnIcon" :name="closeBtnIcon" color="#cccccc" size="18px"></nut-icon>
           <nut-icon v-if="closeBtnIcon" :name="closeBtnIcon" color="#cccccc" size="18px"></nut-icon>
-        </view-block>
-      </view-block>
+        </view>
+      </view>
 
 
       <!-- 请选择 -->
       <!-- 请选择 -->
-      <view-block class="custom-address" v-if="privateType == 'custom'">
-        <view-block class="region-tab">
-          <view-block
+      <view class="custom-address" v-if="privateType == 'custom'">
+        <view class="region-tab">
+          <view
             class="tab-item"
             class="tab-item"
             :class="[index == tabIndex ? 'active' : '', key]"
             :class="[index == tabIndex ? 'active' : '', key]"
             v-for="(item, key, index) in selectedRegion"
             v-for="(item, key, index) in selectedRegion"
@@ -38,12 +38,12 @@
             @click="changeRegionTab(item, key, index)"
             @click="changeRegionTab(item, key, index)"
           >
           >
             <view>{{ getTabName(item, index) }}</view>
             <view>{{ getTabName(item, index) }}</view>
-          </view-block>
+          </view>
 
 
-          <view-block class="region-tab-line" ref="regionLine" :style="{ left: lineDistance + 'px' }"></view-block>
-        </view-block>
+          <view class="region-tab-line" ref="regionLine" :style="{ left: lineDistance + 'px' }"></view>
+        </view>
 
 
-        <view-block class="region-con">
+        <view class="region-con">
           <ul class="region-group">
           <ul class="region-group">
             <li
             <li
               v-for="(item, index) in regionList[tabName[tabIndex]]"
               v-for="(item, index) in regionList[tabName[tabIndex]]"
@@ -63,10 +63,10 @@
               >{{ item.name }}
               >{{ item.name }}
             </li>
             </li>
           </ul>
           </ul>
-        </view-block>
-      </view-block>
+        </view>
+      </view>
 
 
-      <view-block class="custom-address" v-else-if="privateType === 'custom2'">
+      <view class="custom-address" v-else-if="privateType === 'custom2'">
         <view class="region-tab">
         <view class="region-tab">
           <view
           <view
             class="tab-item"
             class="tab-item"
@@ -87,10 +87,10 @@
             @click-item="handleElevatorItem"
             @click-item="handleElevatorItem"
           ></nut-elevator>
           ></nut-elevator>
         </view>
         </view>
-      </view-block>
+      </view>
 
 
       <!-- 配送至 -->
       <!-- 配送至 -->
-      <view-block class="exist-address" v-else-if="privateType == 'exist'">
+      <view class="exist-address" v-else-if="privateType == 'exist'">
         <div class="exist-address-group">
         <div class="exist-address-group">
           <ul class="exist-ul">
           <ul class="exist-ul">
             <li
             <li
@@ -125,8 +125,8 @@
         <div class="choose-other" @click="switchModule" v-if="isShowCustomAddress">
         <div class="choose-other" @click="switchModule" v-if="isShowCustomAddress">
           <div class="btn">{{ customAndExistTitle }}</div>
           <div class="btn">{{ customAndExistTitle }}</div>
         </div>
         </div>
-      </view-block>
-    </view-block>
+      </view>
+    </view>
   </nut-popup>
   </nut-popup>
 </template>
 </template>
 <script lang="ts">
 <script lang="ts">
@@ -137,6 +137,7 @@ import Taro from '@tarojs/taro';
 const { create, componentName } = createComponent('address');
 const { create, componentName } = createComponent('address');
 
 
 interface RegionData {
 interface RegionData {
+  id: string;
   name: string;
   name: string;
   [key: string]: any;
   [key: string]: any;
 }
 }
@@ -256,6 +257,13 @@ export default create({
 
 
       if (!data.length) return [];
       if (!data.length) return [];
 
 
+      data.forEach((item: RegionData) => {
+        if (!item.title) {
+          console.error('[NutUI] <Address> 请检查数组选项的 title 值是否有设置 ,title 为必填项 .');
+          return;
+        }
+      });
+
       const newData: CustomRegionData[] = [];
       const newData: CustomRegionData[] = [];
 
 
       data = data.sort((a: RegionData, b: RegionData) => {
       data = data.sort((a: RegionData, b: RegionData) => {

+ 9 - 1
src/packages/__VUE/address/index.vue

@@ -241,6 +241,13 @@ export default create({
 
 
       if (!data.length) return [];
       if (!data.length) return [];
 
 
+      data.forEach((item: RegionData) => {
+        if (!item.title) {
+          console.error('[NutUI] <Address> 请检查数组选项的 title 值是否有设置 ,title 为必填项 .');
+          return;
+        }
+      });
+
       const newData: CustomRegionData[] = [];
       const newData: CustomRegionData[] = [];
 
 
       data = data.sort((a: RegionData, b: RegionData) => {
       data = data.sort((a: RegionData, b: RegionData) => {
@@ -313,7 +320,8 @@ export default create({
       nextTick(() => {
       nextTick(() => {
         if (name) {
         if (name) {
           const distance = name.offsetLeft;
           const distance = name.offsetLeft;
-          lineDistance.value = distance;
+
+          lineDistance.value = distance ? distance : 20;
         }
         }
       });
       });
     };
     };

+ 44 - 52
src/packages/__VUE/collapseitem/index.taro.vue

@@ -1,11 +1,7 @@
 <template>
 <template>
   <view :class="classes">
   <view :class="classes">
     <view
     <view
-      :class="[
-        'collapse-item',
-        { 'item-expanded': openExpanded },
-        { 'nut-collapse-item-disabled': disabled }
-      ]"
+      :class="['collapse-item', { 'item-expanded': openExpanded }, { 'nut-collapse-item-disabled': disabled }]"
       @click="toggleOpen"
       @click="toggleOpen"
     >
     >
       <view class="collapse-title">
       <view class="collapse-title">
@@ -16,10 +12,7 @@
               :name="titleIcon"
               :name="titleIcon"
               :size="titleIconSize"
               :size="titleIconSize"
               :color="titleIconColor"
               :color="titleIconColor"
-              :class="[
-                'collapse-title-icon',
-                titleIconPosition == 'left' ? 'titleIconLeft' : 'titleIconRight'
-              ]"
+              :class="['collapse-title-icon', titleIconPosition == 'left' ? 'titleIconLeft' : 'titleIconRight']"
             ></nut-icon>
             ></nut-icon>
             <template v-if="$slots.mTitle">
             <template v-if="$slots.mTitle">
               <slot name="mTitle"></slot>
               <slot name="mTitle"></slot>
@@ -51,11 +44,7 @@
         :name="icon"
         :name="icon"
         :size="iconSize"
         :size="iconSize"
         :color="iconColor"
         :color="iconColor"
-        :class="[
-          'collapse-icon',
-          { 'col-expanded': openExpanded },
-          { 'collapse-icon-disabled': disabled }
-        ]"
+        :class="['collapse-icon', { 'col-expanded': openExpanded }, { 'collapse-icon-disabled': disabled }]"
         :style="iconStyle"
         :style="iconStyle"
       ></nut-icon>
       ></nut-icon>
     </view>
     </view>
@@ -76,18 +65,18 @@ import {
   inject,
   inject,
   toRefs,
   toRefs,
   onMounted,
   onMounted,
+  Ref,
   ref,
   ref,
+  unref,
   nextTick,
   nextTick,
   computed,
   computed,
   watch,
   watch,
   getCurrentInstance,
   getCurrentInstance,
   ComponentInternalInstance
   ComponentInternalInstance
 } from 'vue';
 } from 'vue';
-import Taro, {
-  eventCenter,
-  getCurrentInstance as getCurrentInstanceTaro
-} from '@tarojs/taro';
+import Taro, { eventCenter, getCurrentInstance as getCurrentInstanceTaro } from '@tarojs/taro';
 import { createComponent } from '../../utils/create';
 import { createComponent } from '../../utils/create';
+import { useTaroRect } from '../../utils/useTaroRect';
 const { create, componentName } = createComponent('collapse-item');
 const { create, componentName } = createComponent('collapse-item');
 
 
 export default create({
 export default create({
@@ -140,9 +129,7 @@ export default create({
       // classDirection: 'right',
       // classDirection: 'right',
       iconStyle: {
       iconStyle: {
         transform: 'rotate(0deg)',
         transform: 'rotate(0deg)',
-        marginTop: parent.props.iconHeght
-          ? '-' + parent.props.iconHeght / 2 + 'px'
-          : '-10px'
+        marginTop: parent.props.iconHeght ? '-' + parent.props.iconHeght / 2 + 'px' : '-10px'
       }
       }
     });
     });
 
 
@@ -180,8 +167,7 @@ export default create({
       if (parent.props.icon && !proxyData.openExpanded) {
       if (parent.props.icon && !proxyData.openExpanded) {
         proxyData.iconStyle['transform'] = 'rotate(0deg)';
         proxyData.iconStyle['transform'] = 'rotate(0deg)';
       } else {
       } else {
-        proxyData.iconStyle['transform'] =
-          'rotate(' + parent.props.rotate + 'deg)';
+        proxyData.iconStyle['transform'] = 'rotate(' + parent.props.rotate + 'deg)';
       }
       }
       nextTick(() => {
       nextTick(() => {
         const query = Taro.createSelectorQuery();
         const query = Taro.createSelectorQuery();
@@ -203,8 +189,7 @@ export default create({
     const defaultOpen = () => {
     const defaultOpen = () => {
       open();
       open();
       if (parent.props.icon) {
       if (parent.props.icon) {
-        proxyData['iconStyle']['transform'] =
-          'rotate(' + parent.props.rotate + 'deg)';
+        proxyData['iconStyle']['transform'] = 'rotate(' + parent.props.rotate + 'deg)';
       }
       }
     };
     };
 
 
@@ -255,41 +240,48 @@ export default create({
           let h = tm[0]['height'];
           let h = tm[0]['height'];
           item1.conHeight = h;
           item1.conHeight = h;
         }
         }
-        // ary.forEach((item2: any, index2: number) => {
-        //   let ary2 = Array.from(item2.children);
-        //   ary2.length > 0 &&
-        //     ary2.forEach((item3: any, index3: number) => {
-        //       if (domID.includes(item3.uid)) {
-        //         const h = list.filter((item4: any) => item4.id == item3.uid)[0]
-        //           ?.height;
-        //         item1.conHeight = h;
-        //       }
-        //     });
-        // });
       });
       });
     };
     };
 
 
-    // let list: any = [],
-    //   domID: any = [];
+    const getH5 = () => {
+      parent.children.forEach((item1: any, index1: number) => {
+        let ary: any = Array.from(item1.$el.children);
+        let h = ary[1].children[0]['offsetHeight'];
+        item1.conHeight = h;
+      });
+    };
+
+    const getRefHeight = () => {
+      const query = Taro.createSelectorQuery();
+      query.selectAll('.collapse-content').boundingClientRect();
+      query.exec((res) => {
+        if (Taro.getEnv() === 'WEB') {
+          getH5();
+        } else {
+          getH(res[0]);
+        }
+      });
+    };
+
     onMounted(() => {
     onMounted(() => {
       const { name } = props;
       const { name } = props;
       const active = parent && parent.props.active;
       const active = parent && parent.props.active;
       // 获取 DOM 元素
       // 获取 DOM 元素
-      eventCenter.once((getCurrentInstanceTaro() as any).router.onReady, () => {
-        const query = Taro.createSelectorQuery();
-        query.selectAll('.collapse-content').boundingClientRect();
-        query.exec((res) => {
-          // list = res[0];
-          // list.forEach((item: any) => {
-          //   domID.push(item.id);
-          // });
-          getH(res[0]);
-          // parent.activeIndex().forEach((item:any) => {
-          //   const h = list[item]?.height;
-          //   parent.children[item].conHeight = h;
-          // });
+      if (Taro.getEnv() === 'WEB') {
+        getRefHeight();
+      } else {
+        eventCenter.once((getCurrentInstanceTaro() as any).router.onReady, () => {
+          getRefHeight();
         });
         });
-      });
+      }
+
+      // const query = Taro.createSelectorQuery();
+      // query.selectAll('.collapse-content').boundingClientRect();
+      // query.exec((res) => {
+      //   console.log(res[0]);
+      //   getH(res[0]);
+      // });
+
       if (typeof active == 'number' || typeof active == 'string') {
       if (typeof active == 'number' || typeof active == 'string') {
         if (name == active) {
         if (name == active) {
           defaultOpen();
           defaultOpen();

+ 9 - 26
src/packages/__VUE/collapseitem/index.vue

@@ -1,11 +1,7 @@
 <template>
 <template>
   <view :class="classes">
   <view :class="classes">
     <view
     <view
-      :class="[
-        'collapse-item',
-        { 'item-expanded': openExpanded },
-        { 'nut-collapse-item-disabled': disabled }
-      ]"
+      :class="['collapse-item', { 'item-expanded': openExpanded }, { 'nut-collapse-item-disabled': disabled }]"
       @click="toggleOpen"
       @click="toggleOpen"
     >
     >
       <view class="collapse-title">
       <view class="collapse-title">
@@ -16,13 +12,11 @@
               :name="titleIcon"
               :name="titleIcon"
               :size="titleIconSize"
               :size="titleIconSize"
               :color="titleIconColor"
               :color="titleIconColor"
-              :class="[
-                titleIconPosition == 'left' ? 'titleIconLeft' : 'titleIconRight'
-              ]"
+              :class="[titleIconPosition == 'left' ? 'titleIconLeft' : 'titleIconRight']"
             ></nut-icon>
             ></nut-icon>
             <slot v-if="$slots.mTitle" name="mTitle"></slot>
             <slot v-if="$slots.mTitle" name="mTitle"></slot>
             <template v-else>
             <template v-else>
-              <view v-html="title"></view>
+              <view v-html="title" class="collapse-icon-title"></view>
             </template>
             </template>
           </view>
           </view>
         </view>
         </view>
@@ -36,11 +30,7 @@
         :name="icon"
         :name="icon"
         :size="iconSize"
         :size="iconSize"
         :color="iconColor"
         :color="iconColor"
-        :class="[
-          'collapse-icon',
-          { 'col-expanded': openExpanded },
-          { 'collapse-icon-disabled': disabled }
-        ]"
+        :class="['collapse-icon', { 'col-expanded': openExpanded }, { 'collapse-icon-disabled': disabled }]"
         :style="iconStyle"
         :style="iconStyle"
       ></nut-icon>
       ></nut-icon>
     </view>
     </view>
@@ -115,9 +105,7 @@ export default create({
       // classDirection: 'right',
       // classDirection: 'right',
       iconStyle: {
       iconStyle: {
         transform: 'rotate(0deg)',
         transform: 'rotate(0deg)',
-        marginTop: parent.props.iconHeght
-          ? '-' + parent.props.iconHeght / 2 + 'px'
-          : '-10px'
+        marginTop: parent.props.iconHeght ? '-' + parent.props.iconHeght / 2 + 'px' : '-10px'
       }
       }
     });
     });
 
 
@@ -138,8 +126,7 @@ export default create({
 
 
     // 清除 willChange 减少性能浪费
     // 清除 willChange 减少性能浪费
     const onTransitionEnd = () => {
     const onTransitionEnd = () => {
-      const wrapperRefEle: any =
-        document.getElementsByClassName('collapse-wrapper')[0];
+      const wrapperRefEle: any = document.getElementsByClassName('collapse-wrapper')[0];
       wrapperRefEle.style.willChange = 'auto';
       wrapperRefEle.style.willChange = 'auto';
 
 
       // const query = wx.createSelectorQuery();
       // const query = wx.createSelectorQuery();
@@ -157,14 +144,11 @@ export default create({
       if (offsetHeight) {
       if (offsetHeight) {
         const contentHeight = `${offsetHeight}px`;
         const contentHeight = `${offsetHeight}px`;
         wrapperRefEle.style.willChange = 'height';
         wrapperRefEle.style.willChange = 'height';
-        wrapperRefEle.style.height = !proxyData.openExpanded
-          ? 0
-          : contentHeight;
+        wrapperRefEle.style.height = !proxyData.openExpanded ? 0 : contentHeight;
         if (parent.props.icon && !proxyData.openExpanded) {
         if (parent.props.icon && !proxyData.openExpanded) {
           proxyData.iconStyle['transform'] = 'rotate(0deg)';
           proxyData.iconStyle['transform'] = 'rotate(0deg)';
         } else {
         } else {
-          proxyData.iconStyle['transform'] =
-            'rotate(' + parent.props.rotate + 'deg)';
+          proxyData.iconStyle['transform'] = 'rotate(' + parent.props.rotate + 'deg)';
         }
         }
       }
       }
       if (!proxyData.openExpanded) {
       if (!proxyData.openExpanded) {
@@ -180,8 +164,7 @@ export default create({
     const defaultOpen = () => {
     const defaultOpen = () => {
       open();
       open();
       if (parent.props.icon) {
       if (parent.props.icon) {
-        proxyData['iconStyle']['transform'] =
-          'rotate(' + parent.props.rotate + 'deg)';
+        proxyData['iconStyle']['transform'] = 'rotate(' + parent.props.rotate + 'deg)';
       }
       }
     };
     };
 
 

+ 2 - 4
src/packages/__VUE/elevator/index.scss

@@ -3,8 +3,10 @@
   display: block;
   display: block;
   position: relative;
   position: relative;
   &__list {
   &__list {
+    display: block;
     overflow: auto;
     overflow: auto;
     &__item {
     &__item {
+      display: block;
       font-size: 12px;
       font-size: 12px;
       color: #333;
       color: #333;
       &__code {
       &__code {
@@ -66,7 +68,3 @@
     }
     }
   }
   }
 }
 }
-
-view {
-  display: block;
-}

+ 10 - 3
src/packages/__VUE/elevator/index.taro.vue

@@ -23,9 +23,9 @@
         ></view>
         ></view>
       </view>
       </view>
     </scroll-view>
     </scroll-view>
-    <view class="nut-elevator__code--current" v-show="scrollStart" v-if="indexList.length">{{
-      indexList[currentIndex][acceptKey]
-    }}</view>
+    <view class="nut-elevator__code--current" v-show="scrollStart" v-if="indexList.length > 0">
+      {{ indexList[currentIndex][acceptKey] }}
+    </view>
     <view class="nut-elevator__bars" @touchstart="touchStart" @touchmove.stop.prevent="touchMove" @touchend="touchEnd">
     <view class="nut-elevator__bars" @touchstart="touchStart" @touchmove.stop.prevent="touchMove" @touchend="touchEnd">
       <view class="nut-elevator__bars__inner">
       <view class="nut-elevator__bars__inner">
         <view
         <view
@@ -182,6 +182,13 @@ export default create({
       Taro.nextTick(() => {
       Taro.nextTick(() => {
         calculateHeight();
         calculateHeight();
       });
       });
+      if (Taro.getEnv() === 'WEB') {
+        calculateHeight();
+      } else {
+        eventCenter.once((getCurrentInstance() as any).router.onReady, () => {
+          calculateHeight();
+        });
+      }
     });
     });
 
 
     return {
     return {

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

@@ -136,6 +136,7 @@ export default createDemo({
   height: 300px;
   height: 300px;
   width: 100%;
   width: 100%;
   padding: 0;
   padding: 0;
+  margin: 0;
   overflow-y: auto;
   overflow-y: auto;
   overflow-x: hidden;
   overflow-x: hidden;
 }
 }

+ 18 - 26
src/packages/__VUE/infiniteloading/index.taro.vue

@@ -10,28 +10,28 @@
     @touchmove="touchMove"
     @touchmove="touchMove"
     @touchend="touchEnd"
     @touchend="touchEnd"
   >
   >
-    <view-block class="nut-infinite-top" :style="getStyle">
-      <view-block class="top-box" id="refreshTop">
+    <view class="nut-infinite-top" :style="getStyle">
+      <view class="top-box" id="refreshTop">
         <nut-icon class="top-img" :name="pullIcon"></nut-icon>
         <nut-icon class="top-img" :name="pullIcon"></nut-icon>
-        <view-block class="top-text">{{ pullTxt }}</view-block>
-      </view-block>
-    </view-block>
+        <view class="top-text">{{ pullTxt }}</view>
+      </view>
+    </view>
 
 
-    <view-block class="nut-infinite-container">
+    <view class="nut-infinite-container">
       <slot></slot>
       <slot></slot>
-    </view-block>
+    </view>
 
 
-    <view-block class="nut-infinite-bottom">
+    <view class="nut-infinite-bottom">
       <template v-if="isInfiniting">
       <template v-if="isInfiniting">
-        <view-block class="bottom-box">
+        <view class="bottom-box">
           <nut-icon class="bottom-img" :name="loadIcon"></nut-icon>
           <nut-icon class="bottom-img" :name="loadIcon"></nut-icon>
-          <view-block class="bottom-text">{{ loadTxt }}</view-block>
-        </view-block>
+          <view class="bottom-text">{{ loadTxt }}</view>
+        </view>
       </template>
       </template>
       <template v-else-if="!hasMore">
       <template v-else-if="!hasMore">
-        <view-block class="tips">{{ loadMoreTxt }}</view-block>
+        <view class="tips">{{ loadMoreTxt }}</view>
       </template>
       </template>
-    </view-block>
+    </view>
   </scroll-view>
   </scroll-view>
 </template>
 </template>
 <script lang="ts">
 <script lang="ts">
@@ -52,8 +52,7 @@ export default create({
     },
     },
     pullIcon: {
     pullIcon: {
       type: String,
       type: String,
-      default:
-        'https://img10.360buyimg.com/imagetools/jfs/t1/169863/6/4565/6306/60125948E7e92774e/40b3a0cf42852bcb.png'
+      default: 'https://img10.360buyimg.com/imagetools/jfs/t1/169863/6/4565/6306/60125948E7e92774e/40b3a0cf42852bcb.png'
     },
     },
     pullTxt: {
     pullTxt: {
       type: String,
       type: String,
@@ -61,8 +60,7 @@ export default create({
     },
     },
     loadIcon: {
     loadIcon: {
       type: String,
       type: String,
-      default:
-        'https://img10.360buyimg.com/imagetools/jfs/t1/169863/6/4565/6306/60125948E7e92774e/40b3a0cf42852bcb.png'
+      default: 'https://img10.360buyimg.com/imagetools/jfs/t1/169863/6/4565/6306/60125948E7e92774e/40b3a0cf42852bcb.png'
     },
     },
     loadTxt: {
     loadTxt: {
       type: String,
       type: String,
@@ -123,9 +121,7 @@ export default create({
       };
       };
     });
     });
     const getParentElement = (el) => {
     const getParentElement = (el) => {
-      return Taro.createSelectorQuery().select(
-        !!props.containerId ? `#${props.containerId} #${el}` : `#${el}`
-      );
+      return Taro.createSelectorQuery().select(!!props.containerId ? `#${props.containerId} #${el}` : `#${el}`);
     };
     };
     /** 获取需要滚动的距离 */
     /** 获取需要滚动的距离 */
     const getScrollHeight = () => {
     const getScrollHeight = () => {
@@ -157,10 +153,7 @@ export default create({
         // 滚动到最底部
         // 滚动到最底部
         e.detail.scrollTop = state.scrollHeight;
         e.detail.scrollTop = state.scrollHeight;
       }
       }
-      if (
-        e.detail.scrollTop > state.scrollTop ||
-        e.detail.scrollTop >= state.scrollHeight
-      ) {
+      if (e.detail.scrollTop > state.scrollTop || e.detail.scrollTop >= state.scrollHeight) {
         state.direction = 'down';
         state.direction = 'down';
       } else {
       } else {
         state.direction = 'up';
         state.direction = 'up';
@@ -191,8 +184,7 @@ export default create({
 
 
       if (state.distance > 0 && state.isTouching) {
       if (state.distance > 0 && state.isTouching) {
         event.preventDefault();
         event.preventDefault();
-        if (state.distance >= state.refreshMaxH)
-          state.distance = state.refreshMaxH;
+        if (state.distance >= state.refreshMaxH) state.distance = state.refreshMaxH;
       } else {
       } else {
         state.distance = 0;
         state.distance = 0;
         state.isTouching = false;
         state.isTouching = false;

+ 0 - 147
src/packages/__VUE/tabbaritem/index.taro.vue

@@ -1,147 +0,0 @@
-<template>
-  <div
-    class="nut-tabbar-item"
-    :class="{ 'nut-tabbar-item__icon--unactive': state.active != state.index }"
-    :style="{
-      color:
-        state.active == state.index ? state.activeColor : state.unactiveColor
-    }"
-    @click="change(state.index)"
-  >
-    <view class="nut-tabbar-item_icon-box">
-      <view
-        class="nut-tabbar-item_icon-box_tips nut-tabbar-item_icon-box_num"
-        v-if="num && num <= 99"
-      >
-        {{ num }}
-      </view>
-      <view
-        class="nut-tabbar-item_icon-box_tips nut-tabbar-item_icon-box_nums"
-        v-else-if="num && num > 100"
-        >{{ '99+' }}</view
-      >
-      <view v-if="icon">
-        <nut-icon
-          class="nut-tabbar-item_icon-box_icon"
-          :size="state.size"
-          :name="icon"
-          :font-class-name="fontClassName"
-          :class-prefix="classPrefix"
-        ></nut-icon>
-      </view>
-      <div
-        v-if="!icon && activeImg"
-        class="nut-tabbar-item_icon-box_icon"
-        :style="{
-          backgroundImage: `url(${
-            state.active == state.index ? activeImg : img
-          })`,
-          width: state.size,
-          height: state.size
-        }"
-      ></div>
-      <view
-        :class="[
-          'nut-tabbar-item_icon-box_nav-word',
-          { 'nut-tabbar-item_icon-box_big-word': !icon && !activeImg }
-        ]"
-        >{{ tabTitle }}</view
-      >
-    </view>
-  </div>
-</template>
-<script lang="ts">
-import { createComponent } from '../../utils/create';
-import {
-  ComponentInternalInstance,
-  computed,
-  getCurrentInstance,
-  inject,
-  reactive,
-  watch
-} from 'vue';
-const { create } = createComponent('tabbar-item');
-export default create({
-  props: {
-    tabTitle: {
-      // 标签页的标题
-      type: String,
-      default: ''
-    },
-    icon: {
-      // 标签页显示的icon
-      type: String,
-      default: ''
-    },
-    href: {
-      // 标签页的跳转链接
-      type: String,
-      default: ''
-    },
-    num: {
-      // 页签右上角的数字角标
-      type: String,
-      default: ''
-    },
-    activeImg: {
-      type: String,
-      default: ''
-    },
-    img: {
-      type: String,
-      default: ''
-    },
-    classPrefix: {
-      type: String,
-      default: 'nut-icon'
-    },
-    fontClassName: {
-      type: String,
-      default: 'nutui-iconfont'
-    }
-  },
-  setup(props, ctx) {
-    const parent: any = inject('parent');
-    console.log(props.classPrefix);
-
-    const state = reactive({
-      size: parent.size,
-      unactiveColor: parent.unactiveColor, // 未选中的颜色
-      activeColor: parent.activeColor, // 选中的颜色
-      active: parent.modelValue, // 是否选中
-      index: 0
-    });
-    const relation = (child: ComponentInternalInstance): void => {
-      if (child.proxy) {
-        let index = parent.children.length;
-        state.index = index;
-        let obj = Object.assign({}, child.proxy, { index });
-        parent.children.push(obj);
-      }
-    };
-    relation(getCurrentInstance() as ComponentInternalInstance);
-    function change(index: Number) {
-      parent.changeIndex(index);
-    }
-    const choosed = computed(() => {
-      if (parent) {
-        return parent.modelValue;
-      }
-      return null;
-    });
-
-    watch(choosed, (value, oldValue) => {
-      state.active = value;
-      setTimeout(() => {
-        if (parent.children[value].href) {
-          window.location.href = parent.children[value].href;
-        }
-      });
-    });
-    return {
-      state,
-      change
-    };
-  }
-});
-</script>

+ 18 - 30
src/packages/__VUE/uploader/demo.vue

@@ -13,41 +13,29 @@
     <h2>限制上传数量5个</h2>
     <h2>限制上传数量5个</h2>
     <nut-uploader :url="uploadUrl" multiple maximum="5"></nut-uploader>
     <nut-uploader :url="uploadUrl" multiple maximum="5"></nut-uploader>
     <h2>限制上传大小(每个文件最大不超过 50kb)</h2>
     <h2>限制上传大小(每个文件最大不超过 50kb)</h2>
-    <nut-uploader
-      :url="uploadUrl"
-      multiple
-      :maximize="1024 * 50"
-      @oversize="onOversize"
-    ></nut-uploader>
+    <nut-uploader :url="uploadUrl" multiple :maximize="1024 * 50" @oversize="onOversize"></nut-uploader>
     <h2>限制上传大小(在beforeupload钩子中处理)</h2>
     <h2>限制上传大小(在beforeupload钩子中处理)</h2>
-    <nut-uploader
-      :url="uploadUrl"
-      multiple
-      :before-upload="beforeUpload"
-      :maximize="1024 * 50"
-      @oversize="onOversize"
-    >
+    <nut-uploader :url="uploadUrl" multiple :before-upload="beforeUpload" :maximize="1024 * 50" @oversize="onOversize">
     </nut-uploader>
     </nut-uploader>
     <h2>自定义数据 FormData 、 headers </h2>
     <h2>自定义数据 FormData 、 headers </h2>
-    <nut-uploader
-      :url="uploadUrl"
-      :data="formData"
-      :headers="formData"
-      :with-credentials="true"
-    ></nut-uploader>
+    <nut-uploader :url="uploadUrl" :data="formData" :headers="formData" :with-credentials="true"></nut-uploader>
+    <h2>手动上传 </h2>
+    <nut-uploader :url="uploadUrl" maximum="5" :auto-upload="false" ref="uploadRef"></nut-uploader>
+    <br />
+    <nut-button type="success" size="small" @click="submitUpload">执行上传</nut-button>
     <h2>禁用状态</h2>
     <h2>禁用状态</h2>
     <nut-uploader disabled></nut-uploader>
     <nut-uploader disabled></nut-uploader>
   </div>
   </div>
 </template>
 </template>
 
 
 <script lang="ts">
 <script lang="ts">
+import { ref } from 'vue';
 import { createComponent } from '../../utils/create';
 import { createComponent } from '../../utils/create';
 import { FileItem } from './index.vue';
 import { FileItem } from './index.vue';
 const { createDemo } = createComponent('uploader');
 const { createDemo } = createComponent('uploader');
 export default createDemo({
 export default createDemo({
   setup() {
   setup() {
-    const uploadUrl =
-      'https://my-json-server.typicode.com/linrufeng/demo/posts';
+    const uploadUrl = 'https://my-json-server.typicode.com/linrufeng/demo/posts';
     const formData = {
     const formData = {
       custom: 'test'
       custom: 'test'
     };
     };
@@ -65,14 +53,8 @@ export default createDemo({
         img.src = dataURL;
         img.src = dataURL;
       });
       });
     };
     };
-    const canvastoFile = (
-      canvas: HTMLCanvasElement,
-      type: string,
-      quality: number
-    ): Promise<Blob | null> => {
-      return new Promise((resolve) =>
-        canvas.toBlob((blob) => resolve(blob), type, quality)
-      );
+    const canvastoFile = (canvas: HTMLCanvasElement, type: string, quality: number): Promise<Blob | null> => {
+      return new Promise((resolve) => canvas.toBlob((blob) => resolve(blob), type, quality));
     };
     };
     const onOversize = (files: File[]) => {
     const onOversize = (files: File[]) => {
       console.log('oversize 触发 文件大小不能超过 50kb', files);
       console.log('oversize 触发 文件大小不能超过 50kb', files);
@@ -95,12 +77,18 @@ export default createDemo({
       const f = await new File([blob], file[0].name);
       const f = await new File([blob], file[0].name);
       return [f];
       return [f];
     };
     };
+    const uploadRef = ref<any>(null);
+    const submitUpload = () => {
+      uploadRef.value.submit();
+    };
     return {
     return {
       onOversize,
       onOversize,
       beforeUpload,
       beforeUpload,
       onDelete,
       onDelete,
       uploadUrl,
       uploadUrl,
-      formData
+      formData,
+      uploadRef,
+      submitUpload
     };
     };
   }
   }
 });
 });

+ 31 - 0
src/packages/__VUE/uploader/doc.md

@@ -91,6 +91,28 @@ setup() {
 }
 }
 ```
 ```
 
 
+### 手动上传
+    
+``` html 
+<nut-uploader url="http://服务器地址" maximum="5" :auto-upload="false" ref="uploadRef"></nut-uploader>
+<nut-button type="success" size="small" @click="submitUpload">执行上传</nut-button>
+```
+
+``` javascript
+import { ref } from 'vue';
+setup() {
+    const uploadRef = ref<any>(null);
+    const submitUpload = () => {
+      uploadRef.value.submit();
+    };
+    return {
+      uploadRef,
+      submitUpload
+    };
+}
+```
+
+
 ### 禁用状态
 ### 禁用状态
 
 
 ``` html
 ``` html
@@ -101,6 +123,7 @@ setup() {
 
 
 | 字段              | 说明                                                                                                                                                                                   | 类型                              | 默认值           |
 | 字段              | 说明                                                                                                                                                                                   | 类型                              | 默认值           |
 |-------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------|------------------|
 |-------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------|------------------|
+| auto-upload       | 是否在选取文件后立即进行上传,false 时需要手动执行 ref submit 方法进行上传                                                                                                             | Boolean                           | true             |
 | name              | `input` 标签 `name` 的名称,发到后台的文件参数名                                                                                                                                       | String                            | "file"           |
 | name              | `input` 标签 `name` 的名称,发到后台的文件参数名                                                                                                                                       | String                            | "file"           |
 | url               | 上传服务器的接口地址                                                                                                                                                                   | String                            | -                |
 | url               | 上传服务器的接口地址                                                                                                                                                                   | String                            | -                |
 | v-model:file-list | 默认已经上传的文件列表                                                                                                                                                                 | FileItem[]                        | []               |
 | v-model:file-list | 默认已经上传的文件列表                                                                                                                                                                 | FileItem[]                        | []               |
@@ -149,3 +172,11 @@ setup() {
 | change   | 上传文件改变时的状态   | fileList,event       |
 | change   | 上传文件改变时的状态   | fileList,event       |
 | delete   | 文件删除之前的状态     | files,fileList       |
 | delete   | 文件删除之前的状态     | files,fileList       |
 
 
+### Methods
+
+通过 [ref](https://v3.cn.vuejs.org/api/special-attributes.html#key) 可以获取到 Uploader 实例并调用实例方法
+
+| 方法名           | 说明                                                       | 参数 | 返回值 |
+|------------------|------------------------------------------------------------|------|--------|
+| submit           | 手动上传模式,执行上传操作                                 | -    | -      |
+| clearUploadQueue | 清空已选择的文件队列(该方法一般配合在手动模式上传时使用) | -    | -      |

+ 31 - 0
src/packages/__VUE/uploader/doc.taro.md

@@ -87,6 +87,27 @@ setup() {
 }
 }
 ```
 ```
 
 
+### 手动上传
+    
+``` html 
+<nut-uploader url="http://服务器地址" maximum="5" :auto-upload="false" ref="uploadRef"></nut-uploader>
+<nut-button type="success" size="small" @click="submitUpload">执行上传</nut-button>
+```
+
+``` javascript
+import { ref } from 'vue';
+setup() {
+    const uploadRef = ref<any>(null);
+    const submitUpload = () => {
+      uploadRef.value.submit();
+    };
+    return {
+      uploadRef,
+      submitUpload
+    };
+}
+```
+
 ### 禁用状态
 ### 禁用状态
 
 
 ``` html
 ``` html
@@ -97,6 +118,7 @@ setup() {
 
 
 | 字段              | 说明                                                                                                                   | 类型                              | 默认值                    |
 | 字段              | 说明                                                                                                                   | 类型                              | 默认值                    |
 |-------------------|------------------------------------------------------------------------------------------------------------------------|-----------------------------------|---------------------------|
 |-------------------|------------------------------------------------------------------------------------------------------------------------|-----------------------------------|---------------------------|
+| auto-upload       | 是否在选取文件后立即进行上传,false 时需要手动执行 ref submit 方法进行上传                                             | Boolean                           | true                      |
 | name              | 发到后台的文件参数名                                                                                                   | String                            | "file"                    |
 | name              | 发到后台的文件参数名                                                                                                   | String                            | "file"                    |
 | url               | 上传服务器的接口地址                                                                                                   | String                            | -                         |
 | url               | 上传服务器的接口地址                                                                                                   | String                            | -                         |
 | v-model:file-list | 默认已经上传的文件列表                                                                                                 | FileItem[]                        | []                        |
 | v-model:file-list | 默认已经上传的文件列表                                                                                                 | FileItem[]                        | []                        |
@@ -140,3 +162,12 @@ setup() {
 | change   | 上传文件改变时的状态   | fileList,event   |
 | change   | 上传文件改变时的状态   | fileList,event   |
 | delete   | 文件删除之前的状态     | files,fileList   |
 | delete   | 文件删除之前的状态     | files,fileList   |
 
 
+
+### Methods
+
+通过 [ref](https://v3.cn.vuejs.org/api/special-attributes.html#key) 可以获取到 Uploader 实例并调用实例方法
+
+| 方法名           | 说明                                                       | 参数 | 返回值 |
+|------------------|------------------------------------------------------------|------|--------|
+| submit           | 手动上传模式,执行上传操作                                 | -    | -      |
+| clearUploadQueue | 清空已选择的文件队列(该方法一般配合在手动模式上传时使用) | -    | -      |

+ 35 - 6
src/packages/__VUE/uploader/index.taro.vue

@@ -75,6 +75,7 @@ export default create({
     uploadIconSize: { type: [String, Number], default: '' },
     uploadIconSize: { type: [String, Number], default: '' },
     xhrState: { type: [Number, String], default: 200 },
     xhrState: { type: [Number, String], default: 200 },
     disabled: { type: Boolean, default: false },
     disabled: { type: Boolean, default: false },
+    autoUpload: { type: Boolean, default: true },
     beforeDelete: {
     beforeDelete: {
       type: Function,
       type: Function,
       default: (file: FileItem, files: FileItem[]) => {
       default: (file: FileItem, files: FileItem[]) => {
@@ -86,6 +87,8 @@ export default create({
   emits: ['start', 'progress', 'oversize', 'success', 'failure', 'change', 'delete', 'update:fileList'],
   emits: ['start', 'progress', 'oversize', 'success', 'failure', 'change', 'delete', 'update:fileList'],
   setup(props, { emit }) {
   setup(props, { emit }) {
     const fileList = reactive(props.fileList) as Array<FileItem>;
     const fileList = reactive(props.fileList) as Array<FileItem>;
+    let uploadQueue: Promise<Uploader>[] = [];
+
     const classes = computed(() => {
     const classes = computed(() => {
       const prefixCls = componentName;
       const prefixCls = componentName;
       return {
       return {
@@ -107,7 +110,7 @@ export default create({
       });
       });
     };
     };
 
 
-    const executeUpload = (fileItem: FileItem) => {
+    const executeUpload = (fileItem: FileItem, index: number) => {
       const uploadOption = new UploadOptions();
       const uploadOption = new UploadOptions();
       uploadOption.name = props.name;
       uploadOption.name = props.name;
       uploadOption.url = props.url;
       uploadOption.url = props.url;
@@ -117,7 +120,9 @@ export default create({
       uploadOption.formData = fileItem.formData;
       uploadOption.formData = fileItem.formData;
       uploadOption.method = props.method;
       uploadOption.method = props.method;
       uploadOption.headers = props.headers;
       uploadOption.headers = props.headers;
+      uploadOption.taroFilePath = fileItem.path;
       uploadOption.onStart = (option: UploadOptions) => {
       uploadOption.onStart = (option: UploadOptions) => {
+        clearUploadQueue(index);
         fileItem.status = 'ready';
         fileItem.status = 'ready';
         emit('start', option);
         emit('start', option);
       };
       };
@@ -141,21 +146,42 @@ export default create({
           option
           option
         });
         });
       };
       };
+      let task = new Uploader(uploadOption);
+      if (props.autoUpload) {
+        task.uploadTaro(Taro.uploadFile);
+      } else {
+        uploadQueue.push(
+          new Promise((resolve, reject) => {
+            resolve(task);
+          })
+        );
+      }
+    };
 
 
-      new Uploader(uploadOption).uploadTaro(fileItem.path!, Taro.uploadFile);
+    const clearUploadQueue = (index = -1) => {
+      if (index > -1) {
+        uploadQueue.splice(index, 1);
+      } else {
+        uploadQueue = [];
+      }
+    };
+    const submit = () => {
+      Promise.all(uploadQueue).then((res) => {
+        res.forEach((i) => i.uploadTaro(Taro.uploadFile));
+      });
     };
     };
 
 
     const readFile = (files: Taro.chooseImage.ImageFile[]) => {
     const readFile = (files: Taro.chooseImage.ImageFile[]) => {
-      files.forEach((file: Taro.chooseImage.ImageFile) => {
+      files.forEach((file: Taro.chooseImage.ImageFile, index: number) => {
         const fileItem = reactive(new FileItem());
         const fileItem = reactive(new FileItem());
         fileItem.path = file.path;
         fileItem.path = file.path;
-        fileItem.status = 'uploading';
+        fileItem.status = 'ready';
         fileItem.type = file.type;
         fileItem.type = file.type;
         if (props.isPreview) {
         if (props.isPreview) {
           fileItem.url = file.path;
           fileItem.url = file.path;
         }
         }
         fileList.push(fileItem);
         fileList.push(fileItem);
-        executeUpload(fileItem);
+        executeUpload(fileItem, index);
       });
       });
     };
     };
 
 
@@ -180,6 +206,7 @@ export default create({
       return files;
       return files;
     };
     };
     const onDelete = (file: FileItem, index: number) => {
     const onDelete = (file: FileItem, index: number) => {
+      clearUploadQueue(index);
       if (props.beforeDelete(file, fileList)) {
       if (props.beforeDelete(file, fileList)) {
         fileList.splice(index, 1);
         fileList.splice(index, 1);
         emit('delete', {
         emit('delete', {
@@ -206,7 +233,9 @@ export default create({
       onDelete,
       onDelete,
       fileList,
       fileList,
       classes,
       classes,
-      chooseImage
+      chooseImage,
+      clearUploadQueue,
+      submit
     };
     };
   }
   }
 });
 });

+ 43 - 52
src/packages/__VUE/uploader/index.vue

@@ -28,11 +28,7 @@
     </view>
     </view>
 
 
     <template v-else>
     <template v-else>
-      <view
-        class="nut-uploader__preview"
-        v-for="(item, index) in fileList"
-        :key="item.uid"
-      >
+      <view class="nut-uploader__preview" v-for="(item, index) in fileList" :key="item.uid">
         <view class="nut-uploader__preview-img">
         <view class="nut-uploader__preview-img">
           <nut-icon
           <nut-icon
             v-if="isDeletable"
             v-if="isDeletable"
@@ -41,22 +37,12 @@
             class="close"
             class="close"
             name="circle-close"
             name="circle-close"
           ></nut-icon>
           ></nut-icon>
-          <img
-            class="nut-uploader__preview-img__c"
-            v-if="item.type.includes('image') && item.url"
-            :src="item.url"
-          />
-          <view class="tips" v-if="item.status != 'success'">{{
-            item.status
-          }}</view>
+          <img class="nut-uploader__preview-img__c" v-if="item.type.includes('image') && item.url" :src="item.url" />
+          <view class="tips" v-if="item.status != 'success'">{{ item.status }}</view>
         </view>
         </view>
       </view>
       </view>
       <view class="nut-uploader__upload" v-if="maximum - fileList.length">
       <view class="nut-uploader__upload" v-if="maximum - fileList.length">
-        <nut-icon
-          :size="uploadIconSize"
-          color="#808080"
-          :name="uploadIcon"
-        ></nut-icon>
+        <nut-icon :size="uploadIconSize" color="#808080" :name="uploadIcon"></nut-icon>
         <input
         <input
           class="nut-uploader__input"
           class="nut-uploader__input"
           v-if="capture"
           v-if="capture"
@@ -88,12 +74,7 @@ import { computed, reactive } from 'vue';
 import { createComponent } from '../../utils/create';
 import { createComponent } from '../../utils/create';
 import { Uploader, UploadOptions } from './uploader';
 import { Uploader, UploadOptions } from './uploader';
 const { componentName, create } = createComponent('uploader');
 const { componentName, create } = createComponent('uploader');
-export type FileItemStatus =
-  | 'ready'
-  | 'uploading'
-  | 'success'
-  | 'error'
-  | 'removed';
+export type FileItemStatus = 'ready' | 'uploading' | 'success' | 'error' | 'removed';
 export class FileItem {
 export class FileItem {
   status: FileItemStatus = 'ready';
   status: FileItemStatus = 'ready';
   uid: string = new Date().getTime().toString();
   uid: string = new Date().getTime().toString();
@@ -125,6 +106,7 @@ export default create({
     withCredentials: { type: Boolean, default: false },
     withCredentials: { type: Boolean, default: false },
     multiple: { type: Boolean, default: false },
     multiple: { type: Boolean, default: false },
     disabled: { type: Boolean, default: false },
     disabled: { type: Boolean, default: false },
+    autoUpload: { type: Boolean, default: true },
     beforeUpload: {
     beforeUpload: {
       type: Function,
       type: Function,
       default: null
       default: null
@@ -138,18 +120,11 @@ export default create({
     onChange: { type: Function }
     onChange: { type: Function }
     // customRequest: { type: Function }
     // customRequest: { type: Function }
   },
   },
-  emits: [
-    'start',
-    'progress',
-    'oversize',
-    'success',
-    'failure',
-    'change',
-    'delete',
-    'update:fileList'
-  ],
+  emits: ['start', 'progress', 'oversize', 'success', 'failure', 'change', 'delete', 'update:fileList'],
   setup(props, { emit }) {
   setup(props, { emit }) {
     const fileList = reactive(props.fileList) as Array<FileItem>;
     const fileList = reactive(props.fileList) as Array<FileItem>;
+    let uploadQueue: Promise<Uploader>[] = [];
+
     const classes = computed(() => {
     const classes = computed(() => {
       const prefixCls = componentName;
       const prefixCls = componentName;
       return {
       return {
@@ -161,7 +136,7 @@ export default create({
       el.value = '';
       el.value = '';
     };
     };
 
 
-    const executeUpload = (fileItem: FileItem) => {
+    const executeUpload = (fileItem: FileItem, index: number) => {
       const uploadOption = new UploadOptions();
       const uploadOption = new UploadOptions();
       uploadOption.url = props.url;
       uploadOption.url = props.url;
       for (const [key, value] of Object.entries(props.data)) {
       for (const [key, value] of Object.entries(props.data)) {
@@ -175,20 +150,15 @@ export default create({
       uploadOption.withCredentials = props.withCredentials;
       uploadOption.withCredentials = props.withCredentials;
       uploadOption.onStart = (option: UploadOptions) => {
       uploadOption.onStart = (option: UploadOptions) => {
         fileItem.status = 'ready';
         fileItem.status = 'ready';
+        clearUploadQueue(index);
         emit('start', option);
         emit('start', option);
       };
       };
-      uploadOption.onProgress = (
-        e: ProgressEvent<XMLHttpRequestEventTarget>,
-        option: UploadOptions
-      ) => {
+      uploadOption.onProgress = (e: ProgressEvent<XMLHttpRequestEventTarget>, option: UploadOptions) => {
         fileItem.status = 'uploading';
         fileItem.status = 'uploading';
         emit('progress', { e, option });
         emit('progress', { e, option });
       };
       };
 
 
-      uploadOption.onSuccess = (
-        responseText: XMLHttpRequest['responseText'],
-        option: UploadOptions
-      ) => {
+      uploadOption.onSuccess = (responseText: XMLHttpRequest['responseText'], option: UploadOptions) => {
         fileItem.status = 'success';
         fileItem.status = 'success';
         emit('success', {
         emit('success', {
           responseText,
           responseText,
@@ -196,30 +166,48 @@ export default create({
         });
         });
         emit('update:fileList', fileList);
         emit('update:fileList', fileList);
       };
       };
-      uploadOption.onFailure = (
-        responseText: XMLHttpRequest['responseText'],
-        option: UploadOptions
-      ) => {
+      uploadOption.onFailure = (responseText: XMLHttpRequest['responseText'], option: UploadOptions) => {
         fileItem.status = 'error';
         fileItem.status = 'error';
         emit('failure', {
         emit('failure', {
           responseText,
           responseText,
           option
           option
         });
         });
       };
       };
-      new Uploader(uploadOption).upload();
+      let task = new Uploader(uploadOption);
+      if (props.autoUpload) {
+        task.upload();
+      } else {
+        uploadQueue.push(
+          new Promise((resolve, reject) => {
+            resolve(task);
+          })
+        );
+      }
+    };
+    const clearUploadQueue = (index = -1) => {
+      if (index > -1) {
+        uploadQueue.splice(index, 1);
+      } else {
+        uploadQueue = [];
+      }
+    };
+    const submit = () => {
+      Promise.all(uploadQueue).then((res) => {
+        res.forEach((i) => i.upload());
+      });
     };
     };
 
 
     const readFile = (files: File[]) => {
     const readFile = (files: File[]) => {
-      files.forEach((file: File) => {
+      files.forEach((file: File, index: number) => {
         const formData = new FormData();
         const formData = new FormData();
         formData.append(props.name, file);
         formData.append(props.name, file);
 
 
         const fileItem = reactive(new FileItem());
         const fileItem = reactive(new FileItem());
         fileItem.name = file.name;
         fileItem.name = file.name;
-        fileItem.status = 'uploading';
+        fileItem.status = 'ready';
         fileItem.type = file.type;
         fileItem.type = file.type;
         fileItem.formData = formData;
         fileItem.formData = formData;
-        executeUpload(fileItem);
+        executeUpload(fileItem, index);
 
 
         if (props.isPreview && file.type.includes('image')) {
         if (props.isPreview && file.type.includes('image')) {
           const reader = new FileReader();
           const reader = new FileReader();
@@ -255,6 +243,7 @@ export default create({
       return files;
       return files;
     };
     };
     const onDelete = (file: FileItem, index: number) => {
     const onDelete = (file: FileItem, index: number) => {
+      clearUploadQueue(index);
       if (props.beforeDelete(file, fileList)) {
       if (props.beforeDelete(file, fileList)) {
         fileList.splice(index, 1);
         fileList.splice(index, 1);
         emit('delete', {
         emit('delete', {
@@ -297,7 +286,9 @@ export default create({
       onChange,
       onChange,
       onDelete,
       onDelete,
       fileList,
       fileList,
-      classes
+      classes,
+      clearUploadQueue,
+      submit
     };
     };
   }
   }
 });
 });

+ 3 - 2
src/packages/__VUE/uploader/uploader.ts

@@ -8,6 +8,7 @@ export class UploadOptions {
   headers = {};
   headers = {};
   withCredentials = false;
   withCredentials = false;
   onStart?: Function;
   onStart?: Function;
+  taroFilePath?: string;
   onProgress?: Function;
   onProgress?: Function;
   onSuccess?: Function;
   onSuccess?: Function;
   onFailure?: Function;
   onFailure?: Function;
@@ -50,11 +51,11 @@ export class Uploader {
       console.warn('浏览器不支持 XMLHttpRequest');
       console.warn('浏览器不支持 XMLHttpRequest');
     }
     }
   }
   }
-  uploadTaro(filePath: string, uploadFile: Function) {
+  uploadTaro(uploadFile: Function) {
     const options = this.options;
     const options = this.options;
     const uploadTask = uploadFile({
     const uploadTask = uploadFile({
       url: options.url,
       url: options.url,
-      filePath,
+      filePath: options.taroFilePath,
       header: {
       header: {
         'Content-Type': 'multipart/form-data',
         'Content-Type': 'multipart/form-data',
         ...options.headers
         ...options.headers

+ 22 - 86
src/sites/doc/components/Footer.vue

@@ -2,77 +2,36 @@
   <div class="doc-footer" :class="`doc-footer-${themeColor}`">
   <div class="doc-footer" :class="`doc-footer-${themeColor}`">
     <div class="doc-footer-content">
     <div class="doc-footer-content">
       <div class="doc-footer-list">
       <div class="doc-footer-list">
-        <img
-          class="doc-footer-logo"
-          src="../../assets/images/logo-header-red.png"
-        />
+        <img class="doc-footer-logo" src="../../assets/images/logo-header-red.png" />
       </div>
       </div>
       <div class="doc-footer-list">
       <div class="doc-footer-list">
-        <h4 class="doc-footer-title">相关资源</h4>
+        <h4 class="doc-footer-title">相关产品</h4>
+        <div class="doc-footer-item"><a class="sub-link" target="_blank" href="https://vuejs.org" v-hover>Vue</a> </div>
         <div class="doc-footer-item"
         <div class="doc-footer-item"
-          ><a class="sub-link" target="_blank" href="https://vuejs.org" v-hover
-            >Vue</a
-          >
+          ><a class="sub-link" target="_blank" href="https://vitejs.dev" v-hover>Vite</a>
         </div>
         </div>
         <div class="doc-footer-item"
         <div class="doc-footer-item"
-          ><a class="sub-link" target="_blank" href="https://vitejs.dev" v-hover
-            >Vite</a
-          >
+          ><a class="sub-link" target="_blank" href="https://relay.jd.com" v-hover>Relay</a>
         </div>
         </div>
         <div class="doc-footer-item"
         <div class="doc-footer-item"
-          ><a
-            class="sub-link"
-            target="_blank"
-            href="https://relay.jd.com"
-            v-hover
-            >Relay</a
-          >
+          ><a class="sub-link" target="_blank" href="https://taro.jd.com" v-hover>Taro</a>
         </div>
         </div>
         <div class="doc-footer-item"
         <div class="doc-footer-item"
-          ><a
-            class="sub-link"
-            target="_blank"
-            href="https://taro.jd.com"
-            v-hover
-            >Taro</a
-          >
-        </div>
-        <div class="doc-footer-item"
-          ><a
-            class="sub-link"
-            target="_blank"
-            href="https://ling.jd.com/jdw"
-            v-hover
-            >羚珑</a
-          >
+          ><a class="sub-link" target="_blank" href="https://ling.jd.com/jdw" v-hover>羚珑</a>
         </div>
         </div>
       </div>
       </div>
       <div class="doc-footer-list">
       <div class="doc-footer-list">
         <h4 class="doc-footer-title">社区</h4>
         <h4 class="doc-footer-title">社区</h4>
         <div class="doc-footer-item"
         <div class="doc-footer-item"
-          ><a
-            class="sub-link"
-            target="_blank"
-            href="https://github.com/jdf2e/nutui"
-            v-hover
-            >GitHub</a
-          ></div
+          ><a class="sub-link" target="_blank" href="https://github.com/jdf2e/nutui" v-hover>GitHub</a></div
         >
         >
         <div class="doc-footer-item"
         <div class="doc-footer-item"
-          ><a
-            class="sub-link"
-            target="_blank"
-            href="https://www.zhihu.com/column/c_1263837684834889728"
-            v-hover
+          ><a class="sub-link" target="_blank" href="https://www.zhihu.com/column/c_1263837684834889728" v-hover
             >知乎专栏</a
             >知乎专栏</a
           ></div
           ></div
         >
         >
         <div class="doc-footer-item"
         <div class="doc-footer-item"
-          ><a
-            class="sub-link"
-            target="_blank"
-            href="timline://chat/?topin=82957939&type=2"
-            v-hover
+          ><a class="sub-link" target="_blank" href="timline://chat/?topin=82957939&type=2" v-hover
             >咚咚群:82957939</a
             >咚咚群:82957939</a
           ></div
           ></div
         >
         >
@@ -87,39 +46,22 @@
             <p class="vx-desc">回复<span class="vx-red">NutUI</span>即刻进群</p>
             <p class="vx-desc">回复<span class="vx-red">NutUI</span>即刻进群</p>
           </div>
           </div>
         </div>
         </div>
+
+        <div class="doc-footer-item"><a class="sub-link" href="#/notice" target="_blank" v-hover>常见问题</a></div>
       </div>
       </div>
       <div class="doc-footer-list">
       <div class="doc-footer-list">
         <h4 class="doc-footer-title">关于我们</h4>
         <h4 class="doc-footer-title">关于我们</h4>
+        <div class="doc-footer-item"><a class="sub-link" href="#/joinus" v-hover>加入我们</a></div>
+        <div class="doc-footer-item"><a class="sub-link" href="mailto:nutui@jd.com" v-hover>联系我们</a></div>
         <div class="doc-footer-item"
         <div class="doc-footer-item"
-          ><a class="sub-link" href="#/joinus" v-hover>加入我们</a></div
+          ><a class="sub-link" target="_blank" href="https://github.com/jdf2e/nutui/issues" v-hover>意见反馈</a></div
         >
         >
         <div class="doc-footer-item"
         <div class="doc-footer-item"
-          ><a class="sub-link" href="mailto:nutui@jd.com" v-hover
-            >联系我们</a
-          ></div
-        >
-        <div class="doc-footer-item"
-          ><a
-            class="sub-link"
-            target="_blank"
-            href="https://github.com/jdf2e/nutui/issues"
-            v-hover
-            >意见反馈</a
-          ></div
-        >
-        <div class="doc-footer-item"
-          ><a class="sub-link" target="_blank" href="https://fe.jd.com" v-hover
-            >京东前端</a
-          ></div
+          ><a class="sub-link" target="_blank" href="https://fe.jd.com" v-hover>京东前端</a></div
         >
         >
       </div>
       </div>
-      <div
-        class="doc-footer-list"
-        @click.stop="data.isShowSelect = !data.isShowSelect"
-      >
-        <div class="doc-footer-select-hd"
-          ><i class="icon-color"></i>主题换肤</div
-        >
+      <div class="doc-footer-list" @click.stop="data.isShowSelect = !data.isShowSelect">
+        <div class="doc-footer-select-hd"><i class="icon-color"></i>主题换肤</div>
         <div class="doc-footer-select-bd" v-show="data.isShowSelect">
         <div class="doc-footer-select-bd" v-show="data.isShowSelect">
           <div
           <div
             class="doc-footer-select-item"
             class="doc-footer-select-item"
@@ -133,9 +75,7 @@
         </div>
         </div>
       </div>
       </div>
     </div>
     </div>
-    <p class="doc-footer-desc"
-      >2021 京东零售 - 基础业务体验部.&nbsp;All Rights Reserved.</p
-    >
+    <p class="doc-footer-desc">2021 京东零售 - 基础业务体验部.&nbsp;All Rights Reserved.</p>
   </div>
   </div>
 </template>
 </template>
 <script lang="ts">
 <script lang="ts">
@@ -173,9 +113,7 @@ export default defineComponent({
       );
       );
     };
     };
     // checked active index
     // checked active index
-    data.activeIndex = data.themeList.findIndex(
-      (i) => i.color == RefData.getInstance().themeColor.value
-    );
+    data.activeIndex = data.themeList.findIndex((i) => i.color == RefData.getInstance().themeColor.value);
     const checkTheme = (color: string, index: number) => {
     const checkTheme = (color: string, index: number) => {
       data.isShowSelect = false;
       data.isShowSelect = false;
       data.activeIndex = index;
       data.activeIndex = index;
@@ -266,8 +204,7 @@ export default defineComponent({
       width: 12px;
       width: 12px;
       height: 10px;
       height: 10px;
       margin-right: 10px;
       margin-right: 10px;
-      background: url('../../assets/images/icon-color.png') no-repeat
-        center/100%;
+      background: url('../../assets/images/icon-color.png') no-repeat center/100%;
     }
     }
     .circle-red,
     .circle-red,
     .circle-black,
     .circle-black,
@@ -424,8 +361,7 @@ export default defineComponent({
   height: 13px;
   height: 13px;
   margin-left: 5px;
   margin-left: 5px;
   vertical-align: -2px;
   vertical-align: -2px;
-  background: url('../../assets/images/icon-footer-vx.png') no-repeat
-    center/100%;
+  background: url('../../assets/images/icon-footer-vx.png') no-repeat center/100%;
 }
 }
 .vx-box {
 .vx-box {
   display: none;
   display: none;

+ 1 - 0
src/sites/doc/components/Header.vue

@@ -197,6 +197,7 @@ export default defineComponent({
         line-height: 63px;
         line-height: 63px;
         text-align: center;
         text-align: center;
         cursor: pointer;
         cursor: pointer;
+        flex-shrink: 0;
         a {
         a {
           display: inline-block;
           display: inline-block;
           line-height: 64px;
           line-height: 64px;

+ 3 - 1
src/sites/mobile-taro/vue/project.config.json

@@ -32,5 +32,7 @@
   "simulatorType": "wechat",
   "simulatorType": "wechat",
   "simulatorPluginLibVersion": {},
   "simulatorPluginLibVersion": {},
   "libVersion": "2.17.3",
   "libVersion": "2.17.3",
-  "condition": {}
+  "condition": {
+    "miniprogram": {}
+  }
 }
 }

+ 3 - 0
src/sites/mobile-taro/vue/src/base/pages/avatar/index.vue

@@ -63,4 +63,7 @@ export default {
 .demo-avatar {
 .demo-avatar {
   color: #fff;
   color: #fff;
 }
 }
+.nut-avatar {
+  margin-right: 24px;
+}
 </style>
 </style>

+ 15 - 14
src/sites/mobile-taro/vue/src/dentry/pages/uploader/index.vue

@@ -19,28 +19,23 @@
     <h2>限制上传数量5个</h2>
     <h2>限制上传数量5个</h2>
     <nut-uploader :url="uploadUrl" maximum="5"></nut-uploader>
     <nut-uploader :url="uploadUrl" maximum="5"></nut-uploader>
     <h2>限制上传大小(每个文件最大不超过 50kb)</h2>
     <h2>限制上传大小(每个文件最大不超过 50kb)</h2>
-    <nut-uploader
-      :url="uploadUrl"
-      :maximize="1024 * 50"
-      @oversize="onOversize"
-    ></nut-uploader>
+    <nut-uploader :url="uploadUrl" :maximize="1024 * 50" @oversize="onOversize"></nut-uploader>
     <h2>自定义数据 FormData 、 headers </h2>
     <h2>自定义数据 FormData 、 headers </h2>
-    <nut-uploader
-      :url="uploadUrl"
-      :data="formData"
-      :headers="formData"
-      :with-credentials="true"
-    ></nut-uploader>
+    <nut-uploader :url="uploadUrl" :data="formData" :headers="formData" :with-credentials="true"></nut-uploader>
+    <h2>手动上传 </h2>
+    <nut-uploader :url="uploadUrl" maximum="5" :auto-upload="false" ref="uploadRef"></nut-uploader>
+    <br />
+    <nut-button type="success" size="small" @click="submitUpload">执行上传</nut-button>
     <h2>禁用状态</h2>
     <h2>禁用状态</h2>
     <nut-uploader disabled></nut-uploader>
     <nut-uploader disabled></nut-uploader>
   </div>
   </div>
 </template>
 </template>
 
 
 <script lang="ts">
 <script lang="ts">
+import { ref } from 'vue';
 export default {
 export default {
   setup() {
   setup() {
-    const uploadUrl =
-      'https://my-json-server.typicode.com/linrufeng/demo/posts';
+    const uploadUrl = 'https://my-json-server.typicode.com/linrufeng/demo/posts';
     const formData = {
     const formData = {
       custom: 'test'
       custom: 'test'
     };
     };
@@ -50,12 +45,18 @@ export default {
     const onDelete = (file: any, fileList: any[]) => {
     const onDelete = (file: any, fileList: any[]) => {
       console.log('delete 事件触发', file, fileList);
       console.log('delete 事件触发', file, fileList);
     };
     };
+    const uploadRef = ref<any>(null);
+    const submitUpload = () => {
+      uploadRef.value.submit();
+    };
 
 
     return {
     return {
       onOversize,
       onOversize,
       onDelete,
       onDelete,
       uploadUrl,
       uploadUrl,
-      formData
+      formData,
+      uploadRef,
+      submitUpload
     };
     };
   }
   }
 };
 };

+ 5 - 8
src/sites/mobile-taro/vue/src/feedback/pages/infiniteloading/index.vue

@@ -2,7 +2,7 @@
   <view class="demo">
   <view class="demo">
     <h2>基础演示</h2>
     <h2>基础演示</h2>
     <nut-cell>
     <nut-cell>
-      <view-block class="infiniteUl" id="scrollDemo">
+      <view class="infiniteUl" id="scrollDemo">
         <nut-infiniteloading
         <nut-infiniteloading
           pull-icon="JD"
           pull-icon="JD"
           load-txt="loading"
           load-txt="loading"
@@ -13,14 +13,9 @@
           @load-more="loadMore"
           @load-more="loadMore"
           @refresh="refresh"
           @refresh="refresh"
         >
         >
-          <view-block
-            class="infiniteLi"
-            v-for="(item, index) in defultList"
-            :key="index"
-            >{{ item }}</view-block
-          >
+          <view class="infiniteLi" v-for="(item, index) in defultList" :key="index">{{ item }}</view>
         </nut-infiniteloading>
         </nut-infiniteloading>
-      </view-block>
+      </view>
     </nut-cell>
     </nut-cell>
   </view>
   </view>
 </template>
 </template>
@@ -81,6 +76,8 @@ export default {
 .infiniteUl {
 .infiniteUl {
   height: 500px;
   height: 500px;
   width: 100%;
   width: 100%;
+  padding: 0;
+  margin: 0;
   overflow-y: auto;
   overflow-y: auto;
   overflow-x: hidden;
   overflow-x: hidden;
 }
 }

+ 4 - 2
vite.config.build.taro.vue.disperse.ts

@@ -14,9 +14,11 @@ let input = {};
 
 
 configPkg.nav.map((item) => {
 configPkg.nav.map((item) => {
   item.packages.forEach((element) => {
   item.packages.forEach((element) => {
-    let { name, show, taro, type, exportEmpty } = element;
+    let { name, show, taro, type, exportEmpty, exportEmptyTaro } = element;
     if (taro && (show || exportEmpty)) {
     if (taro && (show || exportEmpty)) {
-      input[name] = `./src/packages/__VUE/${name.toLowerCase()}/index${exportEmpty ? '.vue' : '.taro.vue'}`;
+      input[name] = `./src/packages/__VUE/${name.toLowerCase()}/index${
+        exportEmpty && !exportEmptyTaro ? '.vue' : '.taro.vue'
+      }`;
     }
     }
   });
   });
 });
 });