u-qrcode.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. <template>
  2. <view class="u-qrcode"
  3. :id="rootId"
  4. :style="{
  5. width: useRootHeightAndWidth ? '100%' : 'auto',
  6. height: useRootHeightAndWidth ? '100%' : 'auto',
  7. }"
  8. @longpress="longpress">
  9. <view class="u-qrcode__content" @click="preview">
  10. <!-- #ifndef APP-NVUE || APP-VUE -->
  11. <canvas
  12. class="u-qrcode__canvas"
  13. :id="cid"
  14. :canvas-id="cid"
  15. type="2d"
  16. :style="{ width: sizeLocal + unit, height: sizeLocal + unit }" />
  17. <!-- #endif -->
  18. <!-- #ifdef APP-VUE -->
  19. <canvas
  20. class="u-qrcode__canvas"
  21. :id="cid"
  22. :canvas-id="cid"
  23. :style="{ width: sizeLocal + unit, height: sizeLocal + unit }" />
  24. <!-- #endif -->
  25. <!-- #ifdef APP-NVUE -->
  26. <web-view v-if="icon != ''" ref="web" src="/static/app-plus/up-canvas/local.html"
  27. :style="'width:' + sizeLocal + 'px;height:' + sizeLocal + 'px'"
  28. @onPostMessage="_onMessage" />
  29. <gcanvas v-else class="u-qrcode__canvas" ref="gcanvess"
  30. :style="{ width: sizeLocal + unit, height: sizeLocal + unit }">
  31. </gcanvas>
  32. <!-- #endif -->
  33. <view v-if="showLoading && loading" class="u-qrcode__loading"
  34. :style="{ width: sizeLocal + unit, height: sizeLocal + unit }">
  35. <up-loading-icon vertical :text="loadingText" textSize="14px"></up-loading-icon>
  36. </view>
  37. </view>
  38. </view>
  39. </template>
  40. <script>
  41. import QRCode from "./qrcode.js"
  42. // #ifdef APP-NVUE
  43. // https://github.com/dcloudio/NvueCanvasDemo/blob/master/README.md
  44. import {
  45. enable,
  46. WeexBridge,
  47. Image as GImage
  48. } from '../../libs/util/gcanvas/index.js';
  49. // #endif
  50. let qrcode
  51. export default {
  52. name: "u-qrcode",
  53. props: {
  54. cid: {
  55. type: String,
  56. default: () => `u-qrcode-canvas${Math.floor(Math.random() * 1000000)}`
  57. },
  58. size: {
  59. type: Number,
  60. default: 200
  61. },
  62. unit: {
  63. type: String,
  64. default: 'px'
  65. },
  66. show: {
  67. type: Boolean,
  68. default: true
  69. },
  70. val: {
  71. type: String,
  72. default: ''
  73. },
  74. background: {
  75. type: String,
  76. default: '#ffffff'
  77. },
  78. foreground: {
  79. type: String,
  80. default: '#000000'
  81. },
  82. pdground: {
  83. type: String,
  84. default: '#000000'
  85. },
  86. icon: {
  87. type: String,
  88. default: ''
  89. },
  90. iconSize: {
  91. type: Number,
  92. default: 40
  93. },
  94. lv: {
  95. type: Number,
  96. default: 3
  97. },
  98. onval: {
  99. type: Boolean,
  100. default: true
  101. },
  102. loadMake: {
  103. type: Boolean,
  104. default: true
  105. },
  106. usingComponents: {
  107. type: Boolean,
  108. default: true
  109. },
  110. showLoading: {
  111. type: Boolean,
  112. default: true
  113. },
  114. loadingText: {
  115. type: String,
  116. default: '生成中'
  117. },
  118. allowPreview: {
  119. type: Boolean,
  120. default: false
  121. },
  122. // 是否使用根节点宽高
  123. useRootHeightAndWidth: {
  124. type: Boolean,
  125. default: () => false
  126. },
  127. },
  128. emits: ['result', 'longpressCallback'],
  129. data() {
  130. return {
  131. loading: false,
  132. result: '',
  133. popupShow: false,
  134. list: [
  135. {
  136. name: '保存二维码',
  137. }
  138. ],
  139. rootId: `rootId${Number(Math.random() * 100).toFixed(0)}`,
  140. ganvas: null,
  141. canvasObj: {},
  142. sizeLocal: this.size,
  143. ctx: null, // ctx 在new Qrcode 时js文件内部设置
  144. canvas: null, // ctx 在new Qrcode 时js文件内部设置
  145. _ready: false
  146. }
  147. },
  148. async mounted(){
  149. // 如果使用根节点的宽高 则 重新设置 size
  150. if(this.useRootHeightAndWidth){
  151. await this.setNewSize()
  152. }
  153. this.canvas = await this.getCanvasNode(this.cid)
  154. // #ifdef APP-NVUE
  155. this.isNvue = true
  156. /*获取绘图所需的上下文,目前不支持3d*/
  157. this.ctx = this.canvas.getContext('2d')
  158. // #endif
  159. // #ifndef APP-NVUE
  160. this.ctx = this.getContext()
  161. // #endif
  162. if (this.loadMake) {
  163. if (!this._empty(this.val)) {
  164. setTimeout(() => {
  165. setTimeout(()=>{
  166. this._makeCode()
  167. })
  168. }, 0);
  169. }
  170. }
  171. },
  172. methods: {
  173. _onMessage(e) {
  174. // console.log('post message', e)
  175. const message = e.detail.data[0]
  176. switch (message.action) {
  177. // web-view 初始化完毕
  178. case 'onJSBridgeReady':
  179. this._ready = true
  180. this.$refs.web.evalJs('setContent('+JSON.stringify(this.$props) +')')
  181. break
  182. // qrcodeOk
  183. case 'qrcodeOk':
  184. this._result(message.imageData)
  185. // this.$emit('load')
  186. break
  187. }
  188. },
  189. _makeCode() {
  190. let that = this
  191. if (!this._empty(this.val)) {
  192. // #ifndef APP-NVUE
  193. this.loading = true
  194. // #endif
  195. // nvue下时因为gcanvas的GImage不生效,因此icon模式会采用webview
  196. if ((this.icon == '' && that.isNvue) || !that.isNvue) {
  197. qrcode = new QRCode({
  198. vuectx: that, // 上下文环境
  199. canvasId: that.cid, // canvas-id
  200. canvas: that.canvas,
  201. ctx: that.ctx,
  202. isNvue: that.isNvue,
  203. usingComponents: that.usingComponents, // 是否是自定义组件
  204. showLoading: false, // 是否显示loading
  205. loadingText: that.loadingText, // loading文字
  206. text: that.val, // 生成内容
  207. size: that.sizeLocal, // 二维码大小
  208. background: that.background, // 背景色
  209. foreground: that.foreground, // 前景色
  210. pdground: that.pdground, // 定位角点颜色
  211. correctLevel: that.lv, // 容错级别
  212. image: that.icon, // 二维码图标
  213. imageSize: that.iconSize,// 二维码图标大小
  214. cbResult: function (res) { // 生成二维码的回调
  215. that._result(res)
  216. },
  217. });
  218. }
  219. } else {
  220. uni.showToast({
  221. title: '二维码内容不能为空',
  222. icon: 'none',
  223. duration: 2000
  224. });
  225. }
  226. },
  227. _clearCode() {
  228. this._result('')
  229. qrcode.clear()
  230. },
  231. _saveCode() {
  232. let that = this;
  233. if (this.result != "") {
  234. uni.saveImageToPhotosAlbum({
  235. filePath: that.result,
  236. success: function () {
  237. uni.showToast({
  238. title: '二维码保存成功',
  239. icon: 'success',
  240. duration: 2000
  241. });
  242. }
  243. });
  244. } else {
  245. this.toTempFilePath({
  246. success: res => {
  247. that.result = res.tempFilePath
  248. uni.saveImageToPhotosAlbum({
  249. filePath: that.result,
  250. success: function () {
  251. uni.showToast({
  252. title: '二维码保存成功',
  253. icon: 'success',
  254. duration: 2000
  255. });
  256. }
  257. });
  258. },
  259. fail: err => {
  260. }
  261. })
  262. }
  263. },
  264. preview(e) {
  265. // 预览图片
  266. // console.log(this.result)
  267. if (this.allowPreview) {
  268. uni.previewImage({
  269. urls: [this.result],
  270. longPressActions: {
  271. itemList: ['保存二维码图片'],
  272. success: function(data) {
  273. // console.log('选中了第' + (data.tapIndex + 1) + '个按钮,第' + (data.index + 1) + '张图片');
  274. switch (data.tapIndex) {
  275. case 0:
  276. that._saveCode();
  277. break;
  278. }
  279. },
  280. fail: function(err) {
  281. console.log(err.errMsg);
  282. }
  283. }
  284. });
  285. }
  286. this.$emit('preview', {
  287. url: this.result
  288. }, e)
  289. },
  290. async toTempFilePath({success, fail}) {
  291. if (this.ct) {
  292. this.ctx.toTempFilePath(
  293. 0,
  294. 0,
  295. this.sizeLocal,
  296. this.sizeLocal,
  297. this.sizeLocal,
  298. this.sizeLocal,
  299. "",
  300. 1,
  301. res => {
  302. success(res)
  303. }
  304. );
  305. }
  306. else {
  307. // #ifdef H5
  308. success({
  309. tempFilePath: this.ctx.canvas.toDataURL("image/png", 1)
  310. })
  311. // #endif
  312. // #ifndef H5
  313. uni.canvasToTempFilePath(
  314. {
  315. canvasId: this.cid,
  316. success :res => {
  317. success(res)
  318. },
  319. fail: fail
  320. },
  321. this)
  322. // #endif
  323. }
  324. },
  325. async longpress() {
  326. this.toTempFilePath({
  327. success: res => {
  328. this.$emit('longpressCallback', res.tempFilePath)
  329. },
  330. fail: err => {
  331. }
  332. })
  333. },
  334. /**
  335. * 使用根节点宽高 设置新的size
  336. * @return {Promise<void>}
  337. */
  338. async setNewSize(){
  339. const rootNode = await this.getCanvasNode(this.rootId, false);
  340. const { width , height } = rootNode;
  341. // 将最短的设置为二维码 的size
  342. if(width > height){
  343. this.sizeLocal = height
  344. }
  345. else{
  346. this.sizeLocal = width
  347. }
  348. },
  349. /**
  350. * 获取节点
  351. * @param id 节点id
  352. * @param isCanvas 是否为Canvas节点
  353. * @return {Promise<unknown>}
  354. */
  355. async getCanvasNode(id, isCanvas = true) {
  356. return new Promise((resolve, reject) => {
  357. try {
  358. // #ifdef APP-NVUE
  359. setTimeout(() => {
  360. /*获取元素引用*/
  361. this.ganvas = this.$refs["gcanvess"]
  362. /*通过元素引用获取canvas对象*/
  363. let canvasNode = enable(this.ganvas, {
  364. bridge: WeexBridge
  365. })
  366. resolve(canvasNode)
  367. }, 200)
  368. // #endif
  369. // #ifndef APP-NVUE
  370. const query = uni.createSelectorQuery().in(this);
  371. query.select(`#${id}`)
  372. .fields({
  373. node: true,
  374. size: true
  375. })
  376. .exec((res) => {
  377. if (isCanvas) {
  378. resolve(res[0].node)
  379. } else {
  380. resolve(res[0])
  381. }
  382. })
  383. // #endif
  384. } catch (e) {
  385. console.error("获取节点失败", e)
  386. }
  387. })
  388. },
  389. getContext() {
  390. // #ifdef APP-VUE
  391. return uni.createCanvasContext(this.cid, this);
  392. // #endif
  393. // #ifndef APP-VUE
  394. return this.canvas.getContext('2d');
  395. // #endif
  396. },
  397. drawImage(url, x, y, w, h) {
  398. try {
  399. let img = {}
  400. // #ifdef APP-NVUE
  401. img = new GImage();
  402. // #endif
  403. // #ifdef H5
  404. // APP下不支持会一直卡住
  405. img = new Image();
  406. // #endif
  407. // #ifdef MP
  408. // 小程序2d
  409. // https://developers.weixin.qq.com/miniprogram/dev/api/canvas/Canvas.createImage.html
  410. img = canvas.createImage();
  411. // #endif
  412. // #ifdef APP-NVUE
  413. let that = this
  414. console.log(img)
  415. img.onload = function(){
  416. if (process.env.NODE_ENV === 'development') {
  417. console.log('drawImage绘制2...')
  418. }
  419. that.cxt.drawImage(img, x, y, w, h);
  420. }
  421. // #endif
  422. // #ifdef H5 || MP
  423. img.onload = () => {
  424. this.ctx.drawImage(img, x, y, w, h);
  425. };
  426. // #endif
  427. img.src = url;
  428. // #ifdef APP-VUE
  429. this.ctx.drawImage(url, x, y, w, h);
  430. // #endif
  431. } catch (error) {
  432. console.log('drawImage绘制出错', error)
  433. }
  434. },
  435. selectClick(index) {
  436. switch (index) {
  437. case 0:
  438. alert('保存二维码')
  439. this._saveCode();
  440. break;
  441. }
  442. },
  443. _result(res) {
  444. this.loading = false;
  445. this.result = res;
  446. this.$emit('result', res);
  447. },
  448. _empty(v) {
  449. let tp = typeof v,
  450. rt = false;
  451. if (tp == "number" && String(v) == "") {
  452. rt = true
  453. } else if (tp == "undefined") {
  454. rt = true
  455. } else if (tp == "object") {
  456. if (JSON.stringify(v) == "{}" || JSON.stringify(v) == "[]" || v == null) rt = true
  457. } else if (tp == "string") {
  458. if (v == "" || v == "undefined" || v == "null" || v == "{}" || v == "[]") rt = true
  459. } else if (tp == "function") {
  460. rt = false
  461. }
  462. return rt
  463. },
  464. },
  465. watch: {
  466. size: function (n, o) {
  467. if (n != o && !this._empty(n)) {
  468. this.cSize = n
  469. if (!this._empty(this.val)) {
  470. setTimeout(() => {
  471. this._makeCode()
  472. }, 100);
  473. }
  474. }
  475. },
  476. val: function (n, o) {
  477. if (this.onval) {
  478. if (n != o && !this._empty(n)) {
  479. setTimeout(() => {
  480. this._makeCode()
  481. }, 0);
  482. }
  483. }
  484. }
  485. },
  486. computed: {
  487. }
  488. }
  489. </script>
  490. <style lang="scss" scoped>
  491. .u-qrcode {
  492. &__loading {
  493. display: flex;
  494. justify-content: center;
  495. align-items: center;
  496. background-color: #f7f7f7;
  497. position: absolute;
  498. top: 0;
  499. bottom: 0;
  500. left: 0;
  501. right: 0;
  502. }
  503. /* #ifdef MP-TOUTIAO */
  504. /**字节小程序在编译时会出现一个 [hidde]:{ display: none !important; } 这个样式
  505. * 会导致canvas 隐藏掉 没有找到具体原因先这样处理
  506. */
  507. &__canvas {
  508. display: block !important;
  509. }
  510. /* #endif */
  511. &__content {
  512. position: relative;
  513. &__canvas {
  514. position: fixed;
  515. top: -99999rpx;
  516. left: -99999rpx;
  517. z-index: -99999;
  518. }
  519. }
  520. }
  521. </style>