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.
236 lines
9.9 KiB
236 lines
9.9 KiB
<template>
|
|
<view class="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">校园圈聊天</view>
|
|
<view class="nav-sub">文字和图片会进行内容审核</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="post-card" v-if="session" @tap="goPost">
|
|
<image class="cover" v-if="session.displayCover" :src="session.displayCover" mode="aspectFill"></image>
|
|
<view class="cover no-cover" v-else>圈</view>
|
|
<view class="post-main">
|
|
<view class="post-title">{{session.displayTitle || '校园圈内容'}}</view>
|
|
<view class="post-sub">{{session.postType === 'partner' ? '找搭子' : '校园墙'}}</view>
|
|
</view>
|
|
<view class="link">查看 ›</view>
|
|
</view>
|
|
|
|
<scroll-view
|
|
class="message-scroll"
|
|
scroll-y
|
|
:scroll-into-view="scrollIntoView"
|
|
:scroll-with-animation="true"
|
|
:style="{ paddingTop: contentTop + 'px' }"
|
|
@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">
|
|
<view class="avatar">{{item.mine ? '我' : 'TA'}}</view>
|
|
<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" :class="{ disabled: sending }" @tap="sendText">{{sending ? '发送中' : '发送'}}</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import {
|
|
getCampusChatSession,
|
|
pageCampusChatMessages,
|
|
sendCampusChatMessage,
|
|
uploadCampusImage
|
|
} from '@/common/campusApi.js'
|
|
|
|
export default {
|
|
data() {
|
|
return {
|
|
statusBarTop: 24,
|
|
sessionId: '',
|
|
session: null,
|
|
messages: [],
|
|
pageNum: 1,
|
|
pageSize: 20,
|
|
totalPages: 1,
|
|
loadingOlder: false,
|
|
draft: '',
|
|
sending: false,
|
|
scrollIntoView: 'chat-bottom',
|
|
selfId: String(uni.getStorageSync('id') || '')
|
|
}
|
|
},
|
|
computed: {
|
|
contentTop() {
|
|
return this.statusBarTop + uni.upx2px(220)
|
|
}
|
|
},
|
|
onLoad(options = {}) {
|
|
this.sessionId = options.sessionId || ''
|
|
if (uni.getMenuButtonBoundingClientRect) this.statusBarTop = uni.getMenuButtonBoundingClientRect().top || this.statusBarTop
|
|
this.load()
|
|
},
|
|
methods: {
|
|
back() { uni.navigateBack() },
|
|
load() {
|
|
if (!this.sessionId) return
|
|
getCampusChatSession(this.sessionId).then(session => {
|
|
this.session = session
|
|
})
|
|
this.loadMessages(true)
|
|
},
|
|
loadMessages(reset) {
|
|
if (reset) {
|
|
this.pageNum = 1
|
|
this.totalPages = 1
|
|
}
|
|
return pageCampusChatMessages(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('campusUnreadChange')
|
|
})
|
|
},
|
|
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 || ''
|
|
}
|
|
},
|
|
sendText() {
|
|
if (this.sending) return
|
|
const text = this.draft
|
|
if (!text) return
|
|
const clientMsgId = 'local_' + Date.now()
|
|
this.draft = ''
|
|
this.appendLocal({ id: clientMsgId, clientMsgId, messageType: 1, content: text })
|
|
this.sending = true
|
|
sendCampusChatMessage({ sessionId: this.sessionId, messageType: 1, content: text, clientMsgId }).then(message => {
|
|
this.replaceLocal(clientMsgId, message)
|
|
}).catch(err => {
|
|
this.markFailed(clientMsgId, (err && err.message) || '发送失败')
|
|
}).finally(() => {
|
|
this.sending = false
|
|
})
|
|
},
|
|
chooseImage() {
|
|
if (this.sending) return
|
|
uni.chooseImage({
|
|
count: 1,
|
|
sizeType: ['compressed'],
|
|
success: async res => {
|
|
const path = res.tempFilePaths && res.tempFilePaths[0]
|
|
if (!path) return
|
|
const clientMsgId = 'local_' + Date.now()
|
|
this.appendLocal({ id: clientMsgId, clientMsgId, messageType: 2, content: path })
|
|
this.sending = true
|
|
try {
|
|
const url = await uploadCampusImage(path)
|
|
const message = await sendCampusChatMessage({ sessionId: this.sessionId, messageType: 2, content: url, clientMsgId })
|
|
this.replaceLocal(clientMsgId, message)
|
|
} catch (e) {
|
|
this.markFailed(clientMsgId, (e && e.message) || '图片发送失败')
|
|
} finally {
|
|
this.sending = false
|
|
}
|
|
}
|
|
})
|
|
},
|
|
appendLocal(message) {
|
|
this.messages.push({ ...message, mine: true, localState: 'sending', senderId: this.selfId })
|
|
this.scrollToBottom()
|
|
},
|
|
replaceLocal(clientMsgId, message) {
|
|
const index = this.messages.findIndex(item => item.clientMsgId === clientMsgId)
|
|
const normalized = this.normalizeMessage({ ...message, localState: '' })
|
|
if (index > -1) this.$set(this.messages, index, normalized)
|
|
else this.messages.push(normalized)
|
|
this.scrollToBottom()
|
|
},
|
|
markFailed(clientMsgId, reason) {
|
|
const item = this.messages.find(row => row.clientMsgId === clientMsgId)
|
|
if (item) {
|
|
item.localState = 'failed'
|
|
item.auditReason = reason
|
|
}
|
|
uni.showToast({ title: reason, icon: 'none', duration: 3000 })
|
|
},
|
|
statusText(item) {
|
|
if (item.localState === 'sending') return '发送中'
|
|
if (item.localState === 'failed') return item.auditReason || '发送失败'
|
|
return ''
|
|
},
|
|
scrollToBottom() {
|
|
this.$nextTick(() => {
|
|
this.scrollIntoView = ''
|
|
this.$nextTick(() => { this.scrollIntoView = 'chat-bottom' })
|
|
})
|
|
},
|
|
previewImage(url) {
|
|
uni.previewImage({ urls: [url] })
|
|
},
|
|
goPost() {
|
|
if (!this.session || !this.session.postId) return
|
|
uni.navigateTo({ url: '/package1/post/postDetail?id=' + this.session.postId + '&source=campus' })
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.chat-page { min-height: 100vh; background: #f5faf8; }
|
|
.nav { position: fixed; left: 0; right: 0; top: 0; z-index: 20; display: flex; align-items: center; gap: 16rpx; padding: 14rpx 24rpx; background: rgba(245,250,248,.98); }
|
|
.back { width: 64rpx; height: 64rpx; border-radius: 50%; background: #fff; display: flex; align-items: center; justify-content: center; }
|
|
.nav-title { color: #15362f; font-size: 31rpx; font-weight: 800; }
|
|
.nav-sub { margin-top: 4rpx; color: #7b8c87; font-size: 22rpx; }
|
|
.post-card { position: fixed; left: 24rpx; right: 24rpx; top: 118rpx; z-index: 18; display: flex; align-items: center; gap: 14rpx; padding: 16rpx; border-radius: 24rpx; background: #fff; box-shadow: 0 10rpx 26rpx rgba(39, 76, 70, .08); }
|
|
.cover { width: 82rpx; height: 82rpx; border-radius: 18rpx; background: #e8f7f3; }
|
|
.no-cover { display: flex; align-items: center; justify-content: center; color: #28a889; font-weight: 800; }
|
|
.post-main { flex: 1; min-width: 0; }
|
|
.post-title { font-size: 25rpx; color: #253d37; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
.post-sub, .link { margin-top: 6rpx; color: #83948f; font-size: 22rpx; }
|
|
.message-scroll { height: 100vh; box-sizing: border-box; padding-left: 24rpx; padding-right: 24rpx; padding-bottom: 130rpx; }
|
|
.load-tip, .safe-tip { text-align: center; color: #879691; font-size: 22rpx; padding: 18rpx 0; }
|
|
.safe-tip { margin: 0 8rpx 18rpx; background: #ecf8f4; border-radius: 999rpx; }
|
|
.message-row { display: flex; gap: 12rpx; margin: 18rpx 0; }
|
|
.message-row.mine { flex-direction: row-reverse; }
|
|
.avatar { width: 56rpx; height: 56rpx; line-height: 56rpx; border-radius: 50%; background: #dff8f0; color: #257c69; text-align: center; font-size: 22rpx; }
|
|
.message-main { max-width: 72%; }
|
|
.bubble { padding: 18rpx 22rpx; border-radius: 24rpx; background: #fff; color: #263d38; font-size: 27rpx; line-height: 1.5; box-shadow: 0 8rpx 22rpx rgba(39, 76, 70, .06); }
|
|
.mine .bubble { background: #34c7a2; color: #fff; }
|
|
.bubble.failed { opacity: .55; }
|
|
.image-bubble { padding: 8rpx; }
|
|
.image-bubble image { width: 220rpx; height: 220rpx; border-radius: 18rpx; }
|
|
.message-status { margin-top: 8rpx; color: #9a8a7b; font-size: 21rpx; text-align: right; }
|
|
.bottom-anchor { height: 20rpx; }
|
|
.input-bar { position: fixed; left: 0; right: 0; bottom: 0; z-index: 20; display: flex; align-items: center; gap: 14rpx; padding: 18rpx 24rpx 34rpx; background: #fff; box-shadow: 0 -8rpx 28rpx rgba(31, 67, 59, .08); }
|
|
.image-btn { width: 70rpx; height: 70rpx; border-radius: 50%; background: #f1f6f4; display: flex; align-items: center; justify-content: center; }
|
|
.input-bar input { flex: 1; height: 72rpx; border-radius: 999rpx; background: #f4f8f7; padding: 0 24rpx; font-size: 26rpx; }
|
|
.send-btn { padding: 0 24rpx; height: 72rpx; line-height: 72rpx; border-radius: 999rpx; background: #34c7a2; color: #fff; font-weight: 700; }
|
|
.send-btn.disabled { opacity: .6; }
|
|
</style>
|
|
|