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

打印 上一主题 下一主题

主题 999|帖子 999|积分 2997

一、项目先容


1、技能栈



  • vue3+vite+vue-router
  • pinia
  • element-plus
  • axios
  • mock
  • Echarts
2、业务功能



  • 登录
  • 首页
  • 商品
  • 用户管理
3、应用场景



  • 举行后台管理项目的
  • 根据不同用户的权限授予不同的功能
4、项目源码

xuyuan-upward 盼望每位大佬获取时候点个小小的赞!
二、项目实战

2.1、项目初始化

1、构建项目
  1. # npm 7+,需要添加额外的 --:
  2. npm create vite@latest my-vue-app -- --template vue
复制代码
2、安装成功后举行安装依靠
  1. npm install
复制代码
3、修改路径替换符
  1. import { defineConfig } from 'vite'
  2. import vue from '@vitejs/plugin-vue'
  3. // https://vite.dev/config/
  4. export default defineConfig({
  5.   plugins: [vue()],
  6.   /* 添加的是别名 */
  7.   resolve: {
  8.     alias: {
  9.       '@': '/src',
  10.     },
  11.   },
  12. })
复制代码
4、引入element-plus依靠,axios,router
安装依靠内容过于简单,请自行取相关内容官网查看文档举行安装
element-plus官网
axios官网
router
2.2、项目实战

1、引入router配置

  1. import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
  2. const routes = [
  3.   {
  4.     path: '/',
  5.     name: 'main',
  6.     component: () => import('@/views/RootView.vue'),
  7.     redirect: '/home',
  8.     /* 访问 /home 时的行为
  9. 当你访问 /home 时,会发生以下情况:
  10. 匹配父路由:
  11. 首先匹配到 path: '/' 的路由配置。
  12. 由于 Main.vue 是父组件,它会被渲染。
  13. 匹配子路由:
  14. 在 Main.vue 内部的 <router-view> 中,会匹配到 path: '/home' 的子路由。
  15. 因此,Home.vue 会在 Main.vue 的 <router-view> 中渲染。 */
  16.     children: [
  17.        {
  18.         path: '/home',
  19.         name: 'home',
  20.         component: () => import('@/views/MainView.vue')
  21.       },
  22.     ]
  23.   },
  24. ]
  25. const router = createRouter({
  26.   history: createWebHistory(),
  27.   routes
  28. })
  29. export default router
复制代码
接着还需要导入main.js一些配置文件


  • 全局样式
  • pinia等等
  1. import { createApp } from 'vue'
  2. import App from './App.vue'
  3. // 导入全局样式
  4. import "@/assets/less/index.less"
  5. import router from '@/router'
  6. import ElementPlus from 'element-plus'
  7. // 导入element-plus组件的全局样式
  8. import 'element-plus/dist/index.css'
  9. import * as ElementPlusIconsVue from '@element-plus/icons-vue'
  10. import { createPinia } from 'pinia'
  11. import { useAllDataStore } from '@/stores'
  12. /* 引入mock */
  13. import "@/apis/mock.js"
  14. /* 引入apis 管理请求接口 */
  15. import apis from "@/apis/apis.js"
  16. const pinia = createPinia()
  17. const app = createApp(App)
  18. /* 定义全局配置使用 */
  19. app.config.globalProperties.$apis = apis
  20. app.use(ElementPlus)
  21. app.use(pinia)
  22. // localStorage.removeItem("store")
  23. const store = useAllDataStore()
  24. store.addRoutes(router, "refresh")
  25. app.use(router)
  26. /*function isRoute(to) {
  27.     let routes = router.getRoutes()
  28.     console.log("routes", routes);
  29.     let resFil = routes.filter(item =>
  30.         /* 相当于return */
  31.         item.path === to.path
  32.     )
  33.     /*  let resFil = routes.filter(item => {
  34.      相当于一段代码,只有return为true时候才会保留对应的数据
  35.      item.path === to.path}
  36.       
  37.      ) */
  38.     return resFil
  39. }
  40. /*
  41. router.beforeEach((to, from, next) => {
  42.     console.log("store.state.token", store.state.token);
  43.     if (!store.state.token && to.path !== '/login') {
  44.         console.log("to.path1", to.path);
  45.         next({ name: 'login' })
  46.     }
  47.     if (store.state.token && to.path === '/login') {
  48.         console.log("to.path2", to.path);
  49.         next({ name: 'home' })
  50.     }
  51.     if (store.state.token && to.path !== '/login') {
  52.         console.log("to.path3", to.path);
  53.         console.log("isRoute", isRoute(to));
  54.         if (isRoute(to).length === 0) {
  55.             console.log("to.path3", to.path);
  56.             next({ name: '404' })
  57.         }
  58.     }
  59.     console.log("to.path4", to.path);
  60.     next()
  61. })
  62. */
  63. for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  64.     app.component(key, component)
  65. }
  66. app.mount('#app')
复制代码
2、App.vue组件引入

  1. <script setup>
  2. </script>
  3. <template>
  4.   <div class="app">
  5.     <router-view />
  6.   </div>
  7. </template>
  8. <style >
  9. #app,
  10. .app {
  11.   width: 100%;
  12.   height: 100%;
  13.   overflow: hidden;
  14. }
  15. </style>
复制代码
3、创建RootView根路由组件

  1. <template>
  2.   <div class="common-layout">
  3.     <el-container class="main-container">
  4.       <div class="aside-container">
  5.         <CommonAside />
  6.       </div>
  7.       <el-container class="right-container">
  8.         <el-header style="height: 40px;">
  9.           <CommonHeader />
  10.         </el-header>
  11.         <el-divider style="margin: 5px 0;" />
  12.         <CommonTab />
  13.         <el-main>
  14.           <router-view />
  15.         </el-main>
  16.         <el-footer>
  17.           <Footer />
  18.         </el-footer>
  19.       </el-container>
  20.     </el-container>
  21.   </div>
  22. </template>
  23. <script setup>
  24. import { reactive, toRefs, onMounted, nextTick } from 'vue'
  25. import CommonAside from '@/components/CommonAside.vue';
  26. import CommonHeader from '@/components/CommonHeader.vue';
  27. import CommonTab from '@/components/CommonTab.vue';
  28. import Footer from '@/components/Footer.vue';
  29. </script>
  30. <style lang='less' scoped>
  31. .common-layout,
  32. .main-container,
  33. right-container {
  34.   height: 100%;
  35.   background-color: #f0f2f5;
  36.   .el-main {
  37.     padding: 8px;
  38.   }
  39. }
  40. </style>
复制代码
4、依次创建

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


  • CommonAside.vue
  1. <template>
  2.     <!-- default-active通常与对应的index相关 -->
  3.     <el-aside :width="width">
  4.         <el-menu text-color="#fff" background-color="#545c64" :collapse="isCollapse" :collapse-transition="false"
  5.         :default-active="activeMenu"
  6.             class="el-menu-vertical-demo">
  7.             <div class="title">
  8.                 <i class="iconfont icon-quanpingtai"> </i>
  9.                 <h4 v-show="!isCollapse">许苑后台管理</h4>
  10.             </div>
  11.             <!-- 没有子菜单 -->
  12.             <el-menu-item v-for="item in noChildren" :key="item.path" :index="item.path" @click="handleMenu(item)">
  13.                 <component :is="item.icon" class="icon" />
  14.                 <span style="margin-left: 10px">{{ item.label }}</span>
  15.             </el-menu-item>
  16.             <!-- 有子菜单 -->
  17.             <el-sub-menu v-for="item in hasChildren" :key="item.path" :index="item.path">
  18.                 <template #title>
  19.                     <component :is="item.icon" class="icon" />
  20.                     <span style="margin-left: 10px">{{ item.label }}</span>
  21.                 </template>
  22.                 <el-menu-item-group>
  23.                     <el-menu-item v-for="(subItem) in item.children" :index="subItem.path" :key="subItem.path">
  24.                         <component :is="subItem.icon" class="icon" />
  25.                         <span>{{ subItem.label }}</span>
  26.                     </el-menu-item>
  27.                 </el-menu-item-group>
  28.             </el-sub-menu>
  29.         </el-menu>
  30.     </el-aside>
  31. </template>
  32. <script setup>
  33. import { ref, reactive, toRefs, onMounted, nextTick, computed } from 'vue'
  34. import { useRouter, useRoute } from 'vue-router'
  35. import { useAllDataStore } from '@/stores'
  36. const store = useAllDataStore()
  37. const router = useRouter()
  38. const route = useRoute()
  39. // 测试数据,初始化,刚开始可以使用,后续通过配置路由权限获取
  40. /* const list = ref([
  41.     {
  42.         path: '/home',
  43.         name: 'home',
  44.         label: '首页',
  45.         icon: 'house',
  46.         url: 'Home'
  47.     },
  48.     {
  49.         path: '/mall',
  50.         name: 'mall',
  51.         label: '商品管理',
  52.         icon: 'ShoppingBag',
  53.         url: 'Mall'
  54.     },
  55.     {
  56.         path: '/user',
  57.         name: 'user',
  58.         label: '用户管理',
  59.         icon: 'user',
  60.         url: 'User'
  61.     },
  62.     {
  63.         path: 'other',
  64.         label: '其他',
  65.         icon: 'location',
  66.         children: [
  67.             {
  68.                 path: '/page1',
  69.                 name: 'page1',
  70.                 label: '页面1',
  71.                 icon: 'setting',
  72.                 url: 'Page1'
  73.             },
  74.             {
  75.                 path: '/page2',
  76.                 name: 'page2',
  77.                 label: '页面2',
  78.                 icon: 'setting',
  79.                 url: 'Page2'
  80.             }
  81.         ]
  82.     }
  83. ])
  84. */
  85. const list = computed(() => store.state.menuList)
  86. const noChildren = computed(() => list.value.filter(item => !item.children))
  87. const hasChildren = computed(() => list.value.filter(item => item.children))
  88. const width = computed(() => store.state.isCollapse ? '60px' : '200px')
  89. // 涉及组件之间的传递 => 使用pinia进行各组件之间的传递
  90. const isCollapse = computed(() => store.state.isCollapse)
  91. const activeMenu = computed(() => route.path)
  92. const handleMenu = (item) => {
  93.     if (item.children) {
  94.         return
  95.     }
  96.     router.push(item.path)
  97.     store.selectMenu(item)
  98. }
  99. </script>
  100. <style lang='less' scoped>
  101. .icon {
  102.     width: 18px;
  103.     height: 18px;
  104.     margin-right: 5px;
  105. }
  106. .el-aside {
  107.     background-color: #545c64;
  108.     height: 100vh;
  109.     .el-menu {
  110.         border-right: none;
  111.         .title {
  112.             display: flex;
  113.             align-items: center;
  114.             justify-content: center;
  115.         }
  116.         h4 {
  117.             color: #fff;
  118.             font-size: 17px;
  119.             margin: 20px;
  120.             font-weight: 500px;
  121.             text-align: center;
  122.         }
  123.     }
  124. }
  125. </style>
复制代码


  • CommonHeader.vue
  1. <template>
  2.     <div class="header">
  3.         <div class="l-header">
  4.             <el-button size="small" @click="store.state.isCollapse = !store.state.isCollapse">
  5.                 <el-icon>
  6.                     <Menu />
  7.                 </el-icon>
  8.             </el-button>
  9.         
  10.         </div>
  11.         <div class="r-header">
  12.             <el-dropdown>
  13.                 <span class="el-dropdown-link">
  14.                     <img src="@/assets/images/xuyuan.jpg" alt="" class="r-header-avatar">
  15.                 </span>
  16.                 <template #dropdown>
  17.                     <el-dropdown-menu>
  18.                         <el-dropdown-item>个人中心</el-dropdown-item>
  19.                         <el-dropdown-item @click="handlerLogout">退出登录</el-dropdown-item>
  20.                     </el-dropdown-menu>
  21.                 </template>
  22.             </el-dropdown>
  23.         </div>
  24.     </div>
  25. </template>
  26. <script setup>
  27. import { ref, reactive, toRefs, onMounted, nextTick, computed } from 'vue'
  28. import { useAllDataStore } from '@/stores'
  29. import { ArrowRight } from '@element-plus/icons-vue'
  30. import { useRouter } from 'vue-router'
  31. const store = useAllDataStore()
  32. const router = useRouter()
  33. let currentPath = computed(() => {
  34.     console.log("store.state.currentMenu", store.state.currentMenu);
  35.     return store.state.currentMenu;
  36. })
  37. const handlerLogout = () => {
  38.     store.clean()
  39.     router.push('/login')
  40. }
  41. </script>
  42. <style lang='less' scoped>
  43. .header {
  44.     width: 100%;
  45.     height: 100%;
  46.     display: flex;
  47.     justify-content: space-between;
  48.     align-items: center;
  49.     .l-header {
  50.         display: flex;
  51.         justify-content: center;
  52.         align-items: center;
  53.         .el-button {
  54.             margin-right: 15px;
  55.         }
  56.     }
  57. }
  58. .r-header-avatar {
  59.     width: 30px;
  60.     height: 30px;
  61.     border-radius: 50%;
  62.     margin-right: 10px;
  63. }
  64. </style>
  65.     <!--  -- 面包屑功能 <el-breadcrumb :separator-icon="ArrowRight">
  66.                
  67.                 <el-breadcrumb-item :to=" '/' ">首页</el-breadcrumb-item>
  68.                 <el-breadcrumb-item v-if="currentPath" :to="currentPath.path" @cl>{{ currentPath.label
  69.                 }}
  70.                 </el-breadcrumb-item>
  71.             </el-breadcrumb> -->
复制代码


  • Footer.vue
  1. <template>
  2.     <div class="footer">
  3.         <a href="https://github.com/xuyuan-upward" class="toLearn">
  4.             <span> <i class="iconfont icon-github"></i>站长: 许苑向上</span>
  5.         </a>
  6.         <a href="https://blog.csdn.net/a147775" class="toLearn">
  7.             <span> <i class="iconfont icon-bokeyuan"></i>博客: xuyuan-upward</span>
  8.         </a>
  9.         <a href="https://user.qzone.qq.com/2517115657/main" class="toLearn">
  10.             <span> <i class="iconfont icon-shouye"></i>联系方式: 许苑向上</span>
  11.         </a>
  12.     </div>
  13. </template>
  14. <script setup>
  15. import { ref, reactive, toRefs, onMounted, nextTick } from 'vue'
  16. </script>
  17. <style lang='less' scoped>
  18. .toGithub {
  19.     text-decoration: none;
  20.     font-size: 14px;
  21.     font-weight: bold;
  22.     padding: 10px 0;
  23.     display: block;
  24.     text-align: center;
  25.     border-radius: 5px;
  26.     transition: all 0.3s ease-in-out;
  27. }
  28. .iconfont {
  29.     margin-right: 5px;
  30. }
  31. .footer {
  32.     height: 100%;
  33.     display: flex;
  34.     justify-content: center;
  35.     a {
  36.         margin-right: 40px;
  37.         color: #00000073;
  38.         span {
  39.             line-height: 60px;
  40.         }
  41.     }
  42. }
  43. </style>
复制代码


  • MainView.vue路由组件
  1. <template>
  2.     <div class="home">
  3.         <el-row>
  4.             <!-- 左侧 -->
  5.             <el-col :span="7">
  6.                 <div class="l-user">
  7.                     <el-card style="max-width: 480px" shadow="hover" class="user-info">
  8.                         <div class="user">
  9.                             <img src="@/assets/images/xuyuan.jpg" alt=""
  10.                                 style="width: 100px;height: 100px;border-radius: 50%;margin-right: 10px;">
  11.                             <div class="userInfo">
  12.                                 <h>admin</h>
  13.                                 <p style="margin-top: 20px; color: #999;">超级管理员</p>
  14.                             </div>
  15.                         </div>
  16.                         <el-divider />
  17.                         <div class="login-info">
  18.                             <p>上次登录时间:<span>2024-11-18 1:00:00</span></p>
  19.                             <p style="margin-top: 10px;">上次登录地点:<span>广西</span></p>
  20.                         </div>
  21.                     </el-card>
  22.                     <el-card style="max-width: 480px" shadow="hover" class="user-table">
  23.                         <el-table :data="tableData" style="width: 100%">
  24.                             <!-- 遍历val是值 key是键 -->
  25.                             <el-table-column v-for="(val, key) in tableLabel" :key="key" :prop="key" :label="val">
  26.                             </el-table-column>
  27.                         </el-table>
  28.                     </el-card>
  29.                 </div>
  30.             </el-col>
  31.             <!-- 右侧 -->
  32.             <el-col :span="17">
  33.                 <div class="r-echart">
  34.                     <div class="top">
  35.                         <el-card v-for="(item) in counterData" :key="item.name"
  36.                             :body-style="{ padding: '20px', display: 'flex' }" shadow="hover">
  37.                             <component :is="item.icon" class="icons" :style="{ background: item.color }" />
  38.                             <div class="detail">
  39.                                 <p class="num">¥{{ item.value }}</p>
  40.                                 <p class="txt">¥{{ item.name }}</p>
  41.                             </div>
  42.                         </el-card>
  43.                     </div>
  44.                     <div class="bottom">
  45.                         <!-- 三个图表容器 -->
  46.                         <div class="echart-top">
  47.                             <el-card shadow="hover">
  48.                                 <div ref="echart" style="height: 220px;"></div>
  49.                             </el-card>
  50.                         </div>
  51.                         <div class="echart-bottom">
  52.                             <el-card shadow="hover">
  53.                                 <div ref="userEchart" style="height: 140px"></div>
  54.                             </el-card>
  55.                             <el-card shadow="hover">
  56.                                 <div ref="videoEchart" style="height: 140px"></div>
  57.                             </el-card>
  58.                         </div>
  59.                     </div>
  60.                 </div>
  61.             </el-col>
  62.         </el-row>
  63.     </div>
  64. </template>
  65. <script setup>
  66. import { ref, reactive, toRefs, onMounted, nextTick, getCurrentInstance } from 'vue'
  67. import * as echarts from 'echarts';
  68. //这个tableData是假数据,等会我们使用axios请求mock数据
  69. const { proxy } = getCurrentInstance()
  70. const tableData = ref([])
  71. const counterData = ref([])
  72. //observer 接收观察器实例对象
  73. const observer = ref(null)
  74. //这个是折线图和柱状图 两个图表共用的公共配置
  75. const xOptions = reactive({
  76.     // 图例文字颜色
  77.     textStyle: {
  78.         color: "#333",
  79.     },
  80.     legend: {},
  81.     grid: {
  82.         left: "20%",
  83.     },
  84.     // 提示框
  85.     tooltip: {
  86.         trigger: "axis",
  87.     },
  88.     xAxis: {
  89.         type: "category", // 类目轴
  90.         data: [],
  91.         axisLine: {
  92.             lineStyle: {
  93.                 color: "#17b3a3",
  94.             },
  95.         },
  96.         axisLabel: {
  97.             interval: 0,
  98.             color: "#333",
  99.         },
  100.     },
  101.     yAxis: [
  102.         {
  103.             type: "value",
  104.             axisLine: {
  105.                 lineStyle: {
  106.                     color: "#17b3a3",
  107.                 },
  108.             },
  109.         },
  110.     ],
  111.     color: ["#2ec7c9", "#b6a2de", "#5ab1ef", "#ffb980", "#d87a80", "#8d98b3"],
  112.     series: [],
  113. })
  114. const pieOptions = reactive({
  115.     tooltip: {
  116.         trigger: "item",
  117.     },
  118.     legend: {},
  119.     color: [
  120.         "#0f78f4",
  121.         "#dd536b",
  122.         "#9462e5",
  123.         "#a6a6a6",
  124.         "#e1bb22",
  125.         "#39c362",
  126.         "#3ed1cf",
  127.     ],
  128.     series: []
  129. })
  130. const getTableData = async () => {
  131.     const data = await proxy.$apis.getTableData()
  132.     console.log("home,tableData获取到的数据:", data);
  133.     tableData.value = data.tableData
  134. }
  135. const getCounterData = async () => {
  136.     const data = await proxy.$apis.getCounterData()
  137.     console.log("home,counterData获取到的数据:", data);
  138.     counterData.value = data
  139. }
  140. const getChartData = async () => {
  141.     // 获取图标信息 ,解构
  142.     const { orderData, userData, videoData } = await proxy.$apis.getChartData()
  143.     console.log("home,orderData获取到的数据:", orderData);
  144.     //对第一个图表的xAxis和series赋值
  145.     xOptions.xAxis.data = orderData.date
  146.     xOptions.series = Object.keys(orderData.data[0]).map(val => {
  147.         return {
  148.             name: val,
  149.             data: orderData.data.map(item => item[val]),
  150.             type: "line"
  151.         }
  152.     }
  153.     )
  154.     //one               echarts.init方法初始化ECharts实例,需要传入dom对象
  155.     const OneEcharts = echarts.init(proxy.$refs["echart"])
  156.     //setOption方法应用配置对象
  157.     OneEcharts.setOption(xOptions)
  158.     //对第二个图表的xAxis和series赋值
  159.     xOptions.xAxis.data = userData.map((item) => item.date)
  160.     xOptions.series = [
  161.         {
  162.             name: "新增用户",
  163.             data: userData.map((item) => item.new),
  164.             type: "bar",
  165.         },
  166.         {
  167.             name: "活跃用户",
  168.             data: userData.map((item) => item.active),
  169.             type: "bar",
  170.         }
  171.     ]
  172.     //two
  173.     const TwoEcharts = echarts.init(proxy.$refs["userEchart"])
  174.     TwoEcharts.setOption(xOptions)
  175.     //对第三个图表的series赋值
  176.     pieOptions.series = [
  177.         {
  178.             data: videoData,
  179.             type: "pie",
  180.         },
  181.     ]
  182.     //three
  183.     const ThreeEcharts = echarts.init(proxy.$refs["videoEchart"])
  184.     ThreeEcharts.setOption(pieOptions);
  185.     //ResizeObserver 如果监视的容器大小变化,如果改变会执行传递的回调
  186.     observer.value = new ResizeObserver(entries => {
  187.         OneEcharts.resize()
  188.         TwoEcharts.resize()
  189.         ThreeEcharts.resize()
  190.     })
  191.     //如果这个容器存在
  192.     if (proxy.$refs["echart"]) {
  193.         //则调用监视器的observe方法,监视这个容器的大小
  194.         observer.value.observe(proxy.$refs["echart"]);
  195.     }
  196. }
  197. onMounted(() => {
  198.     getTableData()
  199.     getCounterData()
  200.     getChartData()
  201.     console.log(proxy);
  202. })
  203. const tableLabel = ref({
  204.     name: "课程",
  205.     todayBuy: "今日购买",
  206.     monthBuy: "本月购买",
  207.     totalBuy: "总购买",
  208. })
  209. </script>
  210. <style lang='less' scoped>
  211. .home {
  212.     height: 100%;
  213.     overflow: hidden;
  214.     .l-user {
  215.         .user-info {
  216.             .user {
  217.                 display: flex;
  218.                 align-items: center;
  219.                 .userInfo {
  220.                     margin-left: 30px;
  221.                 }
  222.             }
  223.             .login-info {
  224.                 p {
  225.                     font-size: 14px;
  226.                     color: #999;
  227.                     span {
  228.                         color: #666;
  229.                         margin-left: 30px;
  230.                     }
  231.                 }
  232.             }
  233.         }
  234.         .user-table {
  235.             margin-top: 50px;
  236.         }
  237.     }
  238.     .r-echart {
  239.         .top {
  240.             display: flex;
  241.             justify-content: space-between;
  242.             flex-wrap: wrap;
  243.             .el-card {
  244.                 width: 30%;
  245.                 margin-bottom: 10px;
  246.                 margin-left: 20px;
  247.             }
  248.             .icons {
  249.                 width: 50px;
  250.                 height: 50px;
  251.                 border-radius: 50%;
  252.                 margin-right: 20px;
  253.             }
  254.             .detail {
  255.                 display: flex;
  256.                 flex-direction: column;
  257.                 justify-content: center;
  258.                 .num {
  259.                     margin-bottom: 10px;
  260.                 }
  261.             }
  262.         }
  263.         .bottom {
  264.             margin-left: 20px;
  265.             .echart-top {
  266.                 margin-bottom: 20px;
  267.             }
  268.             .echart-bottom {
  269.                 display: flex;
  270.                 justify-content: space-between;
  271.                 align-items: center;
  272.                 .el-card {
  273.                     width: 48%;
  274.                 }
  275.             }
  276.         }
  277.     }
  278. }
  279. </style>
复制代码


  • UserView.vue组件
  1. <template>
  2.     <div class="user">
  3.         <div class="user-head">
  4.             <el-button type="primary" @click="handleAdd">新增</el-button>
  5.             <el-form :inline="true" :model="formData">
  6.                 <el-form-item label="请输入">
  7.                     <el-input placeholder="请输入姓名" v-model="formData.keyWord"></el-input>
  8.                 </el-form-item>
  9.                 <el-form-item>
  10.                     <el-button type="primary" @click="handlerSearch">搜索</el-button>
  11.                 </el-form-item>
  12.             </el-form>
  13.         </div>
  14.         <div class="user-table">
  15.             <el-dialog v-model="dialogVisible" :title="action == 'add' ? '新增用户' : '编辑用户'" width="35%"
  16.                 :before-close="handleClose">
  17.                 <!--需要注意的是设置了:inline="true",
  18.                 会对el-select的样式造成影响,我们通过给他设置一个class=select-
  19.                 在css进行处理-->
  20.                 <el-form :inline="true" :model="formUser" :rules="rules" ref="userForm">
  21.                     <el-row>
  22.                         <el-col :span="12">
  23.                             <el-form-item label="姓名" prop="name">
  24.                                 <el-input v-model="formUser.name" placeholder="请输入姓名" />
  25.                             </el-form-item>
  26.                         </el-col>
  27.                         <el-col :span="12">
  28.                             <el-form-item label="年龄" prop="age">
  29.                                 <el-input v-model.number="formUser.age" placeholder="请输入年龄" />
  30.                             </el-form-item>
  31.                         </el-col>
  32.                     </el-row>
  33.                     <el-row>
  34.                         <el-col :span="12">
  35.                             <!-- 使用:inline="true"会对select造成影响,此时长度应该设置最大 -->
  36.                             <el-form-item label="性别" prop="sex" style="width: 80%;">
  37.                                 <el-select v-model="formUser.sex" placeholder="请选择" class="select-clean">
  38.                                     <el-option label="男" :value="1" />
  39.                                     <!-- 注意这里的 :value 表示绑定一个表达式即所谓的"1" 其实代表的是number类型1 -->
  40.                                     <el-option label="女" :value="0" />
  41.                                 </el-select>
  42.                             </el-form-item>
  43.                         </el-col>
  44.                         <el-col :span="12">
  45.                             <el-form-item label="出生日期" prop="birth">
  46.                                 <el-date-picker v-model="formUser.birth" type="date" placeholder="请输入"
  47.                                     style="width: 100%" />
  48.                             </el-form-item>
  49.                         </el-col>
  50.                     </el-row>
  51.                     <el-row>
  52.                         <el-form-item label="地址" prop="addr">
  53.                             <el-input v-model="formUser.addr" placeholder="请输入地址" />
  54.                         </el-form-item>
  55.                     </el-row>
  56.                     <el-row style="justify-content: flex-end">
  57.                         <el-form-item>
  58.                             <el-button type="primary" @click="handleCancel">取消</el-button>
  59.                             <el-button type="primary" @click="onSubmit">确定</el-button>
  60.                         </el-form-item>
  61.                     </el-row>
  62.                 </el-form>
  63.             </el-dialog>
  64.             <el-table :data="tableData" style="width: 100%">
  65.                 <el-table-column v-for="item in tableLabel" :key="item.prop" :prop="item.prop" :label="item.label"
  66.                     :width="item.width" />
  67.                 <el-table-column fixed="right" label="Operations" min-width="120">
  68.                     <template #="scoped">
  69.                         <el-button type="primary" size="small" @click="onEdit(scoped.row)">
  70.                             编辑
  71.                         </el-button>
  72.                         <el-button type="danger" size="small" @click="onDelete(scoped.row)">删除</el-button>
  73.                     </template>
  74.                 </el-table-column>
  75.             </el-table>
  76.             <el-pagination layout="prev, pager, next" :total="config.total" @current-change="handlerChangePage"
  77.                 class="page" />
  78.         </div>
  79.     </div>
  80. </template>
  81. <script setup>
  82. import { ref, reactive, toRefs, onMounted, nextTick, getCurrentInstance } from 'vue'
  83. import { ElMessage, ElMessageBox } from 'element-plus'
  84. const { proxy } = getCurrentInstance()
  85. const tableData = ref([])
  86. const tableLabel = reactive([
  87.     {
  88.         prop: "name",
  89.         label: "姓名",
  90.     },
  91.     {
  92.         prop: "age",
  93.         label: "年龄",
  94.     },
  95.     {
  96.         prop: "sex",
  97.         label: "性别",
  98.     },
  99.     {
  100.         prop: "birth",
  101.         label: "出生日期",
  102.         width: 200,
  103.     },
  104.     {
  105.         prop: "addr",
  106.         label: "地址",
  107.         width: 400,
  108.     },
  109. ])
  110. const config = reactive({
  111.     name: "",
  112.     total: 0,
  113. }
  114. )
  115. const formData = reactive({
  116.     keyWord: ""
  117. })
  118. const dialogVisible = ref(false)
  119. const action = ref("add")
  120. const formUser = ref({
  121.     sex: 0,
  122. })
  123. //表单校验规则
  124. const rules = reactive({
  125.     name: [{ required: true, message: "姓名是必填项", trigger: "blur" }],
  126.     age: [
  127.         { required: true, message: "年龄是必填项", trigger: "blur" },
  128.         { type: "number", message: "年龄必须是数字" },
  129.     ],
  130.     sex: [{ required: true, message: "性别是必选项", trigger: "change" }],
  131.     birth: [{ required: true, message: "出生日期是必选项" }],
  132.     addr: [{ required: true, message: '地址是必填项' }]
  133. })
  134. const handlerSearch = () => {
  135.     console.log("搜索", formData.keyWord);
  136.     config.name = formData.keyWord
  137.     // console.log("搜索", searchText);
  138.     getUserData(config)
  139. }
  140. const getUserData = async (query) => {
  141.     const data = await proxy.$apis.getUserData(query)
  142.     console.log("UserView的数据", data);
  143.     config.total = data.count
  144.     tableData.value = data.list.map((item) => {
  145.         return {
  146.             ...item,
  147.             sex: item.sex === 1 ? '女' : '男'
  148.         }
  149.     })
  150. }
  151. onMounted(() => {
  152.     getUserData()
  153. })
  154. const handlerChangePage = (value) => {
  155.     console.log("当前页码", value);
  156.     config.page = value
  157.     getUserData(config)
  158. }
  159. const onDelete = (row) => {
  160.     console.log("删除", row);
  161.     ElMessageBox.confirm(
  162.         '你确定要删除吗?',
  163.         '删除提示',
  164.         {
  165.             confirmButtonText: '确定删除',
  166.             cancelButtonText: '取消',
  167.             type: 'danger   ',
  168.         }
  169.     )
  170.         .then(() => {
  171.             proxy.$apis.deleteUser({ id: row.id })
  172.             ElMessage({
  173.                 type: 'success',
  174.                 message: '删除成功',
  175.             })
  176.             getUserData()
  177.         })
  178.         .catch(() => {
  179.             ElMessage({
  180.                 type: 'info',
  181.                 message: '取消删除',
  182.             })
  183.         })
  184. }
  185. const onEdit = (row) => {
  186.     console.log("编辑", row);
  187.     action.value = "edit"
  188.     dialogVisible.value = true
  189.     /* nextTick 确保在 DOM 更新完成之后再执行回调函数
  190.     也就是编辑表单
  191.     */
  192.     nextTick(() => {
  193.         formUser.value = {
  194.             ...row,
  195.         }
  196.     }
  197.     )
  198.     /*   formUser.value = {
  199.          ...row,
  200.      } */
  201. }
  202. //这个方法之前定义过
  203. const handleAdd = () => {
  204.     action.value = "add"
  205.     //打开对话窗
  206.     dialogVisible.value = true
  207. }
  208. //对话框右上角的关闭事件
  209. const handleClose = () => {
  210.     //获取到表单dom,执行resetFields重置表单
  211.     //关闭对话框
  212.     dialogVisible.value = false
  213.     proxy.$refs["userForm"].resetFields()
  214. }
  215. //对话框右下角的取消事件
  216. const handleCancel = () => {
  217.     dialogVisible.value = false
  218.     proxy.$refs["userForm"].resetFields()
  219. }
  220. //格式化日期,格式化为:1997-01-02这种
  221. const timeFormat = (time) => {
  222.     var time = new Date(time);
  223.     var year = time.getFullYear();
  224.     var month = time.getMonth() + 1;
  225.     var date = time.getDate();
  226.     function add(m) {
  227.         return m < 10 ? "0" + m : m;
  228.     }
  229.     return year + "-" + add(month) + "-" + add(date);
  230. }
  231. const onSubmit = async () => {
  232.     // 获取表单数据
  233.     console.log("添加的xxx", formUser.value);
  234.     // 先进行校验
  235.     proxy.$refs["userForm"].validate(async (validate) => {
  236.         if (validate) {
  237.             let res = null;
  238.             //这里无论是新增或者是编辑,我们都要对这个日期进行一个格式化
  239.             //如果不是1997-01-02这种格式,使用timeFormat方法进行格式化
  240.             formUser.birth = /^\d{4}-\d{2}-\d{2}$/.test(formUser.birth)
  241.                 ? formUser.birth
  242.                 : timeFormat(formUser.birth)
  243.             // 提交表单时候,还需要判断是add or edit
  244.             if (action.value === "add") {
  245.                 res = await proxy.$apis.addUser(formUser.value)
  246.             } else {
  247.                 res = await proxy.$apis.editUser(formUser.value)
  248.             }
  249.             if (res) {
  250.                 ElMessage({
  251.                     type: 'success',
  252.                     message: action.value === "add" ? '添加成功' : "编辑成功",
  253.                 })
  254.                 dialogVisible.value = false
  255.                 proxy.$refs["userForm"].resetFields()
  256.                 // 刷新页面数据
  257.                 getUserData()
  258.             }
  259.         }
  260.         else {
  261.             ElMessage({
  262.                 type: 'error',
  263.                 message: "请输入正确内容",
  264.             })
  265.         }
  266.     })
  267.     // 校验通过,执行添加操作
  268.     proxy.$apis.addUser(formUser.value)
  269. }
  270. </script>
  271. <style lang='less' scoped>
  272. .user {
  273.     height: 100%;
  274.     .user-head {
  275.         display: flex;
  276.         justify-content: space-between;
  277.     }
  278.     .user-table {
  279.         height: 540px;
  280.         position: relative;
  281.         .page {
  282.             position: absolute;
  283.             bottom: 50px;
  284.             right: 50px;
  285.         }
  286.     }
  287. }
  288. </style>
复制代码
5、每个组件之间需要共享配置导入pinia配置举行信息共享和传递。
pinia.js
  1. import { defineStore } from 'pinia'
  2. import { ref, watch } from 'vue'
  3. function initData() {
  4.     return {
  5.         isCollapse: false,
  6.         tags: [
  7.             {
  8.                 path: '/home',
  9.                 name: 'home',
  10.                 label: '首页',
  11.                 icon: 'hone'
  12.             }
  13.         ],
  14.         currentMenu: null,
  15.         /* 展示菜单列表的数组 */
  16.         menuList: [],
  17.         token: null,
  18.         routerList: [],
  19.     }
  20. }
  21. export const useAllDataStore = defineStore('allData', () => {
  22.     // 全部数据的获取和修改
  23.     const state = ref(initData())
  24.     // 进行数据持久化
  25.     watch(state, newObj => {
  26.         if (!newObj.token) return
  27.         localStorage.setItem('store', JSON.stringify(newObj))
  28.     }, {
  29.         deep: true,
  30.     })
  31.     function selectMenu(val) {
  32.         if (val.name === 'home') {
  33.             state.value.currentMenu = null
  34.         }
  35.         else {
  36.             state.value.currentMenu = val
  37.             let index = state.value.tags.findIndex(item => item.name === val.name)
  38.             index === -1 ? state.value.tags.push(val) : ""
  39.         }
  40.     }
  41.     function deleteMenu(tag) {
  42.         let index = state.value.tags.findIndex(item => item.name == tag.name)
  43.         // 将当前tags切除
  44.         state.value.tags.splice(index, 1);
  45.     }
  46.     function updateMenuList(val) {
  47.         // 将当前tags切除
  48.         state.value.menuList = val;
  49.     }
  50.     function clean() {
  51.         // 将所有路由移除
  52.         state.value.routerList.forEach(item => {
  53.             if (item) item();
  54.             state.value = initData();
  55.             // 删除本地的缓存
  56.             localStorage.removeItem('store')
  57.         })
  58.     }
  59.     function addRoutes(router, type) {
  60.         // 刷新页面时候
  61.         if (type === 'refresh') {
  62.             if (JSON.parse(localStorage.getItem('store'))) {
  63.                 state.value = JSON.parse(localStorage.getItem('store'))
  64.                 //
  65.                 state.value.routerList = []
  66.             }
  67.             else {
  68.                 return;
  69.             }
  70.         }
  71.         // 将当前tags切除
  72.         const menu = state.value.menuList;
  73.         console.log("menu", menu);
  74.         /* 执行该代码后  import.meta.glob可能返回的是这样的对象
  75.   '@/views/Home.vue': () => import('@/views/Home.vue'),
  76.   '@/views/About.vue': () => import('@/views/About.vue'),
  77.   '@/views/User/Profile.vue': () => import('@/views/User/Profile.vue')
  78. */
  79.         const module = import.meta.glob('../views/*.vue')
  80.         console.log("module", module);
  81.         const routeArr = []
  82.         menu.forEach(item => {
  83.             if (item.children) {
  84.                 item.children.forEach(child => {
  85.                     let url = `../views/${child.url}.vue`
  86.                     console.log("url", url);
  87.                     child.component = module[url]
  88.                     console.log("child.component", child.component);
  89.                     routeArr.push(...item.children)
  90.                 })
  91.             }
  92.             else {
  93.                 let url = `../views/${item.url}.vue`
  94.                 console.log("url", url);
  95.                 item.component = module[url]
  96.                 console.log("item.component", item.component);
  97.                 routeArr.push(item)
  98.             }
  99.             routeArr.forEach(item => {
  100.                 state.value.routerList.push(router.addRoute("main", item));
  101.             })
  102.         })
  103.         console.log("state.value.routerList", state.value.routerList);
  104.         console.log("state.value.routeArr", routeArr);
  105.     }
  106.     return {
  107.         /* 其实是直接返回的是state.value */
  108.         state,
  109.         selectMenu,
  110.         deleteMenu,
  111.         updateMenuList,
  112.         addRoutes,
  113.         clean,
  114.     }
  115. })
复制代码
5、举行对应各个环境的配置环境设置

config.js
  1. // 用于获取对应的环境变量
  2. const env = process.env.NODE_ENV || "prod";
  3. const EnvConfig = {
  4.     development: {
  5.         baseURL: "/api",
  6.         mockApi: "https://mock.apipark.cn/m1/4068509-0-default/api"
  7.     },
  8.     test: {
  9.         baseURL: "//test.xuyuan.com/api",
  10.         mockApi: "https://mock.apipark.cn/m1/4068509-0-default/api"
  11.     },
  12.     prod: {
  13.         baseURL: "//xuyuan.com/api",
  14.         mockApi: "https://mock.apipark.cn/m1/4068509-0-default/api"
  15.     },
  16. }
  17. export default {
  18.     env,
  19.     /* 将其重新解构成一个对象,并将其合并到默认配置中 */
  20.     ...EnvConfig[env],
  21.     isMock:false,
  22. };
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

万有斥力

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表