chat-app/src/pages/dialog/index.vue

1482 lines
38 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="dialog-page">
<ZPaging
use-chat-record-mode
use-virtual-list
cell-height-mode="dynamic"
:refresher-enabled="true"
:show-scrollbar="false"
:loading-more-enabled="true"
:hide-empty-view="true"
height="100%"
ref="zpagingRef"
v-model="virtualList"
:loading-more-custom-style="{ display: 'none', height: '0' }"
@scrolltolower="onScrollToLower"
>
<template #top>
<customNavbar :title="talkParams.username">
<template
#subTitle
v-if="talkStore?.findItem(talkParams.index_name)?.group_type === 4"
>
<div class="text-[24rpx] text-[#999999]">公司群</div>
</template>
<template #right>
<div class="mr-[36rpx] toChatSetting_btn">
<tm-icon
color="rgb(51, 51, 51)"
:font-size="36"
name="tmicon-gengduo"
@click="toChatSettingsPage"
></tm-icon>
</div>
</template>
</customNavbar>
</template>
<!-- <template #top>
<div class="load-toolbar pointer">
<span v-if="loadConfig.status == 0"> 正在加载数据中 ... </span>
<span v-else-if="loadConfig.status == 1" @click="onScrollToLower"> 查看更多消息 ... </span>
<span v-else class="no-more"> 没有更多消息了 </span>
</div>
</template> -->
<!-- 数据加载状态栏 -->
<div class="dialog-list" @touchstart="handleHidePanel">
<div
class="message-item"
v-for="item in virtualList"
:id="`zp-id-${item.zp_index}`"
:key="item.zp_index"
style="transform: scaleY(-1);"
>
<!-- 系统消息 -->
<div v-if="item.msg_type >= 1000" class="message-box">
<component
:is="MessageComponents[item.msg_type] || 'unknown-message'"
:extra="item.extra"
:data="item"
/>
</div>
<!-- 撤回消息 -->
<div v-else-if="item.is_revoke == 1" class="message-box">
<revoke-message
:login_uid="userStore.uid"
:user_id="item.user_id"
:nickname="item.nickname"
:talk_type="item.talk_type"
:datetime="item.created_at"
/>
</div>
<div
v-else
class="message-box record-box"
:class="{
'direction-rt': item.float == 'right',
'multi-select': dialogueStore.isOpenMultiSelect,
'multi-select-check': item.isCheck,
}"
>
<!-- 多选按钮 -->
<aside
v-if="dialogueStore.isOpenMultiSelect"
class="checkbox-column"
>
<!-- <n-checkbox size="small" :checked="item.isCheck" @update:checked="item.isCheck = !item.isCheck" /> -->
<tm-checkbox
:round="10"
:defaultChecked="item.isCheck"
@update:modelValue="item.isCheck = !item.isCheck"
:size="42"
color="#46299D"
></tm-checkbox>
</aside>
<!-- 头像信息 -->
<aside class="avatar-column" @click="toUserDetailPage(item)">
<im-avatar
class="pointer"
:src="item.avatar"
:size="80"
:username="item.nickname"
@click="showUserInfoModal(item.user_id)"
/>
</aside>
<!-- 主体信息 -->
<main class="main-column">
<div class="talk-title">
<span
class="nickname pointer"
v-show="talkParams.type == 2 && item.float == 'left'"
@click="onClickNickname(item)"
>
<span class="at">@</span>
{{ item.nickname }}
</span>
<span>
{{ parseTime(item.created_at, '{m}/{d} {h}:{i}') }}
</span>
</div>
<div
class="talk-content"
:class="{ pointer: dialogueStore.isOpenMultiSelect }"
>
<deepBubble
@clickMenu="(menuType) => onContextMenu(menuType, item)"
:isShowCopy="isShowCopy(item)"
:isShowWithdraw="isRevoke(talkParams.uid, item)"
>
<component
class="component-content"
:key="item.zp_index"
:is="MessageComponents[item.msg_type] || 'unknown-message'"
:extra="item.extra"
:data="item"
:max-width="true"
:source="'panel'"
/>
</deepBubble>
<!-- <div class="talk-tools">
<template v-if="talkParams.type == 1 && item.float == 'right'">
<loading theme="outline" size="19" fill="#000" :sxtrokeWidth="1" class="icon-rotate"
v-show="item.send_status == 1" />
<span v-show="item.send_status == 1"> 正在发送... </span>
<span v-show="item.send_status != 1"> 已送达 </span>
</template>
</div> -->
</div>
<div
v-if="item.extra.reply"
class="talk-reply pointer"
@click="onJumpMessage(item.extra?.reply?.msg_id)"
>
<!-- <n-icon :component="ToTop" size="14" class="icon-top" /> -->
<span class="ellipsis">
回复 {{ item.extra?.reply?.nickname }}:
{{ item.extra?.reply?.content }}
</span>
</div>
</main>
</div>
</div>
<div class="load-toolbar pointer" style="transform: scaleY(-1);">
<span v-if="loadConfig.status == 0">正在加载数据中 ...</span>
<span v-else-if="loadConfig.status == 1" @click="onScrollToLower">
查看更多消息 ...
</span>
<span
v-else-if="
loadConfig.status != 0 &&
loadConfig.status != 1 &&
state.localPageLoadDone
"
class="no-more"
>
没有更多消息了
</span>
</div>
</div>
<template #bottom>
<div class="footBox">
<div v-if="!dialogueStore.isOpenMultiSelect">
<div
class="pt-[16rpx] ml-[32rpx] mr-[32rpx] flex items-start justify-between"
>
<div class="flex-1 quillBox">
<QuillEditor
ref="editor"
id="editor"
:options="editorOption"
@editorChange="onEditorChange"
style="width: 100%; flex: 1; height: 100%; border: none;"
@click="onEditorClick"
/>
<!-- <tm-input type=textarea autoHeight focusColor="#F9F9F9" color="#F9F9F9" :inputPadding="[12]"
placeholder=""></tm-input> -->
<div class="quote-area" v-if="state?.quoteInfo">
<span
v-if="state?.quoteInfo?.msg_type === 1"
class="text-[28rpx] text-[#999]"
>
{{
state?.quoteInfo?.nickname +
'' +
state?.quoteInfo?.extra?.content
}}
</span>
<span
v-if="state?.quoteInfo"
class="text-[28rpx] text-[#999]"
>
{{
state?.quoteInfo?.nickname +
'' +
ChatMsgTypeMapping[state?.quoteInfo?.msg_type]
}}
</span>
<img
@click="clearQuoteInfo"
style="width: 30rpx; height: 30rpx;"
src="/src/static/image/login/check-circle-filled@3x.png"
/>
</div>
</div>
<div class="flex items-center justify-end h-[72rpx]">
<tm-image
:margin="[10, 0]"
@click="handleEmojiPanel"
:width="52"
:height="52"
:round="12"
:src="state.isOpenEmojiPanel ? keyboard : smile"
></tm-image>
<tm-image
@click="handleFilePanel"
:margin="[10, 0]"
:width="52"
:height="52"
:round="12"
:src="addCircleGray"
></tm-image>
<tm-button
@click="onSendMessageClick"
:margin="[0, 0]"
:padding="[0, 30]"
color="#46299D"
:fontSize="28"
size="mini"
:shadow="0"
label="发送"
></tm-button>
</div>
</div>
<div v-if="state.isOpenEmojiPanel" class="mt-[50rpx]">
<emojiPanel @on-select="onEmoticonEvent" />
</div>
<div v-if="state.isOpenFilePanel" class="mt-[16rpx]">
<filePanel
@selectImg="handleSelectImg"
:talkParams="talkParams"
/>
</div>
</div>
<div v-else class="h-[232rpx]">
<div
class="flex items-center justify-center mt-[12rpx] text-[24rpx] text-[#747474] leading-[44rpx]"
>
<div class="mr-[8rpx]">已选中:</div>
<div>{{ selectedMessage.length }}条消息</div>
</div>
<div
class="flex items-center justify-around pl-[128rpx] pr-[128rpx] mt-[18rpx] text-[20rpx] text-[#737373]"
>
<div
@click="handleMergeForward"
class="flex flex-col items-center justify-center"
>
<tm-image :width="68" :height="68" :src="zu6050"></tm-image>
<div class="mt-[6rpx]">合并转发</div>
</div>
<div
@click="handleSingleForward"
class="flex flex-col items-center justify-center"
>
<tm-image :width="68" :height="68" :src="zu6051"></tm-image>
<div class="mt-[6rpx]">逐条转发</div>
</div>
<div
@click="handleWechatForward"
class="flex flex-col items-center justify-center"
>
<tm-image :width="68" :height="68" :src="zu6052"></tm-image>
<div class="mt-[6rpx]">微信</div>
</div>
<div
@click="handleDelete"
class="flex flex-col items-center justify-center"
>
<tm-image :width="68" :height="68" :src="zu6053"></tm-image>
<div class="mt-[6rpx]">删除</div>
</div>
</div>
</div>
<!--底部安全区-->
<div class="content-placeholder"></div>
<tm-drawer
placement="bottom"
v-model:show="state.showWin"
:hideHeader="true"
:height="416"
:round="6"
>
<div class="w-full h-full flex flex-col items-center">
<div
class="mt-[46rpx] mb-[44rpx] leading-[48rpx] text-[#747474] text-[24rpx]"
>
撤回该条消息
</div>
<div class="divider"></div>
<div
@click="withdrawerConfirm"
class="mt-[32rpx] mb-[32rpx] text-[32rpx] text-[#CF3050] leading-[48rpx]"
>
撤回
</div>
<div class="divider"></div>
<div
@click="state.showWin = false"
class="mt-[32rpx] mb-[32rpx] text-[32rpx] text-[#000000] leading-[48rpx]"
>
取消
</div>
</div>
</tm-drawer>
</div>
</template>
</ZPaging>
<tm-drawer
placement="bottom"
:show="state.isShowMentionSelect"
:hideHeader="true"
:round="5"
:height="state.mentionSelectHeight"
>
<div
class="mention-select-drawer flex flex-row flex-1 flex-row flex-row-center-between"
>
<div
class="cancel-btns flex-row flex flex-row-center-start"
style="width: 210rpx;"
>
<div
class="hide-btn"
v-if="!state.mentionIsMulSelect"
@click="hideMentionSelect"
>
<img
style="width: 40rpx; height: 40rpx;"
src="/src/static/image/chatList/mention_select_hide_bg.png"
/>
<img
style="
position: absolute;
top: 50%;
left: 50%;
margin-left: -9rpx;
margin-top: -5rpx;
"
src="/src/static/image/chatList/mention_select_hide_icon.png"
/>
</div>
<span
style="flex-shrink: 0; display: block;"
class="text-[32rpx] font-regular text-[#191919]"
v-if="state.mentionIsMulSelect"
@click="changeMentionSelectMul(false)"
>
{{ $t('cancel') }}
</span>
</div>
<div
class="flex flex-row-center-center flex-col"
style="padding: 6rpx 0;"
>
<text>{{ $t('chat.mention.select') }}</text>
</div>
<div class="flex-row flex flex-row-center-end" style="width: 210rpx;">
<div
class="mention-edit-btn"
v-if="!state.mentionIsMulSelect"
@click="changeMentionSelectMul(true)"
>
<span class="text-[32rpx] font-regular text-[#191919]">
{{ $t('button.multiple.choice') }}
</span>
</div>
<div
class="mention-done-btn"
:class="
state?.selectedMembersNum > 0 ? 'mention-done-btn-can-do' : ''
"
v-if="state.mentionIsMulSelect"
@click="confirmMentionSelect"
>
<span class="text-[32rpx] font-regular text-[#191919]">
{{ $t('button.text.done') }}
</span>
<span
class="text-[32rpx] font-regular text-[#191919]"
v-if="state?.selectedMembersNum > 0"
>
{{ '(' + state?.selectedMembersNum + ')' }}
</span>
</div>
</div>
</div>
<selectMemberByAlphabet
:manageType="'mention'"
ref="selectMemberByAlphabetRef"
:selectAreaHeight="state.selectAreaHeight"
@updateSelectedMembersNum="updateSelectedMembersNum"
:isMulSelect="state.mentionIsMulSelect"
@getSelectResult="getSelectResult"
></selectMemberByAlphabet>
</tm-drawer>
</div>
</template>
<script setup>
import selectMemberByAlphabet from '../chatSettings/components/selectMemberByAlphabet.vue'
import {
ref,
reactive,
watch,
computed,
onMounted,
onUnmounted,
nextTick,
} from 'vue'
import { QuillEditor, Quill } from '@vueup/vue-quill'
import EmojiBlot from './formats/emoji'
import { useChatList } from '@/store/chatList/index.js'
import { useAuth } from '@/store/auth'
import {
useUserStore,
useDialogueStore,
useUploadsStore,
useEditorDraftStore,
useTalkStore,
useSettingsStore,
useDialogueListStore,
} from '@/store'
import addCircleGray from '@/static/image/chatList/addCircleGray.png'
import {
MessageComponents,
ForwardableMessageType,
ChatMsgTypeMapping,
} from '@/constant/message'
import { formatTime, parseTime } from '@/utils/datetime'
import { deltaToMessage, deltaToString, isEmptyDelta } from './util'
import smile from '@/static/image/chatList/smile@2x.png'
import keyboard from '@/static/image/chatList/keyboard@2x.png'
import { useInject, useTalkRecord } from '@/hooks'
import { emitCall } from '@/utils/common'
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import useZPaging from '@/uni_modules/z-paging/components/z-paging/js/hooks/useZPaging.js'
import emojiPanel from './components/emojiPanel.vue'
import filePanel from './components/filePanel.vue'
import lodash from 'lodash'
import { ServePublishMessage } from '@/api/chat'
import copy07 from '@/static/image/chatList/copy07@2x.png'
import multipleChoices from '@/static/image/chatList/multipleChoices@2x.png'
import cite from '@/static/image/chatList/cite@2x.png'
import withdraw from '@/static/image/chatList/withdraw@2x.png'
import delete07 from '@/static/image/chatList/delete@2x.png'
import zu6050 from '@/static/image/chatList/zu6050@2x.png'
import zu6051 from '@/static/image/chatList/zu6051@2x.png'
import zu6052 from '@/static/image/chatList/zu6052@2x.png'
import zu6053 from '@/static/image/chatList/zu6053@2x.png'
import deepBubble from '@/components/deep-bubble/deep-bubble.vue'
import { isRevoke } from './menu'
import useConfirm from '@/components/x-confirm/useConfirm.js'
import { onLoad as uniOnload } from '@dcloudio/uni-app'
Quill.register('formats/emoji', EmojiBlot)
const selectMemberByAlphabetRef = ref(null)
const {
getDialogueList,
updateZpagingRef,
virtualList,
} = useDialogueListStore()
const talkStore = useTalkStore()
const { showConfirm } = useConfirm()
const settingsStore = useSettingsStore()
const userStore = useUserStore()
const dialogueStore = useDialogueStore()
const editorDraftStore = useEditorDraftStore()
const editor = ref()
const zpagingRef = ref()
useZPaging(zpagingRef)
const indexName = computed(() => dialogueStore.index_name)
const talkParams = reactive({
uid: computed(() => userStore.uid),
index_name: computed(() => dialogueStore.index_name),
type: computed(() => dialogueStore.talk.talk_type),
receiver_id: computed(() => dialogueStore.talk.receiver_id),
username: computed(() => dialogueStore.talk.username),
online: computed(() => dialogueStore.online),
keyboard: computed(() => dialogueStore.keyboard),
num: computed(() => dialogueStore.members.length),
})
const state = ref({
isOpenEmojiPanel: false,
isOpenFilePanel: false,
showWin: false,
onfocusItem: null,
sessionId: '',
localPageLoadDone: true, //分页加载缓存中的聊天记录是否完毕
quoteInfo: null, //引用信息
mentionIsMulSelect: false, //是否是多选提醒的人
selectedMembersNum: 0, //选中的要提醒的人数
mentionSelectHeight: 0, //选择要提醒人的区域高度
selectAreaHeight: 0, //选择要提醒人的可选人员列表区域高度
isShowMentionSelect: true, //是否显示要提醒人的选择区域
})
uniOnload((options) => {
if (options.sessionId) {
state.value.sessionId = options.sessionId
}
})
const handleEmojiPanel = () => {
state.value.isOpenFilePanel = false
state.value.isOpenEmojiPanel = !state.value.isOpenEmojiPanel
}
const handleFilePanel = () => {
state.value.isOpenEmojiPanel = false
state.value.isOpenFilePanel = !state.value.isOpenFilePanel
}
//点击隐藏表情/文件上传 面板
const handleHidePanel = () => {
state.value.isOpenFilePanel = false
state.value.isOpenEmojiPanel = false
}
//点击编辑区聚焦输入框
const onEditorClick = () => {
handleHidePanel()
}
const onSendMessage = (data = {}, callBack) => {
let message = {
...data,
receiver: {
receiver_id: talkParams.receiver_id,
talk_type: talkParams.type,
},
}
ServePublishMessage(message)
.then(({ code, message }) => {
if (code == 200) {
if (callBack) {
callBack(true)
}
} else {
message.warning(message)
}
})
.catch(() => {
message.warning('网络繁忙,请稍后重试!')
})
}
const onSendMessageClick = () => {
let delta = getQuill().getContents()
if (state.value.quoteInfo) {
delta.ops.unshift({
insert: {
quote: {
id: state.value.quoteInfo.msg_id,
},
},
})
}
let data = deltaToMessage(delta)
if (data.items.length === 0) {
return
}
switch (data.msgType) {
case 1: // 文字消息
if (data.items[0].content.length > 1024) {
return message.info('发送内容超长,请分条发送')
}
onSendTextEvent({
data,
callBack: (ok) => {
if (!ok) return
getQuill().setContents([], Quill.sources.USER)
if (state.value.quoteInfo) {
state.value.quoteInfo = null
}
},
})
break
}
}
// 发送文本消息
const onSendTextEvent = lodash.throttle((value) => {
let { data, callBack } = value
let message = {
type: 'text',
content: data.items[0].content,
quote_id: data.quoteId,
mentions: data.mentionUids,
}
onSendMessage(message, callBack)
}, 1000)
// 编辑器输入事件
const onInputEvent = ({ data }) => {
talkStore.updateItem({
index_name: indexName.value,
draft_text: data,
})
// 判断对方是否在线和是否需要推送
// 3秒时间内推送一次
if (settingsStore.isKeyboard && props.online) {
onKeyboardPush()
}
}
// 发送表情消息
const onSendEmoticonEvent = ({ data }) => {
onSendMessage({ type: 'emoticon', emoticon_id: data })
}
// 注册事件
const evnets = {
text_event: onSendTextEvent,
input_event: onInputEvent,
emoticon_event: onSendEmoticonEvent,
history_event: () => {
isShowHistory.value = true
},
}
const {
loadConfig,
records,
onLoad,
onRefreshLoad,
onJumpMessage,
} = useTalkRecord(talkParams.uid)
const getQuill = () => {
return editor.value?.getQuill()
}
const isShowCopy = (item) => {
switch (item.msg_type) {
case 1:
return true
case 3:
return true
case 5:
return true
case 6:
return true
default:
return false
}
}
const selectedMessage = computed(() => {
return virtualList.value.filter((item) => item.isCheck)
})
// 编辑器事件
const onEditorEvent = (msg) => {
evnets[msg.event] && evnets[msg.event](msg)
}
const getQuillSelectionIndex = () => {
let quill = getQuill()
return (quill.getSelection() || {}).index
}
const onEmoticonEvent = (data) => {
if (data.type == 1) {
const quill = getQuill()
let index = getQuillSelectionIndex()
if (index == 1 && quill.getLength() == 1 && quill.getText(0, 1) == '\n') {
quill.deleteText(0, 1)
index = 0
}
if (data.img) {
quill.insertEmbed(index, 'emoji', {
alt: data.value,
src: data.img,
})
} else {
quill.insertText(index, data.value)
}
quill.setSelection(index + 1, 0, 'user')
} else {
let fn = emitCall('emoticon_event', data.value, () => {})
emit('editor-event', fn)
}
}
const onEditorChange = () => {
let delta = getQuill().getContents()
let text = deltaToString(delta)
if (!isEmptyDelta(delta)) {
editorDraftStore.items[indexName.value || ''] = JSON.stringify({
text: text,
ops: delta.ops,
})
} else {
// 删除 editorDraftStore.items 下的元素
delete editorDraftStore.items[indexName.value || '']
}
onEditorEvent(emitCall('input_event', text))
// emit('editor-event', emitCall('input_event', text))
}
const onClipboardMatcher = (node, Delta) => {
const ops = []
Delta.ops.forEach((op) => {
// 如果粘贴了图片,这里会是一个对象,所以可以这样处理
if (op.insert && typeof op.insert === 'string') {
ops.push({
insert: op.insert, // 文字内容
attributes: {}, //文字样式(包括背景色和文字颜色等)
})
} else {
ops.push(op)
}
})
Delta.ops = ops
return Delta
}
const editorOption = {
debug: false,
modules: {
toolbar: false,
clipboard: {
// 粘贴版,处理粘贴时候的自带样式
matchers: [[Node.ELEMENT_NODE, onClipboardMatcher]],
},
keyboard: {
bindings: {
enter: {
key: 13,
handler: onSendMessageClick,
},
},
},
// imageUploader: {
// upload: onEditorUpload
// },
// mention: {
// allowedChars: /^[\u4e00-\u9fa5]*$/,
// mentionDenotationChars: ['@'],
// positioningStrategy: 'fixed',
// renderItem: (data) => {
// const el = document.createElement('div')
// el.className = 'ed-member-item'
// el.innerHTML = `<img src="${data.avatar}" class="avator"/>`
// el.innerHTML += `<span class="nickname">${data.nickname}</span>`
// return el
// },
// source: function (searchTerm, renderList) {
// if (!props.members.length) {
// return renderList([])
// }
// let list = [
// { id: 0, nickname: '所有人', avatar: defAvatar, value: '所有人' },
// ...props.members
// ]
// const items = list.filter(
// (item) => item.nickname.toLowerCase().indexOf(searchTerm) !== -1
// )
// renderList(items)
// },
// mentionContainerClass: 'ql-mention-list-container me-scrollbar me-scrollbar-thumb'
// }
},
placeholder: '',
}
const handleSelectImg = (data) => {
onSendMessage({ ...data })
}
const virtualListChange = (vList) => {
virtualList.value = vList
}
const onContextMenu = (menuType, item) => {
console.log(menuType, item, 'item')
switch (menuType) {
case 'actionCopy':
actionCopy(item)
break
case 'multipleChoose':
multipleChoose(item)
break
case 'actionCite':
actionCite(item)
break
case 'actionWithdraw':
actionWithdraw(item)
break
case 'actionDelete':
actionDelete(item)
break
default:
break
}
}
const actionCopy = (item) => {
console.log('复制')
let content = ''
switch (item.msg_type) {
case 1:
content = item.extra.content
break
case 3:
content = item.extra.url
break
case 5:
content = item.extra.url
break
default:
break
}
uni.setClipboardData({
data: content,
})
}
const multipleChoose = (item) => {
item.isCheck = true
dialogueStore.setMultiSelect(true)
}
const actionCite = (item) => {
console.log('引用')
state.value.quoteInfo = item
}
//清除引用信息
const clearQuoteInfo = () => {
state.value.quoteInfo = null
}
const actionWithdraw = (item) => {
console.log('撤回')
state.value.onfocusItem = item
state.value.showWin = true
}
const withdrawerConfirm = () => {
dialogueStore.ApiRevokeRecord(state.value.onfocusItem.msg_id)
state.value.onfocusItem = null
state.value.showWin = false
}
const actionDelete = (item) => {
console.log('删除')
item.isCheck = true
handleDelete()
}
const handleMergeForward = () => {
if (selectedMessage.value.length == 0) {
return message.warning('未选择消息')
}
console.log('合并转发')
dialogueStore.setForwardType(2)
dialogueStore.setForwardMessages(selectedMessage.value)
uni.navigateTo({
url: '/pages/chooseChat/index',
success: function (res) {
clearMultiSelect()
},
})
}
const handleSingleForward = () => {
if (selectedMessage.value.length == 0) {
return message.warning('未选择消息')
}
console.log('逐条转发')
dialogueStore.setForwardType(1)
dialogueStore.setForwardMessages(selectedMessage.value)
uni.navigateTo({
url: '/pages/chooseChat/index',
success: function (res) {
clearMultiSelect()
},
})
}
const handleWechatForward = () => {
if (selectedMessage.value.length == 0) {
return message.warning('未选择消息')
}
console.log('微信转发')
}
const handleDelete = () => {
if (selectedMessage.value.length == 0) {
return message.warning('未选择消息')
}
console.log('删除')
showConfirm({
content: '确定删除聊天记录',
confirmText: '删除',
confirmColor: '#CF3050',
onConfirm: async () => {
const msgIds = selectedMessage.value.map((item) => item.msg_id)
virtualList.value = virtualList.value.filter(
(item) => !msgIds.includes(item.msg_id),
)
dialogueStore.ApiDeleteRecord(msgIds)
clearMultiSelect()
},
onCancel: () => {},
})
}
//更新选中的要提醒的人数
const updateSelectedMembersNum = (numChange) => {
state.value.selectedMembersNum = state.value.selectedMembersNum + numChange
}
watch(
() => zpagingRef.value,
(newValue, oldValue) => {
if (newValue) {
updateZpagingRef(newValue)
}
},
)
watch(
() => virtualList.value,
(newValue, oldValue) => {
if (newValue) {
const dialogueList = getDialogueList(talkParams.index_name)
// console.log(newValue[newValue.length - 1]?.sequence, dialogueList?.records?.[0]?.sequence)
if (
newValue[newValue.length - 1]?.sequence ===
dialogueList.records?.[0]?.sequence
) {
//相同意味着分页加载缓存中的聊天记录完毕
state.value.localPageLoadDone = true
} else {
state.value.localPageLoadDone = false
}
}
},
{
deep: true,
},
)
const onScrollToLower = () => {
if (state.value.localPageLoadDone) {
//本地缓存的聊天记录分页加载完之后,才可以请求接口
onRefreshLoad()
}
}
const clearMultiSelect = () => {
dialogueStore.setMultiSelect(false)
virtualList.value.forEach((item) => {
item.isCheck = false
})
}
const initData = async () => {
const dialogueList = getDialogueList(talkParams.index_name)
let objT = {
uid: talkParams.uid,
talk_type: talkParams.type,
receiver_id: talkParams.receiver_id,
index_name: talkParams.index_name,
direction: dialogueList ? 'down' : 'up',
no_limit: dialogueList ? 1 : 0,
}
await onLoad({ ...objT })
zpagingRef.value?.setLocalPaging(records.value)
}
//点击跳转到聊天设置页面
const toChatSettingsPage = () => {
uni.navigateTo({
url:
'/pages/chatSettings/index?groupId=' +
talkParams?.receiver_id +
'&sessionId=' +
state.value.sessionId,
})
}
//点击跳转到用户详情页面
const toUserDetailPage = (userItem) => {
uni.navigateTo({
url:
'/pages/dialog/dialogDetail/userDetail?erpUserId=' + userItem.erp_user_id,
})
}
//切换提醒的人选择弹窗多选状态
const changeMentionSelectMul = (status) => {
state.value.mentionIsMulSelect = status
}
//隐藏要提醒人的选择
const hideMentionSelect = () => {
state.value.isShowMentionSelect = false
}
//确认要提醒人的选择
const confirmMentionSelect = () => {
if (state?.value.selectedMembersNum > 0) {
if (selectMemberByAlphabetRef.value) {
selectMemberByAlphabetRef.value.confirmSelectMembers()
}
hideMentionSelect()
}
}
//获取选择的结果
const getSelectResult = (mentionSelect) => {
console.log(mentionSelect)
}
onMounted(async () => {
initData()
nextTick(() => {
state.value.mentionSelectHeight = pxTorPx(
uni.getSystemInfoSync().windowHeight * 0.86,
)
state.value.selectAreaHeight =
rpxToPx(state.value.mentionSelectHeight) - rpxToPx(90) + 'px'
})
})
const pxTorPx = (px) => {
const sysInfo = uni.getSystemInfoSync()
const rpx = px / (sysInfo.screenWidth / 750)
return rpx
}
const rpxToPx = (rpx) => {
const sysInfo = uni.getSystemInfoSync()
const px = (sysInfo.screenWidth / 750) * rpx
return px
}
onUnmounted(() => {
dialogueStore.setDialogue({})
clearMultiSelect()
})
</script>
<style scoped lang="less">
.dialog-page {
flex: 1;
background-image: url('@/static/image/clockIn/z3280@3x.png');
background-size: cover;
background-position: bottom center;
background-attachment: fixed;
width: 100%;
.dialog-list {
padding: 20rpx 32rpx;
}
.toChatSetting_btn {
::v-deep .tmicon-gengduo {
line-height: unset !important;
}
}
}
.searchRoot {
background-color: #fff;
padding: 22rpx 18rpx;
}
.contentRoot {
margin-top: 20rpx;
background-color: #fff;
}
.footBox {
min-height: 162rpx;
background-color: #fff;
.quote-area {
margin: 4rpx 0 0 0;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
overflow: hidden;
width: 100%;
span {
display: -webkit-inline-box;
text-overflow: ellipsis;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
width: 100%;
}
img {
margin: 0 0 0 30rpx;
flex-shrink: 0;
}
}
}
.load-toolbar {
height: 50rpx;
color: #409eff;
text-align: center;
line-height: 50rpx;
font-size: 24rpx;
.no-more {
color: #b9b3b3;
}
}
.message-item {
&.border {
border-radius: 16rpx;
}
}
.message-box {
width: 100%;
min-height: 30rpx;
margin-bottom: 5rpx;
}
.record-box {
display: flex;
flex-direction: row;
align-items: flex-start;
.checkbox-column {
display: flex;
justify-content: center;
width: 42rpx;
order: 1;
user-select: none;
margin-top: 20rpx;
margin-right: 20rpx;
}
.avatar-column {
width: 80rpx;
display: flex;
align-items: center;
order: 2;
user-select: none;
margin-top: 16rpx;
flex-direction: column;
}
.main-column {
flex: 1 auto;
order: 3;
position: relative;
box-sizing: border-box;
padding: 16rpx 20rpx 14rpx 20rpx;
// overflow: hidden;
min-height: 30px;
.talk-title {
display: flex;
align-items: center;
margin-bottom: 6rpx;
font-size: 24rpx;
user-select: none;
color: #bababa;
opacity: 1;
&.show {
opacity: 1;
}
.nickname {
color: var(--im-text-color);
margin-right: 5rpx;
.at {
display: none;
}
&:hover {
color: var(--im-primary-color);
.at {
display: inline-block;
}
}
}
span {
transform: scale(0.88);
transform-origin: left center;
}
}
.talk-content {
display: flex;
justify-content: flex-start;
align-items: flex-end;
box-sizing: border-box;
width: 100%;
.talk-tools {
display: flex;
margin: 0 16rpx;
color: #a79e9e;
font-size: 24rpx;
user-select: none;
align-items: center;
justify-content: space-around;
.more-tools {
visibility: hidden;
margin-left: 5rpx;
}
}
}
.talk-reply {
display: flex;
align-items: flex-start;
align-items: center;
width: fit-content;
padding: 8rpx;
margin-top: 6rpx;
margin-right: auto;
font-size: 24rpx;
color: #8f8f8f;
word-break: break-all;
background-color: var(--im-message-left-bg-color);
border-radius: 10rpx;
max-width: 450rpx;
overflow: hidden;
user-select: none;
.icon-top {
margin-right: 6rpx;
}
.ellipsis {
display: -webkit-inline-box;
text-overflow: ellipsis;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
}
&:hover {
.talk-title {
opacity: 1;
}
.more-tools {
visibility: visible !important;
}
}
}
&.direction-rt {
.avatar-column {
order: 3;
}
.main-column {
order: 2;
.talk-title {
justify-content: flex-end;
span {
transform-origin: right center;
}
}
.talk-content {
flex-direction: row-reverse;
}
.talk-reply {
margin-right: 0;
margin-left: auto;
}
}
}
&.multi-select {
border-radius: 5px;
&:hover,
&.multi-select-check {
background-color: var(--im-active-bg-color);
}
}
}
.content-placeholder {
height: 58rpx;
}
.quillBox {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
:deep(.ql-clipboard) {
position: relative;
opacity: 0;
height: 1rpx;
overflow: auto;
}
:deep(.ql-editor) {
padding: 14rpx 22rpx;
background-color: #f9f9f9;
border-radius: 8rpx;
outline: none !important;
max-height: 294rpx;
overflow: auto;
line-height: 44rpx;
font-size: 32rpx;
p {
display: inline-flex;
align-items: center;
justify-content: flex-start;
flex-wrap: wrap;
white-space: normal;
word-break: break-all;
.ed-emoji {
width: 44rpx;
height: 44rpx;
display: inline-block;
}
}
}
}
:deep(.wd-action-sheet) {
background-color: #8b8b8b !important;
}
:deep(.wd-action-sheet__panel-title) {
color: #fff !important;
}
.component-content {
position: relative;
z-index: 1;
/* 确保 z-index 低于 deepBubble */
}
.divider {
width: 100%;
height: 1rpx;
background-color: #e7e7e7;
}
.mention-select-drawer {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 36rpx 32rpx 0;
.cancel-btns {
flex-shrink: 0;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
.hide-btn {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
position: relative;
text {
}
img {
width: 18rpx;
height: 10rpx;
}
}
}
.mention-done-btn {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 6rpx 24rpx;
background-color: #f3f3f3;
border-radius: 8rpx;
flex-shrink: 0;
span {
color: #bababa;
line-height: 40rpx;
flex-shrink: 0;
}
}
.mention-done-btn-can-do {
background-color: #46299d;
span {
color: #fff;
}
}
.mention-edit-btn {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
flex-shrink: 0;
span {
flex-shrink: 0;
}
}
}
</style>