ldc-qrcollector-back/resources/ts/bo/components/QrCodeComponent.vue
2026-04-10 17:07:47 +02:00

676 lines
19 KiB
Vue

<template>
<div class="hello">
<h3>Personnalisation du QRCode</h3>
<div class="row">
<div class="col-6 code-viewer">
<div
id="qr-code"
ref="div"
/>
<div class="download-group">
<select
v-model="extension"
class="form-select"
>
<option value="svg">
SVG
</option>
<option value="png">
PNG
</option>
<option value="jpeg">
JPEG
</option>
<option value="webp">
WEBP
</option>
</select>
<button
type="button"
@click="download"
class="btn btn-primary"
>
Download
</button>
</div>
</div>
<div class="col-6">
<div
class="accordion"
id="accordionExample"
>
<div class="accordion-item">
<h2
class="accordion-header"
id="headingOne"
>
<button
class="accordion-button"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseOne"
aria-expanded="true"
aria-controls="collapseOne"
>
Options principales
</button>
</h2>
<div
id="collapseOne"
class="accordion-collapse collapse show"
aria-labelledby="headingOne"
data-bs-parent="#accordionExample"
>
<div class="accordion-body">
<label
for="width"
class="form-label"
>Largeur</label>
<input
v-model="options.width"
placeholder="Add data"
class="form-control"
type="number"
id="width"
>
<label
for="height"
class="form-label"
>Hauteur</label>
<input
v-model="options.height"
placeholder="Add data"
class="form-control"
type="number"
id="height"
>
<label
for="margin"
class="form-label"
>Marge</label>
<input
v-model="options.margin"
placeholder="Add data"
class="form-control"
type="number"
id="margin"
>
<div class="row">
<div class="col-12 my-3">
<div class="form-check">
<label
for="backgroundOptions-transparency"
class="form-check-label"
>Transparent ?</label>
<input
v-model="isTransparent"
class="form-check-input"
type="checkbox"
id="backgroundOptions-transparency"
>
</div>
</div>
<div class="col-12 d-flex justify-content-center">
<Vue3ColorPicker
v-show="!isTransparent"
v-model="pickerColor"
mode="solid"
type="HEX"
input-type="RGB"
:show-input-menu="false"
:show-picker-mode="false"
:show-color-list="false"
:show-alpha="false"
@on-change="changeColor"
/>
</div>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2
class="accordion-header"
id="headingTwo"
>
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseTwo"
aria-expanded="false"
aria-controls="collapseTwo"
>
Options des points
</button>
</h2>
<div
id="collapseTwo"
class="accordion-collapse collapse"
aria-labelledby="headingTwo"
data-bs-parent="#accordionExample"
>
<div class="accordion-body">
<label
for="dotsOptions-color"
class="form-label"
>Couleur des points</label>
<input
v-model="options.dotsOptions.color"
placeholder="Add data"
class="form-control"
id="dotsOptions-color"
>
<label
for="dotsOptions-type"
class="form-label"
>Forme des points</label>
<select
v-model="options.dotsOptions.type"
placeholder="Add data"
class="form-select"
id="dotsOptions-type"
>
<option value="rounded">
Rounded
</option>
<option value="dots">
Dots
</option>
<option value="classy">
Classy
</option>
<option value="classy-rounded">
Classy-Rounded
</option>
<option value="square">
Square
</option>
<option value="extra-rounded">
Extra-Rounded
</option>
</select>
</div>
</div>
</div>
<div class="accordion-item">
<h2
class="accordion-header"
id="headingThree"
>
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseThree"
aria-expanded="false"
aria-controls="collapseThree"
>
Options des coins
</button>
</h2>
<div
id="collapseThree"
class="accordion-collapse collapse"
aria-labelledby="headingThree"
data-bs-parent="#accordionExample"
>
<div class="accordion-body">
<label
for="cornersSquareOptions-color"
class="form-label"
>Couleur des coins</label>
<input
v-model="options.cornersSquareOptions.color"
placeholder="Add data"
class="form-control"
id="cornersSquareOptions-color"
>
<label
for="cornersSquareOptions-type"
class="form-label"
>Forme des coins</label>
<select
v-model="options.cornersSquareOptions.type"
placeholder="Add data"
class="form-select"
id="cornersSquareOptions-type"
>
<option value="dots">
Dots
</option>
<option value="square">
Square
</option>
<option value="extra-rounded">
Extra-Rounded
</option>
</select>
<label
for="cornersDotOptions-color"
class="form-label"
>Couleur de l'intérieur des coins</label>
<input
v-model="options.cornersDotOptions.color"
placeholder="Add data"
class="form-control"
id="cornersDotOptions-color"
>
<label
for="cornersDotOptions-type"
class="form-label"
>Type de l'intérieur des coins</label>
<select
v-model="options.cornersDotOptions.type"
placeholder="Add data"
class="form-select"
id="cornersDotOptions-type"
>
<option value="dots">
Dots
</option>
<option value="square">
Square
</option>
</select>
</div>
</div>
</div>
<div class="accordion-item">
<h2
class="accordion-header"
id="headingFour"
>
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseFour"
aria-expanded="false"
aria-controls="collapseFour"
>
Options de l'image
</button>
</h2>
<div
id="collapseFour"
class="accordion-collapse collapse"
aria-labelledby="headingFour"
data-bs-parent="#accordionExample"
>
<div class="accordion-body">
<label
for="imageOptions-hideBackgroundDots"
class="form-label"
>Cacher les points derrière l'image</label>
<input
v-model="options.imageOptions.hideBackgroundDots"
placeholder="Add data"
class="form-check"
type="checkbox"
id="imageOptions-hideBackgroundDots"
>
<label
for="imageOptions-imageSize"
class="form-label"
>Taille de l'image</label>
<input
v-model="options.imageOptions.imageSize"
placeholder="Add data"
class="form-control"
type="number"
step="0.1"
id="imageOptions-imageSize"
name="imageOptions-imageSize"
>
<label
for="imageOptions-margin"
class="form-label"
>Marge de l'image</label>
<input
v-model="options.imageOptions.margin"
placeholder="Add data"
class="form-control"
type="number"
id="imageOptions-margin"
>
<template v-if="imageLoaded">
<ImageInputComponent
v-model="options.image"
:id="'logo'"
:name="'logo'"
:helper="'L\'image qui va servir de vignette au produit, doit être de 500px (largeur) par 500px (hauteur) maximum'"
:width="100"
:height="100"
:required="false"
:value="options.image"
@image-file="updatePicture()"
/>
</template>
<input
type="hidden"
:value="`${ JSON.stringify(options) }`"
name="image_settings"
>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import QRCodeStyling from "qr-code-styling";
import type {
DrawType,
TypeNumber,
Mode,
ErrorCorrectionLevel,
DotType,
CornerSquareType,
CornerDotType,
FileExtension,
} from "qr-code-styling";
import trans from "../../modules/trans";
import { defineComponent } from "vue";
import ImageInputComponent from "../../bo/components/ImageInputComponent.vue";
import {Vue3ColorPicker} from "@cyhnkckali/vue3-color-picker";
import "@cyhnkckali/vue3-color-picker/dist/style.css";
export default defineComponent({
name: "QrCodeComponent",
mixins: [trans],
mounted() {
const json = String(this.$attrs.json ?? "{}");
if (json.length) {
const data = JSON.parse(json);
if(data.baseUrlEnv) {
this.baseUrl = data.baseUrlEnv;
}
if(data.id) {
this.urlId = data.id;
this.urlInput = document.getElementById(data.id) as HTMLInputElement;
this.options.data = this.baseUrl + "/r/" + this.urlInput.value;
}
if(data.settings) {
this.options = data.settings;
}
if(data.logo !== "") {
this.options.image = "/" + data.logo;
} else {
this.options.image = "";
}
}
this.imageLoaded = true;
this.qrCode = new QRCodeStyling(this.options);
this.qrCode.append(this.$refs.div as HTMLElement);
this.isTransparent = this.options.backgroundOptions.color === "transparent";
},
components: {
ImageInputComponent,
Vue3ColorPicker
},
methods: {
download() {
this.qrCode.update(this.options);
this.qrCode.download({ extension: this.extension as FileExtension });
},
updatePicture() {
const el = document.getElementById("PreviewHelplogo")?.parentElement?.children[1] as HTMLElement;
if (el instanceof HTMLElement) {
this.options.image = el.getAttribute("src") as string;
this.qrCode.update(this.options);
}
},
changeColor(color: string) {
if (!this.isTransparent) {
this.options.backgroundOptions.color = color;
}
},
toggleTransparency() {
if (this.isTransparent) {
this.options.backgroundOptions.color = "transparent";
} else {
this.options.backgroundOptions.color = this.pickerColor;
}
}
},
props: {
id: {
type: String,
default: null,
},
baseUrlEnv: {
type: String,
default: null,
}
},
data(): {
baseUrl: string,
urlId: string,
urlInput: HTMLInputElement,
urlInputValue: string,
imageLoaded: boolean,
options: {
type: DrawType;
width: number;
height: number;
margin: number;
data: string;
image: string;
qrOptions: {
typeNumber: TypeNumber;
mode: Mode;
errorCorrectionLevel: ErrorCorrectionLevel;
};
imageOptions: {
hideBackgroundDots: boolean;
imageSize: number;
crossOrigin: string;
margin: number;
};
dotsOptions: {
type: DotType;
color: string;
};
cornersSquareOptions: {
type: CornerSquareType;
color: string;
};
cornersDotOptions: {
type: CornerDotType;
color: string;
};
backgroundOptions: {
color: string;
};
},
extension: FileExtension,
qrCode: QRCodeStyling,
isTransparent: boolean,
pickerColor: string,
} {
return {
baseUrl: "",
urlId: "",
urlInput: document.getElementById(this.urlInputId) as HTMLInputElement,
urlInputValue: "",
imageLoaded: false,
options: {
width: 300,
height: 300,
type: "svg",
data: "http://www.google.com",
image: "",
margin: 10,
qrOptions: {
typeNumber: 0,
mode: "Byte",
errorCorrectionLevel: "H"
},
imageOptions: {
hideBackgroundDots: true,
imageSize: 0.4,
margin: 10,
crossOrigin: "anonymous",
},
dotsOptions: {
color: "#000000",
type: "square",
},
backgroundOptions: {
color: "#ffffff",
},
cornersSquareOptions: {
color: "#000000",
type: "square",
},
cornersDotOptions: {
color: "#000000",
type: "square",
}
},
extension: "png",
qrCode: new QRCodeStyling(),
isTransparent: false,
pickerColor: "#ffffff",
};
},
watch: {
id() {
this.urlId = this.id;
},
urlId() {
if (!this.urlId) {
return;
}
this.$nextTick(() => {
const input = document.getElementById(this.urlId);
if (
!input ||
!(input instanceof HTMLInputElement)
) {
throw new Error(`${this.urlId} is not a input`);
}
this.urlInput = input;
});
},
urlInput() {
if (
!this.urlInput ||
!(
this.urlInput instanceof HTMLInputElement
)
) {
throw new Error(`${this.urlId} is not a input`);
}
this.urlInput.addEventListener("input", (e) => {
const el = e.target;
if (
!el ||
!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)
) {
throw new Error(`${this.urlId} is not a input or textarea`);
}
this.options.data = this.baseUrl + "/r/" + el.value.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, "")
.replace(/[\s_-]+/g, "-")
.replace(/^-+|-+$/g, "");
});
this.qrCode.update(this.options);
},
isTransparent(newVal) {
if (newVal) {
this.options.backgroundOptions.color = "transparent";
} else {
this.options.backgroundOptions.color = this.pickerColor;
}
},
"options.data"() {
this.qrCode.update(this.options);
},
"options.height"() {
this.options.width = this.options.height;
this.qrCode.update(this.options);
},
"options.width"() {
this.options.height = this.options.width;
this.qrCode.update(this.options);
},
"options.margin"() {
this.qrCode.update(this.options);
},
"options.type"() {
this.qrCode.update(this.options);
},
"options.dotsOptions.color"() {
this.qrCode.update(this.options);
},
"options.dotsOptions.type"() {
this.qrCode.update(this.options);
},
"options.backgroundOptions.color"() {
this.qrCode.update(this.options);
},
"options.cornersSquareOptions.color"() {
this.qrCode.update(this.options);
},
"options.cornersSquareOptions.type"() {
this.qrCode.update(this.options);
},
"options.cornersDotOptions.color"() {
this.qrCode.update(this.options);
},
"options.cornersDotOptions.type"() {
this.qrCode.update(this.options);
},
"options.imageOptions.hideBackgroundDots"() {
this.qrCode.update(this.options);
},
"options.imageOptions.imageSize"() {
this.qrCode.update(this.options);
},
"options.imageOptions.margin"() {
this.qrCode.update(this.options);
},
},
});
</script>
<style lang="scss">
.code-viewer {
display: flex;
flex-direction: column;
align-items: center;
}
.download-group {
display: flex;
select,
button {
width: 10rem;
}
}
#qr-code {
// Apply a color to keep the dots visible when the background is transparent.
background-color: #aaa;
}
</style>