11 changed files with 421 additions and 6 deletions
@ -0,0 +1,402 @@ |
|||||
|
<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> |
||||
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 911 B |
|
After Width: | Height: | Size: 24 KiB |
Loading…
Reference in new issue