Commit 27cb958c2ef4ef74bb204a46b49af9fbd9b7eeff
Committed by
GitHub
1 parent
155ad458
fix: Cropper typo (#2891)
Showing
2 changed files
with
287 additions
and
287 deletions
src/components/Cropper/src/CropperAvatar.vue
... | ... | @@ -20,7 +20,7 @@ |
20 | 20 | {{ btnText ? btnText : t('component.cropper.selectImage') }} |
21 | 21 | </a-button> |
22 | 22 | |
23 | - <CopperModal | |
23 | + <CropperModal | |
24 | 24 | @register="register" |
25 | 25 | @upload-success="handleUploadSuccess" |
26 | 26 | :uploadApi="uploadApi" |
... | ... | @@ -39,7 +39,7 @@ |
39 | 39 | watch, |
40 | 40 | PropType, |
41 | 41 | } from 'vue'; |
42 | - import CopperModal from './CopperModal.vue'; | |
42 | + import CropperModal from './CropperModal.vue'; | |
43 | 43 | import { useDesign } from '/@/hooks/web/useDesign'; |
44 | 44 | import { useModal } from '/@/components/Modal'; |
45 | 45 | import { useMessage } from '/@/hooks/web/useMessage'; |
... | ... | @@ -58,7 +58,7 @@ |
58 | 58 | |
59 | 59 | export default defineComponent({ |
60 | 60 | name: 'CropperAvatar', |
61 | - components: { CopperModal, Icon }, | |
61 | + components: { CropperModal, Icon }, | |
62 | 62 | props, |
63 | 63 | emits: ['update:value', 'change'], |
64 | 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> | ... | ... |