signature.vue 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  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. console.log('dx');
  110. },
  111. methods: {
  112. onOK() {
  113. uni.showModal({
  114. title: '提示',
  115. content: '确定提交签名并签约?',
  116. success: (res) => {
  117. if (res.confirm) {
  118. uni.canvasToTempFilePath(
  119. {
  120. canvasId: this.cid,
  121. fileType: 'png',
  122. quality: 1, //图片质量
  123. success: (res) => {
  124. uni.uploadFile({
  125. url: this.ip + '/app/common/upload',
  126. filePath: res.tempFilePath,
  127. name: 'file',
  128. header: { Authorization: this.getUser().token },
  129. success: (res) => {
  130. let data = JSON.parse(res.data);
  131. if (data.code == 200) {
  132. this.$emit('sign', data.fileName);
  133. this.hideSignature();
  134. } else {
  135. uni.showModal({ content: data.msg, showCancel: false });
  136. }
  137. uni.hideLoading();
  138. },
  139. fail: (res) => {
  140. uni.hideLoading();
  141. uni.showModal({ content: '图片上传失败', showCancel: false });
  142. }
  143. });
  144. },
  145. fail: (err) => {}
  146. },
  147. this
  148. );
  149. }
  150. }
  151. });
  152. },
  153. touchSignature() {
  154. let sig = this.prevView;
  155. if (!sig || !sig.length) {
  156. this.showSignature();
  157. }
  158. },
  159. showSignature() {
  160. if (this.disabled) return;
  161. if (!this.ctrl) {
  162. this.initCtrl();
  163. } else if (!this.show) {
  164. this.clearSignature();
  165. this.show = true;
  166. }
  167. },
  168. async getSyncSignature() {
  169. this.showSignature();
  170. return await new Promise(async (resolve, reject) => {
  171. this.listeners.push((res) => {
  172. resolve(res);
  173. });
  174. });
  175. },
  176. cancelSignature() {
  177. this.listeners.map((f) => {
  178. f(null);
  179. });
  180. this.hideSignature();
  181. },
  182. hideSignature() {
  183. this.ctrl && this.ctrl.clear();
  184. this.show = false;
  185. },
  186. clearSignature() {
  187. this.ctrl && this.ctrl.clear();
  188. },
  189. async initCtrl() {
  190. this.show = true;
  191. let cxt = uni.createCanvasContext(this.cid, this);
  192. this.cxt = cxt;
  193. // cxt.clearRect(0,0,c.width,c.height);
  194. this.ctrl = {
  195. width: 0,
  196. height: 0,
  197. clear: () => {
  198. this.lines = [];
  199. let info = uni
  200. .createSelectorQuery()
  201. .in(this)
  202. .select('.' + this.cid);
  203. info.boundingClientRect((data) => {
  204. if (data) {
  205. cxt.clearRect(0, 0, data.width, data.height);
  206. if (data.width && data.height) {
  207. this.ctrl.width = data.width;
  208. this.ctrl.height = data.height;
  209. }
  210. }
  211. }).exec();
  212. this.redraw();
  213. },
  214. getValue: () => {
  215. if (!this.lines.length) return '';
  216. let svg = this._get_svg();
  217. // new Buff
  218. let b64 = base64encode(svg);
  219. let data = 'data:image/svg+xml;base64,' + b64;
  220. // console.log(svg);
  221. // console.log(data);
  222. return data;
  223. }
  224. };
  225. this.$nextTick(function () {
  226. this.ctrl.clear();
  227. });
  228. },
  229. _get_svg() {
  230. let r = -90;
  231. let paths = [];
  232. let raww = this.ctrl.width;
  233. let rawh = this.ctrl.height;
  234. let width = Math.abs(r) != 90 ? raww : rawh;
  235. let height = Math.abs(r) == 90 ? raww : rawh;
  236. let cx = raww / 2;
  237. let cy = rawh / 2;
  238. let PI = Math.PI;
  239. let R = (r || 0) % 360;
  240. let cosv = Math.cos((R * PI) / 180);
  241. let sinv = Math.sin((R * PI) / 180);
  242. let dcx = (width - raww) / 2;
  243. let dcy = (height - rawh) / 2;
  244. let trans = function (p) {
  245. if (!R) {
  246. return p;
  247. } else {
  248. let nx = (p.x - cx) * cosv - (p.y - cy) * sinv + cx;
  249. let ny = (p.x - cx) * sinv + (p.y - cy) * cosv + cy;
  250. return {
  251. x: nx + dcx,
  252. y: ny + dcy
  253. };
  254. }
  255. return p;
  256. };
  257. this.lines.map((l) => {
  258. if (l.points.length < 2) {
  259. return;
  260. }
  261. let sp = trans(l.start);
  262. let pts = [`M ${sp.x} ${Number(sp.y)}`];
  263. l.points.map((p) => {
  264. let np = trans(p);
  265. pts.push(`L ${np.x} ${Number(np.y)}`);
  266. });
  267. paths.push(`<path stroke-linejoin="round" stroke-linecap="round" stroke-width="3" stroke="rgb(0,0,0)" fill="none" d="${pts.join(' ')}"/>`);
  268. });
  269. 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(
  270. '\n'
  271. )}</svg>`;
  272. return svg;
  273. },
  274. _get_svg_raw() {
  275. let paths = [];
  276. this.lines.map((l) => {
  277. if (l.points.length < 2) {
  278. return;
  279. }
  280. let pts = [`M ${l.start.x} ${Number(l.start.y)}`];
  281. l.points.map((p) => {
  282. pts.push(`L ${p.x} ${Number(p.y)}`);
  283. });
  284. paths.push(`<path stroke-linejoin="round" stroke-linecap="round" stroke-width="3" stroke="rgb(0,0,0)" fill="none" d="${pts.join(' ')}"/>`);
  285. });
  286. let width = this.ctrl.width;
  287. let height = this.ctrl.height;
  288. 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(
  289. '\n'
  290. )}</svg>`;
  291. return svg;
  292. },
  293. _get_point(e) {
  294. return {
  295. x: e.changedTouches[0].x.toFixed(1),
  296. y: e.changedTouches[0].y.toFixed(1)
  297. };
  298. },
  299. touchstart(e) {
  300. let p = this._get_point(e);
  301. this.line = {
  302. start: p,
  303. points: [p]
  304. };
  305. this.lines.push(this.line);
  306. },
  307. touchmove(e) {
  308. let p = this._get_point(e);
  309. this.line.points.push(p);
  310. if (!this.tm) {
  311. this.tm = setTimeout(() => {
  312. this.redraw();
  313. this.tm = 0;
  314. }, 10);
  315. }
  316. },
  317. touchend(e) {
  318. let p = this._get_point(e);
  319. this.line.points.push(p);
  320. this.line.end = p;
  321. this.redraw();
  322. },
  323. redraw() {
  324. let cxt = this.cxt;
  325. cxt.setStrokeStyle('#000');
  326. cxt.setLineWidth(3);
  327. var last = null;
  328. this.lines.map((l) => {
  329. cxt.beginPath();
  330. if (l.points.length < 2) {
  331. return;
  332. }
  333. cxt.moveTo(l.start.x, l.start.y);
  334. l.points.map((p) => {
  335. cxt.lineTo(p.x, p.y);
  336. });
  337. cxt.stroke();
  338. });
  339. cxt.draw();
  340. },
  341. canvasIdErrorCallback: function (e) {
  342. console.error(e.detail.errMsg);
  343. }
  344. }
  345. };
  346. </script>
  347. <style lang="scss">
  348. .signature-contain {
  349. width: 100%;
  350. .signature-main {
  351. .title {
  352. text-align: center;
  353. }
  354. .signature {
  355. width: 100%;
  356. border: 1px dotted black;
  357. border-bottom: 1px dotted black;
  358. margin: 10px 0;
  359. }
  360. .btn {
  361. margin-top: 10px;
  362. }
  363. }
  364. }
  365. </style>