539 lines
15 KiB
Vue
539 lines
15 KiB
Vue
<template>
|
|
<view class="common-room">
|
|
<view class="main">
|
|
|
|
<view class="queue" :class="{'last':index+1===list.length}" v-for="(item,index) in list" :key="index">
|
|
<view class="datetime">{{ item.created_at }}</view>
|
|
|
|
<view class="service-box flex" v-if="item.user_type===1">
|
|
<image class="cover" :src="item.header || webUrl+'/20221114155314348118.png'"/>
|
|
<view class="text">
|
|
<view class="name one-t">{{ item.username }}</view>
|
|
<text class="msg" v-if="item.msg_type===0">{{ item.msg_content }}</text>
|
|
<view class="msg" v-if="item.msg_type===1">
|
|
<image class="img" :src="imgUrl(item.msg_content)" @click="preview(imgUrl(item.msg_content))"></image>
|
|
</view>
|
|
|
|
</view>
|
|
</view>
|
|
|
|
<view class="customer-box flex jc-end" v-else>
|
|
<view class="text flex flex-col ai-end">
|
|
<view class="name one-t">{{ item.username }}</view>
|
|
<text class="msg" v-if="item.msg_type===0">{{ item.msg_content }}</text>
|
|
<view class="msg" v-if="item.msg_type===1">
|
|
<image class="img" :src="imgUrl(item.msg_content)" @click="preview(imgUrl(item.msg_content))"></image>
|
|
</view>
|
|
<view class="msg v12-d-flex" style="box-sizing: content-box" v-if="item.msg_type===2" @click="toGoods(item)">
|
|
<view class="goods-logo">
|
|
<image class="img v12-radius-8" :src="wsJson(item.msg_content).cover"></image>
|
|
</view>
|
|
<view class="conent v12-ml-3 v12-justify-between v12-flex-column">
|
|
<view class="">
|
|
<view class="goodsName one-t v12-font-26">
|
|
{{ wsJson(item.msg_content).goodsName }}
|
|
</view>
|
|
<view class="goodsSku v12-secondary-dark-text v12-font-22 v12-font-weight-400 one-t">
|
|
{{ wsJson(item.msg_content).skuStr }}
|
|
</view>
|
|
</view>
|
|
<view class="price v12-primary-text v12-font-24" v-if="wsJson(item.msg_content).isNegotiable !== 1">
|
|
¥{{ wsJson(item.msg_content).price }}
|
|
</view>
|
|
<view class="price v12-primary-text v12-font-24" v-else>
|
|
价格面议
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<image class="cover" :src="item.header || webUrl+'/20221114155314348118.png'"/>
|
|
</view>
|
|
|
|
</view>
|
|
<view class="msg v12-d-flex goods-link" style="box-sizing: content-box" v-if="params" @click="sendGoods">
|
|
<view class="goods-logo">
|
|
<image class="img v12-radius-8" :src="params.cover"></image>
|
|
</view>
|
|
<view class="conent v12-ml-3 v12-justify-between v12-flex-column">
|
|
<view class="">
|
|
<view class="goodsName one-t v12-font-26">
|
|
{{ params.goodsName }}
|
|
</view>
|
|
<view class="goodsSku v12-secondary-dark-text v12-font-22 v12-font-weight-400 one-t">
|
|
{{ params.skuStr }}
|
|
</view>
|
|
</view>
|
|
<view class="price v12-primary-text v12-font-24" v-if="params.isNegotiable !== 1">
|
|
¥{{ params.price }}
|
|
</view>
|
|
<view class="price v12-primary-text v12-font-24" v-else>
|
|
价格面议
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="footer flex jc-between ai-end v12-align-center">
|
|
<image :src="webUrl+'/20221014095236624035.png'" @click="getUploadToken"></image>
|
|
<view class="input-box">
|
|
<textarea
|
|
v-model="content"
|
|
maxlength="500"
|
|
:auto-height="true"
|
|
:fixed="true"
|
|
:show-confirm-bar="false"
|
|
confirm-type="send"
|
|
@input="userInput"
|
|
@confirm="userSend"
|
|
cursor-spacing="15"
|
|
:adjust-position="true"
|
|
/>
|
|
</view>
|
|
<button @click="userSend" class="v12-primary">发送</button>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
name: "room",
|
|
data() {
|
|
return {
|
|
webUrl: this.$VUE_APP_RESOURCES_URL,
|
|
token: null,
|
|
uid: null,
|
|
userInfo: null,
|
|
storeId: null,
|
|
merName: null,
|
|
uploadToken: null, //上传token
|
|
|
|
socketOpen: false,
|
|
socketConnecting: false, // 是否正在建连
|
|
socketTask: null,
|
|
socketMsgQueue: [],
|
|
hasConnected: false, // 是否成功建过连接
|
|
reconnectHistory: false, // 重连后用服务端历史整体替换本地列表,防止消息重复
|
|
|
|
roomId: null, //房间ID
|
|
content: "", //消息内容
|
|
list: [], //消息列表,
|
|
params: null
|
|
}
|
|
},
|
|
onLoad(options) {
|
|
this.storeId = options.id;
|
|
this.merName = options.name;
|
|
this.params = options.params && JSON.parse(options.params) || null
|
|
setTimeout(()=>{
|
|
this.params = null
|
|
}, 10000)
|
|
if (this.merName!="undefined" && this.merName!="null") {
|
|
uni.setNavigationBarTitle({
|
|
title: this.merName
|
|
})
|
|
}
|
|
this.token = uni.getStorageSync("login_status");
|
|
this.userInfo = uni.getStorageSync("userInfo");
|
|
this.uid = this.userInfo.uid
|
|
|
|
},
|
|
onShow() {
|
|
this.content = uni.getStorageSync(this.merName+this.uid+this.storeId)
|
|
// 已连接/连接中不重复建连;清空 list 会导致图片操作(触发 onHide/onShow)后页面白屏
|
|
if (!this.socketOpen && !this.socketConnecting) {
|
|
this.connectSocketInit();
|
|
}
|
|
},
|
|
onUnload() {
|
|
this.closeSocket();
|
|
uni.setStorageSync(this.merName+this.uid+this.storeId, this.content)
|
|
},
|
|
onHide() {
|
|
this.closeSocket();
|
|
uni.setStorageSync(this.merName+this.uid+this.storeId, this.content)
|
|
},
|
|
methods: {
|
|
toGoods(item) {
|
|
const itemJson = this.wsJson(item.msg_content)
|
|
if(!itemJson.goodsId) {
|
|
// 提示数据错误
|
|
// uni.showToast({
|
|
// title: '数据错误',
|
|
// icon: 'none'
|
|
// })
|
|
return
|
|
}
|
|
|
|
this.$yrouter.push({
|
|
path: '/pages/shop/GoodsCon/index',
|
|
query: {
|
|
id: itemJson.goodsId
|
|
}
|
|
})
|
|
},
|
|
wsJson(json) {
|
|
// 兼容脏数据,避免渲染时报错导致白屏
|
|
try {
|
|
return JSON.parse(json) || {}
|
|
} catch (e) {
|
|
return {}
|
|
}
|
|
},
|
|
// 拼接图片完整地址:七牛上传后消息里只存 key,需要补上资源域名;http 统一转 https
|
|
imgUrl(content) {
|
|
if (!content) return ''
|
|
if (/^https?:\/\//i.test(content)) return content.replace(/^http:\/\//i, 'https://')
|
|
return this.webUrl + '/' + content
|
|
},
|
|
getUploadToken() {
|
|
let that = this;
|
|
|
|
uni.request({
|
|
url: this.$SERVICE_API_URL + "/get-upload-token",
|
|
method: "POST",
|
|
success: ({data}) => {
|
|
if (data.status === "success") {
|
|
this.uploadToken = data.content;
|
|
|
|
uni.chooseImage({
|
|
count: 1, //数量
|
|
sizeType: ['original'], //可以指定是原图还是压缩图,默认二者都有
|
|
sourceType: ['album', 'camera'], //从相册选择
|
|
success: (chooseImageRes) => {
|
|
const tempFilePaths = chooseImageRes.tempFilePaths;
|
|
|
|
uni.uploadFile({
|
|
url: 'https://up-z2.qiniup.com', //接口地址
|
|
filePath: tempFilePaths[0],
|
|
name: 'file',
|
|
formData: {
|
|
'token': that.uploadToken
|
|
},
|
|
success: (uploadFileRes) => {
|
|
// uni.showModal({
|
|
// title: that.uploadToken + ' ',
|
|
// content: uploadFileRes.data + ' ',
|
|
// showCancel: false
|
|
// })
|
|
|
|
let imgKey = JSON.parse(uploadFileRes.data).key;
|
|
|
|
if (imgKey.length > 0) {
|
|
let param = {
|
|
type: "msg",
|
|
user_type: 0,
|
|
uid: that.uid,
|
|
username: that.userInfo.nickname,
|
|
header: that.userInfo.avatar,
|
|
room_id: that.roomId,
|
|
msg_type: 1,
|
|
content: imgKey,
|
|
token: that.token
|
|
}
|
|
that.sendSocketMessage(JSON.stringify(param));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
})
|
|
},
|
|
sendGoods() {
|
|
if(Object.values(this.params).length > 0) {
|
|
this.content = JSON.stringify(this.params)
|
|
// this.list.push(this.params)
|
|
this.userSend(2)
|
|
}
|
|
this.params = null
|
|
},
|
|
// 预览图片
|
|
preview(url) {
|
|
uni.previewImage({
|
|
urls: [url]
|
|
});
|
|
},
|
|
|
|
// 进入页面时创建websocket连接【整个页面随时使用】
|
|
connectSocketInit() {
|
|
if (this.socketOpen || this.socketConnecting) return;
|
|
this.socketConnecting = true;
|
|
|
|
// 创建一个this.socketTask对象【发送、接收、关闭socket都由这个对象操作】
|
|
const task = uni.connectSocket({
|
|
url: this.$SOCKET_URL,
|
|
success: () => {
|
|
}
|
|
})
|
|
if (!task) {
|
|
this.socketConnecting = false;
|
|
return;
|
|
}
|
|
this.socketTask = task;
|
|
|
|
// 任务级回调:全局 uni.onSocket* 每次进页面都会重复注册,导致消息重复处理、连接串台
|
|
const isCurrent = () => this.socketTask === task;
|
|
|
|
task.onOpen(() => {
|
|
if (!isCurrent()) return;
|
|
this.socketOpen = true;
|
|
this.socketConnecting = false;
|
|
// 断线重连后重新加入房间;本地已有历史时用服务端历史整体替换,避免重复
|
|
this.reconnectHistory = this.hasConnected && this.list.length > 0;
|
|
this.hasConnected = true;
|
|
this.createRoom();
|
|
// 补发连接未就绪期间排队的消息(如发图片时连接被隐藏页关闭)
|
|
if (this.socketMsgQueue.length) {
|
|
const queue = this.socketMsgQueue.splice(0);
|
|
queue.forEach(msg => this.sendSocketMessage(msg));
|
|
}
|
|
});
|
|
|
|
task.onMessage(res => {
|
|
if (!isCurrent()) return;
|
|
this.handleMessage(res.data);
|
|
});
|
|
|
|
task.onError(err => {
|
|
console.log('WebSocket连接打开失败,请检查!', err);
|
|
if (!isCurrent()) return;
|
|
this.socketOpen = false;
|
|
this.socketConnecting = false;
|
|
});
|
|
|
|
task.onClose(() => {
|
|
if (!isCurrent()) return;
|
|
this.socketOpen = false;
|
|
this.socketConnecting = false;
|
|
});
|
|
},
|
|
|
|
closeSocket() {
|
|
this.socketOpen = false;
|
|
this.socketConnecting = false;
|
|
if (this.socketTask) {
|
|
try {
|
|
this.socketTask.close({});
|
|
} catch (e) {}
|
|
}
|
|
},
|
|
|
|
userInput(event) {
|
|
// 只取前500字
|
|
this.content = event.detail.value.slice(0, 500);
|
|
},
|
|
|
|
userSend(msg_type) {
|
|
if (this.content.length === 0) return;
|
|
|
|
let param = {
|
|
type: "msg",
|
|
user_type: 0,
|
|
uid: this.uid,
|
|
username: this.userInfo.nickname,
|
|
header: this.userInfo.avatar,
|
|
room_id: this.roomId,
|
|
msg_type: msg_type === 2 ? msg_type : 0,
|
|
content: this.content,
|
|
token: this.token
|
|
}
|
|
this.sendSocketMessage(JSON.stringify(param));
|
|
this.content = "";
|
|
},
|
|
|
|
createRoom() {
|
|
|
|
let param = {
|
|
type: "join",
|
|
user_type: 0,
|
|
uid: this.uid,
|
|
username: this.userInfo.nickname,
|
|
header: this.userInfo.avatar,
|
|
store_id: this.storeId
|
|
};
|
|
this.sendSocketMessage(JSON.stringify(param));
|
|
},
|
|
|
|
sendSocketMessage(msg) {
|
|
if (this.socketOpen && this.socketTask) {
|
|
this.socketTask.send({
|
|
data: msg,
|
|
fail: () => {
|
|
// 发送失败说明连接可能已断开,重新排队并尝试重连后补发
|
|
this.socketMsgQueue.push(msg);
|
|
this.closeSocket();
|
|
this.connectSocketInit();
|
|
}
|
|
});
|
|
} else {
|
|
this.socketMsgQueue.push(msg);
|
|
}
|
|
},
|
|
|
|
handleMessage(str) {
|
|
let data;
|
|
try {
|
|
data = JSON.parse(str);
|
|
} catch (e) {
|
|
return;
|
|
}
|
|
switch (data.type) {
|
|
case "room_id":
|
|
this.roomId = data.msg_content;
|
|
break;
|
|
case "service_join":
|
|
break;
|
|
case "leave":
|
|
break;
|
|
case "msg":
|
|
if (data.msg_content) {
|
|
this.list.push(data.msg_content);
|
|
this.scrollLast();
|
|
}
|
|
break;
|
|
case "msg_history": {
|
|
const history = data.msg_content || [];
|
|
if (this.reconnectHistory) {
|
|
// 重连后服务端重发历史:整体替换,避免消息重复
|
|
this.reconnectHistory = false;
|
|
this.list = history;
|
|
} else {
|
|
this.list = history.concat(this.list);
|
|
}
|
|
this.scrollLast();
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
},
|
|
|
|
scrollLast() {
|
|
setTimeout(() => {
|
|
uni.pageScrollTo({
|
|
selector: ".last .msg",
|
|
duration: 1
|
|
})
|
|
}, 50)
|
|
}
|
|
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped lang="less">
|
|
.goods-link{
|
|
box-sizing: content-box;
|
|
position: fixed;
|
|
bottom: 200rpx;
|
|
right: 20rpx;
|
|
}
|
|
.goodsName,.goodsSku{
|
|
max-width: 400rpx;
|
|
}
|
|
.common-room {
|
|
min-height: 100vh;
|
|
background: #F5F5F5;
|
|
font-size: 26rpx;
|
|
line-height: 38rpx;
|
|
color: #333333;
|
|
|
|
view {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.main {
|
|
padding: 0 32rpx 160rpx;
|
|
|
|
.datetime {
|
|
margin: 30rpx 0;
|
|
font-size: 22rpx;
|
|
line-height: 32rpx;
|
|
color: #999999;
|
|
text-align: center;
|
|
}
|
|
|
|
.cover {
|
|
width: 48rpx;
|
|
height: 48rpx;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.text {
|
|
width: 550rpx;
|
|
margin: 0 20rpx;
|
|
}
|
|
|
|
.name {
|
|
margin-bottom: 16rpx;
|
|
font-size: 22rpx;
|
|
line-height: 32rpx;
|
|
}
|
|
|
|
.msg {
|
|
display: inline-block;
|
|
max-width: 560rpx;
|
|
padding: 16rpx;
|
|
background: #FFFFFF;
|
|
font-weight: bold;
|
|
word-break: break-all;
|
|
word-wrap: break-word;
|
|
|
|
.img {
|
|
max-width: 140rpx;
|
|
max-height: 140rpx;
|
|
vertical-align: middle;
|
|
}
|
|
}
|
|
|
|
.service-box {
|
|
margin: 30rpx 0;
|
|
}
|
|
|
|
.customer-box {
|
|
margin: 30rpx 0;
|
|
}
|
|
}
|
|
|
|
.footer {
|
|
position: fixed;
|
|
bottom: 0;
|
|
width: 100vw;
|
|
padding: 30rpx 32rpx 28rpx;
|
|
border-radius: 40rpx 40rpx 0rpx 0rpx;
|
|
background: #FFFFFF;
|
|
height: 160rpx;
|
|
image {
|
|
width: 64rpx;
|
|
height: 64rpx;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.input-box {
|
|
width: 472rpx;
|
|
min-height: 64rpx;
|
|
max-height: 124rpx;
|
|
box-sizing: border-box;
|
|
padding: 18rpx 30rpx;
|
|
border-radius: 32rpx;
|
|
background: #F5F5F5;
|
|
overflow: scroll;
|
|
textarea {
|
|
width: 100%;
|
|
}
|
|
}
|
|
|
|
|
|
button {
|
|
width: 120rpx;
|
|
margin: 0;
|
|
border-radius: 100rpx;
|
|
background: #C52733;
|
|
font-size: 28rpx;
|
|
color: #FFFFFF;
|
|
font-weight: bold;
|
|
}
|
|
}
|
|
}
|
|
</style> |