From 95e91728a02a613440183c61a10e7e88af6a663d Mon Sep 17 00:00:00 2001 From: 15004070936 <1097449274@qq.com> Date: Fri, 10 Jul 2026 18:07:13 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App.vue | 399 ++++- common/date.js | 66 + common/emotion.js | 56 + common/enums.js | 68 + common/messageType.js | 40 + common/url.js | 15 + common/websocket-manager.js | 211 +++ common/wssocket.js | 159 ++ components/chat-at-box/chat-at-box.vue | 173 ++ .../chat-group-readed/chat-group-readed.vue | 147 ++ components/chat-item/chat-item.vue | 169 ++ .../chat-message-item/chat-message-item.vue | 559 ++++++ components/chat-record/chat-record.vue | 221 +++ components/chat/submit.vue | 1147 ++++++------ components/chat/submit1.vue | 591 +++++++ .../group-member-selector.vue | 180 ++ components/group-rtc-join/group-rtc-join.vue | 88 + components/head-image/head-image.vue | 124 ++ components/loading/loading.vue | 62 + .../long-press-menu/long-press-menu.vue | 114 ++ components/myVideo.vue | 48 +- components/nav-bar/nav-bar.vue | 114 ++ im-var.scss | 60 + im.scss | 217 +++ main.js | 49 +- package-lock.json | 21 + package.json | 1 + pages.json | 77 +- pages/book/booklist.vue | 50 +- pages/chat/chat.vue | 172 +- pages/chat/chat1.vue | 172 +- pages/chat/groupchat.vue | 216 +-- pages/chat/groupchat1.vue | 166 +- pages/chat/groupchat3.vue | 208 +-- pages/chatbox/chat-box.vue | 1135 ++++++++++++ pages/chatbox/chat-box1.vue | 1572 +++++++++++++++++ pages/chatbox/chat.vue | 339 ++++ pages/chatbox/chat2.vue | 174 ++ pages/login/login.vue | 43 +- pages/message/contact.vue | 100 +- pages/message/group.vue | 115 +- pages/message/group0.vue | 40 +- pages/message/group1.vue | 64 +- pages/message/group2.vue | 90 +- pages/product/cdetail.vue | 227 +-- pages/product/detail.vue | 146 +- pages/teacher/detail.vue | 78 +- static/icon/iconfont.css | 177 ++ static/icon/iconfont.ttf | Bin 0 -> 12100 bytes static/icon/order.png | Bin 0 -> 2796 bytes static/icon/shopping_bag.png | Bin 0 -> 6401 bytes store/chatStore.js | 751 ++++++++ store/friendStore.js | 146 ++ store/groupStore.js | 81 + store/index.js | 10 +- uni.scss | 3 +- 56 files changed, 9896 insertions(+), 1555 deletions(-) create mode 100644 common/date.js create mode 100644 common/emotion.js create mode 100644 common/enums.js create mode 100644 common/messageType.js create mode 100644 common/url.js create mode 100644 common/websocket-manager.js create mode 100644 common/wssocket.js create mode 100644 components/chat-at-box/chat-at-box.vue create mode 100644 components/chat-group-readed/chat-group-readed.vue create mode 100644 components/chat-item/chat-item.vue create mode 100644 components/chat-message-item/chat-message-item.vue create mode 100644 components/chat-record/chat-record.vue create mode 100644 components/chat/submit1.vue create mode 100644 components/group-member-selector/group-member-selector.vue create mode 100644 components/group-rtc-join/group-rtc-join.vue create mode 100644 components/head-image/head-image.vue create mode 100644 components/loading/loading.vue create mode 100644 components/long-press-menu/long-press-menu.vue create mode 100644 components/nav-bar/nav-bar.vue create mode 100644 im-var.scss create mode 100644 im.scss create mode 100644 pages/chatbox/chat-box.vue create mode 100644 pages/chatbox/chat-box1.vue create mode 100644 pages/chatbox/chat.vue create mode 100644 pages/chatbox/chat2.vue create mode 100644 static/icon/iconfont.css create mode 100644 static/icon/iconfont.ttf create mode 100644 static/icon/order.png create mode 100644 static/icon/shopping_bag.png create mode 100644 store/chatStore.js create mode 100644 store/friendStore.js create mode 100644 store/groupStore.js diff --git a/App.vue b/App.vue index 70a01bc..053cf56 100644 --- a/App.vue +++ b/App.vue @@ -3,25 +3,33 @@ //import checkupdate from "@/uni_modules/uni-upgrade-center-app/utils/check-update.js" //引进检查版本 // import callCheckVersion from '@/uni_modules/uni-upgrade-center-app/utils/call-check-version'; - + import { myCache } from '@/utils/utils.js'; - + import * as wsApi from './common/wssocket'; + import chatStore from "./store/chatStore"; + import * as enums from "./common/enums"; + import * as msgType from "./common/messageType"; //#ifdef APP-PLUS const jpushModule = uni.requireNativePlugin('JG-JPush'); //#endif - + export default { data() { return { userid:"", userName:"", userheadimg:"", + isInit: false, // 是否已经初始化 + isExit: false, // 是否已退出 + audioTip: null, + reconnecting: false // 正在重连标志 + } }, onLaunch() { // 判断版本更新 需要真机进行测试 //checkupdate(); - + //#ifdef APP-PLUS // 消息推送 // jpushModule.setLoggerEnable(true); @@ -30,7 +38,7 @@ // let connectEnable = result.connectEnable // console.log("jpush连接", connectEnable) // }); - + // 设置别名 (再登录时候设置) // var user= JSON.parse(uni.getStorageSync("user")); // if(user&&user.yhdm){ @@ -39,7 +47,7 @@ // 'sequence': 1 // }) // } - + // jpushModule.addTagAliasListener(result => { // let code = result.code // let sequence = result.sequence @@ -49,7 +57,7 @@ // let alias = result.alias // console.log(alias, '别名') // }); - + // // 通知事件回调 // jpushModule.addNotificationListener(result => { // let notificationEventType = result.notificationEventType @@ -78,19 +86,340 @@ // console.log("自定义消息", result) // }) //#endif - + var user = myCache('user'); - this.userid = user.userid? user.userid:''; + this.userId = user.userid? user.userid:''; this.userName=user.nickName?user.nickName:""; this.userheadimg=user.avatar?user.avatar:require("@/static/image/girl.png"); - +//--------------------------------------------------- // 接收信息 - if(uni.getStorageSync("token")&&this.userid){ - this.startHeartbeat(); - } + // if(uni.getStorageSync("token")&&this.userid){ + // this.startHeartbeat(); + // } +//--------------------------------------------------- + + // 登录状态校验 + let token =uni.getStorageSync("token"); + if(token){ + this.init(this.userId); + }else { + // 跳转到登录页 + uni.navigateTo({ + url: "/pages/login/login" + }) + } + +//--------------------------------------------------- }, methods: { +//--------------------------------------------------- + init(userId) { + + if (!this.userId && userId){ + this.userId=userId; + } + this.reconnecting = false; + this.isExit = false; + // 加载数据 + this.loadStore().then(() => { + // 初始化websocket + this.initWebSocket(); + this.isInit = true; + }).catch((e) => { + console.log(e); + this.exit(); + }) + }, + // ws接收消息 + initWebSocket(){ + let token = uni.getStorageSync("token"); + wsApi.connect(this.$wsUrl, token); + wsApi.onConnect(() => { + if (this.reconnecting) { + // 重连成功 + this.onReconnectWs(); + } else { + // 加载离线消息 + this.pullPrivateOfflineMessage(this.$store.state.chatStore.privateMsgMaxId); + this.pullGroupOfflineMessage(this.$store.state.chatStore.groupMsgMaxId); + + } + }); + wsApi.onMessage((cmd, msgInfo) => { + if (cmd == 2) { + // 异地登录,强制下线 + uni.showModal({ + content: '您已在其他地方登录,将被强制下线', + showCancel: false, + }) + this.exit(); + } else if (cmd == 3) { + // 私聊消息 + this.handlePrivateMessage(msgInfo); + } else if (cmd == 4) { + // 群聊消息 + this.handleGroupMessage(msgInfo); + } else if (cmd == 5) { + // 系统消息 + this.handleSystemMessage(msgInfo); + } + }); + wsApi.onClose((res) => { + console.log("ws断开", res); + // 重新连接 + this.reconnectWs(); + + }) + + }, + loadStore() { + const promises = []; + promises.push(this.$store.dispatch('chatStore/loadChat')); + promises.push(this.$store.dispatch('groupStore/loadGroup')); + promises.push(this.$store.dispatch('friendStore/loadFriend')); + return Promise.all(promises); + }, + reconnectWs() { + // 已退出则不再重连 + if (this.isExit) { + return; + } + // 记录标志 + this.reconnecting = true; + // 重新加载一次个人信息,目的是为了保证网络已经正常且token有效 + uni.$http.get('/api/getInfo').then((res) => { + uni.showToast({ + title: '连接已断开,尝试重新连接...', + icon: 'none' + }) + // 重新连接 + let token = uni.getStorageSync("token"); + wsApi.reconnect(this.$wsUrl, token); + }).catch(() => { + // 5s后重试 + setTimeout(() => { + this.reconnectWs(); + }, 5000) + }) + }, + exit() { + console.log("exit"); + this.isExit = true; + wsApi.close(3099); + uni.removeStorageSync("token"); + uni.reLaunch({ + url: "/pages/login/login" + }) + this.unloadStore(); + }, + unloadStore() { + this.$store.dispatch('chatStore/clear') + }, + async pullPrivateOfflineMessage(minId) { + this.$store.dispatch('chatStore/setLoadingPrivateMsg',true) + try { + console.log("拉取私信") + await uni.$http.get("/api/message/private/pullOfflineMessage", { minId:minId }); + } catch (error) { + console.error("pullPrivateOfflineMessage error", error); + } finally { + this.$store.dispatch('chatStore/setLoadingPrivateMsg',false) + } + }, + async pullGroupOfflineMessage(minId) { + this.$store.dispatch('chatStore/setLoadingGroupMsg',true) + try { + console.log("拉取群聊") + await uni.$http.get('/api/message/group/pullOfflineMessage',{minId:minId}); + } catch (error) { + console.error("pullGroupOfflineMessage error", error); + } finally { + this.$store.dispatch('chatStore/setLoadingGroupMsg',false) + } + }, + handlePrivateMessage(msg) { + msg.selfSend = msg.sendId === this.userId; + const friendId = msg.selfSend ? msg.recvId : msg.sendId; + const chatInfo = { type: 'PRIVATE', targetId: friendId }; + const type=Number(msg.type) + if (type === enums.MESSAGE_TYPE.LOADING) { + this.$store.dispatch('chatStore/setLoadingPrivateMsg', JSON.parse(msg.content)); + return; + } + + if (type === enums.MESSAGE_TYPE.READED) { + this.$store.dispatch('chatStore/resetUnreadCount', chatInfo); + return; + } + // 消息回执处理,改消息状态为已读 + if (type === enums.MESSAGE_TYPE.RECEIPT) { + console.log("消息回执处理") + this.$store.dispatch('chatStore/readedMessage', { friendId: msg.sendId }); + return; + } + if (type === enums.MESSAGE_TYPE.RECALL) { + this.$store.dispatch('chatStore/recallMessage', { msgInfo: msg, chatInfo }); + return; + } + if (type === enums.MESSAGE_TYPE.FRIEND_NEW) { + this.$store.dispatch('friendStore/addFriend', JSON.parse(msg.content)); + // this.friendStore.addFriend(JSON.parse(msg.content)); + return; + } + // if (msg.type === enums.MESSAGE_TYPE.FRIEND_DEL) { + // this.friendStore.removeFriend(friendId); + // return; + // } + + // 视频信令处理 + // if (msgType.isRtcPrivate(msg.type)) { + // this.handleRtcPrivate(msg); + // return; + // } + + // 普通消息插入 + if (msgType.isNormal(type) || msgType.isTip(type) || msgType.isAction(type)) { + const friend = this.loadFriendInfo(friendId); + + this.insertPrivateMessage(friend, msg); + } + }, + + handleGroupMessage(msg) { + msg.selfSend = msg.sendId === this.userId; + const chatInfo = { type: 'GROUP', targetId: msg.groupId }; + const type=Number(msg.type) + if (type === enums.MESSAGE_TYPE.LOADING) { + console.log("收到setLoadingGroupMsg") + this.$store.dispatch('chatStore/setLoadingGroupMsg', JSON.parse(msg.content)); + return; + } + if (type === enums.MESSAGE_TYPE.READED) { + this.$store.dispatch('chatStore/resetUnreadCount', chatInfo); + return; + } + if (type === enums.MESSAGE_TYPE.RECEIPT) { + this.$store.dispatch('chatStore/updateMessage', { + msgInfo: { + id: msg.id, + groupId: msg.groupId, + readedCount: msg.readedCount, + receiptOk: msg.receiptOk, + }, + chatInfo + }); + return; + } + if (type === enums.MESSAGE_TYPE.RECALL) { + this.$store.dispatch('chatStore/recallMessage', { msgInfo: msg, chatInfo }); + return; + } + if (type === enums.MESSAGE_TYPE.GROUP_NEW) { + this.$store.dispatch('groupStore/addGroup', JSON.parse(msg.content)); + return; + } + if (type === enums.MESSAGE_TYPE.GROUP_DEL) { + this.$store.dispatch('groupStore/removeGroup', msg.groupId); + return; + } + + // 群组视频信令 + // if (msgType.isRtcGroup(msg.type)) { + // this.handleRtcGroup(msg); + // return; + // } + + if (msgType.isNormal(type) || msgType.isTip(type) || msgType.isAction(type)) { + const group = this.loadGroupInfo(msg.groupId); + this.insertGroupMessage(group, msg); + } + }, + + handleSystemMessage(msg) { + if (msg.type === enums.MESSAGE_TYPE.USER_BANNED) { + wsApi.close(3099); + uni.showModal({ + content: `您的账号已被管理员封禁,原因:${msg.content}`, + showCancel: false, + }); + this.exit(); + } + }, + insertPrivateMessage(friend,msg) { + const chatInfo = { + type: 'PRIVATE', + targetId: msg.sendId, + showName: friend.nickName, + headImage: friend.headImage, + }; + this.$store.dispatch('chatStore/openChat', chatInfo); + this.$store.dispatch('chatStore/insertMessage', { msgInfo: msg, chatInfo }); + this.playAudioTip(); + }, + + insertGroupMessage(group, msg) { + const chatInfo = { + type: 'GROUP', + targetId: group.id, + showName: group.name, + headImage: group.headImageThumb, + }; + this.$store.dispatch('chatStore/openChat', chatInfo); + this.$store.dispatch('chatStore/insertMessage', { msgInfo: msg, chatInfo }); + this.playAudioTip(); + }, + onReconnectWs() { + this.reconnecting = false; + Promise.all([ + this.$store.dispatch('friendStore/loadFriend'), + this.$store.dispatch('groupStore/loadGroup') + ]).then(() => { + uni.showToast({ + title: "已重新连接", + icon: 'none' + }); + // 加载离线消息 + const chatState = this.$store.state.chatStore; + this.pullPrivateOfflineMessage(chatState.privateMsgMaxId); + this.pullGroupOfflineMessage(chatState.groupMsgMaxId); + }).catch((e) => { + console.log(e); + this.exit(); + }) + }, + loadFriendInfo(id, callback) { + let friend = this.$store.getters['friendStore/findFriend'](id) + if (!friend) { + console.log("未知用户:", id) + friend = { + id: id, + nickName: "未知用户", + headImage: "" + } + } + return friend; + }, + loadGroupInfo(id) { + let group = this.$store.getters['groupStore/findGroup'](id) + if (!group) { + group = { + id: id, + showGroupName: "未知群聊", + headImageThumb: "" + } + } + return group; + }, + playAudioTip() { + // 音频播放实现(如需) + // this.audioTip = uni.createInnerAudioContext(); + // this.audioTip.src = "/static/audio/tip.wav"; + // this.audioTip.play(); + }, +//--------------------------------------------------- + // 定时心跳包 + /** startHeartbeat() { console.log("startHeartbeat"); var that=this; @@ -150,7 +479,7 @@ console.log(res,'socket连接成功complete'); } }); - + uni.onSocketOpen(resopen => { console.log('WebSocket连接已打开!'); this.isConnected = true; @@ -171,12 +500,12 @@ console.log('消息发送失败!') } }); - + // 定时发送心跳包 this.startHeartbeat(); - + }); - + // 连接关闭后重新连接 uni.onSocketClose(res => { console.log('WebSocket连接已关闭!',res); @@ -193,7 +522,7 @@ // 重新连接socket this.reconnect(); }); - + // 接收信息 uni.onSocketMessage(res => { console.log('收到WebSocket服务器消息:'); @@ -210,7 +539,7 @@ // 消息类型 0:文字 1:图片 2:文件 3:语音 4:视频 10, "撤回" 11, "已读 "12, "消息已读回执 " 30,"加载中标记" if(data.type==0||data.type==1||data.type==2||data.type==3||data.type==4||data.type==5||data.type==6){ if(data.type==0){ - data.content=decodeURIComponent(data.content); + data.content=decodeURIComponent(data.content); } var id="privatechat-" + data.recvId +"-" + data.sendId; var index=this.ifadd(id); @@ -264,11 +593,11 @@ // 加入聊天记录 // 消息类型 0:文字 1:图片 2:文件 3:语音 4:视频 10, "撤回" 11, "已读 "12, "消息已读回执 " 30,"加载中标记" if(data.type==0||data.type==1||data.type==2||data.type==3||data.type==4||data.type==5||data.type==6){ - + if(data.type==0){ - data.content=decodeURIComponent(data.content); + data.content=decodeURIComponent(data.content); } - + var id="groupchat-" + data.groupId; var index=this.ifadd(id); var chatinfo=null; @@ -311,14 +640,14 @@ } else{ // 10, "撤回" 11, "已读 " 12, "消息已读回执 " 30,"加载中标记" - + } } } } } }); - + }, // 消息是否存在列表中 ifadd(id){ @@ -398,7 +727,7 @@ // 重新排序 保存缓存 this.reorder(); myCache("chatlist-"+ this.userid,this.grouplist); - + // 聊天框消息缓存 // 获取发送人的信息 var name=info[info.sendId]?info[info.sendId].name:"匿名",img=info[info.sendId]?info[info.sendId].img:require("@/static/image/girl.png"); @@ -446,7 +775,7 @@ msgchat.push(firstmsg); myCache(info.id,msgchat); } - + } }, async updateChatList(info){ @@ -466,7 +795,7 @@ // 重新排序 保存缓存 this.reorder(); myCache("chatlist-"+this.userid,this.grouplist); - + // 聊天框消息缓存 // 获取发送人的信息 var name=info[info.fromuser]?info[info.fromuser].name:"匿名",img=info[info.fromuser]?info[info.fromuser].img:require("@/static/image/girl.png"); @@ -516,11 +845,13 @@ myCache(info.id,msgchat); } } - + }, - + **/ + }, - + + } @@ -530,6 +861,8 @@ @import "common/demo.scss"; @import 'colorui/main.css'; @import 'colorui/icon.css'; + @import "/im.scss"; + @import url('./static/icon/iconfont.css'); body{ font-family: 'FZLTZHUNHJW--GB1-0'; } @@ -567,7 +900,7 @@ .page { width: 100%; } - + /* 当屏幕宽度小于600px时,调整布局 */ @media (max-width: 600px) { .page { @@ -602,7 +935,7 @@ .imgload{ background-image: url('/static/image/nopic.png'); background-size: 66% auto; - background-position: center center; + background-position: center center; background-repeat: no-repeat; } - \ No newline at end of file + diff --git a/common/date.js b/common/date.js new file mode 100644 index 0000000..cd4fc17 --- /dev/null +++ b/common/date.js @@ -0,0 +1,66 @@ +let toTimeText = (timeStamp, simple) => { + var dateTime = new Date(timeStamp) + var currentTime = Date.parse(new Date()); //当前时间 + var timeDiff = currentTime - dateTime; //与当前时间误差 + var timeText = ''; + if (timeDiff <= 60000) { //一分钟内 + timeText = '刚刚'; + } else if (timeDiff > 60000 && timeDiff < 3600000) { + //1小时内 + timeText = Math.floor(timeDiff / 60000) + '分钟前'; + } else if (timeDiff >= 3600000 && timeDiff < 86400000 && !isYestday(dateTime)) { + //今日 + timeText = formatDateTime(dateTime).substr(11, 5); + } else if (isYestday(dateTime)) { + //昨天 + timeText = '昨天' + formatDateTime(dateTime).substr(11, 5); + } else if (isYear(dateTime)) { + //今年 + timeText = formatDateTime(dateTime).substr(5, simple ? 5 : 14); + } else { + //不属于今年 + timeText = formatDateTime(dateTime); + if (simple) { + timeText = timeText.substr(2, 8); + } + } + return timeText; +} + +let isYestday = (date) => { + var yesterday = new Date(new Date() - 1000 * 60 * 60 * 24); + return yesterday.getYear() === date.getYear() && + yesterday.getMonth() === date.getMonth() && + yesterday.getDate() === date.getDate(); +} + +let isYear = (date) => { + return date.getYear() === new Date().getYear(); +} + +let formatDateTime = (date) => { + if (date === '' || !date) { + return '' + } + var dateObject = new Date(date) + var y = dateObject.getFullYear() + var m = dateObject.getMonth() + 1 + m = m < 10 ? ('0' + m) : m + var d = dateObject.getDate() + d = d < 10 ? ('0' + d) : d + var h = dateObject.getHours() + h = h < 10 ? ('0' + h) : h + var minute = dateObject.getMinutes() + minute = minute < 10 ? ('0' + minute) : minute + var second = dateObject.getSeconds() + second = second < 10 ? ('0' + second) : second + return y + '/' + m + '/' + d + ' ' + h + ':' + minute + ':' + second +} + + +export { + toTimeText, + isYestday, + isYear, + formatDateTime +} \ No newline at end of file diff --git a/common/emotion.js b/common/emotion.js new file mode 100644 index 0000000..405734f --- /dev/null +++ b/common/emotion.js @@ -0,0 +1,56 @@ +// 表情图片基础路径(若环境变量未定义,使用默认路径 /static/emoji/) +const EMO_BASE_URL = '/static/emoji/' + +const emoTextList = [ + '憨笑', '媚眼', '开心', '坏笑', '可怜', '爱心', '笑哭', '拍手', '惊喜', '打气', + '大哭', '流泪', '饥饿', '难受', '健身', '示爱', '色色', '眨眼', '暴怒', '惊恐', + '思考', '头晕', '大吐', '酷笑', '翻滚', '享受', '鼻涕', '快乐', '雀跃', '微笑', + '贪婪', '红心', '粉心', '星星', '大火', '眼睛', '音符', '叹号', '问号', '绿叶', + '燃烧', '喇叭', '警告', '信封', '房子', '礼物', '点赞', '举手', '拍手', '点头', + '摇头', '偷瞄', '庆祝', '疾跑', '打滚', '惊吓', '起跳' +] + +const regex = /\#[\u4E00-\u9FA5]{1,3}\;/gi + +/** + * 判断文本是否包含表情标记 + * @param {string} content 文本内容 + * @returns {boolean} + */ +const containEmoji = (content) => regex.test(content) + +/** + * 将文本中的表情标记替换为 标签 + * @param {string} content 原始文本 + * @param {string} extClass 图片的 CSS 类名 + * @returns {string} 替换后的 HTML 字符串 + */ +const transform = (content, extClass) => { + return content.replace(regex, (emoText) => { + const word = emoText.replace(/\#|\;/g, '') + const idx = emoTextList.indexOf(word) + if (idx === -1) return emoText + const path = textToPath(emoText) + return `` + }) +} + +/** + * 根据表情标记生成图片路径 + * @param {string} emoText 表情标记,如 '#开心;' + * @returns {string} 图片完整路径 + */ +const textToPath = (emoText) => { + const word = emoText.replace(/\#|\;/g, '') + const idx = emoTextList.indexOf(word) + // 若找不到对应表情,返回占位图或空字符串(此处返回原标记) + if (idx === -1) return emoText + return EMO_BASE_URL + idx + '.gif' +} + +export default { + containEmoji, + emoTextList, + transform, + textToPath +} diff --git a/common/enums.js b/common/enums.js new file mode 100644 index 0000000..1521c87 --- /dev/null +++ b/common/enums.js @@ -0,0 +1,68 @@ + +const MESSAGE_TYPE = { + TEXT: 0, + IMAGE: 1, + FILE: 2, + AUDIO: 3, + VIDEO: 4, + ORDER: 5, + PRODUCT: 6, + RECALL: 10, + READED: 11, + RECEIPT: 12, + TIP_TIME: 20, + TIP_TEXT: 21, + LOADING: 30, + ACT_RT_VOICE: 40, + ACT_RT_VIDEO: 41, + USER_BANNED: 50, + FRIEND_NEW: 80, + FRIEND_DEL: 81, + GROUP_NEW: 90, + GROUP_DEL: 91, + RTC_CALL_VOICE: 100, + RTC_CALL_VIDEO: 101, + RTC_ACCEPT: 102, + RTC_REJECT: 103, + RTC_CANCEL: 104, + RTC_FAILED: 105, + RTC_HANDUP: 106, + RTC_CANDIDATE: 107, + RTC_GROUP_SETUP: 200, + RTC_GROUP_ACCEPT: 201, + RTC_GROUP_REJECT: 202, + RTC_GROUP_FAILED: 203, + RTC_GROUP_CANCEL: 204, + RTC_GROUP_QUIT: 205, + RTC_GROUP_INVITE: 206, + RTC_GROUP_JOIN: 207, + RTC_GROUP_OFFER: 208, + RTC_GROUP_ANSWER: 209, + RTC_GROUP_CANDIDATE: 210, + RTC_GROUP_DEVICE: 211 +} + +const USER_STATE = { + OFFLINE: 0, + FREE: 1, + BUSY: 2 +} + +const TERMINAL_TYPE = { + WEB: 0, + APP: 1 +} + +const MESSAGE_STATUS = { + UNSEND: 0, + SENDED: 1, + RECALL: 2, + READED: 3 +} + +export { + MESSAGE_TYPE, + USER_STATE, + TERMINAL_TYPE, + MESSAGE_STATUS +} diff --git a/common/messageType.js b/common/messageType.js new file mode 100644 index 0000000..f6b1caf --- /dev/null +++ b/common/messageType.js @@ -0,0 +1,40 @@ + +// 是否普通消息 +let isNormal = function (type) { + return type >= 0 && type < 10; +} + +// 是否状态消息 +let isStatus = function (type) { + return type >= 10 && type < 20; +} + +// 是否提示消息 +let isTip = function (type) { + return type >= 20 && type < 30; +} + +// 操作交互类消息 +let isAction = function (type) { + return type >= 40 && type < 50; +} + +// 单人通话信令 +let isRtcPrivate = function (type) { + return type >= 100 && type < 200; +} + +// 多人通话信令 +let isRtcGroup = function (type) { + return type >= 200 && type < 300; +} + + +export { + isNormal, + isStatus, + isTip, + isAction, + isRtcPrivate, + isRtcGroup +} \ No newline at end of file diff --git a/common/url.js b/common/url.js new file mode 100644 index 0000000..ff04275 --- /dev/null +++ b/common/url.js @@ -0,0 +1,15 @@ +let replaceURLWithHTMLLinks = (content, color) => { + // 使用正则表达式匹配更广泛的URL格式(此正则由deepseek生成) + const urlRegex = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|]|\bwww\.[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig; + return content.replace(urlRegex, (url) => { + // 如果URL不以http(s)://开头,则添加http://前缀 + if (!url.startsWith("http")) { + url = "http://" + url; + } + return `${url}`; + }); +} + +export default { + replaceURLWithHTMLLinks +} \ No newline at end of file diff --git a/common/websocket-manager.js b/common/websocket-manager.js new file mode 100644 index 0000000..b808624 --- /dev/null +++ b/common/websocket-manager.js @@ -0,0 +1,211 @@ +// common/websocket-manager.js + +import * as wsApi from './wssocket'; +import * as msgType from './messageType'; +import * as enums from './enums'; + +class WebSocketManager { + constructor() { + if (WebSocketManager.instance) return WebSocketManager.instance; + WebSocketManager.instance = this; + + // 状态标志 + this.isInit = false; + this.isExit = false; + this.reconnecting = false; + this.audioTip = null; + this.WS_URL=this.$wsUrl + + // Store 引用(需外部注入) + this.chatStore = null; + this.userId=null; + // 事件监听器(供页面注册) + this.messageListeners = []; + } + + /** + * 初始化 WebSocket 管理器 + * @param {Object} stores - 各个 store 实例 + * @param {string} accessToken - 用户 token + */ + init(stores, accessToken) { + if (this.isInit) return; + + this.chatStore = stores.chatStore; + this.userId=stores.userId; + this.isExit = false; + this.reconnecting = false; + + // 加载本地数据并启动 WebSocket + this.loadStore() + .then(() => { + this.initWebSocket(accessToken); + this.isInit = true; + }) + .catch((e) => { + console.error(e); + this.exit(); + }); + } + + initWebSocket(accessToken) { + wsApi.connect(this.WS_URL, accessToken); + + wsApi.onConnect(() => { + if (this.reconnecting) { + this.onReconnectWs(); + } else { + // 首次连接,拉取离线消息 + this.pullPrivateOfflineMessage(this.chatStore.privateMsgMaxId).catch(() => {}); + this.pullGroupOfflineMessage(this.chatStore.groupMsgMaxId).catch(() => {}); + } + }); + + wsApi.onMessage((cmd, msgInfo) => { + // 分发原始消息给外部监听器 + this.messageListeners.forEach(listener => listener(cmd, msgInfo)); + + // 内部业务处理 + if (cmd == 2) { + // 异地登录 + uni.showModal({ + content: '您已在其他地方登录,将被强制下线', + showCancel: false, + }); + this.exit(); + } else if (cmd == 3) { + this.handlePrivateMessage(msgInfo); + } else if (cmd == 4) { + this.handleGroupMessage(msgInfo); + } else if (cmd == 5) { + this.handleSystemMessage(msgInfo); + } + }); + + wsApi.onClose((res) => { + console.log("ws断开", res); + this.reconnect(accessToken); + }); + } + + + /** + * 重新连接(token 刷新后调用) + * @param {string} accessToken + */ + reconnect(accessToken) { + if (this.isExit) return; + this.reconnecting = true; + wsApi.reconnect(this.WS_URL, accessToken); + } + + /** + * 关闭连接并退出登录 + */ + exit() { + console.log("WebSocket exit"); + this.isExit = true; + wsApi.close(3099); + // uni.removeStorageSync("loginInfo"); + // uni.reLaunch({ url: "/pages/login/login" }); + this.unloadStore(); + } + + /** + * 注册消息监听器(页面可用来接收新消息通知) + * @param {Function} listener - (cmd, msgInfo) => {} + */ + addMessageListener(listener) { + if (typeof listener === 'function') { + this.messageListeners.push(listener); + } + } + + /** + * 移除消息监听器 + */ + removeMessageListener(listener) { + const index = this.messageListeners.indexOf(listener); + if (index !== -1) this.messageListeners.splice(index, 1); + } + + // ============= 私有方法 ============= + + + // 视频通话相关(可根据需要保留或删除) + handleRtcPrivate(msg) { + // #ifdef MP-WEIXIN + // return; // 小程序不支持 + // // #endif + // let delayTime = 100; + // if (msg.type === enums.MESSAGE_TYPE.RTC_CALL_VOICE || msg.type === enums.MESSAGE_TYPE.RTC_CALL_VIDEO) { + // const mode = msg.type === enums.MESSAGE_TYPE.RTC_CALL_VIDEO ? "video" : "voice"; + // const pages = getCurrentPages(); + // const curPage = pages[pages.length - 1].route; + // if (curPage !== "pages/chat/chat-private-video") { + // const friend = this.loadFriendInfo(msg.selfSend ? msg.recvId : msg.sendId); + // const friendInfo = encodeURIComponent(JSON.stringify(friend)); + // uni.navigateTo({ + // url: `/pages/chat/chat-private-video?mode=${mode}&friend=${friendInfo}&isHost=false`, + // }); + // delayTime = 500; + // } + // } + // setTimeout(() => { + // uni.$emit('WS_RTC_PRIVATE', msg); + // }, delayTime); + } + + handleRtcGroup(msg) { + // #ifdef MP-WEIXIN + return; + // #endif + let delayTime = 100; + if (msg.type === enums.MESSAGE_TYPE.RTC_GROUP_SETUP) { + const pages = getCurrentPages(); + const curPage = pages[pages.length - 1].route; + if (curPage !== "pages/chat/chat-group-video") { + const userInfos = encodeURIComponent(msg.content); + const inviterId = msg.sendId; + const groupId = msg.groupId; + uni.navigateTo({ + url: `/pages/chat/chat-group-video?groupId=${groupId}&isHost=false&inviterId=${inviterId}&userInfos=${userInfos}`, + }); + delayTime = 500; + } + } + setTimeout(() => { + uni.$emit('WS_RTC_GROUP', msg); + }, delayTime); + } + + + + playAudioTip() { + // 音频播放实现(如需) + // this.audioTip = uni.createInnerAudioContext(); + // this.audioTip.src = "/static/audio/tip.wav"; + // this.audioTip.play(); + } + + async refreshToken(loginInfo) { + // if (!loginInfo || !loginInfo.refreshToken) { + // throw new Error("No refreshToken"); + // } + // const newLoginInfo = await http({ + // url: '/refreshToken', + // method: 'PUT', + // header: { refreshToken: loginInfo.refreshToken }, + // }); + // uni.setStorageSync("loginInfo", newLoginInfo); + // return newLoginInfo; + } + + + + +} + +// 导出单例 +const wsManager = new WebSocketManager(); +export default wsManager; diff --git a/common/wssocket.js b/common/wssocket.js new file mode 100644 index 0000000..150aa0c --- /dev/null +++ b/common/wssocket.js @@ -0,0 +1,159 @@ +let accessToken = ""; +let messageCallBack = null; +let closeCallBack = null; +let connectCallBack = null; +let isConnect = false; //连接标识 避免重复连接 +let rec = null; +let lastConnectTime = new Date(); // 最后一次连接时间 +let socketTask = null; + +let connect = (wsurl, token) => { + accessToken = token; + if (isConnect) { + return; + } + lastConnectTime = new Date(); + socketTask = uni.connectSocket({ + url: wsurl, + success: (res) => { + console.log("websocket连接成功"); + }, + fail: (e) => { + console.log(e); + console.log("websocket连接失败,10s后重连"); + setTimeout(() => { + connect(); + }, 10000) + } + }); + + socketTask.onOpen((res) => { + console.log("WebSocket连接已打开"); + isConnect = true; + // 发送登录命令 + let loginInfo = { + cmd: 0, + data: { + accessToken: accessToken + } + }; + socketTask.send({ + data: JSON.stringify(loginInfo) + }); + }) + + socketTask.onMessage((res) => { + let sendInfo = JSON.parse(res.data) + if (sendInfo.cmd == 0) { + heartCheck.start() + connectCallBack && connectCallBack(); + console.log('WebSocket登录成功') + } else if (sendInfo.cmd == 1) { + // 重新开启心跳定时 + heartCheck.reset(); + } else { + // 其他消息转发出去 + // console.log("接收到消息", sendInfo); + messageCallBack && messageCallBack(sendInfo.cmd, sendInfo.data) + // console.log("cmd",sendInfo.cmd) + // console.log("msgInfo",sendInfo.data) + + } + }) + + socketTask.onClose((res) => { + console.log('WebSocket连接关闭') + isConnect = false; + closeCallBack && closeCallBack(res); + }) + + socketTask.onError((e) => { + console.log(e) + isConnect = false; + // APP 应用切出超过一定时间(约1分钟)会触发报错,此处回调给应用进行重连 + closeCallBack && closeCallBack({ code: 1006 }); + }) +} + +//定义重连函数 +let reconnect = (wsurl, accessToken) => { + console.log("尝试重新连接"); + if (isConnect) { + return; + } + // 延迟10秒重连 避免过多次过频繁请求重连 + let timeDiff = new Date().getTime() - lastConnectTime.getTime() + let delay = timeDiff < 10000 ? 10000 - timeDiff : 0; + rec && clearTimeout(rec); + rec = setTimeout(function() { + connect(wsurl, accessToken); + }, delay); +}; + +//设置关闭连接 +let close = (code) => { + if (!isConnect) { + return; + } + socketTask.close({ + code: code, + complete: (res) => { + console.log("关闭websocket连接"); + isConnect = false; + }, + fail: (e) => { + console.log("关闭websocket连接失败", e); + } + }); +}; + + +// 心跳设置 +let heartCheck = { + timeout: 20000, // 每段时间发送一次心跳包 这里设置为20s + timeoutObj: null, // 延时发送消息对象(启动心跳新建这个对象,收到消息后重置对象) + start: function() { + if (isConnect) { + console.log('发送WebSocket心跳') + let heartBeat = { + cmd: 1, + data: {} + }; + sendMessage(JSON.stringify(heartBeat)) + } + }, + reset: function() { + clearTimeout(this.timeoutObj); + this.timeoutObj = setTimeout(() => heartCheck.start(), this.timeout); + } +}; + +let sendMessage = (message) => { + socketTask.send({ data: message }) +} + +let onConnect = (callback) => { + connectCallBack = callback; +} + + +let onMessage = (callback) => { + messageCallBack = callback; +} + + +let onClose = (callback) => { + closeCallBack = callback; +} + + +// 将方法暴露出去 +export { + connect, + reconnect, + close, + sendMessage, + onConnect, + onMessage, + onClose +} diff --git a/components/chat-at-box/chat-at-box.vue b/components/chat-at-box/chat-at-box.vue new file mode 100644 index 0000000..8f7c257 --- /dev/null +++ b/components/chat-at-box/chat-at-box.vue @@ -0,0 +1,173 @@ + + + + + + \ No newline at end of file diff --git a/components/chat-group-readed/chat-group-readed.vue b/components/chat-group-readed/chat-group-readed.vue new file mode 100644 index 0000000..f43cf90 --- /dev/null +++ b/components/chat-group-readed/chat-group-readed.vue @@ -0,0 +1,147 @@ + + + + + diff --git a/components/chat-item/chat-item.vue b/components/chat-item/chat-item.vue new file mode 100644 index 0000000..0de7bcf --- /dev/null +++ b/components/chat-item/chat-item.vue @@ -0,0 +1,169 @@ + + + + + diff --git a/components/chat-message-item/chat-message-item.vue b/components/chat-message-item/chat-message-item.vue new file mode 100644 index 0000000..168eba2 --- /dev/null +++ b/components/chat-message-item/chat-message-item.vue @@ -0,0 +1,559 @@ + + + + + diff --git a/components/chat-record/chat-record.vue b/components/chat-record/chat-record.vue new file mode 100644 index 0000000..7d55af0 --- /dev/null +++ b/components/chat-record/chat-record.vue @@ -0,0 +1,221 @@ + + + + + diff --git a/components/chat/submit.vue b/components/chat/submit.vue index 5905484..2f7a5fc 100644 --- a/components/chat/submit.vue +++ b/components/chat/submit.vue @@ -1,590 +1,617 @@ diff --git a/components/chat/submit1.vue b/components/chat/submit1.vue new file mode 100644 index 0000000..5c97eed --- /dev/null +++ b/components/chat/submit1.vue @@ -0,0 +1,591 @@ + + + + + diff --git a/components/group-member-selector/group-member-selector.vue b/components/group-member-selector/group-member-selector.vue new file mode 100644 index 0000000..58d9cd8 --- /dev/null +++ b/components/group-member-selector/group-member-selector.vue @@ -0,0 +1,180 @@ + + + + + + + \ No newline at end of file diff --git a/components/group-rtc-join/group-rtc-join.vue b/components/group-rtc-join/group-rtc-join.vue new file mode 100644 index 0000000..6ee2459 --- /dev/null +++ b/components/group-rtc-join/group-rtc-join.vue @@ -0,0 +1,88 @@ + + + + + \ No newline at end of file diff --git a/components/head-image/head-image.vue b/components/head-image/head-image.vue new file mode 100644 index 0000000..90a50a7 --- /dev/null +++ b/components/head-image/head-image.vue @@ -0,0 +1,124 @@ + + + + + diff --git a/components/loading/loading.vue b/components/loading/loading.vue new file mode 100644 index 0000000..a56b8ea --- /dev/null +++ b/components/loading/loading.vue @@ -0,0 +1,62 @@ + + + + + \ No newline at end of file diff --git a/components/long-press-menu/long-press-menu.vue b/components/long-press-menu/long-press-menu.vue new file mode 100644 index 0000000..e401154 --- /dev/null +++ b/components/long-press-menu/long-press-menu.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/components/myVideo.vue b/components/myVideo.vue index aa04541..de5c77f 100644 --- a/components/myVideo.vue +++ b/components/myVideo.vue @@ -6,31 +6,31 @@ export default { name: "myVideo", props: { - videoUrl: { - // 确定按钮颜色 - type: String, - default: "", - }, + videoUrl: { + // 确定按钮颜色 + type: String, + default: "", + }, }, data() { - return { - onloadCode: ` - const url = '${this.videoUrl}' - this.contentWindow.document.body.innerHTML = ''; - const iframe = top.document.getElementsByTagName('iframe')[0] - var evObj = document.createEvent('MouseEvents'); - evObj.initEvent( 'event1', true, false ); - iframe.dispatchEvent(evObj) - ` - // onloadCode: ` - // const url = 'https://vd3.bdstatic.com/mda-mja2qsy47mbyh1xc/sc/cae_h264_clips/1633918903861514556/mda-mja2qsy47mbyh1xc.mp4?auth_key=1634030413-0-0-7898726f7bd328302e2119fdf694fd5e&bcevod_channel=searchbox_feed&pd=1&pt=3&abtest=' - // this.contentWindow.document.body.innerHTML = ''; - // const iframe = top.document.getElementsByTagName('iframe')[0] - // var evObj = document.createEvent('MouseEvents'); - // evObj.initEvent( 'event1', true, false ); - // iframe.dispatchEvent(evObj) - // ` - }; + return { + onloadCode: ` + const url = '${this.videoUrl}' + this.contentWindow.document.body.innerHTML = ''; + const iframe = top.document.getElementsByTagName('iframe')[0] + var evObj = document.createEvent('MouseEvents'); + evObj.initEvent( 'event1', true, false ); + iframe.dispatchEvent(evObj) + ` + // onloadCode: ` + // const url = 'https://vd3.bdstatic.com/mda-mja2qsy47mbyh1xc/sc/cae_h264_clips/1633918903861514556/mda-mja2qsy47mbyh1xc.mp4?auth_key=1634030413-0-0-7898726f7bd328302e2119fdf694fd5e&bcevod_channel=searchbox_feed&pd=1&pt=3&abtest=' + // this.contentWindow.document.body.innerHTML = ''; + // const iframe = top.document.getElementsByTagName('iframe')[0] + // var evObj = document.createEvent('MouseEvents'); + // evObj.initEvent( 'event1', true, false ); + // iframe.dispatchEvent(evObj) + // ` + }; } } - \ No newline at end of file + diff --git a/components/nav-bar/nav-bar.vue b/components/nav-bar/nav-bar.vue new file mode 100644 index 0000000..87c1d7f --- /dev/null +++ b/components/nav-bar/nav-bar.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/im-var.scss b/im-var.scss new file mode 100644 index 0000000..ed1ece4 --- /dev/null +++ b/im-var.scss @@ -0,0 +1,60 @@ +// 颜色 +$im-color-primary: #00a89b; +$im-color-primary-light-1: mix(#fff, $im-color-primary, 10%); +$im-color-primary-light-2: mix(#fff, $im-color-primary, 20%); +$im-color-primary-light-3: mix(#fff, $im-color-primary, 30%); +$im-color-primary-light-4: mix(#fff, $im-color-primary, 40%); +$im-color-primary-light-5: mix(#fff, $im-color-primary, 50%); +$im-color-primary-light-6: mix(#fff, $im-color-primary, 60%); +$im-color-primary-light-7: mix(#fff, $im-color-primary, 70%); +$im-color-primary-light-8: mix(#fff, $im-color-primary, 80%); +$im-color-primary-light-9: mix(#fff, $im-color-primary, 90%); +$im-color-primary-dark-1: mix(#000, $im-color-primary, 10%); +$im-color-primary-dark-2: mix(#000, $im-color-primary, 20%); +$im-color-primary-dark-3: mix(#000, $im-color-primary, 30%); +$im-color-primary-dark-4: mix(#000, $im-color-primary, 40%); + +$im-color-success: #18bc37; +$im-color-warning: #f3a73f; +$im-color-danger: #e43d33; +$im-color-info: #8f939c; + +// 文字颜色 +$im-text-color: #000000; +$im-text-color-light: #6a6a6a; +$im-text-color-lighter: #909399; +$im-text-color-lighter-extra: #c7c7c7; + +// 边框颜色 +$im-border: #F0F0F0; +$im-border-light: #EDEDED; +$im-border-lighter: #DCDCDC; +$im-border-lighter-extra: #B9B9B9; + +// 文字大小 +$im-font-size: 30rpx; +$im-font-size-small: 28rpx; +$im-font-size-smaller: 26rpx; +$im-font-size-smaller-extra: 24rpx; +$im-font-size-large: 32rpx; +$im-font-size-larger: 34rpx; +$im-font-size-larger-extra: 36rpx; + +// 阴影 +$im-box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04); +$im-box-shadow-light: 0 2px 12px 0 rgba(0, 0, 0, 0.1); +$im-box-shadow-lighter: 0px 0px 6px rgba(0, 0, 0, .12); +$im-box-shadow-dark: 0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5); + +// 背景 +$im-bg: #f7f7f7; +$im-bg-active: #f1f1f1; + +// 标题 +$im-title-size: 26px; +$im-title-size-1: 22px; +$im-title-size-2: 18px; + +$font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif; + +$im-nav-bar-height: 45px; diff --git a/im.scss b/im.scss new file mode 100644 index 0000000..e5aefe7 --- /dev/null +++ b/im.scss @@ -0,0 +1,217 @@ +/** 原生button样式 **/ +uni-button { + font-size: $im-font-size !important; +} +uni-button[type='primary'] { + color: #fff !important; + background-color: $im-color-primary !important; +} + +uni-button[type='primary'][plain] { + color: $im-color-primary !important; + border: 1px solid $im-color-primary; + background-color: transparent; +} + +uni-button[type='warn'] { + color: #fff !important; + background-color: $im-color-danger !important; +} + +uni-button[type='warn'][plain] { + color: $im-color-danger !important; + border: 1px solid $im-color-danger !important; + background-color: transparent !important; +} + +uni-button[size='mini'] { + font-size: $im-font-size-smaller !important; +} + +// #ifdef MP-WEIXIN +// wx小程序只有button,没有uni-botton +button { + font-size: $im-font-size !important; +} + +button[type='primary'] { + color: #fff !important; + background-color: $im-color-primary !important; +} + +button[type='primary'][plain] { + color: $im-color-primary !important; + border: 1px solid $im-color-primary; + background-color: transparent; +} + +button[type='warn'] { + color: #fff !important; + background-color: $im-color-danger !important; +} + +button[type='warn'][plain] { + color: $im-color-danger !important; + border: 1px solid $im-color-danger !important; + background-color: transparent !important; +} + +button[size='mini'] { + font-size: $im-font-size-smaller !important; +} +// #endif + +.button-hover[type='primary'] { + color: #fff !important; + background-color: $im-color-primary-dark-1 !important; +} + +/** uni-ui input激活后边框、图标颜色 **/ +.uni-easyinput__content.is-focused:not(.is-input-error-border) { + border-color: $im-color-primary-light-2 !important; + + .content-clear-icon { + color: $im-color-primary-light-2 !important; + } +} + +/** 底部导航 **/ +.uni-tabbar-bottom .uni-tabbar { + box-shadow: $im-box-shadow; +} + +.uni-tabbar-border { + display: none; +} + +.segmented-control { + border-color: $im-color-primary !important; + + .segmented-control__item--button { + border-color: $im-color-primary !important; + } + .segmented-control__item--button--active { + background-color: $im-color-primary !important; + + .segmented-control__text{ + color: #fff !important; + } + } + .segmented-control__text{ + color: $im-color-primary !important; + } +} + + +.uni-radio-input svg{ + border-color: white !important; + background-color: $im-color-primary !important; +} + +.uni-radio-input svg { + background-color: $im-color-primary !important; + border-color: $im-color-primary !important; + background-clip: content-box !important; + box-sizing: border-box; + border-radius: 50%; + transform: translate(-50%, -50%) scale(0.7)!important; +} + +.uni-radio-input svg path{ + fill: $im-color-primary !important; +} + +.uni-radio-input { + background-color: white !important; + border-color: $im-color-primary !important; +} + +.uni-radio-input-disabled { + background-color: rgb(225, 225, 225) !important; + border-color: rgb(209, 209, 209) !important; + opacity: 0.5; +} + +.uni-section__content-title { + font-size: $im-font-size !important; + color: $im-text-color-light; +} + +.uni-forms-item__label { + color: $im-text-color; + font-size: $im-font-size !important; +} +.uni-forms-item { + //margin-bottom: 8px !important; +} +.uni-easyinput__content-input { + font-size: $im-font-size !important; +} +.uni-easyinput__placeholder-class { + color: $im-text-color-lighter; + font-size: $im-font-size !important;; +} +.uni-easyinput__content-textarea { + font-size: $im-font-size !important;; +} +.uni-input-input:disabled { + color: $im-text-color-light; +} +.uni-forms-item.is-direction-top .uni-forms-item__label { + padding: 0 !important; +} +.uni-data-checklist .checklist-group .checklist-box .checklist-content .checklist-text { + font-size: $im-font-size !important; +} +.uni-card .uni-card__content { + color: unset !important; + padding: 10px 0 !important; +} +.uni-tag-text--small{ + font-size: 10px !important; + font-weight: bolder !important; +} + +.nav-bar { + height: 100rpx; + padding: 0 20rpx; + display: flex; + align-items: center; + background-color: white; + border-bottom: 1px solid $im-border; + + .nav-search { + flex: 1; + } + + .nav-add { + cursor: pointer; + } +} + + +.bottom-btn { + margin: 40rpx 40rpx; + + uni-button + uni-button { + margin-top: 20rpx; + } +} + +.emoji-large { + width: 64rpx !important; + height: 64rpx !important; + vertical-align: bottom !important; +} + +.emoji-normal { + width: 54rpx !important; + height: 54rpx !important; + vertical-align: bottom !important; +} + +.emoji-small { + width: 36rpx !important; + height: 36rpx !important; + vertical-align: bottom !important; +} \ No newline at end of file diff --git a/main.js b/main.js index a0da7d1..3358354 100644 --- a/main.js +++ b/main.js @@ -1,9 +1,11 @@ import Vue from "vue"; import App from "./App"; -//配置公共方法 - -//配置公共方法 - +import store from './store' +import * as enums from './common/enums.js'; +import * as date from './common/date'; +import * as messageType from './common/messageType'; +import emotion from './common/emotion.js'; +import url from './common/url.js'; // 导入网络请求的包 import { $http } from '@escook/request-miniprogram'; @@ -12,21 +14,29 @@ uni.$http = $http; // 请求的根路径 // #ifdef H5 $http.baseUrl = 'https://www.sanduolantoyoga.com/yoga' +// $http.baseUrl = 'http://192.168.13.9:8083' // #endif //#ifdef APP-PLUS $http.baseUrl = 'https://www.sanduolantoyoga.com/yoga' +// $http.baseUrl = 'http://192.168.13.9:8083' //#endif + +// WebSocket 地址(可根据平台条件编译) +// #ifdef H5 +// Vue.prototype.$wsUrl = 'ws://192.168.13.9:8528/im' // 开发环境 +Vue.prototype.$wsUrl = 'wss://www.sanduolantoyoga.com/yoga-imserver' // 生产环境 +// #endif + +// #ifdef APP-PLUS +// Vue.prototype.$wsUrl = 'ws://192.168.13.9:8528/im' // 开发环境 +Vue.prototype.$wsUrl = 'wss://www.sanduolantoyoga.com/yoga-imserver' // 生产环境 +// #endif + // 请求开始之前做一些事情 $http.beforeRequest = function (options) { - // 判断是否需要登录 - // 暂时隐藏加载 - // uni.showLoading({ - // title: '数据加载中...' - // }); - - + var iftoken = uni.getStorageSync("iftoken"); if(iftoken){ // 去掉身份认证 @@ -40,18 +50,18 @@ $http.beforeRequest = function (options) { // 判断请求是否为有权限的 API 接口 if(token) { // 为请求头添加身份认证 - options.header = { + options.header = { Authorization: token } } uni.setStorageSync('iftoken', ""); } - + }; // 请求完成之后做一些事情 $http.afterRequest = function (response) { - + uni.hideLoading(); // 拦截未登录,跳转到登录 if (response.data.status == 10105) { @@ -65,7 +75,7 @@ $http.afterRequest = function (response) { url: '/pages/login/login' }); }, 2000); - + } // else if (response.data.status == 10106) { @@ -89,7 +99,11 @@ Vue.config.productionTip = false; Vue.prototype.isConnected = false; // ws是否已经连接; Vue.prototype.heartbeatInterval = null; // 心跳; Vue.prototype.heartbeatTimeout =20000; // ws20秒执行一次 - +Vue.prototype.$enums = enums +Vue.prototype.$date = date +Vue.prototype.$msgType = messageType +Vue.prototype.$emo = emotion +Vue.prototype.$url = url App.mpType = "app"; // 引入全局uView @@ -97,7 +111,8 @@ import uView from "uview-ui"; Vue.use(uView); const app = new Vue({ - ...App + store, + ...App }) app.$mount(); diff --git a/package-lock.json b/package-lock.json index 2862eb5..75fa2f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -149,6 +149,11 @@ "to-regex-range": "^5.0.1" } }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, "immutable": { "version": "5.0.3", "resolved": "https://registry.npmmirror.com/immutable/-/immutable-5.0.3.tgz", @@ -175,6 +180,22 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "optional": true }, + "lie": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/lie/-/lie-3.1.1.tgz", + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", + "requires": { + "immediate": "~3.0.5" + } + }, + "localforage": { + "version": "1.10.0", + "resolved": "https://registry.npmmirror.com/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", + "requires": { + "lie": "3.1.1" + } + }, "micromatch": { "version": "4.0.8", "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", diff --git a/package.json b/package.json index ec99218..f9a99db 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "dependencies": { "@escook/request-miniprogram": "^0.2.1", "@hyoga/uni-socket.io": "^3.0.4", + "localforage": "^1.10.0", "mp-html": "^2.5.1", "sass": "^1.86.0" }, diff --git a/pages.json b/pages.json index 5d48652..b7745d5 100644 --- a/pages.json +++ b/pages.json @@ -53,7 +53,7 @@ }, { "path" : "pages/category/detail", - "style" : + "style" : { "navigationBarTitleText": "", "enablePullDownRefresh": false @@ -74,7 +74,7 @@ }, { "path" : "pages/teacher/course", - "style" : + "style" : { "navigationBarTitleText": "课程表", "enablePullDownRefresh": false @@ -82,7 +82,7 @@ }, { "path" : "pages/user/mycourse", - "style" : + "style" : { "navigationBarTitleText": "我的课程", "enablePullDownRefresh": false @@ -90,7 +90,7 @@ }, { "path" : "pages/user/mycourseinfo", - "style" : + "style" : { "navigationBarTitleText": "课程详情", "enablePullDownRefresh": false @@ -98,7 +98,7 @@ }, { "path" : "pages/user/mycustom", - "style" : + "style" : { "navigationBarTitleText": "我的客户", "enablePullDownRefresh": false @@ -106,7 +106,7 @@ }, { "path" : "pages/user/mycollect", - "style" : + "style" : { "navigationBarTitleText": "我的收藏", "enablePullDownRefresh": false @@ -114,7 +114,7 @@ }, { "path" : "pages/user/tclist", - "style" : + "style" : { "navigationBarTitleText": "提成统计", "enablePullDownRefresh": false @@ -122,7 +122,7 @@ }, { "path" : "pages/user/tclist", - "style" : + "style" : { "navigationBarTitleText": "提成统计", "enablePullDownRefresh": false @@ -130,7 +130,7 @@ }, { "path" : "pages/user/kslist", - "style" : + "style" : { "navigationBarTitleText": "课时统计", "enablePullDownRefresh": false @@ -138,7 +138,7 @@ }, { "path" : "pages/user/xslist", - "style" : + "style" : { "navigationBarTitleText": "销售信息", "enablePullDownRefresh": false @@ -146,7 +146,7 @@ }, { "path" : "pages/user/cdlist", - "style" : + "style" : { "navigationBarTitleText": "场地销售", "enablePullDownRefresh": false @@ -154,21 +154,30 @@ }, { "path" : "pages/user/jslist", - "style" : + "style" : { "navigationBarTitleText": "教练费用", "enablePullDownRefresh": false } }, - { - "path" : "pages/chat/chat", - "style" : - { - "navigationBarTitleText": "教练咨询", - "enablePullDownRefresh": false - } - }, - { +// { +// "path" : "pages/chat/chat", +// "style" : +// { +// "navigationBarTitleText": "教练咨询", +// "enablePullDownRefresh": false +// } +// }, + { + "path" : "pages/chatbox/chat-box", + "style" : + { + "navigationBarTitleText": "消息2", + "enablePullDownRefresh": false + } + }, + + { "path" : "pages/product/list", "style": { "navigationBarTextStyle": "black", @@ -215,7 +224,7 @@ }, { "path" : "pages/chat/groupchat", - "style" : + "style" : { "navigationBarTitleText": "客服", "enablePullDownRefresh": false, @@ -237,7 +246,7 @@ }, { "path" : "pages/user/serviceInfo", - "style" : + "style" : { "navigationBarTitleText": "客服", "enablePullDownRefresh": false @@ -331,6 +340,16 @@ } } }, + { + "path": "pages/chatbox/chat", + "style": { + "enablePullDownRefresh": true, + "navigationBarBackgroundColor": "#00a89b", + "app-plus": { + "titleNView": false + } + } + }, { "path": "pages/message/contact", "style": { @@ -421,7 +440,7 @@ "enablePullDownRefresh": false } }, - + // ……其他页面配置 更新版本 { "path": "uni_modules/uni-upgrade-center-app/pages/upgrade-popup", @@ -438,7 +457,7 @@ } } } - + ], "globalStyle": { "pageOrientation": "auto", @@ -471,8 +490,14 @@ "iconPath": "static/image/icon/good.png", "selectedIconPath": "/static/image/icon/good-active.png" }, +// { +// "pagePath": "pages/message/group", +// "text": "消息1", +// "iconPath": "/static/image/icon/message.png", +// "selectedIconPath": "/static/image/icon/message-active.png" +// }, { - "pagePath": "pages/message/group", + "pagePath": "pages/chatbox/chat", "text": "消息", "iconPath": "/static/image/icon/message.png", "selectedIconPath": "/static/image/icon/message-active.png" diff --git a/pages/book/booklist.vue b/pages/book/booklist.vue index b3ed86a..749a584 100644 --- a/pages/book/booklist.vue +++ b/pages/book/booklist.vue @@ -85,6 +85,7 @@ + + diff --git a/pages/chatbox/chat-box1.vue b/pages/chatbox/chat-box1.vue new file mode 100644 index 0000000..404a020 --- /dev/null +++ b/pages/chatbox/chat-box1.vue @@ -0,0 +1,1572 @@ + + + + + diff --git a/pages/chatbox/chat.vue b/pages/chatbox/chat.vue new file mode 100644 index 0000000..53a2d55 --- /dev/null +++ b/pages/chatbox/chat.vue @@ -0,0 +1,339 @@ + + + + + diff --git a/pages/chatbox/chat2.vue b/pages/chatbox/chat2.vue new file mode 100644 index 0000000..e10a1d1 --- /dev/null +++ b/pages/chatbox/chat2.vue @@ -0,0 +1,174 @@ + + + + + diff --git a/pages/login/login.vue b/pages/login/login.vue index 9394def..69d188b 100644 --- a/pages/login/login.vue +++ b/pages/login/login.vue @@ -5,32 +5,32 @@ yoga - + - + - + 看不清? - + - 登录中.. - + 登录中.. + 注册账号 → - + - + @@ -102,7 +102,7 @@ success: function (res) { if (res.confirm) { console.log('用户点击确定'); - + // #ifdef APP-PLUS // 跳转到设置页面 plus.device.vendor = 'apple'; // 如果是iOS系统 @@ -112,7 +112,7 @@ plus.runtime.openURL('package:' + plus.android.runtimeMainActivity()); } // #endif - + } } }); @@ -123,20 +123,20 @@ }, // 获取设备信息 getsystemInfo(){ - const info = uni.getSystemInfoSync(); + const info = uni.getSystemInfoSync(); const platform = info.platform; // 手机安卓还是苹果 if (info.platform === 'ios') { // ios console.log('iOS 设备唯一标识:', info.deviceId); - } + } else{ //安卓 console.log('设备信息ID:',info.deviceId); } - + this.form.registerId=info.deviceId; this.$forceUpdate(); - + // const iosApp = info.platform; console.log('iOS 设备名称:', info.model); console.log('iOS 系统版本:', info.system); @@ -179,7 +179,7 @@ // duration: 2000 // }); } - + }, // 校验密码 checkMM(mm) { @@ -227,7 +227,7 @@ } return true; }, - + //地图选择地址 chooseLocation(){ var _this=this; @@ -273,7 +273,7 @@ }, // 密码登录 async loginpwdDo(){ - + // 手机号码 密码是否输入 if(!this.form.phonenumber){ this.$refs.uToast.show({ @@ -356,6 +356,7 @@ // var shoplist=[shop] // myCache('shoplist',shoplist); } + getApp().$vm.init(user.userId) }, async loadshop(){ // 获取门店 @@ -403,7 +404,7 @@ diff --git a/pages/message/contact.vue b/pages/message/contact.vue index ff2c0bf..a7cf952 100644 --- a/pages/message/contact.vue +++ b/pages/message/contact.vue @@ -41,35 +41,36 @@ - + - + 暂无数据.. - + - {{info.friendNickName}} + {{info.nickName}} - - + - + @@ -401,7 +431,7 @@ width: 100%; z-index: 999; background-color: #00a89b; - + .edit { position: absolute; top: 50%; diff --git a/pages/message/group.vue b/pages/message/group.vue index 9823f51..c94b18d 100644 --- a/pages/message/group.vue +++ b/pages/message/group.vue @@ -1,19 +1,19 @@ @@ -84,17 +84,17 @@ onLoad(options) { }, onShow(){ - + this.openId = myCache('openId'); var user = myCache('user'); this.userid = user.userid? user.userid:''; this.userName=user.nickName?user.nickName:""; this.userheadimg=user.avatar?user.avatar:require("@/static/image/girl.png"); this.phone = user.userphone; - + // 从缓存聊天群里获取聊天群列表 this.loadData(); - + var that=this; setTimeout(() => { if(!that.isConnected){ @@ -102,12 +102,12 @@ that.reconnect(); } }, 500); - + that.setmsgnum(); setTimeout(() => { that.setmsgnum(); }, 2000); - + }, onPullDownRefresh() { console.log('onPullDownRefresh'); @@ -155,8 +155,9 @@ // ws接收消息 socketinit(){ var that = this; - uni.connectSocket({ - url: "wss://www.sanduolantoyoga.com/yoga-imserver/", + let socketTask = null; + socketTask=uni.connectSocket({ + url: "ws://192.168.13.9:8528/im", header: { // 'content-type': 'application/json', Authorization: uni.getStorageSync("token"), @@ -171,8 +172,8 @@ console.log(res,'socket连接成功complete'); } }); - - uni.onSocketOpen(resopen => { + + socketTask.onOpen(resopen => { console.log('WebSocket连接已打开!'); this.isConnected = true; this.$forceUpdate(); @@ -192,10 +193,10 @@ console.log('消息发送失败!') } }); - + // 定时发送心跳包 this.startHeartbeat(); - + // 拉取离线消息 this.grouplist.forEach((cell,i)=>{ if(cell.minId){ @@ -203,7 +204,7 @@ this.pullMessage(i,cell); } }); - + // 已经删除的有最大id的继续拉取是否有消息 var data= myCache("privateMsgMaxId")?myCache("privateMsgMaxId"):[{id:0,minId:0}]; data.forEach((cell)=>{ @@ -220,7 +221,7 @@ this.getList(cell); } }); - + var data= myCache("groupMsgMaxId")?myCache("groupMsgMaxId"):[{id:0,minId:0}]; data.forEach((cell)=>{ var ifcz=false; @@ -235,11 +236,11 @@ this.getGroupList(cell); } }); - + }); - + // 连接关闭后重新连接 - uni.onSocketClose(res => { + socketTask.onClose(res => { console.log('WebSocket连接已关闭!',res); this.isConnected = false; this.$forceUpdate(); @@ -247,7 +248,7 @@ this.reconnect(); }); // 连接失败后重新连接 - uni.onSocketError(err => { + socketTask.onError(err => { console.error('WebSocket连接打开失败,请检查:', err); this.isConnected = false; this.$forceUpdate(); @@ -255,7 +256,7 @@ this.reconnect(); }); // 接收信息 - uni.onSocketMessage(res => { + socketTask.onMessage(res => { console.log('收到WebSocket服务器消息:'); if(res.data){ var rs=JSON.parse(res.data); @@ -270,9 +271,9 @@ // 消息类型 0:文字 1:图片 2:文件 3:语音 4:视频 10, "撤回" 11, "已读 "12, "消息已读回执 " 30,"加载中标记" if(data.type==0||data.type==1||data.type==2||data.type==3||data.type==4||data.type==5||data.type==6){ if(data.type==0){ - data.content=decodeURIComponent(data.content); + data.content=decodeURIComponent(data.content); } - + var id="privatechat-" + data.recvId +"-" + data.sendId; var index=this.ifadd(id); var chatinfo=null; @@ -313,7 +314,7 @@ } else{ // 10, "撤回" 11, "已读 " 12, "消息已读回执 " 30,"加载中标记" - + } } } @@ -327,11 +328,11 @@ // 加入聊天记录 // 消息类型 0:文字 1:图片 2:文件 3:语音 4:视频 10, "撤回" 11, "已读 "12, "消息已读回执 " 30,"加载中标记" if(data.type==0||data.type==1||data.type==2||data.type==3||data.type==4||data.type==5||data.type==6){ - + if(data.type==0){ - data.content=decodeURIComponent(data.content); + data.content=decodeURIComponent(data.content); } - + var id="groupchat-" + data.groupId; var index=this.ifadd(id); var chatinfo=null; @@ -382,7 +383,7 @@ } } }); - + }, // 消息 setmsgnum(){ @@ -402,12 +403,12 @@ } }, handleLongPress(info,index) { - console.log(index); + console.log(index); this.$refs.swipeRef[index].open(); }, // 删除 actionClick(info,index) { - console.log(info,index); + console.log(info,index); var that=this; uni.showModal({ title: '提示', @@ -425,7 +426,7 @@ this.setmsgnum(); } } - }); + }); }, // 消息是否存在列表中 ifadd(id){ @@ -483,16 +484,16 @@ minId: chat.minId, sort:"groupchat", // privatechat 私聊 groupchat 群聊 from:"message", // yh 用户咨询进入聊天框,message 从消息进入聊天框 - notice: chat.notice, - remarkNickName: chat.remarkNickName, - showNickName: chat.showNickName, - showGroupName: chat.showGroupName, - remarkGroupName: chat.remarkGroupName, - reason: chat.reason, - customerService: chat.customerService, - instructor: chat.instructor, - ownerId: chat.ownerId, - productId: chat.productId, + notice: chat.notice, + remarkNickName: chat.remarkNickName, + showNickName: chat.showNickName, + showGroupName: chat.showGroupName, + remarkGroupName: chat.remarkGroupName, + reason: chat.reason, + customerService: chat.customerService, + instructor: chat.instructor, + ownerId: chat.ownerId, + productId: chat.productId, productName: chat.productName } var data=encodeURIComponent(JSON.stringify(info)); @@ -590,7 +591,7 @@ img: "", sort:"privatechat", ifload:1 - + }; this.getPrivateInfo(chatlastinfo); }); @@ -755,7 +756,7 @@ myCache("chatlist-"+this.userid,this.grouplist); // 未读消息数量获取 this.setmsgnum(); - + // 聊天框消息缓存 var msgchat=myCache(info.id); if(!msgchat){ @@ -801,7 +802,7 @@ msgchat.push(firstmsg); myCache(info.id,msgchat); } - + } }, async updateChatList(info){ @@ -831,7 +832,7 @@ myCache("chatlist-"+this.userid,this.grouplist); // 未读消息数量获取 this.setmsgnum(); - + // 聊天框消息缓存 var msgchat=myCache(info.id); if(!msgchat){ @@ -879,7 +880,7 @@ myCache(info.id,msgchat); } } - + }, // 消息是否存在列表中 ifchatadd(id,msgid){ @@ -944,7 +945,7 @@ getNavBarHeight(){ return 45+uni.getSystemInfoSync()['statusBarHeight']; }, - + } } diff --git a/pages/message/group0.vue b/pages/message/group0.vue index 9341fd1..13a0b22 100644 --- a/pages/message/group0.vue +++ b/pages/message/group0.vue @@ -1,15 +1,15 @@ @@ -80,7 +80,7 @@ // }, ], socketTask: null, - isConnected: false, // WebSocket连接状态 + isConnected: false, // WebSocket连接状态 socketmsg:[],//接收信息 heartbeatInterval: null, // 心跳包20秒连接一次 heartbeatTimeout: 20000, // 心跳间隔时间,例如每20秒发送一次 @@ -132,9 +132,9 @@ socketinit(){ var that = this; if (!that.isConnected) { - console.log("wss://www.sanduolantoyoga.com/yoga-imserver/") + console.log("ws://192.168.13.9:8528/im") this.socketTask=uni.connectSocket({ - url: "wss://www.sanduolantoyoga.com/yoga-imserver/", + url: "ws://192.168.13.9:8528/im", header: { // 'content-type': 'application/json', Authorization: uni.getStorageSync("token"), @@ -149,7 +149,7 @@ console.log(res,'socket连接成功complete'); } }); - + uni.onSocketOpen(resopen => { console.log('WebSocket连接已打开!'); that.isConnected = true; @@ -169,13 +169,13 @@ console.log('消息发送失败!') } }); - + // 发送心跳包 // this.startHeartbeat(); // 测试发送 消息 // this.sendWebSocketMessage(); }); - + uni.onSocketMessage(res => { console.log('收到WebSocket服务器消息:', res); if(res.data){ @@ -205,7 +205,7 @@ // }; // if(data.type==0||data.type==1||data.type==3||data.type==4){ // that.socketmsg.push(data); - // that.$forceUpdate(); + // that.$forceUpdate(); // } // else{ // // 10, "撤回" 11, "已读 " 12, "消息已读回执 " 30,"加载中标记" @@ -213,13 +213,13 @@ // } } }); - + uni.onSocketClose(res => { console.log('WebSocket连接已关闭!'); that.isConnected = false; clearInterval(that.heartbeatInterval); // 停止心跳包发送 }); - + uni.onSocketError(err => { console.error('WebSocket连接打开失败,请检查:', err); clearInterval(that.heartbeatInterval); // 停止心跳包发送 @@ -231,7 +231,7 @@ this.heartbeatInterval = setInterval(() => { if (this.socketTask) { this.socketTask.send({ - data: JSON.stringify({ + data: JSON.stringify({ 'cmd': 1,//心跳 'data': { 'accessToken':uni.getStorageSync("token") @@ -254,13 +254,13 @@ } }); }, - // 关闭WebSocket连接 + // 关闭WebSocket连接 closeWebSocket() { uni.closeSocket(); this.isConnected = false; clearInterval(this.heartbeatInterval); // 停止心跳包发送 }, - + gotoContacts(){ uni.navigateTo({ url: `/pages/message/contact` @@ -331,7 +331,7 @@ getNavBarHeight(){ return 45+uni.getSystemInfoSync()['statusBarHeight']; }, - + } } diff --git a/pages/message/group1.vue b/pages/message/group1.vue index c011b0a..3b4f2fd 100644 --- a/pages/message/group1.vue +++ b/pages/message/group1.vue @@ -1,18 +1,18 @@ @@ -75,7 +75,7 @@ imgurl:uni.$http.baseUrl, grouplist:[], socketTask: null, - isConnected: false, // WebSocket连接状态 + isConnected: false, // WebSocket连接状态 socketmsg:[],//接收信息 heartbeatInterval: null, // 心跳包20秒连接一次 heartbeatTimeout: 20000, // 心跳间隔时间,例如每20秒发送一次 @@ -130,7 +130,7 @@ methods: { // 删除 actionClick(info,index) { - console.log(info,index); + console.log(info,index); var that=this; uni.showModal({ title: '提示', @@ -145,7 +145,7 @@ myCache("chatlist",that.grouplist); } } - }); + }); }, // 定时心跳包 startHeartbeat() { @@ -153,7 +153,7 @@ this.heartbeatInterval = setInterval(() => { if (this.socketTask) { this.socketTask.send({ - data: JSON.stringify({ + data: JSON.stringify({ 'cmd': 1,//心跳 'data': { 'accessToken':uni.getStorageSync("token") @@ -167,9 +167,9 @@ socketinit(){ var that = this; // if (!that.isConnected) { - + that.socketTask=uni.connectSocket({ - url: "wss://www.sanduolantoyoga.com/yoga-imserver/", + url: "ws://192.168.13.9:8528/im", header: { // 'content-type': 'application/json', Authorization: uni.getStorageSync("token"), @@ -184,7 +184,7 @@ console.log(res,'socket连接成功complete'); } }); - + uni.onSocketOpen(resopen => { console.log('WebSocket连接已打开!'); that.isConnected = true; @@ -205,11 +205,11 @@ console.log('消息发送失败!') } }); - + // 发送心跳包 this.startHeartbeat(); }); - + uni.onSocketMessage(res => { console.log('收到WebSocket服务器消息:'); if(res.data){ @@ -218,12 +218,12 @@ if(rs.data){ var data=rs.data; if(data){ - if(data.recvId==that.userid){ + if(data.recvId==that.userid){ // 是这个用户的接收的信息 // 加入聊天记录 // 消息类型 0:文字 1:图片 2:文件 3:语音 4:视频 10, "撤回" 11, "已读 "12, "消息已读回执 " 30,"加载中标记" if(data.type==0){ - data.content=decodeURIComponent(data.content); + data.content=decodeURIComponent(data.content); } // else if(data.type==1){ // data.content="图片"; @@ -255,7 +255,7 @@ that.$forceUpdate(); } }); - + if(ifexist==0){ // 未保存缓存 // 接收到socket后更新聊天列表记录 @@ -287,14 +287,14 @@ } } }); - + uni.onSocketClose(res => { console.log('WebSocket连接已关闭!',res); that.isConnected = false; that.$forceUpdate(); clearInterval(that.heartbeatInterval); // 停止心跳包发送 }); - + uni.onSocketError(err => { console.error('WebSocket连接打开失败,请检查:', err); that.isConnected = false; @@ -303,13 +303,13 @@ }); // } }, - // 关闭WebSocket连接 + // 关闭WebSocket连接 closeWebSocket() { uni.closeSocket(); this.isConnected = false; this.$forceUpdate() }, - + gotoContacts(){ uni.navigateTo({ url: `/pages/message/contact` @@ -381,7 +381,7 @@ that.reorder(); myCache("chatlist",that.grouplist); }, 300); - + // socket接收实时消息 this.socketinit(); }, @@ -555,7 +555,7 @@ getNavBarHeight(){ return 45+uni.getSystemInfoSync()['statusBarHeight']; }, - + } } diff --git a/pages/message/group2.vue b/pages/message/group2.vue index af2493c..47b794d 100644 --- a/pages/message/group2.vue +++ b/pages/message/group2.vue @@ -1,19 +1,19 @@ @@ -76,7 +76,7 @@ imgurl:uni.$http.baseUrl, grouplist:[], socketTask: null, - isConnected: false, // WebSocket连接状态 + isConnected: false, // WebSocket连接状态 socketmsg:[],//接收信息 heartbeatInterval: null, // 心跳包20秒连接一次 heartbeatTimeout: 20000, // 心跳间隔时间,例如每20秒发送一次 @@ -128,12 +128,12 @@ // }, methods: { handleLongPress(info,index) { - console.log(index); + console.log(index); this.$refs.swipeRef[index].open(); }, // 删除 actionClick(info,index) { - console.log(info,index); + console.log(info,index); var that=this; uni.showModal({ title: '提示', @@ -149,7 +149,7 @@ myCache("chatlist-"+this.userid,that.grouplist); } } - }); + }); }, // 定时心跳包 startHeartbeat() { @@ -157,7 +157,7 @@ this.heartbeatInterval = setInterval(() => { if (this.socketTask) { this.socketTask.send({ - data: JSON.stringify({ + data: JSON.stringify({ 'cmd': 1,//心跳 'data': { 'accessToken':uni.getStorageSync("token") @@ -171,7 +171,7 @@ socketinit(){ var that = this; that.socketTask=uni.connectSocket({ - url: "wss://www.sanduolantoyoga.com/yoga-imserver/", + url: "ws://192.168.13.9:8528/im", header: { // 'content-type': 'application/json', Authorization: uni.getStorageSync("token"), @@ -186,7 +186,7 @@ console.log(res,'socket连接成功complete'); } }); - + uni.onSocketOpen(resopen => { console.log('WebSocket连接已打开!'); that.isConnected = true; @@ -207,10 +207,10 @@ console.log('消息发送失败!') } }); - + // 定时发送心跳包 this.startHeartbeat(); - + // 拉取离线消息 this.grouplist.forEach((cell,i)=>{ if(cell.minId){ @@ -218,7 +218,7 @@ this.pullMessage(i,cell); } }); - + // 已经删除的有最大id的继续拉取是否有消息 var data= myCache("privateMsgMaxId")?myCache("privateMsgMaxId"):[{id:0,minId:0}]; data.forEach((cell)=>{ @@ -235,7 +235,7 @@ this.getList(cell); } }); - + var data= myCache("groupMsgMaxId")?myCache("groupMsgMaxId"):[{id:0,minId:0}]; data.forEach((cell)=>{ var ifcz=false; @@ -250,9 +250,9 @@ this.getGroupList(cell); } }); - + }); - + uni.onSocketMessage(res => { console.log('收到WebSocket服务器消息:'); if(res.data){ @@ -267,7 +267,7 @@ // 加入聊天记录 // 消息类型 0:文字 1:图片 2:文件 3:语音 4:视频 10, "撤回" 11, "已读 "12, "消息已读回执 " 30,"加载中标记" if(data.type==0){ - data.content=decodeURIComponent(data.content); + data.content=decodeURIComponent(data.content); } if(data.type==0||data.type==1||data.type==2||data.type==3||data.type==4||data.type==5||data.type==6){ var ifexist=0,chatinfo=null; @@ -307,7 +307,7 @@ } else{ // 10, "撤回" 11, "已读 " 12, "消息已读回执 " 30,"加载中标记" - + } } } @@ -321,7 +321,7 @@ // 加入聊天记录 // 消息类型 0:文字 1:图片 2:文件 3:语音 4:视频 10, "撤回" 11, "已读 "12, "消息已读回执 " 30,"加载中标记" if(data.type==0){ - data.content=decodeURIComponent(data.content); + data.content=decodeURIComponent(data.content); } if(data.type==0||data.type==1||data.type==2||data.type==3||data.type==4||data.type==5||data.type==6){ var ifexist=0,idx=null; @@ -337,7 +337,7 @@ that.$forceUpdate(); } }); - + if(ifexist==0){ // 未保存缓存 // 接收到socket后更新聊天列表记录 @@ -365,14 +365,14 @@ } else{ // 10, "撤回" 11, "已读 " 12, "消息已读回执 " 30,"加载中标记" - + } } } } } }); - + // 连接关闭后重新连接 uni.onSocketClose(res => { console.log('WebSocket连接已关闭!',res); @@ -392,7 +392,7 @@ // this.socketinit(); }); }, - // 关闭WebSocket连接 + // 关闭WebSocket连接 closeWebSocket() { uni.closeSocket(); this.isConnected = false; @@ -452,15 +452,15 @@ minId: chat.minId, sort:"groupchat", // privatechat 私聊 groupchat 群聊 from:"message", // yh 用户咨询进入聊天框,message 从消息进入聊天框 - notice: chat.notice, - remarkNickName: chat.remarkNickName, - showNickName: chat.showNickName, - showGroupName: chat.showGroupName, - remarkGroupName: chat.remarkGroupName, - reason: chat.reason, - customerService: chat.customerService, - instructor: chat.instructor, - productId: chat.productId, + notice: chat.notice, + remarkNickName: chat.remarkNickName, + showNickName: chat.showNickName, + showGroupName: chat.showGroupName, + remarkGroupName: chat.remarkGroupName, + reason: chat.reason, + customerService: chat.customerService, + instructor: chat.instructor, + productId: chat.productId, productName: chat.productName } var data=encodeURIComponent(JSON.stringify(info)); @@ -489,7 +489,7 @@ that.reorder(); myCache("chatlist-"+this.userid,that.grouplist); }, 300); - + // socket接收实时消息 this.socketinit(); }, @@ -873,7 +873,7 @@ // 重新排序 this.reorder(); myCache("chatlist-"+this.userid,this.grouplist); - + var msgchat=myCache(info.id); console.log(info.id,msgchat); if(!msgchat){ @@ -973,7 +973,7 @@ getNavBarHeight(){ return 45+uni.getSystemInfoSync()['statusBarHeight']; }, - + } } diff --git a/pages/product/cdetail.vue b/pages/product/cdetail.vue index fb7e17f..ff47029 100644 --- a/pages/product/cdetail.vue +++ b/pages/product/cdetail.vue @@ -1,6 +1,6 @@