一、vue-cli的使用及项目构建

1,vue-cli是vue项目的脚手架,可以协助我们快速搭建项目环境。

2,vue-cli的使用流程。
(1)使用npm安装大型包文件的时候,因为npm是一个国外平台,所以可能导致安装速度过慢,我们可以《淘宝镜像》的方式来加快安装速度。在命令窗口输入以下命令,可以让我们的电脑支持cnpm安装命令。

  1. npm install -g cnpm --registry=https://registry.npm.taobao.org

(2)使用cnpm命令全局安装vue-cli的命令环境。

  1. cnpm install -g vue-cli

(3)基于vue-cli环境所提供的命令集,进行vue工程项目目录的初始化。

  1. vue init webpack my-app

【备注】上面的命令中的webpack代表项目模板类型,my-app代表项目文件夹的名称。
在这一步有很多项目相关的配置项需要我们自定义,例如,项目名称,项目简介等。为了初期学习的难易度考虑,我们只安装vue-router,所有测试相关(esLint,e2e,unit test)的功能包都选择不安装(No)。
在这一步执行完毕之后,我们会在指定目录下得到一个my-app文件夹,vue-cli已经在该文件夹中帮我们创建好了vue项目开发相关文件,但是这个my-app目录中的依赖包尚未安装,所以我们需要在该文件夹中执行后续步骤,安装对应的【开发依赖】与【生产依赖】相关的第三方包文件。

(4)安装项目依赖包文件(前提是在初始化的过程中您选择了自行安装)。

  1. cd my-app //将命令行执行到当前项目文件夹下
  2. cnpm install //为my-app项目安装依赖包


(5)如果正确执行了前面所有的步骤流程,那我们已经正确搭建了项目的基础目录,就可以运行项目并查看。

  1. npm run dev

该命令会将我们项目的模板代码打包,并开启一个本地webpack-server服务,该服务会主动监听代码变化,并实时刷新浏览器,方便我们进行项目开发与测试。

(6)我们将在浏览器中输入如下地址,查看项目的运行情况。

  1. http://localhost:8080

二,认识vue-cli项目目录结构

smartisan商城实战之前端篇 - 图1

三,渲染商城首页

1,准备Nav.vue(导航)单文件组件。
在目录结构中的components下新建Nav.vue文件,并在内部写入组件的HTML结构与CSS样式等基础代码。

  1. <template>
  2. <div>
  3. 写入组件对应的HTML结构
  4. </div>
  5. </template>
  6. <script>
  7. export default {
  8. 写入组件相关的Vue逻辑代码
  9. }
  10. </script>
  11. <style scoped>
  12. 写入组件对应的CSS样式
  13. </style>

2,调整router文件夹下的index.js路由文件。
目的是当用户访问【根路由】的时候,可以看到我们在前面定义好的Nav.vue组件。

  1. import Vue from 'vue'
  2. import Router from 'vue-router'
  3. import Nav from '@/components/Nav'
  4. Vue.use(Router)
  5. export default new Router({
  6. routes: [
  7. {
  8. path: '/',
  9. name: 'Nav',
  10. component: Nav
  11. }
  12. ]
  13. })

3,引入reset.css文件,清除默认样式。
在项目所有.vue单文件组件的后续开发过程中,我们都需要清除所有组件里面HTML标签的默认样式,操作流程如下:
(1)在项目的assets文件夹中,放入reset.css文件
(2)在main.js中使用import方法,全局引入reset.css文件,注意其引入的相对路径。

  1. import Vue from 'vue'
  2. import App from './App'
  3. import router from './router'
  4. import './assets/css/reset.css' //全局引入样式资源文件
  5. Vue.config.productionTip = false
  6. new Vue({
  7. el: '#app',
  8. router,
  9. components: { App },
  10. template: '<App/>'
  11. })

4,完成以上流程之后,我们将会在浏览器中,看到如下界面。
smartisan商城实战之前端篇 - 图2

四,首页商品组件封装

1,将商品结构提取为一个独立的GoodItem.vue组件。

2,在Home.vue组件中,使用import方法引入GoodItem.vue组件,并进行相应配置。

  1. import GoodItem from './GoodItem' //引入组件文件
  2. export default {
  3. components:{
  4. "good-item":GoodItem //将子组件配置给Home组件
  5. }
  6. }

3,在Home.vue组件的HTML结构中调用子组件。

  1. <good-item></good-item>

4,在Home.vue组件中,引入商品列表的模拟数据包goodsData.js数据包,并遍历生成多个商品组件。
(1)准备数据。

  1. import GoodItem from './GoodItem'
  2. import goodsData from '../assets/js/goodsData' //引入数据包
  3. export default {
  4. data() {
  5. return {
  6. goodsData //为组件对象设置数据包
  7. }
  8. },
  9. components:{
  10. "good-item":GoodItem
  11. }
  12. }

(2)调用数据

  1. <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数据。

  1. export default {
  2. props: {
  3. gData: Object
  4. }
  5. }

(2)将数据绑定至HTML结构。

  1. <div>
  2. <div class="item-img">
  3. <img :src="gData.stock_info[0].image" style="opacity: 1;">
  4. </div>
  5. <h6>{{gData.stock_info[0].title}}</h6>
  6. <h3 >{{gData.stock_info[0].sub_title}}</h3>
  7. </div>

6,保存后,观察浏览器界面变化,成功的界面如下。
smartisan商城实战之前端篇 - 图3

五,商品类型切换

1,调整GoodItem.vue组件代码。
将上面每个商品下的用以切换的小圆点,根据数据包进行动态生成。并且将每个圆点的背景色做动态绑定,默认给第一个圆点添加class名称。

  1. <div class="item-img">
  2. <img :src="gData.stock_info[num].image">
  3. </div>
  4. <h6>{{gData.stock_info[num].title}}</h6>
  5. <h3 >{{gData.stock_info[num].sub_title}}</h3>
  6. <div class="params-colors">
  7. <ul class="colors-list">
  8. <li v-for="(stock,index) in gData.stock_info" :key="index" @click="change(index)">
  9. <a href="javascript:;" :class="[index==num?'active':'']" :style="'background-color:'+stock.detail.color"></a>
  10. </li>
  11. </ul>
  12. </div>

2,在组件的js代码区,新增data属性,并设置一个num,用以标志被切换到的商品序号。

3,为每个li绑定@click事件。
用以在点击的过程中,通过改变num的值,从而引起视图变化(图片、标题、价格等)。

  1. export default {
  2. props: {
  3. gData: Object
  4. },
  5. data() {
  6. return {
  7. num: 0 //标志商品切换的数据
  8. }
  9. },
  10. methods: {
  11. change(i) { //完成商品切换行为的事件
  12. this.num=i;
  13. }
  14. },
  15. }

六,组件切换动画

  1. 在点击商品列表的【查看详情】按钮的时候,我们会使用路由跳转的方式,将原来显示Home.vue的区域,替换为商品详情Detail.vue组件,并为这个切换添加一个淡入淡出动画。

1,在components文件夹中新建Detail.vue组件,并添加对应HTML与CSS组件代码。

2,调整router文件夹下的index.js路由文件,让用户可以通过/detail路由访问到Detail.vue组件。

  1. import Detail from '@/components/Detail'
  2. ```

export default new Router({ mode: ‘history’, routes: [ { path: ‘/‘, name: ‘Nav’, components: { default:Home, nav:Nav } }, { path: ‘/detail’, name: ‘Detail’, components: { default:Detail, nav:Nav } } ] })

  1. **3,为GoodItem.vue组件中【查看详情】按钮包裹router-link。**
  2. ```html
  3. <span class="item-gray-btn">
  4. <router-link to="/detail">
  5. 查看详情
  6. </router-link>
  7. </span>

4,在App.vue中为router-view添加切换动画。
(1)为需要添加动画的router-view包裹transition标签。

  1. <router-view name="nav"></router-view>
  2. <transition name="fade">
  3. <router-view/>
  4. </transition>

(2)在App.vue中新增组件动画的CSS样式

  1. .fade-enter,
  2. .fade-leave-to{
  3. opacity: 0;
  4. }
  5. .fade-enter-active{
  6. transition: all 0.5s;
  7. }
  8. .fade-leave-active{
  9. transition: all 0.3s;
  10. }
  11. .fade-enter-to,
  12. .fade-leave{
  13. opacity: 1;
  14. }

七,使用vuex进行状态管理

1,在my-app项目目录下,使用如下命令安装vuex模块。

  1. cnpm install vuex --save

2,在my-app项目的src目录下新建一个store文件夹,并在该文件夹下新建store.js文件,作为vuex对象模块。

3,在store.js中定义vuex对象,并对外抛出。

  1. import Vue from 'vue'
  2. import Vuex from 'vuex'
  3. Vue.use(Vuex);
  4. var store = new Vuex.Store({ //定义vuex对象
  5. state:{
  6. num:1
  7. },
  8. mutations:{
  9. plus:function(state){
  10. state.num++
  11. }
  12. }
  13. })
  14. export default store //对外抛出vuex对象

4,在main.js中调整其代码,将store.js提供的vuex对象注入到vue根对象下。

  1. import Vue from 'vue'
  2. import App from './App'
  3. import router from './router'
  4. import store from './store/store' //引入vuex实例化对象模块
  5. import './assets/css/reset.css'
  6. Vue.config.productionTip = false
  7. /* eslint-disable no-new */
  8. new Vue({
  9. el: '#app',
  10. router,
  11. store, //将vuex的实例化对象注入到vue根对象中
  12. components: { App },
  13. template: '<App/>'
  14. })

5,理解vuex对象中的各个配置项的含义及用途。
(1)state 相当于组件中的data
(2)getter 相当于组件中的computed
(3)mutation 相当于组件中的methods

6,通常我们会基于vuex对象做两个操作:
(1)获取vuex对象中定义的值,并绑定到组件中使用。需要在组件中使用如下代码获取vuex中的state数据。

  1. export default {
  2. computed: {
  3. cartNum() {
  4. return this.$store.state.num //这一句为核心代码
  5. }
  6. },
  7. }

上面的核心代码的含义:
this 可以认为指向的是vue根对象
$store 可以视为我们在初始化vuex时候所注入的那个vuex对象
state 指的就是vuex实例对象里面的state

(2)触发vuex对象下的某个mutation,让其帮我们修改vuex中的state值。组件一般不直接操作vuex,而是通过mutation间接操作。

  1. export default {
  2. methods: {
  3. plusFn(){
  4. this.$store.commit('plus'); //注意,这里的plus要跟第3步中的mutations下的plus相对应
  5. }
  6. },
  7. }

注意,这里的plus要跟第3步中的mutations下的plus相对应

7,如果vuex中的state发生变化,那么绑定了该state的所有组件中的值都会【同步发生变化】。

八,vuex中的getters使用流程

1,在store.js中的vuex对象下,新增getters属性,并添加响应逻辑代码用以计算购物车【商品总数】【商品总价】。

  1. var store = new Vuex.Store({
  2. state:{
  3. num:1,
  4. cartData:[] //购物车数据
  5. },
  6. getters:{
  7. cartAllInfo:function(state){
  8. var all=0; //存放购物车中商品总数量
  9. var allPrice=0; //用以存放商品总价
  10. var data = state.cartData;
  11. var Len = data.length;
  12. for(var i=0;i<Len;i++){
  13. all+=data[i].num;
  14. allPrice+=data[i].num*data[i].price
  15. }
  16. return {
  17. 'all':all,
  18. 'allPrice':allPrice
  19. };
  20. }
  21. }
  22. })

2,在需要使用getters提供的值的那个Cart.vue组件中,使用指定方法,获取getters的值。

  1. computed: {
  2. cartAllInfo(){
  3. return this.$store.getters.cartAllInfo;
  4. }
  5. },

3,在Cart.vue组件的HTML结构中绑定并显示对应内容。

  1. <div class="nav-cart-total">
  2. <p><strong class="ng-binding">{{cartAllInfo.all}}</strong> 件商品</p>
  3. <h5>合计:
  4. <span class="price-icon">¥</span>
  5. <span class="price-num ng-binding" ng-bind="cartMenu.totalPrice">{{cartAllInfo.allPrice}}</span>
  6. </h5>
  7. <h6>
  8. <a class="nav-cart-btn">
  9. 去购物车
  10. </a>
  11. </h6>
  12. </div>

九,购物清单【单选】&【多选】

1,完成单选按钮的切换功能。

(1),在我们将商品加入购物车的时候,应该为商品数据对象新增一个isChecked属性,用以控制商品是否选中,如下面代码中第14行所示。

  1. mutations:{
  2. plus:function(state,obj){ //加入购物车的mutation
  3. var id = obj.stock_id; //获取到新提交商品对象的id
  4. var data = state.cartData; //获取购物车中已有的所有商品数据
  5. var Len = data.length;
  6. for(var i=0;i<Len;i++){
  7. if(data[i].stock_id==id){
  8. // var newNum = data[i].num+1;
  9. state.cartData[i].num = data[i].num+1;
  10. return false;
  11. }
  12. }
  13. Vue.set(obj,'num',1); //通过响应式方式为对象设置新的属性,否则该属性的后期变化将无法被vue对象检测到
  14. Vue.set(obj,'isChecked',true);
  15. state.cartData.push(obj);
  16. },
  17. }

(2)在CartList.vue组件中,为每个商品的勾选按钮,动态绑定一个class来控制其选中状态。并绑定一个handleCheck事件用以触发其勾选状态的修改。

  1. <div class="items-choose">
  2. <span
  3. class="blue-checkbox-new"
  4. :class="{'checkbox-on':item.isChecked}"
  5. @click="handleCheck(index)">
  6. <a></a>
  7. </span>
  8. </div>

(3),当我们点击商品的勾选按钮的时候,触发一个mutaion,并将当前被点击按钮序号index提交给vuex对象。

  1. methods: {
  2. handleCheck(i) { //单选按钮事件
  3. this.$store.commit('checkMut',i)
  4. }
  5. }

(4),在store.js中的vuex对象中,新增名为checkMut的mutation,并修改指定购物车商品的勾选状态isChecked。

  1. mutations:{
  2. checkMut:function(state,i){ //单选按钮的mutation
  3. state.cartData[i].isChecked = !state.cartData[i].isChecked;
  4. },
  5. }

2,完成选按钮的切换功能。

(1)在store.js中的getters中新增一个isAllChecked属性,让所有单个商品的isChecked来决定isAllChecked的状态。

  1. getters:{
  2. isAllChecked:function(state){
  3. var data = state.cartData;
  4. var Len = data.length;
  5. var check = true; //全选与否的标志变量
  6. for(var i=0;i<Len;i++){
  7. if(!data[i].isChecked){ //如果发现购物车中有一个非选中的商品,说明全选按钮应该为非选中状态
  8. check=false;
  9. break;
  10. }
  11. }
  12. return check;
  13. }
  14. }

【注意】getters里面return返回的数据有个特征,就是一旦state里面相关的值发生变化,getters里面会自动关联并重新计算。类似于组件中data与computed的关系。

(2)在CartList.vue组件中获取isAllChecked属性值。

  1. computed: {
  2. isAllChecked(){
  3. return this.$store.getters.isAllChecked
  4. }
  5. }

(3)在CartList.vue组件中使用获取到的isAllChecked的属性,控制HTML结构状态变化。并为其绑定事件handleAllCheck,用以改变全选按钮状态。

  1. <div class="choose-all js-choose-all">
  2. <span
  3. class="blue-checkbox-new "
  4. :class="{'checkbox-on':isAllChecked}"
  5. @click="handleAllCheck">
  6. <a></a>
  7. </span>
  8. 全选
  9. </div>

(4)在handleAllCheck内部触发一个mutation,并将全选按钮的当前按钮的反向状态同步提交。

  1. methods: {
  2. handleAllCheck(){
  3. this.$store.commit('allCheckMut',!this.isAllChecked);
  4. //!this.isAllChecked的意义在于,如果当前已经是选中状态,我们必然需要将其切换为非选中状态
  5. }
  6. }

(5)在store.js中的vuex下的mutaions属性中新增allCheckMut,并将所有商品状态改为【对应的选中状态】

  1. mutaions:{
  2. allCheckMut:function(state,bool){ //全选按钮的mutation
  3. var Len = state.cartData.length;
  4. for(var i=0;i<Len;i++){
  5. state.cartData[i].isChecked = bool;
  6. }
  7. }
  8. }

十,项目功能的实现思路

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

  1. {
  2. path: '/detail/:gIdx',
  3. name: 'Detail',
  4. components: {
  5. default:Detail,
  6. nav:Nav
  7. }
  8. }

(2)在GoodItem.vue组件下,为【查看详情】按钮路由,新增【路由实参】

  1. <router-link :to="'/detail/'+gIndex">
  2. 查看详情
  3. </router-link>

【思考】gIndex从何而来?是在Home.vue组件中调用GoodItem组件的时候传递过来的。

  1. <good-item
  2. v-for="(item,index) in goodsData"
  3. v-bind:key="index"
  4. :g-data="item"
  5. :g-index="index">
  6. </good-item>

gIndex传参流程图如下:
smartisan商城实战之前端篇 - 图4

(3)在详情页Detail.vue获取路由传递过来的gIdx值,然后以这个值从所有数据包中提取指定一条数据出来。

  1. import goodData from '../assets/js/goodsData' //引入所有商品数据
  2. export default {
  3. data() {
  4. return {
  5. detailD: goodData[this.$route.params.gIdx],
  6. smallNum:0, //小缩略图切换标志
  7. num:0 //切换商品颜色
  8. }
  9. },
  10. beforeMount(){
  11. console.log(this.$route.params.gIdx) //获取路由携带的参数
  12. }
  13. }

(4)查看goodsData.js数据包文件,搞清楚上一步中detailD的具体格式,然后将对应数据渲染至Detail.vue的HTML结构中去。

  1. <div class="sku-custom-title">
  2. <div class="params-price">
  3. <span><em>¥</em><i>{{detailD.stock_info[num].price}}</i></span>
  4. </div>
  5. <div class="params-info">
  6. <h4>{{detailD.stock_info[num].title}}</h4>
  7. <h6>{{detailD.stock_info[num].sub_title}}</h6>
  8. </div>
  9. </div>

十二,地址遮罩层的交互实现

【现场翻车点】

控制Address.vue显示与隐藏的方式有两种:
(1)第一种,将控制变量设置在Address.vue组件的props属性下。但是会影响后面的编辑地址功能,这种方法只是让组件内部的容器div在显示与隐藏,并不是让组件进行变化,所有无法在每次显示的时候重新出发【声明周期函数】。
(2)第二种,将控制变量设置在CheckOut.vue的组件调用之上,代码如下:

  1. <address-pop
  2. v-if="popShow" //这个v-if的变化会直接触发该组件的生命周期函数
  3. @hide-click="popShow=false"
  4. :edit-num='editnum'>
  5. </address-pop>

【翻车代码】

1,准备确认订单组件CheckOut.vue,准备地址弹框组件AddressPop.vue。

2,在AddressPop.vue组件中,新增一个props,用以让外部组件(如CheckOut.vue)控制弹框的显示。
(1)设置props参数,并添加触发弹窗关闭的事件

  1. props: {
  2. isShow: {
  3. type: Boolean,
  4. default: false
  5. }
  6. },
  7. methods: {
  8. hide() {
  9. this.$emit('hide-click') //这个$emit会触发CheckOut.vue组件中address-pop组件标签的事件
  10. }
  11. },

(2)使用props控制弹窗组件的根节点是否显示。

  1. <div id="pop" v-if="isShow"></div>

3,在CheckOut.vue组件中,引入Address.vue组件,并使用props控制它。
(1)在js代码中引入并配置。

  1. import AddressPop from './AddressPop'
  2. export default {
  3. data() {
  4. return {
  5. popShow: false
  6. }
  7. },
  8. components:{
  9. 'address-pop':AddressPop
  10. },
  11. methods: {
  12. realHide() {
  13. this.popShow = false;
  14. }
  15. },
  16. }

(2)在HTML结构中使用组件。

  1. <address-pop :is-show="popShow" @hide-click="realHide"></address-pop>
  2. //@hide-click是一个等待被AddressPop.vue内部触发的事件

4,遮罩交互的数据流程图。
smartisan商城实战之前端篇 - 图5

【填坑代码流程】

这里的总结在期待您的表演~

十三,Javascript对象引用

一,普通变量的引用,只是值的复制

  1. var a=1;
  2. var b=a;
  3. b=2;
  4. alert(a); // a的值依旧为 1


二,对象的引用,不单是值的复制,同时也是内存空间的共享

  1. var arr=[1,2,3];
  2. var newArr = arr;
  3. newArr.push(4);
  4. alert(arr) //[1,2,3,4] ,两个变量指向同一个内存空间


三,拷贝继承

  1. var eAddress={'username':'张三丰','phone'13954714785}
  2. var obj={};
  3. for(var attr in d){ //拷贝继承
  4. obj[attr] = d[attr];
  5. }
  6. eAddress=obj //修改obj之后,将不再影响eAddress对象

十四,前端代码的打包与线上部署

1,得到百度云的账号信息。
登录百度云—【管理控制台】—【产品服务】—【云虚拟主机BCH】—【控制面板】—【初始化密码】—得到下图信息面板。
smartisan商城实战之前端篇 - 图6

2,下载并安装FTP工具(以winscp为例),使用FTP工具连接百度云虚拟主机
smartisan商城实战之前端篇 - 图7

3,连接成功之后,我们会得到如下面板:
smartisan商城实战之前端篇 - 图8

4,在上图webroot目录下,上传商城项目中的assets资源文件夹。
使用如下路径测试资源文件是否上传成功

  1. http://wenfeiwenfei.gz01.bdysite.com/assets/img/ui.png
  2. //注意,域名要在第一步打开的自己的控制面板中去获取临时域名

5,在项目代码中,将所有的.vue单文件组件内图片的相对路径,替换为线上图片绝对路径。
例如:

  1. ../assets/img
  2. 替换为 http://wenfeiwenfei.gz01.bdysite.com/assets/img

6,在vue工程项目目录下,执行以下命令进行打包。

  1. npm run build

7,上面命令打包成功后,在项目目录下会得到一个dist文件夹。将该文件夹中的资源全部上传至云虚拟主机的webroot目录下【注意,这里不要再包裹任何文件夹】
smartisan商城实战之前端篇 - 图9

8,使用你自己的临时域名,访问查看线上部署的结果。

  1. http://wenfeiwenfei.gz01.bdysite.com

如果部署成功,则会得到一个跟开发阶段运行在http://localhost:8080上相同的项目

十五,iview组件库搭建管理界面

1,使用vue-cli提供的命令集初始化一个vue工程项目

  1. vue init webpack myapp

2,进入myapp目录并安装相关依赖包文件。

  1. cnpm install

3,使用命令运行基础工程项目,确认vue工程搭建成功。

  1. npm run dev

4,在上面的vue工程目录中安装iview组件库包文件。

  1. cnpm install iview --save

5,在myapp项目的main.js中,对iview进行初始化配置。

  1. import Vue from 'vue'
  2. import App from './App'
  3. import router from './router'
  4. import iView from 'iview'; //引入iview模块
  5. import 'iview/dist/styles/iview.css'; //引入依赖样式
  6. Vue.config.productionTip = false
  7. Vue.use(iView);
  8. /* eslint-disable no-new */
  9. new Vue({
  10. el: '#app',
  11. router,
  12. render: h => h(App) //使用指定方法渲染vue根组件
  13. })

6,在HelloWorld.vue中使用iview组件添加一个【iview按钮】。
(1)按需引入对应组件

  1. import { Button, Table } from 'iview';

(2)将iview组件配置到HelloWorld组件当中。

  1. export default {
  2. copoments:{
  3. Button,
  4. Table
  5. }
  6. }

(3)在HelloWorld组件的HTML结构中调用iview提供的Button组件。

  1. <Button type="primary">Primary</Button>
  2. <Button type="dashed">Dashed</Button>