官网 初版

This commit is contained in:
rucky
2026-03-18 17:13:27 +08:00
parent 879c4bdfc8
commit 241a76caeb
95 changed files with 8889 additions and 113 deletions

View File

@@ -0,0 +1,153 @@
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { toast } from "sonner";
import { Upload } from "lucide-react";
export function SoftwareVersionForm({ softwareId }: { softwareId: string }) {
const router = useRouter();
const [loading, setLoading] = useState(false);
const [downloadType, setDownloadType] = useState("local");
const [uploadedFilePath, setUploadedFilePath] = useState("");
const [fileSize, setFileSize] = useState(0);
const [uploading, setUploading] = useState(false);
async function handleFileUpload(e: React.ChangeEvent<HTMLInputElement>) {
const file = e.target.files?.[0];
if (!file) return;
setUploading(true);
const formData = new FormData();
formData.append("file", file);
const res = await fetch("/api/upload", { method: "POST", body: formData });
if (res.ok) {
const data = await res.json();
setUploadedFilePath(data.filePath);
setFileSize(data.size);
toast.success(`文件 ${data.originalName} 上传成功`);
} else {
toast.error("文件上传失败");
}
setUploading(false);
}
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
setLoading(true);
const fd = new FormData(e.currentTarget);
if (downloadType === "local" && !uploadedFilePath) {
toast.error("请先上传文件");
setLoading(false);
return;
}
const res = await fetch(`/api/software/${softwareId}/versions`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
version: fd.get("version"),
versionCode: Number(fd.get("versionCode")),
changelog: fd.get("changelog"),
downloadType,
filePath: downloadType === "local" ? uploadedFilePath : null,
externalUrl: downloadType === "url" ? fd.get("externalUrl") : null,
fileSize,
forceUpdate: fd.get("forceUpdate") === "on",
minVersion: fd.get("minVersion") || null,
}),
});
if (res.ok) {
toast.success("版本发布成功");
router.push("/admin/software");
router.refresh();
} else {
const err = await res.json();
toast.error(err.error || "发布失败");
}
setLoading(false);
}
return (
<Card>
<CardHeader><CardTitle></CardTitle></CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-6">
<div className="grid gap-4 md:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="version"> *</Label>
<Input id="version" name="version" required placeholder="1.0.0" />
</div>
<div className="space-y-2">
<Label htmlFor="versionCode"> () *</Label>
<Input id="versionCode" name="versionCode" type="number" required placeholder="100" />
</div>
</div>
<div className="space-y-2">
<Label htmlFor="minVersion"></Label>
<Input id="minVersion" name="minVersion" placeholder="0.9.0(可选,低于此版本需升级)" />
</div>
<div className="space-y-2">
<Label htmlFor="changelog"></Label>
<Textarea id="changelog" name="changelog" rows={6} placeholder="- 新增xxx功能&#10;- 修复xxx问题" />
</div>
<div className="space-y-4">
<Label></Label>
<div className="flex gap-4">
<Button type="button" variant={downloadType === "local" ? "default" : "outline"} onClick={() => setDownloadType("local")}>
</Button>
<Button type="button" variant={downloadType === "url" ? "default" : "outline"} onClick={() => setDownloadType("url")}>
</Button>
</div>
{downloadType === "local" ? (
<div className="space-y-2">
<Label htmlFor="file" className="flex cursor-pointer items-center gap-2 rounded-lg border-2 border-dashed px-6 py-4 transition-colors hover:border-primary">
<Upload className="h-5 w-5" />
{uploading ? "上传中..." : uploadedFilePath ? "重新选择文件" : "选择文件"}
</Label>
<Input id="file" type="file" className="hidden" onChange={handleFileUpload} />
{uploadedFilePath && (
<p className="text-sm text-muted-foreground">: {uploadedFilePath} ({(fileSize / 1024).toFixed(1)} KB)</p>
)}
</div>
) : (
<div className="space-y-2">
<Label htmlFor="externalUrl"></Label>
<Input id="externalUrl" name="externalUrl" type="url" placeholder="https://..." />
</div>
)}
</div>
<div className="flex items-center gap-2">
<input type="checkbox" id="forceUpdate" name="forceUpdate" className="h-4 w-4 rounded border-input" />
<Label htmlFor="forceUpdate">使</Label>
</div>
<div className="flex gap-3">
<Button type="submit" disabled={loading}>
{loading ? "发布中..." : "发布版本"}
</Button>
<Button type="button" variant="outline" onClick={() => router.back()}>
</Button>
</div>
</form>
</CardContent>
</Card>
);
}