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