仿美团pc,koa+ssr(五)
一,地点详情页功能开发
1.在searchbar.vue组件,点击热门景点,跳转到景点详情页,keyword中文需要编码下
<template> <div class="search-panel"> <el-row class="m-header-searchbar"> <el-col :span="3" class="left"> <img src="//s0.meituan.net/bs/fe-web-meituan/e5eeaef/img/logo.png" alt="美团"> </el-col> <el-col :span="15" class="center"> <div class="wrapper"> <el-input v-model="search" placeholder="搜索商家或地点" @focus="focus" @blur="blur" @input="input"/> <button class="el-button el-button--primary"><i class="el-icon-search"/></button> <dl v-if="isHotPlace" class="hotPlace"> <dt>热门搜索</dt> <dd v-for="(item,idx) in $store.state.home.hotPlace.slice(0,5)" :key="idx"> <a :href="'/products?keyword='+encodeURIComponent(item.name)">{{ item.name }}</a> </dd> </dl> <dl v-if="isSearchList" class="searchList"> <dd v-for="(item,idx) in searchList" :key="idx"> <a :href="'/products?keyword='+encodeURIComponent(item.name)">{{ item.name }}</a> </dd> </dl> </div> <p class="suggest"> <a v-for="(item,idx) in $store.state.home.hotPlace.slice(0,5)" :key="idx" :href="'/products?keyword='+encodeURIComponent(item.name)">{{ item.name }}</a> </p> <ul class="nav"> <li><nuxt-link to="/" class="takeout">美团外卖</nuxt-link></li> <li><nuxt-link to="/" class="movie">猫眼电影</nuxt-link></li> <li><nuxt-link to="/" class="hotel">美团酒店</nuxt-link></li> <li><nuxt-link to="/" class="apartment">民宿/公寓</nuxt-link></li> <li><nuxt-link to="/" class="business">商家入驻</nuxt-link></li> </ul> </el-col> <el-col :span="6" class="right"> <ul class="security"> <li><i class="refund"/><p class="txt">随时退</p></li> <li><i class="single"/><p class="txt">不满意免单</p></li> <li><i class="overdue"/><p class="txt">过期退</p></li> </ul> </el-col> </el-row> </div> </template>
景点详情页
pages-->新建products.vue组件,
crumbs.vue面包屑,categroy.vue分类组件,list.vue列表组件(iselect.vue弹层),map.vue组件都在该父组件引入
<template> <el-row class="page-product"> <el-col :span="19"> <crumbs :keyword="keyword"/> <categroy :types="types" :areas="areas"/> <list :list="list"/> </el-col> <el-col :span="5"> <amap v-if="point.length" :width="230" :height="290" :point="point"/> </el-col> </el-row> </template> <script> import Crumbs from '@/components/products/crumbs.vue' import Categroy from '@/components/products/categroy.vue' import List from '@/components/products/list.vue' import Amap from '@/components/public/map.vue' export default { components:{ Crumbs, Categroy, List, Amap }, data(){ return { list:[], types:[], areas:[], keyword:'', point:[] } }, // 通过ssr获取数据 async asyncData(ctx){ // 获取搜索的关键字 let keyword = ctx.query.keyword let city = ctx.store.state.geo.position.city let {status,data:{count,pois}} = await ctx.$axios.get('/search/resultsByKeywords',{ params:{ keyword, city } }) let {status:status2,data:{areas,types}} = await ctx.$axios.get('/categroy/crumbs',{ params:{ city } }) if(status===200&&count>0&&status2===200){ return { list: pois.filter(item=>item.photos.length).map(item=>{ return { type: item.type, img: item.photos[0].url, name: item.name, comment: Math.floor(Math.random()*10000), rate: Number(item.biz_ext.rating), price: Number(item.biz_ext.cost), scene: item.tag, tel: item.tel, status: '可订明日', // 经纬度 location: item.location, module: item.type.split(';')[0] } }), keyword, areas: areas.filter(item=>item.type!=='').slice(0,5), types: types.filter(item=>item.type!=='').slice(0,5), point: (pois.find(item=>item.location).location||'').split(',') } } } } </script> <style lang="scss"> @import "@/assets/css/products/index.scss"; </style>
crumbs.vue面包屑,element-ui的面包屑组件,keyword需要解码
<template> <div class="m-crumbs"> <el-breadcrumb separator=">"> <el-breadcrumb-item :to="{ path: '/' }">{{ $store.state.geo.position.city.replace('市','') }}美团</el-breadcrumb-item> <el-breadcrumb-item><a href="/">{{ $store.state.geo.position.city.replace('市','') }}{{ decodeURIComponent(keyword) }}</a></el-breadcrumb-item> </el-breadcrumb> </div> </template> <script> export default { props: { keyword: { type: String, default: '' } } } </script>
categroy.vue分类组件
<template> <div class="m-product-categroy"> <dl class="classic"> <dt>分类</dt> <dt>全部</dt> <dd v-for="(item,idx) in types" :key="idx"> <iselect :name="item.type" :list="item.module"/> </dd> </dl> <dl class="classic"> <dt>分类</dt> <dt>全部</dt> <dd v-for="(item,idx) in areas" :key="idx"> <iselect :name="item.type" :list="item.module"/> </dd> </dl> </div> </template> <script> import iselect from './iselect.vue' export default { components: { iselect }, props: { types: { type: Array, default(){ return [] } }, areas: { type:Array, default(){ return [] } } }, } </script> <style lang="scss"> .m-product-categroy{ padding: 15px 20px 0; background: #FFF; border: 1px solid #E5E5E5; border-radius: 4px; color: #333; font-size: 14px; font-weight: 500; line-height: 20px; } .classic{ display: flex; padding-bottom: 10px; >dt{ width: 80px; &:nth-child(2){ border-radius: 100px; background: #13D1BE; color: #FFF; height: 22px; line-height: 22px; width: 40px; box-sizing: border-box; margin-right: 40px; text-align: center; } } &:nth-child(1){ dd{ border-bottom: 1px solid #ddd; } } } </style>
iselect.vue组件弹层
<template> <div class="m-product-select"> <dl class="tab"> <dt>{{ name }}<i class="el-icon-arrow-down el-icon--right"/></dt> <dd> <h3>{{ name }}</h3> <span v-for="(item,idx) in list" :key="idx">{{ item }}</span> </dd> </dl> </div> </template> <script> export default { props: { name: { type:String, default:'' }, list: { type: Array, default(){ return [] } } } } </script>
list.vue列表
<template> <div class="m-products-list"> <dl> <dd v-for="item in nav" :key="item.name" :class="[item.name,item.acitve?'s-nav-active':'']" @click="navSelect" >{{ item.txt }}</dd> </dl> <ul> <Item v-for="(item,idx) in list" :key="idx" :meta="item"/> </ul> </div> </template> <script> import Item from './product.vue' export default { components: { Item }, props: { list: { type:Array, default(){ return [] } } }, data() { return { nav: [ { name: 's-default', txt: '智能排序', acitve: true }, { name: 's-price', txt: '价格最低', active: false }, { name: 's-visit', txt: '人气最高', active: false }, { name: 's-comment', txt: '评价最高', active: false } ] } }, async asyncData({app}) { let { data } = await app.$axios.get('searchList') return { items: data.list } }, methods: { navSelect: function () { console.log('select') } } } </script>
item.vue组件,element-ui的el-tare评分组件
<template> <dl class="s-item"> <dt> <img :src="meta.img" alt="商品图片"> </dt> <dd> <h3><nuxt-link :to="{path:'detail',query:{keyword:meta.name,type:meta.module}}">{{ meta.name }}</nuxt-link></h3> <el-rate v-model="meta.rate" :colors="['#ff9900', '#ff9900', '#FF9900']" disabled/> <span v-if="meta.rate>4" class="s-item-comment">很好</span><span v-else-if="meta.rate>3" class="s-item-comment">一般</span><span v-else class="s-item-comment">很差</span> <span class="s-item-value">{{ meta.rate }}分</span> <span class="s-item-comment-total">{{ meta.comment }}人评论</span> <p> <span class="s-item-type">{{ meta.type }}</span> <span class="s-item-addr">{{ meta.addr }}</span> </p> <p> <em class="s-item-price">¥{{ meta.price }}起</em> <b>{{ meta.status }}</b> </p> <ul> <!-- <li> <span class="detail-type">门票</span>{{meta.ticket}} </li> <li> <span class="detail-type">跟团</span>{{meta.group}} </li> --> <li v-if="meta.scene&&meta.scene.length"> <span class="detail-type">景酒</span>{{ meta.scene }} </li> <li v-else> <span class="detail-type">景酒</span>暂无描述 </li> </ul> </dd> </dl> </template> <script> export default { props: { meta: { type:Object, default(){ return {} } } } } </script>
map.vue地图组件,需要用到高德的第三方平台的API
<template> <div :id="id" :style="{width:width+'px',height:height+'px',margin:'34px auto'}" class="m-map"/> </template> <script> export default { props: { width: { type:Number, default:300 }, height: { type:Number, default:300 }, point: { type:Array, default(){ return [116.46,39.92] } } }, data() { return { id: `map`, key: '0dbc0dfd7c775f2a927174493eab8220' } },
//监视经纬度的变化 watch: { point: function (val, old) { this.map.setCenter(val) this.marker.setPosition(val) } }, mounted() { let self = this // 动态id self.id = `map${Math.random().toString().slice(4, 6)}` // 高德开放平台,调用地图组件 window.onmaploaded = () => { let map = new window.AMap.Map(self.id, { resizeEnable: true, zoom: 11, center: self.point }) self.map = map window.AMap.plugin('AMap.ToolBar', () => { let toolbar = new window.AMap.ToolBar() map.addControl(toolbar) let marker = new window.AMap.Marker({ icon: 'https://webapi.amap.com/theme/v1.3/markers/n/mark_b.png', position: self.point }) self.marker = marker marker.setMap(map) }) } const url = `https://webapi.amap.com/maps?v=1.4.10&key=${self.key}&callback=onmaploaded` let jsapi = document.createElement('script') jsapi.charset = 'utf-8' jsapi.src = url document.head.appendChild(jsapi) }, } </script>