官网 初版
This commit is contained in:
153
src/components/admin/SoftwareVersionForm.tsx
Normal file
153
src/components/admin/SoftwareVersionForm.tsx
Normal 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功能 - 修复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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user