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