You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
317 lines
7.9 KiB
317 lines
7.9 KiB
<template>
|
|
<ScrollView ref="scroll" class="brand-scroll" scrollbar>
|
|
<div class="brand-content" ref="content">
|
|
<div
|
|
class="groups"
|
|
v-for="(item, i) of filteredList"
|
|
:ref="
|
|
el => {
|
|
groups[i] = el
|
|
}
|
|
"
|
|
v-show="item.shopList.length"
|
|
:key="item.name"
|
|
>
|
|
<h1 class="info">
|
|
{{ item.name }}<span class="meta">/ {{ item.shopList.length }}个</span>
|
|
<span v-if="item.name === currentFloor.floor" class="current" id="current">您在本层</span>
|
|
</h1>
|
|
<div class="group" :class="{ isRow }">
|
|
<ShopItem
|
|
:config="config"
|
|
:isGuide="path === '/guide' && !isRow"
|
|
:isRow="isRow"
|
|
:isFood="isFood"
|
|
:shop="el"
|
|
@click="handleShop(el)"
|
|
v-for="el of item.shopList"
|
|
:isActive="shop && shop.shopCode === el.shopCode"
|
|
:key="el.shopCode"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="indicator" :style="{ visibility: hasScollBar ? 'visible' : 'hidden' }" id="indicator" ref="indicatorWrapper">
|
|
<div class="handle">{{ filteredList[indicatorIndex]?.name }}</div>
|
|
</div>
|
|
</ScrollView>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, nextTick, watch, computed } from 'vue'
|
|
import ShopItem from '@/base/ShopItem/ShopItem.vue'
|
|
import ScrollView from '@/base/ScrollView/ScrollView.vue'
|
|
import { storeToRefs } from 'pinia'
|
|
import { useStore } from '@/store/root'
|
|
import { useElementSize, useMutationObserver, useThrottleFn } from '@vueuse/core'
|
|
import { getTranslateValues } from './getTranslateValues'
|
|
|
|
const store = useStore()
|
|
const { currentFloor, path } = storeToRefs(store)
|
|
const indicatorWrapper = ref(null)
|
|
const scroll = ref(null)
|
|
const content = ref(null)
|
|
const groups = ref([])
|
|
const { height: containerHeight, width: containerWidth } = useElementSize(scroll)
|
|
|
|
const { height: contentHeight } = useElementSize(content)
|
|
const filteredList = computed(() => props.list.filter(({ shopList }) => shopList.length > 0))
|
|
const hasScollBar = computed(() => containerHeight.value < contentHeight.value)
|
|
const heights = computed(() => groups.value.map(useElementSize).map(({ height }) => height))
|
|
const bounds = computed(() =>
|
|
heights.value.reduce((acc, nxt, i) => {
|
|
const arr = [...acc]
|
|
arr[i] = i === 0 ? nxt.value : arr[i - 1] + nxt.value
|
|
return arr
|
|
}, [])
|
|
)
|
|
const y = ref(0)
|
|
|
|
const indicatorIndex = computed(() =>
|
|
bounds.value.findIndex((el, i) => {
|
|
const start = 40 - y.value
|
|
if (i === 0 && start < el) return true
|
|
if (i === bounds.value.length - 1) return true
|
|
return start > bounds.value[i - 1] && start <= el
|
|
})
|
|
)
|
|
const throttledFn = useThrottleFn(([{ attributeName, target }]) => {
|
|
if (attributeName === 'style') y.value = getTranslateValues(target).y
|
|
}, 500)
|
|
|
|
useMutationObserver(content, throttledFn, {
|
|
attributes: true
|
|
})
|
|
const props = defineProps({
|
|
list: Array,
|
|
config: Object,
|
|
isRow: Boolean,
|
|
isFood: Boolean,
|
|
shop: Object,
|
|
needFocus: Boolean
|
|
})
|
|
|
|
const emits = defineEmits(['click'])
|
|
function handleShop(item) {
|
|
emits('click', item)
|
|
}
|
|
|
|
watch(containerHeight, () =>
|
|
nextTick(() => {
|
|
scroll.value.refresh()
|
|
if (props.shop) {
|
|
const el = document.getElementById(props.shop.houseNumber)
|
|
if (!el) return
|
|
nextTick(() => {
|
|
scroll.value.scrollToElement(el, 500, 0, -100)
|
|
})
|
|
} else {
|
|
const el = document.getElementById('current')
|
|
if (!el) return
|
|
nextTick(() => {
|
|
scroll.value.scrollToElement(el, 500, 0, -100)
|
|
})
|
|
}
|
|
})
|
|
)
|
|
|
|
watch(containerWidth, () =>
|
|
nextTick(() => {
|
|
scroll.value.refresh()
|
|
if (props.shop) {
|
|
const el = document.getElementById(props.shop.houseNumber)
|
|
if (!el) return
|
|
nextTick(() => {
|
|
scroll.value.scrollToElement(el, 500, 0, -100)
|
|
})
|
|
} else {
|
|
const el = document.getElementById('current')
|
|
if (!el) return
|
|
nextTick(() => {
|
|
scroll.value.scrollToElement(el, 500, 0, -100)
|
|
})
|
|
}
|
|
})
|
|
)
|
|
|
|
watch(
|
|
() => props.isRow,
|
|
() =>
|
|
nextTick(() => {
|
|
scroll.value.refresh()
|
|
})
|
|
)
|
|
watch(
|
|
() => props.list,
|
|
() =>
|
|
nextTick(() => {
|
|
scroll.value.refresh()
|
|
scroll.value.scrollTo(0, 0)
|
|
if (props.shop) {
|
|
const el = document.getElementById(props.shop.houseNumber)
|
|
if (!el) return
|
|
nextTick(() => {
|
|
scroll.value.scrollToElement(el, 500, 0, -100)
|
|
})
|
|
} else {
|
|
const el = document.getElementById('current')
|
|
if (!el) return
|
|
nextTick(() => {
|
|
scroll.value.scrollToElement(el, 500, 0, -100)
|
|
})
|
|
}
|
|
})
|
|
)
|
|
|
|
watch([scroll, () => props.shop], () => {
|
|
if (!props.needFocus || !scroll.value || !props.shop || !props.list) return
|
|
const el = document.getElementById(props.shop.houseNumber)
|
|
if (!el) return
|
|
nextTick(() => {
|
|
scroll.value.scrollToElement(el, 500, 0, -100)
|
|
})
|
|
})
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.brand-scroll {
|
|
position: relative;
|
|
overflow: hidden;
|
|
height: 1255px;
|
|
margin-left: 68px;
|
|
.indicator {
|
|
position: absolute;
|
|
height: 250px;
|
|
top: 102px;
|
|
right: 43px;
|
|
width: 77px;
|
|
pointer-events: none;
|
|
.handle {
|
|
position: absolute;
|
|
width: 77px;
|
|
height: 32px;
|
|
background: center / cover no-repeat url(./handle.png);
|
|
font-family: 'Montserrat';
|
|
font-style: normal;
|
|
font-weight: 700;
|
|
font-size: 14px;
|
|
line-height: 32px;
|
|
color: rgba(0, 0, 0, 0.8);
|
|
text-align: center;
|
|
padding-right: 6px;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
}
|
|
&.guide {
|
|
height: 770px;
|
|
}
|
|
:deep(.bscroll-vertical-scrollbar) {
|
|
top: 102px !important;
|
|
width: 48px !important;
|
|
background: center / 6px 250px no-repeat url(@/assets/images/scrollBar.png);
|
|
border-radius: 6px;
|
|
opacity: 1 !important;
|
|
height: 250px !important;
|
|
&::after {
|
|
position: absolute;
|
|
content: '';
|
|
left: 0;
|
|
top: 120px;
|
|
margin: auto;
|
|
width: 48px;
|
|
height: 61px;
|
|
background: center / cover no-repeat url(@/assets/images/scrollHand.png);
|
|
animation: slideNhide 3s ease-in-out forwards;
|
|
}
|
|
.bscroll-indicator {
|
|
height: 95px !important;
|
|
width: 6px !important;
|
|
left: 0;
|
|
right: 0;
|
|
margin: auto;
|
|
background: #ffffff !important;
|
|
border-radius: 6px !important;
|
|
border: none !important;
|
|
margin-top: -50%;
|
|
}
|
|
}
|
|
}
|
|
.brand-content {
|
|
padding-top: 40px;
|
|
padding-bottom: 180px;
|
|
}
|
|
.info {
|
|
font-weight: 900;
|
|
font-size: 32px;
|
|
line-height: 38px;
|
|
color: var(--brand-floorNameColor);
|
|
padding-bottom: 24px;
|
|
display: flex;
|
|
align-items: baseline;
|
|
.meta {
|
|
margin-left: 12px;
|
|
font-weight: 500;
|
|
font-size: 20px;
|
|
line-height: 23px;
|
|
color: var(--brand-floorMetaColor);
|
|
}
|
|
.current {
|
|
display: flex;
|
|
width: 96px;
|
|
height: 27px;
|
|
background: linear-gradient(113.71deg, #435acd 0%, #749cf3 100%);
|
|
font-size: 16px;
|
|
color: #ffffff;
|
|
justify-content: center;
|
|
align-items: center;
|
|
border-radius: var(--global-radius, 6px);
|
|
margin-left: 12px;
|
|
}
|
|
}
|
|
.group {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr 1fr 1fr;
|
|
gap: 24px 8px;
|
|
margin-bottom: 40px;
|
|
padding-right: 68px;
|
|
}
|
|
.isRow {
|
|
&.group {
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 8px 24px;
|
|
}
|
|
}
|
|
@media (min-aspect-ratio: 1/1) {
|
|
.brand-scroll {
|
|
height: calc(100vh - 280px);
|
|
&.brand {
|
|
height: calc(100vh - 280px);
|
|
&.showMap {
|
|
margin-left: 54px;
|
|
.group {
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 24px 8px;
|
|
}
|
|
}
|
|
}
|
|
&.guide {
|
|
height: calc(100vh - 280px);
|
|
margin-left: 54px;
|
|
.group {
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 24px 8px;
|
|
&.isRow {
|
|
grid-template-columns: 1fr;
|
|
gap: 8px;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.group {
|
|
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
|
|
gap: 32px 29px;
|
|
}
|
|
}
|
|
</style>
|
|
|