chore: initial import
This commit is contained in:
828
pages/index/index.vue
Normal file
828
pages/index/index.vue
Normal file
@@ -0,0 +1,828 @@
|
||||
<template>
|
||||
<view class="index" style="background-color: #F6F6F6;">
|
||||
|
||||
<!-- 启动全屏加载遮罩 -->
|
||||
<view v-if="isAppInitLoading" class="init-loading-mask tn-flex tn-flex-direction-column tn-flex-col-center tn-flex-row-center">
|
||||
<view class="loading-logo">
|
||||
<text class="tn-icon-moon tn-color-white" style="font-size: 80rpx; transform: rotate(-45deg); display: block;"></text>
|
||||
</view>
|
||||
<view class="loading-desc tn-margin-top-lg" style="color: rgba(255,255,255,0.8); letter-spacing: 4rpx; font-size: 32rpx; font-weight: bold;">{{ t('loadingBrand') }}</view>
|
||||
<view class="loading-spinner tn-margin-top-xl">
|
||||
<tn-loading mode="circle" color="#00BBE8" size="60"></tn-loading>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 二级页面 -->
|
||||
<view v-if="hasPage('select-room')" v-show="currentPageId === 'select-room'">
|
||||
<scroll-view class="custom-tabbar-page" scroll-y enable-back-to-top @scrolltolower="tabbarPageScrollLower">
|
||||
<select-room-tab ref="selectRoomTab"></select-room-tab>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view v-if="hasPage('stay-service')" v-show="currentPageId === 'stay-service'">
|
||||
<scroll-view class="custom-tabbar-page" scroll-y enable-back-to-top @scrolltolower="tabbarPageScrollLower">
|
||||
<stay-service-tab
|
||||
ref="stayServiceTab"
|
||||
@refresh-shell-state="handleStayServiceRefreshState"
|
||||
></stay-service-tab>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view v-if="hasPage('my-application')" v-show="currentPageId === 'my-application'">
|
||||
<scroll-view class="custom-tabbar-page" scroll-y enable-back-to-top @scrolltolower="tabbarPageScrollLower">
|
||||
<my-application-tab
|
||||
ref="myApplicationTab"
|
||||
@refresh-shell-state="handleMyApplicationRefreshState"
|
||||
></my-application-tab>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view v-if="hasPage('message-center')" v-show="currentPageId === 'message-center'">
|
||||
<scroll-view class="custom-tabbar-page" scroll-y enable-back-to-top @scrolltolower="tabbarPageScrollLower">
|
||||
<message-center-tab ref="messageCenterTab"></message-center-tab>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view v-if="hasPage('profile-center')" v-show="currentPageId === 'profile-center'">
|
||||
<scroll-view class="custom-tabbar-page" scroll-y enable-back-to-top @scrolltolower="tabbarPageScrollLower">
|
||||
<profile-center-tab ref="profileCenterTab"></profile-center-tab>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<contract-alert
|
||||
v-if="sessionState.isLogin && sessionState.pendingContracts.length"
|
||||
:pending-contracts="sessionState.pendingContracts"
|
||||
/>
|
||||
<!-- <view class="bg-tabbar-shadow"></view> -->
|
||||
<!-- 底部导航栏 -->
|
||||
<view class="tabbar">
|
||||
<view class="tabbar__bg" :style="[wrapStyle]"></view>
|
||||
<view class="tabbar__list">
|
||||
<block v-for="(item, index) in currentTabbarList" :key="item.id">
|
||||
<view :id="`tabbar_item_${index}`" class="tabbar__item"
|
||||
:class="[{'tabbar__item--active': index === currentTabbarIndex}]" @click="changeTabbar(index, item.id)">
|
||||
<!-- 可以用字体图标的方式 -->
|
||||
<view class="tabbar__item__icon-wrap">
|
||||
<view class="tabbar__item__icon" :class="[item.icon]"></view>
|
||||
<view
|
||||
v-if="item.id === 'message-center' && messageBadgeCount > 0"
|
||||
class="tabbar__item__badge"
|
||||
>
|
||||
{{ getMessageBadgeText() }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- <image class="tabbar__item__image" :src="item.image"></image> -->
|
||||
<!-- 底部文字,需要自行显示出来-->
|
||||
<view class="tabbar__item__text">{{ item.name }}</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
<!-- <view class="tabbar__select-active-bg" :class="[showActiceBg ? 'tabbar__select-active-bg--show' : 'tabbar__select-active-bg--hide']" :style="[activeBgStyle]"></view> -->
|
||||
<view class="tabbar__select-active-bg" :animation="activeBgAnimation"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SelectRoomTab from './component/SelectRoomTab.vue'
|
||||
import StayServiceTab from './component/StayServiceTab.vue'
|
||||
import MessageCenterTab from './component/MessageCenterTab.vue'
|
||||
import ProfileCenterTab from './component/ProfileCenterTab.vue'
|
||||
import MyApplicationTab from './component/MyApplicationTab.vue'
|
||||
import ContractAlert from '../../components/contract-alert/contract-alert.vue'
|
||||
import { clearLegacyDemoCache, getHomePageTabId, getSessionSnapshot, saveHomePageTabId } from '../../utils/app-state'
|
||||
import { checkRecordByChecked, notificationPage, applicationFormMePage } from '@/api/index.js'
|
||||
import { countPendingInspectionRecords, extractInspectionList, resolveNotificationUnreadTotal } from '../../utils/message-helper'
|
||||
import { syncLoginStayState } from '../../utils/session-sync'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SelectRoomTab,
|
||||
StayServiceTab,
|
||||
MessageCenterTab,
|
||||
ProfileCenterTab,
|
||||
MyApplicationTab,
|
||||
ContractAlert
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
wrapMaskPositionLeft: -15,
|
||||
activeBgPositionLeft: 0,
|
||||
currentPageId: 'select-room',
|
||||
tabbarRectInfo: [],
|
||||
activeBgAnimation: {},
|
||||
messageBadgeCount: 0,
|
||||
sessionState: {
|
||||
isLogin: false,
|
||||
hasCheckedIn: false,
|
||||
hasStayOrder: false,
|
||||
hasPendingApplication: false,
|
||||
pendingContracts: []
|
||||
},
|
||||
tabDefinitions: [
|
||||
{ id: 'select-room', labelKey: 'tabSelectRoom', icon: 'tn-icon-home-fill', image: '/static/tabbar/tn-tabbar0.png', component: 'SelectRoomTab', ref: 'selectRoomTab', refreshMethod: 'checkLoginStatus' },
|
||||
{ id: 'stay-service', labelKey: 'tabStayService', icon: 'tn-icon-safe', image: '/static/tabbar/tn-tabbar1.png', component: 'StayServiceTab', ref: 'stayServiceTab', refreshMethod: 'checkLocalStatus' },
|
||||
{ id: 'my-application', labelKey: 'tabMyApplication', icon: 'tn-icon-order', image: '/static/tabbar/tn-tabbar2.png', component: 'MyApplicationTab', ref: 'myApplicationTab', refreshMethod: 'load' },
|
||||
{ id: 'message-center', labelKey: 'tabMessageCenter', icon: 'tn-icon-message', image: '/static/tabbar/tn-tabbar3.png', component: 'MessageCenterTab', ref: 'messageCenterTab', refreshMethod: 'load' },
|
||||
{ id: 'profile-center', labelKey: 'tabProfileCenter', icon: 'tn-icon-my-fill', image: '/static/tabbar/tn-tabbar3.png', component: 'ProfileCenterTab', ref: 'profileCenterTab', refreshMethod: 'checkLoginStatus' }
|
||||
],
|
||||
loadedPages: ['select-room'],
|
||||
isAppInitLoading: true,
|
||||
isTabSwitching: false,
|
||||
i18n: {
|
||||
zh: {
|
||||
loadingBrand: '绿居公寓',
|
||||
shareTitle: '绿居公寓 - 国际化青年社区',
|
||||
tabSelectRoom: '选 房',
|
||||
tabStayService: '入 住',
|
||||
tabMyApplication: '申请单',
|
||||
tabMessageCenter: '消 息',
|
||||
tabProfileCenter: '我 的'
|
||||
},
|
||||
en: {
|
||||
loadingBrand: 'Luju Apartments',
|
||||
shareTitle: 'Luju Apartments - International Youth Community',
|
||||
tabSelectRoom: 'Explore',
|
||||
tabStayService: 'Stay',
|
||||
tabMyApplication: 'Applications',
|
||||
tabMessageCenter: 'Messages',
|
||||
tabProfileCenter: 'Profile'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
wrapStyle() {
|
||||
return {
|
||||
'-webkit-mask-position': `${this.wrapMaskPositionLeft}px -1px, 100%`
|
||||
}
|
||||
},
|
||||
visibleTabIds() {
|
||||
if (!this.sessionState.isLogin) {
|
||||
return ['select-room', 'profile-center']
|
||||
}
|
||||
const tabs = []
|
||||
if (this.sessionState.hasStayOrder) {
|
||||
tabs.push('stay-service')
|
||||
} else if (this.sessionState.hasPendingApplication) {
|
||||
tabs.push('my-application')
|
||||
} else {
|
||||
tabs.push('select-room')
|
||||
}
|
||||
if (this.sessionState.hasPendingApplication && !tabs.includes('my-application')) {
|
||||
tabs.push('my-application')
|
||||
}
|
||||
tabs.push('message-center', 'profile-center')
|
||||
return tabs
|
||||
},
|
||||
currentTabbarList() {
|
||||
return this.tabDefinitions
|
||||
.filter(item => this.visibleTabIds.includes(item.id))
|
||||
.map(item => ({
|
||||
...item,
|
||||
name: this.t(item.labelKey)
|
||||
}))
|
||||
},
|
||||
currentTabbarIndex() {
|
||||
const index = this.currentTabbarList.findIndex(item => item.id === this.currentPageId)
|
||||
return index === -1 ? 0 : index
|
||||
},
|
||||
},
|
||||
onShow() {
|
||||
this.handlePageShow()
|
||||
},
|
||||
onLoad(options) {
|
||||
clearLegacyDemoCache()
|
||||
uni.$on('messageBadgeCountChange', this.handleMessageBadgeCountChange)
|
||||
this.handlePageLoad(options)
|
||||
},
|
||||
onUnload() {
|
||||
uni.$off('messageBadgeCountChange', this.handleMessageBadgeCountChange)
|
||||
},
|
||||
onReady() {
|
||||
this.$nextTick(() => {
|
||||
this.getTabbarItemInfo()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
t(key) {
|
||||
return this.$tm('pages.index.index', key)
|
||||
},
|
||||
getStoreUser() {
|
||||
return (this.$store && this.$store.state && this.$store.state.vuex_user) || {}
|
||||
},
|
||||
isCurrentLogin() {
|
||||
const storeUser = this.getStoreUser()
|
||||
if (storeUser && Object.keys(storeUser).length) {
|
||||
return true
|
||||
}
|
||||
return !!getSessionSnapshot().isLogin
|
||||
},
|
||||
getStoreCheckinStatus() {
|
||||
return !!(this.$store && this.$store.state && this.$store.state.vuex_checkin_status)
|
||||
},
|
||||
getStoreCurrentOrder() {
|
||||
return (this.$store && this.$store.state && this.$store.state.vuex_current_order) || null
|
||||
},
|
||||
getStoreCurrentBooking() {
|
||||
const user = this.getStoreUser()
|
||||
return (user && user.currentBooking) || null
|
||||
},
|
||||
getStorePendingContracts() {
|
||||
const list = this.$store && this.$store.state && this.$store.state.vuex_pending_contracts
|
||||
return Array.isArray(list) ? list : []
|
||||
},
|
||||
syncStayState(force = false) {
|
||||
if (!this.isCurrentLogin()) {
|
||||
this.refreshSessionState()
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
return syncLoginStayState({ force }).catch((error) => {
|
||||
console.error('sync login stay state failed', error)
|
||||
})
|
||||
},
|
||||
refreshShellState(force = false, options = {}) {
|
||||
const { skipMessageBadge = false } = options
|
||||
const tasks = [
|
||||
this.$store && this.$store.dispatch ? this.$store.dispatch('fetchGlobalEnums').catch(() => ({})) : Promise.resolve(),
|
||||
this.syncStayState(force)
|
||||
]
|
||||
if (!skipMessageBadge) {
|
||||
tasks.push(this.refreshMessageBadgeCount())
|
||||
}
|
||||
if (this.isCurrentLogin()) {
|
||||
tasks.push(
|
||||
applicationFormMePage({ Status: 0, PageIndex: 1, PageSize: 1 })
|
||||
.then(res => {
|
||||
const payload = res && res.data ? res.data : res
|
||||
const total = payload && payload.total ? Number(payload.total) : (payload && payload.data && payload.data.length ? payload.data.length : 0)
|
||||
this.sessionState.hasPendingApplication = total > 0
|
||||
})
|
||||
.catch(() => {
|
||||
this.sessionState.hasPendingApplication = false
|
||||
})
|
||||
)
|
||||
} else {
|
||||
this.sessionState.hasPendingApplication = false
|
||||
}
|
||||
return Promise.all(tasks).finally(() => {
|
||||
this.refreshSessionState()
|
||||
})
|
||||
},
|
||||
async handlePageLoad(options = {}) {
|
||||
await this.refreshShellState(true, {
|
||||
skipMessageBadge: options.pageId === 'message-center'
|
||||
})
|
||||
this.syncCurrentTab({
|
||||
pageId: options.pageId,
|
||||
index: Number(options.index || 0)
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
this.isAppInitLoading = false
|
||||
}, 1500)
|
||||
},
|
||||
async handlePageShow() {
|
||||
await this.refreshShellState(true, {
|
||||
skipMessageBadge: this.currentPageId === 'message-center'
|
||||
})
|
||||
this.syncCurrentTab()
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.loadedPages.forEach(pageId => {
|
||||
this.refreshTabPageState(pageId)
|
||||
})
|
||||
this.getTabbarItemInfo()
|
||||
})
|
||||
},
|
||||
hasPage(id) {
|
||||
return this.loadedPages.includes(id)
|
||||
},
|
||||
ensurePageLoaded(id) {
|
||||
if (!id || this.loadedPages.includes(id)) return
|
||||
this.loadedPages = [...this.loadedPages, id]
|
||||
},
|
||||
refreshSessionState() {
|
||||
const session = getSessionSnapshot()
|
||||
const storePendingContracts = this.getStorePendingContracts()
|
||||
const storeUser = this.getStoreUser()
|
||||
const storeHasCheckedIn = this.getStoreCheckinStatus()
|
||||
const storeCurrentBooking = this.getStoreCurrentBooking()
|
||||
const pendingContracts = storePendingContracts.length
|
||||
? storePendingContracts
|
||||
: (storeUser.pendingContracts || (Array.isArray(session.pendingContracts) ? session.pendingContracts : []))
|
||||
const hasStayOrder = !!(storeHasCheckedIn || storeCurrentBooking || session.currentBooking)
|
||||
this.sessionState = {
|
||||
isLogin: this.isCurrentLogin(),
|
||||
hasCheckedIn: storeHasCheckedIn || session.hasCheckedIn,
|
||||
hasStayOrder: hasStayOrder || pendingContracts.length > 0,
|
||||
hasPendingApplication: !!this.sessionState.hasPendingApplication,
|
||||
pendingContracts
|
||||
}
|
||||
},
|
||||
getCurrentUserCode() {
|
||||
const storeUser = this.getStoreUser()
|
||||
if (Object.keys(storeUser).length) {
|
||||
return storeUser.code || storeUser.userCode || ''
|
||||
}
|
||||
const session = getSessionSnapshot()
|
||||
const user = session.currentUser || session.registeredUser || session.resolvedUser || {}
|
||||
return user.code || user.userCode || ''
|
||||
},
|
||||
getCurrentOrderNo() {
|
||||
const storeCurrentOrder = this.getStoreCurrentOrder()
|
||||
if (storeCurrentOrder && storeCurrentOrder.orderNo) {
|
||||
return String(storeCurrentOrder.orderNo).trim()
|
||||
}
|
||||
const session = getSessionSnapshot()
|
||||
const room = session.myRoomData || {}
|
||||
const user = session.currentUser || session.registeredUser || session.resolvedUser || {}
|
||||
const currentOrder = (user && user.currentOrder) || {}
|
||||
return String(room.orderNo || currentOrder.orderNo || '').trim()
|
||||
},
|
||||
isPendingInspectionRecord(record) {
|
||||
return countPendingInspectionRecords([record]) > 0
|
||||
},
|
||||
refreshMessageBadgeCount() {
|
||||
if (!this.isCurrentLogin()) {
|
||||
this.messageBadgeCount = 0
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
const tasks = [
|
||||
notificationPage({
|
||||
IsRead: false,
|
||||
PageIndex: 1,
|
||||
PageSize: 1,
|
||||
Search: '',
|
||||
IsExport: false
|
||||
})
|
||||
]
|
||||
const userCode = this.getCurrentUserCode()
|
||||
const orderNo = this.getCurrentOrderNo()
|
||||
if (orderNo) {
|
||||
tasks.push(checkRecordByChecked({
|
||||
checkedUserCode: userCode,
|
||||
orderNo
|
||||
}))
|
||||
}
|
||||
|
||||
return Promise.all(tasks)
|
||||
.then(([notificationResponse, inspectionResponse]) => {
|
||||
const remoteUnread = resolveNotificationUnreadTotal(notificationResponse)
|
||||
const inspectionList = extractInspectionList(inspectionResponse)
|
||||
const pendingInspectionCount = countPendingInspectionRecords(inspectionList)
|
||||
this.messageBadgeCount = remoteUnread + pendingInspectionCount
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('refresh message badge count failed', error)
|
||||
this.messageBadgeCount = 0
|
||||
})
|
||||
},
|
||||
handleMessageBadgeCountChange(count) {
|
||||
this.messageBadgeCount = Math.max(Number(count) || 0, 0)
|
||||
},
|
||||
getMessageBadgeText() {
|
||||
if (!this.messageBadgeCount) return ''
|
||||
return this.messageBadgeCount > 99 ? '99+' : String(this.messageBadgeCount)
|
||||
},
|
||||
resolveTargetTab({ pageId, index = 0 } = {}) {
|
||||
const list = this.currentTabbarList
|
||||
if (!list.length) return null
|
||||
if (pageId) {
|
||||
const explicitMatchedTab = list.find(item => item.id === pageId)
|
||||
if (explicitMatchedTab) return explicitMatchedTab
|
||||
}
|
||||
const preferredPageId = getHomePageTabId()
|
||||
const shouldPreferStayService = this.sessionState.isLogin && this.sessionState.hasStayOrder
|
||||
if (shouldPreferStayService && (!preferredPageId || preferredPageId === 'select-room')) {
|
||||
const stayServiceTab = list.find(item => item.id === 'stay-service')
|
||||
if (stayServiceTab) return stayServiceTab
|
||||
}
|
||||
if (preferredPageId) {
|
||||
const matchedTab = list.find(item => item.id === preferredPageId)
|
||||
if (matchedTab) return matchedTab
|
||||
}
|
||||
return list[index] || list[0]
|
||||
},
|
||||
syncCurrentTab({ pageId, index = 0 } = {}) {
|
||||
const targetTab = this.resolveTargetTab({ pageId, index })
|
||||
if (!targetTab) return
|
||||
this.ensurePageLoaded(targetTab.id)
|
||||
this.currentPageId = targetTab.id
|
||||
saveHomePageTabId(targetTab.id)
|
||||
},
|
||||
getPageRef(refName) {
|
||||
const ref = this.$refs[refName]
|
||||
return Array.isArray(ref) ? ref[0] : ref
|
||||
},
|
||||
refreshTabPageState(pageId, retryCount = 0) {
|
||||
const page = this.tabDefinitions.find(item => item.id === pageId)
|
||||
if (!page || !page.refreshMethod) return
|
||||
const pageVm = this.getPageRef(page.ref)
|
||||
if (pageVm && typeof pageVm[page.refreshMethod] === 'function') {
|
||||
pageVm[page.refreshMethod]()
|
||||
return
|
||||
}
|
||||
if (retryCount < 8) {
|
||||
setTimeout(() => {
|
||||
this.refreshTabPageState(pageId, retryCount + 1)
|
||||
}, 60)
|
||||
}
|
||||
},
|
||||
handleStayServiceRefreshState() {
|
||||
this.refreshSessionState()
|
||||
},
|
||||
async handleMyApplicationRefreshState() {
|
||||
await this.refreshShellState(true)
|
||||
this.syncCurrentTab()
|
||||
this.$nextTick(() => {
|
||||
this.refreshTabPageState(this.currentPageId)
|
||||
this.getTabbarItemInfo()
|
||||
})
|
||||
},
|
||||
tabbarPageScrollLower(e) {
|
||||
if (this.currentPageId === 'select-room') {
|
||||
const selectRoomTab = this.getPageRef('selectRoomTab')
|
||||
selectRoomTab && selectRoomTab.getRandomData && selectRoomTab.getRandomData()
|
||||
}
|
||||
},
|
||||
|
||||
// 获取底部元素的位置
|
||||
getTabbarItemInfo() {
|
||||
const view = uni.createSelectorQuery().in(this)
|
||||
for (let i = 0; i < this.currentTabbarList.length; i++) {
|
||||
view.select('#tabbar_item_' + i).boundingClientRect()
|
||||
}
|
||||
view.exec(res => {
|
||||
if (!res || !res.length) {
|
||||
if (!this._tabbarRetryCount) this._tabbarRetryCount = 0;
|
||||
if (this._tabbarRetryCount < 20) {
|
||||
this._tabbarRetryCount++;
|
||||
setTimeout(() => {
|
||||
this.getTabbarItemInfo()
|
||||
}, 50)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 将信息存入数组中
|
||||
this.tabbarRectInfo = [];
|
||||
res.map((item) => {
|
||||
if (item) {
|
||||
this.tabbarRectInfo.push({
|
||||
left: item.left,
|
||||
width: item.width
|
||||
})
|
||||
}
|
||||
})
|
||||
if (this.tabbarRectInfo[this.currentTabbarIndex]) {
|
||||
this.updateHollowsPosition()
|
||||
this.updateActiveBgPosition(true)
|
||||
}
|
||||
})
|
||||
},
|
||||
// 更新凹陷位置
|
||||
updateHollowsPosition() {
|
||||
if (!this.tabbarRectInfo[this.currentTabbarIndex]) return;
|
||||
const {
|
||||
width,
|
||||
left
|
||||
} = this.tabbarRectInfo[this.currentTabbarIndex]
|
||||
// 计算掩模图片的宽高比
|
||||
// const imageRatio = 200 / 92
|
||||
// 计算定高的宽比
|
||||
const imageFixedHeightWidthRatioValue = 300 * (uni.upx2px(20) / 92)
|
||||
this.wrapMaskPositionLeft = left - ((imageFixedHeightWidthRatioValue - width) / 2)
|
||||
},
|
||||
// 更新激活时背景的位置
|
||||
updateActiveBgPosition(init = false) {
|
||||
if (!this.tabbarRectInfo[this.currentTabbarIndex]) return;
|
||||
const {
|
||||
width,
|
||||
left
|
||||
} = this.tabbarRectInfo[this.currentTabbarIndex]
|
||||
const oldActiveBgPositionLeft = this.activeBgPositionLeft
|
||||
this.activeBgPositionLeft = left + ((width - uni.upx2px(16)) / 2)
|
||||
if (!init) {
|
||||
const animation = uni.createAnimation({
|
||||
duration: 160,
|
||||
timingFunction: "linear"
|
||||
})
|
||||
animation.top(uni.upx2px(-60)).left(oldActiveBgPositionLeft + ((this.activeBgPositionLeft -
|
||||
oldActiveBgPositionLeft) / 2)).scale(1).step()
|
||||
animation.left(this.activeBgPositionLeft).top(uni.upx2px(-10)).scale(1).step()
|
||||
this.activeBgAnimation = animation.export()
|
||||
} else {
|
||||
const animation = uni.createAnimation({
|
||||
duration: 160,
|
||||
timingFunction: "linear"
|
||||
})
|
||||
animation.left(this.activeBgPositionLeft).top(uni.upx2px(-10)).step()
|
||||
this.activeBgAnimation = animation.export()
|
||||
}
|
||||
|
||||
},
|
||||
async changeTabbar(index, id) {
|
||||
if (this.currentTabbarIndex === index && this.currentPageId === id) return;
|
||||
if (this.isTabSwitching) return
|
||||
|
||||
// 兼容处理:防止 H5 等非小程序环境下找不到 wx 对象导致 JS 报错,从而中断后续页面切换逻辑
|
||||
if (typeof uni !== 'undefined' && uni.vibrateShort) {
|
||||
uni.vibrateShort({
|
||||
fail: () => {} // 忽略 H5 环境下的不支持报错
|
||||
});
|
||||
}
|
||||
|
||||
this.isTabSwitching = true
|
||||
|
||||
try {
|
||||
await this.refreshShellState(true, {
|
||||
skipMessageBadge: id === 'message-center'
|
||||
})
|
||||
|
||||
const targetTab = this.resolveTargetTab({
|
||||
pageId: id,
|
||||
index
|
||||
})
|
||||
const nextPageId = targetTab ? targetTab.id : id
|
||||
|
||||
this.ensurePageLoaded(nextPageId)
|
||||
this.currentPageId = nextPageId
|
||||
saveHomePageTabId(nextPageId)
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.refreshTabPageState(nextPageId)
|
||||
this.updateHollowsPosition()
|
||||
this.updateActiveBgPosition()
|
||||
})
|
||||
} finally {
|
||||
this.isTabSwitching = false
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
},
|
||||
// 分享到朋友
|
||||
onShareAppMessage() {
|
||||
return {
|
||||
title: this.t('shareTitle'),
|
||||
path: '/pages/index/index'
|
||||
}
|
||||
},
|
||||
// 分享到朋友圈
|
||||
onShareTimeline() {
|
||||
return {
|
||||
title: this.t('shareTitle')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 页面初始化全屏遮罩 */
|
||||
.init-loading-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #111111;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
.loading-logo {
|
||||
animation: pulse 2s infinite ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { transform: scale(1); opacity: 0.8; }
|
||||
50% { transform: scale(1.1); opacity: 1; }
|
||||
100% { transform: scale(1); opacity: 0.8; }
|
||||
}
|
||||
|
||||
/*整体样式 */
|
||||
.index {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
|
||||
.custom-tabbar-page {
|
||||
width: 100%;
|
||||
height: calc(100vh - 114rpx - constant(safe-area-inset-bottom));
|
||||
height: calc(100vh - 114rpx - env(safe-area-inset-bottom));
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
/* 底部导航 statr */
|
||||
.tabbar {
|
||||
width: 100%;
|
||||
height: 114rpx;
|
||||
height: calc(114rpx + constant(safe-area-inset-bottom));
|
||||
height: calc(114rpx + env(safe-area-inset-bottom));
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: transparent;
|
||||
z-index: 998;
|
||||
/* 图鸟温馨提醒,裁剪式也生效的阴影样式,不用box-shadow*/
|
||||
filter: drop-shadow(0rpx 0rpx 30rpx rgba(0, 0, 0, 0.08));
|
||||
|
||||
&__bg {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: #FFFFFF;
|
||||
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 61.5'%3E%3Cpath d='M100 0H0c32.9 0 49.3 61.5 100 61.5S167.1 0 200 0H100z'/%3E%3C/svg%3E"), linear-gradient(#000, #000);
|
||||
-webkit-mask-size: auto 20rpx, cover;
|
||||
-webkit-mask-position: center top;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
-webkit-mask-composite: xor;
|
||||
/*只显示不重合的地方, chorem 、safari 支持*/
|
||||
z-index: -1;
|
||||
transition: 0.5s;
|
||||
}
|
||||
|
||||
&__list {
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&__item {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
margin-top: 10rpx;
|
||||
|
||||
&--active {
|
||||
.tabbar__item__icon {
|
||||
// top: 10rpx;
|
||||
color: $icon-tone-brand;
|
||||
}
|
||||
|
||||
.tabbar__item__image {
|
||||
top: -42rpx;
|
||||
}
|
||||
|
||||
.tabbar__item__text {
|
||||
opacity: 1;
|
||||
color: $icon-tone-strong;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
font-size: 48rpx;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 22rpx;
|
||||
transition: 0.5s;
|
||||
color: $icon-tone-muted;
|
||||
}
|
||||
|
||||
&__icon-wrap {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
height: 88rpx;
|
||||
}
|
||||
|
||||
&__badge {
|
||||
position: absolute;
|
||||
top: 10rpx;
|
||||
right: 50%;
|
||||
margin-right: -42rpx;
|
||||
min-width: 30rpx;
|
||||
height: 30rpx;
|
||||
padding: 0 8rpx;
|
||||
border-radius: 999rpx;
|
||||
background: #ff5b5b;
|
||||
color: #ffffff;
|
||||
font-size: 18rpx;
|
||||
line-height: 30rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 8rpx 18rpx rgba(255, 91, 91, 0.25);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&__image {
|
||||
width: 58rpx;
|
||||
height: 58rpx;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 20rpx;
|
||||
transition: 0.5s;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
&__text {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 20rpx;
|
||||
bottom: calc(20rpx + constant(safe-area-inset-bottom)*0.8);
|
||||
bottom: calc(20rpx + env(safe-area-inset-bottom)*0.8);
|
||||
transition: 0.5s;
|
||||
font-size: 20rpx;
|
||||
color: $icon-tone-muted;
|
||||
}
|
||||
}
|
||||
|
||||
&__select-active-bg {
|
||||
position: absolute;
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 50%;
|
||||
background-color: $brand-primary;
|
||||
border: 2rpx solid #FFFFFF;
|
||||
// transition: 0.5s;
|
||||
z-index: -1;
|
||||
left: calc(64rpx);
|
||||
box-shadow: 0rpx 10rpx 30rpx 0rpx rgba($brand-primary, 0.28);
|
||||
// box-shadow: inset 0rpx 0rpx 50rpx 0rpx rgba(0, 0, 0, 0.05);
|
||||
// box-shadow: 0rpx 10rpx 30rpx rgba(70,23,129, 0.07),
|
||||
// 0rpx -8rpx 40rpx rgba(255, 255, 255, 0.07),
|
||||
// inset 0rpx -10rpx 10rpx rgba(70,23,129, 0.07),
|
||||
// inset 0rpx 10rpx 20rpx rgba(255, 255, 255, 1);
|
||||
transition: box-shadow .2s ease-out;
|
||||
|
||||
&--hide {
|
||||
top: calc(110rpx + 50rpx);
|
||||
}
|
||||
|
||||
&--show {
|
||||
top: -54rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 底部导航 end */
|
||||
|
||||
|
||||
/* 移动背景部分 start*/
|
||||
.bg-contaniner {
|
||||
position: fixed;
|
||||
top: -0rpx;
|
||||
left: -300rpx;
|
||||
--text-color: hsl(0 95% 60%);
|
||||
--bg-color: #F6F6F6;
|
||||
--bg-size: 200px;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
place-content: center;
|
||||
overflow: hidden;
|
||||
background-color: var(--bg-color);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.bg-contaniner::before {
|
||||
--size: 150vmax;
|
||||
content: "";
|
||||
inline-size: var(--size);
|
||||
block-size: var(--size);
|
||||
background-image: url("https://cdn.nlark.com/yuque/0/2022/png/280373/1663568736856-assets/web-upload/97cab67d-6f17-4c6e-a3f9-ac81961d571a.png");
|
||||
background-size: var(--bg-size);
|
||||
background-repeat: repeat;
|
||||
transform: rotate(45deg);
|
||||
opacity: 0.1;
|
||||
animation: bg 6s linear infinite;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.bg-contaniner::before {
|
||||
animation-duration: 0s;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bg {
|
||||
to {
|
||||
background-position: 0 calc(var(--bg-size) * -1);
|
||||
}
|
||||
}
|
||||
|
||||
/* 移动背景部分 end*/
|
||||
}
|
||||
|
||||
/* 毛玻璃*/
|
||||
.dd-glass {
|
||||
width: 100%;
|
||||
backdrop-filter: blur(20rpx);
|
||||
-webkit-backdrop-filter: blur(20rpx);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user