Skip to content

Commit

Permalink
feat: add CropperAvatar component
Browse files Browse the repository at this point in the history
  • Loading branch information
anncwb committed Jun 10, 2021
1 parent de12bab commit 8e410fc
Show file tree
Hide file tree
Showing 19 changed files with 550 additions and 121 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.zh_CN.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## Wip

### ✨ Features

- `Cropper` 头像裁剪新增圆形裁剪功能
- 新增头像上传组件

## 2.4.2(2021-06-10)

### ✨ Refactor
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"@logicflow/extension": "^0.4.13",
"@vueuse/core": "^5.0.2",
"@zxcvbn-ts/core": "^0.3.0",
"ant-design-vue": "2.1.2",
"ant-design-vue": "2.1.6",
"axios": "^0.21.1",
"codemirror": "^5.61.1",
"cropperjs": "^1.5.11",
Expand Down
7 changes: 3 additions & 4 deletions src/components/Application/src/search/AppSearchModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<div :class="getClass" @click.stop v-if="visible">
<div :class="`${prefixCls}-content`" v-click-outside="handleClose">
<div :class="`${prefixCls}-input__wrapper`">
<Input
<a-input
:class="`${prefixCls}-input`"
:placeholder="t('common.searchText')"
ref="inputRef"
Expand All @@ -14,7 +14,7 @@
<template #prefix>
<SearchOutlined />
</template>
</Input>
</a-input>
<span :class="`${prefixCls}-cancel`" @click="handleClose">
{{ t('common.cancelText') }}
</span>
Expand Down Expand Up @@ -59,7 +59,6 @@
<script lang="ts">
import { defineComponent, computed, unref, ref, watch, nextTick } from 'vue';
import { SearchOutlined } from '@ant-design/icons-vue';
import { Input } from 'ant-design-vue';
import AppSearchFooter from './AppSearchFooter.vue';
import Icon from '/@/components/Icon';
import clickOutside from '/@/directives/clickOutside';
Expand All @@ -75,7 +74,7 @@
export default defineComponent({
name: 'AppSearchModal',
components: { Icon, SearchOutlined, AppSearchFooter, Input },
components: { Icon, SearchOutlined, AppSearchFooter },
directives: {
clickOutside,
},
Expand Down
7 changes: 3 additions & 4 deletions src/components/CountDown/src/CountdownInput.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
<template>
<AInput v-bind="$attrs" :class="prefixCls" :size="size" :value="state">
<a-input v-bind="$attrs" :class="prefixCls" :size="size" :value="state">
<template #addonAfter>
<CountButton :size="size" :count="count" :value="state" :beforeStartFunc="sendCodeApi" />
</template>
</AInput>
</a-input>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';
import { Input } from 'ant-design-vue';
import CountButton from './CountButton.vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
Expand All @@ -24,7 +23,7 @@
export default defineComponent({
name: 'CountDownInput',
components: { [Input.name]: Input, CountButton },
components: { CountButton },
inheritAttrs: false,
props,
setup(props) {
Expand Down
9 changes: 6 additions & 3 deletions src/components/Cropper/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import type Cropper from 'cropperjs';
import { withInstall } from '/@/utils';
import cropperImage from './src/Cropper.vue';
import avatarCropper from './src/CropperAvatar.vue';

export type { Cropper };
export { default as CropperImage } from './src/Cropper.vue';
export * from './src/typing';
export const CropperImage = withInstall(cropperImage);
export const CropperAvatar = withInstall(avatarCropper);
15 changes: 0 additions & 15 deletions src/components/Cropper/src/AvatarCropper.vue

This file was deleted.

258 changes: 258 additions & 0 deletions src/components/Cropper/src/CopperModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
<template>
<BasicModal
v-bind="$attrs"
@register="register"
:title="t('component.cropper.modalTitle')"
width="800px"
:canFullscreen="false"
@ok="handleOk"
:okText="t('component.cropper.okText')"
>
<div :class="prefixCls">
<div :class="`${prefixCls}-left`">
<div :class="`${prefixCls}-cropper`">
<CropperImage
v-if="src"
:src="src"
height="300px"
:circled="circled"
@cropend="handleCropend"
@ready="handleReady"
/>
</div>

<div :class="`${prefixCls}-toolbar`">
<Upload :fileList="[]" accept="image/*" :beforeUpload="handleBeforeUpload">
<a-button size="small" preIcon="ant-design:upload-outlined" type="primary" />
</Upload>
<Space>
<a-button
type="primary"
preIcon="ant-design:reload-outlined"
size="small"
@click="handlerToolbar('reset')"
/>
<a-button
type="primary"
preIcon="ant-design:rotate-left-outlined"
size="small"
@click="handlerToolbar('rotate', -45)"
/>
<a-button
type="primary"
preIcon="ant-design:rotate-right-outlined"
size="small"
@click="handlerToolbar('rotate', 45)"
/>
<a-button
type="primary"
preIcon="vaadin:arrows-long-h"
size="small"
@click="handlerToolbar('scaleX')"
/>
<a-button
type="primary"
preIcon="vaadin:arrows-long-v"
size="small"
@click="handlerToolbar('scaleY')"
/>
<a-button
type="primary"
preIcon="ant-design:zoom-in-outlined"
size="small"
@click="handlerToolbar('zoom', 0.1)"
/>
<a-button
type="primary"
preIcon="ant-design:zoom-out-outlined"
size="small"
@click="handlerToolbar('zoom', -0.1)"
/>
</Space>
</div>
</div>
<div :class="`${prefixCls}-right`">
<div :class="`${prefixCls}-preview`">
<img :src="previewSource" v-if="previewSource" />
</div>
<template v-if="previewSource">
<div :class="`${prefixCls}-group`">
<Avatar :src="previewSource" size="large" />
<Avatar :src="previewSource" :size="48" />
<Avatar :src="previewSource" :size="64" />
<Avatar :src="previewSource" :size="80" />
</div>
</template>
</div>
</div>
</BasicModal>
</template>
<script lang="ts">
import type { CropendResult, Cropper } from './typing';
import { defineComponent, ref } from 'vue';
import CropperImage from './Cropper.vue';
import { Space, Upload, Avatar } from 'ant-design-vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { dataURLtoBlob } from '/@/utils/file/base64Conver';
import { isFunction } from '/@/utils/is';
import { useI18n } from '/@/hooks/web/useI18n';
const props = {
circled: { type: Boolean, default: true },
uploadApi: {
type: Function as PropType<({ file: Blob, name: stirng, filename: string }) => Promise<any>>,
},
};
export default defineComponent({
name: 'CropperAvatar',
components: { BasicModal, Space, CropperImage, Upload, Avatar },
props,
emits: ['uploadSuccess', 'register'],
setup(props, { emit }) {
let filename = '';
const src = ref('');
const previewSource = ref('');
const cropper = ref<Cropper>();
let scaleX = 1;
let scaleY = 1;
const { prefixCls } = useDesign('cropper-am');
const [register, { closeModal, setModalProps }] = useModalInner();
const { t } = useI18n();
// Block upload
function handleBeforeUpload(file: File) {
const reader = new FileReader();
reader.readAsDataURL(file);
src.value = '';
previewSource.value = '';
reader.onload = function (e) {
src.value = (e.target?.result as string) ?? '';
filename = file.name;
};
return false;
}
function handleCropend({ imgBase64 }: CropendResult) {
previewSource.value = imgBase64;
}
function handleReady(cropperInstance: Cropper) {
cropper.value = cropperInstance;
}
function handlerToolbar(event: string, arg?: number) {
if (event === 'scaleX') {
scaleX = arg = scaleX === -1 ? 1 : -1;
}
if (event === 'scaleY') {
scaleY = arg = scaleY === -1 ? 1 : -1;
}
cropper?.value?.[event]?.(arg);
}
async function handleOk() {
const uploadApi = props.uploadApi;
if (uploadApi && isFunction(uploadApi)) {
const blob = dataURLtoBlob(previewSource.value);
try {
setModalProps({ confirmLoading: true });
const result = await uploadApi({ name: 'file', file: blob, filename });
emit('uploadSuccess', { source: previewSource.value, data: result.data });
closeModal();
} finally {
setModalProps({ confirmLoading: false });
}
}
}
return {
t,
prefixCls,
src,
register,
previewSource,
handleBeforeUpload,
handleCropend,
handleReady,
handlerToolbar,
handleOk,
};
},
});
</script>

<style lang="less">
@prefix-cls: ~'@{namespace}-cropper-am';
.@{prefix-cls} {
display: flex;
&-left,
&-right {
height: 340px;
}
&-left {
width: 55%;
}
&-right {
width: 45%;
}
&-cropper {
height: 300px;
background: #eee;
background-image: linear-gradient(
45deg,
rgba(0, 0, 0, 0.25) 25%,
transparent 0,
transparent 75%,
rgba(0, 0, 0, 0.25) 0
),
linear-gradient(
45deg,
rgba(0, 0, 0, 0.25) 25%,
transparent 0,
transparent 75%,
rgba(0, 0, 0, 0.25) 0
);
background-position: 0 0, 12px 12px;
background-size: 24px 24px;
}
&-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 10px;
}
&-preview {
width: 220px;
height: 220px;
margin: 0 auto;
overflow: hidden;
border: 1px solid @border-color-base;
border-radius: 50%;
img {
width: 100%;
height: 100%;
}
}
&-group {
display: flex;
padding-top: 8px;
margin-top: 8px;
border-top: 1px solid @border-color-base;
justify-content: space-around;
align-items: center;
}
}
</style>
Loading

0 comments on commit 8e410fc

Please sign in to comment.