feat: 实现 Mini Game AI 工具平台
基于 Next.js 15、React 19 和 TypeScript 构建面向小游戏开发者的 AI 赋能工具平台。 主要功能: - 首页:包含 Hero、功能展示、优势介绍、定价和 CTA 区域 - 三大核心工具:视频转序列帧、图片压缩、音频压缩 - 响应式布局:包含顶部导航、页脚和侧边栏 - 文件上传:支持拖拽上传,使用 react-dropzone - 进度追踪:实时显示上传和处理进度 - 可配置工具:每个工具都支持自定义参数配置 - 结果预览:支持下载处理后的文件 - 4K 优化:针对大屏幕优化的响应式设计 - API 路由:文件上传和处理的模拟实现 技术栈: - Next.js 15 (App Router) - React 19 - TypeScript (严格模式) - Tailwind CSS(自定义 4K 断点) - shadcn/ui 组件库 - Framer Motion 动画 - Zustand 状态管理 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
135
src/components/tools/ConfigPanel.tsx
Normal file
135
src/components/tools/ConfigPanel.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
"use client";
|
||||
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export interface ConfigOption {
|
||||
id: string;
|
||||
type: "slider" | "select" | "toggle" | "radio";
|
||||
label: string;
|
||||
description?: string;
|
||||
value: any;
|
||||
options?: { label: string; value: any }[];
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
suffix?: string;
|
||||
icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
interface ConfigPanelProps {
|
||||
title: string;
|
||||
description?: string;
|
||||
options: ConfigOption[];
|
||||
onChange: (id: string, value: any) => void;
|
||||
onReset?: () => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function ConfigPanel({
|
||||
title,
|
||||
description,
|
||||
options,
|
||||
onChange,
|
||||
onReset,
|
||||
className,
|
||||
}: ConfigPanelProps) {
|
||||
return (
|
||||
<Card className={className}>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="text-lg">{title}</CardTitle>
|
||||
{description && (
|
||||
<p className="mt-1 text-sm text-muted-foreground">{description}</p>
|
||||
)}
|
||||
</div>
|
||||
{onReset && (
|
||||
<Button variant="ghost" size="sm" onClick={onReset}>
|
||||
Reset
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
{options.map((option) => (
|
||||
<div key={option.id} className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
{option.icon && <div className="text-muted-foreground">{option.icon}</div>}
|
||||
<Label htmlFor={option.id} className="font-medium">
|
||||
{option.label}
|
||||
</Label>
|
||||
</div>
|
||||
{option.type === "slider" && (
|
||||
<Badge variant="secondary">
|
||||
{option.value}
|
||||
{option.suffix}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{option.description && (
|
||||
<p className="text-xs text-muted-foreground">{option.description}</p>
|
||||
)}
|
||||
|
||||
{option.type === "slider" && (
|
||||
<Slider
|
||||
id={option.id}
|
||||
min={option.min ?? 0}
|
||||
max={option.max ?? 100}
|
||||
step={option.step ?? 1}
|
||||
value={[option.value]}
|
||||
onValueChange={(values: number[]) => onChange(option.id, values[0])}
|
||||
className="mt-2"
|
||||
/>
|
||||
)}
|
||||
|
||||
{option.type === "select" && option.options && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{option.options.map((opt) => (
|
||||
<Button
|
||||
key={opt.value}
|
||||
variant={option.value === opt.value ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => onChange(option.id, opt.value)}
|
||||
>
|
||||
{opt.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{option.type === "radio" && option.options && (
|
||||
<div className="space-y-2">
|
||||
{option.options.map((opt) => (
|
||||
<label
|
||||
key={opt.value}
|
||||
className={cn(
|
||||
"flex cursor-pointer items-center gap-3 rounded-lg border p-3 transition-colors hover:bg-accent",
|
||||
option.value === opt.value && "border-primary bg-primary/10"
|
||||
)}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name={option.id}
|
||||
value={opt.value}
|
||||
checked={option.value === opt.value}
|
||||
onChange={() => onChange(option.id, opt.value)}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
<span className="text-sm">{opt.label}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user