四零语境前端代码仓库
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.

663 lines
15 KiB

1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
  1. <template>
  2. <view class="home-container">
  3. <!-- 状态栏安全区域 -->
  4. <uv-status-bar></uv-status-bar>
  5. <!-- 顶部搜索栏 -->
  6. <view class="header">
  7. <view class="search-container" @click="goSearch">
  8. <uv-search
  9. placeholder="请输入要查询的内容"
  10. :show-action="false"
  11. shape="round"
  12. bg-color="#f5f5f5"
  13. color="#666"
  14. height="38"
  15. margin="0 200rpx 0 0"
  16. placeholderColor="#c6c6c6"
  17. ></uv-search>
  18. </view>
  19. </view>
  20. <!-- Tab栏 -->
  21. <view class="tab-container">
  22. <scroll-view show-scrollbar="false" class="tab-scroll" scroll-x="true" >
  23. <view class="tab-list">
  24. <view
  25. v-for="(tab, index) in tabs"
  26. :key="index"
  27. class="tab-item"
  28. :class="{ active: activeTab === index }"
  29. @click="switchTab(index)"
  30. >
  31. {{ tab.title }}
  32. </view>
  33. </view>
  34. </scroll-view>
  35. </view>
  36. <!-- 轮播图 -->
  37. <view class="swiper-container">
  38. <uv-swiper
  39. :list="bannerList"
  40. keyName="image"
  41. height="121"
  42. radius="12"
  43. indicator
  44. ndicatorInactiveColor="#fff"
  45. :loading="false"
  46. indicatorMode="dot"
  47. indicatorActiveColor="#F95A01"
  48. @click="onBannerClick"
  49. ></uv-swiper>
  50. </view>
  51. <!-- 根据labelBooksData动态渲染书籍区块 -->
  52. <view
  53. v-for="(labelData, labelIndex) in labelBooksData"
  54. :key="labelIndex"
  55. class="section"
  56. >
  57. <view class="section-header" @click="goLabel(labelData.labelInfo)">
  58. <text class="section-title">{{ labelData.labelInfo.title }}</text>
  59. <view class="section-more">
  60. <text>更多</text>
  61. <uv-icon name="arrow-right" size="14" color="#888"></uv-icon>
  62. </view>
  63. </view>
  64. <!-- 第一个label今日更新样式 -->
  65. <scroll-view
  66. v-if="labelIndex === 0"
  67. show-scrollbar="false"
  68. class="content-scroll"
  69. scroll-x="true"
  70. >
  71. <view class="content-list">
  72. <view
  73. v-for="(item, index) in labelData.books"
  74. :key="index"
  75. class="content-item"
  76. @click="goBook(item)"
  77. >
  78. <view class="item-cover">
  79. <image :src="item.booksImg || '/static/默认图片.png'" mode="aspectFill"></image>
  80. </view>
  81. <view class="item-info">
  82. <text class="item-title">{{ item.booksName }}</text>
  83. <text class="item-author">{{ item.booksAuthor }}</text>
  84. <view class="item-duration">
  85. <image src="/static/播放图标.png" class="item-icon" />
  86. <text>{{ item.duration }}</text>
  87. </view>
  88. </view>
  89. </view>
  90. </view>
  91. </scroll-view>
  92. <!-- 第二个label推荐书籍样式 -->
  93. <scroll-view
  94. v-else-if="labelIndex === 1"
  95. show-scrollbar="false"
  96. class="content-scroll"
  97. scroll-x="true"
  98. >
  99. <view class="book-list">
  100. <view
  101. v-for="(book, index) in labelData.books"
  102. :key="index"
  103. class="book-item"
  104. @click="goBook(book)"
  105. >
  106. <view class="book-cover">
  107. <image :src="book.booksImg || '/static/默认图片.png'" mode="aspectFill"></image>
  108. <view class="book-overlay">
  109. <view class="book-duration">
  110. <image src="/static/闹钟图标.png" class="book-duration-icon" />
  111. <text class="book-duration-text">{{ book.duration }}</text>
  112. </view>
  113. <view class="book-title">{{ book.booksName }}</view>
  114. </view>
  115. </view>
  116. </view>
  117. </view>
  118. </scroll-view>
  119. <!-- 第三个及以后的label网格样式 -->
  120. <view v-else class="book-grid">
  121. <view
  122. v-for="(book, index) in labelData.books"
  123. :key="index"
  124. class="book-grid-item"
  125. @click="goBook(book)"
  126. >
  127. <view class="book-grid-cover">
  128. <image :src="book.booksImg || '/static/默认图片.png'" mode="aspectFill"></image>
  129. </view>
  130. <view class="book-grid-info">
  131. <text class="book-grid-title">{{ book.booksName }}</text>
  132. <view class="book-grid-meta">
  133. <text class="book-grid-grade">{{ book.categoryName }}/</text>
  134. <image src="/static/播放图标.png" class="book-grid-duration-icon" />
  135. <text class="book-grid-duration">{{ book.duration }}</text>
  136. </view>
  137. </view>
  138. </view>
  139. </view>
  140. </view>
  141. <!-- 推荐内容列表 -->
  142. <view class="section">
  143. <view class="recommend-list">
  144. <view
  145. @click="goPlan(item.id, item.type)"
  146. v-for="(item, index) in recommendList"
  147. :key="index"
  148. class="recommend-item"
  149. >
  150. <image :src="item.img" mode="aspectFill" class="recommend-image"></image>
  151. </view>
  152. </view>
  153. </view>
  154. </view>
  155. </template>
  156. <script>
  157. export default {
  158. data() {
  159. return {
  160. // Tab数据
  161. tabs: [ ],
  162. activeTab: 0,
  163. // 轮播图数据
  164. bannerList: [
  165. ],
  166. // 书籍分类
  167. labels: [
  168. ],
  169. // 根据label获取的书籍数据(二维数组)
  170. labelBooksData: [],
  171. // 推荐列表数据
  172. recommendList: [
  173. ]
  174. }
  175. },
  176. methods: {
  177. // 切换Tab
  178. async switchTab(index) {
  179. this.activeTab = index
  180. await this.getBooksByLabels()
  181. },
  182. // 轮播图点击事件
  183. onBannerClick(index) {
  184. console.log('点击轮播图:', index)
  185. // 这里可以添加跳转逻辑
  186. },
  187. // 跳转计划定制
  188. goPlan(id, type) {
  189. uni.navigateTo({
  190. url: '/subPages/home/plan?id=' + id + '&type=' + type
  191. })
  192. },
  193. goSearch() {
  194. uni.navigateTo({
  195. url: '/subPages/home/search'
  196. })
  197. },
  198. goBook(book) {
  199. uni.navigateTo({
  200. url: '/subPages/home/directory?id=' + book.id
  201. })
  202. },
  203. async getBanner() {
  204. const bannerRes = await this.$api.home.getBanner()
  205. if (bannerRes.code === 200){
  206. this.bannerList = bannerRes.result.map(item => ({
  207. image: item.img,
  208. title: item.title
  209. }))
  210. }
  211. },
  212. async getSignup() {
  213. const signupRes = await this.$api.home.getLink()
  214. if (signupRes.code === 200){
  215. this.recommendList = signupRes.result.map(item => ({
  216. img: item.img,
  217. id: item.id,
  218. type: item.type
  219. }))
  220. }
  221. },
  222. // 获取书籍分类
  223. async getCategory() {
  224. const categoryRes = await this.$api.book.category()
  225. if (categoryRes.code === 200){
  226. this.tabs = categoryRes.result.map(item => ({
  227. title:item.title,
  228. id: item.id
  229. }))
  230. }
  231. },
  232. // 获取书籍标签
  233. async getLabel() {
  234. const labelRes = await this.$api.book.label()
  235. if (labelRes.code === 200){
  236. this.labels = labelRes.result.map(item => ({
  237. title:item.lable,
  238. id: item.id
  239. }))
  240. }
  241. },
  242. // 根据label数组获取书籍数据
  243. async getBooksByLabels() {
  244. if (!this.labels || this.labels.length === 0) {
  245. console.log('labels数据为空,无法获取书籍')
  246. return
  247. }
  248. try {
  249. // 创建请求数组,每个label发起一次请求
  250. const requests = this.labels.map(label =>
  251. this.$api.book.list({
  252. label: label.id,
  253. pageNo: 1,
  254. pageSize: 6,
  255. category: this.tabs[this.activeTab].id
  256. }, false)
  257. )
  258. // 并发执行所有请求
  259. const responses = await Promise.all(requests)
  260. // 创建二维数组存储结果
  261. this.labelBooksData = responses.map((response, index) => {
  262. if (response.code === 200) {
  263. return {
  264. labelInfo: this.labels[index],
  265. books: response.result.records || []
  266. }
  267. } else {
  268. console.error(`获取label ${this.labels[index].title} 的书籍失败:`, response)
  269. return {
  270. labelInfo: this.labels[index],
  271. books: []
  272. }
  273. }
  274. })
  275. console.log('根据label获取的书籍数据:', this.labelBooksData)
  276. } catch (error) {
  277. console.error('获取书籍数据失败:', error)
  278. this.labelBooksData = []
  279. }
  280. },
  281. goLabel(label){
  282. uni.navigateTo({
  283. url: '/subPages/home/search?label=' + label.id
  284. })
  285. }
  286. },
  287. async onShow() {
  288. // 先获取基础数据
  289. await Promise.all([this.getBanner(), this.getSignup(), this.getCategory(), this.getLabel()])
  290. // 根据label数据获取对应的书籍
  291. await this.getBooksByLabels()
  292. }
  293. }
  294. </script>
  295. <style lang="scss" scoped>
  296. .home-container {
  297. background: #fff;
  298. min-height: 100vh;
  299. padding-bottom: 80rpx;
  300. }
  301. // 顶部搜索栏
  302. .header {
  303. display: flex;
  304. align-items: center;
  305. padding: 6rpx 32rpx;
  306. background: #fff;
  307. .search-container {
  308. flex: 1;
  309. }
  310. }
  311. // Tab栏
  312. .tab-container {
  313. background: #fff;
  314. // border-bottom: 1px solid #f0f0f0;
  315. top: 0;
  316. left: 0;
  317. right: 0;
  318. z-index: 999;
  319. .tab-scroll {
  320. white-space: nowrap;
  321. .tab-list {
  322. display: flex;
  323. padding: 0 20rpx;
  324. .tab-item {
  325. flex-shrink: 0;
  326. padding: 20rpx 20rpx;
  327. font-size: 32rpx;
  328. color: #666;
  329. position: relative;
  330. &.active {
  331. color: $primary-text-color;
  332. font-weight: 700;
  333. &::after {
  334. content: '';
  335. position: absolute;
  336. bottom: 0;
  337. left: 50%;
  338. transform: translateX(-50%);
  339. width: 22rpx;
  340. height: 4rpx;
  341. background: $primary-text-color;
  342. border-radius: 2rpx;
  343. }
  344. }
  345. }
  346. }
  347. }
  348. }
  349. // 轮播图容器
  350. .swiper-container {
  351. margin: 20rpx;
  352. border-radius: 12rpx;
  353. overflow: hidden;
  354. }
  355. // 内容区块
  356. .section {
  357. margin-top: 40rpx;
  358. .section-header {
  359. display: flex;
  360. align-items: center;
  361. justify-content: space-between;
  362. padding: 0 30rpx ;
  363. margin-bottom: 24rpx;
  364. .section-title {
  365. font-size: 36rpx;
  366. // font-weight: 600;
  367. color: $primary-text-color;
  368. }
  369. .section-more {
  370. display: flex;
  371. align-items: center;
  372. gap: 4rpx;
  373. text {
  374. font-size: 24rpx;
  375. color: $secondary-text-color;
  376. }
  377. }
  378. }
  379. .content-scroll {
  380. white-space: nowrap;
  381. }
  382. }
  383. // 今日更新列表
  384. .content-list {
  385. display: flex;
  386. padding: 0 30rpx;
  387. gap: 32rpx;
  388. .content-item {
  389. flex-shrink: 0;
  390. width: 602rpx;
  391. height: 212rpx;
  392. display: flex;
  393. align-items: center;
  394. background: #F8F8F8;
  395. padding: 16rpx;
  396. border-radius: 16rpx;
  397. gap: 16rpx;
  398. .item-cover {
  399. width: 136rpx;
  400. height: 200rpx;
  401. border-radius: 16rpx;
  402. // overflow: hidden;
  403. image {
  404. width: 136rpx;
  405. height: 200rpx;
  406. }
  407. }
  408. .item-info {
  409. // padding-top: 20rpx;
  410. gap: 16rpx;
  411. display: flex;
  412. flex-direction: column;
  413. .item-title {
  414. font-size: 32rpx;
  415. font-weight: 700;
  416. color: $primary-text-color;
  417. letter-spacing: 0;
  418. line-height: 48rpx;
  419. // margin-bottom: 12rpx;
  420. overflow: hidden;
  421. text-overflow: ellipsis;
  422. white-space: nowrap;
  423. }
  424. .item-author {
  425. font-size: 24rpx;
  426. color: $secondary-text-color;
  427. // margin-bottom: 8rpx;
  428. letter-spacing: 0;
  429. overflow: hidden;
  430. text-overflow: ellipsis;
  431. white-space: nowrap;
  432. }
  433. .item-duration {
  434. gap: 12rpx;
  435. display: flex;
  436. align-items: center;
  437. font-size: 22rpx;
  438. letter-spacing: 0;
  439. color: $secondary-text-color;
  440. .item-icon{
  441. width: 22rpx;
  442. height: 25rpx;
  443. }
  444. }
  445. }
  446. }
  447. }
  448. // 推荐书籍列表
  449. .book-list {
  450. display: flex;
  451. padding: 0 30rpx;
  452. gap: 32rpx;
  453. .book-item {
  454. flex-shrink: 0;
  455. width: 270rpx;
  456. // border-radius: 16rpx;
  457. .book-cover {
  458. width: 100%;
  459. height: 360rpx;
  460. border-radius: 16rpx;
  461. overflow: hidden;
  462. position: relative;
  463. image {
  464. width: 100%;
  465. height: 100%;
  466. }
  467. .book-overlay {
  468. position: absolute;
  469. bottom: 0;
  470. left: 0;
  471. right: 0;
  472. width: 100%;
  473. height: 140rpx;
  474. padding-top: 4rpx;
  475. padding-right: 16rpx;
  476. padding-bottom: 8rpx;
  477. padding-left: 16rpx;
  478. backdrop-filter: blur(5px);
  479. box-sizing: border-box;
  480. background: #00000066;
  481. padding: 20rpx 16rpx 8rpx;
  482. // gap: 26rpx;
  483. .book-duration{
  484. display: flex;
  485. gap: 8rpx;
  486. &-icon{
  487. width: 24rpx;
  488. height: 24rpx;
  489. }
  490. &-text{
  491. font-size: 20rpx;
  492. color: #DCDCDC;
  493. }
  494. }
  495. .book-title {
  496. margin-top: 10rpx;
  497. max-width: 220rpx;
  498. font-size: 24rpx;
  499. line-height: 1.4;
  500. color: #fff;
  501. // max-height: 68rpx; /* = line-height * 2(34rpx * 2)作为保险 */
  502. display: -webkit-box;
  503. -webkit-box-orient: vertical;
  504. -webkit-line-clamp: 2; /* 关键:显示两行,超过两行才省略 */
  505. overflow: hidden;
  506. word-break: break-word; /* 长单词也能断行 */
  507. white-space: normal; /* 允许换行 */
  508. }
  509. }
  510. }
  511. }
  512. }
  513. // 书籍网格布局
  514. .book-grid {
  515. display: flex;
  516. flex-wrap: wrap;
  517. padding: 0 30rpx;
  518. gap: 32rpx;
  519. .book-grid-item {
  520. width: 208rpx;
  521. display: flex;
  522. flex-direction: column;
  523. .book-grid-cover {
  524. box-shadow: 0px 4px 4px 0px #C0BCBA75;
  525. width: 100%;
  526. height: 278rpx;
  527. border-radius: 16rpx;
  528. overflow: hidden;
  529. margin-bottom: 16rpx;
  530. image {
  531. width: 100%;
  532. height: 100%;
  533. }
  534. }
  535. .book-grid-info {
  536. padding: 6rpx;
  537. .book-grid-title {
  538. font-size: 28rpx;
  539. font-weight: 700;
  540. color: $primary-text-color;
  541. margin-bottom: 14rpx;
  542. overflow: hidden;
  543. text-overflow: ellipsis;
  544. white-space: nowrap;
  545. }
  546. .book-grid-meta {
  547. display: flex;
  548. align-items: center;
  549. // gap: 16rpx;
  550. .book-grid-duration-icon {
  551. width: 24rpx;
  552. height: 24rpx;
  553. margin-right: 12rpx;
  554. }
  555. .book-grid-grade {
  556. font-size: 24rpx;
  557. color: $secondary-text-color;
  558. margin-right: 8rpx;
  559. }
  560. .book-grid-duration {
  561. font-size: 24rpx;
  562. color: $secondary-text-color;
  563. }
  564. }
  565. }
  566. }
  567. }
  568. // 推荐列表样式
  569. .recommend-list {
  570. padding: 0 30rpx;
  571. .recommend-item {
  572. width: 100%;
  573. height: 200rpx;
  574. margin-bottom: 48rpx;
  575. border-radius: 32rpx;
  576. overflow: hidden;
  577. &:last-child {
  578. margin-bottom: 0;
  579. }
  580. .recommend-image {
  581. width: 100%;
  582. height: 100%;
  583. }
  584. }
  585. }
  586. </style>