kuaifan 5 лет назад
Родитель
Сommit
43e1bc606d

+ 1 - 1
app/Http/Controllers/Api/ChatController.php

@@ -83,7 +83,7 @@ class ChatController extends Controller
         }
         $dialog = $res['data'];
         $lastMsg = Base::DBC2A(DB::table('chat_msg')
-            ->select('id')
+            ->select(['id'])
             ->where('did', $dialog['id'])
             ->orderByDesc('indate')
             ->orderByDesc('id')

+ 18 - 0
app/Model/DBCache.php

@@ -22,6 +22,7 @@ class DBCache
     protected $builder = null;
 
     private $__cache = true;               //启用缓存(默认启用)
+    private $__cacheKeyname = null;        //缓存名称(默认自动生成)
     private $__cacheMinutes = 1;           //缓存时间(分钟, 默认1分钟)
     private $__removeCache = false;        //删除缓存
     private $__join = [];
@@ -77,6 +78,18 @@ class DBCache
     }
 
     /**
+     * 缓存名称
+     *
+     * @param $name
+     * @return $this
+     */
+    public function cacheKeyname($name)
+    {
+        $this->__cacheKeyname = $name ?: null;
+        return $this;
+    }
+
+    /**
      * 缓存时间(分钟)
      *
      * @param $Minutes
@@ -228,6 +241,7 @@ class DBCache
      */
     private function initParameter() {
         $this->__cache = true;
+        $this->__cacheKeyname = null;
         $this->__cacheMinutes = 1;
         $this->__removeCache = false;
         //
@@ -250,6 +264,10 @@ class DBCache
      * @return string
      */
     private function identify($type, $attach = '') {
+        if ($this->__cacheKeyname) {
+            return $this->__cacheKeyname;
+        }
+        //
         $identify = $this->addEncode($this->attributes);
         $identify.= $this->addEncode($this->__join);
         $identify.= $this->addEncode($this->__take);

+ 6 - 3
app/Module/Chat.php

@@ -126,9 +126,12 @@ class Chat
                 break;
         }
         if ($lastText) {
-            $upArray = Base::DBUP([
-                ($dialog['recField'] == 1 ? 'unread1' : 'unread2') => 1,
-            ]);
+            $upArray = [];
+            if ($username != $receive) {
+                $upArray = Base::DBUP([
+                    ($dialog['recField'] == 1 ? 'unread1' : 'unread2') => 1,
+                ]);
+            }
             $upArray['lasttext'] = $lastText;
             $upArray['lastdate'] = $indate;
             if ($dialog['del1']) {

+ 202 - 48
app/Services/WebSocketService.php

@@ -2,8 +2,10 @@
 
 namespace App\Services;
 
+use App\Model\DBCache;
 use App\Module\Base;
 use App\Module\Chat;
+use App\Module\Users;
 use Cache;
 use DB;
 use Hhxsv5\LaravelS\Swoole\WebSocketHandlerInterface;
@@ -32,28 +34,30 @@ class WebSocketService implements WebSocketHandlerInterface
      */
     public function onOpen(Server $server, Request $request)
     {
+        global $_A;
+        $_A = [
+            '__static_langdata' => [],
+        ];
+        //
         $to = $request->fd;
         if (!isset($request->get['token'])) {
-            $server->push($to, Base::array2json([
+            $server->push($to, $this->formatMsgSend([
                 'messageType' => 'error',
                 'type' => 'user',
-                'sender' => null,
-                'target' => null,
                 'content' => [
                     'error' => '参数错误'
                 ],
-                'time' => Base::time()
             ]));
             $server->close($to);
-            self::forgetUser($to);
+            $this->forgetUser($to);
             return;
         }
         //
         $token = $request->get['token'];
-        $cacheKey = "ws-token:" . md5($token);
+        $cacheKey = "ws::token:" . md5($token);
         $username = Cache::remember($cacheKey, now()->addSeconds(1), function () use ($token) {
             list($id, $username, $encrypt, $timestamp) = explode("@", base64_decode($token) . "@@@@");
-            if (intval($id) > 0 && intval($timestamp) + 2592000 > Base::time()) {
+            if (intval($id) > 0 && intval($timestamp) + 2592000 > time()) {
                 if (DB::table('users')->where(['id' => $id, 'username' => $username, 'encrypt' => $encrypt])->exists()) {
                     return $username;
                 }
@@ -62,32 +66,58 @@ class WebSocketService implements WebSocketHandlerInterface
         });
         if (empty($username)) {
             Cache::forget($cacheKey);
-            $server->push($to, Base::array2json([
+            $server->push($to, $this->formatMsgSend([
                 'messageType' => 'error',
                 'type' => 'user',
-                'sender' => null,
-                'target' => null,
                 'content' => [
                     'error' => '会员不存在',
                 ],
-                'time' => Base::time()
             ]));
             $server->close($to);
-            self::forgetUser($to);
+            $this->forgetUser($to);
             return;
         }
         //
-        self::saveUser($to, $username);
-        $server->push($to, Base::array2json([
+        $wsid = $this->name2fd($username);
+        if ($wsid > 0) {
+            $server->push($wsid, $this->formatMsgSend([
+                'messageType' => 'forced',
+                'type' => 'user',
+                'content' => [
+                    'ip' => Base::getIp(),
+                    'time' => time(),
+                    'new_wsid' => $to,
+                    'old_wsid' => $wsid,
+                ],
+            ]));
+        }
+        $this->saveUser($to, $username);
+        $server->push($to, $this->formatMsgSend([
             'messageType' => 'open',
             'type' => 'user',
-            'sender' => null,
-            'target' => null,
             'content' => [
                 'swid' => $to,
             ],
-            'time' => Base::time()
         ]));
+        //
+        $lastMsg = Base::DBC2A(DB::table('chat_msg')->where('receive', $username)->orderByDesc('indate')->first());
+        if ($lastMsg && $lastMsg['roger'] === 0) {
+            $content = Base::string2array($lastMsg['message']);
+            $content['resend'] = 1;
+            $content['id'] = $lastMsg['id'];
+            $content['username'] = $lastMsg['username'];
+            $content['userimg'] = Users::userimg($lastMsg['username']);
+            $content['indate'] = $lastMsg['indate'];
+            $server->push($to, $this->formatMsgSend([
+                'messageType' => 'send',
+                'contentId' => $lastMsg['id'],
+                'type' => 'user',
+                'sender' => $lastMsg['username'],
+                'target' => $lastMsg['receive'],
+                'content' => $content,
+                'time' => $lastMsg['indate'],
+            ]));
+        }
     }
 
     /**
@@ -102,17 +132,24 @@ class WebSocketService implements WebSocketHandlerInterface
             '__static_langdata' => [],
         ];
         //
-        $data = Base::json2array($frame->data);
+        $data = $this->formatMsgReceive($frame->data);
         $feedback = [
             'status' => 1,
             'message' => '',
         ];
         switch ($data['type']) {
             /**
+             * 刷新
+             */
+            case 'refresh':
+                DB::table('users')->where('username', $this->fd2name($frame->fd))->update(['wsdate' => time()]);
+                break;
+
+            /**
              * 未读消息总数
              */
             case 'unread':
-                $from = self::fd2name($frame->fd);
+                $from = $this->fd2name($frame->fd);
                 if ($from) {
                     $num = intval(DB::table('chat_dialog')->where('user1', $from)->sum('unread1'));
                     $num+= intval(DB::table('chat_dialog')->where('user2', $from)->sum('unread2'));
@@ -123,12 +160,12 @@ class WebSocketService implements WebSocketHandlerInterface
                 break;
 
             /**
-             * 已读消息
+             * 已读会员消息
              */
             case 'read':
-                $to = self::name2fd($data['target']);
+                $to = $this->name2fd($data['target']);
                 if ($to) {
-                    $dialog = Chat::openDialog(self::fd2name($frame->fd), $data['target']);
+                    $dialog = Chat::openDialog($this->fd2name($frame->fd), $data['target']);
                     if (!Base::isError($dialog)) {
                         $dialog = $dialog['data'];
                         $upArray = [];
@@ -144,21 +181,37 @@ class WebSocketService implements WebSocketHandlerInterface
                 break;
 
             /**
+             * 收到信息回执
+             */
+            case 'roger':
+                if ($data['contentId'] > 0) {
+                    DB::table('chat_msg')->where([
+                        'id' => $data['contentId'],
+                        'receive' => $this->fd2name($frame->fd),
+                    ])->update([
+                        'roger' => 1,
+                    ]);
+                }
+                break;
+
+            /**
              * 发给用户
              */
             case 'user':
-                $to = self::name2fd($data['target']);
-                $res = Chat::saveMessage(self::fd2name($frame->fd), $data['target'], $data['content']);
+                $to = $this->name2fd($data['target']);
+                $res = Chat::saveMessage($this->fd2name($frame->fd), $data['target'], $data['content']);
                 if (Base::isError($res)) {
                     $feedback = [
                         'status' => 0,
                         'message' => $res['msg'],
                     ];
                 } else {
-                    $data['content']['id'] = $res['data']['id'];
-                    $feedback['message'] = $res['data']['id'];
+                    $contentId = $res['data']['id'];
+                    $data['contentId'] = $contentId;
+                    $data['content']['id'] = $contentId;
+                    $feedback['message'] = $contentId;
                     if ($to) {
-                        $server->push($to, Base::array2json($data));
+                        $server->push($to, $this->formatMsgSend($data));
                     }
                 }
                 break;
@@ -167,21 +220,26 @@ class WebSocketService implements WebSocketHandlerInterface
              * 发给整个团队
              */
             case 'team':
-                foreach (self::getUsersAll() as $user) {
-                    $data['target'] = $user['username'];
-                    $server->push($user['wsid'], Base::array2json($data));
+                if (Base::val($data['content'], 'type') === 'taskA') {
+                    $taskId = intval(Base::val($data['content'], 'taskDetail.id'));
+                    if ($taskId > 0) {
+                        $userLists = $this->getTaskUsers($taskId);
+                    } else {
+                        $userLists = $this->getTeamUsers();
+                    }
+                    foreach ($userLists as $user) {
+                        $data['target'] = $user['username'];
+                        $server->push($user['wsid'], $this->formatMsgSend($data));
+                    }
                 }
                 break;
         }
         if ($data['messageId']) {
-            $server->push($frame->fd, Base::array2json([
+            $server->push($frame->fd, $this->formatMsgSend([
                 'messageType' => 'feedback',
                 'messageId' => $data['messageId'],
                 'type' => 'user',
-                'sender' => null,
-                'target' => null,
                 'content' => $feedback,
-                'time' => Base::time()
             ]));
         }
     }
@@ -194,7 +252,7 @@ class WebSocketService implements WebSocketHandlerInterface
      */
     public function onClose(Server $server, $fd, $reactorId)
     {
-        self::forgetUser($fd);
+        $this->forgetUser($fd);
     }
 
     /** ****************************************************************************** */
@@ -202,49 +260,145 @@ class WebSocketService implements WebSocketHandlerInterface
     /** ****************************************************************************** */
 
     /**
-     * 缓存用户信息
+     * 格式化信息(来自接收)
+     * @param $data
+     * @return array
+     */
+    private function formatMsgReceive($data) {
+        return $this->formatMsgData(Base::json2array($data));
+    }
+
+    /**
+     * 格式化信息(用于发送)
+     * @param $array
+     * @return string
+     */
+    private function formatMsgSend($array) {
+        return Base::array2json($this->formatMsgData($array));
+    }
+
+    /**
+     * 格式化信息
+     * @param array $array
+     * @return array
+     */
+    private function formatMsgData($array = []) {
+        if (!is_array($array)) {
+            $array = [];
+        }
+        if (!isset($array['messageType'])) $array['messageType'] = '';
+        if (!isset($array['messageId'])) $array['messageId'] = '';
+        if (!isset($array['contentId'])) $array['contentId'] = 0;
+        if (!isset($array['type'])) $array['type'] = '';
+        if (!isset($array['sender'])) $array['sender'] = null;
+        if (!isset($array['target'])) $array['target'] = null;
+        if (!isset($array['content'])) $array['content'] = [];
+        if (!isset($array['time'])) $array['time'] = time();
+        if (!is_array($array['content'])) $array['content'] = [];
+        $array['contentId'] = intval($array['contentId']);
+        return $array;
+    }
+
+    /**
+     * 保存用户wsid
      * @param $fd
      * @param $username
      */
-    public static function saveUser($fd, $username)
+    private function saveUser($fd, $username)
     {
-        DB::table('users')->where('wsid', $fd)->update(['wsid' => 0]);
-        DB::table('users')->where('username', $username)->update(['wsid' => $fd]);
+        $this->forgetUser($fd);
+        $this->forgetName($username);
+        DB::table('users')->where('username', $username)->update(['wsid' => $fd, 'wsdate' => time()]);
     }
 
     /**
-     * 清除用户缓存
+     * 清除用户wsid
      * @param $fd
      */
-    public static function forgetUser($fd)
+    private function forgetUser($fd)
     {
+        $this->forgetFd($fd);
         DB::table('users')->where('wsid', $fd)->update(['wsid' => 0]);
     }
 
     /**
-     * 获取当前用户
+     * 根据wsid清除缓存
+     * @param $fd
+     */
+    private function forgetFd($fd) {
+        Cache::forget('ws::name:' . $this->fd2name($fd));
+        Cache::forget('ws::fd:' . $fd);
+    }
+
+    /**
+     * 根据username清除缓存
+     * @param $username
+     */
+    private function forgetName($username) {
+        Cache::forget('ws::fd:' . $this->name2fd($username));
+        Cache::forget('ws::name:' . $username);
+    }
+
+    /**
+     * 获取团队用户
      * @return array|string
      */
-    public static function getUsersAll()
+    private function getTeamUsers()
+    {
+        return Base::DBC2A(DB::table('users')->select(['wsid', 'username'])->where([
+            ['wsid', '>', 0],
+            ['wsdate', '>', time() - 600],
+        ])->get());
+    }
+
+    /**
+     * 获取跟任务有关系的用户(关注的、在项目里的、负责人、创建者)
+     * @param $taskId
+     * @return array
+     */
+    private function getTaskUsers($taskId)
     {
-        return Base::DBC2A(DB::table('users')->select(['wsid', 'username'])->where('wsid', '>', 0)->get());
+        $taskDeatil = Base::DBC2A(DB::table('project_task')->select(['follower', 'createuser', 'username', 'projectid'])->where('id', $taskId)->first());
+        if (empty($taskDeatil)) {
+            return [];
+        }
+        //关注的用户
+        $userArray = Base::string2array($taskDeatil['follower']);
+        //创建者
+        $userArray[] = $taskDeatil['createuser'];
+        //负责人
+        $userArray[] = $taskDeatil['username'];
+        //在项目里的用户
+        if ($taskDeatil['projectid'] > 0) {
+            $tempLists = Base::DBC2A(DB::table('project_users')->select(['username'])->where(['projectid' => $taskDeatil['projectid'], 'type' => '成员' ])->get());
+            foreach ($tempLists AS $item) {
+                $userArray[] = $item['username'];
+            }
+        }
+        //
+        return Base::DBC2A(DB::table('users')->select(['wsid', 'username'])->where([
+            ['wsid', '>', 0],
+            ['wsdate', '>', time() - 600],
+        ])->whereIn('username', array_values(array_unique($userArray)))->get());
     }
 
     /**
+     * wsid获取用户名
      * @param $fd
      * @return mixed
      */
-    public static function fd2name($fd)
+    private function fd2name($fd)
     {
-        return DB::table('users')->select(['username'])->where('wsid', $fd)->value('username');
+        return DBCache::table('users')->cacheKeyname('ws::fd:' . $fd)->select(['username'])->where('wsid', $fd)->value('username');
     }
 
     /**
+     * 用户名获取wsid
      * @param $username
      * @return mixed
      */
-    public static function name2fd($username)
+    private function name2fd($username)
     {
-        return DB::table('users')->select(['wsid'])->where('username', $username)->value('wsid');
+        return DBCache::table('users')->cacheKeyname('ws::name:' . $username)->select(['wsid'])->where('username', $username)->value('wsid');
     }
 }

+ 15 - 1
resources/assets/js/main/App.vue

@@ -157,7 +157,21 @@
                         if (msgDetail.sender == $A.getUserName()) {
                             return;
                         }
-                        if (msgDetail.messageType != 'send') {
+                        if (msgDetail.messageType == 'forced') {
+                            $A.token("");
+                            $A.storage("userInfo", {});
+                            $A.triggerUserInfoListener({});
+                            //
+                            let content = $A.jsonParse(msgDetail.content)
+                            this.$Modal.warning({
+                                title: this.$L("系统提示"),
+                                content: this.$L('您的帐号在其他地方(%)登录,您被迫退出,如果这不是您本人的操作,请注意帐号安全!', content.ip),
+                                onOk: () => {
+                                    this.goForward({path: '/'}, true);
+                                }
+                            });
+                            return;
+                        } else if (msgDetail.messageType != 'send') {
                             return;
                         }
                         let content = $A.jsonParse(msgDetail.content)

+ 58 - 33
resources/assets/js/main/components/chat/Index.vue

@@ -120,6 +120,7 @@
     .chat-notice-box {
         display: flex;
         align-items: flex-start;
+        cursor: pointer;
         .chat-notice-userimg {
             width: 42px;
             height: 42px;
@@ -130,12 +131,13 @@
             padding: 0 12px;
         }
         .ivu-notice-desc {
-            word-break:break-all;
+            font-size: 13px;
+            word-break: break-all;
             line-height: 1.3;
             text-overflow: ellipsis;
             display: -webkit-box;
             -webkit-line-clamp: 2;
-            overflow:hidden;
+            overflow: hidden;
             -webkit-box-orient: vertical;
         }
     }
@@ -185,6 +187,7 @@
                 background-color: transparent;
                 cursor: pointer;
                 &.self {
+                    cursor: default;
                     img {
                         width: 36px;
                         height: 36px;
@@ -195,6 +198,14 @@
                     color: #ffffff;
                     background-color: rgba(255, 255, 255, 0.06);
                 }
+                &:hover {
+                    > i {
+                        transform: scale(1.1);
+                    }
+                }
+                > i {
+                    transition: all 0.2s;
+                }
                 .chat-num {
                     position: absolute;
                     top: 50%;
@@ -604,6 +615,8 @@
             return {
                 loadIng: 0,
 
+                openAlready: false,
+
                 userInfo: {},
 
                 chatTap: 'dialog',
@@ -666,44 +679,37 @@
                 if (msgDetail.sender == $A.getUserName()) {
                     return;
                 }
+                if (msgDetail.messageType == 'open') {
+                    if (this.openWindow) {
+                        this.getDialogLists();
+                        this.getDialogMessage();
+                    } else {
+                        this.openAlready = false;
+                        this.dialogTarget = {};
+                    }
+                    return;
+                }
                 if (msgDetail.messageType != 'send') {
                     return;
                 }
                 //
-                let data = $A.jsonParse(msgDetail.content);
-                if (['taskA'].indexOf(data.type) !== -1) {
+                let content = $A.jsonParse(msgDetail.content);
+                if (['taskA'].indexOf(content.type) !== -1) {
                     return;
                 }
-                let lasttext;
-                switch (data.type) {
-                    case 'text':
-                        lasttext = data.text;
-                        break;
-                    case 'image':
-                        lasttext = this.$L('[图片]');
-                        break;
-                    case 'taskB':
-                        lasttext = data.text + " " + this.$L("[来自关注任务]");
-                        break;
-                    case 'report':
-                        lasttext = data.text + " " + this.$L("[来自工作报告]");
-                        break;
-                    default:
-                        lasttext = this.$L('[未知类型]');
-                        break;
-                }
+                let lasttext = $A.WS.getMsgDesc(content);
                 let plusUnread = msgDetail.sender != this.dialogTarget.username || !this.openWindow;
                 this.addDialog({
-                    username: data.username,
-                    userimg: data.userimg,
+                    username: content.username,
+                    userimg: content.userimg,
                     lasttext: lasttext,
-                    lastdate: data.indate
-                }, plusUnread);
+                    lastdate: content.indate
+                }, plusUnread && content.resend !== 1);
                 if (msgDetail.sender == this.dialogTarget.username) {
-                    this.addMessageData(data, true);
+                    this.addMessageData(content, true);
                 }
                 if (!plusUnread) {
-                    $A.WS.sendTo('read', data.username);
+                    $A.WS.sendTo('read', content.username);
                 }
                 if (!this.openWindow) {
                     this.$Notice.close('chat-notice');
@@ -716,15 +722,15 @@
                                 on: {
                                     click: () => {
                                         this.$Notice.close('chat-notice');
-                                        this.$emit("on-open-notice", data.username);
-                                        this.clickDialog(data.username);
+                                        this.$emit("on-open-notice", content.username);
+                                        this.clickDialog(content.username);
                                     }
                                 }
                             }, [
-                                h('img', { class: 'chat-notice-userimg', attrs: { src: data.userimg } }),
+                                h('img', { class: 'chat-notice-userimg', attrs: { src: content.userimg } }),
                                 h('div', { class: 'ivu-notice-with-desc' }, [
                                     h('div', { class: 'ivu-notice-title' }, [
-                                        h('UserView', { props: { username: data.username } })
+                                        h('UserView', { props: { username: content.username } })
                                     ]),
                                     h('div', { class: 'ivu-notice-desc' }, lasttext)
                                 ])
@@ -733,6 +739,14 @@
                     });
                 }
             });
+            $A.WS.setOnSpecialListener("chat/index", (content) => {
+                this.addDialog({
+                    username: content.username,
+                    userimg: content.userimg,
+                    lasttext: $A.WS.getMsgDesc(content),
+                    lastdate: content.indate
+                });
+            });
         },
 
         watch: {
@@ -751,6 +765,10 @@
             openWindow(val) {
                 if (val) {
                     $A.WS.connection();
+                    if (!this.openAlready) {
+                        this.openAlready = true;
+                        this.getDialogLists();
+                    }
                 }
             },
 
@@ -806,6 +824,9 @@
             },
 
             getDialogLists() {
+                if (!this.openAlready) {
+                    return;
+                }
                 this.loadIng++;
                 this.dialogNoDataText = this.$L("数据加载中.....");
                 $A.aAjax({
@@ -829,6 +850,11 @@
             },
 
             getDialogMessage(isNextPage = false) {
+                let username = this.dialogTarget.username;
+                if (!username) {
+                    return;
+                }
+                //
                 if (isNextPage === true) {
                     if (!this.messageHasMorePages) {
                         return;
@@ -842,7 +868,6 @@
                 }
                 this.messageHasMorePages = false;
                 //
-                let username = this.dialogTarget.username;
                 this.loadIng++;
                 this.messageNoDataText = this.$L("数据加载中.....");
                 $A.aAjax({

+ 2 - 2
resources/assets/js/main/components/chat/message.vue

@@ -21,10 +21,10 @@
                     </template>
                     <div v-else-if="info.type==='report'" class="item-link" @click="reportDetail(info.other.id, info.other.title)">{{$L('来自工作报告')}}:<a href="javascript:void(0)">{{info.other.title}}</a></div>
                 </div>
-                <img class="item-userimg" @click="clickUser" :src="info.userimg" onerror="this.src=window.location.origin+'/images/other/avatar.png'"/>
+                <img class="item-userimg" @click="clickUser" :src="info.userimg"/>
             </div>
             <div v-else-if="info.self===false" class="list-item">
-                <img class="item-userimg" @click="clickUser" :src="info.userimg" onerror="this.src=window.location.origin+'/images/other/avatar.png'"/>
+                <img class="item-userimg" @click="clickUser" :src="info.userimg"/>
                 <div class="item-left">
                     <div class="item-username" @click="clickUser">
                         <em class="item-name"><user-view :username="info.username" placement="right"/></em>

+ 3 - 0
resources/assets/js/main/components/project/task/detail/detail.vue

@@ -856,6 +856,9 @@
                     margin-top: 8px;
                     padding-left: 10px;
                     padding-right: 10px;
+                    overflow: hidden;
+                    white-space: nowrap;
+                    text-overflow: ellipsis;
                 }
             }
         }

+ 1 - 1
resources/assets/js/main/components/report/add.vue

@@ -177,7 +177,7 @@
                                 };
                                 this.dataDetail.ccuserArray.forEach((username) => {
                                     if (username != msgData.username) {
-                                        $A.WS.sendTo('user', username, msgData);
+                                        $A.WS.sendTo('user', username, msgData, 'special');
                                     }
                                 });
                             }

+ 1 - 1
resources/assets/js/main/components/report/my.vue

@@ -290,7 +290,7 @@
                                 };
                                 res.data.ccuserArray.forEach((username) => {
                                     if (username != msgData.username) {
-                                        $A.WS.sendTo('user', username, msgData);
+                                        $A.WS.sendTo('user', username, msgData, 'special');
                                     }
                                 });
                             }

+ 116 - 28
resources/assets/js/main/main.js

@@ -270,7 +270,7 @@ import '../../sass/main.scss';
                 }
             }
             if (sendToWS === true) {
-                $A.WS.sendTo('team', null, {
+                $A.WS.sendTo('team', {
                     type: "taskA",
                     act: act,
                     taskDetail: taskDetail
@@ -303,7 +303,7 @@ import '../../sass/main.scss';
                             };
                             res.data.follower.forEach((username) => {
                                 if (username != msgData.username) {
-                                    $A.WS.sendTo('user', username, msgData);
+                                    $A.WS.sendTo('user', username, msgData, 'special');
                                 }
                             });
                         });
@@ -323,18 +323,24 @@ import '../../sass/main.scss';
             __instance: null,
             __connected: false,
             __callbackid: {},
+            __openNum: 0,
             __autoNum: 0,
-            __autoLine() {
+            __autoLine(timeout) {
                 let tempNum = this.__autoNum;
                 setTimeout(() => {
                     if (tempNum === this.__autoNum) {
                         this.__autoNum++
-                        this.sendTo('refresh', (res) => {
-                            console.log("[WS] Connection " + (res.status ? 'success' : 'error'));
-                            this.__autoLine();
-                        });
+                        if ($A.getToken() === false) {
+                            console.log("[WS] No token");
+                            this.__autoLine(timeout + 5);
+                        } else {
+                            this.sendTo('refresh', (res) => {
+                                console.log("[WS] Connection " + (res.status ? 'success' : 'error'));
+                                this.__autoLine(timeout + 5);
+                            });
+                        }
                     }
-                }, 30 * 1000);
+                }, Math.min(timeout, 30) * 1000);
             },
 
             /**
@@ -344,17 +350,17 @@ import '../../sass/main.scss';
                 let url = $A.getObject(window.webSocketConfig, 'URL');
                 url += ($A.strExists(url, "?") ? "&" : "?") + "token=" + $A.getToken();
                 if (!$A.leftExists(url, "ws://") && !$A.leftExists(url, "wss://")) {
-                    console.log("[WS] Connection noUrl ...");
+                    console.log("[WS] No connection address");
                     return;
                 }
 
                 if ($A.getToken() === false) {
-                    console.log("[WS] Connection noToken ...");
+                    console.log("[WS] No connected token");
                     return;
                 }
 
                 if (this.__instance !== null && force !== true) {
-                    console.log("[WS] Connection already ...");
+                    console.log("[WS] Connection exists");
                     return;
                 }
 
@@ -363,38 +369,43 @@ import '../../sass/main.scss';
 
                 // 连接建立时触发
                 this.__instance.onopen = (event) => {
-                    console.log("[WS] Connection open ...");
+                    console.log("[WS] Connection opened");
                 }
 
                 // 接收到服务端推送时执行
                 this.__instance.onmessage = (event) => {
-                    // console.log("[WS] Connection message ...");
                     let msgDetail = $A.jsonParse(event.data);
                     if (msgDetail.messageType === 'open') {
-                        console.log("[WS] Connection connected ...");
+                        console.log("[WS] Connection connected");
+                        msgDetail.openNum = this.__openNum;
+                        this.__openNum++;
                         this.__connected = true;
-                        this.__autoLine();
-                    }
-                    if (msgDetail.messageType === 'feedback') {
+                        this.__autoLine(30);
+                    } else if (msgDetail.messageType === 'feedback') {
                         typeof this.__callbackid[msgDetail.messageId] === "function" && this.__callbackid[msgDetail.messageId](msgDetail.content);
                         delete this.__callbackid[msgDetail.messageId];
                         return;
                     }
+                    if ($A.runNum(msgDetail.contentId) > 0) {
+                        $A.WS.sendTo('roger', msgDetail.contentId);
+                    }
                     this.triggerMsgListener(msgDetail);
                 };
 
                 // 连接关闭时触发
                 this.__instance.onclose = (event) => {
-                    console.log("[WS] Connection closed ...");
+                    console.log("[WS] Connection closed");
                     this.__connected = false;
                     this.__instance = null;
+                    this.__autoLine(5);
                 }
 
                 // 连接出错
                 this.__instance.onerror = (event) => {
-                    console.log("[WS] Connection error ...");
+                    console.log("[WS] Connection error");
                     this.__connected = false;
                     this.__instance = null;
+                    this.__autoLine(5);
                 }
 
                 return this;
@@ -429,14 +440,52 @@ import '../../sass/main.scss';
             __msgListenerObject: {},
 
             /**
+             * 添加特殊监听
+             * @param listenerName
+             * @param callback
+             */
+            setOnSpecialListener(listenerName, callback) {
+                if (typeof listenerName != "string") {
+                    return;
+                }
+                if (typeof callback === "function") {
+                    this.__specialListenerObject[listenerName] = {
+                        callback: callback,
+                    }
+                }
+                return this;
+            },
+            triggerSpecialListener(content) {
+                let key, item;
+                for (key in this.__specialListenerObject) {
+                    if (!this.__specialListenerObject.hasOwnProperty(key)) continue;
+                    item = this.__specialListenerObject[key];
+                    if (typeof item.callback === "function") {
+                        item.callback(content);
+                    }
+                }
+            },
+            __specialListenerObject: {},
+
+            /**
              * 发送消息
-             * @param type      会话类型:user:指定target、team:团队会员
-             * @param target    接收方的标识,仅type=user时有效
-             * @param content   发送内容
+             * @param type      会话类型
+             * - refresh: 刷新
+             * - unread: 未读信息总数量
+             * - read: 已读会员信息
+             * - roger: 收到信息回执
+             * - user: 指定target
+             * - team: 团队会员
+             * @param target    发送目标
+             * @param content   发送内容(对象或数组)
              * @param callback  发送回调
              * @param againNum
              */
             sendTo(type, target, content, callback, againNum = 0) {
+                if (typeof target === "object" && typeof content === "undefined") {
+                    content = target;
+                    target = null;
+                }
                 if (typeof target === "function") {
                     content = target;
                     target = null;
@@ -455,22 +504,33 @@ import '../../sass/main.scss';
                             this.connection();
                         }
                     } else {
-                        console.log("[WS] 服务未连接");
+                        console.log("[WS] Service not connected");
                         typeof callback === "function" && callback({status: 0, message: '服务未连接'});
                     }
                     return;
                 }
                 if (this.__connected === false) {
-                    console.log("[WS] 未连接成功");
+                    console.log("[WS] Failed connection");
                     typeof callback === "function" && callback({status: 0, message: '未连接成功'});
                     return;
                 }
-                if (['refresh', 'unread', 'read', 'user', 'team'].indexOf(type) === -1) {
-                    console.log("[WS] 错误的消息类型: " + type);
+                if (['refresh', 'unread', 'read', 'roger', 'user', 'team'].indexOf(type) === -1) {
+                    console.log("[WS] Wrong message type: " + type);
                     typeof callback === "function" && callback({status: 0, message: '错误的消息类型: ' + type});
                     return;
                 }
+                //
+                let contentId = 0;
+                if (type === 'roger') {
+                    contentId = target;
+                    target = null;
+                }
                 let messageId = '';
+                if (typeof callback === "string" && callback === 'special') {
+                    callback = (res) => {
+                        res.status === 1 && this.triggerSpecialListener(content);
+                    }
+                }
                 if (typeof callback === "function") {
                     messageId = $A.randomString(16);
                     this.__callbackid[messageId] = callback;
@@ -478,6 +538,7 @@ import '../../sass/main.scss';
                 this.__instance.send(JSON.stringify({
                     messageType: 'send',
                     messageId: messageId,
+                    contentId: contentId,
                     type: type,
                     sender: $A.getUserName(),
                     target: target,
@@ -492,15 +553,42 @@ import '../../sass/main.scss';
              */
             close() {
                 if (this.__instance === null) {
-                    console.log("[WS] 服务未连接");
+                    console.log("[WS] Service not connected");
                     return;
                 }
                 if (this.__connected === false) {
-                    console.log("[WS] 未连接成功");
+                    console.log("[WS] Failed connection");
                     return;
                 }
                 this.__instance.close();
             },
+
+            /**
+             * 获取消息描述
+             * @param content
+             * @returns {string}
+             */
+            getMsgDesc(content) {
+                let desc;
+                switch (content.type) {
+                    case 'text':
+                        desc = content.text;
+                        break;
+                    case 'image':
+                        desc = $A.app.$L('[图片]');
+                        break;
+                    case 'taskB':
+                        desc = content.text + " " + $A.app.$L("[来自关注任务]");
+                        break;
+                    case 'report':
+                        desc = content.text + " " + $A.app.$L("[来自工作报告]");
+                        break;
+                    default:
+                        desc = $A.app.$L('[未知类型]');
+                        break;
+                }
+                return desc;
+            }
         }
     });
 

+ 1 - 0
resources/assets/js/main/pages/index.vue

@@ -366,6 +366,7 @@
                 ruleLogin: {},
 
                 systemConfig: $A.jsonParse($A.storage("systemSetting"), {
+                    logo: '',
                     github: '',
                     reg: '',
                 }),

+ 5 - 3
resources/assets/js/main/pages/todo.vue

@@ -358,15 +358,17 @@
             }
         },
         mounted() {
+            if ($A.getToken() === false) {
+                this.goForward({path: '/'}, true);
+                return;
+            }
+            this.refreshTask();
             this.userInfo = $A.getUserInfo((res, isLogin) => {
                 if (this.userInfo.id != res.id) {
                     this.userInfo = res;
                     isLogin && this.refreshTask();
                 }
             }, false);
-            if ($A.getToken() !== false) {
-                this.refreshTask();
-            }
             //
             $A.setOnTaskInfoListener('pages/todo',(act, detail) => {
                 if (detail.username != $A.getUserName()) {