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

1354 lines
32 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 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" :class="{ 'tablet-swiper': isTablet }">
  40. <uv-swiper
  41. :list="bannerList"
  42. keyName="image"
  43. :height="swiperConfig.height"
  44. :radius="swiperConfig.radius"
  45. :previousMargin="swiperConfig.previousMargin"
  46. :nextMargin="swiperConfig.nextMargin"
  47. :displayMultipleItems="swiperConfig.displayMultipleItems"
  48. indicator
  49. indicatorInactiveColor="#fff"
  50. :loading="false"
  51. indicatorMode="dot"
  52. indicatorActiveColor="#F95A01"
  53. :autoplay="true"
  54. :interval="4000"
  55. :circular="true"
  56. @click="onBannerClick"
  57. ></uv-swiper>
  58. </view>
  59. <!-- 书本列表当选择非"全部"tab时显示 -->
  60. <view class="book-list-container" v-if="showBookList">
  61. <view class="book-list-results">
  62. <view
  63. v-for="(book, index) in bookList"
  64. :key="index"
  65. class="book-list-item"
  66. @click="goToBookDetail(book)"
  67. >
  68. <view class="book-list-cover">
  69. <image :src="book.booksImg" mode="aspectFill"></image>
  70. </view>
  71. <view class="book-list-info">
  72. <view class="book-list-title">{{ book.booksName }}</view>
  73. <view class="book-list-author">{{ book.booksAuthor }}</view>
  74. <view class="book-list-meta">
  75. <view class="book-list-duration">
  76. <image src="/static/play-icon.png" mode="aspectFill" class="book-list-icon"></image>
  77. <text>{{ book.duration }}</text>
  78. </view>
  79. <view class="book-list-membership" :class="classMap[book.vipInfo.title]">
  80. {{ book.vipInfo.title }}
  81. </view>
  82. </view>
  83. </view>
  84. </view>
  85. <uv-loading-icon text="加载中" textSize="30rpx" v-if="isLoadingBooks"></uv-loading-icon>
  86. <uv-empty v-else-if="bookList.length === 0"></uv-empty>
  87. </view>
  88. </view>
  89. <!-- 文章列表 -->
  90. <view class="article-section" v-if="articleList.length > 0 && !showBookList">
  91. <view class="section-header">
  92. <text class="section-title">精选文章 </text>
  93. </view>
  94. <scroll-view
  95. show-scrollbar="false"
  96. class="article-scroll"
  97. scroll-x="true"
  98. >
  99. <view class="article-list">
  100. <view
  101. v-for="(article, index) in articleList"
  102. :key="article.id"
  103. class="article-item"
  104. @click="goArticleDetail(article)"
  105. >
  106. <view class="article-content">
  107. <view class="article-title">{{ article.title }}</view>
  108. <view class="article-meta">
  109. <view class="article-tag">精选</view>
  110. <text class="article-time">{{ formatTime(article.createTime) }}</text>
  111. </view>
  112. </view>
  113. <view class="article-arrow">
  114. <uv-icon name="arrow-right" size="16" color="#ccc"></uv-icon>
  115. </view>
  116. </view>
  117. </view>
  118. </scroll-view>
  119. </view>
  120. <!-- 根据labelBooksData动态渲染书籍区块 -->
  121. <view
  122. v-for="(labelData, labelIndex) in labelBooksData"
  123. :key="labelIndex"
  124. class="section"
  125. v-if="!showBookList"
  126. >
  127. <view class="section-header" @click="goLabel(labelData.labelInfo)">
  128. <text class="section-title">{{ labelData.labelInfo.title }}</text>
  129. <view class="section-more">
  130. <text>更多</text>
  131. <uv-icon name="arrow-right" size="14" color="#888"></uv-icon>
  132. </view>
  133. </view>
  134. <!-- 第一个label今日更新样式 -->
  135. <!--
  136. <scroll-view
  137. v-if="labelIndex === 0"
  138. show-scrollbar="false"
  139. class="content-scroll"
  140. scroll-x="true"
  141. >
  142. <view class="content-list">
  143. <view
  144. v-for="(item, index) in labelData.books"
  145. :key="index"
  146. class="content-item"
  147. @click="goBook(item)"
  148. >
  149. <view class="item-cover">
  150. <image :src="item.booksImg || '/static/default-image.png'" mode="aspectFill"></image>
  151. </view>
  152. <view class="item-info">
  153. <text class="item-title">{{ item.booksName }}</text>
  154. <text class="item-author">{{ item.booksAuthor }}</text>
  155. <view class="item-duration">
  156. <image src="/static/play-icon.png" class="item-icon" />
  157. <text>{{ item.duration }}</text>
  158. </view>
  159. </view>
  160. </view>
  161. </view>
  162. </scroll-view> -->
  163. <!-- 第二个label推荐书籍样式 -->
  164. <scroll-view
  165. v-if="labelIndex === 0"
  166. show-scrollbar="false"
  167. class="content-scroll"
  168. scroll-x="true"
  169. >
  170. <view class="book-list">
  171. <view
  172. v-for="(book, index) in labelData.books"
  173. :key="index"
  174. class="book-item"
  175. @click="goBook(book)"
  176. >
  177. <view class="book-cover">
  178. <image :src="book.booksImg || '/static/default-image.png'" mode="aspectFill"></image>
  179. <view class="book-overlay">
  180. <view class="book-duration" v-if="book.duration">
  181. <image src="/static/alarm-icon.png" class="book-duration-icon" />
  182. <text class="book-duration-text">{{ book.duration }}</text>
  183. </view>
  184. <view class="book-title">{{ book.booksName }}</view>
  185. </view>
  186. </view>
  187. </view>
  188. </view>
  189. </scroll-view>
  190. <!-- 第三个及以后的label网格样式 -->
  191. <view v-else class="book-grid">
  192. <view
  193. v-for="(book, index) in labelData.books"
  194. :key="index"
  195. class="book-grid-item"
  196. @click="goBook(book)"
  197. >
  198. <view class="book-grid-cover">
  199. <image :src="book.booksImg || '/static/default-image.png'" mode="aspectFill"></image>
  200. </view>
  201. <!-- <view class="book-grid-info">
  202. <text class="book-grid-title">{{ book.booksName }}</text>
  203. <view class="book-grid-meta">
  204. <text class="book-grid-grade">{{ book.categoryName }}/</text>
  205. <image src="/static/play-icon.png" class="book-grid-duration-icon" />
  206. <text class="book-grid-duration">{{ book.duration }}</text>
  207. </view>
  208. </view> -->
  209. </view>
  210. </view>
  211. </view>
  212. <!-- 推荐内容列表 -->
  213. <view class="section" v-if="!showBookList">
  214. <view class="recommend-list">
  215. <view
  216. @click="goPlan(item.id, item.type)"
  217. v-for="(item, index) in recommendList"
  218. :key="index"
  219. class="recommend-item"
  220. >
  221. <image :src="item.img" mode="aspectFill" class="recommend-image"></image>
  222. </view>
  223. </view>
  224. </view>
  225. <!-- 视频播放弹窗 -->
  226. <uv-popup
  227. ref="videoModal"
  228. title="视频播放"
  229. :show-cancel-button="false"
  230. :show-confirm-button="false"
  231. :close-on-click-overlay="true"
  232. :safeAreaInsetBottom="false"
  233. @close="closeVideoModal"
  234. >
  235. <template #default>
  236. <view class="video-container">
  237. <video
  238. v-if="currentVideo"
  239. :src="currentVideo"
  240. controls
  241. autoplay
  242. :show-fullscreen-btn="true"
  243. :show-play-btn="true"
  244. :show-center-play-btn="true"
  245. style="width: 100%; height: 400rpx; border-radius: 8rpx;"
  246. @error="onVideoError"
  247. @play="onVideoPlay"
  248. @pause="onVideoPause"
  249. ></video>
  250. <view v-else class="video-loading">
  251. <text>视频加载中...</text>
  252. </view>
  253. </view>
  254. </template>
  255. </uv-popup>
  256. </view>
  257. </template>
  258. <script>
  259. import SplashScreen from '../components/SplashScreen.vue'
  260. export default {
  261. components: {
  262. SplashScreen
  263. },
  264. data() {
  265. return {
  266. // Tab数据
  267. tabs: [ ],
  268. activeTab: 0,
  269. // 轮播图数据
  270. bannerList: [
  271. ],
  272. // 书籍分类
  273. labels: [
  274. ],
  275. // 根据label获取的书籍数据(二维数组)
  276. labelBooksData: [],
  277. // 书本列表数据(用于tab切换时显示)
  278. bookList: [],
  279. isLoadingBooks: false,
  280. showBookList: false, // 控制是否显示书本列表
  281. // 类型映射表(从搜索页面复制)
  282. classMap: {
  283. '蕾朵会员': 'book-membership-premium',
  284. '盛放会员': 'book-membership-vip',
  285. '萌芽会员': 'book-membership-basic',
  286. },
  287. // 推荐列表数据
  288. recommendList: [
  289. ],
  290. // 文章列表数据
  291. articleList: [],
  292. currentVideo: '',
  293. // 设备类型检测
  294. isTablet: false
  295. }
  296. },
  297. computed: {
  298. // 轮播图配置
  299. swiperConfig() {
  300. if (this.isTablet) {
  301. // 平板设备使用卡片式轮播
  302. return {
  303. height: "200",
  304. radius: "16",
  305. previousMargin: "60",
  306. nextMargin: "60",
  307. displayMultipleItems: 1.5
  308. }
  309. } else {
  310. // 手机设备使用普通轮播
  311. return {
  312. height: "121",
  313. radius: "12",
  314. previousMargin: "0",
  315. nextMargin: "0",
  316. displayMultipleItems: 1
  317. }
  318. }
  319. }
  320. },
  321. methods: {
  322. // 检测设备类型
  323. detectDevice() {
  324. let screenWidth = 0
  325. // #ifdef H5
  326. const userAgent = navigator.userAgent
  327. screenWidth = window.innerWidth || document.documentElement.clientWidth
  328. const screenHeight = window.innerHeight || document.documentElement.clientHeight
  329. // 判断是否为平板设备
  330. // 1. 屏幕宽度大于768px
  331. // 2. 或者是iPad设备
  332. this.isTablet = screenWidth >= 768 || /iPad|Android.*(?=.*\b(tablet|pad)\b)/i.test(userAgent)
  333. console.log('设备检测结果:', {
  334. screenWidth,
  335. screenHeight,
  336. userAgent,
  337. isTablet: this.isTablet
  338. })
  339. // #endif
  340. // #ifndef H5
  341. // 非H5环境,通过系统信息判断
  342. const systemInfo = uni.getSystemInfoSync()
  343. screenWidth = systemInfo.screenWidth
  344. this.isTablet = screenWidth >= 768
  345. // #endif
  346. },
  347. // 開動頁面關閉處理
  348. onSplashClose() {
  349. console.log('開動頁面已關閉')
  350. // 可以在這裡添加其他邏輯,比如統計、初始化等
  351. },
  352. // 切换Tab
  353. async switchTab(index) {
  354. this.activeTab = index
  355. if (index === 0) {
  356. // 第一个tab(全部)显示原有内容
  357. this.showBookList = false
  358. await this.getBooksByLabels()
  359. } else {
  360. // 其他tab显示书本列表
  361. await this.getBookList()
  362. }
  363. },
  364. // 轮播图点击事件
  365. onBannerClick(index) {
  366. console.log('点击轮播图:', index)
  367. const bannerItem = this.bannerList[index]
  368. if (!bannerItem) return
  369. // 根据 typ 字段判断跳转类型
  370. switch(bannerItem.typ) {
  371. case '1': // 课程详情
  372. if (bannerItem.bookId) {
  373. uni.navigateTo({
  374. url: '/subPages/home/directory?id=' + bannerItem.bookId
  375. })
  376. }
  377. break
  378. case '2': // 视频播放
  379. if (bannerItem.video) {
  380. this.currentVideo = bannerItem.video
  381. this.$refs.videoModal.open()
  382. }
  383. break
  384. case '0': // 富文本内容
  385. if (bannerItem.content) {
  386. uni.navigateTo({
  387. url: '/subPages/home/richtext?content=' + encodeURIComponent(bannerItem.content)
  388. })
  389. }
  390. break
  391. default:
  392. console.log('未知的轮播图类型:', bannerItem.typ)
  393. }
  394. },
  395. // 跳转计划定制
  396. goPlan(id, type) {
  397. uni.navigateTo({
  398. url: '/subPages/home/plan?id=' + id + '&type=' + type
  399. })
  400. },
  401. goSearch() {
  402. uni.navigateTo({
  403. url: '/subPages/home/search'
  404. })
  405. },
  406. goBook(book) {
  407. uni.navigateTo({
  408. url: '/subPages/home/directory?id=' + book.id
  409. })
  410. },
  411. async getBanner() {
  412. const bannerRes = await this.$api.home.getBanner()
  413. if (bannerRes.code === 200){
  414. this.bannerList = bannerRes.result.map(item => ({
  415. image: item.img,
  416. title: item.title,
  417. typ: item.typ,
  418. bookId: item.bookId,
  419. video: item.video,
  420. content: item.content,
  421. id: item.id
  422. }))
  423. }
  424. },
  425. async getSignup() {
  426. const signupRes = await this.$api.home.getLink()
  427. if (signupRes.code === 200){
  428. this.recommendList = signupRes.result.map(item => ({
  429. img: item.img,
  430. id: item.id,
  431. type: item.type
  432. }))
  433. }
  434. },
  435. // 获取文章列表
  436. async getArticleList() {
  437. try {
  438. const articleRes = await this.$api.home.getArticle()
  439. if (articleRes.code === 200) {
  440. this.articleList = articleRes.result || []
  441. console.log('文章列表数据:', this.articleList)
  442. }
  443. } catch (error) {
  444. console.error('获取文章列表失败:', error)
  445. this.articleList = []
  446. }
  447. },
  448. // 获取书籍分类
  449. async getCategory() {
  450. const categoryRes = await this.$api.book.category()
  451. if (categoryRes.code === 200){
  452. // 硬编码第一个tab为"全部"
  453. this.tabs = [
  454. { title: '全部', id: null },
  455. ...categoryRes.result.map(item => ({
  456. title: item.title,
  457. id: item.id
  458. }))
  459. ]
  460. }
  461. },
  462. // 获取书籍标签
  463. async getLabel() {
  464. const labelRes = await this.$api.book.label()
  465. if (labelRes.code === 200){
  466. this.labels = labelRes.result.map(item => ({
  467. title:item.lable,
  468. id: item.id
  469. }))
  470. }
  471. },
  472. // 根据label数组获取书籍数据
  473. async getBooksByLabels() {
  474. if (!this.labels || this.labels.length === 0) {
  475. console.log('labels数据为空,无法获取书籍')
  476. return
  477. }
  478. try {
  479. // 创建请求数组,每个label发起一次请求
  480. const requests = this.labels.map(label =>
  481. this.$api.book.list({
  482. label: label.id,
  483. pageNo: 1,
  484. pageSize: 6,
  485. category: this.tabs[this.activeTab].id
  486. }, false)
  487. )
  488. // 并发执行所有请求
  489. const responses = await Promise.all(requests)
  490. // 创建二维数组存储结果
  491. this.labelBooksData = responses.map((response, index) => {
  492. if (response.code === 200) {
  493. return {
  494. labelInfo: this.labels[index],
  495. books: response.result.records || []
  496. }
  497. } else {
  498. console.error(`获取label ${this.labels[index].title} 的书籍失败:`, response)
  499. return {
  500. labelInfo: this.labels[index],
  501. books: []
  502. }
  503. }
  504. })
  505. console.log('根据label获取的书籍数据:', this.labelBooksData)
  506. } catch (error) {
  507. console.error('获取书籍数据失败:', error)
  508. this.labelBooksData = []
  509. }
  510. },
  511. goLabel(label){
  512. uni.navigateTo({
  513. url: '/subPages/home/search?label=' + label.id
  514. })
  515. },
  516. // 获取书本列表(用于tab切换)
  517. async getBookList() {
  518. if (this.activeTab === 0) {
  519. // 第一个tab(全部)不显示书本列表,显示原有内容
  520. this.showBookList = false
  521. return
  522. }
  523. this.isLoadingBooks = true
  524. this.showBookList = true
  525. try {
  526. const params = {
  527. category: this.tabs[this.activeTab].id,
  528. pageNo: 1,
  529. pageSize: 20
  530. }
  531. const res = await this.$api.book.list(params)
  532. if (res.code === 200) {
  533. this.bookList = res.result.records || []
  534. } else {
  535. this.bookList = []
  536. console.error('获取书本列表失败:', res)
  537. }
  538. } catch (error) {
  539. console.error('获取书本列表出错:', error)
  540. this.bookList = []
  541. } finally {
  542. this.isLoadingBooks = false
  543. }
  544. },
  545. // 跳转到书本详情(从搜索页面复制)
  546. goToBookDetail(book) {
  547. uni.navigateTo({
  548. url: '/subPages/home/directory?id=' + book.id
  549. })
  550. },
  551. // 跳转文章详情
  552. goArticleDetail(article) {
  553. console.log('点击文章:', article)
  554. uni.navigateTo({
  555. url: `/subPages/home/article?id=${article.id}`
  556. })
  557. },
  558. // 跳转更多文章页面
  559. goMoreArticles() {
  560. uni.navigateTo({
  561. url: '/subPages/home/articleList'
  562. })
  563. },
  564. // 格式化时间
  565. formatTime(timeStr) {
  566. if (!timeStr) return ''
  567. try {
  568. // 处理iOS兼容性问题,将 "2025-10-23 16:36:43" 格式转换为 "2025/10/23 16:36:43"
  569. let formattedTimeStr = timeStr.replace(/-/g, '/')
  570. const date = new Date(formattedTimeStr)
  571. // 检查日期是否有效
  572. if (isNaN(date.getTime())) {
  573. console.warn('Invalid date format:', timeStr)
  574. return ''
  575. }
  576. const now = new Date()
  577. const diff = now - date
  578. // 小于1分钟
  579. if (diff < 60000) {
  580. return '刚刚'
  581. }
  582. // 小于1小时
  583. if (diff < 3600000) {
  584. return Math.floor(diff / 60000) + '分钟前'
  585. }
  586. // 小于1天
  587. if (diff < 86400000) {
  588. return Math.floor(diff / 3600000) + '小时前'
  589. }
  590. // 小于7天
  591. if (diff < 604800000) {
  592. return Math.floor(diff / 86400000) + '天前'
  593. }
  594. // 超过7天显示具体日期
  595. const month = date.getMonth() + 1
  596. const day = date.getDate()
  597. return `${month}${day}`
  598. } catch (error) {
  599. return ''
  600. }
  601. },
  602. // 关闭视频弹窗
  603. closeVideoModal() {
  604. this.$refs.videoModal.close()
  605. this.currentVideo = ''
  606. },
  607. // 视频错误处理
  608. onVideoError(e) {
  609. console.error('视频播放错误:', e)
  610. uni.showToast({
  611. title: '视频播放失败',
  612. icon: 'error'
  613. })
  614. this.closeVideoModal()
  615. },
  616. // 视频开始播放
  617. onVideoPlay() {
  618. console.log('视频开始播放')
  619. },
  620. // 视频暂停
  621. onVideoPause() {
  622. console.log('视频暂停播放')
  623. }
  624. },
  625. async onShow() {
  626. // 检测设备类型
  627. this.detectDevice()
  628. // 先获取基础数据
  629. await Promise.all([this.getBanner(), this.getSignup(), this.getCategory(), this.getLabel(), this.getArticleList()])
  630. // 根据label数据获取对应的书籍
  631. await this.getBooksByLabels()
  632. },
  633. mounted() {
  634. // 页面挂载时也检测一次设备类型
  635. this.detectDevice()
  636. // #ifdef H5
  637. // 监听窗口大小变化
  638. window.addEventListener('resize', this.detectDevice)
  639. // #endif
  640. },
  641. beforeDestroy() {
  642. // #ifdef H5
  643. // 移除事件监听
  644. window.removeEventListener('resize', this.detectDevice)
  645. // #endif
  646. }
  647. }
  648. </script>
  649. <style lang="scss" scoped>
  650. .home-container {
  651. background: #fff;
  652. min-height: 100vh;
  653. padding-bottom: 80rpx;
  654. }
  655. // 顶部搜索栏
  656. .header {
  657. display: flex;
  658. align-items: center;
  659. padding: 6rpx 32rpx;
  660. background: #fff;
  661. .search-container {
  662. flex: 1;
  663. }
  664. }
  665. // Tab栏
  666. .tab-container {
  667. background: #fff;
  668. // border-bottom: 1px solid #f0f0f0;
  669. top: 0;
  670. left: 0;
  671. right: 0;
  672. z-index: 999;
  673. .tab-scroll {
  674. white-space: nowrap;
  675. .tab-list {
  676. display: flex;
  677. padding: 0 20rpx;
  678. .tab-item {
  679. flex-shrink: 0;
  680. padding: 20rpx 20rpx;
  681. font-size: 32rpx;
  682. color: #666;
  683. position: relative;
  684. &.active {
  685. color: $primary-text-color;
  686. font-weight: 700;
  687. &::after {
  688. content: '';
  689. position: absolute;
  690. bottom: 0;
  691. left: 50%;
  692. transform: translateX(-50%);
  693. width: 22rpx;
  694. height: 4rpx;
  695. background: $primary-text-color;
  696. border-radius: 2rpx;
  697. }
  698. }
  699. }
  700. }
  701. }
  702. }
  703. // 轮播图容器
  704. .swiper-container {
  705. margin: 20rpx;
  706. border-radius: 12rpx;
  707. overflow: hidden;
  708. // 平板设备的轮播图样式
  709. &.tablet-swiper {
  710. margin: 30rpx 0;
  711. border-radius: 16rpx;
  712. // 卡片式轮播的额外样式
  713. :deep(.uv-swiper) {
  714. .swiper-slide {
  715. transition: all 0.3s ease;
  716. transform-origin: center;
  717. // 非激活状态的卡片
  718. &:not(.swiper-slide-active) {
  719. transform: scale(0.9);
  720. opacity: 0.7;
  721. }
  722. // 激活状态的卡片
  723. &.swiper-slide-active {
  724. transform: scale(1);
  725. opacity: 1;
  726. z-index: 2;
  727. }
  728. }
  729. // 调整指示器位置
  730. .uv-swiper__indicator {
  731. bottom: -40rpx;
  732. }
  733. }
  734. }
  735. }
  736. .section-header {
  737. display: flex;
  738. align-items: center;
  739. justify-content: space-between;
  740. padding: 0 30rpx ;
  741. margin-bottom: 24rpx;
  742. .section-title {
  743. font-size: 36rpx;
  744. // font-weight: 600;
  745. color: $primary-text-color;
  746. }
  747. .section-more {
  748. display: flex;
  749. align-items: center;
  750. gap: 4rpx;
  751. text {
  752. font-size: 24rpx;
  753. color: $secondary-text-color;
  754. }
  755. }
  756. }
  757. // 内容区块
  758. .section {
  759. margin-top: 40rpx;
  760. .section-header {
  761. display: flex;
  762. align-items: center;
  763. justify-content: space-between;
  764. padding: 0 30rpx ;
  765. margin-bottom: 24rpx;
  766. .section-title {
  767. font-size: 36rpx;
  768. // font-weight: 600;
  769. color: $primary-text-color;
  770. }
  771. .section-more {
  772. display: flex;
  773. align-items: center;
  774. gap: 4rpx;
  775. text {
  776. font-size: 24rpx;
  777. color: $secondary-text-color;
  778. }
  779. }
  780. }
  781. .content-scroll {
  782. white-space: nowrap;
  783. }
  784. }
  785. // 今日更新列表
  786. .content-list {
  787. display: flex;
  788. padding: 0 30rpx;
  789. gap: 32rpx;
  790. .content-item {
  791. flex-shrink: 0;
  792. width: 602rpx;
  793. height: 212rpx;
  794. display: flex;
  795. align-items: center;
  796. background: #F8F8F8;
  797. padding: 16rpx;
  798. border-radius: 16rpx;
  799. gap: 16rpx;
  800. .item-cover {
  801. width: 136rpx;
  802. height: 200rpx;
  803. border-radius: 16rpx;
  804. // overflow: hidden;
  805. image {
  806. width: 136rpx;
  807. height: 200rpx;
  808. }
  809. }
  810. .item-info {
  811. // padding-top: 20rpx;
  812. gap: 16rpx;
  813. display: flex;
  814. flex-direction: column;
  815. .item-title {
  816. font-size: 32rpx;
  817. font-weight: 700;
  818. color: $primary-text-color;
  819. letter-spacing: 0;
  820. line-height: 48rpx;
  821. // margin-bottom: 12rpx;
  822. overflow: hidden;
  823. text-overflow: ellipsis;
  824. white-space: nowrap;
  825. }
  826. .item-author {
  827. font-size: 24rpx;
  828. color: $secondary-text-color;
  829. // margin-bottom: 8rpx;
  830. letter-spacing: 0;
  831. overflow: hidden;
  832. text-overflow: ellipsis;
  833. white-space: nowrap;
  834. }
  835. .item-duration {
  836. gap: 12rpx;
  837. display: flex;
  838. align-items: center;
  839. font-size: 22rpx;
  840. letter-spacing: 0;
  841. color: $secondary-text-color;
  842. .item-icon{
  843. width: 22rpx;
  844. height: 25rpx;
  845. }
  846. }
  847. }
  848. }
  849. }
  850. // 推荐书籍列表
  851. .book-list {
  852. display: flex;
  853. padding: 0 30rpx;
  854. gap: 32rpx;
  855. .book-item {
  856. flex-shrink: 0;
  857. width: 270rpx;
  858. transition: transform 0.3s ease, box-shadow 0.3s ease;
  859. &:active {
  860. transform: scale(0.98);
  861. }
  862. .book-cover {
  863. width: 100%;
  864. height: 360rpx;
  865. border-radius: 16rpx;
  866. overflow: hidden;
  867. position: relative;
  868. box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.15);
  869. transition: box-shadow 0.3s ease, transform 0.3s ease;
  870. &:active {
  871. box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.25);
  872. transform: translateY(-2rpx);
  873. }
  874. image {
  875. width: 100%;
  876. height: 100%;
  877. transition: transform 0.3s ease;
  878. }
  879. .book-overlay {
  880. position: absolute;
  881. bottom: 0;
  882. left: 0;
  883. right: 0;
  884. width: 100%;
  885. height: 140rpx;
  886. padding: 20rpx 16rpx 12rpx;
  887. box-sizing: border-box;
  888. /* 优化的渐变遮罩效果 */
  889. background: linear-gradient(
  890. 180deg,
  891. rgba(0, 0, 0, 0) 0%,
  892. rgba(0, 0, 0, 0.3) 30%,
  893. rgba(0, 0, 0, 0.7) 70%,
  894. rgba(0, 0, 0, 0.85) 100%
  895. );
  896. /* 增强的毛玻璃效果 */
  897. backdrop-filter: blur(8px) saturate(1.2);
  898. -webkit-backdrop-filter: blur(8px) saturate(1.2);
  899. /* 添加微妙的边框 */
  900. border-top: 1px solid rgba(255, 255, 255, 0.1);
  901. /* 平滑过渡效果 */
  902. transition: all 0.3s ease;
  903. .book-duration{
  904. display: flex;
  905. align-items: center;
  906. gap: 6rpx;
  907. margin-bottom: 8rpx;
  908. &-icon{
  909. width: 22rpx;
  910. height: 22rpx;
  911. opacity: 0.9;
  912. }
  913. &-text{
  914. font-size: 20rpx;
  915. font-weight: 500;
  916. color: rgba(255, 255, 255, 0.9);
  917. text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
  918. }
  919. }
  920. .book-title {
  921. max-width: 220rpx;
  922. font-size: 24rpx;
  923. font-weight: 600;
  924. line-height: 1.3;
  925. color: #ffffff;
  926. text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
  927. /* 文本截断优化 */
  928. display: -webkit-box;
  929. -webkit-box-orient: vertical;
  930. -webkit-line-clamp: 2;
  931. overflow: hidden;
  932. word-break: break-word;
  933. white-space: normal;
  934. }
  935. }
  936. }
  937. }
  938. }
  939. // 书籍网格布局
  940. .book-grid {
  941. display: flex;
  942. flex-wrap: wrap;
  943. padding: 0 30rpx;
  944. gap: 32rpx;
  945. .book-grid-item {
  946. width: 208rpx;
  947. display: flex;
  948. flex-direction: column;
  949. // backdrop-filter: red;
  950. .book-grid-cover {
  951. box-shadow: 0px 4px 4px 0px #C0BCBA75;
  952. width: 100%;
  953. height: 278rpx;
  954. border-radius: 16rpx;
  955. overflow: hidden;
  956. margin-bottom: 16rpx;
  957. image {
  958. width: 100%;
  959. height: 100%;
  960. }
  961. }
  962. .book-grid-info {
  963. width: 208rpx;
  964. padding: 6rpx;
  965. overflow: hidden;
  966. text-overflow: ellipsis;
  967. white-space: nowrap;
  968. .book-grid-title {
  969. font-size: 28rpx;
  970. font-weight: 700;
  971. color: $primary-text-color;
  972. margin-bottom: 14rpx;
  973. }
  974. .book-grid-meta {
  975. display: flex;
  976. align-items: center;
  977. // gap: 16rpx;
  978. .book-grid-duration-icon {
  979. width: 24rpx;
  980. height: 24rpx;
  981. margin-right: 12rpx;
  982. }
  983. .book-grid-grade {
  984. font-size: 24rpx;
  985. color: $secondary-text-color;
  986. margin-right: 8rpx;
  987. }
  988. .book-grid-duration {
  989. font-size: 24rpx;
  990. color: $secondary-text-color;
  991. }
  992. }
  993. }
  994. }
  995. }
  996. // 推荐列表样式
  997. .recommend-list {
  998. padding: 0 30rpx;
  999. .recommend-item {
  1000. width: 100%;
  1001. height: 200rpx;
  1002. margin-bottom: 48rpx;
  1003. border-radius: 32rpx;
  1004. overflow: hidden;
  1005. &:last-child {
  1006. margin-bottom: 0;
  1007. }
  1008. .recommend-image {
  1009. width: 100%;
  1010. height: 100%;
  1011. }
  1012. }
  1013. }
  1014. // 视频弹窗样式
  1015. .video-container {
  1016. position: relative;
  1017. // padding: 20rpx 0;
  1018. width: 90vw;
  1019. .video-loading {
  1020. display: flex;
  1021. align-items: center;
  1022. justify-content: center;
  1023. height: 400rpx;
  1024. background: #f5f5f5;
  1025. border-radius: 8rpx;
  1026. text {
  1027. font-size: 28rpx;
  1028. color: #999;
  1029. }
  1030. }
  1031. }
  1032. // 文章列表样式
  1033. .article-section {
  1034. margin-top: 40rpx;
  1035. .article-scroll {
  1036. white-space: nowrap;
  1037. }
  1038. .article-list {
  1039. display: flex;
  1040. padding: 0 30rpx;
  1041. gap: 24rpx;
  1042. .article-item {
  1043. flex-shrink: 0;
  1044. width: 580rpx;
  1045. height: 120rpx;
  1046. background: #f8f9fa;
  1047. border-radius: 16rpx;
  1048. padding: 24rpx;
  1049. display: flex;
  1050. align-items: center;
  1051. justify-content: space-between;
  1052. transition: all 0.3s ease;
  1053. &:active {
  1054. transform: scale(0.98);
  1055. background: #f0f1f2;
  1056. }
  1057. .article-content {
  1058. flex: 1;
  1059. display: flex;
  1060. flex-direction: column;
  1061. gap: 12rpx;
  1062. .article-title {
  1063. font-size: 32rpx;
  1064. font-weight: 600;
  1065. color: $primary-text-color;
  1066. line-height: 1.4;
  1067. display: -webkit-box;
  1068. -webkit-box-orient: vertical;
  1069. -webkit-line-clamp: 2;
  1070. overflow: hidden;
  1071. word-break: break-word;
  1072. }
  1073. .article-meta {
  1074. display: flex;
  1075. align-items: center;
  1076. gap: 16rpx;
  1077. .article-tag {
  1078. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  1079. color: #fff;
  1080. font-size: 20rpx;
  1081. padding: 4rpx 12rpx;
  1082. border-radius: 12rpx;
  1083. font-weight: 500;
  1084. }
  1085. .article-time {
  1086. font-size: 24rpx;
  1087. color: $secondary-text-color;
  1088. }
  1089. }
  1090. }
  1091. .article-arrow {
  1092. margin-left: 16rpx;
  1093. opacity: 0.6;
  1094. }
  1095. }
  1096. }
  1097. }
  1098. // 书本列表样式(从搜索页面复制)
  1099. .book-list-container {
  1100. background: #fff;
  1101. min-height: 50vh;
  1102. }
  1103. .book-list-results {
  1104. padding: 32rpx;
  1105. display: flex;
  1106. flex-direction: column;
  1107. gap: 32rpx;
  1108. }
  1109. .book-list-item {
  1110. display: flex;
  1111. align-items: center;
  1112. background: #F8F8F8;
  1113. height: 212rpx;
  1114. gap: 16rpx;
  1115. border-radius: 16rpx;
  1116. padding: 0rpx 16rpx;
  1117. &:last-child {
  1118. border-bottom: none;
  1119. }
  1120. .book-list-cover {
  1121. width: 136rpx;
  1122. height: 180rpx;
  1123. border-radius: 16rpx;
  1124. overflow: hidden;
  1125. margin-right: 16rpx;
  1126. image {
  1127. width: 100%;
  1128. height: 100%;
  1129. }
  1130. }
  1131. .book-list-info {
  1132. flex: 1;
  1133. display: flex;
  1134. flex-direction: column;
  1135. justify-content: space-between;
  1136. }
  1137. .book-list-title {
  1138. font-size: 32rpx;
  1139. font-weight: 600;
  1140. color: $primary-text-color;
  1141. line-height: 48rpx;
  1142. letter-spacing: 0;
  1143. margin-bottom: 12rpx;
  1144. overflow: hidden;
  1145. text-overflow: ellipsis;
  1146. }
  1147. .book-list-author {
  1148. font-size: 24rpx;
  1149. color: $secondary-text-color;
  1150. margin-bottom: 16rpx;
  1151. overflow: hidden;
  1152. text-overflow: ellipsis;
  1153. white-space: nowrap;
  1154. }
  1155. .book-list-meta {
  1156. display: flex;
  1157. align-items: center;
  1158. justify-content: space-between;
  1159. }
  1160. .book-list-duration {
  1161. display: flex;
  1162. align-items: center;
  1163. font-size: 22rpx;
  1164. color: #999;
  1165. .book-list-icon {
  1166. width: 18rpx;
  1167. height: 18rpx;
  1168. }
  1169. text {
  1170. margin-left: 8rpx;
  1171. }
  1172. }
  1173. .book-list-membership {
  1174. padding: 8rpx 16rpx;
  1175. border-radius: 8rpx;
  1176. font-size: 24rpx;
  1177. color: #211508;
  1178. }
  1179. }
  1180. .book-membership-premium {
  1181. background: #E9F1FF;
  1182. border: 2rpx solid #C4DAFF;
  1183. }
  1184. .book-membership-vip {
  1185. background: #FFF4E9;
  1186. border: 2rpx solid #FFE2C4;
  1187. }
  1188. .book-membership-basic {
  1189. background: #FFE9E9;
  1190. border: 2rpx solid #FFDBC4;
  1191. }
  1192. </style>