laravelguru / laravel-filehandler
该 Laravel Inertia React 文件管理包通过 React 和 Inertia.js 无缝地将文件管理集成到您的 Laravel 应用程序中。它提供了预构建的文件输入组件和弹出文件对话框,简化了文件上传、存储、浏览和管理。
Requires
- php: ^7.3|~8.0.0|~8.1.0|~8.2.0|~8.3.0
- illuminate/support: ^10.0|^11.0
- inertiajs/inertia-laravel: ^1.3.0
- laravel/framework: ^8.74|^9.0|^10.0|^11.0
Requires (Dev)
- orchestra/testbench: ^6.4|^7.0|^8.0|^9.0
- phpunit/phpunit: ^8.0|^9.5.8|^10.4
This package is auto-updated.
Last update: 2024-09-07 11:06:14 UTC
README
概述
该 Laravel Inertia React 文件管理包使用 React 和 Inertia.js 为您的 Laravel 应用程序提供无缝的文件管理功能。它包括预构建的文件输入组件和弹出文件对话框,方便文件上传、存储、浏览和管理。
功能
- 无缝集成 React 和 Inertia.js
- 使用 ShadCN 组件(对话框、按钮、滚动区域和选项卡)
- 预构建的文件输入组件和弹出文件对话框
- 平滑的单页应用程序(SPA)转换
- 全面文件操作:上传、下载、删除、移动文件
- 响应式文件浏览器
- 适用于 CMS、电子商务平台、项目管理工具和个人作品集
安装
您可以通过 composer 安装此包
composer require laravelguru/laravel-filehandler
注册服务提供者
如果您使用的是 Laravel 11 或更新版本,您应该在 bootstrap/providers.php 中添加服务提供者
<?php return [ // Other Service Providers LaravelGuru\LaravelFilehandler\ServiceProvider::class, ];
发布资产
运行以下命令以发布包资产
- 此包仅在应用程序以网络环境运行时加载,确保在命令行操作期间不会不必要地加载。通过检查应用程序是否在控制台运行使用 app()->runningInConsole();
php artisan serve npm run dev
- 通过仅在需要时激活,该包通过检查应用程序是否在控制台运行来优化性能,保持应用程序在 CLI 操作期间轻量级。
在发布资源之前,请了解用例
- 如果您不使用预构建的组件,只需要迁移即可
- 如果您不使用预构建的组件,则可能不需要迁移、模型、资源、组件的修改
- 否则,您需要要求一切
php artisan vendor:publish --provider="LaravelGuru\LaravelFilehandler\LaravelFilehandlerServiceProvider" or php artisan vendor:publish --provider="LaravelGuru\LaravelFilehandler\LaravelFilehandlerServiceProvider" --tag=filehandler-config php artisan vendor:publish --provider="LaravelGuru\LaravelFilehandler\LaravelFilehandlerServiceProvider" --tag=filehandler-migration php artisan vendor:publish --provider="LaravelGuru\LaravelFilehandler\LaravelFilehandlerServiceProvider" --tag=filehandler-controller php artisan vendor:publish --provider="LaravelGuru\LaravelFilehandler\LaravelFilehandlerServiceProvider" --tag=filehandler-resource php artisan vendor:publish --provider="LaravelGuru\LaravelFilehandler\LaravelFilehandlerServiceProvider" --tag=filehandler-model php artisan vendor:publish --provider="LaravelGuru\LaravelFilehandler\LaravelFilehandlerServiceProvider" --tag=filehandler-components php artisan vendor:publish --provider="LaravelGuru\LaravelFilehandler\LaravelFilehandlerServiceProvider" --tag=filehandler-css
链接 Laravel 的存储
php artisan storage:link
安装 shadcn/ui 组件(仅当使用预定义组件时)
按照官方安装指南初始化 shadcn/ui 组件。使用以下命令添加所需组件
npx shadcn-ui@latest add button npx shadcn-ui@latest add dialog npx shadcn-ui@latest add tabs npx shadcn-ui@latest add scroll-area
设置
- 安装依赖项:确保您已在 Laravel 项目中设置 React 和 Inertia.js。
- 集成组件:在您的应用程序中使用提供的 React 组件和 Inertia.js 中间件。
- 自定义:根据需要修改组件和处理程序以符合您的需求。
- 运行迁移:将迁移应用到您的数据库
运行迁移
当应用程序启动时,服务提供者将自动为 file_repos 表生成迁移。使用以下命令运行迁移
php artisan migrate
用法
虽然视图/组件对于 API 开发可能是可选的,但集成它们可以增强用户体验。此包专注于提供核心 API 路由,用于索引、显示、存储、更新和删除操作。如果您主要处理文件管理,此包可以作为坚实的基础。
public function __construct(FileService $fileService)
- 描述:构造函数方法,将 FileService 类注入到 FileController 类中,使其可用。
public function index()
描述:检索并返回与已认证用户关联的文件列表,位于 'documents' 文件夹中。
public function store(Request $request)
描述:处理新文件的上传。它从请求中接收文件,处理上传,并返回一个 JSON 响应,指示操作的成功或失败。
private function upload($user_id, $files)
描述: 一个私有方法,用于执行文件上传到存储并更新数据库的操作,在事务中执行。它返回上传文件的详细信息。
public function show(File $file)
描述: 获取并返回特定文件的详细信息。
public function update(Request $request, File $file)
描述: 使用新数据更新现有文件。它处理文件修改并返回一个JSON响应,指示操作的成败。
public function destroy(File $file)
描述: 删除指定的文件,并返回一个JSON响应,指示删除操作的成败。
利用预构建组件进行高效开发。
本包提供基于Shadcn UI构建的即用文件输入和多个文件处理组件。使用预设计的可定制元素简化开发流程。享受提升的用户体验和快速原型设计。
文件输入
处理多个文件
- 验证规则配置
$data = $request->validate([ "cv_path" => ["nullable", "integer", "exists:files,id"], "brochures" => ["nullable", "array", "max:5"], "brochures.*" => ["nullable", "integer", "exists:files,id"], ])
- 数据库迁移设置
public function up(): void { Schema::create('courses', function (Blueprint $table) { $table->string('brochures')->nullable(); }); }
- 创建函数的控制器方法(确保在视图中的文件同步时关注documents变量)
public function create() { $faculties = Faculty::query()->orderBy('name', 'asc')->get(); $documents = File::query()->where('user_id', auth()->id())->where('folder', 'documents')->paginate(9)->onEachSide(1); return Inertia::render('Courses/Create', [ 'documents' => FileResource::collection($documents), 'faculties' => FacultyResource::collection($faculties), ]); }
- 创建视图中使用文件输入进行文件同步的指南(确保在视图中的文件同步时关注documents变量 + brochures)
export default function Create({ auth, documents, faculties }) { const { data, setData, post, processing, errors, reset } = useForm({ brochures: null, }); const [files, setFiles] = useState([]); const [submitTriggered, setSubmitTriggered] = useState(false); const onSubmit = (e) => { e.preventDefault(); if (files.length === 0) { post(route("courses.store"), { onSuccess: () => { reset(); }, onError: () => { console.log("Error creating course"); }, }); return; } setData( "brochures", files.map((file) => file.id) ); setSubmitTriggered(true); }; useEffect(() => { if (submitTriggered) { post(route("courses.store"), { onSuccess: () => { reset(); setSubmitTriggered(false); }, onError: () => { console.log("Error creating course"); setSubmitTriggered(false); }, }); } }, [submitTriggered]); return ( <div className="grid gap-2"> <Label htmlFor="name">Brochures</Label> <div className="w-full overflow-x-auto"> <FileInput selectedFiles={files} onFileChange={(files) => { setFiles(files); }} apiUrl={route("files.store")} multiple={true} documents={documents} /> </div> </div> ) }
- 存储函数的控制器方法(确保在相应数据库表中存储文件时关注brochures变量)
public function store(StoreCourseRequest $request) { $data = $request->validated(); if (isset($data['brochures'])) { $data['brochures'] = json_encode($data['brochures'], true); } $data['created_by'] = auth()->id(); $data['updated_by'] = auth()->id(); if (Gate::allows('create_course')) { Course::create($data); return redirect()->route('courses.index')->with('success', 'Course created successfully'); } else { return redirect()->back()->with('error', 'You are not authorized to create a course'); } }
- 编辑函数的控制器方法(确保在视图中的文件同步时关注documents变量 + brochures)
public function edit(Course $course) { if (Gate::allows('update_course', $course)) { $faculties = Faculty::query()->orderBy('name', 'asc')->get(); $documents = File::query()->where('user_id', auth()->id())->where('folder', 'documents')->paginate(9)->onEachSide(1); $array = json_decode($course->brochures) ?? []; $brochures = File::whereIn('id', $array)->get(); return Inertia::render('Courses/Edit', [ 'course' => new CourseResource($course), 'brochures' => FileResource::collection($brochures), 'documents' => FileResource::collection($documents), 'faculties' => FacultyResource::collection($faculties), ]); } else { return redirect()->back()->with('error', 'You are not authorized to edit this course'); } }
- 编辑视图中使用文件输入进行文件同步的指南(确保在视图中的文件同步时关注documents变量 + brochures)
export default function Edit({auth, course, brochures, documents, faculties }){ const { data, setData, put, processing, errors, reset } = useForm({ brochures: null, }) const [files, setFiles] = useState(brochures.data ?? []); const onSubmit = (e) => { e.preventDefault(); if (files.length === 0) { put(route("courses.update", course.id), { preserveScroll: true, onSuccess: () => { reset(); }, onError: () => { console.log("Error updating course"); }, }); return; } setData( "brochures", files.map((file) => file.id) ); setSubmitTriggered(true); }; useEffect(() => { if (submitTriggered) { put(route("courses.update", course.id), { onSuccess: () => { reset(); setSubmitTriggered(false); }, onError: () => { console.log("Error updating course"); setSubmitTriggered(false); }, }); } }, [submitTriggered]); return( <div className="grid gap-2"> <Label htmlFor="name">Brochures</Label> <div className="w-full overflow-x-auto"> <FileInput selectedFiles={files} onFileChange={(files) => { setFiles(files); }} apiUrl={route("files.store")} multiple={true} documents={documents} /> </div> </div> ) }
- 更新函数的控制器方法(确保在相应数据库表中更新文件时关注brochures变量)
public function update(UpdateCourseRequest $request, Course $course) { // $data = $request->validated(); if (isset($data['brochures'])) { $data['brochures'] = json_encode($data['brochures'], true); } $data['updated_by'] = auth()->id(); if (Gate::allows('update_course', $course)) { $course->update($data); return redirect()->route('courses.index')->with('success', 'Course updated successfully'); } else { return redirect()->back()->with('error', 'You are not authorized to update this course'); } }
处理单个文件
- 验证规则配置
$data = $request->validate([ "cv_path" => ["nullable", "integer", "exists:files,id"], ])
- 数据库迁移设置
public function up(): void { Schema::create('users', function (Blueprint $table) { $table->string('cv_path')->nullable(); }); }
- 编辑函数的控制器方法(确保在视图中的文件同步时编辑文件时关注cv_path和documents变量)
public function edit(User $user) { $documents = File::query()->where('user_id', auth()->id())->where('folder', 'documents')->paginate(9)->onEachSide(1); $cv_array = [json_decode($user->cv_path)] ?? []; $cv_path = []; if (is_array($cv_array) && count($cv_array) > 0) { $cv_path = File::whereIn('id', $cv_array)->get(); } return Inertia::render('User/Edit', [ 'user' => new UserResource($user), 'cv_path' => FileResource::collection($cv_path), 'documents' => FileResource::collection($documents), ]); }
- 编辑视图中使用文件输入进行文件同步的指南(确保在视图中的文件同步时关注cv_path变量 + brochures)
export default function EditStaff({ auth, user, cv_path, documents }) { const { data, setData, put, processing, errors, reset } = useForm({ cv_path: null }); const [cvFile, setCvFile] = useState(cv_path.data ?? []); return ( <div className="grid gap-2"> <Label htmlFor="cv_path">Upload CV</Label> <div className="w-full overflow-x-auto"> <FileInput selectedFiles={cvFile} onFileChange={(files) => { setCvFile(files); setData("cv_path", files?.[0]?.id); }} apiUrl={route("files.store")} multiple={false} documents={documents} /> </div> </div> ) }
显示文件处理
- 显示函数的控制器方法(确保在视图中的文件同步时显示文件时关注brochures变量)
public function show(Course $course) { $modules = $course->modules()->orderBy('created_at', 'asc') ->paginate(10) ->onEachSide(1); $array = json_decode($course->brochures) ?? []; $brochures = File::whereIn('id', $array)->get(); return Inertia::render('Courses/Show', [ 'course' => new CourseResource($course->loadCount('modules')), 'brochures' => FileResource::collection($brochures), 'modules' => ModuleResource::collection($modules), ]); }
- 显示视图中使用文件输入进行文件同步的文件的指南(确保在视图中的文件同步时关注brochures变量)
export default function Show({ auth, course, brochures, modules }) { return( <div> { brochures.data.map((brochure) => ( <div className="flex items-center justify-between py-4 pl-4 pr-5 text-sm leading-6"> <div className="flex w-0 flex-1 items-center"> <svg className="h-5 w-5 flex-shrink-0 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" > <path fillRule="evenodd" d="M15.621 4.379a3 3 0 00-4.242 0l-7 7a3 3 0 004.241 4.243h.001l.497-.5a.75.75 0 011.064 1.057l-.498.501-.002.002a4.5 4.5 0 01-6.364-6.364l7-7a4.5 4.5 0 016.368 6.36l-3.455 3.553A2.625 2.625 0 119.52 9.52l3.45-3.451a.75.75 0 111.061 1.06l-3.45 3.451a1.125 1.125 0 001.587 1.595l3.454-3.553a3 3 0 000-4.242z" clipRule="evenodd" /> </svg> <div className="ml-4 flex min-w-0 flex-1 justify-between"> <span className="truncate font-medium hover:underline"> <a href={brochure.path} target="_blank" rel="noopener noreferrer" > {brochure.name} </a> </span> <span className="flex-shrink-0 text-gray-400"> 2.4MB </span> </div> </div> <div className="ml-4 flex-shrink-0"> <a href={brochure.path} download className="font-medium text-indigo-600 hover:text-indigo-500" > Download </a> </div> </div> )) } </div> ) }
文件对话框
- 验证规则配置
$data = $request->validate([ "image" => ["nullable", "string"], ])
- 数据库迁移设置
public function up(): void { Schema::create('courses', function (Blueprint $table) { $table->string('image')->nullable(); }); }
- 前端实现
<div className="relative h-full w-80 bg-gray-100 dark:bg-gray-900"> <img src={data.image ?? "https://via.placeholder.com/150"} alt="Course Image" className="w-full h-full object-cover ring-1 ring-gray-700 dark:ring-gray-300 p-1 object-center rounded-md" /> <div className="absolute -bottom-5 -right-5"> <FileDialog selectedFiles={selectedFiles} setSelectedFiles={(files) => { if (!files?.length) setData("image", null); setSelectedFiles(files); setData("image", files?.[0]?.path); }} multiple={false} apiUrl={route("files.store")} documents={documents} > <Button variant="outline" className="w-10 h-10 rounded-full shadow-md dark:bg-gray-800 dark:border-gray-600 dark:hover:bg-gray-700 dark:text-gray-300" > <div> <Camera className="w-5 h-5 text-gray-500 dark:text-gray-400" /> </div> </Button> </FileDialog> </div> </div>
使用文件对话框的模型函数存储和更新方法与处理Laravel中其他字符串变量的方式相似。有关详细说明,请参阅Laravel文档。
测试
composer test
更新日志
有关最近更改的更多信息,请参阅更新日志。
贡献
有关详细信息,请参阅贡献指南。
安全
如果您发现任何与安全相关的问题,请通过insafnilam.2000@gmail.com发送电子邮件,而不是使用问题跟踪器。
鸣谢
许可证
MIT许可证(MIT)。有关更多信息,请参阅许可证文件。
Laravel包模板
本包使用Laravel包模板生成。