vue+Nodejs+Koa搭建前后端系统(八)-- vue router路由嵌套、store状态管理

9 篇文章 0 订阅

前言

本文是在该系列的基础上,针对前端的修改。其中前端采用vue3框架,脚手架为vite,ui组件库为ElementPlus,路由为vue-router,状态管理库为Pinia。

路由嵌套

整合模块数据文件(路由、菜单)

整合模块数据为路由、菜单所用数据

/** /src/router/modules.ts */
import type { ModulesItemType, MenuItemType } from "./modules.type"
//从 ./modules/ 目录中导入多个模块 适用于vite
const modules = import.meta.glob('./modules/*.ts', { eager: true }) as { [propName: string]: { default: ModulesItemType } }

const parentPath = "/";//所有模块的父路径path
/** 首字母大写方法 */
function __FirstWordUpperCase(letters: string[]) {
    return letters.reduce((s: string, c: string) => {
        return s += c.charAt(0).toUpperCase() + c.slice(1)
    }, '')
}
/** 
去掉字母前缀,并首字母小写方法 
letters:要处理的字母
ignoredPre:要去掉的前缀
*/
function __FirstWordLowerCase(letters: string, ignoredPre = "") {
    if (ignoredPre) {
        const regx = new RegExp("^" + ignoredPre, "gim");
        letters = letters.replace(regx, "")
    }
    return letters.charAt(0).toLowerCase() + letters.slice(1);
}
/**
整合单个模块数据为路由文件所需格式
nameSpace:模块的命名空间
item:模块中的数据
routes:用于存储路由数据
*/
function __makeModulesRoutesData(nameSpace: string, item: ModulesItemType, routes: any[] = []) {
	//将模块数据中以menu为前缀的key过滤掉
    const routeKeys = Object.keys(item).filter((key: string) => /^[^menu]/.test(key));
    //模块数据中有key为path,则处理为路由数据
    if ('path' in item) {
        const routePath = `${nameSpace}/${item.path}`;//路由path:【命名空间】+【当前path值】 
        const routeName = __FirstWordUpperCase([nameSpace, item.path || '']);//路由name:【命名空间】+【当前path值】 (驼峰式)
        //将过滤的key的值整合:除了path值是以【命名空间】+【当前path值】,其他值不变
        const routeItem = routeKeys.reduce((m: any, key: string) => {
            if (key === 'path') m['path'] = routePath;
            else m[key] = item[key as keyof ModulesItemType];
            return m
        }, {});
        //将整合后的路由存储到routes变量中
        routes.push({ ...routeItem, name: routeName });
    }
    //模块数据还有子数据 - menuChildren 则递归再次重复以上动作
    if (item['menuChildren']) {
        for (let i in item['menuChildren']) {
            __makeModulesRoutesData(nameSpace, item['menuChildren'][i], routes)
        }
    }
    return routes;
}
/**
整合各个模块数据为路由文件所需格式
routes:用于存储路由数据
*/
function getModulesRoutes(routes: any[] = []) {
    for (let key in modules) {
    	//命名空间:以模块文件与	./modules/ 目录的相对路径为命名空间
    	//比如 ./modules/first.ts 命名空间为first
        const nameSpace = key.replace(/^\.\/modules\//, "").replace(/\.\w+$/, "");
        //模块文件中的数据
        const modulesItem = modules[key].default;
        __makeModulesRoutesData(nameSpace, modulesItem, routes)
    }
    return routes;
}
/**
整合单个模块数据为菜单所需格式
nameSpace:模块的命名空间
item:模块中的数据
menu:用于存储菜单数据
index:每个菜单数据中的key,相当于id
*/
function __makeModulesMenusData(nameSpace: string, item: ModulesItemType, menu: any = {}, index = "") {
	//将模块数据中以非menu为前缀的key过滤掉
    const menuKeys = Object.keys(item).filter((key: string) => /^menu/.test(key));
    //模块数据中有key为menuName,则处理为菜单数据
    if ('menuName' in item) {
    	//将过滤的key的值整合:去掉menu前缀并首字母小写。将菜单数据存储到menu中
        for (let key of menuKeys) {
            const mkey = __FirstWordLowerCase(key, 'menu');
            menu[mkey] = item[key as keyof ModulesItemType];
        }
        //处理key为path的数据:【所有模块的父路径path】+【命名空间】+【当前path值】
        if ('path' in item) {
            menu['path'] = `${parentPath}${nameSpace}/${item.path}`;
        }
        //设置菜单数据key
        menu['key'] = index;
    }
    //模块数据还有子数据 - menuChildren 则递归再次重复以上动作
    if (item['menuChildren']) {
        menu['children'] = [];//创建子数据children
        let cIndex = 1;//子数据索引:用作设置菜单key
        for (let i in item['menuChildren']) {
            if ('menuName' in item) {
                const menuItem = {}
                __makeModulesMenusData(nameSpace, item['menuChildren'][i], menuItem, `${index}-${cIndex++}`)
                menu['children'].push(menuItem)
            }
        }
    }
    return menu;
}
/**
整合各个模块数据为菜单所需格式
*/
function getModulesMenus(): MenuItemType[] {
    const menus: MenuItemType[] = [];//menu:用于存储菜单数据
    const MAX_ORDER = 999999;//最大顺序,用于模块间的菜单排序
    //将各个模块asc排序:以menuOrder设置数字顺序,没有该值(或设置为0)则按最大值处理
    const modulesKeys = Object.keys(modules).sort((key1: string, key2: string) => (modules[key1].default.menuOrder || MAX_ORDER) - (modules[key2].default.menuOrder || MAX_ORDER));
    //模块间菜单数据的处理
    modulesKeys.forEach((key: string, index: number) => {
    	//命名空间:以模块文件与	./modules/ 目录的相对路径为命名空间
    	//比如 ./modules/first/index.ts 命名空间为first/index
        const nameSpace = key.replace(/^\.\/modules\//, "").replace(/\.\w+$/, "");
        //模块文件中的数据
        const modulesItem = modules[key].default;
        if (modulesItem['menuName']) {
            const modulesMenuItem = __makeModulesMenusData(nameSpace, modulesItem, {}, String(index))
            menus.push(modulesMenuItem);
        }
    })
    return menus;
}
export {
    getModulesRoutes,
    getModulesMenus,
    parentPath
}

模块接口文件

/** /src/router/modules.type.ts */
import { DefineComponent } from "vue";
type ComponentType = DefineComponent<{}, {}, {}, import("vue").ComputedOptions, import("vue").MethodOptions, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{}>>, {}>
/**各模块单个数据接口:凡是不带menu前缀的都是vue-route用的 */
export interface ModulesItemType {
    menuName?: string;//菜单名称,有值则显示
    menuOrder?: number;//菜单顺序
    menuIcon?: string | ComponentType;//菜单图标
    menuChildren?: ModulesItemType[];//子菜单
    path?: string;
    component?: () => Promise<typeof import("*.vue")>;
    meta?: { [propName: string]: any }
}
/**菜单接口 */
export interface MenuItemType {
    name?: string;//菜单名称,有值则显示
    order?: number;//菜单顺序
    icon?: string | ComponentType;//菜单图标
    children?: MenuItemType[];//子菜单
    path?: string;
}

模块文件

模块文件在都必须在 /src/router/modules/ 目录下,并且每个模块必须有导出默认,export default

/** /src/router/modules/first.ts */
import { Location } from "@element-plus/icons-vue";
import { type ModulesItemType } from "../modules.type"
export default {
    menuName: '菜单1',
    menuOrder:1,
    path: 'index',
    menuIcon: Location,//ElementPlus icon图标
    component: () => import("@/pages/first/index.vue"),
} as ModulesItemType
/** /src/router/modules/second.ts */
import { type ModulesItemType } from "../modules.type"
export default {
    menuName: '菜单2',
    menuOrder: 2,
    path: 'index',
    menuIcon: 'iconfont icon-ding',//iconfont图标
    component: () => import("@/pages/second/index.vue"),
} as ModulesItemType
/** /src/router/modules/third.ts */
import { Location } from "@element-plus/icons-vue";
import { type ModulesItemType } from "../modules.type"
export default {
    menuName: '菜单3',
    menuOrder: 3,
    menuIcon: Location,
    menuChildren: [
        {
            menuName: '菜单3-1',
            menuIcon: Location,
            menuChildren: [
                { menuName: '菜单3-1-1', path: 'third_1_1', component: () => import("@/pages/third/third_1_1.vue"), menuIcon: Location },
                { menuName: '菜单3-1-2', path: 'third_1_2', component: () => import("@/pages/third/third_1_2.vue"), menuIcon: '' }
            ]
        },
        { menuName: '菜单3-2', path: 'third_2', component: () => import("@/pages/third/third_2.vue"), menuIcon: '' },
        { menuName: '菜单3-3', path: 'third_3', component: () => import("@/pages/third/third_3.vue"), menuIcon: '' }
    ]
} as ModulesItemType
/** /src/router/modules/four.ts */
import { type ModulesItemType } from "../modules.type"
export default {
    menuName: '菜单4',
    menuOrder: 4,
    path: 'index',
    menuIcon: '',
    component: () => import("@/pages/four/index.vue"),
} as ModulesItemType

路由文件

/** /src/router/index.ts */
import {
    createRouter,
    createWebHistory,
    RouteRecordRaw,
    createWebHashHistory,
} from "vue-router";
import { getModulesRoutes, parentPath } from "./modules"//整合路由的文件

const routes: Array<RouteRecordRaw> = [
    {
        path: "/login",
        name: "Login",
        component: () => import("@/pages/login/login.vue"),
    },
    {
        path: "/register",
        name: "Register",
        component: () => import("@/pages/register/register.vue"),
        meta: {}
    },
    {
        path: parentPath,//各模块的父路径
        component: () => import("@/pages/index.vue"),
        children: [
            ...getModulesRoutes(),//获取各模块的路由
            {
                path: "",
                redirect: "first/index"
            },
            // 以上都未匹配,跳转404页面
            {
                path: '/:pathMatch(.*)*',
                name: 'NotFound',
                component: () => import("@/pages/not-found.vue"),
            },
        ]
    },
]

const router = createRouter({
    history: createWebHashHistory(),
    routes,
});
export default router;

getModulesRoutes()最终生成的数据

[
    {
        "path": "first/index",
        "name": "FirstIndex",
        "component": () => import("@/pages/first/index.vue")
    },
    {
        "path": "four/index",
        "name": "FourIndex",
        "component": () => import("@/pages/four/index.vue")
    },
    {
        "path": "second/index",
        "name": "SecondIndex",
        "component": () => import("@/pages/second/index.vue")
    },
    {
        "path": "third/third_1_1",
        "name": "ThirdThird_1_1",
        "component": () => import("@/pages/third/third_1_1.vue")
    },
    {
        "path": "third/third_1_2",
        "name": "ThirdThird_1_2",
        "component": () => import("@/pages/third/third_1_2.vue")
    },
    {
        "path": "third/third_2",
        "name": "ThirdThird_2",
        "component": () => import("@/pages/third/third_2.vue")
    },
    {
        "path": "third/third_3",
        "name": "ThirdThird_3",
        "component": () => import("@/pages/third/third_3.vue")
    }
]

嵌套路由index页面

<!-- /src/pages/index.vue -->
<script setup lang="ts">
import Menu from "./menu/menu.vue";//左侧菜单栏组件
import TopHeader from "./top-header/top-header.vue";//顶部组件
</script>
<template>
  <div class="index">
  	<!--顶部显示区域-->
    <header><TopHeader /></header>
    <section>
      <!--左侧菜单显示区域-->
      <nav><Menu /></nav>
      <!--中间内容显示区域-->
      <main>
      	<!--嵌套路由(嵌套在App.vue中的路由下的路由)-->
        <router-view v-slot="{ Component, route }">
          <transition name="fade">
            <component :is="Component" :key="route.path" />
          </transition>
        </router-view>
      </main>
    </section>
  </div>
</template>
<style lang="less" scoped>
.index {
  @headerH: 60px;
  & > header {
    height: @headerH;
    box-sizing: border-box;
    line-height: @headerH;
    background: #000;
  }
  & > section {
    height: calc(100vh - @headerH);
    display: flex;

    & > nav {
      height: 100%;
    }
    & > main {
      box-sizing: border-box;
      flex-grow: 1;
      height: 100%;
      overflow: auto;
      padding: 15px 15px 10px;
    }
  }
}
</style>

顶部TopHeader页面

<!-- /src/pages/top-header/top-header.vue -->
<template>
  <div class="top-header">我是顶部区域</div>
</template>

菜单页面

左侧菜单Menu页面,由于菜单数据是一种树形结构,所以该页面采用vue的渲染函数(h函数)写法

<!-- /src/pages/menu/menu.vue -->
<script lang="ts">
import { h, watch, getCurrentInstance } from "vue";
import { RouteLocationNormalizedLoaded, useRoute } from "vue-router";
import { ElMenu, ElSubMenu, ElMenuItem, ElIcon } from "element-plus";
import { getModulesMenus } from "@/router/modules";
import { MenuItemType } from "@/router/modules.types";
export default {
  setup() {
    const internalInstance = getCurrentInstance();
    const route = useRoute();
    const menuList = getModulesMenus();
    //ElMenu组件属性
    const elMenuAttrs = {
      class: "menu-component",
      "active-text-color": "#ffd04b",
      "background-color": "#545c64",
      "default-active": route.path,
      "text-color": "#fff",
      router: true,
    };
    //生成ElMenuItem组件下面内容或ElSubMenu组件的title slot
    const getMenuTitleVNode = (item: MenuItemType) => {
      const v = [];
      if (item.icon) {//如果数据中有icon属性有值
        if (typeof item.icon === "string") {//icon属性值是string类型(适用于iconfont)
          /** 相当于<el-icon :class="item.icon"></el-icon> */
          v.push(h(ElIcon, { class: item.icon }));
        } else {//icon属性值是导入的组件(适用于elementPlus图标组件)
          /** 相当于
          <el-icon :class="item.icon">
          	<component :is="item.icon"></component
          </el-icon> 
          */
          v.push(h(ElIcon, null, { default: () => [h(item.icon)] }));
        }
      }
      /**相当于<span>{{item.name}}</span>*/
      v.push(h("span", item.name));
      return v;
    };
    //生成ElMenu组件下面的ElMenuItem或 ElSubMenu组件
    const getMenuVNode = (m: MenuItemType[]) => {
      return m.map((item: MenuItemType) => {
        let node: any = null;
        if (item.children?.length) {//有下级菜单
          /** 相当于
			<el-sub-menu :index="item.key" :data-tree="item.key">
				<template #title>....</template>
				<template #default>...递归该函数</template>
			</el-sub-menu>
		  */
          node = h(
            ElSubMenu,
            {
              index: item.key,
              "data-tree": item.key,
            },
            {
              title: () => [...getMenuTitleVNode(item)],
              default: () => [...getMenuVNode(item.children)],
            }
          );
        } else {//没有下级菜单
          /** 相当于
			<el-menu-item :index="item.path" :route="item.path" :data-tree="item.key" :data-route="item.path">
				<template #default>...</template>
			</el-menu-item>
		  */
          node = h(
            ElMenuItem,
            {
              index: item.path,
              route: item.path,
              "data-tree": item.key,
              "data-route": item.path,
            },
            {
              default: () => [...getMenuTitleVNode(item)],
            }
          );
        }
        return node;
      });
    };
    /**监听路有变化:菜单重新渲染,自动高亮路由所在的菜单*/
    watch(
      route,
      (route: RouteLocationNormalizedLoaded) => {
        elMenuAttrs["default-active"] = route.path;
        //强制刷新组件视图
        internalInstance.proxy.$forceUpdate();
      },
      { deep: true }
    );
    /**
    <el-menu class="menu-componet" active-text-color="#ffd04b" background-color="#545c64" :default-active="route.path" text-color"="#fff" router>...</el-menu>
    */
    return () => h(ElMenu, elMenuAttrs, { default: () => [...getMenuVNode(menuList)] });
  },
};
</script>
<style lang="less" scoped>
.menu-component {
  height: 100%;
  width: 220px;
  overflow: auto;
  * {
    user-select: none;
  }
}
</style>

getModulesMenus()最终生成的数据

/** Location 是引入的ElIcon */
[
  { "name": "菜单1", "order": 1, "icon": Location, "path": "/first/index", "key": "1" },
  { "name": "菜单2", "order": 2, "icon": "iconfont icon-ding", "path": "/second/index", "key": "2" },
  {
     "name": "菜单3",
     "order": 3,
     "icon": Location,
     "key": "3",
     "children": [
       {
         "name": "菜单3-1",
         "icon": Location,
         "key": "3-1",
         "children": [
           { "name": "菜单3-1-1", "icon":Location, "path": "/third/third_1_1", "key": "3-1-1"  },
           { "name": "菜单3-1-2", "icon": "", "path": "/third/third_1_2", "key": "3-1-2" }
         ]
       },
       { "name": "菜单3-2", "icon": "", "path": "/third/third_2", "key": "3-2" },
       { "name": "菜单3-3", "icon": "", "path": "/third/third_3", "key": "3-3" }
     ]
  },
  { "name": "菜单4", "order": 4, "icon": "", "path": "/four/index", "key": "4" }
]

最终生成的模板为

<el-menu class="menu-componet" active-text-color="#ffd04b" background-color="#545c64" :default-active="route.path" text-color"="#fff" router>
	<el-menu-item index="/first/index" route="/first/index" data-tree="1" data-route="/first/index">
		<el-icon><Location /></el-icon>
		<span>菜单1</span>
	</el-menu-item>
	<el-menu-item index="/second/index" route="/second/index" data-tree="2" data-route="/second/index">
		<el-icon class="iconfont icon-ding"></el-icon>
		<span>菜单2</span>
	</el-menu-item>
	<el-sub-menu index="3" data-tree="3">
		<template #title>
			<el-icon><Location /></el-icon>
			<span>菜单3</span>
		</template>
		<el-sub-menu index="3-1" data-tree="3-1">
			<template #title>
				<el-icon><Location /></el-icon>
				<span>菜单3-1</span>
			</template>
			<el-menu-item index="/third/third_1_1" route="/third/third_1_1" data-tree="3-1-1" data-route="/third/third_1_1">
				<el-icon><Location /></el-icon>
				<span>菜单3-1-1</span>
			</el-menu-item>
			<el-menu-item index="/third/third_1_2" route="/third/third_1_2"  data-tree="3-1-2" data-route="/third/third_1_2">
				<span>菜单3-1-2</span>
			</el-menu-item>
		</el-sub-menu>
		<el-menu-item index="/third/third_2" route="/third/third_2" data-tree="3-2" data-route="/third/third_2">
			<span>菜单3-2</span>
		</el-menu-item>
		<el-menu-item index="/third/third_3" route="/third/third_3" data-tree="3-3" data-route="/third/third_3">
			<span>菜单3-3</span>
		</el-menu-item>
	</el-sub-menu>
	<el-menu-item index="/four/index" route="/four/index" data-tree="4" data-route="/four/index">
		<span>菜单4</span>
	</el-menu-item>
</el-menu>

路由显示的页面

即模块文件中component字段import的页面

<!-- /src/pages/first/index.vue -->
<script setup lang="ts">
import { ref } from "vue";
import http from "@/http";
import { ElMessage } from "element-plus";
import { useRouter } from "vue-router";

const isload = ref(false);
const list = ref([]);
const router = useRouter();

const logout = async () => {
  window.localStorage.removeItem("secret_key");
  window.localStorage.removeItem("token");
  window.location.href = `/#/login`;
};
const lookUser = async () => {
  const params = {};
  isload.value = true;
  await http
    .post("users/look", params)
    .then((data: any) => (list.value = data.list))
    .catch((err: any) => {
      ElMessage({
        message: err.message,
        type: "error",
      });
    });
  isload.value = false;
};
const goToMenu2 = ()=>{
  router.push("/second/index")
}
lookUser();
</script>
<template>
  <div class="user-index">
    <el-table :data="list" style="width: 100%" v-loading="isload">
      <el-table-column prop="username" label="用户名" />
      <el-table-column prop="password" label="密码" />
      <el-table-column prop="create_time" label="创建时间" />
    </el-table>
    <el-button class="refresh-btn" @click="lookUser">刷新列表</el-button>
    <el-button class="refresh-btn" @click="logout">注销</el-button>
    <el-button class="refresh-btn" @click="goToMenu2">跳转菜单2</el-button>
  </div>
</template>
<style lang="less" scoped>
.user-index {
  width: 100%;
  .refresh-btn {
    margin-top: 20px;
  }
}
</style>
<!-- /src/pages/second/second.vue -->
<template>
  <div class="user-second">user-second</div>
</template>
<!-- /src/pages/third/third_1_1.vue -->
<template>
  <div class="user-third_1_1">third_1_1</div>
</template>
<!-- /src/pages/third/third_1_2.vue -->
<template>
  <div class="user-third_1_2">third_1_2</div>
</template>
<!-- /src/pages/third/third_2.vue -->
<template>
  <div class="user-third_2">third_2</div>
</template>
<!-- /src/pages/third/third_3.vue -->
<template>
  <div class="user-third_3">third_3</div>
</template>
<!-- /src/pages/four/index.vue -->
<template>
  <div class="user-third_3">user-four</div>
</template>

NotFound页面

<!-- /src/pages/not-found.vue -->
<script setup lang="ts">
import { useRouter } from "vue-router";

const router = useRouter();
const goBack = () => {
  router.back();
};
</script>
<template>
  <div class="user-not-found">
    <h2>404</h2>
    <div>未找到页面</div>
    <el-button @click="goBack">返回</el-button>
  </div>
</template>
<style lang="less" scoped>
.user-not-found {
  text-align: center;
}
</style>

最后修改登录页 /src/pages/login/login.vue 登录成功后的路由跳转:将router.push("/index");改为router.push("/");

最终页面效果:

在这里插入图片描述

总结一下:modules.ts文件是枢纽,用于分发路由和菜单。getModulesRoutes()分发路由,getModulesMenus()分发菜单。modules/目录是数据源,其下的模块文件是modules.ts各方法获取数据的资源库。若添加菜单menuName + [path + component],若仅添加路由path + component

状态管理

Vue状态管理可以看成是用于设置或获取Vue下的全局变量,可以供整个Vue项目使用。以前Vue使用VUEX来管理状态,Vue3推荐Pinia。不管哪个,他们都是Vue项目状态的仓储。

安装Pinia

npm install pinia

VUE中应用Pinia

/** /src/main.ts */
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router/index'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import VueCookies from 'vue-cookies'
import { createPinia } from "pinia"
import "./assets/iconfont/iconfont.css" //iconfont

const pinia = createPinia();

createApp(App).use(router).use(ElementPlus).use(VueCookies).use(pinia).mount('#app')

创建Pinia仓储

/** /src/store/index.ts */
import { defineStore } from "pinia"

export const useUserStore = defineStore('user', {
    state: () => ({
        userId: 0,//存储登录用户ID
    }),
    actions: {
    	//设置登录用户ID方法
        setUserId(id: number) {
            this.userId = id;
        }
    }
})

使用仓储创建和获取信息

在登录页用户点击登录,成功后将后端返回的用户ID存储到Pinia仓储中

import { useUserStore } from "@/store/index";
//获取仓储
const userStore = useUserStore();
const submit = async (formEl: FormInstance | undefined) => {
  if (!formEl) return;
  await formEl.validate((valid, fields) => {
    if (valid) {
      http
        .post("login/loginIn", {
          ...formData,
          password: md5(formData.password),
        })
        .then(async (data: any) => {
          if (data.code == 0) {
          	//设置用户ID到Pinia仓储
            userStore.setUserId(data.id);
            /** 其他代码省略 */
            
          } else {
            ElMessage({
              message: data.message,
              type: "error",
            });
          }
        })
        .catch((err: any) => {
          ElMessage({
            message: err.message,
            type: "error",
          });
        });
    } else {
      ElMessage({
        message: "请按提示登录",
        type: "error",
      });
    }
  });
};

在需要的页面获取仓储中存储的信息

import { useUserStore } from "@/store/index";
//获取仓储
const userStore = useUserStore();
//获取仓储中的UserId
const userId = userStore.userId;

最后,本篇文章所构建的目录结构

在这里插入图片描述

参考资料

CSDN:在vue3中使用 forceUpdate()
简书:(十四)Vue3.x核心之getCurrentInstance
Vite Glob 导入
Pinia 中文文档

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是搭建后台管理系统的简单步骤: 1. 创建项目 使用 Vue CLI 创建项目,选择 vue3 preset,安装完成后进入项目目录。 ``` vue create my-project cd my-project ``` 2. 安装依赖包 安装 vite、vue-router 和 element-plus。 ``` npm install vite vue-router@4 element-plus --save ``` 安装 pinia 和 echarts。 ``` npm install pinia echarts@5 --save ``` 3. 配置 vite 在根目录下创建 `vite.config.js` 文件,配置 alias 和 server。 ```js import path from 'path' import { defineConfig } from 'vite' export default defineConfig({ resolve: { alias: { '@': path.resolve(__dirname, 'src'), }, }, server: { port: 3000, open: true, }, }) ``` 4. 配置路由 在 `src` 目录下创建 `router` 文件夹,并创建 `index.js` 文件,配置路由。 ```js import { createRouter, createWebHistory } from 'vue-router' import Home from '@/views/Home.vue' const routes = [ { path: '/', name: 'Home', component: Home, }, ] const router = createRouter({ history: createWebHistory(), routes, }) export default router ``` 在 `src` 目录下的 `main.js` 中引入路由。 ```js import { createApp } from 'vue' import App from './App.vue' import router from './router' const app = createApp(App) app.use(router) app.mount('#app') ``` 5. 配置状态管理 在 `src` 目录下创建 `store` 文件夹,并创建 `index.js` 文件,配置状态管理。 ```js import { createPinia } from 'pinia' const store = createPinia() export default store ``` 在 `src` 目录下的 `main.js` 中引入状态管理。 ```js import { createApp } from 'vue' import App from './App.vue' import router from './router' import store from './store' const app = createApp(App) app.use(router) app.use(store) app.mount('#app') ``` 6. 配置 UI 框架 在 `src` 目录下的 `main.js` 中引入 element-plus。 ```js import { createApp } from 'vue' import App from './App.vue' import router from './router' import store from './store' import ElementPlus from 'element-plus' import 'element-plus/lib/theme-chalk/index.css' const app = createApp(App) app.use(router) app.use(store) app.use(ElementPlus) app.mount('#app') ``` 7. 配置 echarts 在 `src` 目录下的 `main.js` 中引入 echarts。 ```js import { createApp } from 'vue' import App from './App.vue' import router from './router' import store from './store' import ElementPlus from 'element-plus' import 'element-plus/lib/theme-chalk/index.css' import * as echarts from 'echarts' const app = createApp(App) app.use(router) app.use(store) app.use(ElementPlus) app.config.globalProperties.$echarts = echarts app.mount('#app') ``` 8. 创建页面 在 `src` 目录下创建 `views` 文件夹,并创建页面组件。 9. 创建布局 在 `src` 目录下创建 `layouts` 文件夹,并创建布局组件。 10. 配置路由和布局 在 `router/index.js` 中配置路由和布局。 ```js import { createRouter, createWebHistory } from 'vue-router' import Layout from '@/layouts/Layout.vue' import Home from '@/views/Home.vue' const routes = [ { path: '/', component: Layout, children: [ { path: '', name: 'Home', component: Home, }, ], }, ] const router = createRouter({ history: createWebHistory(), routes, }) export default router ``` 11. 运行项目 在项目根目录下运行 `npm run dev`,打开浏览器访问 `http://localhost:3000` 查看效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值