下面将针对Vue2.0仿饿了么webapp单页面应用的详细步骤进行讲解,内容包含以下几个部分:
- 技术选型
- 项目搭建
- 基本页面结构及组件编写
- API接口封装及调用
- 数据的存储及使用
- 基础功能的实现
- 进一步实现复杂功能
- 项目部署
技术选型
这里使用Vue2.0进行开发,Vue是一个轻量级的MVVM框架,其核心思想是把DOM操作抽象成组件,提高代码的可重用性和可维护性。此外,使用Vue还需要涉及到几个相关的技术栈:
- vue-router:Vue的官方路由插件,用于单页面应用的路由控制;
- axios:一个基于Promise的HTTP客户端,用于与后端API进行数据交互;
- vant-ui:一个基于Vue的移动端UI框架,提供了一些常用的组件。
项目搭建
在搭建项目前,需要使用Vue-cli进行初始化,该工具是Vue的脚手架,封装了Vue项目的基本配置项和开发工具,是快速开始Vue项目的好工具。
接下来,可以按照以下命令来创建项目:
vue create eleme
在创建过程中,可以选择想要的特性,例如CSS预处理器、Linter、自定义主题等。完整的项目搭建过程可以参考Vue-cli官方文档。
基本页面结构及组件编写
在项目搭建完成后,需要进行基础页面的编写,主要包括头部、底部和页面内容三部分。
整个页面主要依据Vant的UI框架进行开发,需要包含一些组件,如Tabbar、Tabbar-Item、Card、Button、Search等。页面结构大致是这样:
<template>
<div>
<div class="header">头部</div>
<div class="content">页面内容</div>
<div class="footer">
<van-tabbar v-model="active">
<van-tabbar-item icon="search" center-text="搜索" />
<van-tabbar-item icon="home" center-text="首页" />
<van-tabbar-item icon="my" center-text="我的" />
</van-tabbar>
</div>
</div>
</template>
其中,头部和底部部分可以通过CSS样式进行自定义。
接下来,需要编写组件代码。例如,创建一个FoodCard组件,用于展示菜品信息:
<template>
<van-card class="food-card">
<img :src="food.image" class="food-image" />
<div class="food-name">{{food.name}}</div>
<div class="food-desc">{{food.description}}</div>
<div class="food-price">¥ {{food.price}}</div>
<van-button type="primary" class="buy-button">购买</van-button>
</van-card>
</template>
<script>
export default {
props: ['food']
}
</script>
<style scoped>
.food-card {
width: 45%;
margin: 10px;
}
.food-image {
height: 200px;
}
.food-name {
font-size: 16px;
margin-top: 10px;
}
.food-desc {
font-size: 14px;
color: #555;
margin-top: 5px;
}
.food-price {
font-size: 18px;
color: #f60;
margin-top: 10px;
}
.buy-button {
margin-top: 10px;
}
</style>
上面代码中,FoodCard组件采用props接收父组件传递的food数据,并通过v-for循环生成多个食品卡片。组件内部使用Vant-UI提供的Card和Button组件进行布局和样式的设置。
API接口封装及调用
数据交互是Web应用程序的重要组成部分,我们需要编写API接口来与后端进行交互。这里使用axios进行API调用,并对axios进行封装,方便后续API调用。
下面,我们以获取商家列表为例,演示API的调用和封装:
首先在src目录下创建API目录,用于存放API相关的代码。
创建api.js文件,这里我们定义了获取商家列表的API:
import axios from 'axios'
export default {
getSellerList() {
return axios.get('/api/seller')
}
}
我们使用了axios的get方法,向服务器请求商家列表数据。
接着,我们还需要对axios进行封装,以方便API调用。在API目录下创建request.js文件(命名可以自定):
import axios from 'axios'
const request = axios.create({
baseURL: process.env.BASE_URL || '/',
timeout: 5000
})
export default request
上面代码中,我们使用axios.create方法创建了一个axios实例,并设置了默认的baseURL和timeout,这里的baseURL指的是请求的API接口地址。timeout表示请求的超时时间,超时后请求将被中断。
最后,我们需要在main.js文件中引入request.js并使用:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import request from './api/request'
Vue.prototype.$request = request
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
上面代码中,我们将封装好的request.js文件引入到Vue中,并添加到Vue的原型上,这样在Vue的组件中就可以使用this.$request来发起API请求了。
API接口调用完成后,我们需要将返回的数据存储到Vuex状态管理器中,方便后续的组件调用。
数据的存储及使用
这里我们使用Vuex进行全局状态管理。
在src目录下创建store目录,并编写state.js、mutations.js、actions.js和getters.js文件,这些文件分别用于存储状态、修改状态、异步操作和计算状态值。
在state.js文件中,我们定义一个seller变量,用于存储商家列表数据:
export default {
seller: {}
}
在mutations.js文件中,我们添加一个SET_SELLER方法,用于修改seller状态:
export default {
SET_SELLER(state, seller) {
state.seller = seller
}
}
在actions.js文件中,我们添加一个fetchSeller方法,用于获取商家列表信息:
import Api from '@/api/api'
import { SET_SELLER } from './mutations'
export default {
async fetchSeller({ commit }) {
const resp = await Api.getSellerList()
commit(SET_SELLER, resp.data)
}
}
在fetchSeller方法中,我们使用我们上面封装好的Api.getSellerList方法获取商家列表数据,并利用commit方法提交数据变动,即调用mutations中的SET_SELLER方法。
在getters.js文件中,我们添加一个seller方法,用于提供seller状态信息:
export default {
seller: state => state.seller
}
在store目录下,我们还需要创建一个index.js文件,用于将state、mutations、actions和getters模块进行组装:
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
import actions from './actions'
import getters from './getters'
Vue.use(Vuex)
export default new Vuex.Store({
state,
mutations,
actions,
getters
})
在组装后,我们需要在main.js文件中引入store,以便应用中其他组件可以访问到store中存储的数据:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import request from './api/request'
Vue.prototype.$request = request
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
现在,我们成功将API数据存储到了Vuex状态管理器中,下一步则是实现一些基础功能。
基础功能的实现
基础功能包括搜索、商家列表及食品详情等。这些功能的实现需要使用vue-router控制路由转换。
在router目录下创建index.js文件,用于存储路由相关的配置信息。
首先,我们需要引入vue-router并配置路由信息:
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '@/views/Home.vue'
import Detail from '@/views/Detail.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/detail/:id',
name: 'Detail',
component: Detail
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
上面代码中,我们定义了两条路由信息:Home和Detail。其中Home对应Home.vue组件,Detail对应Detail.vue组件。Detail路由中,我们使用了动态路由参数:id,并在组件中通过this.$route.params.id的方式进行访问。
这里我们还需要进行一些路由拦截和页面切换的操作,以便进行页面访问控制和动画效果展示。关于这一部分的详细讲解超出了本文讨论的范畴。
接下来,我们需要编写组件代码。
在src/views目录下,创建Home.vue和Detail.vue组件。
对于Home.vue组件,我们可以做如下事情:
- 添加一个Search组件,用于搜索商家;
- 使用v-for循环展示商家列表中的数据;
- 点击商家卡片,跳转到商家详情页。
以下是Home.vue组件的代码:
<template>
<div>
<van-search
v-model="searchValue"
placeholder="请输入商家或美食名称"
@search="search"
/>
<div class="seller-list">
<food-card v-for="seller in sellers" :key="seller.id" :food="seller" @click="goToDetail(seller.id)" />
</div>
</div>
</template>
<script>
import FoodCard from '@/components/FoodCard.vue'
import { mapGetters, mapActions } from 'vuex'
export default {
components: { FoodCard },
data() {
return {
searchValue: ''
}
},
computed: {
...mapGetters(['sellerList'])
},
created() {
this.fetchSellerList()
},
methods: {
...mapActions(['fetchSellerList']),
search() {
console.log('search: ' + this.searchValue)
},
goToDetail(id) {
this.$router.push({ name: 'Detail', params: { id } })
}
}
}
</script>
<style scoped>
.seller-list {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
}
</style>
在上面代码中,我们使用了mapGetters和mapActions帮助我们在Vue组件中访问到store中存储的seller列表数据和fetchSellerList方法。在search方法中,我们打印了搜索内容,但未对搜索结果进行处理。
在Detail.vue组件中,我们需要展示商家信息和菜品列表等信息。
以下是Detail.vue组件的代码:
<template>
<div>
<van-image :src="seller.avatar" class="seller-banner" />
<div class="seller-info">
<div class="seller-name">{{seller.name}}</div>
<div class="seller-ratings">{seller.ratings}}</div>
<div class="seller-sales">{seller.sales}}</div>
</div>
<div>
<van-tabs v-model="tab">
<van-tab title="点餐">
<div class="foods">
<food-card v-for="food in seller.foods" :key="food.id" :food="food" />
</div>
</van-tab>
<van-tab title="评价">
<van-cell-group>
<van-cell title="评价内容" label="评价时间" />
</van-cell-group>
</van-tab>
<van-tab title="商家">
<van-cell-group>
<van-cell title="商家信息" label="{{seller.info}}" />
<van-cell title="商家地址" label="{{seller.address}}" />
<van-cell title="商家电话" label="{{seller.phone}}" />
</van-cell-group>
</van-tab>
</van-tabs>
</div>
</div>
</template>
<script>
import FoodCard from '@/components/FoodCard.vue'
import { mapGetters } from 'vuex'
export default {
components: { FoodCard },
data() {
return {
tab: 0
}
},
computed: {
seller() {
const id = Number(this.$route.params.id)
return this.sellerList.find(seller => seller.id === id) || {}
},
...mapGetters(['sellerList'])
}
}
</script>
<style scoped>
.seller-banner {
width: 100%;
height: 300px;
}
.seller-info {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
background-color: #eee;
}
.seller-name {
font-size: 20px;
}
.seller-ratings {
font-size: 16px;
}
.seller-sales {
font-size: 16px;
}
.foods {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
}
</style>
在上面代码中,我们使用了van-image、van-tabs等UI组件来完成商家详情信息的展现。在组件中,使用mapGetters方法帮助我们从store中访问到sellerList状态,通过路由传递的params.id来获取当前商家的详情数据。
至此,我们已经完成了整个Web应用的基础功能开发。
进一步实现复杂功能
我们基本实现了多个页面之间的路由切换和数据交互。接下来,我们可以进一步增加一些细节功能,如动画效果、搜索功能、购物车功能等等。
例如,为搜索框添加关键词联想功能,需要增加如下功能:
- 监听搜索框的输入,并通过API获取与关键词相关的商品;
- 将商品列表传递给Suggest组件;
- 在Suggest组件中展示商品信息,并响应用户的点击事件;
- 在Home.vue中,对商品的点击事件进行处理,更新搜索框内容并隐藏Suggest组件。
以下是对Home.vue组件的代码进行修改后实现的搜索功能:
```