From 6517c082d53df16c3c36111399a0e426bdd98727 Mon Sep 17 00:00:00 2001 From: wangyifeng <812766448@qq.com> Date: Thu, 22 May 2025 15:24:13 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=84=E7=90=86=E6=8C=87=E5=AE=9A=E8=81=8A?= =?UTF-8?q?=E5=A4=A9=E8=AE=B0=E5=BD=95=E7=9A=84=E5=AE=9A=E4=BD=8D=E8=B7=B3?= =?UTF-8?q?=E8=BD=AC=EF=BC=8C=E6=9F=A5=E6=89=BE=E4=B8=8A=E4=B8=8B=E6=96=87?= =?UTF-8?q?=E3=80=90=E4=B8=8D=E7=A8=B3=E5=AE=9A=E3=80=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/search/searchByCondition.vue | 4 +- src/hooks/useTalkRecord.ts | 185 +++++++++++++++++- src/store/modules/dialogue.js | 13 ++ src/utils/auth.js | 2 +- src/views/message/inner/IndexContent.vue | 4 +- src/views/message/inner/IndexSider.vue | 46 +++-- .../message/inner/panel/PanelContent.vue | 170 ++++++++++++---- 7 files changed, 358 insertions(+), 66 deletions(-) diff --git a/src/components/search/searchByCondition.vue b/src/components/search/searchByCondition.vue index f8e7f39..09b0824 100644 --- a/src/components/search/searchByCondition.vue +++ b/src/components/search/searchByCondition.vue @@ -200,10 +200,10 @@ >
- + /> -->
diff --git a/src/hooks/useTalkRecord.ts b/src/hooks/useTalkRecord.ts index aee0272..880ada2 100644 --- a/src/hooks/useTalkRecord.ts +++ b/src/hooks/useTalkRecord.ts @@ -11,6 +11,17 @@ interface Params { limit: number } +interface SpecialParams extends Params { + msg_id?: string + cursor?: number + direction?: 'up' | 'down' +} + +interface LoadOptions { + specifiedMsg?: SpecialParams + middleMsgCreatedAt?: string +} + export const useTalkRecord = (uid: number) => { const dialogueStore = useDialogueStore() @@ -25,9 +36,19 @@ export const useTalkRecord = (uid: number) => { receiver_id: 0, talk_type: 0, status: 0, - cursor: 0 + cursor: 0, + specialParams: undefined as SpecialParams | undefined }) + // 重置 loadConfig + const resetLoadConfig = () => { + loadConfig.receiver_id = 0 + loadConfig.talk_type = 0 + loadConfig.status = 0 + loadConfig.cursor = 0 + loadConfig.specialParams = undefined + } + const onJumpMessage = (msgid: string) => { const element = document.getElementById(msgid) if (!element) { @@ -131,8 +152,160 @@ export const useTalkRecord = (uid: number) => { }) } + // 获取当前消息的最小 sequence + const getMinSequence = () => { + console.error('records.value', records.value) + if (!records.value.length) return 0 + console.error(Math.min(...records.value.map(item => item.sequence))) + return Math.min(...records.value.map(item => item.sequence)) + } + // 获取当前消息的最大 sequence + const getMaxSequence = () => { + if (!records.value.length) return 0 + return Math.max(...records.value.map(item => item.sequence)) + } + + /** + * 加载数据主入口,支持指定消息定位模式 + * @param params 原有参数 + * @param options 可选,{ specifiedMsg } 指定消息对象 + */ + const onLoad = (params: Params, options?: LoadOptions) => { + // 如果会话切换,重置所有状态 + if (params.talk_type !== loadConfig.talk_type || params.receiver_id !== loadConfig.receiver_id) { + resetLoadConfig() + } + + loadConfig.cursor = 0 + loadConfig.receiver_id = params.receiver_id + loadConfig.talk_type = params.talk_type + + console.error('onLoad', params, options) + + // 新增:支持指定消息定位模式,参数以传入为准合并 + if (options?.specifiedMsg?.cursor !== undefined) { + loadConfig.specialParams = { ...options.specifiedMsg } // 记录特殊参数,供分页加载用 + console.error('options', options) + loadConfig.status = 0 // 复用主流程 loading 状态 + // 以 params 为基础,合并 specifiedMsg 的所有字段(只要有就覆盖) + const contextParams = { + ...params, + ...options.specifiedMsg + } + ServeTalkRecords(contextParams).then(({ data, code }) => { + if (code !== 200) { + loadConfig.status = 2 + return + } + dialogueStore.clearDialogueRecord() + const items = (data.items || []).map((item: ITalkRecord) => formatTalkRecord(uid, item)) + dialogueStore.unshiftDialogueRecord(items.reverse()) + loadConfig.status = items.length >= contextParams.limit ? 1 : 2 + loadConfig.cursor = data.cursor + nextTick(() => { + setTimeout(() => { + const el = document.getElementById('imChatPanel') + const target = document.getElementById(options.specifiedMsg?.msg_id || '') + if (el && target) { + const containerRect = el.getBoundingClientRect() + const targetRect = target.getBoundingClientRect() + const offset = targetRect.top - containerRect.top + // 居中 + const scrollTo = el.scrollTop + offset - el.clientHeight / 2 + target.clientHeight / 2 + el.scrollTo({ top: scrollTo, behavior: 'smooth' }) + + addClass(target, 'border') + setTimeout(() => removeClass(target, 'border'), 3000) + } else if (el) { + el.scrollTop = el.scrollHeight + } + }, 50) + }) + }) + return + } + + loadConfig.specialParams = undefined // 普通模式清空 + // 原有逻辑 + load(params) + } + + // 向上加载更多(兼容特殊参数模式) const onRefreshLoad = () => { + console.error('loadConfig.status', loadConfig.status) if (loadConfig.status == 1) { + console.log('specialParams', loadConfig.specialParams) + // 判断是否是特殊参数模式 + if (loadConfig.specialParams && typeof loadConfig.specialParams === 'object') { + // 检查特殊参数是否与当前会话匹配 + if (loadConfig.specialParams.talk_type === loadConfig.talk_type && + loadConfig.specialParams.receiver_id === loadConfig.receiver_id) { + // 特殊参数模式下,direction: 'up',cursor: 当前最小 sequence + onLoad( + { + receiver_id: loadConfig.receiver_id, + talk_type: loadConfig.talk_type, + limit: 30 + }, + { + specifiedMsg: { + ...loadConfig.specialParams, + direction: 'up', + cursor: getMinSequence() + } + } + ) + } else { + // 如果不匹配,重置为普通模式 + resetLoadConfig() + load({ + receiver_id: loadConfig.receiver_id, + talk_type: loadConfig.talk_type, + limit: 30 + }) + } + } else { + // 原有逻辑 + load({ + receiver_id: loadConfig.receiver_id, + talk_type: loadConfig.talk_type, + limit: 30 + }) + } + } + } + + // 向下加载更多(兼容特殊参数模式) + const onLoadMoreDown = () => { + // 判断是否是特殊参数模式 + if (loadConfig.specialParams && typeof loadConfig.specialParams === 'object') { + // 检查特殊参数是否与当前会话匹配 + if (loadConfig.specialParams.talk_type === loadConfig.talk_type && + loadConfig.specialParams.receiver_id === loadConfig.receiver_id) { + onLoad( + { + receiver_id: loadConfig.receiver_id, + talk_type: loadConfig.talk_type, + limit: 30 + }, + { + specifiedMsg: { + ...loadConfig.specialParams, + direction: 'down', + cursor: getMaxSequence() + } + } + ) + } else { + // 如果不匹配,重置为普通模式 + resetLoadConfig() + load({ + receiver_id: loadConfig.receiver_id, + talk_type: loadConfig.talk_type, + limit: 30 + }) + } + } else { load({ receiver_id: loadConfig.receiver_id, talk_type: loadConfig.talk_type, @@ -141,13 +314,5 @@ export const useTalkRecord = (uid: number) => { } } - const onLoad = (params: Params) => { - loadConfig.cursor = 0 - loadConfig.receiver_id = params.receiver_id - loadConfig.talk_type = params.talk_type - - load(params) - } - - return { loadConfig, records, onLoad, onRefreshLoad, onJumpMessage } + return { loadConfig, records, onLoad, onRefreshLoad, onLoadMoreDown, onJumpMessage, resetLoadConfig } } diff --git a/src/store/modules/dialogue.js b/src/store/modules/dialogue.js index 509400b..7f13422 100644 --- a/src/store/modules/dialogue.js +++ b/src/store/modules/dialogue.js @@ -35,6 +35,12 @@ export const useDialogueStore = defineStore('dialogue', { // 聊天记录 records: [], + // 查询指定消息上下文的消息信息 + specifiedMsg: '', + + // 是否是手动切换会话 + isManualSwitch: false, + // 新消息提示 unreadBubble: 0, @@ -76,6 +82,7 @@ export const useDialogueStore = defineStore('dialogue', { // 更新对话信息 setDialogue(data = {}) { console.log('data', data) + this.online = data.is_online == 1 this.talk = { username: data.remark || data.name, @@ -89,6 +96,12 @@ export const useDialogueStore = defineStore('dialogue', { this.records = [] this.unreadBubble = 0 this.isShowEditor = data?.is_robot === 0 + + // 只在手动切换会话时清空 specifiedMsg + // if (this.isManualSwitch) { + // this.specifiedMsg = '' + // this.isManualSwitch = false + // } this.members = [] if (data.talk_type == 2) { diff --git a/src/utils/auth.js b/src/utils/auth.js index 605aea5..10ed3a0 100644 --- a/src/utils/auth.js +++ b/src/utils/auth.js @@ -18,7 +18,7 @@ export function isLoggedIn() { */ export function getAccessToken() { // return storage.get(AccessToken) || '' - return JSON.parse(localStorage.getItem('token'))||'79b5c732d96d2b27a48a99dfd4a5566c43aaa5796242e854ebe3ffc198d6876b9628e7b764d9af65ab5dbb2d517ced88170491b74b048c0ba827c0d3741462cb89dc59ed46653a449af837a8262941caaef1334d640773710f8cd96473bacfb190cba595a5d6a9c87d70f0999a3ebb41147213b31b4bdccffca66a56acf3baab5af0154f0dce360079f37709f78e13711036899344bddb0fb4cf0f2890287cb62c3fcbe33368caa5e213624577be8b8420ab75b1f50775ee16142a4321c5d56995f37354a66a969da98d95ba6e65d142ed097e04b411c1ebad2f62866d0ec7e1838420530a9941dbbcd00490199f8b89855de0ffae3adff28137845adce8c6b2c8160f779573f4e6553e86ac85dc8e689aa8970c6a4de122173f4db7bf7cdfa3ca7796d181a43b3fdf8ec549a8f33c82fbab7f0175efce75aca5cf6188ccf2a8' + return JSON.parse(localStorage.getItem('token'))||'79b5c732d96d2b27a48a99dfd4a5566c43aaa5796242e854ebe3ffc198d6876b9628e7b764d9af65ab5dbb2d517ced88170491b74b048c0ba827c0d3741462cb89dc59ed46653a449af837a8262941caaef1334d640773710f8cd96473bacfb190cba595a5d6a9c87d70f0999a3ebb41147213b31b4bdccffca66a56acf3baab5af0154f0dce360079f37709f78e13711036899344bddb0fb4cf0f2890287cb62c3fcbe33368caa5e213624577be8b8420ab75b1f50775ee16142a4321c5d56995f37354a66a969da98d95ba6e65d142ed097e04b411c1ebad2f62866d0ec7e1838420530a9941dbbcd00490199f8b89a6a1266874decb435c4ea86321f35d90bdc30204aed39f91f4d34d074462099ce788933c4b4c4c10e31272d7bb0f3b1d8086a4695de3f9cb4cd702f79b98dc4efd5f2b753acec3bad2d1d3a4b6c85972' } /** diff --git a/src/views/message/inner/IndexContent.vue b/src/views/message/inner/IndexContent.vue index 46b8fb9..03fd8a5 100644 --- a/src/views/message/inner/IndexContent.vue +++ b/src/views/message/inner/IndexContent.vue @@ -31,7 +31,8 @@ const talkParams = reactive({ online: computed(() => dialogueStore.online), keyboard: computed(() => dialogueStore.keyboard), num: computed(() => dialogueStore.members.length), - avatar:computed(() => dialogueStore.talk.avatar) + avatar:computed(() => dialogueStore.talk.avatar), + specifiedMsg: computed(() => dialogueStore.specifiedMsg) }) const state = reactive({ @@ -394,6 +395,7 @@ const handleGroupNoticeModalShow = (isAdmin) => { :talk_type="talkParams.type" :receiver_id="talkParams.receiver_id" :index_name="talkParams.index_name" + :specifiedMsg="talkParams.specifiedMsg" /> diff --git a/src/views/message/inner/IndexSider.vue b/src/views/message/inner/IndexSider.vue index 385e608..32097e8 100644 --- a/src/views/message/inner/IndexSider.vue +++ b/src/views/message/inner/IndexSider.vue @@ -66,21 +66,10 @@ const renderChatAppSearch = () => { return h( chatAppSearchList, { - // searchResultKey: 'user_infos', - // searchItem: { - // avatar: - // 'https://e-cdn.fontree.cn/fonchain-main/prod/image/18248/avatar/a0b2bee7-947f-465a-986e-10a1b2b87032.png', - // created_at: '2025-03-27 14:44:23', - // erp_user_id: 18248, - // id: 44, - // mobile: '18994430450', - // nickname: '周俊耀' - // }, - // searchText: '周' searchResultPageSize: 3, listLimit: true, apiRequest: ServeSeachQueryAll, - searchText: '王', + searchText: searchKeyword.value, onClickSearchItem: (searchText, searchResultKey, talk_type, receiver_id, res) => { console.log(searchText, searchResultKey, talk_type, receiver_id) const result = JSON.parse(decodeURIComponent(res)) @@ -377,6 +366,8 @@ const onTabTalk = (item: ISession, follow = false) => { searchKeyword.value = '' + dialogueStore.isManualSwitch = true + // 更新编辑信息 dialogueStore.setDialogue(item) @@ -619,6 +610,25 @@ const handleClickSearchItem = (searchText, searchResultKey, talk_type, receiver_ }) } } +//处理点击搜索结果item +const handleClickSearchResultItem = (searchText, searchResultKey, talk_type, receiver_id, res) => { + const result = JSON.parse(decodeURIComponent(res)) + console.error(result, 'result') + // 根据搜索结果, 指定用于查询指定消息上下文的sequence + dialogueStore.specifiedMsg = encodeURIComponent( + JSON.stringify({ + talk_type, + receiver_id, + msg_id: result.msg_id, + cursor: result.sequence - 15 > 0 ? result.sequence - 15 : 0, + direction: 'down', + sort_sequence: 'asc', + create_time: result.created_at + }) + ) + console.error(dialogueStore.specifiedMsg, 'dialogueStore.specifiedMsg') + talkStore.toTalk(talk_type, receiver_id, router) +} //处理点击停留item变化 const handleClickStayItemChange = (item) => { if (item) { @@ -682,6 +692,15 @@ const handleCloseSearchRecordModal = () => { const getResultTotalCount = (total) => { state.searchDetailList.total = total } + +// 进入搜索结果聊天 +const handleEnterSearchResultChat = () => { + const searchResult = JSON.parse(decodeURIComponent(state.searchDetailList.apiParams)) + talkStore.toTalk(searchResult.talk_type, searchResult.receiver_id, router) + state.isShowSearchRecordModal = false + state.searchRecordText = '' + searchKeyword.value = '' +}