├── docs ├── README.md ├── regular │ ├── README.md │ └── regular-special-char.md ├── javascript │ └── README.md ├── css │ ├── README.md │ ├── img │ │ ├── wlwys1.png │ │ ├── wlwys2.png │ │ ├── wlwys3.png │ │ └── sticky1.png │ ├── grid.md │ ├── sticky.md │ ├── pseudo-classes-element.md │ └── css.md ├── web │ ├── README.md │ ├── img │ │ ├── dddl1.jpg │ │ └── dddl2.jpg │ ├── proxy.md │ ├── single-sign-in.md │ ├── http-status.md │ ├── svg-stroke.md │ └── svg.md └── .vuepress │ ├── components │ └── Home.vue │ └── config.js ├── images └── interview │ ├── 1.png │ ├── 10.png │ ├── 11.png │ ├── 12.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ ├── 6.jpg │ ├── 7.png │ ├── 8.png │ └── 9.png ├── README.md ├── .gitignore ├── deploy.sh ├── package.json ├── js ├── flexible.js ├── clipboard.js ├── video-info.ts └── util.js ├── css ├── mobile-base.css └── pc-base.css ├── 常用正则大全(转).md ├── interview-3.md ├── interview-2.md └── interview-1.md /docs/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/regular/README.md: -------------------------------------------------------------------------------- 1 | # 正则 2 | -------------------------------------------------------------------------------- /docs/javascript/README.md: -------------------------------------------------------------------------------- 1 | # JavaScript 2 | -------------------------------------------------------------------------------- /docs/css/README.md: -------------------------------------------------------------------------------- 1 | # CSS 2 | :::tip 3 | 这里会介绍一些关于css的知识 4 | ::: 5 | 6 | -------------------------------------------------------------------------------- /docs/web/README.md: -------------------------------------------------------------------------------- 1 | # Web 2 | :::tip 3 | 这里会介绍一些关于web的知识 4 | ::: 5 | 6 | -------------------------------------------------------------------------------- /docs/css/img/wlwys1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincentzyc/notes/HEAD/docs/css/img/wlwys1.png -------------------------------------------------------------------------------- /docs/css/img/wlwys2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincentzyc/notes/HEAD/docs/css/img/wlwys2.png -------------------------------------------------------------------------------- /docs/css/img/wlwys3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincentzyc/notes/HEAD/docs/css/img/wlwys3.png -------------------------------------------------------------------------------- /docs/web/img/dddl1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincentzyc/notes/HEAD/docs/web/img/dddl1.jpg -------------------------------------------------------------------------------- /docs/web/img/dddl2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincentzyc/notes/HEAD/docs/web/img/dddl2.jpg -------------------------------------------------------------------------------- /images/interview/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincentzyc/notes/HEAD/images/interview/1.png -------------------------------------------------------------------------------- /images/interview/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincentzyc/notes/HEAD/images/interview/10.png -------------------------------------------------------------------------------- /images/interview/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincentzyc/notes/HEAD/images/interview/11.png -------------------------------------------------------------------------------- /images/interview/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincentzyc/notes/HEAD/images/interview/12.png -------------------------------------------------------------------------------- /images/interview/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincentzyc/notes/HEAD/images/interview/2.png -------------------------------------------------------------------------------- /images/interview/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincentzyc/notes/HEAD/images/interview/3.png -------------------------------------------------------------------------------- /images/interview/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincentzyc/notes/HEAD/images/interview/4.png -------------------------------------------------------------------------------- /images/interview/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincentzyc/notes/HEAD/images/interview/5.png -------------------------------------------------------------------------------- /images/interview/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincentzyc/notes/HEAD/images/interview/6.jpg -------------------------------------------------------------------------------- /images/interview/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincentzyc/notes/HEAD/images/interview/7.png -------------------------------------------------------------------------------- /images/interview/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincentzyc/notes/HEAD/images/interview/8.png -------------------------------------------------------------------------------- /images/interview/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincentzyc/notes/HEAD/images/interview/9.png -------------------------------------------------------------------------------- /docs/css/img/sticky1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincentzyc/notes/HEAD/docs/css/img/sticky1.png -------------------------------------------------------------------------------- /docs/css/grid.md: -------------------------------------------------------------------------------- 1 | # Grid 网格布局 2 | 3 | 网格布局(Grid)是最强大的 CSS 布局方案。 4 | 5 | 它将网页划分成一个个网格,可以任意组合不同的网格,做出各种各样的布局。以前,只能通过复杂的 CSS 框架达到的效果,现在浏览器内置了。 6 | 7 | Flex 布局是轴线布局,只能指定"项目"针对轴线的位置,可以看作是一维布局。Grid 布局则是将容器划分成"行"和"列",产生单元格,然后指定"项目所在"的单元格,可以看作是二维布局。Grid 布局远比 Flex 布局强大。 8 | 9 | -------------------------------------------------------------------------------- /docs/.vuepress/components/Home.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # notes 2 | 个人笔记 详见 Issues 3 | 4 | ### 中高级前端大厂面试秘籍 5 | - [面试上篇](https://github.com/vincentzyc/notes/edit/master/interview-1.md); 6 | - [面试中篇](https://github.com/vincentzyc/notes/edit/master/interview-2.md); 7 | - [面试下篇](https://github.com/vincentzyc/notes/edit/master/interview-3.md); 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .svn 16 | .idea 17 | .vscode 18 | .temp 19 | .cache 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw* -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 确保脚本抛出遇到的错误 4 | set -e 5 | 6 | # 生成静态文件 7 | npm run docs:build 8 | 9 | # 进入生成的文件夹 10 | cd docs/.vuepress/dist 11 | 12 | # 如果是发布到自定义域名 13 | # echo 'www.example.com' > CNAME 14 | 15 | git init 16 | git add -A 17 | git commit -m 'deploy' 18 | 19 | # 如果发布到 https://.github.io 20 | # git push -f git@github.com:/.github.io.git master 21 | 22 | # 如果发布到 https://.github.io/ 23 | git push -f git@github.com:vincentzyc/notes.git master:gh-pages 24 | 25 | cd - -------------------------------------------------------------------------------- /docs/css/sticky.md: -------------------------------------------------------------------------------- 1 | # position: sticky 2 | 3 | position:sticky是css定位新增属性;可以说是相对定位relative和固定定位fixed的结合;它主要用在对scroll事件的监听上;简单来说,在滑动过程中,某个元素距离其父元素的距离达到sticky粘性定位的要求时(比如top:100px);position:sticky这时的效果相当于fixed定位,固定到适当位置。 4 | 5 | # sticky 生效规则 6 | 7 | 1. 须指定 top, right, bottom 或 left 四个阈值其中之一(且达到设定的阈值),才可使粘性定位生效。否则其行为与相对定位相同,并且 top 和 bottom 同时设置时,top 生效的优先级高,left 和 right 同时设置时,left 的优先级高 8 | 9 | 2. 设定为 position: sticky 的元素的任意父节点的 overflow 属性必须是 visible,否则 position:sticky 不会生效;这里需要解释一下: 10 | - 如果 position:sticky 元素的任意父节点定位设置为 overflow:hidden,则父容器无法进行滚动,所以 position:sticky 元素也不会有滚动然后固定的情况。 11 | - 如果 position:sticky 元素的任意父节点定位设置为 position:relative | absolute | fixed,则元素相对父元素进行定位,而不会相对 viewprot 定位。 12 | 13 | 3. 在满足上述情况下,设定了 position: sticky 的元素的父容器的高度必须大于当前元素,否则也会失效。(当然,此时,sticky 吸附的基准元素就会变成父元素) 14 | 15 | # sticky 兼容性 16 | 17 | ![](./img/sticky1.png) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "notes", 3 | "version": "1.0.0", 4 | "description": "个人笔记", 5 | "main": "index.js", 6 | "directories": { 7 | "doc": "docs" 8 | }, 9 | "scripts": { 10 | "docs:dev": "vuepress dev docs", 11 | "docs:build": "vuepress build docs", 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/vincentzyc/notes.git" 17 | }, 18 | "keywords": [ 19 | "notes", 20 | "blog" 21 | ], 22 | "author": "vincentzyc", 23 | "license": "ISC", 24 | "bugs": { 25 | "url": "https://github.com/vincentzyc/notes/issues" 26 | }, 27 | "homepage": "https://github.com/vincentzyc/notes#readme", 28 | "devDependencies": { 29 | "@vuepress/plugin-back-to-top": "^1.8.2", 30 | "vuepress": "^1.8.2", 31 | "vuepress-plugin-fulltext-search": "^2.2.1", 32 | "vuepress-plugin-one-click-copy": "^1.0.5" 33 | } 34 | } -------------------------------------------------------------------------------- /docs/web/proxy.md: -------------------------------------------------------------------------------- 1 | # 什么是代理 2 | 3 | 代理其实就是一个中介,`A`和`B`本来可以直连,中间插入一个`C`,`C`就是中介. 4 | 刚开始的时候,代理多数是帮助内网`client`访问外网`server`用的 5 | 6 | # 正向代理 7 | 8 | 正向代理类似一个跳板机,代理访问外部资源. 9 | 10 | 最常见的就是翻墙工具以及`vpn`,甚至可以理解正向代理就是伪装 11 | 12 | > 买票的黄牛 13 | 14 | A(客户端)想租C(服务端)的房子,但是A(客户端)并不认识C(服务端)租不到。 15 | B(代理)认识C(服务端)能租这个房子所以你找了B(代理)帮忙租到了这个房子。 16 | 17 | 这个过程中C(服务端)不认识A(客户端)只认识B(代理) 18 | C(服务端)并不知道A(客户端)租了房子,只知道房子租给了B(代理)。 19 | 20 | 就是现在的贝壳租房 21 | 22 | ![](https://gitee.com/Uvdream/images/raw/master/images/20210126153917.png) 23 | 24 | **正向代理的用途:** 25 | 26 |   (1)访问原来无法访问的资源,如`google` 27 | 28 | ​ (2) 可以做缓存,加速访问资源 29 | 30 |   (3)对客户端访问授权,上网进行认证 31 | 32 |   (4)代理可以记录用户访问记录(上网行为管理),对外隐藏用户信息] 33 | 34 | 35 | 36 | # 反向代理 37 | 38 | 反向代理(`Reverse Proxy`)实际运行方式是指以代理服务器来接受`internet`上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给`internet`上请求连接的客户端,此时代理服务器对外就表现为一个服务器 39 | 40 | 说白了就是让客户端访问不同的网站,客户端的感知就是一个服务器,其实后面是更多的服务器集结到了一台服务器上 41 | 42 | > 租房的代理 43 | 44 | A(客户端)想租一个房子,B(代理)就把这个房子租给了他。 45 | 这时候实际上C(服务端)才是房东。 46 | B(代理)是中介把这个房子租给了A(客户端)。 47 | 48 | 就是现在的链家 49 | 50 | 51 | 52 | ![反向代理](https://gitee.com/Uvdream/images/raw/master/images/20210126152527.png) 53 | 54 | #### 反向代理的作用: 55 | 56 | (1)保证内网的安全,阻止web攻击,大型网站,通常将反向代理作为公网访问地址,Web服务器是内网 57 | 58 | (2)负载均衡,通过反向代理服务器来优化网站的负载 -------------------------------------------------------------------------------- /docs/web/single-sign-in.md: -------------------------------------------------------------------------------- 1 | # 单点登录 2 | 3 | 单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一 4 | 5 | SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统 6 | 7 | SSO 一般都需要一个独立的认证中心(passport),子系统的登录均得通过passport,子系统本身将不参与登录操作 8 | 9 | 当一个系统成功登录以后,passport将会颁发一个令牌给各个子系统,子系统可以拿着令牌会获取各自的受保护资源,为了减少频繁认证,各个子系统在被passport授权以后,会建立一个局部会话,在一定时间内可以无需再次向passport发起认证 10 | 11 | #### 1、登录 12 | 13 | 相比于单系统登录,sso需要一个独立的认证中心,只有认证中心能接受用户的用户名密码等安全信息,其他系统不提供登录入口,只接受认证中心的间接授权。间接授权通过令牌实现,sso认证中心验证用户的用户名密码没问题,创建授权令牌,在接下来的跳转过程中,授权令牌作为参数发送给各个子系统,子系统拿到令牌,即得到了授权,可以借此创建局部会话,局部会话登录方式与单系统的登录方式相同。这个过程,也就是单点登录的原理,用下图说明 14 | 15 | ![](./img/dddl1.jpg) 16 | 17 | 下面对上图简要描述 18 | 19 | 20 | - 用户访问系统1的受保护资源,系统1发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数 21 | - sso认证中心发现用户未登录,将用户引导至登录页面 22 | - 用户输入用户名密码提交登录申请 23 | - sso认证中心校验用户信息,创建用户与sso认证中心之间的会话,称为全局会话,同时创建授权令牌 24 | - sso认证中心带着令牌跳转会最初的请求地址(系统1) 25 | - 系统1拿到令牌,去sso认证中心校验令牌是否有效 26 | - sso认证中心校验令牌,返回有效,注册系统1 27 | - 系统1使用该令牌创建与用户的会话,称为局部会话,返回受保护资源 28 | - 用户访问系统2的受保护资源 29 | - 系统2发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数 30 | - sso认证中心发现用户已登录,跳转回系统2的地址,并附上令牌 31 | - 系统2拿到令牌,去sso认证中心校验令牌是否有效 32 | - sso认证中心校验令牌,返回有效,注册系统2 33 | - 系统2使用该令牌创建与用户的局部会话,返回受保护资源 34 | 35 |   用户登录成功之后,会与sso认证中心及各个子系统建立会话,用户与sso认证中心建立的会话称为全局会话,用户与各个子系统建立的会话称为局部会话,局部会话建立之后,用户访问子系统受保护资源将不再通过sso认证中心,全局会话与局部会话有如下约束关系 36 | 37 | 38 | - 局部会话存在,全局会话一定存在 39 | - 全局会话存在,局部会话不一定存在 40 | - 全局会话销毁,局部会话必须销毁 41 | 42 | 43 | ### 部署图 44 | 45 | 单点登录涉及sso认证中心与众子系统,子系统与sso认证中心需要通信以交换令牌、校验令牌及发起注销请求,因而子系统必须集成sso的客户端,sso认证中心则是sso服务端,整个单点登录过程实质是sso客户端与服务端通信的过程,用下图描述 46 | 47 | ![](./img/dddl2.jpg) 48 | 49 | sso认证中心与sso客户端通信方式有多种,这里以简单好用的httpClient为例,web service、rpc、restful api都可以 -------------------------------------------------------------------------------- /js/flexible.js: -------------------------------------------------------------------------------- 1 | /** 2 | designWidth: 设计稿的实际宽度值,需要根据实际设置 3 | maxWidth: 制作稿(页面)的最大宽度值,需要根据实际设置 4 | 这段js的最后面有两个参数记得要设置,一个为设计稿实际宽度,一个为制作稿(页面)最大宽度,例如设计稿为750,最大宽度为750,则为(750,750) 5 | */ 6 | !(function(designWidth, maxWidth) { 7 | var doc = document, 8 | win = window, 9 | docEl = doc.documentElement, 10 | rem = 14, 11 | tid; 12 | 13 | function refreshRem() { 14 | var width = docEl.getBoundingClientRect().width; 15 | maxWidth = maxWidth || 540; 16 | width > maxWidth && (width = maxWidth); 17 | rem = width * 100 / designWidth; 18 | docEl.style.fontSize = rem + 'px'; 19 | if (doc.body) setBodyStyle() 20 | } 21 | 22 | function setBodyStyle() { 23 | doc.body.style.position = "relative"; 24 | doc.body.style.fontSize = 14 / rem + "rem"; 25 | doc.body.style.margin = "0 auto"; 26 | doc.body.style.maxWidth = maxWidth + "px"; 27 | } 28 | refreshRem(); 29 | win.addEventListener("resize", function() { 30 | clearTimeout(tid); //防止执行两次 31 | tid = setTimeout(refreshRem(), 300); 32 | }, false); 33 | win.addEventListener("pageshow", function(e) { 34 | if (e.persisted) { // 浏览器后退的时候重新计算 35 | clearTimeout(tid); 36 | tid = setTimeout(refreshRem(), 300); 37 | } 38 | }, false); 39 | win.addEventListener("DOMContentLoaded", function() { 40 | setBodyStyle() 41 | }, false); 42 | })(720, 640); 43 | 44 | //压缩版 45 | // !function(a,b){function h(){var d=e.getBoundingClientRect().width;b=b||540,d>b&&(d=b),f=100*d/a,e.style.fontSize=f+"px",c.body&&i()}function i(){c.body.style.position="relative",c.body.style.fontSize=14/f+"rem",c.body.style.margin="0 auto",c.body.style.maxWidth=b+"px"}var g,c=document,d=window,e=c.documentElement,f=14;h(),d.addEventListener("resize",function(){clearTimeout(g),g=setTimeout(h(),300)},!1),d.addEventListener("pageshow",function(a){a.persisted&&(clearTimeout(g),g=setTimeout(h(),300))},!1),d.addEventListener("DOMContentLoaded",function(){i()},!1)}(720,640); 46 | -------------------------------------------------------------------------------- /docs/web/http-status.md: -------------------------------------------------------------------------------- 1 | # HTTP 常见状态码 2 | 3 | ### 2开头 (请求成功)表示成功处理了请求的状态代码。 4 | 5 | 200 (成功) 服务器已成功处理了请求。 通常,这表示服务器提供了请求的网页。 6 | 7 | 201 (已创建) 请求成功并且服务器创建了新的资源。 8 | 9 | 202 (已接受) 服务器已接受请求,但尚未处理。 10 | 11 | 203 (非授权信息) 服务器已成功处理了请求,但返回的信息可能来自另一来源。 12 | 13 | 204 (无内容) 服务器成功处理了请求,但没有返回任何内容。 14 | 15 | 205 (重置内容) 服务器成功处理了请求,但没有返回任何内容。 16 | 17 | 206 (部分内容) 服务器成功处理了部分 GET 请求。 18 | 19 | ### 3开头 (请求被重定向)表示要完成请求,需要进一步操作。 通常,这些状态代码用来重定向。 20 | 21 | 300 (多种选择) 针对请求,服务器可执行多种操作。 服务器可根据请求者 (user agent) 选择一项操作,或提供操作列表供请求者选择。 22 | 23 | 301 (永久移动) 请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。 24 | 25 | 302 (临时移动) 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。 26 | 27 | 303 (查看其他位置) 请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码。 28 | 29 | 304 (未修改) 自从上次请求后,请求的网页未修改过。 服务器返回此响应时,不会返回网页内容。 30 | 31 | 305 (使用代理) 请求者只能使用代理访问请求的网页。 如果服务器返回此响应,还表示请求者应使用代理。 32 | 33 | 307 (临时重定向) 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。 34 | 35 | ### 4开头 (请求错误)这些状态代码表示请求可能出错,妨碍了服务器的处理。 36 | 37 | 400 (错误请求) 服务器不理解请求的语法。 38 | 39 | 401 (未授权) 请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。 40 | 41 | 403 (禁止) 服务器拒绝请求。 42 | 43 | 404 (未找到) 服务器找不到请求的网页。 44 | 45 | 405 (方法禁用) 禁用请求中指定的方法。 46 | 47 | 406 (不接受) 无法使用请求的内容特性响应请求的网页。 48 | 49 | 407 (需要代理授权) 此状态代码与 401(未授权)类似,但指定请求者应当授权使用代理。 50 | 51 | 408 (请求超时) 服务器等候请求时发生超时。 52 | 53 | 409 (冲突) 服务器在完成请求时发生冲突。 服务器必须在响应中包含有关冲突的信息。 54 | 55 | 410 (已删除) 如果请求的资源已永久删除,服务器就会返回此响应。 56 | 57 | 411 (需要有效长度) 服务器不接受不含有效内容长度标头字段的请求。 58 | 59 | 412 (未满足前提条件) 服务器未满足请求者在请求中设置的其中一个前提条件。 60 | 61 | 413 (请求实体过大) 服务器无法处理请求,因为请求实体过大,超出服务器的处理能力。 62 | 63 | 414 (请求的 URI 过长) 请求的 URI(通常为网址)过长,服务器无法处理。 64 | 65 | 415 (不支持的媒体类型) 请求的格式不受请求页面的支持。 66 | 67 | 416 (请求范围不符合要求) 如果页面无法提供请求的范围,则服务器会返回此状态代码。 68 | 69 | 417 (未满足期望值) 服务器未满足"期望"请求标头字段的要求。 70 | 71 | ### 5开头(服务器错误)这些状态代码表示服务器在尝试处理请求时发生内部错误。 这些错误可能是服务器本身的错误,而不是请求出错。 72 | 73 | 500 (服务器内部错误) 服务器遇到错误,无法完成请求。 74 | 75 | 501 (尚未实施) 服务器不具备完成请求的功能。 例如,服务器无法识别请求方法时可能会返回此代码。 76 | 77 | 502 (错误网关) 服务器作为网关或代理,从上游服务器收到无效响应。 78 | 79 | 503 (服务不可用) 服务器目前无法使用(由于超载或停机维护)。 通常,这只是暂时状态。 80 | 81 | 504 (网关超时) 服务器作为网关或代理,但是没有及时从上游服务器收到请求。 82 | 83 | 505 (HTTP 版本不受支持) 服务器不支持请求中所用的 HTTP 协议版本。 -------------------------------------------------------------------------------- /docs/css/pseudo-classes-element.md: -------------------------------------------------------------------------------- 1 | # 伪类与伪元素 2 | 3 | ## 目录 4 | ![](./img/wlwys1.png) 5 | 6 | ## CSS 选择器 7 | 8 | 想要了解伪类与伪元素,我们不得不提到 CSS 选择器,CSS 选择器是元素和其他部分组合起来,告诉浏览器哪个 HTML 元素应当是被选为应用规则中的 CSS 属性值的方式 9 | 10 | 选择器的种类有: 11 | 12 | - 类型、类和ID选择器 13 | - 标签属性选择器 14 | - 运算符 15 | - 伪类与伪元素 16 | 17 | ### 类型、类和ID选择器 18 | 19 | ```css 20 | h1 { } /* 类型选择器 */ 21 | .box { } /* 类选择器 */ 22 | #unique { } /* ID选择器 */ 23 | ``` 24 | ### 标签属性选择器 25 | 这组选择器根据一个元素上的某个标签属性是否存在来选择: 26 | 27 | ```css 28 | a[title] { } 29 | ``` 30 | 或者根据标签属性是否是特定值来选择: 31 | ```css 32 | a[href="https://example.com"] { } 33 | ``` 34 | 35 | ### 运算符 36 | 这种选择器可以将其他选择器组合起来,更复杂的选择元素。下面的示例就是利用运算符`(>)`选择了`
`元素的初代子元素 37 | ```css 38 | article > p { } 39 | ``` 40 | 41 | ## 伪类与伪元素 42 | 43 | > 什么是伪类?什么是伪元素? 44 | - 伪类:用于选择处于 特定状态 的元素,比如鼠标悬浮状态( :hover )。它们表现得会像是你向你的文档的某个部分应用了一个类一样,帮你在你的标记文本中减少多余的类,让你的代码更灵活、更易于维护。 45 | - 伪元素:以类似方式表现,不过表现得是像你往标记文本中加入全新的HTML元素一样,产生的效果是把不存在的元素硬选出来 46 | 47 | > 伪类与伪元素的区别 48 | 49 | ### 表现方式区别: 50 | 伪类表现的是某种状态被选择出来,例如` :hover 、 :checked` ,而伪元素表现的是选择元素的某个部分,使这部分看起来像一个独立的元素,其实并不是,例如 `::before 、 ::after` 51 | 抽象的说,伪类就是选择元素某状态,伪元素就是创建一个HTML元素 52 | 53 | ### 符号区别 54 | 伪类使用单冒号` : `,伪元素开头为双冒号` :: `,单需要注意的是 CSS3 之前并没有定义伪元素,都统称为伪类,所以目前绝大多数的浏览器都同时支持使用这两种方式来表示伪元素 55 | 56 | 57 | 58 | > 常见的伪类与伪元素 59 | ### 伪类: 60 | 61 | ![](./img/wlwys2.png) 62 | 63 | ### 伪元素: 64 | 65 | ![](./img/wlwys3.png) 66 | 67 | ## ::before 与 ::after 68 | 69 | 接下来我们说说最常用、最经典的 `::before` 和 `::after`伪元素, `::before` 表示在元素内容之前插入一个虚拟的元素, `::after`则表示在元素内容之后插入,并且 `::before` 和 `::after`中支持所有的 CSS 属性。 70 | 71 | 但需要注意的是这两个伪元素所在的 CSS 规则必须指定 `content` 属性才会生效 72 | ### content 属性 73 | `content` 可取string、attr()、url()/uri(): 74 | 75 | #### string 76 | ```css 77 |

Hello Nuage

78 | 88 | ``` 89 | #### attr() 90 | ```css 91 | ❤️Hello Nuage❤️ 92 | 97 | ``` 98 | #### url()/uri() 99 | ```css 100 |

❤️Hello Nuage❤️

101 | 106 | ``` 107 | 108 | 这两个伪元素常用于一些修饰性元素,以纯 CSS 代码添加进去,就能很好地保持 HTML 代码中的语义,既完成了显示效果,又不会让 DOM 中出现很多无语义的空元素 -------------------------------------------------------------------------------- /docs/css/css.md: -------------------------------------------------------------------------------- 1 | # Css移动端适配 2 | 3 | 前一段时间群里有人问我rem相关的问题,一直想整理一下,可是都忘记了.今天终于抽出时间来整理一下相关知识点! 4 | 说到rem就要谈到移动端布局,现在很多人在移动端布局上面还是用px,我们先来谈谈px;px:像素(Pixel),相对单位长度,px相对于屏幕分辨率而言的. 5 | 我们为什么使用rem,rem的诞生也是webapp的推动,rem完美解决了webapp的屏幕适应问题,大家都知道移动端设备屏幕大小各异,像素也是各个不同的,那么webapp使用px就相当鸡肋,因为大家都知道px是像素,由屏幕的分辨率决定,用px在很大程度上影响webapp的美观. 6 | 下面介绍如何使用rem,首先我们得设置rem的初始值,然后我们每个尺寸按照这个初始值进行换算得出rem值进行布局. 7 | ## 1.媒体查询 8 | 9 | ```css 10 | html { 11 | font-size : 20px; 12 | } 13 | @media only screen and (min-width: 401px){ 14 | html { 15 | font-size: 25px !important; 16 | } 17 | } 18 | @media only screen and (min-width: 428px){ 19 | html { 20 | font-size: 26.75px !important; 21 | } 22 | } 23 | @media only screen and (min-width: 481px){ 24 | html { 25 | font-size: 30px !important; 26 | } 27 | } 28 | @media only screen and (min-width: 569px){ 29 | html { 30 | font-size: 35px !important; 31 | } 32 | } 33 | @media only screen and (min-width: 641px){ 34 | html { 35 | font-size: 40px !important; 36 | } 37 | } 38 | ``` 39 | 40 | 41 | ## 2.自动设置html的font-size 42 | 43 | ```js 44 | (function (doc, win) { 45 | var docEl = doc.documentElement, 46 | resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize', 47 | recalc = function () { 48 | var clientWidth = docEl.clientWidth; 49 | if (!clientWidth) return; 50 | docEl.style.fontSize = 20 * (clientWidth / 320) + 'px'; 51 | }; 52 | if (!doc.addEventListener) return; 53 | win.addEventListener(resizeEvt, recalc, false); 54 | doc.addEventListener('DOMContentLoaded', recalc, false); 55 | })(document, window); 56 | ``` 57 | ## 3.designWidth:设计稿的实际宽度值,需要根据实际设置,maxWidth:制作稿的最大宽度值,需要根据实际设置 58 | 59 | ```js 60 | (function(designWidth, maxWidth) { 61 | var doc = document, 62 | win = window, 63 | docEl = doc.documentElement, 64 | remStyle = document.createElement("style"), 65 | tid; 66 | 67 | function refreshRem() { 68 | var width = docEl.getBoundingClientRect().width; 69 | maxWidth = maxWidth || 540; 70 | width>maxWidth && (width=maxWidth); 71 | var rem = width * 100 / designWidth; 72 | remStyle.innerHTML = 'html{font-size:' + rem + 'px;}'; 73 | } 74 | 75 | if (docEl.firstElementChild) { 76 | docEl.firstElementChild.appendChild(remStyle); 77 | } else { 78 | var wrap = doc.createElement("div"); 79 | wrap.appendChild(remStyle); 80 | doc.write(wrap.innerHTML); 81 | wrap = null; 82 | } 83 | //要等 wiewport 设置好后才能执行 refreshRem,不然 refreshRem 会执行2次; 84 | refreshRem(); 85 | 86 | win.addEventListener("resize", function() { 87 | clearTimeout(tid); //防止执行两次 88 | tid = setTimeout(refreshRem, 300); 89 | }, false); 90 | 91 | win.addEventListener("pageshow", function(e) { 92 | if (e.persisted) { // 浏览器后退的时候重新计算 93 | clearTimeout(tid); 94 | tid = setTimeout(refreshRem, 300); 95 | } 96 | }, false); 97 | 98 | if (doc.readyState === "complete") { 99 | doc.body.style.fontSize = "16px"; 100 | } else { 101 | doc.addEventListener("DOMContentLoaded", function(e) { 102 | doc.body.style.fontSize = "16px"; 103 | }, false); 104 | } 105 | })(750, 750); 106 | ``` 107 | -------------------------------------------------------------------------------- /docs/web/svg-stroke.md: -------------------------------------------------------------------------------- 1 | # SVG Stroke 属性 2 | 3 | SVG提供了一个范围广泛stroke 属性。在本章中,我们将看看下面: 4 | 5 | - stroke 6 | - stroke-width 7 | - stroke-linecap 8 | - stroke-dasharray 9 | 10 | 所有stroke属性,可应用于任何种类的线条,文字和元素就像一个圆的轮廓。 11 | 12 | 1. stroke 属性定义一条线,文本或元素轮廓颜色: 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 代码如下: 22 | 23 | ```html 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ``` 32 | 33 | 2. stroke-width 属性定义了一条线,文本或元素轮廓厚度: 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 代码如下: 43 | 44 | ```html 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | ``` 53 | 54 | 3. stroke-linecap 属性定义不同类型的开放路径的终结: 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 代码如下: 64 | 65 | ```html 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | ``` 74 | 75 | 4. stroke-dasharray 属性用于创建虚线: 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 代码如下: 85 | 86 | ```html 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | ``` 95 | 96 | ### 示例一 97 | 用stroke属性画一条绿色的直线 98 | ```html 99 | 100 | 101 | 102 | ``` 103 | 104 | 105 | 106 | 107 | ### 示例二 108 | 用Stroke属性画一个拥有蓝色边框的黑色的圆形 109 | ```html 110 | ​​ 111 | 112 | 113 | ``` 114 | ​​ 115 | 116 | -------------------------------------------------------------------------------- /js/clipboard.js: -------------------------------------------------------------------------------- 1 | class Clipboard { 2 | constructor() { 3 | this.textArea = ''; 4 | } 5 | /** 6 | * 默认复制成功函数 7 | */ 8 | success() { 9 | alert('复制成功!') 10 | } 11 | /** 12 | * 默认复制失败函数 13 | */ 14 | fail() { 15 | alert('您的浏览器不支持自动复制!请手动复制!') 16 | } 17 | /** 18 | * 创建文本元素 19 | */ 20 | createTextArea() { 21 | this.textArea = document.createElement('textArea'); 22 | this.textArea.value = this.text; 23 | document.body.appendChild(this.textArea); 24 | } 25 | /** 26 | * 选择内容 27 | */ 28 | selectText() { 29 | if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) { 30 | // ios 31 | let range, 32 | selection; 33 | range = document.createRange(); 34 | range.selectNodeContents(this.textArea); 35 | selection = window.getSelection(); 36 | selection.removeAllRanges(); 37 | selection.addRange(range); 38 | this.textArea.setSelectionRange(0, 999999); 39 | } else { 40 | // 其它 41 | this.textArea.select(); 42 | } 43 | } 44 | /** 45 | * 复制到剪贴板 46 | */ 47 | copyToClipboard() { 48 | if (document.execCommand("Copy")) { 49 | this.success() 50 | } else { 51 | this.fail() 52 | } 53 | document.body.removeChild(this.textArea); 54 | } 55 | /** 56 | * @param {String} text 需要复制的内容 57 | * @param {function} success 复制成功回调函数 58 | * @param {function} fail 复制失败回调函数 59 | */ 60 | copy(text = '', success = this.success, fail = this.fail) { 61 | this.text = text; 62 | this.success = success; 63 | this.fail = fail; 64 | this.createTextArea(); 65 | this.selectText(); 66 | this.copyToClipboard(); 67 | } 68 | } 69 | export default Clipboard 70 | 71 | //使用方法 72 | // var val = document.getElementById('textAreas').innerText; //获取需复制的内容 73 | // var clipboard = new Clipboard(); 74 | // clipboard.copy(val); //调用函数执行复制 75 | 76 | 77 | // let textArea = ''; 78 | // /** 79 | // * 默认复制成功函数 80 | // */ 81 | // function fnSuccess() { 82 | // alert('复制成功!') 83 | // } 84 | // /** 85 | // * 默认复制失败函数 86 | // */ 87 | // function fnFail() { 88 | // alert('您的浏览器不支持自动复制!请手动复制!') 89 | // } 90 | // /** 91 | // * 创建文本元素 92 | // * @param {String} text 需要复制的内容 93 | // */ 94 | // function createTextArea(text) { 95 | // textArea = document.createElement('textArea'); 96 | // textArea.value = text; 97 | // document.body.appendChild(textArea); 98 | // } 99 | // /** 100 | // * 选择内容 101 | // */ 102 | // function selectText() { 103 | // if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) { 104 | // // ios 105 | // let range, 106 | // selection; 107 | // range = document.createRange(); 108 | // range.selectNodeContents(textArea); 109 | // selection = window.getSelection(); 110 | // selection.removeAllRanges(); 111 | // selection.addRange(range); 112 | // textArea.setSelectionRange(0, 999999); 113 | // } else { 114 | // // 其它 115 | // textArea.select(); 116 | // } 117 | // } 118 | // /** 119 | // * 复制到剪贴板 120 | // */ 121 | // function copyToClipboard(success, fail) { 122 | // if (document.execCommand("Copy")) { 123 | // success() 124 | // } else { 125 | // fail() 126 | // } 127 | // document.body.removeChild(textArea); 128 | // } 129 | 130 | // function copy(text = '', success = fnSuccess, fail = fnFail) { 131 | // createTextArea(text); 132 | // selectText(); 133 | // copyToClipboard(success, fail); 134 | // } 135 | 136 | // export default { copy } 137 | 138 | //使用函数 139 | // var val = document.getElementById('textAreas').innerText; //获取需复制的内容 140 | // clipboard.copy(val); //调用函数执行复制 141 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: 6060, 3 | base: "/notes/", 4 | title: "Hello Nuage", 5 | description: "Never Give Up", 6 | themeConfig: { 7 | // 导航栏 8 | nav: [ 9 | { 10 | text: "首页", 11 | link: "/", 12 | }, 13 | { 14 | text: "前端", 15 | items: [ 16 | { 17 | text: "Web", 18 | link: "/web/", 19 | }, 20 | { 21 | text: "Css", 22 | link: "/css/", 23 | }, 24 | { 25 | text: "JavaScript", 26 | link: "/javascript/", 27 | }, 28 | { 29 | text: "Vue", 30 | link: "/vue/", 31 | }, 32 | { 33 | text: "Vite", 34 | link: "/vite/" 35 | }, 36 | { 37 | text: "React", 38 | link: "/react/", 39 | }, 40 | { 41 | text: "Vue-Next", 42 | link: "/vue-next/", 43 | }, 44 | { 45 | text: "Nodejs", 46 | link: "/nodejs/", 47 | }, 48 | { 49 | text: "Typescript", 50 | link: "/typescript/", 51 | }, 52 | { 53 | text: "正则", 54 | link: "/regular/", 55 | }, 56 | { 57 | text: "面试", 58 | link: "/interview/", 59 | }, 60 | ], 61 | } 62 | ], 63 | sidebar: { 64 | "/typescript/": [ 65 | { 66 | title: "Web", 67 | collapsable: false, 68 | children: ["", "base-type", "type-inference", "types-auto-create"], 69 | }, 70 | ], 71 | "/nodejs/": [ 72 | { 73 | title: "Web", 74 | collapsable: false, 75 | children: ["", "nrm", "package"], 76 | }, 77 | ], 78 | "/css/": [ 79 | { 80 | title: "CSS", 81 | collapsable: false, 82 | children: ["", "css", "sticky", "pseudo-classes-element", "grid"], 83 | }, 84 | ], 85 | "/web/": [ 86 | { 87 | title: "Web", 88 | collapsable: false, 89 | children: ["", "single-sign-in", "kuayu", "proxy", "webpack", "emoji", "svg", "svg-stroke", "http-status"], 90 | }, 91 | ], 92 | "/javascript/": [ 93 | { 94 | title: "JavaScript", 95 | collapsable: false, 96 | children: ["", "js1", "closure", "design", "canvas", "canvas_api"], 97 | }, 98 | ], 99 | "/vue/": [ 100 | { 101 | title: "Vue", 102 | collapsable: false, 103 | children: ["", "vue1", "vue2"], 104 | }, 105 | ], 106 | "/vue-next/": [ 107 | { 108 | title: "Vue-next", 109 | collapsable: false, 110 | children: ["", "vue-vite", "vue-api"], 111 | }, 112 | ], 113 | "/react/": [ 114 | { 115 | title: "React", 116 | collapsable: false, 117 | children: ["", "props", "vscode", "life", "other", "redux"], 118 | }, 119 | ], 120 | "/regular/": [ 121 | { 122 | title: "正则", 123 | collapsable: false, 124 | children: ["", "regular-special-char"], 125 | }, 126 | ], 127 | "/interview/": [ 128 | { 129 | title: "面试", 130 | collapsable: false, 131 | children: [ 132 | "", 133 | "css", 134 | "javascript", 135 | "vue", 136 | "typescript", 137 | "react", 138 | "code", 139 | "type", 140 | "http", 141 | "catch", 142 | ], 143 | }, 144 | ], 145 | }, 146 | logo: 'https://vuejs.org/images/logo.png', 147 | repo: 'vincentzyc/notes', 148 | sidebarDepth: 2, 149 | lastUpdated: "最后更新时间", 150 | }, 151 | markdown: { 152 | lineNumbers: true, 153 | }, 154 | plugins: [ 155 | "@vuepress/back-to-top", 156 | "fulltext-search", 157 | [ 158 | "one-click-copy", 159 | { 160 | copySelector: [ 161 | 'div[class*="language-"] pre', 162 | 'div[class*="aside-code"] aside', 163 | ], 164 | copyMessage: "代码拷贝成功", 165 | duration: 3000, 166 | showInMobile: false, 167 | }, 168 | ], 169 | ] 170 | } -------------------------------------------------------------------------------- /docs/web/svg.md: -------------------------------------------------------------------------------- 1 | # SVG路径 2 | 3 | ``元素可以用来定义一个路径。 4 | 5 | 首先理解一下定义路径时各种字符命令代码的意思: 6 | 7 | | 指令 | 参数 | 名称 | 描述 | 8 | |------|-----|------------|--------------------------| 9 | | M | x,y | moveto 移动到 | 移动虚拟画笔到指定的(x,y)坐标,仅移动不绘制 | 10 | | m | x,y | moveto | 同M,但使用相对坐标 | 11 | | L | x,y | lineto连直线到 | 从当前画笔所在位置绘制一条直线到指定的(x,y)坐标 | 12 | | l | x,y | lineto | 同L,但使用相对坐标 | 13 | | H | x | horizontal lineto水平连线到 | 绘制一条水平直线到参数指定的x坐标点,y坐标为画笔的y坐标 | 14 | | h | x | horizontal lineto | 同H,但使用相对坐标 | 15 | | V | y | vertical lineto垂直连线到 | 从当前位置绘制一条垂直直线到参数指定的y坐标 | 16 | | v | y | vertical lineto | 同V,但使用相对坐标 | 17 | | C | x1,y1 x2,y2 x,y |curveto三次方贝塞尔曲线 | 从当前画笔位置绘制一条三次贝兹曲线到参数(x,y)指定的坐标。x1,y1和x2,y2是曲线的开始和结束控制点,用于控制曲线的弧度 | 18 | | c | x1,y1 x2,y2 x,y | curveto | 同C,但使用相对坐标 | 19 | | S |x2,y2 x,y|shorthand / 平滑三次方贝塞尔曲线|从当前画笔位置绘制一条三次贝塞尔曲线到参数(x,y)指定的坐标。x2,y2是结束控制点。开始控制点和前一条曲线的结束控制点相同| 20 | | s |x2,y2 x,y | shorthand / 平滑三次方贝塞尔曲线 | 同S,但使用相对坐标 | 21 | | Q | x1,y1 x,y | 二次方贝塞尔曲线 | 从当前画笔位置绘制一条二次方贝塞尔曲线到参数(x,y)指定的坐标。x1,y1是控制点,用于控制曲线的弧度 | 22 | | q | x1,y1 x,y | 二次方贝塞尔曲线 | 同Q,但使用相对坐标 | 23 | | T | x,y | 平滑的二次贝塞尔曲线 | 从当前画笔位置绘制一条二次贝塞尔曲线到参数(x,y)指定的坐标。控制点被假定为最后一次使用的控制点 | 24 | | t | x,y | 平滑的二次贝塞尔曲线 | 同T,但使用相对坐标 | 25 | | A | rx,ry x-axis-rotation large-arc-flag,sweepflag x,y | 椭圆弧线 | 从当前画笔位置开始绘制一条椭圆弧线到(x,y)指定的坐标。rx和ry分别为椭圆弧线水平和垂直方向上的半径。x-axis-rotation指定弧线绕x轴旋转的度数,它只在rx和ry的值不相同是有效果。large-arc-flag是大小弧标志位,取值0或1,0表示弧线小于180度,1表示弧线大于180度。sweep-flag用于决定弧线绘制的方向,取值0或1,0表示弧线逆时针旋转,1表示顺时针旋转 | 26 | | a | rx,ry x-axis-rotation large-arc-flag,sweepflag x,y | 椭圆弧线 | 同A,但使用相对坐标 | 27 | | Z |无 | 闭合路径 | 从结束点绘制一条直线到开始点,闭合路径 | 28 | | Z |无 | 闭合路径 | 同Z | 29 | 30 | **注意:** 所有的这些字符命令都可以用大小写两种形式。大小表示绝对位置,而小写表示相对位置。 31 | 32 | ### 例 1 33 | 下面的例子里定义了一个路径,从点 150,0 开始,连直线到点 75,200,然后连直线到点 225,200,最后闭合这个路径,连直线回到点 150,0: 34 | 35 | 36 | 37 | 38 | 39 | 上面的例子使用了下面的SVG代码: 40 | 41 | ```html 42 | 43 | 44 | 45 | ``` 46 | 47 | ### 例 2 48 | 贝塞尔曲线是一种非常顺滑的曲线。通常,用户需要提供两个端点和一个或两个控制点。使用一个控制点的贝塞尔曲线叫做二次方贝塞尔曲线,使用两个控制点的贝塞尔曲线叫做三次方贝塞尔曲线。 49 | 50 | 下面的例子里使用了二次方贝塞尔曲线,其中 A 和 C 分别是两个端点,B是控制点: 51 | 52 | 53 | 55 | 57 | 59 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 70 | A 71 | B 72 | C 73 | 74 | 75 | 76 | 上面的例子里使用了下面的SVG代码: 77 | ```html 78 | 79 | 81 | 83 | 85 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 96 | A 97 | B 98 | C 99 | 100 | 101 | ``` 102 | 103 | ### 例3 104 | 105 | 106 | 108 | 109 | 110 | 111 | ```html 112 | 113 | 114 | 116 | 117 | 118 | ``` 119 | 120 | -------------------------------------------------------------------------------- /docs/regular/regular-special-char.md: -------------------------------------------------------------------------------- 1 | # 正则表达式特殊字符 2 | 3 | | 字符 | 描述 | 4 | |-------|--------------------------------------------------------------------------------------------------| 5 | | \ | 将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转义符。例如,“n"匹配字符"n"。"\n"匹配一个换行符。串行"\\"匹配"\"而"\("则匹配"("。 | 6 | | ^ | 匹配输入字符串的开始位置。如果设置了RegExp对象的Multiline属性,^也匹配“\n"或"\r"之后的位置。 | 7 | | $ | 匹配输入字符串的结束位置。如果设置了RegExp对象的Multiline属性,$也匹配“\n"或"\r"之前的位置。 | 8 | | * | 匹配前面的子表达式零次或多次。例如,zo*能匹配“z"以及"zoo"。*等价于{0,}。 | 9 | | + | 匹配前面的子表达式一次或多次。例如,“zo+"能匹配"zo"以及"zoo",但不能匹配"z"。+等价于{1,}。 | 10 | | ? | 匹配前面的子表达式零次或一次。例如,“do(es)?"可以匹配"does"或"does"中的"do"。?等价于{0,1}。 | 11 | | {n} | n是一个非负整数。匹配确定的n次。例如,“o{2}"不能匹配"Bob"中的"o",但是能匹配"food"中的两个o。 | 12 | | {n,} | n是一个非负整数。至少匹配n次。例如,“o{2,}"不能匹配"Bob"中的"o",但能匹配"foooood"中的所有o。"o{1,}"等价于"o+"。"o{0,}"则等价于"o*"。 | 13 | | {n,m} | m和n均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。例如,“o{1,3}"将匹配"fooooood"中的前三个o。"o{0,1}"等价于"o?"。请注意在逗号和两个数之间不能有空格。 | 14 | | ? | 当该字符紧跟在任何一个其他限制符(*,+,?,{n},{n,},{n,m})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串“oooo","o+?"将匹配单个"o",而"o+"将匹配所有"o"。 15 | | . | 匹配除“\n"之外的任何单个字符。要匹配包括"\n"在内的任何字符,请使用像"(. | \n)"的模式。 | | | 16 | | (pattern) | 匹配pattern并获取这一匹配。所获取的匹配可以从产生的Matches集合得到,在VBScript中使用SubMatches集合,在JScript中则使用$0…$9属性。要匹配圆括号字符,请使用\\(或\ )。 | 17 | | (?:pattern) | 匹配pattern但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用或字符“( \| )"来组合一个模式的各个部分是很有用。例如"industr(?:y \| ies)"就是一个比"industry \| industries"更简略的表达式。 | 18 | | (?=pattern) | 正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,“Windows(?=95 \| 98 \| NT\| 2000)"能匹配"Windows2000"中的"Windows",但不能匹配"Windows3.1"中的"Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。 | 19 | | (?!pattern) | 正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如“Windows(?!95\| 98\| NT \| 2000)"能匹配"Windows3.1"中的"Windows",但不能匹配"Windows2000"中的"Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始 | 20 | | (?<=pattern) | 反向肯定预查,与正向肯定预查类拟,只是方向相反。例如,“(?<=95\| 98\| NT\| 2000)Windows"能匹配"2000Windows"中的"Windows",但不能匹配"3.1Windows"中的"Windows"。 | 21 | | (? { 12 | const imageSrc = URL.createObjectURL(imageFile) 13 | const image = new Image() 14 | const { name, size, type } = imageFile 15 | image.src = imageSrc 16 | return new Promise((resolve, reject) => { 17 | image.addEventListener('error', error => { 18 | reject(error) 19 | }) 20 | image.addEventListener('load', () => { 21 | resolve({ 22 | width: image.width, 23 | height: image.height, 24 | name, 25 | size, 26 | type 27 | }) 28 | }) 29 | }) 30 | } 31 | 32 | export interface TypeAllVideoInfo { 33 | saturation: number; 34 | video: HTMLVideoElement; 35 | width: number; 36 | height: number; 37 | duration: number; 38 | name: string; 39 | size: number; 40 | type: string; 41 | posterFile: TypeFileInfo; 42 | sizeType?: string, 43 | fileMd5?:string 44 | } 45 | 46 | 47 | interface TypeVideoInfo { 48 | video: HTMLVideoElement, 49 | width: number, 50 | height: number, 51 | duration: number 52 | } 53 | 54 | interface TypeRGBA { 55 | r: number, 56 | g: number, 57 | b: number, 58 | a?: number 59 | } 60 | 61 | interface TypePosterVideo extends TypeVideoInfo { 62 | posterUrl: string, 63 | saturation: number 64 | } 65 | 66 | export interface TypeFileInfo extends File { 67 | fileInfo: TypeAllVideoInfo | TypeImageInfo; 68 | thumbnailUrl?: string 69 | } 70 | 71 | // 获取视频基本信息 72 | function getVideoBasicInfo(videoSrc: string): Promise { 73 | return new Promise((resolve, reject) => { 74 | const video = document.createElement('video') 75 | video.src = videoSrc 76 | // 视频一定要添加预加载 77 | video.preload = 'auto' 78 | // 视频一定要同源或者必须允许跨域 79 | video.crossOrigin = 'Anonymous' 80 | // 监听:异常 81 | video.addEventListener('error', error => { 82 | reject(error) 83 | }) 84 | // 监听:加载完成基本信息,设置要播放的时长 85 | video.addEventListener('loadedmetadata', () => { 86 | const videoInfo = { 87 | video, 88 | width: video.videoWidth, 89 | height: video.videoHeight, 90 | duration: video.duration 91 | } 92 | resolve(videoInfo) 93 | }) 94 | }) 95 | } 96 | 97 | // 将获取到的视频信息,转化为图片地址 98 | function getVideoPosterInfo(videoInfo: TypeVideoInfo): Promise { 99 | return new Promise(resolve => { 100 | const { video, width, height } = videoInfo 101 | video.addEventListener('canplay', () => { 102 | const canvas = document.createElement('canvas') 103 | canvas.width = width 104 | canvas.height = height 105 | const ctx = canvas.getContext('2d') 106 | if (ctx) { 107 | ctx.drawImage(video, 0, 0, width, height) 108 | const saturation = getImageSaturation(canvas) 109 | const posterUrl = canvas.toDataURL('image/png') 110 | resolve({ posterUrl, saturation, ...videoInfo }) 111 | } 112 | }) 113 | }) 114 | } 115 | // 获取一个图片的平均饱和度 116 | function getImageSaturation(canvas: HTMLCanvasElement) { 117 | const ctx = canvas.getContext('2d') 118 | const uint8ClampedArray = ctx ? ctx.getImageData(0, 0, canvas.width, canvas.height).data : null 119 | if (uint8ClampedArray) { 120 | const rgbaList = binary2rgba(uint8ClampedArray) 121 | const hslList = rgbaList.map(item => { 122 | return rgb2hsl(item.r, item.g, item.b) 123 | }) 124 | const avarageSaturation = hslList.reduce((total, curr) => total + curr.s, 0) / hslList.length 125 | return avarageSaturation 126 | } else { 127 | return 0 128 | } 129 | } 130 | 131 | function rgb2hsl(r: number, g: number, b: number) { 132 | r = r / 255; 133 | g = g / 255; 134 | b = b / 255; 135 | 136 | const min = Math.min(r, g, b); 137 | const max = Math.max(r, g, b); 138 | const difference = max - min; 139 | let l = (min + max) / 2, h = 0, s = 0; 140 | if (max == min) { 141 | h = 0; 142 | s = 0; 143 | } else { 144 | s = l > 0.5 ? difference / (2.0 - max - min) : difference / (max + min); 145 | switch (max) { 146 | case r: h = (g - b) / difference + (g < b ? 6 : 0); break; 147 | case g: h = 2.0 + (b - r) / difference; break; 148 | case b: h = 4.0 + (r - g) / difference; break; 149 | } 150 | h = Math.round(h * 60); 151 | } 152 | s = Math.round(s * 100);//转换成百分比的形式 153 | l = Math.round(l * 100); 154 | return { h, s, l }; 155 | } 156 | 157 | function binary2rgba(uint8ClampedArray: Uint8ClampedArray) { 158 | const rgbaList: TypeRGBA[] = [] 159 | for (let i = 0; i < uint8ClampedArray.length; i++) { 160 | if (i % 4 === 0) { 161 | rgbaList.push({ r: uint8ClampedArray[i], g: 0, b: 0 }) 162 | continue 163 | } 164 | const currentRgba = rgbaList[rgbaList.length - 1] 165 | if (i % 4 === 1) { 166 | currentRgba.g = uint8ClampedArray[i] 167 | continue 168 | } 169 | if (i % 4 === 2) { 170 | currentRgba.b = uint8ClampedArray[i] 171 | continue 172 | } 173 | if (i % 4 === 3) { 174 | currentRgba.a = uint8ClampedArray[i] 175 | continue 176 | } 177 | } 178 | return rgbaList 179 | } 180 | 181 | // 根据视频地址与播放时长获取图片信息与图片平均饱和度 182 | async function getVideoPosterByFrame(videoSrc: string, targetTime: number) { 183 | const videoInfo = await getVideoBasicInfo(videoSrc) 184 | const { video } = videoInfo 185 | video.currentTime = targetTime 186 | return await getVideoPosterInfo(videoInfo) 187 | } 188 | 189 | function dataURLtoFile(videoUrl: string, filename: string) { 190 | const arr = videoUrl.split(',') 191 | const mime = arr[0] ? arr[0].match(/:(.*?);/)?.[1] : '' 192 | const bstr = window.atob(arr[1]) 193 | let n = bstr.length 194 | const u8arr = new Uint8Array(n); 195 | while (n--) { 196 | u8arr[n] = bstr.charCodeAt(n); 197 | } 198 | const typeSuffix = mime ? mime.split('/')[1] : 'png' 199 | return new File([u8arr], filename + '.' + typeSuffix, { type: mime }); 200 | } 201 | 202 | export async function getBestPoster(videoSrc: string, targetSaturation = 0) { 203 | for (let i = 1; i <= 3; i++) { 204 | const newVideoInfo = await getVideoPosterByFrame(videoSrc, i * 0.5) 205 | if (newVideoInfo.saturation > targetSaturation) { 206 | return newVideoInfo 207 | } 208 | } 209 | return await getVideoPosterByFrame(videoSrc, 1) 210 | } 211 | 212 | export async function getPoster(videoFile: File, targetSaturation = 0): Promise { 213 | const videoSrc = URL.createObjectURL(videoFile) 214 | const fileInfo = await getBestPoster(videoSrc, targetSaturation) 215 | const { name, size, type } = videoFile 216 | const lastIndex = name.lastIndexOf("."); 217 | let filename = name 218 | if (lastIndex > -1) filename = name.substring(0, lastIndex); 219 | const { posterUrl, ...newFileInfo } = fileInfo 220 | const posterFile = dataURLtoFile(posterUrl, filename) as TypeFileInfo 221 | const imageInfo = await getImageInfo(posterFile) 222 | posterFile.fileInfo = imageInfo 223 | return { 224 | name, 225 | size, 226 | type, 227 | posterFile, 228 | ...newFileInfo 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /css/mobile-base.css: -------------------------------------------------------------------------------- 1 | /*reset css*/ 2 | @charset "utf-8"; 3 | *, *::before, *::after { 4 | -webkit-tap-highlight-color: transparent; 5 | -ms-tap-highlight-color: transparent; 6 | -moz-box-sizing: border-box; 7 | -webkit-box-sizing: border-box; 8 | box-sizing: border-box; 9 | } 10 | 11 | ::-webkit-scrollbar { 12 | display: none; 13 | } 14 | /*reset css end*/ 15 | a { 16 | text-decoration: none; 17 | } 18 | li { 19 | list-style: none; 20 | } 21 | h1, h2, h3, h4, h5, h6 { 22 | font-size: 100%; 23 | font-weight: 500 24 | } 25 | small, 26 | .small { 27 | font-size: 80%; 28 | } 29 | .large { 30 | font-size: 150%; 31 | } 32 | strong, 33 | .strong { 34 | font-weight: bolder; 35 | } 36 | .thin { 37 | font-weight: lighter; 38 | } 39 | .bold{ 40 | font-weight: bold; 41 | } 42 | .container { 43 | position: relative; 44 | } 45 | .row { 46 | position: relative; 47 | width: 100%; 48 | } 49 | .col-1 { 50 | width: 8.33333333%; 51 | } 52 | .col-2 { 53 | width: 16.66666667%; 54 | } 55 | .col-3 { 56 | width: 25%; 57 | } 58 | .col-4 { 59 | width: 33.33333333%; 60 | } 61 | .col-5 { 62 | width: 41.66666667%; 63 | } 64 | .col-6 { 65 | width: 50%; 66 | } 67 | .col-7 { 68 | width: 58.33333333%; 69 | } 70 | .col-8 { 71 | width: 66.66666667%; 72 | } 73 | .col-9 { 74 | width: 75%; 75 | } 76 | .col-10 { 77 | width: 83.33333333%; 78 | } 79 | .col-11 { 80 | width: 91.66666667%; 81 | } 82 | .col-12 { 83 | width: 100%; 84 | } 85 | .col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12 { 86 | float: left; 87 | } 88 | /*flex*/ 89 | 90 | .flex { 91 | display: flex !important; 92 | } 93 | .flex-inline { 94 | display: inline-flex !important; 95 | } 96 | /*flex-direction*/ 97 | 98 | .flex-row { 99 | flex-direction: row; 100 | } 101 | .flex-column { 102 | flex-direction: column; 103 | } 104 | .row-reverse { 105 | flex-direction: row-reverse; 106 | } 107 | .column-reverse { 108 | flex-direction: column-reverse; 109 | } 110 | /*flex-wrap*/ 111 | 112 | .flex-wrap { 113 | flex-wrap: wrap; 114 | } 115 | .flex-nowrap { 116 | flex-wrap: nowrap; 117 | } 118 | /*justify-content*/ 119 | 120 | .space-between { 121 | justify-content: space-between; 122 | } 123 | .justify-start { 124 | justify-content: flex-start; 125 | } 126 | .justify-end { 127 | justify-content: flex-end; 128 | } 129 | .justify-center { 130 | justify-content: center; 131 | } 132 | /*align-items*/ 133 | 134 | .stretch { 135 | align-items: stretch; 136 | } 137 | .align-start { 138 | align-items: flex-start; 139 | } 140 | .align-end { 141 | align-items: flex-end; 142 | } 143 | .align-middle { 144 | align-items: center; 145 | } 146 | .flex-center { 147 | justify-content: center; 148 | align-items: center; 149 | } 150 | /*order*/ 151 | 152 | .flex-first { 153 | order: -1; 154 | } 155 | .flex-last { 156 | order: 1; 157 | } 158 | /*flex*/ 159 | 160 | .flex-auto { 161 | flex: auto; 162 | } 163 | .flex-none { 164 | flex: none; 165 | } 166 | /*align-self*/ 167 | 168 | .selft-stretch { 169 | align-self: stretch; 170 | } 171 | .align-self-start { 172 | align-self: flex-start; 173 | } 174 | .align-self-end { 175 | align-self: flex-end; 176 | } 177 | .align-self-middle { 178 | align-self: center; 179 | } 180 | /*flex end*/ 181 | 182 | /*超出部分省略号*/ 183 | 184 | .txtover { 185 | overflow: hidden; 186 | white-space: nowrap; 187 | text-overflow: ellipsis; 188 | } 189 | .txtover-2 { 190 | overflow: hidden; 191 | text-overflow: ellipsis; 192 | display: -webkit-box; 193 | -webkit-line-clamp: 2; 194 | -webkit-box-orient: vertical; 195 | } 196 | .txtover-3 { 197 | overflow: hidden; 198 | text-overflow: ellipsis; 199 | display: -webkit-box; 200 | -webkit-line-clamp: 3; 201 | -webkit-box-orient: vertical; 202 | } 203 | /*end*/ 204 | 205 | .text-left { 206 | text-align: left; 207 | } 208 | .text-center { 209 | text-align: center; 210 | } 211 | .text-right { 212 | text-align: right; 213 | } 214 | .pull-right { 215 | float: right !important; 216 | } 217 | .pull-left { 218 | float: left !important; 219 | } 220 | .block { 221 | display: block !important; 222 | } 223 | .inline { 224 | display: inline !important; 225 | } 226 | .inline-block { 227 | display: inline-block !important; 228 | } 229 | .clearfix:before, .clearfix:after { 230 | display: table; 231 | content: ' '; 232 | } 233 | .clearfix:after { 234 | clear: both; 235 | } 236 | .relative { 237 | position: relative; 238 | } 239 | .lh16 { 240 | line-height: .32rem; 241 | } 242 | .lh18 { 243 | line-height: .36rem; 244 | } 245 | .lh20 { 246 | line-height: .4rem; 247 | } 248 | .lh22 { 249 | line-height: .44rem; 250 | } 251 | .lh24 { 252 | line-height: .48rem; 253 | } 254 | .lh26 { 255 | line-height: .52rem; 256 | } 257 | .lh28 { 258 | line-height: .56rem; 259 | } 260 | .lh30 { 261 | line-height: .6rem; 262 | } 263 | .fs10 { 264 | font-size: .2rem; 265 | } 266 | .fs11 { 267 | font-size: .22rem; 268 | } 269 | .fs12 { 270 | font-size: .24rem; 271 | } 272 | .fs13 { 273 | font-size: .26rem; 274 | } 275 | .fs14 { 276 | font-size: .28rem; 277 | } 278 | .fs15 { 279 | font-size: .3rem; 280 | } 281 | .fs16 { 282 | font-size: .32rem; 283 | } 284 | .fs17 { 285 | font-size: .34rem; 286 | } 287 | .fs18 { 288 | font-size: .36rem; 289 | } 290 | .c000 { 291 | color: #000; 292 | } 293 | .c333 { 294 | color: #333; 295 | } 296 | .c666 { 297 | color: #666; 298 | } 299 | .c999 { 300 | color: #999; 301 | } 302 | .cccc { 303 | color: #ccc; 304 | } 305 | .cfff { 306 | color: #fff; 307 | } 308 | .bg333 { 309 | background-color: #333; 310 | } 311 | .bg666 { 312 | background-color: #666; 313 | } 314 | .bg999 { 315 | background-color: #999; 316 | } 317 | .bgccc { 318 | background-color: #ccc; 319 | } 320 | .bgddd { 321 | background-color: #ddd; 322 | } 323 | .bgeee { 324 | background-color: #eee; 325 | } 326 | .bgfff { 327 | background-color: #fff; 328 | } 329 | .mg5 { 330 | margin: .1rem; 331 | } 332 | .mg10 { 333 | margin: .2rem; 334 | } 335 | .mg15 { 336 | margin: .3rem; 337 | } 338 | .mg20 { 339 | margin: .4rem; 340 | } 341 | .mg-t0 { 342 | margin-top: 0; 343 | } 344 | .mg-r0 { 345 | margin-right: 0; 346 | } 347 | .mg-b0 { 348 | margin-bottom: 0; 349 | } 350 | .mg-l0 { 351 | margin-left: 0; 352 | } 353 | .mg-t5 { 354 | margin-top: .1rem; 355 | } 356 | .mg-r5 { 357 | margin-right: .1rem; 358 | } 359 | .mg-b5 { 360 | margin-bottom: .1rem; 361 | } 362 | .mg-l5 { 363 | margin-left: .1rem; 364 | } 365 | .mg-t10 { 366 | margin-top: .2rem; 367 | } 368 | .mg-r10 { 369 | margin-right: .2rem; 370 | } 371 | .mg-b10 { 372 | margin-bottom: .2rem; 373 | } 374 | .mg-l10 { 375 | margin-left: .2rem; 376 | } 377 | .mg-t15 { 378 | margin-top: .3rem; 379 | } 380 | .mg-r15 { 381 | margin-right: .3rem; 382 | } 383 | .mg-b15 { 384 | margin-bottom: .3rem; 385 | } 386 | .mg-l15 { 387 | margin-left: .3rem; 388 | } 389 | .mg-t20 { 390 | margin-top: .4rem; 391 | } 392 | .mg-r20 { 393 | margin-right: .4rem; 394 | } 395 | .mg-b20 { 396 | margin-bottom: .4rem; 397 | } 398 | .mg-l20 { 399 | margin-left: .4rem; 400 | } 401 | .pd5 { 402 | padding: .1rem; 403 | } 404 | .pd10 { 405 | padding: .2rem; 406 | } 407 | .pd15 { 408 | padding: .3rem; 409 | } 410 | .pd20 { 411 | padding: .4rem; 412 | } 413 | .pd-t0 { 414 | padding-top: 0; 415 | } 416 | .pd-r0 { 417 | padding-right: 0; 418 | } 419 | .pd-b0 { 420 | padding-bottom: 0; 421 | } 422 | .pd-l0 { 423 | padding-left: 0; 424 | } 425 | .pd-t5 { 426 | padding-top: .1rem; 427 | } 428 | .pd-r5 { 429 | padding-right: .1rem; 430 | } 431 | .pd-b5 { 432 | padding-bottom: .1rem; 433 | } 434 | .pd-l5 { 435 | padding-left: .1rem; 436 | } 437 | .pd-t10 { 438 | padding-top: .2rem; 439 | } 440 | .pd-r10 { 441 | padding-right: .2rem; 442 | } 443 | .pd-b10 { 444 | padding-bottom: .2rem; 445 | } 446 | .pd-l10 { 447 | padding-left: .2rem; 448 | } 449 | .pd-t15 { 450 | padding-top: .3rem; 451 | } 452 | .pd-r15 { 453 | padding-right: .3rem; 454 | } 455 | .pd-b15 { 456 | padding-bottom: .3rem; 457 | } 458 | .pd-l15 { 459 | padding-left: .3rem; 460 | } 461 | .pd-t20 { 462 | padding-top: .4rem; 463 | } 464 | .pd-r20 { 465 | padding-right: .4rem; 466 | } 467 | .pd-b20 { 468 | padding-bottom: .4rem; 469 | } 470 | .pd-l20 { 471 | padding-left: .4rem; 472 | } 473 | -------------------------------------------------------------------------------- /常用正则大全(转).md: -------------------------------------------------------------------------------- 1 | # 正则大全 2 | :whale:图形界面: [https://any86.github.io/any-rule/](https://any86.github.io/any-rule/) 3 | 4 | ### 火车车次 5 | ```javascript 6 | /^[GCDZTSPKXLY1-9]\d{1,4}$/ 7 | ``` 8 | 9 | ### 手机机身码(IMEI) 10 | ```javascript 11 | /^\d{15,17}$/ 12 | ``` 13 | 14 | ### 必须带端口号的网址(或ip) 15 | ```javascript 16 | /^((ht|f)tps?:\/\/)?[\w-]+(\.[\w-]+)+:\d{1,5}\/?$/ 17 | ``` 18 | 19 | ### 网址(url,支持端口和"?+参数"和"#+参数) 20 | ```javascript 21 | /^(((ht|f)tps?):\/\/)?[\w-]+(\.[\w-]+)+([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])?$/ 22 | ``` 23 | 24 | ### 统一社会信用代码 25 | ```javascript 26 | /^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$/ 27 | ``` 28 | 29 | ### 迅雷链接 30 | ```javascript 31 | /^thunderx?:\/\/[a-zA-Z\d]+=$/ 32 | ``` 33 | 34 | ### ed2k链接(宽松匹配) 35 | ```javascript 36 | /^ed2k:\/\/\|file\|.+\|\/$/ 37 | ``` 38 | 39 | ### 磁力链接(宽松匹配) 40 | ```javascript 41 | /^magnet:\?xt=urn:btih:[0-9a-fA-F]{40,}.*$/ 42 | ``` 43 | 44 | ### 子网掩码 45 | ```javascript 46 | /^(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])(?:\.(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])){3}$/ 47 | ``` 48 | 49 | ### linux"隐藏文件"路径 50 | ```javascript 51 | /^\/(?:[^\/]+\/)*\.[^\/]*/ 52 | ``` 53 | 54 | ### linux文件夹路径 55 | ```javascript 56 | /^\/(?:[^\/]+\/)*$/ 57 | ``` 58 | 59 | ### linux文件路径 60 | ```javascript 61 | /^\/(?:[^\/]+\/)*[^\/]+$/ 62 | ``` 63 | 64 | ### window"文件夹"路径 65 | ```javascript 66 | /^[a-zA-Z]:\\(?:\w+\\?)*$/ 67 | ``` 68 | 69 | ### window下"文件"路径 70 | ```javascript 71 | /^[a-zA-Z]:\\(?:\w+\\)*\w+\.\w+$/ 72 | ``` 73 | 74 | ### 股票代码(A股) 75 | ```javascript 76 | /^(s[hz]|S[HZ])(000[\d]{3}|002[\d]{3}|300[\d]{3}|600[\d]{3}|60[\d]{4})$/ 77 | ``` 78 | 79 | ### 大于等于0, 小于等于150, 支持小数位出现5, 如145.5, 用于判断考卷分数 80 | ```javascript 81 | /^150$|^(?:\d|[1-9]\d|1[0-4]\d)(?:.5)?$/ 82 | ``` 83 | 84 | ### html注释 85 | ```javascript 86 | /^$/ 87 | ``` 88 | 89 | ### md5格式(32位) 90 | ```javascript 91 | /^([a-f\d]{32}|[A-F\d]{32})$/ 92 | ``` 93 | 94 | ### 版本号(version)格式必须为X.Y.Z 95 | ```javascript 96 | /^\d+(?:\.\d+){2}$/ 97 | ``` 98 | 99 | ### 视频(video)链接地址(视频格式可按需增删) 100 | ```javascript 101 | /^https?:\/\/(.+\/)+.+(\.(swf|avi|flv|mpg|rm|mov|wav|asf|3gp|mkv|rmvb|mp4))$/i 102 | ``` 103 | 104 | ### 图片(image)链接地址(图片格式可按需增删) 105 | ```javascript 106 | /^https?:\/\/(.+\/)+.+(\.(gif|png|jpg|jpeg|webp|svg|psd|bmp|tif))$/i 107 | ``` 108 | 109 | ### 24小时制时间(HH:mm:ss) 110 | ```javascript 111 | /^(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d$/ 112 | ``` 113 | 114 | ### 12小时制时间(hh:mm:ss) 115 | ```javascript 116 | /^(?:1[0-2]|0?[1-9]):[0-5]\d:[0-5]\d$/ 117 | ``` 118 | 119 | ### base64格式 120 | ```javascript 121 | /^\s*data:(?:[a-z]+\/[a-z0-9-+.]+(?:;[a-z-]+=[a-z0-9-]+)?)?(?:;base64)?,([a-z0-9!$&',()*+;=\-._~:@\/?%\s]*?)\s*$/i 122 | ``` 123 | 124 | ### 数字/货币金额(支持负数、千分位分隔符) 125 | ```javascript 126 | /^-?\d+(,\d{3})*(\.\d{1,2})?$/ 127 | ``` 128 | 129 | ### 数字/货币金额 (只支持正数、不支持校验千分位分隔符) 130 | ```javascript 131 | /(?:^[1-9]([0-9]+)?(?:\.[0-9]{1,2})?$)|(?:^(?:0){1}$)|(?:^[0-9]\.[0-9](?:[0-9])?$)/ 132 | ``` 133 | 134 | ### 银行卡号(10到30位, 覆盖对公/私账户, 参考[微信支付](https://pay.weixin.qq.com/wiki/doc/api/xiaowei.php?chapter=22_1)) 135 | ```javascript 136 | /^[1-9]\d{9,29}$/ 137 | ``` 138 | 139 | ### 中文姓名 140 | ```javascript 141 | /^(?:[\u4e00-\u9fa5·]{2,16})$/ 142 | ``` 143 | 144 | ### 英文姓名 145 | ```javascript 146 | /(^[a-zA-Z]{1}[a-zA-Z\s]{0,20}[a-zA-Z]{1}$)/ 147 | ``` 148 | 149 | ### 车牌号(新能源) 150 | ```javascript 151 | /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领]{1}[A-HJ-NP-Z]{1}(([0-9]{5}[DF])|([DF][A-HJ-NP-Z0-9][0-9]{4}))$/ 152 | ``` 153 | 154 | ### 车牌号(非新能源) 155 | ```javascript 156 | /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领]{1}[A-HJ-NP-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/ 157 | ``` 158 | 159 | ### 车牌号(新能源+非新能源) 160 | ```javascript 161 | /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领]{1}[A-HJ-NP-Z]{1}(?:(([0-9]{5}[DF])|([DF][A-HJ-NP-Z0-9][0-9]{4}))|[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1})$/ 162 | ``` 163 | 164 | ### 手机号(mobile phone)中国(严谨), 根据工信部2019年最新公布的手机号段 165 | ```javascript 166 | /^(?:(?:\+|00)86)?1(?:(?:3[\d])|(?:4[5-7|9])|(?:5[0-3|5-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[1|8|9]))\d{8}$/ 167 | ``` 168 | 169 | ### 手机号(mobile phone)中国(宽松), 只要是13,14,15,16,17,18,19开头即可 170 | ```javascript 171 | /^(?:(?:\+|00)86)?1[3-9]\d{9}$/ 172 | ``` 173 | 174 | ### 手机号(mobile phone)中国(最宽松), 只要是1开头即可, 如果你的手机号是用来接收短信, 优先建议选择这一条 175 | ```javascript 176 | /^(?:(?:\+|00)86)?1\d{10}$/ 177 | ``` 178 | 179 | ### date(日期) 180 | ```javascript 181 | /^\d{4}(-)(1[0-2]|0?\d)\1([0-2]\d|\d|30|31)$/ 182 | ``` 183 | 184 | ### email(邮箱) 185 | ```javascript 186 | /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ 187 | ``` 188 | 189 | ### 座机(tel phone)电话(国内),如: 0341-86091234 190 | ```javascript 191 | /^\d{3}-\d{8}$|^\d{4}-\d{7,8}$/ 192 | ``` 193 | 194 | ### 身份证号(1代,15位数字) 195 | ```javascript 196 | /^[1-9]\d{7}(?:0\d|10|11|12)(?:0[1-9]|[1-2][\d]|30|31)\d{3}$/ 197 | ``` 198 | 199 | ### 身份证号(2代,18位数字),最后一位是校验位,可能为数字或字符X 200 | ```javascript 201 | /^[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|10|11|12)(?:0[1-9]|[1-2]\d|30|31)\d{3}[\dXx]$/ 202 | ``` 203 | 204 | ### 身份证号, 支持1/2代(15位/18位数字) 205 | ```javascript 206 | /(^\d{8}(0\d|10|11|12)([0-2]\d|30|31)\d{3}$)|(^\d{6}(18|19|20)\d{2}(0[1-9]|10|11|12)([0-2]\d|30|31)\d{3}(\d|X|x)$)/ 207 | ``` 208 | 209 | ### 护照(包含香港、澳门) 210 | ```javascript 211 | /(^[EeKkGgDdSsPpHh]\d{8}$)|(^(([Ee][a-fA-F])|([DdSsPp][Ee])|([Kk][Jj])|([Mm][Aa])|(1[45]))\d{7}$)/ 212 | ``` 213 | 214 | ### 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线组合 215 | ```javascript 216 | /^[a-zA-Z]\w{4,15}$/ 217 | ``` 218 | 219 | ### 中文/汉字 220 | ```javascript 221 | /^(?:[\u3400-\u4DB5\u4E00-\u9FEA\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]|[\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0])+$/ 222 | ``` 223 | 224 | ### 小数 225 | ```javascript 226 | /^\d+\.\d+$/ 227 | ``` 228 | 229 | ### 数字 230 | ```javascript 231 | /^\d{1,}$/ 232 | ``` 233 | 234 | ### html标签(宽松匹配) 235 | ```javascript 236 | /<(\w+)[^>]*>(.*?<\/\1>)?/ 237 | ``` 238 | 239 | ### qq号格式正确 240 | ```javascript 241 | /^[1-9][0-9]{4,10}$/ 242 | ``` 243 | 244 | ### 数字和字母组成 245 | ```javascript 246 | /^[A-Za-z0-9]+$/ 247 | ``` 248 | 249 | ### 英文字母 250 | ```javascript 251 | /^[a-zA-Z]+$/ 252 | ``` 253 | 254 | ### 小写英文字母组成 255 | ```javascript 256 | /^[a-z]+$/ 257 | ``` 258 | 259 | ### 大写英文字母 260 | ```javascript 261 | /^[A-Z]+$/ 262 | ``` 263 | 264 | ### 密码强度校验,最少6位,包括至少1个大写字母,1个小写字母,1个数字,1个特殊字符 265 | ```javascript 266 | /^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Z])(?=\S*[a-z])(?=\S*[!@#$%^&*? ])\S*$/ 267 | ``` 268 | 269 | ### 用户名校验,4到16位(字母,数字,下划线,减号) 270 | ```javascript 271 | /^[a-zA-Z0-9_-]{4,16}$/ 272 | ``` 273 | 274 | ### ip-v4 275 | ```javascript 276 | /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/ 277 | ``` 278 | 279 | ### ip-v6 280 | ```javascript 281 | /^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/i 282 | ``` 283 | 284 | ### 16进制颜色 285 | ```javascript 286 | /^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/ 287 | ``` 288 | 289 | ### 微信号(wx),6至20位,以字母开头,字母,数字,减号,下划线 290 | ```javascript 291 | /^[a-zA-Z][-_a-zA-Z0-9]{5,19}$/ 292 | ``` 293 | 294 | ### 邮政编码(中国) 295 | ```javascript 296 | /^(0[1-7]|1[0-356]|2[0-7]|3[0-6]|4[0-7]|5[1-7]|6[1-7]|7[0-5]|8[013-6])\d{4}$/ 297 | ``` 298 | 299 | ### 中文和数字 300 | ```javascript 301 | /^((?:[\u3400-\u4DB5\u4E00-\u9FEA\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]|[\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0])|(\d))+$/ 302 | ``` 303 | 304 | ### 不能包含字母 305 | ```javascript 306 | /^[^A-Za-z]*$/ 307 | ``` 308 | 309 | ### java包名 310 | ```javascript 311 | /^([a-zA-Z_][a-zA-Z0-9_]*)+([.][a-zA-Z_][a-zA-Z0-9_]*)+$/ 312 | ``` 313 | 314 | ### mac地址 315 | ```javascript 316 | /^((([a-f0-9]{2}:){5})|(([a-f0-9]{2}-){5}))[a-f0-9]{2}$/i 317 | ``` 318 | 319 | ### 匹配连续重复的字符 320 | ```javascript 321 | /(.)\1+/ 322 | ``` 323 | 324 | ### 数字和英文字母组成,并且同时含有数字和英文字母 325 | ```javascript 326 | /^(?=.*[a-zA-Z])(?=.*\d).+$/ 327 | ``` 328 | -------------------------------------------------------------------------------- /css/pc-base.css: -------------------------------------------------------------------------------- 1 | /*reset css*/ 2 | 3 | *, 4 | *::before, 5 | *::after { 6 | -webkit-tap-highlight-color: transparent; 7 | -ms-tap-highlight-color: transparent; 8 | -moz-box-sizing: border-box; 9 | -webkit-box-sizing: border-box; 10 | box-sizing: border-box; 11 | } 12 | html, 13 | body, 14 | div, 15 | span, 16 | applet, 17 | object, 18 | iframe, 19 | h1, 20 | h2, 21 | h3, 22 | h4, 23 | h5, 24 | h6, 25 | p, 26 | blockquote, 27 | pre, 28 | a, 29 | abbr, 30 | acronym, 31 | address, 32 | big, 33 | cite, 34 | code, 35 | del, 36 | dfn, 37 | em, 38 | img, 39 | ins, 40 | kbd, 41 | q, 42 | s, 43 | samp, 44 | small, 45 | strike, 46 | strong, 47 | sub, 48 | sup, 49 | tt, 50 | var, 51 | b, 52 | u, 53 | i, 54 | dl, 55 | dt, 56 | dd, 57 | ol, 58 | ul, 59 | li, 60 | fieldset, 61 | form, 62 | label, 63 | legend, 64 | table, 65 | caption, 66 | tbody, 67 | tfoot, 68 | thead, 69 | tr, 70 | th, 71 | td, 72 | article, 73 | aside, 74 | canvas, 75 | details, 76 | embed, 77 | figure, 78 | figcaption, 79 | footer, 80 | header, 81 | menu, 82 | nav, 83 | output, 84 | section, 85 | summary, 86 | time, 87 | mark, 88 | audio, 89 | video, 90 | input { 91 | margin: 0; 92 | padding: 0; 93 | border: 0; 94 | font-weight: normal; 95 | vertical-align: baseline; 96 | } 97 | /*reset css end*/ 98 | 99 | /*flex*/ 100 | 101 | .flex { 102 | display: flex !important; 103 | } 104 | .flex-inline { 105 | display: inline-flex !important; 106 | } 107 | /*flex-direction*/ 108 | 109 | .flex-row { 110 | flex-direction: row; 111 | } 112 | .flex-column { 113 | flex-direction: column; 114 | } 115 | .row-reverse { 116 | flex-direction: row-reverse; 117 | } 118 | .column-reverse { 119 | flex-direction: column-reverse; 120 | } 121 | /*flex-wrap*/ 122 | 123 | .flex-wrap { 124 | flex-wrap: wrap; 125 | } 126 | .flex-nowrap { 127 | flex-wrap: nowrap; 128 | } 129 | /*justify-content*/ 130 | 131 | .space-around { 132 | justify-content: space-around; 133 | } 134 | .space-between { 135 | justify-content: space-between; 136 | } 137 | .justify-start { 138 | justify-content: flex-start; 139 | } 140 | .justify-end { 141 | justify-content: flex-end; 142 | } 143 | .justify-center { 144 | justify-content: center; 145 | } 146 | /*align-items*/ 147 | 148 | .stretch { 149 | align-items: stretch; 150 | } 151 | .align-start { 152 | align-items: flex-start; 153 | } 154 | .align-end { 155 | align-items: flex-end; 156 | } 157 | .align-middle { 158 | align-items: center; 159 | } 160 | /*同时获得 .justify-center 和 .align-middle 样式*/ 161 | 162 | .flex-center { 163 | justify-content: center; 164 | align-items: center; 165 | } 166 | /*以下辅助类应用于弹性盒子元素*/ 167 | 168 | /*order*/ 169 | 170 | .flex-first { 171 | order: -1; 172 | } 173 | .flex-last { 174 | order: 1; 175 | } 176 | /*flex*/ 177 | 178 | .flex-auto { 179 | flex: auto; 180 | } 181 | .flex-none { 182 | flex: none; 183 | } 184 | /*align-self*/ 185 | 186 | .selft-stretch { 187 | align-self: stretch; 188 | } 189 | .align-self-start { 190 | align-self: flex-start; 191 | } 192 | .align-self-end { 193 | align-self: flex-end; 194 | } 195 | .align-self-middle { 196 | align-self: center; 197 | } 198 | /*flex end*/ 199 | 200 | /*custom by zyc*/ 201 | 202 | html, 203 | body, 204 | #app { 205 | position: relative; 206 | width: 100%; 207 | height: 100%; 208 | overflow: hidden; 209 | } 210 | body { 211 | font-family: "Helvetica Neue", Helvetica, "microsoft yahei", arial, STHeiTi, sans-serif; 212 | } 213 | a { 214 | text-decoration: none; 215 | } 216 | li { 217 | list-style: none; 218 | } 219 | small, 220 | .small { 221 | font-size: 80%; 222 | } 223 | .large { 224 | font-size: 150%; 225 | } 226 | strong, 227 | .strong { 228 | font-weight: bolder; 229 | } 230 | .thin { 231 | font-weight: lighter; 232 | } 233 | .bold { 234 | font-weight: bold; 235 | } 236 | .row { 237 | position: relative; 238 | width: 100%; 239 | } 240 | .col-1 { 241 | width: 8.33333333%; 242 | } 243 | .col-2 { 244 | width: 16.66666667%; 245 | } 246 | .col-2-4 { 247 | width: 20%; 248 | } 249 | .col-3 { 250 | width: 25%; 251 | } 252 | .col-4 { 253 | width: 33.33333333%; 254 | } 255 | .col-5 { 256 | width: 41.66666667%; 257 | } 258 | .col-6 { 259 | width: 50%; 260 | } 261 | .col-7 { 262 | width: 58.33333333%; 263 | } 264 | .col-8 { 265 | width: 66.66666667%; 266 | } 267 | .col-9 { 268 | width: 75%; 269 | } 270 | .col-10 { 271 | width: 83.33333333%; 272 | } 273 | .col-11 { 274 | width: 91.66666667%; 275 | } 276 | .col-12 { 277 | width: 100%; 278 | } 279 | .col-1, 280 | .col-2, 281 | .col-2-4, 282 | .col-3, 283 | .col-4, 284 | .col-5, 285 | .col-6, 286 | .col-7, 287 | .col-8, 288 | .col-9, 289 | .col-10, 290 | .col-11, 291 | .col-12 { 292 | float: left; 293 | } 294 | .text-left { 295 | text-align: left; 296 | } 297 | .text-center { 298 | text-align: center; 299 | } 300 | .text-right { 301 | text-align: right; 302 | } 303 | .pull-right { 304 | float: right !important; 305 | } 306 | .pull-left { 307 | float: left !important; 308 | } 309 | .block { 310 | display: block !important; 311 | } 312 | .inline { 313 | display: inline !important; 314 | } 315 | .inline-block { 316 | display: inline-block !important; 317 | } 318 | .clearfix:before, 319 | .clearfix:after { 320 | display: table; 321 | content: " "; 322 | } 323 | .clearfix:after { 324 | clear: both; 325 | } 326 | .relative { 327 | position: relative; 328 | } 329 | .lh20 { 330 | line-height: 20px; 331 | } 332 | .lh22 { 333 | line-height: 22px; 334 | } 335 | .lh24 { 336 | line-height: 24px; 337 | } 338 | .lh26 { 339 | line-height: 26px; 340 | } 341 | .lh28 { 342 | line-height: 28px; 343 | } 344 | .lh30 { 345 | line-height: 30px; 346 | } 347 | .fs10 { 348 | font-size: 10px; 349 | } 350 | .fs11 { 351 | font-size: 11px; 352 | } 353 | .fs12 { 354 | font-size: 12px; 355 | } 356 | .fs13 { 357 | font-size: 13px; 358 | } 359 | .fs14 { 360 | font-size: 14px; 361 | } 362 | .fs15 { 363 | font-size: 15px; 364 | } 365 | .fs16 { 366 | font-size: 16px; 367 | } 368 | .fs17 { 369 | font-size: 17px; 370 | } 371 | .fs18 { 372 | font-size: 18px; 373 | } 374 | .fs20 { 375 | font-size: 19px; 376 | } 377 | .fs20 { 378 | font-size: 20px; 379 | } 380 | .c333 { 381 | color: #333; 382 | } 383 | .c666 { 384 | color: #666; 385 | } 386 | .c999 { 387 | color: #999; 388 | } 389 | .cccc { 390 | color: #ccc; 391 | } 392 | .cfff { 393 | color: #fff; 394 | } 395 | .bg333 { 396 | background-color: #333; 397 | } 398 | .bg666 { 399 | background-color: #666; 400 | } 401 | .bg999 { 402 | background-color: #999; 403 | } 404 | .bgccc { 405 | background-color: #ccc; 406 | } 407 | .bgddd { 408 | background-color: #ddd; 409 | } 410 | .bgeee { 411 | background-color: #eee; 412 | } 413 | .bgfff { 414 | background-color: #fff; 415 | } 416 | .mg5 { 417 | margin: 5px; 418 | } 419 | .mg10 { 420 | margin: 10px; 421 | } 422 | .mg15 { 423 | margin: 15px; 424 | } 425 | .mg20 { 426 | margin: 20px; 427 | } 428 | .mg-t0 { 429 | margin-top: 0; 430 | } 431 | .mg-r0 { 432 | margin-right: 0; 433 | } 434 | .mg-b0 { 435 | margin-bottom: 0; 436 | } 437 | .mg-l0 { 438 | margin-left: 0; 439 | } 440 | .mg-t5 { 441 | margin-top: 5px; 442 | } 443 | .mg-r5 { 444 | margin-right: 5px; 445 | } 446 | .mg-b5 { 447 | margin-bottom: 5px; 448 | } 449 | .mg-l5 { 450 | margin-left: 5px; 451 | } 452 | .mg-t10 { 453 | margin-top: 10px; 454 | } 455 | .mg-r10 { 456 | margin-right: 10px; 457 | } 458 | .mg-b10 { 459 | margin-bottom: 10px; 460 | } 461 | .mg-l10 { 462 | margin-left: 10px; 463 | } 464 | .mg-t15 { 465 | margin-top: 15px; 466 | } 467 | .mg-r15 { 468 | margin-right: 15px; 469 | } 470 | .mg-b15 { 471 | margin-bottom: 15px; 472 | } 473 | .mg-l15 { 474 | margin-left: 15px; 475 | } 476 | .mg-t20 { 477 | margin-top: 20px; 478 | } 479 | .mg-r20 { 480 | margin-right: 20px; 481 | } 482 | .mg-b20 { 483 | margin-bottom: 20px; 484 | } 485 | .mg-l20 { 486 | margin-left: 20px; 487 | } 488 | .pd5 { 489 | padding: 5px; 490 | } 491 | .pd10 { 492 | padding: 10px; 493 | } 494 | .pd15 { 495 | padding: 15px; 496 | } 497 | .pd20 { 498 | padding: 20px; 499 | } 500 | .pd-t0 { 501 | padding-top: 0; 502 | } 503 | .pd-r0 { 504 | padding-right: 0; 505 | } 506 | .pd-b0 { 507 | padding-bottom: 0; 508 | } 509 | .pd-l0 { 510 | padding-left: 0; 511 | } 512 | .pd-t5 { 513 | padding-top: 5px; 514 | } 515 | .pd-r5 { 516 | padding-right: 5px; 517 | } 518 | .pd-b5 { 519 | padding-bottom: 5px; 520 | } 521 | .pd-l5 { 522 | padding-left: 5px; 523 | } 524 | .pd-t10 { 525 | padding-top: 10px; 526 | } 527 | .pd-r10 { 528 | padding-right: 10px; 529 | } 530 | .pd-b10 { 531 | padding-bottom: 10px; 532 | } 533 | .pd-l10 { 534 | padding-left: 10px; 535 | } 536 | .pd-t15 { 537 | padding-top: 15px; 538 | } 539 | .pd-r15 { 540 | padding-right: 15px; 541 | } 542 | .pd-b15 { 543 | padding-bottom: 15px; 544 | } 545 | .pd-l15 { 546 | padding-left: 15px; 547 | } 548 | .pd-t20 { 549 | padding-top: 20px; 550 | } 551 | .pd-r20 { 552 | padding-right: 20px; 553 | } 554 | .pd-b20 { 555 | padding-bottom: 20px; 556 | } 557 | .pd-l20 { 558 | padding-left: 20px; 559 | } 560 | /*超出部分省略号*/ 561 | 562 | .txtover { 563 | overflow: hidden; 564 | white-space: nowrap; 565 | text-overflow: ellipsis; 566 | } 567 | .txtover-2 { 568 | overflow: hidden; 569 | text-overflow: ellipsis; 570 | display: -webkit-box; 571 | -webkit-line-clamp: 2; 572 | -webkit-box-orient: vertical; 573 | } 574 | .txtover-3 { 575 | overflow: hidden; 576 | text-overflow: ellipsis; 577 | display: -webkit-box; 578 | -webkit-line-clamp: 3; 579 | -webkit-box-orient: vertical; 580 | } 581 | /*custom by zyc end*/ 582 | -------------------------------------------------------------------------------- /js/util.js: -------------------------------------------------------------------------------- 1 | /*JS开发常用工具函数*/ 2 | 3 | /** 4 | 1、isStatic:检测数据是不是除了symbol外的原始数据 5 | */ 6 | function isStatic(value) { 7 | return ( 8 | typeof value === 'string' || 9 | typeof value === 'number' || 10 | typeof value === 'boolean' || 11 | typeof value === 'undefined' || 12 | value === null 13 | ) 14 | } 15 | 16 | /** 17 | 2、isPrimitive:检测数据是不是原始数据 18 | */ 19 | function isPrimitive(value) { 20 | return isStatic(value) || typeof value === 'symbol' 21 | } 22 | 23 | /** 24 | 3、isObject:判断数据是不是引用类型的数据 (例如: arrays, functions, objects, regexes, new Number(0),以及 new String('')) 25 | */ 26 | function isObject(value) { 27 | let type = typeof value; 28 | return value != null && (type == 'object' || type == 'function'); 29 | } 30 | 31 | /** 32 | 4、isObjectLike:检查 value 是否是 类对象。 如果一个值是类对象,那么它不应该是 null,而且 typeof 后的结果是 "object" 33 | */ 34 | function isObjectLike(value) { 35 | return value != null && typeof value == 'object'; 36 | } 37 | 38 | /* 39 | 5、getRawType:获取数据类型,返回结果为 Number、String、Object、Array等 40 | */ 41 | function getRawType(value) { 42 | return Object.prototype.toString.call(value).slice(8, -1) 43 | } 44 | //getoRawType([]) ==> Array 45 | 46 | /** 47 | 6、isPlainObject:判断数据是不是Object类型的数据 48 | */ 49 | function isPlainObject(obj) { 50 | return Object.prototype.toString.call(obj) === '[object Object]' 51 | } 52 | 53 | /** 54 | 7、isArray:判断数据是不是数组类型的数据 55 | */ 56 | function isArray(arr) { 57 | return Object.prototype.toString.call(arr) === '[object Array]' 58 | } 59 | /* 60 | 将isArray挂载到Array上 61 | * */ 62 | Array.isArray = Array.isArray || isArray; 63 | 64 | /** 65 | 8、isRegExp:判断数据是不是正则对象 66 | */ 67 | function isRegExp(value) { 68 | return Object.prototype.toString.call(value) === '[object RegExp]' 69 | } 70 | 71 | /** 72 | 9、isDate:判断数据是不是时间对象 73 | */ 74 | function isDate(value) { 75 | return Object.prototype.toString.call(value) === '[object Date]' 76 | } 77 | 78 | /* 79 | 10、isNative:判断 value 是不是浏览器内置函数 80 | 内置函数toString后的主体代码块为 [native code] ,而非内置函数则为相关代码,所以非内置函数可以进行拷贝(toString后掐头去尾再由Function转) 81 | * */ 82 | function isNative(value) { 83 | return typeof value === 'function' && /native code/.test(value.toString()) 84 | } 85 | 86 | /* 87 | 11、isFunction:检查 value 是不是函数 88 | * */ 89 | function isFunction(value) { 90 | return Object.prototype.toString.call(value) === '[object Function]' 91 | } 92 | 93 | /* 94 | 12、isLength:检查 value 是否为有效的类数组长度。 95 | * */ 96 | function isLength(value) { 97 | return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= Number.MAX_SAFE_INTEGER; 98 | } 99 | 100 | /* 101 | 13、isArrayLike:检查 value 是否是类数组。 102 | 如果一个值被认为是类数组,那么它不是一个函数,并且value.length是个整数,大于等于 0,小于或等于 Number.MAX_SAFE_INTEGER。 103 | 这里字符串也将被当作类数组 104 | * */ 105 | function isArrayLike(value) { 106 | return value != null && isLength(value.length) && !isFunction(value); 107 | } 108 | 109 | /* 110 | 14、isEmpty:检查 value 是否为空 111 | 如果是null,直接返回true;如果是类数组,判断数据长度;如果是Object对象,判断是否具有属性;如果是其他数据,直接返回false(也可改为返回true) 112 | * */ 113 | function isEmpty(value) { 114 | if (value == null) { 115 | return true; 116 | } 117 | if (isArrayLike(value)) { 118 | return !value.length; 119 | } else if (isPlainObject(value)) { 120 | for (let key in value) { 121 | if (hasOwnProperty.call(value, key)) { 122 | return false; 123 | } 124 | } 125 | return true; 126 | } 127 | return false; 128 | } 129 | 130 | /** 131 | 15、cached:记忆函数:缓存函数的运算结果。 132 | */ 133 | function cached(fn) { 134 | let cache = Object.create(null); 135 | return function cachedFn(str) { 136 | let hit = cache[str]; 137 | return hit || (cache[str] = fn(str)) 138 | } 139 | } 140 | 141 | /* 142 | 16、camelize:横线转驼峰命名 143 | * */ 144 | let camelizeRE = /-(\w)/g; 145 | function camelize(str) { 146 | return str.replace(camelizeRE, function (_, c) { 147 | return c ? c.toUpperCase() : ''; 148 | }) 149 | } 150 | //ab-cd-ef ==> abCdEf 151 | //使用记忆函数 152 | let _camelize = cached(camelize) 153 | 154 | /** 155 | 17、hyphenate:驼峰命名转横线命名:拆分字符串,使用 - 相连,并且转换为小写 156 | */ 157 | let hyphenateRE = /\B([A-Z])/g; 158 | function hyphenate(str) { 159 | return str.replace(hyphenateRE, '-$1').toLowerCase() 160 | } 161 | //abCd ==> ab-cd 162 | //使用记忆函数 163 | let _hyphenate = cached(hyphenate); 164 | 165 | /** 166 | 18、capitalize:字符串首位大写 167 | */ 168 | function capitalize(str) { 169 | return str.charAt(0).toUpperCase() + str.slice(1) 170 | } 171 | // abc ==> Abc 172 | //使用记忆函数 173 | let _capitalize = cached(capitalize) 174 | 175 | /** 176 | 19、extend:将属性混合到目标对象中 177 | */ 178 | function extend(to, _from) { 179 | for (let key in _from) { 180 | to[key] = _from[key]; 181 | } 182 | return to 183 | } 184 | 185 | /* 186 | 20、Object.assign:对象属性复制,浅拷贝 187 | * */ 188 | Object.assign = Object.assign || function () { 189 | if (arguments.length == 0) throw new TypeError('Cannot convert undefined or null to object'); 190 | 191 | let target = arguments[0], 192 | args = Array.prototype.slice.call(arguments, 1), 193 | key 194 | args.forEach(function (item) { 195 | for (key in item) { 196 | item.hasOwnProperty(key) && (target[key] = item[key]) 197 | } 198 | }) 199 | return target 200 | } 201 | /* 202 | 使用Object.assign可以浅克隆一个对象:let clone = Object.assign({}, target) 203 | 简单的深克隆可以使用JSON.parse()和JSON.stringify(),这两个api是解析json数据的,所以只能解析除symbol外的原始类型及数组和对象 204 | let clone = JSON.parse( JSON.stringify(target) ) 205 | * */ 206 | 207 | /* 208 | 21、clone:克隆数据,可深度克隆 209 | 这里列出了原始类型,时间、正则、错误、数组、对象的克隆规则,其他的可自行补充 210 | * */ 211 | function clone(value, deep) { 212 | if (isPrimitive(value)) { 213 | return value 214 | } 215 | 216 | if (isArrayLike(value)) { //是类数组 217 | value = Array.prototype.slice.call(value) 218 | return value.map(item => deep ? clone(item, deep) : item) 219 | } else if (isPlainObject(value)) { //是对象 220 | let target = {}, key; 221 | for (key in value) { 222 | value.hasOwnProperty(key) && (target[key] = deep ? clone(value[key], deep) : value[key]) 223 | } 224 | } 225 | 226 | let type = getRawType(value) 227 | 228 | switch (type) { 229 | case 'Date': 230 | case 'RegExp': 231 | case 'Error': value = new window[type](value); break; 232 | } 233 | return value 234 | } 235 | 236 | /* 237 | 22、识别各种浏览器及平台 238 | * */ 239 | //运行环境是浏览器 240 | let inBrowser = typeof window !== 'undefined'; 241 | //运行环境是微信 242 | let inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform; 243 | let weexPlatform = inWeex && WXEnvironment.platform.toLowerCase(); 244 | //浏览器 UA 判断 245 | let UA = inBrowser && window.navigator.userAgent.toLowerCase(); 246 | let isIE = UA && /msie|trident/.test(UA); 247 | let isIE9 = UA && UA.indexOf('msie 9.0') > 0; 248 | let isEdge = UA && UA.indexOf('edge/') > 0; 249 | let isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android'); 250 | let isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios'); 251 | let isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge; 252 | 253 | /* 254 | 23、getExplorerInfo:获取浏览器信息 255 | * */ 256 | function getExplorerInfo() { 257 | let t = navigator.userAgent.toLowerCase(); 258 | return 0 <= t.indexOf("msie") ? { //ie < 11 259 | type: "IE", 260 | version: Number(t.match(/msie ([\d]+)/)[1]) 261 | } : !!t.match(/trident\/.+?rv:(([\d.]+))/) ? { // ie 11 262 | type: "IE", 263 | version: 11 264 | } : 0 <= t.indexOf("edge") ? { 265 | type: "Edge", 266 | version: Number(t.match(/edge\/([\d]+)/)[1]) 267 | } : 0 <= t.indexOf("firefox") ? { 268 | type: "Firefox", 269 | version: Number(t.match(/firefox\/([\d]+)/)[1]) 270 | } : 0 <= t.indexOf("chrome") ? { 271 | type: "Chrome", 272 | version: Number(t.match(/chrome\/([\d]+)/)[1]) 273 | } : 0 <= t.indexOf("opera") ? { 274 | type: "Opera", 275 | version: Number(t.match(/opera.([\d]+)/)[1]) 276 | } : 0 <= t.indexOf("Safari") ? { 277 | type: "Safari", 278 | version: Number(t.match(/version\/([\d]+)/)[1]) 279 | } : { 280 | type: t, 281 | version: -1 282 | } 283 | } 284 | 285 | /* 286 | 24、isPCBroswer:检测是否为PC端浏览器模式 287 | * */ 288 | function isPCBroswer() { 289 | let e = navigator.userAgent.toLowerCase() 290 | , t = "ipad" == e.match(/ipad/i) 291 | , i = "iphone" == e.match(/iphone/i) 292 | , r = "midp" == e.match(/midp/i) 293 | , n = "rv:1.2.3.4" == e.match(/rv:1.2.3.4/i) 294 | , a = "ucweb" == e.match(/ucweb/i) 295 | , o = "android" == e.match(/android/i) 296 | , s = "windows ce" == e.match(/windows ce/i) 297 | , l = "windows mobile" == e.match(/windows mobile/i); 298 | return !(t || i || r || n || a || o || s || l) 299 | } 300 | 301 | /* 302 | 25、unique:数组去重,返回一个新数组 303 | * */ 304 | function unique(arr) { 305 | if (!isArrayLink(arr)) { //不是类数组对象 306 | return arr 307 | } 308 | let result = [] 309 | let objarr = [] 310 | let obj = Object.create(null) 311 | 312 | arr.forEach(item => { 313 | if (isStatic(item)) {//是除了symbol外的原始数据 314 | let key = item + '_' + getRawType(item); 315 | if (!obj[key]) { 316 | obj[key] = true 317 | result.push(item) 318 | } 319 | } else {//引用类型及symbol 320 | if (!objarr.includes(item)) { 321 | objarr.push(item) 322 | result.push(item) 323 | } 324 | } 325 | }) 326 | 327 | return resulte 328 | } 329 | 330 | /* 331 | 26、Set简单实现 332 | * */ 333 | window.Set = window.Set || (function () { 334 | function Set(arr) { 335 | this.items = arr ? unique(arr) : []; 336 | this.size = this.items.length; // Array的大小 337 | } 338 | Set.prototype = { 339 | add: function (value) { 340 | // 添加元素,若元素已存在,则跳过,返回 Set 结构本身。 341 | if (!this.has(value)) { 342 | this.items.push(value); 343 | this.size++; 344 | } 345 | return this; 346 | }, 347 | clear: function () { 348 | //清除所有成员,没有返回值。 349 | this.items = [] 350 | this.size = 0 351 | }, 352 | delete: function (value) { 353 | //删除某个值,返回一个布尔值,表示删除是否成功。 354 | return this.items.some((v, i) => { 355 | if (v === value) { 356 | this.items.splice(i, 1) 357 | return true 358 | } 359 | return false 360 | }) 361 | }, 362 | has: function (value) { 363 | //返回一个布尔值,表示该值是否为Set的成员。 364 | return this.items.some(v => v === value) 365 | }, 366 | values: function () { 367 | return this.items 368 | }, 369 | } 370 | 371 | return Set; 372 | }()); 373 | 374 | /* 375 | 27、repeat:生成一个重复的字符串,有n个str组成,可修改为填充为数组等 376 | * */ 377 | function repeat(str, n) { 378 | let res = ''; 379 | while (n) { 380 | if (n % 2 === 1) { 381 | res += str; 382 | } 383 | if (n > 1) { 384 | str += str; 385 | } 386 | n >>= 1; 387 | } 388 | return res 389 | }; 390 | //repeat('123',3) ==> 123123123 391 | 392 | /* 393 | 28、dateFormater:格式化时间 394 | * */ 395 | function dateFormater(formater, t) { 396 | let date = t ? new Date(t) : new Date(), 397 | Y = date.getFullYear() + '', 398 | M = date.getMonth() + 1, 399 | D = date.getDate(), 400 | H = date.getHours(), 401 | m = date.getMinutes(), 402 | s = date.getSeconds(); 403 | return formater.replace(/YYYY|yyyy/g, Y) 404 | .replace(/YY|yy/g, Y.substr(2, 2)) 405 | .replace(/MM/g, (M < 10 ? '0' : '') + M) 406 | .replace(/DD/g, (D < 10 ? '0' : '') + D) 407 | .replace(/HH|hh/g, (H < 10 ? '0' : '') + H) 408 | .replace(/mm/g, (m < 10 ? '0' : '') + m) 409 | .replace(/ss/g, (s < 10 ? '0' : '') + s) 410 | } 411 | // dateFormater('YYYY-MM-DD HH:mm', t) ==> 2019-06-26 18:30 412 | // dateFormater('YYYYMMDDHHmm', t) ==> 201906261830 413 | 414 | /* 415 | 29、dateStrForma:将指定字符串由一种时间格式转化为另一种 416 | from的格式应对应str的位置 417 | * */ 418 | function dateStrForma(str, from, to) { 419 | //'20190626' 'YYYYMMDD' 'YYYY年MM月DD日' 420 | str += '' 421 | let Y = '' 422 | if (~(Y = from.indexOf('YYYY'))) { 423 | Y = str.substr(Y, 4) 424 | to = to.replace(/YYYY|yyyy/g, Y) 425 | } else if (~(Y = from.indexOf('YY'))) { 426 | Y = str.substr(Y, 2) 427 | to = to.replace(/YY|yy/g, Y) 428 | } 429 | 430 | let k, i 431 | ['M', 'D', 'H', 'h', 'm', 's'].forEach(s => { 432 | i = from.indexOf(s + s) 433 | k = ~i ? str.substr(i, 2) : '' 434 | to = to.replace(s + s, k) 435 | }) 436 | return to 437 | } 438 | // dateStrForma('20190626', 'YYYYMMDD', 'YYYY年MM月DD日') ==> 2019年06月26日 439 | // dateStrForma('121220190626', '----YYYYMMDD', 'YYYY年MM月DD日') ==> 2019年06月26日 440 | // dateStrForma('2019年06月26日', 'YYYY年MM月DD日', 'YYYYMMDD') ==> 20190626 441 | 442 | // 一般的也可以使用正则来实现 443 | //'2019年06月26日'.replace(/(\d{4})年(\d{2})月(\d{2})日/, '$1-$2-$3') ==> 2019-06-26 444 | 445 | /* 446 | 30、getPropByPath:根据字符串路径获取对象属性 : 'dcjgs[0].count' 447 | * */ 448 | function getPropByPath(obj, path, strict) { 449 | let tempObj = obj; 450 | path = path.replace(/\[(\w+)\]/g, '.$1'); //将[0]转化为.0 451 | path = path.replace(/^\./, ''); //去除开头的. 452 | 453 | let keyArr = path.split('.'); //根据.切割 454 | let i = 0; 455 | for (let len = keyArr.length; i < len - 1; ++i) { 456 | if (!tempObj && !strict) break; 457 | let key = keyArr[i]; 458 | if (key in tempObj) { 459 | tempObj = tempObj[key]; 460 | } else { 461 | if (strict) {//开启严格模式,没找到对应key值,抛出错误 462 | throw new Error('please transfer a valid prop path to form item!'); 463 | } 464 | break; 465 | } 466 | } 467 | return { 468 | o: tempObj, //原始数据 469 | k: keyArr[i], //key值 470 | v: tempObj ? tempObj[keyArr[i]] : null // key值对应的值 471 | }; 472 | }; 473 | 474 | /* 475 | 31、GetUrlParam:获取Url参数,返回一个对象 476 | * */ 477 | function GetUrlParam() { 478 | let url = document.location.toString(); 479 | let arrObj = url.split("?"); 480 | let params = Object.create(null) 481 | if (arrObj.length > 1) { 482 | arrObj = arrObj[1].split("&"); 483 | arrObj.forEach(item => { 484 | item = item.split("="); 485 | params[item[0]] = item[1] 486 | }) 487 | } 488 | return params; 489 | } 490 | // ?a=1&b=2&c=3 ==> {a: "1", b: "2", c: "3"} 491 | 492 | 493 | /* 494 | 32、downloadFile:base64数据导出文件,文件下载 495 | * */ 496 | function downloadFile(filename, data) { 497 | let DownloadLink = document.createElement('a'); 498 | 499 | if (DownloadLink) { 500 | document.body.appendChild(DownloadLink); 501 | DownloadLink.style = 'display: none'; 502 | DownloadLink.download = filename; 503 | DownloadLink.href = data; 504 | 505 | if (document.createEvent) { 506 | let DownloadEvt = document.createEvent('MouseEvents'); 507 | 508 | DownloadEvt.initEvent('click', true, false); 509 | DownloadLink.dispatchEvent(DownloadEvt); 510 | } 511 | else if (document.createEventObject) 512 | DownloadLink.fireEvent('onclick'); 513 | else if (typeof DownloadLink.onclick == 'function') 514 | DownloadLink.onclick(); 515 | 516 | document.body.removeChild(DownloadLink); 517 | } 518 | } 519 | 520 | //33、toFullScreen:全屏 521 | function toFullScreen() { 522 | let elem = document.body; 523 | elem.webkitRequestFullScreen 524 | ? elem.webkitRequestFullScreen() 525 | : elem.mozRequestFullScreen 526 | ? elem.mozRequestFullScreen() 527 | : elem.msRequestFullscreen 528 | ? elem.msRequestFullscreen() 529 | : elem.requestFullScreen 530 | ? elem.requestFullScreen() 531 | : alert("浏览器不支持全屏"); 532 | } 533 | 534 | //34、exitFullscreen:退出全屏 535 | function exitFullscreen() { 536 | let elem = parent.document; 537 | elem.webkitCancelFullScreen 538 | ? elem.webkitCancelFullScreen() 539 | : elem.mozCancelFullScreen 540 | ? elem.mozCancelFullScreen() 541 | : elem.cancelFullScreen 542 | ? elem.cancelFullScreen() 543 | : elem.msExitFullscreen 544 | ? elem.msExitFullscreen() 545 | : elem.exitFullscreen 546 | ? elem.exitFullscreen() 547 | : alert("切换失败,可尝试Esc退出"); 548 | } 549 | 550 | //35、requestAnimationFrame:window动画 551 | window.requestAnimationFrame = window.requestAnimationFrame || 552 | window.webkitRequestAnimationFrame || 553 | window.mozRequestAnimationFrame || 554 | window.msRequestAnimationFrame || 555 | window.oRequestAnimationFrame || 556 | function (callback) { 557 | //为了使setTimteout的尽可能的接近每秒60帧的效果 558 | window.setTimeout(callback, 1000 / 60); 559 | }; 560 | 561 | window.cancelAnimationFrame = window.cancelAnimationFrame || 562 | Window.webkitCancelAnimationFrame || 563 | window.mozCancelAnimationFrame || 564 | window.msCancelAnimationFrame || 565 | window.oCancelAnimationFrame || 566 | function (id) { 567 | //为了使setTimteout的尽可能的接近每秒60帧的效果 568 | window.clearTimeout(id); 569 | } 570 | 571 | /* 572 | 36、_isNaN:检查数据是否是非数字值 573 | 原生的isNaN会把参数转换成数字(valueof),而null、true、false以及长度小于等于1的数组(元素为非NaN数据)会被转换成数字,这不是我想要的 574 | Symbol类型的数据不具有valueof接口,所以isNaN会抛出错误,这里放在后面,可避免错误 575 | * */ 576 | function _isNaN(v) { 577 | return !(typeof v === 'string' || typeof v === 'number') || isNaN(v) 578 | } 579 | 580 | /* 581 | 37、max:求取数组中非NaN数据中的最大值 582 | * */ 583 | function max(arr) { 584 | arr = arr.filter(item => !_isNaN(item)) 585 | return arr.length ? Math.max.apply(null, arr) : undefined 586 | } 587 | //max([1, 2, '11', null, 'fdf', []]) ==> 11 588 | 589 | /* 590 | 38、min:求取数组中非NaN数据中的最小值 591 | * */ 592 | function min(arr) { 593 | arr = arr.filter(item => !_isNaN(item)) 594 | return arr.length ? Math.min.apply(null, arr) : undefined 595 | } 596 | //min([1, 2, '11', null, 'fdf', []]) ==> 1 597 | 598 | /* 599 | 39、random:返回一个lower - upper之间的随机数 600 | lower、upper无论正负与大小,但必须是非NaN的数据 601 | * */ 602 | function random(lower, upper) { 603 | lower = +lower || 0 604 | upper = +upper || 0 605 | return Math.random() * (upper - lower) + lower; 606 | } 607 | //random(0, 0.5) ==> 0.3567039135734613 608 | //random(2, 1) ===> 1.6718418553475423 609 | //random(-2, -1) ==> -1.4474325452361945 610 | 611 | /* 612 | 40、Object.keys:返回一个由一个给定对象的自身可枚举属性组成的数组 613 | * */ 614 | Object.keys = Object.keys || function keys(object) { 615 | if (object === null || object === undefined) { 616 | throw new TypeError('Cannot convert undefined or null to object'); 617 | } 618 | let result = [] 619 | if (isArrayLike(object) || isPlainObject(object)) { 620 | for (let key in object) { 621 | object.hasOwnProperty(key) && (result.push(key)) 622 | } 623 | } 624 | return result 625 | } 626 | 627 | /* 628 | 41、Object.values:返回一个给定对象自身的所有可枚举属性值的数组 629 | * */ 630 | Object.values = Object.values || function values(object) { 631 | if (object === null || object === undefined) { 632 | throw new TypeError('Cannot convert undefined or null to object'); 633 | } 634 | let result = [] 635 | if (isArrayLike(object) || isPlainObject(object)) { 636 | for (let key in object) { 637 | object.hasOwnProperty(key) && (result.push(object[key])) 638 | } 639 | } 640 | return result 641 | } 642 | 643 | /* 644 | 42、arr.fill:使用 value 值来填充 array,从start位置开始, 到end位置结束(但不包含end位置),返回原数组 645 | * */ 646 | Array.prototype.fill = Array.prototype.fill || function fill(value, start, end) { 647 | let ctx = this 648 | let length = ctx.length; 649 | 650 | start = parseInt(start) 651 | if (isNaN(start)) { 652 | start = 0 653 | } else if (start < 0) { 654 | start = -start > length ? 0 : (length + start); 655 | } 656 | 657 | end = parseInt(end) 658 | if (isNaN(end) || end > length) { 659 | end = length 660 | } else if (end < 0) { 661 | end += length; 662 | } 663 | 664 | while (start < end) { 665 | ctx[start++] = value; 666 | } 667 | return ctx; 668 | } 669 | //Array(3).fill(2) ===> [2, 2, 2] 670 | 671 | /* 672 | 43、arr.includes:用来判断一个数组是否包含一个指定的值,如果是返回 true,否则false,可指定开始查询的位置 673 | * */ 674 | Array.prototype.includes = Array.prototype.includes || function includes(value, start) { 675 | let ctx = this 676 | let length = ctx.length; 677 | 678 | start = parseInt(start) 679 | if (isNaN(start)) { 680 | start = 0 681 | } else if (start < 0) { 682 | start = -start > length ? 0 : (length + start); 683 | } 684 | 685 | let index = ctx.indexOf(value) 686 | 687 | return index >= start; 688 | } 689 | 690 | /* 691 | 44、arr.find:返回数组中通过测试(函数fn内判断)的第一个元素的值 692 | * */ 693 | Array.prototype.find = Array.prototype.find || function find(fn, ctx) { 694 | fn = fn.bind(ctx) 695 | 696 | let result; 697 | this.some((value, index, arr) => { 698 | return fn(value, index, arr) ? (result = value, true) : false 699 | }) 700 | 701 | return result 702 | } 703 | 704 | /* 705 | 45、arr.findIndex:返回数组中通过测试(函数fn内判断)的第一个元素的下标 706 | * */ 707 | Array.prototype.findIndex = Array.prototype.findIndex || function findIndex(fn, ctx) { 708 | fn = fn.bind(ctx) 709 | 710 | let result; 711 | this.some((value, index, arr) => { 712 | return fn(value, index, arr) ? (result = index, true) : false 713 | }) 714 | 715 | return result 716 | } 717 | 718 | /* 719 | 46、performance.timing:利用performance.timing进行性能分析 720 | * */ 721 | window.onload = function () { 722 | setTimeout(function () { 723 | let t = performance.timing 724 | console.log('DNS查询耗时 :' + (t.domainLookupEnd - t.domainLookupStart).toFixed(0)) 725 | console.log('TCP链接耗时 :' + (t.connectEnd - t.connectStart).toFixed(0)) 726 | console.log('request请求耗时 :' + (t.responseEnd - t.responseStart).toFixed(0)) 727 | console.log('解析dom树耗时 :' + (t.domComplete - t.domInteractive).toFixed(0)) 728 | console.log('白屏时间 :' + (t.responseStart - t.navigationStart).toFixed(0)) 729 | console.log('domready时间 :' + (t.domContentLoadedEventEnd - t.navigationStart).toFixed(0)) 730 | console.log('onload时间 :' + (t.loadEventEnd - t.navigationStart).toFixed(0)) 731 | 732 | if (t = performance.memory) { 733 | console.log('js内存使用占比 :' + (t.usedJSHeapSize / t.totalJSHeapSize * 100).toFixed(2) + '%') 734 | } 735 | }) 736 | } 737 | 738 | //47、禁止某些键盘事件 739 | document.addEventListener('keydown', function (event) { 740 | return !( 741 | 112 == event.keyCode || //F1 742 | 123 == event.keyCode || //F12 743 | event.ctrlKey && 82 == event.keyCode || //ctrl + R 744 | event.ctrlKey && 78 == event.keyCode || //ctrl + N 745 | event.shiftKey && 121 == event.keyCode || //shift + F10 746 | event.altKey && 115 == event.keyCode || //alt + F4 747 | "A" == event.srcElement.tagName && event.shiftKey //shift + 点击a标签 748 | ) || (event.returnValue = false) 749 | }); 750 | //48、禁止右键、选择、复制 751 | ['contextmenu', 'selectstart', 'copy'].forEach(function (ev) { 752 | document.addEventListener(ev, function (event) { 753 | return event.returnValue = false 754 | }) 755 | }); 756 | 757 | //49 758 | /** 759 | * 生成随机字母数字组合 760 | * @param {Number} len 随机串长度 761 | */ 762 | function randomWord(len) { 763 | len = Number(len) || 6; 764 | let str = "", pos = 0; 765 | let arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; 766 | let arrLength = arr.length; 767 | for (let i = 0; i < len; i++) { 768 | pos = Math.round(Math.random() * (arrLength - 1)); 769 | str += arr[pos]; 770 | } 771 | return str; 772 | } -------------------------------------------------------------------------------- /interview-3.md: -------------------------------------------------------------------------------- 1 | # (下篇)中高级前端大厂面试秘籍,寒冬中为您保驾护航,直通大厂 2 | 3 | ## 引言 4 | 5 | 本篇文章会继续沿着前面两篇的脚步,继续梳理前端领域一些比较主流的进阶知识点,力求能让大家在横向层面有个全面的概念。能在面试时有限的时间里,能够快速抓住重点与面试官交流。这些知识点属于加分项,如果能在面试时从容侃侃而谈,想必面试官会记忆深刻,为你折服的~🤤 6 | 7 | 另外有许多童鞋提到: 面试造火箭,实践全不会,对这种应试策略表达一些担忧。其实我是觉得面试或者这些知识点,也仅仅是个初级的 **开始**。能帮助在初期的快速成长,但这种策略并没办法让你达到更高的水平,只有后续不断地真正实践和深入研究,才能突破自己的瓶颈,继续成长。面试,不也只是一个开始而已嘛。~😋 8 | 9 | 建议各位小伙从基础入手,先看 10 | 11 | - [(上篇)中高级前端大厂面试秘籍,寒冬中为您保驾护航,直通大厂](https://github.com/vincentzyc/notes/blob/master/interview-1.md) 12 | - [(中篇)中高级前端大厂面试秘籍,寒冬中为您保驾护航,直通大厂](https://github.com/vincentzyc/notes/blob/master/interview-2.md) 13 | 14 | ## 进阶知识 15 | 16 | ## Hybrid 17 | 18 | 随着 Web技术 和 移动设备 的快速发展,在各家大厂中,Hybrid 技术已经成为一种最主流最不可取代的架构方案之一。一套好的 Hybrid 架构方案能让 App 既能拥有 **极致的体验和性能**,同时也能拥有 Web技术 **灵活的开发模式、跨平台能力以及热更新机制**。因此,相关的 Hybrid 领域人才也是十分的吃香,精通Hybrid 技术和相关的实战经验,也是面试中一项大大的加分项。 19 | 20 | ### 1. 混合方案简析 21 | 22 | Hybrid App,俗称 **混合应用**,即混合了 Native技术 与 Web技术 进行开发的移动应用。现在比较流行的混合方案主要有三种,主要是在UI渲染机制上的不同: 23 | 24 | - **Webview UI**: 25 | - 通过 JSBridge 完成 H5 与 Native 的双向通讯,并 **基于 Webview** 进行页面的渲染; 26 | - **优势**: 简单易用,架构门槛/成本较低,适用性与灵活性极强; 27 | - **劣势**: Webview 性能局限,在复杂页面中,表现远不如原生页面; 28 | 29 | - **Native UI**: 30 | - 通过 JSBridge 赋予 H5 原生能力,并进一步将 JS 生成的虚拟节点树(Virtual DOM)传递至 Native 层,并使用 **原生系统渲染**。 31 | - **优势**: 用户体验基本接近原生,且能发挥 Web技术 开发灵活与易更新的特性; 32 | - **劣势**: 上手/改造门槛较高,最好需要掌握一定程度的客户端技术。相比于常规 Web开发,需要更高的开发调试、问题排查成本; 33 | 34 | - **小程序** 35 | - 通过更加定制化的 JSBridge,赋予了 Web 更大的权限,并使用双 WebView 双线程的模式隔离了 JS逻辑 与 UI渲染,形成了特殊的开发模式,加强了 H5 与 Native 混合程度,属于第一种方案的优化版本; 36 | - **优势**: 用户体验好于常规 Webview 方案,且通常依托的平台也能提供更为友好的开发调试体验以及功能; 37 | - **劣势**: 需要依托于特定的平台的规范限定 38 | 39 | ### 2. Webviev 40 | 41 | Webview 是 Native App 中内置的一款基于 Webkit内核 的浏览器,主要由两部分组成: 42 | 43 | - **WebCore 排版引擎**; 44 | - **JSCore 解析引擎**; 45 | 46 | 在原生开发 SDK 中 Webview 被封装成了一个组件,用于作为 Web页面 的容器。因此,作为宿主的客户端中拥有更高的权限,可以对 Webview 中的 Web页面 进行配置和开发。 47 | 48 | Hybrid技术中双端的交互原理,便是基于 Webview 的一些 API 和特性。 49 | 50 | ### 3. 交互原理 51 | 52 | Hybrid技术 中最核心的点就是 Native端 与 H5端 之间的 **双向通讯层**,其实这里也可以理解为我们需要一套 **跨语言通讯方案**,便是我们常听到的 JSBridge。 53 | 54 | - JavaScript 通知 Native 55 | - **API注入**,Native 直接在 JS 上下文中挂载数据或者方法 56 | - 延迟较低,在安卓4.1以下具有安全性问题,风险较高 57 | - WebView **URL Scheme** 跳转拦截 58 | - 兼容性好,但延迟较高,且有长度限制 59 | - WebView 中的 **prompt/console/alert拦截**(通常使用 prompt) 60 | 61 | - **Native 通知 Javascript**: 62 | - **IOS**: `stringByEvaluatingJavaScriptFromString` 63 | 64 | ```js 65 | // Swift 66 | webview.stringByEvaluatingJavaScriptFromString("alert('NativeCall')") 67 | ``` 68 | 69 | - **Android**: `loadUrl` (4.4-) 70 | 71 | ```js 72 | // 调用js中的JSBridge.trigger方法 73 | // 该方法的弊端是无法获取函数返回值; 74 | webView.loadUrl("javascript:JSBridge.trigger('NativeCall')") 75 | ``` 76 | 77 | - **Android**: `evaluateJavascript` (4.4+) 78 | 79 | ```js 80 | // 4.4+后使用该方法便可调用并获取函数返回值; 81 | mWebView.evaluateJavascript("javascript:JSBridge.trigger('NativeCall')", new ValueCallback() { 82 | @Override 83 | public void onReceiveValue(String value) { 84 | //此处为 js 返回的结果 85 | } 86 | }); 87 | ``` 88 | 89 | ### 4. 接入方案 90 | 91 | 整套方案需要 Web 与 Native 两部分共同来完成: 92 | 93 | - **Native**: 负责实现URL拦截与解析、环境信息的注入、拓展功能的映射、版本更新等功能; 94 | - **JavaScirpt**: 负责实现功能协议的拼装、协议的发送、参数的传递、回调等一系列基础功能。 95 | 96 | **接入方式**: 97 | 98 | - **在线H5**: 直接将项目部署于线上服务器,并由客户端在 HTML 头部注入对应的 Bridge。 99 | - **优势**: 接入/开发成本低,对 App 的侵入小; 100 | - **劣势**: 重度依赖网络,无法离线使用,首屏加载慢; 101 | - **内置离线包**: 将代码直接内置于 App 中,即本地存储中,可由 H5 或者 客户端引用 Bridge。 102 | - **优势**: 首屏加载快,可离线化使用; 103 | - **劣势**: 开发、调试成本变高,需要多端合作,且会增加 App 包体积 104 | 105 | ### 5. 优化方案简述 106 | 107 | - **Webview 预加载**: Webview 的初始化其实挺耗时的。我们测试过,大概在100~200ms之间,因此如果能前置做好初始化于内存中,会大大加快渲染速度。 108 | - **更新机制**: 使用离线包的时候,便会涉及到本地离线代码的更新问题,因此需要建立一套云端下发包的机制,由客户端下载云端最新代码包 (zip包),并解压替换本地代码。 109 | - **增量更新**: 由于下发包是一个下载的过程,因此包的体积越小,下载速度越快,流量损耗越低。只打包改变的文件,客户端下载后覆盖式替换,能大大减小每次更新包的体积。 110 | - **条件分发**: 云平台下发更新包时,可以配合客户端设置一系列的条件与规则,从而实现代码的条件更新: 111 | - 单 **地区** 更新: 例如一个只有中国地区才能更新的版本; 112 | - 按 **语言** 更新: 例如只有中文版本会更新; 113 | - 按 App **版本** 更新: 例如只有最新版本的 App 才会更新; 114 | - **灰度** 更新: 只有小比例用户会更新; 115 | - **AB测试**: 只有命中的用户会更新; 116 | - **降级机制**: 当用户下载或解压代码包失败时,需要有套降级方案,通常有两种做法: 117 | - **本地内置**: 随着 App 打包时内置一份线上最新完整代码包,保证本地代码文件的存在,资源加载均使用本地化路径; 118 | - **域名拦截**: 资源加载使用线上域名,通过拦截域名映射到本地路径。当本地不存在时,则请求线上文件,当存在时,直接加载; 119 | - **跨平台部署**: Bridge层 可以做一套浏览器适配,在一些无法适配的功能,做好降级处理,从而保证代码在任何环境的可用性,一套代码可同时运行于 App内 与 普通浏览器; 120 | - **环境系统**: 与客户端进行统一配合,搭建出 **正式 / 预上线 / 测试 / 开发**环境,能大大提高项目稳定性与问题排查; 121 | - **开发模式**: 122 | - 能连接PC Chrome/safari 进行代码调试; 123 | - 具有开发调试入口,可以使用同样的 Webview 加载开发时的本地代码; 124 | - 具备日志系统,可以查看 Log 信息; 125 | 126 | 详细内容由兴趣的童鞋可以看文章: 127 | 128 | - [Hybrid App技术解析 -- 原理篇](https://github.com/xd-tayde/blog/blob/master/hybrid-1.md) 129 | - [Hybrid App技术解析 -- 实战篇](https://github.com/xd-tayde/blog/blob/master/hybrid-2.md) 130 | 131 | ## Webpack 132 | 133 | ### 1. 原理简述 134 | 135 | Webpack 已经成为了现在前端工程化中最重要的一环,通过`Webpack`与`Node`的配合,前端领域完成了不可思议的进步。通过预编译,将软件编程中先进的思想和理念能够真正运用于生产,让前端开发领域告别原始的蛮荒阶段。深入理解`Webpack`,可以让你在编程思维及技术领域上产生质的成长,极大拓展技术边界。这也是在面试中必不可少的一个内容。 136 | 137 | - **核心概念** 138 | - JavaScript 的 **模块打包工具** (module bundler)。通过分析模块之间的依赖,最终将所有模块打包成一份或者多份代码包 (bundler),供 HTML 直接引用。实质上,Webpack 仅仅提供了 **打包功能** 和一套 **文件处理机制**,然后通过生态中的各种 Loader 和 Plugin 对代码进行预编译和打包。因此 Webpack 具有高度的可拓展性,能更好的发挥社区生态的力量。 139 | - **Entry**: 入口文件,Webpack 会从该文件开始进行分析与编译; 140 | - **Output**: 出口路径,打包后创建 bundler 的文件路径以及文件名; 141 | - **Module**: 模块,在 Webpack 中任何文件都可以作为一个模块,会根据配置的不同的 Loader 进行加载和打包; 142 | - **Chunk**: 代码块,可以根据配置,将所有模块代码合并成一个或多个代码块,以便按需加载,提高性能; 143 | - **Loader**: 模块加载器,进行各种文件类型的加载与转换; 144 | - **Plugin**: 拓展插件,可以通过 Webpack 相应的事件钩子,介入到打包过程中的任意环节,从而对代码按需修改; 145 | 146 | - **工作流程** (加载 - 编译 - 输出) 147 | - 1、读取配置文件,按命令 **初始化** 配置参数,创建 Compiler 对象; 148 | - 2、调用插件的 apply 方法 **挂载插件** 监听,然后从入口文件开始执行编译; 149 | - 3、按文件类型,调用相应的 Loader 对模块进行 **编译**,并在合适的时机点触发对应的事件,调用 Plugin 执行,最后再根据模块 **依赖查找** 到所依赖的模块,递归执行第三步; 150 | - 4、将编译后的所有代码包装成一个个代码块 (Chuck), 并按依赖和配置确定 **输出内容**。这个步骤,仍然可以通过 Plugin 进行文件的修改; 151 | - 5、最后,根据 Output 把文件内容一一写入到指定的文件夹中,完成整个过程; 152 | 153 | - **模块包装**: 154 | 155 | ```js 156 | (function(modules) { 157 | // 模拟 require 函数,从内存中加载模块; 158 | function __webpack_require__(moduleId) { 159 | // 缓存模块 160 | if (installedModules[moduleId]) { 161 | return installedModules[moduleId].exports; 162 | } 163 | 164 | var module = installedModules[moduleId] = { 165 | i: moduleId, 166 | l: false, 167 | exports: {} 168 | }; 169 | 170 | // 执行代码; 171 | modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 172 | 173 | // Flag: 标记是否加载完成; 174 | module.l = true; 175 | 176 | return module.exports; 177 | } 178 | 179 | // ... 180 | 181 | // 开始执行加载入口文件; 182 | return __webpack_require__(__webpack_require__.s = "./src/index.js"); 183 | })({ 184 | "./src/index.js": function (module, __webpack_exports__, __webpack_require__) { 185 | // 使用 eval 执行编译后的代码; 186 | // 继续递归引用模块内部依赖; 187 | // 实际情况并不是使用模板字符串,这里是为了代码的可读性; 188 | eval(` 189 | __webpack_require__.r(__webpack_exports__); 190 | // 191 | var _test__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("test", ./src/test.js"); 192 | `); 193 | }, 194 | "./src/test.js": function (module, __webpack_exports__, __webpack_require__) { 195 | // ... 196 | }, 197 | }) 198 | ``` 199 | 200 | - **总结**: 201 | - **模块机制**: webpack 自己实现了一套模拟模块的机制,将其包裹于业务代码的外部,从而提供了一套模块机制; 202 | - **文件编译**: webpack 规定了一套编译规则,通过 Loader 和 Plugin,以管道的形式对文件字符串进行处理; 203 | 204 | ### 2. Loader 205 | 206 | 由于 Webpack 是基于 Node,因此 Webpack 其实是只能识别 js 模块,比如 css / html / 图片等类型的文件并无法加载,因此就需要一个对 **不同格式文件转换器**。其实 Loader 做的事,也并不难理解: **对 Webpack 传入的字符串进行按需修改**。例如一个最简单的 Loader: 207 | 208 | ```js 209 | // html-loader/index.js 210 | module.exports = function(htmlSource) { 211 | // 返回处理后的代码字符串 212 | // 删除 html 文件中的所有注释 213 | return htmlSource.replace(//g, '') 214 | } 215 | ``` 216 | 217 | 当然,实际的 Loader 不会这么简单,通常是需要将代码进行分析,构建 **AST (抽象语法树)**, 遍历进行定向的修改后,再重新生成新的代码字符串。如我们常用的 Babel-loader 会执行以下步骤: 218 | 219 | - babylon 将 ES6/ES7 代码解析成 AST 220 | - babel-traverse 对 AST 进行遍历转译,得到新的 AST 221 | - 新 AST 通过 babel-generator 转换成 ES5 222 | 223 | **Loader 特性**: 224 | 225 | - **链式传递**,按照配置时相反的顺序链式执行; 226 | - 基于 Node 环境,拥有 **较高权限**,比如文件的增删查改; 227 | - 可同步也可异步; 228 | 229 | **常用 Loader**: 230 | 231 | - file-loader: 加载文件资源,如 字体 / 图片 等,具有移动/复制/命名等功能; 232 | - url-loader: 通常用于加载图片,可以将小图片直接转换为 Date Url,减少请求; 233 | - babel-loader: 加载 js / jsx 文件, 将 ES6 / ES7 代码转换成 ES5,抹平兼容性问题; 234 | - ts-loader: 加载 ts / tsx 文件,编译 TypeScript; 235 | - style-loader: 将 css 代码以`