Browse Source

feat: 添加滚动插件并优化多个组件功能

- 新增scrollTo插件支持H5和小程序环境滚动功能
- 修改SplashScreen图片显示模式为aspectFit
- 在CustomTabbar添加page-data-needed事件处理
- 优化login页面隐私协议弹窗可滚动显示
- 调整directory页面z-index和滚动相关逻辑
hfll
hflllll 1 week ago
parent
commit
ea1698fc83
8 changed files with 708 additions and 902 deletions
  1. +3
    -1
      main.js
  2. +1
    -1
      pages/components/SplashScreen.vue
  3. +103
    -0
      plugins/scrollTo.js
  4. +318
    -215
      subPages/home/AudioControls.vue
  5. +250
    -674
      subPages/home/book.vue
  6. +8
    -1
      subPages/home/components/CustomTabbar.vue
  7. +9
    -4
      subPages/home/directory.vue
  8. +16
    -6
      subPages/login/login.vue

+ 3
- 1
main.js View File

@ -8,7 +8,7 @@ import * as api from '@/api'
import utils from '@/utils'
import config from '@/config'
import MixinConfig from '@/mixins/config'
import scrollTo from '@/plugins/scrollTo'
import store from '@/stores'
Vue.config.productionTip = false
@ -19,6 +19,8 @@ Vue.mixin(MixinConfig)
// 全局注册弹窗组件
// Vue.component('GlobalPopup', GlobalPopup)
// 将插件挂载到Vue中
Vue.use(scrollTo)
// 将api挂载到Vue的原型
Vue.prototype.$api = api
Vue.prototype.$utils = utils


+ 1
- 1
pages/components/SplashScreen.vue View File

@ -4,7 +4,7 @@
<view class="splash-content">
<image
:src="splashContent"
mode="scaleToFill"
mode="aspectFit"
class="splash-image"
@error="onImageError"
@load="onImageLoad"


+ 103
- 0
plugins/scrollTo.js View File

@ -0,0 +1,103 @@
export default {
install(Vue) {
Vue.prototype.$scrollTo = function(el, behavior = 'smooth', retryCount = 0) {
if (!el) {
console.warn('scrollTo: 元素参数为空')
return
}
const maxRetries = 3
const retryDelay = 100 // 100ms
let targetEL = typeof el === 'string' ? this.$refs[el] : el
console.log(`scrollTo called with: ${el} (尝试次数: ${retryCount + 1})`)
console.log('原始targetEL:', targetEL)
// 如果targetEL为undefined且还有重试次数,则延迟重试
if (!targetEL && retryCount < maxRetries) {
console.log(`元素未找到,${retryDelay}ms后重试...`)
setTimeout(() => {
this.$scrollTo(el, behavior, retryCount + 1)
}, retryDelay)
return
}
// 如果重试次数用完仍未找到元素,输出详细信息
if (!targetEL) {
console.error(`scrollTo: 经过${maxRetries + 1}次尝试仍未找到元素 "${el}"`)
console.log('当前可用的refs:', Object.keys(this.$refs))
return
}
// 如果targetEL是数组,取第一个元素
if (Array.isArray(targetEL) && targetEL.length > 0) {
targetEL = targetEL[0]
console.log('检测到数组,取第一个元素:', targetEL)
} else if (Array.isArray(targetEL) && targetEL.length === 0) {
console.error('scrollTo: 找到的是空数组')
return
}
if (targetEL) {
// #ifdef H5
console.log('进h5环境的滚动插件了');
console.log('targetEL类型:', typeof targetEL);
console.log('targetEL是否有$el属性:', !!targetEL.$el);
console.log('targetEL是否有scrollIntoView方法:', !!targetEL.scrollIntoView);
try {
// H5环境下使用原生的scrollIntoView方法
if (targetEL.$el) {
// 如果是Vue组件(uni-app的view等组件),获取其DOM元素
console.log('检测到uni-app Vue组件,使用$el获取DOM元素');
console.log('DOM元素:', targetEL.$el);
targetEL.$el.scrollIntoView({
behavior: behavior,
block: 'start',
inline: 'nearest'
})
console.log('✅ 滚动成功')
} else if (targetEL.scrollIntoView) {
console.log('检测到原生DOM元素');
// 如果是原生DOM元素
targetEL.scrollIntoView({
behavior: behavior,
block: 'start',
inline: 'nearest'
})
console.log('✅ 滚动成功')
} else {
console.log('无法识别的元素类型,尝试其他方法');
// 如果都不是,可能需要其他处理方式
}
} catch (error) {
console.error('scrollTo 执行失败:', error)
}
// #endif
// #ifndef H5
// 非H5环境(小程序等)使用uni.pageScrollTo
const query = uni.createSelectorQuery().in(this)
query.select(`[ref="${el}"]`).boundingClientRect((data) => {
console.log('element position:', data)
if (data) {
uni.pageScrollTo({
scrollTop: data.top,
duration: behavior === 'smooth' ? 300 : 0
})
console.log('✅ 滚动成功')
} else {
console.error('scrollTo: 未找到元素位置信息')
}
}).exec()
// #endif
}
}
}
}
// 使用方式
// 在 main.js 中引入:Vue.use(require('./plugins/scrollTo'))
// 然后在组件中:this.$scrollTo('targetRef')

+ 318
- 215
subPages/home/AudioControls.vue
File diff suppressed because it is too large
View File


+ 250
- 674
subPages/home/book.vue
File diff suppressed because it is too large
View File


+ 8
- 1
subPages/home/components/CustomTabbar.vue View File

@ -18,6 +18,7 @@
@highlight-change="onHighlightChange"
@voice-change-complete="onVoiceChangeComplete"
@voice-change-error="onVoiceChangeError"
@page-data-needed="onPageDataNeeded"
ref="audioControls"
/>
@ -136,7 +137,13 @@ export default {
},
onVoiceChangeError(error) {
this.$emit('voice-change-error', error)
}
},
onPageDataNeeded(pageNumber) {
this.$emit('page-data-needed', pageNumber)
},
// onScrollToText(refName) {
// this.$emit('scroll-to-text', refName)
// }
}
}
</script>


+ 9
- 4
subPages/home/directory.vue View File

@ -1,6 +1,6 @@
<template>
<view class="directory-container">
<view class="book-container">
<view class="book-container" >
<view class="book-info">
<view class="book-cover">
<image :src="bookInfo.booksImg" mode="aspectFill" :style="{width: '100%', height: '100%'}"></image>
@ -32,7 +32,7 @@
</view>
<!-- 课程和简介容器 -->
<view class="content-container">
<view class="content-container" >
<!-- 课程部分 -->
<view class="course-section">
<view class="course-header">
@ -200,6 +200,11 @@ export default {
})
},
scroll(){
console.log('被点击了');
this.$scrollTo('testRef')
},
//
async getDetail() {
const detailRes = await this.$api.book.detail({
@ -365,7 +370,7 @@ export default {
gap: 24rpx;
flex-direction: column;
position: relative;
z-index: 9999;
}
/* 课程部分 */
@ -620,7 +625,7 @@ padding: 32rpx;
background: #fff;
padding: 24rpx 32rpx 0;
box-shadow: 0rpx -2rpx 0rpx 0rpx #0000001A;
z-index: 10000;
z-index: 999;
.bottom-action-container{
display: flex;


+ 16
- 6
subPages/login/login.vue View File

@ -40,25 +40,31 @@
</view>
<!-- 用户协议和隐私政策弹窗 -->
<uv-modal ref="serviceModal" title="《服务协议与隐私条款》" :show-cancel-button="false" confirm-text="我知道了" confirm-color="#06DADC" @confirm="isAgreed = true">
<uv-modal ref="serviceModal" title="《服务协议与隐私条款》" confirm-text="我知道了" confirm-color="#06DADC" @confirm="isAgreed = true">
<view class="privacy-content">
<!-- 如果是富文本 -->
<uv-parse :content="configParamContent('privacy_policy')"></uv-parse>
<scroll-view scroll-y style="height: 600rpx;">
<!-- 如果是富文本 -->
<uv-parse :content="configParamContent('privacy_policy')"></uv-parse>
</scroll-view>
</view>
</uv-modal>
<!-- 用户协议和隐私政策弹窗 -->
<uv-modal ref="guideModal" title="《个人信息保护指引》" :show-cancel-button="false" confirm-text="我知道了" confirm-color="#06DADC" @confirm="isAgreed = true">
<uv-modal ref="guideModal" title="《个人信息保护指引》" confirm-text="我知道了" confirm-color="#06DADC" @confirm="isAgreed = true">
<view class="privacy-content">
<!-- 如果是富文本 -->
<uv-parse :content="configParamContent('user_agreement')"></uv-parse>
<scroll-view scroll-y style="height: 600rpx;">
<!-- 如果是富文本 -->
<uv-parse :content="configParamContent('user_agreement')"></uv-parse>
</scroll-view>
</view>
</uv-modal>
</view>
</template>
<script>
import uvParse from '../../uni_modules/uv-parse/components/uv-parse/uv-parse.vue';
export default {
components: { uvParse },
name: 'Login',
data() {
return {
@ -393,5 +399,9 @@ export default {
}
}
}
.privacy-content{
max-height: 600rpx;
}
}
</style>

Loading…
Cancel
Save