小程序有哪些用_Vue实战教程之仿肯德基宅急送

Vue实战教程之仿肯德基宅急送App       这篇文章主要介绍了Vue实战教程之仿肯德基宅急送App,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下

Vue学习有一段时间了,就想着用Vue来写个项目练练手,弄了半个月,到今天为止也算勉强能看了。

由于不知道怎么拿手机App的接口,并且KFC电脑端官网真的...一言难尽,所以项目所有数据都是我截图然后写在EasyMock里的,有需要的同学可以自取

技术栈

vue + webpack + vuex + axios

文件目录

│ App.vue
│ main.js
├─assets
│ logo.png
├─components
│ │ cartcontrol.vue
│ │ code.vue
│ │ coupon.vue
│ │ mineHeader.vue
│ │ scroll.vue
│ │ shopHeader.vue
│ │ sidebar.vue
│ │ submitBar.vue
│ │ takeout.vue
│ │ wallet.vue
│ └─tabs
│ Other.vue
│ Outward.vue
│ Selfhelp.vue
│ Vgold.vue
├─pages
│ ├─home
│ │ home.vue
│ ├─mine
│ │ mine.vue
│ ├─order
│ │ order.vue
│ └─shop
│ shop.vue
├─router
│ index.js
└─vuex
 │ store.js
 │ types.js
 └─modules
 com.js
 cou.js
 take.js

效果展示

定义的组件

better-scroll

因为每个页面都需要滑动,所以一开始就把scroll组件封装好,之后使用的话引入一下就行了

 template 
 div ref="wrapper" 
 slot /slot 
 /div 
 /template 
 script 
import BScroll from 'better-scroll';
const DIRECTION_H = 'horizontal';
const DIRECTION_V = 'vertical';
export default {
 name: 'scroll',
 pro凡科抠图: {
 * 1 滚动的时候会派发scroll事件,会节流。
 * 2 滚动的时候实时派发scroll事件,不会节流。
 * 3 除了实时派发scroll事件,在swipe的情况下仍然能实时派发scroll事件
 probeType: {
 type: Number,
 default: 1
 * 点击列表是否派发click事件
 click: {
 type: Boolean,
 default: true
 * 是否开启横向滚动
 scrollX: {
 type: Boolean,
 default: false
 * 是否派发滚动事件
 listenScroll: {
 type: Boolean,
 default: false
 * 列表的数据
 data: {
 type: Array,
 default: null
 pullup: {
 type: Boolean,
 default: false
 pulldown: {
 type: Boolean,
 default: false
 beforeScroll: {
 type: Boolean,
 default: false
 * 当数据更新后,刷新scroll的延时。
 refreshDelay: {
 type: Number,
 default: 20
 direction: {
 type: String,
 default: DIRECTION_V
 methods: {
 _initScroll() {
 if(!this.$refs.wrapper) {
 return
 this.scroll = new BScroll(this.$refs.wrapper, {
 probeType: this.probeType,
 click: this.click,
 eventPassthrough: this.direction === DIRECTION_V DIRECTION_H : DIRECTION_V
 // 是否派发滚动事件
 if (this.listenScroll) {
 this.scroll.on('scroll', (pos) = {
 this.$emit('scroll', pos)
 // 是否派发滚动到底部事件,用于上拉加载
 if (this.pullup) {
 this.scroll.on('scrollEnd', () = {
 if (this.scroll.y = (this.scroll.maxScrollY + 50)) {
 this.$emit('scrollToEnd')
 // 是否派发顶部下拉事件,用于下拉刷新
 if (this.pulldown) {
 this.scroll.on('touchend', (pos) = {
 // 下拉动作
 if (pos.y 50) {
 this.$emit('pulldown')
 // 是否派发列表滚动开始的事件
 if (this.beforeScroll) {
 this.scroll.on('beforeScrollStart', () = {
 this.$emit('beforeScroll')
 disable() {
 // 代理better-scroll的disable方法
 this.scroll this.scroll.disable()
 enable() {
 // 代理better-scroll的enable方法
 this.scroll this.scroll.enable()
 refresh() {
 // 代理better-scroll的refresh方法
 this.scroll this.scroll.refresh()
 scrollTo() {
 // 代理better-scroll的scrollTo方法
 this.scroll this.scroll.scrollTo.apply(this.scroll, arguments)
 scrollToElement() {
 // 代理better-scroll的scrollToElement方法
 this.scroll this.scroll.scrollToElement.apply(this.scroll, arguments)
 mounted() {
 setTimeout(() = {
 this._initScroll()
 },20)
 watch: {
 data () {
 setTimeout(() = {
 this.refresh()
 },this.refreshDelay)
 /script 
 style 
 /style 

slot 插槽是一块模板,显示不显示,以及怎样显示由父组件来决定, 也就是把你想要滑动的区域插进去,剩下的内容都是官方文档定义好的,复制一遍就好了

固定头部

 

头部相对页面是固定的,这里我把头部都封装成了组件,在面引入头部,要滑动的部分放入上面定义好的scroll组件即可

侧边栏以及弹出框

起初我的想法是用router-link直接跳转,然后发现这样做页面会自带导航栏,于是我决定通过CSS动态绑定来实现它

 template 
 div 
 div : 
 div @click="backTo" 
 img src="../pages/mine/zuo.png" alt="" 
 /div 
 van-tree-select :height="850" :items="items" :main-active-index="mainActiveIndex" :active-id="activeId" @navclick="onNavClick" @itemclick="onItemClick"/ 
 /div 
 /div 
 /template 

样式用的是Vant UI组件,最外面绑定了一个动态样式showbar,然后把整体的初始位置设在屏幕之外,当传入参数为true时再回来,用Vuex管理它的状态

.sidebar-con {
 position: absolute;
 top: 0;
 left: -400px;
 transform: translateZ(0);
 opacity: 0;
 width: 100%;
 z-index: 1002;
 height: 100%;
 overflow: auto;
 transition: all 0.3s ease;
.showbar {
 transform: translateX(400px);
 opacity: 1;
}

Vuex状态管理

const state = {
 showSidebar: false
const mutations = {
 [types.COM_SHOW_SIDE_BAR] (state, status) {
 state.showSidebar = status
const actions = {
 setShowSidebar ({commit}, status) {
 commit(types.COM_SHOW_SIDE_BAR, status)
const getters = {
 showSidebar: state = state.showSidebar
}

puted属性,对象可以直接使用

computed: {
 ...mapGetters([
 'showSidebar'
 },

当需要显示的时候使用dispatch将参数传入 this.$store.dispatch('setShowSidebar', true)

整体代码

 template 
 div 
 div : 
 div @click="backTo" 
 img src="../pages/mine/zuo.png" alt="" 
 /div 
 van-tree-select :height="850" :items="items" :main-active-index="mainActiveIndex" :active-id="activeId" @navclick="onNavClick" @itemclick="onItemClick"/ 
 /div 
 /div 
 /template 
 script 
import { TreeSelect } from 'vant';
import { mapGetters } from 'vuex';
export default {
data() {
 return {
 // 左侧高亮元素的index
 mainActiveIndex: 0,
 // 被选中元素的id
 activeId: 1
 computed: {
 ...mapGetters([
 'showSidebar'
 methods: {
 onNavClick(index) {
 this.mainActiveIndex = index;
 onItemClick(data) {
 this.activeId = data.id;
 this.$emit('active', data.text)
 this.$store.dispatch('setShowSidebar', false)
 backTo(){
 this.$store.dispatch('setShowSidebar', false)
 /script 
 style scoped 
.sidebar-con {
 position: absolute;
 top: 0;
 left: -400px;
 transform: translateZ(0);
 opacity: 0;
 width: 100%;
 z-index: 1002;
 height: 100%;
 overflow: auto;
 transition: all 0.3s ease;
.showbar {
 transform: translateX(400px);
 opacity: 1;
.navbar_left {
 background-color: #da3a35;
.navbar_left img {
 width: 25px;
 height: 25px;
 margin-left: 3vw;
 margin-top: 5px;
 /style 

外卖点餐


这里参考的是慕课网黄奕大大的课程,课程地址

商品展示

 template 
 div : 
 div 
 div 
 div @click="backTo" 
 img src="../pages/shop/zuo.png" alt="" 
 /div 
 div 
 div 
 div 预约 /div 
 div K咖啡 /div 
 /div 
 div 
 router-link to="/coupon" 
 div 
 卡包 p 3 /p 张
 /div 
 /router-link 
 /div 
 /div 
 /div 
 div 
 div ref="menuWrapper" 
 v-for="(item,index) in goods"
 :key="index"
 :class="{'current':currentIndex===index}"
 @click="selectMenu(index,$event)"
 span 
 {{item.name}}
 /span 
 /li 
 /ul 
 /div 
 div ref="foodsWrapper" 
 li v-for="(item,index) in goods" :key="index" ref="foodList" 
 h1 {{item.name}} /h1 
 v-for="(food,index) in item.foods"
 :key="index"
 @click="selectFood(index, $event)"
 div 
 img :src="food.image" 
 /div 
 div 
 h2 {{food.name}} /h2 
 div 
 span ¥{{food.price}} /span 
 /div 
 div 
 cartcontrol @add="addFood" :food="food" /cartcontrol 
 /div 
 /div 
 /li 
 /ul 
 /li 
 /ul 
 /div 
 /div 
 submit-bar ref="shopcart" :selectFoods="selectFoods" /submit-bar 
 /div 
 /div 
 /template 

这里通过currentIndex和index做对比,来确认是否添加current类,通过添加current类来实现当前页面的区域的样式变化,他们之间的对比关系也就是menu区域和foods区域的显示区域的对比关系

需要注意的是vue传递原生事件使用$event

 script 
import BScroll from 'better-scroll'
import cartcontrol from './cartcontrol'
import submitBar from './submitBar'
import { mapGetters } from 'vuex'
export default {
 name: 'takeout',
 data() {
 return {
 goods: [],
 listHeight: [],
 scrollY: 0
 components: {
 cartcontrol,
 submitBar
 computed: {
 ...mapGetters([
 'showTakeout'
 currentIndex () {
 for(let i = 0; i this.listHeight.length; i++) {
 let height1 = this.listHeight[i - 1]
 let height2 = this.listHeight[i]
 if (!height2 || (this.scrollY = height1 this.scrollY height2)) {
 return i
 return 0
 selectFoods () {
 let foods = []
 this.goods.forEach(good = {
 good.foods.forEach(food = {
 if (food.count) {
 foods.push(food)
 return foods
 methods: {
 backTo () {
 this.$store.dispatch('setShowTakeout', false)
 selectMenu(index, event) {
 if (!event._constructed) {
 return;
 let foodList = this.$refs.foodList;
 let el = foodList[index];
 this.foodsScroll.scrollToElement(el, 300);
 selectFood(food, event) {
 if (!event._constructed) {
 return;
 this.selectedFood = food;
 _initScroll() {
 this.meunScroll = new BScroll(this.$refs.menuWrapper, {
 click: true
 this.foodsScroll = new BScroll(this.$refs.foodsWrapper, {
 click: true,
 probeType: 3
 this.foodsScroll.on('scroll', pos = {
 this.scrollY = Math.abs(Math.round(pos.y))
 _calculateHeight () {
 let foodList = this.$refs.foodList
 let height = 0
 for (let i = 0; i foodList.length; i++) {
 let item = foodList[i]
 height += item.clientHeight
 this.listHeight.push(height)
 created () {
 this.$http.get('mock/5ca49494ea0dc52bf3b67f4e/example/takeout')
 .then(res = {
 if (res.data.errno === 0) {
 this.goods = res.data.data
 this.$nextTick(() = {
 this._initScroll()
 this._calculateHeight()
 /script 

购物车

 template 
 div 
 van-submit-bar
 :loading="setloading"
 :price="totalPrice"
 button-text="提交订单"
 @submit="onSubmit"
 div @click="toggleList" 
 img src="../../images/gwc.png" alt="" 
 span v-if="selectFoods.length 0" {{selectFoods.length}} /span 
 /div 
 /van-submit-bar 
 transition name="fold" 
 div v-show="listShow" 
 div 
 h1 购物车 /h1 
 span @click="empty" 清空 /span 
 /div 
 div ref="listContent" 
 li v-for="(food, index) in selectFoods" :key="index" 
 span {{food.name}} /span 
 div 
 span ¥{{food.price*food.count}} /span 
 /div 
 div 
 cartcontrol @add="addFood" :food="food" /cartcontrol 
 /div 
 /li 
 /ul 
 /div 
 /div 
 /transition 
 transition name="fade" 
 div @click="hideList" v-show="listShow" /div 
 /transition 
 /div 
 /template 

购物车列表的显示和隐藏以及清空按钮是通过数据fold来决定的,购物车列表是通过计算属性listshow来实现,清空按钮也是通过设置count属性来实现,这样都达到了不用操作dom就可以改变dom行为的效果。

 script 
import { SubmitBar } from 'vant';
import BScroll from 'better-scroll';
import cartcontrol from './cartcontrol';
export default {
 pro凡科抠图: {
 selectFoods: {
 type: Array,
 default() {
 return [
 price: 10,
 count: 1
 data() {
 return {
 setloading: false,
 fold: true
 computed: {
 totalCount () {
 let count = 0
 this.selectFoods.forEach((food) = {
 count += food.count
 return count
 totalPrice () {
 let total = 0
 this.selectFoods.forEach((food) = {
 total += food.price * food.count * 100
 return total
 listShow () {
 if (!this.totalCount) {
 this.fold = true
 return false
 let show = !this.fold
 if (show) {
 this.$nextTick(() = {
 if (!this.scroll) {
 this.scroll = new BScroll(this.$refs.listContent, {
 click: true
 } else {
 this.scroll.refresh()
 return show
 methods: {
 toggleList(){
 console.log(this.totalCount)
 if (!this.totalCount) {
 return;
 this.fold = !this.fold;
 onSubmit() {
 this.setloading = true
 empty() {
 this.selectFoods.forEach((food) = {
 food.count = 0;
 hideList() {
 this.fold = true;
 addFood() {}
 components: {
 cartcontrol
 /script 

操作按钮

这个模块主要通过三个小模块实现,删除按钮,显示数量块,增加按钮

 template 
 div 
 transition name="move" 
 div v-show="food.count 0" @click="decreaseCart" 
 div 
 img width="15px" height="15px" src="../../images/jian.png" alt="" 
 /div 
 /div 
 /transition 
 div v-show="food.count 0" {{food.count}} /div 
 div @click="addCart" 
 img width="15px" height="15px" src="../../images/add.png" alt="" 
 /div 
 /div 
 /template 

addCart以及decreaseCart方法,默认会传入event原生dom事件,food数据是从父组件传入的,所以对这个数据的修改,也能够反应到父组件,也因为购物车的数据也是从父组件传入的,使用同一个food数据,从而关联到购物车的购买数量统计。

 script 
export default {
 name: "cartcontrol",
 pro凡科抠图: {
 food: {
 type: Object
 data() {
 return {
 methods: {
 addCart (event) {
 console.log(event)
 if (!event._constructed) {
 return
 if (!this.food.count) {
 this.$set(this.food, 'count', 1)
 } else {
 this.food.count++
 this.$emit('add', event.target)
 decreaseCart (event) {
 if (!event._constructed) {
 return
 if (this.food.count) {
 this.food.count--
 /script 

异步问题

 div v-for="(item,index) in various" :key="index" 
 div 
 div /div 
 p {{item[0].name}} /p 
 div /div 
 /div 
 div 
 div 
 img :src="item[0].urll" alt="" 
 /div 
 div ref="listwrapper" 
 div 
 div v-for="(u,i) in item.slice(1)" :key="i" 
 img :src="u.url" alt="" 
 /div 
 /div 
 /div 
 /div 
 /div 

这里循环嵌套,整个DOM结构都是循环出来的,而better-scroll需要操作DOM结构,要实现横向滑动效果,难免会有异步问题。

可是无论我使用.then或者$nextTick都无法挂载better-scroll,查阅了大量文档也无法解决,最后只能使用原生的overflow-X,若是有解决办法,欢迎提出,感激不尽!

结语

总的来说这个项目还有很多不足,实现的功能也很少,后续我会继续改进。

如果这篇文章对你有帮助,不妨点个赞吧!

总结

以上所述是小编给大家介绍的Vue实战教程之仿肯德基宅急送App,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对凡科网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!




扫描二维码分享到微信