# Vue3 基础

# VueRouter 路由配置

# 安装路由

npm install vue-router@4

安装完成在项目 src 目录下新建一个 router 文件和 views 文件夹,在 router 下建立一个 index.ts
在 views 文件夹新建立 HomePage.vue 和 TestPage.veu 文件 内容

import {createRouter, createWebHistory} from "vue-router";

const router = createRouter({
    history: createWebHistory(),
    routes: [
        {
            name: "home",
            path: "/",
            component: () => import("../views/HomePage.vue"),
        },
        {
            name: "test",
            path: "/test",
            component: () => import("../views/TestPage.vue"),
        },
    ],
});
export default router;

#main.ts 文件中导入

import router from "./router/index";

createApp(App).use(router).mount("#app");

# 挂载倒 App.vue 文件中


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

# 安装 sass

目的

  1. 嵌套 css 编写
  2. 支持定义变量
npm install sass

# element-plus 定制主题

  1. 安装 sass 基于 vite 的项目默认不支持 css 预处理器,需要开发者单独安装
npm i sass -D
  1. 准备定制化的样式文件
<
!
--

styles

/element/
index

.
scss文件--

>


/* 只需要重写你需要的即可 */
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
  $colors: (
'primary'
:
(
/
/
主色

'base'
:
#27ba9b,

)
,
'success'
:
(
// 成功色
'base'
:
#1dc779,

)
,
'warning'
:
(
// 警告色
'base'
:
#ffb302,

)
,
'danger'
:
(
// 危险色
'base'
:
#e26237,

)
,
'error'
:
(
// 错误色
'base'
:
#cf4444,

)
,
)
)
  1. 自动导入配置 这里自动导入需要深入到 elementPlus 的组件中,按照官方的配置文档来
  • 自动导入定制化样式文件进行样式覆盖
  • 按需定制主题配置 (需要安装 unplugin-element-plus)
import {fileURLToPath, URL} from "node:url";
import {defineConfig} from "vite";
import vue from "@vitejs/plugin-vue";
// 配置element-plus 按需引入
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(),
        // 配置插件
        AutoImport({
            resolvers: [ElementPlusResolver()],
        }),
        Components({
            //配置element-plus采用sass样式配色系统
            resolvers: [ElementPlusResolver({importStyle: "sass"})],
        }),
    ],
    resolve: {
        alias: {
            "@": fileURLToPath(new URL("./src", import.meta.url)),
        },
    },
    css: {
        preprocessorOptions: {
            scss: {
                // 自动导入定制化样式文件进行样式覆盖
                additionalData: `
          @use "@/styles/element/index.scss" as *;
        `,
            },
        },
    },
});

# axios 的安装

npm i axios

官方文档地址 (opens new window)

  1. 基础配置 基础配置通常包括:
  • 实例化 - baseURL + timeout
  • 拦截器 - 携带 token 401 拦截等
// utils/http.js 文件夹下

import axios from "axios";

// 创建axios实例
const http = axios.create({
    baseURL: "http://pcapi-xiaotuxian-front-devtest.itheima.net",
    timeout: 5000,
});

// axios请求拦截器
instance.interceptors.request.use(
    (config) => {
        return config;
    },
    (e) => Promise.reject(e)
);

// axios响应式拦截器
instance.interceptors.response.use(
    (res) => res.data,
    (e) => {
        return Promise.reject(e);
    }
);

export default http;
  1. 封装请求函数并测试
import http from "@/utils/http";

export function getCategoryAPI() {
    return http({
        url: "home/category/head",
    });
    // httpinstance.get('home/category/head')
}

注意

如果项目里具有多个请求根路径时
axios.create()方法可以执行多次,每次执行就会生成一个新的实例

const http1 = axios.create({baseURL: "url1"});
const http2 = axios.create({baseURL: "url2"});

# eslint 配置不强制要求组件命名

// .eslintrc.cjs 文件夹下

/* eslint-env node */
module.exports = {
    root: true,
    extends: ["plugin:vue/vue3-essential", "eslint:recommended"],
    parserOptions: {
        ecmaVersion: "latest",
    },
    //配置eslint 规则
    rules: {
        "vue/multi-word-component-names": 0, //不再强制要求组件命名
    },
};

# sass 文件的自动导入

  1. 新建需要导入的公共样式文件
$xtxColor: #27ba9b;
$helpColor: #e26237;
$sucColor: #1dc779;
$warnColor: #ffb302;
$priceColor: #cf4444;
  1. vite.config 文件配置
  css: {
    preprocessorOptions: {
        scss: {
            // 自动导入定制化样式文件进行样式覆盖
            additionalData: `
        @use "@/styles/element/index.scss" as *;
         @use "@/styles/var.scss" as *;
        `,

        }
    ,
    }
,
}
,

# 解决路由缓存问题

使用带有参数得路由时需要注意得是,当用户从users/johoon导航到users/monny 时,相同得组件实例将被重复使用,因为两个路由都渲染同个组件,比起销毁再创建。复用显得更加高效。**不过这也意味着组件得生命周期钩子不会被调用 **

# 解决问题思路

  1. 让组件实例不复用,强制销毁重建 router-view添加 key
    以当前路由完整路径 key 的值,给router-view组件绑定
    key 属性虽然最常见的用法是跟v-for配合,他也可以用于强制替换一个元素/组件而不是复用他
    key 属性使用场景
    • 在适当的时候触发组件生命周期钩子
    • 触发过渡
<!-- 添加一个独一无二的key 破环复用机制 强制销毁重建-->
<RouterView :key="$router.fullPath"/>
  1. 监听路由变化,变化之后执行数据更新操作 使用onBeforeRouteUpdate钩子函数,可以在每次路由更新之前执行,在回调函数中执行需要数据更新的业务逻辑即可
    onBeforeRouteUpdate导航守卫 它也可以取消导航
import {onBeforeRouteUpdate} from "vue-router";
//目标:路由参数发生变化时 可以把分类数据接口重新发送
onBeforeRouteUpdate((to) => {
    console.log("路由变化了,最新的路由参数", to);
    //存在问题:使用最新的路由参数请求最新的分类数据
    getCategory(to.params.id);
});

# 路由切换时回到顶部

不同路由切换时,可以自动滚动到页面的顶部,而不是停留在原来的位置

# 如何配置

vue-router支持scrollBehavior配置项,可以指定路由切换时的滚动位置

// router/index.js
import {createRouter, createWebHistory} from "vue-router";

const router = createRouter({
    history: createWebHistory(import.meta.env.BASE_URL),
    routes: [
        {
            path: "/",
            component: () => import("@/views/Layout/index.vue"),
        },
        {
            path: "/login",
            component: () => import("@/views/Login/index.vue"),
        },
    ],
    //路由滚动行为配置
    scrollBehavior() {
        return {
            top: 0,
        };
    },
});

export default router;

# elementPlus 自定义校验规则

const rules = {
    account: [{required: true, message: "用户名不能为空!", trigger: "blur"}],
    password: [
        {required: true, message: "密码不能为空!", trigger: "blur"},
        {max: 14, min: 6, message: "密码必须为6~14个字符", trigger: "blur"},
    ],
    agree: {
        //自定义校验逻辑
        //value:当前输入的数据
        // callback 校验处理函数 校验通过调用
        validator: (rule, value, callback) => {
            //通过校验直接调回调函数
            if (value) callback();
            //未通过校验显示提示语 new Error()
            else callback(new Error("请勾选同意协议"));
            console.log(rule, value);
        },
    },
};

# pinia 数据持久化插件

# 安装

npm i pinia-plugin-persistedstate

# 使用

main.js

//引入持久化存储插件
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
//挂载插件
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);

store.js 中配置

import {defineStore} from "pinia";

export const useUserStore = defineStore("user", () => {
}, {
    //配置
    persist: true,
});

# vue3 配置自动导入文件后缀

vite.config.js文件配置

import {fileURLToPath} from 'url'
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import eslintPlugin from 'vite-plugin-eslint'
// https://vitejs.dev/config/
export default defineConfig({
    plugins: [vue()],
    resolve: {
        // 负责把`@`符转化为 src
        alias: {
            '@': fileURLToPath(new URL('./src', import.meta.url)),
        },
        //配置文件后缀自动检索 自动导入 .vue .js .ts 文件后缀
        extensions: ['.vue', '.js', '.ts'],
    },

    server: {
        host: 'localhost',
        port: 9999,
        open: true,
    },
})

# 瀑布流函数的封装

  • 瀑布流要点在于,每一行的宽度是固定的,但高度是随着内容的高度而变化的。
  • 因此要动态计算每一个img标签的位置
//瀑布流插件
import {getMax, getMin, debounce} from '../utils.ts'
import type {Ref} from 'vue'
import {ref, computed, createApp, nextTick} from 'vue'
import AComponent from '../components/AComponent.vue'

type WaterfallFlowConfig = {
    pageSize?: number
    pageNum?: number
    total?: number
    imageWidth?: number
}

export const useWaterfallFlow = (el: Ref<HTMLElement>, config?: WaterfallFlowConfig) => {
    //分页数据
    const pageSize = config?.pageSize || 10
    const pageNum = ref(config?.pageNum || 1)
    const total = config?.total || 100
    //图片宽度
    const imageWidth = config?.imageWidth || 200
    //界面变化的处理函数
    const resizeChange = debounce(() => {
        refresh()
    }, 1000)
    window.onresize = resizeChange
    //当前请求多少数据
    const getCount = computed(() => {
        if (pageNum.value * pageSize > total) return total - (pageNum.value - 1) * pageSize
        return pageSize
    })
    //元素数组
    const elements = ref([])
    //创建元素的处理函数
    const createElements = () => {
        console.log(pageSize, pageNum.value, total)
        console.log('煤业', getCount.value)
        if (getCount.value === 0) throw new Error('没有数据')
        const elArr = new Array(getCount.value).fill(0)
        for (let i = 0; i < getCount.value; i++) {
            const elDiv = document.createElement('div')
            elDiv.className = 'container-box'
            const component = createApp(AComponent, {
                src: `https://picsum.photos/${imageWidth}/${Math.floor(Math.random() * (2 - 8) + 8) * 100}?random=${i}`
            })
            component.mount(elDiv)
            elArr[i] = elDiv
            el.value?.appendChild(elDiv)
        }
        elements.value = [...elements.value, ...elArr]
        pageNum.value++
        initArr(elArr)
    }
    //布局辅助数组
    const layOutArr = ref<number[]>([])
    //布局数据
    const layData = ref({
        singleGap: 0,
        column: 0
    })
    //计算元素布局的处理函数
    const layout = () => {
        //大盒子宽度
        const containerWidth = el.value?.offsetWidth || 3
        //根据大盒子宽度获取应该排几列
        const column = Math.floor(containerWidth / imageWidth)
        //算出空隙数量 = 列数 - 1
        const gapCount = column - 1
        //总共的空隙宽度
        const freeSace = containerWidth - imageWidth * column
        //横向单个空隙的宽度
        const singleGap = freeSace / gapCount
        layData.value = {
            singleGap,
            column
        }
        layOutArr.value = new Array(column).fill(0)
    }
    //重新摆放元素位置的处理函数
    const initArr = async (elArr: any[]) => {
        await nextTick()
        const {singleGap} = layData.value
        elArr.forEach((elelmet: HTMLElement) => {
            const {min, index} = getMin(layOutArr.value)
            elelmet.style.left = `${index * (imageWidth + singleGap)}px`
            elelmet.style.top = `${min + singleGap / 2}px`
            layOutArr.value[index] += elelmet.offsetHeight + singleGap / 2
            el.value.style.height = getMax(layOutArr.value) + 'px'
        })
    }
    //刷新界面的处理函数
    const refresh = () => {
        //计算布局
        layout()
        //摆放元素
        initArr(elements.value)
    }
    //页面开始加载的处理函数
    const onLoad = () => {
        //计算布局
        layout()
        //创建元素
        createElements()
    }
    return {
        refresh,
        onLoad,
        createElements
    }
}

# 视差滚动

  • 视差滚动原理:滚动条向下滚动时,元素上移,但元素的背景图下移,达到视觉上元素上移的效果
上次更新: 4/25/2024, 3:42:57 PM