feat: 增加友链申请、独立页面、投票/悬赏/抽奖帖与侧栏签到,并统一开发数据目录
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
98
frontend/src/components/PostLotteryCard.tsx
Normal file
98
frontend/src/components/PostLotteryCard.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import { useState } from 'react';
|
||||
import { Gift } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from '@/components/ui/alert-dialog';
|
||||
import { notify } from '@/lib/notify';
|
||||
import { api } from '../api/client';
|
||||
import type { PostLotteryView } from '../api/types';
|
||||
import { userPath } from '../utils/userPath';
|
||||
|
||||
interface Props {
|
||||
postId: number;
|
||||
lottery: PostLotteryView;
|
||||
isOwnerOrAdmin: boolean;
|
||||
onUpdate: (lottery: PostLotteryView) => void;
|
||||
}
|
||||
|
||||
/** 抽奖帖信息卡 */
|
||||
export default function PostLotteryCard({ postId, lottery, isOwnerOrAdmin, onUpdate }: Props) {
|
||||
const [drawOpen, setDrawOpen] = useState(false);
|
||||
const [drawing, setDrawing] = useState(false);
|
||||
const drawn = lottery.status === 'drawn';
|
||||
const canDraw = !drawn && isOwnerOrAdmin && lottery.participant_count >= lottery.winner_count;
|
||||
|
||||
const confirmDraw = async () => {
|
||||
setDrawing(true);
|
||||
try {
|
||||
const r = await api.postLotteryDraw(postId);
|
||||
onUpdate(r.lottery);
|
||||
notify.success('开奖完成');
|
||||
setDrawOpen(false);
|
||||
} catch (e: unknown) {
|
||||
notify.error(e instanceof Error ? e.message : '开奖失败');
|
||||
} finally {
|
||||
setDrawing(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="post-lottery" aria-label="抽奖">
|
||||
<div className="post-lottery__head">
|
||||
<Gift size={18} aria-hidden />
|
||||
<strong>{drawn ? '已开奖' : '抽奖进行中'}</strong>
|
||||
<span>抽取 {lottery.winner_count} 人 · 当前 {lottery.participant_count} 人参与</span>
|
||||
</div>
|
||||
{!drawn && (
|
||||
<p className="post-lottery__hint">回帖即可参与(楼主除外),由作者或管理员手动开奖</p>
|
||||
)}
|
||||
{drawn && lottery.winners && lottery.winners.length > 0 && (
|
||||
<ul className="post-lottery__winners">
|
||||
{lottery.winners.map(w => (
|
||||
<li key={`${w.user_id}-${w.comment_id}`}>
|
||||
<a href={userPath(w.user_id)}>{w.nickname || w.username}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
{canDraw && (
|
||||
<Button type="button" size="sm" onClick={() => setDrawOpen(true)}>立即开奖</Button>
|
||||
)}
|
||||
</section>
|
||||
<AlertDialog
|
||||
open={drawOpen}
|
||||
onOpenChange={(next) => { if (!next && !drawing) setDrawOpen(false); }}
|
||||
>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>立即开奖?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
将从 {lottery.participant_count} 位参与者中抽取 {lottery.winner_count} 名中奖者。此操作不可撤销。
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={drawing}>取消</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
disabled={drawing}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
void confirmDraw();
|
||||
}}
|
||||
>
|
||||
{drawing ? '开奖中…' : '确认开奖'}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user