工作复盘
大佬勿看,从别的博文总结抄录用来自己看的!!!!
一、埋点
目的:
- 数据监控:运营需要Google分析来分析官网首页的用户浏览量,监控用户行为,进行数据分析;
- 错误监控:及时的上报异常情况,可以避免线上故障的发生。
1.1 script/image/link埋点
- 新建一个脚本,在脚本里动态创建一个带 async 的 script 标签请求埋点sdk的安装脚本,append到html的body或者head上。
- 通过 Image 对象的 src 属性指向上报脚本并携带参数,就可以将我们收集的数据传给后端。
- 把这个脚本插到xxxxx(框架里)地方, 将标签挂载到页面上。
- 用户浏览页面或者浏览表单,点击提交表单时就发送相关数据。
优点: 虽然埋点域名和当前域名不是同一个,但是script/image/link标签请求没有跨域的风险。
缺点: script和link标签需要挂在到页面上,载入js资源的时候会阻塞首页渲染
补充: 使用image对象无需挂载到页面上,反复操作dom。img的加载不会阻塞html的解析,但img加载后并不渲染,它需要等待Render Tree生成完后才和Render Tree一起渲染出来。通常埋点上报会使用gif图,合法的 GIF 只需要 43 个字节。
*image的缺陷:*浏览器设置了禁止图片显示时,无法触发请求。
// 通过script 需要插到DOM上
function sendByScriptInsertDOM(src){
var script = document.createElement("script");
script.src = src; (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(script);
}
// 通过image 不需要
function sendByImage(src) {
var img = new Image();
img.src = src;
}
来自:教你怎么前端实现埋点上报 - 掘金 (juejin.cn)
1.2 css/script阻塞
1.2.1 浏览器解析和渲染HTML过程
概念:
- DOM解析:将HTML的各种标签生成 DOM Tree 的过程,没有任何 CSS 样式。
- DOM渲染:将 DOM Tree 和 CSS 结合生成 render tree,并将其呈现在浏览器页面上。
DOM 解析和 DOM 渲染由两个线程完成,两者可以并行完成。
过程:
- DOM 解析:将 HTML 各标签生成 DOM 树,同时将 CSS 样式解析成 CSSOM 树;
- DOM 渲染:将 DOM 树和 CSS 树渲染成 Render tree;
- 进入布局阶段,将 Render 树上的节点分配他们在页面上的坐标(这一阶段还是渲染树);
- 进入绘制阶段,此时渲染引擎的工作就结束了,把渲染树的节点绘制在页面上。
浏览器是如何解析和渲染HTML的🔥🔥🔥 - 掘金 (juejin.cn)
1.2.2 css/js 阻塞
CSS:
- css 不会阻塞 DOM 解析,因为 css 解析和 DOM 解析是并行的。
- css 会阻塞 DOM 渲染,DOM解析完成了,页面上出现了 HTML 的节点,但是还没在页面上展示出来。是因为 DOM 渲染需要 CSSOM 和 DOM 树,但是 css 样式没有请求完,阻塞了 DOM 渲染,即CSS会阻塞 render tree 的生成,进而会阻塞DOM的渲染。
- css 会阻塞 JS 的执行,JS 脚本需要等到 css样式都加载完后,才能执行,这样 JS 才能获取到 DOM 元素最新的样式。因为这个原因,script 标签一般放在 link 标签的前面。
- body 内的外链 css,会因为浏览器的差异(有些浏览器的css或造成 DOM 解析阻塞,导致页面 DOM 解析及其样式渲染推迟;有些 CSS 的加载与 DOM 的解析就是并行的,等到 CSS 加载完成后再更新样式),造成样式闪烁。
JS:
- JS会阻塞 DOM 解析,,因为 JS可能有操作 DOM 结构的行为,比如删除节点啥的。所以浏览器会等待 JS 执行完了再继续解析 DOM。
- JS 会触发页面渲染,浏览器无法预知脚本的具体内容,因此在碰到 script 标签时,只好先渲染一次页面,以确保 script 脚本内能获取到 DOM 最新样式。
关于 JS 与 CSS 是否阻塞 DOM 的渲染和解析 - 掘金 (juejin.cn)
1.2.3 defer/async
绿色为 HTML 解析,蓝色 js 下载,红色为 JS 执行。
defer
- js 下载和 HTML 解析并行,js 执行在 HTML 解析完成后。
- 有序
async
- js 下载和 HTML 解析并行,js 下载完后就执行,执行的时候会阻塞 HTML 解析。
- 无序,谁先下载完就先执行谁
二、前端分页
前端拿到请求的所有数据,对数据进行处理,操作分页组件完成。
获取全部数据,获取当前页码page,和每页展示的数据条数pageSize,通过slice函数, 获取当前页展示的数据。
// 组件
<page
:current-page="page.page"
:page-count="page.pageCount"
:record-count="page.recordCount"
@change="switchToTablePage_1"
/>
async switchToTablePage_1(page = 1) {
this.page.page = page;
try {
...
this.searchList = res.list || [];
this.list = this.searchList.slice(
(page - 1) * this.page.pageSize,
this.page.pageSize * page,
);
this.page.pageCount = Math.ceil(
this.searchList.length / this.page.pageSize,
);
this.page.recordCount = this.searchList.length;
} catch (err) {
this.$dialog.show(err.message);
}
},
三、权限控制
按钮权限
1、用户登陆后请求接口,获得这个用户的权限列表,并把权限列表放到 window 上,通过window._params获取权限;
2、在vue原型上定义一个checkPermission函数用来查看权限节点:
Vue.prototype.checkPermission = function (key, success, failure) {
const that = this;
const call = function (cb) {
if (!cb) return;
if (typeof cb === 'function') {
cb();
} else if (typeof cb === 'string') {
that.$router.push(cb);
} else if (typeof cb === 'object') {
that.$router.push(cb);
}
};
if (that.checkPermissionBool(key)) {
call(success);
return true;
}
return false;
};
3、在页面上调用checkPermission函数,传入节点名称
<a @click="checkPermission('permission')"></a>
路由权限
- 初始化挂载全部路由
- 在前端路由元信息里面,设置一个权限 key。
- 对照路由表和权限表,重建路由(递归整个路由,里面做判断,如果一级路由没有这个权限节点的话,二级的路由也要隐藏起来元信息里的condition设为hidden; 如果当前路径path为空 '' 的话,就把路径设为重定向的路径)。
- 在每次路由跳转时,通过路由守卫beforeEach检查权限,确保手动输入url时,能根据权限进入。
接口权限
在 rbac系统,给用户设置用户角色,以及给接口模块设置一个权限码。系统可以将用户角色和接口模块关联起来。这个接口的权限码,就放到 node 层的请求接口里。发送请求的时候,框架里面用的一个中间件就会校验 rbac 权限。
四、大文件分片下载
4.1 文件下载
服务端写content-type附件下载
前端如何通过a链接下载文件 - 掘金 (juejin.cn) 通过 a 标签下载
download(href) {
const a = document.createElement('a');
a.href = href;
document.body.appendChild(a);
a.click();
a.remove();
}
-
同源和跨域下,都可以使用
a标签对超链接文件进行预览或者下载 -
同源下,超链接文件可以通过
a标签download属性值更改下载文件的名称;跨域下,超链接文件不能被更改文件名称。
blob 对象
Blob 是二进制类型的大对象。将接口返回的内容转换成 blob 对象,再通过 createObjectURL 创建对象链接,通过 a 标签下载。
downloadFileHelper(fileName, fileContent) {
const a = document.createElement('a');
const blog = new Blob([fileContent]);
a.download = fileName;
const url = URL.createObjectURL(blog);
a.href = url;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
},
封装输出文件的工具
exports.outputFile = (ctx, name, content, type) => {
let fileType = type;
let fileName = name;
if (!fileType) {
fileType = fileName.split('.').pop();
}
fileName = encodeURI(fileName).replace(/,/g, '');
const fileMimeType = mime.getType(fileType) || 'application/octet-stream';
let disposition = `attachment; filename=${fileName}; filename*=UTF-8''${fileName}`;
ctx.set('Content-Type', fileMimeType);
ctx.set('Content-Disposition', disposition);
if (Buffer.isBuffer(content)) {
ctx.body = content;
} else {
ctx.body = Buffer.from(content, 'base64');
}
ctx.type = `.${fileType}`;
};
在接入层controller中接口返回结果的时候调用
const res = await api.downloadFile(ctx, data);
前端用a标签调用,a.href 设置为对应的路由链接。
4.2 大文件分片下载
- 后端将文件分块上传到服务器
- 先请求一个接口,获得文件的总大小、分块总数、分片大小。
- 调用下载接口获取各个分片。
发起请求获取分块数据:向服务器发起请求获取文件分块数据。需要注意的是,每个分块请求需要带上 Range 头部,指定要请求的数据范围。服务器需要支持 Range 头部,返回对应范围的数据。在请求分块数据时,可以通过 Promise.all() 或 async/await 等方式实现并发请求,以提高下载速度
// 定义一个数组用于存储分块数据
const chunkData = [];
// 发起请求获取每个分块数据
const requests = chunks.map((chunk, index) => {
return new Promise((resolve, reject) => {
const res = await api.download();
if(res){
chunkData[index] = res.data;
resolve();
} else {
reject();
}
});
});
// 等待所有请求完成后,合并分块数据
Promise.all(requests).then(() => {
const blob = new Blob(chunkData, { type: file.type });
const url = URL.createObjectURL(blob);
// 下载完整文件
const a = document.createElement('a');
a.href = url; a.download = file.name;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
});
4.2 文件上传
(1) 将需要上传的文件按照一定的分割规则,分割成相同大小的数据块;
(2) 向服务端发送上传请求,上传时携带完整文件的唯一标识(建议使用MD5加密值就行),服务端判断该文件是否存在,不存在则返回同意上传信息。
(3) 接收到服务端同意上传后,按照一定的策略(串行或并行)发送各个分片数据块,每个分片携带分片信息(分片总数,分片索引,分片唯一索引,分片数据等),采用并行可实现分片秒传的功能,采用串行更好实现断点续传的功能。
(4) 发送完成后,服务端根据判断数据上传是否完整,如果完整,则进行数据块合成得到原始文件。
五、响应式布局
对不同屏幕适配做处理。
页面头部添加:
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no”>
- width=device-width: 自适应手机屏幕的尺寸宽度
- maximum-scale: 缩放比例的最大值
- inital-scale: 缩放的初始化
- user-scalable: 用户的可以缩放的操作
实现响应式布局的方法:
- 媒体查询
- 百分比
- vw/vh
- rem
5.1 媒体查询
@media screen (min-width: 375px) and (max-width: 600px) {}
5.2 vh/vw
相对视窗的百分比
5.3 rem
rem是相对根html的font-size属性,1rem=16px;
对话框 rds
业务背景:用户登陆系统后,如果当前用户没有开通父账户和子账户,就弹窗显示跳转链接去开通账户页面,一旦当前用户开通过账户,或账户在流程中的话,就不再显示这个弹窗。
是通过接口里两个字段来控制弹窗跳转链接展示,比如说a字段和b字段,当a字段为0且b字段也为0的时候,然后 redis 里面设置一个字段 key C也为0,然后当b字段为1的时候 redis里的c就加一。这些是在node接入层做的逻辑,然后每次请求接口,就判断redis的状态,为0的时候就把重新创建字段d=0连带这后端返回的对象一起传到前端页面作判断,此时弹窗展示。用户两个账户都开通完后,判断redis的key值为1,并将d=1值传给前端, 此时前端不会再弹窗。