signature.vue 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. <template>
  2. <view>
  3. <u-popup :show="show" round="15" mode="center" :closeable="true" :customStyle="{ width: '95%' }" @close="cancelSignature">
  4. <view class="popup">
  5. <view class="signature-contain">
  6. <view class="signature-main">
  7. <view class="title">请签字</view>
  8. <canvas disable-scroll="true" class="signature" :class="cid" canvas-id="cvs" @touchstart="touchstart" @touchmove="touchmove" @touchend="touchend"></canvas>
  9. <view class="signature-btns">
  10. <button class="btn" style="background-color: #9e9e9e" @tap="cancelSignature()">取消</button>
  11. <button class="btn" @tap="onOK()">确定</button>
  12. </view>
  13. </view>
  14. </view>
  15. </view>
  16. </u-popup>
  17. </view>
  18. </template>
  19. <script>
  20. let _keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
  21. var _utf8_encode = function (string) {
  22. string = string.replace(/\r\n/g, '\n');
  23. var utftext = '';
  24. for (var n = 0; n < string.length; n++) {
  25. var c = string.charCodeAt(n);
  26. if (c < 128) {
  27. utftext += String.fromCharCode(c);
  28. } else if (c > 127 && c < 2048) {
  29. utftext += String.fromCharCode((c >> 6) | 192);
  30. utftext += String.fromCharCode((c & 63) | 128);
  31. } else {
  32. utftext += String.fromCharCode((c >> 12) | 224);
  33. utftext += String.fromCharCode(((c >> 6) & 63) | 128);
  34. utftext += String.fromCharCode((c & 63) | 128);
  35. }
  36. }
  37. return utftext;
  38. };
  39. let base64encode = function (input) {
  40. var output = '';
  41. var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
  42. var i = 0;
  43. input = _utf8_encode(input);
  44. while (i < input.length) {
  45. chr1 = input.charCodeAt(i++);
  46. chr2 = input.charCodeAt(i++);
  47. chr3 = input.charCodeAt(i++);
  48. enc1 = chr1 >> 2;
  49. enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
  50. enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
  51. enc4 = chr3 & 63;
  52. if (isNaN(chr2)) {
  53. enc3 = enc4 = 64;
  54. } else if (isNaN(chr3)) {
  55. enc4 = 64;
  56. }
  57. output = output + _keyStr.charAt(enc1) + _keyStr.charAt(enc2) + _keyStr.charAt(enc3) + _keyStr.charAt(enc4);
  58. }
  59. return output;
  60. };
  61. export default {
  62. cxt: null,
  63. data() {
  64. return {
  65. ip: this.http.ip,
  66. VERSION: '1.0.0',
  67. cid: 'cvs',
  68. show: false,
  69. ctrl: null,
  70. listeners: [],
  71. prevView: '',
  72. draws: [],
  73. lines: [],
  74. line: null
  75. };
  76. },
  77. props: {
  78. value: {
  79. default: ''
  80. },
  81. title: {
  82. type: String,
  83. default: '请签字'
  84. },
  85. disabled: {
  86. type: Boolean,
  87. default: false
  88. }
  89. },
  90. watch: {
  91. value() {
  92. this.prevView = this.value;
  93. }
  94. },
  95. computed: {
  96. titles() {
  97. return this.title.split('');
  98. },
  99. absPrevView() {
  100. var pv = this.prevView;
  101. // if(pv){
  102. // pv = this.$wrapUrl(pv)
  103. // }
  104. return pv;
  105. }
  106. },
  107. mounted() {
  108. this.prevView = this.value;
  109. },
  110. methods: {
  111. onOK() {
  112. uni.showModal({
  113. title: '提示',
  114. content: '确定提交签名并签约?',
  115. success: (res) => {
  116. if (res.confirm) {
  117. uni.canvasToTempFilePath(
  118. {
  119. canvasId: this.cid,
  120. fileType: 'png',
  121. quality: 1, //图片质量
  122. success: (res) => {
  123. uni.uploadFile({
  124. url: this.ip + '/app/common/upload',
  125. filePath: res.tempFilePath,
  126. name: 'file',
  127. header: { Authorization: this.getUser().token },
  128. success: (res) => {
  129. let data = JSON.parse(res.data);
  130. if (data.code == 200) {
  131. this.$emit('sign', data.fileName);
  132. this.hideSignature();
  133. } else {
  134. uni.showModal({ content: data.msg, showCancel: false });
  135. }
  136. uni.hideLoading();
  137. },
  138. fail: (res) => {
  139. uni.hideLoading();
  140. uni.showModal({ content: '图片上传失败', showCancel: false });
  141. }
  142. });
  143. },
  144. fail: (err) => {}
  145. },
  146. this
  147. );
  148. }
  149. }
  150. });
  151. },
  152. touchSignature() {
  153. let sig = this.prevView;
  154. if (!sig || !sig.length) {
  155. this.showSignature();
  156. }
  157. },
  158. showSignature() {
  159. if (this.disabled) return;
  160. if (!this.ctrl) {
  161. this.initCtrl();
  162. } else if (!this.show) {
  163. this.clearSignature();
  164. this.show = true;
  165. }
  166. },
  167. async getSyncSignature() {
  168. this.showSignature();
  169. return await new Promise(async (resolve, reject) => {
  170. this.listeners.push((res) => {
  171. resolve(res);
  172. });
  173. });
  174. },
  175. cancelSignature() {
  176. this.listeners.map((f) => {
  177. f(null);
  178. });
  179. this.hideSignature();
  180. },
  181. hideSignature() {
  182. this.ctrl && this.ctrl.clear();
  183. this.show = false;
  184. },
  185. clearSignature() {
  186. this.ctrl && this.ctrl.clear();
  187. },
  188. async initCtrl() {
  189. this.show = true;
  190. let cxt = uni.createCanvasContext(this.cid, this);
  191. this.cxt = cxt;
  192. // cxt.clearRect(0,0,c.width,c.height);
  193. this.ctrl = {
  194. width: 0,
  195. height: 0,
  196. clear: () => {
  197. this.lines = [];
  198. let info = uni
  199. .createSelectorQuery()
  200. .in(this)
  201. .select('.' + this.cid);
  202. info.boundingClientRect((data) => {
  203. if (data) {
  204. cxt.clearRect(0, 0, data.width, data.height);
  205. if (data.width && data.height) {
  206. this.ctrl.width = data.width;
  207. this.ctrl.height = data.height;
  208. }
  209. }
  210. }).exec();
  211. this.redraw();
  212. },
  213. getValue: () => {
  214. if (!this.lines.length) return '';
  215. let svg = this._get_svg();
  216. // new Buff
  217. let b64 = base64encode(svg);
  218. let data = 'data:image/svg+xml;base64,' + b64;
  219. // console.log(svg);
  220. // console.log(data);
  221. return data;
  222. }
  223. };
  224. this.$nextTick(function () {
  225. this.ctrl.clear();
  226. });
  227. },
  228. _get_svg() {
  229. let r = -90;
  230. let paths = [];
  231. let raww = this.ctrl.width;
  232. let rawh = this.ctrl.height;
  233. let width = Math.abs(r) != 90 ? raww : rawh;
  234. let height = Math.abs(r) == 90 ? raww : rawh;
  235. let cx = raww / 2;
  236. let cy = rawh / 2;
  237. let PI = Math.PI;
  238. let R = (r || 0) % 360;
  239. let cosv = Math.cos((R * PI) / 180);
  240. let sinv = Math.sin((R * PI) / 180);
  241. let dcx = (width - raww) / 2;
  242. let dcy = (height - rawh) / 2;
  243. let trans = function (p) {
  244. if (!R) {
  245. return p;
  246. } else {
  247. let nx = (p.x - cx) * cosv - (p.y - cy) * sinv + cx;
  248. let ny = (p.x - cx) * sinv + (p.y - cy) * cosv + cy;
  249. return {
  250. x: nx + dcx,
  251. y: ny + dcy
  252. };
  253. }
  254. return p;
  255. };
  256. this.lines.map((l) => {
  257. if (l.points.length < 2) {
  258. return;
  259. }
  260. let sp = trans(l.start);
  261. let pts = [`M ${sp.x} ${Number(sp.y)}`];
  262. l.points.map((p) => {
  263. let np = trans(p);
  264. pts.push(`L ${np.x} ${Number(np.y)}`);
  265. });
  266. paths.push(`<path stroke-linejoin="round" stroke-linecap="round" stroke-width="3" stroke="rgb(0,0,0)" fill="none" d="${pts.join(' ')}"/>`);
  267. });
  268. 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(
  269. '\n'
  270. )}</svg>`;
  271. return svg;
  272. },
  273. _get_svg_raw() {
  274. let paths = [];
  275. this.lines.map((l) => {
  276. if (l.points.length < 2) {
  277. return;
  278. }
  279. let pts = [`M ${l.start.x} ${Number(l.start.y)}`];
  280. l.points.map((p) => {
  281. pts.push(`L ${p.x} ${Number(p.y)}`);
  282. });
  283. paths.push(`<path stroke-linejoin="round" stroke-linecap="round" stroke-width="3" stroke="rgb(0,0,0)" fill="none" d="${pts.join(' ')}"/>`);
  284. });
  285. let width = this.ctrl.width;
  286. let height = this.ctrl.height;
  287. 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(
  288. '\n'
  289. )}</svg>`;
  290. return svg;
  291. },
  292. _get_point(e) {
  293. return {
  294. x: e.changedTouches[0].x.toFixed(1),
  295. y: e.changedTouches[0].y.toFixed(1)
  296. };
  297. },
  298. touchstart(e) {
  299. let p = this._get_point(e);
  300. this.line = {
  301. start: p,
  302. points: [p]
  303. };
  304. this.lines.push(this.line);
  305. },
  306. touchmove(e) {
  307. let p = this._get_point(e);
  308. this.line.points.push(p);
  309. if (!this.tm) {
  310. this.tm = setTimeout(() => {
  311. this.redraw();
  312. this.tm = 0;
  313. }, 10);
  314. }
  315. },
  316. touchend(e) {
  317. let p = this._get_point(e);
  318. this.line.points.push(p);
  319. this.line.end = p;
  320. this.redraw();
  321. },
  322. redraw() {
  323. let cxt = this.cxt;
  324. cxt.setStrokeStyle('#000');
  325. cxt.setLineWidth(3);
  326. var last = null;
  327. this.lines.map((l) => {
  328. cxt.beginPath();
  329. if (l.points.length < 2) {
  330. return;
  331. }
  332. cxt.moveTo(l.start.x, l.start.y);
  333. l.points.map((p) => {
  334. cxt.lineTo(p.x, p.y);
  335. });
  336. cxt.stroke();
  337. });
  338. cxt.draw();
  339. },
  340. canvasIdErrorCallback: function (e) {
  341. console.error(e.detail.errMsg);
  342. }
  343. }
  344. };
  345. </script>
  346. <style lang="scss">
  347. .signature-contain {
  348. width: 100%;
  349. .signature-main {
  350. .title {
  351. text-align: center;
  352. }
  353. .signature {
  354. width: 100%;
  355. border: 1px dotted black;
  356. border-bottom: 1px dotted black;
  357. margin: 10px 0;
  358. }
  359. .btn {
  360. margin-top: 10px;
  361. }
  362. }
  363. }
  364. </style>