diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 929e36008..759e1f756 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -635,7 +635,12 @@ paths: description: | Avatar image file. - Maximum size is 5 MiB. + Avatar requirements: + + - The image format must be PNG (`image/png`) or JPEG (`image/jpeg`). + - The maximum file size is 5 MiB. + + Uploaded avatars are normalized to a 512x512 PNG. Upload a square image for best visual quality. type: string format: binary encoding: @@ -934,7 +939,12 @@ paths: description: | Avatar image file. - Maximum size is 5 MiB. + Avatar requirements: + + - The image format must be PNG (`image/png`) or JPEG (`image/jpeg`). + - The maximum file size is 5 MiB. + + Uploaded avatars are normalized to a 512x512 PNG. Upload a square image for best visual quality. type: string format: binary encoding: @@ -4832,7 +4842,12 @@ paths: description: | Avatar image file. - Maximum size is 5 MiB. + Avatar requirements: + + - The image format must be PNG (`image/png`) or JPEG (`image/jpeg`). + - The maximum file size is 5 MiB. + + Uploaded avatars are normalized to a 512x512 PNG. Upload a square image for best visual quality. type: string format: binary encoding: diff --git a/spx-gui/src/apps/xbuilder/pages/admin/user.vue b/spx-gui/src/apps/xbuilder/pages/admin/user.vue index b377d7b3a..02a0f1fee 100644 --- a/spx-gui/src/apps/xbuilder/pages/admin/user.vue +++ b/spx-gui/src/apps/xbuilder/pages/admin/user.vue @@ -51,6 +51,10 @@ import * as authorizationAdminApis from '@/apis/admin/authorization' import { validateAccountUserPassword } from '@/components/account/admin/password' import { formatJSON, formatTime } from './common' +const avatarSize = 512 +const maxAvatarFileSize = 5 * 1024 * 1024 +const acceptedAvatarContentTypes = new Set(['image/png', 'image/jpeg']) + const props = defineProps<{ userID: string }>() @@ -255,12 +259,29 @@ async function handleAvatarFile(event: Event) { ;(event.target as HTMLInputElement).value = '' } +function validateAvatarFile(file: File) { + if (file.type !== '' && !acceptedAvatarContentTypes.has(file.type)) { + throw new DefaultException({ + en: 'Avatar image must be PNG or JPEG', + zh: '头像图片必须是 PNG 或 JPEG' + }) + } + + if (file.size > maxAvatarFileSize) { + throw new DefaultException({ + en: 'Avatar image must be 5 MB or smaller', + zh: '头像图片不能超过 5 MB' + }) + } +} + function hideBrokenImage(event: Event) { ;(event.currentTarget as HTMLImageElement).style.display = 'none' } const handleUpdateAvatar = useMessageHandle( async (file: File) => { + validateAvatarFile(file) await accountAdminApis.updateAccountUserAvatar(props.userID, file) userQuery.refetch() }, @@ -444,7 +465,14 @@ function deleteAllSessions() { }} -