├── .gitignore ├── routes └── web.php ├── src ├── AdminConfigModel.php ├── AdminConfig.php ├── AdminConfigServiceProvider.php ├── ConfigForm.php └── Http │ └── Controllers │ └── AdminConfigController.php ├── database └── migrations │ └── 2017_07_17_040159_create_config_table.php ├── composer.json ├── LICENSE ├── config └── admin-config.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | phpunit.phar 3 | /vendor 4 | composer.phar 5 | composer.lock 6 | *.project 7 | .idea/ -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | 'Adminconfig', 13 | 'path' => 'admin-config', 14 | 'icon' => 'fa-gears', 15 | ]; 16 | } -------------------------------------------------------------------------------- /database/migrations/2017_07_17_040159_create_config_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 13 | $table->string('name')->unique(); 14 | $table->string('value'); 15 | $table->text('description')->nullable(); 16 | $table->timestamps(); 17 | }); 18 | } 19 | 20 | public function down() 21 | { 22 | Schema::dropIfExists('admin_config'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fourn/admin-config", 3 | "description": "Manage your database configuration as profiles", 4 | "type": "library", 5 | "keywords": ["laravel-admin", "extension", "config"], 6 | "homepage": "https://github.com/fourn/admin-config", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "fourn", 11 | "email": "fourn@foxmail.com" 12 | } 13 | ], 14 | "require": { 15 | "laravel/framework": "^5.5|^6.0", 16 | "encore/laravel-admin": "^1.6" 17 | }, 18 | "autoload": { 19 | "psr-4": { 20 | "Fourn\\AdminConfig\\": "src/" 21 | } 22 | }, 23 | "extra": { 24 | "laravel": { 25 | "providers": [ 26 | "Fourn\\AdminConfig\\AdminConfigServiceProvider" 27 | ] 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/AdminConfigServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 13 | // 数据库迁移 14 | $this->loadMigrationsFrom(__DIR__.'/../database/migrations'); 15 | // 发布配置文件 16 | $this->publishes([ 17 | __DIR__.'/../config/admin-config.php' => config_path('admin-config.php') 18 | ], 'admin-config'); 19 | } 20 | 21 | // 全局引入数据库配置 22 | if (\Schema::hasTable('admin_config')) { 23 | foreach (AdminConfigModel::all(['name', 'value']) as $config) { 24 | config([$config['name'] => $config['value']]); 25 | } 26 | } 27 | 28 | // 注册路由 29 | $this->app->booted(function () { 30 | AdminConfig::routes(__DIR__.'/../routes/web.php'); 31 | AdminConfig::boot(); 32 | }); 33 | } 34 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jens Segers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /config/admin-config.php: -------------------------------------------------------------------------------- 1 | [ 11 | 'sample' => 'sample-name', 12 | 'sample2' 13 | ], 14 | /* 15 | * -------------------------------------------------------------------------- 16 | * Define configuration permissions 17 | * -------------------------------------------------------------------------- 18 | * Each configuration group will be visible to all roles if not specified or empty array. 19 | * Example: ['role1', 'role2'] 20 | */ 21 | 'admin_config_permissions' => [ 22 | 'sample' => [], 23 | 'sample2' => [], 24 | ], 25 | /** 26 | * -------------------------------------------------------------------------- 27 | * Define configuration items 28 | * -------------------------------------------------------------------------- 29 | * access:config('sample') config('sample.value') 30 | */ 31 | 'sample' => [ 32 | 'value', 33 | 'value1'=>['help'=>'help content', 'default'=>'default value'], 34 | 'value2'=>['label text', 'placeholder'=>'typing...', 'rules'=>'required'], 35 | 'value3'=>['type'=>'select', 'select label text', 'options'=>['option1'=>'option1', 'option2'=>'option2']], 36 | // 'value4'=>['type'=>'listbox', 'options'=>['foo'=>'foo', 'bar'=>'bar']], 37 | 'value5'=>['type'=>'checkbox', 'options'=>['foo'=>'foo', 'bar'=>'bar']], 38 | 'value6'=>['type'=>'ip'], 39 | 'value7'=>['type'=>'mobile'], 40 | 'value8'=>['type'=>'color'], 41 | 'value9'=>['type'=>'time', 'format'=>'HH:mm'], 42 | 'value10'=>['type'=>'dateRange', 'dateRange label text'], 43 | 'value11'=>['type'=>'number', 'min'=>100, 'default'=>100], 44 | 'value12'=>['type'=>'rate'], 45 | 'value13'=>['type'=>'image', 'uniqueName'], 46 | 'value14'=>['type'=>'file', 'uniqueName'], 47 | // 'value15'=>['type'=>'multipleImage', 'removable', 'uniqueName'], 48 | // 'value16'=>['type'=>'multipleFile', 'removable', 'uniqueName'], 49 | 'value17'=>['type'=>'editor'], 50 | 'value18'=>['type'=>'switch'], 51 | 'value19'=>['type'=>'tags'], 52 | ], 53 | 'sample2' => [ 54 | 'value' 55 | ] 56 | ]; -------------------------------------------------------------------------------- /src/ConfigForm.php: -------------------------------------------------------------------------------- 1 | getConfigValues(); 18 | return $this; 19 | } 20 | 21 | public function getConfigValues($is_edit = false) 22 | { 23 | $data = $this->model->pluck('value', 'name')->toArray(); 24 | $values = []; 25 | foreach ($data as $key => $val) { 26 | $k = str_replace('.', self::SEPARATOR, $key); 27 | $values[$k] = $val; 28 | } 29 | 30 | $this->builder()->fields()->each(function (Field $field) use ($values, $is_edit) { 31 | if ($field instanceof Field\MultipleImage 32 | or $field instanceof Field\MultipleFile) { 33 | if (isset($values[$field->column()])) { 34 | $values[$field->column()] = explode(',', $values[$field->column()]); 35 | } 36 | } 37 | if ($is_edit) { 38 | $field->setOriginal($values); 39 | } else { 40 | $field->fill($values); 41 | } 42 | }); 43 | } 44 | 45 | public function configUpdate() 46 | { 47 | $data = Request::all(); 48 | 49 | $isEditable = $this->isEditable($data); 50 | 51 | $data = $this->handleEditable($data); 52 | 53 | $data = $this->handleFileDelete($data); 54 | 55 | $this->getConfigValues(true); 56 | 57 | // Handle validation errors. 58 | if ($validationMessages = $this->validationMessages($data)) { 59 | if (!$isEditable) { 60 | return back()->withInput()->withErrors($validationMessages); 61 | } else { 62 | return response()->json(['errors' => array_dot($validationMessages->getMessages())], 422); 63 | } 64 | } 65 | 66 | if (($response = $this->prepare($data)) instanceof Response) { 67 | return $response; 68 | } 69 | 70 | DB::transaction(function () { 71 | $updates = $this->prepareUpdate($this->updates); 72 | 73 | if ($updates) { 74 | foreach ($updates as $key => $val) { 75 | $name = str_replace(self::SEPARATOR, '.', $key); 76 | if (is_null($val)) 77 | $val = ''; 78 | if (is_array($val)) { 79 | $val = implode(',', $val); 80 | } 81 | $this->model->updateOrCreate( 82 | ['name'=>$name], 83 | ['value'=>$val] 84 | ); 85 | } 86 | } 87 | }); 88 | 89 | if (($result = $this->callSaved()) instanceof Response) { 90 | return $result; 91 | } 92 | 93 | if ($response = $this->ajaxResponse(trans('admin.update_succeeded'))) { 94 | return $response; 95 | } 96 | 97 | admin_toastr(trans('admin.save_succeeded')); 98 | 99 | return redirect(admin_base_path('admin-config')); 100 | } 101 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | admin-config 2 | ====== 3 | 4 | 基于[laravel-admin](https://github.com/z-song/laravel-admin)的数据库配置管理工具,仅通过配置文件就可生成整个表单,支持使用tab页对配置项进行分组。 5 | 6 | ![Untitled](https://ws4.sinaimg.cn/large/006tKfTcgy1g194izbkghg312y0mr1ky.gif) 7 | 8 | ## 安装: 9 | 10 | 步骤一、使用 composer 安装 11 | 12 | ``` 13 | composer require fourn/admin-config 14 | ``` 15 | 16 | 步骤二、执行数据库迁移 17 | 18 | ```php 19 | php artisan migrate 20 | ``` 21 | 22 | 步骤三、发布配置文件 23 | 24 | ``` 25 | artisan vendor:publish --tag=admin-config 26 | ``` 27 | 28 | ## 使用方法: 29 | 30 | 第一步:在配置文件中加入配置组及配置项,详细参考下面的"配置文件语法" 31 | 32 | 第二步:使用laravel中的config函数 33 | 34 | ``` 35 | // 获取一组 36 | config('sample') 37 | // 获取一项 38 | config('sample.value') 39 | ``` 40 | 41 | ## 其他: 42 | 43 | 你可以生成后台菜单: 44 | 45 | ```php 46 | php artisan admin:import admin-config 47 | ``` 48 | 49 | 或者直接访问: 50 | 51 | http://your-host/admin/admin-config 52 | 53 | 扩展配置: 54 | 55 | ```php 56 | 'extensions' => [ 57 | 'admin-config' => [ 58 | 'title'=>'AdminConfig', 59 | 'description'=>'Manage your profiles as profiles', 60 | 'action'=>' ', 61 | ], 62 | ], 63 | ``` 64 | 65 | 66 | 67 | ## 配置文件语法: 68 | 69 | 配置文件发布后路径为:config/admin-config.php 70 | 71 | 定义配置组: 72 | 73 | ```php 74 | 'admin_config_groups' => [ 75 | 76 | // 配置组值 => tab选项卡显示文字 77 | 'sample' => 'sample-name', 78 | 79 | // 省略写法,等同于 'sample2' => 'sample2' 80 | 'sample2' 81 | 82 | ], 83 | ``` 84 | 85 | 定义配置项: 86 | 87 | ```php 88 | // 配置组名作为键,可以使用config('sample')访问一组值 89 | 'sample' => [ 90 | 91 | // 默认情况写法,以下等同于 'value' => ['label'=>'value', 'type'=>'test'] 92 | // 可以使用config('sample.value')访问其值 93 | 'value', 94 | 95 | // 支持配置链式调用,以下将执行$form->text('value1')->help('help content')->default('default value') 96 | 'value1'=>['help'=>'help content', 'default'=>'default value'], 97 | 98 | // 支持几乎所有Encore\Admin\Form\Field对象的链式调用方法,非链式调用的值将在Field实例化时作为参数传入 99 | // 以下将执行$form->test('value2', 'label text')->placeholder('typing...')->rules('required') 100 | 'value2'=>['label text', 'placeholder'=>'typing...', 'rules'=>'required'], 101 | 102 | // 需要定义字段类型,type键值不可省略 103 | 'value3'=>['type'=>'select', 'select label text', 'options'=>['option1'=>'option1', 'option2'=>'option2']], 104 | 'value5'=>['type'=>'checkbox', 'options'=>['foo'=>'foo', 'bar'=>'bar']], 105 | 'value6'=>['type'=>'ip'], 106 | 'value7'=>['type'=>'mobile'], 107 | 'value8'=>['type'=>'color'], 108 | 'value9'=>['type'=>'time', 'format'=>'HH:mm'], 109 | 110 | // 范围类型的字段会分别存储为两个配置项,'sample.value10.start' 及 'sample.value10.end' 111 | 'value10'=>['type'=>'dateRange', 'dateRange label text'], 112 | 'value11'=>['type'=>'number', 'min'=>100, 'default'=>100], 113 | 'value12'=>['type'=>'rate'], 114 | 115 | // 支持没有参数的链式调用,以下将执行$form->image('value13')->uniqueName() 116 | 'value13'=>['type'=>'image', 'uniqueName'], 117 | 'value14'=>['type'=>'file', 'uniqueName'], 118 | 'value17'=>['type'=>'editor'], 119 | 'value18'=>['type'=>'switch'], 120 | 'value19'=>['type'=>'tags'], 121 | 122 | // 以下一对多关系将被自动转化为逗号隔开的数据存入数据库 123 | 'value4'=>['type'=>'listbox', 'options'=>['foo'=>'foo', 'bar'=>'bar']], 124 | 'value15'=>['type'=>'multipleImage', 'removable', 'uniqueName'], 125 | 'value16'=>['type'=>'multipleFile', 'removable', 'uniqueName'], 126 | 127 | ], 128 | // 对应配置组值 129 | 'sample2' => [ 130 | 'value' 131 | ] 132 | ``` 133 | 134 | 135 | 136 | ## 效果示例: 137 | 138 | 配置文件自动转化为表单: 139 | 140 | ![Snipaste_2019-03-18_16-14-05](https://ws2.sinaimg.cn/large/006tKfTcgy1g171q2oy8vj31b70qjwgd.jpg) 141 | 142 | 数据库: 143 | 144 | ![Snipaste_2019-03-18_16-21-44](https://ws1.sinaimg.cn/large/006tKfTcgy1g171q8ri68j30uk0fa411.jpg) 145 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /src/Http/Controllers/AdminConfigController.php: -------------------------------------------------------------------------------- 1 | header(config('admin.extensions.admin-config.title')) 29 | ->description(config('admin.extensions.admin-config.description')) 30 | ->body($this->form()->configEdit()); 31 | } 32 | 33 | protected function form() 34 | { 35 | $tabs = config('admin-config.admin_config_groups'); 36 | $permissions = config('admin-config.admin_config_permissions'); 37 | $form = new ConfigForm(new AdminConfigModel()); 38 | if ($tabs) { 39 | foreach ($tabs as $prefix => $title) { 40 | // Skip building the tab if no permission 41 | if (!Admin::user()->isAdministrator() && !empty($permissions[$prefix]) && !Admin::user()->inRoles($permissions[$prefix])) { 42 | continue; 43 | } 44 | 45 | // When prefixes are configured only, the label key value can be undefined 46 | if (is_numeric($prefix) && is_string($title)) { 47 | $prefix = $title; 48 | } 49 | $fields = config('admin-config.' . $prefix); 50 | $form->tab($title, function (ConfigForm $form) use ($fields, $prefix){ 51 | foreach ($fields as $name => $settings) { 52 | 53 | // When only the field name is configured, the field key value can be undefined 54 | if (is_numeric($name) && is_string($settings)) { 55 | $name = $settings; 56 | $settings = []; 57 | } 58 | 59 | // The field type must have type as the key name 60 | $fieldType = isset($settings['type']) ? $settings['type'] : 'text'; 61 | $fieldName = $prefix . ConfigForm::SEPARATOR . $name; 62 | unset($settings['type']); 63 | 64 | // Determine whether the field type is supported 65 | if (isset($form::$availableFields[$fieldType])) { 66 | 67 | foreach ($settings as $settingKey => $settingValue) { 68 | 69 | // Filter out serpentine invocation methods in the configuration to support single or no arguments 70 | $key = $settingKey; 71 | // Snake methods with no parameters 72 | if (is_numeric($settingKey) && is_string($settingValue)) { 73 | $settingKey = $settingValue; 74 | } 75 | if (in_array($settingKey, $this->getFieldFoo())) { 76 | if ($settingKey == $settingValue) { 77 | $snakelikes[$settingValue] = $settingValue; 78 | } else { 79 | $snakelikes[$settingKey] = $settingValue; 80 | } 81 | unset($settings[$key]); 82 | } 83 | 84 | // Filter out the callback method 85 | if ($settingValue instanceof \Closure) { 86 | $callbacks[] = $settingValue; 87 | unset($settings[$key]); 88 | } 89 | } 90 | 91 | // Build the field with the remaining parameters 92 | $settings = array_values($settings); 93 | if (in_array($fieldType, $this->rangeFoo)) { 94 | $fieldNameEnd = $fieldName . ConfigForm::SEPARATOR . 'end'; 95 | $fieldName = $fieldName . ConfigForm::SEPARATOR . 'start'; 96 | array_unshift($settings, $fieldNameEnd); 97 | } 98 | $field = $form->$fieldType($fieldName, ...$settings); 99 | 100 | 101 | // Call the snake method 102 | if (isset($snakelikes)) { 103 | foreach ($snakelikes as $foo => $params) { 104 | if ($foo == $params) { 105 | $field->$foo(); 106 | } else { 107 | $field->$foo($params); 108 | } 109 | } 110 | unset($snakelikes); 111 | } 112 | 113 | // Call the callback method 114 | if (isset($callbacks)) { 115 | foreach ($callbacks as $callback) { 116 | call_user_func($callback, $field); 117 | } 118 | unset($callback); 119 | } 120 | } 121 | } 122 | }); 123 | } 124 | } 125 | $form->setAction(admin_base_path('admin-config')); 126 | $form->tools(function (Tools $tools) { 127 | $tools->disableList(); 128 | $tools->disableDelete(); 129 | $tools->disableView(); 130 | }); 131 | $form->footer(function (Footer $footer) { 132 | $footer->disableReset(); 133 | $footer->disableViewCheck(); 134 | $footer->disableEditingCheck(); 135 | $footer->disableCreatingCheck(); 136 | }); 137 | $form->setTitle(config('admin.extensions.admin-config.action', ' ')); 138 | return $form; 139 | } 140 | 141 | public function update() 142 | { 143 | return $this->form()->configUpdate(); 144 | } 145 | 146 | protected function getFieldFoo() 147 | { 148 | return $this->publicFieldFoo; 149 | } 150 | } --------------------------------------------------------------------------------