hn-hlw-app/pages/article/article-detail.vue
2026-07-27 11:26:39 +08:00

258 lines
5.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<full-page pageStyle="background:#fff">
<scroll-view class="article-detail" scroll-y :style="elderVarStyle">
<!-- 加载中 -->
<view v-if="loading" class="loading-container">
<uni-load-more status="loading" />
</view>
<!-- 文章内容 -->
<view v-else-if="article" class="article-container">
<view class="article-header">
<view class="article-title">{{ article.title }}</view>
<view class="article-meta">
<text class="article-time">{{ formatTime(article.createTime) }}</text>
</view>
</view>
<!-- 文章封面 -->
<!-- <image
v-if="article.cover"
class="article-cover"
:src="article.cover"
mode="widthFix"
/> -->
<!-- 文章摘要 -->
<!-- <view v-if="article.summary" class="article-summary">
{{ article.summary }}
</view> -->
<!-- 文章正文 -->
<view class="article-body">
<rich-text :nodes="article.content" />
</view>
<!-- 关键词标签 -->
<view v-if="article.keyword" class="article-keywords">
<view class="keyword-label">关键词</view>
<view class="keyword-list">
<view
v-for="(keyword, index) in keywordList"
:key="index"
class="keyword-tag"
>
{{ keyword }}
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<empty v-else text="文章不存在或已删除" />
</scroll-view>
</full-page>
</template>
<script setup>
import { ref, computed } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import fullPage from "@/components/full-page.vue";
import empty from "@/components/empty.vue";
import useElder from "@/hooks/useElder";
import useUser from "@/hooks/useUser";
import { getArticle, addArticleReadRecord } from "@/utils/api";
const { elderVarStyle } = useElder();
const { logined, userInfo } = useUser();
const loading = ref(true);
const article = ref(null);
const articleId = ref("");
// 关键词列表
const keywordList = computed(() => {
if (!article.value?.keyword) return [];
return article.value.keyword.split(/[,,、\s]+/).filter(Boolean);
});
onLoad((options) => {
if (options.id) {
articleId.value = options.id;
fetchArticle();
} else {
loading.value = false;
}
});
async function fetchArticle() {
loading.value = true;
try {
const res = await getArticle(articleId.value);
if (res.success && res.data) {
article.value = res.data;
// 处理富文本中的图片样式
if (article.value.content) {
article.value.content = article.value.content.replace(/<img/gi, '<img style="max-width:100%;height:auto;display:block;"');
}
// 设置页面标题
uni.setNavigationBarTitle({
title: res.data.title || "文章详情",
});
// 阅读打点(不阻塞渲染)
recordArticleRead();
}
} catch (e) {
console.error("获取文章详情失败", e);
} finally {
loading.value = false;
}
}
function getArticleVisitorId() {
let visitorId = uni.getStorageSync("articleVisitorId");
if (!visitorId) {
visitorId = `mp_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
uni.setStorageSync("articleVisitorId", visitorId);
}
return visitorId;
}
async function recordArticleRead() {
try {
if (!articleId.value) return;
const accountId = logined.value && userInfo.value && userInfo.value.userId ? String(userInfo.value.userId) : "";
await addArticleReadRecord(
accountId
? { articleId: articleId.value, accountId }
: { articleId: articleId.value, unionid: getArticleVisitorId() }
);
} catch (e) {
// 统计失败不影响用户阅读
console.warn("文章阅读打点失败", e);
}
}
// 格式化时间
function formatTime(timestamp) {
if (!timestamp) return "";
const date = new Date(timestamp);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
const hour = String(date.getHours()).padStart(2, "0");
const minute = String(date.getMinutes()).padStart(2, "0");
return `${year}-${month}-${day} ${hour}:${minute}`;
}
</script>
<style lang="scss" scoped>
.article-detail {
height: 100%;
}
.loading-container {
display: flex;
justify-content: center;
align-items: center;
height: 400rpx;
}
.article-container {
padding: 30rpx;
}
.article-header {
margin-bottom: 30rpx;
}
.article-title {
font-size: 40rpx;
font-weight: 600;
color: #333;
line-height: 1.4;
}
.article-meta {
margin-top: 20rpx;
display: flex;
align-items: center;
}
.article-time {
font-size: var(--text-sm);
color: #999;
}
.article-cover {
width: 100%;
border-radius: 16rpx;
margin-bottom: 30rpx;
}
.article-summary {
padding: 24rpx;
margin-bottom: 30rpx;
background: #f8f9fa;
border-radius: 12rpx;
font-size: var(--text-sm);
color: #666;
line-height: 1.8;
border-left: 6rpx solid $theme-brown-primary;
}
.article-body {
font-size: var(--text-base);
color: #333;
line-height: 1.8;
:deep(img) {
max-width: 100%;
border-radius: 12rpx;
}
:deep(p) {
margin-bottom: 20rpx;
}
:deep(h1),
:deep(h2),
:deep(h3),
:deep(h4) {
font-weight: 600;
margin: 30rpx 0 20rpx;
}
}
.article-keywords {
margin-top: 40rpx;
padding-top: 30rpx;
border-top: 1px solid #eee;
display: flex;
align-items: flex-start;
}
.keyword-label {
flex-shrink: 0;
font-size: var(--text-sm);
color: #999;
margin-right: 16rpx;
line-height: 48rpx;
}
.keyword-list {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.keyword-tag {
padding: 8rpx 20rpx;
background: $theme-brown-light;
color: $theme-brown-primary;
font-size: var(--text-xs);
border-radius: 20rpx;
}
</style>