Browse Source

:hammer: extension management

jqh 5 năm trước cách đây
mục cha
commit
ffbae62c97
38 tập tin đã thay đổi với 873 bổ sung340 xóa
  1. 29 18
      resources/assets/dcat/extra/upload.js
  2. 8 9
      resources/assets/dcat/extra/upload.scss
  3. 2 1
      resources/assets/dcat/sass/components/_grid.scss
  4. 6 7
      resources/assets/dcat/sass/components/_table.scss
  5. 22 9
      resources/lang/en/admin.php
  6. 22 21
      resources/lang/zh-CN/admin.php
  7. 17 0
      resources/lang/zh-CN/menu.php
  8. 0 3
      resources/views/form/select.blade.php
  9. 15 0
      resources/views/grid/displayer/extensions/description.blade.php
  10. 33 0
      resources/views/grid/displayer/extensions/name.blade.php
  11. 78 3
      resources/views/grid/quick-create/form.blade.php
  12. 2 0
      resources/views/grid/quick-create/select.blade.php
  13. 30 28
      resources/views/scripts/select.blade.php
  14. 1 1
      src/Console/ExtensionDiableCommand.php
  15. 1 1
      src/Console/ExtensionEnableCommand.php
  16. 1 1
      src/Console/ExtensionInstallCommand.php
  17. 2 2
      src/Console/ExtensionRefreshCommand.php
  18. 1 1
      src/Console/ExtensionRollbackCommand.php
  19. 3 1
      src/Console/ExtensionUninstallCommand.php
  20. 1 1
      src/Console/ExtensionUpdateCommand.php
  21. 3 0
      src/Extend/Manager.php
  22. 102 4
      src/Extend/ServiceProvider.php
  23. 89 0
      src/Extend/Setting.php
  24. 0 4
      src/Extend/UpdateManager.php
  25. 4 91
      src/Grid/Tools/QuickCreate.php
  26. 24 0
      src/Http/Actions/Extensions/Disable.php
  27. 24 0
      src/Http/Actions/Extensions/Enable.php
  28. 21 0
      src/Http/Actions/Extensions/InstallFromLocal.php
  29. 20 0
      src/Http/Actions/Extensions/Marketplace.php
  30. 35 0
      src/Http/Actions/Extensions/Uninstall.php
  31. 26 0
      src/Http/Actions/Extensions/Update.php
  32. 35 105
      src/Http/Controllers/ExtensionController.php
  33. 49 0
      src/Http/Displayers/Extensions/Description.php
  34. 34 0
      src/Http/Displayers/Extensions/Name.php
  35. 74 0
      src/Http/Forms/InstallFromLocal.php
  36. 4 2
      src/Http/Repositories/Extension.php
  37. 37 27
      src/Layout/Asset.php
  38. 18 0
      src/Support/helpers.php

+ 29 - 18
resources/assets/dcat/extra/upload.js

@@ -363,15 +363,19 @@
                             post._relation = relation;
 
                             Dcat.loading();
-                            $.post(opts.deleteUrl, post, function (result) {
-                                Dcat.loading(false);
-                                if (result.status) {
-                                    deleteInput(file.serverId);
-                                    uploader.removeFile(file);
-                                    return;
+                            $.post({
+                                url: opts.deleteUrl,
+                                data: post,
+                                success: function (result) {
+                                    Dcat.loading(false);
+                                    if (result.status) {
+                                        deleteInput(file.serverId);
+                                        uploader.removeFile(file);
+                                        return;
+                                    }
+
+                                    Dcat.error(result.message || 'Remove file failed.');
                                 }
-
-                                Dcat.error(result.message || 'Remove file failed.');
                             });
 
                         });
@@ -505,7 +509,10 @@
             delete form['_relation'];
             delete form['upload_column'];
 
-            $.post(opts.updateServer, form);
+            $.post({
+                url: opts.updateServer,
+                data: form,
+            });
         }
 
         function setState(val, args) {
@@ -894,17 +901,21 @@
                     post._relation = relation;
 
                     Dcat.loading();
-                    $.post(opts.deleteUrl, post, function (result) {
-                        Dcat.loading(false);
-                        if (result.status) {
-                            // 移除
-                            html.remove();
+                    $.post({
+                        url: opts.deleteUrl,
+                        data: post,
+                        success: function (result) {
+                            Dcat.loading(false);
+                            if (result.status) {
+                                // 移除
+                                html.remove();
+
+                                removeFormFile(fileId);
+                                return;
+                            }
 
-                            removeFormFile(fileId);
-                            return;
+                            Dcat.error(result.message || 'Remove file failed.')
                         }
-
-                        Dcat.error(result.message || 'Remove file failed.')
                     });
                 });
             };

+ 8 - 9
resources/assets/dcat/extra/upload.scss

@@ -164,20 +164,20 @@
 
 .web-uploader.file .filelist li {
   width: 100%;
-  height: 40px;
+  height: 38px;
   background: $white;
   box-shadow: $shadow-100;
   margin: 0 8px 10px 0;
-  border-radius: 5px;
+  border-radius: $card-border-radius;
   border: 0;
-  padding: 0
+  padding: 0;
 }
 
 .web-uploader.file .filelist li .file-action {
   float: right;
-  margin: 16px 10px 0;
+  margin: 12px 10px 0;
   cursor: pointer;
-  font-size: 15px;
+  font-size: 1rem;
   position: absolute;
   right: 0;
 }
@@ -204,14 +204,13 @@
 
 .web-uploader.file .filelist li p.title {
   font-weight: 600;
-  font-size: 15px;
+  font-size: 1rem;
   vertical-align: middle;
-  height: 42px;
-  line-height: 35px;
+  height: 38px;
+  line-height: 30px;
   padding-left: 8px;
   float: left;
   text-align: left;
-  //color: #333;
   width: 100%
 }
 

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

@@ -66,8 +66,9 @@
     background-color: #f3f3f3;
     vertical-align: middle;
     //border-radius: .5rem;
-    height: 32px;
+    height: 42px;
     color: #777;
+    border-bottom: $table-border-color;
   }
 }
 

+ 6 - 7
resources/assets/dcat/sass/components/_table.scss

@@ -177,8 +177,13 @@ table.table-bordered.data-table.complex-headers {
 
 $table-border-color: #eff3f8;
 $table-bg: darken(#f7f7f9, 3.8%);
+//$table-bg: #f7f7f9;
 
 .table-collapse {
+  //table.data-table thead th {
+  //  font-weight: 500;
+  //}
+
   .custom-data-table.data-table tbody tr td:first-child {
     border-top-left-radius: 0;
     border-bottom-left-radius: 0;
@@ -223,22 +228,16 @@ body:not(.dark-mode) {
     box-shadow: $shadow;
     background: $table-bg;
     margin-top: 1rem;
-    //padding: 0 3px 3px;
 
     .custom-data-table {
       border-spacing: 0;
-      //border-collapse:collapse!important;
       margin-top: 0!important;
       background-color: $table-bg;
-      //border-radius: $card-border-radius;
     }
 
     table.custom-data-table thead th {
-      //border-top: 1px solid $table-border-color;
-      //border-bottom: 2px solid $table-border-color;
-      height: 42px;
+      height: 40px;
       vertical-align: middle;
-      //background: $white;
     }
 
     .custom-data-table tbody td {

+ 22 - 9
resources/lang/en/admin.php

@@ -191,18 +191,31 @@ return [
         'F_DUPLICATE'            => 'Duplicate file.',
         'confirm_delete_file'    => 'Are you sure delete this file from server?',
     ],
-    'import_extension_confirm' => 'Are you sure import the extension?',
-    'quick_create'             => 'Quick create',
-    'grid_items_selected'      => '{n} items selected',
-    'nothing_updated'          => 'Nothing has been updated.',
-    'welcome_back'             => 'Welcome back, please login to your account.',
-    'documentation'            => 'Documentation',
-    'demo'                     => 'Demo',
-    'extensions'               => 'Extensions',
+    'import_extension_confirm'  => 'Are you sure import the extension?',
+    'quick_create'              => 'Quick create',
+    'grid_items_selected'       => '{n} items selected',
+    'nothing_updated'           => 'Nothing has been updated.',
+    'welcome_back'              => 'Welcome back, please login to your account.',
+    'documentation'             => 'Documentation',
+    'demo'                      => 'Demo',
+    'extensions'                => 'Extensions',
+    'version'                   => 'Version',
+    'current_version'           => 'Current version',
+    'latest_version'            => 'Latest version',
+    'upgrade_to_version'        => 'Upgrade to version :version',
+    'enable'                    => 'Enable',
+    'disable'                   => 'Disable',
+    'uninstall'                 => 'Uninstall',
+    'confirm_uninstall'         => 'Please confirm that you wish to uninstall this extension. This may result in potential data loss.',
+    'marketplace'               => 'Marketplace',
+    'theme'                     => 'Theme',
+    'application'               => 'Application',
+    'install_from_local'        => 'Install From Local',
+    'install_succeeded'         => 'Install succeeded !',
+    'invalid_extension_package' => 'Invalid extension package !',
     'validation'               => [
         'match'     => 'The :attribute and :other must match.',
         'minlength' => 'The :attribute must be at least :min characters.',
         'maxlength' => 'The :attribute may not be greater than :max characters.',
     ],
-    'menu_titles' => [],
 ];

+ 22 - 21
resources/lang/zh-CN/admin.php

@@ -193,30 +193,31 @@ return [
         'F_DUPLICATE'            => '文件重复',
         'confirm_delete_file'    => '您确定要删除这个文件吗?',
     ],
-    'import_extension_confirm' => '确认导入拓展?',
-    'quick_create'             => '快速创建',
-    'grid_items_selected'      => '已选择 {n} 项',
-    'nothing_updated'          => '没有任何数据被更改',
-    'welcome_back'             => '欢迎回来,请登录您的账号。',
-    'documentation'            => '文档',
-    'demo'                     => '示例',
-    'extensions'               => '扩展',
+    'import_extension_confirm'  => '确认导入拓展?',
+    'quick_create'              => '快速创建',
+    'grid_items_selected'       => '已选择 {n} 项',
+    'nothing_updated'           => '没有任何数据被更改',
+    'welcome_back'              => '欢迎回来,请登录您的账号。',
+    'documentation'             => '文档',
+    'demo'                      => '示例',
+    'extensions'                => '扩展',
+    'version'                   => '版本',
+    'current_version'           => '当前版本',
+    'latest_version'            => '最新版本',
+    'upgrade_to_version'        => '更新至版本 :version',
+    'enable'                    => '启用',
+    'disable'                   => '禁用',
+    'uninstall'                 => '卸载',
+    'confirm_uninstall'         => '您确定要卸载当前扩展吗?此操作将会移除扩展数据!',
+    'marketplace'               => '应用市场',
+    'theme'                     => '主题',
+    'application'               => '应用',
+    'install_from_local'        => '本地安装',
+    'install_succeeded'         => '安装成功',
+    'invalid_extension_package' => '安装包异常',
     'validation'               => [
         'match'     => '与 :attribute 不匹配。',
         'minlength' => ':attribute 字符长度不能少于 :min。',
         'maxlength' => ':attribute 字符长度不能超出 :max。',
     ],
-    'menu_titles' => [
-        'index'         => '主页',
-        'admin'         => '系统',
-        'users'         => '管理员',
-        'roles'         => '角色',
-        'permission'    => '权限',
-        'menu'          => '菜单',
-        'operation_log' => '操作日志',
-        'helpers'       => '开发工具',
-        'extensions'    => '扩展',
-        'scaffold'      => '代码生成器',
-        'icons'         => '图标',
-    ],
 ];

+ 17 - 0
resources/lang/zh-CN/menu.php

@@ -0,0 +1,17 @@
+<?php
+
+return [
+    'titles' => [
+        'index'         => '主页',
+        'admin'         => '系统',
+        'users'         => '管理员',
+        'roles'         => '角色',
+        'permission'    => '权限',
+        'menu'          => '菜单',
+        'operation_log' => '操作日志',
+        'helpers'       => '开发工具',
+        'extensions'    => '扩展',
+        'scaffold'      => '代码生成器',
+        'icons'         => '图标',
+    ],
+];

+ 0 - 3
resources/views/form/select.blade.php

@@ -4,7 +4,6 @@
         <span>{!! $label !!}</span>
     </div>
 
-
     <div class="{{$viewClass['field']}}">
 
         @include('admin::form.error')
@@ -32,5 +31,3 @@
 
     </div>
 </div>
-
-@include('admin::form.select-script')

+ 15 - 0
resources/views/grid/displayer/extensions/description.blade.php

@@ -0,0 +1,15 @@
+<div style="margin-bottom: 10px">{{ $value }}</div>
+
+@if($row->version)
+    {{ trans('admin.version').' '.$row->version }}
+@else
+    {!! $updateAction !!}
+@endif
+ |&nbsp;
+
+@if($settingAction)
+    {!! $settingAction !!}
+    &nbsp;|&nbsp;
+@endif
+
+<a href="javascript:void(0)">{{ trans('admin.view') }}</a>

+ 33 - 0
resources/views/grid/displayer/extensions/name.blade.php

@@ -0,0 +1,33 @@
+<span class="ext-name">
+    {{ $value }}
+    @if($row->homepage)
+        <a href='{!! $row->homepage !!}' target='_blank' class="feather icon-chrome"></a>
+    @endif
+</span>
+<div style="height: 10px"></div>
+
+@if($row->version)
+
+    @if($row->enabled)
+        {!! $disableAction !!}
+    @else
+        {!! $enableAction !!}
+    @endif
+
+    <span class="hover-display" onclick="$(this).css({display: 'inline'})">
+        | {!! $uninstallAction !!}
+    </span>
+
+@endif
+
+<style>
+    .hover-display {
+        display:none;
+    }
+    table tbody tr:hover .hover-display {
+        display: inline;
+    }
+    .ext-name {
+        font-size: 1.1rem;
+    }
+</style>

+ 78 - 3
resources/views/grid/quick-create/form.blade.php

@@ -1,6 +1,6 @@
 <thead>
 <tr class="{{ $elementClass }} quick-create" style="cursor: pointer">
-    <td colspan="{{ $columnCount }}" style="background: {{ Admin::color()->darken('#ededed', 1) }}">
+    <td colspan="{{ $columnCount }}" style="background: {{ Dcat\Admin\Admin::color()->darken('#ededed', 1) }}">
         <span class="create cursor-pointer" style="display: block;">
              <i class="feather icon-plus"></i>&nbsp;{{ __('admin.quick_create') }}
         </span>
@@ -14,8 +14,83 @@
             <button type="submit" class="btn btn-primary btn-sm">{{ __('admin.submit') }}</button>&nbsp;
             &nbsp;
             <a href="javascript:void(0);" class="cancel">{{ __('admin.cancel') }}</a>
-            {{ csrf_field() }}
         </form>
     </td>
 </tr>
-</thead>
+</thead>
+
+<script>
+    var ctr = $('.{!! $elementClass !!}'),
+        btn = $('.quick-create-button-{!! $uniqueName !!}');
+
+    btn.on('click', function () {
+        ctr.toggle().click();
+    });
+
+    ctr.on('click', function () {
+        ctr.find('.create-form').show();
+        ctr.find('.create').hide();
+    });
+
+    ctr.find('.cancel').on('click', function () {
+        if (btn.length) {
+            ctr.hide();
+            return;
+        }
+
+        ctr.find('.create-form').hide();
+        ctr.find('.create').show();
+        return false;
+    });
+
+    ctr.find('.create-form').submit(function (e) {
+        e.preventDefault();
+
+        if (ctr.attr('submitting')) {
+            return;
+        }
+
+        var btn = $(this).find(':submit').buttonLoading();
+
+        ctr.attr('submitting', 1);
+
+        $.ajax({
+            url: '{!! $url !!}',
+            type: '{!! $method !!}',
+            data: $(this).serialize(),
+            success: function(data) {
+                ctr.attr('submitting', '');
+                btn.buttonLoading(false);
+
+                if (data.status == true) {
+                    Dcat.success(data.message);
+                    Dcat.reload();
+                    return;
+                }
+
+                if (typeof data.validation !== 'undefined') {
+                    Dcat.warning(data.message)
+                }
+            },
+            error:function(xhq){
+                btn.buttonLoading(false);
+                ctr.attr('submitting', '');
+                var json = xhq.responseJSON;
+                if (typeof json === 'object') {
+                    if (json.message) {
+                        Dcat.error(json.message);
+                    } else if (json.errors) {
+                        var i, errors = [];
+                        for (i in json.errors) {
+                            errors.push(json.errors[i].join("<br>"));
+                        }
+
+                        Dcat.error(errors.join("<br>"));
+                    }
+                }
+            }
+        });
+
+        return false;
+    });
+</script>

+ 2 - 0
resources/views/grid/quick-create/select.blade.php

@@ -8,3 +8,5 @@
     </select>
 </div>
 
+@include('admin::form.select-script')
+

+ 30 - 28
resources/views/scripts/select.blade.php

@@ -65,34 +65,36 @@
 @endsection
 
 @section('admin.select-lang')
-{{--本地化--}}
-<script once>
-    @php
-        $lang = trans('select2');
-        $locale = config('app.locale');
-    @endphp
-    if ($.fn.select2) {
-        var e = $.fn.select2.amd;
+@if (config('app.locale') !== 'en')
+    {{--本地化--}}
+    <script once>
+        @php
+            $lang = trans('select2');
+            $locale = config('app.locale');
+        @endphp
+        if ($.fn.select2) {
+            var e = $.fn.select2.amd;
 
-        e.define("select2/i18n/{{ $locale }}", [], function () {
-            return {
-                errorLoading: function () {
-                    return "{{ $lang['error_loading'] }}"
-                }, inputTooLong: function (e) {
-                    return "{{ $lang['input_too_long'] }}".replace(':num', e.input.length - e.maximum)
-                }, inputTooShort: function (e) {
-                    return "{{ $lang['input_too_short'] }}".replace(':num', e.minimum - e.input.length)
-                }, loadingMore: function () {
-                    return "{{ $lang['loading_more'] }}"
-                }, maximumSelected: function (e) {
-                    return "{{ $lang['maximum_selected'] }}".replace(':num', e.maximum)
-                }, noResults: function () {
-                    return "{{ $lang['no_results'] }}"
-                }, searching: function () {
-                    return "{{ $lang['searching'] }}"
+            e.define("select2/i18n/{{ $locale }}", [], function () {
+                return {
+                    errorLoading: function () {
+                        return "{{ $lang['error_loading'] }}"
+                    }, inputTooLong: function (e) {
+                        return "{{ $lang['input_too_long'] }}".replace(':num', e.input.length - e.maximum)
+                    }, inputTooShort: function (e) {
+                        return "{{ $lang['input_too_short'] }}".replace(':num', e.minimum - e.input.length)
+                    }, loadingMore: function () {
+                        return "{{ $lang['loading_more'] }}"
+                    }, maximumSelected: function (e) {
+                        return "{{ $lang['maximum_selected'] }}".replace(':num', e.maximum)
+                    }, noResults: function () {
+                        return "{{ $lang['no_results'] }}"
+                    }, searching: function () {
+                        return "{{ $lang['searching'] }}"
+                    }
                 }
-            }
-        }), {define: e.define, require: e.require}
-    }
-</script>
+            }), {define: e.define, require: e.require}
+        }
+    </script>
+@endif
 @endsection

+ 1 - 1
src/Console/ExtensionDiableCommand.php

@@ -7,7 +7,7 @@ use Illuminate\Console\Command;
 
 class ExtensionDiableCommand extends Command
 {
-    protected $signature = 'admin:extension-disable {name : The name of the extension. Eg: author-name/extension-name} ';
+    protected $signature = 'admin:ext-disable {name : The name of the extension. Eg: author-name/extension-name} ';
 
     protected $description = 'Disable an existing extension';
 

+ 1 - 1
src/Console/ExtensionEnableCommand.php

@@ -7,7 +7,7 @@ use Illuminate\Console\Command;
 
 class ExtensionEnableCommand extends Command
 {
-    protected $signature = 'admin:extension-enable 
+    protected $signature = 'admin:ext-enable 
     {name : The name of the extension. Eg: author-name/extension-name}';
 
     protected $description = 'Enable an existing extension';

+ 1 - 1
src/Console/ExtensionInstallCommand.php

@@ -8,7 +8,7 @@ use Illuminate\Support\Arr;
 
 class ExtensionInstallCommand extends Command
 {
-    protected $signature = 'admin:extension-install 
+    protected $signature = 'admin:ext-install 
     {name : The name of the extension. Eg: author-name/extension-name} 
     {--path= : The path of the extension.}';
 

+ 2 - 2
src/Console/ExtensionRefreshCommand.php

@@ -7,7 +7,7 @@ use Illuminate\Console\Command;
 
 class ExtensionRefreshCommand extends Command
 {
-    protected $signature = 'admin:extension-refresh 
+    protected $signature = 'admin:ext-refresh 
     {name : The name of the extension. Eg: author-name/extension-name} 
     {--path= : The path of the extension.}';
 
@@ -21,7 +21,7 @@ class ExtensionRefreshCommand extends Command
             throw new \InvalidArgumentException(sprintf('Plugin "%s" not found.', $name));
         }
 
-        $confirmQuestion = 'Please confirm that you wish to remove and re-add this extension?';
+        $confirmQuestion = 'Please confirm that you wish to reinstall this extension?';
 
         if (! $this->confirm($confirmQuestion)) {
             return;

+ 1 - 1
src/Console/ExtensionRollbackCommand.php

@@ -7,7 +7,7 @@ use Illuminate\Console\Command;
 
 class ExtensionRollbackCommand extends Command
 {
-    protected $signature = 'admin:extension-rollback 
+    protected $signature = 'admin:ext-rollback 
      {name : The name of the extension. Eg: author-name/extension-name} 
      {ver : If this parameter is specified, the process will stop on the specified version, if not, it will completely rollback the extension. Example: 1.3.9} 
      {--force : Force rollback}';

+ 3 - 1
src/Console/ExtensionUninstallCommand.php

@@ -7,7 +7,7 @@ use Illuminate\Console\Command;
 
 class ExtensionUninstallCommand extends Command
 {
-    protected $signature = 'admin:extension-uninstall 
+    protected $signature = 'admin:ext-uninstall 
     {name : The name of the extension. Eg: author-name/extension-name}';
 
     protected $description = 'Uninstall an existing extension';
@@ -24,6 +24,8 @@ class ExtensionUninstallCommand extends Command
                     ->updateManager()
                     ->setOutPut($this->output)
                     ->rollback($name);
+
+                Admin::extension()->get($name)->uninstall();
             } catch (\Throwable $exception) {
                 $lastVersion = Admin::extension()->versionManager()->getCurrentVersion($name);
 

+ 1 - 1
src/Console/ExtensionUpdateCommand.php

@@ -7,7 +7,7 @@ use Illuminate\Console\Command;
 
 class ExtensionUpdateCommand extends Command
 {
-    protected $signature = 'admin:extension-update 
+    protected $signature = 'admin:ext-update 
     {name : The name of the extension. Eg: author-name/extension-name}
     {--ver= : If this parameter is specified, the process will stop on the specified version, if not, it will update to the latest version. Example: 1.3.9}';
 

+ 3 - 0
src/Extend/Manager.php

@@ -300,6 +300,9 @@ class Manager
     public function addExtension(ServiceProvider $serviceProvider)
     {
         $this->extensions->put($serviceProvider->getName(), $serviceProvider);
+
+        $this->app->instance($abstract = get_class($serviceProvider), $serviceProvider);
+        $this->app->alias($abstract, $serviceProvider->getName());
     }
 
     /**

+ 102 - 4
src/Extend/ServiceProvider.php

@@ -12,6 +12,8 @@ use Symfony\Component\Console\Output\NullOutput;
 
 abstract class ServiceProvider extends LaravelServiceProvider
 {
+    const TYPE_THEME = 'theme';
+
     /**
      * @var ComposerProperty
      */
@@ -22,6 +24,11 @@ abstract class ServiceProvider extends LaravelServiceProvider
      */
     protected $name;
 
+    /**
+     * @var string
+     */
+    protected $type;
+
     /**
      * @var string
      */
@@ -70,6 +77,11 @@ abstract class ServiceProvider extends LaravelServiceProvider
      */
     public $output;
 
+    /**
+     * @var array
+     */
+    protected $config;
+
     public function __construct($app)
     {
         parent::__construct($app);
@@ -107,6 +119,16 @@ abstract class ServiceProvider extends LaravelServiceProvider
         return $this->name ?: ($this->name = str_replace('/', '.', $this->composerProperty->name));
     }
 
+    /**
+     * 获取插件类型.
+     *
+     * @return string
+     */
+    public function getType()
+    {
+        return $this->type;
+    }
+
     /**
      * 获取当前已安装版本.
      *
@@ -182,16 +204,80 @@ abstract class ServiceProvider extends LaravelServiceProvider
     }
 
     /**
-     * 获取配置.
+     * 获取或保存配置.
      *
      * @param string $key
      * @param null   $default
      *
-     * @return \Illuminate\Config\Repository|mixed
+     * @return mixed
      */
     final public function config($key = null, $default = null)
     {
-        return Admin::setting()->get($this->name);
+        if ($this->config === null) {
+            $this->config = Admin::setting()->get($this->getConfigKey());
+            $this->config = $this->config ? $this->unserializeConfig($this->config) : [];
+        }
+
+        if (is_array($key)) {
+            $this->config = array_merge($this->config, $key);
+
+            Admin::setting()->save([$this->getConfigKey() => $this->serializeConfig($this->config)]);
+
+            return;
+        }
+
+        if ($key === null) {
+            return $this->config;
+        }
+
+        return Arr::get($this->config, $key, $default);
+    }
+
+    /**
+     * 获取或保存配置.
+     *
+     * @param string $key
+     * @param string $value
+     *
+     * @return mixed
+     */
+    public static function setting($key = null, $value = null)
+    {
+        $extension = app(static::class);
+
+        if ($extension instanceof ServiceProvider) {
+            return $extension->config($key, $value);
+        }
+    }
+
+    /**
+     * 配置key.
+     *
+     * @return mixed
+     */
+    protected function getConfigKey()
+    {
+        return str_replace('.', ':', $this->getName());
+    }
+
+    /**
+     * @param $config
+     *
+     * @return false|string
+     */
+    protected function serializeConfig($config)
+    {
+        return json_encode($config);
+    }
+
+    /**
+     * @param $config
+     *
+     * @return array
+     */
+    protected function unserializeConfig($config)
+    {
+        return json_decode($config, true);
     }
 
     /**
@@ -217,11 +303,23 @@ abstract class ServiceProvider extends LaravelServiceProvider
     {
         if ($assets = $this->getAssetPath()) {
             $this->publishes([
-                $assets => public_path(Admin::asset()->getRealPath('@extension'))
+                $assets => $this->getPublishsPath(),
             ], $this->getName());
         }
     }
 
+    /**
+     * 获取资源发布路径.
+     *
+     * @return string
+     */
+    protected function getPublishsPath()
+    {
+        return public_path(
+            Admin::asset()->getRealPath('@extension/'.str_replace('.', '/', $this->getName()))
+        );
+    }
+
     /**
      * 注册路由.
      *

+ 89 - 0
src/Extend/Setting.php

@@ -0,0 +1,89 @@
+<?php
+
+namespace Dcat\Admin\Extend;
+
+use Dcat\Admin\Admin;
+use Dcat\Admin\Contracts\LazyRenderable;
+use Dcat\Admin\Traits\LazyWidget;
+use Dcat\Admin\Widgets\Form;
+
+abstract class Setting extends Form implements LazyRenderable
+{
+    use LazyWidget;
+
+    /**
+     * @var ServiceProvider
+     */
+    protected $extension;
+
+    public function __construct(ServiceProvider $extension = null)
+    {
+        parent::__construct();
+
+        $this->initExtension($extension);
+    }
+
+    protected function initExtension(?ServiceProvider $extension)
+    {
+        if ($extension) {
+            $this->extension = $extension;
+
+            $this->payload(['_extension_' => $extension->getName()]);
+        }
+    }
+
+    /**
+     * 处理请求.
+     *
+     * @param array $input
+     *
+     * @return \Dcat\Admin\Http\JsonResponse
+     */
+    public function handle(array $input)
+    {
+        $this->extension()->config($this->formatConfig($input));
+
+        return $this->response()->success(trans('admin.save_succeeded'));
+    }
+
+    /**
+     * 格式化配置信息.
+     *
+     * @param array $input
+     *
+     * @return array
+     */
+    protected function formatConfig(array $input)
+    {
+        return $input;
+    }
+
+    /**
+     * 表单字段定义.
+     *
+     * @return void
+     */
+    abstract public function form();
+
+    /**
+     * 填充表单数据.
+     *
+     * @return array
+     */
+    public function default()
+    {
+        return $this->extension()->config() ?: [];
+    }
+
+    /**
+     * @return ServiceProvider
+     */
+    public function extension()
+    {
+        if (! empty($this->payload['_extension_'])) {
+            return Admin::extension()->get($this->payload['_extension_']);
+        }
+
+        return $this->extension;
+    }
+}

+ 0 - 4
src/Extend/UpdateManager.php

@@ -68,10 +68,6 @@ class UpdateManager
 
         $this->note('<error>Unable to find:</error> '.$name);
 
-        if ($stopOnVersion === null) {
-            // 移除静态资源
-        }
-
         return $this;
     }
 

+ 4 - 91
src/Grid/Tools/QuickCreate.php

@@ -307,94 +307,6 @@ class QuickCreate implements Renderable
         return $this;
     }
 
-    protected function script()
-    {
-        $url = $this->action;
-        $method = $this->method;
-
-        $uniqueName = $this->parent->getName();
-
-        $script = <<<JS
-(function () {
-    var ctr = $('.{$this->getElementClass()}'),
-        btn = $('.quick-create-button-{$uniqueName}');
-    
-    btn.on('click', function () {
-        ctr.toggle().click();
-    });
-    
-    ctr.on('click', function () {
-        ctr.find('.create-form').show();
-        ctr.find('.create').hide();
-    });
-    
-    ctr.find('.cancel').on('click', function () {
-        if (btn.length) {
-            ctr.hide();
-            return;
-        }
-        
-        ctr.find('.create-form').hide();
-        ctr.find('.create').show();
-        return false;
-    });
-
-    ctr.find('.create-form').submit(function (e) {
-        e.preventDefault();
-        
-        if (ctr.attr('submitting')) {
-            return;
-        }
-        
-        var btn = $(this).find(':submit').buttonLoading();
-        
-        ctr.attr('submitting', 1);
-    
-        $.ajax({
-            url: '{$url}',
-            type: '{$method}',
-            data: $(this).serialize(),
-            success: function(data) {
-                ctr.attr('submitting', '');
-                btn.buttonLoading(false);
-                
-                if (data.status == true) {
-                    Dcat.success(data.message);
-                    Dcat.reload();
-                    return;
-                }
-                
-                if (typeof data.validation !== 'undefined') {
-                    Dcat.warning(data.message)
-                }
-            },
-            error:function(xhq){
-                btn.buttonLoading(false);
-                ctr.attr('submitting', '');
-                var json = xhq.responseJSON;
-                if (typeof json === 'object') {
-                    if (json.message) {
-                        Dcat.error(json.message);
-                    } else if (json.errors) {
-                        var i, errors = [];
-                        for (i in json.errors) {
-                            errors.push(json.errors[i].join("<br>"));
-                        } 
-                        
-                        Dcat.error(errors.join("<br>"));
-                    }
-                }
-            }
-        });
-        
-        return false;
-    });
-})();
-JS;
-
-        Admin::script($script);
-    }
-
     public function getElementClass()
     {
         $name = $this->parent->getName();
@@ -413,14 +325,15 @@ JS;
             return '';
         }
 
-        $this->script();
-
         $vars = [
             'columnCount'  => $columnCount,
             'fields'       => $this->fields,
             'elementClass' => $this->getElementClass(),
+            'url'          => $this->action,
+            'method'       => $this->method,
+            'uniqueName'   => $this->parent->getName(),
         ];
 
-        return view('admin::grid.quick-create.form', $vars)->render();
+        return Admin::view('admin::grid.quick-create.form', $vars);
     }
 }

+ 24 - 0
src/Http/Actions/Extensions/Disable.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace Dcat\Admin\Http\Actions\Extensions;
+
+use Dcat\Admin\Admin;
+use Dcat\Admin\Grid\RowAction;
+
+class Disable extends RowAction
+{
+    public function title()
+    {
+        return trans('admin.disable');
+    }
+
+    public function handle()
+    {
+        Admin::extension()->enable($this->getKey(), false);
+
+        return $this
+            ->response()
+            ->success(trans('admin.update_succeeded'))
+            ->refresh();
+    }
+}

+ 24 - 0
src/Http/Actions/Extensions/Enable.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace Dcat\Admin\Http\Actions\Extensions;
+
+use Dcat\Admin\Admin;
+use Dcat\Admin\Grid\RowAction;
+
+class Enable extends RowAction
+{
+    public function title()
+    {
+        return trans('admin.enable');
+    }
+
+    public function handle()
+    {
+        Admin::extension()->enable($this->getKey());
+
+        return $this
+            ->response()
+            ->success(trans('admin.update_succeeded'))
+            ->refresh();
+    }
+}

+ 21 - 0
src/Http/Actions/Extensions/InstallFromLocal.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace Dcat\Admin\Http\Actions\Extensions;
+
+use Dcat\Admin\Http\Forms\InstallFromLocal as InstallFromLocalForm;
+use Dcat\Admin\Grid\Tools\AbstractTool;
+use Dcat\Admin\Widgets\Modal;
+
+class InstallFromLocal extends AbstractTool
+{
+    protected $style = 'btn btn-primary';
+
+    public function html()
+    {
+        return Modal::make()
+            ->lg()
+            ->title($title = trans('admin.install_from_local'))
+            ->body(InstallFromLocalForm::make())
+            ->button("<button class='btn btn-primary'><i class=\"feather icon-folder\"></i> &nbsp;{$title}</button> &nbsp;");
+    }
+}

+ 20 - 0
src/Http/Actions/Extensions/Marketplace.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace Dcat\Admin\Http\Actions\Extensions;
+
+use Dcat\Admin\Grid\Tools\AbstractTool;
+
+class Marketplace extends AbstractTool
+{
+    protected $style = 'btn btn-primary';
+
+    public function title()
+    {
+        return '<i class="feather icon-shopping-cart"></i> &nbsp;'.trans('admin.marketplace');
+    }
+
+    public function html()
+    {
+        return parent::html().'&nbsp;';
+    }
+}

+ 35 - 0
src/Http/Actions/Extensions/Uninstall.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace Dcat\Admin\Http\Actions\Extensions;
+
+use Dcat\Admin\Admin;
+use Dcat\Admin\Grid\RowAction;
+
+class Uninstall extends RowAction
+{
+    public function title()
+    {
+        $label = trans('admin.uninstall');
+
+        return "<span class='text-danger'>{$label}</span>";
+    }
+
+    public function confirm()
+    {
+        return [trans('admin.confirm_uninstall'), $this->getKey()];
+    }
+
+    public function handle()
+    {
+        $manager = Admin::extension()
+            ->updateManager()
+            ->rollback($this->getKey());
+
+        Admin::extension()->get($this->getKey())->uninstall();
+
+        return $this
+            ->response()
+            ->success(implode('<br>', $manager->notes))
+            ->refresh();
+    }
+}

+ 26 - 0
src/Http/Actions/Extensions/Update.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace Dcat\Admin\Http\Actions\Extensions;
+
+use Dcat\Admin\Admin;
+use Dcat\Admin\Grid\RowAction;
+
+class Update extends RowAction
+{
+    public function title()
+    {
+        return trans('admin.upgrade_to_version', ['version' => $this->row->new_version]);
+    }
+
+    public function handle()
+    {
+        $manager = Admin::extension()
+            ->updateManager()
+            ->update($this->getKey());
+
+        return $this
+            ->response()
+            ->success(implode('<br>', $manager->notes))
+            ->refresh();
+    }
+}

+ 35 - 105
src/Http/Controllers/ExtensionController.php

@@ -2,16 +2,16 @@
 
 namespace Dcat\Admin\Http\Controllers;
 
-use Dcat\Admin\Http\Actions\ImportButton;
+use Dcat\Admin\Extend\ServiceProvider;
 use Dcat\Admin\Form;
 use Dcat\Admin\Grid;
+use Dcat\Admin\Http\Actions\Extensions\InstallFromLocal;
+use Dcat\Admin\Http\Actions\Extensions\Marketplace;
+use Dcat\Admin\Http\Displayers\Extensions;
 use Dcat\Admin\Layout\Content;
 use Dcat\Admin\Http\Repositories\Extension;
 use Dcat\Admin\Support\Helper;
 use Dcat\Admin\Support\StringOutput;
-use Dcat\Admin\Widgets\Alert;
-use Dcat\Admin\Widgets\Table;
-use Dcat\Admin\Widgets\Terminal;
 use Illuminate\Routing\Controller;
 use Illuminate\Support\Facades\Artisan;
 
@@ -21,31 +21,12 @@ class ExtensionController extends Controller
 
     public function index(Content $content)
     {
-        $this->define();
-
         return $content
             ->title(admin_trans_label('Extensions'))
             ->description(trans('admin.list'))
             ->body($this->grid());
     }
 
-    public function import()
-    {
-        $extension = request('id');
-
-        if (! $extension) {
-            return response()->json(['status' => false, 'messages' => 'Invalid extension hash.']);
-        }
-
-        $box = Alert::make()
-            ->title("<span>php artisan admin:import $extension</span>")
-            ->content(Terminal::call('admin:import', ['extension' => $extension, '--force' => '1'])->transparent())
-            ->success()
-            ->removable();
-
-        return response()->json(['status' => true, 'content' => $box->render()]);
-    }
-
     /**
      * Make a grid builder.
      *
@@ -55,25 +36,23 @@ class ExtensionController extends Controller
     {
         return new Grid(new Extension(), function (Grid $grid) {
             $grid->number();
-            $grid->column('name');
-            $grid->column('version');
-            $grid->column('description')
-                ->if(function ($column) {
-                    return mb_strlen($column->getValue(), 'UTF-8') > 14;
-                })
-                ->display(function ($v) {
-                    return Helper::strLimit($v, 0, 14);
-                })
-                ->expand(function ($expand) {
-                    if (! $this->description) {
-                        return;
-                    }
+            $grid->column('type')->display(function ($v) {
+                return $v === ServiceProvider::TYPE_THEME ? trans('admin.theme') : trans('admin.application');
+            });
+            $grid->column('name')->displayUsing(Extensions\Name::class);
+            $grid->column('description')->displayUsing(Extensions\Description::class)->width('55%');
 
-                    return "<div style='padding:10px 20px'>{$this->description}</div>";
-                });
+            $grid->column('authors')->display(function ($v) {
+                if (! $v) {
+                    return;
+                }
 
-            $grid->column('authors');
-            $grid->column('enable')->switch();
+                foreach ($v as &$item) {
+                    $item = "<span class='text-80'>{$item['name']}</span> <<code>{$item['email']}</code>>";
+                }
+
+                return implode('<div style="margin-top: 5px"></div>', $v);
+            });
 
             $grid->disablePagination();
             $grid->disableCreateButton();
@@ -85,15 +64,27 @@ class ExtensionController extends Controller
             $grid->disableEditButton();
             $grid->disableDeleteButton();
             $grid->disableViewButton();
+            $grid->disableActions();
 
-            $grid->actions([new ImportButton()]);
+            $grid->tools([
+                new Marketplace(),
+                new InstallFromLocal()
+            ]);
 
             $grid->quickCreate(function (Grid\Tools\QuickCreate $create) {
-                $create->text('package_name')->required();
+                $create->text('name')
+                    ->attribute('style', 'width:240px')
+                    ->placeholder('Input Name. Eg: dcat-admin/demo')
+                    ->required();
                 $create->text('namespace')
                     ->attribute('style', 'width:240px')
-                    ->required()
-                    ->default('Dcat\\Admin\\Extension\\:Name');
+                    ->placeholder('Input Namespace. Eg: DcatAdmin\\Demo')
+                    ->required();
+                $create->select('type')
+                    ->options([1 => trans('admin.application'), 2 => trans('admin.theme')])
+                    ->attribute('style', 'width:140px!important')
+                    ->default(1)
+                    ->required();
             });
         });
     }
@@ -144,65 +135,4 @@ class ExtensionController extends Controller
 
         return $output->getContent();
     }
-
-    protected function getExpandHandler($key = 'require')
-    {
-        return function () use ($key) {
-            if (! $this->{$key}) {
-                return;
-            }
-
-            $rows = [];
-            foreach ((array) $this->{$key} as $k => $v) {
-                $k = "<b class='text-80'>$k</b>";
-
-                $rows[$k] = is_array($v) ? "<pre>{$v}</pre>" : $v;
-            }
-
-            return new Table($rows);
-        };
-    }
-
-    protected function define()
-    {
-        $name = function ($v) {
-            $url = $this->homepage;
-
-            return "<a href='$url' target='_blank'>$v</a>";
-        };
-
-        $version = function ($v) {
-            $this->version = $this->version ?: 'unknown';
-            $style = in_array($this->version, ['dev-master', 'unknown']) ? 'default' : 'primary';
-
-            return $this->version ? "<span class='label bg-$style'>{$this->version}</span>" : '';
-        };
-
-        $authors = function ($v) {
-            if (! $v) {
-                return;
-            }
-
-            foreach ($v as &$item) {
-                $item = "<span class='text-80'>{$item['name']}</span> <<code>{$item['email']}</code>>";
-            }
-
-            return implode('<br/>', $v);
-        };
-
-        $imported = function ($v) {
-            if (! $v) {
-                $text = trans('admin.is_not_import');
-
-                return "<label class='label label-default'>$text</label>";
-            }
-
-            return $this->imported_at;
-        };
-
-        Grid\Column::define('name', $name);
-        Grid\Column::define('version', $version);
-        Grid\Column::define('authors', $authors);
-        Grid\Column::define('imported', $imported);
-    }
 }

+ 49 - 0
src/Http/Displayers/Extensions/Description.php

@@ -0,0 +1,49 @@
+<?php
+
+namespace Dcat\Admin\Http\Displayers\Extensions;
+
+use Dcat\Admin\Admin;
+use Dcat\Admin\Grid\Displayers\AbstractDisplayer;
+use Dcat\Admin\Http\Actions\Extensions\Update;
+use Dcat\Admin\Widgets\Modal;
+
+class Description extends AbstractDisplayer
+{
+    public function display()
+    {
+        return Admin::view('admin::grid.displayer.extensions.description', [
+            'value' => $this->value,
+            'row'   => $this->row,
+            'settingAction' => $this->resolveSettingForm(),
+            'updateAction' => $this->resolveAction(Update::class),
+        ]);
+    }
+
+    protected function resolveSettingForm()
+    {
+        $extension = Admin::extension()->get($this->getKey());
+
+        if (! method_exists($extension, 'settingForm')) {
+            return;
+        }
+
+        $label = trans('admin.setting');
+
+        return Modal::make()
+            ->lg()
+            ->title(trans('admin.setting').' - '.$this->getKey())
+            ->body($extension->settingForm())
+            ->button($label);
+    }
+
+    protected function resolveAction($action)
+    {
+        $action = new $action();
+
+        $action->setGrid($this->grid);
+        $action->setColumn($this->column);
+        $action->setRow($this->row);
+
+        return $action->render();
+    }
+}

+ 34 - 0
src/Http/Displayers/Extensions/Name.php

@@ -0,0 +1,34 @@
+<?php
+
+namespace Dcat\Admin\Http\Displayers\Extensions;
+
+use Dcat\Admin\Admin;
+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;
+
+class Name extends AbstractDisplayer
+{
+    public function display()
+    {
+        return Admin::view('admin::grid.displayer.extensions.name', [
+            'value'           => $this->value,
+            'row'             => $this->row,
+            'enableAction'    => $this->resolveAction(Enable::class),
+            'disableAction'   => $this->resolveAction(Disable::class),
+            'uninstallAction' => $this->resolveAction(Uninstall::class),
+        ]);
+    }
+
+    protected function resolveAction($action)
+    {
+        $action = new $action();
+
+        $action->setGrid($this->grid);
+        $action->setColumn($this->column);
+        $action->setRow($this->row);
+
+        return $action->render();
+    }
+}

+ 74 - 0
src/Http/Forms/InstallFromLocal.php

@@ -0,0 +1,74 @@
+<?php
+
+namespace Dcat\Admin\Http\Forms;
+
+use Dcat\Admin\Admin;
+use Dcat\Admin\Contracts\LazyRenderable;
+use Dcat\Admin\Exception\RuntimeException;
+use Dcat\Admin\Traits\LazyWidget;
+use Dcat\Admin\Widgets\Form;
+
+class InstallFromLocal extends Form implements LazyRenderable
+{
+    use LazyWidget;
+
+    public function handle(array $input)
+    {
+        $file = $input['extension'];
+
+        if (! $file) {
+            return $this->response()->error('Invalid arguments.');
+        }
+
+        $path = $this->getFilePath($file);
+
+        $manager = Admin::extension();
+
+        $allNames = $manager->all()->keys()->toArray();
+
+        $manager->extract($path);
+
+        $manager->load();
+
+        $newAllNames = $manager->all()->keys()->toArray();
+
+        $diff = array_diff($newAllNames, $allNames);
+
+        if (! $diff) {
+            return $this->response()->error(trans('admin.invalid_extension_package'));
+        }
+
+        $manager
+            ->updateManager()
+            ->update(current($diff));
+
+        return $this->response()
+            ->success(implode('<br>', $manager->updateManager()->notes))
+            ->refresh();
+    }
+
+    protected function getFilePath($file)
+    {
+        $root = config("filesystems.disks.{$this->disk()}.root");
+
+        if (! $root) {
+            throw new RuntimeException(sprintf('Invalid configurations of disk [%s], missing "root".', $this->disk()));
+        }
+
+        return rtrim($root, '/').'/'.$file;
+    }
+
+    protected function disk()
+    {
+        return config('admin.extension.disk') ?: 'local';
+    }
+
+    public function form()
+    {
+        $this->file('extension')
+            ->required()
+            ->disk($this->disk())
+            ->accept('zip,arc,rar,tar.gz', 'application/zip')
+            ->autoUpload();
+    }
+}

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

@@ -33,12 +33,14 @@ class Extension extends Repository
         return [
             'id'           => $extension->getName(),
             'alias'        => $extension->getName(),
-            'name'         => $property->name,
+            'name'         => $extension->getName(),
             'version'      => $extension->getVersion(),
+            'type'         => $extension->getType(),
             'description'  => $property->description,
             'authors'      => $property->authors,
             'homepage'     => $property->homepage,
-            'enable'       => $extension->enabled(),
+            'enabled'      => $extension->enabled(),
+            'new_version'  => key($extension->getLocalLatestVersion()),
         ];
     }
 

+ 37 - 27
src/Layout/Asset.php

@@ -9,24 +9,16 @@ use Illuminate\Support\Str;
 class Asset
 {
     /**
-     * 路径别名.
+     * 别名.
      *
      * @var array
      */
-    protected $pathAlias = [
+    protected $alias = [
         // Dcat Admin静态资源路径别名
-        '@admin' => 'vendors/dcat-admin',
-
+        '@admin' => 'vendor/dcat-admin',
         // Dcat Acmin扩展静态资源路径别名
-        '@extension' => 'vendors/dcat-admin-extensions',
-    ];
+        '@extension' => 'vendor/dcat-admin-extensions',
 
-    /**
-     * 别名.
-     *
-     * @var array
-     */
-    protected $alias = [
         '@adminlte' => [
             'js' => [
                 '@admin/adminlte/adminlte.js',
@@ -307,22 +299,21 @@ class Asset
      * 设置或获取别名.
      *
      * @param string|array $name
-     * @param string|array $js
-     * @param string|array $css
+     * @param string|array $value
      *
      * @return void|array
      */
-    public function alias($name, $js = null, $css = null)
+    public function alias($name, $value = null)
     {
         if (is_array($name)) {
             foreach ($name as $key => $value) {
-                $this->alias($key, $value['js'] ?? [], $value['css'] ?? []);
+                $this->alias($key, $value);
             }
 
             return;
         }
 
-        if ($js === null && $css === null) {
+        if ($value === null) {
             return $this->alias[$name] ?? [];
         }
 
@@ -330,10 +321,7 @@ class Asset
             $name = '@'.$name;
         }
 
-        $this->alias[$name] = [
-            'js'  => $js,
-            'css' => $css,
-        ];
+        $this->alias[$name] = $value;
     }
 
     /**
@@ -463,7 +451,7 @@ class Asset
      */
     public function getRealPath(?string $path)
     {
-        if (! $this->hasAlias($path)) {
+        if (! $this->containsAlias($path)) {
             return $path;
         }
 
@@ -471,19 +459,41 @@ class Asset
             '/',
             array_map(
                 function ($v) {
-                    $v = $this->pathAlias[$v] ?? $v;
-
-                    if (! $this->hasAlias($v)) {
+                    if (! $this->isPathAlias($v)) {
                         return $v;
                     }
 
-                    return $this->getRealPath($v);
+                    return $this->getRealPath($this->alias($v));
                 },
                 explode('/', $path)
             )
         );
     }
 
+    /**
+     * 判断是否是路径别名.
+     *
+     * @param mixed $value
+     *
+     * @return bool
+     */
+    public function isPathAlias($value)
+    {
+        return $this->hasAlias($value) && is_string($this->alias[$value]);
+    }
+
+    /**
+     * 判断别名是否存在
+     *
+     * @param $value
+     * 
+     * @return bool
+     */
+    public function hasAlias($value)
+    {
+        return isset($this->alias[$value]);
+    }
+
     /**
      * 判断是否含有别名.
      *
@@ -491,7 +501,7 @@ class Asset
      *
      * @return bool
      */
-    protected function hasAlias($value)
+    protected function containsAlias($value)
     {
         return $value && mb_strpos($value, '@') === 0;
     }

+ 18 - 0
src/Support/helpers.php

@@ -29,6 +29,24 @@ if (! function_exists('admin_setting')) {
     }
 }
 
+if (! function_exists('admin_extension_setting')) {
+    /**
+     * @param string       $extension
+     * @param string|array $key
+     * @param mixed        $default
+     *
+     * @return mixed
+     */
+    function admin_extension_setting($extension, $key = null, $default = [])
+    {
+        $extension = app($extension);
+
+        if ($extension instanceof Dcat\Admin\Extend\ServiceProvider) {
+            return $extension->config($key, $default);
+        }
+    }
+}
+
 if (! function_exists('admin_section')) {
     /**
      * Get the string contents of a section.