first commit
This commit is contained in:
@@ -0,0 +1,464 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user