Merge branch 'temp221' into dev/1.0

This commit is contained in:
2026-10-09 16:57:33 +08:00
2 changed files with 112 additions and 44 deletions

View File

@@ -257,7 +257,11 @@ const localDevConfig = ({
// })['2811']; // 本地
// })['5g-317']; // 电磁兼容
// })['5g-351']; //
<<<<<<< HEAD
})['5g-97']; // 本地
=======
})['2881']; // 本地
>>>>>>> temp221
// })['local-2']; // 本地
// })['local-11']; // 本地
// })['1-test']; // test

View File

@@ -234,10 +234,10 @@
<!-- #ifdef MP-WEIXIN -->
<!-- PR-CHAT-SELECTABLE (2026-09-23): rich-text 不支持文本选中, 改用 mp-html + selectable
开启原生长按选中(剪切/复制/粘贴/全选); force 同时兼容微信 iOS -->
<mp-html class="ai-message-rich" :content="getAIDisplayContent(message.content)" selectable="force" />
<mp-html class="ai-message-rich" :content="getAIDisplayContent(message.content, message.isWelcome)" selectable="force" />
<!-- #endif -->
<!-- #ifdef H5 -->
<view class="ai-message-text selectable-md" v-html="getAIDisplayContent(message.content)"></view>
<view class="ai-message-text selectable-md" v-html="getAIDisplayContent(message.content, message.isWelcome)"></view>
<!-- #endif -->
</view>
@@ -250,10 +250,10 @@
<view class="markdown-body">
<!-- #ifdef MP-WEIXIN -->
<!-- PR-CHAT-SELECTABLE (2026-09-23): 同上, mp-html + selectable 支持长按选中复制 -->
<mp-html class="ai-message-rich" :content="getAIDisplayContent(message.content)" selectable="force" />
<mp-html class="ai-message-rich" :content="getAIDisplayContent(message.content, message.isWelcome)" selectable="force" />
<!-- #endif -->
<!-- #ifdef H5 -->
<view class="selectable-md" v-html="getAIDisplayContent(message.content)"></view>
<view class="selectable-md" v-html="getAIDisplayContent(message.content, message.isWelcome)"></view>
<!-- #endif -->
</view>
</view>
@@ -529,17 +529,30 @@
① 默认(未输入/未聚焦): 单行胶囊 —— 语音图标 + 输入框 + 加号
② 语音模式(点语音图标进入): 「按住说话」按钮 + 底部工具栏(表情/模型/加号)
③ 文字输入(聚焦或有文字): 输入框独占一行 + 底部工具栏(语音/模型/加号/发送) -->
<!-- 语音模式: 按住说话 (PR-FRONTEND-VOICE-INPUT 2026-09-24) -->
<!-- 语音模式 (PR-VOICE-MODE-ONE-ROW-V2): 整行即白色胶囊(flex),
键盘图标在左、+ 在右、按住说话占中间 —— 不依赖绝对定位, 结构上保证单行 -->
<view
v-if="voiceMode"
class="voice-hold-btn"
:class="{ 'is-holding': voiceHolding, 'is-cancelling': voiceHoldCancelling }"
@touchstart.stop.prevent="onVoiceHoldStart"
@touchmove.stop.prevent="onVoiceHoldMove"
@touchend.stop.prevent="onVoiceHoldEnd"
@touchcancel.stop.prevent="onVoiceHoldCancel"
@click.stop="onVoiceHoldEnd">
<text class="voice-hold-text">{{ voiceHoldText }}</text>
class="voice-mode-row"
:class="{ 'is-holding': voiceHolding, 'is-cancelling': voiceHoldCancelling }">
<!-- 左侧: 键盘图标 (点击回到文字输入) -->
<view class="voice-side-btn voice-side-left" @click="exitVoiceMode">
<view class="emoji-icon-bg"></view>
</view>
<!-- 中间: 按住说话 (按住录音, 松开发送) -->
<view
class="voice-hold-btn"
@touchstart.stop.prevent="onVoiceHoldStart"
@touchmove.stop.prevent="onVoiceHoldMove"
@touchend.stop.prevent="onVoiceHoldEnd"
@touchcancel.stop.prevent="onVoiceHoldCancel"
@click.stop="onVoiceHoldEnd">
<text class="voice-hold-text">{{ voiceHoldText }}</text>
</view>
<!-- 右侧: 加号 -->
<button class="top-plus-btn voice-side-right" @click="showMoreTools">
<text class="plus-icon">+</text>
</button>
</view>
<!-- 按住时的「上滑取消」提示 (H5/MP 共用) -->
<view v-if="voiceHolding" class="voice-cancel-hint">{{ voiceHoldCancelling ? $lang('ai-chat.index.slideUpToCancelHint') : $lang('ai-chat.index.slideUpToCancel') }}</view>
@@ -587,14 +600,10 @@
</view>
</view>
<!-- 底部工具栏行: 语音模式 / 文字输入时显示 -->
<view v-if="voiceMode || showTextMode" class="input-toolbar-row">
<!-- 语音模式: 键盘图标 (点击回到文字输入) -->
<view v-if="voiceMode" class="toolbar-round-btn" @click="exitVoiceMode">
<view class="emoji-icon-bg"></view>
</view>
<!-- 底部工具栏行: 仅文字输入时显示 (语音模式的键盘/加号已并入按住说话同一行) -->
<view v-if="showTextMode" class="input-toolbar-row">
<!-- 文字输入: 语音图标 (切到按住说话) -->
<view v-else class="toolbar-round-btn" @click="enterVoiceMode">
<view class="toolbar-round-btn" @click="enterVoiceMode">
<view class="voice-icon-bg"></view>
</view>
@@ -3928,17 +3937,24 @@ _handleStreamError(msg) {
},
// AI 消息渲染(含关键词高亮)
getAIDisplayContent(content) {
// isWelcome: 欢迎语消息 — 小程序端 mp-html 组件有样式隔离, 页面样式(::v-deep)进不去,
// 图片间距只能在内容层面改内联样式(机器人图片与下方标题的间距, 对齐 WorkBuddy 参考稿)
getAIDisplayContent(content, isWelcome) {
let html
if (!this.searchKeyword || !content) {
return this.parseMarkdown(content)
html = this.parseMarkdown(content)
} else if (content.toLowerCase().indexOf(this.searchKeyword.toLowerCase()) === -1) {
html = this.parseMarkdown(content)
} else {
// 先做 markdown 渲染,再做关键词高亮
const mdHtml = this.parseMarkdown(content)
html = this._wrapHighlightInHtml(mdHtml, this.searchKeyword)
}
// 仅当内容包含关键词时才执行高亮,避免非匹配消息的额外处理
if (content.toLowerCase().indexOf(this.searchKeyword.toLowerCase()) === -1) {
return this.parseMarkdown(content)
if (isWelcome && html) {
// 仅替换 img 的 margin(机器人图片), 不影响列表等其他元素的 margin:8rpx 0
html = html.replace(/(<img[^>]*style="[^"]*?)margin:\s*8rpx 0;/, '$1margin:0 0 48rpx 0;')
}
// 先做 markdown 渲染,再做关键词高亮
const mdHtml = this.parseMarkdown(content)
return this._wrapHighlightInHtml(mdHtml, this.searchKeyword)
return html
},
// 在纯文本中高亮关键词(用于用户消息)
@@ -4780,11 +4796,24 @@ _handleStreamError(msg) {
background: #ffffff !important;
}
// 欢迎语消息: 图片(机器人)与下方标题的间距加大, 对齐 WorkBuddy 参考稿
// 图片是 v-html 插入的 <img>, 不带 scoped 的 data-v 属性,
// 普通 img 选择器编译后会匹配不到, 必须用 ::v-deep 穿透
&.is-welcome .ai-message .message-bubble ::v-deep img {
margin-bottom: 48rpx !important;
}
// 欢迎语消息: 隐藏上方时间戳
&.is-welcome .ai-message .message-nickname.ai-nickname {
display: none;
}
// 欢迎语消息: 标题与快捷问题的间距, 与"图片→标题"保持一致 (48rpx)
// 下方 5326 行的 .quick-questions-row 嵌套链更长、特异性更高, 必须 !important 覆盖
&.is-welcome .quick-questions-row {
margin-top: 48rpx !important;
}
// 搜索定位 - 当前结果: 用柔和的浅绿底色提示 (WorkBuddy 风格, 无气泡 box-shadow)
&.search-current {
position: relative;
@@ -5323,7 +5352,7 @@ _handleStreamError(msg) {
margin-bottom: 16rpx;
padding: 12rpx 28rpx;
background: #ffffff;
border: 1rpx solid #e4e6eb;
border: 1rpx solid #eef0f3;
border-radius: 999rpx;
font-size: 26rpx;
color: #1a1a1a;
@@ -5331,7 +5360,7 @@ _handleStreamError(msg) {
transition: all 0.2s ease;
max-width: 100%;
line-height: 1.4;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.14);
&:last-child {
margin-bottom: 0;
@@ -5753,6 +5782,7 @@ _handleStreamError(msg) {
height: 56rpx;
padding: 0 20rpx;
gap: 8rpx;
margin-top: 20rpx;
.refresh-icon {
width: 28rpx;
@@ -5810,9 +5840,12 @@ _handleStreamError(msg) {
justify-content: space-between;
background: #ffffff;
border-radius: 999rpx;
padding: 18rpx 14rpx 18rpx 32rpx;
min-height: 108rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
/* 高度与语音模式胶囊(.voice-mode-row: height 96rpx)保持一致:
内部图标容器 76rpx + 上下 padding 10rpx*2 = 96rpx */
padding: 10rpx 14rpx 10rpx 32rpx;
min-height: 96rpx;
/* 与快捷问题按钮(.quick-question-chip)同款阴影, 白底更突出 */
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.14);
/* 无文字时左侧语音图标,点击打开语音面板
PR-INPUT-LAYOUT-SYMMETRY (2026-09-28): 容器从 52rpx 扩大到 76rpx,
@@ -5944,17 +5977,24 @@ _handleStreamError(msg) {
background-size: contain;
}
/* 展开态: 卡片变白底圆角容器, 内部输入行不再单独成胶囊 */
/* 展开态: 文字输入时卡片变白底圆角容器;
语音模式卡片保持透明, 白色胶囊由 .voice-mode-row 自身呈现 (避免双层白底) */
.input-card {
&.is-voice-mode,
&.is-text-mode {
background: #ffffff;
border-radius: 28rpx;
padding: 14rpx 16rpx 6rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
/* 与快捷问题按钮(.quick-question-chip)同款阴影 */
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.14);
}
&.is-voice-mode {
background: transparent;
border-radius: 0;
padding: 0;
box-shadow: none;
}
&.is-voice-mode .input-row-top,
&.is-text-mode .input-row-top {
background: transparent;
border-radius: 0;
@@ -5964,30 +6004,54 @@ _handleStreamError(msg) {
}
}
/* 语音模式: 按住说话 */
.voice-hold-btn {
/* 语音模式单行 (PR-VOICE-MODE-ONE-ROW-V2): 整行即白色胶囊(flex布局,不依赖绝对定位)
键盘图标在左 / 按住说话占中间 / + 在右, 与参考稿一致 */
.voice-mode-row {
position: relative; /* 「上滑取消」提示 .voice-cancel-hint 锚定到这里 */
display: flex;
align-items: center;
justify-content: center;
height: 96rpx;
margin: 4rpx 0 8rpx;
border-radius: 16rpx;
background: #f2f2f3;
position: relative; // PR-FRONTEND-VOICE-INPUT: 让 .voice-cancel-hint 锚定到这里
padding: 0 10rpx 0 24rpx;
border-radius: 999rpx;
background: #ffffff;
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.14);
transition: background-color 0.15s ease-out;
&.is-holding {
background: #e2e2e4;
background: #f2f2f3;
}
// PR-FRONTEND-VOICE-INPUT (2026-09-24): 上滑到 80px+ 视为取消手势
&.is-cancelling {
background: #fdecec;
.voice-hold-text {
color: #d23f3f;
}
}
.voice-side-btn {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
}
.voice-side-right {
margin-left: 8rpx;
}
/* 按住说话区域: 透明, 占据中间剩余全部空间 */
.voice-hold-btn {
flex: 1;
height: 100%;
min-width: 0;
display: flex;
align-items: center;
justify-content: center;
}
.voice-hold-text {
font-size: 30rpx;
color: #1f1f1f;