v0 前端AI 效率工具
前言
随着人工智能的崛起,AI 工具也越来越多。这次要介绍的是一款可以帮助程序员生成对应需求的前端页面源码—— v0。
v0 可以跟 chatgpt 一样,开发者可以提供需求让它帮忙开发一个前端项目。
首先让我们看看它们之间的一些区别:
chatgpt | vo | |
---|---|---|
科学上网环境 | ✔️ | ❌ |
一键生成项目 | ❌ | ✔️ |
可以预览项目页面 | ❌ | ✔️ |
简单的项目式询问(一个项目一个项目问) | ❌ | ✔️ |
响应准确度 | ❌ | ✔️ |
根据效果图生成对应页面 | ❌ | ✔️ |
免费使用次数 | ✔️ | ✔️ |
下面让我们走进示例看看怎么使用?以及开发效率怎么样?
示例
v1
首先,我给 v0 一个页面需求:
帮我写一个上传页面,要求美观,需求是我要的是能上传一个一个的文件夹 然后文件夹里面有图片。我点击那个已经上传的文件夹就能够看到里面的图片.
它给的代码和预览:
folder-upload.tsx
'use client'
import { useState, useRef } from 'react'
import { Folder, Image, Upload, X } from 'lucide-react'
import { Button } from "@/components/ui/button"
import { ScrollArea } from "@/components/ui/scroll-area"
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"
interface FolderContent {
name: string;
files: File[];
}
export default function Component() {
const [folders, setFolders] = useState<FolderContent[]>([]);
const [selectedFolder, setSelectedFolder] = useState<FolderContent | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const handleFolderUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
const files = event.target.files;
if (files) {
const newFolders: FolderContent[] = [];
for (let i = 0; i < files.length; i++) {
const file = files[i];
const pathParts = file.webkitRelativePath.split('/');
const folderName = pathParts[0];
let folder = newFolders.find(f => f.name === folderName);
if (!folder) {
folder = { name: folderName, files: [] };
newFolders.push(folder);
}
folder.files.push(file);
}
setFolders([...folders, ...newFolders]);
}
};
const handleFolderClick = (folder: FolderContent) => {
setSelectedFolder(folder);
};
const handleRemoveFolder = (folderToRemove: FolderContent) => {
setFolders(folders.filter(folder => folder !== folderToRemove));
if (selectedFolder === folderToRemove) {
setSelectedFolder(null);
}
};
return (
<div className="container mx-auto p-4 max-w-4xl">
<h1 className="text-2xl font-bold mb-4">文件夹上传</h1>
<div className="mb-4">
<Button
onClick={() => fileInputRef.current?.click()}
className="bg-blue-500 hover:bg-blue-600 text-white"
>
<Upload className="mr-2 h-4 w-4" /> 上传文件夹
</Button>
<input
type="file"
ref={fileInputRef}
onChange={handleFolderUpload}
webkitdirectory="true"
directory="true"
multiple
className="hidden"
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="border rounded-lg p-4">
<h2 className="text-lg font-semibold mb-2">已上传的文件夹</h2>
<ScrollArea className="h-[400px]">
{folders.map((folder, index) => (
<div key={index} className="flex items-center justify-between p-2 hover:bg-gray-100 rounded">
<Button
variant="ghost"
className="flex items-center text-left w-full"
onClick={() => handleFolderClick(folder)}
>
<Folder className="mr-2 h-4 w-4" /> {folder.name}
</Button>
<Button
variant="ghost"
size="icon"
onClick={() => handleRemoveFolder(folder)}
>
<X className="h-4 w-4" />
</Button>
</div>
))}
</ScrollArea>
</div>
<div className="border rounded-lg p-4">
<h2 className="text-lg font-semibold mb-2">文件夹内容</h2>
<ScrollArea className="h-[400px]">
{selectedFolder ? (
<div className="grid grid-cols-2 sm:grid-cols-3 gap-4">
{selectedFolder.files.map((file, index) => (
<Dialog key={index}>
<DialogTrigger asChild>
<Button variant="outline" className="w-full h-24 flex flex-col items-center justify-center">
<Image className="h-8 w-8 mb-2" />
<span className="text-xs text-center break-all">{file.name}</span>
</Button>
</DialogTrigger>
<DialogContent className="max-w-3xl">
<img src={URL.createObjectURL(file)} alt={file.name} className="max-h-[80vh] object-contain" />
</DialogContent>
</Dialog>
))}
</div>
) : (
<p className="text-center text-gray-500">请选择一个文件夹查看内容</p>
)}
</ScrollArea>
</div>
</div>
</div>
)
}
效果预览:
v2
由于效果不怎么美观,于是我给出我的第二个需求:
在美化一下,已上传的文件框要求做成符合美观的类似于windows那种文件资源管理,预览做成点击预览显示在文件上面一层
于是它给出第二种代码和预览:
enhanced-folder-upload.tsx
'use client'
import { useState, useRef } from 'react'
import { Folder, File, Upload, ChevronRight, X } from 'lucide-react'
import { Button } from "@/components/ui/button"
import { ScrollArea } from "@/components/ui/scroll-area"
interface FolderContent {
name: string;
files: File[];
}
export default function Component() {
const [folders, setFolders] = useState<FolderContent[]>([]);
const [selectedFolder, setSelectedFolder] = useState<FolderContent | null>(null);
const [previewImage, setPreviewImage] = useState<string | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const handleFolderUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
const files = event.target.files;
if (files) {
const newFolders: FolderContent[] = [];
for (let i = 0; i < files.length; i++) {
const file = files[i];
const pathParts = file.webkitRelativePath.split('/');
const folderName = pathParts[0];
let folder = newFolders.find(f => f.name === folderName);
if (!folder) {
folder = { name: folderName, files: [] };
newFolders.push(folder);
}
folder.files.push(file);
}
setFolders([...folders, ...newFolders]);
}
};
const handleFolderClick = (folder: FolderContent) => {
setSelectedFolder(folder);
setPreviewImage(null);
};
const handleRemoveFolder = (folderToRemove: FolderContent) => {
setFolders(folders.filter(folder => folder !== folderToRemove));
if (selectedFolder === folderToRemove) {
setSelectedFolder(null);
}
setPreviewImage(null);
};
const handleFileClick = (file: File) => {
setPreviewImage(URL.createObjectURL(file));
};
return (
<div className="container mx-auto p-4 max-w-4xl">
<h1 className="text-2xl font-bold mb-4">文件夹上传与预览</h1>
<div className="mb-4">
<Button
onClick={() => fileInputRef.current?.click()}
className="bg-blue-500 hover:bg-blue-600 text-white"
>
<Upload className="mr-2 h-4 w-4" /> 上传文件夹
</Button>
<input
type="file"
ref={fileInputRef}
onChange={handleFolderUpload}
webkitdirectory="true"
directory="true"
multiple
className="hidden"
/>
</div>
<div className="border rounded-lg overflow-hidden">
{previewImage && (
<div className="relative">
<img src={previewImage} alt="Preview" className="w-full h-64 object-contain bg-gray-100" />
<Button
variant="ghost"
size="icon"
className="absolute top-2 right-2 bg-white/50 hover:bg-white/75"
onClick={() => setPreviewImage(null)}
>
<X className="h-4 w-4" />
</Button>
</div>
)}
<div className="flex">
<div className="w-1/3 border-r">
<ScrollArea className="h-[400px]">
{folders.map((folder, index) => (
<div
key={index}
className={`flex items-center p-2 cursor-pointer hover:bg-gray-100 ${selectedFolder === folder ? 'bg-blue-100' : ''}`}
onClick={() => handleFolderClick(folder)}
>
<Folder className="mr-2 h-4 w-4 text-yellow-500" />
<span className="flex-grow truncate">{folder.name}</span>
<Button
variant="ghost"
size="icon"
className="opacity-0 group-hover:opacity-100"
onClick={(e) => {
e.stopPropagation();
handleRemoveFolder(folder);
}}
>
<X className="h-4 w-4" />
</Button>
<ChevronRight className="h-4 w-4 text-gray-400" />
</div>
))}
</ScrollArea>
</div>
<div className="w-2/3">
<ScrollArea className="h-[400px]">
{selectedFolder ? (
<div className="grid grid-cols-3 gap-4 p-4">
{selectedFolder.files.map((file, index) => (
<div
key={index}
className="flex flex-col items-center p-2 border rounded hover:bg-gray-50 cursor-pointer"
onClick={() => handleFileClick(file)}
>
<File className="h-8 w-8 text-blue-500 mb-2" />
<span className="text-xs text-center truncate w-full">{file.name}</span>
</div>
))}
</div>
) : (
<div className="flex items-center justify-center h-full text-gray-500">
请选择一个文件夹查看内容
</div>
)}
</ScrollArea>
</div>
</div>
</div>
</div>
)
}
效果预览:
v3
我感觉还是十分丑,于是我又重新给它提出需求:
能不能将这个改成全屏文件资源管理,上传文件按钮美化一下放在资源管理器的右上角
最后,它给了我一个完美的源码和效果展示(我觉得已经很完美了):
file-manager.tsx
'use client'
import { useState, useRef } from 'react'
import { Folder, File, Upload, ChevronRight, X, FolderOpen } from 'lucide-react'
import { Button } from "@/components/ui/button"
import { ScrollArea } from "@/components/ui/scroll-area"
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
interface FolderContent {
name: string;
files: File[];
}
export default function Component() {
const [folders, setFolders] = useState<FolderContent[]>([]);
const [selectedFolder, setSelectedFolder] = useState<FolderContent | null>(null);
const [previewImage, setPreviewImage] = useState<string | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const handleFolderUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
const files = event.target.files;
if (files) {
const newFolders: FolderContent[] = [];
for (let i = 0; i < files.length; i++) {
const file = files[i];
const pathParts = file.webkitRelativePath.split('/');
const folderName = pathParts[0];
let folder = newFolders.find(f => f.name === folderName);
if (!folder) {
folder = { name: folderName, files: [] };
newFolders.push(folder);
}
folder.files.push(file);
}
setFolders([...folders, ...newFolders]);
}
};
const handleFolderClick = (folder: FolderContent) => {
setSelectedFolder(folder);
setPreviewImage(null);
};
const handleRemoveFolder = (folderToRemove: FolderContent) => {
setFolders(folders.filter(folder => folder !== folderToRemove));
if (selectedFolder === folderToRemove) {
setSelectedFolder(null);
}
setPreviewImage(null);
};
const handleFileClick = (file: File) => {
setPreviewImage(URL.createObjectURL(file));
};
return (
<div className="h-screen flex flex-col bg-gray-100">
<header className="bg-white shadow-sm p-4 flex justify-between items-center">
<h1 className="text-2xl font-bold">文件资源管理器</h1>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
onClick={() => fileInputRef.current?.click()}
className="bg-blue-500 hover:bg-blue-600 text-white"
>
<Upload className="mr-2 h-4 w-4" /> 上传文件夹
</Button>
</TooltipTrigger>
<TooltipContent>
<p>选择并上传一个文件夹</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<input
type="file"
ref={fileInputRef}
onChange={handleFolderUpload}
webkitdirectory="true"
directory="true"
multiple
className="hidden"
/>
</header>
<main className="flex-1 flex overflow-hidden">
<aside className="w-1/4 bg-white border-r border-gray-200">
<ScrollArea className="h-full">
{folders.map((folder, index) => (
<div
key={index}
className={`flex items-center p-3 cursor-pointer hover:bg-gray-100 ${selectedFolder === folder ? 'bg-blue-100' : ''}`}
onClick={() => handleFolderClick(folder)}
>
{selectedFolder === folder ? (
<FolderOpen className="mr-2 h-5 w-5 text-blue-500" />
) : (
<Folder className="mr-2 h-5 w-5 text-yellow-500" />
)}
<span className="flex-grow truncate">{folder.name}</span>
<Button
variant="ghost"
size="icon"
className="opacity-0 group-hover:opacity-100"
onClick={(e) => {
e.stopPropagation();
handleRemoveFolder(folder);
}}
>
<X className="h-4 w-4" />
</Button>
<ChevronRight className="h-4 w-4 text-gray-400" />
</div>
))}
</ScrollArea>
</aside>
<section className="flex-1 flex flex-col">
{previewImage && (
<div className="relative h-1/3 bg-gray-800">
<img src={previewImage} alt="Preview" className="w-full h-full object-contain" />
<Button
variant="ghost"
size="icon"
className="absolute top-2 right-2 bg-white/10 hover:bg-white/20 text-white"
onClick={() => setPreviewImage(null)}
>
<X className="h-4 w-4" />
</Button>
</div>
)}
<div className="flex-1 bg-white p-4 overflow-hidden">
<ScrollArea className="h-full">
{selectedFolder ? (
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-4">
{selectedFolder.files.map((file, index) => (
<div
key={index}
className="flex flex-col items-center p-3 border rounded hover:bg-gray-50 cursor-pointer transition-colors duration-200"
onClick={() => handleFileClick(file)}
>
<File className="h-12 w-12 text-blue-500 mb-2" />
<span className="text-xs text-center truncate w-full">{file.name}</span>
</div>
))}
</div>
) : (
<div className="flex items-center justify-center h-full text-gray-500">
请选择一个文件夹查看内容
</div>
)}
</ScrollArea>
</div>
</section>
</main>
</div>
)
}
效果预览:
然后,我们可以选择使用命令导出并下载到电脑上运行:
npx shadcn@latest add "https://v0.dev/chat/b/koXtMu8?token=eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..M-Z9eEXS6dfU12lD.tXqxor27DA7_g0adWzgrf_fFu3tYNap1MsFNs1dp7TXxNUMpaDI.3OA9-Gw5_ogSAYHzJkFjbg"
将上述复制的命令粘贴到控制台运行(必须要有node环境),耐心等待项目生成。
出现如下图即成功安装项目:
最后我们可以使用以下命令启动项目来看看效果:
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
效果还是非常不错的。
总结
v0 是个很不错的 AI 效率开发工具,可以一键生成项目并且可以导出命令供本地或远程生成项目。并且 v0 还支持边生成边预览生成的效果。对于开发可以更好地提高开发效率。
参考资料
[1] v0:https://v0.dev/
[2] Chatgpt:https://chatgpt.com/
作者资料
作者:Oberl Fitzgerald
Github:https://github.com/JK407
博客:https://oberl.club/
Telegram:https://t.me/oberlFitzgerald