권한 UI와 글 목록 검색 보정 v1.5.10
This commit is contained in:
@@ -14,6 +14,7 @@ const statusFilter = ref('all')
|
||||
const tagFilter = ref('all')
|
||||
const featuredFilter = ref('all')
|
||||
const sortOrder = ref('newest')
|
||||
const searchQuery = ref('')
|
||||
const postMenuTriggerRefs = new Map()
|
||||
|
||||
const { data: posts, refresh } = await useFetch('/admin/api/posts', {
|
||||
@@ -205,7 +206,35 @@ const featuredPostCount = computed(() => posts.value.filter((post) => post.isFea
|
||||
/** 필터가 적용됐는지 */
|
||||
const hasActiveListFilters = computed(() => statusFilter.value !== 'all'
|
||||
|| tagFilter.value !== 'all'
|
||||
|| featuredFilter.value !== 'all')
|
||||
|| featuredFilter.value !== 'all'
|
||||
|| Boolean(searchQuery.value.trim()))
|
||||
|
||||
/**
|
||||
* 게시물이 검색어와 일치하는지 확인한다.
|
||||
* @param {Object} post - 게시물
|
||||
* @param {string} query - 검색어
|
||||
* @returns {boolean} 일치 여부
|
||||
*/
|
||||
const doesPostMatchSearch = (post, query) => {
|
||||
const keyword = query.trim().toLowerCase()
|
||||
if (!keyword) {
|
||||
return true
|
||||
}
|
||||
|
||||
const tagText = (post.tags || [])
|
||||
.map((tag) => `${tag} ${getTagName(tag)}`)
|
||||
.join(' ')
|
||||
|
||||
const haystack = [
|
||||
post.title,
|
||||
post.slug,
|
||||
post.excerpt,
|
||||
post.content,
|
||||
tagText
|
||||
].join(' ').toLowerCase()
|
||||
|
||||
return haystack.includes(keyword)
|
||||
}
|
||||
|
||||
const filteredPosts = computed(() => {
|
||||
const filtered = posts.value.filter((post) => {
|
||||
@@ -213,8 +242,9 @@ const filteredPosts = computed(() => {
|
||||
const matchesTag = tagFilter.value === 'all' || (post.tags || []).includes(tagFilter.value)
|
||||
const matchesFeatured = featuredFilter.value === 'all'
|
||||
|| (featuredFilter.value === 'featured' && post.isFeatured)
|
||||
const matchesSearch = doesPostMatchSearch(post, searchQuery.value)
|
||||
|
||||
return matchesStatus && matchesTag && matchesFeatured
|
||||
return matchesStatus && matchesTag && matchesFeatured && matchesSearch
|
||||
})
|
||||
|
||||
return [...filtered].sort((a, b) => {
|
||||
@@ -424,39 +454,64 @@ watch(openPostMenuId, async (postId) => {
|
||||
</div>
|
||||
<div class="admin-posts__header-actions flex min-w-0 flex-1 flex-wrap items-center justify-start gap-2 lg:flex-nowrap lg:justify-end">
|
||||
<div class="admin-posts__filters flex min-w-0 flex-wrap items-center gap-2">
|
||||
<label class="admin-posts__search relative">
|
||||
<span class="sr-only">글 검색</span>
|
||||
<svg class="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-[#8e9cac]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="m21 21-4.34-4.34" />
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
</svg>
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
class="admin-posts__search-input h-10 w-52 rounded border border-line bg-white pl-9 pr-3 text-sm text-[#394047] outline-none transition-colors placeholder:text-[#8e9cac] hover:border-[#c8ced3] focus:border-[#8e9cac]"
|
||||
type="search"
|
||||
placeholder="글 검색"
|
||||
>
|
||||
</label>
|
||||
<label class="admin-posts__filter">
|
||||
<span class="sr-only">상태 필터</span>
|
||||
<select v-model="statusFilter" class="admin-posts__filter-select h-10 rounded border border-line bg-white px-3 text-sm text-[#394047] outline-none transition-colors hover:border-[#c8ced3] focus:border-[#8e9cac]">
|
||||
<span class="admin-posts__filter-select-wrap relative block">
|
||||
<select v-model="statusFilter" class="admin-posts__filter-select h-10 appearance-none rounded border border-line bg-white pl-3 pr-9 text-sm text-[#394047] outline-none transition-colors hover:border-[#c8ced3] focus:border-[#8e9cac]">
|
||||
<option value="all">전체 상태</option>
|
||||
<option value="published">발행</option>
|
||||
<option value="draft">초안</option>
|
||||
<option value="scheduled">예약</option>
|
||||
<option value="members">멤버십</option>
|
||||
<option value="private">비공개</option>
|
||||
</select>
|
||||
</select>
|
||||
<svg class="pointer-events-none absolute right-3 top-1/2 size-4 -translate-y-1/2 text-[#394047]" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</span>
|
||||
</label>
|
||||
<label class="admin-posts__filter">
|
||||
<span class="sr-only">태그 필터</span>
|
||||
<select v-model="tagFilter" class="admin-posts__filter-select h-10 rounded border border-line bg-white px-3 text-sm text-[#394047] outline-none transition-colors hover:border-[#c8ced3] focus:border-[#8e9cac]">
|
||||
<span class="admin-posts__filter-select-wrap relative block">
|
||||
<select v-model="tagFilter" class="admin-posts__filter-select h-10 appearance-none rounded border border-line bg-white pl-3 pr-9 text-sm text-[#394047] outline-none transition-colors hover:border-[#c8ced3] focus:border-[#8e9cac]">
|
||||
<option value="all">전체 태그</option>
|
||||
<option v-for="tag in usedTagSlugs" :key="tag" :value="tag">
|
||||
{{ getTagName(tag) }}
|
||||
</option>
|
||||
</select>
|
||||
</select>
|
||||
<svg class="pointer-events-none absolute right-3 top-1/2 size-4 -translate-y-1/2 text-[#394047]" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</span>
|
||||
</label>
|
||||
<label class="admin-posts__filter">
|
||||
<span class="sr-only">추천 필터</span>
|
||||
<select v-model="featuredFilter" class="admin-posts__filter-select h-10 rounded border border-line bg-white px-3 text-sm text-[#394047] outline-none transition-colors hover:border-[#c8ced3] focus:border-[#8e9cac]">
|
||||
<span class="admin-posts__filter-select-wrap relative block">
|
||||
<select v-model="featuredFilter" class="admin-posts__filter-select h-10 appearance-none rounded border border-line bg-white pl-3 pr-9 text-sm text-[#394047] outline-none transition-colors hover:border-[#c8ced3] focus:border-[#8e9cac]">
|
||||
<option value="all">전체 글</option>
|
||||
<option value="featured">추천만</option>
|
||||
</select>
|
||||
</select>
|
||||
<svg class="pointer-events-none absolute right-3 top-1/2 size-4 -translate-y-1/2 text-[#394047]" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</span>
|
||||
</label>
|
||||
<label class="admin-posts__filter">
|
||||
<span class="sr-only">정렬</span>
|
||||
<select v-model="sortOrder" class="admin-posts__filter-select h-10 rounded border border-line bg-white px-3 text-sm text-[#394047] outline-none transition-colors hover:border-[#c8ced3] focus:border-[#8e9cac]">
|
||||
<span class="admin-posts__filter-select-wrap relative block">
|
||||
<select v-model="sortOrder" class="admin-posts__filter-select h-10 appearance-none rounded border border-line bg-white pl-3 pr-9 text-sm text-[#394047] outline-none transition-colors hover:border-[#c8ced3] focus:border-[#8e9cac]">
|
||||
<option value="newest">최신순</option>
|
||||
<option value="oldest">오래된순</option>
|
||||
</select>
|
||||
</select>
|
||||
<svg class="pointer-events-none absolute right-3 top-1/2 size-4 -translate-y-1/2 text-[#394047]" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<NuxtLink class="admin-posts__new shrink-0 rounded bg-[#15171a] px-4 py-2 text-sm font-semibold text-white" to="/admin/posts/new">
|
||||
@@ -477,6 +532,9 @@ watch(openPostMenuId, async (postId) => {
|
||||
<span class="sr-only">추천</span>
|
||||
<span class="inline-flex size-4 items-center justify-center text-[#f5a623]" aria-hidden="true">★</span>
|
||||
</th>
|
||||
<th class="admin-posts__cell admin-posts__cell-thumbnail w-16 px-2 py-3">
|
||||
<span class="sr-only">썸네일</span>
|
||||
</th>
|
||||
<th class="admin-posts__cell px-4 py-3">제목</th>
|
||||
<th class="admin-posts__cell px-4 py-3">상태</th>
|
||||
<th class="admin-posts__cell px-4 py-3">태그</th>
|
||||
@@ -500,6 +558,17 @@ watch(openPostMenuId, async (postId) => {
|
||||
</svg>
|
||||
</span>
|
||||
</td>
|
||||
<td class="admin-posts__cell admin-posts__cell-thumbnail w-16 px-2 py-4">
|
||||
<span class="admin-posts__thumbnail block h-9 w-12 overflow-hidden rounded bg-[#eef1f4]">
|
||||
<img
|
||||
v-if="post.featuredImage"
|
||||
class="h-full w-full object-cover"
|
||||
:src="post.featuredImage"
|
||||
:alt="post.title || '게시물 썸네일'"
|
||||
loading="lazy"
|
||||
>
|
||||
</span>
|
||||
</td>
|
||||
<td class="admin-posts__cell px-4 py-4">
|
||||
<div class="admin-posts__title-row flex flex-wrap items-center gap-x-2 gap-y-1">
|
||||
<NuxtLink class="admin-posts__title-link font-semibold hover:opacity-70" :to="`/admin/posts/${post.id}`">
|
||||
|
||||
Reference in New Issue
Block a user