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