Browse Source

feat: 美食、品牌

潮流
jiannibang 4 years ago
parent
commit
3c126533c9
  1. 51
      src/base/ShopItem/ShopItem.vue
  2. 5
      src/components/BrandScroll/BrandScroll.vue
  3. 209
      src/views/Brand/Brand.vue
  4. 14
      src/views/Brand/Tabs.js
  5. 158
      src/views/Foods/Foods.vue
  6. 10
      src/views/Guide/Guide.vue
  7. 98
      src/views/Index/Index.vue
  8. BIN
      src/views/Index/foodBg.png
  9. BIN
      src/views/Index/recBg.png

51
src/base/ShopItem/ShopItem.vue

@ -1,11 +1,11 @@
<template> <template>
<div :class="{ 'group-item': true, isRow }" @click="handleShop">
<div class="logo-wrapper">
<div :class="{ 'group-item': true, isRow, isFood }" @click="handleShop">
<div :class="{ 'logo-wrapper': true, hasFood: isFood && shop.foodMaterialList.length }">
<img loading="lazy" :src="config.sourceUrl + shop.logoUrl" alt="" class="shop-logo" /> <img loading="lazy" :src="config.sourceUrl + shop.logoUrl" alt="" class="shop-logo" />
</div> </div>
<p class="name"> <p class="name">
<span class="shop-name">{{ switchLanguage(shop, 'shopName') }}</span> <span class="shop-name">{{ switchLanguage(shop, 'shopName') }}</span>
<span class="name-right"><img :src="config.sourceUrl + shop.industryUrl" class="format-icon" alt="" />{{ shop.floor }}</span>
<span class="name-right">{{ shop.floor }}</span>
</p> </p>
</div> </div>
</template> </template>
@ -14,7 +14,8 @@
const props = defineProps({ const props = defineProps({
shop: Object, shop: Object,
config: Object, config: Object,
isRow: Boolean
isRow: Boolean,
isFood: Boolean
}) })
const emits = defineEmits(['click']) const emits = defineEmits(['click'])
@ -97,5 +98,47 @@ function handleShop() {
} }
} }
} }
&.isFood {
display: flex;
flex-direction: column;
width: 230px;
height: 242px;
background: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.5) 100%);
border-radius: 40px;
align-items: center;
.logo-wrapper {
width: 180px;
height: 180px;
background: rgba(255, 255, 255, 0.8);
border-radius: 200px;
padding: 30px;
&.hasFood {
padding: 0;
}
.shop-logo {
border-radius: 200px;
}
}
.name {
display: block;
width: 100%;
.shop-name {
display: block;
margin-top: 12px;
text-align: center;
}
.name-right {
display: block;
text-align: center;
font-family: 'Montserrat';
font-style: normal;
font-weight: 700;
font-size: 14px;
line-height: 17px;
color: rgba(0, 0, 0, 0.6);
margin-top: 6px;
}
}
}
} }
</style> </style>

5
src/components/BrandScroll/BrandScroll.vue

@ -6,7 +6,7 @@
{{ item.name }}<span class="meta">/ {{ item.shopList.length }}</span> {{ item.name }}<span class="meta">/ {{ item.shopList.length }}</span>
</h1> </h1>
<TransitionGroup name="zoom" mode="out-in" tag="div" :class="{ group: true, isRow }"> <TransitionGroup name="zoom" mode="out-in" tag="div" :class="{ group: true, isRow }">
<ShopItem :config="config" :isRow="isRow" :shop="shop" @click="handleShop(shop)" v-for="shop of item.shopList" :key="shop.shopId" />
<ShopItem :config="config" :isRow="isRow" :isFood="isFood" :shop="shop" @click="handleShop(shop)" v-for="shop of item.shopList" :key="shop.shopId" />
</TransitionGroup> </TransitionGroup>
</div> </div>
</div> </div>
@ -21,7 +21,8 @@ const scroll = ref(null)
const props = defineProps({ const props = defineProps({
list: Array, list: Array,
config: Object, config: Object,
isRow: Boolean
isRow: Boolean,
isFood: Boolean
}) })
const emits = defineEmits(['click']) const emits = defineEmits(['click'])

209
src/views/Brand/Brand.vue

@ -1,132 +1,28 @@
<template> <template>
<View title="品牌列表" sub-title="Brand list">
<div class="container flex">
<BrandRecommend @click="handleShop" :config="config" :list="recommendList" />
<div class="brand-left">
<div class="top-title">
<p class="title">{{ selectedTitle }}</p>
<div class="currentfloor">
<img src="../../assets/images/guide/guide_zhong.png" class="zhong" alt="" />
<span class="floor"><i>当前楼层</i>{{ currentFloor.floor }}</span>
</div>
</div>
<div class="line"></div>
<div class="bottom-content">
<BrandScroll @click="handleShop" :list="selectedList" :config="config" />
<div class="brand-middle">
<Tabs class="width" :list="list" @click="handleTab" />
<Transition name="zoom" mode="out-in">
<Industry @click="handleFormat" v-if="tabIdx === 0" :config="config" :list="formatList" />
<div v-else class="floor-list">
<div
class="floor-item"
@click="handleFloor(item, index)"
:class="[index === 0 ? 'width' : '', index === floorIdx ? 'active' : '']"
v-for="(item, index) of mergeFloorsList"
:key="item.floor"
>
{{ item.floor }}
</div>
</div>
</Transition>
</div>
</div>
</div>
</div>
<View>
<BrandScroll class="brand" @click="handleShop" :list="selectedList" :config="config" />
</View> </View>
</template> </template>
<script setup> <script setup>
import { ref, computed } from 'vue'
import { ref } from 'vue'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { useStore } from '@/store/root' import { useStore } from '@/store/root'
import { getBrandList, getBrandListByFormat, getBrandListByFloor } from '@/http/brand/api'
import { list } from './Tabs'
import { getBrandListByFloor } from '@/http/brand/api'
import View from '@/layouts/View.vue' import View from '@/layouts/View.vue'
import Industry from '@/components/Industry/Industry.vue'
import Tabs from '@/components/Tabs/Tabs.vue'
import BrandRecommend from '@/components/BrandRecommend/BrandRecommend.vue'
import BrandScroll from '@/components/BrandScroll/BrandScroll.vue' import BrandScroll from '@/components/BrandScroll/BrandScroll.vue'
import { useStatistics } from '@/composables/useStatistics'
const ALL_BRAND = '全部品牌'
const store = useStore() const store = useStore()
const { config, currentFloor, shopList } = storeToRefs(store)
useStatistics('industry')
const { config, shopList } = storeToRefs(store)
const selectedList = ref([]) const selectedList = ref([])
const formatList = ref([])
const recommendList = ref([])
const selectedTitle = ref('')
const idlecallback = ref(null)
const data = {
formatList: [],
floorListBySort: [],
brandListByFormat: [],
brandListByFloor: []
}
Promise.all([getBrandList(), getBrandListByFormat(), getBrandListByFloor()]).then(([brandList, listByFormat, listByFloor]) => {
data.formatList = brandList.data.industryFatherList
data.floorListBySort = brandList.data.floorList
data.brandListByFormat = listByFormat.data.list
data.brandListByFloor = listByFloor.data.list
recommendList.value = brandList.data.recommendList
formatList.value = data.formatList
selectedList.value = listByFloor.data.list
Promise.all([getBrandListByFloor()]).then(([_brandList]) => {
const list = _brandList.data.list
selectedTitle.value = ALL_BRAND
selectedList.value = list
}) })
const tabIdx = ref(0)
function handleTab(index) {
tabIdx.value = index
selectedTitle.value = ALL_BRAND
selectedList.value = tabIdx.value === 0 ? data.brandListByFloor : data.brandListByFormat
}
function handleFormat(format) {
cancelIdleCallback(idlecallback.value)
selectedTitle.value = format.industryName
if (format.industryName === '全部品牌') {
selectedList.value = data.brandListByFloor
return
}
idlecallback.value = requestIdleCallback(() => {
selectedList.value = data.brandListByFloor.map(item => ({
...item,
shopList: item.shopList.filter(shop => shop[format.industryList ? 'industryFatherId' : 'industryId'] === format.industryId)
}))
})
}
const floorIdx = ref(0)
const mergeFloorsList = computed(() => [{ floor: 'ALL' }, ...data.floorListBySort])
function handleFloor(item, index) {
cancelIdleCallback(idlecallback.value)
if (index === 0) {
selectedTitle.value = ALL_BRAND
} else {
selectedTitle.value = item.floor
}
floorIdx.value = index
if (item.floor === 'ALL') {
selectedList.value = data.brandListByFloor
return
}
idlecallback.value = requestIdleCallback(() => {
selectedList.value = data.brandListByFormat.map(brand => ({
...brand,
shopList: brand.shopList.filter(shop => shop.floor === item.floor)
}))
})
}
function handleShop(item) { function handleShop(item) {
const shop = shopList.value.find(_shop => _shop.shopId === item.shopId) const shop = shopList.value.find(_shop => _shop.shopId === item.shopId)
store.SET_SHOP(shop) store.SET_SHOP(shop)
@ -135,93 +31,8 @@ function handleShop(item) {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.flex {
.brand {
position: relative; position: relative;
}
.title {
font-weight: 700;
font-size: 24px;
font-family: 'font_bold';
color: rgba(0, 0, 0, 0.8);
padding-bottom: 12px;
}
.bottom-content {
display: flex;
justify-content: space-between;
margin-right: 40px;
}
.brand-left {
.top-title {
margin-left: 170px;
margin-right: 258px;
margin-bottom: 14px;
display: flex;
align-items: baseline;
justify-content: space-between;
.title {
padding-bottom: 0;
}
}
.line {
border-bottom: 1px solid rgba(255, 255, 255, 0.4);
}
.currentfloor {
display: flex;
align-items: center;
}
.zhong {
width: 16px;
}
.floor {
font-weight: 700;
font-size: 24px;
font-family: 'font_bold';
color: rgba(0, 0, 0, 0.6);
i {
font-weight: 700;
font-size: 16px;
line-height: 19px;
color: rgba(0, 0, 0, 0.4);
padding-left: 7px;
padding-right: 17px;
}
}
}
.brand-middle {
padding-top: 20px;
.width {
height: 180px;
flex-direction: column;
width: 200px;
:deep(.tab) {
height: 90px;
}
}
.floor-list {
width: 200px;
border-radius: 8px;
overflow: hidden;
margin-top: 18px;
}
.floor-item {
width: 200px;
height: 72px;
background: rgba(255, 255, 255, 0.6);
line-height: 72px;
text-align: center;
font-family: 'font_bold';
font-weight: 700;
font-size: 20px;
color: rgba(0, 0, 0, 0.6);
&.active {
background: linear-gradient(90deg, #ffbd35 0%, #ffd260 100%);
color: rgba(0, 0, 0, 0.8);
}
}
flex: 1;
} }
</style> </style>

14
src/views/Brand/Tabs.js

@ -1,14 +0,0 @@
export const list = [
{
name: '业态筛选',
nameEn: 'Format',
icon: require('@/assets/images/brand/format.png'),
iconActive: require('@/assets/images/brand/format_active.png')
},
{
name: '楼层筛选',
nameEn: 'floor',
icon: require('@/assets/images/brand/floor.png'),
iconActive: require('@/assets/images/brand/floor_active.png')
}
]

158
src/views/Foods/Foods.vue

@ -1,37 +1,6 @@
<template> <template>
<View title="品牌列表" sub-title="Brand list">
<div class="container flex">
<BrandRecommend @click="handleShop" :config="config" :list="recommendList" />
<div class="brand-left">
<div class="top-title">
<p class="title">{{ selectedTitle }}</p>
<div class="currentfloor mr-16">
<img src="../../assets/images/guide/guide_zhong.png" class="zhong" alt="" />
<span class="floor"><i>当前楼层</i>{{ currentFloor.floor }}</span>
</div>
</div>
<div class="line"></div>
<div class="bottom-content">
<BrandScroll @click="handleShop" :list="selectedList" :config="config" />
<div class="brand-middle">
<Transition appear name="zoom" mode="out-in">
<div class="format-wrapper">
<div
class="format-item"
@click="handleFormat(item, index)"
:class="{ active: formatIdx === index }"
v-for="(item, index) of formatList"
:key="item.industryId"
>
{{ switchLanguage(item, 'industryName') }}
</div>
</div>
</Transition>
</div>
</div>
</div>
</div>
<View>
<BrandScroll :isFood="true" class="foods" @click="handleShop" :list="selectedList" :config="config" />
</View> </View>
</template> </template>
@ -39,58 +8,23 @@
import { ref } from 'vue' import { ref } from 'vue'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { useStore } from '@/store/root' import { useStore } from '@/store/root'
import { getBrandList, getBrandListByFloor } from '@/http/brand/api'
import { getBrandListByFloor } from '@/http/brand/api'
import View from '@/layouts/View.vue' import View from '@/layouts/View.vue'
import BrandRecommend from '@/components/BrandRecommend/BrandRecommend.vue'
import BrandScroll from '@/components/BrandScroll/BrandScroll.vue' import BrandScroll from '@/components/BrandScroll/BrandScroll.vue'
const ALL_BRAND = '全部品牌'
const store = useStore() const store = useStore()
const { config, currentFloor, shopList } = storeToRefs(store)
const { config, shopList } = storeToRefs(store)
const idlecallback = ref(null)
const selectedList = ref([]) const selectedList = ref([])
let foodsList = []
const selectedTitle = ref(ALL_BRAND)
const formatList = ref([])
const recommendList = ref([])
Promise.all([getBrandList(), getBrandListByFloor()]).then(([_formatList, _brandList]) => {
const child = _formatList.data.industryFatherList.find(item => item.isSpecial)?.industryList ?? []
idlecallback.value = requestIdleCallback(() => {
formatList.value = [
{
code: '123',
industryName: ALL_BRAND,
industryNameEn: 'All',
isSpecial: true
},
...child
]
selectedList.value = foodsList = _brandList.data.list.map(item => ({
Promise.all([getBrandListByFloor()]).then(([_brandList]) => {
const list = _brandList.data.list.map(item => ({
name: item.name, name: item.name,
shopList: item.shopList.filter(shop => shop.isSpecial) shopList: item.shopList.filter(shop => shop.isSpecial)
})) }))
recommendList.value = _formatList.data.recommendList
})
})
const formatIdx = ref(0)
function handleFormat(item, index) {
formatIdx.value = index
selectedTitle.value = item.industryName
if (index === 0) {
selectedList.value = foodsList
return
}
idlecallback.value = requestIdleCallback(() => {
selectedList.value = foodsList.map(brand => ({
...brand,
shopList: brand.shopList.filter(shop => shop.industryId === item.industryId)
}))
selectedList.value = list
}) })
}
function handleShop(item) { function handleShop(item) {
const shop = shopList.value.find(_shop => _shop.shopId === item.shopId) const shop = shopList.value.find(_shop => _shop.shopId === item.shopId)
@ -100,82 +34,8 @@ function handleShop(item) {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.flex {
.foods {
position: relative; position: relative;
}
.title {
font-weight: 700;
font-size: 24px;
font-family: 'font_bold';
color: rgba(0, 0, 0, 0.8);
padding-bottom: 12px;
}
.bottom-content {
display: flex;
justify-content: space-between;
margin-right: 40px;
}
.brand-left {
.top-title {
margin-left: 170px;
margin-right: 258px;
margin-bottom: 14px;
display: flex;
align-items: baseline;
justify-content: space-between;
.title {
padding-bottom: 0;
}
}
.line {
border-bottom: 1px solid rgba(255, 255, 255, 0.4);
}
.currentfloor {
display: flex;
align-items: center;
}
.zhong {
width: 16px;
}
.floor {
font-weight: 700;
font-size: 24px;
font-family: 'font_bold';
color: rgba(0, 0, 0, 0.6);
i {
font-weight: 700;
font-size: 16px;
line-height: 19px;
color: rgba(0, 0, 0, 0.4);
padding-left: 7px;
padding-right: 17px;
}
}
}
.brand-middle {
.format-wrapper {
width: 200px;
background: rgba(255, 255, 255, 0.6);
margin-top: 20px;
border-radius: 12px;
overflow: hidden;
.format-item {
height: 72px;
line-height: 72px;
text-align: center;
font-weight: 700;
font-size: 14px;
text-align: center;
font-family: 'font_bold';
color: rgba(0, 0, 0, 0.6);
transition: color 0.3s;
&.active {
background: linear-gradient(90deg, #ffbd35 0%, #ffd260 100%);
color: rgba(0, 0, 0, 0.8);
}
}
}
flex: 1;
} }
</style> </style>

10
src/views/Guide/Guide.vue

@ -177,11 +177,17 @@ watch(
top: 1050px; top: 1050px;
height: 100px; height: 100px;
z-index: 60; z-index: 60;
display: flex;
display: block;
overflow-y: hidden;
overflow-x: scroll;
background: linear-gradient(113.71deg, #435acd 0%, #749cf3 100%); background: linear-gradient(113.71deg, #435acd 0%, #749cf3 100%);
&::-webkit-scrollbar {
display: none;
}
.floors-item { .floors-item {
flex: 1; flex: 1;
display: flex;
display: inline-flex;
width: 122.5px;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
position: relative; position: relative;

98
src/views/Index/Index.vue

@ -26,6 +26,24 @@
<div class="meta2">{{ bf }}</div> <div class="meta2">{{ bf }}</div>
<div class="bottom-right"></div> <div class="bottom-right"></div>
</div> </div>
<!-- <div class="food" @click="goPage({ title: '推荐美食', path: '/foods' })">
<div class="title">
<h1>推荐美食</h1>
<h2>此时此刻美食正在等你</h2>
</div>
<div class="grid">
<img class="item" v-for="shop of foodList" :key="shop.id" :src="config.sourceUrl + shop.logoUrl" alt="" />
</div>
</div> -->
<div class="rec" @click="goPage({ title: '品牌列表', path: '/brand' })">
<div class="title">
<h1>推荐品牌</h1>
<h2>心动之选拥抱美好生活</h2>
</div>
<div class="grid">
<img class="item" v-for="shop of shopList.slice(0, 16)" :key="shop.id" :src="config.sourceUrl + shop.logoUrl" alt="" />
</div>
</div>
</div> </div>
<div class="r2"> <div class="r2">
<div class="item" v-for="tab of sidebarList" :key="tab.title" @click="goPage(tab)"> <div class="item" v-for="tab of sidebarList" :key="tab.title" @click="goPage(tab)">
@ -54,13 +72,14 @@ import { useRouter } from 'vue-router'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { useStore } from '@/store/root' import { useStore } from '@/store/root'
import { getBrandList } from '@/http/brand/api' import { getBrandList } from '@/http/brand/api'
import { getWaterfallList } from '@/http/api'
import View from '@/layouts/View.vue' import View from '@/layouts/View.vue'
const router = useRouter() const router = useRouter()
const store = useStore() const store = useStore()
const { indexList, currentFloor, buildingList, shopList, sidebarList, selectedModule } = storeToRefs(store)
const { indexList, currentFloor, buildingList, shopList, sidebarList, selectedModule, config } = storeToRefs(store)
const guideDesc = ref('') const guideDesc = ref('')
const foodList = computed(() => shopList.value.filter(({ isSpecial }) => isSpecial).slice(0, 12) ?? [])
const hotRecommend = computed(() => indexList.value.hotSearch.slice(0, 5) ?? []) const hotRecommend = computed(() => indexList.value.hotSearch.slice(0, 5) ?? [])
const cardsList = computed(() => indexList.value.columnList ?? []) const cardsList = computed(() => indexList.value.columnList ?? [])
const bf = computed(() => (buildingList.length > 1 ? currentFloor.value.building + '-' : '') + currentFloor.value.floor) const bf = computed(() => (buildingList.length > 1 ? currentFloor.value.building + '-' : '') + currentFloor.value.floor)
@ -151,6 +170,7 @@ function handleHot(item) {
height: 557px; height: 557px;
.guide { .guide {
position: relative; position: relative;
flex: 0 0 339px;
width: 339px; width: 339px;
height: 557px; height: 557px;
background: linear-gradient(99.5deg, #f0b92b 0%, #f9d556 100%); background: linear-gradient(99.5deg, #f0b92b 0%, #f9d556 100%);
@ -216,6 +236,80 @@ function handleHot(item) {
color: #000000; color: #000000;
} }
} }
.food,
.rec {
h1 {
font-weight: 900;
font-size: 56px;
line-height: 66px;
color: #ffffff;
margin-bottom: 8px;
}
h2 {
font-weight: 700;
font-size: 16px;
line-height: 19px;
color: rgba(255, 255, 255, 0.8);
}
.grid {
margin-top: 40px;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
width: 485px;
gap: 15px;
.item {
width: 110px;
height: 110px;
background: #ffffff;
border-radius: 12px;
padding: 10px;
object-fit: contain;
}
}
}
.food {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
margin-left: 40px;
background: linear-gradient(180deg, #ffffff 0%, #f2f4f8 100%);
border-radius: 32px;
border: 4px solid rgba(246, 139, 81, 1);
align-items: center;
overflow: hidden;
.title {
margin-top: 4px;
width: 549px;
background: center / cover no-repeat url(./foodBg.png);
height: 160px;
flex: 0 0 160px;
box-shadow: 0px 15px 26px rgba(244, 142, 88, 0.32);
border-radius: 27px;
padding: 32px 0 0 40px;
}
}
.rec {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
margin-left: 40px;
background: linear-gradient(180deg, #ffffff 0%, #f2f4f8 100%);
border-radius: 32px;
align-items: center;
overflow: hidden;
.title {
margin-top: 8px;
width: 549px;
background: center / cover no-repeat url(./recBg.png);
height: 160px;
flex: 0 0 160px;
box-shadow: 0px 15px 26px rgba(230, 201, 148, 0.3);
border-radius: 27px;
padding: 32px 0 0 40px;
}
}
} }
.r2 { .r2 {
display: flex; display: flex;

BIN
src/views/Index/foodBg.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 KiB

BIN
src/views/Index/recBg.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Loading…
Cancel
Save