1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > Vue移动端商城项目

Vue移动端商城项目

时间:2019-11-10 04:32:28

相关推荐

Vue移动端商城项目

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">第一楼&nbsp;&nbsp;用户:匿名用户&nbsp;&nbsp;发表时间:-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}}楼&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){//将获取的数据拼接到原来的数据后面 不覆盖原来的数据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>&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;}}}

渲染商品数据

定义商品数据的对象

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>

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

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

分别给这两个按钮绑定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}}}}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。