电商项目(附加github 学习项目)

简单电商项目代码地址:https://github.com/Betty09Zhang/shoppingMall

效果浏览:www.bettyzm.clude:4000/index.html   登录名:zm 密码:123

1. slot 插槽(父子组件通信)

全局模态框组件实现:

在modal.vue 中添加槽

<slot name="tip"></slot>

父传子

在父组件中直接添加组件

 <modal v-bind:class="{'md-show':isShow}" v-on:close="closeModal">
          <p slot="tips">请先登录!</p>
          <div slot="closeDialog" @click="isShow=false">关闭</div>
 </modal>

在子组件中引入isShow 变量  props["isShow"]

子传父

通过触发在父组件定义的事件   如close =====> 执行父组件中定义的closeModal 方法。

methods:{

           closeModal(){
              this.isShow=false;
            },

在子组件中,通过this.$emit('close') 触发

 

 

emit/on  事件发布 和订阅 

 

2.测试技巧

 模拟mock 数据,json 文件验证,不用后台数据。

3. 主页面开发

头部:mint-ui headbar

底部:m-ui tarbar  www.dcloud.io/hellomui   (可更换ICON 图标)

移动端 双飞翼  搜索框布局

input 搜索框 display:inline-block  转换为行内块才能一行显示。

3.1 商品列表开发

加载商品图片时, <a href="#"><img    v-bind:src=" ‘/static/1.img‘’"  alt=""></a>  图片需要动态赋值,否则渲染DOM树是,图像来不及加载。动态加载时,一开始无SRC属性,就不会造成图像路径找不到的错误,使用命令后,需要用单引号将其引入,因为使用命令后,就要使用js 语句,不再是字符串,要使用字符串时必须用单引号引入。

客户端发出请求,可用axios插件。

axios

基于promise用于浏览器和node.js的http客户端

特点

  • 支持浏览器和node.js
  • 支持promise
  • 能拦截请求和响应
  • 能转换请求和响应数据
  • 能取消请求
  • 自动转换JSON数据
  • 浏览器端支持防止CSRF(跨站请求伪造)

axois npm 安装后,导入模块以后,需要将其挂载为vue 的原型对象才能使用

Vue.prototype.$axios=axios

若为跨域请求,需要在服务器端的入口函数中  设置服务器的response Header 

//设置响应头response header

var app=express()
app.all('*', function(req, res, next) {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Headers", "X-Requested-With,Content-Type");
    res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
    next();
});

在 利用axios跨域接收 服务器响应的cookie 时,这是不能设置响应头   res.header("Access-Control-Allow-Origin", "*");必须指定具体前端页面的域名和端口。在响应头里面加上MIME TYPE 的类型。

eg:  res.header("Access-Control-Allow-Origin", "http://127.0.0.1:8080");

       res.header("Access-Control-Allow-Headers", "X-Requested-With,Content-Type");
       res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");

        res.header("Content-Type", "application/json;charset=utf-8");

 

开发时,webpack -dev -server 默认是以本地地址 http://localhost/8080 打开前端页面,要指定具体的域来打开,不要用localhost,否则 服务器响应的cookie 不会由axios跨域访问写到客户端 

 

设置cookie 利用express封装的方法 response.cookie("userId",value);   获取cookie       request.headers.cookie。

在使用axio 请求时,在then 的内部不能使用vue 的实例化this ,因为在内部没有被绑定。

解决方案:用es6 箭头函数,箭头函数可以和父方法共享变量。

export default {
    data(){
        return {
            newsList:[]
        }
    },
    created(){
      this.$ajax.get('/news/').then(function(response){
        
            this.newsList=response.data.list;=======>//  注意response.data 才可以接收到响应数据。
            console.log(this.newsList);
        }).catch(function(error){
            console.log(error)
            })
    }
}

axios get传参请求,添加params 字段,传递参数;

 var param={pageSize:this.pageSize,pageIndex:this.pageIndex,sort:this.sortFlag?1:-1};
 this.$ajax.get('/goods',{'params':param}).then((response)=>{...

Vue实例中有$route 和$router 属性,可通过控制台输出this 查看。

在Vue2.0 this.$route.query ,this.$route.params 接收router-link 传的参数。

$route 为当前router 跳转对象里面可以获取得name,path,query,params 等。

$router 为 VueRouter 实例,想要导航到不同url,可使用$router.push(‘要跳转的路径名’) 的方法。

服务端问题:

服务端express 路由get 方法传参,url中 需用占位符

var router=express.Router();

router.get('/newsDetails/:id',function(req,resp,next){
    // resp.send('获取服务端信息  goods');
    // 接收到请求后,利用模型取数据库里面查找  有findAPI  
    var id=req.params.id;
    console.log("id:"+id);....

}

vue-inifinite-scroll   分页插件  鼠标滚动之后调用的方法  鼠标滚轮距离底部多远时开始加载滚动接口

<div v-infinite-scroll="loadMore" infinite-scroll-disabled="busy" infinite-scroll-distance="10"></div>

注意:初始值  应设置busy 为 false。busy=true 表示禁止加载显示。

 

4.数据格式问题

get 请求

url 传值 通过request 获取

http://localhost:4000/goods?pageSize=3&pageIndex=2&sort=1    ==》express 框架取参数: request.query("pageSize");

http://localhost:4000/goods/3   ==》express 框架取参数: request.params("pageSize");

原生nodejs 是通过     request.url

router.get('/',function(req,resp,next){
    // resp.send('获取服务端信息  goods');
    // 接收到请求后,利用模型取数据库里面查找  有findAPI  


    var pageIndex=req.query('pageIndex');
    console.log(pageIndex);
    var  pageSize=parseInt(req.param('pageSize'));
    var param={};

    var nums=pageSize*(pageIndex-1);

    var sort=req.param('sort');

    var goodsModel=Goods.find(param).skip(nums).limit(pageSize);
    goodsModel.sort({'salePrice':sort});
    goodsModel.exec(function(err,doc){
            if(err){
                resp.json({
                    status: '0',
                    msg: err
                })
            }
            else{
                resp.json({
                    status:'1',
                    msg: '',
                    count: doc.length,
                    list:doc
                })
            }
    })
})

post 请求

通过request.body.属性  来获取参数。

注意:在浏览器端定义完数据模型model 后记得导出 module.exports=mongoose.model('Users',userSchema);

日期时间转换  : 定义为Date 日期型格式数据从数据库取出数据时格式为 2015-04-23T00:00:00.000Z,可用moment 日期转换时间插件来转化。在main.js 中定义一个过滤器即可。

response.json({})  // 输出json 格式数据

   response.end()     // 输出字符串格式数据

    router.get('/',function(req,res,next){

})

var app=express()

app.use(router) =========> 设置完路由之后 ,记得挂载路由。

5.img 动态加载路径问题

跑起来发现图片不显示,错误码为404, 
 原因:在webpack中会将图片图片来当做模块来用,因为是动态加载的,所以url-loader将无法解析图片地址,然后npm run dev 或者npm run build之后导致路径没有被加工【被webpack解析到的路径都会被解析为/static/img/[filename].png,完整地址为localhost:8080/static/img/[filename].png】 

利用static目录 ,而不是把图片放到别的地方,比如你自己新建一个img文件夹,在动态引用是无效的

js动态生成的路径无法被url-loader解析到,如果你去build,会发现图片甚至不会打包输出到dist目录(webpack是按需打包的)。

 解决办法:①将图片作为模块加载进去,比如images:[{src:require(‘./1.png’)},{src:require(‘./2.png’)}]这样webpack就能将其解析。②将图片放到static目录下,但必须写成绝对路径如images:[{src:”/static/1.png”},{src:”/static/2.png”}]这样图片也会显示出来,任何放在static/中文件需要以绝对路径的形式引用:/static[filename]

   <a href="#"><img :src=" '/static/'+item.productImage" alt=""></a>

 

5.router-link 点击设置

设置 链接激活时使用的 CSS 类名。默认值可以通过路由的构造选项 linkActiveClass 来全局配置。

6.引入自己的样式路径问题

./: 当前目录下;         ./component  表示当前目录同级component文件目录      
../: 父级目录;

定义一个css 文件,在main.js 中引入(import 即可)

7.登录拦截

采用express 中间件

app.use(function(req,resp,next){
    console.log("req.originalUrl: "+req.originalUrl);
    console.log("req.path: "+req.path);
    console.log("cookie:"+req.headers.cookie);

    if(req.headers.cookie){
        console.log('已登录');
        next();
    }
    else{
        //白名单
        if(req.originalUrl=='/users/logout' || req.originalUrl=='/users/login' || req.originalUrl=='/news/newsList'  || (req.originalUrl.indexOf('/goods/list')>-1)){
            console.log('白名单  通过!');
            next();
        }
        else{
            console.log('请先登录!');
            resp.json({                     ====> 传数据到前端===>  前端根据状态码 弹框提示
                status:'0',
                msg:'请登录',
                result:''
            });

            
        }
    }
})

enter 登录 

在密码输入框 中注册 回车键的keyup 时间 @keyup.enter

 

update 更新mongoose

Users.update({'userId':userId,'cartList.productId':productId},{'cartList.$.productNum':productNum,'cartList.$.checked':checked},function(err,doc){
             if(err){
                 resp.json({
                    status: '0',
                    msg: err.message
                })
             }
             else{
                 

                resp.json({
                    status:'1',
                    msg:'',
                    result:'success'
                    });
                     
                 
             }
             
         })

8 利用computed 属性实时监控变量,得到另一变量的值

  1.监控商品选中框状态,是否选中全选按钮。

  2. 监控商品数量按钮,实时改变价格。

在computed 属性中定义变量: 例子:

 

<div id="example">
  <p>Original message: "{{ message }}"</p>
  <p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: 'Hello'
  },
  computed: {
    // 计算属性的 getter
    reversedMessage: function () {
      // `this` 指向 vm 实例
      return this.message.split('').reverse().join('')
    }
  }
})

  computed:{

          allSelectedFlag(){
           return this.countSelect==this.cartList.length;
        },
        countSelect(){
           var count=0;

            for(var i=0;i<this.cartList.length;i++){
              if(this.cartList[i].check=='1'){
                ++count;
              }
            }
          return count;
        },

        countSum(){
            var sum=0;
            this.cartList.forEach(function(item){
                sum+=item.productNum*item.salePrice;
            })
            return sum;
          }
      },

8. 定义过滤器   对时间、数字等进行过滤处理,保留两位小数等。

 8.1 局部过滤器。

import 导入

  在 export default{} 中 定义过滤器 filters: {

       过滤器名称:导入模块名

}

import {currency} from '.././util/currency.js'
    export default{
        data(){
            return{
                cartList:[],
                isShow:false,
                productId:'',
               
            }
        },
        mounted(){
          this.getCart();
        },

        filters:{
            currency: currency
        },

 8.2  全局过滤器

import 导入   Vue.filter(过滤器名称,导入模块名);

|  过滤器名称  使用

 <div class="item-total">
                Item total: <span class="total-price">{{countSum | currency('$')}}</span>
 </div>
 


import './util/currency.js'
 //挂载
 Vue.prototype.$ajax=Axios;

 Axios.defaults.baseURL='http://127.0.0.1:4000';
 Axios.defaults.withCredentials=true;

 Vue.filter('converDate',function(value) {
     return Moment(value).format('YYYY-MM-DD');
 });

 Vue.filter('currency',currency);

9.mongoose update   $pull删除字段对应数组中的对象

10.跳转页面

this.$router.push({path: '/user/item?addressId='+ temp})

11. vuex 统一状态管理

在项目中如购物车的数量等需要实时的在各个单页面统一更新,若利用父子组件通信传值,则较不方便,若有一个全局的变量来进行更新,则方便许多,在mutations 中通过commit 方法修改变量值,需要用到的数据封装成一个对象payload,一起传过去。

npm install vuex --save
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);

 在vue 中注册。

var store=new Vuex.Store({
    state:{
        count:0
    },
    mutations:{
        getCount(state,payload){
            if(payload.flag=='add'){
                console.log('数量count:'+store.state.count);
                console.log('数量传参:'+payload.count);
                store.state.count+=payload.count;
                console.log('数量结果:'+store.state.count);
            }
            else if(payload.flag=='minus'){
                 store.state.count-=payload.count;
            }
        }
    }
})

new Vue({
    el:'#myapp',
    store,
    router: router,
    render: function(create){
        return create(App);
    }
})

在组件footer.vue 中

methods:{
                getCart(){
                     this.$ajax.get('/users/cart').then((response)=>{
                        if(response){
                          var re=response.data;
                          if(re.status=='1'){
                              this.cartList=re.result.cartList;
                              console.log('购物车数量:'+re.cartCount);
                              var payload={
                                  flag:'add',
                                  count: re.cartCount
                              }
                              this.$store.commit("getCount",payload);

                          }
                        }
                        else{
                          console.log('获取购物车失败!');
                        }
                     })
                  }

12. 样式上需要注意的点:

在 style 中引入css ,scss 文件 @import  '../../style/mixin' ; 分号分割;

定义 animation动画  @keyframes tipMove{

0%{ transform: scale(1)}

}

 

 

 

展开阅读全文