1 准备基本的项目模板
文件结构如下(用之前webpack配置好的文件目录)
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';ponent(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
<!-- 底部tabber区域 --><!-- 复制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-email"><span class="mui-badge">9</span></span><span class="mui-tab-label">消息</span></a><a class="mui-tab-item" href="#tabbar-with-contact"><span class="mui-icon mui-icon-contact"></span><span class="mui-tab-label">通讯录</span></a><a class="mui-tab-item" href="#tabbar-with-map"><span class="mui-icon mui-icon-gear"></span><span class="mui-tab-label">设置</span></a></nav>
在main.js导入MUI样式文件
//导入MUI的样式import './lib/mui-master/dist/css/mui.min.css'
完成底部小图标的设置
修改之前的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
实现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文件夹 新建子组件文件
初始化子组件的内容
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的代码片段 -->
完成轮播图效果使用mint-ui提供的swipe
main.js
//按需导入mint-ui的header swiper组件import {Header,Swipe, SwipeItem } from 'mint-ui';ponent(Header.name, Header);ponent(Swipe.name, Swipe);ponent(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:'/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,需要手动改成 falsename: "[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;}}}}
实现不同页面切换时的动画效果
将中间的路由区域用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>
获取新闻咨询列表并渲染页面
在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.messageconsole.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>
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>
实现新闻详细页的评论区域
在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';ponent(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">第一楼 用户:匿名用户 发表时间:-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;}}}
获取评论数据
根据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){ments=result.body.message}else{Toast('获取评论失败')}})}},//获取父组件的idprops:['id']}
渲染评论
<div class="cmt-item" v-for='(item,i) in comments' :key="item.add_time"><div class="cmt-title">第{{i+1}}楼 用户:{{item.user_name}} 发表时间:{{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){//将获取的数据拼接到原来的数据后面 不覆盖原来的数据ments=ments.concat(result.body.message)}else{Toast('获取评论失败')}
实现发表评论的功能
为文本框做双向数据绑定
<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不能写为ments=ments.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()};ments.unshift(cmt)this.msg="";})}
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
修改底部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;}
渲染分类列表的数据
在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}})}}
渲染数据
<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'>{{item.title}}</a></div></div></div>
获取分类的图片 并渲染图片列表
使用mint-ui提供的lazy load
<ul ><li v-for="item in list" :key="item.id"><img v-lazy="item.img_url"></li></ul>
在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';// ponent(Header.name, Header);// ponent(Swipe.name, Swipe);// ponent(SwipeItem.name, SwipeItem);// ponent(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;}}}
补充说明:由于给的获取图片列表的接口有问题 只能获取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']=jconsole.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>
解决在手机端调试的时候点击顶部菜单无法切换的问题
将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;}}
实现图片详情的数据加载和页面
修改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}
获取图片详情的数据
export default{data(){return{id:this.$route.params.id,//从路由中获取到的图片idphotoinfo:{}//图片详情}},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-preview这个缩略图插件
安装插件:npm i vue-preview -S
main.js导入插件
//使用缩略图插件import VuePreview from 'vue-preview'// defalut installVue.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,//从路由中获取到的图片idphotoinfo:{},//图片详情//根据缩略图插件官方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%;}}}}
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>
实现加载更多
使用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()}
实现商品列表详情页
使用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;}
获取商品详情页的轮播图数据
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">// <!-- 由于每一个url地址都是唯一的 可以使用item.url当key -->//<!-- 将来谁用次轮播图组件 谁为我们传递lunboList -->// <!-- 此时lunboList应该是父组件向子组件传值 --><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.imgresult.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}">
绘制商品购买区域和商品参数样式
购买使用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> 销售价:<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;}}}
渲染商品数据
定义商品数据的对象
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> 销售价:<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>
实现商品图文介绍和发表评论
使用编程导航实现图文介绍和发表评论页面的跳转
分别给这两个按钮绑定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>
<style lang="scss" scoped>.goodsdesc-container{padding: 4px;h3{font-size:16px;color: #226eff;text-align: center;margin: 15px 0;}.content{img{width: 100%;}} }</style>
在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>
实现加入购物车的动画效果
添加小球元素 设置小球的样式 给小球设置不可见
<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.leftconst 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还是falseif(!flag){state.car.push(goodsinfo)}}},
在goodsinfo.vue中调用store中的mutations来将商品加入购物车
this.$mit("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还是falseif(!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>
设置页面样式
<style lang="scss" scoped>.shopcar-container{background-color: #eee;overflow: hidden;.goods-list{.mui-card-content-inner{display: flex;align-items: center;}img{width: 60px;height: 60px;}h1{font-size: 13px;}.info{.price{color: red;font-size: bold;}}}}</style>
获取购物车商品列表数据并渲染
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>
初始化购物车列表的商品数量
如何从购物车中获取商品的数量
可以先创建一个空对象 然后循环购物车中所有的商品的数据 把当前循环这条商品的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"/>
当改变列表商品数量时同时也将数量同步到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.$mit("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))}
删除购车中的商品
改变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.$mit("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))}
绘制结算区域
<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;}}
把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>
同步商品的勾选状态到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.$mit('updateGoodsSelected',{id,selected:value})}
在vuex的mutations中定义updateGoodsSelected
updateGoodsSelected(state,info){//更新商品选择的状态到storestate.car.some(item=>{if(item.id==info.id){item.selected=info.selectedreturn true}})localStorage.setItem('car',JSON.stringify(state.car))}
刷新后仍旧能保存上一次的选择状态
实现勾选数量和总价的自动计算
根据选择状态的改变 件数和总价也随之改变
在vuex的getters中定义 getGoodsCountAndAmount方法
getGoodsCountAndAmount(state){var o={count:0,//勾选的数量amount:0//勾选的总价}state.car.forEach(item=>{if(item.selected){o.count+=item.counto.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>
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}}}}