Browse Source

新增邮箱验证码功能
新增Token默认有效期
新增Token检测和刷新API接口
新增前台发送验证码事件监听

修复修改个人资料时跳转的BUG
修复API文档Token设置无效的BUG
修复后台移除左侧链接后菜单消失的BUG

优化前台样式CSS,精简多余的CSS
优化会员登录注册视图模板

Karson 7 years ago
parent
commit
45b84424e8
33 changed files with 2041 additions and 991 deletions
  1. 1 1
      application/admin/command/Api/template/index.html
  2. 17 2
      application/admin/command/Install/fastadmin.sql
  3. 123 0
      application/api/controller/Ems.php
  4. 1 1
      application/api/controller/Sms.php
  5. 45 0
      application/api/controller/Token.php
  6. 47 22
      application/api/controller/User.php
  7. 47 12
      application/api/controller/Validate.php
  8. 4 3
      application/common/library/Auth.php
  9. 144 0
      application/common/library/Ems.php
  10. 3 2
      application/common/library/Sms.php
  11. 22 0
      application/common/model/Ems.php
  12. 10 0
      application/common/model/Token.php
  13. 1 1
      application/config.php
  14. 5 37
      application/index/controller/User.php
  15. 2 1
      application/index/lang/zh-cn/user.php
  16. 3 2
      application/index/view/layout/common.html
  17. 4 3
      application/index/view/layout/default.html
  18. 50 36
      application/index/view/user/login.html
  19. 82 44
      application/index/view/user/profile.html
  20. 5 5
      application/index/view/user/register.html
  21. 1067 391
      public/api.html
  22. 29 0
      public/assets/css/backend.css
  23. 1 1
      public/assets/css/backend.min.css
  24. 104 171
      public/assets/css/frontend.css
  25. 1 1
      public/assets/css/frontend.min.css
  26. 1 1
      public/assets/js/backend/index.js
  27. 1 1
      public/assets/js/fast.js
  28. 27 15
      public/assets/js/frontend.js
  29. 30 34
      public/assets/js/frontend/user.js
  30. 1 1
      public/assets/js/require-backend.min.js
  31. 28 16
      public/assets/js/require-frontend.min.js
  32. 28 0
      public/assets/less/backend.less
  33. 107 187
      public/assets/less/frontend.less

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

@@ -413,7 +413,7 @@
 
                     var token = $('#token').val();
                     if (token.length > 0) {
-                        headers[token] = token;
+                        headers['token'] = token;
                     }
 
                     $("#sandbox" + theId + " .headers input[type=text]").each(function () {

+ 17 - 2
application/admin/command/Install/fastadmin.sql

@@ -338,6 +338,21 @@ INSERT INTO `fa_config` VALUES (17, 'mail_from', 'email', 'Mail from', '', 'stri
 COMMIT;
 
 -- ----------------------------
+-- Table structure for fa_ems
+-- ----------------------------
+DROP TABLE IF EXISTS `fa_ems`;
+CREATE TABLE `fa_ems`  (
+  `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID',
+  `event` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '事件',
+  `email` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '邮箱',
+  `code` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '验证码',
+  `times` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '验证次数',
+  `ip` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT 'IP',
+  `createtime` int(10) UNSIGNED NULL DEFAULT 0 COMMENT '创建时间',
+  PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='邮箱验证码表';
+
+-- ----------------------------
 -- Table structure for fa_sms
 -- ----------------------------
 DROP TABLE IF EXISTS `fa_sms`;
@@ -412,8 +427,8 @@ CREATE TABLE `fa_user` (
   `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 NOT NULL COMMENT '生日',
-  `bio` varchar(100) NOT NULL COMMENT '格言',
+  `birthday` date COMMENT '生日',
+  `bio` varchar(100) NOT NULL DEFAULT '' COMMENT '格言',
   `score` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '积分',
   `successions` int(10) unsigned NOT NULL DEFAULT '1' COMMENT '连续登录天数',
   `maxsuccessions` int(10) unsigned NOT NULL DEFAULT '1' COMMENT '最大连续登录天数',

+ 123 - 0
application/api/controller/Ems.php

@@ -0,0 +1,123 @@
+<?php
+
+namespace app\api\controller;
+
+use app\common\controller\Api;
+use app\common\library\Ems as Emslib;
+use app\common\model\User;
+
+/**
+ * 邮箱验证码接口
+ */
+class Ems extends Api
+{
+
+    protected $noNeedLogin = '*';
+    protected $noNeedRight = '*';
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        \think\Hook::add('ems_send', function($params) {
+            $obj = \app\common\library\Email::instance();
+            $result = $obj
+                    ->to($params->email)
+                    ->subject('验证码')
+                    ->message("你的验证码是:" . $params->code)
+                    ->send();
+            return $result;
+        });
+    }
+
+    /**
+     * 发送验证码
+     *
+     * @param string    $email      邮箱
+     * @param string    $event      事件名称
+     */
+    public function send()
+    {
+        $email = $this->request->request("email");
+        $event = $this->request->request("event");
+        $event = $event ? $event : 'register';
+
+        $last = Emslib::get($email, $event);
+        if ($last && time() - $last['createtime'] < 60)
+        {
+            $this->error(__('发送频繁'));
+        }
+        if ($event)
+        {
+            $userinfo = User::getByEmail($email);
+            if ($event == 'register' && $userinfo)
+            {
+                //已被注册
+                $this->error(__('已被注册'));
+            }
+            else if (in_array($event, ['changeemail']) && $userinfo)
+            {
+                //被占用
+                $this->error(__('已被占用'));
+            }
+            else if (in_array($event, ['changepwd', 'resetpwd']) && !$userinfo)
+            {
+                //未注册
+                $this->error(__('未注册'));
+            }
+        }
+        $ret = Emslib::send($email, NULL, $event);
+        if ($ret)
+        {
+            $this->success(__('发送成功'));
+        }
+        else
+        {
+            $this->error(__('发送失败'));
+        }
+    }
+
+    /**
+     * 检测验证码
+     *
+     * @param string    $email      邮箱
+     * @param string    $event      事件名称
+     * @param string    $captcha    验证码
+     */
+    public function check()
+    {
+        $email = $this->request->request("email");
+        $event = $this->request->request("event");
+        $event = $event ? $event : 'register';
+        $captcha = $this->request->request("captcha");
+
+        if ($event)
+        {
+            $userinfo = User::getByEmail($email);
+            if ($event == 'register' && $userinfo)
+            {
+                //已被注册
+                $this->error(__('已被注册'));
+            }
+            else if (in_array($event, ['changeemail']) && $userinfo)
+            {
+                //被占用
+                $this->error(__('已被占用'));
+            }
+            else if (in_array($event, ['changepwd', 'resetpwd']) && !$userinfo)
+            {
+                //未注册
+                $this->error(__('未注册'));
+            }
+        }
+        $ret = Emslib::check($email, $captcha, $event);
+        if ($ret)
+        {
+            $this->success(__('成功'));
+        }
+        else
+        {
+            $this->error(__('验证码不正确'));
+        }
+    }
+
+}

+ 1 - 1
application/api/controller/Sms.php

@@ -7,7 +7,7 @@ use app\common\library\Sms as Smslib;
 use app\common\model\User;
 
 /**
- * 短信接口
+ * 手机短信接口
  */
 class Sms extends Api
 {

+ 45 - 0
application/api/controller/Token.php

@@ -0,0 +1,45 @@
+<?php
+
+namespace app\api\controller;
+
+use app\common\controller\Api;
+
+/**
+ * Token接口
+ */
+class Token extends Api
+{
+
+    protected $noNeedLogin = [];
+    protected $noNeedRight = '*';
+
+    public function _initialize()
+    {
+        parent::_initialize();
+    }
+
+    /**
+     * 检测Token是否过期
+     *
+     */
+    public function check()
+    {
+        $token = $this->auth->getToken();
+        $tokenInfo = \app\common\library\Token::get($token);
+        $this->success('', ['token' => $tokenInfo['token'], 'expires_in' => $tokenInfo['expires_in']]);
+    }
+
+    /**
+     * 刷新Token
+     *
+     */
+    public function refresh()
+    {
+        $token = $this->auth->getToken();
+        $tokenInfo = \app\common\library\Token::get($token);
+        $tokenInfo->expiretime = time() + 2592000;
+        $tokenInfo->save();
+        $this->success('', ['token' => $tokenInfo['token'], 'expires_in' => $tokenInfo['expires_in']]);
+    }
+
+}

+ 47 - 22
application/api/controller/User.php

@@ -3,7 +3,7 @@
 namespace app\api\controller;
 
 use app\common\controller\Api;
-use app\common\library\Email;
+use app\common\library\Ems;
 use app\common\library\Sms;
 use fast\Random;
 use think\Validate;
@@ -179,12 +179,14 @@ class User extends Api
      * 修改邮箱
      * 
      * @param string $email 邮箱
+     * @param string $captcha 验证码
      */
     public function changeemail()
     {
         $user = $this->auth->getUser();
         $email = $this->request->post('email');
-        if (!$email)
+        $captcha = $this->request->request('captcha');
+        if (!$email || !$captcha)
         {
             $this->error(__('Invalid parameters'));
         }
@@ -196,17 +198,18 @@ class User extends Api
         {
             $this->error(__('Email already exists'));
         }
+        $result = Ems::check($email, $captcha, 'changeemail');
+        if (!$result)
+        {
+            $this->error(__('Captcha is incorrect'));
+        }
         $verification = $user->verification;
-        $verification->email = 0;
+        $verification->email = 1;
         $user->verification = $verification;
         $user->email = $email;
         $user->save();
-        $time = time();
-        $code = ['id' => $user->id, 'time' => $time, 'key' => md5(md5($user->id . $user->email . $time) . $user->salt)];
-        $code = base64_encode(http_build_query($code));
-        $url = url("index/user/activeemail", ['code' => $code], true, true);
-        $message = __('Verify email') . ":<a href='{$url}'>{$url}</a>";
-        Email::instance()->to($email)->subject(__('Verify email'))->message($message)->send();
+
+        Ems::flush($email, 'changeemail');
         $this->success();
     }
 
@@ -291,29 +294,51 @@ class User extends Api
      */
     public function resetpwd()
     {
+        $type = $this->request->request("type");
         $mobile = $this->request->request("mobile");
+        $email = $this->request->request("email");
         $newpassword = $this->request->request("newpassword");
         $captcha = $this->request->request("captcha");
-        if (!$mobile || !$newpassword || !$captcha)
+        if (!$newpassword || !$captcha)
         {
             $this->error(__('Invalid parameters'));
         }
-        if ($mobile && !Validate::regex($mobile, "^1\d{10}$"))
+        if ($type == 'mobile')
         {
-            $this->error(__('Mobile is incorrect'));
-        }
-        $user = \app\common\model\User::getByMobile($mobile);
-        if (!$user)
-        {
-            $this->error(__('User not found'));
+            if (!Validate::regex($mobile, "^1\d{10}$"))
+            {
+                $this->error(__('Mobile is incorrect'));
+            }
+            $user = \app\common\model\User::getByMobile($mobile);
+            if (!$user)
+            {
+                $this->error(__('User not found'));
+            }
+            $ret = Sms::check($mobile, $captcha, 'resetpwd');
+            if (!$ret)
+            {
+                $this->error(__('Captcha is incorrect'));
+            }
+            Sms::flush($mobile, 'resetpwd');
         }
-        $ret = Sms::check($mobile, $captcha, 'resetpwd');
-        if (!$ret)
+        else
         {
-            $this->error(__('Captcha is incorrect'));
+            if (!Validate::is($email, "email"))
+            {
+                $this->error(__('Email is incorrect'));
+            }
+            $user = \app\common\model\User::getByEmail($email);
+            if (!$user)
+            {
+                $this->error(__('User not found'));
+            }
+            $ret = Ems::check($email, $captcha, 'resetpwd');
+            if (!$ret)
+            {
+                $this->error(__('Captcha is incorrect'));
+            }
+            Ems::flush($email, 'resetpwd');
         }
-        Sms::flush($mobile, 'resetpwd');
-
         //模拟一次登录
         $this->auth->direct($user->id);
         $ret = $this->auth->changepwd($newpassword, '', true);

+ 47 - 12
application/api/controller/Validate.php

@@ -24,7 +24,7 @@ class Validate extends Api
      * 检测邮箱
      * 
      * @param string $email 邮箱
-     * @param string $id 会员ID
+     * @param string $id 排除会员ID
      */
     public function check_email_available()
     {
@@ -42,7 +42,7 @@ class Validate extends Api
      * 检测用户名
      * 
      * @param string $username 用户名
-     * @param string $id 会员ID
+     * @param string $id 排除会员ID
      */
     public function check_username_available()
     {
@@ -60,16 +60,16 @@ class Validate extends Api
      * 检测手机
      * 
      * @param string $mobile 手机号
-     * @param string $id 会员ID
+     * @param string $id 排除会员ID
      */
     public function check_mobile_available()
     {
-        $email = $this->request->request('mobile');
+        $mobile = $this->request->request('mobile');
         $id = (int) $this->request->request('id');
-        $count = User::where('mobile', '=', $email)->where('id', '<>', $id)->count();
+        $count = User::where('mobile', '=', $mobile)->where('id', '<>', $id)->count();
         if ($count > 0)
         {
-            $this->error(__('已经使用该手机号注册'));
+            $this->error(__('该手机号已经占用'));
         }
         $this->success();
     }
@@ -81,8 +81,8 @@ class Validate extends Api
      */
     public function check_mobile_exist()
     {
-        $email = $this->request->request('mobile');
-        $count = User::where('mobile', '=', $email)->count();
+        $mobile = $this->request->request('mobile');
+        $count = User::where('mobile', '=', $mobile)->count();
         if (!$count)
         {
             $this->error(__('手机号不存在'));
@@ -91,11 +91,27 @@ class Validate extends Api
     }
 
     /**
-     * 检测验证码
+     * 检测邮箱
      * 
-     * @param string $mobile 手机号
-     * @param string $captcha 验证码
-     * @param string $event 事件
+     * @param string $mobile 邮箱
+     */
+    public function check_email_exist()
+    {
+        $email = $this->request->request('email');
+        $count = User::where('email', '=', $email)->count();
+        if (!$count)
+        {
+            $this->error(__('邮箱不存在'));
+        }
+        $this->success();
+    }
+
+    /**
+     * 检测手机验证码
+     * 
+     * @param string $mobile    手机号
+     * @param string $captcha   验证码
+     * @param string $event     事件
      */
     public function check_sms_correct()
     {
@@ -109,4 +125,23 @@ class Validate extends Api
         $this->success();
     }
 
+    /**
+     * 检测邮箱验证码
+     * 
+     * @param string $email     邮箱
+     * @param string $captcha   验证码
+     * @param string $event     事件
+     */
+    public function check_ems_correct()
+    {
+        $email = $this->request->request('email');
+        $captcha = $this->request->request('captcha');
+        $event = $this->request->request('event');
+        if (!\app\common\library\Ems::check($email, $captcha, $event))
+        {
+            $this->error(__('验证码不正确'));
+        }
+        $this->success();
+    }
+
 }

+ 4 - 3
application/common/library/Auth.php

@@ -19,7 +19,8 @@ class Auth
     protected $_logined = FALSE;
     protected $_user = NULL;
     protected $_token = '';
-    protected $keeptime = 0;
+    //Token默认有效时长
+    protected $keeptime = 2592000;
     protected $requestUri = '';
     protected $rules = [];
     //默认配置
@@ -203,7 +204,7 @@ class Auth
 
             //设置Token
             $this->_token = Random::uuid();
-            Token::set($this->_token, $user->id);
+            Token::set($this->_token, $user->id, $this->keeptime);
 
             //注册成功的事件
             Hook::listen("user_register_successed", $this->_user);
@@ -349,7 +350,7 @@ class Auth
             $this->_user = $user;
 
             $this->_token = Random::uuid();
-            Token::set($this->_token, $user->id);
+            Token::set($this->_token, $user->id, $this->keeptime);
 
             $this->_logined = TRUE;
 

+ 144 - 0
application/common/library/Ems.php

@@ -0,0 +1,144 @@
+<?php
+
+namespace app\common\library;
+
+use think\Hook;
+
+/**
+ * 邮箱验证码类
+ */
+class Ems
+{
+
+    /**
+     * 验证码有效时长
+     * @var int 
+     */
+    protected static $expire = 120;
+
+    /**
+     * 最大允许检测的次数
+     * @var int 
+     */
+    protected static $maxCheckNums = 10;
+
+    /**
+     * 获取最后一次邮箱发送的数据
+     *
+     * @param   int       $email   邮箱
+     * @param   string    $event    事件
+     * @return  Ems
+     */
+    public static function get($email, $event = 'default')
+    {
+        $ems = \app\common\model\Ems::
+                where(['email' => $email, 'event' => $event])
+                ->order('id', 'DESC')
+                ->find();
+        Hook::listen('ems_get', $ems, null, true);
+        return $ems ? $ems : NULL;
+    }
+
+    /**
+     * 发送验证码
+     *
+     * @param   int       $email   邮箱
+     * @param   int       $code     验证码,为空时将自动生成4位数字
+     * @param   string    $event    事件
+     * @return  boolean
+     */
+    public static function send($email, $code = NULL, $event = 'default')
+    {
+        $code = is_null($code) ? mt_rand(1000, 9999) : $code;
+        $time = time();
+        $ip = request()->ip();
+        $ems = \app\common\model\Ems::create(['event' => $event, 'email' => $email, 'code' => $code, 'ip' => $ip, 'createtime' => $time]);
+        $result = Hook::listen('ems_send', $ems, null, true);
+        if (!$result)
+        {
+            $ems->delete();
+            return FALSE;
+        }
+        return TRUE;
+    }
+
+    /**
+     * 发送通知
+     * 
+     * @param   mixed     $email   邮箱,多个以,分隔
+     * @param   string    $msg      消息内容
+     * @param   string    $template 消息模板
+     * @return  boolean
+     */
+    public static function notice($email, $msg = '', $template = NULL)
+    {
+        $params = [
+            'email'    => $email,
+            'msg'      => $msg,
+            'template' => $template
+        ];
+        $result = Hook::listen('ems_notice', $params, null, true);
+        return $result ? TRUE : FALSE;
+    }
+
+    /**
+     * 校验验证码
+     *
+     * @param   int       $email     邮箱
+     * @param   int       $code       验证码
+     * @param   string    $event      事件
+     * @return  boolean
+     */
+    public static function check($email, $code, $event = 'default')
+    {
+        $time = time() - self::$expire;
+        $ems = \app\common\model\Ems::where(['email' => $email, 'event' => $event])
+                ->order('id', 'DESC')
+                ->find();
+        if ($ems)
+        {
+            if ($ems['createtime'] > $time && $ems['times'] <= self::$maxCheckNums)
+            {
+                $correct = $code == $ems['code'];
+                if (!$correct)
+                {
+                    $ems->times = $ems->times + 1;
+                    $ems->save();
+                    return FALSE;
+                }
+                else
+                {
+                    $result = Hook::listen('ems_check', $ems, null, true);
+                    return TRUE;
+                }
+            }
+            else
+            {
+                // 过期则清空该邮箱验证码
+                self::flush($email, $event);
+                return FALSE;
+            }
+        }
+        else
+        {
+            return FALSE;
+        }
+    }
+
+    /**
+     * 清空指定邮箱验证码
+     *
+     * @param   int       $email     邮箱
+     * @param   string    $event      事件
+     * @return  boolean
+     */
+    public static function flush($email, $event = 'default')
+    {
+        \app\common\model\Ems::
+                where(['email' => $email, 'event' => $event])
+                ->delete();
+        Hook::listen('ems_flush');
+        return TRUE;
+    }
+
+}

+ 3 - 2
application/common/library/Sms.php

@@ -5,7 +5,7 @@ namespace app\common\library;
 use think\Hook;
 
 /**
- * 验证码类
+ * 短信验证码类
  */
 class Sms
 {
@@ -51,7 +51,8 @@ class Sms
     {
         $code = is_null($code) ? mt_rand(1000, 9999) : $code;
         $time = time();
-        $sms = \app\common\model\Sms::create(['event' => $event, 'mobile' => $mobile, 'code' => $code, 'createtime' => $time]);
+        $ip = request()->ip();
+        $sms = \app\common\model\Sms::create(['event' => $event, 'mobile' => $mobile, 'code' => $code, 'ip' => $ip, 'createtime' => $time]);
         $result = Hook::listen('sms_send', $sms, null, true);
         if (!$result)
         {

+ 22 - 0
application/common/model/Ems.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace app\common\model;
+
+use think\Model;
+
+/**
+ * 邮箱验证码
+ */
+class Ems Extends Model
+{
+
+    // 开启自动写入时间戳字段
+    protected $autoWriteTimestamp = 'int';
+    // 定义时间戳字段名
+    protected $createTime = 'createtime';
+    protected $updateTime = false;
+    // 追加属性
+    protected $append = [
+    ];
+
+}

+ 10 - 0
application/common/model/Token.php

@@ -21,6 +21,16 @@ class Token Extends Model
     protected $pk = 'token';
     // 追加属性
     protected $append = [
+        'expires_in'
     ];
+    
+    /**
+     * 获取Token剩余有效期
+     * @return int
+     */
+    public function getExpiresInAttr($value, $data)
+    {
+        return $data['expiretime'] ? max(0, $data['expiretime'] - time()) : 365 * 86400;
+    }
 
 }

+ 1 - 1
application/config.php

@@ -253,7 +253,7 @@ return [
         //自动检测更新
         'checkupdate'      => false,
         //版本号
-        'version'          => '1.0.0.20180308_beta',
+        'version'          => '1.0.0.20180310_beta',
         'api_url'          => 'http://api.fastadmin.net',
     ],
 ];

+ 5 - 37
application/index/controller/User.php

@@ -31,8 +31,9 @@ class User extends Frontend
 
         //监听注册登录注销的事件
         Hook::add('user_login_successed', function($user) use($auth) {
-            Cookie::set('uid', $user->id);
-            Cookie::set('token', $auth->getToken());
+            $expire = input('post.keeplogin') ? 30 * 86400 : 0;
+            Cookie::set('uid', $user->id, $expire);
+            Cookie::set('token', $auth->getToken(), $expire);
         });
         Hook::add('user_register_successed', function($user) use($auth) {
             Cookie::set('uid', $user->id);
@@ -139,7 +140,7 @@ class User extends Frontend
         {
             $account = $this->request->post('account');
             $password = $this->request->post('password');
-            $keeptime = (int) $this->request->post('keeptime');
+            $keeplogin = (int) $this->request->post('keeplogin');
             $token = $this->request->post('__token__');
             $rule = [
                 'account'   => 'require|length:3,50',
@@ -165,7 +166,7 @@ class User extends Frontend
                 $this->error(__($validate->getError()));
                 return FALSE;
             }
-            if ($this->auth->login($account, $password, $keeptime))
+            if ($this->auth->login($account, $password))
             {
                 $synchtml = '';
                 ////////////////同步到Ucenter////////////////
@@ -263,39 +264,6 @@ class User extends Frontend
     }
 
     /**
-     * 激活邮箱
-     */
-    public function activeemail()
-    {
-        $code = $this->request->request('code');
-        $code = base64_decode($code);
-        parse_str($code, $params);
-        if (!isset($params['id']) || !isset($params['time']) || !isset($params['key']))
-        {
-            $this->error(__('Invalid parameters'));
-        }
-        $user = \app\common\model\User::get($params['id']);
-        if (!$user)
-        {
-            $this->error(__('User not found'));
-        }
-        if ($user->verification->email)
-        {
-            $this->error(__('Email already activation'));
-        }
-        if ($key !== md5(md5($user->id . $user->email . $time) . $user->salt) || time() - $params['time'] > 1800)
-        {
-            $this->error(__('Secrity code already invalid'));
-        }
-        $verification = $user->verification;
-        $verification->email = 1;
-        $user->verification = $verification;
-        $user->save();
-        $this->success(__('Active email successful'), url('user/index'));
-        return;
-    }
-
-    /**
      * 修改密码
      */
     public function changepwd()

+ 2 - 1
application/index/lang/zh-cn/user.php

@@ -22,7 +22,8 @@ return [
     'Sign up successful'                    => '注册成功',
     'Email active successful'               => '邮箱激活成功',
     'Username can not be empty'             => '用户名不能为空',
-    'Username must be 6 to 30 characters'   => '用户名必须6-30个字符',
+    'Username must be 3 to 30 characters'   => '用户名必须3-30个字符',
+    'Account must be 3 to 50 characters'    => '账户必须3-50个字符',
     'Password can not be empty'             => '密码不能为空',
     'Password must be 6 to 30 characters'   => '密码必须6-30个字符',
     'Email is incorrect'                    => '邮箱格式不正确',

+ 3 - 2
application/index/view/layout/common.html

@@ -65,8 +65,9 @@
             <!-- /.container -->
         </nav>
 
-
-        {__CONTENT__}
+        <main class="content">
+            {__CONTENT__}
+        </main>
 
         <footer>
             <div class="container">

+ 4 - 3
application/index/view/layout/default.html

@@ -54,11 +54,12 @@
             </div>
         </nav>
 
-        {__CONTENT__}
+        <main class="content">
+            {__CONTENT__}
+        </main>
 
         <footer class="footer" style="clear:both">
-            
-            <p class="address">Copyright&nbsp;©&nbsp;2017-2018 fastadmin.net All Rights Reserved {$site.name} {:__('Copyrights')} <a href="http://www.miibeian.gov.cn" target="_blank">{$site.beian}</a></p>
+            <p class="copyright">Copyright&nbsp;©&nbsp;2017-2018 fastadmin.net All Rights Reserved {$site.name} {:__('Copyrights')} <a href="http://www.miibeian.gov.cn" target="_blank">{$site.beian}</a></p>
         </footer>
 
         {include file="common/script" /}

+ 50 - 36
application/index/view/user/login.html

@@ -6,19 +6,19 @@
                 <div class="form-group">
                     <label class="control-label" for="account">{:__('Account')}</label>
                     <div class="controls">
-                        <input class="form-control input-lg" id="account" type="text" name="account" value="" required="" placeholder="{:__('Email/Mobile/Username')}" autocomplete="off">
+                        <input class="form-control input-lg" id="account" type="text" name="account" value="" data-rule="required" placeholder="{:__('Email/Mobile/Username')}" autocomplete="off">
                         <div class="help-block"></div>
                     </div>
                 </div>
                 <div class="form-group">
                     <label class="control-label" for="password">{:__('Password')}</label>
                     <div class="controls">
-                        <input class="form-control input-lg" id="password" type="password" name="password" required="" placeholder="{:__('Password')}" autocomplete="off">
+                        <input class="form-control input-lg" id="password" type="password" name="password" data-rule="required;password" placeholder="{:__('Password')}" autocomplete="off">
                     </div>
                 </div>
                 <div class="form-group">
                     <div class="controls">
-                        <input type="checkbox" name="keeptime" checked="checked" value="86400"> {:__('Keep login')} 
+                        <input type="checkbox" name="keeplogin" checked="checked" value="1"> {:__('Keep login')} 
                         <div class="pull-right"><a href="javascript:;" class="btn-forgot">{:__('Forgot password')}</a></div>
                     </div>
                 </div>
@@ -30,43 +30,57 @@
     </div>
 </div>
 <script type="text/html" id="resetpwdtpl">
-    <div style="padding:30px;">
-        <div class="row">
-            <form id="resetpwd-form" class="form-horizontal" method="POST" action="{:url('api/user/resetpwd')}">
-                <input type="hidden" name="action" value="changemobile" />
-                <div class="form-group">
-                    <label for="c-mobile" class="control-label col-xs-12 col-sm-3">{:__('Mobile')}:</label>
-                    <div class="col-xs-12 col-sm-8">
-                        <input type="text" class="form-control" id="mobile" name="mobile" value="" data-rule="required;mobile;remote({:url('api/validate/check_mobile_exist')}, event=resetpwd, id={$user.id})" placeholder="">
-                        <span class="msg-box"></span>
-                    </div>
+    <form id="resetpwd-form" class="form-horizontal form-layer" method="POST" action="{:url('api/user/resetpwd')}">
+        <div class="form-body">
+            <input type="hidden" name="action" value="resetpwd" />
+            <div class="form-group">
+                <label for="" class="control-label col-xs-12 col-sm-3">{:__('Type')}:</label>
+                <div class="col-xs-12 col-sm-8">
+                    <div class="radio">
+                        <label for="type-email"><input id="type-email" checked="checked" name="type" data-send-url="{:url('api/ems/send')}" data-check-url="{:url('api/validate/check_ems_correct')}" type="radio" value="email"> 通过邮箱找回</label> 
+                        <label for="type-mobile"><input id="type-mobile" name="type" type="radio" data-send-url="{:url('api/sms/send')}" data-check-url="{:url('api/validate/check_sms_correct')}" value="mobile"> 通过手机找回</label>
+                    </div>        
                 </div>
-                <div class="form-group">
-                    <label for="c-captcha" class="control-label col-xs-12 col-sm-3">{:__('Captcha')}:</label>
-                    <div class="col-xs-12 col-sm-8">
-                        <div class="input-group">
-                            <input type="text" name="captcha" class="form-control" data-rule="required;length(4);integer[+];remote({:url('api/validate/check_sms_correct')}, event=resetpwd, mobile:#mobile)" />
-                            <span class="input-group-btn" style="padding:0;border:none;">
-                                <a href="javascript:;" class="btn btn-info btn-captcha" data-url="{:url('api/sms/send')}" data-event="resetpwd">{:__('Send verification code')}</a>
-                            </span>
-                        </div>
-                        <span class="msg-box"></span>
-                    </div>
+            </div>
+            <div class="form-group" data-type="email">
+                <label for="email" class="control-label col-xs-12 col-sm-3">{:__('Email')}:</label>
+                <div class="col-xs-12 col-sm-8">
+                    <input type="text" class="form-control" id="email" name="email" value="" data-rule="required(#type-email:checked);email;remote({:url('api/validate/check_email_exist')}, event=resetpwd, id={$user.id})" placeholder="">
+                    <span class="msg-box"></span>
                 </div>
-                <div class="form-group">
-                    <label for="c-newpassword" class="control-label col-xs-12 col-sm-3">{:__('New password')}:</label>
-                    <div class="col-xs-12 col-sm-8">
-                        <input type="password" class="form-control" id="newpassword" name="newpassword" value="" data-rule="required;password" placeholder="">
-                        <span class="msg-box"></span>
-                    </div>
+            </div>
+            <div class="form-group hide" data-type="mobile">
+                <label for="mobile" class="control-label col-xs-12 col-sm-3">{:__('Mobile')}:</label>
+                <div class="col-xs-12 col-sm-8">
+                    <input type="text" class="form-control" id="mobile" name="mobile" value="" data-rule="required(#type-mobile:checked);mobile;remote({:url('api/validate/check_mobile_exist')}, event=resetpwd, id={$user.id})" placeholder="">
+                    <span class="msg-box"></span>
                 </div>
-                <div class="form-group">
-                    <label class="control-label col-xs-12 col-sm-3"></label>
-                    <div class="col-xs-12 col-sm-8">
-                        <button type="submit" class="btn btn-md btn-info">{:__('Ok')}</button>
+            </div>
+            <div class="form-group">
+                <label for="captcha" class="control-label col-xs-12 col-sm-3">{:__('Captcha')}:</label>
+                <div class="col-xs-12 col-sm-8">
+                    <div class="input-group">
+                        <input type="text" name="captcha" class="form-control" data-rule="required;length(4);integer[+];remote({:url('api/validate/check_ems_correct')}, event=resetpwd, email:#email)" />
+                        <span class="input-group-btn" style="padding:0;border:none;">
+                            <a href="javascript:;" class="btn btn-info btn-captcha" data-url="{:url('api/ems/send')}" data-type="email" data-event="resetpwd">{:__('Send verification code')}</a>
+                        </span>
                     </div>
+                    <span class="msg-box"></span>
                 </div>
-            </form>
+            </div>
+            <div class="form-group">
+                <label for="newpassword" class="control-label col-xs-12 col-sm-3">{:__('New password')}:</label>
+                <div class="col-xs-12 col-sm-8">
+                    <input type="password" class="form-control" id="newpassword" name="newpassword" value="" data-rule="required;password" placeholder="">
+                    <span class="msg-box"></span>
+                </div>
+            </div>
         </div>
-    </div>
+        <div class="form-group form-footer">
+            <label class="control-label col-xs-12 col-sm-3"></label>
+            <div class="col-xs-12 col-sm-8">
+                <button type="submit" class="btn btn-md btn-info">{:__('Ok')}</button>
+            </div>
+        </div>
+    </form>
 </script>

+ 82 - 44
application/index/view/user/profile.html

@@ -107,58 +107,96 @@
 </div>
 
 <script type="text/html" id="emailtpl">
-    <div style="padding:30px;">
-        <div class="row">
-            <form id="email-form" class="form-horizontal" method="POST" action="{:url('api/user/changeemail')}">
-                <input type="hidden" name="action" value="changeemail" />
-                <div class="form-group">
-                    <label for="c-email" class="control-label col-xs-12 col-sm-3">{:__('New email')}:</label>
-                    <div class="col-xs-12 col-sm-8">
-                        <input type="text" class="form-control" id="email" name="email" value="" data-rule="required;email;remote({:url('api/validate/check_email_available')}, type=changeemail, id={$user.id})" placeholder="{:__('New email')}">
-                        <span class="msg-box"></span>
-                    </div>
+    <form id="email-form" class="form-horizontal form-layer" method="POST" action="{:url('api/user/changeemail')}">
+        <div class="form-body">
+            <input type="hidden" name="action" value="changeemail" />
+            <div class="form-group">
+                <label for="c-email" class="control-label col-xs-12 col-sm-3">{:__('New Email')}:</label>
+                <div class="col-xs-12 col-sm-8">
+                    <input type="text" class="form-control" id="email" name="email" value="" data-rule="required;email;remote({:url('api/validate/check_email_available')}, event=changeemail, id={$user.id})" placeholder="{:__('New email')}">
+                    <span class="msg-box"></span>
                 </div>
-                <div class="form-group">
-                    <label class="control-label col-xs-12 col-sm-3"></label>
-                    <div class="col-xs-12 col-sm-8">
-                        <button type="submit" class="btn btn-md btn-info">{:__('Submit')}</button>
+            </div>
+            <div class="form-group">
+                <label for="c-captcha" class="control-label col-xs-12 col-sm-3">{:__('Captcha')}:</label>
+                <div class="col-xs-12 col-sm-8">
+                    <div class="input-group">
+                        <input type="text" name="captcha" id="email-captcha" class="form-control" data-rule="required;length(4);integer[+];remote({:url('api/validate/check_ems_correct')}, event=changeemail, email:#email)" />
+                        <span class="input-group-btn" style="padding:0;border:none;">
+                            <a href="javascript:;" class="btn btn-info btn-captcha" data-url="{:url('api/ems/send')}" data-type="email" data-event="changeemail">获取验证码</a>
+                        </span>
                     </div>
+                    <span class="msg-box"></span>
                 </div>
-            </form>
+            </div>
         </div>
-    </div>
+        <div class="form-footer">
+            <div class="form-group" style="margin-bottom:0;">
+                <label class="control-label col-xs-12 col-sm-3"></label>
+                <div class="col-xs-12 col-sm-8">
+                    <button type="submit" class="btn btn-md btn-info">{:__('Submit')}</button>
+                </div>
+            </div>
+        </div>
+    </form>
 </script>
 <script type="text/html" id="mobiletpl">
-    <div style="padding:30px;">
-        <div class="row">
-            <form id="mobile-form" class="form-horizontal" method="POST" action="{:url('api/user/changemobile')}">
-                <input type="hidden" name="action" value="changemobile" />
-                <div class="form-group">
-                    <label for="c-mobile" class="control-label col-xs-12 col-sm-3">{:__('New mobile')}:</label>
-                    <div class="col-xs-12 col-sm-8">
-                        <input type="text" class="form-control" id="mobile" name="mobile" value="" data-rule="required;mobile;remote({:url('api/validate/check_mobile_available')}, event=changemobile, id={$user.id})" placeholder="{:__('New mobile')}">
-                        <span class="msg-box"></span>
-                    </div>
+    <form id="mobile-form" class="form-horizontal form-layer" method="POST" action="{:url('api/user/changemobile')}">
+        <div class="form-body">
+            <input type="hidden" name="action" value="changemobile" />
+            <div class="form-group">
+                <label for="c-mobile" class="control-label col-xs-12 col-sm-3">{:__('New mobile')}:</label>
+                <div class="col-xs-12 col-sm-8">
+                    <input type="text" class="form-control" id="mobile" name="mobile" value="" data-rule="required;mobile;remote({:url('api/validate/check_mobile_available')}, event=changemobile, id={$user.id})" placeholder="{:__('New mobile')}">
+                    <span class="msg-box"></span>
                 </div>
-                <div class="form-group">
-                    <label for="c-captcha" class="control-label col-xs-12 col-sm-3">{:__('Captcha')}:</label>
-                    <div class="col-xs-12 col-sm-8">
-                        <div class="input-group">
-                            <input type="text" name="captcha" class="form-control" data-rule="required;length(4);integer[+];remote({:url('api/validate/check_sms_correct')}, event=changemobile, mobile:#mobile)" />
-                            <span class="input-group-btn" style="padding:0;border:none;">
-                                <a href="javascript:;" class="btn btn-info btn-captcha" data-url="{:url('api/sms/send')}" data-event="changemobile">获取验证码</a>
-                            </span>
-                        </div>
-                        <span class="msg-box"></span>
+            </div>
+            <div class="form-group">
+                <label for="c-captcha" class="control-label col-xs-12 col-sm-3">{:__('Captcha')}:</label>
+                <div class="col-xs-12 col-sm-8">
+                    <div class="input-group">
+                        <input type="text" name="captcha" id="mobile-captcha" class="form-control" data-rule="required;length(4);integer[+];remote({:url('api/validate/check_sms_correct')}, event=changemobile, mobile:#mobile)" />
+                        <span class="input-group-btn" style="padding:0;border:none;">
+                            <a href="javascript:;" class="btn btn-info btn-captcha" data-url="{:url('api/sms/send')}" data-type="mobile" data-event="changemobile">获取验证码</a>
+                        </span>
                     </div>
+                    <span class="msg-box"></span>
                 </div>
-                <div class="form-group">
-                    <label class="control-label col-xs-12 col-sm-3"></label>
-                    <div class="col-xs-12 col-sm-8">
-                        <button type="submit" class="btn btn-md btn-info">{:__('Submit')}</button>
-                    </div>
+            </div>
+        </div>
+        <div class="form-footer">
+            <div class="form-group" style="margin-bottom:0;">
+                <label class="control-label col-xs-12 col-sm-3"></label>
+                <div class="col-xs-12 col-sm-8">
+                    <button type="submit" class="btn btn-md btn-info">{:__('Submit')}</button>
                 </div>
-            </form>
+            </div>
         </div>
-    </div>
-</script>
+    </form>
+</script>
+<style>
+    .form-layer {height:100%;min-height:150px;min-width:300px;}
+    .form-body {
+        width:100%;
+        overflow:auto;
+        top:0;
+        position:absolute;
+        z-index:10;
+        bottom:50px;
+        padding:15px;
+    }
+    .form-layer .form-footer {
+        height:50px;
+        line-height:50px;
+        background-color: #ecf0f1;
+        width:100%;
+        position:absolute;
+        z-index:200;
+        bottom:0;
+        margin:0;
+    }
+    .form-footer .form-group{
+        margin-left:0;
+        margin-right:0;
+    }
+</style>

+ 5 - 5
application/index/view/user/register.html

@@ -7,28 +7,28 @@
                 <div class="form-group">
                     <label class="control-label required">{:__('Email')}<span class="text-success"></span></label>
                     <div class="controls">
-                        <input type="text" name="email" id="email" required class="form-control input-lg" placeholder="{:__('Email')}">
+                        <input type="text" name="email" id="email" data-rule="required;email" class="form-control input-lg" placeholder="{:__('Email')}">
                         <p class="help-block"></p>
                     </div>
                 </div>
                 <div class="form-group">
                     <label class="control-label">{:__('Username')}</label>
                     <div class="controls">
-                        <input type="text" id="username" name="username" required class="form-control input-lg" placeholder="{:__('Username must be 6 to 30 characters')}">
+                        <input type="text" id="username" name="username" data-rule="required;username" class="form-control input-lg" placeholder="{:__('Username must be 3 to 30 characters')}">
                         <p class="help-block"></p>
                     </div>
                 </div>
                 <div class="form-group">
                     <label class="control-label">{:__('Password')}</label>
                     <div class="controls">
-                        <input type="password" id="password" name="password" required class="form-control input-lg" placeholder="{:__('Password must be 6 to 30 characters')}">
+                        <input type="password" id="password" name="password" data-rule="required;password" class="form-control input-lg" placeholder="{:__('Password must be 6 to 30 characters')}">
                         <p class="help-block"></p>
                     </div>
                 </div>
                 <div class="form-group">
                     <label class="control-label">{:__('Mobile')}</label>
                     <div class="controls">
-                        <input type="text" id="mobile" name="mobile" required class="form-control input-lg" placeholder="{:__('Mobile')}">
+                        <input type="text" id="mobile" name="mobile" data-rule="required;mobile" class="form-control input-lg" placeholder="{:__('Mobile')}">
                         <p class="help-block"></p>
                     </div>
                 </div>
@@ -36,7 +36,7 @@
                     <label class="control-label">{:__('Captcha')}</label>
                     <div class="controls">
                         <div class="input-group input-group-lg">
-                            <input type="text" name="captcha" class="form-control" placeholder="{:__('Captcha')}" required style="border-radius: 0;" />
+                            <input type="text" name="captcha" class="form-control" placeholder="{:__('Captcha')}" data-rule="required;length(4);integer[+]" style="border-radius: 0;" />
                             <span class="input-group-addon" style="padding:0;border:none;">
                                 <img src="{:captcha_src()}" width="140" height="42" onclick="this.src = '{:captcha_src()}?r=' + Math.random();"/>
                             </span>

File diff suppressed because it is too large
+ 1067 - 391
public/api.html


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

@@ -417,6 +417,35 @@ form.form-horizontal .control-label {
   text-decoration: none;
   font-weight: bold;
 }
+/* 弹窗中的表单 */
+.form-layer {
+  height: 100%;
+  min-height: 150px;
+  min-width: 300px;
+}
+.form-layer .form-body {
+  width: 100%;
+  overflow: auto;
+  top: 0;
+  position: absolute;
+  z-index: 10;
+  bottom: 50px;
+  padding: 15px;
+}
+.form-layer .form-footer {
+  height: 50px;
+  line-height: 50px;
+  background-color: #ecf0f1;
+  width: 100%;
+  position: absolute;
+  z-index: 200;
+  bottom: 0;
+  margin: 0;
+}
+.form-layer .form-footer .form-group {
+  margin-left: 0;
+  margin-right: 0;
+}
 #treeview .jstree-container-ul .jstree-node {
   display: block;
   clear: both;

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


+ 104 - 171
public/assets/css/frontend.css

@@ -14,15 +14,6 @@ body {
   padding-top: 50px;
   font-size: 13px;
 }
-.wow {
-  visibility: hidden;
-}
-.img-portfolio {
-  margin-bottom: 30px;
-}
-.img-hover:hover {
-  opacity: 0.8;
-}
 .dropdown:hover .dropdown-menu {
   display: block;
   margin-top: 0;
@@ -66,12 +57,22 @@ body {
   height: 80px;
   object-fit: cover;
 }
+.layui-layer-content {
+  clear: both;
+}
+.layui-layer-fast-msg {
+  min-width: 100px;
+  border-radius: 2px;
+  background-color: rgba(0, 0, 0, 0.6);
+  color: #fff;
+}
+.layui-layer-fast-msg .layui-layer-content {
+  padding: 12px 25px;
+  text-align: center;
+}
 #header-navbar li.dropdown ul.dropdown-menu {
   min-width: 94px;
 }
-.panel-col {
-  min-height: 400px;
-}
 .panel-default {
   padding: 0 15px;
   border-color: #e4ecf3;
@@ -132,23 +133,6 @@ body {
     background: none;
   }
 }
-@media (max-width: 1920px) {
-  .panel-default > .panel-body .zuixin {
-    width: 100%;
-    border-bottom: 1px solid #f5f5f5;
-    padding-bottom: 5px;
-    margin-bottom: 10px;
-  }
-}
-@media (max-width: 992px) {
-  .panel-default > .panel-body .zuixin {
-    width: 50%;
-    padding-bottom: 5px;
-    margin-bottom: 10px;
-    float: left;
-    padding-right: 5px;
-  }
-}
 .panel-primary > .panel-heading {
   background-color: #46c37b;
   color: #fff;
@@ -226,84 +210,35 @@ body {
   line-height: 1.5;
   padding: 4px 13px;
 }
-.metas {
-  position: relative;
-  padding: 10px;
-  color: #c1c1c1;
+/* 弹窗中的表单 */
+.form-layer {
+  height: 100%;
+  min-height: 150px;
+  min-width: 300px;
 }
-.metas i {
-  margin-right: 5px;
+.form-layer .form-body {
+  width: 100%;
+  overflow: auto;
+  top: 0;
+  position: absolute;
+  z-index: 10;
+  bottom: 50px;
+  padding: 15px;
 }
-.metas .addon-price {
-  float: right;
+.form-layer .form-footer {
+  height: 50px;
+  line-height: 50px;
+  background-color: #ecf0f1;
+  width: 100%;
+  position: absolute;
+  z-index: 200;
+  bottom: 0;
+  margin: 0;
 }
-.metas .price {
-  color: #e83d2c;
-  font-size: 14px;
+.form-layer .form-footer .form-group {
+  margin-left: 0;
   margin-right: 0;
 }
-.metas .free {
-  color: #238312;
-}
-.metas .comment {
-  margin-left: 10px;
-}
-@media (max-width: 767px) {
-  .metas .metas {
-    padding: 5px;
-  }
-  .metas .comment {
-    display: none;
-  }
-}
-.text-line {
-  position: relative;
-  padding: 30px 0;
-  text-align: center;
-}
-.text-line.small {
-  padding: 10px 0;
-}
-.text-line.small h5 {
-  font-size: 14px;
-}
-.text-line.small h5 > span {
-  padding: 0 20px;
-}
-.text-line h5 {
-  position: relative;
-  margin-bottom: 20px;
-  font-size: 32px;
-  z-index: 1;
-  color: #313131;
-}
-.text-line h5 > i {
-  padding-left: 20px;
-  background: #fff;
-}
-.text-line h5 > span {
-  padding: 0 40px;
-}
-.text-line .subtitle {
-  font-size: 16px;
-  color: #919191;
-}
-@media (max-width: 767px) {
-  .text-line {
-    padding: 20px 0;
-  }
-  .text-line h5 {
-    font-size: 16px;
-  }
-  .text-line .subtitle {
-    font-size: 14px;
-  }
-}
-@media (max-width: 767px) {
-  header.carousel .carousel {
-    height: 70%;
-  }
-}
 footer.footer {
   width: 100%;
   color: #aaa;
@@ -311,87 +246,61 @@ footer.footer {
   margin-top: 25px;
   position: fixed;
   bottom: 0;
+  z-index: 99;
 }
-footer.footer ul {
-  margin: 60px 0 30px 0;
-  padding: 0;
-}
-footer.footer ul li.f-tit {
-  margin-bottom: 10px;
-  font-size: 14px;
-  color: #fff;
-}
-footer.footer ul li {
-  line-height: 26px;
-  white-space: nowrap;
-  list-style: none;
-  margin: 0;
-  padding: 0;
-}
-footer.footer ul li a {
-  color: #aaa;
-}
-footer.footer ul li a:hover {
-  color: #aaa;
-  text-decoration: underline !important;
-}
-footer.footer .address {
+footer.footer .copyright {
   line-height: 50px;
   text-align: center;
   background: #393939;
   margin: 0;
 }
-footer p.address a {
+footer.footer .copyright a {
   color: #aaa;
 }
-footer p.address a:hover {
+footer.footer .copyright a:hover {
   color: #fff;
 }
-@media (max-width: 767px) {
-  footer.footer {
-    overflow: hidden;
-  }
-  footer.footer .container {
-    margin: 0 20px;
-  }
-  footer.footer ul {
-    margin: 20px 0 10px 0;
-  }
-  footer.footer .address {
-    padding: 0 10px;
-  }
-}
 .rotate {
   -webkit-transition-duration: 0.8s;
   -moz-transition-duration: 0.8s;
   -o-transition-duration: 0.8s;
   transition-duration: 0.8s;
+  -webkit-transition-property: transform;
+  transition-property: transform;
   -webkit-transition-property: -webkit-transform;
   -moz-transition-property: -moz-transform;
   -o-transition-property: -o-transform;
-  transition-property: transform;
+  transition-property: -webkit-transform,-moz-transform,-o-transform,transform;
   overflow: hidden;
 }
 .rotate:hover {
   -webkit-transform: rotate(360deg);
   -moz-transform: rotate(360deg);
   -o-transform: rotate(360deg);
+  -ms-transform: rotate(360deg);
+  transform: rotate(360deg);
 }
 .user-section {
   background: #fff;
   padding: 15px;
   margin-bottom: 20px;
   -webkit-border-radius: 4px;
+  -webkit-background-clip: padding-box;
   -moz-border-radius: 4px;
+  -moz-background-clip: padding;
   border-radius: 4px;
+  background-clip: padding-box;
   border: 1px solid #e4ecf3;
 }
 .login-section {
   margin: 50px auto;
   width: 460px;
   -webkit-border-radius: 0;
+  -webkit-background-clip: padding-box;
   -moz-border-radius: 0;
+  -moz-background-clip: padding;
   border-radius: 0;
+  background-clip: padding-box;
 }
 .login-section.login-section-weixin {
   min-height: 315px;
@@ -433,42 +342,43 @@ footer p.address a:hover {
 .login-section .control-label {
   font-size: 13px;
 }
-@media (max-width: 767px) {
-  .login-section {
-    width: 100%;
-    margin: 20px auto;
-  }
-  .login-section .login-main {
-    padding: 20px 0 0 0;
-  }
+.login-section .n-bootstrap .form-group {
+  position: relative;
 }
-@media (min-width: 768px) {
-  .login-modal {
-    width: 350px;
-  }
-  .login-modal .modal-body {
-    padding: 30px 30px 15px 30px;
-  }
-  .login-modal .modal-footer {
-    padding: 30px;
-  }
+.login-section .n-bootstrap .input-group {
+  position: inherit;
 }
-#content-container {
-  margin: 30px auto;
-  min-height: 400px;
+.login-section .n-bootstrap .n-right {
+  margin-top: 0;
+  top: 0;
+  position: absolute;
+  left: 0;
+  text-align: right;
+  width: 100%;
 }
-@media (max-width: 767px) {
-  #content-container {
-    min-height: 250px;
-  }
+.login-section .n-bootstrap .n-right .msg-wrap {
+  position: relative;
+}
+main.content {
+  width: 100%;
+  overflow: auto;
+  top: 0;
+  position: absolute;
+  z-index: 10;
+  bottom: 50px;
+  padding: 15px;
+  padding-top: 67px;
 }
 .sidenav {
   padding: 20px 0 10px 0;
   margin-bottom: 20px;
   background-color: #fff;
   -webkit-border-radius: 4px;
+  -webkit-background-clip: padding-box;
   -moz-border-radius: 4px;
+  -moz-background-clip: padding;
   border-radius: 4px;
+  background-clip: padding-box;
   border: 1px solid #e4ecf3;
 }
 .sidenav .list-group:last-child {
@@ -483,8 +393,11 @@ footer p.address a:hover {
 }
 .sidenav .list-group .list-group-item {
   -webkit-border-radius: 0;
+  -webkit-background-clip: padding-box;
   -moz-border-radius: 0;
+  -moz-background-clip: padding;
   border-radius: 0;
+  background-clip: padding-box;
   border: none;
   padding: 0;
   border-left: 2px solid transparent;
@@ -492,8 +405,11 @@ footer p.address a:hover {
 .sidenav .list-group .list-group-item:last-child,
 .sidenav .list-group .list-group-item:first-child {
   -webkit-border-radius: 0;
+  -webkit-background-clip: padding-box;
   -moz-border-radius: 0;
+  -moz-background-clip: padding;
   border-radius: 0;
+  background-clip: padding-box;
 }
 .sidenav .list-group .list-group-item:hover {
   background-color: #f5f5f5;
@@ -510,9 +426,6 @@ footer p.address a:hover {
 .sidenav .list-group .list-group-item.active > a {
   color: #46c37b;
 }
-.flarum-section ul li a {
-  font-size: 13px;
-}
 .nav li .avatar-text,
 .nav li .avatar-img {
   height: 30px;
@@ -551,4 +464,24 @@ footer p.address a:hover {
   width: 48px;
   height: 48px;
 }
+@media (max-width: 767px) {
+  main.content {
+    position: inherit;
+    padding: 15px 0;
+  }
+  .login-section {
+    width: 100%;
+    margin: 20px auto;
+  }
+  .login-section .login-main {
+    padding: 20px 0 0 0;
+  }
+  footer.footer {
+    position: inherit;
+  }
+  footer.footer .copyright {
+    padding: 10px;
+    line-height: 30px;
+  }
+}
 /*# sourceMappingURL=frontend.css.map */

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


+ 1 - 1
public/assets/js/backend/index.js

@@ -185,7 +185,7 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], functi
                     data: {action: 'refreshmenu'}
                 }, function (data) {
                     $(".sidebar-menu li:not([data-rel='external'])").remove();
-                    $(data.menulist).insertBefore($(".sidebar-menu li:first"));
+                    $(".sidebar-menu").prepend(data.menulist);
                     $("#nav ul li[role='presentation'].active a").trigger('click');
                     return false;
                 }, function () {

+ 1 - 1
public/assets/js/fast.js

@@ -42,7 +42,7 @@ define(['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, undefine
                         return;
                     }
                 }
-                Toastr.error(ret.msg + "(code:" + ret.code + ")");
+                Toastr.error(ret.msg);
             },
             //服务器响应数据后
             onAjaxResponse: function (response) {

+ 27 - 15
public/assets/js/frontend.js

@@ -2,36 +2,48 @@ define(['fast'], function (Fast) {
     var Frontend = {
         api: Fast.api,
         init: function () {
-            //发送手机验证码
+            var si = {};
+            //发送验证码
             $(document).on("click", ".btn-captcha", function (e) {
-                var mobile = $(this).closest("form").find("#mobile");
-                if (mobile.val() == "") {
-                    Layer.alert("手机号码不能为空!");
+                var type = $(this).data("type") ? $(this).data("type") : 'mobile';
+                var element = $(this).data("input-id") ? $("#" + $(this).data("input-id")) : $("input[name='" + type + "']", $(this).closest("form"));
+                var text = type === 'email' ? '邮箱' : '手机号码';
+                if (element.val() === "") {
+                    Layer.msg(text + "不能为空!");
+                    element.focus();
                     return false;
-                } else if (!mobile.val().match(/^1[3-9]\d{9}$/)) {
-                    Layer.alert("请输入正确的手机号码!");
+                } else if (type === 'mobile' && !element.val().match(/^1[3-9]\d{9}$/)) {
+                    Layer.msg("请输入正确的" + text + "!");
+                    element.focus();
+                    return false;
+                } else if (type === 'email' && !element.val().match(/^[\w\+\-]+(\.[\w\+\-]+)*@[a-z\d\-]+(\.[a-z\d\-]+)*\.([a-z]{2,4})$/)) {
+                    Layer.msg("请输入正确的" + text + "!");
+                    element.focus();
                     return false;
                 }
                 var that = this;
-                mobile.isValid(function (v) {
+                element.isValid(function (v) {
                     if (v) {
-                        $(that).addClass("disabled", true).text("获取中...");
-                        var si;
-                        Frontend.api.ajax({url: $(that).data("url"), data: {event: $(that).data("event"), mobile: mobile.val()}}, function () {
-                            clearInterval(si);
+                        $(that).addClass("disabled", true).text("发送中...");
+                        var data = {event: $(that).data("event")};
+                        data[type] = element.val();
+                        Frontend.api.ajax({url: $(that).data("url"), data: data}, function () {
+                            clearInterval(si[type]);
                             var seconds = 60;
-                            si = setInterval(function () {
+                            si[type] = setInterval(function () {
                                 seconds--;
                                 if (seconds <= 0) {
                                     clearInterval(si);
-                                    $(that).removeClass("disabled", false).text("获取验证码");
+                                    $(that).removeClass("disabled").text("发送验证码");
                                 } else {
-                                    $(that).addClass("disabled", true).text(seconds + "秒后可再次发送");
+                                    $(that).addClass("disabled").text(seconds + "秒后可再次发送");
                                 }
                             }, 1000);
+                        }, function () {
+                            $(that).removeClass("disabled").text('发送验证码');
                         });
                     } else {
-                        Layer.alert("请确认已经输入了正解的手机号!");
+                        Layer.msg("请确认已经输入了正确的" + text + "!");
                     }
                 });
 

+ 30 - 34
public/assets/js/frontend/user.js

@@ -1,13 +1,24 @@
 define(['jquery', 'bootstrap', 'frontend', 'form', 'template'], function ($, undefined, Frontend, Form, Template) {
+    var validatoroptions = {
+        invalid: function (form, errors) {
+            $.each(errors, function (i, j) {
+                Layer.msg(j);
+            });
+        }
+    };
     var Controller = {
         login: function () {
             //本地验证未通过时提示
-            $("#login-form").data("validator-options", {
-                invalid: function (form, errors) {
-                    $.each(errors, function (i, j) {
-                        Layer.alert(j);
-                    });
-                },
+            $("#login-form").data("validator-options", validatoroptions);
+
+            $(document).on("change", "input[name=type]", function () {
+                var type = $(this).val();
+                $("div.form-group[data-type]").addClass("hide");
+                $("div.form-group[data-type='" + type + "']").removeClass("hide");
+                $('#resetpwd-form').validator("setField", {
+                    captcha: "required;length(4);integer[+];remote(" + $(this).data("check-url") + ", event=resetpwd, " + type + ":#" + type + ")",
+                });
+                $(".btn-captcha").data("url", $(this).data("send-url")).data("type", type);
             });
 
             //为表单绑定事件
@@ -27,7 +38,7 @@ define(['jquery', 'bootstrap', 'frontend', 'form', 'template'], function ($, und
                 Layer.open({
                     type: 1,
                     title: "修改",
-                    area: ["450px", "auto"],
+                    area: ["450px", "355px"],
                     content: content,
                     success: function (layero) {
                         Form.api.bindevent($("#resetpwd-form", layero), function (data) {
@@ -39,13 +50,7 @@ define(['jquery', 'bootstrap', 'frontend', 'form', 'template'], function ($, und
         },
         register: function () {
             //本地验证未通过时提示
-            $("#register-form").data("validator-options", {
-                invalid: function (form, errors) {
-                    $.each(errors, function (i, j) {
-                        Layer.alert(j);
-                    });
-                },
-            });
+            $("#register-form").data("validator-options", validatoroptions);
 
             //为表单绑定事件
             Form.api.bindevent($("#register-form"), function (data, ret) {
@@ -56,13 +61,7 @@ define(['jquery', 'bootstrap', 'frontend', 'form', 'template'], function ($, und
         },
         changepwd: function () {
             //本地验证未通过时提示
-            $("#resetpwd-form").data("validator-options", {
-                invalid: function (form, errors) {
-                    $.each(errors, function (i, j) {
-                        Layer.alert(j);
-                    });
-                },
-            });
+            $("#changepwd-form").data("validator-options", validatoroptions);
 
             //为表单绑定事件
             Form.api.bindevent($("#changepwd-form"), function (data, ret) {
@@ -78,26 +77,23 @@ define(['jquery', 'bootstrap', 'frontend', 'form', 'template'], function ($, und
                 $(".profile-user-img").prop("src", url);
                 Toastr.success("上传成功!");
             });
-
-            //为表单绑定事件
-            Form.api.bindevent($("#profile-form"), function (data) {
-            });
-            Form.api.bindevent($("#email-form"), function (data) {
-                Layer.closeAll();
-                $("#basic-form #email").val($("#email").val());
-            });
-            Form.api.bindevent($("#mobile-form"), function (data) {
-                Layer.closeAll();
-                $("#basic-form #mobile").val($("#mobile").val());
-            });
+            Form.api.bindevent($("#profile-form"));
             $(document).on("click", ".btn-change", function () {
+                var that = this;
                 var id = $(this).data("type") + "tpl";
                 var content = Template(id, {});
                 Layer.open({
                     type: 1,
                     title: "修改",
-                    area: ["450px", "auto"],
+                    area: ["400px", "250px"],
                     content: content,
+                    success: function (layero) {
+                        var form = $("form", layero);
+                        Form.api.bindevent(form, function (data) {
+                            Layer.closeAll();
+                            console.log(123);
+                        });
+                    }
                 });
             });
         }

+ 1 - 1
public/assets/js/require-backend.min.js

@@ -696,7 +696,7 @@ define('fast',['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, u
                         return;
                     }
                 }
-                Toastr.error(ret.msg + "(code:" + ret.code + ")");
+                Toastr.error(ret.msg);
             },
             //服务器响应数据后
             onAjaxResponse: function (response) {

+ 28 - 16
public/assets/js/require-frontend.min.js

@@ -696,7 +696,7 @@ define('fast',['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, u
                         return;
                     }
                 }
-                Toastr.error(ret.msg + "(code:" + ret.code + ")");
+                Toastr.error(ret.msg);
             },
             //服务器响应数据后
             onAjaxResponse: function (response) {
@@ -977,36 +977,48 @@ define('frontend',['fast'], function (Fast) {
     var Frontend = {
         api: Fast.api,
         init: function () {
-            //发送手机验证码
+            var si = {};
+            //发送验证码
             $(document).on("click", ".btn-captcha", function (e) {
-                var mobile = $(this).closest("form").find("#mobile");
-                if (mobile.val() == "") {
-                    Layer.alert("手机号码不能为空!");
+                var type = $(this).data("type") ? $(this).data("type") : 'mobile';
+                var element = $(this).data("input-id") ? $("#" + $(this).data("input-id")) : $("input[name='" + type + "']", $(this).closest("form"));
+                var text = type === 'email' ? '邮箱' : '手机号码';
+                if (element.val() === "") {
+                    Layer.msg(text + "不能为空!");
+                    element.focus();
                     return false;
-                } else if (!mobile.val().match(/^1[3-9]\d{9}$/)) {
-                    Layer.alert("请输入正确的手机号码!");
+                } else if (type === 'mobile' && !element.val().match(/^1[3-9]\d{9}$/)) {
+                    Layer.msg("请输入正确的" + text + "!");
+                    element.focus();
+                    return false;
+                } else if (type === 'email' && !element.val().match(/^[\w\+\-]+(\.[\w\+\-]+)*@[a-z\d\-]+(\.[a-z\d\-]+)*\.([a-z]{2,4})$/)) {
+                    Layer.msg("请输入正确的" + text + "!");
+                    element.focus();
                     return false;
                 }
                 var that = this;
-                mobile.isValid(function (v) {
+                element.isValid(function (v) {
                     if (v) {
-                        $(that).addClass("disabled", true).text("获取中...");
-                        var si;
-                        Frontend.api.ajax({url: $(that).data("url"), data: {event: $(that).data("event"), mobile: mobile.val()}}, function () {
-                            clearInterval(si);
+                        $(that).addClass("disabled", true).text("发送中...");
+                        var data = {event: $(that).data("event")};
+                        data[type] = element.val();
+                        Frontend.api.ajax({url: $(that).data("url"), data: data}, function () {
+                            clearInterval(si[type]);
                             var seconds = 60;
-                            si = setInterval(function () {
+                            si[type] = setInterval(function () {
                                 seconds--;
                                 if (seconds <= 0) {
                                     clearInterval(si);
-                                    $(that).removeClass("disabled", false).text("获取验证码");
+                                    $(that).removeClass("disabled").text("发送验证码");
                                 } else {
-                                    $(that).addClass("disabled", true).text(seconds + "秒后可再次发送");
+                                    $(that).addClass("disabled").text(seconds + "秒后可再次发送");
                                 }
                             }, 1000);
+                        }, function () {
+                            $(that).removeClass("disabled").text('发送验证码');
                         });
                     } else {
-                        Layer.alert("请确认已经输入了正解的手机号!");
+                        Layer.msg("请确认已经输入了正确的" + text + "!");
                     }
                 });
 

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

@@ -452,6 +452,34 @@ form.form-horizontal .control-label {
     ins{width:110px;display: inline-block;text-decoration:none;font-weight: bold;}
 }
 
+/* 弹窗中的表单 */
+.form-layer {
+    height:100%;min-height:150px;min-width:300px;
+    .form-body {
+        width:100%;
+        overflow:auto;
+        top:0;
+        position:absolute;
+        z-index:10;
+        bottom:50px;
+        padding:15px;
+    }
+    .form-footer {
+        height:50px;
+        line-height:50px;
+        background-color: #ecf0f1;
+        width:100%;
+        position:absolute;
+        z-index:200;
+        bottom:0;
+        margin:0;
+    }
+    .form-footer .form-group{
+        margin-left:0;
+        margin-right:0;
+    }
+}
+
 #treeview {
     .jstree-container-ul .jstree-node{
         display:block;clear:both;

+ 107 - 187
public/assets/less/frontend.less

@@ -32,16 +32,6 @@ body {
     font-size:13px;
 }
 
-.wow { visibility: hidden; }
-
-.img-portfolio {
-    margin-bottom: 30px;
-}
-
-.img-hover:hover {
-    opacity: 0.8;
-}
-
 .dropdown:hover .dropdown-menu {
     display: block;
     margin-top: 0;
@@ -66,7 +56,6 @@ body {
     display:inherit;
 }
 
-
 /*预览区域*/
 .plupload-preview {
     padding:10px;
@@ -89,13 +78,24 @@ body {
     }
 }
 
+.layui-layer-content {
+    clear: both;
+}
+.layui-layer-fast-msg {
+    min-width: 100px;
+    border-radius: 2px;
+    background-color: rgba(0,0,0,.6);
+    color: #fff;
+    .layui-layer-content {
+        padding: 12px 25px;
+        text-align: center;
+    }
+}
+
 #header-navbar li.dropdown ul.dropdown-menu {
     min-width:94px;
 }
 
-.panel-col {
-    min-height: 400px;
-}
 .panel-default {
     padding: 0 15px;
     border-color: #e4ecf3;
@@ -154,17 +154,6 @@ body {
     }
 }
 
-@media (max-width: 1920px) {
-    .panel-default > .panel-body .zuixin{ 
-        width:100%;border-bottom: 1px solid #f5f5f5; padding-bottom:5px; margin-bottom:10px;
-    }
-}
-@media (max-width: 992px) {
-    .panel-default > .panel-body .zuixin{ 
-        width:50%; padding-bottom:5px; margin-bottom:10px; float:left; padding-right:5px;
-    }
-}
-
 .panel-primary {
     > .panel-heading {
         background-color: #46c37b;
@@ -203,6 +192,7 @@ body {
         }
     }
 }
+
 @media (max-width: 767px) {
     .panel-page {
         padding: 15px;
@@ -235,143 +225,65 @@ body {
     padding: 4px 13px;
 }
 
-.metas {
-    position: relative;
-    padding: 10px;
-    color: #c1c1c1;
-
-    i {
-        margin-right: 5px;
-    }
-    .addon-price {
-        float: right;
-    }
-    .price {
-        color: #e83d2c;
-        font-size: 14px;
-        margin-right: 0;
-    }
-    .free {
-        color: #238312;
-    }
-    .comment {
-        margin-left: 10px;
-    }
-}
-@media (max-width: 767px) {
-    .metas {
-        .metas {
-            padding: 5px;
-        }
-        .comment {
-            display: none;
-        }
-    }
-}
-
-.text-line {
-    position: relative;
-    padding: 30px 0;
-    text-align: center;
-    &.small {
-        padding: 10px 0;
-        h5 {
-            font-size: 14px;
-            > span {
-                padding: 0 20px;
-            }
-        }
-    }
-    h5 {
-        position: relative;
-        margin-bottom: 20px;
-        font-size: 32px;
-        z-index: 1;
-        color: #313131;
-        > i {
-            padding-left: 20px;
-            background: #fff;
-        }
-        > span {
-            padding: 0 40px;
-        }
-    }
-
-    .subtitle {
-        font-size: 16px;
-        color: #919191;
-    }
-}
-@media (max-width: 767px) {
-    .text-line {
-        padding: 20px 0;
-        h5 {
-            font-size: 16px;
-        }
-        .subtitle {
-            font-size: 14px;
+/* 弹窗中的表单 */
+.form-layer {
+    height:100%;min-height:150px;min-width:300px;
+    .form-body {
+        width:100%;
+        overflow:auto;
+        top:0;
+        position:absolute;
+        z-index:10;
+        bottom:50px;
+        padding:15px;
+    }
+    .form-footer {
+        height:50px;
+        line-height:50px;
+        background-color: #ecf0f1;
+        width:100%;
+        position:absolute;
+        z-index:200;
+        bottom:0;
+        margin:0;
+    }
+    .form-footer .form-group{
+        margin-left:0;
+        margin-right:0;
+    }
+}
+
+footer.footer{
+    width:100%;color: #aaa;background: #555;margin-top:25px;position: fixed;bottom: 0;z-index:99;
+    .copyright{
+        line-height: 50px;text-align: center;background: #393939;margin:0;
+        a{
+            color: #aaa;
+            &:hover{color: #fff;}
         }
     }
 }
 
-@media(max-width:767px) {
-    header.carousel .carousel {
-        height: 70%;
-    }
-}
-
-footer.footer{width:100%;color: #aaa;background: #555;margin-top:25px;position: fixed;bottom: 0;}
-footer.footer ul{margin:60px 0 30px 0;padding:0;}
-footer.footer ul li.f-tit{margin-bottom:10px;font-size: 14px;color: #fff;}
-footer.footer ul li{line-height: 26px;white-space: nowrap;list-style: none;margin:0;padding:0;}
-footer.footer ul li a{color: #aaa;}
-footer.footer ul li a:hover{color: #aaa;text-decoration: underline !important;}
-footer.footer .address{line-height: 50px;text-align: center;background: #393939;margin:0;}
-footer p.address a{color: #aaa;}
-footer p.address a:hover{color: #fff;}
-@media(max-width:767px) {
-    footer.footer {overflow: hidden;}
-    footer.footer .container{margin:0 20px;}
-    footer.footer ul {margin:20px 0 10px 0;}
-    footer.footer .address{padding:0 10px;}
-}
-
 .rotate{
-    -webkit-transition-duration: 0.8s;
-    -moz-transition-duration: 0.8s;
-    -o-transition-duration: 0.8s;
-    transition-duration: 0.8s;
-
-    -webkit-transition-property: -webkit-transform;
-    -moz-transition-property: -moz-transform;
-    -o-transition-property: -o-transform;
-    transition-property: transform;
-
+    .transition-duration(0.8s);
+    .transition-property(transform);
     overflow:hidden;
-    &:hover
-        {
-        -webkit-transform:rotate(360deg);
-        -moz-transform:rotate(360deg);
-        -o-transform:rotate(360deg);
+    &:hover{
+        .transform(rotate(360deg));
     }
-
 }
 
 .user-section {
     background: #fff;
     padding: 15px;
     margin-bottom: 20px;
-    -webkit-border-radius: 4px;
-    -moz-border-radius: 4px;
-    border-radius: 4px;
+    .border-radius(4px);
     border: 1px solid #e4ecf3;
 }
 .login-section {
     margin: 50px auto;
     width: 460px;
-    -webkit-border-radius: 0;
-    -moz-border-radius: 0;
-    border-radius: 0;
+    .border-radius(0);
     &.login-section-weixin {
         min-height: 315px;
     }
@@ -403,47 +315,42 @@ footer p.address a:hover{color: #fff;}
     .control-label {
         font-size:13px;
     }
-}
-@media (max-width: 767px) {
-    .login-section {
-        width: 100%;
-        margin: 20px auto;
-        .login-main {
-            padding: 20px 0 0 0;
+    .n-bootstrap {
+        .form-group {
+            position:relative;
         }
-    }
-}
-@media (min-width: 768px) {
-    .login-modal {
-        width: 350px;
-        .modal-body {
-            padding: 30px 30px 15px 30px;
+        .input-group {
+            position: inherit;
         }
-        .modal-footer {
-            padding: 30px;
+        .n-right {
+            margin-top:0;
+            top:0;
+            position:absolute;
+            left:0;
+            text-align:right;
+            width:100%;
+            .msg-wrap {
+                position:relative;
+            }
         }
     }
 }
-
-
-#content-container {
-    margin: 30px auto;
-    min-height: 400px;
-}
-@media (max-width: 767px) {
-    #content-container {
-        min-height: 250px;
-    }
+main.content {
+    width:100%;
+    overflow:auto;
+    top:0;
+    position:absolute;
+    z-index:10;
+    bottom:50px;
+    padding:15px;
+    padding-top:67px;
 }
 
-
 .sidenav {
     padding: 20px 0 10px 0;
     margin-bottom: 20px;
     background-color: #fff;
-    -webkit-border-radius: 4px;
-    -moz-border-radius: 4px;
-    border-radius: 4px;
+    .border-radius(4px);
     border: 1px solid #e4ecf3;
     .list-group{
         &:last-child {
@@ -457,16 +364,12 @@ footer p.address a:hover{color: #fff;}
             font-size:14px;
         }
         .list-group-item {
-            -webkit-border-radius: 0;
-            -moz-border-radius: 0;
-            border-radius: 0;
+            .border-radius(0);
             border: none;
             padding: 0;
             border-left: 2px solid transparent;
             &:last-child,&:first-child {
-                -webkit-border-radius: 0;
-                -moz-border-radius: 0;
-                border-radius: 0;
+                .border-radius(0);
             }
             &:hover {
                 background-color: #f5f5f5;
@@ -487,9 +390,6 @@ footer p.address a:hover{color: #fff;}
     }
 }
 
-
-.flarum-section ul li a{font-size:13px;}
-
 .nav li{
     .avatar-text,.avatar-img {
         height:30px;
@@ -521,8 +421,28 @@ footer p.address a:hover{color: #fff;}
 }
 .avatar-img {
     font-size:0;
+    img{
+        border-radius:48px;
+        width:48px;height:48px;
+    }
+}
+
+@media (max-width: 767px) {
+    main.content {
+        position:inherit;
+        padding:15px 0;
+    }
+
+    .login-section {
+        width: 100%;
+        margin: 20px auto;
+        .login-main {
+            padding: 20px 0 0 0;
+        }
+    }
+
+    footer.footer {
+        position:inherit;
+        .copyright{padding:10px;line-height:30px;}
+    }
 }
-.avatar-img img{
-    border-radius:48px;
-    width:48px;height:48px;
-}