feat(底部导航): 重构小程序 WebView 底部导航组件
- 新增 JlsBottomNav 组件目录,包含配置加载、状态管理、预览工具等模块 - 统一底部导航数据源,支持 API 动态配置与本地 mock 预览 - 重构 activeTab 解析逻辑,兼容新旧 key 映射关系 - 优化组件样式,支持滚动布局与响应式设计 - 更新组件类型声明,移除未使用的 Element Plus 组件
Showing
14 changed files
with
1198 additions
and
207 deletions
| ... | @@ -13,10 +13,6 @@ declare module '@vue/runtime-core' { | ... | @@ -13,10 +13,6 @@ declare module '@vue/runtime-core' { |
| 13 | AudioBackground1: typeof import('./src/components/audioBackground1.vue')['default'] | 13 | AudioBackground1: typeof import('./src/components/audioBackground1.vue')['default'] |
| 14 | AudioList: typeof import('./src/components/audioList.vue')['default'] | 14 | AudioList: typeof import('./src/components/audioList.vue')['default'] |
| 15 | BottomNav: typeof import('./src/components/BottomNav.vue')['default'] | 15 | BottomNav: typeof import('./src/components/BottomNav.vue')['default'] |
| 16 | - ElButton: typeof import('element-plus/es')['ElButton'] | ||
| 17 | - ElInput: typeof import('element-plus/es')['ElInput'] | ||
| 18 | - ElOption: typeof import('element-plus/es')['ElOption'] | ||
| 19 | - ElSelect: typeof import('element-plus/es')['ElSelect'] | ||
| 20 | Floor: typeof import('./src/components/Floor/index.vue')['default'] | 16 | Floor: typeof import('./src/components/Floor/index.vue')['default'] |
| 21 | InfoPopup: typeof import('./src/components/InfoPopup.vue')['default'] | 17 | InfoPopup: typeof import('./src/components/InfoPopup.vue')['default'] |
| 22 | InfoPopupLite: typeof import('./src/components/InfoPopupLite.vue')['default'] | 18 | InfoPopupLite: typeof import('./src/components/InfoPopupLite.vue')['default'] |
| ... | @@ -25,25 +21,20 @@ declare module '@vue/runtime-core' { | ... | @@ -25,25 +21,20 @@ declare module '@vue/runtime-core' { |
| 25 | InfoWindowLite: typeof import('./src/components/InfoWindowLite.vue')['default'] | 21 | InfoWindowLite: typeof import('./src/components/InfoWindowLite.vue')['default'] |
| 26 | InfoWindowWarn: typeof import('./src/components/InfoWindowWarn.vue')['default'] | 22 | InfoWindowWarn: typeof import('./src/components/InfoWindowWarn.vue')['default'] |
| 27 | InfoWindowYard: typeof import('./src/components/InfoWindowYard.vue')['default'] | 23 | InfoWindowYard: typeof import('./src/components/InfoWindowYard.vue')['default'] |
| 28 | - JlsBottomNav: typeof import('./src/components/JlsBottomNav.vue')['default'] | 24 | + JlsBottomNav: typeof import('./src/components/JlsBottomNav/JlsBottomNav.vue')['default'] |
| 29 | RouterLink: typeof import('vue-router')['RouterLink'] | 25 | RouterLink: typeof import('vue-router')['RouterLink'] |
| 30 | RouterView: typeof import('vue-router')['RouterView'] | 26 | RouterView: typeof import('vue-router')['RouterView'] |
| 31 | SvgIcon: typeof import('./src/components/Floor/svgIcon.vue')['default'] | 27 | SvgIcon: typeof import('./src/components/Floor/svgIcon.vue')['default'] |
| 32 | VanBackTop: typeof import('vant/es')['BackTop'] | 28 | VanBackTop: typeof import('vant/es')['BackTop'] |
| 33 | VanButton: typeof import('vant/es')['Button'] | 29 | VanButton: typeof import('vant/es')['Button'] |
| 34 | - VanCol: typeof import('vant/es')['Col'] | ||
| 35 | VanConfigProvider: typeof import('vant/es')['ConfigProvider'] | 30 | VanConfigProvider: typeof import('vant/es')['ConfigProvider'] |
| 36 | VanDialog: typeof import('vant/es')['Dialog'] | 31 | VanDialog: typeof import('vant/es')['Dialog'] |
| 37 | - VanField: typeof import('vant/es')['Field'] | ||
| 38 | VanFloatingPanel: typeof import('vant/es')['FloatingPanel'] | 32 | VanFloatingPanel: typeof import('vant/es')['FloatingPanel'] |
| 39 | VanIcon: typeof import('vant/es')['Icon'] | 33 | VanIcon: typeof import('vant/es')['Icon'] |
| 40 | VanImage: typeof import('vant/es')['Image'] | 34 | VanImage: typeof import('vant/es')['Image'] |
| 41 | VanImagePreview: typeof import('vant/es')['ImagePreview'] | 35 | VanImagePreview: typeof import('vant/es')['ImagePreview'] |
| 42 | VanLoading: typeof import('vant/es')['Loading'] | 36 | VanLoading: typeof import('vant/es')['Loading'] |
| 43 | - VanOverlay: typeof import('vant/es')['Overlay'] | ||
| 44 | VanPopup: typeof import('vant/es')['Popup'] | 37 | VanPopup: typeof import('vant/es')['Popup'] |
| 45 | - VanRow: typeof import('vant/es')['Row'] | ||
| 46 | - VanSlider: typeof import('vant/es')['Slider'] | ||
| 47 | VanSwipe: typeof import('vant/es')['Swipe'] | 38 | VanSwipe: typeof import('vant/es')['Swipe'] |
| 48 | VanSwipeItem: typeof import('vant/es')['SwipeItem'] | 39 | VanSwipeItem: typeof import('vant/es')['SwipeItem'] |
| 49 | VanTab: typeof import('vant/es')['Tab'] | 40 | VanTab: typeof import('vant/es')['Tab'] | ... | ... |
src/components/JlsBottomNav.vue
deleted
100644 → 0
| 1 | -<template> | ||
| 2 | - <div v-if="isMiniProgramWebView" class="bottom-nav jls-bottom-nav"> | ||
| 3 | - <div | ||
| 4 | - v-for="item in navItems" | ||
| 5 | - :key="item.path" | ||
| 6 | - @click="navigate(item.path)" | ||
| 7 | - :class="['nav-item', isActive(item.name) ? 'nav-item-active' : 'nav-item-inactive']" | ||
| 8 | - > | ||
| 9 | - <div class="nav-item-inner"> | ||
| 10 | - <div class="nav-icon"> | ||
| 11 | - <component :is="item.icon" size="18" :color="getIconColor(item.name)" /> | ||
| 12 | - </div> | ||
| 13 | - <span class="nav-label">{{ item.label }}</span> | ||
| 14 | - </div> | ||
| 15 | - </div> | ||
| 16 | - </div> | ||
| 17 | -</template> | ||
| 18 | - | ||
| 19 | -<script setup> | ||
| 20 | -import { computed } from 'vue'; | ||
| 21 | -import { useRoute } from 'vue-router'; | ||
| 22 | -import HomeIcon from '@nutui/icons-vue/dist/es/icons/Home.js'; | ||
| 23 | -import MessageIcon from '@nutui/icons-vue/dist/es/icons/Message.js'; | ||
| 24 | -import MyIcon from '@nutui/icons-vue/dist/es/icons/My.js'; | ||
| 25 | -import wx from 'weixin-js-sdk'; | ||
| 26 | -import { resolveCheckinActiveTab } from '@/views/checkin/nav-mode.js'; | ||
| 27 | - | ||
| 28 | -const ACTIVE_COLOR = '#a67939'; | ||
| 29 | -const INACTIVE_COLOR = '#8b95a7'; | ||
| 30 | - | ||
| 31 | -const route = useRoute(); | ||
| 32 | - | ||
| 33 | -const isMiniProgramWebView = computed(() => { | ||
| 34 | - return navigator.userAgent.includes('miniProgram'); | ||
| 35 | -}); | ||
| 36 | - | ||
| 37 | -const navItems = [ | ||
| 38 | - { name: 'home', path: '/pages/index/index', icon: HomeIcon, label: '首页' }, | ||
| 39 | - { name: 'message', path: '/pages/message/index', icon: MessageIcon, label: '消息' }, | ||
| 40 | - { name: 'mine', path: '/pages/mine/index', icon: MyIcon, label: '我的' }, | ||
| 41 | -]; | ||
| 42 | - | ||
| 43 | -const currentActiveTab = computed(() => { | ||
| 44 | - return resolveCheckinActiveTab(route.query || {}); | ||
| 45 | -}); | ||
| 46 | - | ||
| 47 | -const isActive = (name) => { | ||
| 48 | - return currentActiveTab.value === name; | ||
| 49 | -}; | ||
| 50 | - | ||
| 51 | -const getIconColor = (name) => { | ||
| 52 | - return isActive(name) ? ACTIVE_COLOR : INACTIVE_COLOR; | ||
| 53 | -}; | ||
| 54 | - | ||
| 55 | -const navigate = (path) => { | ||
| 56 | - if (!path) { | ||
| 57 | - return; | ||
| 58 | - } | ||
| 59 | - | ||
| 60 | - if (wx?.miniProgram?.redirectTo) { | ||
| 61 | - wx.miniProgram.redirectTo({ | ||
| 62 | - url: path, | ||
| 63 | - fail: () => { | ||
| 64 | - wx?.miniProgram?.reLaunch?.({ url: path }); | ||
| 65 | - }, | ||
| 66 | - }); | ||
| 67 | - return; | ||
| 68 | - } | ||
| 69 | - | ||
| 70 | - wx?.miniProgram?.reLaunch?.({ url: path }); | ||
| 71 | -}; | ||
| 72 | -</script> | ||
| 73 | - | ||
| 74 | -<style scoped> | ||
| 75 | -.bottom-nav { | ||
| 76 | - position: fixed; | ||
| 77 | - bottom: 0; | ||
| 78 | - left: 0; | ||
| 79 | - right: 0; | ||
| 80 | - display: flex; | ||
| 81 | - align-items: stretch; | ||
| 82 | - justify-content: space-around; | ||
| 83 | - box-sizing: border-box; | ||
| 84 | - height: 80px; | ||
| 85 | - padding: 8px 12px; | ||
| 86 | - z-index: 50; | ||
| 87 | - min-height: 80px; | ||
| 88 | - background: rgba(255, 255, 255, 0.98); | ||
| 89 | - backdrop-filter: blur(6px); | ||
| 90 | -} | ||
| 91 | - | ||
| 92 | -.nav-item { | ||
| 93 | - display: flex; | ||
| 94 | - flex: 1; | ||
| 95 | - min-width: 0; | ||
| 96 | - align-items: stretch; | ||
| 97 | - justify-content: center; | ||
| 98 | - padding: 0; | ||
| 99 | - position: relative; | ||
| 100 | - cursor: pointer; | ||
| 101 | - transition: all 0.2s ease; | ||
| 102 | -} | ||
| 103 | - | ||
| 104 | -.nav-item-inner { | ||
| 105 | - display: flex; | ||
| 106 | - flex-direction: column; | ||
| 107 | - align-items: center; | ||
| 108 | - justify-content: center; | ||
| 109 | - width: 100%; | ||
| 110 | - height: 100%; | ||
| 111 | - gap: 5px; | ||
| 112 | - border-radius: 10px; | ||
| 113 | - color: #8b95a7; | ||
| 114 | - transition: background-color 0.2s ease, color 0.2s ease, transform 0.2s ease; | ||
| 115 | -} | ||
| 116 | - | ||
| 117 | -.nav-item-active { | ||
| 118 | - color: #a67939; | ||
| 119 | -} | ||
| 120 | - | ||
| 121 | -.nav-item-inactive { | ||
| 122 | - color: #8b95a7; | ||
| 123 | -} | ||
| 124 | - | ||
| 125 | -.nav-icon { | ||
| 126 | - display: flex; | ||
| 127 | - align-items: center; | ||
| 128 | - justify-content: center; | ||
| 129 | - width: 18px; | ||
| 130 | - height: 18px; | ||
| 131 | - line-height: 1; | ||
| 132 | - transition: transform 0.2s ease; | ||
| 133 | -} | ||
| 134 | - | ||
| 135 | -.nav-label { | ||
| 136 | - font-size: 11px; | ||
| 137 | - margin-top: 0; | ||
| 138 | - line-height: 1.2; | ||
| 139 | - font-weight: 600; | ||
| 140 | -} | ||
| 141 | - | ||
| 142 | -.jls-bottom-nav .nav-item-active .nav-item-inner { | ||
| 143 | - color: #a67939; | ||
| 144 | -} | ||
| 145 | - | ||
| 146 | -@media screen and (min-width: 768px) { | ||
| 147 | - .bottom-nav { | ||
| 148 | - height: 80px; | ||
| 149 | - min-height: 80px; | ||
| 150 | - padding: 8px 16px; | ||
| 151 | - } | ||
| 152 | - | ||
| 153 | - .nav-item-inner { | ||
| 154 | - gap: 6px; | ||
| 155 | - } | ||
| 156 | - | ||
| 157 | - .nav-icon { | ||
| 158 | - width: 20px; | ||
| 159 | - height: 20px; | ||
| 160 | - } | ||
| 161 | - | ||
| 162 | - .nav-label { | ||
| 163 | - font-size: 12px; | ||
| 164 | - } | ||
| 165 | -} | ||
| 166 | - | ||
| 167 | -@media (hover: hover) { | ||
| 168 | - .nav-item:hover .nav-item-inner { | ||
| 169 | - transform: translateY(-1px); | ||
| 170 | - } | ||
| 171 | -} | ||
| 172 | -</style> |
src/components/JlsBottomNav/JlsBottomNav.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div v-if="visible" class="jls-bottom-nav"> | ||
| 3 | + <div ref="panelRef" class="jls-bottom-nav__panel" @scroll="syncScrollState"> | ||
| 4 | + <div class="jls-bottom-nav__content" :class="{ 'is-scrollable': isScrollable }"> | ||
| 5 | + <button | ||
| 6 | + v-for="item in tabItems" | ||
| 7 | + :key="item.key" | ||
| 8 | + type="button" | ||
| 9 | + class="jls-bottom-nav__item" | ||
| 10 | + :class="{ 'is-active': isActive(item.key), 'is-scrollable': isScrollable }" | ||
| 11 | + :style="itemStyle" | ||
| 12 | + @click="navigate(item)" | ||
| 13 | + > | ||
| 14 | + <span class="jls-bottom-nav__item-inner"> | ||
| 15 | + <span class="jls-bottom-nav__icon"> | ||
| 16 | + <i class="jls-bottom-nav__icon-font" :class="getIconClasses(item)" aria-hidden="true"></i> | ||
| 17 | + </span> | ||
| 18 | + <span class="jls-bottom-nav__label">{{ item.title }}</span> | ||
| 19 | + </span> | ||
| 20 | + </button> | ||
| 21 | + </div> | ||
| 22 | + </div> | ||
| 23 | + | ||
| 24 | + <div v-if="showScrollFade" class="jls-bottom-nav__fade" /> | ||
| 25 | + </div> | ||
| 26 | +</template> | ||
| 27 | + | ||
| 28 | +<script setup> | ||
| 29 | +import { computed, nextTick, onMounted, ref, watch } from 'vue'; | ||
| 30 | +import { useRoute } from 'vue-router'; | ||
| 31 | +import wx from 'weixin-js-sdk'; | ||
| 32 | +import './iconfont.css'; | ||
| 33 | +import { useJlsTabbar } from './useTabbar'; | ||
| 34 | +import { getTabbarIconClasses } from './tabbar.utils'; | ||
| 35 | +import { resolveJlsCheckinActiveTab } from './nav-state'; | ||
| 36 | + | ||
| 37 | +const SCROLLABLE_ITEM_WIDTH = 92; | ||
| 38 | +const defaultLoadOptions = Object.freeze({ | ||
| 39 | + useMock: false, | ||
| 40 | + mockData: null, | ||
| 41 | +}); | ||
| 42 | + | ||
| 43 | +const props = defineProps({ | ||
| 44 | + visible: { | ||
| 45 | + type: Boolean, | ||
| 46 | + default: false, | ||
| 47 | + }, | ||
| 48 | + loadOptions: { | ||
| 49 | + type: Object, | ||
| 50 | + default: () => ({ | ||
| 51 | + useMock: false, | ||
| 52 | + mockData: null, | ||
| 53 | + }), | ||
| 54 | + }, | ||
| 55 | +}); | ||
| 56 | + | ||
| 57 | +const route = useRoute(); | ||
| 58 | +const panelRef = ref(null); | ||
| 59 | +const isScrolledToEnd = ref(false); | ||
| 60 | +const tabbar = useJlsTabbar(); | ||
| 61 | + | ||
| 62 | +const tabItems = computed(() => tabbar.visibleTabItems.value); | ||
| 63 | +const isScrollable = computed(() => tabItems.value.length > 4); | ||
| 64 | +const itemStyle = computed(() => { | ||
| 65 | + if (!isScrollable.value) { | ||
| 66 | + return null; | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + return { | ||
| 70 | + width: `${SCROLLABLE_ITEM_WIDTH}px`, | ||
| 71 | + minWidth: `${SCROLLABLE_ITEM_WIDTH}px`, | ||
| 72 | + }; | ||
| 73 | +}); | ||
| 74 | +const showScrollFade = computed(() => isScrollable.value && !isScrolledToEnd.value); | ||
| 75 | +const currentActiveTab = computed(() => resolveJlsCheckinActiveTab(route.query || {})); | ||
| 76 | + | ||
| 77 | +const isActive = (key) => key === currentActiveTab.value; | ||
| 78 | + | ||
| 79 | +const getIconClasses = (item) => getTabbarIconClasses(item); | ||
| 80 | + | ||
| 81 | +const syncScrollState = () => { | ||
| 82 | + const panel = panelRef.value; | ||
| 83 | + | ||
| 84 | + if (!panel || !isScrollable.value) { | ||
| 85 | + isScrolledToEnd.value = false; | ||
| 86 | + return; | ||
| 87 | + } | ||
| 88 | + | ||
| 89 | + const maxScrollLeft = Math.max(panel.scrollWidth - panel.clientWidth, 0); | ||
| 90 | + isScrolledToEnd.value = panel.scrollLeft >= maxScrollLeft - 4; | ||
| 91 | +}; | ||
| 92 | + | ||
| 93 | +const navigate = (item) => { | ||
| 94 | + const targetUrl = tabbar.resolveTargetUrl(item); | ||
| 95 | + | ||
| 96 | + if (!targetUrl || isActive(item?.key)) { | ||
| 97 | + return; | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + if (wx?.miniProgram?.redirectTo) { | ||
| 101 | + wx.miniProgram.redirectTo({ | ||
| 102 | + url: targetUrl, | ||
| 103 | + fail: () => { | ||
| 104 | + wx?.miniProgram?.reLaunch?.({ url: targetUrl }); | ||
| 105 | + }, | ||
| 106 | + }); | ||
| 107 | + return; | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + wx?.miniProgram?.reLaunch?.({ url: targetUrl }); | ||
| 111 | +}; | ||
| 112 | + | ||
| 113 | +watch( | ||
| 114 | + () => tabItems.value.length, | ||
| 115 | + async () => { | ||
| 116 | + await nextTick(); | ||
| 117 | + syncScrollState(); | ||
| 118 | + }, | ||
| 119 | + { immediate: true }, | ||
| 120 | +); | ||
| 121 | + | ||
| 122 | +onMounted(async () => { | ||
| 123 | + if (!props.visible) { | ||
| 124 | + return; | ||
| 125 | + } | ||
| 126 | + | ||
| 127 | + await tabbar.ensureLoaded(props.loadOptions || defaultLoadOptions); | ||
| 128 | + await nextTick(); | ||
| 129 | + syncScrollState(); | ||
| 130 | +}); | ||
| 131 | +</script> | ||
| 132 | + | ||
| 133 | +<style scoped> | ||
| 134 | +.jls-bottom-nav { | ||
| 135 | + position: fixed; | ||
| 136 | + bottom: 0; | ||
| 137 | + left: 0; | ||
| 138 | + right: 0; | ||
| 139 | + z-index: 50; | ||
| 140 | + height: 80px; | ||
| 141 | + min-height: 80px; | ||
| 142 | + background: rgba(255, 255, 255, 0.98); | ||
| 143 | + border-top: 1px solid rgba(166, 121, 57, 0.12); | ||
| 144 | + backdrop-filter: blur(6px); | ||
| 145 | + overflow: hidden; | ||
| 146 | +} | ||
| 147 | + | ||
| 148 | +.jls-bottom-nav__panel { | ||
| 149 | + height: 100%; | ||
| 150 | + overflow-x: auto; | ||
| 151 | + overflow-y: hidden; | ||
| 152 | + scrollbar-width: none; | ||
| 153 | + -ms-overflow-style: none; | ||
| 154 | +} | ||
| 155 | + | ||
| 156 | +.jls-bottom-nav__panel::-webkit-scrollbar { | ||
| 157 | + display: none; | ||
| 158 | +} | ||
| 159 | + | ||
| 160 | +.jls-bottom-nav__content { | ||
| 161 | + display: flex; | ||
| 162 | + align-items: stretch; | ||
| 163 | + justify-content: space-around; | ||
| 164 | + box-sizing: border-box; | ||
| 165 | + height: 100%; | ||
| 166 | + padding: 8px 12px; | ||
| 167 | +} | ||
| 168 | + | ||
| 169 | +.jls-bottom-nav__content.is-scrollable { | ||
| 170 | + justify-content: flex-start; | ||
| 171 | + width: max-content; | ||
| 172 | + min-width: 100%; | ||
| 173 | +} | ||
| 174 | + | ||
| 175 | +.jls-bottom-nav__item { | ||
| 176 | + display: flex; | ||
| 177 | + flex: 1; | ||
| 178 | + min-width: 0; | ||
| 179 | + align-items: stretch; | ||
| 180 | + justify-content: center; | ||
| 181 | + padding: 0; | ||
| 182 | + border: 0; | ||
| 183 | + background: transparent; | ||
| 184 | + color: #8b95a7; | ||
| 185 | + cursor: pointer; | ||
| 186 | +} | ||
| 187 | + | ||
| 188 | +.jls-bottom-nav__item.is-scrollable { | ||
| 189 | + flex: 0 0 auto; | ||
| 190 | +} | ||
| 191 | + | ||
| 192 | +.jls-bottom-nav__item-inner { | ||
| 193 | + display: flex; | ||
| 194 | + flex-direction: column; | ||
| 195 | + align-items: center; | ||
| 196 | + justify-content: center; | ||
| 197 | + width: 100%; | ||
| 198 | + height: 100%; | ||
| 199 | + gap: 5px; | ||
| 200 | + border-radius: 10px; | ||
| 201 | + color: inherit; | ||
| 202 | + transition: color 0.2s ease, transform 0.2s ease; | ||
| 203 | +} | ||
| 204 | + | ||
| 205 | +.jls-bottom-nav__item.is-active { | ||
| 206 | + color: #a67939; | ||
| 207 | +} | ||
| 208 | + | ||
| 209 | +.jls-bottom-nav__icon { | ||
| 210 | + display: flex; | ||
| 211 | + align-items: center; | ||
| 212 | + justify-content: center; | ||
| 213 | + width: 18px; | ||
| 214 | + height: 18px; | ||
| 215 | + line-height: 1; | ||
| 216 | +} | ||
| 217 | + | ||
| 218 | +.jls-bottom-nav__icon-font { | ||
| 219 | + font-size: 18px; | ||
| 220 | + line-height: 1; | ||
| 221 | +} | ||
| 222 | + | ||
| 223 | +.jls-bottom-nav__label { | ||
| 224 | + font-size: 11px; | ||
| 225 | + line-height: 1.2; | ||
| 226 | + font-weight: 600; | ||
| 227 | +} | ||
| 228 | + | ||
| 229 | +.jls-bottom-nav__fade { | ||
| 230 | + position: absolute; | ||
| 231 | + top: 0; | ||
| 232 | + right: 0; | ||
| 233 | + bottom: 0; | ||
| 234 | + width: 48px; | ||
| 235 | + pointer-events: none; | ||
| 236 | + background: linear-gradient( | ||
| 237 | + 90deg, | ||
| 238 | + rgba(255, 255, 255, 0) 0%, | ||
| 239 | + rgba(255, 255, 255, 0.36) 42%, | ||
| 240 | + rgba(255, 255, 255, 0.98) 100% | ||
| 241 | + ); | ||
| 242 | +} | ||
| 243 | + | ||
| 244 | +@media screen and (min-width: 768px) { | ||
| 245 | + .jls-bottom-nav__content { | ||
| 246 | + padding: 8px 16px; | ||
| 247 | + } | ||
| 248 | + | ||
| 249 | + .jls-bottom-nav__item-inner { | ||
| 250 | + gap: 6px; | ||
| 251 | + } | ||
| 252 | + | ||
| 253 | + .jls-bottom-nav__icon { | ||
| 254 | + width: 20px; | ||
| 255 | + height: 20px; | ||
| 256 | + } | ||
| 257 | + | ||
| 258 | + .jls-bottom-nav__icon-font { | ||
| 259 | + font-size: 20px; | ||
| 260 | + } | ||
| 261 | + | ||
| 262 | + .jls-bottom-nav__label { | ||
| 263 | + font-size: 12px; | ||
| 264 | + } | ||
| 265 | +} | ||
| 266 | + | ||
| 267 | +@media (hover: hover) { | ||
| 268 | + .jls-bottom-nav__item:hover .jls-bottom-nav__item-inner { | ||
| 269 | + transform: translateY(-1px); | ||
| 270 | + } | ||
| 271 | +} | ||
| 272 | +</style> |
src/components/JlsBottomNav/README.md
0 → 100644
| 1 | +# JlsBottomNav | ||
| 2 | + | ||
| 3 | +面向小程序 WebView 场景的底部导航组件,H5 端保留当前 `80px` 高度体系,并同步小程序侧的底栏数据来源、显示规则和 icon 使用方式。 | ||
| 4 | + | ||
| 5 | +## 文件说明 | ||
| 6 | + | ||
| 7 | +- [JlsBottomNav.vue](/Users/huyirui/program/itomix/git/map-demo/src/components/JlsBottomNav/JlsBottomNav.vue) | ||
| 8 | + 组件主文件,只负责渲染、激活态、高亮、跳转和滚动表现。 | ||
| 9 | +- [tabbar.api.js](/Users/huyirui/program/itomix/git/map-demo/src/components/JlsBottomNav/tabbar.api.js) | ||
| 10 | + 底栏配置接口请求,当前使用 `/srv/?a=app_menu`。 | ||
| 11 | +- [tabbar.utils.js](/Users/huyirui/program/itomix/git/map-demo/src/components/JlsBottomNav/tabbar.utils.js) | ||
| 12 | + 底栏数据归一化、默认项、顺序、icon class 解析、目标地址解析。 | ||
| 13 | +- [useTabbar.js](/Users/huyirui/program/itomix/git/map-demo/src/components/JlsBottomNav/useTabbar.js) | ||
| 14 | + 组件内状态和加载逻辑,支持 `api` / `mock` 两种加载模式。 | ||
| 15 | +- [nav-state.js](/Users/huyirui/program/itomix/git/map-demo/src/components/JlsBottomNav/nav-state.js) | ||
| 16 | + `activeTab` 的新旧 key 兼容规则。 | ||
| 17 | +- [iconfont.css](/Users/huyirui/program/itomix/git/map-demo/src/components/JlsBottomNav/iconfont.css) | ||
| 18 | + 小程序侧 `iconfont` 字体定义。 | ||
| 19 | +- [preview.js](/Users/huyirui/program/itomix/git/map-demo/src/components/JlsBottomNav/preview.js) | ||
| 20 | + 预览辅助层,用于把路由 query 解析成预览参数。 | ||
| 21 | +- [tabbar.mock.js](/Users/huyirui/program/itomix/git/map-demo/src/components/JlsBottomNav/tabbar.mock.js) | ||
| 22 | + 预览用 mock 数据。 | ||
| 23 | +- [index.js](/Users/huyirui/program/itomix/git/map-demo/src/components/JlsBottomNav/index.js) | ||
| 24 | + 目录入口。 | ||
| 25 | + | ||
| 26 | +## 正式接入 | ||
| 27 | + | ||
| 28 | +### 1. 引入组件 | ||
| 29 | + | ||
| 30 | +```js | ||
| 31 | +import JlsBottomNav from '@/components/JlsBottomNav'; | ||
| 32 | +``` | ||
| 33 | + | ||
| 34 | +### 2. 页面里控制显示 | ||
| 35 | + | ||
| 36 | +```vue | ||
| 37 | +<JlsBottomNav | ||
| 38 | + :visible="shouldShowJlsBottomNav" | ||
| 39 | + :load-options="{ | ||
| 40 | + useMock: false, | ||
| 41 | + mockData: null, | ||
| 42 | + }" | ||
| 43 | +/> | ||
| 44 | +``` | ||
| 45 | + | ||
| 46 | +`visible` 为 `true` 时组件才会渲染并尝试加载数据。 | ||
| 47 | + | ||
| 48 | +### 3. 激活态 | ||
| 49 | + | ||
| 50 | +组件通过当前路由的 `activeTab` 识别高亮项,兼容以下写法: | ||
| 51 | + | ||
| 52 | +- `home` | ||
| 53 | +- `message` / `news` | ||
| 54 | +- `application` / `list` | ||
| 55 | +- `mine` / `user` | ||
| 56 | + | ||
| 57 | +例如: | ||
| 58 | + | ||
| 59 | +```txt | ||
| 60 | +?navMode=jls&activeTab=home | ||
| 61 | +?navMode=jls&activeTab=news | ||
| 62 | +?navMode=jls&activeTab=list | ||
| 63 | +?navMode=jls&activeTab=user | ||
| 64 | +``` | ||
| 65 | + | ||
| 66 | +## Icon 规则 | ||
| 67 | + | ||
| 68 | +组件和小程序保持一致: | ||
| 69 | + | ||
| 70 | +- `fa-` 开头:按 `font-awesome` 渲染 | ||
| 71 | +- 其他 class:按 `iconfont` 渲染 | ||
| 72 | + | ||
| 73 | +示例: | ||
| 74 | + | ||
| 75 | +```js | ||
| 76 | +{ icon: 'fa-home' } | ||
| 77 | +{ icon: 'icon-shijian' } | ||
| 78 | +``` | ||
| 79 | + | ||
| 80 | +当前仓库已全局引入 `font-awesome`。如果移植到别的项目,对方项目也需要提供 `font-awesome`,或者自行改成别的图标方案。 | ||
| 81 | + | ||
| 82 | +## 预览与 Mock | ||
| 83 | + | ||
| 84 | +如果只是本地看效果,可以配合 [preview.js](/Users/huyirui/program/itomix/git/map-demo/src/components/JlsBottomNav/preview.js) 使用。 | ||
| 85 | + | ||
| 86 | +当前项目里的用法是: | ||
| 87 | + | ||
| 88 | +```js | ||
| 89 | +import { getJlsTabbarPreviewOptions } from '@/components/JlsBottomNav/preview'; | ||
| 90 | +``` | ||
| 91 | + | ||
| 92 | +它会读取: | ||
| 93 | + | ||
| 94 | +- `jlsTabbarPreview=1` | ||
| 95 | +- `jlsTabbarMock=1` | ||
| 96 | + | ||
| 97 | +并转换成组件需要的 `visible` / `loadOptions`。 | ||
| 98 | + | ||
| 99 | +示例地址: | ||
| 100 | + | ||
| 101 | +```txt | ||
| 102 | +/#/checkin/?navMode=jls&activeTab=home&jlsTabbarPreview=1&jlsTabbarMock=1 | ||
| 103 | +``` | ||
| 104 | + | ||
| 105 | +## 移植建议 | ||
| 106 | + | ||
| 107 | +### 最小正式版本 | ||
| 108 | + | ||
| 109 | +如果对方项目不需要预览辅助,建议带走这些文件: | ||
| 110 | + | ||
| 111 | +- `JlsBottomNav.vue` | ||
| 112 | +- `tabbar.api.js` | ||
| 113 | +- `tabbar.utils.js` | ||
| 114 | +- `useTabbar.js` | ||
| 115 | +- `nav-state.js` | ||
| 116 | +- `iconfont.css` | ||
| 117 | +- `index.js` | ||
| 118 | + | ||
| 119 | +### 可选开发辅助 | ||
| 120 | + | ||
| 121 | +如果对方也希望保留本地预览,再额外带走: | ||
| 122 | + | ||
| 123 | +- `preview.js` | ||
| 124 | +- `tabbar.mock.js` | ||
| 125 | + | ||
| 126 | +## 注意点 | ||
| 127 | + | ||
| 128 | +- 组件默认按小程序 WebView 跳转方式调用 `wx.miniProgram.redirectTo / reLaunch`。 | ||
| 129 | +- 当前跳转逻辑是: | ||
| 130 | + - `home` 默认跳回小程序页 `/pages/index/index` | ||
| 131 | + - 其他项不会直接跳裸 H5 URL,而是优先使用接口返回的 `page_url` | ||
| 132 | + - 如果接口没有返回 `page_url`,则会把 `webview_url` 包装成小程序承接页地址,例如 `/pages/webview-preview/index?url=...` | ||
| 133 | +- 也就是说,当前实现仍然是“首页回小程序页,其他项走小程序承接页再打开 H5”,不是“其他项直接在当前 H5 中跳转到接口 URL”。 | ||
| 134 | +- 当前样式尺寸以 H5 版本为准,没有照搬小程序 `rpx` 布局。 |
src/components/JlsBottomNav/iconfont.css
0 → 100644
| 1 | +@font-face { | ||
| 2 | + font-family: 'iconfont'; | ||
| 3 | + src: | ||
| 4 | + url('https://at.alicdn.com/t/c/font_4618760_7lrp6nlwv9y.woff2?t=1778124018667') format('woff2'), | ||
| 5 | + url('https://at.alicdn.com/t/c/font_4618760_7lrp6nlwv9y.woff?t=1778124018667') format('woff'), | ||
| 6 | + url('https://at.alicdn.com/t/c/font_4618760_7lrp6nlwv9y.ttf?t=1778124018667') format('truetype'); | ||
| 7 | +} | ||
| 8 | + | ||
| 9 | +.iconfont { | ||
| 10 | + font-family: 'iconfont' !important; | ||
| 11 | + font-size: 16px; | ||
| 12 | + font-style: normal; | ||
| 13 | + -webkit-font-smoothing: antialiased; | ||
| 14 | + -moz-osx-font-smoothing: grayscale; | ||
| 15 | +} | ||
| 16 | + | ||
| 17 | +.icon-zanwuneirong:before { | ||
| 18 | + content: '\e64b'; | ||
| 19 | +} | ||
| 20 | + | ||
| 21 | +.icon-zhiribiao:before { | ||
| 22 | + content: '\e6c2'; | ||
| 23 | +} | ||
| 24 | + | ||
| 25 | +.icon-yanzhengma:before { | ||
| 26 | + content: '\e6c1'; | ||
| 27 | +} | ||
| 28 | + | ||
| 29 | +.icon-changdigailan:before { | ||
| 30 | + content: '\e6bf'; | ||
| 31 | +} | ||
| 32 | + | ||
| 33 | +.icon-queren:before { | ||
| 34 | + content: '\e6c0'; | ||
| 35 | +} | ||
| 36 | + | ||
| 37 | +.icon-jichengmingdanguanli:before { | ||
| 38 | + content: '\e6bb'; | ||
| 39 | +} | ||
| 40 | + | ||
| 41 | +.icon-shenqingbiao:before { | ||
| 42 | + content: '\e6bc'; | ||
| 43 | +} | ||
| 44 | + | ||
| 45 | +.icon-weixintongzhimoban:before { | ||
| 46 | + content: '\e6bd'; | ||
| 47 | +} | ||
| 48 | + | ||
| 49 | +.icon-gangweihuizong:before { | ||
| 50 | + content: '\e6be'; | ||
| 51 | +} | ||
| 52 | + | ||
| 53 | +.icon-mima:before { | ||
| 54 | + content: '\e6b5'; | ||
| 55 | +} | ||
| 56 | + | ||
| 57 | +.icon-shoucang:before { | ||
| 58 | + content: '\e6b6'; | ||
| 59 | +} | ||
| 60 | + | ||
| 61 | +.icon-daiban:before { | ||
| 62 | + content: '\e6b7'; | ||
| 63 | +} | ||
| 64 | + | ||
| 65 | +.icon-faqi:before { | ||
| 66 | + content: '\e6b8'; | ||
| 67 | +} | ||
| 68 | + | ||
| 69 | +.icon-chaosong:before { | ||
| 70 | + content: '\e6b9'; | ||
| 71 | +} | ||
| 72 | + | ||
| 73 | +.icon-geren:before { | ||
| 74 | + content: '\e6ba'; | ||
| 75 | +} | ||
| 76 | + | ||
| 77 | +.icon-zhankai:before { | ||
| 78 | + content: '\e6b0'; | ||
| 79 | +} | ||
| 80 | + | ||
| 81 | +.icon-fanhui:before { | ||
| 82 | + content: '\e6b1'; | ||
| 83 | +} | ||
| 84 | + | ||
| 85 | +.icon-guanbi:before { | ||
| 86 | + content: '\e6b2'; | ||
| 87 | +} | ||
| 88 | + | ||
| 89 | +.icon-guan:before { | ||
| 90 | + content: '\e6b3'; | ||
| 91 | +} | ||
| 92 | + | ||
| 93 | +.icon-kai:before { | ||
| 94 | + content: '\e6b4'; | ||
| 95 | +} | ||
| 96 | + | ||
| 97 | +.icon-bianji:before { | ||
| 98 | + content: '\e6a8'; | ||
| 99 | +} | ||
| 100 | + | ||
| 101 | +.icon-tianjia:before { | ||
| 102 | + content: '\e6a9'; | ||
| 103 | +} | ||
| 104 | + | ||
| 105 | +.icon-gouwuche:before { | ||
| 106 | + content: '\e6aa'; | ||
| 107 | +} | ||
| 108 | + | ||
| 109 | +.icon-rili:before { | ||
| 110 | + content: '\e6ab'; | ||
| 111 | +} | ||
| 112 | + | ||
| 113 | +.icon-shanchu:before { | ||
| 114 | + content: '\e6ac'; | ||
| 115 | +} | ||
| 116 | + | ||
| 117 | +.icon-dianhua:before { | ||
| 118 | + content: '\e6ad'; | ||
| 119 | +} | ||
| 120 | + | ||
| 121 | +.icon-right:before { | ||
| 122 | + content: '\e6ae'; | ||
| 123 | +} | ||
| 124 | + | ||
| 125 | +.icon-dui:before { | ||
| 126 | + content: '\e6af'; | ||
| 127 | +} | ||
| 128 | + | ||
| 129 | +.icon-left:before { | ||
| 130 | + content: '\e6a7'; | ||
| 131 | +} | ||
| 132 | + | ||
| 133 | +.icon-nv-01:before { | ||
| 134 | + content: '\e6a5'; | ||
| 135 | +} | ||
| 136 | + | ||
| 137 | +.icon-nan-01:before { | ||
| 138 | + content: '\e6a6'; | ||
| 139 | +} | ||
| 140 | + | ||
| 141 | +.icon-sousuo:before { | ||
| 142 | + content: '\e69c'; | ||
| 143 | +} | ||
| 144 | + | ||
| 145 | +.icon-nianfogongxiu:before { | ||
| 146 | + content: '\e69d'; | ||
| 147 | +} | ||
| 148 | + | ||
| 149 | +.icon-qita:before { | ||
| 150 | + content: '\e69e'; | ||
| 151 | +} | ||
| 152 | + | ||
| 153 | +.icon-huodonghaibao:before { | ||
| 154 | + content: '\e69f'; | ||
| 155 | +} | ||
| 156 | + | ||
| 157 | +.icon-rusizhinan:before { | ||
| 158 | + content: '\e6a0'; | ||
| 159 | +} | ||
| 160 | + | ||
| 161 | +.icon-renyuan:before { | ||
| 162 | + content: '\e6a1'; | ||
| 163 | +} | ||
| 164 | + | ||
| 165 | +.icon-changdishenqing:before { | ||
| 166 | + content: '\e6a2'; | ||
| 167 | +} | ||
| 168 | + | ||
| 169 | +.icon-huodongguanli:before { | ||
| 170 | + content: '\e6a3'; | ||
| 171 | +} | ||
| 172 | + | ||
| 173 | +.icon-changguicanxue:before { | ||
| 174 | + content: '\e6a4'; | ||
| 175 | +} | ||
| 176 | + | ||
| 177 | +.icon-yingyong:before { | ||
| 178 | + content: '\e68d'; | ||
| 179 | +} | ||
| 180 | + | ||
| 181 | +.icon-renyuandingwei:before { | ||
| 182 | + content: '\e68e'; | ||
| 183 | +} | ||
| 184 | + | ||
| 185 | +.icon-xiaoxi:before { | ||
| 186 | + content: '\e68f'; | ||
| 187 | +} | ||
| 188 | + | ||
| 189 | +.icon-guiyigongxiu:before { | ||
| 190 | + content: '\e690'; | ||
| 191 | +} | ||
| 192 | + | ||
| 193 | +.icon-wodebaoming:before { | ||
| 194 | + content: '\e691'; | ||
| 195 | +} | ||
| 196 | + | ||
| 197 | +.icon-wodeyuyue:before { | ||
| 198 | + content: '\e692'; | ||
| 199 | +} | ||
| 200 | + | ||
| 201 | +.icon-shaotouxiang:before { | ||
| 202 | + content: '\e693'; | ||
| 203 | +} | ||
| 204 | + | ||
| 205 | +.icon-xinzhongmingdan:before { | ||
| 206 | + content: '\e694'; | ||
| 207 | +} | ||
| 208 | + | ||
| 209 | +.icon-a-3Dloucengdaolan:before { | ||
| 210 | + content: '\e695'; | ||
| 211 | +} | ||
| 212 | + | ||
| 213 | +.icon-huodongkanban:before { | ||
| 214 | + content: '\e696'; | ||
| 215 | +} | ||
| 216 | + | ||
| 217 | +.icon-wode:before { | ||
| 218 | + content: '\e697'; | ||
| 219 | +} | ||
| 220 | + | ||
| 221 | +.icon-dicangfahui:before { | ||
| 222 | + content: '\e698'; | ||
| 223 | +} | ||
| 224 | + | ||
| 225 | +.icon-shijian:before { | ||
| 226 | + content: '\e699'; | ||
| 227 | +} | ||
| 228 | + | ||
| 229 | +.icon-shouye:before { | ||
| 230 | + content: '\e69a'; | ||
| 231 | +} | ||
| 232 | + | ||
| 233 | +.icon-nianduhuodongbiao:before { | ||
| 234 | + content: '\e69b'; | ||
| 235 | +} | ||
| 236 | + | ||
| 237 | +.icon-yigongmingdan:before { | ||
| 238 | + content: '\e67f'; | ||
| 239 | +} | ||
| 240 | + | ||
| 241 | +.icon-jingjinfoqi:before { | ||
| 242 | + content: '\e680'; | ||
| 243 | +} | ||
| 244 | + | ||
| 245 | +.icon-yufojie:before { | ||
| 246 | + content: '\e681'; | ||
| 247 | +} | ||
| 248 | + | ||
| 249 | +.icon-zhongyuanjie:before { | ||
| 250 | + content: '\e682'; | ||
| 251 | +} | ||
| 252 | + | ||
| 253 | +.icon-shuhuazhan:before { | ||
| 254 | + content: '\e683'; | ||
| 255 | +} | ||
| 256 | + | ||
| 257 | +.icon-zuchang:before { | ||
| 258 | + content: '\e684'; | ||
| 259 | +} | ||
| 260 | + | ||
| 261 | +.icon-wuzishenqing:before { | ||
| 262 | + content: '\e685'; | ||
| 263 | +} | ||
| 264 | + | ||
| 265 | +.icon-zuchangtongxunlu:before { | ||
| 266 | + content: '\e686'; | ||
| 267 | +} | ||
| 268 | + | ||
| 269 | +.icon-zhusushenqing:before { | ||
| 270 | + content: '\e687'; | ||
| 271 | +} | ||
| 272 | + | ||
| 273 | +.icon-shezhi:before { | ||
| 274 | + content: '\e688'; | ||
| 275 | +} | ||
| 276 | + | ||
| 277 | +.icon-yijian:before { | ||
| 278 | + content: '\e689'; | ||
| 279 | +} | ||
| 280 | + | ||
| 281 | +.icon-zhongyaoshebeibaojing:before { | ||
| 282 | + content: '\e68a'; | ||
| 283 | +} | ||
| 284 | + | ||
| 285 | +.icon-yongcanshenqing:before { | ||
| 286 | + content: '\e68b'; | ||
| 287 | +} | ||
| 288 | + | ||
| 289 | +.icon-shengtaifangsheng:before { | ||
| 290 | + content: '\e68c'; | ||
| 291 | +} | ||
| 292 | + | ||
| 293 | +.icon-VRzhanshi:before { | ||
| 294 | + content: '\e67e'; | ||
| 295 | +} | ||
| 296 | + | ||
| 297 | +.icon-ditudaolan:before { | ||
| 298 | + content: '\e677'; | ||
| 299 | +} | ||
| 300 | + | ||
| 301 | +.icon-guanyindan:before { | ||
| 302 | + content: '\e678'; | ||
| 303 | +} | ||
| 304 | + | ||
| 305 | +.icon-chanxiuying:before { | ||
| 306 | + content: '\e679'; | ||
| 307 | +} | ||
| 308 | + | ||
| 309 | +.icon-sanguiwujie:before { | ||
| 310 | + content: '\e67a'; | ||
| 311 | +} | ||
| 312 | + | ||
| 313 | +.icon-baguanzhaijie:before { | ||
| 314 | + content: '\e67b'; | ||
| 315 | +} | ||
| 316 | + | ||
| 317 | +.icon-fangsheng:before { | ||
| 318 | + content: '\e67c'; | ||
| 319 | +} | ||
| 320 | + | ||
| 321 | +.icon-jingxiuying:before { | ||
| 322 | + content: '\e67d'; | ||
| 323 | +} |
src/components/JlsBottomNav/index.js
0 → 100644
| 1 | +export { default } from './JlsBottomNav.vue'; |
src/components/JlsBottomNav/nav-state.js
0 → 100644
| 1 | +export const JLS_CHECKIN_ACTIVE_TAB = { | ||
| 2 | + HOME: 'home', | ||
| 3 | + MESSAGE: 'news', | ||
| 4 | + APPLICATION: 'list', | ||
| 5 | + MINE: 'user', | ||
| 6 | +}; | ||
| 7 | + | ||
| 8 | +const JLS_CHECKIN_ACTIVE_TAB_ALIAS_MAP = { | ||
| 9 | + home: JLS_CHECKIN_ACTIVE_TAB.HOME, | ||
| 10 | + message: JLS_CHECKIN_ACTIVE_TAB.MESSAGE, | ||
| 11 | + news: JLS_CHECKIN_ACTIVE_TAB.MESSAGE, | ||
| 12 | + application: JLS_CHECKIN_ACTIVE_TAB.APPLICATION, | ||
| 13 | + list: JLS_CHECKIN_ACTIVE_TAB.APPLICATION, | ||
| 14 | + mine: JLS_CHECKIN_ACTIVE_TAB.MINE, | ||
| 15 | + user: JLS_CHECKIN_ACTIVE_TAB.MINE, | ||
| 16 | +}; | ||
| 17 | + | ||
| 18 | +export const resolveJlsCheckinActiveTab = (query = {}) => { | ||
| 19 | + const rawTab = Array.isArray(query.activeTab) ? query.activeTab[0] : query.activeTab; | ||
| 20 | + const normalizedTab = String(rawTab || '').trim().toLowerCase(); | ||
| 21 | + return JLS_CHECKIN_ACTIVE_TAB_ALIAS_MAP[normalizedTab] || ''; | ||
| 22 | +}; |
src/components/JlsBottomNav/preview.js
0 → 100644
| 1 | +import { getJlsTabbarMockData } from './tabbar.mock'; | ||
| 2 | + | ||
| 3 | +const getQueryValue = (query = {}, key = '') => String(query?.[key] || ''); | ||
| 4 | + | ||
| 5 | +export const getJlsTabbarPreviewOptions = (query = {}) => { | ||
| 6 | + const previewEnabled = getQueryValue(query, 'jlsTabbarPreview') === '1'; | ||
| 7 | + const mockEnabled = getQueryValue(query, 'jlsTabbarMock') === '1'; | ||
| 8 | + | ||
| 9 | + return { | ||
| 10 | + previewEnabled, | ||
| 11 | + loadOptions: mockEnabled | ||
| 12 | + ? { | ||
| 13 | + useMock: true, | ||
| 14 | + mockData: getJlsTabbarMockData(), | ||
| 15 | + } | ||
| 16 | + : { | ||
| 17 | + useMock: false, | ||
| 18 | + mockData: null, | ||
| 19 | + }, | ||
| 20 | + }; | ||
| 21 | +}; |
src/components/JlsBottomNav/tabbar.api.js
0 → 100644
src/components/JlsBottomNav/tabbar.mock.js
0 → 100644
| 1 | +export const getJlsTabbarMockData = () => ({ | ||
| 2 | + home: { | ||
| 3 | + title: '首页', | ||
| 4 | + icon: 'fa-home', | ||
| 5 | + link: '', | ||
| 6 | + }, | ||
| 7 | + news: { | ||
| 8 | + title: '资讯', | ||
| 9 | + icon: 'fa-newspaper-o', | ||
| 10 | + link: 'https://oa-dev.onwall.cn/f/futian_home/?f=f&p=news_list', | ||
| 11 | + }, | ||
| 12 | + list: { | ||
| 13 | + title: '应用', | ||
| 14 | + icon: 'fa-th-large', | ||
| 15 | + link: 'https://oa-dev.onwall.cn/f/futian_home/?f=f&p=futian_list', | ||
| 16 | + }, | ||
| 17 | + user: { | ||
| 18 | + title: '我的', | ||
| 19 | + icon: 'fa-user', | ||
| 20 | + link: 'https://oa-dev.onwall.cn/f/futian_home/?f=f&p=futian_list', | ||
| 21 | + }, | ||
| 22 | + calendar: { | ||
| 23 | + title: '日程', | ||
| 24 | + icon: 'icon-shijian', | ||
| 25 | + link: 'https://oa-dev.onwall.cn/f/futian_home/?f=f&p=schedule', | ||
| 26 | + }, | ||
| 27 | + board: { | ||
| 28 | + title: '看板', | ||
| 29 | + icon: 'icon-huodongkanban', | ||
| 30 | + link: 'https://oa-dev.onwall.cn/f/futian_home/?f=f&p=dashboard', | ||
| 31 | + }, | ||
| 32 | +}); |
src/components/JlsBottomNav/tabbar.utils.js
0 → 100644
| 1 | +const TABBAR_KEY_ALIAS_MAP = { | ||
| 2 | + message: 'news', | ||
| 3 | + application: 'list', | ||
| 4 | + mine: 'user', | ||
| 5 | +}; | ||
| 6 | + | ||
| 7 | +const KNOWN_TABBAR_ITEM_MAP = { | ||
| 8 | + home: { | ||
| 9 | + key: 'home', | ||
| 10 | + title: '首页', | ||
| 11 | + class: 'fa-home', | ||
| 12 | + visible: true, | ||
| 13 | + page_url: '/pages/index/index', | ||
| 14 | + webview_url: '', | ||
| 15 | + webview_title: '首页', | ||
| 16 | + }, | ||
| 17 | + news: { | ||
| 18 | + key: 'news', | ||
| 19 | + title: '资讯', | ||
| 20 | + class: 'fa-newspaper-o', | ||
| 21 | + visible: true, | ||
| 22 | + page_url: '', | ||
| 23 | + webview_url: 'https://oa-dev.onwall.cn/f/futian_home/?f=f&p=news_list', | ||
| 24 | + webview_title: '资讯', | ||
| 25 | + }, | ||
| 26 | + list: { | ||
| 27 | + key: 'list', | ||
| 28 | + title: '应用', | ||
| 29 | + class: 'fa-th-large', | ||
| 30 | + visible: true, | ||
| 31 | + page_url: '', | ||
| 32 | + webview_url: 'https://oa-dev.onwall.cn/f/futian_home/?f=f&p=futian_list', | ||
| 33 | + webview_title: '应用', | ||
| 34 | + }, | ||
| 35 | + user: { | ||
| 36 | + key: 'user', | ||
| 37 | + title: '我的', | ||
| 38 | + class: 'fa-user', | ||
| 39 | + visible: true, | ||
| 40 | + page_url: '', | ||
| 41 | + webview_url: 'https://oa-dev.onwall.cn/f/futian_home/?f=f&p=futian_list', | ||
| 42 | + webview_title: '我的', | ||
| 43 | + }, | ||
| 44 | +}; | ||
| 45 | + | ||
| 46 | +const TABBAR_ORDER = ['home', 'news', 'list', 'user']; | ||
| 47 | +const DEFAULT_ICON_CLASS = 'fa-circle-o'; | ||
| 48 | + | ||
| 49 | +const normalizeVisibleValue = (rawValue, fallbackValue = true) => { | ||
| 50 | + if (typeof rawValue === 'boolean') { | ||
| 51 | + return rawValue; | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + if (rawValue === 1 || rawValue === '1') { | ||
| 55 | + return true; | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + if (rawValue === 0 || rawValue === '0') { | ||
| 59 | + return false; | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + return fallbackValue; | ||
| 63 | +}; | ||
| 64 | + | ||
| 65 | +export const normalizeTabbarKey = (key) => { | ||
| 66 | + const normalizedKey = String(key || '').trim().toLowerCase(); | ||
| 67 | + return TABBAR_KEY_ALIAS_MAP[normalizedKey] || normalizedKey; | ||
| 68 | +}; | ||
| 69 | + | ||
| 70 | +export const buildWebviewPreviewUrl = (targetUrl = '', pageTitle = '') => { | ||
| 71 | + const normalizedUrl = String(targetUrl || '').trim(); | ||
| 72 | + const normalizedTitle = String(pageTitle || '').trim(); | ||
| 73 | + | ||
| 74 | + if (!normalizedUrl) { | ||
| 75 | + return '/pages/webview-preview/index'; | ||
| 76 | + } | ||
| 77 | + | ||
| 78 | + const queryList = [`url=${encodeURIComponent(normalizedUrl)}`]; | ||
| 79 | + | ||
| 80 | + if (normalizedTitle) { | ||
| 81 | + queryList.push(`title=${encodeURIComponent(normalizedTitle)}`); | ||
| 82 | + } | ||
| 83 | + | ||
| 84 | + return `/pages/webview-preview/index?${queryList.join('&')}`; | ||
| 85 | +}; | ||
| 86 | + | ||
| 87 | +const createFallbackTabbarItem = (key, title = '') => { | ||
| 88 | + const normalizedKey = normalizeTabbarKey(key); | ||
| 89 | + | ||
| 90 | + return { | ||
| 91 | + key: normalizedKey, | ||
| 92 | + title: title || '栏目', | ||
| 93 | + class: DEFAULT_ICON_CLASS, | ||
| 94 | + visible: true, | ||
| 95 | + page_url: normalizedKey === 'home' ? '/pages/index/index' : '', | ||
| 96 | + webview_url: '', | ||
| 97 | + webview_title: title || '栏目', | ||
| 98 | + }; | ||
| 99 | +}; | ||
| 100 | + | ||
| 101 | +export const getDefaultTabbarItem = (key) => { | ||
| 102 | + const normalizedKey = normalizeTabbarKey(key); | ||
| 103 | + const fallbackItem = | ||
| 104 | + KNOWN_TABBAR_ITEM_MAP[normalizedKey] || createFallbackTabbarItem(normalizedKey); | ||
| 105 | + | ||
| 106 | + return { | ||
| 107 | + ...fallbackItem, | ||
| 108 | + }; | ||
| 109 | +}; | ||
| 110 | + | ||
| 111 | +export const getDefaultTabbarItems = () => | ||
| 112 | + TABBAR_ORDER.map((key) => getDefaultTabbarItem(key)).filter(Boolean); | ||
| 113 | + | ||
| 114 | +const buildTabbarPageUrl = (key, webviewUrl, webviewTitle, rawPageUrl = '') => { | ||
| 115 | + if (key === 'home') { | ||
| 116 | + return rawPageUrl || '/pages/index/index'; | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + if (!webviewUrl) { | ||
| 120 | + return ''; | ||
| 121 | + } | ||
| 122 | + | ||
| 123 | + return buildWebviewPreviewUrl(webviewUrl, webviewTitle); | ||
| 124 | +}; | ||
| 125 | + | ||
| 126 | +export const normalizeTabbarItem = (rawItem = {}) => { | ||
| 127 | + const normalizedKey = normalizeTabbarKey(rawItem.key || rawItem.name); | ||
| 128 | + const fallbackItem = getDefaultTabbarItem(normalizedKey || rawItem.title); | ||
| 129 | + const title = String(rawItem.title || fallbackItem.title || '栏目'); | ||
| 130 | + const webviewUrl = String( | ||
| 131 | + rawItem.webview_url || | ||
| 132 | + rawItem.link_url || | ||
| 133 | + rawItem.link || | ||
| 134 | + rawItem.url || | ||
| 135 | + fallbackItem.webview_url || | ||
| 136 | + '', | ||
| 137 | + ); | ||
| 138 | + const webviewTitle = String( | ||
| 139 | + rawItem.webview_title || | ||
| 140 | + rawItem.link_title || | ||
| 141 | + rawItem.title || | ||
| 142 | + fallbackItem.webview_title || | ||
| 143 | + title, | ||
| 144 | + ); | ||
| 145 | + | ||
| 146 | + return { | ||
| 147 | + ...fallbackItem, | ||
| 148 | + ...rawItem, | ||
| 149 | + key: fallbackItem.key, | ||
| 150 | + title, | ||
| 151 | + class: String( | ||
| 152 | + rawItem.class || | ||
| 153 | + rawItem.icon_class || | ||
| 154 | + rawItem.icon || | ||
| 155 | + fallbackItem.class || | ||
| 156 | + DEFAULT_ICON_CLASS, | ||
| 157 | + ), | ||
| 158 | + webview_url: webviewUrl, | ||
| 159 | + webview_title: webviewTitle, | ||
| 160 | + page_url: buildTabbarPageUrl( | ||
| 161 | + fallbackItem.key, | ||
| 162 | + webviewUrl, | ||
| 163 | + webviewTitle, | ||
| 164 | + String(rawItem.page_url || fallbackItem.page_url || ''), | ||
| 165 | + ), | ||
| 166 | + visible: normalizeVisibleValue( | ||
| 167 | + rawItem.visible ?? rawItem.is_show ?? rawItem.show, | ||
| 168 | + fallbackItem.visible, | ||
| 169 | + ), | ||
| 170 | + }; | ||
| 171 | +}; | ||
| 172 | + | ||
| 173 | +const sortTabbarObjectEntries = (entries = []) => { | ||
| 174 | + const orderMap = TABBAR_ORDER.reduce((map, key, index) => { | ||
| 175 | + map[key] = index; | ||
| 176 | + return map; | ||
| 177 | + }, {}); | ||
| 178 | + | ||
| 179 | + return entries | ||
| 180 | + .map((entry, index) => ({ entry, index })) | ||
| 181 | + .sort((left, right) => { | ||
| 182 | + const leftKey = normalizeTabbarKey(left.entry[0]); | ||
| 183 | + const rightKey = normalizeTabbarKey(right.entry[0]); | ||
| 184 | + const leftOrder = orderMap[leftKey]; | ||
| 185 | + const rightOrder = orderMap[rightKey]; | ||
| 186 | + const leftKnown = Number.isInteger(leftOrder); | ||
| 187 | + const rightKnown = Number.isInteger(rightOrder); | ||
| 188 | + | ||
| 189 | + if (leftKnown && rightKnown) { | ||
| 190 | + return leftOrder - rightOrder; | ||
| 191 | + } | ||
| 192 | + | ||
| 193 | + if (leftKnown) { | ||
| 194 | + return -1; | ||
| 195 | + } | ||
| 196 | + | ||
| 197 | + if (rightKnown) { | ||
| 198 | + return 1; | ||
| 199 | + } | ||
| 200 | + | ||
| 201 | + return left.index - right.index; | ||
| 202 | + }) | ||
| 203 | + .map(({ entry }) => entry); | ||
| 204 | +}; | ||
| 205 | + | ||
| 206 | +const normalizeTabbarObjectItems = (rawMap = {}) => { | ||
| 207 | + const objectEntries = Object.entries(rawMap).filter( | ||
| 208 | + ([, value]) => value && typeof value === 'object' && !Array.isArray(value), | ||
| 209 | + ); | ||
| 210 | + | ||
| 211 | + if (!objectEntries.length) { | ||
| 212 | + return []; | ||
| 213 | + } | ||
| 214 | + | ||
| 215 | + return sortTabbarObjectEntries(objectEntries) | ||
| 216 | + .map(([key, value]) => | ||
| 217 | + normalizeTabbarItem({ | ||
| 218 | + ...value, | ||
| 219 | + key, | ||
| 220 | + }), | ||
| 221 | + ) | ||
| 222 | + .filter(Boolean); | ||
| 223 | +}; | ||
| 224 | + | ||
| 225 | +export const normalizeTabbarItems = (rawItems = []) => { | ||
| 226 | + if (!Array.isArray(rawItems) || !rawItems.length) { | ||
| 227 | + return getDefaultTabbarItems(); | ||
| 228 | + } | ||
| 229 | + | ||
| 230 | + const normalizedItems = rawItems.map((item) => normalizeTabbarItem(item)).filter(Boolean); | ||
| 231 | + | ||
| 232 | + return normalizedItems.length ? normalizedItems : getDefaultTabbarItems(); | ||
| 233 | +}; | ||
| 234 | + | ||
| 235 | +export const normalizeTabbarPayload = (rawPayload = null) => { | ||
| 236 | + if (Array.isArray(rawPayload)) { | ||
| 237 | + return normalizeTabbarItems(rawPayload); | ||
| 238 | + } | ||
| 239 | + | ||
| 240 | + const arrayPayload = rawPayload?.tab_items || rawPayload?.tabs || rawPayload?.menus; | ||
| 241 | + if (Array.isArray(arrayPayload)) { | ||
| 242 | + return normalizeTabbarItems(arrayPayload); | ||
| 243 | + } | ||
| 244 | + | ||
| 245 | + const objectPayload = rawPayload?.tab_items || rawPayload?.tabs || rawPayload?.menus || rawPayload; | ||
| 246 | + const normalizedItems = normalizeTabbarObjectItems(objectPayload); | ||
| 247 | + | ||
| 248 | + return normalizedItems.length ? normalizedItems : getDefaultTabbarItems(); | ||
| 249 | +}; | ||
| 250 | + | ||
| 251 | +export const resolveTabbarTargetUrl = (item = {}) => { | ||
| 252 | + if (item?.page_url) { | ||
| 253 | + return item.page_url; | ||
| 254 | + } | ||
| 255 | + | ||
| 256 | + if (normalizeTabbarKey(item?.key) === 'home') { | ||
| 257 | + return '/pages/index/index'; | ||
| 258 | + } | ||
| 259 | + | ||
| 260 | + return buildWebviewPreviewUrl(item?.webview_url, item?.webview_title || item?.title); | ||
| 261 | +}; | ||
| 262 | + | ||
| 263 | +export const getTabbarIconClasses = (item = {}) => { | ||
| 264 | + const icon = String(item?.class || item?.icon || DEFAULT_ICON_CLASS).trim() || DEFAULT_ICON_CLASS; | ||
| 265 | + const fontClass = icon.startsWith('fa-') ? 'fa' : 'iconfont'; | ||
| 266 | + return [fontClass, icon]; | ||
| 267 | +}; |
src/components/JlsBottomNav/useTabbar.js
0 → 100644
| 1 | +import { computed, reactive, readonly } from 'vue'; | ||
| 2 | +import { getTabbarConfigAPI } from './tabbar.api'; | ||
| 3 | +import { | ||
| 4 | + getDefaultTabbarItem, | ||
| 5 | + getDefaultTabbarItems, | ||
| 6 | + normalizeTabbarKey, | ||
| 7 | + normalizeTabbarPayload, | ||
| 8 | + resolveTabbarTargetUrl, | ||
| 9 | +} from './tabbar.utils'; | ||
| 10 | + | ||
| 11 | +const state = reactive({ | ||
| 12 | + tabItems: getDefaultTabbarItems(), | ||
| 13 | + loaded: false, | ||
| 14 | + loading: false, | ||
| 15 | + loadMode: 'api', | ||
| 16 | +}); | ||
| 17 | + | ||
| 18 | +let tabbarRequestPromise = null; | ||
| 19 | + | ||
| 20 | +const applyFallbackItems = () => { | ||
| 21 | + state.tabItems = getDefaultTabbarItems(); | ||
| 22 | +}; | ||
| 23 | + | ||
| 24 | +const resolveLoadMode = (options = {}) => (options.useMock ? 'mock' : 'api'); | ||
| 25 | + | ||
| 26 | +export const fetchTabbarConfig = async (force = false, options = {}) => { | ||
| 27 | + const loadMode = resolveLoadMode(options); | ||
| 28 | + | ||
| 29 | + if (!force && state.loaded && state.loadMode === loadMode) { | ||
| 30 | + return state.tabItems; | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + if (state.loading && tabbarRequestPromise) { | ||
| 34 | + return tabbarRequestPromise; | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + state.loading = true; | ||
| 38 | + | ||
| 39 | + tabbarRequestPromise = (async () => { | ||
| 40 | + try { | ||
| 41 | + if (loadMode === 'mock') { | ||
| 42 | + state.tabItems = normalizeTabbarPayload(options.mockData); | ||
| 43 | + state.loadMode = loadMode; | ||
| 44 | + return state.tabItems; | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + const response = await getTabbarConfigAPI(); | ||
| 48 | + | ||
| 49 | + if (response?.code === 1) { | ||
| 50 | + state.tabItems = normalizeTabbarPayload(response?.data); | ||
| 51 | + } else { | ||
| 52 | + applyFallbackItems(); | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + state.loadMode = loadMode; | ||
| 56 | + } catch (error) { | ||
| 57 | + console.error('获取底部导航配置失败:', error); | ||
| 58 | + applyFallbackItems(); | ||
| 59 | + state.loadMode = loadMode; | ||
| 60 | + } finally { | ||
| 61 | + state.loaded = true; | ||
| 62 | + state.loading = false; | ||
| 63 | + tabbarRequestPromise = null; | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + return state.tabItems; | ||
| 67 | + })(); | ||
| 68 | + | ||
| 69 | + return tabbarRequestPromise; | ||
| 70 | +}; | ||
| 71 | + | ||
| 72 | +export const ensureTabbarLoaded = (options = {}) => { | ||
| 73 | + const loadMode = resolveLoadMode(options); | ||
| 74 | + | ||
| 75 | + if (state.loaded && state.loadMode === loadMode) { | ||
| 76 | + return Promise.resolve(state.tabItems); | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + return fetchTabbarConfig(false, options); | ||
| 80 | +}; | ||
| 81 | + | ||
| 82 | +export const useJlsTabbar = () => { | ||
| 83 | + const visibleTabItems = computed(() => state.tabItems.filter((item) => item.visible !== false)); | ||
| 84 | + | ||
| 85 | + const getTabItem = (key) => { | ||
| 86 | + const normalizedKey = normalizeTabbarKey(key); | ||
| 87 | + return state.tabItems.find((item) => item.key === normalizedKey) || getDefaultTabbarItem(normalizedKey); | ||
| 88 | + }; | ||
| 89 | + | ||
| 90 | + return { | ||
| 91 | + state: readonly(state), | ||
| 92 | + visibleTabItems, | ||
| 93 | + getTabItem, | ||
| 94 | + ensureLoaded: ensureTabbarLoaded, | ||
| 95 | + fetchTabbarConfig, | ||
| 96 | + resolveTargetUrl: resolveTabbarTargetUrl, | ||
| 97 | + }; | ||
| 98 | +}; |
| ... | @@ -125,7 +125,11 @@ | ... | @@ -125,7 +125,11 @@ |
| 125 | 125 | ||
| 126 | <!-- 底部导航组件 --> | 126 | <!-- 底部导航组件 --> |
| 127 | <BottomNav v-if="shouldShowLegacyBottomNav" /> | 127 | <BottomNav v-if="shouldShowLegacyBottomNav" /> |
| 128 | - <JlsBottomNav v-else-if="shouldShowJlsBottomNav" /> | 128 | + <JlsBottomNav |
| 129 | + v-else-if="shouldShowJlsBottomNav" | ||
| 130 | + :visible="shouldShowJlsBottomNav" | ||
| 131 | + :load-options="jlsBottomNavLoadOptions" | ||
| 132 | + /> | ||
| 129 | </div> | 133 | </div> |
| 130 | </template> | 134 | </template> |
| 131 | 135 | ||
| ... | @@ -142,13 +146,14 @@ import wx from 'weixin-js-sdk' | ... | @@ -142,13 +146,14 @@ import wx from 'weixin-js-sdk' |
| 142 | import pageInfo from '@/views/checkin/info.vue' | 146 | import pageInfo from '@/views/checkin/info.vue' |
| 143 | import audioBackground1 from '@/components/audioBackground1.vue' | 147 | import audioBackground1 from '@/components/audioBackground1.vue' |
| 144 | import BottomNav from '@/components/BottomNav.vue' | 148 | import BottomNav from '@/components/BottomNav.vue' |
| 145 | -import JlsBottomNav from '@/components/JlsBottomNav.vue' | 149 | +import JlsBottomNav from '@/components/JlsBottomNav' |
| 146 | import { mapState, mapActions } from 'pinia' | 150 | import { mapState, mapActions } from 'pinia' |
| 147 | import { mainStore } from '@/store' | 151 | import { mainStore } from '@/store' |
| 148 | import { parseQueryString, getAdaptiveFontSize, getAdaptivePadding } from '@/utils/tools' | 152 | import { parseQueryString, getAdaptiveFontSize, getAdaptivePadding } from '@/utils/tools' |
| 149 | import AMapLoader from '@amap/amap-jsapi-loader' | 153 | import AMapLoader from '@amap/amap-jsapi-loader' |
| 150 | import { mapAudioAPI } from '@/api/map.js' | 154 | import { mapAudioAPI } from '@/api/map.js' |
| 151 | import { CHECKIN_NAV_MODE, resolveCheckinNavMode, getCheckinMapHeight } from '@/views/checkin/nav-mode.js' | 155 | import { CHECKIN_NAV_MODE, resolveCheckinNavMode, getCheckinMapHeight } from '@/views/checkin/nav-mode.js' |
| 156 | +import { getJlsTabbarPreviewOptions } from '@/components/JlsBottomNav/preview' | ||
| 152 | 157 | ||
| 153 | // 地图缓存管理器 | 158 | // 地图缓存管理器 |
| 154 | class MapCacheManager { | 159 | class MapCacheManager { |
| ... | @@ -345,6 +350,12 @@ export default { | ... | @@ -345,6 +350,12 @@ export default { |
| 345 | isMiniProgramWebView() { | 350 | isMiniProgramWebView() { |
| 346 | return navigator.userAgent.includes('miniProgram'); | 351 | return navigator.userAgent.includes('miniProgram'); |
| 347 | }, | 352 | }, |
| 353 | + isJlsBottomNavPreview() { | ||
| 354 | + return getJlsTabbarPreviewOptions(this.$route?.query || {}).previewEnabled; | ||
| 355 | + }, | ||
| 356 | + jlsBottomNavLoadOptions() { | ||
| 357 | + return getJlsTabbarPreviewOptions(this.$route?.query || {}).loadOptions; | ||
| 358 | + }, | ||
| 348 | currentCheckinNavMode() { | 359 | currentCheckinNavMode() { |
| 349 | return resolveCheckinNavMode(this.$route?.query || {}); | 360 | return resolveCheckinNavMode(this.$route?.query || {}); |
| 350 | }, | 361 | }, |
| ... | @@ -352,7 +363,8 @@ export default { | ... | @@ -352,7 +363,8 @@ export default { |
| 352 | return this.isMiniProgramWebView && this.currentCheckinNavMode === CHECKIN_NAV_MODE.LEGACY; | 363 | return this.isMiniProgramWebView && this.currentCheckinNavMode === CHECKIN_NAV_MODE.LEGACY; |
| 353 | }, | 364 | }, |
| 354 | shouldShowJlsBottomNav() { | 365 | shouldShowJlsBottomNav() { |
| 355 | - return this.isMiniProgramWebView && this.currentCheckinNavMode === CHECKIN_NAV_MODE.JLS; | 366 | + return (this.isMiniProgramWebView || this.isJlsBottomNavPreview) |
| 367 | + && this.currentCheckinNavMode === CHECKIN_NAV_MODE.JLS; | ||
| 356 | }, | 368 | }, |
| 357 | /** | 369 | /** |
| 358 | * 动态计算地图高度 | 370 | * 动态计算地图高度 |
| ... | @@ -360,7 +372,7 @@ export default { | ... | @@ -360,7 +372,7 @@ export default { |
| 360 | */ | 372 | */ |
| 361 | mapHeight() { | 373 | mapHeight() { |
| 362 | return getCheckinMapHeight({ | 374 | return getCheckinMapHeight({ |
| 363 | - isMiniProgramWebView: this.isMiniProgramWebView, | 375 | + isMiniProgramWebView: this.isMiniProgramWebView || this.isJlsBottomNavPreview, |
| 364 | navMode: this.currentCheckinNavMode, | 376 | navMode: this.currentCheckinNavMode, |
| 365 | }); | 377 | }); |
| 366 | }, | 378 | }, | ... | ... |
| 1 | +import { JLS_CHECKIN_ACTIVE_TAB, resolveJlsCheckinActiveTab } from '@/components/JlsBottomNav/nav-state.js'; | ||
| 2 | + | ||
| 1 | export const CHECKIN_NAV_MODE = { | 3 | export const CHECKIN_NAV_MODE = { |
| 2 | LEGACY: 'legacy', | 4 | LEGACY: 'legacy', |
| 3 | JLS: 'jls', | 5 | JLS: 'jls', |
| 4 | NONE: 'none', | 6 | NONE: 'none', |
| 5 | }; | 7 | }; |
| 6 | 8 | ||
| 7 | -export const CHECKIN_ACTIVE_TAB = { | 9 | +export const CHECKIN_ACTIVE_TAB = JLS_CHECKIN_ACTIVE_TAB; |
| 8 | - HOME: 'home', | ||
| 9 | - MESSAGE: 'message', | ||
| 10 | - MINE: 'mine', | ||
| 11 | -}; | ||
| 12 | 10 | ||
| 13 | const NAV_HEIGHT = '80px'; | 11 | const NAV_HEIGHT = '80px'; |
| 14 | 12 | ||
| ... | @@ -40,20 +38,5 @@ export function getCheckinMapHeight({ | ... | @@ -40,20 +38,5 @@ export function getCheckinMapHeight({ |
| 40 | } | 38 | } |
| 41 | 39 | ||
| 42 | export function resolveCheckinActiveTab(query = {}) { | 40 | export function resolveCheckinActiveTab(query = {}) { |
| 43 | - const rawTab = Array.isArray(query.activeTab) ? query.activeTab[0] : query.activeTab; | 41 | + return resolveJlsCheckinActiveTab(query); |
| 44 | - const normalizedTab = String(rawTab || '').trim().toLowerCase(); | ||
| 45 | - | ||
| 46 | - if (normalizedTab === CHECKIN_ACTIVE_TAB.HOME) { | ||
| 47 | - return CHECKIN_ACTIVE_TAB.HOME; | ||
| 48 | - } | ||
| 49 | - | ||
| 50 | - if (normalizedTab === CHECKIN_ACTIVE_TAB.MESSAGE) { | ||
| 51 | - return CHECKIN_ACTIVE_TAB.MESSAGE; | ||
| 52 | - } | ||
| 53 | - | ||
| 54 | - if (normalizedTab === CHECKIN_ACTIVE_TAB.MINE) { | ||
| 55 | - return CHECKIN_ACTIVE_TAB.MINE; | ||
| 56 | - } | ||
| 57 | - | ||
| 58 | - return ''; | ||
| 59 | } | 42 | } | ... | ... |
-
Please register or login to post a comment