preface
I have learned Vue for some time, and I want to use Vue to write a project to practice my hands. I have been doing it for half a month, so far I can barely see it. Because I don’t know how to take the interface of mobile phone App, and THE OFFICIAL website of KFC computer side really… It’s a long story, so I took screenshots of all the data of the project and wrote them in EasyMock. Students who need them can help themselves
Home page Goods page takeaway page
Technology stack
vue
+ webpack
+ vuex
+ axios
File directory
│ ├─ Anti-Flag - Parts │ cartControl. Vue │ code. Vue │ │ coupon Vue │ │ sidebar. Vue │ submitBar. Vue │ takeout. Vue │ wallet └ ─ tabs │ Other. Vue │ Outward. The vue │ Selfhelp. Vue │ Vgold. Vue │ ├ ─ pages │ ├ ─ home │ │ home. Vue │ │ │ ├ ─ mime │ │ mime. Vue │ │ │ ├ ─ order │ │ order. Vue │ │ │ └ ─ shop │ shop. The vue │ ├ ─ the router │ index. The js │ └ ─ vuex │ store. Js │ types. The js │ └ ─ modules com.js cou.js take.jsCopy the code
Results show
Defined components
better-scroll
Since every page needs to be slid, the Scroll component should be wrapped at the beginning and introduced later
<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', props: {/** * 1 Will emit scroll events when scrolling, which will throttle the function. * 2 Send scroll events in real time without throttling. * 3 Swipe can swipe scroll events in real time */ probeType: {type: Number, default: 1}, /** * whether to issue a click event */ click: {type: Boolean, default: true}, /** * whether to enable horizontal scrolling */ scrollX: {type: */ listenScroll: {type: Boolean, default: false}, /** * List of data */ data: { type: Array, default: null }, pullup: { type: Boolean, default: false }, pulldown: { type: Boolean, default: False}, beforeScroll: {type: Boolean, default: false}, /** * The scroll delay after data is updated. */ 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 : If (this.listenScroll) {this.scroll. On ('scroll', (pos) => {this.listenScroll ('scroll', (pos) => {this.listenScroll ('scroll', Pos)})} // Whether to send the scroll to the bottom event, If (this.pullup) {this.scroll. On ('scrollEnd', () => {if (this.scroll. Y <= (this.scroll. MaxScrollY + 50)) {this.scroll ('scrollToEnd')}})} If (this.pulldown) {this.scroll. On ('touchend', Y > 50) {this.$emit('pulldown')}}} if (this.beforeScroll) { this.scroll.on('beforeScrollStart', () => { this.$emit('beforeScroll') }) } }, Disable () {this.scroll && this.scroll. Disable ()}, Enable () {this.scroll && this.scroll. Enable ()}, Refresh () {this.scroll && this.scroll. Refresh ()}, ScrollTo () {/ agents/better - scroll scrollTo method of enclosing scroll && enclosing scroll. The scrollTo. Apply (enclosing scroll, the arguments)}, ScrollToElement () {/ agents/better - scroll scrollToElement method of enclosing scroll && enclosing scroll. ScrollToElement. Apply (enclosing scroll, arguments) }, }, mounted() { setTimeout(() => { this._initScroll() },20) }, watch: { data () { setTimeout(() => { this.refresh() },this.refreshDelay) } }, } </script> <style> </style>Copy the code
A slot is a template. It is up to the parent component to decide whether or not to display the slot and how to display it. Insert the area you want to slide into the slot and the rest is defined in the official document
Fixed to the head
The head is fixed relative to the page. Here, I have encapsulated the head into components, introduced the head in the main page, and put the part to slide into the Scroll component defined above
Sidebar and pop-up box
<template>
<div class="sidebar">
<div class="sidebar-con" :class="{showbar: showSidebar}">
<div class="navbar_left" @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>
Copy the code
The style uses the Vant UI component, binds a dynamic style showbar to the outside, and then sets the initial position of the whole outside the screen, comes back when the argument is true, and manages its state with 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.3 s ease;
}
.showbar {
transform: translateX(400px);
opacity: 1;
}
Copy the code
Vuex status management
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
}
Copy the code
Get the object with mapGetter, pass it to a computed property, and the object can be used directly
computed: { ... mapGetters(['showSidebar'])},Copy the code
$store.dispatch(‘setShowSidebar’, true)
The overall code
<template>
<div class="sidebar">
<div class="sidebar-con" :class="{showbar: showSidebar}">
<div class="navbar_left" @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{},].// Highlight the index of the element on the left
mainActiveIndex: 0.// The id of the selected element
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.3 s 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>
Copy the code
The selling point meal
This is the course address of Huang Yida on MOOC
Commodity display
<template>
<div class="takeout" :class="{showtakeout: showTakeout}">
<div class="goods">
<div class="header">
<div class="navbar_left" @click="backTo">
<img src=".. /pages/shop/zuo.png" alt="">
</div>
<div class="appointment">
<div class="btn">
<div class="yy">To make an appointment</div>
<div class="Kcoffee">K coffee</div>
</div>
<div class="bag">
<router-link style="color: #000" to="/coupon">
<div class="bagtext">Card package<p>3</p>zhang</div>
</router-link>
</div>
</div>
</div>
<div class="goodList">
<div class="menu-wrapper" ref="menuWrapper">
<ul>
<li
v-for="(item,index) in goods"
:key="index"
class="menu-item"
:class="{'current':currentIndex===index}"
@click="selectMenu(index,$event)"
>
<span class="text border-1px">
{{item.name}}
</span>
</li>
</ul>
</div>
<div class="foods-wrapper" ref="foodsWrapper">
<ul>
<li v-for="(item,index) in goods" :key="index" class="food-list" ref="foodList">
<h1 class="title">{{item.name}}</h1>
<ul>
<li
v-for="(food,index) in item.foods"
:key="index"
class="food-item border-1px"
@click="selectFood(index, $event)"
>
<div class="icon">
<img :src="food.image">
</div>
<div class="content">
<h2 class="name">{{food.name}}</h2>
<div class="price">
<span class="now">${{food. Price}}</span>
</div>
<div class="cartcontrol-wrapper">
<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>
Copy the code
Here, the current index and index are compared to confirm whether to add the current class. By adding the current class, the style change of the current page area is realized. The comparison between them is also the comparison between the menu area and the display area of foods area
Note that vue passes native events using $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('https://www.easy-mock.com/mock/5ca49494ea0dc52bf3b67f4e/example/takeout') .then(res => { if (res.data.errno === 0) { this.goods = res.data.data this.$nextTick(() => { this._initScroll() this._calculateHeight() }) } }) } } </script>Copy the code
The shopping cart
<template>
<div class="submitBar">
<van-submit-bar
:loading="setloading"
:price="totalPrice"
button-text="Submit order"
@submit="onSubmit"
>
<div class="shoppingCart" @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 class="shopcart-list" v-show="listShow">
<div class="list-header">
<h1 class="title">The shopping cart</h1>
<span class="empty" @click="empty">empty</span>
</div>
<div class="list-content" ref="listContent">
<ul>
<li class="food" v-for="(food, index) in selectFoods" :key="index">
<span class="name">{{food.name}}</span>
<div class="price">
<span>${{food. The food price *. Count}}</span>
</div>
<div class="cartcontrol-wrapper">
<cartcontrol @add="addFood" :food="food"></cartcontrol>
</div>
</li>
</ul>
</div>
</div>
</transition>
<transition name="fade">
<div class="list-mask" @click="hideList" v-show="listShow"></div>
</transition>
</div>
</template>
Copy the code
The display and hide of the shopping cart list and the empty button are determined by the data fold, the shopping cart list is calculated by the listShow property, and the empty button is set by the count property, all of which change the BEHAVIOR of the DOM without manipulating the DOM.
<script> import { SubmitBar } from 'vant'; import BScroll from 'better-scroll'; import cartcontrol from './cartcontrol'; export default { props: { 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>Copy the code
Action buttons
This module is mainly implemented through three small modules, delete button, display quantity block, add button
<template>
<div class="cartcontrol">
<transition name="move">
<div class="cart-decrease" v-show="food.count > 0" @click="decreaseCart">
<div class="inner">
<img width="15px" height="15px" src=".. /.. /images/jian.png" alt="">
</div>
</div>
</transition>
<div class="cart-count" v-show="food.count > 0">{{food.count}}</div>
<div class="cart-add" @click="addCart">
<img width="15px" height="15px" src=".. /.. /images/add.png" alt="">
</div>
</div>
</template>
Copy the code
The addCart and decreaseCart methods, by default, pass in the event native DOM event. The food data is passed in from the parent component, so changes to this data can also reflect the parent component. Because the cart data is also passed in from the parent component, using the same food data, This is related to the shopping cart purchase quantity statistics.
<script> export default { name: "cartcontrol", props: { 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>Copy the code
Asynchronous problem
<div class="various" v-for="(item,index) in various" :key="index">
<div class="title">
<div class="strip"></div>
<p>{{item[0].name}}</p>
<div class="strip"></div>
</div>
<div class="various_img">
<div class="various_title">
<img :src="item[0].urll" alt="">
</div>
<div ref="listwrapper" class="index">
<div class="various_list">
<div class="various_box" v-for="(u,i) in item.slice(1)" :key="i">
<img :src="u.url" alt="">
</div>
</div>
</div>
</div>
</div>
Copy the code
In this case, the whole DOM structure is looped out, while Better Scroll needs to manipulate the DOM structure, so there will inevitably be asynchronous problems in order to achieve horizontal sliding effect. However, no matter I use.then or $nextTick, I can’t mount better Scroll, and I can’t solve the problem after looking up a lot of documents. Finally, I can only use native overflow-X, if there is a solution, welcome to put forward, thank you very much!
conclusion
In general, this project still has many deficiencies and few functions. I will continue to improve in the future. If this article has helped you, give it a thumbs up! Making the address