前言
- 抽空整理練手項(xiàng)目升級(jí)后的一些坑點(diǎn); 各位項(xiàng)目中有問題遇到困難也可以留言;我會(huì)盡快回復(fù)! 后續(xù)還會(huì)繼續(xù)更新問題點(diǎn)!
1. 預(yù)處理器
安裝sass以及配置全局scss
npm install node-sass
npm install sass-loader
node 版本不能過高; 目前成功版本為
10.15.0; 可通過安裝"n"來管理node版本
配置全局mixin.scss
根目錄找到vite.config.js
// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';
// https://vitejs.dev/config/
export default defineConfig({
...
plugins: [
vue(),
],
resolve: {
// 配置別名
alias: [
{ find: '@', replacement: resolve(__dirname, './src') },
{ find: 'views', replacement: resolve(__dirname, './src/views') },
],
},
css: {
// 添加全局mixin.scss
preprocessorOptions: {
scss: {
additionalData: '@import "@/style/libs.scss";',
},
},
},
});
scss參與計(jì)算 (內(nèi)置模塊)
原來的寫法直接計(jì)算是不行的, 需要使用內(nèi)置模塊
math.div()方法參與計(jì)算
//libs.scss
//使用math 需要先聲明使用; 添加在文件頂部
@use "sass:math"
// 原來的寫法
border-width: $w/2;
// 新寫法
border-width: math.div($w/2)
新寫法; 切記math需要聲明使用(https://sass-lang.com/documentation/modules); 不然就會(huì)提示:
Error: There is no module with the namespace "math".
2. require 批量引入
-
問題: 想引入某個(gè)目錄下的所有文件? 下面例子
// 批量引入.vue
const file = require.context('@/views/', true, /\.vue$/)
// 批量引入路由
const asyncFiles = require.context('./permissionModules', true, /\.ts$/)
let permissionModules: Array<RouteRecordRaw> = []
asyncFiles.keys().forEach((key) => {
if (key === './index.ts') return
permissionModules = permissionModules.concat(asyncFiles(key).default)
})
如果提示找不到名稱“require”。是否需要為節(jié)點(diǎn)安裝類型定義? 請(qǐng)嘗試使用
npm i --save-dev @types/node,然后將 “node” 添加到類型字段。
解決辦法:
按照提示安裝庫
npm i --save-dev @types/node
然后在tsconfig.json types字段中添加node; 然后重啟一下; 就行了
// tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": false,
"jsx": "preserve",
"moduleResolution": "node",
"types": [
"vite/client",
"node",
],
...
}
}
}
3.別名配置
-
問題: 當(dāng)你使用別名@, 你會(huì)發(fā)現(xiàn)別名框架并沒有配置 會(huì)出現(xiàn)提示
找不到模塊“@/router/index”或其相應(yīng)的類型聲明
import router from '@/router/index';
解決辦法:
在vite.config.js 配置別名
// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
],
resolve: {
// 配置別名
alias: [
{ find: '@', replacement: resolve(__dirname, './src') },
{ find: 'views', replacement: resolve(__dirname, './src/views') },
],
},
css: {
// 添加全局mixin.scss
preprocessorOptions: {
scss: {
additionalData: '@import "@/style/libs.scss";',
},
},
},
});
很多人以為這樣就結(jié)束了;在js 文件中確實(shí)就已經(jīng)可以用了; 但是在.vue 文件中還是不行,其實(shí)還需要在tsconfig.json里面配置對(duì)應(yīng)的別名; 詳情見http://www.itdecent.cn/p/1477b68b2d69
// tsconfig.json
{
"compilerOptions": {
...
"paths": {
"@/*":[ "./src/*" ],
"views/*":[ "./src/views/*" ],
}
}
}
4.pinia 使用
注釋:pinia跟vuex一樣也是一個(gè)基于vue的狀態(tài)管理庫;使用pinia時(shí),涉及到扁平架構(gòu)可以理解為它每一個(gè)store 都是動(dòng)態(tài)的、獨(dú)立的;刪除了mutations,更好的支持typescript。搭配TS一起使用時(shí)有靠譜的類型推斷支持。
pinia 安裝:
npm install pinia
or
yarn add pinia
// src/store/index.ts
import {createPinia} from "pinia"
const pinia = createPinia()
export default pinia
//main.ts
....
import pinia from "./store/index"
createApp(App).use(router).use(pinia).mount('#app')
定義store
// src/store/modules/user/index.ts
import {defineStore} from "pinia"
impor {UserInfoType} from "./type"
import {userInfo} from "@/server/user"
// 封裝的路由
// defineStore的第一個(gè)參數(shù)是應(yīng)用程序中 store 的唯一id;這些注解官網(wǎng)都有
const userStore = defineStore('userStore',{
// 類似于data
state:()=>({
user: {} as UserInfoType
}),
// 類似于computed
getters:{
getUser: state=>state.user,
...
},
// 沒有mutations!!
//actions類似于組件中methods, 可以定義一些業(yè)務(wù)邏輯; 或者復(fù)雜的$patch
actions: {
async getUserInfo () {
const {data} = await userInfo()
// this.user = data
或者
this.$patch(state=>{
state.user = data
})
}
}
})
export userStore
使用useStore
// src/layout/index.vue
<script lang="ts" setup>
import {userStore} from "@/store/***/user"
import {storeToRefs} from "pinia"
const store = useStore()
// 使參數(shù)變?yōu)轫憫?yīng)式 ,如果不解構(gòu)直接使用useStore() 也是響應(yīng)式的。
// storeToRefs()只針對(duì)解構(gòu)后需要數(shù)據(jù)保持響應(yīng)式狀態(tài)。
const {user} = storeToRefs(store)
</script>
<template>
<div class="info">{{user.name}}</div>
</template>
狀態(tài)更新的幾種方式
// .vue
<script lang="ts" setup>
import {storeToRefs} from "pinia"
import {userStore} from "@/store"
const store = userStore()
// 改變?yōu)轫憫?yīng)式數(shù)據(jù)
const {user} = storeToRefs(store)
const handleChangeStore = ()=>{
// 方式一: 最簡單的方法,
// 如果修改多個(gè)參數(shù)不推薦,意味著會(huì)多次提交,影響性能
userStore().$state.name = 'zhangsi'
userStore().$state.sex = 2
user.phone = 1234
// 方式二: 如果需要修改多個(gè)數(shù)據(jù),建議使用$patch批量更新,
store.$patch({
user: {
name: 'zhangsan',
sex: 2,
phone:456
},
remark: '備注1233'
})
// 方式三: $patch 一個(gè)函數(shù),最推薦的批量更新方式,
// 如果只更改一個(gè),可以使用第一種方式
store.$patch((state)=>{
state.user.name = 'lisi'
state.remark = '更新備注'
})
// 第四種: 使用actions,
// store/module/user/index.ts
export const userStore = defineStore('userStore', {
// 類似于data,定義數(shù)據(jù)最初始狀態(tài)
state:() => {
return {
...
}
},
// 類似于組件的computed,用于封裝計(jì)算屬性,有緩存的功能
getters: {},
// 類似于組件methods, 用與封裝業(yè)務(wù)邏輯,state修改等等
actions:{
getContent(){
// action中可以通過this訪問整個(gè)store事例
console.log(this.content)
},
async user(){ // 異步
let result = await userInfo()
this.user = result.data
}
//第四種更新方式: 邏輯比較多的時(shí)候建議使用actions 做處理
changeState(){
this.user.name = 'zhangsan'
// 一次性修改多個(gè)參數(shù)
this.$patch({
user: {
name:'123'
....
}
})
this.$patch(state=>{
state.user.name = '1212'
....
})
}
}
})
</script>
頁面更新后提示報(bào)錯(cuò)信息:
問題: Uncaught Error: [??]: getActivePinia was called with no active Pinia. Did you forget to install pinia?
什么意思呢? 你當(dāng)前正在使用pinia; 但是pinia 還沒有掛載成功!!!! what? 我使用vuex就不會(huì)這樣呀!!!! 戲精
解決方案:
無意間在廖雪峰的官網(wǎng)看到過一句評(píng)論!!所謂的對(duì)象、函數(shù)在瀏覽器內(nèi)存中就是一個(gè)地址;找個(gè)地址只想某個(gè)函數(shù)或者對(duì)象或者方法、誰拿到這個(gè)地址就擁有操作的權(quán)利; 所謂的函數(shù)作為參數(shù)傳入別的函數(shù); 其實(shí)就是把地址給了它; 讓它擁有的操作的權(quán)利! 我覺得挺精辟!!
使用時(shí)引入pinia
// src/store/index.ts
import {createPinia} from "pinia"
const pinia = createPinia()
export default pinia
//_______________分割線__________________ //
//src/store/asyncRoute.ts
import routes from "@/mockjs/mock.routes"
// 路由元信息
import useStore from "@/store/createRouteInfo"
// 封裝的路由
import pinia from "./index"
// 引入pinia
const store = useStore(pinia) // 把pinia 傳進(jìn)去 就可以任意任意使用里面的屬性了
6.Vue-router
6.1.動(dòng)態(tài)路由遇到的問題合集 (后面附有完整動(dòng)態(tài)路由案例)
-
問題:
Invalid route component at extractComponentsGuards ?這個(gè)問題不陌生吧!! 有倆個(gè)因素;
1. 如果你用
JSON.stringify()轉(zhuǎn)為string 后存儲(chǔ); 因?yàn)?code>JSON.stringify()在序列化的過程中function、undefined、symbol這幾種類型是不可枚舉屬性; 序列化后這個(gè)鍵值對(duì)會(huì)消失;addRoute()的時(shí)候就會(huì)發(fā)現(xiàn)沒有這個(gè)component.
2. 使用
component:()=>import('views/Order/index.vue')懶加載方式引入;也會(huì)提示Invalid route component;
tips: 調(diào)試路由時(shí)可通過router.getRoutes(); 查看動(dòng)態(tài)路由是否已經(jīng)完整注入進(jìn)去
解決辦法1. 使用Glob Import 動(dòng)態(tài)導(dǎo)入
// src/mockjs/mock.routes.ts
export default routes = [
{
path: '/',
name: 'home',
component: 'views/Home/index.vue',
meta: {
// 頁面標(biāo)題
title: '首頁',
// 身份認(rèn)證
userAuth: false,
},
},
]
// src/router/index.ts
import routes from "@/mockjs/mock.routes"http:// 路由元信息
// 不推薦這種用法,這里只是為了踩坑,
const modules = import.meta.golb("../views/**/**.vue") //使用golb ++
routes.forEach(item=>{
router.addRoute("home", {
path: item.path,
name: item.name,
meta: ...item.meta,
component:
//本地能使用,上生產(chǎn)直接GG
//()=>import(/* @vite-ignore */ `/@views/${itemRouter.component}`),
//使用modules
modules[/* @vite-ignore */ `../views/${item.component}`],
})
})
解決辦法2 : 在聲明路由數(shù)據(jù)時(shí)使用 RouteRecordRaw; 下面是RouteRecordRaw的注解
當(dāng)用戶通過 routes option 或者 router.addRoute() 來添加路由時(shí),可以得到路由記錄。 有三種不同的路由記錄:
- 單一視圖記錄:有一個(gè) component 配置
- 多視圖記錄 (命名視圖) :有一個(gè) components 配置
- 重定向記錄:沒有 component 或 components 配置,因?yàn)橹囟ㄏ蛴涗浻肋h(yuǎn)不會(huì)到達(dá)。
// src/mockjs/mock.routes.ts
export default routes:RouteRecordRaw[] = [
{
path: '/',
name: 'home',
component: component: () => import("views/Home/index.vue"),
meta: {
// 頁面標(biāo)題
title: '首頁',
// 身份認(rèn)證
userAuth: false,
},
},
]
// src/router/index.ts
import routes from "@/mockjs/mock.routes"
// 路由元信息 確保自己的地址是否正確
routes.forEach(item=>{ router.addRoute(item}) })
完整的動(dòng)態(tài)路由權(quán)限
很多人做動(dòng)態(tài)路由時(shí) 數(shù)據(jù)存在
pinia;頁面刷新后頁面空白或者進(jìn)入404頁面; 并且使用router.getRoutes() 查看新增的路由已經(jīng)消失; 是因?yàn)閟tore是存在瀏覽器的堆棧內(nèi)存里面; 刷新后頁面數(shù)據(jù)被釋放掉; 那么如何解決呢? 想一想 頁面刷新后我們的框架是會(huì)重新掛載的, 思考??,是不是可以在路由權(quán)限里面訪問actionsaddRoute()
公開路由和權(quán)限路由一般我會(huì)分開,方便維護(hù)
// src/router/permissions.ts
import { permissionRoutes } from "@/router"
import pinia, { userStore } from "@/store/index"
import { ElMessage } from 'element-plus';
//路由實(shí)例
import router from "./index"
import { RouteLocationNormalized } from "vue-router"
// 白名單
import { routeWhitelist } from "@/config/whitelist"
const store = userStore(pinia)
router.beforeEach(async (to: RouteLocationNormalized, from: RouteLocationNormalized, next: any) => {
// 防止token未獲取到就執(zhí)行或者取到一個(gè)失效的token
let meta = to.meta, token = await store.getToken();
//登錄后
if (!!token) {
// 如果當(dāng)前是登錄頁
if (to.path === '/login') {
next({ path: '/' })
} else {
if (store.$state.routeModules.length === 0) {
try {
// 獲取用戶信息
const { result } = await store.getUsers()
// 當(dāng)前地址需要登錄 && 權(quán)限為admin
if (result?.roles === 'admin') {
//添加動(dòng)態(tài)路由
store.$patch(state => {
state.routeModules = permissionRoutes
})
// 添加異步就是跟token一樣的道理, 這是為什么會(huì)404或者白屏的原因
await store.addRouteModules()
next({...to, replace: true})
} else {
if (result?.roles === 'client') {
ElMessage.warning("普通用戶暫不開放管理權(quán)限");
return
}
// 如果當(dāng)前路由不是登錄頁,則執(zhí)行跳轉(zhuǎn)到登錄頁面
if (routeWhitelist.indexOf(to.path) !== -1 || to.name == 'NotFound') {
next()
} else {
next({ path: `/login?redirect=${to.path}`, replace: true })
}
}
} catch (error) {
next(`/login?redirect=${to.path}`)
}
} else {
next()
}
}
} else {
// 如果是前往登陸就正常跳轉(zhuǎn); 不是則強(qiáng)制到登陸頁
if (to.path == '/login') {
next()
} else {
// 是否需要登錄
if ('userAuth' in meta && !meta.userAuth) {
next()
} else {
next({ path: `/login?redirect=${to.path}`, replace: true })
}
}
}
})
router.afterEach((to: RouteLocationNormalized) => {
// title
document.title = to.meta.title.toString()
})
// store/user/index.ts
import {RouteRecordRaw} from "vue-router"
import { loginType, UserInfoType } from './type';
import { toRaw } from '@vue/reactivity';
const userStore = defineStore('userStore', {
state: () => ({
routeModules: [] as RouteRecordRaw [] , // route
userInfo: {} as UserInfoType,
token: '', // 單個(gè)數(shù)據(jù)ts會(huì)參與類型推斷,除非你手欠中途強(qiáng)制改類型,
...
}),
getters: {
...
},
actions: {
// token 獲取
async getToken() {
return this.token = this.token ? this.token : localStorage.getItem(Storage.TOKEN) || null
},
// 用戶信息獲取
async getUsers() {
const res = await getUserProfile()
if (res?.code === 200) {
const { result } = res
this.userInfo = result
LocalStorage.set(Storage.USER, JSON.stringify(result))
}
return res
},
// 設(shè)置路由權(quán)限
async addRouteModules() {
toRaw(this.routeModules).forEach(el => {
router.addRoute(el)
})
// console.log(router.getRoutes())
}
},
})
export default userStore
// main.js
import router from "./router/index"
.....
import "./router/permissions"
.....
createApp(App).use(router).use(pinia).mount('#app')