Browse Source

新增附件记录上传会员和上传管理员ID
新增后台管理员admin_login_after和admin_logout_after的行为
新增tooltip和popover
优化和改版插件管理功能
优化多个Layer弹窗的切换体验
优化弹窗高度大于视察高度的操作体验
优化系统配置中发送测试邮件的逻辑
修复生成关联模型时未判断selectpage的BUG
修复表单前置返回失败未释放锁定的BUG
修复API文档生成时采用了GET方法的BUG

Karson 7 years ago
parent
commit
e57171dea9
56 changed files with 903 additions and 816 deletions
  1. 5 2
      application/admin/command/Addon.php
  2. 2 2
      application/admin/command/Api/template/index.html
  3. 2 2
      application/admin/command/Crud.php
  4. 1 0
      application/admin/command/Crud/stubs/controller.stub
  5. 5 0
      application/admin/command/Crud/stubs/controllerindex.stub
  6. 6 4
      application/admin/command/Install/fastadmin.sql
  7. 15 13
      application/admin/controller/Addon.php
  8. 2 0
      application/admin/controller/Ajax.php
  9. 3 1
      application/admin/controller/Index.php
  10. 45 81
      application/admin/controller/general/Config.php
  11. 1 2
      application/admin/controller/user/User.php
  12. 2 6
      application/admin/lang/zh-cn.php
  13. 18 8
      application/admin/lang/zh-cn/addon.php
  14. 4 3
      application/admin/lang/zh-cn/auth/rule.php
  15. 2 1
      application/admin/lang/zh-cn/general/attachment.php
  16. 3 2
      application/admin/lang/zh-cn/general/config.php
  17. 0 22
      application/admin/view/addon/add.html
  18. 148 194
      application/admin/view/addon/index.html
  19. 2 2
      application/admin/view/auth/admin/add.html
  20. 2 2
      application/admin/view/auth/admin/edit.html
  21. 4 4
      application/admin/view/auth/group/add.html
  22. 4 4
      application/admin/view/auth/group/edit.html
  23. 5 8
      application/admin/view/auth/rule/add.html
  24. 4 4
      application/admin/view/auth/rule/edit.html
  25. 5 4
      application/admin/view/auth/rule/tpl.html
  26. 1 1
      application/admin/view/category/add.html
  27. 1 1
      application/admin/view/category/edit.html
  28. 1 0
      application/admin/view/common/header.html
  29. 11 17
      application/api/controller/Common.php
  30. 69 102
      application/common.php
  31. 1 1
      application/config.php
  32. 6 6
      application/index/controller/User.php
  33. 1 0
      application/index/view/user/login.html
  34. 1 0
      application/index/view/user/register.html
  35. 1 1
      composer.json
  36. 4 4
      public/api.html
  37. 3 0
      public/assets/css/backend.css
  38. 1 1
      public/assets/css/backend.min.css
  39. 50 47
      public/assets/js/backend.js
  40. 131 67
      public/assets/js/backend/addon.js
  41. 10 1
      public/assets/js/backend/auth/admin.js
  42. 1 1
      public/assets/js/backend/auth/adminlog.js
  43. 9 0
      public/assets/js/backend/auth/group.js
  44. 33 18
      public/assets/js/backend/auth/rule.js
  45. 33 16
      public/assets/js/backend/general/attachment.js
  46. 15 2
      public/assets/js/backend/general/config.js
  47. 2 2
      public/assets/js/backend/general/profile.js
  48. 2 2
      public/assets/js/backend/user/group.js
  49. 2 2
      public/assets/js/backend/user/rule.js
  50. 36 25
      public/assets/js/fast.js
  51. 3 1
      public/assets/js/frontend.js
  52. 117 87
      public/assets/js/require-backend.min.js
  53. 14 8
      public/assets/js/require-form.js
  54. 40 27
      public/assets/js/require-frontend.min.js
  55. 11 5
      public/assets/js/require-table.js
  56. 3 0
      public/assets/less/backend.less

+ 5 - 2
application/admin/command/Addon.php

@@ -33,6 +33,9 @@ class Addon extends Command
     {
         $name = $input->getOption('name') ?: '';
         $action = $input->getOption('action') ?: '';
+        if(stripos($name, 'addons/')!==false){
+            $name = explode('/', $name)[1];
+        }
         //强制覆盖
         $force = $input->getOption('force');
         //版本
@@ -65,8 +68,8 @@ class Addon extends Command
                 if (is_dir($addonDir)) {
                     rmdirs($addonDir);
                 }
-                mkdir($addonDir);
-                mkdir($addonDir . DS . 'controller');
+                mkdir($addonDir, 0755, true);
+                mkdir($addonDir . DS . 'controller', 0755, true);
                 $menuList = \app\common\library\Menu::export($name);
                 $createMenu = $this->getCreateMenu($menuList);
                 $prefix = Config::get('database.prefix');

+ 2 - 2
application/admin/command/Api/template/index.html

@@ -437,8 +437,8 @@
 
                     $.ajax({
                         url: $('#apiUrl').val() + url,
-                        data: $(form).attr('method') == 'get' ? $(form).serialize() : formData,
-                        type: $(form).attr('method') + '',
+                        data: $(form).prop('method').toLowerCase() == 'get' ? $(form).serialize() : formData,
+                        type: $(form).prop('method') + '',
                         dataType: 'json',
                         contentType: false,
                         processData: false,

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

@@ -1137,7 +1137,7 @@ EOD;
         $langField = mb_ucfirst($field);
         return <<<EOD
     <div class="form-group">
-        <label for="c-{$field}" class="control-label col-xs-12 col-sm-2">{:__('{$langField}')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('{$langField}')}:</label>
         <div class="col-xs-12 col-sm-8">
             {$content}
         </div>
@@ -1149,7 +1149,7 @@ EOD;
      * 获取图片模板数据
      * @param string $field
      * @param string $content
-     * @return array
+     * @return string
      */
     protected function getImageUpload($field, $content)
     {

+ 1 - 0
application/admin/command/Crud/stubs/controller.stub

@@ -14,6 +14,7 @@ class {%controllerName%} extends Backend
     
     /**
      * {%modelName%}模型对象
+     * @var \{%modelNamespace%}\{%modelName%}
      */
     protected $model = null;
 

+ 5 - 0
application/admin/command/Crud/stubs/controllerindex.stub

@@ -10,6 +10,11 @@
         $this->request->filter(['strip_tags']);
         if ($this->request->isAjax())
         {
+            //如果发送的来源是Selectpage,则转发到Selectpage
+            if ($this->request->request('keyField'))
+            {
+                return $this->selectpage();
+            }
             list($where, $sort, $order, $offset, $limit) = $this->buildparams();
             $total = $this->model
                     {%relationWithList%}

+ 6 - 4
application/admin/command/Install/fastadmin.sql

@@ -50,7 +50,7 @@ CREATE TABLE `fa_admin_log` (
   `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
   `admin_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '管理员ID',
   `username` varchar(30) NOT NULL DEFAULT '' COMMENT '管理员名字',
-  `url` varchar(255) NOT NULL DEFAULT '' COMMENT '操作页面',
+  `url` varchar(1500) NOT NULL DEFAULT '' COMMENT '操作页面',
   `title` varchar(100) NOT NULL DEFAULT '' COMMENT '日志标题',
   `content` text NOT NULL COMMENT '内容',
   `ip` varchar(50) NOT NULL DEFAULT '' COMMENT 'IP',
@@ -66,6 +66,8 @@ CREATE TABLE `fa_admin_log` (
 DROP TABLE IF EXISTS `fa_attachment`;
 CREATE TABLE `fa_attachment` (
   `id` int(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
+  `admin_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '管理员ID',
+  `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '会员ID',
   `url` varchar(255) NOT NULL DEFAULT '' COMMENT '物理路径',
   `imagewidth` varchar(30) NOT NULL DEFAULT '' COMMENT '宽度',
   `imageheight` varchar(30) NOT NULL DEFAULT '' COMMENT '高度',
@@ -77,7 +79,7 @@ CREATE TABLE `fa_attachment` (
   `createtime` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '创建日期',
   `updatetime` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',
   `uploadtime` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '上传时间',
-  `storage` enum('local','upyun','qiniu') NOT NULL DEFAULT 'local' COMMENT '存储位置',
+  `storage` varchar(100) NOT NULL DEFAULT 'local' COMMENT '存储位置',
   `sha1` varchar(40) NOT NULL DEFAULT '' COMMENT '文件 sha1编码',
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='附件表';
@@ -86,7 +88,7 @@ CREATE TABLE `fa_attachment` (
 -- Records of fa_attachment
 -- ----------------------------
 BEGIN;
-INSERT INTO `fa_attachment` VALUES (1, '/assets/img/qrcode.png', '150', '150', 'png', 0, 21859, 'image/png', '', 1499681848, 1499681848, 1499681848, 'local', '17163603d0263e4838b9387ff2cd4877e8b018f6');
+INSERT INTO `fa_attachment` VALUES (1, 1, 0, '/assets/img/qrcode.png', '150', '150', 'png', 0, 21859, 'image/png', '', 1499681848, 1499681848, 1499681848, 'local', '17163603d0263e4838b9387ff2cd4877e8b018f6');
 COMMIT;
 
 -- ----------------------------
@@ -344,7 +346,7 @@ DROP TABLE IF EXISTS `fa_ems`;
 CREATE TABLE `fa_ems`  (
   `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID',
   `event` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '事件',
-  `email` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '邮箱',
+  `email` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '邮箱',
   `code` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '验证码',
   `times` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '验证次数',
   `ip` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT 'IP',

+ 15 - 13
application/admin/controller/Addon.php

@@ -76,7 +76,7 @@ class Addon extends Backend
                     Service::refresh();
                     $this->success();
                 } catch (Exception $e) {
-                    $this->error($e->getMessage());
+                    $this->error(__($e->getMessage()));
                 }
             }
             $this->error(__('Parameter %s can not be empty', ''));
@@ -112,9 +112,9 @@ class Addon extends Backend
             $info['state'] = 1;
             $this->success(__('Install successful'), null, ['addon' => $info]);
         } catch (AddonException $e) {
-            $this->result($e->getData(), $e->getCode(), $e->getMessage());
+            $this->result($e->getData(), $e->getCode(), __($e->getMessage()));
         } catch (Exception $e) {
-            $this->error($e->getMessage(), $e->getCode());
+            $this->error(__($e->getMessage()), $e->getCode());
         }
     }
 
@@ -132,9 +132,9 @@ class Addon extends Backend
             Service::uninstall($name, $force);
             $this->success(__('Uninstall successful'));
         } catch (AddonException $e) {
-            $this->result($e->getData(), $e->getCode(), $e->getMessage());
+            $this->result($e->getData(), $e->getCode(), __($e->getMessage()));
         } catch (Exception $e) {
-            $this->error($e->getMessage());
+            $this->error(__($e->getMessage()));
         }
     }
 
@@ -156,9 +156,9 @@ class Addon extends Backend
             Cache::rm('__menu__');
             $this->success(__('Operate successful'));
         } catch (AddonException $e) {
-            $this->result($e->getData(), $e->getCode(), $e->getMessage());
+            $this->result($e->getData(), $e->getCode(), __($e->getMessage()));
         } catch (Exception $e) {
-            $this->error($e->getMessage());
+            $this->error(__($e->getMessage()));
         }
     }
 
@@ -222,16 +222,16 @@ class Addon extends Backend
                     $this->success(__('Offline installed tips'), null, ['addon' => $info]);
                 } catch (Exception $e) {
                     @rmdirs($newAddonDir);
-                    throw new Exception($e->getMessage());
+                    throw new Exception(__($e->getMessage()));
                 }
             } catch (Exception $e) {
                 @unlink($tmpFile);
                 @rmdirs($tmpAddonDir);
-                $this->error($e->getMessage());
+                $this->error(__($e->getMessage()));
             }
         } else {
             // 上传失败获取错误信息
-            $this->error($file->getError());
+            $this->error(__($file->getError()));
         }
     }
 
@@ -260,9 +260,9 @@ class Addon extends Backend
             Cache::rm('__menu__');
             $this->success(__('Operate successful'));
         } catch (AddonException $e) {
-            $this->result($e->getData(), $e->getCode(), $e->getMessage());
+            $this->result($e->getData(), $e->getCode(), __($e->getMessage()));
         } catch (Exception $e) {
-            $this->error($e->getMessage());
+            $this->error(__($e->getMessage()));
         }
     }
 
@@ -297,7 +297,7 @@ class Addon extends Backend
                 continue;
 
             if (isset($onlineaddons[$v['name']])) {
-                $v = array_merge($onlineaddons[$v['name']], $v);
+                $v = array_merge($v, $onlineaddons[$v['name']]);
             } else {
                 $v['category_id'] = 0;
                 $v['flag'] = '';
@@ -306,6 +306,8 @@ class Addon extends Backend
                 $v['donateimage'] = '';
                 $v['demourl'] = '';
                 $v['price'] = '0.00';
+                $v['screenshots'] = [];
+                $v['releaselist'] = [];
             }
             $v['url'] = addon_url($v['name']);
             $v['createtime'] = filemtime(ADDON_PATH . $v['name']);

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

@@ -106,6 +106,8 @@ class Ajax extends Backend
                 $imageheight = isset($imgInfo[1]) ? $imgInfo[1] : $imageheight;
             }
             $params = array(
+                'admin_id'    => (int)$this->auth->id,
+                'user_id'     => 0,
                 'filesize'    => $fileInfo['size'],
                 'imagewidth'  => $imagewidth,
                 'imageheight' => $imageheight,

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

@@ -90,6 +90,7 @@ class Index extends Backend
             $result = $this->auth->login($username, $password, $keeplogin ? 86400 : 0);
             if ($result === true)
             {
+                Hook::listen("admin_login_after", $this->request);
                 $this->success(__('Login successful'), $url, ['url' => $url, 'id' => $this->auth->id, 'username' => $username, 'avatar' => $this->auth->avatar]);
             }
             else
@@ -109,7 +110,7 @@ class Index extends Backend
         $background = stripos($background, 'http')===0 ? $background : config('site.cdnurl') . $background;
         $this->view->assign('background', $background);
         $this->view->assign('title', __('Login'));
-        Hook::listen("login_init", $this->request);
+        Hook::listen("admin_login_init", $this->request);
         return $this->view->fetch();
     }
 
@@ -119,6 +120,7 @@ class Index extends Backend
     public function logout()
     {
         $this->auth->logout();
+        Hook::listen("admin_logout_after", $this->request);
         $this->success(__('Logout successful'), 'index/login');
     }
 

+ 45 - 81
application/admin/controller/general/Config.php

@@ -10,7 +10,7 @@ use think\Exception;
 /**
  * 系统配置
  *
- * @icon fa fa-circle-o
+ * @icon fa fa-cogs
  * @remark 可以在此增改系统的变量和分组,也可以自定义分组和变量,如果需要删除请从数据库中删除
  */
 class Config extends Backend
@@ -32,31 +32,26 @@ class Config extends Backend
     {
         $siteList = [];
         $groupList = ConfigModel::getGroupList();
-        foreach ($groupList as $k => $v)
-        {
+        foreach ($groupList as $k => $v) {
             $siteList[$k]['name'] = $k;
             $siteList[$k]['title'] = $v;
             $siteList[$k]['list'] = [];
         }
 
-        foreach ($this->model->all() as $k => $v)
-        {
-            if (!isset($siteList[$v['group']]))
-            {
+        foreach ($this->model->all() as $k => $v) {
+            if (!isset($siteList[$v['group']])) {
                 continue;
             }
             $value = $v->toArray();
             $value['title'] = __($value['title']);
-            if (in_array($value['type'], ['select', 'selects', 'checkbox', 'radio']))
-            {
+            if (in_array($value['type'], ['select', 'selects', 'checkbox', 'radio'])) {
                 $value['value'] = explode(',', $value['value']);
             }
             $value['content'] = json_decode($value['content'], TRUE);
             $siteList[$v['group']]['list'][] = $value;
         }
         $index = 0;
-        foreach ($siteList as $k => &$v)
-        {
+        foreach ($siteList as $k => &$v) {
             $v['active'] = !$index ? true : false;
             $index++;
         }
@@ -71,45 +66,30 @@ class Config extends Backend
      */
     public function add()
     {
-        if ($this->request->isPost())
-        {
+        if ($this->request->isPost()) {
             $params = $this->request->post("row/a");
-            if ($params)
-            {
-                foreach ($params as $k => &$v)
-                {
+            if ($params) {
+                foreach ($params as $k => &$v) {
                     $v = is_array($v) ? implode(',', $v) : $v;
                 }
-                try
-                {
-                    if (in_array($params['type'], ['select', 'selects', 'checkbox', 'radio', 'array']))
-                    {
+                try {
+                    if (in_array($params['type'], ['select', 'selects', 'checkbox', 'radio', 'array'])) {
                         $params['content'] = json_encode(ConfigModel::decode($params['content']), JSON_UNESCAPED_UNICODE);
-                    }
-                    else
-                    {
+                    } else {
                         $params['content'] = '';
                     }
                     $result = $this->model->create($params);
-                    if ($result !== false)
-                    {
-                        try
-                        {
+                    if ($result !== false) {
+                        try {
                             $this->refreshFile();
                             $this->success();
-                        }
-                        catch (Exception $e)
-                        {
+                        } catch (Exception $e) {
                             $this->error($e->getMessage());
                         }
-                    }
-                    else
-                    {
+                    } else {
                         $this->error($this->model->getError());
                     }
-                }
-                catch (Exception $e)
-                {
+                } catch (Exception $e) {
                     $this->error($e->getMessage());
                 }
             }
@@ -124,23 +104,16 @@ class Config extends Backend
      */
     public function edit($ids = NULL)
     {
-        if ($this->request->isPost())
-        {
+        if ($this->request->isPost()) {
             $row = $this->request->post("row/a");
-            if ($row)
-            {
+            if ($row) {
                 $configList = [];
-                foreach ($this->model->all() as $v)
-                {
-                    if (isset($row[$v['name']]))
-                    {
+                foreach ($this->model->all() as $v) {
+                    if (isset($row[$v['name']])) {
                         $value = $row[$v['name']];
-                        if (is_array($value) && isset($value['field']))
-                        {
+                        if (is_array($value) && isset($value['field'])) {
                             $value = json_encode(ConfigModel::getArrayData($value), JSON_UNESCAPED_UNICODE);
-                        }
-                        else
-                        {
+                        } else {
                             $value = is_array($value) ? implode(',', $value) : $value;
                         }
                         $v['value'] = $value;
@@ -148,13 +121,10 @@ class Config extends Backend
                     }
                 }
                 $this->model->allowField(true)->saveAll($configList);
-                try
-                {
+                try {
                     $this->refreshFile();
                     $this->success();
-                }
-                catch (Exception $e)
-                {
+                } catch (Exception $e) {
                     $this->error($e->getMessage());
                 }
             }
@@ -162,20 +132,20 @@ class Config extends Backend
         }
     }
 
+    /**
+     * 刷新配置文件
+     */
     protected function refreshFile()
     {
         $config = [];
-        foreach ($this->model->all() as $k => $v)
-        {
+        foreach ($this->model->all() as $k => $v) {
 
             $value = $v->toArray();
-            if (in_array($value['type'], ['selects', 'checkbox', 'images', 'files']))
-            {
+            if (in_array($value['type'], ['selects', 'checkbox', 'images', 'files'])) {
                 $value['value'] = explode(',', $value['value']);
             }
-            if ($value['type'] == 'array')
-            {
-                $value['value'] = (array) json_decode($value['value'], TRUE);
+            if ($value['type'] == 'array') {
+                $value['value'] = (array)json_decode($value['value'], TRUE);
             }
             $config[$value['name']] = $value['value'];
         }
@@ -183,26 +153,21 @@ class Config extends Backend
     }
 
     /**
+     * 检测配置项是否存在
      * @internal
      */
     public function check()
     {
         $params = $this->request->post("row/a");
-        if ($params)
-        {
+        if ($params) {
 
             $config = $this->model->get($params);
-            if (!$config)
-            {
+            if (!$config) {
                 return $this->success();
-            }
-            else
-            {
+            } else {
                 return $this->error(__('Name already exist'));
             }
-        }
-        else
-        {
+        } else {
             return $this->error(__('Invalid parameters'));
         }
     }
@@ -213,19 +178,18 @@ class Config extends Backend
      */
     public function emailtest()
     {
+        $row = $this->request->post('row/a');
+        \think\Config::set('site', array_merge(\think\Config::get('site'), $row));
         $receiver = $this->request->request("receiver");
         $email = new Email;
         $result = $email
-                ->to($receiver)
-                ->subject(__("This is a test mail"))
-                ->message('<div style="min-height:550px; padding: 100px 55px 200px;">' . __('This is a test mail content') . '</div>')
-                ->send();
-        if ($result)
-        {
+            ->to($receiver)
+            ->subject(__("This is a test mail"))
+            ->message('<div style="min-height:550px; padding: 100px 55px 200px;">' . __('This is a test mail content') . '</div>')
+            ->send();
+        if ($result) {
             $this->success();
-        }
-        else
-        {
+        } else {
             $this->error($email->getError());
         }
     }

+ 1 - 2
application/admin/controller/user/User.php

@@ -53,8 +53,7 @@ class User extends Backend
                     ->select();
             foreach ($list as $k => $v)
             {
-                $v->password = '';
-                $v->salt = '';
+                $v->hidden(['password', 'salt']);
             }
             $result = array("total" => $total, "rows" => $list);
 

+ 2 - 6
application/admin/lang/zh-cn.php

@@ -34,7 +34,6 @@ return [
     'Cancel'                                                => '取消',
     'Clear'                                                 => '清空',
     'Custom Range'                                          => '自定义',
-    'Cancel'                                                => '取消',
     'Today'                                                 => '今天',
     'Yesterday'                                             => '昨天',
     'Last 7 days'                                           => '最近7天',
@@ -78,13 +77,9 @@ return [
     'Line'                                                  => '行号',
     'File'                                                  => '文件',
     'Menu'                                                  => '菜单',
-    'Name'                                                  => '名称',
-    'Weigh'                                                 => '权重',
     'Type'                                                  => '类型',
     'Title'                                                 => '标题',
     'Content'                                               => '内容',
-    'Status'                                                => '状态',
-    'Operate'                                               => '操作',
     'Append'                                                => '追加',
     'Select'                                                => '选择',
     'Memo'                                                  => '备注',
@@ -115,6 +110,7 @@ return [
     //提示
     'Go back'                                               => '返回首页',
     'Jump now'                                              => '立即跳转',
+    'Click to search %s'                                    => '点击搜索 %s',
     'Operation completed'                                   => '操作成功!',
     'Operation failed'                                      => '操作失败!',
     'Unknown data format'                                   => '未知的数据格式!',
@@ -147,7 +143,7 @@ return [
     'Admin'                                                 => '管理员管理',
     'Admin log'                                             => '管理员日志',
     'Group'                                                 => '角色组',
-    'Rule'                                                  => '规则管理',
+    'Rule'                                                  => '菜单规则',
     'User'                                                  => '会员管理',
     'User group'                                            => '会员分组',
     'User rule'                                             => '会员规则',

+ 18 - 8
application/admin/lang/zh-cn/addon.php

@@ -2,13 +2,14 @@
 
 return [
     'Id'                             => 'ID',
-    'Title'                          => '标题',
+    'Title'                          => '插件名称',
     'Value'                          => '配置值',
     'Array key'                      => '键',
     'Array value'                    => '值',
     'File'                           => '文件',
     'Donate'                         => '打赏作者',
     'Warmtips'                       => '温馨提示',
+    'Pay now'                        => '立即支付',
     'Offline install'                => '离线安装',
     'Refresh addon cache'            => '刷新插件缓存',
     'Userinfo'                       => '会员信息',
@@ -17,38 +18,47 @@ return [
     'Conflict tips'                  => '此插件中发现和现有系统中部分文件发现冲突!以下文件将会被影响,请备份好相关文件后再继续操作',
     'Login tips'                     => '此处登录账号为<a href="https://www.fastadmin.net" target="_blank">FastAdmin官网账号</a>',
     'Logined tips'                   => '你好!%s<br />当前你已经登录,将同步保存你的购买记录',
-    'Pay tips'                       => '支付完成后请稍等1~5分钟后再尝试安装,请不要重复支付,如果仍然无法安装,请加<a href="https://jq.qq.com/?_wv=1027&k=487PNBb" target="_blank">QQ群:636393962</a>向管理员反馈',
+    'Pay tips'                       => '扫码支付后如果仍然无法立即下载,请不要重复支付,请加<a href="https://jq.qq.com/?_wv=1027&k=487PNBb" target="_blank">QQ群:636393962</a>向管理员反馈',
     'Pay click tips'                 => '请点击这里在新窗口中进行支付!',
     'Pay new window tips'            => '请在新弹出的窗口中进行支付,支付完成后再重新点击安装按钮进行安装!',
     'Uninstall tips'                 => '确认卸载插件?<p class="text-danger">卸载将会删除所有插件文件且不可找回!!! 插件如果有创建数据库表请手动删除!!!</p>如有重要数据请备份后再操作!',
     'Upgrade tips'                   => '确认升级插件?<p class="text-danger">如果之前购买插件时未登录,此次升级可能出现购买后才可以下载的提示!!!<br>升级后可能出现部分冗余数据记录,请根据需要移除即可!!!</p>如有重要数据请备份后再操作!',
-    'Offline installed tips'         => '插件安装成功!清除插件缓存和框架缓存后生效!',
-    'Online installed tips'          => '插件安装成功!清除插件缓存和框架缓存后生效!',
+    'Offline installed tips'         => '插件安装成功!清除浏览器缓存和框架缓存后生效!',
+    'Online installed tips'          => '插件安装成功!清除浏览器缓存和框架缓存后生效!',
     'Not login tips'                 => '你当前未登录FastAdmin,登录后将同步已购买的记录,下载时无需二次付费!',
     'Not installed tips'             => '请安装后再访问插件前台页面!',
     'Not enabled tips'               => '插件已经禁用,请启用后再访问插件前台页面!',
     'Please disable addon first'     => '请先禁用插件再进行升级',
     'Login now'                      => '立即登录',
     'Continue install'               => '不登录,继续安装',
+    'View addon home page'           => '查看插件介绍和帮助',
+    'View addon index page'          => '查看插件前台首页',
+    'View addon screenshots'         => '点击查看插件截图',
+    'Click to toggle status'         => '点击切换插件状态',
+    'Click to contact developer'     => '点击与插件开发者取得联系',
+    'Index'                          => '前台',
     'All'                            => '全部',
     'Uncategoried'                   => '未归类',
     'Recommend'                      => '推荐',
     'Hot'                            => '热门',
     'New'                            => '新',
+    'Paying'                         => '付费',
     'Free'                           => '免费',
     'Sale'                           => '折扣',
     'No image'                       => '暂无缩略图',
     'Price'                          => '价格',
+    'Downloads'                      => '下载',
     'Author'                         => '作者',
     'Identify'                       => '标识',
     'Homepage'                       => '主页',
     'Intro'                          => '介绍',
     'Version'                        => '版本',
+    'New version'                    => '新版本',
     'Createtime'                     => '添加时间',
     'Releasetime'                    => '更新时间',
     'Detail'                         => '插件详情',
     'Document'                       => '文档',
-    'Demo'                           => '在线演示',
+    'Demo'                           => '演示',
     'Feedback'                       => '反馈BUG',
     'Install'                        => '安装',
     'Uninstall'                      => '卸载',
@@ -63,9 +73,7 @@ return [
     'Logout'                         => '退出登录',
     'Register'                       => '注册账号',
     'You\'re not login'              => '当前未登录',
-    'Pay now'                        => '立即支付',
-    'Continue install'               => '继续安装',
-    'Continue uninstall'             => '继续安装',
+    'Continue uninstall'             => '继续卸载',
     'Continue operate'               => '继续操作',
     'Install successful'             => '安装成功',
     'Uninstall successful'           => '卸载成功',
@@ -73,4 +81,6 @@ return [
     'Addon info file was not found'  => '插件配置文件未找到',
     'Addon info file data incorrect' => '插件配置信息不正确',
     'Addon already exists'           => '上传的插件已经存在',
+    'Unable to open the zip file'    => '无法打开ZIP文件',
+    'Unable to extract the file'     => '无法解压ZIP文件',
 ];

+ 4 - 3
application/admin/lang/zh-cn/auth/rule.php

@@ -10,9 +10,10 @@ return [
     'Controller/Action'                                         => '控制器名/方法名',
     'Ismenu'                                                    => '菜单',
     'Search icon'                                               => '搜索图标',
-    'Menu tips'                                                 => '规则任意,不可重复,仅做层级显示,无需匹配控制器和方法',
-    'Node tips'                                                 => '控制器/方法名',
+    'Toggle menu visible'                                       => '点击切换菜单显示',
+    'Toggle sub menu'                                           => '点击切换子菜单',
+    'Menu tips'                                                 => '父级菜单无需匹配控制器和方法,子级菜单请使用控制器名',
+    'Node tips'                                                 => '控制器/方法名,如果有目录请使用 目录名/控制器名/方法名',
     'The non-menu rule must have parent'                        => '非菜单规则节点必须有父级',
-    'If not necessary, use the command line to build rule'      => '非必要情况下请直接使用命令行<a href="https://doc.fastadmin.net/docs/command.html#一键生成菜单" target="_blank">php think menu</a>来生成',
     'Name only supports letters, numbers, underscore and slash' => 'URL规则只能是小写字母、数字、下划线和/组成',
 ];

+ 2 - 1
application/admin/lang/zh-cn/general/attachment.php

@@ -1,9 +1,10 @@
 <?php
 
 return [
+    'Id'                 => 'ID',
     'Url'                => '物理路径',
     'Imagewidth'         => '宽度',
-    'Imageheight'        => '度',
+    'Imageheight'        => '度',
     'Imagetype'          => '图片类型',
     'Imageframes'        => '图片帧数',
     'Preview'            => '预览',

+ 3 - 2
application/admin/lang/zh-cn/general/config.php

@@ -53,6 +53,7 @@ return [
     'Mail from'                   => '发件人邮箱',
     'Name already exist'          => '变量名称已经存在',
     'Send a test message'         => '发送测试邮件',
-    'This is a test mail content' => '这是一封测试邮件,用于测试邮件配置是否正常!',
-    'This is a test mail'         => '这是一封测试邮件',
+    'This is a test mail content' => '这是一封来自FastAdmin校验邮件,用于校验邮件配置是否正常!',
+    'This is a test mail'         => '这是一封来自FastAdmin的邮件',
+    'Please input your email'     => '请输入测试接收者邮箱',
 ];

+ 0 - 22
application/admin/view/addon/add.html

@@ -1,22 +0,0 @@
-<form id="add-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
-
-    <div class="form-group">
-        <label for="c-name" class="control-label col-xs-12 col-sm-2">{:__('Name')}:</label>
-        <div class="col-xs-12 col-sm-8">
-            <button type="button" id="plupload-addon" class="btn btn-danger plupload" data-url="addon/local" data-mimetype="application/zip" data-multiple="false"><i class="fa fa-upload"></i> {:__('请选择一个安装包')}</button>
-        </div>
-    </div>
-    <div class="form-group">
-        <label for="c-nickname" class="control-label col-xs-12 col-sm-2">{:__('Nickname')}:</label>
-        <div class="col-xs-12 col-sm-8">
-            <input id="c-nickname" data-rule="required" class="form-control" name="row[nickname]" type="text" value="">
-        </div>
-    </div>
-    <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 disabled">{:__('OK')}</button>
-            <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
-        </div>
-    </div>
-</form>

+ 148 - 194
application/admin/view/addon/index.html

@@ -1,38 +1,57 @@
 <style type="text/css">
-    .noimage {width:100%;text-align: center;background:#18bc9c;color:#fff;padding-bottom:66.66%;position:relative;}
-    .noimage > div {position: absolute;top:48%;width:100%;text-align:center;}
-    .addon {position: relative;border-color:#e4e4e4;}
-    .addon > span {position:absolute;left:15px;top:15px;z-index:9;}
-    .addon > span a{opacity: 0.5;border:none;color:#fff;}
-    .layui-layer-pay .layui-layer-content {padding:0;height:600px!important;}
-    .layui-layer-pay {border:none;}
-    .payimg{position:relative;width:800px;height:600px;}
-    .payimg .alipaycode {position:absolute;left:265px;top:442px;}
-    .payimg .wechatcode {position:absolute;left:660px;top:442px;}
-    .thumbnail img{width:100%;}
+    .layui-layer-pay .layui-layer-content {
+        padding: 0;
+        height: 600px !important;
+    }
+
+    .layui-layer-pay {
+        border: none;
+    }
+
+    .payimg {
+        position: relative;
+        width: 800px;
+        height: 600px;
+    }
+
+    .payimg .alipaycode {
+        position: absolute;
+        left: 265px;
+        top: 442px;
+    }
+
+    .payimg .wechatcode {
+        position: absolute;
+        left: 660px;
+        top: 442px;
+    }
+
+    .thumbnail img {
+        width: 100%;
+    }
+
     .fixed-table-toolbar .pull-right.search {
         min-width: 300px;
     }
-    .status-disabled .noimage {
-        background:#d2d6de;
+
+    a.title {
+        color: #444;
+    }
+
+    .releasetips {
+        position: relative;
     }
-    @media (min-width: 992px) {
-        .addon {
-            -webkit-transition:all 0.3s ease;
-            -moz-transition: all 0.3s ease;
-            -o-transition: all 0.3s ease;
-            transition: all 0.3s ease;
-        }
-        .addon:hover {
-            border-color: #dddddd;
-            -webkit-box-shadow: 0 26px 40px -24px rgba(0,36,100,0.3);
-            -moz-box-shadow: 0 26px 40px -24px rgba(0,36,100,0.3);
-            box-shadow: 0 26px 40px -24px rgba(0,36,100,0.3);
-            -webkit-transition: all 0.3s ease;
-            -moz-transition: all 0.3s ease;
-            -o-transition: all 0.3s ease;
-            transition: all 0.3s ease;
-        }
+
+    .releasetips i {
+        display: block;
+        background: #f00;
+        border-radius: 50%;
+        width: 0.3em;
+        height: 0.3em;
+        top: 0px;
+        right: -8px;
+        position: absolute;
+        box-shadow: 0px 0px 2px #f11414;
     }
 </style>
 <div class="panel panel-default panel-intro">
@@ -51,16 +70,27 @@
                 <div class="widget-body no-padding">
                     <div id="toolbar" class="toolbar">
                         {:build_toolbar('refresh')}
-                        <button type="button" id="plupload-addon" class="btn btn-danger plupload" data-url="addon/local" data-mimetype="application/zip" data-multiple="false"><i class="fa fa-upload"></i> {:__('Offline install')}</button>
+                        <button type="button" id="plupload-addon" class="btn btn-danger plupload" data-url="addon/local"
+                                data-mimetype="application/zip" data-multiple="false"><i class="fa fa-upload"></i>
+                            {:__('Offline install')}
+                        </button>
                         <div class="btn-group">
-                            <a href="#" class="btn btn-info btn-switch active" data-type="all" data-url="{$config.fastadmin.api_url}/addon/index"><i class="fa fa-list"></i> {:__('全部')}</a>
-                            <a href="#" class="btn btn-info btn-switch" data-type="free" data-url="{$config.fastadmin.api_url}/addon/index"><i class="fa fa-gift"></i> {:__('免费')}</a>
-                            <a href="#" class="btn btn-info btn-switch" data-type="price" data-url="{$config.fastadmin.api_url}/addon/index"><i class="fa fa-rmb"></i> {:__('付费')}</a>
-                            <a href="#" class="btn btn-info btn-switch" data-type="local" data-url="addon/downloaded"><i class="fa fa-laptop"></i> {:__('Local addon')}</a>
+                            <a href="#" class="btn btn-info btn-switch active" data-type="all"
+                               data-url="{$config.fastadmin.api_url}/addon/index"><i class="fa fa-list"></i>
+                                {:__('All')}</a>
+                            <a href="#" class="btn btn-info btn-switch" data-type="free"
+                               data-url="{$config.fastadmin.api_url}/addon/index"><i class="fa fa-gift"></i>
+                                {:__('Free')}</a>
+                            <a href="#" class="btn btn-info btn-switch" data-type="price"
+                               data-url="{$config.fastadmin.api_url}/addon/index"><i class="fa fa-rmb"></i>
+                                {:__('Paying')}</a>
+                            <a href="#" class="btn btn-info btn-switch" data-type="local" data-url="addon/downloaded"><i
+                                    class="fa fa-laptop"></i> {:__('Local addon')}</a>
                         </div>
-                        <a class="btn btn-primary btn-userinfo" href="javascript:;"><i class="fa fa-user"></i> {:__('Userinfo')}</a>
+                        <a class="btn btn-primary btn-userinfo" href="javascript:;"><i class="fa fa-user"></i>
+                            {:__('Userinfo')}</a>
                     </div>
-                    <table id="table" class="table table-striped table-hover" width="100%">
+                    <table id="table" class="table table-striped table-bordered table-hover" width="100%">
 
                     </table>
 
@@ -76,28 +106,28 @@
             <div class="row">
                 <div class="col-xs-12 col-sm-6 col-md-3">
                     <div class="form-group">
-                        <label class="control-label">标题</label>
+                        <label class="control-label">{:__('Title')}</label>
                         <input class="operate" type="hidden" data-name="title" value="like"/>
-                        <input class="form-control" type="text" name="title" placeholder="请输入查找的标题" value=""/>
+                        <input class="form-control" type="text" name="title" placeholder="" value=""/>
                     </div>
                 </div>
                 <div class="col-xs-12 col-sm-6 col-md-3">
                     <div class="form-group">
-                        <label class="control-label">类型</label>
+                        <label class="control-label">{:__('Type')}</label>
                         <input class="operate" type="hidden" data-name="type" value="="/>
                         <input class="form-control" type="text" name="type" placeholder="all" value=""/>
                     </div>
                 </div>
                 <div class="col-xs-12 col-sm-6 col-md-3">
                     <div class="form-group">
-                        <label class="control-label">分类</label>
+                        <label class="control-label">{:__('Category')}</label>
                         <input type="hidden" class="operate" data-name="category_id" value="="/>
                         <input class="form-control" name="category_id" type="text" value="">
                     </div>
                 </div>
                 <div class="col-xs-12 col-sm-6 col-md-3">
                     <div class="form-group">
-                        <label class="control-label">版本号</label>
+                        <label class="control-label">{:__('Version')}</label>
                         <input type="hidden" class="operate" data-name="faversion" value="="/>
                         <input class="form-control" name="faversion" type="text" value="{$config.fastadmin.version}">
                     </div>
@@ -107,10 +137,10 @@
                         <label class="control-label"></label>
                         <div class="row">
                             <div class="col-xs-6">
-                                <input type="submit" class="btn btn-success btn-block" value="提交"/>
+                                <input type="submit" class="btn btn-success btn-block" value="{:__('Submit')}"/>
                             </div>
                             <div class="col-xs-6">
-                                <input type="reset" class="btn btn-primary btn-block" value="重置"/>
+                                <input type="reset" class="btn btn-primary btn-block" value="{:__('Reset')}"/>
                             </div>
                         </div>
                     </div>
@@ -125,18 +155,20 @@
             <fieldset>
                 <div class="alert alert-dismissable alert-danger">
                     <button type="button" class="close" data-dismiss="alert">×</button>
-                    <strong>{:__('Warning')}</strong><br />{:__('Login tips')}
+                    <strong>{:__('Warning')}</strong><br/>{:__('Login tips')}
                 </div>
                 <div class="form-group">
                     <label for="inputAccount" class="col-lg-3 control-label">{:__('Username')}</label>
                     <div class="col-lg-9">
-                        <input type="text" class="form-control" id="inputAccount" value="" placeholder="{:__('Your username or email')}">
+                        <input type="text" class="form-control" id="inputAccount" value=""
+                               placeholder="{:__('Your username or email')}">
                     </div>
                 </div>
                 <div class="form-group">
                     <label for="inputPassword" class="col-lg-3 control-label">{:__('Password')}</label>
                     <div class="col-lg-9">
-                        <input type="password" class="form-control" id="inputPassword" value="" placeholder="{:__('Your password')}">
+                        <input type="password" class="form-control" id="inputPassword" value=""
+                               placeholder="{:__('Your password')}">
                     </div>
                 </div>
             </fieldset>
@@ -149,57 +181,12 @@
             <fieldset>
                 <div class="alert alert-dismissable alert-success">
                     <button type="button" class="close" data-dismiss="alert">×</button>
-                    <strong>{:__('Warning')}</strong><br />{:__('Logined tips', '<%=username%>')}
+                    <strong>{:__('Warning')}</strong><br/>{:__('Logined tips', '<%=username%>')}
                 </div>
             </fieldset>
         </form>
     </div>
 </script>
-<script id="addoninfotpl" type="text/html">
-    <div style="font-size:13px;">
-        <table class="table table-striped">
-            <thead>
-                <tr>
-                    <th colspan="2"><h4><%=item.title%></h4></th>
-                </tr>
-            </thead>
-            <tbody>
-                <tr>
-                    <th scope="row">{:__('Price')}</th>
-                    <td><div class="text-<%=item.price>0?'danger':'success'%>"><b>¥<%=item.price%></b></div></td>
-                </tr>
-                <tr>
-                    <th scope="row">{:__('Author')}</th>
-                    <td><a href="<%=item.url?item.url:'javascript:;'%>" target="_blank"><%=item.author%></a></td>
-                </tr>
-                <tr>
-                    <th scope="row">{:__('Identify')}</th>
-                    <td><%=item.name%></td>
-                </tr>
-                <tr>
-                    <th scope="row">{:__('Homepage')}</th>
-                    <td><a href="https://www.fastadmin.net/store/<%=item.name%>.html" target="_blank">https://www.fastadmin.net/store/<%=item.name%>.html</a></td>
-                </tr>
-                <tr>
-                    <th scope="row">{:__('Intro')}</th>
-                    <td><%=item.intro%></td>
-                </tr>
-                <tr>
-                    <th scope="row">{:__('Version')}</th>
-                    <td><%=# addon && item && addon.version!=item.version?'<span class="label label-danger">'+addon.version+'</span> -> <span class="label label-success">'+item.version+'</span>':item.version%></td>
-                </tr>
-                <tr>
-                    <th scope="row">{:__('Createtime')}</th>
-                    <td><%=Moment(item.createtime*1000).format("YYYY-MM-DD HH:mm:ss")%></td>
-                </tr>
-                <tr>
-                    <th scope="row">{:__('Releasetime')}</th>
-                    <td><%=Moment(item.releasetime*1000).format("YYYY-MM-DD HH:mm:ss")%></td>
-                </tr>
-            </tbody>
-        </table>
-    </div>
-</script>
 <script id="paytpl" type="text/html">
     <div class="payimg" style="background:url('<%=payimg%>') 0 0 no-repeat;background-size:cover;">
         <%if(paycode){%>
@@ -219,118 +206,85 @@
     </div>
     <table class="table table-striped">
         <thead>
-            <tr>
-                <th>#</th>
-                <th>{:__('File')}</th>
-            </tr>
+        <tr>
+            <th>#</th>
+            <th>{:__('File')}</th>
+        </tr>
         </thead>
         <tbody>
-            <%for(var i=0;i < conflictlist.length;i++){%>
-            <tr>
-                <th scope="row"><%=i+1%></th>
-                <td><%=conflictlist[i]%></td>
-            </tr>
-            <%}%>
+        <%for(var i=0;i < conflictlist.length;i++){%>
+        <tr>
+            <th scope="row"><%=i+1%></th>
+            <td><%=conflictlist[i]%></td>
+        </tr>
+        <%}%>
         </tbody>
     </table>
 </script>
-<script id="itemtpl" type="text/html">
+<script id="operatetpl" type="text/html">
     <% var labelarr = ['primary', 'success', 'info', 'danger', 'warning']; %>
     <% var label = labelarr[item.id % 5]; %>
-    <% var addon = typeof addons[item.name]!= 'undefined' ? addons[item.name] : null; %>
-    <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 mt-4 status-<%=addon ? (addon.state==1?'enabled':'disabled') : 'uninstalled'%>">
-        <div class="thumbnail addon">
-            <%if(addon){%>
-            <span>
-                <a href="<%=addon.url%>" target="_blank" class="btn btn-xs">
-                    <i class="fa fa-home"></i>
+    <% var addon = item.addon; %>
+
+    <div class="operate" data-id="<%=item.id%>" data-name="<%=item.name%>">
+        <% if(!addon){ %>
+        <% if(typeof item.releaselist !="undefined" && item.releaselist.length>1){%>
+        <span class="btn-group">
+                <a href="javascript:;" class="btn btn-xs btn-primary btn-success btn-install"
+                   data-type="<%=item.price<=0?'free':'price';%>" data-donateimage="<%=item.donateimage%>"
+                   data-version="<%=item.version%>"><i class="fa fa-cloud-download"></i> {:__('Install')}</a>
+                <a class="btn btn-xs btn-success dropdown-toggle" data-toggle="dropdown" href="javascript:;">
+                    <span class="fa fa-caret-down"></span>
                 </a>
-            </span>
-            <%}%>
-            <a href="<%=item.url%>" class="btn-addonindex" target="_blank">
-                <%if(item.image){%>
-                <img src="<%=item.image%>" class="img-responsive" alt="<%=item.title%>">
-                <%}else{%>
-                <div class="noimage"><div>{:__('No image')}</div></div>
-                <%}%>
-            </a>
-            <div class="caption">
-                <h4><%=item.title?item.title:'{:__('None')}'%>
-                    <% if(item.flag.indexOf("recommend")>-1){%>
-                    <span class="label label-success">{:__('Recommend')}</span>
-                    <% } %>
-                    <% if(item.flag.indexOf("hot")>-1){%>
-                    <span class="label label-danger">{:__('Hot')}</span>
-                    <% } %>
-                    <% if(item.flag.indexOf("free")>-1){%>
-                    <span class="label label-info">{:__('Free')}</span>
-                    <% } %>
-                    <% if(item.flag.indexOf("sale")>-1){%>
-                    <span class="label label-warning">{:__('Sale')}</span>
-                    <% } %>
-                </h4>
-                <p class="text-<%=item.price>0?'danger':'success'%>"><b>¥<%=item.price%></b></p>
-                <p class="text-muted">{:__('Author')}: <a href="<%=item.url?item.url:'javascript:;'%>" target="_blank"><%=item.author%></a></p>
-                <p class="text-muted">{:__('Intro')}: <%=item.intro%></p>
-                <p class="text-muted">{:__('Version')}: <%=# addon && item && addon.version!=item.version?'<span class="label label-danger">'+addon.version+'</span> -> <span class="label label-success">'+item.version+'</span>':item.version%></p>
-                <p class="text-muted">{:__('Createtime')}: <%=Moment(item.createtime*1000).format("YYYY-MM-DD HH:mm:ss")%></p>
-                <div class="operate" data-id="<%=item.id%>" data-name="<%=item.name%>">
-                    <% if(!addon){ %>
-                    <% if(typeof item.releaselist !="undefined" && item.releaselist.length>1){%>
-                    <span class="btn-group">
-                        <a href="javascript:;" class="btn btn-primary btn-success btn-install" data-type="<%=item.price<=0?'free':'price';%>" data-donateimage="<%=item.donateimage%>" data-version="<%=item.version%>"><i class="fa fa-cloud-download"></i> {:__('Install')}</a>
-                        <a class="btn btn-success dropdown-toggle" data-toggle="dropdown" href="javascript:;">
-                            <span class="fa fa-caret-down"></span>
-                        </a>
-                        <ul class="dropdown-menu">
-                            <% for(var j=0;j< item.releaselist.length;j++){ %>
-                            <li><a href="javascript:;" class="btn-install" data-type="<%=item.price<=0?'free':'price';%>" data-donateimage="<%=item.donateimage%>" data-version="<%=item.releaselist[j].version%>"><%=item.releaselist[j].version%></a></li>
-                            <% } %>
-                        </ul>
-                    </span>
-                    <% }else{%>
-                    <a href="javascript:;" class="btn btn-primary btn-success btn-install" data-type="<%=item.price<=0?'free':'price';%>" data-donateimage="<%=item.donateimage%>" data-version="<%=item.version%>"><i class="fa fa-cloud-download"></i> {:__('Install')}</a>
-                    <% } %>
-                    <% if(item.demourl){ %>
-                    <a href="<%=item.demourl%>" class="btn btn-primary btn-info btn-demo" target="_blank"><i class="fa fa-flash"></i> {:__('Demo')}</a>
-                    <% } %>
+                <ul class="dropdown-menu">
+                    <% for(var j=0;j< item.releaselist.length;j++){ %>
+                    <li><a href="javascript:;" class="btn-install" data-type="<%=item.price<=0?'free':'price';%>"
+                           data-donateimage="<%=item.donateimage%>"
+                           data-version="<%=item.releaselist[j].version%>"><%=item.releaselist[j].version%></a></li>
                     <% } %>
+                </ul>
+            </span>
+        <% }else{%>
+        <a href="javascript:;" class="btn btn-xs btn-primary btn-success btn-install"
+           data-type="<%=item.price<=0?'free':'price';%>" data-donateimage="<%=item.donateimage%>"
+           data-version="<%=item.version%>"><i class="fa fa-cloud-download"></i> {:__('Install')}</a>
+        <% } %>
 
-                    <% if(addon){ %>
-                    <% if(addon.config){ %>
-                    <a href="javascript:;" class="btn btn-primary btn-config"><i class="fa fa-pencil"></i> {:__('Setting')}</a>
-                    <% } %>
-                    <% if(addon.state == "1"){ %>
-                    <a href="javascript:;" class="btn btn-warning btn-disable" data-action="disable"><i class="fa fa-times"></i> {:__('Disable')}</a>
-                    <% }else{ %>
-                    <a href="javascript:;" class="btn btn-success btn-enable" data-action="enable"><i class="fa fa-check"></i> {:__('Enable')}</a>
-                    <a href="javascript:;" class="btn btn-danger btn-uninstall"><i class="fa fa-times"></i> {:__('Uninstall')}</a>
-                    <% } %>
-                    <% } %>
-                    <% if(addon && item && addon.version!=item.version){%>
-                    <% if(typeof item.releaselist !="undefined" && item.releaselist.length>1){%>
-                    <span class="btn-group">
-                        <a href="javascript:;" class="btn btn-info btn-success btn-upgrade" data-version="<%=item.version%>"><i class="fa fa-cloud"></i> {:__('Upgrade')}</a>
-                        <a class="btn btn-info dropdown-toggle" data-toggle="dropdown" href="javascript:;">
-                            <span class="fa fa-caret-down"></span>
-                        </a>
-                        <ul class="dropdown-menu">
-                            <% for(var j=0;j< item.releaselist.length;j++){ %>
-                            <li><a href="javascript:;" class="btn-upgrade" data-version="<%=item.releaselist[j].version%>"><%=item.releaselist[j].version%></a></li>
-                            <% } %>
-                        </ul>
-                    </span>
-                    <% }else{%>
-                    <a href="javascript:;" class="btn btn-info btn-upgrade" data-version="<%=item.version%>"><i class="fa fa-cloud"></i> {:__('Upgrade')}</a>
-                    <% }%>
-                    <% }%>
+        <% if(item.demourl){ %>
+        <a href="<%=item.demourl%>" class="btn btn-xs btn-primary btn-info btn-demo" target="_blank">
+            <i class="fa fa-flash"></i> {:__('Demo')}
+        </a>
+        <% } %>
+        <% } else {%>
+        <% if(addon.version!=item.version){%>
+        <% if(typeof item.releaselist !="undefined" && item.releaselist.length>1){%>
+        <span class="btn-group">
+                                <a href="javascript:;" class="btn btn-xs btn-info btn-success btn-upgrade"
+                                   data-version="<%=item.version%>"><i class="fa fa-cloud"></i> {:__('Upgrade')}</a>
+                                <a class="btn btn-xs btn-info dropdown-toggle" data-toggle="dropdown"
+                                   href="javascript:;">
+                                    <span class="fa fa-caret-down"></span>
+                                </a>
+                                <ul class="dropdown-menu">
+                                    <% for(var j=0;j< item.releaselist.length;j++){ %>
+                                    <li><a href="javascript:;" class="btn-upgrade"
+                                           data-version="<%=item.releaselist[j].version%>"><%=item.releaselist[j].version%></a></li>
+                                    <% } %>
+                                </ul>
+                            </span>
+        <% }else{%>
+        <a href="javascript:;" class="btn btn-xs btn-info btn-upgrade" data-version="<%=item.version%>"><i
+                class="fa fa-cloud"></i> {:__('Upgrade')}</a>
+        <% }%>
+        <% }%>
+        <% if(addon.config){ %>
+        <a href="javascript:;" class="btn btn-xs btn-primary btn-config"><i class="fa fa-pencil"></i>
+            {:__('Setting')}</a>
+        <% } %>
+        <a href="javascript:;" class="btn btn-xs btn-danger btn-uninstall"><i class="fa fa-times"></i>
+            {:__('Uninstall')}</a>
+        <% } %>
 
-                    <span class="pull-right" style="margin-top:10px;">
-                        <a href="javascript:;" class="btn-addoninfo text-gray" data-index="<%=i%>" title="<%=item.title?item.title:'{:__('None')}'%>"><i class="fa fa-bars"></i></a>
-                    </span>
 
-                </div>
-            </div>
-        </div>
     </div>
 </script>

+ 2 - 2
application/admin/view/auth/admin/add.html

@@ -1,6 +1,6 @@
 <form id="add-form" class="form-horizontal form-ajax" role="form" data-toggle="validator" method="POST" action="">
     <div class="form-group">
-        <label for="role_id" class="control-label col-xs-12 col-sm-2">{:__('Group')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Group')}:</label>
         <div class="col-xs-12 col-sm-8">
             {:build_select('group[]', $groupdata, null, ['class'=>'form-control selectpicker', 'multiple'=>'', 'data-rule'=>'required'])}
         </div>
@@ -30,7 +30,7 @@
         </div>
     </div>
     <div class="form-group">
-        <label for="content" class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
         <div class="col-xs-12 col-sm-8">
             {:build_radios('row[status]', ['normal'=>__('Normal'), 'hidden'=>__('Hidden')])}
         </div>

+ 2 - 2
application/admin/view/auth/admin/edit.html

@@ -1,6 +1,6 @@
 <form id="edit-form" class="form-horizontal form-ajax" role="form" data-toggle="validator" method="POST" action="">
     <div class="form-group">
-        <label for="role_id" class="control-label col-xs-12 col-sm-2">{:__('Group')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Group')}:</label>
         <div class="col-xs-12 col-sm-8">
             {:build_select('group[]', $groupdata, $groupids, ['class'=>'form-control selectpicker', 'multiple'=>'', 'data-rule'=>'required'])}
         </div>
@@ -36,7 +36,7 @@
         </div>
     </div>
     <div class="form-group">
-        <label for="content" class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
         <div class="col-xs-12 col-sm-8">
             {:build_radios('row[status]', ['normal'=>__('Normal'), 'hidden'=>__('Hidden')], $row['status'])}
         </div>

+ 4 - 4
application/admin/view/auth/group/add.html

@@ -1,19 +1,19 @@
 <form id="add-form" class="form-horizontal form-ajax" role="form" data-toggle="validator" method="POST" action="">
     <input type="hidden" name="row[rules]" value="" />
     <div class="form-group">
-        <label for="pid" class="control-label col-xs-12 col-sm-2">{:__('Parent')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Parent')}:</label>
         <div class="col-xs-12 col-sm-8">
             {:build_select('row[pid]', $groupdata, null, ['class'=>'form-control selectpicker', 'data-rule'=>'required'])}
         </div>
     </div>
     <div class="form-group">
-        <label for="username" class="control-label col-xs-12 col-sm-2">{:__('Name')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Name')}:</label>
         <div class="col-xs-12 col-sm-8">
             <input type="text" class="form-control" id="name" name="row[name]" value="" data-rule="required" />
         </div>
     </div>
     <div class="form-group">
-        <label for="nickname" class="control-label col-xs-12 col-sm-2">{:__('Permission')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Permission')}:</label>
         <div class="col-xs-12 col-sm-8">
             <span class="text-muted"><input type="checkbox" name="" id="checkall" /> <label for="checkall"><small>{:__('Check all')}</small></label></span>
             <span class="text-muted"><input type="checkbox" name="" id="expandall" /> <label for="expandall"><small>{:__('Expand all')}</small></label></span>
@@ -22,7 +22,7 @@
         </div>
     </div>
     <div class="form-group">
-        <label for="content" class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
         <div class="col-xs-12 col-sm-8">
             {:build_radios('row[status]', ['normal'=>__('Normal'), 'hidden'=>__('Hidden')])}
         </div>

+ 4 - 4
application/admin/view/auth/group/edit.html

@@ -1,19 +1,19 @@
 <form id="edit-form" class="form-horizontal form-ajax" role="form" method="POST" action="">
     <input type="hidden" name="row[rules]" value="" />
     <div class="form-group">
-        <label for="pid" class="control-label col-xs-12 col-sm-2">{:__('Parent')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Parent')}:</label>
         <div class="col-xs-12 col-sm-8">
             {:build_select('row[pid]', $groupdata, $row['pid'], ['class'=>'form-control selectpicker', 'data-rule'=>'required', 'data-id'=>$row['id'], 'data-pid'=>$row['pid']])}
         </div>
     </div>
     <div class="form-group">
-        <label for="username" class="control-label col-xs-12 col-sm-2">{:__('Name')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Name')}:</label>
         <div class="col-xs-12 col-sm-8">
             <input type="text" class="form-control" id="name" name="row[name]" value="{$row.name}" data-rule="required" />
         </div>
     </div>
     <div class="form-group">
-        <label for="nickname" class="control-label col-xs-12 col-sm-2">{:__('Permission')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Permission')}:</label>
         <div class="col-xs-12 col-sm-8">
             <span class="text-muted"><input type="checkbox" name="" id="checkall" /> <label for="checkall"><small>{:__('Check all')}</small></label></span>
             <span class="text-muted"><input type="checkbox" name="" id="expandall" /> <label for="expandall"><small>{:__('Expand all')}</small></label></span>
@@ -22,7 +22,7 @@
         </div>
     </div>
     <div class="form-group">
-        <label for="content" class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
         <div class="col-xs-12 col-sm-8">
             {:build_radios('row[status]', ['normal'=>__('Normal'), 'hidden'=>__('Hidden')], $row['status'])}
         </div>

+ 5 - 8
application/admin/view/auth/rule/add.html

@@ -1,16 +1,12 @@
-<div class="callout callout-info">
-    <h4>{:__('Alert')}!</h4>
-    {:__('If not necessary, use the command line to build rule')}
-</div>
 <form id="add-form" class="form-horizontal form-ajax" role="form" data-toggle="validator" method="POST" action="">
     <div class="form-group">
-        <label for="content" class="control-label col-xs-12 col-sm-2">{:__('Ismenu')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Ismenu')}:</label>
         <div class="col-xs-12 col-sm-8">
             {:build_radios('row[ismenu]', ['1'=>__('Yes'), '0'=>__('No')])}
         </div>
     </div>
     <div class="form-group">
-        <label for="pid" class="control-label col-xs-12 col-sm-2">{:__('Parent')}:</label>
+        <label  class="control-label col-xs-12 col-sm-2">{:__('Parent')}:</label>
         <div class="col-xs-12 col-sm-8">
             {:build_select('row[pid]', $ruledata, null, ['class'=>'form-control', 'required'=>''])}
         </div>
@@ -18,11 +14,12 @@
     <div class="form-group">
         <label for="name" class="control-label col-xs-12 col-sm-2">{:__('Name')}:</label>
         <div class="col-xs-12 col-sm-8">
+            <a href="javascript:;" data-toggle="tooltip" title="提示">测试</a>
             <input type="text" class="form-control" id="name" name="row[name]" data-placeholder-node="{:__('Node tips')}" data-placeholder-menu="{:__('Menu tips')}" value="" data-rule="required" />
         </div>
     </div>
     <div class="form-group">
-        <label for="module" class="control-label col-xs-12 col-sm-2">{:__('Title')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Title')}:</label>
         <div class="col-xs-12 col-sm-8">
             <input type="text" class="form-control" id="title" name="row[title]" value="" data-rule="required" />
         </div>
@@ -55,7 +52,7 @@
         </div>
     </div>
     <div class="form-group">
-        <label for="content" class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
         <div class="col-xs-12 col-sm-8">
             {:build_radios('row[status]', ['normal'=>__('Normal'), 'hidden'=>__('Hidden')])}
         </div>

+ 4 - 4
application/admin/view/auth/rule/edit.html

@@ -1,12 +1,12 @@
 <form id="edit-form" class="form-horizontal form-ajax" role="form" method="POST" action="">
     <div class="form-group">
-        <label for="content" class="control-label col-xs-12 col-sm-2">{:__('Ismenu')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Ismenu')}:</label>
         <div class="col-xs-12 col-sm-8">
             {:build_radios('row[ismenu]', ['1'=>__('Yes'), '0'=>__('No')], $row['ismenu'])}
         </div>
     </div>
     <div class="form-group">
-        <label for="pid" class="control-label col-xs-12 col-sm-2">{:__('Parent')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Parent')}:</label>
         <div class="col-xs-12 col-sm-8">
             {:build_select('row[pid]', $ruledata, $row['pid'], ['class'=>'form-control', 'required'=>''])}
         </div>
@@ -18,7 +18,7 @@
         </div>
     </div>
     <div class="form-group">
-        <label for="action" class="control-label col-xs-12 col-sm-2">{:__('Title')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Title')}:</label>
         <div class="col-xs-12 col-sm-8">
             <input type="text" class="form-control" id="title" name="row[title]" value="{$row.title}" data-rule="required" />
         </div>
@@ -51,7 +51,7 @@
         </div>
     </div>
     <div class="form-group">
-        <label for="content" class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
         <div class="col-xs-12 col-sm-8">
             {:build_radios('row[status]', ['normal'=>__('Normal'), 'hidden'=>__('Hidden')], $row['status'])}
         </div>

+ 5 - 4
application/admin/view/auth/rule/tpl.html

@@ -6,12 +6,13 @@
         margin:5px 0 0 0;
     }
     #chooseicon ul li{
-        width:30px;height:30px;
-        line-height:30px;
-        border:1px solid #ddd;
+        width:41px;height:42px;
+        line-height:42px;
+        border:1px solid #efefef;
         padding:1px;
         margin:1px;
         text-align: center;
+        font-size:18px;
     }
     #chooseicon ul li:hover{
         border:1px solid #2c3e50;
@@ -31,7 +32,7 @@
         <div>
             <ul class="list-inline">
                 <% for(var i=0; i<iconlist.length; i++){ %>
-                    <li data-font="<%=iconlist[i]%>" title="<%=iconlist[i]%>">
+                    <li data-font="<%=iconlist[i]%>" data-toggle="tooltip" title="<%=iconlist[i]%>">
                     <i class="fa fa-<%=iconlist[i]%>"></i>
                 </li>
                 <% } %>

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

@@ -81,7 +81,7 @@
         </div>
     </div>
     <div class="form-group">
-        <label for="c-status" class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
         <div class="col-xs-12 col-sm-8">
             {:build_radios('row[status]', ['normal'=>__('Normal'), 'hidden'=>__('Hidden')])}
         </div>

+ 1 - 1
application/admin/view/category/edit.html

@@ -81,7 +81,7 @@
         </div>
     </div>
     <div class="form-group">
-        <label for="c-status" class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
         <div class="col-xs-12 col-sm-8">
             {:build_radios('row[status]', ['normal'=>__('Normal'), 'hidden'=>__('Hidden')], $row['status'])}
         </div>

+ 1 - 0
application/admin/view/common/header.html

@@ -54,6 +54,7 @@
                 </a>
                 <ul class="dropdown-menu wipecache">
                     <li><a href="javascript:;" data-type="all"><i class="fa fa-trash"></i> {:__('Wipe all cache')}</a></li>
+                    <li class="divider"></li>
                     <li><a href="javascript:;" data-type="content"><i class="fa fa-file-text"></i> {:__('Wipe content cache')}</a></li>
                     <li><a href="javascript:;" data-type="template"><i class="fa fa-file-image-o"></i> {:__('Wipe template cache')}</a></li>
                     <li><a href="javascript:;" data-type="addons"><i class="fa fa-rocket"></i> {:__('Wipe addons cache')}</a></li>

+ 11 - 17
application/api/controller/Common.php

@@ -24,15 +24,14 @@ class Common extends Api
 
     /**
      * 加载初始化
-     * 
+     *
      * @param string $version 版本号
      * @param string $lng 经度
      * @param string $lat 纬度
      */
     public function init()
     {
-        if ($version = $this->request->request('version'))
-        {
+        if ($version = $this->request->request('version')) {
             $lng = $this->request->request('lng');
             $lat = $this->request->request('lat');
             $content = [
@@ -42,23 +41,20 @@ class Common extends Api
                 'coverdata'   => Config::get("cover"),
             ];
             $this->success('', $content);
-        }
-        else
-        {
+        } else {
             $this->error(__('Invalid parameters'));
         }
     }
 
     /**
      * 上传文件
-     * 
+     *
      * @param File $file 文件流
      */
     public function upload()
     {
         $file = $this->request->file('file');
-        if (empty($file))
-        {
+        if (empty($file)) {
             $this->error(__('No file upload or server upload limit exceeded'));
         }
 
@@ -70,7 +66,7 @@ class Common extends Api
         preg_match('/(\d+)(\w+)/', $upload['maxsize'], $matches);
         $type = strtolower($matches[2]);
         $typeDict = ['b' => 0, 'k' => 1, 'kb' => 1, 'm' => 2, 'mb' => 2, 'gb' => 3, 'g' => 3];
-        $size = (int) $upload['maxsize'] * pow(1024, isset($typeDict[$type]) ? $typeDict[$type] : 0);
+        $size = (int)$upload['maxsize'] * pow(1024, isset($typeDict[$type]) ? $typeDict[$type] : 0);
         $fileInfo = $file->getInfo();
         $suffix = strtolower(pathinfo($fileInfo['name'], PATHINFO_EXTENSION));
         $suffix = $suffix ? $suffix : 'file';
@@ -108,16 +104,16 @@ class Common extends Api
         $fileName = substr($savekey, strripos($savekey, '/') + 1);
         //
         $splInfo = $file->validate(['size' => $size])->move(ROOT_PATH . '/public' . $uploadDir, $fileName);
-        if ($splInfo)
-        {
+        if ($splInfo) {
             $imagewidth = $imageheight = 0;
-            if (in_array($suffix, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf']))
-            {
+            if (in_array($suffix, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf'])) {
                 $imgInfo = getimagesize($splInfo->getPathname());
                 $imagewidth = isset($imgInfo[0]) ? $imgInfo[0] : $imagewidth;
                 $imageheight = isset($imgInfo[1]) ? $imgInfo[1] : $imageheight;
             }
             $params = array(
+                'admin_id'    => 0,
+                'user_id'     => (int)$this->auth->id,
                 'filesize'    => $fileInfo['size'],
                 'imagewidth'  => $imagewidth,
                 'imageheight' => $imageheight,
@@ -136,9 +132,7 @@ class Common extends Api
             $this->success(__('Upload successful'), [
                 'url' => $uploadDir . $splInfo->getSaveName()
             ]);
-        }
-        else
-        {
+        } else {
             // 上传失败获取错误信息
             $this->error($file->getError());
         }

+ 69 - 102
application/common.php

@@ -2,22 +2,20 @@
 
 // 公共助手函数
 
-if (!function_exists('__'))
-{
+if (!function_exists('__')) {
 
     /**
      * 获取语言变量值
-     * @param string    $name 语言变量名
-     * @param array     $vars 动态变量值
-     * @param string    $lang 语言
+     * @param string $name 语言变量名
+     * @param array $vars 动态变量值
+     * @param string $lang 语言
      * @return mixed
      */
     function __($name, $vars = [], $lang = '')
     {
         if (is_numeric($name) || !$name)
             return $name;
-        if (!is_array($vars))
-        {
+        if (!is_array($vars)) {
             $vars = func_get_args();
             array_shift($vars);
             $lang = '';
@@ -27,8 +25,7 @@ if (!function_exists('__'))
 
 }
 
-if (!function_exists('format_bytes'))
-{
+if (!function_exists('format_bytes')) {
 
     /**
      * 将字节转换为可读文本
@@ -46,8 +43,7 @@ if (!function_exists('format_bytes'))
 
 }
 
-if (!function_exists('datetime'))
-{
+if (!function_exists('datetime')) {
 
     /**
      * 将时间戳转换为日期时间
@@ -63,8 +59,7 @@ if (!function_exists('datetime'))
 
 }
 
-if (!function_exists('human_date'))
-{
+if (!function_exists('human_date')) {
 
     /**
      * 获取语义化时间
@@ -79,50 +74,56 @@ if (!function_exists('human_date'))
 
 }
 
-if (!function_exists('cdnurl'))
-{
+if (!function_exists('cdnurl')) {
 
     /**
      * 获取上传资源的CDN的地址
      * @param string $url 资源相对地址
+     * @param boolean $domain 是否显示域名 或者直接传入域名
      * @return string
      */
-    function cdnurl($url)
+    function cdnurl($url, $domain = false)
     {
-        return preg_match("/^https?:\/\/(.*)/i", $url) ? $url : \think\Config::get('upload.cdnurl') . $url;
+        $url = preg_match("/^https?:\/\/(.*)/i", $url) ? $url : \think\Config::get('upload.cdnurl') . $url;
+        if ($domain && !preg_match("/^(http:\/\/|https:\/\/)/i", $url)) {
+            if (is_bool($domain)) {
+                $public = \think\Config::get('view_replace_str.__PUBLIC__');
+                $url = rtrim($public, '/') . $url;
+                if (!preg_match("/^(http:\/\/|https:\/\/)/i", $url)) {
+                    $url = request()->domain() . $url;
+                }
+            } else {
+                $url = $domain . $url;
+            }
+        }
+        return $url;
     }
 
 }
 
 
-if (!function_exists('is_really_writable'))
-{
+if (!function_exists('is_really_writable')) {
 
     /**
      * 判断文件或文件夹是否可写
-     * @param	string $file 文件或目录
-     * @return	bool
+     * @param    string $file 文件或目录
+     * @return    bool
      */
     function is_really_writable($file)
     {
-        if (DIRECTORY_SEPARATOR === '/')
-        {
+        if (DIRECTORY_SEPARATOR === '/') {
             return is_writable($file);
         }
-        if (is_dir($file))
-        {
+        if (is_dir($file)) {
             $file = rtrim($file, '/') . '/' . md5(mt_rand());
-            if (($fp = @fopen($file, 'ab')) === FALSE)
-            {
+            if (($fp = @fopen($file, 'ab')) === FALSE) {
                 return FALSE;
             }
             fclose($fp);
             @chmod($file, 0777);
             @unlink($file);
             return TRUE;
-        }
-        elseif (!is_file($file) OR ( $fp = @fopen($file, 'ab')) === FALSE)
-        {
+        } elseif (!is_file($file) OR ($fp = @fopen($file, 'ab')) === FALSE) {
             return FALSE;
         }
         fclose($fp);
@@ -131,8 +132,7 @@ if (!function_exists('is_really_writable'))
 
 }
 
-if (!function_exists('rmdirs'))
-{
+if (!function_exists('rmdirs')) {
 
     /**
      * 删除文件夹
@@ -145,16 +145,14 @@ if (!function_exists('rmdirs'))
         if (!is_dir($dirname))
             return false;
         $files = new RecursiveIteratorIterator(
-                new RecursiveDirectoryIterator($dirname, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST
+            new RecursiveDirectoryIterator($dirname, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST
         );
 
-        foreach ($files as $fileinfo)
-        {
+        foreach ($files as $fileinfo) {
             $todo = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
             $todo($fileinfo->getRealPath());
         }
-        if ($withself)
-        {
+        if ($withself) {
             @rmdir($dirname);
         }
         return true;
@@ -162,8 +160,7 @@ if (!function_exists('rmdirs'))
 
 }
 
-if (!function_exists('copydirs'))
-{
+if (!function_exists('copydirs')) {
 
     /**
      * 复制文件夹
@@ -172,25 +169,19 @@ if (!function_exists('copydirs'))
      */
     function copydirs($source, $dest)
     {
-        if (!is_dir($dest))
-        {
-            mkdir($dest, 0755);
+        if (!is_dir($dest)) {
+            mkdir($dest, 0755, true);
         }
         foreach (
-        $iterator = new RecursiveIteratorIterator(
-        new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST) as $item
-        )
-        {
-            if ($item->isDir())
-            {
+            $iterator = new RecursiveIteratorIterator(
+                new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST) as $item
+        ) {
+            if ($item->isDir()) {
                 $sontDir = $dest . DS . $iterator->getSubPathName();
-                if (!is_dir($sontDir))
-                {
-                    mkdir($sontDir);
+                if (!is_dir($sontDir)) {
+                    mkdir($sontDir, 0755, true);
                 }
-            }
-            else
-            {
+            } else {
                 copy($item, $dest . DS . $iterator->getSubPathName());
             }
         }
@@ -198,8 +189,7 @@ if (!function_exists('copydirs'))
 
 }
 
-if (!function_exists('mb_ucfirst'))
-{
+if (!function_exists('mb_ucfirst')) {
 
     function mb_ucfirst($string)
     {
@@ -208,8 +198,7 @@ if (!function_exists('mb_ucfirst'))
 
 }
 
-if (!function_exists('addtion'))
-{
+if (!function_exists('addtion')) {
 
     /**
      * 附加关联字段数据
@@ -222,31 +211,22 @@ if (!function_exists('addtion'))
         if (!$items || !$fields)
             return $items;
         $fieldsArr = [];
-        if (!is_array($fields))
-        {
+        if (!is_array($fields)) {
             $arr = explode(',', $fields);
-            foreach ($arr as $k => $v)
-            {
+            foreach ($arr as $k => $v) {
                 $fieldsArr[$v] = ['field' => $v];
             }
-        }
-        else
-        {
-            foreach ($fields as $k => $v)
-            {
-                if (is_array($v))
-                {
+        } else {
+            foreach ($fields as $k => $v) {
+                if (is_array($v)) {
                     $v['field'] = isset($v['field']) ? $v['field'] : $k;
-                }
-                else
-                {
+                } else {
                     $v = ['field' => $v];
                 }
                 $fieldsArr[$v['field']] = $v;
             }
         }
-        foreach ($fieldsArr as $k => &$v)
-        {
+        foreach ($fieldsArr as $k => &$v) {
             $v = is_array($v) ? $v : ['field' => $v];
             $v['display'] = isset($v['display']) ? $v['display'] : str_replace(['_ids', '_id'], ['_names', '_name'], $v['field']);
             $v['primary'] = isset($v['primary']) ? $v['primary'] : '';
@@ -258,37 +238,27 @@ if (!function_exists('addtion'))
         unset($v);
         $ids = [];
         $fields = array_keys($fieldsArr);
-        foreach ($items as $k => $v)
-        {
-            foreach ($fields as $m => $n)
-            {
-                if (isset($v[$n]))
-                {
+        foreach ($items as $k => $v) {
+            foreach ($fields as $m => $n) {
+                if (isset($v[$n])) {
                     $ids[$n] = array_merge(isset($ids[$n]) && is_array($ids[$n]) ? $ids[$n] : [], explode(',', $v[$n]));
                 }
             }
         }
         $result = [];
-        foreach ($fieldsArr as $k => $v)
-        {
-            if ($v['model'])
-            {
+        foreach ($fieldsArr as $k => $v) {
+            if ($v['model']) {
                 $model = new $v['model'];
-            }
-            else
-            {
+            } else {
                 $model = $v['name'] ? \think\Db::name($v['name']) : \think\Db::table($v['table']);
             }
             $primary = $v['primary'] ? $v['primary'] : $model->getPk();
             $result[$v['field']] = $model->where($primary, 'in', $ids[$v['field']])->column("{$primary},{$v['column']}");
         }
 
-        foreach ($items as $k => &$v)
-        {
-            foreach ($fields as $m => $n)
-            {
-                if (isset($v[$n]))
-                {
+        foreach ($items as $k => &$v) {
+            foreach ($fields as $m => $n) {
+                if (isset($v[$n])) {
                     $curr = array_flip(explode(',', $v[$n]));
 
                     $v[$fieldsArr[$n]['display']] = implode(',', array_intersect_key($result[$n], $curr));
@@ -300,29 +270,26 @@ if (!function_exists('addtion'))
 
 }
 
-if (!function_exists('var_export_short'))
-{
+if (!function_exists('var_export_short')) {
 
     /**
      * 返回打印数组结构
-     * @param string $var   数组
+     * @param string $var 数组
      * @param string $indent 缩进字符
      * @return string
      */
     function var_export_short($var, $indent = "")
     {
-        switch (gettype($var))
-        {
+        switch (gettype($var)) {
             case "string":
                 return '"' . addcslashes($var, "\\\$\"\r\n\t\v\f") . '"';
             case "array":
                 $indexed = array_keys($var) === range(0, count($var) - 1);
                 $r = [];
-                foreach ($var as $key => $value)
-                {
+                foreach ($var as $key => $value) {
                     $r[] = "$indent    "
-                            . ($indexed ? "" : var_export_short($key) . " => ")
-                            . var_export_short($value, "$indent    ");
+                        . ($indexed ? "" : var_export_short($key) . " => ")
+                        . var_export_short($value, "$indent    ");
                 }
                 return "[\n" . implode(",\n", $r) . "\n" . $indent . "]";
             case "boolean":

+ 1 - 1
application/config.php

@@ -270,7 +270,7 @@ return [
         //自动检测更新
         'checkupdate'         => false,
         //版本号
-        'version'             => '1.0.0.20180417_beta',
+        'version'             => '1.0.0.20180506_beta',
         //API接口地址
         'api_url'             => 'https://api.fastadmin.net',
     ],

+ 6 - 6
application/index/controller/User.php

@@ -107,7 +107,7 @@ class User extends Frontend
             $validate = new Validate($rule, $msg);
             $result = $validate->check($data);
             if (!$result) {
-                $this->error(__($validate->getError()));
+                $this->error(__($validate->getError()), null, ['token' => $this->request->token()]);
             }
             if ($this->auth->register($username, $password, $email, $mobile)) {
                 $synchtml = '';
@@ -118,7 +118,7 @@ class User extends Frontend
                 }
                 $this->success(__('Sign up successful') . $synchtml, $url ? $url : url('user/index'));
             } else {
-                $this->error($this->auth->getError());
+                $this->error($this->auth->getError(), null, ['token' => $this->request->token()]);
             }
         }
         //判断来源
@@ -165,7 +165,7 @@ class User extends Frontend
             $validate = new Validate($rule, $msg);
             $result = $validate->check($data);
             if (!$result) {
-                $this->error(__($validate->getError()));
+                $this->error(__($validate->getError()), null, ['token' => $this->request->token()]);
                 return FALSE;
             }
             if ($this->auth->login($account, $password)) {
@@ -177,7 +177,7 @@ class User extends Frontend
                 }
                 $this->success(__('Logged in successful') . $synchtml, $url ? $url : url('user/index'));
             } else {
-                $this->error($this->auth->getError());
+                $this->error($this->auth->getError(), null, ['token' => $this->request->token()]);
             }
         }
         //判断来源
@@ -249,7 +249,7 @@ class User extends Frontend
             $validate = new Validate($rule, $msg, $field);
             $result = $validate->check($data);
             if (!$result) {
-                $this->error(__($validate->getError()));
+                $this->error(__($validate->getError()), null, ['token' => $this->request->token()]);
                 return FALSE;
             }
 
@@ -263,7 +263,7 @@ class User extends Frontend
                 }
                 $this->success(__('Reset password successful') . $synchtml, url('user/login'));
             } else {
-                $this->error($this->auth->getError());
+                $this->error($this->auth->getError(), null, ['token' => $this->request->token()]);
             }
         }
         $this->view->assign('title', __('Change password'));

+ 1 - 0
application/index/view/user/login.html

@@ -4,6 +4,7 @@
         <div class="login-main"> 
             <form name="form" id="login-form" class="form-vertical" method="POST" action="">
                 <input type="hidden" name="url" value="{$url}" />
+                {:token()}
                 <div class="form-group">
                     <label class="control-label" for="account">{:__('Account')}</label>
                     <div class="controls">

+ 1 - 0
application/index/view/user/register.html

@@ -5,6 +5,7 @@
             <form name="form1" id="register-form" class="form-vertical" method="POST" action="">
                 <input type="hidden" name="invite_user_id" value="0" />
                 <input type="hidden" name="url" value="{$url}" />
+                {:token()}
                 <div class="form-group">
                     <label class="control-label required">{:__('Email')}<span class="text-success"></span></label>
                     <div class="controls">

+ 1 - 1
composer.json

@@ -22,7 +22,7 @@
         "topthink/think-captcha": "^1.0",
         "mtdowling/cron-expression": "^1.2",
         "phpmailer/phpmailer": "^5.2",
-        "karsonzhang/fastadmin-addons": "~1.1.0",
+        "karsonzhang/fastadmin-addons": "~1.1.4",
         "overtrue/pinyin": "~3.0",
         "phpoffice/phpexcel": "^1.8"
     },

+ 4 - 4
public/api.html

@@ -356,7 +356,7 @@
                                                                                         <div class="panel panel-default">
                                                 <div class="panel-heading"><strong>参数</strong></div>
                                                 <div class="panel-body">
-                                                    <form enctype="application/x-www-form-urlencoded" role="form" action="/api/demo/test1" method="get" name="form2" id="form2">
+                                                    <form enctype="application/x-www-form-urlencoded" role="form" action="/api/demo/test1" method="GET" name="form2" id="form2">
                                                                                                                 <div class="form-group">
                                                         </div>
@@ -3359,7 +3359,7 @@
 
             <div class="row mt0 footer">
                 <div class="col-md-6" align="left">
-                    Generated on 2018-04-06 19:15:01                </div>
+                    Generated on 2018-05-04 11:06:48                </div>
                 <div class="col-md-6" align="right">
                     <a href="https://www.fastadmin.net" target="_blank">FastAdmin</a>
                 </div>
@@ -3531,8 +3531,8 @@
 
                     $.ajax({
                         url: $('#apiUrl').val() + url,
-                        data: $(form).attr('method') == 'get' ? $(form).serialize() : formData,
-                        type: $(form).attr('method') + '',
+                        data: $(form).prop('method').toLowerCase() == 'get' ? $(form).serialize() : formData,
+                        type: $(form).prop('method') + '',
                         dataType: 'json',
                         contentType: false,
                         processData: false,

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

@@ -559,6 +559,9 @@ form.form-horizontal .control-label {
 .bootstrap-table td.bs-checkbox {
   vertical-align: middle;
 }
+.fixed-table-container thead th .sortable {
+  padding-right: 0;
+}
 .dropdown-submenu {
   position: relative;
 }

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


+ 50 - 47
public/assets/js/backend.js

@@ -103,20 +103,8 @@ define(['fast', 'template', 'moment'], function (Fast, Template, Moment) {
             },
             refreshmenu: function () {
                 top.window.$(".sidebar-menu").trigger("refresh");
-            }
-        },
-        init: function () {
-            //公共代码
-            //添加ios-fix兼容iOS下的iframe
-            if (/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream) {
-                $("html").addClass("ios-fix");
-            }
-            //配置Toastr的参数
-            Toastr.options.positionClass = Config.controllername === 'index' ? "toast-top-right-index" : "toast-top-right";
-            //点击包含.btn-dialog的元素时弹出dialog
-            $(document).on('click', '.btn-dialog,.dialogit', function (e) {
-                var that = this;
-                var options = $.extend({}, $(that).data() || {});
+            },
+            gettablecolumnbutton: function(options){
                 if (typeof options.tableId !== 'undefined' && typeof options.fieldIndex !== 'undefined' && typeof options.buttonIndex !== 'undefined') {
                     var tableOptions = $("#" + options.tableId).bootstrapTable('getOptions');
                     if (tableOptions) {
@@ -133,20 +121,38 @@ define(['fast', 'template', 'moment'], function (Fast, Template, Moment) {
                             }
                         });
                         if (columnObj) {
-                            var button = columnObj['buttons'][options.buttonIndex];
-                            if (button && typeof button.callback === 'function') {
-                                options.callback = button.callback;
-                            }
+                            return columnObj['buttons'][options.buttonIndex];
                         }
                     }
                 }
+                return null;
+            },
+        },
+        init: function () {
+            //公共代码
+            //添加ios-fix兼容iOS下的iframe
+            if (/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream) {
+                $("html").addClass("ios-fix");
+            }
+            //配置Toastr的参数
+            Toastr.options.positionClass = Config.controllername === 'index' ? "toast-top-right-index" : "toast-top-right";
+            //点击包含.btn-dialog的元素时弹出dialog
+            $(document).on('click', '.btn-dialog,.dialogit', function (e) {
+                var that = this;
+                var options = $.extend({}, $(that).data() || {});
+                var url = Backend.api.replaceids(that, $(that).attr('href'));
+                var title = $(that).attr("title") || $(that).data("title") || $(that).data('original-title');
+                var button = Backend.api.gettablecolumnbutton(options);
+                if (button && typeof button.callback === 'function') {
+                    options.callback = button.callback;
+                }
                 if (typeof options.confirm !== 'undefined') {
                     Layer.confirm(options.confirm, function (index) {
-                        Backend.api.open(Backend.api.replaceids(that, $(that).attr('href')), $(that).attr('title'), options);
+                        Backend.api.open(url, title, options);
                         Layer.close(index);
                     });
                 } else {
-                    Backend.api.open(Backend.api.replaceids(that, $(that).attr('href')), $(that).attr('title'), options);
+                    Backend.api.open(url, title, options);
                 }
                 return false;
             });
@@ -154,15 +160,16 @@ define(['fast', 'template', 'moment'], function (Fast, Template, Moment) {
             $(document).on('click', '.btn-addtabs,.addtabsit', function (e) {
                 var that = this;
                 var options = $.extend({}, $(that).data() || {});
+                var url = Backend.api.replaceids(that, $(that).attr('href'));
+                var title = $(that).attr("title") || $(that).data("title") || $(that).data('original-title');
                 if (typeof options.confirm !== 'undefined') {
                     Layer.confirm(options.confirm, function (index) {
-                        Backend.api.addtabs(Backend.api.replaceids(that, $(that).attr('href')), $(that).attr("title"));
+                        Backend.api.addtabs(url, title);
                         Layer.close(index);
                     });
                 } else {
-                    Backend.api.addtabs(Backend.api.replaceids(that, $(that).attr('href')), $(that).attr("title"));
+                    Backend.api.addtabs(url, title);
                 }
-
                 return false;
             });
             //点击包含.btn-ajax的元素时发送Ajax请求
@@ -177,30 +184,13 @@ define(['fast', 'template', 'moment'], function (Fast, Template, Moment) {
                 var error = typeof options.error === 'function' ? options.error : null;
                 delete options.success;
                 delete options.error;
-                if (typeof options.tableId !== 'undefined' && typeof options.fieldIndex !== 'undefined' && typeof options.buttonIndex !== 'undefined') {
-                    var tableOptions = $("#" + options.tableId).bootstrapTable('getOptions');
-                    if (tableOptions) {
-                        var columnObj = null;
-                        $.each(tableOptions.columns, function (i, columns) {
-                            $.each(columns, function (j, column) {
-                                if (typeof column.fieldIndex !== 'undefined' && column.fieldIndex === options.fieldIndex) {
-                                    columnObj = column;
-                                    return false;
-                                }
-                            });
-                            if (columnObj) {
-                                return false;
-                            }
-                        });
-                        if (columnObj) {
-                            var button = columnObj['buttons'][options.buttonIndex];
-                            if (button && typeof button.success === 'function') {
-                                success = button.success;
-                            }
-                            if (button && typeof button.error === 'function') {
-                                error = button.error;
-                            }
-                        }
+                var button = Backend.api.gettablecolumnbutton(options);
+                if (button) {
+                    if (typeof button.success === 'function') {
+                        success = button.success;
+                    }
+                    if (typeof button.error === 'function') {
+                        error = button.error;
                     }
                 }
                 //如果未设备成功的回调,设定了自动刷新的情况下自动进行刷新
@@ -225,6 +215,19 @@ define(['fast', 'template', 'moment'], function (Fast, Template, Moment) {
             if ($(".layer-footer").size() > 0 && self === top) {
                 $(".layer-footer").show();
             }
+            //优化在多个弹窗下点击不能切换的操作体验
+            if (Fast.api.query("dialog") == "1" && self != top && self.frameElement && self.frameElement.tagName == "IFRAME") {
+                $(window).on('click', function () {
+                    var layero = self.frameElement.parentNode.parentElement;
+                    if (parent.Layer.zIndex != parseInt(parent.window.$(layero).css("z-index"))) {
+                        parent.window.$(layero).trigger("mousedown");
+                        parent.Layer.zIndex = parseInt(parent.window.$(layero).css("z-index"));
+                    }
+                });
+            }
+            //tooltip和popover
+            $('body').tooltip({selector: '[data-toggle="tooltip"]'});
+            $('body').tooltip({selector: '[data-toggle="popover"]'});
         }
     };
     Backend.api = $.extend(Fast.api, Backend.api);

+ 131 - 67
public/assets/js/backend/addon.js

@@ -49,13 +49,78 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
                 url: $.fn.bootstrapTable.defaults.extend.index_url,
                 columns: [
                     [
-                        {field: 'id', title: 'ID', operate: false},
-                        {field: 'name', title: __('Name'), operate: false},
-                        {field: 'title', title: __('Title'), operate: 'LIKE'}
+                        {field: 'id', title: 'ID', operate: false, visible: false},
+                        {
+                            field: 'home',
+                            title: __('Index'),
+                            width: '50px',
+                            formatter: Controller.api.formatter.home
+                        },
+                        {field: 'name', title: __('Name'), operate: false, visible: false, width: '120px'},
+                        {
+                            field: 'title',
+                            title: __('Title'),
+                            operate: 'LIKE',
+                            align: 'left',
+                            formatter: Controller.api.formatter.title
+                        },
+                        {field: 'intro', title: __('Intro'), operate: 'LIKE', align: 'left', class:'visible-lg'},
+                        {
+                            field: 'author',
+                            title: __('Author'),
+                            operate: 'LIKE',
+                            width: '100px',
+                            formatter: Controller.api.formatter.author
+                        },
+                        {
+                            field: 'price',
+                            title: __('Price'),
+                            operate: 'LIKE',
+                            width: '100px',
+                            align: 'center',
+                            formatter: Controller.api.formatter.price
+                        },
+                        {
+                            field: 'downloads',
+                            title: __('Downloads'),
+                            operate: 'LIKE',
+                            width: '100px',
+                            align: 'center',
+                            formatter: Controller.api.formatter.downloads
+                        },
+                        {
+                            field: 'version',
+                            title: __('Version'),
+                            operate: 'LIKE',
+                            width: '100px',
+                            align: 'center',
+                            formatter: Controller.api.formatter.version
+                        },
+                        {
+                            field: 'toggle',
+                            title: __('Status'),
+                            width: '100px',
+                            formatter: Controller.api.formatter.toggle
+                        },
+                        {
+                            field: 'id',
+                            title: __('Operate'),
+                            align: 'center',
+                            table: table,
+                            formatter: Controller.api.formatter.operate,
+                            align: 'right'
+                        },
                     ]
                 ],
+                responseHandler: function (res) {
+                    $.each(res.rows, function (i, j) {
+                        j.addon = typeof Config.addons[j.name] != 'undefined' ? Config.addons[j.name] : null;
+                    });
+                    return res;
+                },
                 dataType: 'jsonp',
-                templateView: true,
+                templateView: false,
+                clickToSelect: false,
                 search: true,
                 showColumns: false,
                 showToggle: false,
@@ -71,41 +136,6 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
             // 为表格绑定事件
             Table.api.bindevent(table);
 
-            table.on('click', '.btn-addoninfo', function (event) {
-                var index = parseInt($(this).data("index"));
-                var data = table.bootstrapTable("getData");
-                var item = data[index];
-                var addon = typeof Config.addons[item.name] != 'undefined' ? Config.addons[item.name] : null;
-                Layer.alert(Template("addoninfotpl", {item: item, addon: addon}), {
-                    btn: [__('OK'), __('Donate'), __('Feedback'), __('Document')],
-                    title: __('Detail'),
-                    area: ['450px', '490px'],
-                    btn2: function () {
-                        //打赏
-                        Layer.open({
-                            content: Template("paytpl", {payimg: item.donateimage}),
-                            shade: 0.8,
-                            area: ['800px', '600px'],
-                            skin: 'layui-layer-msg layui-layer-pay',
-                            title: false,
-                            closeBtn: true,
-                            btn: false,
-                            resize: false,
-                        });
-                    },
-                    btn3: function () {
-                        return false;
-                    },
-                    btn4: function () {
-                        return false;
-                    },
-                    success: function (layero, index) {
-                        $(".layui-layer-btn2", layero).attr("href", "http://forum.fastadmin.net/t/bug?ref=addon&name=" + item.name).attr("target", "_blank");
-                        $(".layui-layer-btn3", layero).attr("href", "http://www.fastadmin.net/store/" + item.name + ".html?ref=addon").attr("target", "_blank");
-                    }
-                });
-            });
-
             // 离线安装
             require(['upload'], function (Upload) {
                 Upload.api.plupload("#plupload-addon", function (data, ret) {
@@ -224,22 +254,9 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
                     Layer.closeAll();
                     Config['addons'][data.addon.name] = ret.data.addon;
                     Layer.alert(__('Online installed tips'), {
-                        btn: [__('OK'), __('Donate')],
+                        btn: [__('OK')],
                         title: __('Warning'),
-                        icon: 1,
-                        btn2: function () {
-                            //打赏
-                            Layer.open({
-                                content: Template("paytpl", {payimg: $(that).data("donateimage")}),
-                                shade: 0.8,
-                                area: ['800px', '600px'],
-                                skin: 'layui-layer-msg layui-layer-pay',
-                                title: false,
-                                closeBtn: true,
-                                btn: false,
-                                resize: false,
-                            });
-                        }
+                        icon: 1
                     });
                     $('.btn-refresh').trigger('click');
                     Fast.api.refreshmenu();
@@ -261,17 +278,12 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
                             }
                         });
                     } else if (ret && ret.code === -2) {
-                        //跳转支付
-                        Layer.alert(__('Pay click tips'), {
-                            btn: [__('Pay now'), __('Cancel')],
-                            icon: 0,
-                            success: function (layero) {
-                                $(".layui-layer-btn0", layero).attr("href", ret.data.payurl).attr("target", "_blank");
+                        Fast.api.open(ret.data.payurl, __('Pay now'), {
+                            area: ["650px", "700px"],
+                            end: function () {
+                                Layer.alert(__('Pay tips'));
                             }
-                        }, function () {
-                            Layer.alert(__('Pay new window tips'), {icon: 0});
                         });
-
                     } else if (ret && ret.code === -3) {
                         //插件目录发现影响全局的文件
                         Layer.open({
@@ -408,7 +420,11 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
 
             // 点击卸载
             $(document).on("click", ".btn-uninstall", function () {
-                var name = $(this).closest(".operate").data("name");
+                var name = $(this).closest(".operate").data('name');
+                if (Config['addons'][name].state == 1) {
+                    Layer.alert(__('Please disable addon first'), {icon: 7});
+                    return false;
+                }
                 Layer.confirm(__('Uninstall tips'), function () {
                     uninstall(name, false);
                 });
@@ -422,18 +438,18 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
 
             // 点击启用/禁用
             $(document).on("click", ".btn-enable,.btn-disable", function () {
-                var name = $(this).closest(".operate").data("name");
+                var name = $(this).data("name");
                 var action = $(this).data("action");
                 operate(name, action, false);
             });
 
             // 点击升级
             $(document).on("click", ".btn-upgrade", function () {
-                if ($(this).closest(".operate").find("a.btn-disable").size() > 0) {
+                var name = $(this).closest(".operate").data('name');
+                if (Config['addons'][name].state == 1) {
                     Layer.alert(__('Please disable addon first'), {icon: 7});
                     return false;
                 }
-                var name = $(this).closest(".operate").data("name");
                 var version = $(this).data("version");
 
                 Layer.confirm(__('Upgrade tips'), function () {
@@ -444,6 +460,21 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
             $(document).on("click", ".operate .btn-group .dropdown-toggle", function () {
                 $(this).closest(".btn-group").toggleClass("dropup", $(document).height() - $(this).offset().top <= 200);
             });
+
+            $(document).on("click", ".view-screenshots", function () {
+                var row = Table.api.getrowbyindex(table, parseInt($(this).data("index")));
+                var data = [];
+                $.each(row.screenshots, function (i, j) {
+                    data.push({
+                        "src": "http://www.fh.com" + j
+                    });
+                });
+                var json = {
+                    "title": row.title,
+                    "data": data
+                };
+                top.Layer.photos(top.JSON.parse(JSON.stringify({photos: json})));
+            });
         },
         add: function () {
             Controller.api.bindevent();
@@ -452,6 +483,39 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
             Controller.api.bindevent();
         },
         api: {
+            formatter: {
+                title: function (value, row, index) {
+                    var title = '<a class="title" href="' + row.url + '" data-toggle="tooltip" title="' + __('View addon home page') + '" target="_blank">' + value + '</a>';
+                    if (row.screenshots.length > 0) {
+                        title += ' <a href="javascript:;" data-index="' + index + '" class="view-screenshots text-success" title="' + __('View addon screenshots') + '" data-toggle="tooltip"><i class="fa fa-image"></i></a>';
+                    }
+                    return title;
+                },
+                operate: function (value, row, index) {
+                    return Template("operatetpl", {item: row, index: index});
+                },
+                toggle: function (value, row, index) {
+                    if (!row.addon) {
+                        return '';
+                    }
+                    return '<a href="javascript:;" data-toggle="tooltip" title="' + __('Click to toggle status') + '" class="btn-' + (row.addon.state == 1 ? "disable" : "enable") + '" data-action="' + (row.addon.state == 1 ? "disable" : "enable") + '" data-name="' + row.name + '"><i class="fa ' + (row.addon.state == 0 ? 'fa-toggle-on fa-rotate-180 text-gray' : 'fa-toggle-on text-success') + ' fa-2x"></i></a>';
+                },
+                author: function (value, row, index) {
+                    return '<a href="https://wpa.qq.com/msgrd?v=3&uin=' + row.qq + '&site=fastadmin.net&menu=yes" target="_blank" data-toggle="tooltip" title="'+__('Click to contact developer')+'" class="text-primary">' + value + '</a>';
+                },
+                price: function (value, row, index) {
+                    return parseFloat(value) == 0 ? '<span class="text-success">' + __('Free') + '</span>' : '<span class="text-danger">¥' + value + '</span>';
+                },
+                downloads: function (value, row, index) {
+                    return value;
+                },
+                version: function (value, row, index) {
+                    return row.addon && row.addon.version != row.version ? '<span class="releasetips" data-toggle="tooltip" title="' + __('New version') + ':' + row.version + '">' + row.addon.version + '<i></i></span>' : row.version;
+                },
+                home: function (value, row, index) {
+                    return row.addon ? '<a href="' + row.addon.url + '" data-toggle="tooltip" title="' + __('View addon index page') + '" target="_blank"><i class="fa fa-home text-primary"></i></a>' : '<a href="javascript:;"><i class="fa fa-home text-gray"></i></a>';
+                },
+            },
             bindevent: function () {
                 Form.api.bindevent($("form[role=form]"));
             },

+ 10 - 1
public/assets/js/backend/auth/admin.js

@@ -15,6 +15,15 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
 
             var table = $("#table");
 
+            //在表格内容渲染完成后回调的事件
+            table.on('post-body.bs.table', function (e, json) {
+                $("tbody tr[data-index]", this).each(function () {
+                    if (parseInt($("td:eq(1)", this).text()) == Config.admin.id) {
+                        $("input[type=checkbox]", this).prop("disabled", true);
+                    }
+                });
+            });
+
             // 初始化表格
             table.bootstrapTable({
                 url: $.fn.bootstrapTable.defaults.extend.index_url,
@@ -27,7 +36,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
                         {field: 'groups_text', title: __('Group'), operate:false, formatter: Table.api.formatter.label},
                         {field: 'email', title: __('Email')},
                         {field: 'status', title: __("Status"), formatter: Table.api.formatter.status},
-                        {field: 'logintime', title: __('Login time'), formatter: Table.api.formatter.datetime},
+                        {field: 'logintime', title: __('Login time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true},
                         {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: function (value, row, index) {
                                 if(row.id == Config.admin.id){
                                     return '';

+ 1 - 1
public/assets/js/backend/auth/adminlog.js

@@ -27,7 +27,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
                         {field: 'url', title: __('Url'), align: 'left', formatter: Table.api.formatter.url},
                         {field: 'ip', title: __('IP'), events: Table.api.events.ip, formatter: Table.api.formatter.search},
                         {field: 'browser', title: __('Browser'), operate: false, formatter: Controller.api.formatter.browser},
-                        {field: 'createtime', title: __('Create time'), formatter: Table.api.formatter.datetime, operate: 'BETWEEN', type: 'datetime', addclass: 'datetimepicker', data: 'data-date-format="YYYY-MM-DD HH:mm:ss"'},
+                        {field: 'createtime', title: __('Create time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true},
                         {field: 'operate', title: __('Operate'), table: table,
                             events: Table.api.events.operate,
                             buttons: [{

+ 9 - 0
public/assets/js/backend/auth/group.js

@@ -30,6 +30,15 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'jstree'], function (
 
             var table = $("#table");
 
+            //在表格内容渲染完成后回调的事件
+            table.on('post-body.bs.table', function (e, json) {
+                $("tbody tr[data-index]", this).each(function () {
+                    if (Config.admin.group_ids.indexOf(parseInt(parseInt($("td:eq(1)", this).text()))) > -1) {
+                        $("input[type=checkbox]", this).prop("disabled", true);
+                    }
+                });
+            });
+
             // 初始化表格
             table.bootstrapTable({
                 url: $.fn.bootstrapTable.defaults.extend.index_url,

+ 33 - 18
public/assets/js/backend/auth/rule.js

@@ -23,16 +23,32 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
                 escape: false,
                 columns: [
                     [
-                        {field: 'state', checkbox: true, },
+                        {field: 'state', checkbox: true,},
                         {field: 'id', title: 'ID'},
                         {field: 'title', title: __('Title'), align: 'left', formatter: Controller.api.formatter.title},
                         {field: 'icon', title: __('Icon'), formatter: Controller.api.formatter.icon},
                         {field: 'name', title: __('Name'), align: 'left', formatter: Controller.api.formatter.name},
                         {field: 'weigh', title: __('Weigh')},
                         {field: 'status', title: __('Status'), formatter: Table.api.formatter.status},
-                        {field: 'ismenu', title: __('Ismenu'), align: 'center', formatter: Controller.api.formatter.menu},
-                        {field: 'id', title: '<a href="javascript:;" class="btn btn-success btn-xs btn-toggle"><i class="fa fa-chevron-up"></i></a>', operate: false, formatter: Controller.api.formatter.subnode},
-                        {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
+                        {
+                            field: 'ismenu',
+                            title: __('Ismenu'),
+                            align: 'center',
+                            formatter: Controller.api.formatter.menu
+                        },
+                        {
+                            field: 'id',
+                            title: '<a href="javascript:;" class="btn btn-success btn-xs btn-toggle"><i class="fa fa-chevron-up"></i></a>',
+                            operate: false,
+                            formatter: Controller.api.formatter.subnode
+                        },
+                        {
+                            field: 'operate',
+                            title: __('Operate'),
+                            table: table,
+                            events: Table.api.events.operate,
+                            formatter: Table.api.formatter.operate
+                        }
                     ]
                 ],
                 pagination: false,
@@ -102,15 +118,15 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
                     return !row.ismenu || row.status == 'hidden' ? "<span class='text-muted'>" + value + "</span>" : value;
                 },
                 menu: function (value, row, index) {
-                    return "<a href='javascript:;' class='btn btn-" + (value ? "info" : "default") + " btn-xs btn-change' data-id='"
-                            + row.id + "' data-params='ismenu=" + (value ? 0 : 1) + "'>" + (value ? __('Yes') : __('No')) + "</a>";
+                    return "<a href='javascript:;' data-toggle='tooltip' title='" + __('Toggle menu visible') + "' class='btn btn-" + (value ? "info" : "default") + " btn-xs btn-change' data-id='"
+                        + row.id + "' data-params='ismenu=" + (value ? 0 : 1) + "'>" + (value ? __('Yes') : __('No')) + "</a>";
                 },
                 icon: function (value, row, index) {
                     return '<span class="' + (!row.ismenu || row.status == 'hidden' ? 'text-muted' : '') + '"><i class="' + value + '"></i></span>';
                 },
                 subnode: function (value, row, index) {
-                    return '<a href="javascript:;" data-id="' + row.id + '" data-pid="' + row.pid + '" class="btn btn-xs '
-                            + (row.haschild == 1 || row.ismenu == 1 ? 'btn-success' : 'btn-default disabled') + ' btn-node-sub"><i class="fa fa-sitemap"></i></a>';
+                    return '<a href="javascript:;" data-toggle="tooltip" title="' + __('Toggle sub menu') + '" data-id="' + row.id + '" data-pid="' + row.pid + '" class="btn btn-xs '
+                        + (row.haschild == 1 || row.ismenu == 1 ? 'btn-success' : 'btn-default disabled') + ' btn-node-sub"><i class="fa fa-sitemap"></i></a>';
                 }
             },
             bindevent: function () {
@@ -121,6 +137,13 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
                 $("input[name='row[ismenu]']:checked").trigger("click");
 
                 var iconlist = [];
+                var iconfunc = function () {
+                    Layer.open({
+                        type: 1,
+                        area: ['99%', '98%'], //宽高
+                        content: Template('chooseicontpl', {iconlist: iconlist})
+                    });
+                };
                 Form.api.bindevent($("form[role=form]"), function (data) {
                     Fast.api.refreshmenu();
                 });
@@ -132,18 +155,10 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
                             while ((result = exp.exec(ret)) != null) {
                                 iconlist.push(result[1]);
                             }
-                            Layer.open({
-                                type: 1,
-                                area: ['460px', '300px'], //宽高
-                                content: Template('chooseicontpl', {iconlist: iconlist})
-                            });
+                            iconfunc();
                         });
                     } else {
-                        Layer.open({
-                            type: 1,
-                            area: ['460px', '300px'], //宽高
-                            content: Template('chooseicontpl', {iconlist: iconlist})
-                        });
+                        iconfunc();
                     }
                 });
                 $(document).on('click', '#chooseicon ul li', function () {

+ 33 - 16
public/assets/js/backend/general/attachment.js

@@ -22,18 +22,31 @@ define(['jquery', 'bootstrap', 'backend', 'form', 'table'], function ($, undefin
                 sortName: 'id',
                 columns: [
                     [
-                        {field: 'state', checkbox: true, },
+                        {field: 'state', checkbox: true,},
                         {field: 'id', title: __('Id')},
-                        {field: 'url', title: __('Preview'), formatter: Controller.api.formatter.thumb},
+                        {field: 'url', title: __('Preview'), formatter: Controller.api.formatter.thumb, operate: false},
                         {field: 'url', title: __('Url'), formatter: Controller.api.formatter.url},
-                        {field: 'imagewidth', title: __('Imagewidth')},
-                        {field: 'imageheight', title: __('Imageheight')},
-                        {field: 'imagetype', title: __('Imagetype')},
-                        {field: 'storage', title: __('Storage')},
-                        {field: 'filesize', title: __('Filesize')},
-                        {field: 'mimetype', title: __('Mimetype')},
-                        {field: 'createtime', title: __('Createtime'), formatter: Table.api.formatter.datetime},
-                        {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
+                        {field: 'imagewidth', title: __('Imagewidth'), sortable: true},
+                        {field: 'imageheight', title: __('Imageheight'), sortable: true},
+                        {field: 'imagetype', title: __('Imagetype'), formatter:Table.api.formatter.search},
+                        {field: 'storage', title: __('Storage'), formatter: Table.api.formatter.search},
+                        {field: 'filesize', title: __('Filesize'), operate: 'BETWEEN', sortable: true},
+                        {field: 'mimetype', title: __('Mimetype'), formatter:Table.api.formatter.search},
+                        {
+                            field: 'createtime',
+                            title: __('Createtime'),
+                            formatter: Table.api.formatter.datetime,
+                            operate: 'RANGE',
+                            addclass: 'datetimerange',
+                            sortable: true
+                        },
+                        {
+                            field: 'operate',
+                            title: __('Operate'),
+                            table: table,
+                            events: Table.api.events.operate,
+                            formatter: Table.api.formatter.operate
+                        }
                     ]
                 ],
             });
@@ -58,17 +71,20 @@ define(['jquery', 'bootstrap', 'backend', 'form', 'table'], function ($, undefin
                 sortName: 'id',
                 columns: [
                     [
-                        {field: 'state', checkbox: true, },
+                        {field: 'state', checkbox: true,},
                         {field: 'id', title: __('Id')},
                         {field: 'url', title: __('Preview'), formatter: Controller.api.formatter.thumb},
                         {field: 'imagewidth', title: __('Imagewidth')},
                         {field: 'imageheight', title: __('Imageheight')},
-                        {field: 'mimetype', title: __('Mimetype'), operate: 'LIKE %...%',
+                        {
+                            field: 'mimetype', title: __('Mimetype'), operate: 'LIKE %...%',
                             process: function (value, arg) {
                                 return value.replace(/\*/g, '%');
-                            }},
-                        {field: 'createtime', title: __('Createtime'), formatter: Table.api.formatter.datetime},
-                        {field: 'operate', title: __('Operate'), events: {
+                            }
+                        },
+                        {field: 'createtime', title: __('Createtime'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true},
+                        {
+                            field: 'operate', title: __('Operate'), events: {
                                 'click .btn-chooseone': function (e, value, row, index) {
                                     var multiple = Backend.api.query('multiple');
                                     multiple = multiple == 'true' ? true : false;
@@ -76,7 +92,8 @@ define(['jquery', 'bootstrap', 'backend', 'form', 'table'], function ($, undefin
                                 },
                             }, formatter: function () {
                                 return '<a href="javascript:;" class="btn btn-danger btn-chooseone btn-xs"><i class="fa fa-check"></i> ' + __('Choose') + '</a>';
-                            }}
+                            }
+                        }
                     ]
                 ]
             });

+ 15 - 2
public/assets/js/backend/general/config.js

@@ -29,7 +29,13 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
                         {field: 'intro', title: __('Intro')},
                         {field: 'group', title: __('Group')},
                         {field: 'type', title: __('Type')},
-                        {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
+                        {
+                            field: 'operate',
+                            title: __('Operate'),
+                            table: table,
+                            events: Table.api.events.operate,
+                            formatter: Table.api.formatter.operate
+                        }
                     ]
                 ]
             });
@@ -58,7 +64,14 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
             //添加向发件人发送测试邮件按钮和方法
             $('input[name="row[mail_from]"]').parent().next().append('<a class="btn btn-info testmail">' + __('Send a test message') + '</a>');
             $(document).on("click", ".testmail", function () {
-                Backend.api.ajax({url: "general/config/emailtest", data: {receiver: $('input[name="row[mail_from]"]').val()}});
+                var that = this;
+                Layer.prompt({title: __('Please input your email'), formType: 0}, function (value, index) {
+                    Backend.api.ajax({
+                        url: "general/config/emailtest?receiver=" + value,
+                        data: $(that).closest("form").serialize()
+                    });
+                });
+
             });
         },
         add: function () {

+ 2 - 2
public/assets/js/backend/general/profile.js

@@ -27,8 +27,8 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'upload'], function (
                         {field: 'id', title: 'ID'},
                         {field: 'title', title: __('Title')},
                         {field: 'url', title: __('Url'), align: 'left', formatter: Table.api.formatter.url},
-                        {field: 'ip', title: __('ip')},
-                        {field: 'createtime', title: __('Createtime'), formatter: Table.api.formatter.datetime},
+                        {field: 'ip', title: __('ip'), formatter:Table.api.formatter.search},
+                        {field: 'createtime', title: __('Createtime'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true},
                     ]
                 ],
                 commonSearch: false

+ 2 - 2
public/assets/js/backend/user/group.js

@@ -41,8 +41,8 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'jstree'], function (
                         {checkbox: true},
                         {field: 'id', title: __('Id')},
                         {field: 'name', title: __('Name')},
-                        {field: 'createtime', title: __('Createtime'), formatter: Table.api.formatter.datetime},
-                        {field: 'updatetime', title: __('Updatetime'), formatter: Table.api.formatter.datetime},
+                        {field: 'createtime', title: __('Createtime'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true},
+                        {field: 'updatetime', title: __('Updatetime'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true},
                         {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}
                     ]

+ 2 - 2
public/assets/js/backend/user/rule.js

@@ -31,8 +31,8 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
                         {field: 'name', title: __('Name'), align: 'left'},
                         {field: 'remark', title: __('Remark')},
                         {field: 'ismenu', title: __('Ismenu'), formatter: Controller.api.formatter.toggle},
-                        {field: 'createtime', title: __('Createtime'), formatter: Table.api.formatter.datetime, visible: false},
-                        {field: 'updatetime', title: __('Updatetime'), formatter: Table.api.formatter.datetime, visible: false},
+                        {field: 'createtime', title: __('Createtime'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true, visible: false},
+                        {field: 'updatetime', title: __('Updatetime'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true, visible: false},
                         {field: 'weigh', title: __('Weigh')},
                         {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}

+ 36 - 25
public/assets/js/fast.js

@@ -105,7 +105,7 @@ define(['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, undefine
                 }
                 name = name.replace(/[\[\]]/g, "\\$&");
                 var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
-                        results = regex.exec(url);
+                    results = regex.exec(url);
                 if (!results)
                     return null;
                 if (!results[2])
@@ -134,31 +134,42 @@ define(['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, undefine
                         $(layero).data("callback", that.callback);
                         //$(layero).removeClass("layui-layer-border");
                         Layer.setTop(layero);
-                        var frame = Layer.getChildFrame('html', index);
-                        var layerfooter = frame.find(".layer-footer");
-                        Fast.api.layerfooter(layero, index, that);
+                        try {
+                            var frame = Layer.getChildFrame('html', index);
+                            var layerfooter = frame.find(".layer-footer");
+                            Fast.api.layerfooter(layero, index, that);
 
-                        //绑定事件
-                        if (layerfooter.size() > 0) {
-                            // 监听窗口内的元素及属性变化
-                            // Firefox和Chrome早期版本中带有前缀
-                            var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
-                            if (MutationObserver) {
-                                // 选择目标节点
-                                var target = layerfooter[0];
-                                // 创建观察者对象
-                                var observer = new MutationObserver(function (mutations) {
-                                    Fast.api.layerfooter(layero, index, that);
-                                    mutations.forEach(function (mutation) {
+                            //绑定事件
+                            if (layerfooter.size() > 0) {
+                                // 监听窗口内的元素及属性变化
+                                // Firefox和Chrome早期版本中带有前缀
+                                var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
+                                if (MutationObserver) {
+                                    // 选择目标节点
+                                    var target = layerfooter[0];
+                                    // 创建观察者对象
+                                    var observer = new MutationObserver(function (mutations) {
+                                        Fast.api.layerfooter(layero, index, that);
+                                        mutations.forEach(function (mutation) {
+                                        });
                                     });
-                                });
-                                // 配置观察选项:
-                                var config = {attributes: true, childList: true, characterData: true, subtree: true}
-                                // 传入目标节点和观察选项
-                                observer.observe(target, config);
-                                // 随后,你还可以停止观察
-                                // observer.disconnect();
+                                    // 配置观察选项:
+                                    var config = {attributes: true, childList: true, characterData: true, subtree: true}
+                                    // 传入目标节点和观察选项
+                                    observer.observe(target, config);
+                                    // 随后,你还可以停止观察
+                                    // observer.disconnect();
+                                }
                             }
+                        } catch (e) {
+
+                        }
+                        if ($(layero).height() > $(window).height()) {
+                            //当弹出窗口大于浏览器可视高度时,重定位
+                            Layer.style(index, {
+                                top: 0,
+                                height: $(window).height()
+                            });
                         }
                     }
                 }, options ? options : {});
@@ -234,8 +245,8 @@ define(['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, undefine
         },
         lang: function () {
             var args = arguments,
-                    string = args[0],
-                    i = 1;
+                string = args[0],
+                i = 1;
             string = string.toLowerCase();
             //string = typeof Lang[string] != 'undefined' ? Lang[string] : string;
             if (typeof Lang[string] != 'undefined') {

+ 3 - 1
public/assets/js/frontend.js

@@ -49,7 +49,9 @@ define(['fast', 'template'], function (Fast, Template) {
 
                 return false;
             });
-
+            //tooltip和popover
+            $('body').tooltip({selector: '[data-toggle="tooltip"]'});
+            $('body').tooltip({selector: '[data-toggle="popover"]'});
         }
     };
     Frontend.api = $.extend(Fast.api, Frontend.api);

+ 117 - 87
public/assets/js/require-backend.min.js

@@ -759,7 +759,7 @@ define('fast',['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, u
                 }
                 name = name.replace(/[\[\]]/g, "\\$&");
                 var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
-                        results = regex.exec(url);
+                    results = regex.exec(url);
                 if (!results)
                     return null;
                 if (!results[2])
@@ -788,31 +788,42 @@ define('fast',['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, u
                         $(layero).data("callback", that.callback);
                         //$(layero).removeClass("layui-layer-border");
                         Layer.setTop(layero);
-                        var frame = Layer.getChildFrame('html', index);
-                        var layerfooter = frame.find(".layer-footer");
-                        Fast.api.layerfooter(layero, index, that);
-
-                        //绑定事件
-                        if (layerfooter.size() > 0) {
-                            // 监听窗口内的元素及属性变化
-                            // Firefox和Chrome早期版本中带有前缀
-                            var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
-                            if (MutationObserver) {
-                                // 选择目标节点
-                                var target = layerfooter[0];
-                                // 创建观察者对象
-                                var observer = new MutationObserver(function (mutations) {
-                                    Fast.api.layerfooter(layero, index, that);
-                                    mutations.forEach(function (mutation) {
+                        try {
+                            var frame = Layer.getChildFrame('html', index);
+                            var layerfooter = frame.find(".layer-footer");
+                            Fast.api.layerfooter(layero, index, that);
+
+                            //绑定事件
+                            if (layerfooter.size() > 0) {
+                                // 监听窗口内的元素及属性变化
+                                // Firefox和Chrome早期版本中带有前缀
+                                var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
+                                if (MutationObserver) {
+                                    // 选择目标节点
+                                    var target = layerfooter[0];
+                                    // 创建观察者对象
+                                    var observer = new MutationObserver(function (mutations) {
+                                        Fast.api.layerfooter(layero, index, that);
+                                        mutations.forEach(function (mutation) {
+                                        });
                                     });
-                                });
-                                // 配置观察选项:
-                                var config = {attributes: true, childList: true, characterData: true, subtree: true}
-                                // 传入目标节点和观察选项
-                                observer.observe(target, config);
-                                // 随后,你还可以停止观察
-                                // observer.disconnect();
+                                    // 配置观察选项:
+                                    var config = {attributes: true, childList: true, characterData: true, subtree: true}
+                                    // 传入目标节点和观察选项
+                                    observer.observe(target, config);
+                                    // 随后,你还可以停止观察
+                                    // observer.disconnect();
+                                }
                             }
+                        } catch (e) {
+
+                        }
+                        if ($(layero).height() > $(window).height()) {
+                            //当弹出窗口大于浏览器可视高度时,重定位
+                            Layer.style(index, {
+                                top: 0,
+                                height: $(window).height()
+                            });
                         }
                     }
                 }, options ? options : {});
@@ -888,8 +899,8 @@ define('fast',['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, u
         },
         lang: function () {
             var args = arguments,
-                    string = args[0],
-                    i = 1;
+                string = args[0],
+                i = 1;
             string = string.toLowerCase();
             //string = typeof Lang[string] != 'undefined' ? Lang[string] : string;
             if (typeof Lang[string] != 'undefined') {
@@ -5318,20 +5329,8 @@ define('backend',['fast', 'template', 'moment'], function (Fast, Template, Momen
             },
             refreshmenu: function () {
                 top.window.$(".sidebar-menu").trigger("refresh");
-            }
-        },
-        init: function () {
-            //公共代码
-            //添加ios-fix兼容iOS下的iframe
-            if (/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream) {
-                $("html").addClass("ios-fix");
-            }
-            //配置Toastr的参数
-            Toastr.options.positionClass = Config.controllername === 'index' ? "toast-top-right-index" : "toast-top-right";
-            //点击包含.btn-dialog的元素时弹出dialog
-            $(document).on('click', '.btn-dialog,.dialogit', function (e) {
-                var that = this;
-                var options = $.extend({}, $(that).data() || {});
+            },
+            gettablecolumnbutton: function(options){
                 if (typeof options.tableId !== 'undefined' && typeof options.fieldIndex !== 'undefined' && typeof options.buttonIndex !== 'undefined') {
                     var tableOptions = $("#" + options.tableId).bootstrapTable('getOptions');
                     if (tableOptions) {
@@ -5348,20 +5347,38 @@ define('backend',['fast', 'template', 'moment'], function (Fast, Template, Momen
                             }
                         });
                         if (columnObj) {
-                            var button = columnObj['buttons'][options.buttonIndex];
-                            if (button && typeof button.callback === 'function') {
-                                options.callback = button.callback;
-                            }
+                            return columnObj['buttons'][options.buttonIndex];
                         }
                     }
                 }
+                return null;
+            },
+        },
+        init: function () {
+            //公共代码
+            //添加ios-fix兼容iOS下的iframe
+            if (/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream) {
+                $("html").addClass("ios-fix");
+            }
+            //配置Toastr的参数
+            Toastr.options.positionClass = Config.controllername === 'index' ? "toast-top-right-index" : "toast-top-right";
+            //点击包含.btn-dialog的元素时弹出dialog
+            $(document).on('click', '.btn-dialog,.dialogit', function (e) {
+                var that = this;
+                var options = $.extend({}, $(that).data() || {});
+                var url = Backend.api.replaceids(that, $(that).attr('href'));
+                var title = $(that).attr("title") || $(that).data("title") || $(that).data('original-title');
+                var button = Backend.api.gettablecolumnbutton(options);
+                if (button && typeof button.callback === 'function') {
+                    options.callback = button.callback;
+                }
                 if (typeof options.confirm !== 'undefined') {
                     Layer.confirm(options.confirm, function (index) {
-                        Backend.api.open(Backend.api.replaceids(that, $(that).attr('href')), $(that).attr('title'), options);
+                        Backend.api.open(url, title, options);
                         Layer.close(index);
                     });
                 } else {
-                    Backend.api.open(Backend.api.replaceids(that, $(that).attr('href')), $(that).attr('title'), options);
+                    Backend.api.open(url, title, options);
                 }
                 return false;
             });
@@ -5369,15 +5386,16 @@ define('backend',['fast', 'template', 'moment'], function (Fast, Template, Momen
             $(document).on('click', '.btn-addtabs,.addtabsit', function (e) {
                 var that = this;
                 var options = $.extend({}, $(that).data() || {});
+                var url = Backend.api.replaceids(that, $(that).attr('href'));
+                var title = $(that).attr("title") || $(that).data("title") || $(that).data('original-title');
                 if (typeof options.confirm !== 'undefined') {
                     Layer.confirm(options.confirm, function (index) {
-                        Backend.api.addtabs(Backend.api.replaceids(that, $(that).attr('href')), $(that).attr("title"));
+                        Backend.api.addtabs(url, title);
                         Layer.close(index);
                     });
                 } else {
-                    Backend.api.addtabs(Backend.api.replaceids(that, $(that).attr('href')), $(that).attr("title"));
+                    Backend.api.addtabs(url, title);
                 }
-
                 return false;
             });
             //点击包含.btn-ajax的元素时发送Ajax请求
@@ -5392,30 +5410,13 @@ define('backend',['fast', 'template', 'moment'], function (Fast, Template, Momen
                 var error = typeof options.error === 'function' ? options.error : null;
                 delete options.success;
                 delete options.error;
-                if (typeof options.tableId !== 'undefined' && typeof options.fieldIndex !== 'undefined' && typeof options.buttonIndex !== 'undefined') {
-                    var tableOptions = $("#" + options.tableId).bootstrapTable('getOptions');
-                    if (tableOptions) {
-                        var columnObj = null;
-                        $.each(tableOptions.columns, function (i, columns) {
-                            $.each(columns, function (j, column) {
-                                if (typeof column.fieldIndex !== 'undefined' && column.fieldIndex === options.fieldIndex) {
-                                    columnObj = column;
-                                    return false;
-                                }
-                            });
-                            if (columnObj) {
-                                return false;
-                            }
-                        });
-                        if (columnObj) {
-                            var button = columnObj['buttons'][options.buttonIndex];
-                            if (button && typeof button.success === 'function') {
-                                success = button.success;
-                            }
-                            if (button && typeof button.error === 'function') {
-                                error = button.error;
-                            }
-                        }
+                var button = Backend.api.gettablecolumnbutton(options);
+                if (button) {
+                    if (typeof button.success === 'function') {
+                        success = button.success;
+                    }
+                    if (typeof button.error === 'function') {
+                        error = button.error;
                     }
                 }
                 //如果未设备成功的回调,设定了自动刷新的情况下自动进行刷新
@@ -5440,6 +5441,19 @@ define('backend',['fast', 'template', 'moment'], function (Fast, Template, Momen
             if ($(".layer-footer").size() > 0 && self === top) {
                 $(".layer-footer").show();
             }
+            //优化在多个弹窗下点击不能切换的操作体验
+            if (Fast.api.query("dialog") == "1" && self != top && self.frameElement && self.frameElement.tagName == "IFRAME") {
+                $(window).on('click', function () {
+                    var layero = self.frameElement.parentNode.parentElement;
+                    if (parent.Layer.zIndex != parseInt(parent.window.$(layero).css("z-index"))) {
+                        parent.window.$(layero).trigger("mousedown");
+                        parent.Layer.zIndex = parseInt(parent.window.$(layero).css("z-index"));
+                    }
+                });
+            }
+            //tooltip和popover
+            $('body').tooltip({selector: '[data-toggle="tooltip"]'});
+            $('body').tooltip({selector: '[data-toggle="popover"]'});
         }
     };
     Backend.api = $.extend(Fast.api, Backend.api);
@@ -8434,10 +8448,10 @@ define('form',['jquery', 'bootstrap', 'upload', 'validator'], function ($, undef
                     },
                     valid: function (ret) {
                         var that = this, submitBtn = $(".layer-footer [type=submit]", form);
-                        that.holdSubmit();
-                        $(".layer-footer [type=submit]", form).addClass("disabled");
+                        that.holdSubmit(true);
+                        submitBtn.addClass("disabled");
                         //验证通过提交表单
-                        Form.api.submit($(ret), function (data, ret) {
+                        var submitResult = Form.api.submit($(ret), function (data, ret) {
                             that.holdSubmit(false);
                             submitBtn.removeClass("disabled");
                             if (false === $(this).triggerHandler("success.form", [data, ret])) {
@@ -8467,6 +8481,11 @@ define('form',['jquery', 'bootstrap', 'upload', 'validator'], function ($, undef
                                 }
                             }
                         }, submit);
+                        //如果提交失败则释放锁定
+                        if (!submitResult) {
+                            that.holdSubmit(false);
+                            submitBtn.removeClass("disabled");
+                        }
                         return false;
                     }
                 }, form.data("validator-options") || {}));
@@ -8650,7 +8669,6 @@ define('form',['jquery', 'bootstrap', 'upload', 'validator'], function ($, undef
                             var textarea = $("textarea[name='" + name + "']", form);
                             var container = textarea.closest("dl");
                             var template = container.data("template");
-                            console.log(name, container);
                             $.each($("input,select", container).serializeArray(), function (i, j) {
                                 var reg = /\[(\w+)\]\[(\w+)\]$/g;
                                 var match = reg.exec(j.name);
@@ -8739,10 +8757,12 @@ define('form',['jquery', 'bootstrap', 'upload', 'validator'], function ($, undef
         },
         api: {
             submit: function (form, success, error, submit) {
-                if (form.size() === 0)
-                    return Toastr.error("表单未初始化完成,无法提交");
+                if (form.size() === 0) {
+                    Toastr.error("表单未初始化完成,无法提交");
+                    return false;
+                }
                 if (typeof submit === 'function') {
-                    if (false === submit.call(form)) {
+                    if (false === submit.call(form, success, error)) {
                         return false;
                     }
                 }
@@ -8802,7 +8822,7 @@ define('form',['jquery', 'bootstrap', 'upload', 'validator'], function ($, undef
                         }
                     }
                 });
-                return false;
+                return true;
             },
             bindevent: function (form, success, error, submit) {
 
@@ -9326,6 +9346,7 @@ define('table',['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstr
             pageList: [10, 25, 50, 'All'],
             pagination: true,
             clickToSelect: true, //是否启用点击选中
+            dblClickToEdit: true, //是否启用双击编辑
             singleSelect: false, //是否启用单选
             showRefresh: false,
             locale: 'zh-CN',
@@ -9420,10 +9441,12 @@ define('table',['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstr
                 table.on('refresh.bs.table', function (e, settings, data) {
                     $(Table.config.refreshbtn, toolbar).find(".fa").addClass("fa-spin");
                 });
-                //当双击单元格时
-                table.on('dbl-click-row.bs.table', function (e, row, element, field) {
-                    $(Table.config.editonebtn, element).trigger("click");
-                });
+                if (options.dblClickToEdit) {
+                    //当双击单元格时
+                    table.on('dbl-click-row.bs.table', function (e, row, element, field) {
+                        $(Table.config.editonebtn, element).trigger("click");
+                    });
+                }
                 //当内容渲染完成后
                 table.on('post-body.bs.table', function (e, settings, json, xhr) {
                     $(Table.config.refreshbtn, toolbar).find(".fa").removeClass("fa-spin");
@@ -9706,7 +9729,7 @@ define('table',['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstr
                     return '<div class="input-group input-group-sm" style="width:250px;margin:0 auto;"><input type="text" class="form-control input-sm" value="' + value + '"><span class="input-group-btn input-group-sm"><a href="' + value + '" target="_blank" class="btn btn-default btn-sm"><i class="fa fa-link"></i></a></span></div>';
                 },
                 search: function (value, row, index) {
-                    return '<a href="javascript:;" class="searchit" data-field="' + this.field + '" data-value="' + value + '">' + value + '</a>';
+                    return '<a href="javascript:;" class="searchit" data-toggle="tooltip" title="' + __('Click to search %s', value) + '" data-field="' + this.field + '" data-value="' + value + '">' + value + '</a>';
                 },
                 addtabs: function (value, row, index) {
                     var url = Table.api.replaceurl(this.url, row, this.table);
@@ -9759,6 +9782,7 @@ define('table',['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstr
                             name: 'dragsort',
                             icon: 'fa fa-arrows',
                             title: __('Drag to sort'),
+                            extend: 'data-toggle="tooltip"',
                             classname: 'btn btn-xs btn-primary btn-dragsort'
                         });
                     }
@@ -9767,6 +9791,7 @@ define('table',['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstr
                             name: 'edit',
                             icon: 'fa fa-pencil',
                             title: __('Edit'),
+                            extend: 'data-toggle="tooltip"',
                             classname: 'btn btn-xs btn-success btn-editone',
                             url: options.extend.edit_url
                         });
@@ -9776,6 +9801,7 @@ define('table',['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstr
                             name: 'del',
                             icon: 'fa fa-trash',
                             title: __('Del'),
+                            extend: 'data-toggle="tooltip"',
                             classname: 'btn btn-xs btn-danger btn-delone'
                         });
                     }
@@ -10338,7 +10364,11 @@ $.fn.addtabs = function (options) {
             document.title = title;
             if (history.pushState && !$(this).data("pushstate")) {
                 var pushurl = url.indexOf("ref=addtabs") == -1 ? (url + (url.indexOf("?") > -1 ? "&" : "?") + "ref=addtabs") : url;
-                window.history.pushState(state, title, pushurl);
+                try {
+                    window.history.pushState(state, title, pushurl);
+                }catch(e){
+
+                }
             }
             $(this).data("pushstate", null);
             _add.call(this, {

+ 14 - 8
public/assets/js/require-form.js

@@ -39,10 +39,10 @@ define(['jquery', 'bootstrap', 'upload', 'validator'], function ($, undefined, U
                     },
                     valid: function (ret) {
                         var that = this, submitBtn = $(".layer-footer [type=submit]", form);
-                        that.holdSubmit();
-                        $(".layer-footer [type=submit]", form).addClass("disabled");
+                        that.holdSubmit(true);
+                        submitBtn.addClass("disabled");
                         //验证通过提交表单
-                        Form.api.submit($(ret), function (data, ret) {
+                        var submitResult = Form.api.submit($(ret), function (data, ret) {
                             that.holdSubmit(false);
                             submitBtn.removeClass("disabled");
                             if (false === $(this).triggerHandler("success.form", [data, ret])) {
@@ -72,6 +72,11 @@ define(['jquery', 'bootstrap', 'upload', 'validator'], function ($, undefined, U
                                 }
                             }
                         }, submit);
+                        //如果提交失败则释放锁定
+                        if (!submitResult) {
+                            that.holdSubmit(false);
+                            submitBtn.removeClass("disabled");
+                        }
                         return false;
                     }
                 }, form.data("validator-options") || {}));
@@ -255,7 +260,6 @@ define(['jquery', 'bootstrap', 'upload', 'validator'], function ($, undefined, U
                             var textarea = $("textarea[name='" + name + "']", form);
                             var container = textarea.closest("dl");
                             var template = container.data("template");
-                            console.log(name, container);
                             $.each($("input,select", container).serializeArray(), function (i, j) {
                                 var reg = /\[(\w+)\]\[(\w+)\]$/g;
                                 var match = reg.exec(j.name);
@@ -344,10 +348,12 @@ define(['jquery', 'bootstrap', 'upload', 'validator'], function ($, undefined, U
         },
         api: {
             submit: function (form, success, error, submit) {
-                if (form.size() === 0)
-                    return Toastr.error("表单未初始化完成,无法提交");
+                if (form.size() === 0) {
+                    Toastr.error("表单未初始化完成,无法提交");
+                    return false;
+                }
                 if (typeof submit === 'function') {
-                    if (false === submit.call(form)) {
+                    if (false === submit.call(form, success, error)) {
                         return false;
                     }
                 }
@@ -407,7 +413,7 @@ define(['jquery', 'bootstrap', 'upload', 'validator'], function ($, undefined, U
                         }
                     }
                 });
-                return false;
+                return true;
             },
             bindevent: function (form, success, error, submit) {
 

+ 40 - 27
public/assets/js/require-frontend.min.js

@@ -759,7 +759,7 @@ define('fast',['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, u
                 }
                 name = name.replace(/[\[\]]/g, "\\$&");
                 var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
-                        results = regex.exec(url);
+                    results = regex.exec(url);
                 if (!results)
                     return null;
                 if (!results[2])
@@ -788,31 +788,42 @@ define('fast',['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, u
                         $(layero).data("callback", that.callback);
                         //$(layero).removeClass("layui-layer-border");
                         Layer.setTop(layero);
-                        var frame = Layer.getChildFrame('html', index);
-                        var layerfooter = frame.find(".layer-footer");
-                        Fast.api.layerfooter(layero, index, that);
-
-                        //绑定事件
-                        if (layerfooter.size() > 0) {
-                            // 监听窗口内的元素及属性变化
-                            // Firefox和Chrome早期版本中带有前缀
-                            var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
-                            if (MutationObserver) {
-                                // 选择目标节点
-                                var target = layerfooter[0];
-                                // 创建观察者对象
-                                var observer = new MutationObserver(function (mutations) {
-                                    Fast.api.layerfooter(layero, index, that);
-                                    mutations.forEach(function (mutation) {
+                        try {
+                            var frame = Layer.getChildFrame('html', index);
+                            var layerfooter = frame.find(".layer-footer");
+                            Fast.api.layerfooter(layero, index, that);
+
+                            //绑定事件
+                            if (layerfooter.size() > 0) {
+                                // 监听窗口内的元素及属性变化
+                                // Firefox和Chrome早期版本中带有前缀
+                                var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
+                                if (MutationObserver) {
+                                    // 选择目标节点
+                                    var target = layerfooter[0];
+                                    // 创建观察者对象
+                                    var observer = new MutationObserver(function (mutations) {
+                                        Fast.api.layerfooter(layero, index, that);
+                                        mutations.forEach(function (mutation) {
+                                        });
                                     });
-                                });
-                                // 配置观察选项:
-                                var config = {attributes: true, childList: true, characterData: true, subtree: true}
-                                // 传入目标节点和观察选项
-                                observer.observe(target, config);
-                                // 随后,你还可以停止观察
-                                // observer.disconnect();
+                                    // 配置观察选项:
+                                    var config = {attributes: true, childList: true, characterData: true, subtree: true}
+                                    // 传入目标节点和观察选项
+                                    observer.observe(target, config);
+                                    // 随后,你还可以停止观察
+                                    // observer.disconnect();
+                                }
                             }
+                        } catch (e) {
+
+                        }
+                        if ($(layero).height() > $(window).height()) {
+                            //当弹出窗口大于浏览器可视高度时,重定位
+                            Layer.style(index, {
+                                top: 0,
+                                height: $(window).height()
+                            });
                         }
                     }
                 }, options ? options : {});
@@ -888,8 +899,8 @@ define('fast',['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, u
         },
         lang: function () {
             var args = arguments,
-                    string = args[0],
-                    i = 1;
+                string = args[0],
+                i = 1;
             string = string.toLowerCase();
             //string = typeof Lang[string] != 'undefined' ? Lang[string] : string;
             if (typeof Lang[string] != 'undefined') {
@@ -1028,7 +1039,9 @@ define('frontend',['fast', 'template'], function (Fast, Template) {
 
                 return false;
             });
-
+            //tooltip和popover
+            $('body').tooltip({selector: '[data-toggle="tooltip"]'});
+            $('body').tooltip({selector: '[data-toggle="popover"]'});
         }
     };
     Frontend.api = $.extend(Fast.api, Frontend.api);

+ 11 - 5
public/assets/js/require-table.js

@@ -20,6 +20,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
             pageList: [10, 25, 50, 'All'],
             pagination: true,
             clickToSelect: true, //是否启用点击选中
+            dblClickToEdit: true, //是否启用双击编辑
             singleSelect: false, //是否启用单选
             showRefresh: false,
             locale: 'zh-CN',
@@ -114,10 +115,12 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
                 table.on('refresh.bs.table', function (e, settings, data) {
                     $(Table.config.refreshbtn, toolbar).find(".fa").addClass("fa-spin");
                 });
-                //当双击单元格时
-                table.on('dbl-click-row.bs.table', function (e, row, element, field) {
-                    $(Table.config.editonebtn, element).trigger("click");
-                });
+                if (options.dblClickToEdit) {
+                    //当双击单元格时
+                    table.on('dbl-click-row.bs.table', function (e, row, element, field) {
+                        $(Table.config.editonebtn, element).trigger("click");
+                    });
+                }
                 //当内容渲染完成后
                 table.on('post-body.bs.table', function (e, settings, json, xhr) {
                     $(Table.config.refreshbtn, toolbar).find(".fa").removeClass("fa-spin");
@@ -400,7 +403,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
                     return '<div class="input-group input-group-sm" style="width:250px;margin:0 auto;"><input type="text" class="form-control input-sm" value="' + value + '"><span class="input-group-btn input-group-sm"><a href="' + value + '" target="_blank" class="btn btn-default btn-sm"><i class="fa fa-link"></i></a></span></div>';
                 },
                 search: function (value, row, index) {
-                    return '<a href="javascript:;" class="searchit" data-field="' + this.field + '" data-value="' + value + '">' + value + '</a>';
+                    return '<a href="javascript:;" class="searchit" data-toggle="tooltip" title="' + __('Click to search %s', value) + '" data-field="' + this.field + '" data-value="' + value + '">' + value + '</a>';
                 },
                 addtabs: function (value, row, index) {
                     var url = Table.api.replaceurl(this.url, row, this.table);
@@ -453,6 +456,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
                             name: 'dragsort',
                             icon: 'fa fa-arrows',
                             title: __('Drag to sort'),
+                            extend: 'data-toggle="tooltip"',
                             classname: 'btn btn-xs btn-primary btn-dragsort'
                         });
                     }
@@ -461,6 +465,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
                             name: 'edit',
                             icon: 'fa fa-pencil',
                             title: __('Edit'),
+                            extend: 'data-toggle="tooltip"',
                             classname: 'btn btn-xs btn-success btn-editone',
                             url: options.extend.edit_url
                         });
@@ -470,6 +475,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
                             name: 'del',
                             icon: 'fa fa-trash',
                             title: __('Del'),
+                            extend: 'data-toggle="tooltip"',
                             classname: 'btn btn-xs btn-danger btn-delone'
                         });
                     }

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

@@ -591,6 +591,9 @@ form.form-horizontal .control-label {
 .bootstrap-table td.bs-checkbox {
     vertical-align: middle;
 }
+.fixed-table-container thead th .sortable{
+    padding-right:0;
+}
 .dropdown-submenu {
     position: relative;
     >.dropdown-menu {