From 7855298eacf6d6b7691024226bcf6f06fadf8cb7 Mon Sep 17 00:00:00 2001 From: ByungCheol Date: Sun, 28 Jun 2026 11:39:48 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EA=B3=84=EC=A0=95=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EC=82=AC=EC=A7=84=20=E2=80=94=20?= =?UTF-8?q?=EA=B5=AC=EA=B8=80=20=EC=95=84=EB=B0=94=ED=83=80=20=EC=9A=B0?= =?UTF-8?q?=EC=84=A0=20+=20=EB=B3=80=EA=B2=BD/=EB=90=98=EB=8F=8C=EB=A6=AC?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 아바타 표시 우선순위: 사용자 지정(profileImage) > 구글(googlePicture) > 이니셜 - 사진 변경: 이미지 선택 → 정사각형 크롭·192px 축소(JPEG) → PUT /auth/profile-image - 되돌리기: 사용자 지정 사진 해제 → 구글 사진(없으면 이니셜)으로 폴백 - authApi.updateProfileImage 추가, utils/avatar.js(표시 소스/이니셜/다운스케일) Co-Authored-By: Claude Opus 4.8 --- src/api/authApi.js | 4 + src/utils/avatar.js | 55 ++++++++ src/views/settings/AccountInfoView.vue | 168 ++++++++++++++++++++++++- 3 files changed, 226 insertions(+), 1 deletion(-) create mode 100644 src/utils/avatar.js diff --git a/src/api/authApi.js b/src/api/authApi.js index ede1e61..08ceda7 100644 --- a/src/api/authApi.js +++ b/src/api/authApi.js @@ -35,4 +35,8 @@ export const authApi = { updateProfile(payload) { return http.put('/auth/profile', payload) }, + // 프로필 사진 변경/해제 — image 가 null/빈값이면 해제(구글 사진으로 폴백) + updateProfileImage(image) { + return http.put('/auth/profile-image', { image }) + }, } diff --git a/src/utils/avatar.js b/src/utils/avatar.js new file mode 100644 index 0000000..85302fd --- /dev/null +++ b/src/utils/avatar.js @@ -0,0 +1,55 @@ +// 프로필 아바타 유틸 — 표시 우선순위(커스텀 > 구글 > 이니셜)와 업로드용 다운스케일. + +/** + * 표시할 아바타 이미지 소스를 고른다. + * 우선순위: 사용자 지정(profileImage) > 구글 아바타(googlePicture) > null(이니셜 폴백) + */ +export function avatarSrc(user) { + if (!user) return null + return user.profileImage || user.googlePicture || null +} + +/** 이름/아이디에서 이니셜(첫 글자) 한 글자 — 이미지 없을 때 폴백 */ +export function avatarInitial(user) { + const s = (user?.name || user?.loginId || '').trim() + return s ? s[0].toUpperCase() : '?' +} + +/** + * 업로드용으로 이미지 파일을 정사각형으로 크롭·축소해 JPEG data URL 로 변환한다. + * (서버/로컬 저장 용량을 작게 — 기본 192px) + */ +export function fileToAvatarDataUrl(file, size = 192, quality = 0.85) { + return new Promise((resolve, reject) => { + if (!file || !file.type?.startsWith('image/')) { + reject(new Error('이미지 파일이 아닙니다.')) + return + } + const url = URL.createObjectURL(file) + const img = new Image() + img.onload = () => { + try { + const canvas = document.createElement('canvas') + canvas.width = size + canvas.height = size + const ctx = canvas.getContext('2d') + // 중앙 정사각형 크롭(cover) + const min = Math.min(img.width, img.height) + const sx = (img.width - min) / 2 + const sy = (img.height - min) / 2 + ctx.drawImage(img, sx, sy, min, min, 0, 0, size, size) + const dataUrl = canvas.toDataURL('image/jpeg', quality) + URL.revokeObjectURL(url) + resolve(dataUrl) + } catch (e) { + URL.revokeObjectURL(url) + reject(e) + } + } + img.onerror = () => { + URL.revokeObjectURL(url) + reject(new Error('이미지를 읽지 못했습니다.')) + } + img.src = url + }) +} diff --git a/src/views/settings/AccountInfoView.vue b/src/views/settings/AccountInfoView.vue index 35c94a3..03754bf 100644 --- a/src/views/settings/AccountInfoView.vue +++ b/src/views/settings/AccountInfoView.vue @@ -1,16 +1,69 @@