首页
关于我
Search
1
Vue3 Hooks 的使用
1,405 阅读
2
Vue3 中 ref 标记组件使用
1,221 阅读
3
搭建博客
994 阅读
4
npm i 安装依赖 node-sass 报错
891 阅读
5
Vue2 中混入的使用
726 阅读
Web前端基础
JavaScript
jQuery
css
Vue
ElementUI
博客
Ant Design Vue
友情链接
凌哥技术分享
coder的技术博客
登录
Search
子衿
累计撰写
26
篇文章
累计收到
197
条评论
首页
栏目
Web前端基础
JavaScript
jQuery
css
Vue
ElementUI
博客
Ant Design Vue
页面
关于我
搜索到
14
篇与
Vue
的结果
2024-04-29
Vue3 - H5项目 扫描二维码
开发环境、组件库Vue3@zxing/libraryVant注意 !!! @zxing/library插件只能运行在https环境下或localhostVue3 本地开启Https如何在Vue3 项目中开启本地 Https 可以看这篇文章 Vue3 开启Https安装npm install @zxing/library代码<script setup> import { ref, reactive, onMounted, onUnmounted } from "vue" import { BrowserMultiFormatReader } from "@zxing/library" // 我对 Vant 库的 Dialog 组件做了二次封装 import { useDialog } from "@/hooks/useDialog.ts" // ref组件 const videoRef = ref() const reader = ref() // 初始化摄像头 const init = () => { reader.value = new BrowserMultiFormatReader() reader.value.listVideoInputDevices().then(res => { // res 是当前手机的摄像头数据,是一个数组 let backDevice // 这里对res的数量作判定,如果小于等于0,则证明该设备没有摄像头 if (res.length > 0) { // 获取到后置摄像头 backDevice = findBackDevice(res) }else { useDialog({ title: "未检测到摄像头", showCancel: false, success: () => { window.history.back(); } }); } // 重置摄像头 reader.value.reset() // 获取摄像头ID let videoId = backDevice?.deviceId // 参数分别是 摄像头ID video标签 回调函数 reader.value.decodeFromVideoDevice(videoId, videoRef.value, res => { // 判断是否扫描到数据,扫描到数据会产生res if (res) { // res.text 为扫描二维码的url } }) } } const findBackDevice = (value) => { let backDevice // 对获取到的摄像头数据进行遍历 for (let i = 0; i < value.length; i++) { // 找到后置摄像头 if (value[i].label.indexOf("back") > -1) return backDevice = item } return backDevice } // 这个函数是对url的query参数进行拆分 const analyzeUrl = (url) => { let urlQueryArray = url.split('?')[1] urlQueryArray = urlQueryArray.split('&') const query = {} urlQueryArray.forEach(item => { let itemSplit = item.split('=') query[itemSplit[0]] = itemSplit[1] }) return query } onMounted(() => { initScan() }) onUnmounted(() => { reader.value.reset() }) </script> <template> <div class="scan-page"> <div class="scan-box"> <!-- 扫码所需要的video标签 --> <video ref="videoRef" class="scan-video" autoplay></video> <div class="scan-scanner"> <div class="scan-scanner-box"> <div class="scan-scanner-line"></div> <div class="scan-scanner-angle"></div> </div> </div> </div> </div> </template><style lang="less" scoped> .scan-box { position: fixed; top: 0; left: 0; height: 100%; width: 100vw; 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: 3px 3px; background-position: -1px -1px; } .scan-video { height: 100vh; width: 100vw; object-fit: cover; } .scan-scanner { .scan-scanner-box { width: 213px; height: 213px; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); overflow: hidden; border: 1px solid rgba(0, 255, 51, 0.2); ::after { .pseudo-content; .pseudo-content-top; right: 0; border-right-color: #00ff33; } ::before { .pseudo-content; .pseudo-content-top; left: 0; border-left-color: #00ff33; } } .scan-scanner-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; ::after { .pseudo-content; .pseudo-content-bottom; right: 0; border-right-color: #00ff33; } ::before { .pseudo-content; .pseudo-content-bottom; left: 0; border-left-color: #00ff33; } } .scan-pseudo-content { content: ""; display: block; position: absolute; width: 3vw; height: 3vw; border: 2px solid transparent; } .pseudo-content-top { top: 0; border-top-color: #00ff33; } .pseudo-content-bottom { bottom: 0; border-bottom-color: #00ff33; } } @keyframes radar-beam { 0% { transform: translateY(-100%); } 100% { transform: translateY(0); } } .scan-page { overflow-y: hidden; } </style>
2024年04月29日
203 阅读
0 评论
0 点赞
2024-04-29
Vue3 本地开启Https
我们日常开发中一些 API 需要运行在 HTTPS 环境下,所以需要配置本地开发环境的https访问。安装三方包npm i @vitejs/plugin-basic-ssl -D`代码 (vite.config.ts 文件中)import basicSsl from '@vitejs/plugin-basic-ssl' export default { server: { // 开启 https https: true }, plugins: [ basicSsl() ] } 这个包的作用是为你提供一个自动签名用来启动https,但是在你启动项目后,打开页面时,会提示该页面不被信任,这时候我们只需要点击 高级 -> 继续访问即可。
2024年04月29日
318 阅读
0 评论
0 点赞
2023-10-27
Vue项目登录页加载优化
最近在项目中遇见了一个登录页要加载很久,在项目很复杂的时候打的 js 包很大,会导致加载时间过长从而影响到用户体验,需要对加载进行优化。使用分析工具分析js包 使用webpack的可视化资源分析工具 webpack-bundle-analyzer,它可以直观分析打包出的文件包含哪些,大小占比如何,模块包含关系,依赖项等,我们可以根据这些来对文件的分割进行操作,是我们后面分析资源包大小的基础。// 安装 webpack-bundle-analyzer npm i webpack-bundle-analyzer --save-dev // 随后在 vue.config.js 文件中进行配置 const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; module.exports = { configureWebpack: config => { config.plugins.push(new BundleAnalyzerPlugin()) } } // 随后打包项目生成分析图 npm run build 分割模块 我们可以通过 splitchunks 将一些常用的库进行分割抽离出来,这样也可以加快加载速度。这一步也需要在 vue.config.js 中进行配置。module.exports = { chainWebpack: config => { config.optimization.splitChunks({ // async异步代码分割 initial同步代码分割 all同步异步分割都开启 chunks: "all", // 形成一个新代码块最小的体积 minSize: 30000, // 模块至少使用次数 minChunks: 1, // 按需加载时候最大的并行请求数,默认为5 maxAsyncRequests: 30, // 一个入口最大的并行请求数,默认为3 maxInitialRequests: 30, cacheGroups: { antDesignVue: { // name 打包的 chunks 的名字 name: 'chunk-ant-design-vue', // 将匹配到的模块打进这个缓存组 test: /[\\/]node_modules[\\/]ant-design-vue[\\/]/, chunks: 'initial', // 打包优先级,数值越大优先级高 priority: 3, // 如果当前的 chunk 已被从 split 出来,那么将会直接复用这个 chunk 而不是重新创建一个 reuseExistingChunk: true, // 强制抽离此模块 enforce: true } } }) } }使用GZIP进行代码压缩 我们还可以使用GZIP对代码进行压缩,减少文件大小从而达到减少加载时间的目的。// 安装 CompressionPlugin npm i compression-webpack-plugin -D // 在 vue.config.js 文件中进行配置 const CompressionPlugin = require("compression-webpack-plugin") module.exports = { chainWebpack: config => { // 在生产环境中,压缩 js、css 代码 if (process.env.NODE_ENV === 'production') { config.plugin('compressionPlugin').use(new CompressionPlugin({ algorithm: 'gzip', // 所有匹配此正则的文件都会被进行压缩 test: /\.(js|css|less)$/, filename: '[path].gz[query]', minRatio: 1, // 设置压缩门槛,单位字节,只有大于这个大小的文件才会被压缩 threshold: 10240, // 是否删除原资源,建议设置为 false,这样可以让压缩资源和原始资源共存,当客户端或者服务端不支持GZIP压缩时,能够返回原始资源,保障项目正常运行 deleteOriginalAssets: false })) } } } 由上图可以看出 GZIP 对于大文件的压缩效果还是很明显的,我们还需要开启服务端的 GZIP 功能,来让效果更好。这里以 Nginx 服务器为例:# 开启gzip gzip on; gzip_static on; # 启用gzip压缩的最小文件;小于设置值的文件将不会被压缩 gzip_min_length 1k; # 压缩级别1~9,值越小压缩效果越差,值越大处理越慢 gzip_comp_level 9; # 进行压缩的文件类型 gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; # 是否在http header中添加Vary: Accept-Encoding,建议开启 gzip_vary on;按需引入UI组件 减少全局注入,也可以有效的减少加载时间,在项目中很多时候我们为了方便都是选择将组件库进行全局引入,但是在优化角度而言更好的做法其实是按需引入,这样可以减少加载时间。使用 babel-plugin-import// 安装 babel-plugin-import npm install babel-plugin-import --save-dev // 在 babel.config.js 中进行配置 'plugins': [ ['import', { 'libraryName': 'ant-design-vue', 'libraryDirectory': 'es', // `style: true` 会加载 less 文件 'style': true }] ] // 在 main.js 文件中,需要什么组件按需进行引入 import { DatePicker } from 'ant-design-vue';
2023年10月27日
175 阅读
0 评论
0 点赞
2023-07-03
使用Promise完成多组件表单的验证
近期在项目中遇到一个表单需求,因为表单过多而且部分表单和其他表单可以通用,所以将表单拆成多个vue文件,但是又遇到表单验证的问题,最后用promise来完成多组件表单验证的需求。 项目使用的是 ant-design-vue 这个UI库,他的验证表单组件使用 this.$refs.ruleForm.validate() 函数来完成表单的验证,我们可以在组件内在这个函数外面套一层 promise 函数。methods: { validate() { return new Promise((resolve, reject) => { this.$refs.ruleForm.validate(valid => { if (valid) { resolve('true') }else { reject(valid) } } }) } } 在父组件中,我们使用 Promise.all 和 ref 来完成多组件的同时验证。<template> <div> <child-one ref="childOne"></child-one> <child-two ref="childTwo"></child-two> <a-button icon="check" type="primary" @click="submit">提 交</a-button> </div> </template> <script> import ChildOne from './ChildOne' import childTwo from './childTwo' export default { components: { ChildOne, childTwo }, methods: { submit() { const refArray = ['childOne', 'childTwo'] Promise.all(refArray.map(item => this.$refs[item].validate())).then(() => { // 提交的交互写在这里,只有在所有的组件中的表单验证通过后,才会执行这里面的请求交互 }) } } } </script>
2023年07月03日
217 阅读
0 评论
0 点赞
2023-04-10
Vue2 sync 修饰符的使用
在自己封装的组件中我们希望传入子组件的一些属性可以完成双向绑定的效果,我们就可以使用 sync 修饰符来完成这个需求。父组件:<template> <child :title.sync="childTitle"></child> </template> <script> import child from './child' export default { data() { return { childTitle: 'fatherData' } }, components:{ child } } </script> 子组件:<template> <div> <div>{{ title }}</div> <button @click="changeTitle">点击改变title</button> </div> </template> <script> export default { props: ['title'] methods: { changeTitle() { this.$emit('update:title', 'childChange') } } } </script> 这样我们就可以完成类似于双向绑定的修改了,他和双向绑定的不同是:双向绑定 v-model 他在一个组件中只能存在一个,sync 修饰符在一个组件中可以存在多个。实现方式的不同:v-model=value 是 @input + value 来完成的,但是 :value.sync 是通过 @update:value 来实现。// v-model <child v-model="value"></child> <child :value="value" @input="val => value = val"></child> <!-- v-model 是给子组件绑定了 value 属性,子组件内部更改 value 的时候,必须通过 $emit 派发一个 input 事件,并携最新的值 v-model 会自动监听 input 事件,把接收到的最新的值同步赋值到 v-model 绑定的变量上 --> // .sync <child :value.sync="value"></child> <child :value="value" @update:value="val => value = val"></child> <!-- sync 其实就是一个update的时间监听,将获取到的值赋值给value -->
2023年04月10日
191 阅读
0 评论
1 点赞
1
2
3