【vue前端项目实战案例】之Vue仿饿了么App

本文将介绍一款仿“饿了么”商家页面的App。该案例是基于 Vue2.0 + Vue Router + webpack + ES6
等技术栈实现的一款外卖类App,适合初学者进行学习。

项目源码下载链接在文章末尾

1 项目概述

该项目是一款仿“饿了么”商家页面的外卖类App,主要有以下功能。

  • 商品导航。
  • 商品列表使用手势上下滑动。
  • 购物车中商品的添加和删除操作。
  • 点击商品查看详情。
  • 商家评价。
  • 商家信息。

1.1 开发环境

首先需要安装Node.js 12以上的版本,因为Node.js中已经继承了NPM,所以无需在单独安装NPM。然后再安装Vue脚手架(Vue-CLI)以及创建项目。
项目的调试使用Google Chrome浏览器的控制台进行,在浏览器中按下F12键,然后单击“切换设备工具栏”,进入移动端的调试界面,可以选择相应的设备进行调试,效果如图1 所示。
在这里插入图片描述
图 1 项目效果图

1.2 项目结构

项目结构如图2所示,其中src文件夹是项目的源文件目录,src文件夹下的项目结构如图3所示。
在这里插入图片描述
图2 项目结构

在这里插入图片描述
图3 src文件夹

项目结构中主要文件说明如下。

  • dist:项目打包后的静态文件存放目录。
  • node_modules:项目依赖管理目录。
  • public:项目的静态文件存放目录,也是本地服务器的根目录。
  • src:项目源文件存放目录。
  • package.json:项目npm配置文件。

src文件夹目录说明如下。

  • assets:静态资源文件存放目。
  • components:公共组件存放目录。
  • router:路由配置文件存放目录。
  • store:状态管理配置存放目录。
  • views:视图组件存放目录。
  • App.vue:项目的根组件。
  • main.js:项目的入口文件。

2 入口文件

项目的入口文件有 index.html、main.js和App.vue三个文件,这些入口文件的具体内容介绍如下。

2.1 项目入口页面

index.html是项目默认的主渲染页面文件,主要用于Vue实例挂载点的声明与DOM渲染。代码如下:

<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1.0"><link rel="icon" href="<%= BASE_URL %>favicon.ico"><title><%= htmlWebpackPlugin.options.title %></title></head><body><div id="app"></div></body>
</html>

2.2 程序入口文件

main.js是程序的入口文件,主要用于加载各种公共组件和初始化Vue实例。本项目中的路由设置和引用的Vant UI组件库就是在该文件中定义的。代码如下:

import Vue from 'vue'
import App from './App.vue'
import './cube-ui'
import './register'import 'common/stylus/index.styl'Vue.config.productionTip = falsenew Vue({render: h => h(App)
}).$mount('#app')

本项目案例使用了 Cube UI 组件库,在项目src目录下创建 cube-ui.js 文件,用于引入项目中要用到的组件,代码如下:

import Vue from 'vue'
import {Style,TabBar,Popup,Dialog,Scroll,Slide,ScrollNav,ScrollNavBar
} from 'cube-ui'Vue.use(TabBar)
Vue.use(Popup)
Vue.use(Dialog)
Vue.use(Scroll)
Vue.use(Slide)
Vue.use(ScrollNav)
Vue.use(ScrollNavBar)

2.3 组件入口文件

App.vue是项目的根组件,所有的页面都是在App.vue下面切换的,所有的页面组件都是App.vue的子组件。在App.vue组件内只需要使用 组件作为占位符,就可以实现各个页面的引入。代码如下:

<template><div id="app" @touchmove.prevent><v-header :seller="seller"></v-header><div class="tab-wrapper"><tab :tabs="tabs"></tab></div></div>
</template><script>import qs from 'query-string'import { getSeller } from 'api'import VHeader from 'components/v-header/v-header'import Goods from 'components/goods/goods'import Ratings from 'components/ratings/ratings'import Seller from 'components/seller/seller'import Tab from 'components/tab/tab'export default {data() {return {seller: {id: qs.parse(location.search).id}}},computed: {tabs() {return [{label: '商品',component: Goods,data: {seller: this.seller}},{label: '评论',component: Ratings,data: {seller: this.seller}},{label: '商家',component: Seller,data: {seller: this.seller}}]}},created() {this._getSeller()},methods: {_getSeller() {getSeller({id: this.seller.id}).then((seller) => {this.seller = Object.assign({}, this.seller, seller)})}},components: {Tab,VHeader}}
</script><style lang="stylus" scoped>#app.tab-wrapperposition: fixedtop: 136pxleft: 0right: 0bottom: 0
</style>

3 项目组件

项目中所有页面组件都在views文件夹中定义,具体组件内容介绍如下。

3.1 头部组件

头部组件主要展示商家的基本信息,如图4所示。
在这里插入图片描述
图 4 头部组件效果

代码如下:

<template><div class="header" @click="showDetail"><div class="content-wrapper"><div class="avatar"><img width="64" height="64" :src="seller.avatar"></div><div class="content"><div class="title"><span class="brand"></span><span class="name">{{seller.name}}</span></div><div class="description">{{seller.description}}/{{seller.deliveryTime}}分钟送达</div><div v-if="seller.supports" class="support"><support-ico :size=1 :type="seller.supports[0].type"></support-ico><span class="text">{{seller.supports[0].description}}</span></div></div><div v-if="seller.supports" class="support-count"><span class="count">{{seller.supports.length}}个</span><i class="icon-keyboard_arrow_right"></i></div></div><div class="bulletin-wrapper"><span class="bulletin-title"></span><span class="bulletin-text">{{seller.bulletin}}</span><i class="icon-keyboard_arrow_right"></i></div><div class="background"><img :src="seller.avatar" width="100%" height="100%"></div></div>
</template><script type="text/ecmascript-6">import SupportIco from 'components/support-ico/support-ico'export default {name: 'v-header',props: {seller: {type: Object,default() {return {}}}},methods: {showDetail() {this.headerDetailComp = this.headerDetailComp || this.$createHeaderDetail({$props: {seller: 'seller'}})this.headerDetailComp.show()}},components: {SupportIco}}
</script><style lang="stylus" rel="stylesheet/stylus">@import "~common/stylus/mixin"@import "~common/stylus/variable".headerposition: relativeoverflow: hiddencolor: $color-whitebackground: $color-background-ss.content-wrapperposition: relativedisplay: flexalign-items: centerpadding: 24px 12px 18px 24px.avatarflex: 0 0 64pxwidth: 64pxmargin-right: 16pximgborder-radius: 2px.contentflex: 1.titledisplay: flexalign-items: centermargin-bottom: 8px.brandwidth: 30pxheight: 18pxbg-image('brand')background-size: 30px 18pxbackground-repeat: no-repeat.namemargin-left: 6pxfont-size: $fontsize-largefont-weight: bold.descriptionmargin-bottom: 8pxline-height: 12pxfont-size: $fontsize-small.supportdisplay: flexalign-items: center.support-icomargin-right: 4px.textline-height: 12pxfont-size: $fontsize-small-s.support-countposition: absoluteright: 12pxbottom: 14pxdisplay: flexalign-items: centerpadding: 0 8pxheight: 24pxline-height: 24pxtext-align: centerborder-radius: 14pxbackground: $color-background-sss.countfont-size: $fontsize-small-s.icon-keyboard_arrow_rightmargin-left: 2pxline-height: 24pxfont-size: $fontsize-small-s.bulletin-wrapperposition: relativedisplay: flexalign-items: centerheight: 28pxline-height: 28pxpadding: 0 8pxbackground: $color-background-sss.bulletin-titleflex: 0 0 22pxwidth: 22pxheight: 12pxmargin-right: 4pxbg-image('bulletin')background-size: 22px 12pxbackground-repeat: no-repeat.bulletin-textflex: 1white-space: nowrapoverflow: hiddentext-overflow: ellipsisfont-size: $fontsize-small-s.icon-keyboard_arrow_rightflex: 0 0 10pxwidth: 10pxfont-size: $fontsize-small-s.backgroundposition: absolutetop: 0left: 0width: 100%height: 100%z-index: -1filter: blur(10px)
</style>

3.2 商品标签栏与侧边导航组件

在商家信息下方,通过商品标签栏实现商品、评价和商家信息的切换,在商品标签中,通过侧边导航实现对商品列表的滚动和分类展示等功能。效果如图5所示。

在这里插入图片描述
图 5 商品标签栏效果

代码如下:

<template><div class="tab"><cube-tab-bar:useTransition=false:showSlider=truev-model="selectedLabel":data="tabs"ref="tabBar"class="border-bottom-1px"></cube-tab-bar><div class="slide-wrapper"><cube-slide:loop=false:auto-play=false:show-dots=false:initial-index="index"ref="slide":options="slideOptions"@scroll="onScroll"@change="onChange"><cube-slide-item v-for="(tab,index) in tabs" :key="index"><component ref="component" :is="tab.component" :data="tab.data"></component></cube-slide-item></cube-slide></div></div>
</template><script>export default {name: 'tab',props: {tabs: {type: Array,default() {return []}},initialIndex: {type: Number,default: 0}},data() {return {index: this.initialIndex,slideOptions: {listenScroll: true,probeType: 3,directionLockThreshold: 0}}},computed: {selectedLabel: {get() {return this.tabs[this.index].label},set(newVal) {this.index = this.tabs.findIndex((value) => {return value.label === newVal})}}},mounted() {this.onChange(this.index)},methods: {onScroll(pos) {const tabBarWidth = this.$refs.tabBar.$el.clientWidthconst slideWidth = this.$refs.slide.slide.scrollerWidthconst transform = -pos.x / slideWidth * tabBarWidththis.$refs.tabBar.setSliderTransform(transform)},onChange(current) {this.index = currentconst instance = this.$refs.component[current]if (instance && instance.fetch) {instance.fetch()}}}}
</script><style lang="stylus" scoped>@import "~common/stylus/variable".tabdisplay: flexflex-direction: columnheight: 100%>>> .cube-tabpadding: 10px 0.slide-wrapperflex: 1overflow: hidden
</style>

3.3 购物车组件

在购物车组件中,当没有任何商品的情况下,无法直接选择,效果如图6所示。当选择商品后,购物车将被激活,效果如图7所示。
在这里插入图片描述
图 6 购物车默认状态

在这里插入图片描述
图 7 选择商品后的状态

当点击购物车图标后,将显示用户选中的商品,效果如图8所示,在购物车商品列表页面中可以对商品进行加减操作,也可以直接清空购物车。
在这里插入图片描述
图8 购物车商品列表

当点击“去结算”按钮时,将弹出购买商品花费的金额提示对话框,效果如图9所示。
在这里插入图片描述
图9 提示对话框

具体实现的代码如下。
商品购物车组件 shop-cart.vue 文件代码如下:

<template><div><div class="shopcart"><div class="content" @click="toggleList"><div class="content-left"><div class="logo-wrapper"><div class="logo" :class="{'highlight':totalCount>0}"><i class="icon-shopping_cart" :class="{'highlight':totalCount>0}"></i></div><div class="num" v-show="totalCount>0"><bubble :num="totalCount"></bubble></div></div><div class="price" :class="{'highlight':totalPrice>0}">¥{{totalPrice}}</div><div class="desc">另需配送费¥{{deliveryPrice}}元</div></div><div class="content-right" @click="pay"><div class="pay" :class="payClass">{{payDesc}}</div></div></div><div class="ball-container"><div v-for="(ball,index) in balls" :key="index"><transition@before-enter="beforeDrop"@enter="dropping"@after-enter="afterDrop"><div class="ball" v-show="ball.show"><div class="inner inner-hook"></div></div></transition></div></div></div></div>
</template><script>import Bubble from 'components/bubble/bubble'const BALL_LEN = 10const innerClsHook = 'inner-hook'function createBalls() {let balls = []for (let i = 0; i < BALL_LEN; i++) {balls.push({show: false})}return balls}export default {name: 'shop-cart',props: {selectFoods: {type: Array,default() {return []}},deliveryPrice: {type: Number,default: 0},minPrice: {type: Number,default: 0},sticky: {type: Boolean,default: false},fold: {type: Boolean,default: true}},data() {return {balls: createBalls(),listFold: this.fold}},created() {this.dropBalls = []},computed: {totalPrice() {let total = 0this.selectFoods.forEach((food) => {total += food.price * food.count})return total},totalCount() {let count = 0this.selectFoods.forEach((food) => {count += food.count})return count},payDesc() {if (this.totalPrice === 0) {return `${this.minPrice}元起送`} else if (this.totalPrice < this.minPrice) {let diff = this.minPrice - this.totalPricereturn `还差¥${diff}元起送`} else {return '去结算'}},payClass() {if (!this.totalCount || this.totalPrice < this.minPrice) {return 'not-enough'} else {return 'enough'}}},methods: {toggleList() {if (this.listFold) {if (!this.totalCount) {return}this.listFold = falsethis._showShopCartList()this._showShopCartSticky()} else {this.listFold = truethis._hideShopCartList()}},pay(e) {if (this.totalPrice < this.minPrice) {return}this.$createDialog({title: '支付',content: `您需要支付${this.totalPrice}`}).show()e.stopPropagation()},drop(el) {for (let i = 0; i < this.balls.length; i++) {const ball = this.balls[i]if (!ball.show) {ball.show = trueball.el = elthis.dropBalls.push(ball)return}}},beforeDrop(el) {const ball = this.dropBalls[this.dropBalls.length - 1]const rect = ball.el.getBoundingClientRect()const x = rect.left - 32const y = -(window.innerHeight - rect.top - 22)el.style.display = ''el.style.transform = el.style.webkitTransform = `translate3d(0,${y}px,0)`const inner = el.getElementsByClassName(innerClsHook)[0]inner.style.transform = inner.style.webkitTransform = `translate3d(${x}px,0,0)`},dropping(el, done) {this._reflow = document.body.offsetHeightel.style.transform = el.style.webkitTransform = `translate3d(0,0,0)`const inner = el.getElementsByClassName(innerClsHook)[0]inner.style.transform = inner.style.webkitTransform = `translate3d(0,0,0)`el.addEventListener('transitionend', done)},afterDrop(el) {const ball = this.dropBalls.shift()if (ball) {ball.show = falseel.style.display = 'none'}},_showShopCartList() {this.shopCartListComp = this.shopCartListComp || this.$createShopCartList({$props: {selectFoods: 'selectFoods'},$events: {leave: () => {this._hideShopCartSticky()},hide: () => {this.listFold = true},add: (el) => {this.shopCartStickyComp.drop(el)}}})this.shopCartListComp.show()},_showShopCartSticky() {this.shopCartStickyComp = this.shopCartStickyComp || this.$createShopCartSticky({$props: {selectFoods: 'selectFoods',deliveryPrice: 'deliveryPrice',minPrice: 'minPrice',fold: 'listFold',list: this.shopCartListComp}})this.shopCartStickyComp.show()},_hideShopCartList() {const list = this.sticky ? this.$parent.list : this.shopCartListComplist.hide && list.hide()},_hideShopCartSticky() {this.shopCartStickyComp.hide()}},watch: {fold(newVal) {this.listFold = newVal},totalCount(count) {if (!this.fold && count === 0) {this._hideShopCartList()}}},components: {Bubble}}
</script><style lang="stylus" scoped>@import "~common/stylus/mixin"@import "~common/stylus/variable".shopcartheight: 100%.contentdisplay: flexbackground: $color-backgroundfont-size: 0color: $color-light-grey.content-leftflex: 1.logo-wrapperdisplay: inline-blockvertical-align: topposition: relativetop: -10pxmargin: 0 12pxpadding: 6pxwidth: 56pxheight: 56pxbox-sizing: border-boxborder-radius: 50%background: $color-background.logowidth: 100%height: 100%border-radius: 50%text-align: centerbackground: $color-dark-grey&.highlightbackground: $color-blue.icon-shopping_cartline-height: 44pxfont-size: $fontsize-large-xxxcolor: $color-light-grey&.highlightcolor: $color-white.numposition: absolutetop: 0right: 0.pricedisplay: inline-blockvertical-align: topmargin-top: 12pxline-height: 24pxpadding-right: 12pxbox-sizing: border-boxborder-right: 1px solid rgba(255, 255, 255, 0.1)font-weight: 700font-size: $fontsize-large&.highlightcolor: $color-white.descdisplay: inline-blockvertical-align: topmargin: 12px 0 0 12pxline-height: 24pxfont-size: $fontsize-small-s.content-rightflex: 0 0 105pxwidth: 105px.payheight: 48pxline-height: 48pxtext-align: centerfont-weight: 700font-size: $fontsize-small&.not-enoughbackground: $color-dark-grey&.enoughbackground: $color-greencolor: $color-white.ball-container.ballposition: fixedleft: 32pxbottom: 22pxz-index: 200transition: all 0.4s cubic-bezier(0.49, -0.29, 0.75, 0.41).innerwidth: 16pxheight: 16pxborder-radius: 50%background: $color-bluetransition: all 0.4s linear
</style>

商品购物车列表组件 shop-cart-list.vue 文件代码如下:

<template><transition name="fade"><cube-popup:mask-closable=truev-show="visible"@mask-click="maskClick"position="bottom"type="shop-cart-list":z-index=90><transitionname="move"@after-leave="afterLeave"><div v-show="visible"><div class="list-header"><h1 class="title">购物车</h1><span class="empty" @click="empty">清空</span></div><cube-scroll class="list-content" ref="listContent"><ul><liclass="food"v-for="(food,index) in selectFoods":key="index"><span class="name">{{food.name}}</span><div class="price"><span>¥{{food.price*food.count}}</span></div><div class="cart-control-wrapper"><cart-control @add="onAdd" :food="food"></cart-control></div></li></ul></cube-scroll></div></transition></cube-popup></transition>
</template><script>import CartControl from 'components/cart-control/cart-control'import popupMixin from 'common/mixins/popup'const EVENT_SHOW = 'show'const EVENT_ADD = 'add'const EVENT_LEAVE = 'leave'export default {name: 'shop-cart-list',mixins: [popupMixin],props: {selectFoods: {type: Array,default() {return []}}},created() {this.$on(EVENT_SHOW, () => {this.$nextTick(() => {this.$refs.listContent.refresh()})})},methods: {onAdd(target) {this.$emit(EVENT_ADD, target)},afterLeave() {this.$emit(EVENT_LEAVE)},maskClick() {this.hide()},empty() {this.dialogComp = this.$createDialog({type: 'confirm',content: '清空购物车?',$events: {confirm: () => {this.selectFoods.forEach((food) => {food.count = 0})this.hide()}}})this.dialogComp.show()}},components: {CartControl}}
</script><style lang="stylus" scoped>@import "~common/stylus/variable".cube-shop-cart-listbottom: 48px&.fade-enter, &.fade-leave-activeopacity: 0&.fade-enter-active, &.fade-leave-activetransition: all .3s ease-in-out.move-enter, .move-leave-activetransform: translate3d(0, 100%, 0).move-enter-active, .move-leave-activetransition: all .3s ease-in-out.list-headerheight: 40pxline-height: 40pxpadding: 0 18pxbackground: $color-background-ssss.titlefloat: leftfont-size: $fontsize-mediumcolor: $color-dark-grey.emptyfloat: rightfont-size: $fontsize-smallcolor: $color-blue.list-contentpadding: 0 18pxmax-height: 217pxoverflow: hiddenbackground: $color-white.foodposition: relativepadding: 12px 0box-sizing: border-box.nameline-height: 24pxfont-size: $fontsize-mediumcolor: $color-dark-grey.priceposition: absoluteright: 90pxbottom: 12pxline-height: 24pxfont-weight: 700font-size: $fontsize-mediumcolor: $color-red.cart-control-wrapperposition: absoluteright: 0bottom: 6px</style>

3.4 商品列表组件

在商品标签页面中,商品列表主要展示所有商品的信息,可以点击商品卡片右侧的加号添加购物车。效果如图10所示。
在这里插入图片描述
图 10 商品列表效果

代码如下:

<template><div class="goods"><div class="scroll-nav-wrapper"><cube-scroll-nav:side=true:data="goods":options="scrollOptions"v-if="goods.length"><template slot="bar" slot-scope="props"><cube-scroll-nav-bardirection="vertical":labels="props.labels":txts="barTxts":current="props.current"><template slot-scope="props"><div class="text"><support-icov-if="props.txt.type>=1":size=3:type="props.txt.type"></support-ico><span>{{props.txt.name}}</span><span class="num" v-if="props.txt.count"><bubble :num="props.txt.count"></bubble></span></div></template></cube-scroll-nav-bar></template><cube-scroll-nav-panelv-for="good in goods":key="good.name":label="good.name":title="good.name"><ul><li@click="selectFood(food)"v-for="food in good.foods":key="food.name"class="food-item"><div class="icon"><img width="57" height="57" :src="food.icon"></div><div class="content"><h2 class="name">{{food.name}}</h2><p class="desc">{{food.description}}</p><div class="extra"><span class="count">月售{{food.sellCount}}份</span><span>好评率{{food.rating}}%</span></div><div class="price"><span class="now">¥{{food.price}}</span><span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span></div><div class="cart-control-wrapper"><cart-control @add="onAdd" :food="food"></cart-control></div></div></li></ul></cube-scroll-nav-panel></cube-scroll-nav></div><div class="shop-cart-wrapper"><shop-cartref="shopCart":select-foods="selectFoods":delivery-price="seller.deliveryPrice":min-price="seller.minPrice"></shop-cart></div></div>
</template><script>import { getGoods } from 'api'import CartControl from 'components/cart-control/cart-control'import ShopCart from 'components/shop-cart/shop-cart'import Food from 'components/food/food'import SupportIco from 'components/support-ico/support-ico'import Bubble from 'components/bubble/bubble'export default {name: 'goods',props: {data: {type: Object,default() {return {}}}},data() {return {goods: [],selectedFood: {},scrollOptions: {click: false,directionLockThreshold: 0}}},computed: {seller() {return this.data.seller},selectFoods() {let foods = []this.goods.forEach((good) => {good.foods.forEach((food) => {if (food.count) {foods.push(food)}})})return foods},barTxts() {let ret = []this.goods.forEach((good) => {const {type, name, foods} = goodlet count = 0foods.forEach((food) => {count += food.count || 0})ret.push({type,name,count})})return ret}},methods: {fetch() {if (!this.fetched) {this.fetched = truegetGoods({id: this.seller.id}).then((goods) => {this.goods = goods})}},selectFood(food) {this.selectedFood = foodthis._showFood()this._showShopCartSticky()},onAdd(target) {this.$refs.shopCart.drop(target)},_showFood() {this.foodComp = this.foodComp || this.$createFood({$props: {food: 'selectedFood'},$events: {add: (target) => {this.shopCartStickyComp.drop(target)},leave: () => {this._hideShopCartSticky()}}})this.foodComp.show()},_showShopCartSticky() {this.shopCartStickyComp = this.shopCartStickyComp || this.$createShopCartSticky({$props: {selectFoods: 'selectFoods',deliveryPrice: this.seller.deliveryPrice,minPrice: this.seller.minPrice,fold: true}})this.shopCartStickyComp.show()},_hideShopCartSticky() {this.shopCartStickyComp.hide()}},components: {Bubble,SupportIco,CartControl,ShopCart,Food}}
</script><style lang="stylus" scoped>@import "~common/stylus/mixin"@import "~common/stylus/variable".goodsposition: relativetext-align: leftheight: 100%.scroll-nav-wrapperposition: absolutewidth: 100%top: 0left: 0bottom: 48px>>> .cube-scroll-nav-barwidth: 80pxwhite-space: normaloverflow: hidden>>> .cube-scroll-nav-bar-itempadding: 0 10pxdisplay: flexalign-items: centerheight: 56pxline-height: 14pxfont-size: $fontsize-smallbackground: $color-background-ssss.textflex: 1position: relative.numposition: absoluteright: -8pxtop: -10px.support-icodisplay: inline-blockvertical-align: topmargin-right: 4px>>> .cube-scroll-nav-bar-item_activebackground: $color-whitecolor: $color-dark-grey>>> .cube-scroll-nav-panel-titlepadding-left: 14pxheight: 26pxline-height: 26pxborder-left: 2px solid $color-col-linefont-size: $fontsize-smallcolor: $color-greybackground: $color-background-ssss.food-itemdisplay: flexmargin: 18pxpadding-bottom: 18pxposition: relative&:last-childborder-none()margin-bottom: 0.iconflex: 0 0 57pxmargin-right: 10pximgheight: auto.contentflex: 1.namemargin: 2px 0 8px 0height: 14pxline-height: 14pxfont-size: $fontsize-mediumcolor: $color-dark-grey.desc, .extraline-height: 10pxfont-size: $fontsize-small-scolor: $color-light-grey.descline-height: 12pxmargin-bottom: 8px.extra.countmargin-right: 12px.pricefont-weight: 700line-height: 24px.nowmargin-right: 8pxfont-size: $fontsize-mediumcolor: $color-red.oldtext-decoration: line-throughfont-size: $fontsize-small-scolor: $color-light-grey.cart-control-wrapperposition: absoluteright: 0bottom: 12px.shop-cart-wrapperposition: absoluteleft: 0bottom: 0z-index: 50width: 100%height: 48px
</style>

3.5 商家公告组件

点击头部区域,会弹出商家公告的详细内容,效果如图11所示。
在这里插入图片描述
图11 商家公告内容

代码如下:

<template><transition name="fade"><div v-show="visible" class="header-detail" @touchmove.stop.prevent><div class="detail-wrapper clear-fix"><div class="detail-main"><h1 class="name">{{seller.name}}</h1><div class="star-wrapper"><star :size="48" :score="seller.score"></star></div><div class="title"><div class="line"></div><div class="text">优惠信息</div><div class="line"></div></div><ul v-if="seller.supports" class="supports"><li class="support-item" v-for="(item,index) in seller.supports" :key="item.id"><support-ico :size=2 :type="seller.supports[index].type"></support-ico><span class="text">{{seller.supports[index].description}}</span></li></ul><div class="title"><div class="line"></div><div class="text">商家公告</div><div class="line"></div></div><div class="bulletin"><p class="content">{{seller.bulletin}}</p></div></div></div><div class="detail-close" @click="hide"><i class="icon-close"></i></div></div></transition>
</template><script>import popupMixin from 'common/mixins/popup'import Star from 'components/star/star'import SupportIco from 'components/support-ico/support-ico'export default {name: 'header-detail',mixins: [popupMixin],props: {seller: {type: Object,default() {return {}}}},components: {SupportIco,Star}}
</script><style lang="stylus" scoped>@import "~common/stylus/mixin"@import "~common/stylus/variable".header-detailposition: fixedz-index: 100top: 0left: 0width: 100%height: 100%overflow: autobackdrop-filter: blur(10px)opacity: 1color: $color-whitebackground: $color-background-s&.fade-enter-active, &.fade-leave-activetransition: all 0.5s&.fade-enter, &.fade-leave-activeopacity: 0background: $color-background.detail-wrapperdisplay: inline-blockwidth: 100%min-height: 100%.detail-mainmargin-top: 64pxpadding-bottom: 64px.nameline-height: 16pxtext-align: centerfont-size: $fontsize-largefont-weight: 700.star-wrappermargin-top: 18pxpadding: 2px 0text-align: center.titledisplay: flexwidth: 80%margin: 28px auto 24px auto.lineflex: 1position: relativetop: -6pxborder-bottom: 1px solid rgba(255, 255, 255, 0.2).textpadding: 0 12pxfont-weight: 700font-size: $fontsize-medium.supportswidth: 80%margin: 0 auto.support-itemdisplay: flexalign-items: centerpadding: 0 12pxmargin-bottom: 12px&:last-childmargin-bottom: 0.support-icomargin-right: 6px.textline-height: 16pxfont-size: $fontsize-small.bulletinwidth: 80%margin: 0 auto.contentpadding: 0 12pxline-height: 24pxfont-size: $fontsize-small.detail-closeposition: relativewidth: 30pxheight: 30pxmargin: -64px auto 0 autoclear: bothfont-size: $fontsize-large-xxxx
</style>

3.6 评价内容组件

在商家评价内容的组件中,共有两个组成部分,一个是商家的评分组件,效果如图12所示;另一个是评价列表内容,效果如图13所示。
在这里插入图片描述
图 12 评分组件效果

在这里插入图片描述
图 13 评价列表效果

商家评分组件 ratings.vue 文件代码如下:

<template><cube-scroll ref="scroll" class="ratings" :options="scrollOptions"><div class="ratings-content"><div class="overview"><div class="overview-left"><h1 class="score">{{seller.score}}</h1><div class="title">综合评分</div><div class="rank">高于周边商家{{seller.rankRate}}%</div></div><div class="overview-right"><div class="score-wrapper"><span class="title">服务态度</span><star :size="36" :score="seller.serviceScore"></star><span class="score">{{seller.serviceScore}}</span></div><div class="score-wrapper"><span class="title">商品评分</span><star :size="36" :score="seller.foodScore"></star><span class="score">{{seller.foodScore}}</span></div><div class="delivery-wrapper"><span class="title">送达时间</span><span class="delivery">{{seller.deliveryTime}}分钟</span></div></div></div><split></split><rating-select@select="onSelect"@toggle="onToggle":selectType="selectType":onlyContent="onlyContent":ratings="ratings"></rating-select><div class="rating-wrapper"><ul><liv-for="(rating,index) in computedRatings":key="index"class="rating-item border-bottom-1px"><div class="avatar"><img width="28" height="28" :src="rating.avatar"></div><div class="content"><h1 class="name">{{rating.username}}</h1><div class="star-wrapper"><star :size="24" :score="rating.score"></star><span class="delivery" v-show="rating.deliveryTime">{{rating.deliveryTime}}</span></div><p class="text">{{rating.text}}</p><div class="recommend" v-show="rating.recommend && rating.recommend.length"><span class="icon-thumb_up"></span><spanclass="item"v-for="(item,index) in rating.recommend":key="index">{{item}}</span></div><div class="time">{{format(rating.rateTime)}}</div></div></li></ul></div></div></cube-scroll>
</template><script>import Star from 'components/star/star'import RatingSelect from 'components/rating-select/rating-select'import Split from 'components/split/split'import ratingMixin from 'common/mixins/rating'import { getRatings } from 'api'import moment from 'moment'export default {name: 'ratings',mixins: [ratingMixin],props: {data: {type: Object}},data () {return {ratings: [],scrollOptions: {click: false,directionLockThreshold: 0}}},computed: {seller () {return this.data.seller || {}}},methods: {fetch () {if (!this.fetched) {this.fetched = truegetRatings({id: this.seller.id}).then((ratings) => {this.ratings = ratings})}},format (time) {return moment(time).format('YYYY-MM-DD hh:mm')}},components: {Star,Split,RatingSelect},watch: {selectType () {this.$nextTick(() => {this.$refs.scroll.refresh()})}}}
</script><style lang="stylus" scoped>@import "~common/stylus/variable"@import "~common/stylus/mixin".ratingsposition: relativetext-align: leftwhite-space: normalheight: 100%.overviewdisplay: flexpadding: 18px 0.overview-leftflex: 0 0 137pxpadding: 6px 0width: 137pxborder-right: 1px solid $color-col-linetext-align: center@media only screen and (max-width: 320px)flex: 0 0 120pxwidth: 120px.scoremargin-bottom: 6pxline-height: 28pxfont-size: $fontsize-large-xxxcolor: $color-orange.titlemargin-bottom: 8pxline-height: 12pxfont-size: $fontsize-smallcolor: $color-dark-grey.rankline-height: 10pxfont-size: $fontsize-small-scolor: $color-light-grey.overview-rightflex: 1padding: 6px 0 6px 24px@media only screen and (max-width: 320px)padding-left: 6px.score-wrapperdisplay: flexalign-items: centermargin-bottom: 8px.titleline-height: 18pxfont-size: $fontsize-smallcolor: $color-dark-grey.starmargin: 0 12px.scoreline-height: 18pxfont-size: $fontsize-smallcolor: $color-orange.delivery-wrapperdisplay: flexalign-items: center.titleline-height: 18pxfont-size: $fontsize-smallcolor: $color-dark-grey.deliverymargin-left: 12pxfont-size: $fontsize-smallcolor: $color-light-grey.rating-wrapperpadding: 0 18px.rating-itemdisplay: flexpadding: 18px 0&:last-childborder-none().avatarflex: 0 0 28pxwidth: 28pxmargin-right: 12pximgheight: autoborder-radius: 50%.contentposition: relativeflex: 1.namemargin-bottom: 4pxline-height: 12pxfont-size: $fontsize-small-scolor: $color-dark-grey.star-wrappermargin-bottom: 6pxdisplay: flexalign-items: center.starmargin-right: 6px.deliveryfont-size: $fontsize-small-scolor: $color-light-grey.textmargin-bottom: 8pxline-height: 18pxcolor: $color-dark-greyfont-size: $fontsize-small.recommenddisplay: flexalign-items: centerflex-wrap: wrapline-height: 16px.icon-thumb_up, .itemmargin: 0 8px 4px 0font-size: $fontsize-small-s.icon-thumb_upcolor: $color-blue.itempadding: 0 6pxborder: 1px solid $color-row-lineborder-radius: 1pxcolor: $color-light-greybackground: $color-white.timeposition: absolutetop: 0right: 0line-height: 12pxfont-size: $fontsize-smallcolor: $color-light-grey
</style>

评价内容列表组件 rating-select.vue 文件代码如下:

<template><div class="rating-select"><div class="rating-type border-bottom-1px"><span @click="select(2)" class="block positive" :class="{'active':selectType===2}">{{desc.all}}<spanclass="count">{{ratings.length}}</span></span><span @click="select(0)" class="block positive" :class="{'active':selectType===0}">{{desc.positive}}<spanclass="count">{{positives.length}}</span></span><span @click="select(1)" class="block negative" :class="{'active':selectType===1}">{{desc.negative}}<spanclass="count">{{negatives.length}}</span></span></div><div @click="toggleContent" class="switch" :class="{'on':onlyContent}"><span class="icon-check_circle"></span><span class="text">只看有内容的评价</span></div></div>
</template>
<script>const POSITIVE = 0const NEGATIVE = 1const ALL = 2const EVENT_TOGGLE = 'toggle'const EVENT_SELECT = 'select'export default {props: {ratings: {type: Array,default() {return []}},selectType: {type: Number,default: ALL},onlyContent: {type: Boolean,default: false},desc: {type: Object,default() {return {all: '全部',positive: '满意',negative: '不满意'}}}},computed: {positives() {return this.ratings.filter((rating) => {return rating.rateType === POSITIVE})},negatives() {return this.ratings.filter((rating) => {return rating.rateType === NEGATIVE})}},methods: {select(type) {this.$emit(EVENT_SELECT, type)},toggleContent() {this.$emit(EVENT_TOGGLE)}}}
</script>
<style lang="stylus" rel="stylesheet/stylus">@import "~common/stylus/variable".rating-select.rating-typepadding: 18px 0margin: 0 18px.blockdisplay: inline-blockpadding: 8px 12pxmargin-right: 8pxline-height: 16pxborder-radius: 1pxfont-size: $fontsize-smallcolor: $color-grey&.activecolor: $color-white.countmargin-left: 2px&.positivebackground: $color-light-blue&.activebackground: $color-blue&.negativebackground: $color-light-grey-s&.activebackground: $color-grey.switchdisplay: flexalign-items: centerpadding: 12px 18pxline-height: 24pxborder-bottom: 1px solid $color-row-linecolor: $color-light-grey&.on.icon-check_circlecolor: $color-green.icon-check_circlemargin-right: 4pxfont-size: $fontsize-large-xxx.textfont-size: $fontsize-small
</style>

3.7 商家信息组件

商家信息组件中设计了商家的星级和服务内容,效果如图14所示。

在这里插入图片描述
图 14 商家服务信息效果

以及商家的优惠活动和公告内容。效果如图15所示。

在这里插入图片描述
图15 商家活动公告内容

代码如下:

<template><cube-scroll class="seller" :options="sellerScrollOptions"><div class="seller-content"><div class="overview"><h1 class="title">{{seller.name}}</h1><div class="desc border-bottom-1px"><star :size="36" :score="seller.score"></star><span class="text">({{seller.ratingCount}})</span><span class="text">月售{{seller.sellCount}}单</span></div><ul class="remark"><li class="block"><h2>起送价</h2><div class="content"><span class="stress">{{seller.minPrice}}</span></div></li><li class="block"><h2>商家配送</h2><div class="content"><span class="stress">{{seller.deliveryPrice}}</span></div></li><li class="block"><h2>平均配送时间</h2><div class="content"><span class="stress">{{seller.deliveryTime}}</span>分钟</div></li></ul><div class="favorite" @click="toggleFavorite"><span class="icon-favorite" :class="{'active':favorite}"></span><span class="text">{{favoriteText}}</span></div></div><split></split><div class="bulletin"><h1 class="title">公告与活动</h1><div class="content-wrapper border-bottom-1px"><p class="content">{{seller.bulletin}}</p></div><ul v-if="seller.supports" class="supports"><liclass="support-item border-bottom-1px"v-for="(item,index) in seller.supports":key="index"><support-ico :size=4 :type="seller.supports[index].type"></support-ico><span class="text">{{seller.supports[index].description}}</span></li></ul></div><split></split><div class="pics"><h1 class="title">商家实景</h1><cube-scroll class="pic-wrapper" :options="picScrollOptions"><ul class="pic-list"><li class="pic-item"v-for="(pic,index) in seller.pics":key="index"><img :src="pic" width="120" height="90"></li></ul></cube-scroll></div><split></split><div class="info"><h1 class="title border-bottom-1px">商家信息</h1><ul><liclass="info-item border-bottom-1px"v-for="(info,index) in seller.infos":key="index">{{info}}</li></ul></div></div></cube-scroll>
</template><script>import { saveToLocal, loadFromLocal } from 'common/js/storage'import Star from 'components/star/star'import Split from 'components/split/split'import SupportIco from 'components/support-ico/support-ico'export default {props: {data: {type: Object,default() {return {}}}},data() {return {favorite: false,sellerScrollOptions: {directionLockThreshold: 0,click: false},picScrollOptions: {scrollX: true,stopPropagation: true,directionLockThreshold: 0}}},computed: {seller() {return this.data.seller || {}},favoriteText() {return this.favorite ? '已收藏' : '收藏'}},created() {this.favorite = loadFromLocal(this.seller.id, 'favorite', false)},methods: {toggleFavorite() {this.favorite = !this.favoritesaveToLocal(this.seller.id, 'favorite', this.favorite)}},components: {SupportIco,Star,Split}}
</script><style lang="stylus" scoped>@import "~common/stylus/variable"@import "~common/stylus/mixin".sellerheight: 100%text-align: left.overviewposition: relativepadding: 18px.titlemargin-bottom: 8pxline-height: 14pxfont-size: $fontsize-mediumcolor: $color-dark-grey.descdisplay: flexalign-items: centerpadding-bottom: 18px.starmargin-right: 8px.textmargin-right: 12pxline-height: 18pxfont-size: $fontsize-small-scolor: $color-grey.remarkdisplay: flexpadding-top: 18px.blockflex: 1text-align: centerborder-right: 1px solid $color-col-line&:last-childborder: noneh2margin-bottom: 4pxline-height: 10pxfont-size: $fontsize-small-scolor: $color-light-grey.contentline-height: 24pxfont-size: $fontsize-small-scolor: $color-dark-grey.stressfont-size: $fontsize-large-xxx.favoriteposition: absolutewidth: 50pxright: 11pxtop: 18pxtext-align: center.icon-favoritedisplay: blockmargin-bottom: 4pxline-height: 24pxfont-size: $fontsize-large-xxxcolor: $color-light-grey-s&.activecolor: $color-red.textline-height: 10pxfont-size: $fontsize-small-scolor: $color-grey.bulletinpadding: 18px 18px 0 18pxwhite-space: normal.titlemargin-bottom: 8pxline-height: 14pxcolor: $color-dark-greyfont-size: $fontsize-medium.content-wrapperpadding: 0 12px 16px 12px.contentline-height: 24pxfont-size: $fontsize-smallcolor: $color-red.supports.support-itemdisplay: flexalign-items: centerpadding: 16px 12px&:last-childborder-none().support-icomargin-right: 6px.textline-height: 16pxfont-size: $fontsize-smallcolor: $color-dark-grey.picspadding: 18px.titlemargin-bottom: 12pxline-height: 14pxcolor: $color-dark-greyfont-size: $fontsize-medium.pic-wrapperdisplay: flexalign-items: center.pic-list.pic-itemdisplay: inline-blockmargin-right: 6pxwidth: 120pxheight: 90px&:last-childmargin: 0.infopadding: 18px 18px 0 18pxcolor: $color-dark-grey.titlepadding-bottom: 12pxline-height: 14pxfont-size: $fontsize-medium.info-itempadding: 16px 12pxline-height: 16pxfont-size: $fontsize-small&:last-childborder-none()
</style>

项目源码下载:
https://download.csdn.net/download/p445098355/89570496

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/385138.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

51单片机嵌入式开发:17、STC89C52的嵌入式 遥控器 控制步进电机 转速 和 转向 操作并 printf打印信息

51单片机嵌入式开发 STC89C52的嵌入式 遥控器 控制步进电机 转速 和 转向 操作并 printf打印信息 51单片机嵌入式开发STC89C52的嵌入式 遥控器 控制步进电机 转速 和 转向 操作并 printf打印信息1 概述2 硬件电路2.1 遥控器2.2 红外接收器电路2.3 STC89C52单片机电路2.4 数码管…

SpringBoot集成Sharding-JDBC实现分库分表

本文已收录于专栏 《中间件合集》 目录 版本介绍背景介绍拆分方式集成并测试1.引入依赖2.创建库和表3.pom文件配置4.编写测试类Entity层Mapper接口MapperXML文件测试类 5.运行结果 自定义分片规则定义分片类编写pom文件 总结提升 版本介绍 SpringBoot的版本是&#xff1a; 2.3.…

IDEA Maven使用HTTP代理,解决Could not transfer artifact org.xxx问题

文章目录 一、前言二、遇到问题三、分析问题四、HTTP代理五、重新编译验证 一、前言 遇到这个问题&#xff0c;有两种解决办法 IDEA Maven使用HTTP代理&#xff0c;解决Could not transfer artifact org.xxx问题IDEA Maven使用国内镜像&#xff0c;解决Could not transfer arti…

C语言分支语句之if的一些用法

目录 引言C语言结构 1. if 语句1.1 if1.2 else 2. 分支中包含多条语句3. 多重选择 else if4. 嵌套if5. 悬空else / else与if配对问题 引言 C语言作为一种非常常用的编程语言&#xff0c;具有灵活强大的循环和分支结构。循环结构允许我们重复执行一段代码&#xff0c;而分支结构…

【网络爬虫技术】(1·绪论)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;网络爬虫开发技术入门_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 …

本地部署Graphhopper路径规划服务(graphhopper.sh启动版)

文章目录 文章参考源码获取一、配置Java环境变量二、配置Maven环境变量三、构建graphhopper步骤1. 下载数据2. 配置graphhopper配置文件config-example.yml3. 在项目中启动命令行执行./graphhopper.sh build3.1|、遇到的问题3.1.1、pom.xml中front-maven-plugin-无法下载npm6.1…

跨境电商独立站:Shopify/Wordpress/店匠选哪个?

在面对不断增加的平台运营压力时&#xff0c;不少跨境电商的商家逐渐将注意力转向建立自己的独立站。据《中国跨境出口电商发展报告&#xff08;2022&#xff09;》所示&#xff0c;中国拥有的独立站数量在2022年已接近20万个&#xff0c;这表明独立站已成为卖家拓展海外市场的…

昇思25天学习打卡营第11天|xiaoyushao

今天分享ResNet50迁移学习。 在实际应用场景中&#xff0c;由于训练数据集不足&#xff0c;所以很少有人会从头开始训练整个网络。普遍的做法是&#xff0c;在一个非常大的基础数据集上训练得到一个预训练模型&#xff0c;然后使用该模型来初始化网络的权重参数或作为固定特征提…

苦学Opencv的第十一天:图像的形态学操作

Python OpenCV从入门到精通学习日记&#xff1a;图像的形态学操作 前言 图像形态学是图像处理中的一个重要分支&#xff0c;主要关注图像中物体的形状和结构。通过形态学操作&#xff0c;我们可以对图像进行有效的分析和处理&#xff0c;例如图像的腐蚀与膨胀、开运算与闭运算…

大模型学习笔记十四:Agent模型微调

文章目录 一、大模型需要Agent技术的原因二、Prompt Engineering可以实现Agent吗&#xff1f;&#xff08;1&#xff09;ReAct原理展示和代码&#xff08;2&#xff09;ModelScope&#xff08;3&#xff09;AutoGPT&#xff08;4&#xff09;ToolLLaMA 三、既然AutoGPT可以满足…

利用OSMnx求路网最短路径并可视化(二)

书接上回&#xff0c;为了增加多路径的可视化效果和坐标匹配最近点来实现最短路可视化&#xff0c;我们使用图形化工具matplotlib结合OSMnx的绘图功能来展示整个路网图&#xff0c;并特别高亮显示计算出的最短路径。 多起终点最短路路径并计算距离和时间 完整代码#运行环境 P…

C++——QT:保姆级教程,从下载到安装到用QT写出第一个程序

登录官网&#xff0c;在官网选择合适的qt版本进行下载 这里选择5.12.9版本 点击exe文件下载&#xff0c;因为服务器在国外&#xff0c;国内不支持&#xff0c;所以可以从我的网盘下载 链接: https://pan.baidu.com/s/1XMILFS1uHTenH3mH_VlPLw 提取码: 1567 --来自百度网盘超级…

Linux--网络基础

目录 1.计算机网络背景 2. 初识协议 2.1概念 2.2 协议分层 2.3OSI 七层模型 2.4TCP/IP 五层(或四层)模型 3.再识协议 3.1为什么要有 TCP/IP 协议&#xff1f; 3.2什么是 TCP/IP 协议&#xff1f; 3.3TCP/IP 协议与操作系统的关系(宏观上&#xff0c; 怎么实现的) 3.…

一键解锁:科研服务器性能匹配秘籍,选择性能精准匹配科研任务和计算需求的服务器

一键解锁&#xff1a;科研服务器性能匹配秘籍 HPC科研工作站服务器集群细分领域迷途小书童 专注于HPC科研服务器细分领域kyfwq001 &#x1f3af;在当今科技飞速发展的时代&#xff0c;科研工作对计算资源的需求日益增长&#x1f61c;。选择性能精准匹配科研任务和计算需求的服…

HarmonyOS和OpenHarmony区别联系

前言 相信我们在刚开始接触鸿蒙开发的时候经常看到HarmonyOS和OpenHarmony频繁的出现在文章和文档之中&#xff0c;那么这两个名词分别是什么意思&#xff0c;他们之间又有什么联系呢&#xff1f;本文将通过现有的文章和网站内容并与Google的AOSP和Android做对比&#xff0c;带…

助力樱桃智能自动化采摘,基于嵌入式端超轻量级模型LeYOLO全系列【n/s/m/l】参数模型开发构建果园种植采摘场景下樱桃成熟度智能检测识别系统

随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;技术已经渗透到我们生活的方方面面&#xff0c;从智能家居到自动驾驶&#xff0c;再到医疗健康&#xff0c;其影响力无处不在。然而&#xff0c;当我们把目光转向中国的农业领域时&#xff0c;一个令人惊讶的…

python-NLP:2词性标注与命名实体识别

文章目录 词性标注命名实体识别时间命名实体&#xff08;规则方法&#xff09;CRF 命名实体识别方法 词性标注 词性是词汇基本的语法属性&#xff0c;通常也称为词类。词性标注是在给定句子中判定每个词的语法范畴&#xff0c;确定其词性并加以标注的过程。例如&#xff0c;表示…

【React】详解“最新”和“最热”切换与排序

文章目录 一、基本概念和初始化二、切换与排序功能的实现1. 函数定义和参数2. 设置活动 Tab3. 定义新列表变量4. 根据排序类型处理列表4.1 按时间降序排序4.2 按点赞数降序排序 5. 更新评论列表 三、渲染导航 Tab 和评论列表1. map 方法2. key 属性3. className 动态赋值4. onC…

模式Hash和history

vuerouter有两种路由模式Hash和history。区别&#xff1a;Hash为默认模式&#xff0c;url中包含一个#符号的哈希部分。优势&#xff1a;兼容性好&#xff0c;不需要后端服务器的特殊配置。缺点&#xff1a;不够美观&#xff0c;搜索引擎优化较差。History模式使用的浏览器的His…

小程序多排数据横向滚动实现

如何实现多排数据滚动效果 swiper 外部容器 swiper-item 每一页的数据 因为现在有多排数据,现在在swiper-item 中需要循环一个数组 初版 <template><view><view class"container"><view class"swiper-box"><swiper class&qu…