fix(hover-nav): 修复主题色失效和按钮尺寸问题

1.  将按钮尺寸从80rpx调整为72rpx,修正布局计算和样式注释
2.  修复Teleport后主题色无法正确同步的问题:
    - 移除watch的immediate选项,改为在mounted后手动触发主题色注入
    - 新增_applyThemeColor方法,支持安全地更新内联样式并清理旧值
    - 监听themeColor变化,确保主题切换时样式实时更新
This commit is contained in:
2026-07-02 18:21:16 +08:00
parent cf6b683ffc
commit 34f8ae2322

View File

@@ -241,16 +241,16 @@ export default {
// 1) 测试发现 .btn-item 的 min-*/max-*/aspect-ratio/flex-shrink 等 !important
// 规则在 scoped 样式下,被某些 flex 布局的隐式约束吃掉,只 width/height/border-radius
// 生效,导致高度被压缩成 39.5px(从 41px),按钮显示为轻微椭圆。
// 2) inline-style 优先级高于 CSS class,直接锁死 80x80 + 正圆。
// 2) inline-style 优先级高于 CSS class,直接锁死 72x72 + 正圆。
// 3) 用 max-width/max-height + flex-shrink 0 三重兜底,防止被任何层级的样式覆盖。
btnItemStyle() {
return {
width: '80rpx',
height: '80rpx',
'min-width': '80rpx',
'min-height': '80rpx',
'max-width': '80rpx',
'max-height': '80rpx',
width: '72rpx',
height: '72rpx',
'min-width': '72rpx',
'min-height': '72rpx',
'max-width': '72rpx',
'max-height': '72rpx',
'aspect-ratio': '1',
// border-radius 保留作 clip-path 不支持时的 fallback
'border-radius': '50%',
@@ -298,12 +298,14 @@ export default {
//根据显示的按钮数量动态计算容器高度
// 计算明细(buttonCount 按钮场景):
// 每个按钮槽位:80rpx(按钮) + 14rpx(上 margin) + 14rpx(下 margin) = 108rpx
// 每个按钮槽位:72rpx(按钮,与 to-top 一致) + 14rpx(上 margin) + 14rpx(下 margin) = 100rpx
// 容器 padding:20rpx 上 + 20rpx 下 = 40rpx
// 总高度 (rpx):buttonCount × 108 + 40
// 总高度 (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,容器整体更紧凑
containerHeight() {
if (!this.fixBtnShow) return '320rpx';
@@ -311,7 +313,7 @@ export default {
if (this.isLanguageSwitchEnabled) buttonCount++;
if (this.showWeappSystemKefu) buttonCount++;
buttonCount++;
const totalRpx = buttonCount * 108 + 40;
const totalRpx = buttonCount * 100 + 40;
return `${totalRpx}rpx`;
},
@@ -394,6 +396,28 @@ export default {
}
},
immediate: true
},
// 关键修复(全新启动无缓存场景):
// 旧逻辑: mounted() 中同步读一次 $store.state.themeColor 写到 this.$el.style.cssText
// 但 App.vue 的 boot 是异步的,首屏渲染时主题色还没就绪 → 写入被跳过
// 之后主题色就绪,但 hover-nav 不在页面 DOM 树里(已被 Teleport 到 body),
// 且**没有任何 watch 监听 themeColor 变化** → cssText 永远不更新
// 结果: --hover-nav-bg-color / --hover-nav-text-color 等变量读不到,
// 按钮 background-color 退到 transparent,box-shadow 退到 none,
// 按钮视觉上"消失"或样式错乱
//
// 新逻辑:
// - 首次启动(无缓存,themeColor 异步就绪): watch 监听 themeColor 变化,
// 就绪时注入 cssText
// - 刷新(有缓存,themeColor 同步就绪): immediate: true 在 watcher 创建时触发,
// 但此时 this.$el 还不存在(_applyThemeColor return),watch 之后再无变化,
// 导致 cssText 永远不注入
// - 修复: 移除 immediate,改在 mounted() 中 Teleport 完成后手动调一次
// _applyThemeColor(currentTc),此时 $el 已就位,同步路径也能正确注入
'$store.state.themeColor': {
handler(newTc) {
this._applyThemeColor(newTc);
}
}
},
created() {
@@ -460,8 +484,18 @@ export default {
// 副作用: 搬家后,页面根元素上的 CSS 变量(themeColor,如
// --hover-nav-bg-color)不再通过继承传递给 hover-nav,
// 因为 body 不在页面的 DOM 子树里。
// 修复: 把 $store.state.themeColor 同步到 this.$el 的 inline style,
// 让 hover-nav 自己持有这些 CSS 变量,不再依赖页面继承。
// 修复: 由 watch('$store.state.themeColor') 监听主题色变化,
// 同步到 this.$el 的 inline style,让 hover-nav 自己持有这些 CSS 变量。
// 注意:必须在 mounted 中执行 Teleport,**之后** watch 才能找到
// 正确的 this.$el 元素;否则 watch 触发时,hover-nav 还在原父节点下,
// 主题色仍会通过页面继承自动应用,无需手动注入(但 Teleport 后会丢失)。
//
// 关键: mounted 末尾手动调一次 _applyThemeColor(currentTc),
// 解决刷新(有缓存,themeColor 同步就绪)场景下:
// - watch immediate 触发时 $el 不存在 → return
// - 之后 themeColor 已就绪、无变化 → watch 不再触发
// - → cssText 永远不注入
// 在这里调用能保证 $el 已就位,即使 themeColor 已有值也能正确注入。
//
// 注意: this.$el 是 Vue 组件的根元素(模板最外层的 <view>),
// 搬走它后 Vue 的响应式、事件、生命周期**完全不受影响**,
@@ -472,18 +506,12 @@ export default {
if (typeof document !== 'undefined' && document.body && this.$el) {
this._origParent = this.$el.parentNode;
document.body.appendChild(this.$el);
// 搬家后,把页面根元素上的 CSS 变量同步到 hover-nav 自己身上
// 主题系统把 themeColor 挂到页面根元素(如 index.vue 的 <view :style="themeColor">),
// 搬家后 hover-nav 不在页面子树里,继承不到这些变量。
// 这里直接从 store 读 themeColor 写到 this.$el 的 style 上,
// 让 .btn-item .common-bg 能正常读到 --hover-nav-bg-color 等变量。
const tc = this.$store && this.$store.state && this.$store.state.themeColor;
if (tc && this.$el.style) {
this.$el.style.cssText = (this.$el.style.cssText || '') + ';' + tc;
}
}
// #endif
// 关键: 手动同步当前 themeColor(覆盖 watch immediate 失效的场景)
// 详见上面的注释。_applyThemeColor 内部已判 $el 存在性,所有平台都安全。
this._applyThemeColor(this.$store && this.$store.state && this.$store.state.themeColor);
},
beforeDestroy() {
// H5 Teleport 还原:把元素从 body 移回原父节点,避免内存泄漏
@@ -519,6 +547,35 @@ export default {
}
},
// #endif
/**
* 把 themeColor 注入到 this.$el 的 cssText,
* 让 Teleport 到 body 后仍能读到 --hover-nav-bg-color 等 CSS 变量。
*
* 关键设计:
* - 用 data._appliedThemeColor 记录"已注入的 themeColor",
* 再次触发时**先剥离**旧值,再追加新值,避免重复拼接。
* - 主题切换时,旧值要先剥离(否则 cssText 会一直累积,文件膨胀)。
* - themeColor 为空时只剥离、不追加(themeColorSet action
* 内部会去重,不会频繁触发)。
*/
_applyThemeColor(tc) {
if (!this.$el || !this.$el.style) return;
// 1) 剥离旧注入(避免重复 + 处理主题切换场景)
if (this._appliedThemeColor) {
const cur = this.$el.style.cssText || '';
const suffix = ';' + this._appliedThemeColor;
if (cur.endsWith(suffix)) {
this.$el.style.cssText = cur.slice(0, -suffix.length);
}
this._appliedThemeColor = '';
}
// 2) 追加新值
if (tc) {
this._appliedThemeColor = tc;
this.$el.style.cssText = (this.$el.style.cssText || '') + ';' + tc;
}
},
initLanguage() {
// 数据源优先级:
// 1) shopInfo.langList(后端按租户/店铺动态下发的语言配置,推荐)
@@ -734,10 +791,10 @@ export default {
transition: 0.1s;
color: var(--hover-nav-text-color);
border-radius: 50%;
width: 80rpx;
height: 80rpx;
width: 72rpx;
height: 72rpx;
/* 关键:微信小程序的 <button> 元素有默认 appearance/background/border,
* 必须显式重置,否则 80rpx 高度会被默认样式吃掉,渲染成椭圆。 */
* 必须显式重置,否则 72rpx 高度会被默认样式吃掉,渲染成椭圆。 */
appearance: none;
-webkit-appearance: none;
background: transparent;
@@ -747,10 +804,10 @@ export default {
/* CSS 端兜底:即使 inline-style 失效,也能保证 1:1 比例 */
aspect-ratio: 1;
/* 锁死宽高,防止父级 flex 容器压缩 */
min-width: 80rpx;
min-height: 80rpx;
max-width: 80rpx;
max-height: 80rpx;
min-width: 72rpx;
min-height: 72rpx;
max-width: 72rpx;
max-height: 72rpx;
flex-shrink: 0;
flex-grow: 0;
align-self: center;
@@ -775,11 +832,11 @@ export default {
display: flex;
justify-content: center;
align-items: center;
/* 不再设 width: 100% / height: 100% 再覆盖为 80rpx 的写法,
/* 不再设 width: 100% / height: 100% 再覆盖为 72rpx 的写法,
* 微信小程序/H5 对 width: 100% 在 flex 列布局里的高度计算有差异,
* 直接固定 80x80 更稳。 */
width: 80rpx;
height: 80rpx;
* 直接固定 72x72 更稳。 */
width: 72rpx;
height: 72rpx;
margin: 0;
border-radius: 50%;
padding: 0;
@@ -823,7 +880,7 @@ export default {
* 语言按钮的 inner 用 <view class="lang-btn-text"> 包裹文字,
* 如果没有这个规则,view 会用默认 font-size/line-height,
* "CN" / "EN" / "JP" 在中文/日文系统下会渲染成 32-40rpx 高,
* 撑高按钮导致 80rpx width × 38rpx height 的"伪正圆"实际是椭圆。 */
* 撑高按钮导致 72rpx width × 38rpx height 的"伪正圆"实际是椭圆。 */
.lang-btn-text {
display: flex;
align-items: center;