This commit is contained in:
zdg 2024-08-16 10:15:12 +08:00
parent 758a4b09c9
commit 6ae7c2c4b7
6 changed files with 306 additions and 41 deletions

View File

@ -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' }

View File

@ -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)
}
}

View File

@ -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 }

View File

@ -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)

View File

@ -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 })
</script>
<template>

View File

@ -7,10 +7,10 @@
<side-vue v-ignore @ignore-mounted="sideMouse" @change="sideChange"></side-vue>
<!-- 点赞组件 -->
<upvote-vue></upvote-vue>
<upvote-vue ref="upvoteRef"></upvote-vue>
<!-- im-chat 聊天组件 -->
<!-- <im-chat ref="imChatRef" @change="chatChange" /> -->
<im-chat ref="imChatRef" @change="chatChange" />
<!-- 底部工具栏 -->
<div class="tool-bottom-all" @mouseenter="mouseChange(0)" @mouseleave="mouseChange(1)">
@ -39,7 +39,7 @@
// electron
import { onMounted, ref, reactive, watchEffect } from 'vue'
import { useRoute } from 'vue-router';
// import { startClass, endClass } from '@/api/classManage'
import { ElMessageBox, ElMessage } from 'element-plus'
import * as classManageApi from '@/api/classManage'
import logo from '@root/resources/icon.png' // logo
import boardVue from './components/board.vue' // -
@ -50,14 +50,17 @@ import vDrag from './directive/drag' // 自定义指令-拖拽
import vIgnore from './directive/ignore' // -穿
import { useToolState } from '@/store/modules/tool' // -
import { ipcMsgSend, ipcHandle, ipcMain, ipcMsgInvoke } from '@/utils/tool' //
import MsgEnum from '@/plugins/imChat/msgEnum' // -(nuem)
const route = useRoute();
const tabActive = ref('select') //
const isFold = ref(false) //
const isDrag = ref(false) //
const dragtime = ref(0) // -
const isShow = ref(false) // -
const isOver = ref(false) //
const toolStore = useToolState() //
const boardVueRef=ref(null) // ref
const upvoteRef = ref(null) // ref
const imChatRef = ref(null) // im-chat ref
const classObj = reactive({ //
id: route.query.reservId, // id
@ -75,7 +78,6 @@ const btnList = [ // 工具栏按钮列表
// === ===
onMounted(async() => {
setTimeout(() => {
console.log(classObj)
resetStatus() // -
}, 200);
})
@ -103,11 +105,24 @@ const mouseChange = (bool) => {
if (!isShow.value) resBool = !!bool
setIgnore(resBool)
}
// im-chat:
const chatChange = ({type, data}) => {
if (type == 'createGroup') { //
// classManageApi.startClass()
// im-chat: {type, data}
const chatChange = (type, data) => {
if (type == 'createGroup') { // -
classManageApi.startClass(classObj.id, data)
} else if (type == 'msg') { // im-chat
if (!data) return // msg
const { msgKey:head, msgcontent:msg, senduserid:sendId, msgType } = data
switch(head) {
case MsgEnum.HEADS.MSG_0001:
// console.log(':', data)
upvoteRef.value.trigger()
break
default:
console.log('未知消息:', data)
break
}
}
// console.log('im-chat-sphere:', type, data)
}
// 穿
@ -127,6 +142,10 @@ const resetStatus = () => {
// :
const sideMouse = e => {
const {type} = e.detail
if (isOver.value && type == 'mouseleave') {
setIgnore(false) // -穿
return
}
mouseChange(type == 'mouseleave')
}
// :
@ -140,11 +159,30 @@ const sideChange = async o => {
case 'win': //
break
case 'over': //
toolStore.isToolWin = false
await classManageApi.endClass(route.query.reservId)
await imChatRef.value?.deleteGroup() //
await imChatRef.value?.logout() // 退im
ipcMsgSend('tool-sphere:close') //
isOver.value = true
ElMessageBox.confirm('确认结束课程吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
await imChatRef.value?.imChatObj?.imChat?.sendMsgClosed() //
// const elMsg = ElMessage.warning({duration:0,message:'...'})
// // 2
// setTimeout(async() => {
// elMsg.close()
// toolStore.isToolWin = false
// await classManageApi.endClass(route.query.reservId)
// await imChatRef.value?.deleteGroup() //
// await imChatRef.value?.logout() // 退im
// ipcMsgSend('tool-sphere:close') //
// }, 2000);
isOver.value = false
setIgnore(true) // -穿
}).catch(() => {
isOver.value = false
setIgnore(true) // -穿
})
break
}
}