feat(miniprogram): 用药提醒从 localStorage 迁移到服务端 API
- 新增 medication-reminder.ts service(list/create/update/delete) - 重写 medication/index.tsx 页面,通过后端 API 持久化数据 - 支持乐观锁(version)、患者 ID 关联、提醒时间数组 - 移除旧的 localStorage 读写逻辑
This commit is contained in:
@@ -1,111 +1,136 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import { View, Text, Input, Picker } from '@tarojs/components';
|
import { View, Text, Input, Picker } from '@tarojs/components';
|
||||||
import Taro from '@tarojs/taro';
|
import Taro from '@tarojs/taro';
|
||||||
import EmptyState from '../../../components/EmptyState';
|
import EmptyState from '../../../components/EmptyState';
|
||||||
|
import {
|
||||||
|
listReminders,
|
||||||
|
createReminder,
|
||||||
|
updateReminder,
|
||||||
|
deleteReminder,
|
||||||
|
type MedicationReminder,
|
||||||
|
} from '../../../services/medication-reminder';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
interface MedicationReminder {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
dosage: string;
|
|
||||||
time: string;
|
|
||||||
enabled: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const STORAGE_KEY = 'medication_reminders';
|
|
||||||
|
|
||||||
function loadReminders(): MedicationReminder[] {
|
|
||||||
return Taro.getStorageSync(STORAGE_KEY) || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveReminders(reminders: MedicationReminder[]) {
|
|
||||||
Taro.setStorageSync(STORAGE_KEY, reminders);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function MedicationReminder() {
|
export default function MedicationReminder() {
|
||||||
const [reminders, setReminders] = useState<MedicationReminder[]>([]);
|
const [reminders, setReminders] = useState<MedicationReminder[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
const [showForm, setShowForm] = useState(false);
|
const [showForm, setShowForm] = useState(false);
|
||||||
const [formName, setFormName] = useState('');
|
const [formName, setFormName] = useState('');
|
||||||
const [formDosage, setFormDosage] = useState('');
|
const [formDosage, setFormDosage] = useState('');
|
||||||
const [formTime, setFormTime] = useState('08:00');
|
const [formTime, setFormTime] = useState('08:00');
|
||||||
|
|
||||||
useEffect(() => {
|
const fetchReminders = useCallback(async () => {
|
||||||
setReminders(loadReminders());
|
try {
|
||||||
|
const res = await listReminders();
|
||||||
|
setReminders(res.data ?? []);
|
||||||
|
} catch {
|
||||||
|
Taro.showToast({ title: '加载失败', icon: 'none' });
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const updateReminders = (updated: MedicationReminder[]) => {
|
useEffect(() => { fetchReminders(); }, [fetchReminders]);
|
||||||
setReminders(updated);
|
|
||||||
saveReminders(updated);
|
const handleToggle = async (r: MedicationReminder) => {
|
||||||
|
try {
|
||||||
|
await updateReminder(r.id, {
|
||||||
|
is_active: !r.is_active,
|
||||||
|
version: r.version,
|
||||||
|
});
|
||||||
|
fetchReminders();
|
||||||
|
} catch {
|
||||||
|
Taro.showToast({ title: '操作失败', icon: 'none' });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleToggle = (id: string) => {
|
const handleDelete = (r: MedicationReminder) => {
|
||||||
const updated = reminders.map((r) =>
|
|
||||||
r.id === id ? { ...r, enabled: !r.enabled } : r
|
|
||||||
);
|
|
||||||
updateReminders(updated);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDelete = (id: string) => {
|
|
||||||
Taro.showModal({
|
Taro.showModal({
|
||||||
title: '确认删除',
|
title: '确认删除',
|
||||||
content: '确定要删除这个提醒吗?',
|
content: '确定要删除这个提醒吗?',
|
||||||
}).then((res) => {
|
}).then(async (res) => {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
updateReminders(reminders.filter((r) => r.id !== id));
|
try {
|
||||||
|
await deleteReminder(r.id, r.version);
|
||||||
|
Taro.showToast({ title: '已删除', icon: 'success' });
|
||||||
|
fetchReminders();
|
||||||
|
} catch {
|
||||||
|
Taro.showToast({ title: '删除失败', icon: 'none' });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAdd = () => {
|
const handleAdd = async () => {
|
||||||
if (!formName.trim()) {
|
if (!formName.trim()) {
|
||||||
Taro.showToast({ title: '请输入药品名称', icon: 'none' });
|
Taro.showToast({ title: '请输入药品名称', icon: 'none' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const newReminder: MedicationReminder = {
|
const patientId = Taro.getStorageSync('current_patient_id');
|
||||||
id: Date.now().toString(),
|
if (!patientId) {
|
||||||
name: formName.trim(),
|
Taro.showToast({ title: '请先绑定患者档案', icon: 'none' });
|
||||||
dosage: formDosage.trim(),
|
return;
|
||||||
time: formTime,
|
}
|
||||||
enabled: true,
|
try {
|
||||||
};
|
await createReminder({
|
||||||
updateReminders([...reminders, newReminder]);
|
patient_id: patientId,
|
||||||
setFormName('');
|
medication_name: formName.trim(),
|
||||||
setFormDosage('');
|
dosage: formDosage.trim() || undefined,
|
||||||
setFormTime('08:00');
|
reminder_times: [formTime],
|
||||||
setShowForm(false);
|
is_active: true,
|
||||||
Taro.showToast({ title: '添加成功', icon: 'success' });
|
});
|
||||||
|
setFormName('');
|
||||||
|
setFormDosage('');
|
||||||
|
setFormTime('08:00');
|
||||||
|
setShowForm(false);
|
||||||
|
Taro.showToast({ title: '添加成功', icon: 'success' });
|
||||||
|
fetchReminders();
|
||||||
|
} catch {
|
||||||
|
Taro.showToast({ title: '添加失败', icon: 'none' });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const nameInitial = (name: string) => {
|
const nameInitial = (name: string) => {
|
||||||
return name ? name.charAt(0) : '药';
|
return name ? name.charAt(0) : '药';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<View className='medication-page'>
|
||||||
|
<Text className='page-title'>用药提醒</Text>
|
||||||
|
<View style={{ padding: '40px 0', textAlign: 'center' }}>
|
||||||
|
<Text style={{ color: '#94A3B8', fontSize: '28px' }}>加载中...</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='medication-page'>
|
<View className='medication-page'>
|
||||||
<Text className='page-title'>用药提醒</Text>
|
<Text className='page-title'>用药提醒</Text>
|
||||||
|
|
||||||
<View className='reminder-list'>
|
<View className='reminder-list'>
|
||||||
{reminders.map((r) => (
|
{reminders.map((r) => (
|
||||||
<View className={`reminder-card ${!r.enabled ? 'disabled' : ''}`} key={r.id}>
|
<View className={`reminder-card ${!r.is_active ? 'disabled' : ''}`} key={r.id}>
|
||||||
<View className='reminder-avatar'>
|
<View className='reminder-avatar'>
|
||||||
<Text className='reminder-avatar-text'>{nameInitial(r.name)}</Text>
|
<Text className='reminder-avatar-text'>{nameInitial(r.medication_name)}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className='reminder-info'>
|
<View className='reminder-info'>
|
||||||
<Text className='reminder-name'>{r.name}</Text>
|
<Text className='reminder-name'>{r.medication_name}</Text>
|
||||||
<Text className='reminder-dosage'>
|
<Text className='reminder-dosage'>
|
||||||
{r.dosage} | {r.time}
|
{r.dosage || '-'} | {r.reminder_times?.join(', ') || '-'}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className='reminder-actions'>
|
<View className='reminder-actions'>
|
||||||
<View
|
<View
|
||||||
className={`toggle ${r.enabled ? 'on' : 'off'}`}
|
className={`toggle ${r.is_active ? 'on' : 'off'}`}
|
||||||
onClick={() => handleToggle(r.id)}
|
onClick={() => handleToggle(r)}
|
||||||
>
|
>
|
||||||
<View className='toggle-dot' />
|
<View className='toggle-dot' />
|
||||||
</View>
|
</View>
|
||||||
<Text
|
<Text
|
||||||
className='delete-btn'
|
className='delete-btn'
|
||||||
onClick={() => handleDelete(r.id)}
|
onClick={() => handleDelete(r)}
|
||||||
>
|
>
|
||||||
删除
|
删除
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
68
apps/miniprogram/src/services/medication-reminder.ts
Normal file
68
apps/miniprogram/src/services/medication-reminder.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import Taro from '@tarojs/taro';
|
||||||
|
import { api } from './request';
|
||||||
|
|
||||||
|
export interface MedicationReminder {
|
||||||
|
id: string;
|
||||||
|
patient_id: string;
|
||||||
|
medication_name: string;
|
||||||
|
dosage?: string;
|
||||||
|
frequency?: string;
|
||||||
|
reminder_times: string[];
|
||||||
|
start_date?: string;
|
||||||
|
end_date?: string;
|
||||||
|
is_active: boolean;
|
||||||
|
notes?: string;
|
||||||
|
version: number;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateReminderReq {
|
||||||
|
patient_id: string;
|
||||||
|
medication_name: string;
|
||||||
|
dosage?: string;
|
||||||
|
frequency?: string;
|
||||||
|
reminder_times: string[];
|
||||||
|
start_date?: string;
|
||||||
|
end_date?: string;
|
||||||
|
is_active?: boolean;
|
||||||
|
notes?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateReminderReq {
|
||||||
|
medication_name?: string;
|
||||||
|
dosage?: string;
|
||||||
|
frequency?: string;
|
||||||
|
reminder_times?: string[];
|
||||||
|
start_date?: string;
|
||||||
|
end_date?: string;
|
||||||
|
is_active?: boolean;
|
||||||
|
notes?: string;
|
||||||
|
version: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPatientId(): string {
|
||||||
|
return Taro.getStorageSync('current_patient_id') || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listReminders() {
|
||||||
|
const patientId = getPatientId();
|
||||||
|
if (!patientId) return { data: [] as MedicationReminder[], total: 0 };
|
||||||
|
return api.get<{ data: MedicationReminder[]; total: number }>(
|
||||||
|
`/health/patients/${patientId}/medication-reminders`,
|
||||||
|
{ page: 1, page_size: 100 },
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createReminder(req: CreateReminderReq) {
|
||||||
|
return api.post<MedicationReminder>('/health/medication-reminders', req);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateReminder(id: string, req: UpdateReminderReq) {
|
||||||
|
return api.put<MedicationReminder>(`/health/medication-reminders/${id}`, req);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteReminder(id: string, version: number) {
|
||||||
|
return api.delete<void>(`/health/medication-reminders/${id}`, { version });
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user