멤버 필터와 썸네일 편집 개선
This commit is contained in:
@@ -17,6 +17,8 @@ const saveMessage = ref('')
|
||||
const saveError = ref('')
|
||||
const isSaving = ref(false)
|
||||
const savedMemberSnapshot = ref('')
|
||||
const avatarInputRef = ref(null)
|
||||
const isUploadingAvatar = ref(false)
|
||||
|
||||
const form = reactive({
|
||||
username: '',
|
||||
@@ -146,6 +148,57 @@ const {
|
||||
leaveUnsavedPage
|
||||
} = useAdminUnsavedChangesGuard(hasUnsavedMemberChanges)
|
||||
|
||||
/**
|
||||
* 썸네일 파일 선택창을 연다.
|
||||
* @returns {void}
|
||||
*/
|
||||
const openAvatarFilePicker = () => {
|
||||
avatarInputRef.value?.click()
|
||||
}
|
||||
|
||||
/**
|
||||
* 회원 썸네일 파일을 업로드하고 폼에 반영한다.
|
||||
* @param {Event} event - 파일 선택 이벤트
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const uploadAvatar = async (event) => {
|
||||
const target = event.target instanceof HTMLInputElement ? event.target : null
|
||||
const file = target?.files?.[0]
|
||||
|
||||
if (!file || isUploadingAvatar.value) {
|
||||
return
|
||||
}
|
||||
|
||||
isUploadingAvatar.value = true
|
||||
saveError.value = ''
|
||||
saveMessage.value = ''
|
||||
|
||||
try {
|
||||
const formData = new FormData()
|
||||
formData.append('files', file)
|
||||
const result = await $fetch('/admin/api/uploads', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
form.avatarUrl = result.files?.[0]?.url || ''
|
||||
} catch (error) {
|
||||
saveError.value = error?.data?.message || '썸네일 업로드에 실패했습니다.'
|
||||
} finally {
|
||||
isUploadingAvatar.value = false
|
||||
if (target) {
|
||||
target.value = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 회원 썸네일 연결을 제거한다.
|
||||
* @returns {void}
|
||||
*/
|
||||
const removeAvatar = () => {
|
||||
form.avatarUrl = ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 회원 기본 정보를 저장한다.
|
||||
* @returns {Promise<void>}
|
||||
@@ -221,15 +274,39 @@ watch(() => props.member, () => {
|
||||
<div class="admin-member-form__body grid gap-8 py-8 xl:grid-cols-3">
|
||||
<aside class="admin-member-form__summary">
|
||||
<div class="admin-member-form__identity flex items-center gap-4">
|
||||
<img
|
||||
v-if="form.avatarUrl"
|
||||
class="admin-member-form__avatar h-20 w-20 rounded-full object-cover"
|
||||
:src="form.avatarUrl"
|
||||
:alt="pageTitle"
|
||||
>
|
||||
<span v-else class="admin-member-form__avatar flex h-20 w-20 items-center justify-center rounded-full bg-[#15171a] text-2xl font-semibold text-white">
|
||||
{{ memberInitial }}
|
||||
</span>
|
||||
<div class="admin-member-form__avatar-control group relative h-20 w-20 shrink-0">
|
||||
<button
|
||||
class="admin-member-form__avatar-button relative h-20 w-20 overflow-hidden rounded-full bg-[#15171a] text-white"
|
||||
type="button"
|
||||
:aria-label="form.avatarUrl ? '썸네일 변경' : '썸네일 등록'"
|
||||
@click="openAvatarFilePicker"
|
||||
>
|
||||
<img
|
||||
v-if="form.avatarUrl"
|
||||
class="admin-member-form__avatar h-full w-full object-cover"
|
||||
:src="form.avatarUrl"
|
||||
:alt="pageTitle"
|
||||
>
|
||||
<span v-else class="admin-member-form__avatar flex h-full w-full items-center justify-center text-2xl font-semibold">
|
||||
{{ memberInitial }}
|
||||
</span>
|
||||
<span class="admin-member-form__avatar-caption absolute inset-x-0 bottom-0 flex min-h-8 items-end justify-center bg-gradient-to-t from-black/80 via-black/35 to-transparent px-2 pb-2 text-center text-[11px] font-semibold leading-tight text-white opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100">
|
||||
{{ isUploadingAvatar ? '업로드 중' : form.avatarUrl ? '썸네일 변경' : '썸네일 등록' }}
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="form.avatarUrl"
|
||||
class="admin-member-form__avatar-remove absolute right-0 top-0 grid size-6 -translate-y-1 translate-x-1 place-items-center rounded-full bg-black/85 text-white opacity-0 shadow-sm transition hover:bg-[#d21a26] group-hover:opacity-100 group-focus-within:opacity-100"
|
||||
type="button"
|
||||
aria-label="썸네일 제거"
|
||||
@click.stop="removeAvatar"
|
||||
>
|
||||
<svg class="h-3 w-3" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M12.707 12L23.854.854a.5.5 0 00-.707-.707L12 11.293.854.146a.5.5 0 00-.707.707L11.293 12 .146 23.146a.5.5 0 00.708.708L12 12.707l11.146 11.146a.5.5 0 10.708-.706L12.707 12z" fill="currentColor" />
|
||||
</svg>
|
||||
</button>
|
||||
<input ref="avatarInputRef" class="sr-only" type="file" accept="image/*" @change="uploadAvatar">
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<h2 class="truncate text-lg font-semibold text-[#15171a]">{{ pageTitle }}</h2>
|
||||
<p class="mt-1 truncate text-sm text-[#657080]">{{ form.email || '이메일 없음' }}</p>
|
||||
@@ -284,11 +361,6 @@ watch(() => props.member, () => {
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label class="admin-member-form__field mt-5 block">
|
||||
<span class="admin-member-form__label mb-2 block text-sm font-semibold text-[#15171a]">썸네일 URL</span>
|
||||
<input v-model="form.avatarUrl" class="admin-member-form__input h-12 w-full rounded-md border border-transparent bg-[#eef1f4] px-4 text-sm text-[#15171a] outline-none focus:border-[#c5ccd5] focus:bg-white focus:ring-2 focus:ring-[#dce2e8]" type="text" maxlength="500" placeholder="/uploads/members/avatars/...">
|
||||
</label>
|
||||
|
||||
<label class="admin-member-form__field mt-5 block">
|
||||
<span class="admin-member-form__label mb-2 block text-sm font-semibold text-[#15171a]">레이블</span>
|
||||
<input v-model="form.labelsText" class="admin-member-form__input h-12 w-full rounded-md border border-transparent bg-[#eef1f4] px-4 text-sm text-[#15171a] outline-none focus:border-[#c5ccd5] focus:bg-white focus:ring-2 focus:ring-[#dce2e8]" type="text" placeholder="쉼표로 구분">
|
||||
|
||||
@@ -13,7 +13,7 @@ defineEmits(['stay', 'leave'])
|
||||
<Teleport to="body">
|
||||
<div
|
||||
v-if="open"
|
||||
class="admin-unsaved-modal fixed inset-0 z-[100] flex items-center justify-center bg-black/40 px-5 py-8"
|
||||
class="admin-unsaved-modal fixed inset-0 z-[100] flex items-start justify-center bg-black/40 px-5 pb-8 pt-10"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="admin-unsaved-modal-title"
|
||||
|
||||
Reference in New Issue
Block a user