当前位置:   article > 正文

Vue3后台通用管理系统(一):header、aside组件的基本搭建、首页home组件的实现_return new url(`../assets/images/${user}.png`, imp

return new url(`../assets/images/${user}.png`, import.meta.url).href;

目录

(一)项目搭建分析

1.框架分析

2.element-plus插件引入

(二)页面框架的搭建

1.整体布局实现

(1)配置路由

(2)使用element-plus搭建基本框架

2.header头部的搭建

(1)基本框架+菜单图标

(2)下拉菜单的构建

(3)动态引入静态资源的优化

3.menu菜单aside的搭建

(1)使用elemen-plus搭建

(2)点击header内的菜单按钮实现aside菜单的展示和隐藏

(3)menu点击选项跳转路由的实现

4.布局样式的基础实现

(三)首页home组件的实现

1.home组件左侧的实现

(1)info卡片

(2)table表格

(3)二次封装axios+mock

(4)使用mock数据重新渲染table

2.home组件右侧的实现 

(1)账单计数展示

(2)图表 

[1]引入echarts

[2]折线图的实现

[3]柱状图的实现

[4]饼状图的实现


(一)项目搭建分析

1.框架分析

使用vue3+ts+pinia+element-plus+echarts搭建一个通用的后台管理系统(没准后面会改成特定的功能)

header、aside是非路由组件,中间的部分用于放置不同的路由组件,例如home、user等

2.element-plus插件引入

快速开始 | Element Plus

安装element-plus:

npm install element-plus --save 

按需自动导入(推荐)

安装unplugin-vue-components 和 unplugin-auto-import这两款插件:

npm install -D unplugin-vue-components unplugin-auto-import

把下列代码插入到 Vite的配置文件中:

  1. // vite.config.ts
  2. import { defineConfig } from 'vite'
  3. import AutoImport from 'unplugin-auto-import/vite'
  4. import Components from 'unplugin-vue-components/vite'
  5. import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
  6. export default defineConfig({
  7. // ...
  8. plugins: [
  9. // ...
  10. AutoImport({
  11. resolvers: [ElementPlusResolver()],
  12. }),
  13. Components({
  14. resolvers: [ElementPlusResolver()],
  15. }),
  16. ],
  17. })

(二)页面框架的搭建

1.整体布局实现

(1)配置路由

  1. import { createRouter, createWebHistory } from 'vue-router'
  2. const router = createRouter({
  3. history: createWebHistory(import.meta.env.BASE_URL),
  4. routes: [
  5. {
  6. path: '/',
  7. name: 'main',
  8. component: () => import('@/views/Main.vue'), // 路由懒加载
  9. // redirect: '/home',
  10. children: [
  11. {
  12. path: '/',
  13. name: 'home',
  14. component: () => import('@/views/Home/index.vue'),
  15. }
  16. ]
  17. },
  18. ]
  19. })
  20. export default router

(2)使用element-plus搭建基本框架

  1. 在vies/main中:
  2. <!-- element-plus -->
  3. <el-container>
  4. <!-- 左侧 -->
  5. <el-aside width="200px">Aside</el-aside>
  6. <!-- 右侧 -->
  7. <el-container>
  8. <!-- 头部 -->
  9. <el-header>Header</el-header>
  10. <!-- 右侧 路由组件 -->
  11. <el-main>
  12. <router-view></router-view>
  13. </el-main>
  14. </el-container>
  15. </el-container>

2.header头部的搭建

(1)基本框架+菜单图标

  1. 在components/commonheader中:
  2. <el-header>
  3. <!-- 菜单图标 -->
  4. <div class="menu">
  5. </div>
  6. <!-- content -->
  7. <div class="header-content">
  8. 这里是heaeder
  9. </div>
  10. </el-header>
  11. 在views/main中:
  12. <!-- 头部 -->
  13. <el-header>
  14. <CommonHeader></CommonHeader>
  15. </el-header>

element-plus图标的引入

安装icon图标库:

npm install @element-plus/icons-vue

注册所有图标:

  1. // main.ts
  2. // 引入element图标库
  3. import * as ElementPlusIconsVue from '@element-plus/icons-vue'
  4. // 注册element图标
  5. for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  6. app.component(key, component)
  7. }

 使用图标:

  1. <!-- 菜单图标 -->
  2. <div class="menu">
  3. <el-icon>
  4. <Menu />
  5. </el-icon>
  6. </div>

(2)下拉菜单的构建

使用element-plus里的下拉菜单完成头像和下拉菜单的展示 

  1. <!-- content -->
  2. <div class="header-content">
  3. <!-- element-plus 下拉菜单 -->
  4. <el-dropdown>
  5. <span class="el-dropdown-link">
  6. <img class="head-portrait" src="../assets/头像.png" alt="">
  7. </span>
  8. <template #dropdown>
  9. <el-dropdown-menu>
  10. <el-dropdown-item>个人中心</el-dropdown-item>
  11. <el-dropdown-item>退出</el-dropdown-item>
  12. </el-dropdown-menu>
  13. </template>
  14. </el-dropdown>
  15. </div>

再使用flex布局将两个元素拉到左右两边

(3)动态引入静态资源的优化

头像的图片是通过静态引入过来的,为了实现动态引入、便于更改的效果,这里使用new URL()来实现:URL() - Web API 接口参考 | MDN

  1. 在components/commonHeader中:
  2. // 用于动态引入图片的路径
  3. function getImgUrl(user: string) {
  4. // 第一个参数:绝对路径/相对路径
  5. // 第二个参数:base基准路径 当一个参数是相对路径时生效(默认undefined)
  6. return new URL(`../assets/images/${user}.png`, import.meta.url).href
  7. }
  8. <!-- 动态获取路径 -->
  9. <img class="head-portrait" :src="getImgUrl('user')" alt="">

import.meta.url指向的是当前组件的路径

3.menu菜单aside的搭建

(1)使用elemen-plus搭建

暂时使用的菜单数据中,含有有子菜单的一级菜单和没有子菜单的一级菜单 

  1. const menuList = reactive([
  2. {
  3. path: '/user',
  4. name: 'user',
  5. label: '用户管理',
  6. icon: 'user',
  7. url: 'UserManage/UserManage'
  8. },
  9. {
  10. label: '其他',
  11. icon: 'location',
  12. path: '/other',
  13. children: [
  14. {
  15. path: '/page1',
  16. name: 'page1',
  17. label: '页面1',
  18. icon: 'setting',
  19. url: 'Other/PageOne'
  20. },
  21. {
  22. path: '/page2',
  23. name: 'page2',
  24. label: '页面2',
  25. icon: 'setting',
  26. url: 'Other/PageTwo'
  27. }
  28. ]
  29. }
  30. ])

由于element-plus里有无子菜单的写法不太一致,因此将两种情况分开来,使用filter过滤成两个数组

  1. // 返回有二级菜单的一级菜单数组
  2. function hasChildren() {
  3. return menuList.filter((item: any) => {
  4. return item.children != undefined
  5. })
  6. }
  7. // 返回没有二级菜单的一级菜单数组
  8. function noChildren() {
  9. return menuList.filter((item: any) => {
  10. return item.children == undefined
  11. })
  12. }

再将数组数据动态渲染到页面

  1. <el-menu class="el-menu-vertical-demo" active-text-color="#303133">
  2. <!-- 用户管理 没有二级菜单-->
  3. <el-menu-item :index="menuLabel.path" v-for="(menuLabel, index) in noChildren()" :key="index">
  4. <el-icon>
  5. <!-- 自定义图标 -->
  6. <component class="icons" :is="menuLabel.icon"></component>
  7. </el-icon>
  8. <template #title>{{ menuLabel.label }}</template>
  9. </el-menu-item>
  10. <!-- 有二级菜单 -->
  11. <el-sub-menu :index="menuLabel.path" v-for="(menuLabel, index) in hasChildren()" :key="index">
  12. <template #title>
  13. <el-icon>
  14. <component class="icons" :is='menuLabel.icon'></component>
  15. </el-icon>
  16. <span>{{ menuLabel.label }}</span>
  17. </template>
  18. <!-- 子菜单 -->
  19. <el-menu-item-group>
  20. <el-menu-item :index="item.path" v-for="(item, index) in menuLabel.children" :key="index">
  21. <el-icon>
  22. <component class="icons" :is='item.icon'></component>
  23. </el-icon>
  24. <span>{{ item.label }} </span>
  25. </el-menu-item>
  26. </el-menu-item-group>
  27. </el-sub-menu>
  28. </el-menu>

(2)点击header内的菜单按钮实现aside菜单的展示和隐藏

涉及到兄弟组件commonHeader和commonAside的交互,可以使用pinia仓库实现数据的传递

header点击按钮,将是否菜单是否收缩的数据传给aside

  1. 在stores/menu.ts中:
  2. import { ref, computed } from 'vue'
  3. import { defineStore } from 'pinia'
  4. export const useMenuStore = defineStore('menu', () => {
  5. // 菜单是否折叠的状态 默认false
  6. let isCollapse = ref(false)
  7. function handleCollapse() {
  8. isCollapse.value = !isCollapse.value
  9. }
  10. return { isCollapse, handleCollapse }
  11. })
  1. 在components/commonheader中:
  2. import { useMenuStore } from '@/stores/Menu';
  3. // 解构赋值
  4. let { handleCollapse } = useMenuStore()
  5. <!-- 点击 改变showMenu状态 -->
  6. <el-button plain @click="handleCollapse()">

 el-menu的collapse属性可以控制菜单的收缩

  1. 在component/commonAside中:
  2. import { useMenuStore } from '@/stores/Menu';
  3. import { storeToRefs } from 'pinia';
  4. // 解构赋值 storeToRefs使得数据保持响应式
  5. let { isCollapse } = storeToRefs(useMenuStore())
  6. <!-- 折叠width就变小 -->
  7. <el-aside :width="isCollapse ? '64px' : '180px'">
  8. <el-menu class="el-menu-vertical-demo" :collapse="isCollapse">
  9. ...
  10. .el-aside {
  11. transition: 0.6s ease-out;
  12. }

(3)menu点击选项跳转路由的实现

先配置路由

  1. routes: [
  2. {
  3. path: '/',
  4. name: 'main',
  5. component: () => import('@/views/Main.vue'), // 路由懒加载
  6. children: [
  7. {
  8. path: '/',
  9. name: 'home',
  10. component: () => import('@/views/Home/index.vue'),
  11. },
  12. {
  13. path: '/user',
  14. name: 'user',
  15. component: () => import('@/views/User.vue'),
  16. },
  17. {
  18. path: '/page1',
  19. name: 'page1',
  20. component: () => import('@/views/Page1.vue'),
  21. },
  22. {
  23. path: '/page2',
  24. name: 'page2',
  25. component: () => import('@/views/Page2.vue'),
  26. },
  27. ]
  28. },
  29. ]

在commonAisde中,点击对应菜单项,跳转到对应路由 

  1. import router from '@/router';
  2. // 跳转到对应的路由
  3. function goRouter(item: any) {
  4. router.push({
  5. name: item.name,
  6. })
  7. }
  8. <el-menu-item :index="menuLabel.path" v-for="(menuLabel, index) in noChildren()"
  9. :key="index" @click="goRouter(menuLabel)">
  10. ...
  11. <el-menu-item :index="item.path" v-for="(item, index) in menuLabel.children"
  12. :key="index" @click="goRouter(item)">
  13. ...

4.布局样式的基础实现

调整reset.less、调整高度等等,就得到了最基础的框架样式

(三)首页home组件的实现

1.home组件左侧的实现

(1)info卡片

都暂时写成静态数据

  1. <el-row class="home">
  2. <!-- 左侧 user信息和table表格 -->
  3. <el-col :span="8" :gutter="20">
  4. <!-- user信息card -->
  5. <el-card class="info-card" shadow="hover" style="margin-top: 20px;">
  6. <template #header>
  7. <div class="user">
  8. <div class="image">
  9. <img src="../../assets/images/user.png" style="width: 100%;">
  10. </div>
  11. <div class="user-info">
  12. <div class="user-name">Admin</div>
  13. <div class="role">管理员</div>
  14. </div>
  15. </div>
  16. </template>
  17. <div class="login">
  18. <div class="login-info">上次登录的时间:<span>2023-11-7</span> </div>
  19. <div class="login-info">上次登录的地点:<span>成都</span></div>
  20. </div>
  21. </el-card>
  22. <!-- 表格数据card -->
  23. ...
  24. </el-col>
  25. <!-- 右侧 图表 -->
  26. <el-col :span="16">
  27. ...
  28. </el-col>
  29. </el-row>

(2)table表格

先暂时使用静态数据

  1. // 左侧 table数据
  2. const tableData = [
  3. {
  4. name: 'oppo',
  5. todayBuy: 500,
  6. monthBuy: 3500,
  7. totalBuy: 22000
  8. },
  9. ...
  10. ]
  11. // 表头名称
  12. const tableLabel = {
  13. name: '品牌',
  14. todayBuy: '今日购买',
  15. monthBuy: '本月购买',
  16. totalBuy: '总购买'
  17. }

使用element-plus将数据渲染到表格里

  1. <!-- 表格数据card -->
  2. <el-card class="table-card" shadow="hover">
  3. <el-table :data="tableData" height="430" style="width: 100%;" stripe>
  4. <el-table-column v-for="(value, key) in tableLabel"
  5. :key="key" :prop="key" :label="value" />
  6. </el-table>
  7. </el-card>

(3)二次封装axios+mock

二次封装axios 

axios之前封装过了,直接照搬

配置代理的形式与vue2稍有区别

在vite.config.ts中:

  1. // vite.config.ts
  2. server: {
  3. //用来配置跨域
  4. host: '127.0.0.1',
  5. port: 5173,
  6. proxy: { // 配置代理
  7. '/api': {
  8. target: 'http://127.0.0.1:5050', //目标url
  9. changeOrigin: true // 支持跨域
  10. }
  11. }
  12. }

配置mockjs

和之前vue2项目里一样

建立mock文件夹,用于写mockServer和存储传送的data 

  1. 在mock/mockServer中:
  2. // 引入mock.js
  3. import Mock from "mockjs";
  4. // 引入json
  5. // webpack默认图片和json数据格式对外暴露
  6. import tableData from '@/mock/tableData.json'
  7. // mock数据:第一个参数请求地址;第二个参数请求数据
  8. // 当发送ajax请求该地址时,浏览器拦截这条请求并将该数据返回
  9. Mock.mock('/mock/tabledata', { code: 200, data: tableData })

然后要在main.ts里引入才能生效***(不认真看忽略了这一步找了好久的错。。无语)

  1. // 引入mock (引入了才能使用mock)
  2. import '@/mock/mockServer.ts'

用于存储数据的json文件会报错

在tsconfig.app.ts中的include添加json文件路径

在在api文件夹中写mockRequests.ts

  1. // 该文件用于配置mock模拟axios
  2. import axios from "axios"
  3. import NProgress from 'nprogress'
  4. import 'nprogress/nprogress.css'
  5. // 配置不显示右上角的旋转进度条, 只显示水平进度条
  6. NProgress.configure({ showSpinner: false })
  7. const mockRequests = axios.create({
  8. baseURL: '/mock', // 设置公共路径
  9. timeout: 1000 * 10 // 设置请求超过时间 10s
  10. })
  11. // 添加请求拦截器
  12. mockRequests.interceptors.request.use(
  13. ...
  14. )
  15. // 添加响应拦截器
  16. mockRequests.interceptors.response.use(
  17. ...
  18. )
  19. export default mockRequests

使用线上mock  

fastmock 在线接口 Mock 平台

主要是用于临时渲染数据,不需要配置,比较方便

(4)使用mock数据重新渲染table

  1. let tableData = ref([])
  2. async function getData() {
  3. await getTableData().then(res => {
  4. // 使用ref才能实现这里操作的响应式 reactive无法检测这里的替换
  5. tableData.value = res.data
  6. })
  7. }
  8. onMounted(() => {
  9. getData()
  10. })

2.home组件右侧的实现 

(1)账单计数展示

使用mock模拟请求账单countLIst,得到数据渲染即可

  1. [
  2. {
  3. "name": "今日支付订单",
  4. "value": 1234,
  5. "icon": "SuccessFilled",
  6. "color": "#2ec7c9"
  7. },
  8. {
  9. "name": "今日收藏订单",
  10. "value": 210,
  11. "icon": "StarFilled",
  12. "color": "#ffb980"
  13. },
  14. ...
  15. // 获取订单计数数据
  16. let countData: any = ref([])
  17. // 获取count数据
  18. async function getCData() {
  19. await getCountData().then(res => {
  20. countData.value = res.data
  21. })
  22. }
  23. <!-- 订单计数card -->
  24. <div class="count-all">
  25. <el-card class="count-card" shadow="hover" v-for="item in countData" :key="item.name">
  26. <el-icon>
  27. <component :is="item.icon" class="icons" :style="{ background: item.color }"></component>
  28. </el-icon>
  29. <div class="info">
  30. <p>{{ item.value }}</p>
  31. <p>{{ item.name }}</p>
  32. </div>
  33. </el-card>
  34. </div>

(2)图表 

[1]引入echarts

Apache ECharts

项目中引入echarts: 

 npm install echarts --save

获取dom元素:

图表的插入需要获取对应的dom元素,linechart.value即为需要的dom元素

  1. <div ref="linechart" style="height: 280px"> </div>
  2. const linechart = ref()

折线图、柱状图、饼图的配置项:

  1. // 关于echarts 表格的渲染部分 折线、柱状的配置项
  2. let xOptions = reactive({
  3. // 图例文字颜色
  4. textStyle: {
  5. color: "#333",
  6. },
  7. grid: {
  8. left: "20%",
  9. },
  10. // 提示框
  11. tooltip: {
  12. trigger: "axis",
  13. },
  14. xAxis: {
  15. type: "category", // 类目轴
  16. data: [],
  17. axisLine: {
  18. lineStyle: {
  19. color: "#17b3a3",
  20. },
  21. },
  22. axisLabel: {
  23. interval: 0,
  24. color: "#333",
  25. },
  26. },
  27. yAxis: [
  28. {
  29. type: "value",
  30. axisLine: {
  31. lineStyle: {
  32. color: "#17b3a3",
  33. },
  34. },
  35. },
  36. ],
  37. color: ["#2ec7c9", "#b6a2de", "#5ab1ef", "#ffb980", "#d87a80", "#8d98b3"],
  38. series: [],
  39. });
  40. // 饼图的配置项
  41. let pieOptions = reactive({
  42. tooltip: {
  43. trigger: "item",
  44. },
  45. color: [
  46. "#0f78f4",
  47. "#dd536b",
  48. "#9462e5",
  49. "#a6a6a6",
  50. "#e1bb22",
  51. "#39c362",
  52. "#3ed1cf",
  53. ],
  54. series: [],
  55. });
[2]折线图的实现
  1. // 获取发来的图表数据 进行echart初始化和渲染
  2. async function getchartData() {
  3. // 获取chart数据
  4. await getChartData().then(res => {
  5. let orderRes = res.data.orderData
  6. let userRes = res.data.userData
  7. let videoRes = res.data.videoData
  8. let series: any = []
  9. // 提取出键的数组
  10. const keyArray = Object.keys(orderRes.data[0])
  11. // 折线图的初始化和渲染
  12. // 每个品牌的数据
  13. keyArray.forEach((key) => {
  14. series.push({
  15. name: key,
  16. data: orderRes.data.map((item: any) => item[key]),
  17. type: "line",
  18. });
  19. });
  20. xOptions.xAxis.data = orderRes.date // x轴数据
  21. xOptions.series = series
  22. // orderData进行渲染
  23. let lineEcharts = echarts.init(linechart.value)
  24. lineEcharts.setOption(xOptions)
  25. }
  1. <!-- 折线图 -->
  2. <el-card style="height: 280px;margin-top: 20px;margin-left: 20px;" shadow="hover">
  3. <div ref="linechart" style="height: 280px"> </div>
  4. </el-card>
[3]柱状图的实现
  1. // 获取chart数据
  2. await getChartData().then(res => {
  3. let orderRes = res.data.orderData
  4. let userRes = res.data.userData
  5. let videoRes = res.data.videoData
  6. let series: any = []
  7. // 提取出键的数组
  8. const keyArray = Object.keys(orderRes.data[0])
  9. // 柱形图的初始化和渲染
  10. series = [
  11. {
  12. name: "新增用户",
  13. data: userRes.map((item: any) => item.new),
  14. type: "bar",
  15. },
  16. {
  17. name: "活跃用户",
  18. data: userRes.map((item: any) => item.active),
  19. type: "bar",
  20. },
  21. ]
  22. xOptions.xAxis.data = userRes.map((item: any) => item.date);
  23. xOptions.series = series
  24. // userData进行渲染
  25. let userEcharts = echarts.init(userchart.value)
  26. userEcharts.setOption(xOptions)
  27. }
  1. <!-- 柱状图和饼图 -->
  2. <div class="graph" style="height: 260px;">
  3. <el-card style="height: 255px;" shadow="hover">
  4. <div ref="userchart" style="height: 245px"></div>
  5. </el-card>
  6. ...
  7. <div>
[4]饼状图的实现
  1. // 获取发来的图表数据 进行echart初始化和渲染
  2. async function getchartData() {
  3. // 获取chart数据
  4. await getChartData().then(res => {
  5. let orderRes = res.data.orderData
  6. let userRes = res.data.userData
  7. let videoRes = res.data.videoData
  8. let series: any = []
  9. // 提取出键的数组
  10. const keyArray = Object.keys(orderRes.data[0])
  11. // 饼图的初始化和渲染
  12. series = [
  13. {
  14. data: videoRes,
  15. type: "pie",
  16. },
  17. ]
  18. pieOptions.series = series
  19. let videoEcharts = echarts.init(videochart.value)
  20. videoEcharts.setOption(pieOptions)
  21. })
  22. }
  1. <el-card style="height: 255px;padding-bottom: 10px;" shadow="hover">
  2. <div class="video-chart" ref="videochart" style="height: 245px"></div>
  3. </el-card>
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/69836
推荐阅读
相关标签
  

闽ICP备14008679号