无木
2021-07-26 e23bd2696da945291a9b652f1af39ad1936f376b
feat(preview): add more features

为Preview组件添加新的属性及事件
5个文件已修改
163 ■■■■■ 已修改文件
CHANGELOG.zh_CN.md 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Preview/src/Functional.vue 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Preview/src/functional.ts 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Preview/src/typing.ts 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/demo/feat/img-preview/index.vue 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
CHANGELOG.zh_CN.md
@@ -1,3 +1,7 @@
### ✨ Features
- **Preview** 添加新的属性及事件
### 🐛 Bug Fixes
- **ApiTreeSelect** 修复未能正确监听`params`变化的问题
src/components/Preview/src/Functional.vue
@@ -38,13 +38,33 @@
      type: Number as PropType<number>,
      default: 0,
    },
    scaleStep: {
      type: Number as PropType<number>,
    },
    defaultWidth: {
      type: Number as PropType<number>,
    },
    maskClosable: {
      type: Boolean as PropType<boolean>,
    },
    rememberState: {
      type: Boolean as PropType<boolean>,
    },
  };
  const prefixCls = 'img-preview';
  export default defineComponent({
    name: 'ImagePreview',
    props,
    setup(props: Props) {
    emits: ['img-load', 'img-error'],
    setup(props: Props, { expose, emit }) {
      interface stateInfo {
        scale: number;
        rotate: number;
        top: number;
        left: number;
      }
      const stateMap = new Map<string, stateInfo>();
      const imgState = reactive<ImgState>({
        currentUrl: '',
        imgScale: 1,
@@ -96,6 +116,14 @@
        };
      }
      const getScaleStep = computed(() => {
        if (props.scaleStep > 0 && props.scaleStep < 100) {
          return props.scaleStep / 100;
        } else {
          return imgState.imgScale / 10;
        }
      });
      // 监听鼠标滚轮
      function scrollFunc(e: any) {
        e = e || window.event;
@@ -104,11 +132,11 @@
        e.preventDefault();
        if (e.delta > 0) {
          // 滑轮向上滚动
          scaleFunc(0.015);
          scaleFunc(getScaleStep.value);
        }
        if (e.delta < 0) {
          // 滑轮向下滚动
          scaleFunc(-0.015);
          scaleFunc(-getScaleStep.value);
        }
      }
      // 缩放函数
@@ -134,11 +162,54 @@
        imgState.status = StatueEnum.LOADING;
        const img = new Image();
        img.src = url;
        img.onload = () => {
        img.onload = (e: Event) => {
          if (imgState.currentUrl !== url) {
            const ele: HTMLElement[] = e.composedPath();
            if (props.rememberState) {
              // 保存当前图片的缩放信息
              stateMap.set(imgState.currentUrl, {
                scale: imgState.imgScale,
                top: imgState.imgTop,
                left: imgState.imgLeft,
                rotate: imgState.imgRotate,
              });
              // 如果之前已存储缩放信息,就应用
              const stateInfo = stateMap.get(url);
              if (stateInfo) {
                imgState.imgScale = stateInfo.scale;
                imgState.imgTop = stateInfo.top;
                imgState.imgRotate = stateInfo.rotate;
                imgState.imgLeft = stateInfo.left;
              } else {
                initState();
                if (props.defaultWidth) {
                  imgState.imgScale = props.defaultWidth / ele[0].naturalWidth;
                }
              }
            } else {
              if (props.defaultWidth) {
                imgState.imgScale = props.defaultWidth / ele[0].naturalWidth;
              }
            }
            ele &&
              emit('img-load', {
                index: imgState.currentIndex,
                dom: ele[0] as HTMLImageElement,
                url,
              });
          }
          imgState.currentUrl = url;
          imgState.status = StatueEnum.DONE;
        };
        img.onerror = () => {
        img.onerror = (e: Event) => {
          const ele: EventTarget[] = e.composedPath();
          ele &&
            emit('img-error', {
              index: imgState.currentIndex,
              dom: ele[0] as HTMLImageElement,
              url,
            });
          imgState.status = StatueEnum.FAIL;
        };
      }
@@ -146,6 +217,10 @@
      // 关闭
      function handleClose(e: MouseEvent) {
        e && e.stopPropagation();
        close();
      }
      function close() {
        imgState.show = false;
        // 移除火狐浏览器下的鼠标滚动事件
        document.body.removeEventListener('DOMMouseScroll', scrollFunc);
@@ -157,6 +232,19 @@
      function resume() {
        initState();
      }
      expose({
        resume,
        close,
        prev: handleChange.bind(null, 'left'),
        next: handleChange.bind(null, 'right'),
        setScale: (scale: number) => {
          if (scale > 0 && scale <= 10) imgState.imgScale = scale;
        },
        setRotate: (rotate: number) => {
          imgState.imgRotate = rotate;
        },
      } as PreviewActions);
      // 上一页下一页
      function handleChange(direction: 'left' | 'right') {
@@ -205,6 +293,7 @@
          transform: `scale(${imgScale}) rotate(${imgRotate}deg)`,
          marginTop: `${imgTop}px`,
          marginLeft: `${imgLeft}px`,
          maxWidth: props.defaultWidth ? 'unset' : '100%',
        };
      });
@@ -221,6 +310,16 @@
          initState();
        }
      });
      const handleMaskClick = (e: MouseEvent) => {
        if (
          props.maskClosable &&
          e.target &&
          (e.target as HTMLDivElement).classList.contains(`${prefixCls}-content`)
        ) {
          handleClose(e);
        }
      };
      const renderClose = () => {
        return (
@@ -246,10 +345,16 @@
      const renderController = () => {
        return (
          <div class={`${prefixCls}__controller`}>
            <div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(-0.15)}>
            <div
              class={`${prefixCls}__controller-item`}
              onClick={() => scaleFunc(-getScaleStep.value)}
            >
              <img src={unScaleSvg} />
            </div>
            <div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(0.15)}>
            <div
              class={`${prefixCls}__controller-item`}
              onClick={() => scaleFunc(getScaleStep.value)}
            >
              <img src={scaleSvg} />
            </div>
            <div class={`${prefixCls}__controller-item`} onClick={resume}>
@@ -279,7 +384,12 @@
      return () => {
        return (
          imgState.show && (
            <div class={prefixCls} ref={wrapElRef} onMouseup={handleMouseUp}>
            <div
              class={prefixCls}
              ref={wrapElRef}
              onMouseup={handleMouseUp}
              onClick={handleMaskClick}
            >
              <div class={`${prefixCls}-content`}>
                {/*<Spin*/}
                {/*  indicator={<LoadingOutlined style="font-size: 24px" spin />}*/}
src/components/Preview/src/functional.ts
@@ -6,15 +6,12 @@
let instance: ReturnType<typeof createVNode> | null = null;
export function createImgPreview(options: Options) {
  if (!isClient) return;
  const { imageList, show = true, index = 0 } = options;
  const propsData: Partial<Props> = {};
  const container = document.createElement('div');
  propsData.imageList = imageList;
  propsData.show = show;
  propsData.index = index;
  Object.assign(propsData, { show: true, index: 0, scaleStep: 100 }, options);
  instance = createVNode(ImgPreview, propsData);
  render(instance, container);
  document.body.appendChild(container);
  return instance.component?.exposed;
}
src/components/Preview/src/typing.ts
@@ -2,6 +2,12 @@
  show?: boolean;
  imageList: string[];
  index?: number;
  scaleStep?: number;
  defaultWidth?: number;
  maskClosable?: boolean;
  rememberState?: boolean;
  onImgLoad?: (img: HTMLImageElement) => void;
  onImgError?: (img: HTMLImageElement) => void;
}
export interface Props {
@@ -9,6 +15,19 @@
  instance: Props;
  imageList: string[];
  index: number;
  scaleStep: number;
  defaultWidth: number;
  maskClosable: boolean;
  rememberState: boolean;
}
export interface PreviewActions {
  resume: () => void;
  close: () => void;
  prev: () => void;
  next: () => void;
  setScale: (scale: number) => void;
  setRotate: (rotate: number) => void;
}
export interface ImageProps {
src/views/demo/feat/img-preview/index.vue
@@ -8,6 +8,7 @@
  import { defineComponent } from 'vue';
  import { createImgPreview, ImagePreview } from '/@/components/Preview/index';
  import { PageWrapper } from '/@/components/Page';
  // import { PreviewActions } from '/@/components/Preview/src/typing';
  const imgList: string[] = [
    'https://picsum.photos/id/66/346/216',
@@ -18,7 +19,11 @@
    components: { PageWrapper, ImagePreview },
    setup() {
      function openImg() {
        createImgPreview({ imageList: imgList });
        const onImgLoad = ({ index, url, dom }) => {
          console.log(`第${index + 1}张图片已加载,URL为:${url}`, dom);
        };
        // 可以使用createImgPreview返回的 PreviewActions 来控制预览逻辑,实现类似幻灯片、自动旋转之类的骚操作
        createImgPreview({ imageList: imgList, defaultWidth: 700, rememberState: true, onImgLoad });
      }
      return { imgList, openImg };
    },