浏览代码

upd: searchbar imagepreview issue fix (#1033)

* upd: searchbar-点击回车刷新页面修复;imagepreview-issue-引用报错
JackieScorpio 3 年之前
父节点
当前提交
1bcf32d690

+ 1 - 2
src/config.json

@@ -194,8 +194,7 @@
           "desc": "图片预览",
           "sort": 4,
           "show": true,
-          "taro": false,
-          "tarodoc": true,
+          "taro": true,
           "author": "zongyue3"
         },
         {

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

@@ -0,0 +1,26 @@
+import { mount } from '@vue/test-utils';
+import ImagePreview from '../index.vue';
+
+test('render basic correctly', () => {
+  const wrapper = mount(ImagePreview, {
+    props: {
+      show: true,
+      images: [
+        {
+          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'
+        },
+        {
+          src: '//m.360buyimg.com/mobilecms/s750x366_jfs/t1/9542/17/12873/201687/5c3c4362Ea9eb757d/60026b40a9d60d85.jpg'
+        },
+        {
+          src: '//m.360buyimg.com/mobilecms/s750x366_jfs/t1/30042/36/427/82951/5c3bfdabE3faf2f66/9adca782661c988c.jpg'
+        }
+      ]
+    }
+  });
+
+  expect(wrapper.html()).toMatchSnapshot();
+});

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

@@ -168,6 +168,8 @@ app.use(ImagePreview);
 :::
 
 ### 视频、图片预览
+#### taro 场景暂不支持视频预览功能
+
 :::demo
 ```html
 <template>
@@ -243,6 +245,8 @@ app.use(ImagePreview);
 :::
                 
 ### 函数式调用
+#### taro 场景暂不支持此功能
+
 :::demo
 ```html
 <template>
@@ -298,7 +302,7 @@ app.use(ImagePreview);
 | 字段 | 说明 | 类型 | 默认值
 |----- | ----- | ----- | ----- 
 | show | 是否展示预览图片 | Boolean | false
-| videos | 预览的视频数组(视频自动放到图片之前) | Array<`Object`> | []
+| videos | 预览的视频数组(视频自动放到图片之前、taro场景暂不支持) | Array<`Object`> | []
 | images | 预览图片数组 | Array<`String`> | []
 | autoplay | 自动轮播时长,0表示不会自动轮播 | Number、String  | 3000  |
 | init-no | 初始页码 | Number | 1

+ 208 - 23
src/packages/__VUE/imagepreview/index.taro.vue

@@ -1,26 +1,37 @@
 <template>
-  <view class="nut-imagepreview">
-    <nut-popup pop-class="custom-pop" v-model:visible="showPop" @click="closePop">
-      <swiper
+  <nut-popup pop-class="custom-pop" v-model:visible="showPop" @click="onClose" style="width: 100%">
+    <view class="nut-imagepreview" @click.stop="closeOnImg" @touchstart.capture="onTouchStart">
+      <nut-swiper
         v-if="showPop"
-        :autoplay="true"
-        :interval="3000"
+        :auto-play="autoplay"
         class="nut-imagepreview-swiper"
-        :circular="true"
-        :onChange="slideChangeEnd"
+        :loop="true"
+        :is-preventDefault="false"
+        direction="horizontal"
+        @change="slideChangeEnd"
+        :init-page="initNo > maxNo ? maxNo - 1 : initNo - 1"
+        :pagination-visible="paginationVisible"
+        :pagination-color="paginationColor"
       >
-        <swiper-item v-for="(item, index) in images" :key="index">
-          <img :src="item.imgSrc" class="nut-imagepreview-img" />
-        </swiper-item>
-      </swiper>
-      <view class="nut-imagepreview-index"> {{ active }} / {{ images.length }} </view>
-    </nut-popup>
-  </view>
+        <!-- <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>
+      </nut-swiper>
+    </view>
+    <!-- <view class="nut-imagepreview-index"> {{ active }} / {{ images.length + videos.length }} </view> -->
+    <view class="nut-imagepreview-index"> {{ active }} / {{ images.length }} </view>
+  </nut-popup>
 </template>
 <script lang="ts">
-import { toRefs, reactive, watch } from 'vue';
+import { toRefs, reactive, watch, onMounted, ref } from 'vue';
 import { createComponent } from '../../utils/create';
 import Popup from '../popup/index.taro.vue';
+// import Video from '../video/index.vue';
+import Swiper from '../swiper/index.taro.vue';
+import SwiperItem from '../swiperitem/index.taro.vue';
 const { componentName, create } = createComponent('imagepreview');
 
 export default create({
@@ -32,41 +43,215 @@ export default create({
     images: {
       type: Array,
       default: () => []
+    },
+    // videos: {
+    //   type: Array,
+    //   default: () => []
+    // },
+    contentClose: {
+      type: Boolean,
+      default: false
+    },
+    initNo: {
+      type: Number,
+      default: 1
+    },
+    paginationVisible: {
+      type: Boolean,
+      default: false
+    },
+    paginationColor: {
+      type: String,
+      default: '#fff'
+    },
+    autoplay: {
+      type: [Number, String],
+      default: 3000
     }
   },
+  emits: ['close'],
   components: {
-    [Popup.name]: Popup
+    [Popup.name]: Popup,
+    // [Video.name]: Video,
+    [Swiper.name]: Swiper,
+    [SwiperItem.name]: SwiperItem
   },
 
   setup(props, { emit }) {
-    const { value, images } = toRefs(props);
+    const { show, images } = toRefs(props);
 
     const state = reactive({
-      showPop: value,
-      active: 1
+      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 // 用来辅助监听双击
     });
 
-    const slideChangeEnd = function (page: any) {
-      state.active = page.detail.current + 1;
+    const slideChangeEnd = function (page: number) {
+      state.active = page + 1;
     };
 
-    const closePop = function () {
+    const closeOnImg = () => {
+      // 点击内容区域的图片是否可以关闭弹层(视频区域由于nut-video做了限制,无法关闭弹层)
+      if (props.contentClose) {
+        onClose();
+      }
+    };
+
+    const onClose = () => {
       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 init = () => {
+      state.eleImg = document.querySelector('.nut-imagepreview') as any;
+      document.addEventListener('touchmove', onTouchMove);
+      document.addEventListener('touchend', onTouchEnd);
+      document.addEventListener('touchcancel', onTouchEnd);
+    };
+
     watch(
       () => props.show,
       (val) => {
         state.showPop = val;
+        init();
       }
     );
 
+    onMounted(() => {
+      // 初始化页码
+      state.active = props.initNo;
+      state.showPop = props.show;
+      // state.maxNo = props.images.length + props.videos.length;
+      state.maxNo = props.images.length;
+    });
+
     return {
       ...toRefs(state),
       slideChangeEnd,
-      closePop
+      onClose,
+      closeOnImg,
+      onTouchStart,
+      onTouchMove,
+      onTouchEnd,
+      getDistance,
+      scaleNow
     };
   }
 });

+ 88 - 0
src/packages/__VUE/searchbar/__tests__/__snapshots__/searchbar.spec.ts.snap

@@ -0,0 +1,88 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`render basic correctly 1`] = `
+"<view class=\\"nut-searchbar\\">
+  <!--v-if-->
+  <view class=\\"nut-searchbar__search-input\\">
+    <!--v-if-->
+    <view class=\\"nut-searchbar__input-inner\\">
+      <form action=\\"#\\"><input class=\\"nut-searchbar__input-bar\\" type=\\"text\\" maxlength=\\"9999\\" placeholder=\\"请输入\\"></form>
+      <view class=\\"nut-searchbar__input-clear\\" style=\\"display: none;\\">
+        <nut-icon name=\\"circle-close\\" size=\\"12\\" color=\\"#555\\"></nut-icon>
+      </view>
+    </view>
+    <!--v-if-->
+  </view>
+  <!--v-if-->
+</view>"
+`;
+
+exports[`render correctly 1`] = `
+"<view class=\\"nut-searchbar\\">
+  <!--v-if-->
+  <view class=\\"nut-searchbar__search-input\\">
+    <!--v-if-->
+    <view class=\\"nut-searchbar__input-inner\\">
+      <form action=\\"#\\"><input class=\\"nut-searchbar__input-bar\\" type=\\"text\\" maxlength=\\"9999\\" placeholder=\\"请输入\\"></form>
+      <view class=\\"nut-searchbar__input-clear\\" style=\\"display: none;\\">
+        <nut-icon name=\\"circle-close\\" size=\\"12\\" color=\\"#555\\"></nut-icon>
+      </view>
+    </view>
+    <!--v-if-->
+  </view>
+  <!--v-if-->
+</view>"
+`;
+
+exports[`should emit search event when enter key is pressed 1`] = `undefined`;
+
+exports[`should render background prop correctly 1`] = `
+"<view class=\\"nut-searchbar\\">
+  <!--v-if-->
+  <view class=\\"nut-searchbar__search-input\\">
+    <!--v-if-->
+    <view class=\\"nut-searchbar__input-inner\\">
+      <form action=\\"#\\"><input class=\\"nut-searchbar__input-bar\\" type=\\"text\\" maxlength=\\"9999\\" placeholder=\\"请输入\\"></form>
+      <view class=\\"nut-searchbar__input-clear\\" style=\\"display: none;\\">
+        <nut-icon name=\\"circle-close\\" size=\\"12\\" color=\\"#555\\"></nut-icon>
+      </view>
+    </view>
+    <!--v-if-->
+  </view>
+  <!--v-if-->
+</view>"
+`;
+
+exports[`should render rightout slot correctly 1`] = `
+"<view class=\\"nut-searchbar\\">
+  <!--v-if-->
+  <view class=\\"nut-searchbar__search-input\\">
+    <!--v-if-->
+    <view class=\\"nut-searchbar__input-inner\\">
+      <form action=\\"#\\"><input class=\\"nut-searchbar__input-bar\\" type=\\"text\\" maxlength=\\"9999\\" placeholder=\\"请输入\\"></form>
+      <view class=\\"nut-searchbar__input-clear\\" style=\\"display: none;\\">
+        <nut-icon name=\\"circle-close\\" size=\\"12\\" color=\\"#555\\"></nut-icon>
+      </view>
+    </view>
+    <!--v-if-->
+  </view>
+  <view class=\\"nut-searchbar__search-icon nut-searchbar__right-search-icon\\">搜索</view>
+</view>"
+`;
+
+exports[`slot test 1`] = `
+"<view class=\\"nut-searchbar\\">
+  <!--v-if-->
+  <view class=\\"nut-searchbar__search-input\\">
+    <!--v-if-->
+    <view class=\\"nut-searchbar__input-inner\\">
+      <form action=\\"#\\"><input class=\\"nut-searchbar__input-bar\\" type=\\"text\\" maxlength=\\"9999\\" placeholder=\\"请输入\\"></form>
+      <view class=\\"nut-searchbar__input-clear\\" style=\\"display: none;\\">
+        <nut-icon name=\\"circle-close\\" size=\\"12\\" color=\\"#555\\"></nut-icon>
+      </view>
+    </view>
+    <!--v-if-->
+  </view>
+  <!--v-if-->
+</view>"
+`;

+ 45 - 0
src/packages/__VUE/searchbar/__tests__/searchbar.spec.ts

@@ -0,0 +1,45 @@
+import { mount } from '@vue/test-utils';
+import SearchBar from '../index.vue';
+
+test('should emit update:modelValue event when input value changed', () => {
+  const onUpdateModelValue = jest.fn();
+  const wrapper = mount(SearchBar, {
+    props: {
+      'onUpdate:modelValue': onUpdateModelValue
+    }
+  });
+
+  const input = wrapper.find('input');
+  input.element.value = '1';
+  input.trigger('input');
+
+  expect(onUpdateModelValue).toHaveBeenCalledTimes(1);
+});
+
+test('should emit search event when enter key is pressed', () => {
+  const wrapper = mount(SearchBar);
+
+  const input = wrapper.find('input');
+  input.trigger('keypress.enter');
+
+  expect(wrapper.emitted('search')).toMatchSnapshot();
+});
+
+test('should render rightout slot correctly', () => {
+  const wrapper = mount(SearchBar, {
+    slots: {
+      rightout: () => '搜索'
+    }
+  });
+
+  expect(wrapper.html()).toMatchSnapshot();
+});
+
+test('should render background prop correctly', () => {
+  const wrapper = mount(SearchBar, {
+    props: {
+      background: 'linear-gradient(to right, #9866F0, #EB4D50)'
+    }
+  });
+  expect(wrapper.html()).toMatchSnapshot();
+});

+ 1 - 1
src/packages/__VUE/searchbar/index.taro.vue

@@ -8,7 +8,7 @@
         <slot name="leftin"></slot>
       </view>
       <view class="nut-searchbar__input-inner">
-        <form action="#">
+        <form action="#" onsubmit="return false">
           <input
             class="nut-searchbar__input-bar"
             :type="inputType"

+ 1 - 0
src/packages/__VUE/searchbar/index.vue

@@ -15,6 +15,7 @@
             :maxlength="maxLength"
             :placeholder="placeholder"
             :value="modelValue"
+            :enterkeyhint="done"
             @input="valueChange"
             @focus="valueFocus"
             @blur="valueBlur"

+ 70 - 16
src/sites/mobile-taro/vue/src/layout/pages/imagepreview/index.vue

@@ -1,8 +1,29 @@
 <template>
   <div class="demo">
     <h2>基础用法</h2>
-    <nut-imagepreview :show="showPreview" :images="dataImgItem" @close="hideFn" />
-    <nut-cell isLink title="展示图片预览" :showIcon="true" @click="showFn"></nut-cell>
+    <nut-imagepreview :show="showPreview1" :images="imgData" @close="hideFn(1)" />
+    <nut-cell isLink title="展示图片预览" :showIcon="true" @click="showFn(1)"></nut-cell>
+
+    <h2>设置初始页码</h2>
+    <nut-imagepreview :show="showPreview2" :images="imgData" :content-close="true" :init-no="3" @close="hideFn(2)" />
+    <nut-cell isLink title="设置初始页码的图片预览" :showIcon="true" @click="showFn(2)"></nut-cell>
+
+    <h2>设置轮播指示器及颜色</h2>
+    <nut-imagepreview
+      :show="showPreview3"
+      :images="imgData"
+      :pagination-visible="true"
+      pagination-color="red"
+      @close="hideFn(3)"
+    />
+    <nut-cell isLink title="设置轮播指示器及颜色的图片预览" :showIcon="true" @click="showFn(3)"></nut-cell>
+
+    <!-- <h2>视频、图片预览</h2>
+    <nut-imagepreview :show="showPreview4" :videos="videoData" :images="imgData" @close="hideFn(4)" />
+    <nut-cell isLink title="视频、图片预览" :showIcon="true" @click="showFn(4)"></nut-cell> -->
+
+    <!-- <h2>函数式调用</h2>
+    <nut-cell isLink title="函数式调用的图片预览" :showIcon="true" @click="fnShow"></nut-cell> -->
   </div>
 </template>
 
@@ -12,39 +33,72 @@ export default {
   props: {},
   setup() {
     const resData = reactive({
-      showPreview: false,
-      dataImgItem: [
+      showPreview1: false,
+      showPreview2: false,
+      showPreview3: false,
+      showPreview4: false,
+      imgData: [
+        {
+          src: '//m.360buyimg.com/mobilecms/s750x366_jfs/t1/18629/34/3378/144318/5c263f64Ef0e2bff0/0d650e0aa2e852ee.jpg'
+        },
         {
-          imgSrc:
-            '//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'
         },
         {
-          imgSrc:
-            '//m.360buyimg.com/mobilecms/s750x366_jfs/t1/26597/30/4870/174583/5c35c5d2Ed55eedc6/50e27870c25e7a82.png'
+          src: '//m.360buyimg.com/mobilecms/s750x366_jfs/t1/9542/17/12873/201687/5c3c4362Ea9eb757d/60026b40a9d60d85.jpg'
         },
         {
-          imgSrc:
-            '//m.360buyimg.com/mobilecms/s750x366_jfs/t1/9542/17/12873/201687/5c3c4362Ea9eb757d/60026b40a9d60d85.jpg'
+          src: '//m.360buyimg.com/mobilecms/s750x366_jfs/t1/30042/36/427/82951/5c3bfdabE3faf2f66/9adca782661c988c.jpg'
+        }
+      ],
+      videoData: [
+        {
+          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
+          }
         },
         {
-          imgSrc:
-            '//m.360buyimg.com/mobilecms/s750x366_jfs/t1/30042/36/427/82951/5c3bfdabE3faf2f66/9adca782661c988c.jpg'
+          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
+          }
         }
       ]
     });
+    const onClose = () => {
+      console.log('imagepreview closed');
+    };
 
-    const showFn = () => {
-      resData.showPreview = true;
+    const showFn = (i: number) => {
+      (resData as any)['showPreview' + i] = true;
     };
 
-    const hideFn = () => {
-      resData.showPreview = false;
+    // const fnShow = () => {
+    //   ImagePreview({
+    //     show: true,
+    //     images: resData.imgData,
+    //     onClose
+    //   });
+    // };
+
+    const hideFn = (i: number) => {
+      (resData as any)['showPreview' + i] = false;
     };
 
     return {
       ...toRefs(resData),
       showFn,
       hideFn
+      // fnShow
     };
   }
 };