- 引入 mp-html 替代 RichText,支持图文混排、表格等复杂内容 - 新建 RichArticle 组件封装 sanitizeHtml + mp-html - 通过 native-components 拷贝原生组件到 dist - 优化文章排版样式(字号、间距、分隔线、底栏安全区) - sanitize-html 扩展允许 style/data-w-e-type 属性
113 lines
3.4 KiB
TypeScript
113 lines
3.4 KiB
TypeScript
import { useState, useCallback } from 'react';
|
|
import { View, Text } from '@tarojs/components';
|
|
import Taro, { useRouter, useShareAppMessage } from '@tarojs/taro';
|
|
import { usePageData } from '@/hooks/usePageData';
|
|
import { getArticleDetail, getPublicArticleDetail, Article } from '../../../services/article';
|
|
import { trackEvent } from '@/services/analytics';
|
|
import { useElderClass } from '../../../hooks/useElderClass';
|
|
import { useAuthStore } from '../../../stores/auth';
|
|
import PageShell from '@/components/ui/PageShell';
|
|
import RichArticle from '@/components/RichArticle';
|
|
import './index.scss';
|
|
|
|
export default function ArticleDetail() {
|
|
const modeClass = useElderClass();
|
|
const router = useRouter();
|
|
const id = router.params.id || '';
|
|
|
|
const [article, setArticle] = useState<Article | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useShareAppMessage(() => {
|
|
trackEvent('article_share', { article_id: id });
|
|
return {
|
|
title: article?.title || '健康资讯',
|
|
path: `/pages/article/detail/index?id=${id}`,
|
|
};
|
|
});
|
|
|
|
const fetchArticle = useCallback(async () => {
|
|
if (!id) return;
|
|
setLoading(true);
|
|
try {
|
|
const user = useAuthStore.getState().user;
|
|
const fetcher = user ? getArticleDetail(id) : getPublicArticleDetail(id);
|
|
const data = await fetcher;
|
|
setArticle(data);
|
|
} catch (err) {
|
|
console.warn('[article] 加载文章详情失败:', err);
|
|
Taro.showToast({ title: '加载失败', icon: 'none' });
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [id]);
|
|
|
|
usePageData(fetchArticle, { throttleMs: 60000 });
|
|
|
|
if (loading) {
|
|
return (
|
|
<PageShell className={modeClass}>
|
|
<View className='loading-state'>
|
|
<Text className='loading-text'>加载中...</Text>
|
|
</View>
|
|
</PageShell>
|
|
);
|
|
}
|
|
|
|
if (!article) {
|
|
return (
|
|
<PageShell className={modeClass}>
|
|
<View className='empty-state'>
|
|
<Text className='empty-text'>文章不存在</Text>
|
|
</View>
|
|
</PageShell>
|
|
);
|
|
}
|
|
|
|
const handleCollect = () => {
|
|
Taro.showToast({ title: '已收藏', icon: 'success' });
|
|
};
|
|
|
|
const handleShare = () => {
|
|
Taro.showShareMenu({ withShareTicket: true });
|
|
};
|
|
|
|
return (
|
|
<PageShell padding="md" safeBottom={false} scroll={false} className={`article-detail-page ${modeClass}`}>
|
|
<Text className='article-title'>{article.title}</Text>
|
|
|
|
<View className='article-meta'>
|
|
{article.author && (
|
|
<View className='meta-item'>
|
|
<Text className='meta-icon'>✍</Text>
|
|
<Text>{article.author}</Text>
|
|
</View>
|
|
)}
|
|
{article.published_at && (
|
|
<View className='meta-item'>
|
|
<Text className='meta-icon'>📅</Text>
|
|
<Text>{article.published_at.slice(0, 10)}</Text>
|
|
</View>
|
|
)}
|
|
</View>
|
|
|
|
<View className='article-divider' />
|
|
|
|
<View className='article-body'>
|
|
<RichArticle html={article.content || ''} />
|
|
</View>
|
|
|
|
<View className='article-bottom-bar'>
|
|
<View className='article-action-btn' onClick={handleCollect}>
|
|
<Text className='article-action-icon'>♡</Text>
|
|
<Text className='article-action-text'>收藏</Text>
|
|
</View>
|
|
<View className='article-action-btn' onClick={handleShare}>
|
|
<Text className='article-action-icon'>↑</Text>
|
|
<Text className='article-action-text'>分享</Text>
|
|
</View>
|
|
</View>
|
|
</PageShell>
|
|
);
|
|
}
|