Browse Source

Merge branch 'next' into next

yangxiaolu1993 3 years ago
parent
commit
5351b540f4

+ 2 - 2
.npmrc

@@ -1,2 +1,2 @@
-registry=https://registry.npmjs.org
-engine-strict=true
+# registry=https://registry.npmjs.org
+# engine-strict=true

+ 5 - 0
demo.html

@@ -11,6 +11,11 @@
     <meta http-equiv="Expires" content="0" />
   </head>
   <body>
+      <script src='https://cdn.bootcss.com/vConsole/3.3.2/vconsole.min.js'></script>
+      <!-- <script type="text/javascript">
+
+      window.vConsole = new window.VConsole();
+      console.log("错误");</script> -->
     <noscript>
       <strong
         >We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work

+ 1 - 0
package.json

@@ -80,6 +80,7 @@
   "devDependencies": {
     "@commitlint/cli": "^10.0.0",
     "@commitlint/config-conventional": "^10.0.0",
+    "@jd/upload-oss-tools": "^1.1.21",
     "@popperjs/core": "^2.11.5",
     "@tarojs/taro": "^3.4.0",
     "@types/jest": "^26.0.22",

+ 66 - 48
src/packages/__VUE/imagepreview/__tests__/__snapshots__/imagepreview.spec.ts.snap

@@ -9,65 +9,83 @@ exports[`video surported in H5 env 1`] = `
     <view class=\\"nut-swiper nut-imagepreview-swiper\\">
       <view class=\\"nut-swiper-inner\\">
         <view class=\\"nut-swiper-item\\">
-          <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>
+          <!--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 class=\\"played\\"></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 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\\">
-          <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>
+          <!--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 class=\\"played\\"></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 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 class=\\"nut-swiper-item\\"><img src=\\"//m.360buyimg.com/mobilecms/s750x366_jfs/t1/18629/34/3378/144318/5c263f64Ef0e2bff0/0d650e0aa2e852ee.jpg\\" class=\\"nut-imagepreview-img\\"></view>
-        <view class=\\"nut-swiper-item\\"><img src=\\"//m.360buyimg.com/mobilecms/s750x366_jfs/t1/26597/30/4870/174583/5c35c5d2Ed55eedc6/50e27870c25e7a82.png\\" class=\\"nut-imagepreview-img\\"></view>
-        <view class=\\"nut-swiper-item\\"><img src=\\"//m.360buyimg.com/mobilecms/s750x366_jfs/t1/9542/17/12873/201687/5c3c4362Ea9eb757d/60026b40a9d60d85.jpg\\" class=\\"nut-imagepreview-img\\"></view>
-        <view class=\\"nut-swiper-item\\"><img src=\\"//m.360buyimg.com/mobilecms/s750x366_jfs/t1/30042/36/427/82951/5c3bfdabE3faf2f66/9adca782661c988c.jpg\\" class=\\"nut-imagepreview-img\\"></view>
       </view>
       <!--v-if-->
     </view>

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

@@ -97,22 +97,6 @@ test('customize pagination and color', async () => {
   expect(swiperPagination.findAll('i')[0].element.style.backgroundColor).toEqual('red');
 });
 
-// test('test content-close', async () => {
-//   const wrapper = mount(ImagePreview, {
-//     props: {
-//       show: true,
-//       images,
-//       autoplay: 0,
-//       contentClose: true
-//     }
-//   });
-//   await nextTick();
-
-//   // const key = wrapper.find('.nut-imagepreview');
-//   // await key.trigger('click');
-//   // expect((wrapper.find('.custom-pop').element as any).style.display).toEqual('none');
-// });
-
 test('video surported in H5 env', async () => {
   const wrapper = mount(ImagePreview, {
     props: {
@@ -124,17 +108,3 @@ test('video surported in H5 env', async () => {
   await nextTick();
   expect(wrapper.find('.custom-pop').html()).toMatchSnapshot();
 });
-
-// test('close event trigged', async () => {
-//   const wrapper = mount(ImagePreview, {
-//     props: {
-//       show: true,
-//       images,
-//     }
-//   });
-//   await nextTick();
-//   const overlay: any = wrapper.find('.nut-overlay');
-//   await overlay.trigger('click');
-//   expect(wrapper.emitted('close')).toBeTruthy();
-//   expect(wrapper.emitted('update:show')).toBeFalsy();
-// });

+ 3 - 0
src/packages/__VUE/imagepreview/demo.vue

@@ -67,6 +67,9 @@ export default createDemo({
           src: '//m.360buyimg.com/mobilecms/s750x366_jfs/t1/18629/34/3378/144318/5c263f64Ef0e2bff0/0d650e0aa2e852ee.jpg'
         },
         {
+          src: '//m.360buyimg.com/mobilecms/s750x366_jfs/t1/18629/34/3378/144318/5c263f64Ef0e2bff0/0d650e0aa2e852ee.jpg'
+        },
+        {
           src: '//m.360buyimg.com/mobilecms/s750x366_jfs/t1/26597/30/4870/174583/5c35c5d2Ed55eedc6/50e27870c25e7a82.png'
         },
         {

+ 2 - 0
src/packages/__VUE/imagepreview/doc.en-US.md

@@ -314,6 +314,8 @@ app.use(ImagePreview);
 | close-icon`v3.1.22`   | Close icon name    | String  | ‘circle-close’  |
 | close-icon-position`v3.1.22`   |  Close icon position,can be set to `top-left`   | String  | ‘top-right’  |  
 | before-close`v3.1.22`  | Callback function before close   | (active: number) => boolean | Promise<`boolean`>  | -  | 
+| max-zoom`v3.1.23`  | Max zoom`Taro isn't supported`   | Number  | 3  | 
+| min-zoom`v3.1.23`  | Min zoom`Taro isn't supported`   | Number  | 1/3  | 
 
 
     

+ 2 - 1
src/packages/__VUE/imagepreview/doc.md

@@ -314,7 +314,8 @@ app.use(ImagePreview);
 | close-icon`v3.1.22`   | 关闭图片名称或图片链接    | String  | ‘circle-close’  |
 | close-icon-position`v3.1.22`   | 关闭图标位置,可选值:top-left   | String  | ‘top-right’  |  
 | before-close`v3.1.22`  | 关闭前的回调函数,返回 false 可阻止关闭,支持返回 Promise   | (active: number) => boolean | Promise<`boolean`>  | -  | 
-
+| max-zoom`v3.1.23`  | 手势缩放时,最大缩放比例`小程序暂不支持`   | Number  | 3  | 
+| min-zoom`v3.1.23`  | 手势缩放时,最小缩放比例`小程序暂不支持`   | Number  | 1/3  | 
 
 ### Events
 

+ 315 - 0
src/packages/__VUE/imagepreview/imagePreviewItem.vue

@@ -0,0 +1,315 @@
+<template>
+  <nut-swiper-item @touchstart="onTouchStart" @touchmove="onTouchMove" @touchend="onTouchEnd" @touchcancel="onTouchEnd">
+    <view :style="imageStyle" class="nut-imagepreview-box" v-if="image && image.src">
+      <img :src="image.src" class="nut-imagepreview-img" @load="imageLoad" />
+    </view>
+
+    <view class="nut-imagepreview-box" v-if="video">
+      <nut-video :source="video.source" :options="video.options"></nut-video>
+    </view>
+  </nut-swiper-item>
+</template>
+<script lang="ts">
+import { toRefs, reactive, watch, onMounted, ref, computed, CSSProperties } from 'vue';
+import { createComponent } from '@/packages/utils/create';
+import Popup from '../popup/index.vue';
+import Video from '../video/index.vue';
+import Swiper from '../swiper/index.vue';
+import SwiperItem from '../swiperitem/index.vue';
+import Icon from '../icon/index.vue';
+import { isPromise } from '@/packages/utils/util.ts';
+import { useTouch } from '@/packages/utils/useTouch';
+const { componentName, create } = createComponent('imagepreviewitem');
+
+export default create({
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    },
+    initNo: Number,
+    image: {
+      type: Object,
+      default: () => {}
+    },
+    video: {
+      type: Array,
+      default: () => {}
+    },
+
+    showIndex: {
+      type: Boolean,
+      default: true
+    },
+    rootWidth: {
+      type: Number,
+      default: 0
+    },
+    rootHeight: {
+      type: Number,
+      default: 0
+    },
+    minZoom: {
+      type: Number,
+      default: 1 / 3
+    },
+    maxZoom: {
+      type: Number,
+      default: 3
+    }
+  },
+  emits: ['close', 'scale'],
+  components: {
+    [Popup.name]: Popup,
+    [Video.name]: Video,
+    [Swiper.name]: Swiper,
+    [SwiperItem.name]: SwiperItem,
+    [Icon.name]: Icon
+  },
+
+  setup(props, { emit }) {
+    const state = reactive({
+      scale: 1,
+      moveX: 0,
+      moveY: 0,
+      moving: false,
+      zooming: false,
+      imageRatio: 0,
+      displayWidth: 0,
+      displayHeight: 0
+    });
+
+    const touch = useTouch();
+
+    const vertical = computed(() => {
+      const { rootWidth, rootHeight } = props;
+      const rootRatio = rootHeight / rootWidth;
+      return state.imageRatio > rootRatio;
+    });
+
+    // 图片放大
+    const imageStyle = computed(() => {
+      const { scale, moveX, moveY, moving, zooming } = state;
+      const style: CSSProperties = {
+        transitionDuration: zooming || moving ? '0s' : '.3s'
+      };
+
+      if (scale !== 1) {
+        const offsetX = moveX / scale;
+        const offsetY = moveY / scale;
+        style.transform = `scale(${scale}, ${scale}) translate(${offsetX}px, ${offsetY}px)`;
+      }
+
+      return style;
+    });
+
+    const maxMoveX = computed(() => {
+      if (state.imageRatio) {
+        const { rootWidth, rootHeight } = props;
+        const displayWidth = vertical.value ? rootHeight / state.imageRatio : rootWidth;
+
+        return Math.max(0, (state.scale * displayWidth - rootWidth) / 2);
+      }
+
+      return 0;
+    });
+
+    const maxMoveY = computed(() => {
+      if (state.imageRatio) {
+        const { rootWidth, rootHeight } = props;
+        const displayHeight = vertical.value ? rootHeight : rootWidth * state.imageRatio;
+
+        return Math.max(0, (state.scale * displayHeight - rootHeight) / 2);
+      }
+
+      return 0;
+    });
+
+    // 图片加载完成
+    const imageLoad = (event: any) => {
+      const { naturalWidth, naturalHeight } = event.target as HTMLImageElement;
+      state.imageRatio = naturalHeight / naturalWidth;
+    };
+
+    // 重设缩放
+    const resetScale = () => {
+      setScale(1);
+      state.moveX = 0;
+      state.moveY = 0;
+    };
+    // 设置缩放
+    const setScale = (scale: number) => {
+      scale = clamp(scale, +props.minZoom, +props.maxZoom + 1);
+      if (scale !== state.scale) {
+        state.scale = scale;
+        emit('scale', {
+          scale,
+          index: props.initNo
+        });
+      }
+    };
+
+    const toggleScale = () => {
+      const scale = state.scale > 1 ? 1 : 2;
+
+      setScale(scale);
+      state.moveX = 0;
+      state.moveY = 0;
+    };
+
+    // 计算两个点的距离
+    const getDistance = (touches: TouchList) =>
+      Math.sqrt((touches[0].clientX - touches[1].clientX) ** 2 + (touches[0].clientY - touches[1].clientY) ** 2);
+
+    let startMoveX: number;
+    let startMoveY: number;
+    let startScale: number;
+    let startDistance: number;
+    let doubleTapTimer: null;
+    let touchStartTime: number;
+    let fingerNum: number;
+
+    const onTouchStart = (event: TouchEvent) => {
+      const { touches } = event;
+      const { offsetX } = touch;
+
+      touch.start(event);
+
+      fingerNum = touches.length;
+      startMoveX = state.moveX;
+      startMoveY = state.moveY;
+      touchStartTime = Date.now();
+
+      state.moving = fingerNum === 1 && state.scale !== 1;
+
+      state.zooming = fingerNum === 2 && !offsetX.value;
+      if (state.zooming) {
+        startScale = state.scale;
+        startDistance = getDistance(event.touches);
+      }
+    };
+
+    const onTouchMove = (event: TouchEvent) => {
+      const { touches } = event;
+
+      touch.move(event);
+
+      if (state.moving || state.zooming) {
+        preventDefault(event, true);
+      }
+
+      if (state.moving) {
+        const { deltaX, deltaY } = touch;
+        const moveX = deltaX.value + startMoveX;
+        const moveY = deltaY.value + startMoveY;
+        state.moveX = clamp(moveX, -maxMoveX.value, maxMoveX.value);
+        state.moveY = clamp(moveY, -maxMoveY.value, maxMoveY.value);
+      }
+
+      if (state.zooming && touches.length === 2) {
+        const distance = getDistance(touches);
+        const scale = (startScale * distance) / startDistance;
+
+        setScale(scale);
+      }
+    };
+
+    const checkTap = () => {
+      if (fingerNum > 1) {
+        return;
+      }
+
+      const { offsetX, offsetY } = touch;
+      const deltaTime = Date.now() - touchStartTime;
+      const TAP_TIME = 250;
+      const TAP_OFFSET = 5;
+
+      if (offsetX.value < TAP_OFFSET && offsetY.value < TAP_OFFSET && deltaTime < TAP_TIME) {
+        if (doubleTapTimer) {
+          clearTimeout(doubleTapTimer);
+          doubleTapTimer = null;
+          toggleScale();
+        } else {
+          doubleTapTimer = setTimeout(() => {
+            emit('close');
+            doubleTapTimer = null;
+          }, TAP_TIME);
+        }
+      }
+    };
+
+    const onTouchEnd = (event: TouchEvent) => {
+      let stopPropagation = false;
+
+      /* istanbul ignore else */
+      if (state.moving || state.zooming) {
+        stopPropagation = true;
+
+        if (state.moving && startMoveX === state.moveX && startMoveY === state.moveY) {
+          stopPropagation = false;
+        }
+
+        if (!event.touches.length) {
+          if (state.zooming) {
+            state.moveX = clamp(state.moveX, -maxMoveX.value, maxMoveX.value);
+            state.moveY = clamp(state.moveY, -maxMoveY.value, maxMoveY.value);
+            state.zooming = false;
+          }
+
+          state.moving = false;
+          startMoveX = 0;
+          startMoveY = 0;
+          startScale = 1;
+
+          if (state.scale < 1) {
+            resetScale();
+          }
+
+          if (state.scale > props.maxZoom) {
+            state.scale = +props.maxZoom;
+          }
+        }
+      }
+
+      // eliminate tap delay on safari
+      preventDefault(event, stopPropagation);
+
+      checkTap();
+      touch.reset();
+    };
+
+    // 阻止
+    const preventDefault = (event: any, isStopPropagation?: boolean) => {
+      if (typeof event.cancelable !== 'boolean' || event.cancelable) {
+        event.preventDefault();
+      }
+
+      if (isStopPropagation) {
+        event.stopPropagation();
+      }
+    };
+
+    const clamp = (num: number, min: number, max: number): number => Math.min(Math.max(num, min), max);
+
+    watch(() => props.initNo, resetScale);
+    watch(
+      () => props.show,
+      (value) => {
+        if (!value) {
+          resetScale();
+        }
+      }
+    );
+
+    return {
+      ...toRefs(state),
+      onTouchStart,
+      onTouchMove,
+      onTouchEnd,
+      getDistance,
+      imageStyle,
+      imageLoad
+    };
+  }
+});
+</script>

+ 6 - 1
src/packages/__VUE/imagepreview/index.scss

@@ -59,8 +59,13 @@
   justify-content: center;
   height: 100%;
 
+
+  .nut-imagepreview-box {
+    width: 100%;
+  }
+
   .nut-video {
-    height: auto;
+    // height: auto;
     video {
       object-fit: contain;
     }

+ 62 - 141
src/packages/__VUE/imagepreview/index.vue

@@ -7,7 +7,7 @@
     style="width: 100%"
   >
     <!-- @click.stop="closeOnImg" @touchstart.capture="onTouchStart" -->
-    <view class="nut-imagepreview" @click.stop="closeOnImg" @touchstart.capture="onTouchStart">
+    <view class="nut-imagepreview" ref="swipeRef">
       <nut-swiper
         v-if="showPop"
         :auto-play="autoplay"
@@ -20,12 +20,31 @@
         :pagination-visible="paginationVisible"
         :pagination-color="paginationColor"
       >
-        <nut-swiper-item v-for="(item, index) in videos" :key="index">
-          <nut-video :source="item.source" :options="item.options"></nut-video>
-        </nut-swiper-item>
-        <nut-swiper-item v-for="(item, index) in images" :key="index">
-          <img :src="item.src" class="nut-imagepreview-img" />
-        </nut-swiper-item>
+        <template v-if="videos.length">
+          <image-preview-item
+            v-for="(item, index) in videos"
+            :key="index"
+            :video="item"
+            :rootHeight="rootHeight"
+            :rootWidth="rootWidth"
+            :show="showPop"
+            :init-no="active"
+            @close="onClose"
+            :maxZoom="maxZoom"
+            :minZoom="minZoom"
+          ></image-preview-item>
+        </template>
+
+        <template v-for="(item, index) in images" :key="index">
+          <image-preview-item
+            :image="item"
+            :rootHeight="rootHeight"
+            :rootWidth="rootWidth"
+            :show="showPop"
+            :init-no="active"
+            @close="onClose"
+          ></image-preview-item>
+        </template>
       </nut-swiper>
     </view>
     <view class="nut-imagepreview-index" v-if="showIndex"> {{ active }} / {{ images.length + videos.length }} </view>
@@ -43,6 +62,7 @@ import Swiper from '../swiper/index.vue';
 import SwiperItem from '../swiperitem/index.vue';
 import Icon from '../icon/index.vue';
 import { isPromise } from '@/packages/utils/util.ts';
+import ImagePreviewItem from './imagePreviewItem.vue';
 const { componentName, create } = createComponent('imagepreview');
 
 export default create({
@@ -77,7 +97,7 @@ export default create({
     },
     autoplay: {
       type: [Number, String],
-      default: 3000
+      default: 0
     },
     isWrapTeleport: {
       type: Boolean,
@@ -99,7 +119,15 @@ export default create({
       type: String,
       default: 'top-right' // top-right  top-left
     },
-    beforeClose: Function
+    beforeClose: Function,
+    minZoom: {
+      type: Number,
+      default: 1 / 3
+    },
+    maxZoom: {
+      type: Number,
+      default: 3
+    }
   },
   emits: ['close', 'change'],
   components: {
@@ -107,30 +135,31 @@ export default create({
     [Video.name]: Video,
     [Swiper.name]: Swiper,
     [SwiperItem.name]: SwiperItem,
-    [Icon.name]: Icon
+    [Icon.name]: Icon,
+    ImagePreviewItem: ImagePreviewItem
   },
 
   setup(props, { emit }) {
     const { show, images } = toRefs(props);
 
+    const swipeRef = ref();
+
     const state = reactive({
       showPop: false,
       active: 1,
       maxNo: 1,
-      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
-      },
-      eleImg: null,
-      store: {
-        scale: 1,
-        moveable: false
-      },
-      lastTouchEndTime: 0 // 用来辅助监听双击
+      rootWidth: 0,
+      rootHeight: 0
+    });
+
+    const styles = computed(() => {
+      let style: any = {};
+      if (props.closeIconPosition == 'top-right') {
+        style.right = '10px';
+      } else {
+        style.left = '10px';
+      }
+      return style;
     });
 
     const styles = computed(() => {
@@ -148,13 +177,6 @@ export default create({
       emit('change', state.active);
     };
 
-    const closeOnImg = () => {
-      // 点击内容区域的图片是否可以关闭弹层(视频区域由于nut-video做了限制,无法关闭弹层)
-      if (props.contentClose) {
-        onClose();
-      }
-    };
-
     const onClose = () => {
       if (props.beforeClose) {
         const returnVal = props.beforeClose.apply(null, state.active);
@@ -175,110 +197,13 @@ export default create({
     // 执行关闭
     const closeDone = () => {
       state.showPop = false;
-      state.store.scale = 1;
-      scaleNow();
       state.active = 1;
       emit('close');
     };
 
-    // 计算两个点的距离
-    const getDistance = (first: any, second: any) => {
-      // 计算两个点起始时刻的距离和终止时刻的距离,终止时刻距离变大了则放大,变小了则缩小
-      // 放大 k 倍则 scale 也 扩大 k 倍
-      return Math.hypot(Math.abs(second.x - first.x), Math.abs(second.y - first.y));
-    };
-
-    const scaleNow = () => {
-      (state.eleImg as any).style.transform = 'scale(' + state.store.scale + ')';
-    };
-
-    const onTouchStart = (event: any) => {
-      // console.log('start');
-      // 如果已经放大,双击应变回原尺寸;如果是原尺寸,双击应放大
-      const curTouchTime = new Date().getTime();
-      if (curTouchTime - state.lastTouchEndTime < 300) {
-        const store = state.store;
-        if (store.scale > 1) {
-          store.scale = 1;
-        } else if (store.scale == 1) {
-          store.scale = 2;
-        }
-        scaleNow();
-      }
-
-      var touches = event.touches;
-      var events = touches[0];
-      var events2 = touches[1];
-
-      // event.preventDefault();
-
-      const store = state.store as any;
-      store.moveable = true;
-
-      if (events2) {
-        // 如果开始两指操作,记录初始时刻两指间的距离
-        store.oriDistance = getDistance(
-          {
-            x: events.pageX,
-            y: events.pageY
-          },
-          {
-            x: events2.pageX,
-            y: events2.pageY
-          }
-        );
-      }
-      // 取到开始两指操作时的放大(缩小比例),store.scale 存储的是当前的放缩比(相对于标准大小 scale 为 1 的情况的放大缩小比)
-      store.originScale = store.scale || 1;
-    };
-
-    const onTouchMove = (event: any) => {
-      if (!state.store.moveable) {
-        return;
-      }
-      const store = state.store as any;
-      // event.preventDefault();
-      var touches = event.touches;
-      var events = touches[0];
-      var events2 = touches[1];
-      // 双指移动
-      if (events2) {
-        // 获得当前两点间的距离
-        const curDistance = getDistance(
-          {
-            x: events.pageX,
-            y: events.pageY
-          },
-          {
-            x: events2.pageX,
-            y: events2.pageY
-          }
-        );
-
-        /** 此处计算倍数,距离放大(缩小) k 倍则 scale 也 扩大(缩小) k 倍。距离放大(缩小)倍数 = 结束时两点距离 除以 开始时两点距离
-         * 注意此处的 scale 变化是基于 store.scale 的。
-         * store.scale 是一个暂存值,比如第一次放大 2 倍,则 store.scale 为 2。
-         * 再次两指触碰的时候,store.originScale 就为 store.scale 的值,基于此时的 store.scale 继续放大缩小。 **/
-        const curScale = curDistance / store.oriDistance;
-        store.scale = store.originScale * curScale;
-
-        // 最大放大 3 倍,缩小后松手要弹回原比例
-        if (store.scale > 3) {
-          store.scale = 3;
-        }
-        scaleNow();
-      }
-    };
-
-    const onTouchEnd = () => {
-      // console.log('end');
-      state.lastTouchEndTime = new Date().getTime();
-      const store = state.store as any;
-      store.moveable = false;
-      if ((store.scale < 1.1 && store.scale > 1) || store.scale < 1) {
-        store.scale = 1;
-        scaleNow();
-      }
+    // 点击关闭按钮
+    const handleCloseIcon = () => {
+      onClose();
     };
     // 点击关闭按钮
     const handleCloseIcon = () => {
@@ -286,10 +211,10 @@ export default create({
     };
 
     const init = () => {
-      state.eleImg = document.querySelector('.nut-imagepreview') as any;
-      document.addEventListener('touchmove', onTouchMove);
-      document.addEventListener('touchend', onTouchEnd);
-      document.addEventListener('touchcancel', onTouchEnd);
+      setTimeout(() => {
+        state.rootHeight = swipeRef.value.offsetHeight;
+        state.rootWidth = swipeRef.value.offsetWidth;
+      }, 100);
     };
 
     watch(
@@ -308,15 +233,11 @@ export default create({
     });
 
     return {
+      swipeRef,
       ...toRefs(state),
       slideChangeEnd,
       onClose,
-      closeOnImg,
-      onTouchStart,
-      onTouchMove,
-      onTouchEnd,
-      getDistance,
-      scaleNow,
+      handleCloseIcon,
       styles
     };
   }

+ 5 - 2
src/sites/mobile-taro/vue/project.private.config.json

@@ -1,5 +1,7 @@
 {
-  "setting": {},
+  "setting": {
+    "compileHotReLoad": true
+  },
   "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
   "condition": {
     "miniprogram": {
@@ -51,5 +53,6 @@
         }
       ]
     }
-  }
+  },
+  "projectname": "%40nutui%2Fnutui-taro-mobile"
 }

+ 1 - 0
src/sites/mobile-taro/vue/src/app.config.ts

@@ -85,6 +85,7 @@ const subPackages = [
       'pages/skeleton/index',
       'pages/collapse/index',
       'pages/table/index',
+      'pages/animate/index',
       'pages/ellipsis/index'
     ]
   },