移动端浏览器的扫描二维码实现(vue-qrcode-reader与jsQR方式)
1. 实现功能
类似扫一扫的功能,自动识别到画面中的二维码并进行识别,也可以选择从相册中上传。


2. 涉及到的一些插件介绍
vue-qrcode-reader
一组用于检测和解码二维码的Vue.js组件
jsQR
一个纯粹的javascript二维码阅读库,该库接收原始图像,并将定位、提取和解析在其中找到的任何二维码。
zxing-wasm
ZXing-C++ WebAssembly 作为带有类型的 ES/CJS 模块。读/写 web、node、bun 和 deno 中的条形码。
2. vite项目配置本地开发使用https访问
安装basicSsl
pnpm i @vitejs/plugin-basic-ssl
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
import basicSsl from '@vitejs/plugin-basic-ssl'// https://vitejs.dev/config/
export default defineConfig({plugins: [vue(), basicSsl()],server: {host: '0.0.0.0',https: true},resolve: {alias: {'@': path.resolve(__dirname, 'src')}}
})
3. 方式一:vue-qrcode-reader实现
npm install vue-qrcode-reader
🔍vue-qrcode-reader Api 介绍
<template><div><qrcode-streamclass="qrcode-wrap":torch="torch"v-memo="[torch]":constraints="selectedConstraints":track="paintBoundingBox"@error="onError"@detect="onDetect"@camera-on="onCameraReady"><div v-if="isSupportTorch" class="torch-wrap"><div class="torch" @click="() => (torch = !torch)"><div class="flash-light" v-if="torch"><MdiFlashlightOff style="width: 36px; height: 36px" /></div><div class="flash-light" v-else><MdiFlashlight style="width: 36px; height: 36px" /></div>{{ torch ? "关闭闪光灯" : "打开闪光灯" }}</div></div><div class="photo-wrap"><div class="photo" @click="handleOpenFile"><el-icon size="20"><PictureFilled /></el-icon></div><div class="color-[#fff]">相册</div></div></qrcode-stream></div>
</template>
<script setup lang="ts">
// https://gruhn.github.io/vue-qrcode-reader/api/QrcodeStream.html
import { QrcodeStream } from "vue-qrcode-reader";
import { PictureFilled } from "@element-plus/icons-vue";
import MdiFlashlight from "~icons/mdi/flashlight";
import MdiFlashlightOff from "~icons/mdi/flashlight-off";
import { ElMessage } from "element-plus";
import { fileOpen } from "browser-fs-access";
import _ from "lodash";const error = ref("");
const cameraIsReady = ref(false);
const isSupportTorch = ref(false); // 是否支持闪光灯
const torch = ref(false); // 闪光灯状态
// 相机配置选项: 'user'|'environment' (默认:environment)
const selectedConstraints = ref({ facingMode: "environment" });// 检测到二维码后绘制画布类型
function paintBoundingBox(detectedCodes: any, ctx: CanvasRenderingContext2D) {for (const detectedCode of detectedCodes) {const {boundingBox: { x, y, width, height },} = detectedCode;ctx.lineWidth = 2;ctx.strokeStyle = "#007bff";// 绘制边框矩形ctx.strokeRect(x, y, width, height);}
}async function onCameraReady(capabilities: any) {// NOTE: on iOS we can't invoke `enumerateDevices` before the user has given// camera access permission. `QrcodeStream` internally takes care of// requesting the permissions. The `camera-on` event should guarantee that this// has happened.try {isSupportTorch.value = !!capabilities.torch;cameraIsReady.value = true;error.value = "";} catch (error) {onError(error);cameraIsReady.value = false;}
}
// 错误提示
function onError(err: any) {error.value = `[${err.name}]: `;if (err.name === "NotAllowedError") {error.value += "you need to grant camera access permission";} else if (err.name === "NotFoundError") {error.value += "no camera on this device";} else if (err.name === "NotSupportedError") {error.value += "secure context required (HTTPS, localhost)";} else if (err.name === "NotReadableError") {error.value += "is the camera already in use?";} else if (err.name === "OverconstrainedError") {error.value += "installed cameras are not suitable";} else if (err.name === "StreamApiNotSupportedError") {error.value += "Stream API is not supported in this browser";} else if (err.name === "InsecureContextError") {error.value +="Camera access is only permitted in secure context. Use HTTPS or localhost rather than HTTP.";} else {error.value += err.message;}ElMessage.warning(error.value);
}
// 用户摄像头的流后
function onDetect(detectedCodes: any) {if (detectedCodes.length > 0) {onDecode(detectedCodes[0]?.rawValue);}
}const emit = defineEmits(["on-success"]);// 解码(交给父组件处理:进行网络请求)
function onDecode(text: string) {emit("on-success", text);
}
// 文件转成base64
const processFile = async (file: any) => {let reader = new FileReader();reader.readAsDataURL(file);reader.onload = (e) => {let base64String = e.target?.result as string ||'';// 此处可对该base64进行获取赋值传入后端, 如果有直接上传文件的接口就可以直接传文件onDecode(base64String)};
};
// 打开图片选择
async function handleOpenFile() {try {const file = await fileOpen({ mimeTypes: ["image/*"] }).catch(() => null);if (!file) return;// 计算文件的大小const fileSizeMb = _.round(file.size / 1024 / 1024, 2);// 文件大小不能超过 10MBconst limitSizeMb = 10;if (fileSizeMb > limitSizeMb) {return ElMessage.warning(`图片大小限制 ${limitSizeMb}MB`);}processFile(file)} catch (error) {console.log(`[log] - handleOpenUploadIcon - error:`, error);}
}
</script>
<style scoped>
.qrcode-wrap {position: fixed !important;top: 0;right: 0;bottom: 0;left: 0;width: 100vw;height: 100vh;z-index: 1 !important;background: rgba(0, 0, 0, 0.5);
}
.torch-wrap {width: 18.75rem;height: 12.5rem;position: fixed !important;left: 50%;top: 50%;transform: translate(-50%, -30%);z-index: 20;
}.torch {position: fixed;bottom: -6.25rem;left: 50%;transform: translateX(-50%);z-index: 20;color: #fff;display: flex;flex-direction: column;align-items: center;
}
.photo-wrap {position: fixed;bottom: 2.875rem;left: 2.875rem;display: flex;flex-direction: column;align-items: center;gap: 6px;
}.photo {height: 3.125rem;width: 3.125rem;background-color: rgba(250, 250, 250, 0.8);border-radius: 50%;display: grid;place-items: center;cursor: pointer;
}
</style>
browser-fs-access 这个包需要提前下载
pnpm i browser-fs-access
如果需要在离线的环境解析二维码,则需要使用zxing-wasm,在上面文件的js部分添加以下代码:
// 该文件由zxing-wasm项目构建而来
import wasmFile from './zxing_reader.wasm?url';// !为了离线加载
// https://github.com/gruhn/vue-qrcode-reader/issues/354
setZXingModuleOverrides({locateFile: (path: string, prefix: any) => {if (path.endsWith('.wasm')) {return wasmFile;}return prefix + path;},
});
✨ 该部分完整Demo代码在该文件夹下:Demo (二维码实现之vue-qrcode-reader)
4. 方式二:jsQR实现
🔍Canvas的基本介绍与使用
<template><div><div class="canvasBox"><div class="box"><div class="line"></div><div class="angle"></div></div><div v-if="isUseTorch" class="box2"><div class="track" @click="openTrack"><div class="flash-light" v-if="trackStatus"><MdiFlashlightOff style="width: 36px; height: 36px" /></div><div class="flash-light" v-else><MdiFlashlight style="width: 36px; height: 36px" /></div>{{ trackStatus ? "关闭闪光灯" : "打开闪光灯" }}</div></div><div class="photo-wrap"><div class="photo" @click="handleClickFile"><el-icon size="20"><inputclass="hide_file"ref="fileRef"type="file"accept="image/*;"@change="getFile"/><PictureFilled /></el-icon></div><div class="color-[#fff]">相册</div></div></div></div>
</template><script setup lang="ts">
// https://github.com/cozmo/jsQR
import jsQR from "jsqr";
import { PictureFilled } from "@element-plus/icons-vue";
import MdiFlashlight from "~icons/mdi/flashlight";
import MdiFlashlightOff from "~icons/mdi/flashlight-off";
import { ElMessage } from "element-plus";
import _ from "lodash";const props = withDefaults(defineProps<{// environment 后摄像头 user 前摄像头exact?: "environment" | "user";// whole 全屏 half 半屏size?: "whole" | "half";// 清晰度: fasle 正常 true 高清definition?: boolean;}>(),{exact: "environment",size: "whole",definition: false,}
);
const video = ref();
const canvas2d = ref();
const canvasWidth = ref(520);
const canvasHeight = ref(500);
const c = ref();
const track = ref();
const isUseTorch = ref(false);
const trackStatus = ref(false);
const fileRef = ref();onMounted(() => {const windowWidth = window.screen.availWidth;const windowHeight = window.screen.availHeight;canvasWidth.value = windowWidth;canvasHeight.value = windowHeight;nextTick(() => {video.value = document.createElement("video");c.value = document.createElement("canvas");c.value.id = "c";c.value.width = canvasWidth.value;c.value.height = canvasHeight.value;c.value.style.width = "100%";document.querySelector(".canvasBox")?.append(c.value);openScan();});
});onUnmounted(() => {closeCamera();
});
// 开始扫描
async function openScan() {try {let width = canvasHeight.value;width = props.size === "whole" ? width : width * 0.5;width = props.definition ? width * 1.6 : width;let height = canvasWidth.value;height = props.definition ? height * 1.6 : height;const videoParam = {audio: false,video: {facingMode: { exact: props.exact }, //强制使用摄像头类型width,height,},};// 获取用户摄像头的视频流const stream = await navigator.mediaDevices.getUserMedia(videoParam);if (stream) {video.value.srcObject = stream;video.value.setAttribute("playsinline", true); //内联播放video.value.play();requestAnimationFrame(tick);// 返回所有的媒体内容流的轨道列表track.value = stream.getVideoTracks()?.[0];setTimeout(() => {// 检测摄像头是否支持闪光灯isUseTorch.value = track.value.getCapabilities().torch || null;}, 500);}} catch (error) {ElMessage.warning("设备不支持,请检查是否允许摄像头权限!");console.log("获取本地设备(摄像头)---失败", error);}
}
function closeCamera() {if (video.value.srcObject) {video.value.srcObject.getTracks().forEach((track: any) => {track.stop();});}
}
function tick() {if (video.value.readyState === video.value.HAVE_ENOUGH_DATA) {canvasHeight.value = video.value.videoHeight;canvasWidth.value = video.value.videoWidth;c.value.width = canvasWidth.value;c.value.height = canvasHeight.value;if (canvas2d.value === undefined) {canvas2d.value = c.value.getContext("2d");}canvas2d.value.drawImage(video.value,0,0,canvasWidth.value,canvasHeight.value);const imageData = canvas2d.value.getImageData(0,0,canvasWidth.value,canvasHeight.value);// 解析二维码数据const code = jsQR(imageData.data, imageData.width, imageData.height, {inversionAttempts: "dontInvert",});if (!_.isEmpty(code)) {drawLine(code.location.topLeftCorner,code.location.topRightCorner,"#FF3B58");drawLine(code.location.topRightCorner,code.location.bottomRightCorner,"#FF3B58");drawLine(code.location.bottomRightCorner,code.location.bottomLeftCorner,"#FF3B58");drawLine(code.location.bottomLeftCorner,code.location.topLeftCorner,"#FF3B58");if (code.data) {getData(code.data);}}}requestAnimationFrame(tick);
}
function drawLine(begin: any, end: any, color: string) {canvas2d.value.beginPath();canvas2d.value.moveTo(begin.x, begin.y);canvas2d.value.lineTo(end.x, end.y);canvas2d.value.lineWidth = 4;canvas2d.value.strokeStyle = color;canvas2d.value.stroke();
}
const emit = defineEmits(["on-success"]);function getData(data: string) {emit("on-success", data);closeCamera();
}function openTrack() {trackStatus.value = !trackStatus.value;track.value.applyConstraints({advanced: [{ torch: trackStatus.value }],});
}
const handleClickFile = () => {fileRef.value.click();
};
const getFile = (e: any) => {const file = e.target.files[0];emit("on-success", file);closeCamera();
};
</script><style scoped>
.flash-light {display: grid;place-content: center;margin-bottom: 6px;
}.photo-wrap {position: fixed;bottom: 2.875rem;left: 2.875rem;display: flex;flex-direction: column;align-items: center;gap: 6px;
}.photo {height: 3.125rem;width: 3.125rem;background-color: rgba(250, 250, 250, 0.8);border-radius: 50%;display: grid;place-items: center;cursor: pointer;
}.hide_file {display: none;
}page {background-color: #333333;
}.canvasBox {width: 100vw;position: relative;top: 0;bottom: 0;left: 0;right: 0;background-image: linear-gradient(0deg,transparent 24%,rgba(32, 255, 77, 0.1) 25%,rgba(32, 255, 77, 0.1) 26%,transparent 27%,transparent 74%,rgba(32, 255, 77, 0.1) 75%,rgba(32, 255, 77, 0.1) 76%,transparent 77%,transparent),linear-gradient(90deg,transparent 24%,rgba(32, 255, 77, 0.1) 25%,rgba(32, 255, 77, 0.1) 26%,transparent 27%,transparent 74%,rgba(32, 255, 77, 0.1) 75%,rgba(32, 255, 77, 0.1) 76%,transparent 77%,transparent);background-size: 3rem 3rem;background-position: -1rem -1rem;z-index: 10;background-color: #1110;
}.box {width: 11.9375rem;height: 11.9375rem;position: absolute;left: 50%;top: 50%;transform: translate(-50%, -80%);overflow: hidden;border: 0.1rem solid rgba(0, 255, 51, 0.2);z-index: 11;
}.line {height: calc(100% - 2px);width: 100%;background: linear-gradient(180deg, rgba(0, 255, 51, 0) 43%, #00ff33 211%);border-bottom: 3px solid #00ff33;transform: translateY(-100%);animation: radar-beam 2s infinite alternate;animation-timing-function: cubic-bezier(0.53, 0, 0.43, 0.99);animation-delay: 1.4s;
}.box:after,
.box:before,
.angle:after,
.angle:before {content: "";display: block;position: absolute;width: 3vw;height: 3vw;z-index: 12;border: 0.2rem solid transparent;
}.box:after,
.box:before {top: 0;border-top-color: #00ff33;
}.angle:after,
.angle:before {bottom: 0;border-bottom-color: #00ff33;
}.box:before,
.angle:before {left: 0;border-left-color: #00ff33;
}.box:after,
.angle:after {right: 0;border-right-color: #00ff33;
}@keyframes radar-beam {0% {transform: translateY(-100%);}100% {transform: translateY(0);}
}.box2 {width: 18.75rem;height: 12.5rem;position: absolute;left: 50%;top: 50%;transform: translate(-50%, -80%);z-index: 20;
}.track {position: absolute;bottom: -6.25rem;left: 50%;transform: translateX(-50%);z-index: 20;color: #fff;display: flex;flex-direction: column;align-items: center;
}
</style>
✨该部分完整Demo代码在该文件夹下:Demo (二维码实现之jsQR)
5. 总结
方式一结合vue-qrcode-reader的封装性更强且识别二维码速度更快。
方式二接近了vue-qrcode-reader的底层实现过程。
实际项目开发中更推荐方式一,兼容性与稳定性会更好些。
相关文章:
移动端浏览器的扫描二维码实现(vue-qrcode-reader与jsQR方式)
1. 实现功能 类似扫一扫的功能,自动识别到画面中的二维码并进行识别,也可以选择从相册中上传。 2. 涉及到的一些插件介绍 vue-qrcode-reader 一组用于检测和解码二维码的Vue.js组件 jsQR 一个纯粹的javascript二维码阅读库,该库接收原始…...
android中调用onnxruntime框架
创建空白项目 安装Android Studio及创建空白项目参考:【安卓Java原生开发学习记录】一、安卓开发环境的搭建与HelloWorld(详细图文解释)_安卓原生开发-CSDN博客 切记:build configuration language 一定选择Groovy!官…...
【机器学习】与【数据挖掘】技术下【C++】驱动的【嵌入式】智能系统优化
目录 一、嵌入式系统简介 二、C在嵌入式系统中的优势 三、机器学习在嵌入式系统中的挑战 四、C实现机器学习模型的基本步骤 五、实例分析:使用C在嵌入式系统中实现手写数字识别 1. 数据准备 2. 模型训练与压缩 3. 模型部署 六、优化与分析 1. 模型优化 模…...
Apollo9.0 PNC源码学习之Control模块(二)
前面文章:Apollo9.0 PNC源码学习之Control模块(一) 本文将对具体控制器以及原理做一个剖析 1 PID控制器 1.1 PID理论基础 如下图所示,PID各参数(Kp,Ki,Kd)的作用: 任何闭环控制系统的首要任务是要稳、准、快的响…...
直线度测量仪发展历程!
直线度测量仪的发展历程可以概括为以下几个关键阶段: 拉钢丝法: 早期直线度测量的简单直观方法,利用钢丝受重力自然下垂的原理来测量直线度误差。 随着机械设备的大型化和测量精度要求的提高,该方法逐渐无法满足要求,正…...
09-spring的bean创建流程(一)
文章目录 spring中bean的创建流程finishBeanFactoryInitialization(beanFactory)beanFactory.preInstantiateSingletons();getMergedLocalBeanDefinition(beanName);流程实现FactoryBean接口,里面的对象实例化过程 spring中bean的创建流程 finishBeanFactoryInitialization(be…...
spring中基于setting和构造器的注入方式
Spring中可以通过setting和构造器两种方式进行依赖注入。 1.基于setting的注入方式(Setter Injection): 实现方式:在类中添加对应的属性以及对应的setter方法,在配置文件中使用<property>元素进行注入。 示例代码…...
爬虫基本原理?介绍|实现|问题解决
爬虫基本原理: 模拟用户行为: 网络爬虫(Web Crawler)是一种自动化的程序,它模拟人类用户访问网站的方式,通过发送HTTP/HTTPS请求到服务器以获取网页内容。 请求与响应: 爬虫首先构建并发送带有…...
DevOps的原理及应用详解(六)
本系列文章简介: 在当今快速变化的商业环境中,企业对于软件交付的速度、质量和安全性要求日益提高。传统的软件开发和运维模式已经难以满足这些需求,因此,DevOps(Development和Operations的组合)应运而生&a…...
手撸 串口交互命令行 及 AT应用层协议解析框架
在嵌入式系统开发中,命令行接口(CLI)和AT命令解析是常见的需求。CLI提供了方便的调试接口,而AT命令则常用于模块间的通信控制。本文将介绍如何手动实现一个串口交互的命令行及AT应用层协议解析框架,适用于FreeRTOS系统…...
Redis几种部署模式介绍
Redis 提供了几种不同的部署模式,以满足不同的使用场景和可用性需求。这些模式包括单机模式、主从复制、哨兵模式和集群模式。下面我将简要介绍每种模式的特点和用途: 单机模式: 描述:单个 Redis 服务器实例运行在一台机器上&…...
【STM32HAL库学习】定时器功能、时钟以及各种模式理解
一、文章目的 记录自己从学习了定时器理论->代码实现使用定时->查询数据手册,加深了对定时器的理解以及该过程遇到了的一些不清楚的知识。 上图为参考手册里通用定时器框图,关于定时器各种情况的工作都在上面了,在理论学习和实际应用后…...
3588麒麟系统硬解码实战
目录 安装rockchip-mpp deb 查找头文件 .pro文件添加 检查库是否已安装 error: stdlib.h: No such file or directory ffmpeg 查找ffmpeg路径: 查找FFmpeg库和头文件的位置 使用pkg-config工具查找FFmpeg路径 ok的ffmpeg配置: ffmpeg查看是否支持libx264 ffmpeg …...
十二 nginx中location重写和匹配规则
十二 location匹配规则 ^~ ~ ~* !~ !~* /a / 内部服务跳转 十三 nginx地址重写rewrite if rewrite set return 13.1 if 应用环境 server location -x 文件是否可执行 $args $document_rot $host $limit_rate $remote_addr $server_name $document_uri if …...
python的视频处理FFmpeg库使用
FFmpeg 是一个强大的多媒体处理工具,用于录制、转换和流式传输音频和视频。它支持几乎所有的音频和视频格式,并且可以在各种平台上运行。FFmpeg 在 Python 中的使用可以通过调用其命令行工具或使用专门的库如 ffmpeg-python。以下是详细介绍如何在 Python 中使用 FFmpeg,包括…...
接口测试时, 数据Mock为何如此重要?
一、为什么要mock 工作中遇到以下问题,我们可以使用mock解决: 1、无法控制第三方系统某接口的返回,返回的数据不满足要求 2、某依赖系统还未开发完成,就需要对被测系统进行测试 3、有些系统不支持重复请求,或有访问…...
未授权与绕过漏洞
1、Laravel Framework 11 - Credential Leakage(CVE-2024-29291)认证泄漏 导航这个路径storage/logs/laravel.log搜索以下信息: PDO->__construct(mysql:host 2、 Flowise 1.6.5 - Authentication Bypass(CVE-2024-31621&am…...
云原生周刊:Kubernetes 十周年 | 2024.6.11
开源项目推荐 Kubernetes Goat Kubernetes Goat 是一个故意设计成有漏洞的 Kubernetes 集群环境,旨在通过交互式实践场地来学习并练习 Kubernetes 安全性。 kube-state-metrics (KSM) kube-state-metrics 是一个用于收集 Kubernetes 集群状态信息的开源项目&…...
ClickHouse内幕(1)数据存储与过滤机制
本文主要讲述ClickHouse中的数据存储结构,包括文件组织结构和索引结构,以及建立在其基础上的数据过滤机制,从Part裁剪到Mark裁剪,最后到基于SIMD的行过滤机制。 数据过滤机制实质上是构建在数据存储格式之上的算法,所…...
1.Mongodb 介绍及部署
MongoDB 是一个开源的文档导向数据库,采用NoSQL(非关系型数据库)的设计理念。MongoDB是一个基于分布式文件存储的数据库。 分布式文件存储是一种将文件数据分布式的存储在多台计算机上。MongoDB是一款强大的文档导向数据库,适合处…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...
【决胜公务员考试】求职OMG——见面课测验1
2025最新版!!!6.8截至答题,大家注意呀! 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:( B ) A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...
WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成
厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...
让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...
IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...
IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析 一、第一轮基础概念问题 1. Spring框架的核心容器是什么?它的作用是什么? Spring框架的核心容器是IoC(控制反转)容器。它的主要作用是管理对…...
Vue3中的computer和watch
computed的写法 在页面中 <div>{{ calcNumber }}</div>script中 写法1 常用 import { computed, ref } from vue; let price ref(100);const priceAdd () > { //函数方法 price 1price.value ; }//计算属性 let calcNumber computed(() > {return ${p…...
聚六亚甲基单胍盐酸盐市场深度解析:现状、挑战与机遇
根据 QYResearch 发布的市场报告显示,全球市场规模预计在 2031 年达到 9848 万美元,2025 - 2031 年期间年复合增长率(CAGR)为 3.7%。在竞争格局上,市场集中度较高,2024 年全球前十强厂商占据约 74.0% 的市场…...
