Bläddra i källkod

新增插件的多语言包
新增附件删除的行为
新增命令行安装时的参数配置
新增一键生成菜单时忽略未启用软删除的方法
修复在iOS下列表中列字段过多未启用卡片视图的BUG
修复会员管理中规则管理分页的BUG
修复导航菜单隐藏后仍显示的BUG
优化数据列表在移动端的显示
优化异常页面的显示

Karson 7 år sedan
förälder
incheckning
f572e345d5

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 42
.travis.yml


+ 18 - 0
application/admin/command/Addon/stubs/addon.stub

@@ -29,6 +29,24 @@ class {%addonClassName%} extends Addons
     }
 
     /**
+     * 插件启用方法
+     * @return bool
+     */
+    public function enable()
+    {
+        return true;
+    }
+
+    /**
+     * 插件禁用方法
+     * @return bool
+     */
+    public function disable()
+    {
+        return true;
+    }
+
+    /**
      * 实现钩子方法
      * @return mixed
      */

+ 2 - 5
application/admin/command/Crud/stubs/controller.stub

@@ -4,9 +4,6 @@ namespace {%controllerNamespace%};
 
 use app\common\controller\Backend;
 
-use think\Controller;
-use think\Request;
-
 /**
  * {%tableComment%}
  *
@@ -28,8 +25,8 @@ class {%controllerName%} extends Backend
     }
     
     /**
-     * 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个方法
-     * 因此在当前控制器中可不用编写增删改查的代码,如果需要自己控制这部分逻辑
+     * 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
+     * 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
      * 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
      */
     

+ 34 - 5
application/admin/command/Install.php

@@ -18,17 +18,29 @@ class Install extends Command
 
     protected function configure()
     {
+        $config = Config::get('database');
         $this
                 ->setName('install')
+                ->addOption('hostname', 'a', Option::VALUE_OPTIONAL, 'mysql hostname', $config['hostname'])
+                ->addOption('hostport', 'o', Option::VALUE_OPTIONAL, 'mysql hostport', $config['hostport'])
+                ->addOption('database', 'd', Option::VALUE_OPTIONAL, 'mysql database', $config['database'])
+                ->addOption('prefix', 'r', Option::VALUE_OPTIONAL, 'table prefix', $config['prefix'])
+                ->addOption('username', 'u', Option::VALUE_OPTIONAL, 'mysql username', $config['username'])
+                ->addOption('password', 'p', Option::VALUE_OPTIONAL, 'mysql password', $config['password'])
                 ->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override', FALSE)
                 ->setDescription('New installation of FastAdmin');
     }
 
     protected function execute(Input $input, Output $output)
     {
-
-        //覆盖安装
+        // 覆盖安装
         $force = $input->getOption('force');
+        $hostname = $input->getOption('hostname');
+        $hostport = $input->getOption('hostport');
+        $database = $input->getOption('database');
+        $prefix = $input->getOption('prefix');
+        $username = $input->getOption('username');
+        $password = $input->getOption('password');
 
         $installLockFile = __DIR__ . "/Install/install.lock";
         if (is_file($installLockFile) && !$force)
@@ -38,11 +50,13 @@ class Install extends Command
 
         $sql = file_get_contents(__DIR__ . '/Install/fastadmin.sql');
 
+        $sql = str_replace("`fa_", "`{$prefix}", $sql);
+
         // 先尝试能否自动创建数据库
         $config = Config::get('database');
-        $pdo = new PDO("{$config['type']}:host={$config['hostname']}" . ($config['hostport'] ? ";port={$config['hostport']}" : ''), $config['username'], $config['password']);
+        $pdo = new PDO("{$config['type']}:host={$hostname}" . ($hostport ? ";port={$hostport}" : ''), $username, $password);
         $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
-        $pdo->query("CREATE DATABASE IF NOT EXISTS `{$config['database']}` CHARACTER SET utf8 COLLATE utf8_general_ci;");
+        $pdo->query("CREATE DATABASE IF NOT EXISTS `{$database}` CHARACTER SET utf8 COLLATE utf8_general_ci;");
 
         // 查询一次SQL,判断连接是否正常
         Db::execute("SELECT 1");
@@ -51,7 +65,22 @@ class Install extends Command
         Db::getPdo()->exec($sql);
 
         file_put_contents($installLockFile, 1);
-        
+
+        $dbConfigFile = APP_PATH . 'database.php';
+        $config = @file_get_contents($dbConfigFile);
+        $callback = function($matches) use($hostname, $hostport, $username, $password, $database, $prefix) {
+            $field = $matches[1];
+            $replace = $$field;
+            if ($matches[1] == 'hostport' && $hostport == 3306)
+            {
+                $replace = '';
+            }
+            return "'{$matches[1]}'{$matches[2]}=>{$matches[3]}Env::get('database.{$matches[1]}', '{$replace}'),";
+        };
+        $config = preg_replace_callback("/'(hostname|database|username|password|hostport|prefix)'(\s+)=>(\s+)Env::get\((.*)\)\,/", $callback, $config);
+        // 写入数据库配置
+        file_put_contents($dbConfigFile, $config);
+
         $output->info("Install Successed!");
     }
 

+ 19 - 1
application/admin/command/Menu.php

@@ -187,6 +187,19 @@ class Menu extends Command
         //只匹配公共的方法
         $methods = $reflector->getMethods(ReflectionMethod::IS_PUBLIC);
         $classComment = $reflector->getDocComment();
+        //判断是否有启用软删除
+        $softDeleteMethods = ['destroy', 'restore', 'recyclebin'];
+        $withSofeDelete = false;
+        preg_match_all("/\\\$this\->model\s*=\s*model\('(\w+)'\);/", $classContent, $matches);
+        if (isset($matches[1]) && isset($matches[1][0]) && $matches[1][0])
+        {
+            \think\Request::instance()->module('admin');
+            $model = model($matches[1][0]);
+            if (in_array('trashed', get_class_methods($model)))
+            {
+                $withSofeDelete = true;
+            }
+        }
         //忽略的类
         if (stripos($classComment, "@internal") !== FALSE)
         {
@@ -216,7 +229,7 @@ class Menu extends Command
         //导入中文语言包
         \think\Lang::load(dirname(__DIR__) . DS . 'lang/zh-cn.php');
 
-        //先入菜单的数据
+        //先入菜单的数据
         $pid = 0;
         foreach ($controllerArr as $k => $v)
         {
@@ -248,6 +261,11 @@ class Menu extends Command
             {
                 continue;
             }
+            //未启用软删除时过滤相关方法
+            if (!$withSofeDelete && in_array($n->name, $softDeleteMethods))
+            {
+                continue;
+            }
             //只匹配符合的方法
             if (!preg_match('/^(\w+)' . Config::get('action_suffix') . '/', $n->name, $matchtwo))
             {

+ 1 - 0
application/admin/controller/Index.php

@@ -97,6 +97,7 @@ class Index extends Backend
         }
         $background = cdnurl(Config::get('fastadmin.login_background'));
         $this->view->assign('background', $background);
+        $this->view->assign('title', __('Login'));
         Hook::listen("login_init", $this->request);
         return $this->view->fetch();
     }

+ 16 - 4
application/admin/controller/general/Attachment.php

@@ -78,16 +78,28 @@ class Attachment extends Backend
         return $this->view->fetch();
     }
 
+    /**
+     * 删除附件
+     * @param array $ids
+     */
     public function del($ids = "")
     {
         if ($ids)
         {
-            $count = $this->model->destroy($ids);
-            if ($count)
+            \think\Hook::add('upload_delete', function($params) {
+                $attachmentFile = ROOT_PATH . '/public' . $params['url'];
+                if (is_file($attachmentFile))
+                {
+                    @unlink($attachmentFile);
+                }
+            });
+            $attachmentlist = $this->model->where('id', 'in', $ids)->select();
+            foreach ($attachmentlist as $attachment)
             {
-                \think\Hook::listen("upload_after", $this);
-                $this->success();
+                \think\Hook::listen("upload_delete", $attachment);
+                $attachment->delete();
             }
+            $this->success();
         }
         $this->error(__('Parameter %s can not be empty', 'ids'));
     }

+ 1 - 0
application/admin/lang/zh-cn.php

@@ -97,6 +97,7 @@ return [
     'Create time'                                           => '创建时间',
     'Update time'                                           => '更新时间',
     'Flag'                                                  => '标志',
+    'Drag to sort'                                          => '拖动进行排序',
     'Redirect now'                                          => '立即跳转',
     'Common search'                                         => '普通搜索',
     'Search %s'                                             => '搜索 %s',

+ 1 - 0
application/admin/lang/zh-cn/category.php

@@ -4,6 +4,7 @@ return [
     'Id'          => 'ID',
     'Pid'         => '父ID',
     'Type'        => '栏目类型',
+    'All'         => '全部',
     'Image'       => '图片',
     'Keywords'    => '关键字',
     'Description' => '描述',

+ 1 - 1
application/admin/library/Auth.php

@@ -403,7 +403,7 @@ class Auth extends \fast\Auth
         $select_id = 0;
         $pinyin = new \Overtrue\Pinyin\Pinyin('Overtrue\Pinyin\MemoryFileDictLoader');
         // 必须将结果集转换为数组
-        $ruleList = collection(model('AuthRule')->where('ismenu', 1)->order('weigh', 'desc')->cache("__menu__")->select())->toArray();
+        $ruleList = collection(model('AuthRule')->where('status', 'normal')->where('ismenu', 1)->order('weigh', 'desc')->cache("__menu__")->select())->toArray();
         foreach ($ruleList as $k => &$v)
         {
             if (!in_array($v['name'], $userRule))

+ 1 - 1
application/admin/view/general/profile/index.html

@@ -41,7 +41,7 @@
                     <div class="box-body box-profile">
                         
                         <div class="profile-avatar-container">
-                            <img class="profile-user-img img-responsive img-circle plupload" src="{$admin.avatar}" alt="">
+                            <img class="profile-user-img img-responsive img-circle plupload" src="__CDN__{$admin.avatar}" alt="">
                             <div class="profile-avatar-text img-circle">{:__('Click to edit')}</div>
                             <button id="plupload-avatar" class="plupload" data-input-id="c-avatar"><i class="fa fa-upload"></i> {:__('Upload')}</button>
                         </div>

+ 11 - 1
application/common/behavior/Common.php

@@ -3,11 +3,12 @@
 namespace app\common\behavior;
 
 use think\Config;
+use think\Lang;
 
 class Common
 {
 
-    public function run(&$request)
+    public function moduleInit(&$request)
     {
         // 设置mbstring字符编码
         mb_internal_encoding("UTF-8");
@@ -53,4 +54,13 @@ class Common
         }
     }
 
+    public function addonBegin(&$request)
+    {
+        // 加载插件语言包
+        Lang::load([
+            APP_PATH . 'common' . DS . 'lang' . DS . $request->langset() . DS . 'addon' . EXT,
+        ]);
+        $this->moduleInit($request);
+    }
+
 }

+ 9 - 0
application/common/lang/zh-cn/addon.php

@@ -0,0 +1,9 @@
+<?php
+
+return [
+    'addon %s not found'            => '插件未找到',
+    'addon %s is disabled'          => '插件已禁用',
+    'addon controller %s not found' => '插件控制器未找到',
+    'addon action %s not found'     => '插件控制器方法未找到',
+    'addon can not be empty'        => '插件不能为空',
+];

+ 50 - 6
application/common/library/Menu.php

@@ -59,6 +59,55 @@ class Menu
      */
     public static function delete($name)
     {
+        $ids = self::getAuthRuleIdsByName($name);
+        if (!$ids)
+        {
+            return false;
+        }
+        AuthRule::destroy($ids);
+        return true;
+    }
+    
+    /**
+     * 启用菜单
+     * @param string $name
+     * @return boolean
+     */
+    public static function enable($name)
+    {
+        $ids = self::getAuthRuleIdsByName($name);
+        if (!$ids)
+        {
+            return false;
+        }
+        AuthRule::where('id', 'in', $ids)->update(['status' => 'normal']);
+        return true;
+    }
+    
+    /**
+     * 禁用菜单
+     * @param string $name
+     * @return boolean
+     */
+    public static function disable($name)
+    {
+        $ids = self::getAuthRuleIdsByName($name);
+        if (!$ids)
+        {
+            return false;
+        }
+        AuthRule::where('id', 'in', $ids)->update(['status' => 'hidden']);
+        return true;
+    }
+
+    /**
+     * 根据名称获取规则IDS
+     * @param string $name
+     * @return array
+     */
+    public static function getAuthRuleIdsByName($name)
+    {
+        $ids = [];
         $menu = AuthRule::getByName($name);
         if ($menu)
         {
@@ -66,13 +115,8 @@ class Menu
             $ruleList = collection(model('AuthRule')->order('weigh', 'desc')->field('id,pid,name')->select())->toArray();
             // 构造菜单数据
             $ids = Tree::instance()->init($ruleList)->getChildrenIds($menu['id'], true);
-            if ($ids)
-            {
-                AuthRule::destroy($ids);
-            }
-            return true;
         }
-        return false;
+        return $ids;
     }
 
 }

+ 8 - 3
application/common/view/tpl/think_exception.tpl

@@ -1,3 +1,7 @@
+<?php
+$cdnurl = function_exists('config') ? config('view_replace_str.__CDN__') : '';
+$publicurl = function_exists('config') ? config('view_replace_str.__PUBLIC__') : '/';
+?>
 <!DOCTYPE html>
 <html>
     <head>
@@ -5,6 +9,7 @@
         <title>发生错误</title>
         <meta name="robots" content="noindex,nofollow" />
         <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
+        <link rel="shortcut icon" href="<?php echo $cdnurl;?>/assets/img/favicon.ico" />
         <style>
             * {-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;}
             html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,abbr,address,cite,code,del,dfn,em,img,ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,caption,article,aside,canvas,details,figcaption,figure,footer,header,hgroup,menu,nav,section,summary,time,mark,audio,video {margin:0;padding:0;border:0;outline:0;vertical-align:baseline;background:transparent;}
@@ -52,7 +57,7 @@
 
         <div class="content-container">
             <div class="head-line">
-                <img src="/assets/img/error.svg" alt="" width="120" />
+                <img src="<?php echo $cdnurl;?>/assets/img/error.svg" alt="" width="120" />
             </div>
             <div class="subheader">
                 <?php echo htmlentities($message); ?>
@@ -66,8 +71,8 @@
 
             </div>
             <div class="buttons-container">
-                <a href="<?php echo function_exists('url') ? url('/') : '/';?>">返回主页</a>
-                <a href="<?php echo function_exists('url') ? url('/') : '/';?>">反馈错误</a>
+                <a href="<?php echo $publicurl;?>">返回主页</a>
+                <a href="<?php echo $publicurl;?>">反馈错误</a>
             </div>
         </div>
     </body>

+ 1 - 1
application/config.php

@@ -253,7 +253,7 @@ return [
         //自动检测更新
         'checkupdate'      => false,
         //版本号
-        'version'          => '1.0.0.20180204_beta',
+        'version'          => '1.0.0.20180222_beta',
         'api_url'          => 'http://api.fastadmin.net',
     ],
 ];

+ 2 - 2
application/tags.php

@@ -19,8 +19,8 @@ return [
     'module_init'  => [
         'app\\common\\behavior\\Common',
     ],
-    // 模块初始化
-    'addons_init'  => [
+    // 插件开始
+    'addon_begin'  => [
         'app\\common\\behavior\\Common',
     ],
     // 操作开始执行

+ 2 - 0
public/assets/js/backend/addon.js

@@ -186,6 +186,8 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
                                 Layer.closeAll();
                                 Layer.alert(ret.msg);
                             }, function (data, ret) {
+                                Controller.api.userinfo.set(null);
+                                Layer.closeAll();
                                 Layer.alert(ret.msg);
                             });
                         }

+ 4 - 1
public/assets/js/backend/user/rule.js

@@ -37,7 +37,10 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
                         {field: 'status', title: __('Status'), formatter: Table.api.formatter.status},
                         {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
                     ]
-                ]
+                ],
+                pagination: false,
+                search: false,
+                commonSearch: false,
             });
 
             // 为表格绑定事件

+ 6 - 2
public/assets/js/bootstrap-table-commonsearch.js

@@ -345,12 +345,16 @@
 
         var that = this,
                 html = [];
-        html.push(sprintf('<div class="columns-%s pull-%s" style="margin-top:10px;">', this.options.buttonsAlign, this.options.buttonsAlign));
+        html.push(sprintf('<div class="columns-%s pull-%s" style="margin-top:10px;margin-bottom:10px;">', this.options.buttonsAlign, this.options.buttonsAlign));
         html.push(sprintf('<button class="btn btn-default%s' + '" type="button" name="commonSearch" title="%s">', that.options.iconSize === undefined ? '' : ' btn-' + that.options.iconSize, that.options.formatCommonSearch()));
         html.push(sprintf('<i class="%s %s"></i>', that.options.iconsPrefix, that.options.icons.commonSearchIcon))
         html.push('</button></div>');
 
-        that.$toolbar.prepend(html.join(''));
+        if (that.$toolbar.find(".pull-right").size() > 0) {
+            $(html.join('')).insertBefore(that.$toolbar.find(".pull-right:first"));
+        } else {
+            that.$toolbar.append(html.join(''));
+        }
 
         initCommonSearch(that.columns, that);
 

+ 13 - 5
public/assets/js/require-backend.min.js

@@ -9432,12 +9432,16 @@ return d.keepInvalid=a,l},l.datepickerInput=function(a){if(0===arguments.length)
 
         var that = this,
                 html = [];
-        html.push(sprintf('<div class="columns-%s pull-%s" style="margin-top:10px;">', this.options.buttonsAlign, this.options.buttonsAlign));
+        html.push(sprintf('<div class="columns-%s pull-%s" style="margin-top:10px;margin-bottom:10px;">', this.options.buttonsAlign, this.options.buttonsAlign));
         html.push(sprintf('<button class="btn btn-default%s' + '" type="button" name="commonSearch" title="%s">', that.options.iconSize === undefined ? '' : ' btn-' + that.options.iconSize, that.options.formatCommonSearch()));
         html.push(sprintf('<i class="%s %s"></i>', that.options.iconsPrefix, that.options.icons.commonSearchIcon))
         html.push('</button></div>');
 
-        that.$toolbar.prepend(html.join(''));
+        if (that.$toolbar.find(".pull-right").size() > 0) {
+            $(html.join('')).insertBefore(that.$toolbar.find(".pull-right:first"));
+        } else {
+            that.$toolbar.append(html.join(''));
+        }
 
         initCommonSearch(that.columns, that);
 
@@ -9667,6 +9671,10 @@ define('table',['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstr
                 defaults = defaults ? defaults : {};
                 columnDefaults = columnDefaults ? columnDefaults : {};
                 locales = locales ? locales : {};
+                // 如果是iOS设备则启用卡片视图
+                if (navigator.userAgent.match(/(iPod|iPhone|iPad)/)) {
+                    Table.defaults.cardView = true;
+                }
                 // 写入bootstrap-table默认配置
                 $.extend(true, $.fn.bootstrapTable.defaults, Table.defaults, defaults);
                 // 写入bootstrap-table column配置
@@ -10021,13 +10029,13 @@ define('table',['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstr
                     var buttons = $.extend([], this.buttons || []);
 
                     if (options.extend.dragsort_url !== '') {
-                        buttons.push({name: 'dragsort', icon: 'fa fa-arrows', classname: 'btn btn-xs btn-primary btn-dragsort'});
+                        buttons.push({name: 'dragsort', icon: 'fa fa-arrows', title: __('Drag to sort'), classname: 'btn btn-xs btn-primary btn-dragsort'});
                     }
                     if (options.extend.edit_url !== '') {
-                        buttons.push({name: 'edit', icon: 'fa fa-pencil', classname: 'btn btn-xs btn-success btn-editone', url: options.extend.edit_url});
+                        buttons.push({name: 'edit', icon: 'fa fa-pencil', title: __('Edit'), classname: 'btn btn-xs btn-success btn-editone', url: options.extend.edit_url});
                     }
                     if (options.extend.del_url !== '') {
-                        buttons.push({name: 'del', icon: 'fa fa-trash', classname: 'btn btn-xs btn-danger btn-delone'});
+                        buttons.push({name: 'del', icon: 'fa fa-trash', title: __('Del'), classname: 'btn btn-xs btn-danger btn-delone'});
                     }
                     return Table.api.buttonlink(this, buttons, value, row, index, 'operate');
                 },

+ 7 - 3
public/assets/js/require-table.js

@@ -69,6 +69,10 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
                 defaults = defaults ? defaults : {};
                 columnDefaults = columnDefaults ? columnDefaults : {};
                 locales = locales ? locales : {};
+                // 如果是iOS设备则启用卡片视图
+                if (navigator.userAgent.match(/(iPod|iPhone|iPad)/)) {
+                    Table.defaults.cardView = true;
+                }
                 // 写入bootstrap-table默认配置
                 $.extend(true, $.fn.bootstrapTable.defaults, Table.defaults, defaults);
                 // 写入bootstrap-table column配置
@@ -423,13 +427,13 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
                     var buttons = $.extend([], this.buttons || []);
 
                     if (options.extend.dragsort_url !== '') {
-                        buttons.push({name: 'dragsort', icon: 'fa fa-arrows', classname: 'btn btn-xs btn-primary btn-dragsort'});
+                        buttons.push({name: 'dragsort', icon: 'fa fa-arrows', title: __('Drag to sort'), classname: 'btn btn-xs btn-primary btn-dragsort'});
                     }
                     if (options.extend.edit_url !== '') {
-                        buttons.push({name: 'edit', icon: 'fa fa-pencil', classname: 'btn btn-xs btn-success btn-editone', url: options.extend.edit_url});
+                        buttons.push({name: 'edit', icon: 'fa fa-pencil', title: __('Edit'), classname: 'btn btn-xs btn-success btn-editone', url: options.extend.edit_url});
                     }
                     if (options.extend.del_url !== '') {
-                        buttons.push({name: 'del', icon: 'fa fa-trash', classname: 'btn btn-xs btn-danger btn-delone'});
+                        buttons.push({name: 'del', icon: 'fa fa-trash', title: __('Del'), classname: 'btn btn-xs btn-danger btn-delone'});
                     }
                     return Table.api.buttonlink(this, buttons, value, row, index, 'operate');
                 },