fix(mp): 患者端卡死深度审查修复 — CRITICAL 回归 + 并发保护 + 页栈溢出防护
CRITICAL: - 咨询详情页 loadData 引用已删除的 pollingRef → 移除残余引用 HIGH: - 401 重试递归改循环结构,避免并发限制器双 slot 占用 - 医生端 4 个列表页添加 loadingRef 防重入(consultation/alerts/dialysis/prescription) - 新增 safeNavigateTo 页栈溢出保护(栈≥9 自动 redirectTo) 前期修复一并提交: - 全局并发限制 MAX_CONCURRENT=8 - doRefresh 失败时完整清理 Storage + 重置缓存状态 - 401 跳转登录页修正 - 长轮询 generation counter 模式 - 首页/健康页 loadingRef + refreshToday 去重
This commit is contained in:
@@ -32,7 +32,7 @@ export default function ConsultationDetail() {
|
||||
const [sending, setSending] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const scrollViewRef = useRef('');
|
||||
const pollingRef = useRef(false);
|
||||
const pollingGeneration = useRef(0);
|
||||
const mountedRef = useRef(true);
|
||||
const messagesRef = useRef<ConsultationMessage[]>([]);
|
||||
const modeClass = useElderClass();
|
||||
@@ -44,40 +44,40 @@ export default function ConsultationDetail() {
|
||||
startLongPolling();
|
||||
}
|
||||
return () => {
|
||||
pollingRef.current = false;
|
||||
pollingGeneration.current += 1;
|
||||
mountedRef.current = false;
|
||||
};
|
||||
}, [sessionId]);
|
||||
|
||||
// 页面可见时恢复轮询,不可见时暂停(防止 DevTools 后台页面累积轮询)
|
||||
useDidShow(() => {
|
||||
if (sessionId && !pollingRef.current && session?.status !== 'closed') {
|
||||
if (sessionId && session?.status !== 'closed') {
|
||||
startLongPolling();
|
||||
}
|
||||
});
|
||||
useDidHide(() => {
|
||||
pollingRef.current = false;
|
||||
pollingGeneration.current += 1;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (session?.status === 'closed') {
|
||||
pollingRef.current = false;
|
||||
pollingGeneration.current += 1;
|
||||
}
|
||||
}, [session?.status]);
|
||||
|
||||
const startLongPolling = () => {
|
||||
pollingRef.current = true;
|
||||
longPoll();
|
||||
const gen = ++pollingGeneration.current;
|
||||
longPoll(gen);
|
||||
};
|
||||
|
||||
const longPoll = async (failCount = 0) => {
|
||||
if (!pollingRef.current || !mountedRef.current) return;
|
||||
const longPoll = async (gen: number, failCount = 0) => {
|
||||
if (gen !== pollingGeneration.current || !mountedRef.current) return;
|
||||
if (failCount >= MAX_CONSECUTIVE_FAILURES) return;
|
||||
try {
|
||||
const currentMessages = messagesRef.current;
|
||||
const lastId = currentMessages.length > 0 ? currentMessages[currentMessages.length - 1].id : undefined;
|
||||
const newMsgs = await pollMessages(sessionId, lastId);
|
||||
if (!mountedRef.current) return;
|
||||
if (gen !== pollingGeneration.current || !mountedRef.current) return;
|
||||
if (newMsgs && newMsgs.length > 0) {
|
||||
setMessages((prev) => {
|
||||
const existing = new Set(prev.map((msg) => msg.id));
|
||||
@@ -92,11 +92,11 @@ export default function ConsultationDetail() {
|
||||
} catch {
|
||||
failCount++;
|
||||
}
|
||||
if (!pollingRef.current || !mountedRef.current) return;
|
||||
if (gen !== pollingGeneration.current || !mountedRef.current) return;
|
||||
const delay = failCount > 0 ? Math.min(failCount * 2000, 30000) : POLL_INTERVAL_MS;
|
||||
await new Promise((r) => setTimeout(r, delay));
|
||||
if (pollingRef.current && mountedRef.current) {
|
||||
longPoll(failCount);
|
||||
if (gen === pollingGeneration.current && mountedRef.current) {
|
||||
longPoll(gen, failCount);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -112,7 +112,6 @@ export default function ConsultationDetail() {
|
||||
setMessages(msgs);
|
||||
messagesRef.current = msgs;
|
||||
scrollViewRef.current = `msg-${msgs.length}`;
|
||||
if (s.status === 'closed') pollingRef.current = false;
|
||||
} catch {
|
||||
Taro.showToast({ title: '加载失败', icon: 'none' });
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user