feat(original): 원작

- 등록, 수정, 삭제
- 캐릭터 연결, 해제 기능 추가
This commit is contained in:
Yu Sung
2025-09-15 04:27:22 +09:00
parent cd86973b60
commit 6507b025de
6 changed files with 934 additions and 3 deletions

View File

@@ -0,0 +1,205 @@
<template>
<div>
<v-toolbar dark>
<v-spacer />
<v-toolbar-title>원작 리스트</v-toolbar-title>
<v-spacer />
<v-btn
color="primary"
dark
@click="goToCreate"
>
원작 등록
</v-btn>
</v-toolbar>
<v-container>
<v-row v-if="isLoading && originals.length === 0">
<v-col class="text-center">
<v-progress-circular
indeterminate
color="primary"
size="64"
/>
</v-col>
</v-row>
<v-row>
<v-col
v-for="item in originals"
:key="item.id"
cols="12"
sm="6"
md="4"
lg="3"
>
<v-card
class="mx-auto"
max-width="344"
style="cursor:pointer;"
@click="openDetail(item)"
>
<v-img
:src="item.imageUrl"
height="200"
contain
/>
<v-card-title class="text-no-wrap">
{{ item.title }}
</v-card-title>
<v-card-actions>
<v-spacer />
<v-btn
small
color="primary"
@click.stop="editOriginal(item)"
>
수정
</v-btn>
<v-btn
small
color="error"
@click.stop="confirmDelete(item)"
>
삭제
</v-btn>
<v-spacer />
</v-card-actions>
</v-card>
</v-col>
</v-row>
<v-row v-if="!isLoading && originals.length === 0">
<v-col class="text-center">
데이터가 없습니다.
</v-col>
</v-row>
<v-row v-if="isLoading && originals.length > 0">
<v-col class="text-center">
<v-progress-circular
indeterminate
color="primary"
/>
</v-col>
</v-row>
</v-container>
<v-dialog
v-model="deleteDialog"
max-width="400"
>
<v-card>
<v-card-title class="headline">
삭제 확인
</v-card-title>
<v-card-text>정말 삭제하시겠습니까?</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
text
@click="deleteDialog = false"
>
취소
</v-btn>
<v-btn
color="error"
text
@click="deleteItem"
>
삭제
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
import { getOriginalList, deleteOriginal } from '@/api/original'
export default {
name: 'OriginalList',
data() {
return {
isLoading: false,
originals: [],
page: 1,
hasMore: true,
deleteDialog: false,
selected: null
}
},
mounted() {
this.loadMore();
window.addEventListener('scroll', this.handleScroll);
},
beforeDestroy() {
window.removeEventListener('scroll', this.handleScroll);
},
methods: {
notifyError(message) { this.$dialog.notify.error(message) },
notifySuccess(message) { this.$dialog.notify.success(message) },
async loadMore() {
if (this.isLoading || !this.hasMore) return;
this.isLoading = true;
try {
const res = await getOriginalList(this.page);
if (res.status === 200 && res.data.success === true) {
const content = res.data.data?.content || [];
this.originals = this.originals.concat(content);
this.hasMore = content.length > 0;
this.page++;
} else {
this.notifyError('원작 목록 조회 실패');
}
} catch (e) {
this.notifyError('원작 목록 조회 실패');
} finally {
this.isLoading = false;
}
},
handleScroll() {
const scrollPosition = window.innerHeight + window.scrollY;
const documentHeight = document.documentElement.offsetHeight;
if (scrollPosition >= documentHeight - 200 && !this.isLoading && this.hasMore) {
this.loadMore();
}
},
goToCreate() {
this.$router.push('/original-work/form');
},
editOriginal(item) {
this.$router.push({ path: '/original-work/form', query: { id: item.id } });
},
openDetail(item) {
this.$router.push({ path: '/original-work/detail', query: { id: item.id } });
},
confirmDelete(item) {
this.selected = item;
this.deleteDialog = true;
},
async deleteItem() {
if (!this.selected) return;
try {
const res = await deleteOriginal(this.selected.id);
if (res.status === 200 && res.data.success === true) {
this.notifySuccess('삭제되었습니다.');
this.originals = this.originals.filter(x => x.id !== this.selected.id);
} else {
this.notifyError('삭제 실패');
}
} catch (e) {
this.notifyError('삭제 실패');
} finally {
this.deleteDialog = false;
this.selected = null;
}
}
}
}
</script>
<style scoped>
.text-no-wrap { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
</style>