diff --git a/api/modules/home.js b/api/modules/home.js
index 0ceec34..4f9d4b1 100644
--- a/api/modules/home.js
+++ b/api/modules/home.js
@@ -35,5 +35,22 @@ export default{
url: '/index/banner',
method: 'GET',
})
+ },
+
+ // 查询文章列表
+ async getArticle() {
+ return http({
+ url: '/index/articleList',
+ method: 'GET',
+ })
+ },
+
+ // 查询文章详情
+ async getArticleDetail(data) {
+ return http({
+ url: '/index/articleDetail',
+ method: 'GET',
+ data
+ })
}
}
\ No newline at end of file
diff --git a/pages.json b/pages.json
index 24f6b74..0d71e28 100644
--- a/pages.json
+++ b/pages.json
@@ -197,6 +197,13 @@
// #endif
"navigationBarTitleText": "分享"
}
+ },
+ {
+ "path": "home/article",
+ "style": {
+ "navigationStyle": "custom",
+ "navigationBarTitleText": "文章详情"
+ }
}
]
}
diff --git a/pages/components/SplashScreen.vue b/pages/components/SplashScreen.vue
index a5b6c78..6483944 100644
--- a/pages/components/SplashScreen.vue
+++ b/pages/components/SplashScreen.vue
@@ -85,6 +85,16 @@ export default {
// 等待一小段時間確保 store 數據加載完成
await this.$nextTick()
+ // #ifdef H5
+ // H5环境下检查sessionStorage,判断是否在当前会话中已经显示过开屏动画
+ const hasShownSplash = sessionStorage.getItem('splash_shown')
+ if (hasShownSplash) {
+ console.log('当前会话已显示过开屏动画,跳过')
+ this.$emit('close')
+ return
+ }
+ // #endif
+
// 獲取圖片URL,優先使用配置,然後使用本地存儲
let imageUrl = ''
@@ -105,6 +115,12 @@ export default {
this.splashContent = imageUrl
this.countdown = this.duration
this.showSplash = true
+
+ // #ifdef H5
+ // H5环境下设置sessionStorage标记,表示当前会话已显示过开屏动画
+ sessionStorage.setItem('splash_shown', 'true')
+ // #endif
+
this.startCountdown()
} else {
console.log('沒有開屏圖片,跳過開屏動畫')
diff --git a/pages/index/home.vue b/pages/index/home.vue
index 6f40f53..07202fb 100644
--- a/pages/index/home.vue
+++ b/pages/index/home.vue
@@ -61,12 +61,77 @@
@click="onBannerClick"
>
-
+
+
+
+
+
+
+
+
+
+ {{ book.booksName }}
+ {{ book.booksAuthor }}
+
+
+
+ {{ book.duration }}
+
+
+ {{ book.vipInfo.title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ article.title }}
+
+ 精选
+ {{ formatTime(article.createTime) }}
+
+
+
+
+
+
+
+
+
+
+
+
-
+
= 768
// #endif
},
@@ -307,7 +389,15 @@ export default {
// 切换Tab
async switchTab(index) {
this.activeTab = index
- await this.getBooksByLabels()
+
+ if (index === 0) {
+ // 第一个tab(全部)显示原有内容
+ this.showBookList = false
+ await this.getBooksByLabels()
+ } else {
+ // 其他tab显示书本列表
+ await this.getBookList()
+ }
},
// 轮播图点击事件
@@ -382,14 +472,32 @@ export default {
}))
}
},
+
+ // 获取文章列表
+ async getArticleList() {
+ try {
+ const articleRes = await this.$api.home.getArticle()
+ if (articleRes.code === 200) {
+ this.articleList = articleRes.result || []
+ console.log('文章列表数据:', this.articleList)
+ }
+ } catch (error) {
+ console.error('获取文章列表失败:', error)
+ this.articleList = []
+ }
+ },
// 获取书籍分类
async getCategory() {
const categoryRes = await this.$api.book.category()
if (categoryRes.code === 200){
- this.tabs = categoryRes.result.map(item => ({
- title:item.title,
+ // 硬编码第一个tab为"全部"
+ this.tabs = [
+ { title: '全部', id: null },
+ ...categoryRes.result.map(item => ({
+ title: item.title,
id: item.id
- }))
+ }))
+ ]
}
},
// 获取书籍标签
@@ -453,6 +561,104 @@ export default {
})
},
+ // 获取书本列表(用于tab切换)
+ async getBookList() {
+ if (this.activeTab === 0) {
+ // 第一个tab(全部)不显示书本列表,显示原有内容
+ this.showBookList = false
+ return
+ }
+
+ this.isLoadingBooks = true
+ this.showBookList = true
+
+ try {
+ const params = {
+ category: this.tabs[this.activeTab].id,
+ pageNo: 1,
+ pageSize: 20
+ }
+
+ const res = await this.$api.book.list(params)
+ if (res.code === 200) {
+ this.bookList = res.result.records || []
+ } else {
+ this.bookList = []
+ console.error('获取书本列表失败:', res)
+ }
+ } catch (error) {
+ console.error('获取书本列表出错:', error)
+ this.bookList = []
+ } finally {
+ this.isLoadingBooks = false
+ }
+ },
+
+ // 跳转到书本详情(从搜索页面复制)
+ goToBookDetail(book) {
+ uni.navigateTo({
+ url: '/subPages/home/directory?id=' + book.id
+ })
+ },
+
+ // 跳转文章详情
+ goArticleDetail(article) {
+ console.log('点击文章:', article)
+ uni.navigateTo({
+ url: `/subPages/home/article?id=${article.id}`
+ })
+ },
+
+ // 跳转更多文章页面
+ goMoreArticles() {
+ uni.navigateTo({
+ url: '/subPages/home/articleList'
+ })
+ },
+
+ // 格式化时间
+ formatTime(timeStr) {
+ if (!timeStr) return ''
+
+ try {
+ // 处理iOS兼容性问题,将 "2025-10-23 16:36:43" 格式转换为 "2025/10/23 16:36:43"
+ let formattedTimeStr = timeStr.replace(/-/g, '/')
+ const date = new Date(formattedTimeStr)
+
+ // 检查日期是否有效
+ if (isNaN(date.getTime())) {
+ console.warn('Invalid date format:', timeStr)
+ return ''
+ }
+ const now = new Date()
+ const diff = now - date
+
+ // 小于1分钟
+ if (diff < 60000) {
+ return '刚刚'
+ }
+ // 小于1小时
+ if (diff < 3600000) {
+ return Math.floor(diff / 60000) + '分钟前'
+ }
+ // 小于1天
+ if (diff < 86400000) {
+ return Math.floor(diff / 3600000) + '小时前'
+ }
+ // 小于7天
+ if (diff < 604800000) {
+ return Math.floor(diff / 86400000) + '天前'
+ }
+
+ // 超过7天显示具体日期
+ const month = date.getMonth() + 1
+ const day = date.getDate()
+ return `${month}月${day}日`
+ } catch (error) {
+ return ''
+ }
+ },
+
// 关闭视频弹窗
closeVideoModal() {
this.$refs.videoModal.close()
@@ -485,7 +691,7 @@ export default {
this.detectDevice()
// 先获取基础数据
- await Promise.all([this.getBanner(), this.getSignup(), this.getCategory(), this.getLabel()])
+ await Promise.all([this.getBanner(), this.getSignup(), this.getCategory(), this.getLabel(), this.getArticleList()])
// 根据label数据获取对应的书籍
await this.getBooksByLabels()
@@ -614,7 +820,31 @@ export default {
}
}
+.section-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 30rpx ;
+ margin-bottom: 24rpx;
+ .section-title {
+ font-size: 36rpx;
+ // font-weight: 600;
+ color: $primary-text-color;
+ }
+
+ .section-more {
+ display: flex;
+ align-items: center;
+ gap: 4rpx;
+
+ text {
+ font-size: 24rpx;
+ color: $secondary-text-color;
+ }
+ }
+}
// 内容区块
+
.section {
margin-top: 40rpx;
@@ -933,4 +1163,192 @@ export default {
}
}
}
+
+// 文章列表样式
+.article-section {
+ margin-top: 40rpx;
+
+ .article-scroll {
+ white-space: nowrap;
+ }
+
+ .article-list {
+ display: flex;
+ padding: 0 30rpx;
+ gap: 24rpx;
+
+ .article-item {
+ flex-shrink: 0;
+ width: 580rpx;
+ height: 120rpx;
+ background: #f8f9fa;
+ border-radius: 16rpx;
+ padding: 24rpx;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ transition: all 0.3s ease;
+
+ &:active {
+ transform: scale(0.98);
+ background: #f0f1f2;
+ }
+
+ .article-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 12rpx;
+
+ .article-title {
+ font-size: 32rpx;
+ font-weight: 600;
+ color: $primary-text-color;
+ line-height: 1.4;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ overflow: hidden;
+ word-break: break-word;
+ }
+
+ .article-meta {
+ display: flex;
+ align-items: center;
+ gap: 16rpx;
+
+ .article-tag {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: #fff;
+ font-size: 20rpx;
+ padding: 4rpx 12rpx;
+ border-radius: 12rpx;
+ font-weight: 500;
+ }
+
+ .article-time {
+ font-size: 24rpx;
+ color: $secondary-text-color;
+ }
+ }
+ }
+
+ .article-arrow {
+ margin-left: 16rpx;
+ opacity: 0.6;
+ }
+ }
+ }
+}
+
+// 书本列表样式(从搜索页面复制)
+.book-list-container {
+ background: #fff;
+ min-height: 50vh;
+}
+
+.book-list-results {
+ padding: 32rpx;
+ display: flex;
+ flex-direction: column;
+ gap: 32rpx;
+}
+
+.book-list-item {
+ display: flex;
+ align-items: center;
+ background: #F8F8F8;
+ height: 212rpx;
+ gap: 16rpx;
+ border-radius: 16rpx;
+ padding: 0rpx 16rpx;
+
+ &:last-child {
+ border-bottom: none;
+ }
+
+ .book-list-cover {
+ width: 136rpx;
+ height: 180rpx;
+ border-radius: 16rpx;
+ overflow: hidden;
+ margin-right: 16rpx;
+
+ image {
+ width: 100%;
+ height: 100%;
+ }
+ }
+
+ .book-list-info {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ }
+
+ .book-list-title {
+ font-size: 32rpx;
+ font-weight: 600;
+ color: $primary-text-color;
+ line-height: 48rpx;
+ letter-spacing: 0;
+ margin-bottom: 12rpx;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .book-list-author {
+ font-size: 24rpx;
+ color: $secondary-text-color;
+ margin-bottom: 16rpx;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .book-list-meta {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ }
+
+ .book-list-duration {
+ display: flex;
+ align-items: center;
+ font-size: 22rpx;
+ color: #999;
+
+ .book-list-icon {
+ width: 18rpx;
+ height: 18rpx;
+ }
+
+ text {
+ margin-left: 8rpx;
+ }
+ }
+
+ .book-list-membership {
+ padding: 8rpx 16rpx;
+ border-radius: 8rpx;
+ font-size: 24rpx;
+ color: #211508;
+ }
+}
+
+.book-membership-premium {
+ background: #E9F1FF;
+ border: 2rpx solid #C4DAFF;
+}
+
+.book-membership-vip {
+ background: #FFF4E9;
+ border: 2rpx solid #FFE2C4;
+}
+
+.book-membership-basic {
+ background: #FFE9E9;
+ border: 2rpx solid #FFDBC4;
+}
diff --git a/subPages/home/article.vue b/subPages/home/article.vue
new file mode 100644
index 0000000..2857476
--- /dev/null
+++ b/subPages/home/article.vue
@@ -0,0 +1,490 @@
+
+
+
+
+
+
+
+
+ {{ articleData.title }}
+
+
+
+ {{ formatTime(articleData.createTime) }}
+ {{ articleData.createBy }}
+
+
+
+
+
+
+
+
+
+
+
+ 加载中...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/subPages/home/richtext.vue b/subPages/home/richtext.vue
index 5fc82b1..f43344a 100644
--- a/subPages/home/richtext.vue
+++ b/subPages/home/richtext.vue
@@ -24,6 +24,15 @@
+
+
+
+
+
@@ -32,6 +41,10 @@ export default {
data() {
return {
htmlContent: '',
+ articleDetail: null, // 文章详情数据
+ audioUrl: '', // 音频URL
+ isPlaying: false, // 音频播放状态
+ audioContext: null, // 音频上下文
// 富文本样式配置
tagStyle: {
p: 'margin: 16rpx 0; line-height: 1.6; color: #333;',
@@ -55,6 +68,9 @@ export default {
// 获取传递的富文本内容
if (options.content) {
this.htmlContent = decodeURIComponent(options.content)
+ } else if(options.articleId) {
+ // 从数据库获取文章内容
+ this.getArticleContent(options.articleId)
} else {
uni.showToast({
title: '内容加载失败',
@@ -72,7 +88,259 @@ export default {
uni.navigateBack({
delta: 1
})
+ },
+
+ // 获取文章详情
+ async getArticleContent(articleId) {
+ try {
+ const res = await this.$api.home.getArticleDetail({ id: articleId })
+ if (res.code === 200 && res.result) {
+ this.articleDetail = res.result
+ this.htmlContent = res.result.content
+
+ // 获取第一个音频URL
+ if (res.result.audios) {
+ const audioKeys = Object.keys(res.result.audios)
+ if (audioKeys.length > 0) {
+ this.audioUrl = res.result.audios[audioKeys[0]]
+ }
+ }
+ } else {
+ uni.showToast({
+ title: '文章加载失败',
+ icon: 'error'
+ })
+ setTimeout(() => {
+ this.goBack()
+ }, 1500)
+ }
+ } catch (error) {
+ console.error('获取文章详情失败:', error)
+ uni.showToast({
+ title: '网络错误',
+ icon: 'error'
+ })
+ setTimeout(() => {
+ this.goBack()
+ }, 1500)
+ }
+ },
+
+ // 创建HTML5 Audio实例并包装为uni-app兼容接口
+ createHTML5Audio() {
+ const audio = new Audio();
+
+ // 包装为uni-app兼容的接口
+ const wrappedAudio = {
+ // 原生HTML5 Audio实例
+ _nativeAudio: audio,
+
+ // 基本属性
+ get src() { return audio.src; },
+ set src(value) { audio.src = value; },
+
+ get duration() { return audio.duration || 0; },
+ get currentTime() { return audio.currentTime || 0; },
+
+ get paused() { return audio.paused; },
+
+ // 支持倍速的关键属性
+ get playbackRate() { return audio.playbackRate; },
+ set playbackRate(value) {
+ try {
+ audio.playbackRate = value;
+ } catch (error) {
+ console.error('HTML5 Audio倍速设置失败:', error);
+ }
+ },
+
+ // 基本方法
+ play() {
+ return audio.play().catch(error => {
+ console.error('HTML5 Audio播放失败:', error);
+ });
+ },
+
+ pause() {
+ audio.pause();
+ },
+
+ stop() {
+ audio.pause();
+ audio.currentTime = 0;
+ },
+
+ seek(time) {
+ audio.currentTime = time;
+ },
+
+ destroy() {
+ audio.pause();
+ audio.src = '';
+ audio.load();
+ },
+
+ // 事件绑定方法
+ onCanplay(callback) {
+ audio.addEventListener('canplay', callback);
+ },
+
+ onPlay(callback) {
+ audio.addEventListener('play', callback);
+ },
+
+ onPause(callback) {
+ audio.addEventListener('pause', callback);
+ },
+
+ onEnded(callback) {
+ audio.addEventListener('ended', callback);
+ },
+
+ onTimeUpdate(callback) {
+ audio.addEventListener('timeupdate', callback);
+ },
+
+ onError(callback) {
+ // 包装错误事件,过滤掉非关键错误
+ const wrappedCallback = (error) => {
+ // 只在有src且音频正在播放时才传递错误事件
+ if (audio.src && audio.src.trim() !== '' && !audio.paused) {
+ callback(error);
+ } else {
+ console.log('HTML5 Audio错误(已忽略):', {
+ hasSrc: !!audio.src,
+ paused: audio.paused,
+ errorType: error.type || 'unknown'
+ });
+ }
+ };
+ audio.addEventListener('error', wrappedCallback);
+ },
+
+ // 移除事件监听
+ offCanplay(callback) {
+ audio.removeEventListener('canplay', callback);
+ },
+
+ offPlay(callback) {
+ audio.removeEventListener('play', callback);
+ },
+
+ offPause(callback) {
+ audio.removeEventListener('pause', callback);
+ },
+
+ offEnded(callback) {
+ audio.removeEventListener('ended', callback);
+ },
+
+ offTimeUpdate(callback) {
+ audio.removeEventListener('timeupdate', callback);
+ },
+
+ offError(callback) {
+ audio.removeEventListener('error', callback);
+ }
+ };
+
+ return wrappedAudio;
+ },
+
+ // 切换音频播放状态
+ toggleAudio() {
+ if (!this.audioUrl) {
+ uni.showToast({
+ title: '暂无音频',
+ icon: 'none'
+ })
+ return
+ }
+
+ if (this.isPlaying) {
+ this.pauseAudio()
+ } else {
+ this.playAudio()
+ }
+ },
+
+ // 播放音频
+ playAudio() {
+ if (!this.audioUrl) {
+ console.error('音频URL为空');
+ return;
+ }
+
+ try {
+ // 销毁旧的音频实例
+ if (this.audioContext) {
+ this.audioContext.destroy();
+ this.audioContext = null;
+ }
+
+ // 平台判断:H5环境使用createHTML5Audio,其他环境使用uni.createInnerAudioContext
+ if (uni.getSystemInfoSync().platform === 'devtools' || process.env.NODE_ENV === 'development' || typeof window !== 'undefined') {
+ // H5环境:使用包装的HTML5 Audio
+ this.audioContext = this.createHTML5Audio();
+ } else {
+ // 非H5环境(小程序等)
+ this.audioContext = uni.createInnerAudioContext();
+ }
+
+ // 设置音频源
+ this.audioContext.src = this.audioUrl;
+
+ // 绑定事件监听
+ this.audioContext.onPlay(() => {
+ this.isPlaying = true;
+ });
+
+ this.audioContext.onPause(() => {
+ this.isPlaying = false;
+ });
+
+ this.audioContext.onEnded(() => {
+ this.isPlaying = false;
+ });
+
+ this.audioContext.onError((error) => {
+ console.error('音频播放错误:', error);
+ this.isPlaying = false;
+ });
+
+ // 开始播放
+ this.audioContext.play();
+ } catch (error) {
+ console.error('音频播放异常:', error);
+ this.isPlaying = false;
+ }
+ },
+
+ // 暂停音频
+ pauseAudio() {
+ if (this.audioContext) {
+ this.audioContext.pause()
+ }
+ },
+
+ // 停止音频
+ stopAudio() {
+ if (this.audioContext) {
+ try {
+ this.audioContext.stop();
+ this.audioContext.destroy();
+ this.isPlaying = false;
+ this.audioContext = null;
+ } catch (error) {
+ console.error('停止音频失败:', error);
+ }
+ }
}
+ },
+
+ // 页面卸载时清理音频资源
+ onUnload() {
+ this.stopAudio()
}
}
@@ -179,4 +447,44 @@ export default {
}
}
}
+
+// 音频播放悬浮按钮样式
+.audio-float-btn {
+ position: fixed;
+ right: 60rpx;
+ bottom: 120rpx;
+ width: 120rpx;
+ height: 120rpx;
+ background: rgba(255, 255, 255, 0.95);
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15);
+ backdrop-filter: blur(10rpx);
+ z-index: 999;
+ transition: all 0.3s ease;
+
+ &:active {
+ transform: scale(0.95);
+ box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.2);
+ }
+
+ // 添加呼吸动画效果
+ &.playing {
+ animation: pulse 2s infinite;
+ }
+}
+
+@keyframes pulse {
+ 0% {
+ box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15), 0 0 0 0 rgba(76, 175, 80, 0.4);
+ }
+ 70% {
+ box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15), 0 0 0 20rpx rgba(76, 175, 80, 0);
+ }
+ 100% {
+ box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15), 0 0 0 0 rgba(76, 175, 80, 0);
+ }
+}
\ No newline at end of file
diff --git a/subPages/home/search.vue b/subPages/home/search.vue
index 3bd3916..2744d85 100644
--- a/subPages/home/search.vue
+++ b/subPages/home/search.vue
@@ -169,7 +169,7 @@ export default {
top: 0;
left: 0;
right: 0;
-
+ z-index: 999;
}
.category-tabs {
background: #fff;
@@ -239,7 +239,7 @@ export default {
border-radius: 16rpx;
overflow: hidden;
margin-right: 16rpx;
-
+ z-index: 1;
image {
width: 100%;
height: 100%;