diff --git a/Update.md b/Update.md
new file mode 100644
index 0000000000000000000000000000000000000000..1a4e6d7bc87a27f04a1a8640d4a5aedcc0c5bfc0
--- /dev/null
+++ b/Update.md
@@ -0,0 +1,18 @@
+
+
+20181110
+1、新增用户验证UserAuth中的set、addAllowField,用于设置扩展属性;
+2、完善用户等级功能,新增user_level表,具有level标识图片,并相应新增后台菜单;
+
+20181104
+!!!重要更新:框架有了用户端,两端变三端,开发更高效
+1.新增框架用户端,有user模块和userend脚本,文件结构和功能类似于后台admin模块和backend脚本,便于进行用户端快速开发。
+2.新增user_rule表部分字段,修改相关记录,详见fastadmin.sql文件底部。
+3.新增用户日志功能。
+4.新增用户积分功能。
+5.在application下新增三端(前端、后端、用户端)共用语言包,并修改语言包调用函数。
+6.修改cmd命令CRUD,同时建立admin、user模块的相关文件,如controller和view文件、共用语言包,以及\app\user\controller\Base和\app\user\controller\Base脚本(如若不需要,可删除),模型和验证类文件默认存放目录由admin改至common下;如果生成成功,而运行提示未找到相应的控件,请核对命名空间中admin和user是否符合(因暂未对生成模板修改)。
+7.新建cmd命令usermenu,用于管理user菜单,用法与cmd命令menu一致。
+8.修改首页用户中心链接地址,略过原来的index/user/index,跳到新的用户中心user/index/index。
+克隆地址:https://gitee.com/raorf/fastadmin.git
+用法:覆盖掉原fastadmin。覆盖前请先备份原工作代码。git高手可以使用增量更新。
diff --git a/application/admin/command/Crud.php b/application/admin/command/Crud.php
index db3b2ce223df62d815e0155f4db8cdc2f1ffc87c..f84fba7341e033a7e103020323d3627bfeb1cafb 100755
--- a/application/admin/command/Crud.php
+++ b/application/admin/command/Crud.php
@@ -124,7 +124,7 @@ class Crud extends Command
->addOption('model', 'm', Option::VALUE_OPTIONAL, 'model name', null)
->addOption('fields', 'i', Option::VALUE_OPTIONAL, 'model visible fields', null)
->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override or force delete,without tips', null)
- ->addOption('local', 'l', Option::VALUE_OPTIONAL, 'local model', 1)
+ ->addOption('local', 'l', Option::VALUE_OPTIONAL, 'local model', 0)
->addOption('relation', 'r', Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'relation table name without prefix', null)
->addOption('relationmodel', 'e', Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'relation model name', null)
->addOption('relationforeignkey', 'k', Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'relation foreign key', null)
@@ -241,6 +241,8 @@ class Crud extends Command
//模块
$moduleName = 'admin';
+ $moduleNameUser = 'user';
+ $userPath = $adminPath . '../' . $moduleNameUser . DS;
$modelModuleName = $local ? $moduleName : 'common';
$validateModuleName = $local ? $moduleName : 'common';
@@ -315,6 +317,7 @@ class Crud extends Command
$iconName = is_file($iconPath) && stripos(file_get_contents($iconPath), '@fa-var-' . $table . ':') ? 'fa fa-' . $table : 'fa fa-circle-o';
//控制器
+ list($controllerNamespace, $controllerName, $controllerFileUser, $controllerArrUser) = $this->getControllerData($moduleNameUser, $controller, $table);
list($controllerNamespace, $controllerName, $controllerFile, $controllerArr) = $this->getControllerData($moduleName, $controller, $table);
//模型
list($modelNamespace, $modelName, $modelFile, $modelArr) = $this->getModelData($modelModuleName, $model, $table);
@@ -323,21 +326,29 @@ class Crud extends Command
$controllerUrl = strtolower(implode('/', $controllerArr));
$controllerBaseName = strtolower(implode(DS, $controllerArr));
-
+ $controllerUrlUser = strtolower(implode('/', $controllerArrUser));
+ $controllerBaseNameUser = strtolower(implode(DS, $controllerArrUser));
+
//视图文件
$viewDir = $adminPath . 'view' . DS . $controllerBaseName . DS;
-
+ $viewDirUser = $userPath . 'view' . DS . $controllerBaseNameUser . DS;
+
//最终将生成的文件路径
$javascriptFile = ROOT_PATH . 'public' . DS . 'assets' . DS . 'js' . DS . 'backend' . DS . $controllerBaseName . '.js';
+ $javascriptFileUser = ROOT_PATH . 'public' . DS . 'assets' . DS . 'js' . DS . 'userend' . DS . $controllerBaseName . '.js';
$addFile = $viewDir . 'add.html';
$editFile = $viewDir . 'edit.html';
$indexFile = $viewDir . 'index.html';
- $langFile = $adminPath . 'lang' . DS . Lang::detect() . DS . $controllerBaseName . '.php';
-
+ $addFileUser = $viewDirUser . 'add.html';
+ $editFileUser = $viewDirUser . 'edit.html';
+ $indexFileUser = $viewDirUser . 'index.html';
+ //语言包共用
+ $langFile = APP_PATH . 'lang' . DS . Lang::detect() . DS . $controllerBaseName . '.php';
+
//是否为删除模式
$delete = $input->getOption('delete');
if ($delete) {
- $readyFiles = [$controllerFile, $modelFile, $validateFile, $addFile, $editFile, $indexFile, $langFile, $javascriptFile];
+ $readyFiles = [$controllerFile,$controllerFileUser, $modelFile, $validateFile, $addFile, $editFile, $indexFile,$addFileUser, $editFileUser, $indexFileUser, $langFile, $javascriptFile, $javascriptFileUser];
foreach ($readyFiles as $k => $v) {
$output->warning($v);
}
@@ -369,7 +380,10 @@ class Crud extends Command
if (is_file($controllerFile) && !$force) {
throw new Exception("controller already exists!\nIf you need to rebuild again, use the parameter --force=true ");
}
-
+ if (is_file($controllerFileUser) && !$force) {
+ throw new Exception("controller already exists!\nIf you need to rebuild again, use the parameter --force=true ");
+ }
+
//非覆盖模式时如果存在模型文件则报错
if (is_file($modelFile) && !$force) {
throw new Exception("model already exists!\nIf you need to rebuild again, use the parameter --force=true ");
@@ -381,7 +395,7 @@ class Crud extends Command
}
require $adminPath . 'common.php';
-
+
//从数据库中获取表字段信息
$sql = "SELECT * FROM `information_schema`.`columns` "
. "WHERE TABLE_SCHEMA = ? AND table_name = ? "
@@ -478,6 +492,7 @@ class Crud extends Command
$appendAttrList = [];
$controllerAssignList = [];
$headingHtml = '{:build_heading()}';
+ $headingJs = '';
//循环所有字段,开始构造视图的HTML和JS信息
foreach ($columnList as $k => $v) {
@@ -695,6 +710,7 @@ class Crud extends Command
}
if ($this->headingFilterField && $this->headingFilterField == $field && $itemArr) {
$headingHtml = $this->getReplacedStub('html/heading-html', ['field' => $field]);
+ $headingJs = $this->getReplacedStub('html/heading-js', ['field' => $field]);
}
//排序方式,如果有指定排序字段,否则按主键排序
$order = $field == $this->sortField ? $this->sortField : $order;
@@ -770,6 +786,7 @@ class Crud extends Command
'relationMethodList' => '',
'controllerIndex' => '',
'headingHtml' => $headingHtml,
+ 'headingJs' => $headingJs,
'visibleFieldList' => $fields ? "\$row->visible(['" . implode("','", array_filter(explode(',', $fields))) . "']);" : '',
'appendAttrList' => implode(",\n", $appendAttrList),
'getEnumList' => implode("\n\n", $getEnumArr),
@@ -826,6 +843,7 @@ class Crud extends Command
// 生成控制器文件
$result = $this->writeToFile('controller', $data, $controllerFile);
+ $result = $this->writeToFile('controller', $data, $controllerFileUser);
// 生成模型文件
$result = $this->writeToFile('model', $data, $modelFile);
@@ -845,8 +863,12 @@ class Crud extends Command
$result = $this->writeToFile('add', $data, $addFile);
$result = $this->writeToFile('edit', $data, $editFile);
$result = $this->writeToFile('index', $data, $indexFile);
+ $result = $this->writeToFile('add', $data, $addFileUser);
+ $result = $this->writeToFile('edit', $data, $editFileUser);
+ $result = $this->writeToFile('index', $data, $indexFileUser);
// 生成JS文件
$result = $this->writeToFile('javascript', $data, $javascriptFile);
+ $result = $this->writeToFile('javascript', $data, $javascriptFileUser);
// 生成语言文件
if ($langList) {
$result = $this->writeToFile('lang', $data, $langFile);
@@ -858,6 +880,7 @@ class Crud extends Command
//继续生成菜单
if ($menu) {
exec("php think menu -c {$controllerUrl}");
+ exec("php think usermenu -c {$controllerUrlUser}");
}
$output->info("Build Successed");
@@ -1264,7 +1287,7 @@ EOD;
$selectfilter = ' data-mimetype="image/*"';
}
$multiple = substr($field, -1) == 's' ? ' data-multiple="true"' : ' data-multiple="false"';
- $preview = ' data-preview-id="p-' . $field . '"';
+ $preview = $uploadfilter ? ' data-preview-id="p-' . $field . '"' : '';
$previewcontainer = $preview ? '
' : '';
return <<
diff --git a/application/admin/command/Install/fastadmin.sql b/application/admin/command/Install/fastadmin.sql
index 91bce689fd85e3c5e4f236550d135bc437531a2d..f91f730dc9e0c7afa27929fc84b1a98f28fa0183 100755
--- a/application/admin/command/Install/fastadmin.sql
+++ b/application/admin/command/Install/fastadmin.sql
@@ -424,14 +424,14 @@ CREATE TABLE `fa_user` (
`nickname` varchar(50) NOT NULL DEFAULT '' COMMENT '昵称',
`password` varchar(32) NOT NULL DEFAULT '' COMMENT '密码',
`salt` varchar(30) NOT NULL DEFAULT '' COMMENT '密码盐',
- `email` varchar(100) NOT NULL DEFAULT '' COMMENT '电子邮箱',
+ `email` varchar(100) DEFAULT '' COMMENT '电子邮箱',
`mobile` varchar(11) NOT NULL DEFAULT '' COMMENT '手机号',
`avatar` varchar(255) NOT NULL DEFAULT '' COMMENT '头像',
`level` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '等级',
`gender` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '性别',
- `birthday` date COMMENT '生日',
+ `birthday` date DEFAULT NULL COMMENT '生日',
`bio` varchar(100) NOT NULL DEFAULT '' COMMENT '格言',
- `score` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '积分',
+ `score` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '可用积分',
`successions` int(10) unsigned NOT NULL DEFAULT '1' COMMENT '连续登录天数',
`maxsuccessions` int(10) unsigned NOT NULL DEFAULT '1' COMMENT '最大连续登录天数',
`prevtime` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '上次登录时间',
@@ -445,17 +445,51 @@ CREATE TABLE `fa_user` (
`token` varchar(50) NOT NULL DEFAULT '' COMMENT 'Token',
`status` varchar(30) NOT NULL DEFAULT '' COMMENT '状态',
`verification` varchar(255) NOT NULL DEFAULT '' COMMENT '验证',
+ `userfrom` tinyint(3) DEFAULT '0' COMMENT '推荐人',
+ `lockmoney` decimal(11,2) DEFAULT '0.00' COMMENT '锁定金额',
+ `discount` decimal(10,2) DEFAULT '1.00' COMMENT '会员折扣,默认1不享受',
+ `paypwd` varchar(32) DEFAULT NULL COMMENT '支付密码',
+ `usermoney` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '现金余额',
+ `isshoper` tinyint(3) NOT NULL DEFAULT '0' COMMENT '是否店主',
+ `totalscore` int(11) DEFAULT '0' COMMENT '总积分',
+ `user_money` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '用户金额',
+ `frozen_money` decimal(10,2) DEFAULT '0.00' COMMENT '冻结金额',
+ `distribut_money` decimal(10,2) DEFAULT '0.00' COMMENT '累积分佣金额',
+ `underling_number` int(5) DEFAULT '0' COMMENT '用户下线总数',
+ `address_id` mediumint(8) unsigned NOT NULL DEFAULT '0' COMMENT '默认收货地址',
+ `qq` varchar(20) NOT NULL DEFAULT '' COMMENT 'QQ',
+ `oauth` varchar(10) DEFAULT '' COMMENT '第三方来源 wx weibo alipay',
+ `openid` varchar(100) DEFAULT NULL COMMENT '第三方唯一标示',
+ `unionid` varchar(100) DEFAULT NULL,
+ `province` int(6) DEFAULT '0' COMMENT '省',
+ `city` int(6) DEFAULT '0' COMMENT '市',
+ `district` int(6) DEFAULT '0' COMMENT '区',
+ `total_amount` decimal(10,2) DEFAULT '0.00' COMMENT '消费累计额度',
+ `is_lock` tinyint(1) DEFAULT '0' COMMENT '是否被锁定冻结',
+ `is_distribut` tinyint(1) DEFAULT '0' COMMENT '是否为分销商 0 否 1 是',
+ `first_leader` int(11) DEFAULT '0' COMMENT '第一个上级',
+ `second_leader` int(11) DEFAULT '0' COMMENT '第二个上级',
+ `third_leader` int(11) DEFAULT '0' COMMENT '第三个上级',
+ `message_mask` tinyint(1) NOT NULL DEFAULT '63' COMMENT '消息掩码',
+ `push_id` varchar(30) NOT NULL DEFAULT '' COMMENT '推送id',
+ `distribut_level` tinyint(2) DEFAULT '0' COMMENT '分销商等级',
+ `is_vip` tinyint(1) DEFAULT '0' COMMENT '是否为VIP :0不是,1是',
+ `truename` char(50) DEFAULT NULL COMMENT '真实姓名',
+ `truename_images` varchar(255) DEFAULT NULL COMMENT '身份证',
+ `truename_validated` tinyint(1) DEFAULT '0' COMMENT '实名认证',
+ `mobile_validated` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否验证手机',
+ `email_validated` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否验证电子邮箱',
PRIMARY KEY (`id`),
- KEY `username` (`username`),
KEY `email` (`email`),
- KEY `mobile` (`mobile`)
+ KEY `mobile` (`mobile`),
+ KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='会员表';
-- ----------------------------
-- Records of fa_user
-- ----------------------------
BEGIN;
-INSERT INTO `fa_user` VALUES (1, 1, 'admin', 'admin', 'c13f62012fd6a8fdf06b3452a94430e5', 'rpR6Bv', 'admin@163.com', '13888888888', '/assets/img/avatar.png', 0, 0, '2017-04-15', '', 0, 1, 1, 1516170492, 1516171614, '127.0.0.1', 0, '127.0.0.1', 1491461418, 0, 1516171614, '', 'normal','');
+INSERT INTO `fa_user` VALUES (1,1,'admin','admin','c13f62012fd6a8fdf06b3452a94430e5','rpR6Bv','admin@163.com','13888888888','/assets/img/avatar.png',1,0,'2017-04-15','',5,3,3,1541820187,1541820390,'127.0.0.1',0,'127.0.0.1',1491461418,0,1541820390,'','normal','',0,0.00,1.00,NULL,0.00,0,0,0.00,0.00,0.00,0,0,'','',NULL,NULL,0,0,0,0.00,0,0,0,0,0,63,'',0,0,NULL,NULL,0,0,0);
COMMIT;
-- ----------------------------
@@ -494,25 +528,34 @@ CREATE TABLE `fa_user_rule` (
`updatetime` int(10) DEFAULT NULL COMMENT '更新时间',
`weigh` int(10) DEFAULT '0' COMMENT '权重',
`status` enum('normal','hidden') DEFAULT NULL COMMENT '状态',
+ `icon` char(30) NOT NULL DEFAULT 'fa fa-circle-o' COMMENT '图标',
+ `condition` varchar(255) NOT NULL DEFAULT '' COMMENT '条件',
+ `type` enum('menu','file') NOT NULL DEFAULT 'file' COMMENT 'menu为菜单,file为权限节点',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='会员规则表';
-
--- ----------------------------
--- Records of fa_user_rule
--- ----------------------------
+#
+# 修改用户规则表相应记录
+#
BEGIN;
-INSERT INTO `fa_user_rule` VALUES (1, 0, 'index', '前台', '', 1, 1516168079, 1516168079, 1, 'normal');
-INSERT INTO `fa_user_rule` VALUES (2, 0, 'api', 'API接口', '', 1, 1516168062, 1516168062, 2, 'normal');
-INSERT INTO `fa_user_rule` VALUES (3, 1, 'user', '会员模块', '', 1, 1515386221, 1516168103, 12, 'normal');
-INSERT INTO `fa_user_rule` VALUES (4, 2, 'user', '会员模块', '', 1, 1515386221, 1516168092, 11, 'normal');
-INSERT INTO `fa_user_rule` VALUES (5, 3, 'index/user/login', '登录', '', 0, 1515386247, 1515386247, 5, 'normal');
-INSERT INTO `fa_user_rule` VALUES (6, 3, 'index/user/register', '注册', '', 0, 1515386262, 1516015236, 7, 'normal');
-INSERT INTO `fa_user_rule` VALUES (7, 3, 'index/user/index', '会员中心', '', 0, 1516015012, 1516015012, 9, 'normal');
-INSERT INTO `fa_user_rule` VALUES (8, 3, 'index/user/profile', '个人资料', '', 0, 1516015012, 1516015012, 4, 'normal');
-INSERT INTO `fa_user_rule` VALUES (9, 4, 'api/user/login', '登录', '', 0, 1515386247, 1515386247, 6, 'normal');
-INSERT INTO `fa_user_rule` VALUES (10, 4, 'api/user/register', '注册', '', 0, 1515386262, 1516015236, 8, 'normal');
-INSERT INTO `fa_user_rule` VALUES (11, 4, 'api/user/index', '会员中心', '', 0, 1516015012, 1516015012, 10, 'normal');
-INSERT INTO `fa_user_rule` VALUES (12, 4, 'api/user/profile', '个人资料', '', 0, 1516015012, 1516015012, 3, 'normal');
+INSERT INTO `fa_user_rule` (`id`,`pid`,`name`,`title`,`remark`,`ismenu`,`createtime`,`updatetime`,`weigh`,`status`,`icon`,`condition`,`type`) VALUES
+(1,0,'dashboard','Dashboard','Dashboard tips',1,1516168079,1516168079,99,'normal','fa fa-dashboard','','file'),
+(2,0,'api','API接口','',1,1516168062,1537758838,2,'hidden','','','file'),
+(3,1,'dashboard/index','View','',0,1515386247,1515386247,5,'normal','fa fa-circle-o','','file'),
+(4,2,'api/user','会员模块','',1,1515386221,1537758859,11,'hidden','','','file'),
+(5,0,'user','User center','',1,1515386262,1516015236,7,'normal','fa fa-user-o','','file'),
+(6,5,'user/profile','Profile','',1,1516015012,1516015012,10,'normal','fa fa-circle-o','','file'),
+(7,6,'user/profile/index','View','',0,1516015012,1516015012,4,'normal','fa fa-circle-o','','file'),
+(8,6,'user/profile/edit','Edit','',0,1516015012,1516015012,4,'normal','fa fa-circle-o','','file'),
+(9,4,'api/user/login','登录','',0,1515386247,1537758859,6,'hidden','','','file'),
+(10,4,'api/user/register','注册','',0,1515386262,1537758859,8,'hidden','','','file'),
+(11,4,'api/user/index','会员中心','',0,1516015012,1537758859,10,'hidden','','','file'),
+(12,4,'api/user/profile','个人资料','',0,1516015012,1537758859,3,'hidden','','','file'),
+(13,5,'user/scorelog','积分日志','',1,1541045799,1541052272,9,'normal','fa fa-circle-o','','file'),
+(14,13,'user/scorelog/index','查看','',0,1541045799,1541050931,0,'normal','fa fa-circle-o','','file'),
+(15,5,'user/changepwd','Change password','',1,1541045799,1541056067,8,'normal','fa fa-circle-o','','file'),
+(16,15,'user/changepwd/index','View',NULL,0,1541045799,1541045799,0,'normal','fa fa-circle-o','','file'),
+(17,5,'user/log','User log','',1,1516015012,1541043105,7,'normal','fa fa-circle-o','','file'),
+(18,17,'user/log/index','View','',0,1516015012,1537758859,3,'normal','fa fa-circle-o','','file');
COMMIT;
-- ----------------------------
@@ -568,4 +611,66 @@ BEGIN;
INSERT INTO `fa_version` (`id`, `oldversion`, `newversion`, `packagesize`, `content`, `downloadurl`, `enforce`, `createtime`, `updatetime`, `weigh`, `status`) VALUES
(1, '1.1.1,2', '1.2.1', '20M', '更新内容', 'https://www.fastadmin.net/download.html', 1, 1520425318, 0, 0, 'normal');
COMMIT;
+
+#
+# 增加用户日志表
+#
+DROP TABLE IF EXISTS `fa_user_log`;
+CREATE TABLE `fa_user_log` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '用户ID',
+ `username` varchar(30) NOT NULL DEFAULT '' COMMENT '管理员名字',
+ `url` varchar(1500) NOT NULL DEFAULT '' COMMENT '操作页面',
+ `title` varchar(100) NOT NULL DEFAULT '' COMMENT '日志标题',
+ `content` text NOT NULL COMMENT '内容',
+ `ip` varchar(50) NOT NULL DEFAULT '' COMMENT 'IP',
+ `useragent` varchar(255) NOT NULL DEFAULT '' COMMENT 'User-Agent',
+ `createtime` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '操作时间',
+ PRIMARY KEY (`id`),
+ KEY `name` (`username`)
+) ENGINE=InnoDB AUTO_INCREMENT=322 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='用户日志';
+
+#
+# 增加用户等级表
+#
+DROP TABLE IF EXISTS `fa_user_level`;
+CREATE TABLE `fa_user_level` (
+ `level_id` smallint(4) unsigned NOT NULL AUTO_INCREMENT COMMENT '表id',
+ `level_name` varchar(30) DEFAULT NULL COMMENT '头衔名称',
+ `amount` decimal(10,2) DEFAULT NULL COMMENT '必要积分',
+ `discount` smallint(4) DEFAULT '0' COMMENT '折扣',
+ `describe` varchar(200) DEFAULT NULL COMMENT '头街描述',
+ `level_img` varchar(150) DEFAULT NULL COMMENT '标识图片',
+ PRIMARY KEY (`level_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='会员等级';
+
+#
+# 增加用户等级数据
+#
+BEGIN;
+INSERT INTO `fa_user_level` VALUES
+(1,'天降玄铁',0.00,100,'若如初相见,若如初相恋','/uploads/user/level/20181110/57edd6ae5d8b8.png'),
+(2,'百炼成钢',100,95,'','/uploads/user/level/20181110/57edd6d446b75.png'),
+(3,'倔强青铜',500,90,'','/uploads/user/level/20181110/57edd6efc8757.png'),
+(4,'秩序白银',1000,85,'','/uploads/user/level/20181110/57edd70cbd0f5.png'),
+(5,'荣耀黄金',5000,80,'',NULL),
+(6,'尊贵铂金',10000,75,'',NULL),
+(7,'永恒钻石',50000,65,'',NULL),
+(8,'至尊星耀',100000,60,'',NULL),
+(9,'最强王者',1000000,50,'',NULL);
+COMMIT;
+#
+# 增加后台菜单“用户等级”
+#
+BEGIN;
+INSERT INTO `fa_auth_rule` (`id`,`type`,`pid`,`name`,`title`,`icon`,`condition`,`remark`,`ismenu`,`createtime`,`updatetime`,`weigh`,`status`) VALUES
+(161,'file',66,'user/level','会员等级','fa fa-circle-o','','',1,1541814848,1541814848,0,'normal'),
+(162,'file',161,'user/level/index','查看','fa fa-circle-o','','',0,1541814848,1541814848,0,'normal'),
+(163,'file',161,'user/level/add','添加','fa fa-circle-o','','',0,1541814848,1541814848,0,'normal'),
+(164,'file',161,'user/level/edit','编辑','fa fa-circle-o','','',0,1541814848,1541814848,0,'normal'),
+(165,'file',161,'user/level/del','删除','fa fa-circle-o','','',0,1541814848,1541814848,0,'normal'),
+(166,'file',161,'user/level/multi','批量更新','fa fa-circle-o','','',0,1541814848,1541814848,0,'normal');
+COMMIT;
+
SET FOREIGN_KEY_CHECKS = 1;
+
diff --git a/application/admin/command/Min.php b/application/admin/command/Min.php
index aa434bb82b18545c37e1b6bd8ff2009e8859b309..eeafc0c82e2ab70ee197aa5a0a0e3804c84eafe0 100644
--- a/application/admin/command/Min.php
+++ b/application/admin/command/Min.php
@@ -25,7 +25,7 @@ class Min extends Command
{
$this
->setName('min')
- ->addOption('module', 'm', Option::VALUE_REQUIRED, 'module name(frontend or backend),use \'all\' when build all modules', null)
+ ->addOption('module', 'm', Option::VALUE_REQUIRED, 'module name(frontend or backend or userend),use \'all\' when build all modules', null)
->addOption('resource', 'r', Option::VALUE_REQUIRED, 'resource name(js or css),use \'all\' when build all resources', null)
->addOption('optimize', 'o', Option::VALUE_OPTIONAL, 'optimize type(uglify|closure|none)', 'none')
->setDescription('Compress js and css file');
@@ -37,7 +37,7 @@ class Min extends Command
$resource = $input->getOption('resource') ?: '';
$optimize = $input->getOption('optimize') ?: 'none';
- if (!$module || !in_array($module, ['frontend', 'backend', 'all']))
+ if (!$module || !in_array($module, ['frontend', 'backend','userend', 'all']))
{
throw new Exception('Please input correct module name');
}
@@ -46,7 +46,7 @@ class Min extends Command
throw new Exception('Please input correct resource name');
}
- $moduleArr = $module == 'all' ? ['frontend', 'backend'] : [$module];
+ $moduleArr = $module == 'all' ? ['frontend', 'backend', 'userend'] : [$module];
$resourceArr = $resource == 'all' ? ['js', 'css'] : [$resource];
$minPath = __DIR__ . DS . 'Min' . DS;
diff --git a/application/admin/command/Usermenu.php b/application/admin/command/Usermenu.php
new file mode 100644
index 0000000000000000000000000000000000000000..9f3eae5accfc1788db97c847bbf307268b0e0443
--- /dev/null
+++ b/application/admin/command/Usermenu.php
@@ -0,0 +1,289 @@
+setName('usermenu')
+ ->addOption('controller', 'c', Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, 'controller name,use \'all-controller\' when build all menu', null)
+ ->addOption('delete', 'd', Option::VALUE_OPTIONAL, 'delete the specified menu', '')
+ ->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force delete menu,without tips', null)
+ ->addOption('equal', 'e', Option::VALUE_OPTIONAL, 'the controller must be equal', null)
+ ->setDescription('Build auth menu from controller');
+ //要执行的controller必须一样,不适用模糊查询
+ }
+
+ protected function execute(Input $input, Output $output)
+ {
+
+ $this->model = new UserRule();
+ $userPath = dirname(__DIR__) . '../user' . DS;
+ //控制器名
+ $controller = $input->getOption('controller') ?: '';
+ if (!$controller) {
+ throw new Exception("please input controller name");
+ }
+ $force = $input->getOption('force');
+ //是否为删除模式
+ $delete = $input->getOption('delete');
+ //是否控制器完全匹配
+ $equal = $input->getOption('equal');
+
+
+ if ($delete) {
+ if (in_array('all-controller', $controller)) {
+ throw new Exception("could not delete all menu");
+ }
+ $ids = [];
+ $list = $this->model->where(function ($query) use ($controller, $equal) {
+ foreach ($controller as $index => $item) {
+ if ($equal)
+ $query->whereOr('name', 'eq', $item);
+ else
+ $query->whereOr('name', 'like', strtolower($item) . "%");
+ }
+ })->select();
+ foreach ($list as $k => $v) {
+ $output->warning($v->name);
+ $ids[] = $v->id;
+ }
+ if (!$ids) {
+ throw new Exception("There is no menu to delete");
+ }
+ if (!$force) {
+ $output->info("Are you sure you want to delete all those menu? Type 'yes' to continue: ");
+ $line = fgets(STDIN);
+ if (trim($line) != 'yes') {
+ throw new Exception("Operation is aborted!");
+ }
+ }
+ UserRule::destroy($ids);
+
+ Cache::rm("__usermenu__");
+ $output->info("Delete Successed");
+ return;
+ }
+
+ if (!in_array('all-controller', $controller)) {
+ foreach ($controller as $index => $item) {
+ $controllerArr = explode('/', $item);
+ end($controllerArr);
+ $key = key($controllerArr);
+ $controllerArr[$key] = ucfirst($controllerArr[$key]);
+ $userPath = dirname(__DIR__) . DS . 'controller' . DS . implode(DS, $controllerArr) . '.php';
+ if (!is_file($userPath)) {
+ $output->error("controller not found");
+ return;
+ }
+ $this->importRule($item);
+ }
+
+ } else {
+ $this->model->where('id', '>', 0)->delete();
+ $controllerDir = $userPath . 'controller' . DS;
+ // 扫描新的节点信息并导入
+ $treelist = $this->import($this->scandir($controllerDir));
+ }
+ Cache::rm("__usermenu__");
+ $output->info("Build Successed!");
+ }
+
+ /**
+ * 递归扫描文件夹
+ * @param string $dir
+ * @return array
+ */
+ public function scandir($dir)
+ {
+ $result = [];
+ $cdir = scandir($dir);
+ foreach ($cdir as $value) {
+ if (!in_array($value, array(".", ".."))) {
+ if (is_dir($dir . DS . $value)) {
+ $result[$value] = $this->scandir($dir . DS . $value);
+ } else {
+ $result[] = $value;
+ }
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * 导入规则节点
+ * @param array $dirarr
+ * @param array $parentdir
+ * @return array
+ */
+ public function import($dirarr, $parentdir = [])
+ {
+ $menuarr = [];
+ foreach ($dirarr as $k => $v) {
+ if (is_array($v)) {
+ //当前是文件夹
+ $nowparentdir = array_merge($parentdir, [$k]);
+ $this->import($v, $nowparentdir);
+ } else {
+ //只匹配PHP文件
+ if (!preg_match('/^(\w+)\.php$/', $v, $matchone)) {
+ continue;
+ }
+ //导入文件
+ $controller = ($parentdir ? implode('/', $parentdir) . '/' : '') . $matchone[1];
+ $this->importRule($controller);
+ }
+ }
+
+ return $menuarr;
+ }
+
+ protected function importRule($controller)
+ {
+ $controller = str_replace('\\', '/', $controller);
+ $controllerArr = explode('/', $controller);
+ end($controllerArr);
+ $key = key($controllerArr);
+ $controllerArr[$key] = ucfirst($controllerArr[$key]);
+ $classSuffix = Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : '';
+ $className = "\\app\\user\\controller\\" . implode("\\", $controllerArr) . $classSuffix;
+
+ $pathArr = $controllerArr;
+ array_unshift($pathArr, '', 'application', 'user', 'controller');
+ $classFile = ROOT_PATH . implode(DS, $pathArr) . $classSuffix . ".php";
+ $classContent = file_get_contents($classFile);
+ $uniqueName = uniqid("FastAdmin") . $classSuffix;
+ $classContent = str_replace("class " . $controllerArr[$key] . $classSuffix . " ", 'class ' . $uniqueName . ' ', $classContent);
+ $classContent = preg_replace("/namespace\s(.*);/", 'namespace ' . __NAMESPACE__ . ";", $classContent);
+
+ //临时的类文件
+ $tempClassFile = __DIR__ . DS . $uniqueName . ".php";
+ file_put_contents($tempClassFile, $classContent);
+ $className = "\\app\\admin\\command\\" . $uniqueName;
+ //反射机制调用类的注释和方法名
+ $reflector = new ReflectionClass($className);
+
+ if (isset($tempClassFile)) {
+ //删除临时文件
+ @unlink($tempClassFile);
+ }
+
+ //只匹配公共的方法
+ $methods = $reflector->getMethods(ReflectionMethod::IS_PUBLIC);
+ $classComment = $reflector->getDocComment();
+ //判断是否有启用软删除
+ $softDeleteMethods = ['destroy', 'restore', 'recyclebin'];
+ $withSofeDelete = false;
+ preg_match_all("/\\\$this\->model\s*=\s*model\('(\w+)'\);/", $classContent, $matches);
+ if (isset($matches[1]) && isset($matches[1][0]) && $matches[1][0]) {
+ \think\Request::instance()->module('user');
+ $model = model($matches[1][0]);
+ if (in_array('trashed', get_class_methods($model))) {
+ $withSofeDelete = true;
+ }
+ }
+ //忽略的类
+ if (stripos($classComment, "@internal") !== FALSE) {
+ return;
+ }
+ preg_match_all('#(@.*?)\n#s', $classComment, $annotations);
+ $controllerIcon = 'fa fa-circle-o';
+ $controllerRemark = '';
+ //判断注释中是否设置了icon值
+ if (isset($annotations[1])) {
+ foreach ($annotations[1] as $tag) {
+ if (stripos($tag, '@icon') !== FALSE) {
+ $controllerIcon = substr($tag, stripos($tag, ' ') + 1);
+ }
+ if (stripos($tag, '@remark') !== FALSE) {
+ $controllerRemark = substr($tag, stripos($tag, ' ') + 1);
+ }
+ }
+ }
+ //过滤掉其它字符
+ $controllerTitle = trim(preg_replace(array('/^\/\*\*(.*)[\n\r\t]/u', '/[\s]+\*\//u', '/\*\s@(.*)/u', '/[\s|\*]+/u'), '', $classComment));
+
+ //导入中文语言包
+ \think\Lang::load(dirname(__DIR__) . DS . 'lang/zh-cn.php');
+
+ //先导入菜单的数据
+ $pid = 0;
+ foreach ($controllerArr as $k => $v) {
+ $key = $k + 1;
+ $name = strtolower(implode('/', array_slice($controllerArr, 0, $key)));
+ $title = (!isset($controllerArr[$key]) ? $controllerTitle : '');
+ $icon = (!isset($controllerArr[$key]) ? $controllerIcon : 'fa fa-list');
+ $remark = (!isset($controllerArr[$key]) ? $controllerRemark : '');
+ $title = $title ? $title : $v;
+ $rulemodel = $this->model->get(['name' => $name]);
+ if (!$rulemodel) {
+ $this->model
+ ->data(['pid' => $pid, 'name' => $name, 'title' => $title, 'icon' => $icon, 'remark' => $remark, 'ismenu' => 1, 'status' => 'normal'])
+ ->isUpdate(false)
+ ->save();
+ $pid = $this->model->id;
+ } else {
+ $pid = $rulemodel->id;
+ }
+ }
+ $ruleArr = [];
+ foreach ($methods as $m => $n) {
+ //过滤特殊的类
+ if (substr($n->name, 0, 2) == '__' || $n->name == '_initialize') {
+ continue;
+ }
+ //未启用软删除时过滤相关方法
+ if (!$withSofeDelete && in_array($n->name, $softDeleteMethods)) {
+ continue;
+ }
+ //只匹配符合的方法
+ if (!preg_match('/^(\w+)' . Config::get('action_suffix') . '/', $n->name, $matchtwo)) {
+ unset($methods[$m]);
+ continue;
+ }
+ $comment = $reflector->getMethod($n->name)->getDocComment();
+ //忽略的方法
+ if (stripos($comment, "@internal") !== FALSE) {
+ continue;
+ }
+ //过滤掉其它字符
+ $comment = preg_replace(array('/^\/\*\*(.*)[\n\r\t]/u', '/[\s]+\*\//u', '/\*\s@(.*)/u', '/[\s|\*]+/u'), '', $comment);
+
+ $title = $comment ? $comment : ucfirst($n->name);
+
+ //获取主键,作为UserRule更新依据
+ $id = $this->getUserRulePK($name . "/" . strtolower($n->name));
+
+ $ruleArr[] = array('id' => $id, 'pid' => $pid, 'name' => $name . "/" . strtolower($n->name), 'icon' => 'fa fa-circle-o', 'title' => $title, 'ismenu' => 0, 'status' => 'normal');
+ }
+ $this->model->isUpdate(false)->saveAll($ruleArr);
+ }
+
+ //获取主键
+ protected function getUserRulePK($name)
+ {
+ if (!empty($name)) {
+ $id = $this->model
+ ->where('name', $name)
+ ->value('id');
+ return $id ? $id : null;
+ }
+ }
+
+}
diff --git a/application/admin/controller/user/Level.php b/application/admin/controller/user/Level.php
new file mode 100644
index 0000000000000000000000000000000000000000..d0b66679ecc103e2d3209bd88ef4ab0415515ae1
--- /dev/null
+++ b/application/admin/controller/user/Level.php
@@ -0,0 +1,35 @@
+model = new \app\common\model\user\Level;
+
+ }
+
+ /**
+ * 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
+ * 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
+ * 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
+ */
+
+
+}
diff --git a/application/admin/controller/user/Log.php b/application/admin/controller/user/Log.php
new file mode 100644
index 0000000000000000000000000000000000000000..f9e6984bab0716d0cb9a6b26f2b20f244b7dc476
--- /dev/null
+++ b/application/admin/controller/user/Log.php
@@ -0,0 +1,35 @@
+model = new \app\common\model\UserLog;
+
+ }
+
+ /**
+ * 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
+ * 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
+ * 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
+ */
+
+
+}
diff --git a/application/admin/controller/user/Rule.php b/application/admin/controller/user/Rule.php
index 4f818d7a1e232d073eb16d5ca378c41f950499f7..73e2dbe2c3e768b93d0df3df062bf5d550d94f55 100644
--- a/application/admin/controller/user/Rule.php
+++ b/application/admin/controller/user/Rule.php
@@ -63,6 +63,69 @@ class Rule extends Backend
return $this->view->fetch();
}
+ /**
+ * 添加
+ */
+ public function add()
+ {
+ if ($this->request->isPost())
+ {
+ $params = $this->request->post("row/a", [], 'strip_tags');
+ if ($params)
+ {
+ if (!$params['ismenu'] && !$params['pid'])
+ {
+ $this->error(__('The non-menu rule must have parent'));
+ }
+ $result = $this->model->validate()->save($params);
+ if ($result === FALSE)
+ {
+ $this->error($this->model->getError());
+ }
+ \think\Cache::rm('__usermenu__');
+ $this->success();
+ }
+ $this->error();
+ }
+ return $this->view->fetch();
+ }
+
+ /**
+ * 编辑
+ */
+ public function edit($ids = NULL)
+ {
+ $row = $this->model->get(['id' => $ids]);
+ if (!$row)
+ $this->error(__('No Results were found'));
+ if ($this->request->isPost())
+ {
+ $params = $this->request->post("row/a", [], 'strip_tags');
+ if ($params)
+ {
+ if (!$params['ismenu'] && !$params['pid'])
+ {
+ $this->error(__('The non-menu rule must have parent'));
+ }
+ //这里需要针对name做唯一验证
+ $ruleValidate = \think\Loader::validate('UserRule');
+ $ruleValidate->rule([
+ 'name' => 'require|format|unique:UserRule,name,' . $row->id,
+ ]);
+ $result = $row->validate()->save($params);
+ if ($result === FALSE)
+ {
+ $this->error($row->getError());
+ }
+ \think\Cache::rm('__usermenu__');
+ $this->success();
+ }
+ $this->error();
+ }
+ $this->view->assign("row", $row);
+ return $this->view->fetch();
+ }
+
/**
* 删除
*/
diff --git a/application/admin/controller/user/Scorelog.php b/application/admin/controller/user/Scorelog.php
new file mode 100644
index 0000000000000000000000000000000000000000..1abf82a76ee9ffd1f3352eb6b033213d3285ea9a
--- /dev/null
+++ b/application/admin/controller/user/Scorelog.php
@@ -0,0 +1,34 @@
+model = new \app\common\model\user\Scorelog;
+
+ }
+
+ /**
+ * 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
+ * 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
+ * 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
+ */
+
+}
diff --git a/application/admin/lang/zh-cn/user/rule.php b/application/admin/lang/zh-cn/user/rule.php
index 197da5c4d3d244b4f16a560aaa3fcee4edc96034..d78b089f06090ec055478eeb218189cd050b7bdf 100644
--- a/application/admin/lang/zh-cn/user/rule.php
+++ b/application/admin/lang/zh-cn/user/rule.php
@@ -6,10 +6,22 @@ return [
'Title' => '标题',
'Remark' => '备注',
'Ismenu' => '是否菜单',
+ 'Toggle all' => '显示全部',
+ 'Condition' => '规则条件',
+ 'Icon' => '图标',
+ 'Search icon' => '搜索图标',
+ 'Controller/Action' => '控制器名/方法名',
'Createtime' => '创建时间',
'Updatetime' => '更新时间',
+ 'Toggle menu visible' => '点击切换菜单显示',
+ 'Toggle sub menu' => '点击切换子菜单',
'Menu tips' => '规则任意,请不可重复,仅做层级显示,无需匹配控制器和方法',
'Node tips' => '模块/控制器/方法名',
+ 'The non-menu rule must have parent' => '非菜单规则节点必须有父级',
+ 'Name only supports letters, numbers, underscore and slash' => 'URL规则只能是小写字母、数字、下划线和/组成',
'Weigh' => '权重',
- 'Status' => '状态'
+ 'Status' => '状态',
+ 'Type' => '控制方式',
+ 'Menu' => '菜单',
+ 'File' => '权限节点',
];
diff --git a/application/admin/view/user/level/add.html b/application/admin/view/user/level/add.html
new file mode 100644
index 0000000000000000000000000000000000000000..73b28ef67acd9cf7bff09027de432e45f5550f15
--- /dev/null
+++ b/application/admin/view/user/level/add.html
@@ -0,0 +1,48 @@
+
diff --git a/application/admin/view/user/level/edit.html b/application/admin/view/user/level/edit.html
new file mode 100644
index 0000000000000000000000000000000000000000..99f082c9128fe93e33d8a59ca2998fbc9dd96c9f
--- /dev/null
+++ b/application/admin/view/user/level/edit.html
@@ -0,0 +1,48 @@
+
diff --git a/application/admin/view/user/level/index.html b/application/admin/view/user/level/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..245435006ed9cb751e6b204ce544356748e3862e
--- /dev/null
+++ b/application/admin/view/user/level/index.html
@@ -0,0 +1,33 @@
+
+ {:build_heading()}
+
+
+
diff --git a/application/admin/view/user/log/index.html b/application/admin/view/user/log/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..c362b694837d6e32f9d1445bf41b8b02f446ea94
--- /dev/null
+++ b/application/admin/view/user/log/index.html
@@ -0,0 +1,33 @@
+
+ {:build_heading()}
+
+
+
diff --git a/application/admin/view/user/rule/add.html b/application/admin/view/user/rule/add.html
index 9393e9713e2658d9d1e64caaf86b9102b7da7b36..41035356f558af8d35933df3780074b54df566fb 100644
--- a/application/admin/view/user/rule/add.html
+++ b/application/admin/view/user/rule/add.html
@@ -5,7 +5,12 @@
{:build_radios('row[ismenu]', ['1'=>__('Yes'), '0'=>__('No')])}
-
+
@@ -36,6 +44,18 @@
+
+
+{include file="auth/rule/tpl" /}
\ No newline at end of file
diff --git a/application/admin/view/user/rule/edit.html b/application/admin/view/user/rule/edit.html
index 345b9672749dc370488d6606e02eba0e9fe2e7d0..8c70c7baed91e1cf7a092136e32c89fc2fa2a9eb 100644
--- a/application/admin/view/user/rule/edit.html
+++ b/application/admin/view/user/rule/edit.html
@@ -6,6 +6,12 @@
{:build_radios('row[ismenu]', ['1'=>__('Yes'), '0'=>__('No')], $row['ismenu'])}
+
@@ -36,6 +45,18 @@
+
+
+{include file="auth/rule/tpl" /}
\ No newline at end of file
diff --git a/application/admin/view/user/scorelog/index.html b/application/admin/view/user/scorelog/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..c95df8fcff6552ec6d13c7ff03e4392e2f520b93
--- /dev/null
+++ b/application/admin/view/user/scorelog/index.html
@@ -0,0 +1,33 @@
+
+ {:build_heading()}
+
+
+
diff --git a/application/command.php b/application/command.php
index ab4178a367a3e8cc6cb238a9538d17f85785980f..41a64ca98182932112c3caa7328c69ab86b4df8d 100755
--- a/application/command.php
+++ b/application/command.php
@@ -13,6 +13,7 @@
return [
'app\admin\command\Crud',
'app\admin\command\Menu',
+ 'app\admin\command\Usermenu',
'app\admin\command\Install',
'app\admin\command\Min',
'app\admin\command\Addon',
diff --git a/application/common/controller/Backend.php b/application/common/controller/Backend.php
index 8348df70384bda11b04dd333587de2cb5d956c53..89f9e6c917d38e37a385de6e586d304d44c20b38 100644
--- a/application/common/controller/Backend.php
+++ b/application/common/controller/Backend.php
@@ -12,7 +12,7 @@ use think\Session;
/**
* 后台控制器基类
*/
-class Backend extends Controller
+class Backend extends Base
{
/**
@@ -211,25 +211,6 @@ class Backend extends Controller
$this->assign('admin', Session::get('admin'));
}
- /**
- * 加载语言文件
- * @param string $name
- */
- protected function loadlang($name)
- {
- Lang::load(APP_PATH . $this->request->module() . '/lang/' . $this->request->langset() . '/' . str_replace('.', '/', $name) . '.php');
- }
-
- /**
- * 渲染配置信息
- * @param mixed $name 键名或数组
- * @param mixed $value 值
- */
- protected function assignconfig($name, $value = '')
- {
- $this->view->config = array_merge($this->view->config ? $this->view->config : [], is_array($name) ? $name : [$name => $value]);
- }
-
/**
* 生成查询所需要的条件,排序方式
* @param mixed $searchfields 快速查询的字段
@@ -316,32 +297,32 @@ class Backend extends Controller
$arr = array_slice(explode(',', $v), 0, 2);
if (stripos($v, ',') === false || !array_filter($arr))
continue;
- //当出现一边为空时改变操作符
- if ($arr[0] === '') {
- $sym = $sym == 'BETWEEN' ? '<=' : '>';
- $arr = $arr[1];
- } else if ($arr[1] === '') {
- $sym = $sym == 'BETWEEN' ? '>=' : '<';
- $arr = $arr[0];
- }
- $where[] = [$k, $sym, $arr];
- break;
+ //当出现一边为空时改变操作符
+ if ($arr[0] === '') {
+ $sym = $sym == 'BETWEEN' ? '<=' : '>';
+ $arr = $arr[1];
+ } else if ($arr[1] === '') {
+ $sym = $sym == 'BETWEEN' ? '>=' : '<';
+ $arr = $arr[0];
+ }
+ $where[] = [$k, $sym, $arr];
+ break;
case 'RANGE':
case 'NOT RANGE':
$v = str_replace(' - ', ',', $v);
$arr = array_slice(explode(',', $v), 0, 2);
if (stripos($v, ',') === false || !array_filter($arr))
continue;
- //当出现一边为空时改变操作符
- if ($arr[0] === '') {
- $sym = $sym == 'RANGE' ? '<=' : '>';
- $arr = $arr[1];
- } else if ($arr[1] === '') {
- $sym = $sym == 'RANGE' ? '>=' : '<';
- $arr = $arr[0];
- }
- $where[] = [$k, str_replace('RANGE', 'BETWEEN', $sym) . ' time', $arr];
- break;
+ //当出现一边为空时改变操作符
+ if ($arr[0] === '') {
+ $sym = $sym == 'RANGE' ? '<=' : '>';
+ $arr = $arr[1];
+ } else if ($arr[1] === '') {
+ $sym = $sym == 'RANGE' ? '>=' : '<';
+ $arr = $arr[0];
+ }
+ $where[] = [$k, str_replace('RANGE', 'BETWEEN', $sym) . ' time', $arr];
+ break;
case 'LIKE':
case 'LIKE %...%':
$where[] = [$k, 'LIKE', "%{$v}%"];
@@ -367,7 +348,7 @@ class Backend extends Controller
};
return [$where, $sort, $order, $offset, $limit];
}
-
+
/**
* 获取数据限制的管理员ID
* 禁用数据限制时返回的是null
@@ -387,7 +368,7 @@ class Backend extends Controller
}
return $adminIds;
}
-
+
/**
* Selectpage的实现方法
*
@@ -399,7 +380,7 @@ class Backend extends Controller
{
//设置过滤方法
$this->request->filter(['strip_tags', 'htmlspecialchars']);
-
+
//搜索关键词,客户端输入以空格分开,这里接收为数组
$word = (array)$this->request->request("q_word/a");
//当前页
@@ -425,7 +406,7 @@ class Backend extends Controller
$order[$v[0]] = $v[1];
}
$field = $field ? $field : 'name';
-
+
//如果有primaryvalue,说明当前是初始化传值
if ($primaryvalue !== null) {
$where = [$primarykey => ['in', $primaryvalue]];
@@ -454,10 +435,10 @@ class Backend extends Controller
$this->model->where($this->dataLimitField, 'in', $adminIds);
}
$datalist = $this->model->where($where)
- ->order($order)
- ->page($page, $pagesize)
- ->field($this->selectpageFields)
- ->select();
+ ->order($order)
+ ->page($page, $pagesize)
+ ->field($this->selectpageFields)
+ ->select();
foreach ($datalist as $index => $item) {
unset($item['password'], $item['salt']);
$list[] = [
@@ -469,5 +450,5 @@ class Backend extends Controller
//这里一定要返回有list这个字段,total是可选的,如果total<=list的数量,则会隐藏分页按钮
return json(['list' => $list, 'total' => $total]);
}
-
+
}
diff --git a/application/common/controller/Base.php b/application/common/controller/Base.php
new file mode 100644
index 0000000000000000000000000000000000000000..0761bb2f72825656928b6bb6fdb73beb77ca2dd9
--- /dev/null
+++ b/application/common/controller/Base.php
@@ -0,0 +1,82 @@
+module());
+ define('C', request()->controller());
+ define('A', request()->action());
+ // 编辑器图片上传路径
+ //defined('UPLOAD_PATH') or define('UPLOAD_PATH', 'upload/');
+
+ //根据首页的主题,通过cookie传递参数,设置其它页面的主题
+ /*$p = strripos(A,'index');
+ if($p !== false){
+ $N = substr(A,$p+1);
+ //config('template.tpl_replace_string.THEME',$n);
+ cookie('theme',A);
+ }else{
+ cookie('theme','');
+ }*/
+ // 后台公共模板
+ //$this->assign('_admin_base_layout', config('admin_base_layout'));
+ // 当前配色方案
+ //$this->assign('system_color', config('site.system_color'));
+ // 输出弹出层参数
+ //$this->assign('_pop', $this->request->param('_pop'));
+
+ }
+
+ /**
+ * 空的请求
+ */
+ public function _empty()
+ {
+ $this->error("我们正在努力,敬请期待……");
+ //return $this->view->fetch();
+ }
+
+ /**
+ * 加载语言文件
+ * @param string $name
+ */
+ protected function loadlang($name)
+ {
+ Lang::load(APP_PATH . $this->request->module() . '/lang/' . $this->request->langset() . '/' . str_replace('.', '/', $name) . '.php');
+ //加载共用语言包
+ Lang::load(APP_PATH . 'lang/' . $this->request->langset() . '/' . str_replace('.', '/', $name) . '.php');
+ }
+
+ /**
+ * 渲染配置信息
+ * @param mixed $name 键名或数组
+ * @param mixed $value 值
+ */
+ protected function assignconfig($name, $value = '')
+ {
+ $this->view->config = array_merge($this->view->config ? $this->view->config : [], is_array($name) ? $name : [$name => $value]);
+ }
+
+}
\ No newline at end of file
diff --git a/application/common/controller/Frontend.php b/application/common/controller/Frontend.php
index 7ccea8fb7f3f68acbd0cf5078b329fb448efb596..080b37072edbe5c62f191c78b03ca8fafdde960e 100644
--- a/application/common/controller/Frontend.php
+++ b/application/common/controller/Frontend.php
@@ -11,7 +11,7 @@ use think\Lang;
/**
* 前台控制器基类
*/
-class Frontend extends Controller
+class Frontend extends Base
{
/**
@@ -115,23 +115,4 @@ class Frontend extends Controller
$this->assign('config', $config);
}
- /**
- * 加载语言文件
- * @param string $name
- */
- protected function loadlang($name)
- {
- Lang::load(APP_PATH . $this->request->module() . '/lang/' . $this->request->langset() . '/' . str_replace('.', '/', $name) . '.php');
- }
-
- /**
- * 渲染配置信息
- * @param mixed $name 键名或数组
- * @param mixed $value 值
- */
- protected function assignconfig($name, $value = '')
- {
- $this->view->config = array_merge($this->view->config ? $this->view->config : [], is_array($name) ? $name : [$name => $value]);
- }
-
}
diff --git a/application/common/controller/Userend.php b/application/common/controller/Userend.php
new file mode 100644
index 0000000000000000000000000000000000000000..2564c93be94b41046971dee2a78bb2330ea581b2
--- /dev/null
+++ b/application/common/controller/Userend.php
@@ -0,0 +1,484 @@
+request->filter('strip_tags');
+ $this->modulename = $this->request->module();
+ $this->controllername = strtolower($this->request->controller());
+ $this->actionname = strtolower($this->request->action());
+
+ //$path = str_replace('.', '/', $this->controllername) . '/' . $this->actionname;
+ $path = $this->controllername . '/' . $this->actionname;
+
+ // 定义是否Addtabs请求
+ !defined('IS_ADDTABS') && define('IS_ADDTABS', input("addtabs") ? TRUE : FALSE);
+ // 定义是否Dialog请求
+ !defined('IS_DIALOG') && define('IS_DIALOG', input("dialog") ? TRUE : FALSE);
+ // 定义是否AJAX请求
+ !defined('IS_AJAX') && define('IS_AJAX', $this->request->isAjax());
+
+ $this->auth = Auth::instance();
+
+ // token
+ $token = $this->request->server('HTTP_TOKEN', $this->request->request('token', \think\Cookie::get('token')));
+
+ // 设置当前请求的URI
+ $this->auth->setRequestUri($path);
+ // 检测是否需要验证登录
+ if (!$this->auth->match($this->noNeedLogin)) {
+ //初始化
+ $this->auth->init($token);
+ //检测是否登录
+ if (!$this->auth->isLogin()) {
+ $this->error(__('Please login first'), 'index/user/login');
+ }
+ // 判断是否需要验证权限
+ if (!$this->auth->match($this->noNeedRight)) {
+ // 判断控制器和方法判断是否有对应权限
+ if (!$this->auth->check($path)) {
+ $this->error(__('You have no permission'));
+ }
+ }
+ } else {
+ // 如果有传递token才验证是否登录状态
+ if ($token) {
+ $this->auth->init($token);
+ }
+ }
+
+ // 非选项卡时重定向
+ if (!$this->request->isPost() && !IS_AJAX && !IS_ADDTABS && !IS_DIALOG && input("ref") == 'addtabs')
+ {
+ $url = preg_replace_callback("/([\?|&]+)ref=addtabs(&?)/i", function($matches) {
+ return $matches[2] == '&' ? $matches[1] : '';
+ }, $this->request->url());
+ if (Config::get('url_domain_deploy'))
+ {
+ if (stripos($url, $this->request->server('SCRIPT_NAME')) === 0)
+ {
+ $url = substr($url, strlen($this->request->server('SCRIPT_NAME')));
+ }
+ $url = url($url, '', false);
+ }
+ $this->redirect('user/index/index', [], 302, ['referer' => $url]);
+ exit;
+ }
+ // 设置面包屑导航数据
+ $breadcrumb = $this->auth->getBreadCrumb($path);
+ array_pop($breadcrumb);
+ $this->assign('breadcrumb', $breadcrumb);
+
+ // 如果有使用模板布局
+ if ($this->layout) {
+ $this->view->engine->layout('layout/' . $this->layout);
+ }
+
+ // 语言检测
+ $lang = strip_tags($this->request->langset());
+
+ $site = Config::get("site");
+
+ $upload = \app\common\model\Config::upload();
+
+ // 上传信息配置后
+ Hook::listen("upload_config_init", $upload);
+
+ // 配置信息
+ $this->config = [
+ 'site' => array_intersect_key($site, array_flip(['name', 'indexurl', 'cdnurl', 'version', 'timezone', 'languages'])),
+ 'upload' => $upload,
+ 'modulename' => $this->modulename,
+ 'controllername' => $this->controllername,
+ 'actionname' => $this->actionname,
+ 'jsname' => 'userend/'.str_replace('.', '/', $this->controllername),
+ 'moduleurl' => rtrim(url("/{$this->modulename}", '', false), '/'),
+ 'language' => $lang,
+ 'fastadmin' => Config::get('fastadmin'),
+ 'referer' => Session::get("referer"),
+ ];
+ $this->config = array_merge($this->config, Config::get("view_replace_str"));
+
+ Config::set('upload', array_merge(Config::get('upload'), $upload));
+ // 配置信息后
+ Hook::listen("config_init", $this->config);
+ // 加载当前控制器语言包
+ $this->loadlang($this->controllername);
+ $this->assign('site', $site);
+ $this->assign('config', $this->config);
+ //渲染权限对象
+ $this->assign('auth', $this->auth);
+ $this->assign('user', $this->auth->getUser());
+
+ }
+
+ /**
+ * 生成查询所需要的条件,排序方式
+ * @param mixed $searchfields 快速查询的字段
+ * @param boolean $relationSearch 是否关联查询
+ * @return array
+ */
+ protected function buildparams($searchfields = null, $relationSearch = null)
+ {
+ $searchfields = is_null($searchfields) ? $this->searchFields : $searchfields;
+ $relationSearch = is_null($relationSearch) ? $this->relationSearch : $relationSearch;
+ $search = $this->request->get("search", '');
+ $filter = $this->request->get("filter", '');
+ $op = $this->request->get("op", '', 'trim');
+ $sort = $this->request->get("sort", "id");
+ $order = $this->request->get("order", "DESC");
+ $offset = $this->request->get("offset", 0);
+ $limit = $this->request->get("limit", 0);
+ $filter = json_decode($filter, TRUE);
+ $op = json_decode($op, TRUE);
+ $filter = $filter ? $filter : [];
+ $where = [];
+ $tableName = '';
+ if ($relationSearch)
+ {
+ if (!empty($this->model))
+ {
+ $tableName = $this->model->getQuery()->getTable() . ".";
+ }
+ $sort = stripos($sort, ".") === false ? $tableName . $sort : $sort;
+ }
+ $adminIds = $this->getDataLimitUserIds();
+ if (is_array($adminIds))
+ {
+ $where[] = [$tableName . $this->dataLimitField, 'in', $adminIds];
+ }
+ if ($search)
+ {
+ $searcharr = is_array($searchfields) ? $searchfields : explode(',', $searchfields);
+ foreach ($searcharr as $k => &$v)
+ {
+ $v = stripos($v, ".") === false ? $tableName . $v : $v;
+ }
+ unset($v);
+ $where[] = [implode("|", $searcharr), "LIKE", "%{$search}%"];
+ }
+ foreach ($filter as $k => $v)
+ {
+ $sym = isset($op[$k]) ? $op[$k] : '=';
+ if (stripos($k, ".") === false)
+ {
+ $k = $tableName . $k;
+ }
+ $v = !is_array($v) ? trim($v) : $v;
+ $sym = strtoupper(isset($op[$k]) ? $op[$k] : $sym);
+ switch ($sym)
+ {
+ case '=':
+ case '!=':
+ $where[] = [$k, $sym, (string) $v];
+ break;
+ case 'LIKE':
+ case 'NOT LIKE':
+ case 'LIKE %...%':
+ case 'NOT LIKE %...%':
+ $where[] = [$k, trim(str_replace('%...%', '', $sym)), "%{$v}%"];
+ break;
+ case '>':
+ case '>=':
+ case '<':
+ case '<=':
+ $where[] = [$k, $sym, intval($v)];
+ break;
+ case 'FINDIN':
+ case 'FIND_IN_SET':
+ $where[] = "FIND_IN_SET('{$v}', `{$k}`)";
+ break;
+ case 'IN':
+ case 'IN(...)':
+ case 'NOT IN':
+ case 'NOT IN(...)':
+ $where[] = [$k, str_replace('(...)', '', $sym), is_array($v) ? $v : explode(',', $v)];
+ break;
+ case 'BETWEEN':
+ case 'NOT BETWEEN':
+ $arr = array_slice(explode(',', $v), 0, 2);
+ if (stripos($v, ',') === false || !array_filter($arr))
+ continue;
+ //当出现一边为空时改变操作符
+ if ($arr[0] === '')
+ {
+ $sym = $sym == 'BETWEEN' ? '<=' : '>';
+ $arr = $arr[1];
+ }
+ else if ($arr[1] === '')
+ {
+ $sym = $sym == 'BETWEEN' ? '>=' : '<';
+ $arr = $arr[0];
+ }
+ $where[] = [$k, $sym, $arr];
+ break;
+ case 'RANGE':
+ case 'NOT RANGE':
+ $v = str_replace(' - ', ',', $v);
+ $arr = array_slice(explode(',', $v), 0, 2);
+ if (stripos($v, ',') === false || !array_filter($arr))
+ continue;
+ //当出现一边为空时改变操作符
+ if ($arr[0] === '')
+ {
+ $sym = $sym == 'RANGE' ? '<=' : '>';
+ $arr = $arr[1];
+ }
+ else if ($arr[1] === '')
+ {
+ $sym = $sym == 'RANGE' ? '>=' : '<';
+ $arr = $arr[0];
+ }
+ $where[] = [$k, str_replace('RANGE', 'BETWEEN', $sym) . ' time', $arr];
+ break;
+ case 'LIKE':
+ case 'LIKE %...%':
+ $where[] = [$k, 'LIKE', "%{$v}%"];
+ break;
+ case 'NULL':
+ case 'IS NULL':
+ case 'NOT NULL':
+ case 'IS NOT NULL':
+ $where[] = [$k, strtolower(str_replace('IS ', '', $sym))];
+ break;
+ default:
+ break;
+ }
+ }
+ $where = function($query) use ($where) {
+ foreach ($where as $k => $v)
+ {
+ if (is_array($v))
+ {
+ call_user_func_array([$query, 'where'], $v);
+ }
+ else
+ {
+ $query->where($v);
+ }
+ }
+ };
+ return [$where, $sort, $order, $offset, $limit];
+ }
+
+ /**
+ * 获取数据限制的会员ID
+ * 禁用数据限制时返回的是null
+ * @return mixed
+ */
+ protected function getDataLimitUserIds()
+ {
+ if (!$this->dataLimit)
+ {
+ return null;
+ }
+ $userIds = [];
+ if (in_array($this->dataLimit, ['auth', 'personal']))
+ {
+ //$userIds = $this->dataLimit == 'auth' ? $this->auth->getChildrenUserIds(true) : [$this->auth->id];
+ $userIds = [$this->auth->id];
+ }
+ return $userIds;
+ }
+
+ /**
+ * Selectpage的实现方法
+ *
+ * 当前方法只是一个比较通用的搜索匹配,请按需重载此方法来编写自己的搜索逻辑,$where按自己的需求写即可
+ * 这里示例了所有的参数,所以比较复杂,实现上自己实现只需简单的几行即可
+ *
+ */
+ protected function selectpage()
+ {
+ //设置过滤方法
+ $this->request->filter(['strip_tags', 'htmlspecialchars']);
+
+ //搜索关键词,客户端输入以空格分开,这里接收为数组
+ $word = (array) $this->request->request("q_word/a");
+ //当前页
+ $page = $this->request->request("pageNumber");
+ //分页大小
+ $pagesize = $this->request->request("pageSize");
+ //搜索条件
+ $andor = $this->request->request("andOr");
+ //排序方式
+ $orderby = (array) $this->request->request("orderBy/a");
+ //显示的字段
+ $field = $this->request->request("showField");
+ //主键
+ $primarykey = $this->request->request("keyField");
+ //主键值
+ $primaryvalue = $this->request->request("keyValue");
+ //搜索字段
+ $searchfield = (array) $this->request->request("searchField/a");
+ //自定义搜索条件
+ $custom = (array) $this->request->request("custom/a");
+ $order = [];
+ foreach ($orderby as $k => $v)
+ {
+ $order[$v[0]] = $v[1];
+ }
+ $field = $field ? $field : 'name';
+
+ //如果有primaryvalue,说明当前是初始化传值
+ if ($primaryvalue !== null)
+ {
+ $where = [$primarykey => ['in', $primaryvalue]];
+ }
+ else
+ {
+ $where = function($query) use($word, $andor, $field, $searchfield, $custom) {
+ foreach ($word as $k => $v)
+ {
+ foreach ($searchfield as $m => $n)
+ {
+ $query->where($n, "like", "%{$v}%", $andor);
+ }
+ }
+ if ($custom && is_array($custom))
+ {
+ foreach ($custom as $k => $v)
+ {
+ $query->where($k, '=', $v);
+ }
+ }
+ };
+ }
+ /*
+ $adminIds = $this->getDataLimitAdminIds();
+ if (is_array($adminIds))
+ {
+ $this->model->where($this->dataLimitField, 'in', $adminIds);
+ }
+ */
+ $list = [];
+ $total = $this->model->where($where)->count();
+ if ($total > 0)
+ {
+ /*if (is_array($adminIds))
+ {
+ $this->model->where($this->dataLimitField, 'in', $adminIds);
+ }*/
+ $list = $this->model->where($where)
+ ->order($order)
+ ->page($page, $pagesize)
+ ->field("{$primarykey},{$field}")
+ //->field("password,salt", true)
+ ->select();
+ }
+ //这里一定要返回有list这个字段,total是可选的,如果total<=list的数量,则会隐藏分页按钮
+ return json(['list' => $list, 'total' => $total]);
+ }
+
+}
diff --git a/application/common/library/UserAuth.php b/application/common/library/UserAuth.php
new file mode 100644
index 0000000000000000000000000000000000000000..bf9575503a45207c19e270f31dc9264328418e12
--- /dev/null
+++ b/application/common/library/UserAuth.php
@@ -0,0 +1,845 @@
+options = array_merge($this->config, $config);
+ }
+ $this->options = array_merge($this->config, $options);
+ }
+
+ /**
+ *
+ * @param array $options 参数
+ * @return Auth
+ */
+ public static function instance($options = [])
+ {
+ if (is_null(self::$instance))
+ {
+ self::$instance = new static($options);
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * 获取User模型
+ * @return User
+ */
+ public function getUser()
+ {
+ return $this->_user;
+ }
+
+ /**
+ * 兼容调用user模型的属性
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ return $this->_user ? $this->_user->$name : NULL;
+ }
+
+ /**
+ * 根据Token初始化
+ *
+ * @param string $token Token
+ * @return boolean
+ */
+ public function init($token)
+ {
+ if ($this->_logined)
+ {
+ return TRUE;
+ }
+ if ($this->_error)
+ return FALSE;
+ $data = Token::get($token);
+ if (!$data)
+ {
+ return FALSE;
+ }
+ $user_id = intval($data['user_id']);
+ if ($user_id > 0)
+ {
+ $user = User::get($user_id);
+ if (!$user)
+ {
+ $this->setError('Account not exist');
+ return FALSE;
+ }
+ if ($user['status'] != 'normal')
+ {
+ $this->setError('Account is locked');
+ return FALSE;
+ }
+ $this->_user = $user;
+ $this->_logined = TRUE;
+ $this->_token = $token;
+
+ //初始化成功的事件
+ Hook::listen("user_init_successed", $this->_user);
+
+ return TRUE;
+ }
+ else
+ {
+ $this->setError('You are not logged in');
+ return FALSE;
+ }
+ }
+
+ /**
+ * 注册用户
+ *
+ * @param string $username 用户名
+ * @param string $password 密码
+ * @param string $email 邮箱
+ * @param string $mobile 手机号
+ * @param array $extend 扩展参数
+ * @return boolean
+ */
+ public function register($username, $password, $email = '', $mobile = '', $extend = [])
+ {
+ // 检测用户名或邮箱、手机号是否存在
+ if (User::getByUsername($username))
+ {
+ $this->setError('Username already exist');
+ return FALSE;
+ }
+ if ($email && User::getByEmail($email))
+ {
+ $this->setError('Email already exist');
+ return FALSE;
+ }
+ if ($mobile && User::getByMobile($mobile))
+ {
+ $this->setError('Mobile already exist');
+ return FALSE;
+ }
+
+ $ip = request()->ip();
+ $time = time();
+
+ $data = [
+ 'username' => $username,
+ 'password' => $password,
+ 'email' => $email,
+ 'mobile' => $mobile,
+ 'level' => 1,
+ 'score' => 0,
+ 'avatar' => '',
+ ];
+ $params = array_merge($data, [
+ 'nickname' => $username,
+ 'salt' => Random::alnum(),
+ 'jointime' => $time,
+ 'joinip' => $ip,
+ 'logintime' => $time,
+ 'loginip' => $ip,
+ 'prevtime' => $time,
+ 'status' => 'normal'
+ ]);
+ $params['password'] = $this->getEncryptPassword($password, $params['salt']);
+ $params = array_merge($params, $extend);
+
+ ////////////////同步到Ucenter////////////////
+ if (defined('UC_STATUS') && UC_STATUS)
+ {
+ $uc = new \addons\ucenter\library\client\Client();
+ $user_id = $uc->uc_user_register($username, $password, $email);
+ // 如果小于0则说明发生错误
+ if ($user_id <= 0)
+ {
+ $this->setError($user_id > -4 ? 'Username is incorrect' : 'Email is incorrect');
+ return FALSE;
+ }
+ else
+ {
+ $params['id'] = $user_id;
+ }
+ }
+
+ //账号注册时需要开启事务,避免出现垃圾数据
+ Db::startTrans();
+ try
+ {
+ $user = User::create($params);
+ Db::commit();
+
+ // 此时的Model中只包含部分数据
+ $this->_user = User::get($user->id);
+
+ //设置Token
+ $this->_token = Random::uuid();
+ Token::set($this->_token, $user->id, $this->keeptime);
+
+ //注册成功的事件
+ Hook::listen("user_register_successed", $this->_user);
+
+ return TRUE;
+ }
+ catch (Exception $e)
+ {
+ $this->setError($e->getMessage());
+ Db::rollback();
+ return FALSE;
+ }
+ }
+
+ /**
+ * 用户登录
+ *
+ * @param string $account 账号,用户名、邮箱、手机号
+ * @param string $password 密码
+ * @return boolean
+ */
+ public function login($account, $password)
+ {
+ $field = Validate::is($account, 'email') ? 'email' : (Validate::regex($account, '/^1\d{10}$/') ? 'mobile' : 'username');
+ $user = User::get([$field => $account]);
+ if (!$user)
+ {
+ $this->setError('Account is incorrect');
+ return FALSE;
+ }
+
+ if ($user->status != 'normal')
+ {
+ $this->setError('Account is locked');
+ return FALSE;
+ }
+ if ($user->password != $this->getEncryptPassword($password, $user->salt))
+ {
+ $this->setError('Password is incorrect');
+ return FALSE;
+ }
+
+ //直接登录会员
+ $this->direct($user->id);
+
+ return TRUE;
+ }
+
+ /**
+ * 注销
+ *
+ * @return boolean
+ */
+ public function logout()
+ {
+ if (!$this->_logined)
+ {
+ $this->setError('You are not logged in');
+ return false;
+ }
+ //设置登录标识
+ $this->_logined = FALSE;
+ //删除Token
+ Token::delete($this->_token);
+ //注销成功的事件
+ Hook::listen("user_logout_successed", $this->_user);
+ return TRUE;
+ }
+
+ /**
+ * 修改密码
+ * @param string $newpassword 新密码
+ * @param string $oldpassword 旧密码
+ * @param bool $ignoreoldpassword 忽略旧密码
+ * @return boolean
+ */
+ public function changepwd($newpassword, $oldpassword = '', $ignoreoldpassword = false)
+ {
+ if (!$this->_logined)
+ {
+ $this->setError('You are not logged in');
+ return false;
+ }
+ //判断旧密码是否正确
+ if ($this->_user->password == $this->getEncryptPassword($oldpassword, $this->_user->salt) || $ignoreoldpassword)
+ {
+ $salt = Random::alnum();
+ $newpassword = $this->getEncryptPassword($newpassword, $salt);
+ $this->_user->save(['password' => $newpassword, 'salt' => $salt]);
+
+ Token::delete($this->_token);
+ //修改密码成功的事件
+ Hook::listen("user_changepwd_successed", $this->_user);
+ return true;
+ }
+ else
+ {
+ $this->setError('Password is incorrect');
+ return false;
+ }
+ }
+
+ /**
+ * 直接登录账号
+ * @param int $user_id
+ * @return boolean
+ */
+ public function direct($user_id)
+ {
+ $user = User::get($user_id);
+ if ($user)
+ {
+ ////////////////同步到Ucenter////////////////
+ if (defined('UC_STATUS') && UC_STATUS)
+ {
+ $uc = new \addons\ucenter\library\client\Client();
+ $re = $uc->uc_user_login($this->user->id, $this->user->password . '#split#' . $this->user->salt, 3);
+ // 如果小于0则说明发生错误
+ if ($re <= 0)
+ {
+ $this->setError('Username or password is incorrect');
+ return FALSE;
+ }
+ }
+
+ $ip = request()->ip();
+ $time = time();
+
+ //判断连续登录和最大连续登录
+ if ($user->logintime < \fast\Date::unixtime('day'))
+ {
+ $user->successions = $user->logintime < \fast\Date::unixtime('day', -1) ? 1 : $user->successions + 1;
+ $user->maxsuccessions = max($user->successions, $user->maxsuccessions);
+ }
+
+ $user->prevtime = $user->logintime;
+ //记录本次登录的IP和时间
+ $user->loginip = $ip;
+ $user->logintime = $time;
+
+ $user->save();
+
+ //查询用户信息之后, 查询用户的积分等级
+ $levelId = $user['level'];
+ $levelName = Db::name("user_level")->where("level_id", $levelId)->column("level_name");
+ $user['level_name'] = $levelName;
+
+ $this->_user = $user;
+
+ $this->_token = Random::uuid();
+ Token::set($this->_token, $user->id, $this->keeptime);
+
+ $this->_logined = TRUE;
+
+ //登录成功的事件
+ Hook::listen("user_login_successed", $this->_user);
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+ }
+
+ /**
+ * 检测是否是否有对应权限
+ * @param string $path 控制器/方法
+ * @param string $module 模块 默认为当前模块
+ * @return boolean
+ */
+ public function check($path = NULL, $module = NULL)
+ {
+ if (!$this->_logined) return false;
+
+ $ruleList = $this->getRuleList();
+ $rules = [];
+ foreach ($ruleList as $k => $v)
+ {
+ $rules[] = $v['name'];
+ }
+ $url = ($module ? $module : request()->module()) . '/' . (is_null($path) ? $this->getRequestUri() : $path);
+ $url = is_null($path) ? $this->getRequestUri() : $path;
+ $url = strtolower(str_replace('.', '/', $url));
+ return in_array($url, $rules) ? TRUE : FALSE;
+ }
+
+ /**
+ * 判断是否登录
+ * @return boolean
+ */
+ public function isLogin()
+ {
+ if ($this->_logined)
+ {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 获取当前Token
+ * @return string
+ */
+ public function getToken()
+ {
+ return $this->_token;
+ }
+
+ /**
+ * 获取会员基本信息
+ */
+ public function getUserinfo()
+ {
+ $data = $this->_user->toArray();
+ $allowFields = $this->getAllowFields();
+ $userinfo = array_intersect_key($data, array_flip($allowFields));
+ $userinfo = array_merge($userinfo, Token::get($this->_token));
+ return $userinfo;
+ }
+
+ /**
+ * 获取会员组别规则列表
+ * @return array
+ */
+ public function getRuleList()
+ {
+ if ($this->rules) return $this->rules;
+ $group = $this->_user->group;
+ if (!$group) return [];
+ $rules = explode(',', $group->rules);
+ $this->rules = collection(UserRule::where('status', 'normal')->where('id', 'in', $rules)->field('id,pid,name,title,ismenu')->select())->toArray();
+ return $this->rules;
+ }
+
+ /**
+ * 获取当前请求的URI
+ * @return string
+ */
+ public function getRequestUri()
+ {
+ return $this->requestUri;
+ }
+
+ /**
+ * 设置当前请求的URI
+ * @param string $uri
+ */
+ public function setRequestUri($uri)
+ {
+ $this->requestUri = $uri;
+ }
+
+ /**
+ * 获取允许输出的字段
+ * @return array
+ */
+ public function getAllowFields()
+ {
+ return $this->allowFields;
+ }
+
+ /**
+ * 重新设置允许输出的字段
+ * @param array $fields
+ */
+ public function setAllowFields($fields)
+ {
+ $this->allowFields = $fields;
+ }
+
+ /**
+ * 追加设置允许输出的字段
+ * @param array $fields
+ */
+ public function addAllowFields($fields)
+ {
+ $this->allowFields = array_merge(
+ $this->allowFields, array_change_key_case($fields)
+ );
+ }
+
+ /**
+ * 删除一个指定会员
+ * @param int $user_id 会员ID
+ * @return boolean
+ */
+ public function delete($user_id)
+ {
+ $user = User::get($user_id);
+ if (!$user)
+ {
+ return FALSE;
+ }
+
+ ////////////////同步到Ucenter////////////////
+ if (defined('UC_STATUS') && UC_STATUS)
+ {
+ $uc = new \addons\ucenter\library\client\Client();
+ $re = $uc->uc_user_delete($user['id']);
+ // 如果小于0则说明发生错误
+ if ($re <= 0)
+ {
+ $this->setError('Account is locked');
+ return FALSE;
+ }
+ }
+
+ // 调用事务删除账号
+ $result = Db::transaction(function($db) use($user_id) {
+ // 删除会员
+ User::destroy($user_id);
+ // 删除会员指定的所有Token
+ Token::clear($user_id);
+ return TRUE;
+ });
+ if ($result)
+ {
+ Hook::listen("user_delete_successed", $user);
+ }
+ return $result ? TRUE : FALSE;
+ }
+
+ /**
+ * 获取密码加密后的字符串
+ * @param string $password 密码
+ * @param string $salt 密码盐
+ * @return string
+ */
+ public function getEncryptPassword($password, $salt = '')
+ {
+ return md5(md5($password) . $salt);
+ }
+
+ /**
+ * 检测当前控制器和方法是否匹配传递的数组
+ *
+ * @param array $arr 需要验证权限的数组
+ * @return boolean
+ */
+ public function match($arr = [])
+ {
+ $request = Request::instance();
+ $arr = is_array($arr) ? $arr : explode(',', $arr);
+ if (!$arr)
+ {
+ return FALSE;
+ }
+ $arr = array_map('strtolower', $arr);
+ // 是否存在
+ if (in_array(strtolower($request->action()), $arr) || in_array('*', $arr))
+ {
+ return TRUE;
+ }
+
+ // 没找到匹配
+ return FALSE;
+ }
+
+ /**
+ * 设置会话有效时间
+ * @param int $keeptime 默认为永久
+ */
+ public function keeptime($keeptime = 0)
+ {
+ $this->keeptime = $keeptime;
+ }
+
+ /**
+ * 渲染用户数据
+ * @param array $datalist 二维数组
+ * @param mixed $fields 加载的字段列表
+ * @param string $fieldkey 渲染的字段
+ * @param string $renderkey 结果字段
+ * @return array
+ */
+ public function render(&$datalist, $fields = [], $fieldkey = 'user_id', $renderkey = 'userinfo')
+ {
+ $fields = !$fields ? ['id', 'nickname', 'level', 'avatar'] : (is_array($fields) ? $fields : explode(',', $fields));
+ $ids = [];
+ foreach ($datalist as $k => $v)
+ {
+ if (!isset($v[$fieldkey]))
+ continue;
+ $ids[] = $v[$fieldkey];
+ }
+ $list = [];
+ if ($ids)
+ {
+ if (!in_array('id', $fields))
+ {
+ $fields[] = 'id';
+ }
+ $ids = array_unique($ids);
+ $selectlist = User::where('id', 'in', $ids)->column($fields);
+ foreach ($selectlist as $k => $v)
+ {
+ $list[$v['id']] = $v;
+ }
+ }
+ foreach ($datalist as $k => &$v)
+ {
+ $v[$renderkey] = isset($list[$v[$fieldkey]]) ? $list[$v[$fieldkey]] : NULL;
+ }
+ unset($v);
+ return $datalist;
+ }
+
+ /**
+ * 设置错误信息
+ *
+ * @param $error 错误信息
+ * @return Auth
+ */
+ public function setError($error)
+ {
+ $this->_error = $error;
+ return $this;
+ }
+
+ /**
+ * 获取错误信息
+ * @return string
+ */
+ public function getError()
+ {
+ return $this->_error ? __($this->_error) : '';
+ }
+
+ /**
+ * 获得面包屑导航
+ * @param string $path
+ * @return array
+ */
+ public function getBreadCrumb($path = '')
+ {
+ if ($this->breadcrumb || !$path) return $this->breadcrumb;
+ $path_rule_id = 0;
+ if(empty($this->rules)){
+ $this->rules = $this->getRuleList();
+ }
+ $path = str_replace(".", "/", $path);
+ foreach ($this->rules as $rule) {
+ $path_rule_id = $rule['name']==$path ? $rule['id'] : $path_rule_id;
+ }
+ if ($path_rule_id) {
+ $this->breadcrumb = Tree::instance()->init($this->rules)->getParents($path_rule_id, true);
+ foreach ($this->breadcrumb as $k => &$v) {
+ $v['url'] = url($v['name']);
+ $v['title'] = __($v['title']);
+ }
+ }
+ return $this->breadcrumb;
+ }
+
+ /**
+ * 获取左侧菜单栏
+ *
+ * @param array $params URL对应的badge数据
+ * @return string
+ */
+ public function getSidebar($params = [], $fixedPage = 'dashboard')
+ {
+ $colorArr = ['red', 'green', 'yellow', 'blue', 'teal', 'orange', 'purple'];
+ $colorNums = count($colorArr);
+ $badgeList = [];
+ $module = request()->module();
+ // 生成菜单的badge
+ foreach ($params as $k => $v){
+ $url = $k;
+ if (is_array($v)) {
+ $nums = isset($v[0]) ? $v[0] : 0;
+ $color = isset($v[1]) ? $v[1] : $colorArr[(is_numeric($nums) ? $nums : strlen($nums)) % $colorNums];
+ $class = isset($v[2]) ? $v[2] : 'label';
+ }
+ else {
+ $nums = $v;
+ $color = $colorArr[(is_numeric($nums) ? $nums : strlen($nums)) % $colorNums];
+ $class = 'label';
+ }
+ //必须nums大于0才显示
+ if ($nums) {
+ $badgeList[$url] = '' . $nums . ' ';
+ }
+ }
+ // 读取用户当前拥有的权限节点
+ $userRule = $this->getRuleList();
+ $selected = $referer = [];
+ $refererUrl = Session::get('referer');
+ $pinyin = new \Overtrue\Pinyin\Pinyin('Overtrue\Pinyin\MemoryFileDictLoader');
+ // 必须将结果集转换为数组
+ //$ruleList = collection(UserRule::where('status', 'normal')->where('ismenu', 1)->order('weigh', 'desc')->select())->toArray();//->cache("__usermenu__")
+ $ruleList = $this->getMenuList();
+ foreach ($ruleList as $k => &$v)
+ {
+ /*if (!in_array($v['name'], $userRule)) {
+ unset($ruleList[$k]);
+ continue;
+ }*/
+ $v['icon'] = $v['icon'] . ' fa-fw';
+ $v['url'] = '/' . $module . '/' . $v['name'];
+ $v['badge'] = isset($badgeList[$v['name']]) ? $badgeList[$v['name']] : '';
+ $v['py'] = $pinyin->abbr($v['title'], '');
+ $v['pinyin'] = $pinyin->permalink($v['title'], '');
+ $v['title'] = __($v['title']);
+ //$select_id = $v['name'] == $fixedPage ? $v['id'] : $select_id;
+ $selected = $v['name'] == $fixedPage ? $v : $selected;
+ $referer = url($v['url']) == $refererUrl ? $v : $referer;
+ }
+ if ($selected == $referer) {
+ $referer = [];
+ }
+ $selected && $selected['url'] = url($selected['url']);
+ $referer && $referer['url'] = url($referer['url']);
+
+ $select_id = $selected ? $selected['id'] : 0;
+ $menu = $nav = '';
+ //是否启用多级菜单导航
+ if (Config::get('fastadmin.multiplenav')) {
+ $topList = [];
+ foreach ($ruleList as $index => $item) {
+ if (!$item['pid']) {
+ $topList[] = $item;
+ }
+ }
+ $selectParentIds = [];
+ $tree = Tree::instance();
+ $tree->init($ruleList);
+ if ($select_id) {
+ $selectParentIds = $tree->getParentsIds($select_id, true);
+ }
+ foreach ($topList as $index => $item) {
+ $childList = Tree::instance()->getTreeMenu($item['id'], ' @title @caret @badge @childlist ', $select_id, '', 'ul', 'class="treeview-menu"');
+ $current = in_array($item['id'], $selectParentIds);
+ $url = $childList ? 'javascript:;' : url($item['url']);
+ $addtabs = $childList || !$url ? "" : (stripos($url, "?") !== false ? "&" : "?") . "ref=addtabs";
+ $childList = str_replace('" pid="' . $item['id'] . '"', ' treeview ' . ($current ? '' : 'hidden') . '" pid="' . $item['id'] . '"', $childList);
+ $nav .= ' ' . $item['title'] . ' ';
+ $menu .= $childList;
+ }
+ } else {
+ // 构造菜单数据
+ Tree::instance()->init($ruleList);
+ $menu = Tree::instance()->getTreeMenu(0, ' @title @caret @badge @childlist ', $select_id, '', 'ul', 'class="treeview-menu"');
+ if ($selected) {
+ $nav .= ' ' . $selected['title'] . ' ';
+ }
+ if ($referer) {
+ $nav .= ' ' . $referer['title'] . ' ';
+ }
+ }
+ return [$menu, $nav, $selected, $referer];
+ // 构造菜单数据
+ //$usertree = new Tree();
+ //$usertree->init($ruleList);
+ //$usermenu = $usertree->getTreeMenu(0, ' @title @caret @badge @childlist '."\n", $select_id, '', 'ul', 'class="treeview-menu"');
+ //return $usermenu;
+ }
+
+ /**
+ * 获取会员组别规则菜单
+ * @return array
+ */
+ public function getMenuList()
+ {
+ if ($this->menus) return $this->menus;
+ $group = $this->_user->group;
+ if (!$group) return [];
+ $rules = explode(',', $group->rules);
+ $this->menus = collection(UserRule::where('status', 'normal')->where('id', 'in', $rules)->where('ismenu', 1)
+ ->order('weigh', 'desc')->select())->toArray();//->field('id,pid,name,title,ismenu,status,icon')
+ return $this->menus;
+ }
+
+ /**
+ * 设置user扩展属性, name 为数组则为批量设置
+ * @access public
+ * @param string|array $name 配置参数名(支持二级配置 . 号分割)
+ * @param mixed $value 配置值
+ * @return mixed 具有扩展属性的user对象
+ */
+ public function set($name, $value = null)
+ {
+
+ if (!isset($this->_user)) $this->_user = [];
+
+ // 字符串则表示单个配置设置
+ if (is_string($name)) {
+ if (!strpos($name, '.')) {
+ $this->_user[strtolower($name)] = $value;
+ } else {
+ // 二维数组
+ $name = explode('.', $name, 2);
+ $this->_user[strtolower($name[0])][$name[1]] = $value;
+ }
+ return $value;
+ }
+
+ // 数组则表示批量设置
+ if (is_array($name)) {
+ foreach($name as $k=>$v){
+ if (!strpos($k, '.')) {
+ $this->_user[strtolower($k)] = $v;
+ } else {
+ // 二维数组
+ $k = explode('.', $k, 2);
+ $this->_user[strtolower($k[0])][$k[1]] = $v;
+ }
+ }
+ return $this->_user;
+ }
+
+ // 为空直接返回已有配置
+ return $this->_user;
+ }
+
+ /**
+ * 获取等级
+ * @return array
+ */
+ public function getLevel()
+ {
+ if ($this->_logined){//登录才能获取等级
+ $rank = $this->_user->level;
+ if (!$rank) {
+ return false;
+ }
+ $lv = \think\Db::name('user_level')->where('level_id',$rank)->find();//->column('level_id,level_name,level_img');
+ $this->addAllowFields(['level_name','level_img']);
+ return $this->set(['level_name'=>$lv['level_name'],'level_img'=>$lv['level_img']]);
+ }
+ }
+
+}
diff --git a/application/common/library/traits/Userend.php b/application/common/library/traits/Userend.php
new file mode 100644
index 0000000000000000000000000000000000000000..71d6d6dc5c20d4110e6d570693950dac0bd2dfa0
--- /dev/null
+++ b/application/common/library/traits/Userend.php
@@ -0,0 +1,422 @@
+request->filter(['strip_tags']);
+ if ($this->request->isAjax())
+ {
+ //如果发送的来源是Selectpage,则转发到Selectpage
+ if ($this->request->request('pkey_name'))
+ {
+ return $this->selectpage();
+ }
+ list($where, $sort, $order, $offset, $limit) = $this->buildparams();
+
+ $fields = $this->model->getQuery()->getTableInfo('', 'fields');
+ if(in_array("user_id", $fields)){
+ $total = $this->model
+ ->where($where)
+ ->where( 'user_id',$this->auth->id)
+ ->order($sort, $order)
+ ->count();
+ $list = $this->model
+ ->where($where)
+ ->where( 'user_id',$this->auth->id)
+ ->order($sort, $order)
+ ->limit($offset, $limit)
+ ->select();
+ }elseif(in_array("shop_id", $fields)){
+ $total = $this->model
+ ->where($where)
+ ->where( 'shop_id',$this->auth->shopId)
+ ->order($sort, $order)
+ ->count();
+ $list = $this->model
+ ->where($where)
+ ->where( 'shop_id',$this->auth->shopId)
+ ->order($sort, $order)
+ ->limit($offset, $limit)
+ ->select();
+ }else{
+ $total = $this->model
+ ->where($where)
+ ->order($sort, $order)
+ ->count();
+ $list = $this->model
+ ->where($where)
+ ->order($sort, $order)
+ ->limit($offset, $limit)
+ ->select();
+ }
+
+ $list = collection($list)->toArray();
+ $result = array("total" => $total, "rows" => $list);
+
+ return json($result);
+ }
+ return $this->view->fetch();
+ }
+
+ /**
+ * 回收站
+ */
+ public function recyclebin()
+ {
+ //设置过滤方法
+ $this->request->filter(['strip_tags']);
+ if ($this->request->isAjax())
+ {
+ list($where, $sort, $order, $offset, $limit) = $this->buildparams();
+
+ $list = $this->model
+ ->onlyTrashed()
+ ->where($where)
+ ->order($sort, $order)
+ ->limit($offset, $limit)
+ ->select();
+
+ $result = array("total" => count($list), "rows" => $list);
+
+ return json($result);
+ }
+ return $this->view->fetch();
+ }
+
+ /**
+ * 添加
+ */
+ public function add()
+ {
+ if ($this->request->isPost())
+ {
+ $params = $this->request->post("row/a");
+ if ($params)
+ {
+ if ($this->dataLimit && $this->dataLimitFieldAutoFill)
+ {
+ $params[$this->dataLimitField] = $this->auth->id;
+ }
+ try
+ {
+ //将按钮中的状态参数加入记录中
+ $params['status'] = input('status',0);
+
+ //是否采用模型验证
+ if ($this->modelValidate)
+ {
+ $name = basename(str_replace('\\', '/', get_class($this->model)));
+ $validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.add' : true) : $this->modelValidate;
+ $this->model->validate($validate);
+ }
+ $result = $this->model->allowField(true)->save($params);
+ if ($result !== false)
+ {
+ $this->success();
+ }
+ else
+ {
+ $this->error($this->model->getError());
+ }
+ }
+ catch (\think\exception\PDOException $e)
+ {
+ $this->error($e->getMessage());
+ }
+ }
+ $this->error(__('Parameter %s can not be empty', ''));
+ }
+ return $this->view->fetch();
+ }
+
+ /**
+ * 编辑
+ */
+ public function edit($ids = NULL)
+ {
+ $row = $this->model->get($ids);
+ if (!$row)
+ $this->error(__('No Results were found'));
+ $userIds = $this->getDataLimitUserIds();
+ if (is_array($userIds))
+ {
+ if (!in_array($row[$this->dataLimitField], $userIds))
+ {
+ $this->error(__('You have no permission'));
+ }
+ }
+ if ($this->request->isPost())
+ {
+ $params = $this->request->post("row/a");
+ if ($params)
+ {
+ try
+ {
+ //将按钮中的状态参数加入记录中
+ $params['status'] = input('status',0);
+
+ //是否采用模型验证
+ if ($this->modelValidate)
+ {
+ $name = basename(str_replace('\\', '/', get_class($this->model)));
+ $validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.edit' : true) : $this->modelValidate;
+ $row->validate($validate);
+ }
+ $result = $row->allowField(true)->save($params);
+ if ($result !== false)
+ {
+ $this->success();
+ }
+ else
+ {
+ $this->error($row->getError());
+ }
+ }
+ catch (\think\exception\PDOException $e)
+ {
+ $this->error($e->getMessage());
+ }
+ }
+ $this->error(__('Parameter %s can not be empty', ''));
+ }
+ $this->view->assign("row", $row);
+ return $this->view->fetch();
+ }
+
+ /**
+ * 删除
+ */
+ public function del($ids = "")
+ {
+ if($this->request->param("table")) $this->model = model($this->request->param("table"));
+ if ($ids)
+ {
+ $pk = $this->model->getPk();
+ $userIds = $this->getDataLimitUserIds();
+ if (is_array($userIds))
+ {
+ $count = $this->model->where($this->dataLimitField, 'in', $userIds);
+ }
+ $list = $this->model->where($pk, 'in', $ids)->select();
+ $count = 0;
+ foreach ($list as $k => $v)
+ {
+ $count += $v->delete();
+ }
+ if ($count)
+ {
+ $this->success();
+ }
+ else
+ {
+ $this->error(__('No rows were deleted'));
+ }
+ }
+ $this->error(__('Parameter %s can not be empty', 'ids'));
+ }
+
+ /**
+ * 真实删除
+ */
+ public function destroy($ids = "")
+ {
+ $pk = $this->model->getPk();
+ $userIds = $this->getDataLimitUserIds();
+ if (is_array($userIds))
+ {
+ $count = $this->model->where($this->dataLimitField, 'in', $userIds);
+ }
+ if ($ids)
+ {
+ $this->model->where($pk, 'in', $ids);
+ }
+ $count = 0;
+ $list = $this->model->onlyTrashed()->select();
+ foreach ($list as $k => $v)
+ {
+ $count += $v->delete(true);
+ }
+ if ($count)
+ {
+ $this->success();
+ }
+ else
+ {
+ $this->error(__('No rows were deleted'));
+ }
+ $this->error(__('Parameter %s can not be empty', 'ids'));
+ }
+
+ /**
+ * 还原
+ */
+ public function restore($ids = "")
+ {
+ $pk = $this->model->getPk();
+ $userIds = $this->getDataLimitUserIds();
+ if (is_array($userIds))
+ {
+ $this->model->where($this->dataLimitField, 'in', $userIds);
+ }
+ if ($ids)
+ {
+ $this->model->where($pk, 'in', $ids);
+ }
+ $count = $this->model->restore('1=1');
+ if ($count)
+ {
+ $this->success();
+ }
+ $this->error(__('No rows were updated'));
+ }
+
+ /**
+ * 批量更新
+ */
+ public function multi($ids = "")
+ {
+ if($this->request->param("table")) $this->model = model($this->request->param("table"));
+
+ $ids = $ids ? $ids : $this->request->param("ids");
+ if ($ids)
+ {
+ if ($this->request->has('params'))
+ {
+ parse_str($this->request->post("params"), $values);
+ $values = array_intersect_key($values, array_flip(is_array($this->multiFields) ? $this->multiFields : explode(',', $this->multiFields)));
+ if ($values)
+ {
+ $userIds = $this->getDataLimitUserIds();
+ if (is_array($userIds))
+ {
+ $where[]=[$this->dataLimitField, 'in', $userIds];
+ }
+ $where[]=[$this->model->getPk(), 'in', $ids];
+ $count = $this->model->where($where)->update($values);
+ if ($count)
+ {
+ $this->success();
+ }
+ else
+ {
+ $this->error(__('No rows were updated'));
+ }
+ }
+ else
+ {
+ $this->error(__('You have no permission'));
+ }
+ }
+ }
+ $this->error(__('Parameter %s can not be empty', 'ids'));
+ }
+
+ /**
+ * 导入
+ */
+ protected function import()
+ {
+ $file = $this->request->request('file');
+ if (!$file)
+ {
+ $this->error(__('Parameter %s can not be empty', 'file'));
+ }
+ $filePath = ROOT_PATH . DS . 'public' . DS . $file;
+ if (!is_file($filePath))
+ {
+ $this->error(__('No results were found'));
+ }
+ $PHPReader = new \PHPExcel_Reader_Excel2007();
+ if (!$PHPReader->canRead($filePath))
+ {
+ $PHPReader = new \PHPExcel_Reader_Excel5();
+ if (!$PHPReader->canRead($filePath))
+ {
+ $PHPReader = new \PHPExcel_Reader_CSV();
+ if (!$PHPReader->canRead($filePath))
+ {
+ $this->error(__('Unknown data format'));
+ }
+ }
+ }
+
+ //导入文件首行类型,默认是注释,如果需要使用字段名称请使用name
+ $importHeadType = isset($this->importHeadType) ? $this->importHeadType : 'comment';
+
+ $table = $this->model->getQuery()->getTable();
+ $database = \think\Config::get('database.database');
+ $fieldArr = [];
+ $list = db()->query("SELECT COLUMN_NAME,COLUMN_COMMENT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ? AND TABLE_SCHEMA = ?", [$table, $database]);
+ foreach ($list as $k => $v)
+ {
+ if ($importHeadType == 'comment')
+ {
+ $fieldArr[$v['COLUMN_COMMENT']] = $v['COLUMN_NAME'];
+ }
+ else
+ {
+ $fieldArr[$v['COLUMN_NAME']] = $v['COLUMN_NAME'];
+ }
+ }
+
+ $PHPExcel = $PHPReader->load($filePath); //加载文件
+ $currentSheet = $PHPExcel->getSheet(0); //读取文件中的第一个工作表
+ $allColumn = $currentSheet->getHighestDataColumn(); //取得最大的列号
+ $allRow = $currentSheet->getHighestRow(); //取得一共有多少行
+ $maxColumnNumber = \PHPExcel_Cell::columnIndexFromString($allColumn);
+ for ($currentRow = 1; $currentRow <= 1; $currentRow++)
+ {
+ for ($currentColumn = 0; $currentColumn < $maxColumnNumber; $currentColumn++)
+ {
+ $val = $currentSheet->getCellByColumnAndRow($currentColumn, $currentRow)->getValue();
+ $fields[] = $val;
+ }
+ }
+ $insert = [];
+ for ($currentRow = 2; $currentRow <= $allRow; $currentRow++)
+ {
+ $values = [];
+ for ($currentColumn = 0; $currentColumn < $maxColumnNumber; $currentColumn++)
+ {
+ $val = $currentSheet->getCellByColumnAndRow($currentColumn, $currentRow)->getValue();
+ $values[] = is_null($val) ? '' : $val;
+ }
+ $row = [];
+ $temp = array_combine($fields, $values);
+ foreach ($temp as $k => $v)
+ {
+ if (isset($fieldArr[$k]) && $k !== '')
+ {
+ $row[$fieldArr[$k]] = $v;
+ }
+ }
+ if ($row)
+ {
+ $insert[] = $row;
+ }
+ }
+ if (!$insert)
+ {
+ $this->error(__('No rows were updated'));
+ }
+ try
+ {
+ $this->model->saveAll($insert);
+ }
+ catch (\think\exception\PDOException $exception)
+ {
+ $this->error($exception->getMessage());
+ }
+
+ $this->success();
+ }
+
+}
diff --git a/application/common/model/User.php b/application/common/model/User.php
index c2ed693f21ec407dbb3df2d2eacf8fec9b86d2e1..5d6f42e813b2409d724acab662fcef2b3c402a48 100644
--- a/application/common/model/User.php
+++ b/application/common/model/User.php
@@ -102,7 +102,8 @@ class User Extends Model
*/
public static function nextlevel($score = 0)
{
- $lv = array(1 => 0, 2 => 30, 3 => 100, 4 => 500, 5 => 1000, 6 => 2000, 7 => 3000, 8 => 5000, 9 => 8000, 10 => 10000);
+ //$lv = array(1 => 0, 2 => 30, 3 => 100, 4 => 500, 5 => 1000, 6 => 2000, 7 => 3000, 8 => 5000, 9 => 8000, 10 => 10000);
+ $lv = \think\Db::name('user_ranks')->column('level_id,amount');
$level = 1;
foreach ($lv as $key => $value)
{
diff --git a/application/common/model/UserLog.php b/application/common/model/UserLog.php
new file mode 100644
index 0000000000000000000000000000000000000000..47a568232c59d07afba13f24a151f0a3e1443bc6
--- /dev/null
+++ b/application/common/model/UserLog.php
@@ -0,0 +1,73 @@
+server('HTTP_TOKEN', request()->request('token', Cookie::get('token')));
+ $auth->init($token);
+ $user_id = $auth->isLogin() ? $auth->id : 0;
+ $username = $auth->isLogin() ? $auth->username : __('Unknown');
+ $content = self::$content;
+ if (!$content) {
+ $content = request()->param();
+ foreach ($content as $k => $v) {
+ if (is_string($v) && strlen($v) > 200 || stripos($k, 'password') !== false) {
+ unset($content[$k]);
+ }
+ }
+ }
+ $title = self::$title;
+ if (!$title) {
+ $title = [];
+ $breadcrumb = Auth::instance()->getBreadCrumb();
+ if($breadcrumb){
+ foreach ($breadcrumb as $k => $v) {
+ $title[] = $v['title'];
+ }
+ }
+ $title = implode(' ', $title);
+ }
+ self::create([
+ 'title' => $title,
+ 'content' => !is_scalar($content) ? json_encode($content) : $content,
+ 'url' => request()->url(),
+ 'user_id' => $user_id,
+ 'username' => $username,
+ 'useragent' => request()->server('HTTP_USER_AGENT'),
+ 'ip' => request()->ip()
+ ]);
+ }
+
+}
diff --git a/application/common/model/UserRule.php b/application/common/model/UserRule.php
index a6efa88aa2742407f3a4bbc2c069d72acc04af67..8e86d0d1e326c37ff4a7b9ad0eda137f10fd7333 100644
--- a/application/common/model/UserRule.php
+++ b/application/common/model/UserRule.php
@@ -2,20 +2,28 @@
namespace app\common\model;
+use think\Cache;
use think\Model;
class UserRule extends Model
{
- // 表名
- protected $name = 'user_rule';
// 自动写入时间戳字段
protected $autoWriteTimestamp = 'int';
// 定义时间戳字段名
protected $createTime = 'createtime';
protected $updateTime = 'updatetime';
- // 追加属性
- protected $append = [
- ];
-
+
+ protected static function init()
+ {
+ self::afterWrite(function ($row) {
+ Cache::rm('__menu__');
+ });
+ }
+
+ public function getTitleAttr($value, $data)
+ {
+ return __($value);
+ }
+
}
diff --git a/application/common/model/user/Level.php b/application/common/model/user/Level.php
new file mode 100644
index 0000000000000000000000000000000000000000..7a5452f4ebcdce21c3970be69e9f98eb02b17b00
--- /dev/null
+++ b/application/common/model/user/Level.php
@@ -0,0 +1,33 @@
+ [],
+ 'edit' => [],
+ ];
+
+}
diff --git a/application/common/validate/user/Log.php b/application/common/validate/user/Log.php
new file mode 100644
index 0000000000000000000000000000000000000000..383d9b152c482c66d22f15969a26b68da664186c
--- /dev/null
+++ b/application/common/validate/user/Log.php
@@ -0,0 +1,27 @@
+ [],
+ 'edit' => [],
+ ];
+
+}
diff --git a/application/common/validate/user/Scorelog.php b/application/common/validate/user/Scorelog.php
new file mode 100644
index 0000000000000000000000000000000000000000..99ab9cff8bd0cb7373352313d823f3a314dbe030
--- /dev/null
+++ b/application/common/validate/user/Scorelog.php
@@ -0,0 +1,27 @@
+ [],
+ 'edit' => [],
+ ];
+
+}
diff --git a/application/index/controller/Ajax.php b/application/index/controller/Ajax.php
index c4e2e865f1485d8ae87094a0dac1a93a49e9682d..fda48511e5ab5e979ff1b057cd5a3fdda75cdaa3 100644
--- a/application/index/controller/Ajax.php
+++ b/application/index/controller/Ajax.php
@@ -26,7 +26,7 @@ class Ajax extends Frontend
$controllername = input("controllername");
$this->loadlang($controllername);
//强制输出JSON Object
- $result = 'define(' . json_encode(Lang::get(), JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE) . ');';
+ $result = jsonp(Lang::get(), 200, [], ['json_encode_param' => JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE]);
return $result;
}
diff --git a/application/index/controller/User.php b/application/index/controller/User.php
index 1a31801bb1d64926b66d93b79604bbf2e0de7ce9..f3ee447218e38b46825bcd97ed8a10aa82a00637 100644
--- a/application/index/controller/User.php
+++ b/application/index/controller/User.php
@@ -3,6 +3,7 @@
namespace app\index\controller;
use app\common\controller\Frontend;
+use app\common\model\UserLog;
use think\Config;
use think\Cookie;
use think\Hook;
@@ -58,7 +59,7 @@ class User extends Frontend
* @param $name
* @return mixed
*/
- public function _empty($name)
+ public function _empty($name='')
{
$data = Hook::listen("user_request_empty", $name);
foreach ($data as $index => $datum) {
@@ -130,7 +131,7 @@ class User extends Frontend
$uc = new \addons\ucenter\library\client\Client();
$synchtml = $uc->uc_user_synregister($this->auth->id, $password);
}
- $this->success(__('Sign up successful') . $synchtml, $url ? $url : url('user/index'));
+ $this->success(__('Sign up successful') . $synchtml, $url ? $url : url('user/index/index'));
} else {
$this->error($this->auth->getError(), null, ['token' => $this->request->token()]);
}
@@ -138,7 +139,7 @@ class User extends Frontend
//判断来源
$referer = $this->request->server('HTTP_REFERER');
if (!$url && (strtolower(parse_url($referer, PHP_URL_HOST)) == strtolower($this->request->host()))
- && !preg_match("/(user\/login|user\/register)/i", $referer)) {
+ && !preg_match("/(index\/user\/login|index\/user\/register)/i", $referer)) {
$url = $referer;
}
$this->view->assign('url', $url);
@@ -182,6 +183,7 @@ class User extends Frontend
$this->error(__($validate->getError()), null, ['token' => $this->request->token()]);
return FALSE;
}
+ UserLog::setTitle(__('Login'));
if ($this->auth->login($account, $password)) {
$synchtml = '';
////////////////同步到Ucenter////////////////
@@ -189,7 +191,7 @@ class User extends Frontend
$uc = new \addons\ucenter\library\client\Client();
$synchtml = $uc->uc_user_synlogin($this->auth->id);
}
- $this->success(__('Logged in successful') . $synchtml, $url ? $url : url('user/index'));
+ $this->success(__('Logged in successful') . $synchtml, $url ? $url : url('user/index/index'));
} else {
$this->error($this->auth->getError(), null, ['token' => $this->request->token()]);
}
@@ -197,7 +199,7 @@ class User extends Frontend
//判断来源
$referer = $this->request->server('HTTP_REFERER');
if (!$url && (strtolower(parse_url($referer, PHP_URL_HOST)) == strtolower($this->request->host()))
- && !preg_match("/(user\/login|user\/register)/i", $referer)) {
+ && !preg_match("/(index\/user\/login|index\/user\/register)/i", $referer)) {
$url = $referer;
}
$this->view->assign('url', $url);
@@ -218,7 +220,7 @@ class User extends Frontend
$uc = new \addons\ucenter\library\client\Client();
$synchtml = $uc->uc_user_synlogout();
}
- $this->success(__('Logout successful') . $synchtml, url('user/index'));
+ $this->success(__('Logout successful') . $synchtml, url('index/index'));
}
/**
diff --git a/application/index/view/index/index.html b/application/index/view/index/index.html
index 9734f9f8ed76a2cad584f09a6ed5da1a70977bb6..04a399677a6858d6a8f307823775c7a39208560e 100755
--- a/application/index/view/index/index.html
+++ b/application/index/view/index/index.html
@@ -12,17 +12,17 @@
FastAdmin - {:__('The fastest framework based on ThinkPHP5 and Bootstrap')}
-
+
-
-
+
+
@@ -64,7 +64,7 @@
FastAdmin
{:__('The fastest framework based on ThinkPHP5 and Bootstrap')}
{:__('Go to Dashboard')}
- {:__('Go to Member center')}
+ {:__('Go to Member center')}
@@ -163,13 +163,13 @@
-
+
-
+
-
+
+
+
\ No newline at end of file
diff --git a/application/user/view/index/index.html b/application/user/view/index/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..49d9c90c05ee674f35b7c75f48dcbe2066de5ed2
--- /dev/null
+++ b/application/user/view/index/index.html
@@ -0,0 +1,50 @@
+
+
+
+ {include file="layout/meta" /}
+
+
+
+
+
+ {include file='layout/header' /}
+
+
+
+ {include file='layout/menu' /}
+
+
+
+
+ {if $fixedmenu}
+
+ {/if}
+ {if $referermenu}
+
+ {/if}
+
+
+
+
+
+
+
+ Copyright © 2017-2018 Fastadmin . All rights reserved.
+
+
+
+
+ {include file="layout/control" /}
+
+
+
+
+
+ {include file="layout/script" /}
+
+
\ No newline at end of file
diff --git a/application/user/view/layout/control.html b/application/user/view/layout/control.html
new file mode 100644
index 0000000000000000000000000000000000000000..24b6a75e86c615d19bd58e68a8d670fd05d82a64
--- /dev/null
+++ b/application/user/view/layout/control.html
@@ -0,0 +1,59 @@
+
+
+
+
\ No newline at end of file
diff --git a/application/user/view/layout/default.html b/application/user/view/layout/default.html
new file mode 100644
index 0000000000000000000000000000000000000000..3ee5c3e250073f5f53b71fb73555f0b4dc3babb3
--- /dev/null
+++ b/application/user/view/layout/default.html
@@ -0,0 +1,43 @@
+
+
+
+ {include file="layout/meta" /}
+
+
+
+
+
+
+
+
+
+ {if !IS_DIALOG}
+
+
+
+ {/if}
+
+ {__CONTENT__}
+
+
+
+
+
+
+ {include file="layout/script" /}
+
+
\ No newline at end of file
diff --git a/application/user/view/layout/header.html b/application/user/view/layout/header.html
new file mode 100644
index 0000000000000000000000000000000000000000..9e4667ca14dc55e506e0bb417cef4463ddf9e7f2
--- /dev/null
+++ b/application/user/view/layout/header.html
@@ -0,0 +1,132 @@
+
+
+
+ {$site.name|mb_substr=0,4,'utf-8'|mb_strtoupper='utf-8'}
+
+ {$site.name|mb_substr=0,4,'utf-8'} {$site.name|mb_substr=4,null,'utf-8'}
+
+
+
+
+
+
+
+
+ {if $config.fastadmin.multiplenav}
+
+
+
+ {if $fixedmenu}
+
+ {/if}
+ {if $referermenu}
+
+ {/if}
+
+
+ {/if}
+
\ No newline at end of file
diff --git a/application/user/view/layout/menu.html b/application/user/view/layout/menu.html
new file mode 100644
index 0000000000000000000000000000000000000000..92635cf71421fc05ce48b95fadc11b4e15977648
--- /dev/null
+++ b/application/user/view/layout/menu.html
@@ -0,0 +1,34 @@
+
+
+
\ No newline at end of file
diff --git a/application/user/view/layout/meta.html b/application/user/view/layout/meta.html
new file mode 100644
index 0000000000000000000000000000000000000000..01b8e7c33d6dbb670a0561001119f1d6fe078511
--- /dev/null
+++ b/application/user/view/layout/meta.html
@@ -0,0 +1,19 @@
+
+{$title|default=''}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/application/user/view/layout/script.html b/application/user/view/layout/script.html
new file mode 100644
index 0000000000000000000000000000000000000000..9b17ed472b002ccae6fd8be85e6229ad3785a7e2
--- /dev/null
+++ b/application/user/view/layout/script.html
@@ -0,0 +1,2 @@
+
+
diff --git a/application/user/view/user/changepwd.html b/application/user/view/user/changepwd.html
new file mode 100644
index 0000000000000000000000000000000000000000..375b68b557e47b07a0e714096422d0640b43d859
--- /dev/null
+++ b/application/user/view/user/changepwd.html
@@ -0,0 +1,35 @@
+
\ No newline at end of file
diff --git a/application/user/view/user/index.html b/application/user/view/user/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..0cbf3cbfca6b4e6dfaf7e5219fa3a093307e5b4f
--- /dev/null
+++ b/application/user/view/user/index.html
@@ -0,0 +1,74 @@
+
+
+ {:build_heading()}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {$user.bio|default=__("This guy hasn't written anything yet")}
+
+
+
+
+
+
{:__('Lv')}
+
+
{:__('Score')}
+
+
+
+
{:__('Successions')}
+
{$user.successions} {:__('Day')}
+
{:__('Maxsuccessions')}
+
{$user.maxsuccessions} {:__('Day')}
+
+
+
{:__('Logintime')}
+
{$user.logintime|date="Y-m-d H:i:s",###}
+
{:__('Prevtime')}
+
{$user.prevtime|date="Y-m-d H:i:s",###}
+
+
+
+
+
+
+
diff --git a/application/user/view/user/log/detail.html b/application/user/view/user/log/detail.html
new file mode 100644
index 0000000000000000000000000000000000000000..b63f9431444631810bcf434766e7ddf3a72bc1c1
--- /dev/null
+++ b/application/user/view/user/log/detail.html
@@ -0,0 +1,22 @@
+
+
+
+ {:__('Title')}
+ {:__('Content')}
+
+
+
+ {volist name="row" id="vo" }
+
+ {:__($key)}
+ {$vo}
+
+ {/volist}
+
+
+
\ No newline at end of file
diff --git a/application/user/view/user/log/index.html b/application/user/view/user/log/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..00342e19359c0da496ef30fca9271fe42acc6d44
--- /dev/null
+++ b/application/user/view/user/log/index.html
@@ -0,0 +1,19 @@
+
+ {:build_heading()}
+
+
+
diff --git a/application/user/view/user/profile.html b/application/user/view/user/profile.html
new file mode 100644
index 0000000000000000000000000000000000000000..038eca85d9f7c0ddc968b8e031841913048ae2e8
--- /dev/null
+++ b/application/user/view/user/profile.html
@@ -0,0 +1,222 @@
+
+
+
+
+
+ {:__('Profile')}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/application/user/view/user/scorelog/index.html b/application/user/view/user/scorelog/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..39b695ab3ae454568988621d76b57d570013aa5f
--- /dev/null
+++ b/application/user/view/user/scorelog/index.html
@@ -0,0 +1,33 @@
+
+ {:build_heading()}
+
+
+
diff --git a/public/assets/js/backend/auth/rule.js b/public/assets/js/backend/auth/rule.js
index 75737090cea5d98afd87fc76074353b2f0525f3d..f380c8b387e6435c8ba468802856b8b2884c64d2 100755
--- a/public/assets/js/backend/auth/rule.js
+++ b/public/assets/js/backend/auth/rule.js
@@ -24,6 +24,12 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
columns: [
[
{field: 'state', checkbox: true,},
+ {
+ field: 'id',
+ title: ' ',
+ operate: false,
+ formatter: Controller.api.formatter.subnode
+ },
{field: 'id', title: 'ID'},
{field: 'title', title: __('Title'), align: 'left', formatter: Controller.api.formatter.title},
{field: 'icon', title: __('Icon'), formatter: Controller.api.formatter.icon},
@@ -36,12 +42,6 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
align: 'center',
formatter: Table.api.formatter.toggle
},
- {
- field: 'id',
- title: ' ',
- operate: false,
- formatter: Controller.api.formatter.subnode
- },
{
field: 'operate',
title: __('Operate'),
@@ -62,8 +62,8 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
//当内容渲染完成后
table.on('post-body.bs.table', function (e, settings, json, xhr) {
//默认隐藏所有子节点
- //$("a.btn[data-id][data-pid][data-pid!=0]").closest("tr").hide();
- $(".btn-node-sub.disabled").closest("tr").hide();
+ $("a.btn[data-id][data-pid][data-pid!=0]").closest("tr").hide();
+ //$(".btn-node-sub.disabled").closest("tr").hide();
//显示隐藏子节点
$(".btn-node-sub").off("click").on("click", function (e) {
diff --git a/public/assets/js/backend/user/level.js b/public/assets/js/backend/user/level.js
new file mode 100644
index 0000000000000000000000000000000000000000..caf5d3b50067ccb4462baf5008903089f21685fc
--- /dev/null
+++ b/public/assets/js/backend/user/level.js
@@ -0,0 +1,54 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
+
+ var Controller = {
+ index: function () {
+ // 初始化表格参数配置
+ Table.api.init({
+ extend: {
+ index_url: 'user/level/index',
+ add_url: 'user/level/add',
+ edit_url: 'user/level/edit',
+ del_url: 'user/level/del',
+ multi_url: 'user/level/multi',
+ table: 'user_level',
+ }
+ });
+
+ var table = $("#table");
+
+ // 初始化表格
+ table.bootstrapTable({
+ url: $.fn.bootstrapTable.defaults.extend.index_url,
+ pk: 'level_id',
+ sortName: 'level_id',
+ columns: [
+ [
+ {checkbox: true},
+ {field: 'level_id', title: __('Level_id')},
+ {field: 'level_img', title: __('level_img'), operate: false, formatter: Table.api.formatter.image},
+ {field: 'level_name', title: __('Level_name')},
+ {field: 'amount', title: __('Amount'), operate:'BETWEEN'},
+ {field: 'discount', title: __('Discount')},
+ {field: 'describe', title: __('Describe')},
+ {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
+ ]
+ ]
+ });
+
+ // 为表格绑定事件
+ Table.api.bindevent(table);
+ },
+ add: function () {
+ Controller.api.bindevent();
+ },
+ edit: function () {
+ Controller.api.bindevent();
+ },
+ api: {
+ bindevent: function () {
+ Form.api.bindevent($("form[role=form]"));
+ }
+ }
+ };
+ return Controller;
+});
\ No newline at end of file
diff --git a/public/assets/js/backend/user/log.js b/public/assets/js/backend/user/log.js
new file mode 100644
index 0000000000000000000000000000000000000000..3dfef686950348acb30087c3a367a04b74ac569d
--- /dev/null
+++ b/public/assets/js/backend/user/log.js
@@ -0,0 +1,55 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
+
+ var Controller = {
+ index: function () {
+ // 初始化表格参数配置
+ Table.api.init({
+ extend: {
+ index_url: 'user/log/index',
+ add_url: 'user/log/add',
+ edit_url: 'user/log/edit',
+ del_url: 'user/log/del',
+ multi_url: 'user/log/multi',
+ table: 'user_log',
+ }
+ });
+
+ var table = $("#table");
+
+ // 初始化表格
+ table.bootstrapTable({
+ url: $.fn.bootstrapTable.defaults.extend.index_url,
+ pk: 'id',
+ sortName: 'id',
+ columns: [
+ [
+ {checkbox: true},
+ {field: 'id', title: __('Id')},
+ {field: 'user_id', title: __('User_id')},
+ {field: 'username', title: __('Username')},
+ {field: 'url', title: __('Url'), formatter: Table.api.formatter.url},
+ {field: 'title', title: __('Title')},
+ {field: 'ip', title: __('Ip')},
+ {field: 'createtime', title: __('Createtime'), operate:'RANGE', addclass:'datetimerange', formatter: Table.api.formatter.datetime},
+ {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
+ ]
+ ]
+ });
+
+ // 为表格绑定事件
+ Table.api.bindevent(table);
+ },
+ add: function () {
+ Controller.api.bindevent();
+ },
+ edit: function () {
+ Controller.api.bindevent();
+ },
+ api: {
+ bindevent: function () {
+ Form.api.bindevent($("form[role=form]"));
+ }
+ }
+ };
+ return Controller;
+});
\ No newline at end of file
diff --git a/public/assets/js/backend/user/rule.js b/public/assets/js/backend/user/rule.js
index 6cf3575f41355f9192845d64f15d7c2a18bb3145..8cfdd86d022c5a8fc3ee0877bba5962cf27493d8 100644
--- a/public/assets/js/backend/user/rule.js
+++ b/public/assets/js/backend/user/rule.js
@@ -25,13 +25,14 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
columns: [
[
{checkbox: true},
+ {field: 'id', title: ' ', operate: false, formatter: Controller.api.formatter.subnode},
{field: 'id', title: __('Id')},
{field: 'pid', title: __('Pid'), visible: false},
{field: 'title', title: __('Title'), align: 'left'},
{field: 'name', title: __('Name'), align: 'left'},
- {field: 'remark', title: __('Remark')},
+ {field: 'icon', title: __('Icon'), formatter: Controller.api.formatter.icon},
{field: 'ismenu', title: __('Ismenu'), formatter: Table.api.formatter.toggle},
- {field: 'createtime', title: __('Createtime'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true, visible: false},
+ {field: 'type', title: __('Type') },
{field: 'updatetime', title: __('Updatetime'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true, visible: false},
{field: 'weigh', title: __('Weigh')},
{field: 'status', title: __('Status'), formatter: Table.api.formatter.status},
@@ -45,6 +46,50 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
// 为表格绑定事件
Table.api.bindevent(table);
+
+ //当内容渲染完成后
+ table.on('post-body.bs.table', function (e, settings, json, xhr) {
+ //默认隐藏所有子节点
+ $("a.btn[data-id][data-pid][data-pid!=0]").closest("tr").hide();
+ //$(".btn-node-sub.disabled").closest("tr").hide();
+
+ //显示隐藏子节点
+ $(".btn-node-sub").off("click").on("click", function (e) {
+ var status = $(this).data("shown") ? true : false;
+ $("a.btn[data-pid='" + $(this).data("id") + "']").each(function () {
+ $(this).closest("tr").toggle(!status);
+ });
+ $(this).data("shown", !status);
+ return false;
+ });
+ $(".btn-change[data-id],.btn-delone,.btn-dragsort").data("success", function (data, ret) {
+ Fast.api.refreshmenu();
+ });
+
+ });
+ //批量删除后的回调
+ $(".toolbar > .btn-del,.toolbar .btn-more~ul>li>a").data("success", function (e) {
+ Fast.api.refreshmenu();
+ });
+ //展开隐藏一级
+ $(document.body).on("click", ".btn-toggle", function (e) {
+ $("a.btn[data-id][data-pid][data-pid!=0].disabled").closest("tr").hide();
+ var that = this;
+ var show = $("i", that).hasClass("fa-chevron-down");
+ $("i", that).toggleClass("fa-chevron-down", !show);
+ $("i", that).toggleClass("fa-chevron-up", show);
+ $("a.btn[data-id][data-pid][data-pid!=0]").not('.disabled').closest("tr").toggle(show);
+ $(".btn-node-sub[data-pid=0]").data("shown", show);
+ });
+ //展开隐藏全部
+ $(document.body).on("click", ".btn-toggle-all", function (e) {
+ var that = this;
+ var show = $("i", that).hasClass("fa-plus");
+ $("i", that).toggleClass("fa-plus", !show);
+ $("i", that).toggleClass("fa-minus", show);
+ $(".btn-node-sub.disabled").closest("tr").toggle(show);
+ $(".btn-node-sub").data("shown", show);
+ });
},
add: function () {
Controller.api.bindevent();
@@ -59,7 +104,57 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
name.prop("placeholder", $(this).val() == 1 ? name.data("placeholder-menu") : name.data("placeholder-node"));
});
$("input[name='row[ismenu]']:checked").trigger("click");
- Form.api.bindevent($("form[role=form]"));
+
+ var iconlist = [];
+ Form.api.bindevent($("form[role=form]"), function (data) {
+ Fast.api.refreshmenu();
+ });
+ $(document).on('click', ".btn-search-icon", function () {
+ if (iconlist.length == 0) {
+ $.get(Config.site.cdnurl + "/assets/libs/font-awesome/less/variables.less", function (ret) {
+ var exp = /fa-var-(.*):/ig;
+ var result;
+ while ((result = exp.exec(ret)) != null) {
+ iconlist.push(result[1]);
+ }
+ Layer.open({
+ type: 1,
+ area: ['460px', '300px'], //宽高
+ content: Template('chooseicontpl', {iconlist: iconlist})
+ });
+ });
+ } else {
+ Layer.open({
+ type: 1,
+ area: ['460px', '300px'], //宽高
+ content: Template('chooseicontpl', {iconlist: iconlist})
+ });
+ }
+ });
+ $(document).on('click', '#chooseicon ul li', function () {
+ $("input[name='row[icon]']").val('fa fa-' + $(this).data("font"));
+ Layer.closeAll();
+ });
+ $(document).on('keyup', 'input.js-icon-search', function () {
+ $("#chooseicon ul li").show();
+ if ($(this).val() != '') {
+ $("#chooseicon ul li:not([data-font*='" + $(this).val() + "'])").hide();
+ }
+ });
+ },
+ formatter: {
+ toggle: function (value, row, index) {
+ //添加上btn-change可以自定义请求的URL进行数据处理
+ return ' ';
+ },
+ icon: function (value, row, index) {
+ return '';
+ },
+ subnode: function (value, row, index) {
+ return '';
+ }
+
}
}
};
diff --git a/public/assets/js/backend/user/scorelog.js b/public/assets/js/backend/user/scorelog.js
new file mode 100644
index 0000000000000000000000000000000000000000..50fada87273509a20b9167b3c96424b74a5dab50
--- /dev/null
+++ b/public/assets/js/backend/user/scorelog.js
@@ -0,0 +1,55 @@
+define(['jquery', 'bootstrap', 'userend', 'table', 'form'], function ($, undefined, Userend, Table, Form) {
+
+ var Controller = {
+ index: function () {
+ // 初始化表格参数配置
+ Table.api.init({
+ extend: {
+ index_url: 'user/scorelog/index',
+ add_url: 'user/scorelog/add',
+ edit_url: 'user/scorelog/edit',
+ del_url: 'user/scorelog/del',
+ multi_url: 'user/scorelog/multi',
+ table: 'user_score_log',
+ }
+ });
+
+ var table = $("#table");
+
+ // 初始化表格
+ table.bootstrapTable({
+ url: $.fn.bootstrapTable.defaults.extend.index_url,
+ pk: 'id',
+ sortName: 'id',
+ columns: [
+ [
+ {checkbox: true},
+ {field: 'id', title: __('Id')},
+ {field: 'user_id', title: __('User_id')},
+ {field: 'score', title: __('Score')},
+ {field: 'before', title: __('Before')},
+ {field: 'after', title: __('After')},
+ {field: 'memo', title: __('Memo')},
+ {field: 'createtime', title: __('Createtime'), operate:'RANGE', addclass:'datetimerange', formatter: Table.api.formatter.datetime},
+ {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
+ ]
+ ]
+ });
+
+ // 为表格绑定事件
+ Table.api.bindevent(table);
+ },
+ add: function () {
+ Controller.api.bindevent();
+ },
+ edit: function () {
+ Controller.api.bindevent();
+ },
+ api: {
+ bindevent: function () {
+ Form.api.bindevent($("form[role=form]"));
+ }
+ }
+ };
+ return Controller;
+});
\ No newline at end of file
diff --git a/public/assets/js/require-userend.js b/public/assets/js/require-userend.js
new file mode 100644
index 0000000000000000000000000000000000000000..4bfd21961f2b6e2591ea1842141445a6432c24c6
--- /dev/null
+++ b/public/assets/js/require-userend.js
@@ -0,0 +1,157 @@
+require.config({
+ urlArgs: "v=" + requirejs.s.contexts._.config.config.site.version,
+ packages: [{
+ name: 'moment',
+ location: '../libs/moment',
+ main: 'moment'
+ }
+ ],
+ //在打包压缩时将会把include中的模块合并到主文件中
+ include: ['css', 'layer', 'toastr', 'fast', 'userend', 'userend-init', 'table', 'form', 'dragsort', 'drag', 'drop', 'addtabs', 'selectpage'],
+ paths: {
+ 'lang': "empty:",
+ 'form': 'require-form',
+ 'table': 'require-table',
+ 'upload': 'require-upload',
+ 'validator': 'require-validator',
+ 'drag': 'jquery.drag.min',
+ 'drop': 'jquery.drop.min',
+ 'echarts': 'echarts.min',
+ 'echarts-theme': 'echarts-theme',
+ 'adminlte': 'adminlte',
+ 'bootstrap-table-commonsearch': 'bootstrap-table-commonsearch',
+ 'bootstrap-table-template': 'bootstrap-table-template',
+ //
+ // 以下的包从bower的libs目录加载
+ 'jquery': '../libs/jquery/dist/jquery.min',
+ 'bootstrap': '../libs/bootstrap/dist/js/bootstrap.min',
+ 'bootstrap-datetimepicker': '../libs/eonasdan-bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min',
+ 'bootstrap-daterangepicker': '../libs/bootstrap-daterangepicker/daterangepicker',
+ 'bootstrap-select': '../libs/bootstrap-select/dist/js/bootstrap-select.min',
+ 'bootstrap-select-lang': '../libs/bootstrap-select/dist/js/i18n/defaults-zh_CN',
+ 'bootstrap-table': '../libs/bootstrap-table/dist/bootstrap-table.min',
+ 'bootstrap-table-export': '../libs/bootstrap-table/dist/extensions/export/bootstrap-table-export.min',
+ 'bootstrap-table-mobile': '../libs/bootstrap-table/dist/extensions/mobile/bootstrap-table-mobile',
+ 'bootstrap-table-lang': '../libs/bootstrap-table/dist/locale/bootstrap-table-zh-CN',
+ 'tableexport': '../libs/tableExport.jquery.plugin/tableExport.min',
+ 'dragsort': '../libs/fastadmin-dragsort/jquery.dragsort',
+ 'sortable': '../libs/Sortable/Sortable.min',
+ 'addtabs': '../libs/fastadmin-addtabs/jquery.addtabs',
+ 'slimscroll': '../libs/jquery-slimscroll/jquery.slimscroll',
+ 'validator-core': '../libs/nice-validator/dist/jquery.validator',
+ 'validator-lang': '../libs/nice-validator/dist/local/zh-CN',
+ 'plupload': '../libs/plupload/js/plupload.min',
+ 'toastr': '../libs/toastr/toastr',
+ 'jstree': '../libs/jstree/dist/jstree.min',
+ 'layer': '../libs/fastadmin-layer/dist/layer',
+ 'cookie': '../libs/jquery.cookie/jquery.cookie',
+ 'cxselect': '../libs/fastadmin-cxselect/js/jquery.cxselect',
+ 'template': '../libs/art-template/dist/template-native',
+ 'selectpage': '../libs/fastadmin-selectpage/selectpage',
+ 'citypicker': '../libs/fastadmin-citypicker/dist/js/city-picker.min',
+ 'citypicker-data': '../libs/fastadmin-citypicker/dist/js/city-picker.data',
+ },
+ // shim依赖配置
+ shim: {
+ 'addons': ['userend'],
+ 'bootstrap': ['jquery'],
+ 'bootstrap-table': {
+ deps: [
+ 'bootstrap',
+// 'css!../libs/bootstrap-table/dist/bootstrap-table.min.css'
+ ],
+ exports: '$.fn.bootstrapTable'
+ },
+ 'bootstrap-table-lang': {
+ deps: ['bootstrap-table'],
+ exports: '$.fn.bootstrapTable.defaults'
+ },
+ 'bootstrap-table-export': {
+ deps: ['bootstrap-table', 'tableexport'],
+ exports: '$.fn.bootstrapTable.defaults'
+ },
+ 'bootstrap-table-mobile': {
+ deps: ['bootstrap-table'],
+ exports: '$.fn.bootstrapTable.defaults'
+ },
+ 'bootstrap-table-advancedsearch': {
+ deps: ['bootstrap-table'],
+ exports: '$.fn.bootstrapTable.defaults'
+ },
+ 'bootstrap-table-commonsearch': {
+ deps: ['bootstrap-table'],
+ exports: '$.fn.bootstrapTable.defaults'
+ },
+ 'bootstrap-table-template': {
+ deps: ['bootstrap-table', 'template'],
+ exports: '$.fn.bootstrapTable.defaults'
+ },
+ 'tableexport': {
+ deps: ['jquery'],
+ exports: '$.fn.extend'
+ },
+ 'slimscroll': {
+ deps: ['jquery'],
+ exports: '$.fn.extend'
+ },
+ 'adminlte': {
+ deps: ['bootstrap', 'slimscroll'],
+ exports: '$.AdminLTE'
+ },
+ 'bootstrap-datetimepicker': [
+ 'moment/locale/zh-cn',
+// 'css!../libs/eonasdan-bootstrap-datetimepicker/build/css/bootstrap-datetimepicker.min.css',
+ ],
+// 'bootstrap-select': ['css!../libs/bootstrap-select/dist/css/bootstrap-select.min.css', ],
+ 'bootstrap-select-lang': ['bootstrap-select'],
+// 'toastr': ['css!../libs/toastr/toastr.min.css'],
+ 'jstree': ['css!../libs/jstree/dist/themes/default/style.css', ],
+ 'plupload': {
+ deps: ['../libs/plupload/js/moxie.min'],
+ exports: "plupload"
+ },
+// 'layer': ['css!../libs/layer/dist/theme/default/layer.css'],
+// 'validator-core': ['css!../libs/nice-validator/dist/jquery.validator.css'],
+ 'validator-lang': ['validator-core'],
+// 'selectpage': ['css!../libs/fastadmin-selectpage/selectpage.css'],
+ 'citypicker': ['citypicker-data', 'css!../libs/fastadmin-citypicker/dist/css/city-picker.css']
+ },
+ baseUrl: requirejs.s.contexts._.config.config.site.cdnurl + '/assets/js/', //资源基础路径
+ map: {
+ '*': {
+ 'css': '../libs/require-css/css.min'
+ }
+ },
+ waitSeconds: 30,
+ charset: 'utf-8' // 文件编码
+});
+
+require(['jquery', 'bootstrap'], function ($, undefined) {
+ //初始配置
+ var Config = requirejs.s.contexts._.config.config;
+ //将Config渲染到全局
+ window.Config = Config;
+ // 配置语言包的路径
+ var paths = {};
+ paths['lang'] = Config.moduleurl + '/ajax/lang?callback=define&controllername=' + Config.controllername;
+ // 避免目录冲突
+ paths['userend/'] = 'userend/';
+ require.config({paths: paths});
+
+ // 初始化
+ $(function () {
+ require(['fast'], function (Fast) {
+ require(['userend', 'userend-init', 'addons'], function (Userend, undefined, Addons) {
+ //加载相应模块
+ if (Config.jsname) {
+ require([Config.jsname], function (Controller) {
+ Controller[Config.actionname] != undefined && Controller[Config.actionname]();
+ }, function (e) {
+ console.error(e);
+ // 这里可捕获模块加载的错误
+ });
+ }
+ });
+ });
+ });
+});
diff --git a/public/assets/js/require-userend.min.js b/public/assets/js/require-userend.min.js
new file mode 100644
index 0000000000000000000000000000000000000000..92f359e9391b2fd8349f793a49855b6217ceab5b
--- /dev/null
+++ b/public/assets/js/require-userend.min.js
@@ -0,0 +1,12914 @@
+/*! jQuery v2.2.4 | (c) jQuery Foundation | jquery.org/license */
+!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="2.2.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isPlainObject:function(a){var b;if("object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype||{},"isPrototypeOf"))return!1;for(b in a);return void 0===b||k.call(a,b)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=d.createElement("script"),b.text=a,d.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:h.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(d=e.call(arguments,2),f=function(){return a.apply(b||this,d.concat(e.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML=" ",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML=" ","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML=" ",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return h.call(b,a)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&f.parentNode&&(this.length=1,this[0]=f),this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?void 0!==c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?h.call(n(a),this[0]):h.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||n.uniqueSort(e),D.test(a)&&e.reverse()),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.removeEventListener("DOMContentLoaded",J),a.removeEventListener("load",J),n.ready()}n.ready.promise=function(b){return I||(I=n.Deferred(),"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(n.ready):(d.addEventListener("DOMContentLoaded",J),a.addEventListener("load",J))),I.promise(b)},n.ready.promise();var K=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)K(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},L=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function M(){this.expando=n.expando+M.uid++}M.uid=1,M.prototype={register:function(a,b){var c=b||{};return a.nodeType?a[this.expando]=c:Object.defineProperty(a,this.expando,{value:c,writable:!0,configurable:!0}),a[this.expando]},cache:function(a){if(!L(a))return{};var b=a[this.expando];return b||(b={},L(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if("string"==typeof b)e[b]=c;else for(d in b)e[d]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=a[this.expando];if(void 0!==f){if(void 0===b)this.register(a);else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in f?d=[b,e]:(d=e,d=d in f?[d]:d.match(G)||[])),c=d.length;while(c--)delete f[d[c]]}(void 0===b||n.isEmptyObject(f))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!n.isEmptyObject(b)}};var N=new M,O=new M,P=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Q=/[A-Z]/g;function R(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Q,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:P.test(c)?n.parseJSON(c):c;
+}catch(e){}O.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return O.hasData(a)||N.hasData(a)},data:function(a,b,c){return O.access(a,b,c)},removeData:function(a,b){O.remove(a,b)},_data:function(a,b,c){return N.access(a,b,c)},_removeData:function(a,b){N.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=O.get(f),1===f.nodeType&&!N.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),R(f,d,e[d])));N.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){O.set(this,a)}):K(this,function(b){var c,d;if(f&&void 0===b){if(c=O.get(f,a)||O.get(f,a.replace(Q,"-$&").toLowerCase()),void 0!==c)return c;if(d=n.camelCase(a),c=O.get(f,d),void 0!==c)return c;if(c=R(f,d,void 0),void 0!==c)return c}else d=n.camelCase(a),this.each(function(){var c=O.get(this,d);O.set(this,d,b),a.indexOf("-")>-1&&void 0!==c&&O.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){O.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=N.get(a,b),c&&(!d||n.isArray(c)?d=N.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return N.get(a,c)||N.access(a,c,{empty:n.Callbacks("once memory").add(function(){N.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length",""],thead:[1,""],col:[2,""],tr:[2,""],td:[3,""],_default:[0,"",""]};$.optgroup=$.option,$.tbody=$.tfoot=$.colgroup=$.caption=$.thead,$.th=$.td;function _(a,b){var c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function aa(a,b){for(var c=0,d=a.length;d>c;c++)N.set(a[c],"globalEval",!b||N.get(b[c],"globalEval"))}var ba=/<|?\w+;/;function ca(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],o=0,p=a.length;p>o;o++)if(f=a[o],f||0===f)if("object"===n.type(f))n.merge(m,f.nodeType?[f]:f);else if(ba.test(f)){g=g||l.appendChild(b.createElement("div")),h=(Y.exec(f)||["",""])[1].toLowerCase(),i=$[h]||$._default,g.innerHTML=i[1]+n.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;n.merge(m,g.childNodes),g=l.firstChild,g.textContent=""}else m.push(b.createTextNode(f));l.textContent="",o=0;while(f=m[o++])if(d&&n.inArray(f,d)>-1)e&&e.push(f);else if(j=n.contains(f.ownerDocument,f),g=_(l.appendChild(f),"script"),j&&aa(g),c){k=0;while(f=g[k++])Z.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var da=/^key/,ea=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,fa=/^([^.]*)(?:\.(.+)|)/;function ga(){return!0}function ha(){return!1}function ia(){try{return d.activeElement}catch(a){}}function ja(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ja(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=ha;else if(!e)return a;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return"undefined"!=typeof n&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(G)||[""],j=b.length;while(j--)h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.hasData(a)&&N.get(a);if(r&&(i=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&N.remove(a,"handle events")}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(N.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())a.rnamespace&&!a.rnamespace.test(g.namespace)||(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!==this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,la=/