导航

Overview

简介

默认情况下,Filament 将为每个资源自定义页面Cluster 注册导航项目。这些类都包含一些静态属性和方法,你可以重写配置导航项目。

如果你想在应道的导航中添加第二层导航,可以使用 Cluster。这对于将资源和页面分组到一起很有用。

自定义导航项标签

默认情况下,导航标签是由资源或者页面名称生成。你可以使用 $navigationLabel 属性自定义:

protected static ?string $navigationLabel = 'Custom Navigation Label';

此外,你也可以自定义 getNavigationLabel() 方法:

public static function getNavigationLabel(): string
{
    return 'Custom Navigation Label';
}

自定义导航项图标

要自定义导航项的图标,你可以重写资源页面类的 $navigationIcon 属性:

use BackedEnum;
use Filament\Support\Icons\Heroicon;

protected static string | BackedEnum | null $navigationIcon = 'heroicon-o-document-text';
Changed navigation item icon

如果你将同一导航分组内的所有项目都设置 $navigationIcon = null,则这些项目将通过分组标签下方的垂直条连接起来。

当导航项激活时切换图标

通过 $activeNavigationIcon 属性,你指定一个仅用于导航项激活时的图标

use BackedEnum;
use Filament\Support\Icons\Heroicon;

protected static string | BackedEnum | null $activeNavigationIcon = Heroicon::OutlinedDocumentText;
Different navigation item icon when active

导航项排序

默认情况下,导航项按照字母顺序排序。你可以使用 $navigationSort 属性自定义排序:

protected static ?int $navigationSort = 3;

排序值较低的导航项会排在排序值高的项目之前,排序顺序为升序。

Sort navigation items

添加徽章到导航项中

要在导航项旁添加徽章,你可以使用 getNavigationBadge() 方法并返回徽章内容:

public static function getNavigationBadge(): ?string
{
    return static::getModel()::count();
}
Navigation item with badge

如果 getNavigationBadge() 返回徽章值,则默认情况下将使用 primary 颜色显示。要根据上下文设置徽章样式,请在 getNavigationBadgeColor() 方法返回 dangergrayinfoprimary successwarning

public static function getNavigationBadgeColor(): ?string
{
    return static::getModel()::count() > 10 ? 'warning' : 'primary';
}
Navigation item with badge color

导航徽章的自定义 tooltip 提示可以在 $navigationBadgeTooltip 中进行设置:

protected static ?string $navigationBadgeTooltip = 'The number of users';

或者也可以由 getNavigationBadgeTooltip() 返回:

public static function getNavigationBadgeTooltip(): ?string
{
    return 'The number of users';
}
Navigation item with badge tooltip

导航项分组

通过指定资源自定义页面中的 $navigationGroup 属性,你可以对导航项进行分组:

use UnitEnum;

protected static string | UnitEnum | null $navigationGroup = 'Settings';
Grouped navigation items

处于同一个分组的项目将会展示在同一个分组标签之下,比如上例中的 Settings。未分组项将保留在导航的起始位置。.

在其他项目之下分组导航项

You may group navigation items as children of other items by setting the $navigationParentItem property. You may reference the parent item either by its page or resource class, or by its label:

use App\Filament\Resources\Notifications\NotificationResource;
use UnitEnum;

protected static ?string $navigationParentItem = NotificationResource::class;

protected static string | UnitEnum | null $navigationGroup = 'Settings';

Alternatively, you may reference the parent by its label:

use UnitEnum;

protected static ?string $navigationParentItem = 'Notifications';

protected static string | UnitEnum | null $navigationGroup = 'Settings';

You may also use the getNavigationParentItem() method to determine the parent dynamically:

use App\Filament\Resources\Notifications\NotificationResource;

public static function getNavigationParentItem(): ?string
{
    return NotificationResource::class;
}

Alternatively, you may return the parent’s label:

public static function getNavigationParentItem(): ?string
{
    return __('filament/navigation.groups.settings.items.notifications');
}

父项与子项同属同一个导航分组。如果父项有导航分组,子项也必须定义导航分组,否则父项无法正确识别。无论你使用父类还是标签进行引用,这都适用。

TIP

如果你需要类似的第三级导航分组,则可以考虑 Cluster,它是资源和自定义页面的逻辑分组,可以共享自己的单独导航。

自定义导航分组

配置中调用 navigationGroups() 并按照顺序传入 NavigationGroup 对象,你可以自定义导航分组:

use Filament\Navigation\NavigationGroup;
use Filament\Panel;
use Filament\Support\Icons\Heroicon;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->navigationGroups([
            NavigationGroup::make()
                 ->label('Shop')
                 ->icon(Heroicon::OutlinedShoppingCart),
            NavigationGroup::make()
                ->label('Blog')
                ->icon(Heroicon::OutlinedPencil),
            NavigationGroup::make()
                ->label(fn (): string => __('navigation.settings'))
                ->icon(Heroicon::OutlinedCog6Tooth)
                ->collapsed(),
        ]);
}

本例中,我们为分组传入了自定义的 icon(),并让其默认折叠 collapsed()

排序导航分组

使用 navigationGroups(),你可以为导航分组定义新排序。如果你想要重新排序分组,而不是定义所有 NavigationGroup 对象,你可以以新的排序传入分组标签:

$panel
    ->navigationGroups([
        'Shop',
        'Blog',
        'Settings',
    ])

让导航分组不可折叠

默认情况下,导航分组是可折叠的。

Collapsible navigation groups

通过在 NavigationGroup 对象调用 collapsible(false),你可以禁用该行为:

use Filament\Navigation\NavigationGroup;
use Filament\Support\Icons\Heroicon;

NavigationGroup::make()
    ->label('Settings')
    ->icon(Heroicon::OutlinedCog6Tooth)
    ->collapsible(false);
Not collapsible navigation groups

或者,你可以在配置全局禁用可折叠,使之适用所有的分组:

use Filament\Panel;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->collapsibleNavigationGroups(false);
}

向导航分组添加额外的 HTML 属性

你可以将其他 HTML 属性传递给导航分组,它将合并到外层 DOM 元素中。到将属性数组传入到 extraSidebarAttributes()extraTopbarAttributes() 方法,其中键名为属性名,值作为属性值:

NavigationGroup::make()
    ->extraSidebarAttributes(['class' => 'featured-sidebar-group']),
    ->extraTopbarAttributes(['class' => 'featured-topbar-group']),

extraSidebarAttributes() 将会应用到侧边栏包含的导航分组元素中,extraTopbarAttributes() 将只应用到当使用顶部导航时的顶部导航分组下拉菜单。

使用枚举注册导航分组

你可以使用枚举类来注册导航分组,这将允许你在同一个位置中控制分组标签、图标以及排序,而无需到配置中进行注册。

为此,你需要使用每个分组作为 case 注册一个枚举类:

enum NavigationGroup
{
    case Shop;
    
    case Blog;
    
    case Settings;
}

枚举中定义的 case 排序将会控制导航分组的排序。

要未资源或者自定义类设置枚举导航分组,可以将 $navigationGroup 设置为枚举的 case:

protected static string | UnitEnum | null $navigationGroup = NavigationGroup::Shop;

你也可以在枚举类中实现 HasLabel 接口,为每个分组自定义标签:

use Filament\Support\Contracts\HasLabel;

enum NavigationGroup implements HasLabel
{
    case Shop;
    
    case Blog;
    
    case Settings;

    public function getLabel(): string
    {
        return match ($this) {
            self::Shop => __('navigation-groups.shop'),
            self::Blog => __('navigation-groups.blog'),
            self::Settings => __('navigation-groups.settings'),
        };
    }
}

你也可以在枚举类中实现 HasIcon 接口,为每个分组自定义图标:

use BackedEnum;
use Filament\Support\Contracts\HasIcon;
use Filament\Support\Icons\Heroicon;
use Illuminate\Contracts\Support\Htmlable;

enum NavigationGroup implements HasIcon
{
    case Shop;
    
    case Blog;
    
    case Settings;

    public function getIcon(): ?string
    {
        return match ($this) {
            self::Shop => Heroicon::OutlinedShoppingCart,
            self::Blog => Heroicon::OutlinedPencil,
            self::Settings => Heroicon::OutlinedCog6Tooth,
        };
    }
}

桌面端可折叠侧边栏

要让侧边栏在移动端和桌面端可折叠,你可以通过面板配置实现:

use Filament\Panel;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->sidebarCollapsibleOnDesktop();
}
Collapsible sidebar on desktop

默认情况下,在桌面端折叠侧边栏时,导航图标仍会显示。你可以使用 sidebarFullyCollapsibleOnDesktop() 方法完全折叠侧边栏:

use Filament\Panel;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->sidebarFullyCollapsibleOnDesktop();
}
Fully collapsible sidebar on desktop

桌面端可折叠侧边栏导航分组

NOTE

本章节仅适用于 sidebarCollapsibleOnDesktop(),不包括 sidebarFullyCollapsibleOnDesktop()。因为可全折叠 UI 会隐藏整个侧边栏而不只是修改其外观。

当在桌面端使用可折叠侧边栏时,你常常也会使用导航分组。默认情况下,每个导航分组的标签会在侧边栏折叠后隐藏,因为没有空间展示。即使导航分组是可折叠的,所有项目在折叠的侧边栏中仍然可见,因为没有分组标签可用于展开分组。

这个情况可以通过传入一个 icon() 到导航分组对象中来解决。该图标(而非导航项)将会始终显示在折叠后的侧边栏中。当点击该图标时,会在图标旁边打开下拉菜单,显示分组中的项目。

当传入图标到导航分组中时,即使这些项目有图标,展开的侧边栏 UI 也不会显示项目图标。这是为了使导航层次分明,以及设计最小化。不过,项目图标会显示在折叠后的侧边栏下拉菜单中,因为下拉菜单的打开已经使得层次事实上清晰明了了。

Collapsible sidebar with navigation group icons

注册自定义导航项

要注册心的导航项,你可以使用面板配置

use Filament\Navigation\NavigationItem;
use Filament\Pages\Dashboard;
use Filament\Panel;
use Filament\Support\Icons\Heroicon;
use function Filament\Support\original_request;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->navigationItems([
            NavigationItem::make('Analytics')
                ->url('https://filament.pirsch.io', shouldOpenInNewTab: true)
                ->icon(Heroicon::OutlinedPresentationChartLine)
                ->group('Reports')
                ->sort(3),
            NavigationItem::make('dashboard')
                ->label(fn (): string => __('filament-panels::pages/dashboard.title'))
                ->url(fn (): string => Dashboard::getUrl())
                ->isActiveWhen(fn () => original_request()->routeIs('filament.admin.pages.dashboard')),
            // ...
        ]);
}

根据情况隐藏导航项

使用 visible()hidden() 方法,传入要检查的条件,你可以根据情况隐藏导航项:

use Filament\Navigation\NavigationItem;

NavigationItem::make('Analytics')
    ->visible(fn(): bool => auth()->user()->can('view-analytics'))
    // or
    ->hidden(fn(): bool => ! auth()->user()->can('view-analytics')),

禁用资源或者页面导航项

要阻止资源或者页面在导航中显示,你可以使用:

protected static bool $shouldRegisterNavigation = false;

或者,你也可以重写 shouldRegisterNavigation() 方法:

public static function shouldRegisterNavigation(): bool
{
    return false;
}

NOTE

shouldRegisterNavigation() 只是在侧边栏隐藏链接,它不会阻止用户直接输入 URL 访问。如果你也想控制访问权限,请使用资源授权或者页面授权

使用顶部导航

默认情况下,Filament 使用侧边栏导航。你可以在配置中将其转换成使用顶部导航:

use Filament\Panel;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->topNavigation();
}
Top navigation

自定义侧边栏宽度

你可以在配置中将宽度传入到 sidebarWidth() 方法中,自定义侧边栏的宽度:

use Filament\Panel;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->sidebarWidth('40rem');
}
Panel with custom sidebar width

此外,如果使用了 sidebarCollapsibleOnDesktop() 方法,你可以在配置中使用 collapsedSidebarWidth() 方法自定义折叠后图标的宽度:

use Filament\Panel;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->sidebarCollapsibleOnDesktop()
        ->collapsedSidebarWidth('9rem');
}

高级导航自定义

navigation() 方法可以在配置中调用。它允许你构建自定义导航,以覆盖 Filament 自动生成的导航项。此 API 旨在让你完全控制导航。

注册自定义导航项

要注册导航项,请调用 items() 方法:

use App\Filament\Pages\Settings;
use App\Filament\Resources\Users\UserResource;
use Filament\Navigation\NavigationBuilder;
use Filament\Navigation\NavigationItem;
use Filament\Pages\Dashboard;
use Filament\Panel;
use Filament\Support\Icons\Heroicon;
use function Filament\Support\original_request;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->navigation(function (NavigationBuilder $builder): NavigationBuilder {
            return $builder->items([
                NavigationItem::make('Dashboard')
                    ->icon(Heroicon::OutlinedHome)
                    ->isActiveWhen(fn (): bool => original_request()->routeIs('filament.admin.pages.dashboard'))
                    ->url(fn (): string => Dashboard::getUrl()),
                ...UserResource::getNavigationItems(),
                ...Settings::getNavigationItems(),
            ]);
        });
}
Custom navigation items

注册自定义导航分组

如果你项注册分组,你可以调用 groups() 方法:

use App\Filament\Pages\HomePageSettings;
use App\Filament\Resources\Categories\CategoryResource;
use App\Filament\Resources\Pages\PageResource;
use Filament\Navigation\NavigationBuilder;
use Filament\Navigation\NavigationGroup;
use Filament\Panel;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->navigation(function (NavigationBuilder $builder): NavigationBuilder {
            return $builder->groups([
                NavigationGroup::make('Website')
                    ->items([
                        ...PageResource::getNavigationItems(),
                        ...CategoryResource::getNavigationItems(),
                        ...HomePageSettings::getNavigationItems(),
                    ]),
            ]);
        });
}

禁用导航

通过将 false 传递给 navigation() 方法,你可以完全禁用导航栏:

use Filament\Panel;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->navigation(false);
}
Disabled navigation sidebar

Alternatively, you may pass a closure that returns a boolean to decide dynamically. Returning false hides the navigation, while returning true renders the default auto-discovered navigation items. This is useful for flows such as onboarding or setup wizards where the navigation should only appear once the user has reached a particular state:

use Filament\Panel;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->navigation(fn (): bool => auth()->user()->hasCompletedOnboarding());
}

禁用顶部栏

通过将 false 传递给 topbar() 方法,你可以完全禁用顶部栏:

use Filament\Panel;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->topbar(false);
}

Replacing the sidebar and topbar Livewire components

You may completely replace the Livewire components that are used to render the sidebar and topbar, passing your own Livewire component class name into the sidebarLivewireComponent() or topbarLivewireComponent() method:

use App\Livewire\Sidebar;
use App\Livewire\Topbar;
use Filament\Panel;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->sidebarLivewireComponent(Sidebar::class)
        ->topbarLivewireComponent(Topbar::class);
}

禁用面包屑

默认布局将显示面包屑导航,以指示当前页面在应用层次结构中的位置。

你可以在配置中禁用面包屑导航:

use Filament\Panel;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->breadcrumbs(false);
}

重新加载侧边栏和顶部导航栏

当面板中的页面被加载时,侧边栏和顶部导航栏都不会重载,除非离开该页面或者点击菜单项去触发 Action。通过派发 refresh-sidebarrefresh-topbar 浏览器事件,你可以手动重载这些组件。

要使用 PHP 派发事件,你可以在任何 Livewire 组件(比如页面类,关联管理器类或者 Widget 类中)中调用 $this->dispatch() 方法:

$this->dispatch('refresh-sidebar');

如果代码不在某个 Livewire 组件中,比如在你自定义的 Action 类中, 你可以注入 $livewire 参数到闭包函数中,并在其上调用 dispatch()

use Filament\Actions\Action;
use Livewire\Component;

Action::make('create')
    ->action(function (Component $livewire) {
        // ...
    
        $livewire->dispatch('refresh-sidebar');
    })

此外,使用 $dispatch() Alpine.js 辅助方法,或者浏览器原生的 window.dispatchEvent() 方法,你可以使用 JavaScript 派发事件,

<button x-on:click="$dispatch('refresh-sidebar')" type="button">
    Refresh Sidebar
</button>
window.dispatchEvent(new CustomEvent('refresh-sidebar'));
Edit on GitHub

Still need help? Join our Discord community or open a GitHub discussion