릴리스: v1.3.70 관리자 티어표 필터와 관리 액션 보강
This commit is contained in:
@@ -49,6 +49,7 @@ const customItemModalGameSort = ref('recent')
|
||||
|
||||
const adminTierLists = ref([])
|
||||
const adminTierListQuery = ref('')
|
||||
const adminTierListGameId = ref('')
|
||||
const adminTierListPage = ref(1)
|
||||
const adminTierListLimit = ref(50)
|
||||
const adminTierListTotal = ref(0)
|
||||
@@ -64,6 +65,7 @@ const importModalNewGameId = ref('')
|
||||
const importModalNewGameName = ref('')
|
||||
const previewModalOpen = ref(false)
|
||||
const previewTierList = ref(null)
|
||||
const adminTierListManageModalOpen = ref(false)
|
||||
const activeTemplateRequest = ref(null)
|
||||
const userEditModalOpen = ref(false)
|
||||
const userPasswordModalOpen = ref(false)
|
||||
@@ -81,6 +83,12 @@ const modalUserDraftIsAdmin = ref(false)
|
||||
const modalTargetCustomItem = ref(null)
|
||||
const customItemModalDraftLabel = ref('')
|
||||
const customItemModalLabelSaving = ref(false)
|
||||
const modalTargetAdminTierList = ref(null)
|
||||
const adminTierListDraftTitle = ref('')
|
||||
const adminTierListDraftDescription = ref('')
|
||||
const adminTierListDraftIsPublic = ref(false)
|
||||
const adminTierListSaving = ref(false)
|
||||
const adminTierListDeleting = ref(false)
|
||||
|
||||
const users = ref([])
|
||||
const userQuery = ref('')
|
||||
@@ -288,6 +296,7 @@ const isAnyModalOpen = computed(
|
||||
importModalOpen.value ||
|
||||
customItemModalOpen.value ||
|
||||
customItemDeleteModalOpen.value ||
|
||||
adminTierListManageModalOpen.value ||
|
||||
imageResetModalOpen.value ||
|
||||
previewModalOpen.value
|
||||
)
|
||||
@@ -422,6 +431,8 @@ watch(
|
||||
if (name === 'adminTierlists') {
|
||||
const nextMode = route.query.mode === 'all' ? 'all' : 'requests'
|
||||
if (tierlistsMode.value !== nextMode) tierlistsMode.value = nextMode
|
||||
const nextTierListGameId = typeof route.query.gameId === 'string' ? route.query.gameId : ''
|
||||
if (adminTierListGameId.value !== nextTierListGameId) adminTierListGameId.value = nextTierListGameId
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
@@ -447,7 +458,18 @@ watch(
|
||||
() => tierlistsMode.value,
|
||||
(mode) => {
|
||||
if (route.name !== 'adminTierlists') return
|
||||
syncAdminRouteQuery({ mode: mode === 'all' ? 'all' : undefined })
|
||||
syncAdminRouteQuery({
|
||||
mode: mode === 'all' ? 'all' : undefined,
|
||||
gameId: mode === 'all' && adminTierListGameId.value ? adminTierListGameId.value : undefined,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => adminTierListGameId.value,
|
||||
(gameId) => {
|
||||
if (route.name !== 'adminTierlists' || tierlistsMode.value !== 'all') return
|
||||
syncAdminRouteQuery({ gameId: gameId || undefined })
|
||||
}
|
||||
)
|
||||
|
||||
@@ -810,6 +832,7 @@ async function refreshAdminTierLists() {
|
||||
try {
|
||||
const data = await api.listAdminTierLists({
|
||||
q: adminTierListQuery.value,
|
||||
gameId: adminTierListGameId.value,
|
||||
page: adminTierListPage.value,
|
||||
limit: adminTierListLimit.value,
|
||||
})
|
||||
@@ -826,7 +849,7 @@ async function refreshAdminTierLists() {
|
||||
async function refreshAdminTierListStats() {
|
||||
if (!auth.user?.isAdmin) return
|
||||
try {
|
||||
const data = await api.getAdminTierListStats({ q: adminTierListQuery.value })
|
||||
const data = await api.getAdminTierListStats({ q: adminTierListQuery.value, gameId: adminTierListGameId.value })
|
||||
adminTierListStats.value = {
|
||||
total: data.total || 0,
|
||||
publicCount: data.publicCount || 0,
|
||||
@@ -1274,6 +1297,12 @@ function submitAdminTierListSearch() {
|
||||
refreshAdminTierLists()
|
||||
}
|
||||
|
||||
function setAdminTierListGameId(gameId) {
|
||||
adminTierListGameId.value = gameId || ''
|
||||
adminTierListPage.value = 1
|
||||
refreshAdminTierLists()
|
||||
}
|
||||
|
||||
function changeAdminTierListLimit(limit) {
|
||||
adminTierListLimit.value = limit
|
||||
adminTierListPage.value = 1
|
||||
@@ -1325,6 +1354,81 @@ function tierListVisibilityLabel(tierList) {
|
||||
return tierList.isPublic ? '공개' : '비공개'
|
||||
}
|
||||
|
||||
function openAdminTierListManageModal(tierList) {
|
||||
if (!tierList) return
|
||||
modalTargetAdminTierList.value = tierList
|
||||
adminTierListDraftTitle.value = tierList.title || ''
|
||||
adminTierListDraftDescription.value = tierList.description || ''
|
||||
adminTierListDraftIsPublic.value = !!tierList.isPublic
|
||||
adminTierListManageModalOpen.value = true
|
||||
}
|
||||
|
||||
function closeAdminTierListManageModal() {
|
||||
adminTierListManageModalOpen.value = false
|
||||
modalTargetAdminTierList.value = null
|
||||
adminTierListDraftTitle.value = ''
|
||||
adminTierListDraftDescription.value = ''
|
||||
adminTierListDraftIsPublic.value = false
|
||||
adminTierListSaving.value = false
|
||||
adminTierListDeleting.value = false
|
||||
}
|
||||
|
||||
async function saveAdminTierListMeta() {
|
||||
if (!modalTargetAdminTierList.value?.id || adminTierListSaving.value) return
|
||||
const nextTitle = adminTierListDraftTitle.value.trim()
|
||||
if (!nextTitle) {
|
||||
error.value = '티어표 제목을 입력해주세요.'
|
||||
return
|
||||
}
|
||||
|
||||
resetMessages()
|
||||
adminTierListSaving.value = true
|
||||
try {
|
||||
const data = await api.updateAdminTierList(modalTargetAdminTierList.value.id, {
|
||||
title: nextTitle,
|
||||
description: adminTierListDraftDescription.value.trim(),
|
||||
isPublic: !!adminTierListDraftIsPublic.value,
|
||||
})
|
||||
const updated = data.tierList
|
||||
adminTierLists.value = adminTierLists.value.map((tierList) => (tierList.id === updated.id ? { ...tierList, ...updated } : tierList))
|
||||
if (previewTierList.value?.id === updated.id) previewTierList.value = { ...previewTierList.value, ...updated }
|
||||
modalTargetAdminTierList.value = updated
|
||||
await Promise.all([refreshAdminTierListStats(), refreshSelectedGameTierListStats(selectedGame.value?.game?.id || '')])
|
||||
success.value = '티어표 정보를 수정했어요.'
|
||||
closeAdminTierListManageModal()
|
||||
} catch (e) {
|
||||
error.value = '티어표 정보 수정에 실패했어요.'
|
||||
} finally {
|
||||
adminTierListSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteAdminTierListEntry() {
|
||||
if (!modalTargetAdminTierList.value?.id || adminTierListDeleting.value) return
|
||||
const ok = window.confirm(`"${modalTargetAdminTierList.value.title}" 티어표를 삭제할까요? 이 작업은 되돌릴 수 없어요.`)
|
||||
if (!ok) return
|
||||
|
||||
resetMessages()
|
||||
adminTierListDeleting.value = true
|
||||
try {
|
||||
await api.deleteAdminTierList(modalTargetAdminTierList.value.id)
|
||||
adminTierLists.value = adminTierLists.value.filter((tierList) => tierList.id !== modalTargetAdminTierList.value.id)
|
||||
adminTierListTotal.value = Math.max(0, adminTierListTotal.value - 1)
|
||||
if (previewTierList.value?.id === modalTargetAdminTierList.value.id) previewTierList.value = null
|
||||
await Promise.all([refreshAdminTierListStats(), refreshSelectedGameTierListStats(selectedGame.value?.game?.id || '')])
|
||||
success.value = '티어표를 삭제했어요.'
|
||||
closeAdminTierListManageModal()
|
||||
if (!adminTierLists.value.length && adminTierListPage.value > 1) {
|
||||
adminTierListPage.value -= 1
|
||||
await refreshAdminTierLists()
|
||||
}
|
||||
} catch (e) {
|
||||
error.value = '티어표 삭제에 실패했어요.'
|
||||
} finally {
|
||||
adminTierListDeleting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openAdminTierList(tierList) {
|
||||
previewTierList.value = tierList
|
||||
previewModalOpen.value = true
|
||||
@@ -1634,6 +1738,7 @@ function userAvatarFallback(user) {
|
||||
:admin-tier-list-page-count="adminTierListPageCount"
|
||||
:admin-tier-list-total="adminTierListTotal"
|
||||
:admin-tier-list-stats="adminTierListStats"
|
||||
:open-admin-tier-list-manage-modal="openAdminTierListManageModal"
|
||||
:move-admin-tier-list-page="moveAdminTierListPage"
|
||||
/>
|
||||
|
||||
@@ -1921,6 +2026,39 @@ function userAvatarFallback(user) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="adminTierListManageModalOpen" class="modalOverlay" @click.self="closeAdminTierListManageModal">
|
||||
<div class="modalCard" role="dialog" aria-modal="true">
|
||||
<div class="modalCard__title">티어표 관리</div>
|
||||
<div class="modalCard__desc">
|
||||
{{ modalTargetAdminTierList ? `${modalTargetAdminTierList.gameName || modalTargetAdminTierList.gameId} · ${tierListAuthorDisplayName(modalTargetAdminTierList)}` : '' }}
|
||||
</div>
|
||||
<div class="modalCard__form">
|
||||
<label class="field">
|
||||
<span class="field__label">제목</span>
|
||||
<input v-model="adminTierListDraftTitle" class="field__input" maxlength="120" placeholder="티어표 제목" />
|
||||
</label>
|
||||
<label class="field">
|
||||
<span class="field__label">설명</span>
|
||||
<textarea v-model="adminTierListDraftDescription" class="field__input field__input--textarea" rows="4" maxlength="500" placeholder="설명 수정"></textarea>
|
||||
</label>
|
||||
<label class="toggleSwitch">
|
||||
<input v-model="adminTierListDraftIsPublic" type="checkbox" />
|
||||
<span class="toggleSwitch__label">{{ adminTierListDraftIsPublic ? '공개 상태' : '비공개 상태' }}</span>
|
||||
<span class="toggleSwitch__track"><span class="toggleSwitch__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="modalCard__actions">
|
||||
<button class="btn btn--ghost" @click="closeAdminTierListManageModal">취소</button>
|
||||
<button class="btn btn--danger" :disabled="adminTierListDeleting" @click="deleteAdminTierListEntry">
|
||||
{{ adminTierListDeleting ? '삭제중...' : '삭제' }}
|
||||
</button>
|
||||
<button class="btn btn--primary" :disabled="adminTierListSaving || !adminTierListDraftTitle.trim()" @click="saveAdminTierListMeta">
|
||||
{{ adminTierListSaving ? '저장중...' : '저장' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="imageResetModalOpen" class="modalOverlay" @click.self="closeImageResetModal">
|
||||
<div class="modalCard" role="dialog" aria-modal="true">
|
||||
<div class="modalCard__title">최적화 기록 비우기</div>
|
||||
@@ -2110,6 +2248,10 @@ function userAvatarFallback(user) {
|
||||
@keydown.enter.prevent="submitAdminTierListSearch"
|
||||
/>
|
||||
<button class="btn btn--ghost" @click="submitAdminTierListSearch">검색</button>
|
||||
<select :value="adminTierListGameId" class="select" @change="setAdminTierListGameId($event.target.value)">
|
||||
<option value="">모든 게임</option>
|
||||
<option v-for="game in games" :key="game.id" :value="game.id">{{ game.name }}</option>
|
||||
</select>
|
||||
<select :value="adminTierListLimit" class="select" @change="changeAdminTierListLimit(Number($event.target.value))">
|
||||
<option :value="50">50개씩 보기</option>
|
||||
<option :value="200">200개씩 보기</option>
|
||||
@@ -4038,6 +4180,16 @@ function userAvatarFallback(user) {
|
||||
background: rgba(251, 191, 36, 0.12);
|
||||
color: rgba(253, 230, 138, 0.96);
|
||||
}
|
||||
.adminUiScope .pill--public {
|
||||
border-color: rgba(52, 211, 153, 0.34);
|
||||
background: rgba(52, 211, 153, 0.14);
|
||||
color: rgba(209, 250, 229, 0.98);
|
||||
}
|
||||
.adminUiScope .pill--private {
|
||||
border-color: rgba(251, 191, 36, 0.32);
|
||||
background: rgba(251, 191, 36, 0.12);
|
||||
color: rgba(253, 230, 138, 0.96);
|
||||
}
|
||||
.adminUiScope .pill--link {
|
||||
color: var(--theme-text);
|
||||
cursor: pointer;
|
||||
|
||||
Reference in New Issue
Block a user