# 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
目的
- 嵌套 css 编写
- 支持定义变量
npm install sass
# element-plus 定制主题
- 安装 sass 基于 vite 的项目默认不支持 css 预处理器,需要开发者单独安装
npm i sass -D
- 准备定制化的样式文件
<
!
--
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,
)
,
)
)
- 自动导入配置 这里自动导入需要深入到 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
- 基础配置 基础配置通常包括:
- 实例化 - 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;
- 封装请求函数并测试
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 文件的自动导入
- 新建需要导入的公共样式文件
$xtxColor: #27ba9b;
$helpColor: #e26237;
$sucColor: #1dc779;
$warnColor: #ffb302;
$priceColor: #cf4444;
vite.config文件配置
css: {
preprocessorOptions: {
scss: {
// 自动导入定制化样式文件进行样式覆盖
additionalData: `
@use "@/styles/element/index.scss" as *;
@use "@/styles/var.scss" as *;
`,
}
,
}
,
}
,
# 解决路由缓存问题
使用带有参数得路由时需要注意得是,当用户从users/johoon导航到users/monny
时,相同得组件实例将被重复使用,因为两个路由都渲染同个组件,比起销毁再创建。复用显得更加高效。**不过这也意味着组件得生命周期钩子不会被调用
**
# 解决问题思路
- 让组件实例不复用,强制销毁重建
给
router-view添加 key
以当前路由完整路径 key 的值,给router-view组件绑定
key 属性虽然最常见的用法是跟v-for配合,他也可以用于强制替换一个元素/组件而不是复用他
key 属性使用场景- 在适当的时候触发组件生命周期钩子
- 触发过渡
<!-- 添加一个独一无二的key 破环复用机制 强制销毁重建-->
<RouterView :key="$router.fullPath"/>
- 监听路由变化,变化之后执行数据更新操作
使用
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
}
}
# 视差滚动
- 视差滚动原理:滚动条向下滚动时,元素上移,但元素的背景图下移,达到视觉上元素上移的效果
企业级项目初始化 →