u-qrcode.vue 14 KB

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