index.vue 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. <template>
  2. <view class="container">
  3. <view @click="aaaa">asdfasdfdasfads</view>
  4. <view class="icon-title-navBar-box">
  5. <view class="nav-bar-icon" @click="handleBack"></view>
  6. <view class="nav-bar-title">学习</view>
  7. </view>
  8. <!-- z-paging 列表 -->
  9. <z-paging ref="paging" @scroll="onScroll" @query="loadData" :refresher-enabled="false"
  10. :loading-more-enabled="false" :auto="false" :fixed="false" :hide-empty-view="true">
  11. <!-- 单元列表 -->
  12. <view v-for="(danyuan, danyuanIndex) in danyuanList" :key="danyuan.danyuanId"
  13. :id="'danyuan-' + danyuanIndex" class="danyuan-item">
  14. <!-- 当前学科信息 -->
  15. <view class="subject-info" v-if="subjectDetail">
  16. <text class="course-name">{{ subjectDetail.chanpinName }}</text>
  17. <text class="course-name">等级:{{ subjectDetail.dengjiName }}</text>
  18. <text class="course-name">版本:{{ subjectDetail.dengjiName }}</text>
  19. <text class="course-name">单元:{{ subjectDetail.curDanyuanName }}</text>
  20. <text class="course-name">课程:{{ subjectDetail.chanpinName }}</text>
  21. </view>
  22. <!-- 单元标题 -->
  23. <view class="danyuan-header">
  24. <text class="danyuan-title">{{ danyuan.danyuanName }}</text>
  25. <text class="danyuan-intro">{{ danyuan.danyuanIntro }}</text>
  26. </view>
  27. <!-- 节列表 -->
  28. <view v-if="danyuan.jieList && danyuan.jieList.length > 0">
  29. <view v-for="jie in danyuan.jieList" :key="jie.jieId" class="jie-item">
  30. <text class="jie-name">{{ jie.number }}. {{ jie.jieName }}</text>
  31. <text class="jie-desc">{{ jie.jieIntro }}</text>
  32. </view>
  33. </view>
  34. <!-- 无课程内容提示 -->
  35. <view v-else class="empty-jie">
  36. <text class="empty-text">暂无课程内容</text>
  37. </view>
  38. </view>
  39. </z-paging>
  40. <!-- 回到顶部按钮 -->
  41. <view v-if="showBackToTop" class="back-to-top" @click="scrollToTop">
  42. <text class="back-to-top-icon">↑</text>
  43. <text class="back-to-top-text">顶部</text>
  44. </view>
  45. <!-- 当前单元提示 -->
  46. <view v-if="currentUnitName" class="current-unit-tip">
  47. <text>{{ currentUnitName }}</text>
  48. </view>
  49. <CustomTabBar :currentTabNumber="0"></CustomTabBar>
  50. </view>
  51. </template>
  52. <script>
  53. import {
  54. shuxueChanpinBanben
  55. } from "@/api/chanpinneirong.js"
  56. import CustomTabBar from '@/components/custom-tabbar/custom-tabbar.vue';
  57. export default {
  58. data() {
  59. return {
  60. activeSubjectId: 2,
  61. // 学科详情数据
  62. subjectDetail: null,
  63. // 单元列表
  64. danyuanList: [],
  65. // 当前单元名称
  66. currentUnitName: '',
  67. // 单元位置信息
  68. unitPositions: [],
  69. // 新增:是否显示回到顶部按钮
  70. showBackToTop: false,
  71. // 新增:显示回到顶部按钮的阈值(滚动多少距离后显示)
  72. backToTopThreshold: 500
  73. }
  74. },
  75. components: {
  76. CustomTabBar
  77. },
  78. mounted() {
  79. //初始化加载数据
  80. this.$nextTick(() => {
  81. // this.$refs.paging.reload()
  82. })
  83. },
  84. methods: {
  85. aaaa(){
  86. this.$refs.paging.reload()
  87. },
  88. handleBack() {
  89. },
  90. // z-paging 加载数据
  91. async loadData() {
  92. // 目前只处理数学学科
  93. if (this.activeSubjectId !== 2) {
  94. this.$refs.paging.complete([])
  95. return
  96. }
  97. try {
  98. // 调用数学API
  99. const req = {
  100. banbenId: 7
  101. }
  102. const res = await shuxueChanpinBanben(req)
  103. if (res.code === 0 && res.data) {
  104. // 直接使用后端返回的数据
  105. this.subjectDetail = res.data
  106. this.danyuanList = res.data.danyuanList || []
  107. // 告诉z-paging加载完成
  108. this.$refs.paging.complete(this.danyuanList)
  109. // 计算单元位置
  110. this.$nextTick(() => {
  111. this.calculateUnitPositions()
  112. })
  113. }
  114. } catch (error) {
  115. console.error('加载失败:', error)
  116. this.$refs.paging.complete(false)
  117. }
  118. },
  119. // 计算单元位置
  120. calculateUnitPositions() {
  121. if (!this.danyuanList || this.danyuanList.length === 0) return
  122. // 清空位置信息
  123. this.unitPositions = []
  124. // 使用 $nextTick 确保DOM已更新
  125. this.$nextTick(() => {
  126. // 稍微延迟确保渲染完成
  127. setTimeout(() => {
  128. const query = uni.createSelectorQuery().in(this)
  129. // 一次性查询所有单元
  130. const selectors = this.danyuanList.map((_, index) => {
  131. return query.select(`#danyuan-${index}`).boundingClientRect()
  132. })
  133. // 执行查询
  134. query.exec((results) => {
  135. if (results && results.length === this.danyuanList.length) {
  136. results.forEach((rect, index) => {
  137. if (rect) {
  138. // 保存相对位置(注意:这是相对于屏幕的位置)
  139. this.$set(this.unitPositions, index, {
  140. top: rect.top,
  141. height: rect.height,
  142. name: this.danyuanList[index]
  143. .danyuanName
  144. })
  145. }
  146. })
  147. console.log('单元位置计算完成:', this.unitPositions)
  148. }
  149. })
  150. }, 100)
  151. })
  152. },
  153. // 滚动事件 - 修改后版本
  154. onScroll(e) {
  155. const scrollTop = e.detail.scrollTop
  156. console.log('滚动位置:', scrollTop)
  157. // 新增:控制回到顶部按钮的显示/隐藏
  158. this.showBackToTop = scrollTop > this.backToTopThreshold
  159. // 查找当前滚动到的单元
  160. let currentUnitName = ''
  161. for (let i = this.unitPositions.length - 1; i >= 0; i--) {
  162. const position = this.unitPositions[i]
  163. if (position && scrollTop >= position.top - 50) {
  164. currentUnitName = position.name
  165. break
  166. }
  167. }
  168. // 更新当前单元提示
  169. if (currentUnitName && currentUnitName !== this.currentUnitName) {
  170. this.currentUnitName = currentUnitName
  171. console.log('切换到单元:', currentUnitName)
  172. }
  173. },
  174. // 新增:回到顶部方法
  175. scrollToTop() {
  176. console.log('执行回到顶部')
  177. // 方法1: 使用z-paging的scrollToTop方法(最简单)
  178. if (this.$refs.paging) {
  179. this.$refs.paging.scrollToTop({
  180. animated: true,
  181. duration: 300
  182. })
  183. }
  184. // 方法2: 对于H5环境,额外滚动整个页面
  185. // #ifdef H5
  186. setTimeout(() => {
  187. window.scrollTo({
  188. top: 0,
  189. behavior: 'smooth'
  190. })
  191. }, 100)
  192. // #endif
  193. // 方法3: 对于App环境,使用uni的pageScrollTo
  194. // #ifdef APP-PLUS
  195. uni.pageScrollTo({
  196. scrollTop: 0,
  197. duration: 300
  198. })
  199. // #endif
  200. // 隐藏回到顶部按钮
  201. this.showBackToTop = false
  202. // 可选:显示提示
  203. uni.showToast({
  204. title: '回到顶部',
  205. icon: 'none',
  206. duration: 800
  207. })
  208. }
  209. }
  210. }
  211. </script>
  212. <style scoped>
  213. .container {
  214. height: 100vh;
  215. background-color: #f8f9fa;
  216. }
  217. /* 学科标签页 */
  218. .subject-tabs {
  219. background-color: white;
  220. padding: 20rpx 0;
  221. border-bottom: 1rpx solid #eee;
  222. }
  223. .tabs-scroll {
  224. white-space: nowrap;
  225. height: 80rpx;
  226. }
  227. .tabs-content {
  228. display: inline-flex;
  229. padding: 0 30rpx;
  230. }
  231. .tab-item {
  232. padding: 0 40rpx;
  233. height: 60rpx;
  234. display: flex;
  235. align-items: center;
  236. justify-content: center;
  237. }
  238. .tab-text {
  239. font-size: 32rpx;
  240. color: #666;
  241. position: relative;
  242. padding: 10rpx 0;
  243. }
  244. .tab-item.active .tab-text {
  245. color: #4a6fe3;
  246. font-weight: bold;
  247. }
  248. .tab-item.active .tab-text::after {
  249. content: '';
  250. position: absolute;
  251. bottom: 0;
  252. left: 0;
  253. right: 0;
  254. height: 4rpx;
  255. background-color: #4a6fe3;
  256. border-radius: 2rpx;
  257. }
  258. /* 学科信息 */
  259. .subject-info {
  260. padding: 30rpx;
  261. background-color: white;
  262. margin-bottom: 20rpx;
  263. }
  264. .course-name {
  265. display: block;
  266. font-size: 36rpx;
  267. font-weight: bold;
  268. color: #333;
  269. margin-bottom: 10rpx;
  270. }
  271. .current-info {
  272. display: block;
  273. font-size: 28rpx;
  274. color: #666;
  275. }
  276. /* 单元样式 */
  277. .danyuan-item {
  278. background-color: white;
  279. margin: 20rpx;
  280. border-radius: 16rpx;
  281. overflow: hidden;
  282. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
  283. margin-bottom: 30rpx;
  284. }
  285. .danyuan-header {
  286. background: #4a6fe3;
  287. padding: 30rpx;
  288. color: white;
  289. }
  290. .danyuan-title {
  291. display: block;
  292. font-size: 32rpx;
  293. font-weight: bold;
  294. margin-bottom: 10rpx;
  295. }
  296. .danyuan-intro {
  297. font-size: 28rpx;
  298. opacity: 0.9;
  299. }
  300. /* 节样式 */
  301. .jie-item {
  302. padding: 24rpx 30rpx;
  303. border-bottom: 1rpx solid #f0f0f0;
  304. }
  305. .jie-item:last-child {
  306. border-bottom: none;
  307. }
  308. .jie-name {
  309. display: block;
  310. font-size: 30rpx;
  311. font-weight: 500;
  312. color: #333;
  313. margin-bottom: 8rpx;
  314. }
  315. .jie-desc {
  316. display: block;
  317. font-size: 26rpx;
  318. color: #666;
  319. }
  320. /* 空课程提示 */
  321. .empty-jie {
  322. padding: 40rpx;
  323. text-align: center;
  324. }
  325. .empty-text {
  326. font-size: 28rpx;
  327. color: #999;
  328. }
  329. /* 新增:回到顶部按钮样式 */
  330. .back-to-top {
  331. position: fixed;
  332. bottom: 120rpx;
  333. right: 30rpx;
  334. width: 100rpx;
  335. height: 100rpx;
  336. background-color: rgba(74, 111, 227, 0.9);
  337. border-radius: 50%;
  338. display: flex;
  339. flex-direction: column;
  340. align-items: center;
  341. justify-content: center;
  342. z-index: 999;
  343. box-shadow: 0 4rpx 20rpx rgba(74, 111, 227, 0.3);
  344. transition: all 0.3s ease;
  345. }
  346. .back-to-top:active {
  347. transform: scale(0.95);
  348. background-color: rgba(74, 111, 227, 1);
  349. }
  350. .back-to-top-icon {
  351. font-size: 40rpx;
  352. color: white;
  353. font-weight: bold;
  354. }
  355. .back-to-top-text {
  356. font-size: 22rpx;
  357. color: white;
  358. margin-top: 4rpx;
  359. }
  360. /* 当前单元提示 */
  361. .current-unit-tip {
  362. position: fixed;
  363. top: 200rpx;
  364. left: 50%;
  365. transform: translateX(-50%);
  366. background-color: rgba(74, 111, 227, 0.9);
  367. color: white;
  368. padding: 20rpx 40rpx;
  369. border-radius: 50rpx;
  370. font-size: 28rpx;
  371. z-index: 1000;
  372. box-shadow: 0 4rpx 20rpx rgba(74, 111, 227, 0.3);
  373. animation: fadeInOut 3s ease-in-out;
  374. }
  375. @keyframes fadeInOut {
  376. 0%,
  377. 100% {
  378. opacity: 0;
  379. transform: translateX(-50%) translateY(-20rpx);
  380. }
  381. 10%,
  382. 90% {
  383. opacity: 1;
  384. transform: translateX(-50%) translateY(0);
  385. }
  386. }
  387. </style>