chore:语音气泡正常
This commit is contained in:
@@ -148,9 +148,10 @@
|
||||
<text class="message-text" selectable>{{ message.content }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- PR-USER-VOICE-BUBBLE (2026-09-28): 用户语音消息气泡
|
||||
模仿微信/Telegram 的语音消息样式:
|
||||
[▶播放] [语音 + 进度条] [时长]
|
||||
<!-- PR-USER-VOICE-BUBBLE (2026-09-29): 用户语音消息气泡 —— 微信原生风格
|
||||
- 左侧显示时长 X", 右侧显示扬声器 + 声波动画
|
||||
- 气泡右侧带小三角尾巴
|
||||
- 宽度按时长比例伸缩 (短语音窄、长语音宽)
|
||||
PR-USER-MULTI-VOICE (2026-09-28): 支持多条语音同时发送,
|
||||
每条 voice 项都有自己的 url/duration/playing 字段,
|
||||
模板 v-for 渲染多个 .voice-msg 行, 每行点击 → openVoiceOverlay(v)
|
||||
@@ -158,17 +159,16 @@
|
||||
<view
|
||||
v-else-if="message.type === 'audio'"
|
||||
class="message-bubble user-audio-bubble">
|
||||
<view v-for="(v, idx) in (message.voiceList || [])" :key="idx" class="voice-msg" @click.stop="openVoiceOverlay(v)">
|
||||
<view class="voice-play-btn" :class="{ playing: v.playing }">
|
||||
<text class="icondiy" :class="v.playing ? 'icon-system-pause-circle-fill' : 'icon-system-play-circle-fill'"></text>
|
||||
</view>
|
||||
<view class="voice-info">
|
||||
<text class="voice-label">{{ $lang('ai-chat.index.audioMessage') || '语音' }}</text>
|
||||
<view class="voice-progress-track">
|
||||
<view class="voice-progress-bar" :style="{ width: getVoiceProgress(v) + '%' }"></view>
|
||||
<view v-for="(v, idx) in (message.voiceList || [])" :key="idx" class="voice-msg" :style="{ width: getVoiceBubbleWidth(v.duration) }" @click.stop="openVoiceOverlay(v)">
|
||||
<text class="voice-duration">{{ getVoiceDurationText(v.duration) }}</text>
|
||||
<view class="voice-speaker" :class="{ playing: v.playing }">
|
||||
<view class="voice-speaker-body"></view>
|
||||
<view class="voice-speaker-cone"></view>
|
||||
<view class="voice-speaker-waves">
|
||||
<view class="voice-speaker-wave"></view>
|
||||
<view class="voice-speaker-wave"></view>
|
||||
</view>
|
||||
</view>
|
||||
<text class="voice-duration">{{ formatDuration((v.duration || 0) / 1000) }}</text>
|
||||
</view>
|
||||
<!-- 语音 + 文字混合时, 文字写在语音列表下方 -->
|
||||
<view v-if="message.content" class="voice-bubble-text">
|
||||
@@ -3273,6 +3273,24 @@ _handleStreamError(msg) {
|
||||
return Math.min(100, Math.max(0, (cur / dur) * 100))
|
||||
},
|
||||
|
||||
// PR-USER-VOICE-BUBBLE-WECHAT (2026-09-29): 微信风格语音气泡宽度按时长比例伸缩,
|
||||
// 短语音窄、长语音宽, 更像微信原生体验。
|
||||
getVoiceBubbleWidth(durationMs) {
|
||||
const dur = Number(durationMs) || 0
|
||||
const seconds = dur / 1000
|
||||
const minW = 200
|
||||
const maxW = 420
|
||||
const maxDuration = 60
|
||||
const ratio = Math.min(1, Math.max(0, seconds / maxDuration))
|
||||
return `${Math.round(minW + ratio * (maxW - minW))}rpx`
|
||||
},
|
||||
|
||||
// PR-USER-VOICE-BUBBLE-WECHAT (2026-09-29): 微信语音气泡时长显示为 X" 格式
|
||||
getVoiceDurationText(durationMs) {
|
||||
const seconds = Math.round((Number(durationMs) || 0) / 1000)
|
||||
return seconds + '"'
|
||||
},
|
||||
|
||||
// 格式化 AI 处理耗时 (秒 -> "XmXXs" 风格, 0 时显示 "<1s")
|
||||
formatElapsed(seconds) {
|
||||
if (seconds === undefined || seconds === null || isNaN(seconds)) return '1s'
|
||||
@@ -5425,6 +5443,12 @@ _handleStreamError(msg) {
|
||||
display: none !important;
|
||||
content: none !important;
|
||||
}
|
||||
|
||||
/* 语音气泡保持与文字气泡一致的颜色 */
|
||||
&.user-audio-bubble {
|
||||
background: #f5f5f5 !important;
|
||||
padding: 14rpx 22rpx !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6953,24 +6977,40 @@ _handleStreamError(msg) {
|
||||
}
|
||||
}
|
||||
|
||||
/* PR-USER-VOICE-BUBBLE (2026-09-28): 用户语音消息气泡
|
||||
模仿微信/Telegram 语音消息样式: 主色实心圆角胶囊 + 播放按钮 + 进度条 + 时长
|
||||
/* PR-USER-VOICE-BUBBLE (2026-09-29): 用户语音消息气泡 —— 微信原生风格
|
||||
- 左侧显示时长 X", 右侧显示扬声器 + 声波动画
|
||||
- 气泡右侧带小三角尾巴
|
||||
- 宽度按时长比例伸缩 (短语音窄、长语音宽)
|
||||
与 .user-file-bubble 复用基础结构但视觉完全不同, 这里覆盖其样式
|
||||
PR-USER-MULTI-VOICE (2026-09-28): 改为 flex-column 让多条 voice-msg 垂直堆叠 */
|
||||
.user-audio-bubble {
|
||||
/* 覆盖 .user-file-bubble 的 padding/file-item */
|
||||
padding: 18rpx 24rpx !important;
|
||||
min-width: 320rpx;
|
||||
max-width: 480rpx;
|
||||
background: $color-primary !important;
|
||||
border-radius: 24rpx !important;
|
||||
color: #fff !important;
|
||||
padding: 14rpx 22rpx !important;
|
||||
min-width: 180rpx;
|
||||
max-width: 460rpx;
|
||||
background: #f5f5f5 !important;
|
||||
border-radius: 16rpx !important;
|
||||
color: #1f1f1f !important;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex !important;
|
||||
overflow: visible;
|
||||
display: inline-flex !important;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
gap: 12rpx;
|
||||
width: auto !important;
|
||||
|
||||
/* 微信气泡右侧小三角尾巴 */
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: -12rpx;
|
||||
top: 20rpx;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: 12rpx solid transparent;
|
||||
border-left-color: $color-primary;
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
/* 上传中半透明 */
|
||||
&.is-uploading {
|
||||
@@ -6978,93 +7018,90 @@ _handleStreamError(msg) {
|
||||
}
|
||||
&.is-error {
|
||||
background: #c0c4cc !important;
|
||||
&::after {
|
||||
border-left-color: #c0c4cc !important;
|
||||
}
|
||||
}
|
||||
|
||||
.voice-msg {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
|
||||
.voice-play-btn {
|
||||
flex-shrink: 0;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.22);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background 0.2s ease, transform 0.15s ease;
|
||||
|
||||
.icondiy {
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* 播放中按钮背景加深 */
|
||||
&.playing {
|
||||
background: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
&.disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.voice-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
|
||||
.voice-label {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
line-height: 1;
|
||||
/* PR-CROSS-PLATFORM-VOICE-LABEL (2026-09-28): 异常长 i18n 文案或
|
||||
user-agent 自带文本时, 不撑破气泡宽度, 跨平台一致 */
|
||||
max-width: 240rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.voice-progress-track {
|
||||
height: 8rpx;
|
||||
background: rgba(255, 255, 255, 0.28);
|
||||
border-radius: 4rpx;
|
||||
overflow: hidden;
|
||||
|
||||
.voice-progress-bar {
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
border-radius: 4rpx;
|
||||
/* 进度条用 transition 平滑过渡, 配合 innerAudioCtx.onTimeUpdate 的秒级刷新 */
|
||||
transition: width 0.15s linear;
|
||||
}
|
||||
}
|
||||
}
|
||||
width: 100%;
|
||||
min-height: 44rpx;
|
||||
|
||||
.voice-duration {
|
||||
flex-shrink: 0;
|
||||
font-size: 26rpx;
|
||||
color: #fff;
|
||||
font-size: 30rpx;
|
||||
color: #1f1f1f;
|
||||
font-variant-numeric: tabular-nums;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* 微信风格语音图标: 扬声器 + 右侧声波 */
|
||||
.voice-speaker {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 36rpx;
|
||||
position: relative;
|
||||
|
||||
/* 扬声器后身 */
|
||||
.voice-speaker-body {
|
||||
width: 8rpx;
|
||||
height: 14rpx;
|
||||
background: #1f1f1f;
|
||||
border-radius: 2rpx 0 0 2rpx;
|
||||
}
|
||||
|
||||
/* 扬声器喇叭口(指向右侧) */
|
||||
.voice-speaker-cone {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 10rpx solid transparent;
|
||||
border-bottom: 10rpx solid transparent;
|
||||
border-right: 14rpx solid #1f1f1f;
|
||||
margin-right: 4rpx;
|
||||
}
|
||||
|
||||
/* 右侧两条声波弧线 */
|
||||
.voice-speaker-waves {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 2rpx;
|
||||
|
||||
.voice-speaker-wave {
|
||||
border-top: 3rpx solid transparent;
|
||||
border-right: 3rpx solid #1f1f1f;
|
||||
border-bottom: 3rpx solid transparent;
|
||||
border-left: 3rpx solid transparent;
|
||||
border-radius: 0 50% 50% 0;
|
||||
|
||||
&:nth-child(1) {
|
||||
width: 8rpx;
|
||||
height: 14rpx;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
width: 14rpx;
|
||||
height: 22rpx;
|
||||
margin-left: -8rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* PR-USER-MULTI-VOICE (2026-09-28): 语音 + 文字混合时, 文字写在语音列表下方
|
||||
与语音行共用主色背景, 顶部分割线划开两块内容 */
|
||||
.voice-bubble-text {
|
||||
padding-top: 12rpx;
|
||||
border-top: 1rpx solid rgba(255, 255, 255, 0.2);
|
||||
border-top: 1rpx solid rgba(0, 0, 0, 0.12);
|
||||
|
||||
.message-text {
|
||||
font-size: 28rpx;
|
||||
color: #fff;
|
||||
color: #1f1f1f;
|
||||
line-height: 1.5;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user