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.
 
 
 

433 lines
11 KiB

<template>
<div class="billboard">
<div class="ip"></div>
<div class="guide" @click="goPage({ title: '地图导览', path: '/guide' })"></div>
<div class="header" :style="{ backgroundImage: `url(${theme.images.billBoardBg})` }">
<div class="r1">当前位置 <img src="./loc.png" alt="" /></div>
<div class="r2">{{ bf }}</div>
<div class="r3"></div>
<QRCodeFromText
v-if="currentFloor"
class="qrcode"
:size="100"
:text="`${config.mobileNav}?s=${currentFloor.floorOrder}_${currentFloor.location}_屏幕的位置`"
></QRCodeFromText>
</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)`, opacity: Math.floor(i / 2) === index ? 1 : 0.5 }"
>
<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">{{ data.distance ? data.distance + '米' : '' }}</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'
import QRCodeFromText from '@/components/QRCodeFromText/QRCodeFromText.vue'
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 shops = ref([])
const listRef = ref(null)
const { currentFloor, buildingList, currentFloorShopMap, config, theme } = 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
Promise.all([getBrandListByFormat()]).then(([{ data: brandListByFormat }]) => {
shops.value = brandListByFormat.list.map(brand => {
return {
...brand,
shopList: brand.shopList
.filter(shop => shop.floor === currentFloor.value.floor)
.map(shop => {
const meta = currentFloorShopMap.value[shop.shopId]
return { ...shop, ...(meta ? meta : {}) }
})
}
})
})
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: var(--billboard-background);
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;
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: var(--billboard-titleColor);
}
.r3 {
width: 184px;
height: 32px;
background: center / cover no-repeat url(./meta.png);
}
.qrcode {
display: flex;
position: absolute;
left: 314px;
top: 57px;
width: 120px;
height: 120px;
background: #ffffff;
border-radius: 12px;
justify-content: center;
align-items: center;
}
}
.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: var(--billboard-titleColor);
.meta {
font-weight: 500;
font-size: 20px;
line-height: 23px;
color: var(--billboard-metaColor);
}
}
.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 100 px;
font-weight: 500;
font-size: 16px;
line-height: 19px;
justify-content: flex-end;
padding-right: 16px;
color: rgba(0, 0, 0, 0.6);
}
.dir {
width: 24px;
height: 24px;
margin-right: 12px;
background: linear-gradient(113.71deg, #435acd 0%, #749cf3 100%);
border-radius: var(--billboard-arrowRadius);
}
}
.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>