- catch (err: any) → catch (err: unknown) + instanceof Error 类型缩窄(6 文件 13 处) - BLE 回调类型提取 BLEScanResult/BLEConnectionChangeResult/BLECharacteristicChangeResult/BLEServiceItem - BLEManager 7 处 any 注解替换为具体接口类型 - request.ts method as any → method as ValidMethod 字面量联合类型 - appointment ScheduleItem 接口定义替代 any[] - Taro.requestSubscribeMessage 使用类型断言替代 as any - globalThis.__hms 使用 Record<string, unknown> - TrendChart Canvas/useRef 保留 eslint-disable(微信 API 限制) - 0 TS 错误,119 测试通过
163 lines
5.3 KiB
TypeScript
163 lines
5.3 KiB
TypeScript
import { useState, useCallback } from 'react';
|
|
import { View, Text, Textarea } from '@tarojs/components';
|
|
import Taro, { useRouter } from '@tarojs/taro';
|
|
import { usePageData } from '@/hooks/usePageData';
|
|
import { getTaskDetail, submitRecord } from '@/services/followup';
|
|
import type { FollowUpTask } from '@/services/followup';
|
|
import { TEMPLATE_IDS } from '@/services/wechat-templates';
|
|
import { trackEvent } from '@/services/analytics';
|
|
import Loading from '@/components/Loading';
|
|
import ErrorState from '@/components/ErrorState';
|
|
import PageShell from '@/components/ui/PageShell';
|
|
import ContentCard from '@/components/ui/ContentCard';
|
|
import { useElderClass } from '@/hooks/useElderClass';
|
|
import './index.scss';
|
|
|
|
export default function FollowUpDetail() {
|
|
const modeClass = useElderClass();
|
|
const router = useRouter();
|
|
const id = router.params.id || '';
|
|
|
|
const [task, setTask] = useState<FollowUpTask | null>(null);
|
|
const [content, setContent] = useState('');
|
|
const [submitting, setSubmitting] = useState(false);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState(false);
|
|
|
|
const fetchTask = useCallback(async () => {
|
|
if (!id) return;
|
|
setLoading(true);
|
|
try {
|
|
const data = await getTaskDetail(id);
|
|
setTask(data);
|
|
} catch (err) {
|
|
console.error('[FollowUpDetail]', err);
|
|
setError(true);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [id]);
|
|
|
|
usePageData(fetchTask, { throttleMs: 60000 });
|
|
|
|
const handleSubmit = async () => {
|
|
if (!content.trim()) {
|
|
Taro.showToast({ title: '请输入内容', icon: 'none' });
|
|
return;
|
|
}
|
|
setSubmitting(true);
|
|
try {
|
|
await submitRecord(id, {
|
|
result: content.trim(),
|
|
patient_condition: content.trim(),
|
|
});
|
|
Taro.showToast({ title: '提交成功', icon: 'success' });
|
|
trackEvent('followup_submit', { task_id: id });
|
|
const tmplId = TEMPLATE_IDS.FOLLOWUP_REMINDER;
|
|
if (tmplId) {
|
|
try { await (Taro as { requestSubscribeMessage: (opts: { tmplIds: string[] }) => Promise<unknown> }).requestSubscribeMessage({ tmplIds: [tmplId] }); } catch { /* 用户拒绝 */ }
|
|
}
|
|
setContent('');
|
|
} catch (err) {
|
|
console.warn('[followup] 提交失败:', err);
|
|
Taro.showToast({ title: '提交失败', icon: 'none' });
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
};
|
|
|
|
const getStatusLabel = (status: string) => {
|
|
if (status === 'completed') return '已完成';
|
|
if (status === 'overdue') return '已过期';
|
|
return '待完成';
|
|
};
|
|
|
|
const getStatusClass = (status: string) => {
|
|
if (status === 'completed') return 'status-completed';
|
|
if (status === 'overdue') return 'status-overdue';
|
|
return 'status-pending';
|
|
};
|
|
|
|
const getCountdown = (dueDate: string, status: string) => {
|
|
if (status === 'completed') return null;
|
|
const now = new Date();
|
|
const due = new Date(dueDate);
|
|
const diffMs = due.getTime() - now.getTime();
|
|
const diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24));
|
|
if (diffDays < 0) return { text: `已过期 ${Math.abs(diffDays)} 天`, urgent: true };
|
|
if (diffDays === 0) return { text: '今天截止', urgent: true };
|
|
if (diffDays <= 3) return { text: `还剩 ${diffDays} 天`, urgent: true };
|
|
return { text: `还剩 ${diffDays} 天`, urgent: false };
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<PageShell className={modeClass}>
|
|
<Loading />
|
|
</PageShell>
|
|
);
|
|
}
|
|
|
|
if (error || !task) {
|
|
return (
|
|
<PageShell className={modeClass}>
|
|
<ErrorState text='任务不存在' />
|
|
</PageShell>
|
|
);
|
|
}
|
|
|
|
const isCompleted = task.status === 'completed';
|
|
|
|
return (
|
|
<PageShell className={modeClass}>
|
|
<ContentCard>
|
|
<Text className='detail-title'>{task.follow_up_type}</Text>
|
|
<View className='detail-row'>
|
|
<Text className='detail-label'>状态</Text>
|
|
<Text className={`detail-value ${getStatusClass(task.status)}`}>
|
|
{getStatusLabel(task.status)}
|
|
</Text>
|
|
</View>
|
|
<View className='detail-row'>
|
|
<Text className='detail-label'>截止日期</Text>
|
|
<Text className='detail-value'>{task.planned_date}</Text>
|
|
</View>
|
|
{(() => {
|
|
const cd = getCountdown(task.planned_date, task.status);
|
|
return cd ? (
|
|
<View className={`countdown ${cd.urgent ? 'countdown-urgent' : ''}`}>
|
|
<Text className='countdown-text'>{cd.text}</Text>
|
|
</View>
|
|
) : null;
|
|
})()}
|
|
{task.content_template && (
|
|
<View className='detail-desc'>
|
|
<Text className='detail-desc-text'>{task.content_template}</Text>
|
|
</View>
|
|
)}
|
|
</ContentCard>
|
|
|
|
{!isCompleted && (
|
|
<ContentCard>
|
|
<Text className='section-title'>填写随访记录</Text>
|
|
<Textarea
|
|
className='submit-textarea'
|
|
placeholder='请输入随访内容...'
|
|
value={content}
|
|
onInput={(e) => setContent(e.detail.value)}
|
|
maxlength={500}
|
|
/>
|
|
<View
|
|
className={`submit-btn ${submitting ? 'disabled' : ''}`}
|
|
onClick={submitting ? undefined : handleSubmit}
|
|
>
|
|
<Text className='submit-btn-text'>
|
|
{submitting ? '提交中...' : '提交'}
|
|
</Text>
|
|
</View>
|
|
</ContentCard>
|
|
)}
|
|
</PageShell>
|
|
);
|
|
}
|