Files
hms/apps/miniprogram/src/pages/article/index.tsx
iven 59dd5ef38e fix(mp): P1+P2 稳定性加固 — 导航安全+生产日志+分包预加载+logout清理
P1:
- 全局 23 个页面 Taro.navigateTo → safeNavigateTo,防止页栈超10层
- 生产构建保留 console.warn/error,便于线上问题排查
- 添加 preloadRule 分包预加载(首页预加载健康/医生/文章分包)

P2:
- logout 时清理 ai_chat_history + BLE DataBuffer 缓存
- restore() 移除冗余的双重 Storage 读取(secureGet 已包含 getStorageSync)
- 首页文章图片添加 lazyLoad
2026-05-17 17:13:35 +08:00

151 lines
4.7 KiB
TypeScript

import { useState, useCallback } from 'react';
import { View, Text, ScrollView } from '@tarojs/components';
import Taro from '@tarojs/taro';
import { safeNavigateTo } from '@/utils/navigate';
import { usePageData } from '@/hooks/usePageData';
import { listArticles, listCategories } from '../../services/article';
import PageShell from '@/components/ui/PageShell';
import ContentCard from '@/components/ui/ContentCard';
import LoadingCard from '@/components/ui/LoadingCard';
import EmptyState from '@/components/EmptyState';
import ErrorState from '@/components/ErrorState';
import Loading from '@/components/Loading';
import { useElderClass } from '../../hooks/useElderClass';
import './index.scss';
interface ArticleItem {
id: string;
title: string;
summary?: string;
cover_image?: string;
category?: string;
category_id?: string;
category_name?: string;
published_at?: string;
status?: string;
}
interface ArticleCategory {
id: string;
name: string;
parent_id?: string | null;
}
export default function ArticleList() {
const modeClass = useElderClass();
const [articles, setArticles] = useState<ArticleItem[]>([]);
const [page, setPage] = useState(1);
const [total, setTotal] = useState(0);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [categories, setCategories] = useState<ArticleCategory[]>([]);
const [activeCategory, setActiveCategory] = useState<string | null>(null);
const fetchData = useCallback(async (p: number, append = false, categoryId?: string | null) => {
setLoading(true);
setError(false);
try {
const cid = categoryId !== undefined ? categoryId : activeCategory;
const res = await listArticles({
page: p,
category_id: cid || undefined,
});
const list = res.data || [];
setArticles(append ? (prev) => [...prev, ...list] : list);
setTotal(res.total);
setPage(p);
} catch {
setError(true);
Taro.showToast({ title: '加载失败', icon: 'none' });
} finally {
setLoading(false);
}
}, [activeCategory]);
usePageData(
useCallback(async () => {
try {
const cats = await listCategories();
setCategories(cats || []);
} catch {
setCategories([]);
}
await fetchData(1);
}, [fetchData]),
{ throttleMs: 10000, enablePullDown: true },
);
const handleCategoryChange = (categoryId: string | null) => {
setActiveCategory(categoryId);
fetchData(1, false, categoryId);
};
const goToDetail = (id: string) => {
safeNavigateTo(`/pages/article/detail/index?id=${id}`);
};
if (loading && articles.length === 0 && !error) {
return <LoadingCard count={3} />;
}
return (
<PageShell safeBottom padding="none" className={modeClass}>
{categories.length > 0 && (
<ScrollView scrollX className='article-categories'>
<View
className={`article-cat-tab ${!activeCategory ? 'article-cat-tab--active' : ''}`}
onClick={() => handleCategoryChange(null)}
>
<Text></Text>
</View>
{categories.map((cat) => (
<View
key={cat.id}
className={`article-cat-tab ${activeCategory === cat.id ? 'article-cat-tab--active' : ''}`}
onClick={() => handleCategoryChange(cat.id)}
>
<Text>{cat.name}</Text>
</View>
))}
</ScrollView>
)}
{error ? (
<ErrorState onRetry={() => fetchData(1, false, null)} />
) : articles.length === 0 && !loading ? (
<EmptyState text='暂无资讯文章' />
) : (
<View className='article-list'>
{articles.map((a) => (
<ContentCard
key={a.id}
padding="sm"
margin="none"
onPress={() => goToDetail(a.id)}
>
<View className='article-card-body'>
<Text className='article-card-title'>{a.title}</Text>
{a.summary && (
<Text className='article-card-summary'>{a.summary}</Text>
)}
<View className='article-card-meta'>
{(a.category_name || a.category) && (
<Text className='article-card-tag'>{a.category_name || a.category}</Text>
)}
{a.published_at && (
<Text className='article-card-date'>
{a.published_at.slice(0, 10)}
</Text>
)}
</View>
</View>
</ContentCard>
))}
</View>
)}
{loading && <Loading />}
</PageShell>
);
}