<template> <view> <u-popup :show="show" round="15" mode="center" :closeable="true" :customStyle="{ width: '95%' }" @close="cancelSignature"> <view class="popup"> <view class="signature-contain"> <view class="signature-main"> <view class="title">请签字</view> <canvas disable-scroll="true" class="signature" :class="cid" canvas-id="cvs" @touchstart="touchstart" @touchmove="touchmove" @touchend="touchend"></canvas> <view class="signature-btns"> <button class="btn" style="background-color: #9e9e9e" @tap="cancelSignature()">取消</button> <button class="btn" @tap="onOK()">确定</button> </view> </view> </view> </view> </u-popup> </view> </template> <script> let _keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; var _utf8_encode = function (string) { string = string.replace(/\r\n/g, '\n'); var utftext = ''; for (var n = 0; n < string.length; n++) { var c = string.charCodeAt(n); if (c < 128) { utftext += String.fromCharCode(c); } else if (c > 127 && c < 2048) { utftext += String.fromCharCode((c >> 6) | 192); utftext += String.fromCharCode((c & 63) | 128); } else { utftext += String.fromCharCode((c >> 12) | 224); utftext += String.fromCharCode(((c >> 6) & 63) | 128); utftext += String.fromCharCode((c & 63) | 128); } } return utftext; }; let base64encode = function (input) { var output = ''; var chr1, chr2, chr3, enc1, enc2, enc3, enc4; var i = 0; input = _utf8_encode(input); while (i < input.length) { chr1 = input.charCodeAt(i++); chr2 = input.charCodeAt(i++); chr3 = input.charCodeAt(i++); enc1 = chr1 >> 2; enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); enc4 = chr3 & 63; if (isNaN(chr2)) { enc3 = enc4 = 64; } else if (isNaN(chr3)) { enc4 = 64; } output = output + _keyStr.charAt(enc1) + _keyStr.charAt(enc2) + _keyStr.charAt(enc3) + _keyStr.charAt(enc4); } return output; }; export default { cxt: null, data() { return { ip: this.http.ip, VERSION: '1.0.0', cid: 'cvs', show: false, ctrl: null, listeners: [], prevView: '', draws: [], lines: [], line: null }; }, props: { value: { default: '' }, title: { type: String, default: '请签字' }, disabled: { type: Boolean, default: false } }, watch: { value() { this.prevView = this.value; } }, computed: { titles() { return this.title.split(''); }, absPrevView() { var pv = this.prevView; // if(pv){ // pv = this.$wrapUrl(pv) // } return pv; } }, mounted() { this.prevView = this.value; }, methods: { onOK() { uni.showModal({ title: '提示', content: '确定提交签名并签约?', success: (res) => { if (res.confirm) { uni.canvasToTempFilePath( { canvasId: this.cid, fileType: 'png', quality: 1, //图片质量 success: (res) => { uni.uploadFile({ url: this.ip + '/app/common/upload', filePath: res.tempFilePath, name: 'file', header: { Authorization: this.getUser().token }, success: (res) => { let data = JSON.parse(res.data); if (data.code == 200) { this.$emit('sign', data.fileName); this.hideSignature(); } else { uni.showModal({ content: data.msg, showCancel: false }); } uni.hideLoading(); }, fail: (res) => { uni.hideLoading(); uni.showModal({ content: '图片上传失败', showCancel: false }); } }); }, fail: (err) => {} }, this ); } } }); }, touchSignature() { let sig = this.prevView; if (!sig || !sig.length) { this.showSignature(); } }, showSignature() { if (this.disabled) return; if (!this.ctrl) { this.initCtrl(); } else if (!this.show) { this.clearSignature(); this.show = true; } }, async getSyncSignature() { this.showSignature(); return await new Promise(async (resolve, reject) => { this.listeners.push((res) => { resolve(res); }); }); }, cancelSignature() { this.listeners.map((f) => { f(null); }); this.hideSignature(); }, hideSignature() { this.ctrl && this.ctrl.clear(); this.show = false; }, clearSignature() { this.ctrl && this.ctrl.clear(); }, async initCtrl() { this.show = true; let cxt = uni.createCanvasContext(this.cid, this); this.cxt = cxt; // cxt.clearRect(0,0,c.width,c.height); this.ctrl = { width: 0, height: 0, clear: () => { this.lines = []; let info = uni .createSelectorQuery() .in(this) .select('.' + this.cid); info.boundingClientRect((data) => { if (data) { cxt.clearRect(0, 0, data.width, data.height); if (data.width && data.height) { this.ctrl.width = data.width; this.ctrl.height = data.height; } } }).exec(); this.redraw(); }, getValue: () => { if (!this.lines.length) return ''; let svg = this._get_svg(); // new Buff let b64 = base64encode(svg); let data = 'data:image/svg+xml;base64,' + b64; // console.log(svg); // console.log(data); return data; } }; this.$nextTick(function () { this.ctrl.clear(); }); }, _get_svg() { let r = -90; let paths = []; let raww = this.ctrl.width; let rawh = this.ctrl.height; let width = Math.abs(r) != 90 ? raww : rawh; let height = Math.abs(r) == 90 ? raww : rawh; let cx = raww / 2; let cy = rawh / 2; let PI = Math.PI; let R = (r || 0) % 360; let cosv = Math.cos((R * PI) / 180); let sinv = Math.sin((R * PI) / 180); let dcx = (width - raww) / 2; let dcy = (height - rawh) / 2; let trans = function (p) { if (!R) { return p; } else { let nx = (p.x - cx) * cosv - (p.y - cy) * sinv + cx; let ny = (p.x - cx) * sinv + (p.y - cy) * cosv + cy; return { x: nx + dcx, y: ny + dcy }; } return p; }; this.lines.map((l) => { if (l.points.length < 2) { return; } let sp = trans(l.start); let pts = [`M ${sp.x} ${Number(sp.y)}`]; l.points.map((p) => { let np = trans(p); pts.push(`L ${np.x} ${Number(np.y)}`); }); paths.push(`<path stroke-linejoin="round" stroke-linecap="round" stroke-width="3" stroke="rgb(0,0,0)" fill="none" d="${pts.join(' ')}"/>`); }); let svg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="${width}" height="${height}">${paths.join( '\n' )}</svg>`; return svg; }, _get_svg_raw() { let paths = []; this.lines.map((l) => { if (l.points.length < 2) { return; } let pts = [`M ${l.start.x} ${Number(l.start.y)}`]; l.points.map((p) => { pts.push(`L ${p.x} ${Number(p.y)}`); }); paths.push(`<path stroke-linejoin="round" stroke-linecap="round" stroke-width="3" stroke="rgb(0,0,0)" fill="none" d="${pts.join(' ')}"/>`); }); let width = this.ctrl.width; let height = this.ctrl.height; let svg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="${width}" height="${height}" transform="rotate(-90)">${paths.join( '\n' )}</svg>`; return svg; }, _get_point(e) { return { x: e.changedTouches[0].x.toFixed(1), y: e.changedTouches[0].y.toFixed(1) }; }, touchstart(e) { let p = this._get_point(e); this.line = { start: p, points: [p] }; this.lines.push(this.line); }, touchmove(e) { let p = this._get_point(e); this.line.points.push(p); if (!this.tm) { this.tm = setTimeout(() => { this.redraw(); this.tm = 0; }, 10); } }, touchend(e) { let p = this._get_point(e); this.line.points.push(p); this.line.end = p; this.redraw(); }, redraw() { let cxt = this.cxt; cxt.setStrokeStyle('#000'); cxt.setLineWidth(3); var last = null; this.lines.map((l) => { cxt.beginPath(); if (l.points.length < 2) { return; } cxt.moveTo(l.start.x, l.start.y); l.points.map((p) => { cxt.lineTo(p.x, p.y); }); cxt.stroke(); }); cxt.draw(); }, canvasIdErrorCallback: function (e) { console.error(e.detail.errMsg); } } }; </script> <style lang="scss"> .signature-contain { width: 100%; .signature-main { .title { text-align: center; } .signature { width: 100%; border: 1px dotted black; border-bottom: 1px dotted black; margin: 10px 0; } .btn { margin-top: 10px; } } } </style>