chore: initial import

This commit is contained in:
liangjian
2026-06-29 09:42:53 +08:00
commit 9fea127de1
241 changed files with 82767 additions and 0 deletions

828
pages/index/index.vue Normal file
View 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>