先看效果:

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语句当做参数传到后端接口,和原来的项目隔离,实现功能。