PC端面经后台管理项目-element-UI,sass/scss,axios、localstorage二次封装,响应拦截器

PC端面经后台管理项目(1)-element-UI,sass/scss,axios、localstorage二次封装,响应拦截器处理token过期

这一次的项目是基于上一次移动端的面经项目的后台管理部分,与移动端的不同,移动端主要在于对vant组件库的熟悉和学习,pc端则是在于对element-ui的熟悉和学习,整体来说,和移动端面经项目有很多的相似之处,能够从上次的项目中借鉴经验,也能借此来加深对这套架子的熟悉。

vant 项目的定位:

  • 熟悉vant, 熟悉架子(各个目录),熟悉模块 api request storage

element 项目的定位:

  • 熟悉element => 表单组件,表单校验,表格组件
  • 巩固架子,巩固模块封装

接口文档: https://www.apifox.cn/apidoc/project-934563/api-19465917

接口根路径: http://interview-api-t.itheima.net/

本项目的技术栈 本项目技术栈基于 ES2015+vue2vuex3vue-router3vue-cli5axioselement-ui

页面展示

登录页

image-20220617072834031

首页

image-20220617060021359

面经管理 - 列表展示

image-20220617060123478

面经管理 - 预览效果

image-20220617060222666

面经管理 - 删除功能

image-20220617060211868

面经管理 - 添加功能

image-20220617060301375

面经管理 - 修改功能

image-20220617060316985

创建项目

image-20220617060702862

image-20220617060757090

image-20220617060818457

image-20220617060854885

image-20220617061002551

image-20220617061022684

image-20220617061038189

image-20220617061103526

image-20220617061127960

sass/scss 语法说明

less sass stylus 都是 css 预处理器,语法上稍有差异,作用一样
都是让 css,增强能力,具备变量,函数.. 的能力

sass的语法两种语法 .sass(旧) .scss(新)
1 .sass 和 .stylus 语法很像 (了解)
  要求省略 {} 和 分号, 缩进表示嵌套
  
2 .scss 和 .less   语法很像, 都支持嵌套, 变量...
  scss 声明变量:$变量名
  less 声明变量: @变量名

##调整项目目录

默认生成的目录结构不满足我们的开发需求,所以这里需要做一些自定义改动。主要是两个工作:

  • 删除初始化的默认文件
  • 修改剩余代码内容
  • 新增调整我们需要的目录结构
  1. 删除文件
  • components/HelloWorld.vue
  • views/HomeView.vue
  • views/AboutView.vue
  • assets/logo.png
  1. 修改内容

src/router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const router = new VueRouter({
  routes: []
})

export default router

src/App.vue

<template>
  <div id="app">
    <router-view></router-view>
  </div>
</template>

<style lang="scss">

</style>

store/index.js 和 main.js 不用动

  1. 新增需要目录

在 src 目录下中补充创建以下目录:

  • /api : 存储请求函数模块
  • /styles: 样式文件模块
  • /utils: 工具函数模块

image-20211005210420736

  1. 将项目需要的图片资源放置 assets 文件夹

引入 element-ui 组件库

image-20211005211307229

官方文档: https://element.eleme.io/#/zh-CN

全部引入

全部引入, 会导入所有的组件, 但是体积会变大

  • 安装
yarn add element-ui
  • main.js
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
  • 演示
<el-button type="primary">主要按钮</el-button>

按需导入 (推荐)

减轻将来打包后的包的体积

  • 安装
yarn add element-ui
  • 安装babel-plugin-component
yarn add babel-plugin-component -D
  • babel.config.js中配置

    image-20230322214200524

module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset'
  ],
  // 新增plugins插件节点,修改完配置文件一定重启项目
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}
  • 使用插件main.js
import { Button } from 'element-ui'
Vue.use(Button)

抽离element.js模块

  • 由于组件的导入都书写到了main.js中,导致main.js 代码冗余

    将element-ui组件的导入和注册单独抽离到utils文件夹中

  • 新建element.js

  • 项目中 完整按需导入如下:

import Vue from 'vue';
import {
  Popconfirm,
  Avatar,
  Breadcrumb,
  BreadcrumbItem,
  Pagination,
  Dialog,
  Menu,
  Input,
  Option,
  Button,
  Table,
  TableColumn,
  Form,
  FormItem,
  Icon,
  Row,
  Col,
  Card,
  Container,
  Header,
  Aside,
  Main,
  Footer,
  Link,
  Image,
  Loading,
  MessageBox,
  Message,
  Drawer,
  MenuItem
} from 'element-ui';

Vue.use(Breadcrumb);
Vue.use(BreadcrumbItem);
Vue.use(Drawer);
Vue.use(Popconfirm);
Vue.use(Avatar);
Vue.use(Pagination);
Vue.use(Dialog);
Vue.use(Menu);
Vue.use(MenuItem);
Vue.use(Input);
Vue.use(Option);
Vue.use(Button);
Vue.use(Table);
Vue.use(TableColumn);
Vue.use(Form);
Vue.use(FormItem);
Vue.use(Icon);
Vue.use(Row);
Vue.use(Col);
Vue.use(Card);
Vue.use(Container);
Vue.use(Header);
Vue.use(Aside);
Vue.use(Main);
Vue.use(Footer);
Vue.use(Link);
Vue.use(Image);

Vue.use(Loading.directive);

Vue.prototype.$loading = Loading.service;
Vue.prototype.$msgbox = MessageBox;
Vue.prototype.$alert = MessageBox.alert;
Vue.prototype.$confirm = MessageBox.confirm;
Vue.prototype.$prompt = MessageBox.prompt;
Vue.prototype.$notify = Notification;
Vue.prototype.$message = Message;
  • 直接导入main.js中
// 直接导入vant-ui.js
import '@/utils/element.js'

导入公共样式

新建 styles/index.scss

// 修改主题色
$--color-primary: rgba(114,124,245,1);
$--font-path: '~element-ui/lib/theme-chalk/fonts';
@import "~element-ui/packages/theme-chalk/src/index";

body {
  margin: 0;
  padding: 0;
  background: #fafbfe;
}

main.js 引入

import '@/styles/index.scss'

request模块 - axios封装

接口文档地址:https://www.apifox.cn/apidoc/project-934563/api-19465917

我们会使用 axios 来请求后端接口, 一般都会对 axios 进行一些配置 (比如: 配置基础地址等)

一般项目开发中, 都会对 axios 进行基本的二次封装, 单独封装到一个模块中, 便于使用

  1. 安装 axios
npm i axios
  1. 新建 utils/request.js 封装 axios 模块

    利用 axios.create 创建一个自定义的 axios 来使用

    http://www.axios-js.com/zh-cn/docs/#axios-create-config

/* 封装axios用于发送请求 */
import axios from 'axios'

// 创建一个新的axios实例
const request = axios.create({
  baseURL: 'http://interview-api-t.itheima.net/',
  timeout: 5000
})

// 添加请求拦截器
request.interceptors.request.use(function (config) {
  // 在发送请求之前做些什么
  return config
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error)
})

// 添加响应拦截器
request.interceptors.response.use(function (response) {
  // 对响应数据做点什么
  return response.data
}, function (error) {
  // 对响应错误做点什么
  return Promise.reject(error)
})

export default request

storage模块 - 本地存储

新建 utils/storage.js

// 以前 token 令牌,如果存到了本地,每一次都写这么长,太麻烦
// localStorage.setItem(键, 值)
// localStorage.getItem(键)
// localStorage.removeItem(键)

const KEY = 'my-token-element-pc'

// 直接用按需导出,可以导出多个
// 但是按需导出,导入时必须 import { getToken } from '模块名导入'

// 获取
export const getToken = () => {
  return localStorage.getItem(KEY)
}

// 设置
export const setToken = (newToken) => {
  localStorage.setItem(KEY, newToken)
}

// 删除
export const delToken = () => {
  localStorage.removeItem(KEY)
}

路由设计配置

但凡是: 单个页面,独立展示的,都是一级路由 (登录 注册 首页架子 文章详情 …)

路由设计:

  • 登录页 (一级) login
  • 首页架子(一级) layout
    • 数据看板(二级)dashboard
    • 文章管理(二级)article

新建目录

image-20220617065517188

login/index.vue

<template>
  <div>我是一级登录</div>
</template>

<script>
export default {
  name: 'LoginIndex'
}
</script>

<style>

</style>

layout/index.vue

<template>
  <div>我是一级首页架子</div>
</template>

<script>
export default {
  name: 'LayoutIndex'
}
</script>

<style>

</style>

dashboard/index.vue

<template>
  <div>我是二级数据看板页面</div>
</template>

<script>
export default {
  name: 'DashBoard'
}
</script>

<style>

</style>

article/index.vue

<template>
  <div>我是二级文章管理页</div>
</template>

<script>
export default {
  name: 'ArticleIndex'
}
</script>

<style>

</style>

配置路由

router/index.js

import VueRouter from 'vue-router'
import Vue from 'vue'

import Layout from '@/views/layout'
import Login from '@/views/login'
import Dashboard from '@/views/dashboard'
import Article from '@/views/article'

Vue.use(VueRouter)

const router = new VueRouter({
  routes: [
    { path: '/login', component: Login },
    {
      path: '/',
      component: Layout,
      redirect: '/dashboard',
      children: [
        { path: 'dashboard', component: Dashboard },
        { path: 'article', component: Article }
      ]
    }
  ]
})

export default router

layout/index 配置二级路由出口

<template>
  <div>
    <div>头部</div>
    <div>侧边</div>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'LayoutIndex'
}
</script>

<style>

</style>

测试路径1: http://localhost:8080/#/login

测试路径2: http://localhost:8080/#/dashboard

测试路径3: http://localhost:8080/#/article

登录模块

element-ui 基本表单

说明:我们先学习 element-ui 表单组件的基本结构使用

需求:实现如图效果

image-20220617164619682

一般情况,这种第三方的组件,为了样式控制方便,会给组件的根元素,起一个和组件名同名的类名

控制组件的样式:

  1. 直接通过组件名 同名的 类, 进行控制样式
  2. 自己通过添加 class 类名,进行控制样式
如何给组件标签, 设置样式?
1. 给组件标签, 加<自定义类>
   添加的类, 会自动加上渲染出来的组件的根元素上
.my-card {
  width: 420px;
  margin: 0 auto;
}

2. 直接使用<组件标签名>, 作为<类名>控制样式
   组件库定义组件的规范: 声明的所有组件的根元素, 都有一个和组件名同名的类名(提供给你了)

默认,写在scoped中的样式,只会影响到当前组件模板中的元素内容

//加上scoped, 可以让样式, 只作用于当前组件模板(局部样式)
//默认scoped样式, 不会向下渗透, 影响到其他子组件的(除了根元素)
//如果希望样式, 可以向下渗透, 影响到下面的子孙后代, 就需要用到深度作用选择器(vue提供)

深度作用选择器:
::v-deep   scss
/deep/     less
.el-card {
  width: 420px;
  margin: 0 auto;
  // 原理: 一旦选择器前面有深度作用标识, 就会不会附加属性选择器的限制
  ::v-deep .el-card__header {
    background-color: #727cf5;
    text-align: center;
    color: #fff;
  }
}

深度作用选择器:向下影响到子元素的样式

::v-deep (scss)

/deep/ (less)

<template>
  <div class="login-page">
    <el-card class="el-card">
      <template #header>黑马面经运营后台</template>
      <el-form>
        <el-form-item label="用户名:">
          <el-input placeholder="请输入用户名" />
        </el-form-item>
        <el-form-item label="密码:">
          <el-input placeholder="请输入密码:" />
        </el-form-item>
        <el-form-item>
          <el-button type="primary">登录</el-button>
          <el-button>重置</el-button>
        </el-form-item>
      </el-form>
    </el-card>
  </div>
</template>

<script>
export default {
  name: 'login-page'
}
</script>

<style lang="scss" scoped>
.el-card {
  width: 420px;
  margin: 0 auto;
  // 深度作用选择器   ::v-deep   /deep/
  ::v-deep .el-card__header {
    background: rgba(114,124,245,1);
    text-align: center;
    color: white;
  }
}
</style>

样式美化:

<template>
  <div class="login-page">
    <el-card>
      <template #header>黑马面经运营后台</template>
      <el-form autocomplete="off">
        <el-form-item label="用户名">
          <el-input placeholder="输入用户名"></el-input>
        </el-form-item>

        <el-form-item label="密码">
          <el-input type="password" placeholder="输入用户密码"></el-input>
        </el-form-item>

        <el-form-item class="tc">
          <el-button type="primary">登 录</el-button>
          <el-button >重 置</el-button>
        </el-form-item>
      </el-form>
    </el-card>
  </div>
</template>

<script>
export default {
  name: 'login-page',
  data () {
    return {

    }
  },
  methods: {

  }
}
</script>

<style lang="scss" scoped>
.login-page {
  min-height: 100vh;
  background: url(@/assets/login-bg.svg) no-repeat center / cover;
  display: flex;
  align-items: center;
  justify-content: space-around;
  .el-card {
    width: 420px;
    ::v-deep .el-card__header{
      height: 80px;
      background: rgba(114,124,245,1);
      text-align: center;
      line-height: 40px;
      color: #fff;
      font-size: 18px;
    }
  }
  .el-form {
    padding: 0 20px;
  }
  .tc {
    text-align: center;
  }
}
</style>

element-ui 基本校验

说明:在向后端发请求,调用接口之前,我们需要对所要传递的参数进行验证,把用户的错误扼杀在摇篮之中。

讲解内容:

  • element-ui的校验

    • el-form: model属性, rules规则

    • el-form-item: 绑定 prop 属性

    • el-input: 绑定 v-model

Form 组件提供了表单验证的功能

官方文档写的不好,一共有四个地方需要绑定:

image-20230323150637534

image-20230323150743216

  1. form组件需要 :model绑定form对象(必须), 需要通过 rules 属性传入约定的验证规则
<el-form :model="form" :rules="rules">
    
export default {
  data() {
    return {
      form: {
        username: '',
        password: ''
      }
    }
  }
}
  1. 在 data 中准备 rules 规则
rules: {
  username: [
    { required: true, message: '请输入用户名', trigger: ['blur', 'change'] },
    { min: 5, max: 11, message: '长度在 5 到 11 个字符', trigger: ['blur', 'change'] }
  ]
}
  1. 将 Form-Item 的 prop 属性设置为需校验的字段名
<el-form-item label="用户名:" prop="username">
  <el-input v-model="form.username" placeholder="请输入手机号" />
</el-form-item>

element-ui 正则校验

下面是常用内置的基本验证规则:其余校验规则参见 async-validator

规则说明
required必须的,例如校验内容是否非空
pattern正则表达式,例如校验手机号码格式、校验邮箱格式
rules: {
  username: [
    { required: true, message: '请输入用户名', trigger: ['blur', 'change'] },
    { min: 5, max: 11, message: '长度在 5 到 11 个字符', trigger: ['blur', 'change'] }
  ],
  password: [
    { required: true, message: '请输入密码', trigger: ['blur', 'change'] },
    { pattern: /^\w{5,11}$/, message: '请输入 5 到 10 位的密码', trigger: ['blur', 'change'] }
  ]
}

// \d 数字 0-9
// \w 字母数字下划线
// {m,n} 前面的字符,可以出现 m次 ~ n次

不要忘了配置prop

<el-form-item prop="password">

上述已经可以完成大部分需求,如果需要更复杂业务校验需求,可以自定义校验~ (项目课程:人力资源系统会进一步讲解)

提交表单校验 和 重置

每次点击按钮, 进行ajax登录前, 应该先对整个表单内容校验, 不然还是会发送很多无效的请求!!!

要通过校验了, 才发送请求!!!

作用: ref 属性配合 $refs 可以获取 dom 元素 (或者 vue组件实例)

  1. 给组件或者元素, 添加 ref 属性
<hello ref="bb"></hello>
  1. 通过 this.$refs 可以获取对应的引用, 并且调用方法
this.$refs.bb.sayHi()

添加登录提交的校验

<el-form ref="form" :model="form" :rules="rules" autocomplete="off">
...
<el-button @click="login" type="primary">登 录</el-button>

methods: {
  async login () {
    try {
      const valid = await this.$refs.form.validate()
      console.log(valid)
    } catch (e) {
      console.log(e)
    }
  }
}

image-20230323164012161

添加重置功能

<el-button @click="reset">重 置</el-button>

methods: {
  reset () {
    this.$refs.form.resetFields()
  }
}

封装登录api登录请求

新建 api/user.js 提供api接口函数

import request from '@/utils/request'

export const login = ({ username, password }) => {
  return request.post('/auth/login', {
    username,
    password
  })
}

发送请求获取token

methods: {
  async login () {
    try {
      const valid = await this.$refs.form.validate()
      if (valid) {
        const res = await login(this.form)
        console.log(res)
      }
    } catch (e) {
      console.log(e)
    }
  }
}

vuex user 模块 - 存token

image-20230323192434641

新建 store/modules/user.js

import { getToken, setToken } from '@/utils/storage'

export default {
  namespaced: true,
  state () {
    return {
      token: getToken()
    }
  },
  mutations: {
    setUserToken (state, payload) {
      state.token = payload
      setToken(payload)
    }
  }
}

挂载模块

import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    user
  }
})

登录时调用

async login () {
  try {
    const valid = await this.$refs.form.validate()
    if (valid) {
      const res = await login(this.form)
      this.$store.commit('user/setUserToken', res.data.token)
      this.$router.push('/')
    }
  } catch (e) {
    console.log(e)
  }
},

登录访问拦截

router/index.js

image-20230323202132759

没有token 且 访问的不是 登录页,就直接拦截到登录

router.beforeEach((to, from, next) => {
  const { token } = store.state.user;
  if (to.path !== '/login' && !token ) return next('/login')
  next()
})

首页 layout 模块

layout 布局

api/user.js 准备api接口

export const getUser = () => {
  return request.get('/auth/currentUser')
}

layout/index.vue准备结构 (已准备)

<template>
  <el-container class="layout-page">
    <el-aside width="200px">
      <div class="logo">黑马面经</div>
      <el-menu
        router
        :default-active="$route.path"
        background-color="#313a46"
        text-color="#8391a2"
        active-text-color="#FFF"
      >
        <el-menu-item index="/dashboard">
          <i class="el-icon-pie-chart"></i>
          <span>数据看板</span>
        </el-menu-item>
        <el-menu-item index="/article">
          <i class="el-icon-notebook-1"></i>
          <span>面经管理</span>
        </el-menu-item>
      </el-menu>
    </el-aside>
    <el-container>
      <el-header>
        <div class="user">
          <el-avatar
            :size="36"
            :src="avatar"
          ></el-avatar>
          <el-link :underline="false">{{name}}</el-link>
        </div>
        <div class="logout">
          <el-popconfirm title="您确认退出黑马面运营后台吗?" @confirm="handleConfirm">
            <i slot="reference" title="logout" class="el-icon-switch-button"></i>
          </el-popconfirm>
        </div>
      </el-header>
      <el-main>
        <router-view></router-view>
      </el-main>
    </el-container>
  </el-container>
</template>

<script>
import { getUser } from '@/api/user'
export default {
  name: 'layout-page',
  data () {
    return {
      avatar: '',
      name: ''
    }
  },
  created () {
    this.initData()
  },
  methods: {
    async initData () {
      const { data } = await getUser()
      this.avatar = data.avatar
      this.name = data.name
    },
    handleConfirm () {
      this.$router.push('/login')
    }
  }
}
</script>

<style lang="scss" scoped>
.layout-page {
  height: 100vh;
  .el-aside {
    background: #313a46;
    .logo {
      color: #fff;
      font-size: 20px;
      height: 60px;
      line-height: 60px;
      text-align: center;
    }
    .el-menu {
      border-right: none;
      margin-top: 20px;
      &-item {
        background-color: transparent !important;
        > span, i {
          padding-left: 5px;
        }
      }
    }
  }
  .el-header {
    box-shadow: 0px 0px 35px 0px rgba(154, 161, 171, 0.15);
    background: #fff;
    display: flex;
    justify-content: flex-end;
    align-items: center;
    z-index: 999;
    .user {
      display: flex;
      align-items: center;
      background: #fafbfd;
      height: 60px;
      border: 1px solid #f1f3fa;
      padding: 0 15px;
      .el-avatar {
        margin-right: 15px;
      }
    }
    .logout {
      font-size: 20px;
      color: #999;
      cursor: pointer;
      padding: 0 15px;
    }
  }
  .el-footer {
    display: flex;
    justify-content: space-between;
    align-items: center;
    color: #aaa;
    border-top: 1px solid rgba(152, 166, 173, 0.2);
    font-size: 14px;
  }
}
</style>

遇到 401 错误

image-20220617140402138

image-20230323210317182

请求拦截器携带token

utils/request.js

// 添加请求拦截器
request.interceptors.request.use(function (config) {
  // 在发送请求之前做些什么
  const { token } = store.state.user
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error)
})

退出功能

退出操作

handleConfirm () {
  // this.$router.push('/login')
  this.$store.commit('user/logout')
  this.$router.push('/login')
}

提供mutation

import { delToken, getToken, setToken } from '@/utils/storage'

export default {
  namespaced: true,
  state () {
    return {
      token: getToken()
    }
  },
  mutations: {
    ...,
    logout (state) {
      state.token = null
      delToken()
    }
  }
}

处理token过期

响应拦截器,处理token过期

import router from '../router'

// 添加响应拦截器
request.interceptors.response.use(function (response) {
  // 对响应数据做点什么
  return response.data
}, function (error) {
  // 对响应错误做点什么  普通错误 + 401情况
  // console.dir(error)
  if (error.response) {
    if (error.response.status === 401) {
      // 给提示,清除无效token(vuex+本地),拦到登录
      Message.error('尊敬的用户,当前登录状态已过期!')

      // 提交清除token的mutation
      store.commit('user/logout')

      // 跳转到登录
      router.push('/login')
    } else {
      // 给提示
      Message.error(error.response.data.message)
    }
  }
  return Promise.reject(error)
})

数据看板 (了解)

静态结构

dashboard/index.vue

<template>
  <div class="dashboard-page">
    <el-breadcrumb separator-class="el-icon-arrow-right">
      <el-breadcrumb-item>面经后台</el-breadcrumb-item>
      <el-breadcrumb-item>数据看板</el-breadcrumb-item>
    </el-breadcrumb>
    <el-row :gutter="20">
      <el-col :span="6">
        <el-card style="height: 140px" shadow="never">
          <i class="el-icon-user"></i>
          <h5 class="tit">活跃用户</h5>
          <h2 class="num">802</h2>
          <p class="tag"><i>↑ 5.23%</i> 最近一个月</p>
        </el-card>
        <el-card style="height: 140px" shadow="never">
          <i class="el-icon-tickets"></i>
          <h5 class="tit">平均访问量</h5>
          <h2 class="num">1298</h2>
          <p class="tag"><i class="red">↓ 8.56%</i> 截止最近一周</p>
        </el-card>
        <el-card class="row" style="height: 180px" shadow="never">
          <h4>Enhance your Campaign for better outreach →</h4>
          <img src="@/assets/img.svg" alt="" />
        </el-card>
      </el-col>
      <el-col :span="18">
        <el-card style="height: 504px" shadow="never">
          <div class="chart-box" style="height: 500px"></div>
        </el-card>
      </el-col>
      <el-col :span="8">
        <el-card style="height: 420px" shadow="never">
          <h4>性别分布情况</h4>
          <img style="margin-top: 60px" src="@/assets/chart-03.png" alt="" />
        </el-card>
      </el-col>
      <el-col :span="8">
        <el-card style="height: 420px" shadow="never">
          <h4>浏览访问情况</h4>
          <img src="@/assets/chart-01.svg" alt="" />
        </el-card>
      </el-col>
      <el-col :span="8">
        <el-card style="height: 420px" shadow="never">
          <h4>设备系统访问情况</h4>
          <img style="margin-top: 20px" src="@/assets/chart-02.svg" alt="" />
        </el-card>
      </el-col>
    </el-row>
  </div>
</template>

<script>
export default {
  name: 'dashboard-page',
  data () {
    return {
      loading: true
    }
  },
  created () {},
  methods: {}
}
</script>

<style lang="scss" scoped>
.dashboard-page {
  .el-breadcrumb {
    margin-top: 10px;
    margin-bottom: 25px;
  }
  .el-card {
    margin-bottom: 20px;
    position: relative;
    &.row {
      h4 {
        width: 40%;
        float: left;
        font-size: 18px;
        margin-left: 5%;
      }
      img {
        width: 40%;
        float: left;
        margin-left: 10%;
        margin-top: 30px;
      }
    }
    [class^="el-icon"] {
      font-size: 30px;
      color: #ccc;
      position: absolute;
      right: 25px;
      top: 30px;
      font-weight: bold;
    }
    .tit {
      color: #6c757d;
      font-size: 14px;
      margin: 6px 0;
    }
    .num {
      color: #6c757d;
      font-size: 30px;
      margin: 6px 0;
    }
    .tag {
      color: #999;
      margin: 0;
      font-size: 14px;
      > i {
        font-style: normal;
        margin-right: 10px;
        color: rgb(10, 207, 151);
        &.red {
          color: #fa5c7c;
        }
      }
    }
    img {
      width: 100%;
      height: 100%;
    }
    h4 {
      margin: 0;
      padding-bottom: 20px;
      color: #6c757d;
    }
  }
}
</style>

vue中echarts的使用

装包

yarn add echarts

导入

import * as echarts from 'echarts'

添加ref

<div ref="box" class="chart-box" style="height: 500px"></div>

mounted初始化

image-20230324112446088

mounted () {
  const myChart = echarts.init(this.$refs.box)
  // 绘制图表
  myChart.setOption({
    title: {
      text: 'ECharts 入门示例'
    },
    tooltip: {},
    xAxis: {
      data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
    },
    yAxis: {},
    series: [
      {
        name: '销量',
        type: 'bar',
        data: [5, 20, 36, 10, 10, 20]
      }
    ]
  })
},
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值