万有斥力 发表于 2024-12-6 18:16:17

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

一、项目先容

https://i-blog.csdnimg.cn/direct/b05a5eabfbaa40339e2b8d9db91cde5c.png
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: ,
/* 添加的是别名 */
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 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).map(val => {
      return {
            name: val,
            data: orderData.data.map(item => item),
            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
                  console.log("child.component", child.component);
                  routeArr.push(...item.children)
                })
            }
            else {
                let url = `../views/${item.url}.vue`
                console.log("url", url);
                item.component = module
                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,
    isMock:false,
};

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Vue3项目实战(vue3+vite+pinia+element-plus+axios+mock)