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

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