function finiteNumber(value, fallback = 0) { const parsed = Number(value) return Number.isFinite(parsed) ? parsed : fallback } function clamp(value, minimum, maximum) { return Math.min(maximum, Math.max(minimum, value)) } /** * 首页使用自定义导航栏,横屏时既要避开左右刘海与底部 Home * Indicator,也要为微信右上角胶囊留下真实空间。CSS 的 env() * 在部分微信横屏机型上只会返回系统安全区,不包含胶囊,因此这里 * 以运行时尺寸为准,CSS env() 只作为脚本尚未执行时的兜底。 */ function getHomeLayoutMetrics(windowInfo = {}, menuRect = {}) { const windowWidth = Math.max(320, finiteNumber(windowInfo.windowWidth, 844)) const windowHeight = Math.max(240, finiteNumber(windowInfo.windowHeight, 390)) const safeArea = windowInfo.safeArea || {} const safeLeft = clamp( finiteNumber(safeArea.left, 0), 0, windowWidth / 3, ) const safeRightEdge = clamp( finiteNumber(safeArea.right, windowWidth), windowWidth * 2 / 3, windowWidth, ) const safeRight = clamp( windowWidth - safeRightEdge, 0, windowWidth / 3, ) const safeTop = Math.max( 0, finiteNumber(windowInfo.statusBarHeight, 0), finiteNumber(safeArea.top, 0), ) const safeBottomEdge = clamp( finiteNumber(safeArea.bottom, windowHeight), windowHeight * 2 / 3, windowHeight, ) const safeBottom = clamp( windowHeight - safeBottomEdge, 0, windowHeight / 3, ) const menuLeft = finiteNumber(menuRect.left, windowWidth) const menuBottom = finiteNumber(menuRect.bottom, 0) const hasMenuRect = ( menuLeft > windowWidth / 2 && menuLeft < windowWidth && menuBottom > 0 ) const compactHeight = windowHeight <= 620 const horizontalPageGap = compactHeight ? 10 : 14 const capsuleGap = compactHeight ? 8 : 10 const leftInset = Math.ceil(safeLeft + horizontalPageGap) const rightInset = Math.ceil(Math.max( safeRight + horizontalPageGap, hasMenuRect ? windowWidth - menuLeft + capsuleGap : safeRight + horizontalPageGap, )) const topInset = Math.ceil(safeTop + (compactHeight ? 8 : 12)) const bottomInset = Math.ceil(safeBottom + (compactHeight ? 7 : 12)) return { windowWidth, windowHeight, compactHeight, safeLeft, safeRight, safeTop, safeBottom, leftInset, rightInset, topInset, bottomInset, hasMenuRect, } } function getHomeLayoutStyle(metrics = {}) { return [ `height:${Math.max(240, finiteNumber(metrics.windowHeight, 390))}px`, `--home-top-inset:${Math.max(0, finiteNumber(metrics.topInset, 8))}px`, `--home-right-inset:${Math.max(0, finiteNumber(metrics.rightInset, 10))}px`, `--home-bottom-inset:${Math.max(0, finiteNumber(metrics.bottomInset, 7))}px`, `--home-left-inset:${Math.max(0, finiteNumber(metrics.leftInset, 10))}px`, ].join(';') } function hasReadingProgress(progress = {}) { const completedChapters = Array.isArray(progress.completedChapters) ? progress.completedChapters : [] const completedHotspots = progress.completedHotspots && typeof progress.completedHotspots === 'object' ? progress.completedHotspots : {} const hasCompletedHotspot = Object.values(completedHotspots).some( (ids) => Array.isArray(ids) && ids.length > 0, ) return ( completedChapters.length > 0 || finiteNumber(progress.lastChapter, 1) > 1 || hasCompletedHotspot ) } module.exports = { getHomeLayoutMetrics, getHomeLayoutStyle, hasReadingProgress, }