为了扩展自己技术层面的知识,我逐步学习vue3+node相关技术依赖,逐步搭建前后分离项目框架。废话不多讲,开始我的学习历程
一、技术架构
1.前端
vue3+ElementPlus+socket.io
2.后端
node+express+socket.io+mongoDB
二、前端搭建
1.使用vue-cli去创建vue3项目基础架构(小白可以自学其他博主的搭建方式)
本人使用的语法糖是组合式api,根据个人自己习惯去写
2.做自己的项目肯定从登录开始,上才艺。
(1)做好路由守卫
-
看一下接口返回的路由数据结构
小常识:
一般路由会有鉴权;登录后会返回相应的路由此时要做缓存处理可以放到store里面也可以放到localStore中;放在Store中要做好刷新后数据丢失解决的方案,本人建议两者都用,刷新后可以使用localStorage进行补充到Store中。
-
由于考虑到后期要增加菜单所以加入了动态菜单想法,请看关键代码
在router/index.ts中
import { RouteRecordRaw, createRouter, createWebHashHistory } from 'vue-router';
import { parse } from "zipson";
const modules = import.meta.glob("../views/**/**.vue"); // 用于引入动态路由
const { menus } = parse(localStorage.getItem("SPTIH_verify_info") || "") || { menus: [] };
const routes: Array<RouteRecordRaw> = [
{
path: "/404",
name: "404",
component: () => import("@/views/404/404.vue")
},
{
path: "/login",
name: "login",
component: () => import("@/views/login/login.vue")
},
{
path: "/demo",
name: 'demo',
component: () => import("@/views/demo/demo.vue")
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
let HomeRouter = {
path: "/",
redirect: '/home',
component: () => import('@/views/page/index.vue'),
children: [
{
path: '/home',
name: "home",// 与页面name一致,则可渲染同名页面
component: () => import('@/views/home/home.vue'),
},
]
}
// 递归生成菜单路由
function ReconstructionRoutes(MenusRoutes: any) {
let list: any = []
if (MenusRoutes.length > 0) {
MenusRoutes.forEach((item: any) => {
let routeItem = {
path: item.menuPath,
name: item.code,
// 使用/src是因为在引入动态路由的时候webpack或其他编译工具不能识别
component: modules[`../views/${item.menuComponent}.vue`], // 引入动态路由写法
children: []
}
if (item.children && item.children.length > 0) {
routeItem.children = ReconstructionRoutes(routeItem.children)
}
list.push(routeItem)
});
}
return list
}
// 重构路由
menus.forEach((menuItem: any) => {
let routeItem: any = {
path: menuItem.menuPath,
name: menuItem.code,
// 使用/src是因为在引入动态路由的时候webpack或其他编译工具不能识别
component: modules[`../views/${menuItem.menuComponent}.vue`], // 引入动态路由写法
children: []
}
if (menuItem.children && menuItem.children.length > 0) {
routeItem.children = ReconstructionRoutes(routeItem.children)
}
HomeRouter.children.push(routeItem)
})
router.addRoute(HomeRouter)
export default router;
(2)菜单展示效果图
3.有同学要问你的菜单icon显示问题
这个菜单icon想让他动态起来也不是很难,从数据结构中不难看出,本座存储的是字符串;为什么是字符串,给你们5分钟思考时间。
菜单路由icon要求的是icon对象实例,将实例存储到数据库中也不是不行,首先是要转换成字符串在存储,用的时候在转成对象实例;在开发过程中耗费时间精力比较大,是可以提现你的开发能力,但是从时间成本上浪费了你大量时间。因此,存储成字符串让icon跟着项目走,就算换依赖或者在线地址也可以同步匹配。
- 易用性:通俗易懂存储的图标标识,方便存储和使用
- 解构性:菜单icon来源可以是在线也可以是ElementPlus-icon的可以将图标都导出到js文 中,根据字符串比配对应的icon实例,存储到菜单路由数据结构中。
- 原因解释完上关键代码,一个是导入icon的public.ts文件,本座将它放到了store中项目启动进行缓存也不用担心icon丢失;一个是动态渲染SideMenu.vue
import * as Icons from '@element-plus/icons-vue'
import { defineStore } from 'pinia'
// 定义类型
interface IconsStoreType {
icons: any
}
export const useIconsStore = defineStore({
id: 'iconStore',
state: (): IconsStoreType => ({
icons: Icons
}),
})
<template>
<el-menu
class="el-menu-vertical-demo my-side-menu main_theme"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b"
:default-active="vmData.defActive"
:collapse="isCollapse"
:router="true"
@open="handleOpen"
@close="handleClose"
>
<template v-for="item in vmData.menuList" :key="item.menuPath">
<el-sub-menu
v-if="item.menuChild && item.menuChild.length > 0"
:index="item.menuPath"
:router="true"
>
<template #title>
<template v-if="item.icon">
<el-icon>
<component :is="item.icon" style="width: 16px; height: 16px" />
</el-icon>
<span>{{ item.name }}</span>
</template>
</template>
</el-sub-menu>
<el-menu-item v-else :index="item.menuPath">
<template v-if="item.icon">
<el-icon>
<component :is="item.icon" style="width: 16px; height: 16px" />
</el-icon>
</template>
<template #title> {{ item.name }}</template>
</el-menu-item>
</template>
</el-menu>
</template>
<script setup lang="ts">
import { ref, shallowRef } from "vue";
import { useRouter } from "vue-router";
import { icons } from "@/store/modules";
import { parse } from "zipson";
const router = useRouter();
const MenusIcons: any = icons;
const { menus } = parse(localStorage.getItem("SPTIH_verify_info") || "");
const vmData = ref({
defActive: "",
menuList: [
{
menuChild: [],
menuPath: "",
icon: "",
name: "",
},
],
});
defineProps<{
isCollapse: Boolean;
}>();
// 初始化菜单
function initMenus() {
if (menus.length > 0) {
for (let i = 0; i < menus.length; i++) {
for (let key in MenusIcons.value) {
if (menus[i].icon && menus[i].icon == key) {
menus[i].icon = shallowRef(MenusIcons.value[key]);
}
}
}
vmData.value.defActive = menus[0].menuPath;
router.push(vmData.value.defActive);
}
vmData.value.menuList = menus;
}
initMenus();
// 打开菜单
function handleOpen() {}
// 关闭菜单
function handleClose() {}
/**************************************接口*************************************/
</script>
4.总结
前端这部分基础目录框架及package.json附到结尾,给同学们看一下,可以先创建基础框架练习,将数据写成mock形式把菜单等效果做出来。本座还没想好如何讲解后端,待我想想。