vue的一些插件和方法

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压缩了

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

未语君安然

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值