Commit b7c7c46853d332641d116d818e657447884784f3

Authored by jinmao88
Committed by GitHub
1 parent 33cd8fe6

perf: add createImgPreview func (#713)

src/assets/svg/preview/p-rotate.svg 0 → 100644
  1 +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1595306944988" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1820" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><defs><style type="text/css"></style></defs><path d="M1464.3 279.7" p-id="1821" fill="#ffffff"></path><path d="M512 960c-60.5 0-119.1-11.9-174.4-35.2-53.4-22.6-101.3-54.9-142.4-96s-73.4-89-96-142.4C75.9 631.1 64 572.5 64 512s11.9-119.1 35.2-174.4c22.6-53.4 54.9-101.3 96-142.4s89-73.4 142.4-96C392.9 75.9 451.5 64 512 64s119.1 11.9 174.4 35.2c53.4 22.6 101.3 54.9 142.4 96s73.4 89 96 142.4C948.1 392.9 960 451.5 960 512c0 19.1-15.5 34.6-34.6 34.6s-34.6-15.5-34.6-34.6c0-51.2-10-100.8-29.8-147.4-19.1-45.1-46.4-85.6-81.2-120.4C745 209.4 704.5 182 659.4 163c-46.7-19.7-96.3-29.8-147.4-29.8-51.2 0-100.8 10-147.4 29.8-45.1 19.1-85.6 46.4-120.4 81.2S182 319.5 163 364.6c-19.7 46.7-29.8 96.3-29.8 147.4 0 51.2 10 100.8 29.8 147.4 19.1 45.1 46.4 85.6 81.2 120.4C279 814.6 319.5 842 364.6 861c46.7 19.7 96.3 29.8 147.4 29.8 64.6 0 128.4-16.5 184.4-47.8 54.4-30.4 100.9-74.1 134.6-126.6 10.3-16.1 31.7-20.8 47.8-10.4 16.1 10.3 20.8 31.7 10.4 47.8-39.8 62-94.8 113.7-159.1 149.6-66.2 37-141.7 56.6-218.1 56.6z" p-id="1822" fill="#ffffff"></path><path d="M924 552c-19.8 0-36-16.2-36-36V228c0-19.8 16.2-36 36-36s36 16.2 36 36v288c0 19.8-16.2 36-36 36zM275.4 575.5c9.5-2.5 19.1 2.9 22.3 12.2 3.5 10.2 9.9 17.7 19.1 22.6 7.1 3.9 15.1 5.8 24 5.8 16.6 0 30.8-6.9 42.5-20.8 11.7-13.8 20-32.7 24.9-75.1-7.7 12.2-17.3 20.8-28.7 25.8-11.4 5-23.7 7.4-36.8 7.4-26.7 0-47.7-8.3-63.3-24.9-15.5-16.6-23.3-37.9-23.3-64.1 0-25.1 7.7-47.1 23-66.2 15.3-19 37.9-28.6 67.8-28.6 40.3 0 68.1 18.1 83.4 54.4 8.5 19.9 12.7 44.9 12.7 74.9 0 33.8-5.1 63.8-15.3 89.9-16.9 43.5-45.5 65.2-85.8 65.2-27 0-47.6-7.1-61.6-21.2-10-10.1-16.4-22-19.3-35.8-2-9.6 4-19.1 13.5-21.6l0.9 0.1z m103-74.4c9.4-7.5 14.1-20.6 14.1-39.3 0-16.8-4.2-29.3-12.7-37.5S360.6 412 347.5 412c-14 0-25.2 4.7-33.4 14.1-8.2 9.4-12.4 22-12.4 37.7 0 14.9 3.6 26.7 10.9 35.5 7.2 8.8 18.8 13.1 34.6 13.1 11.4 0 21.8-3.8 31.2-11.3zM646.6 414.4c12.4 22.8 18.5 54 18.5 93.7 0 37.6-5.6 68.7-16.8 93.3-16.2 35.3-42.8 52.9-79.6 52.9-33.2 0-57.9-14.4-74.2-43.3-13.5-24.1-20.3-56.4-20.3-97 0-31.4 4.1-58.4 12.2-80.9 15.2-42 42.7-63 82.5-63 35.9 0 61.8 14.8 77.7 44.3z m-40.2 173.3c9.4-13.9 14-39.9 14-78 0-27.4-3.4-50-10.1-67.7-6.8-17.7-19.9-26.6-39.4-26.6-17.9 0-31 8.4-39.3 25.2-8.3 16.8-12.4 41.6-12.4 74.3 0 24.6 2.6 44.4 7.9 59.4 8.1 22.8 22 34.3 41.6 34.3 15.7 0 28.3-7 37.7-20.9zM803.3 387.2c11.2 11.3 16.8 25 16.8 41.2 0 16.7-5.8 30.7-17.5 41.8C791 481.4 777.4 487 762 487c-17.1 0-31.2-5.8-42.1-17.4-10.9-11.6-16.4-25.1-16.4-40.6 0-16.5 5.8-30.4 17.3-41.7 11.5-11.3 25.3-17 41.2-17 16.3 0 30.1 5.7 41.3 16.9zM739.5 451c6.2 6.2 13.7 9.3 22.5 9.3 8.4 0 15.8-3.1 22.1-9.3 6.3-6.2 9.4-13.7 9.4-22.6 0-8.5-3.1-15.9-9.3-22.1-6.2-6.2-13.6-9.3-22.2-9.3s-16.1 3.1-22.4 9.3c-6.3 6.2-9.4 13.7-9.4 22.6-0.1 8.4 3 15.8 9.3 22.1z" p-id="1823" fill="#ffffff"></path></svg>
... ...
src/assets/svg/preview/resume.svg 0 → 100644
  1 +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1595307154239" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7317" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><defs><style type="text/css"></style></defs><path d="M316 672h60c4.4 0 8-3.6 8-8V360c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v304c0 4.4 3.6 8 8 8zM512 622c22.1 0 40-17.9 40-39 0-23.1-17.9-41-40-41s-40 17.9-40 41c0 21.1 17.9 39 40 39zM512 482c22.1 0 40-17.9 40-39 0-23.1-17.9-41-40-41s-40 17.9-40 41c0 21.1 17.9 39 40 39z" p-id="7318" fill="#ffffff"></path><path d="M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32z m-40 728H184V184h656v656z" p-id="7319" fill="#ffffff"></path><path d="M648 672h60c4.4 0 8-3.6 8-8V360c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v304c0 4.4 3.6 8 8 8z" p-id="7320" fill="#ffffff"></path></svg>
... ...
src/assets/svg/preview/scale.svg 0 → 100644
  1 +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1595307195033" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8116" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><defs><style type="text/css"></style></defs><path d="M887.081 904.791a25.8 25.8 0 0 1-18.376-7.619L705.618 734.075l-4.163 3.369c-58.255 47.18-131.522 73.16-206.32 73.16-181.07 0-328.377-147.308-328.377-328.367 0-181.068 147.308-328.376 328.377-328.376 181.063 0 328.376 147.308 328.376 328.376 0 77.072-27.412 152.07-77.169 211.17l-3.522 4.173 162.719 162.744a25.846 25.846 0 0 1 7.639 18.432 26.081 26.081 0 0 1-26.051 26.045l-0.046-0.01zM495.13 205.957c-152.336 0-276.27 123.935-276.27 276.27 0 152.33 123.934 276.27 276.27 276.27 152.34 0 276.275-123.94 276.275-276.27 0-152.335-123.935-276.27-276.275-276.27z" fill="#ffffff" p-id="8117"></path><path d="M626.545 508.355h-262.83a26.127 26.127 0 0 1 0-52.255h262.83a26.127 26.127 0 0 1 0 52.255z" fill="#ffffff" p-id="8118"></path><path d="M495.13 639.77a26.127 26.127 0 0 1-26.128-26.128v-262.83a26.127 26.127 0 0 1 52.255 0v262.835a26.127 26.127 0 0 1-26.127 26.123z" fill="#ffffff" p-id="8119"></path></svg>
... ...
src/assets/svg/preview/unrotate.svg 0 → 100644
  1 +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1595306911635" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1352" width="48" height="48" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M924.8 337.6c-22.6-53.4-54.9-101.3-96-142.4s-89-73.4-142.4-96C631.1 75.9 572.5 64 512 64S392.9 75.9 337.6 99.2c-53.4 22.6-101.3 54.9-142.4 96-22.4 22.4-42.2 46.8-59.2 73.1V228c0-19.8-16.2-36-36-36s-36 16.2-36 36v288c0 19.8 16.2 36 36 36s36-16.2 36-36v-50.2c4.2-34.8 13.2-68.7 27-101.2 19.1-45.1 46.4-85.6 81.2-120.4C279 209.4 319.5 182 364.6 163c46.7-19.7 96.3-29.8 147.4-29.8 51.2 0 100.8 10 147.4 29.8 45.1 19.1 85.6 46.4 120.4 81.2C814.6 279 842 319.5 861 364.6c19.7 46.7 29.8 96.3 29.8 147.4 0 51.2-10 100.8-29.8 147.4-19.1 45.1-46.4 85.6-81.2 120.4C745 814.6 704.5 842 659.4 861c-46.7 19.7-96.3 29.8-147.4 29.8-64.6 0-128.4-16.5-184.4-47.8-54.4-30.4-100.9-74.1-134.6-126.6-10.3-16.1-31.7-20.8-47.8-10.4-16.1 10.3-20.8 31.7-10.4 47.8 39.8 62 94.8 113.7 159.1 149.6 66.2 37 141.7 56.6 218.1 56.6 60.5 0 119.1-11.9 174.4-35.2 53.4-22.6 101.3-54.9 142.4-96 41.1-41.1 73.4-89 96-142.4C948.1 631.1 960 572.5 960 512s-11.9-119.1-35.2-174.4z" p-id="1353" fill="#ffffff"></path><path d="M275.4 575.5c9.5-2.5 19.1 2.9 22.3 12.2 3.5 10.2 9.9 17.7 19.1 22.6 7.1 3.9 15.1 5.8 24 5.8 16.6 0 30.8-6.9 42.5-20.8 11.7-13.8 20-32.7 24.9-75.1-7.7 12.2-17.3 20.8-28.7 25.8-11.4 5-23.7 7.4-36.8 7.4-26.7 0-47.7-8.3-63.3-24.9-15.5-16.6-23.3-37.9-23.3-64.1 0-25.1 7.7-47.1 23-66.2 15.3-19 37.9-28.6 67.8-28.6 40.3 0 68.1 18.1 83.4 54.4 8.5 19.9 12.7 44.9 12.7 74.9 0 33.8-5.1 63.8-15.3 89.9-16.9 43.5-45.5 65.2-85.8 65.2-27 0-47.6-7.1-61.6-21.2-10-10.1-16.4-22-19.3-35.8-2-9.6 4-19.1 13.5-21.6l0.9 0.1z m103-74.4c9.4-7.5 14.1-20.6 14.1-39.3 0-16.8-4.2-29.3-12.7-37.5S360.6 412 347.5 412c-14 0-25.2 4.7-33.4 14.1-8.2 9.4-12.4 22-12.4 37.7 0 14.9 3.6 26.7 10.9 35.5 7.2 8.8 18.8 13.1 34.6 13.1 11.4 0 21.8-3.8 31.2-11.3zM646.6 414.4c12.4 22.8 18.5 54 18.5 93.7 0 37.6-5.6 68.7-16.8 93.3-16.2 35.3-42.8 52.9-79.6 52.9-33.2 0-57.9-14.4-74.2-43.3-13.5-24.1-20.3-56.4-20.3-97 0-31.4 4.1-58.4 12.2-80.9 15.2-42 42.7-63 82.5-63 35.9 0 61.8 14.8 77.7 44.3z m-40.2 173.3c9.4-13.9 14-39.9 14-78 0-27.4-3.4-50-10.1-67.7-6.8-17.7-19.9-26.6-39.4-26.6-17.9 0-31 8.4-39.3 25.2-8.3 16.8-12.4 41.6-12.4 74.3 0 24.6 2.6 44.4 7.9 59.4 8.1 22.8 22 34.3 41.6 34.3 15.7 0 28.3-7 37.7-20.9zM803.3 387.2c11.2 11.3 16.8 25 16.8 41.2 0 16.7-5.8 30.7-17.5 41.8C791 481.4 777.4 487 762 487c-17.1 0-31.2-5.8-42.1-17.4-10.9-11.6-16.4-25.1-16.4-40.6 0-16.5 5.8-30.4 17.3-41.7 11.5-11.3 25.3-17 41.2-17 16.3 0 30.1 5.7 41.3 16.9zM739.5 451c6.2 6.2 13.7 9.3 22.5 9.3 8.4 0 15.8-3.1 22.1-9.3 6.3-6.2 9.4-13.7 9.4-22.6 0-8.5-3.1-15.9-9.3-22.1-6.2-6.2-13.6-9.3-22.2-9.3s-16.1 3.1-22.4 9.3c-6.3 6.2-9.4 13.7-9.4 22.6-0.1 8.4 3 15.8 9.3 22.1z" p-id="1354" fill="#ffffff"></path></svg>
... ...
src/assets/svg/preview/unscale.svg 0 → 100644
  1 +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1595308005241" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9878" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><defs><style type="text/css"></style></defs><path d="M750.3 198.7C598 46.4 351.1 46.4 198.7 198.7s-152.3 399.2 0 551.5C345.1 896.6 578.8 902.3 732 767.3l172.1 172.1 35.4-35.4-172.1-171.9c135-153.2 129.3-387-17.1-533.4z m39.3 403.8c-17.1 42.1-42.2 80-74.7 112.4-32.5 32.5-70.3 57.6-112.4 74.7-40.7 16.5-83.8 24.9-128 24.9s-87.2-8.4-128-24.9c-42.1-17.1-80-42.2-112.4-74.7s-57.6-70.3-74.7-112.4c-16.5-40.7-24.9-83.8-24.9-128s8.4-87.2 24.9-128c17.1-42.1 42.2-80 74.7-112.4s70.3-57.6 112.4-74.7c40.7-16.5 83.8-24.9 128-24.9s87.2 8.4 128 24.9c42.1 17.1 80 42.2 112.4 74.7 32.5 32.5 57.6 70.3 74.7 112.4 16.5 40.7 24.9 83.8 24.9 128s-8.4 87.3-24.9 128zM671 502H271v-50h400v50z" fill="#ffffff" p-id="9879"></path></svg>
... ...
src/components/Preview/index.ts
1 1 export { default as ImagePreview } from './src/Preview.vue';
  2 +export { createImgPreview } from './src/functional';
... ...
src/components/Preview/src/functional.ts 0 → 100644
  1 +import ImgPreview from './index';
  2 +import { isClient } from '/@/utils/is';
  3 +
  4 +import type { Options, Props } from './types';
  5 +
  6 +import { createVNode, render } from 'vue';
  7 +
  8 +let instance: any = null;
  9 +export function createImgPreview(options: Options) {
  10 + if (!isClient) return;
  11 + const { imageList, show = true, index = 0 } = options;
  12 +
  13 + const propsData: Partial<Props> = {};
  14 + const container = document.createElement('div');
  15 + propsData.imageList = imageList;
  16 + propsData.show = show;
  17 + propsData.index = index;
  18 +
  19 + instance = createVNode(ImgPreview, propsData);
  20 + render(instance, container);
  21 + document.body.appendChild(container);
  22 +}
... ...
src/components/Preview/src/index.less 0 → 100644
  1 +.img-preview {
  2 + position: fixed;
  3 + top: 0;
  4 + right: 0;
  5 + bottom: 0;
  6 + left: 0;
  7 + z-index: @preview-comp-z-index;
  8 + background: rgba(0, 0, 0, 0.5);
  9 + user-select: none;
  10 +
  11 + &-content {
  12 + display: flex;
  13 + width: 100%;
  14 + height: 100%;
  15 + color: @white;
  16 + justify-content: center;
  17 + align-items: center;
  18 + }
  19 +
  20 + &-image {
  21 + cursor: pointer;
  22 + transition: transform 0.3s;
  23 + }
  24 +
  25 + &__close {
  26 + position: absolute;
  27 + top: -40px;
  28 + right: -40px;
  29 + width: 80px;
  30 + height: 80px;
  31 + overflow: hidden;
  32 + color: @white;
  33 + cursor: pointer;
  34 + background-color: rgba(0, 0, 0, 0.5);
  35 + border-radius: 50%;
  36 + transition: all 0.2s;
  37 +
  38 + &-icon {
  39 + position: absolute;
  40 + top: 46px;
  41 + left: 16px;
  42 + font-size: 16px;
  43 + }
  44 +
  45 + &:hover {
  46 + background-color: rgba(0, 0, 0, 0.8);
  47 + }
  48 + }
  49 +
  50 + &__index {
  51 + position: absolute;
  52 + bottom: 5%;
  53 + left: 50%;
  54 + padding: 0 22px;
  55 + font-size: 16px;
  56 + background: rgba(109, 109, 109, 0.6);
  57 + border-radius: 15px;
  58 + transform: translateX(-50%);
  59 + }
  60 +
  61 + &__controller {
  62 + position: absolute;
  63 + bottom: 10%;
  64 + left: 50%;
  65 + display: flex;
  66 + width: 260px;
  67 + height: 44px;
  68 + padding: 0 22px;
  69 + margin-left: -139px;
  70 + background: rgba(109, 109, 109, 0.6);
  71 + border-radius: 22px;
  72 + justify-content: center;
  73 +
  74 + &-item {
  75 + display: flex;
  76 + height: 100%;
  77 + padding: 0 9px;
  78 + font-size: 24px;
  79 + cursor: pointer;
  80 + transition: all 0.2s;
  81 +
  82 + &:hover {
  83 + transform: scale(1.2);
  84 + }
  85 +
  86 + img {
  87 + width: 1em;
  88 + }
  89 + }
  90 + }
  91 +
  92 + &__arrow {
  93 + position: absolute;
  94 + top: 50%;
  95 + display: flex;
  96 + align-items: center;
  97 + justify-content: center;
  98 + width: 50px;
  99 + height: 50px;
  100 + font-size: 28px;
  101 + cursor: pointer;
  102 + background-color: rgba(0, 0, 0, 0.5);
  103 + border-radius: 50%;
  104 + transition: all 0.2s;
  105 +
  106 + &:hover {
  107 + background-color: rgba(0, 0, 0, 0.8);
  108 + }
  109 +
  110 + &.left {
  111 + left: 50px;
  112 + }
  113 +
  114 + &.right {
  115 + right: 50px;
  116 + }
  117 + }
  118 +}
... ...
src/components/Preview/src/index.tsx 0 → 100644
  1 +import './index.less';
  2 +
  3 +import { defineComponent, ref, unref, computed, reactive, watchEffect } from 'vue';
  4 +
  5 +// @ts-ignore
  6 +import { basicProps } from './props';
  7 +// @ts-ignore
  8 +import { Props } from './types';
  9 +
  10 +import { CloseOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
  11 +// import { Spin } from 'ant-design-vue';
  12 +
  13 +import resumeSvg from '/@/assets/svg/preview/resume.svg';
  14 +import rotateSvg from '/@/assets/svg/preview/p-rotate.svg';
  15 +import scaleSvg from '/@/assets/svg/preview/scale.svg';
  16 +import unScaleSvg from '/@/assets/svg/preview/unscale.svg';
  17 +import unRotateSvg from '/@/assets/svg/preview/unrotate.svg';
  18 +enum StatueEnum {
  19 + LOADING,
  20 + DONE,
  21 + FAIL,
  22 +}
  23 +interface ImgState {
  24 + currentUrl: string;
  25 + imgScale: number;
  26 + imgRotate: number;
  27 + imgTop: number;
  28 + imgLeft: number;
  29 + currentIndex: number;
  30 + status: StatueEnum;
  31 + moveX: number;
  32 + moveY: number;
  33 + show: boolean;
  34 +}
  35 +
  36 +const prefixCls = 'img-preview';
  37 +export default defineComponent({
  38 + name: 'ImagePreview',
  39 + props: basicProps,
  40 + setup(props: Props) {
  41 + const imgState = reactive<ImgState>({
  42 + currentUrl: '',
  43 + imgScale: 1,
  44 + imgRotate: 0,
  45 + imgTop: 0,
  46 + imgLeft: 0,
  47 + status: StatueEnum.LOADING,
  48 + currentIndex: 0,
  49 + moveX: 0,
  50 + moveY: 0,
  51 + show: props.show,
  52 + });
  53 +
  54 + const wrapElRef = ref<HTMLDivElement | null>(null);
  55 + const imgElRef = ref<HTMLImageElement | null>(null);
  56 +
  57 + // 初始化
  58 + function init() {
  59 + initMouseWheel();
  60 + const { index, imageList } = props;
  61 +
  62 + if (!imageList || !imageList.length) {
  63 + throw new Error('imageList is undefined');
  64 + }
  65 + imgState.currentIndex = index;
  66 + handleIChangeImage(imageList[index]);
  67 + }
  68 +
  69 + // 重置
  70 + function initState() {
  71 + imgState.imgScale = 1;
  72 + imgState.imgRotate = 0;
  73 + imgState.imgTop = 0;
  74 + imgState.imgLeft = 0;
  75 + }
  76 +
  77 + // 初始化鼠标滚轮事件
  78 + function initMouseWheel() {
  79 + const wrapEl = unref(wrapElRef);
  80 + if (!wrapEl) {
  81 + return;
  82 + }
  83 + (wrapEl as any).onmousewheel = scrollFunc;
  84 + // 火狐浏览器没有onmousewheel事件,用DOMMouseScroll代替
  85 + document.body.addEventListener('DOMMouseScroll', scrollFunc);
  86 + // 禁止火狐浏览器下拖拽图片的默认事件
  87 + document.ondragstart = function () {
  88 + return false;
  89 + };
  90 + }
  91 +
  92 + // 监听鼠标滚轮
  93 + function scrollFunc(e: any) {
  94 + e = e || window.event;
  95 + e.delta = e.wheelDelta || -e.detail;
  96 +
  97 + e.preventDefault();
  98 + if (e.delta > 0) {
  99 + // 滑轮向上滚动
  100 + scaleFunc(0.015);
  101 + }
  102 + if (e.delta < 0) {
  103 + // 滑轮向下滚动
  104 + scaleFunc(-0.015);
  105 + }
  106 + }
  107 + // 缩放函数
  108 + function scaleFunc(num: number) {
  109 + if (imgState.imgScale <= 0.2 && num < 0) return;
  110 + imgState.imgScale += num;
  111 + }
  112 +
  113 + // 旋转图片
  114 + function rotateFunc(deg: number) {
  115 + imgState.imgRotate += deg;
  116 + }
  117 +
  118 + // 鼠标事件
  119 + function handleMouseUp() {
  120 + const imgEl = unref(imgElRef);
  121 + if (!imgEl) return;
  122 + imgEl.onmousemove = null;
  123 + }
  124 +
  125 + // 更换图片
  126 + function handleIChangeImage(url: string) {
  127 + imgState.status = StatueEnum.LOADING;
  128 + const img = new Image();
  129 + img.src = url;
  130 + img.onload = () => {
  131 + imgState.currentUrl = url;
  132 + imgState.status = StatueEnum.DONE;
  133 + };
  134 + img.onerror = () => {
  135 + imgState.status = StatueEnum.FAIL;
  136 + };
  137 + }
  138 +
  139 + // 关闭
  140 + function handleClose(e: MouseEvent) {
  141 + e && e.stopPropagation();
  142 + imgState.show = false;
  143 + // 移除火狐浏览器下的鼠标滚动事件
  144 + document.body.removeEventListener('DOMMouseScroll', scrollFunc);
  145 + // 恢复火狐及Safari浏览器下的图片拖拽
  146 + document.ondragstart = null;
  147 + }
  148 +
  149 + // 图片复原
  150 + function resume() {
  151 + initState();
  152 + }
  153 +
  154 + // 上一页下一页
  155 + function handleChange(direction: 'left' | 'right') {
  156 + const { currentIndex } = imgState;
  157 + const { imageList } = props;
  158 + if (direction === 'left') {
  159 + imgState.currentIndex--;
  160 + if (currentIndex <= 0) {
  161 + imgState.currentIndex = imageList.length - 1;
  162 + }
  163 + }
  164 + if (direction === 'right') {
  165 + imgState.currentIndex++;
  166 + if (currentIndex >= imageList.length - 1) {
  167 + imgState.currentIndex = 0;
  168 + }
  169 + }
  170 + handleIChangeImage(imageList[imgState.currentIndex]);
  171 + }
  172 +
  173 + function handleAddMoveListener(e: MouseEvent) {
  174 + e = e || window.event;
  175 + imgState.moveX = e.clientX;
  176 + imgState.moveY = e.clientY;
  177 + const imgEl = unref(imgElRef);
  178 + if (imgEl) {
  179 + imgEl.onmousemove = moveFunc;
  180 + }
  181 + }
  182 +
  183 + function moveFunc(e: MouseEvent) {
  184 + e = e || window.event;
  185 + e.preventDefault();
  186 + const movementX = e.clientX - imgState.moveX;
  187 + const movementY = e.clientY - imgState.moveY;
  188 + imgState.imgLeft += movementX;
  189 + imgState.imgTop += movementY;
  190 + imgState.moveX = e.clientX;
  191 + imgState.moveY = e.clientY;
  192 + }
  193 +
  194 + // 获取图片样式
  195 + const getImageStyle = computed(() => {
  196 + const { imgScale, imgRotate, imgTop, imgLeft } = imgState;
  197 + return {
  198 + transform: `scale(${imgScale}) rotate(${imgRotate}deg)`,
  199 + marginTop: `${imgTop}px`,
  200 + marginLeft: `${imgLeft}px`,
  201 + };
  202 + });
  203 +
  204 + const getIsMultipleImage = computed(() => {
  205 + const { imageList } = props;
  206 + return imageList.length > 1;
  207 + });
  208 +
  209 + watchEffect(() => {
  210 + if (props.show) {
  211 + init();
  212 + }
  213 + if (props.imageList) {
  214 + initState();
  215 + }
  216 + });
  217 +
  218 + const renderClose = () => {
  219 + return (
  220 + <div class={`${prefixCls}__close`} onClick={handleClose}>
  221 + <CloseOutlined class={`${prefixCls}__close-icon`} />
  222 + </div>
  223 + );
  224 + };
  225 +
  226 + const renderIndex = () => {
  227 + if (!unref(getIsMultipleImage)) {
  228 + return null;
  229 + }
  230 + const { currentIndex } = imgState;
  231 + const { imageList } = props;
  232 + return (
  233 + <div class={`${prefixCls}__index`}>
  234 + {currentIndex + 1} / {imageList.length}
  235 + </div>
  236 + );
  237 + };
  238 +
  239 + const renderController = () => {
  240 + return (
  241 + <div class={`${prefixCls}__controller`}>
  242 + <div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(-0.15)}>
  243 + <img src={unScaleSvg} />
  244 + </div>
  245 + <div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(0.15)}>
  246 + <img src={scaleSvg} />
  247 + </div>
  248 + <div class={`${prefixCls}__controller-item`} onClick={resume}>
  249 + <img src={resumeSvg} />
  250 + </div>
  251 + <div class={`${prefixCls}__controller-item`} onClick={() => rotateFunc(-90)}>
  252 + <img src={unRotateSvg} />
  253 + </div>
  254 + <div class={`${prefixCls}__controller-item`} onClick={() => rotateFunc(90)}>
  255 + <img src={rotateSvg} />
  256 + </div>
  257 + </div>
  258 + );
  259 + };
  260 +
  261 + const renderArrow = (direction: 'left' | 'right') => {
  262 + if (!unref(getIsMultipleImage)) {
  263 + return null;
  264 + }
  265 + return (
  266 + <div class={[`${prefixCls}__arrow`, direction]} onClick={() => handleChange(direction)}>
  267 + {direction === 'left' ? <LeftOutlined /> : <RightOutlined />}
  268 + </div>
  269 + );
  270 + };
  271 +
  272 + return () => {
  273 + return (
  274 + imgState.show && (
  275 + <div class={prefixCls} ref={wrapElRef} onMouseup={handleMouseUp}>
  276 + <div class={`${prefixCls}-content`}>
  277 + {/*<Spin*/}
  278 + {/* indicator={<LoadingOutlined style="font-size: 24px" spin />}*/}
  279 + {/* spinning={true}*/}
  280 + {/* class={[*/}
  281 + {/* `${prefixCls}-image`,*/}
  282 + {/* {*/}
  283 + {/* hidden: imgState.status !== StatueEnum.LOADING,*/}
  284 + {/* },*/}
  285 + {/* ]}*/}
  286 + {/*/>*/}
  287 + <img
  288 + style={unref(getImageStyle)}
  289 + class={[`${prefixCls}-image`, imgState.status === StatueEnum.DONE ? '' : 'hidden']}
  290 + ref={imgElRef}
  291 + src={imgState.currentUrl}
  292 + onMousedown={handleAddMoveListener}
  293 + />
  294 + {renderClose()}
  295 + {renderIndex()}
  296 + {renderController()}
  297 + {renderArrow('left')}
  298 + {renderArrow('right')}
  299 + </div>
  300 + </div>
  301 + )
  302 + );
  303 + };
  304 + },
  305 +});
... ...
src/components/Preview/src/props.ts 0 → 100644
  1 +import { PropType } from 'vue';
  2 +export const basicProps = {
  3 + show: {
  4 + type: Boolean as PropType<boolean>,
  5 + default: false,
  6 + },
  7 + imageList: {
  8 + type: [Array] as PropType<string[]>,
  9 + default: null,
  10 + },
  11 + index: {
  12 + type: Number as PropType<number>,
  13 + default: 0,
  14 + },
  15 +};
... ...
src/components/Preview/src/types.ts 0 → 100644
  1 +export interface Options {
  2 + show?: boolean;
  3 + imageList: string[];
  4 + index?: number;
  5 +}
  6 +
  7 +export interface Props {
  8 + show: boolean;
  9 + instance: Props;
  10 + imageList: string[];
  11 + index: number;
  12 +}
  13 +
  14 +export interface ImageProps {
  15 + alt?: string;
  16 + fallback?: string;
  17 + src: string;
  18 + width: string | number;
  19 + height?: string | number;
  20 + placeholder?: string | boolean;
  21 + preview?:
  22 + | boolean
  23 + | {
  24 + visible?: boolean;
  25 + onVisibleChange?: (visible: boolean, prevVisible: boolean) => void;
  26 + getContainer: string | HTMLElement | (() => HTMLElement);
  27 + };
  28 +}
  29 +
  30 +export type ImageItem = string | ImageProps;
... ...
src/views/demo/feat/img-preview/index.vue
1 1 <template>
2 2 <PageWrapper title="图片预览示例">
  3 + <p @click="openImg">打开图片</p>
3 4 <ImagePreview :imageList="imgList" />
4 5 </PageWrapper>
5 6 </template>
6 7 <script lang="ts">
7 8 import { defineComponent } from 'vue';
8   - import { ImagePreview } from '/@/components/Preview/index';
  9 + import { createImgPreview, ImagePreview } from '/@/components/Preview/index';
9 10 import { PageWrapper } from '/@/components/Page';
10 11  
11 12 const imgList: string[] = [
... ... @@ -16,7 +17,10 @@
16 17 export default defineComponent({
17 18 components: { PageWrapper, ImagePreview },
18 19 setup() {
19   - return { imgList };
  20 + function openImg() {
  21 + createImgPreview({ imageList: imgList });
  22 + }
  23 + return { imgList, openImg };
20 24 },
21 25 });
22 26 </script>
... ...