硅谷外賣項目介紹
前后端分離的SPA應用卤档,前臺采用vue全家桶制作webapp浇垦,后臺使用nodejs,mongoDB開發(fā)氯材,后臺是直接拿過來用的,我是做前端的诚些,所以只學習前端這一塊
項目地址
https://github.com/ynzy/gShop
前臺技術選型
vue,vue-router,axios
vant,swiper,mint-ui
stylus
1飞傀、搭建項目結構
- 靜態(tài)資源導入
- --static--css--reset.css,在index.html頁面引入即可
- 引入阿里圖標庫诬烹,在阿里圖標庫設計好自己需要用到的圖標砸烦,生成在線代碼直接在index.html引入
- 解決點擊響應延時 0.3s 問題
<link rel="stylesheet" href="/static//css/reset.css">
<!-- 引入阿里圖標庫 -->
<link rel="stylesheet" >
<!-- TODO: 解決點擊響應延時 0.3s 問題 -->
<script src="https://as.alipayobjects.com/g/component/fastclick/1.0.6/fastclick.js"></script>
<script>
if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function() {
FastClick.attach(document.body);
}, false);
}
if(!window.Promise) {
document.writeln('<script src="https://as.alipayobjects.com/g/component/es6-promise/3.2.2/es6-promise.min.js"'+'>'+'<'+'/'+'script>');
}
</script>
2、vue-router的理解和使用
- router-view/router-link/keep-alive
- $router: 路由對象绞吁,包含一些操作路由的功能函數(shù)幢痘,來實現(xiàn)編程式導航(路由跳轉)
- $route: 當前路由對象,一些當前路由信息數(shù)據(jù)容器家破,path/meta/query/params
- 路由拆分
- 拆分路由颜说,當拿到ui圖或者靜態(tài)頁面時,要對路由進行布局拆分员舵,知曉脑沿,每個頁面跳轉到哪里,會顯示什么
- 底部導航組件:FooterGuide
- 此導航路由組件: Msite/Search/Order/Profile
- 路由結構:
routes: [
{
path: '/',
redirect: '/msite'
},
{
path: '/msite',
name: 'msite',
component: Msite,
meta: { //配置元數(shù)據(jù)確定是否顯示footer
showFooter: true
}
},
{
path: '/order',
name: 'order',
component: Order,
meta: {
showFooter: true
}
},
{
path: '/profile',
name: 'profile',
component: Profile,
meta: {
showFooter: true
}
},
{
path: '/search',
name: 'search',
component: Search,
meta: {
showFooter: true
}
},
{
path: '/login',
name: 'login',
component: Login
},
]
<template>
<div>
<van-tabbar v-model="active" active-color="#07c160">
<van-tabbar-item to="/msite" icon="shop">外賣</van-tabbar-item>
<van-tabbar-item to="/search" icon="search">搜索</van-tabbar-item>
<van-tabbar-item to="/order" icon="column">訂單</van-tabbar-item>
<van-tabbar-item to="/profile" icon="manager">我的</van-tabbar-item>
</van-tabbar>
</div>
</template>
<script>
export default {
data() {
return {
active: 0
};
},
components: {},
computed: {},
methods: {},
mounted() {
//TODO: 監(jiān)聽路由路徑马僻,修改相對應的底部的菜單項
let path = this.$route.path;
if (path == "/msite") {
this.active = 0;
} else if (path == "/search") {
this.active = 1;
} else if (path == "/order") {
this.active = 2;
} else if (path == "/profile") {
this.active = 3;
}
}
};
</script>
<style lang='stylus' rel='stylesheet/stylus' scoped>
@import '../../common/stylus/mixins.styl';
</style>
- 使用vant的Tabbar組件實現(xiàn)底部導航,由于點擊跳轉頁面注服,再次刷新頁面時韭邓,路由頁面與底部導航激活的部分不匹配措近,所以在每次加載頁面時,監(jiān)聽當前路由女淑,使底部激活部分與之匹配
- 抽取組件
有些組件在很多頁面都是一樣的瞭郑,只是顯示的數(shù)據(jù)不一樣,所有將其抽取出來放在--components公共組件中鸭你,通過solt插槽來實現(xiàn)組件通信標簽結構屈张,props獲取父組件數(shù)據(jù),實現(xiàn)多頁面公用一個組件
- 頭部組件: HeaderTop, 通過slot來實現(xiàn)組件通信標簽結構
- 商家列表組件: ShopList
- 示例
//HeaderTop公共組件
<template>
<header class="header">
<!-- 使用插槽獲取內容 -->
<slot name="left"></slot>
<span class="header_title">
<span class="header_title_text ellipsis">{{title}}</span>
</span>
<slot name="right"></slot>
</header>
</template>
<script>
export default {
props: {
title: String //從父組件獲取標題信息
}
}
</script>
//Msite父組件
<template>
<section class="msite">
<!--首頁頭部-->
<HeaderTop title="昌平區(qū)北七家宏父ぞ蓿科技園(337省道北)">
<span class="header_search" slot="left">
<i class="iconfont icon-sousuo"></i>
</span>
<span class="header_login" slot="right">
<span class="header_login_text">登錄|注冊</span>
</span>
</HeaderTop>
</section>
</template>
<script>
import HeaderTop from "../../components/HeaderTop/HeaderTop.vue";
export default {
data() {
return {};
},
components: {
HeaderTop,
ShopList
}
};
</script>
- 登錄路由組件
只有路由導航的四個頁面需要顯示底部菜單阁谆,登錄組件是不需要顯示底部導航的,所以愉老,通過路由的meta屬性场绿,配置點擊的路由組件顯示為true
- 在app頁面進行設置
<template>
<div id="app">
<router-view/>
<!-- TODO: 底部菜單組件是否要顯示 -->
<FooterGuide v-show="$route.meta.showFooter"></FooterGuide>
</div>
</template>
6. 后臺項目
啟動后臺項目: 理解前后臺分離
測試后臺接口: 使用postman
修正接口文檔
7、前后臺交互
- ajax請求:axios
- ajax請求封裝: axios + promise
/**
* ajax請求函數(shù)模塊
* 返回值: promise對象(異步返回的數(shù)據(jù)是: response.data)
*/
import axios from 'axios'
/**
* @export
* @param {*} url //請求地址
* @param {*} [data={}] //請求數(shù)據(jù)對象
* @param {string} [type='GET'] //請求方法
*/
export default function ajax(url, data = {}, type = 'GET') {
return new Promise(function (resolve, reject) {
// 執(zhí)行異步ajax請求
let promise
if (type === 'GET') {
// 準備url query參數(shù)數(shù)據(jù)
let dataStr = '' //數(shù)據(jù)拼接字符串
Object.keys(data).forEach(key => {
dataStr += key + '=' + data[key] + '&'
})
if (dataStr !== '') {
dataStr = dataStr.substring(0, dataStr.lastIndexOf('&'))
url = url + '?' + dataStr
}
// 發(fā)送get請求
promise = axios.get(url)
} else {
// 發(fā)送post請求
promise = axios.post(url, data)
}
promise.then(function (response) {
// 成功了調用resolve()
resolve(response.data)
}).catch(function (error) {
//失敗了調用reject()
reject(error)
})
})
}
- 接口請求函數(shù)封裝: 每個后臺接口
/**
* 包含n個接口請求函數(shù)的模塊
* 函數(shù)的返回值: promise對象
*/
import ajax from './ajax'
const BASE_URL = 'http://localhost:4000'
// const BASE_URL = '/api'
// 1嫉入、根據(jù)經(jīng)緯度獲取位置詳情
export const reqAddress = function (geohash) {
ajax(`${BASE_URL}/position/${geohash}`)
}
// 2焰盗、獲取食品分類列表
export const reqFoodCategorys = () => ajax(BASE_URL+'/index_category')
// 3、根據(jù)經(jīng)緯度獲取商鋪列表
export const reqShops = (latitude,longitude) => ajax(BASE_URL+'/shops', {longitude, latitude})
// 4咒林、根據(jù)經(jīng)緯度和關鍵字搜索商鋪列表
export const reqSearchShop = (geohash,keyword) => ajax(BASE_URL+'/shops', {geohash, keyword})
// 5熬拒、獲取一次性驗證碼
export const reqCaptcha = () => ajax(BASE_URL+'/captcha')
// 6、用戶名密碼登陸
export const reqPwdLogin = ({name,pwd,captcha}) => ajax(BASE_URL+'/login_pwd',{name,pwd,captcha},'POST')
// 7垫竞、發(fā)送短信驗證碼
export const reqSendCode = (phone) => ajax(BASE_URL + '/sendcode', {phone})
// 8澎粟、手機號驗證碼登陸
export const reqSmsLogin = (phone,code) => ajax(BASE_URL + '/login_sms', {phone,code},'POST')
// 9、根據(jù)會話獲取用戶信息
export const reqUserInfo = () => ajax(BASE_URL + '/userinfo')
// 10件甥、用戶登出
export const reqLogout = () => ajax(BASE_URL + '/logout')
/**
* 獲取商家信息
*/
export const reqShopInfo = () => ajax('/info')
/**
* 獲取商家評價數(shù)組
*/
export const reqShopRatings = () => ajax('/ratings')
/**
* 獲取商家商品數(shù)組
*/
export const reqShopGoods = () => ajax('/goods')
- 調用
mounted () {
const result = reqFoodCategorys()
// console.log(result)
result.then(function(res) {
console.log(res)
})
}