import { Plus, Trash2 } from 'lucide-react'; import { cn } from '@/lib/utils'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Switch } from '@/components/ui/switch'; import type { PostType } from './ComposeContextBar'; interface Props { postType: PostType; pollOptions: string[]; onPollOptionsChange: (opts: string[]) => void; pollMulti: boolean; onPollMultiChange: (v: boolean) => void; pollMaxChoices: number; onPollMaxChoicesChange: (v: number) => void; pollEndsAt: string; onPollEndsAtChange: (v: string) => void; pollNoEndTime: boolean; onPollNoEndTimeChange: (v: boolean) => void; bountyPoints: number; onBountyPointsChange: (v: number) => void; userPointsBalance?: number; lotteryWinners: number; onLotteryWinnersChange: (v: number) => void; disabled?: boolean; } /** 默认截止时间:7 天后,分钟进位到下一整点 */ export function defaultPollEndsAtLocal(): string { const d = new Date(Date.now() + 7 * 24 * 3600_000); d.setMinutes(0, 0, 0); if (d.getTime() <= Date.now()) { d.setHours(d.getHours() + 1); } const pad = (n: number) => String(n).padStart(2, '0'); return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`; } /** datetime-local → ISO8601(UTC) */ export function pollEndsAtLocalToISO(local: string): string | undefined { const trimmed = local.trim(); if (!trimmed) return undefined; const d = new Date(trimmed); if (Number.isNaN(d.getTime())) return undefined; return d.toISOString(); } /** ISO8601 → datetime-local */ export function pollEndsAtISOToLocal(iso: string): string { const d = new Date(iso); if (Number.isNaN(d.getTime())) return ''; const pad = (n: number) => String(n).padStart(2, '0'); return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`; } /** 特殊帖类型附加字段(投票/悬赏/抽奖) */ export default function ComposeSpecialFields({ postType, pollOptions, onPollOptionsChange, pollMulti, onPollMultiChange, pollMaxChoices, onPollMaxChoicesChange, pollEndsAt, onPollEndsAtChange, pollNoEndTime, onPollNoEndTimeChange, bountyPoints, onBountyPointsChange, userPointsBalance, lotteryWinners, onLotteryWinnersChange, disabled, }: Props) { if (postType === 'normal' || postType === 'question') return null; if (postType === 'poll') { return (
{pollMulti && ( <> 最多可选 onPollMaxChoicesChange(Number(e.target.value) || 1)} disabled={disabled} className="compose-special__max-choices" aria-label="最多可选" /> 项 )}
{!pollNoEndTime && (
onPollEndsAtChange(e.target.value)} disabled={disabled} className="compose-special__poll-deadline-input w-auto" />
)}
{pollOptions.map((opt, i) => (
{ const next = [...pollOptions]; next[i] = e.target.value; onPollOptionsChange(next); }} />
))}
{pollOptions.length < 10 && ( )}
); } if (postType === 'bounty') { const showBalance = !disabled && typeof userPointsBalance === 'number'; const balance = userPointsBalance ?? 0; const overBudget = showBalance && bountyPoints > balance; const remaining = showBalance && bountyPoints > 0 && !overBudget ? balance - bountyPoints : null; return (
onBountyPointsChange(Math.max(0, Number(e.target.value) || 0))} /> {showBalance && (

{overBudget ? '积分不足' : remaining != null ? `当前余额 ${balance} · 发布后剩余 ${remaining}` : `当前余额 ${balance}`}

)}
); } if (postType === 'lottery') { return (
onLotteryWinnersChange(Math.min(20, Math.max(1, Number(e.target.value) || 1)))} />

参与者为已回帖用户(不含楼主),由作者或管理员开奖

); } return null; } export function buildPollOptionsPayload( options: string[], multi: boolean, maxChoices: number, endsAtISO?: string | null, ): string { const payload: Record = { multi, max_choices: maxChoices, options: options.filter(o => o.trim()).map(text => ({ text: text.trim() })), }; if (endsAtISO) payload.ends_at = endsAtISO; return JSON.stringify(payload); } /** 统计有效(非空)投票选项数量 */ export function countValidPollOptions(options: string[]): number { return options.filter(o => o.trim()).length; } /** 前端校验投票截止时间(非不限时时) */ export function validatePollEndsAtLocal(local: string): string | null { const iso = pollEndsAtLocalToISO(local); if (!iso) return '请选择有效的投票截止时间'; if (new Date(iso).getTime() <= Date.now() + 5 * 60_000) { return '投票截止时间须晚于当前时间至少 5 分钟'; } if (new Date(iso).getTime() > Date.now() + 365 * 24 * 3600_000) { return '投票截止时间不能超过 365 天'; } return null; }