NativePHP 教程:构建 Mac 菜单栏应用程序
发布日期 作者: Shane D Rosenthal
NativePHP,由 Marcel Pociot 在 BeyondCode 创建,使 Laravel 开发人员能够利用我们已有的所有 Laravel 知识来构建原生 Mac、Windows 和 Linux 应用程序。
我最近看到 Christopher Rumpel 在 一个应用程序 上工作,该应用程序允许您存储朋友的时区,以便您可以一目了然地查看他们的时间。
跟我一起构建一个 Mac 菜单栏应用程序,以便了解团队中每个成员的本地时间。
等等,NativePHP 到底是如何工作的?
NativePHP 允许您从两种流行的技术中选择一种在后台使用:Electron 和 Tauri。这两种技术都允许您“使用 JavaScript、HTML 和 CSS 构建跨平台的桌面应用程序”。如果您仔细想想,这有点像魔法——使用 Web 技术构建“原生”应用程序。NativePHP 提供了一个简单的 API,并提供了一个熟悉的(Laravel)方式来在这两种底层技术中构建应用程序。在本例中,我将演示 Electron 包装器。
NativePHP 安装和 Hello World
在一个新的 Laravel 应用程序中
laravel new team-time
让我们从安装包开始
composer require nativephp/electron
运行安装程序
php artisan native:installWould you like to install the NativePHP NPM dependencies? - Select 'yes'Would you like to start the NativePHP development server? - Select 'no'
我希望您手动启动应用程序,这样您就可以习惯这种方式
php artisan native:serve
片刻之后,您应该会看到一个本机桌面应用程序启动,显示默认的 Laravel 首页,hello there!
给我看看代码!
当然,但请稍安勿躁,所有内容很快就会揭晓。导航到 `App\Providers\NativeAppServiceProvider.php`。在这里,您可以看到一些为 NativePHP API 准备好的代码。在本例中,我们不会使用这段代码。请清空 `boot` 方法中的所有内容,并用以下代码替换它
<?php namespace App\Providers; use Native\Laravel\Facades\MenuBar; class NativeAppServiceProvider{ public function boot(): void { Menubar::create(); }}
由于 NativePHP 使用热重载,我们应该看到 `Window` 关闭,并且在电脑顶部出现一个 `Menubar` 图标。点击它将显示相同的默认 Laravel 首页。
不错!让我们构建一些酷炫的东西!
在幕后,我正在安装 TailwindCSS,按照他们的文档、Laravel Livewire 3(有点冒险,我知道,但这是我的首选)Blade Heroicons,然后使用以下命令添加我们的 TeamMember 模型、迁移和工厂
php artisan make:model TeamMember -mf
NOTE: I am keeping `npm run dev` running for hot reloading of the ui.
迁移
public function up(): void{ Schema::create('team_members', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('timezone'); $table->timestamps(); });}
工厂
public function definition(): array{ return [ 'name' => $this->faker->name, 'timezone' => $this->faker->randomElement(timezone_identifiers_list()) ];}
然后将我的 `App\Database\seeders\DatabaseSeeder.php` 更新为
public function run(): void{ \App\Models\TeamMember::factory(10)->create();}
并运行 `php artisan migrate` 和 `php artisan db:seed`。
NOTE: The application inside of NativePHP does NOT have access to the database defined in your `.env`. From my experience, it can be useful to seed your database locally and debug in the browser or by using Spatie/Ray.
让我们创建 Livewire 类和视图
php artisan livewire:make TeamMember/Indexphp artisan livewire:make TeamMember/Createphp artisan livewire:make TeamMember/Update
然后将我们的 `web.php` 更新为以下内容
Route::get('/', \App\Livewire\TeamMember\Index::class)->name('index');Route::get('/team-members/create', \App\Livewire\TeamMember\Create::class)->name('create');Route::get('/team-members/{teamMember}/edit', \App\Livewire\TeamMember\Update::class)->name('edit');
并在 `resources/views/components/layouts` 内创建一个 `app.blade.php`,其中包含以下 HTML 代码
<!DOCTYPE html><html lang="{{ str_replace('_', '-', app()->getLocale()) }}"><head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Laravel</title> @vite('resources/css/app.css')</head><body class="antialiased bg-gray-900 text-gray-100"><div class="max-w-md mx-auto px-4 py-6"> {{$slot}}</div></body></html>
列出我们的队友
在 `App\Livewire\TeamMember\Index` 类中,我们需要获取所有团队成员以显示它们。此外,我们应该提供一个创建新团队成员的链接,并为现有团队成员提供更新和删除按钮。
类
<?php namespace App\Livewire\TeamMember; use App\Models\TeamMember;use Livewire\Component; class Index extends Component{ public function deleteMember(TeamMember $member) { $member->delete(); } public function render() { $team = TeamMember::get(); return view('livewire.team-member.index', compact('team')); }}
视图
<div> <div class="flex items-center justify-between mb-10"> <h1 class="text-xl font-bold">My Team</h1> <a href="{{route('create')}}" type="button" class="rounded-full bg-pink-600 px-2 py-1 text-xs font-bold text-white shadow hover:bg-pink-500">Add Team Mate</a> </div> <div wire:poll> @foreach($team as $member) <div wire:key="{{ $member->id }}" class="my-2 flex items-center justify-between"> <div> <p class="text-xs font-bold text-sky-500">{{$member->name}}</p> <p class="text-lg">{{now()->tz($member->timezone)->format('h:i:s A')}} <span class="text-xs text-gray-500">- {{$member->timezone}}</span></p> </div> <div class="flex items-center"> <a href="{{route('edit', ['team-member' => $member])}}"> <span class="sr-only">Edit</span> <x-heroicon-m-pencil class="w-5 h-5 mr-3 hover:text-pink-500 transition-all duration-300" /> </a> <button wire:click="deleteMember({{$member}})"> <x-heroicon-m-trash class="w-5 h-5 mr-3 hover:text-red-600 transition-all duration-300" /> </button> </div> </div> @endforeach </div></div>
如果您已在本地播种数据库,那么在浏览器中预览应该看起来像这样
在本机应用程序中,它应该看起来像这样,因为我们还没有任何数据(确保运行 `npm run build`,然后运行 `php artisan native:serve`)。NativePHP 在后台使用本地 SQLite 数据库,我们不需要为它进行任何额外的设置或配置。
现在让我们处理 `Create` 操作,这样我们也可以在本机应用程序中看到它。
类
<?php namespace App\Livewire\TeamMember; use App\Models\TeamMember;use Livewire\Attributes\Rule;use Livewire\Component; class Create extends Component{ #[Rule(['required', 'string', 'min:3'])] public string $name; #[Rule(['required', 'string', 'min:3'])] public string $timezone; public function createMember() { TeamMember::create($this->validate()); $this->redirectRoute('index'); } public function render() { return view('livewire.team-member.create'); }}
视图
<div> <div class="flex items-center justify-between mb-10"> <h1 class="text-xl font-bold">Add Team Member</h1> <a href="{{route('index')}}" type="button" class="rounded-full bg-pink-600 px-2 py-1 text-xs font-bold text-white shadow hover:bg-pink-500 flex items-center"> Go Back </a> </div> <form wire:submit="createMember"> <div> <label for="name" class="block text-sm font-medium leading-6 text-gray-100">What is your team member's name?</label> <div class="mt-2"> <input type="text" wire:model="name" id="name" class="block w-full rounded-md border-0 py-1.5 text-gray-400 shadow-sm bg-gray-800 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-pink-600 sm:text-sm sm:leading-6" placeholder="Sarthak"> @error('name') <div class="mt-1 text-red-500 text-sm">{{ $message }}</div> @enderror </div> </div> <div class="mt-6"> <label for="timezone" class="block text-sm font-medium leading-6 text-gray-100">What is your team member's timezone</label> <select id="timezone" wire:model="timezone" class="mt-2 block w-full rounded-md border-0 py-1.5 text-gray-400 shadow-sm bg-gray-800 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-pink-600 sm:text-sm sm:leading-6"> @foreach(timezone_identifiers_list() as $timezone) <option wire:key="{{ $timezone }}">{{$timezone}}</option> @endforeach </select> @error('timezone') <div class="mt-1 text-red-500 text-sm">{{ $message }}</div> @enderror </div> <button type="submit" class="mt-6 rounded bg-pink-600 px-2 py-1 font-bold text-white shadow hover:bg-pink-500 w-full">Add Team Mate </button> </form></div>
现在我们开始着手了!但是,看来我把 Sarthak 设置到了错误的时区,让我们设置我们的 Edit 类和视图,解决这个问题。
类
<?php namespace App\Livewire\TeamMember; use App\Models\TeamMember;use Livewire\Component;use Livewire\Features\SupportValidation\Rule; class Update extends Component{ public TeamMember $teamMember; #[Rule(['required','min:3', 'string'])] public $name; #[Rule(['required','string'])] public $timezone; public function mount(TeamMember $teamMember) { $this->teamMember = $teamMember; $this->name = $teamMember->name; $this->timezone = $teamMember->timezone; } public function saveMember() { $this->teamMember->update([ 'name' => $this->name, 'timezone' => $this->timezone ]); $this->redirectRoute('index'); } public function render() { return view('livewire.team-member.update'); }}
视图
<div> <div class="flex items-center justify-between mb-10"> <h1 class="text-xl font-bold">Update Team Member</h1> <a href="{{route('index')}}" type="button" class="rounded-full bg-pink-600 px-2 py-1 text-xs font-bold text-white shadow hover:bg-pink-500 flex items-center"> Go Back </a> </div> <form wire:submit="saveMember"> <div> <label for="name" class="block text-sm font-medium leading-6 text-gray-100">Name</label> <div class="mt-2"> <input type="text" wire:model.blur="name" id="name" class="block w-full rounded-md border-0 py-1.5 text-gray-200 shadow-sm bg-gray-800 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-pink-600 sm:text-sm sm:leading-6" placeholder="Sarthak"> @error('name') <div class="mt-1 text-red-500 text-sm">{{ $message }}</div> @enderror </div> </div> <div class="mt-6"> <label for="timezone" class="block text-sm font-medium leading-6 text-gray-100">Timezone</label> <select id="timezone" wire:model="timezone" class="mt-2 block w-full rounded-md border-0 py-1.5 text-gray-200 shadow-sm bg-gray-800 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-pink-600 sm:text-sm sm:leading-6"> @foreach(timezone_identifiers_list() as $timezone) <option {{$teamMember->timezone === $timezone ? 'selected' : ''}}>{{$timezone}}</option> @endforeach </select> @error('timezone') <div class="mt-1 text-red-500 text-sm">{{ $message }}</div> @enderror </div> <button type="submit" class="mt-6 rounded bg-pink-600 px-2 py-1 font-bold text-white shadow hover:bg-pink-500 w-full">Add Team Mate </button> </form></div>
总结
现在应用程序已按预期运行并呈现,让我们在构建它之前再做几件事。首先,让我们更新菜单栏图标。我创建了 2 张图像,一张是 22x22 png,另一张是 44x44 png。通过在这些文件的名称后添加单词 `Template`,我们可以获得一些不错的功能。在 Mac 上,NativePHP 会将这些图像转换为具有透明度的白色图标,以便它与原生菜单栏的配色方案相匹配。
这两张图片名为
menuBarIconTemplate.png
通过将这些图标添加到 `storage/app` 目录,然后将我们的 `NativeAppServiceProvider` 启动方法更新为
public function boot(): void{ Menubar::create()->icon(storage_path('app/menuBarIconTemplate.png'));;}
在下次服务时,我们应该会看到菜单栏中的图标更新。
最后,让我们在 `env` 文件中添加一些条目,以告知 NativePHP 有关我们的应用程序的一些详细信息
NATIVEPHP_APP_NAME="TeamTime"NATIVEPHP_APP_VERSION="1.0.0"NATIVEPHP_APP_ID="com.teamtime.desktop"NATIVEPHP_DEEPLINK_SCHEME="teamtime"NATIVEPHP_APP_AUTHOR="Shane D Rosenthal"NATIVEPHP_UPDATER_ENABLED=false
构建您的 NativePHP 应用程序
php artisan native:build
运行此命令将打包我们本地构建应用程序所需的所有内容,并为我们提供一个本地文件(“.dmg”、“.exe”等)。完成后,这些文件将被放置在项目的 `root/dist` 目录中,您可以根据需要分发该应用程序。
截至撰写本文时,`php artisan native:build` 函数有效,但是当我本地打开“.dmg”时,它会“挂起”,并且我的菜单栏应用程序不会启动。同样,NativePHP 目前仍处于 `alpha` 状态,因此可能会出现问题,BeyondCode 团队正在努力解决此类问题,我们预计在未来几周或几个月内将实现完全功能。
总结
好吧,您怎么看?用 Laravel 构建原生应用程序真是太棒了,对吧?我可以想到很多此类功能的用例,我迫不及待地想继续探索,并看到 Laravel 迈向新的高度。在 NativePHP 文档 中还有很多其他内容,这个应用程序没有介绍或讲解,您不妨自己看看,获得灵感,构建一些很棒的东西。 #laravelforever!
我是一个技术狂,家庭成员,社区领袖,飞行员和音乐家。从 80 年代中期开始,我就一直拆卸东西来了解它们的工作原理,并试图重新组装它们,有时比以前更好。在此过程中,我遇到了我生命中的挚爱,组建了一个家庭,在领导和教育他人方面找到了目标,最近还成为了一名飞行员。我对飞行充满热情,喜欢与周围的人分享世界,并在可能的情况下观看现场金属音乐演出。
展望未来,我打算拥有自己的飞机,保持仪表等级,继续教育和指导我们的年轻人,发展我的 YouTube 频道和粉丝,并分享我的热情,以影响我所能影响的任何人。