Files
hms/apps/miniprogram/src/pages/events/index.tsx
iven 7b5138a630
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
feat(miniprogram): 关怀模式全覆盖 — 58/58 页面 100% 接入
- 医生端 18 个页面全部接入(首页/待办/告警/咨询/透析/随访/
  患者/处方/报告及其详情和创建页)
- 商城子包 3 页面(商品详情/积分兑换/订单)
- 患者端剩余页面(AI报告/文章/活动/设备同步/登录/随访详情/
  报告详情/知情同意/诊断/透析处方/透析记录/家庭成员添加)
- 页面覆盖率:22/59 (37%) → 58/58 (100%)
- useElderClass hook 统一接入模式,零样板代码

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 22:34:44 +08:00

118 lines
4.4 KiB
TypeScript

import { useState, useEffect } from 'react';
import { View, Text, ScrollView } from '@tarojs/components';
import Taro from '@tarojs/taro';
import * as pointsApi from '@/services/points';
import Loading from '@/components/Loading';
import EmptyState from '@/components/EmptyState';
import { useElderClass } from '../../hooks/useElderClass';
import './index.scss';
const STATUS_MAP: Record<string, { label: string; className: string }> = {
published: { label: '报名中', className: 'event-card__status--published' },
ongoing: { label: '进行中', className: 'event-card__status--ongoing' },
completed: { label: '已结束', className: 'event-card__status--completed' },
cancelled: { label: '已取消', className: 'event-card__status--cancelled' },
};
export default function EventsPage() {
const modeClass = useElderClass();
const [events, setEvents] = useState<pointsApi.OfflineEvent[]>([]);
const [loading, setLoading] = useState(true);
const [registering, setRegistering] = useState<string | null>(null);
useEffect(() => {
loadEvents();
}, []);
const loadEvents = async () => {
setLoading(true);
try {
const res = await pointsApi.listOfflineEvents({ page: 1, page_size: 50, status: 'published' });
setEvents(res.data || []);
} catch {
Taro.showToast({ title: '加载失败', icon: 'none' });
} finally {
setLoading(false);
}
};
const handleRegister = async (event: pointsApi.OfflineEvent) => {
setRegistering(event.id);
try {
await pointsApi.registerEvent(event.id);
Taro.showToast({ title: '报名成功', icon: 'success' });
loadEvents();
} catch (err: any) {
const msg = err?.message || '报名失败';
Taro.showToast({ title: msg.substring(0, 20), icon: 'none' });
} finally {
setRegistering(null);
}
};
const formatDate = (d: string) => {
return new Date(d).toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
};
if (loading) return <Loading />;
return (
<ScrollView scrollY className={`events-page ${modeClass}`}>
<View className='events-header'>
<Text className='events-header__title'>线</Text>
<Text className='events-header__subtitle'></Text>
</View>
{events.length === 0 ? (
<EmptyState text='暂无可报名的活动' />
) : (
<View className='event-list'>
{events.map((event) => {
const st = STATUS_MAP[event.status] || { label: event.status, className: '' };
const isFull = event.max_participants != null && event.current_participants >= event.max_participants;
const isRegistering = registering === event.id;
return (
<View key={event.id} className='event-card'>
<View className='event-card__header'>
<View className={`event-card__status ${st.className}`}>
<Text>{st.label}</Text>
</View>
<Text className='event-card__points'>+{event.points_reward} </Text>
</View>
<Text className='event-card__title'>{event.title}</Text>
{event.description && (
<Text className='event-card__desc'>{event.description}</Text>
)}
<View className='event-card__info'>
<Text className='event-card__date'>{formatDate(event.event_date)}</Text>
{event.location && (
<Text className='event-card__location'>{event.location}</Text>
)}
</View>
<View className='event-card__footer'>
<Text className='event-card__participants'>
{event.current_participants}{event.max_participants ? `/${event.max_participants}` : ''}
</Text>
<View
className={`event-card__btn ${isFull ? 'event-card__btn--disabled' : ''}`}
onClick={() => !isFull && !isRegistering && handleRegister(event)}
>
<Text className='event-card__btn-text'>
{isRegistering ? '报名中...' : isFull ? '已满' : '立即报名'}
</Text>
</View>
</View>
</View>
);
})}
</View>
)}
</ScrollView>
);
}