@@ -85,6 +85,14 @@ export default {
8585 stepStatus: [' wait' , ' success' , ' finish' , ' process' , ' error' ],
8686 loading: true ,
8787 files: {},
88+ imagePreviews: {},
89+ imageValidationToken: {},
90+ imageFieldConfig: {
91+ logo_index: { width: 185 , height: 55 },
92+ logo_logout: { width: 82 , height: 82 },
93+ favicon: { width: 16 , height: 16 },
94+ login_image: { width: 492 , height: 472 }
95+ },
8896 examples: {
8997 ' primary' : this .$t (' Primary' ), ' info' : this .$t (' Info' ), ' warning' : this .$t (' Warning' ),
9098 ' success' : this .$t (' Success' ), ' danger' : this .$t (' Danger' )
@@ -120,8 +128,11 @@ export default {
120128 tip: this .$t (' LoginImageTip' )
121129 },
122130 on: {
131+ input : ([value ]) => {
132+ this .syncImagePreview (' login_image' , value)
133+ },
123134 fileChange : ([value ], updateForm ) => {
124- this .files [ ' login_image' ] = value
135+ this .handleImageChange ( ' login_image' , value, updateForm)
125136 }
126137 }
127138 },
@@ -134,8 +145,11 @@ export default {
134145 tip: this .$t (' FaviconTip' )
135146 },
136147 on: {
148+ input : ([value ]) => {
149+ this .syncImagePreview (' favicon' , value)
150+ },
137151 fileChange : ([value ], updateForm ) => {
138- this .files [ ' favicon' ] = value
152+ this .handleImageChange ( ' favicon' , value, updateForm)
139153 }
140154 }
141155 },
@@ -149,8 +163,11 @@ export default {
149163 showBG: true
150164 },
151165 on: {
166+ input : ([value ]) => {
167+ this .syncImagePreview (' logo_index' , value)
168+ },
152169 fileChange : ([value ], updateForm ) => {
153- this .files [ ' logo_index' ] = value
170+ this .handleImageChange ( ' logo_index' , value, updateForm)
154171 }
155172 }
156173 },
@@ -163,8 +180,11 @@ export default {
163180 tip: this .$t (' LogoLogoutTip' )
164181 },
165182 on: {
183+ input : ([value ]) => {
184+ this .syncImagePreview (' logo_logout' , value)
185+ },
166186 fileChange : ([value ], updateForm ) => {
167- this .files [ ' logo_logout' ] = value
187+ this .handleImageChange ( ' logo_logout' , value, updateForm)
168188 }
169189 }
170190 },
@@ -242,6 +262,179 @@ export default {
242262 }).catch (error => {
243263 this .$message .error (this .$tc (' UpdateErrorMsg' + ' ' + error))
244264 })
265+ },
266+ async handleImageChange (field , file , updateForm ) {
267+ const token = (this .imageValidationToken [field] || 0 ) + 1
268+ this .imageValidationToken [field] = token
269+
270+ if (! file) {
271+ this .$delete (this .files , field)
272+ this .$delete (this .imagePreviews , field)
273+ return
274+ }
275+
276+ const previousFile = this .files [field]
277+ const previousPreview = this .imagePreviews [field]
278+ const nextFile = await this .validateImage (field, file)
279+
280+ if (this .imageValidationToken [field] !== token) {
281+ return
282+ }
283+
284+ if (! nextFile) {
285+ if (previousFile) {
286+ this .files [field] = previousFile
287+ } else {
288+ this .$delete (this .files , field)
289+ }
290+ if (previousPreview) {
291+ this .imagePreviews [field] = previousPreview
292+ } else {
293+ this .$delete (this .imagePreviews , field)
294+ }
295+ updateForm ({ [field]: previousPreview || this .interfaceInfo [field] || ' ' })
296+ return
297+ }
298+
299+ this .files [field] = nextFile
300+ if (nextFile !== file) {
301+ const previewUrl = this .getObjectURL (nextFile)
302+ this .imagePreviews [field] = previewUrl
303+ updateForm ({ [field]: previewUrl })
304+ }
305+ },
306+ syncImagePreview (field , value ) {
307+ this .imagePreviews [field] = value
308+ },
309+ validateImage (field , file ) {
310+ return new Promise ((resolve ) => {
311+ const url = URL .createObjectURL (file)
312+ const img = new Image ()
313+
314+ img .onload = async () => {
315+ URL .revokeObjectURL (url)
316+ if (file .size <= 10 * 1024 * 1024 ) {
317+ const fieldConfig = this .imageFieldConfig [field]
318+ const isMatchedSize = ! fieldConfig ||
319+ (
320+ img .naturalWidth === fieldConfig .width &&
321+ img .naturalHeight === fieldConfig .height
322+ )
323+
324+ if (isMatchedSize) {
325+ resolve (file)
326+ return
327+ }
328+
329+ const resizedFile = await this .compressImageToLimit (field, file, img)
330+ if (! resizedFile) {
331+ resolve (null )
332+ return
333+ }
334+ resolve (resizedFile)
335+ return
336+ }
337+
338+ this .$message .error (this .$t (' UploadFileLthHelpText' , { limit: 10 }))
339+ resolve (null )
340+ }
341+
342+ img .onerror = () => {
343+ URL .revokeObjectURL (url)
344+ this .$message .error (this .$t (' ImageFileCorruptedOrUnreadable' ))
345+ resolve (null )
346+ }
347+
348+ img .src = url
349+ })
350+ },
351+ async compressImageToLimit (field , file , image ) {
352+ const limitSize = 10 * 1024 * 1024
353+ const { width: targetWidth , height: targetHeight } = this .imageFieldConfig [field] || {
354+ width: image .naturalWidth ,
355+ height: image .naturalHeight
356+ }
357+ const canvas = document .createElement (' canvas' )
358+ canvas .width = targetWidth
359+ canvas .height = targetHeight
360+
361+ const ctx = canvas .getContext (' 2d' )
362+ if (! ctx) {
363+ return null
364+ }
365+
366+ const drawRect = this .getContainRect (
367+ image .naturalWidth ,
368+ image .naturalHeight ,
369+ targetWidth,
370+ targetHeight
371+ )
372+
373+ if (file .type !== ' image/png' ) {
374+ ctx .fillStyle = ' #fff'
375+ ctx .fillRect (0 , 0 , targetWidth, targetHeight)
376+ } else {
377+ ctx .clearRect (0 , 0 , targetWidth, targetHeight)
378+ }
379+
380+ ctx .drawImage (
381+ image,
382+ drawRect .offsetX ,
383+ drawRect .offsetY ,
384+ drawRect .width ,
385+ drawRect .height
386+ )
387+
388+ const mimeType = file .type === ' image/png' ? ' image/png' : ' image/jpeg'
389+ const blob = await this .canvasToBlob (canvas, mimeType, mimeType === ' image/png' ? 1 : 0.92 )
390+ if (! blob || blob .size > limitSize) {
391+ return null
392+ }
393+ return new File (
394+ [blob],
395+ this .renameImage (file .name , mimeType),
396+ {
397+ type: mimeType,
398+ lastModified: Date .now ()
399+ }
400+ )
401+ },
402+ getContainRect (sourceWidth , sourceHeight , targetWidth , targetHeight ) {
403+ const scale = Math .min (targetWidth / sourceWidth, targetHeight / sourceHeight, 1 )
404+ const width = Math .max (1 , Math .round (sourceWidth * scale))
405+ const height = Math .max (1 , Math .round (sourceHeight * scale))
406+
407+ return {
408+ width,
409+ height,
410+ offsetX: Math .floor ((targetWidth - width) / 2 ),
411+ offsetY: Math .floor ((targetHeight - height) / 2 )
412+ }
413+ },
414+ canvasToBlob (canvas , type , quality ) {
415+ return new Promise ((resolve ) => {
416+ canvas .toBlob (blob => {
417+ resolve (blob)
418+ }, type, quality)
419+ })
420+ },
421+ renameImage (fileName , mimeType ) {
422+ const ext = mimeType === ' image/png' ? ' .png' : ' .jpg'
423+ if (/ \. [^ . ] + $ / .test (fileName)) {
424+ return fileName .replace (/ \. [^ . ] + $ / , ext)
425+ }
426+ return fileName + ext
427+ },
428+ getObjectURL (file ) {
429+ let url = null
430+ if (window .createObjectURL !== undefined ) {
431+ url = window .createObjectURL (file)
432+ } else if (window .URL !== undefined ) {
433+ url = window .URL .createObjectURL (file)
434+ } else if (window .webkitURL !== undefined ) {
435+ url = window .webkitURL .createObjectURL (file)
436+ }
437+ return url
245438 }
246439 }
247440}
0 commit comments