's'
This commit is contained in:
@@ -19,9 +19,9 @@ interface FormData {
|
||||
phone: string;
|
||||
}
|
||||
|
||||
interface MediaFile {
|
||||
file: File;
|
||||
interface MediaInfo {
|
||||
preview: string;
|
||||
url: string;
|
||||
type: "image" | "video";
|
||||
}
|
||||
|
||||
@@ -37,16 +37,36 @@ export default function JoinPage() {
|
||||
graduationYear: "",
|
||||
phone: "",
|
||||
});
|
||||
const [media, setMedia] = useState<MediaFile | null>(null);
|
||||
const [media, setMedia] = useState<MediaInfo | null>(null);
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const fileRef = useRef<HTMLInputElement>(null);
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const set = (key: keyof FormData, value: string) =>
|
||||
setForm((prev) => ({ ...prev, [key]: value }));
|
||||
|
||||
const handleSubmit = (e: FormEvent) => {
|
||||
const handleSubmit = async (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
setSubmitted(true);
|
||||
setError(null);
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const res = await fetch("/api/join", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ ...form, media: media?.url }),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok || !data?.ok) {
|
||||
throw new Error(data?.error || "提交失败,请稍后重试");
|
||||
}
|
||||
setSubmitted(true);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "网络错误,请重试");
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (submitted) {
|
||||
@@ -290,7 +310,7 @@ export default function JoinPage() {
|
||||
type="file"
|
||||
accept="image/jpeg,image/png,image/webp,video/mp4,video/quicktime"
|
||||
className="hidden"
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange={async (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
if (file.size > 20 * 1024 * 1024) {
|
||||
@@ -299,12 +319,38 @@ export default function JoinPage() {
|
||||
}
|
||||
const isVideo = file.type.startsWith("video/");
|
||||
const preview = URL.createObjectURL(file);
|
||||
setMedia({ file, preview, type: isVideo ? "video" : "image" });
|
||||
setMedia({ preview, url: "", type: isVideo ? "video" : "image" });
|
||||
setUploading(true);
|
||||
try {
|
||||
const fd = new FormData();
|
||||
fd.append("file", file);
|
||||
const res = await fetch("/api/upload-media", {
|
||||
method: "POST",
|
||||
body: fd,
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok || !data?.ok) {
|
||||
throw new Error(data?.error || "上传失败");
|
||||
}
|
||||
setMedia((m) => (m ? { ...m, url: data.url } : null));
|
||||
} catch (err) {
|
||||
alert(err instanceof Error ? err.message : "上传失败");
|
||||
URL.revokeObjectURL(preview);
|
||||
setMedia(null);
|
||||
if (fileRef.current) fileRef.current.value = "";
|
||||
} finally {
|
||||
setUploading(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{media ? (
|
||||
<div className="relative inline-block">
|
||||
{uploading && (
|
||||
<div className="absolute inset-0 z-10 flex items-center justify-center rounded-xl bg-black/40 text-sm font-medium text-white">
|
||||
上传中…
|
||||
</div>
|
||||
)}
|
||||
{media.type === "image" ? (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img
|
||||
@@ -323,12 +369,13 @@ export default function JoinPage() {
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
disabled={uploading}
|
||||
onClick={() => {
|
||||
URL.revokeObjectURL(media.preview);
|
||||
setMedia(null);
|
||||
if (fileRef.current) fileRef.current.value = "";
|
||||
}}
|
||||
className="absolute -top-2 -right-2 flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-xs text-white shadow-md transition-colors hover:bg-red-600"
|
||||
className="absolute -top-2 -right-2 flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-xs text-white shadow-md transition-colors hover:bg-red-600 disabled:opacity-50"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
@@ -336,24 +383,31 @@ export default function JoinPage() {
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
disabled={uploading}
|
||||
onClick={() => fileRef.current?.click()}
|
||||
className="flex h-32 w-32 flex-col items-center justify-center gap-2 rounded-xl border-2 border-dashed border-slate-200 text-slate-400 transition-colors hover:border-sky-300 hover:text-sky-500 sm:h-40 sm:w-40"
|
||||
className="flex h-32 w-32 flex-col items-center justify-center gap-2 rounded-xl border-2 border-dashed border-slate-200 text-slate-400 transition-colors hover:border-sky-300 hover:text-sky-500 disabled:opacity-50 sm:h-40 sm:w-40"
|
||||
>
|
||||
<svg className="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
<span className="text-xs">点击上传</span>
|
||||
<span className="text-xs">{uploading ? "上传中…" : "点击上传"}</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* ── Submit ── */}
|
||||
<div className="mt-8 sm:mt-10">
|
||||
{error && (
|
||||
<p className="mb-4 rounded-lg bg-red-50 px-4 py-3 text-center text-sm text-red-600">
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full rounded-full bg-[#1aad19] py-3.5 text-lg font-semibold text-white shadow-md shadow-emerald-500/20 transition-all hover:bg-[#179b16] hover:shadow-lg hover:shadow-emerald-500/25 active:scale-[0.98] sm:py-4"
|
||||
disabled={submitting || uploading}
|
||||
className="w-full rounded-full bg-[#1aad19] py-3.5 text-lg font-semibold text-white shadow-md shadow-emerald-500/20 transition-all hover:bg-[#179b16] hover:shadow-lg hover:shadow-emerald-500/25 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-70 sm:py-4"
|
||||
>
|
||||
提交申请
|
||||
{uploading ? "媒体上传中…" : submitting ? "提交中…" : "提交申请"}
|
||||
</button>
|
||||
|
||||
<p className="mt-4 whitespace-pre-line text-center text-sm leading-relaxed text-amber-500">
|
||||
|
||||
Reference in New Issue
Block a user