chore: initial import
This commit is contained in:
617
pages/index/component/SelectRoomTab.vue
Normal file
617
pages/index/component/SelectRoomTab.vue
Normal file
@@ -0,0 +1,617 @@
|
||||
<template>
|
||||
<view class="select-room-page tn-safe-area-inset-bottom">
|
||||
<tn-nav-bar
|
||||
v-if="currentScreen === 0"
|
||||
:isBack="false"
|
||||
:bottomShadow="false"
|
||||
:backgroundColor="navBarBg"
|
||||
:fixed="true"
|
||||
:customBackStyle="navBarTransition"
|
||||
>
|
||||
<view class="custom-nav tn-flex tn-flex-col-center">
|
||||
<view class="nav-left">
|
||||
<view class="lang-text-toggle tn-flex tn-flex-col-center" @click="toggleLang">
|
||||
<text class="lang-text" :class="{ 'lang-text--active': currentLang === 'zh' }">中</text>
|
||||
<text class="lang-split">/</text>
|
||||
<text class="lang-text" :class="{ 'lang-text--active': currentLang === 'en' }">EN</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</tn-nav-bar>
|
||||
|
||||
<swiper
|
||||
class="page-swiper"
|
||||
vertical
|
||||
:current="currentScreen"
|
||||
@change="handleScreenChange"
|
||||
:disable-touch="currentScreen === 1"
|
||||
>
|
||||
<swiper-item>
|
||||
<SelectRoomHero
|
||||
:language="currentLang"
|
||||
:banner-list="bannerList"
|
||||
:current-banner="currentBanner"
|
||||
:current-banner-index="currentBannerIndex"
|
||||
@banner-change="handleBannerChange"
|
||||
@banner-switch="switchBanner"
|
||||
@consult="handleConsult"
|
||||
@submit="handleSubmit"
|
||||
@collapse="collapseToTopBanner"
|
||||
/>
|
||||
</swiper-item>
|
||||
|
||||
<swiper-item>
|
||||
<scroll-view
|
||||
class="panel-scroll"
|
||||
scroll-y
|
||||
scroll-with-animation
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<SelectRoomAreaPanel
|
||||
:language="currentLang"
|
||||
:banner-list="bannerList"
|
||||
:current-banner-index="currentBannerIndex"
|
||||
:compact-banner-top="compactBannerTop"
|
||||
:is-banner-loading="isBannerLoading"
|
||||
:banner-load-error="bannerLoadError"
|
||||
:banner-loaded-from-api="bannerLoadedFromApi"
|
||||
:current-banner="currentBanner"
|
||||
:selected-area-code="selectedAreaCode"
|
||||
:area-list="areaList"
|
||||
:current-area-label="currentAreaLabel"
|
||||
:selected-tag="selectedTag"
|
||||
:current-tag-label="currentTagLabel"
|
||||
:available-filter-tags="availableFilterTags"
|
||||
:selected-sort-key="selectedSortKey"
|
||||
:current-sort-label="currentSortLabel"
|
||||
:is-area-loading="isAreaLoading"
|
||||
:room-load-error="roomLoadError"
|
||||
:area-load-error="areaLoadError"
|
||||
:is-login="isLogin"
|
||||
:user-info="userInfo"
|
||||
:display-area-sections="displayAreaSections"
|
||||
@retry-banner="retryLoadBanner"
|
||||
@select-area="selectArea"
|
||||
@select-tag="selectTag"
|
||||
@select-sort="selectSort"
|
||||
@open-room-type="openRoomTypeEntry"
|
||||
@banner-change="handleBannerChange"
|
||||
@collapse-to-hero="collapseToHero"
|
||||
/>
|
||||
</scroll-view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getSessionSnapshot } from '../../../utils/app-state'
|
||||
import { ensureBookingAccess } from '../../../utils/booking-guard'
|
||||
import { getStoredUser } from '../../../utils/flow'
|
||||
import { areaPagination, bannerAll } from '@/api/index.js'
|
||||
import { normalizeAreaItem, normalizeBannerItem } from '../../../utils/select-room-normalizers'
|
||||
import SelectRoomHero from './SelectRoomHero.vue'
|
||||
import SelectRoomAreaPanel from './SelectRoomAreaPanel.vue'
|
||||
|
||||
const SORT_LABEL_MAP = {
|
||||
zh: {
|
||||
default: '默认排序',
|
||||
'price-asc': '价格从低到高',
|
||||
'price-desc': '价格从高到低',
|
||||
'name-asc': '名称排序'
|
||||
},
|
||||
en: {
|
||||
default: 'Default',
|
||||
'price-asc': 'Price: Low to High',
|
||||
'price-desc': 'Price: High to Low',
|
||||
'name-asc': 'Name'
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'SelectRoomTab',
|
||||
components: {
|
||||
SelectRoomHero,
|
||||
SelectRoomAreaPanel
|
||||
},
|
||||
i18n: {
|
||||
zh: {
|
||||
shareTitle: '绿居公寓 - 国际化青年社区',
|
||||
all: '全部',
|
||||
allAreas: '全部区域',
|
||||
allTags: '全部标签',
|
||||
apartmentDetail: '公寓详情',
|
||||
areaStatus: '区域状态',
|
||||
areaStatusDesc: '暂无符合筛选条件的区域',
|
||||
areaUnavailableTitle: '该区域暂未开放',
|
||||
bookViewing: '预约看房',
|
||||
defaultAreaAddressPrefix: '位于',
|
||||
distancePlaceholder: '附近',
|
||||
emptyAreaDesc: '当前筛选条件下暂无可用房源',
|
||||
emptyAreaTitle: "暂无匹配房源",
|
||||
emptyBannerDesc: "暂无可展示的轮播图",
|
||||
emptyBannerTitle: "暂无轮播图",
|
||||
learnMore: "了解更多",
|
||||
loadingAreaStatus: "区域加载中",
|
||||
pricePending: "待定",
|
||||
priceUnit: "元/月起",
|
||||
searchApartment: "搜索区域、公寓或房型",
|
||||
sortDefault: "默认排序",
|
||||
sortNameAsc: "名称排序",
|
||||
sortPriceAsc: "价格从低到高",
|
||||
sortPriceDesc: "价格从高到低",
|
||||
tagFilter: "标签筛选",
|
||||
viewCommunity: "查看详情"
|
||||
},
|
||||
en: {
|
||||
shareTitle: 'Luju Apartments - International Youth Community',
|
||||
all: "All",
|
||||
allAreas: "All Areas",
|
||||
allTags: "All Tags",
|
||||
apartmentDetail: "Apartment Detail",
|
||||
areaStatus: "Area Status",
|
||||
areaStatusDesc: "No areas matched the current filter.",
|
||||
areaUnavailableTitle: "Area information unavailable",
|
||||
bookViewing: "Book Viewing",
|
||||
defaultAreaAddressPrefix: "Located at ",
|
||||
distancePlaceholder: "Nearby",
|
||||
emptyAreaDesc: "No rooms available under the current filters.",
|
||||
emptyAreaTitle: "No matched rooms",
|
||||
emptyBannerDesc: "No active banners available.",
|
||||
emptyBannerTitle: "No banners",
|
||||
learnMore: "Learn More",
|
||||
loadingAreaStatus: "Loading areas",
|
||||
pricePending: "TBD",
|
||||
priceUnit: "/month",
|
||||
searchApartment: "Search by area, apartment or room type",
|
||||
sortDefault: "Default",
|
||||
sortNameAsc: "Name",
|
||||
sortPriceAsc: "Price: Low to High",
|
||||
sortPriceDesc: "Price: High to Low",
|
||||
tagFilter: "Tags",
|
||||
viewCommunity: "View Details"
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLogin: false,
|
||||
userInfo: null,
|
||||
currentBannerIndex: 0,
|
||||
currentScreen: 0,
|
||||
isHeroCollapsed: false,
|
||||
isBannerLoading: false,
|
||||
bannerLoadedFromApi: false,
|
||||
bannerLoadError: '',
|
||||
isAreaLoading: false,
|
||||
areaLoadError: '',
|
||||
areaList: [],
|
||||
selectedAreaCode: '',
|
||||
selectedTag: '',
|
||||
selectedSortKey: 'default',
|
||||
roomLoadError: '',
|
||||
navBarBg: 'transparent',
|
||||
navBarTransition: { transition: 'background-color 0.3s ease' },
|
||||
bannerList: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentLang() {
|
||||
return this.$getLanguage()
|
||||
},
|
||||
currentBanner() {
|
||||
return this.bannerList[this.currentBannerIndex] || {}
|
||||
},
|
||||
compactBannerTop() {
|
||||
if (this.currentScreen === 0) {
|
||||
return this.vuex_custom_bar_height || 88
|
||||
}
|
||||
return (this.vuex_status_bar_height || 20) + 12
|
||||
},
|
||||
currentAreaLabel() {
|
||||
const matchedArea = this.areaList.find((item) => item.code === this.resolveBannerAreaCode())
|
||||
if (matchedArea) {
|
||||
return this.getLocalizedAreaText(matchedArea)
|
||||
}
|
||||
if (this.currentLang === 'en') {
|
||||
return this.currentBanner.areaLabelEnDisplay || this.currentBanner.areaLabelEn || this.currentBanner.areaLabel || this.t('allAreas')
|
||||
}
|
||||
return this.currentBanner.areaLabel || this.t('allAreas')
|
||||
},
|
||||
currentTagLabel() {
|
||||
return this.selectedTag || this.t('allTags')
|
||||
},
|
||||
availableFilterTags() {
|
||||
const preferredOrder = ['押一付一', '生活便利', '独立厨卫', '商圈环绕', '民水民电', '拎包入住', '近地铁', '智能家居']
|
||||
const tagSet = new Set()
|
||||
|
||||
this.areaList.forEach((area) => {
|
||||
;(area.tags || []).forEach((tag) => tagSet.add(tag))
|
||||
;(area.roomTypes || []).forEach((roomType) => {
|
||||
;(roomType.tags || []).forEach((tag) => tagSet.add(tag))
|
||||
})
|
||||
})
|
||||
|
||||
const preferredTags = preferredOrder.filter((tag) => tagSet.has(tag))
|
||||
const extraTags = [...tagSet].filter((tag) => !preferredOrder.includes(tag))
|
||||
|
||||
return [...preferredTags, ...extraTags]
|
||||
},
|
||||
currentSortLabel() {
|
||||
const labelMap = SORT_LABEL_MAP[this.currentLang] || SORT_LABEL_MAP.zh
|
||||
return labelMap[this.selectedSortKey] || labelMap.default
|
||||
},
|
||||
displayAreaSections() {
|
||||
const filteredAreas = this.areaList
|
||||
.map((area) => ({
|
||||
...area,
|
||||
roomTypes: this.filterAndSortRoomTypes(area)
|
||||
}))
|
||||
.filter((area) => (area.code || area.name) && area.roomTypes.length)
|
||||
|
||||
return this.sortAreaSections(filteredAreas)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentLang() {
|
||||
this.loadBannerList()
|
||||
this.loadAreaList()
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
this.checkLoginStatus()
|
||||
await Promise.all([this.loadBannerList(), this.loadAreaList()])
|
||||
},
|
||||
methods: {
|
||||
t(key) {
|
||||
return this.$tm('pages.index.component.SelectRoomTab', key)
|
||||
},
|
||||
getLocalizedBannerText(banner = {}, field = '') {
|
||||
const key = String(field || '').trim()
|
||||
if (!key) {
|
||||
return ''
|
||||
}
|
||||
const zhText = banner[key]
|
||||
const enText = banner[`${key}En`]
|
||||
if (this.currentLang === 'en') {
|
||||
return enText || zhText || ''
|
||||
}
|
||||
return zhText || ''
|
||||
},
|
||||
getLocalizedAreaText(area = {}) {
|
||||
if (this.currentLang === 'en') {
|
||||
return area.nameEnDisplay || area.nameEn || area.name || ''
|
||||
}
|
||||
return area.name || ''
|
||||
},
|
||||
getLocalizedRoomTypeText(roomType = {}) {
|
||||
if (this.currentLang === 'en') {
|
||||
return roomType.nameEnDisplay || roomType.nameEn || roomType.name || ''
|
||||
}
|
||||
return roomType.name || ''
|
||||
},
|
||||
toggleLang() {
|
||||
this.$toggleLocale()
|
||||
},
|
||||
checkLoginStatus() {
|
||||
const session = getSessionSnapshot()
|
||||
this.isLogin = session.isLogin
|
||||
this.userInfo = this.isLogin ? getStoredUser() : null
|
||||
},
|
||||
loadBannerList() {
|
||||
this.isBannerLoading = true
|
||||
this.bannerLoadError = ''
|
||||
|
||||
return bannerAll(this.currentLang)
|
||||
.then((res) => {
|
||||
return Array.isArray(res?.data)
|
||||
? res.data
|
||||
: Array.isArray(res)
|
||||
? res
|
||||
: []
|
||||
})
|
||||
.catch((e) => {
|
||||
console.warn('bannerAll API failed, using fallback', e)
|
||||
return []
|
||||
})
|
||||
.then((rawList) => {
|
||||
const enabledList = rawList
|
||||
.filter((item) => item && item.enabled !== false)
|
||||
.sort((a, b) => (a.sort || 0) - (b.sort || 0))
|
||||
.map((item, index) => normalizeBannerItem(item, index))
|
||||
|
||||
this.bannerList = enabledList
|
||||
this.bannerLoadedFromApi = true
|
||||
this.currentBannerIndex = 0
|
||||
})
|
||||
.finally(() => {
|
||||
this.isBannerLoading = false
|
||||
})
|
||||
},
|
||||
loadAreaList() {
|
||||
this.isAreaLoading = true
|
||||
this.areaLoadError = ''
|
||||
|
||||
return areaPagination({ PageIndex: 1, PageSize: 10000 }, this.currentLang)
|
||||
.then((res) => {
|
||||
return Array.isArray(res?.data?.data)
|
||||
? res.data.data
|
||||
: Array.isArray(res?.data)
|
||||
? res.data
|
||||
: []
|
||||
})
|
||||
.catch((e) => {
|
||||
console.warn('areaPagination API failed, using fallback', e)
|
||||
return []
|
||||
})
|
||||
.then((rawList) => {
|
||||
if (rawList && rawList.length) {
|
||||
this.areaList = rawList.map((item) => normalizeAreaItem(item))
|
||||
} else {
|
||||
this.areaList = []
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.isAreaLoading = false
|
||||
})
|
||||
},
|
||||
resolveBannerAreaCode() {
|
||||
if (!this.areaList.length) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (this.currentBanner.areaCode) {
|
||||
const codeMatched = this.areaList.find((item) => item.code === this.currentBanner.areaCode)
|
||||
if (codeMatched) {
|
||||
return codeMatched.code
|
||||
}
|
||||
}
|
||||
|
||||
const byName = this.areaList.find((item) => {
|
||||
const labels = [
|
||||
item.name,
|
||||
item.nameEn
|
||||
].filter(Boolean)
|
||||
return labels.includes(this.currentBanner.areaLabel) || labels.includes(this.currentBanner.areaLabelEn)
|
||||
})
|
||||
if (byName) {
|
||||
return byName.code
|
||||
}
|
||||
|
||||
const fuzzy = this.areaList.find((item) => {
|
||||
const labels = [
|
||||
this.currentBanner.areaLabel,
|
||||
this.currentBanner.areaLabelEn
|
||||
].filter(Boolean)
|
||||
return labels.some((label) => label.includes(item.name) || label.includes(item.nameEn || ''))
|
||||
})
|
||||
return fuzzy ? fuzzy.code : ''
|
||||
},
|
||||
applyBannerFilters() {
|
||||
const resolvedAreaCode = this.resolveBannerAreaCode()
|
||||
this.selectedAreaCode = resolvedAreaCode
|
||||
},
|
||||
selectArea(areaCode = '') {
|
||||
this.selectedAreaCode = areaCode
|
||||
},
|
||||
selectTag(tag = '') {
|
||||
this.selectedTag = this.selectedTag === tag ? '' : tag
|
||||
},
|
||||
selectSort(sortKey = 'default') {
|
||||
this.selectedSortKey = sortKey
|
||||
},
|
||||
getPriceValue(priceText) {
|
||||
if (!priceText) {
|
||||
return Number.MAX_SAFE_INTEGER
|
||||
}
|
||||
const matched = String(priceText).match(/(\d+(\.\d+)?)/)
|
||||
return matched ? Number(matched[1]) : Number.MAX_SAFE_INTEGER
|
||||
},
|
||||
getRoomTypePriceValue(roomType = {}) {
|
||||
if (Number.isFinite(roomType.priceValue)) {
|
||||
return roomType.priceValue
|
||||
}
|
||||
return this.getPriceValue(roomType.price)
|
||||
},
|
||||
isTagMatched(tags = [], tag = '') {
|
||||
if (!tag) {
|
||||
return true
|
||||
}
|
||||
return Array.isArray(tags) && tags.includes(tag)
|
||||
},
|
||||
filterAndSortRoomTypes(area = {}) {
|
||||
const areaMatched = this.isTagMatched(area.tags, this.selectedTag)
|
||||
const roomTypes = areaMatched || !this.selectedTag
|
||||
? (area.roomTypes || [])
|
||||
: (area.roomTypes || []).filter((roomType) => this.isTagMatched(roomType.tags, this.selectedTag))
|
||||
|
||||
return this.sortRoomTypes(roomTypes)
|
||||
},
|
||||
sortRoomTypes(roomTypes = []) {
|
||||
const list = [...roomTypes]
|
||||
if (this.selectedSortKey === 'price-asc') {
|
||||
return list.sort((a, b) => this.getRoomTypePriceValue(a) - this.getRoomTypePriceValue(b))
|
||||
}
|
||||
if (this.selectedSortKey === 'price-desc') {
|
||||
return list.sort((a, b) => this.getRoomTypePriceValue(b) - this.getRoomTypePriceValue(a))
|
||||
}
|
||||
if (this.selectedSortKey === 'name-asc') {
|
||||
return list.sort((a, b) => String(this.getLocalizedRoomTypeText(a) || '').localeCompare(String(this.getLocalizedRoomTypeText(b) || ''), this.currentLang === 'en' ? 'en' : 'zh-Hans-CN'))
|
||||
}
|
||||
return list
|
||||
},
|
||||
getAreaSortPrice(area = {}, mode = 'min') {
|
||||
const values = (area.roomTypes || [])
|
||||
.map((roomType) => this.getRoomTypePriceValue(roomType))
|
||||
.filter((value) => Number.isFinite(value) && value !== Number.MAX_SAFE_INTEGER)
|
||||
|
||||
if (!values.length) {
|
||||
return Number.MAX_SAFE_INTEGER
|
||||
}
|
||||
|
||||
return mode === 'max' ? Math.max(...values) : Math.min(...values)
|
||||
},
|
||||
sortAreaSections(areas = []) {
|
||||
const list = [...areas]
|
||||
if (this.selectedSortKey === 'price-asc') {
|
||||
return list.sort((a, b) => this.getAreaSortPrice(a, 'min') - this.getAreaSortPrice(b, 'min'))
|
||||
}
|
||||
if (this.selectedSortKey === 'price-desc') {
|
||||
return list.sort((a, b) => this.getAreaSortPrice(b, 'max') - this.getAreaSortPrice(a, 'max'))
|
||||
}
|
||||
if (this.selectedSortKey === 'name-asc') {
|
||||
return list.sort((a, b) => String(this.getLocalizedAreaText(a) || '').localeCompare(String(this.getLocalizedAreaText(b) || ''), this.currentLang === 'en' ? 'en' : 'zh-Hans-CN'))
|
||||
}
|
||||
return list
|
||||
},
|
||||
openServiceEntry({ path, title, desc, key, payload }) {
|
||||
uni.navigateTo({
|
||||
url: `${path}?title=${encodeURIComponent(title)}&desc=${encodeURIComponent(desc)}&key=${encodeURIComponent(key)}&payload=${encodeURIComponent(JSON.stringify(payload || {}))}`
|
||||
})
|
||||
},
|
||||
handleBannerChange(index) {
|
||||
this.currentBannerIndex = index
|
||||
},
|
||||
switchBanner(index) {
|
||||
this.currentBannerIndex = index
|
||||
},
|
||||
collapseToTopBanner() {
|
||||
this.currentScreen = 1
|
||||
},
|
||||
collapseToHero() {
|
||||
this.currentScreen = 0
|
||||
},
|
||||
handleScreenChange(e) {
|
||||
this.currentScreen = e.detail.current
|
||||
this.isHeroCollapsed = this.currentScreen === 1
|
||||
this.navBarBg = this.isHeroCollapsed ? 'rgba(17, 17, 17, 0.92)' : 'transparent'
|
||||
},
|
||||
retryLoadBanner() {
|
||||
this.loadBannerList()
|
||||
},
|
||||
openRoomTypeEntry(area, roomType) {
|
||||
uni.navigateTo({
|
||||
url: `/pkgExplore/room_detail/room_detail?roomTypeData=${encodeURIComponent(JSON.stringify(roomType))}&areaData=${encodeURIComponent(JSON.stringify({
|
||||
code: area.code,
|
||||
name: area.name,
|
||||
nameEn: area.nameEn || '',
|
||||
address: area.address
|
||||
,
|
||||
addressEn: area.addressEn || ''
|
||||
}))}`
|
||||
})
|
||||
},
|
||||
handleConsult() {
|
||||
// 从 areaList 中匹配当前 banner 对应的小区信息
|
||||
const resolvedAreaCode = this.resolveBannerAreaCode()
|
||||
const area = this.areaList.find(item => item.code === resolvedAreaCode)
|
||||
|
||||
if (area) {
|
||||
uni.navigateTo({
|
||||
url: `/pkgExplore/store_detail/store_detail?storeData=${encodeURIComponent(JSON.stringify(area))}`
|
||||
})
|
||||
} else {
|
||||
// 容错处理
|
||||
uni.showToast({
|
||||
title: this.t('areaUnavailableTitle'),
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
},
|
||||
handleSubmit() {
|
||||
const bookingUrl = `/pkgBooking/book_viewing/book_viewing?payload=${encodeURIComponent(JSON.stringify({
|
||||
sourceType: 'banner',
|
||||
bannerTitle: this.getLocalizedBannerText(this.currentBanner, 'title'),
|
||||
bannerTitleEn: this.currentBanner.titleEn || '',
|
||||
bannerSubtitle: this.getLocalizedBannerText(this.currentBanner, 'subtitle'),
|
||||
bannerSubtitleEn: this.currentBanner.subtitleEn || '',
|
||||
bannerDesc: this.getLocalizedBannerText(this.currentBanner, 'desc'),
|
||||
bannerDescEn: this.currentBanner.descEn || '',
|
||||
areaLabel: this.currentAreaLabel,
|
||||
areaLabelEn: this.currentLang === 'en'
|
||||
? this.currentAreaLabel
|
||||
: ((this.areaList.find((item) => item.code === this.resolveBannerAreaCode()) || {}).nameEn || this.currentBanner.areaLabelEn || ''),
|
||||
areaCode: this.resolveBannerAreaCode()
|
||||
}))}`
|
||||
if (!ensureBookingAccess({
|
||||
language: this.currentLang,
|
||||
userInfo: this.userInfo,
|
||||
loginRedirectUrl: bookingUrl
|
||||
})) {
|
||||
return
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: bookingUrl
|
||||
})
|
||||
}
|
||||
}
|
||||
,
|
||||
onShareAppMessage() {
|
||||
return {
|
||||
title: this.t('shareTitle'),
|
||||
path: '/pages/index/index'
|
||||
}
|
||||
},
|
||||
onShareTimeline() {
|
||||
return {
|
||||
title: this.t('shareTitle')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.select-room-page {
|
||||
height: 100vh;
|
||||
background: linear-gradient(180deg, #F0F6FF 0%, #F8FAFF 100%);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.page-swiper {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.panel-scroll {
|
||||
height: calc(100vh - env(safe-area-inset-bottom));
|
||||
background: linear-gradient(180deg, #F0F6FF 0%, #F8FAFF 100%);
|
||||
}
|
||||
|
||||
/* 隐藏整个页面的滚动条 */
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.custom-nav {
|
||||
width: 100%;
|
||||
padding: 0 40rpx;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.lang-text-toggle {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
|
||||
.lang-text {
|
||||
transition: all 0.3s ease;
|
||||
padding: 10rpx 0;
|
||||
}
|
||||
|
||||
.lang-text--active {
|
||||
color: #ffffff;
|
||||
font-weight: 700;
|
||||
text-shadow: 0 0 12rpx rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.lang-split {
|
||||
margin: 0 12rpx;
|
||||
opacity: 0.3;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user