From ea5478c2fb4e4b6fb3c28dfd67e7a914cae3e956 Mon Sep 17 00:00:00 2001 From: kuaifan Date: Fri, 4 Jun 2021 12:18:28 +0800 Subject: [PATCH] websocket --- app/Services/WebSocketService.php | 172 ++++++ app/Tasks/IhttpTask.php | 6 +- app/Tasks/PushTask.php | 6 +- resources/assets/js/app.js | 25 +- resources/assets/js/functions/common.js | 67 +-- resources/assets/js/functions/web.js | 566 ++---------------- .../pages/manage/components/project-list.vue | 10 +- resources/assets/js/store/mutations.js | 188 ++++-- resources/assets/js/store/state.js | 130 ++-- 9 files changed, 469 insertions(+), 701 deletions(-) create mode 100644 app/Services/WebSocketService.php diff --git a/app/Services/WebSocketService.php b/app/Services/WebSocketService.php new file mode 100644 index 00000000..92f2192d --- /dev/null +++ b/app/Services/WebSocketService.php @@ -0,0 +1,172 @@ + [], + ]; + $fd = $request->fd; + $data = Base::newTrim($request->get); + $action = $data['action']; + switch ($action) { + /** + * 网页访问 + */ + case 'web': + { + // 判断token参数 + $token = $data['token']; + $cacheKey = "ws::token:" . md5($token); + $userid = Cache::remember($cacheKey, now()->addSeconds(1), function () use ($token) { + $authInfo = User::authFind('all', $token); + if ($authInfo['userid'] > 0) { + if (User::whereUserid($authInfo['userid'])->whereEmail($authInfo['email'])->whereEncrypt($authInfo['encrypt'])->exists()) { + return $authInfo['userid']; + } + } + return 0; + }); + if (empty($userid)) { + Cache::forget($cacheKey); + $server->push($fd, Base::array2json([ + 'type' => 'error', + 'data' => [ + 'error' => '会员不存在!' + ], + ])); + $server->close($fd); + $this->deleteUser($fd); + return; + } + // 保存用户、发送open事件 + $this->saveUser($fd, $userid); + $server->push($fd, Base::array2json([ + 'type' => 'open', + 'data' => [ + 'fd' => $fd, + ], + ])); + // 重试发送失败的消息 + PushTask::resendTmpMsgForUserid($userid); + } + break; + + default: + break; + } + } + + /** + * 收到消息时触发 + * @param Server $server + * @param Frame $frame + */ + public function onMessage(Server $server, Frame $frame) + { + global $_A; + $_A = [ + '__static_langdata' => [], + ]; + // + $msg = Base::json2array($frame->data); + $type = $msg['type']; // 消息类型 + $to = $msg['to']; // 发给谁 + $msgId = $msg['msgId']; // 消息ID(用于回调) + $data = $msg['data']; // 消息详情 + // + $reData = []; + switch ($type) { + /** + * 收到回执 + */ + case 'receipt': + + return; + } + // + if ($msgId) { + PushTask::push([ + 'fd' => $frame->fd, + 'msg' => [ + 'type' => 'receipt', + 'msgId' => $msgId, + 'data' => $reData, + ] + ]); + } + } + + /** + * 关闭连接时触发 + * @param Server $server + * @param $fd + * @param $reactorId + * @throws \Exception + */ + public function onClose(Server $server, $fd, $reactorId) + { + $this->deleteUser($fd); + } + + /** ****************************************************************************** */ + /** ****************************************************************************** */ + /** ****************************************************************************** */ + + /** + * 保存用户 + * @param $fd + * @param $userid + */ + private function saveUser($fd, $userid) + { + WebSocket::updateInsert([ + 'key' => md5($fd . '@' . $userid) + ], [ + 'fd' => $fd, + 'userid' => $userid, + ]); + } + + /** + * 清除用户 + * @param $fd + */ + private function deleteUser($fd) + { + WebSocket::whereFd($fd)->delete(); + } +} diff --git a/app/Tasks/IhttpTask.php b/app/Tasks/IhttpTask.php index dfc381d5..6b853e8a 100644 --- a/app/Tasks/IhttpTask.php +++ b/app/Tasks/IhttpTask.php @@ -52,14 +52,14 @@ class IhttpTask extends AbstractTask { $res = Ihttp::ihttp_request($this->url, $this->post, $this->extra); if ($this->apiWebsocket && $this->apiUserid) { - $body = Base::isSuccess($res) ? Base::json2array($res['data']) : $res; + $data = Base::isSuccess($res) ? Base::json2array($res['data']) : $res; PushTask::push([ 'userid' => $this->apiUserid, 'msg' => [ - 'messageType' => 'apiWebsocket', + 'type' => 'apiWebsocket', 'apiWebsocket' => $this->apiWebsocket, 'apiSuccess' => Base::isSuccess($res), - 'body' => $body, + 'data' => $data, ] ]); } diff --git a/app/Tasks/PushTask.php b/app/Tasks/PushTask.php index 88ddbaab..2830b4ae 100644 --- a/app/Tasks/PushTask.php +++ b/app/Tasks/PushTask.php @@ -112,8 +112,8 @@ class PushTask extends AbstractTask if (!is_array($msg)) { continue; } - $messageType = $msg['messageType']; - if (empty($messageType)) { + $type = $msg['type']; + if (empty($type)) { continue; } // 发送对象 @@ -149,7 +149,7 @@ class PushTask extends AbstractTask $userFail[] = WebSocket::whereFd($fid)->value('userid'); } } else { - $key = "PUSH::" . $fid . ":" . $messageType . ":" . $key; + $key = "PUSH::" . $fid . ":" . $type . ":" . $key; Cache::put($key, [ 'fd' => $fid, 'msg' => $msg, diff --git a/resources/assets/js/app.js b/resources/assets/js/app.js index 9c95ad69..c9adc82a 100644 --- a/resources/assets/js/app.js +++ b/resources/assets/js/app.js @@ -38,7 +38,7 @@ const router = new VueRouter({ routes }); -//进度条配置 +// 进度条配置 ViewUI.LoadingBar.config({ color: '#3fcc25', failedColor: '#ff0000' @@ -51,28 +51,27 @@ 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); + app.$router.replace(location).then(() => {}); }else{ - this.$router.push(location); + app.$router.push(location).then(() => {}); } }; -//返回函数 +// 返回函数 Vue.prototype.goBack = function (number) { let history = $A.jsonParse(window.sessionStorage['__history__'] || '{}'); if ($A.runNum(history['::count']) > 2) { - this.$router.go(typeof number === 'number' ? number : -1); + app.$router.go(typeof number === 'number' ? number : -1); } else { - this.$router.replace(typeof number === "object" ? number : {path: '/'}); + app.$router.replace(typeof number === "object" ? number : {path: '/'}).then(() => {}); } }; Vue.prototype.$A = $A; - Vue.config.productionTip = false; const app = new Vue({ @@ -83,4 +82,12 @@ const app = new Vue({ components: { App } }); -$A.app = app; + +$A.goForward = app.goForward; +$A.goBack = app.goBack; +$A.getLanguage = app.getLanguage; +$A.Message = app.$Message; +$A.Notice = app.$Notice; +$A.Modal = app.$Modal; +$A.store = app.$store; +$A.L = app.$L; diff --git a/resources/assets/js/functions/common.js b/resources/assets/js/functions/common.js index 7b7c72aa..38fc36cc 100755 --- a/resources/assets/js/functions/common.js +++ b/resources/assets/js/functions/common.js @@ -1264,65 +1264,14 @@ if (typeof params.method === 'undefined') params.method = 'GET'; if (typeof params.timeout === 'undefined') params.timeout = 30000; if (typeof params.dataType === 'undefined') params.dataType = 'json'; - if (typeof params.beforeSend === 'undefined') params.beforeSend = () => { }; + if (typeof params.before === 'undefined') params.before = () => { }; if (typeof params.complete === 'undefined') params.complete = () => { }; - if (typeof params.afterComplete === 'undefined') params.afterComplete = () => { }; + if (typeof params.after === 'undefined') params.after = () => { }; if (typeof params.success === 'undefined') params.success = () => { }; if (typeof params.error === 'undefined') params.error = () => { }; + if (typeof params.header == 'undefined') params.header = {}; // - let loadText = "数据加载中....."; - let busyNetwork = "网络繁忙,请稍后再试!"; - if (typeof $A.app === 'object' && typeof $A.app.$L === 'function') { - loadText = $A.app.$L(loadText); - busyNetwork = $A.app.$L(busyNetwork); - } - // - let toastID = null, beforeTitle = '', errorTitle = ''; - if (typeof $A.app === 'object' && typeof $A.app.$Message === 'object') { - if (typeof params.beforeSend === 'string') { - beforeTitle = params.beforeSend; - params.beforeSend = () => { toastID = $A.app.$Message.loading({content:beforeTitle, duration: 0}); }; - }else if (params.beforeSend === true) { - params.beforeSend = () => { toastID = $A.app.$Message.loading({content:loadText, duration: 0}); }; - } - if (typeof params.error === 'string') { - errorTitle = params.error; - params.error = () => { $A.app.$Message.error({content:errorTitle, duration: 5}); }; - }else if (params.error === true) { - params.error = () => { $A.app.$Message.error({content:busyNetwork, duration: 5}); }; - } - if (params.complete === true) { - params.complete = () => { toastID?toastID():'' }; - } - }else{ - if (typeof params.beforeSend === 'string') { - beforeTitle = params.beforeSend; - params.beforeSend = () => { toastID = $A.toast({title:beforeTitle, fixed: true, timeout: false}); }; - }else if (params.beforeSend === true) { - params.beforeSend = () => { toastID = $A.toast({title:loadText, fixed: true, timeout: false}); }; - } - if (typeof params.error === 'string') { - errorTitle = params.error; - params.error = () => { $A.toast(errorTitle, "danger"); }; - }else if (params.error === true) { - params.error = () => { $A.toast(busyNetwork, "danger"); }; - } - if (params.complete === true) { - params.complete = () => { toastID?$A.toast(toastID):'' }; - } - } - // - let language = window.localStorage['__language:type__'] || 'en'; - if (typeof $A.app === 'object') { - language = $A.app.languageType || language; - } - // - if (typeof params.header !== 'object') params.header = {}; - params.header['Content-Type'] = 'application/json'; - params.header['language'] = language; - // - params.data['__Access-Control-Allow-Origin'] = true; - params.beforeSend(); + params.before(); $A.ihttp({ url: params.url, data: params.data, @@ -1333,15 +1282,15 @@ crossDomain: true, dataType: params.dataType, timeout: params.timeout, - success: function(data, status, xhr) { + success: function (data, status, xhr) { params.complete(); params.success(data, status, xhr); - params.afterComplete(true); + params.after(true); }, - error: function(xhr, status) { + error: function (xhr, status) { params.complete(); params.error(xhr, status); - params.afterComplete(false); + params.after(false); } }); } diff --git a/resources/assets/js/functions/web.js b/resources/assets/js/functions/web.js index 4304391b..03f953a1 100755 --- a/resources/assets/js/functions/web.js +++ b/resources/assets/js/functions/web.js @@ -35,32 +35,28 @@ }, apiAjax(params) { - if (typeof params !== 'object') return false; + if (!$A.isJson(params)) return false; if (typeof params.success === 'undefined') params.success = () => { }; - if (typeof params.header !== 'object') params.header = {}; + if (typeof params.header === 'undefined') params.header = {}; params.url = this.apiUrl(params.url); - params.header['token'] = $A.app.$store.state.userToken; + params.header['Content-Type'] = 'application/json'; + params.header['language'] = $A.getLanguage(); + params.header['token'] = $A.store.state.userToken; // - let beforeCall = params.beforeSend; - params.beforeSend = () => { + let beforeCall = params.before; + params.before = () => { $A.aAjaxLoadNum++; - $A(".w-spinner").show(); - // - if (typeof beforeCall == "function") { - beforeCall(); - } + $A(".common-spinner").show(); + typeof beforeCall == "function" && beforeCall(); }; // let completeCall = params.complete; params.complete = () => { $A.aAjaxLoadNum--; if ($A.aAjaxLoadNum <= 0) { - $A(".w-spinner").hide(); - } - // - if (typeof completeCall == "function") { - completeCall(); + $A(".common-spinner").hide(); } + typeof completeCall == "function" && completeCall(); }; // let callback = params.success; @@ -71,7 +67,7 @@ $A.modalError({ content: data.msg, onOk: () => { - $A.userLogout(); + $A.logout(); } }); return; @@ -79,7 +75,7 @@ if (data.ret === -2 && params.role !== false) { //没有权限 $A.modalError({ - content: data.msg ? data.msg : "你没有相关的权限查看或编辑!" + content: data.msg || "你没有相关的权限查看或编辑!" }); } } @@ -96,46 +92,47 @@ if (WListener) { WListener.complete(); WListener.error("timeout"); - WListener.afterComplete(); + WListener.after(); } }, params.timeout || 30000); $A.aAjaxWsListener.push({ apiWebsocket: apiWebsocket, complete: typeof params.complete === "function" ? params.complete : () => { }, - afterComplete: typeof params.afterComplete === "function" ? params.afterComplete : () => { }, + after: typeof params.after === "function" ? params.after : () => { }, success: typeof params.success === "function" ? params.success : () => { }, error: typeof params.error === "function" ? params.error : () => { }, }); // params.complete = () => { }; - params.afterComplete = () => { }; + params.after = () => { }; params.success = () => { }; params.error = () => { }; params.header['Api-Websocket'] = apiWebsocket; // if ($A.aAjaxWsReady === false) { $A.aAjaxWsReady = true; - $A.WSOB.setOnMsgListener("apiWebsocket", [ - 'apiWebsocket', - ], (msgDetail) => { - switch (msgDetail.messageType) { - case 'apiWebsocket': - clearTimeout(apiTimeout); - const apiWebsocket = msgDetail.apiWebsocket; - const apiSuccess = msgDetail.apiSuccess; - const apiResult = msgDetail.body; - const WListener = $A.aAjaxWsListener.find((item) => item.apiWebsocket == apiWebsocket); - $A.aAjaxWsListener = $A.aAjaxWsListener.filter((item) => item.apiWebsocket != apiWebsocket); - if (WListener) { - WListener.complete(); - if (apiSuccess) { - WListener.success(apiResult); - } else { - WListener.error(apiResult); + $A.store.commit("wsMsgListener", { + name: "apiWebsocket", + callback: (msg) => { + switch (msg.type) { + case 'apiWebsocket': + clearTimeout(apiTimeout); + const apiWebsocket = msg.apiWebsocket; + const apiSuccess = msg.apiSuccess; + const apiResult = msg.data; + const WListener = $A.aAjaxWsListener.find((item) => item.apiWebsocket == apiWebsocket); + $A.aAjaxWsListener = $A.aAjaxWsListener.filter((item) => item.apiWebsocket != apiWebsocket); + if (WListener) { + WListener.complete(); + if (apiSuccess) { + WListener.success(apiResult); + } else { + WListener.error(apiResult); + } + WListener.after(); } - WListener.afterComplete(); - } - break; + break; + } } }); } @@ -148,454 +145,13 @@ aAjaxWsListener: [], /** - * 打开登录页面 + * 登出(打开登录页面) */ - userLogout() { + logout() { const from = window.location.pathname == '/' ? '' : encodeURIComponent(window.location.href); - $A.app.$store.commit('setUserInfo', {}); - $A.app.goForward({path: '/login', query: from ? {from: from} : {}}, true); + $A.store.commit('setUserInfo', {}); + $A.goForward({path: '/login', query: from ? {from: from} : {}}, true); }, - - /** - * 权限是否通过 - * @param role - * @returns {boolean} - */ - identityCheck(role) { - const userInfo = $A.app.$store.state.userInfo; - return $A.identityRaw(role, userInfo.identity); - }, - - /** - * 权限是否通过 - * @param role - * @param identity - * @returns {boolean} - */ - identityRaw(role, identity) { - let isRole = false; - $A.each(identity, (index, res) => { - if (res === role) { - isRole = true; - } - }); - return isRole; - }, - }); - - /** - * ============================================================================= - * **************************** websocket assist *************************** - * ============================================================================= - */ - $.extend({ - /** - * @param config {userid, url, token, logCallback} - */ - WTWS: function (config) { - this.__instance = null; - this.__connected = false; - this.__callbackid = {}; - this.__openNum = 0; - this.__autoNum = 0; - this.__lastSend = 0; - - this.__autoLine = function (timeout) { - var tempNum = this.__autoNum; - var thas = this; - setTimeout(function () { - if (tempNum === thas.__autoNum) { - thas.__autoNum++ - if (!thas.__config.token) { - thas.__log("[WS] No token"); - thas.__autoLine(timeout); - } else { - // 发refresh之前判断10秒内没有使用过sendTo的再发送refresh - if (thas.__lastSend + 10 < Math.round(new Date().getTime() / 1000)) { - thas.sendTo('refresh', function (res) { - thas.__log("[WS] Connection " + (res.status ? 'success' : 'error')); - thas.__autoLine(timeout); - }); - } - } - } - }, Math.min(timeout, 30) * 1000); - } - this.__log = function (text, event) { - typeof this.__config.logCallback === "function" && this.__config.logCallback(text, event); - } - this.__lExists = function (string, find, lower) { - string += ""; - find += ""; - if (lower !== true) { - string = string.toLowerCase(); - find = find.toLowerCase(); - } - return (string.substring(0, find.length) === find); - } - this.__rNum = function (str, fixed) { - var _s = Number(str); - if (_s + "" === "NaN") { - _s = 0; - } - if (/^[0-9]*[1-9][0-9]*$/.test(fixed)) { - _s = _s.toFixed(fixed); - var rs = _s.indexOf('.'); - if (rs < 0) { - _s += "."; - for (var i = 0; i < fixed; i++) { - _s += "0"; - } - } - } - return _s; - } - this.__jParse = function (str, defaultVal) { - if (str === null) { - return defaultVal ? defaultVal : {}; - } - if (typeof str === "object") { - return str; - } - try { - return JSON.parse(str); - } catch (e) { - return defaultVal ? defaultVal : {}; - } - } - this.__randString = function (len) { - len = len || 32; - var $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678oOLl9gqVvUuI1'; - var maxPos = $chars.length; - var pwd = ''; - for (var i = 0; i < len; i++) { - pwd += $chars.charAt(Math.floor(Math.random() * maxPos)); - } - return pwd; - } - this.__urlParams = function(url, params) { - if (typeof params === "object" && params !== null) { - url+= ""; - url+= url.indexOf("?") === -1 ? '?' : ''; - for (var key in params) { - if (!params.hasOwnProperty(key)) { - continue; - } - url+= '&' + key + '=' + params[key]; - } - } - return url.replace("?&", "?"); - } - this.__isArr = function (obj){ - return Object.prototype.toString.call(obj)=='[object Array]'; - } - - /** - * 设置参数 - * @param config - */ - this.config = function (config) { - if (typeof config !== "object" || config === null) { - config = {}; - } - config.userid = config.userid || ''; - config.url = config.url || ''; - config.token = config.token || ''; - config.logCallback = config.logCallback || null; - this.__config = config; - return this; - } - - /** - * 连接 - * @param force - */ - this.connection = function (force) { - if (!this.__lExists(this.__config.url, "ws://") && !this.__lExists(this.__config.url, "wss://")) { - this.__log("[WS] No connection address"); - return this; - } - - if (!this.__config.token) { - this.__log("[WS] No connected token"); - return this; - } - - if (this.__instance !== null && force !== true) { - this.__log("[WS] Connection exists"); - return this; - } - - var thas = this; - - // 初始化客户端套接字并建立连接 - this.__instance = new WebSocket(this.__urlParams(this.__config.url, { - mode: 'console', - token: this.__config.token, - })); - - // 连接建立时触发 - this.__instance.onopen = function (event) { - thas.__log("[WS] Connection opened", event); - } - - // 接收到服务端推送时执行 - this.__instance.onmessage = function (event) { - thas.__log("[WS] Message", event); - var msgDetail = thas.__jParse(event.data); - if (msgDetail.messageType === 'open') { - thas.__log("[WS] Connection connected"); - msgDetail.openNum = thas.__openNum; - msgDetail.config = thas.__config; - thas.__openNum++; - thas.__connected = true; - thas.__autoLine(30); - } else if (msgDetail.messageType === 'back') { - typeof thas.__callbackid[msgDetail.messageId] === "function" && thas.__callbackid[msgDetail.messageId](msgDetail.body); - delete thas.__callbackid[msgDetail.messageId]; - return; - } - if (thas.__rNum(msgDetail.contentId) > 0) { - thas.sendTo('roger', msgDetail.contentId); - } - thas.triggerMsgListener(msgDetail); - }; - - // 连接关闭时触发 - this.__instance.onclose = function (event) { - thas.__log("[WS] Connection closed", event); - thas.__connected = false; - thas.__instance = null; - thas.__autoLine(3); - } - - // 连接出错 - this.__instance.onerror = function (event) { - thas.__log("[WS] Connection error", event); - thas.__connected = false; - thas.__instance = null; - thas.__autoLine(3); - } - - return this; - } - - /** - * 添加消息监听 - * @param listenerName - * @param listenerType - * @param callback - */ - this.setOnMsgListener = function (listenerName, listenerType, callback) { - if (typeof listenerName != "string") { - return this; - } - if (typeof listenerType === "function") { - callback = listenerType; - listenerType = []; - } - if (!this.__isArr(listenerType)) { - listenerType = [listenerType]; - } - if (typeof callback === "function") { - window.webSocketConfig.LISTENER[listenerName] = { - callback: callback, - listenerType: listenerType, - } - } - return this; - } - this.triggerMsgListener = function (msgDetail) { - var key, item; - for (key in window.webSocketConfig.LISTENER) { - if (!window.webSocketConfig.LISTENER.hasOwnProperty(key)) { - continue; - } - item = window.webSocketConfig.LISTENER[key]; - if (item.listenerType.length > 0 && item.listenerType.indexOf(msgDetail.messageType) === -1) { - continue; - } - if (typeof item.callback === "function") { - try { - item.callback(msgDetail); - } catch (e) { } - } - } - } - - /** - * 发送消息 - * @param messageType 会话类型 - * - refresh: 刷新 - * @param target 发送目标 - * @param body 发送内容(对象或数组) - * @param callback 发送回调 - * @param againNum - */ - this.sendTo = function (messageType, target, body, callback, againNum = 0) { - if (typeof target === "object" && typeof body === "undefined") { - body = target; - target = null; - } - if (typeof target === "function") { - body = target; - target = null; - } - if (typeof body === "function") { - callback = body; - body = null; - } - if (body === null || typeof body !== "object") { - body = {}; - } - // - var thas = this; - if (this.__instance === null || this.__connected === false) { - if (againNum < 10 && messageType != 'team') { - setTimeout(function () { - thas.sendTo(messageType, target, body, callback, thas.__rNum(againNum) + 1) - }, 600); - if (againNum === 0) { - this.connection(); - } - } else { - if (this.__instance === null) { - this.__log("[WS] Service not connected"); - typeof callback === "function" && callback({status: 0, message: '服务未连接'}); - } else { - this.__log("[WS] Failed connection"); - typeof callback === "function" && callback({status: 0, message: '未连接成功'}); - } - } - return this; - } - if (['refresh', 'notificationStatus'].indexOf(messageType) === -1) { - this.__log("[WS] Wrong message messageType: " + messageType); - typeof callback === "function" && callback({status: 0, message: '错误的消息类型: ' + messageType}); - return this; - } - // - var contentId = 0; - if (messageType === 'roger') { - contentId = target; - target = null; - } - var messageId = ''; - if (typeof callback === "function") { - messageId = this.__randString(16); - this.__callbackid[messageId] = callback; - } - this.__lastSend = Math.round(new Date().getTime()/1000); - this.__instance.send(JSON.stringify({ - messageType: messageType, - messageId: messageId, - contentId: contentId, - userid: this.__config.userid, - target: target, - body: body, - time: this.__lastSend, - })); - return this; - } - - /** - * 关闭连接 - */ - this.close = function () { - if (this.__instance === null) { - this.__log("[WS] Service not connected"); - return this; - } - if (this.__connected === false) { - this.__log("[WS] Failed connection"); - return this; - } - this.__instance.close(); - return this; - } - - return this.config(config); - }, - - WSOB: { - instance: null, - isClose: false, - - /** - * 初始化 - */ - initialize() { - let url = window.webSocketConfig.URL; - if (!url) { - url = window.location.origin; - url = url.replace("https://", "wss://"); - url = url.replace("http://", "ws://"); - url+= "/ws"; - } - let config = { - userid: $A.app.$store.state.userInfo.userid, - url: url, - token: $A.app.$store.state.userToken, - }; - if (window.webSocketConfig.DEBUG) { - config.logCallback = function (msg) { - console.log(msg); - }; - } - if (this.instance === null) { - this.instance = new $A.WTWS(config); - this.instance.connection() - } else { - this.instance.config(config); - if (this.isClose) { - this.isClose = false - this.instance.connection(); - } - } - }, - - /** - * 主动连接 - */ - connection() { - this.initialize(); - this.instance.connection(); - }, - - /** - * 监听消息 - * @param listenerName - * @param listenerType - * @param callback - */ - setOnMsgListener(listenerName, listenerType, callback) { - this.initialize(); - this.instance.setOnMsgListener(listenerName, listenerType, callback); - }, - - /** - * 发送消息 - * @param messageType - * @param target - * @param body - * @param callback - */ - sendTo(messageType, target, body, callback) { - this.initialize(); - this.instance.sendTo(messageType, target, body, callback); - }, - - /** - * 关闭连接 - */ - close() { - if (this.instance === null) { - return; - } - this.isClose = true - this.instance.config(null).close(); - }, - } }); /** @@ -611,10 +167,10 @@ content: config }; } - config.title = $A.app.$L(config.title || (typeof config.render === 'undefined' ? '温馨提示' : '')); - config.content = $A.app.$L(config.content || ''); - config.okText = $A.app.$L(config.okText || '确定'); - config.cancelText = $A.app.$L(config.cancelText || '取消'); + config.title = $A.L(config.title || (typeof config.render === 'undefined' ? '温馨提示' : '')); + config.content = $A.L(config.content || ''); + config.okText = $A.L(config.okText || '确定'); + config.cancelText = $A.L(config.cancelText || '取消'); return config; }, @@ -623,7 +179,7 @@ setTimeout(() => { $A.modalConfirm(config) }, millisecond); return; } - $A.app.$Modal.confirm($A.modalConfig(config)); + $A.Modal.confirm($A.modalConfig(config)); }, modalSuccess(config, millisecond = 0) { @@ -631,7 +187,7 @@ setTimeout(() => { $A.modalSuccess(config) }, millisecond); return; } - $A.app.$Modal.success($A.modalConfig(config)); + $A.Modal.success($A.modalConfig(config)); }, modalInfo(config, millisecond = 0) { @@ -639,7 +195,7 @@ setTimeout(() => { $A.modalInfo(config) }, millisecond); return; } - $A.app.$Modal.info($A.modalConfig(config)); + $A.Modal.info($A.modalConfig(config)); }, modalWarning(config, millisecond = 0) { @@ -647,7 +203,7 @@ setTimeout(() => { $A.modalWarning(config) }, millisecond); return; } - $A.app.$Modal.warning($A.modalConfig(config)); + $A.Modal.warning($A.modalConfig(config)); }, modalError(config, millisecond = 0) { @@ -655,7 +211,7 @@ setTimeout(() => { $A.modalError(config) }, millisecond); return; } - $A.app.$Modal.error($A.modalConfig(config)); + $A.Modal.error($A.modalConfig(config)); }, modalInfoShow(title, data, addConfig) { @@ -670,30 +226,30 @@ let config = { title: title, content: content, - okText: $A.app.$L('关闭'), + okText: $A.L('关闭'), closable: true }; if (typeof addConfig == 'object' && addConfig) { config = Object.assign(config, addConfig); } - this.app.$Modal.info(config); + this.Modal.info(config); }, modalAlert(msg) { - alert($A.app.$L(msg)); + alert($A.L(msg)); }, //提示 messageSuccess(msg) { - $A.app.$Message.success($A.app.$L(msg)); + $A.Message.success($A.L(msg)); }, messageWarning(msg) { - $A.app.$Message.warning($A.app.$L(msg)); + $A.Message.warning($A.L(msg)); }, messageError(msg) { - $A.app.$Message.error($A.app.$L(msg)); + $A.Message.error($A.L(msg)); }, //通知 @@ -703,17 +259,17 @@ desc: config }; } - config.title = $A.app.$L(config.title || (typeof config.render === 'undefined' ? '温馨提示' : '')); - config.desc = $A.app.$L(config.desc || ''); + config.title = $A.L(config.title || (typeof config.render === 'undefined' ? '温馨提示' : '')); + config.desc = $A.L(config.desc || ''); return config; }, noticeSuccess(config) { - $A.app.$Notice.success($A.noticeConfig(config)); + $A.Notice.success($A.noticeConfig(config)); }, noticeWarning(config) { - $A.app.$Notice.warning($A.noticeConfig(config)); + $A.Notice.warning($A.noticeConfig(config)); }, noticeError(config) { @@ -723,7 +279,7 @@ duration: 6 }; } - $A.app.$Notice.error($A.noticeConfig(config)); + $A.Notice.error($A.noticeConfig(config)); }, }); diff --git a/resources/assets/js/pages/manage/components/project-list.vue b/resources/assets/js/pages/manage/components/project-list.vue index 59ce0d2f..ac1e3957 100644 --- a/resources/assets/js/pages/manage/components/project-list.vue +++ b/resources/assets/js/pages/manage/components/project-list.vue @@ -24,7 +24,7 @@ -
  • +
  • @@ -41,7 +41,7 @@
    -
    +
    @@ -104,7 +104,7 @@
    -
    +
    @@ -162,7 +162,7 @@
    -
    +
    @@ -209,7 +209,7 @@
    -
    +
    diff --git a/resources/assets/js/store/mutations.js b/resources/assets/js/store/mutations.js index 52918116..2f89ec41 100644 --- a/resources/assets/js/store/mutations.js +++ b/resources/assets/js/store/mutations.js @@ -1,47 +1,12 @@ export default { /** - * 切换项目聊天显隐 + * 切换Boolean变量 * @param state + * @param key */ - toggleProjectChatShow(state) { - state.projectChatShow = !state.projectChatShow - state.setStorage('projectChatShow', state.projectChatShow); - }, - - /** - * 切换项目面板显示类型 - * @param state - */ - toggleProjectListPanel(state) { - state.projectListPanel = !state.projectListPanel - state.setStorage('projectListPanel', state.projectListPanel); - }, - - /** - * 切换项目面板显示显示我的任务 - * @param state - */ - toggleTaskMyShow(state) { - state.taskMyShow = !state.taskMyShow - state.setStorage('taskMyShow', state.taskMyShow); - }, - - /** - * 切换项目面板显示显示未完成任务 - * @param state - */ - toggleTaskUndoneShow(state) { - state.taskUndoneShow = !state.taskUndoneShow - state.setStorage('taskUndoneShow', state.taskUndoneShow); - }, - - /** - * 切换项目面板显示显示已完成任务 - * @param state - */ - toggleTaskCompletedShow(state) { - state.taskCompletedShow = !state.taskCompletedShow - state.setStorage('taskCompletedShow', state.taskCompletedShow); + toggleBoolean(state, key) { + state[key] = !state[key] + state.method.setStorage('boolean:' + key, state[key]); }, /** @@ -71,7 +36,7 @@ export default { $A.apiAjax({ url: 'users/info', error: () => { - $A.userLogout(); + $A.logout(); }, success: ({ret, data, msg}) => { if (ret === 1) { @@ -89,14 +54,16 @@ export default { * @param info */ setUserInfo(state, info) { - const userInfo = state._cloneJSON(info); - userInfo.userid = state._runNum(userInfo.userid); + const userInfo = state.method.cloneJSON(info); + userInfo.userid = state.method.runNum(userInfo.userid); userInfo.token = userInfo.userid > 0 ? (userInfo.token || state.userToken) : ''; state.userInfo = userInfo; state.userId = userInfo.userid; state.userToken = userInfo.token; - state.setStorage('userInfo', state.userInfo); + state.userIsAdmin = state.method.inArray('admin', userInfo.identity); + state.method.setStorage('userInfo', state.userInfo); this.commit('getProjectList'); + this.commit('wsConnection'); }, /** @@ -131,10 +98,10 @@ export default { * @param project_id */ getProjectDetail(state, project_id) { - if (state._runNum(project_id) === 0) { + if (state.method.runNum(project_id) === 0) { return; } - if (state._isJson(state.cacheProject[project_id])) { + if (state.method.isJson(state.cacheProject[project_id])) { state.projectDetail = state.cacheProject[project_id]; } state.projectDetail.id = project_id; @@ -173,13 +140,13 @@ export default { * @param params {userid, success, complete} */ getUserBasic(state, params) { - if (!state._isJson(params)) { + if (!state.method.isJson(params)) { return; } const {userid, success, complete} = params; const time = Math.round(new Date().getTime() / 1000); const array = []; - (state._isArray(userid) ? userid : [userid]).some((uid) => { + (state.method.isArray(userid) ? userid : [userid]).some((uid) => { if (state.cacheUserBasic[uid]) { typeof success === "function" && success(state.cacheUserBasic[uid].data, false); if (time - state.cacheUserBasic[uid].time <= 10) { @@ -223,5 +190,130 @@ export default { } } }); + }, + + /** + * 初始化 websocket + * @param state + */ + wsConnection(state) { + clearTimeout(state.wsTimeout); + if (state.userId === 0) { + if (state.ws) { + state.ws.close(); + state.ws = null; + } + return; + } + let url = window.webSocketConfig.URL; + if (!url) { + url = window.location.origin; + url = url.replace("https://", "wss://"); + url = url.replace("http://", "ws://"); + url += "/ws"; + } + url += "?action=web&token=" + state.userToken; + // + state.ws = new WebSocket(url); + state.ws.onopen = (e) => { + console.log("[WS] Open", e) + }; + state.ws.onclose = (e) => { + console.log("[WS] Close", e); + state.ws = null; + // + clearTimeout(state.wsTimeout); + state.wsTimeout = setTimeout(() => { + this.commit('wsConnection'); + }, 3000); + }; + state.ws.onerror = (e) => { + console.log("[WS] Error", e); + state.ws = null; + // + clearTimeout(state.wsTimeout); + state.wsTimeout = setTimeout(() => { + this.commit('wsConnection'); + }, 3000); + }; + state.ws.onmessage = (e) => { + console.log("[WS] Message", e); + const msgDetail = state.method.jsonParse(event.data); + const {type, msgId} = msgDetail; + switch (type) { + case "open": + break + + case "receipt": + typeof state.wsCall[msgId] === "function" && state.wsCall[msgId](msgDetail.body, true); + delete state.wsCall[msgId]; + break + + default: + msgId && this.commit('wsSend', {type: 'receipt', msgId}); + state.wsMsg = msgDetail; + Object.values(state.wsListener).forEach((call) => { + if (typeof call === "function") { + try { + call(msgDetail); + } catch (err) { + console.log("[WS] Callerr", err); + } + } + }) + break + } + } + }, + + /** + * 发送 websocket 消息 + * @param state + * @param params {type, to, data, callback} + */ + wsSend(state, params) { + if (!state.method.isJson(params)) { + return; + } + const {type, to, data, callback} = params; + if (!state.ws) { + typeof callback === "function" && callback(null, false) + return; + } + if (typeof callback === "function") { + params.msgId = state.method.randomString(16) + state.wsCall[params.msgId] = callback; + } + try { + state.ws.send(JSON.stringify({ + type, + to, + data + })); + } catch (e) { + typeof callback === "function" && callback(null, false) + } + }, + + /** + * 监听消息 + * @param state + * @param params {name, callback} + */ + wsMsgListener(state, params) { + const {name, callback} = params; + if (typeof callback === "function") { + state.wsListener[name] = callback; + } else { + state.wsListener[name] && delete state.wsListener[name]; + } + }, + + /** + * 关闭 websocket + * @param state + */ + wsClose(state) { + state.ws && state.ws.close(); } } diff --git a/resources/assets/js/store/state.js b/resources/assets/js/store/state.js index 87e04d66..e2adee60 100644 --- a/resources/assets/js/store/state.js +++ b/resources/assets/js/store/state.js @@ -1,56 +1,63 @@ -const stateCommon = { +const method = { setStorage(key, value) { - return this._storage(key, value); + return this.storage(key, value); }, getStorage(key, def = null) { - let value = this._storage(key); + let value = this.storage(key); return value || def; }, getStorageString(key, def = '') { - let value = this._storage(key); + let value = this.storage(key); return typeof value === "string" || typeof value === "number" ? value : def; }, getStorageNumber(key, def = 0) { - let value = this._storage(key); + let value = this.storage(key); return typeof value === "number" ? value : def; }, getStorageBoolean(key, def = false) { - let value = this._storage(key); + let value = this.storage(key); return typeof value === "boolean" ? value : def; }, getStorageArray(key, def = {}) { - let value = this._storage(key); - return this._isArray(value) ? value : def; + let value = this.storage(key); + return this.isArray(value) ? value : def; }, getStorageJson(key, def = {}) { - let value = this._storage(key); - return this._isJson(value) ? value : def; + let value = this.storage(key); + return this.isJson(value) ? value : def; }, - _isArray(obj) { + isArray(obj) { return typeof (obj) == "object" && Object.prototype.toString.call(obj).toLowerCase() == '[object array]' && typeof obj.length == "number"; }, - _isJson(obj) { + isJson(obj) { return typeof (obj) == "object" && Object.prototype.toString.call(obj).toLowerCase() == "[object object]" && typeof obj.length == "undefined"; }, - _storage(key, value) { + inArray(key, array) { + if (!this.isArray(array)) { + return false; + } + return array.includes(key); + }, + + storage(key, value) { let keyName = 'state'; if (typeof value === 'undefined') { - return this._loadFromlLocal('__::', key, '', '__' + keyName + '__'); + return this.loadFromlLocal('__::', key, '', '__' + keyName + '__'); } else { - this._savaToLocal('__::', key, value, '__' + keyName + '__'); + this.savaToLocal('__::', key, value, '__' + keyName + '__'); } }, - _savaToLocal(id, key, value, keyName) { + savaToLocal(id, key, value, keyName) { try { if (typeof keyName === 'undefined') keyName = '__seller__'; let seller = window.localStorage[keyName]; @@ -69,7 +76,7 @@ const stateCommon = { } }, - _loadFromlLocal(id, key, def, keyName) { + loadFromlLocal(id, key, def, keyName) { try { if (typeof keyName === 'undefined') keyName = '__seller__'; let seller = window.localStorage[keyName]; @@ -86,29 +93,7 @@ const stateCommon = { } }, - _count(obj) { - try { - if (typeof obj === "undefined") { - return 0; - } - if (typeof obj === "number") { - obj += ""; - } - if (typeof obj.length === 'number') { - return obj.length; - } else { - let i = 0, key; - for (key in obj) { - i++; - } - return i; - } - } catch (e) { - return 0; - } - }, - - _runNum(str, fixed) { + runNum(str, fixed) { let _s = Number(str); if (_s + "" === "NaN") { _s = 0; @@ -126,13 +111,13 @@ const stateCommon = { return _s; }, - _cloneJSON(myObj) { - if(typeof(myObj) !== 'object') return myObj; - if(myObj === null) return myObj; - return this._jsonParse(this._jsonStringify(myObj)) + cloneJSON(myObj) { + if (typeof (myObj) !== 'object') return myObj; + if (myObj === null) return myObj; + return this.jsonParse(this.jsonStringify(myObj)) }, - _jsonParse(str, defaultVal) { + jsonParse(str, defaultVal) { if (str === null) { return defaultVal ? defaultVal : {}; } @@ -140,47 +125,54 @@ const stateCommon = { return str; } try { - return JSON.parse(str.replace(/\n/g,"\\n").replace(/\r/g,"\\r")); + return JSON.parse(str.replace(/\n/g, "\\n").replace(/\r/g, "\\r")); } catch (e) { return defaultVal ? defaultVal : {}; } }, - _jsonStringify(json, defaultVal) { + jsonStringify(json, defaultVal) { if (typeof json !== 'object') { return json; } - try{ + try { return JSON.stringify(json); - }catch (e) { + } catch (e) { return defaultVal ? defaultVal : ""; } - } + }, }; -const projectChatShow = stateCommon.getStorageBoolean('projectChatShow', true); -const projectListPanel = stateCommon.getStorageBoolean('projectListPanel', true); +// 方法类 +const state = { + method +}; -const taskMyShow = stateCommon.getStorageBoolean('taskMyShow', true); -const taskUndoneShow = stateCommon.getStorageBoolean('taskUndoneShow', true); -const taskCompletedShow = stateCommon.getStorageBoolean('taskCompletedShow', true); +// Boolean变量 +[ + 'projectChatShow', // 项目聊天显示 + 'projectListPanel', // 项目面板显示类型 + 'taskMyShow', // 项目面板显示我的任务 + 'taskUndoneShow', // 项目面板显示未完成任务 + 'taskCompletedShow' // 项目面板显示已完成任务 +].forEach((key) => { + state[key] = state.method.getStorageBoolean('boolean:' + key, true) +}) -const userInfo = stateCommon.getStorageJson('userInfo'); -const userId = userInfo.userid = stateCommon._runNum(userInfo.userid); -const userToken = userInfo.token; +// 会员信息 +state.userInfo = state.method.getStorageJson('userInfo'); +state.userId = state.userInfo.userid = state.method.runNum(state.userInfo.userid); +state.userToken = state.userInfo.token; +state.userIsAdmin = state.method.inArray('admin', state.userInfo.identity); -export default Object.assign(stateCommon, { - projectChatShow, - projectListPanel, - - taskMyShow, - taskUndoneShow, - taskCompletedShow, - - userId, - userInfo, - userToken, +// Websocket +state.ws = null; +state.wsMsg = {}; +state.wsCall = {}; +state.wsTimeout = null; +state.wsListener = {}; +export default Object.assign(state, { cacheProject: {}, cacheUserBasic: {},