wew
This commit is contained in:
parent
4e87f437e1
commit
66fabbf475
@ -41,3 +41,14 @@ export const send_template_message = (data) => {
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 查看小程序状态
|
||||
|
||||
export const checkMiniProgramStatus = (params) => {
|
||||
return request({
|
||||
url: `admin/app/check_status`,
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
@ -2,7 +2,7 @@
|
||||
<div style="width: 100%;">
|
||||
<!-- <el-input v-model="centent" suffix-icon="Search" @input="inputSearch" placeholder="搜索"></el-input> -->
|
||||
<ul v-infinite-scroll="load" class="infinite-list" style="overflow: auto" :style="'height:' + height" :infinite-scroll-immediate="false" infinite-scroll-distance="5">
|
||||
<li v-for="item in cardlist" :key="item.sender_id" class="infinite-list-item" :class="{ active: item.active }"
|
||||
<li v-for="item in cardlist" :key="item.sender_id" class="infinite-list-item" :class="{ active: item.sender_id == activeId }"
|
||||
@click="handleItem(item)">
|
||||
<div class="avatar-container">
|
||||
<el-image class="user-avatar" :src="item.sender_avatar"></el-image>
|
||||
@ -65,8 +65,9 @@ const inputSearch = (e) => {
|
||||
emits('input', e)
|
||||
}, 500)
|
||||
}
|
||||
|
||||
const activeId = ref(null)
|
||||
const handleItem = (row) => {
|
||||
activeId.value = row.sender_id
|
||||
if (props.multiple) {
|
||||
if (!row.active) {
|
||||
row.active = true
|
||||
@ -272,7 +273,7 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: var(--el-color-primary-light-7);
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
}
|
||||
|
||||
.infinite-list .infinite-list-item+.list-item {
|
||||
|
||||
@ -1,16 +1,33 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import Setting from '@/layout/tabbar/setting/index.vue'
|
||||
import appDetail from '@/store/modules/appDetail'
|
||||
const appInfoStore = appDetail()
|
||||
|
||||
// ensure route/router are defined
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
console.log('appInfoStore', appInfoStore);
|
||||
const appStatus = computed(() => {
|
||||
// guard access and provide fallback
|
||||
try {
|
||||
return appInfoStore.appDetail?.check_status_text || ''
|
||||
} catch (e) {
|
||||
return ''
|
||||
}
|
||||
})
|
||||
|
||||
const newPath = computed(() => {
|
||||
return route.path
|
||||
})
|
||||
const toLink = (path) => {
|
||||
router.push(path)
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -25,7 +42,8 @@ const toLink = (path) => {
|
||||
小程序聊天系统
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="nav">
|
||||
<div class="nav" v-if="newPath == '/chat'">
|
||||
状态:{{ appStatus }}
|
||||
<!-- <ul><li @click="toLink('/dashboard')" :class="{ active: newPath == '/dashboard' }">首页</li></ul> -->
|
||||
</div>
|
||||
|
||||
@ -70,6 +88,7 @@ const toLink = (path) => {
|
||||
padding-left: 10px;
|
||||
width: calc(100% - 300px);
|
||||
.nav{
|
||||
padding-left: 10px;
|
||||
li{
|
||||
display: inline-block;
|
||||
padding: 0 12px;
|
||||
|
||||
24
src/store/modules/appDetail.ts
Normal file
24
src/store/modules/appDetail.ts
Normal file
@ -0,0 +1,24 @@
|
||||
// 机器人相关仓库
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
|
||||
const appDetail = defineStore(
|
||||
'appDetail',
|
||||
() => {
|
||||
|
||||
const appDetail = ref({
|
||||
check_status_text: ''
|
||||
})
|
||||
|
||||
|
||||
return {
|
||||
appDetail
|
||||
}
|
||||
},
|
||||
{
|
||||
persist: true
|
||||
}
|
||||
)
|
||||
|
||||
export default appDetail
|
||||
@ -428,4 +428,9 @@ span[class=el-tooltip__trigger] {
|
||||
.upload-button{
|
||||
padding-top: 0!important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-image__error{
|
||||
font-size: 12px!important;
|
||||
line-height: 14px;
|
||||
}
|
||||
@ -1,19 +1,22 @@
|
||||
<template>
|
||||
<div class="chat-page">
|
||||
<div class="left-panel">
|
||||
<WxUserCard @load="onLoadUserList" :cardlist="userList" height="calc(100vh - 120px)" @change="onSelectUser" />
|
||||
<WxUserCard @load="onLoadUserList" :cardlist="userList" height="calc(100vh - 120px)"
|
||||
@change="onSelectUser" />
|
||||
</div>
|
||||
<div class="right-panel">
|
||||
<div :class="['chat-body', { 'chat-body--loading': isLoadingMessages }]" ref="chatBody" @scroll="handleScroll">
|
||||
<div :class="['chat-body', { 'chat-body--loading': isLoadingMessages }]" ref="chatBody"
|
||||
@scroll="handleScroll">
|
||||
<!-- 加载更多提示 -->
|
||||
<div v-if="loadingMore" class="loading-more">加载中...</div>
|
||||
|
||||
|
||||
<WxChatRecord :msgList="messages" :sendeInfo="sendeInfo" msgType="user" @retry="handleRetry" />
|
||||
</div>
|
||||
|
||||
<div class="chat-footer">
|
||||
<div class="footer-left" style="">
|
||||
<div class="upload-row" style="display:flex; align-items:center; gap:12px;margin-left: 6px;position: relative; top: 10px;">
|
||||
<div class="upload-row"
|
||||
style="display:flex; align-items:center; gap:12px;margin-left: 6px;position: relative; top: 10px;">
|
||||
<el-icon style="font-size:24px;cursor:pointer" @click="triggerImageInput">
|
||||
<svg class="icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-tupian1"></use>
|
||||
@ -28,21 +31,11 @@
|
||||
</div>
|
||||
<!-- 发送按钮 -->
|
||||
<div class="send-button-container" style="margin-left: 12px;">
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="send"
|
||||
:disabled="!draft.trim() || isSending || isUploading"
|
||||
:loading="isSending"
|
||||
style="height: 60px;"
|
||||
>
|
||||
<el-button type="primary" @click="send" :disabled="!draft.trim() || isSending || isUploading"
|
||||
:loading="isSending" style="height: 60px;">
|
||||
{{ isSending ? '发送中...' : '发送' }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="sendTemplateMessage"
|
||||
:loading="isSending2"
|
||||
style="height: 60px;"
|
||||
>
|
||||
<el-button type="primary" @click="sendTemplateMessage" :loading="isSending2" style="height: 60px;">
|
||||
{{ isSending2 ? '发送中...' : '发送模板消息' }}
|
||||
</el-button>
|
||||
</div>
|
||||
@ -58,10 +51,12 @@ import WxUserCard from '@/components/UserCard/index.vue'
|
||||
import UserMessage from '@/components/UserMessage/index.vue'
|
||||
import mihoutai from '@/assets/images/mihoutai.png'
|
||||
import V3Emoji from "vue3-emoji";
|
||||
import { getUserList, send_message, get_messages, send_template_message } from '@/api/chat'
|
||||
import { getUserList, send_message, get_messages, send_template_message, checkMiniProgramStatus } from '@/api/chat'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { uploadFile } from '@/api/upload'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import appDetail from '@/store/modules/appDetail'
|
||||
const appInfoStore = appDetail()
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
@ -101,6 +96,8 @@ const messageTimer = ref(null) // 定时器实例
|
||||
const isSending = ref(false) // 防止重复发送
|
||||
const lastSendTime = ref(0) // 最后发送时间,用于防抖
|
||||
const isUploading = ref(false) // 防止重复上传
|
||||
// 小程序状态轮询定时器
|
||||
const appStatusTimer = ref(null)
|
||||
|
||||
// 选择用户
|
||||
const onSelectUser = async (id) => {
|
||||
@ -149,12 +146,12 @@ const sortMessagesByTime = (messages) => {
|
||||
const ts = new Date(msg.timestamp).getTime()
|
||||
if (!isNaN(ts)) return ts
|
||||
}
|
||||
|
||||
|
||||
if (msg.created_at) {
|
||||
const ts = new Date(msg.created_at).getTime()
|
||||
if (!isNaN(ts)) return ts
|
||||
}
|
||||
|
||||
|
||||
// 处理send_time字段
|
||||
if (msg.send_time) {
|
||||
// 标准时间格式:2025-10-22 01:52:19
|
||||
@ -172,13 +169,13 @@ const sortMessagesByTime = (messages) => {
|
||||
const days = parseInt(msg.send_time.match(/(\d+)天前/)?.[1] || '0')
|
||||
return Date.now() - (days * 24 * 60 * 60 * 1000)
|
||||
}
|
||||
|
||||
|
||||
// 尝试解析标准时间格式
|
||||
const timestamp = new Date(msg.send_time).getTime()
|
||||
if (!isNaN(timestamp)) return timestamp
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 如果有ID,尝试从ID中提取时间信息
|
||||
if (msg.id || msg._id || msg.message_id) {
|
||||
const id = String(msg.id || msg._id || msg.message_id)
|
||||
@ -196,23 +193,23 @@ const sortMessagesByTime = (messages) => {
|
||||
const timestampMatch = id.match(/(\d{13})/) // 13位时间戳
|
||||
if (timestampMatch) return parseInt(timestampMatch[1])
|
||||
}
|
||||
|
||||
|
||||
// 默认返回当前时间
|
||||
return Date.now()
|
||||
}
|
||||
|
||||
|
||||
return getTimestamp(a) - getTimestamp(b)
|
||||
})
|
||||
|
||||
|
||||
// 调试信息:显示排序后的消息时间
|
||||
console.log('消息排序结果:', sorted.map(m => ({
|
||||
id: m.id || m._id || m.message_id,
|
||||
send_time: m.send_time,
|
||||
content: typeof m.content === 'object' ?
|
||||
(m.content.message || JSON.stringify(m.content)) :
|
||||
content: typeof m.content === 'object' ?
|
||||
(m.content.message || JSON.stringify(m.content)) :
|
||||
String(m.content).slice(0, 20) + '...'
|
||||
})))
|
||||
|
||||
|
||||
return sorted
|
||||
}
|
||||
|
||||
@ -229,7 +226,7 @@ const getMessages = async (isHistory = true, pageOverride = null) => {
|
||||
if (!isPolling) {
|
||||
loadingMore.value = true
|
||||
}
|
||||
|
||||
|
||||
// 在第一次加载(page=1 且 isHistory=true)期间也将 isLoadingMessages 保持为 true
|
||||
if ((pageOverride ?? msgQuery.page) === 1 && isHistory) {
|
||||
isLoadingMessages.value = true
|
||||
@ -241,18 +238,18 @@ const getMessages = async (isHistory = true, pageOverride = null) => {
|
||||
const res = await get_messages({ ...query })
|
||||
msgTotal = res.total || 0
|
||||
const list = res.list || []
|
||||
|
||||
|
||||
console.log(`获取消息 - isHistory: ${isHistory}, page: ${queryPage}, 返回消息数: ${list.length}`)
|
||||
|
||||
if (list.length > 0) {
|
||||
const newMessages = list.map(item => {
|
||||
// 处理消息内容,确保与ChatRecord组件兼容
|
||||
try {
|
||||
try {
|
||||
const text = JSON.parse(item.content)
|
||||
item.content = {
|
||||
message: text.message || text.messages
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
} catch (e) {
|
||||
// 如果解析失败,说明是纯字符串,包装为对象格式
|
||||
item.content = { message: item.content }
|
||||
}
|
||||
@ -282,28 +279,28 @@ const getMessages = async (isHistory = true, pageOverride = null) => {
|
||||
// 拉取最新消息(追加到末尾),改进去重逻辑
|
||||
const currentMessages = [...messages.value]
|
||||
let hasUpdates = false
|
||||
|
||||
|
||||
// 处理每条新消息
|
||||
for (const newMsg of newMessages) {
|
||||
let isProcessed = false
|
||||
|
||||
|
||||
// 1. 首先尝试匹配临时消息(基于内容和时间)
|
||||
const tempMsgIndex = currentMessages.findIndex(existingMsg => {
|
||||
if (!existingMsg._isTemp || existingMsg._failed) return false
|
||||
|
||||
|
||||
// 检查发送者和消息类型
|
||||
const senderMatch = existingMsg.sender_id === newMsg.sender_id
|
||||
const typeMatch = existingMsg.msg_type === newMsg.msg_type
|
||||
|
||||
|
||||
// 检查时间差(10秒内)
|
||||
const timeDiff = Math.abs(Date.now() - (existingMsg._timestamp || 0))
|
||||
const timeMatch = timeDiff < 10000
|
||||
|
||||
|
||||
// 根据消息类型进行不同的内容匹配
|
||||
let contentMatch = false
|
||||
if (existingMsg.msg_type === 1) {
|
||||
// 文本消息:比较文本内容
|
||||
const existingContent = existingMsg._tempContent ||
|
||||
const existingContent = existingMsg._tempContent ||
|
||||
(typeof existingMsg.content === 'object' ? existingMsg.content.message : existingMsg.content)
|
||||
const newContent = typeof newMsg.content === 'object' ? newMsg.content.message : newMsg.content
|
||||
contentMatch = existingContent === newContent
|
||||
@ -317,10 +314,10 @@ const getMessages = async (isHistory = true, pageOverride = null) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return senderMatch && typeMatch && contentMatch && timeMatch
|
||||
})
|
||||
|
||||
|
||||
if (tempMsgIndex !== -1) {
|
||||
// 找到匹配的临时消息,替换它
|
||||
console.log('找到匹配的临时消息,进行替换:', {
|
||||
@ -337,31 +334,31 @@ const getMessages = async (isHistory = true, pageOverride = null) => {
|
||||
// 2. 检查是否已存在相同的真实消息(基于ID)
|
||||
const existingMsgIndex = currentMessages.findIndex(existingMsg => {
|
||||
if (existingMsg._isTemp) return false // 跳过临时消息
|
||||
|
||||
|
||||
// 优先使用真实ID字段进行匹配
|
||||
const existingId = existingMsg.id || existingMsg._id || existingMsg.message_id
|
||||
const newId = newMsg.id || newMsg._id || newMsg.message_id
|
||||
|
||||
|
||||
if (existingId && newId && existingId === newId) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
// 如果没有ID,使用多字段组合进行匹配
|
||||
const existingContentStr = typeof existingMsg.content === 'object' ?
|
||||
(existingMsg.content.message || JSON.stringify(existingMsg.content)) :
|
||||
const existingContentStr = typeof existingMsg.content === 'object' ?
|
||||
(existingMsg.content.message || JSON.stringify(existingMsg.content)) :
|
||||
String(existingMsg.content)
|
||||
const newContentStr = typeof newMsg.content === 'object' ?
|
||||
(newMsg.content.message || JSON.stringify(newMsg.content)) :
|
||||
const newContentStr = typeof newMsg.content === 'object' ?
|
||||
(newMsg.content.message || JSON.stringify(newMsg.content)) :
|
||||
String(newMsg.content)
|
||||
|
||||
|
||||
const compositeMatch = existingMsg.sender_id === newMsg.sender_id &&
|
||||
existingMsg.send_time === newMsg.send_time &&
|
||||
existingMsg.msg_type === newMsg.msg_type &&
|
||||
existingContentStr === newContentStr
|
||||
|
||||
|
||||
return compositeMatch
|
||||
})
|
||||
|
||||
|
||||
if (existingMsgIndex === -1) {
|
||||
// 3. 真正的新消息,添加到列表
|
||||
console.log('发现新消息:', {
|
||||
@ -378,7 +375,7 @@ const getMessages = async (isHistory = true, pageOverride = null) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (hasUpdates) {
|
||||
// 重新排序并更新消息列表
|
||||
messages.value = sortMessagesByTime(currentMessages)
|
||||
@ -411,7 +408,7 @@ const getMessages = async (isHistory = true, pageOverride = null) => {
|
||||
if (!isPolling) {
|
||||
loadingMore.value = false
|
||||
}
|
||||
|
||||
|
||||
// 如果这是第一页历史加载,确保 loading 标志被清理(页面外也会在调用者 finally 中清理)
|
||||
if ((pageOverride ?? msgQuery.page) === 1 && isHistory) {
|
||||
isLoadingMessages.value = false
|
||||
@ -470,32 +467,32 @@ const loadMoreMessages = () => {
|
||||
const send = async () => {
|
||||
const content = draft.value.trim()
|
||||
if (!content || isSending.value || isUploading.value) return
|
||||
|
||||
|
||||
// 防抖检查
|
||||
const now = Date.now()
|
||||
if (now - lastSendTime.value < 500) {
|
||||
console.log('发送过于频繁,已忽略')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// 检查是否有相同内容的发送中消息
|
||||
const hasSendingMessage = messages.value.some(msg =>
|
||||
msg._sending &&
|
||||
msg.sender_id === '888888' &&
|
||||
const hasSendingMessage = messages.value.some(msg =>
|
||||
msg._sending &&
|
||||
msg.sender_id === '888888' &&
|
||||
msg.msg_type === 1 &&
|
||||
typeof msg.content === 'object' &&
|
||||
typeof msg.content === 'object' &&
|
||||
msg.content.message === content &&
|
||||
(now - (msg._timestamp || 0)) < 5000
|
||||
)
|
||||
|
||||
|
||||
if (hasSendingMessage) {
|
||||
console.log('存在相同内容的发送中消息,已忽略')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
isSending.value = true
|
||||
lastSendTime.value = now
|
||||
|
||||
|
||||
// 生成临时消息ID,使用特殊前缀标识
|
||||
const tempId = `temp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
||||
const msg = {
|
||||
@ -511,14 +508,14 @@ const send = async () => {
|
||||
_timestamp: now,
|
||||
_tempContent: content // 保存原始内容用于匹配
|
||||
}
|
||||
|
||||
|
||||
messages.value.push(msg)
|
||||
draft.value = ''
|
||||
|
||||
|
||||
// 滚动到底部
|
||||
setTimeout(() => scrollToBottom(), 100)
|
||||
isAutoScroll.value = true
|
||||
|
||||
|
||||
// 发送到服务器
|
||||
send_message({
|
||||
app_id: route.query.app_id,
|
||||
@ -531,7 +528,7 @@ const send = async () => {
|
||||
msg._sending = false
|
||||
msg._failed = false
|
||||
console.log('消息发送成功', response)
|
||||
|
||||
|
||||
// 发送成功后,立即轮询获取最新消息以获取真实ID
|
||||
setTimeout(() => {
|
||||
getMessages(false, 1) // 轮询最新消息
|
||||
@ -566,21 +563,21 @@ const triggerImageInput = () => {
|
||||
const handleImageChange = (e) => {
|
||||
const file = e.target.files[0]
|
||||
if (!file) return
|
||||
|
||||
|
||||
// 防止重复上传
|
||||
if (isUploading.value) {
|
||||
console.log('图片正在上传中,请稍候')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// 检查文件大小(限制为10MB)
|
||||
if (file.size > 10 * 1024 * 1024) {
|
||||
ElMessage({ type: 'error', message: '图片大小不能超过10MB' })
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
isUploading.value = true
|
||||
|
||||
|
||||
// 本地预览
|
||||
const url = URL.createObjectURL(file)
|
||||
const tempId = `temp_img_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
||||
@ -600,11 +597,11 @@ const handleImageChange = (e) => {
|
||||
}
|
||||
messages.value.push(msg)
|
||||
e.target.value = ''
|
||||
|
||||
|
||||
// 滚动到底部
|
||||
setTimeout(() => scrollToBottom(), 100)
|
||||
isAutoScroll.value = true
|
||||
|
||||
|
||||
// 上传文件
|
||||
const form = new FormData()
|
||||
form.append('file', file)
|
||||
@ -614,7 +611,7 @@ const handleImageChange = (e) => {
|
||||
uploadFile(form, import.meta.env.VITE_APP_BASE_API + 'admin/upload/image').then((resp) => {
|
||||
// 上传成功,调用send_message接口发送图片消息
|
||||
const imageUrl = import.meta.env.VITE_APP_BASE_API_img + resp.preview_image_url.replace('/api/', '')
|
||||
|
||||
|
||||
send_message({
|
||||
app_id: route.query.app_id,
|
||||
msg_type: 2,
|
||||
@ -626,12 +623,12 @@ const handleImageChange = (e) => {
|
||||
if (index !== -1) {
|
||||
messages.value.splice(index, 1)
|
||||
}
|
||||
|
||||
|
||||
// 清理本地预览URL
|
||||
if (url.startsWith('blob:')) {
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
|
||||
// 轮询获取最新消息
|
||||
setTimeout(() => {
|
||||
getMessages(false, 1)
|
||||
@ -661,22 +658,22 @@ const handleImageChange = (e) => {
|
||||
const handleRetry = (id) => {
|
||||
const msg = messages.value.find((m) => m._id === id)
|
||||
if (!msg) return
|
||||
|
||||
|
||||
msg._failed = false
|
||||
msg._sending = true
|
||||
|
||||
|
||||
if (msg.msg_type === 0) {
|
||||
send_message({
|
||||
app_id: route.query.app_id,
|
||||
content: msg.content.content,
|
||||
msg_type: 1,
|
||||
to_user_id: activeUser.value.sender_id
|
||||
}).then(() => {
|
||||
msg._sending = false
|
||||
}).catch(() => {
|
||||
msg._sending = false;
|
||||
msg._failed = true;
|
||||
ElMessage({ type: 'error', message: '重发失败' })
|
||||
send_message({
|
||||
app_id: route.query.app_id,
|
||||
content: msg.content.content,
|
||||
msg_type: 1,
|
||||
to_user_id: activeUser.value.sender_id
|
||||
}).then(() => {
|
||||
msg._sending = false
|
||||
}).catch(() => {
|
||||
msg._sending = false;
|
||||
msg._failed = true;
|
||||
ElMessage({ type: 'error', message: '重发失败' })
|
||||
})
|
||||
} else if (msg.msg_type === 2) {
|
||||
const form = new FormData()
|
||||
@ -684,12 +681,12 @@ const handleRetry = (id) => {
|
||||
form.append('app_id', route.query.app_id)
|
||||
form.append('msg_type', 2)
|
||||
form.append('to_user_id', activeUser.value.sender_id)
|
||||
send_message(form).then(() => {
|
||||
msg._sending = false
|
||||
}).catch(() => {
|
||||
msg._sending = false;
|
||||
msg._failed = true;
|
||||
ElMessage({ type: 'error', message: '重发失败' })
|
||||
send_message(form).then(() => {
|
||||
msg._sending = false
|
||||
}).catch(() => {
|
||||
msg._sending = false;
|
||||
msg._failed = true;
|
||||
ElMessage({ type: 'error', message: '重发失败' })
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -784,37 +781,37 @@ const stopUserListTimer = () => {
|
||||
// 格式化最新消息内容
|
||||
const formatLastMessage = (user) => {
|
||||
if (!user.content) return ''
|
||||
|
||||
|
||||
try {
|
||||
// 尝试解析JSON格式的content
|
||||
const contentObj = JSON.parse(user.content)
|
||||
|
||||
|
||||
// 优先返回messages字段
|
||||
if (contentObj.messages) {
|
||||
const content = String(contentObj.messages).slice(0, 30)
|
||||
return content.length > 30 ? content + '...' : content
|
||||
}
|
||||
|
||||
|
||||
// 其次返回text字段
|
||||
if (contentObj.text) {
|
||||
const content = String(contentObj.text).slice(0, 30)
|
||||
return content.length > 30 ? content + '...' : content
|
||||
}
|
||||
|
||||
|
||||
// 最后返回content字段
|
||||
if (contentObj.content) {
|
||||
const content = String(contentObj.content).slice(0, 30)
|
||||
return content.length > 30 ? content + '...' : content
|
||||
}
|
||||
|
||||
|
||||
// 如果都没有,返回JSON字符串的前30个字符
|
||||
const content = JSON.stringify(contentObj).slice(0, 30)
|
||||
return content.length > 30 ? content + '...' : content
|
||||
|
||||
|
||||
} catch (error) {
|
||||
// JSON解析失败,说明是纯字符串
|
||||
let content = String(user.content)
|
||||
|
||||
|
||||
// 根据消息类型添加前缀
|
||||
if (user.msg_type === 2) {
|
||||
content = '[图片] ' + content
|
||||
@ -825,7 +822,7 @@ const formatLastMessage = (user) => {
|
||||
} else if (user.msg_type === 5) {
|
||||
content = '[视频] ' + content
|
||||
}
|
||||
|
||||
|
||||
// 限制长度
|
||||
content = content.slice(0, 30)
|
||||
return content.length > 30 ? content + '...' : content
|
||||
@ -855,25 +852,68 @@ const sendTemplateMessage = async (templateId) => {
|
||||
"app_secret": route.query.app_secret,
|
||||
"template_id": route.query.template_id,
|
||||
"touser": activeUser.value.sender_id
|
||||
}).then((res) => {
|
||||
if (res.succes == true) {
|
||||
ElMessage({ type: 'success', message: '模板消息发送成功' })
|
||||
// 发送成功后,立即轮询获取最新消息以获取真实ID
|
||||
setTimeout(() => {
|
||||
getMessages(false, 1) // 轮询最新消息
|
||||
}, 500)
|
||||
} else {
|
||||
ElMessage({ type: 'error', message: res.message || '模板消息发送失败' })
|
||||
}
|
||||
|
||||
})
|
||||
ElMessage({ type: 'success', message: '模板消息发送成功' })
|
||||
|
||||
// 发送成功后,立即轮询获取最新消息以获取真实ID
|
||||
setTimeout(() => {
|
||||
getMessages(false, 1) // 轮询最新消息
|
||||
}, 500)
|
||||
|
||||
} catch (error) {
|
||||
console.error('模板消息发送失败:', error)
|
||||
ElMessage({ type: 'error', message: '模板消息发送失败' })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const getAppStaus = async () => {
|
||||
try {
|
||||
const res = await checkMiniProgramStatus({
|
||||
app_id: route.query.app_id
|
||||
})
|
||||
// appInfoStore.appDetail 是一个 ref,使用 .value 同步
|
||||
if (appInfoStore && appInfoStore.appDetail) {
|
||||
appInfoStore.appDetail.check_status_text = res.check_status_text || ''
|
||||
}
|
||||
return res
|
||||
} catch (error) {
|
||||
if (appInfoStore && appInfoStore.appDetail) {
|
||||
appInfoStore.appDetail.check_status_text = ''
|
||||
}
|
||||
console.error('获取应用状态失败:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const startAppStatusTimer = () => {
|
||||
if (appStatusTimer.value) clearInterval(appStatusTimer.value)
|
||||
// 立即触发一次,然后每60秒触发
|
||||
getAppStaus()
|
||||
appStatusTimer.value = setInterval(() => {
|
||||
if (!route.query.app_id) return
|
||||
getAppStaus()
|
||||
}, 60 * 1000)
|
||||
}
|
||||
|
||||
const stopAppStatusTimer = () => {
|
||||
if (appStatusTimer.value) {
|
||||
clearInterval(appStatusTimer.value)
|
||||
appStatusTimer.value = null
|
||||
}
|
||||
}
|
||||
// 组件挂载时
|
||||
onMounted(() => {
|
||||
getUsers()
|
||||
getAppStaus()
|
||||
// 启动用户列表轮询
|
||||
startUserListTimer()
|
||||
|
||||
|
||||
// 监听窗口关闭,清理定时器
|
||||
window.addEventListener('beforeunload', () => {
|
||||
stopMessageTimer()
|
||||
@ -984,24 +1024,30 @@ watch(messages, async (newVal, oldVal) => {
|
||||
/* For WebKit-based browsers (Chrome, Safari, Edge Chromium) */
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.chat-body::-webkit-scrollbar {
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
/* For Firefox */
|
||||
.chat-body {
|
||||
scrollbar-width: none; /* Firefox */
|
||||
scrollbar-width: none;
|
||||
/* Firefox */
|
||||
}
|
||||
|
||||
/* For IE and older Edge */
|
||||
.chat-body {
|
||||
-ms-overflow-style: none; /* IE 10+ */
|
||||
-ms-overflow-style: none;
|
||||
/* IE 10+ */
|
||||
}
|
||||
|
||||
/* When loading messages during user switch, hide visual content to avoid jump/flicker */
|
||||
.chat-body--loading {
|
||||
opacity: 0;
|
||||
transition: opacity 180ms ease-in-out;
|
||||
pointer-events: none; /* prevent interaction during transient loading */
|
||||
pointer-events: none;
|
||||
/* prevent interaction during transient loading */
|
||||
}
|
||||
|
||||
// 加载更多样式
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user