Browse Source

perf: remove qrcode use antdv

main
xingyu 2 years ago
parent
commit
4daf15f26b
  1. 1
      build/vite/optimize.ts
  2. 2
      package.json
  3. 5
      src/components/Qrcode/index.ts
  4. 118
      src/components/Qrcode/src/Qrcode.vue
  5. 31
      src/components/Qrcode/src/drawCanvas.ts
  6. 85
      src/components/Qrcode/src/drawLogo.ts
  7. 5
      src/components/Qrcode/src/qrcodePlus.ts
  8. 11
      src/components/Qrcode/src/toCanvas.ts
  9. 38
      src/components/Qrcode/src/typing.ts
  10. 12
      src/views/base/login/QrCodeForm.vue

1
build/vite/optimize.ts

@ -5,7 +5,6 @@ const include = [
'axios',
'pinia',
'dayjs',
'qrcode',
'echarts',
'cropperjs',
'crypto-js',

2
package.json

@ -65,7 +65,6 @@
"path-to-regexp": "^6.2.1",
"pinia": "^2.1.6",
"print-js": "^1.6.0",
"qrcode": "^1.5.3",
"qs": "^6.11.2",
"resize-observer-polyfill": "^1.5.1",
"sortablejs": "^1.15.0",
@ -92,7 +91,6 @@
"@types/lodash-es": "^4.17.8",
"@types/node": "^20.4.0",
"@types/nprogress": "^0.2.0",
"@types/qrcode": "^1.5.1",
"@types/qs": "^6.9.7",
"@types/sortablejs": "^1.15.1",
"@typescript-eslint/eslint-plugin": "^6.3.0",

5
src/components/Qrcode/index.ts

@ -1,5 +0,0 @@
import qrCode from './src/Qrcode.vue'
import { withInstall } from '@/utils'
export const QrCode = withInstall(qrCode)
export * from './src/typing'

118
src/components/Qrcode/src/Qrcode.vue

@ -1,118 +0,0 @@
<script lang="ts" setup>
import { onMounted, ref, unref, watch } from 'vue'
import { toDataURL } from 'qrcode'
import type { LogoType, QRCodeRenderersOptions } from './qrcodePlus'
import { toCanvas } from './qrcodePlus'
import type { QrcodeDoneEventParams } from './typing'
import { downloadByUrl } from '@/utils/file/download'
defineOptions({ name: 'QrCode' })
const props = defineProps({
value: {
type: [String, Array] as PropType<string | any[]>,
default: null,
},
//
options: {
type: Object as PropType<QRCodeRenderersOptions>,
default: null,
},
//
width: {
type: Number as PropType<number>,
default: 200,
},
// logo
logo: {
type: [String, Object] as PropType<Partial<LogoType> | string>,
default: '',
},
// img logo
tag: {
type: String as PropType<'canvas' | 'img'>,
default: 'canvas',
validator: (v: string) => ['canvas', 'img'].includes(v),
},
})
const emit = defineEmits({
done: (data: QrcodeDoneEventParams) => !!data,
error: (error: any) => !!error,
})
const wrapRef = ref<HTMLCanvasElement | HTMLImageElement | null>(null)
async function createQrcode() {
try {
const { tag, value, options = {}, width, logo } = props
const renderValue = String(value)
const wrapEl = unref(wrapRef)
if (!wrapEl)
return
if (tag === 'canvas') {
const url: string = await toCanvas({
canvas: wrapEl,
width,
logo: logo as any,
content: renderValue,
options: options || {},
})
emit('done', { url, ctx: (wrapEl as HTMLCanvasElement).getContext('2d') })
return
}
if (tag === 'img') {
const url = await toDataURL(renderValue, {
errorCorrectionLevel: 'H',
width,
...options,
})
;(unref(wrapRef) as HTMLImageElement).src = url
emit('done', { url })
}
}
catch (error) {
emit('error', error)
}
}
/**
* file download
*/
function download(fileName?: string) {
let url = ''
const wrapEl = unref(wrapRef)
if (wrapEl instanceof HTMLCanvasElement)
url = wrapEl.toDataURL()
else if (wrapEl instanceof HTMLImageElement)
url = wrapEl.src
if (!url)
return
downloadByUrl({
url,
fileName,
})
}
onMounted(createQrcode)
//
watch(
props,
() => {
createQrcode()
},
{
deep: true,
},
)
defineExpose({ download })
</script>
<template>
<div>
<component :is="tag" ref="wrapRef" />
</div>
</template>

31
src/components/Qrcode/src/drawCanvas.ts

@ -1,31 +0,0 @@
import { toCanvas } from 'qrcode'
import type { QRCodeRenderersOptions } from 'qrcode'
import { cloneDeep } from 'lodash-es'
import type { ContentType, RenderQrCodeParams } from './typing'
export function renderQrCode({ canvas, content, width = 0, options: params = {} }: RenderQrCodeParams) {
const options = cloneDeep(params)
// 容错率,默认对内容少的二维码采用高容错率,内容多的二维码采用低容错率
options.errorCorrectionLevel = options.errorCorrectionLevel || getErrorCorrectionLevel(content)
return getOriginWidth(content, options).then((_width: number) => {
options.scale = width === 0 ? undefined : (width / _width) * 4
return toCanvas(canvas, content, options)
})
}
// 得到原QrCode的大小,以便缩放得到正确的QrCode大小
function getOriginWidth(content: ContentType, options: QRCodeRenderersOptions) {
const _canvas = document.createElement('canvas')
return toCanvas(_canvas, content, options).then(() => _canvas.width)
}
// 对于内容少的QrCode,增大容错率
function getErrorCorrectionLevel(content: ContentType) {
if (content.length > 36)
return 'M'
else if (content.length > 16)
return 'Q'
else
return 'H'
}

85
src/components/Qrcode/src/drawLogo.ts

@ -1,85 +0,0 @@
import type { LogoType, RenderQrCodeParams } from './typing'
import { isString } from '@/utils/is'
export function drawLogo({ canvas, logo }: RenderQrCodeParams) {
if (!logo) {
return new Promise((resolve) => {
resolve((canvas as HTMLCanvasElement).toDataURL())
})
}
const canvasWidth = (canvas as HTMLCanvasElement).width
const { logoSize = 0.15, bgColor = '#ffffff', borderSize = 0.05, crossOrigin, borderRadius = 8, logoRadius = 0 } = logo as LogoType
const logoSrc: string = isString(logo) ? logo : logo.src
const logoWidth = canvasWidth * logoSize
const logoXY = (canvasWidth * (1 - logoSize)) / 2
const logoBgWidth = canvasWidth * (logoSize + borderSize)
const logoBgXY = (canvasWidth * (1 - logoSize - borderSize)) / 2
const ctx = canvas.getContext('2d')
if (!ctx)
return
// logo 底色
canvasRoundRect(ctx)(logoBgXY, logoBgXY, logoBgWidth, logoBgWidth, borderRadius)
ctx.fillStyle = bgColor
ctx.fill()
// logo
const image = new Image()
if (crossOrigin || logoRadius)
image.setAttribute('crossOrigin', crossOrigin || 'anonymous')
image.src = logoSrc
// 使用image绘制可以避免某些跨域情况
const drawLogoWithImage = (image: CanvasImageSource) => {
ctx.drawImage(image, logoXY, logoXY, logoWidth, logoWidth)
}
// 使用canvas绘制以获得更多的功能
const drawLogoWithCanvas = (image: HTMLImageElement) => {
const canvasImage = document.createElement('canvas')
canvasImage.width = logoXY + logoWidth
canvasImage.height = logoXY + logoWidth
const imageCanvas = canvasImage.getContext('2d')
if (!imageCanvas || !ctx)
return
imageCanvas.drawImage(image, logoXY, logoXY, logoWidth, logoWidth)
canvasRoundRect(ctx)(logoXY, logoXY, logoWidth, logoWidth, logoRadius)
if (!ctx)
return
const fillStyle = ctx.createPattern(canvasImage, 'no-repeat')
if (fillStyle) {
ctx.fillStyle = fillStyle
ctx.fill()
}
}
// 将 logo绘制到 canvas上
return new Promise((resolve) => {
image.onload = () => {
logoRadius ? drawLogoWithCanvas(image) : drawLogoWithImage(image)
resolve((canvas as HTMLCanvasElement).toDataURL())
}
})
}
// copy来的方法,用于绘制圆角
function canvasRoundRect(ctx: CanvasRenderingContext2D) {
return (x: number, y: number, w: number, h: number, r: number) => {
const minSize = Math.min(w, h)
if (r > minSize / 2)
r = minSize / 2
ctx.beginPath()
ctx.moveTo(x + r, y)
ctx.arcTo(x + w, y, x + w, y + h, r)
ctx.arcTo(x + w, y + h, x, y + h, r)
ctx.arcTo(x, y + h, x, y, r)
ctx.arcTo(x, y, x + w, y, r)
ctx.closePath()
return ctx
}
}

5
src/components/Qrcode/src/qrcodePlus.ts

@ -1,5 +0,0 @@
// 参考 qr-code-with-logo 进行ts版本修改
import { toCanvas } from './toCanvas'
export * from './typing'
export { toCanvas }

11
src/components/Qrcode/src/toCanvas.ts

@ -1,11 +0,0 @@
import { renderQrCode } from './drawCanvas'
import { drawLogo } from './drawLogo'
import type { RenderQrCodeParams } from './typing'
export function toCanvas(options: RenderQrCodeParams) {
return renderQrCode(options)
.then(() => {
return options
})
.then(drawLogo) as Promise<string>
}

38
src/components/Qrcode/src/typing.ts

@ -1,38 +0,0 @@
import type { QRCodeRenderersOptions, QRCodeSegment } from 'qrcode'
export type ContentType = string | QRCodeSegment[]
export type { QRCodeRenderersOptions }
export interface LogoType {
src: string
logoSize: number
borderColor: string
bgColor: string
borderSize: number
crossOrigin: string
borderRadius: number
logoRadius: number
}
export interface RenderQrCodeParams {
canvas: any
content: ContentType
width?: number
options?: QRCodeRenderersOptions
logo?: LogoType | string
image?: HTMLImageElement
downloadName?: string
download?: boolean | Fn
}
export type ToCanvasFn = (options: RenderQrCodeParams) => Promise<unknown>
export interface QrCodeActionType {
download: (fileName?: string) => void
}
export interface QrcodeDoneEventParams {
url: string
ctx?: CanvasRenderingContext2D | null
}

12
src/views/base/login/QrCodeForm.vue

@ -1,11 +1,12 @@
<script lang="ts" setup>
import { computed, unref } from 'vue'
import { Button, Divider } from 'ant-design-vue'
import { Button, Divider, Popover, QRCode } from 'ant-design-vue'
import LoginFormTitle from './LoginFormTitle.vue'
import { LoginStateEnum, useLoginState } from './useLogin'
import { QrCode } from '@/components/Qrcode'
import { useI18n } from '@/hooks/web/useI18n'
import loginImg from '@/assets/images/logo.png'
const qrCodeUrl = 'https://vben.xingyuv.com/login'
const { t } = useI18n()
@ -18,7 +19,12 @@ const getShow = computed(() => unref(getLoginState) === LoginStateEnum.QR_CODE)
<div v-if="getShow">
<LoginFormTitle class="enter-x" />
<div class="enter-x min-w-64 min-h-64">
<QrCode :value="qrCodeUrl" class="enter-x flex justify-center xl:justify-start" :width="280" />
<Popover :overlay-inner-style="{ padding: 0 }">
<template #content>
<QRCode :value="qrCodeUrl" class="enter-x flex justify-center xl:justify-start" :width="280" :bordered="false" />
</template>
<img width="100" height="100" :src="loginImg">
</Popover>
<Divider class="enter-x">
{{ t('sys.login.scanSign') }}
</Divider>

Loading…
Cancel
Save