1. 程式人生 > >低仿餓了麼H5-純前端Vue版 + 手把手教學

低仿餓了麼H5-純前端Vue版 + 手把手教學

這是一個低仿餓了麼H5的純前端練手,資料是偽造的,寫的比較粗糙,寫這個的目的是為了加深一下熟練度,半年前看到別人寫的仿cnodejs網站,我也用vue1仿了cnodeJs的網站,當時寫的也是粗糙的令人髮指,線上預覽:https://hbxywdk.github.io/vue-cnodeJs/#!/

半年過去,工作中很少有機會用上vue,vue也早已更新到2.+,想想學了不用也是白學了,恰好年後離了職,也沒有再著急找工作,開啟了夜裡不睡覺的修仙模式(#滑稽),沒事看著小說,想起來了就擼擼程式碼,日子過的也是愜意,憋了許久終於把東西憋了出來。

網頁是有假的賬戶密碼的部分頁面需要登入 ↓ ,最好在Chrome手機模式下瀏覽。
username:admin
password:admin

本地預覽步驟

# clone 檔案
git clone https://github.com/hbxywdk/eleme-vue2.git

# 進入 eleme-vue2 資料夾
cd eleme-vue2

# install dependencies
npm install

# 執行 npm run dev 會在瀏覽器開啟 localhost:8080
npm run dev

本人前端水平一般,如果你已經對vue很瞭解,那麼看看預覽就好不用繼續閱讀,如果你知道vue,想學習一下,可以繼續看下去。

使用到的相關庫或工具:vue2 + vuex2 + vue-router2 + vue-swipe + vue-cli




預設大家都已安裝Node.js,且知道基礎的ES6語法。

首先需要安裝vue-cli,vue-cli是一個快速搭建vue專案的工具,就不需要我們一行一行寫webpack配置了。

// 安裝 vue-cli
npm install -g vue-cli

// 初始化一個專案
vue init webpack my-project

// 然後一路回車,記得勾選上vue-router
// 輸入以下命令,等待瀏覽器開啟
cd my-project
npm install
npm run dev

得到以下目錄結構

-build
-config
-node_modules    // node包
-src // 專案程式碼所在資料夾 -static -.babelrc -.editorconfig -.eslintignore -.eslintrc.js -.gitignore -index.html -package.json -README.md

src為專案程式碼所在資料夾

手動新增vuex。(Vuex 是一個專為 Vue.js 應用程式開發的狀態管理模式)
npm install vuex -S
在src目錄下建立store.js
// store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex) // 要記得use一下哦

export default new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    Count (state, platform) {
      state.count = platform
    }
  },
  actions: {
    setCount ({commit}, platform) {
      commit('Count', platform)
    }
  },
  getters: {
    getCount: (state) => state.count
  }
})

state為狀態資料,觸發action,mutations會去改變state的值,getters對外提拱state的值。

修改main.js。(main.js為webpack打包入口檔案)
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './vuex/store'  // 引入store 

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store, // 注入store 
  template: '<App/>',
  components: { App }
})

加入store

修改App.vue
<template>
  <div id="app">
    ![](./assets/logo.png)
    {{ asd }}                                     // here
    <button @click="change(233)">變為233</button> // here
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'app',
  computed: {                                           // here ↓
    asd () {
      return this.$store.getters.getCount
    }
  },
  methods: {
    change (arg) {
      this.$store.dispatch('setCount', arg)
    }
  }
}                                                       // here ↑
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

computed 寫入 asd () {return this.$store.getters.getCount} ,通過vuex的getters就可得到state中的count,並在當前資料夾中以 asd 存在。
methods 中寫入 this.$store.dispatch('setCount', arg), 執行change 方法會觸發action ,改變count值為233,state值改變,computed 就會改變當前asd的值,template中也會相應改變。
更多vuex用法詳見:https://vuex.vuejs.org/zh-cn/intro.html

Vue-router。(vue-router通常用於製作單頁面應用,如其名就是給vue使用的路由,不清楚路由的可以百度一下)

在components資料夾中新建幾個我們要用到的 .vue 檔案,直接複製預設的Hello.vue檔案即可,重新命名並修改其中的不需要的內容。

修改router/index.js
import Vue from 'vue';
import Router from 'vue-router';
//  引入元件
import Homepage from 'components/Homepage';
import Order from 'components/Order';
import Myzone from 'components/Myzone';
import Business from 'components/Business';
import Login from 'components/Login';
import Search from 'components/Search';
Vue.use(Router);

export default new Router({
  routes: [
    {
      path: '/',
      name: 'homepage',
      component: Homepage
    },
    {
      path: '/order',
      name: 'order',
      component: Order
    },
    {
      path: '/myzone',
      name: 'myzone',
      component: Myzone
    },
    {
      path: '/business/:id',
      name: 'business',
      component: Business
    },
    {
      path: '/search/:keyword',
      name: 'Search',
      component: Search
    },
    {
      path: '/login',
      name: 'Login',
      component: Login
    }
  ]
});

我們引入空白元件,並定義路由,當訪問對應路徑時,如localhost:8080/#/business,App.vue中的<router-view></router-view>就會展示對應內容,做到無重新整理跳轉。path: '/business/:id',後面的 :id 則是要傳遞的引數,元件中可以this.$route.params.id得到其值。

template中用<router-link>來跳轉,<router-link> 預設會被渲染成一個 <a> 標籤 <router-link to="/business/123">Go to Foo</router-link>,js中可用router.replace(location)來進行跳轉。

下面進入到程式碼部分

整個專案使用 rem 方式佈局,根據螢幕的寬度來自適應不同手機螢幕。
資料為偽造。
首頁 Homepage.vue

// 部分程式碼
export default {
  mounted () {
    this.$store.dispatch('setLoading', true);// 設定當前狀態為載入中,顯示載入動畫
    var time = Math.floor(Math.random() * 2000); // 模擬請求等待
    setTimeout(() => {
      this.$store.dispatch('setLoading', false); // 頁面顯示
      this.showMe = true;
    }, time);
  },
  computed: {
    ...mapGetters([
      'getLogin',
      'getFalseHotWord',
      'getFalseBussinessbrief' // 商家簡略資訊
    ])
  }
};

由於是純前端頁面,故使用setTimeout模擬下資料的載入,展示載入動畫,computed中使用mapGetters來得到state中的資料,這樣寫與this.$store.getters.getCount實質上沒有什麼區別,但需要得到的資料較多時能少寫不少程式碼。(使用前需引入)

商家列表部分,因為資料是偽造的,只有五個商家資料,所以這裡用簡單的複製,來做出載入更多的效果。

Business.vue(商家詳情頁)
// 部分程式碼
export default {
  computed: {
    // 通過id找到store中對應店鋪資訊
    business_info () {
      return this.$store.getters.getFalseBussinessInfo[this.$route.params.id];
    }
  },
  methods: {
    // 右列表控制左列表樣式
    rightControlLeftClass () {
       // code
    },
    // 左列表點選控制右列表滾動
    leftControlRightScroll (index) {
       // code
    },
    // 監控網頁的resize來改變商品列表的高度
    watchHei () {
       // code
    },
    // 列表中的加按鈕點選
    add_food (n, x, e) { // n 為大類 x為大類種商品
       // code
    },
    // 向購物車新增
    add_shopping_car (type, typename, foodname, foodid, foodprice) {
      if (!this.shoppingCarList[foodid]) {
        this.shoppingCarList[foodid] = {
          'type_accumulation': type,
          'type_name': typename,
          'name': foodname,
          'one_food_id': foodid,
          'unit_price': foodprice,
          'count': 1
        };
      } else {
        this.shoppingCarList[foodid].count++;
      }
      // 購物車改變 相關計算
      this.spChangeComputeAll();
    },
    // 購物車改變 相關計算
    spChangeComputeAll () {
       // code
    },
    // 結賬
    checkout () {
       // code
    },
    // 生成小球丟擲 計算left top 生成動畫
    ball_fly (e) {
      var bound = e.target.getBoundingClientRect(); // 被點元素位置
      var qiu = document.createElement('div');
      qiu.className = 'qiu';
      qiu.style.top = bound.top + 'px';
      qiu.style.left = bound.left + 'px';
      document.body.appendChild(qiu);
      var dsa = this.$refs.carIcon; // 目標元素位置
      var mubiao = dsa.getBoundingClientRect();
      var mubiaoT = mubiao.top;
      var mubiaoL = mubiao.left;
      var timer = null;
      var chaTop = mubiaoT - bound.top;  // top差值 left差值
      // 要減掉目標寬度一半 讓落點對準目標中心
      var chaLeft = bound.left - mubiaoL - dsa.offsetWidth / 2;
      // 規定上拋初速度為 top 差值的55分之1
      var g = chaTop / 55;
      // 規定上拋初速度為 top 差值的15分之一
      var vTop = chaTop / 15;
      timer = setInterval(() => {
        qiu.style.top = (qiu.getBoundingClientRect().top + (-vTop + g)) + 'px';
        qiu.style.left = (qiu.getBoundingClientRect().left + (-chaLeft / 14)) + 'px';
        // 每次 g 對速度的影響
        vTop -= g;
        if (qiu.getBoundingClientRect().top >= mubiaoT) {
          clearInterval(timer);
          qiu.parentNode.removeChild(qiu);
          this.$refs.carIcon.classList.add('tantantan');
        }
      }, 1000 / 25);
    }
  }
};

商家詳情頁佔一整屏,最外層不允許出現滾動條,分上下兩部分,上半部分給固定rem高度,下半部分高度動態計算,螢幕大小改變再次計算,下半部分,左右各一個ul列表,設定高度100%,overflow-y:auto;超出即可滾動。

左右列表
右ul滾動監控所有標題行的offsetTop,對比當前ul的scrollTop,給左ul新增對應樣式。點選左列表,來控制右列表滾動。

購物車
data中定義一個購物車物件{ },新增某樣商品時,已存在就+1,不存在則新增屬性,每次新增計算總價等相關資料。

新增商品的拋球效果
每次新增獲得點選按鈕的座標與目標位置的座標,算得高度差值,利用初中物理,水平方向勻速運動,垂直方向垂直上拋運動,並新增向下的重力,即可得到拋球效果。

結算
結算時將當前購物車中計算出的簡略資訊新增到state中,清空購物車,跳轉路由。

其他幾個頁面基本只是展示功能這裡也就不再贅述。

現在是早上7:25,從凌晨4:00寫到現在也是寫的有點長了,其中廢話頗多,能看到這裡也是對我半夜忙活的肯定,也請大神不要吐槽我的程式碼水平。不過雖然囉嗦,但也對的起標題中的手把手教學了!
如果想要更詳細的內容,可以直接閱讀程式碼,內有更詳細的註釋。
這個練習,還有許多不夠完善的地方,比如拋球效果以及各處細節,目前也沒有想到更好的,如果你有也請告訴我。有機會的話我會完善這些不足併為其加上node的後臺。

如果你願意,可以給我一個大大的star,Thanks!