Files
QCN_rider/.svn/pristine/6c/6cd54d70ca0a52fe79b0f66ebacf9a963c3e6da9.svn-base
2026-02-12 12:19:20 +08:00

464 lines
9.9 KiB
Plaintext

<template>
<view class="chat-page">
<!-- 全新方案:纯 CSS 手动构建的骨架屏 -->
<view v-if="loading" class="skeleton-wrapper">
<view class="skeleton-item skeleton-rect" style="height: 80rpx; margin: 0 30rpx 30rpx;"></view>
<view class="skeleton-item skeleton-text" style="width: 200rpx; height: 40rpx; margin: 0 auto 40rpx;">
</view>
<view style="padding: 0 30rpx;">
<view style="display: flex; gap: 20rpx; margin-bottom: 40rpx;">
<view class="skeleton-item skeleton-rect" style="width: 70%; height: 80rpx;"></view>
</view>
<view style="display: flex; justify-content: flex-end; gap: 20rpx; margin-bottom: 40rpx;">
<view class="skeleton-item skeleton-rect" style="width: 40%; height: 80rpx;"></view>
</view>
<view style="display: flex; gap: 20rpx; margin-bottom: 40rpx;">
<view class="skeleton-item skeleton-rect" style="width: 80%; height: 80rpx;"></view>
</view>
</view>
<view class="skeleton-footer">
<view class="skeleton-item skeleton-circle" style="width: 64rpx; height: 64rpx;"></view>
<view class="skeleton-item skeleton-rect" style="flex: 1; height: 64rpx;"></view>
<view class="skeleton-item skeleton-rect" style="width: 120rpx; height: 64rpx;"></view>
</view>
</view>
<!-- 页面实际内容 -->
<view v-else class="page-content">
<!-- 聊天内容区域 -->
<scroll-view class="chat-scroll-view" scroll-y :scroll-top="scrollTop" scroll-with-animation>
<view class="message-list">
<view class="message-wrapper" v-for="(msg, index) in messages" :key="index"
:class="`type-${msg.codeType}`">
<!-- 系统消息 -->
<view v-if="msg.type === 'system'" class="system-message">
{{ msg.content }}
</view>
<!-- 其他消息 -->
<view v-else class="bubble-wrapper">
<text class="sender-name">{{ msg.sender }}</text>
<view class="message-bubble">
<view class="" v-if="msg.type=='Txt'">
{{ msg.content }}
</view>
<view class="" v-else>
<image :src="Service.GetMateUrlByImg(msg.media)" mode=""></image>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
<!-- 底部输入栏 -->
<view class="input-bar">
<!-- <up-icon name="plus-circle" size="28" color="#666"></up-icon> -->
<view class="" @click="upImg()">
<up-icon name="photo" size="28" color="#666"></up-icon>
</view>
<view class="input-wrapper" style=" padding: 10rpx 20rpx; background-color: #fff;">
<up-input v-model="inputValue" placeholder="输入消息" border="none"></up-input>
</view>
<button class="send-btn" @click="sendMessage">发送</button>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, reactive, nextTick } from 'vue';
import { onLoad, onShow , onBackPress} from '@dcloudio/uni-app';
import { Service, ImConnectService } from "@/Service/Im/ImConnectService";
const loading = ref<boolean>(true);
const inputValue = ref('');
// 聊天消息列表
const messages = ref([]);
let orderId = ref<String>('')
let user = ref<any>({
nick:'骑手',
id:Service.GetUserClientId(),
userType:3
})
let scrollTop = ref(0);
onLoad((Data) => {
if (Data.orderId) {
orderId.value = Data.orderId
// 启动监听
uni.$on(`chat_${orderId.value}`, (data) => {
msgData(data)
});
getData()
} else {
Service.Msg('未查询到订单!')
}
});
onBackPress((options) => {
// 离开聊天室
ImConnectService.ExitChat(Service.GetUserClientId(), orderId.value).then(res => {
// 关闭接收推送
uni.$off(`chat_${orderId.value}`);
});
return false;
});
// ✅ 核心新增函数:滚动到底部
const scrollToBottom = () => {
// 使用 nextTick 确保在 DOM 更新后再执行
nextTick(() => {
const query = uni.createSelectorQuery();
// 选择 scroll-view 内部的根容器
query.select('.message-list').boundingClientRect(data => {
if (data) {
// data.height 就是内容的总高度
// 将 scrollTop 设置为一个非常大的值,即可确保滚动到底部
scrollTop.value = data.height;
}
}).exec();
});
};
const getData = () => {
// 加入聊天室
ImConnectService.JoinChat(Service.GetUserClientId(), orderId.value).then(res => {
if (res.code == 0) {
} else {
Service.Msg(res.msg)
}
})
ImConnectService.GetOrderMessage(orderId.value).then(res=>{
loading.value = false;
if(res.code==0){
messages.value = []
res.data.msgList.forEach(item=>{
let obj = { codeType: 'incoming', content: item.msg, sender:item.code == 'Rider'?'我': item.nick,type:item.type ,media: item.media}
if (item.code != 'Rider') {
obj.codeType = 'incoming'
}else{
obj.codeType = 'outgoing'
}
messages.value.push(obj)
scrollToBottom()
})
}else{
Service.Msg(res.msg)
}
})
}
// 消息处理
const msgData = (data : any) => {
let obj = { codeType: 'incoming', content: data.msg, sender: data.userData.nick,type:data.type ,media: data.media}
if(data.userData.nick!='骑手'){
obj.codeType = 'incoming'
}else{
obj.codeType = 'outgoing'
}
messages.value.push(obj)
scrollToBottom()
}
// // 常见问题列表
// const quickReplies = reactive([
// '已到店,准备取餐',
// '餐已取,正在配送',
// '已到楼下',
// ]);
// 发送消息
const sendMessage = () => {
if (!inputValue.value.trim()) return;
ImConnectService.SendChanMsg(Service.GetUserClientId(),JSON.stringify(user.value) ,orderId.value,'Txt',inputValue.value,'').then(res=>{
if(res.code==0){
messages.value.push({ codeType: 'outgoing', content: inputValue.value, sender: '我',type:'Txt' ,media: '' })
scrollToBottom()
inputValue.value = '';
}else{
Service.Msg(res.msg)
}
})
};
const upImg = () =>{
uni.chooseImage({
count: 1, // 最多选择1张图片
sizeType: ['original', 'compressed'], // 支持原图和压缩图
sourceType: ['album', 'camera'], // 可从相册选择或使用相机拍照
success: function (res) {
let path = res.tempFiles[0].path
Service.uploadH5(path, 'OrderService', data => {
ImConnectService.SendChanMsg(Service.GetUserClientId(),JSON.stringify(user.value) ,orderId.value,'Image','imag',data).then(resdata=>{
if(resdata.code==0){
messages.value.push({ codeType: 'outgoing', content: '', sender: '我',type:'Image' ,media: data })
scrollToBottom()
}else{
Service.Msg(res.msg)
}
})
})
},
fail: function (err) {
console.error('选择失败:', err.errMsg);
}
})
}
onShow(() => { });
</script>
<style lang="scss" scoped>
@keyframes skeleton-blink {
0% {
background-position: 100% 50%;
}
100% {
background-position: 0 50%;
}
}
.skeleton-item {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-blink 1.5s infinite linear;
}
.skeleton-rect {
border-radius: 8rpx;
}
.skeleton-text {
border-radius: 4rpx;
}
.skeleton-circle {
border-radius: 50%;
}
.skeleton-wrapper {
padding-top: calc(var(--status-bar-height) + 44px);
.skeleton-footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
gap: 20rpx;
padding: 20rpx 30rpx;
background-color: #f7f7f7;
}
}
.chat-page {
background-color: #f7f7f7;
height: 100vh;
display: flex;
flex-direction: column;
}
.page-content {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.order-status-bar {
display: flex;
align-items: center;
gap: 10rpx;
background-color: #FFF7E5;
padding: 20rpx 30rpx;
font-size: 26rpx;
.store-name,
.status {
font-weight: 500;
color: #333;
}
.product-info,
.separator {
color: #666;
}
.up-icon {
margin-left: auto;
}
}
.chat-scroll-view {
flex: 1;
overflow-y: auto;
padding-bottom: 40rpx;
}
.message-list {
padding: 30rpx;
.message-wrapper {
margin-bottom: 40rpx;
&.type-system {
text-align: center;
.system-message {
display: inline-block;
background-color: #e5e5e5;
color: #fff;
font-size: 24rpx;
padding: 6rpx 20rpx;
border-radius: 8rpx;
}
}
&.type-incoming {
.bubble-wrapper {
align-items: flex-start;
}
.message-bubble {
background-color: #fff;
}
}
&.type-outgoing {
.bubble-wrapper {
align-items: flex-end;
}
.message-bubble {
background-color: #FFF7E5;
}
}
.bubble-wrapper {
display: flex;
flex-direction: column;
.sender-name {
font-size: 24rpx;
color: #999;
margin-bottom: 12rpx;
}
.message-bubble {
max-width: 70%;
padding: 20rpx;
border-radius: 16rpx;
font-size: 28rpx;
line-height: 1.5;
color: #333;
}
}
}
}
.quick-replies {
padding: 0 30rpx;
.title {
font-size: 26rpx;
color: #999;
margin-bottom: 20rpx;
}
.replies-tags {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
.tag {
background-color: #fff;
padding: 16rpx 24rpx;
border-radius: 30rpx;
font-size: 26rpx;
color: #333;
}
}
}
.input-bar {
display: flex;
align-items: center;
gap: 20rpx;
background-color: #f7f7f7;
padding: 20rpx 30rpx;
padding-bottom: calc(20rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
border-top: 1rpx solid #e5e5e5;
.input-wrapper {
flex: 1;
background-color: #fff;
border-radius: 12rpx;
:deep(.up-input) {
padding: 16rpx 20rpx !important;
}
}
.send-btn {
background-color: #FFD43D;
color: #333;
font-size: 28rpx;
height: 72rpx;
line-height: 72rpx;
padding: 0 40rpx;
border-radius: 12rpx;
margin: 0;
&::after {
border: none;
}
}
}
</style>