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

Merge branch '2.0-init' into 2.0

jqh 5 лет назад
Родитель
Сommit
cc1ab52753
51 измененных файлов с 603 добавлено и 327 удалено
  1. 1 0
      resources/assets/dcat/extra/grid-extend.js
  2. 37 0
      resources/assets/dcat/js/Dcat.js
  3. 22 0
      resources/assets/dcat/plugins/jquery.initialize/LICENSE
  4. 157 0
      resources/assets/dcat/plugins/jquery.initialize/jquery.initialize.js
  5. 0 0
      resources/assets/dcat/plugins/jquery.initialize/jquery.initialize.min.js
  6. 1 1
      resources/assets/dcat/sass/components/_grid.scss
  7. 2 20
      resources/views/form/container.blade.php
  8. 6 6
      resources/views/form/editor.blade.php
  9. 19 0
      resources/views/form/fields.blade.php
  10. 9 11
      resources/views/form/file.blade.php
  11. 4 4
      resources/views/form/markdown.blade.php
  12. 6 8
      resources/views/form/tree.blade.php
  13. 38 23
      resources/views/widgets/dialogtable.blade.php
  14. 1 19
      resources/views/widgets/form.blade.php
  15. 18 24
      src/Actions/Action.php
  16. 28 6
      src/Actions/HasActionHandler.php
  17. 2 5
      src/Console/ExportSeedCommand.php
  18. 0 5
      src/Form/AbstractTool.php
  19. 5 0
      src/Form/BlockForm.php
  20. 37 0
      src/Form/Concerns/HasLayout.php
  21. 10 0
      src/Form/EmbeddedForm.php
  22. 15 0
      src/Form/Field.php
  23. 2 3
      src/Form/Field/Editor.php
  24. 28 13
      src/Form/NestedForm.php
  25. 4 5
      src/Grid.php
  26. 0 17
      src/Grid/Actions/QuickEdit.php
  27. 0 5
      src/Grid/BatchAction.php
  28. 15 0
      src/Grid/Column.php
  29. 0 5
      src/Grid/GridAction.php
  30. 4 6
      src/Grid/Row.php
  31. 0 28
      src/Grid/RowAction.php
  32. 0 5
      src/Grid/Tools/AbstractTool.php
  33. 2 0
      src/Http/Controllers/UserController.php
  34. 1 1
      src/Http/Middleware/Authenticate.php
  35. 2 2
      src/Http/Middleware/Permission.php
  36. 6 2
      src/Layout/Asset.php
  37. 23 5
      src/Layout/Menu.php
  38. 12 0
      src/Models/Administrator.php
  39. 0 5
      src/Show/AbstractTool.php
  40. 15 0
      src/Show/Field.php
  41. 30 6
      src/Support/helpers.php
  42. 1 1
      src/Traits/HasAuthorization.php
  43. 6 1
      src/Traits/HasHtml.php
  44. 0 5
      src/Tree/AbstractTool.php
  45. 0 25
      src/Tree/RowAction.php
  46. 2 1
      src/Widgets/DialogTable.php
  47. 2 28
      src/Widgets/Form.php
  48. 4 15
      src/Widgets/Lazy.php
  49. 8 8
      src/Widgets/LazyTable.php
  50. 2 2
      src/Widgets/Modal.php
  51. 16 1
      src/Widgets/Widget.php

+ 1 - 0
resources/assets/dcat/extra/grid-extend.js

@@ -225,6 +225,7 @@
 
             $.put({
                 url: _this.options.url.replace(':key', key),
+                data: {_orderable: direction},
                 success: function(data){
                     Dcat.loading(false);
                     _this._req = 0;

+ 37 - 0
resources/assets/dcat/js/Dcat.js

@@ -7,6 +7,7 @@ let $ = jQuery,
     pjaxResponded = false,
     bootingCallbacks = [],
     actions = {},
+    initialized = {},
     defaultOptions = {
         pjax_container_selector: '#pjax-container',
     };
@@ -96,6 +97,42 @@ export default class Dcat {
         _window.Dcat.ready(run);
     }
 
+    /**
+     * 监听动态生成元素.
+     *
+     * @param selector
+     * @param callback
+     * @param options
+     */
+    init(selector, callback, options) {
+        if (initialized[selector]) {
+            initialized[selector].takeRecords();
+            initialized[selector].disconnect();
+        }
+
+        let self = this;
+
+        // 这里必须使用定时器,否则无法立即停止上次绑定的观察回调
+        setTimeout(function () {
+            initialized[selector] = $.initialize(selector, function () {
+                var $this = $(this);
+                if ($this.attr('initialized')) {
+                    return;
+                }
+                $this.attr('initialized', '1');
+
+                // 如果没有ID,则自动生成
+                var id = $this.attr('id');
+                if (! id) {
+                    id = "_"+self.helpers.random();
+                    $this.attr('id', id);
+                }
+
+                callback.call(this, $(this), id)
+            }, options);
+        }, 1)
+    }
+
     /**
      * 主动触发 ready 事件
      */

+ 22 - 0
resources/assets/dcat/plugins/jquery.initialize/LICENSE

@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015-2016 Adam Pietrasiak
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+

+ 157 - 0
resources/assets/dcat/plugins/jquery.initialize/jquery.initialize.js

@@ -0,0 +1,157 @@
+/*!
+ * https://github.com/adampietrasiak/jquery.initialize
+ *
+ * Copyright (c) 2015-2016 Adam Pietrasiak
+ * Released under the MIT license
+ * https://github.com/timpler/jquery.initialize/blob/master/LICENSE
+ *
+ * This is based on MutationObserver
+ * https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
+ */
+;(function ($) {
+
+    "use strict";
+
+    var combinators = [' ', '>', '+', '~']; // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors#Combinators
+    var fraternisers = ['+', '~']; // These combinators involve siblings.
+    var complexTypes = ['ATTR', 'PSEUDO', 'ID', 'CLASS']; // These selectors are based upon attributes.
+
+    //Check if browser supports "matches" function
+    if (!Element.prototype.matches) {
+        Element.prototype.matches = Element.prototype.matchesSelector ||
+            Element.prototype.webkitMatchesSelector ||
+            Element.prototype.mozMatchesSelector ||
+            Element.prototype.msMatchesSelector;
+    }
+
+    // Understand what kind of selector the initializer is based upon.
+    function grok(msobserver) {
+        if (!$.find.tokenize) {
+            // This is an old version of jQuery, so cannot parse the selector.
+            // Therefore we must assume the worst case scenario. That is, that
+            // this is a complicated selector. This feature was available in:
+            // https://github.com/jquery/sizzle/issues/242
+            msobserver.isCombinatorial = true;
+            msobserver.isFraternal = true;
+            msobserver.isComplex = true;
+            return;
+        }
+
+        // Parse the selector.
+        msobserver.isCombinatorial = false;
+        msobserver.isFraternal = false;
+        msobserver.isComplex = false;
+        var token = $.find.tokenize(msobserver.selector);
+        for (var i = 0; i < token.length; i++) {
+            for (var j = 0; j < token[i].length; j++) {
+                if (combinators.indexOf(token[i][j].type) != -1)
+                    msobserver.isCombinatorial = true; // This selector uses combinators.
+
+                if (fraternisers.indexOf(token[i][j].type) != -1)
+                    msobserver.isFraternal = true; // This selector uses sibling combinators.
+
+                if (complexTypes.indexOf(token[i][j].type) != -1)
+                    msobserver.isComplex = true; // This selector is based on attributes.
+            }
+        }
+    }
+
+    // MutationSelectorObserver represents a selector and it's associated initialization callback.
+    var MutationSelectorObserver = function (selector, callback, options) {
+        this.selector = selector.trim();
+        this.callback = callback;
+        this.options = options;
+
+        grok(this);
+    };
+
+    // List of MutationSelectorObservers.
+    var msobservers = [];
+    msobservers.initialize = function (selector, callback, options) {
+
+        // Wrap the callback so that we can ensure that it is only
+        // called once per element.
+        var seen = [];
+        var callbackOnce = function () {
+            if (seen.indexOf(this) == -1) {
+                seen.push(this);
+                $(this).each(callback);
+            }
+        };
+
+        // See if the selector matches any elements already on the page.
+        $(options.target).find(selector).each(callbackOnce);
+
+        // Then, add it to the list of selector observers.
+        var msobserver = new MutationSelectorObserver(selector, callbackOnce, options)
+        this.push(msobserver);
+
+        // The MutationObserver watches for when new elements are added to the DOM.
+        var observer = new MutationObserver(function (mutations) {
+            var matches = [];
+
+            // For each mutation.
+            for (var m = 0; m < mutations.length; m++) {
+
+                // If this is an attributes mutation, then the target is the node upon which the mutation occurred.
+                if (mutations[m].type == 'attributes') {
+                    // Check if the mutated node matchs.
+                    if (mutations[m].target.matches(msobserver.selector))
+                        matches.push(mutations[m].target);
+
+                    // If the selector is fraternal, query siblings of the mutated node for matches.
+                    if (msobserver.isFraternal)
+                        matches.push.apply(matches, mutations[m].target.parentElement.querySelectorAll(msobserver.selector));
+                    else
+                        matches.push.apply(matches, mutations[m].target.querySelectorAll(msobserver.selector));
+                }
+
+                // If this is an childList mutation, then inspect added nodes.
+                if (mutations[m].type == 'childList') {
+                    // Search added nodes for matching selectors.
+                    for (var n = 0; n < mutations[m].addedNodes.length; n++) {
+                        if (!(mutations[m].addedNodes[n] instanceof Element)) continue;
+
+                        // Check if the added node matches the selector
+                        if (mutations[m].addedNodes[n].matches(msobserver.selector))
+                            matches.push(mutations[m].addedNodes[n]);
+
+                        // If the selector is fraternal, query siblings for matches.
+                        if (msobserver.isFraternal)
+                            matches.push.apply(matches, mutations[m].addedNodes[n].parentElement.querySelectorAll(msobserver.selector));
+                        else
+                            matches.push.apply(matches, mutations[m].addedNodes[n].querySelectorAll(msobserver.selector));
+                    }
+                }
+            }
+
+            // For each match, call the callback using jQuery.each() to initialize the element (once only.)
+            for (var i = 0; i < matches.length; i++) {
+                $(matches[i]).each(msobserver.callback);
+            }
+        });
+
+        // Observe the target element.
+        var defaultObeserverOpts = { childList: true, subtree: true, attributes: msobserver.isComplex };
+        observer.observe(options.target, options.observer || defaultObeserverOpts );
+
+        return observer;
+    };
+
+    // Deprecated API (does not work with jQuery >= 3.1.1):
+    $.fn.initialize = function (callback, options) {
+        return msobservers.initialize(this.selector, callback, $.extend({}, $.initialize.defaults, options));
+    };
+
+    // Supported API
+    $.initialize = function (selector, callback, options) {
+        return msobservers.initialize(selector, callback, $.extend({}, $.initialize.defaults, options));
+    };
+
+    // Options
+    $.initialize.defaults = {
+        target: document.documentElement, // Defaults to observe the entire document.
+        observer: null // MutationObserverInit: Defaults to internal configuration if not provided.
+    }
+
+})(jQuery);

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


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

@@ -118,7 +118,7 @@ body:not(.dark-mode) .simple-grid {
     }
 
     .custom-data-table.data-table tbody td {
-      height: 35px;
+      height: 48px;
     }
 
     .custom-data-table.data-table thead th {

+ 2 - 20
resources/views/form/container.blade.php

@@ -4,30 +4,12 @@
         <div class="pull-right">{!! $form->renderTools() !!}</div>
     </div>
 @endif
-<div class="box-body" {!! $tabObj->isEmpty() && !$form->hasRows() ? 'style="margin-top: 10px"' : '' !!} >
+<div class="box-body" {!! $tabObj->isEmpty() && !$form->hasRows() ? 'style="margin-top: 6px"' : '' !!} >
     @if(!$tabObj->isEmpty())
         @include('admin::form.tab', compact('tabObj', 'form'))
     @else
         <div class="fields-group">
-            @if($form->hasRows())
-                <div class="ml-2 mb-2">
-                    @foreach($form->rows() as $row)
-                        {!! $row->render() !!}
-                    @endforeach
-
-                    @foreach($form->fields() as $field)
-                        @if($field instanceof \Dcat\Admin\Form\Field\Hidden)
-                            {!! $field->render() !!}
-                        @endif
-                    @endforeach
-                </div>
-            @elseif($form->layout()->hasColumns())
-                {!! $form->layout()->build() !!}
-            @else
-                @foreach($form->fields() as $field)
-                    {!! $field->render() !!}
-                @endforeach
-            @endif
+            @include('admin::form.fields', ['rows' => $form->rows(), 'fields' => $form->fields(), 'layout' => $form->layout()])
         </div>
     @endif
 </div>

+ 6 - 6
resources/views/form/editor.blade.php

@@ -1,22 +1,22 @@
 <div class="{{$viewClass['form-group']}}">
 
-    <label for="{{$id}}" class="{{$viewClass['label']}} control-label">{!! $label !!}</label>
+    <label class="{{$viewClass['label']}} control-label">{!! $label !!}</label>
 
     <div class="{{$viewClass['field']}}">
 
         @include('admin::form.error')
 
-        <textarea class="form-control {{$class}}" id="{{$id}}" name="{{$name}}" placeholder="{{ $placeholder }}" {!! $attributes !!} >{{ $value }}</textarea>
+        <textarea class="form-control {{$class}} form-field-editor" name="{{$name}}" placeholder="{{ $placeholder }}" {!! $attributes !!} >{{ $value }}</textarea>
 
         @include('admin::form.help-block')
 
     </div>
 </div>
 
-<script require="@tinymce">
-    var opts = {!! $options !!};
+<script require="@tinymce" init=".form-field-editor" id>
+    var opts = {!! admin_javascript_json($options) !!};
 
-    opts.selector = replaceNestedFormIndex(opts.selector);
+    opts.selector = '#'+id;
 
     if (! opts.init_instance_callback) {
         opts.init_instance_callback = function (editor) {
@@ -27,7 +27,7 @@
                     content = content.length && content.join('');
                 }
 
-                $(replaceNestedFormIndex('#{{ $id }}')).val(String(content).replace('<p><br data-mce-bogus="1"></p>', '').replace('<p><br></p>', ''));
+                $this.val(String(content).replace('<p><br data-mce-bogus="1"></p>', '').replace('<p><br></p>', ''));
             });
         }
     }

+ 19 - 0
resources/views/form/fields.blade.php

@@ -0,0 +1,19 @@
+@if($rows)
+    <div class="ml-2 mb-2" style="margin-top: -0.5rem">
+        @foreach($rows as $row)
+            {!! $row->render() !!}
+        @endforeach
+
+        @foreach($fields as $field)
+            @if($field instanceof Dcat\Admin\Form\Field\Hidden)
+                {!! $field->render() !!}
+            @endif
+        @endforeach
+    </div>
+@elseif($layout->hasColumns())
+    {!! $layout->build() !!}
+@else
+    @foreach($fields as $field)
+        {!! $field->render() !!}
+    @endforeach
+@endif

+ 9 - 11
resources/views/form/file.blade.php

@@ -13,7 +13,7 @@
     }
 </style>
 
-<div id="{{ $id }}-container" class="{{$viewClass['form-group']}}">
+<div class="{{$viewClass['form-group']}} form-field-file">
 
     <label for="{{$column}}" class="{{$viewClass['label']}} control-label">{!! $label !!}</label>
 
@@ -21,7 +21,7 @@
 
         @include('admin::form.error')
 
-        <input name="{{ $name }}" id="{{ $id }}" type="hidden" />
+        <input name="{{ $name }}" class="file-input" type="hidden" />
 
         <div class="web-uploader {{ $fileType }}">
             <div class="queueList">
@@ -49,30 +49,28 @@
     </div>
 </div>
 
-<script require="@webuploader">
+<script require="@webuploader" init=".form-field-file">
     var uploader,
         newPage,
-        cID = replaceNestedFormIndex('#{{ $id }}-container'),
-        ID = replaceNestedFormIndex('#{{ $id }}'),
         options = {!! $options !!};
 
     init();
 
     function init() {
         var opts = $.extend({
-            selector: cID,
-            addFileButton: cID+' .add-file-button',
-            inputSelector: ID,
+            selector: $this,
+            addFileButton: $this.find('.add-file-button'),
+            inputSelector: $this.find('.file-input'),
         }, options);
 
         opts.upload = $.extend({
             pick: {
-                id: cID+' .file-picker',
+                id: $this.find('.file-picker'),
                 name: '_file_',
                 label: '<i class="feather icon-folder"></i>&nbsp; {!! trans('admin.uploader.add_new_media') !!}'
             },
-            dnd: cID+' .dnd-area',
-            paste: cID+' .web-uploader'
+            dnd: $this.find('.dnd-area'),
+            paste: $this.find('.web-uploader')
         }, opts);
 
         uploader = Dcat.Uploader(opts);

+ 4 - 4
resources/views/form/markdown.blade.php

@@ -4,13 +4,13 @@
 
 <div class="{{$viewClass['form-group']}}">
 
-    <label for="{{$id}}" class="{{$viewClass['label']}} control-label">{!! $label !!}</label>
+    <label class="{{$viewClass['label']}} control-label">{!! $label !!}</label>
 
     <div class="{{$viewClass['field']}}">
 
         @include('admin::form.error')
 
-        <div id="{{$id}}" class="{{$class}}" {!! $attributes !!}>
+        <div class="{{$class}} form-field-markdown" {!! $attributes !!}>
             <textarea class="d-none" name="{{$name}}" placeholder="{{ $placeholder }}">{!! $value !!}</textarea>
         </div>
 
@@ -19,6 +19,6 @@
     </div>
 </div>
 
-<script require="@markdown">
-    editormd(replaceNestedFormIndex("{{ $id }}"), {!! $options !!});
+<script require="@markdown" init=".form-field-markdown" id>
+    editormd(id, {!! $options !!});
 </script>

+ 6 - 8
resources/views/form/tree.blade.php

@@ -2,7 +2,7 @@
 
     <label class="{{$viewClass['label']}} control-label">{!! $label !!}</label>
 
-    <div id="{{ $id }}" class="{{$viewClass['field']}}">
+    <div class="{{$viewClass['field']}} form-field-tree">
 
         @include('admin::form.error')
 
@@ -22,21 +22,19 @@
     </div>
 </div>
 
-<script require="@jstree">
-    var selector = replaceNestedFormIndex('#{{ $id }}'),
-        tree = selector+' .jstree-wrapper .da-tree',
-        $tree = $(tree),
-        $input = $(selector+' .hidden-input'),
+<script require="@jstree" init=".form-field-tree">
+    var $tree = $this.find('.jstree-wrapper .da-tree'),
+        $input = $this.find('.hidden-input'),
         opts = {!! admin_javascript_json($options) !!},
         parents = {!! json_encode($parents) !!};
 
     opts.core = opts.core || {};
     opts.core.data = {!! json_encode($nodes) !!};
 
-    $(document).on("click", selector+" input[value=1]", function () {
+    $this.find('input[value=1]').on("click", function () {
         $(this).parents('.jstree-wrapper').find('.da-tree').jstree($(this).prop("checked") ? "check_all" : "uncheck_all");
     });
-    $(document).on("click", selector+" input[value=2]", function () {
+    $this.find('input[value=2]').on("click", function () {
         $(this).parents('.jstree-wrapper').find('.da-tree').jstree($(this).prop("checked") ? "open_all" : "close_all");
     });
 

+ 38 - 23
resources/views/widgets/dialogtable.blade.php

@@ -1,7 +1,7 @@
-<span>
-    <span style="cursor: pointer" id="button-{{ $id }}">{!! $button !!}</span>
+<span class="{{ $class }}">
+    <span style="cursor: pointer" class="switch-dialog">{!! $button !!}</span>
 
-    <template id="temp-{{ $id }}">
+    <template>
         <div {!! $attributes !!}>
             <div class="p-2 dialog-body">{!! $table !!}</div>
 
@@ -12,18 +12,18 @@
     </template>
 </span>
 
-<script>
-    var id = replaceNestedFormIndex('{{ $id }}'),
-        area = screen.width <= 850 ? ['100%', '100%',] : '{{ $width }}',
+<script init=".{{ $class }}">
+    var area = screen.width <= 850 ? ['100%', '100%',] : '{{ $width }}',
         offset = screen.width <= 850 ? 0 : '70px',
-        _id, _tempId, _btnId, _tb;
+        _tb = '.async-table', _container = '.dialog-table',
+        _id, _temp, _btnId;
 
     setId(id);
 
     function hidden(index) {
         {!! $events['hidden'] !!}
 
-        $(_id).trigger('dialog:hidden');
+        getLayer(index).find(_container).trigger('dialog:hidden');
     }
 
     function open(btn) {
@@ -34,28 +34,32 @@
             offset: offset,
             maxmin: false,
             resize: false,
-            content: $(_tempId).html(),
+            content: $(_temp).html(),
             success: function(layero, index) {
-                $(_id).attr('layer', index);
+                var $c = getLayer(index).find(_container),
+                    $t = getLayer(index).find(_tb);
 
-                setDataId($(_id));
+                $c.attr('layer', index);
+
+                setDataId($c);
+                setMaxHeight(index);
 
                 {!! $events['shown'] !!}
 
                 @if(!empty($events['load']))
-                    $(_tb).on('table:loaded', function (event) { {!! $events['load'] !!} });
+                $t.on('table:loaded', function (event) { {!! $events['load'] !!} });
                 @endif
 
                 setTimeout(function () {
-                    Dcat.grid.AsyncTable({container: _tb});
+                    Dcat.grid.AsyncTable({container: $t});
 
-                    $(_tb).trigger('table:load');
+                    $t.trigger('table:load');
                 }, 100);
 
-                $(_id).trigger('dialog:shown');
+                $c.trigger('dialog:shown');
 
-                $(_id).on('dialog:open', openDialog);
-                $(_id).on('dialog:close', closeDialog)
+                $c.on('dialog:open', openDialog);
+                $c.on('dialog:close', closeDialog)
             },
             cancel: function (index) {
                 btn && btn.removeAttr('layer');
@@ -76,11 +80,9 @@
     function setId(val) {
         if (! val) return;
 
-        id = val;
-        _id = '#'+id;
-        _tempId = '#temp-'+id;
-        _btnId = '#button-'+id;
-        _tb = _id+' .async-table';
+        _id = '#' + val;
+        _temp = _id + ' template';
+        _btnId = _id + ' .switch-dialog';
     }
 
     function openDialog () {
@@ -92,10 +94,14 @@
         }
     }
 
+    function getLayer(index) {
+        return $('#layui-layer'+index)
+    }
+
     function closeDialog() {
         var index = $(this).attr('layer');
 
-        $(_id).removeAttr('layer');
+        getLayer(index).find(_container).removeAttr('layer');
         $(_btnId).removeAttr('layer');
 
         if (index) {
@@ -104,5 +110,14 @@
         }
     }
 
+    function setMaxHeight(index) {
+        var maxHeight = ($(window).height() - 250);
+        if (maxHeight < 250) {
+            maxHeight = maxHeight + 120;
+        }
+
+        getLayer(index).find('.layui-layer-content').css({'max-height': maxHeight});
+    }
+
     $(_btnId).on('click', openDialog);
 </script>

+ 1 - 19
resources/views/widgets/form.blade.php

@@ -9,25 +9,7 @@
                 @endif
             @endforeach
         @else
-            @if($rows)
-                <div class="ml-2 mb-2">
-                    @foreach($rows as $row)
-                        {!! $row->render() !!}
-                    @endforeach
-
-                    @foreach($fields as $field)
-                        @if($field instanceof \Dcat\Admin\Form\Field\Hidden)
-                            {!! $field->render() !!}
-                        @endif
-                    @endforeach
-                </div>
-            @elseif($layout->hasColumns())
-                {!! $layout->build() !!}
-            @else
-                @foreach($fields as $field)
-                    {!! $field->render() !!}
-                @endforeach
-            @endif
+            @include('admin::form.fields')
         @endif
     </div>
 

+ 18 - 24
src/Actions/Action.php

@@ -6,6 +6,7 @@ use Dcat\Admin\Admin;
 use Dcat\Admin\Support\Helper;
 use Dcat\Admin\Traits\HasHtmlAttributes;
 use Illuminate\Contracts\Support\Renderable;
+use Illuminate\Support\Str;
 
 /**
  * Class Action.
@@ -17,11 +18,6 @@ abstract class Action implements Renderable
     use HasHtmlAttributes;
     use HasActionHandler;
 
-    /**
-     * @var array
-     */
-    protected static $selectors = [];
-
     /**
      * @var array|string
      */
@@ -37,11 +33,6 @@ abstract class Action implements Renderable
      */
     protected $selector;
 
-    /**
-     * @var string
-     */
-    public $selectorPrefix = '.admin-action-';
-
     /**
      * @var string
      */
@@ -148,11 +139,19 @@ abstract class Action implements Renderable
      */
     public function selector()
     {
-        if (is_null($this->selector)) {
-            return $this->makeSelector($this->selectorPrefix);
-        }
+        return $this->selector ?: ($this->selector = $this->makeSelector());
+    }
 
-        return $this->selector;
+    /**
+     * 生成选择器.
+     *
+     * @param string $prefix
+     *
+     * @return string
+     */
+    public function makeSelector()
+    {
+        return 'act-'.Str::random();
     }
 
     /**
@@ -161,15 +160,10 @@ abstract class Action implements Renderable
      *
      * @return string
      */
-    public function makeSelector($prefix, $class = null)
+    public function getSelectorKey($prefix, $class)
     {
-        $class = $prefix.'-'.($class ?: static::class);
-
-        if (! isset(static::$selectors[$class])) {
-            static::$selectors[$class] = uniqid($prefix);
-        }
-
-        return static::$selectors[$class];
+        return $prefix.'-'.($class ?: static::class)
+            .md5($this->normalizeConfirmData().$this->normalizeParameters());
     }
 
     /**
@@ -231,7 +225,7 @@ HTML;
 
         $this->prepareHandler();
 
-        $this->setupHtmlAttributes();
+        $this->setUpHtmlAttributes();
 
         if ($script = $this->script()) {
             Admin::script($script);
@@ -251,7 +245,7 @@ HTML;
     /**
      * @return void
      */
-    protected function setupHtmlAttributes()
+    protected function setUpHtmlAttributes()
     {
         $this->addHtmlClass($this->getElementClass());
 

+ 28 - 6
src/Actions/HasActionHandler.php

@@ -15,6 +15,10 @@ trait HasActionHandler
      * @var Response
      */
     protected $response;
+    
+    private $confirmString;
+    
+    private $paramString;
 
     /**
      * @return Response
@@ -70,14 +74,32 @@ trait HasActionHandler
     }
 
     /**
-     * @return void
+     * @return string
      */
-    protected function addHandlerScript()
+    protected function normalizeConfirmData()
     {
-        $data = json_encode($this->parameters());
+        if ($this->confirmString !== null) {
+            return $this->confirmString;
+        }
+        
         $confirm = $this->confirm();
-        $confirm = $confirm ? admin_javascript_json((array) $confirm) : 'false';
 
+        return $this->confirmString = ($confirm ? admin_javascript_json((array) $confirm) : 'false');
+    }
+
+    /**
+     * @return string
+     */
+    protected function normalizeParameters()
+    {
+        return $this->paramString ?: ($this->paramString = json_encode($this->parameters()));
+    }
+
+    /**
+     * @return void
+     */
+    protected function addHandlerScript()
+    {
         $script = <<<JS
 Dcat.Action({
     selector: '{$this->selector()}',
@@ -85,8 +107,8 @@ Dcat.Action({
     method: '{$this->method()}',
     key: '{$this->getKey()}',
     url: '{$this->handlerRoute()}',
-    data: {$data},
-    confirm: {$confirm},
+    data: {$this->normalizeParameters()},
+    confirm: {$this->normalizeConfirmData()},
     calledClass: '{$this->makeCalledClass()}',
     before: {$this->actionScript()},
     html: {$this->handleHtmlResponse()},

+ 2 - 5
src/Console/ExportSeedCommand.php

@@ -60,11 +60,8 @@ class ExportSeedCommand extends Command
             $replaces = array_merge($replaces, [
                 'ClassUsers'            => config('admin.database.users_model'),
                 'TableRoleUsers'        => config('admin.database.role_users_table'),
-                'TablePermissionsUsers' => config('admin.database.user_permissions_table'),
-
-                'ArrayUsers'                 => $this->getTableDataArrayAsString(config('admin.database.users_table'), $exceptFields),
-                'ArrayPivotRoleUsers'        => $this->getTableDataArrayAsString(config('admin.database.role_users_table'), $exceptFields),
-                'ArrayPivotPermissionsUsers' => $this->getTableDataArrayAsString(config('admin.database.user_permissions_table'), $exceptFields),
+                'ArrayUsers'            => $this->getTableDataArrayAsString(config('admin.database.users_table'), $exceptFields),
+                'ArrayPivotRoleUsers'   => $this->getTableDataArrayAsString(config('admin.database.role_users_table'), $exceptFields),
             ]);
         } else {
             $contents = preg_replace('/\/\/ users tables[\s\S]*?(?=\/\/ finish)/mu', '', $contents);

+ 0 - 5
src/Form/AbstractTool.php

@@ -12,11 +12,6 @@ abstract class AbstractTool extends Action
      */
     protected $parent;
 
-    /**
-     * @var string
-     */
-    public $selectorPrefix = '.form-tool-action-';
-
     /**
      * @var string
      */

+ 5 - 0
src/Form/BlockForm.php

@@ -144,6 +144,11 @@ HTML;
         return $this->form->getKey();
     }
 
+    public function model()
+    {
+        return $this->form->model();
+    }
+
     public function __call($method, $arguments)
     {
         try {

+ 37 - 0
src/Form/Concerns/HasLayout.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace Dcat\Admin\Form\Concerns;
+
+use Closure;
+use Dcat\Admin\Form\Layout;
+
+trait HasLayout
+{
+    /**
+     * @var Layout
+     */
+    protected $layout;
+
+    /**
+     * @param int|float $width
+     * @param Closure   $callback
+     *
+     * @return $this
+     */
+    public function column($width, Closure $callback)
+    {
+        $this->layout()->onlyColumn($width, function () use ($callback) {
+            $callback($this);
+        });
+
+        return $this;
+    }
+
+    /**
+     * @return Layout
+     */
+    public function layout()
+    {
+        return $this->layout ?: ($this->layout = new Layout($this));
+    }
+}

+ 10 - 0
src/Form/EmbeddedForm.php

@@ -128,6 +128,16 @@ class EmbeddedForm
         return $this;
     }
 
+    public function getKey()
+    {
+        return $this->parent->getKey();
+    }
+
+    public function model()
+    {
+        return $this->parent->model();
+    }
+
     /**
      * Set original values for fields.
      *

+ 15 - 0
src/Form/Field.php

@@ -1107,6 +1107,21 @@ class Field implements Renderable
         return implode(' ', $this->labelClass);
     }
 
+    /**
+     * @param  mixed  $value
+     * @param  callable  $callback
+     *
+     * @return $this|mixed
+     */
+    public function when($value, $callback)
+    {
+        if ($value) {
+            return $callback($this, $value) ?: $this;
+        }
+
+        return $this;
+    }
+
     public function setFormGroupClass($labelClass, bool $append = true)
     {
         $this->formGroupClass = $append

+ 2 - 3
src/Form/Field/Editor.php

@@ -108,11 +108,10 @@ class Editor extends Field
     }
 
     /**
-     * @return string
+     * @return array
      */
     protected function formatOptions()
     {
-        $this->options['selector'] = '#'.$this->id;
         $this->options['language'] = config('app.locale');
         $this->options['readonly'] = ! empty($this->attributes['readonly']) || ! empty($this->attributes['disabled']);
 
@@ -120,7 +119,7 @@ class Editor extends Field
             $this->options['images_upload_url'] = $this->defaultImageUploadUrl();
         }
 
-        return JavaScript::format($this->options);
+        return $this->options;
     }
 
     /**

+ 28 - 13
src/Form/NestedForm.php

@@ -2,10 +2,7 @@
 
 namespace Dcat\Admin\Form;
 
-use Dcat\Admin\Admin;
 use Dcat\Admin\Form;
-use Dcat\Admin\Form\Field\MultipleSelectTable;
-use Dcat\Admin\Form\Field\SelectTable;
 use Dcat\Admin\Support\Helper;
 use Dcat\Admin\Widgets\Form as WidgetForm;
 use Illuminate\Support\Arr;
@@ -70,6 +67,11 @@ use Illuminate\Support\Collection;
  */
 class NestedForm
 {
+    use Form\Concerns\HandleCascadeFields;
+    use Form\Concerns\HasRows;
+    use Form\Concerns\HasTabs;
+    use Form\Concerns\HasLayout;
+
     const DEFAULT_KEY_NAME = '__LA_KEY__';
 
     const REMOVE_FLAG_NAME = '_remove_';
@@ -143,11 +145,16 @@ class NestedForm
      *
      * @return Form
      */
-    public function getForm()
+    public function form()
     {
         return $this->form;
     }
 
+    public function model()
+    {
+        return $this->form->model();
+    }
+
     /**
      * Set original values for fields.
      *
@@ -438,15 +445,7 @@ class NestedForm
         return Helper::formatElementName($name ?: $this->relationName);
     }
 
-    /**
-     * Add nested-form fields dynamically.
-     *
-     * @param string $method
-     * @param array  $arguments
-     *
-     * @return mixed
-     */
-    public function __call($method, $arguments)
+    protected function resolveField($method, $arguments)
     {
         if ($className = Form::findFieldClass($method)) {
             $column = Arr::get($arguments, 0, '');
@@ -458,6 +457,22 @@ class NestedForm
 
             $field = $this->formatField($field);
 
+            return $field;
+        }
+    }
+
+    /**
+     * Add nested-form fields dynamically.
+     *
+     * @param string $method
+     * @param array  $arguments
+     *
+     * @return mixed
+     */
+    public function __call($method, $arguments)
+    {
+        if ($field = $this->resolveField($method, $arguments)) {
+
             $this->pushField($field);
 
             return $field;

+ 4 - 5
src/Grid.php

@@ -35,8 +35,8 @@ class Grid
     use Concerns\CanFixColumns;
     use Concerns\CanHidesColumns;
     use Macroable {
-            __call as macroCall;
-        }
+        __call as macroCall;
+    }
 
     const CREATE_MODE_DEFAULT = 'default';
     const CREATE_MODE_DIALOG = 'dialog';
@@ -505,9 +505,8 @@ class Grid
         $keyName = $this->getKeyName();
 
         $this->prependColumn(
-            Grid\Column::SELECT_COLUMN_NAME,
-            $rowSelector->renderHeader()
-        )->display(function () use ($rowSelector, $keyName) {
+            Grid\Column::SELECT_COLUMN_NAME
+        )->setLabel($rowSelector->renderHeader())->display(function () use ($rowSelector, $keyName) {
             return $rowSelector->renderColumn($this, $this->{$keyName});
         });
     }

+ 0 - 17
src/Grid/Actions/QuickEdit.php

@@ -39,21 +39,4 @@ class QuickEdit extends RowAction
 
         return parent::render(); // TODO: Change the autogenerated stub
     }
-
-    /**
-     * @param string $prefix
-     * @param string $class
-     *
-     * @return string
-     */
-    public function makeSelector($prefix, $class = null)
-    {
-        $class = $class ?: static::class;
-
-        if (! isset(static::$selectors[$class])) {
-            static::$selectors[$class] = uniqid($prefix);
-        }
-
-        return static::$selectors[$class];
-    }
 }

+ 0 - 5
src/Grid/BatchAction.php

@@ -4,11 +4,6 @@ namespace Dcat\Admin\Grid;
 
 abstract class BatchAction extends GridAction
 {
-    /**
-     * @var string
-     */
-    public $selectorPrefix = '.grid-batch-action-';
-
     /**
      * {@inheritdoc}
      */

+ 15 - 0
src/Grid/Column.php

@@ -720,6 +720,21 @@ class Column
         return implode(' ', $attrArr);
     }
 
+    /**
+     * @param  mixed  $value
+     * @param  callable  $callback
+     *
+     * @return $this|mixed
+     */
+    public function when($value, $callback)
+    {
+        if ($value) {
+            return $callback($this, $value) ?: $this;
+        }
+
+        return $this;
+    }
+
     /**
      * Passes through all unknown calls to builtin displayer or supported displayer.
      *

+ 0 - 5
src/Grid/GridAction.php

@@ -15,11 +15,6 @@ abstract class GridAction extends Action
      */
     protected $parent;
 
-    /**
-     * @var string
-     */
-    public $selectorPrefix = '.grid-action-';
-
     /**
      * @param Grid $grid
      *

+ 4 - 6
src/Grid/Row.php

@@ -4,6 +4,7 @@ namespace Dcat\Admin\Grid;
 
 use Closure;
 use Dcat\Admin\Grid;
+use Dcat\Admin\Support\Helper;
 use Illuminate\Contracts\Support\Arrayable;
 use Illuminate\Contracts\Support\Htmlable;
 use Illuminate\Contracts\Support\Jsonable;
@@ -86,12 +87,7 @@ class Row implements Arrayable
      */
     private function formatHtmlAttributes($attributes = [])
     {
-        $attrArr = [];
-        foreach ($attributes as $name => $val) {
-            $attrArr[] = "$name=\"$val\"";
-        }
-
-        return implode(' ', $attrArr);
+        return Helper::buildHtmlAttributes($attributes);
     }
 
     /**
@@ -102,6 +98,8 @@ class Row implements Arrayable
     public function setAttributes(array $attributes)
     {
         $this->attributes = $attributes;
+
+        return $this;
     }
 
     /**

+ 0 - 28
src/Grid/RowAction.php

@@ -3,7 +3,6 @@
 namespace Dcat\Admin\Grid;
 
 use Illuminate\Support\Fluent;
-use Illuminate\Support\Str;
 
 abstract class RowAction extends GridAction
 {
@@ -17,11 +16,6 @@ abstract class RowAction extends GridAction
      */
     protected $column;
 
-    /**
-     * @var string
-     */
-    public $selectorPrefix = '.grid-row-action-';
-
     /**
      * Get primary key value of current row.
      *
@@ -82,26 +76,4 @@ abstract class RowAction extends GridAction
 
         return $this;
     }
-
-    /**
-     * 生成选择器.
-     * 需要保证每个行操作的选择器都不同.
-     *
-     * @param string $prefix
-     * @param string $class
-     *
-     * @return string
-     */
-    public function makeSelector($prefix, $class = null)
-    {
-        $class = $class ?: static::class;
-
-        $key = $prefix.'-'.$class.'-'.$this->getKey();
-
-        if (! isset(static::$selectors[$key])) {
-            static::$selectors[$key] = $prefix.Str::random(8);
-        }
-
-        return static::$selectors[$key];
-    }
 }

+ 0 - 5
src/Grid/Tools/AbstractTool.php

@@ -6,11 +6,6 @@ use Dcat\Admin\Grid;
 
 abstract class AbstractTool extends Grid\GridAction
 {
-    /**
-     * @var string
-     */
-    public $selectorPrefix = '.tool-action-';
-
     /**
      * @var string
      */

+ 2 - 0
src/Http/Controllers/UserController.php

@@ -129,6 +129,8 @@ class UserController extends AdminController
 
             $form->display('id', 'ID');
 
+            $form->editor('test');
+
             $form->text('username', trans('admin.username'))
                 ->required()
                 ->creationRules(['required', "unique:{$connection}.{$userTable}"])

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

@@ -47,7 +47,7 @@ class Authenticate
      *
      * @return bool
      */
-    protected function shouldPassThrough($request)
+    public static function shouldPassThrough($request)
     {
         $excepts = array_merge(
             (array) config('admin.auth.except', []),

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

@@ -95,9 +95,9 @@ class Permission
      *
      * @return bool
      */
-    protected function shouldPassThrough($request)
+    public function shouldPassThrough($request)
     {
-        if ($this->isApiRoute($request)) {
+        if ($this->isApiRoute($request) || Authenticate::shouldPassThrough($request)) {
             return true;
         }
 

+ 6 - 2
src/Layout/Asset.php

@@ -39,6 +39,9 @@ class Asset
             'js'  => '@admin/dcat/plugins/vendors.min.js',
             'css' => '@admin/dcat/plugins/vendors.min.css',
         ],
+        '@jquery.initialize' => [
+            'js' => '@admin/dcat/plugins/jquery.initialize/jquery.initialize.min.js',
+        ],
         '@datatables' => [
             'css' => '@admin/dcat/plugins/tables/datatable/datatables.min.css',
         ],
@@ -240,6 +243,7 @@ class Asset
         'pjax'      => '@pjax',
         'validator' => '@validator',
         'layer'     => '@layer',
+        'init'      => '@jquery.initialize',
     ];
 
     /**
@@ -568,8 +572,8 @@ class Asset
     protected function addFontCss()
     {
         $this->fonts && ($this->baseCss = array_merge(
-                $this->baseCss,
-                (array) $this->fonts
+            $this->baseCss,
+            (array) $this->fonts
         ));
     }
 

+ 23 - 5
src/Layout/Menu.php

@@ -161,11 +161,11 @@ class Menu
      */
     public function visible($item)
     {
-        if (! $this->checkPermission($item)) {
-            return false;
-        }
-
-        if (! $this->checkExtension($item)) {
+        if (
+            ! $this->checkPermission($item)
+            || ! $this->checkExtension($item)
+            || ! $this->userCanSeeMenu($item)
+        ) {
             return false;
         }
 
@@ -199,6 +199,24 @@ class Menu
         return $extension->enabled();
     }
 
+    /**
+     * 判断用户
+     *
+     * @param array|\Dcat\Admin\Models\Menu $item
+     *
+     * @return bool
+     */
+    protected function userCanSeeMenu($item)
+    {
+        $user = Admin::user();
+
+        if (! $user || ! method_exists($user, 'canSeeMenu')) {
+            return true;
+        }
+
+        return $user->canSeeMenu($item);
+    }
+
     /**
      * 判断权限.
      *

+ 12 - 0
src/Models/Administrator.php

@@ -75,4 +75,16 @@ class Administrator extends Model implements AuthenticatableContract
 
         return $this->belongsToMany($relatedModel, $pivotTable, 'user_id', 'role_id');
     }
+
+    /**
+     * 判断是否允许查看菜单.
+     *
+     * @param array|Menu $menu
+     *
+     * @return bool
+     */
+    public function canSeeMenu($menu)
+    {
+        return true;
+    }
 }

+ 0 - 5
src/Show/AbstractTool.php

@@ -12,11 +12,6 @@ abstract class AbstractTool extends Action
      */
     protected $parent;
 
-    /**
-     * @var string
-     */
-    public $selectorPrefix = '.show-tool-action-';
-
     /**
      * @var string
      */

+ 15 - 0
src/Show/Field.php

@@ -564,6 +564,21 @@ HTML;
         return $this;
     }
 
+    /**
+     * @param  mixed  $value
+     * @param  callable  $callback
+     *
+     * @return $this|mixed
+     */
+    public function when($value, $callback)
+    {
+        if ($value) {
+            return $callback($this, $value) ?: $this;
+        }
+
+        return $this;
+    }
+
     /**
      * @param string $method
      * @param array  $arguments

+ 30 - 6
src/Support/helpers.php

@@ -494,21 +494,45 @@ if (! function_exists('admin_color')) {
     }
 }
 
-if (! function_exists('admin_javascript')) {
+if (! function_exists('admin_js')) {
     /**
-     * @param string|null $color
+     * @param string|array $js
      *
-     * @return string|\Dcat\Admin\Color
+     * @return void
+     */
+    function admin_js($js)
+    {
+        Admin::js($js);
+    }
+}
+
+if (! function_exists('admin_css')) {
+    /**
+     * @param string|array $css
+     *
+     * @return void
+     */
+    function admin_css($css)
+    {
+        Admin::css($css);
+    }
+}
+
+if (! function_exists('admin_require_assets')) {
+    /**
+     * @param string|array $asset
+     *
+     * @return void
      */
-    function admin_javascript($data)
+    function admin_require_assets($asset)
     {
-        return new Dcat\Admin\Support\JavaScript($data);
+        Admin::requireAssets($asset);
     }
 }
 
 if (! function_exists('admin_javascript_json')) {
     /**
-     * @param string|null $color
+     * @param array|object $data
      *
      * @return string
      */

+ 1 - 1
src/Traits/HasAuthorization.php

@@ -3,7 +3,7 @@
 namespace Dcat\Admin\Traits;
 
 use Dcat\Admin\Admin;
-use Dcat\Admin\Models\HasPermissions;
+use Dcat\Admin\Traits\HasPermissions;
 use Illuminate\Contracts\Auth\Authenticatable;
 use Illuminate\Database\Eloquent\Model;
 

+ 6 - 1
src/Traits/HasHtml.php

@@ -120,7 +120,12 @@ trait HasHtml
                 static::asset()->require(explode(',', $require));
             }
 
-            $script = "(function () {{$script}\n})();";
+            if ($init = $element->getAttribute('init')) {
+                $script = "Dcat.init('{$init}', function (\$this, id) { {$script}\n });";
+            } else {
+                $script = "(function () {{$script}\n})();";
+            }
+
 
             if ($element->hasAttribute('once')) {
                 return static::script($script);

+ 0 - 5
src/Tree/AbstractTool.php

@@ -12,11 +12,6 @@ abstract class AbstractTool extends Action
      */
     protected $parent;
 
-    /**
-     * @var string
-     */
-    public $selectorPrefix = '.tree-tool-action-';
-
     /**
      * @var string
      */

+ 0 - 25
src/Tree/RowAction.php

@@ -3,7 +3,6 @@
 namespace Dcat\Admin\Tree;
 
 use Dcat\Admin\Actions\Action;
-use Illuminate\Support\Str;
 
 class RowAction extends Action
 {
@@ -17,8 +16,6 @@ class RowAction extends Action
      */
     protected $row;
 
-    public $selectorPrefix = '.tree-row-action-';
-
     /**
      * 获取主键值.
      *
@@ -67,26 +64,4 @@ class RowAction extends Action
     {
         $this->row = $row;
     }
-
-    /**
-     * 生成选择器.
-     * 需要保证每个行操作的选择器都不同.
-     *
-     * @param string $prefix
-     * @param string $class
-     *
-     * @return string
-     */
-    public function makeSelector($prefix, $class = null)
-    {
-        $class = $class ?: static::class;
-
-        $key = $prefix.'-'.$class.'-'.$this->getKey();
-
-        if (! isset(static::$selectors[$key])) {
-            static::$selectors[$key] = $prefix.Str::random(8);
-        }
-
-        return static::$selectors[$key];
-    }
 }

+ 2 - 1
src/Widgets/DialogTable.php

@@ -51,8 +51,9 @@ class DialogTable extends Widget
         $this->title($title);
         $this->from($table);
 
+        $this->elementClass = 'dialog-table-container';
+
         $this->class('dialog-table');
-        $this->id('dialog-table-'.Str::random(8));
     }
 
     /**

+ 2 - 28
src/Widgets/Form.php

@@ -7,6 +7,7 @@ use Dcat\Admin\Admin;
 use Dcat\Admin\Contracts\LazyRenderable;
 use Dcat\Admin\Exception\RuntimeException;
 use Dcat\Admin\Form\Concerns\HandleCascadeFields;
+use Dcat\Admin\Form\Concerns\HasLayout;
 use Dcat\Admin\Form\Concerns\HasRows;
 use Dcat\Admin\Form\Concerns\HasTabs;
 use Dcat\Admin\Form\Field;
@@ -92,6 +93,7 @@ class Form implements Renderable
     use HandleCascadeFields;
     use HasRows;
     use HasTabs;
+    use HasLayout;
     use HasFormResponse {
         setCurrentUrl as defaultSetCurrentUrl;
     }
@@ -113,11 +115,6 @@ class Form implements Renderable
      */
     protected $fields;
 
-    /**
-     * @var Layout
-     */
-    protected $layout;
-
     /**
      * @var array
      */
@@ -380,29 +377,6 @@ class Form implements Renderable
         return $this->fields;
     }
 
-    /**
-     * @param int|float $width
-     * @param Closure   $callback
-     *
-     * @return $this
-     */
-    public function column($width, \Closure $callback)
-    {
-        $this->layout()->onlyColumn($width, function () use ($callback) {
-            $callback($this);
-        });
-
-        return $this;
-    }
-
-    /**
-     * @return Layout
-     */
-    public function layout()
-    {
-        return $this->layout ?: ($this->layout = new Layout($this));
-    }
-
     /**
      * Validate this form fields.
      *

+ 4 - 15
src/Widgets/Lazy.php

@@ -18,8 +18,7 @@ class Lazy extends Widget
         $this->setRenderable($renderable);
         $this->load($load);
 
-        $this->class('lazy-box');
-        $this->id('lazy-'.Str::random(8));
+        $this->class($this->elementClass = 'lazy-box');
     }
 
     /**
@@ -36,29 +35,19 @@ class Lazy extends Widget
         return $this;
     }
 
-    /**
-     * 获取触发异步渲染JS代码.
-     *
-     * @return string
-     */
-    public function getLoadScript()
-    {
-        return "$('{$this->getElementSelector()}').trigger('{$this->target}:load');";
-    }
-
     protected function addScript()
     {
         $loader = $this->load ? "target.trigger('{$this->target}:load')" : '';
 
         $this->script = <<<JS
-(function () {
-    var target = $('{$this->getElementSelector()}'), body = target;
+Dcat.init('{$this->getElementSelector()}', function (target) {
+    var body = target;
     {$this->getRenderableScript()}
 
     body.html('<div style="min-height:150px"></div>').loading();
     
     {$loader}
-})();
+});
 JS;
     }
 

+ 8 - 8
src/Widgets/LazyTable.php

@@ -3,7 +3,6 @@
 namespace Dcat\Admin\Widgets;
 
 use Dcat\Admin\Grid\LazyRenderable;
-use Illuminate\Support\Str;
 
 class LazyTable extends Widget
 {
@@ -41,8 +40,7 @@ class LazyTable extends Widget
         $this->from($renderable);
         $this->load($load);
 
-        $this->class('async-table');
-        $this->id('async-table-'.Str::random(8));
+        $this->class($this->elementClass = 'async-table');
     }
 
     /**
@@ -100,7 +98,7 @@ class LazyTable extends Widget
      */
     public function onLoad(string $script)
     {
-        $this->script .= "$('{$this->getElementSelector()}').on('table:loaded', function (event) { {$script} });";
+        $this->script .= "\$this.on('table:loaded', function (event) { {$script} });";
 
         return $this;
     }
@@ -110,18 +108,20 @@ class LazyTable extends Widget
         $loader = $this->load ? $this->getLoadScript() : '';
 
         $this->script = <<<JS
-Dcat.grid.AsyncTable({container: '{$this->getElementSelector()}'});
-{$loader}
+Dcat.init('{$this->getElementSelector()}', function (\$this) {
+    Dcat.grid.AsyncTable({container: \$this});
+    {$loader}
+});
 JS;
     }
 
     /**
      * @return string
      */
-    public function getLoadScript()
+    protected function getLoadScript()
     {
         return <<<JS
-$('{$this->getElementSelector()}').trigger('table:load');
+\$this.trigger('table:load');
 JS;
     }
 

+ 2 - 2
src/Widgets/Modal.php

@@ -174,7 +174,7 @@ class Modal extends Widget
                 ->simple()
                 ->load(false);
 
-            $this->onShow($table->getLoadScript());
+            $this->onShow("target.find('{$table->getElementSelector()}').trigger('table:load')");
         }
 
         if ($content instanceof LazyRenderable) {
@@ -303,7 +303,7 @@ class Modal extends Widget
 
         $this->script = <<<JS
 (function () {
-    var target = $('{$this->getElementSelector()}'), body = target.find('.modal-body');
+    var target = $('#{$this->id()}'), body = target.find('.modal-body');
     {$this->getRenderableScript()}
     {$script}
 })();

+ 16 - 1
src/Widgets/Widget.php

@@ -48,6 +48,11 @@ abstract class Widget implements Renderable
      */
     protected $options = [];
 
+    /**
+     * @var string
+     */
+    protected $elementClass;
+
     /**
      * @var bool
      */
@@ -120,6 +125,8 @@ abstract class Widget implements Renderable
         return [
             'attributes' => $this->formatHtmlAttributes(),
             'options'    => $this->options,
+            'class'      => $this->getElementClass(),
+            'selector'   => $this->getElementSelector(),
         ];
     }
 
@@ -173,7 +180,15 @@ abstract class Widget implements Renderable
      */
     public function getElementSelector()
     {
-        return '#'.$this->id();
+        return '.'.$this->getElementClass();
+    }
+
+    /**
+     * @return mixed
+     */
+    public function getElementClass()
+    {
+        return $this->elementClass ?: str_replace('\\', '_', static::class);
     }
 
     /**

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