文章目录
- 前言
- 项目文件目录
- api
- mockServe
- home.js
- permission.js
- index.js
- mock.js
- user.js
- assert
- components
- CommonAside.vue
- CommonHeader.vue
- CommonTags.vue
- data
- echartsData
- order.js
- user.js
- video.js
- mockData
- tableData.js
- userData.js
- videoData.js
- CountData.js
- MenuData.js
- TableData.js
- TableLabel.js
- router
- index.js
- store
- index.js
- tab.js
- utils
- request.js
- Views
- Home.vue
- Login.vue
- Main.vue
- Mall.vue
- PageOne.vue
- PageTwo.vue
- User.vue
- App.vue
- main.js
- 要安装的依赖
前言
本来不打算用博客的方式记录代码的,想用git把它上传到代码仓库。但是由于对git的使用不太熟悉,把写得完善的项目代码初始化掉了!!非常崩溃…后来废了很大力气才把代码找回来。
为了以后把代码搞丢后还能找回来,还是要写个博客来记录一下代码。
项目链接:https://pan.baidu.com/s/1fTh4m_OkqV2PIuWdCgE-QQ
提取码:35sv
项目文件目录
api
mockServe
home.js
// mock数据模拟
import Mock from 'mockjs'
// 导入数据
import videoData from '../../data/mockData/videoData'
import userData from '../../data/mockData/userData'
import tableData from '../../data/mockData/tableData'
// 图表数据
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,
// 柱状图
userData,
// 折线图
orderData: {
date: ['20191001', '20191002', '20191003', '20191004', '20191005', '20191006', '20191007'],
data: List
},
tableData
}
}
}
}
permission.js
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: '密码错误'
}
}
}
}
}
index.js
import http from '../utils/request'
// 请求首页数据,直接把这个对象导出
export const getData = () => {
// 返回一个promise
return http.get('/home/getData')
}
// 下面四个:用户管理-后端-网络请求接口
export const getUser = (params) => {
return http.get('/user/get/', params)
}
export const createUser = (data) => {
return http.post('/user/create', data)
}
export const deleteUser = (data) => {
return http.post('/user/del', data)
}
export const updateUser = (data) => {
return http.post('/user/update', data)
}
// 登录权限
export const getMenu = (data) => {
return http.post('/permission/getMenu',data)
}
mock.js
import Mock from 'mockjs'
import homeMock from '../api/mockServe/home'
import user from './user'
import permission from './mockServe/permission'
// 定义mock拦截
Mock.mock('/api/home/getData',homeMock)
// 用户管理:增删查改
Mock.mock(/\/api\/user\/get/,user.getUserList)
Mock.mock('/api/user/create','post',user.createUser)
Mock.mock('/api/user/update','post',user.updateUser)
Mock.mock('/api/user/del','post',user.deleteUser)
// 登录权限
Mock.mock(/api\/permission\/getMenu/,'post',permission.getMenu)
user.js
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: '编辑成功'
}
}
}
}
assert
这些图片在链接里:
components
CommonAside.vue
<template>
<el-menu default-active="1-4-1" class="el-menu-vertical-demo" @open="handleOpen" @close="handleClose"
:collapse="isCollapse" background-color="#545c64" text-color="#fff" active-text-color="#ffd04b">
<!-- 要放到导航栏里面 -->
<h3>{{ isCollapse ? "后台" : "通用后台管理系统" }}</h3>
<!-- 观察数据,我们发现name是唯一标识 -->
<!-- 查看文档,index是唯一标识 -->
<el-menu-item @click="clickItem(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>
<el-submenu v-for="item in hasChildren" :key="item.label" :index="item.label">
<template slot="title">
<i :class="`el-icon-${item.icon}`"></i>
<span slot="title">{{ item.label }}</span>
</template>
<el-menu-item-group v-for="subItem in item.children" :key="subItem.name">
<el-menu-item @click="clickItem(subItem)" :index="subItem.name">{{ 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 {
height: 100vh;
// Aside和Header之间没有边框缝隙
border-right: none;
h3 {
text-align: center;
line-height: 48px;
color: #fff;
font-size: 16px;
font-weight: 400;
}
}
</style>
<script>
import cookie from 'js-cookie'
export default {
data() {
return {
};
},
methods: {
handleOpen(key, keyPath) {
console.log(key, keyPath);
},
handleClose(key, keyPath) {
console.log(key, keyPath);
},
clickItem(item) {
// 防止自己跳自己的报错
if (this.$route.path !== item.path && !(this.$route.path === '/home' && (item.path === '/'))) {
this.$router.push(item.path)
}
// 面包屑
this.$store.commit('SelectMenu', item)
}
},
computed: {
noChildren() {
// 如果没有children则返回true,会被过滤器留下
return this.MenuData.filter(item => !item.children)
},
hasChildren() {
return this.MenuData.filter(item => item.children)
},
// 要放到计算属性,自动计算
isCollapse() {
return this.$store.state.tab.isCollapse
},
// 获取菜单
MenuData() {
return JSON.parse(cookie.get('menu')) || this.$store.state.tab.menu
}
}
}
</script>
CommonHeader.vue
<template>
<div class="header-container">
<div class="l-content">
<el-button @click="handleMenu" icon="el-icon-menu" size="mini"></el-button>
<!-- 面包屑 -->
<el-breadcrumb separator="/">
<el-breadcrumb-item v-for="item in tags" :key="item.path" :to="{ path: item.path }">{{ item.label }}
</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="r-content">
<el-dropdown @command="handleClick">
<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 command="logout">退出</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import Cookie from 'js-cookie'
export default {
methods: {
handleMenu() {
// 相当于调用这个方法
this.$store.commit('CollapseMenu')
},
handleClick(command) {
if (command === 'logout') {
Cookie.remove('token')
this.$router.push('/login')
}
}
},
computed: {
...mapState({
tags: state => state.tab.tabList
})
}
}
</script>
<style lang="less" scoped>
.header-container {
background-color: #333;
height: 60px;
// 让按钮和头像居中
display: flex;
justify-content: space-between;
align-items: center;
// 不要紧贴边框
padding: 0 20px;
.el-dropdown-link {
cursor: pointer;
color: #409EFF;
.user {
width: 40px;
height: 40px;
// 50%变圆形
border-radius: 50%;
}
}
}
.l-content {
display: flex;
// 上下居中
align-items: center;
.el-breadcrumb {
margin-left: 15px;
// deep 强制生效
/deep/.el-breadcrumb__item {
.el-breadcrumb__inner {
&.is-link {
color: #666;
}
}
&:last-child {
.el-breadcrumb__inner {
color: #fff;
}
}
}
}
}
</style>
CommonTags.vue
<template>
<div class="tabs">
<!-- closable是否可删除:除了"首页"都可删 -->
<!-- effect:主题,当前主题是dark,其他事plain -->
<el-tag v-for="(item, index) in tags" :key="item.path" :closable="item.name !== 'home'"
:effect="item.name === $route.name ? 'dark' : 'plain'" @click="changeMenu(item)"
@close="handleClose(item, index)">
{{ item.label }}
</el-tag>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
methods: {
changeMenu(item) {
this.$router.push({ name: item.name })
},
handleClose(item, index) {
// 删除面包屑数据
this.$store.commit('closeTag', item)
// 如果删除的刚好是自己
if (item.name === this.$route.name) {
const length = this.tags.length
// 如果删除的是最后一个:跳到前一个
if (length === index) {
this.$router.push({ name: this.tags[index - 1].name })
}
// 不是最后一个:往后一个
else {
this.$router.push({ name: this.tags[index].name })
}
}
}
},
computed: {
...mapState({
tags: state => state.tab.tabList
})
}
}
</script>
<style lang="less" scoped>
.tabs{
padding: 20px 20px 0 20px;
.el-tag{
margin-right: 15px;
// 鼠标悬停:小手
cursor: pointer;
}
}
</style>
data
echartsData
order.js
const order = {
legend: {
// 图例文字颜色
textStyle: {
color: "#333",
},
},
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: [],
}
export default order
user.js
const user = {
legend: {
// 图例文字颜色
textStyle: {
color: "#333",
},
},
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"],
series: [],
}
export default user
video.js
const video = {
tooltip: {
trigger: "item",
},
color: [
"#0f78f4",
"#dd536b",
"#9462e5",
"#a6a6a6",
"#e1bb22",
"#39c362",
"#3ed1cf",
],
series: [],
}
export default video
mockData
tableData.js
const 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
}
]
export default tableData
userData.js
// 柱状图
const 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
}
]
export default userData
videoData.js
// 饼图
const videoData = [
{
name: '小米',
value: 2999
},
{
name: '苹果',
value: 5999
},
{
name: 'vivo',
value: 1500
},
{
name: 'oppo',
value: 1999
},
{
name: '魅族',
value: 2200
},
{
name: '三星',
value: 4500
}
]
export default videoData
CountData.js
const 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",
},
]
export default CountData
MenuData.js
const 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'
}
]
}
]
export default MenuData
TableData.js
const TableData = [
{
name: 'oppo',
todayBuy: 100,
monthBuy: 300,
totalBuy: 800
},
{
name: 'vivo',
todayBuy: 100,
monthBuy: 300,
totalBuy: 800
},
{
name: '苹果',
todayBuy: 100,
monthBuy: 300,
totalBuy: 800
},
{
name: '小米',
todayBuy: 100,
monthBuy: 300,
totalBuy: 800
},
{
name: '三星',
todayBuy: 100,
monthBuy: 300,
totalBuy: 800
},
{
name: '魅族',
todayBuy: 100,
monthBuy: 300,
totalBuy: 800
}
]
export default TableData
TableLabel.js
const TableLabel={
name:'课程',
todayBuy:'今日购买',
monthBuy:'本月购买',
totalBuy:'总购买'
}
export default TableLabel
router
index.js
import Vue from "vue";
import VueRouter from "vue-router";
import Main from '../Views/Main'
// import Home from '../Views/Home.vue'
// import Mall from '../Views/Mall.vue'
// import User from '../Views/User.vue'
// import PageOne from '../Views/PageOne.vue'
// import PageTwo from '../Views/PageTwo.vue'
import Login from '../Views/Login.vue'
import Cookie from 'js-cookie'
Vue.use(VueRouter)
const routes = [
// 主路由
{
path: '/',
name:'Main',
component: Main,
redirect: '/home', // 重定向
children: [
// 子路由
// { path: '/home', name: 'home', component: Home }, // 首页
// { path: '/user', name: 'user', component: User }, // 用户管理
// { path: '/mall', name: 'mall', component: Mall }, // 商品管理
// { path: '/page1', name: 'page1', component: PageOne }, // 页面1
// { path: '/page2', name: 'page2', component: PageTwo }, // 页面2
]
},
{
path: '/login',
name: 'login',
component: Login
}
]
const router = new VueRouter({
routes
})
// 路由守卫:全局前置导航守卫
router.beforeEach((to, from, next) => {
// 获取token
const token = Cookie.get('token')
if (!token && to.name !== 'login') {
next({ name: 'login' })
} else if (token && to.name === 'login') {
next({ name: 'home' })
} else {
next()
}
})
export default router
store
index.js
import Vue from "vue";
import Vuex from 'vuex';
import tab from './tab';
Vue.use(Vuex)
// 创建Vuex实例并导出
export default new Vuex.Store({
modules:{
tab
}
})
tab.js
import Cookie from "js-cookie"
export default {
state: {
isCollapse: false,//导航栏是否折叠
tabList: [
{
path: '/',
name: 'home',
label: '首页',
icon: 's-home',
url: 'Home/Home'
}
],//面包屑的数据:点了哪个路由,首页是一定有的
menu: [],//不同用户的菜单
},
mutations: {
// 修改导航栏展开和收起的方法
CollapseMenu(state) {
state.isCollapse = !state.isCollapse
},
// 更新面包屑的数据
SelectMenu(state, item) {
// 如果点击的不在面包屑数据中,则添加
const index = state.tabList.findIndex(val => val.name === item.name)
if (index === -1) {
state.tabList.push(item)
}
},
// 删除tag:删除tabList中对应的item
closeTag(state, item) {
// 要删除的是state.tabList中的item
const index = state.tabList.findIndex(val => val.name === item.name)
state.tabList.splice(index, 1)
},
// 设置不同用户的菜单
setMenu(state, val) {
state.menu = val
Cookie.set('menu', JSON.stringify(val))
},
// 动态添加路由
addMenu(state, router) {
// 判断Cookie
if (!Cookie.get('menu')) return
const menu = JSON.parse(Cookie.get('menu'))
state.menu = menu
const menuArray = []
// 组装路由
menu.forEach(item => {
// 判断是否有子路由
if (item.children) {
item.children = item.children.map(itemm => {
itemm.component = () => import(`../Views/${itemm.url}`)
return itemm
})
menuArray.push(...item.children)
} else {
item.component = () => import(`../Views/${item.url}`)
menuArray.push(item)
}
});
console.log(menuArray, 'menuArray');
menuArray.forEach(item => {
router.addRoute('Main', item)
})
}
}
}
utils
request.js
import axios from "axios";
// 封装一个axios实例
const http = axios.create({
// 通用请求的地址前缀
baseURL: '/api',
// 超时时间
timeout: 100000
})
// 请求拦截器
http.interceptors.request.use(function (config) {
// 在发送请求之前做什么
return config;
}, function (error) {
// 对请求错误做什么
return Promise.reject(error);
})
// 添加响应拦截器
http.interceptors.response.use(function (response) {
// 对响应数据做什么
return response;
}, function (error) {
// 对响应错误做什么
return Promise.reject(error);
})
export default http
Views
Home.vue
<template>
<el-row>
<el-col :span="8">
<!-- user卡片 -->
<el-card>
<div class="user">
<img src="../assets/images/user.png" alt="">
<div class="userInfo">
<p div class="name">Admin</p>
<p div class="access">超级管理员</p>
</div>
</div>
<div class="loginInfo">
<p>上次登录时间:<span>2021-7-19</span></p>
<p>上次登陆地点:<span>武汉</span></p>
</div>
</el-card>
<!-- table卡片 -->
<el-card style="margin-top: 20px;">
<el-table :data="TableData" style="width: 100%">
<!-- 这里的val,key对应的是对象里的 -->
<el-table-column v-for="(value, key) in TableLabel" :prop="key" :label="value">
</el-table-column>
</el-table>
</el-card>
</el-col>
<el-col :span="16">
<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="{ backgroundColor: item.color }"></i>
<div class="details">
<p class="price">{{ priceFormate(item.value) }}</p>
<p class="desc">{{ item.name }}</p>
</div>
</el-card>
</div>
<!-- echarts图表 -->
<div style="margin-left:20px">
<!-- 折线图 -->
<el-card style="height:280px">
<div ref="echarts1" style="height:280px"></div>
</el-card>
<div class="graph">
<!-- 柱状图 -->
<el-card style="height:280px">
<div ref="echarts2" style="height:280px"></div>
</el-card>
<!-- 饼状图 -->
<el-card style="height:320px">
<div ref="echarts3" style="height:320px"></div>
</el-card>
</div>
</div>
</el-col>
</el-row>
</template>
<script>
import TableLabel from '../data/TableLabel'
import CountData from '../data/CountData'
import { getData } from '../api/index'
import * as echarts from 'echarts'
// echarts的配置数据
import order from '../data/echartsData/order'
import user from '../data/echartsData/user'
import video from '../data/echartsData/video'
export default {
data() {
return {
TableData: [],
TableLabel,
CountData
}
},
methods: {
priceFormate(price) {
return "¥" + price
}
},
mounted() {
getData().then((data) => {
// console.log(data);
this.TableData = data.data.getStatisticalData.data.tableData
// echarts图表
// 折线图
// 基于准备好的dom,初始化echarts实例
const echarts1 = echarts.init(this.$refs.echarts1)
var echarts1Option = order
// ES6解构语法
var { orderData, userData, videoData } = data.data.getStatisticalData.data
// 获取x轴:要求是一个对象
const xAxis = Object.keys(orderData.data[0])
const xAxisData = {
data: xAxis
}
// 配置
echarts1Option.legend = xAxisData
echarts1Option.xAxis = xAxisData
echarts1Option.yAxis = {}
echarts1Option.series = []
// 配置series
xAxis.forEach(key => {
echarts1Option.series.push({
name: key,
type: 'line',
// key对应的orderData的所有值
data: orderData.data.map(item => item[key])
})
})
// 使用刚指定的配置项和数据显示图表。
echarts1.setOption(echarts1Option);
// 柱状图
const echarts2 = echarts.init(this.$refs.echarts2)
var echarts2Option = user
// 配置
echarts2Option.xAxis.data = userData.map(item => item.date)
echarts2Option.series = [
{
name: '新增用户',
data: userData.map(item => item.new),
// 类型:bar是柱状图
type: 'bar'
}
,
{
name: '活跃用户',
data: userData.map(item => item.active),
type: 'bar'
}
]
echarts2.setOption(echarts2Option);
// 饼状图
const echarts3 = echarts.init(this.$refs.echarts3)
var echarts3Option = video
echarts3Option.series = {
data: videoData,
type: 'pie'
}
echarts3.setOption(echarts3Option);
})
}
}
</script>
<style lang="less" scoped>
.user {
// 垂直居中
display: flex;
align-items: center;
// 外边距:分割线距离loginInfo的距离
margin-bottom: 20px;
// 内边距:分割线距离User的距离
padding-bottom: 20px;
border-bottom: 1px solid #ccc;
img {
width: 150px;
height: 150px;
border-radius: 50%;
margin-right: 40px;
}
.userInfo {
.name {
font-size: 32px;
margin-bottom: 10px;
}
.access {
color: #999999;
}
}
}
.loginInfo {
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;
margin-left: 20px;
.el-card {
width: 32%;
margin-bottom: 20px;
.icon {
width: 80px;
height: 80px;
line-height: 80px;
text-align: center;
font-size: 30px;
color: #fff;
}
.details {
// 竖着排且居中
display: flex;
flex-direction: column;
justify-content: center;
margin-left: 15px;
.price {
font-size: 30px;
margin-bottom: 10px;
line-height: 30px;
height: 30px;
}
.desc {
font-size: 14px;
color: #999;
text-align: center;
}
}
}
}
.graph {
display: flex;
// 两个靠边
justify-content: space-between;
margin-top: 20px;
.el-card {
width: 49%;
}
}
</style>
Login.vue
<template>
<el-form ref="form" class="login_container" :model="login" status-icon :rules="rules" label-width="70px">
<!-- h3要放在里面:只能有一个根,且title也是表单的一部分 -->
<h3 class="login_title">用户登录</h3>
<!-- prop对应rules里的键 -->
<el-form-item label="用户名" prop="username">
<el-input v-model="login.username" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input type="password" v-model="login.password" autocomplete="off"></el-input>
</el-form-item>
<el-form-item>
<el-button @click="submit" type="primary" style="margin-left:30px;margin-top:10px">提交</el-button>
</el-form-item>
</el-form>
</template>
<script>
import Cookie from 'js-cookie'
import { getMenu } from '../api/index'
export default {
data() {
return {
// 登陆数据
login: {
username: '',
password: ''
},
// 校验规则
rules: {
username: [{ required: 'true', message: '请输入用户名', trigger: 'blur' }],
password: [{ required: 'true', message: '请输入用户名', trigger: 'blur' }]
}
}
},
methods: {
submit() {
// 表单的校验
this.$refs.form.validate((valid) => {
if (valid) {
// 传入表单数据
getMenu(this.login).then((data) => {
// console.log(data);
if(data.data.code===20000){
// 记录cookie
Cookie.set('token',data.data.data.token)
// 设置菜单
this.$store.commit('setMenu',data.data.data.menu)
// 动态添加路由
this.$store.commit('addMenu',this.$router)
// 跳转到首页
this.$router.push('/home')
}else{
// 验证失败的弹窗
this.$message.error(data.data.data.message);
}
})
}
})
}
}
}
</script>
<style lang="less" scoped>
.login_container {
width: 350px;
border: 1px solid #eaeaea;
// 居中
margin: 180px auto;
padding: 35px 35px 15px 35px;
// 让padding在width里面
box-sizing: border-box;
border-radius: 15px;
background-color: #fff;
box-shadow: 0 0 25px #cac6c6;
.login_title {
color: #505458;
// 左右居中
text-align: center;
margin-bottom: 40px;
}
.el-input {
width: 198px;
}
}
</style>
Main.vue
<template>
<el-container>
<!-- width自适应,不然header与aside有间隔 -->
<el-aside width="auto">
<common-aside/>
</el-aside>
<el-container>
<el-header>
<common-header/>
</el-header>
<common-tags/>
<el-main>
<router-view></router-view>
</el-main>
</el-container>
</el-container>
</template>
<script>
import CommonAside from '../components/CommonAside.vue'
import CommonHeader from '../components/CommonHeader.vue'
import CommonTags from '../components/CommonTags.vue'
export default {
data(){
return{}
},
components:{
CommonAside,
CommonHeader,
CommonTags
}
}
</script>
<style lang="less" scoped>
.el-header{
padding:0;
}
</style>
Mall.vue
<template>
<h1>Mall</h1>
</template>
<script>
export default {
}
</script>
<style>
</style>
PageOne.vue
<template>
<h1>Page1</h1>
</template>
<script>
export default {
}
</script>
<style>
</style>
PageTwo.vue
<template>
<h1>Page2</h1>
</template>
<script>
export default {
}
</script>
<style>
</style>
User.vue
<template>
<div class="manage">
<div class="manage-header">
<!-- 新增按钮 -->
<el-button type="primary" @click="handlecreate">+ 新增</el-button>
<!-- 对话框:点击新增或编辑才会弹出表单 -->
<!-- :before-close="closeDialog" 点击关闭的x之前要做的事情 -->
<el-dialog :title="modalType == 0 ? '新建' : '编辑'" :visible.sync="dialogVisible" width="50%"
:before-close="closeDialog">
<!-- 表单Form -->
<!-- ref=form:为了通过this.$refs调用组件的方法 -->
<el-form :inline="true" :model="form" :rules="rules" ref="form" label-width="80px">
<!-- 每一项表单域:el-form-item -->
<el-form-item label="姓名" prop="name">
<el-input placeholder="请输入姓名" v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="年龄" prop="age">
<el-input placeholder="请输入年龄" v-model="form.age"></el-input>
</el-form-item>
<el-form-item label="性别" prop="sex">
<el-select v-model="form.sex" placeholder="请输入性别">
<el-option label="男" :value="1"></el-option>
<el-option label="女" :value="0"></el-option>
</el-select>
</el-form-item>
<el-form-item label="出生日期">
<el-form-item prop="birth">
<el-date-picker type="date" placeholder="请选择日期" v-model="form.birth" value-format="yyyy-MM-DD">
</el-date-picker>
</el-form-item>
</el-form-item>
<el-form-item label="地址" prop="addr">
<el-input placeholder="请输入地址" v-model="form.addr"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="closeDialog">取 消</el-button>
<el-button type="primary" @click="submit">确 定</el-button>
</div>
</el-dialog>
<!-- 搜索框 -->
<el-form :inline="true">
<el-form-item>
<el-input v-model="searchForm.name" placeholder="请输入名称"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="search">查询</el-button>
</el-form-item>
</el-form>
</div>
<div class="common-table">
<!-- 用户数据Table -->
<el-table :data="tableData" stripe style="width: 100%" height="90%">
<el-table-column prop="name" label="姓名">
</el-table-column>
<el-table-column prop="age" label="年龄">
</el-table-column>
<el-table-column prop="sex" label="性别">
<template slot-scope="scope">
<span>{{ scope.row.sex == 1 ? '男' : '女' }}</span>
</template>
</el-table-column>
<el-table-column prop="birth" label="出生日期">
</el-table-column>
<el-table-column prop="addr" label="地址">
</el-table-column>
<!-- 自定义列 -->
<el-table-column label="操作">
<template slot-scope="scope">
<el-button @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="danger" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pager">
<el-pagination layout="prev, pager, next" :total="total" @current-change="currentChange">
</el-pagination>
</div>
</div>
</div>
</template>
<script>
import { getUser, createUser, deleteUser, updateUser } from '../api/index'
export default {
data() {
return {
// 表单绑定的数据
form: {
name: '',
age: '',
sex: '',
birth: '',
addr: ''
},
// 表单验证规则
rules: {
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
age: [{ required: true, message: '请输入年龄', trigger: 'blur' }],
sex: [{ required: true, message: '请输入性别', trigger: 'blur' }],
birth: [{ required: true, message: '请输入日期', trigger: 'blur' }],
addr: [{ required: true, message: '请输入地址', trigger: 'blur' }],
},
// 表单是否打开
dialogVisible: false,
// 列表数据
tableData: [],
// 打开表单:新建0,编辑1
modalType: 0,
// 分页的对象
pageData: {
page: 1,
limit: 20
},
// 分页页数
total: 0,
// 搜索框表单
searchForm: {
name: ''
}
}
},
methods: {
// 获取列表数据
getList() {
// 由接口文档知传入一个对象:要返回的是当前页面数据和搜索到的数据的交集
getUser({ params: { ...this.pageData, ...this.searchForm } }).then((data) => {
this.tableData = data.data.list
this.total = data.data.count || 0
})
},
// 表单提交
submit() {
// 要用箭头函数,若用function会报错,不知道为什么
this.$refs.form.validate((valid) => {
// 符合校验
if (valid) {
// 提交数据
if (this.modalType === 0) {
// 新增
createUser(this.form).then(() => {
this.getList()
})
} else {
// 编辑
updateUser(this.form).then(() => {
this.getList()
})
}
// 清空,关闭
this.closeDialog()
}
})
},
// 关闭对话框
closeDialog() {
// 先重置
this.$refs.form.resetFields()
// 后关闭
this.dialogVisible = false
},
// 编辑按钮
handleEdit(index) {
this.modalType = 1
this.openForm()
// 深拷贝
this.form = JSON.parse(JSON.stringify(index))
},
// 删除按钮
handleDelete(index) {
this.$confirm('此操作将永久删除该用户, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 删除操作:根据后端接口,参数是对象,id是唯一标识符
deleteUser({ id: index.id }).then(() => {
this.$message({
type: 'success',
message: '删除成功!'
})
this.getList()
});
}).catch(() => {
// 点击取消:不删除了
this.$message({
type: 'info',
message: '已取消删除'
});
});
},
// 新建按钮
handlecreate() {
this.modalType = 0
this.openForm()
},
// 打开表单
openForm() {
this.dialogVisible = true
},
// 改变页码
currentChange(val) {
this.pageData.page = val
this.getList()
},
// 搜索
search() {
this.getList()
}
},
mounted() {
this.getList()
}
}
</script>
<style lang="less" scoped>
.manage {
height: 100%;
.manage-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.common-table {
height: 90%;
position: relative;
.pager {
position: absolute;
right:20px;
bottom: 0;
}
}
}
</style>
App.vue
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<script>
export default {
}
</script>
<style>
html,
body,
h3,
p {
margin: 0;
padding: 0
}
</style>
main.js
import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css';
import router from './router/index'
import store from './store/index'
import './api/mock'
Vue.config.productionTip = false
Vue.use(ElementUI)
new Vue({
router,
store,
render: h => h(App),
created() {
store.commit('addMenu', router)
}
}).$mount('#app')
要安装的依赖
"dependencies": {
"axios": "^1.1.3",
"core-js": "^3.8.3",
"echarts": "^5.1.2",
"element-ui": "^2.15.10",
"js-cookie": "^3.0.1",
"less": "^4.1.3",
"less-loader": "^11.1.0",
"mockjs": "^1.1.0",
"vue": "^2.6.14",
"vue-router": "^3.6.5",
"vuex": "^3.6.2"
}