fix(ai-chat): 修复聊天页面键盘弹出时导航栏上移、输入框不可见、间距问题

- 自定义导航栏 position:absolute 固定在顶部,不随键盘上移
- textarea 设置 adjust-position=false,阻止系统上推页面
- 输入框后方加 spacer 垫高层,键盘弹出时将输入框推至键盘上方
- chat-content 根据平台设置不同 top 值(MP:0 / H5:44px)
- 去掉输入框多余的安全区域内边距
- scroll-view 底部 padding 随键盘高度动态调整
This commit is contained in:
2026-05-13 16:01:15 +08:00
parent 39245dfcac
commit 69466f410c
4 changed files with 53 additions and 73 deletions

View File

@@ -68,7 +68,7 @@ export default {
} }
return result; return result;
} catch (error) { } catch (error) {
console.error('[AI Service] Dify API请求失败,详细错误:', error); console.error('[AI Service] AiChat API请求失败,详细错误:', error);
console.error('[AI Service] 错误堆栈:', error.stack); console.error('[AI Service] 错误堆栈:', error.stack);
// 打印更多信息帮助调试 // 打印更多信息帮助调试
console.error('[AI Service] store 状态:', { console.error('[AI Service] store 状态:', {

View File

@@ -119,9 +119,9 @@ export class CustomerService {
} }
/** /**
* 跳转到 AI智能客服页面(Dify) * 跳转到 AI智能客服页面(AiChat)
*/ */
openDifyService() { openAiChatService() {
try { try {
// 清除未读数(如果存在) // 清除未读数(如果存在)
if (typeof this.vm.setAiUnreadCount === 'function') { if (typeof this.vm.setAiUnreadCount === 'function') {
@@ -177,7 +177,7 @@ export class CustomerService {
// 核心路由:根据 type 决定行为 // 核心路由:根据 type 决定行为
switch (config.type) { switch (config.type) {
case 'aikefu': case 'aikefu':
this.openDifyService(); this.openAiChatService();
break; break;
case 'wxwork': case 'wxwork':
this.openWxworkService(false, config, options); this.openWxworkService(false, config, options);
@@ -364,7 +364,7 @@ export class CustomerService {
openAliappService(config) { openAliappService(config) {
if (config.type === 'aikefu') { if (config.type === 'aikefu') {
this.openDifyService(); this.openAiChatService();
} else if (config.type === 'third') { } else if (config.type === 'third') {
this.openThirdService(config); this.openThirdService(config);
} else { } else {

View File

@@ -1,5 +1,5 @@
<template> <template>
<view class="ai-chat-container"> <view class="ai-chat-container" :style="aiChatContainerStyle">
<!-- 聊天消息列表 - 统一使用 scroll-into-view --> <!-- 聊天消息列表 - 统一使用 scroll-into-view -->
<scroll-view <scroll-view
class="chat-messages" class="chat-messages"
@@ -197,7 +197,7 @@
</scroll-view> </scroll-view>
<!-- 输入区域 --> <!-- 输入区域 -->
<view class="input-area" :style="inputAreaStyle" > <view class="input-area" :style="inputAreaStyle">
<view class="input-tools"> <view class="input-tools">
<button class="tool-btn" @click="showMoreTools" style="display: none;"> <button class="tool-btn" @click="showMoreTools" style="display: none;">
<text class="iconfont icon-plus">...</text> <text class="iconfont icon-plus">...</text>
@@ -215,7 +215,7 @@
:maxlength="500" :maxlength="500"
:auto-height="true" :auto-height="true"
:show-confirm-bar="false" :show-confirm-bar="false"
:adjust-position="true" :adjust-position="false"
:cursor-spacing="150" :cursor-spacing="150"
@confirm="sendMessage" @confirm="sendMessage"
@input="onInput" @input="onInput"
@@ -329,6 +329,9 @@
</view> </view>
</view> </view>
<!-- 键盘垫高层:键盘弹出时撑开空间将输入框上推 -->
<view v-if="keyboardHeight > 0" :style="{ height: keyboardHeight + 'px', flexShrink: 0 }"></view>
<!-- 语音输入面板 --> <!-- 语音输入面板 -->
<view v-if="enalbeVoicePanelShow" class="voice-popup"> <view v-if="enalbeVoicePanelShow" class="voice-popup">
<view class="voice-mask" @click="hideVoicePanel"></view> <view class="voice-mask" @click="hideVoicePanel"></view>
@@ -499,7 +502,7 @@ export default {
// AI头像错误状态 // AI头像错误状态
avatarError: false, avatarError: false,
// Dify API相关 // AiChat API相关
currentConversationId: null, currentConversationId: null,
isAIServiceAvailable: true, isAIServiceAvailable: true,
aiServiceError: null, aiServiceError: null,
@@ -523,16 +526,17 @@ export default {
return this.$util.img(IMAGE_CONFIG.aiAvatar); return this.$util.img(IMAGE_CONFIG.aiAvatar);
}, },
chatMessagesStyle() { chatMessagesStyle() {
// 输入框在 flex 流中,scroll-view 仅在键盘弹出时需要底部padding // 键盘弹出时增加底部padding,避免消息内容被键盘遮挡
// 键盘收起时:padding = 0(scroll-view 底部自然紧贴输入框顶部)
// 键盘弹出时:padding = 键盘高度(避免最后消息被键盘遮挡)
return { return {
'padding-bottom': this.keyboardHeight > 0 ? this.keyboardHeight + 'px' : '0px' 'padding-bottom': this.keyboardHeight > 0 ? this.keyboardHeight + 'px' : '0px'
}; };
}, },
aiChatContainerStyle() {
// 键盘高度由末尾垫高 view 处理,容器无需额外样式
return {};
},
inputAreaStyle() { inputAreaStyle() {
// 输入框在 flex 流中自然定位到容器底部,无需额外样式 // 输入框在 flex 流中自然定位,无需额外样式
// 键盘弹出时由 <textarea adjust-position> 自动处理页面偏移
return {}; return {};
}, },
canSendMessage() { canSendMessage() {
@@ -2212,9 +2216,14 @@ $radius-lg: 36rpx;
/* 页面样式 */ /* 页面样式 */
.ai-chat-container { .ai-chat-container {
height: 100vh; position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
box-sizing: border-box;
background: #f8f8f8; /* 微信聊天页面背景色 */ background: #f8f8f8; /* 微信聊天页面背景色 */
overflow: hidden; overflow: hidden;
box-sizing: border-box; box-sizing: border-box;
@@ -2892,8 +2901,6 @@ $radius-lg: 36rpx;
border-top: 1rpx solid #e5e5e5; border-top: 1rpx solid #e5e5e5;
padding: 16rpx 24rpx; padding: 16rpx 24rpx;
flex-shrink: 0; flex-shrink: 0;
/* 微信小程序安全区域适配 */
padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
box-sizing: border-box; box-sizing: border-box;
.input-tools { .input-tools {

View File

@@ -1,23 +1,19 @@
<template> <template>
<view class="container" :style="wrapperPageStyle"> <view class="wrapper">
<!--自定义导航头部 - 只在微信小程序显示,H5端隐藏 --> <!--自定义导航头部 - 绝对定位在顶部,不随键盘上移 -->
<!-- #ifdef MP-WEIXIN --> <!-- #ifdef MP-WEIXIN -->
<view class="custom-navbar" ref="pageHeader" v-if="showCustomNavbar"> <view class="custom-navbar" ref="pageHeader" v-if="showCustomNavbar">
<view class="header-left"> <view class="header-left">
<button class="back-btn" v-show="showBackButton" @click="goBack"> <button class="back-btn" v-show="showBackButton" @click="goBack">
<text class="iconfont icon-back"></text> <text class="iconfont icon-back"></text>
</button> </button>
<!-- 占位元素,确保布局平衡 -->
<view v-show="!showMenuButton" class="placeholder"></view> <view v-show="!showMenuButton" class="placeholder"></view>
</view> </view>
</view> </view>
<!-- #endif --> <!-- #endif -->
<!-- 聊天内容区域 --> <!-- 聊天内容区域:从导航栏下方到容器底部 -->
<view class="chat-content" <view class="chat-content">
:style="wrapperChatContentStyle"
>
<!-- AI聊天组件 -->
<ai-chat-message <ai-chat-message
ref="chat" ref="chat"
:initial-messages="initialMessages" :initial-messages="initialMessages"
@@ -131,37 +127,6 @@ export default {
return this.globalAIKefuConfig?.aiAvatar || this.$util.img(IMAGE_CONFIG.aiAvatar) return this.globalAIKefuConfig?.aiAvatar || this.$util.img(IMAGE_CONFIG.aiAvatar)
}, },
/// ---- others ---- /// ---- others ----
containerHeight() {
return `calc(100vh - ${this.navBarHeight + this.statusBarHeight}px)`
},
wrapperPageStyle() {
// #ifdef H5
return `top: $ {this.navBarHeight + 'px'};`
// #endif
// #ifdef MP-WEIXIN
return `top: -1px;` // 微信小程序需要上移1px, 否则与系统导航栏出现1px的空隙
// #endif
return ``
},
wrapperChatContentStyle() {
// #ifdef H5
// H5端没有自定义导航栏,不需要paddingTop
return {
height: this.containerHeight,
paddingTop: '0'
}
// #endif
// #ifdef MP-WEIXIN
// 微信小程序端根据导航栏显示情况决定
return {
height: this.containerHeight,
paddingTop: this.showCustomNavbar ? '0' : (this.navBarHeight + 'px')
}
// #endif
}
}, },
async onLoad(options) { async onLoad(options) {
@@ -574,20 +539,22 @@ export default {
@import url('/common/css/iconfont.css'); @import url('/common/css/iconfont.css');
/* 页面样式 */ /* 页面样式 */
.container { .wrapper {
display: flex; position: fixed;
flex-direction: column; top: 0;
background-color: white; /* 白色 */ left: 0;
overflow: hidden; right: 0;
position: fixed; bottom: 0;
top: 48px; overflow: hidden;
left: 0; }
right: 0;
bottom: 0;
}
/* 自定义导航头部 */ /* 自定义导航头部:固定在wrapper顶部,不随键盘上移 */
.custom-navbar { .custom-navbar {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 88rpx;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
@@ -667,12 +634,18 @@ export default {
} }
} }
/* 聊天内容区域 */ /* 聊天内容区域:从导航栏下方到容器底部 */
.chat-content { .chat-content {
flex: 1; position: absolute;
display: flex; /* #ifdef MP-WEIXIN */
flex-direction: column; top: 0;
overflow: hidden; /* #endif */
/* #ifndef MP-WEIXIN */
top: 44px;
/* #endif */
left: 0;
right: 0;
bottom: 0;
} }
/* 底部tabBar占位样式 */ /* 底部tabBar占位样式 */