爱家——装修平台练习笔记

下面回顾一下练习项目——爱家——过程中碰到的问题。因为是回顾,所以可能不尽全面。不过,尽力了,能写多少是多少。

一、轮播图兼容问题

因为 ie9 不支持transform,所以引入了jQuery,使用jQueryanimate实现无限轮播。

<template>
  <div id="banner"
    @mouseenter = "stop"
    @mouseleave = "start"
  >
    <div class="pic">
      <ul>
        <li 
          v-for="(img,index) in imgArray"
          :key = "index"
          class = "fl"
        >
          <a>
            <img :src="img" alt="" class="bgc">
          </a>
        </li>
      </ul>
    </div>
    <div class="dot">
      <span
        v-for="index in dotArray"
        :key = "index"
        @click = "slide(index)"
        :class="{active:index === curIndex}"
      ></span>
    </div>
    <div class="left" @click="slide('l')"><</div>
    <div class="right" @click="slide('r')">></div>
  </div>
</template>

<script>
import $ from "jquery";
export default {
  props: {
    banner: {
      type: Object,
      required: true
    }
  },
  data(){
    return {
      imgArray: [
        this.banner['2'].src,
        this.banner['0'].src,
        this.banner['1'].src,
        this.banner['2'].src,
        this.banner['0'].src
      ],
      dotArray: [
        1,
        2,
        3
      ],
      curIndex: 1
    }
  },
  methods: {
    slide:function(index){
      var $pic = $('.pic').first();
      var self = this;
      
      if($pic.is(":animated")){
        return
      }
      index = 'l' === index ? this.curIndex - 1 : index;
      index = 'r' === index ? this.curIndex + 1 : index;

      this.curIndex = index;

      var left = this.curIndex;
      if(this.curIndex >= this.imgArray.length - 1){
        this.curIndex = 1;
      }
      if(this.curIndex <= 0){
        this.curIndex = this.imgArray.length - 2;
      }

      $pic.animate({
        'margin-left': 0 - 1200 * left + 'px'
      },1000,function(){
        if(index >= self.imgArray.length - 1){
          $pic.css({
            'margin-left': '-1200px'
          })
        };
        if(index <= 0){
          $pic.css({
            'margin-left': '-3600px'
          })
        }
      });
    },
    start: function(){
      var self = this;
      // console.log(self);
      self.interval = setInterval(function(){
        // console.log(self);
        self.curIndex++;
        // if(self.curIndex === self.imgArray.length){
        //   self.curIndex = 0;
        // }
        self.slide(self.curIndex);
      },2000)
    },
    stop: function(){
      clearInterval(this.interval);
    }
  },
  mounted: function(){
    this.start();
  }
}
</script>

<style scoped>
#banner {
  position: relative;
  width: 1200px;
  height: 430px;
  overflow: hidden;
  margin: auto;
}

.pic {
  overflow: hidden;
  width: 6000px;
  margin-left: -1200px;
}

.pic img {
  width: 1200px;
  height: 430px;
}

.dot {
  text-align: center;
  margin-top: -50px;
}

.dot span {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  margin: 0 10px;
  display: inline-block;
  background-color: #fff;
  border: 2px solid #fff;
  cursor: pointer;
}

.dot span.active {
  background-color: #333;
  border: 2px solid linen;
}

.left, .right {
  position: absolute;
  top:50%;
  margin-top: -50px;
  height: 100px;
  width: 30px;
  line-height: 100px;
  text-align: center;
  background-color: #333;
  color: #fff;
  cursor: pointer;
}

.right {
  right: 0px;
}
</style>

二、依据条件渲染链接

图片等都是使用vuefor循环渲染的。在实际展示的时候有一个需求:如果内容存在,则链接有效。否则使用默认内容,链接无效。

<a 
  :href="'#' === a_case.id ? 'javascript:;' : './?c_id=' + a_case.id" 
  :target="'#' !== a_case.id ? '_blank' : ''"
><img 
    :src="a_case.cover" 
    :alt="a_case.title" class="cover"
  ></a><br />

<a 
  :href="'#' === a_case.id ? 'javascript:;' : './?c_id=' + a_case.id" 
  :target="'#' !== a_case.id ? '_blank' : ''"
><span 
    v-html="a_case.title" 
    class="ellipse ilb"
  ></span></a>

#是在id不存在的时候约定的默认符号。如果id不存在,渲染时候的默认链接就是javasript:;

默认链接不能是#。如果是锚点,页面会跳到头部,在target_blank的时候还会在新页面打开。

默认链接不能是javascript:void(0);。主要是在 IE 下面会出现问题。(好像是打开新标签。)

三、设计师照片条件渲染

这个问题是由前期设计不足引起的。在之前设计的时候并没有考虑到缩略图的问题。比如设计师,上传的时候是大图片。但一般页面需要的是缩略图。大图太耗带宽了。所以,只能在后台增加了裁切的代码。但是已经注册并上传图片的设计师并没有对应的缩略图——除非再一次上传图片。为了在前端兼容这种情况,需要条件渲染。

<img 
  :src="designer.thumb ? designer.thumb : designer.photo" 
  :alt="designer.name" class="photo"
/>

如果缩略图存在,就使用缩略图,否则用原图。

四、年龄计算

出生年月和工作年月在数据库中是这样存储的:XXXX_XX。比如1980_6。但在展示的时候需要以这种格式展示:XX岁XX年XX月。所以,就需要对原数据进行转换。

computed: {
  age(){
    return Math.floor(new Date().getFullYear() - this.user.birth.split('_')[0] + (new Date().getMonth() - this.user.birth.split('_')[1])/12) + '岁';
  },
  working(){
    return Math.floor(new Date().getFullYear() - this.designer.working_years.split('_')[0] + ( new Date().getMonth() - this.designer.working_years.split('_')[1] ) /12 ) + '年' + (new Date().getMonth() - this.designer.working_years.split('_')[1] + 12)%12 + '月';
  }
}

this.user.birth是出生年月, this.designer.working_years 是参加工作年月。

五、div 垂直居中问题

有一个需求,在一个盒子中显示联系方式。盒子宽高确定,联系方式条数不确定。

@white: white;
@pink: #edd9d9;
.contact-board {
          width: 240px;
          height: 200px;
          line-height: 200px;
          background: @white;
          border: 5px solid @pink;
          left: 50%;
          top: 50%;
          margin-left: -120px;
          margin-top: -100px;
          display: table;
          > div:first-child {
            display: table-cell;
            vertical-align: middle;
          }
          p {
            line-height: 35px;
          }

这里用的less。外层盒子.contact-boarddisplay属性为table。内层盒子> div:first-childdisplay属性为table-cellvertical-align属性为middle

六、scoped 穿透

通用组件确实很方便,但不同的页面布局有所不同,有必要对通用组件进行改造。

还是放参考链接吧。# Vue中的scoped及穿透方法

在练习中我采用了第二种方法:曲线救国。

七、vue-router 默认路由

const routes = [
  {
    path: '/',
    redirect: '/login'
  },
  {
    path: '/login',
    component: login
  },
  {
    path: '/register',
    component: register
  }
];

redirect后面可以是一个函数,根据需要返回不同的路由。

八、通过实例方法改变当前路由

还是先放链接:## vue router.push(),router.replace(),router.go()

this.$router.replace({path:'/setting'});

九、vue-router 传值方法

路由传值有paramsqueryprops

先放链接:Vue-router props 如何传递参数 ,传参请看这里

路由里面props的值确实可以传给子组件的props

我的需求是,父组件向后台发出ajax请求,返回的一堆数据在分解后需要传给子组件。路由的props属性貌似无法取得父组件的数据。虽然可以在父组件中通过this.$route……这样的方式一层一层地找到需要数据的子组件,然后赋值,但这样并不是响应式的。后来,只能通过vuex解决问题。父组件将数据存入vuex

this.$store.commit('store_origin_info',this.info);

子组件监听变化。

created(){
    this.$store.watch(state => {
      if(!state.origin_info){
        return;
      }
      this.origin_info = this.$store.state.origin_info;
      this.init_info();
    });
  }

一开始并不是用的监听的方式,而是在computed中返回。

computed: {
    origin_info: function(){
      return this.$store.state.origin_info;
    }
  },

但不知为什么,这种无法响应store中数据的变化。

十、添加图片并预览的一系列问题

这里的问题比较多,主要是 ie9 的兼容。

  • input不能连续选择同一张图片。
  • ie9 不支持type=file,无法通过e.target.files获取文件信息。
  • ie9 不支持FileReader,无法通过此方法将图片读取为base64用于预览。

先放代码。

add(e,item,index){
      e = e || event;

      //如果值为空,返回。主要针对 createTextRange 触发的onchange
      if(!e.target.value){
        return;
      }

      let pics = [];
      if(window.FileReader){
        pics = e.target.files;
      }else{  //低版本ie兼容
        try{
          e.target.select();
          e.target.blur();
          let path = document.selection.createRange().text;
          let fso = new ActiveXObject("Scripting.FileSystemObject");
          pics[0] = {};
          pics[0].path = path;
          pics[0].size = fso.GetFile(path).size;
          pics[0].type = fso.GetFile(path).type;
        }catch(e){
          alert(e+"\n"+"如果错误为:Error:Automation 服务器不能创建对象;"+"\n"+"请按以下方法配置浏览器:"+"\n"+"请打开【Internet选项-安全-Internet-自定义级别-ActiveX控件和插件-对未标记为可安全执行脚本的ActiveX控件初始化并执行脚本(不安全)-点击启用-确定】");
        }
      }



      let len = pics.length;
      //这里的mark 为什么设置为 -2,而不是0.原因未知,总之下面的 mark++ 执行次数会比循环次数多 2。所以设置为 -2 .
      let mark = -2;
      if(!window.FileReader){ //ie9 不存在多执行两次的情况
        mark = 0;
      }
      // console.log(e.target);
      // console.log(pics);
      let rule = {
        size: 300 * 1024,
        type: [
          'jpeg',
          'png',
          'gif'
        ]
      };
      for(let key in pics){
        this._CHECK_PIC(rule,pics[key])
          .then( result => {
            if(result.right){
              console.log("359" + pics);
              item.detail.push({
                path: result.data,
                description: ''
              });
            }
            mark++;
            console.log('len:'+len+' mark:'+mark);
            if(len === mark){
              e.target.value = '';
              if(window.ActiveXObject){
                e.target.createTextRange().execCommand('delete');
                e.target.blur();
              }
              
              console.log(e.target.value);
              this.check_content(index);
            }
          });
      }
    }

add()input onchange事件的处理函数(方法)。参数分别是事件对象、要添加图片的数组和索引。_CHECK_PIC()是封装的图片检查方法。check_content()是整个内容区域的检查方法。

下面是封装的_CHECK_PIC()

Vue.prototype._CHECK_PIC = function(rules,pic){
    //预设变量
    let variable = rules;
    let result = {
      right: true,
      data: null
    };
    return new Promise(function(resolve,reject){
      //检查图片格式
      if(rules.type){
        let r = rules.type.some(val => {
          if(window.FileReader){
            return pic.type === 'image/' + val;
          }else{  //低版本ie兼容
            console.log(pic);
            let type = pic.type.split(" ")[0].toLowerCase();
            if('jpg' === type){
              type = 'jpeg';
            }
            return val === type;
          }
          
        });
        if(!r){
          result.right = false;
          resolve(result);
          return;
        }
      }
      
      //检查图片大小
      if(rules.size){
        if(pic.size > rules.size){
          result.right = false;
          resolve(result);
          return;
        }
      }
      
      //检查图片宽高
      if(window.FileReader){
        let reader = new FileReader();
        //读取图片
        reader.readAsDataURL(pic);
        reader.onload = function(e){
          let temp_pic = new Image();
          temp_pic.src = e.target.result;
          temp_pic.onload = function(){ 

            // console.log(temp_pic.width);
            // console.log(temp_pic.height);
            // console.log(variable);
            check_w_h(temp_pic,rules,result);
          }
        }
      }else{  //低版本ie兼容
        let temp_pic = new Image();
        temp_pic.src = pic.path;
        temp_pic.onload = function(){
          // console.log("89" + this);
          check_w_h(temp_pic,rules,result);
        }
      }
      


      //检查宽高
      function check_w_h(temp_pic,rules,result){
        // console.log("98" + this);
        //检查最小宽高
        if(rules.min_width && rules.min_height){
          if(temp_pic.width < rules.min_width || temp_pic.height < rules.min_height){
            result.right = false;
            resolve(result);
            return;
          }
        }
        //检查最大宽高
        if(rules.max_width && rules.max_height){
          if(temp_pic.width > rules.max_width || temp_pic.height > rules.max_height){
            result.right = false;
            resolve(result);
            return;
          }
        }
        //检查最小宽高比
        if(rules.min_ratio){
          if(temp_pic.width / temp_pic.height < rules.min_ratio * 0.9){
            result.right = false;
            resolve(result);
            return;
          }
        }
        //检查最大宽高比
        if(rules.max_ratio){
          if(temp_pic.width / temp_pic.height > rules.max_ratio * 1.1){
            result.right = false;
            resolve(result);
            return;
          }
        }

        //图片有效
        result.data = temp_pic.src;
        // console.log(result);
        resolve(result);
      }
    });
  }

input不能连续选择同一张图片可以通过清空input value值来解决。ie11 及非 ie 浏览器可以通过e.target.value=''实现。其余 ie 通过e.target.createTextRange().execCommand('delete'); e.target.blur();解决。但这样貌似会触发onchange事件,导致一次添加两张图的bug。所以,在前面要对input value进行判断。if(!e.target.value){return;}

ie9 不支持type=fileFileReader可以通过ActiveXObject("Scripting.FileSyetemObject")和滤镜解决。下面放链接:

上传图片快速预览HTML5 FileReader + Window.URL+滤镜(兼容低版本IE)

不知为什么,好像不使用滤镜,直接将路径给图片的src属性也可以。

ie9 下将图片转为 base64 的问题尚未解决。

十一、axios post 请求后台接收不到数据

先放链接:

axios 发 post 请求,后端接收不到参数的解决方案

axios用post提交的数据格式

解决方法比较多,我这里是通过transformRequest解决的。

Vue.prototype._POST = function(post_data,func){
    axios({
      method: 'post',
      url: './',
      headers: {
        'Content-type': 'application/x-www-form-urlencoded'
      },
      data: post_data,
      transformRequest: [
        function(data){
          let newData = '';
          for ( let i in data){
            newData += encodeURIComponent(i) + '=' + encodeURIComponent(data[i]) + '&';
          }
          // console.log(newData);
          return newData;
        }
      ]
    }).then(response => {
      func(response);
    });
  }

十二、数据挂载

目前是将数据挂载在vuebody下面。或许可以考虑将数据挂载到meta标签下面,或者弄一个其他不需要的标签,省去读取数据后在将vuebody设为显示的步骤。

十三、webpack css 分离问题

之前写诗词网站demo的时候就尝试过将css分离出来,但失败了。这一次倒是成功了。原因嘛——之前配置写错了。

const webpack = require('webpack');
const VueLoadPlugin = require('vue-loader/lib/plugin');
const path = require('path');
const HTMLPlugin = require('html-webpack-plugin');
const miniCSS = require('mini-css-extract-plugin');
const isDev = process.env.NODE_ENV === 'development';

const config = {
  mode: isDev ? 'development' : 'production',
  target: 'web',
  entry: {
    home: './src/pages/home/home.js',
    designer: './src/pages/designer/designer.js',
    about: './src/pages/about/about.js',
    detail: './src/pages/detail/detail.js',
    login: './src/pages/login/login.js',
    panel: './src/pages/panel/panel.js'
  },
  output: {
    path: path.resolve(__dirname,'dist'),
    filename: 'js/[name].js'
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.css$/,
        use: [
          miniCSS.loader,
          'css-loader'
        ]
      },
      {
        test: /\.less$/,
        use: [
          miniCSS.loader,
          'css-loader',
          'less-loader'
        ]
      },
      {
        test: /iconfont\.(ttf|svg|eot|woff)$/,
        use: [{
          loader: 'file-loader',
          options: {
            name: 'font/[name].[ext]'
          }
        }]
      },
      {
        test: /\.js$/,
        use: {
          loader: 'babel-loader'
        },
        exclude: path.resolve(__dirname,'node_modules/')
      }
    ]
  },
  plugins: [
    new VueLoadPlugin(),
    new miniCSS({
      filename: "css/[name].css",
      allChunks: true
    }),
    new HTMLPlugin({
      template: './src/pages/home/home.html',
      filename: './home.html',
      chunks: ['lib','home']
    }),
    new HTMLPlugin({
      template: './src/pages/about/about.html',
      filename: './about.html',
      chunks: ['lib','about']
    }),
    new HTMLPlugin({
      template: './src/pages/login/login.html',
      filename: './login.html',
      chunks: ['lib','login']
    }),
    new HTMLPlugin({
      template: './src/pages/detail/detail.html',
      filename: './detail.html',
      chunks: ['lib','detail']
    }),
    new HTMLPlugin({
      template: './src/pages/designer/designer.html',
      filename: './designer.html',
      chunks: ['lib','designer']
    }),
    new HTMLPlugin({
      template: './src/pages/panel/panel.html',
      filename: './panel.html',
      chunks: ['lib','panel']
    })
  ],
  optimization: {
    splitChunks: {
      cacheGroups: {
        lib: {
          name: 'lib',
          filename: 'js/[name].js',
          minChunks: 2,
          chunks: 'initial'
        }
      }
    }
  }
};

module.exports = config;

只要将module里面cssless部分的style-loader换成mini-css-extract-pluginloader就可以了。当然,plugins里面也要配置一下就是了。(之前optimization里面也写了,并且style-loader没删掉。)

十四、php 部分

实际上这里也有很多东西可以写。但是,还是直接放源码链接好了。

https://github.com/tonyzhou1890/love-home/blob/master/src/index.php

总结

数据库部分也就不记录了。

1、总体来说,还很弱。

2、总体来说,还有很大进步空间。

文章最初发布在简书,时间为 2018.07.14 18:08。

链接:https://www.jianshu.com/p/268ab74d4d95