返回

Laravel Nova 首页隐藏编辑删除按钮,详情页保留

php

Laravel Nova 首页隐藏编辑和删除按钮,详情页保留

有些时候,咱需要调整 Laravel Nova 资源列表(Index)页面的操作按钮。比如,想隐藏“编辑”和“删除”按钮,但在资源详情(Detail)页面又希望保留它们。直接用 Policy 可能会把所有地方的操作都禁掉,这不行!咱得想个更灵活的招儿。

问题:一刀切的 Policy 不好使

用 Policy 来控制权限,默认情况下,会影响到所有页面(Index、Detail 等)。假如在 Policy 里直接禁止 updatedelete,那在详情页也没法编辑或删除了,这不符合需求。

// 比如这样写 Policy,就会把所有地方的更新操作都禁止掉
public function update(User $user, Customer $customer)
{
    return false;
}

解决方案:多管齐下

有好几种方法可以解决这个问题,每种方法都有自己的特点。咱一个个来看:

1. 利用请求路由名称 (有点“野”,但不失为一种办法)

直接在 Policy 的 updatedelete 方法里判断当前的路由名称。如果是 Nova 资源列表页的路由,就返回 false,禁止操作;否则,允许操作。

// 在你的 Policy 文件里 (比如 CustomerPolicy.php)

public function update(User $user, Customer $customer)
{
    if (request()->routeIs('nova.pages.index')) { //或者检查您资源列表页自定义的特定路由。
        return false;
    }
    return true; // 默认允许
}

public function delete(User $user, Customer $customer)
{
     if (request()->routeIs('nova.pages.index')) {
        return false;
    }
    return true; // 默认允许.
}

原理: Nova 内部在处理请求时,会根据当前的页面(Index、Detail 等)设置不同的路由名称。通过判断路由名称,就能区分出当前是在哪个页面。

优点: 简单粗暴,代码改动小。

缺点: 依赖于 Nova 内部的路由名称,如果 Nova 将来更新了路由命名方式(虽然可能性不大),这招就可能失效了。

额外说明: 可以通过自定义Nova 资源列表路由,使用 ->withIndexQuery,在 indexQuery 做条件判断。这种方法其实类似。

2. 自定义 Action 按钮(推荐,更优雅)

Nova 允许咱自定义 Action 按钮。可以创建两个自定义的 Action:一个用于编辑,一个用于删除。然后在 index 查询作用域修改默认行为.

步骤:

  1. 创建自定义 Action:

    php artisan nova:action EditCustomer
    php artisan nova:action DeleteCustomer
    
  2. 修改 EditCustomer Action (app/Nova/Actions/EditCustomer.php):

    <?php
    
    namespace App\Nova\Actions;
    
    use Illuminate\Bus\Queueable;
    use Illuminate\Contracts\Queue\ShouldQueue;
    use Illuminate\Queue\InteractsWithQueue;
    use Illuminate\Support\Collection;
    use Laravel\Nova\Actions\Action;
    use Laravel\Nova\Fields\ActionFields;
    use Laravel\Nova\Http\Requests\NovaRequest; // 导入这个
    
    class EditCustomer extends Action
    {
        use InteractsWithQueue, Queueable;
    
        /**
         * 在资源列表页隐藏
         * @param  \Laravel\Nova\Http\Requests\NovaRequest  $request
         * @param  \Illuminate\Support\Collection  $models
         *
         * @return bool
         */
        public function authorizedToRun(NovaRequest $request, $model) //使用参数$model
        {
              if( is_null($model) || $request->viaResource() ){
                   return false; // 首页,批量编辑不可见
              }
    
              return  $request->user()->can('update', $model);
        }
        /**
         * Perform the action on the given models.
         *
         * @param  \Laravel\Nova\Fields\ActionFields  $fields
         * @param  \Illuminate\Support\Collection  $models
         * @return mixed
         */
        public function handle(ActionFields $fields, Collection $models)
        {
             $customer = $models->first();
               //注意 nova action handle 默认就是 循环处理选择,
             //如果只是要实现编辑功能,通常选择数量是 1,取 $models 集合中的第一个元素进行后续操作即可。
    
             // 这里可以写编辑逻辑(如果需要的话,比如跳转到自定义的编辑页面)
             // 直接使用 redirect。
             // 通过 nova.path 获取基础配置,resource 资源, $model 模型。
             //return Action::message('编辑成功!'); // 如果不需要跳转,可以显示个消息
    
               return Action::redirect(config('nova.path')."/resources/customers/{$customer->id}/edit");
    
        }
    
        /**
         * Get the fields available on the action.
         *
         * @param  \Laravel\Nova\Http\Requests\NovaRequest  $request
         * @return array
         */
        public function fields(NovaRequest $request)
        {
            return [];
        }
    }
    
    
  3. 修改 DeleteCustomer Action (app/Nova/Actions/DeleteCustomer.php):

       <?php
    
       namespace App\Nova\Actions;
    
       use Illuminate\Bus\Queueable;
       use Illuminate\Contracts\Queue\ShouldQueue;
       use Illuminate\Queue\InteractsWithQueue;
       use Illuminate\Support\Collection;
       use Laravel\Nova\Actions\Action;
       use Laravel\Nova\Fields\ActionFields;
       use Laravel\Nova\Http\Requests\NovaRequest;
       class DeleteCustomer extends Action
       {
           use InteractsWithQueue, Queueable;
    
           /**
            * 只在详情页显示,
            * index 或 批量删除时不显示
            * @param NovaRequest $request
            * @param $model
            * @return bool|void
            */
           public function authorizedToRun(NovaRequest $request, $model)
           {
                 if( is_null($model) || $request->viaResource()  ){ //来自 index
                      return false;
                 }
                 return $request->user()->can('delete', $model);
           }
    
           /**
            * Perform the action on the given models.
            *
            * @param  \Laravel\Nova\Fields\ActionFields  $fields
            * @param  \Illuminate\Support\Collection  $models
            * @return mixed
            */
           public function handle(ActionFields $fields, Collection $models)
           {
    
               foreach ($models as $model) {
                      $model->delete(); //执行真正的删除,或其他符合你需要的删除处理
                  }
               return Action::message('删除成功!');
           }
    
           /**
            * Get the fields available on the action.
            *
            * @param  \Laravel\Nova\Http\Requests\NovaRequest  $request
            * @return array
            */
           public function fields(NovaRequest $request)
           {
               return [];
           }
       }
    
    
  4. 修改 Resource的 actions 方法(例如 app/Nova/Customer.php) :

public function actions(NovaRequest $request)
 {
    return [
        new Actions\EditCustomer,
         new Actions\DeleteCustomer,
    ];
 }

原理:

  • authorizedToRun 方法用于控制 Action 是否显示。利用 NovaRequest 对象,可以判断当前请求是否来自资源列表页($request->viaResource()),以此决定是否隐藏 Action。
  • 当单个资源模型,传入authorizedToRun,第二个参数 $model不为 null. 而首页是多个或者说不确定, 故 $model 总是为null。

优点:

  • 更优雅、更符合 Nova 的设计思想。
  • 不依赖于内部实现细节,更稳定。

进阶用法:
可以增加批量编辑/批量删除 的控制.
例如 $request->viaResourceId 判断是否存在. 决定是显示那个按钮.

3. CSS 障眼法(最简单粗暴,但不推荐)

直接用 CSS 把资源列表页的编辑和删除按钮隐藏掉。

/* 在你的 Nova 自定义 CSS 文件里 (比如 resources/css/nova.css) */

/* 隐藏资源列表页的编辑按钮 */
.index-table [data-testid="edit-button"] {
  display: none;
}

/* 隐藏资源列表页的删除按钮 */
.index-table [data-testid="delete-button"] {
  display: none;
}

/* 或者用更通用的选择器,如果你知道按钮的类名或属性 */
/*
.index-table .your-edit-button-class {
    display: none;
}
*/

原理: 通过 CSS 选择器找到目标元素,然后把它们隐藏。

优点: 简单到爆炸,不需要改任何 PHP 代码。

缺点:

  • 这只是视觉上的隐藏,实际上按钮还在那里,只是看不见了。如果用户懂点前端知识,或者通过其他方式(比如 API 请求),还是可以进行编辑和删除操作。
  • 不够“优雅”,有点“Hack”的味道。

安全建议: 这种方法只适用于对安全性要求不高的场景 。如果需要严格控制权限,请不要使用这种方法。 还是老老实实用上面说的方法。

4.前端组件控制(vue 层面, 相对更灵活.)

这种就需要修改对应的 vue 文件. 需要一定的 vue 开发能力.

大致思路:

找到 index 相关的组件. 在 mounted 生命周期或 computed 属性进行判断处理。

在对应的 components, 找到 Index.vue (可能会是TableIndex.vue 或 CardIndex.vue ,取决你的UI设计)。
找到按钮位置. 通过控制其 v-if 或 动态 class,进行隐藏。
添加计算属性 shouldShowEditButton or shouldShowDeleteButton 进行控制。

原理: 通过 nova 提供的组件, 进行 vue 层级的控制.

<!-- 可能是TableIndex.vue,或者 CardIndex.vue , 根据资源显示来定-->
<template>
<div>
    <button v-if="shouldShowEditButton" @click="editResource">编辑</button>
</div>
</template>

<script>
export default {
  computed:{
        shouldShowEditButton(){
           // 获取当前的 route.name 或 $route.
            // 通过 this.$route 即可. 判断.  this.$route.name==='your-detail-route-name' 之类的判断
            return this.$route.name === 'your-resource-detail-route-name'  ; //自定义的名字.
        }
  },
  methods: {
      editResource()
      {
           // ... 触发路由调整, 跳转.
      }
  }

};
</script>

优缺点:

  • 优点: 前端控制,性能更好.
  • 缺点: 需要 vue 开发. 成本相对高点. 修改不方便(至少比配置php要复杂).

建议综合利用 vue + action 双重结合。

小结

根据上面的介绍,自定义 Action (第二种)是我比较推荐的方式,比较优雅,功能可扩展性好,结合 Policy 和 Action 可控程度较高。其他的方案,根据实际情况和个人喜好选择即可. 前端组件的控制可以作为高级进阶内容, 但是整体成本较大.