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 CONTACT_PAGE_URL = '/pages_tool/contact/contact';
|
||||||
export const MEMBER_PAGE_URL = '/pages_tool/member/index';
|
export const MEMBER_PAGE_URL = '/pages_tool/member/index';
|
||||||
export const LOGIN_PAGE_URL = '/pages_tool/login/login';
|
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)
|
export const AI_AGENT_PAGE_URL = '/pages_tool/ai-chat/index'; // AI 智能体入口页(智能客服是首个 agent)
|
||||||
|
|
||||||
// page_key 到实际路径的映射表(统一管理所有页面的语义常量 → 实际路径)
|
// page_key 到实际路径的映射表(统一管理所有页面的语义常量 → 实际路径)
|
||||||
@@ -1210,10 +1210,14 @@ export default {
|
|||||||
if (link && (link.name === 'AI_AGENT' || link.kind === 'AI_AGENT')) {
|
if (link && (link.name === 'AI_AGENT' || link.kind === 'AI_AGENT')) {
|
||||||
const agentId = (link.page_params && link.page_params.agent_id) || 'agent_aikefu';
|
const agentId = (link.page_params && link.page_params.agent_id) || 'agent_aikefu';
|
||||||
const title = link.title || 'AI 智能体';
|
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, {
|
return this.redirectTo(AI_AGENT_PAGE_URL, {
|
||||||
agent_id: agentId,
|
agent_id: agentId,
|
||||||
title: encodeURIComponent(title),
|
title: encodeURIComponent(title),
|
||||||
enable_set_title: link.enable_set_title ? 1 : 0,
|
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 }"
|
:class="{ left_top: value.bottomPosition == 1, right_top: value.bottomPosition == 2, left_bottom: value.bottomPosition == 3, right_bottom: value.bottomPosition == 4 }"
|
||||||
:style="style">
|
:style="style">
|
||||||
<block v-for="(item, index) in value.list" :key="index">
|
<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' }">
|
: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"
|
<image v-if="!item.iconType || item.iconType == 'img'" :src="$util.img(item.imageUrl)" mode="aspectFit"
|
||||||
show-menu-by-longpress />
|
show-menu-by-longpress />
|
||||||
@@ -44,7 +44,44 @@ export default {
|
|||||||
componentRefresh: function (nval) { }
|
componentRefresh: function (nval) { }
|
||||||
},
|
},
|
||||||
components: {},
|
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: {
|
computed: {
|
||||||
style() {
|
style() {
|
||||||
let style = {},
|
let style = {},
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
|
|
||||||
<!-- 聊天内容区域:从导航栏下方到容器底部 -->
|
<!-- 聊天内容区域:从导航栏下方到容器底部 -->
|
||||||
<view class="chat-content">
|
<view class="chat-content" :style="{ bottom: tabBarHeight }">
|
||||||
<ai-chat-message
|
<ai-chat-message
|
||||||
ref="chat"
|
ref="chat"
|
||||||
:initial-messages="initialMessages"
|
:initial-messages="initialMessages"
|
||||||
@@ -68,6 +68,15 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</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>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -108,7 +117,15 @@ export default {
|
|||||||
keyboardHeight: 0,
|
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'));
|
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();
|
this.initChat();
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -198,6 +235,15 @@ export default {
|
|||||||
console.log(this.$lang('ai-chat.index.logOnUnloadTriggered'));
|
console.log(this.$lang('ai-chat.index.logOnUnloadTriggered'));
|
||||||
this.cleanup();
|
this.cleanup();
|
||||||
aiService.disconnectWebSocket();
|
aiService.disconnectWebSocket();
|
||||||
|
|
||||||
|
// 还原底部导航栏显示状态:如果 onLoad 时因为来自浮动按钮而临时隐藏,
|
||||||
|
// 必须在页面卸载时还原,否则返回到 tabBar 页时 tabBar 会保持隐藏状态。
|
||||||
|
if (this.isFromFloatBtn) {
|
||||||
|
try {
|
||||||
|
this.$store.commit('setBottomNavHidden', false);
|
||||||
|
} catch (e) { /* 兼容没有该 mutation 的旧 store */ }
|
||||||
|
this.isFromFloatBtn = false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@@ -665,7 +711,9 @@ export default {
|
|||||||
/* #endif */
|
/* #endif */
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
/* bottom 由模板动态绑定到 tabBarHeight:
|
||||||
|
- 浮动按钮入口 / 其他隐藏 tabBar 场景:0rpx,占满到底部
|
||||||
|
- tabBar 项入口:tabBarHeight,避免被底部 tabBar 遮挡聊天内容 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 底部tabBar占位样式 */
|
/* 底部tabBar占位样式 */
|
||||||
|
|||||||
Reference in New Issue
Block a user