feat(health+miniprogram): 预约/报告/随访/资讯/家庭管理 — Chunk 4-6
后端: - 添加 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:
118
apps/miniprogram/src/pages/followup/detail/index.scss
Normal file
118
apps/miniprogram/src/pages/followup/detail/index.scss
Normal 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;
|
||||
}
|
||||
119
apps/miniprogram/src/pages/followup/detail/index.tsx
Normal file
119
apps/miniprogram/src/pages/followup/detail/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user