diff --git a/application/admin/command/Api.php b/application/admin/command/Api.php index 925cb977..759c4ff8 100644 --- a/application/admin/command/Api.php +++ b/application/admin/command/Api.php @@ -18,6 +18,7 @@ class Api extends Command $this ->setName('api') ->addOption('url', 'u', Option::VALUE_OPTIONAL, 'default api url', '') + ->addOption('cdnurl', 'd', Option::VALUE_OPTIONAL, 'default cdn url', '') ->addOption('module', 'm', Option::VALUE_OPTIONAL, 'module name(admin/index/api)', 'api') ->addOption('output', 'o', Option::VALUE_OPTIONAL, 'output index file name', 'api.html') ->addOption('template', 'e', Option::VALUE_OPTIONAL, '', 'index.html') @@ -36,6 +37,7 @@ class Api extends Command $force = $input->getOption('force'); $url = $input->getOption('url'); + $cdnurl = $input->getOption('cdnurl'); $language = $input->getOption('language'); $template = $input->getOption('template'); if (!preg_match("/^([a-z0-9]+)\.html\$/i", $template)) { @@ -116,15 +118,19 @@ class Api extends Command $classes = array_unique(array_filter($classes)); + $cdnurl = $cdnurl ? : Config::get('site.cdnurl'); + $config = [ 'sitename' => config('site.name'), 'title' => $title, 'author' => config('site.name'), 'description' => '', 'apiurl' => $url, + 'cdnurl' => $cdnurl, 'language' => $language, ]; + Config::set('view_replace_str.__CDN__', $cdnurl); $builder = new Builder($classes); $content = $builder->render($template_file, ['config' => $config, 'lang' => $lang]); diff --git a/application/admin/command/Api/template/index.html b/application/admin/command/Api/template/index.html index 698e84c2..10d5a977 100755 --- a/application/admin/command/Api/template/index.html +++ b/application/admin/command/Api/template/index.html @@ -8,16 +8,10 @@ {$config.title} - + - - - - +
{:build_heading()} diff --git a/application/admin/view/user/user/edit.html b/application/admin/view/user/user/edit.html index f7c50f84..41a3e0ec 100644 --- a/application/admin/view/user/user/edit.html +++ b/application/admin/view/user/user/edit.html @@ -10,7 +10,7 @@
- +
diff --git a/application/api/controller/User.php b/application/api/controller/User.php index c7b2fa32..938ce2c5 100644 --- a/application/api/controller/User.php +++ b/application/api/controller/User.php @@ -179,14 +179,14 @@ class User extends Api $bio = $this->request->post('bio'); $avatar = $this->request->post('avatar', '', 'trim,strip_tags,htmlspecialchars'); if ($username) { - $exists = \app\common\model\User::where('username', $username)->where('id', '<>', $this->auth->id)->find(); + $exists = \app\common\model\User::where('username', $username)->where('id', '<>', $user->id)->find(); if ($exists) { $this->error(__('Username already exists')); } $user->username = $username; } if ($nickname) { - $exists = \app\common\model\User::where('nickname', $nickname)->where('id', '<>', $this->auth->id)->find(); + $exists = \app\common\model\User::where('nickname', $nickname)->where('id', '<>', $user->id)->find(); if ($exists) { $this->error(__('Nickname already exists')); } diff --git a/application/config.php b/application/config.php index 14cb0a66..f0df19b4 100755 --- a/application/config.php +++ b/application/config.php @@ -293,15 +293,15 @@ return [ 'user_level_rule' => '', //会员等级积分字典,键名表示等级,值表示所需的积分 'user_level_dict' => [1 => 0, 2 => 30, 3 => 100, 4 => 500, 5 => 1000, 6 => 2000, 7 => 3000, 8 => 5000, 9 => 8000, 10 => 10000], - //登录验证码 + //后台登录验证码 'login_captcha' => true, - //登录失败超过10次则1天后重试 + //后台登录失败超过10次则1天后重试 'login_failure_retry' => true, - //是否同一账号同一时间只能在一个地方登录 + //后台是否同一账号同一时间只能在一个地方登录 'login_unique' => false, - //是否开启IP变动检测 + //后台是否开启IP变动检测 'loginip_check' => true, - //登录页默认背景图 + //后台登录页默认背景图 'login_background' => "", //是否启用简洁导航,如同时启用多级菜单导航,简洁导航将失效 'simplenav' => false, diff --git a/application/index/lang/zh-cn/user.php b/application/index/lang/zh-cn/user.php index b9089b7e..64c492d7 100755 --- a/application/index/lang/zh-cn/user.php +++ b/application/index/lang/zh-cn/user.php @@ -8,6 +8,7 @@ return [ 'Mobile' => '手机号', 'Email' => '邮箱', 'Captcha' => '验证码', + 'Get code' => '获取验证码', 'Lv' => 'Lv', 'Score' => '积分', 'Day' => '天', diff --git a/application/index/view/user/profile.html b/application/index/view/user/profile.html index 50c7ed86..8a60d762 100644 --- a/application/index/view/user/profile.html +++ b/application/index/view/user/profile.html @@ -123,7 +123,7 @@ @@ -156,7 +156,7 @@ diff --git a/extend/fast/Tree.php b/extend/fast/Tree.php index 0e63a77d..656c87c3 100644 --- a/extend/fast/Tree.php +++ b/extend/fast/Tree.php @@ -55,15 +55,15 @@ class Tree /** * 初始化方法 * @param array $arr 2维数组,例如: - * array( - * 1 => array('id'=>'1','pid'=>0,'name'=>'一级栏目一'), - * 2 => array('id'=>'2','pid'=>0,'name'=>'一级栏目二'), - * 3 => array('id'=>'3','pid'=>1,'name'=>'二级栏目一'), - * 4 => array('id'=>'4','pid'=>1,'name'=>'二级栏目二'), - * 5 => array('id'=>'5','pid'=>2,'name'=>'二级栏目三'), - * 6 => array('id'=>'6','pid'=>3,'name'=>'三级栏目一'), - * 7 => array('id'=>'7','pid'=>3,'name'=>'三级栏目二') - * ) + * array( + * 1 => array('id'=>'1','pid'=>0,'name'=>'一级栏目一'), + * 2 => array('id'=>'2','pid'=>0,'name'=>'一级栏目二'), + * 3 => array('id'=>'3','pid'=>1,'name'=>'二级栏目一'), + * 4 => array('id'=>'4','pid'=>1,'name'=>'二级栏目二'), + * 5 => array('id'=>'5','pid'=>2,'name'=>'二级栏目三'), + * 6 => array('id'=>'6','pid'=>3,'name'=>'三级栏目一'), + * 7 => array('id'=>'7','pid'=>3,'name'=>'三级栏目二') + * ) * @param string $pidname 父字段名称 * @param string $nbsp 空格占位符 * @return Tree @@ -435,4 +435,55 @@ class Tree } return $arr; } + + public function getTreeArrayList($pid = 0) + { + $map = []; + $data = []; + + $items = $this->arr; + + foreach ($items as &$item) { + $item['children'] = []; // 初始化子节点 + $map[$item['id']] = &$item; + } + unset($item); + + foreach ($items as &$item) { + if ($item[$this->pidname] == $pid) { + $data[] = &$item; + } else { + if (isset($map[$item[$this->pidname]])) { + $map[$item[$this->pidname]]['children'][] = &$item; + } + } + } + unset($item); + + return $this->getFlattenTree($data); + } + + /** + * 将多维树形结构数据扁平化处理 + * + * 该函数递归遍历树形结构,将嵌套的节点展开为一维数组, + * 并为每个节点添加层级标识 + * + * @param array $data 树形结构数据,每个节点可能包含children子节点 + * @param int $level 当前节点的层级深度,默认为0(根节点) + * @param array &$result 引用传递的结果数组,用于存储扁平化后的所有节点 + * + * @return array 扁平化后的节点数组,每个节点包含原始数据和level层级信息 + */ + public function getFlattenTree($data, $level = 0, &$result = []) + { + foreach ($data as $node) { + $node['level'] = $level; + $result[] = $node; + if (!empty($node['children'])) { + $this->getFlattenTree($node['children'], $level + 1, $result); + } + } + return $result; + } } diff --git a/public/assets/css/fastadmin.css b/public/assets/css/fastadmin.css index dc94bd4f..56ecbf53 100644 --- a/public/assets/css/fastadmin.css +++ b/public/assets/css/fastadmin.css @@ -714,6 +714,8 @@ a:focus { .sidebar-menu li > a > .pull-right-container { position: absolute; right: 10px; +} +.sidebar-collapse .sidebar-menu li > a > .pull-right-container { top: 50%; margin-top: -7px; } @@ -1424,7 +1426,7 @@ select.form-control { .input-lg + .form-control-feedback.fa, .input-group-lg + .form-control-feedback.fa, .form-group-lg .form-control + .form-control-feedback.fa { - line-height: 45px; + line-height: 44px; } .input-sm + .form-control-feedback.fa, .input-group-sm + .form-control-feedback.fa, diff --git a/public/assets/js/backend/auth/group.js b/public/assets/js/backend/auth/group.js index 8002fd1b..34cfc237 100755 --- a/public/assets/js/backend/auth/group.js +++ b/public/assets/js/backend/auth/group.js @@ -47,7 +47,8 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'jstree'], function ( {field: 'state', checkbox: true,}, {field: 'id', title: 'ID'}, {field: 'pid', title: __('Parent')}, - {field: 'name', title: __('Name'), align: 'left', formatter:function (value, row, index) { + { + field: 'name', title: __('Name'), align: 'left', formatter: function (value, row, index) { return value.toString().replace(/(&|&)nbsp;/g, ' '); } }, @@ -79,14 +80,17 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'jstree'], function ( }, api: { bindevent: function () { + var treeview = $("#treeview"); + + // 表单提交前设定规则集合 Form.api.bindevent($("form[role=form]"), null, null, function () { - if ($("#treeview").length > 0) { - var r = $("#treeview").jstree("get_all_checked"); + if (treeview.length > 0) { + var r = treeview.jstree("get_all_checked"); $("input[name='row[rules]']").val(r.join(',')); } return true; }); - //渲染权限节点树 + //变更级别后需要重建节点树 $(document).on("change", "select[name='row[pid]']", function () { var pid = $(this).data("pid"); @@ -96,37 +100,21 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'jstree'], function ( Backend.api.toastr.error(__('Can not change the parent to self')); return false; } - $.ajax({ - url: "auth/group/roletree", - type: 'post', - dataType: 'json', - data: {id: id, pid: $(this).val()}, - success: function (ret) { - if (ret.hasOwnProperty("code")) { - var data = ret.hasOwnProperty("data") && ret.data != "" ? ret.data : ""; - if (ret.code === 1) { - //销毁已有的节点树 - $("#treeview").jstree("destroy"); - Controller.api.rendertree(data); - } else { - Backend.api.toastr.error(ret.msg); - } - } - }, error: function (e) { - Backend.api.toastr.error(e.message); - } - }); + treeview.jstree(true).refresh(false); }); + //全选和展开 $(document).on("click", "#checkall", function () { - $("#treeview").jstree($(this).prop("checked") ? "check_all" : "uncheck_all"); + treeview.jstree($(this).prop("checked") ? "check_all" : "uncheck_all"); }); $(document).on("click", "#expandall", function () { - $("#treeview").jstree($(this).prop("checked") ? "open_all" : "close_all"); + treeview.jstree($(this).prop("checked") ? "open_all" : "close_all"); }); - $("select[name='row[pid]']").trigger("change"); + + //首次渲染 + Controller.api.rendertree(); }, - rendertree: function (content) { + rendertree: function () { $("#treeview") .on('redraw.jstree', function (e) { $(".layer-footer").attr("domrefresh", Math.random()); @@ -150,7 +138,32 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'jstree'], function ( "plugins": ["checkbox", "types"], "core": { 'check_callback': true, - "data": content + 'strings': { + 'Loading ...': __('Loading') + }, + "data": function (obj, callback) { + var pidObj = $("select[name='row[pid]']"); + $.ajax({ + url: "auth/group/roletree", + type: 'post', + dataType: 'json', + data: {id: pidObj.data('id'), pid: pidObj.val()}, + success: function (ret) { + if (ret.hasOwnProperty("code")) { + var data = ret.hasOwnProperty("data") && ret.data != "" ? ret.data : ""; + if (ret.code === 1) { + callback(data); + } else { + Backend.api.toastr.error(ret.msg); + callback([]); + } + } + }, error: function (e) { + Backend.api.toastr.error(e.message); + callback([]); + } + }); + } } }); } diff --git a/public/assets/js/backend/auth/rule.js b/public/assets/js/backend/auth/rule.js index 6020ab72..7713b0c4 100755 --- a/public/assets/js/backend/auth/rule.js +++ b/public/assets/js/backend/auth/rule.js @@ -14,6 +14,8 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function } }); + var isExpanded = false; + var table = $("#table"); // 初始化表格 @@ -29,7 +31,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function {field: 'icon', title: __('Icon'), formatter: Controller.api.formatter.icon}, {field: 'name', title: __('Name'), align: 'left', formatter: Controller.api.formatter.name}, {field: 'weigh', title: __('Weigh')}, - {field: 'status', title: __('Status'), formatter: Table.api.formatter.status}, + {field: 'status', title: __('Status'), formatter: Table.api.formatter.status, operate: false}, { field: 'ismenu', title: __('Ismenu'), @@ -50,8 +52,13 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function search: false, commonSearch: false, rowAttributes: function (row, index) { - var expanded = $(".btn-toggle-all i").hasClass("fa-minus"); - return row.pid == 0 || expanded ? {} : {style: "display:none"}; + return row.pid == 0 || isExpanded ? {} : {style: "display:none"}; + } + }); + + table.on('post-body.bs.table', function (e, data) { + if (data.length > 2000) { + $(".btn-dragsort").addClass("disabled"); } }); @@ -142,6 +149,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function $(".btn-node-sub:not([data-pid=0])").closest("tr").toggle(show); $(".btn-node-sub").data("shown", show); $(".btn-node-sub > i").toggleClass("fa-caret-down", show).toggleClass("fa-caret-right", !show); + isExpanded = show; }); }, add: function () { @@ -153,13 +161,11 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function api: { formatter: { title: function (value, row, index) { - value = value.toString().replace(/(&|&)nbsp;/g, ' '); var caret = row.haschild == 1 || row.ismenu == 1 ? '' : ''; - value = value.indexOf(" ") > -1 ? value.replace(/(.*) /, "$1" + caret) : caret + value; value = !row.ismenu || row.status == 'hidden' ? "" + value + "" : value; return '' + value + ''; + + (row.haschild == 1 || row.ismenu == 1 ? 'text-primary' : 'disabled') + ' btn-node-sub">' + (' '.repeat(row.level)) + (' '.repeat(parseInt(row.level) * 4)) + caret + ' ' + value + ''; }, name: function (value, row, index) { return !row.ismenu || row.status == 'hidden' ? "" + value + "" : value; diff --git a/public/assets/js/require-form.js b/public/assets/js/require-form.js index 7b8ecdd0..eea360fd 100755 --- a/public/assets/js/require-form.js +++ b/public/assets/js/require-form.js @@ -106,7 +106,7 @@ define(['jquery', 'bootstrap', 'upload', 'validator', 'validator-lang'], functio $('.selectpicker', form).selectpicker(); $(form).on("reset", function () { setTimeout(function () { - $('.selectpicker').selectpicker('refresh').trigger("change"); + $('.selectpicker', form).selectpicker('refresh').trigger("change"); }, 1); }); }); @@ -135,8 +135,13 @@ define(['jquery', 'bootstrap', 'upload', 'validator', 'validator-lang'], functio setTimeout(function () { $(".selectpage", form).each(function () { var selectpage = $(this).data("selectPageObject"); - selectpage.elem.hidden.val($(this).val()); - $(this).selectPageRefresh(); + if ($(this).val()) { + selectpage.elem.hidden.val($(this).val()); + $(this).selectPageRefresh(); + } else { + $(this).selectPageClear(); + } + selectpage.hideResults(selectpage); }); }, 1); }); @@ -159,7 +164,7 @@ define(['jquery', 'bootstrap', 'upload', 'validator', 'validator-lang'], functio require(['citypicker'], function () { $(form).on("reset", function () { setTimeout(function () { - $("[data-toggle='city-picker']").citypicker('refresh'); + $("[data-toggle='city-picker']", form).citypicker('refresh'); }, 1); }); }); @@ -513,10 +518,10 @@ define(['jquery', 'bootstrap', 'upload', 'validator', 'validator-lang'], functio tagsinput: function (form) { if ($("[data-role='tagsinput']", form).length > 0) { require(['tagsinput', 'autocomplete'], function () { - $("[data-role='tagsinput']").tagsinput(); + $("[data-role='tagsinput']", form).tagsinput(); form.on("reset", function () { setTimeout(function () { - $("[data-role='tagsinput']").tagsinput('reset'); + $("[data-role='tagsinput']", form).tagsinput('reset'); }, 0); }); }); diff --git a/public/assets/js/require-table.js b/public/assets/js/require-table.js index 1eb8e0ac..be96a03c 100644 --- a/public/assets/js/require-table.js +++ b/public/assets/js/require-table.js @@ -725,7 +725,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table if (top + 154 > $(window).height()) { top = top - 154; } - if ($(window).width() < 480) { + if (left < 0 || $(window).width() < 480) { top = left = undefined; } Layer.confirm( diff --git a/public/assets/less/fastadmin/sidebar-mini.less b/public/assets/less/fastadmin/sidebar-mini.less index bb9f32b6..7b96bedd 100755 --- a/public/assets/less/fastadmin/sidebar-mini.less +++ b/public/assets/less/fastadmin/sidebar-mini.less @@ -164,8 +164,15 @@ > .pull-right-container { position: absolute; right: 10px; - top: 50%; - margin-top: -7px; } } +.sidebar-collapse { + .sidebar-menu li > a { + > .pull-right-container { + top: 50%; + margin-top: -7px; + } + } +} +