Просмотр исходного кода

Merge pull request #2 from jqhph/2.0

添加:合并主版本
Ving 4 лет назад
Родитель
Сommit
6dff5cee05
92 измененных файлов с 794 добавлено и 353 удалено
  1. 2 0
      .env.testing
  2. 1 0
      .travis.yml
  3. 4 8
      README.md
  4. 5 1
      config/admin.php
  5. 2 2
      resources/assets/dcat/extra/Upload/AddFile.js
  6. 5 4
      resources/assets/dcat/js/bootstrappers/DataActions.js
  7. 25 0
      resources/assets/dcat/js/bootstrappers/Menu.js
  8. 7 1
      resources/assets/dcat/plugins/number-input/bootstrap-number-input.js
  9. 0 7
      resources/assets/dcat/plugins/vendors.min.css
  10. 0 14
      resources/assets/dcat/plugins/vendors.min.js
  11. 170 0
      resources/assets/dcat/sass/components/_horizontal-menu.scss
  12. 1 1
      resources/assets/dcat/sass/components/_layout.scss
  13. 1 0
      resources/assets/dcat/sass/dcat-app.scss
  14. 4 0
      resources/assets/dcat/sass/variables/_variables.scss
  15. 0 0
      resources/dist/dcat/css/dcat-app-blue-light.css
  16. 0 0
      resources/dist/dcat/css/dcat-app-blue.css
  17. 0 0
      resources/dist/dcat/css/dcat-app-green.css
  18. 0 0
      resources/dist/dcat/css/dcat-app.css
  19. 0 0
      resources/dist/dcat/extra/upload.js
  20. 0 0
      resources/dist/dcat/extra/upload.js.map
  21. 0 0
      resources/dist/dcat/js/dcat-app.js
  22. 0 0
      resources/dist/dcat/js/dcat-app.js.map
  23. 23 7
      resources/dist/dcat/plugins/number-input/bootstrap-number-input.js
  24. 0 7
      resources/dist/dcat/plugins/vendors.min.css
  25. 0 14
      resources/dist/dcat/plugins/vendors.min.js
  26. 1 0
      resources/lang/en/admin.php
  27. 1 0
      resources/lang/zh_CN/admin.php
  28. 1 0
      resources/lang/zh_TW/admin.php
  29. 2 2
      resources/views/form/daterange.blade.php
  30. 1 1
      resources/views/form/datetimerange.blade.php
  31. 10 1
      resources/views/form/markdown.blade.php
  32. 2 2
      resources/views/form/timerange.blade.php
  33. 24 10
      resources/views/grid/displayer/extensions/name.blade.php
  34. 17 0
      resources/views/helpers/scaffold.blade.php
  35. 46 0
      resources/views/layouts/container.blade.php
  36. 1 1
      resources/views/layouts/page.blade.php
  37. 0 46
      resources/views/layouts/vertical.blade.php
  38. 18 12
      resources/views/partials/menu.blade.php
  39. 28 26
      resources/views/partials/navbar.blade.php
  40. 9 4
      resources/views/partials/sidebar.blade.php
  41. 1 1
      src/Actions/HasActionHandler.php
  42. 22 12
      src/Admin.php
  43. 37 7
      src/Application.php
  44. 2 2
      src/Console/ExtensionMakeCommand.php
  45. 5 1
      src/Console/stubs/config.stub
  46. 1 0
      src/Console/stubs/extension/composer.json.stub
  47. 0 1
      src/Console/stubs/routes.stub
  48. 47 2
      src/Extend/ServiceProvider.php
  49. 21 4
      src/Form.php
  50. 4 3
      src/Form/Concerns/HasFieldValidator.php
  51. 1 1
      src/Form/Field.php
  52. 1 1
      src/Form/Field/Editor.php
  53. 1 1
      src/Form/Field/Markdown.php
  54. 6 6
      src/Form/Field/UploadField.php
  55. 1 1
      src/Form/Field/WebUploader.php
  56. 8 3
      src/Grid/Actions/Delete.php
  57. 4 0
      src/Grid/Actions/Edit.php
  58. 11 13
      src/Grid/Actions/QuickEdit.php
  59. 4 0
      src/Grid/Actions/Show.php
  60. 1 1
      src/Grid/BatchAction.php
  61. 4 2
      src/Grid/Column.php
  62. 0 16
      src/Grid/Concerns/CanHidesColumns.php
  63. 2 2
      src/Grid/Concerns/HasTree.php
  64. 28 20
      src/Grid/Displayers/Actions.php
  65. 15 7
      src/Grid/Filter/AbstractFilter.php
  66. 2 4
      src/Grid/Model.php
  67. 6 1
      src/Grid/Tools/BatchDelete.php
  68. 4 12
      src/Grid/Tools/ColumnSelector.php
  69. 1 1
      src/Http/Auth/Permission.php
  70. 1 1
      src/Http/Controllers/ExtensionController.php
  71. 1 2
      src/Http/Displayers/Extensions/Name.php
  72. 4 4
      src/Http/JsonResponse.php
  73. 1 1
      src/Http/Middleware/Authenticate.php
  74. 1 1
      src/Http/Middleware/Bootstrap.php
  75. 2 2
      src/Http/Middleware/Permission.php
  76. 2 6
      src/Http/Repositories/Extension.php
  77. 1 1
      src/Layout/Asset.php
  78. 15 10
      src/Layout/Content.php
  79. 17 1
      src/Layout/Row.php
  80. 1 1
      src/Models/Permission.php
  81. 26 1
      src/Repositories/EloquentRepository.php
  82. 1 1
      src/Repositories/QueryBuilderRepository.php
  83. 1 1
      src/Support/Helper.php
  84. 1 1
      src/Support/Setting.php
  85. 35 4
      src/Support/helpers.php
  86. 1 1
      src/Traits/InteractsWithApi.php
  87. 1 1
      src/Traits/LazyWidget.php
  88. 1 1
      src/Traits/ModelTree.php
  89. 6 1
      src/Tree/Actions/Delete.php
  90. 2 2
      src/Widgets/DarkModeSwitcher.php
  91. 15 9
      src/Widgets/Form.php
  92. 7 4
      src/Widgets/LazyTable.php

+ 2 - 0
.env.testing

@@ -13,6 +13,8 @@ DB_DATABASE=laravel
 DB_USERNAME=root
 DB_PASSWORD=
 
+CODECOV_TOKEN=8124bc76-384b-4528-8875-a33ee1e1ee19
+
 BROADCAST_DRIVER=log
 CACHE_DRIVER=file
 QUEUE_CONNECTION=sync

+ 1 - 0
.travis.yml

@@ -63,3 +63,4 @@ before_script:
 
 script:
   - php artisan dusk
+

+ 4 - 8
README.md

@@ -27,17 +27,13 @@
 - [Demo源码 (码云)](https://gitee.com/jqhph/dcat-admin-demo)
 - [扩展](#)
 
-### 关于官网备案暂停访问的公告
 
-大家好,[Dcat Admin官网](http://www.dcatadmin.com)正在进行备案中,在备案期间会关闭网站访问,直至备案完成。在此期间大家可以通过[Learnku中文文档](https://learnku.com/docs/dcat-admin/2.x)查看文档,给大家带来的不便之处敬请谅解,谢谢支持!
+![](https://cdn.learnku.com/uploads/images/202101/28/38389/YLmL7PLqH7.png!large)
 
-### 截图
 
-![](https://cdn.learnku.com/uploads/images/202008/23/38389/Oam6CYOobf.jpeg!large)
-![](https://cdn.learnku.com/uploads/images/202007/24/38389/35KJXfVXib.png!large)
-![](https://cdn.learnku.com/uploads/images/202008/23/38389/Lu7LZDSX0M.jpg!large)
-![](https://cdn.learnku.com/uploads/images/202004/24/38389/GBkt9jYnW0.png!large)
-![](https://cdn.learnku.com/uploads/images/202008/08/38389/lGYIdhifb5.jpg!large)
+### 关于官网备案暂停访问的公告
+
+大家好,[Dcat Admin官网](http://www.dcatadmin.com)正在进行备案中,在备案期间会关闭网站访问,直至备案完成。在此期间大家可以通过[Learnku中文文档](https://learnku.com/docs/dcat-admin/2.x)查看文档,给大家带来的不便之处敬请谅解,谢谢支持!
 
 
 ### 功能特性

+ 5 - 1
config/admin.php

@@ -211,6 +211,7 @@ return [
         // Whether enable menu bind to a permission.
         'bind_permission' => true,
 
+        'default_icon' => 'feather icon-circle',
     ],
 
     /*
@@ -284,7 +285,10 @@ return [
         // default, blue, blue-light, green
         'color' => 'default',
 
-        'body_class' => '',
+        // sidebar-separate
+        'body_class' => [],
+
+        'horizontal_menu' => false,
 
         'sidebar_collapsed' => false,
 

+ 2 - 2
resources/assets/dcat/extra/Upload/AddFile.js

@@ -219,11 +219,11 @@ export default class AddFile {
                     }
 
                     // 删除请求
-                    uploader.request.delete(file, function () {
+                    parent.request.delete(file, function () {
                         // 删除成功回调
                         parent.input.delete(file.serverId);
 
-                        uploader.uploader.removeFile(file);
+                        uploader.removeFile(file);
                     });
 
                     break;

+ 5 - 4
resources/assets/dcat/js/bootstrappers/DataActions.js

@@ -27,8 +27,8 @@ let defaultActions = {
 
                         response.data.detail = msg;
 
-                        if (! response.then) {
-                            response.then = {action: 'redirect', value: redirect}
+                        if (redirect && ! response.data.then) {
+                            response.data.then = {action: 'redirect', value: redirect}
                         }
 
                         Dcat.handleJsonResponse(response);
@@ -42,6 +42,7 @@ let defaultActions = {
         $document.on('click', action, function() {
             let url = $(this).data('url'),
                 name = $(this).data('name'),
+                redirect = $(this).data('redirect'),
                 keys = Dcat.grid.selected(name),
                 lang = Dcat.lang;
 
@@ -57,8 +58,8 @@ let defaultActions = {
                     success: function (response) {
                         Dcat.NP.done();
 
-                        if (! response.then) {
-                            response.then = {action: 'refresh', value: true}
+                        if (redirect && ! response.data.then) {
+                            response.data.then = {action: 'redirect', value: redirect}
                         }
 
                         Dcat.handleJsonResponse(response);

+ 25 - 0
resources/assets/dcat/js/bootstrappers/Menu.js

@@ -2,6 +2,7 @@
 export default class Menu {
     constructor(Dcat) {
         this.init();
+        this.initHorizontal();
     }
 
     // 菜单点击选中效果
@@ -29,4 +30,28 @@ export default class Menu {
             $(this).addClass('active')
         });
     }
+
+    initHorizontal() {
+        let selectors = {
+            item: '.horizontal-menu .main-menu-content li.nav-item',
+            link: '.horizontal-menu .main-menu-content li.nav-item .nav-link',
+        };
+
+        $(selectors.item).on('mouseover', function () {
+            $(this).addClass('open')
+        }).on('mouseout', function () {
+            $(this).removeClass('open')
+        });
+
+        $(selectors.link).on('click', function () {
+            let $this = $(this);
+
+            $(selectors.link).removeClass('active');
+
+            $this.addClass('active');
+
+            $this.parents('.dropdown').find('.nav-link').eq(0).addClass('active');
+            $this.parents('.dropdown-submenu').find('.nav-link').eq(0).addClass('active')
+        });
+    }
 }

+ 7 - 1
resources/assets/dcat/plugins/number-input/bootstrap-number-input.js

@@ -52,6 +52,7 @@
                 }
 
                 clone.focus().val(n);
+                clone.trigger('change');
                 return true;
             }
 
@@ -89,8 +90,13 @@
             });
 
             clone.prop('type', 'text').blur(function (e) {
-                var c = String.fromCharCode(e.which);
+                var c = parseInt(String.fromCharCode(e.which));
+                if (isNaN(c)) {
+                    c = 0;
+                }
+
                 var n = getVal() + c;
+
                 if ((min && n < min)) {
                     setText(min);
                 }

Разница между файлами не показана из-за своего большого размера
+ 0 - 7
resources/assets/dcat/plugins/vendors.min.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 14
resources/assets/dcat/plugins/vendors.min.js


+ 170 - 0
resources/assets/dcat/sass/components/_horizontal-menu.scss

@@ -0,0 +1,170 @@
+
+.horizontal-menu {
+  .navbar-fixed-top .content-wrapper {
+    padding-top: 6.1rem;
+  }
+
+  .header-navbar.navbar-shadow {
+    box-shadow: none;
+    border-bottom: 1px solid $border-color;
+  }
+
+  .content-wrapper, .main-footer, .main-header {
+    margin-left: 0!important;
+  }
+
+  .header-navbar {
+    position: -webkit-sticky;
+    position: sticky;
+    top: 0;
+  }
+
+  .header-navbar.navbar-horizontal {
+    top: $navbar-horizontal-top;
+    position: fixed;
+  }
+
+  .main-horizontal-sidebar {
+    top: $navbar-horizontal-top;
+    left: 0;
+    position: fixed;
+    width: 100%;
+    height: inherit;
+    min-height: auto;
+    bottom: inherit;
+    box-shadow: none;
+    //padding: 0 $content-wrapper-padding;
+  }
+
+  .main-horizontal-sidebar > div {
+    box-shadow: $shadow;
+    background: $white;
+    //border-radius: .4rem;
+  }
+
+  [class*=sidebar-dark-] .nav-sidebar > .nav-item.menu-open > .nav-link,
+  [class*=sidebar-dark-] .nav-sidebar > .nav-item:hover > .nav-link,
+  [class*=sidebar-dark-] .nav-sidebar > .nav-item > .nav-link:focus,
+  [class*=sidebar-dark-] .nav-sidebar > .nav-item i {
+    color: $font-color;
+  }
+
+  .nav-sidebar > .nav-item {
+    padding-bottom: .6rem;
+
+    .nav-link {
+      color: $font-color;
+      font-size: 1rem;
+      padding: 0.45rem .7rem;
+      transition: all .35s ease!important;
+    }
+
+    .nav-link.dropdown-toggle::after {
+      margin-top: 0!important;
+    }
+
+    .nav-link.active i,
+    .nav-link.active {
+      color: $primary!important;
+      font-weight: 600;
+    }
+  }
+
+  .dropdown-menu {
+    .nav-item:hover > .nav-link {
+      padding-left: 1rem;
+    }
+  }
+
+  .nav.nav-pills {
+    margin-bottom: 0;
+  }
+
+  .nav.dropdown-menu {
+    display: none;
+    margin-top: -.2rem;
+    min-width: 200px;
+  }
+
+  .open > .nav.dropdown-menu {
+    display: block;
+    -webkit-animation: .8s cubic-bezier(.25,.8,.25,1) 0s normal forwards 1 fadein;
+    animation: .8s cubic-bezier(.25,.8,.25,1) 0s normal forwards 1 fadein;
+  }
+
+  [class*=sidebar-light-] {
+    background-color: transparent;
+
+    .nav-sidebar > .nav-item > .nav-treeview {
+      background: $white;
+      border-radius: .4rem;
+    }
+  }
+
+  .nav-sidebar > .nav-item > .nav-link i {
+    margin-right: 0.35rem!important;
+  }
+
+  .nav-pills .nav-link.active {
+    background-color: transparent;
+  }
+
+  .horizontal-navbar-brand {
+    margin-top: -5px;
+    display: none;
+
+    .logo-lg {
+      font-size: 1.45rem;
+    }
+  }
+}
+
+// 黑色主题
+body.dark-mode.horizontal-menu {
+  .header-navbar.navbar-shadow {
+    border-color: $body-dark-border-color;
+  }
+
+  [class*=sidebar-light-] .nav-sidebar > .nav-item > .nav-treeview {
+    background: $body-dark-color;
+    border-radius: .4rem;
+  }
+
+  [class*=sidebar-dark-] .nav-sidebar > .nav-item i {
+    color: $body-dark-font-color;
+  }
+
+  .main-horizontal-sidebar > div,
+  [class*=sidebar-dark-],
+  [class*=sidebar-light-] {
+    background-color: $body-dark-color;
+  }
+
+  [class*=sidebar-dark-] .nav-sidebar > .nav-item > .nav-link.active {
+    background-color: transparent;
+  }
+
+  a.nav-link.active p {
+    color: $primary!important;
+  }
+
+  [class*=sidebar-light-] .nav-sidebar > .nav-item:hover > .nav-link:not(.active) {
+    color: $white;
+  }
+}
+
+@media (min-width: 576px) {
+  .navbar-expand-sm {
+    -webkit-flex-flow: row nowrap;
+    -ms-flex-flow: row nowrap;
+    flex-flow: row nowrap;
+    -webkit-box-pack: start;
+    -webkit-justify-content: flex-start;
+    -ms-flex-pack: start;
+    justify-content: flex-start;
+  }
+  .navbar-expand-sm, .navbar-expand-sm .navbar-nav {
+    -webkit-box-orient: horizontal;
+    -webkit-box-direction: normal;
+  }
+}

+ 1 - 1
resources/assets/dcat/sass/components/_layout.scss

@@ -3,7 +3,7 @@ h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
 }
 
 .content .content-wrapper {
-  padding: 7rem 2.5rem 0;
+  padding: 7rem $content-wrapper-padding 0;
 }
 
 .navbar-fixed-top {

+ 1 - 0
resources/assets/dcat/sass/dcat-app.scss

@@ -96,6 +96,7 @@ li {
 @import "./components/tab";
 // menu
 @import "./components/menu";
+@import "./components/horizontal-menu";
 // alert
 @import "./components/alert";
 // code

+ 4 - 0
resources/assets/dcat/sass/variables/_variables.scss

@@ -34,3 +34,7 @@ $menu-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.05);
 $card-border-radius: .25rem;
 
 $input-height: 34px;
+
+$content-wrapper-padding: 2.5rem;
+
+$navbar-horizontal-top: 60px;

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
resources/dist/dcat/css/dcat-app-blue-light.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
resources/dist/dcat/css/dcat-app-blue.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
resources/dist/dcat/css/dcat-app-green.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
resources/dist/dcat/css/dcat-app.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
resources/dist/dcat/extra/upload.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
resources/dist/dcat/extra/upload.js.map


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
resources/dist/dcat/js/dcat-app.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
resources/dist/dcat/js/dcat-app.js.map


+ 23 - 7
resources/dist/dcat/plugins/number-input/bootstrap-number-input.js

@@ -36,21 +36,32 @@
             var min = self.attr('min');
             var max = self.attr('max');
 
+            function getVal() {
+                var val = clone.val();
+
+                if (! val || val === "NaN") {
+                    return 0;
+                }
+
+                return parseInt(val);
+            }
+
             function setText(n) {
                 if ((min && n < min) || (max && n > max)) {
                     return false;
                 }
 
                 clone.focus().val(n);
+                clone.trigger('change');
                 return true;
             }
 
             var group = $("<div class='input-group'></div>");
             var down = $("<button type='button'>-</button>").attr('class', 'btn btn-' + settings.downClass).click(function () {
-                setText(parseInt(clone.val()) - 1);
+                setText(getVal() - 1);
             });
             var up = $("<button type='button'>+</button>").attr('class', 'btn btn-' + settings.upClass).click(function () {
-                setText(parseInt(clone.val()) + 1);
+                setText(getVal() + 1);
             });
             $("<span class='input-group-btn'></span>").append(down).appendTo(group);
             clone.appendTo(group);
@@ -62,8 +73,8 @@
             // remove spins from original
             clone.prop('type', 'text').keydown(function (e) {
                 if ($.inArray(e.keyCode, [46, 8, 9, 27, 13, 110, 190]) !== -1 ||
-					(e.keyCode == 65 && e.ctrlKey === true) ||
-					(e.keyCode >= 35 && e.keyCode <= 39)) {
+                    (e.keyCode == 65 && e.ctrlKey === true) ||
+                    (e.keyCode >= 35 && e.keyCode <= 39)) {
                     return;
                 }
                 if ((e.shiftKey || (e.keyCode < 48 || e.keyCode > 57)) && (e.keyCode < 96 || e.keyCode > 105)) {
@@ -71,7 +82,7 @@
                 }
 
                 var c = String.fromCharCode(e.which);
-                var n = parseInt(clone.val() + c);
+                var n = (getVal() + c);
 
                 //if ((min && n < min) || (max && n > max)) {
                 //    e.preventDefault();
@@ -79,8 +90,13 @@
             });
 
             clone.prop('type', 'text').blur(function (e) {
-                var c = String.fromCharCode(e.which);
-                var n = parseInt(clone.val() + c);
+                var c = parseInt(String.fromCharCode(e.which));
+                if (isNaN(c)) {
+                    c = 0;
+                }
+
+                var n = getVal() + c;
+
                 if ((min && n < min)) {
                     setText(min);
                 }

Разница между файлами не показана из-за своего большого размера
+ 0 - 7
resources/dist/dcat/plugins/vendors.min.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 14
resources/dist/dcat/plugins/vendors.min.js


+ 1 - 0
resources/lang/en/admin.php

@@ -26,6 +26,7 @@ return [
         'nullable'          => 'nullable',
         'key'               => 'key',
         'translate_title'   => 'Translate Title',
+        'sync_translation_with_comment' => 'Sync translation and comment',
     ],
     'client' => [
         'delete_confirm'    => 'Are you sure to delete this item ?',

+ 1 - 0
resources/lang/zh_CN/admin.php

@@ -26,6 +26,7 @@ return [
         'nullable'          => '允许空值',
         'key'               => '索引',
         'translate_title'   => '翻译标题',
+        'sync_translation_with_comment' => '同步翻译与注释',
     ],
     'client' => [
         'delete_confirm'    => '确认删除?',

+ 1 - 0
resources/lang/zh_TW/admin.php

@@ -26,6 +26,7 @@ return [
         'nullable'          => '允許null',
         'key'               => '索引',
         'translate_title'   => '翻譯標題',
+        'sync_translation_with_comment' => '同步翻譯與註釋',
     ],
     'client' => [
         'delete_confirm'    => '確認刪除?',

+ 2 - 2
resources/views/form/daterange.blade.php

@@ -33,12 +33,12 @@
 
 <script require="@moment,@bootstrap-datetimepicker" init="{!! $selector['start'] !!}">
     var options = {!! admin_javascript_json($options) !!};
-    var $end = $this.parents('.row').find('{!! $selector['end'] !!}');
+    var $end = $('{!! $selector['end'] !!}');
 
     $this.datetimepicker(options);
     $end.datetimepicker($.extend(options, {useCurrent: false}));
     $this.on("dp.change", function (e) {
-        $('{{ $selector['end'] }}').data("DateTimePicker").minDate(e.date);
+        $end.data("DateTimePicker").minDate(e.date);
     });
     $end.on("dp.change", function (e) {
         $this.data("DateTimePicker").maxDate(e.date);

+ 1 - 1
resources/views/form/datetimerange.blade.php

@@ -33,7 +33,7 @@
 
 <script require="@moment,@bootstrap-datetimepicker" init="{!! $selector['start'] !!}">
     var options = {!! admin_javascript_json($options) !!};
-    var $end = $this.parents('.row').find('{!! $selector['end'] !!}');
+    var $end = $('{!! $selector['end'] !!}');
 
     $this.datetimepicker(options);
     $end.datetimepicker($.extend(options, {useCurrent: false}));

+ 10 - 1
resources/views/form/markdown.blade.php

@@ -19,6 +19,15 @@
     </div>
 </div>
 
-<script require="@markdown" init="{!! $selector !!}">
+<script first>
+    var ele = window.Element;
+    Dcat.eMatches = ele.prototype.matches ||
+        ele.prototype.msMatchesSelector ||
+        ele.prototype.webkitMatchesSelector;
+</script>
+
+<script require="@editor-md-form" init="{!! $selector !!}">
     editormd(id, {!! $options !!});
+
+    Element.prototype.matches = Dcat.eMatches;
 </script>

+ 2 - 2
resources/views/form/timerange.blade.php

@@ -33,12 +33,12 @@
 
 <script require="@moment,@bootstrap-datetimepicker" init="{!! $selector['start'] !!}">
     var options = {!! admin_javascript_json($options) !!};
-    var $end = $this.parents('.row').find('{!! $selector['end'] !!}');
+    var $end = $('{!! $selector['end'] !!}');
 
     $this.datetimepicker(options);
     $end.datetimepicker($.extend(options, {useCurrent: false}));
     $this.on("dp.change", function (e) {
-        $('{{ $selector['end'] }}').data("DateTimePicker").minDate(e.date);
+        $end.data("DateTimePicker").minDate(e.date);
     });
     $end.on("dp.change", function (e) {
         $this.data("DateTimePicker").maxDate(e.date);

+ 24 - 10
resources/views/grid/displayer/extensions/name.blade.php

@@ -1,14 +1,25 @@
-@if($row->homepage)
-    <a href='{!! $row->homepage !!}' target='_blank' class="feather {{ $linkIcon }}"></a>
-@endif
-<span class="ext-name">
-    {{ $value }}
-</span>
+<div class="d-flex">
+    @if($row->logo)
+        <img data-action='preview-img' src='{!! $row->logo !!}' style='max-width:40px;max-height:40px;cursor:pointer' class='img img-thumbnail' />&nbsp;&nbsp;
+    @endif
 
-@if($row->new_version || ! $row->version)
-    &nbsp;
-    <span class="badge bg-primary">New</span>
-@endif
+    <span class="ext-name">
+        @if($row->homepage)
+            <a href='{!! $row->homepage !!}' target='_blank' class="feather {{ $linkIcon }}"></a>
+        @endif
+
+        @if($row->alias)
+            {{ $row->alias }} <br><small class="text-80">{{ $value }}</small>
+        @else
+            {{ $value }}
+        @endif
+    </span>
+
+    @if($row->new_version || ! $row->version)
+        &nbsp;
+        <span class="badge bg-primary">New</span>
+    @endif
+</div>
 
 <div style="height: 10px"></div>
 
@@ -34,6 +45,9 @@
 @endif
 
 <style>
+    .badge {
+        max-height: 22px
+    }
     .hover-display {
         display:none;
     }

+ 17 - 0
resources/views/helpers/scaffold.blade.php

@@ -204,6 +204,7 @@
 
                 <div class='form-group'>
                     <button type="button" class="btn btn-sm btn-primary btn-outline text-capitalize" id="add-table-field"><i class="feather icon-plus"></i>&nbsp;&nbsp;{{(trans('admin.scaffold.add_field'))}}</button>
+                    <button type="button" class="btn btn-sm btn-primary btn-outline text-capitalize ml-1" id="sync-translation-with-comment"><i class="feather icon-repeat"></i>&nbsp;&nbsp;{{(trans('admin.scaffold.sync_translation_with_comment'))}}</button>
                 </div>
 
                 <div class="row">
@@ -334,6 +335,22 @@
             addField();
         });
 
+        $('#sync-translation-with-comment').click(function (event) {
+            var element = $('#table-fields-sortable tr');
+            if (element.length > 0) {
+                element.each(function (i, v) {
+                    var translation = $(v).find('input[name="fields[' + i + '][translation]"]');
+                    var comment = $(v).find('input[name="fields[' + i + '][comment]"]');
+                    if (translation.val() !== "" && comment.val() === "") {
+                        comment.val(translation.val());
+                    }
+                    if (translation.val() === "" && comment.val() !== "") {
+                        translation.val(comment.val());
+                    }
+                });
+            }
+        });
+
         $('#table-fields').on('click', '.table-field-remove', function(event) {
             $(event.target).closest('tr').remove();
         });

+ 46 - 0
resources/views/layouts/container.blade.php

@@ -0,0 +1,46 @@
+<body
+        class="dcat-admin-body sidebar-mini layout-fixed {{ $configData['body_class']}} {{ $configData['sidebar_class'] }}
+        {{ $configData['navbar_class'] === 'fixed-top' ? 'navbar-fixed-top' : '' }} " >
+
+<script>
+    var Dcat = CreateDcat({!! Dcat\Admin\Admin::jsVariables() !!});
+</script>
+
+{!! admin_section(Dcat\Admin\Admin::SECTION['BODY_INNER_BEFORE']) !!}
+
+<div class="wrapper">
+    @include('admin::partials.sidebar')
+
+    @include('admin::partials.navbar')
+
+    <div class="app-content content">
+        <div class="content-wrapper" id="{{ $pjaxContainerId }}" style="top: 0;min-height: 900px;">
+            @yield('app')
+        </div>
+    </div>
+</div>
+
+<footer class="main-footer pt-1">
+    <p class="clearfix blue-grey lighten-2 mb-0 text-center">
+            <span class="text-center d-block d-md-inline-block mt-25">
+                Powered by
+                <a target="_blank" href="https://github.com/jqhph/dcat-admin">Dcat Admin</a>
+                <span>&nbsp;·&nbsp;</span>
+                v{{ Dcat\Admin\Admin::VERSION }}
+            </span>
+
+        <button class="btn btn-primary btn-icon scroll-top pull-right" style="position: fixed;bottom: 2%; right: 10px;display: none">
+            <i class="feather icon-arrow-up"></i>
+        </button>
+    </p>
+</footer>
+
+{!! admin_section(Dcat\Admin\Admin::SECTION['BODY_INNER_AFTER']) !!}
+
+{!! Dcat\Admin\Admin::asset()->jsToHtml() !!}
+
+<script>Dcat.boot();</script>
+
+</body>
+
+</html>

+ 1 - 1
resources/views/layouts/page.blade.php

@@ -25,4 +25,4 @@
     {!! Dcat\Admin\Admin::asset()->cssToHtml() !!}
 </head>
 
-@extends('admin::layouts.vertical')
+@extends('admin::layouts.container')

+ 0 - 46
resources/views/layouts/vertical.blade.php

@@ -1,46 +0,0 @@
-<body
-    class="dcat-admin-body sidebar-mini layout-fixed {{ $configData['body_class']}} {{ $configData['sidebar_class'] }}
-    {{ $configData['navbar_class'] === 'fixed-top' ? 'navbar-fixed-top' : '' }} " >
-
-    <script>
-        var Dcat = CreateDcat({!! Dcat\Admin\Admin::jsVariables() !!});
-    </script>
-
-    {!! admin_section(Dcat\Admin\Admin::SECTION['BODY_INNER_BEFORE']) !!}
-
-    <div class="wrapper">
-        @include('admin::partials.sidebar')
-
-        @include('admin::partials.navbar')
-
-        <div class="app-content content">
-            <div class="content-wrapper" id="{{ $pjaxContainerId }}" style="top: 0;min-height: 900px;">
-                @yield('app')
-            </div>
-        </div>
-    </div>
-
-    <footer class="main-footer pt-1">
-        <p class="clearfix blue-grey lighten-2 mb-0 text-center">
-            <span class="text-center d-block d-md-inline-block mt-25">
-                Powered by
-                <a target="_blank" href="https://github.com/jqhph/dcat-admin">Dcat Admin</a>
-                <span>&nbsp;·&nbsp;</span>
-                v{{ Dcat\Admin\Admin::VERSION }}
-            </span>
-
-            <button class="btn btn-primary btn-icon scroll-top pull-right" style="position: fixed;bottom: 2%; right: 10px;display: none">
-                <i class="feather icon-arrow-up"></i>
-            </button>
-        </p>
-    </footer>
-
-    {!! admin_section(Dcat\Admin\Admin::SECTION['BODY_INNER_AFTER']) !!}
-
-    {!! Dcat\Admin\Admin::asset()->jsToHtml() !!}
-
-    <script>Dcat.boot();</script>
-
-</body>
-
-</html>

+ 18 - 12
resources/views/partials/menu.blade.php

@@ -1,13 +1,17 @@
 @php
-    $active = $builder->isActive($item);
-
     $depth = $item['depth'] ?? 0;
+
+    $horizontal = config('admin.layout.horizontal_menu');
+
+    $defaultIcon = config('admin.menu.default_icon', 'feather icon-circle');
 @endphp
 
 @if($builder->visible($item))
     @if(empty($item['children']))
         <li class="nav-item">
-            <a @if(mb_strpos($item['uri'], '://') !== false) target="_blank" @endif href="{{ $builder->getUrl($item['uri']) }}" class="nav-link {!! $builder->isActive($item) ? 'active' : '' !!}">
+            <a @if(mb_strpos($item['uri'], '://') !== false) target="_blank" @endif
+               href="{{ $builder->getUrl($item['uri']) }}"
+               class="nav-link {!! $builder->isActive($item) ? 'active' : '' !!}">
                 {!! str_repeat('&nbsp;', $depth) !!}<i class="fa fa-fw {{ $item['icon'] ?: 'feather icon-circle' }}"></i>
                 <p>
                     {{ $builder->translate($item['title']) }}
@@ -15,25 +19,27 @@
             </a>
         </li>
     @else
-        @php
-            $active = $builder->isActive($item);
-        @endphp
 
-        <li class="nav-item has-treeview {{ $active ? 'menu-open' : '' }}">
-            <a href="#" class="nav-link">
-                {!! str_repeat('&nbsp;', $depth) !!}<i class="fa fa-fw {{ $item['icon'] ?: 'feather icon-circle' }}"></i>
+        <li class="{{ $horizontal ? 'dropdown' : 'has-treeview' }} {{ $depth > 0 ? 'dropdown-submenu' : '' }} nav-item {{ $builder->isActive($item) ? 'menu-open' : '' }}">
+            <a href="#"
+               class="nav-link {{ $builder->isActive($item) ? ($horizontal ? 'active' : '') : '' }}
+                    {{ $horizontal ? 'dropdown-toggle' : '' }}">
+                {!! str_repeat('&nbsp;', $depth) !!}<i class="fa fa-fw {{ $item['icon'] ?: $defaultIcon }}"></i>
                 <p>
                     {{ $builder->translate($item['title']) }}
-                    <i class="right fa fa-angle-left"></i>
+
+                    @if(! $horizontal)
+                        <i class="right fa fa-angle-left"></i>
+                    @endif
                 </p>
             </a>
-            <ul class="nav nav-treeview">
+            <ul class="nav {{ $horizontal ? 'dropdown-menu' : 'nav-treeview' }}">
                 @foreach($item['children'] as $item)
                     @php
                         $item['depth'] = $depth + 1;
                     @endphp
 
-                    @include('admin::partials.menu', $item)
+                    @include('admin::partials.menu', ['item' => $item])
                 @endforeach
             </ul>
         </li>

+ 28 - 26
resources/views/partials/navbar.blade.php

@@ -1,4 +1,6 @@
 
+{!! admin_section(Dcat\Admin\Admin::SECTION['NAVBAR_BEFORE']) !!}
+
 <nav class="header-navbar navbar-expand-lg navbar
     navbar-with-menu {{ $configData['navbar_class'] }}
     {{ $configData['navbar_color'] }}
@@ -6,6 +8,7 @@
 
     <div class="navbar-wrapper">
         <div class="navbar-container content">
+            @if(! $configData['horizontal_menu'])
             <div class="mr-auto float-left bookmark-wrapper d-flex align-items-center">
                 <ul class="nav navbar-nav">
                     <li class="nav-item mr-auto">
@@ -15,39 +18,38 @@
                     </li>
                 </ul>
             </div>
+            @endif
 
-            <div class="navbar-collapse">
-                <div class="mr-auto float-left bookmark-wrapper d-flex align-items-center">
+            <div class="navbar-collapse d-flex justify-content-between">
+                <div class="d-flex align-items-center">
                     {!! Dcat\Admin\Admin::navbar()->render('left') !!}
                 </div>
-                <div class="float-right d-flex align-items-center">
-                    {!! Dcat\Admin\Admin::navbar()->render() !!}
+
+                @if($configData['horizontal_menu'])
+                <div class="d-md-block horizontal-navbar-brand justify-content-center text-center">
+                    <ul class="nav navbar-nav flex-row">
+                        <li class="nav-item mr-auto">
+                            <a href="{{ admin_url('/') }}" class="waves-effect waves-light">
+                                <span class="logo-lg">{!! config('admin.logo') !!}</span>
+                            </a>
+                        </li>
+                    </ul>
                 </div>
-                <ul class="nav navbar-nav float-right">
-                    {{--User Account Menu--}}
-                    {!! admin_section(Dcat\Admin\Admin::SECTION['NAVBAR_USER_PANEL']) !!}
+                @endif
 
-                    {!! admin_section(Dcat\Admin\Admin::SECTION['NAVBAR_AFTER_USER_PANEL']) !!}
-                </ul>
+                <div class=" d-flex align-items-center">
+                    {!! Dcat\Admin\Admin::navbar()->render() !!}
+
+                    <ul class="nav navbar-nav">
+                        {{--User Account Menu--}}
+                        {!! admin_section(Dcat\Admin\Admin::SECTION['NAVBAR_USER_PANEL']) !!}
+
+                        {!! admin_section(Dcat\Admin\Admin::SECTION['NAVBAR_AFTER_USER_PANEL']) !!}
+                    </ul>
+                </div>
             </div>
         </div>
     </div>
 </nav>
 
-{{-- Search Start Here --}}
-<ul class="main-search-list-defaultlist d-none">
-
-</ul>
-<ul class="main-search-list-defaultlist-other-list d-none">
-    <li class="auto-suggestion d-flex align-items-center justify-content-between cursor-pointer">
-        <a class="d-flex align-items-center justify-content-between w-100 py-50">
-            <div class="d-flex justify-content-start"><span class="mr-75 feather icon-alert-circle"></span><span>No
-results found.</span></div>
-        </a>
-    </li>
-</ul>
-<script>
-    $('.menu-toggle').on('click', function () {
-        $(this).find('i').toggleClass('icon-circle icon-disc')
-    })
-</script>
+{!! admin_section(Dcat\Admin\Admin::SECTION['NAVBAR_AFTER']) !!}

+ 9 - 4
resources/views/partials/sidebar.blade.php

@@ -1,6 +1,8 @@
-<div class="main-menu">
+<div class="{{ $configData['horizontal_menu'] ? 'header-navbar navbar-expand-sm navbar navbar-horizontal' : 'main-menu' }}">
     <div class="main-menu-content">
-        <aside class="main-sidebar {{ $configData['sidebar_style'] }} shadow">
+        <aside class="{{ $configData['horizontal_menu'] ? 'main-horizontal-sidebar' : 'main-sidebar shadow' }} {{ $configData['sidebar_style'] }}">
+
+            @if(! $configData['horizontal_menu'])
             <div class="navbar-header">
                 <ul class="nav navbar-nav flex-row">
                     <li class="nav-item mr-auto">
@@ -11,9 +13,12 @@
                     </li>
                 </ul>
             </div>
+            @endif
 
-            <div class="sidebar p-0 pb-3">
-                <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" style="padding-top: 10px">
+            <div class="p-0 {{ $configData['horizontal_menu'] ? 'pl-1 pr-1' : 'sidebar pb-3' }}">
+                <ul class="nav nav-pills nav-sidebar {{ $configData['horizontal_menu'] ? '' : 'flex-column' }}"
+                    {!! $configData['horizontal_menu'] ? '' : 'data-widget="treeview"' !!}
+                     style="padding-top: 10px">
                     {!! admin_section(Dcat\Admin\Admin::SECTION['LEFT_SIDEBAR_MENU_TOP']) !!}
 
                     {!! admin_section(Dcat\Admin\Admin::SECTION['LEFT_SIDEBAR_MENU']) !!}

+ 1 - 1
src/Actions/HasActionHandler.php

@@ -70,7 +70,7 @@ trait HasActionHandler
      */
     public function handlerRoute()
     {
-        return route(admin_api_route('action'));
+        return route(admin_api_route_name('action'));
     }
 
     /**

+ 22 - 12
src/Admin.php

@@ -23,7 +23,6 @@ use Illuminate\Database\Eloquent\Model;
 use Illuminate\Http\Exceptions\HttpResponseException;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\Event;
-use Illuminate\Support\Str;
 use Symfony\Component\HttpFoundation\Response;
 
 class Admin
@@ -31,7 +30,7 @@ class Admin
     use HasAssets;
     use HasHtml;
 
-    const VERSION = '2.0.17-beta';
+    const VERSION = '2.0.19-beta';
 
     const SECTION = [
         // 往 <head> 标签内输入内容
@@ -48,6 +47,10 @@ class Admin
         // 顶部导航栏用户面板
         'NAVBAR_USER_PANEL' => 'ADMIN_NAVBAR_USER_PANEL',
         'NAVBAR_AFTER_USER_PANEL' => 'ADMIN_NAVBAR_AFTER_USER_PANEL',
+        // 顶部导航栏之前
+        'NAVBAR_BEFORE' => 'ADMIN_NAVBAR_BEFORE',
+        // 顶部导航栏底下
+        'NAVBAR_AFTER' => 'ADMIN_NAVBAR_AFTER',
 
         // 侧边栏顶部用户信息面板
         'LEFT_SIDEBAR_USER_PANEL' => 'ADMIN_LEFT_SIDEBAR_USER_PANEL',
@@ -458,13 +461,27 @@ class Admin
         $jsVariables['token'] = csrf_token();
         $jsVariables['lang'] = __('admin.client') ?: [];
         $jsVariables['colors'] = static::color()->all();
-        $jsVariables['dark_mode'] = Str::contains(config('admin.layout.body_class'), 'dark-mode');
+        $jsVariables['dark_mode'] = static::isDarkMode();
         $jsVariables['sidebar_dark'] = config('admin.layout.sidebar_dark') || ($sidebarStyle === 'dark');
         $jsVariables['sidebar_light_style'] = in_array($sidebarStyle, ['dark', 'light'], true) ? 'sidebar-light-primary' : 'sidebar-primary';
 
         return admin_javascript_json($jsVariables);
     }
 
+    /**
+     * @return bool
+     */
+    public static function isDarkMode()
+    {
+        $bodyClass = config('admin.layout.body_class');
+
+        return in_array(
+            'dark-mode',
+            is_array($bodyClass) ? $bodyClass : explode(' ', $bodyClass),
+            true
+        );
+    }
+
     /**
      * 注册路由.
      *
@@ -473,10 +490,8 @@ class Admin
     public static function routes()
     {
         $attributes = [
-            'domain'     => config('admin.route.domain'),
             'prefix'     => config('admin.route.prefix'),
             'middleware' => config('admin.route.middleware'),
-            'as'         => static::app()->getName().'.',
         ];
 
         if (config('admin.auth.enable', true)) {
@@ -511,18 +526,15 @@ class Admin
     /**
      * 注册api路由.
      *
-     * @param string $as
-     *
      * @return void
      */
-    public static function registerApiRoutes(string $as = null)
+    public static function registerApiRoutes()
     {
         $attributes = [
-            'domain'     => config('admin.route.domain'),
             'prefix'     => admin_base_path('dcat-api'),
             'middleware' => config('admin.route.middleware'),
-            'as'         => $as ?: static::app()->getApiRoutePrefix(Application::DEFAULT),
             'namespace'  => 'Dcat\Admin\Http\Controllers',
+            'as'         => 'dcat-api.',
         ];
 
         app('router')->group($attributes, function ($router) {
@@ -550,10 +562,8 @@ class Admin
         }
 
         $attributes = [
-            'domain'     => config('admin.route.domain'),
             'prefix'     => config('admin.route.prefix'),
             'middleware' => config('admin.route.middleware'),
-            'as'         => static::app()->getName().'.',
         ];
 
         app('router')->group($attributes, function ($router) {

+ 37 - 7
src/Application.php

@@ -132,9 +132,37 @@ class Application
      *
      * @return string
      */
-    public function getApiRoutePrefix(?string $app)
+    public function getApiRoutePrefix(?string $app = null)
     {
-        return "dcat.api.{$app}.";
+        return $this->getRoutePrefix($app).'dcat-api.';
+    }
+
+    /**
+     * 获取路由别名前缀.
+     *
+     * @param string|null $app
+     *
+     * @return string
+     */
+    public function getRoutePrefix(?string $app = null)
+    {
+        $app = $app ?: $this->getName();
+
+        return 'dcat.'.$app.'.';
+    }
+
+    /**
+     * 获取路由别名.
+     *
+     * @param string|null $route
+     * @param array $params
+     * @param bool $absolute
+     *
+     * @return string
+     */
+    public function getRoute(?string $route, array $params = [], $absolute = true)
+    {
+        return route($this->getRoutePrefix().$route, $params, $absolute);
     }
 
     /**
@@ -146,8 +174,8 @@ class Application
     {
         $this->switch($app);
 
-        $this->loadRoutesFrom(function () use ($app) {
-            Admin::registerApiRoutes($this->getApiRoutePrefix($app));
+        $this->loadRoutesFrom(function () {
+            Admin::registerApiRoutes();
         }, $app);
 
         if (is_file($routes = admin_path('routes.php'))) {
@@ -179,8 +207,10 @@ class Application
      */
     protected function loadRoutesFrom($path, ?string $app)
     {
-        if (! $this->container->routesAreCached()) {
-            Route::middleware('admin.app:'.$app)->group($path);
-        }
+        Route::group(array_filter([
+            'middleware' => 'admin.app:'.$app,
+            'domain'     => config("{$app}.route.domain"),
+            'as'         => $this->getRoutePrefix($app),
+        ]), $path);
     }
 }

+ 2 - 2
src/Console/ExtensionMakeCommand.php

@@ -187,8 +187,8 @@ TREE;
 
         // make composer.json
         $composerContents = str_replace(
-            ['{package}', '{namespace}', '{className}'],
-            [$this->package, str_replace('\\', '\\\\', $this->namespace).'\\\\', $this->className],
+            ['{package}', '{alias}', '{namespace}', '{className}'],
+            [$this->package, '', str_replace('\\', '\\\\', $this->namespace).'\\\\', $this->className],
             file_get_contents(__DIR__.'/stubs/extension/composer.json.stub')
         );
         $this->putFile('composer.json', $composerContents);

+ 5 - 1
src/Console/stubs/config.stub

@@ -208,6 +208,7 @@ return [
         // Whether enable menu bind to a permission.
         'bind_permission' => true,
 
+		'default_icon' => 'feather icon-circle',
     ],
 
     /*
@@ -281,7 +282,10 @@ return [
         // default, blue, blue-light, green
         'color' => 'default',
 
-        'body_class' => '',
+		// sidebar-separate
+        'body_class' => [],
+
+        'horizontal_menu' => false,
 
         'sidebar_collapsed' => false,
 

+ 1 - 0
src/Console/stubs/extension/composer.json.stub

@@ -1,5 +1,6 @@
 {
     "name": "{package}",
+    "alias": "{alias}",
     "description": "Description...",
     "type": "library",
     "keywords": ["dcat-admin", "extension"],

+ 0 - 1
src/Console/stubs/routes.stub

@@ -7,7 +7,6 @@ use Dcat\Admin\Admin;
 Admin::routes();
 
 Route::group([
-    'domain'     => config('admin.route.domain'),
     'prefix'     => config('admin.route.prefix'),
     'namespace'  => config('admin.route.namespace'),
     'middleware' => config('admin.route.middleware'),

+ 47 - 2
src/Extend/ServiceProvider.php

@@ -143,6 +143,20 @@ abstract class ServiceProvider extends LaravelServiceProvider
         return $this->name ?: ($this->name = str_replace('/', '.', $this->getPackageName()));
     }
 
+    /**
+     * 获取扩展别名.
+     *
+     * @return string
+     */
+    public function getAlias()
+    {
+        if (! $this->composerProperty) {
+            return;
+        }
+
+        return $this->composerProperty->alias;
+    }
+
     /**
      * 获取包名.
      *
@@ -227,6 +241,37 @@ abstract class ServiceProvider extends LaravelServiceProvider
         return $path ? $this->path.'/'.$path : $this->path;
     }
 
+    /**
+     * 获取logo路径.
+     *
+     * @return string
+     *
+     * @throws \ReflectionException
+     */
+    public function getLogoPath()
+    {
+        return $this->path('logo.png');
+    }
+
+    /**
+     * @return string
+     */
+    public function getLogoBase64()
+    {
+        try {
+            $logo = $this->getLogoPath();
+
+            if (is_file($logo) && $file = fopen($logo, 'rb', 0)) {
+                $content = fread($file, filesize($logo));
+                fclose($file);
+                $base64 = chunk_split(base64_encode($content));
+
+                return 'data:image/png;base64,'.$base64;
+            }
+        } catch (\ReflectionException $e) {
+        }
+    }
+
     /**
      * 判断扩展是否启用.
      *
@@ -298,8 +343,8 @@ abstract class ServiceProvider extends LaravelServiceProvider
     /**
      * 更新扩展.
      *
-     * @param $currentVersion
-     * @param $stopOnVersion
+     * @param string $currentVersion
+     * @param string $stopOnVersion
      *
      * @throws \Exception
      */

+ 21 - 4
src/Form.php

@@ -590,7 +590,6 @@ class Form implements Renderable
                 ->alert()
                 ->status($status)
                 ->message($message)
-                ->redirectIf($status, $this->resource(-1))
         );
     }
 
@@ -1370,6 +1369,18 @@ class Form implements Renderable
         return $this;
     }
 
+    /**
+     * @param array $vars
+     *
+     * @return $this
+     */
+    public function addVariables(array $vars)
+    {
+        $this->builder->addVariables($vars);
+
+        return $this;
+    }
+
     /**
      * Get or set title for form.
      *
@@ -1597,8 +1608,8 @@ class Form implements Renderable
     /**
      * Get or set input data.
      *
-     * @param string $key
-     * @param null   $value
+     * @param string|array $key
+     * @param mixed        $value
      *
      * @return array|mixed
      */
@@ -1612,7 +1623,13 @@ class Form implements Renderable
             return Arr::get($this->inputs, $key);
         }
 
-        return Arr::set($this->inputs, $key, $value);
+        if (is_array($key)) {
+            $this->inputs = array_merge($this->inputs, $key);
+
+            return;
+        }
+
+        Arr::set($this->inputs, $key, $value);
     }
 
     /**

+ 4 - 3
src/Form/Concerns/HasFieldValidator.php

@@ -387,9 +387,10 @@ trait HasFieldValidator
                 if (! Arr::has($input, $column)) {
                     continue;
                 }
-                $input[$column.$key] = Arr::get($input, $column);
-                $rules[$column.$key] = $fieldRules;
-                $attributes[$column.$key] = "{$this->label}[$column]";
+                $k = $column.$key;
+                Arr::set($input, $k, Arr::get($input, $column));
+                $rules[$k] = $fieldRules;
+                $attributes[$k] = "{$this->label}[$column]";
             }
         }
 

+ 1 - 1
src/Form/Field.php

@@ -421,7 +421,7 @@ class Field implements Renderable
     }
 
     /**
-     * @return Fluent
+     * @return Fluent|\Illuminate\Database\Eloquent\Model
      */
     public function values()
     {

+ 1 - 1
src/Form/Field/Editor.php

@@ -126,7 +126,7 @@ class Editor extends Field
      */
     protected function defaultImageUploadUrl()
     {
-        return $this->formatUrl(route(admin_api_route('tinymce.upload')));
+        return $this->formatUrl(route(admin_api_route_name('tinymce.upload')));
     }
 
     /**

+ 1 - 1
src/Form/Field/Markdown.php

@@ -131,7 +131,7 @@ class Markdown extends Field
      */
     protected function defaultImageUploadUrl()
     {
-        return $this->formatUrl(route(admin_api_route('editor-md.upload')));
+        return $this->formatUrl(route(admin_api_route_name('editor-md.upload')));
     }
 
     /**

+ 6 - 6
src/Form/Field/UploadField.php

@@ -130,10 +130,10 @@ trait UploadField
         }
 
         if ($this->name instanceof \Closure) {
-            return $this->name->call($this, $file);
+            $this->name = $this->name->call($this->values(), $file);
         }
 
-        if (is_string($this->name)) {
+        if ($this->name !== '' && is_string($this->name)) {
             return $this->name;
         }
 
@@ -148,7 +148,7 @@ trait UploadField
     public function getDirectory()
     {
         if ($this->directory instanceof \Closure) {
-            return call_user_func($this->directory, $this->form);
+            $this->directory = $this->directory->call($this->values(), $this->form);
         }
 
         return $this->directory ?: $this->defaultDirectory();
@@ -230,8 +230,8 @@ trait UploadField
     /**
      * Specify the directory and name for upload file.
      *
-     * @param string      $directory
-     * @param null|string $name
+     * @param string|\Closure $directory
+     * @param null|string     $name
      *
      * @return $this
      */
@@ -247,7 +247,7 @@ trait UploadField
     /**
      * Specify the directory upload file.
      *
-     * @param string $dir
+     * @param string|\Closure $dir
      *
      * @return $this
      */

+ 1 - 1
src/Form/Field/WebUploader.php

@@ -52,7 +52,7 @@ trait WebUploader
     {
         $this->options['chunkSize'] = $size * 1024;
 
-        $this->checked(true);
+        $this->chunked(true);
 
         return $this;
     }

+ 8 - 3
src/Grid/Actions/Delete.php

@@ -11,15 +11,20 @@ class Delete extends RowAction
      */
     public function title()
     {
+        if ($this->title) {
+            return $this->title;
+        }
+
         return '<i class="feather icon-trash"></i> '.__('admin.delete');
     }
 
     public function render()
     {
         $this->setHtmlAttribute([
-            'data-url'     => $this->url(),
-            'data-message' => "ID - {$this->getKey()}",
-            'data-action'  => 'delete',
+            'data-url'      => $this->url(),
+            'data-message'  => "ID - {$this->getKey()}",
+            'data-action'   => 'delete',
+            'data-redirect' => request()->fullUrl(),
         ]);
 
         return parent::render();

+ 4 - 0
src/Grid/Actions/Edit.php

@@ -11,6 +11,10 @@ class Edit extends RowAction
      */
     public function title()
     {
+        if ($this->title) {
+            return $this->title;
+        }
+
         return '<i class="feather icon-edit-1"></i> '.__('admin.edit');
     }
 

+ 11 - 13
src/Grid/Actions/QuickEdit.php

@@ -7,31 +7,29 @@ use Dcat\Admin\Grid\RowAction;
 
 class QuickEdit extends RowAction
 {
-    protected static $resolvedWindow;
-
     /**
      * @return array|null|string
      */
     public function title()
     {
+        if ($this->title) {
+            return $this->title;
+        }
+
         return '<i class="feather icon-edit"></i> '.__('admin.quick_edit');
     }
 
     public function render()
     {
-        if (! static::$resolvedWindow) {
-            static::$resolvedWindow = true;
+        [$width, $height] = $this->parent->option('dialog_form_area');
 
-            [$width, $height] = $this->parent->option('dialog_form_area');
+        $title = trans('admin.edit');
 
-            $title = trans('admin.edit');
-
-            Form::dialog($title)
-                ->click(".{$this->getElementClass()}")
-                ->dimensions($width, $height)
-                ->forceRefresh()
-                ->success('Dcat.reload()');
-        }
+        Form::dialog($title)
+            ->click(".{$this->getElementClass()}")
+            ->dimensions($width, $height)
+            ->forceRefresh()
+            ->success('Dcat.reload()');
 
         $this->setHtmlAttribute([
             'data-url' => "{$this->resource()}/{$this->getKey()}/edit",

+ 4 - 0
src/Grid/Actions/Show.php

@@ -11,6 +11,10 @@ class Show extends RowAction
      */
     public function title()
     {
+        if ($this->title) {
+            return $this->title;
+        }
+
         return '<i class="feather icon-eye"></i> '.__('admin.show');
     }
 

+ 1 - 1
src/Grid/BatchAction.php

@@ -31,6 +31,6 @@ JS;
      */
     public function getSelectedKeysScript()
     {
-        return "Dcat.grid.selected('{$this->parent->getName()}');";
+        return "Dcat.grid.selected('{$this->parent->getName()}')";
     }
 }

+ 4 - 2
src/Grid/Column.php

@@ -528,6 +528,10 @@ class Column
         $i = 0;
 
         $data->transform(function ($row, $key) use (&$i) {
+            $this->setOriginalModel(static::$originalGridModels[$key]);
+
+            $this->originalModel['_index'] = $row['_index'] = $i;
+
             $row = $this->convertModelToArray($row);
 
             $i++;
@@ -535,8 +539,6 @@ class Column
                 $row['#'] = $i;
             }
 
-            $this->setOriginalModel(static::$originalGridModels[$key]);
-
             $this->original = Arr::get($this->originalModel, $this->name);
 
             $this->value = $value = $this->htmlEntityEncode($original = Arr::get($row, $this->name));

+ 0 - 16
src/Grid/Concerns/CanHidesColumns.php

@@ -205,22 +205,6 @@ trait CanHidesColumns
         })->toArray();
     }
 
-    /**
-     * Get default visible column names.
-     *
-     * @return array
-     */
-    public function getDefaultVisibleColumnNames()
-    {
-        return array_values(
-            array_diff(
-                $this->columnNames,
-                $this->hiddenColumns,
-                [Grid\Column::SELECT_COLUMN_NAME, Grid\Column::ACTION_COLUMN_NAME]
-            )
-        );
-    }
-
     protected function hasColumnSelectorRequestInput()
     {
         return $this->request->has($this->getColumnSelectorQueryName());

+ 2 - 2
src/Grid/Concerns/HasTree.php

@@ -158,8 +158,8 @@ HTML
     {
         if (
             $sortable
-            && ! $this->findQueryByMethod('orderBy')
-            && ! $this->findQueryByMethod('orderByDesc')
+            && $this->findQueryByMethod('orderBy')->isEmpty()
+            && $this->findQueryByMethod('orderByDesc')->isEmpty()
             && ($orderColumn = $this->repository->getOrderColumn())
         ) {
             $this->orderBy($orderColumn)

+ 28 - 20
src/Grid/Displayers/Actions.php

@@ -4,6 +4,10 @@ namespace Dcat\Admin\Grid\Displayers;
 
 use Dcat\Admin\Actions\Action;
 use Dcat\Admin\Form;
+use Dcat\Admin\Grid\Actions\Delete;
+use Dcat\Admin\Grid\Actions\Edit;
+use Dcat\Admin\Grid\Actions\QuickEdit;
+use Dcat\Admin\Grid\Actions\Show;
 use Dcat\Admin\Grid\RowAction;
 use Dcat\Admin\Support\Helper;
 use Illuminate\Contracts\Support\Htmlable;
@@ -229,11 +233,12 @@ class Actions extends AbstractDisplayer
     {
         $label = trans('admin.show');
 
-        return <<<EOT
-<a href="{$this->resource()}/{$this->getKey()}" title="{$label}">
-    <i class="feather icon-eye grid-action-icon"></i>
-</a>&nbsp;
-EOT;
+        return Show::make(
+            "<i title='{$label}' class=\"feather icon-eye grid-action-icon\"></i> &nbsp;"
+        )
+            ->setGrid($this->grid)
+            ->setRow($this->row)
+            ->render();
     }
 
     /**
@@ -245,11 +250,12 @@ EOT;
     {
         $label = trans('admin.edit');
 
-        return <<<EOT
-<a href="{$this->grid->getEditUrl($this->getKey())}" title="{$label}">
-    <i class="feather icon-edit-1 grid-action-icon"></i>
-</a>&nbsp;
-EOT;
+        return Edit::make(
+            "<i title='{$label}' class=\"feather icon-edit-1 grid-action-icon\"></i> &nbsp;"
+        )
+            ->setGrid($this->grid)
+            ->setRow($this->row)
+            ->render();
     }
 
     /**
@@ -270,11 +276,12 @@ EOT;
 
         $label = trans('admin.quick_edit');
 
-        return <<<EOF
-<a title="{$label}" class="{$this->grid->getRowName()}-edit" data-url="{$this->resource()}/{$this->getKey()}/edit" href="javascript:void(0);">
-    <i class="feather icon-edit grid-action-icon"></i>
-</a>&nbsp;
-EOF;
+        return QuickEdit::make(
+            "<i title='{$label}' class=\"feather icon-edit grid-action-icon\"></i> &nbsp;"
+        )
+            ->setGrid($this->grid)
+            ->setRow($this->row)
+            ->render();
     }
 
     /**
@@ -286,10 +293,11 @@ EOF;
     {
         $label = trans('admin.delete');
 
-        return <<<EOT
-<a title="{$label}" href="javascript:void(0);" data-message="ID - {$this->getKey()}" data-url="{$this->resource()}/{$this->getKey()}" data-action="delete">
-    <i class="feather icon-trash grid-action-icon"></i>
-</a>&nbsp;
-EOT;
+        return Delete::make(
+            "<i class=\"feather icon-trash grid-action-icon\" title='{$label}'></i> &nbsp;"
+        )
+            ->setGrid($this->grid)
+            ->setRow($this->row)
+            ->render();
     }
 }

+ 15 - 7
src/Grid/Filter/AbstractFilter.php

@@ -203,12 +203,20 @@ abstract class AbstractFilter
     /**
      * Format id.
      *
-     * @param $columns
+     * @param string|array $columns
      *
      * @return array|string
      */
     protected function formatId($columns)
     {
+        if (is_array($columns)) {
+            foreach ($columns as &$column) {
+                $column = $this->formatId($column);
+            }
+
+            return $columns;
+        }
+
         return $this->parent->grid()->makeName('filter-column-'.str_replace('.', '-', $columns));
     }
 
@@ -607,12 +615,12 @@ abstract class AbstractFilter
     protected function defaultVariables()
     {
         return array_merge([
-            'id'        => $this->id,
-            'name'      => $this->formatName($this->column),
-            'label'     => $this->label,
-            'value'     => $this->normalizeValue(),
-            'width'     => $this->width,
-            'style'     => $this->style,
+            'id'    => $this->id,
+            'name'  => $this->formatName($this->column),
+            'label' => $this->label,
+            'value' => $this->normalizeValue(),
+            'width' => $this->width,
+            'style' => $this->style,
         ], $this->presenter()->variables());
     }
 

+ 2 - 4
src/Grid/Model.php

@@ -510,13 +510,11 @@ class Model
      *
      * @param $method
      *
-     * @return static
+     * @return Collection
      */
     public function findQueryByMethod($method)
     {
-        return $this->queries->first(function ($query) use ($method) {
-            return $query['method'] == $method;
-        });
+        return $this->queries->where('method', $method);
     }
 
     /**

+ 6 - 1
src/Grid/Tools/BatchDelete.php

@@ -13,8 +13,13 @@ class BatchDelete extends BatchAction
 
     public function render()
     {
+        $redirect = request()->fullUrl();
+
         return <<<HTML
-<a href="#" data-name="{$this->parent->getName()}" data-action="batch-delete" data-url="{$this->resource()}"><i class="feather icon-trash"></i> {$this->title}</a>
+<a  data-name="{$this->parent->getName()}" 
+    data-action="batch-delete" 
+    data-redirect="{$redirect}"
+    data-url="{$this->resource()}"><i class="feather icon-trash"></i> {$this->title}</a>
 HTML;
     }
 }

+ 4 - 12
src/Grid/Tools/ColumnSelector.php

@@ -44,20 +44,12 @@ class ColumnSelector extends AbstractTool
         $show = $this->getVisibleColumnNames();
         $all = $this->getGridColumns();
 
+        $visibleColumnNames = $this->grid->getVisibleColumnsFromQuery();
+
         $list = Checkbox::make()
             ->class('column-select-item')
             ->options($all)
-            ->check(
-                $this->getGridColumns()->filter(function ($label, $key) use ($show) {
-                    if (empty($show)) {
-                        return true;
-                    }
-
-                    return in_array($key, $show) ? true : false;
-                }
-            )
-            ->keys()
-        );
+            ->check($visibleColumnNames);
 
         $selectAll = Checkbox::make('_all_', [1 => trans('admin.all')])->check(
             $all->count() === count($show) ? 1 : null
@@ -65,7 +57,7 @@ class ColumnSelector extends AbstractTool
 
         return Admin::view('admin::grid.column-selector', [
             'checkbox'   => $list,
-            'defaults'   => $this->grid->getDefaultVisibleColumnNames(),
+            'defaults'   => $visibleColumnNames,
             'selectAll'  => $selectAll,
             'columnName' => $this->grid->getColumnSelectorQueryName(),
         ]);

+ 1 - 1
src/Http/Auth/Permission.php

@@ -88,7 +88,7 @@ class Permission
     /**
      * Send error response page.
      *
-     * @throws \Dcat\Admin\Exception\RespondException
+     * @throws \Illuminate\Http\Exceptions\HttpResponseException
      */
     public static function error()
     {

+ 1 - 1
src/Http/Controllers/ExtensionController.php

@@ -36,7 +36,7 @@ class ExtensionController extends Controller
         return new Grid(new Extension(), function (Grid $grid) {
             $grid->number();
             $grid->column('name')->displayUsing(Extensions\Name::class);
-            $grid->column('description')->displayUsing(Extensions\Description::class)->width('58%');
+            $grid->column('description')->displayUsing(Extensions\Description::class)->width('50%');
 
             $grid->column('authors')->display(function ($v) {
                 if (! $v) {

+ 1 - 2
src/Http/Displayers/Extensions/Name.php

@@ -7,7 +7,6 @@ use Dcat\Admin\Grid\Displayers\AbstractDisplayer;
 use Dcat\Admin\Http\Actions\Extensions\Disable;
 use Dcat\Admin\Http\Actions\Extensions\Enable;
 use Dcat\Admin\Http\Actions\Extensions\Uninstall;
-use Illuminate\Support\Str;
 
 class Name extends AbstractDisplayer
 {
@@ -19,7 +18,7 @@ class Name extends AbstractDisplayer
             'enableAction'    => $this->resolveAction(Enable::class),
             'disableAction'   => $this->resolveAction(Disable::class),
             'uninstallAction' => $this->resolveAction(Uninstall::class),
-            'linkIcon'        => Str::contains($this->row->homepage, 'github.com') ? 'icon-github' : 'icon-link',
+            'linkIcon'        => 'icon-link',
         ]);
     }
 

+ 4 - 4
src/Http/JsonResponse.php

@@ -20,7 +20,7 @@ use Illuminate\Validation\ValidationException;
  * @method $this detailIf($condition, ?string $message)
  * @method $this statusCodeIf($condition, int $code)
  * @method $this redirectIf($condition, ?string $url)
- * @method $this locationIf($condition, ?string $url)
+ * @method $this locationIf($condition, ?string $url = null)
  * @method $this refreshIf($condition)
  * @method $this downloadIf($condition, ?string $url)
  * @method $this scriptIf($condition, ?string $script)
@@ -216,13 +216,13 @@ class JsonResponse implements Arrayable
     /**
      * Location 跳转.
      *
-     * @param string $location
+     * @param string $location 不传则刷新当前页面
      *
      * @return $this
      */
-    public function location(?string $location)
+    public function location(?string $location = null)
     {
-        return $this->then(['action' => 'location', 'value' => admin_url($location)]);
+        return $this->then(['action' => 'location', 'value' => $location ? admin_url($location) : null]);
     }
 
     /**

+ 1 - 1
src/Http/Middleware/Authenticate.php

@@ -55,7 +55,7 @@ class Authenticate
         );
 
         foreach ($excepts as $except) {
-            if ($request->routeIs($except)) {
+            if ($request->routeIs($except) || $request->routeIs(admin_route_name($except))) {
                 return true;
             }
 

+ 1 - 1
src/Http/Middleware/Bootstrap.php

@@ -28,7 +28,7 @@ class Bootstrap
         if (
             config('admin.layout.dark_mode_switch')
             && ! Helper::isAjaxRequest()
-            && ! request()->routeIs(admin_api_route('*'))
+            && ! request()->routeIs(admin_api_route_name('*'))
         ) {
             Admin::navbar()->right((new DarkModeSwitcher())->render());
         }

+ 2 - 2
src/Http/Middleware/Permission.php

@@ -85,7 +85,7 @@ class Permission
      */
     protected function isApiRoute($request)
     {
-        return $request->routeIs('dcat.api.*');
+        return $request->routeIs(admin_api_route_name('*'));
     }
 
     /**
@@ -107,7 +107,7 @@ class Permission
         );
 
         foreach ($excepts as $except) {
-            if ($request->routeIs($except)) {
+            if ($request->routeIs($except) || $request->routeIs(admin_route_name($except))) {
                 return true;
             }
 

+ 2 - 6
src/Http/Repositories/Extension.php

@@ -19,9 +19,6 @@ class Extension extends Repository
         }
 
         return $data;
-        //return collect($data)->sort(function ($row) {
-        //    return ! empty($row['version']) && empty($row['new_version']);
-        //})->toArray();
     }
 
     /**
@@ -39,7 +36,8 @@ class Extension extends Repository
 
         return [
             'id'           => $name,
-            'alias'        => $name,
+            'alias'        => $extension->getAlias(),
+            'logo'         => $extension->getLogoBase64(),
             'name'         => $name,
             'version'      => $current,
             'type'         => $extension->getType(),
@@ -59,8 +57,6 @@ class Extension extends Repository
 
     public function update(Form $form)
     {
-        $id = $form->getKey();
-
         return true;
     }
 

+ 1 - 1
src/Layout/Asset.php

@@ -148,7 +148,7 @@ class Asset
                 '@admin/dcat/extra/markdown.css',
             ],
         ],
-        '@markdown' => [
+        '@editor-md-form' => [
             'js' => [
                 '@admin/dcat/plugins/editor-md/lib/raphael.min.js',
                 '@admin/dcat/plugins/editor-md/editormd.min.js',

+ 15 - 10
src/Layout/Content.php

@@ -7,7 +7,6 @@ use Dcat\Admin\Admin;
 use Dcat\Admin\Exception\RuntimeException;
 use Dcat\Admin\Traits\HasBuilderEvents;
 use Illuminate\Contracts\Support\Renderable;
-use Illuminate\Support\Str;
 use Illuminate\Support\Traits\Macroable;
 use Illuminate\Support\ViewErrorBag;
 
@@ -437,7 +436,8 @@ class Content implements Renderable
             'navbar_color'      => '',
             'navbar_class'      => 'sticky',
             'footer_type'       => '',
-            'body_class'        => '',
+            'body_class'        => [],
+            'horizontal_menu'   => false,
         ];
 
         $data = array_merge(
@@ -445,19 +445,15 @@ class Content implements Renderable
             $this->config
         );
 
-        // 1.0 版本兼容 sidebar_dark 参数
-        if (empty($data['sidebar_style']) && ! empty($data['sidebar_dark'])) {
-            $data['sidebar_style'] = 'sidebar-dark-white';
-        }
-
         $allOptions = [
             'theme'             => '',
             'footer_type'       => '',
-            'body_class'        => '',
+            'body_class'        => [],
             'sidebar_style'     => ['light' => 'sidebar-light-primary', 'primary' => 'sidebar-primary', 'dark' => 'sidebar-dark-white'],
             'sidebar_collapsed' => [],
             'navbar_color'      => [],
             'navbar_class'      => ['floating' => 'floating-nav', 'sticky' => 'fixed-top', 'hidden' => 'd-none'],
+            'horizontal_menu'   => [],
         ];
 
         $maps = [
@@ -487,18 +483,27 @@ class Content implements Renderable
             }
         }
 
-        if ($data['body_class'] && Str::contains($data['body_class'], 'dark-mode')) {
+        if (! is_array($data['body_class'])) {
+            $data['body_class'] = explode(' ', (string) $data['body_class']);
+        }
+
+        if ($data['body_class'] && in_array('dark-mode', $data['body_class'], true)) {
             $data['sidebar_style'] = 'sidebar-dark-white';
         }
 
+        if ($data['horizontal_menu']) {
+            $data['body_class'][] = 'horizontal-menu';
+        }
+
         return [
             'theme'             => $data['theme'],
             'sidebar_collapsed' => $data['sidebar_collapsed'],
             'navbar_color'      => $data['navbar_color'],
             'navbar_class'      => $allOptions['navbar_class'][$data['navbar_class']],
             'sidebar_class'     => $data['sidebar_collapsed'] ? 'sidebar-collapse' : '',
-            'body_class'        => $data['body_class'],
+            'body_class'        => implode(' ', $data['body_class']),
             'sidebar_style'     => $data['sidebar_style'],
+            'horizontal_menu'   => $data['horizontal_menu'],
         ];
     }
 

+ 17 - 1
src/Layout/Row.php

@@ -11,6 +11,8 @@ class Row implements Renderable
      */
     protected $columns = [];
 
+    protected $noGutters = false;
+
     /**
      * Row constructor.
      *
@@ -48,6 +50,18 @@ class Row implements Renderable
         $this->columns[] = $column;
     }
 
+    /**
+     * @param bool $value
+     *
+     * @return $this
+     */
+    public function noGutters(bool $value = true)
+    {
+        $this->noGutters = $value;
+
+        return $this;
+    }
+
     /**
      * Build row column.
      *
@@ -71,7 +85,9 @@ class Row implements Renderable
      */
     protected function startRow()
     {
-        return '<div class="row">';
+        $noGutters = $this->noGutters ? 'no-gutters' : '';
+
+        return "<div class=\"row {$noGutters}\">";
     }
 
     /**

+ 1 - 1
src/Models/Permission.php

@@ -21,7 +21,7 @@ class Permission extends Model implements Sortable
     /**
      * @var array
      */
-    protected $fillable = ['name', 'slug', 'http_method', 'http_path'];
+    protected $fillable = ['parent_id', 'name', 'slug', 'http_method', 'http_path'];
 
     /**
      * @var array

+ 26 - 1
src/Repositories/EloquentRepository.php

@@ -188,11 +188,36 @@ class EloquentRepository extends Repository implements TreeRepository
         [$column, $type, $cast] = $model->getSort();
 
         if (empty($column) || empty($type)) {
+            $orders = $model->findQueryByMethod('orderBy')->merge($model->findQueryByMethod('orderByDesc'));
+
+            $model->resetOrderBy();
+
+            $orders->each(function ($orderBy) use ($model) {
+                $column = $orderBy['arguments'][0];
+                $type = $orderBy['method'] === 'orderByDesc' ? 'desc' : ($orderBy['arguments'][1] ?? 'asc');
+                $cast = null;
+
+                $this->addOrderBy($model, $column, $type, $cast);
+            });
+
             return;
         }
 
         $model->resetOrderBy();
 
+        $this->addOrderBy($model, $column, $type, $cast);
+    }
+
+    /**
+     * @param Grid\Model $model
+     * @param string $column
+     * @param string $type
+     * @param string $cast
+     *
+     * @throws \Exception
+     */
+    protected function addOrderBy(Grid\Model $model, $column, $type, $cast)
+    {
         $explodedCols = explode('.', $column);
         $isRelation = empty($explodedCols[1]) ? false : method_exists($this->model(), $explodedCols[0]);
 
@@ -345,7 +370,7 @@ class EloquentRepository extends Repository implements TreeRepository
      */
     protected function setPaginate(Grid\Model $model)
     {
-        $paginate = $model->findQueryByMethod('paginate');
+        $paginate = $model->findQueryByMethod('paginate')->first();
 
         $model->rejectQuery(['paginate']);
 

+ 1 - 1
src/Repositories/QueryBuilderRepository.php

@@ -198,7 +198,7 @@ class QueryBuilderRepository extends Repository implements TreeRepository
      */
     protected function setPaginate(Grid\Model $model)
     {
-        $paginate = $model->findQueryByMethod('paginate');
+        $paginate = $model->findQueryByMethod('paginate')->first();
 
         $model->rejectQuery(['paginate']);
 

+ 1 - 1
src/Support/Helper.php

@@ -258,7 +258,7 @@ class Helper
         }
 
         // 判断路由名称
-        if ($request->routeIs($path)) {
+        if ($request->routeIs($path) || $request->routeIs(admin_route_name($path))) {
             return true;
         }
 

+ 1 - 1
src/Support/Setting.php

@@ -26,7 +26,7 @@ class Setting extends Fluent
             return [];
         }
 
-        return json_decode($value, true) ?: [];
+        return is_array($value) ? $value : (json_decode($value, true) ?: []);
     }
 
     /**

+ 35 - 4
src/Support/helpers.php

@@ -437,16 +437,47 @@ if (! function_exists('admin_asset')) {
     }
 }
 
-if (! function_exists('admin_api_route')) {
+if (! function_exists('admin_route')) {
+    /**
+     * 根据路由别名获取url.
+     *
+     * @param string|null $route
+     * @param array $params
+     * @param bool $absolute
+     *
+     * @return string
+     */
+    function admin_route(?string $route, array $params = [], $absolute = true)
+    {
+        return Dcat\Admin\Admin::app()->getRoute($route, $params, $absolute);
+    }
+}
 
+if (! function_exists('admin_route_name')) {
     /**
-     * @param string $path
+     * 获取路由别名.
+     *
+     * @param string|null $route
+     *
+     * @return string
+     */
+    function admin_route_name(?string $route)
+    {
+        return Dcat\Admin\Admin::app()->getRoutePrefix().$route;
+    }
+}
+
+if (! function_exists('admin_api_route_name')) {
+    /**
+     * 获取api的路由别名.
+     *
+     * @param string $route
      *
      * @return string
      */
-    function admin_api_route(?string $path = '')
+    function admin_api_route_name(?string $route = '')
     {
-        return Dcat\Admin\Admin::app()->getCurrentApiRoutePrefix().$path;
+        return Dcat\Admin\Admin::app()->getCurrentApiRoutePrefix().$route;
     }
 }
 

+ 1 - 1
src/Traits/InteractsWithApi.php

@@ -83,7 +83,7 @@ trait InteractsWithApi
      */
     public function getRequestUrl()
     {
-        return $this->url ?: route(admin_api_route('value'));
+        return $this->url ?: route(admin_api_route_name('value'));
     }
 
     /**

+ 1 - 1
src/Traits/LazyWidget.php

@@ -19,7 +19,7 @@ trait LazyWidget
             'renderable' => $this->getRenderableName(),
         ]);
 
-        return route(admin_api_route('render'), $data);
+        return route(admin_api_route_name('render'), $data);
     }
 
     protected function getRenderableName()

+ 1 - 1
src/Traits/ModelTree.php

@@ -65,7 +65,7 @@ trait ModelTree
      */
     public function getDefaultParentId()
     {
-        return empty($this->defaultParentId) ? '0' : $this->defaultParentId;
+        return isset($this->defaultParentId) ? $this->defaultParentId : '0';
     }
 
     /**

+ 6 - 1
src/Tree/Actions/Delete.php

@@ -8,8 +8,13 @@ class Delete extends RowAction
 {
     public function html()
     {
+        $url = request()->fullUrl();
+
         return <<<HTML
-<a href="javascript:void(0);" data-message="ID - {$this->getKey()}" data-url="{$this->resource()}/{$this->getKey()}" data-action="delete"><i class="feather icon-trash"></i>&nbsp;</a>
+<a href="javascript:void(0);" 
+    data-message="ID - {$this->getKey()}" 
+    data-redirect="{$url}"
+    data-url="{$this->resource()}/{$this->getKey()}" data-action="delete"><i class="feather icon-trash"></i>&nbsp;</a>
 HTML;
     }
 }

+ 2 - 2
src/Widgets/DarkModeSwitcher.php

@@ -2,8 +2,8 @@
 
 namespace Dcat\Admin\Widgets;
 
+use Dcat\Admin\Admin;
 use Illuminate\Contracts\Support\Renderable;
-use Illuminate\Support\Str;
 
 class DarkModeSwitcher implements Renderable
 {
@@ -11,7 +11,7 @@ class DarkModeSwitcher implements Renderable
 
     public function __construct(?bool $defaultDarkMode = null)
     {
-        $this->defaultDarkMode = is_null($defaultDarkMode) ? Str::contains(config('admin.layout.body_class'), 'dark-mode') : $defaultDarkMode;
+        $this->defaultDarkMode = is_null($defaultDarkMode) ? Admin::isDarkMode() : $defaultDarkMode;
     }
 
     public function render()

+ 15 - 9
src/Widgets/Form.php

@@ -312,9 +312,7 @@ class Form implements Renderable
     }
 
     /**
-     * @param array|Arrayable|Closure $data
-     *
-     * @return Fluent
+     * @return Fluent|\Illuminate\Database\Eloquent\Model
      */
     public function data()
     {
@@ -332,13 +330,21 @@ class Form implements Renderable
      */
     public function fill($data)
     {
-        $this->data = new Fluent(Helper::array($data));
+        if ($data instanceof \Closure) {
+            $data = $data($this);
+        }
+
+        if (is_array($data)) {
+            $this->data = new Fluent($data);
+        } elseif ($data instanceof Arrayable) {
+            $this->data = $data;
+        }
 
         return $this;
     }
 
     /**
-     * @return Fluent
+     * @return Fluent|\Illuminate\Database\Eloquent\Model
      */
     public function model()
     {
@@ -568,8 +574,8 @@ class Form implements Renderable
         if ($field instanceof Field\File && method_exists($this, 'form')) {
             $formData = [static::REQUEST_NAME => get_called_class()];
 
-            $field->url(route(admin_api_route('form.upload')));
-            $field->deleteUrl(route(admin_api_route('form.destroy-file'), $formData));
+            $field->url(route(admin_api_route_name('form.upload')));
+            $field->deleteUrl(route(admin_api_route_name('form.destroy-file'), $formData));
             $field->withFormData($formData);
         }
     }
@@ -828,10 +834,10 @@ HTML;
 
     protected function prepareHandler()
     {
-        if (method_exists($this, 'handle')) {
+        if ($this->allowAjaxSubmit() && method_exists($this, 'handle')) {
             $addHiddenFields = function () {
                 $this->method('POST');
-                $this->action(route(admin_api_route('form')));
+                $this->action(route(admin_api_route_name('form')));
                 $this->hidden(static::REQUEST_NAME)->default(get_called_class());
                 $this->hidden(static::CURRENT_URL_NAME)->default($this->getCurrentUrl());
 

+ 7 - 4
src/Widgets/LazyTable.php

@@ -105,12 +105,11 @@ class LazyTable extends Widget
 
     protected function addScript()
     {
-        $loader = $this->load ? $this->getLoadScript() : '';
-
         $this->script = <<<JS
 Dcat.init('{$this->getElementSelector()}', function (\$this) {
-    Dcat.grid.AsyncTable({container: \$this});
-    {$loader}
+    Dcat.grid.AsyncTable({container: \$this})
+
+    {$this->getLoadScript()}
 });
 JS;
     }
@@ -120,6 +119,10 @@ JS;
      */
     protected function getLoadScript()
     {
+        if (! $this->load) {
+            return;
+        }
+
         return <<<'JS'
 $this.trigger('table:load');
 JS;

Некоторые файлы не были показаны из-за большого количества измененных файлов