feat(miniprogram): 医护工作台角色定制 + 性能优化
- auth store 新增 health_manager 角色,添加 isDoctor/isNurse/isHealthManager/hasRole 辅助方法 - 医生工作台按角色过滤功能卡片和快捷操作(doctor/nurse/health_manager/admin) - 列表页面分页计算提取为 useMemo(patients/alerts/consultation)
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { View, Text, ScrollView } from '@tarojs/components';
|
||||
import Taro from '@tarojs/taro';
|
||||
import * as doctorApi from '@/services/doctor';
|
||||
@@ -34,6 +34,8 @@ export default function AlertList() {
|
||||
const [total, setTotal] = useState(0);
|
||||
const [page, setPage] = useState(1);
|
||||
|
||||
const totalPages = useMemo(() => totalPages, [total]);
|
||||
|
||||
useEffect(() => {
|
||||
loadAlerts();
|
||||
}, [page, activeTab]);
|
||||
@@ -137,11 +139,11 @@ export default function AlertList() {
|
||||
上一页
|
||||
</Text>
|
||||
<Text className='alert-pagination__info'>
|
||||
{page} / {Math.ceil(total / 20)}
|
||||
{page} / {totalPages}
|
||||
</Text>
|
||||
<Text
|
||||
className={`alert-pagination__btn ${page >= Math.ceil(total / 20) ? 'disabled' : ''}`}
|
||||
onClick={() => page < Math.ceil(total / 20) && setPage(page + 1)}
|
||||
className={`alert-pagination__btn ${page >= totalPages ? 'disabled' : ''}`}
|
||||
onClick={() => page < totalPages && setPage(page + 1)}
|
||||
>
|
||||
下一页
|
||||
</Text>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { View, Text, ScrollView } from '@tarojs/components';
|
||||
import Taro from '@tarojs/taro';
|
||||
import * as doctorApi from '@/services/doctor';
|
||||
@@ -22,6 +22,8 @@ export default function ConsultationList() {
|
||||
const [total, setTotal] = useState(0);
|
||||
const [page, setPage] = useState(1);
|
||||
|
||||
const totalPages = useMemo(() => totalPages, [total]);
|
||||
|
||||
useEffect(() => {
|
||||
loadSessions();
|
||||
}, [page, activeTab]);
|
||||
@@ -112,10 +114,10 @@ export default function ConsultationList() {
|
||||
className={`pagination__btn ${page <= 1 ? 'disabled' : ''}`}
|
||||
onClick={() => page > 1 && setPage(page - 1)}
|
||||
>上一页</Text>
|
||||
<Text className='pagination__info'>{page} / {Math.ceil(total / 20)}</Text>
|
||||
<Text className='pagination__info'>{page} / {totalPages}</Text>
|
||||
<Text
|
||||
className={`pagination__btn ${page >= Math.ceil(total / 20) ? 'disabled' : ''}`}
|
||||
onClick={() => page < Math.ceil(total / 20) && setPage(page + 1)}
|
||||
className={`pagination__btn ${page >= totalPages ? 'disabled' : ''}`}
|
||||
onClick={() => page < totalPages && setPage(page + 1)}
|
||||
>下一页</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { View, Text, Input, ScrollView } from '@tarojs/components';
|
||||
import Taro from '@tarojs/taro';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
@@ -11,33 +11,66 @@ interface CardConfig {
|
||||
label: string;
|
||||
initial: string;
|
||||
route: string;
|
||||
roles?: string[];
|
||||
}
|
||||
|
||||
const CARDS: CardConfig[] = [
|
||||
const ALL_CARDS: CardConfig[] = [
|
||||
{ key: 'total_patients', label: '我的患者', initial: '患', route: '/pages/doctor/patients/index' },
|
||||
{ key: 'unread_messages', label: '未读消息', initial: '消', route: '/pages/doctor/consultation/index' },
|
||||
{ key: 'pending_follow_ups', label: '待处理随访', initial: '随', route: '/pages/doctor/followup/index' },
|
||||
{ key: 'today_consultations', label: '今日咨询', initial: '诊', route: '/pages/doctor/consultation/index' },
|
||||
{ key: 'pending_follow_ups', label: '待处理随访', initial: '随', route: '/pages/doctor/followup/index', roles: ['doctor', 'nurse', 'health_manager'] },
|
||||
{ key: 'today_consultations', label: '今日咨询', initial: '诊', route: '/pages/doctor/consultation/index', roles: ['doctor', 'health_manager'] },
|
||||
];
|
||||
|
||||
const HEALTH_CARDS: CardConfig[] = [
|
||||
{ key: 'pending_lab_review', label: '待审化验', initial: '化', route: '/pages/doctor/report/index' },
|
||||
const ALL_HEALTH_CARDS: CardConfig[] = [
|
||||
{ key: 'pending_lab_review', label: '待审化验', initial: '化', route: '/pages/doctor/report/index', roles: ['doctor'] },
|
||||
{ key: 'today_appointments', label: '今日预约', initial: '约', route: '/pages/doctor/patients/index' },
|
||||
];
|
||||
|
||||
const QUICK_ACTIONS = [
|
||||
{ label: '化验审核', initial: '审', route: '/pages/doctor/report/index' },
|
||||
{ label: '患者查询', initial: '查', route: '/pages/doctor/patients/index' },
|
||||
{ label: '随访记录', initial: '随', route: '/pages/doctor/followup/index' },
|
||||
{ label: '告警中心', initial: '警', route: '/pages/doctor/alerts/index' },
|
||||
interface QuickAction {
|
||||
label: string;
|
||||
initial: string;
|
||||
route: string;
|
||||
roles: string[];
|
||||
}
|
||||
|
||||
const ALL_QUICK_ACTIONS: QuickAction[] = [
|
||||
{ label: '化验审核', initial: '审', route: '/pages/doctor/report/index', roles: ['doctor'] },
|
||||
{ label: '患者查询', initial: '查', route: '/pages/doctor/patients/index', roles: ['doctor', 'nurse', 'health_manager'] },
|
||||
{ label: '随访记录', initial: '随', route: '/pages/doctor/followup/index', roles: ['doctor', 'nurse', 'health_manager'] },
|
||||
{ label: '告警中心', initial: '警', route: '/pages/doctor/alerts/index', roles: ['doctor', 'nurse', 'health_manager'] },
|
||||
{ label: '透析管理', initial: '透', route: '/pages/doctor/dialysis/index', roles: ['doctor'] },
|
||||
{ label: '处方管理', initial: '方', route: '/pages/doctor/prescription/index', roles: ['doctor'] },
|
||||
{ label: '行动收件箱', initial: '行', route: '/pages/doctor/action-inbox/index', roles: ['doctor', 'nurse', 'health_manager'] },
|
||||
];
|
||||
|
||||
const ROLE_LABELS: Record<string, string> = {
|
||||
doctor: '医生',
|
||||
nurse: '护士',
|
||||
health_manager: '健康管理师',
|
||||
admin: '管理员',
|
||||
operator: '运营',
|
||||
};
|
||||
|
||||
export default function DoctorHome() {
|
||||
const { user, logout } = useAuthStore();
|
||||
const { user, logout, roles } = useAuthStore();
|
||||
const [dashboard, setDashboard] = useState<doctorApi.DoctorDashboard | null>(null);
|
||||
const [alertCount, setAlertCount] = useState(0);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const hasRole = (allowed: string[] | undefined) => {
|
||||
if (!allowed) return true;
|
||||
return roles.some((r) => r === 'admin' || allowed.includes(r));
|
||||
};
|
||||
|
||||
const cards = useMemo(() => ALL_CARDS.filter((c) => hasRole(c.roles)), [roles]);
|
||||
const healthCards = useMemo(() => ALL_HEALTH_CARDS.filter((c) => hasRole(c.roles)), [roles]);
|
||||
const quickActions = useMemo(() => ALL_QUICK_ACTIONS.filter((a) => hasRole(a.roles)), [roles]);
|
||||
|
||||
const roleLabel = useMemo(() => {
|
||||
const primary = roles.find((r) => r !== 'admin');
|
||||
return primary ? (ROLE_LABELS[primary] || primary) : '医护';
|
||||
}, [roles]);
|
||||
|
||||
useEffect(() => {
|
||||
loadDashboard();
|
||||
}, []);
|
||||
@@ -76,7 +109,7 @@ export default function DoctorHome() {
|
||||
<View className='doctor-home__header'>
|
||||
<Text className='doctor-home__title'>医护工作台</Text>
|
||||
<Text className='doctor-home__greeting'>
|
||||
{user?.display_name || user?.username || '医生'},您好
|
||||
{user?.display_name || user?.username || roleLabel},您好
|
||||
</Text>
|
||||
<Text className='doctor-home__date'>
|
||||
{new Date().toLocaleDateString('zh-CN', { month: 'long', day: 'numeric', weekday: 'long' })}
|
||||
@@ -102,7 +135,7 @@ export default function DoctorHome() {
|
||||
<View className='doctor-home__section'>
|
||||
<Text className='doctor-home__section-title'>工作概览</Text>
|
||||
<View className='doctor-home__grid'>
|
||||
{CARDS.map((card) => (
|
||||
{cards.map((card) => (
|
||||
<View
|
||||
key={card.key}
|
||||
className='doctor-home__card'
|
||||
@@ -116,10 +149,10 @@ export default function DoctorHome() {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='doctor-home__section'>
|
||||
{healthCards.length > 0 && (<View className='doctor-home__section'>
|
||||
<Text className='doctor-home__section-title'>健康审核</Text>
|
||||
<View className='doctor-home__grid'>
|
||||
{HEALTH_CARDS.map((card) => (
|
||||
{healthCards.map((card) => (
|
||||
<View
|
||||
key={card.key}
|
||||
className='doctor-home__card'
|
||||
@@ -131,12 +164,12 @@ export default function DoctorHome() {
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
</View>)}
|
||||
|
||||
<View className='doctor-home__section'>
|
||||
<Text className='doctor-home__section-title'>快捷操作</Text>
|
||||
<View className='doctor-home__quick-actions'>
|
||||
{QUICK_ACTIONS.map((action) => (
|
||||
{quickActions.map((action) => (
|
||||
<View
|
||||
key={action.route}
|
||||
className='quick-action'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { View, Text, Input, ScrollView } from '@tarojs/components';
|
||||
import Taro from '@tarojs/taro';
|
||||
import * as doctorApi from '@/services/doctor';
|
||||
@@ -75,6 +75,8 @@ export default function PatientList() {
|
||||
return `${age}岁`;
|
||||
};
|
||||
|
||||
const totalPages = useMemo(() => Math.ceil(total / 20), [total]);
|
||||
|
||||
if (loading && patients.length === 0) return <Loading />;
|
||||
|
||||
return (
|
||||
@@ -162,10 +164,10 @@ export default function PatientList() {
|
||||
>
|
||||
上一页
|
||||
</Text>
|
||||
<Text className='pagination__info'>{page} / {Math.ceil(total / 20)}</Text>
|
||||
<Text className='pagination__info'>{page} / {totalPages}</Text>
|
||||
<Text
|
||||
className={`pagination__btn ${page >= Math.ceil(total / 20) ? 'disabled' : ''}`}
|
||||
onClick={() => page < Math.ceil(total / 20) && setPage(page + 1)}
|
||||
className={`pagination__btn ${page >= totalPages ? 'disabled' : ''}`}
|
||||
onClick={() => page < totalPages && setPage(page + 1)}
|
||||
>
|
||||
下一页
|
||||
</Text>
|
||||
|
||||
@@ -24,6 +24,10 @@ interface AuthState {
|
||||
logout: () => void;
|
||||
restore: () => void;
|
||||
isMedicalStaff: () => boolean;
|
||||
isDoctor: () => boolean;
|
||||
isNurse: () => boolean;
|
||||
isHealthManager: () => boolean;
|
||||
hasRole: (code: string) => boolean;
|
||||
hasPatientProfile: () => boolean;
|
||||
}
|
||||
|
||||
@@ -36,7 +40,27 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
|
||||
isMedicalStaff: () => {
|
||||
const { roles } = get();
|
||||
return roles.some((r) => r === 'doctor' || r === 'nurse' || r === 'admin');
|
||||
return roles.some((r) => r === 'doctor' || r === 'nurse' || r === 'admin' || r === 'health_manager');
|
||||
},
|
||||
|
||||
isDoctor: () => {
|
||||
const { roles } = get();
|
||||
return roles.some((r) => r === 'doctor' || r === 'admin');
|
||||
},
|
||||
|
||||
isNurse: () => {
|
||||
const { roles } = get();
|
||||
return roles.some((r) => r === 'nurse' || r === 'admin');
|
||||
},
|
||||
|
||||
isHealthManager: () => {
|
||||
const { roles } = get();
|
||||
return roles.some((r) => r === 'health_manager' || r === 'admin');
|
||||
},
|
||||
|
||||
hasRole: (code: string) => {
|
||||
const { roles } = get();
|
||||
return roles.some((r) => r === code || r === 'admin');
|
||||
},
|
||||
|
||||
hasPatientProfile: () => {
|
||||
|
||||
Reference in New Issue
Block a user