You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
540 lines
13 KiB
540 lines
13 KiB
<template>
|
|
<view class="fish-chat-page">
|
|
<view class="nav" :style="{ paddingTop: statusBarTop + 'px' }">
|
|
<view class="back" @tap="back"><uni-icons type="left" size="24" color="#15362f"></uni-icons></view>
|
|
<view class="nav-main">
|
|
<view class="nav-title">{{peerName}}</view>
|
|
<view class="nav-sub">校园二手咨询 · 独立聊天</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="goods-card" v-if="goods" @tap="goGoods">
|
|
<image class="goods-cover" v-if="goodsCover" :src="goodsCover" mode="aspectFill"></image>
|
|
<view class="goods-cover no-cover" v-else>求购</view>
|
|
<view class="goods-main">
|
|
<view class="goods-title">{{goods.title}}</view>
|
|
<view class="goods-price">{{isWant ? '预算 ¥' : '¥'}}{{displayPrice}}</view>
|
|
</view>
|
|
<view class="goods-link">查看 ›</view>
|
|
</view>
|
|
|
|
<scroll-view
|
|
class="message-scroll"
|
|
scroll-y
|
|
:scroll-into-view="scrollIntoView"
|
|
:scroll-with-animation="true"
|
|
@scrolltoupper="loadOlder"
|
|
>
|
|
<view class="load-tip" v-if="loadingOlder">加载中...</view>
|
|
<view class="safe-tip">请勿发送涉黄涉暴、辱骂诈骗等内容,文字和图片会进行内容审核。</view>
|
|
<view
|
|
class="message-row"
|
|
:class="{ mine: item.mine }"
|
|
v-for="item in messages"
|
|
:key="item.id"
|
|
:id="'msg-' + item.id"
|
|
>
|
|
<image class="avatar" :src="defaultAvatar" mode="aspectFill"></image>
|
|
<view class="message-main">
|
|
<view class="bubble text-bubble" :class="{ failed: item.localState == 'failed' }" v-if="item.messageType == 1">{{item.content}}</view>
|
|
<view class="bubble image-bubble" :class="{ failed: item.localState == 'failed' }" v-else>
|
|
<image :src="item.content" mode="aspectFill" @tap="previewImage(item.content)"></image>
|
|
</view>
|
|
<view class="message-status" v-if="item.mine && item.localState">{{statusText(item)}}</view>
|
|
</view>
|
|
</view>
|
|
<view id="chat-bottom" class="bottom-anchor"></view>
|
|
</scroll-view>
|
|
|
|
<view class="input-bar">
|
|
<view class="image-btn" @tap="chooseImage">
|
|
<uni-icons type="image" size="24" color="#61736e"></uni-icons>
|
|
</view>
|
|
<input
|
|
v-model.trim="draft"
|
|
confirm-type="send"
|
|
placeholder="请输入消息..."
|
|
@confirm="sendText"
|
|
/>
|
|
<view class="send-btn" @tap="sendText">发送</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import fishMarketSocket from '@/common/fishMarketSocket.js'
|
|
import {
|
|
getFishGoodsDetail,
|
|
getFishWantDetail,
|
|
getFishChatSession,
|
|
pageFishChatMessages,
|
|
uploadFishImage
|
|
} from '@/common/fishMarketApi.js'
|
|
|
|
export default {
|
|
data() {
|
|
return {
|
|
statusBarTop: 24,
|
|
sessionId: '',
|
|
goodsId: '',
|
|
wantId: '',
|
|
session: null,
|
|
goods: null,
|
|
messages: [],
|
|
pageNum: 1,
|
|
pageSize: 20,
|
|
totalPages: 1,
|
|
loadingOlder: false,
|
|
draft: '',
|
|
scrollIntoView: 'chat-bottom',
|
|
selfId: String(uni.getStorageSync('id') || ''),
|
|
defaultAvatar: '/static/images/img/songshu.png',
|
|
socketReady: false
|
|
}
|
|
},
|
|
computed: {
|
|
imageList() {
|
|
if (!this.goods || !this.goods.imageList) return []
|
|
return this.goods.imageList
|
|
},
|
|
isWant() {
|
|
return !!this.wantId
|
|
},
|
|
goodsCover() {
|
|
if (!this.goods) return ''
|
|
return this.goods.coverImage || this.imageList[0] || ''
|
|
},
|
|
displayPrice() {
|
|
if (!this.goods) return 0
|
|
return this.isWant ? (this.goods.expectedPrice || 0) : (this.goods.price || 0)
|
|
},
|
|
peerName() {
|
|
if (!this.goods || !this.session) return '校园同学'
|
|
if (this.isWant) {
|
|
return this.selfId == String(this.session.buyerId) ? '供货同学' : (this.goods.userName || '求购同学')
|
|
}
|
|
return this.selfId == String(this.session.sellerId) ? '买家同学' : (this.goods.userName || '卖家同学')
|
|
},
|
|
peerAvatar() {
|
|
return this.defaultAvatar
|
|
}
|
|
},
|
|
onLoad(options) {
|
|
this.sessionId = options && options.sessionId ? options.sessionId : ''
|
|
this.goodsId = options && options.goodsId ? options.goodsId : ''
|
|
this.wantId = options && options.wantId ? options.wantId : ''
|
|
if (uni.getMenuButtonBoundingClientRect) {
|
|
this.statusBarTop = uni.getMenuButtonBoundingClientRect().top || this.statusBarTop
|
|
}
|
|
this.load()
|
|
},
|
|
onUnload() {
|
|
if (this.sessionId) {
|
|
fishMarketSocket.unsubscribeSession(this.sessionId)
|
|
}
|
|
fishMarketSocket.resetHandlers()
|
|
fishMarketSocket.close()
|
|
},
|
|
methods: {
|
|
back() {
|
|
uni.navigateBack()
|
|
},
|
|
load() {
|
|
if (!this.sessionId) return
|
|
getFishChatSession(this.sessionId).then(session => {
|
|
this.session = session
|
|
const goodsId = this.goodsId || (session && session.goodsId)
|
|
const wantId = this.wantId || (session && session.wantId)
|
|
if (goodsId) {
|
|
this.goodsId = goodsId
|
|
getFishGoodsDetail(goodsId).then(goods => {
|
|
this.goods = goods
|
|
})
|
|
} else if (wantId) {
|
|
this.wantId = wantId
|
|
getFishWantDetail(wantId).then(want => {
|
|
this.goods = want
|
|
})
|
|
}
|
|
})
|
|
this.loadMessages(true).then(() => {
|
|
this.initSocket()
|
|
})
|
|
},
|
|
loadMessages(reset) {
|
|
if (reset) {
|
|
this.pageNum = 1
|
|
this.totalPages = 1
|
|
}
|
|
return pageFishChatMessages(this.sessionId, this.pageNum, this.pageSize).then(page => {
|
|
const records = page && (page.records || page.content) ? (page.records || page.content) : []
|
|
const normalized = records.reverse().map(this.normalizeMessage)
|
|
this.totalPages = Number(page && (page.pages || page.totalPages)) || 1
|
|
this.messages = reset ? normalized : normalized.concat(this.messages)
|
|
this.scrollToBottom()
|
|
if (reset) {
|
|
uni.$emit('fishUnreadChange')
|
|
}
|
|
})
|
|
},
|
|
loadOlder() {
|
|
if (this.loadingOlder || this.pageNum >= this.totalPages) return
|
|
this.loadingOlder = true
|
|
this.pageNum += 1
|
|
this.loadMessages(false).finally(() => {
|
|
this.loadingOlder = false
|
|
})
|
|
},
|
|
normalizeMessage(item) {
|
|
return {
|
|
...item,
|
|
mine: String(item.senderId) == this.selfId,
|
|
localState: item.localState || ''
|
|
}
|
|
},
|
|
initSocket() {
|
|
if (!this.sessionId) return
|
|
fishMarketSocket.resetHandlers()
|
|
fishMarketSocket.onMessage((message) => {
|
|
if (!message || String(message.sessionId) !== String(this.sessionId)) return
|
|
this.handleSocketMessage(message)
|
|
})
|
|
fishMarketSocket.connect().then(() => {
|
|
this.socketReady = fishMarketSocket.subscribeSession(this.sessionId)
|
|
}).catch(() => {
|
|
this.socketReady = false
|
|
})
|
|
},
|
|
handleSocketMessage(message) {
|
|
if (message.type == 'audit_failed' || Number(message.auditStatus) == 2) {
|
|
this.markMessageFailed(message.clientMsgId, message.auditReason || '内容审核未通过')
|
|
uni.showToast({ title: message.auditReason || '内容审核未通过,请修改后重试', icon: 'none', duration: 3000 })
|
|
return
|
|
}
|
|
this.appendMessage(message)
|
|
},
|
|
appendMessage(message) {
|
|
if (!message || !message.id) return
|
|
if (message.clientMsgId) {
|
|
const local = this.messages.find(item => item.clientMsgId && item.clientMsgId == message.clientMsgId)
|
|
if (local) {
|
|
Object.assign(local, this.normalizeMessage(message), { localState: '' })
|
|
this.scrollToBottom()
|
|
return
|
|
}
|
|
}
|
|
const exists = this.messages.some(item => String(item.id) == String(message.id))
|
|
if (exists) return
|
|
this.messages.push(this.normalizeMessage(message))
|
|
this.scrollToBottom()
|
|
},
|
|
statusText(item) {
|
|
if (item.localState == 'failed') return item.auditReason || '内容审核未通过'
|
|
if (item.localState == 'sending') return '发送中'
|
|
return ''
|
|
},
|
|
pushLocalMessage(messageType, content, clientMsgId) {
|
|
this.messages.push({
|
|
id: clientMsgId,
|
|
clientMsgId,
|
|
sessionId: this.sessionId,
|
|
senderId: this.selfId,
|
|
messageType,
|
|
content,
|
|
mine: true,
|
|
localState: 'sending'
|
|
})
|
|
this.scrollToBottom()
|
|
},
|
|
markMessageFailed(clientMsgId, reason) {
|
|
const local = this.messages.find(item => item.clientMsgId && item.clientMsgId == clientMsgId)
|
|
if (!local) return
|
|
local.localState = 'failed'
|
|
local.auditStatus = 2
|
|
local.auditReason = reason || '内容审核未通过'
|
|
},
|
|
sendText() {
|
|
if (!this.draft) return
|
|
const content = this.draft
|
|
this.draft = ''
|
|
this.sendMessage(1, content)
|
|
},
|
|
sendMessage(messageType, content) {
|
|
const clientMsgId = 'fish-' + Date.now() + '-' + Math.random().toString(16).slice(2)
|
|
this.pushLocalMessage(messageType, content, clientMsgId)
|
|
const payload = {
|
|
sessionId: this.sessionId,
|
|
messageType,
|
|
content,
|
|
clientMsgId
|
|
}
|
|
if (fishMarketSocket.sendMessage(payload)) return
|
|
this.markMessageFailed(clientMsgId, '聊天连接未就绪,请稍后重试')
|
|
uni.showToast({ title: '聊天连接未就绪,请稍后重试', icon: 'none' })
|
|
},
|
|
chooseImage() {
|
|
uni.chooseImage({
|
|
count: 1,
|
|
sizeType: ['compressed'],
|
|
sourceType: ['album', 'camera'],
|
|
success: async (res) => {
|
|
const path = res.tempFilePaths && res.tempFilePaths[0]
|
|
if (!path) return
|
|
const url = await uploadFishImage(path)
|
|
if (url) this.sendMessage(2, url)
|
|
}
|
|
})
|
|
},
|
|
scrollToBottom() {
|
|
this.$nextTick(() => {
|
|
this.scrollIntoView = ''
|
|
setTimeout(() => {
|
|
this.scrollIntoView = 'chat-bottom'
|
|
}, 30)
|
|
})
|
|
},
|
|
previewImage(url) {
|
|
if (!url) return
|
|
uni.previewImage({ urls: [url], current: url })
|
|
},
|
|
goGoods() {
|
|
if (this.isWant) {
|
|
if (!this.wantId) return
|
|
uni.navigateTo({ url: '/package2/IdleTrad/detail?type=want&id=' + this.wantId })
|
|
return
|
|
}
|
|
if (this.goodsId) {
|
|
uni.navigateTo({ url: '/package2/IdleTrad/detail?id=' + this.goodsId })
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
page {
|
|
width: 100%;
|
|
height: 100%;
|
|
background: #f7f9f7;
|
|
}
|
|
|
|
.fish-chat-page {
|
|
width: 100%;
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: linear-gradient(180deg, #e8fbf3 0%, #f7f9f7 360rpx);
|
|
color: #15362f;
|
|
}
|
|
|
|
.nav {
|
|
padding-left: 24rpx;
|
|
padding-right: 24rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
background: rgba(255, 255, 255, 0.88);
|
|
box-shadow: 0 8rpx 20rpx rgba(20, 68, 52, 0.06);
|
|
}
|
|
|
|
.back {
|
|
width: 68rpx;
|
|
height: 68rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.nav-main {
|
|
flex: 1;
|
|
}
|
|
|
|
.nav-title {
|
|
font-size: 30rpx;
|
|
font-weight: 900;
|
|
}
|
|
|
|
.nav-sub {
|
|
margin-top: 4rpx;
|
|
color: #7d8b86;
|
|
font-size: 20rpx;
|
|
}
|
|
|
|
.goods-card {
|
|
margin: 18rpx 24rpx 0;
|
|
padding: 16rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
border-radius: 24rpx;
|
|
background: #fff;
|
|
box-shadow: 0 8rpx 22rpx rgba(20, 60, 48, 0.06);
|
|
}
|
|
|
|
.goods-cover {
|
|
width: 104rpx;
|
|
height: 104rpx;
|
|
border-radius: 18rpx;
|
|
background: #edf4f1;
|
|
}
|
|
|
|
.no-cover {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: #7f8d88;
|
|
font-size: 22rpx;
|
|
font-weight: 900;
|
|
}
|
|
|
|
.goods-main {
|
|
flex: 1;
|
|
min-width: 0;
|
|
margin-left: 16rpx;
|
|
}
|
|
|
|
.goods-title {
|
|
overflow: hidden;
|
|
font-size: 26rpx;
|
|
font-weight: 900;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.goods-price {
|
|
margin-top: 8rpx;
|
|
color: #ef4c3f;
|
|
font-size: 28rpx;
|
|
font-weight: 900;
|
|
}
|
|
|
|
.goods-link {
|
|
color: #7f8d88;
|
|
font-size: 22rpx;
|
|
}
|
|
|
|
.message-scroll {
|
|
flex: 1;
|
|
min-height: 0;
|
|
padding: 20rpx 24rpx;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.safe-tip,
|
|
.load-tip {
|
|
margin: 10rpx auto 24rpx;
|
|
color: #8a9994;
|
|
font-size: 21rpx;
|
|
text-align: center;
|
|
}
|
|
|
|
.message-row {
|
|
margin-bottom: 26rpx;
|
|
display: flex;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.message-row.mine {
|
|
flex-direction: row-reverse;
|
|
}
|
|
|
|
.avatar {
|
|
width: 70rpx;
|
|
height: 70rpx;
|
|
border-radius: 50%;
|
|
background: #edf4f1;
|
|
}
|
|
|
|
.message-main {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.mine .message-main {
|
|
align-items: flex-end;
|
|
}
|
|
|
|
.bubble {
|
|
max-width: 520rpx;
|
|
margin: 0 16rpx;
|
|
border-radius: 24rpx;
|
|
font-size: 27rpx;
|
|
line-height: 42rpx;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.text-bubble {
|
|
padding: 18rpx 22rpx;
|
|
background: #fff;
|
|
color: #253b35;
|
|
}
|
|
|
|
.mine .text-bubble {
|
|
background: linear-gradient(135deg, #37c990, #19af7e);
|
|
color: #fff;
|
|
}
|
|
|
|
.bubble.failed {
|
|
opacity: .58;
|
|
}
|
|
|
|
.image-bubble {
|
|
overflow: hidden;
|
|
background: #fff;
|
|
}
|
|
|
|
.image-bubble image {
|
|
width: 260rpx;
|
|
height: 260rpx;
|
|
display: block;
|
|
}
|
|
|
|
.message-status {
|
|
max-width: 520rpx;
|
|
margin: 8rpx 18rpx 0;
|
|
color: #ef4c3f;
|
|
font-size: 21rpx;
|
|
line-height: 30rpx;
|
|
}
|
|
|
|
.bottom-anchor {
|
|
height: 20rpx;
|
|
}
|
|
|
|
.input-bar {
|
|
padding: 16rpx 22rpx calc(16rpx + env(safe-area-inset-bottom));
|
|
display: flex;
|
|
align-items: center;
|
|
background: rgba(255, 255, 255, 0.96);
|
|
box-shadow: 0 -8rpx 22rpx rgba(20, 60, 48, 0.06);
|
|
}
|
|
|
|
.image-btn {
|
|
width: 70rpx;
|
|
height: 70rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.input-bar input {
|
|
flex: 1;
|
|
height: 72rpx;
|
|
padding: 0 22rpx;
|
|
border-radius: 38rpx;
|
|
background: #f2f7f4;
|
|
color: #213832;
|
|
font-size: 26rpx;
|
|
}
|
|
|
|
.send-btn {
|
|
width: 104rpx;
|
|
height: 72rpx;
|
|
margin-left: 14rpx;
|
|
border-radius: 38rpx;
|
|
background: linear-gradient(135deg, #37c990, #19af7e);
|
|
color: #fff;
|
|
font-size: 26rpx;
|
|
font-weight: 900;
|
|
text-align: center;
|
|
line-height: 72rpx;
|
|
}
|
|
</style>
|
|
|