0


vue3后台管理系统 vue3+vite+pinia+element-plus+axios上

前言

项目安装与启动

使用vite作为项目脚手架

  1. # pnpm
  2. pnpm create vite my-vue-app --template vue

安装相应依赖

  1. # sass
  2. pnpm i sass
  3. # vue-router
  4. pnpm i vue-router
  5. # element-plus
  6. pnpm i element-plus
  7. # element-plus/icon
  8. pnpm i @element-plus/icons-vue

安装element-plus按需自动引入插件

  1. pnpm install -D unplugin-vue-components unplugin-auto-import

并在vite.config.js中配置

  1. import{ defineConfig }from'vite'import vue from'@vitejs/plugin-vue'import AutoImport from'unplugin-auto-import/vite'import Components from'unplugin-vue-components/vite'import{ ElementPlusResolver }from'unplugin-vue-components/resolvers'exportdefaultdefineConfig({plugins:[vue(),AutoImport({resolvers:[ElementPlusResolver()],}),Components({resolvers:[ElementPlusResolver()],}),],resolve:{alias:{'@':'/src'}}})

注册elementplus的icon库

  1. import*as ElementPlusIconsVue from'@element-plus/icons-vue'const app =createApp(App)
  2. app.use(router).mount('#app')for(const[key, component]of Object.entries(ElementPlusIconsVue)){
  3. app.component(key, component)}

删除helloworld组件和style.css 删除App.vue相应代码

在src目录下创建router文件夹在其中创建index.js 并配置基本内容
  1. import{createRouter,createWebHashHistory}from'vue-router'const routes =[{path:'/',name:'main',redirect:"/home",component:()=>import("@/views/main.vue"),children:[path:"/home",name:"Home",component:()=>import("@/views/Home.vue")]}]const router =createRouter({history:createWebHashHistory(),
  2. routes
  3. })exportdefault router

并在main.js中注册和在App.vue中使用

  1. //main.js
  2. app.use(router).mount('#app')//App.vue<template><router-view></router-view></template>
创建views文件夹,创建Main.vue文件

使用element-plus组件库container布局容器
请添加图片描述

  1. <template>
  2. <div class="commom-layout">
  3. <el-container class="lay-container">
  4. <common-aside></common-aside>
  5. <el-container>
  6. <el-header class="el-header">
  7. <commonHeader></commonHeader>
  8. </el-header>
  9. <common-tab></common-tab>
  10. <el-main class="right-main">
  11. <RouterView></RouterView>
  12. </el-main>
  13. </el-container>
  14. </el-container>
  15. </div>
  16. </template>
  17. <script setup>
  18. import commonAside from '../components/commonAside.vue';
  19. import commonHeader from '../components/commonHeader.vue';
  20. import commonTab from '../components/commonTab.vue';
  21. </script>
  22. <style scoped lang="scss">
  23. .commom-layout, .lay-container{
  24. height:100%;
  25. }
  26. .el-header{
  27. background-color: #333;
  28. }
  29. </style>
布局已经搭好 让我们完成里面的组件

创建component文件夹创建common-aside.vue文件,也就是el-aside中套了一个el-menu,并使用vue-router中跳转路由,并用pinia
状态管理工具 来控制兄弟组件中的通信
所以需要先引入pinia,并创建store文件夹与index.js文件

  1. pnpm i pinia
  1. //在main.jsconst pinia =createPinia()
  2. app.use(pinia)// store/index.jsimport{ defineStore }from'pinia'state:()=>{return{isCollapse:true,menuList:[],}},
  1. <template>
  2. <el-aside :width="MenuWidth">
  3. <el-menu
  4. background-color="#545c64"
  5. text-color="#fff"
  6. :collapse="isCollapse"
  7. :collapse-transition="false"
  8. :default-active="activeMenu"
  9. >
  10. <h3 v-show="!isCollapse">通用后台管理系统</h3>
  11. <h3 v-show="isCollapse">后台</h3>
  12. <el-menu-item v-for="item in noChildren" :index="item.path" :key="item.path"
  13. @click="handleMenu(item)"
  14. >
  15. <component class="icons" :is="item.icon"></component>
  16. <span>{{ item.label }}</span>
  17. </el-menu-item>
  18. <el-sub-menu v-for="item in hasChildren" :index="item.path" :key="item.path">
  19. <template #title>
  20. <component class="icons" :is="item.icon"></component>
  21. <span>{{ item.label }}</span>
  22. </template>
  23. <el-menu-item-group>
  24. <el-menu-item
  25. v-for="(subItem,subIndex) in item.children"
  26. :key="subItem.path"
  27. :subIndex="subItem.path"
  28. @click="handleMenu(subItem)"
  29. >
  30. <component class="icons" :is="subItem.icon"></component>
  31. <span>{{ subItem.label }}</span>
  32. </el-menu-item>
  33. </el-menu-item-group>
  34. </el-sub-menu>
  35. </el-menu>
  36. </el-aside>
  37. </template>
  38. <script setup>
  39. import { ref, computed } from 'vue'
  40. import { useRouter ,useRoute} from 'vue-router';
  41. import { useAllDataStore } from '../stores/index.js'
  42. // const list = ref([
  43. // { path: '/home', name: 'home', label: '首页', icon: 'house', url: 'Home' },
  44. // { path: '/mall', name: 'mall', label: '商品管理', icon: 'video-play', url: 'Mall' },
  45. // { path: '/user', name: 'user', label: '用户管理', icon: 'user', url: 'User' },
  46. // {
  47. // path: 'other', label: '其他', icon: 'location', children: [{ path: '/page1', name: 'page1', label: '页面1', icon: 'setting', url: 'Page1' },
  48. // { path: '/page2', name: 'page2', label: '页面2', icon: 'setting', url: 'Page2' }]
  49. // }])
  50. const AllDataStore = useAllDataStore()
  51. const list = computed(()=>AllDataStore.$state.menuList)
  52. const noChildren = computed(() => list.value.filter(item => !item.children))
  53. const hasChildren = computed(() => list.value.filter(item => item.children))
  54. const clickMenu = (item) => { router.push(item.path) }
  55. const isCollapse = computed(() => AllDataStore.$state.isCollapse)
  56. const MenuWidth= computed(() => AllDataStore.$state.isCollapse ? '64px' : '180px')
  57. const router = useRouter()
  58. const route = useRoute()
  59. const activeMenu = computed(()=>route.path)
  60. const handleMenu= (item) => {
  61. router.push(item.path)
  62. AllDataStore.selectMenu(item)
  63. }
  64. </script>
  65. <style lang="scss" scoped>
  66. // var asideColor: #545c64;
  67. .icons{
  68. height:18px;
  69. width:18px;
  70. margin-right:5px;
  71. }
  72. .el-menu{
  73. border-right:none;
  74. h3{
  75. line-height:48px;
  76. color:#fff;
  77. text-align:center;
  78. }
  79. }
  80. .el-aside{
  81. height:100%;
  82. background-color: #545c64;
  83. }
  84. </style>
comonheader组件搭建 使用下拉框el-dropdown和面包屑el-breadcrumb
  1. <template>
  2. <div class="header">
  3. <div class="l-content">
  4. <el-button size="small" @click="handleCollapse">
  5. <component class="icons" is="menu"></component>
  6. </el-button>
  7. <el-breadcrumb separator="/" class="bread">
  8. <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
  9. </el-breadcrumb>
  10. </div>
  11. <div class="r-content">
  12. <el-dropdown>
  13. <span class="el-dropdown-link">
  14. <span><img :src="getImageUrl('user')" class="user"></span>
  15. </span>
  16. <template #dropdown>
  17. <el-dropdown-menu>
  18. <el-dropdown-item>个人中心</el-dropdown-item>
  19. <el-dropdown-item @click="handleLoginOut">退出</el-dropdown-item>
  20. </el-dropdown-menu>
  21. </template>
  22. </el-dropdown>
  23. </div>
  24. </div>
  25. </template>
  26. <script setup>
  27. import {useAllDataStore}from '../stores/index.js'
  28. import {useRouter}from 'vue-router'
  29. import {computed} from 'vue'
  30. const AllData = useAllDataStore()
  31. const getImageUrl = (user) => {
  32. return new URL(`../assets/images/${user}.png`,import.meta.url).href
  33. }
  34. const handleCollapse = () => {
  35. AllData.$state.isCollapse = !AllData.$state.isCollapse
  36. }
  37. </script>
  38. <style lang="scss" scoped>
  39. .header {
  40. display:flex;
  41. justify-content: space-between;
  42. align-items: center;
  43. height:100%;
  44. width:100%;
  45. background-color: #333;
  46. }
  47. .icons{
  48. height:20px;
  49. width:20px;
  50. }
  51. .l-content{
  52. display: flex;
  53. align-items: center;
  54. .el-button{
  55. margin-right:20px;
  56. }
  57. }
  58. .r-content{
  59. .user{
  60. width:40px;
  61. height:40px;
  62. border-radius: 50%;
  63. outline: none;
  64. }
  65. }
  66. //样式穿透
  67. :deep(.bread span){
  68. color:#fff !important;
  69. cursor:pointer !important;
  70. }
  71. </style>
接下来实现首页剩余内容

使用elementplus中的layout布局 通过基础的 24 分栏,迅速简便地创建布局,在使用el-card卡片来分隔不同内容
请添加图片描述

  1. <template>
  2. <el-row class="home" :gutter="20">
  3. <el-col :span="8" style="margin-top:20px">
  4. <el-card shadow="hover">
  5. <div class="user">
  6. <img :src="getImageUrl('user')" class="user"/>
  7. <div class="user-info">
  8. <p class="user-info-admin">Admin</p>
  9. <p class="user-info-p">超级管理员</p>
  10. </div>
  11. </div>
  12. <div class="login_info">
  13. <p>上次登陆时间:<span>2024-07-24</span></p>
  14. <p>上次登陆地点:<span>北京</span></p>
  15. </div>
  16. </el-card>
  17. <el-card shadow="hover" class="user-table">
  18. <el-table :data="tableData">
  19. <el-table-column
  20. v-for="(val,key) in tableLabel"
  21. :key = "key"
  22. :prop = "key"
  23. :label = "val"
  24. >
  25. </el-table-column>
  26. </el-table>
  27. </el-card>
  28. </el-col>
  29. <el-col :span="16" style="margin-top:20px">
  30. <div class="num">
  31. <el-card :body-style="{display:'flex', padding:0}"
  32. v-for="item in countData"
  33. :key="item.name"
  34. >
  35. <component :is="item.icon" class="icons" :style="{background:item.color}">
  36. </component>
  37. <div class="detail">
  38. <p class="num">${{ item.value }}</p>
  39. <p class="txt">${{ item.name }}</p>
  40. </div>
  41. </el-card>
  42. </div>
  43. <el-card class="top-echart">
  44. <div ref="echart" style="height:230px"></div>
  45. </el-card>
  46. <div class="graph">
  47. <el-card>
  48. <div ref="userEchart" style="height:240px"></div>
  49. </el-card>
  50. <el-card>
  51. <div ref="videoEchart" style="height:240px"></div>
  52. </el-card>
  53. </div>
  54. </el-col>
  55. </el-row>
  56. </template>
  57. <script setup>
  58. import {ref,getCurrentInstance,onMounted,reactive} from 'vue'
  59. import * as echarts from 'echarts'
  60. const {proxy} = getCurrentInstance()
  61. const getImageUrl = (user) => {
  62. return new URL(`../assets/images/${user}.png`,import.meta.url).href
  63. }
  64. const observer =ref(null)
  65. const tableData = ref([])
  66. const countData = ref([])
  67. const chartData = ref([])
  68. const tableLabel = ref({
  69. name: "课程",
  70. todayBuy: "今日购买",
  71. monthBuy: "本月购买",
  72. totalBuy: "总购买",
  73. }
  74. )
  75. const getTableData = async () => {
  76. const data = await proxy.$api.getTableData()
  77. tableData.value = data.tableData
  78. }
  79. const getCountData = async () => {
  80. const data = await proxy.$api.getCountData()
  81. countData.value = data
  82. }
  83. const xOptions = reactive({
  84. // 图例文字颜色
  85. textStyle: {
  86. color: "#333",
  87. },
  88. legend: {},
  89. grid: {
  90. left: "20%",
  91. },
  92. // 提示框
  93. tooltip: {
  94. trigger: "axis",
  95. },
  96. xAxis: {
  97. type: "category", // 类目轴
  98. data: [],
  99. axisLine: {
  100. lineStyle: {
  101. color: "#17b3a3",
  102. },
  103. },
  104. axisLabel: {
  105. interval: 0,
  106. color: "#333",
  107. },
  108. },
  109. yAxis: [
  110. {
  111. type: "value",
  112. axisLine: {
  113. lineStyle: {
  114. color: "#17b3a3",
  115. },
  116. },
  117. },
  118. ],
  119. color: ["#2ec7c9", "#b6a2de", "#5ab1ef", "#ffb980", "#d87a80", "#8d98b3"],
  120. series: [],
  121. })
  122. const pieOptions = reactive({
  123. tooltip: {
  124. trigger: "item",
  125. },
  126. legend: {},
  127. color: [
  128. "#0f78f4",
  129. "#dd536b",
  130. "#9462e5",
  131. "#a6a6a6",
  132. "#e1bb22",
  133. "#39c362",
  134. "#3ed1cf",
  135. ],
  136. series: []
  137. })
  138. const getChartData = async () => {
  139. const {orderData,userData,videoData}= await proxy.$api.getChartData()
  140. xOptions.xAxis.data = orderData.date;
  141. xOptions.series = Object.keys(orderData.data[0]).map(val=>({
  142. name:val,
  143. data:orderData.data.map(item => item[val]),
  144. type:'line'
  145. }))
  146. const oneEchart = echarts.init(proxy.$refs['echart'])
  147. oneEchart.setOption(xOptions)
  148. xOptions.xAxis.data = userData.map(item => item.date)
  149. xOptions.series=[
  150. {
  151. name:'新增用户',
  152. data:userData.map(item=>item.new),
  153. type:'bar'
  154. },
  155. {
  156. name:'活跃用户',
  157. data:userData.map(item=>item.active),
  158. type:'bar'
  159. }
  160. ]
  161. const twoEchart = echarts.init(proxy.$refs['userEchart'])
  162. twoEchart.setOption(xOptions)
  163. pieOptions.series = [
  164. {
  165. data:videoData,
  166. type:'pie',
  167. }
  168. ]
  169. const threeEchart = echarts.init(proxy.$refs['videoEchart'])
  170. threeEchart.setOption(pieOptions)
  171. //监听页面的变化
  172. observer.value = new ResizeObserver(() => {
  173. oneEchart.resize()
  174. twoEchart.resize()
  175. threeEchart.resize()
  176. })
  177. if(proxy.$refs['echart']){
  178. observer.value.observe(proxy.$refs["echart"])
  179. }
  180. }
  181. onMounted(() => {
  182. getTableData()
  183. getCountData()
  184. getChartData()
  185. })
  186. </script>
  187. <style lang="scss" scoped>
  188. .home{
  189. height:150vh;
  190. overflow:hidden;
  191. .user{
  192. display:flex;
  193. align-items:center;
  194. border-bottom:1px solid #ccc;
  195. margin-bottom:20px;
  196. img{
  197. width:150px;
  198. height:150px;
  199. border-radius: 50%;
  200. margin-right: 40px;
  201. }
  202. .user-info{
  203. p{
  204. line-height:40px;
  205. }
  206. .user-info-p{
  207. color:#999;
  208. }
  209. .user-info-admin{
  210. font-size:35px;
  211. }
  212. }
  213. }
  214. .login_info{
  215. p{
  216. line-height:30px;
  217. font-size:14px;
  218. color:#990;
  219. }
  220. span{
  221. color:#666;
  222. margin-left:60px;
  223. }
  224. }
  225. .user-table{
  226. margin-top:20px;
  227. }
  228. .num {
  229. display: flex;
  230. flex-wrap: wrap;
  231. justify-content: space-between;
  232. .el-card {
  233. width: 32%;
  234. margin-bottom: 20px;
  235. }
  236. .icons {
  237. width: 80px;
  238. height: 80px;
  239. font-size: 30px;
  240. text-align: center;
  241. line-height: 80px;
  242. color: #fff;
  243. }
  244. .detail {
  245. margin-left: 15px;
  246. display: flex;
  247. flex-direction: column;
  248. justify-content: center;
  249. .num {
  250. font-size: 30px;
  251. margin-bottom: 10px;
  252. }
  253. .txt {
  254. font-size: 14px;
  255. text-align: center;
  256. color: #999;
  257. }
  258. }
  259. }
  260. .graph {
  261. margin-top: 20px;
  262. display: flex;
  263. justify-content: space-between;
  264. .el-card {
  265. width: 48%;
  266. height: 260px;
  267. }
  268. }
  269. }
  270. </style>
没有后端 我们用mock来模拟网络请求 使用axios来处理网络请求
  1. pnpm i axios
  2. pnpm i mockjs
创建api文件夹 创建request.js 文件 二次封装下axios
  1. import axios from"axios";import config from'../config/index'const service = axios.create({baseURL:config.baseApi,});constNETWORK_ERROR='网络错误...'
  2. service.interceptors.request.use((config)=>{return config
  3. },(error)=>{return Promise.reject(error)})
  4. service.interceptors.response.use((res)=>{const{code,data,msg}= res.data
  5. if(code===200){return data
  6. }else{return Promise.reject(msg ||NETWORK_ERROR)}})functionrequest(options){// console.log(config.env)
  7. options.method = options.method ||"get"if(options.method.toLowerCase()==="get"){
  8. options.params = options.data
  9. }//对mock的开关做一个处理let isMock = config.mock
  10. if(config.env !=="undefined"){
  11. isMock = config.env
  12. }//针对环境作处理if(config.env ==="prod"){
  13. service.defaults.baseURL = config.baseAPi;}else{// console.log('isMock',isMock)
  14. service.defaults.baseURL = isMock ? config.mockApi : config.baseApi;}returnservice(options)}exportdefault request

在api文件下创建api.js

  1. import request from'./request.js'exportdefault{getTableData(){returnrequest({url:"/home/getTable",method:'get',})},getCountData(){returnrequest({url:"/home/getCountData",method:'get',})},getChartData(){returnrequest({url:"/home/getChartData",method:'get',})},}

使用mock模拟请求 在api文件下创建mockData下创建home.js 并在api目录下创建mock.js作为出口

  1. //mock.jsimport Mock from"mockjs"import homeApi from"./mockData/home.js"
  2. Mock.mock(/api\/home\/getTableData/,"get",homeApi.getTableData)
  3. Mock.mock(/api\/home\/getCountData/,"get",homeApi.getCountData)
  4. Mock.mock(/api\/home\/getChartData/,"get",homeApi.getChartData)// mockData/home.jsexportdefault{getTableData:()=>{return{code:200,data:{tableData:[{name:"oppo",todayBuy:500,monthBuy:3500,totalBuy:22000,},{name:"vivo",todayBuy:300,monthBuy:2200,totalBuy:24000,},{name:"苹果",todayBuy:800,monthBuy:4500,totalBuy:65000,},{name:"小米",todayBuy:1200,monthBuy:6500,totalBuy:45000,},{name:"三星",todayBuy:300,monthBuy:2000,totalBuy:34000,},{name:"魅族",todayBuy:350,monthBuy:3000,totalBuy:22000,},],},}},getCountData:()=>{return{code:200,data:[{name:"今日支付订单",value:1234,icon:"SuccessFilled",color:"#2ec7c9",},{name:"今日收藏订单",value:210,icon:"StarFilled",color:"#ffb980",},{name:"今日未支付订单",value:1234,icon:"GoodsFilled",color:"#5ab1ef",},{name:"本月支付订单",value:1234,icon:"SuccessFilled",color:"#2ec7c9",},{name:"本月收藏订单",value:210,icon:"StarFilled",color:"#ffb980",},{name:"本月未支付订单",value:1234,icon:"GoodsFilled",color:"#5ab1ef",},],};},getChartData:()=>{return{code:200,data:{orderData:{date:["2019-10-01","2019-10-02","2019-10-03","2019-10-04","2019-10-05","2019-10-06","2019-10-07",],data:[{苹果:3839,小米:1423,华为:4965,oppo:3334,vivo:2820,一加:4751,},{苹果:3560,小米:2099,华为:3192,oppo:4210,vivo:1283,一加:1613,},{苹果:1864,小米:4598,华为:4202,oppo:4377,vivo:4123,一加:4750,},{苹果:2634,小米:1458,华为:4155,oppo:2847,vivo:2551,一加:1733,},{苹果:3622,小米:3990,华为:2860,oppo:3870,vivo:1852,一加:1712,},{苹果:2004,小米:1864,华为:1395,oppo:1315,vivo:4051,一加:2293,},{苹果:3797,小米:3936,华为:3642,oppo:4408,vivo:3374,一加:3874,},],},videoData:[{name:"小米",value:2999},{name:"苹果",value:5999},{name:"vivo",value:1500},{name:"oppo",value:1999},{name:"魅族",value:2200},{name:"三星",value:4500},],userData:[{date:"周一",new:5,active:200},{date:"周二",new:10,active:500},{date:"周三",new:12,active:550},{date:"周四",new:60,active:800},{date:"周五",new:65,active:550},{date:"周六",new:53,active:770},{date:"周日",new:33,active:170},],},};}}

最终效果
请添加图片描述

如果对你有所帮助就点个关注吧

本文是这篇文章的笔记
https://www.bilibili.com/video/BV1LS421d7cY?p=5&vd_source=e73709c9a1618b4c6dfd58c6c40d8986


本文转载自: https://blog.csdn.net/weixin_63625059/article/details/140806226
版权归原作者 ZhaiMou 所有, 如有侵权,请联系我们删除。

“vue3后台管理系统 vue3+vite+pinia+element-plus+axios上”的评论:

还没有评论