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 { View, Text, ScrollView } from '@tarojs/components';
|
||||||
import Taro from '@tarojs/taro';
|
import Taro from '@tarojs/taro';
|
||||||
import * as doctorApi from '@/services/doctor';
|
import * as doctorApi from '@/services/doctor';
|
||||||
@@ -34,6 +34,8 @@ export default function AlertList() {
|
|||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
|
|
||||||
|
const totalPages = useMemo(() => totalPages, [total]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadAlerts();
|
loadAlerts();
|
||||||
}, [page, activeTab]);
|
}, [page, activeTab]);
|
||||||
@@ -137,11 +139,11 @@ export default function AlertList() {
|
|||||||
上一页
|
上一页
|
||||||
</Text>
|
</Text>
|
||||||
<Text className='alert-pagination__info'>
|
<Text className='alert-pagination__info'>
|
||||||
{page} / {Math.ceil(total / 20)}
|
{page} / {totalPages}
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
className={`alert-pagination__btn ${page >= Math.ceil(total / 20) ? 'disabled' : ''}`}
|
className={`alert-pagination__btn ${page >= totalPages ? 'disabled' : ''}`}
|
||||||
onClick={() => page < Math.ceil(total / 20) && setPage(page + 1)}
|
onClick={() => page < totalPages && setPage(page + 1)}
|
||||||
>
|
>
|
||||||
下一页
|
下一页
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
import { View, Text, ScrollView } from '@tarojs/components';
|
import { View, Text, ScrollView } from '@tarojs/components';
|
||||||
import Taro from '@tarojs/taro';
|
import Taro from '@tarojs/taro';
|
||||||
import * as doctorApi from '@/services/doctor';
|
import * as doctorApi from '@/services/doctor';
|
||||||
@@ -22,6 +22,8 @@ export default function ConsultationList() {
|
|||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
|
|
||||||
|
const totalPages = useMemo(() => totalPages, [total]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadSessions();
|
loadSessions();
|
||||||
}, [page, activeTab]);
|
}, [page, activeTab]);
|
||||||
@@ -112,10 +114,10 @@ export default function ConsultationList() {
|
|||||||
className={`pagination__btn ${page <= 1 ? 'disabled' : ''}`}
|
className={`pagination__btn ${page <= 1 ? 'disabled' : ''}`}
|
||||||
onClick={() => page > 1 && setPage(page - 1)}
|
onClick={() => page > 1 && setPage(page - 1)}
|
||||||
>上一页</Text>
|
>上一页</Text>
|
||||||
<Text className='pagination__info'>{page} / {Math.ceil(total / 20)}</Text>
|
<Text className='pagination__info'>{page} / {totalPages}</Text>
|
||||||
<Text
|
<Text
|
||||||
className={`pagination__btn ${page >= Math.ceil(total / 20) ? 'disabled' : ''}`}
|
className={`pagination__btn ${page >= totalPages ? 'disabled' : ''}`}
|
||||||
onClick={() => page < Math.ceil(total / 20) && setPage(page + 1)}
|
onClick={() => page < totalPages && setPage(page + 1)}
|
||||||
>下一页</Text>
|
>下一页</Text>
|
||||||
</View>
|
</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 { View, Text, Input, ScrollView } from '@tarojs/components';
|
||||||
import Taro from '@tarojs/taro';
|
import Taro from '@tarojs/taro';
|
||||||
import { useAuthStore } from '@/stores/auth';
|
import { useAuthStore } from '@/stores/auth';
|
||||||
@@ -11,33 +11,66 @@ interface CardConfig {
|
|||||||
label: string;
|
label: string;
|
||||||
initial: string;
|
initial: string;
|
||||||
route: string;
|
route: string;
|
||||||
|
roles?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const CARDS: CardConfig[] = [
|
const ALL_CARDS: CardConfig[] = [
|
||||||
{ key: 'total_patients', label: '我的患者', initial: '患', route: '/pages/doctor/patients/index' },
|
{ key: 'total_patients', label: '我的患者', initial: '患', route: '/pages/doctor/patients/index' },
|
||||||
{ key: 'unread_messages', label: '未读消息', initial: '消', route: '/pages/doctor/consultation/index' },
|
{ key: 'unread_messages', label: '未读消息', initial: '消', route: '/pages/doctor/consultation/index' },
|
||||||
{ key: 'pending_follow_ups', label: '待处理随访', initial: '随', route: '/pages/doctor/followup/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' },
|
{ key: 'today_consultations', label: '今日咨询', initial: '诊', route: '/pages/doctor/consultation/index', roles: ['doctor', 'health_manager'] },
|
||||||
];
|
];
|
||||||
|
|
||||||
const HEALTH_CARDS: CardConfig[] = [
|
const ALL_HEALTH_CARDS: CardConfig[] = [
|
||||||
{ key: 'pending_lab_review', label: '待审化验', initial: '化', route: '/pages/doctor/report/index' },
|
{ key: 'pending_lab_review', label: '待审化验', initial: '化', route: '/pages/doctor/report/index', roles: ['doctor'] },
|
||||||
{ key: 'today_appointments', label: '今日预约', initial: '约', route: '/pages/doctor/patients/index' },
|
{ key: 'today_appointments', label: '今日预约', initial: '约', route: '/pages/doctor/patients/index' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const QUICK_ACTIONS = [
|
interface QuickAction {
|
||||||
{ label: '化验审核', initial: '审', route: '/pages/doctor/report/index' },
|
label: string;
|
||||||
{ label: '患者查询', initial: '查', route: '/pages/doctor/patients/index' },
|
initial: string;
|
||||||
{ label: '随访记录', initial: '随', route: '/pages/doctor/followup/index' },
|
route: string;
|
||||||
{ label: '告警中心', initial: '警', route: '/pages/doctor/alerts/index' },
|
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() {
|
export default function DoctorHome() {
|
||||||
const { user, logout } = useAuthStore();
|
const { user, logout, roles } = useAuthStore();
|
||||||
const [dashboard, setDashboard] = useState<doctorApi.DoctorDashboard | null>(null);
|
const [dashboard, setDashboard] = useState<doctorApi.DoctorDashboard | null>(null);
|
||||||
const [alertCount, setAlertCount] = useState(0);
|
const [alertCount, setAlertCount] = useState(0);
|
||||||
const [loading, setLoading] = useState(true);
|
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(() => {
|
useEffect(() => {
|
||||||
loadDashboard();
|
loadDashboard();
|
||||||
}, []);
|
}, []);
|
||||||
@@ -76,7 +109,7 @@ export default function DoctorHome() {
|
|||||||
<View className='doctor-home__header'>
|
<View className='doctor-home__header'>
|
||||||
<Text className='doctor-home__title'>医护工作台</Text>
|
<Text className='doctor-home__title'>医护工作台</Text>
|
||||||
<Text className='doctor-home__greeting'>
|
<Text className='doctor-home__greeting'>
|
||||||
{user?.display_name || user?.username || '医生'},您好
|
{user?.display_name || user?.username || roleLabel},您好
|
||||||
</Text>
|
</Text>
|
||||||
<Text className='doctor-home__date'>
|
<Text className='doctor-home__date'>
|
||||||
{new Date().toLocaleDateString('zh-CN', { month: 'long', day: 'numeric', weekday: 'long' })}
|
{new Date().toLocaleDateString('zh-CN', { month: 'long', day: 'numeric', weekday: 'long' })}
|
||||||
@@ -102,7 +135,7 @@ export default function DoctorHome() {
|
|||||||
<View className='doctor-home__section'>
|
<View className='doctor-home__section'>
|
||||||
<Text className='doctor-home__section-title'>工作概览</Text>
|
<Text className='doctor-home__section-title'>工作概览</Text>
|
||||||
<View className='doctor-home__grid'>
|
<View className='doctor-home__grid'>
|
||||||
{CARDS.map((card) => (
|
{cards.map((card) => (
|
||||||
<View
|
<View
|
||||||
key={card.key}
|
key={card.key}
|
||||||
className='doctor-home__card'
|
className='doctor-home__card'
|
||||||
@@ -116,10 +149,10 @@ export default function DoctorHome() {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View className='doctor-home__section'>
|
{healthCards.length > 0 && (<View className='doctor-home__section'>
|
||||||
<Text className='doctor-home__section-title'>健康审核</Text>
|
<Text className='doctor-home__section-title'>健康审核</Text>
|
||||||
<View className='doctor-home__grid'>
|
<View className='doctor-home__grid'>
|
||||||
{HEALTH_CARDS.map((card) => (
|
{healthCards.map((card) => (
|
||||||
<View
|
<View
|
||||||
key={card.key}
|
key={card.key}
|
||||||
className='doctor-home__card'
|
className='doctor-home__card'
|
||||||
@@ -131,12 +164,12 @@ export default function DoctorHome() {
|
|||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>)}
|
||||||
|
|
||||||
<View className='doctor-home__section'>
|
<View className='doctor-home__section'>
|
||||||
<Text className='doctor-home__section-title'>快捷操作</Text>
|
<Text className='doctor-home__section-title'>快捷操作</Text>
|
||||||
<View className='doctor-home__quick-actions'>
|
<View className='doctor-home__quick-actions'>
|
||||||
{QUICK_ACTIONS.map((action) => (
|
{quickActions.map((action) => (
|
||||||
<View
|
<View
|
||||||
key={action.route}
|
key={action.route}
|
||||||
className='quick-action'
|
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 { View, Text, Input, ScrollView } from '@tarojs/components';
|
||||||
import Taro from '@tarojs/taro';
|
import Taro from '@tarojs/taro';
|
||||||
import * as doctorApi from '@/services/doctor';
|
import * as doctorApi from '@/services/doctor';
|
||||||
@@ -75,6 +75,8 @@ export default function PatientList() {
|
|||||||
return `${age}岁`;
|
return `${age}岁`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const totalPages = useMemo(() => Math.ceil(total / 20), [total]);
|
||||||
|
|
||||||
if (loading && patients.length === 0) return <Loading />;
|
if (loading && patients.length === 0) return <Loading />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -162,10 +164,10 @@ export default function PatientList() {
|
|||||||
>
|
>
|
||||||
上一页
|
上一页
|
||||||
</Text>
|
</Text>
|
||||||
<Text className='pagination__info'>{page} / {Math.ceil(total / 20)}</Text>
|
<Text className='pagination__info'>{page} / {totalPages}</Text>
|
||||||
<Text
|
<Text
|
||||||
className={`pagination__btn ${page >= Math.ceil(total / 20) ? 'disabled' : ''}`}
|
className={`pagination__btn ${page >= totalPages ? 'disabled' : ''}`}
|
||||||
onClick={() => page < Math.ceil(total / 20) && setPage(page + 1)}
|
onClick={() => page < totalPages && setPage(page + 1)}
|
||||||
>
|
>
|
||||||
下一页
|
下一页
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ interface AuthState {
|
|||||||
logout: () => void;
|
logout: () => void;
|
||||||
restore: () => void;
|
restore: () => void;
|
||||||
isMedicalStaff: () => boolean;
|
isMedicalStaff: () => boolean;
|
||||||
|
isDoctor: () => boolean;
|
||||||
|
isNurse: () => boolean;
|
||||||
|
isHealthManager: () => boolean;
|
||||||
|
hasRole: (code: string) => boolean;
|
||||||
hasPatientProfile: () => boolean;
|
hasPatientProfile: () => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +40,27 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
|||||||
|
|
||||||
isMedicalStaff: () => {
|
isMedicalStaff: () => {
|
||||||
const { roles } = get();
|
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: () => {
|
hasPatientProfile: () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user