u-qrcode.vue 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118
  1. <template>
  2. <view class="uqrcode" :class="{ 'uqrcode-hide': hide }" :style="{ width: `${templateOptions.width}px`, height: `${templateOptions.height}px` }">
  3. <view class="uqrcode-canvas-wrapper">
  4. <!-- 画布 -->
  5. <!-- #ifndef APP-NVUE -->
  6. <canvas class="uqrcode-canvas" :id="canvasId" :canvas-id="canvasId" :type="type" :style="{
  7. width: `${templateOptions.canvasWidth}px`,
  8. height: `${templateOptions.canvasHeight}px`,
  9. transform: templateOptions.canvasTransform
  10. }" v-if="templateOptions.canvasDisplay" @click="onClick"></canvas>
  11. <!-- #endif -->
  12. <!-- nvue用gcanvas -->
  13. <!-- #ifdef APP-NVUE -->
  14. <gcanvas class="uqrcode-canvas" ref="gcanvas" :style="{
  15. width: `${templateOptions.canvasWidth}px`,
  16. height: `${templateOptions.canvasHeight}px`
  17. }" v-if="templateOptions.canvasDisplay" @click="onClick"></gcanvas>
  18. <!-- #endif -->
  19. </view>
  20. <!-- H5保存提示 -->
  21. <!-- #ifdef H5 -->
  22. <view class="uqrcode-h5-save" v-if="isH5Save">
  23. <slot name="h5save" :tempFilePath="tempFilePath">
  24. <image class="uqrcode-h5-save-image" :src="tempFilePath"></image>
  25. <text class="uqrcode-h5-save-text">{{ h5SaveIsDownload ? '若保存失败,' : '' }}请长按二维码进行保存</text>
  26. </slot>
  27. <view class="uqrcode-h5-save-close" @click.stop="isH5Save = false">
  28. <view class="uqrcode-h5-save-close-before"></view>
  29. <view class="uqrcode-h5-save-close-after"></view>
  30. </view>
  31. </view>
  32. <!-- #endif -->
  33. <!-- 加载效果 -->
  34. <view class="uqrcode-makeing" v-if="loading || makeing">
  35. <slot name="loading">
  36. <image class="uqrcode-makeing-image" :style="{ width: `${templateOptions.size / 4}px`, height: `${templateOptions.size / 4}px` }"
  37. src="data:image/gif;base64,R0lGODlhAAEAAfIEAOHh4SSsWuDg4N3d3f///wAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/wtYTVAgRGF0YVhNUDw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDYuMC1jMDAyIDc5LjE2NDQ4OCwgMjAyMC8wNy8xMC0yMjowNjo1MyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDIyLjAgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjAyODhGMzM4RDEwMTExRUM4MDhCRkVBQkE2QUZDQzkwIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjAyODhGMzM5RDEwMTExRUM4MDhCRkVBQkE2QUZDQzkwIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MDI4OEYzMzZEMTAxMTFFQzgwOEJGRUFCQTZBRkNDOTAiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MDI4OEYzMzdEMTAxMTFFQzgwOEJGRUFCQTZBRkNDOTAiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4B//79/Pv6+fj39vX08/Lx8O/u7ezr6uno5+bl5OPi4eDf3t3c29rZ2NfW1dTT0tHQ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">
  38. </image>
  39. </slot>
  40. </view>
  41. <!-- 错误处理 -->
  42. <view class="uqrcode-error" v-if="isError" @click="onClick">
  43. <slot name="error" :error="error">
  44. <text class="uqrcode-error-message">{{ error.errMsg }}</text>
  45. </slot>
  46. </view>
  47. </view>
  48. </template>
  49. <script>
  50. // #ifdef VUE3
  51. import {
  52. toRaw
  53. } from 'vue';
  54. // #endif
  55. /* 引入uQRCode核心js */
  56. import UQRCode from '../../js_sdk/uqrcode/uqrcode';
  57. /* 引入nvue所需模块 */
  58. // #ifdef APP-NVUE
  59. import {
  60. enable,
  61. WeexBridge
  62. } from '../../js_sdk/gcanvas';
  63. const modal = weex.requireModule('modal');
  64. // #endif
  65. /* 引入队列 */
  66. import {
  67. queueDraw,
  68. queueLoadImage
  69. } from '../../common/queue';
  70. /* 引入缓存图片 */
  71. import {
  72. cacheImageList
  73. } from '../../common/cache';
  74. let instance = null;
  75. export default {
  76. name: 'uqrcode',
  77. props: {
  78. /**
  79. * canvas组件id
  80. */
  81. canvasId: {
  82. type: String,
  83. required: true // canvasId在微信小程序初始值不能为空,created中赋值也不行,必须给一个值,否则挂载组件后无法绘制。不考虑用随机id,uuid
  84. },
  85. /**
  86. * 二维码内容
  87. */
  88. value: {
  89. type: [String, Number]
  90. },
  91. /**
  92. * 选项
  93. */
  94. options: {
  95. type: Object,
  96. default: () => {
  97. return {};
  98. }
  99. },
  100. /**
  101. * 二维码大小
  102. */
  103. size: {
  104. type: [String, Number],
  105. default: 200
  106. },
  107. /**
  108. * 二维码尺寸单位
  109. */
  110. sizeUnit: {
  111. type: String,
  112. default: 'px'
  113. },
  114. /**
  115. * 导出的文件类型
  116. */
  117. fileType: {
  118. type: String,
  119. default: 'png'
  120. },
  121. /**
  122. * 是否初始化组件后就开始生成
  123. */
  124. start: {
  125. type: Boolean,
  126. default: true
  127. },
  128. /**
  129. * 是否数据发生改变自动重绘
  130. */
  131. auto: {
  132. type: Boolean,
  133. default: true
  134. },
  135. /**
  136. * 隐藏组件
  137. */
  138. hide: {
  139. type: Boolean,
  140. default: false
  141. },
  142. /**
  143. * canvas 类型,微信小程序默认使用2d,非2d微信官方已放弃维护,问题比较多
  144. * 注意:微信小程序type2d手机上正常,PC上微信内打开小程序toDataURL报错,看后期微信官方团队会不会做兼容,不兼容的话只能在自行判断在PC使用非2d,或者直接提示用户请在手机上操作,微信团队的海报中心小程序就是这么做的
  145. */
  146. type: {
  147. type: String,
  148. default: () => {
  149. // #ifdef MP-WEIXIN
  150. return '2d';
  151. // #endif
  152. // #ifndef MP-WEIXIN
  153. return undefined;
  154. // #endif
  155. }
  156. },
  157. /**
  158. * 队列绘制,主要针对NVue端
  159. */
  160. queue: {
  161. type: Boolean,
  162. default: false
  163. },
  164. /**
  165. * 是否队列加载图片,可减少canvas发起的网络资源请求,节省服务器资源
  166. */
  167. isQueueLoadImage: {
  168. type: Boolean,
  169. default: false
  170. },
  171. /**
  172. * loading态
  173. */
  174. loading: {
  175. type: Boolean,
  176. default: false
  177. },
  178. /**
  179. * H5保存即自动下载(在支持的环境下),默认false为仅弹层提示用户需要长按图片保存,不会自动下载
  180. */
  181. h5SaveIsDownload: {
  182. type: Boolean,
  183. default: false
  184. },
  185. /**
  186. * H5下载名称
  187. */
  188. h5DownloadName: {
  189. type: String,
  190. default: 'uQRCode'
  191. }
  192. },
  193. data() {
  194. return {
  195. canvas: undefined,
  196. canvasContext: undefined,
  197. makeDelegate: undefined,
  198. drawDelegate: undefined,
  199. toTempFilePathDelegate: undefined,
  200. makeExecuted: false,
  201. makeing: false,
  202. drawing: false,
  203. isError: false,
  204. error: undefined,
  205. isH5Save: false,
  206. tempFilePath: '',
  207. templateOptions: {
  208. size: 0,
  209. width: 0, // 组件宽度
  210. height: 0,
  211. canvasWidth: 0, // canvas宽度
  212. canvasHeight: 0,
  213. canvasTransform: '',
  214. canvasDisplay: false
  215. },
  216. uqrcodeOptions: {
  217. data: ''
  218. },
  219. plugins: [],
  220. makeingPattern: [
  221. [
  222. [true, true, true, false, false, false, false, true, true, true],
  223. [true, true, true, false, false, false, false, true, true, true],
  224. [true, true, true, false, false, false, false, true, true, true],
  225. [true, true, true, false, false, false, false, true, true, true],
  226. [true, true, true, false, false, false, false, true, true, true],
  227. [true, true, true, false, false, false, false, true, true, true],
  228. [true, true, true, false, false, false, false, true, true, true],
  229. [true, true, true, true, true, true, true, true, true, true],
  230. [true, true, true, true, true, true, true, true, true, true],
  231. [true, true, true, true, true, true, true, true, true, true]
  232. ],
  233. [
  234. [true, true, true, true, true, true, true, true, true, true],
  235. [true, true, true, true, true, true, true, true, true, true],
  236. [true, true, true, true, true, true, true, true, true, true],
  237. [true, true, true, false, false, false, false, true, true, true],
  238. [true, true, true, false, false, false, false, true, true, true],
  239. [true, true, true, false, false, false, false, true, true, true],
  240. [true, true, true, false, false, false, false, false, false, false],
  241. [true, true, true, true, true, true, false, true, true, true],
  242. [true, true, true, true, true, true, false, true, true, true],
  243. [true, true, true, true, true, true, false, true, true, true]
  244. ],
  245. [
  246. [true, true, true, true, true, true, true, true, true, true],
  247. [true, true, true, true, true, true, true, true, true, true],
  248. [true, true, true, true, true, true, true, true, true, true],
  249. [true, true, true, false, false, false, false, true, true, true],
  250. [true, true, true, false, false, false, false, true, true, true],
  251. [true, true, true, true, true, true, true, false, false, false],
  252. [true, true, true, true, true, true, true, false, false, false],
  253. [true, true, true, true, true, true, true, false, false, false],
  254. [true, true, true, false, false, false, false, true, true, true],
  255. [true, true, true, false, false, false, false, true, true, true]
  256. ],
  257. [
  258. [true, true, true, true, true, true, true, true, true, true],
  259. [true, true, true, true, true, true, true, true, true, true],
  260. [true, true, true, true, true, true, true, true, true, true],
  261. [true, true, true, false, false, false, false, false, false, false],
  262. [true, true, true, false, false, false, false, false, false, false],
  263. [true, true, true, false, false, false, false, false, false, false],
  264. [true, true, true, false, false, false, false, false, false, false],
  265. [true, true, true, true, true, true, true, true, true, true],
  266. [true, true, true, true, true, true, true, true, true, true],
  267. [true, true, true, true, true, true, true, true, true, true]
  268. ]
  269. ]
  270. };
  271. },
  272. watch: {
  273. value: {
  274. handler() {
  275. if (this.auto) {
  276. this.remake();
  277. }
  278. }
  279. },
  280. size: {
  281. handler() {
  282. if (this.auto) {
  283. this.remake();
  284. }
  285. }
  286. },
  287. options: {
  288. handler() {
  289. if (this.auto) {
  290. this.remake();
  291. }
  292. },
  293. deep: true
  294. },
  295. makeing: {
  296. handler(val) {
  297. if (!val) {
  298. if (typeof this.toTempFilePathDelegate === 'function') {
  299. this.toTempFilePathDelegate();
  300. }
  301. }
  302. }
  303. }
  304. },
  305. mounted() {
  306. this.templateOptions.size = this.sizeUnit == 'rpx' ? uni.upx2px(this.size) : this.size;
  307. this.templateOptions.width = this.templateOptions.size;
  308. this.templateOptions.height = this.templateOptions.size;
  309. this.templateOptions.canvasWidth = this.templateOptions.size;
  310. this.templateOptions.canvasHeight = this.templateOptions.size;
  311. if (this.type == '2d') {
  312. // #ifndef MP-WEIXIN
  313. this.templateOptions.canvasTransform = `scale(${this.templateOptions.size / this.templateOptions.canvasWidth}, ${this.templateOptions.size /
  314. this.templateOptions.canvasHeight})`;
  315. // #endif
  316. } else {
  317. this.templateOptions.canvasTransform = `scale(${this.templateOptions.size / this.templateOptions.canvasWidth}, ${this.templateOptions.size /
  318. this.templateOptions.canvasHeight})`;
  319. }
  320. if (this.start) {
  321. this.make();
  322. }
  323. },
  324. methods: {
  325. /**
  326. * 获取模板选项
  327. */
  328. getTemplateOptions() {
  329. var size = this.sizeUnit == 'rpx' ? uni.upx2px(this.size) : this.size;
  330. return deepReplace(this.templateOptions, {
  331. size,
  332. width: size,
  333. height: size
  334. });
  335. },
  336. /**
  337. * 获取插件选项
  338. */
  339. getUqrcodeOptions() {
  340. return deepReplace(this.options, {
  341. data: String(this.value),
  342. size: Number(this.templateOptions.size)
  343. });
  344. },
  345. /**
  346. * 重置画布
  347. */
  348. resetCanvas(callback) {
  349. this.templateOptions.canvasDisplay = false;
  350. this.$nextTick(() => {
  351. this.templateOptions.canvasDisplay = true;
  352. this.$nextTick(() => {
  353. callback && callback();
  354. });
  355. });
  356. },
  357. /**
  358. * 绘制二维码
  359. */
  360. async draw(callback = {}, isDrawDelegate = false) {
  361. if (typeof callback.success != 'function') {
  362. callback.success = () => {};
  363. }
  364. if (typeof callback.fail != 'function') {
  365. callback.fail = () => {};
  366. }
  367. if (typeof callback.complete != 'function') {
  368. callback.complete = () => {};
  369. }
  370. if (this.drawing) {
  371. if (!isDrawDelegate) {
  372. this.drawDelegate = () => {
  373. this.draw(callback, true);
  374. };
  375. return;
  376. }
  377. } else {
  378. this.drawing = true;
  379. }
  380. if (!this.canvasId) {
  381. console.error('[uQRCode]: canvasId must be set!');
  382. this.isError = true;
  383. this.drawing = false;
  384. callback.fail({
  385. errMsg: '[uQRCode]: canvasId must be set!'
  386. });
  387. callback.complete({
  388. errMsg: '[uQRCode]: canvasId must be set!'
  389. });
  390. return;
  391. }
  392. if (!this.value) {
  393. console.error('[uQRCode]: value must be set!');
  394. this.isError = true;
  395. this.drawing = false;
  396. callback.fail({
  397. errMsg: '[uQRCode]: value must be set!'
  398. });
  399. callback.complete({
  400. errMsg: '[uQRCode]: value must be set!'
  401. });
  402. return;
  403. }
  404. /* 组件数据 */
  405. this.templateOptions = this.getTemplateOptions();
  406. /* uQRCode选项 */
  407. this.uqrcodeOptions = this.getUqrcodeOptions();
  408. /* 纠错等级兼容字母写法 */
  409. if (typeof this.uqrcodeOptions.errorCorrectLevel === 'string') {
  410. this.uqrcodeOptions.errorCorrectLevel = UQRCode.errorCorrectLevel[this.uqrcodeOptions.errorCorrectLevel];
  411. }
  412. /* nvue不支持动态修改gcanvas尺寸,除nvue外,默认使用useDynamicSize */
  413. // #ifndef APP-NVUE
  414. if (typeof this.options.useDynamicSize === 'undefined') {
  415. this.uqrcodeOptions.useDynamicSize = true;
  416. }
  417. // #endif
  418. // #ifdef APP-NVUE
  419. if (typeof this.options.useDynamicSize === 'undefined') {
  420. this.uqrcodeOptions.useDynamicSize = false;
  421. }
  422. // if (typeof this.options.drawReserve === 'undefined') {
  423. // this.uqrcodeOptions.drawReserve = true;
  424. // }
  425. // #endif
  426. /* 获取uQRCode实例 */
  427. const qr = instance = new UQRCode();
  428. /* 注册扩展 */
  429. this.plugins.forEach(p => qr.register(p.plugin));
  430. /* 设置uQRCode选项 */
  431. qr.setOptions(this.uqrcodeOptions);
  432. /* 调用制作二维码方法 */
  433. qr.make();
  434. /* 获取canvas上下文 */
  435. let canvasContext = null;
  436. // #ifndef APP-NVUE
  437. if (this.type === '2d') {
  438. // #ifdef MP-WEIXIN
  439. /* 微信小程序获取canvas2d上下文方式 */
  440. const canvas = (this.canvas = await new Promise(resolve => {
  441. uni
  442. .createSelectorQuery()
  443. .in(this) // 在组件内使用需要
  444. .select(`#${this.canvasId}`)
  445. .fields({
  446. node: true,
  447. size: true
  448. })
  449. .exec(res => {
  450. resolve(res[0].node);
  451. });
  452. }));
  453. canvasContext = this.canvasContext = canvas.getContext('2d');
  454. /* 2d的组件设置宽高与实际canvas绘制宽高不是一个,打个比方,组件size=200,canvas.width设置为100,那么绘制出来就是100=200,组件size=400,canvas.width设置为800,绘制大小还是800=400,所以无需理会下方返回的dynamicSize是多少,按dpr重新赋值给canvas即可 */
  455. this.templateOptions.canvasWidth = qr.size;
  456. this.templateOptions.canvasHeight = qr.size;
  457. this.templateOptions.canvasTransform = '';
  458. /* 使用dynamicSize+scale,可以解决小块间出现白线问题,dpr可以解决模糊问题 */
  459. const dpr = uni.getSystemInfoSync().pixelRatio;
  460. canvas.width = qr.dynamicSize * dpr;
  461. canvas.height = qr.dynamicSize * dpr;
  462. canvasContext.scale(dpr, dpr);
  463. /* 微信小程序获取图像方式 */
  464. qr.loadImage = this.getLoadImage(function(src) {
  465. /* 小程序下获取网络图片信息需先配置download域名白名单才能生效 */
  466. return new Promise((resolve, reject) => {
  467. const img = canvas.createImage();
  468. img.src = src;
  469. img.onload = () => {
  470. resolve(img);
  471. };
  472. img.onerror = err => {
  473. reject(err);
  474. };
  475. });
  476. });
  477. // #endif
  478. // #ifndef MP-WEIXIN
  479. /* 非微信小程序不支持2d,切换回uniapp获取canvas上下文方式 */
  480. canvasContext = this.canvasContext = uni.createCanvasContext(this.canvasId, this);
  481. /* 使用dynamicSize,可以解决小块间出现白线问题,再通过scale缩放至size,使其达到所设尺寸 */
  482. this.templateOptions.canvasWidth = qr.dynamicSize;
  483. this.templateOptions.canvasHeight = qr.dynamicSize;
  484. this.templateOptions.canvasTransform = `scale(${this.templateOptions.size / this.templateOptions.canvasWidth}, ${this.templateOptions.size /
  485. this.templateOptions.canvasHeight})`;
  486. /* uniapp获取图像方式 */
  487. qr.loadImage = this.getLoadImage(function(src) {
  488. return new Promise((resolve, reject) => {
  489. if (src.startsWith('http')) {
  490. uni.getImageInfo({
  491. src,
  492. success: res => {
  493. resolve(res.path);
  494. },
  495. fail: err => {
  496. reject(err);
  497. }
  498. });
  499. } else {
  500. if (src.startsWith('.')) {
  501. console.error('[uQRCode]: 本地图片路径仅支持绝对路径!');
  502. throw new Error('[uQRCode]: local image path only supports absolute path!');
  503. } else {
  504. resolve(src);
  505. }
  506. }
  507. });
  508. });
  509. // #endif
  510. } else {
  511. /* uniapp获取canvas上下文方式 */
  512. canvasContext = this.canvasContext = uni.createCanvasContext(this.canvasId, this);
  513. /* 使用dynamicSize,可以解决小块间出现白线问题,再通过scale缩放至size,使其达到所设尺寸 */
  514. this.templateOptions.canvasWidth = qr.dynamicSize;
  515. this.templateOptions.canvasHeight = qr.dynamicSize;
  516. this.templateOptions.canvasTransform = `scale(${this.templateOptions.size / this.templateOptions.canvasWidth}, ${this.templateOptions.size /
  517. this.templateOptions.canvasHeight})`;
  518. /* uniapp获取图像方式 */
  519. qr.loadImage = this.getLoadImage(function(src) {
  520. return new Promise((resolve, reject) => {
  521. /* getImageInfo在微信小程序的bug:本地路径返回路径会把开头的/或../移除,导致路径错误,解决方法:限制只能使用绝对路径 */
  522. if (src.startsWith('http')) {
  523. uni.getImageInfo({
  524. src,
  525. success: res => {
  526. resolve(res.path);
  527. },
  528. fail: err => {
  529. reject(err);
  530. }
  531. });
  532. } else {
  533. if (src.startsWith('.')) {
  534. console.error('[uQRCode]: 本地图片路径仅支持绝对路径!');
  535. throw new Error('[uQRCode]: local image path only supports absolute path!');
  536. } else {
  537. resolve(src);
  538. }
  539. }
  540. });
  541. });
  542. }
  543. // #endif
  544. // #ifdef APP-NVUE
  545. /* NVue获取canvas上下文方式 */
  546. const gcanvas = this.$refs['gcanvas'];
  547. const canvas = enable(gcanvas, {
  548. bridge: WeexBridge
  549. });
  550. canvasContext = this.canvasContext = canvas.getContext('2d');
  551. /* NVue获取图像方式 */
  552. qr.loadImage = this.getLoadImage(function(src) {
  553. return new Promise((resolve, reject) => {
  554. /* getImageInfo在nvue的bug:获取同一个路径的图片信息,同一时间第一次获取成功,后续失败,猜测是写入本地时产生文件写入冲突,所以没有返回,特别是对于网络资源 --- 已实现队列绘制,已解决此问题 */
  555. if (src.startsWith('.')) {
  556. console.error('[uQRCode]: 本地图片路径仅支持绝对路径!');
  557. throw new Error('[uQRCode]: local image path only supports absolute path!');
  558. } else {
  559. uni.getImageInfo({
  560. src,
  561. success: res => {
  562. resolve(res.path);
  563. },
  564. fail: err => {
  565. reject(err);
  566. }
  567. });
  568. }
  569. });
  570. });
  571. // #endif
  572. /* 设置uQRCode实例的canvas上下文 */
  573. qr.canvasContext = canvasContext;
  574. /* 延时等待页面重新绘制完毕 */
  575. setTimeout(() => {
  576. /* 从插件获取具体要调用哪一个扩展函数 */
  577. var plugin = this.plugins.find(p => p.name == qr.style);
  578. var drawCanvasName = plugin ? plugin.drawCanvas : 'drawCanvas';
  579. /* 虽然qr[drawCanvasName]是直接返回Promise的,但由于js内部this指向问题,故不能直接exec(qr[drawCanvasName])此方式执行,需要改成exec(() => qr[drawCanvasName]())才能正确获取this */
  580. var drawCanvas;
  581. if (this.queue) {
  582. drawCanvas = () => queueDraw.exec(() => qr[drawCanvasName]());
  583. // drawCanvas = () => queueDraw.exec(() => new Promise((resolve, reject) => {
  584. // setTimeout(() => {
  585. // qr[drawCanvasName]().then(resolve).catch(reject);
  586. // }, 1000);
  587. // }));
  588. } else {
  589. drawCanvas = () => qr[drawCanvasName]();
  590. }
  591. /* 调用绘制方法将二维码图案绘制到canvas上 */
  592. drawCanvas()
  593. .then(() => {
  594. if (this.drawDelegate) {
  595. /* 高频重绘纠正 */
  596. let delegate = this.drawDelegate;
  597. this.drawDelegate = undefined;
  598. delegate();
  599. } else {
  600. this.drawing = false;
  601. callback.success();
  602. }
  603. })
  604. .catch(err => {
  605. console.log(err);
  606. if (this.drawDelegate) {
  607. /* 高频重绘纠正 */
  608. let delegate = this.drawDelegate;
  609. this.drawDelegate = undefined;
  610. delegate();
  611. } else {
  612. this.drawing = false;
  613. this.isError = true;
  614. callback.fail(err);
  615. }
  616. })
  617. .finally(() => {
  618. callback.complete();
  619. });
  620. }, 300);
  621. },
  622. /**
  623. * 生成二维码
  624. */
  625. make(callback = {}) {
  626. this.makeExecuted = true;
  627. this.makeing = true;
  628. this.isError = false;
  629. if (typeof callback.success != 'function') {
  630. callback.success = () => {};
  631. }
  632. if (typeof callback.fail != 'function') {
  633. callback.fail = () => {};
  634. }
  635. if (typeof callback.complete != 'function') {
  636. callback.complete = () => {};
  637. }
  638. this.resetCanvas(() => {
  639. clearTimeout(this.makeDelegate);
  640. this.makeDelegate = setTimeout(() => {
  641. this.draw({
  642. success: () => {
  643. setTimeout(() => {
  644. callback.success();
  645. this.complete(true);
  646. }, 300);
  647. },
  648. fail: err => {
  649. callback.fail(err);
  650. this.error = err;
  651. this.complete(false, err.errMsg);
  652. },
  653. complete: () => {
  654. callback.complete();
  655. this.makeing = false;
  656. }
  657. });
  658. }, 300);
  659. });
  660. },
  661. /**
  662. * 重新生成
  663. */
  664. remake(callback) {
  665. this.$emit('change');
  666. this.make(callback);
  667. },
  668. /**
  669. * 生成完成
  670. */
  671. complete(success = true, errMsg = '') {
  672. if (success) {
  673. this.$emit('complete', {
  674. success
  675. });
  676. } else {
  677. this.$emit('complete', {
  678. success,
  679. errMsg
  680. });
  681. }
  682. },
  683. /**
  684. * 导出临时路径
  685. */
  686. toTempFilePath(callback = {}) {
  687. if (typeof callback.success != 'function') {
  688. callback.success = () => {};
  689. }
  690. if (typeof callback.fail != 'function') {
  691. callback.fail = () => {};
  692. }
  693. if (typeof callback.complete != 'function') {
  694. callback.complete = () => {};
  695. }
  696. if (!this.makeExecuted) {
  697. console.error('[uQRCode]: make() 方法从未调用!请先成功调用 make() 后再进行操作。');
  698. var err = {
  699. errMsg: '[uQRCode]: make() method has never been executed! please execute make() successfully before operating.'
  700. };
  701. callback.fail(err);
  702. callback.complete(err);
  703. return;
  704. }
  705. if (this.isError) {
  706. callback.fail(this.error);
  707. callback.complete(this.error);
  708. return;
  709. }
  710. if (this.makeing) {
  711. /* 如果还在生成状态,那当前操作将托管到委托,监听生成完成后再通过委托复调当前方法 */
  712. this.toTempFilePathDelegate = () => {
  713. this.toTempFilePath(callback);
  714. };
  715. return;
  716. } else {
  717. this.toTempFilePathDelegate = null;
  718. }
  719. // #ifndef APP-NVUE
  720. if (this.type === '2d') {
  721. // #ifdef MP-WEIXIN
  722. try {
  723. // #ifdef VUE3
  724. const dataURL = toRaw(this.canvas)
  725. .toDataURL();
  726. // #endif
  727. // #ifndef VUE3
  728. const dataURL = this.canvas.toDataURL();
  729. // #endif
  730. callback.success({
  731. tempFilePath: dataURL
  732. });
  733. callback.complete({
  734. tempFilePath: dataURL
  735. });
  736. } catch (e) {
  737. callback.fail(e);
  738. callback.complete(e);
  739. }
  740. // #endif
  741. } else {
  742. uni.canvasToTempFilePath({
  743. canvasId: this.canvasId,
  744. fileType: this.fileType,
  745. width: Number(this.templateOptions.canvasWidth),
  746. height: Number(this.templateOptions.canvasHeight),
  747. destWidth: Number(this.templateOptions.size),
  748. destHeight: Number(this.templateOptions.size),
  749. success: res => {
  750. callback.success(res);
  751. },
  752. fail: err => {
  753. callback.fail(err);
  754. },
  755. complete: () => {
  756. callback.complete();
  757. }
  758. },
  759. this
  760. );
  761. }
  762. // #endif
  763. // #ifdef APP-NVUE
  764. const dpr = uni.getSystemInfoSync().pixelRatio;
  765. this.canvasContext.toTempFilePath(
  766. 0,
  767. 0,
  768. this.templateOptions.canvasWidth * dpr,
  769. this.templateOptions.canvasHeight * dpr,
  770. this.templateOptions.size * dpr,
  771. this.templateOptions.size * dpr,
  772. '',
  773. 1,
  774. res => {
  775. callback.success(res);
  776. callback.complete(res);
  777. }
  778. );
  779. // #endif
  780. },
  781. /**
  782. * 保存
  783. */
  784. save(callback = {}) {
  785. if (typeof callback.success != 'function') {
  786. callback.success = () => {};
  787. }
  788. if (typeof callback.fail != 'function') {
  789. callback.fail = () => {};
  790. }
  791. if (typeof callback.complete != 'function') {
  792. callback.complete = () => {};
  793. }
  794. this.toTempFilePath({
  795. success: res => {
  796. // #ifndef H5
  797. if (this.type === '2d') {
  798. // #ifdef MP-WEIXIN
  799. /* 需要将 data:image/png;base64, 这段去除 writeFile 才能正常打开文件,否则是损坏文件,无法打开 */
  800. const reg = new RegExp('^data:image/png;base64,', 'g');
  801. const dataURL = res.tempFilePath.replace(reg, '');
  802. const fs = wx.getFileSystemManager();
  803. const tempFilePath = `${wx.env.USER_DATA_PATH}/${new Date().getTime()}${
  804. Math.random()
  805. .toString()
  806. .split('.')[1]
  807. }.png`;
  808. fs.writeFile({
  809. filePath: tempFilePath, // 要写入的文件路径 (本地路径)
  810. data: dataURL, // base64图片
  811. encoding: 'base64',
  812. success: res1 => {
  813. uni.saveImageToPhotosAlbum({
  814. filePath: tempFilePath,
  815. success: res2 => {
  816. callback.success(res2);
  817. },
  818. fail: err2 => {
  819. callback.fail(err2);
  820. },
  821. complete: () => {
  822. callback.complete();
  823. }
  824. });
  825. },
  826. fail: err => {
  827. callback.fail(err);
  828. },
  829. complete: () => {
  830. callback.complete();
  831. }
  832. });
  833. // #endif
  834. } else {
  835. uni.saveImageToPhotosAlbum({
  836. filePath: res.tempFilePath,
  837. success: res1 => {
  838. callback.success(res1);
  839. },
  840. fail: err1 => {
  841. callback.fail(err1);
  842. },
  843. complete: () => {
  844. callback.complete();
  845. }
  846. });
  847. }
  848. // #endif
  849. // #ifdef H5
  850. /* 可以在电脑浏览器下载,移动端iOS不行,安卓微信浏览器不行,安卓外部浏览器可以 */
  851. this.isH5Save = true;
  852. this.tempFilePath = res.tempFilePath;
  853. if (this.h5SaveIsDownload) {
  854. const aEle = document.createElement('a');
  855. aEle.download = this.h5DownloadName; // 设置下载的文件名,默认是'下载'
  856. aEle.href = res.tempFilePath;
  857. document.body.appendChild(aEle);
  858. aEle.click();
  859. aEle.remove(); // 下载之后把创建的元素删除
  860. }
  861. callback.success({
  862. errMsg: 'ok'
  863. });
  864. callback.complete({
  865. errMsg: 'ok'
  866. });
  867. // #endif
  868. },
  869. fail: err => {
  870. callback.fail(err);
  871. callback.complete(err);
  872. }
  873. });
  874. },
  875. /**
  876. * 注册click事件
  877. */
  878. onClick(e) {
  879. this.$emit('click', e);
  880. },
  881. /**
  882. * 获取实例
  883. */
  884. getInstance() {
  885. return instance;
  886. },
  887. /**
  888. * 注册扩展,组件仅支持注册type为style的drawCanvas扩展
  889. * @param {Object} plugin
  890. */
  891. registerStyle(plugin) {
  892. if (plugin.Type != 'style') {
  893. console.warn('[uQRCode]: registerStyle 仅支持注册 style 类型的扩展!');
  894. return {
  895. errMsg: 'registerStyle 仅支持注册 style 类型的扩展!'
  896. };
  897. }
  898. if (typeof plugin === 'function') {
  899. this.plugins.push({
  900. plugin,
  901. name: plugin.Name,
  902. drawCanvas: plugin.DrawCanvas
  903. });
  904. }
  905. },
  906. getLoadImage(loadImage) {
  907. var that = this;
  908. if (typeof loadImage == 'function') {
  909. return function(src) {
  910. /* 判断是否是队列加载图片的 */
  911. if (that.isQueueLoadImage) {
  912. /* 解决iOS APP||NVUE同时绘制多个二维码导致图片丢失需使用队列 */
  913. return queueLoadImage.exec(() => {
  914. return new Promise((resolve, reject) => {
  915. setTimeout(() => {
  916. const cache = cacheImageList.find(x => x.src == src);
  917. if (cache) {
  918. resolve(cache.img);
  919. } else {
  920. loadImage(src)
  921. .then(img => {
  922. cacheImageList.push({
  923. src,
  924. img
  925. });
  926. resolve(img);
  927. })
  928. .catch(err => {
  929. reject(err);
  930. });
  931. }
  932. }, 10);
  933. });
  934. });
  935. } else {
  936. return loadImage(src);
  937. }
  938. };
  939. } else {
  940. return function(src) {
  941. return Promise.resolve(src);
  942. };
  943. }
  944. }
  945. }
  946. };
  947. /**
  948. * 对象属性深度替换
  949. * @param {Object} o 原始对象/默认对象/被替换的对象
  950. * @param {Object} r 从这个对象里取值替换到o对象里
  951. * @return {Object} 替换后的新对象
  952. */
  953. function deepReplace(o = {}, r = {}, c = false) {
  954. let obj;
  955. if (c) {
  956. // 从源替换
  957. obj = o;
  958. } else {
  959. // 不替换源,copy一份备份来替换
  960. obj = {
  961. ...o
  962. };
  963. }
  964. for (let k in r) {
  965. var vr = r[k];
  966. if (vr != undefined) {
  967. if (vr.constructor == Object) {
  968. obj[k] = this.deepReplace(obj[k], vr);
  969. } else if (vr.constructor == String && !vr) {
  970. obj[k] = obj[k];
  971. } else {
  972. obj[k] = vr;
  973. }
  974. }
  975. }
  976. return obj;
  977. }
  978. </script>
  979. <style scoped>
  980. .uqrcode {
  981. position: relative;
  982. }
  983. .uqrcode-hide {
  984. position: fixed;
  985. left: 7500rpx;
  986. }
  987. .uqrcode-canvas {
  988. transform-origin: top left;
  989. }
  990. .uqrcode-makeing {
  991. position: absolute;
  992. top: 0;
  993. right: 0;
  994. bottom: 0;
  995. left: 0;
  996. z-index: 10;
  997. /* #ifndef APP-NVUE */
  998. display: flex;
  999. /* #endif */
  1000. justify-content: center;
  1001. align-items: center;
  1002. }
  1003. .uqrcode-makeing-image {
  1004. /* #ifndef APP-NVUE */
  1005. display: block;
  1006. max-width: 120px;
  1007. max-height: 120px;
  1008. /* #endif */
  1009. }
  1010. .uqrcode-error {
  1011. position: absolute;
  1012. top: 0;
  1013. right: 0;
  1014. bottom: 0;
  1015. left: 0;
  1016. /* #ifndef APP-NVUE */
  1017. display: flex;
  1018. /* #endif */
  1019. justify-content: center;
  1020. align-items: center;
  1021. }
  1022. .uqrcode-error-message {
  1023. font-size: 12px;
  1024. color: #939291;
  1025. }
  1026. /* #ifdef H5 */
  1027. .uqrcode-h5-save {
  1028. position: fixed;
  1029. top: 0;
  1030. right: 0;
  1031. bottom: 0;
  1032. left: 0;
  1033. z-index: 100;
  1034. background-color: rgba(0, 0, 0, 0.68);
  1035. display: flex;
  1036. flex-direction: column;
  1037. justify-content: center;
  1038. align-items: center;
  1039. }
  1040. .uqrcode-h5-save-image {
  1041. width: 512rpx;
  1042. height: 512rpx;
  1043. padding: 32rpx;
  1044. }
  1045. .uqrcode-h5-save-text {
  1046. margin-top: 20rpx;
  1047. font-size: 32rpx;
  1048. font-weight: 700;
  1049. color: #ffffff;
  1050. }
  1051. .uqrcode-h5-save-close {
  1052. position: relative;
  1053. margin-top: 72rpx;
  1054. width: 60rpx;
  1055. height: 60rpx;
  1056. border: 2rpx solid #ffffff;
  1057. border-radius: 60rpx;
  1058. padding: 10rpx;
  1059. }
  1060. .uqrcode-h5-save-close-before {
  1061. position: absolute;
  1062. top: 50%;
  1063. left: 50%;
  1064. transform: translate(-50%, -50%) rotate(45deg);
  1065. width: 40rpx;
  1066. height: 4rpx;
  1067. background: #ffffff;
  1068. }
  1069. .uqrcode-h5-save-close-after {
  1070. position: absolute;
  1071. top: 50%;
  1072. left: 50%;
  1073. transform: translate(-50%, -50%) rotate(-45deg);
  1074. width: 40rpx;
  1075. height: 4rpx;
  1076. background: #ffffff;
  1077. }
  1078. /* #endif */
  1079. </style>