Files
training/website/src/user/Events.jsx
T
Pine 97054a9699 feat: 培训业务 Web(Vite+React)
- user 端:课程/活动/报名/测评/政策/调研
- 后端已并入 server-core/app/training,/api/* 走 opc.pinesound.cn
2026-08-23 22:32:42 +08:00

81 lines
3.8 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React from 'react';
import { ContentTemplate } from '@/components/templates';
import { SectionHead, Button, Icon } from '@/components/atoms';
import { Reveal } from '@/components/organisms';
import Skeleton from '@/components/Skeleton';
import { getBookableEvents } from '@/services/booking';
import '@/styles/booking.css';
const fmtEvt = (iso) => {
if (!iso) return '';
const d = new Date(iso);
const p = (n) => String(n).padStart(2, '0');
const week = ['日', '一', '二', '三', '四', '五', '六'][d.getDay()];
return `${d.getMonth() + 1}${d.getDate()}日 周${week} ${p(d.getHours())}:${p(d.getMinutes())}`;
};
export default function Events() {
const [list, setList] = React.useState([]);
const [loaded, setLoaded] = React.useState(false);
React.useEffect(() => { getBookableEvents().then((l) => { setList(l); setLoaded(true); }); }, []);
const SkeletonCard = () => (
<div className="bk-ev-card">
<Skeleton.Box h={190} />
<div className="bk-ev-body" style={{ padding: '14px 16px' }}>
<Skeleton.Line w="70%" h={18} />
<Skeleton.Line w="100%" h={12} style={{ marginTop: 10 }} />
<Skeleton.Line w="45%" h={12} style={{ marginTop: 8 }} />
</div>
</div>
);
return (
<ContentTemplate
active="events"
kicker="Events"
title="活动 · 公益课与沙龙"
desc="线下与线上活动按主题排期,公益课每周 1 场、交流沙龙每月 2–4 场。选择一期主题,进入详情报名。"
>
<div className="ev-grid">
{list.map((e) => {
const STATUS_TEXT = { open: '报名中', full: '已满员', done: '已结束', pending: '待开放', invite: '邀请中' };
const ST_CLS = { full: ' full', done: ' done', pending: ' pending', invite: ' invite' };
const status = STATUS_TEXT[e.status] || '报名中';
const st = ST_CLS[e.status] || '';
return (
<Reveal as="div" className="bk-ev-card" key={e.id}>
<div className="bk-ev-poster">
{e.image ? <img className="bk-ev-img" src={e.image} alt={e.title} /> : <div className="bk-ev-img ph">{e.title}</div>}
<div className="bk-ev-tags">
<span className={`bk-ev-tag${e.type === 'salon' ? ' alt' : ''}`}>{e.type === 'salon' ? '沙龙' : '公益课'}</span>
<span className="bk-ev-tag mode">{e.mode === 'online' ? '线上' : '线下'}</span>
</div>
</div>
<div className="bk-ev-body">
<div className="bk-ev-title">{e.title}</div>
<div className="bk-ev-sub">{e.subtitle || e.desc}</div>
<div className="bk-ev-meta">
<div className="bk-ev-line"><Icon name="clock" size="sm" /> <span>{fmtEvt(e.startAt)}</span></div>
<div className="bk-ev-line"><Icon name="map-pin" size="sm" /> <span>{e.location}</span></div>
{e.showCapacity && e.capacity > 0 && (
<div className="bk-ev-line"><Icon name="users" size="sm" /> <span>名额 {e.enrolled || 0} / {e.capacity}</span></div>
)}
</div>
<div className="bk-ev-foot">
<span className="bk-ev-price">免费</span>
<span className={`bk-ev-status${st}`}>{status}</span>
</div>
</div>
<div className="bk-ev-cta">
<Button variant="cta" href={`#/event-detail?id=${e.id}`}>查看详情 / 报名 <Icon name="arrow-right" size="sm" /></Button>
</div>
</Reveal>
);
})}
{!loaded && Array.from({ length: 3 }).map((_, i) => <SkeletonCard key={i} />)}
{loaded && list.length === 0 && <div className="bk-empty">暂无可报名活动请稍后再来</div>}
</div>
</ContentTemplate>
);
}