Commit 305630e3fd886b3f690f890a934a8a6ba224fba1
1 parent
3f6920f7
feat(preview): added createImgPreview picture preview function
Showing
18 changed files
with
428 additions
and
442 deletions
CHANGELOG.zh_CN.md
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 '../types'; |
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
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