当前位置: 首页 > news >正文

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

前言

项目安装与启动

使用vite作为项目脚手架

# pnpm
pnpm create vite my-vue-app --template vue

安装相应依赖

# sass
pnpm i sass
# vue-router
pnpm i vue-router
# element-plus
pnpm i element-plus
# element-plus/icon 
pnpm i @element-plus/icons-vue

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

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

并在vite.config.js中配置

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'export default defineConfig({plugins: [vue(),AutoImport({resolvers: [ElementPlusResolver()],}),Components({resolvers: [ElementPlusResolver()],}),],resolve:{alias: {'@': '/src'}}})

注册elementplus的icon库

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

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

在src目录下创建router文件夹在其中创建index.js 并配置基本内容
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() ,routes})export default router

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

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

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

<template><div class="commom-layout"><el-container class="lay-container"><common-aside></common-aside><el-container><el-header class="el-header"><commonHeader></commonHeader></el-header><common-tab></common-tab><el-main class="right-main"><RouterView></RouterView></el-main></el-container></el-container></div></template><script setup>import commonAside from '../components/commonAside.vue';import commonHeader from '../components/commonHeader.vue';import commonTab from '../components/commonTab.vue';</script><style scoped lang="scss">.commom-layout, .lay-container{height:100%;}.el-header{background-color: #333;}</style>
布局已经搭好 让我们完成里面的组件

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

pnpm i pinia 
//在main.js
const pinia = createPinia()app.use(pinia)
// store/index.js
import { defineStore } from 'pinia'state:() => {return {isCollapse: true,
menuList:[],
}},
<template><el-aside :width="MenuWidth"><el-menubackground-color="#545c64"text-color="#fff":collapse="isCollapse":collapse-transition="false":default-active="activeMenu"><h3 v-show="!isCollapse">通用后台管理系统</h3><h3 v-show="isCollapse">后台</h3><el-menu-item v-for="item in noChildren" :index="item.path" :key="item.path"@click="handleMenu(item)"><component class="icons" :is="item.icon"></component><span>{{ item.label }}</span></el-menu-item><el-sub-menu v-for="item in hasChildren" :index="item.path" :key="item.path"><template #title><component class="icons" :is="item.icon"></component><span>{{ item.label }}</span></template><el-menu-item-group><el-menu-itemv-for="(subItem,subIndex) in item.children":key="subItem.path":subIndex="subItem.path"@click="handleMenu(subItem)"><component class="icons" :is="subItem.icon"></component><span>{{ subItem.label }}</span></el-menu-item></el-menu-item-group></el-sub-menu></el-menu></el-aside></template><script setup>import { ref, computed } from 'vue'import { useRouter ,useRoute} from 'vue-router';import { useAllDataStore } from '../stores/index.js'// const list = ref([// { path: '/home', name: 'home', label: '首页', icon: 'house', url: 'Home' },// { path: '/mall', name: 'mall', label: '商品管理', icon: 'video-play', 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 AllDataStore = useAllDataStore()const list = computed(()=>AllDataStore.$state.menuList)const noChildren = computed(() => list.value.filter(item => !item.children))const hasChildren = computed(() => list.value.filter(item => item.children))const clickMenu = (item) => { router.push(item.path) }const isCollapse = computed(() => AllDataStore.$state.isCollapse)const MenuWidth= computed(() => AllDataStore.$state.isCollapse ? '64px' : '180px')const router = useRouter()const route = useRoute()const activeMenu = computed(()=>route.path)const handleMenu= (item) => {router.push(item.path)AllDataStore.selectMenu(item)}</script>
<style lang="scss" scoped>
// var asideColor: #545c64;
.icons{
height:18px;
width:18px;
margin-right:5px;
}
.el-menu{
border-right:none;
h3{
line-height:48px;
color:#fff;
text-align:center;
}
}
.el-aside{
height:100%;
background-color: #545c64;
}
</style>
comonheader组件搭建 使用下拉框el-dropdown和面包屑el-breadcrumb
<template><div class="header"><div class="l-content"><el-button size="small" @click="handleCollapse"><component class="icons" is="menu"></component></el-button><el-breadcrumb separator="/" class="bread"><el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item></el-breadcrumb></div><div class="r-content"><el-dropdown><span class="el-dropdown-link"><span><img :src="getImageUrl('user')" class="user"></span></span><template #dropdown><el-dropdown-menu><el-dropdown-item>个人中心</el-dropdown-item><el-dropdown-item @click="handleLoginOut">退出</el-dropdown-item></el-dropdown-menu></template></el-dropdown></div></div></template><script setup>import {useAllDataStore}from '../stores/index.js'import {useRouter}from 'vue-router'import {computed} from 'vue'const AllData = useAllDataStore()const getImageUrl = (user) => {return new URL(`../assets/images/${user}.png`,import.meta.url).href}const handleCollapse = () => {AllData.$state.isCollapse = !AllData.$state.isCollapse}</script><style lang="scss" scoped>.header {display:flex;justify-content: space-between;align-items: center;height:100%;width:100%;background-color: #333;}.icons{height:20px;width:20px;}.l-content{display: flex;align-items: center;.el-button{margin-right:20px;}}.r-content{.user{width:40px;height:40px;border-radius: 50%;outline: none;}}//样式穿透:deep(.bread span){color:#fff !important;cursor:pointer !important;}</style>
接下来实现首页剩余内容

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

<template><el-row class="home" :gutter="20"><el-col :span="8" style="margin-top:20px"><el-card shadow="hover"><div class="user"><img :src="getImageUrl('user')" class="user"/><div class="user-info"><p class="user-info-admin">Admin</p><p class="user-info-p">超级管理员</p></div></div><div class="login_info"><p>上次登陆时间:<span>2024-07-24</span></p><p>上次登陆地点:<span>北京</span></p></div></el-card><el-card shadow="hover" class="user-table"><el-table :data="tableData"><el-table-columnv-for="(val,key) in tableLabel":key = "key":prop = "key":label = "val"></el-table-column></el-table></el-card></el-col><el-col :span="16" style="margin-top:20px"><div class="num"><el-card :body-style="{display:'flex', padding:0}"v-for="item in countData":key="item.name"><component :is="item.icon" class="icons" :style="{background:item.color}"></component><div class="detail"><p class="num">${{ item.value }}</p><p class="txt">${{ item.name }}</p></div></el-card></div><el-card class="top-echart"><div ref="echart" style="height:230px"></div></el-card><div class="graph"><el-card><div ref="userEchart" style="height:240px"></div></el-card><el-card><div ref="videoEchart" style="height:240px"></div></el-card></div></el-col></el-row></template><script setup>import {ref,getCurrentInstance,onMounted,reactive} from 'vue'import * as echarts from 'echarts'const {proxy} = getCurrentInstance()const getImageUrl = (user) => {return new URL(`../assets/images/${user}.png`,import.meta.url).href}const observer =ref(null)const tableData = ref([])const countData = ref([])const chartData = ref([])const tableLabel = ref({name: "课程",todayBuy: "今日购买",monthBuy: "本月购买",totalBuy: "总购买",})const getTableData = async () => {const data = await proxy.$api.getTableData()tableData.value = data.tableData}const getCountData = async () => {const data = await proxy.$api.getCountData()countData.value = data}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 getChartData = async () => {const {orderData,userData,videoData}= await proxy.$api.getChartData()xOptions.xAxis.data = orderData.date;xOptions.series = Object.keys(orderData.data[0]).map(val=>({name:val,data:orderData.data.map(item => item[val]),type:'line'}))const oneEchart = echarts.init(proxy.$refs['echart'])oneEchart.setOption(xOptions)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'}]const twoEchart = echarts.init(proxy.$refs['userEchart'])twoEchart.setOption(xOptions)pieOptions.series = [{data:videoData,type:'pie',}]const threeEchart = echarts.init(proxy.$refs['videoEchart'])threeEchart.setOption(pieOptions)//监听页面的变化observer.value = new ResizeObserver(() => {oneEchart.resize()twoEchart.resize()threeEchart.resize()})if(proxy.$refs['echart']){observer.value.observe(proxy.$refs["echart"])}}onMounted(() => {getTableData()getCountData()getChartData()})</script><style lang="scss" scoped>.home{height:150vh;overflow:hidden;.user{display:flex;align-items:center;border-bottom:1px solid #ccc;margin-bottom:20px;img{width:150px;height:150px;border-radius: 50%;margin-right: 40px;}.user-info{p{line-height:40px;}.user-info-p{color:#999;}.user-info-admin{font-size:35px;}}}.login_info{p{line-height:30px;font-size:14px;color:#990;}span{color:#666;margin-left:60px;}}.user-table{margin-top:20px;}.num {display: flex;flex-wrap: wrap;justify-content: space-between;.el-card {width: 32%;margin-bottom: 20px;}.icons {width: 80px;height: 80px;font-size: 30px;text-align: center;line-height: 80px;color: #fff;}.detail {margin-left: 15px;display: flex;flex-direction: column;justify-content: center;.num {font-size: 30px;margin-bottom: 10px;}.txt {font-size: 14px;text-align: center;color: #999;}}}.graph {margin-top: 20px;display: flex;justify-content: space-between;.el-card {width: 48%;height: 260px;}}}</style>
没有后端 我们用mock来模拟网络请求 使用axios来处理网络请求
pnpm i axios
pnpm i mockjs
创建api文件夹 创建request.js 文件 二次封装下axios
import axios from "axios";import config from '../config/index'const service = axios.create({baseURL:config.baseApi,});const NETWORK_ERROR = '网络错误...'service.interceptors.request.use((config) => {return config},(error) => {return Promise.reject(error)})service.interceptors.response.use((res) => {const {code,data,msg} = res.dataif(code===200){return data}else{return Promise.reject(msg || NETWORK_ERROR)}})function request(options){// console.log(config.env)options.method = options.method || "get"if(options.method.toLowerCase() === "get"){options.params = options.data}//对mock的开关做一个处理let isMock = config.mockif(config.env !== "undefined"){isMock = config.env}//针对环境作处理if(config.env === "prod"){service.defaults.baseURL = config.baseAPi;}else{// console.log('isMock',isMock)service.defaults.baseURL = isMock ? config.mockApi : config.baseApi;}return service(options)}export default request

在api文件下创建api.js

import request from './request.js'export default {getTableData(){return request({url:"/home/getTable",method:'get',})},getCountData(){return request({url:"/home/getCountData",method:'get',})},getChartData(){return request({url:"/home/getChartData",method:'get',})},}

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

//mock.js
import Mock from "mockjs"
import homeApi from "./mockData/home.js"
Mock.mock(/api\/home\/getTableData/,"get",homeApi.getTableData)Mock.mock(/api\/home\/getCountData/,"get",homeApi.getCountData)Mock.mock(/api\/home\/getChartData/,"get",homeApi.getChartData)
// mockData/home.js
export default {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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 基于Python的鸢尾花聚类与分类
  • VS+opencv+环境配置
  • QEMU虚拟机(TODO)
  • 【Spring】——代理模式、AOP、MyBatis-Spring学习以及Spring事务
  • WPF中的数据模板和样式:实现一致性和可维护性
  • openmetadata安装
  • 赞!蚓链用数字化打造助农扶农电商平台!
  • 【多线程】线程池
  • 详解Qt 之 QColor、QColorSpace与QColorTransform
  • JSONP跨域
  • IDEA优化配置,提高启动和运行速度
  • 什么牌子的洗地机好用?推荐多款质量好洗地机的品牌
  • 说下Linux特点,与windows的区别
  • 【C++标准库】模拟实现string类
  • 【Python】pandas:排序、重复值、缺省值处理、合并、分组
  • IE9 : DOM Exception: INVALID_CHARACTER_ERR (5)
  • 【node学习】协程
  • 【Under-the-hood-ReactJS-Part0】React源码解读
  • 【笔记】你不知道的JS读书笔记——Promise
  • 〔开发系列〕一次关于小程序开发的深度总结
  • CAP理论的例子讲解
  • css系列之关于字体的事
  • Docker下部署自己的LNMP工作环境
  • el-input获取焦点 input输入框为空时高亮 el-input值非法时
  • Essential Studio for ASP.NET Web Forms 2017 v2,新增自定义树形网格工具栏
  • IndexedDB
  • Intervention/image 图片处理扩展包的安装和使用
  • mongo索引构建
  • Redash本地开发环境搭建
  • SpingCloudBus整合RabbitMQ
  • vue脚手架vue-cli
  • 仿天猫超市收藏抛物线动画工具库
  • 计算机常识 - 收藏集 - 掘金
  • 解决jsp引用其他项目时出现的 cannot be resolved to a type错误
  • 每天一个设计模式之命令模式
  • 排序算法之--选择排序
  • 探索 JS 中的模块化
  • 想晋级高级工程师只知道表面是不够的!Git内部原理介绍
  • 源码安装memcached和php memcache扩展
  • 《天龙八部3D》Unity技术方案揭秘
  • Java数据解析之JSON
  • ​html.parser --- 简单的 HTML 和 XHTML 解析器​
  • ​LeetCode解法汇总2808. 使循环数组所有元素相等的最少秒数
  • ​批处理文件中的errorlevel用法
  • ‌分布式计算技术与复杂算法优化:‌现代数据处理的基石
  • # SpringBoot 如何让指定的Bean先加载
  • ###C语言程序设计-----C语言学习(6)#
  • #HarmonyOS:基础语法
  • #if #elif #endif
  • #QT(QCharts绘制曲线)
  • (02)Hive SQL编译成MapReduce任务的过程
  • (1)(1.19) TeraRanger One/EVO测距仪
  • (function(){})()的分步解析
  • (python)数据结构---字典
  • (创新)基于VMD-CNN-BiLSTM的电力负荷预测—代码+数据