Files
digital-pilates/app/article/[id].tsx
richarjiang 5d09cc05dc feat: 更新文章功能和相关依赖
- 新增文章详情页面,支持根据文章 ID 加载和展示文章内容
- 添加文章卡片组件,展示推荐文章的标题、封面和阅读量
- 更新文章服务,支持获取文章列表和根据 ID 获取文章详情
- 集成腾讯云 COS SDK,支持文件上传功能
- 优化打卡功能,支持按日期加载和展示打卡记录
- 更新相关依赖,确保项目兼容性和功能完整性
- 调整样式以适应新功能的展示和交互
2025-08-14 16:03:19 +08:00

127 lines
3.6 KiB
TypeScript

import { HeaderBar } from '@/components/ui/HeaderBar';
import { Colors } from '@/constants/Colors';
import { useColorScheme } from '@/hooks/useColorScheme';
import { Article, getArticleById } from '@/services/articles';
import dayjs from 'dayjs';
import { useLocalSearchParams, useRouter } from 'expo-router';
import React, { useEffect, useState } from 'react';
import { ScrollView, StyleSheet, Text, View, useWindowDimensions } from 'react-native';
import RenderHTML from 'react-native-render-html';
export default function ArticleDetailScreen() {
const { id } = useLocalSearchParams<{ id: string }>();
const router = useRouter();
const [article, setArticle] = useState<Article | undefined>(undefined);
const { width } = useWindowDimensions();
const colorScheme = (useColorScheme() ?? 'light') as 'light' | 'dark';
const theme = Colors[colorScheme];
useEffect(() => {
if (id) {
getArticleById(id).then((article) => {
console.log('article', article);
setArticle(article);
});
}
}, [id]);
if (!article) {
return (
<View style={{ flex: 1 }}>
<HeaderBar title="文章" onBack={() => router.back()} showBottomBorder />
<View style={{ padding: 24 }}>
</View>
</View>
);
}
const source = { html: wrapHtml(article.htmlContent) };
return (
<View style={{ flex: 1, backgroundColor: theme.surface }}>
<HeaderBar title="文章" onBack={() => router.back()} showBottomBorder />
<ScrollView contentContainerStyle={styles.contentContainer} showsVerticalScrollIndicator={false}>
<View style={styles.headerMeta}>
<Text style={[styles.title, { color: theme.text }]}>{article.title}</Text>
<View style={styles.row}>
<Text style={[styles.metaText, { color: theme.textMuted }]}>{dayjs(article.publishedAt).format('YYYY-MM-DD')}</Text>
<Text style={[styles.metaText, styles.dot]}>·</Text>
<Text style={[styles.metaText, { color: theme.textMuted }]}>{article.readCount} </Text>
</View>
</View>
<RenderHTML
contentWidth={width - 48}
source={source}
baseStyle={{ ...htmlBaseStyles, color: theme.text }}
tagsStyles={htmlTagStyles}
enableExperimentalMarginCollapsing={true}
/>
<View style={{ height: 36 }} />
</ScrollView>
</View>
);
}
function wrapHtml(inner: string) {
// 为了统一排版与图片自适应
return `
<div class="article">
${inner}
</div>
<style>
.article img { max-width: 100%; height: auto; border-radius: 12px; }
</style>
`;
}
const styles = StyleSheet.create({
contentContainer: {
paddingHorizontal: 24,
paddingTop: 12,
},
headerMeta: {
marginBottom: 12,
},
title: {
fontSize: 22,
fontWeight: '800',
color: '#192126',
},
row: {
flexDirection: 'row',
alignItems: 'center',
marginTop: 8,
},
metaText: {
fontSize: 12,
color: '#8A8A8E',
},
dot: {
paddingHorizontal: 6,
},
});
const htmlBaseStyles = {
color: '#192126',
lineHeight: 24,
fontSize: 16,
} as const;
const htmlTagStyles = {
h1: { fontSize: 26, fontWeight: '800', marginBottom: 8 },
h2: { fontSize: 22, fontWeight: '800', marginTop: 8, marginBottom: 8 },
h3: { fontSize: 18, fontWeight: '700', marginTop: 12, marginBottom: 6 },
p: { marginBottom: 12 },
ol: { marginBottom: 12, paddingLeft: 18 },
ul: { marginBottom: 12, paddingLeft: 18 },
li: { marginBottom: 6 },
img: { marginTop: 8, marginBottom: 8, borderRadius: 12 },
em: { fontStyle: 'italic' },
strong: { fontWeight: '800' },
} as const;