小说网站前端代码仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

387 lines
12 KiB

<template>
<div class="bookshelf-container">
<div class="bookshelf-header">
<h1 class="bookshelf-title">我的书架</h1>
<div class="bookshelf-actions">
<el-button type="warning" class="clear-btn" @click="confirmClearBookshelf" :disabled="loading">清空书架</el-button>
<el-button type="danger" class="delete-btn" @click="toggleDeleteMode" :disabled="loading">{{ deleteMode ? '取消删除' : '删除书本' }}</el-button>
</div>
</div>
<!-- 加载状态 -->
<div v-if="loading" class="loading-container">
<el-skeleton :rows="4" animated />
</div>
<div v-else-if="bookList.length === 0" class="empty-bookshelf">
<div class="empty-text">您的书架还没有书籍,去书城逛逛吧~</div>
<el-button type="primary" @click="goToHome">去书城看看</el-button>
</div>
<div v-else class="bookshelf-content">
<div class="book-grid">
<bookshelfCard
v-for="book in bookList"
:key="book.id"
:book="book"
:delete-mode="deleteMode"
@click="goToReadBook"
@delete="removeBook"
/>
</div>
<!-- 分页组件 -->
<div class="pagination-container" v-if="pagination.total > 0">
<el-pagination
v-model:current-page="pagination.pageNo"
v-model:page-size="pagination.pageSize"
:total="pagination.total"
layout="prev, pager, next"
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
<!-- 确认清空书架的对话框 -->
<el-dialog
v-model="clearConfirmVisible"
title="确认清空"
width="30%"
>
<span>确定要清空书架吗?此操作不可恢复!</span>
<template #footer>
<span class="dialog-footer">
<el-button @click="clearConfirmVisible = false">取消</el-button>
<el-button type="primary" @click="clearBookshelf" :loading="clearLoading">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script>
import { ref, reactive, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage, ElMessageBox } from 'element-plus';
import bookshelfCard from '@/components/bookshelf/bookshelfCard.vue';
import { bookshelfApi } from '@/api/bookshelf.js';
import { homeApi } from '@/api/modules.js';
export default {
name: 'BookshelfView',
components: {
bookshelfCard
},
setup() {
const router = useRouter();
const deleteMode = ref(false);
const clearConfirmVisible = ref(false);
const loading = ref(false);
const clearLoading = ref(false);
const bookList = ref([]);
// 分页数据
const pagination = reactive({
pageNo: 1,
pageSize: 20,
total: 0
});
// 获取书架列表
const getBookshelfList = async () => {
try {
loading.value = true;
const params = {
pageNo: pagination.pageNo,
pageSize: pagination.pageSize
};
const response = await bookshelfApi.getReadBookPage(params);
if (response.result && response.code == 200) {
const data = response.result;
bookList.value = data.records || [];
pagination.total = data.total || 0;
} else {
ElMessage.error(response?.message || '获取书架数据失败');
}
} catch (error) {
console.error('获取书架数据失败:', error);
ElMessage.error('获取书架数据失败,请稍后重试');
} finally {
loading.value = false;
}
};
// 跳转到阅读页面
const goToReadBook = async (book) => {
if (deleteMode.value) return; // 删除模式下不跳转
if(book.novelId){
// 先验证章节是否存在
try {
const chapterResponse = await homeApi.getBookCatalogDetail(book.novelId);
if (chapterResponse && chapterResponse.success && chapterResponse.result) {
// 章节存在,直接跳转
router.push({
name: 'ChapterDetail',
params: {
id: book.shopId,
chapterId: book.novelId
}
});
return;
}
} catch (error) {
console.warn('章节不存在或获取失败,将跳转到第一章:', error);
}
// 如果章节不存在,继续执行下面的逻辑获取第一章
}
// 获取章节目录
try {
const response = await homeApi.getBookCatalogList({
bookId: book.shopId,
pageNo: 1,
pageSize: 1, // 获取第一章
});
if (response.success && response.result) {
// 处理章节数据格式
const catalogData = response.result.records || response.result || [];
if(catalogData[0]){
router.push({
name: 'ChapterDetail',
params: {
id: book.shopId,
chapterId: catalogData[0].id
}
});
}else{
ElMessage.info('暂无章节内容');
}
}
} catch (error) {
console.error('获取章节目录失败:', error);
ElMessage.error('获取章节失败,请稍后重试');
}
};
// 跳转到首页
const goToHome = () => {
router.push('/');
};
// 切换删除模式
const toggleDeleteMode = () => {
deleteMode.value = !deleteMode.value;
};
// 移除书籍
const removeBook = async (bookId) => {
try {
await ElMessageBox.confirm('确定要将该书从书架中移除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
const response = await bookshelfApi.removeReadBook(bookId);
if (response && response.code === 200) {
ElMessage({
type: 'success',
message: '已从书架移除'
});
// 刷新列表
await getBookshelfList();
} else {
ElMessage.error(response?.message || '移除失败');
}
} catch (error) {
if (error !== 'cancel') {
console.error('移除书籍失败:', error);
ElMessage.error('移除失败,请稍后重试');
}
}
};
// 确认清空书架
const confirmClearBookshelf = () => {
if (bookList.value.length === 0) {
ElMessage.info('书架已经是空的了');
return;
}
clearConfirmVisible.value = true;
};
// 清空书架
const clearBookshelf = async () => {
try {
clearLoading.value = true;
// 获取所有书籍ID
const bookIds = bookList.value.map(book => book.id).join(',');
if (!bookIds) {
ElMessage.info('没有需要清空的书籍');
clearConfirmVisible.value = false;
return;
}
const response = await bookshelfApi.batchRemoveReadBook(bookIds);
if (response && response.code === 200) {
clearConfirmVisible.value = false;
ElMessage({
type: 'success',
message: '书架已清空'
});
// 刷新列表
await getBookshelfList();
} else {
ElMessage.error(response?.message || '清空失败');
}
} catch (error) {
console.error('清空书架失败:', error);
ElMessage.error('清空失败,请稍后重试');
} finally {
clearLoading.value = false;
}
};
// 分页大小改变
const handleSizeChange = (size) => {
pagination.pageSize = size;
pagination.pageNo = 1; // 重置到第一页
getBookshelfList();
};
// 当前页改变
const handleCurrentChange = (page) => {
pagination.pageNo = page;
getBookshelfList();
};
onMounted(() => {
getBookshelfList();
});
return {
bookList,
deleteMode,
clearConfirmVisible,
loading,
clearLoading,
pagination,
goToReadBook,
goToHome,
toggleDeleteMode,
removeBook,
confirmClearBookshelf,
clearBookshelf,
handleSizeChange,
handleCurrentChange,
getBookshelfList
};
}
};
</script>
<style lang="scss" scoped>
@use '@/assets/styles/variables.scss' as vars;
.bookshelf-container {
width: 100%;
background-color: #fff;
min-height: calc(100vh - 60px);
padding: 20px;
}
.bookshelf-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 15px;
border-bottom: 2px solid #f0f0f0;
.bookshelf-title {
font-size: 20px;
font-weight: bold;
color: #333;
position: relative;
padding-left: 15px;
&::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
height: 20px;
width: 4px;
background-color: vars.$primary-color;
border-radius: 2px;
}
}
.bookshelf-actions {
display: flex;
gap: 10px;
.clear-btn, .delete-btn {
font-size: 14px;
}
}
}
.loading-container {
padding: 20px;
}
.empty-bookshelf {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100px 0;
color: #999;
.empty-text {
margin-bottom: 20px;
font-size: 16px;
}
}
.bookshelf-content {
.book-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 25px;
@media screen and (max-width: 1200px) {
grid-template-columns: repeat(2, 1fr);
}
@media screen and (max-width: 768px) {
grid-template-columns: repeat(1, 1fr);
}
@media screen and (max-width: 480px) {
grid-template-columns: 1fr;
}
}
}
.pagination-container {
display: flex;
justify-content: center;
padding: 30px 0 10px 0;
margin-top: 20px;
border-top: 1px solid #f0f0f0;
}
</style>