Bootstrap

Vue3项目实战(vue3+vite+pinia+element-plus+axios+mock)

一、项目介绍

在这里插入图片描述

1、技术栈

  • vue3+vite+vue-router
  • pinia
  • element-plus
  • axios
  • mock
  • Echarts

2、业务功能

  • 登录
  • 首页
  • 商品
  • 用户管理

3、应用场景

  • 进行后台管理项目的
  • 根据不同用户的权限授予不同的功能

4、项目源码

xuyuan-upward 希望每位大佬获取时候点个小小的赞!

二、项目实战

2.1、项目初始化

1、构建项目

# npm 7+,需要添加额外的 --:
npm create vite@latest my-vue-app -- --template vue

2、安装成功后进行安装依赖

npm install 

3、修改路径替代符

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vite.dev/config/
export default defineConfig({
  plugins: [vue()],
  /* 添加的是别名 */
  resolve: {
    alias: {
      '@': '/src',
    },
  },
})

4、引入element-plus依赖,axios,router
安装依赖内容过于简单,请自行取相关内容官网查看文档进行安装
element-plus官网
axios官网
router

2.2、项目实战

1、引入router配置

import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
const routes = [
  {
    path: '/',
    name: 'main',
    component: () => import('@/views/RootView.vue'),
    redirect: '/home',
    /* 访问 /home 时的行为
当你访问 /home 时,会发生以下情况:
匹配父路由:
首先匹配到 path: '/' 的路由配置。
由于 Main.vue 是父组件,它会被渲染。
匹配子路由:
在 Main.vue 内部的 <router-view> 中,会匹配到 path: '/home' 的子路由。
因此,Home.vue 会在 Main.vue 的 <router-view> 中渲染。 */
    children: [
       {
        path: '/home',
        name: 'home',
        component: () => import('@/views/MainView.vue')
      },
    ]
  },

]
const router = createRouter({
  history: createWebHistory(),
  routes
})
export default router

接着还需要导入main.js一些配置文件

  • 全局样式
  • pinia等等
import { createApp } from 'vue'
import App from './App.vue'
// 导入全局样式
import "@/assets/less/index.less"
import router from '@/router'
import ElementPlus from 'element-plus'
// 导入element-plus组件的全局样式
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import { createPinia } from 'pinia'
import { useAllDataStore } from '@/stores'
/* 引入mock */
import "@/apis/mock.js"
/* 引入apis 管理请求接口 */
import apis from "@/apis/apis.js"
const pinia = createPinia()
const app = createApp(App)
/* 定义全局配置使用 */
app.config.globalProperties.$apis = apis

app.use(ElementPlus)
app.use(pinia)
// localStorage.removeItem("store")
const store = useAllDataStore()
store.addRoutes(router, "refresh")
app.use(router)

/*function isRoute(to) {
    let routes = router.getRoutes()
    console.log("routes", routes);
    let resFil = routes.filter(item =>
        /* 相当于return */
        item.path === to.path
    )
    /*  let resFil = routes.filter(item => { 
     相当于一段代码,只有return为true时候才会保留对应的数据
     item.path === to.path}
       
     ) */
    return resFil
}
/*
router.beforeEach((to, from, next) => {
    console.log("store.state.token", store.state.token);
    if (!store.state.token && to.path !== '/login') {
        console.log("to.path1", to.path);
        next({ name: 'login' })
    }
    if (store.state.token && to.path === '/login') {
        console.log("to.path2", to.path);
        next({ name: 'home' })
    }
    if (store.state.token && to.path !== '/login') {
        console.log("to.path3", to.path);
        console.log("isRoute", isRoute(to));
        if (isRoute(to).length === 0) {
            console.log("to.path3", to.path);
            next({ name: '404' })
        }
    }
    console.log("to.path4", to.path);
    next()
})
*/
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    app.component(key, component)
}
app.mount('#app')

2、App.vue组件引入

<script setup>
</script>

<template>
  <div class="app">
    <router-view />
  </div>
</template>

<style >
#app,
.app {
  width: 100%;
  height: 100%;
  overflow: hidden;
}
</style>

3、创建RootView根路由组件

<template>
  <div class="common-layout">
    <el-container class="main-container">
      <div class="aside-container">
        <CommonAside />
      </div>
      <el-container class="right-container">
        <el-header style="height: 40px;">
          <CommonHeader />
        </el-header>
        <el-divider style="margin: 5px 0;" />
        <CommonTab />
        <el-main>
          <router-view />
        </el-main>
        <el-footer>
          <Footer />
        </el-footer>
      </el-container>
    </el-container>
  </div>
</template>

<script setup>
import { reactive, toRefs, onMounted, nextTick } from 'vue'
import CommonAside from '@/components/CommonAside.vue';
import CommonHeader from '@/components/CommonHeader.vue';
import CommonTab from '@/components/CommonTab.vue';
import Footer from '@/components/Footer.vue';
</script>
<style lang='less' scoped>
.common-layout,
.main-container,
right-container {
  height: 100%;
  background-color: #f0f2f5;

  .el-main {
    padding: 8px;
  }
}
</style>

4、依次创建

三个普通组件 CommonAside CommonHeader Footer 以及每个页面的路由组件 router-view

  • CommonAside.vue
<template>
    <!-- default-active通常与对应的index相关 -->
    <el-aside :width="width">
        <el-menu text-color="#fff" background-color="#545c64" :collapse="isCollapse" :collapse-transition="false" 
        :default-active="activeMenu"
            class="el-menu-vertical-demo">
            <div class="title">
                <i class="iconfont icon-quanpingtai"> </i>
                <h4 v-show="!isCollapse">许苑后台管理</h4>
            </div>
            <!-- 没有子菜单 -->
            <el-menu-item v-for="item in noChildren" :key="item.path" :index="item.path" @click="handleMenu(item)">
                <component :is="item.icon" class="icon" />
                <span style="margin-left: 10px">{{ item.label }}</span>
            </el-menu-item>
            <!-- 有子菜单 -->
            <el-sub-menu v-for="item in hasChildren" :key="item.path" :index="item.path">
                <template #title>
                    <component :is="item.icon" class="icon" />
                    <span style="margin-left: 10px">{{ item.label }}</span>
                </template>
                <el-menu-item-group>
                    <el-menu-item v-for="(subItem) in item.children" :index="subItem.path" :key="subItem.path">
                        <component :is="subItem.icon" class="icon" />
                        <span>{{ subItem.label }}</span>
                    </el-menu-item>
                </el-menu-item-group>
            </el-sub-menu>
        </el-menu>
    </el-aside>
</template>

<script setup>
import { ref, reactive, toRefs, onMounted, nextTick, computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useAllDataStore } from '@/stores'
const store = useAllDataStore()
const router = useRouter()
const route = useRoute()
// 测试数据,初始化,刚开始可以使用,后续通过配置路由权限获取
/* const list = ref([
    {
        path: '/home',
        name: 'home',
        label: '首页',
        icon: 'house',
        url: 'Home'
    },
    {
        path: '/mall',
        name: 'mall',
        label: '商品管理',
        icon: 'ShoppingBag',
        url: 'Mall'
    },
    {
        path: '/user',
        name: 'user',
        label: '用户管理',
        icon: 'user',
        url: 'User'
    },
    {
        path: 'other',
        label: '其他',
        icon: 'location',
        children: [
            {
                path: '/page1',
                name: 'page1',
                label: '页面1',
                icon: 'setting',
                url: 'Page1'
            },
            {
                path: '/page2',
                name: 'page2',
                label: '页面2',
                icon: 'setting',
                url: 'Page2'
            }
        ]
    }
])
 */
const list = computed(() => store.state.menuList)
const noChildren = computed(() => list.value.filter(item => !item.children))
const hasChildren = computed(() => list.value.filter(item => item.children))
const width = computed(() => store.state.isCollapse ? '60px' : '200px')
// 涉及组件之间的传递 => 使用pinia进行各组件之间的传递
const isCollapse = computed(() => store.state.isCollapse)
const activeMenu = computed(() => route.path)
const handleMenu = (item) => {
    if (item.children) {
        return
    }
    router.push(item.path)
    store.selectMenu(item)
}
</script>
<style lang='less' scoped>
.icon {
    width: 18px;
    height: 18px;
    margin-right: 5px;
}

.el-aside {
    background-color: #545c64;
    height: 100vh;

    .el-menu {
        border-right: none;

        .title {
            display: flex;
            align-items: center;
            justify-content: center;
        }

        h4 {
            color: #fff;
            font-size: 17px;
            margin: 20px;
            font-weight: 500px;
            text-align: center;
        }


    }
}
</style>
  • CommonHeader.vue
<template>
    <div class="header">
        <div class="l-header">
            <el-button size="small" @click="store.state.isCollapse = !store.state.isCollapse">
                <el-icon>
                    <Menu />
                </el-icon>
            </el-button>
        
        </div>
        <div class="r-header">
            <el-dropdown>
                <span class="el-dropdown-link">
                    <img src="@/assets/images/xuyuan.jpg" alt="" class="r-header-avatar">
                </span>
                <template #dropdown>
                    <el-dropdown-menu>
                        <el-dropdown-item>个人中心</el-dropdown-item>
                        <el-dropdown-item @click="handlerLogout">退出登录</el-dropdown-item>
                    </el-dropdown-menu>
                </template>
            </el-dropdown>

        </div>

    </div>
</template>

<script setup>
import { ref, reactive, toRefs, onMounted, nextTick, computed } from 'vue'
import { useAllDataStore } from '@/stores'
import { ArrowRight } from '@element-plus/icons-vue'
import { useRouter } from 'vue-router'
const store = useAllDataStore()
const router = useRouter()
let currentPath = computed(() => {
    console.log("store.state.currentMenu", store.state.currentMenu);
    return store.state.currentMenu;
})
const handlerLogout = () => {
    store.clean()
    router.push('/login')
}
</script>
<style lang='less' scoped>
.header {
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: space-between;
    align-items: center;

    .l-header {
        display: flex;
        justify-content: center;
        align-items: center;

        .el-button {
            margin-right: 15px;
        }
    }
}

.r-header-avatar {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    margin-right: 10px;
}
</style>

    <!--  -- 面包屑功能 <el-breadcrumb :separator-icon="ArrowRight">
               
                <el-breadcrumb-item :to=" '/' ">首页</el-breadcrumb-item>
                <el-breadcrumb-item v-if="currentPath" :to="currentPath.path" @cl>{{ currentPath.label
                }}
                </el-breadcrumb-item>
            </el-breadcrumb> -->
  • Footer.vue
<template>
    <div class="footer">
        <a href="https://github.com/xuyuan-upward" class="toLearn">
            <span> <i class="iconfont icon-github"></i>站长: 许苑向上</span>
        </a>
        <a href="https://blog.csdn.net/a147775" class="toLearn">
            <span> <i class="iconfont icon-bokeyuan"></i>博客: xuyuan-upward</span>
        </a>
        <a href="https://user.qzone.qq.com/2517115657/main" class="toLearn">
            <span> <i class="iconfont icon-shouye"></i>联系方式: 许苑向上</span>
        </a>
    </div>
</template>

<script setup>
import { ref, reactive, toRefs, onMounted, nextTick } from 'vue'
</script>
<style lang='less' scoped>
.toGithub {
    text-decoration: none;
    font-size: 14px;
    font-weight: bold;
    padding: 10px 0;
    display: block;
    text-align: center;
    border-radius: 5px;
    transition: all 0.3s ease-in-out;
}

.iconfont {
    margin-right: 5px;
}

.footer {
    height: 100%;
    display: flex;
    justify-content: center;

    a {
        margin-right: 40px;
        color: #00000073;

        span {
            line-height: 60px;
        }
    }
}
</style>
  • MainView.vue路由组件
<template>
    <div class="home">
        <el-row>
            <!-- 左侧 -->
            <el-col :span="7">
                <div class="l-user">
                    <el-card style="max-width: 480px" shadow="hover" class="user-info">
                        <div class="user">
                            <img src="@/assets/images/xuyuan.jpg" alt=""
                                style="width: 100px;height: 100px;border-radius: 50%;margin-right: 10px;">
                            <div class="userInfo">
                                <h>admin</h>
                                <p style="margin-top: 20px; color: #999;">超级管理员</p>
                            </div>
                        </div>
                        <el-divider />
                        <div class="login-info">
                            <p>上次登录时间:<span>2024-11-18 1:00:00</span></p>
                            <p style="margin-top: 10px;">上次登录地点:<span>广西</span></p>
                        </div>
                    </el-card>

                    <el-card style="max-width: 480px" shadow="hover" class="user-table">
                        <el-table :data="tableData" style="width: 100%">
                            <!-- 遍历val是值 key是键 -->
                            <el-table-column v-for="(val, key) in tableLabel" :key="key" :prop="key" :label="val">
                            </el-table-column>
                        </el-table>
                    </el-card>
                </div>
            </el-col>
            <!-- 右侧 -->
            <el-col :span="17">
                <div class="r-echart">
                    <div class="top">
                        <el-card v-for="(item) in counterData" :key="item.name"
                            :body-style="{ padding: '20px', display: 'flex' }" shadow="hover">
                            <component :is="item.icon" class="icons" :style="{ background: item.color }" />
                            <div class="detail">
                                <p class="num">¥{{ item.value }}</p>
                                <p class="txt">¥{{ item.name }}</p>
                            </div>
                        </el-card>
                    </div>
                    <div class="bottom">
                        <!-- 三个图表容器 -->
                        <div class="echart-top">
                            <el-card shadow="hover">
                                <div ref="echart" style="height: 220px;"></div>
                            </el-card>
                        </div>
                        <div class="echart-bottom">
                            <el-card shadow="hover">
                                <div ref="userEchart" style="height: 140px"></div>
                            </el-card>
                            <el-card shadow="hover">
                                <div ref="videoEchart" style="height: 140px"></div>
                            </el-card>
                        </div>
                    </div>
                </div>
            </el-col>

        </el-row>
    </div>
</template>

<script setup>
import { ref, reactive, toRefs, onMounted, nextTick, getCurrentInstance } from 'vue'
import * as echarts from 'echarts';

//这个tableData是假数据,等会我们使用axios请求mock数据
const { proxy } = getCurrentInstance()
const tableData = ref([])
const counterData = ref([])
//observer 接收观察器实例对象
const observer = ref(null)

//这个是折线图和柱状图 两个图表共用的公共配置
const xOptions = reactive({
    // 图例文字颜色
    textStyle: {
        color: "#333",
    },
    legend: {},
    grid: {
        left: "20%",
    },
    // 提示框
    tooltip: {
        trigger: "axis",
    },
    xAxis: {
        type: "category", // 类目轴
        data: [],
        axisLine: {
            lineStyle: {
                color: "#17b3a3",
            },
        },
        axisLabel: {
            interval: 0,
            color: "#333",
        },
    },
    yAxis: [
        {
            type: "value",
            axisLine: {
                lineStyle: {
                    color: "#17b3a3",
                },
            },
        },
    ],
    color: ["#2ec7c9", "#b6a2de", "#5ab1ef", "#ffb980", "#d87a80", "#8d98b3"],
    series: [],
})
const pieOptions = reactive({
    tooltip: {
        trigger: "item",
    },
    legend: {},
    color: [
        "#0f78f4",
        "#dd536b",
        "#9462e5",
        "#a6a6a6",
        "#e1bb22",
        "#39c362",
        "#3ed1cf",
    ],
    series: []
})

const getTableData = async () => {
    const data = await proxy.$apis.getTableData()
    console.log("home,tableData获取到的数据:", data);
    tableData.value = data.tableData
}
const getCounterData = async () => {
    const data = await proxy.$apis.getCounterData()
    console.log("home,counterData获取到的数据:", data);
    counterData.value = data
}

const getChartData = async () => {
    // 获取图标信息 ,解构
    const { orderData, userData, videoData } = await proxy.$apis.getChartData()
    console.log("home,orderData获取到的数据:", orderData);
    //对第一个图表的xAxis和series赋值
    xOptions.xAxis.data = orderData.date
    xOptions.series = Object.keys(orderData.data[0]).map(val => {
        return {
            name: val,
            data: orderData.data.map(item => item[val]),
            type: "line"
        }
    }
    )
    //one               echarts.init方法初始化ECharts实例,需要传入dom对象
    const OneEcharts = echarts.init(proxy.$refs["echart"])
    //setOption方法应用配置对象
    OneEcharts.setOption(xOptions)
    //对第二个图表的xAxis和series赋值
    xOptions.xAxis.data = userData.map((item) => item.date)
    xOptions.series = [
        {
            name: "新增用户",
            data: userData.map((item) => item.new),
            type: "bar",
        },
        {
            name: "活跃用户",
            data: userData.map((item) => item.active),
            type: "bar",
        }
    ]
    //two
    const TwoEcharts = echarts.init(proxy.$refs["userEchart"])
    TwoEcharts.setOption(xOptions)

    //对第三个图表的series赋值
    pieOptions.series = [
        {
            data: videoData,
            type: "pie",
        },
    ]
    //three
    const ThreeEcharts = echarts.init(proxy.$refs["videoEchart"])
    ThreeEcharts.setOption(pieOptions);

    //ResizeObserver 如果监视的容器大小变化,如果改变会执行传递的回调
    observer.value = new ResizeObserver(entries => {
        OneEcharts.resize()
        TwoEcharts.resize()
        ThreeEcharts.resize()
    })
    //如果这个容器存在
    if (proxy.$refs["echart"]) {
        //则调用监视器的observe方法,监视这个容器的大小
        observer.value.observe(proxy.$refs["echart"]);
    }
}

onMounted(() => {
    getTableData()
    getCounterData()
    getChartData()
    console.log(proxy);
})
const tableLabel = ref({
    name: "课程",
    todayBuy: "今日购买",
    monthBuy: "本月购买",
    totalBuy: "总购买",
})


</script>
<style lang='less' scoped>
.home {
    height: 100%;
    overflow: hidden;

    .l-user {
        .user-info {
            .user {
                display: flex;
                align-items: center;

                .userInfo {
                    margin-left: 30px;
                }
            }

            .login-info {
                p {
                    font-size: 14px;
                    color: #999;

                    span {
                        color: #666;
                        margin-left: 30px;
                    }
                }
            }
        }

        .user-table {
            margin-top: 50px;
        }
    }

    .r-echart {
        .top {
            display: flex;
            justify-content: space-between;
            flex-wrap: wrap;

            .el-card {
                width: 30%;
                margin-bottom: 10px;
                margin-left: 20px;

            }

            .icons {
                width: 50px;
                height: 50px;
                border-radius: 50%;
                margin-right: 20px;
            }

            .detail {
                display: flex;
                flex-direction: column;
                justify-content: center;

                .num {
                    margin-bottom: 10px;
                }
            }


        }

        .bottom {
            margin-left: 20px;

            .echart-top {
                margin-bottom: 20px;
            }

            .echart-bottom {
                display: flex;
                justify-content: space-between;
                align-items: center;

                .el-card {
                    width: 48%;

                }
            }
        }

    }
}
</style>
  • UserView.vue组件
<template>
    <div class="user">
        <div class="user-head">
            <el-button type="primary" @click="handleAdd">新增</el-button>
            <el-form :inline="true" :model="formData">
                <el-form-item label="请输入">
                    <el-input placeholder="请输入姓名" v-model="formData.keyWord"></el-input>
                </el-form-item>
                <el-form-item>
                    <el-button type="primary" @click="handlerSearch">搜索</el-button>
                </el-form-item>
            </el-form>
        </div>
        <div class="user-table">
            <el-dialog v-model="dialogVisible" :title="action == 'add' ? '新增用户' : '编辑用户'" width="35%"
                :before-close="handleClose">
                <!--需要注意的是设置了:inline="true",
		会对el-select的样式造成影响,我们通过给他设置一个class=select-
		在css进行处理-->
                <el-form :inline="true" :model="formUser" :rules="rules" ref="userForm">
                    <el-row>
                        <el-col :span="12">
                            <el-form-item label="姓名" prop="name">
                                <el-input v-model="formUser.name" placeholder="请输入姓名" />
                            </el-form-item>
                        </el-col>
                        <el-col :span="12">
                            <el-form-item label="年龄" prop="age">
                                <el-input v-model.number="formUser.age" placeholder="请输入年龄" />
                            </el-form-item>
                        </el-col>
                    </el-row>
                    <el-row>
                        <el-col :span="12">
                            <!-- 使用:inline="true"会对select造成影响,此时长度应该设置最大 -->
                            <el-form-item label="性别" prop="sex" style="width: 80%;">
                                <el-select v-model="formUser.sex" placeholder="请选择" class="select-clean">
                                    <el-option label="" :value="1" />
                                    <!-- 注意这里的 :value 表示绑定一个表达式即所谓的"1" 其实代表的是number类型1 -->
                                    <el-option label="" :value="0" />
                                </el-select>
                            </el-form-item>
                        </el-col>
                        <el-col :span="12">

                            <el-form-item label="出生日期" prop="birth">
                                <el-date-picker v-model="formUser.birth" type="date" placeholder="请输入"
                                    style="width: 100%" />
                            </el-form-item>
                        </el-col>
                    </el-row>
                    <el-row>
                        <el-form-item label="地址" prop="addr">
                            <el-input v-model="formUser.addr" placeholder="请输入地址" />
                        </el-form-item>
                    </el-row>
                    <el-row style="justify-content: flex-end">
                        <el-form-item>
                            <el-button type="primary" @click="handleCancel">取消</el-button>
                            <el-button type="primary" @click="onSubmit">确定</el-button>
                        </el-form-item>
                    </el-row>
                </el-form>
            </el-dialog>
            <el-table :data="tableData" style="width: 100%">
                <el-table-column v-for="item in tableLabel" :key="item.prop" :prop="item.prop" :label="item.label"
                    :width="item.width" />
                <el-table-column fixed="right" label="Operations" min-width="120">
                    <template #="scoped">
                        <el-button type="primary" size="small" @click="onEdit(scoped.row)">
                            编辑
                        </el-button>
                        <el-button type="danger" size="small" @click="onDelete(scoped.row)">删除</el-button>
                    </template>
                </el-table-column>
            </el-table>
            <el-pagination layout="prev, pager, next" :total="config.total" @current-change="handlerChangePage"
                class="page" />
        </div>
    </div>
</template>

<script setup>
import { ref, reactive, toRefs, onMounted, nextTick, getCurrentInstance } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
const { proxy } = getCurrentInstance()
const tableData = ref([])
const tableLabel = reactive([
    {
        prop: "name",
        label: "姓名",
    },
    {
        prop: "age",
        label: "年龄",
    },
    {
        prop: "sex",
        label: "性别",
    },
    {
        prop: "birth",
        label: "出生日期",
        width: 200,
    },
    {
        prop: "addr",
        label: "地址",
        width: 400,
    },
])
const config = reactive({
    name: "",
    total: 0,
}
)
const formData = reactive({
    keyWord: ""
})
const dialogVisible = ref(false)
const action = ref("add")
const formUser = ref({
    sex: 0,
})

//表单校验规则
const rules = reactive({
    name: [{ required: true, message: "姓名是必填项", trigger: "blur" }],
    age: [
        { required: true, message: "年龄是必填项", trigger: "blur" },
        { type: "number", message: "年龄必须是数字" },
    ],
    sex: [{ required: true, message: "性别是必选项", trigger: "change" }],
    birth: [{ required: true, message: "出生日期是必选项" }],
    addr: [{ required: true, message: '地址是必填项' }]
})

const handlerSearch = () => {
    console.log("搜索", formData.keyWord);
    config.name = formData.keyWord
    // console.log("搜索", searchText);
    getUserData(config)

}
const getUserData = async (query) => {
    const data = await proxy.$apis.getUserData(query)
    console.log("UserView的数据", data);
    config.total = data.count
    tableData.value = data.list.map((item) => {
        return {
            ...item,
            sex: item.sex === 1 ? '女' : '男'
        }
    })


}
onMounted(() => {
    getUserData()
})
const handlerChangePage = (value) => {
    console.log("当前页码", value);
    config.page = value
    getUserData(config)
}
const onDelete = (row) => {
    console.log("删除", row);
    ElMessageBox.confirm(
        '你确定要删除吗?',
        '删除提示',
        {
            confirmButtonText: '确定删除',
            cancelButtonText: '取消',
            type: 'danger   ',
        }
    )
        .then(() => {
            proxy.$apis.deleteUser({ id: row.id })
            ElMessage({
                type: 'success',
                message: '删除成功',
            })
            getUserData()
        })
        .catch(() => {
            ElMessage({
                type: 'info',
                message: '取消删除',
            })
        })
}
const onEdit = (row) => {

    console.log("编辑", row);
    action.value = "edit"
    dialogVisible.value = true
    /* nextTick 确保在 DOM 更新完成之后再执行回调函数
    也就是编辑表单
    */
    nextTick(() => {
        formUser.value = {
            ...row,
        }
    }
    )
    /*   formUser.value = {
         ...row,
     } */
}

//这个方法之前定义过
const handleAdd = () => {
    action.value = "add"
    //打开对话窗
    dialogVisible.value = true
}

//对话框右上角的关闭事件
const handleClose = () => {
    //获取到表单dom,执行resetFields重置表单
    //关闭对话框
    dialogVisible.value = false
    proxy.$refs["userForm"].resetFields()
}

//对话框右下角的取消事件
const handleCancel = () => {
    dialogVisible.value = false
    proxy.$refs["userForm"].resetFields()
}

//格式化日期,格式化为:1997-01-02这种
const timeFormat = (time) => {
    var time = new Date(time);
    var year = time.getFullYear();
    var month = time.getMonth() + 1;
    var date = time.getDate();
    function add(m) {
        return m < 10 ? "0" + m : m;
    }
    return year + "-" + add(month) + "-" + add(date);
}
const onSubmit = async () => {
    // 获取表单数据
    console.log("添加的xxx", formUser.value);
    // 先进行校验
    proxy.$refs["userForm"].validate(async (validate) => {
        if (validate) {
            let res = null;
            //这里无论是新增或者是编辑,我们都要对这个日期进行一个格式化
            //如果不是1997-01-02这种格式,使用timeFormat方法进行格式化
            formUser.birth = /^\d{4}-\d{2}-\d{2}$/.test(formUser.birth)
                ? formUser.birth
                : timeFormat(formUser.birth)
            // 提交表单时候,还需要判断是add or edit
            if (action.value === "add") {
                res = await proxy.$apis.addUser(formUser.value)
            } else {
                res = await proxy.$apis.editUser(formUser.value)

            }
            if (res) {
                ElMessage({
                    type: 'success',
                    message: action.value === "add" ? '添加成功' : "编辑成功",
                })
                dialogVisible.value = false
                proxy.$refs["userForm"].resetFields()
                // 刷新页面数据
                getUserData()
            }
        }
        else {
            ElMessage({
                type: 'error',
                message: "请输入正确内容",
            })
        }
    })
    // 校验通过,执行添加操作
    proxy.$apis.addUser(formUser.value)


}
</script>
<style lang='less' scoped>
.user {
    height: 100%;

    .user-head {
        display: flex;
        justify-content: space-between;
    }

    .user-table {
        height: 540px;
        position: relative;

        .page {
            position: absolute;
            bottom: 50px;
            right: 50px;
        }

    }
}
</style>

5、每个组件之间需要共享配置导入pinia配置进行信息共享和传递。
pinia.js

import { defineStore } from 'pinia'
import { ref, watch } from 'vue'
function initData() {
    return {
        isCollapse: false,
        tags: [
            {
                path: '/home',
                name: 'home',
                label: '首页',
                icon: 'hone'
            }
        ],
        currentMenu: null,
        /* 展示菜单列表的数组 */
        menuList: [],
        token: null,
        routerList: [],
    }
}

export const useAllDataStore = defineStore('allData', () => {
    // 全部数据的获取和修改
    const state = ref(initData())

    // 进行数据持久化
    watch(state, newObj => {
        if (!newObj.token) return
        localStorage.setItem('store', JSON.stringify(newObj))
    }, {
        deep: true,
    })


    function selectMenu(val) {
        if (val.name === 'home') {
            state.value.currentMenu = null
        }
        else {
            state.value.currentMenu = val
            let index = state.value.tags.findIndex(item => item.name === val.name)
            index === -1 ? state.value.tags.push(val) : ""
        }
    }

    function deleteMenu(tag) {
        let index = state.value.tags.findIndex(item => item.name == tag.name)
        // 将当前tags切除
        state.value.tags.splice(index, 1);
    }

    function updateMenuList(val) {
        // 将当前tags切除
        state.value.menuList = val;
    }
    function clean() {
        // 将所有路由移除
        state.value.routerList.forEach(item => {
            if (item) item();
            state.value = initData();
            // 删除本地的缓存
            localStorage.removeItem('store')
        })
    }

    function addRoutes(router, type) {
        // 刷新页面时候
        if (type === 'refresh') {
            if (JSON.parse(localStorage.getItem('store'))) {
                state.value = JSON.parse(localStorage.getItem('store'))
                //
                state.value.routerList = []
            }
            else {
                return;
            }
        }
        // 将当前tags切除
        const menu = state.value.menuList;
        console.log("menu", menu);
        /* 执行该代码后  import.meta.glob可能返回的是这样的对象
  '@/views/Home.vue': () => import('@/views/Home.vue'),
  '@/views/About.vue': () => import('@/views/About.vue'),
  '@/views/User/Profile.vue': () => import('@/views/User/Profile.vue')
 */
        const module = import.meta.glob('../views/*.vue')
        console.log("module", module);
        const routeArr = []
        menu.forEach(item => {
            if (item.children) {
                item.children.forEach(child => {
                    let url = `../views/${child.url}.vue`
                    console.log("url", url);
                    child.component = module[url]
                    console.log("child.component", child.component);
                    routeArr.push(...item.children)
                })
            }
            else {
                let url = `../views/${item.url}.vue`
                console.log("url", url);
                item.component = module[url]
                console.log("item.component", item.component);
                routeArr.push(item)
            }
            routeArr.forEach(item => {
                state.value.routerList.push(router.addRoute("main", item));
            })
        })
        console.log("state.value.routerList", state.value.routerList);
        console.log("state.value.routeArr", routeArr);
    }
    return {
        /* 其实是直接返回的是state.value */
        state,
        selectMenu,
        deleteMenu,
        updateMenuList,
        addRoutes,
        clean,
    }
})

5、进行对应各个环境的配置环境设置

config.js

// 用于获取对应的环境变量
const env = process.env.NODE_ENV || "prod";
const EnvConfig = {
    development: {
        baseURL: "/api",
        mockApi: "https://mock.apipark.cn/m1/4068509-0-default/api"
    },
    test: {
        baseURL: "//test.xuyuan.com/api",
        mockApi: "https://mock.apipark.cn/m1/4068509-0-default/api"
    },
    prod: {
        baseURL: "//xuyuan.com/api",
        mockApi: "https://mock.apipark.cn/m1/4068509-0-default/api"
    },
}
export default {
    env,
    /* 将其重新解构成一个对象,并将其合并到默认配置中 */
    ...EnvConfig[env],
    isMock:false,
};

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;