feat: 医护仪表盘增强 + 患者端文章分类浏览
- DoctorDashboard 增加 pending_dialysis_review/pending_lab_review/today_appointments - 医护小程序首页增加「健康审核」区块(待审透析/化验/今日预约) - 患者端文章列表增加分类 tabs 横向滚动筛选 - article service 增加 listCategories + category_id 筛选
This commit is contained in:
@@ -7,6 +7,29 @@
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.article-categories {
|
||||
white-space: nowrap;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.article-cat-tab {
|
||||
display: inline-block;
|
||||
padding: 12px 28px;
|
||||
margin-right: 12px;
|
||||
font-size: 26px;
|
||||
color: $tx2;
|
||||
background: $card;
|
||||
border-radius: 32px;
|
||||
border: 2px solid transparent;
|
||||
|
||||
&--active {
|
||||
color: $pri;
|
||||
background: $pri-l;
|
||||
border-color: $pri;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.article-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { View, Text, Image } from '@tarojs/components';
|
||||
import React, { useState, useCallback, useEffect } from 'react';
|
||||
import { View, Text, Image, ScrollView } from '@tarojs/components';
|
||||
import Taro, { useDidShow, usePullDownRefresh, useReachBottom } from '@tarojs/taro';
|
||||
import { listArticles, Article } from '../../services/article';
|
||||
import { listArticles, listCategories, Article, ArticleCategory } from '../../services/article';
|
||||
import EmptyState from '../../components/EmptyState';
|
||||
import Loading from '../../components/Loading';
|
||||
import './index.scss';
|
||||
@@ -11,11 +11,26 @@ export default function ArticleList() {
|
||||
const [page, setPage] = useState(1);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [categories, setCategories] = useState<ArticleCategory[]>([]);
|
||||
const [activeCategory, setActiveCategory] = useState<string | null>(null);
|
||||
|
||||
const fetchData = useCallback(async (p: number, append = false) => {
|
||||
const fetchCategories = useCallback(async () => {
|
||||
try {
|
||||
const data = await listCategories();
|
||||
setCategories(data || []);
|
||||
} catch {
|
||||
// 静默
|
||||
}
|
||||
}, []);
|
||||
|
||||
const fetchData = useCallback(async (p: number, append = false, categoryId?: string | null) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await listArticles(p);
|
||||
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);
|
||||
@@ -25,14 +40,18 @@ export default function ArticleList() {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
}, [activeCategory]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchCategories();
|
||||
}, [fetchCategories]);
|
||||
|
||||
useDidShow(() => {
|
||||
fetchData(1);
|
||||
fetchData(1, false, null);
|
||||
});
|
||||
|
||||
usePullDownRefresh(() => {
|
||||
fetchData(1).finally(() => {
|
||||
fetchData(1, false, null).finally(() => {
|
||||
Taro.stopPullDownRefresh();
|
||||
});
|
||||
});
|
||||
@@ -43,12 +62,38 @@ export default function ArticleList() {
|
||||
}
|
||||
});
|
||||
|
||||
const handleCategoryChange = (categoryId: string | null) => {
|
||||
setActiveCategory(categoryId);
|
||||
fetchData(1, false, categoryId);
|
||||
};
|
||||
|
||||
const goToDetail = (id: string) => {
|
||||
Taro.navigateTo({ url: `/pages/article/detail/index?id=${id}` });
|
||||
};
|
||||
|
||||
return (
|
||||
<View className='article-page'>
|
||||
{/* 分类筛选 */}
|
||||
{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>
|
||||
)}
|
||||
|
||||
<View className='article-list'>
|
||||
{articles.map((a) => (
|
||||
<View
|
||||
@@ -62,8 +107,8 @@ export default function ArticleList() {
|
||||
<Text className='article-card-summary'>{a.summary}</Text>
|
||||
)}
|
||||
<View className='article-card-meta'>
|
||||
{a.category && (
|
||||
<Text className='article-card-tag'>{a.category}</Text>
|
||||
{(a.category_name || a.category) && (
|
||||
<Text className='article-card-tag'>{a.category_name || a.category}</Text>
|
||||
)}
|
||||
{a.published_at && (
|
||||
<Text className='article-card-date'>
|
||||
|
||||
@@ -21,6 +21,12 @@ const CARDS: CardConfig[] = [
|
||||
{ key: 'today_consultations', label: '今日咨询', icon: '🩺', route: '/pages/doctor/consultation/index', color: '#10b981' },
|
||||
];
|
||||
|
||||
const HEALTH_CARDS: CardConfig[] = [
|
||||
{ key: 'pending_dialysis_review', label: '待审透析', icon: '💧', route: '/pages/doctor/patients/index', color: '#0ea5e9' },
|
||||
{ key: 'pending_lab_review', label: '待审化验', icon: '🔬', route: '/pages/doctor/report/index', color: '#f43f5e' },
|
||||
{ key: 'today_appointments', label: '今日预约', icon: '📅', route: '/pages/doctor/patients/index', color: '#14b8a6' },
|
||||
];
|
||||
|
||||
export default function DoctorHome() {
|
||||
const { user, logout } = useAuthStore();
|
||||
const [dashboard, setDashboard] = useState<doctorApi.DoctorDashboard | null>(null);
|
||||
@@ -86,6 +92,24 @@ export default function DoctorHome() {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='doctor-home__section'>
|
||||
<Text className='doctor-home__section-title'>健康审核</Text>
|
||||
<View className='doctor-home__grid'>
|
||||
{HEALTH_CARDS.map((card) => (
|
||||
<View
|
||||
key={card.key}
|
||||
className='doctor-home__card'
|
||||
style={`border-left: 6px solid ${card.color}`}
|
||||
onClick={() => handleCardClick(card)}
|
||||
>
|
||||
<Text className='doctor-home__card-icon'>{card.icon}</Text>
|
||||
<Text className='doctor-home__card-num'>{getValue(card.key)}</Text>
|
||||
<Text className='doctor-home__card-label'>{card.label}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='doctor-home__section'>
|
||||
<Text className='doctor-home__section-title'>快捷操作</Text>
|
||||
<View className='doctor-home__quick-actions'>
|
||||
|
||||
@@ -7,17 +7,40 @@ export interface Article {
|
||||
content?: string;
|
||||
cover_image?: string;
|
||||
category?: string;
|
||||
category_id?: string;
|
||||
category_name?: string;
|
||||
tags?: { id: string; name: string }[];
|
||||
published_at?: string;
|
||||
author?: string;
|
||||
view_count?: number;
|
||||
}
|
||||
|
||||
export async function listArticles(page = 1) {
|
||||
export interface ArticleCategory {
|
||||
id: string;
|
||||
name: string;
|
||||
parent_id?: string;
|
||||
children?: ArticleCategory[];
|
||||
}
|
||||
|
||||
export async function listArticles(params?: {
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
category_id?: string;
|
||||
tag_id?: string;
|
||||
keyword?: string;
|
||||
}) {
|
||||
return api.get<{ data: Article[]; total: number }>('/health/articles', {
|
||||
page,
|
||||
page_size: 20,
|
||||
page: params?.page ?? 1,
|
||||
page_size: params?.page_size ?? 20,
|
||||
status: 'published',
|
||||
...params,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getArticleDetail(id: string) {
|
||||
return api.get<Article>(`/health/articles/${id}`);
|
||||
}
|
||||
|
||||
export async function listCategories() {
|
||||
return api.get<ArticleCategory[]>('/health/article-categories');
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ export interface DoctorDashboard {
|
||||
unread_messages: number;
|
||||
pending_follow_ups: number;
|
||||
today_consultations: number;
|
||||
pending_dialysis_review: number;
|
||||
pending_lab_review: number;
|
||||
today_appointments: number;
|
||||
}
|
||||
|
||||
export async function getDashboard() {
|
||||
|
||||
Reference in New Issue
Block a user