feat(health+miniprogram): 预约/报告/随访/资讯/家庭管理 — Chunk 4-6
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

后端:
- 添加 articles 表迁移 + Entity + Service + Handler
- 健康数据趋势 API (get_mini_trend) 注册路由
- article CRUD (list/get) + DTO

前端 (11个新页面 + 5个服务):
- 预约挂号: 列表/创建向导/详情页
- 报告管理: 列表/详情页
- 随访管理: 任务列表/记录详情页
- 资讯文章: 文章详情页
- 个人中心: 就诊人管理/新增/我的报告/我的随访/用药提醒/设置
- 更新 app.config.ts 注册全部路由
- 更新 profile/article 页面为真实功能
This commit is contained in:
iven
2026-04-24 00:58:40 +08:00
parent ee9a5c4da1
commit 9ef65b9a9f
53 changed files with 6044 additions and 32 deletions

View File

@@ -0,0 +1,118 @@
@import '../../../styles/variables.scss';
.detail-page {
min-height: 100vh;
background: $bg;
padding: 24px;
padding-bottom: 40px;
}
.detail-card {
background: $card;
border-radius: $r;
padding: 28px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
}
.detail-title {
font-size: 34px;
font-weight: bold;
color: $tx;
display: block;
margin-bottom: 20px;
}
.detail-row {
display: flex;
justify-content: space-between;
padding: 12px 0;
border-bottom: 1px solid $bd-l;
&:last-child {
border-bottom: none;
}
}
.detail-label {
font-size: 26px;
color: $tx2;
}
.detail-value {
font-size: 26px;
color: $tx;
}
.detail-desc {
margin-top: 16px;
padding: 20px;
background: $bd-l;
border-radius: $r-sm;
}
.detail-desc-text {
font-size: 26px;
color: $tx;
line-height: 1.6;
}
.submit-card {
background: $card;
border-radius: $r;
padding: 28px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
}
.section-title {
font-size: 30px;
font-weight: bold;
color: $tx;
display: block;
margin-bottom: 16px;
}
.submit-textarea {
width: 100%;
min-height: 200px;
font-size: 28px;
color: $tx;
background: $bd-l;
border-radius: $r-sm;
padding: 20px;
box-sizing: border-box;
border: none;
outline: none;
margin-bottom: 20px;
}
.submit-btn {
background: $pri;
border-radius: $r-sm;
padding: 24px;
text-align: center;
&.disabled {
opacity: 0.6;
}
}
.submit-btn-text {
font-size: 30px;
color: white;
font-weight: bold;
}
.loading-state,
.empty-state {
display: flex;
justify-content: center;
align-items: center;
padding: 120px 0;
}
.loading-text,
.empty-text {
font-size: 28px;
color: $tx3;
}

View File

@@ -0,0 +1,119 @@
import React, { useState, useEffect } from 'react';
import { View, Text } from '@tarojs/components';
import Taro, { useRouter } from '@tarojs/taro';
import { listTasks, submitRecord, FollowUpTask } from '../../../services/followup';
import './index.scss';
export default function FollowUpDetail() {
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);
useEffect(() => {
if (!id) return;
setLoading(true);
listTasks()
.then((res) => {
const found = (res.data || []).find((t) => t.id === id);
setTask(found || null);
})
.catch(() => Taro.showToast({ title: '加载失败', icon: 'none' }))
.finally(() => setLoading(false));
}, [id]);
const handleSubmit = async () => {
if (!content.trim()) {
Taro.showToast({ title: '请输入内容', icon: 'none' });
return;
}
setSubmitting(true);
try {
await submitRecord({
task_id: id,
content: { text: content.trim() },
});
Taro.showToast({ title: '提交成功', icon: 'success' });
setContent('');
} catch {
Taro.showToast({ title: '提交失败', icon: 'none' });
} finally {
setSubmitting(false);
}
};
const getStatusLabel = (status: string) => {
if (status === 'completed') return '已完成';
if (status === 'overdue') return '已过期';
return '待完成';
};
if (loading) {
return (
<View className='detail-page'>
<View className='loading-state'>
<Text className='loading-text'>...</Text>
</View>
</View>
);
}
if (!task) {
return (
<View className='detail-page'>
<View className='empty-state'>
<Text className='empty-text'></Text>
</View>
</View>
);
}
const isCompleted = task.status === 'completed';
return (
<View className='detail-page'>
{/* 任务详情 */}
<View className='detail-card'>
<Text className='detail-title'>{task.task_type}</Text>
<View className='detail-row'>
<Text className='detail-label'></Text>
<Text className='detail-value'>{getStatusLabel(task.status)}</Text>
</View>
<View className='detail-row'>
<Text className='detail-label'></Text>
<Text className='detail-value'>{task.due_date}</Text>
</View>
{task.description && (
<View className='detail-desc'>
<Text className='detail-desc-text'>{task.description}</Text>
</View>
)}
</View>
{/* 提交表单 */}
{!isCompleted && (
<View className='submit-card'>
<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>
</View>
)}
</View>
);
}