• vue3-vite-ts-pinia


    Vue3 + vite + Ts + pinia + 实战 + 源码 +electron

    仓库地址:https://gitee.com/szxio/vue3-vite-ts-pinia

    视频地址:小满Vue3(课程导读)_哔哩哔哩_bilibili

    课件地址:Vue3_小满zs的博客-CSDN博客

    初始化Vue3项目

    方式一

    npm init vite@latest
    
    • 1

    image-20230903162122600

    生成的目录结构

    vite-demo
    ├── .vscode
    │   └── extensions.json
    ├── public
    │   └── vite.svg
    ├── src
    │   ├── assets
    │   │   └── vue.svg
    │   ├── components
    │   │   └── HelloWorld.vue
    │   ├── App.vue
    │   ├── main.ts
    │   ├── style.css
    │   └── vite-env.d.ts
    ├── README.md
    ├── index.html
    ├── package.json
    ├── tsconfig.json
    ├── tsconfig.node.json
    └── vite.config.ts
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    启动

    npm run dev
    
    • 1

    image-20230903162616723

    方式二

    npm init vue@latest
    
    • 1

    image-20230903162819151

    生成的目录结构

    vue-demo
    ├── .vscode
    │   └── extensions.json
    ├── public
    │   └── favicon.ico
    ├── src
    │   ├── assets
    │   │   ├── base.css
    │   │   ├── logo.svg
    │   │   └── main.css
    │   ├── components
    │   │   ├── __tests__
    │   │   ├── icons
    │   │   ├── HelloWorld.vue
    │   │   ├── TheWelcome.vue
    │   │   └── WelcomeItem.vue
    │   ├── router
    │   │   └── index.ts
    │   ├── stores
    │   │   └── counter.ts
    │   ├── views
    │   │   ├── AboutView.vue
    │   │   └── HomeView.vue
    │   ├── App.vue
    │   └── main.ts
    ├── .eslintrc.cjs
    ├── .prettierrc.json
    ├── README.md
    ├── env.d.ts
    ├── index.html
    ├── package.json
    ├── tsconfig.app.json
    ├── tsconfig.json
    ├── tsconfig.node.json
    ├── tsconfig.vitest.json
    ├── vite.config.ts
    └── vitest.config.ts
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    用这种方式生成的项目会全一点

    启动

    npm run dev
    
    • 1

    image-20230903163023078

    自动生成路由

    添加 gen-router.js 文件

    var fs = require('fs');
    const readline = require('readline');
    const os = require('os');
    
    const vueDir = './src/views/';
    
    fs.readdir(vueDir, function (err, files) {
        if (err) {
            console.log(err);
            return;
        }
        let routers = ``;
    
        // 对文件进行排序
        let sortFiles = files.sort((a,b)=>{
            return a.split("_")[0] - b.split("_")[0]
        });
    
        for (const filename of sortFiles) {
            if (filename.indexOf('.') < 0) {
                continue;
            }
            var [name, ext] = filename.split('.');
            if (ext != 'vue') {
                continue;
            }
            let routerName = null
    
            const contentFull = fs.readFileSync( `${vueDir}${filename}`, 'utf-8' );
            var match = /\<\!\-\-\s*(.*)\s*\-\-\>/g.exec(contentFull.split(os.EOL)[0]);
            if (match) {
                routerName = match[1];
            }
    
    
            routers += `  {path: '/${name === 'root' ? '' : encodeURIComponent(name)}',name:'${name}', component: ()=> import(/* webpackChunkName: "${name}" */ "@/views/${filename}") ${ routerName ? ',name: "' + routerName + '"' : ''} },\n`;
        }
    
    const result = `
    import { createRouter, createWebHistory } from 'vue-router'
    import Layout from '@/layout/index.vue'
    
    const router = createRouter({
      history: createWebHistory(import.meta.env.BASE_URL),
      routes: [
        {
          path: '/',
          name: 'index',
          component: Layout,
          redirect: '/index',
          children:[
            ${routers}
          ]
        },
        
      ]
    })
    
    export default router
    `
    
    
    
        // console.log(result);
        fs.writeFile('./src/router/index.ts',result, 'utf-8',
            (err) => {
                if (err) throw err;
            });
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    修改 package.json 中的启动命令

    "scripts": {
      "dev": "node gen-router.js &&  vite",
    },
    
    • 1
    • 2
    • 3

    这样每次新建完一个文件后需要重启一下服务,然后会自动生成路由文件,配置菜单动态显示即可

    Ref全家桶

    ref

    接受一个内部值并返回一个可变响应式的 ref 对象。ref 对象仅有一个 .value property,指向该内部值。

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    调试小技巧

    我们打印 ref 对象时需要点开两层才能看到信息,如下

    image-20230903201400943

    可以打开 启用自定义格式化程序

    image-20230903201422321

    image-20230903201441480

    之后打印就会直接展示具体的信息

    image-20230903201520363

    isRef

    判断一个对象是否是响应式对象

    import { ref, isRef } from "vue";
    
    const product = ref({
      id: "001",
      name: "小米手机"
    })
    
    const change = () => {
      product.value.name = "华为手机"
      // isRef判断一个对象是否是响应式对象
      console.log(isRef(product)) // true
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    shallowRef

    创建一个跟踪自身 .value 变化的 ref,但不会使其值也变成响应式的

    import { ref, isRef, shallowRef } from "vue";
    
    const shaRef = shallowRef({
      price: 100
    })
    
    const change = () => {
      // product.value.name = "华为手机"
      // isRef判断一个对象是否是响应式对象
      console.log(isRef(product)) // true
    
      shaRef.value.price = 200
    
      console.log(shaRef.value);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    上面的例子中页面不会发生变化

    triggerRef

    强制更新页面

    import { ref, isRef, shallowRef, triggerRef } from "vue";
    
    const product = ref({
      id: "001",
      name: "小米手机"
    })
    
    const shaRef = shallowRef({
      price: 100
    })
    
    const change = () => {
      // product.value.name = "华为手机"
      // isRef判断一个对象是否是响应式对象
      console.log(isRef(product)) // true
    
      shaRef.value.price = 200
    
      console.log(shaRef.value);
    
      triggerRef(shaRef)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    需要传入一个要更新的对象

    customRef

    自定义一个ref响应式数据

    import { customRef } from "vue";
    
    function myRef<T>(value: T) {
      return customRef((track, trigger) => {
        return {
          get() {
            track()
            return value
          },
          set(newVal) {
            value = newVal
            trigger()
          },
        }
      })
    }
    
    const song1 = myRef("123")
    
    const change = () => {
      song1.value = "456"
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    Reactive全家桶

    Reactive

    用来绑定复杂的数据类型 例如 对象 数组

    源码中限定只能传入类型是Object的数据

    image-20230903211342898

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    Readonly

    将一个对象设置为只读

    import { reactive, readonly } from 'vue';
    
    let form = reactive({
        name: "张三",
        age: 18
    })
    let readOnlyForm = readonly(form)
    function change() {
        readOnlyForm.age++
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    image-20230903212107449

    shallowReactive

    浅层的响应式

    import { shallowReactive } from 'vue';
    
    let shaReactive = shallowReactive({
        a: {
            b: 123
        }
    })
    function chageSha() {
        shaReactive.a.b = 456 // 页面不会发生改变
        console.log(shaReactive); // 打印的数据发生改变
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    to系列全家桶

    toRef

    将对象中的某个属性变成响应式的

    如果原始数据是非响应式的,则经过 toRef 之后也不会更新视图,但是数据会发生变化

    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    如果源数据就是响应式的,则会触发页面更新

    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    toRefs

    将对象的所有数据都变成响应式数据

    import { toRef, toRefs, toRaw, ref, reactive } from "vue"
    
    const student = reactive({
        name: "Jack",
        age: 18,
        like: "画画",
        code: [1, 2]
    })
    
    // 自实现toRefs
    function myToRefs<T extends Object>(object: T) {
        let map: any = {}
        for (const key in object) {
            map[key] = toRef(object, key)
        }
        return map
    }
    function refs() {
        console.log(myToRefs(student)); // 打印结果如下图
    }
    
    // 使用场景:对象解构
    let { name, age, code } = toRefs(student)
    function fun1() {
        name.value = "Tim"
        age.value = 16
        code.value.push(3)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    myToRefs 打印结果

    image-20230903223545771

    toRaw

    返回对象的原始信息

    function fun2() {
        console.log(toRaw(student));
    }
    
    • 1
    • 2
    • 3

    打印

    image-20230903223501841

    Vue3响应式源码实现

    初始化项目结构

    vue-proxy
    ├── effect.js
    ├── effect.ts
    ├── index.html
    ├── index.js
    ├── package.json
    ├── reactive.js
    ├── reactive.ts
    └── webpack.config.js
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    reactive.ts

    import { track, trigger } from "./effect"
    
    // 判断是否是对象
    const isObject = (target) => target !== null && typeof target === "object"
    
    // 泛型约束只能传入Object类型
    export const reactive = <T extends object>(target: T) => {
    
        return new Proxy(target, {
            get(target, key, receiver) {
                console.log(target);
                console.log(key);
                console.log(receiver);
    
                let res = Reflect.get(target, key, receiver)
    
                track(target, key)
    
                if (isObject(res)) {
                    return reactive(res)
                }
    
                return res
            },
            set(target, key, value, receiver) {
                let res = Reflect.set(target, key, value, receiver)
                console.log(target, key, value);
    
                trigger(target, key)
                return res
            }
        })
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    effect.ts

    // 更新视图的方法
    let activeEffect;
    export const effect = (fn: Function) => {
        const _effect = function () {
            activeEffect = _effect;
            fn()
        }
        _effect()
    }
    
    // 收集依赖
    const targetMap = new WeakMap()
    export const track = (target, key) => {
        let depsMap = targetMap.get(target)
        if (!depsMap) {
            depsMap = new Map()
            targetMap.set(target, depsMap)
        }
        let deps = depsMap.get(key)
        if (!deps) {
            deps = new Set()
            depsMap.set(key, deps)
        }
        deps.add(activeEffect)
    }
    
    // 触发更新
    export const trigger = (target, key) => {
        const depsMap = targetMap.get(target)
        const deps = depsMap.get(key)
        deps.forEach(effect => effect())
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    测试

    执行 tsc 转成 js 代码,没有 tsc 的全局安装 typescript

    npm install typescript -g
    
    • 1

    新建 index.js,分别引入 effect.jsreactive.js

    import { effect } from "./effect.js";
    import { reactive } from "./reactive.js";
    
    let data = reactive({
        name: "lisit",
        age: 18,
        foor: {
            bar: "汽车"
        }
    })
    
    effect(() => {
        document.getElementById("app").innerText = `数据绑定:${data.name} -- ${data.age} -- ${data.foor.bar}`
    })
    
    document.getElementById("btn").addEventListener("click", () => {
        data.age++
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    新建index.html

    DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Documenttitle>
    head>
    
    <body>
        <div id="app">div>
        <button id="btn">按钮button>
    body>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    然后再根目录执行

    npm init -y
    
    • 1

    安装依赖

    npm install webpack webpack-cli webpack-dev-server html-webpack-plugin -D
    
    • 1

    然后新建 webpack.config.js

    const path = require("path")
    const HtmlWebpakcPlugin = require("html-webpack-plugin")
    
    module.exports = {
        entry: "./index.js",
        output: {
            path: path.resolve(__dirname, "dist")
        },
    
        plugins: [
            new HtmlWebpakcPlugin({
                template: path.resolve(__dirname, "./index.html")
            })
        ],
        mode: "development",
        // 开发服务器
        devServer: {
            host: "localhost", // 启动服务器域名
            port: "3000", // 启动服务器端口号
            open: true, // 是否自动打开浏览器
        },
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    执行命令启动项目

    npx webpack serve
    
    • 1

    image-20230904232457817

    image-20230904232522425

    computed的简单使用

    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    image-20230910152223957

    computed源码实现

    effect.ts

    // 更新视图方法
    let activeEffect
    export const effect = (fn:Function,options) => {
        console.log("effect触发")
        const _effect = function () {
            activeEffect = _effect
            return fn()
        }
        _effect.options = options
        _effect()
        return _effect
    }
    
    // 依赖收集
    const targetMap = new WeakMap()
    export const track = (target, key) => {
        let depsMap = targetMap.get(key)
        if (!depsMap) {
            depsMap = new Map()
            targetMap.set(target, depsMap)
        }
        let deps = depsMap.get(key)
        if (!deps) {
            deps = new Set()
            depsMap.set(key, deps)
        }
        deps.add(activeEffect)
    }
    
    
    // 触发更新
    export const trigger = (target, key) => {
        const depsMap = targetMap.get(target)
        const deps = depsMap.get(key)
        deps.forEach(effect => {
            if (effect.options.scheduler){
                effect.options.scheduler()
            }else{
                effect()
            }
        })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    reactive.ts

    import {track, trigger} from "./effect"
    // 判断是否是对象类型
    const isObject = (target) => typeof target === 'object' && target !== null
    
    export const reactive = (target) => {
        return new Proxy(target, {
            get(target, key, receiver) {
                console.log("reactive.get-",key)
                const res = Reflect.get(target, key, receiver)
                // 收集依赖
                track(target, key)
                // 递归
                return isObject(res) ? reactive(res) : res
            },
            set(target, key, value, receiver) {
                console.log("reactive.set-",key)
                const res = Reflect.set(target, key, value, receiver)
                // 触发依赖
                trigger(target, key)
                return res
            }
        })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    computed.ts

    import {effect} from  "./effect"
    
    export const myComputed = (getter:Function)=>{
        let _value = effect(getter,{
            scheduler:()=>{
                _dirty = true
            }
        })
        // 判断是否需要重新计算结果
        let _dirty = true
        // 缓存结果
        let catchValue
    
        class ComputedRefImpl{
            get value(){
                if(_dirty){
                    console.log("依赖发生变化时执行")
                    catchValue = _value()
                    _dirty = false
                }
                return catchValue
            }
        }
        return new ComputedRefImpl()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    watch监听器

    监听单属性值

    let name = ref("李四")
    
    watch(name,(newValue,oldValue)=>{
      console.log(newValue,oldValue)
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5

    同时监听多个属性

    let name = ref("李四")
    let age = ref(20)
    
    watch([name,age],(newValue,oldValue)=>{
      console.log(newValue,oldValue)
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    image-20230910154510725

    深度监听

    let obj = ref({
      foo:{
        bar:{
          name:"张三"
        }
      }
    })
    
    watch(obj,(newValue,oldValue)=>{
      console.log(obj.value.foo.bar.name)
    },{
      deep:true, //  深度监听
      immediate:true, // 立即执行
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    监听对象中的某一个属性

    let obj = ref({
      foo:{
        bar:{
          name:"张三",
          age:18
        }
      }
    })
    
    // 监听某个属性是要传入一个函数来返回要监听的属性值
    watch(()=>obj.value.foo.bar.age,(newValue,oldValue)=>{
      console.log(obj.value.foo.bar.age)
    },{
      immediate:true
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    watchEffect

    简介

    watchEffect不需要传入任何参数,它是一个函数,当依赖变化时,这个函数就会执行,它内部会根据响应式数据的依赖关系,自动执行监听函数

    使用

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    BEM架构和Layout布局

    Layout目录结构

    layout
    ├── Content
    │   └── index.vue
    ├── Header
    │   └── index.vue
    ├── Menu
    │   └── index.vue
    ├── css
    │   └── bem.scss
    └── index.vue
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    新建 bem.scss

    $namespace: "zx" !default;
    $block-sel:"-" !default;
    $element-sel:"__" !default;
    $modifier-sel:"--" !default;
    
    @mixin bfc{
      height:100%;
      overflow: hidden;
    }
    
    @mixin b($block){
      // 拼接的结果为:zx-xxx
      $B:$namespace + $block-sel + $block;
      .#{$B}{
        @content;
      }
    }
    
    @mixin e($element){
      // 拼接的结果为:zx-xxx__xxx
      $selector:&;
      @at-root {
        $E:$selector + $element-sel + $element;
        #{$E}{
          @content;
        }
      }
    }
    
    @mixin m($modifier){
      // 拼接的结果为:zx-xxx--xxx
      $selector:&;
      @at-root {
        $M:$selector + $modifier-sel + $modifier;
        #{$M}{
          @content;
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    配置全局生效

    import { fileURLToPath, URL } from 'node:url'
    
    import { defineConfig } from 'vite'
    import vue from '@vitejs/plugin-vue'
    import vueJsx from '@vitejs/plugin-vue-jsx'
    import AutoImport from 'unplugin-auto-import/vite'
    import Components from 'unplugin-vue-components/vite'
    import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
    
    // https://vitejs.dev/config/
    export default defineConfig({
      plugins: [
        vue(),
        vueJsx(),
        AutoImport({
          resolvers: [ElementPlusResolver()],
        }),
        Components({
          resolvers: [ElementPlusResolver()],
        }),
      ],
      resolve: {
        alias: {
          '@': fileURLToPath(new URL('./src', import.meta.url))
        }
      },
      css: {
        preprocessorOptions: {
          // 配置全局CSS
          scss: {
            additionalData: "@import './src/layout_v2/css/bem.scss';"
          }
        }
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    index.vue

    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    Menu/index

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    Header/index.vue

    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    Content/index.vue

    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    布局效果

    image-20230913205111297

    父子组件传值

    简单使用

    定义父组件

    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    子组件

    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    image-20230913222206507

    实现瀑布流布局

    父组件

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    子组件

    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    效果展示

    image-20230913224830334

    组件递归

    实现一个如下的东西

    image-20230915214800791

    父组件

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    TreeVue.vue

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    控制台打印的东西

    image-20230915214931139

    动态组件

    image-20230915222313132

    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    插槽

    定义子组件

    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    定义父组件

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    效果

    image-20230917182606728

    异步组件

    添加骨架屏组件

    Skeleton.vue

    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    效果是这个样子

    image-20230917214033824

    添加新闻组件

    添加新闻数据,在 public 文件夹中添加 newinfo.json

    [
      {
        "title": "秋粮陆续成熟 多措并举保粮食丰收",
        "description": "眼下,从南到北,各地秋粮陆续成熟。人们全力以赴抓好秋粮生产,多措并举保粮食丰收。\n\n金秋时节,安徽水稻主产区无为市85万亩水稻进入收割期,当地组织机械作业服务队,帮助农民机耕机收,颗粒归仓。今年,安徽计划投入各类农机具240万台套,力争玉米、大豆、中晚稻机收水平达八成以上。",
        "url": "https://baijiahao.baidu.com/s?id=1777244368223895628",
        "image": "https://szx-bucket1.oss-cn-hangzhou.aliyuncs.com/picgo/img.png"
      }
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    引入 axios,请求这个文件

    src/api/index.js

    import axios from 'axios'
    
    export function getNewDataFun(){
        return axios("../public/newinfo.json")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    编写组件 NewCar.vue

    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    效果展示

    image-20230917214242665

    使用异步组件

    Suspense 是vue内置的一个组件,有两个插槽

    • default:默认插槽,展示等待结果返回后的组件
    • fallback:等待过程中展示的组件
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    异步组件必须使用 defineAsyncComponent 函数来导入,接收一个回调函数

    TelePore传送组件

    自定义一个弹框组件

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    使用TelePore

    父组件使用这个组件

    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    效果

    image-20230918075355087

    KeepAlive

    可以缓存组件内容

    默认使用

    切换组件显示后,组件内容不会丢失

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    includes

    只缓存AliveA组件

    
      
      
    
    
    • 1
    • 2
    • 3
    • 4

    exclude

    不缓存AliveA组件

    
      
      
    
    
    • 1
    • 2
    • 3
    • 4

    max

    最多缓存的组件个数

    
      
      
    
    
    • 1
    • 2
    • 3
    • 4

    keep-alive的钩子函数

    <script lang="ts" setup>
    import { ref,onMounted,onActivated,onDeactivated,onUnmounted, } from 'vue'
    
    // 组件显示时只会触发一次
    onMounted(()=>{
      console.log('mounted')
    })
    
    // 组件显示时触发
    onActivated(()=>{
      console.log('activated')
    })
    // 组件隐藏时触发
    onDeactivated(()=>{
      console.log('deactivated')
    })
    // 被keepalive包裹时,组件销毁不会触发unmounted
    onUnmounted(()=>{
      console.log('unmounted')
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    transition

    基本用法

    在进入/离开的过渡中,会有 6 个 class 切换。

    v-enter-from:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。

    v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。

    v-enter-to:定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter-from 被移除),在过渡/动画完成之后移除。

    v-leave-from:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。

    v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。

    v-leave-to:离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave-from 被移除),在过渡/动画完成之后移除。

    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    结合animate

    安装

    npm install animate.css -D
    
    • 1

    官网中有很多动画示例 Animate.css | A cross-browser library of CSS animations.

    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    transtion生命周期

    
      
    • 1
    • 2
    • 3
     @before-enter="beforeEnter" //对应enter-from
     @enter="enter"//对应enter-active
     @after-enter="afterEnter"//对应enter-to
     @enter-cancelled="enterCancelled"//显示过度打断
     @before-leave="beforeLeave"//对应leave-from
     @leave="leave"//对应enter-active
     @after-leave="afterLeave"//对应leave-to
     @leave-cancelled="leaveCancelled"//离开过度打断
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    结合gsap

    安装,官网:https://greensock.com/

    npm install gsap
    
    • 1

    使用

    html

    切换
    
      
    • 1
    • 2
    • 3
    • 4

    js

    <script setup>
    import gsap from "gsap";
    import {ref} from "vue";
    
    let gsapFlag = ref(true)
    
    const beforeEnter = (el) => {
      console.log("显示之前")
      gsap.set(el,{
        width:0,
        height:0,
        background:"green"
      })
    }
    const enter = (el,done) => {
      gsap.to(el,{
        width:"200px",
        height:"200px",
        background:"red",
        rotate:"360dge",
        duration:1, // 动画时长,单位是秒
        onComplete:done, // 动画完成后的回调函数
      })
    }
    const leave = (el,done) => {
      gsap.to(el,{
        width:0,
        height:0,
        background:"green",
        rotate:"-360dge",
        duration:1, // 动画时长,单位是秒
        onComplete:done
      })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    效果

    appear属性

    在 transtion 组件中添加 appear 可以在进入页面时就触发对应的样式代码

    • appear-class:初始样式
    • appear-to-class:结束样式
    • appear-active-class:动画曲线
    
      
    • 1
    • 2
    • 3

    结合animate__animated实现一个进入页面就执行的一个动画效果

    transition-group

    在遍历数组的时候可以给每一个元素添加过度动画,生命周期和transition一致,我们结合animate来实现一个列表的动画效果

      
    add pop
    {{item}}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    import {ref,reactive} from "vue";
    import "animate.css"
    
    const groupList = reactive([1,2,3,4,5])
    
    const add = () => {
      groupList.push(groupList.length + 1)
    }
    const pop = () => {
      groupList.pop()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    动画效果

    实现一个炫酷的动画效果

    安装lodash库 Lodash 简介 | Lodash中文文档 | Lodash中文网 (lodashjs.com)

    npm i --save lodash
    
    • 1

    实现代码

    平面动画过度效果
    动画
    {{item.value}}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    import {ref,reactive} from "vue";
    import _ from "lodash"
    
    let numList = ref(Array.apply(null, {length: 81}).map((_,index)=>{
      return {
        id:index,
        value:(index % 9) + 1
      }
    }))
    
    const shuffle = () => {
      // shuffle 用来创建一个被打乱值的集合
      numList.value = _.shuffle(numList.value)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    $numWidth:60px;
    
    .move-class{
      transition: all 1s ease;
    }
    .num-wrap{
      display: flex;
      flex-wrap: wrap;
      width: calc(#{$numWidth} * 9 + 5px * 8);
      gap: 5px;
      .num-item{
        width: $numWidth;
        height: $numWidth;
        line-height: $numWidth;
        text-align: center;
        border: 1px solid #ccc;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    实现效果

    使用gsap实现数字滚动

    使用gsap实现数字滚动

    {{rolling.numRul.toFixed(0)}}

    • 1
    • 2
    • 3
    • 4
    • 5
    import gsap from "gsap";
    import {ref,reactive,watch} from "vue";
    
    let rolling = reactive({
      num:10,
      numRul:10
    })
    watch(()=>rolling.num,(newVal)=>{
      gsap.to(rolling,{
        numRul:newVal,
        duration:1,
      })
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    依赖注入provide和inject

    爷爷组件

    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    ProvideA

    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    ProvideB

    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    实现效果

    兄弟传参

    Mitt

    安装

    npm install mitt
    
    • 1
    局部使用

    添加一个JS文件导出

    utils/mitt.js

    import mitt from "mitt"
    export default mitt()
    
    • 1
    • 2

    使用,分别定义 A B两个组件

    BusA

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    BusB

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在父组件引入

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    效果

    全局使用

    main文件添加

    import './assets/main.css'
    import { createApp } from 'vue'
    import { createPinia } from 'pinia'
    import ElementPlus from 'element-plus'
    import App from './App.vue'
    import router from './router'
    import 'element-plus/dist/index.css'
    import zhCn from 'element-plus/dist/locale/zh-cn.min.js'
    import 'dayjs/locale/zh-cn'
    
    + import mitt from "mitt"
    + const Mitt = mitt()
    
    const app = createApp(App)
    app.use(createPinia())
    app.use(router)
    app.use(ElementPlus, { locale: zhCn })
    app.mount('#app')
    
    + declare module 'vue'{
    +   export interface ComponentCustomProperties {
    +       $Bus: typeof Mitt
    +   }
    + }
    + app.config.globalProperties.$bus = Mitt
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    文件内部通过从 vue 中导出 getCurrentInstance 方法获取当前实例获取定义的全局变量使用

    BusA

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    BusB

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    手写Bus

    class MyBus{
        constructor() {
            this.list = {}
        }
        emit(event, ...args){
            let funs = this.list[event]
            funs.forEach((fun) =>{
                fun.apply(this,args)
            })
        }
        on(event, callback){
            let funs = this.list[event]
            if(funs){
                funs.push(callback)
            }else{
                funs = [callback]
            }
            this.list[event] = funs
        }
        off(event){
            delete this.list[event]
        }
    }
    export default new MyBus()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    jsx插件

    安装

    npm in stall @vitejs/plugin-vue-jsx -D
    
    • 1

    在 vite.config.js 中使用

    import { fileURLToPath, URL } from 'node:url'
    
    import { defineConfig } from 'vite'
    import vue from '@vitejs/plugin-vue'
    import vueJsx from '@vitejs/plugin-vue-jsx'
    import AutoImport from 'unplugin-auto-import/vite'
    import Components from 'unplugin-vue-components/vite'
    import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
    
    // https://vitejs.dev/config/
    export default defineConfig({
      module:"es2022",
      plugins: [
        vue(),
        vueJsx(),
        AutoImport({
          resolvers: [ElementPlusResolver()],
        }),
        Components({
          resolvers: [ElementPlusResolver()],
        }),
      ],
      resolve: {
        alias: {
          '@': fileURLToPath(new URL('./src', import.meta.url))
        }
      },
      css: {
        preprocessorOptions: {
          scss: {
            additionalData: "@import './src/layout_v2/css/bem.scss';"
          }
        }
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    新建 JsxCom.tsx

    import {defineComponent, reactive, ref} from "vue"
    import {ElButton} from "element-plus"
    
    interface propType  {
        msg?:string
    }
    
    export default defineComponent({
        props:{
            msg:String,
        },
        emits:[],
        setup(prop:propType,{emit,attrs,slots,expose}){
            let flag = ref(false)
    
            const chagneFlag = () => {
                flag.value = true
            }
    
            let list = reactive([1,2,3,4,5])
    
            return ()=> <>
                {/*遍历循环*/}
                {list.map(item => 

    {item}

    )}
    {/*按钮事件,使用οnclick={()=>chagneFlag()}*/} chagneFlag()}>改变这个值 {flag.value &&

    改变后的值

    }
    父组件传递的值:{prop.msg}
    }, })
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    在vue中可以把这个当成普通的组件使用

    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    页面效果

    image-20230924150109742

    自动引入插件

    安装

    npm istall unplugin-auto-import/vite
    
    • 1

    配置

    import AutoImport from 'unplugin-auto-import/vite'
    
    export default defineConfig({
      module:"es2022",
      plugins: [
        vue(),
        vueJsx(),
        AutoImport({
          resolvers: [ElementPlusResolver()],
          imports: ['vue', 'vue-router'], // 自动引入vue,和vue-router相关
          dts: 'src/auto-imports.d.ts' // 自动生成的依赖文件
        }),
        Components({
          resolvers: [ElementPlusResolver()],
        }),
      ],
      
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    保存后查看 src/auto-imports.d.ts 内容

    image-20230924150915291

    里面自动的帮我们了引入

    然后再组件中不需要手动的导入 vue,就可以使用vue中的各种声明

    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    v-model在组件中的使用

    基本使用

    vue3中在组件上绑定v-model时,默认的prop变成了modelValue

    子组件 Vmodel

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    父组件

    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    绑定多个v-model

    父组件

    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    子组件

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    自定义指令

    自定义指令的声明周期

    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    在任意一个钩子函数头能拿到自定义指令绑定的参数,我们通过打印 arg 看看参数有什么

    image-20230924171641529

    我们利用这两个参数实现监听元素宽高变化的指令,当元素宽高发生变化时调用绑定的函数

    mounted(el,bindings){
      console.log("mounted")
      // 监听元素宽高变化
      const resizeObserver = new ResizeObserver(entries => {
        let width = entries[0].contentRect.width;
        let height = entries[0].contentRect.height;
        console.log(`元素宽度:${width},元素高度:${height}`)
        bindings.value()
      });
      resizeObserver.observe(el);
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    修改 mounted 钩子的内容,通过observe 观察 el,然后调用 bindings.value

    自定义指令的简写方式

    我们也可以通过函数的方式来自定义指令

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    上面的例子是一个按钮级别权限的demo

    鼠标拖动元素案例

    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    图片懒加载案例

    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    自定义Hook

    好用的第三方库

    vueuse

    npm i @vueuse/core
    
    • 1

    网址:Get Started | VueUse — 开始使用 |Vueuse

    图片转base64

    新建 useImgToBase64.ts

    import {onMounted} from 'vue'
    
    type optionsType = {
        el:String
    }
    
    export default function (options:optionsType):Promise<string>{
        return new Promise((resolve, reject) =>{
            onMounted(()=>{
                let img:HTMLImageElement = document.querySelector(options.el)
                img.onload = ()=>{
                    resolve(toBase64(img))
                }
                const toBase64 = (img:HTMLImageElement) => {
                    let canvas:HTMLCanvasElement = document.createElement('canvas')
                    let ctx:CanvasRenderingContext2D = canvas.getContext('2d')
                    canvas.width = img.width
                    canvas.height = img.height
                    ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
                    return canvas.toDataURL("image/jpeg")
                }
            })
        })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    使用

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    image-20230924222116173

    自定义Vite库并发布到NPM

    封装useResize

    用于监听绑定元素的宽高变化,当元素宽高发生变化时触发回调并获取最新的宽高

    新建项目

    结合上面学到的 Hook 和 自定义指令封装一个监听元素宽高变化的指令,并发布到 npm

    项目结构

    useResize            
    ├── src              
    │   └── index.ts     
    ├── README.md        
    ├── index.d.ts       
    ├── package-lock.json
    ├── package.json     
    ├── tsconfig.json    
    └── vite.config.ts
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    src/index.ts

    import type {App} from "vue";
    
    /**
     * 自定义Hook
     * @param el
     * @param callback
     */
    const weakMap = new WeakMap<HTMLElement, Function>();
    const resizeObserver = new ResizeObserver((entries) => {
        for (const entry of entries) {
            const handle = weakMap.get(entry.target as HTMLElement);
            handle && handle(entry)
        }
    })
    
    function useResize(el: HTMLElement, callback: Function) {
        if (weakMap.get(el)) {
            return
        }
        weakMap.set(el, callback)
        resizeObserver.observe(el)
    }
    
    /**
     * 定义vite插件时,vue会在底层调用插件的install方法
     * @param app
     */
    function install(app: App) {
        app.directive('resize', {
            mounted(el: HTMLElement, binding: { value: Function }) {
                useResize(el, binding.value)
            }
        })
    }
    
    useResize.install = install
    
    export default useResize
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    vite.config.ts

    import {defineConfig} from "vite"
    
    export default defineConfig({
        build:{
            lib:{
                // 打包入口文件
                entry:"src/index.ts",
                // name
                name:"useResize"
            },
            rollupOptions:{
                // 忽略打包的文件
                external:['vue'],
                output:{
                    globals:{
                        useResize:"useResize"
                    }
                }
            }
        }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    index.d.ts

    declare const useResize:{
        (element:HTMLElement, callback:Function):void
        install:(app:any) => void
    }
    
    export default useResize
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    package.json

    {
      "name": "v-resize-songzx",
      "version": "1.0.0",
      "description": "",
      "main": "dist/v-resize-songzx.umd.js",
      "module": "dist/v-resize-songzx.mjs",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "build": "vite build"
      },
      "keywords": [],
      "author": "songzx",
      "files": [
        "dist",
        "index.d.ts"
      ],
      "license": "ISC",
      "devDependencies": {
        "vue": "^3.3.4"
      },
      "dependencies": {
        "vite": "^4.4.9"
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    pachage.json 文件属性说明:

    • name:对应打包后生成的包名,也就是上传到npm上面的包名,不能包含数字和特殊符号
    • version:包的版本号
    • main:对应打包后的 umd.js 文件,在使用 app.use 时会访问使用文件
    • module:使用import、require等方式引入时会使用 mjs 文件
    • files:指定那些文件需要上传
    打包
    npm run build
    
    • 1
    登录npm
    npm login
    
    • 1

    image-20230924231941434

    发布
    npm publish
    
    • 1

    image-20230924232715692

    打开 npm 网站,搜索查看是否发布成功

    image-20230925090849623

    使用自己的库

    安装
    npm i v-resize-songzx
    
    • 1
    使用方式一

    全局注册 v-resze 指令

    main.ts 引入

    import useResize from "v-resize-songzx";
    
    const app = createApp(App)
    
    app.use(useResize)
    app.mount('#app')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    使用方式二

    使用Hook的方式

    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    定义全局变量和方法

    main.ts 中添加

    import dayjs from "dayjs"
    import mitt from "mitt"
    
    const Mitt = mitt()
    
    // 定义全局变量
    app.config.globalProperties.$bus = Mitt
    app.config.globalProperties.$BaseUrl = 'http://localhost'
    app.config.globalProperties.$formatDate = (date: Date) => dayjs(date).format('YYYY-MM-DD HH:mm:ss')
    
    // 定义声明文件
    declare module 'vue' {
        export interface ComponentCustomProperties {
            $bus: typeof Mitt,
            $BaseUrl: string,
            $formatDate: Date
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在任何组件中都可以使用

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    自定义插件之全局Loading

    ElementPlus的默认全局Loading

    如果完整引入了 Element Plus,那么 app.config.globalProperties 上会有一个全局方法$loading,同样会返回一个 Loading 实例。

    名称说明类型默认
    targetLoading 需要覆盖的 DOM 节点。 可传入一个 DOM 对象或字符串; 若传入字符串,则会将其作为参数传入 document.querySelector以获取到对应 DOM 节点string / HTMLElementdocument.body
    bodyv-loading 指令中的 body 修饰符booleanfalse
    fullscreenv-loading 指令中的 fullscreen 修饰符booleantrue
    lockv-loading 指令中的 lock 修饰符booleanfalse
    text显示在加载图标下方的加载文案string
    spinner自定义加载图标类名string
    background遮罩背景色string
    customClassLoading 的自定义类名string
    指令的方式使用
    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    函数式调用
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    自定义全局Loading

    我们自己动手来实现一个和ElementPlus的Loading,同时支持函数调用和指令调用

    添加MyLoading.vue
    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    添加MyLoading.ts
    import type {App, VNode,} from "vue"
    import {createVNode, render, cloneVNode} from "vue"
    import MyLoading from "@/components/MyLoading.vue"
    
    export default {
        install(app: App) {
            // 使用vue底层的createVNode方法将组件渲染为虚拟节点
            const VNode: VNode = createVNode(MyLoading)
            // 使用render函数将组件挂载到body中
            render(VNode, document.body)
            // 定义全局方法设置组件的显示和隐藏
            app.config.globalProperties.$showLoading = VNode.component?.exposed.showLoading
            app.config.globalProperties.$hideLoading = VNode.component?.exposed.hideLoading
    
            const weakMap = new WeakMap()
    
            // 自定义Loading指令
            app.directive("zx-loading", {
                mounted(el) {
                    if (weakMap.get(el)) return
                    //  记录当前绑定元素的position
                    weakMap.set(el, window.getComputedStyle(el).position)
                },
                updated(el: HTMLElement, binding: { value: Boolean }) {
                    const oldPosition = weakMap.get(el);
                    // 如果不是position: relative或者absolute,就设置为relative
                    // 这里的目的是确保loading组件正确覆盖当前绑定的元素
                    if (oldPosition !== 'absolute' && oldPosition !== 'relative') {
                        el.style.position = 'relative'
                    }
                    // 克隆一份loading元素,
                    // 作用是当页面上有多个zx-loading时,每个dom都维护一份属于自己的loading,不会冲突
                    const newVNode = cloneVNode(VNode)
                    // 挂载当前节点
                    render(newVNode, el)
                    // 判断绑定的值
                    if (binding.value) {
                        newVNode.component?.exposed.showLoading()
                    } else {
                        newVNode.component?.exposed.hideLoading(() => {
                            // 还原布局方式
                            el.style.position = oldPosition
                        })
                    }
                }
            })
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    在上面的文件中定义了两个全局函数和一个自定义指令

    • $showLoading:全局显示一个Loading
    • $hideLoading:关闭全局的Loading
    • zx-loading:自定义指令
    在main.ts中挂载

    main.ts 中去挂载我们自定义的 Loading

    import {createApp} from 'vue'
    import MyLoading from "@/utils/MyLoading";
    
    const app = createApp(App)
    // 引入自定义的全局Loading
    app.use(MyLoading)
    
    app.mount('#app')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    使用方法一:函数式使用

    调用全局方法弹出Loading

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    image-20230925171920861

    使用方法二:指令式使用
    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    image-20230925172100385

    use函数源码实现

    添加 MyUse.ts

    import type {App} from "vue"
    import {app} from "@/main"
    
    // 定义一个接口,声明install方法必传
    interface Use {
        install: (app: App, ...options: any[]) => void
    }
    
    const installList = new Set()
    
    export default function myUse<T extends Use>(plugin: T, ...options: any[]) {
        // 判断这个插件是否已经注册过了,如果注册过了则报错
        if (installList.has(plugin)) {
            console.error("Plugin already installed")
            return
        }
        // 调用插件身上的install方法,并传入main.ts导出的app
        plugin.install(app, ...options)
        installList.add(plugin)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    使用自定义的myUse方法注册我们自定义的Loading

    import {createApp} from 'vue'
    
    // 自定义全局Loading
    import MyLoading from "@/utils/MyLoading";
    // 自定义app.use方法
    import myUse from "@/utils/MyUse";
    
    
    export const app = createApp(App)
    // 引入自定义的全局Loading
    myUse(MyLoading)
    
    app.mount('#app')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    CSS选择器

    :deep

    使用 :deep() 将选择器包裹起来可以将第三方库的样式进行修改

    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    image-20230925223153879

    :slotted

    使用 :slotted() 将插槽中的类名包裹起来,可以修改插槽中的元素样式

    SlotTestCom.vue

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    
      
    私人订制DIV
    • 1
    • 2
    • 3

    image-20230927103314117

    :global

    使用 :global() 用于设置全局样式

    :global(div){
      font-size: 17px;
      color: #222222;
    }
    
    • 1
    • 2
    • 3
    • 4

    全局设置div的样式

    css中使用v-bind

    let color = ref("pink")
    // 随机一个颜色
    const randomColor = () => {
      color.value = `rgb(${Math.random() * 255},${Math.random() * 255},${Math.random() * 255})`
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    使用 v-bind() 将JS中变量包裹起来即可使用

    .el-input {
      width: 300px;
    
      :deep(.el-input__inner) {
        background-color: v-bind(color);
    
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    Vue3集成Tailwind CSS

    官网地址Tailwind CSS 中文文档 - 无需离开您的HTML,即可快速建立现代网站。

    安装

    npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
    
    • 1

    生成配置文件

    npx tailwindcss init -p
    
    • 1

    修改配置文件 tailwind.config.js

    2.6版本

    module.exports = {
      purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
      theme: {
        extend: {},
      },
      plugins: [],
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.0版本

    module.exports = {
      content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
      theme: {
        extend: {},
      },
      plugins: [],
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    新建 index.css 并在 main.ts 中引入

    @tailwind base;
    @tailwind components;
    @tailwind utilities;
    
    • 1
    • 2
    • 3

    image-20230927132455491

    基础使用

    详细类名见文档:https://www.tailwindcss.cn/docs/font-family

    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    image-20230927132522243

    nextTick

    vue 中更新DOM操作是异步的,但是JS程序是同步的,所以当遇到操作DOM时可能会出现延迟更新的情况,vue 也给了一个解决方案,就是可以将操作 DOM 的代码放在 nextTick 中执行,nextTick 会执行一个 Promise 函数去更新DOM,来实现同步更新DOM的操作

    这样做的好处是可以提高程序性能,例如执行一个for循环,每次循环会改变变量的值,然后吧这个变量输出到页面上。用一个watch去监听这个变量,watch函数并不会触发多次,而是只会执行一次

    下面是一个小案例

    
    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    Vue3开发安卓和IOS

    参照博客:https://xiaoman.blog.csdn.net/article/details/131507483

    安装安卓开发工具

    image-20230927161024413

    image-20230927161057832

    image-20230927161139275

    image-20230927161213097

    image-20230927161303242

    安装完成后打开

    image-20230927161352447

    首次运行需要安装一些SDK

    image-20230927161623550

    ionic安装

    npm install -g @ionic/cli
    
    • 1

    初始化项目

    ionic start app tabs --type vue
    
    • 1
    • app 项目名称
    • tabs 使用的预设
    • –type vue 使用的是vue就写vue,react就写react

    image-20230927163158065

    image-20230927165645993

    启动项目

    npm run dev
    
    • 1

    image-20230927165717819

    打包和构建

    先执行打包命令

    npm run build
    
    • 1

    再执行构建命令,将程序打包成Android包

    ionic capacitor copy android
    
    • 1

    运行成功后会自动多一个android文件夹

    image-20230927165845466

    image-20230927165905649

    然后运行下面命令进行预览

    ionic capacitor open android
    
    • 1

    会自动打开安卓编辑器

    等待项目加载完成后,点击绿色的箭头即可启动

    image-20230927171133698

    H5适配

    添加meat信息

    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    
    • 1

    清除默认样式

    <style>
      html,body,#app{
          height: 100%;
          overflow: hidden;
      }
      *{
          padding: 0;
          margin: 0;
      }
    style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    圣杯布局

    <template>
      <div class="header">
        <div>div>
        <div>div>
        <div>div>
      div>
    template>
    
    <style scoped lang="scss">
    .header{
      width: 100%;
      height: 50px;
      line-height: 50px;
      display: flex;
      div:nth-child(1),div:nth-child(3){
        width: 100px;
        background-color: deepskyblue;
      }
      div:nth-child(2){
        flex: 1;
        background-color: pink;
      }
    }
    style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    image-20231007222439590

    使用postCSS将px单位转成vh和vw

    百分比是相对于父元素

    vw和vh相对于视口

    编写postCSS插件

    新建 plugins/PxToVwVh.ts

    import {Plugin} from "postcss"
    
    let Options = {
        defaultWidth: 390,
        defaultHeight: 844,
    }
    interface OptionsTypes {
        defaultWidth?:number,
        defaultHeight?:number,
    }
    
    export function PxToVwVh(options:OptionsTypes=Options):Plugin{
        let opt = Object.assign({}, options)
        return {
            postcssPlugin:"px-to-vw-vh",
            // 钩子函数
            Declaration(node){
                if(node.value.includes("px")){
                    const num = parseFloat(node.value)
                    if(node.prop.includes("width")){
                        node.value = `${((num / opt.defaultWidth) * 100).toFixed(2)}vw`
                    }else if(node.prop.includes("height")){
                        node.value = `${((num / opt.defaultHeight) * 100).toFixed(2)}vh`
                    }
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    tsconfig.node.json 中引入

    {
      "extends": "@tsconfig/node18/tsconfig.json",
      "include": [
        "vite.config.*",
        "vitest.config.*",
        "cypress.config.*",
        "nightwatch.conf.*",
        "playwright.config.*",
        "plugins/**/*"
      ],
      "compilerOptions": {
        "composite": true,
        "module": "ESNext",
        "moduleResolution": "Bundler",
        "types": ["node"],
        "noImplicitAny": false
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • include中添加 plugins/**/*
    • noImplicitAny 允许隐式的使用any

    使用插件

    vite.config.ts 中使用

    import { fileURLToPath, URL } from 'node:url'
    
    import { defineConfig } from 'vite'
    import vue from '@vitejs/plugin-vue'
    import {PxToVwVh} from "./plugins/PxToVwVh";
    
    // https://vitejs.dev/config/
    export default defineConfig({
      plugins: [
        vue(),
      ],
      css: {
        postcss: {
          plugins: [
            PxToVwVh()
          ]
        },
      },
      resolve: {
        alias: {
          '@': fileURLToPath(new URL('./src', import.meta.url))
        }
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    效果展示

    我们通过编写插件,实现了将PX单位转换成相对于视口,这样保证了在不同尺寸的屏幕上都会有一个相同的展示布局

    image-20231007224908253

    image-20231007224848623

    全局控制字体大小

    设置全局CSS变量

    :root{
        --font-size:16px;
    }
    
    • 1
    • 2
    • 3

    然后全局可以通过 var(–font-size) 使用

    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    点击按钮可以实现字体大小切换

    image-20231007230159198

    unoCss原子化

    官网:https://unocss.dev/

    什么是css原子化?

    CSS原子化的优缺点

    1.减少了css体积,提高了css复用

    2.减少起名的复杂度

    3.增加了记忆成本 将css拆分为原子之后,你势必要记住一些class才能书写,哪怕tailwindcss提供了完善的工具链,你写background,也要记住开头是bg

    安装

    npm i -D unocss
    
    • 1

    配置插件

    // vite.config.ts
    import UnoCSS from 'unocss/vite'
    import { defineConfig } from 'vite'
    
    export default defineConfig({
      plugins: [
        UnoCSS(),
      ],
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    创建一个 uno.config.js 文件

    // uno.config.js
    import { defineConfig } from 'unocss'
    
    export default defineConfig({
        // 自定义规则
        rules:[
            ["red",{ color:"red",'font-size':"25px" }]
        ]
    })
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    main.ts 文件中添加

    // main.ts
    import 'virtual:uno.css'
    
    • 1
    • 2

    使用

    直接在页面中使用类名即可

    <div class="red">
      Hello Word
    div>
    
    • 1
    • 2
    • 3

    image-20231008221631938

    动态配置类名

    rules: [
      [/^m-(\d+)$/, ([, d]) => ({ margin: `${Number(d) * 10}px` })],
      ['flex', { display: "flex" }]
    ]
    
    • 1
    • 2
    • 3
    • 4

    使用

    <div class="red m-10">
      Hello Word
    div>
    
    • 1
    • 2
    • 3

    image-20231008222106144

    使用预设

    修改 uno.config.js

    // uno.config.js
    import { defineConfig,presetIcons,presetAttributify,presetUno } from 'unocss'
    
    export default defineConfig({
        // 自定义规则
        rules:[
            [/^m-(\d+)$/, ([, d]) => ({ margin: `${Number(d) * 10}px` })],
            ["red",{ color:"red",'font-size':"25px" }],
        ],
        // 使用预设
        presets:[presetIcons(),presetAttributify(),presetUno()]
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • presetIcons 这个是图标

    • presetAttributify 这个是美化CSS

    • presetUno 预设(实验阶段)是一系列流行的原子化框架的 通用超集,包括了 Tailwind CSS,Windi CSS,Bootstrap,Tachyons 等。

      例如,ml-3(Tailwind),ms-2(Bootstrap),ma4(Tachyons),mt-10px(Windi CSS)均会生效。

    使用图标

    在官网中找到自己需要的图标:https://icones.js.org/

    然后选中后安装

    image-20231008223505900

    查看页面路径上的单词,然后安装

    npm i -D @iconify-json/svg-spinners
    
    • 1

    点击某个要使用的图标,复制类名即可

    image-20231008223618270

    <div class="i-svg-spinners-bars-fade font-size-50px color-pink">div>
    
    • 1

    image-20231008224316603

    Vue编译宏

    首先vue版本必须是3.3及以上版本

    子组件

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    父组件

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    函数名称含义
    defineProps接收父组件传递过来的参数
    defineEmits定义事件名称
    defineOptions配置组件名称和其他信息

    Vue环境变量

    在项目根目录新建两个文件,分别表示开发环境配置、生成环境配置

    注意:设置环境变量时必须以 VITE_ 开头,否则不生效

    .env.development

    # .env.development
    VITE_API=http://localhost:8080
    
    • 1
    • 2

    .env.production

    # .env.production
    VITE_API=/prod-api
    
    • 1
    • 2

    修改 package.json 中的运行命令,在启动dev是设置mode是development,表示读取开发环境配置,名称可以自定义,但是要和上面新建的配置文件后缀名保持一致

    "scripts": {
      "dev": "vite --mode development",
    },
    
    • 1
    • 2
    • 3

    然后在 vue 文件中通过下面方式获取配置项

    console.log(import.meta.env)
    
    • 1

    image-20231015105913888

    这里是开发环境,读取到的 VITE_API 是 http://localhost:8080

    然后打包项目,再看一下打印结果

    image-20231015110224421

    vite.config.ts 中获取环境变量时通过如下方式获取

    import { defineConfig,loadEnv } from 'vite'
    
    
    let {VITE_API} = loadEnv(process.env.NODE_ENV,process.cwd())
    
    console.log(VITE_API)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    控制台会打印出定义的环境变量

    image-20231015110925175

    Webpack从0到1构建Vue3工程

    项目结构

    webpack-vue            
    ├── config             
    │   ├── webpack.dev.js 
    │   └── webpack.prod.js
    ├── src                
    │   ├── App.vue        
    │   └── Child.vue      
    ├── index.html         
    ├── main.js            
    ├── package.json       
    └── pnpm-lock.yaml
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    package.json

    {
      "name": "webpack-vue",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "build": "webpack --config config/webpack.prod.js",
        "dev": "webpack serve --config config/webpack.dev.js"
      },
      "keywords": [],
      "author": "",
      "license": "ISC",
      "dependencies": {
        "@vue/compiler-sfc": "^3.3.4",
        "clean-webpack-plugin": "^4.0.0",
        "css-loader": "^6.8.1",
        "friendly-errors-webpack-plugin": "^1.7.0",
        "html-webpack-plugin": "^5.5.3",
        "less": "^4.2.0",
        "less-loader": "^11.1.3",
        "style-loader": "^3.3.3",
        "typescript": "^5.2.2",
        "vue": "^3.3.4",
        "vue-loader": "^17.3.0",
        "webpack": "^5.89.0",
        "webpack-cli": "^5.1.4",
        "webpack-dev-server": "^4.15.1"
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    webpack.dev.js

    const path = require("path")
    const HtmlWebpackPlugin =  require("html-webpack-plugin");
    const {CleanWebpackPlugin} = require("clean-webpack-plugin");
    const {VueLoaderPlugin} = require("vue-loader");
    
    module.exports = {
        mode:"development",
        entry: "./main.js",
        output: {
            filename: "js/[name].[contenthash:10].js",
            path: path.resolve(__dirname, "dist")
        },
        module: {
            rules: [
                {
                    test:/\.vue$/,
                    use: "vue-loader"
                },
                {
                    test: /\.css$/, //解析css
                    use: ["style-loader", "css-loader"],
                },
                {
                    test:/\.less/,
                    use: ["style-loader","css-loader", "less-loader"],
                }
            ]
        },
        resolve: {
            alias: {
                "@/": path.resolve(__dirname, './src') // 别名
            },
            extensions: ['.js', '.json', '.vue', '.ts', '.tsx'] //识别后缀
        },
        plugins: [
            new CleanWebpackPlugin(),
            new VueLoaderPlugin(),
           new HtmlWebpackPlugin({
               template: "./index.html",
           }),
        ],
        devServer: {
            port: 8088,
            open: true,
            host: "localhost",
            historyApiFallback: true, // 解决vue-router刷新404问题
            proxy: {
                "/api": {
                    changeOrigin: true,
                    pathRewrite: {
                        "^/api": ""
                    }
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    webpack.prod.js

    const path = require("path")
    const HtmlWebpackPlugin =  require("html-webpack-plugin");
    const {CleanWebpackPlugin} = require("clean-webpack-plugin");
    const {VueLoaderPlugin} = require("vue-loader");
    
    module.exports = {
        mode:"production",
        entry: "./main.js",
        output: {
            filename: "js/[name].[contenthash:10].js",
            path: path.resolve(__dirname, "../dist")
        },
        module: {
            rules: [
                {
                    test:/\.vue$/,
                    use: "vue-loader"
                },
                {
                    test: /\.css$/, //解析css
                    use: ["style-loader", "css-loader"],
                },
                {
                    test:/\.less/,
                    use: ["style-loader","css-loader", "less-loader"],
                }
            ]
        },
        resolve: {
            alias: {
                "@": path.resolve(__dirname, './src') // 别名
            },
            extensions: ['.js', '.json', '.vue', '.ts', '.tsx'] //识别后缀
        },
        plugins: [
            new CleanWebpackPlugin(),
            new VueLoaderPlugin(),
           new HtmlWebpackPlugin({
               template: "./index.html",
           }),
        ],
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    Vite性能优化

    打包优化

    vite.config.js 添加 build 配置项

    import { fileURLToPath, URL } from 'node:url'
    
    import { defineConfig,loadEnv } from 'vite'
    import vue from '@vitejs/plugin-vue'
    import vueJsx from '@vitejs/plugin-vue-jsx'
    import AutoImport from 'unplugin-auto-import/vite'
    import Components from 'unplugin-vue-components/vite'
    import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
    import unocss from 'unocss/vite'
    
    
    
    let {VITE_API} = loadEnv(process.env.NODE_ENV,process.cwd())
    
    console.log(VITE_API)
    
    // https://vitejs.dev/config/
    export default defineConfig({
      module:"es2022",
      plugins: [
        vue(),
        vueJsx(),
        AutoImport({
          resolvers: [ElementPlusResolver()],
          imports: ['vue', 'vue-router'],
          dts: 'src/auto-imports.d.ts'
        }),
        Components({
          resolvers: [ElementPlusResolver()],
        }),
        unocss(),
      ],
      resolve: {
        alias: {
          '@': fileURLToPath(new URL('./src', import.meta.url))
        }
      },
      css: {
        preprocessorOptions: {
          scss: {
            additionalData: "@import './src/layout_v2/css/bem.scss';"
          }
        }
      },
      build:{
        minify:"esbuild", // esbuild打包速度最快,terser 打包体积最小
        cssCodeSplit:true,// 拆分CSS文件
        chunkSizeWarningLimit:2000, // 单文件超过2000kb警告
        assetsInlineLimit:1024*10, // 静态资源文件低于10KB时自动转Base64
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    Pinia

    安装

    npm install pinia
    
    • 1

    在 main.ts 中引入

    import {createApp} from 'vue'
    import {createPinia} from 'pinia'
    
    export const app = createApp(App)
    app.use(createPinia())
    
    app.mount('#app')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    基本使用

    userInfoStore.js

    import {defineStore} from 'pinia'
    
    export const useUserInfoStore = defineStore('userInfo', {
      state: () => {
        return {
          name: "李斯特",
          age: 18
        }
      },
      getters: {
        userMsg() {
          return this.name + '---' + this.age
        }
      },
      actions: {
        setName(newName) {
          console.log(this.name)
          this.name = newName
        }
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    actions 中的函数也是支持异步的,this 指向指向的是 state 中返回的对象地址,所以可以通过this来获取到 state 中的属性值

    vue文件中使用方法

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    Pinia的一些API

    • $reset 重置数据
    • $subscribe 监听数据变化
    • $onAction 监听 action 数据变化
    import {useUserInfoStore} from "@/stores/userInfoStore";
    
    const userInfoStore = useUserInfoStore()
    
    const change = () => {
      userInfoStore.setName("张三丰")
    }
    
    // $reset 重置数据
    const reset = () => {
      userInfoStore.$reset()
    }
    
    // $subscribe 监听数据变化
    userInfoStore.$subscribe((mutation, state) =>{
      console.log(mutation, state)
    })
    
    // $onAction 监听 action 数据变化
    userInfoStore.$onAction((action, state) =>{
      console.log(action, state)
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    Pinia持久化缓存

    安装

    npm install pinia-plugin-persistedstate
    
    • 1

    配置

    import {createApp} from 'vue'
    import {createPinia} from 'pinia'
    import PiniaPluginPersistedstate from "pinia-plugin-persistedstate"
    
    export const app = createApp(App)
    // 配置Pinia并设置持久化缓存
    const Pinia = createPinia()
    Pinia.use(PiniaPluginPersistedstate)
    
    app.use(Pinia)
    app.mount('#app')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    然后在需要设置持久化缓存的pinia文件中开启persist配置

    import {defineStore} from 'pinia'
    
    export const useUserInfoStore = defineStore('userInfo', {
      state: () => {
        return {
          name: "李斯特",
          age: 18
        }
      },
      getters: {
        userMsg() {
          return this.name + '---' + this.age
        }
      },
      actions: {
        setName(newName) {
          console.log(this.name)
          this.name = newName
        }
      },
      // 开启数据持久化
      persist: true
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    效果展示

    它原理是将pinia数据保存到 localStorage 缓存中,刷新页面后优先从缓存中读取,如果缓存中没有则再从代码中读取

    Echarts展示地图

    效果图

    image-20231019201753090

    安装

    npm install echarts
    
    • 1

    默认安装的是 5.x 版本

    在这个版本中的引入方式必须是下面这种方法

    import * as echarts from 'echarts'
    
    • 1

    源码

    首先要下载好地图数据 china.js

    下载地址:https://szx-bucket1.oss-cn-hangzhou.aliyuncs.com/picgo/china.js,下载到本地使用即可

    地图实现源码

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154

    cityPostion.js 文件代码,这个文件主要是通过省份名称获取经纬度

    const positionArr = [
      { name: '北京', value: ['116.3979471', '39.9081726'] },
      { name: '上海', value: ['121.4692688', '31.2381763'] },
      { name: '天津', value: ['117.2523808', '39.1038561'] },
      { name: '重庆', value: ['106.548425', '29.5549144'] },
      { name: '河北', value: ['114.4897766', '38.0451279'] },
      { name: '山西', value: ['112.5223053', '37.8357424'] },
      { name: '辽宁', value: ['123.4116821', '41.7966156'] },
      { name: '吉林', value: ['125.3154297', '43.8925629'] },
      { name: '黑龙江', value: ['126.6433411', '45.7414932'] },
      { name: '浙江', value: ['120.1592484', '30.265995'] },
      { name: '福建', value: ['119.2978134', '26.0785904'] },
      { name: '山东', value: ['117.0056', '36.6670723'] },
      { name: '河南', value: ['113.6500473', '34.7570343'] },
      { name: '湖北', value: ['114.2919388', '30.5675144'] },
      { name: '湖南', value: ['112.9812698', '28.2008247'] },
      { name: '广东', value: ['113.2614288', '23.1189117'] },
      { name: '海南', value: ['110.3465118', '20.0317936'] },
      { name: '四川', value: ['104.0817566', '30.6610565'] },
      { name: '贵州', value: ['106.7113724', '26.5768738'] },
      { name: '云南', value: ['102.704567', '25.0438442'] },
      { name: '江西', value: ['115.8999176', '28.6759911'] },
      { name: '陕西', value: ['108.949028', '34.2616844'] },
      { name: '青海', value: ['101.7874527', '36.6094475'] },
      { name: '甘肃', value: ['103.7500534', '36.0680389'] },
      { name: '广西', value: ['108.3117676', '22.8065434'] },
      { name: '新疆', value: ['87.6061172', '43.7909393'] },
      { name: '内蒙古', value: ['111.6632996', '40.8209419'] },
      { name: '西藏', value: ['91.1320496', '29.657589'] },
      { name: '宁夏', value: ['106.2719421', '38.4680099'] },
      { name: '台湾', value: ['120.9581316', '23.8516062'] },
      { name: '香港', value: ['114.139452', '22.391577'] },
      { name: '澳门', value: ['113.5678411', '22.167654'] },
      { name: '安徽', value: ['117.2757034', '31.8632545'] },
      { name: '江苏', value: ['118.7727814', '32.0476151'] },
    ]
    
    export function getCityPositionByName(name) {
      return positionArr.find(item => item.name === name)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    Vue-Router

    安装

    npm install vue-router
    
    • 1

    安装完成后检查一下安装的版本是否是 4.x 版本,确保在 vue3 中可以使用

    image-20231022094941965

    定义路由和404

    新建 router/index.js

    import {createRouter,createWebHashHistory} from "vue-router"
    
    const router = createRouter({
        // 定义路由模式:哈希模式
        history:createWebHashHistory(),
        routes:[
            {
                path:"/",
                component:()=>import("../views/home.vue")
            },
            {
                path:"/about",
                component:()=>import("../views/about.vue")
            },
            // 匹配404页面,当所有路径都匹配不到时,就跳转到404
            {
                path: "/:pathMatch(.*)",
                component: ()=>import("../views/404.vue"),
            },
        ]
    })
    
    // 导出路由
    export default router
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    注册路由

    main.js

    import { createApp } from 'vue'
    import App from './App.vue'
    import router from "./router"
    
    const app = createApp(App)
    
    app.use(router)
    
    app.mount('#app')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    定义路由出口

    App.vue

    
    
    • 1
    • 2
    • 3

    image-20231022100705985

    路由跳转

    方式一:router-link

    home
    about
    
    • 1
    • 2

    router-link是vue-router内置的组件,通过to属性定义要跳转的地址,属性值要和路由中的 path 相对应

    方式二:通过js的方式跳转

    定义两个按钮,点击按钮实现跳转

    
    
    
    • 1
    • 2

    js方法

    import {useRouter} from "vue-router"
    
    const router = useRouter()
    
    const toPath = (url) => {
      router.push({
        path:url
      })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    控制路由返回与前进

    定义两个按钮分别实现返回和前进

    
    
    
    • 1
    • 2

    实现两个方法

    const back = () => {
      // 方式一
      // router.go(-1)
    
      // 方式二
      router.back()
    }
    
    const advance = () => {
      router.go(1)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    replace

    默认通过 push 的方式跳转会留下历史记录。如果不想留下历史记录,可以通过 replace 这种方法跳转。

    例如在登录成功后就可以使用 replace 来跳转

    在 router-link 标签上添加 replace 属性

    <router-link replace class="mr-10" to="/">homerouter-link>
    <router-link replace class="mr-10" to="/about">aboutrouter-link>
    
    • 1
    • 2

    或者通过 router.replace

    const toPath = (url) => {
      router.replace({
        path:url
      })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这种跳转方式不会留下历史记录

    路由传参

    通过添加 query 参数来实现传参

    const toPath = (url) => {
      router.push({
        path:url,
        query:{
          id:1,
          name:"李四",
        }
      })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    通过如下方法接收路由参数

    <template>
      我是详情页,接收到的路由参数是:{{route.query}}
    template>
    
    <script setup>
    import {useRoute} from "vue-router";
    
    const route = useRoute()
    
    console.log(route.query)
    
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    image-20231022102319527

    接收到到的是一个对象

    动态URL

    我们也可以将参数作为页面URL的一部分

    首先定义路由

    注意:

    这里要多定义一个参数:name,动态路由跳转时,需要通过 name 来跳转

    使用 /dyDetail/:xxx/:xxx 这种方式定义动态参数名称

    {
        path:"/dyDetail/:id/:name",
        name:"DyDetail",
        component:()=>import("../views/dyDetail.vue")
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5

    添加跳转方法

    const toDyDetail = () => {
      router.push({
        // 这里使用name来跳转,name名称也要和路由中定义的name一致
        name:"DyDetail",
        // 这里传递的属性名必须和路由中定义的参数名一致
        params:{
          id:"1",
          name:"张三"
        }
      })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    获取动态路由参数方法,通过 route.params 方法获取

    <template>
      <div>id:{{route.params.id}}div>
      <div>name:{{route.params.name}}div>
    template>
    
    <script setup>
    import {useRoute} from "vue-router";
    
    const route = useRoute()
    
    console.log(route.params)
    
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    image-20231022103152777

    这里观察地址栏中的显示方式,直接将参数获取url的一部分来显示

    路由嵌套

    定义路由

    {
        path:"/system",
        component:()=>import("../views/system/index.vue"),
        children:[
            {
                path:"menu",
                component:()=>import("../views/system/menu.vue")
            },
            {
                path:"role",
                component:()=>import("../views/system/role.vue")
            },
        ]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    system/index.vue

    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    跳转到子路由时,需要加上父路由地址

    image-20231022104829861

    重定向

    {
        path:"/system",
        // 重定向到第一个子菜单
        redirect:"/system/menu",
        component:()=>import("../views/system/index.vue"),
        children:[
            {
                path:"menu",
                component:()=>import("../views/system/menu.vue")
            },
            {
                path:"role",
                component:()=>import("../views/system/role.vue")
            },
        ]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    路由守卫

    全局前置路由守卫

    // 全局前置路由守卫
    router.beforeResolve((to,from,next)=>{
        console.log(to) // 去哪个页面
        console.log(from) // 从哪个页面来
        next() // 下一步,必须要写,否则无法跳转
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    全局后置路由守卫

    // 全局后置路由守卫
    router.afterEach((to,from)=>{
        console.log(to) // 去哪个页面
        console.log(from) // 从哪个页面来
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5

    局部路由守卫

    {
        path:"menu",
        component:()=>import("../views/system/menu.vue"),
        // 局部前置路由守卫
        beforeEnter:((to,from,next)=>{
            console.log(to,'局部前置路由守卫')
            console.log(from,'局部前置路由守卫')
            next()
        })
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    滚动行为

    import {createRouter,createWebHashHistory} from "vue-router"
    
    const router = createRouter({
        // 定义路由模式:哈希模式
        history:createWebHashHistory(),
        // 滚动模式
        scrollBehavior:(to,from,savedPosition)=>{
            if(savedPosition){
                // 如果有滚动的位置,则重新回到之前滚动的位置
                return savedPosition
            }else{
                // 否则页面滚动到顶部
                return {x:0,y:0}
            }
        },
        routes:[
            {
                path:"/",
                component:()=>import("../views/home.vue")
            },
            {
                path:"/about",
                component:()=>import("../views/about.vue")
            },
            {
                path:"/detail",
                component:()=>import("../views/detail.vue")
            },
        ]
    })
    
    // 导出路由
    export default router
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    动态路由

    在后台管理系统中常见的场景,根据不同的角色,显示不同的菜单

    编写方法,根据不同的账号名,返回不同的菜单

    export function getDynamicRouting(name){
        return new Promise((resolve,reject)=>{
            // root角色登录
            if(name === "admin"){
                resolve([
                    {
                        path:"/about",
                        component:"about.vue"
                    },
                    {
                        path:"/detail",
                        component:"detail.vue"
                    },
                    {
                        path:"/system",
                        redirect:"/system/menu",
                        component:"system/index.vue",
                        children:[
                            {
                                path:"menu",
                                component:"system/menu.vue",
                            },
                            {
                                path:"role",
                                component:"system/role.vue"
                            },
                        ],
                    },
                ])
            }
            // 普通人员登录
            if(name === "tome"){
                resolve([
                    {
                        path:"/about",
                        component:"about.vue"
                    },
                    {
                        path:"/detail",
                        component:"detail.vue"
                    },
                ])
            }
        })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    login.vue

    登录成功后根据返回的路由信息,添加路由

    <template>
      <div>
        <input placeholder="请输入账号" v-model="name"/>
        <input placeholder="请输入密码" type="password" v-model="pwd"/>
        <button @click="login">登录button>
      div>
    template>
    
    <script setup>
    import {ref} from "vue";
    import router from "../router"
    import {getDynamicRouting} from "../../mock/mockRouter.js";
    
    let name = ref("")
    let pwd = ref("")
    
    const login = () => {
      getDynamicRouting(name.value).then(routers=>{
         let dyRouter = setDyRouter(routers)
         // 只需要添加一级路由信息即可
         dyRouter.forEach(rootRouter=>{
           router.addRoute(rootRouter)
         })
      })
    }
    
    const setDyRouter = (routers,parentPath) => {
      routers.forEach(item=>{
        item.component = import(`../views/${item.component}`)
        if(!item.path.startsWith("/")){
          item.path = `${parentPath}/${item.path}`
        }
        if(item.children){
          setDyRouter(item.children,item.path)
        }
      })
      return routers
    }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    测试

    首先用admin登录,然后点击菜单管理可以正常返回

    image-20231022203258691

    然后刷新页面,使用tome登录,然后点击菜单管理发现是404

    image-20231022203424279

    上面的例子只是简单的实现了一个动态路由,实际开发中,我们会根据接口返回的路由数据渲染不同的菜单来显示

    MarkDown语法高亮

    安装

    npm install marked highlight.js --save
    or
    pnpm add marked highlight.js --save
    
    • 1
    • 2
    • 3

    注册

    import './assets/main.css'
    import { createApp } from 'vue'
    import { createPinia } from 'pinia'
    import App from './App.vue'
    import router from './router'
    import highlight from 'highlight.js'
    import "highlight.js/styles/atom-one-dark.css"
    
    const app = createApp(App)
    
    app.use(createPinia())
    app.use(router)
    
    app.directive("highlight",function(el){
      let blocks = el.querySelectorAll('pre code');
      blocks.forEach((block)=>{
        highlight.highlightBlock(block);
      })
    })
    
    app.mount('#app')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    使用

    <div v-highlight v-html='content'>div>
    
    <script>
    import { marked } from 'marked'
    const content = ref("")
    // 需要使用marked方法吧语法转成html页面
    content = marked(content)
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    效果

    image-20231028152820833

  • 相关阅读:
    (项目实战)RocketMQ5.0延迟消息在聚合支付系统中的应用
    开发工具安装
    【springboot2.0】arthas tunnel server监控配置方法
    vue骨架布局使用
    Filebeat+Kafka+ELK日志分析架构
    检查原生 JavaScript 函数是否被覆盖
    Golang实现Windows的TTS实现文本转语音
    C# 文件监听FileSystemWatcher
    从 Google 离职,前Go 语言负责人跳槽小公司
    JavaScript 变量和运算符
  • 原文地址:https://blog.csdn.net/SongZhengxing_/article/details/134092829