|
|
@ -38,7 +38,7 @@ |
|
|
</view> |
|
|
</view> |
|
|
</view> |
|
|
</view> |
|
|
|
|
|
|
|
|
<scroll-view class="messages" scroll-y :scroll-into-view="scrollIntoView" :scroll-with-animation="scrollAnimation" |
|
|
<scroll-view class="messages" scroll-y :style="messagesStyle" :scroll-into-view="scrollIntoView" :scroll-with-animation="scrollAnimation" |
|
|
upper-threshold="80" @scrolltoupper="loadOlderMessages"> |
|
|
upper-threshold="80" @scrolltoupper="loadOlderMessages"> |
|
|
<view class="load-more" v-if="loadingMessages">正在加载更早消息...</view> |
|
|
<view class="load-more" v-if="loadingMessages">正在加载更早消息...</view> |
|
|
<view class="load-more" v-else-if="!hasMoreMessages && messages.length">没有更早的消息了</view> |
|
|
<view class="load-more" v-else-if="!hasMoreMessages && messages.length">没有更早的消息了</view> |
|
|
@ -62,20 +62,21 @@ |
|
|
<view class="hesitate-line right" v-if="typingHint">{{ typingHint }}</view> |
|
|
<view class="hesitate-line right" v-if="typingHint">{{ typingHint }}</view> |
|
|
</scroll-view> |
|
|
</scroll-view> |
|
|
|
|
|
|
|
|
<view class="presence-row"> |
|
|
<view class="presence-row" :style="{ bottom: presenceBottom }"> |
|
|
<view class="presence-chip" v-for="item in presenceActions" :key="item" @tap="sendPresence(item)"> |
|
|
<view class="presence-chip" v-for="item in presenceActions" :key="item" @tap="sendPresence(item)"> |
|
|
{{ item }} |
|
|
{{ item }} |
|
|
</view> |
|
|
</view> |
|
|
</view> |
|
|
</view> |
|
|
|
|
|
|
|
|
<view class="emoji-panel" v-if="showEmoji"> |
|
|
<view class="emoji-panel" v-if="showEmoji" :style="{ bottom: emojiPanelBottom }"> |
|
|
<view class="emoji-item" v-for="item in emojis" :key="item" @tap="sendEmoji(item)">{{ item }}</view> |
|
|
<view class="emoji-item" v-for="item in emojis" :key="item" @tap="sendEmoji(item)">{{ item }}</view> |
|
|
</view> |
|
|
</view> |
|
|
|
|
|
|
|
|
<view class="input-bar"> |
|
|
<view class="input-bar" :style="{ bottom: inputBarBottom }"> |
|
|
<view class="tool-btn" @tap="toggleEmoji">☺</view> |
|
|
<view class="tool-btn" @tap="toggleEmoji">☺</view> |
|
|
<view class="tool-btn" @tap="voiceMode = !voiceMode">{{ voiceMode ? '键' : '语' }}</view> |
|
|
<view class="tool-btn" @tap="voiceMode = !voiceMode">{{ voiceMode ? '键' : '语' }}</view> |
|
|
<input class="input" v-if="!voiceMode" v-model="draft" placeholder="轻轻说一句,或保持安静" /> |
|
|
<input class="input" v-if="!voiceMode" v-model="draft" placeholder="轻轻说一句,或保持安静" |
|
|
|
|
|
:adjust-position="false" :cursor-spacing="20" @focus="handleInputFocus" @blur="handleInputBlur" /> |
|
|
<view class="voice-hold" v-else :class="{ recording: recording, cancelling: voiceCancelling }" |
|
|
<view class="voice-hold" v-else :class="{ recording: recording, cancelling: voiceCancelling }" |
|
|
@touchstart.prevent="startRecord" @touchmove.prevent="moveRecord" @touchend="stopRecord" @touchcancel="cancelRecord"> |
|
|
@touchstart.prevent="startRecord" @touchmove.prevent="moveRecord" @touchend="stopRecord" @touchcancel="cancelRecord"> |
|
|
{{ voiceHoldText }} |
|
|
{{ voiceHoldText }} |
|
|
@ -99,6 +100,16 @@ |
|
|
<view class="profile-avatar" v-else>{{ targetProfile.avatarText || companion.avatar }}</view> |
|
|
<view class="profile-avatar" v-else>{{ targetProfile.avatarText || companion.avatar }}</view> |
|
|
<view class="profile-name">{{ targetProfile.anonymousName || companion.name }}</view> |
|
|
<view class="profile-name">{{ targetProfile.anonymousName || companion.name }}</view> |
|
|
<view class="profile-mode">{{ targetProfile.currentMode === 'e' ? 'e 人 · 轻轻热闹' : 'i 人 · 安静陪伴' }}</view> |
|
|
<view class="profile-mode">{{ targetProfile.currentMode === 'e' ? 'e 人 · 轻轻热闹' : 'i 人 · 安静陪伴' }}</view> |
|
|
|
|
|
<view class="profile-info-grid"> |
|
|
|
|
|
<view class="profile-info"> |
|
|
|
|
|
<text class="profile-info-label">性别</text> |
|
|
|
|
|
<text>{{ genderText(targetProfile.gender) }}</text> |
|
|
|
|
|
</view> |
|
|
|
|
|
<view class="profile-info"> |
|
|
|
|
|
<text class="profile-info-label">想遇见</text> |
|
|
|
|
|
<text>{{ targetText(targetProfile.targetModePreference, targetProfile.targetGenderPreference) }}</text> |
|
|
|
|
|
</view> |
|
|
|
|
|
</view> |
|
|
<view class="profile-intro">{{ targetProfile.intro || companion.prompt || '这个人还没有写介绍。' }}</view> |
|
|
<view class="profile-intro">{{ targetProfile.intro || companion.prompt || '这个人还没有写介绍。' }}</view> |
|
|
<view class="profile-tags" v-if="targetProfile.interestTags && targetProfile.interestTags.length"> |
|
|
<view class="profile-tags" v-if="targetProfile.interestTags && targetProfile.interestTags.length"> |
|
|
<text v-for="tag in targetProfile.interestTags" :key="tag">{{ tag }}</text> |
|
|
<text v-for="tag in targetProfile.interestTags" :key="tag">{{ tag }}</text> |
|
|
@ -144,6 +155,8 @@ |
|
|
innerAudioContext: null, |
|
|
innerAudioContext: null, |
|
|
playingVoiceUrl: '', |
|
|
playingVoiceUrl: '', |
|
|
voiceSwitching: false, |
|
|
voiceSwitching: false, |
|
|
|
|
|
keyboardHeight: 0, |
|
|
|
|
|
keyboardListener: null, |
|
|
messages: [], |
|
|
messages: [], |
|
|
messagePage: 1, |
|
|
messagePage: 1, |
|
|
messagePageSize: 20, |
|
|
messagePageSize: 20, |
|
|
@ -189,6 +202,23 @@ |
|
|
voiceHoldText() { |
|
|
voiceHoldText() { |
|
|
if (!this.recording) return '按住说话' |
|
|
if (!this.recording) return '按住说话' |
|
|
return this.voiceCancelling ? '松开取消' : '松开发送,上滑取消' |
|
|
return this.voiceCancelling ? '松开取消' : '松开发送,上滑取消' |
|
|
|
|
|
}, |
|
|
|
|
|
keyboardBottomPx() { |
|
|
|
|
|
return this.keyboardHeight > 0 ? (this.keyboardHeight + 8) + 'px' : '30rpx' |
|
|
|
|
|
}, |
|
|
|
|
|
inputBarBottom() { |
|
|
|
|
|
return this.keyboardBottomPx |
|
|
|
|
|
}, |
|
|
|
|
|
presenceBottom() { |
|
|
|
|
|
return this.keyboardHeight > 0 ? (this.keyboardHeight + 76) + 'px' : '132rpx' |
|
|
|
|
|
}, |
|
|
|
|
|
emojiPanelBottom() { |
|
|
|
|
|
return this.keyboardHeight > 0 ? (this.keyboardHeight + 82) + 'px' : '128rpx' |
|
|
|
|
|
}, |
|
|
|
|
|
messagesStyle() { |
|
|
|
|
|
return { |
|
|
|
|
|
paddingBottom: this.keyboardHeight > 0 ? (this.keyboardHeight + 140) + 'px' : '24rpx' |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
onLoad(options) { |
|
|
onLoad(options) { |
|
|
@ -216,16 +246,49 @@ |
|
|
this.loadLatestMessages() |
|
|
this.loadLatestMessages() |
|
|
this.initSocket() |
|
|
this.initSocket() |
|
|
this.startMessagePolling() |
|
|
this.startMessagePolling() |
|
|
|
|
|
this.bindKeyboardListener() |
|
|
}, |
|
|
}, |
|
|
onUnload() { |
|
|
onUnload() { |
|
|
this.clearTimer() |
|
|
this.clearTimer() |
|
|
this.stopMessagePolling() |
|
|
this.stopMessagePolling() |
|
|
|
|
|
this.unbindKeyboardListener() |
|
|
if (this.innerAudioContext) { |
|
|
if (this.innerAudioContext) { |
|
|
this.innerAudioContext.destroy() |
|
|
this.innerAudioContext.destroy() |
|
|
this.innerAudioContext = null |
|
|
this.innerAudioContext = null |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
methods: { |
|
|
methods: { |
|
|
|
|
|
bindKeyboardListener() { |
|
|
|
|
|
if (!uni.onKeyboardHeightChange) return |
|
|
|
|
|
this.keyboardListener = (res = {}) => { |
|
|
|
|
|
this.keyboardHeight = res.height || 0 |
|
|
|
|
|
this.$nextTick(() => { |
|
|
|
|
|
this.scrollToBottom(false) |
|
|
|
|
|
}) |
|
|
|
|
|
} |
|
|
|
|
|
uni.onKeyboardHeightChange(this.keyboardListener) |
|
|
|
|
|
}, |
|
|
|
|
|
unbindKeyboardListener() { |
|
|
|
|
|
if (!this.keyboardListener) return |
|
|
|
|
|
if (uni.offKeyboardHeightChange) { |
|
|
|
|
|
uni.offKeyboardHeightChange(this.keyboardListener) |
|
|
|
|
|
} |
|
|
|
|
|
this.keyboardListener = null |
|
|
|
|
|
}, |
|
|
|
|
|
handleInputFocus(e) { |
|
|
|
|
|
const height = e && e.detail ? e.detail.height : 0 |
|
|
|
|
|
if (height) { |
|
|
|
|
|
this.keyboardHeight = height |
|
|
|
|
|
} |
|
|
|
|
|
this.$nextTick(() => { |
|
|
|
|
|
this.scrollToBottom(false) |
|
|
|
|
|
}) |
|
|
|
|
|
}, |
|
|
|
|
|
handleInputBlur() { |
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
|
this.keyboardHeight = 0 |
|
|
|
|
|
}, 120) |
|
|
|
|
|
}, |
|
|
startTimer() { |
|
|
startTimer() { |
|
|
// 已匹配的陪伴关系可持续聊天,不再自动倒计时结束。 |
|
|
// 已匹配的陪伴关系可持续聊天,不再自动倒计时结束。 |
|
|
}, |
|
|
}, |
|
|
@ -569,6 +632,24 @@ |
|
|
this.innerAudioContext.play() |
|
|
this.innerAudioContext.play() |
|
|
}, 80) |
|
|
}, 80) |
|
|
}, |
|
|
}, |
|
|
|
|
|
genderText(gender) { |
|
|
|
|
|
if (gender === 'male') return '男生' |
|
|
|
|
|
if (gender === 'female') return '女生' |
|
|
|
|
|
return '未设置' |
|
|
|
|
|
}, |
|
|
|
|
|
modePreferenceText(mode) { |
|
|
|
|
|
if (mode === 'i') return 'i 人' |
|
|
|
|
|
if (mode === 'e') return 'e 人' |
|
|
|
|
|
return '都可以' |
|
|
|
|
|
}, |
|
|
|
|
|
genderPreferenceText(gender) { |
|
|
|
|
|
if (gender === 'male') return '男生' |
|
|
|
|
|
if (gender === 'female') return '女生' |
|
|
|
|
|
return '不限性别' |
|
|
|
|
|
}, |
|
|
|
|
|
targetText(mode, gender) { |
|
|
|
|
|
return this.modePreferenceText(mode) + ' · ' + this.genderPreferenceText(gender) |
|
|
|
|
|
}, |
|
|
async openTargetProfile() { |
|
|
async openTargetProfile() { |
|
|
await this.loadTargetProfile() |
|
|
await this.loadTargetProfile() |
|
|
if (!this.targetProfile || !this.targetProfile.userId) { |
|
|
if (!this.targetProfile || !this.targetProfile.userId) { |
|
|
@ -1262,6 +1343,30 @@ |
|
|
font-size: 23rpx; |
|
|
font-size: 23rpx; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.profile-info-grid { |
|
|
|
|
|
display: flex; |
|
|
|
|
|
gap: 18rpx; |
|
|
|
|
|
margin-top: 26rpx; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.profile-info { |
|
|
|
|
|
flex: 1; |
|
|
|
|
|
padding: 18rpx 14rpx; |
|
|
|
|
|
border-radius: 24rpx; |
|
|
|
|
|
color: rgba(22, 27, 46, 0.72); |
|
|
|
|
|
background: rgba(238, 244, 255, 0.72); |
|
|
|
|
|
font-size: 24rpx; |
|
|
|
|
|
font-weight: 800; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.profile-info-label { |
|
|
|
|
|
display: block; |
|
|
|
|
|
margin-bottom: 8rpx; |
|
|
|
|
|
color: rgba(22, 27, 46, 0.38); |
|
|
|
|
|
font-size: 20rpx; |
|
|
|
|
|
font-weight: 600; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
.profile-intro { |
|
|
.profile-intro { |
|
|
margin-top: 26rpx; |
|
|
margin-top: 26rpx; |
|
|
padding: 24rpx; |
|
|
padding: 24rpx; |
|
|
|