| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- <template>
- <view class="art-page">
- <view class="art-nav" :style="{ paddingTop: statusBar + 'px' }">
- <view class="art-nav-row">
- <view class="art-back" @tap="goBack">
- <text class="art-back-icon">←</text>
- </view>
- <text class="art-nav-title">{{ navTitle }}</text>
- <view class="art-nav-placeholder" />
- </view>
- </view>
- <web-view
- v-if="pageUrl"
- class="art-webview"
- :style="{ height: bodyHeight + 'px' }"
- :webview-styles="webviewStyles"
- :src="pageUrl"
- />
- <scroll-view
- v-else
- scroll-y
- class="art-scroll"
- :style="{ height: bodyHeight + 'px' }"
- >
- <view class="art-body" :style="{ paddingBottom: safeBottom + 'px' }">
- <view v-if="loading" class="art-state">
- <text class="art-state-txt">{{ copy.loading }}</text>
- </view>
- <view v-else-if="!hasContent" class="art-state">
- <text class="art-state-txt">{{ copy.empty }}</text>
- </view>
- <view v-else class="art-card">
- <text v-if="article.title && showHeadTitle" class="art-head-title">{{ article.title }}</text>
- <view v-if="metaLine" class="art-meta">
- <text class="art-meta-txt">{{ metaLine }}</text>
- </view>
- <rich-text
- class="art-rich"
- :nodes="richNodes"
- />
- </view>
- </view>
- </scroll-view>
- </view>
- </template>
- <script>
- import {
- fetchArticleDetails,
- formatArticleRichContent,
- isExternalArticleUrl
- } from '@/features/article/article-gateway'
- export default {
- data() {
- return {
- statusBar: 0,
- bodyHeight: 600,
- safeBottom: 0,
- articleId: '',
- externalUrl: '',
- navTitle: '',
- loading: true,
- article: {},
- richHtml: '',
- richNodes: [],
- webviewStyles: {
- progress: { color: '#4a8cff' }
- }
- }
- },
- computed: {
- copy() {
- const t = this.$t.bind(this)
- return {
- loading: t('article.loading'),
- loadFailed: t('article.loadFailed'),
- empty: t('article.empty')
- }
- },
- pageUrl() {
- if (this.externalUrl) return this.externalUrl
- const url = this.article && this.article.url
- return isExternalArticleUrl(url) ? String(url).trim() : ''
- },
- hasContent() {
- return !!(this.richHtml || (this.richNodes && this.richNodes.length))
- },
- showHeadTitle() {
- return this.navTitle !== (this.article.title || '')
- },
- metaLine() {
- const parts = []
- if (this.article.createtime_text) parts.push(this.article.createtime_text)
- if (this.article.views != null && this.article.views !== '') {
- parts.push(`${this.article.views} ${this.$t('article.views')}`)
- }
- return parts.join(' · ')
- }
- },
- onLoad(option) {
- const sys = uni.getSystemInfoSync()
- this.statusBar = sys.statusBarHeight || 0
- this.safeBottom = sys.safeAreaInsets ? sys.safeAreaInsets.bottom : 0
- const navH = this.statusBar + uni.upx2px(88)
- this.bodyHeight = (sys.windowHeight || 667) - navH
- let title = option.title || ''
- try {
- title = decodeURIComponent(title)
- } catch (_) {}
- this.navTitle = title || this.copy.loading
- const directUrl = option.url || ''
- if (isExternalArticleUrl(directUrl)) {
- this.externalUrl = String(directUrl).trim()
- this.loading = false
- return
- }
- this.articleId = option.id || ''
- if (isExternalArticleUrl(this.articleId)) {
- this.externalUrl = String(this.articleId).trim()
- this.loading = false
- return
- }
- this.loadArticle()
- },
- methods: {
- goBack() {
- if (getCurrentPages().length > 1) {
- uni.navigateBack({ delta: 1 })
- } else {
- uni.switchTab({
- url: '/pages/mine/index',
- fail: () => uni.reLaunch({ url: '/pages/index/index' })
- })
- }
- },
- oss(src, w, h) {
- return this.$appKit && this.$appKit.oss ? this.$appKit.oss(src, w, h) : src
- },
- applyRichContent(html) {
- const rich = formatArticleRichContent(html, this.oss.bind(this))
- this.richHtml = rich.html
- // 优先 HTML 字符串(富文本兼容更好);nodes 作兜底
- this.richNodes = rich.html ? rich.html : rich.nodes
- },
- async loadArticle() {
- if (!this.articleId) {
- this.loading = false
- this.$appKit.msg(this.copy.loadFailed)
- return
- }
- this.loading = true
- try {
- const data = await fetchArticleDetails(this.articleId)
- this.article = data || {}
- if (data.title) {
- this.navTitle = data.title
- }
- if (isExternalArticleUrl(data.url)) {
- this.loading = false
- return
- }
- this.applyRichContent(data.content)
- } catch (err) {
- this.$appKit.msg((err && err.message) || this.copy.loadFailed)
- } finally {
- this.loading = false
- }
- }
- }
- }
- </script>
- <style scoped>
- @import './article-shared.css';
- .art-head-title {
- display: block;
- font-size: 36rpx;
- font-weight: 700;
- color: #1a1d26;
- line-height: 1.4;
- margin-bottom: 16rpx;
- }
- .art-meta {
- margin-bottom: 24rpx;
- padding-bottom: 20rpx;
- border-bottom: 1rpx solid #eef1f6;
- }
- .art-meta-txt {
- font-size: 24rpx;
- color: #8a8d9e;
- }
- .art-rich {
- display: block;
- width: 100%;
- font-size: 30rpx;
- color: #3d4150;
- line-height: 1.65;
- word-break: break-word;
- }
- </style>
|