feat(消息发送): 添加发送状态控制和自动回复提示
- 添加 isSending 状态防止重复发送 - 发送中禁用按钮并显示加载状态 - 立即清空输入框提升用户体验 - 添加自动回复提示消息 - 优化发送失败时的输入框恢复逻辑
Showing
1 changed file
with
43 additions
and
6 deletions
| ... | @@ -58,11 +58,11 @@ | ... | @@ -58,11 +58,11 @@ |
| 58 | <!-- 输入框区域 --> | 58 | <!-- 输入框区域 --> |
| 59 | <view class="chat-input-area"> | 59 | <view class="chat-input-area"> |
| 60 | <view class="input-container"> | 60 | <view class="input-container"> |
| 61 | - <nut-textarea v-model="inputMessage" placeholder="请输入回复内容..." :rows="2" :max-length="500" | 61 | + <textarea v-model="inputMessage" placeholder="请输入回复内容..." :rows="2" :max-length="500" |
| 62 | :cursorSpacing="100" class="message-input" @focus="handleInputFocus" /> | 62 | :cursorSpacing="100" class="message-input" @focus="handleInputFocus" /> |
| 63 | <nut-button type="primary" size="small" color="orange" @click="sendMessage" | 63 | <nut-button type="primary" size="small" color="orange" @click="sendMessage" |
| 64 | - :disabled="!inputMessage.trim()" class="send-button"> | 64 | + :disabled="!inputMessage.trim() || isSending" class="send-button"> |
| 65 | - 发送 | 65 | + {{ isSending ? '发送中...' : '发送' }} |
| 66 | </nut-button> | 66 | </nut-button> |
| 67 | </view> | 67 | </view> |
| 68 | </view> | 68 | </view> |
| ... | @@ -114,6 +114,8 @@ const visible = computed({ | ... | @@ -114,6 +114,8 @@ const visible = computed({ |
| 114 | 114 | ||
| 115 | // 输入消息内容 | 115 | // 输入消息内容 |
| 116 | const inputMessage = ref('') | 116 | const inputMessage = ref('') |
| 117 | +// 发送状态标记 | ||
| 118 | +const isSending = ref(false) | ||
| 117 | 119 | ||
| 118 | // 滚动相关 | 120 | // 滚动相关 |
| 119 | const scrollTop = ref(0) | 121 | const scrollTop = ref(0) |
| ... | @@ -229,6 +231,11 @@ const sendMessage = async () => { | ... | @@ -229,6 +231,11 @@ const sendMessage = async () => { |
| 229 | return | 231 | return |
| 230 | } | 232 | } |
| 231 | 233 | ||
| 234 | + // 防止重复发送 | ||
| 235 | + if (isSending.value) { | ||
| 236 | + return | ||
| 237 | + } | ||
| 238 | + | ||
| 232 | // 只有聊天类型才能发送消息 | 239 | // 只有聊天类型才能发送消息 |
| 233 | if (props.conversation?.type !== 'chat') { | 240 | if (props.conversation?.type !== 'chat') { |
| 234 | Taro.showToast({ | 241 | Taro.showToast({ |
| ... | @@ -240,6 +247,11 @@ const sendMessage = async () => { | ... | @@ -240,6 +247,11 @@ const sendMessage = async () => { |
| 240 | 247 | ||
| 241 | const messageContent = inputMessage.value.trim() | 248 | const messageContent = inputMessage.value.trim() |
| 242 | 249 | ||
| 250 | + // 设置发送状态并立即清空输入框 | ||
| 251 | + isSending.value = true | ||
| 252 | + inputMessage.value = '' | ||
| 253 | + await nextTick() // 确保输入框立即清空 | ||
| 254 | + | ||
| 243 | try { | 255 | try { |
| 244 | // 先添加到本地显示(新消息添加到末尾) | 256 | // 先添加到本地显示(新消息添加到末尾) |
| 245 | const newMessage = { | 257 | const newMessage = { |
| ... | @@ -252,9 +264,6 @@ const sendMessage = async () => { | ... | @@ -252,9 +264,6 @@ const sendMessage = async () => { |
| 252 | } | 264 | } |
| 253 | messages.value.push(newMessage) | 265 | messages.value.push(newMessage) |
| 254 | 266 | ||
| 255 | - // 清空输入框 | ||
| 256 | - inputMessage.value = '' | ||
| 257 | - | ||
| 258 | // 滚动到底部 | 267 | // 滚动到底部 |
| 259 | await nextTick() | 268 | await nextTick() |
| 260 | scrollToBottom() | 269 | scrollToBottom() |
| ... | @@ -271,6 +280,25 @@ const sendMessage = async () => { | ... | @@ -271,6 +280,25 @@ const sendMessage = async () => { |
| 271 | newMessage.id = response.data.id | 280 | newMessage.id = response.data.id |
| 272 | } | 281 | } |
| 273 | 282 | ||
| 283 | + // 添加自动回复提示消息 | ||
| 284 | + setTimeout(() => { | ||
| 285 | + const autoReplyMessage = { | ||
| 286 | + type: 'received', | ||
| 287 | + content: '您的消息已发送,我们会尽快回复您,请耐心等待。', | ||
| 288 | + time: new Date().toLocaleTimeString('zh-CN', { | ||
| 289 | + hour: '2-digit', | ||
| 290 | + minute: '2-digit' | ||
| 291 | + }), | ||
| 292 | + isAutoReply: true // 标记为自动回复消息 | ||
| 293 | + } | ||
| 294 | + messages.value.push(autoReplyMessage) | ||
| 295 | + | ||
| 296 | + // 滚动到底部显示新消息 | ||
| 297 | + nextTick(() => { | ||
| 298 | + scrollToBottom() | ||
| 299 | + }) | ||
| 300 | + }, 1000) // 延迟1秒显示自动回复 | ||
| 301 | + | ||
| 274 | // 触发发送消息事件,通知父组件更新列表 | 302 | // 触发发送消息事件,通知父组件更新列表 |
| 275 | emit('sendMessage', { | 303 | emit('sendMessage', { |
| 276 | conversation: props.conversation, | 304 | conversation: props.conversation, |
| ... | @@ -284,6 +312,9 @@ const sendMessage = async () => { | ... | @@ -284,6 +312,9 @@ const sendMessage = async () => { |
| 284 | } else { | 312 | } else { |
| 285 | // 发送失败,移除本地消息 | 313 | // 发送失败,移除本地消息 |
| 286 | messages.value.pop() | 314 | messages.value.pop() |
| 315 | + // 只在真正失败时才恢复输入框内容,避免闪烁 | ||
| 316 | + await nextTick() | ||
| 317 | + inputMessage.value = messageContent | ||
| 287 | Taro.showToast({ | 318 | Taro.showToast({ |
| 288 | title: response.msg || '发送失败', | 319 | title: response.msg || '发送失败', |
| 289 | icon: 'error' | 320 | icon: 'error' |
| ... | @@ -293,10 +324,16 @@ const sendMessage = async () => { | ... | @@ -293,10 +324,16 @@ const sendMessage = async () => { |
| 293 | console.error('发送消息失败:', error) | 324 | console.error('发送消息失败:', error) |
| 294 | // 发送失败,移除本地消息 | 325 | // 发送失败,移除本地消息 |
| 295 | messages.value.pop() | 326 | messages.value.pop() |
| 327 | + // 只在真正失败时才恢复输入框内容,避免闪烁 | ||
| 328 | + await nextTick() | ||
| 329 | + inputMessage.value = messageContent | ||
| 296 | Taro.showToast({ | 330 | Taro.showToast({ |
| 297 | title: '发送失败', | 331 | title: '发送失败', |
| 298 | icon: 'error' | 332 | icon: 'error' |
| 299 | }) | 333 | }) |
| 334 | + } finally { | ||
| 335 | + // 重置发送状态 | ||
| 336 | + isSending.value = false | ||
| 300 | } | 337 | } |
| 301 | } | 338 | } |
| 302 | 339 | ... | ... |
-
Please register or login to post a comment