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

1034 lines
24 KiB

  1. <template>
  2. <view class="book-container">
  3. <!-- 自定义顶部导航栏 -->
  4. <uv-status-bar></uv-status-bar>
  5. <view class="custom-navbar" :class="{ 'navbar-hidden': !showNavbar }">
  6. <uv-status-bar></uv-status-bar>
  7. <view class="navbar-content">
  8. <view class="navbar-left" @click="goBack">
  9. <uv-icon name="arrow-left" size="20" color="#262626"></uv-icon>
  10. </view>
  11. <view class="navbar-title">{{ currentPageTitle }}</view>
  12. </view>
  13. </view>
  14. <!-- Swiper内容区域 -->
  15. <swiper
  16. class="content-swiper"
  17. :current="currentPage - 1"
  18. @change="onSwiperChange"
  19. >
  20. <swiper-item
  21. v-for="(page, index) in bookPages"
  22. :key="index"
  23. class="swiper-item"
  24. >
  25. <view class="content-area" @click="toggleNavbar">
  26. <!-- 图片卡片页面 -->
  27. <view v-for="(item, index) in page" :key="index">
  28. <view v-if="item.type === 'card'" class="card-content">
  29. <view class="card-line">
  30. <image src="/static/划重点图标.png" class="card-line-image" mode="aspectFill" />
  31. <text class="card-line-text">划线重点</text>
  32. </view>
  33. <image class="card-image" :src="item.image" mode="aspectFill"></image>
  34. <text class="english-text" user-select @longpress="showWordMeaning(item.wordMeaning)">{{ item.englishText }}</text>
  35. <text class="chinese-text" user-select>{{ item.chineseText }}</text>
  36. </view>
  37. <!-- 文本页面 -->
  38. <view v-else-if="item.type === 'text'" class="text-content">
  39. <text class="content-text" user-select>{{ item.content }}</text>
  40. </view>
  41. <!-- 文本页面 -->
  42. <view v-else-if="item.type === 'image'" class="image-container">
  43. <image class="content-image" :src="item.imageUrl" mode="aspectFill"></image>
  44. </view>
  45. <!-- 视频页面 -->
  46. <view v-else-if="item.type === 'video'" class="video-content">
  47. <video :src="item.video" class="video-player" controls :poster="item.poster"></video>
  48. </view>
  49. <!-- 会员限制页面 -->
  50. <view v-else-if="item.type === 'member'" class="member-content">
  51. <text class="member-title">{{ item.title }}</text>
  52. <view class="member-button" @click="unlockBook">
  53. <text class="member-button-text">{{ item.buttonText }}</text>
  54. </view>
  55. </view>
  56. </view>
  57. </view>
  58. </swiper-item>
  59. </swiper>
  60. <!-- 自定义底部控制栏 -->
  61. <view class="custom-tabbar" :class="{ 'tabbar-hidden': !showNavbar }">
  62. <!-- 音频控制栏 -->
  63. <view class="audio-controls" :class="{ 'audio-hidden': !isTextPage }">
  64. <view class="audio-time">
  65. <text class="time-text">{{ formatTime(currentTime) }}</text>
  66. <view class="progress-container">
  67. <view class="progress-bar">
  68. <view class="progress-fill" :style="{ width: progressPercent + '%' }"></view>
  69. <view class="progress-thumb" :style="{ left: progressPercent + '%' }"></view>
  70. </view>
  71. </view>
  72. <text class="time-text">{{ formatTime(totalTime) }}</text>
  73. </view>
  74. <view class="audio-controls-row">
  75. <view class="control-btn" @click="toggleLoop">
  76. <uv-icon name="reload" size="20" :color="isLoop ? '#06DADC' : '#999'"></uv-icon>
  77. <text class="control-text">循环</text>
  78. </view>
  79. <view class="control-btn" @click="previousPage">
  80. <text class="control-text">上一页</text>
  81. </view>
  82. <view class="play-btn" @click="togglePlay">
  83. <uv-icon :name="isPlaying ? 'pause-circle-fill' : 'play-circle-fill'" size="40" color="#666"></uv-icon>
  84. </view>
  85. <view class="control-btn" @click="nextPage">
  86. <text class="control-text">下一页</text>
  87. </view>
  88. <view class="control-btn" @click="toggleSpeed">
  89. <text class="control-text">{{ playSpeed }}x</text>
  90. </view>
  91. </view>
  92. </view>
  93. <view style="background-color: #fff;position: relative;z-index: 100" >
  94. <view class="tabbar-content">
  95. <view class="tabbar-left">
  96. <view class="tab-button" @click="toggleCoursePopup">
  97. <image src="/static/课程图标.png" class="tab-icon" />
  98. <text class="tab-text">课程</text>
  99. </view>
  100. <view class="tab-button" @click="toggleSound">
  101. <image src="/static/音色切换图标.png" class="tab-icon" />
  102. <text class="tab-text">音色切换</text>
  103. </view>
  104. </view>
  105. <view class="tabbar-right">
  106. <view class="page-controls">
  107. <view class="page-numbers">
  108. <view
  109. v-for="(page, index) in bookPages"
  110. :key="index"
  111. class="page-number"
  112. :class="{ 'active': (index + 1) === currentPage }"
  113. @click="goToPage(index + 1)"
  114. >
  115. {{ index + 1 }}
  116. </view>
  117. </view>
  118. </view>
  119. </view>
  120. </view>
  121. <uv-safe-bottom></uv-safe-bottom>
  122. </view>
  123. </view>
  124. <!-- 课程选择弹出窗 -->
  125. <uv-popup
  126. mode="bottom"
  127. ref="coursePopup"
  128. round="32rpx"
  129. bg-color="#f8f8f8"
  130. >
  131. <view class="course-popup">
  132. <view class="popup-header">
  133. <view>
  134. <uv-icon name="arrow-down" color="black" size="20"></uv-icon>
  135. </view>
  136. <view class="popup-title">课程</view>
  137. <view class="popup-title" @click="toggleSort">
  138. 倒序
  139. </view>
  140. </view>
  141. <view class="course-list">
  142. <view
  143. v-for="(course, index) in displayCourseList"
  144. :key="course.id"
  145. class="course-item"
  146. :class="{ 'active': course.id === currentCourse }"
  147. @click="selectCourse(course.id)"
  148. >
  149. <view class="course-number " :class="{ 'highlight': course.id === currentCourse }">{{ String(course.index).padStart(2, '0') }}</view>
  150. <view class="course-content">
  151. <view class="course-english" :class="{ 'highlight': course.id === currentCourse }">{{ course.english }}</view>
  152. <view class="course-chinese" :class="{ 'highlight': course.id === currentCourse }">{{ course.chinese }}</view>
  153. </view>
  154. </view>
  155. </view>
  156. </view>
  157. </uv-popup>
  158. <!-- 释义弹出窗 -->
  159. <uv-popup
  160. mode="bottom"
  161. ref="meaningPopup"
  162. round="32rpx"
  163. bg-color="#FFFFFF"
  164. :overlay="true"
  165. >
  166. <view class="meaning-popup" v-if="currentWordMeaning">
  167. <view class="meaning-header">
  168. <view class="close-btn" @click="closeMeaningPopup">
  169. <text class="close-text">关闭</text>
  170. </view>
  171. <view class="meaning-title">释义</view>
  172. <view style="width: 80rpx;"></view>
  173. </view>
  174. <view class="meaning-content">
  175. <image class="meaning-image" src="/static/默认图片.png" mode="aspectFill"></image>
  176. <view class="word-info">
  177. <view class="word-main">
  178. <text class="word-text">{{ currentWordMeaning.word }}</text>
  179. </view>
  180. <view class="phonetic-container">
  181. <uv-icon name="volume-fill" size="16" color="#000"></uv-icon>
  182. <text class="phonetic-text">{{ currentWordMeaning.phonetic }}</text>
  183. </view>
  184. <view class="word-meaning">
  185. <text class="part-of-speech">{{ currentWordMeaning.partOfSpeech }}</text>
  186. <text class="meaning-text">{{ currentWordMeaning.meaning }}</text>
  187. </view>
  188. </view>
  189. <view class="knowledge-gain">
  190. <view class="knowledge-header">
  191. <image src="/static/知识收获图标.png" class="knowledge-icon" mode="aspectFill" />
  192. <text class="knowledge-title">知识收获</text>
  193. </view>
  194. <text class="knowledge-content">{{ currentWordMeaning.knowledgeGain }}</text>
  195. </view>
  196. </view>
  197. </view>
  198. </uv-popup>
  199. </view>
  200. </template>
  201. <script>
  202. export default {
  203. data() {
  204. return {
  205. courseId: '',
  206. showNavbar: true,
  207. currentPage: 1,
  208. currentCourse: 1, // 当前课程索引
  209. currentWordMeaning: null, // 当前显示的单词释义
  210. isReversed: false, // 是否倒序显示
  211. // 音频控制相关数据
  212. isPlaying: false,
  213. currentTime: 0,
  214. totalTime: 317, // 5:17 = 317秒
  215. isLoop: false,
  216. playSpeed: 1.0,
  217. speedOptions: [1.0, 1.25, 1.5, 2.0],
  218. courseIdList: [],
  219. bookTitle: '',
  220. courseList: [
  221. ],
  222. // 二维数组 代表每个页面
  223. bookPages: [
  224. ],
  225. // 存储每个页面的标题
  226. pageTitles: [],
  227. }
  228. },
  229. computed: {
  230. displayCourseList() {
  231. return this.isReversed ? [...this.courseList].reverse() : this.courseList;
  232. },
  233. // 判断当前页面是否为文字类型
  234. isTextPage() {
  235. const currentPageData = this.bookPages[this.currentPage - 1];
  236. // currentPageData是一个数组 其中的一个元素的type是text就会返回true
  237. return currentPageData && currentPageData.some(item => item.type === 'text');
  238. },
  239. // 计算音频播放进度百分比
  240. progressPercent() {
  241. return this.totalTime > 0 ? (this.currentTime / this.totalTime) * 100 : 0;
  242. },
  243. // 动态页面标题
  244. currentPageTitle() {
  245. return this.pageTitles[this.currentPage - 1] || this.bookTitle;
  246. }
  247. },
  248. methods: {
  249. toggleNavbar() {
  250. this.showNavbar = !this.showNavbar
  251. },
  252. goBack() {
  253. uni.navigateBack()
  254. },
  255. toggleCoursePopup() {
  256. if (this.$refs.coursePopup) {
  257. this.$refs.coursePopup.open()
  258. }
  259. // console.log('123123123');
  260. },
  261. toggleSort() {
  262. this.isReversed = !this.isReversed
  263. },
  264. selectCourse(courseId) {
  265. this.currentCourse = courseId
  266. if (this.$refs.coursePopup) {
  267. this.$refs.coursePopup.close()
  268. }
  269. // 这里可以添加切换课程的逻辑
  270. // console.log('选择课程:', courseId)
  271. this.getCourseList(courseId)
  272. },
  273. showWordMeaning(wordMeaning) {
  274. if (wordMeaning) {
  275. this.currentWordMeaning = wordMeaning
  276. if (this.$refs.meaningPopup) {
  277. this.$refs.meaningPopup.open()
  278. }
  279. }
  280. },
  281. closeMeaningPopup() {
  282. if (this.$refs.meaningPopup) {
  283. this.$refs.meaningPopup.close()
  284. }
  285. this.currentWordMeaning = null
  286. },
  287. // 音频控制方法
  288. togglePlay() {
  289. this.isPlaying = !this.isPlaying;
  290. // 这里可以添加实际的音频播放/暂停逻辑
  291. },
  292. toggleLoop() {
  293. this.isLoop = !this.isLoop;
  294. },
  295. toggleSpeed() {
  296. const currentIndex = this.speedOptions.indexOf(this.playSpeed);
  297. const nextIndex = (currentIndex + 1) % this.speedOptions.length;
  298. this.playSpeed = this.speedOptions[nextIndex];
  299. },
  300. async previousPage() {
  301. if (this.currentPage > 1) {
  302. this.currentPage--;
  303. // 获取对应页面的数据(如果还没有获取过)
  304. if (this.courseIdList[this.currentPage - 1] && this.bookPages[this.currentPage - 1].length === 0) {
  305. await this.getBookPages(this.courseIdList[this.currentPage - 1]);
  306. }
  307. }
  308. },
  309. async nextPage() {
  310. if (this.currentPage < this.bookPages.length) {
  311. this.currentPage++;
  312. // 获取对应页面的数据(如果还没有获取过)
  313. if (this.courseIdList[this.currentPage - 1] && this.bookPages[this.currentPage - 1].length === 0) {
  314. await this.getBookPages(this.courseIdList[this.currentPage - 1]);
  315. }
  316. }
  317. },
  318. formatTime(seconds) {
  319. const mins = Math.floor(seconds / 60);
  320. const secs = Math.floor(seconds % 60);
  321. return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
  322. },
  323. toggleSound() {
  324. console.log('音色切换')
  325. uni.navigateTo({
  326. url: '/subPages/home/music'
  327. })
  328. },
  329. unlockBook() {
  330. console.log('解锁全书')
  331. // 这里可以跳转到会员页面或者调用解锁接口
  332. uni.navigateTo({
  333. url: '/pages/index/member'
  334. })
  335. },
  336. async goToPage(page) {
  337. this.currentPage = page
  338. console.log('跳转到页面:', page)
  339. // 获取对应页面的数据(如果还没有获取过)
  340. if (this.courseIdList[this.currentPage - 1] && this.bookPages[this.currentPage - 1].length === 0) {
  341. await this.getBookPages(this.courseIdList[this.currentPage - 1]);
  342. }
  343. },
  344. async onSwiperChange(e) {
  345. this.currentPage = e.detail.current + 1
  346. // 获取对应页面的数据(如果还没有获取过)
  347. if (this.courseIdList[this.currentPage - 1] && this.bookPages[this.currentPage - 1].length === 0) {
  348. await this.getBookPages(this.courseIdList[this.currentPage - 1]);
  349. }
  350. },
  351. async getCourseList(id) {
  352. const res = await this.$api.book.coursePage({
  353. id: id
  354. })
  355. if (res.code === 200) {
  356. this.courseIdList = res.result.map(item => item.id)
  357. // 初始化二维数组 换一种方式
  358. this.bookPages = this.courseIdList.map(() => [])
  359. // 初始化标题数组
  360. this.pageTitles = this.courseIdList.map(() => '')
  361. // 初始化第一页
  362. if (this.courseIdList.length > 0) {
  363. await this.getBookPages(this.courseIdList[0])
  364. }
  365. }
  366. },
  367. async getBookPages(id) {
  368. const res = await this.$api.book.coursesPageDetail({
  369. id: id
  370. })
  371. if (res.code === 200) {
  372. // 使用$set确保响应式更新
  373. // 确保当前页面存在
  374. if (this.currentPage - 1 < this.bookPages.length) {
  375. this.$set(this.bookPages, this.currentPage - 1, JSON.parse(res.result.content))
  376. // 保存页面标题
  377. this.$set(this.pageTitles, this.currentPage - 1, res.result.title || '')
  378. }
  379. }
  380. },
  381. // 获取课程列表
  382. async getCoursePageList (bookId) {
  383. const res = await this.$api.book.course({
  384. id: bookId
  385. })
  386. if (res.code === 200) {
  387. this.courseList = res.result.records
  388. // 打上序列号
  389. this.courseList = this.courseList.map((item, index) => ({
  390. ...item,
  391. index,
  392. }))
  393. }
  394. }
  395. },
  396. async onLoad(args) {
  397. this.courseId = args.courseId
  398. // 先获取点进来的课程的页面列表
  399. await Promise.all([this.getCourseList(this.courseId), this.getCoursePageList(args.bookId)])
  400. }
  401. }
  402. </script>
  403. <style lang="scss" scoped>
  404. .book-container {
  405. width: 100%;
  406. height: 100vh;
  407. background-color: #F8F8F8;
  408. position: relative;
  409. overflow: hidden;
  410. }
  411. .custom-navbar {
  412. position: fixed;
  413. top: 0;
  414. left: 0;
  415. right: 0;
  416. background-color: #F8F8F8;
  417. z-index: 1000;
  418. transition: transform 0.3s ease;
  419. &.navbar-hidden {
  420. transform: translateY(-100%);
  421. }
  422. }
  423. .navbar-content {
  424. display: flex;
  425. align-items: center;
  426. justify-content: space-between;
  427. padding: 20rpx 32rpx;
  428. // padding-top: calc(20rpx + var(--status-bar-height, 0));
  429. height: 60rpx;
  430. }
  431. .navbar-left,
  432. .navbar-right {
  433. width: 80rpx;
  434. display: flex;
  435. align-items: center;
  436. }
  437. .navbar-right {
  438. justify-content: flex-end;
  439. flex: 1;
  440. }
  441. .navbar-title {
  442. transform: translateX(-50rpx);
  443. flex: 1;
  444. text-align: center;
  445. font-family: PingFang SC;
  446. font-weight: 500;
  447. font-size: 32rpx;
  448. color: #262626;
  449. line-height: 48rpx;
  450. }
  451. .content-swiper {
  452. flex: 1;
  453. height: calc(100vh - 100rpx);
  454. // margin-top: 100rpx;
  455. margin-bottom: 100rpx;
  456. }
  457. .swiper-item {
  458. height: 100%;
  459. }
  460. .content-area {
  461. flex: 1;
  462. padding: 0 40rpx;
  463. padding-top: 100rpx;
  464. // background: linear-gradient(180deg, #DEFFFF 0%, #FBFEFF 22.65%, #F0FBFF 100%);
  465. height: 100%;
  466. box-sizing: border-box;
  467. overflow-y: auto;
  468. }
  469. .card-content {
  470. background: linear-gradient(180deg, #DEFFFF 0%, #FBFEFF 22.65%, #F0FBFF 100%);
  471. display: flex;
  472. flex-direction: column;
  473. gap: 32rpx;
  474. height: 1172rpx;
  475. margin-top: 20rpx;
  476. border-radius: 32rpx;
  477. // height: 100%;
  478. padding: 40rpx;
  479. // margin: 0
  480. border: 1px solid #FFFFFF;
  481. box-sizing: border-box;
  482. .card-line {
  483. display: flex;
  484. align-items: center;
  485. // margin-bottom: 20rpx;
  486. }
  487. .card-line-image {
  488. width: 48rpx;
  489. height: 48rpx;
  490. margin-right: 16rpx;
  491. }
  492. .card-line-text {
  493. font-family: PingFang SC;
  494. font-weight: 600;
  495. font-size: 30rpx;
  496. line-height: 48rpx;
  497. color: #3B3D3D;
  498. }
  499. .card-image {
  500. width: 590rpx;
  501. height: 268rpx;
  502. border-radius: 24rpx;
  503. // margin-bottom: 20rpx;
  504. }
  505. .english-text {
  506. display: block;
  507. font-family: PingFang SC;
  508. font-weight: 600;
  509. font-size: 32rpx;
  510. line-height: 48rpx;
  511. color: #3B3D3D;
  512. // margin-bottom: 16rpx;
  513. }
  514. .chinese-text {
  515. display: block;
  516. font-family: PingFang SC;
  517. font-weight: 400;
  518. font-size: 28rpx;
  519. line-height: 48rpx;
  520. color: #4F4F4F;
  521. }
  522. }
  523. /* 会员限制页面样式 */
  524. .member-content {
  525. display: flex;
  526. flex-direction: column;
  527. align-items: center;
  528. justify-content: center;
  529. height: 90%;
  530. background-color: #F8F8F8;
  531. padding: 40rpx;
  532. margin: -40rpx;
  533. box-sizing: border-box;
  534. }
  535. .member-title {
  536. font-family: PingFang SC;
  537. font-weight: 500;
  538. font-size: 40rpx;
  539. line-height: 1;
  540. color: #6f6f6f;
  541. text-align: center;
  542. margin-bottom: 48rpx;
  543. }
  544. .member-button {
  545. width: 670rpx;
  546. height: 72rpx;
  547. background: #06DADC;
  548. border-radius: 200rpx;
  549. display: flex;
  550. align-items: center;
  551. justify-content: center;
  552. }
  553. .member-button-text {
  554. font-family: PingFang SC;
  555. font-weight: 400;
  556. font-size: 30rpx;
  557. color: #FFFFFF;
  558. }
  559. .video-content {
  560. width: 100vw;
  561. margin: 200rpx -40rpx 0;
  562. height: 500rpx;
  563. background-color: #FFFFFF;
  564. // padding: 40rpx;
  565. border-radius: 24rpx;
  566. display: flex;
  567. align-items: center;
  568. justify-content: center;
  569. .video-player{
  570. width: 670rpx;
  571. margin: 0 auto;
  572. height: 376rpx;
  573. }
  574. }
  575. .text-content {
  576. width: 100vw;
  577. background-color: #F6F6F6;
  578. height: 100%;
  579. padding: 40rpx;
  580. margin: -40rpx;
  581. box-sizing: border-box;
  582. }
  583. .content-text {
  584. font-family: PingFang SC;
  585. // font-weight: 400;
  586. font-size: 28rpx;
  587. color: #3B3D3D;
  588. line-height: 48rpx;
  589. letter-spacing: 0;
  590. text-align: justify;
  591. word-break: break-all;
  592. }
  593. .custom-tabbar {
  594. position: fixed;
  595. bottom: 0;
  596. left: 0;
  597. right: 0;
  598. // background-color: #fff;
  599. border-top: 1rpx solid #EEEEEE;
  600. z-index: 1000;
  601. transition: transform 0.3s ease;
  602. &.tabbar-hidden {
  603. transform: translateY(100%);
  604. }
  605. }
  606. .tabbar-content {
  607. display: flex;
  608. align-items: center;
  609. justify-content: space-between;
  610. padding: 24rpx 62rpx;
  611. // z-index: 100;
  612. // position: relative;
  613. // background-color: #fff;
  614. // padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
  615. height: 88rpx;
  616. }
  617. .tabbar-left {
  618. display: flex;
  619. align-items: center;
  620. gap: 35rpx;
  621. }
  622. .tab-button {
  623. display: flex;
  624. align-items: center;
  625. flex-direction: column;
  626. gap: 8rpx;
  627. }
  628. .tab-icon {
  629. width: 52rpx;
  630. height: 52rpx;
  631. }
  632. .tab-text {
  633. font-family: PingFang SC;
  634. // font-weight: 400;
  635. font-size: 22rpx;
  636. color: #999;
  637. line-height: 24rpx;
  638. }
  639. .tabbar-right {
  640. flex: 1;
  641. display: flex;
  642. justify-content: flex-end;
  643. }
  644. .page-controls {
  645. display: flex;
  646. align-items: center;
  647. }
  648. .page-numbers {
  649. display: flex;
  650. align-items: center;
  651. gap: 8rpx;
  652. overflow-x: auto;
  653. max-width: 400rpx;
  654. &::-webkit-scrollbar {
  655. display: none;
  656. }
  657. }
  658. .page-number {
  659. min-width: 84rpx;
  660. height: 58rpx;
  661. display: flex;
  662. align-items: center;
  663. justify-content: center;
  664. border-radius: 100rpx;
  665. font-family: PingFang SC;
  666. // font-weight: 400;
  667. font-size: 30rpx;
  668. color: #3B3D3D;
  669. background-color: transparent;
  670. border: 1px solid #3B3D3D;
  671. transition: all 0.3s ease;
  672. &.active {
  673. border: 1px solid $primary-color;
  674. color: $primary-color;
  675. }
  676. }
  677. /* 课程弹出窗样式 */
  678. .course-popup {
  679. padding: 0 32rpx;
  680. max-height: 80vh;
  681. }
  682. .popup-header {
  683. display: flex;
  684. justify-content: space-between;
  685. align-items: center;
  686. padding: 20rpx 0;
  687. border-bottom: 2rpx solid #EEEEEE
  688. // margin-bottom: 40rpx;
  689. }
  690. .popup-title {
  691. font-family: PingFang SC;
  692. font-weight: 500;
  693. font-size: 34rpx;
  694. color: #181818;
  695. }
  696. .course-list {
  697. max-height: 60vh;
  698. overflow-y: auto;
  699. }
  700. .course-item {
  701. display: flex;
  702. align-items: center;
  703. gap: 24rpx;
  704. padding-top: 24rpx;
  705. padding-right: 8rpx;
  706. padding-bottom: 24rpx;
  707. padding-left: 8rpx;
  708. border-bottom: 1px solid #EEEEEE;
  709. cursor: pointer;
  710. &:last-child {
  711. border-bottom: none;
  712. }
  713. }
  714. .course-number {
  715. width: 80rpx;
  716. font-family: PingFang SC;
  717. // font-weight: 400;
  718. font-size: 36rpx;
  719. color: #999;
  720. &.highlight {
  721. color: $primary-color;
  722. }
  723. // margin-right: 24rpx;
  724. }
  725. .course-content {
  726. flex: 1;
  727. }
  728. .course-english {
  729. font-family: PingFang SC;
  730. font-weight: 600;
  731. font-size: 36rpx;
  732. line-height: 44rpx;
  733. color: #252545;
  734. margin-bottom: 8rpx;
  735. &.highlight {
  736. color: $primary-color;
  737. }
  738. }
  739. .course-chinese {
  740. font-size: 28rpx;
  741. line-height: 48rpx;
  742. color: #3B3D3D;
  743. &.highlight {
  744. color: $primary-color;
  745. }
  746. }
  747. /* 释义弹窗样式 */
  748. .meaning-popup {
  749. // width: 670rpx;
  750. background-color: #FFFFFF;
  751. // border-radius: 32rpx;
  752. overflow: hidden;
  753. }
  754. .meaning-header {
  755. display: flex;
  756. justify-content: space-between;
  757. align-items: center;
  758. padding: 32rpx;
  759. border-bottom: 2rpx solid #EEEEEE;
  760. }
  761. .close-btn {
  762. width: 80rpx;
  763. }
  764. .close-text {
  765. font-family: PingFang SC;
  766. font-size: 32rpx;
  767. color: #8b8b8b;
  768. }
  769. .meaning-title {
  770. font-family: PingFang SC;
  771. font-weight: 500;
  772. font-size: 34rpx;
  773. color: #181818;
  774. }
  775. .meaning-content {
  776. padding: 32rpx;
  777. }
  778. .meaning-image {
  779. width: 670rpx;
  780. height: 268rpx;
  781. border-radius: 24rpx;
  782. margin-bottom: 32rpx;
  783. }
  784. .word-info {
  785. margin-bottom: 32rpx;
  786. }
  787. .word-main {
  788. display: flex;
  789. align-items: center;
  790. gap: 16rpx;
  791. margin-bottom: 8rpx;
  792. }
  793. .word-text {
  794. font-family: PingFang SC;
  795. font-weight: 500;
  796. font-size: 40rpx;
  797. color: #181818;
  798. }
  799. .phonetic-container{
  800. display: flex;
  801. gap: 24rpx;
  802. .phonetic-text {
  803. font-family: PingFang SC;
  804. font-size: 28rpx;
  805. color: #262626;
  806. margin-bottom: 16rpx;
  807. }
  808. }
  809. .word-meaning {
  810. display: flex;
  811. gap: 16rpx;
  812. }
  813. .part-of-speech {
  814. font-family: PingFang SC;
  815. font-size: 28rpx;
  816. color: #262626;
  817. }
  818. .meaning-text {
  819. font-family: PingFang SC;
  820. font-size: 28rpx;
  821. color: #181818;
  822. flex: 1;
  823. }
  824. .knowledge-gain {
  825. background: linear-gradient(180deg, #DEFFFF 0%, #FBFEFF 22.65%, #F0FBFF 100%);
  826. border-radius: 24rpx;
  827. padding: 32rpx 40rpx;
  828. }
  829. .knowledge-header {
  830. display: flex;
  831. align-items: center;
  832. gap: 16rpx;
  833. margin-bottom: 20rpx;
  834. }
  835. .knowledge-icon {
  836. width: 48rpx;
  837. height: 48rpx;
  838. }
  839. .knowledge-title {
  840. font-family: PingFang SC;
  841. font-weight: 600;
  842. font-size: 30rpx;
  843. color: #3B3D3D;
  844. }
  845. .knowledge-content {
  846. font-family: PingFang SC;
  847. font-size: 28rpx;
  848. color: #4f4f4f;
  849. line-height: 48rpx;
  850. }
  851. /* 音频控制栏样式 */
  852. .audio-controls {
  853. background: #fff;
  854. padding: 20rpx 40rpx;
  855. border-bottom: 1rpx solid #eee;
  856. transition: transform 0.3s ease;
  857. position: relative;
  858. z-index: 10;
  859. &.audio-hidden {
  860. transform: translateY(100%);
  861. }
  862. }
  863. .audio-time {
  864. display: flex;
  865. align-items: center;
  866. margin-bottom: 20rpx;
  867. }
  868. .time-text {
  869. font-size: 28rpx;
  870. color: #999;
  871. min-width: 80rpx;
  872. }
  873. .progress-container {
  874. flex: 1;
  875. margin: 0 20rpx;
  876. }
  877. .progress-bar {
  878. position: relative;
  879. height: 6rpx;
  880. background: #f0f0f0;
  881. border-radius: 3rpx;
  882. }
  883. .progress-fill {
  884. position: absolute;
  885. left: 0;
  886. top: 0;
  887. height: 100%;
  888. background: #06DADC;
  889. border-radius: 3rpx;
  890. transition: width 0.1s ease;
  891. }
  892. .progress-thumb {
  893. position: absolute;
  894. top: 50%;
  895. width: 16rpx;
  896. height: 16rpx;
  897. background: #06DADC;
  898. border-radius: 50%;
  899. transform: translate(-50%, -50%);
  900. transition: left 0.1s ease;
  901. }
  902. .audio-controls-row {
  903. display: flex;
  904. align-items: center;
  905. justify-content: space-between;
  906. }
  907. .control-btn {
  908. display: flex;
  909. // flex-direction: column;
  910. align-items: center;
  911. padding: 10rpx;
  912. gap: 8rpx;
  913. }
  914. .control-text {
  915. font-size: 28rpx;
  916. color: #4A4A4A;
  917. // margin-top: 8rpx;
  918. }
  919. .play-btn {
  920. display: flex;
  921. align-items: center;
  922. justify-content: center;
  923. padding: 10rpx;
  924. }
  925. </style>