more-books.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. <template>
  2. <view class="container">
  3. <!-- 顶部导航栏 -->
  4. <view class="header">
  5. <view class="back-btn" @click="goBack">
  6. <text class="back-icon">←</text>
  7. </view>
  8. <text class="header-title">{{ pageTitle }}</text>
  9. <view class="placeholder"></view>
  10. </view>
  11. <!-- 分隔线 -->
  12. <view class="divider"></view>
  13. <!-- 书籍列表 -->
  14. <scroll-view class="scroll-content" scroll-y @scrolltolower="loadMore" :lower-threshold="100">
  15. <!-- 加载中 -->
  16. <view class="loading-container" v-if="isLoading && bookList.length === 0">
  17. <text class="loading-text">加载中...</text>
  18. </view>
  19. <!-- 书籍列表 -->
  20. <view class="book-list" v-else-if="bookList.length > 0">
  21. <view
  22. class="book-item"
  23. v-for="(book, index) in bookList"
  24. :key="book.id || index"
  25. @click="goToBookDetail(book)"
  26. >
  27. <image
  28. class="book-cover"
  29. :src="book.image || book.cover"
  30. mode="aspectFill"
  31. :lazy-load="true"
  32. @error="handleImageError(index)"
  33. ></image>
  34. <view class="book-info">
  35. <text class="book-title">{{ book.title }}</text>
  36. <text class="book-author">{{ book.author || '未知作者' }}</text>
  37. <text class="book-desc" v-if="book.desc || book.brief">{{ (book.desc || book.brief).substring(0, 50) }}...</text>
  38. </view>
  39. </view>
  40. </view>
  41. <!-- 空状态 -->
  42. <view class="empty-container" v-else>
  43. <text class="empty-text">暂无书籍数据</text>
  44. </view>
  45. <!-- 加载更多提示 -->
  46. <view class="load-more" v-if="hasMore && bookList.length > 0">
  47. <text class="load-more-text">加载中...</text>
  48. </view>
  49. <view class="load-more" v-else-if="!hasMore && bookList.length > 0">
  50. <text class="load-more-text">没有更多了</text>
  51. </view>
  52. </scroll-view>
  53. </view>
  54. </template>
  55. <script>
  56. import { getBooks, getAllBooks, getRankingBooks, getPopularBooks, getNewBooks, getVipBooks, getTodayRecommend, getBestsellers, getFeaturedList, getMoreRecommend } from '../../utils/api.js'
  57. export default {
  58. data() {
  59. return {
  60. type: '', // 类型:today, bestseller, featured, category等
  61. categoryId: null, // 分类ID
  62. categoryName: '', // 分类名称
  63. pageTitle: '更多书籍',
  64. bookList: [],
  65. isLoading: false,
  66. hasMore: true,
  67. page: 1,
  68. pageSize: 20
  69. }
  70. },
  71. onLoad(options) {
  72. console.log('more-books页面加载,参数:', options);
  73. // 获取页面参数
  74. if (options && options.type) {
  75. this.type = options.type;
  76. }
  77. if (options && options.categoryId) {
  78. this.categoryId = parseInt(options.categoryId);
  79. }
  80. if (options && options.categoryName) {
  81. try {
  82. this.categoryName = decodeURIComponent(options.categoryName);
  83. } catch(e) {
  84. this.categoryName = options.categoryName;
  85. }
  86. }
  87. // 设置页面标题
  88. this.setPageTitle();
  89. // 加载数据
  90. this.loadBooks();
  91. },
  92. onShow() {
  93. // 返回页面后,如首屏无数据则重新拉取,避免白屏
  94. if (!this.bookList || this.bookList.length === 0) {
  95. this.page = 1
  96. this.hasMore = true
  97. this.loadBooks()
  98. }
  99. },
  100. methods: {
  101. goBack() {
  102. uni.navigateBack({
  103. delta: 1
  104. });
  105. },
  106. setPageTitle() {
  107. if (this.categoryName) {
  108. this.pageTitle = this.categoryName;
  109. } else {
  110. const titleMap = {
  111. 'today': '今日推荐',
  112. 'bestseller': '畅销书籍',
  113. 'featured': '精品书单',
  114. 'ranking': '排行榜',
  115. 'popular': '热门书籍',
  116. 'new': '新书榜',
  117. 'vip': 'VIP书籍',
  118. 'category': '分类书籍',
  119. 'all': '全部分类'
  120. };
  121. this.pageTitle = titleMap[this.type] || '更多书籍';
  122. }
  123. },
  124. loadBooks() {
  125. if (this.isLoading) return;
  126. console.log('开始加载书籍,类型:', this.type, '分类ID:', this.categoryId, '页码:', this.page);
  127. this.isLoading = true;
  128. let promise;
  129. if (this.type === 'all') {
  130. // 全部分类:显示所有书籍
  131. promise = getAllBooks(this.page, this.pageSize);
  132. } else if (this.type === 'category' && this.categoryId) {
  133. // 分类筛选:显示指定分类的书籍
  134. promise = getBooks({
  135. page: this.page,
  136. size: this.pageSize,
  137. categoryId: this.categoryId,
  138. status: 1
  139. });
  140. } else if (this.type === 'today') {
  141. // 今日推荐
  142. promise = getTodayRecommend(this.pageSize * this.page);
  143. } else if (this.type === 'bestseller') {
  144. // 畅销书籍
  145. promise = getBestsellers(this.pageSize * this.page);
  146. } else if (this.type === 'featured') {
  147. // 精品书单
  148. promise = getFeaturedList(this.pageSize * this.page);
  149. } else if (this.type === 'ranking') {
  150. // 排行榜
  151. promise = getRankingBooks(this.pageSize * this.page);
  152. } else if (this.type === 'popular') {
  153. // 热门书籍
  154. promise = getPopularBooks(this.pageSize * this.page);
  155. } else if (this.type === 'new') {
  156. // 新书榜
  157. promise = getNewBooks(this.pageSize * this.page);
  158. } else if (this.type === 'vip') {
  159. // VIP书籍
  160. promise = getVipBooks(this.page, this.pageSize);
  161. } else {
  162. // 默认:全部书籍
  163. promise = getAllBooks(this.page, this.pageSize);
  164. }
  165. promise
  166. .then((res) => {
  167. console.log('书籍列表API响应:', res);
  168. this.isLoading = false;
  169. if (res && (res.code === 200 || res.success === true)) {
  170. let newBooks = [];
  171. if (this.type === 'vip' || this.type === 'category' || this.type === 'all' || this.type === '' || !this.type) {
  172. // 分页接口,返回的是PageResult
  173. const pageData = res && res.data ? res.data : null
  174. if (pageData && Array.isArray(pageData.list)) {
  175. newBooks = pageData.list;
  176. this.hasMore = pageData.list.length >= this.pageSize && (this.page * this.pageSize < (pageData.total || 0));
  177. } else if (Array.isArray(pageData)) {
  178. newBooks = pageData;
  179. this.hasMore = newBooks.length >= this.pageSize;
  180. }
  181. } else {
  182. // 列表接口,返回的是数组
  183. if (res && Array.isArray(res.data)) {
  184. // 对于非分页接口,需要手动处理分页
  185. const start = (this.page - 1) * this.pageSize;
  186. const end = start + this.pageSize;
  187. newBooks = res.data.slice(start, end);
  188. this.hasMore = end < res.data.length;
  189. }
  190. }
  191. if (newBooks.length > 0) {
  192. // 处理数据格式
  193. const formattedBooks = newBooks.map((book) => {
  194. return {
  195. id: book.id,
  196. title: book.title || '未知书名',
  197. author: book.author || '',
  198. image: book.image || book.cover || 'https://picsum.photos/seed/book' + book.id + '/200/300',
  199. cover: book.cover || book.image,
  200. desc: book.desc,
  201. brief: book.brief,
  202. introduction: book.introduction
  203. };
  204. });
  205. if (this.page === 1) {
  206. this.bookList = formattedBooks;
  207. } else {
  208. this.bookList = [...this.bookList, ...formattedBooks];
  209. }
  210. console.log('书籍列表加载成功,当前共', this.bookList.length, '本');
  211. } else {
  212. if (this.page === 1) {
  213. this.bookList = [];
  214. }
  215. this.hasMore = false;
  216. }
  217. } else {
  218. console.warn('书籍列表API返回错误:', res);
  219. if (this.page === 1) {
  220. this.bookList = [];
  221. }
  222. uni.showToast({
  223. title: (res && (res.message || res.msg)) ? (res.message || res.msg) : '获取书籍列表失败',
  224. icon: 'none',
  225. duration: 2000
  226. });
  227. }
  228. })
  229. .catch((err) => {
  230. this.isLoading = false;
  231. console.error('获取书籍列表失败:', err);
  232. if (this.page === 1) {
  233. this.bookList = [];
  234. }
  235. uni.showToast({
  236. title: (err && err.message) ? err.message : '网络请求失败,请检查后端服务',
  237. icon: 'none',
  238. duration: 3000
  239. });
  240. });
  241. },
  242. loadMore() {
  243. if (this.hasMore && !this.isLoading) {
  244. this.page++;
  245. this.loadBooks();
  246. }
  247. },
  248. goToBookDetail(book) {
  249. if (!book || !book.id) {
  250. uni.showToast({
  251. title: '书籍信息不完整',
  252. icon: 'none'
  253. });
  254. return;
  255. }
  256. uni.navigateTo({
  257. url: `/pages/book-detail/book-detail?bookId=${book.id}`
  258. });
  259. },
  260. handleImageError(index) {
  261. // 图片加载失败时使用备用图片
  262. if (this.bookList[index]) {
  263. this.bookList[index].image = 'https://picsum.photos/seed/fallback' + index + '/200/300';
  264. }
  265. }
  266. }
  267. }
  268. </script>
  269. <style scoped>
  270. .container {
  271. width: 100%;
  272. height: 100vh;
  273. background-color: #FFFFFF;
  274. display: flex;
  275. flex-direction: column;
  276. }
  277. /* 顶部导航栏 */
  278. .header {
  279. display: flex;
  280. align-items: center;
  281. justify-content: space-between;
  282. padding: 20rpx 30rpx;
  283. background-color: #FFFFFF;
  284. position: relative;
  285. }
  286. .back-btn {
  287. width: 60rpx;
  288. height: 60rpx;
  289. display: flex;
  290. align-items: center;
  291. justify-content: center;
  292. }
  293. .back-icon {
  294. font-size: 40rpx;
  295. color: #000000;
  296. font-weight: bold;
  297. }
  298. .header-title {
  299. font-size: 36rpx;
  300. font-weight: bold;
  301. color: #000000;
  302. position: absolute;
  303. left: 50%;
  304. transform: translateX(-50%);
  305. }
  306. .placeholder {
  307. width: 60rpx;
  308. }
  309. /* 分隔线 */
  310. .divider {
  311. width: 100%;
  312. height: 1rpx;
  313. background-color: #E5E5E5;
  314. }
  315. /* 滚动内容 */
  316. .scroll-content {
  317. flex: 1;
  318. width: 100%;
  319. height: 0;
  320. overflow: hidden;
  321. padding: 20rpx 30rpx;
  322. box-sizing: border-box;
  323. }
  324. /* 书籍列表 */
  325. .book-list {
  326. display: flex;
  327. flex-direction: column;
  328. }
  329. /* 书籍项 */
  330. .book-item {
  331. display: flex;
  332. flex-direction: row;
  333. align-items: center;
  334. padding: 20rpx 0;
  335. border-bottom: 1rpx solid #F0F0F0;
  336. }
  337. .book-item:last-child {
  338. border-bottom: none;
  339. }
  340. /* 书籍封面 */
  341. .book-cover {
  342. width: 120rpx;
  343. height: 160rpx;
  344. border-radius: 8rpx;
  345. margin-right: 20rpx;
  346. flex-shrink: 0;
  347. background-color: #E0E0E0;
  348. }
  349. /* 书籍信息 */
  350. .book-info {
  351. flex: 1;
  352. display: flex;
  353. flex-direction: column;
  354. justify-content: center;
  355. min-width: 0;
  356. }
  357. .book-title {
  358. font-size: 32rpx;
  359. font-weight: bold;
  360. color: #000000;
  361. margin-bottom: 12rpx;
  362. overflow: hidden;
  363. text-overflow: ellipsis;
  364. white-space: nowrap;
  365. }
  366. .book-author {
  367. font-size: 26rpx;
  368. color: #999999;
  369. margin-bottom: 12rpx;
  370. overflow: hidden;
  371. text-overflow: ellipsis;
  372. white-space: nowrap;
  373. }
  374. .book-desc {
  375. font-size: 24rpx;
  376. color: #666666;
  377. line-height: 1.5;
  378. overflow: hidden;
  379. text-overflow: ellipsis;
  380. display: -webkit-box;
  381. -webkit-line-clamp: 2;
  382. -webkit-box-orient: vertical;
  383. }
  384. /* 加载中 */
  385. .loading-container {
  386. width: 100%;
  387. height: 400rpx;
  388. display: flex;
  389. align-items: center;
  390. justify-content: center;
  391. }
  392. .loading-text {
  393. font-size: 28rpx;
  394. color: #999999;
  395. }
  396. /* 空状态 */
  397. .empty-container {
  398. width: 100%;
  399. height: 400rpx;
  400. display: flex;
  401. align-items: center;
  402. justify-content: center;
  403. }
  404. .empty-text {
  405. font-size: 28rpx;
  406. color: #999999;
  407. }
  408. /* 加载更多 */
  409. .load-more {
  410. width: 100%;
  411. height: 80rpx;
  412. display: flex;
  413. align-items: center;
  414. justify-content: center;
  415. margin-top: 20rpx;
  416. margin-bottom: 40rpx;
  417. }
  418. .load-more-text {
  419. font-size: 28rpx;
  420. color: #999999;
  421. }
  422. </style>