공개 상단 어나운스 바와 관리자 맞춤 설정을 추가하고, 스팸 필터에서 가입 금지 닉네임을 관리·검증한다. POST 설정 읽기 모드 비활성 토글과 설정 내비 아이콘 틀을 반영한다. Co-authored-by: Cursor <cursoragent@cursor.com>
117 lines
3.2 KiB
Vue
117 lines
3.2 KiB
Vue
<script setup>
|
|
import {
|
|
getAnnouncementBarTextColor,
|
|
normalizeAnnouncementUrl
|
|
} from '~/lib/announcement-bar.js'
|
|
|
|
const DISMISS_STORAGE_KEY = 'SITE_ANNOUNCEMENT_DISMISSED_AT'
|
|
|
|
const { data: siteSettings } = await useFetch('/api/site-settings', {
|
|
default: () => ({
|
|
announcementEnabled: false,
|
|
announcementText: '',
|
|
announcementUrl: '',
|
|
announcementBackgroundColor: '#15171a',
|
|
updatedAt: null
|
|
})
|
|
})
|
|
|
|
const dismissed = ref(false)
|
|
|
|
/**
|
|
* 어나운스 바 노출 여부
|
|
* @returns {boolean} 노출 여부
|
|
*/
|
|
const isVisible = computed(() => {
|
|
if (!siteSettings.value?.announcementEnabled) {
|
|
return false
|
|
}
|
|
|
|
const text = (siteSettings.value?.announcementText || '').trim()
|
|
if (!text) {
|
|
return false
|
|
}
|
|
|
|
return !dismissed.value
|
|
})
|
|
|
|
const announcementText = computed(() => (siteSettings.value?.announcementText || '').trim())
|
|
|
|
const announcementLink = computed(() => normalizeAnnouncementUrl(siteSettings.value?.announcementUrl || ''))
|
|
|
|
const barStyle = computed(() => {
|
|
const backgroundColor = siteSettings.value?.announcementBackgroundColor || '#15171a'
|
|
return {
|
|
backgroundColor,
|
|
color: getAnnouncementBarTextColor(backgroundColor)
|
|
}
|
|
})
|
|
|
|
/**
|
|
* 로컬에 저장된 닫기 상태를 반영한다.
|
|
* @returns {void}
|
|
*/
|
|
const syncDismissedFromStorage = () => {
|
|
if (!import.meta.client) {
|
|
return
|
|
}
|
|
|
|
const stored = localStorage.getItem(DISMISS_STORAGE_KEY)
|
|
const updatedAt = siteSettings.value?.updatedAt || ''
|
|
dismissed.value = Boolean(stored && stored === updatedAt)
|
|
}
|
|
|
|
/**
|
|
* 어나운스 바를 닫는다.
|
|
* @returns {void}
|
|
*/
|
|
const dismissAnnouncement = () => {
|
|
dismissed.value = true
|
|
if (!import.meta.client) {
|
|
return
|
|
}
|
|
|
|
localStorage.setItem(DISMISS_STORAGE_KEY, siteSettings.value?.updatedAt || '')
|
|
}
|
|
|
|
watch(() => siteSettings.value?.updatedAt, syncDismissedFromStorage)
|
|
|
|
onMounted(() => {
|
|
syncDismissedFromStorage()
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
v-if="isVisible"
|
|
class="site-announcement-bar relative z-30 min-h-9 w-full text-center text-sm font-medium"
|
|
:style="barStyle"
|
|
role="region"
|
|
aria-label="사이트 공지"
|
|
>
|
|
<div class="site-announcement-bar__inner relative mx-auto flex max-w-[1294px] items-center justify-center px-10 py-2.5 lg:px-12">
|
|
<component
|
|
:is="announcementLink ? 'a' : 'span'"
|
|
class="site-announcement-bar__text line-clamp-2"
|
|
:class="announcementLink ? 'hover:underline' : ''"
|
|
:href="announcementLink || undefined"
|
|
:target="announcementLink ? '_blank' : undefined"
|
|
:rel="announcementLink ? 'noreferrer' : undefined"
|
|
>
|
|
{{ announcementText }}
|
|
</component>
|
|
<button
|
|
class="site-announcement-bar__close absolute top-1/2 right-3 inline-flex size-7 -translate-y-1/2 items-center justify-center rounded-full opacity-80 transition hover:opacity-100 lg:right-4"
|
|
type="button"
|
|
aria-label="공지 닫기"
|
|
@click="dismissAnnouncement"
|
|
>
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
<path d="M18 6 6 18" />
|
|
<path d="m6 6 12 12" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</template>
|