feat: add financial management module (#19)

- Add income stats API endpoint (GET /system/view/incomeStats)
- Create frontend pages: refund, settlement, income stats, transaction detail
- Add finance API module (refund approve, settlement, income stats)
- Update database menus for finance module paths
- Add jiaoyi_detail transaction list with jiaoyi_detail menu

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
renjianbo
2026-05-17 08:47:25 +08:00
parent 68f830c75c
commit 39a371b9ac
10 changed files with 770 additions and 1 deletions

View File

@@ -0,0 +1,71 @@
import request from '@/utils/request'
// ========== 退款管理 ==========
// 退款订单列表status=5:申请退款, 6:退款中)
export function listRefundOrders(query) {
return request({
url: '/system/view/adminList',
method: 'get',
params: { ...query, status: query.status || '5' }
})
}
// 退款审核(通过/拒绝)
export function refundApprove(orderId, data) {
return request({
url: '/system/view/refundApprove/' + orderId,
method: 'put',
data: data
})
}
// ========== 结算管理 ==========
// 待结算订单列表status=4:已完成, 8:已结算)
export function listSettlementOrders(query) {
return request({
url: '/system/view/adminList',
method: 'get',
params: { ...query, status: query.status || '4' }
})
}
// 订单结算
export function settleOrder(orderId, data) {
return request({
url: '/system/view/settleOrder/' + orderId,
method: 'put',
data: data
})
}
// ========== 收入统计 ==========
// 收入统计数据
export function getIncomeStats(params) {
return request({
url: '/system/view/incomeStats',
method: 'get',
params: params
})
}
// ========== 交易明细 ==========
// 交易明细列表
export function listJiaoyiDetail(query) {
return request({
url: '/system/detail/list',
method: 'get',
params: query
})
}
// 查询交易明细详细
export function getJiaoyiDetail(id) {
return request({
url: '/system/detail/' + id,
method: 'get'
})
}

View File

@@ -0,0 +1,97 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
<el-form-item label="交易类型" prop="type">
<el-select v-model="queryParams.type" placeholder="请选择类型" clearable>
<el-option label="全部" :value="null" />
<el-option label="收入" value="1" />
<el-option label="支出" value="2" />
</el-select>
</el-form-item>
<el-form-item label="交易名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入交易名称" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="detailList">
<el-table-column label="ID" align="center" prop="id" width="70" />
<el-table-column label="交易名称" align="center" prop="name" min-width="120" show-overflow-tooltip />
<el-table-column label="交易金额" align="center" prop="jine" width="110">
<template slot-scope="scope">
<span :style="{ color: scope.row.type == '1' ? '#67C23A' : '#F56C6C' }">
{{ scope.row.type == '1' ? '+' : '-' }}{{ scope.row.jine }}
</span>
</template>
</el-table-column>
<el-table-column label="类型" align="center" prop="type" width="80">
<template slot-scope="scope">
<el-tag :type="scope.row.type == '1' ? 'success' : 'danger'" size="small">
{{ scope.row.type == '1' ? '收入' : '支出' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="余额" align="center" prop="yue" width="110" />
<el-table-column label="用户ID" align="center" prop="userId" width="80" />
<el-table-column label="时间" align="center" prop="createTime" width="160" />
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
@pagination="getList" />
</div>
</template>
<script>
import { listJiaoyiDetail } from "@/api/system/finance";
export default {
name: "JiaoyiDetail",
data() {
return {
loading: true,
showSearch: true,
total: 0,
detailList: [],
queryParams: {
pageNum: 1, pageSize: 10,
type: null, name: null,
}
};
},
created() {
this.getList();
},
methods: {
getList() {
this.loading = true;
listJiaoyiDetail(this.queryParams).then(response => {
this.detailList = response.rows;
this.total = response.total;
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
handleExport() {
this.download('system/detail/export', { ...this.queryParams },
`jiaoyi_${new Date().getTime()}.xlsx`)
},
}
};
</script>

View File

@@ -0,0 +1,167 @@
<template>
<div class="app-container">
<el-row :gutter="20" class="mb8">
<el-col :span="6">
<el-date-picker v-model="dateRange" type="daterange" range-separator="至"
start-placeholder="开始日期" end-placeholder="结束日期" value-format="yyyy-MM-dd"
size="small" @change="loadData" />
</el-col>
<el-col :span="3">
<el-select v-model="groupBy" size="small" @change="loadData">
<el-option label="按日" value="day" />
<el-option label="按月" value="month" />
<el-option label="按年" value="year" />
</el-select>
</el-col>
</el-row>
<!-- 汇总卡片 -->
<el-row :gutter="20" class="mb20">
<el-col :span="6">
<div class="stat-card total">
<div class="stat-label">总收入</div>
<div class="stat-value">{{ summary.totalIncome || 0 }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="stat-card refund">
<div class="stat-label">退款总额</div>
<div class="stat-value">{{ summary.totalRefund || 0 }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="stat-card net">
<div class="stat-label">净收入</div>
<div class="stat-value">{{ summary.netIncome || 0 }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="stat-card orderCount">
<div class="stat-label">订单总数</div>
<div class="stat-value">{{ summary.totalOrders || 0 }}</div>
</div>
</el-col>
</el-row>
<!-- 图表 -->
<el-row :gutter="20">
<el-col :span="12">
<el-card>
<div slot="header"><span>收入趋势</span></div>
<div ref="incomeChart" style="height:350px" v-loading="loading" />
</el-card>
</el-col>
<el-col :span="12">
<el-card>
<div slot="header"><span>订单类别分布</span></div>
<div ref="categoryChart" style="height:350px" v-loading="loading" />
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import echarts from 'echarts';
import { getIncomeStats } from "@/api/system/finance";
export default {
name: "Income",
data() {
return {
loading: false,
dateRange: [],
groupBy: 'day',
summary: { totalIncome: 0, totalRefund: 0, netIncome: 0, totalOrders: 0 },
chartData: []
};
},
mounted() {
const end = new Date();
const start = new Date();
start.setDate(start.getDate() - 30);
this.dateRange = [
start.toISOString().slice(0, 10),
end.toISOString().slice(0, 10)
];
this.loadData();
},
methods: {
loadData() {
if (!this.dateRange || this.dateRange.length !== 2) return;
this.loading = true;
getIncomeStats({
startDate: this.dateRange[0],
endDate: this.dateRange[1],
groupBy: this.groupBy
}).then(response => {
const data = response.data || response;
this.summary = {
totalIncome: data.totalIncome || 0,
totalRefund: data.totalRefund || 0,
netIncome: (data.totalIncome || 0) - (data.totalRefund || 0),
totalOrders: data.totalOrders || 0
};
this.chartData = data.trend || data;
this.loading = false;
this.$nextTick(() => { this.renderCharts(); });
}).catch(() => { this.loading = false; });
},
renderCharts() {
this.renderIncomeChart();
this.renderCategoryChart();
},
renderIncomeChart() {
const el = this.$refs.incomeChart;
if (!el) return;
const chart = echarts.init(el);
const labels = (this.chartData || []).map(d => d.date || d.period || '');
const income = (this.chartData || []).map(d => parseFloat(d.income || d.totalIncome || 0));
const refund = (this.chartData || []).map(d => parseFloat(d.refund || d.totalRefund || 0));
chart.setOption({
tooltip: { trigger: 'axis' },
legend: { data: ['收入', '退款'] },
xAxis: { type: 'category', data: labels },
yAxis: { type: 'value' },
series: [
{ name: '收入', type: 'line', data: income, smooth: true, itemStyle: { color: '#67C23A' } },
{ name: '退款', type: 'line', data: refund, smooth: true, itemStyle: { color: '#F56C6C' } }
]
});
window.addEventListener('resize', () => chart.resize());
},
renderCategoryChart() {
const el = this.$refs.categoryChart;
if (!el) return;
const chart = echarts.init(el);
const cats = (this.chartData || []).reduce((acc, d) => {
const key = d.category || d.orderCategory || '其他';
acc[key] = (acc[key] || 0) + parseFloat(d.income || d.totalIncome || 0);
return acc;
}, {});
chart.setOption({
tooltip: { trigger: 'item' },
series: [{
type: 'pie',
radius: ['40%', '70%'],
data: Object.entries(cats).map(([name, value]) => ({ name, value }))
}]
});
window.addEventListener('resize', () => chart.resize());
}
}
};
</script>
<style scoped>
.stat-card {
padding: 20px; border-radius: 4px; color: #fff; text-align: center;
}
.stat-label { font-size: 14px; margin-bottom: 8px; }
.stat-value { font-size: 28px; font-weight: bold; }
.total { background: linear-gradient(135deg, #667eea, #764ba2); }
.refund { background: linear-gradient(135deg, #f093fb, #f5576c); }
.net { background: linear-gradient(135deg, #4facfe, #00f2fe); }
.orderCount { background: linear-gradient(135deg, #43e97b, #38f9d7); }
.mb20 { margin-bottom: 20px; }
</style>

View File

@@ -0,0 +1,191 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
<el-form-item label="订单编号" prop="orderNumber">
<el-input v-model="queryParams.orderNumber" placeholder="请输入订单编号" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="患者姓名" prop="usercName">
<el-input v-model="queryParams.usercName" placeholder="请输入患者姓名" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="退款状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
<el-option label="全部" :value="null" />
<el-option label="申请退款" value="5" />
<el-option label="退款中" value="6" />
<el-option label="已退款" value="7" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="orderList" @selection-change="handleSelectionChange">
<el-table-column label="订单编号" align="center" prop="orderNumber" min-width="160" />
<el-table-column label="患者" align="center" prop="usercName" width="100" />
<el-table-column label="患者电话" align="center" prop="usercPhonenumber" width="120" />
<el-table-column label="陪护" align="center" prop="userbName" width="100" />
<el-table-column label="金额" align="center" prop="jiesuanMoney" width="90">
<template slot-scope="scope">
{{ scope.row.jiesuanMoney || scope.row.yuguMoney || '-' }}
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status" width="100">
<template slot-scope="scope">
<el-tag :type="statusTagType(scope.row.status)" size="small">
{{ statusMap[scope.row.status] || scope.row.status }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="退款原因" align="center" prop="remark" min-width="150" show-overflow-tooltip />
<el-table-column label="申请时间" align="center" prop="createTime" width="160" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180">
<template slot-scope="scope">
<el-button size="mini" type="success" icon="el-icon-check" @click="handleApprove(scope.row)"
v-if="scope.row.status == '5' || scope.row.status == '6'">通过</el-button>
<el-button size="mini" type="danger" icon="el-icon-close" @click="handleReject(scope.row)"
v-if="scope.row.status == '5' || scope.row.status == '6'">拒绝</el-button>
<el-button size="mini" type="text" icon="el-icon-view" @click="handleDetail(scope.row)">详情</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
@pagination="getList" />
<!-- 退款审核对话框 -->
<el-dialog :title="dialogTitle" :visible.sync="dialogOpen" width="450px" append-to-body>
<el-form ref="approveForm" :model="approveForm" :rules="approveRules" label-width="80px">
<el-form-item label="审核结果">
<el-tag :type="approveForm.approved ? 'success' : 'danger'">
{{ approveForm.approved ? '同意退款' : '拒绝退款' }}
</el-tag>
</el-form-item>
<el-form-item label="备注" prop="reason">
<el-input v-model="approveForm.reason" type="textarea" :rows="3"
:placeholder="approveForm.approved ? '请输入通过理由' : '请输入拒绝原因'" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitApprove"> </el-button>
<el-button @click="dialogOpen = false"> </el-button>
</div>
</el-dialog>
<!-- 订单详情对话框 -->
<el-dialog title="订单详情" :visible.sync="detailOpen" width="650px" append-to-body>
<el-descriptions :column="2" border>
<el-descriptions-item label="订单编号">{{ detail.orderNumber }}</el-descriptions-item>
<el-descriptions-item label="订单状态">
<el-tag :type="statusTagType(detail.status)" size="small">
{{ statusMap[detail.status] || detail.status }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="患者姓名">{{ detail.usercName || '-' }}</el-descriptions-item>
<el-descriptions-item label="患者电话">{{ detail.usercPhonenumber || '-' }}</el-descriptions-item>
<el-descriptions-item label="陪护姓名">{{ detail.userbName || '-' }}</el-descriptions-item>
<el-descriptions-item label="陪护电话">{{ detail.userbPhonenumber || '-' }}</el-descriptions-item>
<el-descriptions-item label="医院">{{ detail.hospitalName || '-' }}</el-descriptions-item>
<el-descriptions-item label="金额">{{ detail.jiesuanMoney || detail.yuguMoney || '-' }}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{ detail.remark || '-' }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ detail.createTime }}</el-descriptions-item>
</el-descriptions>
</el-dialog>
</div>
</template>
<script>
import { listRefundOrders, refundApprove } from "@/api/system/finance";
import { getOrderInfo } from "@/api/system/order";
export default {
name: "Refund",
data() {
return {
loading: true,
ids: [],
showSearch: true,
total: 0,
orderList: [],
dialogOpen: false,
dialogTitle: "",
detailOpen: false,
detail: {},
approveForm: { orderId: null, approved: true, reason: '' },
approveRules: {
reason: [{ required: true, message: '请输入备注', trigger: 'blur' }]
},
statusMap: {
'5': '申请退款', '6': '退款中', '7': '已退款'
},
queryParams: {
pageNum: 1, pageSize: 10,
orderNumber: null, usercName: null, status: null,
}
};
},
created() {
this.getList();
},
methods: {
statusTagType(status) {
const map = { '5': 'warning', '6': 'warning', '7': 'info' };
return map[status] || '';
},
getList() {
this.loading = true;
listRefundOrders(this.queryParams).then(response => {
this.orderList = response.data;
this.total = response.total;
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
handleSelectionChange(selection) {
this.ids = selection.map(item => item.orderId);
},
handleApprove(row) {
this.approveForm = { orderId: row.orderId, approved: true, reason: '' };
this.dialogTitle = "审核通过退款";
this.dialogOpen = true;
},
handleReject(row) {
this.approveForm = { orderId: row.orderId, approved: false, reason: '' };
this.dialogTitle = "审核拒绝退款";
this.dialogOpen = true;
},
submitApprove() {
this.$refs["approveForm"].validate(valid => {
if (valid) {
refundApprove(this.approveForm.orderId, {
approved: this.approveForm.approved,
reason: this.approveForm.reason
}).then(() => {
this.$modal.msgSuccess(this.approveForm.approved ? "已同意退款" : "已拒绝退款");
this.dialogOpen = false;
this.getList();
});
}
});
},
handleDetail(row) {
getOrderInfo(row.orderId).then(response => {
this.detail = response.data;
this.detailOpen = true;
});
},
}
};
</script>

View File

@@ -0,0 +1,191 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
<el-form-item label="订单编号" prop="orderNumber">
<el-input v-model="queryParams.orderNumber" placeholder="请输入订单编号" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="陪护姓名" prop="userbName">
<el-input v-model="queryParams.userbName" placeholder="请输入陪护姓名" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="结算状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
<el-option label="全部" :value="null" />
<el-option label="待结算" value="4" />
<el-option label="已结算" value="8" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="orderList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="订单编号" align="center" prop="orderNumber" min-width="160" />
<el-table-column label="患者" align="center" prop="usercName" width="100" />
<el-table-column label="陪护" align="center" prop="userbName" width="100" />
<el-table-column label="医院" align="center" prop="hospitalName" min-width="120" show-overflow-tooltip />
<el-table-column label="结算金额" align="center" prop="jiesuanMoney" width="110">
<template slot-scope="scope">
{{ scope.row.jiesuanMoney || scope.row.yuguMoney || '-' }}
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status" width="90">
<template slot-scope="scope">
<el-tag :type="statusTagType(scope.row.status)" size="small">
{{ statusMap[scope.row.status] || scope.row.status }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="完成时间" align="center" prop="endTime" width="160" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="160">
<template slot-scope="scope">
<el-button size="mini" type="primary" icon="el-icon-bank-card" @click="handleSettle(scope.row)"
v-if="scope.row.status == '4'">结算</el-button>
<el-button size="mini" type="text" icon="el-icon-view" @click="handleDetail(scope.row)">详情</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
@pagination="getList" />
<!-- 结算对话框 -->
<el-dialog title="订单结算" :visible.sync="dialogOpen" width="450px" append-to-body>
<el-form ref="settleForm" :model="settleForm" :rules="settleRules" label-width="100px">
<el-form-item label="订单编号">
<span>{{ settleForm.orderNumber }}</span>
</el-form-item>
<el-form-item label="陪护姓名">
<span>{{ settleForm.userbName }}</span>
</el-form-item>
<el-form-item label="结算金额" prop="jiesuanMoney">
<el-input v-model="settleForm.jiesuanMoney" placeholder="请输入结算金额">
<template slot="append"></template>
</el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitSettle">确认结算</el-button>
<el-button @click="dialogOpen = false"> </el-button>
</div>
</el-dialog>
<!-- 详情对话框 -->
<el-dialog title="订单详情" :visible.sync="detailOpen" width="650px" append-to-body>
<el-descriptions :column="2" border>
<el-descriptions-item label="订单编号">{{ detail.orderNumber }}</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag :type="statusTagType(detail.status)" size="small">
{{ statusMap[detail.status] || detail.status }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="患者">{{ detail.usercName || '-' }}</el-descriptions-item>
<el-descriptions-item label="陪护">{{ detail.userbName || '-' }}</el-descriptions-item>
<el-descriptions-item label="医院">{{ detail.hospitalName || '-' }}</el-descriptions-item>
<el-descriptions-item label="结算金额">{{ detail.jiesuanMoney || detail.yuguMoney || '-' }}</el-descriptions-item>
<el-descriptions-item label="扣费">{{ detail.kouchuMoney || '-' }}</el-descriptions-item>
<el-descriptions-item label="返现">{{ detail.fanxianMoney || '-' }}</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">{{ detail.createTime }}</el-descriptions-item>
</el-descriptions>
</el-dialog>
</div>
</template>
<script>
import { listSettlementOrders, settleOrder } from "@/api/system/finance";
import { getOrderInfo } from "@/api/system/order";
export default {
name: "Settlement",
data() {
return {
loading: true,
ids: [],
showSearch: true,
total: 0,
orderList: [],
dialogOpen: false,
detailOpen: false,
detail: {},
settleForm: { orderId: null, orderNumber: '', userbName: '', jiesuanMoney: '' },
settleRules: {
jiesuanMoney: [{ required: true, message: '请输入结算金额', trigger: 'blur' }]
},
statusMap: { '4': '已完成', '8': '已结算' },
queryParams: {
pageNum: 1, pageSize: 10,
orderNumber: null, userbName: null, status: null,
}
};
},
created() {
this.getList();
},
methods: {
statusTagType(status) {
const map = { '4': 'success', '8': 'info' };
return map[status] || '';
},
getList() {
this.loading = true;
listSettlementOrders(this.queryParams).then(response => {
this.orderList = response.data;
this.total = response.total;
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
handleSelectionChange(selection) {
this.ids = selection.map(item => item.orderId);
},
handleSettle(row) {
this.settleForm = {
orderId: row.orderId,
orderNumber: row.orderNumber,
userbName: row.userbName,
jiesuanMoney: row.jiesuanMoney || row.yuguMoney || ''
};
this.dialogOpen = true;
},
submitSettle() {
this.$refs["settleForm"].validate(valid => {
if (valid) {
settleOrder(this.settleForm.orderId, {
jiesuanMoney: this.settleForm.jiesuanMoney
}).then(() => {
this.$modal.msgSuccess("结算成功");
this.dialogOpen = false;
this.getList();
});
}
});
},
handleDetail(row) {
getOrderInfo(row.orderId).then(response => {
this.detail = response.data;
this.detailOpen = true;
});
},
handleExport() {
this.download('system/view/export', { ...this.queryParams },
`settlement_${new Date().getTime()}.xlsx`)
},
}
};
</script>

View File

@@ -113,6 +113,30 @@ public class OrderViewController extends BaseController
util.exportExcel(response, list, "VIEW数据");
}
/**
* 收入统计
*/
@GetMapping("/incomeStats")
public AjaxResult incomeStats(java.util.Map<String, Object> params)
{
java.util.Map<String, Object> result = new java.util.HashMap<>();
java.util.List<java.util.Map<String, Object>> trend = iRlzOrderService.getIncomeStats(params);
double totalIncome = 0;
double totalRefund = 0;
long totalOrders = 0;
for (java.util.Map<String, Object> row : trend) {
totalIncome += ((Number) row.getOrDefault("income", 0)).doubleValue();
totalRefund += ((Number) row.getOrDefault("refund", 0)).doubleValue();
totalOrders += ((Number) row.getOrDefault("orderCount", 0)).longValue();
}
result.put("totalIncome", String.format("%.2f", totalIncome));
result.put("totalRefund", String.format("%.2f", totalRefund));
result.put("totalOrders", totalOrders);
result.put("trend", trend);
return AjaxResult.success(result);
}
/**
* 获取VIEW详细信息
*/

View File

@@ -58,4 +58,9 @@ public interface RlzOrderMapper
* @return 结果
*/
public int deleteRlzOrderByOrderIds(Long[] orderIds);
/**
* 收入统计
*/
public List<java.util.Map<String, Object>> getIncomeStats(java.util.Map<String, Object> params);
}

View File

@@ -76,4 +76,6 @@ public interface IRlzOrderService
public String refundOrder(CreateRequest request) throws NotFoundException, IOException, GeneralSecurityException, HttpCodeException;
Long insertOrderPz(RlzOrder rlzOrder);
List<java.util.Map<String, Object>> getIncomeStats(java.util.Map<String, Object> params);
}

View File

@@ -344,4 +344,9 @@ public String weixinPayOrderNext(String decryptOrder) {
private RequestBody createRequestBody(Object request) {
return new JsonRequestBody.Builder().body(toJson(request)).build();
}
@Override
public List<java.util.Map<String, Object>> getIncomeStats(java.util.Map<String, Object> params) {
return rlzOrderMapper.getIncomeStats(params);
}
}

View File

@@ -238,9 +238,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</delete>
<delete id="deleteRlzOrderByOrderIds" parameterType="String">
delete from rlz_order where order_id in
delete from rlz_order where order_id in
<foreach item="orderId" collection="array" open="(" separator="," close=")">
#{orderId}
</foreach>
</delete>
<select id="getIncomeStats" parameterType="map" resultType="map">
SELECT
<choose>
<when test="groupBy == 'year'">DATE_FORMAT(create_time, '%Y')</when>
<when test="groupBy == 'month'">DATE_FORMAT(create_time, '%Y-%m')</when>
<otherwise>DATE(create_time)</otherwise>
</choose> as date,
COUNT(*) as orderCount,
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 income,
COALESCE(SUM(CASE WHEN status = '7' THEN CAST(COALESCE(NULLIF(jiesuan_money,''), yugu_money) AS DECIMAL(10,2)) ELSE 0 END), 0) as refund
FROM rlz_order
WHERE create_time BETWEEN #{startDate} AND CONCAT(#{endDate}, ' 23:59:59')
GROUP BY date
ORDER BY date
</select>
</mapper>