.gitignore文件的使用

作用 .gitignore文件可以过滤掉不需要提交到git库的文件。例如node_modules文件夹等等。 用法 用文本编辑器打开.gitignore文件,一行写一条规则。 # Windows: Thumbs.db ehthumbs.db Desktop.ini # MacOs .DS_Store # Python: *.py[cod] *.so *.egg *.egg-info dist build # Node node_modules/ .deploy_git/ public/ package-lock.json *.txt 上述规则中public/代表忽略public文件夹,*.txt代表忽略根目录下所有txt文件 提交 如果是项目中途加入的.gitignore文件,需要清除提交记录,git rm -r --cached .此命令不会删除本地文件 git rm -r --cached . git add . git commit -m 'update .gitignore' git push

July 16, 2019 · 1 min · 49 words · Norman Wang

Vue轻量级表单验证插件wy-validate v0.0.5

目前功能还不全可能会有BUG 入门 以Vue Cli2.x为例 引入 在main.js中引入并use import WyValidate from 'wy-validate'; Vue.use(VeeValidate); 页面使用 <input type="text" name="wangyu1" v-wyValidate="rules1"> <span v-show="wyError.wangyu1 ? wyError.wangyu1.showMessage : false">{{wyError.wangyu1 ? wyError.wangyu1.message : ''}}</span> <br> <input type="text" name="wangyu2" v-wyValidate="rules2"> <span v-show="wyError.wangyu2 ? wyError.wangyu2.showMessage : false">{{wyError.wangyu2 ? wyError.wangyu2.message : ''}}</span> <br> <input type="text" name="wangyu3" v-wyValidate="rules3"> <span v-show="wyError.wangyu3 ? wyError.wangyu3.showMessage : false">{{wyError.wangyu3 ? wyError.wangyu3.message : ''}}</span> 定义规则 export default { name: 'App', data() { return { rules1: [ { required: true,message: "不能为空",trigger: "blur" }, { message: "请输入正确的格式!",min: 5,max: 16,trigger: "input" } ], rules2: [ { required: true,message: "不能为空",trigger: "blur" }, { type: "email",message: "请输入邮箱",trigger: "input" } ], rules3: [ { required: true,message: "不能为空",trigger: "blur" }, { regex: '^1(3|4|5|7|8)\\d{9}$',message: "请输入正确的电话号码",trigger: "input" } ] } } } 规则说明 暂无 ...

July 1, 2019 · 1 min · 130 words · Norman Wang

BilibiliVideoDownload v2.0.2更新,可以下载大会员清晰度

运行 安装ffmpeg (视频合并转码会用到) git clone https://github.com/blogwy/BilibiliVideoDownload.git cd BilibiliVideoDownload npm i node app.js 版本 v2.0.2 2019-06-19 添加了大会员清晰度的支持(前提是必须要有一个大会员的SESSDATA) v2.0.1 2019-03-29 添加了分P检测,分P下载功能 v2.0.0 2019-03-24 Node.js重构,以前的在vuejs分支 演示 实现的功能 视频下载 视频合并 视频转码 分P检测 分P下载 大会员清晰度下载(1080p60,720p60,1080p+) 注意的问题 请定期跟换cookie中的SESSDATA值,在utils/getUrl.js42和96行。跟换方法为:浏览器登陆bilibili账户,在开发者模式 –> application –> cookie中找到SESSDATA值替换即可,一般为一个月的时效。(默认的SESSDATA是大会员的,可以下载大会员清晰度,时效到2019-07-17) 在以后的版本会加上模拟登陆功能。 win用户在命令行CHCP 65001把编码转换成UTF8,不然会出现乱码。 输入的是av号,不要带av 用到的接口 https://api.bilibili.com/x/player/playurl?avid=44743619&cid=78328965&qn=80&otype=json https://api.bilibili.com/x/web-interface/view?aid=44743619

June 25, 2019 · 1 min · 43 words · Norman Wang

localStorage数据跨域共享

postMessage postMessage是Html5引入的新API,可以安全地实现跨源通信。(跨页面/窗口/源等) otherWindow.postMessage(message, targetOrigin); message 要发送的数据,要求是字符串 targetOrigin 目标窗口的源,包括协议+主机+端口号 使用方法 这里要实现A域和B域进行跨源访问localStorage,必须要引入第三者C域 A域 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>A-页面</title> <style> #child{ display: none; } </style> </head> <body> <h2>A-页面</h2> <div> <label for="">Key</label> <input type="text" placeholder="输入key" id="itemKey"> </div> <div> <label for="">Value</label> <input type="text" placeholder="输入value" id="itemValue"> </div> <div> <button id="add">添加</button> </div> <iframe id="child" src="http://c.test.com/"></iframe> <script> var add = document.getElementById("add"); add.addEventListener('click',function () { var itemKey = document.getElementById("itemKey").value; var itemValue = document.getElementById("itemValue").value; if (itemKey && itemValue){ window.frames[0].postMessage(JSON.stringify({type:"set",key: itemKey ,value: itemValue}),'*'); alert('添加成功'); }else { alert('请输入key或者value'); } }); </script> </body> </html> B域 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>B-页面</title> <style> #child{ display: none; } </style> </head> <body> <h2>B-页面</h2> <div> <label for="">Key</label> <input type="text" placeholder="输入key" id="itemKey"> </div> <div> <button id="getValue">获取</button> </div> <div id="itemValue"></div> <iframe id="child" src="http://c.test.com/"></iframe> <script type="text/javascript"> var getValue = document.getElementById("getValue"); getValue.addEventListener('click',function () { var itemKey = document.getElementById("itemKey").value; var itemValue = document.getElementById("itemValue"); if (itemKey){ window.frames[0].postMessage(JSON.stringify({type:"get",key:itemKey}),'*'); window.addEventListener('message', function(e) { if (e.origin && e.origin === 'http://c.test.com'){ var data = e.data; itemValue.innerHTML = 'value为' + data; } }, false); }else { alert('请输入key'); } }); </script> </body> </html> C域 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>C-页面</title> </head> <body> <h2>C-页面</h2> <script> ;(function (win) { win.addEventListener("message",function(evt){ if(win.parent!= evt.source){ return } var options=JSON.parse(evt.data); if(options.type === "get"){ var data = win.localStorage.getItem(options.key); win.parent.postMessage(data, "*"); } if(options.type === "set"){ win.localStorage.setItem(options.key,options.value); } if(options.type === "remove"){ win.localStorage.removeItem(options.key); } if(options.type === "clear"){ win.localStorage.clear(); } },false); })(window); </script> </body> </html> 结果 ...

May 13, 2019 · 2 min · 252 words · Norman Wang

如何优雅的写小程序的分享函数

背景知识 微信小程序是可以通过点击右上角的menu来进行分享(转发)的。但是代码里面不可以写个公共函数来调用,必须每个页面写一遍,这就使得代码冗余了,业务修改的时候必须全都修改一遍,很容易忘记某一页。下面通过一种方法,先写一个公共的Page函数,在公共的Page里面写入分享代码,然后在具体的页面调用使用这个公共Page函数实现分享。 建立公共Page.js export default function (options = {}) { return Page({ onShareAppMessage(res) { if (res.from === 'menu') { // 来自右上角分享菜单 return { title: '分享标题', path: '页面路径', imageUrl: '分享图片路径' } } }, ...options }); } 页面调用 import Page from '../../../utils/Page.js'; Page({ // 注意,这时候Page里面不要出现onShareAppMessage函数 data: {}, onLoad(){}, onReady(){}, onShow(){}, onHide(){}, onUnload(){}, onPullDownRefresh(){}, onReachBottom(){} })

May 8, 2019 · 1 min · 53 words · Norman Wang

H5端获取摄像头并发送流数据给后端

前置知识 上一篇文章是前端调用摄像头,然后把流数据给到video标签,用canvas截取图像。前端调用库解析二维码和条形码。 这篇文章是前端获取到流数据发送给后端,后端解析成功后把结果返回前端。 getUserMedia 获取摄像头/麦克风,回调成功会返回一个MediaStream,里面包含了请求的媒体类型的轨道。此流可以包含一个视频轨道(来自硬件或者虚拟视频源,比如相机、视频采集设备和屏幕共享服务等等)、一个音频轨道(同样来自硬件或虚拟音频源,比如麦克风、A/D转换器等等),也可能是其它轨道类型。MDN navigator.mediaDevices.getUserMedia(constraints) .then(function(stream) { /* 使用这个stream stream */ }) .catch(function(err) { /* 处理error */ }); MediaRecorder 录制MediaStream,产生流数据。MDN navigator.mediaDevices.getUserMedia(constraints) .then(function(stream) { /* 使用这个stream */ let mediaRecorder = new MediaRecorder(stream,{ mimeType : 'video/webm' }); // 每3秒调用一次,这个参数必须写 mediaRecorder.start(3000); mediaRecorder.onstart = function (e) { console.log('mediaRecorder 开始录制'); }; mediaRecorder.ondataavailable = function (e) { // e.data是视频的流数据Blob格式 console.log(e.data); }; }) .catch(function(err) { /* 处理error */ }); demo实现 <template> <div id="scanner"> <div class="model"> <div class="scanner-view"> <div class="scanner-view-arrow arrow1"></div> <div class="scanner-view-arrow arrow2"></div> <div class="scanner-view-arrow arrow3"></div> <div class="scanner-view-arrow arrow4"></div> <div class="scanner-line"></div> </div> </div> <video class="video-view" ref="video" autoplay playsinline="true" webkit-playsinline="true"></video> </div> </template> <script> export default { name: '', data() { return { ws: '', url: 'wss://192.168.0.110/websocket' } }, methods: { initWebsocket(){ let _this = this; if (this.ws){ // 已经建立连接 }else { this.createWebsocket(); this.ws.onopen = function() { //设置发信息送类型为:ArrayBuffer _this.ws.binaryType = "arraybuffer"; }; this.ws.onmessage = function(e) { console.log(e); }; this.ws.onclose = function(e) { console.log("onclose: closed"); _this.ws = ''; _this.createWebsocket(); }; this.ws.onerror = function(e) { console.log("onerror: error"); _this.ws = ''; _this.createWebsocket(); } } }, createWebsocket(){ if ('WebSocket' in window){ this.ws = new WebSocket(this.url); }else { console.log('浏览器版本太低,请更换浏览器'); } }, initVideo(constrains){ let _this = this; if(navigator.mediaDevices.getUserMedia){ //最新标准API navigator.mediaDevices.getUserMedia(constrains).then(_this.videoSuccess).catch(_this.videoError); } else if (navigator.webkitGetUserMedia){ //webkit内核浏览器 navigator.webkitGetUserMedia(constrains).then(_this.videoSuccess).catch(_this.videoError); } else if (navigator.mozGetUserMedia){ //Firefox浏览器 navagator.mozGetUserMedia(constrains).then(_this.videoSuccess).catch(_this.videoError); } else if (navigator.getUserMedia){ //旧版API navigator.getUserMedia(constrains).then(_this.videoSuccess).catch(_this.videoError); } }, videoSuccess(stream){ let video = this.$refs.video, _this = this,chunks = []; //将视频流设置为video元素的源 video.srcObject = stream; //播放视频 video.play(); // 发送视频流 // 建立视频录制 MediaRecorder目前不支持ios let mediaRecorder = new MediaRecorder(stream,{ mimeType : 'video/webm' }); // 每..秒调用一次,这个参数必须写 mediaRecorder.start(3000); mediaRecorder.onstart = function (e) { console.log('mediaRecorder 开始录制'); }; mediaRecorder.ondataavailable = function (e) { chunks.push(e.data); console.log(e.data.type); let reader = new FileReader(); reader.addEventListener("loadend", function() { //reader.result是一个含有视频数据流的Blob对象,这里把blob转成ByteBuffer var videoBlob = new Uint8Array(reader.result); console.log('视频数据流'); if(reader.result.byteLength > 0){ // websocket发送数据 _this.ws.send(videoBlob); } }); reader.readAsArrayBuffer(e.data); }; }, videoError(error){ console.log("访问用户媒体设备失败:",error.name,error.message); }, }, mounted(){ // 建立websocket连接 this.initWebsocket(); if (navigator.mediaDevices.getUserMedia || navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia){ //调用用户媒体设备,访问摄像头 this.initVideo({ video:{ height: 800, facingMode: { // 强制后置摄像头 exact: "environment" } } }); } else { alert("你的浏览器不支持访问用户媒体设备"); } } } </script> <style scoped> #scanner { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; position: relative; } .model{ box-sizing: border-box; width: 100vw; height: 100vh; position: relative; z-index: 88; border-top: calc((100vh - 60vw)/2) solid rgba(0,0,0,.2); border-bottom: calc((100vh - 60vw)/2) solid rgba(0,0,0,.2); border-right: 20vw solid rgba(0,0,0,.2); border-left: 20vw solid rgba(0,0,0,.2); } .scanner-view{ width: 100%; height: 100%; position: relative; border: 1px solid rgba(255,255,255,.3); z-index: 89; } .scanner-line{ position: absolute; width: 100%; height: 1px; background: #49FF46; border-radius: 20px; z-index: 90; animation: myScan 1s infinite alternate; } @keyframes myScan{ from { top: 0; } to { top: 34vh; } } .scanner-view-arrow{ position: absolute; width: 5vw; height: 5vw; border: 2px solid #09bb07; } .scanner-view-arrow.arrow1{ top: -1px; left: 0px; z-index: 99; border-right: none; border-bottom: none; } .scanner-view-arrow.arrow2{ top: -1px; right: 0px; z-index: 99; border-left: none; border-bottom: none; } .scanner-view-arrow.arrow3{ bottom: -1px; left: 0px; z-index: 99; border-right: none; border-top: none; } .scanner-view-arrow.arrow4{ bottom: -1px; right: 0px; z-index: 99; border-left: none; border-top: none; } .video-view{ position: absolute; width: 100vw; height: 100vh; object-fit: cover; top: 0px; left: 0px; z-index: 80; } </style> 注意事项 截止到现在2019-05-06IOS端不支持MediaRecorder,所以IOS端到目前为止不能发送流数据。具体的支持情况可以查看caniuse!

May 6, 2019 · 3 min · 491 words · Norman Wang

H5端获取摄像头进行扫码(条形码/二维码)

getUserMedia了解 HTML5的getUserMedia API为用户提供访问硬件设备媒体(摄像头、麦克风)的接口,基于该接口,开发者可以在不依赖任何浏览器插件的条件下访问硬件媒体设备。 点击查看getUserMedia的api 代码 Vue.js版 HTML <template> <div id="scanner"> <div class="model"> <div class="scanner-view"> <div class="scanner-view-arrow arrow1"></div> <div class="scanner-view-arrow arrow2"></div> <div class="scanner-view-arrow arrow3"></div> <div class="scanner-view-arrow arrow4"></div> <div class="scanner-line"></div> </div> </div> <video class="video-view" ref="video" autoplay playsinline="true" webkit-playsinline="true"></video> <canvas ref="canvas" width="478" height="850" style="display: none"></canvas> </div> </template> JS <script> import jsQR from "jsqr"; import Quagga from "quagga"; export default { name: '', data() { return { cameraWidth: 0, cameraHeight: 0 } }, methods: { initVideo(constrains){ let _this = this; if(navigator.mediaDevices.getUserMedia){ //最新标准API navigator.mediaDevices.getUserMedia(constrains).then(_this.videoSuccess).catch(_this.videoError); } else if (navigator.webkitGetUserMedia){ //webkit内核浏览器 navigator.webkitGetUserMedia(constrains).then(_this.videoSuccess).catch(_this.videoError); } else if (navigator.mozGetUserMedia){ //Firefox浏览器 navagator.mozGetUserMedia(constrains).then(_this.videoSuccess).catch(_this.videoError); } else if (navigator.getUserMedia){ //旧版API navigator.getUserMedia(constrains).then(_this.videoSuccess).catch(_this.videoError); } }, videoSuccess(stream){ let video = this.$refs.video, _this = this; //将视频流设置为video元素的源 video.srcObject = stream; //播放视频 video.play(); video.oncanplay = function () { // 摄像头分辨率,手机480x640 console.log('摄像头分辨率'); console.log(video.videoWidth,video.videoHeight); _this.cameraWidth = video.videoWidth; _this.cameraHeight = video.videoHeight; // 发送图片进行识别 _this.readImg(); }; }, videoError(error){ console.log("访问用户媒体设备失败:",error.name,error.message); }, readImg(){ let video = this.$refs.video, canvas = this.$refs.canvas, context = canvas.getContext("2d"), _this = this; let timer = setInterval(function () { context.drawImage(video,0,0,_this.cameraWidth,_this.cameraHeight,0,0,478,850); // 扫码条形码 let imgUri = canvas.toDataURL(); _this.readBarcode(imgUri,timer); // 扫码二维码 let imageData = context.getImageData(0, 0, 478, 850); _this.readQrcode(imageData.data,timer); },1000) }, readBarcode(imgBase64,timer){ let _this = this; Quagga.decodeSingle({ inputStream: { size: 1920 }, locator: { patchSize: "medium", halfSample: false }, decoder: { readers: [{ format: "code_128_reader", config: {} }] }, locate: true, src: imgBase64 }, function(result){ if (result){ if(result.codeResult) { console.log(result.codeResult); clearInterval(timer); _this.$emit('ondata',result.codeResult.code); // alert("扫码成功,结果是..."+result.codeResult.code); } else { console.log("正在扫条形码...not detected"); } }else { console.log("正在扫条形码...not detected"); } }); }, readQrcode(data,timer){ let _this = this; let code = jsQR(data, 478, 850, { inversionAttempts: "dontInvert", }); if (code){ clearInterval(timer); _this.$emit('ondata',code.data); // alert('扫码成功,结果是...' + code.data); }else { console.log('正在扫二维码...'); } } }, mounted(){ if (navigator.mediaDevices.getUserMedia || navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia){ //调用用户媒体设备,访问摄像头 this.initVideo({ video:{ height: 800, facingMode: { // 强制后置摄像头 exact: "environment" } } }); } else { alert("你的浏览器不支持访问用户媒体设备"); } } } </script> CSS <style scoped> #scanner { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; position: relative; } .model{ box-sizing: border-box; width: 100vw; height: 100vh; position: relative; z-index: 88; border-top: calc((100vh - 60vw)/2) solid rgba(0,0,0,.2); border-bottom: calc((100vh - 60vw)/2) solid rgba(0,0,0,.2); border-right: 20vw solid rgba(0,0,0,.2); border-left: 20vw solid rgba(0,0,0,.2); } .scanner-view{ width: 100%; height: 100%; position: relative; border: 1px solid rgba(255,255,255,.3); z-index: 89; } .scanner-line{ position: absolute; width: 100%; height: 1px; background: #49FF46; border-radius: 20px; z-index: 90; animation: myScan 1s infinite alternate; } @keyframes myScan{ from { top: 0; } to { top: 34vh; } } .scanner-view-arrow{ position: absolute; width: 5vw; height: 5vw; border: 2px solid #09bb07; } .scanner-view-arrow.arrow1{ top: -1px; left: 0px; z-index: 99; border-right: none; border-bottom: none; } .scanner-view-arrow.arrow2{ top: -1px; right: 0px; z-index: 99; border-left: none; border-bottom: none; } .scanner-view-arrow.arrow3{ bottom: -1px; left: 0px; z-index: 99; border-right: none; border-top: none; } .scanner-view-arrow.arrow4{ bottom: -1px; right: 0px; z-index: 99; border-left: none; border-top: none; } .video-view{ position: absolute; width: 100vw; height: 100vh; object-fit: cover; top: 0px; left: 0px; z-index: 80; } </style> 注意事项 video标签里面的视频会用黑边,可以在video标签的css中加入 object-fit: cover; ...

April 23, 2019 · 3 min · 472 words · Norman Wang

chart.js 设置图例Legend的对齐方式

截止到今天,chart.js版本v2.8.0不支持配置Legend对齐方式,默认居中。但是我在Github Pull Request找到了解决方法。 https://github.com/chartjs/Chart.js/pull/6141 克隆 chart.js最新代码 git clone https://github.com/chartjs/Chart.js.git Building chart.js > cd Chart.js > npm install > npm install -g gulp-cli > gulp build 进入dist文件夹引入Chart.bundle.js或者Chart.bundle.min.js 在legend里面添加配置项align,align可选项为start/center/end,默认center

April 9, 2019 · 1 min · 27 words · Norman Wang

Charles 抓包工具配置全过程

下载 根据你都平台进行下载 注册 点击 帮助 -> 注册 输入下面的信息即可 Registered Name: https://zhile.io License Key: 48891cf209c6d32bf4 mac端配置 点击Proxy -> proxy setting 端口维持默认,在Enable transparent HTTP proxying打勾 点击Proxy -> SSL proxy setting 点击add host为* port为443 点击help -> SSL proxying 选择第二项载入证书,并且在钥匙串中把该证书设置为始终信任 ios端配置 点击help -> SSL proxying 选择第五项,此时会出现如下页面 打开手机并连接和mac一样的网络,进入网络详情设置代理,安装上图应该设置服务器地址为192.168.31.221,端口为8888(具体情况请根据实际设置) 浏览器进入chls.pro/ssl,下载描述文件 手机依次打开设置 -> 通用 -> 描述文件与设备管理 选择刚刚下载的描述文件并安装。 手机依次设置 -> 通用 -> 关于本机 -> 证书信任设置 信任当前证书。

March 27, 2019 · 1 min · 58 words · Norman Wang

BilibiliVideoDownload v2.0.0更新 支持下载1080P高清视频

运行 安装ffmpeg (视频合并转码会用到) git clone https://github.com/blogwy/BilibiliVideoDownload.git cd BilibiliVideoDownload npm i node app.js 版本 v2.0.0 2019-03-24 Node.js重构,以前的在vuejs分支 演示 实现的功能 视频下载 视频合并 视频转码 注意的问题 大会员视频不可以下载 请定期跟换cookie中的SESSDATA值,在utils/getUrl.js42行。跟换方法为:浏览器登陆bilibili账户,在开发者模式 –> application –> cookie中找到SESSDATA值替换即可,一般为一个月的实效。 在以后的版本会加上模拟登陆功能。 用到的接口 https://api.bilibili.com/x/player/playurl?avid=44743619&cid=78328965&qn=80&otype=json https://api.bilibili.com/x/web-interface/view?aid=44743619

March 24, 2019 · 1 min · 32 words · Norman Wang