- 新增文章详情页面,支持根据文章 ID 加载和展示文章内容 - 添加文章卡片组件,展示推荐文章的标题、封面和阅读量 - 更新文章服务,支持获取文章列表和根据 ID 获取文章详情 - 集成腾讯云 COS SDK,支持文件上传功能 - 优化打卡功能,支持按日期加载和展示打卡记录 - 更新相关依赖,确保项目兼容性和功能完整性 - 调整样式以适应新功能的展示和交互
127 lines
3.6 KiB
TypeScript
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;
|
|
|
|
|