浏览代码

优化表格排序功能,新增对关联表字段和json字段排序的支持

jqh 4 年之前
父节点
当前提交
141a64625c
共有 4 个文件被更改,包括 135 次插入43 次删除
  1. 3 2
      src/Grid/Column/HasHeader.php
  2. 2 24
      src/Grid/Model.php
  3. 128 15
      src/Repositories/EloquentRepository.php
  4. 2 2
      tests/Controllers/UserController.php

+ 3 - 2
src/Grid/Column/HasHeader.php

@@ -46,13 +46,14 @@ trait HasHeader
     /**
      * Add a column sortable to column header.
      *
+     * @param string $columnName
      * @param string $cast
      *
      * @return $this
      */
-    public function sortable($cast = null)
+    public function sortable($columnName = null, $cast = null)
     {
-        $sorter = new Sorter($this->grid, $this->getName(), $cast);
+        $sorter = new Sorter($this->grid, $columnName ?: $this->getName(), $cast);
 
         return $this->addHeader($sorter);
     }

+ 2 - 24
src/Grid/Model.php

@@ -468,28 +468,6 @@ class Model
         });
     }
 
-    /**
-     * If current page is greater than last page, then redirect to last page.
-     *
-     * @param LengthAwarePaginator $paginator
-     *
-     * @return void
-     */
-    protected function handleInvalidPage(LengthAwarePaginator $paginator)
-    {
-        if (
-            $this->usePaginate
-            && $paginator->lastPage()
-            && $paginator->currentPage() > $paginator->lastPage()
-        ) {
-            $lastPageUrl = $this->request->fullUrlWithQuery([
-                $paginator->getPageName() => $paginator->lastPage(),
-            ]);
-
-            Pjax::respond(redirect($lastPageUrl));
-        }
-    }
-
     /**
      * Get current page.
      *
@@ -579,10 +557,10 @@ class Model
         }
 
         if (empty($this->sort['column']) || empty($this->sort['type'])) {
-            return [null, null];
+            return [null, null, null];
         }
 
-        return [$this->sort['column'], $this->sort['type']];
+        return [$this->sort['column'], $this->sort['type'], $this->sort['cast'] ?? null];
     }
 
     /**

+ 128 - 15
src/Repositories/EloquentRepository.php

@@ -3,6 +3,7 @@
 namespace Dcat\Admin\Repositories;
 
 use Dcat\Admin\Contracts\TreeRepository;
+use Dcat\Admin\Exception\AdminException;
 use Dcat\Admin\Exception\RuntimeException;
 use Dcat\Admin\Form;
 use Dcat\Admin\Grid;
@@ -10,6 +11,9 @@ use Dcat\Admin\Show;
 use Illuminate\Database\Eloquent\Builder;
 use Illuminate\Database\Eloquent\Model as EloquentModel;
 use Illuminate\Database\Eloquent\Relations;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+use Illuminate\Database\Eloquent\Relations\HasOne;
+use Illuminate\Database\Eloquent\Relations\Relation;
 use Illuminate\Database\Eloquent\SoftDeletes;
 use Illuminate\Support\Arr;
 use Illuminate\Support\Collection;
@@ -181,46 +185,155 @@ class EloquentRepository extends Repository implements TreeRepository
      */
     protected function setSort(Grid\Model $model)
     {
-        [$column, $type] = $model->getSort();
+        [$column, $type, $cast] = $model->getSort();
 
         if (empty($column) || empty($type)) {
             return;
         }
 
-        if (Str::contains($column, '.')) {
-            $this->setRelationSort($model, $column, $type);
-        } else {
-            $model->resetOrderBy();
+        $model->resetOrderBy();
+
+        $explodedCols = explode('.', $column);
+        $isRelation = empty($explodedCols[1]) ? false : method_exists($this->model(), $explodedCols[0]);
+
+        if (count($explodedCols) > 1 && $isRelation) {
+            $this->setRelationSort($model, $column, $type, $cast);
+
+            return;
+        }
+
+        $this->setOrderBy(
+            $model,
+            str_replace('.', '->', $column),
+            $type,
+            $cast);
+    }
+
+    /**
+     * @param Grid\Model $model
+     * @param $column
+     * @param $type
+     *
+     * @param $cast
+     */
+    protected function setOrderBy(Grid\Model $model, $column, $type, $cast)
+    {
+        $isJsonColumn = Str::contains($column, '->');
 
+        if ($isJsonColumn) {
+            $explodedCols = explode('->', $column);
+            // json字段排序
+            $col = $this->wrapMySqlColumn(array_shift($explodedCols));
+            $parts = implode('.', $explodedCols);
+            $column = "JSON_UNQUOTE(JSON_EXTRACT({$col}, '$.{$parts}'))";
+        }
+
+        if (! empty($cast)) {
+            $column = $this->wrapMySqlColumn($column);
+
+            $model->addQuery(
+                'orderByRaw',
+                ["CAST({$column} AS {$cast}) {$type}"]
+            );
+
+            return;
+        }
+
+        if ($isJsonColumn) {
+            $model->addQuery('orderByRaw', ["{$column} {$type}"]);
+        } else {
             $model->addQuery('orderBy', [$column, $type]);
         }
     }
 
+    /**
+     * @param string $column
+     *
+     * @return string
+     */
+    protected function wrapMySqlColumn($column)
+    {
+        if (Str::contains($column, '`')) {
+            return $column;
+        }
+
+        $columns = explode('.', $column);
+
+        foreach ($columns as &$column) {
+            if (! Str::contains($column, '`')) {
+                $column = "`{$column}`";
+            }
+        }
+
+        return implode('.', $columns);
+    }
+
     /**
      * 设置关联数据排序.
      *
      * @param Grid\Model $model
-     * @param string     $column
-     * @param string     $type
+     * @param string $column
+     * @param string $type
+     * @param string $cast
      *
-     * @return void
+     * @throws \Exception
      */
-    protected function setRelationSort(Grid\Model $model, $column, $type)
+    protected function setRelationSort(Grid\Model $model, $column, $type, $cast)
     {
-        [$relationName, $relationColumn] = explode('.', $column);
+        [$relationName, $relationColumn] = explode('.', $column, 2);
 
         if ($model->getQueries()->contains(function ($query) use ($relationName) {
             return $query['method'] == 'with' && in_array($relationName, $query['arguments']);
         })) {
-            $model->addQuery('select', [$this->getGridColumns()]);
+            $relation = $this->model()->$relationName();
+
+            $model->addQuery('select', [$this->model()->getTable().'.*']);
 
-            $model->resetOrderBy();
+            $model->addQuery('join', $this->joinParameters($relation));
 
-            $model->addQuery('orderBy', [
-                $relationColumn,
+            $this->setOrderBy(
+                $model,
+                $relation->getRelated()->getTable().'.'.str_replace('.', '->', $relationColumn),
                 $type,
-            ]);
+                $cast
+            );
+        }
+    }
+
+    /**
+     * 关联模型 join 连接查询.
+     *
+     * @param Relation $relation
+     *
+     * @throws \Exception
+     *
+     * @return array
+     */
+    protected function joinParameters(Relation $relation)
+    {
+        $relatedTable = $relation->getRelated()->getTable();
+
+        if ($relation instanceof BelongsTo) {
+            $foreignKeyMethod = version_compare(app()->version(), '5.8.0', '<') ? 'getForeignKey' : 'getForeignKeyName';
+
+            return [
+                $relatedTable,
+                $relation->{$foreignKeyMethod}(),
+                '=',
+                $relatedTable.'.'.$relation->getRelated()->getKeyName(),
+            ];
         }
+
+        if ($relation instanceof HasOne) {
+            return [
+                $relatedTable,
+                $relation->getQualifiedParentKeyName(),
+                '=',
+                $relation->getQualifiedForeignKeyName(),
+            ];
+        }
+
+        throw new AdminException('Related sortable only support `HasOne` and `BelongsTo` relation.');
     }
 
     /**

+ 2 - 2
tests/Controllers/UserController.php

@@ -92,7 +92,7 @@ class UserController extends AdminController
         $grid->avatar()->display(function ($avatar) {
             return "<img src='{$avatar}' />";
         });
-        $grid->column('profile.postcode', 'Post code');
+        $grid->column('profile.postcode', 'Post code')->sortable('SIGNED');
         $grid->column('profile.address');
         $grid->column('profile.color');
         $grid->column('profile.start_at', '开始时间');
@@ -123,7 +123,7 @@ class UserController extends AdminController
             $filter->equal('id');
             $filter->like('username');
             $filter->like('email');
-            $filter->equal('profile.postcode')->select('api/placard-classify');
+            $filter->equal('profile.postcode');
             $filter->between('profile.start_at')->datetime();
             $filter->between('profile.end_at')->datetime();
         });