vue

About 42 min

vue

vue项目打包一键去掉所有console.log

在Vue项目中,可以通过使用babel插件来去除所有的console.log语句。以下是一种常用的方法:

  1. 安装babel插件:babel-plugin-transform-remove-console
npm install babel-plugin-transform-remove-console --save-dev
  1. 在项目的根目录下创建一个babel.config.js文件,并添加以下内容:
module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset'
  ],
  plugins: [
    process.env.NODE_ENV === 'production' ? 'transform-remove-console' : ''
  ]
}
  1. 运行打包命令,例如:
npm run build

这样,在生产环境下,所有的console.log语句都会被自动去除。

请注意,这只会在打包时去除console.log语句,而不会影响开发环境下的调试信息。

你可以参考以下链接获取更多关于此话题的信息:

vue中修改props传进来的值

vue中修改props传进来的值_使用计算属性更新prop的值-CSDN博客open in new window

在子组件修改props的方法:

  1. 子组件data中拷贝一份,注意引用类型需要深拷贝,根据需求可以watch监听
data() {
    return {
        newList: this.list.slice()
    }
},
watch: {
    list(newVal) {
        this.newList = newVal
    }
}
  1. 通过计算属性修改
computed: {
    nList() {
        return this.list.filter(item => {
            return item.isChecked
        })
    }
}
  1. sync修饰符

父组件 穿进去的时候加上 .sync

子组件 通过this.$emit('update:xxx', params)


// 父组件
<todo-list :list.sync="list" />
 
// 子组件
methodName(index) {
    this.$emit('update:list', this.newList)
}

Vue内置组件的component标签

Vue内置组件的component标签-CSDN博客open in new window

@click的事件修饰符

修饰符说明
.stop阻止冒泡 调用 event.stopPropagation()
.prevent阻止默认事件 调用 event.preventDefault()
.capture添加事件侦听器时使用事件捕获模式
.once事件只触发一次
.self只有点击当前元素本身时才会触发回调

element ui按需导入

cnpm install element-ui --save-dev

main.js

import {
    Button,
    Popover,
    Cascader,
    CascaderPanel
} from 'element-ui'

Vue.use(Button)
Vue.use(Popover)
Vue.use(Cascader)
Vue.use(CascaderPanel)

在使用组件的页面导入样式

import 'element-ui/lib/theme-chalk/index.css'

组件二次封装

Vue 基础组件二次封装的高级技巧及方法,能更优雅的进行二次封装组件(Vue3组件封装--继承第三方组件的Methods方法)_vue moment时间处理二次封装-CSDN博客open in new window

vue实现登录后跳转到之前的页面

(202条消息) vue实现登录后跳转到之前的页面_longzhoufeng的博客-CSDN博客open in new window

main.js

router.beforeEach((to, from, next) => {
    if (to.path == '/login' && from.path!='/register') {
    	//保存当前路由
        localStorage.setItem("preRoute", router.currentRoute.fullPath)
    }
    next()
})

登录界面 login.vue

this.$store.dispatch("Login", this.loginForm).then(response => {
	if (response.code == 200) {
		const curr = localStorage.getItem('preRoute')
		if (curr == null) {
			this.$router.push({ path: "/user_center" });
		} else {
			this.$router.push({ path: curr });
		}
		this.$message({ message: response.msg, type: "success", "duration": 1000 });
	} else {
		this.$message.error(response.msg);
	}
}).catch((response) => {
	this.$message.error('请联系管理员!!!');
});

vue2和vue3响应式(简易版)

vue2通过遍历循环对象属性(for key in obj),在对每一个属性的值用 Object.defineProperty 进行getter和setter的改造

vue3通过Proxy(代理)和Reflect(反射)的使用完成 响应式。

function observe(data){
  if(!data || typeof data !== 'object') return
  for(let key in data){
    let value = data[key]
    Object.defineProperty(data, key, {
      enumerable: true,
      configurable: true,
      get(){
        return value
      },
      set(newVal){
        value = newVal
      }
    })
    if(typeof value === 'object'){
      observe(value)
    }
  }
}
function reactive(obj){
  const handler = {
    get(target, key, receiver){
      const value = Reflect.get(...arguments)
      if(typeof value === 'object'){
        return reactive(value)
      }else{
        return value
      }
    },
    set(target, key, val, receiver){
      return Reflect.set(...arguments)
    }
  }
  
  return new Proxy(obj, handler)
}

vuex的基本使用 pinia的基本使用

官网: 安装 | Pinia 中文文档 (web3doc.top)open in new window

vuex的基本使用可以查看以下文章:

https://juejin.cn/post/6928468842377117709open in new window

https://juejin.cn/post/6994337441314242590#heading-15open in new window

https://juejin.cn/post/7087100496762109983#heading-16open in new window

简明易懂:https://juejin.cn/post/7280007176776204327?searchId=2024011109491811B46B930D323378F7D4open in new window

pinia的基本使用可以查看以下文章: (pinia相比vuex简单很多)

https://juejin.cn/post/7078281612013764616#heading-0open in new window

vuex

安装:npm install vuex --save

定义 store

import Vuex from 'vuex'

Vue.use( Vuex );

const store = new Vuex.Store({
    //待添加
    const store = new Vuex.Store({
        state:{ 
          products: [
            {name: '鼠标', price: 20},
            {name: '键盘', price: 40}
          ]
        }getters:{ //添加getters
            saleProducts: (state) => {
              let saleProducts = state.products.map( product => {
                return {
                  name: product.name,
                  price: product.price / 2
                }
              })
              return saleProducts;
            }
          },
        mutations:{ //添加mutations,payload是函数接收到的参数
            minusPrice (state, payload ) {
              let newPrice = state.products.forEach( product => {
                product.price -= payload
              })
            }
          },
        actions:{ //添加actions,payload是函数接收到的参数
            minusPriceAsync( context, payload ) {
              setTimeout( () => {
                context.commit( 'minusPrice', payload );  //context提交,通过调用mutations里面的方法来改变数据
              }, 2000)
            }
          }   
})

})

new Vue({
    el: '#app',
    store,
    render: h => h(App)
})

使用

this.$store.state.products                    //获取store中state的数据
this.$store.getters.saleProducts              //获取store中getters的数据
this.$store.commit('minusPrice', 2);          // 同步方法,调用mutations中的方法,2是传递的参数
this.$store.dispatch('minusPriceAsync', 5);   //分发actions中的minusPriceAsync这个异步函数,5是参数

辅助函数

import { mapState,mapGetters ,mapMutations,mapActions   } from "vuex";  //引入方法
export default {
  name: "App",
  computed: {
    ...mapState(["products"]),        // 注意这里是数组
    ...mapGetters([
      'saleProducts' // 将 saleProducts 映射为组件的 saleProducts 属性
    ])
  },
  methods: {
       //在methods中使用拓展运算符展开函数
       ...mapMutations(["minusPrice"]),
       ...mapActions(["minusPriceAsync"]),
      // 调用函数
      changeBtn2() {
        this.minusPrice("1");
        this.minusPriceAsync("2");
      },
  },

};

Modules 模块

每个模块拥有自己的 state、mutation,action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

页面刷新后Vuex 状态丢失怎么解决?

Vuex 只是在内存中保存状态,刷新后就会丢失,如果要持久化就需要保存起来。

localStorage就很合适,提交mutation的时候同时存入localStorage,在store中把值取出来作为state的初始值即可。

也可以使用第三方插件,推荐使用vuex-persist插件,它是为 Vuex 持久化储存而生的一个插件,不需要你手动存取storage,而是直接将状态保存至 cookie 或者 localStorage中。

pinia(更轻便)

官网:https://pinia.vuejs.org/zh/introduction.htmlopen in new window

npm install pinia

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')

定义

import { defineStore } from 'pinia'

// useStore 可以是 useUser、useCart 之类的任何东西
// 第一个参数是应用程序中 store 的唯一 id,可以自定义
//这个 name,也称为 id,是必要的,Pinia 使用它来将 store 连接到 devtools。 将返回的函数命名为 use... 是跨可组合项的约定,以使其符合你的使用习惯。
export const useStore = defineStore('useStore', {
    // other options...
    // state 为 store 的核心,表示当前模块的状态
    // 为了完整类型推理,推荐使用箭头函数
    state: () => {
        return {
            num: 10
        }
    },
    getters: {
        doubleNum: (state) => state.num * 2,
      },
    actions: {
        add(){
            this.num++
        },
        addMore(num){
            this.num+=parseInt(num)
        }
    }
})

使用

<script setup lang='ts'>
import {useStore} from '@/store/index.js';

const store=useStore()
import { ref, onMounted } from 'vue';

console.log(store.num)

const add=(num)=>{
    store.add()
    store.addMore(num)  // 带参数
}
const storeReset=()=>{
    //您可以通过调用 store 上的 $reset() 方法将状态 重置 到其初始值
    store.$reset()
}

</script>

Pinia 配套有个插件 pinia-plugin-persistopen in new window进行数据持久化,否则一刷新就会造成数据丢失

vue实现全屏滚动

页面全屏滚动很多用于官网首页,使用的频率还挺高的,此处的代码实现的功能是 鼠标滚动,页面切换,点击键盘的上下键,页面切换。类似效果看 全屏滚动_jQuery之家-自由分享jQuery、html5、css3的插件库 (htmleaf.com)open in new window ,以下内容改自 vue实现全屏滚动,非fullpage.js-阿里云开发者社区 (aliyun.com)open in new window

.vue文件

@mousewheel.prevent 鼠标滚动,实现页面的切换

@keyup.prevent 点击键盘实现页面滚动,此处为键盘上下键点击实现页面滚动

<div class="fullPage" ref="fullPage">
	<div
    class="fullPageContainer"
    ref="fullPageContainer"
    @mousewheel.prevent="mouseWheelHandle"
    @keyup.prevent="keyHandle"
    @DOMMouseScroll.prevent="mouseWheelHandle" >
    	<div style="width:100%;height:100vh;overflow: hidden;">
            <div>
            	// 这个div写页面里面的内容,页面的内容占满一整个屏幕
            </div>
        </div>
        <div style="width:100%;height:100vh;overflow: hidden;"></div>
        <div style="width:100%;height:100vh;overflow: hidden;"></div>
		<div style="width:100%;height:100vh;overflow: hidden;"></div>
		........
        //  有几个全屏滚动的页面,就写几个div
    </div>
</div>

css

<style lang="less" scoped>
.fullPage{
  height: 100%;//一定要设置,保证占满
  overflow: hidden;//一定要设置,多余的先隐藏
}
.fullPageContainer{
  width: 100%;
  height: 100%;
  transition: all linear 0.5s;
}
</style>

script

      fullpage: {
        current: 1, // 当前的页面编号
        isScrolling: false, // 是否在滚动,是为了防止滚动多页,需要通过一个变量来控制是否滚动
        deltaY: 0 // 返回鼠标滚轮的垂直滚动量,保存的鼠标滚动事件的deleteY,用来判断是往下还是往上滚
      }


mounted(){
    //  没有在mounted里加上键盘点击事件,使用键盘点击切换页面会出bug。即点击完页面时能使用键盘实现页面滚动,当点击了网页外在点击回网页内,键盘上下键切换页面会失效。
	window.addEventListener('keyup', this.keyHandle, false)
},

methods:{
   next () { // 往下切换
      const len = 4 // 页面的个数
      if (this.fullpage.current + 1 <= len) { // 如果当前页面编号+1 小于总个数,则可以执行向下滑动
        this.fullpage.current += 1 // 页面+1
        this.move(this.fullpage.current) // 执行切换
      }
    },
    pre () { // 往上切换
      if (this.fullpage.current - 1 > 0) { // 如果当前页面编号-1 大于0,则可以执行向下滑动
        this.fullpage.current -= 1// 页面+1
        this.move(this.fullpage.current)// 执行切换
      }
    },
    move (index) {
      this.fullpage.isScrolling = true // 为了防止滚动多页,需要通过一个变量来控制是否滚动
      this.directToMove(index) // 执行滚动
      setTimeout(() => { // 这里的动画是1s执行完,使用setTimeout延迟1s后解锁
        this.fullpage.isScrolling = false
      }, 500)
    },
    directToMove (index) {
      const height = document.body.clientHeight
      // const height = document.documentElement.clientHeight; 上面的不管用,可以用这个
      // const height = this.$refs['fullPage'].clientHeight // 获取屏幕的宽度
      const scrollPage = this.$refs['fullPageContainer'] // 获取执行tarnsform的元素
      let scrollHeight = '' // 计算滚动的告诉,是往上滚还往下滚
      scrollHeight = -(index - 1) * height + 'px'
      scrollPage.style.transform = `translateY(${scrollHeight})`
      this.fullpage.current = index
    },
    mouseWheelHandle (event) { // 监听鼠标监听
      // 添加冒泡阻止
      const evt = event || window.event
      if (evt.stopPropagation) {
        evt.stopPropagation()
      } else {
        evt.returnValue = false
      }
      if (this.fullpage.isScrolling) { // 判断是否可以滚动
        return false
      }
      const e = event.originalEvent || event
      this.fullpage.deltaY = e.deltaY || e.detail // Firefox使用detail
      if (this.fullpage.deltaY > 0) {
        this.next()
      } else if (this.fullpage.deltaY < 0) {
        this.pre()
      }
    },
    keyHandle (event) {
      const evt = event || window.event
      if (evt.keyCode === 40) { // 下滑
        this.next()
      }
      if (evt.keyCode === 38) {
        this.pre()
      }
    },
}

vue项目跳转时如何加密路由上面query传递的参数

vue-router路由中对query中的参数进行加密_router 参数加密_前端_小学生的博客-CSDN博客open in new window

vue-router中传递的参数进行自动加密显示,组件中获取自动解密 (github.com)open in new window

(实际使用有bug,在新页面打开带参数的url时,加密的参数并不能够解析出来)

后端返回很大的数据量,前端如何渲染

https://mp.weixin.qq.com/s/pWsntJbJnBip5CbP7HkvFgopen in new window

vite创建vue3项目

十分钟搭建 Vite+Vue3 项目模板 - 掘金 (juejin.cn)open in new window

从 0 搭建 Vite 3 + Vue 3 前端工程化项目 - 掘金 (juejin.cn)open in new window

Vue3+Element-plus前端学习笔记-巨长版 (qq.com)open in new window

普通创建vite项目

npm init vite@latest
pnpm create vite    // pnpm创建vite项目   https://blog.csdn.net/a486368464/article/details/133697214

模板创建项目

npm init vite@latest myproject -- --template vue

如果使用普通创建的方式,依次选择需要的配置项即可,如果模板创建,即可直接下载依赖运行

配置路由:

npm install vue-router

src目录下创建roter文件夹,文件夹下创建 index.js

src目录下创建views文件夹,用于存放页面。此处创建about.vue和list.vue用于测试

router/index.js内容如下:

import {
    createRouter,
    createWebHashHistory
} from 'vue-router'

const router = createRouter({
    history: createWebHashHistory(),
    // createWebHashHistory 用来配置内部使用hash模式路由
    routes: [{
            path: '/',
            redirect: '/about'
        },
        {
            path: '/about',
            name: 'about',
            component: () =>
                import ('../views/about.vue')
        },
        {
            path: '/list',
            name: 'list',
            component: () =>
                import ('../views/list.vue')
        },
    ]
})

export default router

在main.js引入路由文件并使用

import { createApp } from 'vue'
import './style.css'    // 对于原始样式不需要可以注释掉
import App from './App.vue'
import router from './router/index'

createApp(App).use(router).mount('#app') 

app.vue中只留下

<template>
  <router-view></router-view>
</template>

在使用路由的页面中使用函数式路由跳转

<template>
	<div>
		{{$route.query}}   //界面中可以通过 $route.query 来获取参数
	</div>
</template>

<script setup lang="ts">
import { useRoute,useRouter } from "vue-router";
const router=useRouter()
const route=useRoute()

const go=()=>{
   router.push({path:'/list',query:{name:'张三'}})
}

onMounted(()=>{
    console.log("route",route.query)
}

</script>

至此,路由配置完成。

注意:vue3中引入的组件可以直接使用

vite.config.js 配置 server服务 @ 路径

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import {
    resolve
} from "path"

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [vue()],
    server: {
        // 启动后打开浏览器
        open: true,
        // 设置主机
        host: '127.0.0.1',
        // 设置端口
        port: 3001
    },
    resolve: {
        // ↓路径别名,主要是这部分
        alias: {
            "@": resolve(__dirname, "./src")
        }
    }
})

下载less依赖


npm i less-loader less style-resources-loader --save-dev

下载配置 element-plus

npm install element-plus --save

main.js

import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

// element-plus默认英文,中文配置
// import zhCn from 'element-plus/lib/locale/lang/zh-cn' 这个路径会报错
import zhCn from 'element-plus/es/locale/lang/zh-cn'
createApp(App).use(router).use(ElementPlus, {locale: zhCn}).mount('#app')

下载配置axios

npm install axios qs

src/utils 下创建 request.js 封装axios请求

(写法1)

// 根据环境变量区分接口默认地址
switch(process.env.NODE_ENV){
    case "production":
        axios.defaults.baseURL="http://api.fanko.cn";
        break;
    case "test":
        axios.defaults.baseURL="http://192.168.20.12:8080";
        break;
    default:
        axios.defaults.baseURL="http://127.0.0.1:3000";
}
//  设置超时时间和跨域是否允许携带凭证
axios.defaults.timeout=10000;
axios.defaults.withCredentials=true;
/*
*  设置请求传输数据的格式(看服务器要求什么类型的数据格式)   x-www-form-urlencoded是一种数据格式
*  transformRequest 只对post请求有作用,根据实际要求决定设置不设置
*/
axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded';
axios.defaults.transformRequest = data => {qs.stringify(data)};

// 设置请求拦截器   客户端发送请求 => [请求拦截器] => 服务器 客户端发送请求给服务器的配置项就是 config
// token校验(JWT) 接受服务器返回的token 存储到vuex/本地存储中 
// 每一次向服务器发请求应该把 token 带上
axios.interceptors.request.use(
  config => {     //请求成功时执行
    //获取本地存储中的token
    let token = localStorage.getItem('token');
    //  验证token存在,请求头带上 token
    token && (config.headers.Authorization = token);
    return config;
  },
  error => {  //请求失败时执行
    return Primise.reject(error)
  }
 )
// 设置响应拦截器  服务器返回信息 => [响应拦截器统一处理] => 客户端
/*  axios.defaults.validateStatus=status=>{  基本公司不用配置
*      // 自定义响应成功的HTTP状态码
*      return /^(2|3)\d{2}$/.test(status);
*/  }

axios.interceptors.response.use(
  response=>{
     return response.data;
  },error => {//拦截失败
    return Primise.reject(error);
    let {response } = error;
    if(response) {
        switch (response.status) {
           case 401://权限问题,提示未登录或无权限等;
           break;
           case 403://服务器拒绝执行 (token过期)
           break;
           case 404://找不到页面
           break;
        }
    } else {
        //服务器连结果都没有返回
        if(!window.navigator.onLine) {
            //断网处理:跳转断网页面/提示网络错误等等
            return;
        }
        return Promise.reject(error)
    }
  }
)

export default axios;

(写法2)

//创建一个新 axios 实力
// 请求拦截器,如果有 token 进行头部携带
// 响应拦截器, 剥离无效token 处理 token 失效
// 导出一个函数,调用当前 axios 实例,返回值 promise

import axios from "axios";
//  导出基准地址,原因: 其他地方不是通过 axios 发送请求的地方用上基准地址
export const baseURL='http://localhost:8084'
const instance=axios.create({
  // axios 的一些配置,baseurl timeout 
  baseURL,
  timeout:5000
})

instance.interceptors.request.use(config=>{
  return config
},err=>{
  return Promise.reject(err)
})

// res => res.data  取出data数据,将来调用接口的时候直接拿到的就是后台的数据
instance.interceptors.response.use(res=>res.data,error=>{
  return Promise.reject(error);
  let {response } = error;
  if(response) {
    switch (response.status) {
      case 401://权限问题,提示未登录或无权限等;
        break;
      case 403://服务器拒绝执行 (token过期)
        break;
      case 404://找不到页面
        break;
    }
  } else {
    //服务器连结果都没有返回
    if(!window.navigator.onLine) {
      //断网处理:跳转断网页面/提示网络错误等等
      return;
    }
    return Promise.reject(error)
  }
})

export default(url,method,submitData)=>{
  // 负责发请求:请求地址,请求方式, 提交数据
  return instance({
    url,
    method,
    // 1. 如果是get请求  需要使用params来传递submitData   ?a=10&c=10
    // 2. 如果不是get请求  需要使用data来传递submitData   请求体传参
    // [] 设置一个动态的key, 写js表达式,js表达式的执行结果当作KEY
    // method参数:get,Get,GET  转换成小写再来判断
    // 在对象,['params']:submitData ===== params:submitData 这样理解
    [method.toLowerCase() === 'get' ? 'params' : 'data']: submitData
  })
}

api/use.js

import request from "@/utils/request.js";

export const addUser=(data)=>{
    return request('/user','post',data)
}

export const getListTest=()=>{
    return request('/user/string','get')
}

配置跨域(vite.config.js)

export default defineConfig({
  plugins: [vue()],
  server: {
    // 启动后打开浏览器
    open: true,
    proxy: {
      '/api': { //请求路径关键字
          target: 'http://localhost:8084', //对应自己的接口
          changeOrigin: true,//是否允许跨域,在本地会创建一个虚拟服务端,然后发送请求的数据,
                              // 并同时接收请求的数据,这样服务端和服务端进行数据的交互就不会有跨域问题
          ws: true,
          pathRewrite: {
              '^/api': ''      //这里理解成用‘/api’代替target里面的地址,后面组件中我们掉接口时直接用api代替
          }
      }
  }
  },
  resolve: {
      // ↓路径别名,主要是这部分
      alias: {
          "@": resolve(__dirname, "./src")
      }
  }
})

除了前端配置代理之外,后台也要配置跨域,否则请求不到,后台跨域解决可以参考下面的文章

SpringBoot 项目解决跨域的几种方案 - 掘金 (juejin.cn)open in new window

(vue3中使用消息提示 vue3中使用element-plus调用message_element-plus message_open in new window

配置pinia

Vuex 更好的替代品 Pinia,真香 (qq.com)open in new window

npm install pinia

main.js

import { createPinia } from 'pinia'

createApp(App).use(router).use(ElementPlus).use(createPinia()).mount('#app') 

store/index.js

Store 是使用 defineStore() 定义的,并且它需要一个唯一名称,作为第一个参数传递:

//pinia 中使用 definStore 方法来定义 store
import { defineStore } from 'pinia'

// useStore 可以是 useUser、useCart 之类的任何东西
// 第一个参数是应用程序中 store 的唯一 id,可以自定义
//这个 name,也称为 id,是必要的,Pinia 使用它来将 store 连接到 devtools。 将返回的函数命名为 use... 是跨可组合项的约定,以使其符合你的使用习惯。
export const useStore = defineStore('useStore', {
    // other options...
    // state 为 store 的核心,表示当前模块的状态
    // 为了完整类型推理,推荐使用箭头函数
    state: () => {
        return {
            num: 100
        }
    },
    getters: {
        doubleNum: (state) => state.num * 2,
      },
    // actions 可以定义同步和异步的方法
    actions: {
        add(){
            this.num++
        },
        addMore(num){
            this.num+=num
        }
    }
})

.vue 文件引入使用

<template>
  <div>
   <div>
    {{ store.num }}  {{ store.doubleNum }}
   </div> 
   <div>
    <el-button @click="add" size="mini" style="background-color: aquamarine;"> +1 </el-button>
    <el-button @click="addMore(3)" size="mini" style="background-color: aquamarine;"> +3 </el-button>
    <el-button @click="storeReset" size="mini"  style="margin-left:20px;background-color: aquamarine;"> 重置 </el-button>
   </div>
   <div>
    
   </div>
  </div>
</template>

<script setup lang='ts'>
import {useStore} from '@/store/index.js';

const store=useStore()
onMounted(() => {
    
})
const add=()=>{
    store.add()
}
// 传参
const addMore= (num) =>{
    store.addMore(num)
}
const storeReset=()=>{
    //您可以通过调用 store 上的 $reset() 方法将状态 重置 到其初始值
    store.$reset()
}

</script>

使用拓展库

vue 图片预览

v-viewer

Vue图片预览插件v-viewer - 掘金 (juejin.cn)open in new window

lodash使用

import _ from 'lodash'

// 防抖
 add: _.debounce(function () {
        // 请求接口
        console.log('123')
        }, 800),

树形结构插件 vue2-org-tree(组织结构图)

Vue 树形结构插件 vue2-org-tree - 掘金 (juejin.cn)open in new window

详解树状结构图 vue-org-tree - 知乎 (zhihu.com)open in new window

Vue3 中引入wangeditor富文本编辑器

快速开始 | wangEditoropen in new window wangeditor有vue2,vue3,react的版本 用于 Vue React | wangEditoropen in new window

在vue2中封装一个wangeditor组件_,也可以查看 ruoyi 里面的封装open in new window

基本使用:

npm i wangeditor -S

在需要用到的页面引入

//这里名字无所谓, 就是待会new的时候要用这个名字new
import EWangEditor from "wangeditor";

在页面内使用

<template>
  <div>
    <!-- id要为new wangeditor对象时候使用的id,style设高度可以不设  -->
    <div id="editor" name="editor" ref="editor" style="height:500px"></div>
  </div>
</template>

<script setup lang="ts">
import { ref ,reactive} from '@vue/reactivity'
import EWangEditor from "wangeditor";
import { onMounted } from '@vue/runtime-core';

let data=reactive({})

onMounted(()=>{
  console.log("onMounted",a,b,c)
  //这里的"#editor"对应要渲染为编辑器的html元素的id, 就像以前的querySelector()
  let editor=new EWangEditor("#editor")
  editor.config.uploadImgShowBase64 = true;
  // 你在wangeditor内写入的字符会被wangeditor自动转为HTML, 我们设定的更新频率, 即它每隔多久将你的文字提取并转换为HTML一次.
  /* 配置检测字符变化的最短时间间隔,默认为 200ms */
  editor.config.onchangeTimeout=400
  //如果检测到错误的话就打印.
  editor.config.customAlert=(err)=>{
    console.log(err)
  }
  editor.customConfig=editor.customConfig ? editor.customConfig : editor.config
  //设置customConfig属性
  //设置customConfig对编辑器内文字的变化的处理方法
  editor.customConfig.onchange = (html) => {  //参数html即已经转化HTML格式的文本
  data.editorContent = html;
  //在data中提前声明editorContent来存储
  console.log(html);  //实时输出更新的html格式
  };

   //以下为新增
  const menuItem = [  //工具栏里有哪些工具
    "head",
    "bold",
    "fontSize",
    "fontName",
    "italic",
    "underline",
    "indent",
    "lineHeight",
    "foreColor",
    "backColor",
    "link",
    "list",
    "justify",
    "image",
    "video",
  ];

editor.config.menus = [...menuItem]; /* 应用设置好的工具栏 */
     
editor.config.colors = ["#000000", "#eeece0", "#1c487f", "#4d80bf","#fff0f0","#20a8ff"];  /* 文字颜色、背景色可以选择哪些颜色? */
      
editor.config.fontNames = [ /* 字体工具提供哪些字体? */ 
  "黑体",
  "仿宋",
  "楷体",
  "标楷体",
  "华文仿宋",
  "华文楷体",
  "宋体",
  "微软雅黑",
];

editor.config.height = 500;  //你可能发现这个编辑器是没法用样式调高度的?
//新增至此
  editor.create()
})
</script>



<style scoped>

</style>

vue中引入二维码( vue-qr )

// npm
npm install vue-qr --save
//  页面引入组件
// vue2.x 
import VueQr from 'vue-qr'
// vue3.x 
import vueQr from 'vue-qr/src/packages/vue-qr.vue'

<vue-qr  
         id="vueQrs"   
         :text="textValue"
         :logoSrc="logoPath"
         :logoScale="40" 
         :size="190" 
         :margin="10"
         @click="downloadQR"
     />  
text二维码要展示的内容
logoSrc二维码中间的小logo
logoScale小logo的大小(别搞太大,超过容错率识别不出来的)
size整个二维码所占空间的大小,(宽高相等,包含margin) 可能需要你自己用css设置一下图片宽高100%
margin二维码的外边距(默认 20px)
// 事件处理函数(下载二维码)
	const downloadQR=()=> {
            const a = document.createElement('a')
            // 下载的文件名
            a.download = '二维码'
            // url
            let loadElement=document.getElementById("vueQrs")
            a.href = loadElement.src
            // 触发点击
            a.click()
        }

vue3中 setup语法糖使用组件级的路由导航守卫,定义组件name (unplugin-vue-define-options)

unplugin-vue-macros(github.com) open in new window

<script setup > 中可使用 defineOptions 宏,以便在 <script setup > 中使用 Options API。 尤其是能够在一个函数中设置 name、props、emit 和 render 属性。 有了这个宏,你就可以在 <script setup> 使用 Options API;

 npm i unplugin-vue-define-options -D 

vite.config.ts

import DefineOptions from 'unplugin-vue-define-options/vite'

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [vue(), DefineOptions()],
    resolve: {
        // ↓路径别名,主要是这部分
        alias: {
            "@": resolve(__dirname, "./src")
        }
    }
})

在需要的组件页面加入代码:

<script setup lang="ts">

defineOptions({
  name: '***',     // name可用于配置组件 名称
  beforeRouteEnter(to, from, next) {
    next((vm) => {
      console.log(vm,to,from,'123456')
    })
  }
})

</script>

视频播放器

几款优秀开源的HTML5 视频播放器 - 体验盒子 - 不再关注网络安全 (uedbox.com)open in new window

Vue3VideoPlay(vue3可用,效果好)open in new window

Vue3-video-play视频播放器插件_视频播放插件-CSDN博客open in new window

npm i vue3-video-play --save

main.js

import vue3videoPlay from 'vue3-video-play' // 引入组件
import 'vue3-video-play/dist/style.css' // 引入css
app.use(vue3videoPlay)

报错:Failed to resolve entry for package "vue3-video-play". The package may have incorrect main/module/exports specified in its package.json.

解决:VUE3+VITE:Failed to resolve entry for package “vue3-video-play“._failed to resolve entry for package "vue3-video-pl-CSDN博客open in new window

步骤:找到node_modules\vue3-video-play\package.json 将package.json中 "module": "./dist/index.es.js"改为"module": "./dist/index.mjs"

然后重启项目,可以看到没有报错了

使用(具体参数看官网展示):

<template>
<vue3VideoPlay
      v-bind="options"
      :poster="options.poster"
      @play="onPlay"
      @pause="onPause"
      @timeupdate="onTimeupdate"
      @canplay="onCanplay"
    />
</template>   

<script setup> 

const options = reactive({
  width: "800px", //播放器宽度
  height: "450px", //播放器高度
  color: "#409eff", //主题色
  title: "测试", //视频名称
  src: "http://1.94.16.149:9000/test/movie.mp4",     //视频源
  poster: "https://cdn.jsdelivr.net/gh/xdlumia/files/video-play/ironMan.jpg", // 视频封面
  muted: false, //静音
  speed: true, // 开启进度条拖动
  webFullScreen: false,
  speedRate: ["0.75", "1.0", "1.25", "1.5", "2.0"], //播放倍速
  autoPlay: false, //自动播放
  loop: false, //循环播放
  mirror: false, //镜像画面
  ligthOff: false, //关灯模式
  volume: 0.3, //默认音量大小
  control: true, //是否显示控制
  controlBtns: [
    "audioTrack",
    "quality",
    "speedRate",
    "volume",
    "setting",
    "pip",
    "pageFullScreen",
    "fullScreen",
  ], //显示所有按钮,
});

/**
 * 支持所有原生video事件
 */
const onPlay = (ev) => {
  console.log("播放");
};
const onPause = (ev) => {
  console.log(ev, "暂停");
};
const onTimeupdate = (ev) => {
  console.log(ev, "时间更新");
};
const onCanplay = (ev) => {
  console.log(ev, "可以播放");
};

</script>

Aliplayer阿里播放器

阿里云Aliplayer播放器 (alicdn.com)open in new window

西瓜播放器(可音乐播放)

西瓜播放器 | 快速上手 (bytedance.com)open in new window vue3 集成西瓜视频播放器xgplayer_vue3 xiguaplay-CSDN博客open in new window

只需三步:安装、DOM占位、实例化即可完成播放器的使用。

Mui Player(多端可用,效果好)

MuiPlayeropen in new window vue + mui-player视频播放器_vue+muiplayer-CSDN博客open in new window

npm i mui-player --save
npm i mui-player-desktop-plugin  pc端拓展(插件)

页面引入:

import 'mui-player/dist/mui-player.min.css'
import MuiPlayer from 'mui-player'

// 使用模块管理器引入插件(pc端拓展)
import MuiPlayerDesktopPlugin from 'mui-player-desktop-plugin'

vue3使用

<div id="mui-player" ></div>

<script setup>
    const customSetting=[{
		functions:'清晰度',
		model:'select',
		show:true,
		zIndex:0,
		childConfig:[
			{functions:'蓝光1080P'},
			{functions:'超清'},
			{functions:'高清',selected:true},
			{functions:'标清'},
		],
		onToggle:function(data,selected,back) {
            // Action
        }
	}]
    
onMounted(() => {
  // 初始化 MuiPlayer 插件,MuiPlayer 方法传递一个对象,该对象包括所有插件的配置
  var mp = new MuiPlayer({
    container: "#mui-player",  // 播放器容器
    title: "标题",             // 标题
    src: "http://1.94.16.149:9000/test/movie.mp4", //  视频播放的资源地址
    width: 300,        // 初始化播放器宽度
    height: 300,       // 初始化播放器高度
      
     //  plugins 插件拓展(根据需要进行配置,具体配置查看官网)
    plugins:[
        new MuiPlayerDesktopPlugin({
            customSetting:customSetting, // 设置组配置
            // contextmenu, // 右键菜单组配置
            // thumbnails,  // 缩略图配置
        })
    ]
      
  });

  // 监听播放器已创建完成
  mp.on('ready',function(event) {
      console.log(event);
  });
});
</script >

vue 搭建组件库V2(简易)

b站参考学习视频地址open in new window 参考文章 vue组件库的基本开发步骤 open in new window

一开始跟着视频编写代码,但是到了webpack打包,webpack.component.js文件和依赖引入时出现了挺多的报错(尝试了几个解决方法,最终没能解决,在打包完css后直接发布了),在打包发布到了npm网站后引入组件,打开页面控制台报红,果然失败了。

后来参考文章,用脚手架自带的打包,成功部署到npm网站上了。使用正常。

vue组件库制作:npm初始化项目+编写组件+webpack打包js+gulp打包css+npm发布

基于element-ui构建自定义组件库,发布到npm及安装使用,以及生成组件库说明文档 - 知乎 (zhihu.com)open in new window

搭建vue项目和运行环境(示例)vue2版本

1、首先,创建一个普通的vue2项目

2、打开项目,将src目录下的components文件夹放到项目根目录下,改名为packages,将src文件名改为examples。此时运行项目会报错。原因是vue寻找文件默认是从src目录下寻找,我们改了src名为examples,所以需要对配置进行修改。

修改方法:创建vue.config,js文件,代码如下

module.exports = {
    pages: {
        index: {
            entry: 'examples/main.js',
            template: 'examples/index.html',
            filename: 'index.html'
        }
    }
    
     // 扩展 webpack 配置,使 examples 加入编译
    chainWebpack: config => {
        config.module
            .rule('js')
            .include
            .add('/examples')
            .end()
            .use('babel')
            .loader('babel-loader')
    }
}

修改配置文件后,重启项目,成功运行。

3、css文件夹下编写css样式,(如用到scss则需要引入依赖,注意版本问题)

创建vue组件文件 (packages/lib/demo/src/main.vue)

创建index.js(packages/lib/demo/index.js) ,这个js文件用于编写注册组件的代码,最终引入到main.js

如创建Demo组件,index.js代码如下:

import Demo from '../demo/src/main.vue'

Demo.install = function(Vue) {
    Vue.component(Demo.name, Demo)
}

export default Demo

最终在main.js引入css文件和组件注册的js代码,(此处的demo.scss属于全局引入了,在组件内可以直接使用的)

import Vue from 'vue'
import App from './App.vue'
import '../packages/css/demo.scss'
import Demo from "../packages/lib/demo/index"

Vue.config.productionTip = false
Vue.use(Demo)

new Vue({
    render: h => h(App),
}).$mount('#app')

当配置完成后,项目可正常运行。

组件制作

1、组件设计(通用性)

2、编写html和css样式

3、编写组件逻辑代码

4、测试组件

(card组件示例代码如下)

<template>
  <div class="f-card">
     <div class="f-card-img" :style="width?{width:width+'px'}:{}">
        <img :src="imgSrc" alt="img" :style="imgHeight?{height:imgHeight+'px'}:{}"/>
     </div>
     <div v-if="summary" class="f-card-summary">
        {{summary}}
     </div>
      <div v-else class="f-card-summary">
        <slot></slot>
     </div>
     <!-- <div class="f-card-footer">
        footer
     </div> -->
     <slot name="footer"></slot>
  </div>
</template>

<script>
export default {
    name:'Card',
    props:{
        width:{       //卡片宽度
            type:Number,
            default:0
        },
        imgSrc:{       //图片资源地址
            type:String,
            default:''
        },
        imgHeight:{    //图片高度
            type:Number,
            default:0
        },
        summary:{    //概述
            type:String,
            default:''
        }
    },
  data() {
    return {

    }
  },
  methods: {

  },
  created() {

  },
}
</script>
<style scoped>

</style>
.f-card {
    width: 270px;
    border-radius: 8px;
    background: white;
    overflow: hidden;
    padding-bottom: 8px;
    box-shadow: 10px 5px 5px #efefef;
    &-img {
        height: 152px;
        img {
            width: 100%;
            height: 100%
        }
    }
    &-summary {
        padding: 8px;
        font-size: 14px;
        text-align: left;
    }
    &-footer {}
}

测试的组件代码如下,运行功能正常

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <demo></demo>
    <Card 
      imgSrc="123456.png"
      summary="这是一个card组件"
      width='570'
      imgHeight="130"
    >
     123
      <template v-slot:footer>
        footer
      </template>
  </Card>
  </div>
</template>

打包

在写组件的文件夹 同级目录创建index.js文件,用于汇总导出所有组件,便于打包后引用

import Demo from './demo'
import Card from "./card"

const components = {
    Demo,
    Card
}

const install = function(Vue) {
    if (install.installed) return;
    Object.keys(components).forEach(key => {
        Vue.component(components[key].name, components[key])
    })
}

export default {
    install,
    Card,
    Demo
}

css也是同级创建index.css,引入其他的css文件,便于打包后引用

@import url('./demo.scss');
@import url('./card.scss');

创建打包命令行,并执行

webpack打包js为umd模块

"scripts": {        
        "serve": "vue-cli-service serve",
        "build": "vue-cli-service build",
        "lint": "vue-cli-service lint",
        "build:lib": "vue-cli-service build --target lib --name dfk --dest lib packages/lib/index.js",
        "build:css": "npx gulp sass"
    },
运行  npm run build:lib 打包js代码,注意文件路径报错

创建gulpfile.js文件,用于打包css文件,使用的scss(下载下方需要的4个依赖,注意自己的文件路径)

const gulp = require('gulp');
// const sass = require('gulp-sass');
const sass = require('gulp-sass')(require('sass'));
const minifyCSS = require('gulp-minify-css');

gulp.task('sass', async function() {
    return gulp.src('packages/**/*.scss')
        .pipe(sass())
        .pipe(minifyCSS())
        .pipe(gulp.dest('lib/css'))
})

运行 npm run build:css 打包css代码,注意文件路径报错

发布

图文结合简单易学的 npm 包的发布流程 (qq.com)open in new window

1、准备一个npm账号

2、npm login 登录npm账号

3、npm publish (推送代码,注意修改package.json的配置)

    "name": "fankoui3",
    "version": "0.1.0",
    "description": "npm测试",            //描述
    "main": "lib/dfk.umd.js",           //打包文件入口
    "keywords": [                       //关键字
        "f-ui",
        "vue",
        "ui"
    ],
    "author": "fanko",   //作者
    "files": [          //要打包的文件路径
        "lib",
        "packages"
    ],
    "scripts": {        
        "serve": "vue-cli-service serve",
        "build": "vue-cli-service build",
        "lint": "vue-cli-service lint",
        "build:lib": "vue-cli-service build --target lib --name dfk --dest lib packages/lib/index.js",
        "build:css": "npx gulp sass"
    },

发布成功后从npm网站账号内可看到

测试

创建一个vue项目

下载 fankoui3 依赖

main.js (引入依赖和css样式并使用)

import Vue from 'vue'
import App from './App.vue'

import "fankoui3/lib/css/css/index.css"
import fankoui3 from "fankoui3"
Vue.config.productionTip = false

Vue.use(fankoui3)
new Vue({
    render: h => h(App),
}).$mount('#app')

在vue文件中使用Card组件,如可显示正常,则组件库创建成功。

如何写一个属于自己的Vue3组件库

pnpm 基本详细使用(安装、卸载、使用) - 掘金 (juejin.cn)open in new window

pnpm 修改默认安装包的仓库位置 pnpm config set store-dir E:/pnpmPackage

使用pnpm的项目最好不要有中文路径,否则下载依赖可能有报错

如何写一个属于自己的Vue3组件库 (qq.com)open in new window

【前端工程化-组件库】从0-1构建Vue3组件库(打包发布) - 掘金 (juejin.cn)open in new window

前端工程化😼开源项目都在用的【Monorepo】🏋🏿 - 掘金 (juejin.cn)open in new window

如何写一个属于自己的vue3组件库 - 掘金 (juejin.cn)open in new window

参考,可用:全肝鸽鸽 的个人主页 - 文章 - 掘金 (juejin.cn)open in new window GitHub - yuanwen0327/learn-create-compoents-libopen in new window

一文读懂TS的(.d.ts)文件 - 掘金 (juejin.cn)open in new window

从0到1搭建自己的组件库,详解附源码 - 掘金 (juejin.cn)open in new window

Monorepo pnpm模式管理多个web项目(Vue3) - 掘金 (juejin.cn)open in new window

vue3实现 v-model (封装组件)

// 父组件     v-model 等于 :modelValue="inContent" @update:modelValue="inContent = $event"
<editorIt v-model="inContent" ></editorIt>
// 子组件
<template>
	<ChildComponent v-model="valueHtml" />
</template>

<script setup>
import {  onMounted ,defineProps, defineEmits,toRefs} from 'vue';

const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

    //  子组件内容 HTML
    const valueHtml = ref('<p>hello</p>')
    onMounted(() => {
      valueHtml.value=props.modelValue
    })

 emit('update:modelValue',1234567)
</script>

vue3.4版本简化了v-model的实现

通过 defineModel 来实现

官网查看:组件 v-model | Vue.js (vuejs.org)open in new window

vue3基本使用(用setup 语法糖)

https://cn.vuejs.org/guide/introduction.htmlopen in new window

快速使用Vue3最新的15个常用API - 掘金 (juejin.cn)open in new window

计算属性

import { ref,reactive, computed } from 'vue'

const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})

// 一个计算属性 ref,随后访问publishedBooksMessage的值要用 publishedBooksMessage.value
const publishedBooksMessage = computed(() => {
  return author.books.length 
})


// 计算属性默认是只读的。当你尝试修改一个计算属性时,你会收到一个运行时警告。只在某些特殊场景中你可能才需要用  到“可写”的属性,你可以通过同时提供 getter 和 setter 来创建
const firstName = ref('John')
const lastName = ref('Doe')

const fullName = computed({
  // getter
  get() {
    return firstName.value + ' ' + lastName.value
  },
  // setter
  set(newValue) {
    // 注意:我们这里使用的是解构赋值语法
    [firstName.value, lastName.value] = newValue.split(' ')
  }
})

watch

const x = ref(0)
const y = ref(0)

// 单个 ref
watch(x, (newX) => {
  console.log(`x is ${newX}`)
})

// getter 函数
watch(
  () => x.value + y.value,
  (sum) => {
    console.log(`sum of x + y is: ${sum}`)
  }
)

// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
  console.log(`x is ${newX} and y is ${newY}`)
})


//不能直接侦听响应式对象的属性值,这里需要用一个返回该属性的 getter 函数
// 提供一个 getter 函数
watch(
  () => obj.count,
  (count) => {
    console.log(`count is: ${count}`)
  },
  //  { deep: true }  深度监听
  //  { immediate: true }   //立即执行
) 

watchEffect()

侦听器的回调使用与源完全相同的响应式状态是很常见的。例如下面的代码,在每当 todoId 的引用发生变化时使用侦听器来加载一个远程资源

const todoId = ref(1)
const data = ref(null)

watch(todoId, async () => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
  )
  data.value = await response.json()
}, { immediate: true })

特别是注意侦听器是如何两次使用 todoId 的,一次是作为源,另一次是在回调中。

watchEffect 函数open in new window 来简化上面的代码。watchEffect() 允许我们自动跟踪回调的响应式依赖

watchEffect(async () => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
  )
  data.value = await response.json()
})

停止侦听器

// 它会自动停止
watchEffect(() => {})


// 手动停止
const unwatch = watchEffect(() => {})
// ...当该侦听器不再需要时
unwatch()

生命周期使用

<script setup>
import { onMounted } from 'vue'

onMounted(() => {
  console.log(`the component is now mounted.`)
})
</script>

props


<script setup>
    //  defineProps里面声名props会有哪些值,例如父组件传给子组件一个'num'值
    const props = defineProps(['num'])
    // 在script里使用props的值要带'props.'',在template中直接 {{num}} 就可以获取
    console.log(props.num)
</script>

// 对象式声明props,只是写法不同
defineProps({
  title: String,
  likes: Number
})
const props = defineProps(["num"]);
let num = ref(props.num);

然后操作 num  即可

【Vue3】组件props的使用_vue3 props使用-CSDN博客open in new window

emit

// 父组件 app.vue
<template>
	<son @sonClick="getSon" @sonClick2="getSon2(num)"></son>
</template>
<script setup>
    const getSon=()=>{}
    const getSon2=(num)=>{}
</script>

//子组件  son.vue

 //  defineEmits里面声名 自定义事件  
const emit = defineEmits(['sonClick','sonClick2'])
// emit触发事件,可携带参数
emit('sonClick')
emit('sonClick2','123')

$forceUpdate

import { getCurrentInstance } from "vue";
const { proxy } = getCurrentInstance();
 proxy!.$forceUpdate();

父组件通过ref调用子组件

父组件  app.vue
<chuanzhi ref="myson" ></chuanzhi>
<button @click="mySonRef">{{myson}}</button>

<script setup>
//  定义一个和子组件同名的ref变量
let myson=ref()
const mySonRef=()=>{
  // 通过myson.value获取子组件的属性,但只有子组件暴露出来的才能访问
  console.log('myson',myson.value)
  myson.value.ceshi()
}
</script>

子组件  chuanzhi.vue
<script setup>
let sons=ref('123')
const ceshi=()=>{
    console.log('123')
}
// 子组件通过defineExpose向外暴露属性和方法,父组件才能访问
defineExpose({
  sons,ceshi
})
</script>
</script>


nextTick

若要等待一个状态改变后的 DOM 更新完成 ,可以使用nextTick

import { nextTick } from 'vue'

nextTick(() => {
    // 访问更新后的 DOM
  })

依赖注入(provide,inject )

依赖提供方:provide

<script setup>
import { provide } from 'vue'

provide(/* 注入名 */ 'message', /* 值 */ 'hello!')

// 第二个参数是提供的值,值可以是任意类型,包括响应式的状态,比如一个 ref
const count = ref('0')
provide('message', count.value)
</script>

注入:inject 接收依赖

<script setup>
import { inject } from 'vue'

const message = inject('message')

// 如果没有祖先组件提供 "message", `value` 会是 "这是默认值"
const value = inject('message', '这是默认值')

</script>

vue3 hooks

鼠标跟踪器示例

// mouse.js(hook 完整示例)
import { ref, onMounted, onUnmounted } from 'vue'

// 按照惯例,组合式函数名以“use”开头
export function useMouse() {
  // 被组合式函数封装和管理的状态
  const x = ref(0)
  const y = ref(0)

  // 组合式函数可以随时更改其状态。
  function update(event) {
    x.value = event.pageX
    y.value = event.pageY
  }

  // 一个组合式函数也可以挂靠在所属组件的生命周期上
  // 来启动和卸载副作用
  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))

  // 通过返回值暴露所管理的状态
  return { x, y }
}

(再拆分)我们可以将添加和清除 DOM 事件监听器的逻辑也封装进一个组合式函数中:

// event.js
import { onMounted, onUnmounted } from 'vue'

export function useEventListener(target, event, callback) {
  // 如果你想的话,
  // 也可以用字符串形式的 CSS 选择器来寻找目标 DOM 元素
  onMounted(() => target.addEventListener(event, callback))
  onUnmounted(() => target.removeEventListener(event, callback))
}

有了它,之前的 useMouse() 组合式函数可以被简化为:

// mouse.js
import { ref } from 'vue'
import { useEventListener } from './event'

export function useMouse() {
  const x = ref(0)
  const y = ref(0)

  useEventListener(window, 'mousemove', (event) => {
    x.value = event.pageX
    y.value = event.pageY
  })

  return { x, y }
}

组件中使用

<script setup>
import { useMouse } from './mouse.js'

const { x, y } = useMouse()
</script>

<template>Mouse position is at: {{ x }}, {{ y }}</template>

异步状态示例

在做异步数据请求时,我们常常需要处理不同的状态:加载中、加载成功和加载失败。

接收响应式状态

useFetch() 接收一个静态 URL 字符串作为输入——因此它只会执行一次 fetch 并且就此结束。如果我们想要在 URL 改变时重新 fetch 呢?为了实现这一点,我们需要将响应式状态传入组合式函数,并让它基于传入的状态来创建执行操作的侦听器。

举例来说,useFetch() 应该能够接收一个 ref:或者接收一个 getter 函数:

我们可以用 watchEffect()open in new windowtoValue()open in new window API 来重构我们现有的实现:

toValue() 是一个在 3.3 版本中新增的 API。它的设计目的是将 ref 或 getter 规范化为值。如果参数是 ref,它会返回 ref 的值;如果参数是函数,它会调用函数并返回其返回值。否则,它会原样返回参数。它的工作方式类似于 unref()open in new window,但对函数有特殊处理。

注意 toValue(url) 是在 watchEffect 回调函数的内部调用的。这确保了在 toValue() 规范化期间访问的任何响应式依赖项都会被侦听器跟踪。

这个版本的 useFetch() 现在能接收静态 URL 字符串、ref 和 getter,使其更加灵活。watch effect 会立即运行,并且会跟踪 toValue(url) 期间访问的任何依赖项。如果没有跟踪到依赖项(例如 url 已经是字符串),则 effect 只会运行一次;否则,它将在跟踪到的任何依赖项更改时重新运行。

输入参数

即便不依赖于 ref 或 getter 的响应性,组合式函数也可以接收它们作为参数。如果你正在编写一个可能被其他开发者使用的组合式函数,最好处理一下输入参数是 ref 或 getter 而非原始值的情况。可以利用 toValue()open in new window 工具函数来实现:

import { toValue } from 'vue'

function useFeature(maybeRefOrGetter) {
  // 如果 maybeRefOrGetter 是一个 ref 或 getter,
  // 将返回它的规范化值。
  // 否则原样返回。
  const value = toValue(maybeRefOrGetter)
}

useFetch()实现代码示例:

示例地址open in new window

import { ref, watchEffect, toValue } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)
  
  watchEffect(async () => {
    // reset state before fetching..
    data.value = null
    error.value = null
    
    // resolve the url value synchronously so it's tracked as a
    // dependency by watchEffect()
    const urlValue = toValue(url)
    
    try {
      // artificial delay / random error
  	  await timeout()
  	  // unref() will return the ref value if it's a ref
	    // otherwise the value will be returned as-is
    	const res = await fetch(urlValue)
	    data.value = await res.json()
    } catch (e) {
      error.value = e
    }
  })

  return { data, error }
}

// artificial delay
function timeout() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (Math.random() > 0.3) {
        resolve()
      } else {
        reject(new Error('Random Error'))
      }
    }, 300)
  })
}

使用

<script setup>
import { ref, computed } from 'vue'
import { useFetch } from './useFetch.js'

const baseUrl = 'https://jsonplaceholder.typicode.com/todos/'
const id = ref('1')
const url = computed(() => baseUrl + id.value)

const { data, error, retry } = useFetch(url)
</script>

<template>
  Load post id:
  <button v-for="i in 5" @click="id = i">{{ i }}</button>

	<div v-if="error">
    <p>Oops! Error encountered: {{ error.message }}</p>
    <button @click="retry">Retry</button>
  </div>
  <div v-else-if="data">Data loaded: <pre>{{ data }}</pre></div>
  <div v-else>Loading...</div>
</template>

getCurrentInstance

在Vue3中,getCurrentInstance() 可以用来获取当前组件实例

Vue3组合式API:getCurrentInstance_vue3 getcurrentinstance-CSDN博客open in new window

Vite vue 配置

vite打包视图分析(rollup-plugin-visualizer)

vue3+vite打包视图分析(rollup-plugin-visualizer)+gzip - 掘金 (juejin.cn)open in new window

分析项目中的js文件大小和引用情况,基于展示的依赖项 js 大小对前端项目做优化处理

npm install --save-dev rollup-plugin-visualizer

vite.config.js 增加代码

// 分析项目中的js文件大小和引用情况
import { visualizer } from "rollup-plugin-visualizer";


plugins: [vue(),vueJsx(), visualizer({
    gzipSize: true,
    brotliSize: true,
    emitFile: false,
    filename: "test.html", //分析图生成的文件名
    open:true //如果存在本地服务端口,将在 npm run build 打包后自动展示
  }),],

vite 项目优化 配置 CDN 服务

配置cdn加速,减小打包体积

【vite 4.3 + vue 3.2 项目优化实战】配置 CDN 服务 - 掘金 (juejin.cn)open in new window

Vue和React对比学习之路由角色权限

Vue和React对比学习之路由角色权限(页面、按钮权限控制) - 掘金 (juejin.cn)open in new window

如何在Vue.js中创建模态框(弹出框)

如何在Vue.js中创建模态框(弹出框) (qq.com)open in new window

简易组件

移动端点击侧边展示导航的组件

用的ant-design-vue的 a-drawer

<template>
  <div class="the-dot">
    <div @click="showBannel" class="go-back"><a-icon type="menu-fold" /></div>
    <a-drawer
      class="drawer"
      title="导航菜单"
      :placement="placement"
      :closable="false"
      :visible="visible"
      @close="onClose"
    >
      <div class="company-tab">
        <div v-for="(item,index) in pathList" :key="index" class="tab" :class="path==item.path?'active':''" @click="goThePath(item)"><a-icon type="appstore" class="icon"/>{{ item.name }}</div>
      </div>
    </a-drawer>
  </div>
</template>

  <script>
  export default {
    data () {
      return {
          placement: 'left',
          visible: false,
          style: 'border-color: #167cf3;color: #167cf3;',
          path: '',
          pathList: [
            {
                name: '1',
                path: '/index'
            },
            {
                name: '2',
                path: '/job/tallSchool',
                type: 11
            }, {
                name: '3',
                path: '/needCenter',
                type: 8
            }
          ]
      }
    },
    methods: {
        goThePath (item) {
            this.path = item.path
            this.$router.push({
                path: item.path,
                query: { type: item.type }
            })
            this.showBannel()
        },
      goBack () {
          this.$router.go(-1)
      },
      showBannel () {
          this.visible = !this.visible
      },
      onClose () {
          this.visible = false
      }
    },
    created () {

    },
    mounted () {
      this.path = this.$route.path
    }
  }
  </script>
  <style scoped >
  @media screen and (max-width: 600px) {
    .the-dot{
        display: block;
    }
  }
  @media screen and (min-width: 600px) {
    .the-dot{
        display: none;
    }
}
   .go-back{
      position: fixed;
      background-color: #FFF;
      width: 40px;
      height: 40px;
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 20px;
      box-shadow: 0 0 6px rgb(0 0 0 / 12%);
      cursor: pointer;
      z-index: 1000000000;
      top:10px;
      right:10px
   }

  .company-tab {
          margin: 0 auto;
          background:#001529;
          padding-left: 0;

        }
  .tab{
      display: flex;
      align-items: center;
      padding-left:30px;
      margin:10px 0;
      color:white;
      background: #001529;
      width:100%;
      height:40px;
      margin-bottom: 2px;
      font-size: 14px;
      cursor: pointer;
  }
  .tab.active {
      background: #167cf3;
      /* background: rgba(0, 0, 0, 0.04); */
  }
  .icon{
      margin-right:10px
  }
  >>>.ant-drawer-body {
      padding:0
  }
  >>>.ant-drawer-wrapper-body {
      background:#001529;
  }
  >>>.ant-drawer-header {
     background:#001529;
     color:white;
     font-weight: 600;
  }
  >>>.ant-drawer-title {
      color:white
  }
  </style>

门户头部导航组件

<template>
    <div >
      <div class="main">
        <div class="nav-list">
          <div @click="go(item)" :class="activePath==item.path?'nav-item-active nav-item':'nav-item'" v-for="(item,index) in navList" :key="index">
            <div>{{ item.name }}</div>

            <div class="nav-item-in-in" v-if="item.children "  >
            <div @click="goChild(item,itm)"  v-for="(itm,idx) in item.children" :key="idx" style="width:100%;height:50px;" class="display-center " >
              <div>{{ itm.name }}</div>
            </div>

          </div>
          </div>

        </div>

      </div>
    </div>
  </template>

<script>
export default {
  data () {
    return {
      navList: [
        {
          name: '首页',
          path: '/index',
          isFrame: false
        },
        {
          name: '菜单2',
          path: '/about',
          isFrame: false
        },
        {
          name: '菜单3',
          path: '/index3',
          // 有children的就是目录
          children: [
            {
              name: '内1',
              path: '/index31',
              isFrame: false
            },
            {
              name: '内2',
              path: '/index32',
              isFrame: true, // 是否外链
              outUrl: 'http://www.baidu.com' // 外链地址
            }, {
              name: '内3',
              path: '/index33',
              isFrame: false
            }
          ]

        },
        {
          name: '菜单4',
          path: '/index4',
          isFrame: true,
          outUrl: 'http://www.baidu.com'
        }
      ],
      activePath: '' // 选中的父节点

    }
  },
  components: {

  },
  created () {

  },
  mounted () {
    this.activePath = this.$route.path
  },
  methods: {
    go (item) {
      if (!item.children) {
        if (item.isFrame) {
          window.open(item.outUrl, '_blank')
        } else {
          this.$router.push(item.path)
          this.activePath = item.path
          this.$forceUpdate()
        }
      }
      if (item.children) {
        this.$forceUpdate()
      }
    },
    goChild (item, itm) {
      if (itm.isFrame) {
        window.open(itm.outUrl, '_blank')
      } else {
        this.$router.push(itm.path)
        this.$forceUpdate()
      }
    }

  }
}
</script>

  <style  scoped>
  .main{
    width:100%;
    height:60px;
    /* background: red; */
    display: flex;
    justify-content: center;
  }
  .nav-list{
    display: flex;
    height: 100%;
  }
  .nav-item{
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    padding:0 20px;
    margin:0 10px;
    cursor: pointer;
    position: relative;
  }
  .nav-item:hover{
    display: flex;
    align-items: center;
    justify-content: center;
    padding:0 20px;
    color:#25AFF3;
  }
  .nav-item-active{
    display: flex;
    align-items: center;
    justify-content: center;
    padding:0 20px;
    border-bottom:2px solid #25AFF3;
    color:#25AFF3;
    font-size:20px;
    font-weight: bold;
  }

  .nav-item-in-in{
  display: none;
  color: black;
  position: absolute;top:100%;left:0;background: #E5E5E5;width: 100%;
  font-size:16px;
}

.nav-item:hover  .nav-item-in-in{

      display: block;

  }
  .child-path{

  }
  .active-child-path{
    color:#25AFF3;
    font-size:20px;
    font-weight: bold;

  }
  .display-center{
    display: flex;
    justify-content: center;
    align-items: center;
  }
  </style>

飘窗(vue)

<template>
  <div style="position: relative; z-index: 102">
    <div
      v-show="show"
      @mouseover="clear()"
      @mouseout="set()"
      ref="promptBox"
      class="promptBox"
    >
      <div class="prompt" @click="goDetail()">
        <div class="guanBi" @click.stop="show = false">X</div>
      </div>
    </div>
  </div>
</template>

<script>
// import {getCnList} from '@/api/request/api';
export default {
  data() {
    return {
      myl: 0,
      w: 360,
      h: 160,
      step: 2,
      bw: 0,
      bh: 0,
      myt: 50,
      div: "",
      show: false,
      directionY: "down",
      directionX: "right",
      myVar: 0,
      activeItem: {},
    };
  },
  created() {
    this.getCnInfo();
  },
  mounted() {},
  methods: {
    goDetail() {
      window.open("http://photo.china.com.cn/node_9006497.html", "_blank");
    },
    go() {
      //判断移动方向
      if (this.directionX == "right") {
        //判断向右移动时到最右侧
        if (this.myl + this.w + this.step > this.bw) {
          this.directionX = "left";
        }
      } else {
        if (this.myl - this.step < 0) {
          this.directionX = "right";
        }
      }
      if (this.directionY == "down") {
        //判断向下移动时到最下侧
        if (this.myt + this.h + this.step > this.bh) {
          this.directionY = "up";
        }
      } else {
        if (this.myt - this.step < 0) {
          this.directionY = "down";
        }
      }
      //移动
      if (this.directionX == "right") {
        this.myl += this.step;
      } else {
        this.myl -= this.step;
      }
      if (this.directionY == "down") {
        this.myt += this.step;
      } else {
        this.myt -= this.step;
      }
      this.div.style.left = this.myl + "px";
      this.div.style.top = this.myt + "px";
    },
    clear() {
      clearInterval(this.myVar);
    },
    set() {
      this.myVar = setInterval(this.go, 80);
    },
    getCnInfo() {
      // getCnList('piaochuang_index',1,1).then(res => {
      // if(res.message.length) {
      // this.activeItem = res.message[0];
      this.$nextTick(() => {
        this.show = true;
        this.div = this.$refs.promptBox; //获取对象
        this.bw = window.innerWidth;
        this.bh = window.innerHeight; //获取浏览器可见窗口的宽高
        this.myVar = setInterval(this.go, 80); //定时器
      });
      //   }
      // })
    },
  },
};
</script>

<style lang="scss" scoped>
.promptBox {
  position: fixed;
  z-index: 100000000000999;
  width: 360px;
  height: 160px;
  padding: 5px;
  cursor: pointer;
  transition: all linear 0.08s;
}

.guanBi {
  display: flex;
  justify-content: flex-end;
  width: 20px;
  padding: 5px;
  margin-right: 0;
  margin-left: auto;
  color: #999;
  border: 1px solid transparent;

  &:hover {
    border-color: #999;
  }
}

.prompt {
  height: 100%;
  background-image: url("@/assets/images/index/chunfeng.jpg");
  background-repeat: no-repeat;
  background-size: 100% 100%;
}
</style>

vue3使用jsx

如何在 vue3 中使用 jsx/tsx? - 掘金 (juejin.cn)open in new window

在 vue3 中优雅的使用 jsx/tsx - 掘金 (juejin.cn)open in new window

在Vue3中利用JSX+函数式组件做到更好的代码复用 - 掘金 (juejin.cn)open in new window

JSX 设置样式 - 知乎 (zhihu.com)open in new window

万字长文详解 Vue JSX,带你全面拥抱 JSX 特性!-讲述了jsx的优点 - 掘金 (juejin.cn)open in new window

一起玩转Vue中的JSX:让你一次性掌握它的特性! 微信公众平台 (qq.com)open in new window

想在项目中使用 JSX,我们需要安装一个插件@vitejs/plugin-vue-jsx,这个插件可以让我们在项目中使用 JSX/TSX

npm i @vitejs/plugin-vue-jsx -D

vite.config.js(引入jsc)

import vueJsx from "@vitejs/plugin-vue-jsx";
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue(), vueJsx()],
});

setup语法糖内基本使用

<template>
  <div>
    jsx
    <jsxtests></jsxtests>
  </div>
</template>

<script setup  lang="tsx">
// 引入外部样式文件,vue里面的style样式使用是不生效的
import styles from './jsx.module.css'
const msg=' i am msg'
const classNams={
    color:'blue'
}
const list=['1','2','3']
const jsxtests=()=>{
    return (
    <div>
        <div style={{color:'red',"font-weight":'bold'}}>{msg}</div>
        <div style={classNams}>12332432</div>
        {/* 这里的class使用的是外部样式文件里定义的类名,多个类名可以用数组 */}
        <div class={styles.asc}>12334</div>
       //  twillwind css 样式 可以直接使用
        <div class="bg-green-600 hover:bg-red-300 cursor-pointer ">文字样式3</div>
        // twillwind css 样式  (两个class,会被样式覆盖)
        <div onClick={tiao} className="bg-green-600"  class={styles.asc}>jsx2跳转</div>
        <div>
            {
               list.map((item,index)=>{
                    return <div key="index">{item}</div>
                })  
            }
           
        </div>
    </div>
    )
}
</script>

jsx.module.css

(模块css,隔绝样式的污染,相当于style标签里面的scoped)

.asc{
    color:aqua
}

CSS Modules 引入局部样式,相当于 vue 组件中 `` 标签的 scoped 属性。

import styles from './jsx.module.css'
 <div class={styles.asc}>12334</div>

Vue Vineopen in new window

Main Navigation指引open in new window

Vue3.x 中使用 template + tsx 也许是最佳组合! (qq.com)open in new window

vue使用Tailwindcss

官方文档: Width - TailwindCSS中文文档 | TailwindCSS中文网open in new window

一次就能看懂的Tailwind CSS介绍 - 掘金 (juejin.cn)open in new window

当有不知道的类可以直接到官网文档查看

在vite-vue3中快速使用Tailwindcss - 掘金 (juejin.cn)open in new window

Vue中使用 Tailwind CSS - 掘金 (juejin.cn)open in new window

安装依赖

npm i -D tailwindcss postcss autoprefixer

创建配置文件

npx tailwindcss init

引入样式

// main.js
import "tailwindcss/tailwind.css"

配置tailwind.config.js文件

content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}']

vite.config.js增加配置(若不生效,则使用下一个方式)

css: {
    postcss: {
        plugins: [require('tailwindcss'), require('autoprefixer')],
    },
},

若vite.config.js增加配置,tailwindcss并没有生效,则使用以下的配置

新建postcss.config.js,输入以下内容,查看样式生效。

export default {
    plugins: {
      tailwindcss: {},
      autoprefixer: {},
    },
}  

使用示例:

 <div class="bg-green-600 w-20 h-12 font-semibold">12345</div>

vue3 同时使用 element plus 和 Tailwindcss 可能会出现样式冲突(例如el-button显示异常),此时一种解决方法是先引入 Tailwindcss ,然后再引入 element plus 的样式文件

vscode 安装 Tailwind CSS IntelliSense 插件 ,没有提示功能,通过如下解决:

1 vscode点击设置

2 搜索 editor.quickSuggestions

3 strings 改为 on

权限

按钮级别权限

通过自定义指令来实现

https://juejin.cn/post/7320169878644113408?searchId=20240111091116F28599892BCAA27BF8FDopen in new window

//  按钮权限--显示隐藏控制实现
Vue.directive('permission', {
// 在元素被插入到 DOM 中时触发
  inserted (el, binding) {
    let userInfo = localStorage.getItem('userInfo')
    let btnPermission = JSON.parse(userInfo).buttons //本地存储中取后端返回权限数据
    console.log(btnPermission)
    if (binding.value) {
    // 如果不包含绑定值,则从父节点中移除该元素
      if (!btnPermission.includes(binding.value)) {
        el.parentNode.removeChild(el)  // 移除元素
      }
    }
  }
})

vueUse

起步 | VueUse中文文档 (vueusejs.com)open in new window

npm i @vueuse/core

在使用的页面引入需要的钩子就行(具体钩子查看官网)

index.vue

<template>
	<div>
	  <div>vueUse:useMouse:   x{{ x }}  y{{ y }}  </div>
	<div>
</template>

<script setup>
	import {  useMouse  } from '@vueuse/core'
    const { x, y, sourceType } = useMouse()
</script>

源码(深入了解)

尤雨溪教你写vue 高级vue教程 源码分析 中文字幕翻译完毕_哔哩哔哩_bilibiliopen in new window

Loading...