Ver código fonte

feat(preview): added createImgPreview picture preview function

Vben 3 anos atrás
pai
commit
305630e3fd

+ 1 - 0
CHANGELOG.zh_CN.md

@@ -9,6 +9,7 @@
 - **CropperImage** `Cropper` 头像裁剪新增圆形裁剪功能
 - **CropperAvatar** 新增头像上传组件
 - **Drawer** `useDrawer`新增`closeDrawer`函数
+- **Preview** 新增`createImgPreview`图片预览函数
 
 ### 🐛 Bug Fixes
 

+ 0 - 1
src/components/Form/src/components/ApiSelect.vue

@@ -26,7 +26,6 @@
   import { useRuleFormItem } from '/@/hooks/component/useFormItem';
   import { useAttrs } from '/@/hooks/core/useAttrs';
   import { get, omit } from 'lodash-es';
-
   import { LoadingOutlined } from '@ant-design/icons-vue';
   import { useI18n } from '/@/hooks/web/useI18n';
   import { propTypes } from '/@/utils/propTypes';

+ 0 - 2
src/components/Form/src/components/FormAction.vue

@@ -40,13 +40,11 @@
 <script lang="ts">
   import type { ColEx } from '../types/index';
   import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
-
   import { defineComponent, computed, PropType } from 'vue';
   import { Form, Col } from 'ant-design-vue';
   import { Button } from '/@/components/Button';
   import { BasicArrow } from '/@/components/Basic/index';
   import { useFormContext } from '../hooks/useFormContext';
-
   import { useI18n } from '/@/hooks/web/useI18n';
   import { propTypes } from '/@/utils/propTypes';
 

+ 5 - 9
src/components/Form/src/components/FormItem.vue

@@ -4,17 +4,14 @@
   import type { FormSchema } from '../types/form';
   import type { ValidationRule } from 'ant-design-vue/lib/form/Form';
   import type { TableActionType } from '/@/components/Table';
-
   import { defineComponent, computed, unref, toRefs } from 'vue';
   import { Form, Col } from 'ant-design-vue';
   import { componentMap } from '../componentMap';
   import { BasicHelp } from '/@/components/Basic';
-
   import { isBoolean, isFunction, isNull } from '/@/utils/is';
   import { getSlot } from '/@/utils/helper/tsxHelper';
   import { createPlaceholderMessage, setComponentRuleType } from '../helper';
   import { upperFirst, cloneDeep } from 'lodash-es';
-
   import { useItemLabelWidth } from '../hooks/useLabelWidth';
   import { useI18n } from '/@/hooks/web/useI18n';
 
@@ -91,7 +88,6 @@
         if (isBoolean(dynamicDisabled)) {
           disabled = dynamicDisabled;
         }
-
         if (isFunction(dynamicDisabled)) {
           disabled = dynamicDisabled(unref(getValues));
         }
@@ -276,7 +272,6 @@
           : {
               default: () => renderComponentContent,
             };
-
         return <Comp {...compAttr}>{compSlot}</Comp>;
       }
 
@@ -317,7 +312,6 @@
         };
 
         const showSuffix = !!suffix;
-
         const getSuffix = isFunction(suffix) ? suffix(unref(getValues)) : suffix;
 
         return (
@@ -338,16 +332,18 @@
           </Form.Item>
         );
       }
+
       return () => {
         const { colProps = {}, colSlot, renderColContent, component } = props.schema;
-        if (!componentMap.has(component)) return null;
+        if (!componentMap.has(component)) {
+          return null;
+        }
 
         const { baseColProps = {} } = props.formProps;
-
         const realColProps = { ...baseColProps, ...colProps };
         const { isIfShow, isShow } = getShow();
-
         const values = unref(getValues);
+
         const getContent = () => {
           return colSlot
             ? getSlot(slots, colSlot, values)

+ 2 - 1
src/components/Form/src/components/RadioButtonGroup.vue

@@ -1,7 +1,6 @@
 <!--
  * @Description:It is troublesome to implement radio button group in the form. So it is extracted independently as a separate component
 -->
-
 <template>
   <RadioGroup v-bind="attrs" v-model:value="state" button-style="solid">
     <template v-for="item in getOptions" :key="`${item.value}`">
@@ -17,6 +16,7 @@
   import { isString } from '/@/utils/is';
   import { useRuleFormItem } from '/@/hooks/component/useFormItem';
   import { useAttrs } from '/@/hooks/core/useAttrs';
+
   type OptionsItem = { label: string; value: string | number | boolean; disabled?: boolean };
   type RadioItem = string | OptionsItem;
 
@@ -39,6 +39,7 @@
       const attrs = useAttrs();
       // Embedded in the form, just use the hook binding to perform form verification
       const [state] = useRuleFormItem(props);
+
       // Processing options value
       const getOptions = computed((): OptionsItem[] => {
         const { options } = props;

+ 0 - 2
src/components/Form/src/hooks/useAdvanced.ts

@@ -2,10 +2,8 @@ import type { ColEx } from '../types';
 import type { AdvanceState } from '../types/hooks';
 import type { ComputedRef, Ref } from 'vue';
 import type { FormProps, FormSchema } from '../types/form';
-
 import { computed, unref, watch } from 'vue';
 import { isBoolean, isFunction, isNumber, isObject } from '/@/utils/is';
-
 import { useBreakpoint } from '/@/hooks/event/useBreakpoint';
 import { useDebounceFn } from '@vueuse/core';
 

+ 9 - 3
src/components/Form/src/hooks/useAutoFocus.ts

@@ -16,16 +16,22 @@ export async function useAutoFocus({
   isInitedDefault,
 }: UseAutoFocusContext) {
   watchEffect(async () => {
-    if (unref(isInitedDefault) || !unref(getProps).autoFocusFirstItem) return;
+    if (unref(isInitedDefault) || !unref(getProps).autoFocusFirstItem) {
+      return;
+    }
     await nextTick();
     const schemas = unref(getSchema);
     const formEl = unref(formElRef);
     const el = (formEl as any)?.$el as HTMLElement;
-    if (!formEl || !el || !schemas || schemas.length === 0) return;
+    if (!formEl || !el || !schemas || schemas.length === 0) {
+      return;
+    }
 
     const firstItem = schemas[0];
     // Only open when the first form item is input type
-    if (!firstItem.component.includes('Input')) return;
+    if (!firstItem.component.includes('Input')) {
+      return;
+    }
 
     const inputEl = el.querySelector('.ant-row:first-child input') as Nullable<HTMLInputElement>;
     if (!inputEl) return;

+ 3 - 5
src/components/Form/src/hooks/useForm.ts

@@ -1,13 +1,11 @@
+import type { FormProps, FormActionType, UseFormReturnType, FormSchema } from '../types/form';
+import type { NamePath } from 'ant-design-vue/lib/form/interface';
+import type { DynamicProps } from '/#/utils';
 import { ref, onUnmounted, unref, nextTick, watch } from 'vue';
-
 import { isProdMode } from '/@/utils/env';
 import { error } from '/@/utils/log';
 import { getDynamicProps } from '/@/utils';
 
-import type { FormProps, FormActionType, UseFormReturnType, FormSchema } from '../types/form';
-import type { NamePath } from 'ant-design-vue/lib/form/interface';
-import type { DynamicProps } from '/#/utils';
-
 export declare type ValidateFields = (nameList?: NamePath[]) => Promise<Recordable>;
 
 type Props = Partial<DynamicProps<FormProps>>;

+ 0 - 2
src/components/Form/src/hooks/useFormEvents.ts

@@ -1,9 +1,7 @@
 import type { ComputedRef, Ref } from 'vue';
 import type { FormProps, FormSchema, FormActionType } from '../types/form';
 import type { NamePath } from 'ant-design-vue/lib/form/interface';
-
 import { unref, toRaw } from 'vue';
-
 import { isArray, isFunction, isObject, isString } from '/@/utils/is';
 import { deepMerge } from '/@/utils';
 import { dateItemType, handleInputNumberValue } from '../helper';

+ 0 - 2
src/components/Form/src/hooks/useFormValues.ts

@@ -1,10 +1,8 @@
 import { isArray, isFunction, isObject, isString, isNullOrUnDef } from '/@/utils/is';
 import { dateUtil } from '/@/utils/dateUtil';
-
 import { unref } from 'vue';
 import type { Ref, ComputedRef } from 'vue';
 import type { FormProps, FormSchema } from '../types/form';
-
 import { set } from 'lodash-es';
 
 interface UseFormValuesContext {

+ 0 - 1
src/components/Form/src/types/form.ts

@@ -1,7 +1,6 @@
 import type { NamePath, RuleObject } from 'ant-design-vue/lib/form/interface';
 import type { VNode } from 'vue';
 import type { ButtonProps as AntdButtonProps } from 'ant-design-vue/es/button/buttonTypes';
-
 import type { FormItem } from './formItem';
 import type { ColEx, ComponentType } from './index';
 import type { TableActionType } from '/@/components/Table/src/types/table';

+ 0 - 2
src/components/Form/src/types/index.ts

@@ -90,9 +90,7 @@ export type ComponentType =
   | 'InputCountDown'
   | 'Select'
   | 'ApiSelect'
-  | 'SelectOptGroup'
   | 'TreeSelect'
-  | 'Transfer'
   | 'RadioButtonGroup'
   | 'RadioGroup'
   | 'Checkbox'

+ 436 - 0
src/components/Preview/src/Functional.vue

@@ -0,0 +1,436 @@
+<script lang="tsx">
+  import { defineComponent, ref, unref, computed, reactive, watchEffect } from 'vue';
+  import { Props } from './typing';
+  import { CloseOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
+  import resumeSvg from '/@/assets/svg/preview/resume.svg';
+  import rotateSvg from '/@/assets/svg/preview/p-rotate.svg';
+  import scaleSvg from '/@/assets/svg/preview/scale.svg';
+  import unScaleSvg from '/@/assets/svg/preview/unscale.svg';
+  import unRotateSvg from '/@/assets/svg/preview/unrotate.svg';
+
+  enum StatueEnum {
+    LOADING,
+    DONE,
+    FAIL,
+  }
+  interface ImgState {
+    currentUrl: string;
+    imgScale: number;
+    imgRotate: number;
+    imgTop: number;
+    imgLeft: number;
+    currentIndex: number;
+    status: StatueEnum;
+    moveX: number;
+    moveY: number;
+    show: boolean;
+  }
+  const props = {
+    show: {
+      type: Boolean as PropType<boolean>,
+      default: false,
+    },
+    imageList: {
+      type: [Array] as PropType<string[]>,
+      default: null,
+    },
+    index: {
+      type: Number as PropType<number>,
+      default: 0,
+    },
+  };
+
+  const prefixCls = 'img-preview';
+  export default defineComponent({
+    name: 'ImagePreview',
+    props,
+    setup(props: Props) {
+      const imgState = reactive<ImgState>({
+        currentUrl: '',
+        imgScale: 1,
+        imgRotate: 0,
+        imgTop: 0,
+        imgLeft: 0,
+        status: StatueEnum.LOADING,
+        currentIndex: 0,
+        moveX: 0,
+        moveY: 0,
+        show: props.show,
+      });
+
+      const wrapElRef = ref<HTMLDivElement | null>(null);
+      const imgElRef = ref<HTMLImageElement | null>(null);
+
+      // 初始化
+      function init() {
+        initMouseWheel();
+        const { index, imageList } = props;
+
+        if (!imageList || !imageList.length) {
+          throw new Error('imageList is undefined');
+        }
+        imgState.currentIndex = index;
+        handleIChangeImage(imageList[index]);
+      }
+
+      // 重置
+      function initState() {
+        imgState.imgScale = 1;
+        imgState.imgRotate = 0;
+        imgState.imgTop = 0;
+        imgState.imgLeft = 0;
+      }
+
+      // 初始化鼠标滚轮事件
+      function initMouseWheel() {
+        const wrapEl = unref(wrapElRef);
+        if (!wrapEl) {
+          return;
+        }
+        (wrapEl as any).onmousewheel = scrollFunc;
+        // 火狐浏览器没有onmousewheel事件,用DOMMouseScroll代替
+        document.body.addEventListener('DOMMouseScroll', scrollFunc);
+        // 禁止火狐浏览器下拖拽图片的默认事件
+        document.ondragstart = function () {
+          return false;
+        };
+      }
+
+      // 监听鼠标滚轮
+      function scrollFunc(e: any) {
+        e = e || window.event;
+        e.delta = e.wheelDelta || -e.detail;
+
+        e.preventDefault();
+        if (e.delta > 0) {
+          // 滑轮向上滚动
+          scaleFunc(0.015);
+        }
+        if (e.delta < 0) {
+          // 滑轮向下滚动
+          scaleFunc(-0.015);
+        }
+      }
+      // 缩放函数
+      function scaleFunc(num: number) {
+        if (imgState.imgScale <= 0.2 && num < 0) return;
+        imgState.imgScale += num;
+      }
+
+      // 旋转图片
+      function rotateFunc(deg: number) {
+        imgState.imgRotate += deg;
+      }
+
+      // 鼠标事件
+      function handleMouseUp() {
+        const imgEl = unref(imgElRef);
+        if (!imgEl) return;
+        imgEl.onmousemove = null;
+      }
+
+      // 更换图片
+      function handleIChangeImage(url: string) {
+        imgState.status = StatueEnum.LOADING;
+        const img = new Image();
+        img.src = url;
+        img.onload = () => {
+          imgState.currentUrl = url;
+          imgState.status = StatueEnum.DONE;
+        };
+        img.onerror = () => {
+          imgState.status = StatueEnum.FAIL;
+        };
+      }
+
+      // 关闭
+      function handleClose(e: MouseEvent) {
+        e && e.stopPropagation();
+        imgState.show = false;
+        // 移除火狐浏览器下的鼠标滚动事件
+        document.body.removeEventListener('DOMMouseScroll', scrollFunc);
+        // 恢复火狐及Safari浏览器下的图片拖拽
+        document.ondragstart = null;
+      }
+
+      // 图片复原
+      function resume() {
+        initState();
+      }
+
+      // 上一页下一页
+      function handleChange(direction: 'left' | 'right') {
+        const { currentIndex } = imgState;
+        const { imageList } = props;
+        if (direction === 'left') {
+          imgState.currentIndex--;
+          if (currentIndex <= 0) {
+            imgState.currentIndex = imageList.length - 1;
+          }
+        }
+        if (direction === 'right') {
+          imgState.currentIndex++;
+          if (currentIndex >= imageList.length - 1) {
+            imgState.currentIndex = 0;
+          }
+        }
+        handleIChangeImage(imageList[imgState.currentIndex]);
+      }
+
+      function handleAddMoveListener(e: MouseEvent) {
+        e = e || window.event;
+        imgState.moveX = e.clientX;
+        imgState.moveY = e.clientY;
+        const imgEl = unref(imgElRef);
+        if (imgEl) {
+          imgEl.onmousemove = moveFunc;
+        }
+      }
+
+      function moveFunc(e: MouseEvent) {
+        e = e || window.event;
+        e.preventDefault();
+        const movementX = e.clientX - imgState.moveX;
+        const movementY = e.clientY - imgState.moveY;
+        imgState.imgLeft += movementX;
+        imgState.imgTop += movementY;
+        imgState.moveX = e.clientX;
+        imgState.moveY = e.clientY;
+      }
+
+      // 获取图片样式
+      const getImageStyle = computed(() => {
+        const { imgScale, imgRotate, imgTop, imgLeft } = imgState;
+        return {
+          transform: `scale(${imgScale}) rotate(${imgRotate}deg)`,
+          marginTop: `${imgTop}px`,
+          marginLeft: `${imgLeft}px`,
+        };
+      });
+
+      const getIsMultipleImage = computed(() => {
+        const { imageList } = props;
+        return imageList.length > 1;
+      });
+
+      watchEffect(() => {
+        if (props.show) {
+          init();
+        }
+        if (props.imageList) {
+          initState();
+        }
+      });
+
+      const renderClose = () => {
+        return (
+          <div class={`${prefixCls}__close`} onClick={handleClose}>
+            <CloseOutlined class={`${prefixCls}__close-icon`} />
+          </div>
+        );
+      };
+
+      const renderIndex = () => {
+        if (!unref(getIsMultipleImage)) {
+          return null;
+        }
+        const { currentIndex } = imgState;
+        const { imageList } = props;
+        return (
+          <div class={`${prefixCls}__index`}>
+            {currentIndex + 1} / {imageList.length}
+          </div>
+        );
+      };
+
+      const renderController = () => {
+        return (
+          <div class={`${prefixCls}__controller`}>
+            <div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(-0.15)}>
+              <img src={unScaleSvg} />
+            </div>
+            <div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(0.15)}>
+              <img src={scaleSvg} />
+            </div>
+            <div class={`${prefixCls}__controller-item`} onClick={resume}>
+              <img src={resumeSvg} />
+            </div>
+            <div class={`${prefixCls}__controller-item`} onClick={() => rotateFunc(-90)}>
+              <img src={unRotateSvg} />
+            </div>
+            <div class={`${prefixCls}__controller-item`} onClick={() => rotateFunc(90)}>
+              <img src={rotateSvg} />
+            </div>
+          </div>
+        );
+      };
+
+      const renderArrow = (direction: 'left' | 'right') => {
+        if (!unref(getIsMultipleImage)) {
+          return null;
+        }
+        return (
+          <div class={[`${prefixCls}__arrow`, direction]} onClick={() => handleChange(direction)}>
+            {direction === 'left' ? <LeftOutlined /> : <RightOutlined />}
+          </div>
+        );
+      };
+
+      return () => {
+        return (
+          imgState.show && (
+            <div class={prefixCls} ref={wrapElRef} onMouseup={handleMouseUp}>
+              <div class={`${prefixCls}-content`}>
+                {/*<Spin*/}
+                {/*  indicator={<LoadingOutlined style="font-size: 24px" spin />}*/}
+                {/*  spinning={true}*/}
+                {/*  class={[*/}
+                {/*    `${prefixCls}-image`,*/}
+                {/*    {*/}
+                {/*      hidden: imgState.status !== StatueEnum.LOADING,*/}
+                {/*    },*/}
+                {/*  ]}*/}
+                {/*/>*/}
+                <img
+                  style={unref(getImageStyle)}
+                  class={[
+                    `${prefixCls}-image`,
+                    imgState.status === StatueEnum.DONE ? '' : 'hidden',
+                  ]}
+                  ref={imgElRef}
+                  src={imgState.currentUrl}
+                  onMousedown={handleAddMoveListener}
+                />
+                {renderClose()}
+                {renderIndex()}
+                {renderController()}
+                {renderArrow('left')}
+                {renderArrow('right')}
+              </div>
+            </div>
+          )
+        );
+      };
+    },
+  });
+</script>
+<style lang="less">
+  .img-preview {
+    position: fixed;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    z-index: @preview-comp-z-index;
+    background: rgba(0, 0, 0, 0.5);
+    user-select: none;
+
+    &-content {
+      display: flex;
+      width: 100%;
+      height: 100%;
+      color: @white;
+      justify-content: center;
+      align-items: center;
+    }
+
+    &-image {
+      cursor: pointer;
+      transition: transform 0.3s;
+    }
+
+    &__close {
+      position: absolute;
+      top: -40px;
+      right: -40px;
+      width: 80px;
+      height: 80px;
+      overflow: hidden;
+      color: @white;
+      cursor: pointer;
+      background-color: rgba(0, 0, 0, 0.5);
+      border-radius: 50%;
+      transition: all 0.2s;
+
+      &-icon {
+        position: absolute;
+        top: 46px;
+        left: 16px;
+        font-size: 16px;
+      }
+
+      &:hover {
+        background-color: rgba(0, 0, 0, 0.8);
+      }
+    }
+
+    &__index {
+      position: absolute;
+      bottom: 5%;
+      left: 50%;
+      padding: 0 22px;
+      font-size: 16px;
+      background: rgba(109, 109, 109, 0.6);
+      border-radius: 15px;
+      transform: translateX(-50%);
+    }
+
+    &__controller {
+      position: absolute;
+      bottom: 10%;
+      left: 50%;
+      display: flex;
+      width: 260px;
+      height: 44px;
+      padding: 0 22px;
+      margin-left: -139px;
+      background: rgba(109, 109, 109, 0.6);
+      border-radius: 22px;
+      justify-content: center;
+
+      &-item {
+        display: flex;
+        height: 100%;
+        padding: 0 9px;
+        font-size: 24px;
+        cursor: pointer;
+        transition: all 0.2s;
+
+        &:hover {
+          transform: scale(1.2);
+        }
+
+        img {
+          width: 1em;
+        }
+      }
+    }
+
+    &__arrow {
+      position: absolute;
+      top: 50%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 50px;
+      height: 50px;
+      font-size: 28px;
+      cursor: pointer;
+      background-color: rgba(0, 0, 0, 0.5);
+      border-radius: 50%;
+      transition: all 0.2s;
+
+      &:hover {
+        background-color: rgba(0, 0, 0, 0.8);
+      }
+
+      &.left {
+        left: 50px;
+      }
+
+      &.right {
+        right: 50px;
+      }
+    }
+  }
+</style>

+ 3 - 5
src/components/Preview/src/functional.ts

@@ -1,11 +1,9 @@
-import ImgPreview from './index';
+import type { Options, Props } from './typing';
+import ImgPreview from './Functional.vue';
 import { isClient } from '/@/utils/is';
-
-import type { Options, Props } from './types';
-
 import { createVNode, render } from 'vue';
 
-let instance: any = null;
+let instance: ReturnType<typeof createVNode> | null = null;
 export function createImgPreview(options: Options) {
   if (!isClient) return;
   const { imageList, show = true, index = 0 } = options;

+ 0 - 118
src/components/Preview/src/index.less

@@ -1,118 +0,0 @@
-.img-preview {
-  position: fixed;
-  top: 0;
-  right: 0;
-  bottom: 0;
-  left: 0;
-  z-index: @preview-comp-z-index;
-  background: rgba(0, 0, 0, 0.5);
-  user-select: none;
-
-  &-content {
-    display: flex;
-    width: 100%;
-    height: 100%;
-    color: @white;
-    justify-content: center;
-    align-items: center;
-  }
-
-  &-image {
-    cursor: pointer;
-    transition: transform 0.3s;
-  }
-
-  &__close {
-    position: absolute;
-    top: -40px;
-    right: -40px;
-    width: 80px;
-    height: 80px;
-    overflow: hidden;
-    color: @white;
-    cursor: pointer;
-    background-color: rgba(0, 0, 0, 0.5);
-    border-radius: 50%;
-    transition: all 0.2s;
-
-    &-icon {
-      position: absolute;
-      top: 46px;
-      left: 16px;
-      font-size: 16px;
-    }
-
-    &:hover {
-      background-color: rgba(0, 0, 0, 0.8);
-    }
-  }
-
-  &__index {
-    position: absolute;
-    bottom: 5%;
-    left: 50%;
-    padding: 0 22px;
-    font-size: 16px;
-    background: rgba(109, 109, 109, 0.6);
-    border-radius: 15px;
-    transform: translateX(-50%);
-  }
-
-  &__controller {
-    position: absolute;
-    bottom: 10%;
-    left: 50%;
-    display: flex;
-    width: 260px;
-    height: 44px;
-    padding: 0 22px;
-    margin-left: -139px;
-    background: rgba(109, 109, 109, 0.6);
-    border-radius: 22px;
-    justify-content: center;
-
-    &-item {
-      display: flex;
-      height: 100%;
-      padding: 0 9px;
-      font-size: 24px;
-      cursor: pointer;
-      transition: all 0.2s;
-
-      &:hover {
-        transform: scale(1.2);
-      }
-
-      img {
-        width: 1em;
-      }
-    }
-  }
-
-  &__arrow {
-    position: absolute;
-    top: 50%;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    width: 50px;
-    height: 50px;
-    font-size: 28px;
-    cursor: pointer;
-    background-color: rgba(0, 0, 0, 0.5);
-    border-radius: 50%;
-    transition: all 0.2s;
-
-    &:hover {
-      background-color: rgba(0, 0, 0, 0.8);
-    }
-
-    &.left {
-      left: 50px;
-    }
-
-    &.right {
-      right: 50px;
-    }
-  }
-}

+ 0 - 305
src/components/Preview/src/index.tsx

@@ -1,305 +0,0 @@
-import './index.less';
-
-import { defineComponent, ref, unref, computed, reactive, watchEffect } from 'vue';
-
-// @ts-ignore
-import { basicProps } from './props';
-// @ts-ignore
-import { Props } from './types';
-
-import { CloseOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
-// import { Spin } from 'ant-design-vue';
-
-import resumeSvg from '/@/assets/svg/preview/resume.svg';
-import rotateSvg from '/@/assets/svg/preview/p-rotate.svg';
-import scaleSvg from '/@/assets/svg/preview/scale.svg';
-import unScaleSvg from '/@/assets/svg/preview/unscale.svg';
-import unRotateSvg from '/@/assets/svg/preview/unrotate.svg';
-enum StatueEnum {
-  LOADING,
-  DONE,
-  FAIL,
-}
-interface ImgState {
-  currentUrl: string;
-  imgScale: number;
-  imgRotate: number;
-  imgTop: number;
-  imgLeft: number;
-  currentIndex: number;
-  status: StatueEnum;
-  moveX: number;
-  moveY: number;
-  show: boolean;
-}
-
-const prefixCls = 'img-preview';
-export default defineComponent({
-  name: 'ImagePreview',
-  props: basicProps,
-  setup(props: Props) {
-    const imgState = reactive<ImgState>({
-      currentUrl: '',
-      imgScale: 1,
-      imgRotate: 0,
-      imgTop: 0,
-      imgLeft: 0,
-      status: StatueEnum.LOADING,
-      currentIndex: 0,
-      moveX: 0,
-      moveY: 0,
-      show: props.show,
-    });
-
-    const wrapElRef = ref<HTMLDivElement | null>(null);
-    const imgElRef = ref<HTMLImageElement | null>(null);
-
-    // 初始化
-    function init() {
-      initMouseWheel();
-      const { index, imageList } = props;
-
-      if (!imageList || !imageList.length) {
-        throw new Error('imageList is undefined');
-      }
-      imgState.currentIndex = index;
-      handleIChangeImage(imageList[index]);
-    }
-
-    // 重置
-    function initState() {
-      imgState.imgScale = 1;
-      imgState.imgRotate = 0;
-      imgState.imgTop = 0;
-      imgState.imgLeft = 0;
-    }
-
-    // 初始化鼠标滚轮事件
-    function initMouseWheel() {
-      const wrapEl = unref(wrapElRef);
-      if (!wrapEl) {
-        return;
-      }
-      (wrapEl as any).onmousewheel = scrollFunc;
-      // 火狐浏览器没有onmousewheel事件,用DOMMouseScroll代替
-      document.body.addEventListener('DOMMouseScroll', scrollFunc);
-      // 禁止火狐浏览器下拖拽图片的默认事件
-      document.ondragstart = function () {
-        return false;
-      };
-    }
-
-    // 监听鼠标滚轮
-    function scrollFunc(e: any) {
-      e = e || window.event;
-      e.delta = e.wheelDelta || -e.detail;
-
-      e.preventDefault();
-      if (e.delta > 0) {
-        // 滑轮向上滚动
-        scaleFunc(0.015);
-      }
-      if (e.delta < 0) {
-        // 滑轮向下滚动
-        scaleFunc(-0.015);
-      }
-    }
-    // 缩放函数
-    function scaleFunc(num: number) {
-      if (imgState.imgScale <= 0.2 && num < 0) return;
-      imgState.imgScale += num;
-    }
-
-    // 旋转图片
-    function rotateFunc(deg: number) {
-      imgState.imgRotate += deg;
-    }
-
-    // 鼠标事件
-    function handleMouseUp() {
-      const imgEl = unref(imgElRef);
-      if (!imgEl) return;
-      imgEl.onmousemove = null;
-    }
-
-    // 更换图片
-    function handleIChangeImage(url: string) {
-      imgState.status = StatueEnum.LOADING;
-      const img = new Image();
-      img.src = url;
-      img.onload = () => {
-        imgState.currentUrl = url;
-        imgState.status = StatueEnum.DONE;
-      };
-      img.onerror = () => {
-        imgState.status = StatueEnum.FAIL;
-      };
-    }
-
-    // 关闭
-    function handleClose(e: MouseEvent) {
-      e && e.stopPropagation();
-      imgState.show = false;
-      // 移除火狐浏览器下的鼠标滚动事件
-      document.body.removeEventListener('DOMMouseScroll', scrollFunc);
-      // 恢复火狐及Safari浏览器下的图片拖拽
-      document.ondragstart = null;
-    }
-
-    // 图片复原
-    function resume() {
-      initState();
-    }
-
-    // 上一页下一页
-    function handleChange(direction: 'left' | 'right') {
-      const { currentIndex } = imgState;
-      const { imageList } = props;
-      if (direction === 'left') {
-        imgState.currentIndex--;
-        if (currentIndex <= 0) {
-          imgState.currentIndex = imageList.length - 1;
-        }
-      }
-      if (direction === 'right') {
-        imgState.currentIndex++;
-        if (currentIndex >= imageList.length - 1) {
-          imgState.currentIndex = 0;
-        }
-      }
-      handleIChangeImage(imageList[imgState.currentIndex]);
-    }
-
-    function handleAddMoveListener(e: MouseEvent) {
-      e = e || window.event;
-      imgState.moveX = e.clientX;
-      imgState.moveY = e.clientY;
-      const imgEl = unref(imgElRef);
-      if (imgEl) {
-        imgEl.onmousemove = moveFunc;
-      }
-    }
-
-    function moveFunc(e: MouseEvent) {
-      e = e || window.event;
-      e.preventDefault();
-      const movementX = e.clientX - imgState.moveX;
-      const movementY = e.clientY - imgState.moveY;
-      imgState.imgLeft += movementX;
-      imgState.imgTop += movementY;
-      imgState.moveX = e.clientX;
-      imgState.moveY = e.clientY;
-    }
-
-    // 获取图片样式
-    const getImageStyle = computed(() => {
-      const { imgScale, imgRotate, imgTop, imgLeft } = imgState;
-      return {
-        transform: `scale(${imgScale}) rotate(${imgRotate}deg)`,
-        marginTop: `${imgTop}px`,
-        marginLeft: `${imgLeft}px`,
-      };
-    });
-
-    const getIsMultipleImage = computed(() => {
-      const { imageList } = props;
-      return imageList.length > 1;
-    });
-
-    watchEffect(() => {
-      if (props.show) {
-        init();
-      }
-      if (props.imageList) {
-        initState();
-      }
-    });
-
-    const renderClose = () => {
-      return (
-        <div class={`${prefixCls}__close`} onClick={handleClose}>
-          <CloseOutlined class={`${prefixCls}__close-icon`} />
-        </div>
-      );
-    };
-
-    const renderIndex = () => {
-      if (!unref(getIsMultipleImage)) {
-        return null;
-      }
-      const { currentIndex } = imgState;
-      const { imageList } = props;
-      return (
-        <div class={`${prefixCls}__index`}>
-          {currentIndex + 1} / {imageList.length}
-        </div>
-      );
-    };
-
-    const renderController = () => {
-      return (
-        <div class={`${prefixCls}__controller`}>
-          <div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(-0.15)}>
-            <img src={unScaleSvg} />
-          </div>
-          <div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(0.15)}>
-            <img src={scaleSvg} />
-          </div>
-          <div class={`${prefixCls}__controller-item`} onClick={resume}>
-            <img src={resumeSvg} />
-          </div>
-          <div class={`${prefixCls}__controller-item`} onClick={() => rotateFunc(-90)}>
-            <img src={unRotateSvg} />
-          </div>
-          <div class={`${prefixCls}__controller-item`} onClick={() => rotateFunc(90)}>
-            <img src={rotateSvg} />
-          </div>
-        </div>
-      );
-    };
-
-    const renderArrow = (direction: 'left' | 'right') => {
-      if (!unref(getIsMultipleImage)) {
-        return null;
-      }
-      return (
-        <div class={[`${prefixCls}__arrow`, direction]} onClick={() => handleChange(direction)}>
-          {direction === 'left' ? <LeftOutlined /> : <RightOutlined />}
-        </div>
-      );
-    };
-
-    return () => {
-      return (
-        imgState.show && (
-          <div class={prefixCls} ref={wrapElRef} onMouseup={handleMouseUp}>
-            <div class={`${prefixCls}-content`}>
-              {/*<Spin*/}
-              {/*  indicator={<LoadingOutlined style="font-size: 24px" spin />}*/}
-              {/*  spinning={true}*/}
-              {/*  class={[*/}
-              {/*    `${prefixCls}-image`,*/}
-              {/*    {*/}
-              {/*      hidden: imgState.status !== StatueEnum.LOADING,*/}
-              {/*    },*/}
-              {/*  ]}*/}
-              {/*/>*/}
-              <img
-                style={unref(getImageStyle)}
-                class={[`${prefixCls}-image`, imgState.status === StatueEnum.DONE ? '' : 'hidden']}
-                ref={imgElRef}
-                src={imgState.currentUrl}
-                onMousedown={handleAddMoveListener}
-              />
-              {renderClose()}
-              {renderIndex()}
-              {renderController()}
-              {renderArrow('left')}
-              {renderArrow('right')}
-            </div>
-          </div>
-        )
-      );
-    };
-  },
-});

+ 0 - 15
src/components/Preview/src/props.ts

@@ -1,15 +0,0 @@
-import { PropType } from 'vue';
-export const basicProps = {
-  show: {
-    type: Boolean as PropType<boolean>,
-    default: false,
-  },
-  imageList: {
-    type: [Array] as PropType<string[]>,
-    default: null,
-  },
-  index: {
-    type: Number as PropType<number>,
-    default: 0,
-  },
-};

+ 0 - 0
src/components/Preview/src/types.ts → src/components/Preview/src/typing.ts


+ 1 - 1
src/views/demo/feat/img-preview/index.vue

@@ -1,7 +1,7 @@
 <template>
   <PageWrapper title="图片预览示例">
-    <p @click="openImg">打开图片</p>
     <ImagePreview :imageList="imgList" />
+    <a-button @click="openImg" type="primary">无预览图</a-button>
   </PageWrapper>
 </template>
 <script lang="ts">