게시물 OG 이미지 설정 추가

This commit is contained in:
2026-05-03 10:10:09 +09:00
parent fc5f41b9cc
commit 8c5ccc94ec
14 changed files with 157 additions and 14 deletions

View File

@@ -21,8 +21,10 @@ const slugTouched = ref(Boolean(props.initialPost.slug))
const blockEditor = ref(null)
const mediaItems = ref([])
const isMediaPickerOpen = ref(false)
const mediaPickerTarget = ref('featuredImage')
const isLoadingMedia = ref(false)
const isUploadingFeaturedImage = ref(false)
const isUploadingOgImage = ref(false)
const autosaveTimer = ref(null)
const autosaveNotice = ref(null)
const autosaveStatus = ref('')
@@ -78,6 +80,7 @@ const form = reactive({
seoDescription: props.initialPost.seoDescription || '',
canonicalUrl: props.initialPost.canonicalUrl || '',
noindex: Boolean(props.initialPost.noindex),
ogImage: props.initialPost.ogImage || '',
status: props.initialPost.status || 'draft',
publishedAt: toDateTimeLocalValue(props.initialPost.publishedAt),
tagsText: props.initialPost.tags?.join(', ') || ''
@@ -152,6 +155,7 @@ const createPostPayload = () => {
seoDescription: form.seoDescription.trim(),
canonicalUrl: form.canonicalUrl.trim(),
noindex: form.noindex,
ogImage: form.ogImage.trim() || null,
status: form.status,
publishedAt,
tags: parseTags(form.tagsText)
@@ -172,6 +176,7 @@ const createAutosavePayload = () => ({
seoDescription: form.seoDescription,
canonicalUrl: form.canonicalUrl,
noindex: form.noindex,
ogImage: form.ogImage,
status: form.status,
publishedAt: form.publishedAt,
tagsText: form.tagsText
@@ -188,6 +193,10 @@ const isEmptyAutosavePayload = (payload) => ![
payload.excerpt,
payload.content,
payload.featuredImage,
payload.seoTitle,
payload.seoDescription,
payload.canonicalUrl,
payload.ogImage,
payload.tagsText
].some((value) => String(value || '').trim())
@@ -293,7 +302,8 @@ const fetchMediaItems = async () => {
* 대표 이미지 선택 창 열기
* @returns {Promise<void>}
*/
const openMediaPicker = async () => {
const openMediaPicker = async (target = 'featuredImage') => {
mediaPickerTarget.value = target
isMediaPickerOpen.value = true
await fetchMediaItems()
}
@@ -311,8 +321,8 @@ const closeMediaPicker = () => {
* @param {Object} item - 미디어 항목
* @returns {void}
*/
const selectFeaturedImage = (item) => {
form.featuredImage = item.url
const selectPickedImage = (item) => {
form[mediaPickerTarget.value] = item.url
closeMediaPicker()
}
@@ -324,6 +334,14 @@ const removeFeaturedImage = () => {
form.featuredImage = ''
}
/**
* OG 이미지 삭제
* @returns {void}
*/
const removeOgImage = () => {
form.ogImage = ''
}
/**
* 대표 이미지 파일 업로드
* @param {Event} event - 파일 입력 이벤트
@@ -352,6 +370,34 @@ const uploadFeaturedImage = async (event) => {
}
}
/**
* OG 이미지 파일 업로드
* @param {Event} event - 파일 입력 이벤트
* @returns {Promise<void>}
*/
const uploadOgImage = async (event) => {
const files = event.target.files
if (!files?.length) {
return
}
const formData = new FormData()
formData.append('files', files[0])
isUploadingOgImage.value = true
try {
const result = await $fetch('/admin/api/uploads', {
method: 'POST',
body: formData
})
form.ogImage = result.files?.[0]?.url || ''
} finally {
event.target.value = ''
isUploadingOgImage.value = false
}
}
/**
* 제목 입력 후 본문 에디터로 이동
* @returns {void}
@@ -557,7 +603,7 @@ defineExpose({
{{ form.featuredImage }}
</p>
<div class="admin-post-form__featured-buttons flex flex-wrap gap-2">
<button class="admin-post-form__featured-change rounded border border-line px-3 py-1.5 text-xs font-semibold" type="button" @click="openMediaPicker">
<button class="admin-post-form__featured-change rounded border border-line px-3 py-1.5 text-xs font-semibold" type="button" @click="openMediaPicker('featuredImage')">
변경
</button>
<label class="admin-post-form__featured-reupload cursor-pointer rounded border border-line px-3 py-1.5 text-xs font-semibold">
@@ -580,6 +626,39 @@ defineExpose({
</label>
</div>
</div>
<div class="admin-post-form__field grid gap-2 text-sm">
<span class="admin-post-form__label font-medium">OG 이미지</span>
<figure v-if="form.ogImage" class="admin-post-form__og-image overflow-hidden rounded border border-line bg-white">
<img class="admin-post-form__og-preview aspect-[1.91/1] w-full bg-surface object-cover" :src="form.ogImage" alt="">
<figcaption class="admin-post-form__og-actions grid gap-2 p-3">
<p class="admin-post-form__og-url break-all text-xs text-muted">
{{ form.ogImage }}
</p>
<div class="admin-post-form__og-buttons flex flex-wrap gap-2">
<button class="admin-post-form__og-change rounded border border-line px-3 py-1.5 text-xs font-semibold" type="button" @click="openMediaPicker('ogImage')">
변경
</button>
<label class="admin-post-form__og-reupload cursor-pointer rounded border border-line px-3 py-1.5 text-xs font-semibold">
업로드
<input class="sr-only" type="file" accept="image/*" @change="uploadOgImage">
</label>
<button class="admin-post-form__og-remove rounded border border-red-200 px-3 py-1.5 text-xs font-semibold text-red-700" type="button" @click="removeOgImage">
삭제
</button>
</div>
</figcaption>
</figure>
<div v-else class="admin-post-form__og-empty grid gap-2 rounded border border-dashed border-line bg-white p-4">
<button class="admin-post-form__og-select rounded border border-line px-3 py-2 text-sm font-semibold" type="button" @click="openMediaPicker('ogImage')">
미디어에서 선택
</button>
<label class="admin-post-form__og-upload cursor-pointer rounded bg-[#15171a] px-3 py-2 text-center text-sm font-semibold text-white">
{{ isUploadingOgImage ? '업로드 중' : '새 이미지 업로드' }}
<input class="sr-only" type="file" accept="image/*" @change="uploadOgImage">
</label>
</div>
</div>
</aside>
</div>
@@ -609,7 +688,7 @@ defineExpose({
<section class="admin-post-form__media-picker-panel max-h-[80vh] w-full max-w-4xl overflow-hidden bg-white text-ink shadow-xl">
<div class="admin-post-form__media-picker-header flex items-center justify-between border-b border-line px-5 py-4">
<h2 class="admin-post-form__media-picker-title text-lg font-semibold">
대표 이미지 선택
{{ mediaPickerTarget === 'ogImage' ? 'OG 이미지 선택' : '대표 이미지 선택' }}
</h2>
<button class="admin-post-form__media-picker-close rounded border border-line px-3 py-1.5 text-sm font-semibold" type="button" @click="closeMediaPicker">
닫기
@@ -625,7 +704,7 @@ defineExpose({
:key="item.url"
class="admin-post-form__media-picker-item overflow-hidden border border-line bg-white text-left"
type="button"
@click="selectFeaturedImage(item)"
@click="selectPickedImage(item)"
>
<img class="admin-post-form__media-picker-image aspect-square w-full bg-surface object-cover" :src="item.url" :alt="item.title">
<span class="admin-post-form__media-picker-name block truncate px-2 py-1.5 text-xs font-semibold text-ink">{{ item.name }}</span>