This commit is contained in:
2025-09-16 18:42:55 +09:00
parent 39c9624774
commit 17aea8b43b
31 changed files with 1335 additions and 1327 deletions
@@ -30,9 +30,11 @@
<div class="tab-link active" onclick="openTab(event, 'myInfo')">내 정보</div>
<div class="tab-link" onclick="openTab(event, 'myPosts')">내가 쓴 글</div>
<div class="tab-link" onclick="openTab(event, 'myComments')">내가 쓴 댓글</div>
<div class="tab-link" onclick="openTab(event, 'myRanks')">내 게임 랭킹</div>
<th:block sec:authorize="hasRole('ADMIN')">
<div class="tab-link" onclick="openTab(event, 'userManagement')">회원 관리</div>
<div class="tab-link" onclick="openTab(event, 'contentManagement')">콘텐츠 관리</div>
<div class="tab-link" onclick="openTab(event, 'postManagement')">게시물 관리</div>
<div class="tab-link" onclick="openTab(event, 'bannerManagement')">배너 관리</div>
</th:block>
</div>
@@ -77,6 +79,32 @@
</div>
</div>
<div id="myRanks" class="tab-content">
<div class="box">
<ul class="post-list">
<li th:if="${#lists.isEmpty(myRanks)}">플레이한 게임 기록이 없습니다.</li>
<li th:each="rank : ${myRanks}">
<div>
<span th:switch="${rank.gameType.name()}">
<strong th:case="'GAME_2048'">2048</strong>
<strong th:case="'SUDOKU'">스도쿠</strong>
<strong th:case="'SPIDER'">스파이더 카드</strong>
<strong th:case="'NONOGRAM'">노노그램</strong>
<strong th:case="*">기타 게임</strong>
</span>
<span style="margin-left: 1em;" th:switch="${rank.gameType.name()}">
<span th:case="'SUDOKU'" th:text="|기록: ${rank.primaryScore / 60}분 ${rank.primaryScore % 60}초|"></span>
<span th:case="'NONOGRAM'" th:text="|기록: ${rank.primaryScore / 60}분 ${rank.primaryScore % 60}초|"></span>
<span th:case="'SPIDER'" th:text="|기록: ${rank.primaryScore} moves|"></span>
<span th:case="*" th:text="|점수: ${#numbers.formatInteger(rank.primaryScore, 3, 'COMMA')}|"></span>
</span>
</div>
<span th:text="${#temporals.format(rank.timestamp, 'yyyy-MM-dd HH:mm')}"></span>
</li>
</ul>
</div>
</div>
<div id="userManagement" class="tab-content" sec:authorize="hasRole('ADMIN')">
<div class="box">
<h4>권한 요청</h4>
@@ -105,7 +133,7 @@
</div>
</div>
<div id="contentManagement" class="tab-content" sec:authorize="hasRole('ADMIN')">
<div id="postManagement" class="tab-content" sec:authorize="hasRole('ADMIN')">
<div class="box">
<h4>최신 글 관리</h4>
<ul class="post-list">
@@ -120,6 +148,29 @@
</ul>
</div>
</div>
<div id="bannerManagement" class="tab-content" sec:authorize="hasRole('ADMIN')">
<div class="box">
<h4>배너 이미지 관리</h4>
<ul class="post-list">
<li th:each="image : ${allImages}" th:id="'image-row-' + ${image.id}">
<div style="display: flex; align-items: center; gap: 1em;">
<img th:src="@{'/api/images/' + ${image.fileName}}" alt="Image Thumbnail" style="width: 100px; height: 60px; object-fit: cover; border-radius: 4px;"/>
<div>
<strong th:text="${image.fileName}"></strong><br>
<span th:if="${image.isBannerCandidate}" style="color: #2a9d8f; font-weight: bold;">(배너로 사용 중)</span>
<span th:unless="${image.isBannerCandidate}" style="color: #888;">(배너로 사용 안 함)</span>
</div>
</div>
<div>
<button th:if="${!image.isBannerCandidate}" class="button small primary" th:onclick="handleBannerPermission('[[${image.id}]]', 'approve')">배너로 승인</button>
<button th:if="${image.isBannerCandidate}" class="button small alt" th:onclick="handleBannerPermission('[[${image.id}]]', 'revoke')">승인 해제</button>
</div>
</li>
<li th:if="${#lists.isEmpty(allImages)}">업로드된 이미지가 없습니다.</li>
</ul>
</div>
</div>
</div>
</section>
@@ -211,7 +262,8 @@
* @param {'block' | 'unblock'} action - 수행할 작업
*/
function handleContent(postId, action) {
const url = `/blog/post/${postId}/${action}`;
const cleanpostId = postId.replace(/^"|"$/g, '');
const url = `/blog/post/${cleanpostId}/${action}`;
fetch(url, {
method: 'POST',
headers: { [csrfHeader]: csrfToken }
@@ -227,6 +279,42 @@
})
.catch(error => console.error('Error:', error));
}
/**
* (관리자) 이미지의 배너 사용 권한을 승인하거나 해제하는 함수
* @param {string} imageId - 대상 이미지의 ID
* @param {'approve' | 'revoke'} action - 수행할 작업
*/
function handleBannerPermission(imageId, action) {
const cleanImageId = imageId.replace(/^"|"$/g, '');
// [수정] 깨끗하게 정리된 cleanImageId를 URL에 사용합니다.
const url = `/api/images/${cleanImageId}/${action === 'approve' ? 'approve-banner' : 'revoke-banner'}`;
fetch(url, {
method: 'POST',
headers: { [csrfHeader]: csrfToken }
})
.then(response => {
if (!response.ok) {
throw new Error('Server returned an error.');
}
return response.json();
})
.then(data => {
if (data && data.id) {
alert(`이미지 상태를 성공적으로 변경했습니다.`);
location.reload(); // 페이지를 새로고침하여 상태(버튼, 텍스트)를 즉시 반영
} else {
alert('작업에 실패했습니다.');
}
})
.catch(error => {
console.error('Error:', error);
alert('작업 중 오류가 발생했습니다.');
});
}
</script>
</th:block>
</html>