Browse Source

新增定时任务预期执行
新增CSS中fixed-footer类
新增Install时的权限检测
新增loadlang加载语言包方法
修复上传超出大小限制时的提示文字
修复后台部分语言文字
修复定时任务类型为SQL时的BUG
移除自动生成JS中主键的state字段

Karson 8 years ago
parent
commit
99ec34cd68

+ 1 - 2
application/admin/command/Crud.php

@@ -553,7 +553,7 @@ class Crud extends Command
                     if ($v['COLUMN_KEY'] == 'PRI' && !$priDefined)
                     {
                         $priDefined = TRUE;
-                        $javascriptList[] = "{field: 'state', checkbox: true}";
+                        $javascriptList[] = "{checkbox: true}";
                     }
                     //构造JS列信息
                     $javascriptList[] = $this->getJsColumn($field, $v['DATA_TYPE']);
@@ -708,7 +708,6 @@ class Crud extends Command
             return;
         $fieldList = $this->getFieldListName($field);
         $methodName = 'get' . ucfirst($fieldList);
-        unset($v);
         foreach ($itemArr as $k => &$v)
         {
             $v = "__('" . ucfirst($v) . "')";

+ 1 - 1
application/admin/command/Crud/stubs/add.stub

@@ -1,7 +1,7 @@
 <form id="add-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
 
 {%addList%}
-    <div class="form-group hide layer-footer">
+    <div class="form-group layer-footer">
         <label class="control-label col-xs-12 col-sm-2"></label>
         <div class="col-xs-12 col-sm-8">
             <button type="submit" class="btn btn-success btn-embossed">{:__('OK')}</button>

+ 1 - 1
application/admin/command/Crud/stubs/edit.stub

@@ -1,7 +1,7 @@
 <form id="edit-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
 
 {%editList%}
-    <div class="form-group hide layer-footer">
+    <div class="form-group layer-footer">
         <label class="control-label col-xs-12 col-sm-2"></label>
         <div class="col-xs-12 col-sm-8">
             <button type="submit" class="btn btn-success btn-embossed">{:__('OK')}</button>

+ 1 - 15
application/admin/command/Install/fastadmin.sql

@@ -279,27 +279,7 @@ CREATE TABLE `fa_crontab` (
 --  Records of `fa_crontab`
 -- ----------------------------
 BEGIN;
-INSERT INTO `fa_crontab` VALUES ('1', 'url', '请求URL', 'http://www.fastadmin.net', '* * * * 0-2', '0', '0', '0', '1497070825', '1497070825', '1483200000', '1546272000', '0', '0', 'normal'), ('2', 'url', '执行SQL', 'SELECT 1;', '* * * * 0-2', '0', '0', '0', '1497071095', '1497071095', '1483200000', '1546272000', '0', '0', 'normal');
-COMMIT;
-
-DROP TABLE IF EXISTS `fa_func`;
-CREATE TABLE `fa_func` (
-  `f_id` int(10) NOT NULL AUTO_INCREMENT,
-  `f_title` varchar(50) NOT NULL COMMENT '标题',
-  `createtime` int(10) NOT NULL DEFAULT '0',
-  `updatetime` int(10) NOT NULL DEFAULT '0',
-  `status` varchar(30) NOT NULL DEFAULT '',
-  PRIMARY KEY (`f_id`)
-) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='函数表';
-
-BEGIN;
-INSERT INTO `fa_func` VALUES ('1', 'test', '1495811783', '1495811783', 'hidden'), ('2', 'test2fff', '1495811787', '1495813304', 'hidden'), ('3', 'fds', '1495813308', '1495813308', 'normal');
+INSERT INTO `fa_crontab` VALUES ('1', 'url', '请求FastAdmin', 'http://www.fastadmin.net', '* * * * *', '0', '0', '0', '1497070825', '1497070825', '1483200000', '1546272000', '0', '0', 'normal'), ('2', 'sql', '查询一条SQL', 'SELECT 1;', '* * * * *', '0', '0', '0', '1497071095', '1497071095', '1483200000', '1546272000', '0', '0', 'normal');
 COMMIT;
 
 -- ----------------------------

+ 7 - 2
application/admin/controller/Ajax.php

@@ -139,10 +139,9 @@ class Ajax extends Backend
     public function lang()
     {
         header('Content-Type: application/javascript');
-        $modulename = $this->request->module();
         $callback = $this->request->get('callback');
         $controllername = input("controllername");
-        Lang::load(APP_PATH . $modulename . '/lang/' . Lang::detect() . '/' . str_replace('.', '/', $controllername) . '.php');
+        $this->loadlang($controllername);
         //强制输出JSON Object
         $result = 'define(' . json_encode(Lang::get(), JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE) . ');';
         return $result;
@@ -153,6 +152,7 @@ class Ajax extends Backend
      */
     public function roletree()
     {
+        $this->loadlang('auth/group');
         $model = model('AuthGroup');
         $id = $this->request->post("id");
         $pid = $this->request->post("pid");
@@ -206,6 +206,11 @@ class Ajax extends Backend
     {
         $this->code = -1;
         $file = $this->request->file('file');
+        if (!$file)
+        {
+            $this->msg = "未上传文件或超出服务器上传限制";
+            return;
+        }
 
         //判断是否已经存在附件
         $sha1 = $file->hash();

+ 51 - 5
application/admin/controller/general/Crontab.php

@@ -3,6 +3,7 @@
 namespace app\admin\controller\general;
 
 use app\common\controller\Backend;
+use Cron\CronExpression;
 
 /**
  * 定时任务
@@ -14,16 +15,13 @@ class Crontab extends Backend
 {
 
     protected $model = null;
+    protected $noNeedRight = ['check_schedule', 'get_schedule_future'];
 
     public function _initialize()
     {
         parent::_initialize();
         $this->model = model('Crontab');
-        $this->view->assign('typedata', [
-            'url'   => __('Request Url'),
-            'sql'   => __('Execute Sql Script'),
-            'shell' => __('Execute Shell'),
-        ]);
+        $this->view->assign('typedata', \app\common\model\Crontab::getTypeList());
     }
 
     /**
@@ -44,6 +42,11 @@ class Crontab extends Backend
                     ->order($sort, $order)
                     ->limit($offset, $limit)
                     ->select();
+            foreach ($list as $k => &$v)
+            {
+                $cron = CronExpression::factory($v['schedule']);
+                $v['nexttime'] = $cron->getNextRunDate()->getTimestamp();
+            }
             $result = array("total" => $total, "rows" => $list);
 
             return json($result);
@@ -51,4 +54,47 @@ class Crontab extends Backend
         return $this->view->fetch();
     }
 
+    /**
+     * 判断Crontab格式是否正确
+     * @internal
+     */
+    public function check_schedule()
+    {
+        $row = $this->request->post("row/a");
+        $schedule = isset($row['schedule']) ? $row['schedule'] : '';
+        if (CronExpression::isValidExpression($schedule))
+        {
+            return json(['ok' => '']);
+        }
+        else
+        {
+            return json(['error' => __('Crontab format invalid')]);
+        }
+    }
+
+    /**
+     * 根据Crontab表达式读取未来七次的时间
+     * @internal
+     */
+    public function get_schedule_future()
+    {
+        $time = [];
+        $schedule = $this->request->post('schedule');
+        $days = (int) $this->request->post('days');
+        try
+        {
+            $cron = CronExpression::factory($schedule);
+            for ($i = 0; $i < $days; $i++)
+            {
+                $time[] = $cron->getNextRunDate(null, $i)->format('Y-m-d H:i:s');
+            }
+        }
+        catch (\Exception $e)
+        {
+            
+        }
+
+        return json(['futuretime' => $time]);
+    }
+
 }

+ 2 - 0
application/admin/lang/zh-cn/auth/group.php

@@ -3,5 +3,7 @@
 return [
     'The parent group can not be its own child'                            => '父组别不能是自身的子组别',
     'The parent group can not found'                                       => '父组别未找到',
+    'Group not found'                                                      => '组别未找到',
+    'Can not change the parent to child'                                   => '父组别不能是它的子组别',
     'You can not delete group that contain child group and administrators' => '你不能删除含有子组和管理员的组',
 ];

+ 12 - 9
application/admin/lang/zh-cn/general/crontab.php

@@ -1,13 +1,16 @@
 <?php
 
 return [
-    'Title'              => '任务标题',
-    'Maximums'           => '最多执行',
-    'Sleep'              => '延迟秒数',
-    'Schedule'           => '执行周期',
-    'Executes'           => '执行次数',
-    'Execute time'       => '执行时间',
-    'Request Url'        => '请求URL',
-    'Execute Sql Script' => '执行SQL',
-    'Execute Shell'      => '执行Shell',
+    'Title'                                => '任务标题',
+    'Maximums'                             => '最多执行',
+    'Sleep'                                => '延迟秒数',
+    'Schedule'                             => '执行周期',
+    'Executes'                             => '执行次数',
+    'Execute time'                         => '最后执行时间',
+    'Request Url'                          => '请求URL',
+    'Execute Sql Script'                   => '执行SQL',
+    'Execute Shell'                        => '执行Shell',
+    'Crontab format invalid'               => 'Crontab格式错误',
+    'Next execute time'                    => '下次预计时间',
+    'The next %s times the execution time' => '接下来 %s 次的执行时间',
 ];

+ 0 - 7
application/admin/view/general/attachment/index.html

@@ -7,13 +7,6 @@
                 <div class="widget-body no-padding">
                     <div id="toolbar" class="toolbar">
                         {:build_toolbar()}
-                        <div class="dropdown btn-group">
-                            <a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> {:__('More')}</a>
-                            <ul class="dropdown-menu text-left" role="menu">
-                                <li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=normal"><i class="fa fa-eye"></i> {:__('Set to normal')}</a></li>
-                                <li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=hidden"><i class="fa fa-eye-slash"></i> {:__('Set to hidden')}</a></li>
-                            </ul>
-                        </div>
                     </div>
                     <table id="table" class="table table-striped table-bordered table-hover" width="100%">
                     </table>

+ 13 - 8
application/admin/view/general/crontab/add.html

@@ -25,14 +25,19 @@
     <div class="form-group">
         <label for="schedule" class="control-label col-xs-12 col-sm-2">{:__('Schedule')}:</label>
         <div class="col-xs-12 col-sm-8">
-            <div id="schedulepicker"></div>
-            <input type="text" class="form-control hide" id="schedule" name="row[schedule]" value="" data-rule="required" />
-        </div>
-    </div>
-    <div class="form-group">
-        <label for="sleep" class="control-label col-xs-12 col-sm-2">{:__('sleep')}:</label>
-        <div class="col-xs-12 col-sm-4">
-            <input type="number" class="form-control" id="sleep" name="row[sleep]" value="0" data-rule="required" size="6" />
+            <input type="text" class="form-control" id="schedule" style="font-size:12px;font-family: Verdana;word-spacing:23px;" name="row[schedule]" value="* * * * *" data-rule="required; remote(general/crontab/check_schedule)" />
+            <div id="schedulepicker">
+                <pre><code>*    *    *    *    *
+-    -    -    -    -
+|    |    |    |    +--- day of week (0 - 7) (Sunday=0 or 7)
+|    |    |    +-------- month (1 - 12)
+|    |    +------------- day of month (1 - 31)
+|    +------------------ hour (0 - 23)
++----------------------- min (0 - 59)</code></pre>
+                <h5>{:__('The next %s times the execution time', '<input type="number" id="pickdays" class="form-control text-center" value="7" style="display: inline-block;width:80px;">')}</h5>
+                <ol id="scheduleresult" class="list-group">
+                </ol>
+            </div>
         </div>
     </div>
     <div class="form-group">

+ 13 - 8
application/admin/view/general/crontab/edit.html

@@ -25,14 +25,19 @@
     <div class="form-group">
         <label for="schedule" class="control-label col-xs-12 col-sm-2">{:__('Schedule')}:</label>
         <div class="col-xs-12 col-sm-8">
-            <div id="schedulepicker"></div>
-            <input type="text" class="form-control hide" id="schedule" name="row[schedule]" value="{$row.schedule}" data-rule="required" />
-        </div>
-    </div>
-    <div class="form-group">
-        <label for="sleep" class="control-label col-xs-12 col-sm-2">{:__('sleep')}:</label>
-        <div class="col-xs-12 col-sm-4">
-            <input type="number" class="form-control" id="sleep" name="row[sleep]" value="{$row.sleep}" data-rule="required" size="6" />
+            <input type="text" class="form-control" id="schedule" style="font-size:12px;font-family: Verdana;word-spacing:23px;" name="row[schedule]" value="* * * * *" data-rule="required; remote(general/crontab/check_schedule)" />
+            <div id="schedulepicker">
+                <pre><code>*    *    *    *    *
+-    -    -    -    -
+|    |    |    |    +--- day of week (0 - 7) (Sunday=0 or 7)
+|    |    |    +-------- month (1 - 12)
+|    |    +------------- day of month (1 - 31)
+|    +------------------ hour (0 - 23)
++----------------------- min (0 - 59)</code></pre>
+                <h5>{:__('The next %s times the execution time', '<input type="number" id="pickdays" class="form-control text-center" value="7" style="display: inline-block;width:80px;">')}</h5>
+                <ol id="scheduleresult" class="list-group">
+                </ol>
+            </div>
         </div>
     </div>
     <div class="form-group">

+ 1 - 1
application/admin/view/page/add.html

@@ -70,7 +70,7 @@
             {:build_radios('row[status]', ['normal'=>__('Normal'), 'hidden'=>__('Hidden')])}
         </div>
     </div>
-    <div class="form-group hide layer-footer">
+    <div class="form-group layer-footer">
         <label class="control-label col-xs-12 col-sm-2"></label>
         <div class="col-xs-12 col-sm-8">
             <button type="submit" class="btn btn-success btn-embossed">{:__('OK')}</button>

+ 7 - 1
application/admin/view/version/add.html

@@ -1,3 +1,9 @@
+<style>
+    .content {
+        padding-bottom:50px;
+    }
+</style>
+
 <form id="add-form" class="form-horizontal form-ajax" role="form" data-toggle="validator" method="POST" action="">
 
     <div class="form-group">
@@ -48,7 +54,7 @@
             {:build_radios('row[status]', ['normal'=>__('Normal'), 'hidden'=>__('Hidden')])}
         </div>
     </div>
-    <div class="form-group hide layer-footer">
+    <div class="form-group layer-footer">
         <label class="control-label col-xs-12 col-sm-2"></label>
         <div class="col-xs-12 col-sm-8">
             <button type="submit" class="btn btn-success btn-embossed">{:__('OK')}</button>

+ 1 - 1
application/admin/view/wechat/config/edit.html

@@ -28,7 +28,7 @@
                 {foreach $value as $key => $vo}
                 <dd class="form-inline">
                     <input type="text" name="field[{$key}]" class="form-control" id="field-{$key}" value="{$key}" size="10" />
-                    <input type="text" name="value[{$key}]" class="form-control" id="value-{$key}" value="{$vo}" size="40" />
+                    <input type="text" name="value[{$key}]" class="form-control" id="value-{$key}" value="{:is_array($vo)?'':$vo}" size="40" />
                     <span class="btn btn-sm btn-danger btn-remove"><i class="fa fa-times"></i></span>
                     <span class="btn btn-sm btn-primary btn-dragsort"><i class="fa fa-arrows"></i></span>
                 </dd>

+ 10 - 1
application/common/controller/Backend.php

@@ -165,13 +165,22 @@ class Backend extends Controller
             'referer'        => Session::get("referer")
         ];
 
-        Lang::load(APP_PATH . $modulename . '/lang/' . $lang . '/' . str_replace('.', '/', $controllername) . '.php');
+        $this->loadlang($controllername);
 
         $this->assign('site', $site);
         $this->assign('config', $config);
 
         $this->assign('admin', Session::get('admin'));
     }
+    
+    /**
+     * 加载语言文件
+     * @param string $name
+     */
+    protected function loadlang($name)
+    {
+        Lang::load(APP_PATH . $this->request->module() . '/lang/' . Lang::detect() . '/' . str_replace('.', '/', $name) . '.php');
+    }
 
     /**
      * 生成查询所需要的条件,排序方式

+ 10 - 1
application/common/controller/Frontend.php

@@ -62,9 +62,18 @@ class Frontend extends Controller
             'moduleurl'      => url("/{$modulename}", '', false),
             'language'       => $lang
         ];
-        Lang::load(APP_PATH . $modulename . '/lang/' . $lang . '/' . str_replace('.', '/', $controllername) . '.php');
+        $this->loadlang($controllername);
         $this->assign('site', Config::get("site"));
         $this->assign('config', $config);
     }
+    
+    /**
+     * 加载语言文件
+     * @param string $name
+     */
+    protected function loadlang($name)
+    {
+        Lang::load(APP_PATH . $this->request->module() . '/lang/' . Lang::detect() . '/' . str_replace('.', '/', $name) . '.php');
+    }
 
 }

+ 17 - 0
application/common/model/Crontab.php

@@ -17,8 +17,25 @@ class Crontab extends Model
     ];
     // 追加属性
     protected $append = [
+        'type_text'
     ];
 
+    public static function getTypeList()
+    {
+        return [
+            'url'   => __('Request Url'),
+            'sql'   => __('Execute Sql Script'),
+            'shell' => __('Execute Shell'),
+        ];
+    }
+
+    public function getTypeTextAttr($value, $data)
+    {
+        $typelist = self::getTypeList();
+        $value = $value ? $value : $data['type'];
+        return $value && isset($typelist[$value]) ? $typelist[$value] : $value;
+    }
+
     protected function setBegintimeAttr($value)
     {
         return $value && !is_numeric($value) ? strtotime($value) : $value;

+ 1 - 2
application/index/controller/Ajax.php

@@ -24,10 +24,9 @@ class Ajax extends Frontend
     public function lang()
     {
         header('Content-Type: application/javascript');
-        $modulename = $this->request->module();
         $callback = $this->request->get('callback');
         $controllername = input("controllername");
-        Lang::load(APP_PATH . $modulename . '/lang/' . Lang::detect() . '/' . str_replace('.', '/', $controllername) . '.php');
+        $this->loadlang($controllername);
         //强制输出JSON Object
         $result = 'define(' . json_encode(Lang::get(), JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE) . ');';
         return $result;

+ 5 - 2
application/index/controller/Autotask.php

@@ -112,13 +112,16 @@ class Autotask extends Controller
                 }
                 else if ($crontab['type'] == 'sql')
                 {
+                    //这里需要强制重连数据库,使用已有的连接会报2014错误
+                    $connect = Db::connect([], true);
+                    $connect->execute("select 1");
                     // 执行SQL
-                    Db::getPdo()->exec($crontab['content']);
+                    $connect->getPdo()->exec($crontab['content']);
                 }
                 else if ($crontab['type'] == 'shell')
                 {
                     // 执行Shell
-                    exec( $crontab['content'] . ' >> ' . $logDir . date("Y-m-d") . '.log 2>&1 &');
+                    exec($crontab['content'] . ' >> ' . $logDir . date("Y-m-d") . '.log 2>&1 &');
                 }
             }
             catch (Exception $e)

+ 2 - 1
composer.json

@@ -19,7 +19,8 @@
         "topthink/framework": "^5.0",
         "overtrue/wechat": "~3.1",
         "endroid/qrcode": "^1.9",
-        "topthink/think-captcha": "^1.0"
+        "topthink/think-captcha": "^1.0",
+        "mtdowling/cron-expression": "^1.2"
     },
     "config": {
         "preferred-install": "dist"

+ 13 - 0
public/assets/css/backend.css

@@ -100,6 +100,19 @@ body.is-dialog {
 .searchit {
   border-bottom: 1px dashed #3c8dbc;
 }
+/* 固定的底部按钮 */
+.fixed-footer {
+  position: fixed;
+  bottom: 0;
+  background-color: #ecf0f1;
+  width: 100%;
+  margin-bottom: 0;
+  padding: 10px;
+}
+/* 包裹在layer外层 */
+.layer-footer {
+  display: none;
+}
 table.table-template {
   overflow: hidden;
 }

File diff suppressed because it is too large
+ 1 - 1
public/assets/css/backend.min.css


+ 11 - 6
public/assets/js/backend.js

@@ -140,11 +140,12 @@ define(['jquery', 'bootstrap', 'toastr', 'layer', 'lang', 'moment'], function ($
                     zIndex: Backend.api.layer.zIndex,
                     skin: 'layui-layer-noborder',
                     success: function (layero, index) {
+                        var that = this;
                         //$(layero).removeClass("layui-layer-border");
                         Backend.api.layer.setTop(layero);
                         var frame = Backend.api.layer.getChildFrame('html', index);
                         var layerfooter = frame.find(".layer-footer");
-                        Backend.api.layerfooter(layero, index);
+                        Backend.api.layerfooter(layero, index, that);
 
                         //绑定事件
                         if (layerfooter.size() > 0) {
@@ -155,7 +156,7 @@ define(['jquery', 'bootstrap', 'toastr', 'layer', 'lang', 'moment'], function ($
                             var target = layerfooter[0];
                             // 创建观察者对象
                             var observer = new MutationObserver(function (mutations) {
-                                Backend.api.layerfooter(layero, index);
+                                Backend.api.layerfooter(layero, index, that);
                                 mutations.forEach(function (mutation) {
                                 });
                             });
@@ -170,7 +171,7 @@ define(['jquery', 'bootstrap', 'toastr', 'layer', 'lang', 'moment'], function ($
                 }, options ? options : {}));
                 return false;
             },
-            layerfooter: function (layero, index) {
+            layerfooter: function (layero, index, that) {
                 var frame = Backend.api.layer.getChildFrame('html', index);
                 var layerfooter = frame.find(".layer-footer");
                 if (layerfooter.size() > 0) {
@@ -188,11 +189,11 @@ define(['jquery', 'bootstrap', 'toastr', 'layer', 'lang', 'moment'], function ($
 
                 var oldheg = heg + titHeight + btnHeight;
                 var maxheg = 600;
-                if (frame.outerWidth() < 768) {
-                    maxheg = $(window).height() - 28;
+                if (frame.outerWidth() < 768 || that.area[0].indexOf("%") > -1) {
+                    maxheg = $(window).height();
                 }
                 // 如果有.layer-footer或窗口小于600则重新排
-                if (layerfooter.size() > 0 || oldheg < maxheg) {
+                if (layerfooter.size() > 0 || oldheg < maxheg || that.area[0].indexOf("%") > -1) {
                     var footerHeight = layero.find('.layui-layer-footer').outerHeight() || 0;
                     footerHeight = 0;
                     if (oldheg >= maxheg) {
@@ -374,6 +375,10 @@ define(['jquery', 'bootstrap', 'toastr', 'layer', 'lang', 'moment'], function ($
                     Toastr.error(__('Operation failed'));
                 }
             });
+            //修复含有fixed-footer类的body边距
+            if ($(".fixed-footer").size() > 0) {
+                $(document.body).css("padding-bottom", $(".fixed-footer").height());
+            }
         }
     };
     //将Layer暴露到全局中去

+ 17 - 15
public/assets/js/backend/general/crontab.js

@@ -24,12 +24,13 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
                     [
                         {field: 'state', checkbox: true, },
                         {field: 'id', title: 'ID'},
-                        {field: 'type', title: __('Type')},
+                        {field: 'type_text', title: __('Type'), operate:false},
                         {field: 'title', title: __('Title')},
                         {field: 'maximums', title: __('Maximums')},
                         {field: 'executes', title: __('Executes')},
                         {field: 'begintime', title: __('Begin time'), formatter: Table.api.formatter.datetime},
                         {field: 'endtime', title: __('End time'), formatter: Table.api.formatter.datetime},
+                        {field: 'nexttime', title: __('Next execute time'), formatter: Table.api.formatter.datetime, operate:false},
                         {field: 'executetime', title: __('Execute time'), formatter: Table.api.formatter.datetime},
                         {field: 'weigh', title: __('Weigh')},
                         {field: 'status', title: __('Status'), formatter: Table.api.formatter.status},
@@ -49,22 +50,23 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
         },
         api: {
             bindevent: function () {
+                $('#schedule').on('valid.field', function (e, result) {
+                    $("#pickdays").trigger("change");
+                });
                 Form.api.bindevent($("form[role=form]"));
-                //拖拽排序
-                require(['crontab'], function () {
-                    $('#schedulepicker').jqCron({
-                        lang: 'cn',
-                        default_value: '* * * * 0-2',
-                        multiple_dom: true,
-                        multiple_month: true,
-                        multiple_mins: true,
-                        multiple_dow: true,
-                        multiple_time_hours: true,
-                        multiple_time_minutes: true,
-                        no_reset_button: false,
-                        bind_to: $("#schedule")
-                    });
+                $(document).on("change", "#pickdays", function () {
+                    $("#scheduleresult").html(__('Loading'));
+                    $.post("general/crontab/get_schedule_future", {schedule: $("#schedule").val(), days:$(this).val()}, function (ret) {
+                        $("#scheduleresult").html("");
+                        if (typeof ret.futuretime !== 'undefined' && $.isArray(ret.futuretime)) {
+                            $.each(ret.futuretime, function (i, j) {
+                                $("#scheduleresult").append("<li class='list-group-item'>" + j + "<span class='badge'>" + (i + 1) + "</span></li>");
+                            });
+                        }
+                    }, 'json');
+
                 });
+                $("#pickdays").trigger("change");
             }
         }
     };

+ 12 - 7
public/assets/js/require-backend.min.js

@@ -6551,11 +6551,12 @@ define('backend',['jquery', 'bootstrap', 'toastr', 'layer', 'lang', 'moment'], f
                     zIndex: Backend.api.layer.zIndex,
                     skin: 'layui-layer-noborder',
                     success: function (layero, index) {
+                        var that = this;
                         //$(layero).removeClass("layui-layer-border");
                         Backend.api.layer.setTop(layero);
                         var frame = Backend.api.layer.getChildFrame('html', index);
                         var layerfooter = frame.find(".layer-footer");
-                        Backend.api.layerfooter(layero, index);
+                        Backend.api.layerfooter(layero, index, that);
 
                         //绑定事件
                         if (layerfooter.size() > 0) {
@@ -6566,7 +6567,7 @@ define('backend',['jquery', 'bootstrap', 'toastr', 'layer', 'lang', 'moment'], f
                             var target = layerfooter[0];
                             // 创建观察者对象
                             var observer = new MutationObserver(function (mutations) {
-                                Backend.api.layerfooter(layero, index);
+                                Backend.api.layerfooter(layero, index, that);
                                 mutations.forEach(function (mutation) {
                                 });
                             });
@@ -6581,7 +6582,7 @@ define('backend',['jquery', 'bootstrap', 'toastr', 'layer', 'lang', 'moment'], f
                 }, options ? options : {}));
                 return false;
             },
-            layerfooter: function (layero, index) {
+            layerfooter: function (layero, index, that) {
                 var frame = Backend.api.layer.getChildFrame('html', index);
                 var layerfooter = frame.find(".layer-footer");
                 if (layerfooter.size() > 0) {
@@ -6599,11 +6600,11 @@ define('backend',['jquery', 'bootstrap', 'toastr', 'layer', 'lang', 'moment'], f
 
                 var oldheg = heg + titHeight + btnHeight;
                 var maxheg = 600;
-                if (frame.outerWidth() < 768) {
-                    maxheg = $(window).height() - 28;
+                if (frame.outerWidth() < 768 || that.area[0].indexOf("%") > -1) {
+                    maxheg = $(window).height();
                 }
                 // 如果有.layer-footer或窗口小于600则重新排
-                if (layerfooter.size() > 0 || oldheg < maxheg) {
+                if (layerfooter.size() > 0 || oldheg < maxheg || that.area[0].indexOf("%") > -1) {
                     var footerHeight = layero.find('.layui-layer-footer').outerHeight() || 0;
                     footerHeight = 0;
                     if (oldheg >= maxheg) {
@@ -6785,6 +6786,10 @@ define('backend',['jquery', 'bootstrap', 'toastr', 'layer', 'lang', 'moment'], f
                     Toastr.error(__('Operation failed'));
                 }
             });
+            //修复含有fixed-footer类的body边距
+            if ($(".fixed-footer").size() > 0) {
+                $(document.body).css("padding-bottom", $(".fixed-footer").height());
+            }
         }
     };
     //将Layer暴露到全局中去
@@ -8544,7 +8549,7 @@ define('upload',['jquery', 'bootstrap', 'backend', 'plupload', 'dragsort', 'temp
                             UploadProgress: function (up, file) {
                                 //这里可以改成其它的表现形式
                                 //document.getElementById(file.id).getElementsByTagName('b')[0].innerHTML = '<span>' + file.percent + "%</span>";
-                                $(that).prop("disabled", true).html("<i class='fa fa-upload'></i> 上传" + file.percent + "%");
+                                $(that).prop("disabled", true).html("<i class='fa fa-upload'></i> " + __('Upload') + file.percent + "%");
                             },
                             FileUploaded: function (up, file, info) {
                                 var options = this.getOption();

+ 1 - 1
public/assets/js/require-upload.js

@@ -66,7 +66,7 @@ define(['jquery', 'bootstrap', 'backend', 'plupload', 'dragsort', 'template'], f
                             UploadProgress: function (up, file) {
                                 //这里可以改成其它的表现形式
                                 //document.getElementById(file.id).getElementsByTagName('b')[0].innerHTML = '<span>' + file.percent + "%</span>";
-                                $(that).prop("disabled", true).html("<i class='fa fa-upload'></i> 上传" + file.percent + "%");
+                                $(that).prop("disabled", true).html("<i class='fa fa-upload'></i> " + __('Upload') + file.percent + "%");
                             },
                             FileUploaded: function (up, file, info) {
                                 var options = this.getOption();

+ 13 - 0
public/assets/less/backend.less

@@ -123,6 +123,19 @@ body.is-dialog {
 .searchit{
     border-bottom:1px dashed @link-color;
 }
+/* 固定的底部按钮 */
+.fixed-footer {
+    position: fixed;
+    bottom:0;    
+    background-color: #ecf0f1;
+    width:100%;
+    margin-bottom:0;
+    padding:10px;
+}
+/* 包裹在layer外层 */
+.layer-footer {
+    display:none;
+}
 
 table.table-template{
     overflow:hidden;

+ 5 - 1
public/install.php

@@ -43,6 +43,10 @@ if (is_file($lockFile))
 {
     $errInfo = "当前已经安装{$sitename},如果需要重新安装,请手动移除application/admin/command/Install/install.lock文件";
 }
+else if (!is_writeable($lockFile))
+{
+    $errInfo = "当前权限不足,无法写入锁定文件application/admin/command/Install/install.lock";
+}
 else if (version_compare(PHP_VERSION, '5.5.0', '<'))
 {
     $errInfo = "当前版本(" . PHP_VERSION . ")过低,请使用PHP5.5以上版本";
@@ -62,7 +66,7 @@ else
     {
         if (!is_dir(ROOT_PATH . $v))
         {
-            $errInfo = '请先下载扩展资源包覆盖后再安装,<a href="' . $link['qqun'] . '" target="_blank">群共享下载</a> <a href="' . $link['osc'] . '" target="_blank">码云下载</a>';
+            $errInfo = '请先下载完整包覆盖后再安装,<a href="' . $link['qqun'] . '" target="_blank">群共享下载</a> <a href="' . $link['osc'] . '" target="_blank">码云下载</a>';
             break;
         }
     }