mirror of
https://github.com/pure-admin/vue-pure-admin.git
synced 2025-06-06 16:37:18 +08:00
feat: add qrcode demo (#256)
This commit is contained in:
parent
506bfc8087
commit
6c6d520dcb
@ -79,3 +79,4 @@ menus:
|
||||
hsDebounce: Debounce & Throttle
|
||||
hsFormDesign: Form Design
|
||||
hsBarcode: Barcode
|
||||
hsQarcode: Qarcode
|
||||
|
@ -79,3 +79,4 @@ menus:
|
||||
hsDebounce: 防抖节流
|
||||
hsFormDesign: 表单设计器
|
||||
hsBarcode: 条形码
|
||||
hsQarcode: 二维码
|
||||
|
@ -56,6 +56,7 @@
|
||||
"nprogress": "^0.2.0",
|
||||
"path": "^0.12.7",
|
||||
"pinia": "^2.0.13",
|
||||
"qrcode": "^1.5.0",
|
||||
"qs": "^6.10.1",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"responsive-storage": "^1.0.11",
|
||||
@ -93,6 +94,7 @@
|
||||
"@types/mockjs": "1.0.3",
|
||||
"@types/node": "14.14.14",
|
||||
"@types/nprogress": "0.2.0",
|
||||
"@types/qrcode": "^1.4.2",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@typescript-eslint/eslint-plugin": "^5.10.2",
|
||||
"@typescript-eslint/parser": "^5.10.2",
|
||||
|
131
pnpm-lock.yaml
generated
131
pnpm-lock.yaml
generated
@ -26,6 +26,7 @@ specifiers:
|
||||
"@types/mockjs": 1.0.3
|
||||
"@types/node": 14.14.14
|
||||
"@types/nprogress": 0.2.0
|
||||
"@types/qrcode": ^1.4.2
|
||||
"@types/qs": ^6.9.7
|
||||
"@typescript-eslint/eslint-plugin": ^5.10.2
|
||||
"@typescript-eslint/parser": ^5.10.2
|
||||
@ -73,6 +74,7 @@ specifiers:
|
||||
postcss-scss: ^4.0.3
|
||||
prettier: ^2.5.1
|
||||
pretty-quick: 3.1.1
|
||||
qrcode: ^1.5.0
|
||||
qs: ^6.10.1
|
||||
resize-observer-polyfill: ^1.5.1
|
||||
responsive-storage: ^1.0.11
|
||||
@ -137,6 +139,7 @@ dependencies:
|
||||
nprogress: 0.2.0
|
||||
path: 0.12.7
|
||||
pinia: 2.0.13_typescript@4.6.3+vue@3.2.33
|
||||
qrcode: 1.5.0
|
||||
qs: 6.10.3
|
||||
resize-observer-polyfill: 1.5.1
|
||||
responsive-storage: 1.0.11_vue@3.2.33
|
||||
@ -174,6 +177,7 @@ devDependencies:
|
||||
"@types/mockjs": 1.0.3
|
||||
"@types/node": 14.14.14
|
||||
"@types/nprogress": 0.2.0
|
||||
"@types/qrcode": 1.4.2
|
||||
"@types/qs": 6.9.7
|
||||
"@typescript-eslint/eslint-plugin": 5.16.0_bc68a9cd5bf604202498b1a9faaf9387
|
||||
"@typescript-eslint/parser": 5.16.0_eslint@8.11.0+typescript@4.6.3
|
||||
@ -1493,6 +1497,15 @@ packages:
|
||||
}
|
||||
dev: true
|
||||
|
||||
/@types/qrcode/1.4.2:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-7uNT9L4WQTNJejHTSTdaJhfBSCN73xtXaHFyBJ8TSwiLhe4PRuTue7Iph0s2nG9R/ifUaSnGhLUOZavlBEqDWQ==
|
||||
}
|
||||
dependencies:
|
||||
"@types/node": 14.14.14
|
||||
dev: true
|
||||
|
||||
/@types/qs/6.9.7:
|
||||
resolution:
|
||||
{
|
||||
@ -2476,7 +2489,6 @@ packages:
|
||||
integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
|
||||
}
|
||||
engines: { node: ">=8" }
|
||||
dev: true
|
||||
|
||||
/ansi-styles/3.2.1:
|
||||
resolution:
|
||||
@ -2495,7 +2507,6 @@ packages:
|
||||
engines: { node: ">=8" }
|
||||
dependencies:
|
||||
color-convert: 2.0.1
|
||||
dev: true
|
||||
|
||||
/ant-design-vue/3.2.0_vue@3.2.33:
|
||||
resolution:
|
||||
@ -2763,7 +2774,6 @@ packages:
|
||||
integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
|
||||
}
|
||||
engines: { node: ">=6" }
|
||||
dev: true
|
||||
|
||||
/camelcase/6.3.0:
|
||||
resolution:
|
||||
@ -2872,6 +2882,17 @@ packages:
|
||||
string-width: 4.2.3
|
||||
dev: true
|
||||
|
||||
/cliui/6.0.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==
|
||||
}
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
wrap-ansi: 6.2.0
|
||||
dev: false
|
||||
|
||||
/cliui/7.0.4:
|
||||
resolution:
|
||||
{
|
||||
@ -2914,7 +2935,6 @@ packages:
|
||||
engines: { node: ">=7.0.0" }
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
dev: true
|
||||
|
||||
/color-name/1.1.3:
|
||||
resolution: { integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= }
|
||||
@ -3394,7 +3414,6 @@ packages:
|
||||
/decamelize/1.2.0:
|
||||
resolution: { integrity: sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= }
|
||||
engines: { node: ">=0.10.0" }
|
||||
dev: true
|
||||
|
||||
/deep-is/0.1.4:
|
||||
resolution:
|
||||
@ -3427,6 +3446,13 @@ packages:
|
||||
engines: { node: ">=0.3.1" }
|
||||
dev: true
|
||||
|
||||
/dijkstrajs/1.0.2:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg==
|
||||
}
|
||||
dev: false
|
||||
|
||||
/dir-glob/3.0.1:
|
||||
resolution:
|
||||
{
|
||||
@ -3603,7 +3629,13 @@ packages:
|
||||
{
|
||||
integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
|
||||
}
|
||||
dev: true
|
||||
|
||||
/encode-utf8/1.0.3:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==
|
||||
}
|
||||
dev: false
|
||||
|
||||
/encodeurl/1.0.2:
|
||||
resolution: { integrity: sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= }
|
||||
@ -4435,7 +4467,6 @@ packages:
|
||||
dependencies:
|
||||
locate-path: 5.0.0
|
||||
path-exists: 4.0.0
|
||||
dev: true
|
||||
|
||||
/find-up/5.0.0:
|
||||
resolution:
|
||||
@ -4590,7 +4621,6 @@ packages:
|
||||
integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
||||
}
|
||||
engines: { node: 6.* || 8.* || >= 10.* }
|
||||
dev: true
|
||||
|
||||
/get-intrinsic/1.1.1:
|
||||
resolution:
|
||||
@ -5029,7 +5059,6 @@ packages:
|
||||
integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
|
||||
}
|
||||
engines: { node: ">=8" }
|
||||
dev: true
|
||||
|
||||
/is-glob/4.0.3:
|
||||
resolution:
|
||||
@ -5373,7 +5402,6 @@ packages:
|
||||
engines: { node: ">=8" }
|
||||
dependencies:
|
||||
p-locate: 4.1.0
|
||||
dev: true
|
||||
|
||||
/locate-path/6.0.0:
|
||||
resolution:
|
||||
@ -5937,7 +5965,6 @@ packages:
|
||||
engines: { node: ">=6" }
|
||||
dependencies:
|
||||
p-try: 2.2.0
|
||||
dev: true
|
||||
|
||||
/p-limit/3.1.0:
|
||||
resolution:
|
||||
@ -5957,7 +5984,6 @@ packages:
|
||||
engines: { node: ">=8" }
|
||||
dependencies:
|
||||
p-limit: 2.3.0
|
||||
dev: true
|
||||
|
||||
/p-locate/5.0.0:
|
||||
resolution:
|
||||
@ -5985,7 +6011,6 @@ packages:
|
||||
integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
|
||||
}
|
||||
engines: { node: ">=6" }
|
||||
dev: true
|
||||
|
||||
/parent-module/1.0.1:
|
||||
resolution:
|
||||
@ -6024,7 +6049,6 @@ packages:
|
||||
integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
|
||||
}
|
||||
engines: { node: ">=8" }
|
||||
dev: true
|
||||
|
||||
/path-is-absolute/1.0.1:
|
||||
resolution: { integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18= }
|
||||
@ -6117,6 +6141,14 @@ packages:
|
||||
semver-compare: 1.0.0
|
||||
dev: true
|
||||
|
||||
/pngjs/5.0.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==
|
||||
}
|
||||
engines: { node: ">=10.13.0" }
|
||||
dev: false
|
||||
|
||||
/popmotion/11.0.3:
|
||||
resolution:
|
||||
{
|
||||
@ -6679,6 +6711,20 @@ packages:
|
||||
engines: { node: ">=0.6.0", teleport: ">=0.2.0" }
|
||||
dev: true
|
||||
|
||||
/qrcode/1.5.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-9MgRpgVc+/+47dFvQeD6U2s0Z92EsKzcHogtum4QB+UNd025WOJSHvn/hjk9xmzj7Stj95CyUAs31mrjxliEsQ==
|
||||
}
|
||||
engines: { node: ">=10.13.0" }
|
||||
hasBin: true
|
||||
dependencies:
|
||||
dijkstrajs: 1.0.2
|
||||
encode-utf8: 1.0.3
|
||||
pngjs: 5.0.0
|
||||
yargs: 15.4.1
|
||||
dev: false
|
||||
|
||||
/qs/6.10.3:
|
||||
resolution:
|
||||
{
|
||||
@ -6785,7 +6831,6 @@ packages:
|
||||
/require-directory/2.1.1:
|
||||
resolution: { integrity: sha1-jGStX9MNqxyXbiNE/+f3kqam30I= }
|
||||
engines: { node: ">=0.10.0" }
|
||||
dev: true
|
||||
|
||||
/require-from-string/2.0.2:
|
||||
resolution:
|
||||
@ -6795,6 +6840,13 @@ packages:
|
||||
engines: { node: ">=0.10.0" }
|
||||
dev: true
|
||||
|
||||
/require-main-filename/2.0.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
|
||||
}
|
||||
dev: false
|
||||
|
||||
/resize-observer-polyfill/1.5.1:
|
||||
resolution:
|
||||
{
|
||||
@ -7013,6 +7065,10 @@ packages:
|
||||
lru-cache: 6.0.0
|
||||
dev: true
|
||||
|
||||
/set-blocking/2.0.0:
|
||||
resolution: { integrity: sha1-BF+XgtARrppoA93TgrJDkrPYkPc= }
|
||||
dev: false
|
||||
|
||||
/shallow-equal/1.2.1:
|
||||
resolution:
|
||||
{
|
||||
@ -7274,7 +7330,6 @@ packages:
|
||||
emoji-regex: 8.0.0
|
||||
is-fullwidth-code-point: 3.0.0
|
||||
strip-ansi: 6.0.1
|
||||
dev: true
|
||||
|
||||
/string_decoder/1.3.0:
|
||||
resolution:
|
||||
@ -7305,7 +7360,6 @@ packages:
|
||||
engines: { node: ">=8" }
|
||||
dependencies:
|
||||
ansi-regex: 5.0.1
|
||||
dev: true
|
||||
|
||||
/strip-final-newline/2.0.0:
|
||||
resolution:
|
||||
@ -8119,6 +8173,10 @@ packages:
|
||||
loose-envify: 1.4.0
|
||||
dev: false
|
||||
|
||||
/which-module/2.0.0:
|
||||
resolution: { integrity: sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= }
|
||||
dev: false
|
||||
|
||||
/which/1.3.1:
|
||||
resolution:
|
||||
{
|
||||
@ -8171,7 +8229,6 @@ packages:
|
||||
ansi-styles: 4.3.0
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
dev: true
|
||||
|
||||
/wrap-ansi/7.0.0:
|
||||
resolution:
|
||||
@ -8234,6 +8291,13 @@ packages:
|
||||
xgplayer-subtitles: 1.0.22
|
||||
dev: false
|
||||
|
||||
/y18n/4.0.3:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
|
||||
}
|
||||
dev: false
|
||||
|
||||
/y18n/5.0.8:
|
||||
resolution:
|
||||
{
|
||||
@ -8268,6 +8332,17 @@ packages:
|
||||
engines: { node: ">= 6" }
|
||||
dev: true
|
||||
|
||||
/yargs-parser/18.1.3:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
|
||||
}
|
||||
engines: { node: ">=6" }
|
||||
dependencies:
|
||||
camelcase: 5.3.1
|
||||
decamelize: 1.2.0
|
||||
dev: false
|
||||
|
||||
/yargs-parser/20.2.9:
|
||||
resolution:
|
||||
{
|
||||
@ -8284,6 +8359,26 @@ packages:
|
||||
engines: { node: ">=12" }
|
||||
dev: true
|
||||
|
||||
/yargs/15.4.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
|
||||
}
|
||||
engines: { node: ">=8" }
|
||||
dependencies:
|
||||
cliui: 6.0.0
|
||||
decamelize: 1.2.0
|
||||
find-up: 4.1.0
|
||||
get-caller-file: 2.0.5
|
||||
require-directory: 2.1.1
|
||||
require-main-filename: 2.0.0
|
||||
set-blocking: 2.0.0
|
||||
string-width: 4.2.3
|
||||
which-module: 2.0.0
|
||||
y18n: 4.0.3
|
||||
yargs-parser: 18.1.3
|
||||
dev: false
|
||||
|
||||
/yargs/17.4.0:
|
||||
resolution:
|
||||
{
|
||||
|
10
src/components/ReQrcode/index.ts
Normal file
10
src/components/ReQrcode/index.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { App } from "vue";
|
||||
import reQrcode from "./src/index";
|
||||
|
||||
export const ReQrcode = Object.assign(reQrcode, {
|
||||
install(app: App) {
|
||||
app.component(reQrcode.name, reQrcode);
|
||||
}
|
||||
});
|
||||
|
||||
export default ReQrcode;
|
8
src/components/ReQrcode/src/index.scss
Normal file
8
src/components/ReQrcode/src/index.scss
Normal file
@ -0,0 +1,8 @@
|
||||
.qrcode {
|
||||
&--disabled {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
& > div {
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
}
|
262
src/components/ReQrcode/src/index.tsx
Normal file
262
src/components/ReQrcode/src/index.tsx
Normal file
@ -0,0 +1,262 @@
|
||||
import {
|
||||
ref,
|
||||
unref,
|
||||
watch,
|
||||
nextTick,
|
||||
computed,
|
||||
PropType,
|
||||
defineComponent
|
||||
} from "vue";
|
||||
import "./index.scss";
|
||||
import { isString } from "/@/utils/is";
|
||||
import { cloneDeep } from "lodash-unified";
|
||||
import { propTypes } from "/@/utils/propTypes";
|
||||
import { IconifyIconOffline } from "../../ReIcon";
|
||||
import QRCode, { QRCodeRenderersOptions } from "qrcode";
|
||||
|
||||
interface QrcodeLogo {
|
||||
src?: string;
|
||||
logoSize?: number;
|
||||
bgColor?: string;
|
||||
borderSize?: number;
|
||||
crossOrigin?: string;
|
||||
borderRadius?: number;
|
||||
logoRadius?: number;
|
||||
}
|
||||
|
||||
const props = {
|
||||
// img 或者 canvas,img不支持logo嵌套
|
||||
tag: propTypes.string
|
||||
.validate((v: string) => ["canvas", "img"].includes(v))
|
||||
.def("canvas"),
|
||||
// 二维码内容
|
||||
text: {
|
||||
type: [String, Array] as PropType<string | Recordable[]>,
|
||||
default: null
|
||||
},
|
||||
// qrcode.js配置项
|
||||
options: {
|
||||
type: Object as PropType<QRCodeRenderersOptions>,
|
||||
default: (): QRCodeRenderersOptions => ({})
|
||||
},
|
||||
// 宽度
|
||||
width: propTypes.number.def(200),
|
||||
// logo
|
||||
logo: {
|
||||
type: [String, Object] as PropType<Partial<QrcodeLogo> | string>,
|
||||
default: (): QrcodeLogo | string => ""
|
||||
},
|
||||
// 是否过期
|
||||
disabled: propTypes.bool.def(false),
|
||||
// 过期提示内容
|
||||
disabledText: propTypes.string.def("")
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: "epTableProBar",
|
||||
props,
|
||||
emits: ["done", "click", "disabled-click"],
|
||||
setup(props, { emit }) {
|
||||
const { toCanvas, toDataURL } = QRCode;
|
||||
const loading = ref(true);
|
||||
const wrapRef = ref<Nullable<HTMLCanvasElement | HTMLImageElement>>(null);
|
||||
const renderText = computed(() => String(props.text));
|
||||
const wrapStyle = computed(() => {
|
||||
return {
|
||||
width: props.width + "px",
|
||||
height: props.width + "px"
|
||||
};
|
||||
});
|
||||
const initQrcode = async () => {
|
||||
await nextTick();
|
||||
const options = cloneDeep(props.options || {});
|
||||
if (props.tag === "canvas") {
|
||||
// 容错率,默认对内容少的二维码采用高容错率,内容多的二维码采用低容错率
|
||||
options.errorCorrectionLevel =
|
||||
options.errorCorrectionLevel ||
|
||||
getErrorCorrectionLevel(unref(renderText));
|
||||
const _width: number = await getOriginWidth(unref(renderText), options);
|
||||
options.scale =
|
||||
props.width === 0 ? undefined : (props.width / _width) * 4;
|
||||
const canvasRef: HTMLCanvasElement = await toCanvas(
|
||||
unref(wrapRef) as HTMLCanvasElement,
|
||||
unref(renderText),
|
||||
options
|
||||
);
|
||||
if (props.logo) {
|
||||
const url = await createLogoCode(canvasRef);
|
||||
emit("done", url);
|
||||
loading.value = false;
|
||||
} else {
|
||||
emit("done", canvasRef.toDataURL());
|
||||
loading.value = false;
|
||||
}
|
||||
} else {
|
||||
const url = await toDataURL(renderText.value, {
|
||||
errorCorrectionLevel: "H",
|
||||
width: props.width,
|
||||
...options
|
||||
});
|
||||
(unref(wrapRef) as HTMLImageElement).src = url;
|
||||
emit("done", url);
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
watch(
|
||||
() => renderText.value,
|
||||
val => {
|
||||
if (!val) return;
|
||||
initQrcode();
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
const createLogoCode = (canvasRef: HTMLCanvasElement) => {
|
||||
const canvasWidth = canvasRef.width;
|
||||
const logoOptions: QrcodeLogo = Object.assign(
|
||||
{
|
||||
logoSize: 0.15,
|
||||
bgColor: "#ffffff",
|
||||
borderSize: 0.05,
|
||||
crossOrigin: "anonymous",
|
||||
borderRadius: 8,
|
||||
logoRadius: 0
|
||||
},
|
||||
isString(props.logo) ? {} : props.logo
|
||||
);
|
||||
const {
|
||||
logoSize = 0.15,
|
||||
bgColor = "#ffffff",
|
||||
borderSize = 0.05,
|
||||
crossOrigin = "anonymous",
|
||||
borderRadius = 8,
|
||||
logoRadius = 0
|
||||
} = logoOptions;
|
||||
const logoSrc = isString(props.logo) ? props.logo : props.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 = canvasRef.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);
|
||||
}
|
||||
(image as any).src = logoSrc;
|
||||
// 使用image绘制可以避免某些跨域情况
|
||||
const drawLogoWithImage = (image: HTMLImageElement) => {
|
||||
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: any) => {
|
||||
image.onload = () => {
|
||||
logoRadius ? drawLogoWithCanvas(image) : drawLogoWithImage(image);
|
||||
resolve(canvasRef.toDataURL());
|
||||
};
|
||||
});
|
||||
};
|
||||
// 得到原QrCode的大小,以便缩放得到正确的QrCode大小
|
||||
const getOriginWidth = async (
|
||||
content: string,
|
||||
options: QRCodeRenderersOptions
|
||||
) => {
|
||||
const _canvas = document.createElement("canvas");
|
||||
await toCanvas(_canvas, content, options);
|
||||
return _canvas.width;
|
||||
};
|
||||
// 对于内容少的QrCode,增大容错率
|
||||
const getErrorCorrectionLevel = (content: string) => {
|
||||
if (content.length > 36) {
|
||||
return "M";
|
||||
} else if (content.length > 16) {
|
||||
return "Q";
|
||||
} else {
|
||||
return "H";
|
||||
}
|
||||
};
|
||||
// 用于绘制圆角
|
||||
const 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;
|
||||
};
|
||||
};
|
||||
const clickCode = () => {
|
||||
emit("click");
|
||||
};
|
||||
const disabledClick = () => {
|
||||
emit("disabled-click");
|
||||
};
|
||||
return () => (
|
||||
<>
|
||||
<div
|
||||
v-loading={unref(loading)}
|
||||
class="qrcode relative inline-block"
|
||||
style={unref(wrapStyle)}
|
||||
>
|
||||
{props.tag === "canvas" ? (
|
||||
<canvas ref={wrapRef} onClick={clickCode}></canvas>
|
||||
) : (
|
||||
<img ref={wrapRef} onClick={clickCode}></img>
|
||||
)}
|
||||
{props.disabled && (
|
||||
<div
|
||||
class="qrcode--disabled absolute top-0 left-0 flex w-full h-full items-center justify-center"
|
||||
onClick={disabledClick}
|
||||
>
|
||||
<div class="absolute top-[50%] left-[50%] font-bold">
|
||||
<IconifyIconOffline
|
||||
class="cursor-pointer outline-none"
|
||||
icon="refresh-right"
|
||||
width="30"
|
||||
color="var(--el-color-primary)"
|
||||
/>
|
||||
<div>{props.disabledText}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
});
|
@ -110,6 +110,15 @@ const ableRouter = {
|
||||
title: $t("menus.hsBarcode"),
|
||||
i18n: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/able/qrcode",
|
||||
name: "reQarcode",
|
||||
component: () => import("/@/views/able/qrcode.vue"),
|
||||
meta: {
|
||||
title: $t("menus.hsQarcode"),
|
||||
i18n: true
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
113
src/views/able/qrcode.vue
Normal file
113
src/views/able/qrcode.vue
Normal file
@ -0,0 +1,113 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, unref } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import avatars from "/@/assets/avatars.jpg";
|
||||
import ReQrcode from "/@/components/ReQrcode";
|
||||
|
||||
const qrcodeText = "vue-pure-admin";
|
||||
|
||||
const asyncTitle = ref("");
|
||||
setTimeout(() => {
|
||||
asyncTitle.value = unref(qrcodeText);
|
||||
}, 3000);
|
||||
const codeClick = () => {
|
||||
ElMessage.info("点击事件");
|
||||
};
|
||||
const disabledClick = () => {
|
||||
ElMessage.info("失效");
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="font-medium">
|
||||
二维码(基于<el-link
|
||||
href="https://github.com/soldair/node-qrcode"
|
||||
target="_blank"
|
||||
style="font-size: 16px; margin: 0 5px 4px 0"
|
||||
>qrcode</el-link
|
||||
>生成)
|
||||
</div>
|
||||
</template>
|
||||
<el-row :gutter="20" justify="space-between">
|
||||
<el-col :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
|
||||
<el-card shadow="hover" class="mb-10px text-center">
|
||||
<div class="font-bold">基础用法</div>
|
||||
<ReQrcode :text="qrcodeText" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
|
||||
<el-card shadow="hover" class="mb-10px text-center">
|
||||
<div class="font-bold">img标签</div>
|
||||
<ReQrcode :text="qrcodeText" tag="img" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
|
||||
<el-card shadow="hover" class="mb-10px text-center">
|
||||
<div class="font-bold">样式配置</div>
|
||||
<ReQrcode
|
||||
:text="qrcodeText"
|
||||
:options="{
|
||||
color: {
|
||||
dark: '#55D187',
|
||||
light: '#2d8cf0'
|
||||
}
|
||||
}"
|
||||
/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
|
||||
<el-card shadow="hover" class="mb-10px text-center">
|
||||
<div class="font-bold">点击事件</div>
|
||||
<ReQrcode :text="qrcodeText" @click="codeClick" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
|
||||
<el-card shadow="hover" class="mb-10px text-center">
|
||||
<div class="font-bold">异步内容</div>
|
||||
<ReQrcode :text="asyncTitle" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
|
||||
<el-card shadow="hover" class="mb-10px text-center">
|
||||
<div class="font-bold">失效</div>
|
||||
<ReQrcode
|
||||
:text="qrcodeText"
|
||||
disabled
|
||||
@disabled-click="disabledClick"
|
||||
/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
|
||||
<el-card shadow="hover" class="mb-10px text-center">
|
||||
<div class="font-bold">logo配置</div>
|
||||
<ReQrcode :text="qrcodeText" :logo="avatars" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
|
||||
<el-card shadow="hover" class="mb-10px text-center">
|
||||
<div class="font-bold">logo样式</div>
|
||||
<ReQrcode
|
||||
:text="qrcodeText"
|
||||
:logo="{
|
||||
src: avatars,
|
||||
logoSize: 0.2,
|
||||
borderSize: 0.05,
|
||||
borderRadius: 50,
|
||||
bgColor: 'blue'
|
||||
}"
|
||||
/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
|
||||
<el-card shadow="hover" class="mb-10px text-center">
|
||||
<div class="font-bold">大小配置</div>
|
||||
<ReQrcode :text="qrcodeText" :width="100" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
Loading…
x
Reference in New Issue
Block a user