一、vue-cli的使用及项目构建
1,vue-cli是vue项目的脚手架,可以协助我们快速搭建项目环境。
2,vue-cli的使用流程。
(1)使用npm安装大型包文件的时候,因为npm是一个国外平台,所以可能导致安装速度过慢,我们可以《淘宝镜像》的方式来加快安装速度。在命令窗口输入以下命令,可以让我们的电脑支持cnpm安装命令。
npm install -g cnpm --registry=https://registry.npm.taobao.org
(2)使用cnpm命令全局安装vue-cli的命令环境。
cnpm install -g vue-cli
(3)基于vue-cli环境所提供的命令集,进行vue工程项目目录的初始化。
vue init webpack my-app
【备注】上面的命令中的webpack代表项目模板类型,my-app代表项目文件夹的名称。
在这一步有很多项目相关的配置项需要我们自定义,例如,项目名称,项目简介等。为了初期学习的难易度考虑,我们只安装vue-router,所有测试相关(esLint,e2e,unit test)的功能包都选择不安装(No)。
在这一步执行完毕之后,我们会在指定目录下得到一个my-app文件夹,vue-cli已经在该文件夹中帮我们创建好了vue项目开发相关文件,但是这个my-app目录中的依赖包尚未安装,所以我们需要在该文件夹中执行后续步骤,安装对应的【开发依赖】与【生产依赖】相关的第三方包文件。
(4)安装项目依赖包文件(前提是在初始化的过程中您选择了自行安装)。
cd my-app //将命令行执行到当前项目文件夹下
cnpm install //为my-app项目安装依赖包
(5)如果正确执行了前面所有的步骤流程,那我们已经正确搭建了项目的基础目录,就可以运行项目并查看。
npm run dev
该命令会将我们项目的模板代码打包,并开启一个本地webpack-server服务,该服务会主动监听代码变化,并实时刷新浏览器,方便我们进行项目开发与测试。
(6)我们将在浏览器中输入如下地址,查看项目的运行情况。
http://localhost:8080
二,认识vue-cli项目目录结构
三,渲染商城首页
1,准备Nav.vue(导航)单文件组件。
在目录结构中的components下新建Nav.vue文件,并在内部写入组件的HTML结构与CSS样式等基础代码。
<template>
<div>
写入组件对应的HTML结构
</div>
</template>
<script>
export default {
写入组件相关的Vue逻辑代码
}
</script>
<style scoped>
写入组件对应的CSS样式
</style>
2,调整router文件夹下的index.js路由文件。
目的是当用户访问【根路由】的时候,可以看到我们在前面定义好的Nav.vue组件。
import Vue from 'vue'
import Router from 'vue-router'
import Nav from '@/components/Nav'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'Nav',
component: Nav
}
]
})
3,引入reset.css文件,清除默认样式。
在项目所有.vue单文件组件的后续开发过程中,我们都需要清除所有组件里面HTML标签的默认样式,操作流程如下:
(1)在项目的assets文件夹中,放入reset.css文件
(2)在main.js中使用import方法,全局引入reset.css文件,注意其引入的相对路径。
import Vue from 'vue'
import App from './App'
import router from './router'
import './assets/css/reset.css' //全局引入样式资源文件
Vue.config.productionTip = false
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
4,完成以上流程之后,我们将会在浏览器中,看到如下界面。
四,首页商品组件封装
1,将商品结构提取为一个独立的GoodItem.vue组件。
2,在Home.vue组件中,使用import方法引入GoodItem.vue组件,并进行相应配置。
import GoodItem from './GoodItem' //引入组件文件
export default {
components:{
"good-item":GoodItem //将子组件配置给Home组件
}
}
3,在Home.vue组件的HTML结构中调用子组件。
<good-item></good-item>
4,在Home.vue组件中,引入商品列表的模拟数据包goodsData.js数据包,并遍历生成多个商品组件。
(1)准备数据。
import GoodItem from './GoodItem'
import goodsData from '../assets/js/goodsData' //引入数据包
export default {
data() {
return {
goodsData //为组件对象设置数据包
}
},
components:{
"good-item":GoodItem
}
}
(2)调用数据
<good-item v-for="(item,index) in goodsData" v-bind:key="index" :g-data="item"></good-item>
5,在子组件GoodItem.vue中通过props获取父组件Home.vue传递的数据,并将数据绑定到子组件的HTML结构中。
(1)获取props数据。
export default {
props: {
gData: Object
}
}
(2)将数据绑定至HTML结构。
<div>
<div class="item-img">
<img :src="gData.stock_info[0].image" style="opacity: 1;">
</div>
<h6>{{gData.stock_info[0].title}}</h6>
<h3 >{{gData.stock_info[0].sub_title}}</h3>
</div>
6,保存后,观察浏览器界面变化,成功的界面如下。
五,商品类型切换
1,调整GoodItem.vue组件代码。
将上面每个商品下的用以切换的小圆点,根据数据包进行动态生成。并且将每个圆点的背景色做动态绑定,默认给第一个圆点添加class名称。
<div class="item-img">
<img :src="gData.stock_info[num].image">
</div>
<h6>{{gData.stock_info[num].title}}</h6>
<h3 >{{gData.stock_info[num].sub_title}}</h3>
<div class="params-colors">
<ul class="colors-list">
<li v-for="(stock,index) in gData.stock_info" :key="index" @click="change(index)">
<a href="javascript:;" :class="[index==num?'active':'']" :style="'background-color:'+stock.detail.color"></a>
</li>
</ul>
</div>
2,在组件的js代码区,新增data属性,并设置一个num,用以标志被切换到的商品序号。
3,为每个li绑定@click事件。
用以在点击的过程中,通过改变num的值,从而引起视图变化(图片、标题、价格等)。
export default {
props: {
gData: Object
},
data() {
return {
num: 0 //标志商品切换的数据
}
},
methods: {
change(i) { //完成商品切换行为的事件
this.num=i;
}
},
}
六,组件切换动画
在点击商品列表的【查看详情】按钮的时候,我们会使用路由跳转的方式,将原来显示Home.vue的区域,替换为商品详情Detail.vue组件,并为这个切换添加一个淡入淡出动画。
1,在components文件夹中新建Detail.vue组件,并添加对应HTML与CSS组件代码。
2,调整router文件夹下的index.js路由文件,让用户可以通过/detail路由访问到Detail.vue组件。
import Detail from '@/components/Detail'
```
export default new Router({ mode: ‘history’, routes: [ { path: ‘/‘, name: ‘Nav’, components: { default:Home, nav:Nav } }, { path: ‘/detail’, name: ‘Detail’, components: { default:Detail, nav:Nav } } ] })
**3,为GoodItem.vue组件中【查看详情】按钮包裹router-link。**
```html
<span class="item-gray-btn">
<router-link to="/detail">
查看详情
</router-link>
</span>
4,在App.vue中为router-view添加切换动画。
(1)为需要添加动画的router-view包裹transition标签。
<router-view name="nav"></router-view>
<transition name="fade">
<router-view/>
</transition>
(2)在App.vue中新增组件动画的CSS样式
.fade-enter,
.fade-leave-to{
opacity: 0;
}
.fade-enter-active{
transition: all 0.5s;
}
.fade-leave-active{
transition: all 0.3s;
}
.fade-enter-to,
.fade-leave{
opacity: 1;
}
七,使用vuex进行状态管理
1,在my-app项目目录下,使用如下命令安装vuex模块。
cnpm install vuex --save
2,在my-app项目的src目录下新建一个store文件夹,并在该文件夹下新建store.js文件,作为vuex对象模块。
3,在store.js中定义vuex对象,并对外抛出。
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
var store = new Vuex.Store({ //定义vuex对象
state:{
num:1
},
mutations:{
plus:function(state){
state.num++
}
}
})
export default store //对外抛出vuex对象
4,在main.js中调整其代码,将store.js提供的vuex对象注入到vue根对象下。
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store/store' //引入vuex实例化对象模块
import './assets/css/reset.css'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store, //将vuex的实例化对象注入到vue根对象中
components: { App },
template: '<App/>'
})
5,理解vuex对象中的各个配置项的含义及用途。
(1)state 相当于组件中的data
(2)getter 相当于组件中的computed
(3)mutation 相当于组件中的methods
6,通常我们会基于vuex对象做两个操作:
(1)获取vuex对象中定义的值,并绑定到组件中使用。需要在组件中使用如下代码获取vuex中的state数据。
export default {
computed: {
cartNum() {
return this.$store.state.num //这一句为核心代码
}
},
}
上面的核心代码的含义:
this 可以认为指向的是vue根对象
$store 可以视为我们在初始化vuex时候所注入的那个vuex对象
state 指的就是vuex实例对象里面的state
(2)触发vuex对象下的某个mutation,让其帮我们修改vuex中的state值。组件一般不直接操作vuex,而是通过mutation间接操作。
export default {
methods: {
plusFn(){
this.$store.commit('plus'); //注意,这里的plus要跟第3步中的mutations下的plus相对应
}
},
}
注意,这里的plus要跟第3步中的mutations下的plus相对应
7,如果vuex中的state发生变化,那么绑定了该state的所有组件中的值都会【同步发生变化】。
八,vuex中的getters使用流程
1,在store.js中的vuex对象下,新增getters属性,并添加响应逻辑代码用以计算购物车【商品总数】【商品总价】。
var store = new Vuex.Store({
state:{
num:1,
cartData:[] //购物车数据
},
getters:{
cartAllInfo:function(state){
var all=0; //存放购物车中商品总数量
var allPrice=0; //用以存放商品总价
var data = state.cartData;
var Len = data.length;
for(var i=0;i<Len;i++){
all+=data[i].num;
allPrice+=data[i].num*data[i].price
}
return {
'all':all,
'allPrice':allPrice
};
}
}
})
2,在需要使用getters提供的值的那个Cart.vue组件中,使用指定方法,获取getters的值。
computed: {
cartAllInfo(){
return this.$store.getters.cartAllInfo;
}
},
3,在Cart.vue组件的HTML结构中绑定并显示对应内容。
<div class="nav-cart-total">
<p>共 <strong class="ng-binding">{{cartAllInfo.all}}</strong> 件商品</p>
<h5>合计:
<span class="price-icon">¥</span>
<span class="price-num ng-binding" ng-bind="cartMenu.totalPrice">{{cartAllInfo.allPrice}}</span>
</h5>
<h6>
<a class="nav-cart-btn">
去购物车
</a>
</h6>
</div>
九,购物清单【单选】&【多选】
1,完成单选按钮的切换功能。
(1),在我们将商品加入购物车的时候,应该为商品数据对象新增一个isChecked属性,用以控制商品是否选中,如下面代码中第14行所示。
mutations:{
plus:function(state,obj){ //加入购物车的mutation
var id = obj.stock_id; //获取到新提交商品对象的id
var data = state.cartData; //获取购物车中已有的所有商品数据
var Len = data.length;
for(var i=0;i<Len;i++){
if(data[i].stock_id==id){
// var newNum = data[i].num+1;
state.cartData[i].num = data[i].num+1;
return false;
}
}
Vue.set(obj,'num',1); //通过响应式方式为对象设置新的属性,否则该属性的后期变化将无法被vue对象检测到
Vue.set(obj,'isChecked',true);
state.cartData.push(obj);
},
}
(2)在CartList.vue组件中,为每个商品的勾选按钮,动态绑定一个class来控制其选中状态。并绑定一个handleCheck事件用以触发其勾选状态的修改。
<div class="items-choose">
<span
class="blue-checkbox-new"
:class="{'checkbox-on':item.isChecked}"
@click="handleCheck(index)">
<a></a>
</span>
</div>
(3),当我们点击商品的勾选按钮的时候,触发一个mutaion,并将当前被点击按钮序号index提交给vuex对象。
methods: {
handleCheck(i) { //单选按钮事件
this.$store.commit('checkMut',i)
}
}
(4),在store.js中的vuex对象中,新增名为checkMut的mutation,并修改指定购物车商品的勾选状态isChecked。
mutations:{
checkMut:function(state,i){ //单选按钮的mutation
state.cartData[i].isChecked = !state.cartData[i].isChecked;
},
}
2,完成全选按钮的切换功能。
(1)在store.js中的getters中新增一个isAllChecked属性,让所有单个商品的isChecked来决定isAllChecked的状态。
getters:{
isAllChecked:function(state){
var data = state.cartData;
var Len = data.length;
var check = true; //全选与否的标志变量
for(var i=0;i<Len;i++){
if(!data[i].isChecked){ //如果发现购物车中有一个非选中的商品,说明全选按钮应该为非选中状态
check=false;
break;
}
}
return check;
}
}
【注意】getters里面return返回的数据有个特征,就是一旦state里面相关的值发生变化,getters里面会自动关联并重新计算。类似于组件中data与computed的关系。
(2)在CartList.vue组件中获取isAllChecked属性值。
computed: {
isAllChecked(){
return this.$store.getters.isAllChecked
}
}
(3)在CartList.vue组件中使用获取到的isAllChecked的属性,控制HTML结构状态变化。并为其绑定事件handleAllCheck,用以改变全选按钮状态。
<div class="choose-all js-choose-all">
<span
class="blue-checkbox-new "
:class="{'checkbox-on':isAllChecked}"
@click="handleAllCheck">
<a></a>
</span>
全选
</div>
(4)在handleAllCheck内部触发一个mutation,并将全选按钮的当前按钮的反向状态同步提交。
methods: {
handleAllCheck(){
this.$store.commit('allCheckMut',!this.isAllChecked);
//!this.isAllChecked的意义在于,如果当前已经是选中状态,我们必然需要将其切换为非选中状态
}
}
(5)在store.js中的vuex下的mutaions属性中新增allCheckMut,并将所有商品状态改为【对应的选中状态】
mutaions:{
allCheckMut:function(state,bool){ //全选按钮的mutation
var Len = state.cartData.length;
for(var i=0;i<Len;i++){
state.cartData[i].isChecked = bool;
}
}
}
十,项目功能的实现思路
1,如何打开详情页面?
涉及知识点:
(1).vue单文件组件的基本组成。(2)路由对象配置的流程。(3)组件切换的转场动画如何添加。
(2)打开页面时,如何传递参数?
2,如何在详情页中展示具体商品数据?
涉及知识点:
(1)从什么地方获取数据?如何获取?如何显示?(2)如何发起ajax请求,如何使用axios。(3)v-for v-if等
3,如何给组件内部元素添加交互?
涉及知识点:
(1)组件对象中都有哪些属性?(data,props,computed,methods,生命周期函数,watch)
(2)操作vuex的常见方法?
十一,商品详情页逻辑
1,在打开详情的路由的时候,携带一个【商品下标】参数。
(1)在router文件夹下的index.js给路由对象下的/detail路由追加【路由形参:gIdx】
{
path: '/detail/:gIdx',
name: 'Detail',
components: {
default:Detail,
nav:Nav
}
}
(2)在GoodItem.vue组件下,为【查看详情】按钮路由,新增【路由实参】
<router-link :to="'/detail/'+gIndex">
查看详情
</router-link>
【思考】gIndex从何而来?是在Home.vue组件中调用GoodItem组件的时候传递过来的。
<good-item
v-for="(item,index) in goodsData"
v-bind:key="index"
:g-data="item"
:g-index="index">
</good-item>
gIndex传参流程图如下:
(3)在详情页Detail.vue获取路由传递过来的gIdx值,然后以这个值从所有数据包中提取指定一条数据出来。
import goodData from '../assets/js/goodsData' //引入所有商品数据
export default {
data() {
return {
detailD: goodData[this.$route.params.gIdx],
smallNum:0, //小缩略图切换标志
num:0 //切换商品颜色
}
},
beforeMount(){
console.log(this.$route.params.gIdx) //获取路由携带的参数
}
}
(4)查看goodsData.js数据包文件,搞清楚上一步中detailD的具体格式,然后将对应数据渲染至Detail.vue的HTML结构中去。
<div class="sku-custom-title">
<div class="params-price">
<span><em>¥</em><i>{{detailD.stock_info[num].price}}</i></span>
</div>
<div class="params-info">
<h4>{{detailD.stock_info[num].title}}</h4>
<h6>{{detailD.stock_info[num].sub_title}}</h6>
</div>
</div>
十二,地址遮罩层的交互实现
【现场翻车点】
控制Address.vue显示与隐藏的方式有两种:
(1)第一种,将控制变量设置在Address.vue组件的props属性下。但是会影响后面的编辑地址功能,这种方法只是让组件内部的容器div在显示与隐藏,并不是让组件进行变化,所有无法在每次显示的时候重新出发【声明周期函数】。
(2)第二种,将控制变量设置在CheckOut.vue的组件调用之上,代码如下:
<address-pop
v-if="popShow" //这个v-if的变化会直接触发该组件的生命周期函数
@hide-click="popShow=false"
:edit-num='editnum'>
</address-pop>
【翻车代码】
1,准备确认订单组件CheckOut.vue,准备地址弹框组件AddressPop.vue。
2,在AddressPop.vue组件中,新增一个props,用以让外部组件(如CheckOut.vue)控制弹框的显示。
(1)设置props参数,并添加触发弹窗关闭的事件
props: {
isShow: {
type: Boolean,
default: false
}
},
methods: {
hide() {
this.$emit('hide-click') //这个$emit会触发CheckOut.vue组件中address-pop组件标签的事件
}
},
(2)使用props控制弹窗组件的根节点是否显示。
<div id="pop" v-if="isShow"></div>
3,在CheckOut.vue组件中,引入Address.vue组件,并使用props控制它。
(1)在js代码中引入并配置。
import AddressPop from './AddressPop'
export default {
data() {
return {
popShow: false
}
},
components:{
'address-pop':AddressPop
},
methods: {
realHide() {
this.popShow = false;
}
},
}
(2)在HTML结构中使用组件。
<address-pop :is-show="popShow" @hide-click="realHide"></address-pop>
//@hide-click是一个等待被AddressPop.vue内部触发的事件
4,遮罩交互的数据流程图。
【填坑代码流程】
这里的总结在期待您的表演~
十三,Javascript对象引用
一,普通变量的引用,只是值的复制
var a=1;
var b=a;
b=2;
alert(a); // a的值依旧为 1
二,对象的引用,不单是值的复制,同时也是内存空间的共享
var arr=[1,2,3];
var newArr = arr;
newArr.push(4);
alert(arr) //[1,2,3,4] ,两个变量指向同一个内存空间
三,拷贝继承
var eAddress={'username':'张三丰','phone':13954714785}
var obj={};
for(var attr in d){ //拷贝继承
obj[attr] = d[attr];
}
eAddress=obj //修改obj之后,将不再影响eAddress对象
十四,前端代码的打包与线上部署
1,得到百度云的账号信息。
登录百度云—【管理控制台】—【产品服务】—【云虚拟主机BCH】—【控制面板】—【初始化密码】—得到下图信息面板。
2,下载并安装FTP工具(以winscp为例),使用FTP工具连接百度云虚拟主机
3,连接成功之后,我们会得到如下面板:
4,在上图webroot目录下,上传商城项目中的assets资源文件夹。
使用如下路径测试资源文件是否上传成功
http://wenfeiwenfei.gz01.bdysite.com/assets/img/ui.png
//注意,域名要在第一步打开的自己的控制面板中去获取临时域名
5,在项目代码中,将所有的.vue单文件组件内图片的相对路径,替换为线上图片绝对路径。
例如:
将 ../assets/img
替换为 http://wenfeiwenfei.gz01.bdysite.com/assets/img
6,在vue工程项目目录下,执行以下命令进行打包。
npm run build
7,上面命令打包成功后,在项目目录下会得到一个dist文件夹。将该文件夹中的资源全部上传至云虚拟主机的webroot目录下【注意,这里不要再包裹任何文件夹】
8,使用你自己的临时域名,访问查看线上部署的结果。
http://wenfeiwenfei.gz01.bdysite.com
如果部署成功,则会得到一个跟开发阶段运行在http://localhost:8080上相同的项目
十五,iview组件库搭建管理界面
1,使用vue-cli提供的命令集初始化一个vue工程项目
vue init webpack myapp
2,进入myapp目录并安装相关依赖包文件。
cnpm install
3,使用命令运行基础工程项目,确认vue工程搭建成功。
npm run dev
4,在上面的vue工程目录中安装iview组件库包文件。
cnpm install iview --save
5,在myapp项目的main.js中,对iview进行初始化配置。
import Vue from 'vue'
import App from './App'
import router from './router'
import iView from 'iview'; //引入iview模块
import 'iview/dist/styles/iview.css'; //引入依赖样式
Vue.config.productionTip = false
Vue.use(iView);
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
render: h => h(App) //使用指定方法渲染vue根组件
})
6,在HelloWorld.vue中使用iview组件添加一个【iview按钮】。
(1)按需引入对应组件
import { Button, Table } from 'iview';
(2)将iview组件配置到HelloWorld组件当中。
export default {
copoments:{
Button,
Table
}
}
(3)在HelloWorld组件的HTML结构中调用iview提供的Button组件。
<Button type="primary">Primary</Button>
<Button type="dashed">Dashed</Button>