feat(miniprogram): 用药提醒从 localStorage 迁移到服务端 API
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

- 新增 medication-reminder.ts service(list/create/update/delete)
- 重写 medication/index.tsx 页面,通过后端 API 持久化数据
- 支持乐观锁(version)、患者 ID 关联、提醒时间数组
- 移除旧的 localStorage 读写逻辑
This commit is contained in:
iven
2026-05-03 09:38:24 +08:00
parent 32df9c0655
commit 1a6409eb30
2 changed files with 148 additions and 55 deletions

View File

@@ -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>

View 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 });
}