feat: 页面调整

This commit is contained in:
2026-07-30 17:49:55 +08:00
parent a4e58a780a
commit 7081420384
29 changed files with 946 additions and 432 deletions

View File

@@ -4,17 +4,21 @@
<image class="absolute inset-0 h-full w-full" :src="scenicSpot.heroImage" mode="aspectFill" />
<view class="absolute inset-0 bg-[linear-gradient(180deg,rgba(6,17,31,0.15)_18%,rgba(6,17,31,0.1)_45%,rgba(6,17,31,0.92)_100%)]" />
<view class="absolute left-[16px] right-[16px] top-[calc(env(safe-area-inset-top)+12px)] flex items-center justify-between">
<view class="flex h-[38px] w-[38px] items-center justify-center rounded-full bg-black/30" @click="emit('back')">
<uni-icons type="left" size="20" color="#FFFFFF" />
<view class="absolute inset-x-0 top-0 z-20">
<TopNavBar
title=""
background="transparent"
title-color="#ffffff"
back-icon-color="#ffffff"
@back="emit('back')"
/>
</view>
<view class="absolute right-[100px] top-[calc(env(safe-area-inset-top)+58px)] z-30 flex items-center gap-[10px]">
<view class="flex h-[40px] w-[40px] items-center justify-center rounded-full bg-black/35 shadow-sm" @tap.stop="emit('open-travelers')">
<uni-icons type="contact" size="21" color="#FFFFFF" />
</view>
<view class="flex items-center gap-[10px]">
<view class="flex h-[38px] w-[38px] items-center justify-center rounded-full bg-black/30" @click="emit('open-travelers')">
<uni-icons type="contact" size="20" color="#FFFFFF" />
</view>
<view class="flex h-[38px] w-[38px] items-center justify-center rounded-full bg-black/30" @click="emit('open-orders')">
<uni-icons type="list" size="20" color="#FFFFFF" />
</view>
<view class="flex h-[40px] w-[40px] items-center justify-center rounded-full bg-black/35 shadow-sm" @tap.stop="emit('open-orders')">
<uni-icons type="list" size="21" color="#FFFFFF" />
</view>
</view>
@@ -40,9 +44,15 @@
<view class="-mt-[16px] min-h-0 flex-1 overflow-hidden rounded-t-[22px] bg-white pt-[14px]">
<view class="flex h-full min-h-0">
<scroll-view class="h-full w-[96px] shrink-0 bg-white" scroll-y>
<scroll-view
class="h-full w-[96px] shrink-0 bg-white"
scroll-y
:scroll-into-view="leftScrollIntoViewId"
scroll-with-animation
>
<view
v-for="group in groups"
:id="`ticket-nav-${group.id}`"
:key="group.id"
class="relative flex min-h-[68px] items-center justify-center px-[10px] py-[12px] text-center"
:class="group.id === activeGroupId ? 'bg-[#ecf8f2] text-[#11795c]' : 'bg-white text-[#5f6875]'"
@@ -53,7 +63,14 @@
</view>
</scroll-view>
<scroll-view class="h-full min-w-0 flex-1 bg-[#f4f6f8]" scroll-y :scroll-into-view="scrollIntoViewId" scroll-with-animation>
<scroll-view
id="ticket-product-scroll"
class="h-full min-w-0 flex-1 bg-[#f4f6f8]"
scroll-y
:scroll-into-view="scrollIntoViewId"
scroll-with-animation
@scroll="handleProductScroll"
>
<view class="px-[12px] pb-[24px]">
<view v-for="group in groups" :id="`ticket-group-${group.id}`" :key="group.id" class="pt-[14px]">
<view class="flex items-center">
@@ -95,7 +112,8 @@
</template>
<script setup>
import { computed, ref } from 'vue'
import { computed, getCurrentInstance, nextTick, onMounted, ref, watch } from 'vue'
import TopNavBar from "@/components/TopNavBar/index.vue"
const props = defineProps({
scenicSpot: { type: Object, required: true },
@@ -106,6 +124,10 @@ const props = defineProps({
const emit = defineEmits(['back', 'open-travelers', 'open-orders', 'open-detail', 'buy'])
const activeGroupId = ref(props.groups[0]?.id || '')
const scrollIntoViewId = ref('')
const leftScrollIntoViewId = computed(() => `ticket-nav-${activeGroupId.value}`)
const groupOffsets = ref([])
const rightScrollTop = ref(0)
const instance = getCurrentInstance()
const productsById = computed(() => new Map(props.products.map((product) => [product.id, product])))
const productsForGroup = (group) => (group.productIds || [])
@@ -116,9 +138,57 @@ const ageRuleText = (rule) => `${rule.min}—${rule.max}周岁`
const selectGroup = (groupId) => {
activeGroupId.value = groupId
scrollIntoViewId.value = `ticket-group-${groupId}`
scrollIntoViewId.value = ''
nextTick(() => {
scrollIntoViewId.value = `ticket-group-${groupId}`
})
}
const measureGroupOffsets = () => {
if (!props.groups.length) return
nextTick(() => {
const query = uni.createSelectorQuery().in(instance?.proxy)
query.select('#ticket-product-scroll').boundingClientRect()
props.groups.forEach((group) => {
query.select(`#ticket-group-${group.id}`).boundingClientRect()
})
query.exec((rects = []) => {
const containerRect = rects[0]
if (!containerRect) return
groupOffsets.value = props.groups
.map((group, index) => ({
id: group.id,
top: Math.max(
0,
Number(rects[index + 1]?.top || 0) -
Number(containerRect.top || 0) +
rightScrollTop.value
),
}))
.sort((left, right) => left.top - right.top)
})
})
}
const handleProductScroll = (event) => {
rightScrollTop.value = Number(event.detail?.scrollTop || 0)
if (!groupOffsets.value.length) {
measureGroupOffsets()
return
}
const activationLine = rightScrollTop.value + 24
const activeGroup = groupOffsets.value.reduce(
(current, group) => (group.top <= activationLine ? group : current),
groupOffsets.value[0]
)
if (activeGroup?.id) activeGroupId.value = activeGroup.id
}
onMounted(measureGroupOffsets)
watch(() => props.groups.map((group) => group.id).join(','), measureGroupOffsets)
const makePhoneCall = () => {
if (!props.scenicSpot.phone) return
uni.makePhoneCall({ phoneNumber: props.scenicSpot.phone })