Files
hms/apps/miniprogram/src/pages/doctor/prescription/detail/index.tsx
iven fed1759985 fix(mp): setTimeout 无清理修复 — useSafeTimeout hook + 10 页面接入
新增 useSafeTimeout hook,页面隐藏时自动清理所有定时器。
10 个页面接入:daily-monitoring、exchange、family-add、
health/input、prescription detail/create、dialysis detail/create、
appointment detail/create。所有 fire-and-forget setTimeout 替换为
safeSetTimeout,避免页面切走后定时器回调在错误上下文执行。
2026-05-15 00:38:23 +08:00

169 lines
6.2 KiB
TypeScript

import { useState, useEffect } from 'react';
import { View, Text, ScrollView } from '@tarojs/components';
import Taro, { useRouter } from '@tarojs/taro';
import {
getDialysisPrescription, updateDialysisPrescription, deleteDialysisPrescription,
type DialysisPrescription,
} from '@/services/doctor/dialysis';
import Loading from '@/components/Loading';
import { useElderClass } from '../../../../hooks/useElderClass';
import { useSafeTimeout } from '@/hooks/useSafeTimeout';
import './index.scss';
export default function PrescriptionDetail() {
const router = useRouter();
const id = router.params.id || '';
const modeClass = useElderClass();
const [rx, setRx] = useState<DialysisPrescription | null>(null);
const [loading, setLoading] = useState(true);
const [submitting, setSubmitting] = useState(false);
const { safeSetTimeout } = useSafeTimeout();
useEffect(() => {
if (id) loadRx();
}, [id]);
const loadRx = async () => {
setLoading(true);
try {
const data = await getDialysisPrescription(id);
setRx(data);
} catch {
Taro.showToast({ title: '加载失败', icon: 'none' });
} finally {
setLoading(false);
}
};
const handleDeactivate = async () => {
if (!rx) return;
const { confirm } = await Taro.showModal({
title: '确认停用',
content: '停用后该处方将不再生效,确定停用吗?',
});
if (!confirm) return;
setSubmitting(true);
try {
const updated = await updateDialysisPrescription(id, { status: 'inactive' }, rx.version);
setRx(updated);
Taro.showToast({ title: '已停用', icon: 'success' });
} catch {
Taro.showToast({ title: '操作失败', icon: 'none' });
} finally {
setSubmitting(false);
}
};
const handleDelete = async () => {
if (!rx) return;
const { confirm } = await Taro.showModal({
title: '确认删除',
content: '删除后不可恢复,确定要删除这条处方吗?',
});
if (!confirm) return;
setSubmitting(true);
try {
await deleteDialysisPrescription(id, rx.version);
Taro.showToast({ title: '已删除', icon: 'success' });
safeSetTimeout(() => Taro.navigateBack(), 1000);
} catch {
Taro.showToast({ title: '删除失败', icon: 'none' });
setSubmitting(false);
}
};
const Row = ({ label, value, unit }: { label: string; value?: string | number | null; unit?: string }) => {
if (value == null) return null;
return (
<View className='detail-row'>
<Text className='detail-label'>{label}</Text>
<Text className='detail-value'>{value}{unit || ''}</Text>
</View>
);
};
if (loading) return <Loading />;
if (!rx) return <View className={`error-text ${modeClass}`}><Text></Text></View>;
return (
<ScrollView scrollY className={`prescription-detail ${modeClass}`}>
{/* 状态头部 */}
<View className='section'>
<View className='rx-header'>
<Text className='rx-header__title'>{rx.dialyzer_model || '透析处方'}</Text>
<Text className={`rx-header__status rx-header__status--${rx.status}`}>
{rx.status === 'active' ? '生效中' : rx.status === 'inactive' ? '已停用' : rx.status}
</Text>
</View>
{(rx.effective_from || rx.effective_to) && (
<Text className='rx-sub'>{rx.effective_from || '...'} ~ {rx.effective_to || '...'}</Text>
)}
</View>
{/* 基本参数 */}
<View className='section'>
<Text className='section-title'></Text>
<Row label='透析器型号' value={rx.dialyzer_model} />
<Row label='膜面积' value={rx.membrane_area != null ? `${rx.membrane_area}` : null} unit=' m²' />
<Row label='血流速' value={rx.blood_flow_rate} unit=' ml/min' />
<Row label='透析液流量' value={rx.dialysate_flow_rate} unit=' ml/min' />
<Row label='频率' value={rx.frequency_per_week != null ? `${rx.frequency_per_week} 次/周` : null} />
<Row label='每次时长' value={rx.duration_minutes} unit=' 分钟' />
</View>
{/* 透析液配比 */}
<View className='section'>
<Text className='section-title'></Text>
<Row label='钾浓度' value={rx.dialysate_potassium} unit=' mmol/L' />
<Row label='钙浓度' value={rx.dialysate_calcium} unit=' mmol/L' />
<Row label='碳酸氢盐' value={rx.dialysate_bicarbonate} unit=' mmol/L' />
</View>
{/* 抗凝方案 */}
<View className='section'>
<Text className='section-title'></Text>
<Row label='抗凝类型' value={rx.anticoagulation_type} />
<Row label='抗凝剂量' value={rx.anticoagulation_dose} />
</View>
{/* 血管通路 */}
{(rx.vascular_access_type || rx.vascular_access_location) && (
<View className='section'>
<Text className='section-title'></Text>
<Row label='通路类型' value={rx.vascular_access_type} />
<Row label='通路位置' value={rx.vascular_access_location} />
</View>
)}
{/* 超滤目标 */}
{(rx.target_ultrafiltration_ml != null || rx.target_dry_weight != null) && (
<View className='section'>
<Text className='section-title'></Text>
<Row label='目标超滤量' value={rx.target_ultrafiltration_ml} unit=' ml' />
<Row label='目标干体重' value={rx.target_dry_weight} unit=' kg' />
</View>
)}
{/* 备注 */}
{rx.notes && (
<View className='section'>
<Text className='section-title'></Text>
<Text className='notes-text'>{rx.notes}</Text>
</View>
)}
{/* 操作按钮 */}
<View className='actions'>
{rx.status === 'active' && (
<View className={`action-btn action-btn--secondary ${submitting ? 'action-btn--disabled' : ''}`} onClick={handleDeactivate}>
<Text className='action-btn__text'></Text>
</View>
)}
<View className='action-btn action-btn--danger' onClick={handleDelete}>
<Text className='action-btn__text'></Text>
</View>
</View>
</ScrollView>
);
}