如何在Vue中创建一个动态二维码生成器

如何在Vue中创建一个动态二维码生成器

或多或少,我们都遇到过一个有一些乱七八糟的迷宫式图案的方块,一旦我们扫描它们就会重定向或显示一个新页面的链接。我们说的是这样的东西:

qr-code

这有什么印象吗?我打赌它是的。上面的图片是一个静态QR,我们知道这是因为它的经典黑色代码和白色背景。另一方面,动态QR码是灵活的。它意味着我们可以改变图像的形状、颜色和信息,而无需生成新的代码。

品牌的身份包括颜色、形状和标志。因此,如果使用动态二维码,静态二维码并不能代表一个品牌。

本教程将展示如何使用Vue JavaScript框架创建一个动态二维码生成器。我们将涵盖以下内容:

前提条件

本教程解释了在Vue中建立一个动态二维码生成器的过程。因此,它要求读者具备以下条件:

  • 具有基本的JavaScript工作知识
  • 可使用Visual Studio Code或终端和代码编辑器
  • 用于安装软件包的NodeYarn
  • 一些咖啡

设置Vue项目

下面的章节将包括设置Vue项目,创建QR码组件,以及定制QR码。

我们将首先前往我们的终端,使用这个命令创建一个新的目录:

mkdir <directory name>

然后,我们将用这个命令改变到创建的目录:

cd <directory name>

接下来,我们需要安装Vue CLI。要做到这一点,我们将使用Node.js的Node包管理器(npm)。到我们的终端,使用下面的命令在全局添加Vue CLI:

npm install -g @vue/cli

在本教程中,我们将使用一个预先建立的模板,并对该模板进行轻微的扩展。这些扩展将添加用户输入、自定义(颜色、比例、遮罩图案)、标志(图像),并使用所有组件显示QR码。

本教程将提供一个现成的UI模板,并演示如何建立一个动态QR生成器所需的一些特定功能。我们将下载包含代码的GitHub仓库来使用这个模板。一旦完成,我们将在终端使用以下命令安装QR码包:

npm i @chenfengyuan/vue-qrcode

该命令将在我们的Node模块中添加 @chenfengyuan/vue-qrcode 包。该包将派上用场,因为它将使用我们将在下面的章节中展示的所有组件来显示一个动态QR码。下面是我们运行 npm run serve 命令时模板的样子:

QR代码生成器模板

添加用户输入

为了添加用户输入,我们将前往 ContentOne.vue 文件,用 v-modelInputData 创建一个 required 输入元素。v-model 在输入和一个叫做 InputData 的数据属性之间建立了一个双向的绑定。我们还将添加 @input 属性,即事件监听器,当输入值发生变化时调用 handleInput 方法。

我们将首先在脚本部分定义该组件,导出一个定义组件名称的默认对象,我们将其设置为ContentOne。我们还将创建一个数据方法,返回一个具有单一属性InputData的对象,该属性被设置为一个空字符串。

最后,我们将创建 methods 属性,其中包含在输入元素中创建的 handleInput 方法。这个 handleInput 方法需要两个参数,emitEvent 和 value,并发射一个名称为 emitEvent 的自定义事件,其值为 value

<template>
<div>
<input type="text" name="" required v-model="InputData" @input="handleInput('input-value', InputData)">
<label>Enter Content</label>
</div>
</template>
<script>
export default {
name: "ContentOne",
data() {
return { 
InputData: ''
}
},
methods: {
handleInput(emitEvent, value) {
this.$emit(emitEvent, value)
}
}
}
</script>

因此,我们有一个允许我们输入文本的组件,每当输入发生变化时,就会发出一个带有输入值的自定义事件。

自定义二维码

本节将创建一个用户输入,改变QR码的颜色、比例(大小)和图案。

我们将首先创建一个名为 ContentTwo.vue 的新文件,并创建四个输入元素;颜色(浅色和深色)、遮罩图案和比例。我们还将创建它们与组件数据属性的关联v-model绑定,如 hexCodehexCode2MaskPattern, 和 Scale

与 ContentOne.vue 文件中的代码类似,输入也有事件监听器,当它们的值发生变化时,会发出自定义事件,如 input-value2input-value3input-value4, 和 input-value5 。

脚本部分包含一个定义组件的 export default 对象。该组件有一个emits属性,列出了可以被发射的自定义事件。它也有数据、方法和 handleInput 方法(用于颜色输入),与之前的组件类似。 handleMPInput 和 handleScaleInput 方法是这个组件特有的,分别处理 MaskPattern 和 Scale 输入的发射事件:

<template>
<div class="gradient-input">
<div>
<input type="color" v-model="hexCode" @input="handleInput('input-value2', hexCode)">
<input type="text" v-model="hexCode" @input="handleInput('input-value2', hexCode)">
</div>
<div>
<input type="color" v-model="hexCode2" @input="handleInput('input-value3', hexCode2)">
<input type="text" v-model="hexCode2" @input="handleInput('input-value3', hexCode2)">
</div>
<div>
<label>MaskPattern:</label>
<input type="number" v-model.number="MaskPattern" min="0"
@input="handleMPInput('input-value4', MaskPattern)" /><span>px</span>
</div>
<div>
<label>Scale: </label>
<input type="number" v-model.number="Scale" min="0"
@input="handleScaleInput('input-value5', Scale)" /><span>px</span>
</div>
</div>
</template>
<script>
export default {
name: "ContentTwo",
emits: ['input-value2', 'input-value3', 'input-value4', 'input-value5'],
data() {
return {
hexCode: '#000000',
hexCode2: '#ffffff',
MaskPattern: 0,
Scale: 4,
}
},
methods: {
handleInput(emitEvent, value) {
this.$emit(emitEvent, value)
},
handleMPInput(emitEvent, value) {
this.$emit(emitEvent, value)
},
handleScaleInput(emitEvent, value) {
this.$emit(emitEvent, value)
}
}
}
</script>

添加logo组件

现在,我们将探讨如何创建一个实现文件上传器和图像选择功能的组件。我们允许用户拖放或通过文件选择器选择一个文件。我们验证文件以确保它是一个具有以下扩展名的图像:.jpeg, .jpg, 和 .png。如果文件是有效的,我们就读取它并将其数据作为数据URL存储在一个列表中。如果文件是无效的,我们会显示一个错误信息,并在两秒钟后重置。

因此,我们将创建另一个名为ContentThree.vue的文件。在该文件中,我们将创建一个类为drag-area的div元素,作为拖放区域,并创建一个按钮元素,在点击时触发文件选择器。我们将在div和按钮中添加一个ref属性,以便在我们的JavaScript代码中访问其DOM元素。我们还将创建一个带有ref输入的隐藏文件输入元素。

我们还将使用v-for属性创建一个图像元素,以循环浏览存储数据URL的列表。我们将创建一个@click属性,链接到图像元素中的selectImage方法。最后,我们将创建一个class属性,检查数据属性selectedIndex是否等于index:

<template>
<div class="drag-area" ref="dropArea">
<div class="icon"><i class="fas fa-cloud-upload-alt"></i></div>
<header ref="dragText">Drag & Drop to Upload File</header>
<span>OR</span>
<button @click="openFilePicker">Browse File</button>
<input type="file" hidden ref="input" multiple />
</div>
<div class="images-container">
<img v-for="(imgSrc, index) in imgSrcList" :src="imgSrc" width="120" height="120" :key="index"
@click="selectImage(imgSrc, index)" :class="{ thumbnail: selectedIndex === index }" />
</div>
</template>

在脚本标签中,我们将创建导出默认对象并定义一个数据属性,该属性将返回存储文件数据URL的列表和一个值为-1的selectedIndex键。 接下来,我们将创建计算属性selectedImage,该属性返回当前选择的图像。然后我们将在导出默认对象的方法属性中创建selectImage方法。

selectImage方法更新selectedIndex,并发出一个带有选定图像的事件。我们还将创建另一个方法openFilePicker,它将触发隐藏的文件输入,打开文件选取器:

<script>
export default {
data() {
return {
imgSrcList: [],
selectedIndex: -1
}
},
computed: {
selectedImage() {
return this.imgSrcList[this.selectedIndex];
}
},
methods: {
selectImage(selectedImage, index) {
this.selectedIndex = index;
this.$emit('Selected', selectedImage);
console.log(this.imgSrcList)
},
openFilePicker() {
this.$refs.input.click()
},
}
}

然后,我们将创建一些事件处理程序,如:

  • handleFileChange:处理文件输入的 change 事件。
  • handleDragOver:处理拖放区的 dragover 事件。
  • handleDragLeave:处理拖放区的 dragleave 事件
  • handleDrop:处理拖放区的拖放事件

最后,我们创建一个已安装的生命周期钩子,并为拖放区、文件输入和其相应事件的句柄添加事件监听器:

<script>
export default {
// data property
// computed property
methods: {
// do something
handleFileChange(event) {
let file = event.target.files[0]
let validExtensions = ["image/jpeg", "image/jpg", "image/png"
];
let fileType = file.type
if (validExtensions.includes(fileType)) {
let fileReader = new FileReader();
fileReader.onload = (e) => {
this.imgSrcList.push(e.target.result)
console.log(fileReader.result);
this.$refs.dropArea.classList.remove("active");
this.$refs.dragText.textContent = "The file is successfully uploaded."
setTimeout(() => {
this.$refs.dragText.textContent = "Drag & Drop to Upload File"
}, 2000)
}
fileReader.readAsDataURL(file);
this.openFilePicker();
} else {
this.$refs.dropArea.classList.remove("active");
this.$refs.dragText.textContent = "This is not an valid file!"
setTimeout(() => {
this.$refs.dragText.textContent = "Drag & Drop to Upload File"
}, 2000)
}
},
handleDragOver(event) {
event.preventDefault();
this.$refs.dropArea.classList.add("active");
this.$refs.dragText.textContent = "Release to Upload File";
},
handleDragLeave() {
this.$refs.dropArea.classList.remove("active");
this.$refs.dragText.textContent = "Drag & Drop to Upload File";
},
handleDrop(event) {
event.preventDefault();
let file = event.dataTransfer.files[0];
this.handleFileChange({ target: { files: [file] } });
},
},
mounted() {
this.$nextTick(() => {
this.$refs.dropArea.addEventListener("dragover", this.handleDragOver);
this.$refs.dropArea.addEventListener("dragleave", this.handleDragLeave);
this.$refs.dropArea.addEventListener("drop", this.handleDrop);
this.$refs.input.addEventListener("change", this.handleFileChange);
});
}
}
</script>

将数据传递给父级组件

在我们可以使用QR码组件之前,我们需要能够从我们先前创建的三个独立的组件中访问数据。它们都在发射事件;因此,我们将不得不在父组件中访问它们,并将它们从父组件发送到QR码显示组件。

因此,让我们回到ParentComponent.vue文件,将ContentOne、ContentTwo、ContentThree和DisplayArea组件添加到模板的第一、第二和第三div元素。

我们还将给ContentOne和ContentTwo组件附加一个v-on属性。v-on属性是一个Vue.js指令,用于监听事件;在本例中,是到目前为止发出的所有事件。对于ContentThree组件发出的事件,我们将使用@selected属性代替,因为它只发出一个选定的事件。然后,我们将把数据属性对象传递给DisplayArea组件:

<template>
<div>
<ul>
<li v-for="item in items" @click="showContent(item)" :key="item.id" :class="{ active: item === selectedItem }">{{
item.title
}}</li>
</ul>
<div class="content" v-show="selectedItem === items[0]">
<ContentOne v-on:input-value="updateValue" />
</div>
<div class="content" v-show="selectedItem === items[1]">
<ContentTwo v-on:input-value2="updateValue2" v-on:input-value3="updateValue3" v-on:input-value4="updateValue4"
v-on:input-value5="updateValue5" />
</div>
<div class="content" v-show="selectedItem === items[2]">
<ContentThree @selected="setSelectedImage" />
</div>
<div class="content" v-show="selectedItem === items[3]">
<DisplayArea :InputData="InputData" :InputData2="InputData2" :InputData3="InputData3" :hexCode="hexCode"
:hexCode2="hexCode2" :selectedImage="selectedImage" />
</div>
</div>
</template>

在脚本元素中,我们将创建出口默认对象并传递以下数据属性: InputData: ''hexCode: ''hexCode2: ''InputData2: ''InputData3: '' 和 selectedImage: ''。然后我们将调用附加在v-on和@selected属性上的方法来更新相应的数据属性:

<script>
import ContentOne from './Content/ContentOne.vue';
import ContentTwo from './Content/ContentTwo.vue';
import ContentThree from './Content/ContentThree.vue';
import DisplayArea from './Content/DisplayArea.vue';
export default {
name: 'HelloWorld',
components: {
ContentOne,
ContentTwo,
ContentThree,
DisplayArea
},
data() {
return {
InputData: '',
hexCode: '',
hexCode2: '',
InputData2: '',
InputData3: '',
selectedImage: '',
items: [
{ title: 'ENTER CONTENT' },
{ title: 'CUSTOMIZE' },
{ title: 'ADD LOGO' },
{ title: 'DISPLAY' },
],
selectedItem: null
}
},
methods: {
updateValue(value) {
this.InputData = value
},
updateValue2(value) {
this.hexCode = value
},
updateValue3(value) {
this.hexCode2 = value
},
updateValue4(value) {
this.InputData2 = value
},
updateValue5(value) {
this.InputData3 = value
},
setSelectedImage(selectedImage) {
this.selectedImage = selectedImage;
},
showContent(item) {
this.selectedItem = item
},
},
emits: ['Selected']
}
</script>

基本上,上面的代码将数据从三个子组件传递到父组件。然后,这些数据又从父组件移到另一个子组件,即 DisplayArea 组件。

创建二维码显示

现在,我们将创建另一个名为 DisplayArea.vue 的文件(DisplayArea组件),并使用@陈方圆/vue-qrcode库,我们将调用vue-qrcode元素并将ref、value和option属性分别设置为qrcode、hello和options。我们还将从父组件中调用所有的props,将它们作为按钮元素的参数。

按钮元素包含一个方法,将当前的数据属性与从每个组件传递过来的事件等同起来。这里是下面的代码:

<template>
<vue-qrcode ref="qrcode" :value="hello" :options="options"></vue-qrcode>
<div class="container"> <button
v-on:click="getinput(InputData, hexCode, hexCode2, InputData2, InputData3, selectedImage, this.$refs.qrcode.$el)"><span>Generate
code</span></button>
</div>
</template>
<script>
import VueQrcode from '@chenfengyuan/vue-qrcode'
export default {
components: { VueQrcode },
props: ['InputData', 'hexCode', 'hexCode2', 'InputData2', 'InputData3', 'selectedImage'],
data() {
return {
hello: "hello",
options: {
maskPattern: 7,
scale: 4,
color: {
dark: '#000000',
light: '#ffffff',
},
margin: 0
}
}
},
methods: {
getinput(InputData, hexCode, hexCode2, InputData2, InputData3, selectedImage, canvas) {
try {
if (!InputData) throw new Error("InputData is not defined")
const context = canvas.getContext('2d');
const image = new Image();
image.src = selectedImage;
image.crossorigin = 'anonymous';
image.onload = () => {
const coverage = 0.15;
const width = Math.round(canvas.width * coverage);
const x = (canvas.width - width) / 2;
console.log(canvas.width)
this.drawImage(context, image, x, x, width, width);
};
this.hello = InputData
this.options.maskPattern = InputData2
this.options.scale = InputData3
this.options.color.dark = hexCode
this.options.color.light = hexCode2
} catch (error) {
console.log(error)
}
},
drawImage(context, image, x, y, width, height, radius = 4) {
context.shadowOffsetX = 0;
context.shadowOffsetY = 2;
context.shadowBlur = 4;
context.shadowColor = '#00000040';
context.lineWidth = 8;
context.beginPath();
context.moveTo(x + radius, y);
context.arcTo(x + width, y, x + width, y + height, radius);
context.arcTo(x + width, y + height, x, y + height, radius);
context.arcTo(x, y + height, x, y, radius);
context.arcTo(x, y, x + width, y, radius);
context.closePath();
context.strokeStyle = '#fff';
context.stroke();
context.clip();
context.fillStyle = '#fff';
context.fillRect(x, x, width, height);
context.drawImage(image, x, x, width, height);
},
}
}
</script>

最终确定项目

现在我们已经完成了,让我们运行代码,看看结果:

QR代码生成器的最终结果

额外功能

早些时候,我们把其中一个输入设置为必填。这意味着每次它的字段为空时我们都会得到一个错误。为了解决这个问题,让我们创建一个带有 ref 属性的 paragraph 元素,并设置一个条件,如果错误是 Input data is not defined,它应该显示一个通知,而不是破坏代码:

<template>
// Qr code component
<p ref="DisplayError"></p>
// button contianer
</template>
<script>
import VueQrcode from '@chenfengyuan/vue-qrcode'
export default {
methods: {
getinput(InputData, hexCode, hexCode2, InputData2, InputData3, selectedImage, canvas) {
try {
// do something
// onReady()
} catch (error) {
if (error.message === 'InputData is not defined') {
this.$refs.DisplayError.textContent = "Input is empty"
setTimeout(() => {
this.$refs.DisplayError.textContent = ""
}, 2000)
console.log("error: no input text")
} else {
console.log(error)
this.error = error.message
}
}
},
}
}
</script>

小结

本文展示了如何将输入和图像绑定到一个QR码。一个动态的QR码生成器有各种潜在的用途,如营销和活动管理。生成器的未来发展可以包括增加更多的定制选项,与数据库集成,并改善用户体验。

评论留言