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

1004 lines
27 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="outer-layer">
<div>
<tm-navbar :hideBack="false" hideHome title="" :leftWidth="220">
<div class="flex flex-col items-center justify-center">
<div class="text-[34rpx] font-bold">{{ talkParams.username }}</div>
<div v-if="true" class="text-[24rpx] text-[#999999]">公司群</div>
</div>
<template v-slot:right>
<div class="mr-[36rpx]">
<tm-icon color="rgb(51, 51, 51)" :font-size="36" name="tmicon-gengduo" @click="toChatSettingsPage"></tm-icon>
</div>
</template>
</tm-navbar>
</div>
<div class="root">
<div class="dialogBox">
<ZPaging :fixed="false" use-chat-record-mode :use-page-scroll="false" :refresher-enabled="false"
:show-scrollbar="false" :loading-more-enabled="false" :hide-empty-view="true" height="100%" ref="zpagingRef"
:use-virtual-list="true" :preload-page="1" cell-height-mode="dynamic" virtual-scroll-fps="80"
:loading-more-custom-style="{ display: 'none', height: '0' }" @virtualListChange="virtualListChange"
@scrolltolower="onRefreshLoad">
<!-- <template #top>
<div class="load-toolbar pointer">
<span v-if="loadConfig.status == 0"> 正在加载数据中 ... </span>
<span v-else-if="loadConfig.status == 1" @click="onRefreshLoad"> 查看更多消息 ... </span>
<span v-else class="no-more"> 没有更多消息了 </span>
</div>
</template> -->
<!-- 数据加载状态栏 -->
<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">
<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="onRefreshLoad"> 查看更多消息 ... </span>
<span v-else class="no-more"> 没有更多消息了 </span>
</div>
</ZPaging>
</div>
</div>
<div class="footBox">
<div v-if="!dialogueStore.isOpenMultiSelect">
<div class="mt-[16rpx] ml-[32rpx] mr-[32rpx] flex items-center justify-between">
<div class="flex-1 quillBox">
<QuillEditor ref="editor" id="editor" :options="editorOption" @editorChange="onEditorChange"
style="height: 100%; border: none" />
<!-- <tm-input type=textarea autoHeight focusColor="#F9F9F9" color="#F9F9F9" :inputPadding="[12]"
placeholder=""></tm-input> -->
</div>
<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 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>
</div>
</template>
<script setup>
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 } 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'
Quill.register('formats/emoji', EmojiBlot)
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,
})
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 onSendMessage = (data = {}) => {
let message = {
...data,
receiver: {
receiver_id: talkParams.receiver_id,
talk_type: talkParams.type
}
}
ServePublishMessage(message)
.then(({ code, message }) => {
if (code == 200) {
// callBack(true)
} else {
message.warning(message)
}
})
.catch(() => {
message.warning('网络繁忙,请稍后重试!')
})
}
const onSendMessageClick = () => {
let delta = getQuill().getContents()
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)
}
})
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)
}, 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('引用');
}
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: () => {
}
})
}
watch(() => zpagingRef.value, (newValue, oldValue) => {
if (newValue) {
updateZpagingRef(newValue)
}
})
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?.complete(records.value)
}
//点击跳转到聊天设置页面
const toChatSettingsPage = () => {
uni.navigateTo({
url: '/pages/chatSettings/index?groupId=' + talkParams?.receiver_id
})
}
onMounted(async () => {
initData()
})
onUnmounted(() => {
dialogueStore.setDialogue({})
clearMultiSelect()
})
</script>
<style scoped lang="less">
uni-page-body,
page {
height: 100%;
}
.outer-layer {
flex: 1;
background-image: url("@/static/image/clockIn/z3280@3x.png");
background-size: cover;
display: flex;
flex-direction: column;
overflow: hidden;
}
.root {
flex: 1;
padding: 20rpx 32rpx;
min-height: 0;
}
.searchRoot {
background-color: #fff;
padding: 22rpx 18rpx;
}
.contentRoot {
margin-top: 20rpx;
background-color: #fff;
}
.footBox {
min-height: 162rpx;
background-color: #fff;
}
.dialogBox {
height: 100%;
min-height: 0;
overflow: auto;
// 添加以下样式来隐藏滚动条
&::-webkit-scrollbar {
display: none;
}
-ms-overflow-style: none;
/* IE and Edge */
scrollbar-width: none;
/* Firefox */
}
.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;
}
}
&: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 {
: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;
}
</style>