1,表格数据导出excel
效果:
安装插件
npm install xlsx --save
项目中引入
import * as XLSX from "xlsx";
使用Element-Plus渲染页面
代码:
<template>
<el-table :data="tableData" stripe style="width: 100%">
<el-table-column prop="date" label="Date" width="180" />
<el-table-column prop="name" label="Name" width="180" />
<el-table-column prop="address" label="Address" />
</el-table>
<button @click="createExcel">生成excel</button>
</template>
<script setup>
import * as XLSX from "xlsx";
const tableData = []; //表格数据
var createExcel = () => {
const worksheet = XLSX.utils.json_to_sheet(tableData); //tableData为表格的数据
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "data"); //data为自定义sheet表名
XLSX.writeFile(workbook, "test.xlsx"); //test.xlsx为自定义文件名
};
</script>
<style>
</style>
2,富文本编辑器
1,Quill
这里我们使用Quill编辑器,Quill官网
效果:
输入其他的内容,打印台获取的结果值:
安装插件
npm install @vueup/vue-quill@latest --save
项目中引入
import { Quill,QuillEditor } from '@vueup/vue-quill'
import '@vueup/vue-quill/dist/vue-quill.snow.css';
代码:
<template>
<div>
<QuillEditor v-model="content" :options="editorOptions" @textChange="textChange" ref="quill"/>
</div>
</template>
<script setup>
// 引入了 Quill 编辑器和 QuillEditor 组件,并引入对应的 CSS 样式
import { ref, reactive } from "vue";
import { Quill,QuillEditor } from "@vueup/vue-quill";
import "@vueup/vue-quill/dist/vue-quill.snow.css";
const quill = ref();
const content = ref(""); //定义content
// 定义一个响应式的 editorOptions 对象,其中包含了需要配置的选项参数
const editorOptions = reactive({
placeholder: "Please enter", // 设置占位符提示
readOnly: false, // 是否只读
modules: {
toolbar: [ // 工具栏配置
["bold", "italic", "underline", "strike"], // 粗体、斜体、下划线、删除线
["blockquote", "code-block"], // 引用与编码
[{ header: 1 }, { header: 2 }], // 标题1和标题2
[{ list: "ordered" }, { list: "bullet" }], // 有序列表和无序列表
[{ script: "sub" }, { script: "super" }], // 上标和下标
[{ indent: "-1" }, { indent: "+1" }], // 缩进
[{ direction: "rtl" }], // 文字方向
[{ size: ["small", false, "large", "huge"] }], // 字号
[{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题等级
[{ color: [] }, { background: [] }], // 字体颜色和背景色
[{ font: [] }], // 字体
[{ align: [] }], // 对齐方式
["clean"], // 清除格式化
],
},
});
// 关键
const textChange = (val) => {
console.log(quill.value.getEditor()); // dom格式
console.log(quill.value.getHTML()); // 富文本格式
}
</script>
2,wangEditor 5
貌似quill已经凉了,打印台报错好不好,但还能用
现在推荐一个新的富文本编辑器wangEditor 5
适配vue2,vue2和React版本
效果:
安装(vue3 + 组合式API)
npm install @wangeditor/editor --save
npm install @wangeditor/editor-for-vue --save
vue页面,根据官网demo编写
<template>
<div style="border: 1px solid #ccc">
<Toolbar
style="border-bottom: 1px solid #ccc"
:editor="editorRef"
:defaultConfig="toolbarConfig"
mode="default"
/>
<Editor
style="height: 500px; overflow-y: hidden"
v-model="valueHtml"
:defaultConfig="editorConfig"
mode="default"
@onCreated="handleCreated"
@onChange="handleChange"
/>
</div>
</template>
<script setup>
import "@wangeditor/editor/dist/css/style.css"; // 引入 css
import { onBeforeUnmount, ref, shallowRef } from "vue";
import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
const editorRef = shallowRef();
// 内容 HTML
const valueHtml = ref("<p>hello</p>");
const toolbarConfig = {};
const editorConfig = { placeholder: "请输入内容..." };
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
const editor = editorRef.value;
if (editor == null) return;
editor.destroy();
});
const handleCreated = (editor) => {
editorRef.value = editor;
};
const handleChange = (editor) => {
console.log(editor.getHtml()) // 编辑内容的富文本形式
};
</script>
<style lang="scss" scoped>
</style>
3,NProgress顶部滚动条
使用 NProgress 插件,演示项目为gitee或github上拉取,页面代码来源丢失
效果:
安装插件
npm install nprogress --save
项目中引入
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
ts文件中引入解决方法
1,安装依赖
npm i -save-dev @types/nprogress
2,添加声明
代码:router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../pages/home.vue';
import NProgress from 'nprogress' // 导入 nprogress
import 'nprogress/nprogress.css' // 导入样式
export const routes = [
{
name: 'Home',
path: '/',
component: Home,
}
];
const router = createRouter({
history: createWebHistory(),
routes,
});
// 开启加载条
router.beforeEach((to,from,next)=>{
NProgress.start()
next()
})
// 关闭加载条
router.afterEach(() => {
NProgress.done()
})
export default router;
更改样式
在router/index.js中进行配置
//全局进度条的配置
NProgress.configure({
easing: 'ease', // 动画方式
speed: 1000, // 递增进度条的速度
showSpinner: false, // 是否显示加载ico
trickleSpeed: 200, // 自动递增间隔
minimum: 0.3 // 初始化时的最小百分比
})
4,echarts
效果:
安装插件
npm i echarts -s
项目中引入
import * as echarts from 'echarts'
代码
<template>
<div class="echarts" ref="chartsRef"></div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import * as echarts from 'echarts'
onMounted(()=>{
createEcharts() //调用创建echarts函数
})
const chartsRef = ref(null) //获取dom
// 创建echarts函数
const createEcharts = () =>{
const chartDom = chartsRef.value
const charts = echarts.init(chartDom) //echarts初始化
const option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [
{
data: [150, 230, 224, 218, 135, 147, 260],
type: 'line'
}
]
}
charts.setOption(option);
}
</script>
<style scoped>
.echarts{
width: 400px;
height: 300px;
}
</style>
5,swiper轮播
效果:
安装插件
npm i swiper -s
项目中引入
import { Swiper, SwiperSlide } from 'swiper/vue';
全部代码
<template>
<swiper :modules="modules" :loop="true" :autoplay="{ delay: 5000, disableOnInteraction: false }" :navigation="true" :pagination="{ clickable: true }">
<!-- modules为导入的模块,绑定导入的模块导航模块和分页模块,loop实现轮播图循环模式 -->
<swiper-slide>
1
</swiper-slide>
<swiper-slide>
2
</swiper-slide>
</swiper>
</template>
<script setup>
// 引入组件
import { Swiper, SwiperSlide } from 'swiper/vue';
// 引入模块
//pagination 就是指示器, 在效果中为小圆点...
import { Autoplay, Navigation, Pagination } from 'swiper/modules';
// 引入样式
import 'swiper/css/pagination';
import 'swiper/css/navigation';
import 'swiper/css';
const modules = [ Autoplay, Navigation, Pagination] //使用模块
</script>
<style>
body{
margin: 0;
}
.swiper{
width: 100%;
height: 100vh;
font-size: 150px;
}
</style>
6,pinia数据持久化,压缩,加密
持久化及压缩
效果:
安装插件
npm i pinia -s //安装pinia
npm i pinia-plugin-persistedstate -s //安装pinia持久化
npm i zipson -s //安装压缩工具,localStorage最大储存5Mb
npm i pinia pinia-plugin-persistedstate zipson -s
pinia配置
在main.js进行配置
import { createApp } from 'vue'
import App from './App.vue'
// pinia
import { createPinia } from 'pinia'
// pinia持久化
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
const app = createApp(App)
app.use(pinia) //挂载pinia
app.mount('#app')
在src文件夹下创建store/index.js
在store/index.js中进行配置
import { defineStore } from 'pinia'
import { parse, stringify } from "zipson";
export const inputStore=defineStore('input', {
// 存放状态数据 类似组件中的data
state: ()=> {
return {
inputValue: '',
}
},
//pinia持久化未加密
persist: {
enabled: false,
key: 'inputValue',
strategies: [
{ storage: localStorage } // 保存到localStorage
],
serializer: { // 压缩
deserialize: parse,
serialize: stringify,
}
},
getters: {
},
actions: {
}
})
vue页面中使用
<template>
<div class="main">
<input @input="handleInput" v-model="inputValue"/>
</div>
</template>
<script setup>
import { inputStore } from '../store'
var inputValue = inputStore().inputValue
// 一般是用
// const inputStoreValue = inputStore() //inputStore()与store/index.js中的export导出对象相同
// const newInputValue = ref(inputStoreValue.inputValue) //初始化值
// inputStoreValue.inputValue = 123 //直接赋值
// inputStoreValue.inputValue = newInputValue.value
const handleInput = () =>{
inputStore().inputValue = inputValue
}
</script>
<style>
body{
margin: 0;
}
.main{
height: 100vh;
}
</style>
加密
效果:
安装插件
npm i secure-ls -s
在store/index.js引入
// pinia持久化加密
import SecureLS from 'secure-ls';
const ls = new SecureLS({
isCompression: false, //是否压缩
encodingType: 'AES', //加密类型 Base64/AES/DES/Rabbit/RC4/''
encryptionSecret: "encryption" //PBKDF2值 加密
})
const piniaStorage = {
getItem(key){
return ls.get(key)
},
setItem(key,value) {
ls.set(key,value)
},
}
全部代码
import {defineStore} from 'pinia'
// pinia持久化加密
import SecureLS from 'secure-ls';
const ls = new SecureLS({
isCompression: false, //是否压缩
encodingType: 'AES', //加密类型 Base64/AES/DES/Rabbit/RC4/''
encryptionSecret: "encryption" //PBKDF2值
})
const piniaStorage = {
getItem(key){
return ls.get(key)
},
setItem(key,value) {
ls.set(key,value)
},
}
export const inputStore=defineStore('input', {
// 存放状态数据 类似组件中的data
state: ()=> {
return {
inputValue: '',
}
},
// pinia持久化加密
persist: {
enabled: true,
key: 'user',
storage: piniaStorage
},
getters: {
},
actions: {
}
})
7,axios二次封装
安装依赖
npm i axios -s
在main.js中进行配置
import { createApp } from 'vue'
import App from './App.vue'
// axios
import axios from './plugins/axios/axiosInstance.js'
const app = createApp(App)
app.mount('#app')
app.config.globalProperties.$axios=axios; //配置axios的全局引用
在vue项目文件夹目录src下创建plugins/axios/axiosInstance.js。axiosInstance.js代码如下
import axios from 'axios'
import { ElMessage } from 'element-plus'
//使用axios下面的create([config])方法创建axios实例,其中config参数为axios最基本的配置信息。
const API = axios.create({
baseURL:'', //请求后端数据的基本地址,自定义
// timeout: 2000 //请求超时设置,单位ms
})
// 添加请求拦截器
API.interceptors.request.use(
function(config) {
const token = localStorage.getItem('token')
// 请求头添加token
if (token) {
config.headers.Authorization = token
}
return config
},
function(error) {
return Promise.reject(error)
}
)
// 响应拦截器即异常处理
API.interceptors.response.use(
response => {
return response
},
err => {
console.log(err)
if (err && err.response) {
switch (err.response.status) {
case 400:
err.message = '请求出错!'
break
case 401:
ElMessage.error({
message: '授权失败,请重新登录!'
})
setTimeout(() => {
window.location.href('/login')
}, 1500)
return
case 403:
err.message = '拒绝访问!'
break
case 404:
err.message = '请求错误,未找到该资源!'
break
case 500:
err.message = '服务器端出错!'
break
}
} else {
err.message = '连接服务器失败!'
}
ElMessage.error({
message: err.message
})
return Promise.reject(err.response)
}
)
export default API
在需要使用axios的组件内引用
<template>
</template>
<script setup>
import { ref } from "vue";
import Axios from "../plugins/axios/axiosInstance" //路径
Axios({
url:'', //接口链接
method:'post', //方法:get/post/...
data: { //提交载荷
},
headers: { //header头,按情况分配
"Content-Type": "multipart/form-data"
}
}).then((res)=>{
//输出结果
})
</script>
<style>
</style>
8,scss使用
安装插件
npm i sass -s
直接在vue中使用
<template>
</template>
<script setup>
</script>
<style lang="scss" scoped>
ul{
list-style: none;
li{
color: red;
}
}
</style>
less同理
9,crypto-js数据加密
效果:
安装
npm i crypto-js -s
页面中引用
<template>
</template>
<script setup>
import CryptoJS from 'crypto-js'
const secretKey = 'secretkey'
const originData = 'hello'
//AES加解密
const encryptedMessage = CryptoJS.AES.encrypt(originData, secretKey)
const decryptedMessage = CryptoJS.AES.decrypt(encryptedMessage, secretKey).toString(CryptoJS.enc.Utf8)
//Base64加解密
const encryptedMessage = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(originData))
const decryptedMessage = CryptoJS.enc.Base64.parse(encryptedMessage).toString(CryptoJS.enc.Utf8)
</script>
<style scoped>
</style>
测试:加解密工具网站
10,文字验证码
效果:
在父组件中编写代码
<template>
<el-form :model="user">
<el-form-item>
<el-input v-model="user.username" :prefix-icon="User" placeholder="请输入用户名" size="large"/>
</el-form-item>
<el-form-item>
<el-input v-model="user.password" type="password" show-password :prefix-icon="Lock" placeholder="请输入密码" size="large"/>
</el-form-item>
<el-form-item>
<el-input v-model="user.identifyCode" :prefix-icon="Key" placeholder="请输入验证码" size="large"/>
<Identify :identifyCode="graphIdentifyCode" @click="makeCode()" title="看不清,换一换"></Identify> <!-- 组件 -->
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit" style="width:100%;" size="large">登录</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import { ref, reactive, onMounted } from "vue";
import { User, Lock, Key } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import Identify from './plugins/Identify.vue' //引用组件,注意链接位置
// 组件挂载
onMounted(() => {
// 验证码初始化
graphIdentifyCode.value = ''
makeCode()
});
//用户输入
const user = reactive({
username: "",
password: "",
identifyCode: "" //输入框验证码
});
//验证码登录
const graphIdentifyCode = ref('') //图形验证码
const graphCodes = ref('1234567890abcdefjhijklinopqrsduvwxyz') //验证码出现的数据和字母
const graphIdentifyCodeLength = 4 //验证码长度
//生成随机数
const randomNum = (min, max) => {
return Math.floor(Math.random() * (max - min) + min)
}
// 随机生成验证码字符串
const makeCode = () => {
graphIdentifyCode.value = ''
for(let i = 0; i < graphIdentifyCodeLength; i++){
var value = graphCodes.value[randomNum(0, graphCodes.value.length)]
if(Math.random() > 0.5 && /^[a-zA-Z]+$/.test(value)){ //设置随机数,验证码英文变大写
value = value.toLocaleUpperCase()
}
graphIdentifyCode.value += value
}
console.log('图形验证码', graphIdentifyCode.value)
}
// 登录按钮
const onSubmit = () => {
if(user.identifyCode == ''){
ElMessage({
message: '请输入验证码',
type: 'error'
})
}else if(graphIdentifyCode.value.toLocaleLowerCase() != user.identifyCode.toLocaleLowerCase()){ //用户输入验证码与图片验证码本文比对
ElMessage({
message: '验证码错误',
type: 'error'
})
}else{
ElMessage({
message: '验证码正确',
type: 'success'
})
}
}
</script>
<style scoped>
</style>
子组件Identify.vue
<template>
<div class="s-canvas">
<canvas id="s-canvas" :width="props.contentWidth" :height="props.contentHeight"> </canvas>
</div>
</template>
<script setup>
import { onMounted, watch } from 'vue'
const props = defineProps({
identifyCode: {
type: String,
default: '1234'
},
fontSizeMin: {
type: Number,
default: 25
},
fontSizeMax: {
type: Number,
default: 35
},
backgroundColorMin: {
type: Number,
default: 255
},
backgroundColorMax: {
type: Number,
default: 255
},
colorMin: {
type: Number,
default: 0
},
colorMax: {
type: Number,
default: 160
},
lineColorMin: {
type: Number,
default: 40
},
lineColorMax: {
type: Number,
default: 180
},
dotColorMin: {
type: Number,
default: 0
},
dotColorMax: {
type: Number,
default: 255
},
contentWidth: {
type: Number,
default: 112
},
contentHeight: {
type: Number,
default: 40
}
})
// 生成一个随机数
const randomNum = (min, max) => {
return Math.floor(Math.random() * (max - min) + min)
}
// 生成一个随机的颜色
const randomColor = (min, max) => {
let r = randomNum(min, max)
let g = randomNum(min, max)
let b = randomNum(min, max)
return 'rgb(' + r + ',' + g + ',' + b + ')'
}
// 绘制干扰线
const drawLine = (ctx) => {
for (let i = 0; i < 5; i++) {
ctx.strokeStyle = randomColor(props.lineColorMin, props.lineColorMax)
ctx.beginPath()
ctx.moveTo(randomNum(0, props.contentWidth), randomNum(0, props.contentHeight))
ctx.lineTo(randomNum(0, props.contentWidth), randomNum(0, props.contentHeight))
ctx.stroke()
}
}
//在画布上显示数据
const drawText = (ctx, txt, i) => {
ctx.fillStyle = randomColor(props.colorMin, props.colorMax)
ctx.font = randomNum(props.fontSizeMin, props.fontSizeMax) + 'px SimHei'
let x = (i + 1) * (props.contentWidth / (props.identifyCode.length + 1))
let y = randomNum(props.fontSizeMax, props.contentHeight - 5)
var deg = randomNum(-45, 45)
// 修改坐标原点和旋转角度
ctx.translate(x, y)
ctx.rotate((deg * Math.PI) / 180)
ctx.fillText(txt, 0, 0)
// 恢复坐标原点和旋转角度
ctx.rotate((-deg * Math.PI) / 180)
ctx.translate(-x, -y)
}
// 绘制干扰点
const drawDot = (ctx) => {
for (let i = 0; i < 80; i++) {
ctx.fillStyle = randomColor(0, 255)
ctx.beginPath()
ctx.arc(randomNum(0, props.contentWidth), randomNum(0, props.contentHeight), 1, 0, 2 * Math.PI)
ctx.fill()
}
}
//画图
const drawPic = () => {
let canvas = document.getElementById('s-canvas')
let ctx = canvas.getContext('2d')
ctx.textBaseline = 'bottom'
// 绘制背景
ctx.fillStyle = randomColor(props.backgroundColorMin, props.backgroundColorMax)
ctx.fillRect(0, 0, props.contentWidth, props.contentHeight)
// 绘制文字
for (let i = 0; i < props.identifyCode.length; i++) {
drawText(ctx, props.identifyCode[i], i)
}
drawLine(ctx)
drawDot(ctx)
}
//组件挂载
onMounted(() => {
drawPic()
})
// 监听
watch(
() => props.identifyCode,
() => {
drawPic()
}
)
</script>
<style scoped lang="scss">
.s-canvas {
cursor: pointer;
height: 40px;
}
</style>
参考:vue3 实现登录验证码 作者:奔跑的露西
11,引用自定义文字
把自己的文字文件夹放入vue项目目录src/assets中,并在该文件夹中创建font.css文件
编写font.css文件
@font-face {
font-family: '自定义字体名';
src: url('字体路径');
}
在main.js中引入
import './assets/fonts/font.css'
在需要更改文字的vue文件中引用
<template>
<div class="title">Hello</div>
</template>
<script setup>
</script>
<style lang="scss" scoped>
.title{
font-family: '自定义字体名';
font-size: 20px;
color: #fff;
}
</style>
12,使用mock生成虚拟数据
开发时,后端还没完成接口编写,前端只好写静态模拟数据。
安装
npm i mockjs -s
在vue+vite项目中,src文件夹下创建mock文件夹,并在该文件夹下新建indes.js
import Mock from 'mockjs'
let data = Mock.mock({
"code": 200,
"data|5": [ //生成100条数据 数组
{
"shopId|+1": 1,//生成商品id,自增1
"shopMsg": "@ctitle(10)", //生成商品信息,长度为10个汉字
"shopName": "@cname",//生成商品名 , 都是中国人的名字
"shopTel": /^1(5|3|7|8)[0-9]{9}$/,//生成随机电话号
"shopAddress": "@county(true)", //随机生成地址
"shopStar|1-5": "★", //随机生成1-5个星星
"salesVolume|30-1000": 30, //随机生成商品价格 在30-1000之间
"shopLogo": "@Image('100x40','#c33', '#ffffff','小北鼻')", //生成随机图片,大小/背景色/字体颜色/文字信息
"food|7": [ //每个商品中再随机生成七个food
{
"foodName": "@cname", //food的名字
"foodPic": "@Image('100x40','#c33', '#ffffff','小可爱')",//生成随机图片,大小/背景色/字体颜色/文字信息
"foodPrice|1-100": 20,//生成1-100的随机数
"aname|14": [
{
"aname": "@cname",
"aprice|30-60": 20
}
]
}
]
}
],
"message": "操作成功",
"total": 0
})
Mock.mock(/goods\/goodAll/, 'post', () => { //三个参数。第一个路径,第二个请求方式post/get,第三个回调,返回值
return data
})
在需要调用接口的vue文件内编写,注意import引入的路径
<template>
</template>
<script setup>
import { ref, onMounted } from "vue";
// 之前封装好的axios
import Axios from "../plugins/axios/axiosInstance";
// 引用mock
import "../plugins/mock/index.js"
onMounted(()=>{
getData()
})
const getData = () => {
Axios({
url: "/goods/goodAll",
method: "post"
}).then((res) => {
const request = res.data
console.log(request)
}).catch((err)=>{
console.log(err)
})
}
</script>
<style lang="scss" scoped>
</style>
13,浏览器唯一标识
我在项目中使用的场景:用户登录后把用户信息存入pinia,pinia持久化后存入localStorage,这样就有了一个安全隐患,有心之人会复制别人的localStorage用户信息到自己的浏览器上,冒充别人登录,这里我们忽略token有效时间。
使用效果:有心之人保存复制了别人浏览器localStorage的pinia仓库数据,在自己的浏览器粘贴localStorage,浏览器唯一标识会判断是否当前浏览器和pinia仓库浏览器标识对比,判断是否为同一浏览器,不同则被踢出,重新登录。
同一电脑,不同浏览器软件的唯一标识也不一样
安装
npm i fingerprintjs2 -s
在安装的时候,会提示以下信息,我们可以忽略
vue示例代码
<template>
<router-view></router-view>
</template>
<script setup>
import { onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { userMainStore } from "./store/user"; // pinia仓库
import Fingerprint2 from 'fingerprintjs2'
onMounted(()=>{
createFingerprint()
})
const router = useRouter()
const userStore = userMainStore()
const createFingerprint = () => {
// 浏览器指纹
const fingerprint = Fingerprint2.get((components) => {
// 参数只有回调函数时,默认浏览器指纹依据所有配置信息进行生成
const values = components.map((component) => component.value) // 配置的值的数组
const murmur = Fingerprint2.x64hash128(values.join(''), 31) // 生成浏览器指纹
// userStore.browserFingerprint在登录时存入
if(murmur != userStore.browserFingerprint && userStore.browserFingerprint != ''){
userStore.signOut() // 清空pinia仓库
router.push('/login')
}
})
}
</script>
<style lang="scss">
</style>
接下来我们来解决上面的安装时忽略的信息,它让我们安装的是@fingerprintjs/fingerprintjs
安装
npm i javascript-obfuscator --dev-save
我之所以没有采用这个的原因:这个npm包生成浏览器唯一标识采用了异步方法,在判断时需要加一个定时器来获取生成的唯一标识,我在其他pinia仓库里存了用户的动态菜单信息,在页面刷新重新装载pinia数据时,由于定时器的间隔,会把原先用户的菜单渲染出来,再踢出登录,会暴漏用户信息,所以没有采用
vue实列代码
<template>
<router-view></router-view>
</template>
<script setup>
import { onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { userMainStore } from "./store/user";
import FingerprintJS from '@fingerprintjs/fingerprintjs';
onMounted(()=>{
determineUser()
})
const router = useRouter()
const userStore = userMainStore()
const getFingerPrintID = () => {
const fpPromise = FingerprintJS.load();
const result = fpPromise.get();
return result.visitorId;
}
const determineUser = async() => {
const id = getFingerPrintID();
setTimeout(()=>{
if(id != userStore.browserFingerprint && userStore.browserFingerprint != ''){
userStore.signOut()
router.push('/login')
}
}, 10)
}
</script>
<style lang="scss">
</style>
14,vite项目打包加密混淆
vue+vite项目原先在打包部署后,在浏览器开发者模式(F12)的网络中,可以查看js源代码,大概是下面样子的,看一个分页查询用户的Axios请求,代码大概还能看出个所以然
我们可以再次加密,使用javascript-obfuscator
安装
npm i javascript-obfuscator --dev-save
在package.json中编辑scripts,关键在build这一行,并注意打包的路径
"scripts": {
"dev": "vite --open",
"build": "vite build && javascript-obfuscator dist/assets --output dist/assets",
"preview": "vite preview"
},
编辑完成后,通过npm run build进行打包,部署后在浏览器中打开,通过F12查看分页查询用户的Axios请求源代码是这样的
15,vite打包移除console
两种方法
1,直接在vite.config.js文件里进行配置
export default defineConfig({
plugins: [vue()],
esbuild: {
drop: ['console'],
// drop: ['console', 'debugger'], //移除console, debugger
},
server:{ // 自定义端口
host:'0.0.0.0'
}
})
2,安装terser
npm i terser --sava-dev
直接在vite.config.js文件里进行配置
export default defineConfig({
plugins: [vue()],
build: {
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
// drop_debugger: true, // 去除报错
},
},
}
server:{
host:'0.0.0.0'
}
})
16,router-view,keepAlive和Transition结合使用
<router-view v-slot="{ Component }">
<transition name="slide">
<keep-alive :include="aliveMenu">
<component :is="Component" :key="$route.name" />
</keep-alive>
</transition>
</router-view>
// slide : 样式类名
// aliveMenu : keepalive需要动态保活的组件 格式: 英文逗号分隔的字符串,数组; 内容:组件的name
// include : 限定包括(保活)该组件 另外还有 exclude : 不包括(不保活)组件
// keepAlive 有两个生命周期
// onActivated 组件激活时调用(切换回当前保活组件时) 组件挂载时也调用一次
// onDeactivated 组件停用时调用(切换到其他组件时) 组件卸载时也调用一次
下面是一个能用的css样式, 注意组件内外部定位
.slide-enter-active, .slide-leave-active {
transition: all .3s ease;
position: absolute;
width: 100%;
overflow: hidden;
}
.slide-enter-from{
position: absolute;
transform: translateX(-100%);
opacity: 0;
}
.slide-enter-to{
opacity: 1;
}
.slide-leave-to {
transform: translateX(-100%); /* 向右滑动 */
opacity: 0;
}
.slide-leave-active {
transform: translateX(100%); /* 向左滑动 */
}
.page-item {
width: 100%;
height: 100%;
overflow-x: hidden;
overflow-y: auto;
position: relative;
scrollbar-width: none; /* 火狐浏览器隐藏滚动条 */
&::-webkit-scrollbar {
display: none;
}
/* 组件刚开始离开 */
/* .fade-leave-active{
} */
/* 组件离开结束 */
.fade-leave-to {
transform: translateX(40px);
opacity: 0;
}
/* 组件刚开始进入 */
.fade-enter-active {
transform: translateX(-70px);
opacity: 0;
}
/* 组件进入完成 */
.fade-enter-to {
transform: translateX(0);
opacity: 1;
}
/* 正在进入和离开 */
.fade-leave-active,
.fade-enter-active {
transition: all .3s ease-out;
}
}
17,按需引入
1,element-plus自动按需引入
js中
安装elementplus以及相关插件
// 安装element-plus
npm i element-plus --save
// 安装element-plus图标
npm i @element-plus/icons-vue --save
// 安装自动引入插件
npm i unplugin-vue-components unplugin-auto-import --save-dev
// 安装图标自动引入
npm i unplugin-icons --save-dev
在vite.config.js中进行配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import Icons from 'unplugin-icons/vite'
import IconsResolver from 'unplugin-icons/resolver'
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [
ElementPlusResolver(),
IconsResolver({
prefix: 'Icon',
})
],
}),
Components({
resolvers: [
ElementPlusResolver(),
IconsResolver({
enabledCollections: ['ep'],
})
],
}),
Icons({
autoInstall: true,
})
]
})
elementplus的图标在vue的template中使用要加<i-ep>,比如<User />改为<i-ep-user />
另外ElMessage,ElMessageBox不用再额外引入
!注意:main.js中不必再引入elementplus以及app.use(ElementPlus)
element-plus汉化
<template>
<el-config-provider :locale="zhCn">
<router-view></router-view>
</el-config-provider>
</template>
<script setup>
// 引入配置组件
import { ElConfigProvider } from "element-plus";
// 引入中文包
import zhCn from "element-plus/dist/locale/zh-cn.mjs";
</script>
<style lang="scss">
</style>
ts中
安装elementplus以及相关插件与js方式相同
根目录下新建types文件夹
配置vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// ElementPlus自动导入
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// ElementPlus的Icon自动导入
import Icons from 'unplugin-icons/vite'
import IconsResolver from 'unplugin-icons/resolver'
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [
ElementPlusResolver(),
// 自动导入图标组件
IconsResolver({
prefix: 'Icon',
}),
],
// 导出一个类型声明文件到types文件夹下
dts: fileURLToPath(new URL('./types/auto-imports.d.ts', import.meta.url)),
}),
// 自动注册ElementPlus组件
Components({
resolvers: [
ElementPlusResolver(),
// 自动注册图标组件
IconsResolver({
enabledCollections: ['ep'],
}),
],
// 导出一个类型声明文件到types文件夹下
dts: fileURLToPath(new URL('./types/components.d.ts', import.meta.url)),
}),
// 自动安装图标
Icons({
autoInstall: true,
}),
],
css: {
preprocessorOptions: {
scss: {
additionalData: `@use "./src/assets/css/elementplus.scss" as *;`,
},
},
},
})
在tsconfig.app.json中配置,多引入一个types/*.d.ts
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "types/*.d.ts"]
2,echarts
在src文件夹下新建若干文件夹以及js文件,目录为 src/utils/echarts/echarts.js
在echarts.js文件中进行编写代码,下面代码中如果引入过多或过少,按实际需求引入
// 引入 echarts 核心模块。
import * as echarts from 'echarts/core';
//引入柱状图和折线图组件。
import { BarChart, LineChart, ScatterChart, BoxplotChart, GaugeChart, PieChart } from 'echarts/charts';
// 引入标题、提示框、网格、数据集和数据转换器组件。
import {
TitleComponent,
TooltipComponent,
GridComponent,
// 数据集组件
DatasetComponent,
// 内置数据转换器组件 (filter, sort)
TransformComponent,
LegendComponent,
DataZoomComponent,
BrushComponent,
ToolboxComponent,
MarkPointComponent,
MarkLineComponent,
} from 'echarts/components';
//引入标签布局和通用过渡动画特性。
import { LabelLayout, UniversalTransition } from 'echarts/features';
// 引入 Canvas 渲染器。
import { CanvasRenderer, SVGRenderer } from 'echarts/renderers';
/**
注册必须的组件,包括标题、提示框、网格、数据集、数据转换器,
以及柱状图、折线图、标签布局、通用过渡动画和 Canvas 渲染器。
*/
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
LegendComponent,
DataZoomComponent,
BrushComponent,
ToolboxComponent,
MarkPointComponent,
MarkLineComponent,
BarChart,
LineChart,
ScatterChart,
BoxplotChart,
GaugeChart,
PieChart,
LabelLayout,
UniversalTransition,
CanvasRenderer,
SVGRenderer
]);
// 导出
export default echarts;
在main.js中进行配置
// echarts
import echarts from '@/utils/echarts/echarts.js';
const app = createApp(App)
app.mount('#app')
app.provide('$echarts', echarts);
在使用echarts的vue组件中使用
<template>
</template>
<script setup>
import { ref, inject } from "vue";
const echarts = inject("$echarts");
const domRef = ref();
const chart = null;
chart = echarts.init(domRef.value, null, { locale: "zh" });
</script>
<style lang="scss" scoped>
</style>
18,大屏可视化工具
1,autofit.js自适应屏幕
安装
npm i autofit.js --save
使用
<template>
</template>
<script setup>
import autofit from "autofit.js";
onMounted(() => {
autofit.init({
dh: 1080,
dw: 1920,
el: "#app",
resize: true,
}, false); // 可关闭控制台运行提示输出
});
</script>
<style lang="scss" scoped>
</style>
2,data-view
由于vue2版本的比较完善,我们使用vue2版本的
安装
npm i @jiaminghi/data-view --save
main.js中引入
import dataV from '@jiaminghi/data-view'
const app = createApp(App)
app.use(dataV)
app.mount('#app')
在vite.config.js中配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()]
optimizeDeps: {
// 开发时 解决这些commonjs包转成esm包
include: [
"@jiaminghi/c-render",
"@jiaminghi/c-render/lib/plugin/util",
"@jiaminghi/charts/lib/util/index",
"@jiaminghi/charts/lib/util",
"@jiaminghi/charts/lib/extend/index",
"@jiaminghi/charts",
"@jiaminghi/color",
],
}
})
在vue3项目中会报错
根据错误提示在node_modules/@jiaminghi/data-view/lib/components/decoration6/src/main.vue文件中移动key的位置,其他的组件这是这种方法修改
修改前
修改后
使用
<template>
<dv-border-box-1>
</dv-border-box-1>
</template>
<script setup>
</script>
<style lang="scss" scoped>
</style>
19,vue项目打包开启gzip压缩,部署在nginx
安装
npm i vite-plugin-compression --save-dev
在vite.config.js中配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import viteCompression from 'vite-plugin-compression'
export default defineConfig({
plugins: [
vue(),
viteCompression({
verbose: true, // 默认即可
disable: false, //开启压缩(不禁用),默认即可
deleteOriginFile: false, //删除源文件
threshold: 1024, //压缩前最小文件大小
algorithm: 'gzip', //压缩算法
ext: '.gz', //文件类型
})
]
})
使用npm run build打包后,把打包后的文件夹放到nginx的html下
打开nginx文件夹下的conf/nginx.conf文件,vscode或记事本编辑
server {
listen 端口号;
server_name localhost;
gzip on;
gzip_static on;
gzip_buffers 4 16k;
gzip_http_version 1.1;
gzip_comp_level 8;
gzip_min_length 1k;
gzip_types application/javascript text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; #压缩文件类型
gzip_vary on;
location / {
root html/打包的项目名;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
浏览器F12,响应标头显示Content-Encoding: gzip表示资源使用gzip压缩了