ws更新
Some checks failed
Check / lint (push) Has been cancelled
Check / typecheck (push) Has been cancelled
Check / build (build, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:app, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:app, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Has been cancelled
Some checks failed
Check / lint (push) Has been cancelled
Check / typecheck (push) Has been cancelled
Check / build (build, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:app, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:app, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Has been cancelled
This commit is contained in:
parent
aab593f281
commit
fd060743bf
5
env/.env.test
vendored
5
env/.env.test
vendored
@ -5,4 +5,7 @@ VITE_SHOW_CONSOLE = true
|
|||||||
# 是否开启sourcemap
|
# 是否开启sourcemap
|
||||||
VITE_SHOW_SOURCEMAP = true
|
VITE_SHOW_SOURCEMAP = true
|
||||||
# baseUrl
|
# baseUrl
|
||||||
VITE_BASEURL = 'https://warehouse.szjixun.cn/oa_backend'
|
# VITE_BASEURL = 'https://warehouse.szjixun.cn/oa_backend'
|
||||||
|
VITE_BASEURL = 'http://192.168.88.59:9503'
|
||||||
|
#VITE_SOCKET_API
|
||||||
|
VITE_SOCKET_API = 'ws://192.168.88.59:9504'
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {useStatus} from "@/store/status";
|
import {useStatus} from "@/store/status";
|
||||||
|
import ws from '@/connect'
|
||||||
const {statusBarHeight}= useStatus()
|
const {statusBarHeight}= useStatus()
|
||||||
const root = document.documentElement
|
const root = document.documentElement
|
||||||
root.style.setProperty('--statusBarHeight',`${statusBarHeight.value}px`)
|
root.style.setProperty('--statusBarHeight',`${statusBarHeight.value}px`)
|
||||||
|
const init = () => {
|
||||||
|
ws.connect()
|
||||||
|
}
|
||||||
|
init()
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "@/static/css/color.scss";
|
@import "@/static/css/color.scss";
|
||||||
|
160
src/api/chat/index.js
Normal file
160
src/api/chat/index.js
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
import request from '@/service/index.js'
|
||||||
|
|
||||||
|
// 获取聊天列表服务接口
|
||||||
|
export const ServeGetTalkList = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/talk/list',
|
||||||
|
method: 'GET',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 聊天列表创建服务接口
|
||||||
|
export const ServeCreateTalkList = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/talk/create',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除聊天列表服务接口
|
||||||
|
export const ServeDeleteTalkList = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/talk/delete',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对话列表置顶服务接口
|
||||||
|
export const ServeTopTalkList = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/talk/topping',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除聊天消息未读数服务接口
|
||||||
|
export const ServeClearTalkUnreadNum = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/talk/unread/clear',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取聊天记录服务接口
|
||||||
|
export const ServeTalkRecords = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/talk/records',
|
||||||
|
method: 'GET',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取转发会话记录详情列表服务接口
|
||||||
|
export const ServeGetForwardRecords = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/talk/records/forward',
|
||||||
|
method: 'GET',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对话列表置顶服务接口
|
||||||
|
export const ServeSetNotDisturb = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/talk/disturb',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找用户聊天记录服务接口
|
||||||
|
export const ServeFindTalkRecords = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/talk/records/history',
|
||||||
|
method: 'GET',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索用户聊天记录服务接口
|
||||||
|
export const ServeSearchTalkRecords = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/talk/search-chat-records',
|
||||||
|
method: 'GET',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ServeGetRecordsContext = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/talk/get-records-context',
|
||||||
|
method: 'GET',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送代码块消息服务接口
|
||||||
|
export const ServePublishMessage = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/talk/message/publish',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送聊天文件服务接口
|
||||||
|
export const ServeSendTalkFile = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/talk/message/file',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 撤回消息服务接口
|
||||||
|
export const ServeRevokeRecords = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/talk/message/revoke',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除消息服务接口
|
||||||
|
export const ServeRemoveRecords = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/talk/message/delete',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收藏表情包服务接口
|
||||||
|
export const ServeCollectEmoticon = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/talk/message/collect',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ServeSendVote = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/talk/message/vote',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ServeConfirmVoteHandle = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/talk/message/vote/handle',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
55
src/api/emoticon/index.js
Normal file
55
src/api/emoticon/index.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import request from '@/service/index.js'
|
||||||
|
|
||||||
|
// 查询用户表情包服务接口
|
||||||
|
export const ServeFindUserEmoticon = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/emoticon/list',
|
||||||
|
method: 'GET',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询系统表情包服务接口
|
||||||
|
export const ServeFindSysEmoticon = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/emoticon/system/list',
|
||||||
|
method: 'GET',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置用户表情包服务接口
|
||||||
|
export const ServeSetUserEmoticon = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/emoticon/system/install',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除收藏表情包服务接口
|
||||||
|
export const ServeDelCollectEmoticon = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/emoticon/del-collect-emoticon',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传表情包服务接口
|
||||||
|
export const ServeUploadEmoticon = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/emoticon/customize/create',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ServeDeleteEmoticon = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/emoticon/customize/delete',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
217
src/api/group/index.js
Normal file
217
src/api/group/index.js
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
import request from '@/service/index.js'
|
||||||
|
|
||||||
|
// 查询用户群聊服务接口
|
||||||
|
export const ServeGetGroups = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/group/list',
|
||||||
|
method: 'GET',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ServeGroupOvertList = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/group/overt/list',
|
||||||
|
method: 'GET',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取群信息服务接口
|
||||||
|
export const ServeGroupDetail = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/group/detail',
|
||||||
|
method: 'GET',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建群聊服务接口
|
||||||
|
export const ServeCreateGroup = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/group/create',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改群信息
|
||||||
|
export const ServeEditGroup = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/group/setting',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 邀请好友加入群聊服务接口
|
||||||
|
export const ServeInviteGroup = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/group/invite',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除群聊成员服务接口
|
||||||
|
export const ServeRemoveMembersGroup = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/group/member/remove',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 管理员解散群聊服务接口
|
||||||
|
export const ServeDismissGroup = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/group/dismiss',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ServeMuteGroup = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/group/mute',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ServeOvertGroup = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/group/overt',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户退出群聊服务接口
|
||||||
|
export const ServeSecedeGroup = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/group/secede',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改群聊名片服务接口
|
||||||
|
export const ServeUpdateGroupCard = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/group/member/remark',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户可邀请加入群聊的好友列表
|
||||||
|
export const ServeGetInviteFriends = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/group/member/invites',
|
||||||
|
method: 'GET',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取群聊成员列表
|
||||||
|
export const ServeGetGroupMembers = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/group/member/list',
|
||||||
|
method: 'GET',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取群聊公告列表
|
||||||
|
export const ServeGetGroupNotices = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/group/notice/list',
|
||||||
|
method: 'GET',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑群公告
|
||||||
|
export const ServeEditGroupNotice = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/group/notice/edit',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ServeGetGroupApplyList = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/group/apply/list',
|
||||||
|
method: 'GET',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ServeGetGroupApplyAll = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/group/apply/all',
|
||||||
|
method: 'GET',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ServeDeleteGroupApply = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/group/apply/decline',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ServeAgreeGroupApply = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/group/apply/agree',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ServeCreateGroupApply = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/group/apply/create',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ServeGroupApplyUnread = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/group/apply/unread',
|
||||||
|
method: 'GET',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转让群主
|
||||||
|
export const ServeGroupHandover = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/group/handover',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分配管理员
|
||||||
|
export const ServeGroupAssignAdmin = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/group/assign-admin',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ServeGroupNoSpeak = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/group/no-speak',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
188
src/connect.js
Normal file
188
src/connect.js
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
// import { h } from 'vue'
|
||||||
|
// import { NAvatar } from 'naive-ui'
|
||||||
|
import { useTalkStore,useUserStore } from '@/store'
|
||||||
|
// import { notifyIcon } from '@/constant/default'
|
||||||
|
import WsSocket from './plugins/ws-socket'
|
||||||
|
import EventTalk from './event/talk'
|
||||||
|
// import EventKeyboard from './event/keyboard'
|
||||||
|
// import EventLogin from './event/login'
|
||||||
|
import EventRevoke from './event/revoke'
|
||||||
|
// import { getAccessToken, isLoggedIn } from './utils/auth'
|
||||||
|
import { useAuth } from "@/store/auth";
|
||||||
|
|
||||||
|
const { token } = useAuth()
|
||||||
|
|
||||||
|
const urlCallback = () => {
|
||||||
|
// if (!isLoggedIn()) {
|
||||||
|
// window.location.reload()
|
||||||
|
// }
|
||||||
|
|
||||||
|
return `${import.meta.env.VITE_SOCKET_API}/wss/default.io?token=${token.value}`
|
||||||
|
}
|
||||||
|
|
||||||
|
class Connect {
|
||||||
|
conn
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.conn = new WsSocket(urlCallback, {
|
||||||
|
onError: (evt) => {
|
||||||
|
console.log('Websocket 连接失败回调方法', evt)
|
||||||
|
},
|
||||||
|
// Websocket 连接成功回调方法
|
||||||
|
onOpen: () => {
|
||||||
|
// 更新 WebSocket 连接状态
|
||||||
|
useUserStore().updateSocketStatus(true)
|
||||||
|
// online.value = true;
|
||||||
|
useTalkStore().loadTalkList()
|
||||||
|
},
|
||||||
|
// Websocket 断开连接回调方法
|
||||||
|
onClose: () => {
|
||||||
|
// 更新 WebSocket 连接状态
|
||||||
|
useUserStore().updateSocketStatus(false)
|
||||||
|
// online.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.bindEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接
|
||||||
|
*/
|
||||||
|
connect() {
|
||||||
|
this.conn.connection()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 断开连接
|
||||||
|
*/
|
||||||
|
disconnect() {
|
||||||
|
this.conn.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接状态
|
||||||
|
* @returns WebSocket 连接状态
|
||||||
|
*/
|
||||||
|
isConnect() {
|
||||||
|
if (!this.conn.connect) return false
|
||||||
|
|
||||||
|
return this.conn.connect.readyState === 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推送事件消息
|
||||||
|
* @param event 事件名
|
||||||
|
* @param data 数据
|
||||||
|
*/
|
||||||
|
emit(event, data) {
|
||||||
|
this.conn.emit(event, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绑定监听消息事件
|
||||||
|
*/
|
||||||
|
bindEvents() {
|
||||||
|
this.onPing()
|
||||||
|
this.onPong()
|
||||||
|
this.onImMessage()
|
||||||
|
// this.onImMessageRead()
|
||||||
|
// this.onImContactStatus()
|
||||||
|
this.onImMessageRevoke()
|
||||||
|
// this.onImMessageKeyboard()
|
||||||
|
}
|
||||||
|
|
||||||
|
onPing() {
|
||||||
|
this.conn.on('ping', () => this.emit('pong', ''))
|
||||||
|
}
|
||||||
|
|
||||||
|
onPong() {
|
||||||
|
this.conn.on('pong', () => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
onImMessage() {
|
||||||
|
this.conn.on('im.message', (data) => new EventTalk(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// onImMessageRead() {
|
||||||
|
// this.conn.on('im.message.read', (data) => {
|
||||||
|
// const dialogueStore = useDialogueStore()
|
||||||
|
|
||||||
|
// if (dialogueStore.index_name !== `1_${data.sender_id}`) {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const { msg_ids = [] } = data
|
||||||
|
|
||||||
|
// for (const msgid of msg_ids) {
|
||||||
|
// dialogueStore.updateDialogueRecord({ msg_id: msgid, is_read: 1 })
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
onImContactStatus() {
|
||||||
|
// 好友在线状态事件
|
||||||
|
// this.conn.on('im.contact.status', (data) => new EventLogin(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
onImMessageKeyboard() {
|
||||||
|
// 好友键盘输入事件
|
||||||
|
// this.conn.on('im.message.keyboard', (data) => new EventKeyboard(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 即将废弃
|
||||||
|
onImMessageRevoke() {
|
||||||
|
// 消息撤回事件
|
||||||
|
this.conn.on('im.message.revoke', (data) => new EventRevoke(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
onImContactApply() {
|
||||||
|
// 好友申请事件
|
||||||
|
// this.conn.on('im.contact.apply', (data) => {
|
||||||
|
// window['$notification'].create({
|
||||||
|
// title: '好友申请通知',
|
||||||
|
// content: data.remark,
|
||||||
|
// description: `申请人: ${data.friend.nickname}`,
|
||||||
|
// meta: data.friend.created_at,
|
||||||
|
// avatar: () =>
|
||||||
|
// h(NAvatar, {
|
||||||
|
// size: 'small',
|
||||||
|
// round: true,
|
||||||
|
// src: notifyIcon,
|
||||||
|
// style: 'background-color:#fff;'
|
||||||
|
// }),
|
||||||
|
// duration: 3000
|
||||||
|
// })
|
||||||
|
// useUserStore().isContactApply = true
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
|
||||||
|
onImGroupApply() {
|
||||||
|
// 群申请消息
|
||||||
|
// this.conn.on('im.group.apply', () => {
|
||||||
|
// window['$notification'].create({
|
||||||
|
// title: '入群申请通知',
|
||||||
|
// content: '有新的入群申请,请注意查收',
|
||||||
|
// avatar: () =>
|
||||||
|
// h(NAvatar, {
|
||||||
|
// size: 'small',
|
||||||
|
// round: true,
|
||||||
|
// src: notifyIcon,
|
||||||
|
// style: 'background-color:#fff;'
|
||||||
|
// }),
|
||||||
|
// duration: 30000
|
||||||
|
// })
|
||||||
|
|
||||||
|
// useUserStore().isGroupApply = true
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
|
||||||
|
onEventError() {
|
||||||
|
this.conn.on('event_error', (data) => {
|
||||||
|
// window['$message'] && window['$message'].error(JSON.stringify(data))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出单例
|
||||||
|
export default new Connect()
|
96
src/constant/message.ts
Normal file
96
src/constant/message.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
export const ChatMsgTypeText = 1 // 文本消息
|
||||||
|
export const ChatMsgTypeCode = 2 // 代码消息
|
||||||
|
export const ChatMsgTypeImage = 3 // 图片文件
|
||||||
|
export const ChatMsgTypeAudio = 4 // 语音文件
|
||||||
|
export const ChatMsgTypeVideo = 5 // 视频文件
|
||||||
|
export const ChatMsgTypeFile = 6 // 其它文件
|
||||||
|
export const ChatMsgTypeLocation = 7 // 位置消息
|
||||||
|
export const ChatMsgTypeCard = 8 // 名片消息
|
||||||
|
export const ChatMsgTypeForward = 9 // 转发消息
|
||||||
|
export const ChatMsgTypeLogin = 10 // 登录消息
|
||||||
|
export const ChatMsgTypeVote = 11 // 投票消息
|
||||||
|
export const ChatMsgTypeMixed = 12 // 混合消息
|
||||||
|
export const ChatMsgTypeGroupNotice = 13 // 群公告消息
|
||||||
|
|
||||||
|
export const ChatMsgSysText = 1000 // 系统文本消息
|
||||||
|
export const ChatMsgSysGroupCreate = 1101 // 创建群聊消息
|
||||||
|
export const ChatMsgSysGroupMemberJoin = 1102 // 加入群聊消息
|
||||||
|
export const ChatMsgSysGroupMemberQuit = 1103 // 群成员退出群消息
|
||||||
|
export const ChatMsgSysGroupMemberKicked = 1104 // 踢出群成员消息
|
||||||
|
export const ChatMsgSysGroupMessageRevoke = 1105 // 管理员撤回成员消息
|
||||||
|
export const ChatMsgSysGroupDismissed = 1106 // 群解散
|
||||||
|
export const ChatMsgSysGroupMuted = 1107 // 群禁言
|
||||||
|
export const ChatMsgSysGroupCancelMuted = 1108 // 群解除禁言
|
||||||
|
export const ChatMsgSysGroupMemberMuted = 1109 // 群成员禁言
|
||||||
|
export const ChatMsgSysGroupMemberCancelMuted = 1110 // 群成员解除禁言
|
||||||
|
export const ChatMsgSysGroupNotice = 1111 // 编辑群公告
|
||||||
|
export const ChatMsgSysGroupTransfer = 1113 // 变更群主
|
||||||
|
|
||||||
|
export const ChatMsgTypeMapping = {
|
||||||
|
[ChatMsgTypeText]: '[文本消息]',
|
||||||
|
[ChatMsgTypeImage]: '[图片消息]',
|
||||||
|
[ChatMsgTypeAudio]: '[语音消息]',
|
||||||
|
[ChatMsgTypeVideo]: '[视频消息]',
|
||||||
|
[ChatMsgTypeFile]: '[文件消息]',
|
||||||
|
[ChatMsgTypeLocation]: '[位置消息]',
|
||||||
|
[ChatMsgTypeCard]: '[名片消息]',
|
||||||
|
[ChatMsgTypeForward]: '[转发消息]',
|
||||||
|
[ChatMsgTypeLogin]: '[登录消息]',
|
||||||
|
[ChatMsgTypeVote]: '[投票消息]',
|
||||||
|
[ChatMsgTypeCode]: '[代码消息]',
|
||||||
|
[ChatMsgTypeMixed]: '[图文消息]',
|
||||||
|
[ChatMsgTypeGroupNotice]: '[群公告]',
|
||||||
|
[ChatMsgSysText]: '[系统消息]',
|
||||||
|
[ChatMsgSysGroupCreate]: '[创建群消息]',
|
||||||
|
[ChatMsgSysGroupMemberJoin]: '[加入群消息]',
|
||||||
|
[ChatMsgSysGroupMemberQuit]: '[退出群消息]',
|
||||||
|
[ChatMsgSysGroupMemberKicked]: '[踢出群消息]',
|
||||||
|
[ChatMsgSysGroupMessageRevoke]: '[撤回消息]',
|
||||||
|
[ChatMsgSysGroupDismissed]: '[群解散消息]',
|
||||||
|
[ChatMsgSysGroupMuted]: '[群禁言消息]',
|
||||||
|
[ChatMsgSysGroupCancelMuted]: '[群解除禁言消息]',
|
||||||
|
[ChatMsgSysGroupMemberMuted]: '[群成员禁言消息]',
|
||||||
|
[ChatMsgSysGroupMemberCancelMuted]: '[群成员解除禁言消息]',
|
||||||
|
[ChatMsgSysGroupNotice]: '[群公告]'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 消息类型 - 消息组件 映射关系
|
||||||
|
export const MessageComponents = {
|
||||||
|
[ChatMsgTypeText]: 'text-message',
|
||||||
|
[ChatMsgTypeImage]: 'image-message',
|
||||||
|
[ChatMsgTypeAudio]: 'audio-message',
|
||||||
|
[ChatMsgTypeVideo]: 'video-message',
|
||||||
|
[ChatMsgTypeFile]: 'file-message',
|
||||||
|
[ChatMsgTypeLocation]: 'location-message',
|
||||||
|
[ChatMsgTypeCard]: 'user-card-message',
|
||||||
|
[ChatMsgTypeForward]: 'forward-message',
|
||||||
|
[ChatMsgTypeLogin]: 'login-message',
|
||||||
|
[ChatMsgTypeVote]: 'vote-message',
|
||||||
|
[ChatMsgTypeCode]: 'code-message',
|
||||||
|
[ChatMsgTypeMixed]: 'mixed-message',
|
||||||
|
[ChatMsgTypeGroupNotice]: 'group-notice-message',
|
||||||
|
[ChatMsgSysText]: 'sys-text-message',
|
||||||
|
[ChatMsgSysGroupCreate]: 'sys-group-create-message',
|
||||||
|
[ChatMsgSysGroupMemberJoin]: 'sys-group-join-message',
|
||||||
|
[ChatMsgSysGroupMemberQuit]: 'sys-group-member-quit-message',
|
||||||
|
[ChatMsgSysGroupMemberKicked]: 'sys-group-member-kicked-message',
|
||||||
|
// [ChatMsgSysGroupMessageRevoke]: '[撤回消息]',
|
||||||
|
// [ChatMsgSysGroupDismissed]: '[群解散消息]',
|
||||||
|
[ChatMsgSysGroupMuted]: 'sys-group-muted-message',
|
||||||
|
[ChatMsgSysGroupCancelMuted]: 'sys-group-cancel-muted-message',
|
||||||
|
[ChatMsgSysGroupMemberMuted]: 'sys-group-member-muted-message',
|
||||||
|
[ChatMsgSysGroupMemberCancelMuted]: 'sys-group-member-cancel-muted-message',
|
||||||
|
[ChatMsgSysGroupTransfer]: 'sys-group-transfer-message'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可转发的消息类型
|
||||||
|
export const ForwardableMessageType = [
|
||||||
|
ChatMsgTypeText,
|
||||||
|
ChatMsgTypeCode,
|
||||||
|
ChatMsgTypeImage,
|
||||||
|
ChatMsgTypeAudio,
|
||||||
|
ChatMsgTypeVideo,
|
||||||
|
ChatMsgTypeFile,
|
||||||
|
ChatMsgTypeLocation,
|
||||||
|
ChatMsgTypeCard
|
||||||
|
]
|
59
src/event/base.js
Normal file
59
src/event/base.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { useDialogueStore } from '@/store'
|
||||||
|
// import router from '@/router'
|
||||||
|
import { useAuth } from "@/store/auth/index.js";
|
||||||
|
|
||||||
|
const { userInfo } = useAuth()
|
||||||
|
|
||||||
|
class Base {
|
||||||
|
/**
|
||||||
|
* 初始化
|
||||||
|
*/
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前登录用户的ID
|
||||||
|
*/
|
||||||
|
getAccountId() {
|
||||||
|
return userInfo.value.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
getTalkParams() {
|
||||||
|
let dialogueStore = useDialogueStore()
|
||||||
|
|
||||||
|
let { talk_type, receiver_id } = dialogueStore.talk
|
||||||
|
|
||||||
|
return {
|
||||||
|
talk_type,
|
||||||
|
receiver_id,
|
||||||
|
index_name: dialogueStore.index_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断消息是否来自当前对话
|
||||||
|
*
|
||||||
|
* @param {Number} talk_type 聊天消息类型[1:私信;2:群聊;]
|
||||||
|
* @param {Number} sender_id 发送者ID
|
||||||
|
* @param {Number} receiver_id 接收者ID
|
||||||
|
*/
|
||||||
|
isTalk(talk_type, sender_id, receiver_id) {
|
||||||
|
let params = this.getTalkParams()
|
||||||
|
|
||||||
|
if (talk_type != params.talk_type) {
|
||||||
|
return false
|
||||||
|
} else if (params.receiver_id == receiver_id || params.receiver_id == sender_id) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断用户是否打开对话页
|
||||||
|
*/
|
||||||
|
// isTalkPage() {
|
||||||
|
// return ['/message', '/'].includes(router.currentRoute.value.path)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Base
|
88
src/event/revoke.js
Normal file
88
src/event/revoke.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import Base from './base'
|
||||||
|
import { useDialogueStore, useTalkStore } from '@/store'
|
||||||
|
import { parseTime } from '@/utils/datetime'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 好友状态事件
|
||||||
|
*/
|
||||||
|
class Revoke extends Base {
|
||||||
|
/**
|
||||||
|
* @var resource 资源
|
||||||
|
*/
|
||||||
|
resource
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送者ID
|
||||||
|
*/
|
||||||
|
sender_id = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接收者ID
|
||||||
|
*/
|
||||||
|
receiver_id = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 聊天类型[1:私聊;2:群聊;]
|
||||||
|
*/
|
||||||
|
talk_type = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化构造方法
|
||||||
|
*
|
||||||
|
* @param {Object} resource Socket消息
|
||||||
|
*/
|
||||||
|
constructor(resource) {
|
||||||
|
super()
|
||||||
|
|
||||||
|
this.resource = resource
|
||||||
|
this.sender_id = resource.sender_id
|
||||||
|
this.receiver_id = resource.receiver_id
|
||||||
|
this.talk_type = resource.talk_type
|
||||||
|
this.msg_id = resource.msg_id
|
||||||
|
|
||||||
|
this.handle()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断消息发送者是否来自于我
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
isCurrSender() {
|
||||||
|
return this.sender_id == this.getAccountId()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取对话索引
|
||||||
|
*
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
getIndexName() {
|
||||||
|
if (this.talk_type == 2) {
|
||||||
|
return `${this.talk_type}_${this.receiver_id}`
|
||||||
|
}
|
||||||
|
|
||||||
|
let receiver_id = this.isCurrSender() ? this.receiver_id : this.sender_id
|
||||||
|
|
||||||
|
return `${this.talk_type}_${receiver_id}`
|
||||||
|
}
|
||||||
|
|
||||||
|
handle() {
|
||||||
|
useTalkStore().updateItem({
|
||||||
|
index_name: this.getIndexName(),
|
||||||
|
msg_text: this.resource.text,
|
||||||
|
updated_at: parseTime(new Date())
|
||||||
|
})
|
||||||
|
|
||||||
|
// 判断当前是否正在和好友对话
|
||||||
|
if (!this.isTalk(this.talk_type, this.receiver_id, this.sender_id)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
useDialogueStore().updateDialogueRecord({
|
||||||
|
msg_id: this.msg_id,
|
||||||
|
is_revoke: 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Revoke
|
228
src/event/talk.js
Normal file
228
src/event/talk.js
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
import Base from './base'
|
||||||
|
import { nextTick } from 'vue'
|
||||||
|
import ws from '@/connect'
|
||||||
|
import { parseTime } from '@/utils/datetime'
|
||||||
|
import * as message from '@/constant/message'
|
||||||
|
import { formatTalkItem, palyMusic, formatTalkRecord } from '@/utils/talk'
|
||||||
|
// import { isElectronMode } from '@/utils/common'
|
||||||
|
import { ServeClearTalkUnreadNum, ServeCreateTalkList } from '@/api/chat/index.js'
|
||||||
|
import { useTalkStore, useDialogueStore } from '@/store'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 好友状态事件
|
||||||
|
*/
|
||||||
|
class Talk extends Base {
|
||||||
|
/**
|
||||||
|
* @var resource 资源
|
||||||
|
*/
|
||||||
|
resource
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送者ID
|
||||||
|
*/
|
||||||
|
sender_id = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接收者ID
|
||||||
|
*/
|
||||||
|
receiver_id = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 聊天类型[1:私聊;2:群聊;]
|
||||||
|
*/
|
||||||
|
talk_type = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化构造方法
|
||||||
|
*
|
||||||
|
* @param {Object} resource Socket消息
|
||||||
|
*/
|
||||||
|
constructor(resource) {
|
||||||
|
super()
|
||||||
|
|
||||||
|
this.sender_id = resource.sender_id
|
||||||
|
this.receiver_id = resource.receiver_id
|
||||||
|
this.talk_type = resource.talk_type
|
||||||
|
this.resource = resource.data
|
||||||
|
|
||||||
|
this.handle()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断消息发送者是否来自于我
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
isCurrSender() {
|
||||||
|
return this.sender_id == this.getAccountId()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取对话索引
|
||||||
|
*
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
getIndexName() {
|
||||||
|
if (this.talk_type == 2) {
|
||||||
|
return `${this.talk_type}_${this.receiver_id}`
|
||||||
|
}
|
||||||
|
|
||||||
|
let receiver_id = this.isCurrSender() ? this.receiver_id : this.sender_id
|
||||||
|
|
||||||
|
return `${this.talk_type}_${receiver_id}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取聊天列表左侧的对话信息
|
||||||
|
*/
|
||||||
|
getTalkText() {
|
||||||
|
let text = ''
|
||||||
|
if (this.resource.msg_type != message.ChatMsgTypeText) {
|
||||||
|
text = message.ChatMsgTypeMapping[this.resource.msg_type]
|
||||||
|
} else {
|
||||||
|
text = this.resource.extra.content.replace(/<img .*?>/g, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
// 播放提示音
|
||||||
|
play() {
|
||||||
|
// 客户端有消息提示
|
||||||
|
// if (isElectronMode()) return
|
||||||
|
|
||||||
|
// useSettingsStore().isPromptTone && palyMusic()
|
||||||
|
}
|
||||||
|
|
||||||
|
handle() {
|
||||||
|
// 不是自己发送的消息则需要播放提示音
|
||||||
|
if (!this.isCurrSender()) {
|
||||||
|
this.play()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断会话列表是否存在,不存在则创建
|
||||||
|
if (useTalkStore().findTalkIndex(this.getIndexName()) == -1) {
|
||||||
|
return this.addTalkItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断当前是否正在和好友对话
|
||||||
|
if (this.isTalk(this.talk_type, this.receiver_id, this.sender_id)) {
|
||||||
|
this.insertTalkRecord()
|
||||||
|
} else {
|
||||||
|
this.updateTalkItem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示消息提示
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
showMessageNocice() {
|
||||||
|
// if (useSettingsStore().isLeaveWeb) {
|
||||||
|
// const notification = new Notification('LumenIM 在线聊天', {
|
||||||
|
// dir: 'auto',
|
||||||
|
// lang: 'zh-CN',
|
||||||
|
// body: '您有新的消息请注意查收'
|
||||||
|
// })
|
||||||
|
|
||||||
|
// notification.onclick = () => {
|
||||||
|
// notification.close()
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// window['$notification'].create({
|
||||||
|
// title: '消息通知',
|
||||||
|
// content: '您有新的消息请注意查收',
|
||||||
|
// duration: 3000
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载对接节点
|
||||||
|
*/
|
||||||
|
addTalkItem() {
|
||||||
|
let receiver_id = this.sender_id
|
||||||
|
let talk_type = this.talk_type
|
||||||
|
|
||||||
|
if (talk_type == 1 && this.receiver_id != this.getAccountId()) {
|
||||||
|
receiver_id = this.receiver_id
|
||||||
|
} else if (talk_type == 2) {
|
||||||
|
receiver_id = this.receiver_id
|
||||||
|
}
|
||||||
|
|
||||||
|
ServeCreateTalkList({
|
||||||
|
talk_type,
|
||||||
|
receiver_id
|
||||||
|
}).then(({ code, data }) => {
|
||||||
|
if (code == 200) {
|
||||||
|
let item = formatTalkItem(data)
|
||||||
|
item.unread_num = 1
|
||||||
|
useTalkStore().addItem(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插入对话记录
|
||||||
|
*/
|
||||||
|
insertTalkRecord() {
|
||||||
|
let record = this.resource
|
||||||
|
|
||||||
|
// 群成员变化的消息,需要更新群成员列表
|
||||||
|
if ([1102, 1103, 1104].includes(record.msg_type)) {
|
||||||
|
useDialogueStore().updateGroupMembers()
|
||||||
|
}
|
||||||
|
|
||||||
|
useDialogueStore().addDialogueRecord(formatTalkRecord(this.getAccountId(), this.resource))
|
||||||
|
|
||||||
|
if (!this.isCurrSender()) {
|
||||||
|
// 推送已读消息
|
||||||
|
setTimeout(() => {
|
||||||
|
ws.emit('im.message.read', {
|
||||||
|
receiver_id: this.sender_id,
|
||||||
|
msg_ids: [this.resource.msg_id]
|
||||||
|
})
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取聊天面板元素节点
|
||||||
|
const el = document.getElementById('imChatPanel')
|
||||||
|
if (!el) return
|
||||||
|
|
||||||
|
// 判断的滚动条是否在底部
|
||||||
|
const isBottom = Math.ceil(el.scrollTop) + el.clientHeight >= el.scrollHeight
|
||||||
|
|
||||||
|
if (isBottom || record.user_id == this.getAccountId()) {
|
||||||
|
nextTick(() => {
|
||||||
|
el.scrollTop = el.scrollHeight + 1000
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
useDialogueStore().setUnreadBubble()
|
||||||
|
}
|
||||||
|
|
||||||
|
useTalkStore().updateItem({
|
||||||
|
index_name: this.getIndexName(),
|
||||||
|
msg_text: this.getTalkText(),
|
||||||
|
updated_at: parseTime(new Date())
|
||||||
|
})
|
||||||
|
|
||||||
|
if (this.talk_type == 1 && this.getAccountId() !== this.sender_id) {
|
||||||
|
ServeClearTalkUnreadNum({
|
||||||
|
talk_type: 1,
|
||||||
|
receiver_id: this.sender_id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新对话列表记录
|
||||||
|
*/
|
||||||
|
updateTalkItem() {
|
||||||
|
useTalkStore().updateMessage({
|
||||||
|
index_name: this.getIndexName(),
|
||||||
|
msg_text: this.getTalkText(),
|
||||||
|
updated_at: parseTime(new Date())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Talk
|
@ -1,163 +0,0 @@
|
|||||||
const cache = new Set()
|
|
||||||
|
|
||||||
const maxAttempts = 100
|
|
||||||
|
|
||||||
const defaultEvent = {
|
|
||||||
onError: (evt: any) => console.error('WebSocket Error:', evt),
|
|
||||||
onOpen: (evt: any) => console.log('WebSocket Opened:', evt),
|
|
||||||
onClose: (evt: any) => console.log('WebSocket Closed:', evt)
|
|
||||||
}
|
|
||||||
|
|
||||||
class WsSocket {
|
|
||||||
connect: WebSocket | null = null
|
|
||||||
|
|
||||||
config: any = {
|
|
||||||
heartbeat: {
|
|
||||||
setInterval: null,
|
|
||||||
pingInterval: 20000,
|
|
||||||
pingTimeout: 60000
|
|
||||||
},
|
|
||||||
reconnect: {
|
|
||||||
lockReconnect: false,
|
|
||||||
setTimeout: null,
|
|
||||||
interval: [2000, 2500, 3000, 3000, 5000, 8000], // Exponential backoff
|
|
||||||
attempts: maxAttempts
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lastTime: number = 0
|
|
||||||
|
|
||||||
onCallBacks: Record<string, Function> = {}
|
|
||||||
|
|
||||||
defaultEvent: Record<string, Function> = defaultEvent
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private urlCallBack: () => string,
|
|
||||||
private events: Partial<typeof defaultEvent>
|
|
||||||
) {
|
|
||||||
this.events = { ...this.defaultEvent, ...events }
|
|
||||||
}
|
|
||||||
|
|
||||||
on(event: string, callback: Function): this {
|
|
||||||
this.onCallBacks[event] = callback
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
loadSocket(): void {
|
|
||||||
this.connect = new WebSocket(this.urlCallBack())
|
|
||||||
this.connect.onerror = this.onError.bind(this)
|
|
||||||
this.connect.onopen = this.onOpen.bind(this)
|
|
||||||
this.connect.onmessage = this.onMessage.bind(this)
|
|
||||||
this.connect.onclose = this.onClose.bind(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
connection(): void {
|
|
||||||
this.connect === null && this.loadSocket()
|
|
||||||
}
|
|
||||||
|
|
||||||
reconnect(): void {
|
|
||||||
if (this.config.reconnect.lockReconnect || this.config.reconnect.attempts <= 0) return
|
|
||||||
|
|
||||||
this.config.reconnect.lockReconnect = true
|
|
||||||
this.config.reconnect.attempts--
|
|
||||||
|
|
||||||
const delay = this.config.reconnect.interval.shift()
|
|
||||||
|
|
||||||
this.config.reconnect.setTimeout = setTimeout(() => {
|
|
||||||
console.log(new Date().toLocaleString(), 'Attempting to reconnect to WebSocket...')
|
|
||||||
this.connection()
|
|
||||||
}, delay || 10000)
|
|
||||||
}
|
|
||||||
|
|
||||||
onParse(evt: MessageEvent): any {
|
|
||||||
return JSON.parse(evt.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
onOpen(evt: Event): void {
|
|
||||||
this.lastTime = Date.now()
|
|
||||||
|
|
||||||
this.events.onOpen?.(evt)
|
|
||||||
|
|
||||||
this.config.reconnect.interval = [1000, 1000, 3000, 5000, 10000]
|
|
||||||
this.config.reconnect.lockReconnect = false
|
|
||||||
this.config.reconnect.attempts = maxAttempts
|
|
||||||
|
|
||||||
this.heartbeat()
|
|
||||||
}
|
|
||||||
|
|
||||||
onClose(evt: CloseEvent): void {
|
|
||||||
this.events.onClose?.(evt)
|
|
||||||
this.connect = null
|
|
||||||
|
|
||||||
this.config.heartbeat.setInterval && clearInterval(this.config.heartbeat.setInterval)
|
|
||||||
|
|
||||||
this.config.reconnect.lockReconnect = false
|
|
||||||
|
|
||||||
if (evt.code !== 1000) {
|
|
||||||
this.reconnect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onError(evt: Event): void {
|
|
||||||
this.events.onError?.(evt)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMessage(evt: MessageEvent): void {
|
|
||||||
this.lastTime = Date.now()
|
|
||||||
|
|
||||||
const data = this.onParse(evt)
|
|
||||||
|
|
||||||
if (data.event === 'pong') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.ackid) {
|
|
||||||
this.connect?.send(`{"event":"ack","ackid":"${data.ackid}"}`)
|
|
||||||
|
|
||||||
if (cache.has(data.ackid)) return
|
|
||||||
|
|
||||||
cache.add(data.ackid)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.onCallBacks[data.event]) {
|
|
||||||
this.onCallBacks[data.event](data.payload, evt.data)
|
|
||||||
} else {
|
|
||||||
console.warn(`WsSocket message event [${data.event}] not bound...`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
heartbeat(): void {
|
|
||||||
this.config.heartbeat.setInterval && clearInterval(this.config.heartbeat.setInterval)
|
|
||||||
|
|
||||||
this.config.heartbeat.setInterval = setInterval(() => {
|
|
||||||
this.ping()
|
|
||||||
}, this.config.heartbeat.pingInterval)
|
|
||||||
}
|
|
||||||
|
|
||||||
ping(): void {
|
|
||||||
this.connect?.send(JSON.stringify({ event: 'ping' }))
|
|
||||||
}
|
|
||||||
|
|
||||||
send(message: any): void {
|
|
||||||
if (this.connect && this.connect.readyState === WebSocket.OPEN) {
|
|
||||||
this.connect.send(typeof message === 'string' ? message : JSON.stringify(message))
|
|
||||||
} else {
|
|
||||||
alert('WebSocket 连接已关闭')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
close(): void {
|
|
||||||
this.connect?.close()
|
|
||||||
this.config.heartbeat.setInterval && clearInterval(this.config.heartbeat.setInterval)
|
|
||||||
}
|
|
||||||
|
|
||||||
emit(event: string, payload: any): void {
|
|
||||||
if (this.connect && this.connect.readyState === WebSocket.OPEN) {
|
|
||||||
this.connect.send(JSON.stringify({ event, payload }))
|
|
||||||
} else {
|
|
||||||
console.error('WebSocket connection closed...', this.connect)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default WsSocket
|
|
262
src/plugins/ws-socket.js
Normal file
262
src/plugins/ws-socket.js
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
const cache = new Set()
|
||||||
|
|
||||||
|
class WsSocket {
|
||||||
|
/**
|
||||||
|
* Websocket 连接
|
||||||
|
*
|
||||||
|
* @var Websocket
|
||||||
|
*/
|
||||||
|
connect
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置信息
|
||||||
|
*
|
||||||
|
* @var Object
|
||||||
|
*/
|
||||||
|
config = {
|
||||||
|
heartbeat: {
|
||||||
|
setInterval: null,
|
||||||
|
pingInterval: 20000,
|
||||||
|
pingTimeout: 60000
|
||||||
|
},
|
||||||
|
reconnect: {
|
||||||
|
lockReconnect: false,
|
||||||
|
setTimeout: null, // 计时器对象
|
||||||
|
time: 3000, // 重连间隔时间
|
||||||
|
number: 10000000 // 重连次数
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最后心跳时间
|
||||||
|
lastTime = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义绑定消息事件
|
||||||
|
*
|
||||||
|
* @var Array
|
||||||
|
*/
|
||||||
|
onCallBacks = []
|
||||||
|
|
||||||
|
defaultEvent = {
|
||||||
|
onError: (evt) => {
|
||||||
|
console.log(evt)
|
||||||
|
},
|
||||||
|
onOpen: (evt) => {
|
||||||
|
console.log(evt)
|
||||||
|
},
|
||||||
|
onClose: (evt) => {
|
||||||
|
console.log(evt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 WsSocket 的实例
|
||||||
|
*
|
||||||
|
* @param {Function} urlCallBack url闭包函数
|
||||||
|
* @param {Object} events 原生 WebSocket 绑定事件
|
||||||
|
*/
|
||||||
|
constructor(urlCallBack, events) {
|
||||||
|
this.urlCallBack = urlCallBack
|
||||||
|
|
||||||
|
// 定义 WebSocket 原生方法
|
||||||
|
this.events = Object.assign({}, this.defaultEvent, events)
|
||||||
|
|
||||||
|
this.on('connect', (data) => {
|
||||||
|
this.config.heartbeat.pingInterval = data.ping_interval * 1000
|
||||||
|
this.config.heartbeat.pingTimeout = data.ping_timeout * 1000
|
||||||
|
this.heartbeat()
|
||||||
|
this.connect.send('{"event":"ping"}')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件绑定
|
||||||
|
*
|
||||||
|
* @param {String} event 事件名
|
||||||
|
* @param {Function} callBack 回调方法
|
||||||
|
*/
|
||||||
|
on(event, callBack) {
|
||||||
|
this.onCallBacks[event] = callBack
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载 WebSocket
|
||||||
|
*/
|
||||||
|
loadSocket() {
|
||||||
|
const url = this.urlCallBack()
|
||||||
|
|
||||||
|
const connect = new WebSocket(url)
|
||||||
|
connect.onerror = this.onError.bind(this)
|
||||||
|
connect.onopen = this.onOpen.bind(this)
|
||||||
|
connect.onmessage = this.onMessage.bind(this)
|
||||||
|
connect.onclose = this.onClose.bind(this)
|
||||||
|
|
||||||
|
this.connect = connect
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接 Websocket
|
||||||
|
*/
|
||||||
|
connection() {
|
||||||
|
this.connect == null && this.loadSocket()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 掉线重连 Websocket
|
||||||
|
*/
|
||||||
|
reconnect() {
|
||||||
|
// 没连接上会一直重连,设置延迟避免请求过多
|
||||||
|
clearTimeout(this.config.reconnect.setTimeout)
|
||||||
|
|
||||||
|
this.config.reconnect.setTimeout = setTimeout(() => {
|
||||||
|
this.connection()
|
||||||
|
|
||||||
|
console.log(`网络连接已断开,正在尝试重新连接...`)
|
||||||
|
}, this.config.reconnect.time)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析接受的消息
|
||||||
|
*
|
||||||
|
* @param {Object} evt Websocket 消息
|
||||||
|
*/
|
||||||
|
onParse(evt) {
|
||||||
|
const { sid, event, content } = JSON.parse(evt.data)
|
||||||
|
|
||||||
|
return {
|
||||||
|
sid: sid,
|
||||||
|
event: event,
|
||||||
|
data: content,
|
||||||
|
orginData: evt.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开连接
|
||||||
|
*
|
||||||
|
* @param {Object} evt Websocket 消息
|
||||||
|
*/
|
||||||
|
onOpen(evt) {
|
||||||
|
this.lastTime = new Date().getTime()
|
||||||
|
|
||||||
|
this.events.onOpen(evt)
|
||||||
|
|
||||||
|
this.ping()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭连接
|
||||||
|
*
|
||||||
|
* @param {Object} evt Websocket 消息
|
||||||
|
*/
|
||||||
|
onClose(evt) {
|
||||||
|
this.events.onClose(evt)
|
||||||
|
|
||||||
|
this.connect && this.connect.close()
|
||||||
|
|
||||||
|
this.connect = null
|
||||||
|
|
||||||
|
evt.code == 1006 && this.reconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接错误
|
||||||
|
*
|
||||||
|
* @param {Object} evt Websocket 消息
|
||||||
|
*/
|
||||||
|
onError(evt) {
|
||||||
|
this.events.onError(evt)
|
||||||
|
this.connect.close()
|
||||||
|
this.connect = null
|
||||||
|
this.reconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接收消息
|
||||||
|
*
|
||||||
|
* @param {Object} evt Websocket 消息
|
||||||
|
*/
|
||||||
|
onMessage(evt) {
|
||||||
|
this.lastTime = new Date().getTime()
|
||||||
|
|
||||||
|
let result = this.onParse(evt)
|
||||||
|
|
||||||
|
if (result.sid) {
|
||||||
|
if (cache.has(result.sid)) return
|
||||||
|
|
||||||
|
cache.add(result.sid)
|
||||||
|
|
||||||
|
this.connect.send(`{"event":"ack","sid":"${result.sid}"}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断消息事件是否被绑定
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.onCallBacks, result.event)) {
|
||||||
|
this.onCallBacks[result.event](result.data, result.orginData)
|
||||||
|
} else {
|
||||||
|
console.warn(`WsSocket 消息事件[${result.event}]未绑定...`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket 心跳检测
|
||||||
|
*/
|
||||||
|
heartbeat() {
|
||||||
|
this.config.heartbeat.setInterval = setInterval(() => {
|
||||||
|
let t = new Date().getTime()
|
||||||
|
|
||||||
|
if (t - this.lastTime > this.config.heartbeat.pingTimeout) {
|
||||||
|
if (this.connect) {
|
||||||
|
this.connect.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reconnect()
|
||||||
|
} else {
|
||||||
|
this.ping()
|
||||||
|
}
|
||||||
|
}, this.config.heartbeat.pingInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
ping() {
|
||||||
|
this.connect.send('{"event":"ping"}')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 聊天发送数据
|
||||||
|
*
|
||||||
|
* @param {Object} mesage
|
||||||
|
*/
|
||||||
|
send(mesage) {
|
||||||
|
if (typeof mesage == 'string') {
|
||||||
|
this.connect.send(mesage)
|
||||||
|
} else {
|
||||||
|
this.connect.send(JSON.stringify(mesage))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭连接
|
||||||
|
*/
|
||||||
|
close() {
|
||||||
|
this.connect.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推送消息
|
||||||
|
*
|
||||||
|
* @param {String} event 事件名
|
||||||
|
* @param {Object} data 数据
|
||||||
|
*/
|
||||||
|
emit(event, data) {
|
||||||
|
const content = JSON.stringify({ event, content: data })
|
||||||
|
|
||||||
|
if (this.connect && this.connect.readyState === 1) {
|
||||||
|
this.connect.send(content)
|
||||||
|
} else {
|
||||||
|
console.error('WebSocket 连接已关闭...', this.connect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WsSocket
|
8
src/store/index.js
Normal file
8
src/store/index.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export * from '@/store/modules/user'
|
||||||
|
// export * from '@/store/modules/settings'
|
||||||
|
export * from '@/store/modules/talk'
|
||||||
|
// export * from '@/store/modules/editor'
|
||||||
|
export * from '@/store/modules/dialogue'
|
||||||
|
// export * from '@/store/modules/editor-draft'
|
||||||
|
// export * from '@/store/modules/uploads'
|
||||||
|
// export * from '@/store/modules/note'
|
231
src/store/modules/dialogue.js
Normal file
231
src/store/modules/dialogue.js
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import {
|
||||||
|
ServeRemoveRecords,
|
||||||
|
ServeRevokeRecords,
|
||||||
|
ServePublishMessage,
|
||||||
|
ServeCollectEmoticon
|
||||||
|
} from '@/api/chat/index'
|
||||||
|
import { ServeGetGroupMembers } from '@/api/group/index'
|
||||||
|
import { useEditorStore } from './editor'
|
||||||
|
|
||||||
|
// 键盘消息事件定时器
|
||||||
|
// let keyboardTimeout = null
|
||||||
|
|
||||||
|
export const useDialogueStore = defineStore('dialogue', {
|
||||||
|
state: () => {
|
||||||
|
return {
|
||||||
|
// 对话索引(聊天对话的唯一索引)
|
||||||
|
index_name: '',
|
||||||
|
|
||||||
|
// 对话节点
|
||||||
|
talk: {
|
||||||
|
username: '',
|
||||||
|
talk_type: 0, // 对话来源[1:私聊;2:群聊]
|
||||||
|
receiver_id: 0
|
||||||
|
},
|
||||||
|
|
||||||
|
// 好友是否正在输入文字
|
||||||
|
keyboard: false,
|
||||||
|
|
||||||
|
// 对方是否在线
|
||||||
|
online: false,
|
||||||
|
|
||||||
|
// 聊天记录
|
||||||
|
records: [],
|
||||||
|
|
||||||
|
// 新消息提示
|
||||||
|
unreadBubble: 0,
|
||||||
|
|
||||||
|
// 是否开启多选操作模式
|
||||||
|
isOpenMultiSelect: false,
|
||||||
|
|
||||||
|
// 是否显示编辑器
|
||||||
|
isShowEditor: false,
|
||||||
|
|
||||||
|
// 是否显示会话列表
|
||||||
|
isShowSessionList: true,
|
||||||
|
|
||||||
|
// 群成员列表
|
||||||
|
members: [],
|
||||||
|
|
||||||
|
// 对话记录
|
||||||
|
items: {
|
||||||
|
'1_1': {
|
||||||
|
talk_type: 1, // 对话类型
|
||||||
|
receiver_id: 0, // 接收者ID
|
||||||
|
read_sequence: 0, // 当前已读的最后一条记录
|
||||||
|
records: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
// 多选列表
|
||||||
|
selectItems: (state) => state.records.filter((item) => item.isCheck),
|
||||||
|
// 当前对话是否是群聊
|
||||||
|
isGroupTalk: (state) => state.talk.talk_type === 2
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
// 更新在线状态
|
||||||
|
setOnlineStatus(status) {
|
||||||
|
this.online = status
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新对话信息
|
||||||
|
setDialogue(data = {}) {
|
||||||
|
this.online = data.is_online == 1
|
||||||
|
this.talk = {
|
||||||
|
username: data.remark || data.name,
|
||||||
|
talk_type: data.talk_type,
|
||||||
|
receiver_id: data.receiver_id
|
||||||
|
}
|
||||||
|
|
||||||
|
this.index_name = `${data.talk_type}_${data.receiver_id}`
|
||||||
|
this.records = []
|
||||||
|
this.unreadBubble = 0
|
||||||
|
this.isShowEditor = data?.is_robot === 0
|
||||||
|
|
||||||
|
this.members = []
|
||||||
|
if (data.talk_type == 2) {
|
||||||
|
this.updateGroupMembers()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新提及列表
|
||||||
|
async updateGroupMembers() {
|
||||||
|
let { code, data } = await ServeGetGroupMembers({
|
||||||
|
group_id: this.talk.receiver_id
|
||||||
|
})
|
||||||
|
|
||||||
|
if (code != 200) return
|
||||||
|
|
||||||
|
this.members = data.items.map((o) => ({
|
||||||
|
id: o.user_id,
|
||||||
|
nickname: o.nickname,
|
||||||
|
avatar: o.avatar,
|
||||||
|
gender: o.gender,
|
||||||
|
leader: o.leader,
|
||||||
|
remark: o.remark,
|
||||||
|
online: false,
|
||||||
|
value: o.nickname
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
|
||||||
|
// 清空对话记录
|
||||||
|
clearDialogueRecord() {
|
||||||
|
this.records = []
|
||||||
|
},
|
||||||
|
|
||||||
|
// 数组头部压入对话记录
|
||||||
|
unshiftDialogueRecord(records) {
|
||||||
|
this.records.unshift(...records)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 推送对话记录
|
||||||
|
addDialogueRecord(record) {
|
||||||
|
// TOOD 需要通过 sequence 排序,保证消息一致性
|
||||||
|
// this.records.splice(index, 0, record)
|
||||||
|
|
||||||
|
this.records.push(record)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新对话记录
|
||||||
|
updateDialogueRecord(params) {
|
||||||
|
const { msg_id = '' } = params
|
||||||
|
|
||||||
|
const item = this.records.find((item) => item.msg_id === msg_id)
|
||||||
|
|
||||||
|
item && Object.assign(item, params)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 批量删除对话记录
|
||||||
|
batchDelDialogueRecord(msgIds = []) {
|
||||||
|
msgIds.forEach((msgid) => {
|
||||||
|
const index = this.records.findIndex((item) => item.msg_id === msgid)
|
||||||
|
|
||||||
|
if (index >= 0) this.records.splice(index, 1)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 自增好友键盘输入事件
|
||||||
|
// triggerKeyboard() {
|
||||||
|
// this.keyboard = true
|
||||||
|
|
||||||
|
// clearTimeout(keyboardTimeout)
|
||||||
|
|
||||||
|
// keyboardTimeout = setTimeout(() => (this.keyboard = false), 2000)
|
||||||
|
// },
|
||||||
|
|
||||||
|
setUnreadBubble(value) {
|
||||||
|
if (value === 0) {
|
||||||
|
this.unreadBubble = 0
|
||||||
|
} else {
|
||||||
|
this.unreadBubble++
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 关闭多选模式
|
||||||
|
closeMultiSelect() {
|
||||||
|
this.isOpenMultiSelect = false
|
||||||
|
|
||||||
|
for (const item of this.selectItems) {
|
||||||
|
if (item.isCheck) {
|
||||||
|
item.isCheck = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 删除聊天记录
|
||||||
|
ApiDeleteRecord(msgIds = []) {
|
||||||
|
ServeRemoveRecords({
|
||||||
|
talk_type: this.talk.talk_type,
|
||||||
|
receiver_id: this.talk.receiver_id,
|
||||||
|
msg_ids: msgIds
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.code == 200) {
|
||||||
|
this.batchDelDialogueRecord(msgIds)
|
||||||
|
} else {
|
||||||
|
window['$message'].warning(res.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 撤销聊天记录
|
||||||
|
ApiRevokeRecord(msg_id = '') {
|
||||||
|
ServeRevokeRecords({ msg_id }).then((res) => {
|
||||||
|
if (res.code == 200) {
|
||||||
|
this.updateDialogueRecord({ msg_id, is_revoke: 1 })
|
||||||
|
} else {
|
||||||
|
window['$message'].warning(res.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 转发聊天记录
|
||||||
|
ApiForwardRecord(options) {
|
||||||
|
let data = {
|
||||||
|
type: 'forward',
|
||||||
|
receiver: {
|
||||||
|
talk_type: this.talk.talk_type,
|
||||||
|
receiver_id: this.talk.receiver_id
|
||||||
|
},
|
||||||
|
...options
|
||||||
|
}
|
||||||
|
|
||||||
|
ServePublishMessage(data).then((res) => {
|
||||||
|
if (res.code == 200) {
|
||||||
|
this.closeMultiSelect()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
ApiCollectImage(options) {
|
||||||
|
const { msg_id } = options
|
||||||
|
|
||||||
|
ServeCollectEmoticon({ msg_id }).then(() => {
|
||||||
|
useEditorStore().loadUserEmoticon()
|
||||||
|
window['$message'] && window['$message'].success('收藏成功')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
13
src/store/modules/editor-draft.js
Normal file
13
src/store/modules/editor-draft.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
// 编辑器草稿
|
||||||
|
export const useEditorDraftStore = defineStore('editor-draft', {
|
||||||
|
// 开启数据持久化
|
||||||
|
persist: true,
|
||||||
|
state: () => {
|
||||||
|
return {
|
||||||
|
items: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {}
|
||||||
|
})
|
80
src/store/modules/editor.js
Normal file
80
src/store/modules/editor.js
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ServeFindUserEmoticon, ServeUploadEmoticon, ServeDeleteEmoticon } from '@/api/emoticon/index'
|
||||||
|
import { ServeCollectEmoticon } from '@/api/chat/index'
|
||||||
|
|
||||||
|
const message = window['$message']
|
||||||
|
|
||||||
|
export const useEditorStore = defineStore('editor', {
|
||||||
|
state: () => {
|
||||||
|
return {
|
||||||
|
// 表包相关
|
||||||
|
emoticon: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: '系统表情',
|
||||||
|
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAMAAAC7IEhfAAAAzFBMVEUAAAD7ywP7ywT80wH6wwb6xgX80wH6wwb7zwL7zgP5xAb7ywT5xAb5wgf7ywT80gH80gL7ywP80gH7ywT7zgP6yAX80wH5wQf80QL80wH6xgb81AH5wgf6xwX81AH5wQf81AH5wwb80gL5wQf7zgP5wgf80gH7zAP5wwb6ygT6xwX70AL7zgOudAD6xQb2wgXVnwLwvwPRmgLDigG1ewCxdwDgqQTcpgPYoQPrvALhsAG6gQHosgTntAP1yAPMlQLJkgLJkQK/hgG3fgAADXCMAAAAJnRSTlMATgT7znJxcHAM/fnz7uzs09HMqS4mJfry8u3t1tXVqqpcWy0tD14bIcIAAAGzSURBVDjLnZTXcsIwEEUdcAFCCy290GwgLHKld/7/n6IVljBGDgznxbtXZzSz9niVO+noP+X3dPq9/KN3kq2s/mVG+NKzUi2VKZkxSpnUpdeomBIqjbj38GpKeX0491ppM4F06+w+9JLMyJ2NF/MfXhpiXjGHnAqfPfN7hUz4nkvXxFKWiboI5u7sdDpz56LWmVgehswBgjVv1gHAnDdl9GpDjg0AM97MaGOLoxqOIjrHB8/ijeWB74gjHKdqCfqOFcHpn+oqFZ+sG3iiYvEWsUjFx1vERxT7N4Bi8VgOpAKPizgMFuQQrGTeKjgQfOIwVSyWAP5Acp8PwC7A16MNKCQAsAcX2AABwULDT8iiKQBM4t6EhlNW1RTKJys9Gm5IVCMbGnms/FQQ7RjvaezbDtcc26fBnrBGY2L2bYSQLSCLnTudursFIFvCjt7ClWGMjowXcMZiHB4Y/OdSw6A39kDgjXthrIrFUs/3OGQ5sV3XniyJiPL1yALI9RLJnS2VZi7Ra8aWVF7u5fl9groq89S6ZJEahbhWMFKKjK72EdU+tK6SSNv4VgvPzwX122gr9/EHUym1IM88uoYAAAAASUVORK5CYII='
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '我的收藏',
|
||||||
|
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAAsSAAALEgHS3X78AAAC1klEQVRYw+2XX0hTcRTHP3eOCgOtNCOI/lBYvQSNXtL8t0yRgnI3yjCil7AQkggignwoiN7ypYcopB5cFtypkYRmm38yX6Sgl6YQRQSRpqWUhOhOD9vcmnO781p3D/vCYez3O7/z+9zf+e3uHEgpJXOlLDQhqmoB7MBhIA/YDKQDI8A7wAM0KZr2OdYGoqobgGqgBNgJ5ABTwEdgAGgF3Iqm+XQDiqqqwA0gN84DzgLNwBVF0z5FxNgYiFEFpMWJMwxcUjStLSagz+FYDtwDTiaYiZ9AncXlagzEqQLuABkJxmkEzllcrul5gD6Hwwo8ASoSDBquW4APuGggRhtw1OJyzQBYg6MiUm8QDuCCwfXgv/P1AfOf4Gzlka2ANxzYZM0AO9JaWt9bAEQ4L4JVBJLErCLUETwxETlo9pFFUcUcIMgWs2miaNMcoIhYzKaJoulwwAkg02yiCE2FAJHRJAQcDwGKeIFtZhNFaAjAf/dE+s1+r0SxgTlAEWkXEZLM2iHsv/hXRflrYLfZeQ3ozcpnHbZQigGBBvF/JoM1BLlCgCIPRcSbBKn1ikhzkOuvenCy/EAZ0GFyesszOp53RgUEmCgrdQInTIJzZnZ2VYcPzC+vRGoAG7D9P8MNAWcjB6P2JD9K7bvwNzTp/wluCti7qsv9VhcgwPfSkkOABiz7x3DTgLq6y/M02qQSa+X4/uJq4AHxu7LFygecWvOiu2khByVehDF70Rn8HVpc3wQlQE2Wu+duLCddm47ZC48D94EVSwT3Gzid5e59FM9R96l8KynYAzwGjFbfH4Bj2Z6+QT3OuivpbE/foAg2EZwGihSnCDa9cAmdYLhGi/dVAreB9TqXfAFq13a/bEl0r0Vf/JGi/AzgGlDLwv30TOBB6nN6+icXs4/hX+ZIUX4ucBOojJhqAS7n9PQPG4m/ZK+Or4V5BcD1wNer63pf9S1V7JRSMlN/AO6lbgA7RvQLAAAAAElFTkSuQmCC',
|
||||||
|
children: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
// 加载用户表情包
|
||||||
|
loadUserEmoticon() {
|
||||||
|
ServeFindUserEmoticon().then((res) => {
|
||||||
|
if (res.code == 200) {
|
||||||
|
const { collect_emoticon } = res.data
|
||||||
|
|
||||||
|
// 用户收藏的系统表情包
|
||||||
|
this.emoticon.items[1].children = collect_emoticon || []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 收藏用户表情包
|
||||||
|
saveUserEmoticon(resoure) {
|
||||||
|
ServeCollectEmoticon({
|
||||||
|
record_id: resoure.record_id
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.code == 200) {
|
||||||
|
this.loadUserEmoticon()
|
||||||
|
} else {
|
||||||
|
message.warning(res.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 自定义上传用户表情包
|
||||||
|
uploadUserEmoticon(file) {
|
||||||
|
const data = new FormData()
|
||||||
|
data.append('emoticon', file)
|
||||||
|
|
||||||
|
ServeUploadEmoticon(data).then((res) => {
|
||||||
|
if (res.code == 200) {
|
||||||
|
this.emoticon.items[1].children.unshift(res.data)
|
||||||
|
} else {
|
||||||
|
message.warning(res.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 自定义上传用户表情包
|
||||||
|
removeUserEmoticon(resoure) {
|
||||||
|
ServeDeleteEmoticon({
|
||||||
|
ids: [resoure.id].join(',')
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.code == 200) {
|
||||||
|
this.emoticon.items[1].children.splice(resoure.index, 1)
|
||||||
|
message.success('删除成功')
|
||||||
|
} else {
|
||||||
|
message.warning(res.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
151
src/store/modules/talk.js
Normal file
151
src/store/modules/talk.js
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ServeGetTalkList, ServeCreateTalkList } from '@/api/chat/index'
|
||||||
|
import { formatTalkItem, ttime, KEY_INDEX_NAME } from '@/utils/talk'
|
||||||
|
import { useEditorDraftStore } from './editor-draft'
|
||||||
|
// import { ISession } from '@/types/chat'
|
||||||
|
|
||||||
|
export const useTalkStore = defineStore('talk', {
|
||||||
|
state: () => {
|
||||||
|
return {
|
||||||
|
// 加载状态[1:未加载;2:加载中;3:加载完成;4:加载失败;]
|
||||||
|
loadStatus: 2,
|
||||||
|
|
||||||
|
// 会话列表
|
||||||
|
items: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
// 过滤所有置顶对话列表
|
||||||
|
topItems: (state) => {
|
||||||
|
return state.items.filter((item) => item.is_top == 1)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 对话列表
|
||||||
|
talkItems: (state) => {
|
||||||
|
return state.items.sort((a, b) => {
|
||||||
|
return ttime(b.updated_at) - ttime(a.updated_at)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 消息未读数总计
|
||||||
|
talkUnreadNum: (state) => {
|
||||||
|
return state.items.reduce((total, item) => {
|
||||||
|
return total + item.unread_num
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
findItem(index_name) {
|
||||||
|
return this.items.find((item) => item.index_name === index_name)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新对话节点
|
||||||
|
updateItem(params) {
|
||||||
|
const item = this.items.find((item) => item.index_name === params.index_name)
|
||||||
|
|
||||||
|
item && Object.assign(item, params)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增对话节点
|
||||||
|
addItem(params) {
|
||||||
|
this.items = [params, ...this.items]
|
||||||
|
},
|
||||||
|
|
||||||
|
// 移除对话节点
|
||||||
|
delItem(index_name) {
|
||||||
|
const i = this.items.findIndex((item) => item.index_name === index_name)
|
||||||
|
|
||||||
|
if (i >= 0) {
|
||||||
|
this.items.splice(i, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.items = [...this.items]
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新对话消息
|
||||||
|
updateMessage(params) {
|
||||||
|
const item = this.items.find((item) => item.index_name === params.index_name)
|
||||||
|
|
||||||
|
if (item) {
|
||||||
|
item.unread_num++
|
||||||
|
// item.msg_text = params.msg_text
|
||||||
|
item.updated_at = params.updated_at
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新联系人备注
|
||||||
|
setRemark(params) {
|
||||||
|
const item = this.items.find((item) => item.index_name === `1_${params.user_id}`)
|
||||||
|
|
||||||
|
item && (item.remark = params.remark)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 加载会话列表
|
||||||
|
loadTalkList() {
|
||||||
|
this.loadStatus = 2
|
||||||
|
|
||||||
|
const resp = ServeGetTalkList()
|
||||||
|
|
||||||
|
resp.then(({ code, data }) => {
|
||||||
|
if (code == 200) {
|
||||||
|
this.items = data.items.map((item) => {
|
||||||
|
const value = formatTalkItem(item)
|
||||||
|
|
||||||
|
const draft = useEditorDraftStore().items[value.index_name]
|
||||||
|
if (draft) {
|
||||||
|
value.draft_text = JSON.parse(draft).text || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.is_robot == 1) {
|
||||||
|
value.is_online = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
})
|
||||||
|
|
||||||
|
this.loadStatus = 3
|
||||||
|
} else {
|
||||||
|
this.loadStatus = 4
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
resp.catch(() => {
|
||||||
|
this.loadStatus = 4
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
findTalkIndex(index_name) {
|
||||||
|
return this.items.findIndex((item) => item.index_name === index_name)
|
||||||
|
},
|
||||||
|
|
||||||
|
toTalk(talk_type, receiver_id, router) {
|
||||||
|
const route = {
|
||||||
|
path: '/message',
|
||||||
|
query: {
|
||||||
|
v: new Date().getTime()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.findTalkIndex(`${talk_type}_${receiver_id}`) >= 0) {
|
||||||
|
sessionStorage.setItem(KEY_INDEX_NAME, `${talk_type}_${receiver_id}`)
|
||||||
|
return router.push(route)
|
||||||
|
}
|
||||||
|
|
||||||
|
ServeCreateTalkList({
|
||||||
|
talk_type,
|
||||||
|
receiver_id
|
||||||
|
}).then(({ code, data, message }) => {
|
||||||
|
if (code == 200) {
|
||||||
|
if (this.findTalkIndex(`${talk_type}_${receiver_id}`) === -1) {
|
||||||
|
this.addItem(formatTalkItem(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionStorage.setItem(KEY_INDEX_NAME, `${talk_type}_${receiver_id}`)
|
||||||
|
return router.push(route)
|
||||||
|
} else {
|
||||||
|
window['$message'].info(message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
70
src/store/modules/user.js
Normal file
70
src/store/modules/user.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
// import { ServeGetUserSetting } from '@/api/user'
|
||||||
|
// import { ServeFindFriendApplyNum } from '@/api/contact'
|
||||||
|
import { ServeGroupApplyUnread } from '@/api/group'
|
||||||
|
// import { delAccessToken } from '@/utils/auth'
|
||||||
|
// import { storage } from '@/utils/storage'
|
||||||
|
|
||||||
|
export const useUserStore = defineStore('user', {
|
||||||
|
persist: true,
|
||||||
|
state: () => {
|
||||||
|
return {
|
||||||
|
uid: 0, // 用户ID
|
||||||
|
mobile: '',
|
||||||
|
email: '',
|
||||||
|
nickname: '', // 用户昵称
|
||||||
|
gender: 0, // 性别
|
||||||
|
motto: '', // 个性签名
|
||||||
|
avatar: '',
|
||||||
|
banner: '', // 名片背景
|
||||||
|
online: false, // 在线状态
|
||||||
|
isQiye: false,
|
||||||
|
isContactApply: false,
|
||||||
|
isGroupApply: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getters: {},
|
||||||
|
actions: {
|
||||||
|
// 设置用户登录状态
|
||||||
|
updateSocketStatus(status) {
|
||||||
|
this.online = status
|
||||||
|
},
|
||||||
|
|
||||||
|
// logoutLogin() {
|
||||||
|
// this.$reset()
|
||||||
|
// storage.remove('user_info')
|
||||||
|
// delAccessToken()
|
||||||
|
// location.reload()
|
||||||
|
// },
|
||||||
|
|
||||||
|
loadSetting() {
|
||||||
|
// ServeGetUserSetting().then(({ code, data }) => {
|
||||||
|
// if (code == 200) {
|
||||||
|
// this.nickname = data.user_info.nickname
|
||||||
|
// this.uid = data.user_info.uid
|
||||||
|
// this.avatar = data.user_info.avatar
|
||||||
|
|
||||||
|
// this.gender = data.user_info.gender
|
||||||
|
// this.mobile = data.user_info.mobile || ''
|
||||||
|
// this.email = data.user_info.email || ''
|
||||||
|
// this.motto = data.user_info.motto
|
||||||
|
// this.isQiye = data.user_info.is_qiye || false
|
||||||
|
|
||||||
|
// storage.set('user_info', data)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
// ServeFindFriendApplyNum().then(({ code, data }) => {
|
||||||
|
// if (code == 200) {
|
||||||
|
// this.isContactApply = data.unread_num > 0
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
ServeGroupApplyUnread().then(({ code, data }) => {
|
||||||
|
if (code == 200) {
|
||||||
|
this.isGroupApply = data.unread_num > 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
158
src/utils/datetime.js
Normal file
158
src/utils/datetime.js
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
/**
|
||||||
|
* 人性化时间显示
|
||||||
|
*
|
||||||
|
* @param {Object} datetime
|
||||||
|
*/
|
||||||
|
export function formatTime(datetime) {
|
||||||
|
if (datetime == null) return ''
|
||||||
|
|
||||||
|
datetime = datetime.replace(/-/g, '/')
|
||||||
|
|
||||||
|
let time = new Date()
|
||||||
|
let outTime = new Date(datetime)
|
||||||
|
if (/^[1-9]\d*$/.test(datetime)) {
|
||||||
|
outTime = new Date(parseInt(datetime) * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time.getTime() < outTime.getTime() || time.getFullYear() != outTime.getFullYear()) {
|
||||||
|
return parseTime(outTime, '{y}/{m}/{d} {h}:{i}')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time.getMonth() != outTime.getMonth()) {
|
||||||
|
return parseTime(outTime, '{m}/{d} {h}:{i}')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time.getDate() != outTime.getDate()) {
|
||||||
|
let day = outTime.getDate() - time.getDate()
|
||||||
|
if (day == -1) {
|
||||||
|
return parseTime(outTime, '昨天 {h}:{i}')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (day == -2) {
|
||||||
|
return parseTime(outTime, '前天 {h}:{i}')
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseTime(outTime, '{m}-{d} {h}:{i}')
|
||||||
|
}
|
||||||
|
|
||||||
|
let diff = time.getTime() - outTime.getTime()
|
||||||
|
|
||||||
|
if (time.getHours() != outTime.getHours() || diff > 30 * 60 * 1000) {
|
||||||
|
return parseTime(outTime, '{h}:{i}')
|
||||||
|
}
|
||||||
|
|
||||||
|
let minutes = outTime.getMinutes() - time.getMinutes()
|
||||||
|
if (minutes == 0) {
|
||||||
|
return '刚刚'
|
||||||
|
}
|
||||||
|
|
||||||
|
minutes = Math.abs(minutes)
|
||||||
|
return `${minutes}分钟前`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时间格式化方法
|
||||||
|
*
|
||||||
|
* @param {(Object|string|number)} time
|
||||||
|
* @param {String} cFormat
|
||||||
|
* @returns {String | null}
|
||||||
|
*/
|
||||||
|
export function parseTime(time, cFormat) {
|
||||||
|
if (arguments.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
let date
|
||||||
|
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
|
||||||
|
|
||||||
|
if (typeof time === 'object') {
|
||||||
|
date = time
|
||||||
|
} else {
|
||||||
|
if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
|
||||||
|
time = parseInt(time)
|
||||||
|
}
|
||||||
|
if (typeof time === 'number' && time.toString().length === 10) {
|
||||||
|
time = time * 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
date = new Date(time.replace(/-/g, '/'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatObj = {
|
||||||
|
y: date.getFullYear(),
|
||||||
|
m: date.getMonth() + 1,
|
||||||
|
d: date.getDate(),
|
||||||
|
h: date.getHours(),
|
||||||
|
i: date.getMinutes(),
|
||||||
|
s: date.getSeconds(),
|
||||||
|
a: date.getDay()
|
||||||
|
}
|
||||||
|
|
||||||
|
const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
|
||||||
|
const value = formatObj[key]
|
||||||
|
// Note: getDay() returns 0 on Sunday
|
||||||
|
if (key === 'a') {
|
||||||
|
return ['日', '一', '二', '三', '四', '五', '六'][value]
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.toString().padStart(2, '0')
|
||||||
|
})
|
||||||
|
|
||||||
|
return time_str
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 人性化显示时间
|
||||||
|
*
|
||||||
|
* @param {Object} datetime
|
||||||
|
*/
|
||||||
|
export function beautifyTime(datetime = '') {
|
||||||
|
if (datetime == null) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
datetime = datetime.replace(/-/g, '/')
|
||||||
|
|
||||||
|
let time = new Date()
|
||||||
|
let outTime = new Date(datetime)
|
||||||
|
if (/^[1-9]\d*$/.test(datetime)) {
|
||||||
|
outTime = new Date(parseInt(datetime) * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time.getTime() < outTime.getTime()) {
|
||||||
|
return parseTime(outTime, '{y}/{m}/{d}')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time.getFullYear() != outTime.getFullYear()) {
|
||||||
|
return parseTime(outTime, '{y}/{m}/{d}')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time.getMonth() != outTime.getMonth()) {
|
||||||
|
return parseTime(outTime, '{m}/{d}')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time.getDate() != outTime.getDate()) {
|
||||||
|
let day = outTime.getDate() - time.getDate()
|
||||||
|
if (day == -1) {
|
||||||
|
return parseTime(outTime, '昨天 {h}:{i}')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (day == -2) {
|
||||||
|
return parseTime(outTime, '前天 {h}:{i}')
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseTime(outTime, '{m}-{d}')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time.getHours() != outTime.getHours()) {
|
||||||
|
return parseTime(outTime, '{h}:{i}')
|
||||||
|
}
|
||||||
|
|
||||||
|
let minutes = outTime.getMinutes() - time.getMinutes()
|
||||||
|
if (minutes == 0) {
|
||||||
|
return '刚刚'
|
||||||
|
}
|
||||||
|
|
||||||
|
minutes = Math.abs(minutes)
|
||||||
|
return `${minutes}分钟前`
|
||||||
|
}
|
86
src/utils/talk.js
Normal file
86
src/utils/talk.js
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import { parseTime } from './datetime'
|
||||||
|
|
||||||
|
export const KEY_INDEX_NAME = 'send_message_index_name'
|
||||||
|
|
||||||
|
export function formatTalkRecord(uid, data) {
|
||||||
|
data.float = 'center'
|
||||||
|
|
||||||
|
if (data.user_id > 0) {
|
||||||
|
data.float = data.user_id == uid ? 'right' : 'left'
|
||||||
|
}
|
||||||
|
|
||||||
|
data.isCheck = false
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// 播放消息提示
|
||||||
|
export function palyMusic(muted = false) {
|
||||||
|
let audio = document.getElementById('audio')
|
||||||
|
audio.currentTime = 0
|
||||||
|
audio.muted = muted
|
||||||
|
audio.play()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化聊天对话列表数据
|
||||||
|
*
|
||||||
|
* @param {Object} params
|
||||||
|
*/
|
||||||
|
export function formatTalkItem(params) {
|
||||||
|
let options = {
|
||||||
|
id: 0,
|
||||||
|
talk_type: 1,
|
||||||
|
receiver_id: 0,
|
||||||
|
name: '未设置',
|
||||||
|
remark: '',
|
||||||
|
avatar: '',
|
||||||
|
is_disturb: 0,
|
||||||
|
is_top: 0,
|
||||||
|
is_online: 0,
|
||||||
|
is_robot: 0,
|
||||||
|
unread_num: 0,
|
||||||
|
content: '......',
|
||||||
|
draft_text: '',
|
||||||
|
msg_text: '',
|
||||||
|
index_name: '',
|
||||||
|
updated_at: parseTime(new Date())
|
||||||
|
}
|
||||||
|
|
||||||
|
options = { ...options, ...params }
|
||||||
|
options.index_name = `${options.talk_type}_${options.receiver_id}`
|
||||||
|
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取需要打开的对话索引值
|
||||||
|
*
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getCacheIndexName() {
|
||||||
|
let index_name = sessionStorage.getItem(KEY_INDEX_NAME)
|
||||||
|
|
||||||
|
if (index_name) {
|
||||||
|
sessionStorage.removeItem(KEY_INDEX_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
return index_name
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取需要打开的对话索引值
|
||||||
|
*
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function setCacheIndexName(type, id) {
|
||||||
|
sessionStorage.setItem(KEY_INDEX_NAME, `${type}_${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ttime = (datetime) => {
|
||||||
|
if (datetime == undefined || datetime == '') {
|
||||||
|
return new Date().getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Date(datetime.replace(/-/g, '/')).getTime()
|
||||||
|
}
|
@ -23,7 +23,7 @@
|
|||||||
"./src/*.ts",
|
"./src/*.ts",
|
||||||
"./src/*.d.ts",
|
"./src/*.d.ts",
|
||||||
"./src/**/*.ts"
|
"./src/**/*.ts"
|
||||||
],
|
, "src/connect.js", "src/store/index.js", "src/store/modules/settings.js", "src/store/modules/user.js", "src/store/modules/uploads.js", "src/store/modules/talk.js", "src/event/talk.js", "src/plugins/ws-socket.js" ],
|
||||||
"vueCompilerOptions": {
|
"vueCompilerOptions": {
|
||||||
"experimentalRuntimeMode": "runtime-uni-app",
|
"experimentalRuntimeMode": "runtime-uni-app",
|
||||||
"nativeTags": ["block", "component", "template", "slot"]
|
"nativeTags": ["block", "component", "template", "slot"]
|
||||||
|
Loading…
Reference in New Issue
Block a user