feat(miniprogram): AI 建议卡片 — 健康页顶部显示待审批建议摘要
- 新增 listPendingSuggestions API - 健康页加载待审批 AI 建议(最多 3 条) - 风险等级圆点 + 建议摘要文字 - 点击卡片可跳转
This commit is contained in:
@@ -300,3 +300,50 @@
|
|||||||
color: $tx;
|
color: $tx;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─── AI 建议卡片 ─── */
|
||||||
|
.ai-suggestion-card {
|
||||||
|
background: $card;
|
||||||
|
border-radius: $r;
|
||||||
|
padding: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
box-shadow: $shadow-sm;
|
||||||
|
border-left: 4px solid $pri;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-card-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-card-count {
|
||||||
|
font-size: 12px;
|
||||||
|
color: $acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-suggestion-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-risk-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-suggestion-text {
|
||||||
|
font-size: 13px;
|
||||||
|
color: $tx2;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { useState } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { View, Text, Input } from '@tarojs/components';
|
import { View, Text, Input } from '@tarojs/components';
|
||||||
import Taro, { useDidShow } from '@tarojs/taro';
|
import Taro, { useDidShow } from '@tarojs/taro';
|
||||||
import { useHealthStore } from '../../stores/health';
|
import { useHealthStore } from '../../stores/health';
|
||||||
import { useAuthStore } from '../../stores/auth';
|
import { useAuthStore } from '../../stores/auth';
|
||||||
import { inputVitalSign, getTrend } from '../../services/health';
|
import { inputVitalSign, getTrend } from '../../services/health';
|
||||||
|
import { listPendingSuggestions, type AiSuggestionItem } from '../../services/ai-analysis';
|
||||||
import Loading from '../../components/Loading';
|
import Loading from '../../components/Loading';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
@@ -41,12 +42,23 @@ export default function Health() {
|
|||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [trendData, setTrendData] = useState<TrendPoint[]>([]);
|
const [trendData, setTrendData] = useState<TrendPoint[]>([]);
|
||||||
const [trendLoading, setTrendLoading] = useState(false);
|
const [trendLoading, setTrendLoading] = useState(false);
|
||||||
|
const [aiSuggestions, setAiSuggestions] = useState<AiSuggestionItem[]>([]);
|
||||||
|
|
||||||
useDidShow(() => {
|
useDidShow(() => {
|
||||||
refreshToday();
|
refreshToday();
|
||||||
loadTrend(activeTab);
|
loadTrend(activeTab);
|
||||||
|
loadAiSuggestions();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const loadAiSuggestions = async () => {
|
||||||
|
try {
|
||||||
|
const items = await listPendingSuggestions();
|
||||||
|
setAiSuggestions(items.slice(0, 3));
|
||||||
|
} catch {
|
||||||
|
// 静默
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const loadTrend = async (type: VitalType) => {
|
const loadTrend = async (type: VitalType) => {
|
||||||
setTrendLoading(true);
|
setTrendLoading(true);
|
||||||
try {
|
try {
|
||||||
@@ -162,6 +174,28 @@ export default function Health() {
|
|||||||
<Text className='health-title'>健康数据</Text>
|
<Text className='health-title'>健康数据</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{/* AI 建议卡片 */}
|
||||||
|
{aiSuggestions.length > 0 && (
|
||||||
|
<View className='ai-suggestion-card' onClick={() => Taro.navigateTo({ url: '/pages/pkg-profile/settings/index' })}>
|
||||||
|
<View className='ai-card-header'>
|
||||||
|
<Text className='ai-card-title'>AI 健康建议</Text>
|
||||||
|
<Text className='ai-card-count'>{aiSuggestions.length} 条待查看</Text>
|
||||||
|
</View>
|
||||||
|
{aiSuggestions.map((s) => {
|
||||||
|
const riskColor = s.risk_level === 'high' ? '#ef4444' : s.risk_level === 'medium' ? '#f59e0b' : '#22c55e';
|
||||||
|
const typeLabel = s.suggestion_type === 'followup' ? '随访' : s.suggestion_type === 'appointment' ? '预约' : '预警';
|
||||||
|
const params = s.params as Record<string, unknown> | null;
|
||||||
|
const reason = (params?.reason as string) || (params?.message as string) || typeLabel;
|
||||||
|
return (
|
||||||
|
<View key={s.id} className='ai-suggestion-item'>
|
||||||
|
<View className='ai-risk-dot' style={{ background: riskColor }} />
|
||||||
|
<Text className='ai-suggestion-text'>{reason.slice(0, 40)}</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 类型 Tab */}
|
{/* 类型 Tab */}
|
||||||
<View className='vital-tabs'>
|
<View className='vital-tabs'>
|
||||||
{VITAL_TABS.map((tab) => {
|
{VITAL_TABS.map((tab) => {
|
||||||
|
|||||||
@@ -22,3 +22,21 @@ export async function listAiAnalysis(page = 1, pageSize = 20) {
|
|||||||
export async function getAiAnalysisDetail(id: string) {
|
export async function getAiAnalysisDetail(id: string) {
|
||||||
return api.get<AiAnalysisItem>(`/ai/analysis/${id}`);
|
return api.get<AiAnalysisItem>(`/ai/analysis/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AiSuggestionItem {
|
||||||
|
id: string;
|
||||||
|
analysis_id: string;
|
||||||
|
suggestion_type: string;
|
||||||
|
risk_level: string;
|
||||||
|
params: Record<string, unknown> | null;
|
||||||
|
status: string;
|
||||||
|
created_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listPendingSuggestions() {
|
||||||
|
const resp = await api.get<{ data: AiSuggestionItem[]; total: number }>(
|
||||||
|
'/ai/suggestions',
|
||||||
|
{ status: 'pending' },
|
||||||
|
);
|
||||||
|
return resp.data || [];
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user