fix(mp): Phase 1 核心体验修复 — 咨询描述+体征校验+商城+医生端+跳转
- consultation: 添加 description 字段 + 症状描述输入 + 建议填写提醒 - health/index: 使用 validateNum 添加体征范围校验(血压/心率/血糖/体重) - mall: 隐藏未实现的积分任务空壳入口 - pkg-doctor-core: 工作台加载失败添加重试按钮和错误状态 - index: 医护人员跳转返回 null 替代 Loading 避免无用渲染
This commit is contained in:
@@ -34,6 +34,17 @@
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
&__textarea {
|
||||
width: 100%;
|
||||
min-height: 120px;
|
||||
padding: var(--tk-gap-md);
|
||||
font-size: var(--tk-font-body);
|
||||
background: $card;
|
||||
border: 1px solid $bd;
|
||||
border-radius: $r-sm;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
&__hint {
|
||||
margin: var(--tk-gap-xl) 0;
|
||||
padding: var(--tk-gap-md);
|
||||
|
||||
@@ -27,6 +27,7 @@ export default function ConsultationCreate() {
|
||||
const [typeIdx, setTypeIdx] = useState(0);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [doctorsLoaded, setDoctorsLoaded] = useState(false);
|
||||
const [description, setDescription] = useState('');
|
||||
const modeClass = useElderClass();
|
||||
|
||||
const loadDoctors = async () => {
|
||||
@@ -48,12 +49,24 @@ export default function ConsultationCreate() {
|
||||
return;
|
||||
}
|
||||
if (submitting) return;
|
||||
|
||||
if (!description.trim()) {
|
||||
const { confirm } = await Taro.showModal({
|
||||
title: '提示',
|
||||
content: '建议描述症状以便医生更快响应,是否继续?',
|
||||
confirmText: '继续提交',
|
||||
cancelText: '去填写',
|
||||
});
|
||||
if (!confirm) return;
|
||||
}
|
||||
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const session = await createSession({
|
||||
patient_id: currentPatient.id,
|
||||
doctor_id: selectedDoctorIdx >= 0 ? doctorList[selectedDoctorIdx]?.id : undefined,
|
||||
consultation_type: CONSULTATION_TYPES[typeIdx],
|
||||
description: description.trim() || undefined,
|
||||
});
|
||||
Taro.showToast({ title: '创建成功', icon: 'success' });
|
||||
setTimeout(() => {
|
||||
@@ -109,6 +122,18 @@ export default function ConsultationCreate() {
|
||||
</Picker>
|
||||
</View>
|
||||
|
||||
<View className='consult-create__section'>
|
||||
<Text className='consult-create__label'>症状描述(建议填写)</Text>
|
||||
<Input
|
||||
className='consult-create__textarea'
|
||||
type='text'
|
||||
placeholder='请描述您的症状或问题'
|
||||
value={description}
|
||||
onInput={(e) => setDescription(e.detail.value)}
|
||||
maxlength={500}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View className='consult-create__hint'>
|
||||
<Text className='consult-create__hint-text'>
|
||||
咨询创建后,医生将尽快回复您的消息。
|
||||
|
||||
@@ -5,6 +5,7 @@ import { safeNavigateTo } from '@/utils/navigate';
|
||||
import { useAuthStore } from '../../stores/auth';
|
||||
import { useElderClass } from '../../hooks/useElderClass';
|
||||
import { findThreshold, inputVitalSign, type HealthThreshold } from '../../services/health';
|
||||
import { validateNum } from '../../utils/validate';
|
||||
import Loading from '../../components/Loading';
|
||||
import ErrorState from '../../components/ErrorState';
|
||||
import GuestGuard from '../../components/GuestGuard';
|
||||
@@ -113,6 +114,10 @@ export default function Health() {
|
||||
const sys = parseFloat(systolic);
|
||||
const dia = parseFloat(diastolic);
|
||||
if (!sys || !dia) { Taro.showToast({ title: '请填写完整', icon: 'none' }); return; }
|
||||
const sysErr = validateNum(sys, '收缩压', { min: 60, max: 250 });
|
||||
if (sysErr) { Taro.showToast({ title: sysErr, icon: 'none' }); return; }
|
||||
const diaErr = validateNum(dia, '舒张压', { min: 40, max: 150 });
|
||||
if (diaErr) { Taro.showToast({ title: diaErr, icon: 'none' }); return; }
|
||||
await inputVitalSign(patientId, {
|
||||
indicator_type: 'blood_pressure',
|
||||
value: sys,
|
||||
@@ -125,6 +130,8 @@ export default function Health() {
|
||||
case 'heart_rate': {
|
||||
const val = parseFloat(heartRateVal);
|
||||
if (!val) { Taro.showToast({ title: '请填写心率', icon: 'none' }); return; }
|
||||
const err = validateNum(val, '心率', { min: 30, max: 220 });
|
||||
if (err) { Taro.showToast({ title: err, icon: 'none' }); return; }
|
||||
await inputVitalSign(patientId, { indicator_type: 'heart_rate', value: val });
|
||||
setHeartRateVal('');
|
||||
break;
|
||||
@@ -132,6 +139,8 @@ export default function Health() {
|
||||
case 'blood_sugar': {
|
||||
const val = parseFloat(sugarVal);
|
||||
if (!val) { Taro.showToast({ title: '请填写血糖值', icon: 'none' }); return; }
|
||||
const err = validateNum(val, '血糖', { min: 1.0, max: 33.3 });
|
||||
if (err) { Taro.showToast({ title: err, icon: 'none' }); return; }
|
||||
const bsType = sugarPeriod === 'fasting' ? 'blood_sugar_fasting' : 'blood_sugar_postprandial';
|
||||
await inputVitalSign(patientId, { indicator_type: bsType, value: val });
|
||||
setSugarVal('');
|
||||
@@ -140,6 +149,8 @@ export default function Health() {
|
||||
case 'weight': {
|
||||
const val = parseFloat(weightVal);
|
||||
if (!val) { Taro.showToast({ title: '请填写体重', icon: 'none' }); return; }
|
||||
const err = validateNum(val, '体重', { min: 20, max: 300 });
|
||||
if (err) { Taro.showToast({ title: err, icon: 'none' }); return; }
|
||||
await inputVitalSign(patientId, { indicator_type: 'weight', value: val });
|
||||
setWeightVal('');
|
||||
break;
|
||||
|
||||
@@ -316,7 +316,7 @@ export default function Index() {
|
||||
|
||||
// 医护人员访问患者首页时,自动跳转到医生端
|
||||
// 不渲染 HomeDashboard,避免触发患者首页的 API 请求(并发叠加问题)
|
||||
const shouldRedirect = user && isMedicalStaff();
|
||||
const shouldRedirect = !!(user && isMedicalStaff());
|
||||
|
||||
useDidShow(() => {
|
||||
if (shouldRedirect) {
|
||||
@@ -329,11 +329,10 @@ export default function Index() {
|
||||
}
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return <GuestHome modeClass={modeClass} />;
|
||||
}
|
||||
if (shouldRedirect) {
|
||||
return <Loading />;
|
||||
}
|
||||
// 未登录 → 访客首页
|
||||
if (!user) return <GuestHome modeClass={modeClass} />;
|
||||
// 医护人员 → 等待跳转(返回 null 避免无用渲染)
|
||||
if (shouldRedirect) return null;
|
||||
// 患者用户 → 正常首页
|
||||
return <HomeDashboard modeClass={modeClass} />;
|
||||
}
|
||||
|
||||
@@ -184,12 +184,7 @@ export default function Mall() {
|
||||
</View>
|
||||
<Text className='mall-action-label'>签到打卡</Text>
|
||||
</View>
|
||||
<View className='mall-action'>
|
||||
<View className='mall-action-icon mall-action-icon--task'>
|
||||
<Text className='mall-action-icon-text'>★</Text>
|
||||
</View>
|
||||
<Text className='mall-action-label'>积分任务</Text>
|
||||
</View>
|
||||
{/* TODO: 积分任务功能待实现后恢复 */}
|
||||
<View className='mall-action' onClick={() => safeNavigateTo('/pages/pkg-mall/orders/index')}>
|
||||
<View className='mall-action-icon mall-action-icon--history'>
|
||||
<Text className='mall-action-icon-text'>◷</Text>
|
||||
|
||||
@@ -83,4 +83,30 @@
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
// ── 错误状态 ──
|
||||
&__error {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 120px 32px;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
&__error-text {
|
||||
font-size: var(--tk-body-lg);
|
||||
color: var(--tk-text-secondary);
|
||||
}
|
||||
|
||||
&__error-retry {
|
||||
padding: 16px 48px;
|
||||
background: var(--tk-primary);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
&__error-retry-text {
|
||||
font-size: var(--tk-body);
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ export default function DoctorHome() {
|
||||
const modeClass = useDoctorClass();
|
||||
const [dashboard, setDashboard] = useState<DoctorDashboard | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [loadError, setLoadError] = useState(false);
|
||||
|
||||
const hasRole = (allowed: string[] | undefined) => {
|
||||
if (!allowed) return true;
|
||||
@@ -57,8 +58,10 @@ export default function DoctorHome() {
|
||||
try {
|
||||
const data = await getDashboard();
|
||||
setDashboard(data);
|
||||
setLoadError(false);
|
||||
} catch (err) {
|
||||
console.warn('[doctor] 加载工作台数据失败:', err);
|
||||
setLoadError(true);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -78,6 +81,19 @@ export default function DoctorHome() {
|
||||
|
||||
if (loading) return <Loading />;
|
||||
|
||||
if (loadError && !dashboard) {
|
||||
return (
|
||||
<View className={`doctor-home ${modeClass}`}>
|
||||
<View className='doctor-home__error'>
|
||||
<Text className='doctor-home__error-text'>加载失败</Text>
|
||||
<View className='doctor-home__error-retry' onClick={() => { setLoading(true); loadDashboard(); }}>
|
||||
<Text className='doctor-home__error-retry-text'>点击重试</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View className={`doctor-home ${modeClass}`}>
|
||||
<ScrollView scrollY className="doctor-home__scroll">
|
||||
|
||||
@@ -79,6 +79,7 @@ export async function createSession(params: {
|
||||
patient_id: string;
|
||||
doctor_id?: string;
|
||||
consultation_type?: string;
|
||||
description?: string;
|
||||
}) {
|
||||
return api.post<ConsultationSession>('/health/consultation-sessions', params);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user