珠宝小程序前端代码
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1037 lines
38 KiB

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