feat(health+ai): P2 咨询联动 + AI 巡检消费 — 全链路打通

业务链路打通 5/5 断点全部完成:
- 咨询→随访:医生端新增"创建随访"按钮,从咨询会话直接创建随访任务
- 咨询→AI:医生端新增"AI 分析"按钮,对咨询上下文触发 AI 分析
- 告警→咨询:小程序告警详情页新增"在线咨询"快捷入口
- AI 巡检消费:erp-ai 新增 patrol_consumer,订阅 ai.patrol.requested 事件
- 前端联动:Web ConsultationDetail + 小程序 alerts 页面联动实现

后端:2 新 API + 2 handler + 1 service + AI event consumer
前端:Web 2 API + 1 页面改造 + 小程序 2 页面改造
测试:Web consultations.test.ts 9/9 通过
This commit is contained in:
iven
2026-05-20 17:50:49 +08:00
parent 5f34e5715a
commit fa1dc764a3
15 changed files with 888 additions and 8 deletions

View File

@@ -138,3 +138,24 @@
color: $white;
font-weight: 600;
}
/* ─── 告警上下文横幅 ─── */
.consultation-alert-banner {
margin-bottom: var(--tk-gap-sm);
}
.consultation-alert-banner-inner {
display: flex;
align-items: center;
gap: var(--tk-gap-xs);
}
.consultation-alert-banner-icon {
font-size: var(--tk-font-body-lg);
}
.consultation-alert-banner-text {
font-size: var(--tk-font-body-sm);
color: var(--tk-text-secondary);
flex: 1;
}

View File

@@ -16,6 +16,24 @@ import GuestGuard from '@/components/GuestGuard';
import { useElderClass } from '../../hooks/useElderClass';
import './index.scss';
/** 读取当前页面 URL 中的查询参数 */
function getQueryParams(): Record<string, string> {
try {
const instance = Taro.getCurrentInstance();
const params = instance?.router?.params;
if (!params) return {};
const result: Record<string, string> = {};
for (const [key, value] of Object.entries(params)) {
if (typeof value === 'string') {
result[key] = value;
}
}
return result;
} catch {
return {};
}
}
function formatTime(iso: string): string {
if (!iso) return '';
const d = new Date(iso);
@@ -59,6 +77,9 @@ export default function Consultation() {
const [page, setPage] = useState(1);
const [total, setTotal] = useState(0);
// Alert context: when navigated from an alert page
const [alertContext, setAlertContext] = useState<{ alertId: string; alertTitle: string } | null>(null);
const loadSessions = useCallback(async (pageNum: number, isRefresh = false) => {
if (isRefresh) setLoading(true);
setError('');
@@ -87,6 +108,14 @@ export default function Consultation() {
usePageData(
useCallback(async () => {
Taro.setNavigationBarTitle({ title: '在线咨询' });
// Read alert context from URL query params
const params = getQueryParams();
if (params.context === 'alert' && params.alert_id) {
setAlertContext({
alertId: params.alert_id,
alertTitle: params.alert_title ? decodeURIComponent(params.alert_title) : '健康告警',
});
}
if (!user) return;
await loadSessions(1, true);
}, [user, loadSessions]),
@@ -128,6 +157,18 @@ export default function Consultation() {
{/* 副标题 */}
<Text className='consultation-subtitle'></Text>
{/* Alert context banner */}
{alertContext && (
<ContentCard className='consultation-alert-banner'>
<View className='consultation-alert-banner-inner'>
<Text className='consultation-alert-banner-icon'>&#x26A0;</Text>
<Text className='consultation-alert-banner-text'>
: {alertContext.alertTitle}
</Text>
</View>
</ContentCard>
)}
{/* 发起咨询按钮 */}
<View
className='consultation-create-btn'

View File

@@ -98,3 +98,31 @@
color: $tx;
line-height: 1.5;
}
.alert-message {
display: block;
font-size: var(--tk-font-body);
color: var(--tk-text-secondary);
line-height: 1.5;
margin-top: 8px;
}
.alert-actions {
display: flex;
justify-content: flex-end;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid $bd;
}
.alert-consult-btn {
padding: 8px 24px;
border-radius: $r-pill;
background: var(--tk-pri);
}
.alert-consult-btn-text {
font-size: var(--tk-font-body);
color: $card;
font-weight: 500;
}

View File

@@ -118,6 +118,19 @@ export default function PatientAlerts() {
</Text>
</View>
<Text className='alert-title'>{item.title}</Text>
{item.message && (
<Text className='alert-message'>{item.message}</Text>
)}
<View className='alert-actions'>
<View
className='alert-consult-btn'
onClick={() => safeNavigateTo(
`/pages/consultation/index?context=alert&alert_id=${item.id}&alert_title=${encodeURIComponent(item.title)}`,
)}
>
<Text className='alert-consult-btn-text'>线</Text>
</View>
</View>
</ContentCard>
);
})}