Vue3+vite后台管理系统
项目概述:
通过Vue3+vite脚手架搭建项目,结合Element-Plus、Echarts、Axios、Mock、Vuex完善各功能模块,进行后台管理系统的开发。
实训中注重对图表的应用,数据的引入,数据可以mock生成也可以写静态数据
各官网地址:
Element-Plus
Echarts
Axios
Mock
Vuex
Vite
- 项目准备1. Node.js安装
进入node.js官网选择版本安装,选择较低版本,比较稳定。
同时按WIN+R输入cmd打开命令提示符窗口,输入node -v查看安装的版本,出现版本号即安装成功。注意vite需要Node.js版本>=12.0.0 。
- 项目搭建
- 确定好项目存放的位置,在存放路径下输入cmd进入控制台,输入代码:
npm create vite@latest ,完成项目名称填写,框架选择vue,语言选择javascript。
- cd到已经创建的项目中,使用npm i 安装依赖,安装成功后使用工具打
开整个项目文件夹,进入终端利用cnpm run dev 运行项目。
- 后台管理系统准备工作1. Element Plus1. **安装 **
打开工具终端,输入npm install element-plus --save 进行安装(如果太慢,可以到文件管理器该项目目录下输入cmd进入终端,输入cnpm install element-plus --save ,后续安装操作都可以这样)
- 引入
这边采用局部(按需)引入,vue3的局部引用与全局引用相比不会复杂多少,但最后项目打包体积会减小很多。可以加快运行速度。
1、安装插件:
npm install -D unplugin-vue-components unplugin-auto-import
2、进入vite.config.js文件,将以下代码复制进文件
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
plugins中代码如下:
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
- Router1. 安装
打开终端输入:
npm install vue-router -S
- 引入
- 在src文件夹下新建router文件夹,router文件夹下新建一个index.js文件。
- 打开index.js文件输入以下代码引入路由
import { createRouter,createWebHashHistory } from "vue-router";
- 在src下新建views文件夹,并在views文件夹下新建Main.vue文件。
- 在src下新建home文件夹,并在home文件夹下新建Home.vue文件。
- 打开router文件夹下的index.js文件,进行基本的路由配置如下
const routes =[
{
path: '/',
component:()=> import('../views/Main.vue'),
redirect: '/home',
children: [
{
path:'/home',
name:'home',
component: () => import('../views/home/Home.vue')
},
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router //暴露
redirect:‘/home‘表示运行项目后首先进入home页面,后续有了登录页可以改成rediect:’/login(登录页名)‘
6、进入main.js文件引入router
import router from './router'
再使用router
app.use(router)
7、进入App组件在<template>闭合标签中放入router-view表示位置
8、Main.vue中写入首页布局代码,以及变化路由
<template>
<div>左侧菜单</div>
<div>头部</div>
<div>这个部分是变化的
<router-view />
</div>
</template>
9、进入Home.vue填入以下代码来做路由测试
<template>
<div>我是home组件</div>
</template>
- 可视化大屏创建1. 组件
在src下创建views文件夹,在views文件夹下,创建Screen.vue文件,作为可视化大屏
- 样式
- 导入tailwindcss,(用于样式)利用以下代码安装。
npm install -D tailwindcss
npx tailwindcss init 此行代码会产生一个tailwind.config.js文件,打开它进行修改配置。
将文件中的content修改为: content: ["./src/**/*.{html,js}"]
- 找到src下的style.css文件添加以下代码:
@tailwind base;
@tailwind components;
@tailwind utilities;
- 可视化大屏布局
布局效果:
<template>
<div class="bg-[url('assets/imgs/bgpic.jpg')] bg-cover bg-center h-screen text-white p-5 flex overflow-hidden >
<!-- 左 -->
<div class="flex-1 mr-5 bg-opacity-50 bg-slate-800 p-3 flex flex-col"></div>
<!-- 中 -->
<div class="w-1/2 mr-5 flex flex-col"> </div>
<!-- 右 -->
<div class="flex-1 bg-opacity-50 bg-slate-800 p-3 flex flex-col"> </div>
</div>
</template>
<script setup>
</script>
<style lang="scss" scoped>
.h-96{
height: 500px;
}
</style>
大致可以分为三列,八个图表,具体布局和图表数量可以自行修改。
- 可视化大屏组件
1、在src下,component文件夹中创建需要用到的图表组件,以饼图(遗失物品类)为例。
创建Pie.vue组件。并进行基本代码填充,分别为<template>、<script>、<style>,依格式将其余需要的图表组件创建完毕。
<template>
<div>
<div>【遗失物品类】</div>
</div>
</template>
<script>
</script>
<style>
</style>
- 将创建好的组件依次导入Screen.vue,以饼图(遗失物品类)为例。
<template>
<div class="bg-[url('assets/imgs/bgpic.jpg')] bg-cover bg-center h-screen text-white p-5 flex overflow-hidden bg-opacity-30"
v-if="data">
<!-- 左 -->
<div class="flex-1 mr-5 bg-opacity-50 bg-slate-800 p-3 flex flex-col">
</div>
<!-- 中 -->
<div class="w-1/2 mr-5 flex flex-col">
</div>
<!-- 右 -->
<div class="flex-1 bg-opacity-50 bg-slate-800 p-3 flex flex-col">
<Pie class=" h-96 box-border p-4 mt-2"/>
</div>
</div>
</template>
<script setup>
import Pie from '../components/Pie.vue';
</script>
<style lang="scss" scoped>
</style>
- 后台管理页面创建1. 首页布局
打开Element Plus 组件内容,Container块寻找适合的布局。
这里选择红圈这个布局,有侧边栏Aside,头部Header,主体Main。点击小圈的<>图标即可查看代码,将相应代码复制进Main.vue。并将el-main中的内容替换回router-view
- Header模块
Header****效果图
在components文件夹下,新建CommonHeader.vue组件完成template基本填充(基本填充意为在空vue文件中写好template、script、style)
<template>
<div>我是...</div>
</template>
<script>
</script>
<style>
</style>
进入Main.vue在script闭合标签中引入CommonHeader组件如下:
import { defineComponent } from 'vue';
import CommonHeader from '../components/CommonHeader.vue';
export default defineComponent({
components: {
CommonHeader,
},
});
并将原本布局中的
换为
添加进去。
因为header涉及到图标展示,我们需要使用到element plus中的icon组件
首先打开终端输入以下代码进行icon的全局安装:
npm i @element-plus/icons-vue -S
安装好以后,进入main.js文件进行图标注册,
<template>
<el-header>
<div class="l-content">
<!-- 图标展示 -->
<el-button size="small" plain @click="handleCollapse">
<el-icon :size="20">
<Menu />
</el-icon>
</el-button>
<!-- 面包屑 -->
<el-breadcrumb separator="/" class="bread">
<!-- 首页一定存在,因此写死 -->
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item :to="current.path " v-if="current">{{current.label}}</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="ScreenButton">
<el-button type="primary" @click="GetScreen">水寻可视化</el-button>
</div>
<div class="r-content">
<el-dropdown>
<span class="el-dropdown-link">
<img class="logo" :src="getImgSrc('logo')" alt="">
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>管理员中心</el-dropdown-item>
<el-dropdown-item>个人中心</el-dropdown-item>
<el-dropdown-item @click="handleLoginOut">退出</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</el-header>
</template>
而后即可通过以下形式来使用icon
<el-icon :size="20">
<Menu />
</el-icon>
icon名称可以进入element Plus找到icon组件进行查看
由上方效果图我们可以得知,header主要由icon菜单图标、可视化大屏跳转按钮、logo状下拉菜单组成。
Template****:
代码1-10行为按钮菜单
代码11-17行为静态面包屑--首页
代码21-23行为可视化大屏跳转按钮
代码25-35行为logo状下拉菜单
下拉菜单组件我们可以打开Element Plus找到Dropdown组件查看代码,然后根据需求删减action选项。
Script****:
<script>
import { computed } from 'vue';
import {useStore} from 'vuex';
import { useRouter } from 'vue-router';
export default {
setup(){
let store = useStore()
let getImgSrc = (logo) =>{
return new URL(`../assets/imgs/${logo}.png`, import.meta.url).href;
};
let handleCollapse = () =>{
//调用vuex中的mutations
store.commit('updateIsCollapse');
};
//计算属性
const current = computed(()=>{
return store.state.currentMenu;
});
const router =useRouter();
const handleLoginOut =()=>{
store.commit("cleanMenu");
router.push({
name:"login",
})
};
const GetScreen =()=>{
router.push({
name:'screen',
})
}
return{
getImgSrc,
handleCollapse,
current,
handleLoginOut,
GetScreen,
};
},
};
</script>
代码2-4行分别引入所需的元素
代码7-11行用于动态引入下拉菜单的图片,图片可自行寻找,一般存放在src/assets/imgs路径下。
代码12-19行用于实现左侧菜单按钮的伸展和收缩,利用了vuex的状态管理,因此需要引入vuex。
代码20-26行用于使用下拉菜单中的退出按钮,执行从首页退出至登录页
代码27-31行用于从首页跳转到可视化页面
代码32-38行返回上面各函数,函数不反回无法调用。
CSS****样式
<style lang="less" scoped>
header{
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
background: RGB(36,77,106);
}
.r-content{
.logo{
width: 50px;
height: 50px;
border-radius: 10;
}
}
.l-content{
display: flex;
align-items: center;
.el-button{
margin-right: 20px;
}
h3{
color: #fff;
}
}
}
:deep(.bread span) {
color: #fff !important;
cursor: pointer;
}
.ScreenButton{
margin-right: -1100px;
box-shadow: #000;
}
</style>
具体样式可根据实际情况调整。
当运行以后发现显示有问题,可以通过在浏览器页面打开控制台,快捷键ctrl+shift+i,点击element来审查元素。
下方styles可以查看当前的样式代码,也可以在控制台编写样式代码来预览效果,效果合适以后再回到工具中进行具体修改。
- Aside模块
Aside****效果图
侧边栏
Template
<template>
<el-aside :width="$store.state.isCollapse ? '180px':'64px'">
<el-menu class="el-menu-vertical-demo"
background-color="#479AD3"
text-color="#fff"
:collapse="!$store.state.isCollapse"
:collapse-transition="false"
>
<h3 v-show="$store.state.isCollapse">水寻管理</h3>
<h3 v-show="!$store.state.isCollapse">水寻</h3>
<el-menu-item
:index="item.path"
v-for="item in noChildren()"
:key="item.path"
@click="clickMenu(item)"
>
<!-- 动态引入icon -->
<component class="icons" :is="item.icon"></component>
<span>{{ item.label }}</span>
</el-menu-item>
<el-sub-menu
:index="item.label"
v-for="item in hasChildren()"
:key="item.path"
>
<template #title>
<component class="icons" :is="item.icon"></component>
<span>{{ item.label }}</span>
</template>
<el-menu-item-group >
<el-menu-item
:index="subItem.path"
v-for="(subItem,subIndex) in item.children"
:key="subIndex"
@click="clickMenu(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>
代码2-8行设定侧边栏宽高与颜色,可自行根据需求修改。
代码10-11行配合header中的菜单按钮实现侧边栏的缩放和展开
代码13-25行判断是否有子菜单,并动态引入icon图标
代码27-48行用于子菜单的显示
Script****:
<script>
import {useRouter} from 'vue-router'
import { useStore } from 'vuex';
export default {
setup() {
const store = useStore()
const router = useRouter();
const noChildren= () =>{
return asyncList.filter((item) => !item.children);
};
const hasChildren = () =>{
return asyncList.filter((item) => item.children);
};
const asyncList = store.state.menu;
const clickMenu = (item) => {
router.push({
name: item.name,
});
//vuex 来管理
store.commit('selectMenu',item);
};
return {
noChildren,
hasChildren,
clickMenu,
}
},
}
</script>
Css****样式
<style lang="less" scoped>
.icons{
width: 18px;
height: 18px;
}
.el-menu{
border-right: none;
h3{
line-height: 48px;
color: #fff;
text-align: center;
font-size: 20px;
}
}
</style>
- Home模块
效果图
依图可知我们的home页面主要由一个el-card,一个el-card嵌套el-table,一个el-card组和一个海报走马灯组成。
Template
<template>
<el-row class="home" :gutter="20">
<el-col :span="8" style="margin-top: 20px">
<el-card shadow="hover">
<div class="poster">
<img src="../../assets/imgs/poster.jpg" alt="" />
<div class="postinfo">
<p class="name"></p>
<p class="role"></p>
</div>
</div>
<div class="login-info">
<p>上次登录时间:<span>2024.01.05</span></p>
<p>上次登录地点:<span>浙江水利水电学院</span></p>
</div>
</el-card >
<!-- 失物招领信息数据 -->
<el-card shadow="hover" style="margin-top: 20px" height="450px">
<el-table :data="tableData">
<el-table-column
v-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="name">
<el-card :body-style="{display:'flex',padding:0}" v-for="item in lostData" :key="item.name"
>
<component class="icons" :is="item.icon" :style="{background: item.color}"></component>
<div class="detail">
<p class="number">{{item.value}}件</p>
<p class="txt">{{item.name}}</p>
</div>
</el-card>
</div>
<div class="block text-center">
<span class="demonstration"
></span
>
<el-carousel height="463px">
<el-carousel-item v-for="(item,index) in carouseData" :key="index">
<!-- <h3 class="small justify-center" text="2xl">{{ item }}</h3> -->
<img :src="item.src" class="Pic" alt="" />
</el-carousel-item>
</el-carousel>
</div>
</el-col>
</el-row>
</template>
代码4-16行用于表现海报卡片
代码18-27行用于表现失物数据卡片
代码代码31-38行用于表现失物卡片组
代码44-49行用于表现海报走马灯
Script
<script>
import { defineComponent,onMounted,ref,getCurrentInstance, reactive } from "vue";
export default defineComponent({
setup() {
const {proxy} = getCurrentInstance();
let tableData =ref([]);
let lostData = ref([]);
const tableLabel={
getTime:'捡到时间',
picker:'拾取者',
goods:'物品',
position:'拾取位置'
};
const getTableList = async ()=>{
let res = await proxy.$api.getTableData();
tableData.value=res
};
//获取失物招领数据
const getLostData =async () =>{
let res=await proxy.$api.getLostData();
lostData.value = res;
};
const getImageUrl =(name)=>{
// return new URL(name,import.meta.url).href;
return new URL(`../../assets/imgs/${name}.png`, import.meta.url).href
}
const carouseData = reactive([
{src: getImageUrl('pic1')},
{src: getImageUrl('pic2')},
{src: getImageUrl('pic3')},
{src: getImageUrl('pic4')},
]);
onMounted(()=>{
getTableList();
getLostData();
})
return {
tableData,
tableLabel,
lostData,
carouseData,
getImageUrl,
};
},
})
</script>
在home页面中我们会在第二章失物招领数据卡片上用到数据的引用,这涉及到axios数据请求以及mock模拟数据。上方准备工作已经完成了安装。现在我们来配置我们的mock数据。
- 在src下新建一个api文件夹。在api文件夹下新建api.js文件,mock.js文件,request.js文件。
- 打开request.js文件,输入以下代码进行数据拦截请求,这里会用到axios。
import axios from 'axios'
import config from '../config'
import{ElMessage} from 'element-plus'
const NETWORK_ERROR='网络请求异常,请稍后重试......'
//创建一个axios实例对象
const service = axios.create({
baseURL: config.baseApi
})
//在请求之前
service.interceptors.request.use((req)=>{
//可以自定义header
//jwt-token认证的时候
return req
})
//在请求之后
service.interceptors.response.use((res)=>{
// console.log(res);
const {code,data,msg}=res.data
//根据后端 视情况而定
if(code==200){
return data
}else{
//网络请求错误
ElMessage.error(msg || NETWORK_ERROR)
return Promise.reject(msg || NETWORK_ERROR)
}
})
//封装核心函数
function request(options) {
//{}
options.method = options.method || 'get'
if(options.method.toLowerCase() == 'get'){
options.params =options.data
}
//对mock的处理
let isMOCK = config.ismock //总体的mock
if(typeof options.mock !=='undefined'){
isMOCK = options.mock
}
//对线上环境做处理
if(config.env == 'prod'){
//不给用mock的机会
service.defaults.baseURL = config.baseApi
}else{
service.defaults.baseURL = isMOCK ? config.mockApi : config.baseApi
}
return service(options)
}
export default request
- 打开mock.js进行获取数据的配置
import Mock from 'mockjs'
import homeApi from './mockData/home'
import userApi from './mockData/user'
import lostApi from './mockData/lost'
import permissionApi from './mockData/permission'
//本地拦截请求
Mock.mock('/home/getData',homeApi.getHomeData)
//本地获取user的数据
Mock.mock(/user\/getUser/,'get',userApi.getUserList)
Mock.mock(/user\/add/,'post',userApi.createUser)
Mock.mock(/user\/edit/,'post',userApi.updateUser)
Mock.mock(/user\/delete/,'get',userApi.deleteUser)
Mock.mock(/permission\/getMenu/,'post',permissionApi.getMenu)
//本地获取lost数据
Mock.mock(/lost\/getLost/,'get',lostApi.getLostList)
Mock.mock(/lost\/add/,'post',lostApi.createUser)
Mock.mock(/lost\/edit/,'post',lostApi.updateUser)
Mock.mock(/lost\/delete/,'get',lostApi.deleteUser)
这里的获取数据代码包括了整个项目中的数据,目前步骤仅到getHomeData,这里我们采用了两种方法,一种是本地mock生成数据,另一种则是线上mock生成数据。第二种更为重要。
方法如下:
- 我们打开fastmock快速mock网站,微信扫码登录账号,写我们自己的接口。
- 点击加号创建接口,其中需要设计接口根地址。创建完成后,点击新增接口。
我们就可以看到如图所示页面,随后填入我们的自定义接口名,返回类型,url地址,以及右侧我们的mock数据即可创建接口。Code 200的含义可以理解拦截数据时的验证码,相同在则拦截成功。
- 随后我们在src下创建config文件夹,并在下面创建index.js文件。在其中配置我们的环境配置文件如下:
// 环境配置文件
// 企业级项目中,有三个环境:
//开发环境、测试环境、线上环境
//用env取得当前环境,如果取不到,则为线上环境
const env = import.meta.env.MODE || 'prod'
const EnvConfig = {
development: {
daseApi:'/api',
mockApi:'https://www.fastmock.site/mock/e98d3ea2f4ce5e0e5f85c638ab745984/lost-mockData',
},
test: {
daseApi:'/test.api',
mockApi:'https://www.fastmock.site/mock/e98d3ea2f4ce5e0e5f85c638ab745984/lost-mockData',
},
pro: {
daseApi:'//pro.api',
mockApi:'https://www.fastmock.site/mock/e98d3ea2f4ce5e0e5f85c638ab745984/lost-mockData',
},
}
export default{
env,
//mock总开关
mock:true,
//解构导出api
...EnvConfig[env]
}
其中的mock api即为我们fastmock上所创建的根接口地址。
4、随后我们进入api.js文件进行数据获取,通过创建函数来传送数据。
// 对整个项目api的管理
import request from "./request"
export default{
//home组件 首页失物招领卡片数据获取
getTableData(params){
return request({
url:'/home/getTableData',
method: 'get',
data: params,
mock: true
})
},
getLostData(){
return request({
url:'/home/getLostData',
method: 'get',
mock: true
})
},
getUserData(params){
return request({
url:'/user/getUser',
method: 'get',
mock:false, //该mock置为true则会采用线上的api地址
data:params,
//data:{total: 0,page: 1,}
})
},
gettheLostData(params){
return request({
url:'/lost/getLost',
method: 'get',
mock:false, //该mock置为true则会采用线上的api地址
data:params,
//data:{total: 0,page: 1,}
})
},
addUser(params){
return request({
url:'user/add',
method:'post',
mock: false,
data: params,
})
},
addLost(params){
return request({
url:'lost/add',
method:'post',
mock: false,
data: params,
})
},
editUser(params){
return request({
url:'/user/edit',
method: 'post',
mock:false, //该mock置为true则会采用线上的api地址
data:params,
})
},
editLost(params){
return request({
url:'/lost/edit',
method: 'post',
mock:false, //该mock置为true则会采用线上的api地址
data:params,
})
},
deleteUser(params){
return request({
url:'/user/delete',
method: 'get',
mock:false, //该mock置为true则会采用线上的api地址
data:params,
})
},
deleteLost(params){
return request({
url:'/lost/delete',
method: 'get',
mock:false, //该mock置为true则会采用线上的api地址
data:params,
})
},
//根据用户的用户名不同,返回不一样的菜单列表
getMenu(params){
return request({
url:'/permission/getMenu',
method:'post',
mock:false,
data:params
})
},
getPicData(){
return request({
url:'/home/getPicData',
method: 'get',
mock: true
})
},
getChartData(){
return request({
url:'home/getChartData',
method: 'get',
mock: true
})
}
}
这里同样为整个项目的所有数据获取代码,当前仅看getTableData即可。
Css****样式
<style lang="less" scoped>
.home{
// background: #;
.poster{
display: flex;
align-items: center;
padding-bottom: 20px;
border-bottom: 1px solid #ccc;
margin-bottom: 20px;
img{
width:600px ;
height:200px ;
border-radius:5%;
margin-right: 10px;
}
}
.login-info{
p{
line-height: 30px;
font-size: 14px;
color: #999;
span{
color: #666;
margin-left: 60px;
}
}
}
.name{
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;
.number{
font-size: 30px;
margin-bottom: 10px;
}
.txt{
font-size: 14px;
text-align: center;
color: #999;
}
}
}
.demonstration {
color: var(--el-text-color-secondary);
}
.el-carousel__item h3 {
color: #475669;
opacity: 0.75;
line-height: 150px;
margin: 0;
text-align: center;
}
.el-carousel__item:nth-child(2n) {
background-color: #99a9bf;
}
.el-carousel__item:nth-child(2n + 1) {
background-color: #d3dce6;
}
.Pic{
height: 463px;
width: 100%;
}
}
</style>
版权归原作者 我就说好玩 所有, 如有侵权,请联系我们删除。