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.
402 lines
10 KiB
402 lines
10 KiB
<template>
|
|
<div class="billboard">
|
|
<div class="ip"></div>
|
|
<div class="guide" @click="goPage({ title: '地图导览', path: '/guide' })"></div>
|
|
<div class="header">
|
|
<div class="r1">当前位置 <img src="./loc.png" alt="" /></div>
|
|
<div class="r2">{{ bf }}</div>
|
|
<div class="r3"></div>
|
|
</div>
|
|
<div class="list-container" ref="listRef">
|
|
<div
|
|
class="list"
|
|
:style="{
|
|
height: `calc(${columnHeight / 1080} * 100vw)`,
|
|
width: `calc(484px * ${Math.ceil(columns.length / 2) * 2} + 68px)`
|
|
}"
|
|
>
|
|
<div v-for="(col, i) of columns" :key="'col' + i" class="column" :style="{ height: `calc(${columnHeight / 1080} * 100vw)` }">
|
|
<template v-for="(data, j) of col.list">
|
|
<div v-if="data.type === 'format'" class="format" :key="data.name">
|
|
{{ data.name }}
|
|
<span class="meta">/{{ data.length }}个</span>
|
|
</div>
|
|
|
|
<div v-else-if="data.type === 'shop'" class="shop" :key="data.code">
|
|
<div class="left">
|
|
<img class="dir" :src="data.dir" />
|
|
<div class="text">
|
|
{{ data.shopName }}
|
|
</div>
|
|
</div>
|
|
<div class="right"></div>
|
|
</div>
|
|
|
|
<div v-else class="placeholder" :key="'placeholder' + i + '_' + j"></div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="showBullets" class="bullets">
|
|
<div v-for="(_, i) of scrollLefts" :key="i" class="bullet" :class="{ active: index === i }">
|
|
<div class="burning" :style="{ transition: `all ${timeout}ms linear` }"></div>
|
|
</div>
|
|
<div class="meta">{{ remainingMinutes }}秒后切页</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, ref, watch } from 'vue'
|
|
import { storeToRefs } from 'pinia'
|
|
import { useStore } from '@/store/root'
|
|
import { useRouter } from 'vue-router'
|
|
import { getBrandListByFormat } from '@/http/brand/api'
|
|
|
|
const timeout = 30000
|
|
const heights = { shop: 44, format: 49, placeholder: 40 }
|
|
const setValue = refInstance => value => Object.assign(refInstance, { value })
|
|
const store = useStore()
|
|
const router = useRouter()
|
|
const shopListByFormat = ref([])
|
|
const listRef = ref(null)
|
|
const { currentFloor, buildingList } = storeToRefs(store)
|
|
const bf = computed(() => (buildingList.length > 1 ? currentFloor.value.building + '-' : '') + currentFloor.value.floor)
|
|
|
|
const scrollWidth = ref(0)
|
|
const setScrollWidth = setValue(scrollWidth)
|
|
const scrollLefts = ref([])
|
|
const setScrollLefts = setValue(scrollLefts)
|
|
const index = ref(null)
|
|
const setIndex = setValue(index)
|
|
const columns = ref([])
|
|
const setColumns = setValue(columns)
|
|
const remainingMinutes = ref(Math.ceil(timeout / 1000))
|
|
const setRemainingMinutes = setValue(remainingMinutes)
|
|
|
|
const showBullets = computed(() => scrollLefts.value.length > 1)
|
|
const columnHeight = 746
|
|
|
|
const shops = computed(() =>
|
|
shopListByFormat.value.map(brand => ({
|
|
...brand,
|
|
shopList: brand.shopList.filter(shop => shop.floor === currentFloor.value.floor)
|
|
}))
|
|
)
|
|
getBrandListByFormat().then(({ data }) => {
|
|
shopListByFormat.value = data.list
|
|
})
|
|
|
|
try {
|
|
window.Map_QM?.showFloor?.(currentFloor.value.floorOrder)
|
|
window.Map_QM.onShowMeDir()
|
|
} catch (error) {
|
|
console.log('error:', error)
|
|
}
|
|
|
|
const goPage = item => {
|
|
store.SET_SELECTED_MODULE(item.title)
|
|
router.push(item.path)
|
|
}
|
|
watch(shops, (formats, _, onCleanup) => {
|
|
if (formats.length) {
|
|
const list = formats
|
|
.reduce(
|
|
(acc, nxt) =>
|
|
!nxt.shopList.length
|
|
? [...acc]
|
|
: [
|
|
...acc,
|
|
{
|
|
type: 'format',
|
|
length: nxt.shopList.length,
|
|
name: nxt.name
|
|
},
|
|
...nxt.shopList.map(shop => ({
|
|
...shop,
|
|
type: 'shop'
|
|
})),
|
|
{ type: 'placeholder' }
|
|
],
|
|
[]
|
|
)
|
|
.reduce((acc, nxt) => {
|
|
const lastColumn = acc[acc.length - 1]
|
|
const nxtHeight = heights[nxt.type]
|
|
|
|
if (!lastColumn || lastColumn.height + nxtHeight > columnHeight) {
|
|
if (nxt.type === 'placeholder') return [...acc, { height: 0, list: [], spStartHeight: null }]
|
|
return [
|
|
...acc,
|
|
{
|
|
height: heights[nxt.type],
|
|
list: [nxt],
|
|
spStartHeight: nxt.isSp ? 0 : null,
|
|
hasBanner: nxt.isSp && nxt.type === 'format'
|
|
}
|
|
]
|
|
} else {
|
|
if (nxt.type === 'placeholder' && nxt.isSp) {
|
|
lastColumn.spEndHeight = lastColumn.height
|
|
}
|
|
lastColumn.list.push(nxt)
|
|
lastColumn.height += nxtHeight
|
|
|
|
return acc
|
|
}
|
|
}, [])
|
|
setColumns(list)
|
|
|
|
if (shops.value.length) {
|
|
const tm = setTimeout(() => {
|
|
setScrollWidth(listRef.value.scrollWidth)
|
|
}, 1000)
|
|
onCleanup(() => clearTimeout(tm))
|
|
}
|
|
}
|
|
})
|
|
|
|
watch(index, (nxt, _, onCleanup) => {
|
|
if (nxt === null) return
|
|
listRef.value.scrollLeft = scrollLefts.value[nxt]
|
|
const tm = setTimeout(() => {
|
|
setIndex((nxt + 1) % scrollLefts.value.length)
|
|
}, timeout)
|
|
setRemainingMinutes(Math.ceil(timeout / 1000))
|
|
const interval = setInterval(() => {
|
|
if (remainingMinutes.value > 0) setRemainingMinutes(remainingMinutes.value - 1)
|
|
}, 1000)
|
|
onCleanup(() => {
|
|
clearTimeout(tm)
|
|
clearInterval(interval)
|
|
})
|
|
})
|
|
|
|
watch(scrollWidth, _scrollWidth => {
|
|
if (_scrollWidth > 0) {
|
|
const list = Array.from(document.querySelectorAll('.shop'))
|
|
.map(el => el.getBoundingClientRect().left - (72 / 1080) * window.innerWidth)
|
|
.reduce((acc, nxt) => {
|
|
const last = acc[acc.length - 1]
|
|
return nxt === last ? acc : [...acc, nxt]
|
|
}, [])
|
|
.reduce((acc, nxt, i) => (i % 2 === 0 ? acc.concat(nxt) : acc), [])
|
|
setScrollLefts(list)
|
|
}
|
|
})
|
|
watch(scrollLefts, _scrollLefts => {
|
|
if (_scrollLefts.length > 1) {
|
|
setIndex(0)
|
|
}
|
|
})
|
|
</script>
|
|
<style lang="scss">
|
|
@keyframes counter {
|
|
0% {
|
|
counter-decrement: count 30;
|
|
}
|
|
|
|
100% {
|
|
counter-decrement: count 0;
|
|
}
|
|
}
|
|
</style>
|
|
<style lang="scss" scoped>
|
|
.billboard {
|
|
position: absolute;
|
|
top: 800px;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: #dee6f6;
|
|
z-index: 100;
|
|
.ip {
|
|
position: absolute;
|
|
width: 180px;
|
|
height: 257.5px;
|
|
right: 56px;
|
|
top: -57.5px;
|
|
background: center / cover no-repeat url(./ip.png);
|
|
z-index: 1;
|
|
}
|
|
.guide {
|
|
position: absolute;
|
|
height: 200px;
|
|
width: 500px;
|
|
right: 0;
|
|
top: 0;
|
|
z-index: 1;
|
|
}
|
|
.header {
|
|
position: relative;
|
|
height: 200px;
|
|
padding-top: 48px;
|
|
padding-left: 68px;
|
|
background: top / contain no-repeat url(./bg.png);
|
|
z-index: 0;
|
|
.r1 {
|
|
display: flex;
|
|
height: 23px;
|
|
font-weight: 700;
|
|
font-size: 20px;
|
|
line-height: 23px;
|
|
color: rgba(0, 0, 0, 0.6);
|
|
img {
|
|
width: 20px;
|
|
height: 20px;
|
|
margin-left: 4px;
|
|
}
|
|
}
|
|
.r2 {
|
|
margin-top: 9px;
|
|
margin-bottom: 9px;
|
|
font-weight: 900;
|
|
font-size: 48px;
|
|
line-height: 56px;
|
|
color: #000000;
|
|
}
|
|
.r3 {
|
|
width: 184px;
|
|
height: 32px;
|
|
background: center / cover no-repeat url(./meta.png);
|
|
}
|
|
}
|
|
.list-container {
|
|
overflow-x: scroll;
|
|
overflow-y: hidden;
|
|
scroll-behavior: smooth;
|
|
margin-left: 68px;
|
|
padding-top: 56px;
|
|
width: 1012px;
|
|
&::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
}
|
|
.list {
|
|
display: flex;
|
|
text-align: left;
|
|
.column {
|
|
position: relative;
|
|
display: inline-block;
|
|
width: 484px;
|
|
.format {
|
|
z-index: 1;
|
|
width: 339.995px;
|
|
height: 54px;
|
|
font-weight: 900;
|
|
font-size: 28px;
|
|
line-height: 33px;
|
|
color: rgba(0, 0, 0, 0.8);
|
|
.meta {
|
|
font-weight: 500;
|
|
font-size: 20px;
|
|
line-height: 23px;
|
|
color: rgba(0, 0, 0, 0.6);
|
|
}
|
|
}
|
|
.shop {
|
|
z-index: 1;
|
|
position: relative;
|
|
display: flex;
|
|
font-weight: 700;
|
|
font-size: 16px;
|
|
line-height: 19px;
|
|
color: rgba(0, 0, 0, 0.8);
|
|
width: 460px;
|
|
height: 40px;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
break-inside: avoid-column;
|
|
background: rgba(255, 255, 255, 0.8);
|
|
border-radius: 4px;
|
|
padding-left: 16px;
|
|
.left {
|
|
display: flex;
|
|
flex: 1;
|
|
padding-right: 15px;
|
|
align-items: center;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: hidden;
|
|
.text {
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
}
|
|
.right {
|
|
display: inline-flex;
|
|
flex: 0 0 50px;
|
|
font-weight: 500;
|
|
font-size: 16px;
|
|
line-height: 19px;
|
|
text-align: right;
|
|
color: rgba(0, 0, 0, 0.6);
|
|
}
|
|
.dir {
|
|
width: 24px;
|
|
height: 24px;
|
|
margin-right: 12px;
|
|
}
|
|
}
|
|
.shop + .shop {
|
|
margin-top: 4px;
|
|
}
|
|
.placeholder {
|
|
z-index: 1;
|
|
width: 100%;
|
|
height: 40px;
|
|
}
|
|
}
|
|
}
|
|
|
|
.bullets {
|
|
position: absolute;
|
|
display: flex;
|
|
width: 123px;
|
|
left: 0;
|
|
right: 0;
|
|
margin: auto;
|
|
bottom: 47px;
|
|
justify-content: center;
|
|
.meta {
|
|
position: absolute;
|
|
left: 0;
|
|
right: 0;
|
|
margin: auto;
|
|
top: 14px;
|
|
font-weight: 500;
|
|
font-size: 16px;
|
|
line-height: 19px;
|
|
text-align: center;
|
|
color: rgba(0, 0, 0, 0.6);
|
|
.num {
|
|
content: counter(count);
|
|
|
|
counter-reset: count 30;
|
|
}
|
|
}
|
|
.bullet {
|
|
display: flex;
|
|
width: 0;
|
|
height: 6px;
|
|
background: rgba(255, 255, 255, 0.2);
|
|
border-radius: 6px;
|
|
transition: all 0.5s ease-in-out;
|
|
overflow: hidden;
|
|
.burning {
|
|
flex: 0 0 0;
|
|
height: 6px;
|
|
background: #fff;
|
|
}
|
|
&.active {
|
|
width: 123px;
|
|
.burning {
|
|
flex: 0 0 123px;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|
|
|