app.controller('qrCodeCtrl', ['$scope', '$http', '$timeout', '$q', 'toaster', function ($scope, $http, $timeout, $q, toaster) { moment.locale('zh-cn'); function initColorPicker(initColor) { var colorPicker; if (window.Pickr) { colorPicker = Pickr.create({ el: '#colorPicker', default: initColor || '#000', swatches: [ '#000', '#fff', 'rgb(233, 30, 99)', 'rgb(244, 67, 54)', 'rgb(233, 30, 99)', 'rgb(156, 39, 176)', 'rgb(103, 58, 183)', 'rgb(63, 81, 181)', 'rgb(33, 150, 243)', 'rgb(3, 169, 244)', 'rgb(0, 188, 212)', 'rgb(0, 150, 136)', 'rgb(76, 175, 80)', 'rgb(255, 235, 59)', 'rgb(255, 193, 7)' ], components: { preview: true, opacity: true, hue: true, interaction: { rgba: true, hex: true, hsva: true, input: true, save: true } }, strings: { save: '确定', } }); colorPicker.on('save', function (args) { $scope.condition.labelColor = args.toRGBA().toString() $scope.$apply() }) } } //圆角矩形 CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) { if (w < 2 * r) r = w / 2; if (h < 2 * r) r = h / 2; this.beginPath(); this.moveTo(x + r, y); this.arcTo(x + w, y, x + w, y + h, r); this.arcTo(x + w, y + h, x, y + h, r); this.arcTo(x, y + h, x, y, r); this.arcTo(x, y, x + w, y, r); this.closePath(); return this; }; var defaultCondition = { width: 200,//总画布宽度(有背景图时生效) height: 200,//总画布高度(有背景图时生效) qrcodeLeft: 0,//二维码位于背景left位置 qrcodeTop: 0,//二维码位于背景top位置 labelHeight: 30,//标签占的高度 labelColor: "#000",//标签颜色 size: 200,//二维码尺寸 background: false, backgroundPath: "img/qrcode-back.png", backgroundScale: 100, logo: false, logoPath: "img/logo.png", logoSize: 40, domain: location.protocol + "//" + location.host,//网址,如 https://www.washpayer.com,这个可以自定义 pathname: '/userLogin?l=', prefix: "",//二维码前缀,如蓝牙B开通,还有某些厂商自定义前缀 dataMod: 'serial',// 默认连续serial,还有batch = logicalCode列表,换行分割 start: 100, end: 200, logicalCodeList: "", subIndex: false, subStart: 1, subEnd: 40, fileName: "", fileSuffix: "jpeg", }; $scope.condition = $.extend(true, {}, defaultCondition, JSON.parse(localStorage.getItem("QRCODE_CONDITION"))); initColorPicker($scope.condition.labelColor) $scope.pageStatus = { progress: 0, progressShow: false, progressComplete: false } $scope.previewData = { url: "" } $scope.clickBackImgBtn = function () { } function getLogicalList() { var condition = $scope.condition; var needList = [] if (condition.dataMod === 'serial') { for (var i = condition.start; i <= condition.end; i++) { needList.push(i); } } else { needList = condition.logicalCodeList.split("\n") for (var i = needList.length - 1; i >= 0; i--) { var item = needList[i]; needList[i] = $.trim(item); // 去空和去重 if (needList[i] === '') { // 必须倒删 needList.splice(i, 1) } } // 去重复 needList = needList.filter(function (item, i, self) { // 数组的indexOf是元素的全等匹配,如['1111','1'].indexOf('1')结果是1,而不是0 // 当前遍历元素在整个数组中出现的index 等于 当前遍历序列,则说明这个元素是新元素 返回true(加入到新数组needList),否则false var flag = self.indexOf(item) === i return flag; }) } return needList } //生成二维码 function getQRCodeImageData(text, label) { var condition = $scope.condition; var size = condition.size; var labelHeight = condition.labelHeight; var logoSize = condition.logoSize; //生成二维码 var qrDom = $("
"); qrDom.qrcode({ render: "canvas", // 渲染方式有table方式(IE兼容)和canvas方式 width: size, //宽度 height: size, //高度 text: text, //内容 typeNumber: -1,//计算模式 correctLevel: 2,//二维码纠错级别 background: "#ffffff",//背景颜色 foreground: "#000000" //二维码颜色 }); var canvas = qrDom.find("canvas")[0]; var context = canvas.getContext("2d"); var imgData = context.getImageData(0, 0, size, size);//获取到二维码图片数据流 //生成背景(用来绘制标签、背景图等) var backDom = $(""); var maxW = size; var maxH = size + labelHeight; var left = condition.qrcodeLeft; var top = condition.qrcodeTop; if (condition.background) { var backImgDom = $("#previewBackImg")[0]; maxW = condition.width = backImgDom.naturalWidth * (condition.backgroundScale / 100); maxH = condition.height = backImgDom.naturalHeight * (condition.backgroundScale / 100); left = condition.qrcodeLeft; top = condition.qrcodeTop; } else { maxW = condition.width = size; maxH = condition.height = size + labelHeight; left = 0; top = 0; } backDom.attr({"width": maxW, "height": maxH}); var backCanvas = backDom[0]; var ctx = backCanvas.getContext("2d"); //绘制背景图 if (condition.background) { ctx.drawImage($("#previewBackImg")[0], 0, 0, maxW, maxH); $scope.condition.backWidth = parseInt(maxW) $scope.condition.backHeight = parseInt(maxH) } // ctx.clearRect(0, 0, size, size + labelHeight);//清空绘制标题区域 ctx.putImageData(imgData, left, top);//合并二维码到带背景信息的画布 if (condition.fileSuffix === 'jpeg') { //标题绘制白色背景,避免变黑色 ctx.fillStyle = "#fff"; ctx.fillRect(left, size + top, size, labelHeight); } //绘制标题 ctx.font = "bold 24px Arial";//使用粗体,稍大的字号,否则打印出来的不清晰 ctx.textAlign = "center"; ctx.fillStyle = condition.labelColor; ctx.fillText(label, size / 2 + left, (size + labelHeight - 8) + top); //绘制logo if (condition.logo) { var logoBorder = 4; var centerSize = 2 * logoBorder + logoSize; //清空中心区域用来绘制logo ctx.rect((size - centerSize) / 2 + left, (size - centerSize) / 2 + top, centerSize, centerSize); ctx.fillStyle = "#fff"; ctx.fill(); //logo图片 ctx.drawImage($("#previewLogo")[0], (size - logoSize) / 2 + left, (size - logoSize) / 2 + top, logoSize, logoSize); //边框 0.5px 修复 ctx.lineWidth = 1; ctx.strokeStyle = "#aaa"; ctx.roundRect((size - centerSize) / 2 + left + 0.5, (size - centerSize) / 2 + top + 0.5, centerSize, centerSize, 6).stroke(); } var dataURL = backCanvas.toDataURL("image/" + condition.fileSuffix); return dataURL; } //转换base64到Blob对象,用以zip压缩 function convertImgDataToBlob(base64Data, fileSuffix) { var format = "image/" + fileSuffix; var base64 = base64Data; var code = window.atob(base64.split(",")[1]); var aBuffer = new window.ArrayBuffer(code.length); var uBuffer = new window.Uint8Array(aBuffer); for (var i = 0; i < code.length; i++) { uBuffer[i] = code.charCodeAt(i) & 0xff; } var blob = null; try { blob = new Blob([uBuffer], {type: format}); } catch (e) { window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder; if (e.name == 'TypeError' && window.BlobBuilder) { var bb = new window.BlobBuilder(); bb.append(uBuffer.buffer); blob = bb.getBlob("image/" + fileSuffix); } else if (e.name == "InvalidStateError") { blob = new Blob([aBuffer], {type: format}); } else { } } return blob; } if (BROWSER_TYPE === 'wechat') { $scope.showOpenBrowser = true } //判断访问终端 var browser = { versions: function () { var u = navigator.userAgent, app = navigator.appVersion; return { trident: u.indexOf('Trident') > -1, //IE内核 presto: u.indexOf('Presto') > -1, //opera内核 webKit: u.indexOf('AppleWebKit') > -1, //苹果、谷歌内核 gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') == -1, //火狐内核 mobile: !!u.match(/AppleWebKit.*Mobile.*/), //是否为移动终端 ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), //ios终端 android: u.indexOf('Android') > -1 || u.indexOf('Linux') > -1, //android终端或者uc浏览器 iPhone: u.indexOf('iPhone') > -1, //是否为iPhone或者QQHD浏览器 iPad: u.indexOf('iPad') > -1, //是否iPad webApp: u.indexOf('Safari') == -1, //是否web应该程序,没有头部与底部 weixin: u.indexOf('MicroMessenger') > -1, //是否微信 (2015-01-22新增) qq: u.match(/\sQQ/i) == " qq" //是否QQ }; }(), language: (navigator.browserLanguage || navigator.language).toLowerCase() }; //点击则生成图片:生成过程很慢,生成过程中请不要改表单数据! $scope.getZip = function () { if (BROWSER_TYPE === 'wechat') { $('#browser_type_error').modal(); return; } var condition = $scope.condition; var needList = getLogicalList(); if (needList.length === 0) { alert('没有需要生成的二维码') return; } var hasSubIndex = condition.subIndex; var subIndex = condition.subStart; var sumCodeCount = needList.length; if (hasSubIndex) { sumCodeCount = sumCodeCount * (condition.subEnd - condition.subStart + 1); } var index = 0;// 开始生成的index = 0 var zip; var mask; if (browser.versions.mobile || browser.versions.android || browser.versions.ios) { if (sumCodeCount > 100) { alert("超过100个二维码,无法正常生成。手机端一次性最多只能生成100个二维码,请修改参数减少二维码数量。"); return } } $.confirm({ content: "将生成二维码" + sumCodeCount + "个,确定?", buttons: { ok: { action: function () { zip = new JSZip(); //重置状态 $scope.pageStatus.progressComplete = false; $scope.pageStatus.progressShow = true; index = 0; run(); mask = new Mask("正在生成,请稍候,请不要切换或关闭页面。").show(); } }, } }); //使用timeout避免网页卡死 function run() { $timeout(function () { if (index >= needList.length) { over(); return; } var nowLogical = needList[index] var progress = Math.round(index * 100 / needList.length);//刷新进度 if (progress % 2 === 0) { //控制刷新次数 $scope.pageStatus.progress = progress; } var currentDomain = condition.domain; var pathname = condition.pathname; var qrcode = condition.prefix + "" + nowLogical; var url = currentDomain + pathname + qrcode; var label = qrcode; if (hasSubIndex) { label = label + "-" + subIndex; url = url + "&chargeIndex=" + subIndex } var dataURL = getQRCodeImageData(url, label); var fileContent = dataURL.split('base64,')[1]; zip.file(label + "." + condition.fileSuffix, fileContent, {base64: true}); // 如果子编号遍历完一轮,则逻辑码+1,然后再重置子编号 if (hasSubIndex) { subIndex++; if (subIndex > condition.subEnd) { index++; subIndex = condition.subStart; } } else { // 如果没有子编号,逻辑码+1 index++; } run();//继续run }); } function over() { $scope.pageStatus.progressComplete = true; var fileName = condition.fileName; if (!fileName) { fileName = "逻辑码" if (condition.dataMod === 'serial') { fileName = fileName + condition.start + (condition.end === condition.start ? "" : ("-" + condition.end)) } else { fileName = fileName + '批量自定义' + moment().format("YYYY-MM-DD hh_mm_ss") // 文件系统不支持: } if (condition.subIndex) { fileName = fileName + " 端口" + condition.subStart + (condition.subEnd === condition.subStart ? "" : ("-" + condition.subEnd)) } } zip.generateAsync({type: "blob"}).then(function (content) { // see FileSaver.js window.saveAs && saveAs(content, fileName + ".zip") }); $timeout(function () { $scope.pageStatus.progressShow = false; }, 5000); mask.remove() } }; //建立一個可存取到該file的url function getObjectURL(file) { var url = null; if (window.createObjectURL != undefined) { // basic url = window.createObjectURL(file); } else if (window.URL != undefined) { // mozilla(firefox) url = window.URL.createObjectURL(file); } else if (window.webkitURL != undefined) { // webkit or chrome url = window.webkitURL.createObjectURL(file); } return url; } //预览 $scope.previewQRCode = function () { var condition = $scope.condition; var currentDomain = condition.domain; var pathname = condition.pathname; var qrcode = condition.prefix + "6666"; var url = currentDomain + pathname + qrcode; var label = qrcode; if (condition.subIndex) { label = label + "-" + condition.subStart; url = url + "&chargeIndex=" + condition.subStart } var dataURL = getQRCodeImageData(url, label); $("#previewImg").attr({ "width": condition.width, "height": condition.height, "src": dataURL, "title": url }); $scope.previewData.url = url }; //缓存二维码制作方案 $scope.savePlan = function () { localStorage.setItem("QRCODE_CONDITION", JSON.stringify($scope.condition)); }; //事件绑定 $timeout(function () { // 实时刷新预览,深度监听 $scope.$watch("condition", function (newValue, oldValue) { if (newValue == oldValue) { // 如果两次值相等,则不刷新视图 } else { $scope.previewQRCode(); } }, true); $("#backImgFile").on("change", function () { var file = this.files[0]; if (!file.type.includes("image")) { toaster.pop("info", "提示", "请选择图片文件!"); } else { //预览logo var objUrl = getObjectURL(file); if (objUrl) { $("#previewBackImg").attr("src", objUrl); $scope.condition.backgroundPath = objUrl } } $scope.$apply();//刷新提示和数据 }); $("#logoFile").on("change", function () { //change说明用户需要logo $scope.condition.logo = true; var file = this.files[0]; if (!file.type.includes("image")) { toaster.pop("info", "提示", "请选择图片文件!"); } else { //预览logo var objUrl = getObjectURL(file); if (objUrl) { $("#previewLogo").attr("src", objUrl); $scope.condition.logoPath = objUrl } } $scope.$apply();//刷新提示和数据 }); $("#previewBackImg,#previewLogo").on('load', function () { // input file 没法绑定angular,当图片加载完后手动刷新预览 $scope.previewQRCode(); }); }); }]);