文章目录
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 适配
这里是用了
CSS3
的transform
属性来设置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)
}
},
}
源码解释:
- 首先根据设计稿的宽高比设置我们基本宽高比,例如这里是
1920*1080
, 默认需要保持的比例即是1920 / 1080
,同时默认当前宽高缩放是1,即不进行缩放。- 然后看看
calcRate
函数,我们只需要对每个页面组件进行缩放,即对整体进行了缩放,所以我们先拿到dom
节点,如果没拿到就直接返回,拿到了进行下一步- 获取当前屏幕的宽高比,如果当前的宽高比大于我们设计稿的宽高比则代表当前屏幕更宽,否则代表更高;对不同情况设置不同的缩放比例
- 事实上缩放比例看代码也很容易发现它的公式的,当更宽的时候高度不需要变,只需要跟原来的基本高度进行比较缩放,更高的时候宽度也只需要跟原来的基本宽度进行比较缩放,这个好理解。但是更宽的时候怎么设置宽度的缩放比例呢?我们之前基础的宽高比是
baseProportion = 1920 / 1080
; 那么我们只需要拿当前的高度乘以这个比例,就可以拿到当前的宽度了,再去除以基础的宽度,就可以获得它的缩放比例了,更高情况同样如此。- 最后我们在监听窗口大小,当发生变化时,就再次调用
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
组件源码解析
- 我们先看
styleCfg
函数,因为接收参数那一块咱定义的类型可以是String
, 也可以是Number
,我觉得如果是个人开发一个项目那没必要,如果多个人开发一个项目的前端还是有必要的,因为我们不知道他会传入什么,除非我们提前商量好。这个函数主要就是做了判断它传入的是String
还是Number
, 譬如这句话isNaN(Number(this.width))
就是先把他转化为Number
, 再判断是不是NaN
, 如果他传入的字符串是"20"
,那就是合法的,如果是"20px"
,那就是不合法的,这时候我们就直接选择this.width
,否则就要加上px
- 然后就是
watch
监听,因为Echarts
它的数据改变后,并不会重新渲染的,所以我们要手动给它更新数据,当option
改变后,我们销毁当前myChart
,然后再进行重绘;当然也可以使用this.forceUpdate()
进行强制刷新,然后再setOption
- 然后
mouted/onMounted
、beforeDestroy/onUnmounted
监听窗口大小变化,让图表跟着更新大小,走时候移除监听,销毁myChart
3、跨域
这里参考的这篇博客
3.1 什么是跨域?
跨域指的是浏览器不能执行其它网站的脚本。它是浏览器的同源策略造成的,是浏览器对
javascript
施加的安全限制。同源策略: 同源策略/
SOP
(Same origin policy
)是一种约定,由Netscape
公司1995
年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR
等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip
地址,也非同源。同源策略限制以下几种行为
Cookie
、LocalStorage
和IndexDB
无法获取DOM
和Js
对象无法获取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
进行二次封装。有以下好处:
- 这样可以提高代码的重用性,譬如等待时间、数据格式、请求头等许多都是重复的;
- 还便于我们统一管理,譬如请求拦截我们可以弄一个弹出框
Loading
提示还在加载,然后在响应拦截把弹出框给关了;- 规范接口,我们可以规定统一的数据格式,进行一些数据过滤等操作,又或者进行白名单的校验。
- 可以进行一些常见的错误处理,譬如报
401
权限错误,对用户进行提示。- 避免同一时间对同一请求进行多次发起请求,譬如在双十一高峰期,网络变得拥堵,当用户发起一次请求可能要过
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 #将备份应用