Commit 305630e3fd886b3f690f890a934a8a6ba224fba1

Authored by Vben
1 parent 3f6920f7

feat(preview): added createImgPreview picture preview function

CHANGELOG.zh_CN.md
... ... @@ -9,6 +9,7 @@
9 9 - **CropperImage** `Cropper` 头像裁剪新增圆形裁剪功能
10 10 - **CropperAvatar** 新增头像上传组件
11 11 - **Drawer** `useDrawer`新增`closeDrawer`函数
  12 +- **Preview** 新增`createImgPreview`图片预览函数
12 13  
13 14 ### 🐛 Bug Fixes
14 15  
... ...
src/components/Form/src/components/ApiSelect.vue
... ... @@ -26,7 +26,6 @@
26 26 import { useRuleFormItem } from '/@/hooks/component/useFormItem';
27 27 import { useAttrs } from '/@/hooks/core/useAttrs';
28 28 import { get, omit } from 'lodash-es';
29   -
30 29 import { LoadingOutlined } from '@ant-design/icons-vue';
31 30 import { useI18n } from '/@/hooks/web/useI18n';
32 31 import { propTypes } from '/@/utils/propTypes';
... ...
src/components/Form/src/components/FormAction.vue
... ... @@ -40,13 +40,11 @@
40 40 <script lang="ts">
41 41 import type { ColEx } from '../types/index';
42 42 import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
43   -
44 43 import { defineComponent, computed, PropType } from 'vue';
45 44 import { Form, Col } from 'ant-design-vue';
46 45 import { Button } from '/@/components/Button';
47 46 import { BasicArrow } from '/@/components/Basic/index';
48 47 import { useFormContext } from '../hooks/useFormContext';
49   -
50 48 import { useI18n } from '/@/hooks/web/useI18n';
51 49 import { propTypes } from '/@/utils/propTypes';
52 50  
... ...
src/components/Form/src/components/FormItem.vue
... ... @@ -4,17 +4,14 @@
4 4 import type { FormSchema } from '../types/form';
5 5 import type { ValidationRule } from 'ant-design-vue/lib/form/Form';
6 6 import type { TableActionType } from '/@/components/Table';
7   -
8 7 import { defineComponent, computed, unref, toRefs } from 'vue';
9 8 import { Form, Col } from 'ant-design-vue';
10 9 import { componentMap } from '../componentMap';
11 10 import { BasicHelp } from '/@/components/Basic';
12   -
13 11 import { isBoolean, isFunction, isNull } from '/@/utils/is';
14 12 import { getSlot } from '/@/utils/helper/tsxHelper';
15 13 import { createPlaceholderMessage, setComponentRuleType } from '../helper';
16 14 import { upperFirst, cloneDeep } from 'lodash-es';
17   -
18 15 import { useItemLabelWidth } from '../hooks/useLabelWidth';
19 16 import { useI18n } from '/@/hooks/web/useI18n';
20 17  
... ... @@ -91,7 +88,6 @@
91 88 if (isBoolean(dynamicDisabled)) {
92 89 disabled = dynamicDisabled;
93 90 }
94   -
95 91 if (isFunction(dynamicDisabled)) {
96 92 disabled = dynamicDisabled(unref(getValues));
97 93 }
... ... @@ -276,7 +272,6 @@
276 272 : {
277 273 default: () => renderComponentContent,
278 274 };
279   -
280 275 return <Comp {...compAttr}>{compSlot}</Comp>;
281 276 }
282 277  
... ... @@ -317,7 +312,6 @@
317 312 };
318 313  
319 314 const showSuffix = !!suffix;
320   -
321 315 const getSuffix = isFunction(suffix) ? suffix(unref(getValues)) : suffix;
322 316  
323 317 return (
... ... @@ -338,16 +332,18 @@
338 332 </Form.Item>
339 333 );
340 334 }
  335 +
341 336 return () => {
342 337 const { colProps = {}, colSlot, renderColContent, component } = props.schema;
343   - if (!componentMap.has(component)) return null;
  338 + if (!componentMap.has(component)) {
  339 + return null;
  340 + }
344 341  
345 342 const { baseColProps = {} } = props.formProps;
346   -
347 343 const realColProps = { ...baseColProps, ...colProps };
348 344 const { isIfShow, isShow } = getShow();
349   -
350 345 const values = unref(getValues);
  346 +
351 347 const getContent = () => {
352 348 return colSlot
353 349 ? getSlot(slots, colSlot, values)
... ...
src/components/Form/src/components/RadioButtonGroup.vue
1 1 <!--
2 2 * @Description:It is troublesome to implement radio button group in the form. So it is extracted independently as a separate component
3 3 -->
4   -
5 4 <template>
6 5 <RadioGroup v-bind="attrs" v-model:value="state" button-style="solid">
7 6 <template v-for="item in getOptions" :key="`${item.value}`">
... ... @@ -17,6 +16,7 @@
17 16 import { isString } from '/@/utils/is';
18 17 import { useRuleFormItem } from '/@/hooks/component/useFormItem';
19 18 import { useAttrs } from '/@/hooks/core/useAttrs';
  19 +
20 20 type OptionsItem = { label: string; value: string | number | boolean; disabled?: boolean };
21 21 type RadioItem = string | OptionsItem;
22 22  
... ... @@ -39,6 +39,7 @@
39 39 const attrs = useAttrs();
40 40 // Embedded in the form, just use the hook binding to perform form verification
41 41 const [state] = useRuleFormItem(props);
  42 +
42 43 // Processing options value
43 44 const getOptions = computed((): OptionsItem[] => {
44 45 const { options } = props;
... ...
src/components/Form/src/hooks/useAdvanced.ts
... ... @@ -2,10 +2,8 @@ import type { ColEx } from &#39;../types&#39;;
2 2 import type { AdvanceState } from '../types/hooks';
3 3 import type { ComputedRef, Ref } from 'vue';
4 4 import type { FormProps, FormSchema } from '../types/form';
5   -
6 5 import { computed, unref, watch } from 'vue';
7 6 import { isBoolean, isFunction, isNumber, isObject } from '/@/utils/is';
8   -
9 7 import { useBreakpoint } from '/@/hooks/event/useBreakpoint';
10 8 import { useDebounceFn } from '@vueuse/core';
11 9  
... ...
src/components/Form/src/hooks/useAutoFocus.ts
... ... @@ -16,16 +16,22 @@ export async function useAutoFocus({
16 16 isInitedDefault,
17 17 }: UseAutoFocusContext) {
18 18 watchEffect(async () => {
19   - if (unref(isInitedDefault) || !unref(getProps).autoFocusFirstItem) return;
  19 + if (unref(isInitedDefault) || !unref(getProps).autoFocusFirstItem) {
  20 + return;
  21 + }
20 22 await nextTick();
21 23 const schemas = unref(getSchema);
22 24 const formEl = unref(formElRef);
23 25 const el = (formEl as any)?.$el as HTMLElement;
24   - if (!formEl || !el || !schemas || schemas.length === 0) return;
  26 + if (!formEl || !el || !schemas || schemas.length === 0) {
  27 + return;
  28 + }
25 29  
26 30 const firstItem = schemas[0];
27 31 // Only open when the first form item is input type
28   - if (!firstItem.component.includes('Input')) return;
  32 + if (!firstItem.component.includes('Input')) {
  33 + return;
  34 + }
29 35  
30 36 const inputEl = el.querySelector('.ant-row:first-child input') as Nullable<HTMLInputElement>;
31 37 if (!inputEl) return;
... ...
src/components/Form/src/hooks/useForm.ts
  1 +import type { FormProps, FormActionType, UseFormReturnType, FormSchema } from '../types/form';
  2 +import type { NamePath } from 'ant-design-vue/lib/form/interface';
  3 +import type { DynamicProps } from '/#/utils';
1 4 import { ref, onUnmounted, unref, nextTick, watch } from 'vue';
2   -
3 5 import { isProdMode } from '/@/utils/env';
4 6 import { error } from '/@/utils/log';
5 7 import { getDynamicProps } from '/@/utils';
6 8  
7   -import type { FormProps, FormActionType, UseFormReturnType, FormSchema } from '../types/form';
8   -import type { NamePath } from 'ant-design-vue/lib/form/interface';
9   -import type { DynamicProps } from '/#/utils';
10   -
11 9 export declare type ValidateFields = (nameList?: NamePath[]) => Promise<Recordable>;
12 10  
13 11 type Props = Partial<DynamicProps<FormProps>>;
... ...
src/components/Form/src/hooks/useFormEvents.ts
1 1 import type { ComputedRef, Ref } from 'vue';
2 2 import type { FormProps, FormSchema, FormActionType } from '../types/form';
3 3 import type { NamePath } from 'ant-design-vue/lib/form/interface';
4   -
5 4 import { unref, toRaw } from 'vue';
6   -
7 5 import { isArray, isFunction, isObject, isString } from '/@/utils/is';
8 6 import { deepMerge } from '/@/utils';
9 7 import { dateItemType, handleInputNumberValue } from '../helper';
... ...
src/components/Form/src/hooks/useFormValues.ts
1 1 import { isArray, isFunction, isObject, isString, isNullOrUnDef } from '/@/utils/is';
2 2 import { dateUtil } from '/@/utils/dateUtil';
3   -
4 3 import { unref } from 'vue';
5 4 import type { Ref, ComputedRef } from 'vue';
6 5 import type { FormProps, FormSchema } from '../types/form';
7   -
8 6 import { set } from 'lodash-es';
9 7  
10 8 interface UseFormValuesContext {
... ...
src/components/Form/src/types/form.ts
1 1 import type { NamePath, RuleObject } from 'ant-design-vue/lib/form/interface';
2 2 import type { VNode } from 'vue';
3 3 import type { ButtonProps as AntdButtonProps } from 'ant-design-vue/es/button/buttonTypes';
4   -
5 4 import type { FormItem } from './formItem';
6 5 import type { ColEx, ComponentType } from './index';
7 6 import type { TableActionType } from '/@/components/Table/src/types/table';
... ...
src/components/Form/src/types/index.ts
... ... @@ -90,9 +90,7 @@ export type ComponentType =
90 90 | 'InputCountDown'
91 91 | 'Select'
92 92 | 'ApiSelect'
93   - | 'SelectOptGroup'
94 93 | 'TreeSelect'
95   - | 'Transfer'
96 94 | 'RadioButtonGroup'
97 95 | 'RadioGroup'
98 96 | 'Checkbox'
... ...
src/components/Preview/src/index.tsx renamed to src/components/Preview/src/Functional.vue
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');
  1 +<script lang="tsx">
  2 + import { defineComponent, ref, unref, computed, reactive, watchEffect } from 'vue';
  3 + import { Props } from './typing';
  4 + import { CloseOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
  5 + import resumeSvg from '/@/assets/svg/preview/resume.svg';
  6 + import rotateSvg from '/@/assets/svg/preview/p-rotate.svg';
  7 + import scaleSvg from '/@/assets/svg/preview/scale.svg';
  8 + import unScaleSvg from '/@/assets/svg/preview/unscale.svg';
  9 + import unRotateSvg from '/@/assets/svg/preview/unrotate.svg';
  10 +
  11 + enum StatueEnum {
  12 + LOADING,
  13 + DONE,
  14 + FAIL,
  15 + }
  16 + interface ImgState {
  17 + currentUrl: string;
  18 + imgScale: number;
  19 + imgRotate: number;
  20 + imgTop: number;
  21 + imgLeft: number;
  22 + currentIndex: number;
  23 + status: StatueEnum;
  24 + moveX: number;
  25 + moveY: number;
  26 + show: boolean;
  27 + }
  28 + const props = {
  29 + show: {
  30 + type: Boolean as PropType<boolean>,
  31 + default: false,
  32 + },
  33 + imageList: {
  34 + type: [Array] as PropType<string[]>,
  35 + default: null,
  36 + },
  37 + index: {
  38 + type: Number as PropType<number>,
  39 + default: 0,
  40 + },
  41 + };
  42 +
  43 + const prefixCls = 'img-preview';
  44 + export default defineComponent({
  45 + name: 'ImagePreview',
  46 + props,
  47 + setup(props: Props) {
  48 + const imgState = reactive<ImgState>({
  49 + currentUrl: '',
  50 + imgScale: 1,
  51 + imgRotate: 0,
  52 + imgTop: 0,
  53 + imgLeft: 0,
  54 + status: StatueEnum.LOADING,
  55 + currentIndex: 0,
  56 + moveX: 0,
  57 + moveY: 0,
  58 + show: props.show,
  59 + });
  60 +
  61 + const wrapElRef = ref<HTMLDivElement | null>(null);
  62 + const imgElRef = ref<HTMLImageElement | null>(null);
  63 +
  64 + // 初始化
  65 + function init() {
  66 + initMouseWheel();
  67 + const { index, imageList } = props;
  68 +
  69 + if (!imageList || !imageList.length) {
  70 + throw new Error('imageList is undefined');
  71 + }
  72 + imgState.currentIndex = index;
  73 + handleIChangeImage(imageList[index]);
64 74 }
65   - imgState.currentIndex = index;
66   - handleIChangeImage(imageList[index]);
67   - }
68 75  
69   - // 重置
70   - function initState() {
71   - imgState.imgScale = 1;
72   - imgState.imgRotate = 0;
73   - imgState.imgTop = 0;
74   - imgState.imgLeft = 0;
75   - }
  76 + // 重置
  77 + function initState() {
  78 + imgState.imgScale = 1;
  79 + imgState.imgRotate = 0;
  80 + imgState.imgTop = 0;
  81 + imgState.imgLeft = 0;
  82 + }
76 83  
77   - // 初始化鼠标滚轮事件
78   - function initMouseWheel() {
79   - const wrapEl = unref(wrapElRef);
80   - if (!wrapEl) {
81   - return;
  84 + // 初始化鼠标滚轮事件
  85 + function initMouseWheel() {
  86 + const wrapEl = unref(wrapElRef);
  87 + if (!wrapEl) {
  88 + return;
  89 + }
  90 + (wrapEl as any).onmousewheel = scrollFunc;
  91 + // 火狐浏览器没有onmousewheel事件,用DOMMouseScroll代替
  92 + document.body.addEventListener('DOMMouseScroll', scrollFunc);
  93 + // 禁止火狐浏览器下拖拽图片的默认事件
  94 + document.ondragstart = function () {
  95 + return false;
  96 + };
82 97 }
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 98  
92   - // 监听鼠标滚轮
93   - function scrollFunc(e: any) {
94   - e = e || window.event;
95   - e.delta = e.wheelDelta || -e.detail;
  99 + // 监听鼠标滚轮
  100 + function scrollFunc(e: any) {
  101 + e = e || window.event;
  102 + e.delta = e.wheelDelta || -e.detail;
96 103  
97   - e.preventDefault();
98   - if (e.delta > 0) {
99   - // 滑轮向上滚动
100   - scaleFunc(0.015);
  104 + e.preventDefault();
  105 + if (e.delta > 0) {
  106 + // 滑轮向上滚动
  107 + scaleFunc(0.015);
  108 + }
  109 + if (e.delta < 0) {
  110 + // 滑轮向下滚动
  111 + scaleFunc(-0.015);
  112 + }
101 113 }
102   - if (e.delta < 0) {
103   - // 滑轮向下滚动
104   - scaleFunc(-0.015);
  114 + // 缩放函数
  115 + function scaleFunc(num: number) {
  116 + if (imgState.imgScale <= 0.2 && num < 0) return;
  117 + imgState.imgScale += num;
105 118 }
106   - }
107   - // 缩放函数
108   - function scaleFunc(num: number) {
109   - if (imgState.imgScale <= 0.2 && num < 0) return;
110   - imgState.imgScale += num;
111   - }
112 119  
113   - // 旋转图片
114   - function rotateFunc(deg: number) {
115   - imgState.imgRotate += deg;
116   - }
  120 + // 旋转图片
  121 + function rotateFunc(deg: number) {
  122 + imgState.imgRotate += deg;
  123 + }
117 124  
118   - // 鼠标事件
119   - function handleMouseUp() {
120   - const imgEl = unref(imgElRef);
121   - if (!imgEl) return;
122   - imgEl.onmousemove = null;
123   - }
  125 + // 鼠标事件
  126 + function handleMouseUp() {
  127 + const imgEl = unref(imgElRef);
  128 + if (!imgEl) return;
  129 + imgEl.onmousemove = null;
  130 + }
124 131  
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   - }
  132 + // 更换图片
  133 + function handleIChangeImage(url: string) {
  134 + imgState.status = StatueEnum.LOADING;
  135 + const img = new Image();
  136 + img.src = url;
  137 + img.onload = () => {
  138 + imgState.currentUrl = url;
  139 + imgState.status = StatueEnum.DONE;
  140 + };
  141 + img.onerror = () => {
  142 + imgState.status = StatueEnum.FAIL;
  143 + };
  144 + }
138 145  
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   - }
  146 + // 关闭
  147 + function handleClose(e: MouseEvent) {
  148 + e && e.stopPropagation();
  149 + imgState.show = false;
  150 + // 移除火狐浏览器下的鼠标滚动事件
  151 + document.body.removeEventListener('DOMMouseScroll', scrollFunc);
  152 + // 恢复火狐及Safari浏览器下的图片拖拽
  153 + document.ondragstart = null;
  154 + }
148 155  
149   - // 图片复原
150   - function resume() {
151   - initState();
152   - }
  156 + // 图片复原
  157 + function resume() {
  158 + initState();
  159 + }
153 160  
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;
  161 + // 上一页下一页
  162 + function handleChange(direction: 'left' | 'right') {
  163 + const { currentIndex } = imgState;
  164 + const { imageList } = props;
  165 + if (direction === 'left') {
  166 + imgState.currentIndex--;
  167 + if (currentIndex <= 0) {
  168 + imgState.currentIndex = imageList.length - 1;
  169 + }
  170 + }
  171 + if (direction === 'right') {
  172 + imgState.currentIndex++;
  173 + if (currentIndex >= imageList.length - 1) {
  174 + imgState.currentIndex = 0;
  175 + }
162 176 }
  177 + handleIChangeImage(imageList[imgState.currentIndex]);
163 178 }
164   - if (direction === 'right') {
165   - imgState.currentIndex++;
166   - if (currentIndex >= imageList.length - 1) {
167   - imgState.currentIndex = 0;
  179 +
  180 + function handleAddMoveListener(e: MouseEvent) {
  181 + e = e || window.event;
  182 + imgState.moveX = e.clientX;
  183 + imgState.moveY = e.clientY;
  184 + const imgEl = unref(imgElRef);
  185 + if (imgEl) {
  186 + imgEl.onmousemove = moveFunc;
168 187 }
169 188 }
170   - handleIChangeImage(imageList[imgState.currentIndex]);
  189 +
  190 + function moveFunc(e: MouseEvent) {
  191 + e = e || window.event;
  192 + e.preventDefault();
  193 + const movementX = e.clientX - imgState.moveX;
  194 + const movementY = e.clientY - imgState.moveY;
  195 + imgState.imgLeft += movementX;
  196 + imgState.imgTop += movementY;
  197 + imgState.moveX = e.clientX;
  198 + imgState.moveY = e.clientY;
  199 + }
  200 +
  201 + // 获取图片样式
  202 + const getImageStyle = computed(() => {
  203 + const { imgScale, imgRotate, imgTop, imgLeft } = imgState;
  204 + return {
  205 + transform: `scale(${imgScale}) rotate(${imgRotate}deg)`,
  206 + marginTop: `${imgTop}px`,
  207 + marginLeft: `${imgLeft}px`,
  208 + };
  209 + });
  210 +
  211 + const getIsMultipleImage = computed(() => {
  212 + const { imageList } = props;
  213 + return imageList.length > 1;
  214 + });
  215 +
  216 + watchEffect(() => {
  217 + if (props.show) {
  218 + init();
  219 + }
  220 + if (props.imageList) {
  221 + initState();
  222 + }
  223 + });
  224 +
  225 + const renderClose = () => {
  226 + return (
  227 + <div class={`${prefixCls}__close`} onClick={handleClose}>
  228 + <CloseOutlined class={`${prefixCls}__close-icon`} />
  229 + </div>
  230 + );
  231 + };
  232 +
  233 + const renderIndex = () => {
  234 + if (!unref(getIsMultipleImage)) {
  235 + return null;
  236 + }
  237 + const { currentIndex } = imgState;
  238 + const { imageList } = props;
  239 + return (
  240 + <div class={`${prefixCls}__index`}>
  241 + {currentIndex + 1} / {imageList.length}
  242 + </div>
  243 + );
  244 + };
  245 +
  246 + const renderController = () => {
  247 + return (
  248 + <div class={`${prefixCls}__controller`}>
  249 + <div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(-0.15)}>
  250 + <img src={unScaleSvg} />
  251 + </div>
  252 + <div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(0.15)}>
  253 + <img src={scaleSvg} />
  254 + </div>
  255 + <div class={`${prefixCls}__controller-item`} onClick={resume}>
  256 + <img src={resumeSvg} />
  257 + </div>
  258 + <div class={`${prefixCls}__controller-item`} onClick={() => rotateFunc(-90)}>
  259 + <img src={unRotateSvg} />
  260 + </div>
  261 + <div class={`${prefixCls}__controller-item`} onClick={() => rotateFunc(90)}>
  262 + <img src={rotateSvg} />
  263 + </div>
  264 + </div>
  265 + );
  266 + };
  267 +
  268 + const renderArrow = (direction: 'left' | 'right') => {
  269 + if (!unref(getIsMultipleImage)) {
  270 + return null;
  271 + }
  272 + return (
  273 + <div class={[`${prefixCls}__arrow`, direction]} onClick={() => handleChange(direction)}>
  274 + {direction === 'left' ? <LeftOutlined /> : <RightOutlined />}
  275 + </div>
  276 + );
  277 + };
  278 +
  279 + return () => {
  280 + return (
  281 + imgState.show && (
  282 + <div class={prefixCls} ref={wrapElRef} onMouseup={handleMouseUp}>
  283 + <div class={`${prefixCls}-content`}>
  284 + {/*<Spin*/}
  285 + {/* indicator={<LoadingOutlined style="font-size: 24px" spin />}*/}
  286 + {/* spinning={true}*/}
  287 + {/* class={[*/}
  288 + {/* `${prefixCls}-image`,*/}
  289 + {/* {*/}
  290 + {/* hidden: imgState.status !== StatueEnum.LOADING,*/}
  291 + {/* },*/}
  292 + {/* ]}*/}
  293 + {/*/>*/}
  294 + <img
  295 + style={unref(getImageStyle)}
  296 + class={[
  297 + `${prefixCls}-image`,
  298 + imgState.status === StatueEnum.DONE ? '' : 'hidden',
  299 + ]}
  300 + ref={imgElRef}
  301 + src={imgState.currentUrl}
  302 + onMousedown={handleAddMoveListener}
  303 + />
  304 + {renderClose()}
  305 + {renderIndex()}
  306 + {renderController()}
  307 + {renderArrow('left')}
  308 + {renderArrow('right')}
  309 + </div>
  310 + </div>
  311 + )
  312 + );
  313 + };
  314 + },
  315 + });
  316 +</script>
  317 +<style lang="less">
  318 + .img-preview {
  319 + position: fixed;
  320 + top: 0;
  321 + right: 0;
  322 + bottom: 0;
  323 + left: 0;
  324 + z-index: @preview-comp-z-index;
  325 + background: rgba(0, 0, 0, 0.5);
  326 + user-select: none;
  327 +
  328 + &-content {
  329 + display: flex;
  330 + width: 100%;
  331 + height: 100%;
  332 + color: @white;
  333 + justify-content: center;
  334 + align-items: center;
  335 + }
  336 +
  337 + &-image {
  338 + cursor: pointer;
  339 + transition: transform 0.3s;
171 340 }
172 341  
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;
  342 + &__close {
  343 + position: absolute;
  344 + top: -40px;
  345 + right: -40px;
  346 + width: 80px;
  347 + height: 80px;
  348 + overflow: hidden;
  349 + color: @white;
  350 + cursor: pointer;
  351 + background-color: rgba(0, 0, 0, 0.5);
  352 + border-radius: 50%;
  353 + transition: all 0.2s;
  354 +
  355 + &-icon {
  356 + position: absolute;
  357 + top: 46px;
  358 + left: 16px;
  359 + font-size: 16px;
  360 + }
  361 +
  362 + &:hover {
  363 + background-color: rgba(0, 0, 0, 0.8);
180 364 }
181 365 }
182 366  
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;
  367 + &__index {
  368 + position: absolute;
  369 + bottom: 5%;
  370 + left: 50%;
  371 + padding: 0 22px;
  372 + font-size: 16px;
  373 + background: rgba(109, 109, 109, 0.6);
  374 + border-radius: 15px;
  375 + transform: translateX(-50%);
192 376 }
193 377  
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   - });
  378 + &__controller {
  379 + position: absolute;
  380 + bottom: 10%;
  381 + left: 50%;
  382 + display: flex;
  383 + width: 260px;
  384 + height: 44px;
  385 + padding: 0 22px;
  386 + margin-left: -139px;
  387 + background: rgba(109, 109, 109, 0.6);
  388 + border-radius: 22px;
  389 + justify-content: center;
  390 +
  391 + &-item {
  392 + display: flex;
  393 + height: 100%;
  394 + padding: 0 9px;
  395 + font-size: 24px;
  396 + cursor: pointer;
  397 + transition: all 0.2s;
203 398  
204   - const getIsMultipleImage = computed(() => {
205   - const { imageList } = props;
206   - return imageList.length > 1;
207   - });
  399 + &:hover {
  400 + transform: scale(1.2);
  401 + }
208 402  
209   - watchEffect(() => {
210   - if (props.show) {
211   - init();
  403 + img {
  404 + width: 1em;
  405 + }
212 406 }
213   - if (props.imageList) {
214   - initState();
  407 + }
  408 +
  409 + &__arrow {
  410 + position: absolute;
  411 + top: 50%;
  412 + display: flex;
  413 + align-items: center;
  414 + justify-content: center;
  415 + width: 50px;
  416 + height: 50px;
  417 + font-size: 28px;
  418 + cursor: pointer;
  419 + background-color: rgba(0, 0, 0, 0.5);
  420 + border-radius: 50%;
  421 + transition: all 0.2s;
  422 +
  423 + &:hover {
  424 + background-color: rgba(0, 0, 0, 0.8);
215 425 }
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;
  426 +
  427 + &.left {
  428 + left: 50px;
229 429 }
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 430  
261   - const renderArrow = (direction: 'left' | 'right') => {
262   - if (!unref(getIsMultipleImage)) {
263   - return null;
  431 + &.right {
  432 + right: 50px;
264 433 }
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   -});
  434 + }
  435 + }
  436 +</style>
... ...
src/components/Preview/src/functional.ts
1   -import ImgPreview from './index';
  1 +import type { Options, Props } from './typing';
  2 +import ImgPreview from './Functional.vue';
2 3 import { isClient } from '/@/utils/is';
3   -
4   -import type { Options, Props } from './types';
5   -
6 4 import { createVNode, render } from 'vue';
7 5  
8   -let instance: any = null;
  6 +let instance: ReturnType<typeof createVNode> | null = null;
9 7 export function createImgPreview(options: Options) {
10 8 if (!isClient) return;
11 9 const { imageList, show = true, index = 0 } = options;
... ...
src/components/Preview/src/index.less deleted 100644 → 0
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/props.ts deleted 100644 → 0
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 renamed to src/components/Preview/src/typing.ts
src/views/demo/feat/img-preview/index.vue
1 1 <template>
2 2 <PageWrapper title="图片预览示例">
3   - <p @click="openImg">打开图片</p>
4 3 <ImagePreview :imageList="imgList" />
  4 + <a-button @click="openImg" type="primary">无预览图</a-button>
5 5 </PageWrapper>
6 6 </template>
7 7 <script lang="ts">
... ...