feat: add dashboard enhancement with ECharts charts (#20)
- Add 6 dashboard query methods to RlzOrderMapper (status/type/trend/ranking) - Add dashboard endpoint /system/view/dashboard combining all stats - Rewrite index.vue with ECharts: summary cards, order/revenue trend, status pie, hospital and caregiver ranking charts - Add getDashboard API to finance.js Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -69,3 +69,12 @@ export function getJiaoyiDetail(id) {
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// ========== 仪表盘 ==========
|
||||
|
||||
export function getDashboard() {
|
||||
return request({
|
||||
url: '/system/view/dashboard',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,121 +1,251 @@
|
||||
<template>
|
||||
<div class="app-container home">
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :sm="24" :lg="12" style="padding-left: 20px">
|
||||
<h2>瑞来兹医助后台管理框架</h2>
|
||||
<p>
|
||||
一直想做一款后台管理系统,看了很多优秀的开源项目但是发现没有合适自己的。于是利用空闲休息时间开始自己写一套后台系统。如此有了瑞来兹医助管理系统,她可以用于所有的Web应用程序,如网站管理后台,网站会员中心,CMS,CRM,OA等等,当然,您也可以对她进行深度定制,以做出更强系统。所有前端后台代码封装过后十分精简易上手,出错概率低。同时支持移动客户端访问。系统会陆续更新一些实用功能。
|
||||
</p>
|
||||
<p>
|
||||
<b>当前版本:</b> <span>v{{ version }}</span>
|
||||
</p>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-divider />
|
||||
<!-- <div class="iframe-out" >
|
||||
<iframe
|
||||
id="ifr"
|
||||
:src="src"
|
||||
ref="iframe"
|
||||
style="min-height: 60vh; width: 100%"
|
||||
marginheight="0"
|
||||
marginwidth="0"
|
||||
frameborder="0"
|
||||
scrolling="auto"
|
||||
></iframe>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAuthToken } from "@/api/system/user";
|
||||
export default {
|
||||
name: "Index",
|
||||
data() {
|
||||
return {
|
||||
// 版本号
|
||||
version: "3.8.3",
|
||||
src: ""
|
||||
};
|
||||
},
|
||||
created() {
|
||||
// getAuthToken().then((response=>{
|
||||
// let tokens= response.authToken;
|
||||
// this.src="http://124.70.96.132:8000/ztbPortal/rest/companyLogin/loginUser1?"+tokens;
|
||||
// alert(this.src);
|
||||
// }))
|
||||
},
|
||||
methods: {
|
||||
goTarget(href) {
|
||||
window.open(href, "_blank");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.home {
|
||||
blockquote {
|
||||
padding: 10px 20px;
|
||||
margin: 0 0 20px;
|
||||
font-size: 17.5px;
|
||||
border-left: 5px solid #eee;
|
||||
}
|
||||
hr {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
border: 0;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
.col-item {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
font-family: "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 13px;
|
||||
color: #676a6c;
|
||||
overflow-x: hidden;
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 10px;
|
||||
font-size: 26px;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 10px;
|
||||
|
||||
b {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.update-log {
|
||||
ol {
|
||||
display: block;
|
||||
list-style-type: decimal;
|
||||
margin-block-start: 1em;
|
||||
margin-block-end: 1em;
|
||||
margin-inline-start: 0;
|
||||
margin-inline-end: 0;
|
||||
padding-inline-start: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="app-container home">
|
||||
<!-- 统计卡片 -->
|
||||
<el-row :gutter="20" class="mb20">
|
||||
<el-col :xs="24" :sm="12" :lg="6">
|
||||
<div class="stat-card card-total">
|
||||
<div class="stat-icon"><i class="el-icon-document" /></div>
|
||||
<div class="stat-body">
|
||||
<div class="stat-value">{{ summary.totalOrders }}</div>
|
||||
<div class="stat-label">订单总数</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :lg="6">
|
||||
<div class="stat-card card-today">
|
||||
<div class="stat-icon"><i class="el-icon-date" /></div>
|
||||
<div class="stat-body">
|
||||
<div class="stat-value">{{ summary.todayOrders }}</div>
|
||||
<div class="stat-label">今日订单</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :lg="6">
|
||||
<div class="stat-card card-revenue">
|
||||
<div class="stat-icon"><i class="el-icon-money" /></div>
|
||||
<div class="stat-body">
|
||||
<div class="stat-value">{{ summary.totalRevenue }}</div>
|
||||
<div class="stat-label">总收入(元)</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :lg="6">
|
||||
<div class="stat-card card-pending">
|
||||
<div class="stat-icon"><i class="el-icon-bell" /></div>
|
||||
<div class="stat-body">
|
||||
<div class="stat-value">{{ summary.pendingOrders }}</div>
|
||||
<div class="stat-label">待处理订单</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 第一行图表:订单趋势 + 状态分布 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="16">
|
||||
<el-card>
|
||||
<div slot="header"><span>近30天订单/收入趋势</span></div>
|
||||
<div ref="orderTrendChart" style="height:350px" v-loading="loading" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card>
|
||||
<div slot="header"><span>订单状态分布</span></div>
|
||||
<div ref="statusPieChart" style="height:350px" v-loading="loading" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 第二行图表:医院排名 + 陪护排名 -->
|
||||
<el-row :gutter="20" style="margin-top:20px">
|
||||
<el-col :span="12">
|
||||
<el-card>
|
||||
<div slot="header"><span>医院服务量排名 (TOP10)</span></div>
|
||||
<div ref="hospitalChart" style="height:350px" v-loading="loading" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card>
|
||||
<div slot="header"><span>陪护师服务排名 (TOP10)</span></div>
|
||||
<div ref="caregiverChart" style="height:350px" v-loading="loading" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import echarts from 'echarts';
|
||||
import { getDashboard } from "@/api/system/finance";
|
||||
|
||||
const STATUS_MAP = {
|
||||
'-2': '已取消', '-1': '拒绝接单', '0': '待接单', '1': '已接单',
|
||||
'2': '已支付', '3': '服务中', '4': '已完成',
|
||||
'5': '申请退款', '6': '退款中', '7': '已退款', '8': '已结算'
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "Index",
|
||||
data() {
|
||||
return {
|
||||
version: "3.8.3",
|
||||
loading: false,
|
||||
dashboard: {},
|
||||
summary: { totalOrders: 0, todayOrders: 0, totalRevenue: 0, pendingOrders: 0 }
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.loadDashboard();
|
||||
},
|
||||
methods: {
|
||||
loadDashboard() {
|
||||
this.loading = true;
|
||||
getDashboard().then(response => {
|
||||
this.dashboard = response.data || response;
|
||||
this.calcSummary();
|
||||
this.loading = false;
|
||||
this.$nextTick(() => { this.renderAllCharts(); });
|
||||
}).catch(() => { this.loading = false; });
|
||||
},
|
||||
calcSummary() {
|
||||
const d = this.dashboard;
|
||||
// 订单总数
|
||||
const statusStats = d.orderStatusStats || [];
|
||||
this.summary.totalOrders = statusStats.reduce((s, r) => s + parseInt(r.cnt || 0), 0);
|
||||
// 今日订单
|
||||
const trend = d.dailyOrderTrend || [];
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
const todayItem = trend.find(r => (r.date || '').slice(0, 10) === today);
|
||||
this.summary.todayOrders = todayItem ? parseInt(todayItem.cnt || 0) : 0;
|
||||
// 总收入
|
||||
const revTrend = d.dailyRevenueTrend || [];
|
||||
this.summary.totalRevenue = revTrend.reduce((s, r) => s + parseFloat(r.revenue || 0), 0).toFixed(2);
|
||||
// 待处理: 待接单 + 申请退款
|
||||
const pending = statusStats.filter(r => ['0', '5'].includes(r.status));
|
||||
this.summary.pendingOrders = pending.reduce((s, r) => s + parseInt(r.cnt || 0), 0);
|
||||
},
|
||||
renderAllCharts() {
|
||||
this.renderOrderTrendChart();
|
||||
this.renderStatusPieChart();
|
||||
this.renderHospitalChart();
|
||||
this.renderCaregiverChart();
|
||||
},
|
||||
renderOrderTrendChart() {
|
||||
const el = this.$refs.orderTrendChart;
|
||||
if (!el) return;
|
||||
const chart = echarts.init(el);
|
||||
const orderTrend = this.dashboard.dailyOrderTrend || [];
|
||||
const revenueTrend = this.dashboard.dailyRevenueTrend || [];
|
||||
const dates = orderTrend.map(r => (r.date || '').slice(5));
|
||||
const orders = orderTrend.map(r => parseInt(r.cnt || 0));
|
||||
const revenues = orderTrend.map(r => {
|
||||
const rev = revenueTrend.find(v => v.date === r.date);
|
||||
return rev ? parseFloat(rev.revenue || 0) : 0;
|
||||
});
|
||||
chart.setOption({
|
||||
tooltip: { trigger: 'axis', axisPointer: { type: 'cross' } },
|
||||
legend: { data: ['订单数', '收入(元)'] },
|
||||
grid: { left: 50, right: 50, bottom: 30, top: 30 },
|
||||
xAxis: { type: 'category', data: dates },
|
||||
yAxis: [
|
||||
{ type: 'value', name: '订单数' },
|
||||
{ type: 'value', name: '收入(元)' }
|
||||
],
|
||||
series: [
|
||||
{ name: '订单数', type: 'bar', data: orders, itemStyle: { color: '#409EFF' } },
|
||||
{ name: '收入(元)', type: 'line', yAxisIndex: 1, data: revenues, smooth: true, itemStyle: { color: '#67C23A' } }
|
||||
]
|
||||
});
|
||||
window.addEventListener('resize', () => chart.resize());
|
||||
},
|
||||
renderStatusPieChart() {
|
||||
const el = this.$refs.statusPieChart;
|
||||
if (!el) return;
|
||||
const chart = echarts.init(el);
|
||||
const data = (this.dashboard.orderStatusStats || []).map(r => ({
|
||||
name: STATUS_MAP[r.status] || ('状态' + r.status),
|
||||
value: parseInt(r.cnt || 0)
|
||||
}));
|
||||
chart.setOption({
|
||||
tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
|
||||
series: [{
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
itemStyle: { borderRadius: 4, borderColor: '#fff', borderWidth: 2 },
|
||||
label: { show: true, formatter: '{b}\n{d}%' },
|
||||
data: data
|
||||
}]
|
||||
});
|
||||
window.addEventListener('resize', () => chart.resize());
|
||||
},
|
||||
renderHospitalChart() {
|
||||
const el = this.$refs.hospitalChart;
|
||||
if (!el) return;
|
||||
const chart = echarts.init(el);
|
||||
const data = this.dashboard.hospitalRanking || [];
|
||||
chart.setOption({
|
||||
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
||||
grid: { left: 100, right: 50, bottom: 30, top: 10 },
|
||||
xAxis: { type: 'value' },
|
||||
yAxis: { type: 'category', data: data.map(r => r.name).reverse(), inverse: true,
|
||||
axisLabel: { width: 90, overflow: 'truncate' } },
|
||||
series: [{
|
||||
type: 'bar',
|
||||
data: data.map(r => parseInt(r.cnt || 0)).reverse(),
|
||||
itemStyle: { color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#409EFF' }, { offset: 1, color: '#67C23A' }
|
||||
])}
|
||||
}]
|
||||
});
|
||||
window.addEventListener('resize', () => chart.resize());
|
||||
},
|
||||
renderCaregiverChart() {
|
||||
const el = this.$refs.caregiverChart;
|
||||
if (!el) return;
|
||||
const chart = echarts.init(el);
|
||||
const data = this.dashboard.caregiverRanking || [];
|
||||
chart.setOption({
|
||||
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
||||
grid: { left: 100, right: 50, bottom: 30, top: 10 },
|
||||
xAxis: { type: 'value' },
|
||||
yAxis: { type: 'category', data: data.map(r => r.name).reverse(), inverse: true,
|
||||
axisLabel: { width: 90, overflow: 'truncate' } },
|
||||
series: [{
|
||||
type: 'bar',
|
||||
data: data.map(r => parseInt(r.cnt || 0)).reverse(),
|
||||
itemStyle: { color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#E6A23C' }, { offset: 1, color: '#F56C6C' }
|
||||
])}
|
||||
}]
|
||||
});
|
||||
window.addEventListener('resize', () => chart.resize());
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.home {
|
||||
.mb20 { margin-bottom: 20px; }
|
||||
|
||||
.stat-card {
|
||||
display: flex; align-items: center; padding: 20px; border-radius: 8px;
|
||||
color: #fff; overflow: hidden; position: relative;
|
||||
.stat-icon {
|
||||
font-size: 48px; opacity: 0.3; margin-right: 16px;
|
||||
}
|
||||
.stat-body {
|
||||
.stat-value { font-size: 28px; font-weight: bold; line-height: 1.2; }
|
||||
.stat-label { font-size: 13px; margin-top: 4px; opacity: 0.85; }
|
||||
}
|
||||
}
|
||||
.card-total { background: linear-gradient(135deg, #667eea, #764ba2); }
|
||||
.card-today { background: linear-gradient(135deg, #43e97b, #38f9d7); }
|
||||
.card-revenue { background: linear-gradient(135deg, #f093fb, #f5576c); }
|
||||
.card-pending { background: linear-gradient(135deg, #fa709a, #fee140); }
|
||||
|
||||
hr { margin-top: 20px; margin-bottom: 20px; border: 0; border-top: 1px solid #eee; }
|
||||
h2 { margin-top: 10px; font-size: 26px; font-weight: 100; }
|
||||
p { margin-top: 10px; b { font-weight: 700; } }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -137,6 +137,22 @@ public class OrderViewController extends BaseController
|
||||
return AjaxResult.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 仪表盘统计数据
|
||||
*/
|
||||
@GetMapping("/dashboard")
|
||||
public AjaxResult dashboard()
|
||||
{
|
||||
java.util.Map<String, Object> result = new java.util.HashMap<>();
|
||||
result.put("orderStatusStats", iRlzOrderService.getOrderStatusStats());
|
||||
result.put("orderTypeStats", iRlzOrderService.getOrderTypeStats());
|
||||
result.put("dailyOrderTrend", iRlzOrderService.getDailyOrderTrend());
|
||||
result.put("dailyRevenueTrend", iRlzOrderService.getDailyRevenueTrend());
|
||||
result.put("hospitalRanking", iRlzOrderService.getHospitalRanking());
|
||||
result.put("caregiverRanking", iRlzOrderService.getCaregiverRanking());
|
||||
return AjaxResult.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取VIEW详细信息
|
||||
*/
|
||||
|
||||
@@ -63,4 +63,34 @@ public interface RlzOrderMapper
|
||||
* 收入统计
|
||||
*/
|
||||
public List<java.util.Map<String, Object>> getIncomeStats(java.util.Map<String, Object> params);
|
||||
|
||||
/**
|
||||
* 仪表盘-订单状态分布
|
||||
*/
|
||||
public List<java.util.Map<String, Object>> getOrderStatusStats();
|
||||
|
||||
/**
|
||||
* 仪表盘-服务类型分布
|
||||
*/
|
||||
public List<java.util.Map<String, Object>> getOrderTypeStats();
|
||||
|
||||
/**
|
||||
* 仪表盘-日订单趋势(近30天)
|
||||
*/
|
||||
public List<java.util.Map<String, Object>> getDailyOrderTrend();
|
||||
|
||||
/**
|
||||
* 仪表盘-日收入趋势(近30天)
|
||||
*/
|
||||
public List<java.util.Map<String, Object>> getDailyRevenueTrend();
|
||||
|
||||
/**
|
||||
* 仪表盘-医院服务量排名
|
||||
*/
|
||||
public List<java.util.Map<String, Object>> getHospitalRanking();
|
||||
|
||||
/**
|
||||
* 仪表盘-陪护服务排名
|
||||
*/
|
||||
public List<java.util.Map<String, Object>> getCaregiverRanking();
|
||||
}
|
||||
|
||||
@@ -78,4 +78,34 @@ public interface IRlzOrderService
|
||||
Long insertOrderPz(RlzOrder rlzOrder);
|
||||
|
||||
List<java.util.Map<String, Object>> getIncomeStats(java.util.Map<String, Object> params);
|
||||
|
||||
/**
|
||||
* 仪表盘-订单状态分布
|
||||
*/
|
||||
List<java.util.Map<String, Object>> getOrderStatusStats();
|
||||
|
||||
/**
|
||||
* 仪表盘-服务类型分布
|
||||
*/
|
||||
List<java.util.Map<String, Object>> getOrderTypeStats();
|
||||
|
||||
/**
|
||||
* 仪表盘-日订单趋势(近30天)
|
||||
*/
|
||||
List<java.util.Map<String, Object>> getDailyOrderTrend();
|
||||
|
||||
/**
|
||||
* 仪表盘-日收入趋势(近30天)
|
||||
*/
|
||||
List<java.util.Map<String, Object>> getDailyRevenueTrend();
|
||||
|
||||
/**
|
||||
* 仪表盘-医院服务量排名
|
||||
*/
|
||||
List<java.util.Map<String, Object>> getHospitalRanking();
|
||||
|
||||
/**
|
||||
* 仪表盘-陪护服务排名
|
||||
*/
|
||||
List<java.util.Map<String, Object>> getCaregiverRanking();
|
||||
}
|
||||
|
||||
@@ -349,4 +349,34 @@ public String weixinPayOrderNext(String decryptOrder) {
|
||||
public List<java.util.Map<String, Object>> getIncomeStats(java.util.Map<String, Object> params) {
|
||||
return rlzOrderMapper.getIncomeStats(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<java.util.Map<String, Object>> getOrderStatusStats() {
|
||||
return rlzOrderMapper.getOrderStatusStats();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<java.util.Map<String, Object>> getOrderTypeStats() {
|
||||
return rlzOrderMapper.getOrderTypeStats();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<java.util.Map<String, Object>> getDailyOrderTrend() {
|
||||
return rlzOrderMapper.getDailyOrderTrend();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<java.util.Map<String, Object>> getDailyRevenueTrend() {
|
||||
return rlzOrderMapper.getDailyRevenueTrend();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<java.util.Map<String, Object>> getHospitalRanking() {
|
||||
return rlzOrderMapper.getHospitalRanking();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<java.util.Map<String, Object>> getCaregiverRanking() {
|
||||
return rlzOrderMapper.getCaregiverRanking();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,6 +244,41 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
<select id="getOrderStatusStats" resultType="map">
|
||||
SELECT status, COUNT(*) as cnt FROM rlz_order GROUP BY status ORDER BY status
|
||||
</select>
|
||||
|
||||
<select id="getOrderTypeStats" resultType="map">
|
||||
SELECT COALESCE(NULLIF(yuliu10,''), '其他') as name, COUNT(*) as cnt
|
||||
FROM rlz_order GROUP BY yuliu10 ORDER BY cnt DESC
|
||||
</select>
|
||||
|
||||
<select id="getDailyOrderTrend" resultType="map">
|
||||
SELECT DATE(create_time) as date, COUNT(*) as cnt
|
||||
FROM rlz_order WHERE create_time >= DATE_SUB(NOW(), INTERVAL 30 DAY)
|
||||
GROUP BY DATE(create_time) ORDER BY date
|
||||
</select>
|
||||
|
||||
<select id="getDailyRevenueTrend" resultType="map">
|
||||
SELECT DATE(create_time) as date,
|
||||
COALESCE(SUM(CASE WHEN status IN ('4','8') THEN CAST(COALESCE(NULLIF(jiesuan_money,''), yugu_money) AS DECIMAL(10,2)) ELSE 0 END), 0) as revenue
|
||||
FROM rlz_order WHERE create_time >= DATE_SUB(NOW(), INTERVAL 30 DAY)
|
||||
GROUP BY DATE(create_time) ORDER BY date
|
||||
</select>
|
||||
|
||||
<select id="getHospitalRanking" resultType="map">
|
||||
SELECT COALESCE(h.hospitalname, '未指定') as name, COUNT(*) as cnt
|
||||
FROM rlz_order o LEFT JOIN sys_hospital h ON o.hospital_id = h.id
|
||||
GROUP BY o.hospital_id, h.hospitalname ORDER BY cnt DESC LIMIT 10
|
||||
</select>
|
||||
|
||||
<select id="getCaregiverRanking" resultType="map">
|
||||
SELECT COALESCE(ub.nick_name, CONCAT('用户#', o.b_id)) as name, COUNT(*) as cnt
|
||||
FROM rlz_order o LEFT JOIN sys_user ub ON o.b_id = ub.user_id
|
||||
WHERE o.b_id IS NOT NULL
|
||||
GROUP BY o.b_id, ub.nick_name ORDER BY cnt DESC LIMIT 10
|
||||
</select>
|
||||
|
||||
<select id="getIncomeStats" parameterType="map" resultType="map">
|
||||
SELECT
|
||||
<choose>
|
||||
|
||||
Reference in New Issue
Block a user