From 6ae7c2c4b7a91f465c200ada207352f2ea065571 Mon Sep 17 00:00:00 2001 From: zdg Date: Fri, 16 Aug 2024 10:15:12 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96im?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/src/api/apiService.js | 2 +- src/renderer/src/plugins/imChat/index.js | 96 +++++++++++-- src/renderer/src/plugins/imChat/msgEnum.js | 135 ++++++++++++++++++ src/renderer/src/plugins/shareStore.js | 2 +- .../src/views/tool/components/imChat.vue | 48 ++++--- src/renderer/src/views/tool/sphere.vue | 64 +++++++-- 6 files changed, 306 insertions(+), 41 deletions(-) create mode 100644 src/renderer/src/plugins/imChat/msgEnum.js diff --git a/src/renderer/src/api/apiService.js b/src/renderer/src/api/apiService.js index b487ad8..ad79922 100644 --- a/src/renderer/src/api/apiService.js +++ b/src/renderer/src/api/apiService.js @@ -10,7 +10,7 @@ export class ApiService { static publicHttp(url, data, method, option = {}, type) { method = method || 'get' // 默认GET const config = { url, method } - if (!!data) basic[method=='get'?'params':'data'] = data + if (!!data) config[method=='get'?'params':'data'] = data if (!!option) Object.assign(config, option) // 特殊格式处理 if (type == 'file') config.headers = { 'Content-Type': 'multipart/form-data' } diff --git a/src/renderer/src/plugins/imChat/index.js b/src/renderer/src/plugins/imChat/index.js index 4174652..58a7841 100644 --- a/src/renderer/src/plugins/imChat/index.js +++ b/src/renderer/src/plugins/imChat/index.js @@ -6,7 +6,8 @@ * @date 2023-07-03 */ // const TimRender = require('im_electron_sdk/dist/render') -import * as TYPES from './enumbers' +import * as TYPES from './enumbers' // sdk相关枚举 +import MsgEnum from './msgEnum' // 消息相关枚举(自定义) const API = window.api // TIM生成签名 // import * as GenerateUserSig from './userSig' // 引入签名生成器 @@ -31,11 +32,12 @@ export class ImChat { constructor(SDKAppID, userSig, userID, isInit) { this.SDKAppID = SDKAppID - // this.userSig = userSig - const sig = 'eJwtjN0KgjAYQN9l16Vzcz8I3RhE9J*JV94IW-ZV6nASWfTurfTynAPnjdLNyXvoFkWIeBhN-gxK1x2cYdCMTQnlYmxW3QpjQKEo4BhjGgrKh6KfBlrtPGOMuDTYDqqfE26BWUjEeIHSrW1cL-SulHd5KI7zxDbpdh1cX0nuX7JK7HtroNerZhnnPpYz9PkCe5Mx1w__' - this.userSig = sig + this.userSig = userSig + // const sig = 'eJwtjN0KgjAYQN9l16Vzcz8I3RhE9J*JV94IW-ZV6nASWfTurfTynAPnjdLNyXvoFkWIeBhN-gxK1x2cYdCMTQnlYmxW3QpjQKEo4BhjGgrKh6KfBlrtPGOMuDTYDqqfE26BWUjEeIHSrW1cL-SulHd5KI7zxDbpdh1cX0nuX7JK7HtroNerZhnnPpYz9PkCe5Mx1w__' + // this.userSig = sig this.userID = userID window.test = this + this.timGroupId = '@TGS#3CYWMK2ON' // 测试使用 if (isInit) return this.init() } // 设置配置 @@ -60,7 +62,8 @@ export class ImChat { // 日志监听 this.timChat.TIMSetLogCallback({ callback: data => { - console.log('[im-chat]:', data[1]) + // console.log('[im-chat]:', data[1]) + this.setConsole('%cchat-log ', data[1]) }, user_data: '' }) @@ -91,8 +94,20 @@ export class ImChat { } // 监听 watch(callback) { + // 先移除监听 + this.timChat.TIMRemoveRecvNewMsgCallback() + // 消息监听 this.timChat.TIMAddRecvNewMsgCallback({ - callback, user_data: {type:'msg'} + callback, user_data: this.toStr('msg') + }) + // 群消息监听 + // 群组系统消息事件包括 加入群、退出群、踢出群、设置管理员、取消管理员、群资料变更、群成员资料变更。此消息是针对所有群组成员下发的 + this.timChat.TIMSetGroupTipsEventCallback({ + // callback, user_data: this.toStr('msg-group') + callback: (data) => { + // console.log('群消息', group_tips_event) + this.setConsole('%c群消息', data) + }, }) } // 登录 @@ -104,18 +119,19 @@ export class ImChat { } // 获取登录状态 // [1,2,3,4] | [已登陆,登录中,未登录,登出中] - console.log('登录', this) + // console.log('登录', this) const status = await this.timChat.TIMGetLoginStatus() if (status == 3) { // 未登录 const res = await this.timChat.TIMLogin(option) if (res && res.code == 0) { - console.log('登录成功', res) + // console.log('登录成功', res) this.status.isLogin = true resolve({status:0, msg:'登录成功', data:res}) } else reject(res) } else { if (status == 1) { // 已登录 console.log('已登录') + this.setGroupMsgReceive() resolve({status, msg:'已登录'}) } else if (status == 2) { // 登录中 console.log('登录中') @@ -172,7 +188,11 @@ export class ImChat { return this.timChat.TIMGroupCreate(option).then(res => { if (res && res.code == 0) { const timGroupId = res?.json_param?.create_group_result_groupid - if (!!timGroupId) this.timGroupId = timGroupId + if (!!timGroupId){ + this.setConsole('%c创建群组成功', timGroupId) + this.timGroupId = timGroupId + this.setGroupMsgReceive() + } } return res }) @@ -185,6 +205,14 @@ export class ImChat { data: '', // 用户自定义数据 }) } + // 设置群消息接收 + setGroupMsgReceive() { + return this.timChat.TIMMsgSetGroupReceiveMessageOpt({ + groupId: this.timGroupId, + opt: TYPES.TIMReceiveMessageOpt.kTIMRecvMsgOpt_Not_Notify, + data: '', // 用户自定义数据 + }) + } // 获取群组列表 getGroupList() { return this.timChat.getGroupList().then(res => { @@ -195,4 +223,54 @@ export class ImChat { return error }) } + // 发送消息 + sendMsg(conv_id, msg) { + const option = { + conv_id, + conv_type: TYPES.TIMConvType.kTIMConv_Group, + params: { + message_elem_array: [{ + elem_type: TYPES.TIMElemType.kTIMElem_Text, + text_elem_content: msg + }], + // message_conv_id: conv_id, + // message_conv_type: TYPES.TIMConvType.kTIMConv_Group, + // message_sender: this.userID + }, + user_data: '', // 用户自定义数据 + // callback: (data) => {} + } + return this.timChat.TIMMsgSendMessageV2(option) + } + // 发送关闭(下课)消息 + sendMsgClosed(){ + const msg = this.getMsgObj(MsgEnum.HEADS.MSG_closed, '下课', MsgEnum.TYPES.TEACHER) + console.log('发送关闭消息', msg) + return this.sendMsg(this.timGroupId, msg) + } + // 获取消息对象 + getMsgObj(msgHead, msg, type, sender, option={}) { + if (!msgHead) throw new Error('msgHead is required') + if (!msg) throw new Error('msg is required') + if (typeof msg === 'object') msg = JSON.stringify(msg) + return { + msgKey: msgHead, + msgcontent: msg, + msgType: type ?? MsgEnum.TYPES.STUDENT, // 默认为学生 + senduserid: sender ?? this.userID, + ...option + } + } + // 设置控制台样式 + setConsole(hearStr,...args) { + const css = 'color: #fff;background-color:#2ccb92;padding:3px 5px;border-radius:3px;' + const time = new Date().toLocaleTimeString() + if (!hearStr) hearStr = '%c' + time + console.log(hearStr, css, ...args) + } + // 获取数据字符串 + toStr = (data) => { + if (typeof data === 'string') data = {type: data} + return JSON.stringify(data) + } } \ No newline at end of file diff --git a/src/renderer/src/plugins/imChat/msgEnum.js b/src/renderer/src/plugins/imChat/msgEnum.js new file mode 100644 index 0000000..dec2ba8 --- /dev/null +++ b/src/renderer/src/plugins/imChat/msgEnum.js @@ -0,0 +1,135 @@ +/** + * @description 消息枚举 + * @author zdg + * @date 2021-07-05 14:07:01 + */ + +export class MsgEnum { + /** + * @description: 消息类型 + * + * | 名称 | 含义 | 值(enum) | + * | ---- | ---- | ---- | + * | SYSTEM | 系统消息 | system | + * | TEACHER | 老师消息 | teacher | + * | STUDENT | 学生消息 | student | + * | NOTICE | 通知消息 | notice | + */ + static TYPES = { + /** @desc: 系统消息 */ + SYSTEM: 'system', + /** @desc: 老师消息 */ + TEACHER: 'teacher', + /** @desc: 学生消息 */ + STUDENT: 'student', + /** @desc: 通知消息 */ + NOTICE: 'notice' + } + /** + * @description: 消息头-类型 + * + * | 名称 | 含义 | 值(enum) | + * | ---- | ---- | ---- | + * | --- | 以下为旧定义-消息头 | --- | + * | MSG_closed | 结束课程(下课) | closed | + * | MSG_onlineStatus | 在线状态 | onlineStatus | + * | MSG_pushQuizOfClassWorkdata2Public | 老师端:把选中的学生习题作业,推到大屏 | pushQuizOfClassWorkdata2Public | + * | MSG_pushClassWorkdata2Public | 老师端:把选中的学生作业,推到大屏 | pushClassWorkdata2Public | + * | MSG_shareStudentPresentdata2All | 把某个学生的展示成果数据推给全班所有学生 | shareStudentPresentdata2All | + * | MSG_pushStudentPresentdata2Public | 老师端:课堂展示活动,把选中的学生展示数据,推到大屏 | pushStudentPresentdata2Public | + * | MSG_pushClassWorkPresentList2Public | 老师端:课堂展示活动,任务列表,推到大屏 | pushClassWorkPresentList2Public | + * | MSG_activePageType | 课标研读-分页切换 | activePageType | + * | MSG_slideFlapping | 幻灯片-切换 | slideFlapping | + * | MSG_anmationclick | 幻灯片-动画切换 | anmationclick | + * | MSG_classcourseopen | 群组创建成功 | classcourseopen | + * | MSG_classquizfeedback | 学生的测练结果反馈 | classquizfeedback | + * | MSG_classtaskfeedback | 老师端:接收到学生反馈消息-课堂测练中的其他任务 | classtaskfeedback | + * | MSG_studentfeedback | 老师端:学生反馈的消息,具体要看其中的feedbackkey,类别较繁杂 | studentfeedback | + * | MSG_studentfeedbackcancel | 老师端:学生反馈的消息取消,如取消学会了,取消困惑 | studentfeedbackcancel | + * | MSG_classshowdata | 学生提交的课堂展示数据-要在老师端显示,再由老师选择推送到公屏上 | classshowdata | + * | MSG_classWorkOfPresentDataUpdate | 学生在公屏上展示并完善后,保存后,老师端要更新 | classWorkOfPresentDataUpdate | + * | MSG_classlecturePagesrc | 课堂讲授活动,选择不同的内容 | classlecturePagesrc | + * | --- | 以下为新定义-消息头 | --- | + * | MSG_0001 | 点赞 | 0x0001 | + * | MSG_0002 | xx | 0x0002 | + * | MSG_0003 | xx | 0x0003 | + */ + static HEADS = { + // === 旧定义-消息头(兼容以前) === + /** @desc: 结束课程(下课) */ + MSG_closed : 'closed', + /** @desc: 在线状态 */ + MSG_onlineStatus : 'onlineStatus', + /** @desc: 老师端:把选中的学生习题作业,推到大屏 */ + MSG_pushQuizOfClassWorkdata2Public : 'pushQuizOfClassWorkdata2Public', + /** @desc: 老师端:把选中的学生作业,推到大屏 */ + MSG_pushClassWorkdata2Public : 'pushClassWorkdata2Public', + /** @desc: 把某个学生的展示成果数据推给全班所有学生 */ + MSG_shareStudentPresentdata2All : 'shareStudentPresentdata2All', + /** @desc: 老师端:课堂展示活动,把选中的学生展示数据,推到大屏 */ + MSG_pushStudentPresentdata2Public : 'pushStudentPresentdata2Public', + /** @desc: 老师端:课堂展示活动,任务列表,推到大屏 */ + MSG_pushClassWorkPresentList2Public : 'pushClassWorkPresentList2Public', + /** @desc: 课标研读-分页切换 */ + MSG_activePageType : 'activePageType', + /** @desc: 幻灯片-切换 */ + MSG_slideFlapping : 'slideFlapping', + /** @desc: 幻灯片-动画切换 */ + MSG_anmationclick : 'anmationclick', + /** @desc: 群组创建成功 */ + MSG_classcourseopen : 'classcourseopen', + /** @desc: 学生的测练结果反馈 */ + MSG_classquizfeedback : 'classquizfeedback', + /** @desc: 老师端:接收到学生反馈消息-课堂测练中的其他任务 */ + MSG_classtaskfeedback : 'classtaskfeedback', + /** @desc: 老师端:学生反馈的消息,具体要看其中的feedbackkey,类别较繁杂 */ + MSG_studentfeedback : 'studentfeedback', + /** @desc: 老师端:学生反馈的消息取消,如取消学会了,取消困惑 */ + MSG_studentfeedbackcancel : 'studentfeedbackcancel', + /** @desc: 学生提交的课堂展示数据-要在老师端显示,再由老师选择推送到公屏上 */ + MSG_classshowdata : 'classshowdata', + /** @desc: 学生在公屏上展示并完善后,保存后,老师端要更新 */ + MSG_classWorkOfPresentDataUpdate : 'classWorkOfPresentDataUpdate', + /** @desc: 课堂讲授活动,选择不同的内容 */ + MSG_classlecturePagesrc : 'classlecturePagesrc', + // === 新定义-消息头 === + /** @desc: 点赞 */ + MSG_0001: 0x0001, + MSG_0002: 0x0002, + MSG_0003: 0x0003, + MSG_0004: 0x0004, + MSG_0005: 0x0005, + MSG_0006: 0x0006, + MSG_0007: 0x0007, + MSG_0008: 0x0008, + MSG_0009: 0x0009, + MSG_0010: 0x000a, + MSG_0011: 0x000b, + MSG_0012: 0x000c, + MSG_0013: 0x000d, + MSG_0014: 0x000e, + MSG_0015: 0x000f, + MSG_0016: 0x0010, + MSG_0017: 0x0011, + MSG_0018: 0x0012, + MSG_0019: 0x0013, + MSG_0020: 0x0014, + MSG_0021: 0x0015, + MSG_0022: 0x0016, + MSG_0023: 0x0017, + MSG_0024: 0x0018, + MSG_0025: 0x0019, + MSG_0026: 0x001a, + MSG_0027: 0x001b, + MSG_0028: 0x001c, + MSG_0029: 0x001d, + MSG_0030: 0x001e, + MSG_0031: 0x001f, + MSG_0032: 0x0020, + MSG_0033: 0x0021, + MSG_0034: 0x0022, + MSG_0035: 0x0023, + } +} + +export { MsgEnum as default } \ No newline at end of file diff --git a/src/renderer/src/plugins/shareStore.js b/src/renderer/src/plugins/shareStore.js index 6ff18a8..5b34244 100644 --- a/src/renderer/src/plugins/shareStore.js +++ b/src/renderer/src/plugins/shareStore.js @@ -33,7 +33,7 @@ export function shareStorePlugin({store}) { // 同步数据-发送给主线程-单独 function stateSync(storeName, key, value) { - console.log('state-change', storeName, key, value) + // console.log('state-change', storeName, key, value) let jsonStr = '' if (typeof key === 'string') jsonStr = JSON.stringify({[key]:value}) else if (typeof value === 'object') jsonStr = JSON.stringify(key) diff --git a/src/renderer/src/views/tool/components/imChat.vue b/src/renderer/src/views/tool/components/imChat.vue index d7b1025..8e9ee59 100644 --- a/src/renderer/src/views/tool/components/imChat.vue +++ b/src/renderer/src/views/tool/components/imChat.vue @@ -7,34 +7,48 @@ import * as http from '@/api/apiService' // 自定义api service // import { ipcMsgSend, ipcHandle, ipcMain, ipcMsgInvoke } from '@/utils/tool' // 相关工具 const userStore = useUserStore() const emits = defineEmits(['change']) -let imChat +const props = defineProps({ + isGroup: { type: Boolean, default: false }, // 是否创建群 +}) +const imChatObj = reactive({imChat:null}) onMounted(() => { - // console.log(userStore) + // console.log(imChatObj) initImChat() }) // 初始化 im-chat const initImChat = async () => { // console.log('im-chat', userStore.user.timuserid) try { + const { timuserid, deptId, userId } = userStore.user // 获取腾讯云签名 - const res = await http.imChat.getTxCloudSign() + const res = await http.imChat.getTxCloudSign({imUserId: timuserid}) if (res && res.code == 200) { const { sdkAppId, sign } = res.data - const { timuserid, deptId, userId } = userStore.user // 群名称 const groupName = `${deptId}-classteaching-${userId}` // 注册im-chat // await ipcMsgInvoke('im-chat:init', sdkAppId) - imChat = new ImChat(sdkAppId, sign, timuserid) + imChatObj.imChat = new ImChat(sdkAppId, sign, timuserid) // 初始化 im-chat - await imChat.init() + await imChatObj.imChat.init() // 登录 im-chat - await imChat.login() - imChat.watch(res => { - console.log('im-chat watch: ', res) + await imChatObj.imChat.login() + // 监听 im-chat 消息 + imChatObj.imChat.watch((res) => { + const [msg] = res[0]?JSON.parse(res[0]):[] + imChatObj.imChat.setConsole('%cchat-msg', msg) + // 系统消息 + if(msg.message_sender == '@TIM#SYSTEM'){ + emits('change', 'msg-system', null, msg) + } else { // 普通消息-数据处理 + (msg?.message_elem_array||[]).forEach(o => { + const msgData = !!o.text_elem_content ? JSON.parse(o.text_elem_content)||'' : '' + emits('change', 'msg', msgData, msg) + }) + } }) // 创建群 - await createGroup(groupName) + if (props.isGroup) await createGroup(groupName) } } catch (error) { console.log('im-error: ', error) @@ -42,17 +56,17 @@ const initImChat = async () => { } // 创建群组 const createGroup = async (groupName) => { - if (!imChat) return - await imChat.createGroup(groupName) - const params = {type:'createGroup', data: imChat.timGroupId} - emits('change', params) + if (!imChatObj.imChat) return + await imChatObj.imChat.createGroup(groupName) + // emits('change', {type:'createGroup', data: imChat.timGroupId}) + emits('change', 'createGroup', imChatObj.imChat.timGroupId) } // 退出 -const logout = () => imChat?.logout() +const logout = () => imChatObj.imChat?.logout() // 解散群 -const deleteGroup = () => imChat?.deleteGroup() +const deleteGroup = () => imChatObj.imChat?.deleteGroup() -defineExpose({ logout, deleteGroup, imChat }) +defineExpose({ logout, deleteGroup, imChatObj })