Selaa lähdekoodia

升级到最新版本 V1.3.3.20220121

pengchanglu 3 vuotta sitten
vanhempi
commit
d295246bad
45 muutettua tiedostoa jossa 628 lisäystä ja 305 poistoa
  1. 1 1
      README.md
  2. 5 1
      application/admin/command/Addon/stubs/config.stub
  3. 20 19
      application/admin/command/Install/fastadmin.sql
  4. 9 0
      application/admin/controller/Addon.php
  5. 1 1
      application/admin/controller/Index.php
  6. 3 3
      application/admin/controller/auth/Admin.php
  7. 4 2
      application/admin/lang/zh-cn/auth/admin.php
  8. 1 0
      application/admin/lang/zh-cn/general/config.php
  9. 2 1
      application/admin/lang/zh-cn/general/profile.php
  10. 2 1
      application/admin/library/Auth.php
  11. 1 0
      application/admin/validate/Admin.php
  12. 122 106
      application/admin/view/addon/config.html
  13. 4 4
      application/admin/view/auth/admin/index.html
  14. 4 4
      application/admin/view/auth/adminlog/index.html
  15. 3 3
      application/admin/view/auth/group/index.html
  16. 3 3
      application/admin/view/category/index.html
  17. 1 1
      application/admin/view/common/menu.html
  18. 1 1
      application/admin/view/general/attachment/select.html
  19. 9 3
      application/admin/view/general/config/index.html
  20. 1 1
      application/admin/view/general/profile/index.html
  21. 3 3
      application/admin/view/user/group/index.html
  22. 3 3
      application/admin/view/user/rule/index.html
  23. 1 9
      application/api/controller/Ems.php
  24. 5 49
      application/common.php
  25. 38 13
      application/common/library/Ems.php
  26. 1 0
      application/common/library/Upload.php
  27. 3 1
      application/config.php
  28. 1 0
      application/index/controller/Index.php
  29. 2 0
      application/index/view/user/login.html
  30. 7 3
      application/index/view/user/register.html
  31. 1 1
      bower.json
  32. 1 1
      composer.json
  33. 17 2
      extend/fast/Date.php
  34. 3 3
      extend/fast/Tree.php
  35. BIN
      public/assets/img/favicon.ico
  36. 14 0
      public/assets/js/backend/addon.js
  37. 11 8
      public/assets/js/backend/auth/adminlog.js
  38. 7 2
      public/assets/js/fast.js
  39. 104 14
      public/assets/js/require-backend.min.js
  40. 92 4
      public/assets/js/require-form.js
  41. 104 14
      public/assets/js/require-frontend.min.js
  42. 5 8
      public/assets/js/require-table.js
  43. 6 6
      vendor/composer/installed.json
  44. 1 1
      vendor/karsonzhang/fastadmin-addons/composer.json
  45. 1 5
      vendor/karsonzhang/fastadmin-addons/src/addons/Service.php

+ 1 - 1
README.md

@@ -92,6 +92,6 @@ FastAdmin遵循Apache2开源协议发布,并提供免费使用。
 
 本项目包含的第三方源码和二进制文件之版权信息另行标注。
 
-版权所有Copyright © 2017-2020 by FastAdmin (https://www.fastadmin.net)
+版权所有Copyright © 2017-2022 by FastAdmin (https://www.fastadmin.net)
 
 All rights reserved。

+ 5 - 1
application/admin/command/Addon/stubs/config.stub

@@ -8,12 +8,16 @@ return [
         'title'   => '用户名',
         //类型
         'type'    => 'string',
+        //分组
+        'group'    => '',
+        //动态显示
+        'visible'    => '',
         //数据字典
         'content' => [
         ],
         //值
         'value'   => '',
-        //验证规则 
+        //验证规则
         'rule'    => 'required',
         //错误消息
         'msg'     => '',

+ 20 - 19
application/admin/command/Install/fastadmin.sql

@@ -187,7 +187,7 @@ CREATE TABLE `fa_auth_rule` (
 BEGIN;
 INSERT INTO `fa_auth_rule` VALUES (1, 'file', 0, 'dashboard', 'Dashboard', 'fa fa-dashboard', '', '', 'Dashboard tips', 1, NULL, '', 'kzt', 'kongzhitai', 1491635035, 1491635035, 143, 'normal');
 INSERT INTO `fa_auth_rule` VALUES (2, 'file', 0, 'general', 'General', 'fa fa-cogs', '', '', '', 1, NULL, '', 'cggl', 'changguiguanli', 1491635035, 1491635035, 137, 'normal');
-INSERT INTO `fa_auth_rule` VALUES (3, 'file', 0, 'category', 'Category', 'fa fa-leaf', '', '', 'Category tips', 1, NULL, '', 'flgl', 'fenleiguanli', 1491635035, 1491635035, 119, 'normal');
+INSERT INTO `fa_auth_rule` VALUES (3, 'file', 0, 'category', 'Category', 'fa fa-leaf', '', '', 'Category tips', 0, NULL, '', 'flgl', 'fenleiguanli', 1491635035, 1491635035, 119, 'normal');
 INSERT INTO `fa_auth_rule` VALUES (4, 'file', 0, 'addon', 'Addon', 'fa fa-rocket', '', '', 'Addon tips', 1, NULL, '', 'cjgl', 'chajianguanli', 1491635035, 1491635035, 0, 'normal');
 INSERT INTO `fa_auth_rule` VALUES (5, 'file', 0, 'auth', 'Auth', 'fa fa-group', '', '', '', 1, NULL, '', 'qxgl', 'quanxianguanli', 1491635035, 1491635035, 99, 'normal');
 INSERT INTO `fa_auth_rule` VALUES (6, 'file', 2, 'general/config', 'Config', 'fa fa-cog', '', '', 'Config tips', 1, NULL, '', 'xtpz', 'xitongpeizhi', 1491635035, 1491635035, 60, 'normal');
@@ -323,6 +323,7 @@ CREATE TABLE `fa_config` (
   `title` varchar(100) DEFAULT '' COMMENT '变量标题',
   `tip` varchar(100) DEFAULT '' COMMENT '变量描述',
   `type` varchar(30) DEFAULT '' COMMENT '类型:string,text,int,bool,array,datetime,date,file',
+  `visible` varchar(255) DEFAULT '' COMMENT '可见条件',
   `value` text COMMENT '变量值',
   `content` text COMMENT '变量字典数据',
   `rule` varchar(100) DEFAULT '' COMMENT '验证规则',
@@ -336,24 +337,24 @@ CREATE TABLE `fa_config` (
 -- Records of fa_config
 -- ----------------------------
 BEGIN;
-INSERT INTO `fa_config` VALUES (1, 'name', 'basic', 'Site name', '请填写站点名称', 'string', '我的网站', '', 'required', '', '');
-INSERT INTO `fa_config` VALUES (2, 'beian', 'basic', 'Beian', '粤ICP备15000000号-1', 'string', '', '', '', '', '');
-INSERT INTO `fa_config` VALUES (3, 'cdnurl', 'basic', 'Cdn url', '如果全站静态资源使用第三方云储存请配置该值', 'string', '', '', '', '', '');
-INSERT INTO `fa_config` VALUES (4, 'version', 'basic', 'Version', '如果静态资源有变动请重新配置该值', 'string', '1.0.1', '', 'required', '', '');
-INSERT INTO `fa_config` VALUES (5, 'timezone', 'basic', 'Timezone', '', 'string', 'Asia/Shanghai', '', 'required', '', '');
-INSERT INTO `fa_config` VALUES (6, 'forbiddenip', 'basic', 'Forbidden ip', '一行一条记录', 'text', '', '', '', '', '');
-INSERT INTO `fa_config` VALUES (7, 'languages', 'basic', 'Languages', '', 'array', '{\"backend\":\"zh-cn\",\"frontend\":\"zh-cn\"}', '', 'required', '', '');
-INSERT INTO `fa_config` VALUES (8, 'fixedpage', 'basic', 'Fixed page', '请尽量输入左侧菜单栏存在的链接', 'string', 'dashboard', '', 'required', '', '');
-INSERT INTO `fa_config` VALUES (9, 'categorytype', 'dictionary', 'Category type', '', 'array', '{\"default\":\"Default\",\"page\":\"Page\",\"article\":\"Article\",\"test\":\"Test\"}', '', '', '', '');
-INSERT INTO `fa_config` VALUES (10, 'configgroup', 'dictionary', 'Config group', '', 'array', '{\"basic\":\"Basic\",\"email\":\"Email\",\"dictionary\":\"Dictionary\",\"user\":\"User\",\"example\":\"Example\"}', '', '', '', '');
-INSERT INTO `fa_config` VALUES (11, 'mail_type', 'email', 'Mail type', '选择邮件发送方式', 'select', '1', '[\"请选择\",\"SMTP\"]', '', '', '');
-INSERT INTO `fa_config` VALUES (12, 'mail_smtp_host', 'email', 'Mail smtp host', '错误的配置发送邮件会导致服务器超时', 'string', 'smtp.qq.com', '', '', '', '');
-INSERT INTO `fa_config` VALUES (13, 'mail_smtp_port', 'email', 'Mail smtp port', '(不加密默认25,SSL默认465,TLS默认587)', 'string', '465', '', '', '', '');
-INSERT INTO `fa_config` VALUES (14, 'mail_smtp_user', 'email', 'Mail smtp user', '(填写完整用户名)', 'string', '10000', '', '', '', '');
-INSERT INTO `fa_config` VALUES (15, 'mail_smtp_pass', 'email', 'Mail smtp password', '(填写您的密码或授权码)', 'string', 'password', '', '', '', '');
-INSERT INTO `fa_config` VALUES (16, 'mail_verify_type', 'email', 'Mail vertify type', '(SMTP验证方式[推荐SSL])', 'select', '2', '[\"无\",\"TLS\",\"SSL\"]', '', '', '');
-INSERT INTO `fa_config` VALUES (17, 'mail_from', 'email', 'Mail from', '', 'string', '10000@qq.com', '', '', '', '');
-INSERT INTO `fa_config` VALUES (18, 'attachmentcategory', 'dictionary', 'Attachment category', '', 'array', '{\"category1\":\"Category1\",\"category2\":\"Category2\",\"custom\":\"Custom\"}', '', '', '', '');
+INSERT INTO `fa_config` VALUES (1, 'name', 'basic', 'Site name', '请填写站点名称', 'string', '', '我的网站', '', 'required', '', '');
+INSERT INTO `fa_config` VALUES (2, 'beian', 'basic', 'Beian', '粤ICP备15000000号-1', 'string', '', '', '', '', '', '');
+INSERT INTO `fa_config` VALUES (3, 'cdnurl', 'basic', 'Cdn url', '如果全站静态资源使用第三方云储存请配置该值', 'string', '', '', '', '', '', '');
+INSERT INTO `fa_config` VALUES (4, 'version', 'basic', 'Version', '如果静态资源有变动请重新配置该值', 'string', '', '1.0.1', '', 'required', '', '');
+INSERT INTO `fa_config` VALUES (5, 'timezone', 'basic', 'Timezone', '', 'string', '', 'Asia/Shanghai', '', 'required', '', '');
+INSERT INTO `fa_config` VALUES (6, 'forbiddenip', 'basic', 'Forbidden ip', '一行一条记录', 'text', '', '', '', '', '', '');
+INSERT INTO `fa_config` VALUES (7, 'languages', 'basic', 'Languages', '', 'array', '', '{\"backend\":\"zh-cn\",\"frontend\":\"zh-cn\"}', '', 'required', '', '');
+INSERT INTO `fa_config` VALUES (8, 'fixedpage', 'basic', 'Fixed page', '请尽量输入左侧菜单栏存在的链接', 'string', '', 'dashboard', '', 'required', '', '');
+INSERT INTO `fa_config` VALUES (9, 'categorytype', 'dictionary', 'Category type', '', 'array', '', '{\"default\":\"Default\",\"page\":\"Page\",\"article\":\"Article\",\"test\":\"Test\"}', '', '', '', '');
+INSERT INTO `fa_config` VALUES (10, 'configgroup', 'dictionary', 'Config group', '', 'array', '', '{\"basic\":\"Basic\",\"email\":\"Email\",\"dictionary\":\"Dictionary\",\"user\":\"User\",\"example\":\"Example\"}', '', '', '', '');
+INSERT INTO `fa_config` VALUES (11, 'mail_type', 'email', 'Mail type', '选择邮件发送方式', 'select', '', '1', '[\"请选择\",\"SMTP\"]', '', '', '');
+INSERT INTO `fa_config` VALUES (12, 'mail_smtp_host', 'email', 'Mail smtp host', '错误的配置发送邮件会导致服务器超时', 'string', '', 'smtp.qq.com', '', '', '', '');
+INSERT INTO `fa_config` VALUES (13, 'mail_smtp_port', 'email', 'Mail smtp port', '(不加密默认25,SSL默认465,TLS默认587)', 'string', '', '465', '', '', '', '');
+INSERT INTO `fa_config` VALUES (14, 'mail_smtp_user', 'email', 'Mail smtp user', '(填写完整用户名)', 'string', '', '10000', '', '', '', '');
+INSERT INTO `fa_config` VALUES (15, 'mail_smtp_pass', 'email', 'Mail smtp password', '(填写您的密码或授权码)', 'string', '', 'password', '', '', '', '');
+INSERT INTO `fa_config` VALUES (16, 'mail_verify_type', 'email', 'Mail vertify type', '(SMTP验证方式[推荐SSL])', 'select', '', '2', '[\"无\",\"TLS\",\"SSL\"]', '', '', '');
+INSERT INTO `fa_config` VALUES (17, 'mail_from', 'email', 'Mail from', '', 'string', '', '10000@qq.com', '', '', '', '');
+INSERT INTO `fa_config` VALUES (18, 'attachmentcategory', 'dictionary', 'Attachment category', '', 'array', '', '{\"category1\":\"Category1\",\"category2\":\"Category2\",\"custom\":\"Custom\"}', '', '', '', '');
 COMMIT;
 
 -- ----------------------------

+ 9 - 0
application/admin/controller/Addon.php

@@ -94,12 +94,21 @@ class Addon extends Backend
             $this->error(__('Parameter %s can not be empty', ''));
         }
         $tips = [];
+        $groupList = [];
         foreach ($config as $index => &$item) {
+            //如果有设置分组
+            if (isset($item['group']) && $item['group']) {
+                if (!in_array($item['group'], $groupList)) {
+                    $groupList["custom" . (count($groupList) + 1)] = $item['group'];
+                }
+            }
             if ($item['name'] == '__tips__') {
                 $tips = $item;
                 unset($config[$index]);
             }
         }
+        $groupList['other'] = '其它';
+        $this->view->assign("groupList", $groupList);
         $this->view->assign("addon", ['info' => $info, 'config' => $config, 'tips' => $tips]);
         $configFile = ADDON_PATH . $name . DS . 'config.html';
         $viewFile = is_file($configFile) ? $configFile : '';

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

@@ -32,7 +32,7 @@ class Index extends Backend
      */
     public function index()
     {
-        $cookieArr = ['adminskin' => "/^skin\-([a-z\-]+)\$/i", 'multiplenav' => "/^(0|1)\$/", 'multipletab' => "/^(0|1)\$/"];
+        $cookieArr = ['adminskin' => "/^skin\-([a-z\-]+)\$/i", 'multiplenav' => "/^(0|1)\$/", 'multipletab' => "/^(0|1)\$/", 'show_submenu' => "/^(0|1)\$/"];
         foreach ($cookieArr as $key => $regex) {
             $cookieValue = $this->request->cookie($key);
             if (!is_null($cookieValue) && preg_match($regex, $cookieValue)) {

+ 3 - 3
application/admin/controller/auth/Admin.php

@@ -126,7 +126,7 @@ class Admin extends Backend
             if ($params) {
                 Db::startTrans();
                 try {
-                    if (!Validate::is($params['password'], '\S{6,16}')) {
+                    if (!Validate::is($params['password'], '\S{6,30}')) {
                         exception(__("Please input correct password"));
                     }
                     $params['salt'] = Random::alnum();
@@ -184,7 +184,7 @@ class Admin extends Backend
                 Db::startTrans();
                 try {
                     if ($params['password']) {
-                        if (!Validate::is($params['password'], '\S{6,16}')) {
+                        if (!Validate::is($params['password'], '\S{6,30}')) {
                             exception(__("Please input correct password"));
                         }
                         $params['salt'] = Random::alnum();
@@ -195,7 +195,7 @@ class Admin extends Backend
                     //这里需要针对username和email做唯一验证
                     $adminValidate = \think\Loader::validate('Admin');
                     $adminValidate->rule([
-                        'username' => 'require|regex:\w{3,12}|unique:admin,username,' . $row->id,
+                        'username' => 'require|regex:\w{3,30}|unique:admin,username,' . $row->id,
                         'email'    => 'require|email|unique:admin,email,' . $row->id,
                         'password' => 'regex:\S{32}',
                     ]);

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

@@ -5,6 +5,8 @@ return [
     'Loginfailure'                              => '登录失败次数',
     'Login time'                                => '最后登录',
     'The parent group exceeds permission limit' => '父组别超出权限范围',
-    'Please input correct username'             => '用户名只能由3-12位数字、字母、下划线组合',
-    'Please input correct password'             => '密码长度必须在6-16位之间,不能包含空格',
+    'Please input correct username'             => '用户名只能由3-30位数字、字母、下划线组合',
+    'Username must be 3 to 30 characters'       => '用户名只能由3-30位数字、字母、下划线组合',
+    'Please input correct password'             => '密码长度必须在6-30位之间,不能包含空格',
+    'Password must be 6 to 30 characters'       => '密码长度必须在6-30位之间,不能包含空格',
 ];

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

@@ -48,6 +48,7 @@ return [
     'Field value'                          => '字段值',
     'Content'                              => '数据列表',
     'Rule'                                 => '校验规则',
+    'Visible condition'                    => '可见条件',
     'Site name'                            => '站点名称',
     'Beian'                                => '备案号',
     'Cdn url'                              => 'CDN地址',

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

@@ -8,6 +8,7 @@ return [
     'Admin log'                                   => '操作日志',
     'Leave password blank if dont want to change' => '不修改密码请留空',
     'Please input correct email'                  => '请输入正确的Email地址',
-    'Please input correct password'               => '密码长度不正确',
+    'Please input correct password'               => '密码长度必须在6-30位之间,不能包含空格',
+    'Password must be 6 to 30 characters'         => '密码长度必须在6-30位之间,不能包含空格',
     'Email already exists'                        => '邮箱已经存在',
 ];

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

@@ -371,6 +371,7 @@ class Auth extends \fast\Auth
                 $titleArr[$pathArr[$rule['name']]] = $rule['title'];
                 $menuArr[$pathArr[$rule['name']]] = $rule;
             }
+
         }
         ksort($menuArr);
         $this->breadcrumb = $menuArr;
@@ -458,7 +459,7 @@ class Auth extends \fast\Auth
 
         $select_id = $referer ? $referer['id'] : ($selected ? $selected['id'] : 0);
         $menu = $nav = '';
-        $showSubmenu = (int)cookie('show_submenu');
+        $showSubmenu = config('fastadmin.show_submenu');
         if (Config::get('fastadmin.multiplenav')) {
             $topList = [];
             foreach ($ruleList as $index => $item) {

+ 1 - 0
application/admin/validate/Admin.php

@@ -51,4 +51,5 @@ class Admin extends Validate
         ]);
         parent::__construct($rules, $message, $field);
     }
+
 }

+ 122 - 106
application/admin/view/addon/config.html

@@ -1,118 +1,134 @@
 <form id="config-form" class="edit-form form-horizontal" role="form" data-toggle="validator" method="POST" action="">
-    {if $addon.tips}
+    {if $addon.tips && $addon.tips.value}
     <div class="alert {$addon.tips.extend|default='alert-info-light'}" style="margin-bottom:10px;">
+        {if $addon.tips.title}
         <b>{$addon.tips.title}</b><br>
+        {/if}
         {$addon.tips.value}
     </div>
     {/if}
-    <table class="table table-striped">
-        <thead>
-        <tr>
-            <th width="15%">{:__('Title')}</th>
-            <th width="85%">{:__('Value')}</th>
-        </tr>
-        </thead>
-        <tbody>
-        {foreach $addon.config as $item}
-        <tr>
-            <td>{$item.title}</td>
-            <td>
-                <div class="row">
-                    <div class="col-sm-8 col-xs-12">
-                        {switch $item.type}
-                        {case string}
-                        <input {$item.extend} type="text" name="row[{$item.name}]" value="{$item.value|htmlentities}" class="form-control" data-rule="{$item.rule}" data-tip="{$item.tip}"/>
-                        {/case}
-                        {case password}
-                        <input {$item.extend} type="password" name="row[{$item.name}]" value="{$item.value|htmlentities}" class="form-control" data-rule="{$item.rule}" data-tip="{$item.tip}"/>
-                        {/case}
-                        {case text}
-                        <textarea {$item.extend} name="row[{$item.name}]" class="form-control" data-rule="{$item.rule}" rows="5" data-tip="{$item.tip}">{$item.value|htmlentities}</textarea>
-                        {/case}
-                        {case array}
-                        <dl class="fieldlist" data-name="row[{$item.name}]">
-                            <dd>
-                                <ins>{:__('Array key')}</ins>
-                                <ins>{:__('Array value')}</ins>
-                            </dd>
-                            <dd><a href="javascript:;" class="btn btn-sm btn-success btn-append"><i class="fa fa-plus"></i> {:__('Append')}</a></dd>
-                            <textarea name="row[{$item.name}]" cols="30" rows="5" class="hide">{$item.value|json_encode|htmlentities}</textarea>
-                        </dl>
-                        {/case}
-                        {case date}
-                        <input {$item.extend} type="text" name="row[{$item.name}]" value="{$item.value|htmlentities}" class="form-control datetimepicker" data-date-format="YYYY-MM-DD" data-tip="{$item.tip}" data-rule="{$item.rule}"/>
-                        {/case}
-                        {case time}
-                        <input {$item.extend} type="text" name="row[{$item.name}]" value="{$item.value|htmlentities}" class="form-control datetimepicker" data-date-format="HH:mm:ss" data-tip="{$item.tip}" data-rule="{$item.rule}"/>
-                        {/case}
-                        {case datetime}
-                        <input {$item.extend} type="text" name="row[{$item.name}]" value="{$item.value|htmlentities}" class="form-control datetimepicker" data-date-format="YYYY-MM-DD HH:mm:ss" data-tip="{$item.tip}" data-rule="{$item.rule}"/>
-                        {/case}
-                        {case number}
-                        <input {$item.extend} type="number" name="row[{$item.name}]" value="{$item.value|htmlentities}" class="form-control" data-tip="{$item.tip}" data-rule="{$item.rule}"/>
-                        {/case}
-                        {case checkbox}
-                        {foreach name="item.content" item="vo"}
-                        <label for="row[{$item.name}][]-{$key}"><input id="row[{$item.name}][]-{$key}" name="row[{$item.name}][]" type="checkbox" value="{$key}" data-tip="{$item.tip}" {in name="key" value="$item.value" }checked{/in} /> {$vo}</label>
-                        {/foreach}
-                        {/case}
-                        {case radio}
-                        {foreach name="item.content" item="vo"}
-                        <label for="row[{$item.name}]-{$key}"><input id="row[{$item.name}]-{$key}" name="row[{$item.name}]" type="radio" value="{$key}" data-tip="{$item.tip}" {in name="key" value="$item.value" }checked{/in} /> {$vo}</label>
+
+    <div class="panel panel-default panel-intro">
+        {if count($groupList)>1}
+        <div class="panel-heading mb-3">
+            <ul class="nav nav-tabs nav-group">
+                <li class="active"><a href="#all" data-toggle="tab">全部</a></li>
+                {foreach name="groupList" id="tab"}
+                    <li><a href="#tab-{$key}" title="{$tab}" data-toggle="tab">{$tab}</a></li>
+                {/foreach}
+            </ul>
+        </div>
+        {/if}
+
+        <div class="panel-body no-padding">
+            <div id="myTabContent" class="tab-content">
+                {foreach name="groupList" id="group" key="groupName"}
+                <div class="tab-pane fade active in" id="tab-{$groupName}">
+
+                    <table class="table table-striped table-config mb-0">
+                        <tbody>
+                        {foreach name="$addon.config" id="item"}
+                        {if ((!isset($item['group']) || $item['group']=='') && $groupName=='other') || (isset($item['group']) && $item['group']==$group)}
+                        <tr data-favisible="{$item.visible|default=''|htmlentities}" data-name="{$item.name}" class="{if $item.visible??''}hidden{/if}">
+                            <td width="15%">{$item.title}</td>
+                            <td>
+                                <div class="row">
+                                    <div class="col-sm-8 col-xs-12">
+                                        {switch $item.type}
+                                        {case string}
+                                        <input {$item.extend} type="text" name="row[{$item.name}]" value="{$item.value|htmlentities}" class="form-control" data-rule="{$item.rule}" data-tip="{$item.tip}"/>
+                                        {/case}
+                                        {case password}
+                                        <input {$item.extend} type="password" name="row[{$item.name}]" value="{$item.value|htmlentities}" class="form-control" data-rule="{$item.rule}" data-tip="{$item.tip}"/>
+                                        {/case}
+                                        {case text}
+                                        <textarea {$item.extend} name="row[{$item.name}]" class="form-control" data-rule="{$item.rule}" rows="5" data-tip="{$item.tip}">{$item.value|htmlentities}</textarea>
+                                        {/case}
+                                        {case array}
+                                        <dl class="fieldlist" data-name="row[{$item.name}]">
+                                            <dd>
+                                                <ins>{:__('Array key')}</ins>
+                                                <ins>{:__('Array value')}</ins>
+                                            </dd>
+                                            <dd><a href="javascript:;" class="btn btn-sm btn-success btn-append"><i class="fa fa-plus"></i> {:__('Append')}</a></dd>
+                                            <textarea name="row[{$item.name}]" cols="30" rows="5" class="hide">{$item.value|json_encode|htmlentities}</textarea>
+                                        </dl>
+                                        {/case}
+                                        {case date}
+                                        <input {$item.extend} type="text" name="row[{$item.name}]" value="{$item.value|htmlentities}" class="form-control datetimepicker" data-date-format="YYYY-MM-DD" data-tip="{$item.tip}" data-rule="{$item.rule}"/>
+                                        {/case}
+                                        {case time}
+                                        <input {$item.extend} type="text" name="row[{$item.name}]" value="{$item.value|htmlentities}" class="form-control datetimepicker" data-date-format="HH:mm:ss" data-tip="{$item.tip}" data-rule="{$item.rule}"/>
+                                        {/case}
+                                        {case datetime}
+                                        <input {$item.extend} type="text" name="row[{$item.name}]" value="{$item.value|htmlentities}" class="form-control datetimepicker" data-date-format="YYYY-MM-DD HH:mm:ss" data-tip="{$item.tip}" data-rule="{$item.rule}"/>
+                                        {/case}
+                                        {case number}
+                                        <input {$item.extend} type="number" name="row[{$item.name}]" value="{$item.value|htmlentities}" class="form-control" data-tip="{$item.tip}" data-rule="{$item.rule}"/>
+                                        {/case}
+                                        {case checkbox}
+                                        {foreach name="item.content" item="vo"}
+                                        <label for="row[{$item.name}][]-{$key}"><input id="row[{$item.name}][]-{$key}" name="row[{$item.name}][]" type="checkbox" value="{$key}" data-tip="{$item.tip}" {in name="key" value="$item.value" }checked{/in} /> {$vo}</label>
+                                        {/foreach}
+                                        <span class="msg-box n-right" for="c-{$item.name}"></span>
+                                        {/case}
+                                        {case radio}
+                                        {foreach name="item.content" item="vo"}
+                                        <label for="row[{$item.name}]-{$key}"><input id="row[{$item.name}]-{$key}" name="row[{$item.name}]" type="radio" value="{$key}" data-tip="{$item.tip}" {in name="key" value="$item.value" }checked{/in} /> {$vo}</label>
+                                        {/foreach}
+                                        <span class="msg-box n-right" for="c-{$item.name}"></span>
+                                        {/case}
+                                        {case value="select" break="0"}{/case}
+                                        {case value="selects"}
+                                        <select {$item.extend} name="row[{$item.name}]{$item.type=='selects'?'[]':''}" class="form-control selectpicker" data-tip="{$item.tip}" {$item.type=='selects'?'multiple':''}>
+                                            {foreach name="item.content" item="vo"}
+                                            <option value="{$key}" {in name="key" value="$item.value" }selected{/in}>{$vo}</option>
+                                            {/foreach}
+                                        </select>
+                                        {/case}
+                                        {case value="image" break="0"}{/case}
+                                        {case value="images"}
+                                        <div class="form-inline">
+                                            <input id="c-{$item.name}" class="form-control" size="35" name="row[{$item.name}]" type="text" value="{$item.value|htmlentities}" data-tip="{$item.tip}">
+                                            <span><button type="button" id="plupload-{$item.name}" class="btn btn-danger plupload" data-input-id="c-{$item.name}" data-mimetype="image/*" data-multiple="{$item.type=='image'?'false':'true'}" data-preview-id="p-{$item.name}"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
+                                            <span><button type="button" id="fachoose-{$item.name}" class="btn btn-primary fachoose" data-input-id="c-{$item.name}" data-mimetype="image/*" data-multiple="{$item.type=='image'?'false':'true'}"><i class="fa fa-list"></i> {:__('Choose')}</button></span>
+                                            <ul class="row list-inline plupload-preview" id="p-{$item.name}"></ul>
+                                        </div>
+                                        {/case}
+                                        {case value="file" break="0"}{/case}
+                                        {case value="files"}
+                                        <div class="form-inline">
+                                            <input id="c-{$item.name}" class="form-control" size="35" name="row[{$item.name}]" type="text" value="{$item.value|htmlentities}" data-tip="{$item.tip}">
+                                            <span><button type="button" id="plupload-{$item.name}" class="btn btn-danger plupload" data-input-id="c-{$item.name}" data-multiple="{$item.type=='file'?'false':'true'}"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
+                                            <span><button type="button" id="fachoose-{$item.name}" class="btn btn-primary fachoose" data-input-id="c-{$item.name}" data-multiple="{$item.type=='file'?'false':'true'}"><i class="fa fa-list"></i> {:__('Choose')}</button></span>
+                                        </div>
+                                        {/case}
+                                        {case bool}
+                                        <label for="row[{$item.name}]-yes"><input id="row[{$item.name}]-yes" name="row[{$item.name}]" type="radio" value="1" {$item.value?'checked':''} data-tip="{$item.tip}" /> {:__('Yes')}</label>
+                                        <label for="row[{$item.name}]-no"><input id="row[{$item.name}]-no" name="row[{$item.name}]" type="radio" value="0" {$item.value?'':'checked'} data-tip="{$item.tip}" /> {:__('No')}</label>
+                                        {/case}
+                                        {default /}{$item.value}
+                                        {/switch}
+                                    </div>
+                                    <div class="col-sm-4"></div>
+                                </div>
+
+                            </td>
+                        </tr>
+                        {/if}
                         {/foreach}
-                        {/case}
-                        {case value="select" break="0"}{/case}
-                        {case value="selects"}
-                        <select {$item.extend} name="row[{$item.name}]{$item.type=='selects'?'[]':''}" class="form-control selectpicker" data-tip="{$item.tip}" {$item.type=='selects'?'multiple':''}>
-                            {foreach name="item.content" item="vo"}
-                            <option value="{$key}" {in name="key" value="$item.value" }selected{/in}>{$vo}</option>
-                            {/foreach}
-                        </select>
-                        {/case}
-                        {case value="image" break="0"}{/case}
-                        {case value="images"}
-                        <div class="input-group">
-                            <input {$item.extend} id="c-{$item.name}" class="form-control" size="50" name="row[{$item.name}]" type="text" value="{$item.value|htmlentities}" data-tip="{$item.tip}" data-rule="{$item.rule}">
-                            <div class="input-group-addon no-border no-padding">
-                                <span><button type="button" id="faupload-{$item.name}" class="btn btn-danger faupload" data-input-id="c-{$item.name}" data-mimetype="image/*" data-multiple="{$item.type=='image'?'false':'true'}" data-preview-id="p-{$item.name}"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
-                                <span><button type="button" id="fachoose-{$item.name}" class="btn btn-primary fachoose" data-input-id="c-{$item.name}" data-mimetype="image/*" data-multiple="{$item.type=='image'?'false':'true'}"><i class="fa fa-list"></i> {:__('Choose')}</button></span>
-                            </div>
-                            <span class="msg-box n-right" for="c-{$item.name}"></span>
-                        </div>
-                        <ul class="row list-inline faupload-preview" id="p-{$item.name}"></ul>
-                        {/case}
-                        {case value="file" break="0"}{/case}
-                        {case value="files"}
-                        <div class="input-group">
-                            <input {$item.extend} id="c-{$item.name}" class="form-control" size="50" name="row[{$item.name}]" type="text" value="{$item.value|htmlentities}" data-tip="{$item.tip}" data-rule="{$item.rule}">
-                            <div class="input-group-addon no-border no-padding">
-                                <span><button type="button" id="faupload-{$item.name}" class="btn btn-danger faupload" data-input-id="c-{$item.name}" data-multiple="{$item.type=='file'?'false':'true'}" data-preview-id="p-{$item.name}"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
-                                <span><button type="button" id="fachoose-{$item.name}" class="btn btn-primary fachoose" data-input-id="c-{$item.name}" data-multiple="{$item.type=='file'?'false':'true'}"><i class="fa fa-list"></i> {:__('Choose')}</button></span>
-                            </div>
-                            <span class="msg-box n-right" for="c-{$item.name}"></span>
-                        </div>
-                        <ul class="row list-inline faupload-preview" id="p-{$item.name}"></ul>
-                        {/case}
-                        {case bool}
-                        <label for="row[{$item.name}]-yes"><input id="row[{$item.name}]-yes" name="row[{$item.name}]" type="radio" value="1" {$item.value?'checked':''} data-tip="{$item.tip}" /> {:__('Yes')}</label>
-                        <label for="row[{$item.name}]-no"><input id="row[{$item.name}]-no" name="row[{$item.name}]" type="radio" value="0" {$item.value?'':'checked'} data-tip="{$item.tip}" /> {:__('No')}</label>
-                        {/case}
-                        {default /}{$item.value}
-                        {/switch}
+                        </tbody>
+                    </table>
+                </div>
+                {/foreach}
+                <div class="form-group layer-footer">
+                    <label class="control-label col-xs-12 col-sm-2" style="width:15%;"></label>
+                    <div class="col-xs-12 col-sm-8">
+                        <button type="submit" class="btn btn-primary btn-embossed disabled">{:__('OK')}</button>
+                        <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
                     </div>
-                    <div class="col-sm-4"></div>
                 </div>
-
-            </td>
-        </tr>
-        {/foreach}
-        </tbody>
-    </table>
-    <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-primary btn-embossed disabled">{:__('OK')}</button>
-            <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
+            </div>
         </div>
     </div>
 </form>

+ 4 - 4
application/admin/view/auth/admin/index.html

@@ -8,9 +8,9 @@
                     <div id="toolbar" class="toolbar">
                         {:build_toolbar('refresh,add,delete')}
                     </div>
-                    <table id="table" class="table table-striped table-bordered table-hover" 
-                           data-operate-edit="{:$auth->check('auth/admin/edit')}" 
-                           data-operate-del="{:$auth->check('auth/admin/del')}" 
+                    <table id="table" class="table table-striped table-bordered table-hover table-nowrap"
+                           data-operate-edit="{:$auth->check('auth/admin/edit')}"
+                           data-operate-del="{:$auth->check('auth/admin/del')}"
                            width="100%">
                     </table>
                 </div>
@@ -18,4 +18,4 @@
 
         </div>
     </div>
-</div>
+</div>

+ 4 - 4
application/admin/view/auth/adminlog/index.html

@@ -8,9 +8,9 @@
                     <div id="toolbar" class="toolbar">
                         {:build_toolbar('refresh,delete')}
                     </div>
-                    <table id="table" class="table table-striped table-bordered table-hover" 
-                           data-operate-detail="{:$auth->check('auth/adminlog/index')}" 
-                           data-operate-del="{:$auth->check('auth/adminlog/del')}" 
+                    <table id="table" class="table table-striped table-bordered table-hover table-nowrap"
+                           data-operate-detail="{:$auth->check('auth/adminlog/index')}"
+                           data-operate-del="{:$auth->check('auth/adminlog/del')}"
                            width="100%">
                     </table>
                 </div>
@@ -18,4 +18,4 @@
 
         </div>
     </div>
-</div>
+</div>

+ 3 - 3
application/admin/view/auth/group/index.html

@@ -8,9 +8,9 @@
                     <div id="toolbar" class="toolbar">
                         {:build_toolbar('refresh,add,delete')}
                     </div>
-                    <table id="table" class="table table-striped table-bordered table-hover" 
-                           data-operate-edit="{:$auth->check('auth/group/edit')}" 
-                           data-operate-del="{:$auth->check('auth/group/del')}" 
+                    <table id="table" class="table table-striped table-bordered table-hover table-nowrap"
+                           data-operate-edit="{:$auth->check('auth/group/edit')}"
+                           data-operate-del="{:$auth->check('auth/group/del')}"
                            width="100%">
                     </table>
                 </div>

+ 3 - 3
application/admin/view/category/index.html

@@ -23,9 +23,9 @@
                             </ul>
                         </div>
                     </div>
-                    <table id="table" class="table table-striped table-bordered table-hover" 
-                           data-operate-edit="{:$auth->check('category/edit')}" 
-                           data-operate-del="{:$auth->check('category/del')}" 
+                    <table id="table" class="table table-striped table-bordered table-hover table-nowrap"
+                           data-operate-edit="{:$auth->check('category/edit')}"
+                           data-operate-del="{:$auth->check('category/del')}"
                            width="100%">
                     </table>
                 </div>

+ 1 - 1
application/admin/view/common/menu.html

@@ -30,7 +30,7 @@
     </div>
 
     <!-- 左侧菜单栏 -->
-    <ul class="sidebar-menu {if $Think.cookie.show_submenu}show-submenu{/if}">
+    <ul class="sidebar-menu {if $Think.config.fastadmin.show_submenu}show-submenu{/if}">
 
         <!-- 菜单可以在 后台管理->权限管理->菜单规则 中进行增删改排序 -->
         {$menulist}

+ 1 - 1
application/admin/view/general/attachment/select.html

@@ -36,7 +36,7 @@
                         <a class="btn btn-danger btn-choose-multi"><i class="fa fa-check"></i> {:__('Choose')}</a>
                         {/if}
                     </div>
-                    <table id="table" class="table table-bordered table-hover" width="100%">
+                    <table id="table" class="table table-bordered table-hover table-nowrap" width="100%">
 
                     </table>
                 </div>

+ 9 - 3
application/admin/view/general/config/index.html

@@ -36,7 +36,7 @@
         {:build_heading(null, false)}
         <ul class="nav nav-tabs">
             {foreach $siteList as $index=>$vo}
-            <li class="{$vo.active?'active':''}"><a href="#{$vo.name}" data-toggle="tab">{:__($vo.title)}</a></li>
+            <li class="{$vo.active?'active':''}"><a href="#tab-{$vo.name}" data-toggle="tab">{:__($vo.title)}</a></li>
             {/foreach}
             {if $Think.config.app_debug}
             <li data-toggle="tooltip" title="{:__('Add new config')}">
@@ -50,7 +50,7 @@
         <div id="myTabContent" class="tab-content">
             <!--@formatter:off-->
             {foreach $siteList as $index=>$vo}
-            <div class="tab-pane fade {$vo.active ? 'active in' : ''}" id="{$vo.name}">
+            <div class="tab-pane fade {$vo.active ? 'active in' : ''}" id="tab-{$vo.name}">
                 <div class="widget-body no-padding">
                     <form id="{$vo.name}-form" class="edit-form form-horizontal" role="form" data-toggle="validator" method="POST" action="{:url('general.config/edit')}">
                         {:token()}
@@ -67,7 +67,7 @@
                             </thead>
                             <tbody>
                             {foreach $vo.list as $item}
-                            <tr>
+                            <tr data-favisible="{$item.visible|default=''|htmlentities}" data-name="{$item.name}" class="{if $item.visible??''}hidden{/if}">
                                 <td>{$item.title}</td>
                                 <td>
                                     <div class="row">
@@ -324,6 +324,12 @@ value2|title2</textarea>
                         </div>
                     </div>
                     <div class="form-group">
+                        <label for="visible" class="control-label col-xs-12 col-sm-2">{:__('Visible condition')}:</label>
+                        <div class="col-xs-12 col-sm-4">
+                            <input type="text" class="form-control" id="visible" name="row[visible]" value="" data-rule=""/>
+                        </div>
+                    </div>
+                    <div class="form-group">
                         <label for="extend" class="control-label col-xs-12 col-sm-2">{:__('Extend')}:</label>
                         <div class="col-xs-12 col-sm-4">
                             <textarea name="row[extend]" id="extend" cols="30" rows="5" class="form-control" data-tip="{:__('Extend tips')}" data-rule="required(extend)" data-msg-extend="当类型为自定义时,扩展属性不能为空"></textarea>

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

@@ -100,7 +100,7 @@
                             <div id="toolbar" class="toolbar">
                                 {:build_toolbar('refresh')}
                             </div>
-                            <table id="table" class="table table-striped table-bordered table-hover" width="100%">
+                            <table id="table" class="table table-striped table-bordered table-hover table-nowrap" width="100%">
 
                             </table>
 

+ 3 - 3
application/admin/view/user/group/index.html

@@ -15,9 +15,9 @@
                             </ul>
                         </div>
                     </div>
-                    <table id="table" class="table table-striped table-bordered table-hover" 
-                           data-operate-edit="{:$auth->check('user/group/edit')}" 
-                           data-operate-del="{:$auth->check('user/group/del')}" 
+                    <table id="table" class="table table-striped table-bordered table-hover table-nowrap"
+                           data-operate-edit="{:$auth->check('user/group/edit')}"
+                           data-operate-del="{:$auth->check('user/group/del')}"
                            width="100%">
                     </table>
                 </div>

+ 3 - 3
application/admin/view/user/rule/index.html

@@ -15,9 +15,9 @@
                             </ul>
                         </div>
                     </div>
-                    <table id="table" class="table table-striped table-bordered table-hover" 
-                           data-operate-edit="{:$auth->check('user/rule/edit')}" 
-                           data-operate-del="{:$auth->check('user/rule/del')}" 
+                    <table id="table" class="table table-striped table-bordered table-hover table-nowrap"
+                           data-operate-edit="{:$auth->check('user/rule/edit')}"
+                           data-operate-del="{:$auth->check('user/rule/del')}"
                            width="100%">
                     </table>
                 </div>

+ 1 - 9
application/api/controller/Ems.php

@@ -5,6 +5,7 @@ namespace app\api\controller;
 use app\common\controller\Api;
 use app\common\library\Ems as Emslib;
 use app\common\model\User;
+use think\Hook;
 
 /**
  * 邮箱验证码接口
@@ -17,15 +18,6 @@ class Ems extends Api
     public function _initialize()
     {
         parent::_initialize();
-        \think\Hook::add('ems_send', function ($params) {
-            $obj = \app\common\library\Email::instance();
-            $result = $obj
-                ->to($params->email)
-                ->subject('验证码')
-                ->message("你的验证码是:" . $params->code)
-                ->send();
-            return $result;
-        });
     }
 
     /**

+ 5 - 49
application/common.php

@@ -169,10 +169,11 @@ if (!function_exists('copydirs')) {
         if (!is_dir($dest)) {
             mkdir($dest, 0755, true);
         }
-        foreach ($iterator = new RecursiveIteratorIterator(
-            new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS),
-            RecursiveIteratorIterator::SELF_FIRST
-        ) as $item
+        foreach (
+            $iterator = new RecursiveIteratorIterator(
+                new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS),
+                RecursiveIteratorIterator::SELF_FIRST
+            ) as $item
         ) {
             if ($item->isDir()) {
                 $sontDir = $dest . DS . $iterator->getSubPathName();
@@ -510,49 +511,4 @@ if (!function_exists('build_suffix_image')) {
 EOT;
         return $icon;
     }
-
-    if (!function_exists('encrypt')) {
-        /**
-        * @desc加密
-        * @param string $str 待加密字符串
-        * @param string $key 密钥
-        * @return string
-        */
-        function encrypt($str, $key = 'pig')
-        {
-            $mixStr = md5(date('Y-m-d H:i:s').rand(0, 1000));
-            $tmp = '';
-            $strLen = strlen($str);
-            for ($i=0, $j=0; $i<$strLen; $i++, $j++) {
-                $j = $j == 32 ? 0 : $j;
-                $tmp .= $mixStr[$j].($str[$i] ^ $mixStr[$j]);
-            }
-            return base64_encode($tmp.$key);
-        }
-    }
-    if (!function_exists('decrypt')) {
-        /**
-        * @desc解密
-        * @param string $str 待解密字符串
-        * @param string $key 密钥
-        * @return string
-        */
-        function decrypt($str, $key = 'pig')
-        {
-            $str = base64_decode($str);
-            if (!strpos($str, $key)) {
-                return '';
-            }
-            $str = substr($str, 0, -strLen($key));
-            $strLen = strlen($str);
-            $tmp = '';
-            for ($i=0; $i<$strLen; $i++) {
-                if (!isset($str[$i]) || !isset($str[$i+1])) {
-                    break;//错误编码
-                }
-                $tmp .= $str[$i] ^ $str[++$i];
-            }
-            return $tmp;
-        }
-    }
 }

+ 38 - 13
application/common/library/Ems.php

@@ -26,8 +26,8 @@ class Ems
     /**
      * 获取最后一次邮箱发送的数据
      *
-     * @param   int    $email 邮箱
-     * @param   string $event 事件
+     * @param int    $email 邮箱
+     * @param string $event 事件
      * @return  Ems
      */
     public static function get($email, $event = 'default')
@@ -43,9 +43,9 @@ class Ems
     /**
      * 发送验证码
      *
-     * @param   int    $email 邮箱
-     * @param   int    $code  验证码,为空时将自动生成4位数字
-     * @param   string $event 事件
+     * @param int    $email 邮箱
+     * @param int    $code  验证码,为空时将自动生成4位数字
+     * @param string $event 事件
      * @return  boolean
      */
     public static function send($email, $code = null, $event = 'default')
@@ -54,6 +54,18 @@ class Ems
         $time = time();
         $ip = request()->ip();
         $ems = \app\common\model\Ems::create(['event' => $event, 'email' => $email, 'code' => $code, 'ip' => $ip, 'createtime' => $time]);
+        if (!Hook::get('ems_send')) {
+            //采用框架默认的邮件推送
+            Hook::add('ems_send', function ($params) {
+                $obj = new Email();
+                $result = $obj
+                    ->to($params->email)
+                    ->subject('请查收你的验证码!')
+                    ->message("你的验证码是:" . $params->code . "," . ceil(self::$expire / 60) . "分钟内有效。")
+                    ->send();
+                return $result;
+            });
+        }
         $result = Hook::listen('ems_send', $ems, null, true);
         if (!$result) {
             $ems->delete();
@@ -65,9 +77,9 @@ class Ems
     /**
      * 发送通知
      *
-     * @param   mixed  $email    邮箱,多个以,分隔
-     * @param   string $msg      消息内容
-     * @param   string $template 消息模板
+     * @param mixed  $email    邮箱,多个以,分隔
+     * @param string $msg      消息内容
+     * @param string $template 消息模板
      * @return  boolean
      */
     public static function notice($email, $msg = '', $template = null)
@@ -77,6 +89,19 @@ class Ems
             'msg'      => $msg,
             'template' => $template
         ];
+        if (!Hook::get('ems_notice')) {
+            //采用框架默认的邮件推送
+            Hook::add('ems_notice', function ($params) {
+                $subject = '你收到一封新的邮件!';
+                $content = $params['msg'];
+                $email = new Email();
+                $result = $email->to($params['email'])
+                    ->subject($subject)
+                    ->message($content)
+                    ->send();
+                return $result;
+            });
+        }
         $result = Hook::listen('ems_notice', $params, null, true);
         return $result ? true : false;
     }
@@ -84,9 +109,9 @@ class Ems
     /**
      * 校验验证码
      *
-     * @param   int    $email 邮箱
-     * @param   int    $code  验证码
-     * @param   string $event 事件
+     * @param int    $email 邮箱
+     * @param int    $code  验证码
+     * @param string $event 事件
      * @return  boolean
      */
     public static function check($email, $code, $event = 'default')
@@ -119,8 +144,8 @@ class Ems
     /**
      * 清空指定邮箱验证码
      *
-     * @param   int    $email 邮箱
-     * @param   string $event 事件
+     * @param int    $email 邮箱
+     * @param string $event 事件
      * @return  boolean
      */
     public static function flush($email, $event = 'default')

+ 1 - 0
application/common/library/Upload.php

@@ -181,6 +181,7 @@ class Upload
             $suffix = $this->fileInfo['suffix'];
         }
         $filename = $filename ? $filename : ($suffix ? substr($this->fileInfo['name'], 0, strripos($this->fileInfo['name'], '.')) : $this->fileInfo['name']);
+        $filename = xss_clean(strip_tags(htmlspecialchars($filename)));
         $md5 = $md5 ? $md5 : md5_file($this->fileInfo['tmp_name']);
         $replaceArr = [
             '{year}'     => date("Y"),

+ 3 - 1
application/config.php

@@ -277,6 +277,8 @@ return [
         'multiplenav'           => false,
         //是否开启多选项卡(仅在开启多级菜单时起作用)
         'multipletab'           => true,
+        //是否默认展示子菜单
+        'show_submenu'          => false,
         //后台皮肤,为空时表示使用skin-black-blue
         'adminskin'             => '',
         //后台是否启用面包屑
@@ -292,7 +294,7 @@ return [
         //允许跨域的域名,多个以,分隔
         'cors_request_domain'   => 'localhost,127.0.0.1',
         //版本号
-        'version'               => '1.3.1.20220112',
+        'version'               => '1.3.3.20220121',
         //API接口地址
         'api_url'               => 'https://api.fastadmin.net',
     ],

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

@@ -16,4 +16,5 @@ class Index extends Frontend
         $this->redirect('source/info');
         return $this->view->fetch();
     }
+
 }

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

@@ -3,6 +3,7 @@
         <div class="logon-tab clearfix"><a class="active">{:__('Sign in')}</a> <a href="{:url('user/register')}?url={$url|urlencode|htmlentities}">{:__('Sign up')}</a></div>
         <div class="login-main">
             <form name="form" id="login-form" class="form-vertical" method="POST" action="">
+                <!--@IndexLoginFormBegin-->
                 <input type="hidden" name="url" value="{$url|htmlentities}"/>
                 {:token()}
                 <div class="form-group">
@@ -32,6 +33,7 @@
                     <button type="submit" class="btn btn-primary btn-lg btn-block">{:__('Sign in')}</button>
                     <a href="{:url('user/register')}?url={$url|urlencode|htmlentities}" class="btn btn-default btn-lg btn-block mt-3 no-border">还没有账号?点击注册</a>
                 </div>
+                <!--@IndexLoginFormEnd-->
             </form>
         </div>
     </div>

+ 7 - 3
application/index/view/user/register.html

@@ -1,10 +1,11 @@
 <div id="content-container" class="container">
     <div class="user-section login-section">
-        <div class="logon-tab clearfix"> <a href="{:url('user/login')}?url={$url|urlencode|htmlentities}">{:__('Sign in')}</a> <a class="active">{:__('Sign up')}</a> </div>
+        <div class="logon-tab clearfix"><a href="{:url('user/login')}?url={$url|urlencode|htmlentities}">{:__('Sign in')}</a> <a class="active">{:__('Sign up')}</a></div>
         <div class="login-main">
             <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|htmlentities}" />
+                <!--@IndexRegisterFormBegin-->
+                <input type="hidden" name="invite_user_id" value="0"/>
+                <input type="hidden" name="url" value="{$url|htmlentities}"/>
                 {:token()}
                 <div class="form-group">
                     <label class="control-label required">{:__('Email')}<span class="text-success"></span></label>
@@ -35,6 +36,7 @@
                     </div>
                 </div>
 
+                <!--@CaptchaBegin-->
                 {if $captchaType}
                 <div class="form-group">
                     <label class="control-label">{:__('Captcha')}</label>
@@ -46,11 +48,13 @@
                     </div>
                 </div>
                 {/if}
+                <!--@CaptchaEnd-->
 
                 <div class="form-group">
                     <button type="submit" class="btn btn-primary btn-lg btn-block">{:__('Sign up')}</button>
                     <a href="{:url('user/login')}?url={$url|urlencode|htmlentities}" class="btn btn-default btn-lg btn-block mt-3 no-border">已经有账号?点击登录</a>
                 </div>
+                <!--@IndexRegisterFormEnd-->
             </form>
         </div>
     </div>

+ 1 - 1
bower.json

@@ -7,7 +7,7 @@
   "private": true,
   "dependencies": {
     "jquery": "^2.1.4",
-    "bootstrap": "~3.3.7",
+    "bootstrap": "^3.3.7",
     "font-awesome": "^4.6.1",
     "bootstrap-table": "fastadmin-bootstraptable#~1.11.5",
     "jstree": "~3.3.2",

+ 1 - 1
composer.json

@@ -21,7 +21,7 @@
         "topthink/think-installer": "^1.0.14",
         "topthink/think-queue": "1.1.6",
         "topthink/think-helper": "^1.0.7",
-        "karsonzhang/fastadmin-addons": "~1.3.1",
+        "karsonzhang/fastadmin-addons": "~1.3.2",
         "overtrue/pinyin": "^3.0",
         "phpoffice/phpspreadsheet": "1.12",
         "overtrue/wechat": "4.2.11",

+ 17 - 2
extend/fast/Date.php

@@ -191,13 +191,13 @@ class Date
                 break;
             case 'month':
                 $_timestamp = mktime(0, 0, 0, $month + $offset, 1, $year);
-                $time = $position ? $_timestamp : mktime(23, 59, 59, $month + $offset, cal_days_in_month(CAL_GREGORIAN, date("m", $_timestamp), date("Y", $_timestamp)), $year);
+                $time = $position ? $_timestamp : mktime(23, 59, 59, $month + $offset, self::days_in_month(date("m", $_timestamp), date("Y", $_timestamp)), $year);
                 break;
             case 'quarter':
                 $_month = date("m", mktime(0, 0, 0, (ceil(date('n', mktime(0, 0, 0, $month, $day, $year)) / 3) + $offset) * 3, $day, $year));
                 $time = $position ?
                     mktime(0, 0, 0, 1 + ((ceil(date('n', $baseTime) / 3) + $offset) - 1) * 3, 1, $year) :
-                    mktime(23, 59, 59, (ceil(date('n', $baseTime) / 3) + $offset) * 3, cal_days_in_month(CAL_GREGORIAN, (ceil(date('n', $baseTime) / 3) + $offset) * 3, $year), $year);
+                    mktime(23, 59, 59, (ceil(date('n', $baseTime) / 3) + $offset) * 3, self::days_in_month((ceil(date('n', $baseTime) / 3) + $offset) * 3, $year), $year);
                 break;
             case 'year':
                 $time = $position ? mktime(0, 0, 0, 1, 1, $year + $offset) : mktime(23, 59, 59, 12, 31, $year + $offset);
@@ -208,4 +208,19 @@ class Date
         }
         return $time;
     }
+
+    /**
+     * 获取指定年月拥有的天数
+     * @param int $month
+     * @param int $year
+     * @return false|int|string
+     */
+    public static function days_in_month($month, $year)
+    {
+        if (function_exists("cal_days_in_month")) {
+            return cal_days_in_month(CAL_GREGORIAN, $month, $year);
+        } else {
+            return date('t', mktime(0, 0, 0, $month, 1, $year));
+        }
+    }
 }

+ 3 - 3
extend/fast/Tree.php

@@ -112,10 +112,10 @@ class Tree
             if (!isset($value['id'])) {
                 continue;
             }
-            if ($value[$this->pidname] == $myid) {
+            if ((string)$value[$this->pidname] == (string)$myid) {
                 $newarr[] = $value;
                 $newarr = array_merge($newarr, $this->getChildren($value['id']));
-            } elseif ($withself && $value['id'] == $myid) {
+            } elseif ($withself && (string)$value['id'] == (string)$myid) {
                 $newarr[] = $value;
             }
         }
@@ -326,7 +326,7 @@ class Tree
                     '@addtabs'   => $childdata || !isset($value['@url']) ? "" : (stripos($value['@url'], "?") !== false ? "&" : "?") . "ref=addtabs",
                     '@caret'     => ($childdata && (!isset($value['@badge']) || !$value['@badge']) ? '<i class="fa fa-angle-left"></i>' : ''),
                     '@badge'     => isset($value['@badge']) ? $value['@badge'] : '',
-                    '@class'     => ($selected ? ' active' : '') . ($disabled ? ' disabled' : '') . ($childdata ? ' treeview' . (cookie('show_submenu') ? ' treeview-open' : '') : ''),
+                    '@class'     => ($selected ? ' active' : '') . ($disabled ? ' disabled' : '') . ($childdata ? ' treeview' . (config('fastadmin.show_submenu') ? ' treeview-open' : '') : ''),
                 );
                 $str .= strtr($nstr, $value);
             }

BIN
public/assets/img/favicon.ico


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

@@ -678,6 +678,20 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
             Controller.api.bindevent();
         },
         config: function () {
+            $(document).on("click", ".nav-group li a[data-toggle='tab']", function () {
+                if ($(this).attr("href") == "#all") {
+                    $(".tab-pane").addClass("active in");
+                }
+                return;
+                var type = $(this).attr("href").substring(1);
+                if (type == 'all') {
+                    $(".table-config tr").show();
+                } else {
+                    $(".table-config tr").hide();
+                    $(".table-config tr[data-group='" + type + "']").show();
+                }
+            });
+
             Controller.api.bindevent();
         },
         api: {

+ 11 - 8
public/assets/js/backend/auth/adminlog.js

@@ -18,9 +18,11 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
             // 初始化表格
             table.bootstrapTable({
                 url: $.fn.bootstrapTable.defaults.extend.index_url,
+                fixedColumns: true,
+                fixedRightNumber: 1,
                 columns: [
                     [
-                        {field: 'state', checkbox: true, },
+                        {field: 'state', checkbox: true,},
                         {field: 'id', title: 'ID', operate: false},
                         {field: 'username', title: __('Username'), formatter: Table.api.formatter.search},
                         {field: 'title', title: __('Title'), operate: 'LIKE %...%', placeholder: '模糊搜索'},
@@ -28,15 +30,16 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
                         {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: 'RANGE', addclass: 'datetimerange', sortable: true},
-                        {field: 'operate', title: __('Operate'), table: table,
+                        {
+                            field: 'operate', title: __('Operate'), table: table,
                             events: Table.api.events.operate,
                             buttons: [{
-                                    name: 'detail',
-                                    text: __('Detail'),
-                                    icon: 'fa fa-list',
-                                    classname: 'btn btn-info btn-xs btn-detail btn-dialog',
-                                    url: 'auth/adminlog/detail'
-                                }],
+                                name: 'detail',
+                                text: __('Detail'),
+                                icon: 'fa fa-list',
+                                classname: 'btn btn-info btn-xs btn-detail btn-dialog',
+                                url: 'auth/adminlog/detail'
+                            }],
                             formatter: Table.api.formatter.operate
                         }
                     ]

+ 7 - 2
public/assets/js/fast.js

@@ -187,8 +187,13 @@ define(['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, undefine
                     }
                 }, options ? options : {});
                 if ($(window).width() < 480 || (/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream && top.$(".tab-pane.active").size() > 0)) {
-                    options.area = [top.$(".tab-pane.active").width() + "px", top.$(".tab-pane.active").height() + "px"];
-                    options.offset = [top.$(".tab-pane.active").scrollTop() + "px", "0px"];
+                    if (top.$(".tab-pane.active").length > 0) {
+                        options.area = [top.$(".tab-pane.active").width() + "px", top.$(".tab-pane.active").height() + "px"];
+                        options.offset = [top.$(".tab-pane.active").scrollTop() + "px", "0px"];
+                    } else {
+                        options.area = [$(window).width() + "px", $(window).height() + "px"];
+                        options.offset = ["0px", "0px"];
+                    }
                 }
                 return Layer.open(options);
             },

+ 104 - 14
public/assets/js/require-backend.min.js

@@ -845,8 +845,13 @@ define('fast',['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, u
                     }
                 }, options ? options : {});
                 if ($(window).width() < 480 || (/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream && top.$(".tab-pane.active").size() > 0)) {
-                    options.area = [top.$(".tab-pane.active").width() + "px", top.$(".tab-pane.active").height() + "px"];
-                    options.offset = [top.$(".tab-pane.active").scrollTop() + "px", "0px"];
+                    if (top.$(".tab-pane.active").length > 0) {
+                        options.area = [top.$(".tab-pane.active").width() + "px", top.$(".tab-pane.active").height() + "px"];
+                        options.offset = [top.$(".tab-pane.active").scrollTop() + "px", "0px"];
+                    } else {
+                        options.area = [$(window).width() + "px", $(window).height() + "px"];
+                        options.offset = ["0px", "0px"];
+                    }
                 }
                 return Layer.open(options);
             },
@@ -10364,10 +10369,13 @@ define('form',['jquery', 'bootstrap', 'upload', 'validator', 'validator-lang'],
                             }
                             obj.attr("fieldlist-item", true);
                             obj.insertAfter($(tagName + "[fieldlist-item]", container).length > 0 ? $(tagName + "[fieldlist-item]:last", container) : $(tagName + ":first", container));
-                            //兼容旧版本事件
-                            $(".btn-append,.append", container).trigger("fa.event.appendfieldlist", obj);
-                            //新版本事件
-                            container.trigger("fa.event.appendfieldlist", obj);
+                            if ($(".btn-append,.append", container).length > 0) {
+                                //兼容旧版本事件
+                                $(".btn-append,.append", container).trigger("fa.event.appendfieldlist", obj);
+                            } else {
+                                //新版本事件
+                                container.trigger("fa.event.appendfieldlist", obj);
+                            }
                             return obj;
                         };
                         var fieldlist = $(".fieldlist", form);
@@ -10497,6 +10505,89 @@ define('form',['jquery', 'bootstrap', 'upload', 'validator', 'validator-lang'],
                         $("[data-role='autocomplete']").autocomplete();
                     });
                 }
+            },
+            favisible: function (form) {
+                if ($("[data-favisible]", form).length == 0) {
+                    return;
+                }
+                var checkCondition = function (condition) {
+                    var conditionArr = condition.split(/&&/);
+                    var success = 0;
+                    var baseregex = /^([a-z0-9\_]+)([>|<|=|\!]=?)(.*)$/i, strregex = /^('|")(.*)('|")$/, regregex = /^regex:(.*)$/;
+                    // @formatter:off
+                    var operator_result = {
+                        '>': function(a, b) { return a > b; },
+                        '>=': function(a, b) { return a >= b; },
+                        '<': function(a, b) { return a < b; },
+                        '<=': function(a, b) { return a <= b; },
+                        '==': function(a, b) { return a == b; },
+                        '!=': function(a, b) { return a != b; },
+                        'in': function(a, b) { return b.split(/\,/).indexOf(a) > -1; },
+                        'regex': function(a, b) {
+                            var regParts = b.match(/^\/(.*?)\/([gim]*)$/);
+                            var regexp = regParts ? new RegExp(regParts[1], regParts[2]) : new RegExp(b);
+                            return regexp.test(a);
+                        }
+                    };
+                    // @formatter:on
+                    var dataArr = form.serializeArray(), dataObj = {};
+                    $(dataArr).each(function (i, field) {
+                        dataObj[field.name] = field.value;
+                    });
+
+                    $.each(conditionArr, function (i, item) {
+                        var basematches = baseregex.exec(item);
+                        if (basematches) {
+                            var name = basematches[1], operator = basematches[2], value = basematches[3].toString();
+                            if (operator === '=') {
+                                var strmatches = strregex.exec(value);
+                                operator = strmatches ? '==' : 'in';
+                                value = strmatches ? strmatches[2] : value;
+                            }
+                            var regmatches = regregex.exec(value);
+                            if (regmatches) {
+                                operator = 'regex';
+                                value = regmatches[1];
+                            }
+                            var chkname = "row[" + name + "]";
+                            if (typeof dataObj[chkname] === 'undefined') {
+                                return false;
+                            }
+                            var objvalue = dataObj[chkname];
+                            if (['>', '>=', '<', '<='].indexOf(operator) > -1) {
+                                objvalue = parseFloat(objvalue);
+                                value = parseFloat(value);
+                            }
+                            var result = operator_result[operator](objvalue, value);
+                            success += (result ? 1 : 0);
+                        }
+                    });
+                    return success === conditionArr.length;
+                };
+                form.on("keyup change click configchange", "input,select", function () {
+                    $("[data-favisible][data-favisible!='']", form).each(function () {
+                        var visible = $(this).data("favisible");
+                        var groupArr = visible.split(/\|\|/);
+                        var success = 0;
+                        $.each(groupArr, function (i, j) {
+                            if (checkCondition(j)) {
+                                success++;
+                            }
+                        });
+                        if (success > 0) {
+                            $(this).removeClass("hidden");
+                        } else {
+                            $(this).addClass("hidden");
+                        }
+                    });
+                });
+
+                //追加上忽略元素
+                setTimeout(function () {
+                    form.data('validator').options.ignore += ((form.data('validator').options.ignore ? ',' : '') + '[data-favisible] :hidden,[data-favisible]:hidden');
+                }, 0);
+
+                $("input,select", form).trigger("configchange");
             }
         },
         api: {
@@ -10603,6 +10694,8 @@ define('form',['jquery', 'bootstrap', 'upload', 'validator', 'validator-lang'],
                 events.tagsinput(form);
 
                 events.autocomplete(form);
+
+                events.favisible(form);
             },
             custom: {}
         },
@@ -12142,16 +12235,13 @@ define('table',['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstr
                         var btnGroup = $(this);
                         var isPullRight = dropdownMenu.hasClass("pull-right") || dropdownMenu.hasClass("dropdown-menu-right");
                         var left, top, position;
-                        if (dropdownMenu.outerHeight() + btnGroup.outerHeight() > tableBody.outerHeight() - 41) {
+                        if (true || dropdownMenu.outerHeight() + btnGroup.outerHeight() > tableBody.outerHeight() - 41) {
                             position = 'fixed';
                             top = btnGroup.offset().top - $(window).scrollTop() + btnGroup.outerHeight();
-                            left = isPullRight ? btnGroup.offset().left + btnGroup.outerWidth() - dropdownMenu.outerWidth() : btnGroup.offset().left;
-                        } else {
-                            if (btnGroup.offset().top + btnGroup.outerHeight() + dropdownMenu.outerHeight() > tableBody.offset().top + tableBody.outerHeight() - 30) {
-                                position = 'absolute';
-                                left = isPullRight ? -(dropdownMenu.outerWidth() - btnGroup.outerWidth()) : 0;
-                                top = -(dropdownMenu.outerHeight() + 3);
+                            if ((top + dropdownMenu.outerHeight()) > $(window).height()) {
+                                top = btnGroup.offset().top - dropdownMenu.outerHeight() - 5;
                             }
+                            left = isPullRight ? btnGroup.offset().left + btnGroup.outerWidth() - dropdownMenu.outerWidth() : btnGroup.offset().left;
                         }
                         if (left || top) {
                             dropdownMenu.css({
@@ -12511,7 +12601,7 @@ define('table',['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstr
                         dropdown = j.dropdown ? j.dropdown : '';
                         url = j.url ? j.url : '';
                         url = typeof url === 'function' ? url.call(table, row, j) : (url ? Fast.api.fixurl(Table.api.replaceurl(url, row, table)) : 'javascript:;');
-                        classname = j.classname ? j.classname : 'btn-primary btn-' + name + 'one';
+                        classname = j.classname ? j.classname : (dropdown ? 'btn-' + name + 'one' : 'btn-primary btn-' + name + 'one');
                         icon = j.icon ? j.icon : '';
                         text = typeof j.text === 'function' ? j.text.call(table, row, j) : j.text ? j.text : '';
                         title = typeof j.title === 'function' ? j.title.call(table, row, j) : j.title ? j.title : text;

+ 92 - 4
public/assets/js/require-form.js

@@ -351,10 +351,13 @@ define(['jquery', 'bootstrap', 'upload', 'validator', 'validator-lang'], functio
                             }
                             obj.attr("fieldlist-item", true);
                             obj.insertAfter($(tagName + "[fieldlist-item]", container).length > 0 ? $(tagName + "[fieldlist-item]:last", container) : $(tagName + ":first", container));
-                            //兼容旧版本事件
-                            $(".btn-append,.append", container).trigger("fa.event.appendfieldlist", obj);
-                            //新版本事件
-                            container.trigger("fa.event.appendfieldlist", obj);
+                            if ($(".btn-append,.append", container).length > 0) {
+                                //兼容旧版本事件
+                                $(".btn-append,.append", container).trigger("fa.event.appendfieldlist", obj);
+                            } else {
+                                //新版本事件
+                                container.trigger("fa.event.appendfieldlist", obj);
+                            }
                             return obj;
                         };
                         var fieldlist = $(".fieldlist", form);
@@ -484,6 +487,89 @@ define(['jquery', 'bootstrap', 'upload', 'validator', 'validator-lang'], functio
                         $("[data-role='autocomplete']").autocomplete();
                     });
                 }
+            },
+            favisible: function (form) {
+                if ($("[data-favisible]", form).length == 0) {
+                    return;
+                }
+                var checkCondition = function (condition) {
+                    var conditionArr = condition.split(/&&/);
+                    var success = 0;
+                    var baseregex = /^([a-z0-9\_]+)([>|<|=|\!]=?)(.*)$/i, strregex = /^('|")(.*)('|")$/, regregex = /^regex:(.*)$/;
+                    // @formatter:off
+                    var operator_result = {
+                        '>': function(a, b) { return a > b; },
+                        '>=': function(a, b) { return a >= b; },
+                        '<': function(a, b) { return a < b; },
+                        '<=': function(a, b) { return a <= b; },
+                        '==': function(a, b) { return a == b; },
+                        '!=': function(a, b) { return a != b; },
+                        'in': function(a, b) { return b.split(/\,/).indexOf(a) > -1; },
+                        'regex': function(a, b) {
+                            var regParts = b.match(/^\/(.*?)\/([gim]*)$/);
+                            var regexp = regParts ? new RegExp(regParts[1], regParts[2]) : new RegExp(b);
+                            return regexp.test(a);
+                        }
+                    };
+                    // @formatter:on
+                    var dataArr = form.serializeArray(), dataObj = {};
+                    $(dataArr).each(function (i, field) {
+                        dataObj[field.name] = field.value;
+                    });
+
+                    $.each(conditionArr, function (i, item) {
+                        var basematches = baseregex.exec(item);
+                        if (basematches) {
+                            var name = basematches[1], operator = basematches[2], value = basematches[3].toString();
+                            if (operator === '=') {
+                                var strmatches = strregex.exec(value);
+                                operator = strmatches ? '==' : 'in';
+                                value = strmatches ? strmatches[2] : value;
+                            }
+                            var regmatches = regregex.exec(value);
+                            if (regmatches) {
+                                operator = 'regex';
+                                value = regmatches[1];
+                            }
+                            var chkname = "row[" + name + "]";
+                            if (typeof dataObj[chkname] === 'undefined') {
+                                return false;
+                            }
+                            var objvalue = dataObj[chkname];
+                            if (['>', '>=', '<', '<='].indexOf(operator) > -1) {
+                                objvalue = parseFloat(objvalue);
+                                value = parseFloat(value);
+                            }
+                            var result = operator_result[operator](objvalue, value);
+                            success += (result ? 1 : 0);
+                        }
+                    });
+                    return success === conditionArr.length;
+                };
+                form.on("keyup change click configchange", "input,select", function () {
+                    $("[data-favisible][data-favisible!='']", form).each(function () {
+                        var visible = $(this).data("favisible");
+                        var groupArr = visible.split(/\|\|/);
+                        var success = 0;
+                        $.each(groupArr, function (i, j) {
+                            if (checkCondition(j)) {
+                                success++;
+                            }
+                        });
+                        if (success > 0) {
+                            $(this).removeClass("hidden");
+                        } else {
+                            $(this).addClass("hidden");
+                        }
+                    });
+                });
+
+                //追加上忽略元素
+                setTimeout(function () {
+                    form.data('validator').options.ignore += ((form.data('validator').options.ignore ? ',' : '') + '[data-favisible] :hidden,[data-favisible]:hidden');
+                }, 0);
+
+                $("input,select", form).trigger("configchange");
             }
         },
         api: {
@@ -590,6 +676,8 @@ define(['jquery', 'bootstrap', 'upload', 'validator', 'validator-lang'], functio
                 events.tagsinput(form);
 
                 events.autocomplete(form);
+
+                events.favisible(form);
             },
             custom: {}
         },

+ 104 - 14
public/assets/js/require-frontend.min.js

@@ -838,8 +838,13 @@ define('fast',['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, u
                     }
                 }, options ? options : {});
                 if ($(window).width() < 480 || (/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream && top.$(".tab-pane.active").size() > 0)) {
-                    options.area = [top.$(".tab-pane.active").width() + "px", top.$(".tab-pane.active").height() + "px"];
-                    options.offset = [top.$(".tab-pane.active").scrollTop() + "px", "0px"];
+                    if (top.$(".tab-pane.active").length > 0) {
+                        options.area = [top.$(".tab-pane.active").width() + "px", top.$(".tab-pane.active").height() + "px"];
+                        options.offset = [top.$(".tab-pane.active").scrollTop() + "px", "0px"];
+                    } else {
+                        options.area = [$(window).width() + "px", $(window).height() + "px"];
+                        options.offset = ["0px", "0px"];
+                    }
                 }
                 return Layer.open(options);
             },
@@ -10213,10 +10218,13 @@ define('form',['jquery', 'bootstrap', 'upload', 'validator', 'validator-lang'],
                             }
                             obj.attr("fieldlist-item", true);
                             obj.insertAfter($(tagName + "[fieldlist-item]", container).length > 0 ? $(tagName + "[fieldlist-item]:last", container) : $(tagName + ":first", container));
-                            //兼容旧版本事件
-                            $(".btn-append,.append", container).trigger("fa.event.appendfieldlist", obj);
-                            //新版本事件
-                            container.trigger("fa.event.appendfieldlist", obj);
+                            if ($(".btn-append,.append", container).length > 0) {
+                                //兼容旧版本事件
+                                $(".btn-append,.append", container).trigger("fa.event.appendfieldlist", obj);
+                            } else {
+                                //新版本事件
+                                container.trigger("fa.event.appendfieldlist", obj);
+                            }
                             return obj;
                         };
                         var fieldlist = $(".fieldlist", form);
@@ -10346,6 +10354,89 @@ define('form',['jquery', 'bootstrap', 'upload', 'validator', 'validator-lang'],
                         $("[data-role='autocomplete']").autocomplete();
                     });
                 }
+            },
+            favisible: function (form) {
+                if ($("[data-favisible]", form).length == 0) {
+                    return;
+                }
+                var checkCondition = function (condition) {
+                    var conditionArr = condition.split(/&&/);
+                    var success = 0;
+                    var baseregex = /^([a-z0-9\_]+)([>|<|=|\!]=?)(.*)$/i, strregex = /^('|")(.*)('|")$/, regregex = /^regex:(.*)$/;
+                    // @formatter:off
+                    var operator_result = {
+                        '>': function(a, b) { return a > b; },
+                        '>=': function(a, b) { return a >= b; },
+                        '<': function(a, b) { return a < b; },
+                        '<=': function(a, b) { return a <= b; },
+                        '==': function(a, b) { return a == b; },
+                        '!=': function(a, b) { return a != b; },
+                        'in': function(a, b) { return b.split(/\,/).indexOf(a) > -1; },
+                        'regex': function(a, b) {
+                            var regParts = b.match(/^\/(.*?)\/([gim]*)$/);
+                            var regexp = regParts ? new RegExp(regParts[1], regParts[2]) : new RegExp(b);
+                            return regexp.test(a);
+                        }
+                    };
+                    // @formatter:on
+                    var dataArr = form.serializeArray(), dataObj = {};
+                    $(dataArr).each(function (i, field) {
+                        dataObj[field.name] = field.value;
+                    });
+
+                    $.each(conditionArr, function (i, item) {
+                        var basematches = baseregex.exec(item);
+                        if (basematches) {
+                            var name = basematches[1], operator = basematches[2], value = basematches[3].toString();
+                            if (operator === '=') {
+                                var strmatches = strregex.exec(value);
+                                operator = strmatches ? '==' : 'in';
+                                value = strmatches ? strmatches[2] : value;
+                            }
+                            var regmatches = regregex.exec(value);
+                            if (regmatches) {
+                                operator = 'regex';
+                                value = regmatches[1];
+                            }
+                            var chkname = "row[" + name + "]";
+                            if (typeof dataObj[chkname] === 'undefined') {
+                                return false;
+                            }
+                            var objvalue = dataObj[chkname];
+                            if (['>', '>=', '<', '<='].indexOf(operator) > -1) {
+                                objvalue = parseFloat(objvalue);
+                                value = parseFloat(value);
+                            }
+                            var result = operator_result[operator](objvalue, value);
+                            success += (result ? 1 : 0);
+                        }
+                    });
+                    return success === conditionArr.length;
+                };
+                form.on("keyup change click configchange", "input,select", function () {
+                    $("[data-favisible][data-favisible!='']", form).each(function () {
+                        var visible = $(this).data("favisible");
+                        var groupArr = visible.split(/\|\|/);
+                        var success = 0;
+                        $.each(groupArr, function (i, j) {
+                            if (checkCondition(j)) {
+                                success++;
+                            }
+                        });
+                        if (success > 0) {
+                            $(this).removeClass("hidden");
+                        } else {
+                            $(this).addClass("hidden");
+                        }
+                    });
+                });
+
+                //追加上忽略元素
+                setTimeout(function () {
+                    form.data('validator').options.ignore += ((form.data('validator').options.ignore ? ',' : '') + '[data-favisible] :hidden,[data-favisible]:hidden');
+                }, 0);
+
+                $("input,select", form).trigger("configchange");
             }
         },
         api: {
@@ -10452,6 +10543,8 @@ define('form',['jquery', 'bootstrap', 'upload', 'validator', 'validator-lang'],
                 events.tagsinput(form);
 
                 events.autocomplete(form);
+
+                events.favisible(form);
             },
             custom: {}
         },
@@ -11991,16 +12084,13 @@ define('table',['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstr
                         var btnGroup = $(this);
                         var isPullRight = dropdownMenu.hasClass("pull-right") || dropdownMenu.hasClass("dropdown-menu-right");
                         var left, top, position;
-                        if (dropdownMenu.outerHeight() + btnGroup.outerHeight() > tableBody.outerHeight() - 41) {
+                        if (true || dropdownMenu.outerHeight() + btnGroup.outerHeight() > tableBody.outerHeight() - 41) {
                             position = 'fixed';
                             top = btnGroup.offset().top - $(window).scrollTop() + btnGroup.outerHeight();
-                            left = isPullRight ? btnGroup.offset().left + btnGroup.outerWidth() - dropdownMenu.outerWidth() : btnGroup.offset().left;
-                        } else {
-                            if (btnGroup.offset().top + btnGroup.outerHeight() + dropdownMenu.outerHeight() > tableBody.offset().top + tableBody.outerHeight() - 30) {
-                                position = 'absolute';
-                                left = isPullRight ? -(dropdownMenu.outerWidth() - btnGroup.outerWidth()) : 0;
-                                top = -(dropdownMenu.outerHeight() + 3);
+                            if ((top + dropdownMenu.outerHeight()) > $(window).height()) {
+                                top = btnGroup.offset().top - dropdownMenu.outerHeight() - 5;
                             }
+                            left = isPullRight ? btnGroup.offset().left + btnGroup.outerWidth() - dropdownMenu.outerWidth() : btnGroup.offset().left;
                         }
                         if (left || top) {
                             dropdownMenu.css({
@@ -12360,7 +12450,7 @@ define('table',['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstr
                         dropdown = j.dropdown ? j.dropdown : '';
                         url = j.url ? j.url : '';
                         url = typeof url === 'function' ? url.call(table, row, j) : (url ? Fast.api.fixurl(Table.api.replaceurl(url, row, table)) : 'javascript:;');
-                        classname = j.classname ? j.classname : 'btn-primary btn-' + name + 'one';
+                        classname = j.classname ? j.classname : (dropdown ? 'btn-' + name + 'one' : 'btn-primary btn-' + name + 'one');
                         icon = j.icon ? j.icon : '';
                         text = typeof j.text === 'function' ? j.text.call(table, row, j) : j.text ? j.text : '';
                         title = typeof j.title === 'function' ? j.title.call(table, row, j) : j.title ? j.title : text;

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

@@ -509,16 +509,13 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
                         var btnGroup = $(this);
                         var isPullRight = dropdownMenu.hasClass("pull-right") || dropdownMenu.hasClass("dropdown-menu-right");
                         var left, top, position;
-                        if (dropdownMenu.outerHeight() + btnGroup.outerHeight() > tableBody.outerHeight() - 41) {
+                        if (true || dropdownMenu.outerHeight() + btnGroup.outerHeight() > tableBody.outerHeight() - 41) {
                             position = 'fixed';
                             top = btnGroup.offset().top - $(window).scrollTop() + btnGroup.outerHeight();
-                            left = isPullRight ? btnGroup.offset().left + btnGroup.outerWidth() - dropdownMenu.outerWidth() : btnGroup.offset().left;
-                        } else {
-                            if (btnGroup.offset().top + btnGroup.outerHeight() + dropdownMenu.outerHeight() > tableBody.offset().top + tableBody.outerHeight() - 30) {
-                                position = 'absolute';
-                                left = isPullRight ? -(dropdownMenu.outerWidth() - btnGroup.outerWidth()) : 0;
-                                top = -(dropdownMenu.outerHeight() + 3);
+                            if ((top + dropdownMenu.outerHeight()) > $(window).height()) {
+                                top = btnGroup.offset().top - dropdownMenu.outerHeight() - 5;
                             }
+                            left = isPullRight ? btnGroup.offset().left + btnGroup.outerWidth() - dropdownMenu.outerWidth() : btnGroup.offset().left;
                         }
                         if (left || top) {
                             dropdownMenu.css({
@@ -878,7 +875,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
                         dropdown = j.dropdown ? j.dropdown : '';
                         url = j.url ? j.url : '';
                         url = typeof url === 'function' ? url.call(table, row, j) : (url ? Fast.api.fixurl(Table.api.replaceurl(url, row, table)) : 'javascript:;');
-                        classname = j.classname ? j.classname : 'btn-primary btn-' + name + 'one';
+                        classname = j.classname ? j.classname : (dropdown ? 'btn-' + name + 'one' : 'btn-primary btn-' + name + 'one');
                         icon = j.icon ? j.icon : '';
                         text = typeof j.text === 'function' ? j.text.call(table, row, j) : j.text ? j.text : '';
                         title = typeof j.title === 'function' ? j.title.call(table, row, j) : j.title ? j.title : text;

+ 6 - 6
vendor/composer/installed.json

@@ -2783,17 +2783,17 @@
     },
     {
         "name": "karsonzhang/fastadmin-addons",
-        "version": "1.3.1",
-        "version_normalized": "1.3.1.0",
+        "version": "1.3.2",
+        "version_normalized": "1.3.2.0",
         "source": {
             "type": "git",
             "url": "https://github.com/karsonzhang/fastadmin-addons.git",
-            "reference": "68ec965a680612f8046b13fc19ca2c891bfb588e"
+            "reference": "b62f656466811df79586f72bbfbc4636b1c0507d"
         },
         "dist": {
             "type": "zip",
-            "url": "https://api.github.com/repos/karsonzhang/fastadmin-addons/zipball/68ec965a680612f8046b13fc19ca2c891bfb588e",
-            "reference": "68ec965a680612f8046b13fc19ca2c891bfb588e",
+            "url": "https://api.github.com/repos/karsonzhang/fastadmin-addons/zipball/b62f656466811df79586f72bbfbc4636b1c0507d",
+            "reference": "b62f656466811df79586f72bbfbc4636b1c0507d",
             "shasum": "",
             "mirrors": [
                 {
@@ -2807,7 +2807,7 @@
             "php": ">=7.0.0",
             "symfony/var-exporter": "^4.4.13"
         },
-        "time": "2022-01-11 15:06:54",
+        "time": "2022-01-20 10:03:48",
         "type": "library",
         "extra": {
             "think-config": {

+ 1 - 1
vendor/karsonzhang/fastadmin-addons/composer.json

@@ -3,7 +3,7 @@
     "description": "addons package for fastadmin",
     "homepage": "https://github.com/karsonzhang/fastadmin-addons",
     "license": "Apache-2.0",
-    "version": "1.3.1",
+    "version": "1.3.2",
     "authors": [
         {
             "name": "Karson",

+ 1 - 5
vendor/karsonzhang/fastadmin-addons/src/addons/Service.php

@@ -84,13 +84,11 @@ class Service
                     $body = $response->getBody();
                     $content = $body->getContents();
                 } else {
-                    Log::write("[addon]" . $content);
                     //下载返回错误,抛出异常
                     throw new AddonException($json['msg'], $json['code'], $json['data']);
                 }
             }
         } catch (TransferException $e) {
-            Log::write("[addon]" . $e->getMessage());
             throw new Exception(config('app_debug') ? $e->getMessage() : "Addon package download failed");
         }
 
@@ -387,7 +385,7 @@ class Service
         $bootstrapArr = [];
         foreach ($addons as $name => $addon) {
             $bootstrapFile = self::getBootstrapFile($name);
-            if ($addon['state'] && is_file($bootstrapFile) && self::isAuthorization($name)) {
+            if ($addon['state'] && is_file($bootstrapFile)) {
                 $bootstrapArr[] = file_get_contents($bootstrapFile);
             }
         }
@@ -1123,10 +1121,8 @@ EOD;
             $content = $body->getContents();
             $json = (array)json_decode($content, true);
         } catch (TransferException $e) {
-            Log::write("[addon]" . $e->getMessage());
             throw new Exception(config('app_debug') ? $e->getMessage() : __('Network error'));
         } catch (\Exception $e) {
-            Log::write("[addon]" . $e->getMessage());
             throw new Exception(config('app_debug') ? $e->getMessage() : __('Unknown data format'));
         }
         return $json;