houzhongjian
2024-08-08 820397e43a0b64d35c6d31d2a55475061438593b
提交 | 用户 | 时间
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>