赞
踩
用VUE框架搭建一个通用后台管理系统,组件化、模块化实现用户登陆、后台管理、页面显示和跳转等功能。
vue通用后台管理系统(一)
vue通用后台管理系统(二)
vue通用后台管理系统(三)
vue通用后台管理系统(四)
创建:vue create my-app
注意版本的兼容性,这里选择安装2.15.13版本
//导入Element-UI
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI)
cnpm i less@4.1.2 npm i less-loader@6.0.0
安装:cnpm i vuex@3.6.2
安装:cnpm install axios@0.27.2
安装:cnpm i echarts@5.1.2
cnpm i vue-router@3.6.5
import Vue from 'vue' import VueRouter from 'vue-router' //导入组件 import Home from '../views/Home.vue' import User from '../views/User.vue' //使用vuerouter Vue.use(VueRouter) //路由与组件映射 const routes = [ { path: '/home', component: Home }, { path: '/user', component: User } ] //创建router实例 const router = new VueRouter({ routes, }) //对外暴露路由 export default router
![[屏幕截图 2023-12-04 145945.png]]
//导入router
import router from './router'
new Vue({
//挂载router
router,
render: h => h(App),
}).$mount('#app')
<router-view></router-view>
import Main from '../views/Main.vue'
const routes = [
{
path: '/',
component: Main,
children: [
{ path: 'home', component: Home },
{ path: 'user', component: User }
]
}
]
<el-container>
<el-aside width="200px">Aside</el-aside>
<el-container>
<el-header>Header</el-header>
<el-main>
<router-view></router-view>
</el-main>
</el-container>
</el-container>
将ElementUI中的导航菜单导入进来,注意一个容器内只允许容纳一个ElementUI标签。
menuData: [ { path: '/', name: 'home', label: '首页', icon: 's-home', url: 'Home/Home' }, { path: '/mall', name: 'mall', label: '商品管理', icon: 'video-play', url: 'MallManage/MallManage' }, { path: '/user', name: 'user', label: '用户管理', icon: 'user', url: 'UserManage/UserManage' }, { label: '其他', icon: 'location', children: [ { path: '/page1', name: 'page1', label: '页面1', icon: 'setting', url: 'Other/PageOne' }, { path: '/page2', name: 'page2', label: '页面2', icon: 'setting', url: 'Other/PageTwo' } ] } ]
computed: {
//没有子菜单
noChildren() {
return this.menuData.filter(item => !item.children)
},
//有子菜单
hasChildren() {
return this.menuData.filter(item => item.children)
},
}
<template> <el-menu default-active="1-4-1" class="el-menu-vertical-demo" @open="handleOpen" @close="handleClose" :collapse="isCollapse" background-color="#454545" text-color="#ffffff" active-text-color="#00ffff"> <!-- 对menuData中无子节点的对象进行遍历 --> <el-menu-item v-for="item in noChildren" :key="item.name" :index="item.name"> <i :class="`el-icon-${item.icon}`"></i> <span slot="title">{{ item.label }}</span> </el-menu-item> <!-- 对menuData中有子节点的对象进行遍历 --> <el-submenu v-for="item in hasChildren" :key="item.name" :index="item.name"> <template slot="title"> <i :class="`el-icon-${item.icon}`"></i> <span slot="title">{{ item.label }}</span> </template> <!-- <el-menu-item v-for="subItem in item.children" :key="subItem.path" :index="subItem.path">{{ subItem.label }}</el-menu-item> --> <el-menu-item-group v-for="subItem in item.children" :key="subItem.path"> <el-menu-item :index="subItem.path">{{ subItem.label }}</el-menu-item> </el-menu-item-group> </el-submenu> </el-menu> </template>
<style lang="less" scoped> .el-menu-vertical-demo:not(.el-menu--collapse) { width: 200px; min-height: 400px; } .el-menu{ border-right: none; height: 100vh; //less样式 h3 { color: #fff; text-align: center; line-height: 48px; font-size: 16px; font-weight: 400; } } </style>
<style lang="less" scoped>
el-header{
padding: 0;
}
</style>
<!-- 清除边框 -->
<style lang="less">
html, body, h3{
margin:0px;
padding: 0px;
}
</style>
![[Pasted image 20231204175502.png]]
![[屏幕截图 2023-12-04 203236.png]]
在mothods中添加方法
clickMenu(item){
//跳转前判断路由是否合法
if(this.$route.path !== item.path && !(this.$route.path === '/home' && item.path == '/')){
this.$router.push(item.path);
}
}
![[屏幕截图 2023-12-04 204327.png]]
<template> <div class="headerContainer"> <div class="l-content"> <el-button style="margin-right:20px" icon="el-icon-menu" size="mini"></el-button> <el-breadcrumb separator="/"> <el-breadcrumb-item>首页</el-breadcrumb-item> </el-breadcrumb> </div> <div class="r-content"> <el-dropdown> <span class="el-dropdown-link"> <img class="user" src="../assets/images/user.png" alt=""> </span> <el-dropdown-menu slot="dropdown"> <el-dropdown-item>个人中心</el-dropdown-item> <el-dropdown-item>退出</el-dropdown-item> </el-dropdown-menu> </el-dropdown> </div> </div> </template> <script> </script> <style lang="less" scoped> .headerContainer{ padding: 0 20px; background-color: #333; height: 60px; display: flex; justify-content: space-between;//左右分离 align-items: center;//垂直居中 .l-content{ display: flex; align-items: center; /deep/.el-breadcrumb__item { .el-breadcrumb__inner { font-weight: normal; &.is-link{ color: #999 } } &:last-child { .el-breadcrumb__inner { color: #FFF } } } } .r-content{ .user { width: 40px; height: 40px; border-radius: 50%; } } .text { color: #fff; font-size: 14px; margin-left: 10px; } } </style>
import Vue from 'vue'
import Vuex from 'vuex'
import tab from './tab'
Vue.use(Vuex)
export default new Vuex.Store({
modules:{
tab,
}
})
export default {
state:{
},
mutations: {
}
}
<h3>{{ isCollapse ? '后台':'通用后台管理系统' }}</h3>
<template>
<el-row>
<el-col :span="8">
<div ></div>
</el-col>
<el-col :span="16">
<div ></div>
</el-col>
</el-row>
</template>
<template> <el-row> <el-col :span="8"> <el-card class="box-card"> <div class="user"> <img src="../assets/images/user.png" alt=""> <div class="userinfo"> <p class="name">Admin</p> <p class="access">管理员</p> <p></p> </div> </div> <div class="login-info"> <p>上次登陆地点: <span>广东 珠海</span></p> <p>上次登陆时间: <span>20231224</span></p> </div> </el-card> <el-zard style="margin-top: 20px; height: 460px"></el-card </el-col> <el-col :span="16"> <div ></div> </el-col> </el-row> </template> <script> export default { name: 'Home', data() { return { } } } </script> <style lang="less" scoped> .user{ display: flex; align-items: center; padding-bottom: 20px; margin-bottom: 20px; img{ margin-right: 40px; width: 150px; height: 150px; border-radius: 50%; } .userinfo { .name { font-size: 32px; margin-bottom: 10px; } .access{ color: #999999; } } } .login-info { p { line-height: 28px; font-size: 14px; color:#999999; span { color: #666; margin-left: 60px; } } } </style>
countData: [ { name: "今日支付订单", value: 1234, icon: "success", color: "#2ec7c9", }, { name: "今日收藏订单", value: 210, icon: "star-on", color: "#ffb980", }, { name: "今日未支付订单", value: 1234, icon: "s-goods", color: "#5ab1ef", }, { name: "本月支付订单", value: 1234, icon: "success", color: "#2ec7c9", }, { name: "本月收藏订单", value: 210, icon: "star-on", color: "#ffb980", }, { name: "本月未支付订单", value: 1234, icon: "s-goods", color: "#5ab1ef", }, ],
<div class="num">
<el-card v-for="item in countData" :key="item.name" :body-style="{ display: 'flex', padding: 0 }">
<i class="icon" :class="`el-icon-${item.icon}`" :style="{ background: item.color }"></i>
<div class="detail">
<p class="price">¥{{ item.value }}</p>
<p class="desc">{{ item.name }}</p>
</div>
</el-card>
</div>
import axios from 'axios' const http = axios.create({ baseURL: '/api', timeout: 10000, }) // 添加请求拦截器 http.interceptors.request.use(function (config) { // 在发送请求之前做些什么 return config; }, function (error) { // 对请求错误做些什么 return Promise.reject(error); }); // 添加响应拦截器 http.interceptors.response.use(function (response) { // 2xx 范围内的状态码都会触发该函数。 // 对响应数据做点什么 return response; }, function (error) { // 超出 2xx 范围的状态码都会触发该函数。 // 对响应错误做点什么 return Promise.reject(error); }); export default http
//api文件夹中放置关于接口数据的信息
import http from '../utils/request'
//请求首页数据
export const getData = () => {
//返回一个Promise对象
return http.get('/home/getData')
}
cnpm i mockjs@1.1.0
import Mock from 'mockjs'
import homeApi from './mockServeData/home'
import user from './mockServeData/user'
import permission from './mockServeData/permission'
Mock.mock('/api/home/getData', homeApi.getStatisticalData);
//用户列表的数据
Mock.mock('/api/user/add', 'post', user.createUser);
Mock.mock('/api/user/edit', 'post', user.updateUser);
Mock.mock('/api/user/del', 'post', user.deleteUser);
Mock.mock(/api\/user\/get/, user.getUserList);
Mock.mock(/api\/permission\/getMenu/, 'post', permission.getMenu)
import './api/mock'
// mock数据模拟 import Mock from 'mockjs' // 图表数据 let List = [] export default { getStatisticalData: () => //Mock.Random.float 产生随机数100到8000之间 保留小数 最小0位 最大0位 for (let i = 0; i < 7; i++) { List.push( Mock.mock({ 苹果: Mock.Random.float(100, 8000, 0, 0), vivo: Mock.Random.float(100, 8000, 0, 0), oppo: Mock.Random.float(100, 8000, 0, 0), 魅族: Mock.Random.float(100, 8000, 0, 0), 三星: Mock.Random.float(100, 8000, 0, 0), 小米: Mock.Random.float(100, 8000, 0, 0) }) ) } return { code: 20000, data: { // 饼图 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 } ], // 折线图 orderData: { date: ['20191001', '20191002', '20191003', '20191004', '20191005', '20191006', '20191007'], data: List }, 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 } ] } } } }
import Mock from 'mockjs' export default { getMenu: config => { const { username, password } = JSON.parse(config.body) // 先判断用户是否存在 // 判断账号和密码是否对应 if (username === 'admin' && password === 'admin') { return { code: 20000, data: { menu: [ { path: '/home', name: 'home', label: '首页', icon: 's-home', url: 'Home.vue' }, { path: '/mall', name: 'mall', label: '商品管理', icon: 'video-play', url: 'Mall.vue' }, { path: '/user', name: 'user', label: '用户管理', icon: 'user', url: 'User.vue' }, { label: '其他', icon: 'location', children: [ { path: '/page1', name: 'page1', label: '页面1', icon: 'setting', url: 'PageOne.vue' }, { path: '/page2', name: 'page2', label: '页面2', icon: 'setting', url: 'PageTwo.vue' } ] } ], token: Mock.Random.guid(), message: '获取成功' } } } else if (username === 'xiaoxiao' && password === 'xiaoxiao') { return { code: 20000, data: { menu: [ { path: '/home', name: 'home', label: '首页', icon: 's-home', url: 'Home.vue' }, { path: '/video', name: 'video', label: '商品管理', icon: 'video-play', url: 'Mall.vue' } ], token: Mock.Random.guid(), message: '获取成功' } } } else { return { code: -999, data: { message: '密码错误' } } } } }
import Mock from 'mockjs' // get请求从config.url获取参数,post从config.body中获取参数 function param2Obj (url) { const search = url.split('?')[1] if (!search) { return {} } return JSON.parse( '{"' + decodeURIComponent(search) .replace(/"/g, '\\"') .replace(/&/g, '","') .replace(/=/g, '":"') + '"}' ) } let List = [] const count = 200 for (let i = 0; i < count; i++) { List.push( Mock.mock({ id: Mock.Random.guid(), name: Mock.Random.cname(), addr: Mock.mock('@county(true)'), 'age|18-60': 1, birth: Mock.Random.date(), sex: Mock.Random.integer(0, 1) }) ) } export default { // /** // * 获取列表 // * 要带参数 name, page, limt; name可以不填, page,limit有默认值。 // * @param name, page, limit // * @return {{code: number, count: number, data: *[]}} // */ getUserList: config => { const { name, page = 1, limit = 20 } = param2Obj(config.url) console.log('name:' + name, 'page:' + page, '分页大小limit:' + limit) const mockList = List.filter(user => { if (name && user.name.indexOf(name) === -1 && user.addr.indexOf(name) === -1) return false return true }) const pageList = mockList.filter((item, index) => index < limit * page && index >= limit * (page - 1)) return { code: 20000, count: mockList.length, list: pageList } }, /** * 增加用户 * @param name, addr, age, birth, sex * @return {{code: number, data: {message: string}}} */ createUser: config => { const { name, addr, age, birth, sex } = JSON.parse(config.body) console.log(JSON.parse(config.body)) List.unshift({ id: Mock.Random.guid(), name: name, addr: addr, age: age, birth: birth, sex: sex }) return { code: 20000, data: { message: '添加成功' } } }, /** * 删除用户 * @param id * @return {*} */ deleteUser: config => { const { id } = JSON.parse(config.body) if (!id) { return { code: -999, message: '参数不正确' } } else { List = List.filter(u => u.id !== id) return { code: 20000, message: '删除成功' } } }, /** * 批量删除 * @param config * @return {{code: number, data: {message: string}}} */ batchremove: config => { let { ids } = param2Obj(config.url) ids = ids.split(',') List = List.filter(u => !ids.includes(u.id)) return { code: 20000, data: { message: '批量删除成功' } } }, /** * 修改用户 * @param id, name, addr, age, birth, sex * @return {{code: number, data: {message: string}}} */ updateUser: config => { const { id, name, addr, age, birth, sex } = JSON.parse(config.body) const sex_num = parseInt(sex) List.some(u => { if (u.id === id) { u.name = name u.addr = addr u.age = age u.birth = birth u.sex = sex_num return true } }) return { code: 20000, data: { message: '编辑成功' } } } }
<template> <el-row> <el-col :span="8" style="padding-right: 10px"> <el-card class="box-card"> <div class="user"> <img src="../assets/images/user.png" alt=""> <div class="userinfo"> <p class="name">Admin</p> <p class="access">SuperAdmin</p> </div> </div> <div class="login-info"> <p>上次登录时间:<span>20231123</span></p> <p>上次登录地点:<span>珠海</span></p> </div> </el-card> <el-card style="margin-top:20px; height:460px"> <el-table :data="tableData" style="width: 100%"> <!-- <el-table-column prop="name" label="课程" width="180"> </el-table-column> <el-table-column prop="todayBuy" label="今日购买" width="180"> </el-table-column> <el-table-column prop="monthBuy" label="本月购买" width="180"> </el-table-column> <el-table-column prop="totalBuy" label="总购买" width="180"> </el-table-column> --> <el-table-column v-for="(val, key) in tableLabel" :key="key" :prop="key" :label="val" /> </el-table> </el-card> </el-col> <el-col :span="16" style="padding-left: 10px"> <div class="num"> <el-card v-for="item in countData" :key="item.name" :body-style="{ display: 'flex', padding: 0 }"> <i class="icon" :class="`el-icon-${item.icon}`" :style="{ background: item.color }"></i> <div class="detail"> <p class="price">¥{{ item.value }}</p> <p class="desc">{{ item.name }}</p> </div> </el-card> </div> <el-card style="height: 280px"> <!-- 折线图 --> <div ref="echarts1" style="height: 280px;"></div> </el-card> <div class="graph"> <el-card style="height: 260px"> <div ref="echarts2" style="height: 260px;"></div> </el-card> <el-card style="height: 260px"> <div ref="echarts3" style="height: 240px;"></div> </el-card> </div> </el-col> </el-row> </template> <script> import { getData } from '../api' import * as echarts from 'echarts' import { toRefs } from 'vue'; import { ref } from 'vue'; import { onMounted } from 'vue'; export default { data() { return { tableData: [], tableLabel: { name: '课程', todayBuy: '今日购买', monthBuy: '本月购买', totalBuy: '总购买' }, countData: [ { name: "今日支付订单", value: 1234, icon: "success", color: "#2ec7c9", }, { name: "今日收藏订单", value: 210, icon: "star-on", color: "#ffb980", }, { name: "今日未支付订单", value: 1234, icon: "s-goods", color: "#5ab1ef", }, { name: "本月支付订单", value: 1234, icon: "success", color: "#2ec7c9", }, { name: "本月收藏订单", value: 210, icon: "star-on", color: "#ffb980", }, { name: "本月未支付订单", value: 1234, icon: "s-goods", color: "#5ab1ef", }, ], } }, mounted() { getData().then(({ data }) => { const { tableData } = data.data; this.tableData = tableData; //基于准备好的dom,初始化echarts实例 const echarts1 = echarts.init(this.$refs.echarts1) //指定图标的配置项和数据 var echarts1option = {} //处理数据xAxis-x轴 const { orderData, userData, videoData } = data.data const xAxis = Object.keys(orderData.data[0]) const xAxisData = { data: xAxis } echarts1option.xAxis = xAxisData; echarts1option.yAxis = {} echarts1option.legend = xAxisData; echarts1option.series = [] //对原数据进行重新组装 xAxis.forEach(key => { echarts1option.series.push({ name: key, data: orderData.data.map(item => item[key]), type: 'line', }) }) //使用刚指定的配置项和数据显示图表 echarts1.setOption(echarts1option) //柱状图 const echarts2 = echarts.init(this.$refs.echarts2) const echarts2option = { legend: { // 图例文字颜色 textStyle: { color: "#333", }, }, grid: { left: "20%", }, // 提示框 tooltip: { trigger: "axis", }, xAxis: { type: "category", // 类目轴 data: userData.map(item => item.date), axisLine: { lineStyle: { color: "#17b3a3", }, }, axisLabel: { interval: 0, color: "#333", }, }, yAxis: [ { type: "value", axisLine: { lineStyle: { color: "#17b3a3", }, }, }, ], color: ["#2ec7c9", "#b6a2de"], series: [ { name: '新增用户', data: userData.map(item => item.new), type: 'bar' }, { name: '活跃用户', data: userData.map(item => item.active), type: 'bar' } ], } //调用实例的setOption echarts2.setOption(echarts2option) //饼状图 const echarts3 = echarts.init(this.$refs.echarts3) const echarts3option = { tooltip: { trigger: "item", }, color: [ "#0f78f4", "#dd536b", "#9462e5", "#a6a6a6", "#e1bb22", "#39c362", "#3ed1cf", ], series: [ { data: videoData, type: 'pie', }, ], } echarts3.setOption(echarts3option); }) } } </script> <style lang="less" scoped> .user { padding-bottom: 20px; margin-bottom: 20px; border-bottom: 1px solid #ccc; display: flex; align-items: center; img { margin-right: 40px; width: 150px; height: 150px; border-radius: 50%; } .userinfo { .name { font-size: 32px; margin-bottom: 10px; } .access { color: #999999; } } } .login-info { p { line-height: 28px; font-size: 14px; color: #999999; span { color: #666666; margin-left: 60px; } } } .num { display: flex; flex-wrap: wrap; justify-content: space-between; .icon { width: 80px; height: 80px; font-size: 30px; color: #fff; text-align: center; line-height: 80px; } .detail { margin-left: 15px; display: flex; flex-direction: column; justify-content: center; .price { padding: none; font-size: 30px; margin-top: 10px; margin-bottom: 10px; line-height: 30px; height: 30px; } .desc { font-size: 10px; color: #999; text-align: center; } } .el-card { width: 32%; margin-bottom: 20px; } } .graph { margin-top: 20px; display: flex; justify-content: space-between; .el-card { width: 48% } } </style>
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。