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:
71
rlz-ui/src/api/system/finance.js
Normal file
71
rlz-ui/src/api/system/finance.js
Normal 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'
|
||||
})
|
||||
}
|
||||
97
rlz-ui/src/views/finance/detail/index.vue
Normal file
97
rlz-ui/src/views/finance/detail/index.vue
Normal 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>
|
||||
167
rlz-ui/src/views/finance/income/index.vue
Normal file
167
rlz-ui/src/views/finance/income/index.vue
Normal 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>
|
||||
191
rlz-ui/src/views/finance/refund/index.vue
Normal file
191
rlz-ui/src/views/finance/refund/index.vue
Normal 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>
|
||||
191
rlz-ui/src/views/finance/settlement/index.vue
Normal file
191
rlz-ui/src/views/finance/settlement/index.vue
Normal 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>
|
||||
@@ -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详细信息
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user