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

2065 lines
54 KiB
Vue
Raw Normal View History

2024-11-22 01:06:37 +00:00
<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"
@scrolltoupper="onScrollToUpper"
>
<template #top>
<customNavbar :title="talkParams.username" id="navBarArea">
<template
#subTitle
v-if="talkStore?.findItem(talkParams.index_name)?.group_type === 4"
>
<div class="text-[24rpx] text-[#999999]">公司群</div>
</template>
<template #right>
2025-03-14 05:15:54 +00:00
<div
class="mr-[36rpx] toChatSetting_btn"
v-if="!talkParams.isDismiss"
>
<tm-icon
color="rgb(51, 51, 51)"
:font-size="36"
name="tmicon-gengduo"
@click="toChatSettingsPage"
></tm-icon>
</div>
</template>
</customNavbar>
</template>
<!-- <template #top>
2024-12-06 08:55:15 +00:00
<div class="load-toolbar pointer">
<span v-if="loadConfig.status == 0"> 正在加载数据中 ... </span>
<span v-else-if="loadConfig.status == 1" @click="onScrollToLower"> 查看更多消息 ... </span>
2024-12-06 08:55:15 +00:00
<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.msg_id}`"
:key="item.zp_index"
2025-03-20 03:02:29 +00:00
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"
2025-03-13 05:08:58 +00:00
:msg_id="item.msg_id"
2025-03-17 08:32:29 +00:00
:revokeInfo="item.revokeInfo"
:extra="item.extra"
2025-03-13 05:08:58 +00:00
>
2025-03-14 05:15:54 +00:00
<template
v-if="
canEditRevokedMessage(item) && item.user_id === userStore.uid
"
>
<span
class="edit-revoked-message"
@click="restoreRevokedMessage(item)"
>
重新编辑
</span>
</template>
2025-03-13 05:08:58 +00:00
</revoke-message>
</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'"
>
<span class="at">@</span>
{{ item.nickname }}
</span>
<span>
2025-03-20 03:02:29 +00:00
{{ 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)"
2025-03-14 05:15:54 +00:00
:isShowWithdraw="isRevoke(talkParams.uid, item) || isLeader"
>
<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">
2024-12-06 08:55:15 +00:00
<template v-if="talkParams.type == 1 && item.float == 'right'">
2024-12-19 03:02:47 +00:00
<loading theme="outline" size="19" fill="#000" :sxtrokeWidth="1" class="icon-rotate"
2024-11-26 08:51:36 +00:00
v-show="item.send_status == 1" />
2024-11-22 09:00:03 +00:00
2024-11-26 08:51:36 +00:00
<span v-show="item.send_status == 1"> 正在发送... </span>
2024-12-06 08:55:15 +00:00
<span v-show="item.send_status != 1"> 已送达 </span>
2024-11-26 08:51:36 +00:00
</template>
2024-12-06 08:55:15 +00:00
</div> -->
</div>
2024-11-22 01:06:37 +00:00
<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>
2024-12-19 03:02:47 +00:00
</div>
</div>
2025-03-20 03:02:29 +00:00
<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>
2024-11-22 01:06:37 +00:00
</div>
</div>
<template #bottom>
<div class="footBox" id="footBoxArea">
<div v-if="!dialogueStore.isOpenMultiSelect">
<div
2025-03-10 07:09:26 +00:00
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"
2025-03-20 03:02:29 +00:00
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> -->
2025-03-10 07:09:26 +00:00
<div class="quote-area" v-if="state?.quoteInfo">
<span
v-if="state?.quoteInfo?.msg_type === 1"
class="text-[28rpx] text-[#999]"
>
{{
state?.quoteInfo?.nickname +
2025-03-20 03:02:29 +00:00
"" +
2025-03-10 07:09:26 +00:00
state?.quoteInfo?.extra?.content
}}
</span>
<span
v-if="state?.quoteInfo"
2025-03-10 07:09:26 +00:00
class="text-[28rpx] text-[#999]"
>
{{
state?.quoteInfo?.nickname +
2025-03-20 03:02:29 +00:00
"" +
ChatMsgTypeMapping[state?.quoteInfo?.msg_type]
2025-03-10 07:09:26 +00:00
}}
</span>
<img
@click="clearQuoteInfo"
2025-03-20 03:02:29 +00:00
style="width: 30rpx; height: 30rpx"
2025-03-10 07:09:26 +00:00
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>
2024-12-19 03:02:47 +00:00
</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>
2024-12-19 03:02:47 +00:00
</div>
2024-12-06 08:55:15 +00:00
<!--底部安全区-->
<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>
2024-12-24 08:28:44 +00:00
</div>
</template>
</ZPaging>
<tm-drawer
placement="bottom"
2025-03-12 11:59:54 +00:00
v-model: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"
2025-03-20 03:02:29 +00:00
style="width: 210rpx"
>
<div
class="hide-btn"
v-if="!state.mentionIsMulSelect"
@click="hideMentionSelect"
>
<img
2025-03-20 03:02:29 +00:00
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
2025-03-20 03:02:29 +00:00
style="flex-shrink: 0; display: block"
class="text-[32rpx] font-regular text-[#191919]"
v-if="state.mentionIsMulSelect"
@click="changeMentionSelectMul(false)"
>
2025-03-20 03:02:29 +00:00
{{ $t("cancel") }}
</span>
</div>
<div
class="flex flex-row-center-center flex-col"
2025-03-20 03:02:29 +00:00
style="padding: 6rpx 0"
>
2025-03-20 03:02:29 +00:00
<text>{{ $t("chat.mention.select") }}</text>
</div>
2025-03-20 03:02:29 +00:00
<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]">
2025-03-20 03:02:29 +00:00
{{ $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]">
2025-03-20 03:02:29 +00:00
{{ $t("button.text.done") }}
</span>
<span
class="text-[32rpx] font-regular text-[#191919]"
v-if="state?.selectedMembersNum > 0"
>
2025-03-20 03:02:29 +00:00
{{ "(" + state?.selectedMembersNum + ")" }}
</span>
</div>
</div>
</div>
<selectMemberByAlphabet
:manageType="'mention'"
ref="selectMemberByAlphabetRef"
:selectAreaHeight="state.selectAreaHeight"
@updateSelectedMembersNum="updateSelectedMembersNum"
:isMulSelect="state.mentionIsMulSelect"
@getSelectResult="getSelectResult"
2025-03-12 11:59:54 +00:00
@getMentionSelectLists="getMentionSelectLists"
></selectMemberByAlphabet>
</tm-drawer>
2024-11-22 01:06:37 +00:00
</div>
</template>
<script setup>
2025-03-20 03:02:29 +00:00
import selectMemberByAlphabet from "../chatSettings/components/selectMemberByAlphabet.vue";
import {
ref,
reactive,
watch,
computed,
onMounted,
onUnmounted,
nextTick,
2025-03-20 03:02:29 +00:00
} 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";
2024-12-20 08:59:58 +00:00
import {
useUserStore,
useDialogueStore,
useUploadsStore,
useEditorDraftStore,
useTalkStore,
useSettingsStore,
useDialogueListStore,
2025-03-20 03:02:29 +00:00
} from "@/store";
import addCircleGray from "@/static/image/chatList/addCircleGray.png";
import {
MessageComponents,
ForwardableMessageType,
ChatMsgTypeMapping,
2025-03-20 03:02:29 +00:00
} 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,
detailGetRecordsContext,
ServeClearTalkUnreadNum,
ServeTalkRecords,
2025-03-20 03:02:29 +00:00
} 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,
onUnload as uniOnUnload,
} from "@dcloudio/uni-app";
Quill.register("formats/emoji", EmojiBlot);
import "quill-mention";
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);
2024-11-22 01:06:37 +00:00
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),
2025-03-14 05:15:54 +00:00
isDismiss: computed(() => dialogueStore.isDismiss),
adminList: computed(() => dialogueStore.getAdminList),
2025-03-20 03:02:29 +00:00
});
2024-11-22 01:06:37 +00:00
2024-11-26 08:51:36 +00:00
const state = ref({
2024-12-06 08:55:15 +00:00
isOpenEmojiPanel: false,
2024-12-19 03:02:47 +00:00
isOpenFilePanel: false,
2024-12-24 08:28:44 +00:00
showWin: false,
onfocusItem: null,
2025-03-20 03:02:29 +00:00
sessionId: "", //会话Id
localPageLoadDone: true, //分页加载缓存中的聊天记录是否完毕
2025-03-10 07:09:26 +00:00
quoteInfo: null, //引用信息
mentionIsMulSelect: false, //是否是多选提醒的人
selectedMembersNum: 0, //选中的要提醒的人数
mentionSelectHeight: 0, //选择要提醒人的区域高度
selectAreaHeight: 0, //选择要提醒人的可选人员列表区域高度
2025-03-12 11:59:54 +00:00
isShowMentionSelect: false, //是否显示要提醒人的选择区域
useCustomLoadMore: false, //是否使用自定义加载更多事件(下拉刷新、上拉加载)
2025-03-20 03:02:29 +00:00
recordDate: "", //按日期查询聊天记录的开始日期
serveFindRecord: [], //调用接口查找到的聊天记录
middleMsg: {}, //缓存中没有时,调用接口初次使用的依据记录
keepDialogInfo: false, //是否保存会话信息
})
uniOnload(async (options) => {
console.log('onLoad' + JSON.stringify(options))
if (options.sessionId) {
2025-03-20 03:02:29 +00:00
state.value.sessionId = options.sessionId;
}
if (options.keepDialogInfo) {
state.value.keepDialogInfo = options.keepDialogInfo === '1' ? true : false
}
if (options.msgInfo) {
2025-03-20 03:02:29 +00:00
const msgInfo = JSON.parse(decodeURIComponent(options.msgInfo));
queryRecordsByMsgInfo(msgInfo);
state.value.useCustomLoadMore = true;
return;
}
if (options.recordDate) {
2025-03-20 03:02:29 +00:00
state.value.recordDate = options.recordDate;
const msgInfo = await findTalkRecords(options.recordDate, true);
queryRecordsByMsgInfo(msgInfo);
state.value.useCustomLoadMore = true;
return;
}
2025-03-20 03:02:29 +00:00
initData();
});
uniOnUnload(() => {
2025-03-20 03:02:29 +00:00
console.log("onUnload");
ServeClearTalkUnreadNum({
talk_type: Number(talkParams.type),
receiver_id: Number(talkParams.receiver_id),
}).then(() => {
talkStore.updateItem({
index_name: talkParams.index_name,
unread_num: 0,
2025-03-20 03:02:29 +00:00
});
});
});
2024-12-06 08:55:15 +00:00
const handleEmojiPanel = () => {
2025-03-20 03:02:29 +00:00
state.value.isOpenFilePanel = false;
state.value.isOpenEmojiPanel = !state.value.isOpenEmojiPanel;
};
2024-12-06 08:55:15 +00:00
const handleFilePanel = () => {
2025-03-20 03:02:29 +00:00
state.value.isOpenEmojiPanel = false;
state.value.isOpenFilePanel = !state.value.isOpenFilePanel;
};
2024-12-06 08:55:15 +00:00
//点击隐藏表情/文件上传 面板
const handleHidePanel = () => {
2025-03-20 03:02:29 +00:00
state.value.isOpenFilePanel = false;
state.value.isOpenEmojiPanel = false;
};
//点击编辑区聚焦输入框
const onEditorClick = () => {
2025-03-20 03:02:29 +00:00
handleHidePanel();
};
const onSendMessage = (data = {}, callBack) => {
2024-11-28 08:55:45 +00:00
let message = {
...data,
receiver: {
receiver_id: talkParams.receiver_id,
talk_type: talkParams.type,
},
2025-03-20 03:02:29 +00:00
};
2024-11-28 08:55:45 +00:00
ServePublishMessage(message)
.then(({ code, message }) => {
if (code == 200) {
if (callBack) {
2025-03-20 03:02:29 +00:00
callBack(true);
}
2024-11-28 08:55:45 +00:00
} else {
2025-03-20 03:02:29 +00:00
message.warning(message);
2024-11-28 08:55:45 +00:00
}
})
.catch(() => {
2025-03-20 03:02:29 +00:00
message.warning("网络繁忙,请稍后重试!");
});
};
2024-11-28 08:55:45 +00:00
const onSendMessageClick = () => {
2025-03-20 03:02:29 +00:00
let delta = getQuill().getContents();
if (state.value.quoteInfo) {
delta.ops.unshift({
insert: {
quote: {
id: state.value.quoteInfo.msg_id,
},
},
2025-03-20 03:02:29 +00:00
});
}
2025-03-20 03:02:29 +00:00
let data = deltaToMessage(delta);
2024-11-28 08:55:45 +00:00
if (data.items.length === 0) {
2025-03-20 03:02:29 +00:00
return;
2024-11-28 08:55:45 +00:00
}
switch (data.msgType) {
case 1: // 文字消息
if (data.items[0].content.length > 1024) {
2025-03-20 03:02:29 +00:00
return message.info("发送内容超长,请分条发送");
2024-11-28 08:55:45 +00:00
}
2024-12-19 03:02:47 +00:00
onSendTextEvent({
data,
callBack: (ok) => {
2025-03-20 03:02:29 +00:00
if (!ok) return;
getQuill().setContents([], Quill.sources.USER);
if (state.value.quoteInfo) {
2025-03-20 03:02:29 +00:00
state.value.quoteInfo = null;
}
},
2025-03-20 03:02:29 +00:00
});
break;
2024-11-28 08:55:45 +00:00
}
2025-03-20 03:02:29 +00:00
};
2024-11-28 08:55:45 +00:00
// 发送文本消息
const onSendTextEvent = lodash.throttle((value) => {
2025-03-20 03:02:29 +00:00
let { data, callBack } = value;
2024-11-28 08:55:45 +00:00
let message = {
2025-03-20 03:02:29 +00:00
type: "text",
2024-11-28 08:55:45 +00:00
content: data.items[0].content,
quote_id: data.quoteId,
mentions: data.mentionUids,
2025-03-20 03:02:29 +00:00
};
2024-11-28 08:55:45 +00:00
2025-03-20 03:02:29 +00:00
onSendMessage(message, callBack);
}, 1000);
2024-11-28 08:55:45 +00:00
// 编辑器输入事件
const onInputEvent = ({ data }) => {
talkStore.updateItem({
index_name: indexName.value,
draft_text: data,
2025-03-20 03:02:29 +00:00
});
2024-11-28 08:55:45 +00:00
// 判断对方是否在线和是否需要推送
// 3秒时间内推送一次
if (settingsStore.isKeyboard && props.online) {
2025-03-20 03:02:29 +00:00
onKeyboardPush();
2024-11-28 08:55:45 +00:00
}
2025-03-20 03:02:29 +00:00
};
2024-11-28 08:55:45 +00:00
// 发送表情消息
2024-12-13 05:09:38 +00:00
const onSendEmoticonEvent = ({ data }) => {
2025-03-20 03:02:29 +00:00
onSendMessage({ type: "emoticon", emoticon_id: data });
};
2024-11-28 08:55:45 +00:00
// 注册事件
const evnets = {
text_event: onSendTextEvent,
input_event: onInputEvent,
emoticon_event: onSendEmoticonEvent,
history_event: () => {
2025-03-20 03:02:29 +00:00
isShowHistory.value = true;
2024-11-28 08:55:45 +00:00
},
2025-03-20 03:02:29 +00:00
};
2024-11-28 08:55:45 +00:00
2025-03-20 03:02:29 +00:00
const { loadConfig, records, onLoad, onRefreshLoad, onJumpMessage } =
useTalkRecord(talkParams.uid);
2024-11-26 08:51:36 +00:00
const getQuill = () => {
2025-03-20 03:02:29 +00:00
return editor.value?.getQuill();
};
2024-11-26 08:51:36 +00:00
2024-12-20 08:59:58 +00:00
const isShowCopy = (item) => {
switch (item.msg_type) {
case 1:
2025-03-20 03:02:29 +00:00
return true;
2024-12-20 08:59:58 +00:00
case 3:
2025-03-20 03:02:29 +00:00
return true;
2024-12-20 08:59:58 +00:00
case 5:
2025-03-20 03:02:29 +00:00
return true;
2024-12-20 08:59:58 +00:00
case 6:
2025-03-20 03:02:29 +00:00
return true;
2024-12-20 08:59:58 +00:00
default:
2025-03-20 03:02:29 +00:00
return false;
2024-12-20 08:59:58 +00:00
}
2025-03-20 03:02:29 +00:00
};
2024-12-20 08:59:58 +00:00
2024-12-19 03:02:47 +00:00
const selectedMessage = computed(() => {
2025-03-20 03:02:29 +00:00
return virtualList.value.filter((item) => item.isCheck);
});
2024-12-19 03:02:47 +00:00
2024-11-28 08:55:45 +00:00
// 编辑器事件
const onEditorEvent = (msg) => {
2025-03-20 03:02:29 +00:00
evnets[msg.event] && evnets[msg.event](msg);
};
2024-11-28 08:55:45 +00:00
const getQuillSelectionIndex = () => {
2025-03-20 03:02:29 +00:00
let quill = getQuill();
return (quill.getSelection() || {}).index;
};
2024-11-28 08:55:45 +00:00
const onEmoticonEvent = (data) => {
if (data.type == 1) {
2025-03-20 03:02:29 +00:00
const quill = getQuill();
let index = getQuillSelectionIndex();
2024-11-28 08:55:45 +00:00
2025-03-20 03:02:29 +00:00
if (index == 1 && quill.getLength() == 1 && quill.getText(0, 1) == "\n") {
quill.deleteText(0, 1);
index = 0;
2024-11-28 08:55:45 +00:00
}
if (data.img) {
2025-03-20 03:02:29 +00:00
quill.insertEmbed(index, "emoji", {
2024-11-28 08:55:45 +00:00
alt: data.value,
src: data.img,
2025-03-20 03:02:29 +00:00
});
2024-11-28 08:55:45 +00:00
} else {
2025-03-20 03:02:29 +00:00
quill.insertText(index, data.value);
2024-11-28 08:55:45 +00:00
}
2025-03-20 03:02:29 +00:00
quill.setSelection(index + 1, 0, "user");
2024-11-28 08:55:45 +00:00
} else {
2025-03-20 03:02:29 +00:00
let fn = emitCall("emoticon_event", data.value, () => {});
emit("editor-event", fn);
2024-11-28 08:55:45 +00:00
}
2025-03-20 03:02:29 +00:00
};
2024-11-28 08:55:45 +00:00
2024-11-26 08:51:36 +00:00
const onEditorChange = () => {
2025-03-20 03:02:29 +00:00
let delta = getQuill().getContents();
2024-11-26 08:51:36 +00:00
2025-03-20 03:02:29 +00:00
let text = deltaToString(delta);
2024-11-26 08:51:36 +00:00
2025-03-20 03:02:29 +00:00
// if (
// text.length > 0 &&
// text.slice(-2).trim() === "@" &&
// talkParams.type === 2
// ) {
// state.value.isShowMentionSelect = true;
// }
2025-03-12 11:59:54 +00:00
2024-11-26 08:51:36 +00:00
if (!isEmptyDelta(delta)) {
2025-03-20 03:02:29 +00:00
editorDraftStore.items[indexName.value || ""] = JSON.stringify({
2024-11-26 08:51:36 +00:00
text: text,
ops: delta.ops,
2025-03-20 03:02:29 +00:00
});
2024-11-26 08:51:36 +00:00
} else {
// 删除 editorDraftStore.items 下的元素
2025-03-20 03:02:29 +00:00
delete editorDraftStore.items[indexName.value || ""];
2024-11-26 08:51:36 +00:00
}
2025-03-20 03:02:29 +00:00
onEditorEvent(emitCall("input_event", text));
2024-11-26 08:51:36 +00:00
// emit('editor-event', emitCall('input_event', text))
2025-03-13 05:08:58 +00:00
// 清理过期的撤回消息超过5分钟
2025-03-20 03:02:29 +00:00
const now = new Date().getTime();
Object.keys(state.value.revokedMessages || {}).forEach((msgId) => {
2025-03-14 05:15:54 +00:00
if (now - state.value.revokedMessages[msgId].revokeTime > 5 * 60 * 1000) {
2025-03-20 03:02:29 +00:00
delete state.value.revokedMessages[msgId];
2025-03-13 05:08:58 +00:00
}
2025-03-20 03:02:29 +00:00
});
};
2024-11-26 08:51:36 +00:00
2024-11-28 08:55:45 +00:00
const onClipboardMatcher = (node, Delta) => {
2025-03-20 03:02:29 +00:00
const ops = [];
2024-11-28 08:55:45 +00:00
Delta.ops.forEach((op) => {
// 如果粘贴了图片,这里会是一个对象,所以可以这样处理
2025-03-20 03:02:29 +00:00
if (op.insert && typeof op.insert === "string") {
2024-11-28 08:55:45 +00:00
ops.push({
insert: op.insert, // 文字内容
attributes: {}, //文字样式(包括背景色和文字颜色等)
2025-03-20 03:02:29 +00:00
});
2024-11-28 08:55:45 +00:00
} else {
2025-03-20 03:02:29 +00:00
ops.push(op);
2024-11-28 08:55:45 +00:00
}
2025-03-20 03:02:29 +00:00
});
2024-11-26 08:51:36 +00:00
2025-03-20 03:02:29 +00:00
Delta.ops = ops;
return Delta;
};
2024-11-26 08:51:36 +00:00
const editorOption = {
debug: false,
modules: {
toolbar: false,
2024-11-28 08:55:45 +00:00
clipboard: {
// 粘贴版,处理粘贴时候的自带样式
matchers: [[Node.ELEMENT_NODE, onClipboardMatcher]],
2024-11-28 08:55:45 +00:00
},
2024-11-26 08:51:36 +00:00
keyboard: {
bindings: {
enter: {
key: 13,
handler: onSendMessageClick,
},
},
2024-11-26 08:51:36 +00:00
},
// imageUploader: {
// upload: onEditorUpload
// },
2025-03-20 03:02:29 +00:00
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 (!dialogueStore.members.length) {
return renderList([]);
}
let list = [
{ id: 0, nickname: "所有人", avatar: "https://cdn-test.szjixun.cn/artistinfo/test/4455/81e23b7a-d9a3-45c2-af9b-405d33083fed.jpg", value: "所有人" },
...dialogueStore.members,
];
const items = list.filter(
(item) => item.nickname.toLowerCase().indexOf(searchTerm) !== -1
);
renderList(items);
},
mentionContainerClass:
"ql-mention-list-container me-scrollbar me-scrollbar-thumb",
},
2024-11-26 08:51:36 +00:00
},
2025-03-20 03:02:29 +00:00
placeholder: "",
};
2024-11-26 08:51:36 +00:00
const handleSelectImg = (data, file_num) => {
2025-03-20 03:02:29 +00:00
onSendMessage({ ...data, file_num });
};
2024-11-26 08:51:36 +00:00
2024-12-06 08:55:15 +00:00
const virtualListChange = (vList) => {
2025-03-20 03:02:29 +00:00
virtualList.value = vList;
};
2024-11-26 08:51:36 +00:00
2024-12-20 08:59:58 +00:00
const onContextMenu = (menuType, item) => {
2025-03-20 03:02:29 +00:00
console.log(menuType, item, "item");
2024-12-20 08:59:58 +00:00
switch (menuType) {
2025-03-20 03:02:29 +00:00
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;
2024-12-20 08:59:58 +00:00
default:
2025-03-20 03:02:29 +00:00
break;
2024-12-20 08:59:58 +00:00
}
2025-03-20 03:02:29 +00:00
};
2024-12-19 03:02:47 +00:00
const actionCopy = (item) => {
2025-03-20 03:02:29 +00:00
console.log("复制");
let content = "";
2024-12-19 03:02:47 +00:00
switch (item.msg_type) {
case 1:
2025-03-20 03:02:29 +00:00
content = item.extra.content;
break;
2024-12-19 03:02:47 +00:00
case 3:
2025-03-20 03:02:29 +00:00
content = item.extra.url;
break;
2024-12-19 03:02:47 +00:00
case 5:
2025-03-20 03:02:29 +00:00
content = item.extra.url;
break;
2024-12-19 03:02:47 +00:00
default:
2025-03-20 03:02:29 +00:00
break;
2024-12-19 03:02:47 +00:00
}
uni.setClipboardData({
data: content,
2025-03-20 03:02:29 +00:00
});
};
2024-12-19 03:02:47 +00:00
const multipleChoose = (item) => {
2025-03-20 03:02:29 +00:00
item.isCheck = true;
dialogueStore.setMultiSelect(true);
};
2024-12-19 03:02:47 +00:00
2024-12-20 08:59:58 +00:00
const actionCite = (item) => {
2025-03-20 03:02:29 +00:00
console.log("引用");
state.value.quoteInfo = item;
};
2025-03-10 07:09:26 +00:00
//清除引用信息
const clearQuoteInfo = () => {
2025-03-20 03:02:29 +00:00
state.value.quoteInfo = null;
};
2024-12-19 03:02:47 +00:00
2024-12-20 08:59:58 +00:00
const actionWithdraw = (item) => {
2025-03-20 03:02:29 +00:00
console.log("撤回");
state.value.onfocusItem = item;
state.value.showWin = true;
};
2024-12-24 08:28:44 +00:00
const withdrawerConfirm = () => {
2025-03-20 03:02:29 +00:00
dialogueStore.ApiRevokeRecord(state.value.onfocusItem.msg_id);
state.value.onfocusItem = null;
state.value.showWin = false;
};
2024-12-19 03:02:47 +00:00
2025-03-13 05:08:58 +00:00
// 添加检查是否可以重新编辑撤回消息的函数
const canEditRevokedMessage = (item) => {
// console.log(item)
2025-03-14 05:15:54 +00:00
if (item.is_revoke === 1 && item.msg_type === 1) {
const now = new Date().getTime()
const revokeTime = new Date(item.created_at).getTime()
// console.log(now)
// 检查是否在5分钟内
2025-03-20 03:02:29 +00:00
return now - revokeTime <= 5 * 60 * 1000;
}
2025-03-20 03:02:29 +00:00
return false;
};
2025-03-13 05:08:58 +00:00
// 添加恢复撤回消息到输入框的函数
const restoreRevokedMessage = async (item) => {
// 接口拿数据,之后把查询的内容给输入框
const res = await detailGetRecordsContext({
2025-03-14 05:15:54 +00:00
msgId: item.msg_id,
2025-03-20 03:02:29 +00:00
});
console.log(res);
2025-03-14 05:15:54 +00:00
if (res.code == 200) {
2025-03-20 03:02:29 +00:00
const content = res.data.item?.extra?.content;
const quill = getQuill();
quill.setText(content);
// 将光标设置到文本末尾
2025-03-20 03:02:29 +00:00
quill.setSelection(content.length, 0);
quill.focus();
}
/* const revokedMsg = state.value.revokedMessages[msgId]
2025-03-13 05:08:58 +00:00
// 根据消息类型处理
if (revokedMsg.msgType === 1) { // 文本消息
const quill = getQuill()
quill.setText(revokedMsg.content)
quill.focus()
} */
2025-03-13 05:08:58 +00:00
// 可以根据需要添加其他类型消息的处理
2025-03-20 03:02:29 +00:00
};
2025-03-13 05:08:58 +00:00
2024-12-20 08:59:58 +00:00
const actionDelete = (item) => {
2025-03-20 03:02:29 +00:00
console.log("删除");
item.isCheck = true;
handleDelete();
};
2024-12-19 03:02:47 +00:00
const handleMergeForward = () => {
if (selectedMessage.value.length == 0) {
2025-03-20 03:02:29 +00:00
return message.warning("未选择消息");
2024-12-19 03:02:47 +00:00
}
2025-03-20 03:02:29 +00:00
console.log("合并转发");
dialogueStore.setForwardType(2);
dialogueStore.setForwardMessages(selectedMessage.value);
2024-12-19 03:02:47 +00:00
uni.navigateTo({
2025-03-20 03:02:29 +00:00
url: "/pages/chooseChat/index",
2024-12-19 03:02:47 +00:00
success: function (res) {
2025-03-20 03:02:29 +00:00
clearMultiSelect();
},
2025-03-20 03:02:29 +00:00
});
};
2024-12-19 03:02:47 +00:00
const handleSingleForward = () => {
if (selectedMessage.value.length == 0) {
2025-03-20 03:02:29 +00:00
return message.warning("未选择消息");
2024-12-19 03:02:47 +00:00
}
2025-03-20 03:02:29 +00:00
console.log("逐条转发");
dialogueStore.setForwardType(1);
dialogueStore.setForwardMessages(selectedMessage.value);
2024-12-20 08:59:58 +00:00
uni.navigateTo({
2025-03-20 03:02:29 +00:00
url: "/pages/chooseChat/index",
2024-12-20 08:59:58 +00:00
success: function (res) {
2025-03-20 03:02:29 +00:00
clearMultiSelect();
},
2025-03-20 03:02:29 +00:00
});
};
2024-12-19 03:02:47 +00:00
const handleWechatForward = () => {
if (selectedMessage.value.length == 0) {
2025-03-20 03:02:29 +00:00
return message.warning("未选择消息");
2024-12-19 03:02:47 +00:00
}
2025-03-20 03:02:29 +00:00
console.log("微信转发");
};
2024-12-19 03:02:47 +00:00
const handleDelete = () => {
if (selectedMessage.value.length == 0) {
2025-03-20 03:02:29 +00:00
return message.warning("未选择消息");
2024-12-19 03:02:47 +00:00
}
2025-03-20 03:02:29 +00:00
console.log("删除");
2024-12-24 08:28:44 +00:00
showConfirm({
2025-03-20 03:02:29 +00:00
content: "确定删除聊天记录",
confirmText: "删除",
confirmColor: "#CF3050",
2024-12-24 08:28:44 +00:00
onConfirm: async () => {
2025-03-20 03:02:29 +00:00
const msgIds = selectedMessage.value.map((item) => item.msg_id);
virtualList.value = virtualList.value.filter(
2025-03-20 03:02:29 +00:00
(item) => !msgIds.includes(item.msg_id)
);
dialogueStore.ApiDeleteRecord(msgIds);
clearMultiSelect();
2024-12-24 08:28:44 +00:00
},
onCancel: () => {},
2025-03-20 03:02:29 +00:00
});
};
2024-12-19 03:02:47 +00:00
//更新选中的要提醒的人数
const updateSelectedMembersNum = (numChange) => {
2025-03-20 03:02:29 +00:00
state.value.selectedMembersNum = state.value.selectedMembersNum + numChange;
};
watch(
() => zpagingRef.value,
(newValue, oldValue) => {
if (newValue) {
2025-03-20 03:02:29 +00:00
updateZpagingRef(newValue);
}
2025-03-20 03:02:29 +00:00
}
);
2024-11-22 01:06:37 +00:00
watch(
() => virtualList.value,
(newValue, oldValue) => {
if (newValue) {
2025-03-20 03:02:29 +00:00
const dialogueList = getDialogueList(talkParams.index_name);
2025-03-07 08:14:25 +00:00
// console.log(newValue[newValue.length - 1]?.sequence, dialogueList?.records?.[0]?.sequence)
if (!dialogueList || dialogueList?.length === 0) {
2025-03-20 03:02:29 +00:00
state.value.localPageLoadDone = true;
return;
}
// 只在正常加载模式下检查是否加载完缓存数据
if (!state.value.useCustomLoadMore) {
if (
newValue[newValue.length - 1]?.sequence ===
dialogueList?.records?.[0]?.sequence
) {
//相同意味着分页加载缓存中的聊天记录完毕
state.value.localPageLoadDone = true
} else {
state.value.localPageLoadDone = false
}
}
}
},
{
deep: true,
2025-03-20 03:02:29 +00:00
}
);
const onScrollToLower = async () => {
if (state.value.useCustomLoadMore) {
2025-03-20 03:02:29 +00:00
const tempVirtualList = lodash.cloneDeep(virtualList.value).reverse();
const dialogueList = getDialogueList(talkParams.index_name);
const recordIndex = dialogueList?.records?.findIndex(
2025-03-20 03:02:29 +00:00
(record) => record.msg_id === tempVirtualList[0].msg_id
);
if (!recordIndex || recordIndex === -1) {
const moreRecords = await findTalkRecords(
2025-03-20 03:02:29 +00:00
"",
false,
tempVirtualList[0].sequence,
{
2025-03-20 03:02:29 +00:00
direction: "up",
sort_sequence: "",
}
);
console.log(moreRecords);
// 格式化新加载的消息
const formattedMoreRecords = moreRecords.map((item) => ({
...item,
float: item.user_id === talkParams.uid ? 'right' : 'left',
}))
virtualList.value = formattedMoreRecords.concat(tempVirtualList).reverse()
2025-03-20 03:02:29 +00:00
console.log(virtualList.value);
} else {
if (tempVirtualList[0].sequence > dialogueList.records[0].sequence) {
virtualList.value = dialogueList.records
.slice(Math.max(0, recordIndex - 10), recordIndex)
.concat(tempVirtualList)
2025-03-20 03:02:29 +00:00
.reverse();
// zpagingRef.value.setLocalPaging(
// dialogueList.records
// .slice(0, recordIndex)
// .concat(tempVirtualList)
// .reverse(),
// // zpagingRef.value.scrollIntoViewById('zp-id-' + virtualList.value[virtualList.value.length - 1].msg_id)
// )
2025-03-20 03:02:29 +00:00
console.log(virtualList.value);
}
}
2025-03-20 03:02:29 +00:00
return;
}
if (state.value.localPageLoadDone) {
//本地缓存的聊天记录分页加载完之后,才可以请求接口
2025-03-20 03:02:29 +00:00
onRefreshLoad();
}
2025-03-20 03:02:29 +00:00
};
//本来的下拉刷新——列表倒置后为上拉加载
const onScrollToUpper = async () => {
if (state.value.useCustomLoadMore) {
2025-03-20 03:02:29 +00:00
const tempVirtualList = lodash.cloneDeep(virtualList.value).reverse();
const dialogueList = getDialogueList(talkParams.index_name);
const recordIndex = dialogueList?.records?.findIndex(
(record) =>
2025-03-20 03:02:29 +00:00
record.msg_id === tempVirtualList[tempVirtualList.length - 1].msg_id
);
console.log(recordIndex);
if (!recordIndex || recordIndex === -1) {
// 记住加载更多前消息的ID
2025-03-20 03:02:29 +00:00
const currentMsgId = tempVirtualList[tempVirtualList.length - 1].msg_id;
const moreRecords = await findTalkRecords(
2025-03-20 03:02:29 +00:00
"",
false,
tempVirtualList[tempVirtualList.length - 1].sequence,
)
console.log(moreRecords)
// 格式化新加载的消息
const formattedMoreRecords = moreRecords.map((item) => ({
...item,
float: item.user_id === talkParams.uid ? 'right' : 'left',
}))
2025-03-20 03:02:29 +00:00
virtualList.value = tempVirtualList
.concat(formattedMoreRecords.reverse())
.reverse()
2025-03-20 03:02:29 +00:00
console.log(virtualList.value);
// 数据更新后,滚动到之前的位置
nextTick(() => {
2025-03-20 03:02:29 +00:00
zpagingRef.value?.scrollIntoViewById("zp-id-" + currentMsgId);
});
} else {
if (
tempVirtualList[tempVirtualList.length - 1].sequence <
dialogueList.records[dialogueList.records.length - 1].sequence
) {
// 记住加载更多前消息的ID
2025-03-20 03:02:29 +00:00
const currentMsgId = tempVirtualList[tempVirtualList.length - 1].msg_id;
virtualList.value = tempVirtualList
.concat(
dialogueList.records.slice(
Math.min(recordIndex + 11, dialogueList.records.length),
2025-03-20 03:02:29 +00:00
dialogueList.records.length
)
)
2025-03-20 03:02:29 +00:00
.reverse();
// 数据更新后,滚动到之前的位置
nextTick(() => {
2025-03-20 03:02:29 +00:00
zpagingRef.value?.scrollIntoViewById("zp-id-" + currentMsgId);
});
}
}
}
2025-03-20 03:02:29 +00:00
};
2024-12-19 03:02:47 +00:00
const clearMultiSelect = () => {
2025-03-20 03:02:29 +00:00
dialogueStore.setMultiSelect(false);
virtualList.value.forEach((item) => {
2025-03-20 03:02:29 +00:00
item.isCheck = false;
});
};
2024-12-19 03:02:47 +00:00
const initData = async () => {
2025-03-20 03:02:29 +00:00
const dialogueList = getDialogueList(talkParams.index_name);
2024-11-22 01:06:37 +00:00
let objT = {
uid: talkParams.uid,
talk_type: talkParams.type,
receiver_id: talkParams.receiver_id,
2024-12-06 08:55:15 +00:00
index_name: talkParams.index_name,
2025-03-20 03:02:29 +00:00
direction: dialogueList ? "down" : "up",
2024-12-19 03:02:47 +00:00
no_limit: dialogueList ? 1 : 0,
2025-03-20 03:02:29 +00:00
};
await onLoad({ ...objT });
zpagingRef.value?.setLocalPaging(records.value);
};
2024-12-10 03:31:36 +00:00
//点击跳转到聊天设置页面
const toChatSettingsPage = () => {
uni.navigateTo({
url:
2025-03-20 03:02:29 +00:00
"/pages/chatSettings/index?groupId=" +
talkParams?.receiver_id +
2025-03-20 03:02:29 +00:00
"&sessionId=" +
state.value.sessionId,
2025-03-20 03:02:29 +00:00
});
};
//点击跳转到用户详情页面
const toUserDetailPage = (userItem) => {
uni.navigateTo({
url:
2025-03-20 03:02:29 +00:00
"/pages/dialog/dialogDetail/userDetail?erpUserId=" + userItem.erp_user_id,
});
};
//切换提醒的人选择弹窗多选状态
const changeMentionSelectMul = (status) => {
2025-03-20 03:02:29 +00:00
state.value.mentionIsMulSelect = status;
};
//隐藏要提醒人的选择
const hideMentionSelect = () => {
2025-03-20 03:02:29 +00:00
state.value.isShowMentionSelect = false;
};
//确认要提醒人的选择
const confirmMentionSelect = () => {
if (state?.value.selectedMembersNum > 0) {
if (selectMemberByAlphabetRef.value) {
2025-03-20 03:02:29 +00:00
selectMemberByAlphabetRef.value.confirmSelectMembers();
}
2025-03-20 03:02:29 +00:00
hideMentionSelect();
}
2025-03-20 03:02:29 +00:00
};
//获取选择的结果
const getSelectResult = (mentionSelect) => {
2025-03-20 03:02:29 +00:00
console.log(mentionSelect);
getMentionSelectLists(mentionSelect);
};
2025-03-12 11:59:54 +00:00
//处理要提醒人的消息样式
const getMentionSelectLists = (mentionSelectList) => {
2025-03-20 03:02:29 +00:00
// @
console.log(mentionSelectList);
let mentionUserIds = [];
let mentionUsers = getQuill().getContents().ops; //先读出来之前的信息内容
2025-03-12 11:59:54 +00:00
mentionUsers[0].insert =
2025-03-20 03:02:29 +00:00
mentionUsers[0].insert.slice(0, -2) + mentionUsers[0].insert.slice(-1);
console.log(mentionUsers[0].insert);
2025-03-12 11:59:54 +00:00
mentionSelectList.forEach((mentionSelectItem) => {
2025-03-20 03:02:29 +00:00
mentionUserIds.push(mentionSelectItem.id);
2025-03-12 11:59:54 +00:00
mentionUsers.push({
2025-03-20 03:02:29 +00:00
insert: "@" + mentionSelectItem.nickname + " ",
2025-03-12 11:59:54 +00:00
attributes: {
// mention: {
// id: mentionSelectItem.id,
// },
2025-03-20 03:02:29 +00:00
color: "#1890ff",
2025-03-12 11:59:54 +00:00
},
2025-03-20 03:02:29 +00:00
});
});
getQuill().setContents(mentionUsers);
hideMentionSelect();
};
//根据msg信息找到对应的聊天记录并根据sequence等查看上下文
const queryRecordsByMsgInfo = async (msgInfo) => {
console.log(msgInfo)
state.value.middleMsg = msgInfo
const dialogueList = getDialogueList(talkParams.index_name)
const recordIndex = dialogueList?.records?.findIndex(
2025-03-20 03:02:29 +00:00
(record) => record.msg_id === msgInfo.msg_id
);
let recordsList = [];
console.log(recordIndex);
if (!recordIndex || recordIndex === -1) {
2025-03-20 03:02:29 +00:00
recordsList = await findTalkRecords("", true, msgInfo.sequence);
} else {
// console.log(recordIndex)
2025-03-20 03:02:29 +00:00
const startRecordIndex = Math.max(0, recordIndex - 10);
const endRecordIndex = Math.max(0, recordIndex + 10);
// console.log(dialogueList.records.slice(startRecordIndex, endRecordIndex))
// console.log(recordIndex-startRecordIndex)
2025-03-20 03:02:29 +00:00
recordsList = dialogueList.records.slice(startRecordIndex, endRecordIndex);
}
// 格式化消息,确保每条消息都有正确的 float
recordsList = recordsList.map((item) => {
return {
...item,
float: item.user_id === talkParams.uid ? 'right' : 'left',
}
})
nextTick(() => {
2025-03-20 03:02:29 +00:00
zpagingRef.value.complete(recordsList.reverse());
loadConfig.status = dialogueList?.records?.[0]?.sequence > 1 ? 1 : 2;
nextTick(() => {
2025-03-20 03:02:29 +00:00
let offset = uni.getSystemInfoSync().windowHeight;
const navBarAreaQuery = uni.createSelectorQuery();
navBarAreaQuery
2025-03-20 03:02:29 +00:00
.select("#navBarArea")
.boundingClientRect((res) => {
if (res) {
// console.log('元素高度:', res.height)
2025-03-20 03:02:29 +00:00
offset = offset - res.height;
}
})
2025-03-20 03:02:29 +00:00
.exec();
const footBoxAreaQuery = uni.createSelectorQuery();
footBoxAreaQuery
2025-03-20 03:02:29 +00:00
.select("#footBoxArea")
.boundingClientRect((res) => {
if (res) {
// console.log('元素高度:', res.height)
2025-03-20 03:02:29 +00:00
offset = offset - res.height;
}
})
2025-03-20 03:02:29 +00:00
.exec();
setTimeout(() => {
zpagingRef.value.scrollIntoViewById(
2025-03-20 03:02:29 +00:00
"zp-id-" + msgInfo.msg_id,
offset - 60
);
}, 1000);
});
});
};
//查找聊天记录
const findTalkRecords = (record, isMiddle, sequence, appointParams) => {
return new Promise((resolve, reject) => {
let params = {
talk_type: talkParams.type, //1私聊2群聊
receiver_id: talkParams.receiver_id, //目标用户id或群聊id
2025-03-20 03:02:29 +00:00
no_limit: "", //1不限制
file_name: "",
msg_type: 0, //消息类型0:全部;2:代码;3:图片;4:音频;5:视频;6:文件;7:位置;9:会话;11群投票;12图文混合
cursor: sequence || 0, //上次查询的游标
limit: 10, //数据行数
2025-03-20 03:02:29 +00:00
direction: "down", //down向下查最新up向上查老数据
start_time: "",
end_time: "",
group_member_user_id: 0, //群成员id当查询群历史消息的时候需要指定群成员的时候送
sort_sequence: 'asc',
create_time: state.value.middleMsg.created_at,
}
if (record) {
params = Object.assign({}, params, {
start_time: record,
end_time: record,
limit: 1,
2025-03-20 03:02:29 +00:00
direction: "",
});
}
if (appointParams) {
2025-03-20 03:02:29 +00:00
params = Object.assign({}, params, appointParams);
}
2025-03-20 03:02:29 +00:00
console.log(params);
const resp = ServeTalkRecords(params);
console.log(resp);
resp.then(({ code, data }) => {
2025-03-20 03:02:29 +00:00
console.log(data);
if (code == 200) {
if (data?.items.length > 0) {
if (record) {
2025-03-20 03:02:29 +00:00
resolve(data?.items[0]);
return;
}
if (isMiddle) {
state.value.serveFindRecord = JSON.parse(
2025-03-20 03:02:29 +00:00
JSON.stringify(data?.items)
);
return findTalkRecords("", false, sequence + 1, {
direction: "up",
sort_sequence: "",
}).then((finalResult) => {
2025-03-20 03:02:29 +00:00
console.log(finalResult);
resolve(finalResult);
});
} else {
state.value.serveFindRecord = data?.items
.reverse()
2025-03-20 03:02:29 +00:00
.concat(state.value.serveFindRecord);
resolve(JSON.parse(JSON.stringify(state.value.serveFindRecord)));
state.value.serveFindRecord = [];
}
}
} else {
}
2025-03-20 03:02:29 +00:00
});
resp.catch(() => {});
});
};
2025-03-14 05:15:54 +00:00
//是否是管理员
const isLeader = computed(() => {
if (talkParams.adminList.length > 0) {
return (
talkParams.adminList.filter(
2025-03-20 03:02:29 +00:00
(adminItem) => adminItem.erp_user_id === useAuth()?.userInfo?.value?.ID
2025-03-14 05:15:54 +00:00
).length > 0
2025-03-20 03:02:29 +00:00
);
2025-03-14 05:15:54 +00:00
}
2025-03-20 03:02:29 +00:00
return false;
});
2025-03-14 05:15:54 +00:00
2024-12-19 03:02:47 +00:00
onMounted(async () => {
2025-03-20 03:02:29 +00:00
if (typeof plus !== "undefined") {
const webview = plus.webview.currentWebview();
webview.setStyle({
bottom: 0,
2025-03-20 03:02:29 +00:00
});
} else {
2025-03-20 03:02:29 +00:00
document.addEventListener("plusready", () => {
const webview = plus.webview.currentWebview();
webview.setStyle({
bottom: 0,
2025-03-20 03:02:29 +00:00
});
});
}
nextTick(() => {
state.value.mentionSelectHeight = pxTorPx(
2025-03-20 03:02:29 +00:00
uni.getSystemInfoSync().windowHeight * 0.86
);
state.value.selectAreaHeight =
2025-03-20 03:02:29 +00:00
rpxToPx(state.value.mentionSelectHeight) - rpxToPx(90) + "px";
});
});
2024-11-22 01:06:37 +00:00
const pxTorPx = (px) => {
2025-03-20 03:02:29 +00:00
const sysInfo = uni.getSystemInfoSync();
const rpx = px / (sysInfo.screenWidth / 750);
return rpx;
};
const rpxToPx = (rpx) => {
2025-03-20 03:02:29 +00:00
const sysInfo = uni.getSystemInfoSync();
const px = (sysInfo.screenWidth / 750) * rpx;
return px;
};
2024-12-06 08:55:15 +00:00
onUnmounted(() => {
if (!state.value.keepDialogInfo) {
dialogueStore.setDialogue({})
}
2025-03-20 03:02:29 +00:00
clearMultiSelect();
});
2024-11-22 01:06:37 +00:00
</script>
2024-11-22 09:00:03 +00:00
<style scoped lang="less">
.dialog-page {
2024-11-22 01:06:37 +00:00
flex: 1;
2025-03-20 03:02:29 +00:00
background-image: url("@/static/image/clockIn/z3280@3x.png");
2024-11-22 01:06:37 +00:00
background-size: cover;
background-position: bottom center;
background-attachment: fixed;
width: 100%;
2024-11-22 09:00:03 +00:00
.dialog-list {
padding: 20rpx 32rpx;
}
.toChatSetting_btn {
::v-deep .tmicon-gengduo {
line-height: unset !important;
}
}
2024-11-22 01:06:37 +00:00
}
2024-11-22 09:00:03 +00:00
2025-03-13 05:08:58 +00:00
.edit-revoked-message {
margin-left: 10rpx;
2025-03-14 05:15:54 +00:00
color: #46299d;
2025-03-13 05:08:58 +00:00
cursor: pointer;
font-size: 24rpx;
&:hover {
text-decoration: underline;
}
}
2024-11-22 01:06:37 +00:00
.searchRoot {
background-color: #fff;
padding: 22rpx 18rpx;
}
2024-11-22 09:00:03 +00:00
2024-11-22 01:06:37 +00:00
.contentRoot {
margin-top: 20rpx;
background-color: #fff;
}
2024-11-22 09:00:03 +00:00
2024-11-22 01:06:37 +00:00
.footBox {
2024-11-26 08:51:36 +00:00
min-height: 162rpx;
2024-11-22 01:06:37 +00:00
background-color: #fff;
2025-03-10 07:09:26 +00:00
.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;
}
}
2024-11-22 01:06:37 +00:00
}
2024-11-22 09:00:03 +00:00
.load-toolbar {
2024-11-26 08:51:36 +00:00
height: 50rpx;
2024-11-22 09:00:03 +00:00
color: #409eff;
text-align: center;
2024-11-26 08:51:36 +00:00
line-height: 50rpx;
font-size: 24rpx;
2024-11-22 09:00:03 +00:00
.no-more {
color: #b9b3b3;
}
}
.message-item {
&.border {
2024-11-26 08:51:36 +00:00
border-radius: 16rpx;
2024-11-22 09:00:03 +00:00
}
}
.message-box {
width: 100%;
2024-11-26 08:51:36 +00:00
min-height: 30rpx;
margin-bottom: 5rpx;
2024-11-22 09:00:03 +00:00
}
.record-box {
display: flex;
flex-direction: row;
align-items: flex-start;
2024-11-26 08:51:36 +00:00
2024-11-22 09:00:03 +00:00
.checkbox-column {
display: flex;
justify-content: center;
2024-12-19 03:02:47 +00:00
width: 42rpx;
2024-11-22 09:00:03 +00:00
order: 1;
user-select: none;
2024-12-19 03:02:47 +00:00
margin-top: 20rpx;
margin-right: 20rpx;
2024-11-22 09:00:03 +00:00
}
.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;
2024-12-19 03:02:47 +00:00
// overflow: hidden;
2024-11-22 09:00:03 +00:00
min-height: 30px;
.talk-title {
display: flex;
align-items: center;
margin-bottom: 6rpx;
font-size: 24rpx;
user-select: none;
color: #bababa;
2024-11-22 09:00:03 +00:00
opacity: 1;
&.show {
opacity: 1;
}
.nickname {
color: var(--im-text-color);
2024-11-26 08:51:36 +00:00
margin-right: 5rpx;
2024-11-22 09:00:03 +00:00
.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;
2024-11-26 08:51:36 +00:00
margin: 0 16rpx;
2024-11-22 09:00:03 +00:00
color: #a79e9e;
2024-11-26 08:51:36 +00:00
font-size: 24rpx;
2024-11-22 09:00:03 +00:00
user-select: none;
align-items: center;
justify-content: space-around;
.more-tools {
visibility: hidden;
2024-11-26 08:51:36 +00:00
margin-left: 5rpx;
2024-11-22 09:00:03 +00:00
}
}
}
.talk-reply {
display: flex;
align-items: flex-start;
align-items: center;
width: fit-content;
2024-11-26 08:51:36 +00:00
padding: 8rpx;
margin-top: 6rpx;
2024-11-22 09:00:03 +00:00
margin-right: auto;
2024-11-26 08:51:36 +00:00
font-size: 24rpx;
2024-11-22 09:00:03 +00:00
color: #8f8f8f;
word-break: break-all;
background-color: var(--im-message-left-bg-color);
2024-11-26 08:51:36 +00:00
border-radius: 10rpx;
max-width: 450rpx;
2024-11-22 09:00:03 +00:00
overflow: hidden;
user-select: none;
.icon-top {
2024-11-26 08:51:36 +00:00
margin-right: 6rpx;
2024-11-22 09:00:03 +00:00
}
.ellipsis {
display: -webkit-inline-box;
text-overflow: ellipsis;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
2025-03-10 07:09:26 +00:00
overflow: hidden;
2024-11-22 09:00:03 +00:00
}
}
&: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);
}
}
}
2024-11-26 08:51:36 +00:00
.content-placeholder {
height: 58rpx;
}
.quillBox {
2025-03-10 07:09:26 +00:00
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
2024-11-26 08:51:36 +00:00
:deep(.ql-clipboard) {
2024-11-28 08:55:45 +00:00
position: relative;
opacity: 0;
height: 1rpx;
overflow: auto;
2024-11-26 08:51:36 +00:00
}
:deep(.ql-editor) {
padding: 14rpx 22rpx;
background-color: #f9f9f9;
2024-11-26 08:51:36 +00:00
border-radius: 8rpx;
outline: none !important;
2024-11-28 08:55:45 +00:00
max-height: 294rpx;
overflow: auto;
line-height: 44rpx;
font-size: 32rpx;
2024-12-19 03:02:47 +00:00
p {
2024-11-28 08:55:45 +00:00
display: inline-flex;
align-items: center;
justify-content: flex-start;
flex-wrap: wrap;
white-space: normal;
word-break: break-all;
2024-12-19 03:02:47 +00:00
.ed-emoji {
2024-11-28 08:55:45 +00:00
width: 44rpx;
height: 44rpx;
display: inline-block;
}
span {
user-select: all;
}
2024-11-28 08:55:45 +00:00
}
2024-11-26 08:51:36 +00:00
}
}
2024-12-19 03:02:47 +00:00
:deep(.wd-action-sheet) {
background-color: #8b8b8b !important;
}
:deep(.wd-action-sheet__panel-title) {
color: #fff !important;
}
2024-12-20 08:59:58 +00:00
.component-content {
position: relative;
z-index: 1;
/* 确保 z-index 低于 deepBubble */
}
2024-12-24 08:28:44 +00:00
.divider {
width: 100%;
height: 1rpx;
background-color: #e7e7e7;
2024-12-24 08:28:44 +00:00
}
.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;
}
}
}
2025-03-20 03:02:29 +00:00
.ql-mention-list-container {
width: 160px;
max-height: 200px;
border: 1px solid #f0f0f0;
border-radius: 10px;
background-color: #ffffff;
box-shadow: 0 2px 12px 0 rgba(30, 30, 30, 0.08);
z-index: 9001;
overflow: auto;
padding: 3px;
padding-left: 6px;
}
html[theme-mode="dark"] {
.ql-mention-list-container {
background-color: rgb(44 44 49);
color: #fff;
border: unset;
.ql-mention-list-item {
color: var(--im-text-color-grey);
}
}
}
// 滚动条样式
.me-scrollbar {
&::-webkit-scrollbar {
width: 3px;
height: 3px;
background-color: transparent;
}
&::-webkit-scrollbar-thumb {
border-radius: 3px;
background-color: transparent;
}
&:hover {
&::-webkit-scrollbar {
background-color: var(--im-scrollbar);
}
&::-webkit-scrollbar-thumb {
background-color: var(--im-scrollbar-thumb);
}
}
&.me-scrollbar-thumb {
&::-webkit-scrollbar {
background-color: unset;
}
}
}
.ql-mention-list-container {
width: 160px;
max-height: 200px;
border: 1px solid #f0f0f0;
border-radius: 10px;
background-color: #ffffff;
box-shadow: 0 2px 12px 0 rgba(30, 30, 30, 0.08);
z-index: 9001;
overflow: auto;
padding: 3px;
padding-left: 6px;
}
html[theme-mode="dark"] {
.ql-mention-list-container {
background-color: rgb(44 44 49);
color: #fff;
border: unset;
.ql-mention-list-item {
color: var(--im-text-color-grey);
}
}
}
.ql-mention-loading {
line-height: 44px;
padding: 0 20px;
vertical-align: middle;
font-size: 16px;
}
.ql-mention-list {
list-style: none;
margin: 0;
padding: 0;
}
.ql-mention-list-item {
cursor: pointer;
line-height: 44px;
font-size: 16px;
vertical-align: middle;
padding: 0 10px;
overflow: hidden;
}
.ql-mention-list-item.disabled {
cursor: auto;
}
.ql-mention-list-item.selected {
background-color: var(--im-primary-color);
color: #fff;
text-decoration: none;
border-radius: 3px;
}
.mention {
height: 24px;
width: 65px;
border-radius: 6px;
background-color: #d3e1eb;
padding: 3px 0;
margin-right: 2px;
user-select: all;
}
.mention {
color: var(--im-primary-color);
background-color: transparent;
> span {
margin: 0 3px;
}
}
.ed-member-item {
height: 35px;
display: flex;
align-items: center;
img {
height: 22px;
width: 22px;
border-radius: 50%;
}
.nickname {
margin-left: 10px;
font-size: 13px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
html[theme-mode="dark"] {
.ql-mention-list-container {
background-color: rgb(44 44 49);
color: #fff;
border: unset;
.ql-mention-list-item {
color: var(--im-text-color-grey);
}
}
}
2024-11-22 01:06:37 +00:00
</style>