Bladeren bron

no message

kuaifan 5 jaren geleden
bovenliggende
commit
45924eeaac

+ 165 - 0
app/Http/Controllers/Api/UsersController.php

@@ -0,0 +1,165 @@
+<?php
+
+namespace App\Http\Controllers\Api;
+
+use App\Http\Controllers\Controller;
+use App\Module\Base;
+use App\Module\Users;
+use DB;
+use Request;
+use Session;
+
+/**
+ * @apiDefine users
+ *
+ * 会员
+ */
+class UsersController extends Controller
+{
+    public function __invoke($method, $action = '')
+    {
+        $app = $method ? $method : 'main';
+        if ($action) {
+            $app .= "__" . $action;
+        }
+        return (method_exists($this, $app)) ? $this->$app() : Base::ajaxError("404 not found (" . str_replace("__", "/", $app) . ").");
+    }
+
+    /**
+     * 登陆
+     * @return array
+     */
+    public function login()
+    {
+        $user = Base::DBC2A(DB::table('users')->where('username', trim(Request::input('username')))->first());
+        if (empty($user)) {
+            return Base::retError('账号或密码错误。');
+        }
+        if ($user['userpass'] != Base::md52(Request::input('userpass'))) {
+            return Base::retError('账号或密码错误!');
+        }
+        //
+        $array = [
+            'token' => Users::token($user),
+            'loginnum' => $user['loginnum'] + 1,
+            'lastip' => Base::getIp(),
+            'lastdate' => Base::time(),
+            'lineip' => Base::getIp(),
+            'linedate' => Base::time(),
+        ];
+        Base::array_over($user, $array);
+        DB::table('users')->where('id', $user['id'])->update($array);
+        //
+        if (intval(Request::input('onlydata')) !== 1) {
+            Session::put('sessionToken', $array['token']);
+        }
+        return Base::retSuccess("登陆成功", Users::retInfo($user));
+    }
+
+    /**
+     * 获取会员信息
+     * @return array|mixed
+     */
+    public function info()
+    {
+        $callback = Request::input('callback');
+        //
+        $user = Users::authE();
+        if (Base::isError($user)) {
+            if (strlen($callback) > 3) {
+                return $callback . '(' . json_encode($user) . ')';
+            }
+            return $user;
+        } else {
+            $user = $user['data'];
+        }
+        //
+        if (strlen($callback) > 3) {
+            return $callback . '(' . json_encode(Base::retSuccess('success', Users::retInfo($user))) . ')';
+        }
+        return Base::retSuccess('success', Users::retInfo($user));
+    }
+
+    /**
+     * 修改资料
+     * @return array|mixed
+     */
+    public function editdata()
+    {
+        $user = Users::authE();
+        if (Base::isError($user)) {
+            return $user;
+        } else {
+            $user = $user['data'];
+        }
+        //
+        $array = [];
+        //头像
+        $userimg = Request::input('userimg');
+        if ($userimg) {
+            $userimg = is_array($userimg) ? $userimg[0]['path'] : $userimg;
+            $array['userimg'] = Base::unFillUrl($userimg);
+        }
+        //昵称
+        $nickname = trim(Request::input('nickname'));
+        if ($nickname) {
+            if (mb_strlen($nickname) < 2) {
+                return Base::retError('昵称不可以少于2个字!');
+            } elseif (mb_strlen($nickname) > 8) {
+                return Base::retError('昵称最多只能设置8个字!');
+            } else {
+                $array['nickname'] = $nickname;
+            }
+        }
+        //职位/职称
+        $profession = trim(Request::input('profession'));
+        if ($profession) {
+            if (mb_strlen($profession) < 2) {
+                return Base::retError('昵称不可以少于2个字!');
+            } elseif (mb_strlen($profession) > 20) {
+                return Base::retError('昵称最多只能设置20个字!');
+            } else {
+                $array['profession'] = $profession;
+            }
+        }
+        //
+        if ($array) {
+            DB::table('users')->where('id', $user['id'])->update($array);
+        } else {
+            return Base::retError('请设置要修改的内容!');
+        }
+        return Base::retSuccess('修改成功!');
+    }
+
+    /**
+     * 修改密码
+     * @return array|mixed
+     */
+    public function editpass()
+    {
+        $user = Users::authE();
+        if (Base::isError($user)) {
+            return $user;
+        } else {
+            $user = $user['data'];
+        }
+        //
+        $oldpass = trim(Request::input('oldpass'));
+        $newpass = trim(Request::input('newpass'));
+        if (strlen($newpass) < 6) {
+            return Base::retError('密码设置不能小于6位数!');
+        }
+        if ($oldpass == $newpass) {
+            return Base::retError('新旧密码一致!');
+        }
+        //
+        if ($user['setpass']) {
+            $verify = DB::table('users')->where(['id'=>$user['id'], 'userpass'=>Base::md52($oldpass)])->count();
+            if (empty($verify)) {
+                return Base::retError('请填写正确的旧密码!');
+            }
+        }
+        DB::table('users')->where('id', $user['id'])->update(['encrypt' => Base::generatePassword(6), 'userpass'=>Base::md52($newpass)]);
+        return Base::retSuccess('修改成功');
+    }
+}

+ 10 - 0
app/Http/Controllers/Api/apidoc.json

@@ -0,0 +1,10 @@
+{
+  "name": "API",
+  "title": "APP接口",
+  "version": "2.0.0",
+  "description": "APP接口文档",
+  "url": "https://你的域名/",
+  "template": {
+    "withGenerator": false
+  }
+}

+ 89 - 0
app/Http/Controllers/Api/apidoc.php

@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * 给apidoc项目增加顺序编号
+ */
+
+error_reporting(E_ALL & ~E_NOTICE);
+
+$path = dirname(__FILE__). '/';
+$lists = scandir($path);
+//
+foreach ($lists AS $item) {
+    $fillPath = $path . $item;
+    if (substr($fillPath, -14) == 'Controller.php') {
+        $content = file_get_contents($fillPath);
+        preg_match_all("/\* @api \{(.+?)\} (.*?)\n/i", $content, $matchs);
+        $i = 1;
+        foreach ($matchs[2] AS $key=>$text) {
+            if (in_array(strtolower($matchs[1][$key]), array('get', 'post'))) {
+                $expl = explode(" ", __sRemove($text));
+                $end = $expl[1];
+                if ($expl[2]) {
+                    $end = '';
+                    foreach ($expl AS $k=>$v) { if ($k >= 2) { $end.= " ".$v; } }
+                }
+                $newtext = "* @api {".$matchs[1][$key]."} ".$expl[0]."          ".__zeroFill($i, 2).". ".trim($end);
+                $content = str_replace("* @api {".$matchs[1][$key]."} ".$text, $newtext, $content);
+                $i++;
+                //
+                echo $newtext;
+                echo "\r\n";
+            }
+        }
+        if ($i > 1) {
+            file_put_contents($fillPath, $content);
+        }
+    }
+}
+echo "Success \n";
+
+/** ************************************************************** */
+/** ************************************************************** */
+/** ************************************************************** */
+
+/**
+ * 替换所有空格
+ * @param $str
+ * @return mixed
+ */
+function __sRemove($str) {
+    $str = str_replace("  ", " ", $str);
+    if (__strExists($str, "  ")) {
+        return __sRemove($str);
+    }
+    return $str;
+}
+
+/**
+ * 是否包含字符
+ * @param $string
+ * @param $find
+ * @return bool
+ */
+function __strExists($string, $find)
+{
+    return !(strpos($string, $find) === FALSE);
+}
+
+/**
+ * @param string $str 补零
+ * @param int $length
+ * @param int $after
+ * @return bool|string
+ */
+function __zeroFill($str, $length = 0, $after = 1) {
+    if (strlen($str) >= $length) {
+        return $str;
+    }
+    $_str = '';
+    for ($i = 0; $i < $length; $i++) {
+        $_str .= '0';
+    }
+    if ($after) {
+        $_ret = substr($_str . $str, $length * -1);
+    } else {
+        $_ret = substr($str . $_str, 0, $length);
+    }
+    return $_ret;
+}

+ 129 - 1
app/Http/Controllers/IndexController.php

@@ -3,6 +3,8 @@
 namespace App\Http\Controllers;
 
 use App\Module\Base;
+use App\Module\Users;
+use Redirect;
 use Request;
 
 
@@ -14,7 +16,7 @@ use Request;
 class IndexController extends Controller
 {
 
-    private $version = '10161';
+    private $version = '100001';
 
     public function __invoke($method, $action = '', $child = '', $name = '')
     {
@@ -26,6 +28,12 @@ class IndexController extends Controller
         if ($action) {
             $app .= "__" . $action;
         }
+        @error_reporting(E_ALL & ~E_NOTICE);
+        if (Request::input('__Access-Control-Allow-Origin')) {
+            header('Access-Control-Allow-Origin:*');
+            header('Access-Control-Allow-Methods:GET,POST,PUT,DELETE,OPTIONS');
+            header('Access-Control-Allow-Headers:Content-Type, platform, platform-channel, token, release, Access-Control-Allow-Origin');
+        }
         return (method_exists($this, $app)) ? $this->$app($child) : Base::ajaxError("404 not found (" . str_replace("__", "/", $app) . ").");
     }
 
@@ -62,6 +70,126 @@ class IndexController extends Controller
         return view('main', ['version' => $this->version]);
     }
 
+    /**
+     * 接口文档
+     * @return \Illuminate\Http\RedirectResponse
+     */
+    public function api()
+    {
+        return Redirect::to(Base::fillUrl('docs'), 301);
+    }
+
+    /**
+     * 上传图片
+     * @return array
+     */
+    public function api__imgupload()
+    {
+        if (Users::token2userid() === 0) {
+            return Base::retError('身份失效,等重新登录!');
+        }
+        $scale = [intval(Request::input('width')), intval(Request::input('height'))];
+        if (!$scale[0] && !$scale[1]) {
+            $scale = [2160, 4160, -1];
+        }
+        $data = Base::upload([
+            "file" => Request::file('image'),
+            "type" => 'image',
+            "path" => "uploads/picture/" . Users::token2userid() . "/" . date("Ym") . "/",
+            "scale" => $scale
+        ]);
+        if (Base::isError($data)) {
+            return Base::retError($data['msg']);
+        } else {
+            return Base::retSuccess('success', $data['data']);
+        }
+    }
+
+    /**
+     * 浏览图片空间
+     * @return array
+     */
+    public function api__imgview()
+    {
+        if (Users::token2userid() === 0) {
+            return Base::retError('身份失效,等重新登录!');
+        }
+        $publicPath = "uploads/picture/" . Users::token2userid() . "/";
+        $dirPath = public_path($publicPath);
+        $dirs = $files = [];
+        //
+        $path = Request::input('path');
+        if ($path && is_string($path)) {
+            $path = str_replace(array('||', '|'), '/', $path);
+            $path = trim($path, '/');
+            $path = str_replace('..', '', $path);
+            $path = Base::leftDelete($path, $publicPath);
+            if ($path) {
+                $path = $path . '/';
+                $dirPath .= $path;
+                //
+                $dirs[] = [
+                    'type' => 'dir',
+                    'title' => '...',
+                    'path' => substr(substr($path, 0, -1), 0, strripos(substr($path, 0, -1), '/')),
+                    'url' => '',
+                    'thumb' => Base::fillUrl('images/dir.png'),
+                    'inode' => 0,
+                ];
+            }
+        } else {
+            $path = '';
+        }
+        $list = glob($dirPath . '*', GLOB_BRACE);
+        foreach ($list as $v) {
+            $filename = basename($v);
+            $pathTemp = $publicPath . $path . $filename;
+            if (is_dir($v)) {
+                $dirs[] = [
+                    'type' => 'dir',
+                    'title' => $filename,
+                    'path' => $pathTemp,
+                    'url' => Base::fillUrl($pathTemp),
+                    'thumb' => Base::fillUrl('images/dir.png'),
+                    'inode' => fileatime($v),
+                ];
+            } elseif (substr($filename, -10) != "_thumb.jpg") {
+                $array = [
+                    'type' => 'file',
+                    'title' => $filename,
+                    'path' => $pathTemp,
+                    'url' => Base::fillUrl($pathTemp),
+                    'thumb' => $pathTemp,
+                    'inode' => fileatime($v),
+                ];
+                //
+                $extension = pathinfo($dirPath . $filename, PATHINFO_EXTENSION);
+                if (in_array($extension, array('gif', 'jpg', 'jpeg', 'png', 'bmp'))) {
+                    if (file_exists($dirPath . $filename . '_thumb.jpg')) {
+                        $array['thumb'] .= '_thumb.jpg';
+                    }
+                    $array['thumb'] = Base::fillUrl($array['thumb']);
+                    $files[] = $array;
+                }
+            }
+        }
+        if ($dirs) {
+            $inOrder = [];
+            foreach ($dirs as $key => $item) {
+                $inOrder[$key] = $item['title'];
+            }
+            array_multisort($inOrder, SORT_DESC, $dirs);
+        }
+        if ($files) {
+            $inOrder = [];
+            foreach ($files as $key => $item) {
+                $inOrder[$key] = $item['inode'];
+            }
+            array_multisort($inOrder, SORT_DESC, $files);
+        }
+        //
+        return Base::retSuccess('success', ['dirs' => $dirs, 'files' => $files]);
+    }
 
     /**
      * 清理opcache数据

+ 2 - 1
app/Http/Middleware/VerifyCsrfToken.php

@@ -12,6 +12,7 @@ class VerifyCsrfToken extends Middleware
      * @var array
      */
     protected $except = [
-        //
+        //上传图片
+        'api/imgupload/',
     ];
 }

+ 178 - 0
app/Module/Users.php

@@ -0,0 +1,178 @@
+<?php
+
+namespace App\Module;
+
+use DB;
+use Request;
+use Session;
+
+/**
+ * Class Users
+ * @package App\Module
+ */
+class Users
+{
+    /**
+     * 临时身份标识
+     * @return mixed|string
+     */
+    public static function tmpID()
+    {
+        if (strlen(Request::input("tmpid")) == 16) {
+            return Request::input("tmpid");
+        }
+        $tmpID = Session::get('user::tmpID');
+        if (strlen($tmpID) != 16) {
+            $tmpID = Base::generatePassword(16);
+            Session::put('user::tmpID', $tmpID);
+        }
+        return $tmpID;
+    }
+
+    /**
+     * id获取用户名
+     * @param $id
+     * @return mixed
+     */
+    public static function id2username($id) {
+        return DB::table('users')->where('id', intval($id))->value('username');
+    }
+
+    /**
+     * 用户名获取id
+     * @param $username
+     * @return mixed
+     */
+    public static function username2id($username) {
+        return intval(DB::table('users')->where('username', $username)->value('id'));
+    }
+
+    /**
+     * token获取会员ID
+     * @return int
+     */
+    public static function token2userid()
+    {
+        $authorization = Base::getToken();
+        $id = 0;
+        if ($authorization) {
+            list($id, $username, $encrypt, $timestamp) = explode("@", base64_decode($authorization));
+        }
+        return intval($id);
+    }
+
+    /**
+     * token获取会员手机号
+     * @return int
+     */
+    public static function token2username()
+    {
+        $authorization = Base::getToken();
+        $username = '';
+        if ($authorization) {
+            list($id, $username, $encrypt, $timestamp) = explode("@", base64_decode($authorization));
+        }
+        return Base::isMobile($username) ? $username : '';
+    }
+
+    /**
+     * 用户身份认证(获取用户信息)
+     * @return array|mixed
+     */
+    public static function auth()
+    {
+        global $_A;
+        if (isset($_A["__static_auth"])) {
+            return $_A["__static_auth"];
+        }
+        $authorization = Base::getToken();
+        if ($authorization) {
+            list($id, $username, $encrypt, $timestamp) = explode("@", base64_decode($authorization));
+            if ($id > 0 && $timestamp + 2592000 > Base::time()) {
+                $userinfo = DB::table('users')->where(['id' => $id, 'username' => $username, 'encrypt' => $encrypt])->first();
+                Base::coll2array($userinfo);
+                if ($userinfo['token']) {
+                    $upArray = [];
+                    if (Base::getIp() && $userinfo['lineip'] != Base::getIp()) {
+                        $upArray['lineip'] = Base::getIp();
+                    }
+                    if ($userinfo['linedate'] + 30 < Base::time()) {
+                        $upArray['linedate'] = Base::time();
+                    }
+                    if ($upArray) {
+                        DB::table('users')->where('id', $userinfo['id'])->update($upArray);
+                    }
+                    return $_A["__static_auth"] = Users::retInfo($userinfo);
+                }
+            }
+        }
+        return $_A["__static_auth"] = false;
+    }
+
+    /**
+     * 用户身份认证, 身份丢失时exit输出(获取用户信息)
+     * @return array|mixed
+     */
+    public static function authE()
+    {
+        $user = Users::auth();
+        if (!$user) {
+            $authorization = Base::getToken();
+            if ($authorization) {
+                return Base::retError('身份已失效,请重新登录!', [], -1);
+            } else {
+                return Base::retError('请登录后继续...', [], -1);
+            }
+        }
+        return Base::retSuccess("auth", $user);
+    }
+
+    /**
+     * 生成token
+     * @param $userinfo
+     * @return bool|string
+     */
+    public static function token($userinfo)
+    {
+        if (strlen($userinfo['encrypt']) < 6) {
+            $userinfo['encrypt'] = Base::generatePassword(6);
+            DB::table('users')->where('id', $userinfo['id'])->update(['encrypt' => $userinfo['encrypt']]);
+        }
+        return base64_encode($userinfo['id'] . '@' . $userinfo['username'] . '@' . $userinfo['encrypt'] . '@' . Base::time() . '@' . Base::generatePassword(6));
+    }
+
+    /**
+     * 判断用户权限(身份)
+     * @param $identity
+     * @return array
+     */
+    public static function identity($identity)
+    {
+        $user = Users::auth();
+        if (is_array($user['identity'])
+            && in_array($identity, $user['identity'])) {
+            return Base::retSuccess("权限通过");
+        }
+        return Base::retError("权限不足");
+    }
+
+    /**
+     * 筛选用户信息
+     * @param $userinfo
+     * @return mixed
+     */
+    public static function retInfo($userinfo)
+    {
+        //是否设置密码
+        if (!isset($userinfo['setpass'])) {
+            $userinfo['setpass'] = $userinfo['userpass'] ? 1 : 0;
+        }
+        //
+        $userinfo['setting'] = Base::string2array($userinfo['setting']);
+        $userinfo['userimg'] = $userinfo['userimg'] ? Base::fillUrl($userinfo['userimg']) : url('images/avatar.png');
+        $userinfo['identity'] = is_array($userinfo['identity']) ? $userinfo['identity'] : explode(",", trim($userinfo['identity'], ","));
+        unset($userinfo['encrypt']);
+        unset($userinfo['userpass']);
+        return $userinfo;
+    }
+}

+ 4 - 4
config/database.php

@@ -39,7 +39,7 @@ return [
             'driver' => 'sqlite',
             'url' => env('DATABASE_URL'),
             'database' => env('DB_DATABASE', database_path('database.sqlite')),
-            'prefix' => '',
+            'prefix' => env('DB_PREFIX', ''),
             'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
         ],
 
@@ -54,7 +54,7 @@ return [
             'unix_socket' => env('DB_SOCKET', ''),
             'charset' => 'utf8mb4',
             'collation' => 'utf8mb4_unicode_ci',
-            'prefix' => '',
+            'prefix' => env('DB_PREFIX', ''),
             'prefix_indexes' => true,
             'strict' => true,
             'engine' => null,
@@ -72,7 +72,7 @@ return [
             'username' => env('DB_USERNAME', 'forge'),
             'password' => env('DB_PASSWORD', ''),
             'charset' => 'utf8',
-            'prefix' => '',
+            'prefix' => env('DB_PREFIX', ''),
             'prefix_indexes' => true,
             'schema' => 'public',
             'sslmode' => 'prefer',
@@ -87,7 +87,7 @@ return [
             'username' => env('DB_USERNAME', 'forge'),
             'password' => env('DB_PASSWORD', ''),
             'charset' => 'utf8',
-            'prefix' => '',
+            'prefix' => env('DB_PREFIX', ''),
             'prefix_indexes' => true,
         ],
 

+ 44 - 80
resources/assets/js/_modules/language/global/en.js

@@ -1,81 +1,45 @@
 export default {
-    "系统管理平台": "System",
-    "管理首页": "Homepage",
-    "充值方式": "Recharge method",
-    "充值订单": "Recharge order",
-    "修改密码": "change Password",
-    "退出登录": "Sign out",
-    "系统设置": "System settings",
-    "分享设置": "Sharing settings",
-    "其他设置": "Other settings",
-    "用户协议": "User Agreement",
-    "操作日志": "Admin log",
-    "会员管理": "Member",
-    "会员等级": "Membership level",
-    "地址管理": "Address",
-    "银行卡管理": "Bank card",
-    "代理管理": "Agent",
-    "代理设置": "Proxy settings",
-    "申请列表": "Application list",
-    "代理列表": "Proxy list",
-    "代理提成": "Agent commission",
-    "支付管理": "Payment",
-    "系统入账": "System accounting",
-    "积分管理": "Point",
-    "账变管理": "Account change",
-    "一倍流水": "Double water",
-    "提现管理": "Cash withdrawal",
-    "竞彩管理": "Lottery",
-    "竞彩配置": "Lottery configuration",
-    "开奖记录": "Lottery record",
-    "竞彩订单": "Lottery orders",
-    "坐庄订单": "Banker Order",
-    "坐庄记录": "Banker Records",
-    "扫雷红包": "Red Packet",
-    "扫雷配置": "Packet configuration",
-    "红包记录": "Packet record",
-    "会员房间": "Vip room",
-    "刮刮彩": "Scratch",
-    "场次列表": "List of Shows",
-    "批次列表": "Batch List",
-    "代理购买记录": "Agent purchase record",
-    "卡片列表": "Card List",
-    "卡片赠送记录": "Card Gift Record",
-    "市场营销": "Marketing",
-    "充值满送": "Top up",
-    "注册赠送": "Registration gift",
-    "2019迎圣诞": "2019 Christmas",
-    "大转盘抽奖": "Big turntable draw",
-    "中秋摇色子": "Mid-Autumn Shakes",
-    "国庆大派送": "National Day delivery",
-    "兑换码系统": "Redemption code",
-    "王者归来": "King's return",
-    "签到2019": "Sign in 2019",
-    "内容管理": "Content",
-    "弹窗公告": "Pop Notice",
-    "图片广告": "Picture AD",
-    "漂浮广告": "Floating AD",
-    "优惠信息": "Discount information",
-    "发现管理": "Discovery",
-    "发现列表": "Discovery list",
-    "发现评论": "Discover comments",
-    "信息管理": "Information",
-    "站内消息": "Station news",
-    "举报信息": "Report information",
-    "短信验证码": "SMS verification code",
-    "旧密码": "Old password",
-    "新密码": "New password",
-    "确认新密码": "Confirm the new password",
-    "取消": "Cancel",
-    "提交": "Submit",
-
-    "审核头像": "Review avatar",
-    "通过": "Pass",
-    "不通过": "Fail",
-
-    "正在加载中.....": "Loading.....",
-    "网络繁忙,请稍后再试!": "Network is busy, please try again later!",
-
-    "充值订单待审核": "Top up",
-    "有提现!有提现!": "Withdraw",
-}
+    "请填写用户名!": null,
+    "请填写登录密码!": null,
+    "密码长度不能少于6位!": null,
+    "轻量级的团队在线协作": null,
+    "酷团队协作工具就从这里开始": null,
+    "立即登陆": null,
+    "待办四象限": null,
+    "项目管理": null,
+    "在线知识库": null,
+    "日程管理": null,
+    "待办四象限:突出事情优先级,帮助员工合理安排时间,提高工作效率。": null,
+    "项目管理:自定义项目看板,可视化任务安排。": null,
+    "在线知识库:在线流程图,在线文档,以及可视化的目录编排,文档管理无忧。": null,
+    "日程管理:可视化日程管理,快速搞定工作计划,了解工作宏观安排。": null,
+    "用户登录": null,
+    "用户名": null,
+    "密码": null,
+    "取消": null,
+    "登录": null,
+    "登录成功": null,
+    "待办": null,
+    "我的待办": null,
+    "待办日程": null,
+    "已完成的任务": null,
+    "我关注的任务": null,
+    "周报/日报": null,
+    "重要且紧急": null,
+    "恭喜你!已完成了所有待办": "Praise you! All to-do completed.",
+    "重要不紧急": null,
+    "紧急不重要": null,
+    "不重要不紧急": null,
+    "项目": null,
+    "知识库": null,
+    "同事": null,
+    "欢迎您": null,
+    "个人中心": null,
+    "退出登录": null,
+    "新建项目": null,
+    "收藏的项目": null,
+    "参与的项目": null,
+    "我创建的项目": null,
+    "新建知识库": null,
+    "尊敬的会员": null,
+}

+ 4 - 1
resources/assets/js/_modules/language/index.js

@@ -123,6 +123,9 @@ export default {
                         //
                         if (typeof this.privateLanguageData[this.privateLanguageType] === "object") {
                             let temp = this.privateLanguageData[this.privateLanguageType][text];
+                            if (temp === null) {
+                                return text;
+                            }
                             if (typeof temp !== 'undefined') {
                                 return temp;
                             }
@@ -145,4 +148,4 @@ export default {
             }
         });
     }
-}
+}

+ 3 - 0
resources/assets/js/main/App.vue

@@ -5,11 +5,14 @@
                 <router-view class="child-view"></router-view>
             </keep-alive>
         </transition>
+        <w-spinner></w-spinner>
     </div>
 </template>
 
 <script>
+    import WSpinner from "./components/WSpinner";
     export default {
+        components: {WSpinner},
         data () {
             return {
                 transitionName: null,

+ 60 - 0
resources/assets/js/main/app.js

@@ -0,0 +1,60 @@
+import Vue from 'vue'
+import App from './App.vue'
+import routes from './routes'
+import VueRouter from 'vue-router'
+import ViewUI from 'view-design';
+import Language from '../_modules/language'
+
+import '../common'
+import './main'
+
+Vue.use(VueRouter);
+Vue.use(ViewUI);
+Vue.use(Language);
+
+import Title from '../_components/Title.vue'
+Vue.component('VTitle', Title);
+
+const router = new VueRouter({routes});
+
+//进度条配置
+ViewUI.LoadingBar.config({
+    color: '#3fcc25',
+    failedColor: '#ff0000'
+});
+router.beforeEach((to, from, next) => {
+    ViewUI.LoadingBar.start();
+    next();
+});
+router.afterEach((to, from, next) => {
+    ViewUI.LoadingBar.finish();
+});
+
+//加载函数
+Vue.prototype.goForward = function(location, isReplace) {
+    if (typeof location === 'string') location = {name: location};
+    if (isReplace === true) {
+        this.$router.replace(location);
+    }else{
+        this.$router.push(location);
+    }
+};
+
+//返回函数
+Vue.prototype.goBack = function(number) {
+    window.history.go(typeof number==='number'?number:-1)
+};
+
+Vue.prototype.$A = $A;
+
+Vue.config.productionTip = false;
+
+const app = new Vue({
+    el: '#app',
+    router,
+    template: '<App/>',
+    components: { App }
+});
+
+$A.app = app;
+

+ 544 - 0
resources/assets/js/main/components/ImgUpload.vue

@@ -0,0 +1,544 @@
+<template>
+    <div>
+        <div v-if="type !== 'callback'" class="imgcomp-upload-list" v-for="item in uploadList">
+            <template v-if="item.status === 'finished'">
+                <div class="imgcomp-upload-img" v-bind:style="{ 'background-image': 'url(' + __thumb(item.thumb) + ')' }"></div>
+                <div class="imgcomp-upload-list-cover">
+                    <Icon type="ios-eye-outline" @click.native="handleView(item)"></Icon>
+                    <Icon type="ios-trash-outline" @click.native="handleRemove(item)"></Icon>
+                </div>
+            </template>
+            <template v-else>
+                <Progress v-if="item.showProgress" :percent="item.percentage" hide-info></Progress>
+            </template>
+        </div>
+        <div class="add-box" v-bind:class="{ 'callback-add-box': type === 'callback' }">
+            <div class="add-box-icon">
+                <Icon type="md-add" size="32"></Icon>
+            </div>
+            <div class="add-box-upload">
+                <div class="add-box-item" @click="browsePicture">
+                    <span>浏览<em v-if="type === 'callback'">图片</em></span>
+                </div>
+                <div class="add-box-item">
+                    <Upload
+                            name="image"
+                            ref="upload"
+                            accept="image/*"
+                            :action="actionUrl"
+                            :data="params"
+                            :show-upload-list="false"
+                            :max-size="2048"
+                            :format="['jpg', 'jpeg', 'gif', 'png']"
+                            :default-file-list="defaultList"
+                            :on-success="handleSuccess"
+                            :on-format-error="handleFormatError"
+                            :on-exceeded-size="handleMaxSize"
+                            :before-upload="handleBeforeUpload"
+                            :multiple=multiple>
+                        <span>上传<em v-if="type === 'callback'">图片</em></span>
+                    </Upload>
+                </div>
+            </div>
+        </div>
+        <Modal title="浏览图片空间的图片" v-model="browseVisible" class="img-upload-modal" width="710" :styles="{top: '35px',paddingBottom: '35px'}">
+            <div class="browse-load" v-if="isLoading">加载中...</div>
+            <div class="browse-list" :class="httpType==='input'?'browse-list-disabled':''" ref="browselistbox">
+                <div class="browse-item" v-for="item in browseList" @click="browseItem(item)">
+                    <Icon v-if="item.active" class="browse-icon" type="ios-checkmark-circle"></Icon>
+                    <div class="browse-img" v-bind:style="{ 'background-image': 'url(' + item.thumb + ')' }"></div>
+                    <div class="browse-title">{{item.title}}</div>
+                </div>
+            </div>
+            <div slot="footer" class="img-upload-foot">
+                <div v-if="type !== 'callback' && http && httpType===''" class="img-upload-foot-input" @click="httpType='input'">
+                    <Icon type="ios-image" size="22"/>
+                    <div class="img-upload-foot-httptitle">自定义图片地址</div>
+                </div>
+                <div v-if="type !== 'callback' && http && httpType==='input'" class="img-upload-foot-input">
+                    <Input v-model="httpValue" placeholder="以“http://”或“https://”开头" @on-search="httpEnter" search enter-button="确定">
+                        <span slot="prepend" @click="httpType=''" style="cursor:pointer">自定义地址:</span>
+                    </Input>
+                </div>
+                <Button v-if="httpType===''" @click="browseVisible=false">关闭</Button>
+                <Button v-if="httpType===''" type="primary" @click="handleCallback(true)">完成</Button>
+            </div>
+        </Modal>
+        <Modal title="查看图片" v-model="visible" class="img-upload-modal" draggable>
+            <div style="max-height:480px;overflow:auto;">
+                <a :href="imgVisible" target="_blank"><img :src="imgVisible" v-if="visible" style="max-width:100%;max-height:900px;display:block;margin:0 auto"></a>
+            </div>
+        </Modal>
+    </div>
+</template>
+
+<style lang="scss">
+    .img-upload-modal {
+        .ivu-modal-mask {
+            z-index: 1001;
+        }
+        .ivu-modal-no-mask {
+            background-color: rgba(55,55,55,.2);
+        }
+        .ivu-modal-wrap {
+            z-index: 1001;
+        }
+    }
+    .imgcomp-upload-list{
+        display: inline-block;
+        width: 60px;
+        height: 60px;
+        text-align: center;
+        line-height: 60px;
+        border: 1px solid transparent;
+        border-radius: 4px;
+        overflow: hidden;
+        background: #fff;
+        position: relative;
+        box-shadow: 0 1px 1px rgba(0,0,0,.2);
+        margin-right: 4px;
+        vertical-align: top;
+        .imgcomp-upload-img {
+            position: absolute;
+            top: 0;
+            left: 0;
+            width: 100%;
+            height: 100%;
+            background-position: center;
+            background-size: cover;
+        }
+        .imgcomp-upload-list-cover{
+            display: none;
+            position: absolute;
+            top: 0;
+            bottom: 0;
+            left: 0;
+            right: 0;
+            background: rgba(0,0,0,.6);
+        }
+        .imgcomp-upload-list-cover i{
+            color: #fff;
+            font-size: 24px;
+            cursor: pointer;
+            vertical-align: middle;
+            margin: 0;
+            transition: all .2s;
+        }
+        .imgcomp-upload-list-cover i:hover{
+            font-size: 28px;
+        }
+        .ivu-progress-outer {
+            background-color: rgba(0, 0, 0, 0.68);
+            .ivu-progress-inner{
+                width: 88%;
+            }
+        }
+    }
+    .imgcomp-upload-list:hover .imgcomp-upload-list-cover{
+        display: block;
+    }
+    .img-upload-foot {
+        display: flex;
+        align-items: center;
+        justify-content: flex-end;
+        .img-upload-foot-input {
+            flex: 1;
+            text-align: left;
+            display: flex;
+            align-items: center;
+            justify-content: flex-end;
+            .img-upload-foot-httptitle {
+                cursor: pointer;
+                padding-left: 3px;
+                margin-right: 22px;
+            }
+        }
+    }
+    .add-box {
+        width: 60px;
+        height: 60px;
+        line-height: 60px;
+        display: inline-block;
+        background: #fff;
+        border: 1px dashed #dddee1;
+        border-radius: 4px;
+        text-align: center;
+        position: relative;
+        overflow: hidden;
+        vertical-align: top;
+        .add-box-icon {
+            i {
+                vertical-align:middle;
+                padding-bottom: 2px;
+            }
+        }
+        .add-box-upload {
+            display: none;
+            position: absolute;
+            top: 0;
+            left: 0;
+            height: 100%;
+            width: 100%;
+            color: #ffffff;
+            padding-top: 9px;
+            background: rgba(0, 0, 0, 0.6);
+            .add-box-item {
+                height: 22px;
+                line-height: 22px;
+                cursor: pointer;
+                .ivu-upload-drag,.ivu-upload-drag:hover {
+                    background:transparent;
+                    border:0;
+                    border-radius:0;
+                }
+                span {
+                    transition: all .2s;
+                    font-size: 12px;
+                }
+            }
+            .add-box-item:hover {
+                span {
+                    font-size: 14px;
+                }
+            }
+        }
+        em {
+            font-style: normal;
+        }
+    }
+    .add-box:hover {
+        border-color: rgba(0,0,0,.6);
+        .add-box-upload {
+            display: block;
+        }
+    }
+    .callback-add-box {
+        display: block;
+        width: auto;
+        height: 25px;
+        line-height: 25px;
+        border: 0;
+        background: transparent;
+        .add-box-icon {
+            display: none;
+        }
+        .add-box-upload {
+            display: block;
+            width: auto;
+            background: transparent;
+            color: #333;
+            padding: 0;
+            > div {
+                display: inline-block;
+                padding-right: 10px;
+            }
+        }
+    }
+    .browse-load {
+        margin: 20px;
+        text-align: center;
+    }
+    .browse-list {
+        max-height: 540px;
+        overflow: auto;
+        .browse-item {
+            margin: 10px 15px;
+            display: inline-block;
+            text-align: center;
+            cursor: pointer;
+            position: relative;
+            .browse-img {
+                width: 64px;
+                height: 64px;
+                background-image: url("../../../statics/images/loading.png");
+                background-position: center;
+                background-repeat: no-repeat;
+                background-size: cover;
+            }
+            .browse-title {
+                display: block;
+                width: 64px;
+                margin-top: 5px;
+                white-space: nowrap;
+                overflow: hidden;
+                text-overflow: ellipsis;
+            }
+            .browse-icon {
+                position: absolute;
+                top: 0;
+                left: 0;
+                width: 100%;
+                height: 64px;
+                font-size: 36px;
+                padding-top: 15px;
+                color: #ffffff;
+                background-color: rgba(0, 0, 0, 0.5);
+            }
+        }
+    }
+    .browse-list-disabled {
+        position: relative;
+    }
+    .browse-list-disabled:after {
+        position: absolute;
+        content: '';
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        background-color: rgba(255, 255, 255, 0.9);
+        z-index: 1;
+    }
+</style>
+<script>
+    export default {
+        name: 'ImgUpload',
+        props: {
+            value: {
+            },
+            num: {
+            },
+            width: {
+            },
+            height: {
+            },
+            type: {
+            },
+            http: {
+                type: Boolean,
+                default: false
+            },
+        },
+        data () {
+            return {
+                actionUrl: $A.aUrl('imgupload'),
+                params: {'token': $A.token(), 'width': this.width, 'height': this.height},
+                multiple: this.num > 1,
+                visible: false,
+                browseVisible: false,
+                isLoading: false,
+                browseList: [],
+                browseListNext: [],
+                imgVisible: '',
+                defaultList: this.initItems(this.value),
+                uploadList: [],
+                maxNum: Math.min(Math.max($A.runNum(this.num), 1), 99),
+                httpValue: '',
+                httpType: '',
+            }
+        },
+        mounted () {
+            this.uploadList = this.$refs.upload.fileList;
+            this.$emit('input', this.uploadList);
+            //
+            let browseBox = $A(this.$refs.browselistbox);
+            browseBox.scroll(()=>{
+                let nHight = browseBox[0].scrollHeight;
+                let nTop = browseBox[0].scrollTop;
+                let boxHight = browseBox.height();
+                if(nTop + boxHight >= nHight) {
+                    //到底了
+                    if (this.browseListNext.length > 0) {
+                        let tmpNext = this.browseListNext;
+                        this.browseListNext = [];
+                        this.browsePictureFor(tmpNext);
+                    }
+                }
+            });
+        },
+        watch: {
+            value (val) {
+                if (typeof val === 'string') {
+                    this.$emit('input', this.initItems(val));
+                    return;
+                }
+                if (val === this.$refs.upload.fileList) {
+                    return;
+                }
+                this.$refs.upload.fileList = this.initItems(val);
+                this.uploadList = this.$refs.upload.fileList;
+            },
+            browseVisible() {
+                this.httpType = '';
+                this.httpValue = '';
+            }
+        },
+        methods: {
+            handleCallback(file) {
+                if (this.type === 'callback') {
+                    if (file === true) {
+                        this.$emit('on-callback', this.uploadList);
+                        this.$refs.upload.fileList = [];
+                        this.uploadList = this.$refs.upload.fileList;
+                    }else if (typeof file === "object") {
+                        this.$emit('on-callback', [file]);
+                    }
+                }
+                this.browseVisible = false;
+            },
+            initItems(items) {
+                //数据初始化
+                if (typeof items === 'string') {
+                    items = [{'url': items}];
+                }
+                let lists = [];
+                $A.each(items, (index, item)=>{
+                    if (typeof item === 'string') item = {'url': item};
+                    if (item.url) {
+                        item.active = true;
+                        item.status = 'finished';
+                        if (typeof item.path === 'undefined') item.path = item.url;
+                        if (typeof item.thumb === 'undefined') item.thumb = item.url;
+                        lists.push(item);
+                    }
+                });
+                return lists;
+            },
+            handleView (item) {
+                //查看
+                this.visible = true;
+                this.imgVisible = item.url;
+            },
+            handleRemove (item) {
+                //删除
+                let fileList = this.$refs.upload.fileList;
+                this.$refs.upload.fileList.splice(fileList.indexOf(item), 1);
+                this.$emit('input', this.$refs.upload.fileList);
+            },
+            handleSuccess (res, file) {
+                //上传完成
+                if (res.ret === 1) {
+                    file.url = res.data.url;
+                    file.path = res.data.path;
+                    file.thumb = res.data.thumb;
+                    this.handleCallback(file);
+                }else{
+                    this.$Modal.warning({
+                        title: '上传失败',
+                        content: '文件 ' + file.name + ' 上传失败,' + res.msg
+                    });
+                    this.$refs.upload.fileList.pop();
+                }
+                this.$emit('input', this.$refs.upload.fileList);
+            },
+            handleFormatError (file) {
+                //上传类型错误
+                this.$Modal.warning({
+                    title: '文件格式不正确',
+                    content: '文件 ' + file.name + ' 格式不正确,请上传 jpg、jpeg、gif、png 格式的图片。'
+                });
+            },
+            handleMaxSize (file) {
+                //上传大小错误
+                this.$Modal.warning({
+                    title: '超出文件大小限制',
+                    content: '文件 ' + file.name + ' 太大,不能超过 2M。'
+                });
+            },
+            handleBeforeUpload () {
+                //上传前判断
+                let check = this.uploadList.length < this.maxNum;
+                if (!check) {
+                    this.$Modal.warning({ title: '温馨提示', content: '最多只能上传 ' + this.maxNum + ' 张图片。' });
+                }
+                return check;
+            },
+            handleClick() {
+                //手动上传
+                if (this.handleBeforeUpload()) {
+                    this.$refs.upload.handleClick()
+                }
+            },
+            browsePicture(path) {
+                //获取图片空间
+                this.browseVisible = true;
+                this.browseList = [];
+                this.browseListNext = [];
+                this.isLoading = true;
+                $A.aAjax({
+                    url: 'imgview',
+                    data: { path: path?path:'' },
+                    beforeSend: true,
+                    complete: true,
+                    error: true,
+                    success: (res) => {
+                        this.isLoading = false;
+                        if (res.ret === 1) {
+                            let dirs = res.data['dirs'];
+                            for (let i = 0; i < dirs.length; i++) {
+                                this.browseList.push(dirs[i]);
+                            }
+                            this.browsePictureFor(res.data['files']);
+                        }else if (res.ret === -2) {
+                            this.browseVisible = false;
+                            this.$Modal.warning({ title: '温馨提示', content: res.msg });
+                        }
+                    }
+                });
+            },
+
+            browsePictureFor(files) {
+                for (let o = 0; o < files.length; o++) {
+                    for (let j = 0; j < this.uploadList.length; j++) {
+                        if (this.uploadList[j]['url'] === files[o]['url']
+                            || this.uploadList[j]['url'] === files[o]['path']) {
+                            files[o]['active'] = true;
+                            break;
+                        }
+                    }
+                    if (o < 100) {
+                        this.browseList.push(files[o]);
+                    }else{
+                        this.browseListNext.push(files[o]);
+                    }
+                }
+            },
+
+            browseItem(item) {
+                //点击选择图片
+                if (item.type === 'dir') {
+                    //目录
+                    this.browsePicture(item.path);
+                }else if (item.type === 'file') {
+                    //文件
+                    if (item.active) {
+                        let fileList = this.$refs.upload.fileList;
+                        this.$refs.upload.fileList.splice(fileList.indexOf(item), 1);
+                        item.active = false;
+                    }else{
+                        if (this.maxNum === 1) {
+                            for (let i = 0; i < this.browseList.length; i++) {
+                                this.browseList[i].active = false;
+                            }
+                            this.$refs.upload.fileList = [];
+                            this.uploadList = this.$refs.upload.fileList;
+                        }
+                        let check = this.uploadList.length < this.maxNum;
+                        if (!check) {
+                            this.$Modal.warning({ title: '温馨提示', content: '最多只能选择 ' + this.maxNum + ' 张图片。' });
+                            return;
+                        }
+                        item.active = true;
+                        item.status = 'finished';
+                        this.$refs.upload.fileList.push(item);
+                        this.uploadList = this.$refs.upload.fileList;
+                    }
+                    this.$emit('input', this.$refs.upload.fileList);
+                }
+            },
+
+            __thumb(url) {
+                if ($A.strExists(url, "?", false)) {
+                    return url + "&__thumb=true";
+                }else{
+                    return url + "?__thumb=true";
+                }
+            },
+
+            httpEnter() {
+                this.$emit('input', this.initItems(this.httpValue));
+                this.browseVisible = false;
+            }
+        }
+    }
+</script>

+ 442 - 0
resources/assets/js/main/components/TEditor.vue

@@ -0,0 +1,442 @@
+<template>
+    <div>
+        <div class="teditor-box" :class="[spinShow?'teditor-loadstyle':'teditor-loadedstyle']">
+            <textarea ref="myTextarea" :id="id">{{ content }}</textarea>
+            <Spin fix v-if="spinShow">
+                <Icon type="ios-loading" size=18 class="teditor-spin-icon-load"></Icon>
+                <div>加载组件中...</div>
+            </Spin>
+            <img-upload ref="myUpload" class="teditor-upload" type="callback" @on-callback="editorImage" num="50" style="margin-top:5px;height:26px;"></img-upload>
+        </div>
+        <Modal v-model="transfer" class="teditor-transfer" @on-visible-change="transferChange" footer-hide fullscreen transfer>
+            <div slot="close">
+                <Button type="primary" size="small">完成</Button>
+            </div>
+            <div class="teditor-transfer-body">
+                <textarea :id="'T_' + id">{{ content }}</textarea>
+            </div>
+        </Modal>
+    </div>
+</template>
+
+<style lang="scss">
+    .teditor-box {
+        textarea {
+            opacity: 0;
+        }
+        .tox-tinymce {
+            box-shadow: none;
+            box-sizing: border-box;
+            border-color: #dddee1;
+            border-radius: 4px;
+            overflow: hidden;
+            .tox-statusbar {
+                span.tox-statusbar__branding {
+                    a {
+                        display: none;
+                    }
+                }
+            }
+        }
+    }
+    .teditor-transfer {
+        background-color: #ffffff;
+        .ivu-modal-header {
+            display: none;
+        }
+        .ivu-modal-close {
+            top: 7px;
+        }
+        .teditor-transfer-body {
+            position: absolute;
+            top: 0;
+            left: 0;
+            width: 100%;
+            height: 100%;
+            padding: 0;
+            margin: 0;
+            textarea {
+                opacity: 0;
+            }
+            .tox-tinymce {
+                border: 0;
+                .tox-statusbar {
+                    span.tox-statusbar__branding {
+                        a {
+                            display: none;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    .tox {
+        &.tox-silver-sink {
+            z-index: 13000;
+        }
+    }
+</style>
+<style lang="scss" scoped>
+    .teditor-loadstyle {
+        width: 100%;
+        height: 180px;
+        overflow: hidden;
+        position: relative;
+    }
+    .teditor-loadedstyle {
+        width: 100%;
+        max-height: inherit;
+        overflow: inherit;
+        position: relative;
+    }
+    .teditor-spin-icon-load {
+        animation: ani-teditor-spin 1s linear infinite;
+    }
+    @keyframes ani-teditor-spin {
+        from { transform: rotate(0deg);}
+        50%  { transform: rotate(180deg);}
+        to   { transform: rotate(360deg);}
+    }
+    .teditor-upload {
+        display: none;
+        width: 0;
+        height: 0;
+        overflow: hidden;
+    }
+</style>
+
+<script>
+    import tinymce from 'tinymce/tinymce';
+    import ImgUpload from "./ImgUpload";
+
+    export default {
+        name: 'TEditor',
+        components: {ImgUpload},
+        props: {
+            id: {
+                type: String,
+                default: () => {
+                    return  "tinymce_" + Math.round(Math.random() * 10000);
+                }
+            },
+            value: {
+                default: ''
+            },
+            height: {
+                default: 360,
+            },
+            htmlClass: {
+                default: '',
+                type: String
+            },
+            plugins: {
+                type: Array,
+                default: () => {
+                    return [
+                        'advlist autolink lists link image charmap print preview hr anchor pagebreak imagetools',
+                        'searchreplace visualblocks visualchars code',
+                        'insertdatetime media nonbreaking save table contextmenu directionality',
+                        'emoticons paste textcolor colorpicker imagetools codesample'
+                    ];
+                }
+            },
+            toolbar1: {
+                type: String,
+                default: ' undo redo | styleselect | uploadImages | bold italic underline forecolor backcolor | alignleft aligncenter alignright | bullist numlist outdent indent | link image emoticons media codesample | preview screenload',
+            },
+            toolbar2: {
+                type: String,
+                default: '',
+            },
+            other_options: {
+                type: Object,
+                default: () => {
+                    return {};
+                }
+            },
+            readonly: {
+                type: Boolean,
+                default: false
+            }
+        },
+        data() {
+            return {
+                content: '',
+                editor: null,
+                editorT: null,
+                cTinyMce: null,
+                checkerTimeout: null,
+                isTyping: false,
+
+                spinShow: true,
+                transfer: false,
+            };
+        },
+        mounted() {
+            this.content = this.value;
+            this.init();
+        },
+        activated() {
+            this.content = this.value;
+            this.init();
+        },
+        deactivated() {
+            if (this.editor !== null) {
+                this.editor.destroy();
+            }
+            this.spinShow = true;
+            $A(this.$refs.myTextarea).show();
+        },
+        watch: {
+            value(newValue) {
+                if (newValue == null) {
+                    newValue = "";
+                }
+                if (!this.isTyping) {
+                    if (this.getEditor() !== null) {
+                        this.getEditor().setContent(newValue);
+                    } else{
+                        this.content = newValue;
+                    }
+                }
+            },
+            readonly(value) {
+                if (this.editor !== null) {
+                    if (value) {
+                        this.editor.setMode('readonly');
+                    } else {
+                        this.editor.setMode('design');
+                    }
+                }
+            }
+        },
+        methods: {
+            init() {
+                this.$nextTick(() => {
+                    tinymce.init(this.concatAssciativeArrays(this.options(false), this.other_options));
+                });
+            },
+
+            initTransfer() {
+                this.$nextTick(() => {
+                    tinymce.init(this.concatAssciativeArrays(this.options(true), this.other_options));
+                });
+            },
+
+            options(isFull) {
+                return {
+                    selector: (isFull ? '#T_' : '#') + this.id,
+                    language: "zh_CN",
+                    toolbar1: this.toolbar1,
+                    toolbar2: this.toolbar2,
+                    plugins: this.plugins,
+                    menu: {
+                        view: {
+                            title: 'View',
+                            items: 'code | visualaid visualchars visualblocks | spellchecker | preview fullscreen screenload | showcomments'
+                        },
+                        insert: {
+                            title: "Insert",
+                            items: "image link media addcomment pageembed template codesample inserttable | charmap emoticons hr | pagebreak nonbreaking anchor toc | insertdatetime | uploadImages browseImages"
+                        }
+                    },
+                    codesample_languages: [
+                        {text:"HTML/VUE/XML",value:"markup"},
+                        {text:"JavaScript",value:"javascript"},
+                        {text:"CSS",value:"css"},
+                        {text:"PHP",value:"php"},
+                        {text:"Ruby",value:"ruby"},
+                        {text:"Python",value:"python"},
+                        {text:"Java",value:"java"},
+                        {text:"C",value:"c"},
+                        {text:"C#",value:"csharp"},
+                        {text:"C++",value:"cpp"}
+                    ],
+                    height: isFull ? '100%' : ($A.runNum(this.height) || 360),
+                    resize: !isFull,
+                    convert_urls:false,
+                    toolbar_drawer: 'floating',
+                    setup: (editor) => {
+                        editor.ui.registry.addMenuButton('uploadImages', {
+                            text: '图片',
+                            tooltip: '上传/浏览 图片',
+                            fetch: (callback) => {
+                                let items = [{
+                                    type: 'menuitem',
+                                    text: '上传图片',
+                                    onAction: () => {
+                                        this.$refs.myUpload.handleClick();
+                                    }
+                                }, {
+                                    type: 'menuitem',
+                                    text: '浏览图片',
+                                    onAction: () => {
+                                        this.$refs.myUpload.browsePicture();
+                                    }
+                                }];
+                                callback(items);
+                            }
+                        });
+                        editor.ui.registry.addMenuItem('uploadImages', {
+                            text: '上传图片',
+                            onAction: () => {
+                                this.$refs.myUpload.handleClick();
+                            }
+                        });
+                        editor.ui.registry.addMenuItem('browseImages', {
+                            text: '浏览图片',
+                            onAction: () => {
+                                this.$refs.myUpload.browsePicture();
+                            }
+                        });
+                        if (isFull) {
+                            editor.ui.registry.addButton('screenload', {
+                                icon: 'fullscreen',
+                                tooltip: '退出全屏',
+                                onAction: () => {
+                                    this.closeFull();
+                                }
+                            });
+                            editor.ui.registry.addMenuItem('screenload', {
+                                text: '退出全屏',
+                                onAction: () => {
+                                    this.closeFull();
+                                }
+                            });
+                            editor.on('Init', (e) => {
+                                this.editorT = editor;
+                                this.editorT.setContent(this.content);
+                                if (this.readonly) {
+                                    this.editorT.setMode('readonly');
+                                } else {
+                                    this.editorT.setMode('design');
+                                }
+                            });
+                        }else{
+                            editor.ui.registry.addButton('screenload', {
+                                icon: 'fullscreen',
+                                tooltip: '全屏',
+                                onAction: () => {
+                                    this.content = editor.getContent();
+                                    this.transfer = true;
+                                    this.initTransfer();
+                                }
+                            });
+                            editor.ui.registry.addMenuItem('screenload', {
+                                text: '全屏',
+                                onAction: () => {
+                                    this.content = editor.getContent();
+                                    this.transfer = true;
+                                    this.initTransfer();
+                                }
+                            });
+                            editor.on('Init', (e) => {
+                                this.spinShow = false;
+                                this.editor = editor;
+                                this.editor.setContent(this.content);
+                                if (this.readonly) {
+                                    this.editor.setMode('readonly');
+                                } else {
+                                    this.editor.setMode('design');
+                                }
+                                this.$emit('editorInit', this.editor);
+                            });
+                            editor.on('KeyUp', (e) => {
+                                if (this.editor !== null) {
+                                    this.submitNewContent();
+                                }
+                            });
+                            editor.on('Change', (e) => {
+                                if (this.editor !== null) {
+                                    if (this.getContent() !== this.value) {
+                                        this.submitNewContent();
+                                    }
+                                    this.$emit('editorChange', e);
+                                }
+                            });
+                        }
+                    },
+                };
+            },
+
+            closeFull() {
+                this.content = this.getContent();
+                this.$emit('input', this.content);
+                this.transfer = false;
+                if (this.editorT != null) {
+                    this.editorT.destroy();
+                    this.editorT = null;
+                }
+            },
+
+            transferChange(visible) {
+                if (!visible && this.editorT != null) {
+                    this.content = this.editorT.getContent();
+                    this.$emit('input', this.content);
+                    this.editorT.destroy();
+                    this.editorT = null;
+                }
+            },
+
+            getEditor() {
+                return this.transfer ? this.editorT : this.editor;
+            },
+
+            concatAssciativeArrays(array1, array2) {
+                if (array2.length === 0) return array1;
+                if (array1.length === 0) return array2;
+                let dest = [];
+                for (let key in array1) {
+                    if (array1.hasOwnProperty(key)) {
+                        dest[key] = array1[key];
+                    }
+                }
+                for (let key in array2) {
+                    if (array2.hasOwnProperty(key)) {
+                        dest[key] = array2[key];
+                    }
+                }
+                return dest;
+            },
+
+            submitNewContent() {
+                this.isTyping = true;
+                if (this.checkerTimeout !== null) {
+                    clearTimeout(this.checkerTimeout);
+                }
+                this.checkerTimeout = setTimeout(() => {
+                    this.isTyping = false;
+                }, 300);
+                this.$emit('input', this.getContent());
+            },
+
+            insertContent(content) {
+                if (this.getEditor() !== null) {
+                    this.getEditor().insertContent(content);
+                }else{
+                    this.content+= content;
+                }
+            },
+
+            getContent() {
+                if (this.getEditor() === null) {
+                    return "";
+                }
+                return this.getEditor().getContent();
+            },
+
+            insertImage(src) {
+                this.insertContent('<img src="' + src + '">');
+            },
+
+            editorImage(lists) {
+                for (let i = 0; i < lists.length; i++) {
+                    let item = lists[i];
+                    if (typeof item === 'object' && typeof item.url === "string") {
+                        this.insertImage(item.url);
+                    }
+                }
+            },
+        }
+    }
+</script>

+ 2 - 1
resources/assets/js/main/components/WContent.vue

@@ -12,7 +12,8 @@
         right: 0;
         bottom: 0;
         overflow: auto;
-        background-image: url("../../../statics/images/bg1.jpg");
+        background: url("../../../statics/images/bg1.jpg") no-repeat center;
+        background-size: cover;
 
         .w-container {
             min-height: 500px;

+ 205 - 47
resources/assets/js/main/components/WHeader.vue

@@ -4,27 +4,80 @@
             <div class="w-header-row-left">
                 <ul>
                     <li :class="value==='todo'?'active':''">
-                        <a href="javascript:void(0)" @click="tabPage('todo')"><i class="ft icon">&#xe89e;</i>待办</a>
+                        <a href="javascript:void(0)" @click="tabPage('todo')"><i class="ft icon">&#xe89e;</i>{{$L('待办')}}</a>
                     </li><li :class="value==='project'?'active':''">
-                        <a href="javascript:void(0)" @click="tabPage('project')"><i class="ft icon">&#xe6b8;</i>项目</a>
+                        <a href="javascript:void(0)" @click="tabPage('project')"><i class="ft icon">&#xe6b8;</i>{{$L('项目')}}</a>
                     </li><li :class="value==='doc'?'active':''">
-                        <a href="javascript:void(0)" @click="tabPage('doc')"><i class="ft icon">&#xe915;</i>知识库</a>
+                        <a href="javascript:void(0)" @click="tabPage('doc')"><i class="ft icon">&#xe915;</i>{{$L('知识库')}}</a>
                     </li><li :class="value==='team'?'active':''">
-                        <a href="javascript:void(0)" @click="tabPage('team')"><i class="ft icon">&#xe90d;</i>同事</a>
+                        <a href="javascript:void(0)" @click="tabPage('team')"><i class="ft icon">&#xe90d;</i>{{$L('同事')}}</a>
                     </li>
                 </ul>
             </div>
             <div class="w-header-row-flex"></div>
             <div class="w-header-row-right">
-                <div class="user-info">
-                    <span class="username">欢迎您,{{userInfo.username || "尊敬的会员"}}!</span>
-                    <ul>
-                        <li><span class="ft hover">个人中心</span></li>
-                        <li @click="logout"><span class="ft hover">退出登录</span></li>
-                    </ul>
-                </div>
+                <Dropdown class="right-info" trigger="hover" @on-click="setRightSelect" placement="bottom-end" transfer>
+                   <div>
+                       <span class="username">{{$L('欢迎您')}}, {{(userInfo.nickname || userInfo.username) || $L('尊敬的会员')}}</span>
+                       <Icon type="md-arrow-dropdown"/>
+                   </div>
+                    <Dropdown-menu slot="list">
+                        <Dropdown-item name="user">{{$L('个人中心')}}</Dropdown-item>
+                        <Dropdown-item name="out">{{$L('退出登录')}}</Dropdown-item>
+                    </Dropdown-menu>
+                </Dropdown>
+                <Dropdown class="right-info" trigger="hover" @on-click="setLanguage" transfer>
+                    <div>
+                        <Icon class="right-globe" type="md-globe" size="24"/>
+                        <Icon type="md-arrow-dropdown"/>
+                    </div>
+                    <Dropdown-menu slot="list">
+                        <Dropdown-item name="zh" :selected="getLanguage() === 'zh'">中文</Dropdown-item>
+                        <Dropdown-item name="en" :selected="getLanguage() === 'en'">English</Dropdown-item>
+                    </Dropdown-menu>
+                </Dropdown>
             </div>
         </div>
+        <Drawer v-model="userDrawer" width="70%">
+            <Tabs value="name1">
+                <TabPane :label="$L('个人资料')" name="name1">
+                    <Form ref="formDatum" :model="formDatum" :rules="ruleDatum" :label-width="80">
+                        <FormItem :label="$L('头像')" prop="userimg">
+                            <ImgUpload v-model="formDatum.userimg"></ImgUpload>
+                        </FormItem>
+                        <FormItem :label="$L('昵称')" prop="nickname">
+                            <Input v-model="formDatum.nickname"></Input>
+                        </FormItem>
+                        <FormItem :label="$L('职位/职称')" prop="profession">
+                            <Input v-model="formDatum.profession"></Input>
+                        </FormItem>
+                        <FormItem>
+                            <Button :loading="loadIng > 0" type="primary" @click="handleSubmit('formDatum')">{{$L('提交')}}</Button>
+                            <Button :loading="loadIng > 0" @click="handleReset('formDatum')" style="margin-left: 8px">{{$L('重置')}}</Button>
+                        </FormItem>
+                    </Form>
+                </TabPane>
+                <!--<TabPane :label="$L('偏好设置')" name="name2"></TabPane>
+                <TabPane :label="$L('我创建的任务')" name="name3"></TabPane>-->
+                <TabPane :label="$L('账号密码')" name="name4">
+                    <Form ref="formPass" :model="formPass" :rules="rulePass" :label-width="100">
+                        <FormItem :label="$L('旧密码')" prop="oldpass">
+                            <Input v-model="formPass.oldpass"></Input>
+                        </FormItem>
+                        <FormItem :label="$L('新密码')" prop="newpass">
+                            <Input v-model="formPass.newpass"></Input>
+                        </FormItem>
+                        <FormItem :label="$L('确认新密码')" prop="checkpass">
+                            <Input v-model="formPass.checkpass"></Input>
+                        </FormItem>
+                        <FormItem>
+                            <Button :loading="loadIng > 0" type="primary" @click="handleSubmit('formPass')">{{$L('提交')}}</Button>
+                            <Button :loading="loadIng > 0" @click="handleReset('formPass')" style="margin-left: 8px">{{$L('重置')}}</Button>
+                        </FormItem>
+                    </Form>
+                </TabPane>
+            </Tabs>
+        </Drawer>
     </div>
 </template>
 
@@ -59,7 +112,6 @@
                 overflow-x: auto;
                 -webkit-backface-visibility: hidden;
                 -webkit-overflow-scrolling: touch;
-                -webkit-perspective: 1000;
                 li {
                     line-height: 40px;
                     color: #fff;
@@ -88,39 +140,14 @@
                 white-space: nowrap;
                 text-align: right;
                 line-height: 40px;
-                .user-info {
+                .right-info {
                     display: inline-block;
                     position: relative;
-                    margin-right: 6px;
+                    margin-left: 12px;
                     cursor: pointer;
-                    &:hover {
-                        color: #f0f0f0 !important;
-                        ul {
-                            display: block;
-                        }
-                    }
-                    ul {
-                        display: none;
-                        position: absolute;
-                        background: #fff;
-                        border: 1px solid #eee;
-                        right: 0;
-                        top: 38px;
-                        width: 84px;
-                        text-align: center;
-                        border-radius: 2px;
-                        li {
-                            height: 36px;
-                            line-height: 36px;
-                            color: #666;
-                            border-bottom: 1px solid #eee;
-                            &:last-child {
-                                border-bottom: 0;
-                            }
-                            &:hover {
-                                color: #0396f2;
-                            }
-                        }
+                    .right-globe {
+                        vertical-align: top;
+                        margin-top: 8px;
                     }
                 }
             }
@@ -128,32 +155,163 @@
     }
 </style>
 <script>
+    import ImgUpload from "./ImgUpload";
     export default {
         name: 'WHeader',
+        components: {ImgUpload},
         props: {
             value: {
             },
         },
         data() {
             return {
-                userInfo: $A.jsonParse($A.storage("userInfo")),
+                loadIng: 0,
+                userInfo: {},
+                userDrawer: false,
+
+                formDatum: {
+                    userimg: '',
+                    nickname: '',
+                    profession: ''
+                },
+                ruleDatum: { },
+
+                formPass: {
+                    oldpass: '',
+                    newpass: '',
+                    checkpass: '',
+                },
+                rulePass: { }
             }
         },
+        created() {
+            this.ruleDatum = {
+                nickname: [
+                    { required: true, message: this.$L('请输入昵称'), trigger: 'blur' },
+                    { type: 'string', min: 2, message: this.$L('昵称长度至少2位'), trigger: 'blur' }
+                ]
+            };
+            this.rulePass = {
+                oldpass: [
+                    { required: true, message: this.$L('请输入旧密码'), trigger: 'blur' },
+                    { type: 'string', min: 6, message: this.$L('密码长度至少6位'), trigger: 'blur' }
+                ],
+                newpass: [
+                    {
+                        validator: (rule, value, callback) => {
+                            if (value === '') {
+                                callback(new Error(this.$L('请输入新密码')));
+                            } else {
+                                if (this.formPass.checkpass !== '') {
+                                    this.$refs.formPass.validateField('checkpass');
+                                }
+                                callback();
+                            }
+                        },
+                        required: true,
+                        trigger: 'blur'
+                    },
+                    { type: 'string', min: 6, message: this.$L('密码长度至少6位'), trigger: 'blur' }
+                ],
+                checkpass: [
+                    {
+                        validator: (rule, value, callback) => {
+                            if (value === '') {
+                                callback(new Error(this.$L('请输入确认新密码')));
+                            } else if (value !== this.formPass.newpass) {
+                                callback(new Error(this.$L('两次密码输入不一致!')));
+                            } else {
+                                callback();
+                            }
+                        },
+                        required: true,
+                        trigger: 'blur'
+                    }
+                ],
+            };
+        },
         mounted() {
-
+            this.userInfo = $A.getUserInfo((res) => {
+                this.userInfo = res;
+                this.$set(this.formDatum, 'userimg', res.userimg)
+                this.$set(this.formDatum, 'nickname', res.nickname)
+                this.$set(this.formDatum, 'profession', res.profession)
+            }, true);
         },
         methods: {
             tabPage(path) {
                 this.goForward({path: '/' + path});
             },
+            setRightSelect(act) {
+                switch (act) {
+                    case 'user':
+                        this.userDrawer = true;
+                        break;
+
+                    case 'out':
+                        this.logout();
+                        break;
+                }
+            },
             logout() {
                 this.$Modal.confirm({
-                    title: '退出登录',
-                    content: '<p>您确定要退出登录吗?</p>',
+                    title: this.$L('退出登录'),
+                    content: this.$L('您确定要退出登录吗?'),
                     onOk: () => {
-                        this.goForward({path: '/'}, true);
+                        $A.userLogout();
                     },
                 });
+            },
+            handleSubmit(name) {
+                this.$refs[name].validate((valid) => {
+                    if (valid) {
+                        switch (name) {
+                            case "formDatum": {
+                                this.loadIng++;
+                                $A.aAjax({
+                                    url: 'users/editdata',
+                                    data: this.formDatum,
+                                    complete: () => {
+                                        this.loadIng--;
+                                    },
+                                    success: (res) => {
+                                        if (res.ret === 1) {
+                                            $A.getUserInfo(true);
+                                            this.$Message.success(this.$L('修改成功'));
+                                        } else {
+                                            this.$Modal.error({title: this.$L('温馨提示'), content: res.msg });
+                                        }
+                                    }
+                                });
+                                break;
+                            }
+                            case "formPass": {
+                                this.loadIng++;
+                                $A.aAjax({
+                                    url: 'users/editpass',
+                                    data: this.formPass,
+                                    complete: () => {
+                                        this.loadIng--;
+                                    },
+                                    success: (res) => {
+                                        if (res.ret === 1) {
+                                            this.$Message.success(this.$L('修改成功,请重新登录!'));
+                                            this.userDrawer = false;
+                                            this.$refs[name].resetFields();
+                                            $A.userLogout();
+                                        } else {
+                                            this.$Modal.error({title: this.$L('温馨提示'), content: res.msg });
+                                        }
+                                    }
+                                });
+                                break;
+                            }
+                        }
+                    }
+                })
+            },
+            handleReset(name) {
+                this.$refs[name].resetFields();
             }
         },
     }

+ 123 - 0
resources/assets/js/main/components/WSpinner.vue

@@ -0,0 +1,123 @@
+<template>
+    <div class="w-spinner">
+        <svg viewBox="25 25 50 50" class="w-circular"><circle cx="50" cy="50" r="20" fill="none" stroke-width="5" stroke-miterlimit="10" class="w-path"></circle></svg>
+    </div>
+</template>
+
+<style lang="scss" scoped>
+    .w-spinner {
+        display: none;
+        position: fixed;
+        z-index: 9999;
+        bottom: 20px;
+        right: 20px;
+        margin: 0 auto;
+        width: 30px;
+        height: 30px;
+        .w-circular {
+            -webkit-animation: rotate 2s linear infinite;
+            animation: rotate 2s linear infinite;
+            height: 100%;
+            -webkit-transform-origin: center center;
+            transform-origin: center center;
+            width: 100%;
+            position: absolute;
+            top: 0;
+            bottom: 0;
+            left: 0;
+            right: 0;
+            margin: auto;
+            overflow: hidden;
+            .w-path {
+                stroke-dasharray: 1,200;
+                stroke-dashoffset: 0;
+                -webkit-animation: dash 1.5s ease-in-out infinite,color 6s ease-in-out infinite;
+                animation: dash 1.5s ease-in-out infinite,color 6s ease-in-out infinite;
+                stroke-linecap: round;
+            }
+        }
+        @-webkit-keyframes rotate {
+            to {
+                -webkit-transform: rotate(1turn);
+                transform: rotate(1turn)
+            }
+        }
+        @keyframes rotate {
+            to {
+                -webkit-transform: rotate(1turn);
+                transform: rotate(1turn)
+            }
+        }
+        @-webkit-keyframes dash {
+            0% {
+                stroke-dasharray: 1,200;
+                stroke-dashoffset: 0
+            }
+
+            50% {
+                stroke-dasharray: 89,200;
+                stroke-dashoffset: -35
+            }
+
+            to {
+                stroke-dasharray: 89,200;
+                stroke-dashoffset: -124
+            }
+        }
+        @keyframes dash {
+            0% {
+                stroke-dasharray: 1,200;
+                stroke-dashoffset: 0
+            }
+
+            50% {
+                stroke-dasharray: 89,200;
+                stroke-dashoffset: -35
+            }
+
+            to {
+                stroke-dasharray: 89,200;
+                stroke-dashoffset: -124
+            }
+        }
+        @-webkit-keyframes color {
+            0%,to {
+                stroke: #d62d20
+            }
+
+            40% {
+                stroke: #0057e7
+            }
+
+            66% {
+                stroke: #008744
+            }
+
+            80%,90% {
+                stroke: #ffa700
+            }
+        }
+        @keyframes color {
+            0%,to {
+                stroke: #d62d20
+            }
+
+            40% {
+                stroke: #0057e7
+            }
+
+            66% {
+                stroke: #008744
+            }
+
+            80%,90% {
+                stroke: #ffa700
+            }
+        }
+    }
+</style>
+<script>
+    export default {
+        name: 'WSpinner',
+    }
+</script>

+ 179 - 58
resources/assets/js/main/main.js

@@ -1,59 +1,180 @@
-import Vue from 'vue'
-import App from './App.vue'
-import routes from './routes'
-import VueRouter from 'vue-router'
-import ViewUI from 'view-design';
-import Language from '../_modules/language'
-
-import '../common'
-
-Vue.use(VueRouter);
-Vue.use(ViewUI);
-Vue.use(Language);
-
-import Title from '../_components/Title.vue'
-Vue.component('VTitle', Title);
-
-const router = new VueRouter({routes});
-
-//进度条配置
-ViewUI.LoadingBar.config({
-    color: '#3fcc25',
-    failedColor: '#ff0000'
-});
-router.beforeEach((to, from, next) => {
-    ViewUI.LoadingBar.start();
-    next();
-});
-router.afterEach((to, from, next) => {
-    ViewUI.LoadingBar.finish();
-});
-
-//加载函数
-Vue.prototype.goForward = function(location, isReplace) {
-    if (typeof location === 'string') location = {name: location};
-    if (isReplace === true) {
-        this.$router.replace(location);
-    }else{
-        this.$router.push(location);
-    }
-};
-
-//返回函数
-Vue.prototype.goBack = function(number) {
-    window.history.go(typeof number==='number'?number:-1)
-};
-
-Vue.prototype.$A = $A;
-
-Vue.config.productionTip = false;
-
-const app = new Vue({
-    el: '#app',
-    router,
-    template: '<App/>',
-    components: { App }
-});
-
-$A.app = app;
+/**
+ * 页面专用
+ */
 
+import '../../sass/main.scss';
+
+(function (window) {
+
+    let apiUrl = window.location.origin + '/api/';
+    let $ = window.$A;
+
+    $.extend({
+
+        fillUrl(str) {
+            if (str.substring(0, 2) === "//" ||
+                str.substring(0, 7) === "http://" ||
+                str.substring(0, 8) === "https://" ||
+                str.substring(0, 6) === "ftp://" ||
+                str.substring(0, 1) === "/") {
+                return str;
+            }
+            return window.location.origin + '/' + str;
+        },
+
+        aUrl(str) {
+            if (str.substring(0, 2) === "//" ||
+                str.substring(0, 7) === "http://" ||
+                str.substring(0, 8) === "https://" ||
+                str.substring(0, 6) === "ftp://" ||
+                str.substring(0, 1) === "/") {
+                return str;
+            }
+            return apiUrl + str;
+        },
+
+        aAjax(params) {
+            if (typeof params !== 'object') return false;
+            if (typeof params.success === 'undefined') params.success = () => { };
+            params.url = this.aUrl(params.url);
+            //
+            let beforeCall = params.beforeSend;
+            params.beforeSend = () => {
+                $A.aAjaxLoad++;
+                $A(".w-spinner").show();
+                //
+                if (typeof beforeCall == "function") {
+                    beforeCall();
+                }
+            };
+            //
+            let completeCall = params.complete;
+            params.complete = () => {
+                $A.aAjaxLoad--;
+                if ($A.aAjaxLoad <= 0) {
+                    $A(".w-spinner").hide();
+                }
+                //
+                if (typeof completeCall == "function") {
+                    completeCall();
+                }
+            };
+            //
+            let callback = params.success;
+            params.success = (data, status, xhr) => {
+                if (typeof data === 'object') {
+                    if (data.ret === -1 && params.checkRole !== false) {
+                        //身份丢失
+                        $A.app.$Modal.error({
+                            title: '温馨提示',
+                            content: data.msg,
+                            onOk: () => {
+                                $A.token("");
+                                $A.userLogout();
+                            }
+                        });
+                        return;
+                    }
+                    if (data.ret === -2 && params.role !== false) {
+                        //没有权限
+                        $A.app.$Modal.error({
+                            title: '权限不足',
+                            content: data.msg ? data.msg : "你没有相关的权限查看或编辑!"
+                        });
+                    }
+                }
+                if (typeof callback === "function") {
+                    callback(data, status, xhr);
+                }
+            };
+            //
+            $A.ajax(params);
+        },
+        aAjaxLoad: 0,
+
+        /**
+         * 编辑器参数配置
+         * @returns {{modules: {toolbar: *[]}}}
+         */
+        editorOption() {
+            return {
+                modules: {
+                    toolbar: [
+                        ['bold', 'italic'],
+                        [{ 'list': 'ordered'}, { 'list': 'bullet' }],
+                        [{ 'size': ['small', false, 'large', 'huge'] }],
+                        [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
+                        [{ 'color': [] }, { 'background': [] }],
+                        [{ 'align': [] }]
+                    ]
+                }
+            };
+        },
+
+        /**
+         * 获取用户信息(并保存)
+         * @param callback          网络请求获取到用户信息回调(监听用户信息发生变化)
+         * @param onlyListener      只监听不重新网络请求获取
+         * @returns Object
+         */
+        getUserInfo(callback, onlyListener) {
+            if (typeof callback === 'function' || (typeof callback === "boolean" && callback === true)) {
+                if (onlyListener === true) {
+                    typeof callback === "function" && callback($A.jsonParse($A.storage("userInfo")));
+                    $A.setOnUserInfoListener(callback);
+                } else {
+                    $A.aAjax({
+                        url: 'users/info',
+                        error: () => {
+                            this.userLogout();
+                        },
+                        success: (res) => {
+                            if (res.ret === 1) {
+                                $A.token(res.data.token);
+                                $A.storage("userInfo", res.data);
+                                $A.triggerUserInfoListener(res.data);
+                                //
+                                typeof callback === "function" && callback(res.data);
+                            }
+                            $A.setOnUserInfoListener(callback);
+                        }
+                    });
+                }
+            }
+            return $A.jsonParse($A.storage("userInfo"));
+        },
+
+        /**
+         * 打开登录页面
+         */
+        userLogout() {
+            $A.token("");
+            $A.storage("userInfo", {});
+            $A.triggerUserInfoListener({});
+            if (typeof $A.app === "object") {
+                $A.app.goForward({path: '/'}, true);
+            } else {
+                window.location.href = window.location.origin;
+            }
+        },
+
+        /**
+         * 监听用户信息发生变化
+         * @param callback
+         */
+        setOnUserInfoListener(callback) {
+            if (typeof callback === "function") {
+                $A.__userInfoListener.push(callback);
+            }
+        },
+        triggerUserInfoListener(userInfo) {
+            $A.__userInfoListener.forEach((callback) => {
+                typeof callback === "function" && callback(userInfo);
+            });
+        },
+        __userInfoListener: [],
+
+    });
+
+    window.$A = $;
+})(window);

+ 2 - 2
resources/assets/js/main/pages/doc.vue

@@ -1,14 +1,14 @@
 <template>
     <div class="w-main doc">
 
-        <v-title>知识库-轻量级的团队在线协作</v-title>
+        <v-title>{{$L('知识库')}}-{{$L('轻量级的团队在线协作')}}</v-title>
 
         <w-header value="doc"></w-header>
 
         <div class="w-nav">
             <div class="nav-row">
                 <div class="w-nav-left">
-                    <span class="new ft hover"><i class="ft icon"></i>新建知识库</span>
+                    <span class="new ft hover"><i class="ft icon"></i> {{$L('新建知识库')}}</span>
                 </div>
                 <div class="w-nav-flex"></div>
             </div>

+ 93 - 44
resources/assets/js/main/pages/index.vue

@@ -1,17 +1,32 @@
 <template>
     <div class="w-box index">
 
-        <v-title>轻量级的团队在线协作</v-title>
+        <v-title>{{$L('轻量级的团队在线协作')}}</v-title>
 
         <div class="header">
             <div class="z-row">
                 <div class="header-col-sub">
                     <h2>
                         <img src="../../../statics/images/logo4.png">
-                        <span>轻量级的团队在线协作</span>
+                        <span>{{$L('轻量级的团队在线协作')}}</span>
                     </h2>
                 </div>
-                <div class="z-1"></div>
+                <div class="z-1">
+                    <dl>
+                        <dd>
+                            <Dropdown class="right-info" trigger="hover" @on-click="setLanguage" transfer>
+                                <div>
+                                    <Icon class="right-globe" type="md-globe" size="26"/>
+                                    <Icon type="md-arrow-dropdown"/>
+                                </div>
+                                <Dropdown-menu slot="list">
+                                    <Dropdown-item name="zh" :selected="getLanguage() === 'zh'">中文</Dropdown-item>
+                                    <Dropdown-item name="en" :selected="getLanguage() === 'en'">English</Dropdown-item>
+                                </Dropdown-menu>
+                            </Dropdown>
+                        </dd>
+                    </dl>
+                </div>
             </div>
         </div>
 
@@ -28,9 +43,9 @@
                             </CarouselItem>
                         </Carousel>
                     </div>
-                    <div class="z-8"><h3>酷团队协作工具就从这里开始</h3>
+                    <div class="z-8"><h3>{{$L('酷团队协作工具就从这里开始')}}</h3>
                         <div class="bl inline-block">
-                            <span class="start" @click="loginShow=true">立即登陆</span>
+                            <span class="start" @click="loginShow=true">{{$L('立即登陆')}}</span>
                         </div>
                     </div>
                 </div>
@@ -38,36 +53,36 @@
             <div class="second">
                 <div class="bg"></div>
                 <div class="z-row">
-                    <div class="z-6"><a href="#W_link1"><i class="ft icon">&#xe753;</i>待办四象限</a></div>
-                    <div class="z-6"><a href="#W_link2"><i class="ft icon">&#xe6b8;</i>项目管理</a></div>
-                    <div class="z-6"><a href="#W_link3"><i class="ft icon">&#xe915;</i>在线知识库</a></div>
-                    <div class="z-6"><a href="#W_link4"><i class="ft icon">&#xe706;</i>日程管理</a></div>
+                    <div class="z-6"><a href="#W_link1"><i class="ft icon">&#xe753;</i>{{$L('待办四象限')}}</a></div>
+                    <div class="z-6"><a href="#W_link2"><i class="ft icon">&#xe6b8;</i>{{$L('项目管理')}}</a></div>
+                    <div class="z-6"><a href="#W_link3"><i class="ft icon">&#xe915;</i>{{$L('在线知识库')}}</a></div>
+                    <div class="z-6"><a href="#W_link4"><i class="ft icon">&#xe706;</i>{{$L('日程管理')}}</a></div>
                 </div>
             </div>
         </div>
 
         <div class="z-row block">
             <div class="z-6">
-                <div class="wrap-left" id="W_link1"><i class="ft icon">&#xe753;</i>待办四象限:突出事情优先级,帮助员工合理安排时间,提高工作效率。</div>
+                <div class="wrap-left" id="W_link1"><i class="ft icon">&#xe753;</i>{{$L('待办四象限:突出事情优先级,帮助员工合理安排时间,提高工作效率。')}}</div>
             </div>
             <div class="z-18"><img src="../../../statics/images/index/todo.jpg"/></div>
         </div>
         <div class="z-row block">
             <div class="z-18"><img src="../../../statics/images/index/project.jpg"/></div>
             <div class="z-6">
-                <div class="wrap-right" id="W_link2"><i class="ft icon">&#xe6b8;</i>项目管理:自定义项目看板,可视化任务安排。</div>
+                <div class="wrap-right" id="W_link2"><i class="ft icon">&#xe6b8;</i>{{$L('项目管理:自定义项目看板,可视化任务安排。')}}</div>
             </div>
         </div>
         <div class="z-row block">
             <div class="z-6">
-                <div class="wrap-left" id="W_link3"><i class="ft icon">&#xe915;</i>在线知识库:在线流程图,在线文档,以及可视化的目录编排,文档管理无忧。</div>
+                <div class="wrap-left" id="W_link3"><i class="ft icon">&#xe915;</i>{{$L('在线知识库:在线流程图,在线文档,以及可视化的目录编排,文档管理无忧。')}}</div>
             </div>
             <div class="z-18"><img src="../../../statics/images/index/wiki.jpg"/></div>
         </div>
         <div class="z-row block">
             <div class="z-18"><img src="../../../statics/images/index/week.jpg"/></div>
             <div class="z-6">
-                <div class="wrap-right" id="W_link4"><i class="ft icon">&#xe706;</i>日程管理:可视化日程管理,快速搞定工作计划,了解工作宏观安排</div>
+                <div class="wrap-right" id="W_link4"><i class="ft icon">&#xe706;</i>{{$L('日程管理:可视化日程管理,快速搞定工作计划,了解工作宏观安排。')}}</div>
             </div>
         </div>
 
@@ -75,24 +90,24 @@
 
         <Modal
             v-model="loginShow"
-            title="用户登录"
+            :title="$L('用户登录')"
             :closable="false"
             :mask-closable="false">
-            <Form ref="login" :model="formInline" :rules="ruleInline">
+            <Form ref="login" :model="formLogin" :rules="ruleLogin">
                 <FormItem prop="username">
-                    <Input type="text" v-model="formInline.username" placeholder="用户名">
+                    <Input type="text" v-model="formLogin.username" :placeholder="$L('用户名')">
                         <Icon type="ios-person-outline" slot="prepend"></Icon>
                     </Input>
                 </FormItem>
-                <FormItem prop="password">
-                    <Input type="password" v-model="formInline.password" placeholder="密码">
+                <FormItem prop="userpass">
+                    <Input type="password" v-model="formLogin.userpass" :placeholder="$L('密码')">
                         <Icon type="ios-lock-outline" slot="prepend"></Icon>
                     </Input>
                 </FormItem>
             </Form>
             <div slot="footer">
-                <Button type="default" @click="loginShow=false">取消</Button>
-                <Button type="primary" :loading="loadIng > 0" @click="onLogin">登录</Button>
+                <Button type="default" @click="loginShow=false">{{$L('取消')}}</Button>
+                <Button type="primary" :loading="loadIng > 0" @click="onLogin">{{$L('登录')}}</Button>
             </div>
         </Modal>
     </div>
@@ -144,11 +159,25 @@
                         }
                     }
                 }
-                .header-1 {
-                    flex-grow: 1;
-                    flex-shrink: 1;
-                    flex-basis: 0;
-                    max-width: 100%;
+                .z-1 {
+                    dl {
+                        position: absolute;
+                        right: 20px;
+                        top: 0;
+                        font-size: 14px;
+                        dd {
+                            line-height: 50px;
+                            color: #fff;
+                            cursor: pointer;
+                            margin-right: 1px;
+                            .right-info {
+                                cursor: pointer;
+                                .right-globe {
+                                    vertical-align: middle;
+                                }
+                            }
+                        }
+                    }
                 }
             }
         }
@@ -294,21 +323,24 @@
             return {
                 loadIng: 0,
                 loginShow: false,
-                formInline: {
+                formLogin: {
                     username: '',
-                    password: ''
+                    userpass: ''
                 },
-                ruleInline: {
-                    username: [
-                        { required: true, message: '请填写用户名!', trigger: 'blur' }
-                    ],
-                    password: [
-                        { required: true, message: '请填写登录密码!', trigger: 'blur' },
-                        { type: 'string', min: 6, message: '密码长度不能少于6位!', trigger: 'blur' }
-                    ]
-                }
+                ruleLogin: {}
             }
         },
+        created() {
+            this.ruleLogin = {
+                username: [
+                    { required: true, message: this.$L('请填写用户名!'), trigger: 'blur' }
+                ],
+                userpass: [
+                    { required: true, message: this.$L('请填写登录密码!'), trigger: 'blur' },
+                    { type: 'string', min: 6, message: this.$L('密码长度不能少于6位!'), trigger: 'blur' }
+                ]
+            };
+        },
         mounted() {
 
         },
@@ -323,14 +355,31 @@
                 this.$refs.login.validate((valid) => {
                     if (valid) {
                         this.loadIng++;
-                        setTimeout(() => {
-                            this.loadIng--;
-                            this.$Message.success('登录成功');
-                            this.loginShow = false;
-                            this.goForward({path: '/todo'}, true);
-                        }, 3000);
-                    } else {
-                        this.$Message.error('请填写正确的信息!');
+                        $A.ajax({
+                            url: $A.urlApi('users/login'),
+                            data: this.formLogin,
+                            complete: () => {
+                                this.loadIng--;
+                            },
+                            success: (res) => {
+                                if (res.ret === 1) {
+                                    $A.token(res.data.token);
+                                    $A.storage("userInfo", res.data);
+                                    $A.triggerUserInfoListener(res.data);
+                                    //
+                                    this.loadIng--;
+                                    this.loginShow = false;
+                                    this.$refs.login.resetFields();
+                                    this.$Message.success(this.$L('登录成功'));
+                                    this.goForward({path: '/todo'}, true);
+                                } else {
+                                    this.$Modal.error({
+                                        title: "温馨提示",
+                                        content: res.msg
+                                    });
+                                }
+                            }
+                        })
                     }
                 })
             }

+ 5 - 5
resources/assets/js/main/pages/project.vue

@@ -1,20 +1,20 @@
 <template>
     <div class="w-main project">
 
-        <v-title>项目-轻量级的团队在线协作</v-title>
+        <v-title>{{$L('项目')}}-{{$L('轻量级的团队在线协作')}}</v-title>
 
         <w-header value="project"></w-header>
 
         <div class="w-nav">
             <div class="nav-row">
                 <div class="w-nav-left">
-                    <span class="ft hover"><i class="ft icon"></i>新建项目</span>
+                    <span class="ft hover"><i class="ft icon"></i> {{$L('新建项目')}}</span>
                 </div>
                 <div class="w-nav-flex"></div>
                 <div class="w-nav-right">
-                    <span class="ft hover"><i class="ft icon"></i> 收藏的项目</span>
-                    <span class="ft hover"><i class="ft icon"></i> 参与的项目</span>
-                    <span class="ft hover"><i class="ft icon"></i> 我创建的项目</span>
+                    <span class="ft hover"><i class="ft icon"></i> {{$L('收藏的项目')}}</span>
+                    <span class="ft hover"><i class="ft icon"></i> {{$L('参与的项目')}}</span>
+                    <span class="ft hover"><i class="ft icon"></i> {{$L('我创建的项目')}}</span>
                 </div>
             </div>
         </div>

+ 3 - 3
resources/assets/js/main/pages/team.vue

@@ -1,18 +1,18 @@
 <template>
     <div class="w-main team">
 
-        <v-title>团队-轻量级的团队在线协作</v-title>
+        <v-title>{{$L('团队')}}-{{$L('轻量级的团队在线协作')}}</v-title>
 
         <w-header value="team"></w-header>
 
         <div class="w-nav">
             <div class="nav-row">
                 <div class="w-nav-left">
-                    <i class="ft icon"></i>同事列表
+                    <i class="ft icon"></i> {{$L('同事列表')}}
                 </div>
                 <div class="w-nav-flex"></div>
                 <div class="w-nav-right">
-                    <span class="ft hover"><i class="ft icon"></i>添加同事</span>
+                    <span class="ft hover"><i class="ft icon"></i> {{$L('添加同事')}}</span>
                 </div>
             </div>
         </div>

+ 26 - 24
resources/assets/js/main/pages/todo.vue

@@ -1,21 +1,21 @@
 <template>
     <div class="w-main todo">
 
-        <v-title>待办-轻量级的团队在线协作</v-title>
+        <v-title>{{$L('待办')}}-{{$L('轻量级的团队在线协作')}}</v-title>
 
         <w-header value="todo"></w-header>
 
         <div class="w-nav">
             <div class="nav-row">
                 <div class="w-nav-left">
-                    <i class="ft icon">&#xE787;</i>我的待办
+                    <i class="ft icon">&#xE787;</i> {{$L('我的待办')}}
                 </div>
                 <div class="w-nav-flex"></div>
                 <div class="w-nav-right">
-                    <span class="ft hover"><i class="ft icon">&#xE706;</i>待办日程</span>
-                    <span class="ft hover"><i class="ft icon">&#xE73D;</i>已完成的任务</span>
-                    <span class="ft hover"><i class="ft icon">&#xE748;</i>我关注的任务</span>
-                    <span class="ft hover"><i class="ft icon">&#xE743;</i>周报/日报</span>
+                    <span class="ft hover"><i class="ft icon">&#xE706;</i> {{$L('待办日程')}}</span>
+                    <span class="ft hover"><i class="ft icon">&#xE73D;</i> {{$L('已完成的任务')}}</span>
+                    <span class="ft hover"><i class="ft icon">&#xE748;</i> {{$L('我关注的任务')}}</span>
+                    <span class="ft hover"><i class="ft icon">&#xE743;</i> {{$L('周报/日报')}}</span>
                 </div>
             </div>
         </div>
@@ -26,24 +26,24 @@
                     <li>
                         <div class="todo-card">
                             <div class="todo-card-head p1">
-                                <i class="ft icon flag"></i>
-                                <div class="todo-card-title">重要且紧急</div>
-                                <i class="ft icon close"></i>
+                                <i class="ft icon flag">&#xE753;</i>
+                                <div class="todo-card-title">{{$L('重要且紧急')}}</div>
+                                <i class="ft icon close">&#xE740;</i>
                             </div>
                             <div class="todo-card-content">
-                                <div class="empty">恭喜你!已完成了所有待办</div>
+                                <div class="empty">{{$L('恭喜你!已完成了所有待办')}}</div>
                             </div>
                         </div>
                     </li>
                     <li>
                         <div class="todo-card">
                             <div class="todo-card-head p2">
-                                <i class="ft icon flag"></i>
-                                <div class="todo-card-title">重要不紧急</div>
-                                <i class="ft icon close"></i>
+                                <i class="ft icon flag">&#xE753;</i>
+                                <div class="todo-card-title">{{$L('重要不紧急')}}</div>
+                                <i class="ft icon close">&#xE740;</i>
                             </div>
                             <div class="todo-card-content">
-                                <div class="empty">恭喜你!已完成了所有待办</div>
+                                <div class="empty">{{$L('恭喜你!已完成了所有待办')}}</div>
                             </div>
                         </div>
                     </li>
@@ -52,24 +52,24 @@
                     <li>
                         <div class="todo-card">
                             <div class="todo-card-head p3">
-                                <i class="ft icon flag"></i>
-                                <div class="todo-card-title">紧急不重要</div>
-                                <i class="ft icon close"></i>
+                                <i class="ft icon flag">&#xE753;</i>
+                                <div class="todo-card-title">{{$L('紧急不重要')}}</div>
+                                <i class="ft icon close">&#xE740;</i>
                             </div>
                             <div class="todo-card-content">
-                                <div class="empty">恭喜你!已完成了所有待办</div>
+                                <div class="empty">{{$L('恭喜你!已完成了所有待办')}}</div>
                             </div>
                         </div>
                     </li>
                     <li>
                         <div class="todo-card">
                             <div class="todo-card-head p4">
-                                <i class="ft icon flag"></i>
-                                <div class="todo-card-title">不重要不紧急</div>
-                                <i class="ft icon close"></i>
+                                <i class="ft icon flag">&#xE753;</i>
+                                <div class="todo-card-title">{{$L('不重要不紧急')}}</div>
+                                <i class="ft icon close">&#xE740;</i>
                             </div>
                             <div class="todo-card-content">
-                                <div class="empty">恭喜你!已完成了所有待办</div>
+                                <div class="empty">{{$L('恭喜你!已完成了所有待办')}}</div>
                             </div>
                         </div>
                     </li>
@@ -168,11 +168,13 @@
         components: {WContent, WHeader},
         data () {
             return {
-
+                userInfo: {},
             }
         },
         mounted() {
-
+            this.userInfo = $A.getUserInfo((res) => {
+                this.userInfo = res;
+            });
         },
         computed: {
 

+ 0 - 144
resources/assets/sass/app.scss

@@ -30,147 +30,3 @@ dd, dl, dt, li, ol, ul {
     padding: 0;
     list-style: none;
 }
-
-.z-row {
-    text-rendering: optimizespeed;
-    display: flex;
-    align-items: stretch;
-    flex-flow: row wrap
-}
-
-.z-row.gap .z-1,.z-row.gap .z-3,.z-row.gap .z-4,.z-row.gap .z-6,.z-row.gap .z-8,.z-row.gap .z-12,.z-row.gap .z-16,.z-row.gap .z-18,.z-row.gap .z-20,.z-row.gap .z-21,.z-row.gap .z-col {
-    padding: .35rem .5rem
-}
-
-.z-row>.z-row {
-    width: 100%
-}
-
-.z-1,.z-3,.z-4,.z-6,.z-8,.z-12,.z-16,.z-18,.z-20,.z-21,.z-col {
-    display: inline-block;
-    zoom: 1;
-    box-sizing: border-box;
-    vertical-align: top;
-    text-rendering: auto
-}
-
-.z-1 {
-    flex-grow: 1;
-    flex-shrink: 1;
-    flex-basis: 0;
-    max-width: 100%
-}
-
-.z-3 {
-    width: 12.5%;
-    max-width: 12.5%
-}
-
-.z-4 {
-    width: 16.6667%;
-    max-width: 16.6667%
-}
-
-.z-6 {
-    width: 25%
-}
-
-.z-8 {
-    width: 33.3333%
-}
-
-.z-12 {
-    width: 50%
-}
-
-.z-16 {
-    width: 66.6667%
-}
-
-.z-18 {
-    width: 75%
-}
-
-.z-20 {
-    width: 83.3333%
-}
-
-.z-21 {
-    width: 87.5%
-}
-
-.w-box {
-    -webkit-box-sizing: content-box;
-    box-sizing: content-box;
-    * {
-        -webkit-box-sizing: content-box;
-        box-sizing: content-box;
-    }
-}
-
-.w-main {
-    position: absolute;
-    color: #000000;
-    top: 0;
-    left: 0;
-    min-width: 100%;
-    min-height: 100%;
-    padding: 0;
-    margin: 0;
-    background: no-repeat center;
-    background-size: cover;
-
-    .w-nav {
-        position: fixed;
-        left: 0;
-        right: 0;
-        top: 40px;
-        font-size: 14px;
-        background-color: rgba(255, 255, 255, 0.9);
-        z-index: 12;
-        color: #606266;
-        height: 32px;
-        line-height: 34px;
-        .nav-row {
-            margin: 0 32px;
-            display: flex;
-            flex-direction: row;
-            align-items: center;
-            &:before,
-            &:after {
-                display: table;
-                content: "";
-            }
-            &:after {
-                clear: both;
-            }
-            span {
-                margin: 0 12px 0 0;
-                cursor: pointer;
-            }
-            span + span {
-                padding-left: 12px;
-                border-left: 1px solid #ccc;
-            }
-            .icon {
-                font-size: 16px;
-                margin-right: 2px;
-            }
-            .w-nav-left {
-                white-space: nowrap;
-                padding-right: 24px;
-            }
-            .w-nav-flex {
-                flex: 1;
-            }
-            .w-nav-right {
-                white-space: nowrap;
-                overflow: hidden;
-                overflow-x: auto;
-                -webkit-backface-visibility: hidden;
-                -webkit-overflow-scrolling: touch;
-                -webkit-perspective: 1000;
-            }
-        }
-    }
-}

+ 142 - 0
resources/assets/sass/main.scss

@@ -0,0 +1,142 @@
+
+.z-row {
+    text-rendering: optimizespeed;
+    display: flex;
+    align-items: stretch;
+    flex-flow: row wrap
+}
+
+.z-row.gap .z-1,.z-row.gap .z-3,.z-row.gap .z-4,.z-row.gap .z-6,.z-row.gap .z-8,.z-row.gap .z-12,.z-row.gap .z-16,.z-row.gap .z-18,.z-row.gap .z-20,.z-row.gap .z-21,.z-row.gap .z-col {
+    padding: .35rem .5rem
+}
+
+.z-row>.z-row {
+    width: 100%
+}
+
+.z-1,.z-3,.z-4,.z-6,.z-8,.z-12,.z-16,.z-18,.z-20,.z-21,.z-col {
+    display: inline-block;
+    zoom: 1;
+    box-sizing: border-box;
+    vertical-align: top;
+    text-rendering: auto
+}
+
+.z-1 {
+    flex-grow: 1;
+    flex-shrink: 1;
+    flex-basis: 0;
+    max-width: 100%
+}
+
+.z-3 {
+    width: 12.5%;
+    max-width: 12.5%
+}
+
+.z-4 {
+    width: 16.6667%;
+    max-width: 16.6667%
+}
+
+.z-6 {
+    width: 25%
+}
+
+.z-8 {
+    width: 33.3333%
+}
+
+.z-12 {
+    width: 50%
+}
+
+.z-16 {
+    width: 66.6667%
+}
+
+.z-18 {
+    width: 75%
+}
+
+.z-20 {
+    width: 83.3333%
+}
+
+.z-21 {
+    width: 87.5%
+}
+
+.w-box {
+    -webkit-box-sizing: content-box;
+    box-sizing: content-box;
+    * {
+        -webkit-box-sizing: content-box;
+        box-sizing: content-box;
+    }
+}
+
+.w-main {
+    position: absolute;
+    color: #000000;
+    top: 0;
+    left: 0;
+    min-width: 100%;
+    min-height: 100%;
+    padding: 0;
+    margin: 0;
+
+    .w-nav {
+        position: fixed;
+        left: 0;
+        right: 0;
+        top: 40px;
+        font-size: 14px;
+        background-color: rgba(255, 255, 255, 0.9);
+        z-index: 12;
+        color: #606266;
+        height: 32px;
+        line-height: 34px;
+        .nav-row {
+            margin: 0 32px;
+            display: flex;
+            flex-direction: row;
+            align-items: center;
+            &:before,
+            &:after {
+                display: table;
+                content: "";
+            }
+            &:after {
+                clear: both;
+            }
+            span {
+                margin: 0 12px 0 0;
+                cursor: pointer;
+            }
+            span + span {
+                padding-left: 12px;
+                border-left: 1px solid #ccc;
+            }
+            .icon {
+                font-size: 16px;
+                margin-right: 2px;
+            }
+            .w-nav-left {
+                white-space: nowrap;
+                padding-right: 24px;
+            }
+            .w-nav-flex {
+                flex: 1;
+            }
+            .w-nav-right {
+                white-space: nowrap;
+                overflow: hidden;
+                overflow-x: auto;
+                -webkit-backface-visibility: hidden;
+                -webkit-overflow-scrolling: touch;
+                -webkit-perspective: 1000;
+            }
+        }
+    }
+}

BIN
resources/assets/statics/images/loading.png


+ 1 - 1
resources/views/main.blade.php

@@ -35,7 +35,7 @@
     </div>
 </div>
 
-<script src="{{ mix('js/main.js') }}?__={{ $version }}"></script>
+<script src="{{ mix('js/app.js') }}?__={{ $version }}"></script>
 
 </body>
 </html>

+ 3 - 1
routes/web.php

@@ -7,7 +7,9 @@ use App\Http\Middleware\ApiMiddleware;
  * 接口
  */
 Route::prefix('api')->middleware(ApiMiddleware::class)->group(function () {
-
+    //会员
+    Route::any('users/{method}',                    'Api\UsersController');
+    Route::any('users/{method}/{action}',           'Api\UsersController');
 });
 
 

+ 1 - 1
webpack.mix.js

@@ -16,7 +16,7 @@ const mix = require('laravel-mix');
 mix.copy('resources/assets/statics/public',     'public');
 
 //生成vue页面js
-mix.js('resources/assets/js/main/main.js',      'js/main.js');
+mix.js('resources/assets/js/main/app.js',       'js/app.js');
 
 //生成css样式文件
 mix.sass('resources/assets/sass/app.scss',      'css');