<route lang="json5" type="page">
{
layout: 'default',
style: {
navigationBarTitleText: '聊天',
navigationStyle: 'custom',
disableScroll: true, // 微信禁止页面滚动
'app-plus': {
bounce: 'none', // 禁用 iOS 弹性效果
},
},
}
</route>
<template>
<PageLayout
:navTitle="navTitle"
:backRouteName="backRouteName"
:routeMethod="routeMethod"
@navBack="handleNavBack"
navRightTextMp="设置"
@navRightMp="handleGoChatSetting"
>
<view class="wrap">
<!-- prettier-ignore -->
<z-paging ref="paging" v-model="dataList" :fixed="false" use-chat-record-mode use-virtual-list cell-height-mode="dynamic" safe-area-inset-bottom bottom-bg-color="#e5e5e5" @query="queryList" @keyboardHeightChange="keyboardHeightChange" @hidedKeyboard="hidedKeyboard">
<template #cell="{item,index}">
<view style="transform: scaleY(-1)">
<chat-item :item="item" :playMsgid="playMsgid" @playVoice="handlePlayVoice"></chat-item>
</view>
</template>
<template #bottom>
<chat-input-bar ref="inputBar" @send="doSend" />
</template>
</z-paging>
</view>
</PageLayout>
</template>
<script lang="ts" setup>
//
import { onLaunch, onShow, onHide, onLoad, onReady } from '@dcloudio/uni-app'
import { nextTick, onMounted, ref } from 'vue'
import { useUserStore } from '@/store/user'
import { http } from '@/utils/http'
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
import { useRouter } from '@/plugin/uni-mini-router'
import { cache, getFileAccessHttpUrl, hasRoute, formatDate } from '@/common/uitls'
import { TENANT_LIST } from '@/common/constants'
import socket from '@/common/socket'
import { textReplaceEmoji, getEmojiImageUrl } from './emojis'
import chatInputBar from './components/chat-input-bar.vue'
import chatItem from './components/chat-item.vue'
import { getEnvBaseUrl } from '@/utils/index'
import { useParamsStore } from '@/store/page-params'
defineOptions({
name: 'chat',
options: {
// apply-shared:当前页面样式会影响到子组件样式.(小程序)
// shared:当前页面样式影响到子组件,子组件样式也会影响到当前页面.(小程序)
styleIsolation: 'apply-shared',
},
})
const api = {
chatlog_old: '/eoa/im/api/queryChatLogList',
chatlog: '/eoa/im/newApi/records',
sendMsg: '/eoa/im/newApi/sendMessage',
sendFile: '/eoa/im/newApi/sendFile',
creatFriendSession: '/eoa/im/newApi/creatFriendSession',
uploadUrl: `${getEnvBaseUrl()}/eoa/im/newApi/sendImage`,
}
const toast = useToast()
const userStore = useUserStore()
const paging = ref(null)
// 信息
const chatObj = ref(null)
// 对方userid 或者 群id 或者 讨论组id
const chatto = ref()
const navTitle = ref('')
const myuid = ref(userStore.userInfo.userid)
const msgList = ref([])
// const pageNo = ref(1)
// const pageSize = ref(10)
const loadingShow = ref(false)
const hasRecord = ref(false)
const dataList = ref([])
const inputBar = ref(null)
const AUDIO = uni.createInnerAudioContext()
const playMsgid = ref('')
let stopWatch: any = null
const paramsStore = useParamsStore()
const backRouteName = ref('message')
const routeMethod = ref('pushTab')
const router = useRouter()
// 是否首次加载
let isFirstLoad = true
let chatItemData = {}
// 页面初始化
const init = () => {
const localData = paramsStore.getPageParams('chat')
const params = localData.data
if (!params) {
return
}
chatObj.value = { ...params }
// 对方头像、群头像、讨论组头像
navTitle.value = params.fromUserName
// 对方userid 或者 群id 或者 讨论组id
chatto.value = chatObj.value.msgTo
chatItemData = params
if (params.type == 'friend') {
// 创建会话数据
creatFriendSession(chatObj.value.msgTo)
}
// 启动webSocket
onSocketOpen()
// 接收消息
onSocketReceive()
// 加载初始页面消息
getMsgList()
if (localData.back) {
backRouteName.value = localData.back
routeMethod.value = localData.routeMethod
}
}
// 创建会话数据
const creatFriendSession = (userId) => {
http.post(api.creatFriendSession, {
type: 'friend',
userId,
})
}
const onSocketOpen = () => {
// console.log('启动webSocket')
// socket.init('eoaNewChatSocket')
}
const onSocketReceive = () => {
var _this = this
socket.acceptMessage = function (res) {
console.log('页面收到的消息=====》', res)
if (res.event == 'event_talk_revoke') {
// 撤回了消息
removeMsg(res)
} else {
// event_chat_talk
if (res.event.startsWith('event_chat')) {
//聊天消息
screenMsg(res)
unreadClear()
}
}
}
}
const removeMsg = (data) => {
let arr = msgList.value.filter((item) => item.id != data.id)
msgList.value = arr
}
const screenMsg = (msg) => {
// 消息处理
let reuslt = false
if (chatObj.value.type == 'friend') {
reuslt = msg.data.msgFrom == chatto.value && msg.data.msgTo == myuid.value
} else if (['group', 'discussion'].includes(chatObj.value.type)) {
reuslt = msg.data.msgFrom !== myuid.value && msg.data.msgTo == chatto.value
}
if (reuslt) {
console.log('用户消息')
let time = formatDate(msg.data.sendTime, 'yyyy-MM-dd hh:mm:ss')
let id = time.replace(/\:/g, '').replace(/\-/g, '').replace(' ', '')
paging.value?.addChatRecordData(
analysis([
{
fromUserName: msg.data.fromUserName,
msgTo: msg.data.msgTo,
msgFrom: msg.data.msgFrom,
msgData: msg.data.msgData,
fromAvatar: msg.data.fromAvatar,
sendTime: msg.data.sendTime,
msgType: msg.data.msgType,
sendTimeId: id,
fileName: msg.data.fileName,
id: msg.data.id,
},
]),
)
//非自己的消息震动
if (msg.msgFrom != myuid.value) {
console.log('振动')
uni.vibrateLong()
}
}
}
//替换表情符号为图片
const replaceEmoji = (str) => {
let temp = textReplaceEmoji(str)
return '<div style="display:inline-block">' + temp + '</div>'
}
const queryList = (pageNo, pageSize) => {
//数据库查询消息列表
let params = {
type: chatObj.value.type,
pageNo: pageNo,
pageSize: pageSize,
msgTo: chatto.value,
// id: myuid.value,
// sort: 'DESC',
}
console.log('params', params)
const data = [
{
id: 1,
fromUserName: '顾平',
sendTime: '1977-11-13',
fromAvatar: 'https://dummyimage.com/100x100/000/fff&text=%E6%9D%8E%E5%9B%9B',
type: 'friend',
izTop: 1,
status: 'offline',
msgFrom: 4000,
msgTo: 100,
userId: '1678948772039729154',
msgType: 'text',
msgData:
'可半达办外将物起算置知空子。上题务点条界思清法导集马为和计。始在形计各强可求开去手先下识极级育专。形子民委想己给号全维精道应。斯适般不拉个世被资提达须之多关。己除约确元则次同风平维音毛听。少验且内果论治量军们活展观情面元越。',
},
{
id: 2,
fromUserName: '熊艳',
sendTime: '1977-11-13',
fromAvatar: 'https://dummyimage.com/100x100/000/fff&text=%E6%9D%8E%E5%9B%9B',
type: 'friend',
izTop: 0,
status: 'online',
msgFrom: 4012,
msgTo: 134,
userId: '1678948772039729154',
msgType: 'text',
msgData:
'斗取写也展需会于儿次只三自到界民品因。只代装细打三管规这前千器她她入音即准。必象和长使南资几时明因米多交极须空。',
},
{
id: 3,
fromUserName: '康娜',
sendTime: '1977-11-13',
fromAvatar: 'https://dummyimage.com/100x100/000/fff&text=%E6%9D%8E%E5%9B%9B',
type: 'discussion',
izTop: 1,
status: 'offline',
msgFrom: 4024,
msgTo: 168,
userId: '1678948772039729154',
msgType: 'text',
msgData:
'图步把单增利老何列力道何认象。子各交群容产识一界边先式声。被思务共是圆音少际指王元压没任内共。养作包京何铁历属信战族再线理却来情料。能周内家术向建满思书温音太装。时她切调它论族温中三关且与志千。',
},
{
id: 4,
fromUserName: '石平',
sendTime: '1977-11-13',
fromAvatar: 'https://dummyimage.com/100x100/000/fff&text=%E6%9D%8E%E5%9B%9B',
type: 'group',
izTop: 1,
status: 'offline',
msgFrom: 4036,
msgTo: 202,
userId: '1678948772039729154',
msgType: 'text',
msgData:
'民高价般直素划达期矿身更价律或支今。争局便工争相算则参调信斯。把清得完别青院阶火老先位包回速变。意温太合为这来物只我非象专型又加军。至取拉方毛眼例着大你这每相亲该元风。',
},
{
id: 5,
fromUserName: '马杰',
sendTime: '1977-11-13',
fromAvatar: 'https://dummyimage.com/100x100/000/fff&text=%E6%9D%8E%E5%9B%9B',
type: 'group',
izTop: 1,
status: 'offline',
msgFrom: 4048,
msgTo: 236,
userId: '1678948772039729154',
msgType: 'text',
msgData:
'除命市与命成型两口接在及民志提能。需参处空容感学长革段易给分。始真结红型图处技界党非青机山。广直声从老照如写中么省风但。论斗展就积信区北形大报达据把青要。',
},
{
id: 6,
fromUserName: '沈艳',
sendTime: '1977-11-13',
fromAvatar: 'https://dummyimage.com/100x100/000/fff&text=%E6%9D%8E%E5%9B%9B',
type: 'friend',
izTop: 0,
status: 'offline',
msgFrom: 4060,
msgTo: 270,
userId: '1678948772039729154',
msgType: 'text',
msgData:
'规响用其江众器老也成等听节电西。系石教建还厂文气要际长中电今。情素他头资风矿象非这经格老包问过。史期带之走北形历值从所因断八技。红行没众说共史张在任千物今老。传队命做打此无直转术直其门公。',
},
{
id: 7,
fromUserName: '崔芳',
sendTime: '1977-11-13',
fromAvatar: 'https://dummyimage.com/100x100/000/fff&text=%E6%9D%8E%E5%9B%9B',
type: 'discussion',
izTop: 0,
status: 'online',
msgFrom: 4072,
msgTo: 304,
userId: '1678948772039729154',
msgType: 'text',
msgData:
'识保住并非先严间眼马级点叫识只管。写信为每下数集被料前号变很整合。收业后局看并太能决来二府展建片活即体。给史求音很三动作目重因质除提。法关活量管集求公又再时共小明个自确。集提支很也规入并我基照计最要飞院面。',
},
{
id: 8,
fromUserName: '郝超',
sendTime: '1977-11-13',
fromAvatar: 'https://dummyimage.com/100x100/000/fff&text=%E6%9D%8E%E5%9B%9B',
type: 'discussion',
izTop: 1,
status: 'online',
msgFrom: 4084,
msgTo: 338,
userId: '1678948772039729154',
msgType: 'text',
msgData:
'状断只派器新以真业强说部多确料。始矿认要联清才权况况法色式。引办研角且百国路里还计走中细位。或温作经人周复技常位交文共没运。',
},
]
const records = analysis(data)
paging.value.complete(records)
if (isFirstLoad) {
uni.$emit('chatList:unreadClear', chatItemData)
unreadClear()
}
}
const analysis = (data) => {
let arr = data
if (arr.length > 0) {
let list = arr.map((item) => {
let id = String(item.sendTime).replace(/\:/g, '').replace(/\-/g, '').replace(' ', '')
item.sendTimeId = id
let content = item.msgData
if (item.msgType == 'text') {
content = replaceEmoji(content)
}
if (item.msgType == 'voice') {
content = JSON.parse(content)
}
item.msgData = content
return item
})
for (let i = 0; i < list.length; i++) {
if (list[i].msgType == 'revoke') {
continue
}
if (list[i].referenceMsgId) {
list[i] = handleReplyMsg(list[i], list)
}
}
}
return data
}
// 加载初始页面消息
const getMsgList = () => {}
const handleReplyMsg = (item, list) => {
let tempId = item.referenceMsgId
item.reply = true
let replyContent = ''
for (let i = 0; i < list.length; i++) {
if (list[i].id == tempId) {
replyContent = '"' + list[i].fromUserName + ':' + list[i].msgData + '"'
break
}
}
item.replyContent = replyContent
return item
}
const unreadClear = () => {}
// 播放语音
const handlePlayVoice = (item) => {
if (item.id == playMsgid.value) {
AUDIO.stop()
playMsgid.value = ''
} else {
playMsgid.value = item.id
AUDIO.src = item.msgData.url
nextTick(function () {
AUDIO.play()
})
}
}
// 播放语音结束
AUDIO.onEnded((res) => {
playMsgid.value = ''
})
// 监听键盘高度改变,请不要直接通过uni.onKeyboardHeightChange监听,否则可能导致z-paging内置的键盘高度改变监听失效(如果不需要切换表情面板则不用写)
const keyboardHeightChange = (res) => {
inputBar.value.updateKeyboardHeightChange(res)
}
// 用户尝试隐藏键盘,此时如果表情面板在展示中,应当通知chatInputBar隐藏表情面板(如果不需要切换表情面板则不用写)
const hidedKeyboard = () => {
inputBar.value.hidedKeyboard()
}
const doSend = (textMsg) => {
let content = replaceEmoji(textMsg)
//let msg = {'text':content}
//content = (content||'').replace(/&(?!#?[a-zA-Z0-9]+;)/g, '&')
console.log('content', content)
let msg = textMsg
//发送
sendMsg(msg, 'text')
send({ msgData: content, msgType: 'text' })
}
const sendMsg = (content, type) => {
//实际应用中,此处应该提交长连接,模板仅做本地处理。
var nowDate = new Date()
// 发送消息
var obj = {
mine: {
avatar: userStore.userInfo.avatar,
content: content,
id: myuid.value,
mine: true,
username: userStore.userInfo.username,
},
to: {
avatar: chatObj.value.avatar,
id: chatObj.value.msgTo,
type: chatObj.value.type,
username: chatObj.value.username,
},
}
let sendData = {
type: 'chatMessage',
data: obj,
}
console.log('sendData======>', sendData)
let params = {
type: chatObj.value.type,
msgTo: chatObj.value.msgTo,
text: content,
msgType: 'text',
}
// http.post(api.sendMsg, params).then((res: any) => {
// console.log('消息发送结果:', res)
// if (!res.success) {
// toast.error(res.message)
// }
// })
}
const handleImage = (type) => {
let time = formatDate(new Date().getTime(), 'yyyy-MM-dd hh:mm:ss')
let id = time.replace(/\:/g, '').replace(/\-/g, '').replace(' ', '')
let formData = {
type: 'friend',
msgTo: chatto.value,
fileId: id,
msgType: 'images',
fileName: '',
}
const { loading, data, error, run } = useUpload(
{ ...formData, name: 'image' },
{ url: api.uploadUrl, sourceType: [type] },
)
if (stopWatch) stopWatch()
run()
stopWatch = watch(
() => [loading.value, error.value, data.value],
([loading, err, data]: any, oldValue) => {
if (loading === false) {
if (err) {
// toast.warning('修改失败')
uni.hideLoading()
} else {
if (data.code === 200) {
send({ msgData: data.result.text, msgType: 'image' })
} else {
toast.warning(data.message)
}
}
}
},
)
}
const send = ({ msgData, msgType }) => {
let time = formatDate(new Date().getTime(), 'yyyy-MM-dd hh:mm:ss')
let id = time.replace(/\:/g, '').replace(/\-/g, '').replace(' ', '')
//发送
paging.value.addChatRecordData(
analysis([
{
fromUserName: userStore.userInfo.realname,
msgTo: chatto.value,
msgFrom: myuid.value,
msgData: msgData,
fromAvatar: userStore.userInfo.avatar,
sendTime: time,
sendTimeId: id,
msgType,
},
]),
)
}
init()
const handleNavBack = () => {
uni.$emit('chatList:unreadClear', chatItemData)
uni.$emit('chatList:reload')
}
const handleGoChatSetting = () => {
paramsStore.setPageParams('chatSetting', {
type: chatObj.value.type,
chatto: chatto.value,
})
router.push({
name: 'chatSetting',
})
}
onMounted(() => {
uni.$on('chat:updateTile', (title) => {
navTitle.value = title
})
})
onBeforeUnmount(() => {
// socket?.closeSocket()
uni.$off('chat:updateTile')
})
</script>
<style lang="scss" scoped>
//
.wrap {
height: 100%;
}
</style>