perf: 优化 cropper 组件的类型
							parent
							
								
									bd02645e26
								
							
						
					
					
						commit
						5af8a3c40c
					
				|  | @ -1,7 +1,7 @@ | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import type { ButtonProps } from 'ant-design-vue'; | import type { CSSProperties } from 'vue'; | ||||||
| 
 | 
 | ||||||
| import type { CSSProperties, PropType } from 'vue'; | import type { CropperAvatarProps } from './typing'; | ||||||
| 
 | 
 | ||||||
| import { computed, ref, unref, watch, watchEffect } from 'vue'; | import { computed, ref, unref, watch, watchEffect } from 'vue'; | ||||||
| 
 | 
 | ||||||
|  | @ -14,27 +14,14 @@ import cropperModal from './cropper-modal.vue'; | ||||||
| 
 | 
 | ||||||
| defineOptions({ name: 'CropperAvatar' }); | defineOptions({ name: 'CropperAvatar' }); | ||||||
| 
 | 
 | ||||||
| const props = defineProps({ | const props = withDefaults(defineProps<CropperAvatarProps>(), { | ||||||
|   width: { default: '200px', type: [String, Number] }, |   width: 200, | ||||||
|   value: { default: '', type: String }, |   value: '', | ||||||
|   showBtn: { default: true, type: Boolean }, |   showBtn: true, | ||||||
|   btnProps: { default: () => ({}), type: Object as PropType<ButtonProps> }, |   btnProps: () => ({}), | ||||||
|   btnText: { default: '', type: String }, |   btnText: '', | ||||||
|   uploadApi: { |   uploadApi: () => Promise.resolve(), | ||||||
|     required: true, |   size: 5, | ||||||
|     type: Function as PropType< |  | ||||||
|       ({ |  | ||||||
|         file, |  | ||||||
|         filename, |  | ||||||
|         name, |  | ||||||
|       }: { |  | ||||||
|         file: Blob; |  | ||||||
|         filename: string; |  | ||||||
|         name: string; |  | ||||||
|       }) => Promise<any> |  | ||||||
|     >, |  | ||||||
|   }, |  | ||||||
|   size: { default: 5, type: Number }, |  | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| const emit = defineEmits(['update:value', 'change']); | const emit = defineEmits(['update:value', 'change']); | ||||||
|  |  | ||||||
|  | @ -1,7 +1,5 @@ | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import type { PropType } from 'vue'; | import type { CropendResult, CropperModalProps, CropperType } from './typing'; | ||||||
| 
 |  | ||||||
| import type { CropendResult, Cropper } from './typing'; |  | ||||||
| 
 | 
 | ||||||
| import { ref } from 'vue'; | import { ref } from 'vue'; | ||||||
| 
 | 
 | ||||||
|  | @ -20,18 +18,13 @@ import { | ||||||
| 
 | 
 | ||||||
| import CropperImage from './cropper.vue'; | import CropperImage from './cropper.vue'; | ||||||
| 
 | 
 | ||||||
| type apiFunParams = { file: Blob; filename: string; name: string }; |  | ||||||
| 
 |  | ||||||
| defineOptions({ name: 'CropperModal' }); | defineOptions({ name: 'CropperModal' }); | ||||||
| 
 | 
 | ||||||
| const props = defineProps({ | const props = withDefaults(defineProps<CropperModalProps>(), { | ||||||
|   circled: { default: true, type: Boolean }, |   circled: true, | ||||||
|   uploadApi: { |   size: 0, | ||||||
|     required: true, |   src: '', | ||||||
|     type: Function as PropType<(params: apiFunParams) => Promise<any>>, |   uploadApi: () => Promise.resolve(), | ||||||
|   }, |  | ||||||
|   src: { default: '', type: String }, |  | ||||||
|   size: { default: 0, type: Number }, |  | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| const emit = defineEmits(['uploadSuccess', 'uploadError', 'register']); | const emit = defineEmits(['uploadSuccess', 'uploadError', 'register']); | ||||||
|  | @ -39,7 +32,7 @@ const emit = defineEmits(['uploadSuccess', 'uploadError', 'register']); | ||||||
| let filename = ''; | let filename = ''; | ||||||
| const src = ref(props.src || ''); | const src = ref(props.src || ''); | ||||||
| const previewSource = ref(''); | const previewSource = ref(''); | ||||||
| const cropper = ref<Cropper>(); | const cropper = ref<CropperType>(); | ||||||
| let scaleX = 1; | let scaleX = 1; | ||||||
| let scaleY = 1; | let scaleY = 1; | ||||||
| 
 | 
 | ||||||
|  | @ -83,7 +76,7 @@ function handleCropend({ imgBase64 }: CropendResult) { | ||||||
|   previewSource.value = imgBase64; |   previewSource.value = imgBase64; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function handleReady(cropperInstance: Cropper) { | function handleReady(cropperInstance: CropperType) { | ||||||
|   cropper.value = cropperInstance; |   cropper.value = cropperInstance; | ||||||
|   // 画布加载完毕 关闭 loading |   // 画布加载完毕 关闭 loading | ||||||
|   modalLoading(false); |   modalLoading(false); | ||||||
|  |  | ||||||
|  | @ -1,64 +1,40 @@ | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import type { CSSProperties, PropType } from 'vue'; | import type { CSSProperties } from 'vue'; | ||||||
|  | 
 | ||||||
|  | import type { CropperProps } from './typing'; | ||||||
| 
 | 
 | ||||||
| import { computed, onMounted, onUnmounted, ref, unref, useAttrs } from 'vue'; | import { computed, onMounted, onUnmounted, ref, unref, useAttrs } from 'vue'; | ||||||
| 
 | 
 | ||||||
| import { useDebounceFn } from '@vueuse/core'; | import { useDebounceFn } from '@vueuse/core'; | ||||||
| import Cropper from 'cropperjs'; | import Cropper from 'cropperjs'; | ||||||
| 
 | 
 | ||||||
| import 'cropperjs/dist/cropper.css'; | import { defaultOptions } from './typing'; | ||||||
| 
 | 
 | ||||||
| type Options = Cropper.Options; | import 'cropperjs/dist/cropper.css'; | ||||||
| 
 | 
 | ||||||
| defineOptions({ name: 'CropperImage' }); | defineOptions({ name: 'CropperImage' }); | ||||||
| 
 | 
 | ||||||
| const props = defineProps({ | const props = withDefaults(defineProps<CropperProps>(), { | ||||||
|   src: { required: true, type: String }, |   src: '', | ||||||
|   alt: { default: '', type: String }, |   alt: '', | ||||||
|   circled: { default: false, type: Boolean }, |   circled: false, | ||||||
|   realTimePreview: { default: true, type: Boolean }, |   realTimePreview: true, | ||||||
|   height: { default: '360px', type: [String, Number] }, |   height: '360px', | ||||||
|   crossorigin: { |   crossorigin: undefined, | ||||||
|     type: String as PropType<'' | 'anonymous' | 'use-credentials' | undefined>, |   imageStyle: () => ({}), | ||||||
|     default: undefined, |   options: () => ({}), | ||||||
|   }, |  | ||||||
|   imageStyle: { default: () => ({}), type: Object as PropType<CSSProperties> }, |  | ||||||
|   options: { default: () => ({}), type: Object as PropType<Options> }, |  | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| const emit = defineEmits(['cropend', 'ready', 'cropendError']); | const emit = defineEmits(['cropend', 'ready', 'cropendError']); | ||||||
| const attrs = useAttrs(); | const attrs = useAttrs(); | ||||||
| 
 | 
 | ||||||
| const defaultOptions: Options = { |  | ||||||
|   aspectRatio: 1, |  | ||||||
|   zoomable: true, |  | ||||||
|   zoomOnTouch: true, |  | ||||||
|   zoomOnWheel: true, |  | ||||||
|   cropBoxMovable: true, |  | ||||||
|   cropBoxResizable: true, |  | ||||||
|   toggleDragModeOnDblclick: true, |  | ||||||
|   autoCrop: true, |  | ||||||
|   background: true, |  | ||||||
|   highlight: true, |  | ||||||
|   center: true, |  | ||||||
|   responsive: true, |  | ||||||
|   restore: true, |  | ||||||
|   checkCrossOrigin: true, |  | ||||||
|   checkOrientation: true, |  | ||||||
|   scalable: true, |  | ||||||
|   modal: true, |  | ||||||
|   guides: true, |  | ||||||
|   movable: true, |  | ||||||
|   rotatable: true, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| type ElRef<T extends HTMLElement = HTMLDivElement> = null | T; | type ElRef<T extends HTMLElement = HTMLDivElement> = null | T; | ||||||
| const imgElRef = ref<ElRef<HTMLImageElement>>(); | const imgElRef = ref<ElRef<HTMLImageElement>>(); | ||||||
| const cropper = ref<Cropper | null>(); | const cropper = ref<Cropper | null>(); | ||||||
| const isReady = ref(false); | const isReady = ref(false); | ||||||
| 
 | 
 | ||||||
| const prefixCls = 'cropper-image'; | const prefixCls = 'cropper-image'; | ||||||
| const debounceRealTimeCroppered = useDebounceFn(realTimeCroppered, 80); | const debounceRealTimeCropped = useDebounceFn(realTimeCropped, 80); | ||||||
| 
 | 
 | ||||||
| const getImageStyle = computed((): CSSProperties => { | const getImageStyle = computed((): CSSProperties => { | ||||||
|   return { |   return { | ||||||
|  | @ -97,29 +73,29 @@ async function init() { | ||||||
|     ...defaultOptions, |     ...defaultOptions, | ||||||
|     ready: () => { |     ready: () => { | ||||||
|       isReady.value = true; |       isReady.value = true; | ||||||
|       realTimeCroppered(); |       realTimeCropped(); | ||||||
|       emit('ready', cropper.value); |       emit('ready', cropper.value); | ||||||
|     }, |     }, | ||||||
|     crop() { |     crop() { | ||||||
|       debounceRealTimeCroppered(); |       debounceRealTimeCropped(); | ||||||
|     }, |     }, | ||||||
|     zoom() { |     zoom() { | ||||||
|       debounceRealTimeCroppered(); |       debounceRealTimeCropped(); | ||||||
|     }, |     }, | ||||||
|     cropmove() { |     cropmove() { | ||||||
|       debounceRealTimeCroppered(); |       debounceRealTimeCropped(); | ||||||
|     }, |     }, | ||||||
|     ...props.options, |     ...props.options, | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Real-time display preview | // Real-time display preview | ||||||
| function realTimeCroppered() { | function realTimeCropped() { | ||||||
|   props.realTimePreview && croppered(); |   props.realTimePreview && cropped(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // event: return base64 and width and height information after cropping | // event: return base64 and width and height information after cropping | ||||||
| function croppered() { | function cropped() { | ||||||
|   if (!cropper.value) { |   if (!cropper.value) { | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -1,8 +1,68 @@ | ||||||
|  | import type { ButtonProps } from 'ant-design-vue'; | ||||||
| import type Cropper from 'cropperjs'; | import type Cropper from 'cropperjs'; | ||||||
| 
 | 
 | ||||||
|  | import type { CSSProperties } from 'vue'; | ||||||
|  | 
 | ||||||
|  | export interface apiFunParams { | ||||||
|  |   file: Blob; | ||||||
|  |   filename: string; | ||||||
|  |   name: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export interface CropendResult { | export interface CropendResult { | ||||||
|   imgBase64: string; |   imgBase64: string; | ||||||
|   imgInfo: Cropper.Data; |   imgInfo: Cropper.Data; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export type { Cropper }; | export interface CropperProps { | ||||||
|  |   src?: string; | ||||||
|  |   alt?: string; | ||||||
|  |   circled?: boolean; | ||||||
|  |   realTimePreview?: boolean; | ||||||
|  |   height?: number | string; | ||||||
|  |   crossorigin?: '' | 'anonymous' | 'use-credentials' | undefined; | ||||||
|  |   imageStyle?: CSSProperties; | ||||||
|  |   options?: Cropper.Options; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface CropperAvatarProps { | ||||||
|  |   width?: number | string; | ||||||
|  |   value?: string; | ||||||
|  |   showBtn?: boolean; | ||||||
|  |   btnProps?: ButtonProps; | ||||||
|  |   btnText?: string; | ||||||
|  |   uploadApi?: (params: apiFunParams) => Promise<any>; | ||||||
|  |   size?: number; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface CropperModalProps { | ||||||
|  |   circled?: boolean; | ||||||
|  |   uploadApi?: (params: apiFunParams) => Promise<any>; | ||||||
|  |   src?: string; | ||||||
|  |   size?: number; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const defaultOptions: Cropper.Options = { | ||||||
|  |   aspectRatio: 1, | ||||||
|  |   zoomable: true, | ||||||
|  |   zoomOnTouch: true, | ||||||
|  |   zoomOnWheel: true, | ||||||
|  |   cropBoxMovable: true, | ||||||
|  |   cropBoxResizable: true, | ||||||
|  |   toggleDragModeOnDblclick: true, | ||||||
|  |   autoCrop: true, | ||||||
|  |   background: true, | ||||||
|  |   highlight: true, | ||||||
|  |   center: true, | ||||||
|  |   responsive: true, | ||||||
|  |   restore: true, | ||||||
|  |   checkCrossOrigin: true, | ||||||
|  |   checkOrientation: true, | ||||||
|  |   scalable: true, | ||||||
|  |   modal: true, | ||||||
|  |   guides: true, | ||||||
|  |   movable: true, | ||||||
|  |   rotatable: true, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export type { Cropper as CropperType }; | ||||||
|  |  | ||||||
|  | @ -50,7 +50,7 @@ async function handelUpload({ | ||||||
|           :show-btn="false" |           :show-btn="false" | ||||||
|           :upload-api="handelUpload" |           :upload-api="handelUpload" | ||||||
|           :value="avatar" |           :value="avatar" | ||||||
|           width="120" |           :width="120" | ||||||
|           @change="emit('success')" |           @change="emit('success')" | ||||||
|         /> |         /> | ||||||
|       </Tooltip> |       </Tooltip> | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 xingyu4j
						xingyu4j