下载二维码功能

This commit is contained in:
1378012178@qq.com 2025-07-10 13:40:01 +08:00
parent 7cdfb9c7b6
commit e65263cffb
1 changed files with 223 additions and 205 deletions

View File

@ -1,215 +1,233 @@
<template> <template>
<div ref="qrcodeRef"> <div class="qrcode-container">
<QRCodeVue3 <!-- 直接绑定ref到QRCodeVue3组件 -->
:margin="margin" <QRCodeVue3 ref="qrcodeInstance" render-as="canvas" :margin="margin" :width="width" :height="height" :value="value"
:width="width" :qrOptions="qrOptions" :image="image" :imageOptions="imageOptions" :dotsOptions="dotsOptions"
:height="height" :backgroundOptions="backgroundOptions" :cornersSquareOptions="cornersSquareOptions"
:value="value" :cornersDotOptions="cornersDotOptions" :key="key" />
:qrOptions="qrOptions" <button @click="downloadQRCode" :disabled="isDownloading" class="download-btn">
:image="image" {{ isDownloading ? '生成中...' : '下载二维码' }}
:imageOptions="imageOptions" </button>
:dotsOptions="dotsOptions"
:backgroundOptions="backgroundOptions"
:cornersSquareOptions="cornersSquareOptions"
:cornersDotOptions="cornersDotOptions"
:key="key"
/>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import {defineComponent, reactive, toRefs, PropType, ref} from "vue"; import { ref, reactive, toRefs, nextTick } from 'vue'
import QRCodeVue3 from "qrcode-vue3"; import QRCodeVue3 from 'qrcode-vue3'
import type { PropType } from 'vue'
export default defineComponent({ //
name: "Qrcode", interface QRCodeExpose {
components: { $el: HTMLCanvasElement
QRCodeVue3, }
},
props: { const props = defineProps({
//
key: { key: {
type: String as PropType<string>, type: String as PropType<string>,
default: 0, default: "0",
}, },
//
width: { width: {
type: String as PropType<string>, type: String as PropType<string>,
default: 600, default: "600",
}, },
//
height: { height: {
type: String as PropType<string>, type: String as PropType<string>,
default: 600, default: "600",
}, },
//
value: { value: {
type: String as PropType<string>, type: String as PropType<string>,
default: "https://www.focusnu.com/devops", default: "https://www.focusnu.com/devops",
}, },
//
margin: { margin: {
type: String as PropType<string>, type: String as PropType<string>,
default:6, default: "6",
}, },
//
backgroundColor: { backgroundColor: {
type: String as PropType<string>, type: String as PropType<string>,
default: "white", default: "white",
}, },
//logo
logo: { logo: {
type: String as PropType<string>, type: String as PropType<string>,
default: '', default: '',
}, },
//
hideLogoDots: { hideLogoDots: {
type: Boolean as PropType<boolean>, type: Boolean as PropType<boolean>,
default: true, default: true,
}, },
//logo
logoSize: { logoSize: {
type: String as PropType<string>, type: String as PropType<string>,
default: 0.5, default: "0.5",
}, },
//logo
logoMargin: { logoMargin: {
type: String as PropType<string>, type: String as PropType<string>,
default: 5, default: "5",
}, },
//
dotsOptions: { dotsOptions: {
type: Object, type: Object as PropType<{
type: string;
color: string;
gradient?: {
type: string;
rotation: number;
colorStops: Array<{ offset: number; color: string }>;
};
}>,
default: () => ({ default: () => ({
type: "rounded", // square | dots | rounded | extra-rounded | classy | classy-rounded type: "rounded",
color: "#0d2a56", // color: "#0d2a56",
// color
gradient: { gradient: {
type: "radial", // linear线 | radial type: "radial",
rotation: 0, rotation: 0,
colorStops: [ colorStops: [
{ { offset: 0, color: "#21a3fa" },
offset: 0, { offset: 1, color: "#0d2a56" }
color: "#21a3fa", ]
}
})
}, },
{
offset: 1,
color: "#0d2a56",
},
],
},
}),
},
/**
* 角落广场配置
*/
cornersSquareOptions: { cornersSquareOptions: {
type: Object, type: Object as PropType<{
type: string;
color: string;
gradient?: {
type: string;
rotation: number;
colorStops: Array<{ offset: number; color: string }>;
};
}>,
default: () => ({ default: () => ({
type: "extra-rounded", // none | square | dot | extra-rounded type: "extra-rounded",
color: "#0d2a56",// color: "#0d2a56",
//
gradient: { gradient: {
type: "linear", type: "linear",
rotation: 0, rotation: 0,
colorStops: [ colorStops: [
{ { offset: 0, color: "#0d2a56" },
offset: 0, { offset: 1, color: "#21a3fa" }
color: "#0d2a56", ]
}
})
}, },
{
offset: 1,
color: "#21a3fa",
},
],
},
}),
},
/**
* 角落点配置
*/
cornersDotOptions: { cornersDotOptions: {
type: Object, type: Object as PropType<{
type: string;
color: string;
gradient?: {
type: string;
rotation: number;
colorStops: Array<{ offset: number; color: string }>;
};
}>,
default: () => ({ default: () => ({
type: "dot", // none | square | dot type: "dot",
color: "#0d2a56",// color: "#0d2a56",
//
gradient: { gradient: {
type: "linear", type: "linear",
rotation: 0, rotation: 0,
colorStops: [ colorStops: [
{ { offset: 0, color: "#0d2a56" },
offset: 0, { offset: 1, color: "#21a3fa" }
color: "#0d2a56", ]
}, }
{ })
offset: 1, }
color: "#21a3fa", })
},
], const qrcodeInstance = ref<QRCodeExpose | null>(null)
}, const isDownloading = ref(false)
}),
}, const data = reactive({
}, key: props.key,
setup(props) { width: props.width,
console.log(props); height: props.height,
const qrcodeRef = ref(null); value: props.value,
const data = reactive({ margin: props.margin,
key: props.key,//key
/**
* 基础配置
* https://qr-code-styling.com
*/
width: props.width, //
height: props.height, //
value: props.value, //
margin: props.margin, //
/**
* 背景配置
*/
backgroundOptions: { backgroundOptions: {
//
color: props.backgroundColor, color: props.backgroundColor,
}, },
/**
* 二维码配置
*/
qrOptions: { qrOptions: {
typeNumber: "0", // 0 - 40 typeNumber: "0",
mode: "Byte", // Numeric | Alphanumeric | Byte | Kanji mode: "Byte",
errorCorrectionLevel: "Q", // L | M | Q | H 'L''M''Q''H' errorCorrectionLevel: "Q",
}, },
image: props.logo,
/**
* 图像配置中心图片
*/
image: props.logo, //
imageOptions: { imageOptions: {
hideBackgroundDots: props.hideLogoDots, // hideBackgroundDots: props.hideLogoDots,
imageSize: props.logoSize, imageSize: props.logoSize,
margin: props.logoMargin, margin: props.logoMargin,
crossOrigin: "anonymous", // anonymous | use-credentials crossOrigin: "anonymous",
}, },
/**
* 二维码点配置
*/
dotsOptions: props.dotsOptions, dotsOptions: props.dotsOptions,
/**
* 角落广场配置
*/
cornersSquareOptions: props.cornersSquareOptions, cornersSquareOptions: props.cornersSquareOptions,
/**
* 角落点配置
*/
cornersDotOptions: props.cornersDotOptions, cornersDotOptions: props.cornersDotOptions,
}); })
return { const {
...toRefs(data), key,
qrcodeRef width,
}; height,
}, value,
}); margin,
backgroundOptions,
qrOptions,
image,
imageOptions,
dotsOptions,
cornersSquareOptions,
cornersDotOptions
} = toRefs(data)
const downloadQRCode = async () => {
isDownloading.value = true
await nextTick() // DOM
// img
const imgEl = qrcodeInstance.value?.$el?.querySelector?.('img')
if (imgEl && imgEl.src) {
const imgSrc = imgEl.src
const link = document.createElement('a')
link.href = imgSrc
link.download = 'qrcode.png'
link.click()
} else {
console.error('未找到二维码 img 或 src 为空')
}
isDownloading.value = false
}
</script> </script>
<style scoped>
.qrcode-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
}
.download-btn {
padding: 8px 16px;
background-color: #42b983;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 14px;
min-width: 120px;
}
.download-btn:hover {
background-color: #3aa876;
transform: translateY(-1px);
}
.download-btn:disabled {
background-color: #cccccc;
cursor: not-allowed;
transform: none;
}
</style>