Vue移动端商城项目
1 准备基本的项目模板文件结构如下index.html<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport"content="width=device-width, user...
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';
Vue.component(Header.name, Header);
给mt-header标签的父元素div设置padding值 防止文字出现在顶部导航栏的底下
app.vue
<template>
<div class="app-container">
<!-- 顶部header区域 -->
<!-- 引入mint-ui的header -->
<mt-header fixed title="欣子的商城"></mt-header>
<h1>app组件</h1>
</div>
</template>
<script>
</script>
<style lang="scss" scoped>
.app-container{
padding-top:40px
}
</style>
npm run dev运行
-
实现底部tabbar
使用MUI的代码片段app.vue
<!-- 底部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';
Vue.component(Header.name, Header);
Vue.component(Swipe.name, Swipe);
Vue.component(SwipeItem.name, SwipeItem);
HomeCotainer.vue
<mt-swipe :auto="4000">
<mt-swipe-item>1</mt-swipe-item>
<mt-swipe-item>2</mt-swipe-item>
<mt-swipe-item>3</mt-swipe-item>
</mt-swipe>
设置相关样式要给父元素一个高度轮播图才能显示出来
<style lang="scss" scoped>
.mint-swipe{
height: 200px;
.mint-swipe-item{
&:nth-child(1){
background-color: red;
}
&:nth-child(2){
background-color: yellow;
}
&:nth-child(3){
background-color: green;
}
img{
width: 100%;
height: 100%;
}
}
}
</style>
- 完成首页中轮播图数据的加载
在main.js导入vue.resource的包
import VueResource from 'vue-resource'
Vue.use(VueResource)
用vueResource的this.$http.get获取数据 并将数据保存到data上
HomeContainer.vue
import {Toast} from 'mint-ui'
export default{
data(){
return{
img:'../../img/img1.jpg',
lunboList:[]//保存轮播图的数组
}
},
created(){
this.getLunbo()
},
methods:{
//获取轮播图
getLunbo(){
this.$http.get('api/getlunbo').then(result=>{
// console.log(result.body)
if(result.body.status===0){
this.lunboList=result.body.message;
//拼接一个对象代替加载不出来的那张轮播图
var obj={
id:2,
url:'http://www.itcast.cn/subject/phoneweb/index.html',
img:'../../img/img1.jpg'
}
this.lunboList.splice(1,1,obj)
console.log( this.lunboList)
}else{
Toast('加载轮播失败')
}
})
}
}
}
用v-for循环渲染图片
<template>
<div>
<mt-swipe :auto="4000">
<!-- 由于每一个url地址都是唯一的 可以使用item.url当key -->
<mt-swipe-item v-for="item in lunboList" :key="item.url">
<img :src="item.img">
</mt-swipe-item>
</mt-swipe>
</div>
</template>
- 完成9宫格布局效果
将body的整体颜色改为白色
index.html
<body style="background-color: white !important">
使用MUI提供的grid-default
注意:当 file-loader 的版本是 4.3.0 及以上,则需要在 webpack.config.js 中手动配置属性 esModule :否则图片会显示不出
{
test: /\.(jpg|jpeg|png|gif|svg)$/,
loader: "url-loader",
// limit:8834,
options: {
esModule: false, // 默认值是 true,需要手动改成 false
name: "[hash:8]-[name].[ext]"
}
},
Homecontainer.vue
<!--使用MUI提供的gird-default 六宫格 -->
<ul class="mui-table-view mui-grid-view mui-grid-9">
<li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3">
<a href="#">
<img src="../../img/menu1.png">
<div class="mui-media-body">新闻咨询</div>
</a>
</li>
<li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3">
<a href="#">
<img src="../../img/menu2.png">
<div class="mui-media-body">图片分享</div>
</a>
</li>
<li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3">
<a href="#">
<img src="../../img/menu3.png">
<div class="mui-media-body">商品购买</div>
</a>
</li>
<li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3">
<a href="#">
<img src="../../img/menu4.png">
<div class="mui-media-body">留言反馈</div>
</a>
</li>
<li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3">
<a href="#">
<img src="../../img/menu5.png">
<div class="mui-media-body">视频专区</div>
</a>
</li>
<li class="mui-table-view-cell mui-media mui-col-xs-4 mui-col-sm-3">
<a href="#">
<img src="../../img/menu6.png">
<div class="mui-media-body">联系我们</div>
</a>
</li>
</ul>
修改6宫格的样式
.mui-grid-view{
font-size: 13px;
&.mui-grid-9{
background-color: white;
border: 0;
.mui-table-view-cell{
border: 0;
img{
width: 60px;
height: 60px;
}
}
}
}
- 实现不同页面切换时的动画效果
将中间的路由区域用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.message
console.log(result.body.status)
}else{
Toast('获取数据失败')
}
})
}
}
}
</script>
v-for循环渲染
<ul class="mui-table-view">
<li class="mui-table-view-cell mui-media" v-for='item in newslist' :key='item.id'>
<a href="javascript:;">
<img class="mui-media-object mui-pull-left" :src="item.img_url">
<div class="mui-media-body">
<h1>{{item.title}}</h1>
<p class='mui-ellipsis'>
<span>发表时间:{{item.add_time|dateFormat}}</span>
<span>点击:{{item.click}}</span>
</p>
</div>
</a>
</li>
</ul>
- 解决最底下的列表被底部导航栏覆盖的bug
在app.vue增加一个padding-bottom
.app-container{
padding-top:40px;
padding-bottom: 40px;
overflow-x: hidden;//将向左滑动时超出屏幕宽度的那一部分隐藏
//不然 顶部的header和底部tabbar都会被右侧进来的组件挤走并出现滚动条
}
- 定义全局过滤器格式化时间
安装格式化时间的插件:npm i moment -S
导入格式化时间的插件 并定义全局过滤器
main.js
import moment from 'moment'
//定义全局的过滤器
Vue.filter('dateFormat',function(dataStr,pattern='YYYY-MM-DD HH:mm:ss'){
return moment(dataStr).format(pattern)
})
调用过滤器
NewsList.vue
<span>发表时间:{{item.add_time|dateFormat}}</span>
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';
Vue.component(Button.name, Button);
comment.vue
<template>
<div class="cmt-container">
<h3>
发表评论
</h3>
<hr>
<textarea placeholder="请输入要bb的内容" maxlength="120"></textarea>
<mt-button type='primary' size='large'>发表评论</mt-button>
<div class="cmt-list">
<div class="cmt-item">
<div class="cmt-title">
第一楼 用户:匿名用户 发表时间:2012-12-12 12:12:12
</div>
<div class="cmt-body">
我是鼠鼠
</div>
</div>
</div>
<mt-button type='danger' size='large' plain>加载更多</mt-button>
</div>
</template>
h3{
font-size: 18px;
}
textarea{
font-size: 14px;
height: 85px;
margin: 0;
}
.cmt-list{
margin: 5px 0;
.cmt-item{
font-size: 13px;
.cmt-title{
background-color: #aaa;
line-height: 30px;
}
.cmt-body{
line-height: 35px;
// 缩进
text-indent: 2em;
}
}
}
- 获取评论数据
根据api文档获取评论数据还要传入id值 id值从父组件那获取
父组件向子组件传id
NewsInfo.js
<!-- 评论子组件 -->
<!-- 父组件像子组件传的id值 -->
<comment :id='this.id'></comment>
子组件通过props获取id值 并获取评论数据
comment.vue
import {Toast} from 'mint-ui'
export default{
data(){
return{
pageIndex:1,//默认展示第一页数据
comments:[]//所以的评论数据
}
},
created(){
this.getComments()
},
methods:{
getComments(){
//根据所提供的第三方接口api文档获取评论数据还要传入id值
this.$http.get('api/getcomments/'+this.id+'?pageindex='+this.pageIndex).then(result=>{
console.log(result.body.status)
if(result.body.status===0){
this.comments=result.body.message
}else{
Toast('获取评论失败')
}
})
}
},
//获取父组件的id
props:['id']
}
渲染评论
<div class="cmt-item" v-for='(item,i) in comments' :key="item.add_time">
<div class="cmt-title">
第{{i+1}}楼 用户:{{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){
//将获取的数据拼接到原来的数据后面 不覆盖原来的数据
this.comments=this.comments.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不能写为this.comments=this.comments.unshift(cmt)
postComments(){
//校验是否为空内容
//trim() 函数移除字符串两侧的空白字符或其他预定义字符
if(this.msg.trim().length===0){
//如果为空就return出去 后续代码不执行了
return Toast('评论内容不能为空')
}
//根据所提供的第三方接口api文档 发表评论数据还要传入id值
this.$http.post('api/postcomment/'+this.id,
{content:this.msg.trim()})
.then(result=>{
//拼接出一个评论对象
var cmt={
user_name:'匿名用户',
add_time:Date.now(),
content:this.msg.trim()
};
this.comments.unshift(cmt)
this.msg="";
})
}
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';
// Vue.component(Header.name, Header);
// Vue.component(Swipe.name, Swipe);
// Vue.component(SwipeItem.name, SwipeItem);
// Vue.component(Button.name, Button);
// Vue.use(Lazyload);
//要想实现图片懒加载 就得全部导入不能按需导入
import MintUI from 'mint-ui'
Vue.use(MintUI)
import 'mint-ui/lib/style.css'
修改样式
要注意在向上滑动时会出现滑动导航栏会覆盖顶部header
不能调整滑动导航栏的层级为-1 不然会滑动不了 应该在app.vue中设置header的层级
// .mui-slider{
// z-index: -1;//不能设置z-index:-1 不然顶部栏会滑动不了
// }
app.vue
.mint-header.is-fixed{//设置header层级
z-index:2
}
<ul class="photo-list">
<li v-for="item in list" :key="item.id">
<img v-lazy="item.img_url">
</li>
</ul>
设置图片列表样式
.photo-list{
margin: 0;
padding: 10px;
list-style: none;
padding-bottom: 0;
li{
margin-bottom: 10px;
background-color: #ccc;
text-align: center;
box-shadow: 0 0 9px #999;
img{
width: 100%;
//去除图片边距
display: block;
}
//实现图片懒加载
img[lazy=loading] {
width: 40px;
height: 300px;
margin: auto;
}
}
}
补充说明:由于给的获取图片列表的接口有问题 只能获取0和17-22的id值的图片列表 而顶部的滚动导航从第二个开始对应的id为14 所以在点击第二个导航栏时页面会空白
解决方式:
给将顶部分类列表的数据获取到之后放到cates数组中 给cates数组中的每一个对象在增加一个id2的属性 赋值为17-22 i值要从1开始 因为第一个导航栏对应的id值为0 是有图片列表的的
getAllCategory(){
//获取所有图片分类
this.$http.get('api/getimgcategory').then(result=>{
if(result.body.status===0){
result.body.message.unshift({title:'全部',id:0});
this.cates=result.body.message
//增加id2的属性 并赋值
for(var i=1,j=17;i<=6,j<22;i++,j++){
this.cates[i]['id2']=j
console.log(this.cates)
}
}
}
)
},
修改顶部滚动条绑定的点击事件传递的id值 除了id值为0的第一个导航条 其他的都要传递自定义的id2的值 这样就改变了图片列表加载时对应的id值 点击其他的就可以加载出来了
<a :class="['mui-control-item',item.id==0?'mui-active':'']"
v-for='item in cates' :key='item.id' @click="getImgList(item.id==0?0:item.id2)" >
{{item.title}}
</a>
解决在手机端调试的时候点击顶部菜单无法切换的问题
将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,//从路由中获取到的图片id
photoinfo:{}//图片详情
}
},
created(){
this.getPhotoInfo()
},
methods:{
getPhotoInfo(){
//获取图片详情
this.$http.get('api/getimageInfo/'+this.id).then(result=>{
if(result.body.status===0){
if(result.body.status===0){
this.photoinfo=result.body.message[0]
}
}
})
}
},
渲染页面 并重新设置样式
<div class="photoinfo-container">
<h3>{{photoinfo.title}}</h3>
<p class="subtitle">
<span>发表时间:{{photoinfo.add_time|dateFormat}}</span>
<span>点击:{{photoinfo.click}}次</span>
</p>
<hr>
<!-- 缩略图区域 -->
<!-- 图片内容区域 -->
<!-- 根据接口文档 content带有html标签 所以要v-html -->
<div class="content" v-html='photoinfo.content'></div>
<!-- 放置一个现成的评论子组件 -->
</div>
.photoinfo-container{
padding:3px;
h3{
font-size: 15px;
text-align: center;
margin: 15px 0;
color:#26a2ff;
}
.subtitle{
display: flex;
justify-content: space-between;
font-size: 13px;
}
.content{
font-size: 13px;
line-height: 30px;
}
}
导入之前写好的评论子组件
import comment from '../sub/comment.vue'
挂载该组件
components:{
'cmt-box':comment
}
放置子组件
<!-- 放置一个现成的评论子组件 -->
<cmt-box :id='id'></cmt-box>
- 实现图片详情中缩略图的功能
使用插件 vue-preview这个缩略图插件
安装插件:npm i vue-preview -S
main.js导入插件
//使用缩略图插件
import VuePreview from 'vue-preview'
// defalut install
Vue.use(VuePreview)
根据api文档使用缩略图插件
photoInfo.vue
<!-- 使用缩略图插件 -->
<div class="suonue">
<vue-preview :slides="list" @close="handleClose"></vue-preview>
</div>
在data中创建一个list数组 根据api list得是一个数组
data(){
return{
id:this.$route.params.id,//从路由中获取到的图片id
photoinfo:{},//图片详情
//根据缩略图插件官方api list得是一个数组
list:[]
}
},
根据api:每个图片的数据对象中 必须有msrc,w和h属性
获取到所有的图片列表 然后v-for指令渲染数据 增加w,和h属性
//获取缩略图
getThumbs(){
this.$http.get('api/getthumimages/'+this.id).then(result=>{
if(result.body.status===0){
//根据官方api 必须提供默认的宽高值 所以循环遍历每一项添加宽高值
result.body.message.forEach(item => {
item.w=100;
item.h=200;
item.msrc = item.src;
});
//把完整的数据保存到list中
this.list=result.body.message
}
})
},
在created调用该方法
设置缩略图样式
.suonue {
/deep/ .my-gallery {
display: flex;
flex-wrap: wrap;
figure {
width: 30%;
margin: 5px;
img {
width: 100%;
}
}
}
}
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.img
result.body.message.forEach(item => {
item.img=item.src
});
this.lunbotu=result.body.message
}
})
}
解决轮播图过宽的问题
由于首页中使用的轮播图组件图片宽高为100% 而商品详情页中图片高度也会是100%所以就会导致轮播图过宽
商品详情页中的轮播图高度应该是100% 宽度应该自适应
可以定义一个属性让使用轮播图的调用者 手动指定是否为100%宽度
给homeContainer.vue的swipe组件添加属性
<swiper :lunboList="lunboList" :isfull='true'></swiper>
给goodsinfo.vue也同样指定
<swiper :lunboList="lunbotu" :isfull="false"></swiper>
在swiper.vue的props中添加isfull
export default{
props:['lunboList','isfull']
}
设置.full类名的样式 并使轮播图居中显示
.mint-swipe{
height: 200px;
.mint-swipe-item{
text-align: center;
img{
// width: 100%;
height: 100%;
}
}
}
.full{
width: 100%;
}
给img标签添加class属性 根据isfull来添加类名
<!-- 如果isfull为真 就添加full类名 -->
<img :src="item.img" :class="{'full':isfull}">
绘制商品购买区域和商品参数样式
购买使用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.left
const yDist=badgePosition.top-ballPosition.top
//使用es6模板字符串拼接
el.style.transform=`translate(${xDist}px, ${yDist}px)`;
el.style.transition="all 0.5s cubic-bezier(.4,-0.3,1,.68)";
done()
},
-
加入购物车时购物车数量随numbox动态变化
分析 如何实现加入购物车的时候拿到选择的数量
1 加入购物车按钮属于goodsinfo页面 数字属于numberbox页面
2 由于涉及到了父子组件的嵌套 无法直接在goodsinfo页面中获取到选中的商品数量的值
3 涉及到子组件向父组件传值 (事件调用机制)
4 事件调用本质 父向子传递方法 子调用这个方法 同时把数据当作参数传递给这个方法
goodsinfo.vue
<!-- 拿到子组件向父组件传递的数量 -->
<numbox @getcount="getSeletedCount"></numbox>
在data中定义商品数量
selectedCount:1//保存用户选中的商品数量 默认用户买一个
定义getSelectCount事件
getSeletedCount(count){
//当子组件把选中的数量传递给父组件的时候 把选中的值保存到data上
this.selectedCount=count
}
在文本框的值发生改变的时候 子组件就要把数据传递给父组件 调用文本框的change事件
numbox.vue
<input id="test" class="mui-input-numbox" type="number" value="1"
@change="countChange" ref="numbox"/>
methods:{
countChange(){
// 在文本框的值发生改变的时候 子组件就要通过事件调用把数据传递给父组件
this.$emit('getcount',parseInt(this.$refs.numbox.value))
}
}
实现numbox能增加到的最大值为库存情况的件数
子组件numbox最大能选择的数量由库存量决定 库存量由父组件向子组件传递
<numbox @getcount="getSeletedCount" :max="goodsinfo.stock_quantity"></numbox>
在number.vue通过props保存父组件传来的值
//父组件向子组件传递的库存量值
props:["max"]
组件中绑定max值
<div class="mui-numbox" data-numbox-min='1' :data-numbox-max='max'>
问题:此时numbox值在超过了库存量 还能够继续增加
原因:goodsinfo是异步获取的数据 在渲染组件并手动传值的时候 还未获取到goodsinfo的值 所以传递的是undifined
解决:我们不知道什么时候能拿到max值 但是总归有一刻会拿到max值 使用watch属性监听 父组件传递过来的max值 不管watch会被触发几次 但是 最后一次肯定是一个合法的数值
通过MUI的number box组件的官方api可知 通过js的方式设置numbox的最大和最小值
watch:{
//属性监听
max:function(newVal,oldVal){
mui(".mui-numbox").numbox().setOption('max',newVal)
}
}
- vuex实现加入购物车功能
在goodsinfo.vue中拼接处一个要保存到store中car数组里的商品信息对象
var goodsinfo={
id:this.id,
count:this.selectedCount,
price:this.goodsinfo.sell_price,
selected:true
}
安装vuex包 在main.js导入并注册vuex
import Vuex from 'vuex'
Vue.use(Vuex)
new一个store实例 并挂载
var store=new Vuex.Store({})
var vm=new Vue({
el:'#app',
render:c=>c(app),
router,
store
})
在state中定义car数组 存储商品对象 并在mutations中定义addToCar方法将商品对象保存到car数组中
var store=new Vuex.Store({
state:{
car:[]//将购物车中商品的数据用一个数组存储起来 在car数组中存储一些商品的
//对象,暂时将商品的对象设计成 {id:商品的id,count:要购买的数量,price:商品的单价,seleted:true}
},
mutations:{
addToCar(state,goodsinfo){
//点击加入购物车 把商品信息 保存到store中的car上
//1 如果购物车中,之前就已经有这个对应的商品了 那么,只需要更新数量
//2 如果没有 则直接把数据push到car中即可
//假设在购物车中没有找到对应的商品
var flag=false
state.car.some(item=>{//some是找到就停止了
if(item.id==goodsinfo.id){
item.count+=parseInt(goodsinfo.count)//把字符串转为数字
return true
}
})
//如果最终循环完毕得到的flag还是false
if(!flag){
state.car.push(goodsinfo)
}
}
},
在goodsinfo.vue中调用store中的mutations来将商品加入购物车
this.$store.commit("addToCar",goodsinfo);
- 实现加入购物车徽标数量的变化
在vue的getters中实现
getters:{
getAllCount(state){
var c=0;
state.car.forEach(item=>{
c+=item.count
})
return c
}
}
在app.vue中绑定数据
<span class="mui-icon mui-icon-extra mui-icon-extra-cart">
<span class="mui-badge" id="badge">{{$store.getters.getAllCount}}</span>
</span>
- 实现购物车数据持久存储
在mutations中 当更新car后把car数组存储到本地的localStorage中
mutations:{
addToCar(state,goodsinfo){
//点击加入购物车 把商品信息 保存到store中的car上
//1 如果购物车中,之前就已经有这个对应的商品了 那么,只需要更新数量
//2 如果没有 则直接把数据push到car中即可
//假设在购物车中没有找到对应的商品
var flag=false
state.car.some(item=>{//some是找到就停止了
if(item.id==goodsinfo.id){
item.count+=parseInt(goodsinfo.count)//把字符串转为数字
return true
}
})
//如果最终循环完毕得到的flag还是false
if(!flag){
state.car.push(goodsinfo)
}
//当更新car后把car数组存储到本地的localStorage中
localStorage.setItem('car',JSON.stringify(state.car))
}
每次刚进入网站 肯定会调用main.js在刚调用的时候 先从本地存储中 把购物车的数据读出来 放到store中
var car=JSON.parse(localStorage.getItem('car')||'[]')
var store=new Vuex.Store({
state:{
car:car
},
7 购物车页面的制作
- 绘制购物车页面的商品列表布局
新建shopcarbox.vue子组件 复制粘贴之前numbox.vue的代码 并修改
<template>
<!-- 最大值为props中max值 -->
<!-- 我们不知道什么时候能拿到max值 但是总归有一刻会拿到max值 -->
<!-- 使用watch属性监听 父组件传递过来的max值 不管watch会被触发几次 但是
最后一次肯定是一个合法的数值 -->
<div class="mui-numbox" data-numbox-min='1' :data-numbox-max='max'>
<button class="mui-btn mui-btn-numbox-minus" type="button">-</button>
<!-- 在文本框的值发生改变的时候 子组件就要把数据传递给父组件 调用文本框的change事件-->
<input id="test" class="mui-input-numbox" type="number" value="1"
@change="countChange" ref="numbox"/>
<button class="mui-btn mui-btn-numbox-plus" type="button">+</button>
</div>
</template>
<script>
import mui from '../../lib/mui-master/dist/js/mui.js'
export default{
mounted(){
//要自己手动初始化该组件
mui('.mui-numbox').numbox();
},
methods:{
countChange(){
}
},
}
</script>
导入该子组件 并初步绘制界面
shopcarContainer.vue
<script>
import numbox from '../sub/shopcarbox.vue'
export default{
components:{
numbox
}
}
</script>
<template>
<div class="shopcar-container">
<div class="goods-list">
<div class="mui-card">
<div class="mui-card-content">
<div class="mui-card-content-inner">
<mt-switch></mt-switch>
<img src="">
<div class="info">
<h1>小米</h1>
<p>
<span class="price">¥2100</span>
<numbox></numbox>
<a href="删除"></a>
</p>
</div>
</div>
</div>
</div>
<div class="mui-card">
<div class="mui-card-content">
<div class="mui-card-content-inner">
这是一个最简单的卡片视图控件;卡片视图常用来显示完整独立的一段信息,比如一篇文章的预览图、作者信息、点赞数量等
</div>
</div>
</div>
</div>
</div>
</template>
设置页面样式
<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.$store.commit("updateGoodsInfo",{
id:this.goodsid,
count:this.$refs.numbox.value
})
}
},
在vuex的mutations中定义updateGoodsInfo方法
updateGoodsInfo(state,goodsinfo){
//修改购物车中商品的数量值
state.car.some(item=>{
if(item.id==goodsinfo.id){
item.count=parseInt(goodsinfo.count)
return true
}
})
//当修改商品的数量 把最新的购物车数据 保存到本地存储中
localStorage.setItem('car',JSON.stringify(state.car))
}
- 删除购车中的商品
改变v-for循环 给删除绑定remove事件
shopcarContainer.vue
<div class="mui-card" v-for="(item,i) in goodslist" :key="item.id">
<div class="mui-card-content">
<div class="mui-card-content-inner">
<mt-switch></mt-switch>
<img :src="item.thumb_path">
<div class="info">
<h1>{{item.title}}</h1>
<p>
<span class="price">¥{{item.sell_price}}</span>
<!-- 传递购物车的商品数量 -->
<numbox :initcount="$store.getters.getGoodsCount[item.id]" :goodsid="item.id"></numbox>
<!-- 如何从购物车中获取商品的数量 -->
<!-- 1 可以先创建一个空对象 然后循环购物车中所有的商品的数据 把当前循环
这条商品的id,作为对象的属性名 count值作为对象的属性值 这样 把所有的商品循环
一遍 就会得到一个对象 {88:2,89:1,90:4} -->
<a href="#" @click.prevent="remove(item.id,i)">删除</a>
</p>
</div>
</div>
</div>
</div>
定义remove事件
remove(id,index){
//点击删除 把商品从store中根据传递的id删除 同时把当前组件中的goodslist中对应要删除的那个
//商品通过index来删除
this.goodslist.splice(index,1)
this.$store.commit("removeFormCar",id)
}
在vuex的mutations中定义 removeFormCar
main.js
removeFormCar(state,id){
//根据id 从store中的购物车中删除对应的那条商品的数据
state.car.some((item,i)=>{
if(item.id==id){
state.car.splice(i,1)
return true
}
})
//将删除完毕后 最新的购物车数据 同步到本地存储中
localStorage.setItem('car',JSON.stringify(state.car))
}
- 绘制结算区域
<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.$store.commit('updateGoodsSelected',{id,selected:value})
}
在vuex的mutations中定义updateGoodsSelected
updateGoodsSelected(state,info){
//更新商品选择的状态到store
state.car.some(item=>{
if(item.id==info.id){
item.selected=info.selected
return true
}
})
localStorage.setItem('car',JSON.stringify(state.car))
}
刷新后仍旧能保存上一次的选择状态
- 实现勾选数量和总价的自动计算
根据选择状态的改变 件数和总价也随之改变
在vuex的getters中定义 getGoodsCountAndAmount方法
getGoodsCountAndAmount(state){
var o={
count:0,//勾选的数量
amount:0//勾选的总价
}
state.car.forEach(item=>{
if(item.selected){
o.count+=item.count
o.amount+=item.price*item.count
}
})
return o
}
在shopcarContainer.vue中调用
<div class="mui-card">
<div class="mui-card-content">
<div class="mui-card-content-inner jiesuan">
<div class="left">
<p>总计 (不含运费)</p>
<p>已勾选商品 <span class="red">{{$store.getters.getGoodsCountAndAmount.count}}</span> 件,
总价 <span class="red">¥{{$store.getters.getGoodsCountAndAmount.amount}}</span></p>
</div>
<mt-button type="danger">结算</mt-button>
</div>
</div>
</div>
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
}
}
}
}
更多推荐
所有评论(0)