屏幕适配、剖析跨域、Echarts万能封装、Axios二次封装、常见GIT...

1、屏幕适配

1.1 em/rem布局

  • em 作为 font-size 的单位时,其代表父元素的字体大小,em 作为其他属性单位时,代表自身字体大小—— MDN
  • rem 作用于非根元素时,相对于根元素字体大小;rem 作用于根元素字体大小时,相对于其出初始字体大小—— MDN

这里就 rem 实践, 前面我们介绍 rem 是根据根节点字体大小来决定的,所以我们先找到我们的根节点在哪,实现效果大概如下

在这里插入图片描述

实习的第一个项目我是用的 Vue3 ,用的Vue-Cli 脚手架工具, SPA 单页面开发。它的根节点就是Pulic 文件夹下的index.html 事实上所有组件就是在这个页面显示的。整个项目本质上就这一个页面,只是不同组件的更替在用户眼里有许多页面。

我们先来看看 rem 的计算公式,事实上我们就是要在根节点上设置fontSize 的大小,所以假设我们设置为 16px ,但是不同屏幕宽度我们就要设置不同的字体大小,所以我们只需要获取当前屏幕的宽度,然后除以我们的设计稿 1920 的宽度,再乘以16,就可以拿到对应屏宽的根节点字体大小了,事实上我们维持的是比例,而不是大小。

document.documentElement.style.fontSize = window.innerWidth / 1920 * 16 + 'px';

根节点字体大小解决后,我们就只需要监听窗口大小了,当窗口大小改变的时候,我们就调用我们的函数,跟随改变。除此之外,我们还需要在页面加载完成后初始化根节点的字体大小

 // 在窗口大小改变时调用设置根元素字体大小的函数
window.addEventListener('resize', setRootFontSize, false);

    // 页面加载完成后初始化设置根元素字体大小
window.addEventListener('DOMContentLoaded', setRootFontSize, false);

但是计算 rem 是个问题,我们可以用一个插件,px to rem , 它可以在我们输入 px 的时候,提示当前转化为 rem 是多少; 如果使用的 VsCode 开发的,可以看下面的截图

在这里插入图片描述

不过使用这个工具我们也要进行设置,因为插件默认是 16px ,所以如果我们如果设置根节点是以16之外的话就要进行额外的设置, 也就是设置上图的cssrem.rootFontSize

点击文件 --> 首选项 --> 设置 (快捷键方式打开 Ctrl + ,)然后找到 settings.json ,打开后再最后一行加入 cssrem.rootFontSize = 你设置的值


1.2 scale 适配

这里是用了 CSS3transform 属性来设置 scale 缩放,我们只需要给每个页面组件绑定一个 ref , 而后获取其style 属性对其进行缩放。由于我们每个页面组件都要使用,所以我们不妨把它提取出来作为一个单独的文件,而后再mixins 混入就好了。

实现效果大概这样的

在这里插入图片描述

它的源码如下

// 屏幕适配 mixin 函数

// * 默认缩放值
const scale = {
  width: '1',
  height: '1',
}

// * 设计稿尺寸(px)
const baseWidth = 1920
const baseHeight = 1080

// * 需保持的比例(默认1.77778)
const baseProportion = parseFloat((baseWidth / baseHeight).toFixed(5))

export default {
  data() {
    return {
      // * 定时函数
      drawTiming: null
    }
  },
  mounted () {
    this.calcRate()
    window.addEventListener('resize', this.resize)
  },
  beforeDestroy () {
    window.removeEventListener('resize', this.resize)
  },
  methods: {
    calcRate () {
      const appRef = this.$refs["appRef"]
      if (!appRef) return 
      // 当前宽高比
      const currentRate = parseFloat((window.innerWidth / window.innerHeight).toFixed(5))
      if (appRef) {
        if (currentRate > baseProportion) {
          // 表示更宽
          scale.width = ((window.innerHeight * baseProportion) / baseWidth).toFixed(5)
          scale.height = (window.innerHeight / baseHeight).toFixed(5)
          appRef.style.transform = `scale(${scale.width}, ${scale.height}) translate(-50%, -50%)`
        } else {
          // 表示更高
          scale.height = ((window.innerWidth / baseProportion) / baseHeight).toFixed(5)
          scale.width = (window.innerWidth / baseWidth).toFixed(5)
          appRef.style.transform = `scale(${scale.width}, ${scale.height}) translate(-50%, -50%)`
        }
      }
    },
    resize () {
      clearTimeout(this.drawTiming)
      this.drawTiming = setTimeout(() => {
        this.calcRate()
      }, 200)
    }
  },
}

源码解释:

  1. 首先根据设计稿的宽高比设置我们基本宽高比,例如这里是 1920*1080 , 默认需要保持的比例即是 1920 / 1080 ,同时默认当前宽高缩放是1,即不进行缩放。
  2. 然后看看 calcRate 函数,我们只需要对每个页面组件进行缩放,即对整体进行了缩放,所以我们先拿到 dom 节点,如果没拿到就直接返回,拿到了进行下一步
  3. 获取当前屏幕的宽高比,如果当前的宽高比大于我们设计稿的宽高比则代表当前屏幕更宽,否则代表更高;对不同情况设置不同的缩放比例
  4. 事实上缩放比例看代码也很容易发现它的公式的,当更宽的时候高度不需要变,只需要跟原来的基本高度进行比较缩放,更高的时候宽度也只需要跟原来的基本宽度进行比较缩放,这个好理解。但是更宽的时候怎么设置宽度的缩放比例呢?我们之前基础的宽高比是 baseProportion = 1920 / 1080 ; 那么我们只需要拿当前的高度乘以这个比例,就可以拿到当前的宽度了,再去除以基础的宽度,就可以获得它的缩放比例了,更高情况同样如此。
  5. 最后我们在监听窗口大小,当发生变化时,就再次调用calcRate 函数。



2、 Echarts封装

在项目中 Echarts 用到了柱状图、饼状图、雷达图,不过我觉得这些都不重要,怎么用我们只需要参考官网文档 的属性即可,除此之外网上也有许多案例分享。

我觉得重要的是,这次看到一个前辈的代码,是对 Echarts 进行了封装,而我是每使用一个图,就把它封装成组件,虽然也可以重复使用那一个图表,但是显然前者更便捷, 这样我们只需要一个组件就能使用所有的图表

Echarts 封装源码

<template>
  <div ref="chart" :style="styleCfg"></div>
</template>

<script>
export default {
  name: "EchartIndex",
  props: {
    option: {
      default: () => {},
      type: Object,
    },
    width: {
      default: "auto",
      type: [String, Number],
    },
    height: {
      default: "300",
      type: String,
    },
    autoHeight: {
      default: false,
      type: Boolean,
    },
    minWidth: {
      default: "",
      type: String,
    },
  },
  data() {
    return {
      myChart: "",
    };
  },
  computed: {
    styleCfg() {
      let width = isNaN(Number(this.width)) ? this.width : `${this.width}px`;
      let height = isNaN(Number(this.height))
        ? this.height
        : `${this.height}px`;
      let returnStyle = `width: ${width}; height: ${height}; `;
      if (this.minWidth) {
        returnStyle += "min-width:" + this.minWidth;
      }
      return returnStyle;
    },
  },
  watch: {
    // option: {
    //   handler(a, b) {
    //     console.log(b);
    //     this.$forceUpdate();
    //     this.myChart && this.myChart.setOption(a);
    //   },
    //   immediate: true,
    //   deep: true,
    // },
    option() {
      if (this.myChart) {
        this.myChart.clear();
      }
      this.draw();
      window.addEventListener(
        "resize",
        () => {
          this.myChart.resize();
        },
        false
      );
    },
  },
  mounted() {
    this.draw();
    window.addEventListener(
      "resize",
      () => {
        this.myChart.resize();
      },
      false
    );
  },
  beforeDestroy() {
    window.removeEventListener("resize", this.myChart.resize());
    if (this.myChart) {
      this.myChart.dispose();
    }
  },
  methods: {
    draw() {
      this.myChart = this.$echarts.init(this.$refs.chart);
      this.myChart.on("click", (param) => {
        this.echartOnClick(param);
      });
      this.myChart.on("mouseover", (param) => {
        this.echartOnMouseover(param);
      });
      this.myChart.on("mouseout", (param) => {
        this.echartOnMouseout(param);
      });
      this.myChart.on("legendselectchanged", (param) => {
        this.echartOnLegendselectchanged(param);
      });

      this.myChart.setOption(this.option);
    },
    // 点击事件
    echartOnClick(param) {
      this.$emit("echartOnClick", param);
    },
    // 点击legend事件
    echartOnLegendselectchanged(param) {
      this.$emit("echartOnLegendse", param);
    },
    // 鼠标悬浮事件
    echartOnMouseover(param) {
      this.$emit("echartOnMouseover", param);
    },
    // 鼠标移除事件
    echartOnMouseout(param) {
      this.$emit("echartOnMouseout", param);
    },
    setOption(param) {
      this.myChart && this.myChart.setOption(param);
    },
    init() {
      this.myChart && this.myChart.setOption(this.option);
    },
  },
};
</script>

<style lang="scss" scoped>
.no-data {
  position: absolute;
  display: flex;
  width: 100%;
  height: 100%;
  align-items: center;
  text-align: center;
  p {
    flex: 1;
  }
}
</style>

我们有了这个封装好的组件,就只需要在需要用Echarts 的地方传入options 就好了,方便快捷,并且组件封装了一系列事件。再需要的时候也可以引入。

Echarts 组件源码解析

  1. 我们先看 styleCfg 函数,因为接收参数那一块咱定义的类型可以是String , 也可以是 Number ,我觉得如果是个人开发一个项目那没必要,如果多个人开发一个项目的前端还是有必要的,因为我们不知道他会传入什么,除非我们提前商量好。这个函数主要就是做了判断它传入的是 String 还是 Number, 譬如这句话 isNaN(Number(this.width)) 就是先把他转化为 Number , 再判断是不是 NaN, 如果他传入的字符串是 "20" ,那就是合法的,如果是 "20px" ,那就是不合法的,这时候我们就直接选择this.width ,否则就要加上 px
  2. 然后就是 watch监听,因为 Echarts 它的数据改变后,并不会重新渲染的,所以我们要手动给它更新数据,当option 改变后,我们销毁当前myChart ,然后再进行重绘;当然也可以使用this.forceUpdate() 进行强制刷新,然后再setOption
  3. 然后 mouted/onMountedbeforeDestroy/onUnmounted 监听窗口大小变化,让图表跟着更新大小,走时候移除监听,销毁myChart



3、跨域

这里参考的这篇博客

3.1 什么是跨域?

跨域指的是浏览器不能执行其它网站的脚本。它是浏览器的同源策略造成的,是浏览器对 javascript 施加的安全限制。

同源策略: 同源策略/SOPSame origin policy)是一种约定,由 Netscape 公司 1995 年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个 ip 地址,也非同源。

同源策略限制以下几种行为

  1. CookieLocalStorageIndexDB 无法获取
  2. DOMJs 对象无法获取
  3. Ajax 请求不能发送

3.2 为什么会出现跨域?

上面其实已经说了出现跨域的原因,就是因为同源策略,即协议、域名、或端口有一个不同就会出现跨域问题。

我们可以看看下面的例子

当前页面url被请求页面url是否跨域原因
http://www.test.com/http://www.test.com/index.html同源
http://www.test.com/https://www.test.com/index.html协议不同
http://www.test.com/http://www.baidu.com/二级域名不同
http://www.test.com/http://blog.test.com/主机域名不同
http://www.test.com:8080/http://www.test.com:8010/端口号不同

3.3 跨域的解决方案

3.3.1 CORS (跨域资源共享)

在之前帮忙写个 web 接口时候处理过,不过记忆中那时候就是下载了个 corsHeaders 的包,然后配置下允许的请求方法、请求头等就好了。python后端解决跨域

现在还是来搞清楚他的原理吧

CORS 定义了必须在访问跨域资源时,浏览器与服务器应该如何沟通,其背后的思路就是使用自定的 HTTP 头部 让浏览器与服务器进行沟通,从而决定请求或响应应该成功或者失败。

注意实现cors 需要后台配合

请求过程

  • 浏览器发出cors 请求,会在headers 中增加一个 origin 字段,服务器根据 Origin 的值决定是否同意这次请求。
  • 而服务端只需设置 Access-Control-Allow-Origin 即可,如果有cookie 请求,则还需要进行设置。

前端原生 ajax 设置

var xhr = new XMLHttpRequest(); // IE8/9 需要window.XDomainRequest兼容

xhr.withCredentials = true;// 前端设置是否带cookie
xhr.open('post', 'http://www.xx.com:8080/login', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('user=admin');

xhr.onreadystatechange = function() {
    if (xhr.readyState == 4 && xhr.status == 200) {
        alert(xhr.responseText);
    }
}

前端 jQuery 设置

$.ajax({
    ...
   xhrFields: {
       withCredentials: true    // 前端设置是否带cookie
   },
   crossDomain: true,   // 会让请求头中包含跨域的额外信息,但不会含cookie
    ...
});

Vue 设置

axios 设置:axios.defaults.withCredentials = true

vue-resource 设置:Vue.http.options.credentials = true


服务端设置

/*
 * 导入包:import javax.servlet.http.HttpServletResponse;
 * 接口参数中定义:HttpServletResponse response
 */

// 允许跨域访问的域名:若有端口需写全(协议+域名+端口),若没有端口末尾不用加'/'
response.setHeader("Access-Control-Allow-Origin", "http://www.domain1.com"); 

// 允许前端带认证cookie:启用此项后,上面的域名不能为'*',必须指定具体的域名,否则浏览器会提示
response.setHeader("Access-Control-Allow-Credentials", "true"); 

// 提示OPTIONS预检时,后端需要设置的两个常用自定义头
response.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With");

到这里其实就发现,其实之前用 Django 做的跨域,下载一个包,然后配置其请求方法、请求头、域名白名单等,好像也差不多,只是手写更详细。


3.3.2 nginx 反向代理

除了cors 之前我还尝试过nginx 的代理,不过是为了处理请求资源不存在时候重定向的,在之前的是Vue 里用devServer:{proxy:{target:"域名",changeOrigin: true, // 允许跨域}} 来代理,原理和使用nginx 代理服务器是一样的,绕过浏览器自然就没有同源策略,指定域名以及允许跨域

nginx 跨域原理: 同源策略是浏览器的安全策略,不是 HTTP 协议的一部分。服务器端调用 HTTP 接口只是使用 HTTP 协议,不会 执行JS 脚本,不需要同源策略,也就不存在跨越问题。

nginx 配置示例

#proxy服务器
server {
    listen       81; #监听端口
    server_name  www.domain1.com; #域名

    location / {
        proxy_pass   http://www.domain2.com:8080;  #反向代理
        proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
        index  index.html index.htm;

        # 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
        add_header Access-Control-Allow-Origin http://www.domain1.com;  #当前端只跨域不带cookie时,可为*
        add_header Access-Control-Allow-Credentials true; #携带cookie
    }
}

前端示例

var xhr = new XMLHttpRequest();

// 前端开关:浏览器是否读写cookie
xhr.withCredentials = true;

// 访问nginx中的代理服务器
xhr.open('get', 'http://www.domain1.com:81/?user=admin', true);
xhr.send();



4、Axios 二次封装

Axios 进行二次封装。有以下好处:

  1. 这样可以提高代码的重用性,譬如等待时间、数据格式、请求头等许多都是重复的;
  2. 还便于我们统一管理,譬如请求拦截我们可以弄一个弹出框Loading 提示还在加载,然后在响应拦截把弹出框给关了;
  3. 规范接口,我们可以规定统一的数据格式,进行一些数据过滤等操作,又或者进行白名单的校验。
  4. 可以进行一些常见的错误处理,譬如报 401 权限错误,对用户进行提示。
  5. 避免同一时间对同一请求进行多次发起请求,譬如在双十一高峰期,网络变得拥堵,当用户发起一次请求可能要过 2s 才能返回数据,而在这两秒内,用户又发起了同一个请求,这时候我们就不要再向后端发起请求了。给用户提示不要重复发起请求。降低负荷。

这个的原理:就是创建一个数组把正在请求的url地址给存储起来,然后判断用户当前请求的是否在数组内,在的话就不予处理,不在的话就加进去并发起请求,每次请求成功后把请求成功的url从数组中删除。我们可以把网络速度调成2G进行测试。

示例

import axios from 'axios'
import testConfig from './globalConfig'

// 基本配置 baseURL,timeout,header,responseType,withCredentials
const myAxios = axios.create({
  baseURL: '/json', //  /api/bulletinBoard
  timeout: 10 * 1000
})

//请求拦截器 ------>  即发送请求要干啥子(常见的token、密钥的设置)
myAxios.interceptors.request.use(config => {
  console.log('请求拦截', config);
  const whiteList = testConfig.whilteApiList;
  const url = config.url;
  if(whiteList.indexOf(url) === -1) { // 如果没在白名单里面获取token,加入头部
    let token = localStorage.getItem('token');
    config.headers.token = token;
  }
  // 加载提示框
  // showLoadingToast({
  //   message: '加载中...',
  //   forbidClick: true,
  //   duration: 0
  // })
  return config
}, err => {
  return Promise.reject(err);
})

// 响应拦截器  ------>  即拿到数据后要干啥子
myAxios.interceptors.response.use(res => {
  console.log('响应拦截', res);
  // 关闭加载中提示框
  // closeToast();

  // 请求成功错误处理
  const status = res.status || 200;
  const msg = res.data.msg || '未知错误';
  if(status === 401) {
    alert('您没有操作权限');
    return Promise.reject(new Error(msg));
  }
  if(status !== 200) {
    alert("错误码" + status + "  " + msg);
    return Promise.reject(new Error(msg));
  }
  return res;
}, err => {
  return Promise.reject(err);
})


// 避免短时间内对同一个url重复请求,即第一个请求还没返回就又请求了。
const myAxiosComplexity = (function() {
    let hasRequest = []; // 用于存储发送了请求但是还没有接受到的url
    return function (config) {
      let url = config.url;
      if(hasRequest.indexOf(url) !== -1) { // 如果这个已经发送请求了
        return Promise.reject(new Error('请求已经提交,请等待回应后再发送新的请求'));
      }
      hasRequest.push(url);
      // 如果还没有发送请求,就直接发送请求
      return myAxios({ // 使用我们基本的myAxios
        ...config
      }).then((res) => {
        // 请求成功后过滤掉这个url
        hasRequest = hasRequest.filter(item => {
          if(item !== url) {
            return item;
          }
        });
        return res; //返回请求成功后后端返回的数据
      })
    }
})()

export {
  myAxios,
  myAxiosComplexity
}

使用示例

在这里插入图片描述

<template>
  <div @click="getTest" style="cursor: pointer">你好,佳钰</div>
</template>

<script>
import {myAxios, myAxiosComplexity} from '@/utils/myAxios'
export default {
  setup(props) {
    function getTest() {
      myAxiosComplexity({
      method: 'GET',
      url: '/test.json' 
      }).then(res => {
        console.log('测试',res.data);
      }).catch(err => {
        console.log(err.message);
      })
    }

    return {
      getTest,
    }
  }
}
</script>
<style scoped>
</style>



5、Git

在实习前专门学过一下 git ,也进行了实操,但是在工作中,有时候就直接把自己代码覆盖了O.O,果然还是得多实践哇。

5.1 本地链接远程仓库

  • git remote add origin 远程仓库地址 只有第一次即创建远程仓库时候才执行这条命令,之后只需要push 或者 pull
  • git clone项目地址 下拉项目

5.2 本地仓库操作

  • git init 初始化本地仓库
  • git add . 添加到缓存区 (. 是当前目录下全部文件)
  • git status 查看当前目录下可提交的文件(被修改的文件/没有提交的文件)
  • git commit -m '注释' 提交到本地仓库
  • git commit -m '注释' --no-verify 规避代码格式检查( Vue 开启了ESlint 代码格式检查的情况下)
  • git push origin分支名 上传到远程仓库指定分支
    • git push -u origin master : 如果当前分支与多个主机存在追踪关系,则可以使用-u参数指定一个默认主机,这样后面就可以不加任何参数使用git push,不带任何参数的git push,默认只推送当前分支,这叫做simple方式。
    • git push --all origin : 当遇到这种情况就是不管是否存在对应的远程分支,将本地的所有分支都推送到远程主机,这时需要-all选项
    • git push --force origin : git push的时候,需要本地先git pull更新到跟服务器版本一致,如果本地版本库比远程服务器上的低,那么一般会提示你git pull更新,如果一定要提交,那么可以使用这个命令
    • git push origin --tags: git push的时候,不会推送分支,如果一定要推送标签的话那么可以使用这个命令
  • git log 查看提交记录
  • git reset --hard HEAD^ 回退到上一个版本,两个^即回退到上上个版本;也可以是 HEAD-1
  • git relog 查看所有的版本及版本号
  • git reset --hard '每个版本的ID号' 到指定版本,版本号 ID 可通过relog 来查看

5.3 当本地仓库与远程仓库项目目录不一致时

不一致会上传不成功,这可能是其它项目组成员修改了代码目录,或者直接在远程仓库上修改了。所以一般开发我们都是建立不同的分支,最后合并,这样避免咱们修改同一文件起冲突,不一致的时候我们又要重新拉一下代码

  • git pull origin master 当本地仓库与远程仓库不一致时,用push 上传会失败,用 pull 上传会合并别人改的和你这个改的。以及更新你的本地仓库

5.4 分支操作

  • git branch -a 查看所有的分支
  • git checkout -b 分支名 创建一个新的分支
  • git checkout 分支名 切换当前的分支
  • git merge 分支名 当前分支合并另一个分支
  • git push origin :分支名 删除远程仓库中的分支(逻辑就是把空的推到远程的要删的分支上,等价于分支=null)
  • git branch -d分支名 删除本地分支

注意,当切换分支时,本地文件也会跟着改变,所以咱要先备份,像下面这样就不会把我们代码给覆盖了,或者说我们把覆盖的还原了。

git stash  #备份当前的commit
git checkout 切换的分支名
git stash apply  #将备份应用
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值