Browse Source

feat: 首页水牌

潮流
jiannibang 4 years ago
parent
commit
eea8f41376
  1. 2
      src/App.vue
  2. 1
      src/base/Temperature/Temperature.vue
  3. 3
      src/base/Time/Time.vue
  4. 6
      src/components/PublicComponent/PublicComponent.vue
  5. 9
      src/router/routes.js
  6. 402
      src/views/Billboard/Billboard.vue
  7. BIN
      src/views/Billboard/bg.png
  8. BIN
      src/views/Billboard/ip.png
  9. BIN
      src/views/Billboard/loc.png
  10. BIN
      src/views/Billboard/meta.png
  11. 4
      src/views/Guide/Guide.vue

2
src/App.vue

@ -15,7 +15,7 @@ html,
#app {
width: 1080px;
height: 1920px;
background-color: #eaebef;
background-color: #dee6f6;
overflow: hidden;
}

1
src/base/Temperature/Temperature.vue

@ -19,6 +19,7 @@ const { weather, icon } = useWeather()
height: 44px;
display: flex;
align-items: center;
z-index: 11;
}
.weather-icon {
color: #000000;

3
src/base/Time/Time.vue

@ -24,6 +24,7 @@ const { dateRef, whichWeekRef } = useDay()
font-size: 36px;
line-height: 44px;
color: #000;
z-index: 11;
}
.month {
position: fixed;
@ -34,6 +35,7 @@ const { dateRef, whichWeekRef } = useDay()
font-weight: 700;
font-size: 13px;
line-height: 16px;
z-index: 11;
}
.week {
position: fixed;
@ -45,5 +47,6 @@ const { dateRef, whichWeekRef } = useDay()
font-size: 13px;
line-height: 16px;
color: #516dd8;
z-index: 11;
}
</style>

6
src/components/PublicComponent/PublicComponent.vue

@ -19,7 +19,7 @@
</Transition>
<Temperature />
<Time />
<SearchBar />
<SearchBar v-if="$route.path !== '/billboard'" />
<Tabs />
</template>
@ -66,7 +66,7 @@ async function handleScreen() {
showSearch.value && store.SET_SHOW_SEARCH(false)
showVoice.value && store.SET_SHOW_VOICE(false)
language.value !== 'zh' && store.SET_LANGUAGE('zh')
await router.push('/')
await router.push('/billboard')
}
const timer = ref(null)
@ -83,7 +83,7 @@ onBeforeUnmount(() => {
})
watch(route, to => {
if (to.fullPath === '/guide' || to.fullPath === '/nav') {
if (to.fullPath === '/guide' || to.fullPath === '/nav' || to.fullPath === '/billboard') {
window?.Map_QM?.startRender()
} else {
window?.Map_QM?.cancelRender()

9
src/router/routes.js

@ -3,6 +3,15 @@ export const staticRoutes = [
path: '/',
redirect: '/index'
},
{
path: '/billboard',
name: 'Billboard',
component: () => import(/* webpackChunkName: "guide" */ '@/views/Billboard/Billboard'),
meta: {
showMenu: true,
showMap: true
}
},
{
path: '/guide',
name: 'Guide',

402
src/views/Billboard/Billboard.vue

@ -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>

BIN
src/views/Billboard/bg.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

BIN
src/views/Billboard/ip.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
src/views/Billboard/loc.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 911 B

BIN
src/views/Billboard/meta.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

4
src/views/Guide/Guide.vue

@ -38,7 +38,7 @@
<Teleport to="body">
<div class="control-area">
<div class="map-control-wrapper animate__fast animate__animated animate__fadeInUp">
<div class="map-item" @click="handleMapIcon(item, index)" v-for="(item, index) of list" :key="item.name">
<div class="map-item" @click="handleMapIcon(item, index)" v-for="(item, index) of buttonList" :key="item.name">
<img :src="mapIdx === index ? item.iconActive : item.icon" alt="" class="map-icon" />
<span class="map-name">{{ switchLanguage(item, 'name') }}</span>
</div>
@ -67,7 +67,7 @@
import { ref, watch, onBeforeUnmount } from 'vue'
import { storeToRefs } from 'pinia'
import { useStore } from '@/store/root'
import { RESET, DIRECTION, ACTIVITY_BRAND } from './list'
import { RESET, DIRECTION, ACTIVITY_BRAND, list as buttonList } from './list'
import View from '@/layouts/View.vue'
import { hideMapDialog } from '@/composables/useInitMap'
import { getBrandListByFormat, getBrandListByFloor } from '@/http/brand/api'

Loading…
Cancel
Save