|
@@ -41,40 +41,8 @@
|
|
|
<view class="divider"></view>
|
|
<view class="divider"></view>
|
|
|
<view class="detail-body"><mp-html :content="article.content" :domain="config.API_BASE_URL" :tag-style="contentTagStyle" /></view>
|
|
<view class="detail-body"><mp-html :content="article.content" :domain="config.API_BASE_URL" :tag-style="contentTagStyle" /></view>
|
|
|
|
|
|
|
|
- <!-- 相关文章(按标签关联,无匹配整块隐藏;触底分页加载) -->
|
|
|
|
|
- <view v-if="relatedArticles.length > 0" class="related-section">
|
|
|
|
|
- <view class="related-header">
|
|
|
|
|
- <text class="related-title">相关文章</text>
|
|
|
|
|
- <text class="related-subtitle">根据标签推荐</text>
|
|
|
|
|
- </view>
|
|
|
|
|
- <view class="ra-list">
|
|
|
|
|
- <view
|
|
|
|
|
- v-for="item in relatedArticles"
|
|
|
|
|
- :key="item.id"
|
|
|
|
|
- class="ra-item"
|
|
|
|
|
- hover-class="ra-item-pressed"
|
|
|
|
|
- @click="goRelatedArticle(item)"
|
|
|
|
|
- >
|
|
|
|
|
- <image class="ra-cover" :src="getImageUrl(item.coverImage) || '/static/default-article.png'" mode="aspectFill" lazy-load />
|
|
|
|
|
- <view class="ra-body">
|
|
|
|
|
- <text class="ra-title">{{ item.title }}</text>
|
|
|
|
|
- <text class="ra-summary" v-if="item.summary">{{ item.summary }}</text>
|
|
|
|
|
- </view>
|
|
|
|
|
- </view>
|
|
|
|
|
- </view>
|
|
|
|
|
- <view v-if="relatedArticlesLoading" class="related-loading-hint">加载中...</view>
|
|
|
|
|
- </view>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 关联商品推荐(文章标签 ↔ 商品标签匹配,登录/匿名均可返回;商城两列大卡片样式,触底分页加载) -->
|
|
|
|
|
- <view v-if="relatedProducts.length > 0" class="related-section">
|
|
|
|
|
- <view class="related-header">
|
|
|
|
|
- <text class="related-title">相关商品</text>
|
|
|
|
|
- <text class="related-subtitle">根据文章内容推荐</text>
|
|
|
|
|
- </view>
|
|
|
|
|
- <dimension-products :products="relatedProducts" layout="mall" @productClick="goRelatedProduct" />
|
|
|
|
|
- <view v-if="relatedProductsLoading" class="related-loading-hint">加载中...</view>
|
|
|
|
|
- <view v-else-if="relatedProductsNoMore" class="related-loading-hint dim">没有更多了</view>
|
|
|
|
|
- </view>
|
|
|
|
|
|
|
+ <!-- 内容关联(相关文章 / 相关活动 / 相关商品,按标签匹配;无匹配整块隐藏,触底分页加载) -->
|
|
|
|
|
+ <related-content ref="relatedContent" source-type="article" :source-id="articleId" />
|
|
|
|
|
|
|
|
<!-- 评论区(已屏蔽) -->
|
|
<!-- 评论区(已屏蔽) -->
|
|
|
<view style="height: 200rpx;"></view>
|
|
<view style="height: 200rpx;"></view>
|
|
@@ -139,25 +107,20 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
|
<script>
|
|
|
-import { getArticleDetail, getArticleAudio, getArticleRelatedProducts, getRelatedArticles, reportReadingTime, getShareQrCode, completeArticleRead, generateQuiz, submitQuiz, completeTask } from '@/utils/api.js'
|
|
|
|
|
|
|
+import { getArticleDetail, getArticleAudio, reportReadingTime, getShareQrCode, completeArticleRead, generateQuiz, submitQuiz, completeTask } from '@/utils/api.js'
|
|
|
import shareMixin from '../../components/share-mixin.js'
|
|
import shareMixin from '../../components/share-mixin.js'
|
|
|
import ContentSharePoster from '@/components/ContentSharePoster.vue'
|
|
import ContentSharePoster from '@/components/ContentSharePoster.vue'
|
|
|
-import DimensionProducts from '@/components/DimensionProducts.vue'
|
|
|
|
|
|
|
+import RelatedContent from '@/components/RelatedContent.vue'
|
|
|
import MpHtml from '@/components/mp-html/mp-html.vue'
|
|
import MpHtml from '@/components/mp-html/mp-html.vue'
|
|
|
import config from '@/config.js'
|
|
import config from '@/config.js'
|
|
|
import { getFontScale } from '@/utils/fontScale.js'
|
|
import { getFontScale } from '@/utils/fontScale.js'
|
|
|
|
|
|
|
|
export default {
|
|
export default {
|
|
|
mixins: [shareMixin],
|
|
mixins: [shareMixin],
|
|
|
- components: { ContentSharePoster, DimensionProducts, MpHtml },
|
|
|
|
|
|
|
+ components: { ContentSharePoster, RelatedContent, MpHtml },
|
|
|
data() {
|
|
data() {
|
|
|
return {
|
|
return {
|
|
|
articleId: '', article: null, loading: true, error: false, errorMsg: '',
|
|
articleId: '', article: null, loading: true, error: false, errorMsg: '',
|
|
|
- relatedProducts: [],
|
|
|
|
|
- relatedArticles: [],
|
|
|
|
|
- // 关联区块分页状态
|
|
|
|
|
- relatedProductsPage: 1, relatedProductsLoading: false, relatedProductsNoMore: false,
|
|
|
|
|
- relatedArticlesPage: 1, relatedArticlesLoading: false, relatedArticlesNoMore: false,
|
|
|
|
|
readingSeconds: 0, lastReportedSeconds: 0, readingCompleted: false,
|
|
readingSeconds: 0, lastReportedSeconds: 0, readingCompleted: false,
|
|
|
isTimerRunning: false, timerHandle: null, syncHandle: null,
|
|
isTimerRunning: false, timerHandle: null, syncHandle: null,
|
|
|
currentChildId: null, isLogin: false,
|
|
currentChildId: null, isLogin: false,
|
|
@@ -244,92 +207,11 @@ export default {
|
|
|
this.article.content = this.resolveContentUrls(this.article.content)
|
|
this.article.content = this.resolveContentUrls(this.article.content)
|
|
|
this.setShareInfo('推荐阅读: ' + (res.data.title || ''), '/pages/article-center/article-detail?id=' + id)
|
|
this.setShareInfo('推荐阅读: ' + (res.data.title || ''), '/pages/article-center/article-detail?id=' + id)
|
|
|
this.startReadingTimer()
|
|
this.startReadingTimer()
|
|
|
- // 关联区块:放在 loadDetail 内只走一次(onShow 不重复请求,避免双发);先重置分页状态再加载第 1 页
|
|
|
|
|
- this.resetRelatedState()
|
|
|
|
|
- this.loadRelatedProducts(id, 1, false)
|
|
|
|
|
- this.loadRelatedArticles(id, 1, false)
|
|
|
|
|
|
|
+ // 内容关联区块由 <related-content> 组件自加载(sourceId=articleId),无需在此触发
|
|
|
} else { this.error = true; this.errorMsg = '文章不存在或无权限查看' }
|
|
} else { this.error = true; this.errorMsg = '文章不存在或无权限查看' }
|
|
|
} catch (e) { this.error = true; this.errorMsg = '加载失败' }
|
|
} catch (e) { this.error = true; this.errorMsg = '加载失败' }
|
|
|
finally { this.loading = false }
|
|
finally { this.loading = false }
|
|
|
},
|
|
},
|
|
|
- // 重置关联区块分页状态(loadDetail 每次加载前调用,避免切文章后旧状态残留)
|
|
|
|
|
- resetRelatedState: function() {
|
|
|
|
|
- this.relatedProductsPage = 1
|
|
|
|
|
- this.relatedProductsLoading = false
|
|
|
|
|
- this.relatedProductsNoMore = false
|
|
|
|
|
- this.relatedArticlesPage = 1
|
|
|
|
|
- this.relatedArticlesLoading = false
|
|
|
|
|
- this.relatedArticlesNoMore = false
|
|
|
|
|
- },
|
|
|
|
|
- // 加载文章关联商品(后端按 标签匹配数 > 用户权重 > 家庭权重 > 销量 排序),每页 4 个,触底翻页
|
|
|
|
|
- loadRelatedProducts: function(articleId, page, append) {
|
|
|
|
|
- var self = this
|
|
|
|
|
- if (!articleId) return
|
|
|
|
|
- var pg = page || 1
|
|
|
|
|
- if (pg === 1) {
|
|
|
|
|
- this.relatedProducts = []
|
|
|
|
|
- this.relatedProductsNoMore = false
|
|
|
|
|
- }
|
|
|
|
|
- if (this.relatedProductsLoading) return
|
|
|
|
|
- this.relatedProductsLoading = true
|
|
|
|
|
- getArticleRelatedProducts({ articleId: articleId, size: 4, page: pg }).then(function(res) {
|
|
|
|
|
- self.relatedProductsLoading = false
|
|
|
|
|
- if (res && res.code === 200 && res.data && Array.isArray(res.data)) {
|
|
|
|
|
- if (append && pg > 1) {
|
|
|
|
|
- self.relatedProducts = self.relatedProducts.concat(res.data)
|
|
|
|
|
- } else {
|
|
|
|
|
- self.relatedProducts = res.data
|
|
|
|
|
- }
|
|
|
|
|
- if (res.data.length < 4) self.relatedProductsNoMore = true
|
|
|
|
|
- } else {
|
|
|
|
|
- self.relatedProductsNoMore = true
|
|
|
|
|
- }
|
|
|
|
|
- }).catch(function() {
|
|
|
|
|
- self.relatedProductsLoading = false
|
|
|
|
|
- self.relatedProductsNoMore = true
|
|
|
|
|
- })
|
|
|
|
|
- },
|
|
|
|
|
- // 加载相关文章(后端按共享标签数降序),每页 3 篇,触底翻页;无匹配返回空数组 → 前端整块隐藏
|
|
|
|
|
- loadRelatedArticles: function(articleId, page, append) {
|
|
|
|
|
- var self = this
|
|
|
|
|
- if (!articleId) return
|
|
|
|
|
- var pg = page || 1
|
|
|
|
|
- if (pg === 1) {
|
|
|
|
|
- this.relatedArticles = []
|
|
|
|
|
- this.relatedArticlesNoMore = false
|
|
|
|
|
- }
|
|
|
|
|
- if (this.relatedArticlesLoading) return
|
|
|
|
|
- this.relatedArticlesLoading = true
|
|
|
|
|
- getRelatedArticles({ articleId: articleId, size: 3, page: pg }).then(function(res) {
|
|
|
|
|
- self.relatedArticlesLoading = false
|
|
|
|
|
- if (res && res.code === 200 && res.data && Array.isArray(res.data)) {
|
|
|
|
|
- if (append && pg > 1) {
|
|
|
|
|
- self.relatedArticles = self.relatedArticles.concat(res.data)
|
|
|
|
|
- } else {
|
|
|
|
|
- self.relatedArticles = res.data
|
|
|
|
|
- }
|
|
|
|
|
- if (res.data.length < 3) self.relatedArticlesNoMore = true
|
|
|
|
|
- } else {
|
|
|
|
|
- self.relatedArticlesNoMore = true
|
|
|
|
|
- }
|
|
|
|
|
- }).catch(function() {
|
|
|
|
|
- self.relatedArticlesLoading = false
|
|
|
|
|
- self.relatedArticlesNoMore = true
|
|
|
|
|
- })
|
|
|
|
|
- },
|
|
|
|
|
- // 跳转相关文章详情
|
|
|
|
|
- goRelatedArticle: function(item) {
|
|
|
|
|
- if (!item || !item.id) return
|
|
|
|
|
- uni.navigateTo({ url: '/pages/article-center/article-detail?id=' + item.id })
|
|
|
|
|
- },
|
|
|
|
|
- // 跳转商品详情(优先使用后端返回的 url)
|
|
|
|
|
- goRelatedProduct: function(prod) {
|
|
|
|
|
- if (!prod) return
|
|
|
|
|
- var pid = prod.productId || prod.id
|
|
|
|
|
- var url = prod.url || ('/pages/discover-detail/product-detail/product-detail?id=' + (pid || ''))
|
|
|
|
|
- if (url.indexOf('/pages/') !== 0) return
|
|
|
|
|
- uni.navigateTo({ url: url })
|
|
|
|
|
- },
|
|
|
|
|
formatDate(d) { return d ? d.slice(0, 10) : '' },
|
|
formatDate(d) { return d ? d.slice(0, 10) : '' },
|
|
|
getImageUrl: function(path) {
|
|
getImageUrl: function(path) {
|
|
|
if (!path) return ''
|
|
if (!path) return ''
|
|
@@ -364,15 +246,9 @@ export default {
|
|
|
return best ? best.code : ''
|
|
return best ? best.code : ''
|
|
|
},
|
|
},
|
|
|
onScrollToBottom: function() {
|
|
onScrollToBottom: function() {
|
|
|
- // 触底优先翻页「相关商品」(页面下方区块),商品加载完再翻「相关文章」
|
|
|
|
|
- if (this.relatedProducts.length > 0 && !this.relatedProductsNoMore && !this.relatedProductsLoading) {
|
|
|
|
|
- this.relatedProductsPage++
|
|
|
|
|
- this.loadRelatedProducts(this.articleId, this.relatedProductsPage, true)
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- if (this.relatedArticles.length > 0 && !this.relatedArticlesNoMore && !this.relatedArticlesLoading) {
|
|
|
|
|
- this.relatedArticlesPage++
|
|
|
|
|
- this.loadRelatedArticles(this.articleId, this.relatedArticlesPage, true)
|
|
|
|
|
|
|
+ // 触底翻页内容关联区块(文章→活动→商品,按组件内 DOM 顺序)
|
|
|
|
|
+ if (this.$refs.relatedContent && this.$refs.relatedContent.loadMore) {
|
|
|
|
|
+ this.$refs.relatedContent.loadMore()
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
startNewReadingSession: function() {
|
|
startNewReadingSession: function() {
|
|
@@ -599,23 +475,6 @@ export default {
|
|
|
.divider { height: 1rpx; background: $facade-hairline-2; margin: 24rpx 30rpx; }
|
|
.divider { height: 1rpx; background: $facade-hairline-2; margin: 24rpx 30rpx; }
|
|
|
.detail-body { padding: 0 30rpx; font-size: calc(32rpx * var(--font-scale, 1)); color: #444; line-height: 1.8; }
|
|
.detail-body { padding: 0 30rpx; font-size: calc(32rpx * var(--font-scale, 1)); color: #444; line-height: 1.8; }
|
|
|
|
|
|
|
|
-/* 关联商品推荐 */
|
|
|
|
|
-.related-section { margin-top: 20rpx; padding: 0 30rpx 8rpx; }
|
|
|
|
|
-.related-header { display: flex; align-items: baseline; margin-bottom: 16rpx; }
|
|
|
|
|
-.related-title { font-size: 30rpx; font-weight: bold; color: $facade-text-strong; }
|
|
|
|
|
-.related-subtitle { font-size: 20rpx; color: $facade-text-muted; margin-left: 12rpx; }
|
|
|
|
|
-.related-loading-hint { text-align: center; font-size: 22rpx; color: $brand-primary; padding: 16rpx 0 8rpx; }
|
|
|
|
|
-.related-loading-hint.dim { color: $facade-text-ghost-light; }
|
|
|
|
|
-
|
|
|
|
|
-/* 相关文章 */
|
|
|
|
|
-.ra-list { display: flex; flex-direction: column; gap: 16rpx; }
|
|
|
|
|
-.ra-item { display: flex; align-items: center; background: $facade-fill-ghost; border-radius: 12rpx; padding: 16rpx; }
|
|
|
|
|
-.ra-item-pressed { opacity: 0.7; }
|
|
|
|
|
-.ra-cover { width: 140rpx; height: 100rpx; border-radius: 8rpx; background: $facade-hairline-2; flex-shrink: 0; }
|
|
|
|
|
-.ra-body { margin-left: 20rpx; flex: 1; min-width: 0; }
|
|
|
|
|
-.ra-title { display: block; font-size: 28rpx; font-weight: 600; color: $facade-text-strong; line-height: 1.4; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
|
|
-.ra-summary { display: block; margin-top: 8rpx; font-size: 22rpx; color: $facade-text-muted; line-height: 1.4; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
|
|
-
|
|
|
|
|
.detail-footer { position: fixed; bottom: 0; left: 0; right: 0; background: $app-surface; padding: 20rpx 30rpx; display: flex; align-items: center; gap: 16rpx; box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.06); z-index: 10; }
|
|
.detail-footer { position: fixed; bottom: 0; left: 0; right: 0; background: $app-surface; padding: 20rpx 30rpx; display: flex; align-items: center; gap: 16rpx; box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.06); z-index: 10; }
|
|
|
.share-btn { height: 72rpx; padding: 0 30rpx; background: linear-gradient(135deg, $brand-primary, $brand-primary-light); border-radius: 36rpx; display: flex; align-items: center; justify-content: center; border: none; }
|
|
.share-btn { height: 72rpx; padding: 0 30rpx; background: linear-gradient(135deg, $brand-primary, $brand-primary-light); border-radius: 36rpx; display: flex; align-items: center; justify-content: center; border: none; }
|
|
|
.share-btn::after { border: none; }
|
|
.share-btn::after { border: none; }
|