feat: Iteration 3 — 咨询轮询、统计概览、埋点后端
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

- consultation_service 支持 after_id 增量消息查询
- 小程序咨询详情页 8 秒轮询新消息
- 新增 DashboardStatsResp 综合统计端点 (/statistics/dashboard)
- 新增 /analytics/batch 埋点接收端点(日志记录模式)
This commit is contained in:
iven
2026-04-26 13:54:21 +08:00
parent 0cf69815d9
commit f0076aa240
10 changed files with 125 additions and 4 deletions

View File

@@ -5,6 +5,8 @@ import * as doctorApi from '@/services/doctor';
import Loading from '@/components/Loading';
import './index.scss';
const POLL_INTERVAL = 8000;
export default function ConsultationDetail() {
const router = useRouter();
const sessionId = router.params.id || '';
@@ -14,14 +16,53 @@ export default function ConsultationDetail() {
const [sending, setSending] = useState(false);
const [loading, setLoading] = useState(true);
const scrollViewRef = useRef('');
const pollTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
useEffect(() => {
if (sessionId) {
loadData();
markRead();
startPolling();
}
return () => stopPolling();
}, [sessionId]);
const startPolling = () => {
stopPolling();
pollTimerRef.current = setInterval(pollNewMessages, POLL_INTERVAL);
};
const stopPolling = () => {
if (pollTimerRef.current) {
clearInterval(pollTimerRef.current);
pollTimerRef.current = null;
}
};
const pollNewMessages = async () => {
if (!session || session.status === 'closed') {
stopPolling();
return;
}
try {
const lastId = messages.length > 0 ? messages[messages.length - 1].id : undefined;
const m = await doctorApi.listMessages(sessionId, {
page: 1,
page_size: 50,
after_id: lastId,
});
const newMsgs = m.data || [];
if (newMsgs.length > 0) {
setMessages((prev) => {
const existing = new Set(prev.map((msg) => msg.id));
const fresh = newMsgs.filter((msg) => !existing.has(msg.id));
return [...prev, ...fresh];
});
scrollViewRef.current = `msg-${messages.length + newMsgs.length}`;
}
} catch { /* 轮询失败静默忽略 */ }
};
const loadData = async () => {
setLoading(true);
try {
@@ -32,6 +73,7 @@ export default function ConsultationDetail() {
setSession(s);
setMessages(m.data || []);
scrollViewRef.current = `msg-${(m.data || []).length}`;
if (s.status === 'closed') stopPolling();
} catch {
Taro.showToast({ title: '加载失败', icon: 'none' });
} finally {

View File

@@ -122,7 +122,7 @@ export async function getSession(id: string) {
return api.get<ConsultationSession>(`/health/consultation-sessions/${id}`);
}
export async function listMessages(sessionId: string, params?: { page?: number; page_size?: number }) {
export async function listMessages(sessionId: string, params?: { page?: number; page_size?: number; after_id?: string }) {
return api.get<{ data: ConsultationMessage[]; total: number }>(
`/health/consultation-sessions/${sessionId}/messages`,
params,