SpringBoot+Vue+Echarts实现可视化图表的渲染

先看效果:

1.首页

javascript 复制代码
<template>
  <div class="dashboard-container">


    <!-- 侧边栏 -->


    <!-- 主内容区 -->
    <div class="main-content">
      <!-- 数据卡片 -->
      <div class="data-cards">
        <div class="card">
          <div class="card-header">
            <span class="title">总订单数</span>
            <span class="iconfont icon-order"></span>
          </div>
          <div class="card-body">
            <div class="value">{{ orderCount }}</div>
            <div class="trend">
              <span class="up">↑ 12.5%</span>
              <span class="text">较上月</span>
            </div>
          </div>
        </div>

        <div class="card">
          <div class="card-header">
            <span class="title">商品总数</span>
            <span class="iconfont icon-product"></span>
          </div>
          <div class="card-body">
            <div class="value">{{ productCount }}</div>
            <div class="trend">
              <span class="up">↑ 8.3%</span>
              <span class="text">较上月</span>
            </div>
          </div>
        </div>

        <div class="card">
          <div class="card-header">
            <span class="title">销售总额</span>
            <span class="iconfont icon-money"></span>
          </div>
          <div class="card-body">
            <div class="value">¥{{ salesAmount }}</div>
            <div class="trend">
              <span class="up">↑ 18.7%</span>
              <span class="text">较上月</span>
            </div>
          </div>
        </div>

        <div class="card">
          <div class="card-header">
            <span class="title">用户总数</span>
            <span class="iconfont icon-user"></span>
          </div>
          <div class="card-body">
            <div class="value">{{ userCount }}</div>
            <div class="trend">
              <span class="up">↑ 23.1%</span>
              <span class="text">较上月</span>
            </div>
          </div>
        </div>
      </div>

      <!-- 图表区域 -->
      <div class="charts">
        <div class="chart-container">
          <div class="chart-header">
            <h3>商品销售数量分布</h3>
<!--            <div class="time-filter">
              <button class="filter-btn active">本周</button>
              <button class="filter-btn">本月</button>
              <button class="filter-btn">全年</button>
            </div>-->
          </div>
          <div class="chart-content" ref="salesChart"></div>
        </div>

        <div class="chart-container">
          <div class="chart-header">
            <h3>销售趋势分析</h3>
<!--            <div class="time-filter">
              <button class="filter-btn active">本周</button>
              <button class="filter-btn">本月</button>
              <button class="filter-btn">全年</button>
            </div>-->
          </div>
          <div class="chart-content" ref="trendChart"></div>
        </div>
      </div>

      <!-- 最近订单表格 -->
<!--      <div class="recent-orders">
        <div class="table-header">
          <h3>最近订单</h3>
          <button class="view-all">查看全部</button>
        </div>
        <div class="table-container">
          <table>
            <thead>
            <tr>
              <th>订单编号</th>
              <th>用户</th>
              <th>商品</th>
              <th>金额</th>
              <th>状态</th>
              <th>操作</th>
            </tr>
            </thead>
            <tbody>
            <tr v-for="(order, index) in recentOrders" :key="index">
              <td>{{ order.orderNo }}</td>
              <td>{{ order.username }}</td>
              <td>{{ order.productName }}</td>
              <td>¥{{ order.amount.toFixed(2) }}</td>
              <td>
                <span class="status" :class="order.statusClass">{{ order.status }}</span>
              </td>
              <td>
                <button class="action-btn">详情</button>
              </td>
            </tr>
            </tbody>
          </table>
        </div>
      </div>-->
    </div>
  </div>
</template>

<script>
// import echarts from 'echarts';
import * as echarts from 'echarts';


export default {
  data() {
    return {
      // 统计数据
      orderCount: 1284,
      productCount: 356,
      salesAmount: 89456.25,
      userCount: 5214,
      // 图表数据
      salesData: [
        {name: '电子产品', value: 286},
        {name: '服装鞋帽', value: 412},
        {name: '食品饮料', value: 325},
        {name: '家居用品', value: 189},
        {name: '美妆个护', value: 247},
        {name: '图书音像', value: 124}
      ],

      // 最近订单数据
      recentOrders: [
        {
          orderNo: 'ORD-20250516-001',
          username: '张三',
          productName: 'iPhone 15 Pro',
          amount: 8999.00,
          status: '已完成',
          statusClass: 'completed'
        },
        {
          orderNo: 'ORD-20250516-002',
          username: '李四',
          productName: '连衣裙',
          amount: 329.00,
          status: '已完成',
          statusClass: 'completed'
        },
        {
          orderNo: 'ORD-20250516-003',
          username: '王五',
          productName: '无线耳机',
          amount: 1299.00,
          status: '处理中',
          statusClass: 'processing'
        },
        {
          orderNo: 'ORD-20250516-004',
          username: '赵六',
          productName: '咖啡礼盒',
          amount: 298.00,
          status: '已取消',
          statusClass: 'cancelled'
        },
        {
          orderNo: 'ORD-20250516-005',
          username: '孙七',
          productName: '智能手表',
          amount: 1899.00,
          status: '待付款',
          statusClass: 'pending'
        }
      ],

      // 销售趋势数据
      trendData: {
        dates: ['5/10', '5/11', '5/12', '5/13', '5/14', '5/15', '5/16'],
        sales: [12540, 13860, 11250, 14680, 15920, 16850, 17240]
      }
    };
  },

  mounted() {
    this.fetchDashboardData();
    // setTimeout(() => {
    //   this.initSalesChart();
    //   this.initTrendChart();
    // }, 0);
    this.initSalesChart();
    this.initTrendChart();

    // 监听窗口大小变化,重新绘制图表
    window.addEventListener('resize', this.resizeCharts);
  },

  beforeDestroy() {
    // 移除事件监听
    window.removeEventListener('resize', this.resizeCharts);
  },

  methods: {

    // orderCount: 1284,
    // productCount: 356,
    // salesAmount: 89456.25,
    // userCount: 5214,

    fetchDashboardData() {
      var that=this;

      this.$http.post('/order/welcome').then(response => {
        console.log(response.data)
        // 调用API获取数据
        this.orderCount = response.data.orderCount;
        this.productCount = response.data.productCount;
        this.salesAmount=response.data.salesAmount;
        this.userCount=response.data.userCount
      })
      const data = {
        sql: `SELECT
                DATE (o.ceatetime) AS name,sum(od.amount) AS val
              FROM
                \`order\` o
                JOIN
                orderdetail od
              ON o.id = od.orderid
                JOIN
                product p ON od.productid = p.id
              WHERE
                o.ceatetime BETWEEN '2025-05-01'
                AND CURDATE()
                AND o.state = '已完成'
              GROUP BY
                DATE (o.ceatetime)
              ORDER BY
                name ASC;
        `
      }
      this.$http.post('/order/selectAction',data).then(response => {

        var data=response.data.data;
        var namesArray = []; //名称
        var valsArray =[]; //数值
        var beanArray =[]; //数值

        for (var i = 0; i < data.length; i++) {
          var obj = data[i];
          namesArray.push(obj.name);
          valsArray.push(obj.val);
          beanArray.push({"name": obj.name, "value": obj.val});
        }
        this.trendData.dates=namesArray;
        this.trendData.sales=valsArray;

        // this.initTrendChart();
      })


      const data02 = {
        sql: `SELECT t.name                     AS name,
                     COALESCE(SUM(od.count), 0) AS val
              FROM types t
                     LEFT JOIN
                   product p ON t.id = p.mark2
                     LEFT JOIN
                   orderdetail od ON p.id = od.productid
                     LEFT JOIN
                   \`order\` o ON od.orderid = o.id AND o.state = '已完成'
              GROUP BY t.id, t.name;
        `
      }
      this.$http.post('/order/selectAction',data02).then(response => {

        var data=response.data.data;
        var namesArray = []; //名称
        var valsArray =[]; //数值
        var beanArray =[]; //数值

        for (var i = 0; i < data.length; i++) {
          var obj = data[i];
          namesArray.push(obj.name);
          valsArray.push(obj.val);
          beanArray.push({"name": obj.name, "value": obj.val});
        }
        this.salesData=beanArray;
        // 初始化图表
        // this.initSalesChart();
      })

    },
    // 初始化商品销售数量分布图表
    initSalesChart() {
      const chartDom = this.$refs.salesChart;
      const myChart = echarts.init(chartDom);

      // 图表配置
      const option = {
        tooltip: {
          trigger: 'axis',
          axisPointer: {
            type: 'shadow'
          }
        },
        grid: {
          left: '3%',
          right: '4%',
          bottom: '3%',
          containLabel: true
        },
        xAxis: {
          type: 'category',
          data: this.salesData.map(item => item.name),
          axisTick: {
            alignWithLabel: true
          }
        },
        yAxis: {
          type: 'value'
        },
        series: [
          {
            name: '销售数量',
            type: 'bar',
            barWidth: '60%',
            data: this.salesData.map(item => item.value),
            itemStyle: {
              color: '#007bff'
            }
          }
        ]
      };

      // 使用配置项显示图表
      myChart.setOption(option);
      this.salesChart = myChart;
    },

    // 初始化销售趋势图表
    initTrendChart() {
      const chartDom = this.$refs.trendChart;
      const myChart = echarts.init(chartDom);

      // 图表配置
      const option = {
        tooltip: {
          trigger: 'axis'
        },
        grid: {
          left: '3%',
          right: '4%',
          bottom: '3%',
          containLabel: true
        },
        xAxis: {
          type: 'category',
          boundaryGap: false,
          data: this.trendData.dates
        },
        yAxis: {
          type: 'value'
        },
        series: [
          {
            name: '销售额',
            type: 'line',
            data: this.trendData.sales,
            itemStyle: {
              color: '#28a745'
            },
            areaStyle: {
              color: {
                type: 'linear',
                x: 0,
                y: 0,
                x2: 0,
                y2: 1,
                colorStops: [
                  {offset: 0, color: 'rgba(40, 167, 69, 0.3)'},
                  {offset: 1, color: 'rgba(40, 167, 69, 0)'}
                ]
              }
            }
          }
        ]
      };

      // 使用配置项显示图表
      myChart.setOption(option);
      this.trendChart = myChart;
    },

    // 图表自适应窗口大小
    resizeCharts() {
      if (this.salesChart) {
        this.salesChart.resize();
      }
      if (this.trendChart) {
        this.trendChart.resize();
      }
    }
  }
};
</script>

<style scoped>
.dashboard-container {
  display: flex;
  height: 100vh;
  overflow: hidden;
}

.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  height: 60px;
  background-color: #2c3e50;
  color: white;
  padding: 0 20px;
}

.logo {
  display: flex;
  align-items: center;
  font-size: 18px;
  font-weight: bold;
}

.logo .iconfont {
  margin-right: 10px;
}

.user-info {
  display: flex;
  align-items: center;
}

.username {
  margin-right: 10px;
}

.sidebar {
  width: 220px;
  background-color: #34495e;
  color: white;
  height: calc(100vh - 60px);
  overflow-y: auto;
}

.menu-item {
  display: flex;
  align-items: center;
  padding: 15px 20px;
  cursor: pointer;
  transition: background-color 0.3s;
}

.menu-item:hover,
.menu-item.active {
  background-color: #2c3e50;
}

.menu-item .iconfont {
  margin-right: 15px;
  width: 20px;
}

.main-content {
  flex: 1;
  padding: 20px;
  background-color: #f5f6fa;
  overflow-y: auto;
}

.data-cards {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 20px;
  margin-bottom: 20px;
}

.card {
  background-color: white;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
  padding: 20px;
}

.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 15px;
}

.title {
  font-size: 16px;
  color: #777;
}

.card-header .iconfont {
  font-size: 24px;
  color: #007bff;
}

.card-body .value {
  font-size: 28px;
  font-weight: bold;
  margin-bottom: 10px;
}

.trend {
  display: flex;
  align-items: center;
  font-size: 14px;
}

.up {
  color: #28a745;
  margin-right: 5px;
}

.down {
  color: #dc3545;
  margin-right: 5px;
}

.charts {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
  margin-bottom: 20px;
}

.chart-container {
  background-color: white;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
  padding: 20px;
}

.chart-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}

.chart-header h3 {
  font-size: 18px;
  font-weight: 500;
}

.time-filter {
  display: flex;
}

.filter-btn {
  padding: 5px 15px;
  border: 1px solid #ddd;
  background-color: white;
  cursor: pointer;
  margin-left: -1px;
}

.filter-btn.active {
  background-color: #007bff;
  color: white;
  border-color: #007bff;
}

.filter-btn:first-child {
  border-radius: 4px 0 0 4px;
}

.filter-btn:last-child {
  border-radius: 0 4px 4px 0;
}

.chart-content {
  height: 300px;
}

.recent-orders {
  background-color: white;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
  padding: 20px;
}

.table-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}

.table-header h3 {
  font-size: 18px;
  font-weight: 500;
}

.view-all {
  background-color: #007bff;
  color: white;
  border: none;
  padding: 8px 15px;
  border-radius: 4px;
  cursor: pointer;
}

.table-container {
  overflow-x: auto;
}

table {
  width: 100%;
  border-collapse: collapse;
}

th,
td {
  padding: 12px 15px;
  text-align: left;
  border-bottom: 1px solid #eee;
}

th {
  background-color: #f8f9fa;
  font-weight: 500;
}

.status {
  padding: 3px 8px;
  border-radius: 4px;
  font-size: 12px;
}

.status.pending {
  background-color: #fff3cd;
  color: #856404;
}

.status.processing {
  background-color: #cce5ff;
  color: #004085;
}

.status.completed {
  background-color: #d4edda;
  color: #155724;
}

.status.cancelled {
  background-color: #f8d7da;
  color: #721c24;
}

.action-btn {
  background-color: #007bff;
  color: white;
  border: none;
  padding: 5px 10px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
}
</style>

2.控制层

java 复制代码
 //公共查询方法

    @RequestMapping("/selectAction")
    public Object selectAction(@RequestBody  SqlRequest sql) {
        List<Map> mapList = orderMapper.selectAction(sql.getSql());
        System.out.println("sql = " + sql);
        Map map = new HashMap();
        map.put("data", mapList);
        return map;
    }

//首页数据
  @RequestMapping("welcome")
    public Map<String, Object> welcome() {
        String sql="select count(*) nums  from `order`";
        String sql1="select count(*) nums1  from product";
        String sql2="SELECT SUM(amount) AS nums2 FROM `order`";
        String sql3="select count(*) nums3  from user";
        Map<String, Object> map = new HashMap<String, Object>();
        List<Map> maps = orderMapper.selectAction(sql);
        List<Map> maps1 = productMapper.selectAction(sql1);
        List<Map> maps2 = orderMapper.selectAction(sql2);
        List<Map> maps3 = userMapper.selectAction(sql3);
        Object nums = maps.get(0).get("nums");

        map.put("orderCount",nums);
        map.put("productCount",maps1.get(0).get("nums1"));
        map.put("salesAmount",maps2.get(0).get("nums2"));
        map.put("userCount",maps3.get(0).get("nums3"));
        return map;
    }

3.Mapper

java 复制代码
@Select(" ${sql} ")
	public List<Map> selectAction(@Param("sql") String sql);

4.前端直接传SQL,后台接收需要一个实体类封装:

java 复制代码
class SqlRequest {
    private String sql; // 接收SQL语句
    // 可添加其他参数,如查询类型、分页信息等

     public String getSql() {
         return sql;
     }

     public void setSql(String sql) {
         this.sql = sql;
     }
 }

写法不是很规范,但是很多时候在别人的项目里去添加功能就很方便,不用去读原来的项目代码,直接在前端把SQL语句当做参数传到后端接口,和原来的项目隔离,实现功能。

相关推荐
徐_三岁5 分钟前
const ‘不可变’到底是值不变还是地址不变
前端·javascript·vue.js
当归102435 分钟前
SpringBoot集成第三方jar的完整指南
spring boot·python·jar
!chen1 小时前
基于element-UI 实现下拉框滚动翻页查询通用组件
javascript·vue.js·ui
啊阿狸不会拉杆2 小时前
《软件工程》实战— 在线教育平台开发
java·vue.js·软件工程·团队开发
酷爱码2 小时前
SpringBoot整合Sa-Token实现RBAC权限模型的过程解析
数据库·spring boot·后端
工业互联网专业5 小时前
基于Android的记录生活APP_springboot+vue
android·vue.js·spring boot·毕业设计·源码·课程设计·记录生活app
sunddy_x5 小时前
宝塔部署 Vue + NestJS 全栈项目
前端·javascript·vue.js·node.js
0.0~0.05 小时前
若依框架修改模板,添加通过excel导入数据功能
java·spring boot·vue
劲爽小猴头6 小时前
企业级Spring MVC高级主题与实用技术讲解
java·spring boot·后端·spring·mvc
前端工作日常6 小时前
我理解的`npm`依赖安装机制
vue.js·前端工程化