图书馆小程序
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.

245 lines
6.3 KiB

2 weeks ago
  1. <template>
  2. <view class="lending-container">
  3. <view class="tab-sticky">
  4. <my-tabs
  5. :tabData="tabData"
  6. :defaultIndex="currentIndex"
  7. :config="{ textColor: '#333' }"
  8. @tabClick="tabClick"
  9. />
  10. </view>
  11. <swiper
  12. class="swiper"
  13. :current="currentIndex"
  14. :style="{ height: currentSwiperHeight + 'px' }"
  15. @animationfinish="onSwiperEnd"
  16. @change="onSwiperChange"
  17. >
  18. <swiper-item v-for="(tabItem, idx) in tabData" :key="idx">
  19. <view class="list-wrapper">
  20. <uni-load-more status="loading" v-if="loadingMap[tabItem.status]" />
  21. <view class="empty" v-else-if="!listData[tabItem.status] || listData[tabItem.status].length === 0">
  22. 暂无{{ tabItem.label }}记录
  23. </view>
  24. <block v-else>
  25. <book-list-item
  26. :class="'list-item-' + tabItem.status"
  27. v-for="(item, index) in listData[tabItem.status]"
  28. :key="index"
  29. :data="item"
  30. :ranking="index + 1"
  31. @click="goToDetail(item)"
  32. />
  33. </block>
  34. </view>
  35. </swiper-item>
  36. </swiper>
  37. </view>
  38. </template>
  39. <script>
  40. import myTabs from "@/components/my-tabs/my-tabs.vue";
  41. import bookListItem from "@/components/book-list-item/book-list-item.vue";
  42. export default {
  43. components: { myTabs, bookListItem },
  44. data() {
  45. return {
  46. tabData: [
  47. { label: "全部", status: "all", apiStatus: -1 },
  48. { label: "在借中", status: "lending", apiStatus: 0 },
  49. { label: "将过期", status: "expiring", apiStatus: 1 },
  50. { label: "已过期", status: "expired", apiStatus: 2 },
  51. ],
  52. currentIndex: 0,
  53. listData: {},
  54. loadingMap: {},
  55. swiperHeightData: {},
  56. currentSwiperHeight: 400,
  57. currentPageScrollTop: 0
  58. };
  59. },
  60. onLoad() {
  61. // 初始化数据结构
  62. this.initDataStructure();
  63. // 加载第一个tab的数据
  64. const firstTab = this.getCurrentTab();
  65. if (firstTab) {
  66. this.getListData(firstTab.status);
  67. }
  68. },
  69. onPageScroll(res) {
  70. this.currentPageScrollTop = res.scrollTop;
  71. },
  72. methods: {
  73. // 初始化数据结构
  74. initDataStructure() {
  75. this.tabData.forEach(tab => {
  76. const key = tab.status;
  77. this.$set(this.listData, key, []);
  78. this.$set(this.loadingMap, key, true);
  79. });
  80. },
  81. // 获取当前选中的tab对象
  82. getCurrentTab() {
  83. return this.tabData[this.currentIndex];
  84. },
  85. // 获取列表数据
  86. async getListData(statusKey) {
  87. // 避免重复请求
  88. if (this.listData[statusKey]?.length > 0) return;
  89. this.loadingMap[statusKey] = true;
  90. // 找到对应的tab配置
  91. const tabConfig = this.tabData.find(tab => tab.status === statusKey);
  92. const apiStatus = tabConfig?.apiStatus ?? 0;
  93. // 调用API
  94. const data = await this.fetchBorrowList(apiStatus);
  95. this.listData[statusKey] = data;
  96. this.loadingMap[statusKey] = false;
  97. // 计算高度
  98. this.$nextTick(() => {
  99. this.calcSwiperHeight(statusKey);
  100. });
  101. },
  102. // API请求(可独立到 api 文件)
  103. async fetchBorrowList(apiStatus) {
  104. // 模拟接口请求
  105. return new Promise((resolve) => {
  106. setTimeout(() => {
  107. resolve(this.getMockData(apiStatus));
  108. }, 600);
  109. });
  110. },
  111. // Mock数据(模拟后端返回)
  112. getMockData(apiStatus) {
  113. const baseList = [
  114. {
  115. imgCover: "https://qiniu.aiyxlib.com/1606124577077",
  116. title: "名侦探柯南",
  117. nickname: "青山刚昌",
  118. publish: "长春出版社",
  119. isbn: "1001"
  120. },
  121. {
  122. imgCover: "https://qiniu.aiyxlib.com/1606124577077",
  123. title: "三体",
  124. nickname: "刘慈欣",
  125. publish: "重庆出版社",
  126. isbn: "1002"
  127. }
  128. ];
  129. // 根据状态返回不同数据
  130. switch(apiStatus) {
  131. case 0: return baseList;
  132. case 1: return [baseList[0]];
  133. case 2: return [];
  134. case -1: return baseList;
  135. default: return [];
  136. }
  137. },
  138. // 计算swiper高度
  139. calcSwiperHeight(statusKey) {
  140. const selector = `.list-item-${statusKey}`;
  141. const query = uni.createSelectorQuery().in(this);
  142. query.selectAll(selector).boundingClientRect((res) => {
  143. let totalHeight = 200; // 默认高度
  144. if (res && res.length) {
  145. totalHeight = res.reduce((sum, item) => sum + item.height + 8, 0);
  146. }
  147. this.swiperHeightData[statusKey] = totalHeight;
  148. this.currentSwiperHeight = totalHeight;
  149. }).exec();
  150. },
  151. // tab点击事件
  152. tabClick(index) {
  153. this.currentIndex = index;
  154. const currentTab = this.getCurrentTab();
  155. // 滚动到顶部
  156. if (this.currentPageScrollTop > 100) {
  157. uni.pageScrollTo({ scrollTop: 100, duration: 100 });
  158. }
  159. // 按需加载数据
  160. const statusKey = currentTab.status;
  161. if (!this.listData[statusKey] || this.listData[statusKey].length === 0) {
  162. this.getListData(statusKey);
  163. } else {
  164. this.currentSwiperHeight = this.swiperHeightData[statusKey] || 400;
  165. }
  166. },
  167. onSwiperChange(e) {
  168. if (e.detail.source === "touch") {
  169. this.currentIndex = e.detail.current;
  170. }
  171. },
  172. onSwiperEnd() {
  173. const currentTab = this.getCurrentTab();
  174. const statusKey = currentTab.status;
  175. if (!this.listData[statusKey] || this.listData[statusKey].length === 0) {
  176. this.getListData(statusKey);
  177. } else {
  178. this.currentSwiperHeight = this.swiperHeightData[statusKey] || 400;
  179. }
  180. },
  181. goToDetail(item) {
  182. uni.navigateTo({
  183. url: `/subpkg/pages/book-detail/book-detail?isbn=${item.isbn}`
  184. });
  185. }
  186. }
  187. };
  188. </script>
  189. <style lang="scss" scoped>
  190. .lending-container {
  191. background-color: #f5f5f5;
  192. min-height: 100vh;
  193. .tab-sticky {
  194. position: sticky;
  195. top: 0;
  196. z-index: 99;
  197. background: #fff;
  198. }
  199. .swiper {
  200. width: 100%;
  201. min-height: 300px;
  202. }
  203. .swiper-item {
  204. width: 100%;
  205. height: 100%;
  206. }
  207. .list-wrapper {
  208. padding: 10px;
  209. box-sizing: border-box;
  210. }
  211. .empty {
  212. text-align: center;
  213. padding: 100px 0;
  214. color: #999;
  215. font-size: 14px;
  216. }
  217. }
  218. </style>