Skip to content

Commit 1dbb99c

Browse files
committed
feat:New image verification
1 parent c86c555 commit 1dbb99c

1 file changed

Lines changed: 197 additions & 4 deletions

File tree

‎src/views/settings/Interface.vue‎

Lines changed: 197 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)