feat(ai-chat): 支持长按复制消息内容
This commit is contained in:
@@ -39,8 +39,13 @@
|
||||
<view class="message-nickname" v-else>
|
||||
<text class="message-time">{{ formatTime(message.timestamp) }}</text>
|
||||
</view>
|
||||
<view class="message-bubble">
|
||||
<text class="message-text">{{ message.content }}</text>
|
||||
<view class="message-bubble"
|
||||
@contextmenu.prevent="onMessageLongPress(message)"
|
||||
@touchstart="onBubbleTouchStart($event, message)"
|
||||
@touchmove="onBubbleTouchMove"
|
||||
@touchend="onBubbleTouchEnd"
|
||||
@touchcancel="onBubbleTouchEnd">
|
||||
<text class="message-text" selectable>{{ message.content }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -63,7 +68,12 @@
|
||||
<text class="ai-nickname-text">智能助手</text>
|
||||
<text class="message-time">{{ formatTime(message.timestamp) }}</text>
|
||||
</view>
|
||||
<view class="message-bubble">
|
||||
<view class="message-bubble"
|
||||
@contextmenu.prevent="onMessageLongPress(message)"
|
||||
@touchstart="onBubbleTouchStart($event, message)"
|
||||
@touchmove="onBubbleTouchMove"
|
||||
@touchend="onBubbleTouchEnd"
|
||||
@touchcancel="onBubbleTouchEnd">
|
||||
<!-- 文本消息 -->
|
||||
<view v-if="message.type === 'text'" class="text-content">
|
||||
<rich-text :nodes="parseMarkdown(message.content)" />
|
||||
@@ -512,7 +522,11 @@ export default {
|
||||
lastScrollTop: 0,
|
||||
historyOffset: 0,
|
||||
isFetchingHistory: false,
|
||||
keyboardHeight: 0
|
||||
keyboardHeight: 0,
|
||||
|
||||
// 手动长按检测相关(touch 计时器)
|
||||
_longPressTimer: null,
|
||||
_longPressMessage: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -1755,7 +1769,126 @@ _handleStreamError(msg) {
|
||||
|
||||
this.$emit('action-click', { action, message })
|
||||
},
|
||||
|
||||
|
||||
// 长按消息→直接复制
|
||||
onMessageLongPress(message) {
|
||||
// loading 消息不允许操作
|
||||
if (message.type === 'loading') return
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
// 微信小程序:震动反馈提升手感(小米等 Android 设备也可用)
|
||||
try {
|
||||
wx.vibrateShort({ type: 'medium' })
|
||||
} catch (e) { /* 不支持震动的设备忽略 */ }
|
||||
// #endif
|
||||
|
||||
this.copyMessage(message)
|
||||
},
|
||||
|
||||
// 手动长按检测(touch 事件方案,兼容所有平台)
|
||||
// 在 H5 desktop 上用 @contextmenu 触发,
|
||||
// 在 H5 mobile / 小米微信小程序等场景靠 touch 计时器兜底
|
||||
onBubbleTouchStart(event, message) {
|
||||
// 单指长按才触发,忽略多点触控
|
||||
if (event.touches && event.touches.length > 1) return
|
||||
this._longPressMessage = message
|
||||
this._longPressTimer = setTimeout(() => {
|
||||
this._longPressTimer = null
|
||||
this.onMessageLongPress(message)
|
||||
}, 500) // 和系统长按接近的 500ms
|
||||
},
|
||||
onBubbleTouchMove() {
|
||||
// 手指移动(滚动列表)→ 取消长按
|
||||
if (this._longPressTimer) {
|
||||
clearTimeout(this._longPressTimer)
|
||||
this._longPressTimer = null
|
||||
}
|
||||
this._longPressMessage = null
|
||||
},
|
||||
onBubbleTouchEnd() {
|
||||
// 手指提前松开 → 取消长按
|
||||
if (this._longPressTimer) {
|
||||
clearTimeout(this._longPressTimer)
|
||||
this._longPressTimer = null
|
||||
}
|
||||
this._longPressMessage = null
|
||||
},
|
||||
|
||||
// 复制消息内容到剪贴板(兼容 H5 / 微信小程序)
|
||||
copyMessage(message) {
|
||||
let copyText = message.content || ''
|
||||
if (!copyText && message.type === 'text') {
|
||||
copyText = message.content || ''
|
||||
}
|
||||
if (!copyText) {
|
||||
uni.showToast({ title: '无可复制的内容', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
// #ifdef H5
|
||||
// H5:检测是否有局部文本被选中,有则复制选区文本,无则复制全文
|
||||
const selection = window.getSelection()
|
||||
const selectedText = selection && selection.toString().trim()
|
||||
const finalText = selectedText || copyText
|
||||
|
||||
// H5 优先使用 Clipboard API,降级使用 document.execCommand
|
||||
try {
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(finalText).then(() => {
|
||||
uni.showToast({ title: selectedText ? '已复制选中内容' : '已复制', icon: 'success' })
|
||||
}).catch(() => {
|
||||
this._fallbackCopyH5(finalText)
|
||||
})
|
||||
} else {
|
||||
this._fallbackCopyH5(finalText)
|
||||
}
|
||||
} catch (e) {
|
||||
this._fallbackCopyH5(finalText)
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
// 微信小程序专用(含小米等 Android 兼容)
|
||||
console.log('[AI Chat] 开始复制, 内容长度:', copyText.length, '内容前50字:', copyText.substring(0, 50))
|
||||
uni.setClipboardData({
|
||||
data: copyText,
|
||||
success: () => {
|
||||
console.log('[AI Chat] 复制成功')
|
||||
uni.showToast({ title: '已复制', icon: 'success' })
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('[AI Chat] 复制失败, 详细错误:', JSON.stringify(err))
|
||||
console.error('[AI Chat] 复制失败 errMsg:', err.errMsg || err.message || '无错误消息')
|
||||
uni.showToast({ title: '复制失败: ' + (err.errMsg || '未知错误'), icon: 'none', duration: 3000 })
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
|
||||
// H5 降级复制方案(document.execCommand + textarea)
|
||||
_fallbackCopyH5(text) {
|
||||
try {
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.value = text
|
||||
textarea.style.position = 'fixed'
|
||||
textarea.style.left = '-9999px'
|
||||
textarea.style.top = '-9999px'
|
||||
textarea.style.opacity = '0'
|
||||
document.body.appendChild(textarea)
|
||||
textarea.select()
|
||||
textarea.setSelectionRange(0, text.length)
|
||||
const success = document.execCommand('copy')
|
||||
document.body.removeChild(textarea)
|
||||
if (success) {
|
||||
uni.showToast({ title: '已复制', icon: 'success' })
|
||||
} else {
|
||||
uni.showToast({ title: '复制失败', icon: 'none' })
|
||||
}
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '复制失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
|
||||
// 重试消息
|
||||
async retryMessage(message) {
|
||||
// 找到对应的用户消息
|
||||
@@ -4066,5 +4199,12 @@ $radius-lg: 36rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// H5 下允许 rich-text 内容文本选择(鼠标拖选复制)
|
||||
.text-content,
|
||||
.markdown-body {
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user