Merge pull request 'zdg' (#150) from zdg into main

Reviewed-on: #150
This commit is contained in:
zhengdegang 2024-08-22 09:29:49 +08:00
commit 968b5d3e43
14 changed files with 254 additions and 53 deletions

View File

@ -1,9 +1,9 @@
@font-face {
font-family: "iconfont"; /* Project id 2794390 */
src: url('iconfont.woff2?t=1724134927539') format('woff2'),
url('iconfont.woff?t=1724134927539') format('woff'),
url('iconfont.ttf?t=1724134927539') format('truetype'),
url('iconfont.svg?t=1724134927539#iconfont') format('svg');
src: url('iconfont.woff2?t=1724212790213') format('woff2'),
url('iconfont.woff?t=1724212790213') format('woff'),
url('iconfont.ttf?t=1724212790213') format('truetype'),
url('iconfont.svg?t=1724212790213#iconfont') format('svg');
}
.iconfont {
@ -14,6 +14,22 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-yiwen:before {
content: "\e687";
}
.icon-yiwen-01:before {
content: "\e688";
}
.icon-yihuo:before {
content: "\e689";
}
.icon-a-yiwen:before {
content: "\e6b1";
}
.icon-zan:before {
content: "\e658";
}

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,34 @@
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "20574719",
"name": "疑问",
"font_class": "yiwen",
"unicode": "e687",
"unicode_decimal": 59015
},
{
"icon_id": "21052326",
"name": "yiwen-01",
"font_class": "yiwen-01",
"unicode": "e688",
"unicode_decimal": 59016
},
{
"icon_id": "30456317",
"name": "疑惑",
"font_class": "yihuo",
"unicode": "e689",
"unicode_decimal": 59017
},
{
"icon_id": "33439935",
"name": "[疑问]",
"font_class": "a-yiwen",
"unicode": "e6b1",
"unicode_decimal": 59057
},
{
"icon_id": "1242129",
"name": "赞",

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 302 KiB

After

Width:  |  Height:  |  Size: 337 KiB

View File

@ -9,6 +9,7 @@
import * as TYPES from './enumbers' // sdk相关枚举
import MsgEnum from './msgEnum' // 消息相关枚举(自定义)
import IMListeners from './imLiseners' // im消息-监听器
// @ts-ignore
const API = window.api
// TIM生成签名
// import * as GenerateUserSig from './userSig' // 引入签名生成器
@ -31,16 +32,23 @@ export class ImChat {
}
/**
* @description 构造函数
* @param {number} SDKAppID
* @param {string} userSig
* @param {string} userID
* @param {boolean} isInit
*/
constructor(SDKAppID, userSig, userID, isInit) {
this.SDKAppID = SDKAppID
this.userSig = userSig
this.userID = userID
// window.test = this
if (isInit) return this.init()
if (isInit) this.init()
}
// 设置配置
async setConfig() {
const res=await this.timChat.TIMSetConfig({ // TIMSetConfigParam
await this.timChat.TIMSetConfig({ // TIMSetConfigParam
json_config: { // JSONCongfig
set_config_log_level: TYPES.TIMLogLevel.kTIMLog_Test,
set_config_callback_log_level: TYPES.TIMLogLevel.kTIMLog_Error,
@ -87,15 +95,18 @@ export class ImChat {
}
// 生成签名
genTestUserSig() {
const options = {
SDKAppID: this.SDKAppID,
secretKey: this.secretKey,
userID: this.userID,
}
const { userSig } = GenerateUserSig.genTestUserSig(options)
this.userSig = userSig
// const options = {
// SDKAppID: this.SDKAppID,
// secretKey: this.secretKey,
// userID: this.userID,
// }
// const { userSig } = GenerateUserSig.genTestUserSig(options)
// this.userSig = userSig
}
// 监听
/**
* @description 监听消息
* @param {Function} callback
*/
watch(callback) {
// // 先移除监听
// this.timChat.TIMRemoveRecvNewMsgCallback()
@ -167,7 +178,11 @@ export class ImChat {
return error
})
}
// 创建群组 群名和初始成员 userID
/**
* @description 创建群组 群名和初始成员 userID
* @param {any} name
* @param {any[]} memberList
*/
createGroup(name, memberList=[]) {
if (!this.timChat) return
if (!!this.timGroupId) return console.log('群组已存在')
@ -216,7 +231,10 @@ export class ImChat {
data: '', // 用户自定义数据
})
}
// 设置群消息接收
/**
* @description 设置群消息接收
* @param {string} timGroupId
*/
setGroupMsgReceive(timGroupId) {
if (!this.timGroupId) this.timGroupId = timGroupId || ''
if (!this.timGroupId) return console.log('timGroupId为空')
@ -236,7 +254,11 @@ export class ImChat {
return error
})
}
// 发送消息
/**
* @description 发送消息
* @param {any} conv_id
* @param {any} msg
*/
sendMsg(conv_id, msg) {
if (!conv_id) return console.log('conv_id为空')
if (typeof msg == 'object') msg = JSON.stringify(msg)
@ -255,15 +277,33 @@ export class ImChat {
user_data: '', // 用户自定义数据
// callback: (data) => {}
}
console.log('发送消息', option)
// console.log('发送消息', option)
this.setConsole('%cim-chat: 发送消息', option)
return this.timChat.TIMMsgSendMessageV2(option)
}
/**
* @description 发送群消息
* @param {any} msg
* @param {*} head
* @param {*} type
*/
sendMsgGroup(msg, head, type) {
const msgObj = this.getMsgObj(head, msg, type)
// console.log('发送群消息', msgObj)
return this.sendMsg(this.timGroupId, msgObj)
}
// 发送关闭(下课)消息
sendMsgClosed(){
const msg = this.getMsgObj(MsgEnum.HEADS.MSG_closed, '下课', MsgEnum.TYPES.TEACHER)
const msg = this.getMsgObj(MsgEnum.HEADS.MSG_closed, '下课')
return this.sendMsg(this.timGroupId, msg)
}
// 获取消息对象
/**
* @description 获取消息对象
* @param {string} msgHead
* @param {string} msg
* @param {string} type
* @param {string|number} sender
*/
getMsgObj(msgHead, msg, type, sender, option={}) {
if (!msgHead) throw new Error('msgHead is required')
if (!msg) throw new Error('msg is required')
@ -271,19 +311,27 @@ export class ImChat {
return {
msgKey: msgHead,
msgcontent: msg,
msgType: type ?? MsgEnum.TYPES.STUDENT, // 默认为学生
msgType: type || MsgEnum.TYPES.TEACHER, // 默认为老师
senduserid: sender ?? this.userID,
...option
}
}
// 设置控制台样式
/**
* @description 设置控制台样式
* @param {string} hearStr
* @param {string[]} args
*/
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)
}
// 获取数据字符串
/**
* @description 获取数据字符串
* @param {*} data
* @returns
*/
toStr = (data) => {
if (typeof data === 'string') data = {type: data}
return JSON.stringify(data)

View File

@ -95,6 +95,7 @@ export class MsgEnum {
// === 新定义-消息头 ===
/** @desc: 点赞 */
MSG_0001: 0x0001,
/** @desc: 疑惑 */
MSG_0002: 0x0002,
MSG_0003: 0x0003,
MSG_0004: 0x0004,
@ -109,6 +110,7 @@ export class MsgEnum {
MSG_0013: 0x000d,
MSG_0014: 0x000e,
MSG_0015: 0x000f,
/** @desc: 作业推送 */
MSG_0016: 0x0010,
MSG_0017: 0x0011,
MSG_0018: 0x0012,

View File

@ -77,14 +77,14 @@ import { getSmarttalkPage, getPrepareById } from '@/api/file'
import SetHomework from '@/views/prepare/container/set-homework.vue'
import FileImage from '@/components/file-image/index.vue'
import { useGetHomework } from '@/hooks/useGetHomework'
import { ipcMsgSend } from '@/utils/tool'
import { ipcMsgSend, ipcMsgInvoke } from '@/utils/tool'
import { useToolState } from '@/store/modules/tool'
import { asyncLocalFile } from '@/utils/talkFile'
import Lesson from './lesson.vue';
import { parseCataByNode } from '@/utils/talkFile'
import outLink from '@/utils/linkConfig'
import MsgEnum from '@/plugins/imChat/msgEnum' //
import { ipcMsgSend2 } from '@/utils/tool'
const route = useRoute();
const usertore = useUserStore().user
const toolStore = useToolState()
@ -110,8 +110,10 @@ const sendHomework = (row) => {
setDialog.value = true
}
//
const closeHomework = () => {
const closeHomework = async() => {
ipcMsgSend('tool-sphere:set:ignore', true)
// im-(app|)
await ipcMsgInvoke('im-chat:msg', curRow.value.id, MsgEnum.HEADS.MSG_0016)
setDialog.value = false
}

View File

@ -4,7 +4,7 @@ import { onMounted, ref, reactive, watchEffect } from 'vue'
import { ImChat } from '@/plugins/imChat'
import useUserStore from '@/store/modules/user'
import * as http from '@/api/apiService' // api service
// import { ipcMsgSend, ipcHandle, ipcMain, ipcMsgInvoke } from '@/utils/tool' //
import { ipcMsgSend, ipcHandle, ipcMain, ipcMsgInvoke } from '@/utils/tool' //
const userStore = useUserStore()
const emits = defineEmits(['change'])
const props = defineProps({
@ -12,8 +12,7 @@ const props = defineProps({
})
const imChatObj = reactive({imChat:null})
onMounted(() => {
// console.log(imChatObj)
// initImChat()
ipcMainHandle() // -ipcMain im
})
// im-chat
const initImChat = async (timGroupId) => {
@ -95,6 +94,14 @@ const logout = () => imChatObj.imChat?.logout()
//
const deleteGroup = () => imChatObj.imChat?.deleteGroup()
// -ipcMain im
const ipcMainHandle = () => {
ipcMain?.removeHandler?.('im-chat:msg') // ipcMain
ipcMain?.handle?.('im-chat:msg', (e, msg, head, type) => { // ipcMain
return imChatObj.imChat?.sendMsgGroup(msg, head, type)
})
}
defineExpose({ initImChat, logout, deleteGroup, imChatObj })
</script>

View File

@ -1,8 +1,8 @@
<template>
<div class="warp" ref="btnRef">
<div class="warp" ref="btnRef" :style="isFold?'min-height:auto;':''">
<slot name="start"></slot>
<!-- 工具按钮 -->
<el-space direction="vertical">
<el-space direction="vertical" v-show="!isFold">
<template v-for="(item,index) in list">
<slot :name="item.prop" :item="item" :index="index">
<div class="c-btn flex flex-col items-center gap-2 p-2" @click.stop="clickHandel(item,$event)">
@ -13,7 +13,11 @@
</template>
<slot name="append"></slot>
</el-space>
<slot name="end"></slot>
<slot name="end">
<span class="fold" @click="isFold=!isFold" :style="isFold?'margin: 5px;':''">
{{isFold?'<<<':'>>>'}}
</span>
</slot>
<!-- 内容部分 -->
<transition name="el-fade-in">
<div class="c-popover" :style="`--top: ${topPos}px;--height:${hPost}px;`" v-show="isVisible">
@ -54,6 +58,7 @@ const activeObj = ref(null) // 当前激活的按钮
const btnRef = ref(null) // -ref
const topPos = ref(30) // -
const hPost = ref(0) // -
const isFold = ref(false) //
let posBtnAll = {} //
// === ===
const list = computed(() => props.data.map((o,i) => {
@ -100,7 +105,7 @@ const clickHandel = (o, e) => {
min-width: 4em;
border-radius: 4em;
background-color: #121212;
.el-space{margin: 20px 0;}
.el-space{margin: 20px 0 0;}
.c-btn{
color: #d9dce3;
cursor: pointer;
@ -113,6 +118,20 @@ const clickHandel = (o, e) => {
background: #454545fa;
}
}
.fold{
display: block;
color: #ccc;
font-size: 14px;
padding: 3px 6px;
cursor: pointer;
user-select: none;
margin-bottom: 10px;
&:hover{
color: #409eff;
border-radius: 4px;
// background: #454545fa;
}
}
}
.c-popover{
--top: 30px;

View File

@ -1,12 +1,21 @@
<template>
<el-button v-if="props.test" type="primary" @click="trigger">测试</el-button>
<div v-if="props.test">
<el-button type="primary" @click="trigger">点赞</el-button>
<el-button type="primary" @click="trigger(1, '学生A')">疑惑</el-button>
</div>
<div ref="warpRef" class="c-warp">
<template v-for="(item, index) in iconCache.list">
<slot>
<el-icon v-if="props.def"><Star /></el-icon>
<svg v-else class="icon" aria-hidden="true">
<use :xlink:href="`#icon-`+item.icon"></use>
</svg>
<div v-else class="item">
<svg class="icon" aria-hidden="true">
<use :xlink:href="`#icon-`+item.icon"></use>
</svg>
<!-- 学生姓名 -->
<div class="tip" v-if="item.name">
<span :title="item.name">{{item.name}}</span>
</div>
</div>
</slot>
</template>
</div>
@ -20,18 +29,22 @@ const props = defineProps({ test: Boolean, def: Boolean })
const slots = useSlots() //
let iconCache = reactive({list:[]}) //
const isSlot = !!Object.keys(slots).length // 使
// === ===
const trigger = async () => {
// === type 1 def ===
const trigger = async (type, name) => {
if (typeof type == 'string') { name = type; type = 1 } //
iconCache.list.push({
icon: isSlot||props.def ? '' : getRandomIcon() //
name,
icon: isSlot||props.def ? '' : getRandomIcon(type) //
})
nextTick(() => {
const el = warpRef.value.lastElementChild //
animInit(el)
const tipEl = el.querySelector('.tip') //
// console.log(el)
animInit(el, tipEl, type)
})
}
//
const animInit = (el) => {
const animInit = (el, tipEl, type) => {
const { height, width } = el.getBoundingClientRect()
const sumH = warpRef.value.clientHeight
const sumW = warpRef.value.clientWidth
@ -41,13 +54,23 @@ const animInit = (el) => {
let opacity = 0 //
let scale = 0 //
let transform = '' //
let transform2 = '' // -
let right = getRandom(5, maxw, false, true) // -
let isPlus = getRandomBool()?'':'-' // -
let isPlus2 = isPlus ? '' : '-' // --
let rotate = getRandom(0, 60, false, true) // -
let scaleMax = getRandom(0.5, 1.5, true, true)+1 // -
setStatic(el,'position','absolute') // -
setStatic(el, 'right', right, 'px') // -
//
if (type === 1) { //
rotate = getRandom(0, 15, false, true) // -
scaleMax = getRandom(2.5, 3, true, true) // -
}
// console.log('zdg: ', right, isPlus, rotate, scaleMax, maxw)
const amFn = () => { // opacity: 0.5;
// if (bottom == 130) return
// -
if (bottom > maxH) {el.remove();resetIconCache();return}
// : -
@ -58,12 +81,16 @@ const animInit = (el) => {
// 退-
else if (bottom > maxH - (scaleMax/0.1) && scale > 0) scale = toNumber(scale - 0.1)
bottom++
// console.log('zdg: ', scale)
// console.log('zdg: ', scale) rotate(${isPlus2}${rotate}deg)
//
transform = `rotate(${isPlus}${rotate}deg) scale(${scale})`
setStatic(el, 'bottom', bottom, 'px')
setStatic(el, 'opacity', opacity)
setStatic(el, 'transform', transform)
if (!!tipEl && type) {
transform2 = `rotate(${isPlus2}${rotate}deg) scale(${toNumber(1/scale)})`
setStatic(tipEl, 'transform', transform2)
}
requestAnimationFrame(amFn)
}
amFn() //
@ -77,13 +104,21 @@ const resetIconCache = () => {
if (!len && iconCache.list.length) iconCache.list = []
}
// -
const getRandomIcon = () => {
const iconArr = [ // icon
'zan10', 'zanping', 'zan9', 'dianzan-red', 'zan8', 'zan11', 'zan7', 'MBEfenggeduosetubiao-xihuan',
'zan6', 'zan-yizan', 'zan5', 'yizan', 'zan4', 'zan3', 'zan2', 'zan1', 'zan'
]
const getRandomIcon = (type) => {
let iconArr = []
switch (type) {
case 1: // icon-
iconArr = ['yiwen', 'a-yiwen', 'yihuo', 'yiwen-01']
break
default:
iconArr = [ // icon-
'zan10', 'zanping', 'zan9', 'dianzan-red', 'zan8', 'zan11', 'zan7', 'MBEfenggeduosetubiao-xihuan',
'zan6', 'zan-yizan', 'zan5', 'yizan', 'zan4', 'zan3', 'zan2', 'zan1', 'zan'
]
break
}
const ind = getRandom(0, iconArr.length-1)
return iconArr[ind]
return iconArr[ind]||''
}
// isFloat isMax
const getRandom = (min, max, isFloat, isMax, pos=2) => {
@ -109,5 +144,39 @@ defineExpose({ trigger })
width: 30%;
height: 50%;
pointer-events: none;
.item{
display: inline-block;
.tip{
position: absolute;
padding: 3px 6px;
border-radius: 4px;
background: linear-gradient(90deg, #9fe597, #cce581);
font-size: 12px;
bottom: 0;
right: 0;
width: 65px;
transform-origin: right top;
span{
display: inline-block;
max-width: 100%;
overflow: hidden;
/* text-overflow: ellipsis; */
white-space: nowrap;
cursor: default;
}
&::before{
content: " ";
position: absolute;
width: 10px;
height: 10px;
z-index: -1;
box-sizing: border-box;
background: linear-gradient(45deg, #b2e68d, #bce689);
bottom: -3px;
left: 50%;
transform: translateX(-50%) rotate(45deg);
}
}
}
}
</style>

View File

@ -78,7 +78,7 @@ const btnList = [ // 工具栏按钮列表
]
// === ===
onMounted(async() => {
// getClassInfo() // ex3
getClassInfo() // ex3
setTimeout(() => {
resetStatus() // -
}, 200);
@ -126,10 +126,11 @@ const chatChange = (type, data, ...args) => {
const msgId = (args||[])[0].message_msg_id
const { msgKey:head, msgcontent:msg, senduserid:sendId, msgType } = data
switch(head) {
case MsgEnum.HEADS.MSG_0001:
case MsgEnum.HEADS.MSG_0001: //
case MsgEnum.HEADS.MSG_0002: //
// console.log(':', data)
if(msgIds.includes(msgId)) return // -
upvoteRef.value.trigger() //
upvoteRef.value.trigger(data.name) // |
if (msgIds.length >= 100) msgIds.shift() //
msgIds.push(msgId) //
break
@ -146,6 +147,7 @@ const setIgnore = (bool) => {ipcMsgSend('tool-sphere:set:ignore', bool)}
// : |
const resetStatus = () => {
if (toolStore.isToolWin) return // -
ipcMain?.removeHandler('tool-sphere:reset') //
ipcMain?.handle?.('tool-sphere:reset', () => {
setTimeout(() => {
boardVueRef.value.handleMode(tabActive.value)