有趣的地方

有趣的地方

三步实现SpringBoot全局日志记录,整合Echarts实现数据大屏


🚀 注重版权,转载请注明原作者和原文链接


效果展示

image.png
image.png

MySQL

建表

CREATE TABLE `access_log` (
  `access_log_id` bigint NOT NULL AUTO_INCREMENT,
  `access_time` datetime NOT NULL COMMENT '访问时间',
  `access_ip` varchar(30) NOT NULL COMMENT '访问IP',
  `api_group` varchar(50) NOT NULL DEFAULT '默认' COMMENT '接口分组',
  `req_url` varchar(100) NOT NULL COMMENT '请求URL',
  `req_method` varchar(10) NOT NULL COMMENT '请求方式',
  `os` varchar(100) NULL DEFAULT NULL COMMENT '操作系统',
  `browser` varchar(50) NULL DEFAULT NULL COMMENT '浏览器',
  `lsp` varchar(15) NULL DEFAULT NULL COMMENT '运营商',
  `country` varchar(15) NULL DEFAULT NULL COMMENT '国家',
  `province` varchar(15) NULL DEFAULT NULL COMMENT '省',
  `city` varchar(15) NULL DEFAULT NULL COMMENT '城市',
  PRIMARY KEY (`access_log_id`)
) COMMENT='访问日志表';

后端

POJO实体

@Data
@TableName(value = "access_log")
public class AccessLog implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 访问日志ID  
     */
    @TableId(type = IdType.AUTO)
    private Long accessLogId;

    /**
     * 访问时间  
     */
    private Date accessTime;

    /**
     * 访问IP  
     */
    private String accessIp;

    /**
     * 接口分组  
     */
    private String apiGroup;

    /**
     * 请求URL  
     */
    private String reqUrl;

    /**
     * 请求方式  
     */
    private String reqMethod;

    /**
     * 操作系统  
     */
    private String os;

    /**
     * 浏览器  
     */
    private String browser;

    /**
     * 运营商  
     */
    private String lsp;

    /**
     * 国家  
     */
    private String country;

    /**
     * 省  
     */
    private String province;

    /**
     * 城市  
     */
    private String city;
}

Mapper接口

@Repository
public interface AccessLogMapper extends BaseMapper<AccessLog> {
    
}

自定义注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {

    /**
     * 给接口分组
     */
    String apiGroup() default "默认";
}

案例:

@Log(apiGroup = "文章模块")
@RestController
@RequestMapping("/article")
public class ArticleController {
    ......
}

拦截器

ps:https://api.vvhan.com/api/getIpInfo?ip=[你的IP],这个网址是一个免费获取国家、省、市、运营商的地址
当然这种对IP地址的解析应该是放在定时任务中,每天晚上定时解析日志IP,如果解析IP的API挂了,接口会受到影响,我这里只是为了方便写在这里

@Slf4j
@Component
public class AccessLogInterceptor implements HandlerInterceptor {
    
    @Autowired
    private AccessLogMapper accessLogMapper;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        
        try {
            // 获取客户端真是IP地址,这种网上很多现成代码
            String accessIp = NetUtil.getRemoteHost(request);
            // 获取User-Agent
            String requestUserAgent = request.getHeader("User-Agent");
            // 获取浏览器用户标识
            UserAgent userAgent = UserAgentUtil.parse(requestUserAgent);
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Log logAnnotation = handlerMethod.getMethod().getDeclaringClass().getAnnotation(Log.class);

            AccessLog accessLog = new AccessLog();
            accessLog.setAccessIp(accessIp);
            accessLog.setAccessTime(new Date());
            if (logAnnotation != null) {
                accessLog.setApiGroup(logAnnotation.apiGroup());
            }
            accessLog.setReqUrl(request.getRequestURI());
            accessLog.setReqMethod(request.getMethod());
            accessLog.setOs(userAgent.getOs().getName());
            accessLog.setBrowser(userAgent.getBrowser().getName());

            // 解析IP
            try {
                String ipParseStr = HttpUtil.get("https://api.vvhan.com/api/getIpInfo?ip=" + accessIp);
                JSONObject ipParseJson = JSONUtil.parseObj(ipParseStr);
                if (ipParseJson.getBool("success")) {
                    JSONObject infoJson = ipParseJson.getJSONObject("info");
                    accessLog.setLsp(infoJson.getStr("lsp"));
                    accessLog.setCountry(infoJson.getStr("country"));
                    accessLog.setProvince(infoJson.getStr("prov"));
                    accessLog.setCity(infoJson.getStr("city"));
                }
            } catch (Exception e) {
                accessLog.setLsp("未知");
                accessLog.setCountry("未知");
                accessLog.setProvince("未知");
                accessLog.setCity("未知");
            }
            accessLogMapper.insert(accessLog);
        } catch (Exception e) {
            log.error("", e);
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

注册拦截器

@Configuration
public class WebMVCConfig implements WebMvcConfigurer {
    
    @Autowired
    private AccessLogInterceptor accessLogInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册全局日志拦截器
        registry.addInterceptor(accessLogInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/static/**")
                .excludePathPatterns("/error");

        // 其他拦截器......
    }
}

测试

到这里就完成一个简单的全局日志拦截器了,随便发几个请求测试一下,成功记录入库!
image.png

进阶——整合Echarts实现数据大屏

数据VO实体

浏览器访问占比情况VO
@Data
public class AccessBrowserGroupVo {
    
    private String browser;
    
    private Integer count;
}
运营商访问占比情况VO
@Data
public class AccessLspGroupVo {
    
    private String lsp;
    
    private Integer count;
}
各省份访问情况VO
@Data
public class AccessProvinceGroupVo {
    
    private String province;
    
    private Integer count;
}
每天访问情况VO
@Data
public class AccessTimeGroupVo {
    
    private String accessTime;
    
    private Integer count;
}

查询SQL

浏览器访问统计
<select id="countBrowserGroupAccess" resultType="com.xiaoyuan.common.vo.logs.AccessBrowserGroupVo">
  select browser, count(*) as count from access_log group by browser
</select>
运营商访问统计
<select id="countLspGroupAccess" resultType="com.xiaoyuan.common.vo.logs.AccessLspGroupVo">
  select lsp, count(*) as count from access_log group by lsp
</select>
各省份访问统计
<select id="countProvinceGroupAccess" resultType="com.xiaoyuan.common.vo.logs.AccessProvinceGroupVo">
  select province, count(*) as count from access_log group by province order by count desc limit 15
</select>
近15天内访问统计
<select id="countTimeGroupAccess" resultType="com.xiaoyuan.common.vo.logs.AccessTimeGroupVo">
    SELECT
        date_match.date_c as access_time,
        IFNULL( count, 0 ) as count
    FROM
        (
            SELECT
                DATE_FORMAT( @now := date_sub( @now, INTERVAL 1 DAY ), '%Y-%m-%d' ) AS date_c
            FROM
                ( SELECT @now := date_add( CURDATE(), INTERVAL 1 DAY ) FROM access_log LIMIT 15 ) date_match
            ORDER BY
                date_c
        ) date_match
            LEFT JOIN (
            SELECT
                DATE_FORMAT( access_time, '%Y-%m-%d' ) AS access_time,
                count(*) AS count
            FROM
                access_log
            WHERE
                    access_time >= (
                    SELECT
                        date_sub( curdate(), INTERVAL 15 DAY ))
            GROUP BY
                DATE_FORMAT( access_time, '%Y-%m-%d' )
        ) acc ON acc.access_time = date_match.date_c
</select>

后端业务

封装统一接口返回
/**
 * FileName:    R
 * Author:      小袁
 * Date:        2022/3/12 12:23
 * Description: 统一结果返回的类
 */
@Data
public class R<T> {

    private Boolean success;

    private Integer code;

    private String message;

    private T data;

    // 成功静态方法
    public static <T> R<T> success() {
        R<T> r = new R<>();
        r.setSuccess(true);
        r.setCode(HttpStatusEnum.SUCCESS.getCode());
        r.setMessage(HttpStatusEnum.SUCCESS.getName());
        return r;
    }

    public static <T> R<T> success(String message) {
        R<T> r = new R<>();
        r.setSuccess(true);
        r.setCode(HttpStatusEnum.SUCCESS.getCode());
        r.message(message);
        return r;
    }

    public static <T> R<T> success(T object) {
        R<T> r = new R<>();
        r.setData(object);
        r.setSuccess(true);
        r.setCode(HttpStatusEnum.SUCCESS.getCode());
        r.setMessage(HttpStatusEnum.SUCCESS.getName());
        return r;
    }

    public static <T> R<T> success(String msg, T object) {
        R<T> r = new R<>();
        r.setData(object);
        r.setCode(HttpStatusEnum.SUCCESS.getCode());
        r.setMessage(msg);
        return r;
    }

    // 失败静态方法
    public static <T> R<T> fail() {
        R<T> r = new R<>();
        r.setSuccess(false);
        r.setCode(HttpStatusEnum.FAIL.getCode());
        r.setMessage(HttpStatusEnum.FAIL.getName());
        return r;
    }

    public static <T> R<T> fail(String msg) {
        R<T> r = new R<>();
        r.setSuccess(false);
        r.setCode(HttpStatusEnum.FAIL.getCode());
        r.setMessage(msg);
        return r;
    }

    public static <T> R<T> fail(HttpStatusEnum httpStatusEnum) {
        R<T> r = new R<>();
        r.setSuccess(false);
        r.setCode(httpStatusEnum.getCode());
        r.setMessage(httpStatusEnum.getName());
        return r;
    }

    public R<T> message(String message){
        this.setMessage(message);
        return this;
    }

    public R<T> code(Integer code){
        this.setCode(code);
        return this;
    }

    public R<T> data(T data){
        this.setData(data);
        return this;
    }
}
封装客户端响应码
/**
 * FileName:    Code
 * Author:      小袁
 * Date:        2022/5/1 23:29
 * Description: 客户端响应状态码
 */
public enum HttpStatusEnum implements BaseCodeEnum {

    SUCCESS(200, "成功"),
    FAIL(20001, "失败"),
    INTERNAL_SERVER_ERROR(500, "服务器异常"),

    private final Integer code;
    private final String name;

    HttpStatusEnum(int code, String msg) {
        this.code = code;
        this.name = msg;
    }

    @Override
    public Integer getCode() {
        return this.code;
    }

    @Override
    public String getName() {
        return this.name;
    }
}
Mapper接口
@Repository
public interface AccessLogMapper extends BaseMapper<AccessLog> {

    List<AccessLspGroupVo> countLspGroupAccess();

    List<AccessBrowserGroupVo> countBrowserGroupAccess();

    List<AccessProvinceGroupVo> countProvinceGroupAccess();

    List<AccessTimeGroupVo> countTimeGroupAccess();
}
Service接口
public interface AccessLogService extends IService<AccessLog> {

    List<AccessLspGroupVo> countLspGroupAccess();

    List<AccessBrowserGroupVo> countBrowserGroupAccess();

    List<AccessProvinceGroupVo> countProvinceGroupAccess();

    List<AccessTimeGroupVo> countTimeGroupAccess();
}
Service实现类
@Slf4j
@Service
public class AccessLogServiceImpl extends ServiceImpl<AccessLogMapper, AccessLog> implements AccessLogService {
    
    @Override
    public List<AccessLspGroupVo> countLspGroupAccess() {
        return this.baseMapper.countLspGroupAccess();
    }

    @Override
    public List<AccessBrowserGroupVo> countBrowserGroupAccess() {
        return this.baseMapper.countBrowserGroupAccess();
    }

    @Override
    public List<AccessProvinceGroupVo> countProvinceGroupAccess() {
        return this.baseMapper.countProvinceGroupAccess();
    }

    @Override
    public List<AccessTimeGroupVo> countTimeGroupAccess() {
        return this.baseMapper.countTimeGroupAccess();
    }
}
Controller接口
@RestController
@RequestMapping("/stat/access")
public class AccessStatController {
    
    @Autowired
    private AccessLogService accessLogService;

    /**
     * 查询15天内的访问次数情况-折线图
     */
    @GetMapping("/query_line_by_day")
    public R<List<AccessTimeGroupVo>> queryAccessLogByTimeGroup() {
        return R.success(accessLogService.countTimeGroupAccess());
    }

    /**
     * 查询省份访问占比-柱形图
     */
    @GetMapping("/query_col_by_province")
    public R<List<AccessProvinceGroupVo>> queryAccessLogByProvinceGroup() {
        return R.success(accessLogService.countProvinceGroupAccess());
    }

    /**
     * 查询运营商访问占比-饼图
     */
    @GetMapping("/query_pie_by_lsp")
    public R<List<AccessLspGroupVo>> queryAccessLogByLspGroup() {
        return R.success(accessLogService.countLspGroupAccess());
    }

    /**
     * 查询浏览器访问占比-饼图
     */
    @GetMapping("/query_pie_by_browser")
    public R<List<AccessBrowserGroupVo>> queryAccessLogByBrowserGroup() {
        return R.success(accessLogService.countBrowserGroupAccess());
    }
}

前端配置

安装axios、echarts
npm install axios
npm install echarts
封装request
import axios from 'axios'
import { Message, MessageBox,} from 'element-ui'
import store from '../store'
import { getToken } from '@/utils/auth'
import router from '@/router'

// 创建axios实例
const service = axios.create({
  baseURL: process.env.BASE_API, // api 的 base_url
  // timeout: 5000 // 请求超时时间
})

// request拦截器
service.interceptors.request.use(
  config => {
    if (store.getters.token) {
     config.headers['token'] = getToken()
    }
    return config
  },
  error => {
    // Do something with request error
    console.log(error) // for debug
    Promise.reject(error)
  }
)

// response 拦截器
service.interceptors.response.use(
  response => {
    /**
     * code为非200是抛错 可结合自己业务进行修改
     */
    const res = response.data

    const url = response.config.url

    if (res.code !== 200) {
      if (url.indexOf("/login") < 0 && res.code === 40005) {
        store.dispatch('FedLogOut').then(() => {
          router.push(`/login`)
        })
        Message({
          message: res.message,
          type: 'warning',
          duration: 2 * 1000,
        })

        return Promise.resolve(res)
      }else if (res.code >= 40000) {
        Message({
          message: res.message,
          type: 'error',
          duration: 3 * 1000
        })
        return Promise.resolve(res)
      }else {
        Message({
          message: res.message,
          type: 'error',
          duration: 5 * 1000
        })
        return Promise.reject(new Error(res.message || 'Error'))
      }
    } else {
      return res
    }
  },
  error => {
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)

export default service
定义API
import request from "../../utils/request";

export default {
  getAccessStatByTime() {
    return request({
      url: '/stat/access/query_line_by_day',
      method: 'get'
    })
  },

  getAccessStatByProvince() {
    return request({
      url: '/stat/access/query_col_by_province',
      method: 'get'
    })
  },

  getAccessStatByLsp() {
    return request({
      url: '/stat/access/query_pie_by_lsp',
      method: 'get'
    })
  },

  getAccessStatByBrowser() {
    return request({
      url: '/stat/access/query_pie_by_browser',
      method: 'get'
    })
  },
}
封装echarts
<template>
  <div :id="id" :class="className" :style="{ height: height, width: width }" />
</template>

<script>
import * as echarts from 'echarts'
export default {
  name: 'echart',
  props: {
    className: {
      type: String,
      default: 'chart'
    },
    id: {
      type: String,
      default: 'chart'
    },
    width: {
      type: String,
      default: '100%'
    },
    height: {
      type: String,
      default: '2.5rem'
    },
    options: {
      type: Object,
      default: ()=>({})
    }
  },
  data () {
    return {
      chart: null
    }
  },
  watch: {
    options: {
      handler (options) {
        // 设置true清空echart缓存
        this.chart.setOption(options, true)
      },
      deep: true
    }
  },
  mounted () {
    // echarts.registerTheme('tdTheme', tdTheme); // 覆盖默认主题
    this.initChart();
  },
  beforeDestroy () {
    this.chart.dispose()
    this.chart = null
  },
  methods: {
    initChart () {
      // 初始化echart
      this.chart = echarts.init(this.$el)
      this.chart.setOption(this.options, true)
    }
  }
}
</script>

<style>
body {
  margin: 0;
  padding: 0;
}
</style>
饼图-浏览器访问占比
<template>
  <div>
    <Echart :options="options"
      id="lspEcharts"
      height="300px"
      width="100%"/>
  </div>
</template>

<script>
  import statAccess from "@/api/stat/statAccess";
  import Echart from "@/components/Echart/index.vue";

  export default {
    components: {
      Echart
    },
    data() {
      return {
        options: {},
        lspData: []
      }
    },
    methods: {
      initData() {
        statAccess.getAccessStatByBrowser().then(res => {
          this.lspData = res.data.map(obj => {
            return {
              name: obj.browser,
              value: obj.count
            }
          })
          this.executeDraw()
        })
      },
      executeDraw() {
        this.options = {
          title: {
            text: '浏览器访问占比',
            left: 'center',
            textStyle: {
              color: '#FDF5E6'
            }
          },
          tooltip: {
            trigger: 'item'
          },
          legend: {
            orient: 'vertical',
            left: 'left',
            top: '20%',
            textStyle: {
              color: '#FDF5E6'
            }
          },
          series: [
            {
              name: '访问次数',
              type: 'pie',
              radius: '90%',
              top: '20%',
              data: this.lspData,
              label: {
                color: '#FDF5E6'
              },
              emphasis: {
                itemStyle: {
                  shadowBlur: 10,
                  shadowOffsetX: 0,
                  shadowColor: 'rgba(0, 0, 0, 0.5)'
                }
              }
            }
          ]
        }
      }
    },
    mounted() {
      this.initData()
    }
  }
</script>
饼图-运营商访问占比
<template>
  <div>
    <Echart :options="options"
            id="lspEcharts"
            height="300px"
            width="100%"/>
  </div>
</template>

<script>
import statAccess from "@/api/stat/statAccess";
import Echart from "@/components/Echart/index.vue";

export default {
  components: {
    Echart
  },
  data() {
    return {
      options: {},
      lspData: []
    }
  },
  methods: {
    initData() {
      statAccess.getAccessStatByLsp().then(res => {
        this.lspData = res.data.map(obj => {
          return {
            name: obj.lsp,
            value: obj.count
          }
        })
        this.executeDraw()
      })
    },
    executeDraw() {
      this.options = {
        title: {
          text: '运营商访问占比',
          left: 'center',
          textStyle: {
            color: '#FDF5E6'
          }
        },
        tooltip: {
          trigger: 'item'
        },
        legend: {
          orient: 'vertical',
          left: 'left',
          top: '20%',
          textStyle: {
            color: '#FDF5E6'
          }
        },
        series: [
          {
            name: '访问次数',
            type: 'pie',
            radius: '90%',
            top: '20%',
            data: this.lspData,
            label: {
              color: '#FDF5E6'
            },
            emphasis: {
              itemStyle: {
                shadowBlur: 10,
                shadowOffsetX: 0,
                shadowColor: 'rgba(0, 0, 0, 0.5)'
              }
            }
          }
        ]
      }
    }
  },
  mounted() {
    this.initData()
  }
}
</script>
折线图-每天访问量情况
<template>
  <div>
    <Echart :options="options"
            id="timeEcharts"
            height="400px"
            width="100%"/>
  </div>
</template>

<script>
import statAccess from "@/api/stat/statAccess";
import Echart from "@/components/Echart/index.vue";

export default {
  components: {
    Echart
  },
  data() {
    return {
      options: {},
      xAxis: [],
      yAxis: []
    }
  },
  methods: {
    initData() {
      statAccess.getAccessStatByTime().then(res => {
        let x = []
        let y = []
        for (let i = 0; i < res.data.length; i++) {
          x.push(res.data[i].accessTime)
          y.push(res.data[i].count)
        }
        this.xAxis = x
        this.yAxis = y
        this.executeDraw()
      })
    },
    executeDraw() {
      this.options = {
        title: {
          show: true,
          text: '小袁博客15天内访问情况统计',
          left: 'center',
          textStyle: {
            color: '#FDF5E6'
          }
        },
        legend: {
          show: true,
          left: '1%',
          textStyle: {
            color: '#FDF5E6'
          }
        },
        tooltip: {
          trigger: 'axis',
          axisPointer: { type: 'line' }
        },
        grid:{
          left:"1%",
          right:"1%",
          bottom:"1%",
          containLabel:true,
        },
        xAxis: {
          type: 'category',
          data: this.xAxis,
          boundaryGap: ['5%', '5%',],//坐标轴两边留白
          axisLabel: {
            color: '#FDF5E6'
          },
          axisLine: {//坐标轴
            lineStyle:{
              opacity: 0.01,//设置透明度就可以控制显示不显示
            },
          },
          splitLine: {//网格线
            lineStyle:{
              color: '#eeeeee',
            },
          },
          axisTick: {//刻度线
            show: false,//去掉刻度线
          },
        },
        yAxis: {
          type: 'value',
          name:'次         ',//是基于Y轴线对齐,用空格站位让坐标轴名称与刻度名称对齐
          axisLabel: {
            color: '#eee'
          },
          nameTextStyle: {
            color:'#444e65',
            align:'left',//文字水平对齐方式
            verticalAlign:'middle',//文字垂直对齐方式
          },
          axisTick: {//刻度线
            show: false,//去掉刻度线
          },
          axisLine: {//坐标轴线
            lineStyle:{
              opacity: 0,//透明度为0
            },
          },
          splitLine: {//网格线
            show: true,//网格线
            lineStyle:{
              color: 'rgba(211, 211, 211, 0.5)',
            },
          },
        },
        series: [
          {
            data: this.yAxis,
            smooth: true,
            type: 'line',
            name: '访问次数',
            itemStyle: {//折线拐点标志的样式。
              normal: {
                color: '#98F5FF',
              },
            },
          }
        ]
      }
    }
  },
  mounted() {
    this.initData()
  }
}
</script>
柱状图-各省份访问情况
<template>
  <div>
    <Echart :options="options"
            id="lspEcharts"
            height="300px"
            width="100%"/>
  </div>
</template>

<script>
import statAccess from "@/api/stat/statAccess";
import Echart from "@/components/Echart/index.vue";

export default {
  components: {
    Echart
  },
  data() {
    return {
      options: {},
      axis: [],
      series: []
    }
  },
  methods: {
    initData() {
      statAccess.getAccessStatByProvince().then(res => {
        let x = []
        let y = []
        for (let i = 0; i < res.data.length; i++) {
          x.push(res.data[i].province)
          y.push(res.data[i].count)
        }
        this.axis = x
        this.series = y
        this.executeDraw()
      })
    },
    executeDraw() {
      this.options = {
        tooltip: {},
        title: {
          text: '各城市访问情况占比',
          left: 'center',
          textStyle: {
            color: '#FDF5E6'
          }
        },
        grid:{
          left: "1%",
          right: "1%",
          bottom: "1%",
          containLabel: true,
        },
        legend: {
          show: true,
          left: '1%',
          textStyle: {
            color: '#FDF5E6'
          }
        },
        xAxis: {
          data: this.axis,
          axisLine: {
            show: false,
          },
          axisLabel: {
            color: '#FDF5E6'
          }
        },
        yAxis: {
          // 网格样式
          splitLine: {
            show: false,
          },
          axisLabel: {
            color: '#FDF5E6'
          }
        },
        series: [{
          name: '访问量',
          type: 'bar',
          data: this.series,
          barWidth: 15,
          itemStyle: {
            color: {
              type:'linear',
              x: 0,
              y: 0,
              x2: 0,
              y2: 1,
              colorStops: [
                {
                  offset: 0,
                  color: 'rgba(255, 130, 71, 1)',
                },
                {
                  offset: 1,
                  color: 'rgba(255, 130, 71, 0.5)',
                },
              ],
              globaCoord: false,
            },
            barBorderRadius: [5, 5, 0, 0], // (顺时针左上,右上,右下,左下)
          },
        }],
      }
    }
  },
  mounted() {
    this.initData()
  }
}
</script>
首页

直接引入

<template>
  <div class="dashboard-container">
    <div class="dashboard-bg"></div>
    <div class="echart-div">
      <el-row :gutter="1" class="item">
      <el-col :span="8">
        <BrowserPie></BrowserPie>
      </el-col>
      <el-col :span="16">
        <TimeLine></TimeLine>
      </el-col>
      </el-row>
      <el-row style="margin-top: 35px" class="item">
        <el-col span="8">
          <LspPie></LspPie>
        </el-col>
        <el-col span="16">
          <ProvinceCol></ProvinceCol>
        </el-col>
      </el-row>
    </div>
  </div>
</template>

<script>
import LspPie from "./components/LspPie.vue";
import ProvinceCol from "./components/ProvinceCol.vue";
import BrowserPie from "./components/BrowserPie.vue";
import TimeLine from "./components/TimeLine.vue";

export default {
  components: {
    LspPie,
    ProvinceCol,
    BrowserPie,
    TimeLine
  },
  name: 'home',
  data() {
    return {
    }
  },
  mounted() {
  },
  methods: {
  }
}
</script>

<style rel="stylesheet/scss" lang="scss" scoped>
.dashboard {
  &-container {
    .echart-div {
      padding: 30px;
    }
  }
  &-text {
    font-size: 22px;
    line-height: 46px;
  }
  .personal {
    .box-card-header {
      position: relative;
      height: 220px;
      img {
        width: 100%;
        height: 100%;
        transition: all 0.2s linear;
        &:hover {
          transform: scale(1.1, 1.1);
          filter: contrast(130%);
        }
      }
    }
  }
}
.dashboard {
  &-bg {
    background-image: url('../../assets/img/home_bg.png');
    background-size: cover;
    background-repeat: no-repeat;
    background-attachment: fixed; /* 可选,固定背景图片 */
    background-position: center; /* 可选,设置背景图片位置 */
    position: absolute;
    top: 0;
    left: 0;
    height: 100%;
    width: 100%;
    filter: blur(1px);
  }
}
</style>

结束

到这里整篇文章就结束了,我们重新捋一下整个流程

  • 全局过滤器拦截请求
  • 对请求信息进行解析入库
  • 定义API接口
  • 前端引入axios、echarts
  • 编写图形Vue组件
  • 前后端数据交互

🚀 注重版权,转载请注明原作者和原文链接


发表评论:

Powered By Z-BlogPHP 1.7.3

© 2018-2020 有趣的地方 粤ICP备18140861号-1 网站地图