【前端】Vue+Element UI案例:通用后台管理系统-代码总结(已开源)


参考视频: VUE项目,VUE项目实战,vue后台管理系统,前端面试,前端面试项目

案例链接
【前端】Vue+Element UI案例:通用后台管理系统-导航栏(视频p1-16)https://blog.csdn.net/karshey/article/details/127640658
【前端】Vue+Element UI案例:通用后台管理系统-Header+导航栏折叠(p17-19)https://blog.csdn.net/karshey/article/details/127652862
【前端】Vue+Element UI案例:通用后台管理系统-Home组件:卡片、表格(p20-22)https://blog.csdn.net/karshey/article/details/127674643
【前端】Vue+Element UI案例:通用后台管理系统-Echarts图表准备:axios封装、mock数据模拟实战(p23-25)https://blog.csdn.net/karshey/article/details/127735159
【前端】Vue+Element UI案例:通用后台管理系统-Echarts图表:折线图、柱状图、饼状图(p27-30)https://blog.csdn.net/karshey/article/details/127737979
【前端】Vue+Element UI案例:通用后台管理系统-面包屑、tag栏(p31-35)https://blog.csdn.net/karshey/article/details/127756733
【前端】Vue+Element UI案例:通用后台管理系统-用户管理:Form表单填写、Dialog对话框弹出(p36-38)https://blog.csdn.net/karshey/article/details/127787418
【前端】Vue+Element UI案例:通用后台管理系统-用户管理:Table表格增删查改、Pagination分页、搜索框(p39-42)https://blog.csdn.net/karshey/article/details/127777962
【前端】Vue+Element UI案例:通用后台管理系统-登陆页面Login(p44)https://blog.csdn.net/karshey/article/details/127795302
【前端】Vue+Element UI案例:通用后台管理系统-登陆页面功能:登录权限跳转、路由守卫、退出(p45-46)https://blog.csdn.net/karshey/article/details/127849502
【前端】Vue+Element UI案例:通用后台管理系统-登陆不同用户显示不同菜单、动态添加路由(p47-48)https://blog.csdn.net/karshey/article/details/127865621

前言

本来不打算用博客的方式记录代码的,想用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"
  }

已上传到Gitee

代码已经上传到Gitee:https://gitee.com/karshey/general-background

  • 17
    点赞
  • 85
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
### 回答1: Vue Element UI是一个基于Vue.js前端组件库,而海康视频则是一种视频监控系统。结合两者,可以使用Vue Element UI的弹框组件来实现海康视频的功能。 在Vue Element UI中,有一个弹框组件`Dialog`可以用于展示内容、表单、图片等。我们可以利用这个弹框组件来实现海康视频的弹框功能。 首先,我们需要引入Vue Element UI和海康视频的相关依赖包。然后,在Vue的模板中,使用`<el-dialog>`标签来定义一个弹框组件。通过设置`visible`属性来控制弹框的显示与隐藏。 在弹框中,可以使用`<el-form>`标签来展示视频相关的表单,比如输入视频名称、选择视频文件等等。同时,可以使用`<el-upload>`标签来实现视频文件的上传功能。 除此之外,还可以使用`<el-image>`标签来展示视频的缩略图或者封面图。 最后,通过调用Vue的方法或者通过事件绑定来控制弹框的显示与隐藏。比如,在点击确认按钮时,可以调用一个方法来处理相关的逻辑。或者,在获取到视频文件后,可以通过事件绑定来触发视频上传的操作。 综上所述,通过Vue Element UI的弹框组件,我们可以方便地实现海康视频的弹框功能。通过结合VueElement UI的优势,可以快速开发出具有弹框功能的海康视频系统。 ### 回答2: VueElement UI是一种用于构建用户界面的开源框架和库。Vue是一种流行的JavaScript框架,用于构建交互式的Web界面。它具有简单易用的API和强大的能力,使开发人员可以快速构建复杂的前端应用程序。 Element UIVue的一个UI框架,它提供了各种用户界面组件和工具,包括弹框组件。弹框是一种常用的交互式组件,可用于显示消息、警告、确认对话框等。 海康视频是一个知名的视频监控解决方案提供商。它提供了一系列视频监控设备、软件和服务,可用于保护和监控各种场所。 结合VueElement UI的弹框功能,我们可以使用Element UI提供的弹框组件来显示海康视频相关的信息和操作。例如,当需要展示一个海康视频的预览时,我们可以使用Element UI的弹框组件来创建一个模态框,其中包含视频的预览界面和相关控制按钮,用户可以通过弹框中的按钮来操作视频的播放、暂停、停止等功能。 除了预览功能,我们还可以使用弹框来展示视频相关的警告信息。例如,当监控系统检测到异常情况时,可以通过弹框来显示警告信息,提醒用户及时处理。 总之,VueElement UI的弹框功能可以很好地与海康视频集成,为用户提供一个友好的界面来展示和操作海康视频的功能。 ### 回答3: Vue是一种流行的JavaScript框架,而Element UI是一个基于Vue的组件库。弹框是Element UI提供的一个组件,可以在网页中显示弹出窗口,用于展示一些提示、警告或者用户交互的内容。 海康视频是一家专业的视频监控设备供应商,其产品包括监控摄像头、视频录像机等。在使用VueElement UI开发网页时,我们可以使用弹框组件来展示海康视频相关的内容。 例如,当用户需要查看一段海康视频时,可以点击一个按钮,触发弹框组件显示在界面中央,同时在弹框的内容区域中,嵌入海康视频的播放器。这样用户就可以方便地观看视频,而无需离开当前页面。 利用Vue的动态数据绑定特性,我们还可以根据用户的操作改变海康视频的播放状态。例如,在弹框中提供一个播放/暂停按钮,用户点击按钮时,我们可以修改Vue的数据状态,从而控制视频的播放或暂停。 此外,Element UI的弹框组件还提供了其他丰富的功能选项,如设置弹框的大小、位置、背景等样式,以及自定义按钮、输入框等交互元素。可以利用这些特性来完善海康视频弹框的用户体验。 总结而言,借助VueElement UI的弹框组件,可以在网页中方便地展示海康视频,提供给用户良好的用户体验,同时还可以通过Vue的数据绑定特性来控制视频的播放状态。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

karshey

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值