cl-qrcode.uvue 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. <template>
  2. <view
  3. ref="qrcodeRef"
  4. :style="{ width: getPx(props.width) + 'px', height: getPx(props.height) + 'px' }"
  5. >
  6. <canvas
  7. ref="canvasRef"
  8. :canvas-id="qrcodeId"
  9. type="2d"
  10. :id="qrcodeId"
  11. :style="{ width: getPx(props.width) + 'px', height: getPx(props.height) + 'px' }"
  12. ></canvas>
  13. </view>
  14. </template>
  15. <script lang="ts" setup>
  16. import {
  17. ref,
  18. watch,
  19. onMounted,
  20. getCurrentInstance,
  21. nextTick,
  22. computed,
  23. type PropType,
  24. onUnmounted
  25. } from "vue";
  26. import { drawQrcode, type QrcodeOptions } from "./draw";
  27. import { getPx, isHarmony, uuid } from "@/cool";
  28. import type { ClQrcodeMode } from "../../types";
  29. import { base64ToBlob } from "./utils";
  30. defineOptions({
  31. name: "cl-qrcode"
  32. });
  33. const props = defineProps({
  34. // 二维码宽度,支持 px/rpx 单位
  35. width: {
  36. type: String,
  37. default: "200px"
  38. },
  39. // 二维码高度,支持 px/rpx 单位
  40. height: {
  41. type: String,
  42. default: "200px"
  43. },
  44. // 二维码前景色
  45. foreground: {
  46. type: String,
  47. default: "#131313"
  48. },
  49. // 二维码背景色
  50. background: {
  51. type: String,
  52. default: "#FFFFFF"
  53. },
  54. // 定位点颜色,不填写时与前景色一致
  55. pdColor: {
  56. type: String as PropType<string | null>,
  57. default: null
  58. },
  59. // 定位图案圆角半径,为0时绘制直角矩形
  60. pdRadius: {
  61. type: Number,
  62. default: 10
  63. },
  64. // 二维码内容
  65. text: {
  66. type: String,
  67. default: "https://cool-js.com/"
  68. },
  69. // logo 图片地址,支持网络、本地路径
  70. logo: {
  71. type: String,
  72. default: ""
  73. },
  74. // logo 大小,支持 px/rpx 单位
  75. logoSize: {
  76. type: String,
  77. default: "50px"
  78. },
  79. // 二维码边距,单位 px
  80. padding: {
  81. type: Number,
  82. default: 5
  83. },
  84. // 二维码样式:rect 普通矩形、circular 小圆点、line 线条、rectSmall 小方格
  85. mode: {
  86. type: String as PropType<ClQrcodeMode>,
  87. default: "circular"
  88. }
  89. });
  90. const { proxy } = getCurrentInstance()!;
  91. // 二维码组件id
  92. const qrcodeId = ref<string>("cl-qrcode-" + uuid());
  93. // 二维码组件元素
  94. const qrcodeRef = ref<UniElement | null>(null);
  95. // 二维码组件画布
  96. const canvasRef = ref(null);
  97. /**
  98. * 主绘制方法,根据当前 props 生成二维码并绘制到 canvas。
  99. * 支持多平台(APP、H5、微信小程序),自动适配高分屏。
  100. * 内部调用 drawQrcode 进行二维码点阵绘制。
  101. */
  102. function drawer() {
  103. const data = {
  104. text: props.text,
  105. size: getPx(props.width),
  106. foreground: props.foreground,
  107. background: props.background,
  108. padding: props.padding,
  109. logo: props.logo,
  110. logoSize: getPx(props.logoSize),
  111. ecc: "H", // 使用最高纠错级别
  112. mode: props.mode,
  113. pdColor: props.pdColor,
  114. pdRadius: props.pdRadius
  115. } as QrcodeOptions;
  116. nextTick(() => {
  117. // #ifdef APP || MP-WEIXIN
  118. uni.createCanvasContextAsync({
  119. id: qrcodeId.value,
  120. component: proxy,
  121. success(context) {
  122. drawQrcode(context, data);
  123. },
  124. fail(err) {
  125. console.error(err);
  126. }
  127. });
  128. // #endif
  129. // #ifdef H5
  130. // @ts-ignore
  131. drawQrcode(canvasRef.value, data);
  132. // #endif
  133. });
  134. }
  135. /**
  136. * 获取当前二维码图片的临时文件地址
  137. * @param call 回调函数,返回图片路径,失败返回空字符串
  138. */
  139. function toPng(): Promise<string> {
  140. return new Promise((resolve) => {
  141. // #ifdef APP
  142. qrcodeRef.value!.takeSnapshot({
  143. success(res) {
  144. resolve(res.tempFilePath);
  145. },
  146. fail(err) {
  147. console.error(err);
  148. resolve("");
  149. }
  150. });
  151. // #endif
  152. // #ifdef H5
  153. const url = URL.createObjectURL(
  154. base64ToBlob(
  155. (canvasRef.value as unknown as HTMLCanvasElement)
  156. .querySelector("canvas")
  157. ?.toDataURL("image/png", 1) ?? ""
  158. )
  159. );
  160. resolve(url);
  161. // #endif
  162. // #ifdef MP-WEIXIN
  163. uni.createCanvasContextAsync({
  164. id: qrcodeId.value,
  165. component: proxy,
  166. success(context) {
  167. // 获取2D绘图上下文
  168. const ctx = context.getContext("2d")!;
  169. const canvas = ctx.canvas;
  170. // 将canvas转换为base64格式的PNG图片数据
  171. const data = canvas.toDataURL("image/png", 1);
  172. // 获取base64数据部分(去掉data:image/png;base64,前缀)
  173. const bdataBase64 = data.split(",")[1];
  174. // 获取文件系统管理器
  175. const fileMg = uni.getFileSystemManager();
  176. // 生成临时文件路径
  177. // @ts-ignore
  178. const filepath = `${wx.env.USER_DATA_PATH}/${uuid()}.png`;
  179. // 将base64数据写入文件
  180. fileMg.writeFile({
  181. filePath: filepath,
  182. data: bdataBase64,
  183. encoding: "base64",
  184. success() {
  185. // 写入成功返回文件路径
  186. resolve(filepath);
  187. },
  188. fail() {
  189. // 写入失败返回空字符串
  190. resolve("");
  191. }
  192. });
  193. },
  194. fail(err) {
  195. console.error(err);
  196. resolve("");
  197. }
  198. });
  199. // #endif
  200. });
  201. }
  202. // 自动重绘
  203. const stopWatch = watch(
  204. computed(() => [
  205. props.pdColor,
  206. props.pdRadius,
  207. props.foreground,
  208. props.background,
  209. props.text,
  210. props.logo,
  211. props.logoSize,
  212. props.mode,
  213. props.padding
  214. ]),
  215. () => {
  216. drawer();
  217. }
  218. );
  219. onMounted(() => {
  220. setTimeout(
  221. () => {
  222. drawer();
  223. },
  224. isHarmony() ? 50 : 0
  225. );
  226. });
  227. onUnmounted(() => {
  228. stopWatch();
  229. });
  230. defineExpose({
  231. toPng
  232. });
  233. </script>