Vue移动端商城项目

1 准备基本的项目模板

  • 文件结构如下(用之前webpack配置好的文件目录)

    Vue移动端商城项目_第1张图片

  • index.html

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<div id="app">
    
</div>
</body>
</html>
  • router.js
import VueRouter from 'vue-router'

//创建路由对象
var router=new VueRouter({
    routes:[]
})

//导出路由对象
export default router
  • app.vue
<template>
   <div>
      <h1>app组件</h1>
   </div>

</template>
<script>

</script>

<style lang="sass" scoped>

</style>

2 实现首页布局和动画效果

  • 顶部header实现

    在main.js中导入vue包和引入app.vue组件

//入口文件
import Vue from 'vue'

import app from './App.vue'

var vm=new Vue({
    el:'#app',
    render:c=>c(app)
})

使用mint.ui提供的组件
app.vue

 <!-- 顶部header区域 -->
      <!-- 引入mint-ui的header -->
      <mt-header fixed title="欣子的商城"></mt-header>

按需导入mint-ui的header组件
main.js

import { Header } from 'mint-ui';
Vue.component(Header.name, Header);

给mt-header标签的父元素div设置padding值 防止文字出现在顶部导航栏的底下
app.vue

 <template>
   <div class="app-container">
      <!-- 顶部header区域 -->
      <!-- 引入mint-ui的header -->
      <mt-header fixed title="欣子的商城"></mt-header>

      <h1>app组件</h1>
   </div>

</template>
<script>

</script>

<style lang="scss" scoped>
    .app-container{
       padding-top:40px
    }
</style>

npm run dev运行
在这里插入图片描述

  • 实现底部tabbar
    使用MUI的代码片段

    app.vue


      
      
      

在main.js导入MUI样式文件

//导入MUI的样式
import './lib/mui-master/dist/css/mui.min.css'

Vue移动端商城项目_第2张图片

  • 完成底部小图标的设置
    修改之前的MUI的代码片段
<nav class="mui-bar mui-bar-tab">
			<a class="mui-tab-item mui-active" href="#tabbar">
				<span class="mui-icon mui-icon-home"></span>
				<span class="mui-tab-label">首页</span>
			</a>
			<a class="mui-tab-item" href="#tabbar-with-chat">
				<span class="mui-icon mui-icon-contact">
					
				</span>
				<span class="mui-tab-label">会员</span>
			</a>
			<a class="mui-tab-item" href="#tabbar-with-contact">
				<!-- 使用MUI icon-extra的图标类名 -->
				<span class="mui-icon mui-icon-extra mui-icon-extra-cart">
					<span class="mui-badge">0</span>
				</span>
				<span class="mui-tab-label">购物车</span>
			</a>
			<a class="mui-tab-item" href="#tabbar-with-map">
				<span class="mui-icon mui-icon-search"></span>
				<span class="mui-tab-label">搜索</span>
			</a>
		</nav>

在main.js导入icon.extra图标所需要的css文件

import './lib/mui-master/examples/hello-mui/css/icons-extra.css'
  • 完成tabbar路由链接的改造和路由高亮显示
    在main.js导入路由的包和挂载路由对象
//导入路由的包
import VueRouter from 'vue-router'
Vue.use(VueRouter)
import router from './router.js'
var vm=new Vue({
    el:'#app',
    render:c=>c(app),
    router
})

修改MUI代码片段

<nav class="mui-bar mui-bar-tab">
			<router-link class="mui-tab-item" to="/home">
				<span class="mui-icon mui-icon-home"></span>
				<span class="mui-tab-label">首页</span>
			</router-link>
			<router-link class="mui-tab-item" to="/member">
				<span class="mui-icon mui-icon-contact">
					
				</span>
				<span class="mui-tab-label">会员</span>
			</router-link>
			<router-link class="mui-tab-item" to="/shopcar">
				<!-- 使用MUI icon-extra的图标类名 -->
				<span class="mui-icon mui-icon-extra mui-icon-extra-cart">
					<span class="mui-badge">0</span>
				</span>
				<span class="mui-tab-label">购物车</span>
			</router-link>
			<router-link class="mui-tab-item" to="/search">
				<span class="mui-icon mui-icon-search"></span>
				<span class="mui-tab-label">搜索</span>
			</router-link>
		</nav>

在router.js中设置路由高亮的类

import VueRouter from 'vue-router'

//创建路由对象
var router=new VueRouter({
    routes:[

    ],
    linkActiveClass:'mui-active'//覆盖默认的路由高亮的类(使用MUI提供的图标高亮显示的类名来覆盖)
})

//导出路由对象
export default router

Vue移动端商城项目_第3张图片

  • 实现tabbar路由组件的切换
    将MUI的代码片段的a标签改为router-link 并将herf改为to 修改路径名称
 <nav class="mui-bar mui-bar-tab">
			<router-link class="mui-tab-item" to="/home">
				<span class="mui-icon mui-icon-home"></span>
				<span class="mui-tab-label">首页</span>
			</router-link>
			<router-link class="mui-tab-item" to="/member">
				<span class="mui-icon mui-icon-contact">
					
				</span>
				<span class="mui-tab-label">会员</span>
			</router-link>
			<router-link class="mui-tab-item" to="/shopcar">
				<!-- 使用MUI icon-extra的图标类名 -->
				<span class="mui-icon mui-icon-extra mui-icon-extra-cart">
					<span class="mui-badge">0</span>
				</span>
				<span class="mui-tab-label">购物车</span>
			</router-link>
			<router-link class="mui-tab-item" to="/search">
				<span class="mui-icon mui-icon-search"></span>
				<span class="mui-tab-label">搜索</span>
			</router-link>
		</nav>

在src文件夹下创建components文件夹 在其下创建tabbar文件夹 新建子组件文件
Vue移动端商城项目_第4张图片
初始化子组件的内容
HomeContainer.vue(另外三个子组件同样添加以下代码)

<template>
    <div>
        <h1>HomeContainer</h1>
    </div>
</template>

<script>

</script>

<style lang="scss" scoped>

</style>

在router.js中导入vue-router包 和 子组件 并创建路由对象配置对应的路由关系

import VueRouter from 'vue-router'

//导入对应的路由组件
import HomeContainer from './components/tabbar/HomeContainer.vue'
import MemberContainer from './components/tabbar/MemberContainer.vue'
import ShopcarContainer from './components/tabbar/ShopcarContainer.vue'
import SearchContainer from './components/tabbar/SearchContainer.vue'
//创建路由对象
var router=new VueRouter({
    routes:[//配置路由对应关系
        {path:'/home',component:HomeContainer},
        {path:'/member',component:MemberContainer},
        {path:'/shopcar',component:ShopcarContainer},
        {path:'/search',component:SearchContainer}
    ],
    linkActiveClass:'mui-active'//覆盖默认的路由高亮的类(使用MUI提供的图标高亮显示的类名来覆盖)
})

在app.vue中让如组件展示对应的容器router-view

<template>
   <div class="app-container">
      <!-- 顶部header区域 -->
      <!-- 引入mint-ui的header -->
      <mt-header fixed title="欣子的商城"></mt-header>

      <!-- 中间的路由router-view区域 -->
	  <router-view></router-view>

      <!-- 底部tabber区域 -->
      <!-- 复制MUI的代码片段 -->

Vue移动端商城项目_第5张图片Vue移动端商城项目_第6张图片

  • 完成轮播图效果
  • 使用mint-ui提供的swipe
    main.js
//按需导入mint-ui的header swiper组件
import { Header,Swipe, SwipeItem } from 'mint-ui';
Vue.component(Header.name, Header);
Vue.component(Swipe.name, Swipe);
Vue.component(SwipeItem.name, SwipeItem);

HomeCotainer.vue

<mt-swipe :auto="4000">
        <mt-swipe-item>1</mt-swipe-item>
        <mt-swipe-item>2</mt-swipe-item>
        <mt-swipe-item>3</mt-swipe-item>
      </mt-swipe>

设置相关样式要给父元素一个高度轮播图才能显示出来

<style lang="scss" scoped>
.mint-swipe{
    height: 200px;
    .mint-swipe-item{
    &:nth-child(1){
        background-color: red;
    }
    &:nth-child(2){
        background-color: yellow;
    }
    &:nth-child(3){
        background-color: green;
    }
    img{
        width: 100%;
        height: 100%;
    }
 }
}

</style>
  • 完成首页中轮播图数据的加载

在main.js导入vue.resource的包

import VueResource from 'vue-resource'
Vue.use(VueResource)

用vueResource的this.$http.get获取数据 并将数据保存到data上
HomeContainer.vue

 import {Toast} from 'mint-ui'
  export default{
      data(){
          return{
              img:'../../img/img1.jpg',
              lunboList:[]//保存轮播图的数组
          }
      },
      created(){
        this.getLunbo()  
      },
      methods:{
          //获取轮播图
          getLunbo(){
              this.$http.get('api/getlunbo').then(result=>{
                //   console.log(result.body)
                if(result.body.status===0){
                    this.lunboList=result.body.message;
                    //拼接一个对象代替加载不出来的那张轮播图
                    var obj={
                        id:2,
                        url:'http://www.itcast.cn/subject/phoneweb/index.html',
                        img:'../../img/img1.jpg'
                    }
                    this.lunboList.splice(1,1,obj)
                    console.log( this.lunboList)
                }else{
                    Toast('加载轮播失败')
                }
              })
          }
      }
  }

用v-for循环渲染图片

<template>
    <div>
     <mt-swipe :auto="4000">
         <!-- 由于每一个url地址都是唯一的 可以使用item.url当key -->
        <mt-swipe-item v-for="item in lunboList" :key="item.url">
            <img :src="item.img">
        </mt-swipe-item>
      </mt-swipe>
    </div>

</template>
  • 完成9宫格布局效果

将body的整体颜色改为白色
index.html

<body style="background-color: white !important">

使用MUI提供的grid-default

注意:当 file-loader 的版本是 4.3.0 及以上,则需要在 webpack.config.js 中手动配置属性 esModule :否则图片会显示不出

 {
                test: /\.(jpg|jpeg|png|gif|svg)$/,
                loader: "url-loader",
                // limit:8834,
                options: {
                  esModule: false, // 默认值是 true,需要手动改成 false
                  name: "[hash:8]-[name].[ext]"
                }
              },

Homecontainer.vue

<!--使用MUI提供的gird-default 六宫格  -->
      <ul class="mui-table-view mui-grid-view mui-grid-9">
		    <li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3">
                <a href="#">
		           <img src="../../img/menu1.png">
		            <div class="mui-media-body">新闻咨询</div>
                </a>
            </li>
		    <li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3">
                <a href="#">
		           <img src="../../img/menu2.png">
		            <div class="mui-media-body">图片分享</div>
                </a>
            </li>
		    <li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3">
                <a href="#">
		            <img src="../../img/menu3.png">
		            <div class="mui-media-body">商品购买</div>
                </a>
            </li>
		    <li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3">
                <a href="#">
		                <img src="../../img/menu4.png">
		                <div class="mui-media-body">留言反馈</div>
                </a>
            </li>
		    <li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3">
                <a href="#">
		           <img src="../../img/menu5.png">
		            <div class="mui-media-body">视频专区</div>
                </a>
            </li>
		    <li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3">
                <a href="#">
		            <img src="../../img/menu6.png">
		            <div class="mui-media-body">联系我们</div>
                </a>
            </li>
		</ul> 

修改6宫格的样式

.mui-grid-view{
    font-size: 13px;
    &.mui-grid-9{
        background-color: white;
        border: 0;
        .mui-table-view-cell{
            border: 0;
            img{
                width: 60px;
                height: 60px;
            }
        }
    }
}

Vue移动端商城项目_第7张图片

  • 实现不同页面切换时的动画效果

将中间的路由区域用transition包裹

app.vue

 <!-- 中间的路由router-view区域 -->
	  <transition>
		  <router-view></router-view>
	  </transition>

实现组件的动画效果

<style lang="scss" scoped>
    .app-container{
       padding-top:40px;
	   overflow-x: hidden;//将向左滑动时超出屏幕宽度的那一部分隐藏 
	             //不然 顶部的header和底部tabbar都会被右侧进来的组件挤走并出现滚动条
    }

	// 设置中间区域的动画滑动效果
	//此时v-enter和v-leave-to要分开写 不然页面会从右侧进入从右侧消失
	.v-enter
	{
		opacity: 0;
		transform: translateX(100%);
	}
	.v-leave-to{
		opacity: 0;
		transform: translateX(-100%);
		position: absolute;//防止页面进入时会往从下往上飘
	}
	.v-enter-active,
	.v-leave-active{
		transition: all 0.5s ease;
	}
</style>

3 实现新闻咨询列表布局和效果

  • 改造新闻咨询的路由

在components下新建一个news文件夹 在其中新建NewsList组件

修改HomeContainer.vue中六宫格a链接为router-link

<li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3">
                <router-link to="/home/newslist">
		           <img src="../../img/menu1.png">
		            <div class="mui-media-body">新闻咨询</div>
                </router-link>
            </li>

在router.js中导入路由组件 并配置路由对象

import NewsList from './components/news/NewsList.vue'
{path:'/home/newslist',component:NewsList}
  • 绘制新闻列表

使用MUI提供的media-list 并加以修改

NewsList.vue

 <ul class="mui-table-view">
				<li class="mui-table-view-cell mui-media">
					<a href="javascript:;">
						<img class="mui-media-object mui-pull-left" src="../../img/img1.jpg">
						<div class="mui-media-body">
							<h1>幸福</h1>
							<p class='mui-ellipsis'>
                                <span>发表时间:2000-09-30</span>
                                <span>点击:0</span>
                            </p>
						</div>
					</a>
				</li>
				<li class="mui-table-view-cell mui-media">
					<a href="javascript:;">
						<img class="mui-media-object mui-pull-left" src="../../img/img1.jpg">
						<div class="mui-media-body">
							<h1>幸福</h1>
							<p class='mui-ellipsis'><p class='mui-ellipsis'>
                                <span>发表时间:2000-09-30</span>
                                <span>点击:0</span>
                            </p>
						</div>
					</a>
				</li>
				<li class="mui-table-view-cell mui-media">
					<a href="javascript:;">
						<img class="mui-media-object mui-pull-left" src="../../img/img1.jpg">
						<div class="mui-media-body">
							<h1>幸福</h1>
							<p class='mui-ellipsis'><p class='mui-ellipsis'>
                                <span>发表时间:2000-09-30</span>
                                <span>点击:0</span>
                            </p>
						</div>
					</a>
				</li>

			</ul>
    </div>
</template>

设置样式

<style lang="scss" scoped>
   .mui-table-view{
       li{
           h1{
               font-size: 14px;
           }
           .mui-ellipsis{
               font-size: 12px;
               color: #226aff;
               display: flex;
               justify-content: space-between;
           }
       }
   }
</style>

Vue移动端商城项目_第8张图片

  • 获取新闻咨询列表并渲染页面

在main.js全局设置请求路径根地址

Vue.http.options.root='http://www.liulongbin.top:3005'

NewsList.vue

<script>
import {Toast} from 'mint-ui'
export default{
    data(){
        return{
            newslist:[]
        }
    },
    created(){
        this.getNews()
    },
    methods:{
        getNews(){//获取新闻列表
            this.$http.get('api/getnewslist').then(result=>{
                console.log(result.body.status)
                if(result.body.status===0){
                    this.newslist=result.body.message
                    console.log(result.body.status)
                }else{
                    Toast('获取数据失败')
                }
            })
        }
    }
}
</script>

v-for循环渲染

 <ul class="mui-table-view">
				<li class="mui-table-view-cell mui-media" v-for='item in newslist' :key='item.id'>
					<a href="javascript:;">
						<img class="mui-media-object mui-pull-left" :src="item.img_url">
						<div class="mui-media-body">
							<h1>{{item.title}}</h1>
							<p class='mui-ellipsis'>
                                <span>发表时间:{{item.add_time|dateFormat}}</span>
                                <span>点击:{{item.click}}</span>
                            </p>
						</div>
					</a>
				</li>
			</ul>
  • 解决最底下的列表被底部导航栏覆盖的bug

在app.vue增加一个padding-bottom

  .app-container{
       padding-top:40px;
	   padding-bottom: 40px;
	   overflow-x: hidden;//将向左滑动时超出屏幕宽度的那一部分隐藏 
	             //不然 顶部的header和底部tabbar都会被右侧进来的组件挤走并出现滚动条
    }
  • 定义全局过滤器格式化时间

安装格式化时间的插件:npm i moment -S

导入格式化时间的插件 并定义全局过滤器

main.js

import moment from 'moment'
//定义全局的过滤器
Vue.filter('dateFormat',function(dataStr,pattern='YYYY-MM-DD HH:mm:ss'){
    return moment(dataStr).format(pattern)
})

调用过滤器

NewsList.vue

 <span>发表时间:{{item.add_time|dateFormat}}</span>

Vue移动端商城项目_第9张图片

4 实现新闻详细页

  • 完成新闻列表跳转到新闻详情

改变NewsList.vue的a链接为router-link 并传递一个id值

 <!-- 要传一个id值 根据不同的id值渲染不同的新闻详情 -->
					<router-link :to="'/home/newsinfo/'+item.id">
						<img class="mui-media-object mui-pull-left" :src="item.img_url">
						<div class="mui-media-body">
							<h1>{{item.title}}</h1>
							<p class='mui-ellipsis'>
                                <span>发表时间:{{item.add_time|dateFormat}}</span>
                                <span>点击:{{item.click}}</span>
                            </p>
						</div>
					</router-link>

在news下新建一个NewsInfo.vue

在router.js导入NewsInfo.vue并配置对应关系

import NewsInfo from './components/news/NewsInfo.vue'
 {path:'/home/newsinfo/:id',component:NewsInfo}

画出新闻详情页面 并设置样式

    <template>
    <div class="news-container">
        <h3>新闻标签</h3>
        <p class="subtitle">
            <span>发表时间</span>
            <span>点击:0</span>
        </p>
        <hr>
        <div class="content"></div>
    </div>
</template>
 .news-container{
         padding: 0 4px;
        .title{
           font-size:16px;
           text-align: center;
           margin: 15 0;
           color: red;
        }
        .subtitle{
            font-size: 13px;
            color: #226aff;
            display: flex;
            justify-content: space-between;
        }
    }     

获取新闻详情数据 并渲染页面
NewsInfo.vue

import {Toast} from 'mint-ui'
export default{
    data(){
        return{
            //只要一进入页面就获取id的值 根据id的值来决定渲染哪一条新闻详情
            id:this.$route.params.id,
            //新闻对象(注意是对象不能定义成数组)
            newsinfo:{}
        }
    },
    created(){
        this.getNewsInfo()
    },
    methods:{
        getNewsInfo(){
            this.$http.get('api/getnew/'+this.id).then(result=>{
                if(result.body.status===0){
                    this.newsinfo=result.body.message[0]
                }else{
                    Toast('获取数据失败')
                }
            })
        }
    }
}
 <div class="news-container">
        <h3 class="title">{{newsinfo.title}}</h3>
        <p class="subtitle">
            <span>发表时间:{{newsinfo.add_time|dateFormat}}</span>
            <span>点击:{{newsinfo.click}}</span>
        </p>
        <hr>
        <!-- 根据文档说明 新闻内容要带有html标签 所以用v-html -->
        <div class="content" v-html="newsinfo.content"></div>
    </div>

Vue移动端商城项目_第10张图片

  • 实现新闻详细页的评论区域

在components下新建一个sub文件夹 在其中新建一个comment子组件

在NewsIndo.vue导入子组件

import comment from '../sub/comment.vue'
 components:{
        comment
    }

       <!-- 评论子组件 -->
        <comment></comment>

绘制评论区域界面 设置样式
在main.js按需导入button组件

import { Header,Swipe, SwipeItem,Button } from 'mint-ui';
Vue.component(Button.name, Button);

comment.vue

<template>
    <div class="cmt-container">
        <h3>
            发表评论
        </h3>
        <hr>
         <textarea placeholder="请输入要bb的内容" maxlength="120"></textarea>
        <mt-button type='primary' size='large'>发表评论</mt-button>    
        <div class="cmt-list">
            <div class="cmt-item">
                <div class="cmt-title">
                    第一楼&nbsp;&nbsp;用户:匿名用户&nbsp;&nbsp;发表时间:2012-12-12 12:12:12
                </div>
                <div class="cmt-body">
                    我是鼠鼠
                </div>
            </div>
        </div>
        <mt-button type='danger' size='large' plain>加载更多</mt-button>
    </div>
</template>
 h3{
        font-size: 18px;
    }
    textarea{
        font-size: 14px;
        height: 85px;
        margin: 0;
    }
    .cmt-list{
        margin: 5px 0;
        .cmt-item{
            font-size: 13px;
            .cmt-title{
                background-color: #aaa;
                line-height: 30px;
            }
            .cmt-body{
                line-height: 35px;
                // 缩进
                text-indent: 2em;
            }
        }
    }

Vue移动端商城项目_第11张图片

  • 获取评论数据

根据api文档获取评论数据还要传入id值 id值从父组件那获取
父组件向子组件传id
NewsInfo.js

 <!-- 评论子组件 -->
        <!-- 父组件像子组件传的id值 -->
        <comment :id='this.id'></comment>

子组件通过props获取id值 并获取评论数据

comment.vue

import {Toast} from 'mint-ui'
   export default{
       data(){
           return{
               pageIndex:1,//默认展示第一页数据
               comments:[]//所以的评论数据
           }
       },
       created(){
           this.getComments()
       },
        methods:{
            getComments(){
                //根据所提供的第三方接口api文档获取评论数据还要传入id值
                this.$http.get('api/getcomments/'+this.id+'?pageindex='+this.pageIndex).then(result=>{
                    console.log(result.body.status)
                    if(result.body.status===0){
                        this.comments=result.body.message
                    }else{
                        Toast('获取评论失败')
                    }
                })
            }
        },
        //获取父组件的id
        props:['id']
   }

渲染评论

   <div class="cmt-item" v-for='(item,i) in comments' :key="item.add_time">
                <div class="cmt-title">{{i+1}}&nbsp;&nbsp;用户:{{item.user_name}}&nbsp;&nbsp;发表时间:{{item.add_time|dateFormat}}
                </div>
                <div class="cmt-body">
                    {{item.content==='undefined'?'此用户很懒':item.content}}
                </div>
            </div>
  • 完成加载更多的功能

给加载更多按钮绑定click事件

   <mt-button type='danger' size='large' plain @click="getMore">加载更多</mt-button>

实现加载更多的功能

 getMore(){
                this.pageIndex++
                this.getComments()
            }

修改getcomments方法 将获取的数据拼接到原来的数据后面 不覆盖原来的数据

 if(result.body.status===0){
                        //将获取的数据拼接到原来的数据后面 不覆盖原来的数据
                        this.comments=this.comments.concat(result.body.message)
                    }else{
                        Toast('获取评论失败')
                    }

Vue移动端商城项目_第12张图片

  • 实现发表评论的功能

为文本框做双向数据绑定

 <textarea placeholder="请输入要bb的内容" maxlength="120" v-model="msg"></textarea>
 data(){
           return{
               pageIndex:1,//默认展示第一页数据
               comments:[],//所以的评论数据
               msg:''//评论输入的内容
           }
       },

为发表按钮绑定一个事件

 <mt-button type='primary' size='large' @click="postComments">发表评论</mt-button>

在main.js全局设置post时候表单数据格式组织形式

Vue.http.options.emulateJSON=true

定义发表评论方法
1 校验评论内容是否为空 如果为空则 Toast弹框提示用户 内容不为空
2 通过vue-reasoure发请求 把用户的评论内容提交给服务器
3 发表评论完后重新刷新列表 查看最新评论
注意1:如果调用getcomments方法重新刷新评论列表的话 此时如 果加载到第二,三页 可能得到最后一页的评论 前面几页获取不到
换一个方法:当评论成功后 在客户端 手动拼接一个 最新的评论对象然后调用数组的unshitf方法把最新的评论追加到data中comments的开头 这样就不用刷新评论列表了

注意2: unshit不能写为this.comments=this.comments.unshift(cmt)

 postComments(){
                //校验是否为空内容
                //trim() 函数移除字符串两侧的空白字符或其他预定义字符
                if(this.msg.trim().length===0){
                    //如果为空就return出去 后续代码不执行了
                    return Toast('评论内容不能为空')
                }
                //根据所提供的第三方接口api文档 发表评论数据还要传入id值
                this.$http.post('api/postcomment/'+this.id,
                {content:this.msg.trim()})
                .then(result=>{
                    //拼接出一个评论对象
                    var cmt={
                        user_name:'匿名用户',
                        add_time:Date.now(),
                        content:this.msg.trim()
                    };
                    this.comments.unshift(cmt)
                    this.msg="";
                })
            }

Vue移动端商城项目_第13张图片Vue移动端商城项目_第14张图片

5 图片分享列表和详情页制作

在components下新建photos文件夹 在其中新建PhotoList.vue组件

修改HomeContainer.vue的图片分享的链接为router-link

<router-link to="/home/photolist">
		           <img src="../../img/menu2.png">
		            <div class="mui-media-body">图片分享</div>
           </router-link>

在router.js下导入photolist组件 并设置路由对应关系

import PhotoList from './components/photos/Photolist.vue'
  {path:'/home/photolist',component:PhotoList}
  • 实现顶部滑动条

使用MUI提供的 tab-top-webview-main
注意:要去除mui-fullscreen的类否则会全屏显示

<template>
    <div>
        <!-- 使用MUI提供的tab-top-webview-main -->
        <!-- 去掉mui-fullscreen的类否则会全屏显示 -->
        <div id="slider" class="mui-slider">
				<div id="sliderSegmentedControl" class="mui-scroll-wrapper mui-slider-indicator mui-segmented-control mui-segmented-control-inverted">
					<div class="mui-scroll">
						<a class="mui-control-item mui-active" href="#item1mobile" data-wid="tab-top-subpage-1.html">
							推荐
						</a>
						<a class="mui-control-item" href="#item2mobile" data-wid="tab-top-subpage-2.html">
							热点
						</a>
						<a class="mui-control-item" href="#item3mobile" data-wid="tab-top-subpage-3.html">
							北京
						</a>
						<a class="mui-control-item" href="#item4mobile" data-wid="tab-top-subpage-4.html">
							社会
						</a>
						<a class="mui-control-item" href="#item5mobile" data-wid="tab-top-subpage-5.html">
							娱乐
						</a>
						<a class="mui-control-item" href="#item6mobile" data-wid="tab-top-subpage-6.html">
							科技
						</a>
					</div>
				</div>

			</div>
    </div>
</template>

由于这是一个js实现的控件 所以要导入mui的js文件

import mui from '../../lib/mui-master/dist/js/mui.min.js'

根据MUI的api文档 还需初始化控件

//导入mui的js文件
  import mui from '../../lib/mui-master/dist/js/mui.min.js'
  //根据官方api文档 初始化控件
  mui('.mui-scroll-wrapper').scroll({
	deceleration: 0.0005 //flick 减速系数,系数越大,滚动速度越慢,滚动距离越小,默认值0.0006
});
  export default{
      data(){
          return{}
      },

此时运行 会出现以下报错
在这里插入图片描述
原因分析
是muijs用到了’caller’,‘callee’,'arguments’的东西 但是webpack打包好的bundle.js中 默认是启用严格模式的,所以这两者冲突了

解决方案:
使用babel-plugin-transform-remove-strict-mode 插件来移除严格模式

安装插件:
npm i babel-plugin-transform-remove-strict-mode -D

根据该插件提供的官方api 若我们使用的.babelrc 那么就在.babelrc 文件中添加 “transform-remove-strict-mode”
.babelrc

{
  "presets": ["env","stage-0"],
   "plugins": ["transform-runtime",["component", [
    {
      "libraryName": "mint-ui",
      "style": true
    }
  ]],"transform-remove-strict-mode"]
}

解决 刚进入页面时 滑动条无法滑动的问题

将初始化滚动条的代码 写在mounted生命周期钩子函数中 因为要初始化滚动条必须要等DOM元素加载完毕

export default{
      data(){
          return{}
      },
      mounted(){//当组件中的dom结构被渲染好并放到页面上的后 会执行该钩子函数
                //如果要操作元素 最好在mounted里 因为这个时候的元 否则刚进入页面无法滑动该控件)
        mui('.mui-scroll-wrapper').scroll({
            deceleration: 0.0005 //flick 减速系数,系数越大,滚动速度越慢,滚动距离越小,默认值0.0006
        });
      }
  }

此时滚动条可以滑动了

但是在滚动时会有以下报错:
在这里插入图片描述
解决:需要在css中添加以下样式

<style lang="scss" scoped>
     *{
         touch-action: pan-y;
     }
</style>

解决底部tab栏无法切换的问题

检查元素复制所有出现mui-tab-item类的样式 粘贴至app.vue
Vue移动端商城项目_第15张图片
Vue移动端商城项目_第16张图片
修改底部tab栏中的所以.mui-tab-item的类名
app.vue

<router-link class="mui-tab-item11" to="/home">
				<span class="mui-icon mui-icon-home"></span>
				<span class="mui-tab-label">首页</span>
			</router-link>

复制过来的样式中的mui-tab-item也要修改类名

.mui-bar-tab .mui-tab-item11 {
    display: table-cell;
    overflow: hidden;
    width: 1%;
    height: 50px;
    text-align: center;
    vertical-align: middle;
    white-space: nowrap;
    text-overflow: ellipsis;
    color: #929292;
   }
   .mui-bar-tab .mui-tab-item11 .mui-icon {
    top: 3px;
    width: 24px;
    height: 24px;
    padding-top: 0;
    padding-bottom: 0;
}
.mui-bar-tab .mui-tab-item11 .mui-icon~.mui-tab-label {
    font-size: 11px;
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
}

Vue移动端商城项目_第17张图片

  • 渲染分类列表的数据

在data上定义cates数组 在methods中获取图片列表数据 并调用
photolist.vue

 data(){
          return{
              //所有分类的列表
              cates:[]
          }
      },
 created(){
          this.getAllCategory()
      },
      methods:{
          getAllCategory(){
              //获取所有图片分类
              this.$http.get('api/getimgcategory').then(result=>{
                  if(result.body.status===0){
                      result.body.message.unshift({title:'全部',id:0});
                      this.cates=result.body.message
                  }
              })
          }
      }

渲染数据

 
  • 获取分类的图片 并渲染图片列表
    使用mint-ui提供的lazy load
 
  • "item in list" :key="item.id"> "item.img_url">

在data中定义图片列表数组

 data(){
          return{
              //所有分类的列表
              cates:[],
              //图片列表
              list:[]
          }
      },

定义获取图片列表的方法

 getImgList(cateId){
              this.$http.get('api/getimages/'+cateId).then(result=>{
                  if(result.body.status===0){
                      this.list=result.body.message
                  }
              })
          }

在created中调用该方法

 //默认进入页面就去请求所有图片数据
  this.getImgList(0)

给顶部分类列表也要绑定事件 使得根据不同得分类来获取不同得图片列表

<a :class="['mui-control-item',item.id==0?'mui-active':'']" 
            v-for='item in cates' :key='item.id' @click="getImgList(item.id)" >
	{{item.title}}
</a>	
  • 实现图片懒加载 美化图片列表样式

将image类名改为img

//实现图片懒加载
  img[lazy=loading] {
      width: 40px;
      height: 300px;
      margin: auto;
  }

注意:实现图片懒加载要引入整个mint-ui以及css
main.js

// import { Header,Swipe, SwipeItem,Button, Lazyload } from 'mint-ui';
// Vue.component(Header.name, Header);
// Vue.component(Swipe.name, Swipe);
// Vue.component(SwipeItem.name, SwipeItem);
// Vue.component(Button.name, Button);
// Vue.use(Lazyload);
//要想实现图片懒加载 就得全部导入不能按需导入
import MintUI from 'mint-ui'
Vue.use(MintUI)
import 'mint-ui/lib/style.css'

修改样式
要注意在向上滑动时会出现滑动导航栏会覆盖顶部header
不能调整滑动导航栏的层级为-1 不然会滑动不了 应该在app.vue中设置header的层级

  // .mui-slider{
    //      z-index: -1;//不能设置z-index:-1 不然顶部栏会滑动不了
    //  }

app.vue

.mint-header.is-fixed{//设置header层级
		z-index:2
	}
<ul class="photo-list">
            <li v-for="item in list" :key="item.id">
                <img v-lazy="item.img_url">
            </li>
        </ul>

设置图片列表样式

 .photo-list{
         margin: 0;
         padding: 10px;
         list-style: none;
         padding-bottom: 0;
         li{  
              margin-bottom: 10px;
              background-color: #ccc;
              text-align: center;
              box-shadow: 0 0 9px #999;
              img{
                  width: 100%;
                  //去除图片边距
                  display: block;
              }
              //实现图片懒加载
              img[lazy=loading] {
                width: 40px;
                height: 300px;
                margin: auto;
            }
         }
     }

Vue移动端商城项目_第18张图片
Vue移动端商城项目_第19张图片
补充说明:由于给的获取图片列表的接口有问题 只能获取0和17-22的id值的图片列表 而顶部的滚动导航从第二个开始对应的id为14 所以在点击第二个导航栏时页面会空白

解决方式:
给将顶部分类列表的数据获取到之后放到cates数组中 给cates数组中的每一个对象在增加一个id2的属性 赋值为17-22 i值要从1开始 因为第一个导航栏对应的id值为0 是有图片列表的的

 getAllCategory(){
              //获取所有图片分类
              this.$http.get('api/getimgcategory').then(result=>{
                  if(result.body.status===0){
                      result.body.message.unshift({title:'全部',id:0});
                      this.cates=result.body.message
                      //增加id2的属性 并赋值
                      for(var i=1,j=17;i<=6,j<22;i++,j++){
                            this.cates[i]['id2']=j
                            console.log(this.cates)
                          }
                      }
                  }
              )
          },

修改顶部滚动条绑定的点击事件传递的id值 除了id值为0的第一个导航条 其他的都要传递自定义的id2的值 这样就改变了图片列表加载时对应的id值 点击其他的就可以加载出来了

<a :class="['mui-control-item',item.id==0?'mui-active':'']" 
   v-for='item in cates' :key='item.id' @click="getImgList(item.id==0?0:item.id2)" >
	{{item.title}}
</a>	

Vue移动端商城项目_第20张图片
解决在手机端调试的时候点击顶部菜单无法切换的问题
将MUI组件中的@click事件改为@tap
注意:tap只能用于MUI组件

 <!-- 使用MUI提供的tab-top-webview-main -->
        <!-- 去掉mui-fullscreen的类否则会全屏显示 -->
        <div id="slider" class="mui-slider">
				<div id="sliderSegmentedControl" class="mui-scroll-wrapper mui-slider-indicator mui-segmented-control mui-segmented-control-inverted">
					<div class="mui-scroll">
                        <!-- 属性绑定 只有全部这一项才高亮显示 -->
						<a :class="['mui-control-item',item.id==0?'mui-active':'']" 
                            v-for='item in cates' :key='item.id' @tap="getImgList(item.id==0?0:item.id2)" >
							{{item.title}}
						</a>		
					</div> 
				</div>
		</div>
  • 添加图片文字的介绍
<ul class="photo-list">
            <li v-for="item in list" :key="item.id">
                <img v-lazy="item.img_url">
                <!-- 图片的文字介绍 -->
                <div class="info">
                    <h1 class="info-title">{{item.title}}</h1>
                    <div class="info-body">{{item.zhaiyao}}</div>
                </div>
            </li>
        </ul>

设置样式
先给li标签添加position:relative

  .info{
         color:#fff;
         text-align: left;
         position:absolute;
         bottom: 0;
         background-color: rgba(0,0,0,0.4);
         //给盒子设置最大高度
         max-height: 84px;
         overflow-y:auto ;//超出y轴隐藏 可滚动
         .info-title{
               font-size: 14px;
              }
         .info-body{
               font-size: 13px;
              }
         }

Vue移动端商城项目_第21张图片

  • 实现图片详情的数据加载和页面
    修改Photo.vue中图片列表的路由
  <!-- tag属性 使其渲染成li标签 -->
            <router-link v-for="item in list" :key="item.id" :to="'/home/photoinfo/'+item.id" tag='li'>
                <img v-lazy="item.img_url">
                <!-- 图片的文字介绍 -->
                <div class="info">
                    <h1 class="info-title">{{item.title}}</h1>
                    <div class="info-body">{{item.zhaiyao}}</div>
                </div>
            </router-link>

在photos下新建photoInfo.vue 初步绘制界面

<template>
    <div>
        <h3>标题</h3>
        <p class="subtitle">
            <span>发表时间</span>
            <span>点击:0</span>
        </p>
        <hr>

        <!-- 缩略图区域 -->
        <!-- 图片内容区域 -->
        <div class="content"></div>

        <!-- 放置一个现成的评论子组件 -->
    </div>
</template>

在router.js导入该组件 并设置对应关系

import PhotoInfo from './components/photos/PhotoInfo.vue'
{path:'/home/photoinfo/:id',component:PhotoInfo}

Vue移动端商城项目_第22张图片

获取图片详情的数据

export default{
    data(){
        return{
            id:this.$route.params.id,//从路由中获取到的图片id
            photoinfo:{}//图片详情
        }
    },
    created(){
        this.getPhotoInfo()
    },
    methods:{
        getPhotoInfo(){
            //获取图片详情
            this.$http.get('api/getimageInfo/'+this.id).then(result=>{
                if(result.body.status===0){
                    if(result.body.status===0){
                        this.photoinfo=result.body.message[0]
                    }
                }
            })
        }
    },

渲染页面 并重新设置样式

<div class="photoinfo-container">
        <h3>{{photoinfo.title}}</h3>
        <p class="subtitle">
            <span>发表时间:{{photoinfo.add_time|dateFormat}}</span>
            <span>点击:{{photoinfo.click}}</span>
        </p>
        <hr>

        <!-- 缩略图区域 -->
        <!-- 图片内容区域 -->
        <!-- 根据接口文档 content带有html标签 所以要v-html -->
        <div class="content" v-html='photoinfo.content'></div>

        <!-- 放置一个现成的评论子组件 -->
       
    </div>
.photoinfo-container{
       padding:3px;
       h3{
           font-size: 15px;
           text-align: center;
           margin: 15px 0;
           color:#26a2ff;
       }
       .subtitle{
           display: flex;
           justify-content: space-between;
           font-size: 13px;
       }
       .content{
           font-size: 13px;
           line-height: 30px;
       }
   }

导入之前写好的评论子组件

import comment from '../sub/comment.vue'

挂载该组件

components:{
        'cmt-box':comment
    }

放置子组件

  <!-- 放置一个现成的评论子组件 -->
  <cmt-box :id='id'></cmt-box>

Vue移动端商城项目_第23张图片

  • 实现图片详情中缩略图的功能

使用插件 vue-preview这个缩略图插件
安装插件:npm i vue-preview -S
main.js导入插件

//使用缩略图插件
import VuePreview from 'vue-preview'
// defalut install
Vue.use(VuePreview)

根据api文档使用缩略图插件
photoInfo.vue

  <!-- 使用缩略图插件 -->
        <div class="suonue">
             <vue-preview :slides="list" @close="handleClose"></vue-preview>
        </div>

在data中创建一个list数组 根据api list得是一个数组

 data(){
        return{
            id:this.$route.params.id,//从路由中获取到的图片id
            photoinfo:{},//图片详情
            //根据缩略图插件官方api list得是一个数组
            list:[]
        }
    },

根据api:每个图片的数据对象中 必须有msrc,w和h属性
获取到所有的图片列表 然后v-for指令渲染数据 增加w,和h属性

  //获取缩略图
        getThumbs(){
        this.$http.get('api/getthumimages/'+this.id).then(result=>{
            if(result.body.status===0){
                //根据官方api 必须提供默认的宽高值 所以循环遍历每一项添加宽高值
                result.body.message.forEach(item => {
                    item.w=100;
                    item.h=200;
                    item.msrc = item.src;
                });
                //把完整的数据保存到list中
                this.list=result.body.message
            }
        })
       },

在created调用该方法

设置缩略图样式

  .suonue {
  /deep/ .my-gallery {
    display: flex;
    flex-wrap: wrap;
    figure {
      width: 30%;
      margin: 5px;
      img {
        width: 100%;
      }
    }
  }
}

Vue移动端商城项目_第24张图片

Vue移动端商城项目_第25张图片

6 商品列表和商品详情页制作

  • 改造商品购买路由

homeContainer.vue

<li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3">
                <router-link to="/home/goodslist">
		            <img src="../../img/menu3.png">
		            <div class="mui-media-body">商品购买</div>
                </router-link>

创建goods文件夹 创建goodslist.vue

创建对应的路由关系
router.js

import GoodsList from './components/goods/GoodsList.vue'
{path:'/home/goodslist',component:GoodsList}
  • 实现商品列表的经典两列布局
<template>
    <div class="goods-list">
        <div class="goods-item">
            <img src="">
            <h1 class="title">小米</h1>
            <div class="info">
                <p class="price">
                    <span class="now">990</span>
                    <span class="old">890</span>
                </p>
                <p class="sell">
                    <span>热卖中</span>
                    <span>60</span>
                </p>
            </div>
        </div>
    </div>
</template>
.goods-list{
      display: flex;
      flex-wrap: wrap;//换行
      padding: 7px;
      justify-content: space-between;

      .goods-item{
          width: 49%;
          border:1px solid #ccc;
          box-shadow: 0 0 8px #ccc;
          margin: 4px 0;
          padding: 2px 0;
          display: flex;//使灰色的盒子始终紧贴底部不留空白 
          //否则在设置了换行布局后  高度是自适应的 有的盒子会过高而内容不够填充 从而底部留白
          flex-direction: column;
          justify-content: space-between;
          min-height: 293px;//图片未加载出撑开一个最小高度
          img{
              width: 100%;
          }
          .title{
              font-size:14px;
          }

          .info{
              background-color: #eee;
              p{
                  margin:0;
                  padding: 5px;
              }
              .price{
                  .now{
                      color:red;
                      font-weight:bold;
                      font-size:16px;
                  }
                  .old{
                      text-decoration: line-through;
                      font-size: 12px;
                      margin-left: 10px;
                  }
              }
              .sell{
                  display: flex;
                  justify-content: space-between;
                  font-size: 13px;
              }
          }

      }
  }
  • 实现数据的加载和渲染
 export default{
       data(){
           return{
               pageindex:1,
               goodslist:[]//商品数据
           }
       },
       created(){
           this.getGoodsList()
       },
       methods:{
           getGoodsList(){
               this.$http.get('api/getgoods?pageindex'+this.pageindex).then(result=>{
                   if(result.body.status===0){
                       this.goodslist=result.body.message;
                   }
               })
           }
       } 
   }
 <div class="goods-list">
        <div class="goods-item" v-for="item in goodslist" :key="item.id">
            <img :src="item.img_url">
            <h1 class="title">{{item.title}}</h1>
            <div class="info">
                <p class="price">
                    <span class="now">{{item.sell_price}}</span>
                    <span class="old">{{item.market_price}}</span>
                </p>
                <p class="sell">
                    <span>热卖中</span>
                    <span>{{item.stock_quantity}}</span>
                </p>
            </div>
        </div>

Vue移动端商城项目_第26张图片

  • 实现加载更多

使用mint-ui按钮 定义加载更多的事件

 <mt-button type="danger" size="large" @click="getMore">加载更多</mt-button>

修改getGoodsList方法

 getGoodsList(){
               this.$http.get('api/getgoods?pageindex'+this.pageindex).then(result=>{
                   if(result.body.status===0){
                       this.goodslist=this.goodslist.concat(result.body.message);
                   }
               })
           }

定义getMore方法

  getMore(){
               this.pageindex++
               this.getGoodsList()
           }

Vue移动端商城项目_第27张图片

  • 实现商品列表详情页

使用js代码的形式路由导航 实现点击每一个div后跳转到商品详情页 【编程式导航】
给div添加一个click事件

  <div class="goods-item" v-for="item in goodslist" :key="item.id"
        @click="goDetail(item.id)">

定义该方法

 goDetail(id){
                //使用js的形式进行路由导航
                //注意:this.$route是路由【参数对象】,所所有路由中的参数params,query都属于它
                //this.$router是一个路由【导航对象】,用它可以方便的使用js实现路由的前进后退 跳转新的URL地址
                //最简单的
            //    this.$router.push('/home/goodsinfo/'+id)
                //传递对象
                // this.$router.push({path:"/home/goodsinfo/"+id})
                //传递命名的路由
                this.$router.push({name:"goodsinfo",params:{id}})

           }

初步绘制详情页界面
goodsinfo.vue

<div class="goodsinfo-container">
        <!-- 使用MUI提供的card -->
        <!-- 商品轮播图 -->
        <div class="mui-card">
				<div class="mui-card-content">
					<div class="mui-card-content-inner">
						这是一个最简单的卡片视图控件;卡片视图常用来显示完整独立的一段信息,比如一篇文章的预览图、作者信息、点赞数量等
					</div>
				</div>
			</div>
        <!-- 商品购买 -->
        <div class="mui-card">
				<div class="mui-card-header">页眉</div>
				<div class="mui-card-content">
					<div class="mui-card-content-inner">
						包含页眉页脚的卡片,页眉常用来显示面板标题,页脚用来显示额外信息或支持的操作(比如点赞、评论等)
					</div>
				</div>
				<div class="mui-card-footer">页脚</div>
			</div>
        <!-- 商品参数 -->
        <div class="mui-card">
				<div class="mui-card-header">页眉</div>
				<div class="mui-card-content">
					<div class="mui-card-content-inner">
						包含页眉页脚的卡片,页眉常用来显示面板标题,页脚用来显示额外信息或支持的操作(比如点赞、评论等)
					</div>
				</div>
				<div class="mui-card-footer">页脚</div>
			</div>
    </div>
.goodsinfo-container{
    background-color: #eee;
    padding: 1px;
}

Vue移动端商城项目_第28张图片

获取商品详情页的轮播图数据

export default{
    data(){
        return{
            id:this.$route.params.id,
            lunbotu:[]
        }
    },
    created(){
        this.getLunbo()
    },
    methods:{
        getLunbo(){
            this.$http.get('api/getthumimages/'+this.id).then(result=>{
                if(result.body.status===0){
                    this.lunbotu=result.body.message
                }
            })
        }
    }
}

由于详情页也要用到轮播图组件 所以我们将之前的轮播图组件抽离

新建swiper.vue子组件将homeContainer.vue中的轮播图的组件和样式剪切到其中

<template>
    <div>
        <mt-swipe :auto="4000">
        // 
         //
        // 
        <mt-swipe-item v-for="item in lunboList" :key="item.url">
            <img :src="item.img">
        </mt-swipe-item>
     </mt-swipe>
    </div>
</template>

```css
.mint-swipe{
    height: 200px;
    .mint-swipe-item{
    &:nth-child(1){
        background-color: red;
    }
    &:nth-child(2){
        background-color: yellow;
    }
    
    img{
        width: 100%;
        height: 100%;
    }
 }
}

props接收父组件向子组件传的值

export default{
    props:['lunboList']
}

在homeContainer.vue中导入抽离出的轮播图组件

 import swiper from '../sub/swiper.vue'

挂载

 components:{
          swiper
      }

添加组件

  //
     <swiper :lunboList="lunboList"></swiper>

在goodslist.vue中引入轮播图的组件并挂载

import swiper from '../sub/swiper.vue'
 components:{
        swiper
    }
  <div class="mui-card">
				<div class="mui-card-content">
					<div class="mui-card-content-inner">
						<swiper :lunboList="lunbotu"></swiper>
					</div>
				</div>
			</div>

给轮播图的每一项item添加img属性 因为该接口提供的图片属性名是img.src而轮播图组件中只认知item.img

   getLunbo(){
            this.$http.get('api/getthumimages/'+this.id).then(result=>{
                if(result.body.status===0){
                    //给轮播图的每一项item添加img属性 因为该接口提供的图片属性名是img.src
                    //而轮播图组件中只认知item.img
                    result.body.message.forEach(item => {
                        item.img=item.src
                    });
                    this.lunbotu=result.body.message
                }
            })
        }

解决轮播图过宽的问题

由于首页中使用的轮播图组件图片宽高为100% 而商品详情页中图片高度也会是100%所以就会导致轮播图过宽
商品详情页中的轮播图高度应该是100% 宽度应该自适应
可以定义一个属性让使用轮播图的调用者 手动指定是否为100%宽度

给homeContainer.vue的swipe组件添加属性

  <swiper :lunboList="lunboList" :isfull='true'></swiper>

给goodsinfo.vue也同样指定

<swiper :lunboList="lunbotu" :isfull="false"></swiper>

在swiper.vue的props中添加isfull

export default{
    props:['lunboList','isfull']
}

设置.full类名的样式 并使轮播图居中显示

  .mint-swipe{
    height: 200px;
    .mint-swipe-item{
      text-align: center;
    img{
    //    width: 100%;
       height: 100%;
    }
 }
}
.full{
    width: 100%;
}

给img标签添加class属性 根据isfull来添加类名

 <!-- 如果isfull为真 就添加full类名 -->
            <img :src="item.img" :class="{'full':isfull}">

Vue移动端商城项目_第29张图片

绘制商品购买区域和商品参数样式
购买使用MUI的numbox 由于之后还要用到numbox 所以将其抽离为一个子组件
新建numbox.vue

<template>
   	<div class="mui-numbox" data-numbox-min='1' data-numbox-max='9'>
		<button class="mui-btn mui-btn-numbox-minus" type="button">-</button>
		<input id="test" class="mui-input-numbox" type="number" value="1" />
		<button class="mui-btn mui-btn-numbox-plus" type="button">+</button>
	</div>
</template>

初始化该组件

 import mui from '../../lib/mui-master/dist/js/mui.js'
export default{
    mounted(){
        //要自己手动初始化该组件
        mui('.mui-numbox').numbox();
    },

在goodsinfo.vue中导入该组件 并挂载

import numbox from '../sub/numbox.vue'
 components:{
        swiper,
        numbox
    }
 <div class="mui-card">
				<div class="mui-card-header">商品的名称标题</div>
				<div class="mui-card-content">
					<div class="mui-card-content-inner">
						<p class="price">
                            市场价:<del>2399</del>&nbsp;&nbsp;销售价:<span class="now_price">2199</span>
                        </p>
                        <p class="number">购买数量: </p>
    					<numbox></numbox>
                        <p class="btn">
                            <mt-button type="primary" size="small">立即购买</mt-button>
                            <mt-button type="danger" size="small">加入购物车</mt-button>
                        </p>
					</div>
				</div>
</div>

 <div class="mui-card">
				<div class="mui-card-header">商品参数</div>
				<div class="mui-card-content">
					<div class="mui-card-content-inner">
						<p>商品货号:</p>
                        <p>库存情况:</p>
                        <p>上架时间:</p>
					</div>
				</div>
				<div class="mui-card-footer">
                    <mt-button type="primary" size="large" plain>图文介绍</mt-button>
                    <mt-button type="danger" size="large" plain>商品评论</mt-button>
                </div>
</div>
.goodsinfo-container{
    background-color: #eee;
    padding: 1px;
    .now_price{
        color: red;
        font-size: 16px;
        font-weight: bold;
    }
    .number{
        display: inline-block;
    }
    .btn{
        margin-top: 10px;
    }
    .mui-numbox{
        height: 30px;
    }
    .mui-card-footer{
        display: block;//取消flex布局 是按钮纵向排列
        button{
            margin: 15px 0;
        }
    }
}

Vue移动端商城项目_第30张图片
渲染商品数据
定义商品数据的对象

  return{
            id:this.$route.params.id,
            lunbotu:[],
            goodsinfo:{}
        }

定义获取商品信息的方法

  getGoodsInfo(){
            //获取商品信息
            this.$http.get('api/goods/getinfo/'+this.id).then(result=>{
                if(result.body.status===0){
                    this.goodsinfo=result.body.message[0]
                }
            })
        }

调用

 created(){
        this.getLunbo()
        this.getGoodsInfo()
    },

渲染

 <div class="mui-card">
				<div class="mui-card-header">{{goodsinfo.title}}</div>
				<div class="mui-card-content">
					<div class="mui-card-content-inner">
						<p class="price">
                            市场价:<del>{{goodsinfo.market_price}}</del>&nbsp;&nbsp;销售价:<span class="now_price">{{goodsinfo.sell_price}}</span>
                        </p>
                        <p class="number">购买数量:{{goodsinfo.stock_quantity}} </p>
                         <div class="mui-numbox" data-numbox-min='1' data-numbox-max='9'>
                            <button class="mui-btn mui-btn-numbox-minus" type="button">-</button>
                            <input id="test" class="mui-input-numbox" type="number" value="5" />
                            <button class="mui-btn mui-btn-numbox-plus" type="button">+</button>
				        </div>
                        <p class="btn">
                            <mt-button type="primary" size="small">立即购买</mt-button>
                            <mt-button type="danger" size="small">加入购物车</mt-button>
                        </p>
					</div>
				</div>
			</div>
        <!-- 商品参数 -->
        <div class="mui-card">
				<div class="mui-card-header">商品参数</div>
				<div class="mui-card-content">
					<div class="mui-card-content-inner">
						<p>商品货号:{{goodsinfo.goods_no}}</p>
                        <p>库存情况:{{goodsinfo.stock_quantity}}</p>
                        <p>上架时间:{{goodsinfo.add_time|dateFormat}}</p>
					</div>
				</div>
				<div class="mui-card-footer">
                    <mt-button type="primary" size="large" plain>图文介绍</mt-button>
                    <mt-button type="danger" size="large" plain>商品评论</mt-button>
                </div>
			</div>

Vue移动端商城项目_第31张图片

  • 实现商品图文介绍和发表评论

使用编程导航实现图文介绍和发表评论页面的跳转

分别给这两个按钮绑定click事件

 <mt-button type="primary" size="large" plain @click="goDesc(id)">图文介绍</mt-button>
 <mt-button type="danger" size="large" plain @click="goComment(id)">商品评论</mt-button>

定义方法

 goDesc(id){
            //使用编程式导航跳转到图文介绍页面
            this.$router.push({name:'goodsdesc',params:{id}})
        },
 goComment(id){
             this.$router.push({name:'goodscomment',params:{id}})
        }

在goods下创建goodsdesc.vue和goodscomment.vue
创建对应的路由关系

import GoodsDesc from './components/goods/goodsdesc.vue'
import GoodsComment from './components/goods/goodsComment.vue'
 {path:'/home/goodsdesc/:id',component:GoodsDesc,name:'goodsdesc'},
 {path:'/home/goodscomment/:id',component:GoodsComment,name:'goodscomment'}

在goodsdesc.vue绘制界面 并渲染数据

<template>
    <div class="goodsdesc-container">
        <h3>{{info.title}}</h3>
        <!-- 根据文档 要带有html标签 -->
        <div class="content" v-html="info.content"></div>
    </div>
    
</template>
<script>
export default{
    data(){
        return{
            info:{}//图文数据
        }
    },
    created(){
        this.getGoodsDesc()
    },
    methods:{
        getGoodsDesc(){
            this.$http.get('api/goods/getdesc/'+this.$route.params.id)
            .then(result=>{
                if(result.body.status===0){
                    this.info=result.body.message[0]
                }
            })
        }
    }
}
</script>

Vue移动端商城项目_第32张图片
在goodscomment.vue导入并放置评论子组件

<template>
    <div>
        <comment :id='this.$route.params.id'></comment>
    </div>
</template>
<script>
import comment from '../sub/comment.vue'
export default{
  components:{
      comment
  }
}
</script>

Vue移动端商城项目_第33张图片

  • 实现加入购物车的动画效果

添加小球元素 设置小球的样式 给小球设置不可见

<div class="ball" v-show="ballFlag"></div>
  .ball{
        width: 15px;
        height: 15px;
        border-radius: 50%;
        position: absolute;
        background-color: red;
        z-index: 20;
        top:390px;
        left:166px;
    }

在data中添加ballflag指定小球是否可见

 data(){
        return{
            id:this.$route.params.id,
            lunbotu:[],
            goodsinfo:{},
            ballFlag:false//控制小球隐藏和显示的表示符
        }
    },

给加入购物车绑定事件 使之点击后出现小球

 <mt-button type="danger" size="small" @click="addToShopCar">加入购物车</mt-button>
 addToShopCar(){
            //添加到购物车
            this.ballFlag=!this.ballFlag
        },

用包裹小球元素 通过钩子函数实现半场动画效果

     <!--由于实现的是半场动画所以只能用钩子函数不能用类  -->
        <transition
            @before-enter="beforeEnter"
            @enter="enter"
            @after-enter="afterEnter">
            <div class="ball" v-show="ballFlag"></div>
        </transition>   
  beforeEnter(el,done){
           el.style.transform="translate(0,0)"
        },
        enter(el){
            el.offsetWidth;
            el.style.transform="translate(70px, 230px)";
            el.style.transition="all 1s cubic-bezier(.4,-0.3,1,.68)";
            done()
        },
        afterEnter(el){
            this.ballFlag=!this.ballFlag
        }
  • 实现小球适配滚动条滚动不同的高度和不同的分辨率

小球动画优化
//小球动画不准确的原因 :1 我们把小球最终位移2到的位置已经局限在了某一分辨率下 滚动条未滚动的情况下
//2 只要分辨率和测试的时候不一样 或者滚动条滚动了一定的距离后 那么小球的位置会发生偏离
//3 不能将坐标写死 应该根据不同的情况动态计算这个坐标值
//4 解决:先得到徽标的横纵坐标,再得到小球的横纵坐标 然后让y值求差 x值也求差 得到的结果就是横纵坐标要位移的距离

给ball元素添加ref ref只能获取本组件的元素

<!-- 通过ref来获取dom元素 -->
 <div class="ball" v-show="ballFlag" ref="ball"></div>

给app.vue的购物车红色图标添加id值

<span class="mui-icon mui-icon-extra mui-icon-extra-cart">
					<span class="mui-badge" id="badge">0</span>
				</span>

在enter中继续优化动画

enter(el){
            el.offsetWidth;      
        
        //获取小球在页面中的位置
            const ballPosition=this.$refs.ball.getBoundingClientRect()
            //可以通过dom操作来拿到页面中的任意元素 哪怕是另一个组件的
            //获取徽标在页面中的位置
            const badgePosition=document.getElementById('badge').getBoundingClientRect()

            const xDist=badgePosition.left-ballPosition.left
            const yDist=badgePosition.top-ballPosition.top

            //使用es6模板字符串拼接
            el.style.transform=`translate(${xDist}px, ${yDist}px)`;
            el.style.transition="all 0.5s cubic-bezier(.4,-0.3,1,.68)";
            done()
        },
  • 加入购物车时购物车数量随numbox动态变化

    分析 如何实现加入购物车的时候拿到选择的数量
    1 加入购物车按钮属于goodsinfo页面 数字属于numberbox页面
    2 由于涉及到了父子组件的嵌套 无法直接在goodsinfo页面中获取到选中的商品数量的值
    3 涉及到子组件向父组件传值 (事件调用机制)
    4 事件调用本质 父向子传递方法 子调用这个方法 同时把数据当作参数传递给这个方法
    goodsinfo.vue

 <!-- 拿到子组件向父组件传递的数量 -->
 <numbox @getcount="getSeletedCount"></numbox>

在data中定义商品数量

 selectedCount:1//保存用户选中的商品数量 默认用户买一个

定义getSelectCount事件

 getSeletedCount(count){
            //当子组件把选中的数量传递给父组件的时候 把选中的值保存到data上
            this.selectedCount=count
        }

在文本框的值发生改变的时候 子组件就要把数据传递给父组件 调用文本框的change事件
numbox.vue

<input id="test" class="mui-input-numbox" type="number" value="1" 
 @change="countChange" ref="numbox"/>
 methods:{
        countChange(){
        // 在文本框的值发生改变的时候 子组件就要通过事件调用把数据传递给父组件
         this.$emit('getcount',parseInt(this.$refs.numbox.value))
        }
    }

实现numbox能增加到的最大值为库存情况的件数
子组件numbox最大能选择的数量由库存量决定 库存量由父组件向子组件传递

<numbox @getcount="getSeletedCount" :max="goodsinfo.stock_quantity"></numbox>

在number.vue通过props保存父组件传来的值

 //父组件向子组件传递的库存量值
    props:["max"]

组件中绑定max值

<div class="mui-numbox" data-numbox-min='1' :data-numbox-max='max'>

问题:此时numbox值在超过了库存量 还能够继续增加

原因:goodsinfo是异步获取的数据 在渲染组件并手动传值的时候 还未获取到goodsinfo的值 所以传递的是undifined

解决:我们不知道什么时候能拿到max值 但是总归有一刻会拿到max值 使用watch属性监听 父组件传递过来的max值 不管watch会被触发几次 但是 最后一次肯定是一个合法的数值

通过MUI的number box组件的官方api可知 通过js的方式设置numbox的最大和最小值

 watch:{
        //属性监听
        max:function(newVal,oldVal){
           mui(".mui-numbox").numbox().setOption('max',newVal)
        }
    }
  • vuex实现加入购物车功能
    在goodsinfo.vue中拼接处一个要保存到store中car数组里的商品信息对象
 var goodsinfo={
                id:this.id,
                count:this.selectedCount,
                price:this.goodsinfo.sell_price,
                selected:true
            }

安装vuex包 在main.js导入并注册vuex

import Vuex from 'vuex'
Vue.use(Vuex)

new一个store实例 并挂载

var store=new Vuex.Store({})
var vm=new Vue({
    el:'#app',
    render:c=>c(app),
    router,
    store
})

在state中定义car数组 存储商品对象 并在mutations中定义addToCar方法将商品对象保存到car数组中

var store=new Vuex.Store({
    state:{
        car:[]//将购物车中商品的数据用一个数组存储起来 在car数组中存储一些商品的
        //对象,暂时将商品的对象设计成 {id:商品的id,count:要购买的数量,price:商品的单价,seleted:true}
    },
    mutations:{
        addToCar(state,goodsinfo){
            //点击加入购物车 把商品信息 保存到store中的car上
           //1 如果购物车中,之前就已经有这个对应的商品了 那么,只需要更新数量
           //2 如果没有 则直接把数据push到car中即可

           //假设在购物车中没有找到对应的商品
         var flag=false  
         state.car.some(item=>{//some是找到就停止了
             if(item.id==goodsinfo.id){
                 item.count+=parseInt(goodsinfo.count)//把字符串转为数字
                 return true
             }
         })
         //如果最终循环完毕得到的flag还是false
         if(!flag){
             state.car.push(goodsinfo)
         }
        }
    },

在goodsinfo.vue中调用store中的mutations来将商品加入购物车

 this.$store.commit("addToCar",goodsinfo);
  • 实现加入购物车徽标数量的变化

在vue的getters中实现

 getters:{
        getAllCount(state){
            var c=0;
            state.car.forEach(item=>{
                c+=item.count
            })
            return c
        }
    }

在app.vue中绑定数据

<span class="mui-icon mui-icon-extra mui-icon-extra-cart">
	<span class="mui-badge" id="badge">{{$store.getters.getAllCount}}</span>
</span>
  • 实现购物车数据持久存储
    在mutations中 当更新car后把car数组存储到本地的localStorage中
  mutations:{
        addToCar(state,goodsinfo){
            //点击加入购物车 把商品信息 保存到store中的car上
           //1 如果购物车中,之前就已经有这个对应的商品了 那么,只需要更新数量
           //2 如果没有 则直接把数据push到car中即可

           //假设在购物车中没有找到对应的商品
         var flag=false  
         state.car.some(item=>{//some是找到就停止了
             if(item.id==goodsinfo.id){
                 item.count+=parseInt(goodsinfo.count)//把字符串转为数字
                 return true
             }
         })
         //如果最终循环完毕得到的flag还是false
         if(!flag){
             state.car.push(goodsinfo)
         }
         //当更新car后把car数组存储到本地的localStorage中
         localStorage.setItem('car',JSON.stringify(state.car))
        }

每次刚进入网站 肯定会调用main.js在刚调用的时候 先从本地存储中 把购物车的数据读出来 放到store中

var car=JSON.parse(localStorage.getItem('car')||'[]')

var store=new Vuex.Store({
    state:{
        car:car
    },

7 购物车页面的制作

  • 绘制购物车页面的商品列表布局

新建shopcarbox.vue子组件 复制粘贴之前numbox.vue的代码 并修改

<template>
    <!-- 最大值为props中max值 -->
    <!-- 我们不知道什么时候能拿到max值 但是总归有一刻会拿到max值 -->
    <!-- 使用watch属性监听 父组件传递过来的max值 不管watch会被触发几次 但是
    最后一次肯定是一个合法的数值 -->
   	<div class="mui-numbox" data-numbox-min='1' :data-numbox-max='max'>
		<button class="mui-btn mui-btn-numbox-minus" type="button">-</button>
        <!-- 在文本框的值发生改变的时候 子组件就要把数据传递给父组件 调用文本框的change事件-->
		<input id="test" class="mui-input-numbox" type="number" value="1" 
        @change="countChange" ref="numbox"/>
		<button class="mui-btn mui-btn-numbox-plus" type="button">+</button>
	</div>
    
</template>
<script>
  import mui from '../../lib/mui-master/dist/js/mui.js'
export default{
    mounted(){
        //要自己手动初始化该组件
        mui('.mui-numbox').numbox();
    },
    methods:{
        countChange(){
        }
    },

}
</script>

导入该子组件 并初步绘制界面
shopcarContainer.vue

<script>
  import numbox from '../sub/shopcarbox.vue'
  export default{
      components:{
          numbox
      }
  }
</script>
<template>
    <div class="shopcar-container">
        <div class="goods-list">
            <div class="mui-card">
				<div class="mui-card-content">
					<div class="mui-card-content-inner">
					    <mt-switch></mt-switch>
                        <img src="">
                        <div class="info">
                            <h1>小米</h1>
                            <p>
                                <span class="price">2100</span>
                                <numbox></numbox>
                                <a href="删除"></a>
                            </p>
                        </div>
					</div>
				</div>
		</div>
        
        <div class="mui-card">
				<div class="mui-card-content">
					<div class="mui-card-content-inner">
						这是一个最简单的卡片视图控件;卡片视图常用来显示完整独立的一段信息,比如一篇文章的预览图、作者信息、点赞数量等
					</div>
				</div>
		</div>    
        </div>
    </div>
</template>

设置页面样式


  • 获取购物车商品列表数据并渲染
  data(){
          return{
              goodslist:[]
          }
      },
      created(){
          this.getGoodsList()
      },
      methods:{
          getGoodsList(){
              var idArr=[];
              this.$store.state.car.forEach(item =>idArr.push(item.id))
                
              if(idArr.length==0){
                  return
              }
              //获取购物车商品列表 根据api路径后面要加上所有商品的id值
              this.$http.get('api/goods/getshopcarlist/'+idArr.join(",")).then(result=>{
                  if(result.body.status==0){
                     this.goodslist=result.body.message
                  }
              });
          }
      },
  <div class="goods-list">
            <div class="mui-card" v-for="item in goodslist" :key="item.id">
				<div class="mui-card-content">
					<div class="mui-card-content-inner">
					    <mt-switch></mt-switch>
                        <img :src="item.thumb_path">
                        <div class="info">
                            <h1>{{item.title}}</h1>
                            <p>
                                <span class="price">{{item.sell_price}}</span>
                                <numbox></numbox>
                                <a href="删除"></a>
                            </p>
                        </div>
					</div>
				</div>
		</div>

Vue移动端商城项目_第34张图片

  • 初始化购物车列表的商品数量
    如何从购物车中获取商品的数量
    可以先创建一个空对象 然后循环购物车中所有的商品的数据 把当前循环这条商品的id,作为对象的属性名 count值作为对象的属性值 这样 把所有的商品循环 一遍 就会得到一个对象 {88:2,89:1,90:4}

在vuex的getters中循环创建对象
main.js

  getGoodsCount(state){
            var o=[]
            state.car.forEach(item=>{
                o[item.id]=item.count
            })
            return o
        }

向子组件传递购物车的商品数量
shopcontainer.vue

  <numbox :initcount="$store.getters.getGoodsCount[item.id]"></numbox>

在子组件中通过props接收并渲染
shopcarbox.vue

export default{
    mounted(){
        //要自己手动初始化该组件
        mui('.mui-numbox').numbox();
    },
    methods:{
        countChange(){
        }
    },
    props:["initcount"]

}
	<input id="test" class="mui-input-numbox" type="number" :value="initcount" 
        @change="countChange" ref="numbox"/>

Vue移动端商城项目_第35张图片

  • 当改变列表商品数量时同时也将数量同步到store中 使购物车数量改变
    通过goodsid给子组件传递商品id值
     <numbox :initcount="$store.getters.getGoodsCount[item.id]" :goodsid="item.id"></numbox>

子组件接收goodsid值
shopcarbox.vue

 props:["initcount","goodsid"]

定义shopcarbox.vue的countChange方法 调用vuex的mutations中的updateGoodsInfo方法

  methods:{
        countChange(){
            //数量改变了
            //每当数量值改变 则立即把最新的数量同步到 购物车store中 覆盖之前的数量值
            this.$store.commit("updateGoodsInfo",{
                id:this.goodsid,
                count:this.$refs.numbox.value
            })
        }
    },

在vuex的mutations中定义updateGoodsInfo方法

 updateGoodsInfo(state,goodsinfo){
            //修改购物车中商品的数量值
            state.car.some(item=>{
                if(item.id==goodsinfo.id){
                    item.count=parseInt(goodsinfo.count)
                        return true              
                }
            })
            //当修改商品的数量 把最新的购物车数据 保存到本地存储中
            localStorage.setItem('car',JSON.stringify(state.car))
        }

Vue移动端商城项目_第36张图片

  • 删除购车中的商品
    改变v-for循环 给删除绑定remove事件
    shopcarContainer.vue
<div class="mui-card" v-for="(item,i) in goodslist" :key="item.id">
				<div class="mui-card-content">
					<div class="mui-card-content-inner">
					    <mt-switch></mt-switch>
                        <img :src="item.thumb_path">
                        <div class="info">
                            <h1>{{item.title}}</h1>
                            <p>
                                <span class="price">{{item.sell_price}}</span>
                                <!-- 传递购物车的商品数量 -->
                                <numbox :initcount="$store.getters.getGoodsCount[item.id]" :goodsid="item.id"></numbox>
                                <!-- 如何从购物车中获取商品的数量 -->
                                <!-- 1 可以先创建一个空对象 然后循环购物车中所有的商品的数据 把当前循环
                                这条商品的id,作为对象的属性名 count值作为对象的属性值 这样 把所有的商品循环
                                一遍 就会得到一个对象 {88:2,89:1,90:4} -->
                                <a href="#" @click.prevent="remove(item.id,i)">删除</a>
                            </p>
                        </div>
					</div>
				</div>
		</div>

定义remove事件

 remove(id,index){
              //点击删除 把商品从store中根据传递的id删除 同时把当前组件中的goodslist中对应要删除的那个
              //商品通过index来删除
              this.goodslist.splice(index,1)
              this.$store.commit("removeFormCar",id)
          }

在vuex的mutations中定义 removeFormCar
main.js

 removeFormCar(state,id){
            //根据id 从store中的购物车中删除对应的那条商品的数据
            state.car.some((item,i)=>{
                if(item.id==id){
                    state.car.splice(i,1)
                    return true
                }
            })
            //将删除完毕后 最新的购物车数据 同步到本地存储中
            localStorage.setItem('car',JSON.stringify(state.car))
        }

Vue移动端商城项目_第37张图片

  • 绘制结算区域
 <div class="mui-card">
				<div class="mui-card-content">
					<div class="mui-card-content-inner jiesuan">
						<div class="left">
                            <p>总计 (不含运费)</p>
                            <p>已勾选商品 <span class="red">0</span> 件,总价 <span class="red">0</span></p>
                        </div>
                        <mt-button type="danger">结算</mt-button>
					</div>
                 
				</div>
		 </div>  
 .jiesuan{
        display: flex;
        justify-content: space-between;
        align-items: center;
        .red{
            color: red;
            font-weight: bold;
            font-size: 16px;
        }
    }

Vue移动端商城项目_第38张图片

  • 把store中选中的状态同步到页面上

在vuex的gatters中定义getGoodsSelect方法

  getGoodsSelect(state){
            var o={}
            state.car.forEach(item=>{
                o[item.id]=item.selected
            })

            return o
        }

通过v-model双向数据绑定
shopcarContainer.vue

 <mt-switch v-model="$store.getters.getGoodsSelect[item.id]"></mt-switch>

Vue移动端商城项目_第39张图片

  • 同步商品的勾选状态到store中保存

监听选择状态的改变
shopcarContainer.vue

<mt-switch v-model="$store.getters.getGoodsSelect[item.id]"
    @change="seletedChange(item.id,$store.getters.getGoodsSelect[item.id])"></mt-switch>

定义seletedChange事件

 seletedChange(id,value){
              //每当点击开关把最新的开关状态 同步到store中
              this.$store.commit('updateGoodsSelected',{id,selected:value})
          }

在vuex的mutations中定义updateGoodsSelected

  updateGoodsSelected(state,info){
            //更新商品选择的状态到store
            state.car.some(item=>{
                if(item.id==info.id){
                    item.selected=info.selected
                    return true
                }
            })
        
            localStorage.setItem('car',JSON.stringify(state.car))
        }

刷新后仍旧能保存上一次的选择状态
Vue移动端商城项目_第40张图片

  • 实现勾选数量和总价的自动计算

根据选择状态的改变 件数和总价也随之改变

在vuex的getters中定义 getGoodsCountAndAmount方法

getGoodsCountAndAmount(state){
            var o={
                count:0,//勾选的数量
                amount:0//勾选的总价
            }
            state.car.forEach(item=>{
                if(item.selected){
                    o.count+=item.count
                    o.amount+=item.price*item.count
                }
            })
            return o
        }

在shopcarContainer.vue中调用

 <div class="mui-card">
				<div class="mui-card-content">
					<div class="mui-card-content-inner jiesuan">
						<div class="left">
                            <p>总计 (不含运费)</p>
                            <p>已勾选商品 <span class="red">{{$store.getters.getGoodsCountAndAmount.count}}</span> 件,
                            总价 <span class="red">{{$store.getters.getGoodsCountAndAmount.amount}}</span></p>
                        </div>
                        <mt-button type="danger">结算</mt-button>
					</div>
                 
				</div>
		 </div> 

Vue移动端商城项目_第41张图片

Vue移动端商城项目_第42张图片
Vue移动端商城项目_第43张图片
Vue移动端商城项目_第44张图片

8 返回功能的实现

使用mint-ui的返回按钮

<mt-header fixed title="欣子的商城">
		   <span slot="left" @click="goBack" v-show="flag">
				<mt-button icon="back">返回</mt-button>
			</span>
	  </mt-header>
export default{
	data(){
		return{
			flag:false
		}
	},
	created(){
		//防止页面刚进入时未触发路由的改变 而不显示返回按钮
		 this.flag=this.$route.path==="/home"?false:true;
	},
	methods:{
		goBack(){
			//点击后退
			this.$router.go(-1)
		}
	},
	watch:{
		//监听路径 如果到首页则隐藏
		'$route.path':function(newVal){
			if(newVal=='/home'){
				this.flag=false
			}else{
				this.flag=true
			}
		}
	}
}

Vue移动端商城项目_第45张图片
Vue移动端商城项目_第46张图片
Vue移动端商城项目_第47张图片

你可能感兴趣的:(Vue移动端商城项目)