Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e70e685a06 | |||
| 09acebc2d5 | |||
| e3391b5f07 |
@@ -1,5 +1,15 @@
|
|||||||
# 업데이트 로그
|
# 업데이트 로그
|
||||||
|
|
||||||
|
## 2026-04-01 v1.3.22
|
||||||
|
- 내 티어표 카드는 게임 목록과 같은 상단 히어로/패널 문법으로 다시 맞추고, 깨진 썸네일은 alt 텍스트가 카드 폭을 밀지 않도록 플레이스홀더로 즉시 대체해 카드 수와 헤더 폭이 흔들리지 않게 보정함.
|
||||||
|
- 오른쪽 사이드 광고 프레임은 별도 보더·패딩·배경을 제거해, 광고 자체가 가진 각진 형태와 색이 그대로 보이도록 더 담백하게 정리함.
|
||||||
|
|
||||||
|
## 2026-04-01 v1.3.21
|
||||||
|
- 내 티어표 카드는 게임 목록 화면과 같은 카드 폭/헤더/메타 배치 문법으로 맞춰, 화면 간 카드 크기와 정보 정렬이 더 통일된 인상으로 보이도록 정리함.
|
||||||
|
|
||||||
|
## 2026-04-01 v1.3.20
|
||||||
|
- 내 티어표 카드 그리드는 카드 최대폭 우선 규칙 대신 더 촘촘한 auto-fill 기준으로 조정해, 넓은 화면에서도 한 줄에 더 많은 카드가 자연스럽게 배치되도록 보정함.
|
||||||
|
|
||||||
## 2026-04-01 v1.3.19
|
## 2026-04-01 v1.3.19
|
||||||
- 관리자 Image Optimization 기간 선택은 연도/월을 가로로 나란히 두고, 연도를 고르기 전에는 월 셀렉트를 숨겨 비어 있는 박스처럼 보이던 상태를 없앰.
|
- 관리자 Image Optimization 기간 선택은 연도/월을 가로로 나란히 두고, 연도를 고르기 전에는 월 셀렉트를 숨겨 비어 있는 박스처럼 보이던 상태를 없앰.
|
||||||
- 전체 초기화 버튼도 실제 월이 선택된 경우에만 보이도록 정리해, 사이드바 상단 필터 줄이 더 단정하게 보이도록 보정함.
|
- 전체 초기화 버튼도 실제 월이 선택된 경우에만 보이도록 정리해, 사이드바 상단 필터 줄이 더 단정하게 보이도록 보정함.
|
||||||
|
|||||||
@@ -78,14 +78,10 @@ onMounted(async () => {
|
|||||||
|
|
||||||
.rightRailAd__frame {
|
.rightRailAd__frame {
|
||||||
min-height: 520px;
|
min-height: 520px;
|
||||||
padding: 14px;
|
|
||||||
border-radius: 20px;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
||||||
background: rgba(255, 255, 255, 0.02);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.rightRailAd__slot {
|
.rightRailAd__slot {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 490px;
|
min-height: 520px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const router = useRouter()
|
|||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const myLists = ref([])
|
const myLists = ref([])
|
||||||
const error = ref('')
|
const error = ref('')
|
||||||
|
const brokenThumbnailIds = ref({})
|
||||||
|
|
||||||
watch(error, (message) => {
|
watch(error, (message) => {
|
||||||
if (!message) return
|
if (!message) return
|
||||||
@@ -37,12 +38,19 @@ function avatarFallbackOf(tierList) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function tierListThumbnailUrl(tierList) {
|
function tierListThumbnailUrl(tierList) {
|
||||||
|
if (!tierList?.id || brokenThumbnailIds.value[tierList.id]) return ''
|
||||||
return tierList.thumbnailSrc ? toApiUrl(tierList.thumbnailSrc) : ''
|
return tierList.thumbnailSrc ? toApiUrl(tierList.thumbnailSrc) : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleThumbnailError(tierListId) {
|
||||||
|
if (!tierListId || brokenThumbnailIds.value[tierListId]) return
|
||||||
|
brokenThumbnailIds.value = { ...brokenThumbnailIds.value, [tierListId]: true }
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
const data = await api.listMyTierLists()
|
const data = await api.listMyTierLists()
|
||||||
|
brokenThumbnailIds.value = {}
|
||||||
myLists.value = data.tierLists || []
|
myLists.value = data.tierLists || []
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error('로그인이 필요해요.')
|
toast.error('로그인이 필요해요.')
|
||||||
@@ -51,53 +59,87 @@ onMounted(async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
function openList(t) {
|
function openList(t) {
|
||||||
router.push(`/editor/${t.gameId}/${t.id}`)
|
router.push(
|
||||||
|
"/editor/" + t.gameId + "/" + t.id,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="pageWrap">
|
<section class="dashboardHero">
|
||||||
<header class="pageHead">
|
<div class="dashboardHero__left">
|
||||||
<div class="pageHead__main">
|
<div class="dashboardHero__eyebrow">Library</div>
|
||||||
<div class="pageHead__eyebrow">Library</div>
|
<h2 class="dashboardHero__title">내 티어표</h2>
|
||||||
<h2 class="pageHead__title">내 티어표</h2>
|
<p class="dashboardHero__desc">직접 저장한 티어표를 같은 카드 레이아웃으로 다시 열고 정리할 수 있어요.</p>
|
||||||
<div class="pageHead__desc">직접 저장한 티어표를 같은 카드 레이아웃으로 다시 열고 정리할 수 있어요.</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</header>
|
|
||||||
<div class="card">
|
<section class="panel">
|
||||||
<div v-if="myLists.length === 0" class="empty">아직 저장한 티어표가 없어요.</div>
|
<div v-if="myLists.length === 0" class="empty">아직 저장한 티어표가 없어요.</div>
|
||||||
<div v-else class="list">
|
<div v-else class="list">
|
||||||
<article v-for="t in myLists" :key="t.id" class="boardCard">
|
<article v-for="t in myLists" :key="t.id" class="boardCard">
|
||||||
<button class="boardCard__body" @click="openList(t)">
|
<button class="boardCard__body" @click="openList(t)">
|
||||||
<div class="boardCard__thumbWrap">
|
<div class="boardCard__thumbWrap">
|
||||||
<img v-if="tierListThumbnailUrl(t)" class="boardCard__thumb" :src="tierListThumbnailUrl(t)" :alt="t.title" />
|
<img
|
||||||
<div v-else class="boardCard__thumbPlaceholder">대표 썸네일</div>
|
v-if="tierListThumbnailUrl(t)"
|
||||||
|
class="boardCard__thumb"
|
||||||
|
:src="tierListThumbnailUrl(t)"
|
||||||
|
alt=""
|
||||||
|
@error="handleThumbnailError(t.id)"
|
||||||
|
/>
|
||||||
|
<div v-else class="boardCard__thumbPlaceholder">대표 썸네일</div>
|
||||||
|
</div>
|
||||||
|
<div class="boardCard__head">
|
||||||
|
<div class="boardCard__titleRow">
|
||||||
|
<div class="boardCard__title">{{ t.title }}</div>
|
||||||
|
<div class="favoriteStat">♡ {{ t.favoriteCount || 0 }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="boardCard__head">
|
<div class="boardCard__metaRow">
|
||||||
<div class="boardCard__titleRow">
|
<div class="boardCard__author">
|
||||||
<div class="boardCard__title">{{ t.title }}</div>
|
<img v-if="avatarSrcOf(t)" class="boardCard__avatar" :src="avatarSrcOf(t)" :alt="displayNameOf(t)" />
|
||||||
<div class="favoriteStat">♡ {{ t.favoriteCount || 0 }}</div>
|
<div v-else class="boardCard__avatar boardCard__avatar--fallback">{{ avatarFallbackOf(t) }}</div>
|
||||||
</div>
|
<span class="boardCard__authorName">{{ displayNameOf(t) }}</span>
|
||||||
<div class="boardCard__metaRow">
|
|
||||||
<div class="boardCard__author">
|
|
||||||
<img v-if="avatarSrcOf(t)" class="boardCard__avatar" :src="avatarSrcOf(t)" :alt="displayNameOf(t)" />
|
|
||||||
<div v-else class="boardCard__avatar boardCard__avatar--fallback">{{ avatarFallbackOf(t) }}</div>
|
|
||||||
<span class="boardCard__authorName">{{ displayNameOf(t) }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="boardCard__date">{{ fmt(t.updatedAt) }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="boardCard__date">{{ fmt(t.updatedAt) }}</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</div>
|
||||||
</article>
|
</button>
|
||||||
</div>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.card {
|
.dashboardHero {
|
||||||
border: 0;
|
display: flex;
|
||||||
|
gap: 18px;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding: 6px 2px 18px;
|
||||||
|
}
|
||||||
|
.dashboardHero__left {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.dashboardHero__eyebrow {
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(255, 255, 255, 0.42);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
.dashboardHero__title {
|
||||||
|
margin: 4px 0 6px;
|
||||||
|
font-size: 32px;
|
||||||
|
letter-spacing: -0.04em;
|
||||||
|
color: rgba(255, 255, 255, 0.96);
|
||||||
|
}
|
||||||
|
.dashboardHero__desc {
|
||||||
|
margin: 0;
|
||||||
|
color: rgba(255, 255, 255, 0.58);
|
||||||
|
max-width: 720px;
|
||||||
|
}
|
||||||
|
.panel {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -112,7 +154,6 @@ function openList(t) {
|
|||||||
gap: 18px;
|
gap: 18px;
|
||||||
}
|
}
|
||||||
.boardCard {
|
.boardCard {
|
||||||
display: grid;
|
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
border-radius: 22px;
|
border-radius: 22px;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.16);
|
border: 1px solid rgba(255, 255, 255, 0.16);
|
||||||
@@ -129,7 +170,6 @@ function openList(t) {
|
|||||||
background: rgba(70, 70, 70, 0.96);
|
background: rgba(70, 70, 70, 0.96);
|
||||||
}
|
}
|
||||||
.boardCard__body {
|
.boardCard__body {
|
||||||
flex: 1 1 auto;
|
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -137,9 +177,12 @@ function openList(t) {
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.boardCard__thumbWrap {
|
.boardCard__thumbWrap {
|
||||||
|
min-width: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
aspect-ratio: 16 / 9;
|
aspect-ratio: 16 / 9;
|
||||||
padding: 14px 14px 0;
|
padding: 14px 14px 0;
|
||||||
@@ -164,46 +207,46 @@ function openList(t) {
|
|||||||
border-radius: 18px;
|
border-radius: 18px;
|
||||||
}
|
}
|
||||||
.boardCard__title {
|
.boardCard__title {
|
||||||
flex: 1 1 auto;
|
font-weight: 800;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
font-weight: 900;
|
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
line-height: 1.3;
|
line-height: 1.35;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
.boardCard__head {
|
.boardCard__head {
|
||||||
|
min-width: 0;
|
||||||
padding: 16px 18px 18px;
|
padding: 16px 18px 18px;
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
min-width: 0;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.boardCard__titleRow,
|
.boardCard__titleRow,
|
||||||
.boardCard__metaRow {
|
.boardCard__metaRow {
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
align-items: center;
|
display: grid;
|
||||||
justify-content: space-between;
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.boardCard__titleRow {
|
.boardCard__titleRow {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.boardCard__metaRow {
|
.boardCard__metaRow {
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
.boardCard__author {
|
.boardCard__author {
|
||||||
flex: 1 1 auto;
|
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
gap: 7px;
|
gap: 7px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
opacity: 0.84;
|
opacity: 0.86;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.boardCard__authorName {
|
.boardCard__authorName {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
@@ -229,9 +272,13 @@ function openList(t) {
|
|||||||
.boardCard__date,
|
.boardCard__date,
|
||||||
.favoriteStat {
|
.favoriteStat {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: rgba(255, 255, 255, 0.64);
|
color: rgba(255, 255, 255, 0.64);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
.boardCard__date {
|
.boardCard__date {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
|
|||||||
Reference in New Issue
Block a user