committed by
GitHub
2 changed files with 287 additions and 287 deletions
@ -1,284 +1,284 @@ |
|||||
<template> |
<template> |
||||
<BasicModal |
<BasicModal |
||||
v-bind="$attrs" |
v-bind="$attrs" |
||||
@register="register" |
@register="register" |
||||
:title="t('component.cropper.modalTitle')" |
:title="t('component.cropper.modalTitle')" |
||||
width="800px" |
width="800px" |
||||
:canFullscreen="false" |
:canFullscreen="false" |
||||
@ok="handleOk" |
@ok="handleOk" |
||||
:okText="t('component.cropper.okText')" |
:okText="t('component.cropper.okText')" |
||||
> |
> |
||||
<div :class="prefixCls"> |
<div :class="prefixCls"> |
||||
<div :class="`${prefixCls}-left`"> |
<div :class="`${prefixCls}-left`"> |
||||
<div :class="`${prefixCls}-cropper`"> |
<div :class="`${prefixCls}-cropper`"> |
||||
<CropperImage |
<CropperImage |
||||
v-if="src" |
v-if="src" |
||||
:src="src" |
:src="src" |
||||
height="300px" |
height="300px" |
||||
:circled="circled" |
:circled="circled" |
||||
@cropend="handleCropend" |
@cropend="handleCropend" |
||||
@ready="handleReady" |
@ready="handleReady" |
||||
/> |
/> |
||||
</div> |
</div> |
||||
|
|
||||
<div :class="`${prefixCls}-toolbar`"> |
<div :class="`${prefixCls}-toolbar`"> |
||||
<Upload :fileList="[]" accept="image/*" :beforeUpload="handleBeforeUpload"> |
<Upload :fileList="[]" accept="image/*" :beforeUpload="handleBeforeUpload"> |
||||
<Tooltip :title="t('component.cropper.selectImage')" placement="bottom"> |
<Tooltip :title="t('component.cropper.selectImage')" placement="bottom"> |
||||
<a-button size="small" preIcon="ant-design:upload-outlined" type="primary" /> |
<a-button size="small" preIcon="ant-design:upload-outlined" type="primary" /> |
||||
</Tooltip> |
</Tooltip> |
||||
</Upload> |
</Upload> |
||||
<Space> |
<Space> |
||||
<Tooltip :title="t('component.cropper.btn_reset')" placement="bottom"> |
<Tooltip :title="t('component.cropper.btn_reset')" placement="bottom"> |
||||
<a-button |
<a-button |
||||
type="primary" |
type="primary" |
||||
preIcon="ant-design:reload-outlined" |
preIcon="ant-design:reload-outlined" |
||||
size="small" |
size="small" |
||||
:disabled="!src" |
:disabled="!src" |
||||
@click="handlerToolbar('reset')" |
@click="handlerToolbar('reset')" |
||||
/> |
/> |
||||
</Tooltip> |
</Tooltip> |
||||
<Tooltip :title="t('component.cropper.btn_rotate_left')" placement="bottom"> |
<Tooltip :title="t('component.cropper.btn_rotate_left')" placement="bottom"> |
||||
<a-button |
<a-button |
||||
type="primary" |
type="primary" |
||||
preIcon="ant-design:rotate-left-outlined" |
preIcon="ant-design:rotate-left-outlined" |
||||
size="small" |
size="small" |
||||
:disabled="!src" |
:disabled="!src" |
||||
@click="handlerToolbar('rotate', -45)" |
@click="handlerToolbar('rotate', -45)" |
||||
/> |
/> |
||||
</Tooltip> |
</Tooltip> |
||||
<Tooltip :title="t('component.cropper.btn_rotate_right')" placement="bottom"> |
<Tooltip :title="t('component.cropper.btn_rotate_right')" placement="bottom"> |
||||
<a-button |
<a-button |
||||
type="primary" |
type="primary" |
||||
preIcon="ant-design:rotate-right-outlined" |
preIcon="ant-design:rotate-right-outlined" |
||||
size="small" |
size="small" |
||||
:disabled="!src" |
:disabled="!src" |
||||
@click="handlerToolbar('rotate', 45)" |
@click="handlerToolbar('rotate', 45)" |
||||
/> |
/> |
||||
</Tooltip> |
</Tooltip> |
||||
<Tooltip :title="t('component.cropper.btn_scale_x')" placement="bottom"> |
<Tooltip :title="t('component.cropper.btn_scale_x')" placement="bottom"> |
||||
<a-button |
<a-button |
||||
type="primary" |
type="primary" |
||||
preIcon="vaadin:arrows-long-h" |
preIcon="vaadin:arrows-long-h" |
||||
size="small" |
size="small" |
||||
:disabled="!src" |
:disabled="!src" |
||||
@click="handlerToolbar('scaleX')" |
@click="handlerToolbar('scaleX')" |
||||
/> |
/> |
||||
</Tooltip> |
</Tooltip> |
||||
<Tooltip :title="t('component.cropper.btn_scale_y')" placement="bottom"> |
<Tooltip :title="t('component.cropper.btn_scale_y')" placement="bottom"> |
||||
<a-button |
<a-button |
||||
type="primary" |
type="primary" |
||||
preIcon="vaadin:arrows-long-v" |
preIcon="vaadin:arrows-long-v" |
||||
size="small" |
size="small" |
||||
:disabled="!src" |
:disabled="!src" |
||||
@click="handlerToolbar('scaleY')" |
@click="handlerToolbar('scaleY')" |
||||
/> |
/> |
||||
</Tooltip> |
</Tooltip> |
||||
<Tooltip :title="t('component.cropper.btn_zoom_in')" placement="bottom"> |
<Tooltip :title="t('component.cropper.btn_zoom_in')" placement="bottom"> |
||||
<a-button |
<a-button |
||||
type="primary" |
type="primary" |
||||
preIcon="ant-design:zoom-in-outlined" |
preIcon="ant-design:zoom-in-outlined" |
||||
size="small" |
size="small" |
||||
:disabled="!src" |
:disabled="!src" |
||||
@click="handlerToolbar('zoom', 0.1)" |
@click="handlerToolbar('zoom', 0.1)" |
||||
/> |
/> |
||||
</Tooltip> |
</Tooltip> |
||||
<Tooltip :title="t('component.cropper.btn_zoom_out')" placement="bottom"> |
<Tooltip :title="t('component.cropper.btn_zoom_out')" placement="bottom"> |
||||
<a-button |
<a-button |
||||
type="primary" |
type="primary" |
||||
preIcon="ant-design:zoom-out-outlined" |
preIcon="ant-design:zoom-out-outlined" |
||||
size="small" |
size="small" |
||||
:disabled="!src" |
:disabled="!src" |
||||
@click="handlerToolbar('zoom', -0.1)" |
@click="handlerToolbar('zoom', -0.1)" |
||||
/> |
/> |
||||
</Tooltip> |
</Tooltip> |
||||
</Space> |
</Space> |
||||
</div> |
</div> |
||||
</div> |
</div> |
||||
<div :class="`${prefixCls}-right`"> |
<div :class="`${prefixCls}-right`"> |
||||
<div :class="`${prefixCls}-preview`"> |
<div :class="`${prefixCls}-preview`"> |
||||
<img :src="previewSource" v-if="previewSource" :alt="t('component.cropper.preview')" /> |
<img :src="previewSource" v-if="previewSource" :alt="t('component.cropper.preview')" /> |
||||
</div> |
</div> |
||||
<template v-if="previewSource"> |
<template v-if="previewSource"> |
||||
<div :class="`${prefixCls}-group`"> |
<div :class="`${prefixCls}-group`"> |
||||
<Avatar :src="previewSource" size="large" /> |
<Avatar :src="previewSource" size="large" /> |
||||
<Avatar :src="previewSource" :size="48" /> |
<Avatar :src="previewSource" :size="48" /> |
||||
<Avatar :src="previewSource" :size="64" /> |
<Avatar :src="previewSource" :size="64" /> |
||||
<Avatar :src="previewSource" :size="80" /> |
<Avatar :src="previewSource" :size="80" /> |
||||
</div> |
</div> |
||||
</template> |
</template> |
||||
</div> |
</div> |
||||
</div> |
</div> |
||||
</BasicModal> |
</BasicModal> |
||||
</template> |
</template> |
||||
<script lang="ts"> |
<script lang="ts"> |
||||
import type { CropendResult, Cropper } from './typing'; |
import type { CropendResult, Cropper } from './typing'; |
||||
|
|
||||
import { defineComponent, ref, PropType } from 'vue'; |
import { defineComponent, ref, PropType } from 'vue'; |
||||
import CropperImage from './Cropper.vue'; |
import CropperImage from './Cropper.vue'; |
||||
import { Space, Upload, Avatar, Tooltip } from 'ant-design-vue'; |
import { Space, Upload, Avatar, Tooltip } from 'ant-design-vue'; |
||||
import { useDesign } from '/@/hooks/web/useDesign'; |
import { useDesign } from '/@/hooks/web/useDesign'; |
||||
import { BasicModal, useModalInner } from '/@/components/Modal'; |
import { BasicModal, useModalInner } from '/@/components/Modal'; |
||||
import { dataURLtoBlob } from '/@/utils/file/base64Conver'; |
import { dataURLtoBlob } from '/@/utils/file/base64Conver'; |
||||
import { isFunction } from '/@/utils/is'; |
import { isFunction } from '/@/utils/is'; |
||||
import { useI18n } from '/@/hooks/web/useI18n'; |
import { useI18n } from '/@/hooks/web/useI18n'; |
||||
|
|
||||
type apiFunParams = { file: Blob; name: string; filename: string }; |
type apiFunParams = { file: Blob; name: string; filename: string }; |
||||
|
|
||||
const props = { |
const props = { |
||||
circled: { type: Boolean, default: true }, |
circled: { type: Boolean, default: true }, |
||||
uploadApi: { |
uploadApi: { |
||||
type: Function as PropType<(params: apiFunParams) => Promise<any>>, |
type: Function as PropType<(params: apiFunParams) => Promise<any>>, |
||||
}, |
}, |
||||
src: { type: String }, |
src: { type: String }, |
||||
}; |
}; |
||||
|
|
||||
export default defineComponent({ |
export default defineComponent({ |
||||
name: 'CropperModal', |
name: 'CropperModal', |
||||
components: { BasicModal, Space, CropperImage, Upload, Avatar, Tooltip }, |
components: { BasicModal, Space, CropperImage, Upload, Avatar, Tooltip }, |
||||
props, |
props, |
||||
emits: ['uploadSuccess', 'register'], |
emits: ['uploadSuccess', 'register'], |
||||
setup(props, { emit }) { |
setup(props, { emit }) { |
||||
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<Cropper>(); |
||||
let scaleX = 1; |
let scaleX = 1; |
||||
let scaleY = 1; |
let scaleY = 1; |
||||
|
|
||||
const { prefixCls } = useDesign('cropper-am'); |
const { prefixCls } = useDesign('cropper-am'); |
||||
const [register, { closeModal, setModalProps }] = useModalInner(); |
const [register, { closeModal, setModalProps }] = useModalInner(); |
||||
const { t } = useI18n(); |
const { t } = useI18n(); |
||||
|
|
||||
// Block upload |
// Block upload |
||||
function handleBeforeUpload(file: File) { |
function handleBeforeUpload(file: File) { |
||||
const reader = new FileReader(); |
const reader = new FileReader(); |
||||
reader.readAsDataURL(file); |
reader.readAsDataURL(file); |
||||
src.value = ''; |
src.value = ''; |
||||
previewSource.value = ''; |
previewSource.value = ''; |
||||
reader.onload = function (e) { |
reader.onload = function (e) { |
||||
src.value = (e.target?.result as string) ?? ''; |
src.value = (e.target?.result as string) ?? ''; |
||||
filename = file.name; |
filename = file.name; |
||||
}; |
}; |
||||
return false; |
return false; |
||||
} |
} |
||||
|
|
||||
function handleCropend({ imgBase64 }: CropendResult) { |
function handleCropend({ imgBase64 }: CropendResult) { |
||||
previewSource.value = imgBase64; |
previewSource.value = imgBase64; |
||||
} |
} |
||||
|
|
||||
function handleReady(cropperInstance: Cropper) { |
function handleReady(cropperInstance: Cropper) { |
||||
cropper.value = cropperInstance; |
cropper.value = cropperInstance; |
||||
} |
} |
||||
|
|
||||
function handlerToolbar(event: string, arg?: number) { |
function handlerToolbar(event: string, arg?: number) { |
||||
if (event === 'scaleX') { |
if (event === 'scaleX') { |
||||
scaleX = arg = scaleX === -1 ? 1 : -1; |
scaleX = arg = scaleX === -1 ? 1 : -1; |
||||
} |
} |
||||
if (event === 'scaleY') { |
if (event === 'scaleY') { |
||||
scaleY = arg = scaleY === -1 ? 1 : -1; |
scaleY = arg = scaleY === -1 ? 1 : -1; |
||||
} |
} |
||||
cropper?.value?.[event]?.(arg); |
cropper?.value?.[event]?.(arg); |
||||
} |
} |
||||
|
|
||||
async function handleOk() { |
async function handleOk() { |
||||
const uploadApi = props.uploadApi; |
const uploadApi = props.uploadApi; |
||||
if (uploadApi && isFunction(uploadApi)) { |
if (uploadApi && isFunction(uploadApi)) { |
||||
const blob = dataURLtoBlob(previewSource.value); |
const blob = dataURLtoBlob(previewSource.value); |
||||
try { |
try { |
||||
setModalProps({ confirmLoading: true }); |
setModalProps({ confirmLoading: true }); |
||||
const result = await uploadApi({ name: 'file', file: blob, filename }); |
const result = await uploadApi({ name: 'file', file: blob, filename }); |
||||
emit('uploadSuccess', { source: previewSource.value, data: result.url }); |
emit('uploadSuccess', { source: previewSource.value, data: result.url }); |
||||
closeModal(); |
closeModal(); |
||||
} finally { |
} finally { |
||||
setModalProps({ confirmLoading: false }); |
setModalProps({ confirmLoading: false }); |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
return { |
return { |
||||
t, |
t, |
||||
prefixCls, |
prefixCls, |
||||
src, |
src, |
||||
register, |
register, |
||||
previewSource, |
previewSource, |
||||
handleBeforeUpload, |
handleBeforeUpload, |
||||
handleCropend, |
handleCropend, |
||||
handleReady, |
handleReady, |
||||
handlerToolbar, |
handlerToolbar, |
||||
handleOk, |
handleOk, |
||||
}; |
}; |
||||
}, |
}, |
||||
}); |
}); |
||||
</script> |
</script> |
||||
|
|
||||
<style lang="less"> |
<style lang="less"> |
||||
@prefix-cls: ~'@{namespace}-cropper-am'; |
@prefix-cls: ~'@{namespace}-cropper-am'; |
||||
|
|
||||
.@{prefix-cls} { |
.@{prefix-cls} { |
||||
display: flex; |
display: flex; |
||||
|
|
||||
&-left, |
&-left, |
||||
&-right { |
&-right { |
||||
height: 340px; |
height: 340px; |
||||
} |
} |
||||
|
|
||||
&-left { |
&-left { |
||||
width: 55%; |
width: 55%; |
||||
} |
} |
||||
|
|
||||
&-right { |
&-right { |
||||
width: 45%; |
width: 45%; |
||||
} |
} |
||||
|
|
||||
&-cropper { |
&-cropper { |
||||
height: 300px; |
height: 300px; |
||||
background: #eee; |
background: #eee; |
||||
background-image: linear-gradient( |
background-image: linear-gradient( |
||||
45deg, |
45deg, |
||||
rgb(0 0 0 / 25%) 25%, |
rgb(0 0 0 / 25%) 25%, |
||||
transparent 0, |
transparent 0, |
||||
transparent 75%, |
transparent 75%, |
||||
rgb(0 0 0 / 25%) 0 |
rgb(0 0 0 / 25%) 0 |
||||
), |
), |
||||
linear-gradient( |
linear-gradient( |
||||
45deg, |
45deg, |
||||
rgb(0 0 0 / 25%) 25%, |
rgb(0 0 0 / 25%) 25%, |
||||
transparent 0, |
transparent 0, |
||||
transparent 75%, |
transparent 75%, |
||||
rgb(0 0 0 / 25%) 0 |
rgb(0 0 0 / 25%) 0 |
||||
); |
); |
||||
background-position: 0 0, 12px 12px; |
background-position: 0 0, 12px 12px; |
||||
background-size: 24px 24px; |
background-size: 24px 24px; |
||||
} |
} |
||||
|
|
||||
&-toolbar { |
&-toolbar { |
||||
display: flex; |
display: flex; |
||||
align-items: center; |
align-items: center; |
||||
justify-content: space-between; |
justify-content: space-between; |
||||
margin-top: 10px; |
margin-top: 10px; |
||||
} |
} |
||||
|
|
||||
&-preview { |
&-preview { |
||||
width: 220px; |
width: 220px; |
||||
height: 220px; |
height: 220px; |
||||
margin: 0 auto; |
margin: 0 auto; |
||||
overflow: hidden; |
overflow: hidden; |
||||
border: 1px solid @border-color-base; |
border: 1px solid @border-color-base; |
||||
border-radius: 50%; |
border-radius: 50%; |
||||
|
|
||||
img { |
img { |
||||
width: 100%; |
width: 100%; |
||||
height: 100%; |
height: 100%; |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
&-group { |
&-group { |
||||
display: flex; |
display: flex; |
||||
align-items: center; |
align-items: center; |
||||
justify-content: space-around; |
justify-content: space-around; |
||||
margin-top: 8px; |
margin-top: 8px; |
||||
padding-top: 8px; |
padding-top: 8px; |
||||
border-top: 1px solid @border-color-base; |
border-top: 1px solid @border-color-base; |
||||
} |
} |
||||
} |
} |
||||
</style> |
</style> |
||||
Loading…
Reference in new issue