feat(ai-chat): 适配浮动按钮入口隐藏底部tabBar
实现浮动按钮跳转AI聊天页时自动隐藏底部tabBar,通过__dyu标记区分入口来源: 1. 浮动按钮点击时注入__dyu=FloatBtn来源标记 2. AI聊天页根据该标记临时隐藏tabBar并在卸载时恢复 3. 兼容旧store无对应mutation的场景
This commit is contained in:
@@ -138,7 +138,7 @@ export const CATEGORY_PAGE_URL = '/pages_goods/category';
|
||||
export const CONTACT_PAGE_URL = '/pages_tool/contact/contact';
|
||||
export const MEMBER_PAGE_URL = '/pages_tool/member/index';
|
||||
export const LOGIN_PAGE_URL = '/pages_tool/login/login';
|
||||
export const AI_CHAT_PAGE_URL = '/pages_tool/ai-chat/index';
|
||||
export const AI_CHAT_PAGE_URL = '/pages_tool/ai-chat/index';
|
||||
export const AI_AGENT_PAGE_URL = '/pages_tool/ai-chat/index'; // AI 智能体入口页(智能客服是首个 agent)
|
||||
|
||||
// page_key 到实际路径的映射表(统一管理所有页面的语义常量 → 实际路径)
|
||||
@@ -1210,10 +1210,14 @@ export default {
|
||||
if (link && (link.name === 'AI_AGENT' || link.kind === 'AI_AGENT')) {
|
||||
const agentId = (link.page_params && link.page_params.agent_id) || 'agent_aikefu';
|
||||
const title = link.title || 'AI 智能体';
|
||||
// 透传来源标记:浮动按钮点击时 __dyu=FloatBtn,目标页据此隐藏底部 tabBar
|
||||
// (diy-float-btn.vue#onFloatBtnClick 已写入 page_params.__dyu)
|
||||
const dyuEntry = (link.page_params && link.page_params.__dyu) || '';
|
||||
return this.redirectTo(AI_AGENT_PAGE_URL, {
|
||||
agent_id: agentId,
|
||||
title: encodeURIComponent(title),
|
||||
enable_set_title: link.enable_set_title ? 1 : 0,
|
||||
...(dyuEntry ? { __dyu: dyuEntry } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
:class="{ left_top: value.bottomPosition == 1, right_top: value.bottomPosition == 2, left_bottom: value.bottomPosition == 3, right_bottom: value.bottomPosition == 4 }"
|
||||
:style="style">
|
||||
<block v-for="(item, index) in value.list" :key="index">
|
||||
<view class="button-box" @tap.stop="$util.diyRedirectTo(item.link)"
|
||||
<view class="button-box" @tap.stop="onFloatBtnClick(item)"
|
||||
:style="{ width: value.imageSize + 'px', height: value.imageSize + 'px', fontSize: value.imageSize + 'px' }">
|
||||
<image v-if="!item.iconType || item.iconType == 'img'" :src="$util.img(item.imageUrl)" mode="aspectFit"
|
||||
show-menu-by-longpress />
|
||||
@@ -44,7 +44,44 @@ export default {
|
||||
componentRefresh: function (nval) { }
|
||||
},
|
||||
components: {},
|
||||
methods: {},
|
||||
methods: {
|
||||
/**
|
||||
* 浮动按钮点击处理:
|
||||
* 在跳转前给 link 注入 __dyu=FloatBtn 来源标记,
|
||||
* 让目标页面(尤其是 ai-chat)能识别"来自浮动按钮"从而隐藏底部 tabBar,
|
||||
* 与"从底部导航栏点击进入时显示并高亮 tabBar"的场景并存。
|
||||
*
|
||||
* 为什么用 __dyu 这种 3 字符命名:
|
||||
* - 双下划线 __ 前缀 + 3 位字母(dy-ui 框架) 是前端约定俗成的"系统内部保留字段"标志
|
||||
* (如 __proto__, __esModule),业务方配置 link 时几乎不可能使用这种短命名 → 与业务参数零冲突
|
||||
* - URL 中占用空间极小,不污染业务 query 字符串
|
||||
* - 未来如需扩展其他组件的入口来源,可继续使用 __dyu 前缀(如 __dyu=Banner / __dyu=Nav)
|
||||
*
|
||||
* 实现要点:
|
||||
* - 浅拷贝 item.link 避免污染原数据(可能被 v-for 复用)
|
||||
* - 把标记写入 page_params,diyRedirectTo → redirectTo 会自动拼到 URL query,
|
||||
* 目标页 onLoad(options) 可直接读取
|
||||
* - 同时在 wap_url 末尾追加/合并 __dyu=FloatBtn 兜底:某些路径不走 page_params 拼接
|
||||
*/
|
||||
onFloatBtnClick(item) {
|
||||
if (!item || !item.link) return;
|
||||
const link = { ...item.link };
|
||||
link.page_params = Object.assign({}, link.page_params || {}, {
|
||||
__dyu: 'FloatBtn'
|
||||
});
|
||||
|
||||
// 兜底:直接把标记拼到 wap_url query,防止 page_params 在某些分支未被透传
|
||||
if (link.wap_url && typeof link.wap_url === 'string' && link.wap_url.indexOf('http') === -1) {
|
||||
const sep = link.wap_url.indexOf('?') === -1 ? '?' : '&';
|
||||
// 避免重复追加(精确匹配 __dyu= 而不是 __dyu,避免误伤可能的 __dyu_other)
|
||||
if (link.wap_url.indexOf('__dyu=') === -1) {
|
||||
link.wap_url = link.wap_url + sep + '__dyu=FloatBtn';
|
||||
}
|
||||
}
|
||||
|
||||
this.$util.diyRedirectTo(link);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
let style = {},
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- 聊天内容区域:从导航栏下方到容器底部 -->
|
||||
<view class="chat-content">
|
||||
<view class="chat-content" :style="{ bottom: tabBarHeight }">
|
||||
<ai-chat-message
|
||||
ref="chat"
|
||||
:initial-messages="initialMessages"
|
||||
@@ -68,6 +68,15 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部导航栏(仅在 tabBar 场景下显示):
|
||||
通过 golbalConfig mixin 的 shouldShowTabBar 控制 v-show,
|
||||
组件内部已绑定 data-component-name="diy-bottom-nav"。
|
||||
- 后端把 ai-chat 配为底部导航栏项 → 用户点击该项进入 → tabBar 显示并高亮对应项
|
||||
- 浮动按钮(diy-float-btn)进入时 onLoad commit setBottomNavHidden(true)
|
||||
→ shouldShowTabBar=false → tabBar 隐藏
|
||||
- 离开页面(onUnload)还原 bottomNavHidden → 不污染其他页面 -->
|
||||
<diy-bottom-nav></diy-bottom-nav>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -108,7 +117,15 @@ export default {
|
||||
keyboardHeight: 0,
|
||||
|
||||
// 事件处理器引用(用于清理)
|
||||
safeEventHandlers: new Map()
|
||||
safeEventHandlers: new Map(),
|
||||
|
||||
// 是否从浮动按钮进入
|
||||
// 用来在运行时区分两种场景:
|
||||
// 1) 后台把 ai-chat 配为底部导航栏 tabBar 项 → 用户点击该项进入 → 底部 tabBar 显示并高亮
|
||||
// 2) 浮动按钮(diy-float-btn)的链接指向 ai-chat → 用户点击浮动按钮进入 → 底部 tabBar 隐藏
|
||||
// 通过 onLoad(options).from 判定;同时会通过 $store.state.bottomNavHidden 临时隐藏 tabBar,
|
||||
// 并在 onUnload 还原,避免影响其他页面。
|
||||
isFromFloatBtn: false,
|
||||
}
|
||||
},
|
||||
|
||||
@@ -153,6 +170,26 @@ export default {
|
||||
// }
|
||||
|
||||
this.$langConfig.title(this.pageTitle || this.$lang('ai-chat.index.title'));
|
||||
|
||||
// ====== 区分底部导航栏/浮动按钮两种进入来源 ======
|
||||
// diy-float-btn 跳转时会在 link 上注入 __dyu=FloatBtn
|
||||
// (见 diy-float-btn.vue#onFloatBtnClick)
|
||||
// 这种情况应当隐藏底部 tabBar(AI 客服页是全屏聊天体验,不应被 tabBar 占用屏幕)
|
||||
// 而点击底部导航栏 tabBar 项进入时,通常不会携带 __dyu query,
|
||||
// 此时 tabBar 正常显示并由 checkTabBarActive 步骤2精确命中高亮当前项。
|
||||
// 用 __dyu 这种 3 字符 + 双下划线前缀的命名,几乎不可能与业务 page_params 冲突
|
||||
// (业务方不会使用 __ 开头的短变量名)。
|
||||
const dyuEntry = (options && options.__dyu) || '';
|
||||
if (dyuEntry === 'FloatBtn') {
|
||||
this.isFromFloatBtn = true;
|
||||
// 临时隐藏底部导航栏;onUnload 会还原。
|
||||
try {
|
||||
this.$store.commit('setBottomNavHidden', true);
|
||||
} catch (e) { /* 兼容没有该 mutation 的旧 store */ }
|
||||
} else {
|
||||
this.isFromFloatBtn = false;
|
||||
}
|
||||
|
||||
this.initChat();
|
||||
},
|
||||
|
||||
@@ -198,6 +235,15 @@ export default {
|
||||
console.log(this.$lang('ai-chat.index.logOnUnloadTriggered'));
|
||||
this.cleanup();
|
||||
aiService.disconnectWebSocket();
|
||||
|
||||
// 还原底部导航栏显示状态:如果 onLoad 时因为来自浮动按钮而临时隐藏,
|
||||
// 必须在页面卸载时还原,否则返回到 tabBar 页时 tabBar 会保持隐藏状态。
|
||||
if (this.isFromFloatBtn) {
|
||||
try {
|
||||
this.$store.commit('setBottomNavHidden', false);
|
||||
} catch (e) { /* 兼容没有该 mutation 的旧 store */ }
|
||||
this.isFromFloatBtn = false;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
@@ -665,7 +711,9 @@ export default {
|
||||
/* #endif */
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
/* bottom 由模板动态绑定到 tabBarHeight:
|
||||
- 浮动按钮入口 / 其他隐藏 tabBar 场景:0rpx,占满到底部
|
||||
- tabBar 项入口:tabBarHeight,避免被底部 tabBar 遮挡聊天内容 */
|
||||
}
|
||||
|
||||
/* 底部tabBar占位样式 */
|
||||
|
||||
Reference in New Issue
Block a user