v0 AI 效率工具

v0 前端AI 效率工具

前言

随着人工智能的崛起,AI 工具也越来越多。这次要介绍的是一款可以帮助程序员生成对应需求的前端页面源码—— v0

v0 可以跟 chatgpt 一样,开发者可以提供需求让它帮忙开发一个前端项目。

首先让我们看看它们之间的一些区别:

chatgptvo
科学上网环境✔️
一键生成项目✔️
可以预览项目页面✔️
简单的项目式询问(一个项目一个项目问)✔️
响应准确度✔️
根据效果图生成对应页面✔️
免费使用次数✔️✔️

下面让我们走进示例看看怎么使用?以及开发效率怎么样?

示例

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>
  )
}

效果预览:

image-20240913172519055

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>
  )
}

效果预览:

image-20240913172920726

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>
  )
}

效果预览:

image-20240913173208292

然后,我们可以选择使用命令导出并下载到电脑上运行:

image-20240913173319456

npx shadcn@latest add "https://v0.dev/chat/b/koXtMu8?token=eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..M-Z9eEXS6dfU12lD.tXqxor27DA7_g0adWzgrf_fFu3tYNap1MsFNs1dp7TXxNUMpaDI.3OA9-Gw5_ogSAYHzJkFjbg"

将上述复制的命令粘贴到控制台运行(必须要有node环境),耐心等待项目生成。

出现如下图即成功安装项目:

image-20240913174216589

最后我们可以使用以下命令启动项目来看看效果:

npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev

image-20240913174401073

image-20240913174425595

image-20240913174431410

效果还是非常不错的。

总结

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

毂梁蔚竹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值