0


Geeker-Admin项目跟做笔记(vue3+vite+pinia)

文章目录

一、路由配置

(一) 静态路由

1.配置路由

constroutes: RouteRecordRaw[]=[{path:'/login',name:'login',component:()=>import('../pages/login/index.vue'),meta:{requiresAuth:false,title:'登录',key:'login'}}]

RouteRecordRaw:为了规范ts的开发,增加对路由对象类型的限制

2.创建一个路由对象

const router =createRouter({history:createWebHashHistory(),
    routes,strict:false,// 切换页面,滚动到最顶部scrollBehavior:()=>({left:0,top:0})})

history:hash模式(链接地址中有一个#)

3. 暴露路由对象

exportdefault router

4. main.js中引入注册路由

import router from'./routers/index'
app.use(router).mount('#app')

5. App.vue中将路由显示出来

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

6. 子路由的设置与引入
6.1 将侧边菜单Layout组件设置为一级路由

将路由组件展示到页面

exportconstLayout=()=>import("../layout/index.vue")

6.2 设置modules文件夹,存放各路由组件路由
6.3 子路由的书写

path:路由路径
name:路由名称
redirect:路由重定向
meta:路由元信息
meta.requireAuth:是否需要权限验证
param meta.keepAlive:是否需要缓存该路由
param meta.title:路由标题
param meta.key:路由key,用来匹配权限按钮
children:二级路由

import{ RouteRecordRaw }from"vue-router";import{ Layout }from"../constant";// 常用组件模块constdashboardRouter: Array<RouteRecordRaw>=[{path:'/dashboard',component: Layout,redirect:'/dashboard/dataVisualize',meta:{title:'Dashboard'},children:[{path:'/dashboard/dataVisualize',name:'dataVisualize',component:()=>import('@/pages/dashboard/dataVisualize/index.vue'),meta:{keepAlive:true,requiresAuth:true,title:'数据可视化',key:'dataVisualize'}},{path:'/dashboard/embedded',name:'embedded',component:()=>import('@/pages/dashboard/embedded/index.vue'),meta:{keepAlive:true,requiresAuth:true,title:'内嵌页面',key:'embedded'}},]}]exportdefault dashboardRouter

6.4 在router.ts中导入所有路由

const metaRouters =import.meta.glob("./modules/*.ts",{eager:true});

如果你倾向于直接引入所有的模块(例如依赖于这些模块中的副作用首先被应用),你可以传入 { eager: true } 作为第二个参数

import.meta.glob全局导入参考文档vite官网

6.5 处理路由表

exportconstrouterArray: RouteRecordRaw[]=[]// Object.keys 返回一个所有元素为字符串的数组
Object.keys(metaRouters).forEach(item=>{
    Object.keys(<Object>metaRouters[item]).forEach((key:any)=>{
        routerArray.push(...metaRouters[item][key])})})

Object.keys方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致。

6.6 注册路由

constroutes: RouteRecordRaw[]=[...routerArray
]

(二)动态路由

1. 动态路由配置
1.1 目录
在这里插入图片描述
1.2 配置参数

* @description 动态路由参数配置简介
 * @param path ==> 菜单路径
 * @param name ==> 菜单别名
 * @param redirect ==> 重定向地址
 * @param component ==> 视图文件路径
 * @param meta ==> 菜单信息
 * @param meta.icon ==> 菜单图标
 * @param meta.title ==> 菜单标题
 * @param meta.activeMenu ==> 当前路由为详情页时,需要高亮的菜单
 * @param meta.isLink ==> 是否外链
 * @param meta.isHide ==> 是否隐藏
 * @param meta.isFull ==>是否全屏(示例:数据大屏页面)* @param meta.isAffix ==> 是否固定在 tabs nav
 * @param meta.isKeepAlive ==> 是否缓存

2. 创建路由对象(index.ts)

const router =createRouter({history:createWebHashHistory(),routes:[...staticRouter,...errorRouter],strict:false,scrollBehavior:()=>({left:0,top:0})})

二、axios的配置

1. 创建axiosCancel.ts文件,用于有pending后直接取消

1.1 声明一个Map用于存储每个请求的标识和取消函数

Map对象的好处是可以快速判断是否有重复的请求

let pendingMap =newMap<string, Canceler>()

1.2 序列化参数

根据当前请求的信息生成请求的 Key

exportconstgetPendingUrl=(config: AxiosRequestConfig)=>[
        config.method, config.url, qs.stringify(config.data), qs.stringify(config.params)].join('&')

1.3 创建AxiosCanceler类

  • 添加请求—用于把当前请求信息添加到 pendingRequest对象中
addPending(config: AxiosRequestConfig){// 在请求开始之前,对之前的请求做检查取消操作this.removePending(config)const url =getPendingUrl(config);
    config.cancelToken = config.cancelToken ||newaxios.CancelToken(cancel=>{if(!pendingMap.has(url)){// 如果pending中不存在当前请求,则添加进去
            pendingMap.set(url, cancel)}});}
  • 移除请求–检查是否存在重复请求,若存在则需要取消已发出的请求
removePending(config: AxiosRequestConfig){const url =getPendingUrl(config);if(pendingMap.has(url)){// 如果pending中存在当前请求标识,需要取消当前请求,并且移除const cancel = pendingMap.get(url)
        cancel &&cancel()
        pendingMap.delete(url)}};
  • 清空所有pending
removeAllPending(){
    pendingMap.forEach(cancel=>{
        cancel &&isFunction(cancel)&&cancel();})
    pendingMap.clear();};

其中,isFunction()是自定义的一个类型

  • 重置
reset():void{
   pendingMap =newMap<string, Canceler>()}

2. axios封装

2.1 创建一个AxiosCanceler对象

import{ AxiosCanceler }from"./helper/axiosCancel";const axiosCanceler =newAxiosCanceler()

2.2 配置config对象

const config ={// 默认请求地址baseURL:import.meta.env.VITE_API_URLas string,// 设置超时时间:500timeout: ResultEnum.TIMEOUTas number
}

默认请求地址在.evn开头的文件中

2.3 创建RequestHttp类

  • 2.3.1 创建axios实例
service: AxiosInstance;//AXIOS实例

以下步骤在构造函数public-constructor中

  • 2.3.2 实例化axios
this.service = axios.create(config)
  • 2.3.3 配置请求拦截器

客户端发送请求 - 请求拦截器 - 服务器

this.service.interceptors.request.use((config: AxiosRequestConfig)=>{const globalStore =GlobalStore();// 将当前请求添加到pending中
       axiosCanceler.addPending(config);// 如果当前请求不需要显示Loading,在api服务站通过指定的第三个参数:{headers:{noLoading:true}}来控制不显示loading
       config.headers!.noLoading ||showFullScreenLoading();//从GlobalStore仓库中获取tokenconsttoken: string = globalStore.token;return{...config,headers:{...config.headers,"x-access-token": token }};},(error: AxiosError)=>{return Promise.reject(error)});
  • 配置响应拦截器

服务器返回信息- 拦截统一处理 -客户端Js获取信息

this.service.interceptors.response.use((response: AxiosResponse)=>{const{ data, config }= response;const globalStore =GlobalStore()// console.log('reaponse', data);// 在请求结束后,移除本次请求
       axiosCanceler.removePending(config)// 关闭LoadingtryHideFullScreenLoading()//1. 登录失效(code==599)if(data.code == ResultEnum.OVERDUE){
           ElMessage.error(data.msg)// setToken为仓库的action
           globalStore.setToken('')// 跳转至登录页面
           router.replace({path:"/login"})return Promise.reject(data);}// 2.全局错误信息拦截(防止下载文件的时候返回数据流,没有code,直接报错)//后面页面请求就不用判断data.code==200if(data.code && data.code !== ResultEnum.SUCCESS){
           ElMessage.error(data.msg);return Promise.reject(data);}// 3.请求成功return data;},async(error: AxiosError)=>{const{ response }= error;tryHideFullScreenLoading();// 请求超时单独判断,因为请求超时没有reaponseif(error.message.indexOf("timeout")!==-1) ElMessage.error('请求超时,请稍后重试')// 根据响应的错误状态码,做不同的处理if(response)checkStatus(response.status);// 服务器结果都没有返回(可能服务器错误也可能服务端断网),断网处理:可以跳转到段网页面if(!window.navigator.onLine) router.replace({path:"/errorPage/500"});return Promise.reject(error)})

2.4 常用请求方法封装

get<T>(url: string, params?: object, _object ={}):
    Promise<ResultData<T>>{returnthis.service.get(url,{ params,..._object });}

post<T>(url: string, params?: object, _object ={}):
    Promise<ResultData<T>>{returnthis.service.post(url, params, _object);}

2.5 暴露RequestHttp类

exportdefaultnewRequestHttp(config)

3. 使用

以登录接口为例
3.1. 为组件的api标注类型(api–interface–index.ts)

  • 3.1.1 请求响应参数(不包含data)
exportinterfaceResult{code: string,msg: string
}
  • 3.1.2 请求响应参数(包含data)
exportinterfaceResultData<T= any>extendsResult{
    data?:T;}
  • 3.1.3 登录模块
export namespace Login {exportinterfaceReqLoginForm{username: string;password: string;}exportinterfaceResLogin{access_token: string
    }}

3.2 后端为服务器端口名(api–config–servicePort.ts)

exportconstPORT1="/geeker";

3.3 登录模块(api–modules–login.ts)

import{ Login }from"../interface";import{PORT1}from"@/api/config/servicePort";import http from"../../api"// 用户登录接口exportconstloginApi=(params:Login.ReqLoginForm)=>{return http.post<Login.ResLogin>(PORT1+`/login`,params);}

3.4 在登录组件中使用

formEl.validate(asyncvalid=>{if(!valid)return
    loading.value =truetry{constrequestLoginForm:Login.ReqLoginForm={username:loginFrom.username,password:md5(loginFrom.password)}const res=awaitloginApi(requestLoginForm)
      ElMessage.success('登录成功')}finally{
      loading.value =false}})

3.5 请求结果
在这里插入图片描述

三、pinia仓库的使用

四、Header 设计笔记

1. 国际化(中英文切换)

参考文档:Mpx框架—国际化i18n
4.1.1安装vue-i18n

npm install vue-i18n --save

4.1.2 在vite.config.ts中对vue-i18n进行配置

alias:{'vue-i18n':'vue-i18n/dist/vue-i18n.cjs.js',}

4.1.3 配置i18n

  • index.ts引入createI18n
import{ createI18n }from"vue-i18n";
  • 设置语言 (1) 中文zh.ts
exportdefault{home:{welcome:"欢迎使用"},tabs:{more:"更多",closeCurrent:"关闭当前",closeOther:"关闭其他",closeAll:"关闭所有"}}

(2)英文en.ts

exportdefault{home:{welcome:"Welcome"},tabs:{more:"More",closeCurrent:"Close current",closeOther:"Close other",closeAll:"Close All"}}
  • 将上面两个文件引入index.ts
import zh from"./modules/zh";import en from"./modules/en";
  • 配置createI18n
const i18n =createI18n({legacy:false,// 如果要支持 compositionAPI,此项必须设置为 falselocale:"zh",//设置语言类型globalInjection:true,//全局注册$t方法messages:{
        zh,
        en
    }});exportdefault i18n;

4.1.4 main.js中引入并配置i18n

import i18n from'@/language/index'const app =createApp(App)
app.use(i18n).mount('#app')

4.1.5 使用

<span>{{$t('tabs.more')}}</span>

五、Menu 设计笔记

1. Menu 组件结构

<template><div class="menu">//子组件<Logo :isCollapse="isCollapse"/><el-scrollbar><el-menu><el-menu>//子组件<SubItem :menuList="menuList"/></el-menu></el-menu></el-scrollbar></div></template>

2. 父级菜单与子菜单

5.2.1 利用pinia设计菜单仓库MenuStore

  • 设计MenuStore的ts类型MenuState
exportinterfaceMenuState{menuList: Menu.MenuOptions[];}
  • MenuStore仓库设计
import{ defineStore }from"pinia";import{ MenuState }from"../interface";import piniaPersistConfig from"@/config/piniaPersist";exportconst MenuStore =defineStore({id:"MenuStore",state:():MenuState=>({// menu listmenuList:[]}),getters:{},actions:{asyncsetMenuList(menuList: Menu.MenuOptions[]){this.menuList = menuList
        }},//开启该插件,开启数据存储persist:piniaPersistConfig("MenuState")})

id : 作为store的第一个参数,是store唯一的名称(必须!!!)
state:相当于data
geters:相当于computed
actions:相当于methods
开启持久化:persist: piniaPersistConfig(“MenuState”)

5.2.2 从MenuStore中获取menuList,并将它传递给子组件SubItem

  • 在Menu组件中引入MenuStore
import{ MenuStore }from'@/store/modules/menu';
  • 使用计算属性从MenuStore中获取menulist
const menuStore =MenuStore();const menuList =computed((): Menu.MenuOptions[]=> menuStore.menuList);
  • 将menuList传递给子组件
<SubItem :menuList="menuList"/>
  • SubItem子组件接收列表
defineProps<{menuList: Menu.MenuOptions[]}>();

tips :当前菜单列表为空

5.2.2 获取菜单列表

login.ts文件

  • 引入本地Json文件(mock.js)
import menu from'@/assets/json/menu.json'
  • 获取菜单列表
exportconstgetMenuList=()=>{return menu;}
  • 使用递归处理路由菜单,生成一维数组(util.ts)
/**
 * @description: 使用递归处理路由菜单,生成一维数组
 * @param {Array} menuList 所有菜单列表
 * @param {Array} newArr 菜单的一维数组
 * @return array
 */exportfunctionhandleRouter(routerList: Menu.MenuOptions[],newArr: string[]=[]){// console.log(routerList);
    routerList.forEach((item: Menu.MenuOptions)=>{typeof item ==="object"&& item.path && newArr.push(item.path);
        item.children && item.children.length &&handleRouter(item.children, newArr)})// console.log(newArr);return newArr;}

Menu.vue:获取菜单列表

onMounted(async()=>{// 获取菜单列表try{const res =awaitgetMenuList();
        console.log(res);if(!res.data)return;// 把路由菜单处理成一维数组(存储到pinia中)const dynamicRouter =handleRouter(res.data);
        menuStore.setMenuList(res.data);}finally{}})

5.2.3 router-view将路由组件渲染至页面

<el-main><router-view v-slot="{Component,route}"><transition appear name="fade-transform" mode="out-in"><keep-alive :include="cacheRouter"><component :is="Component":key="route.path"></component></keep-alive></transition></router-view></el-main>
  • v-slot = “{Component,route}”— 接收Props的默认插槽,并解构
  • 当使用 <component :is="..."> 来在多个组件间作切换时,被切换掉的组件会被卸载。我们可以通过 <KeepAlive> 组件强制被切换掉的组件仍然保持“存活”的状态。
  • cacheRouter — 使用递归,过滤需要缓存的路由

_route 所有路由表
_cache 缓存的路由表

import{ RouteRecordRaw, RouteRecordName }from"vue-router";import{ routerArray }from"./router";letcacheRouter: any[]=[];const filterKeepAlive =(_route: RouteRecordRaw[],_cache: RouteRecordName[]):void=>{
    _route.forEach(item=>{
        item.meta?.keepAlive && item.name && _cache.push(item.name);
        item.children && item.children.length !==0&&filterKeepAlive(item.children,_cache)})};filterKeepAlive(routerArray,cacheRouter);exportdefault cacheRouter;

cacheRouter:
在这里插入图片描述

六、tabs标签页

1. 利用pinia设计tabs仓库

6.1.1 点击左侧菜单栏添加tabs标签页

  • 不添加黑名单中的路径:如果路径包含在黑名单中,则退出方法
if(TABS_BLACK_LIST.includes(tabsItem.path))return;
  • 6.1.2 定义标签信息

title:标签名(默认值:首页)
path:标签路径(默认值:HOME_URL)
close:标签状态(标签是否关闭,默认关闭)

consttabInfo: TabsOptions ={title: tabsItem.title,path: tabsItem.path,close: tabsItem.close
};
  • 如果tabsMenuList中每一个元素的路径都等于 tabsItem.path ,则把该元素添加至tabsMenuList

Array.every:一个数组内的所有元素是否都能通过某个指定函数的测试,都通过则返回true,否则返回false

if(this.tabsMenuList.every(item=> item.path !== tabsItem.path)){this.tabsMenuList.push(tabInfo);};
  • 将路径赋值给tabsMenuValue
this.setTabsMenuValue(tabsItem.path);

完整代码:

asyncaddTabs(tabsItem: TabsOptions){// 不添加黑名单中的路径if(TABS_BLACK_LIST.includes(tabsItem.path))return;consttabInfo: TabsOptions ={title: tabsItem.title,path: tabsItem.path,close: tabsItem.close
    };if(this.tabsMenuList.every(item=> item.path !== tabsItem.path)){this.tabsMenuList.push(tabInfo);};this.setTabsMenuValue(tabsItem.path);},

6.1.2 关闭标签页

  • 循环遍历tabsMenuList
if(tabsMenuValue === tabsPath){
    tabsMenuList.forEach((item, index)=>{if(item.path !== tabsPath)return;const nextTab = tabsMenuList[index +1]|| tabsMenuList[index -1];if(!nextTab)return;
        tabsMenuValue = nextTab.path;
        router.push(nextTab.path);});}

完整代码

asyncremoveTabs(tabsPath: string){let tabsMenuValue =this.tabsMenuValue;let tabsMenuList =this.tabsMenuList;if(tabsMenuValue === tabsPath){
        tabsMenuList.forEach((item, index)=>{if(item.path !== tabsPath)return;const nextTab = tabsMenuList[index +1]|| tabsMenuList[index -1];if(!nextTab)return;
            tabsMenuValue = nextTab.path;
            router.push(nextTab.path);});}this.tabsMenuValue = tabsMenuValue;this.tabsMenuList = tabsMenuList.filter(item=> item.path !== tabsPath);},

2. Tabs.vue

6.2.1 监听路由变化

// getter函数形式watch(()=> route.path,()=>{let params ={title: route.meta.title as string,path: route.path,close:true};
        tabStore.addTabs(params)},{immediate:true})

七、数据大屏

1. 数据大屏自适应屏幕大小

7.1.1 为外层盒子添加一个ref属性

<div class="dataScreen_container"><div class="dataScreen" ref="dataScreenRef"></div></div>

7.1.2 初始化ref

const dataScreenRef = ref<HTMLElement |null>(null);

7.1.3 初始化时为外层盒子加上缩放属性,防止界面刷新时就已经缩放

onMounted(()=>{// 初始化时为外层盒子加上缩放属性,防止界面刷新时就已经缩放if(dataScreenRef.value){
    dataScreenRef.value.style.transform =`scale(${getScale()}) translate(-50%,-50%)`
    dataScreenRef.value.style.width=`1920px`;
    dataScreenRef.value.style.height=`1080px`;// 为浏览器绑定事件
    window.addEventListener("resize",resize)}})

7.1.4 根据浏览器大小推断缩放比例

constgetScale=(width =1920, height =1080)=>{let ww = window.innerWidth / width;let wh = window.innerWidth / height;return ww < wh ? ww : wh;}

7.1.5 浏览器监听resize事件

constresize=()=>{if(dataScreenRef.value){
   dataScreenRef.value.style.transform=`scale(${getScale()})translate(-50%,-50%)`}}

7.1.6 css设计

.dataScreen_container{width: 100%;height: 100%;background:url('./images/bg.png') no-repeat;background-repeat: no-repeat;background-attachment: fixed;background-position: center;background-size: 100% 100%;background-size: cover;.dataScreen{position: fixed;top: 50%;left: 50%;z-index: 999;display: flex;flex-direction: column;overflow: hidden;transition: all 0.3s;transform-origin: left top;}}

2. 常用EChart资源

EChart官网、MCChart、PPChart、isqqw


本文转载自: https://blog.csdn.net/weixin_44085020/article/details/126558216
版权归原作者 _Cecilia 所有, 如有侵权,请联系我们删除。

“Geeker-Admin项目跟做笔记(vue3+vite+pinia)”的评论:

还没有评论