低仿餓了麼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中,清空購物車,跳轉路由。
其他幾個頁面基本只是展示功能這裡也就不再贅述。