@@ -1,7 +1,7 @@
< template >
<!-- Vue 2 / uni - app 单根约束 : 用 view 包一层 -- >
< view >
< view v-if = "pageCount === 1 || need" class="fixed-box"
< view v-if = "( pageCount === 1 || need) && fbVisible && hasAnyButton " class="fixed-box"
: style = "[customContainerStyle, {
height: containerHeight,
bottom: containerBottom,
@@ -11,18 +11,18 @@
}]" >
<!-- 多语言切换按钮 : 根据语言数智能切换交互 -- >
< view
v-if = "isLanguageSwitchEnabled && fixBtnShow "
v-if = "isLanguageSwitchEnabled && hasLangButton "
class = "btn-item common-bg"
: style= "btnItemStyle"
: style= "[ btnItemStyle, { order: orderIndex.lang }] "
@click ="handleLangButtonClick"
>
< view class = "lang-btn-text" > { { currentLangDisplayName } } < / view >
< view class = "lang-btn-text" > { { langButtonText } } < / view >
< / view >
<!-- 微信官方客服按钮 -- >
< button
v-if = "fixBtnShow && hasValidKefuConfig && isWeappOfficialKefu"
v-if = "hasKefuButton && hasValidKefuConfig && isWeappOfficialKefu"
class = "btn-item common-bg"
: style= "btnItemStyle"
: style= "[ btnItemStyle, { order: orderIndex.kefu }] "
open -type = " contact "
>
< text class = "icox icox-kefu" v-if = "!kefuimg" > < / text >
@@ -30,9 +30,9 @@
< ! - - 其他类型客服按钮 ( 智能客服 、 企业微信等 ) - - >
< view
v-if = "fixBtnShow && hasValidKefuConfig && !isWeappOfficialKefu && !showWeappSystemKefu"
v-if = "hasKefuButton && hasValidKefuConfig && !isWeappOfficialKefu && !showWeappSystemKefu"
class = "btn-item common-bg"
: style= "btnItemStyle"
: style= "[ btnItemStyle, { order: orderIndex.kefu }] "
@click ="handleUnifiedKefuClick"
>
< image
@@ -48,9 +48,9 @@
< ! - - 智能客服按钮 ( 当同时显示小程序系统客服时 ) - - >
< view
v-if = "fixBtnShow && hasValidKefuConfig && !isWeappOfficialKefu && showWeappSystemKefu"
v-if = "hasKefuButton && hasValidKefuConfig && !isWeappOfficialKefu && showWeappSystemKefu"
class = "btn-item common-bg"
: style= "btnItemStyle"
: style= "[ btnItemStyle, { order: orderIndex.kefu }] "
@click ="handleUnifiedKefuClick"
>
< image
@@ -66,9 +66,9 @@
< ! - - 小程序系统客服按钮 - - >
< button
v-if = "fixBtnShow && hasValidKefuConfig && showWeappSystemKefu"
v-if = "hasKefuButton && hasValidKefuConfig && showWeappSystemKefu"
class = "btn-item common-bg"
: style= "btnItemStyle"
: style= "[ btnItemStyle, { order: orderIndex.kefu }] "
open -type = " contact "
>
< text class = "icox icox-kefu" v-if = "!kefuimg" > < / text >
@@ -76,9 +76,9 @@
< ! - - 电话按钮 - - >
< view
v-if = "fixBtnShow "
v-if = "hasPhoneButton "
class = "btn-item common-bg"
: style = "[btnItemStyle, { backgroundImage: phoneimg ? `url(${phoneimg})` : '', backgroundSize: '100% 100%' }, customButtonStyle]"
: style = "[btnItemStyle, { order: orderIndex.phone }, { backgroundImage: phoneimg ? `url(${phoneimg})` : '', backgroundSize: '100% 100%' }, customButtonStyle]"
@click ="call"
>
< text class = "iconfont icon-dianhua" v-if = "!phoneimg" > < / text >
@@ -109,6 +109,10 @@
< script >
import { createCustomerService } from '@/common/js/customer-service.js' ;
import i18nLazyMixin from '@/common/mixin/i18n-lazy.js' ;
import {
getCurrentRoute , shouldShowOnPage , resolveVisibleButtons ,
resolveLangButtonText , isLangButtonVisible ,
} from '@/common/js/floating-button.js' ;
// 语言短码映射表(currentLangDisplayName 展示用,可按需扩展)
// key: 语言 value,value: 显示短码(按钮上展示)
@@ -170,7 +174,7 @@ export default {
data ( ) {
return {
pageCount : 0 ,
fixBtnShow : true ,
currentRoute : '' , // 当前页面路由(onShow / created 时刷新, 供页面范围判定)
shopInfo : null ,
currentLangIndex : 0 ,
langIndexMap : { } ,
@@ -237,6 +241,57 @@ export default {
customButtonStyle ( ) {
return this . shopInfo ? . floatingButton ? . button || { } ;
} ,
// ============================================================
// 浮动侧边栏配置消费 (来源: /api/config/init → data.floating_button,
// store/index.js 的 floatingButtonConfig, 初始 state 已声明 → 响应式)
// fbConfig 为 null (老缓存响应缺字段) 时, 各判定全部走老行为兜底。
// ============================================================
// 接口下发的浮动侧边栏配置 (老缓存响应 → null)
fbConfig ( ) {
return ( this . $store && this . $store . state && this . $store . state . floatingButtonConfig ) || null ;
} ,
// 总开关 + 强制隐藏 + 页面范围 判定结果
fbVisible ( ) {
return shouldShowOnPage ( this . fbConfig , this . currentRoute , true ) ;
} ,
// 最终要渲染的按钮 (已按 button_order 排序 + 显隐过滤); null = 用老行为
visibleButtons ( ) {
return resolveVisibleButtons ( this . fbConfig ) ;
} ,
hasAnyButton ( ) {
const b = this . visibleButtons ;
return b ? b . length > 0 : true ; // 老行为: 电话按钮恒显
} ,
hasLangButton ( ) {
const b = this . visibleButtons ;
return b ? b . indexOf ( 'lang' ) !== - 1
: isLangButtonVisible ( this . fbConfig , this . shopInfo , this . langList . length ) ;
} ,
hasKefuButton ( ) {
const b = this . visibleButtons ;
return b ? b . indexOf ( 'kefu' ) !== - 1 : true ;
} ,
hasPhoneButton ( ) {
const b = this . visibleButtons ;
return b ? b . indexOf ( 'phone' ) !== - 1 : true ;
} ,
// 语言按钮文案: 商家可配「目标语言 / 当前语言 / 固定文案」。
// 配置缺失时回退老行为 currentLangDisplayName(展示当前语言)。
langButtonText ( ) {
if ( ! this . fbConfig ) return this . currentLangDisplayName ;
return resolveLangButtonText ( this . fbConfig , this . langList , this . currentLangIndex ) ;
} ,
// 按钮渲染顺序 (button_order 自上而下); .fixed-box 是 flex-direction: column,
// 用 CSS order 控制三段(语言/客服/电话)的先后, 配置缺失时退回老顺序。
orderIndex ( ) {
const order = ( this . fbConfig && Array . isArray ( this . fbConfig . button _order ) )
? this . fbConfig . button _order
: [ 'lang' , 'kefu' , 'phone' ] ;
const map = { } ;
for ( let i = 0 ; i < order . length ; i ++ ) map [ order [ i ] ] = i + 1 ;
map . lang = map . lang || 1 ; map . kefu = map . kefu || 2 ; map . phone = map . phone || 3 ;
return map ;
} ,
// 所有 .btn-item 的 inline-style 兜底:
// 1) 测试发现 .btn-item 的 min-*/max-*/aspect-ratio/flex-shrink 等 !important
// 规则在 scoped 样式下,被某些 flex 布局的隐式约束吃掉,只 width/height/border-radius
@@ -301,18 +356,19 @@ export default {
// 每个按钮槽位:72rpx(按钮,与 to-top 一致) + 14rpx(上 margin) + 14rpx(下 margin) = 100rpx
// 容器 padding:20rpx 上 + 20rpx 下 = 40rpx
// 总高度 (rpx):buttonCount × 100 + 40
// 修复:改返回 rpx 而非 px(原 *0.5 硬编码 iPhone 6 375px 转换比),
// 在微信小程序/其他设备(iPhone 13 390px、iPad 等)下由 uni-app 框架
// 按 windowWidth 动态换算,避免 4+ 按钮时容器偏小导致内容被裁切
// 关联:按钮尺寸与 to-top 同步(72rpx),所以槽位高度也是 100rpx;
// 之前按钮 80rpx 时槽位 108rpx,改为 72rpx 后槽位缩小 8rpx,容器整体更紧凑
// 新配置生效时按 visibleButtons 的实际按钮数算,避免隐藏按钮后留下空白档;
// 全部按钮被关掉时返回 0rpx(根节点 v-if 已含 hasAnyButton, 这里是双保险);
// 配置缺失(fbConfig 为 null)时保持老行为: 电话恒显 + 可选语言 + 可选系统客服。
// extraKefuSlot: 小程序系统客服与客服主按钮并存时模板会多渲染一个按钮,
// 需要为它多留一个槽位(fallback 分支与老逻辑等价)。
containerHeight ( ) {
if ( ! this . fixBtnShow ) return '320rpx' ;
const b = this . visibleButtons ;
if ( b && b . length === 0 ) return '0rpx' ;
let buttonCount = 1 ;
if ( this . isLanguageSwitchEnabled ) buttonCount ++ ;
if ( this . showWeappSystemKefu ) buttonCount ++ ;
buttonCount ++ ;
const extraKefuSlot = ( this . hasKefuButton && this . showWeappSystemKefu ) ? 1 : 0 ;
const buttonCount = b
? b . length + extraKefuSlot
: 1 + ( this . isLanguageSwitchEnabled ? 1 : 0 ) + extraKefuSlot ;
const totalRpx = buttonCount * 100 + 40 ;
return ` ${ totalRpx } rpx ` ;
@@ -337,7 +393,6 @@ export default {
// 与 Top 按钮的 .mescroll-totop { margin-bottom: var(--window-bottom) } 一致,
// 两者会被安全区同步上推,space 保持不变
containerBottom ( ) {
if ( ! this . fixBtnShow ) return '240rpx' ;
let space = 18 * 5 ;
// #ifdef MP-WEIXIN
space = 18 * 1 ;
@@ -357,7 +412,6 @@ export default {
// - right edge 对齐(46rpx):左侧突出 28rpx,中心错位 14rpx
// center 对齐不仅中心精准,突出量也更小
containerRight ( ) {
if ( ! this . fixBtnShow ) return '0rpx' ;
const offset = this . layoutTopRight + this . layoutTopSize / 2 - this . layoutFloatingWidth / 2 ;
return ` ${ offset } rpx ` ;
} ,
@@ -420,9 +474,14 @@ export default {
}
}
} ,
// 页面生命周期: hover-nav 是常驻组件, 路由变化必须靠 onShow 通知,
// 否则 currentRoute 停留在首次加载页, 页面范围判定(fbVisible)会失效。
onShow ( ) {
this . refreshRoute ( ) ;
} ,
created ( ) {
this . initLanguage ( ) ;
this . pageCount = getCurrentPages ( ) . length ;
this . refreshRoute ( ) ;
// 三源兜底(store / 同步 storage / 异步 storage),
// 任何一个先就绪就先使用,后续由 initStatus watcher 再次同步
@@ -523,6 +582,11 @@ export default {
// #endif
} ,
methods : {
// 刷新当前路由与页面栈深度(供 fbVisible 的页面范围判定 / pageCount 老判定)
refreshRoute ( ) {
this . currentRoute = getCurrentRoute ( ) ;
this . pageCount = getCurrentPages ( ) . length ;
} ,
// #ifdef H5
/**
* 从 :root 读取 CSS 变量(App.vue 中定义)到 data,