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,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> |