.gitignore 里面设置上传项目时可以忽略的文件,eg:node_modules

一、制作首页hadder和tabbar

  1. 完成Header区域,使用的的是mint-ui中的Header组件
  2. 制作底部的Tabbar区域,使用的是mui中的Tabbar.html
    - 在制作购物车小图标的时候,先把扩展图标的css样式,拷贝到项目中
    - 再拷贝我们的字体库ttf文件到项目中
    - 为购物车小图标添加图像样式
  3. 要在中间区域放置一个router-view来展示路由匹配的组件

切换页面(改造tabbar为router-link):

  • 在main.js中引入vue-router import VueRouter from "vue-router"
  • 将a标签改为router-link,href改为to
  • 在router.js中引入路由组件
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: '/', redirect: '/home' },
	    { path: '/home', component: HomeContainer },
	    { path: '/member', component: MemberContainer },
	    { path: '/shopcar', component: ShopcarContainer },
	    { path: '/search', component: SearchContainer }
	  ],
	  linkActiveClass: 'mui-active' // 覆盖默认的路由高亮的类,默认的类叫做router-link-active
	})

	export default router
  • 给App.vue中添加一个router-view标签

scss中&符号是交集选择器,不写是后代选择器

二、制作轮播图

  1. 获取数据使用vue-resource
  2. 在main.js中导入vue-rosource
  3. 在main.js中配置请求路径
    Vue.http.options.root = 'http://www.liulongbin.top:3005'
  • 在script中methods中定义方法,获取数据
<script>
		export default {
			data () {
				return{
					lunbotulist:[]
				}
			},
			created() {
				this.getlunbotu()
			},
			methods: {
				getlunbotu() {
					this.$http.get('api/getlunbo').then(res => {
						if(res.body.status === 0) {
							this.lunbotulist = res.body.message
						}
					})
				}
			}
		}
	</script>

渲染页面使用v-for循环,在组件中使用v-for时,要使用:key

三、九宫格到六宫格改造工程

  • 使用mui中的组件,将九宫格改造为六宫格
  • 图标可引入自己的图标,修改样式就ok

四、组件切换时的动画效果

用transition将中间内容包起来

<transition>
		  <router-view></router-view>
	</transition>

设置类

<style>
	.v-enter,
	.v-leave-to {
		  opacity: 0;
		  transform: translateX(100%);
	  }
	  
	  .v-enter-active,
	  .v-leave-active {
		  transition: all 0.5s ease;
	  }
</style>

问题:

  1. Header栏向右偏移
  2. 底部也会偏移,并且出现滚动条
  3. 要进入的元素先向左然后向上飘

解决:

  • 出现滚动条的原因:因为在切换页面的时候,他的真正宽度是两个页面拼接起来的宽度
    - 给外层container添加一个overflow-x: hidden
.app-container {
	       	padding-top:40px;
			padding-bottom: 50px;
			overflow-x: hidden;
	  }
  • 解决底部滚动条问题
.v-enter {
		  opacity: 0;
		  transform: translateX(100%);
	  }
	  
	  .v-leave-to {
		  opacity: 0;
		  transform: translateX(-100%);
	  }
	
	  .v-enter-active,
	  .v-leave-active {
		  transition: all 0.5s ease;
	  }
  • 解决元素飘动问题,给.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;
	  }

五、改造新闻资讯的路由连接

  • 将a标签改为router-link,href改为to,to=‘home/newslist’
  • 在router.js导入对应的路由组件

六、新闻资讯页面绘制

  1. 绘制界面,使用mui中的midia-list.html
  2. 使用vue-resource获取数据
  3. 渲染真实数据

给时间定义一个全局过滤器

安装moment npm install moment -S

import moment from 'moment'   // 导入格式化的时间插件
	
	Vue.filter('dateFormat', function (dataStr, pattern = 'YYYY-MM-DD HH:mm:ss') {
	  return moment(dataStr).format(pattern)
	})

使用:

<span>发表时间:{{item.add_time | dateFormat('YYYY-MM-DD')}}</span>

七、点击新闻资讯列表跳转到新闻详情

  1. 把列表中的每一项改造为 router-link,同时在跳转的时候应该提供唯一的id标识符
    <router-link :to="'/home/newsinfo/'+item.id">
  2. 创建新闻详情的组件页面NewsInfo.vue
  3. 在路由模块router.js中,将新闻详情的路由地址和组件页面对应起来
<script>
		export default {
		    data(){
		        return {
		            id: this.$route.params.id, //将url地址中传递过来的id值,挂载到data上,方便以后调用
		            newsinfo:{}
		        }
		    }
		}
	</script>
  1. 实现新闻详情页面布局和渲染

八、单独封装一个 comment.vue 的评论子组件

  1. 先创建一个单独的comment.vue组件模板
  2. 在需要使用comment组件的页面中,先动手导入comment组件
    import comment from './comment.vue'
  3. 在父组件中,使用components属性。将刚才导入comment组件注册为自己的子组件
methods: {
	        getNewsInfo(){//获取新闻详情
	            this.$http.get('api/getnew/'+this.id).then(res => {
	                if(res.body.status ===0 ){
	                    this.newsinfo = res.body.message[0];
	                }else {
	                    Toast('加载失败');
	                }
	            })
	        }
	    },
	
	    components: {// 用来注册子组件
	        "comment-box": comment
	    }
  1. 将注册子组件时候的注册名称以标签形式在页面中引用即可
    <comment-box></comment-box>
  2. 获取所有的评论数据显示到页面中getComments()方法
    父组件向子组件传值
    <comment-box :id="this.id"></comment-box>props: ['id']

九、点击加载更多评论的功能

  1. 为加载更多按钮绑定点击事件,在事件中,请求下一页数据
<mt-button type="danger" size="large" plain @click="getMore">加载更多</mt-button>
  1. 点击加载更多,让pageIndex++,然后重新调用this.getComments()方法,重新获取最新一页的数据
  2. 为了防止新数据覆盖老数据的情况,点击加载更多的时候,每当获取到新数据,应该让老数据调用数组concat方法,拼接新数组
methods:{
	        getComments(){// 获取评论
	            this.$http.get('api/getcomments/' + this.id + '?pageindex=' + this.pageIndex).then(res => {
	                if(res.body.status === 0){
	                    // 获取新评论时不会把老数据清空覆盖
	                    this.comments = this.comments.concat(res.body.message);
	                }else {
	                    Toast('获取评论失败');
	                }
	            })
	        },
	        getMore(){// 加载更多
	            this.pageIndex++;
	            this.getComments();
	        }
	    }

十、发表评论

  1. 把文本框做双向数据绑定v-model='msg'
  2. 为发表按钮绑定一个事件@click="postComment"
  3. 校验评论内容是否为空,如果为空则Toast提示用户
  4. 通过vue-resource发送一个请求,把评论内容提交给服务器并保存
  5. 当发表评论完成后,重新刷新列表,以查看最近的评论
    - 如果调用getComments方法重新刷新评论列表的话,可能只能得到 最后一页的评论,前几页的评论获取不到
    - 换一种思路:当评论成功后,在客户端手动拼接出一个最新的评论对象,然后调用数组的unshift方法,把最新的评论追加到date中comments的开头;这样就能完美实现刷新评论列表的需求

在main.js中全局设置post时候表单数据格式组织形式 application/x-www-form-urlencoded

Vue.http.options.emulateJSON = true

postComment(){// 发表评论

            // 校验是否为空内容
            if(this.msg.trim().length === 0){
                return Toast('评论内容不能为空!');
            }
            
            this.$http.post("api/postcomment/" + this.$route.params.id,{content:this.msg.trim()}).then(function(res) {
                if(res.body.status === 0){
                	//拼接出一个评论对象
                    var cmt = {user_name: '匿名用户', add_time: Date.now(), content: this.msg.trim()};
                    this.comments.unshift(cmt);
                    this.msg = "";
                }
            })
        }

十一、制作图片分享页面

  1. 改造图片分析按钮为路由的链接并显示对应的组件页面
  2. 绘制图片列表组件页面结构并美化样式
    - 制作顶部的滑动条
    - 制作底部的图片列表
  3. 获取所有分类,并渲染分类列表

制作顶部滑动条的坑

另有博客专门介绍这部分内容:vue在引用mui.js文件时会遇到的各种问题

  1. 使用MUI中的tab-top-webview-main.html
  2. 需要把slider区域的mui-fullscreen类去掉
  3. 滑动条无法正常触发滑动,通过检查官方文档,发现这是JS组件,需要被初始化一下
    - 导入 mui.js
    - 调用官方提供的方式去初始化:
mui('.mui-scroll-wrapper').scroll({
    deceleration: 0.0005 //flick 减速系数,系数越大,滚动速度越慢,滚动距离越小,默认值0.0006
});
  1. 我们在初始化滑动条的时候,导入的mui.js ,但是控制台报错: Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode - mui.js 中用到了’caller’,‘callee’,and’arguments’,但是webpack打包好的bundle.js 中,默认是启用严格模式的,所以这两者冲突了
    - 解决方案:
    a. 把mui.js中的非严格模式的代码改掉,但是不现实
    b. 把webpack打包时候的严格模式禁用掉
    - 最终,我们选择了plan B移除严格模式:使用这个插件babel-plugin-transform-remove-strict-mode
  2. 刚进入图片分享页面的时候滑动条无法正常工作,然后发现如果要初始化滑动条,必须要等DOM元素加载完毕,所以我们把初始化滑动条的代码,搬到了mounted生命周期函数中
  3. 当滑动条调试OK后,发现tabbar无法正常工作了,这时需要把每个tabbar按钮的样式中mui-tab-item重新改一下名字,并且复制其样式

十二、制作图片列表区域

  1. 图片列表需要使用懒加载技术,使用Mint-UI提供的现成的组件lazy-load
  2. 渲染图片列表数据
  3. 实现了点击图片跳转到图片详情页面
  4. 实现详情页面的布局和美化,同时获取数据渲染页面
  5. 实现图片详情中缩略图的功能
    - 使用插件vue-preview这个缩略图插件
    安装 npm i vue-preview -S直接使用标签 <vue-preview :slides="list"></vue-preview> - 获取到所有的图片列表
    - 每个图片数据对象中,必须有w和h属性

十三、完成商品列表页面

  • 绘制商品列表页面
  • 实现商品列表的经典两列布局
.good-list {
	    display: flex;
	    flex-wrap: wrap; 
	    padding: 7px;
	    justify-content: space-between;
	
	    .goods-item {
	        padding: 2px;
	        width: 49%;
	        border: 1px solid #ccc;
	        box-shadow: 0 0 8px #ccc;
	        margin: 4px 0;
	        display: flex;
	        flex-direction: column;
	        justify-content: space-between;
	        min-height: 293px;
	
	        img {
	            width: 100%;
	        }
	
	        .title {
	            font-size: 14px;
	        }
	
	        .info {
	            background-color: #ddd;
	            
	            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;
	            }
	        }
	    }
	}
  • 加载商品列表中的数据并实现加载更多

十四、商品详情页面

  • 改造路由链接,绘制商品详情页面并美化
  • 使用编程式导航,传递对象
goDesc(id) {
	            this.$router.push({name: 'goodsdesc', params: {id}})
	        },
	
	        goComment(id) {
	            this.$router.push({name: 'goodscomment', params: {id}})
	        }
  • 绘制商品购买区域样式
  • 使用编程式导航实现图文介绍和商品评论跳转
  • 完成商品详情中的图文介绍和评论页面的渲染

十五、加入购物车的小球动画

  • 绘制小球,添加样式
.ball {
	        width: 15px;
	        height: 15px;
	        border-radius: 50%;
	        background-color: red;
	        position: absolute;
	        z-index: 99;
	        top: 390px;
	        left: 146px;
	    }
  • 小球动画
<transition 
            @before-enter="beforeEnter"
            @enter="enter"
            @after-enter="afterEnter">
            <div class="ball" v-show="ballFlag" ref="ball"></div>
        </transition>
beforeEnter(el) {
            el.style.transform = 'translate(0,0)'
        },

        enter(el,done) {
        	//先得到徽标的横纵坐标,再得到小球的横纵坐标
        	//然后让 y 值求差, x 值也求差,得到的结果,就是横纵坐标要位移的距离
            el.offsetWidth;
            //获取小球在页面上的位置
            const ballPosition = this.$refs.ball.getBoundingClientRect();
            //获取徽标在页面上的位置
            const badgePosition = document.getElementById('badge').getBoundingClientRect();
            const xDist = badgePosition.left - ballPosition.left;
            const yDist = badgePosition.top - ballPosition.top;
			
            el.style.transform = `translate(${xDist}px,${yDist}px)`;
            el.style.transition = 'all 0.5s cubic-bezier(.4,-0.3,1,.68)';
            done()
        },

        afterEnter(el) {
            this.ballFlag = !this.ballFlag
        }

十六、加入购物车及购物车页面的数量传递问题

如何实现加入购物车时,拿到选择的数量

  • 涉及到子组件向父组件传值(事件调用机制)
  • 事件调用本质:父向子传递方法,子调用这个方法, 同时把数据当作参数传递给这个方法
getSelectedCount(count) {
			//当子组件把选中的数量传递给父组件时,把选中的值保存到data上
            this.selectedCount = count;

        }

<p>购买数量:<numbox @getcount="getSelectedCount" :max="goodsinfo.stock_quantity"></numbox></p>

  • 子组件什么时候把值传给父组件

<input id="test" class="mui-input-numbox" type="number" value="1" @change="countChanged" ref="numbox"/>

methods: {
        countChanged() {
        	//每当文本框的数据被修改时,立即把最新的数据通过事件调用,传递给父组件
            this.$emit("getcount",parseInt(this.$refs.numbox.value));
        }
    },

    props: ["max"], // 设置可加入购物车数量最大值

    watch: {
    		//使用watch监听父组件传递过来的max值
        	max: function(newVal,oldVal) {
            mui(".mui-numbox").numbox().setOption("max",newVal);
        }
    }

在父组件的getSelectedCount()方法中可以拿到传递过来的值

十七、使用vuex并设计购物车数据存储方式

vuex是什么?怎么用?

将商品对象设计成以下样式

{ id: 商品id, count: 要购买数量, price: 商品价格, selected: false }

  • 在goodsInfo.vue中拼接出一个要保存到store中car数组里的商品信息对象
  • 点击加入购物车,把商品信息保存到store中的car上
    - 如果在购物车中,之前就已经有了这个对应的商品了,那么,只需要更新数据
    - 如果没有,则直接把商品数量push到car中即可

项目源码:商城app源码 vue_app