wujingling0908 6 days ago
commit 3b2dc79517
  1. 228
      src/components/chat-new.vue
  2. 6
      src/components/footer-bar-input.vue
  3. 17
      src/components/text-folding.vue

@ -17,6 +17,8 @@ import imageService from '@/utils/imageService'
import { messageSend } from '@/utils/api'
import { Post } from '@/utils/https'
import { SJHLCmdList } from '@/utils/constant'
import MeScroll from 'mescroll.js'
import 'mescroll.js/mescroll.min.css'
defineOptions({
name: 'chat-new',
@ -42,6 +44,9 @@ const sessionId = inject('sessionId', ref(''))
const userId = inject('userId', ref(''))
const isLostMessage = inject('isLostMessage', ref(null))
const mainContainerRef = ref(null)
const webSocketChannel = '95460000' // 投诉直连渠道号
// mescroll 实例(非响应式)
let mescroll = null
const appraiseShow = ref(false) // 有帮助/没帮助评价弹层
const baseHandle = ref('') // 评价弹层基础数据(含被点按钮 event)
@ -53,7 +58,6 @@ function appraiseIsClose(val, param) {
// 参考 liics/chat-main 的 tokenExist:token 获取成功后才渲染聊天主内容
const historylist = ref([])
const historyLoading = ref(false)
const historyReady = ref(true)
const introReadyCount = ref(0)
const businessHandleRef = ref(null)
@ -221,14 +225,6 @@ function getMainEl() {
return mainContainerRef.value || store?.mescrollContent || null
}
function scrollMainTo(top) {
const el = getMainEl()
if (!el) return false
const value = Math.max(0, Number(top) || 0)
el.scrollTop = value
return Math.abs((el.scrollTop || 0) - value) < 2 || value === 0
}
let smoothScrollToken = 0
function smoothScrollMainToBottom(duration = 300) {
@ -323,10 +319,6 @@ function onMainScroll(event) {
const top = event?.target?.scrollTop
if (top == null) return
persistScrollTop(top, { allowZero: true })
// 上滑到顶部附近时加载更多历史记录
if (top <= 60 && !nolist.value && !historyLoading.value) {
getChatHistory()
}
}
onBeforeRouteLeave(() => {
@ -378,105 +370,137 @@ function getHeaderUrl(context, payload) {
});
});
}
const getChatHistory = async (isAuto = false) => {
function initMescroll() {
let isLostMessageFlag =
isLostMessage.value && (isLostMessage.value == '1' || isLostMessage.value == '3') ? true : false
// 投诉直连渠道首次进入需要刷新
const isTouSuZhiLian = store.source === webSocketChannel
isLostMessageFlag = isTouSuZhiLian ? true : isLostMessageFlag
// 创建 MeScroll 对象(下拉加载历史记录,参考 chat-main)
mescroll = new MeScroll(mainContainerRef.value, {
down: {
isBounce: false,
auto: isLostMessageFlag,
callback: getChatHistory,
htmlContent: '<p class="downwarp-progress"></p><p class="downwarp-tip">没有数据了</p>',
hardwareClass: 'mescroll-hardware',
warpClass: 'mescroll-downwarp', // 下拉刷新的布局容器样式
offset: 70, // 触发刷新的距离,默认80
mustToTop: false,
autoShowLoading: true, // 自动执行下拉回调时是否显示下拉刷新进度
textInOffset: '下拉加载历史记录', // 下拉的距离在offset范围内的提示文本
textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
textLoading: '加载中 ...', // 加载中的提示文本
onMoving: upOnMoving,
},
up: {
use: false,
},
})
}
function upOnMoving() {
nolist.value = false
}
// 下拉回调 拉取历史记录
function getChatHistory() {
const params = {
page: pageNum.value,
pageSize: pageSize.value,
sessionId: sessionId.value || store?.sessionId,
userId: userId.value || store?.userId,
}
historyLoading.value = true
Post('history/chatInfo', params)
.then((response) => {
historyLoading.value = false
historyReady.value = true
if (response.code && response.code != 0) {
showToast(response.msg)
return
}
// 成功返回
if (response.data?.list?.length > 0) {
const dataList = response.data.list
pageNum.value++
// 不满一页,说明已经无更多消息
if (dataList.length < pageSize.value) {
nolist.value = true
nolistTxt.value = '没有更多数据了'
setTimeout(() => {
Post('history/chatInfo', params)
.then((response) => {
if (response.code && response.code != 0) {
showToast(response.msg)
return
}
// 生成 VIEW_ID,大写,避免污染源数据
dataList.forEach((val) => {
val.VIEW_ID = 'msg' + val.timestamp
})
// 成功返回
if (response.data?.list?.length > 0) {
const dataList = response.data.list
// 获取当前最顶部的 VIEW_ID(注意写在 concat 前面)
let topMsg = historylist.value[0]
pageNum.value++
// 先隐藏下拉刷新的状态
mescroll && mescroll.endSuccess()
// 处理隐藏的数据
if (topMsg && !topMsg.msgText) {
topMsg = historylist.value[1]
if (topMsg && !topMsg.msgText) {
topMsg = historylist.value[2]
// 不满一页,说明已经无更多消息
if (dataList.length < pageSize.value) {
nolist.value = true
nolistTxt.value = '没有更多数据了'
}
}
// 设置列表数据(历史消息也走同一套字段归一,保证 msgType=01 卡片能显示)
const normalizedList = store.normalizeHistoryMessages
? store.normalizeHistoryMessages(dataList)
: dataList
historylist.value = normalizedList.concat(historylist.value)
_.map(historylist.value, (item) => {
// 历史记录不展示数据互联
if (SJHLCmdList.includes(item.cmd) && !_.isEmpty(item.VIEW_ID)) {
item.msgText = ''
}
// 历史问句去掉 & 符号
if (item.fromFlag === 1 && item.msgText && item.msgText.indexOf('&') !== -1) {
item.msgText = item.msgText.replace(/&/g, '')
}
})
// 生成 VIEW_ID,大写,避免污染源数据
dataList.forEach((val) => {
val.VIEW_ID = 'msg' + val.timestamp
})
// 更新未读状态
updateReadStatus(dataList)
// 获取当前最顶部的 VIEW_ID(注意写在 concat 前面)
let topMsg = historylist.value[0]
nextTick(() => {
if (isAuto) {
// 参考 chat-main:有未读自动加载时滚到底部并提示未读
if (isLostMessage.value === 3) {
showToast('您有新消息未读,请及时查看历史消息')
// 处理隐藏的数据
if (topMsg && !topMsg.msgText) {
topMsg = historylist.value[1]
if (topMsg && !topMsg.msgText) {
topMsg = historylist.value[2]
}
setTimeout(() => {
scrollMainTo(99999)
}, 150)
} else if (topMsg) {
const view = document.querySelector('#' + topMsg.VIEW_ID)
console.log(topMsg.VIEW_ID + ' 节点离页面顶部的距离=' + view?.offsetTop)
setTimeout(() => {
if (view) scrollMainTo(view.offsetTop - 20)
}, 150)
} else {
// 首次手动上拉加载:停在顶部
setTimeout(() => {
scrollMainTo(0)
}, 150)
}
})
} else {
// 设置列表数据(历史消息也走同一套字段归一,保证 msgType=01 卡片能显示)
const normalizedList = store.normalizeHistoryMessages
? store.normalizeHistoryMessages(dataList)
: dataList
historylist.value = normalizedList.concat(historylist.value)
_.map(historylist.value, (item) => {
// 历史记录不展示数据互联
if (SJHLCmdList.includes(item.cmd) && !_.isEmpty(item.VIEW_ID)) {
item.msgText = ''
}
// 历史问句去掉 & 符号
if (item.fromFlag === 1 && item.msgText && item.msgText.indexOf('&') !== -1) {
item.msgText = item.msgText.replace(/&/g, '')
}
})
// 更新未读状态
updateReadStatus(dataList)
nextTick(() => {
if (pageNum.value <= 2) {
if (isLostMessage.value === 3) {
showToast('您有新消息未读,请及时查看历史消息')
}
setTimeout(() => {
mescroll && mescroll.scrollTo(99999, 0)
}, 150)
} else if (topMsg) {
// 保持顶部消息的位置
const view = document.querySelector('#' + topMsg.VIEW_ID)
console.log(topMsg.VIEW_ID + ' 节点离页面顶部的距离=' + view?.offsetTop)
setTimeout(() => {
if (view) mescroll.scrollTo(view.offsetTop - 20, 0)
}, 150)
}
})
} else {
mescroll && mescroll.endErr()
nolist.value = true
nolistTxt.value = pageNum.value === 1 ? '当前还没有您的聊天记录' : '没有更多数据了'
}
})
.catch(() => {
pageNum.value--
mescroll && mescroll.endErr()
nolist.value = true
nolistTxt.value = pageNum.value === 1 ? '当前还没有您的聊天记录' : '没有更多数据了'
}
})
.catch(() => {
// 网络异常不置 nolist,用户再次上滑可重试(chat-main 靠下拉手势重置 nolist,这里等价放开)
historyLoading.value = false
historyReady.value = true
pageNum.value--
})
})
}, 600)
}
onMounted(async () => {
getHeaderUrl()
@ -484,16 +508,12 @@ onMounted(async () => {
if (mainContainerRef.value) {
store?.setMescrollContent?.(mainContainerRef.value)
}
// 下拉加载历史记录初始化(有未读/投诉直连渠道时 down.auto 自动拉第一页,参考 chat-main)
initMescroll()
const persisted = readPersistedScrollTop()
if (persisted != null && persisted > 0) {
shouldRestoreScroll.value = true
restoreScrollPosition()
} else {
// 参考 chat-main 的 mescroll down.auto:有未读消息或投诉直连渠道时,进入页面自动加载历史
const hasUnread = isLostMessage.value == '1' || isLostMessage.value == '3'
if (hasUnread || store.source === '95460000') {
getChatHistory(true)
}
}
})
watch(
@ -512,9 +532,6 @@ watch(
<template>
<div class="chat-new">
<div class="main-container" ref="mainContainerRef" @scroll.passive="onMainScroll">
<div class="history-loading" v-if="historyLoading">
<van-loading type="spinner" size="18rem" />
</div>
<div class="empty-tips opacity" v-if="nolist">{{ nolistTxt }}</div>
<div class="historicRecords" v-if="historylist.length > 0">
<chatContent
@ -590,11 +607,6 @@ watch(
overflow-y: auto;
overflow-x: hidden;
.history-loading {
display: flex;
justify-content: center;
}
.message-con {
display: flex;
flex-direction: column;

@ -76,9 +76,9 @@
</div> -->
<!-- <span v-show="!soundFlag" class="input-text-tips">{{ txtTips }}</span> -->
<div class="input-btn-box btn-box2" style="flex-shrink: 0">
<button class="btn-item opacity" v-if="!sendFlag" :class="addFlag ? 'add-box' : 'close-box'"
:disabled="sendDisabled || elderAndOutwork" @click.stop="addUpload()"></button>
<button class="btn-item opacity send-box" v-else :disabled="sendDisabled || elderAndOutwork"
<!-- <button class="btn-item opacity" v-if="!sendFlag" :class="addFlag ? 'add-box' : 'close-box'"
:disabled="sendDisabled || elderAndOutwork" @click.stop="addUpload()"></button> -->
<button class="btn-item opacity send-box" v-if="sendFlag" :disabled="sendDisabled || elderAndOutwork"
@click.stop="clickSendMessage()"></button>
</div>
</div>

@ -103,10 +103,27 @@ const tips1 = ref('')
const tips2 = ref('')
const cmd = ref('')
// 小程序 webview 里开放标签不生效(仅公众号 H5 可渲染),改为提取标签的 path
// 用 wx.miniProgram.navigateTo 自动跳转;公众号 H5 环境仍靠用户点击开放标签
function autoLaunchWeapp() {
if (props.isHistory) return
if (!utils.isMiniProgram()) return
const raw = String(props.introduce || props.itemlist?.msgText || '')
const m = raw.match(/<wx-open-launch-weapp[^>]*\spath="([^"]+)"/)
if (!m) return
const key = `weapp_auto_jump_${props.itemlist?.timestamp || ''}`
if (sessionStorage.getItem(key)) return
sessionStorage.setItem(key, '1')
setTimeout(() => {
wx.miniProgram.navigateTo({ url: m[1] })
}, 300)
}
onMounted(() => {
processedEmoji()
descHeightChange()
cmd.value = props.itemlist.cmd
autoLaunchWeapp()
nextTick(() => {
initGlobalCallbacks()
})

Loading…
Cancel
Save