Commit 27cb958c2ef4ef74bb204a46b49af9fbd9b7eeff

Authored by Li Kui
Committed by GitHub
1 parent 155ad458

fix: Cropper typo (#2891)

src/components/Cropper/src/CropperAvatar.vue
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 {{ btnText ? btnText : t('component.cropper.selectImage') }} 20 {{ btnText ? btnText : t('component.cropper.selectImage') }}
21 </a-button> 21 </a-button>
22 22
23 - <CopperModal 23 + <CropperModal
24 @register="register" 24 @register="register"
25 @upload-success="handleUploadSuccess" 25 @upload-success="handleUploadSuccess"
26 :uploadApi="uploadApi" 26 :uploadApi="uploadApi"
@@ -39,7 +39,7 @@ @@ -39,7 +39,7 @@
39 watch, 39 watch,
40 PropType, 40 PropType,
41 } from 'vue'; 41 } from 'vue';
42 - import CopperModal from './CopperModal.vue'; 42 + import CropperModal from './CropperModal.vue';
43 import { useDesign } from '/@/hooks/web/useDesign'; 43 import { useDesign } from '/@/hooks/web/useDesign';
44 import { useModal } from '/@/components/Modal'; 44 import { useModal } from '/@/components/Modal';
45 import { useMessage } from '/@/hooks/web/useMessage'; 45 import { useMessage } from '/@/hooks/web/useMessage';
@@ -58,7 +58,7 @@ @@ -58,7 +58,7 @@
58 58
59 export default defineComponent({ 59 export default defineComponent({
60 name: 'CropperAvatar', 60 name: 'CropperAvatar',
61 - components: { CopperModal, Icon }, 61 + components: { CropperModal, Icon },
62 props, 62 props,
63 emits: ['update:value', 'change'], 63 emits: ['update:value', 'change'],
64 setup(props, { emit, expose }) { 64 setup(props, { emit, expose }) {
src/components/Cropper/src/CopperModal.vue renamed to src/components/Cropper/src/CropperModal.vue
1 -<template>  
2 - <BasicModal  
3 - v-bind="$attrs"  
4 - @register="register"  
5 - :title="t('component.cropper.modalTitle')"  
6 - width="800px"  
7 - :canFullscreen="false"  
8 - @ok="handleOk"  
9 - :okText="t('component.cropper.okText')"  
10 - >  
11 - <div :class="prefixCls">  
12 - <div :class="`${prefixCls}-left`">  
13 - <div :class="`${prefixCls}-cropper`">  
14 - <CropperImage  
15 - v-if="src"  
16 - :src="src"  
17 - height="300px"  
18 - :circled="circled"  
19 - @cropend="handleCropend"  
20 - @ready="handleReady"  
21 - />  
22 - </div>  
23 -  
24 - <div :class="`${prefixCls}-toolbar`">  
25 - <Upload :fileList="[]" accept="image/*" :beforeUpload="handleBeforeUpload">  
26 - <Tooltip :title="t('component.cropper.selectImage')" placement="bottom">  
27 - <a-button size="small" preIcon="ant-design:upload-outlined" type="primary" />  
28 - </Tooltip>  
29 - </Upload>  
30 - <Space>  
31 - <Tooltip :title="t('component.cropper.btn_reset')" placement="bottom">  
32 - <a-button  
33 - type="primary"  
34 - preIcon="ant-design:reload-outlined"  
35 - size="small"  
36 - :disabled="!src"  
37 - @click="handlerToolbar('reset')"  
38 - />  
39 - </Tooltip>  
40 - <Tooltip :title="t('component.cropper.btn_rotate_left')" placement="bottom">  
41 - <a-button  
42 - type="primary"  
43 - preIcon="ant-design:rotate-left-outlined"  
44 - size="small"  
45 - :disabled="!src"  
46 - @click="handlerToolbar('rotate', -45)"  
47 - />  
48 - </Tooltip>  
49 - <Tooltip :title="t('component.cropper.btn_rotate_right')" placement="bottom">  
50 - <a-button  
51 - type="primary"  
52 - preIcon="ant-design:rotate-right-outlined"  
53 - size="small"  
54 - :disabled="!src"  
55 - @click="handlerToolbar('rotate', 45)"  
56 - />  
57 - </Tooltip>  
58 - <Tooltip :title="t('component.cropper.btn_scale_x')" placement="bottom">  
59 - <a-button  
60 - type="primary"  
61 - preIcon="vaadin:arrows-long-h"  
62 - size="small"  
63 - :disabled="!src"  
64 - @click="handlerToolbar('scaleX')"  
65 - />  
66 - </Tooltip>  
67 - <Tooltip :title="t('component.cropper.btn_scale_y')" placement="bottom">  
68 - <a-button  
69 - type="primary"  
70 - preIcon="vaadin:arrows-long-v"  
71 - size="small"  
72 - :disabled="!src"  
73 - @click="handlerToolbar('scaleY')"  
74 - />  
75 - </Tooltip>  
76 - <Tooltip :title="t('component.cropper.btn_zoom_in')" placement="bottom">  
77 - <a-button  
78 - type="primary"  
79 - preIcon="ant-design:zoom-in-outlined"  
80 - size="small"  
81 - :disabled="!src"  
82 - @click="handlerToolbar('zoom', 0.1)"  
83 - />  
84 - </Tooltip>  
85 - <Tooltip :title="t('component.cropper.btn_zoom_out')" placement="bottom">  
86 - <a-button  
87 - type="primary"  
88 - preIcon="ant-design:zoom-out-outlined"  
89 - size="small"  
90 - :disabled="!src"  
91 - @click="handlerToolbar('zoom', -0.1)"  
92 - />  
93 - </Tooltip>  
94 - </Space>  
95 - </div>  
96 - </div>  
97 - <div :class="`${prefixCls}-right`">  
98 - <div :class="`${prefixCls}-preview`">  
99 - <img :src="previewSource" v-if="previewSource" :alt="t('component.cropper.preview')" />  
100 - </div>  
101 - <template v-if="previewSource">  
102 - <div :class="`${prefixCls}-group`">  
103 - <Avatar :src="previewSource" size="large" />  
104 - <Avatar :src="previewSource" :size="48" />  
105 - <Avatar :src="previewSource" :size="64" />  
106 - <Avatar :src="previewSource" :size="80" />  
107 - </div>  
108 - </template>  
109 - </div>  
110 - </div>  
111 - </BasicModal>  
112 -</template>  
113 -<script lang="ts">  
114 - import type { CropendResult, Cropper } from './typing';  
115 -  
116 - import { defineComponent, ref, PropType } from 'vue';  
117 - import CropperImage from './Cropper.vue';  
118 - import { Space, Upload, Avatar, Tooltip } from 'ant-design-vue';  
119 - import { useDesign } from '/@/hooks/web/useDesign';  
120 - import { BasicModal, useModalInner } from '/@/components/Modal';  
121 - import { dataURLtoBlob } from '/@/utils/file/base64Conver';  
122 - import { isFunction } from '/@/utils/is';  
123 - import { useI18n } from '/@/hooks/web/useI18n';  
124 -  
125 - type apiFunParams = { file: Blob; name: string; filename: string };  
126 -  
127 - const props = {  
128 - circled: { type: Boolean, default: true },  
129 - uploadApi: {  
130 - type: Function as PropType<(params: apiFunParams) => Promise<any>>,  
131 - },  
132 - src: { type: String },  
133 - };  
134 -  
135 - export default defineComponent({  
136 - name: 'CropperModal',  
137 - components: { BasicModal, Space, CropperImage, Upload, Avatar, Tooltip },  
138 - props,  
139 - emits: ['uploadSuccess', 'register'],  
140 - setup(props, { emit }) {  
141 - let filename = '';  
142 - const src = ref(props.src || '');  
143 - const previewSource = ref('');  
144 - const cropper = ref<Cropper>();  
145 - let scaleX = 1;  
146 - let scaleY = 1;  
147 -  
148 - const { prefixCls } = useDesign('cropper-am');  
149 - const [register, { closeModal, setModalProps }] = useModalInner();  
150 - const { t } = useI18n();  
151 -  
152 - // Block upload  
153 - function handleBeforeUpload(file: File) {  
154 - const reader = new FileReader();  
155 - reader.readAsDataURL(file);  
156 - src.value = '';  
157 - previewSource.value = '';  
158 - reader.onload = function (e) {  
159 - src.value = (e.target?.result as string) ?? '';  
160 - filename = file.name;  
161 - };  
162 - return false;  
163 - }  
164 -  
165 - function handleCropend({ imgBase64 }: CropendResult) {  
166 - previewSource.value = imgBase64;  
167 - }  
168 -  
169 - function handleReady(cropperInstance: Cropper) {  
170 - cropper.value = cropperInstance;  
171 - }  
172 -  
173 - function handlerToolbar(event: string, arg?: number) {  
174 - if (event === 'scaleX') {  
175 - scaleX = arg = scaleX === -1 ? 1 : -1;  
176 - }  
177 - if (event === 'scaleY') {  
178 - scaleY = arg = scaleY === -1 ? 1 : -1;  
179 - }  
180 - cropper?.value?.[event]?.(arg);  
181 - }  
182 -  
183 - async function handleOk() {  
184 - const uploadApi = props.uploadApi;  
185 - if (uploadApi && isFunction(uploadApi)) {  
186 - const blob = dataURLtoBlob(previewSource.value);  
187 - try {  
188 - setModalProps({ confirmLoading: true });  
189 - const result = await uploadApi({ name: 'file', file: blob, filename });  
190 - emit('uploadSuccess', { source: previewSource.value, data: result.url });  
191 - closeModal();  
192 - } finally {  
193 - setModalProps({ confirmLoading: false });  
194 - }  
195 - }  
196 - }  
197 -  
198 - return {  
199 - t,  
200 - prefixCls,  
201 - src,  
202 - register,  
203 - previewSource,  
204 - handleBeforeUpload,  
205 - handleCropend,  
206 - handleReady,  
207 - handlerToolbar,  
208 - handleOk,  
209 - };  
210 - },  
211 - });  
212 -</script>  
213 -  
214 -<style lang="less">  
215 - @prefix-cls: ~'@{namespace}-cropper-am';  
216 -  
217 - .@{prefix-cls} {  
218 - display: flex;  
219 -  
220 - &-left,  
221 - &-right {  
222 - height: 340px;  
223 - }  
224 -  
225 - &-left {  
226 - width: 55%;  
227 - }  
228 -  
229 - &-right {  
230 - width: 45%;  
231 - }  
232 -  
233 - &-cropper {  
234 - height: 300px;  
235 - background: #eee;  
236 - background-image: linear-gradient(  
237 - 45deg,  
238 - rgb(0 0 0 / 25%) 25%,  
239 - transparent 0,  
240 - transparent 75%,  
241 - rgb(0 0 0 / 25%) 0  
242 - ),  
243 - linear-gradient(  
244 - 45deg,  
245 - rgb(0 0 0 / 25%) 25%,  
246 - transparent 0,  
247 - transparent 75%,  
248 - rgb(0 0 0 / 25%) 0  
249 - );  
250 - background-position: 0 0, 12px 12px;  
251 - background-size: 24px 24px;  
252 - }  
253 -  
254 - &-toolbar {  
255 - display: flex;  
256 - align-items: center;  
257 - justify-content: space-between;  
258 - margin-top: 10px;  
259 - }  
260 -  
261 - &-preview {  
262 - width: 220px;  
263 - height: 220px;  
264 - margin: 0 auto;  
265 - overflow: hidden;  
266 - border: 1px solid @border-color-base;  
267 - border-radius: 50%;  
268 -  
269 - img {  
270 - width: 100%;  
271 - height: 100%;  
272 - }  
273 - }  
274 -  
275 - &-group {  
276 - display: flex;  
277 - align-items: center;  
278 - justify-content: space-around;  
279 - margin-top: 8px;  
280 - padding-top: 8px;  
281 - border-top: 1px solid @border-color-base;  
282 - }  
283 - }  
284 -</style> 1 +<template>
  2 + <BasicModal
  3 + v-bind="$attrs"
  4 + @register="register"
  5 + :title="t('component.cropper.modalTitle')"
  6 + width="800px"
  7 + :canFullscreen="false"
  8 + @ok="handleOk"
  9 + :okText="t('component.cropper.okText')"
  10 + >
  11 + <div :class="prefixCls">
  12 + <div :class="`${prefixCls}-left`">
  13 + <div :class="`${prefixCls}-cropper`">
  14 + <CropperImage
  15 + v-if="src"
  16 + :src="src"
  17 + height="300px"
  18 + :circled="circled"
  19 + @cropend="handleCropend"
  20 + @ready="handleReady"
  21 + />
  22 + </div>
  23 +
  24 + <div :class="`${prefixCls}-toolbar`">
  25 + <Upload :fileList="[]" accept="image/*" :beforeUpload="handleBeforeUpload">
  26 + <Tooltip :title="t('component.cropper.selectImage')" placement="bottom">
  27 + <a-button size="small" preIcon="ant-design:upload-outlined" type="primary" />
  28 + </Tooltip>
  29 + </Upload>
  30 + <Space>
  31 + <Tooltip :title="t('component.cropper.btn_reset')" placement="bottom">
  32 + <a-button
  33 + type="primary"
  34 + preIcon="ant-design:reload-outlined"
  35 + size="small"
  36 + :disabled="!src"
  37 + @click="handlerToolbar('reset')"
  38 + />
  39 + </Tooltip>
  40 + <Tooltip :title="t('component.cropper.btn_rotate_left')" placement="bottom">
  41 + <a-button
  42 + type="primary"
  43 + preIcon="ant-design:rotate-left-outlined"
  44 + size="small"
  45 + :disabled="!src"
  46 + @click="handlerToolbar('rotate', -45)"
  47 + />
  48 + </Tooltip>
  49 + <Tooltip :title="t('component.cropper.btn_rotate_right')" placement="bottom">
  50 + <a-button
  51 + type="primary"
  52 + preIcon="ant-design:rotate-right-outlined"
  53 + size="small"
  54 + :disabled="!src"
  55 + @click="handlerToolbar('rotate', 45)"
  56 + />
  57 + </Tooltip>
  58 + <Tooltip :title="t('component.cropper.btn_scale_x')" placement="bottom">
  59 + <a-button
  60 + type="primary"
  61 + preIcon="vaadin:arrows-long-h"
  62 + size="small"
  63 + :disabled="!src"
  64 + @click="handlerToolbar('scaleX')"
  65 + />
  66 + </Tooltip>
  67 + <Tooltip :title="t('component.cropper.btn_scale_y')" placement="bottom">
  68 + <a-button
  69 + type="primary"
  70 + preIcon="vaadin:arrows-long-v"
  71 + size="small"
  72 + :disabled="!src"
  73 + @click="handlerToolbar('scaleY')"
  74 + />
  75 + </Tooltip>
  76 + <Tooltip :title="t('component.cropper.btn_zoom_in')" placement="bottom">
  77 + <a-button
  78 + type="primary"
  79 + preIcon="ant-design:zoom-in-outlined"
  80 + size="small"
  81 + :disabled="!src"
  82 + @click="handlerToolbar('zoom', 0.1)"
  83 + />
  84 + </Tooltip>
  85 + <Tooltip :title="t('component.cropper.btn_zoom_out')" placement="bottom">
  86 + <a-button
  87 + type="primary"
  88 + preIcon="ant-design:zoom-out-outlined"
  89 + size="small"
  90 + :disabled="!src"
  91 + @click="handlerToolbar('zoom', -0.1)"
  92 + />
  93 + </Tooltip>
  94 + </Space>
  95 + </div>
  96 + </div>
  97 + <div :class="`${prefixCls}-right`">
  98 + <div :class="`${prefixCls}-preview`">
  99 + <img :src="previewSource" v-if="previewSource" :alt="t('component.cropper.preview')" />
  100 + </div>
  101 + <template v-if="previewSource">
  102 + <div :class="`${prefixCls}-group`">
  103 + <Avatar :src="previewSource" size="large" />
  104 + <Avatar :src="previewSource" :size="48" />
  105 + <Avatar :src="previewSource" :size="64" />
  106 + <Avatar :src="previewSource" :size="80" />
  107 + </div>
  108 + </template>
  109 + </div>
  110 + </div>
  111 + </BasicModal>
  112 +</template>
  113 +<script lang="ts">
  114 + import type { CropendResult, Cropper } from './typing';
  115 +
  116 + import { defineComponent, ref, PropType } from 'vue';
  117 + import CropperImage from './Cropper.vue';
  118 + import { Space, Upload, Avatar, Tooltip } from 'ant-design-vue';
  119 + import { useDesign } from '/@/hooks/web/useDesign';
  120 + import { BasicModal, useModalInner } from '/@/components/Modal';
  121 + import { dataURLtoBlob } from '/@/utils/file/base64Conver';
  122 + import { isFunction } from '/@/utils/is';
  123 + import { useI18n } from '/@/hooks/web/useI18n';
  124 +
  125 + type apiFunParams = { file: Blob; name: string; filename: string };
  126 +
  127 + const props = {
  128 + circled: { type: Boolean, default: true },
  129 + uploadApi: {
  130 + type: Function as PropType<(params: apiFunParams) => Promise<any>>,
  131 + },
  132 + src: { type: String },
  133 + };
  134 +
  135 + export default defineComponent({
  136 + name: 'CropperModal',
  137 + components: { BasicModal, Space, CropperImage, Upload, Avatar, Tooltip },
  138 + props,
  139 + emits: ['uploadSuccess', 'register'],
  140 + setup(props, { emit }) {
  141 + let filename = '';
  142 + const src = ref(props.src || '');
  143 + const previewSource = ref('');
  144 + const cropper = ref<Cropper>();
  145 + let scaleX = 1;
  146 + let scaleY = 1;
  147 +
  148 + const { prefixCls } = useDesign('cropper-am');
  149 + const [register, { closeModal, setModalProps }] = useModalInner();
  150 + const { t } = useI18n();
  151 +
  152 + // Block upload
  153 + function handleBeforeUpload(file: File) {
  154 + const reader = new FileReader();
  155 + reader.readAsDataURL(file);
  156 + src.value = '';
  157 + previewSource.value = '';
  158 + reader.onload = function (e) {
  159 + src.value = (e.target?.result as string) ?? '';
  160 + filename = file.name;
  161 + };
  162 + return false;
  163 + }
  164 +
  165 + function handleCropend({ imgBase64 }: CropendResult) {
  166 + previewSource.value = imgBase64;
  167 + }
  168 +
  169 + function handleReady(cropperInstance: Cropper) {
  170 + cropper.value = cropperInstance;
  171 + }
  172 +
  173 + function handlerToolbar(event: string, arg?: number) {
  174 + if (event === 'scaleX') {
  175 + scaleX = arg = scaleX === -1 ? 1 : -1;
  176 + }
  177 + if (event === 'scaleY') {
  178 + scaleY = arg = scaleY === -1 ? 1 : -1;
  179 + }
  180 + cropper?.value?.[event]?.(arg);
  181 + }
  182 +
  183 + async function handleOk() {
  184 + const uploadApi = props.uploadApi;
  185 + if (uploadApi && isFunction(uploadApi)) {
  186 + const blob = dataURLtoBlob(previewSource.value);
  187 + try {
  188 + setModalProps({ confirmLoading: true });
  189 + const result = await uploadApi({ name: 'file', file: blob, filename });
  190 + emit('uploadSuccess', { source: previewSource.value, data: result.url });
  191 + closeModal();
  192 + } finally {
  193 + setModalProps({ confirmLoading: false });
  194 + }
  195 + }
  196 + }
  197 +
  198 + return {
  199 + t,
  200 + prefixCls,
  201 + src,
  202 + register,
  203 + previewSource,
  204 + handleBeforeUpload,
  205 + handleCropend,
  206 + handleReady,
  207 + handlerToolbar,
  208 + handleOk,
  209 + };
  210 + },
  211 + });
  212 +</script>
  213 +
  214 +<style lang="less">
  215 + @prefix-cls: ~'@{namespace}-cropper-am';
  216 +
  217 + .@{prefix-cls} {
  218 + display: flex;
  219 +
  220 + &-left,
  221 + &-right {
  222 + height: 340px;
  223 + }
  224 +
  225 + &-left {
  226 + width: 55%;
  227 + }
  228 +
  229 + &-right {
  230 + width: 45%;
  231 + }
  232 +
  233 + &-cropper {
  234 + height: 300px;
  235 + background: #eee;
  236 + background-image: linear-gradient(
  237 + 45deg,
  238 + rgb(0 0 0 / 25%) 25%,
  239 + transparent 0,
  240 + transparent 75%,
  241 + rgb(0 0 0 / 25%) 0
  242 + ),
  243 + linear-gradient(
  244 + 45deg,
  245 + rgb(0 0 0 / 25%) 25%,
  246 + transparent 0,
  247 + transparent 75%,
  248 + rgb(0 0 0 / 25%) 0
  249 + );
  250 + background-position: 0 0, 12px 12px;
  251 + background-size: 24px 24px;
  252 + }
  253 +
  254 + &-toolbar {
  255 + display: flex;
  256 + align-items: center;
  257 + justify-content: space-between;
  258 + margin-top: 10px;
  259 + }
  260 +
  261 + &-preview {
  262 + width: 220px;
  263 + height: 220px;
  264 + margin: 0 auto;
  265 + overflow: hidden;
  266 + border: 1px solid @border-color-base;
  267 + border-radius: 50%;
  268 +
  269 + img {
  270 + width: 100%;
  271 + height: 100%;
  272 + }
  273 + }
  274 +
  275 + &-group {
  276 + display: flex;
  277 + align-items: center;
  278 + justify-content: space-around;
  279 + margin-top: 8px;
  280 + padding-top: 8px;
  281 + border-top: 1px solid @border-color-base;
  282 + }
  283 + }
  284 +</style>