153 lines
6.5 KiB
JavaScript
153 lines
6.5 KiB
JavaScript
"use strict";
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.qrCodeProps = void 0;
|
|
const vue_1 = require("vue");
|
|
const _mixins_1 = require("../../_mixins");
|
|
const index_cssr_1 = __importDefault(require("./styles/index.cssr"));
|
|
const styles_1 = require("../styles");
|
|
const qrcodegen_1 = __importDefault(require("./qrcodegen"));
|
|
const ERROR_CORRECTION_LEVEL = {
|
|
L: qrcodegen_1.default.QrCode.Ecc.LOW,
|
|
M: qrcodegen_1.default.QrCode.Ecc.MEDIUM,
|
|
Q: qrcodegen_1.default.QrCode.Ecc.QUARTILE,
|
|
H: qrcodegen_1.default.QrCode.Ecc.HIGH
|
|
};
|
|
exports.qrCodeProps = Object.assign(Object.assign({}, _mixins_1.useTheme.props), { value: String, color: {
|
|
type: String,
|
|
default: '#000'
|
|
}, backgroundColor: {
|
|
type: String,
|
|
default: '#FFF'
|
|
}, iconSrc: String, iconSize: {
|
|
type: Number,
|
|
default: 40
|
|
}, iconBackgroundColor: {
|
|
type: String,
|
|
default: '#FFF'
|
|
}, iconBorderRadius: {
|
|
type: Number,
|
|
default: 4
|
|
}, size: {
|
|
type: Number,
|
|
default: 100
|
|
}, padding: {
|
|
type: [Number, String],
|
|
default: 12
|
|
}, errorCorrectionLevel: {
|
|
type: String,
|
|
default: 'M'
|
|
} });
|
|
// For retina display
|
|
const UPSCALE_RATIO = 2;
|
|
exports.default = (0, vue_1.defineComponent)({
|
|
name: 'QrCode',
|
|
props: exports.qrCodeProps,
|
|
setup(props) {
|
|
const { mergedClsPrefixRef, inlineThemeDisabled } = (0, _mixins_1.useConfig)(props);
|
|
const themeRef = (0, _mixins_1.useTheme)('QrCode', '-qr-code', index_cssr_1.default, styles_1.qrcodeLight, props, mergedClsPrefixRef);
|
|
const cssVarsRef = (0, vue_1.computed)(() => {
|
|
return {
|
|
'--n-border-radius': themeRef.value.self.borderRadius
|
|
};
|
|
});
|
|
const themeClassHandle = inlineThemeDisabled
|
|
? (0, _mixins_1.useThemeClass)('qr-code', undefined, cssVarsRef, props)
|
|
: undefined;
|
|
const canvasRef = (0, vue_1.ref)();
|
|
const qr = (0, vue_1.computed)(() => {
|
|
var _a;
|
|
const errorCorrectionLevel = ERROR_CORRECTION_LEVEL[props.errorCorrectionLevel];
|
|
return qrcodegen_1.default.QrCode.encodeText((_a = props.value) !== null && _a !== void 0 ? _a : '-', errorCorrectionLevel);
|
|
});
|
|
(0, vue_1.onMounted)(() => {
|
|
const imageLoadedTrigger = (0, vue_1.ref)(0);
|
|
let loadedIcon = null;
|
|
(0, vue_1.watchEffect)(() => {
|
|
void imageLoadedTrigger.value;
|
|
drawCanvas(qr.value, props.size, props.color, props.backgroundColor, loadedIcon
|
|
? {
|
|
icon: loadedIcon,
|
|
iconBorderRadius: props.iconBorderRadius,
|
|
iconSize: props.iconSize,
|
|
iconBackgroundColor: props.iconBackgroundColor
|
|
}
|
|
: null);
|
|
});
|
|
(0, vue_1.watchEffect)(() => {
|
|
const { iconSrc } = props;
|
|
if (iconSrc) {
|
|
let aborted = false;
|
|
const img = new Image();
|
|
img.src = iconSrc;
|
|
img.onload = () => {
|
|
if (aborted)
|
|
return;
|
|
loadedIcon = img;
|
|
imageLoadedTrigger.value++;
|
|
};
|
|
return () => {
|
|
aborted = true;
|
|
};
|
|
}
|
|
});
|
|
});
|
|
function drawCanvas(qr, size, foregroundColor, backgroundColor, iconConfig) {
|
|
const canvas = canvasRef.value;
|
|
if (!canvas)
|
|
return;
|
|
const canvasWidth = size * UPSCALE_RATIO;
|
|
const width = qr.size;
|
|
const scale = canvasWidth / width;
|
|
canvas.width = canvasWidth;
|
|
canvas.height = canvasWidth;
|
|
const ctx = canvas.getContext('2d');
|
|
if (!ctx)
|
|
return;
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
for (let y = 0; y < qr.size; y++) {
|
|
for (let x = 0; x < qr.size; x++) {
|
|
ctx.fillStyle = qr.getModule(x, y) ? foregroundColor : backgroundColor;
|
|
const startX = Math.floor(x * scale);
|
|
const endX = Math.ceil((x + 1) * scale);
|
|
const startY = Math.floor(y * scale);
|
|
const endY = Math.ceil((y + 1) * scale);
|
|
ctx.fillRect(startX, startY, endX - startX, endY - startY);
|
|
}
|
|
}
|
|
if (iconConfig) {
|
|
const { icon, iconBackgroundColor, iconBorderRadius, iconSize } = iconConfig;
|
|
const finalIconSize = iconSize * UPSCALE_RATIO;
|
|
const centerX = (canvas.width - finalIconSize) / 2;
|
|
const centerY = (canvas.height - finalIconSize) / 2;
|
|
ctx.fillStyle = iconBackgroundColor;
|
|
ctx.beginPath();
|
|
ctx.roundRect(centerX, centerY, finalIconSize, finalIconSize, iconBorderRadius * UPSCALE_RATIO);
|
|
ctx.fill();
|
|
const aspectRatio = icon.width / icon.height;
|
|
const scaledWidth = aspectRatio >= 1 ? finalIconSize : finalIconSize * aspectRatio;
|
|
const scaledHeight = aspectRatio <= 1 ? finalIconSize : finalIconSize / aspectRatio;
|
|
const left = centerX + (finalIconSize - scaledWidth) / 2;
|
|
const top = centerY + (finalIconSize - scaledHeight) / 2;
|
|
ctx.drawImage(icon, left, top, scaledWidth, scaledHeight);
|
|
}
|
|
}
|
|
return {
|
|
canvasRef,
|
|
mergedClsPrefix: mergedClsPrefixRef,
|
|
cssVars: inlineThemeDisabled ? undefined : cssVarsRef,
|
|
themeClass: themeClassHandle === null || themeClassHandle === void 0 ? void 0 : themeClassHandle.themeClass
|
|
};
|
|
},
|
|
render() {
|
|
const { mergedClsPrefix, backgroundColor, padding, cssVars, themeClass, size } = this;
|
|
return ((0, vue_1.h)("div", { class: [`${mergedClsPrefix}-qr-code`, themeClass], style: Object.assign({ padding: typeof padding === 'number' ? `${padding}px` : padding, backgroundColor }, cssVars) },
|
|
(0, vue_1.h)("canvas", { ref: "canvasRef", style: {
|
|
width: `${size}px`,
|
|
height: `${size}px`
|
|
} })));
|
|
}
|
|
});
|