小说网站前端代码仓库
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.

386 lines
12 KiB

2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
  1. <template>
  2. <div class="bookshelf-container">
  3. <div class="bookshelf-header">
  4. <h1 class="bookshelf-title">我的书架</h1>
  5. <div class="bookshelf-actions">
  6. <el-button type="warning" class="clear-btn" @click="confirmClearBookshelf" :disabled="loading">清空书架</el-button>
  7. <el-button type="danger" class="delete-btn" @click="toggleDeleteMode" :disabled="loading">{{ deleteMode ? '取消删除' : '删除书本' }}</el-button>
  8. </div>
  9. </div>
  10. <!-- 加载状态 -->
  11. <div v-if="loading" class="loading-container">
  12. <el-skeleton :rows="4" animated />
  13. </div>
  14. <div v-else-if="bookList.length === 0" class="empty-bookshelf">
  15. <div class="empty-text">您的书架还没有书籍去书城逛逛吧</div>
  16. <el-button type="primary" @click="goToHome">去书城看看</el-button>
  17. </div>
  18. <div v-else class="bookshelf-content">
  19. <div class="book-grid">
  20. <bookshelfCard
  21. v-for="book in bookList"
  22. :key="book.id"
  23. :book="book"
  24. :delete-mode="deleteMode"
  25. @click="goToReadBook"
  26. @delete="removeBook"
  27. />
  28. </div>
  29. <!-- 分页组件 -->
  30. <div class="pagination-container" v-if="pagination.total > 0">
  31. <el-pagination
  32. v-model:current-page="pagination.pageNo"
  33. v-model:page-size="pagination.pageSize"
  34. :total="pagination.total"
  35. layout="prev, pager, next"
  36. background
  37. @size-change="handleSizeChange"
  38. @current-change="handleCurrentChange"
  39. />
  40. </div>
  41. </div>
  42. <!-- 确认清空书架的对话框 -->
  43. <el-dialog
  44. v-model="clearConfirmVisible"
  45. title="确认清空"
  46. width="30%"
  47. >
  48. <span>确定要清空书架吗此操作不可恢复</span>
  49. <template #footer>
  50. <span class="dialog-footer">
  51. <el-button @click="clearConfirmVisible = false">取消</el-button>
  52. <el-button type="primary" @click="clearBookshelf" :loading="clearLoading">确定</el-button>
  53. </span>
  54. </template>
  55. </el-dialog>
  56. </div>
  57. </template>
  58. <script>
  59. import { ref, reactive, onMounted } from 'vue';
  60. import { useRouter } from 'vue-router';
  61. import { ElMessage, ElMessageBox } from 'element-plus';
  62. import bookshelfCard from '@/components/bookshelf/bookshelfCard.vue';
  63. import { bookshelfApi } from '@/api/bookshelf.js';
  64. import { homeApi } from '@/api/modules.js';
  65. export default {
  66. name: 'BookshelfView',
  67. components: {
  68. bookshelfCard
  69. },
  70. setup() {
  71. const router = useRouter();
  72. const deleteMode = ref(false);
  73. const clearConfirmVisible = ref(false);
  74. const loading = ref(false);
  75. const clearLoading = ref(false);
  76. const bookList = ref([]);
  77. // 分页数据
  78. const pagination = reactive({
  79. pageNo: 1,
  80. pageSize: 20,
  81. total: 0
  82. });
  83. // 获取书架列表
  84. const getBookshelfList = async () => {
  85. try {
  86. loading.value = true;
  87. const params = {
  88. pageNo: pagination.pageNo,
  89. pageSize: pagination.pageSize
  90. };
  91. const response = await bookshelfApi.getReadBookPage(params);
  92. if (response.result && response.code == 200) {
  93. const data = response.result;
  94. bookList.value = data.records || [];
  95. pagination.total = data.total || 0;
  96. } else {
  97. ElMessage.error(response?.message || '获取书架数据失败');
  98. }
  99. } catch (error) {
  100. console.error('获取书架数据失败:', error);
  101. ElMessage.error('获取书架数据失败,请稍后重试');
  102. } finally {
  103. loading.value = false;
  104. }
  105. };
  106. // 跳转到阅读页面
  107. const goToReadBook = async (book) => {
  108. if (deleteMode.value) return; // 删除模式下不跳转
  109. if(book.novelId){
  110. // 先验证章节是否存在
  111. try {
  112. const chapterResponse = await homeApi.getBookCatalogDetail(book.novelId);
  113. if (chapterResponse && chapterResponse.success && chapterResponse.result) {
  114. // 章节存在,直接跳转
  115. router.push({
  116. name: 'ChapterDetail',
  117. params: {
  118. id: book.shopId,
  119. chapterId: book.novelId
  120. }
  121. });
  122. return;
  123. }
  124. } catch (error) {
  125. console.warn('章节不存在或获取失败,将跳转到第一章:', error);
  126. }
  127. // 如果章节不存在,继续执行下面的逻辑获取第一章
  128. }
  129. // 获取章节目录
  130. try {
  131. const response = await homeApi.getBookCatalogList({
  132. bookId: book.shopId,
  133. pageNo: 1,
  134. pageSize: 1, // 获取第一章
  135. });
  136. if (response.success && response.result) {
  137. // 处理章节数据格式
  138. const catalogData = response.result.records || response.result || [];
  139. if(catalogData[0]){
  140. router.push({
  141. name: 'ChapterDetail',
  142. params: {
  143. id: book.shopId,
  144. chapterId: catalogData[0].id
  145. }
  146. });
  147. }else{
  148. ElMessage.info('暂无章节内容');
  149. }
  150. }
  151. } catch (error) {
  152. console.error('获取章节目录失败:', error);
  153. ElMessage.error('获取章节失败,请稍后重试');
  154. }
  155. };
  156. // 跳转到首页
  157. const goToHome = () => {
  158. router.push('/');
  159. };
  160. // 切换删除模式
  161. const toggleDeleteMode = () => {
  162. deleteMode.value = !deleteMode.value;
  163. };
  164. // 移除书籍
  165. const removeBook = async (bookId) => {
  166. try {
  167. await ElMessageBox.confirm('确定要将该书从书架中移除吗?', '提示', {
  168. confirmButtonText: '确定',
  169. cancelButtonText: '取消',
  170. type: 'warning'
  171. });
  172. const response = await bookshelfApi.removeReadBook(bookId);
  173. if (response && response.code === 200) {
  174. ElMessage({
  175. type: 'success',
  176. message: '已从书架移除'
  177. });
  178. // 刷新列表
  179. await getBookshelfList();
  180. } else {
  181. ElMessage.error(response?.message || '移除失败');
  182. }
  183. } catch (error) {
  184. if (error !== 'cancel') {
  185. console.error('移除书籍失败:', error);
  186. ElMessage.error('移除失败,请稍后重试');
  187. }
  188. }
  189. };
  190. // 确认清空书架
  191. const confirmClearBookshelf = () => {
  192. if (bookList.value.length === 0) {
  193. ElMessage.info('书架已经是空的了');
  194. return;
  195. }
  196. clearConfirmVisible.value = true;
  197. };
  198. // 清空书架
  199. const clearBookshelf = async () => {
  200. try {
  201. clearLoading.value = true;
  202. // 获取所有书籍ID
  203. const bookIds = bookList.value.map(book => book.id).join(',');
  204. if (!bookIds) {
  205. ElMessage.info('没有需要清空的书籍');
  206. clearConfirmVisible.value = false;
  207. return;
  208. }
  209. const response = await bookshelfApi.batchRemoveReadBook(bookIds);
  210. if (response && response.code === 200) {
  211. clearConfirmVisible.value = false;
  212. ElMessage({
  213. type: 'success',
  214. message: '书架已清空'
  215. });
  216. // 刷新列表
  217. await getBookshelfList();
  218. } else {
  219. ElMessage.error(response?.message || '清空失败');
  220. }
  221. } catch (error) {
  222. console.error('清空书架失败:', error);
  223. ElMessage.error('清空失败,请稍后重试');
  224. } finally {
  225. clearLoading.value = false;
  226. }
  227. };
  228. // 分页大小改变
  229. const handleSizeChange = (size) => {
  230. pagination.pageSize = size;
  231. pagination.pageNo = 1; // 重置到第一页
  232. getBookshelfList();
  233. };
  234. // 当前页改变
  235. const handleCurrentChange = (page) => {
  236. pagination.pageNo = page;
  237. getBookshelfList();
  238. };
  239. onMounted(() => {
  240. getBookshelfList();
  241. });
  242. return {
  243. bookList,
  244. deleteMode,
  245. clearConfirmVisible,
  246. loading,
  247. clearLoading,
  248. pagination,
  249. goToReadBook,
  250. goToHome,
  251. toggleDeleteMode,
  252. removeBook,
  253. confirmClearBookshelf,
  254. clearBookshelf,
  255. handleSizeChange,
  256. handleCurrentChange,
  257. getBookshelfList
  258. };
  259. }
  260. };
  261. </script>
  262. <style lang="scss" scoped>
  263. @use '@/assets/styles/variables.scss' as vars;
  264. .bookshelf-container {
  265. width: 100%;
  266. background-color: #fff;
  267. min-height: calc(100vh - 60px);
  268. padding: 20px;
  269. }
  270. .bookshelf-header {
  271. display: flex;
  272. justify-content: space-between;
  273. align-items: center;
  274. margin-bottom: 30px;
  275. padding-bottom: 15px;
  276. border-bottom: 2px solid #f0f0f0;
  277. .bookshelf-title {
  278. font-size: 20px;
  279. font-weight: bold;
  280. color: #333;
  281. position: relative;
  282. padding-left: 15px;
  283. &::before {
  284. content: '';
  285. position: absolute;
  286. left: 0;
  287. top: 50%;
  288. transform: translateY(-50%);
  289. height: 20px;
  290. width: 4px;
  291. background-color: vars.$primary-color;
  292. border-radius: 2px;
  293. }
  294. }
  295. .bookshelf-actions {
  296. display: flex;
  297. gap: 10px;
  298. .clear-btn, .delete-btn {
  299. font-size: 14px;
  300. }
  301. }
  302. }
  303. .loading-container {
  304. padding: 20px;
  305. }
  306. .empty-bookshelf {
  307. display: flex;
  308. flex-direction: column;
  309. align-items: center;
  310. justify-content: center;
  311. padding: 100px 0;
  312. color: #999;
  313. .empty-text {
  314. margin-bottom: 20px;
  315. font-size: 16px;
  316. }
  317. }
  318. .bookshelf-content {
  319. .book-grid {
  320. display: grid;
  321. grid-template-columns: repeat(2, 1fr);
  322. gap: 25px;
  323. @media screen and (max-width: 1200px) {
  324. grid-template-columns: repeat(2, 1fr);
  325. }
  326. @media screen and (max-width: 768px) {
  327. grid-template-columns: repeat(1, 1fr);
  328. }
  329. @media screen and (max-width: 480px) {
  330. grid-template-columns: 1fr;
  331. }
  332. }
  333. }
  334. .pagination-container {
  335. display: flex;
  336. justify-content: center;
  337. padding: 30px 0 10px 0;
  338. margin-top: 20px;
  339. border-top: 1px solid #f0f0f0;
  340. }
  341. </style>