2025-12-29 11:11:23 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="crm-sales-report-page">
|
2025-12-29 17:21:32 +08:00
|
|
|
|
<!-- 时间筛选区域(根页面保留,统一管理查询参数) -->
|
2025-12-29 11:11:23 +08:00
|
|
|
|
<div class="date-filter-container">
|
|
|
|
|
|
<el-form :model="dateQuery" inline class="date-form">
|
|
|
|
|
|
<el-form-item label="统计时间" prop="dateRange">
|
|
|
|
|
|
<el-date-picker
|
|
|
|
|
|
v-model="dateQuery.dateRange"
|
|
|
|
|
|
type="daterange"
|
|
|
|
|
|
range-separator="至"
|
|
|
|
|
|
start-placeholder="开始日期"
|
|
|
|
|
|
end-placeholder="结束日期"
|
|
|
|
|
|
format="yyyy-MM-dd"
|
|
|
|
|
|
value-format="yyyy-MM-dd"
|
|
|
|
|
|
clearable
|
|
|
|
|
|
></el-date-picker>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item>
|
|
|
|
|
|
<el-button type="primary" icon="el-icon-search" @click="queryAllData">查询</el-button>
|
|
|
|
|
|
<el-button icon="el-icon-refresh" @click="resetDateRange">重置</el-button>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-12-29 17:21:32 +08:00
|
|
|
|
<!-- 1. 销售汇总指标卡组件 -->
|
|
|
|
|
|
<SalesReportSummaryCard
|
|
|
|
|
|
:summary-data="summaryData"
|
|
|
|
|
|
:loading="summaryLoading"
|
|
|
|
|
|
:format-amount="formatAmount"
|
|
|
|
|
|
/>
|
2025-12-29 11:11:23 +08:00
|
|
|
|
|
2025-12-29 17:21:32 +08:00
|
|
|
|
<!-- 2. 三个ECharts图表组件(布局由根页面控制) -->
|
|
|
|
|
|
<div class="echarts-container">
|
2025-12-29 11:11:23 +08:00
|
|
|
|
<el-row :gutter="20">
|
|
|
|
|
|
<!-- 销售员统计图表 -->
|
|
|
|
|
|
<el-col :span="8">
|
2025-12-29 17:21:32 +08:00
|
|
|
|
<SalesmanChart :salesman-stat-list="salesmanStatList" />
|
2025-12-29 11:11:23 +08:00
|
|
|
|
</el-col>
|
|
|
|
|
|
<!-- 客户等级统计图表 -->
|
|
|
|
|
|
<el-col :span="8">
|
2025-12-29 17:21:32 +08:00
|
|
|
|
<CustomerLevelChart
|
|
|
|
|
|
:customer-level-stat-list="customerLevelStatList"
|
|
|
|
|
|
:customer-level-dict="dict.type.customer_level"
|
|
|
|
|
|
/>
|
2025-12-29 11:11:23 +08:00
|
|
|
|
</el-col>
|
|
|
|
|
|
<!-- 行业统计图表 -->
|
|
|
|
|
|
<el-col :span="8">
|
2025-12-29 17:21:32 +08:00
|
|
|
|
<IndustryChart :industry-stat-list="industryStatList" />
|
2025-12-29 11:11:23 +08:00
|
|
|
|
</el-col>
|
|
|
|
|
|
</el-row>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-12-29 17:21:32 +08:00
|
|
|
|
<!-- 3. 订单明细组件 -->
|
|
|
|
|
|
<SalesReportOrderDetail
|
|
|
|
|
|
:order-detail-list="orderDetailList"
|
|
|
|
|
|
:page-params="pageParams"
|
|
|
|
|
|
:total="total"
|
|
|
|
|
|
:loading="orderLoading"
|
|
|
|
|
|
:format-amount="formatAmount"
|
|
|
|
|
|
:format-date="formatDate"
|
|
|
|
|
|
@size-change="handleSizeChange"
|
|
|
|
|
|
@current-change="handleCurrentChange"
|
|
|
|
|
|
@export-order="exportOrderDetails"
|
|
|
|
|
|
@selection-change="handleSelectionChange"
|
|
|
|
|
|
/>
|
2025-12-29 11:11:23 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
2025-12-29 17:21:32 +08:00
|
|
|
|
// 导入子组件
|
|
|
|
|
|
import SalesReportSummaryCard from "./SalesReportSummaryCard.vue";
|
|
|
|
|
|
import SalesmanChart from "./SalesmanChart.vue";
|
|
|
|
|
|
import CustomerLevelChart from "./CustomerLevelChart.vue";
|
|
|
|
|
|
import IndustryChart from "./IndustryChart.vue";
|
|
|
|
|
|
import SalesReportOrderDetail from "./SalesReportOrderDetail.vue";
|
|
|
|
|
|
|
2025-12-29 11:11:23 +08:00
|
|
|
|
// 导入API函数
|
|
|
|
|
|
import {
|
|
|
|
|
|
getSummary,
|
|
|
|
|
|
getOrderDetails,
|
|
|
|
|
|
getSalesmanStats,
|
|
|
|
|
|
getCustomerLevelStats,
|
|
|
|
|
|
getIndustryStats,
|
2025-12-29 17:21:32 +08:00
|
|
|
|
exportOrderDetails as apiExportOrderDetails
|
2025-12-29 11:11:23 +08:00
|
|
|
|
} from "@/api/crm/report";
|
|
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
|
name: "CrmSalesReport",
|
|
|
|
|
|
dicts: ['customer_level'],
|
2025-12-29 17:21:32 +08:00
|
|
|
|
components: {
|
|
|
|
|
|
SalesReportSummaryCard,
|
|
|
|
|
|
SalesmanChart,
|
|
|
|
|
|
CustomerLevelChart,
|
|
|
|
|
|
IndustryChart,
|
|
|
|
|
|
SalesReportOrderDetail
|
|
|
|
|
|
},
|
2025-12-29 11:11:23 +08:00
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
2025-12-29 17:21:32 +08:00
|
|
|
|
// 时间查询参数
|
2025-12-29 11:11:23 +08:00
|
|
|
|
dateQuery: {
|
|
|
|
|
|
dateRange: []
|
|
|
|
|
|
},
|
|
|
|
|
|
// 分页参数
|
|
|
|
|
|
pageParams: {
|
|
|
|
|
|
pageNum: 1,
|
|
|
|
|
|
pageSize: 10
|
|
|
|
|
|
},
|
|
|
|
|
|
// 汇总数据
|
|
|
|
|
|
summaryData: {},
|
|
|
|
|
|
// 订单明细数据
|
|
|
|
|
|
orderDetailList: [],
|
|
|
|
|
|
// 总条数
|
|
|
|
|
|
total: 0,
|
2025-12-29 17:21:32 +08:00
|
|
|
|
// 图表数据(传递给对应图表组件)
|
2025-12-29 11:11:23 +08:00
|
|
|
|
salesmanStatList: [],
|
|
|
|
|
|
customerLevelStatList: [],
|
|
|
|
|
|
industryStatList: [],
|
|
|
|
|
|
// 加载状态
|
|
|
|
|
|
summaryLoading: false,
|
|
|
|
|
|
orderLoading: false,
|
|
|
|
|
|
// 表格勾选数据
|
2025-12-29 17:21:32 +08:00
|
|
|
|
multipleSelection: []
|
2025-12-29 11:11:23 +08:00
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
created() {
|
2025-12-29 17:21:32 +08:00
|
|
|
|
// 初始化默认时间
|
2025-12-29 11:11:23 +08:00
|
|
|
|
this.initDefaultDateRange();
|
2025-12-29 17:21:32 +08:00
|
|
|
|
// 页面加载查询所有数据
|
2025-12-29 11:11:23 +08:00
|
|
|
|
this.queryAllData();
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
// 初始化默认时间范围:当月第一天到今天
|
|
|
|
|
|
initDefaultDateRange() {
|
|
|
|
|
|
const now = new Date();
|
|
|
|
|
|
const monthFirstDay = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
|
|
|
|
const firstDayStr = this.formatToDateStr(monthFirstDay);
|
|
|
|
|
|
const todayStr = this.formatToDateStr(now);
|
|
|
|
|
|
this.dateQuery.dateRange = [firstDayStr, todayStr];
|
|
|
|
|
|
},
|
|
|
|
|
|
// 日期对象格式化为yyyy-MM-dd字符串
|
|
|
|
|
|
formatToDateStr(date) {
|
|
|
|
|
|
const year = date.getFullYear();
|
|
|
|
|
|
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
|
|
|
|
|
const day = date.getDate().toString().padStart(2, "0");
|
|
|
|
|
|
return `${year}-${month}-${day}`;
|
|
|
|
|
|
},
|
2025-12-29 17:21:32 +08:00
|
|
|
|
// 格式化金额
|
2025-12-29 11:11:23 +08:00
|
|
|
|
formatAmount(amount) {
|
|
|
|
|
|
if (!amount) {
|
|
|
|
|
|
return "0.00";
|
|
|
|
|
|
}
|
|
|
|
|
|
return Number(amount).toFixed(2);
|
|
|
|
|
|
},
|
2025-12-29 17:21:32 +08:00
|
|
|
|
// 格式化日期
|
2025-12-29 11:11:23 +08:00
|
|
|
|
formatDate(timestamp) {
|
|
|
|
|
|
if (!timestamp) return "-";
|
|
|
|
|
|
return timestamp;
|
|
|
|
|
|
},
|
|
|
|
|
|
// 重置日期范围
|
|
|
|
|
|
resetDateRange() {
|
|
|
|
|
|
this.initDefaultDateRange();
|
|
|
|
|
|
this.queryAllData();
|
|
|
|
|
|
},
|
2025-12-29 17:21:32 +08:00
|
|
|
|
// 统一查询所有数据
|
2025-12-29 11:11:23 +08:00
|
|
|
|
queryAllData() {
|
|
|
|
|
|
this.loadSummaryData();
|
|
|
|
|
|
this.loadChartData();
|
|
|
|
|
|
this.loadOrderDetailData();
|
|
|
|
|
|
},
|
2025-12-29 17:21:32 +08:00
|
|
|
|
// 组装公共查询参数
|
2025-12-29 11:11:23 +08:00
|
|
|
|
getCommonParams(needPage = false) {
|
|
|
|
|
|
const params = {};
|
|
|
|
|
|
if (this.dateQuery.dateRange && this.dateQuery.dateRange.length === 2) {
|
2025-12-29 13:10:47 +08:00
|
|
|
|
params.startTime = this.dateQuery.dateRange[0];
|
|
|
|
|
|
params.endTime = this.dateQuery.dateRange[1];
|
2025-12-29 11:11:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (needPage) {
|
|
|
|
|
|
params.pageNum = this.pageParams.pageNum;
|
|
|
|
|
|
params.pageSize = this.pageParams.pageSize;
|
|
|
|
|
|
}
|
|
|
|
|
|
return params;
|
|
|
|
|
|
},
|
2025-12-29 17:21:32 +08:00
|
|
|
|
// 加载销售汇总数据
|
2025-12-29 11:11:23 +08:00
|
|
|
|
loadSummaryData() {
|
|
|
|
|
|
this.summaryLoading = true;
|
|
|
|
|
|
const params = this.getCommonParams();
|
|
|
|
|
|
getSummary(params)
|
|
|
|
|
|
.then((res) => {
|
|
|
|
|
|
if (res.code === 200) {
|
|
|
|
|
|
this.summaryData = res.data || {};
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.finally(() => {
|
|
|
|
|
|
this.summaryLoading = false;
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
2025-12-29 17:21:32 +08:00
|
|
|
|
// 加载图表数据
|
2025-12-29 11:11:23 +08:00
|
|
|
|
loadChartData() {
|
|
|
|
|
|
const params = this.getCommonParams();
|
|
|
|
|
|
// 并行加载三个图表数据
|
|
|
|
|
|
Promise.all([
|
|
|
|
|
|
getSalesmanStats(params),
|
|
|
|
|
|
getCustomerLevelStats(params),
|
|
|
|
|
|
getIndustryStats(params)
|
|
|
|
|
|
])
|
|
|
|
|
|
.then(([salesmanRes, customerRes, industryRes]) => {
|
|
|
|
|
|
if (salesmanRes.code === 200) {
|
|
|
|
|
|
this.salesmanStatList = salesmanRes.data || [];
|
|
|
|
|
|
}
|
|
|
|
|
|
if (customerRes.code === 200) {
|
|
|
|
|
|
this.customerLevelStatList = customerRes.data || [];
|
|
|
|
|
|
}
|
|
|
|
|
|
if (industryRes.code === 200) {
|
|
|
|
|
|
this.industryStatList = industryRes.data || [];
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
2025-12-29 17:21:32 +08:00
|
|
|
|
// 加载订单明细数据
|
2025-12-29 11:11:23 +08:00
|
|
|
|
loadOrderDetailData() {
|
|
|
|
|
|
this.orderLoading = true;
|
2025-12-29 17:21:32 +08:00
|
|
|
|
const params = this.getCommonParams(true);
|
2025-12-29 11:11:23 +08:00
|
|
|
|
getOrderDetails(params)
|
|
|
|
|
|
.then((res) => {
|
|
|
|
|
|
if (res.code === 200) {
|
|
|
|
|
|
this.orderDetailList = res.rows || [];
|
|
|
|
|
|
this.total = res.total || 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.finally(() => {
|
|
|
|
|
|
this.orderLoading = false;
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
// 分页大小改变
|
|
|
|
|
|
handleSizeChange(val) {
|
|
|
|
|
|
this.pageParams.pageSize = val;
|
|
|
|
|
|
this.loadOrderDetailData();
|
|
|
|
|
|
},
|
|
|
|
|
|
// 当前页改变
|
|
|
|
|
|
handleCurrentChange(val) {
|
|
|
|
|
|
this.pageParams.pageNum = val;
|
|
|
|
|
|
this.loadOrderDetailData();
|
|
|
|
|
|
},
|
|
|
|
|
|
// 表格勾选事件
|
|
|
|
|
|
handleSelectionChange(val) {
|
|
|
|
|
|
this.multipleSelection = val;
|
|
|
|
|
|
},
|
|
|
|
|
|
// 导出订单明细
|
|
|
|
|
|
exportOrderDetails() {
|
|
|
|
|
|
const params = this.getCommonParams();
|
2025-12-29 17:21:32 +08:00
|
|
|
|
apiExportOrderDetails(params).then((res) => {
|
2025-12-29 11:11:23 +08:00
|
|
|
|
this.handleExportBlob(res, "销售报表订单明细.xlsx");
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
// 处理blob文件导出
|
|
|
|
|
|
handleExportBlob(res, fileName) {
|
|
|
|
|
|
const blob = new Blob([res], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
|
|
|
|
|
|
const url = window.URL.createObjectURL(blob);
|
|
|
|
|
|
const a = document.createElement("a");
|
|
|
|
|
|
a.href = url;
|
|
|
|
|
|
a.download = fileName;
|
|
|
|
|
|
document.body.appendChild(a);
|
|
|
|
|
|
a.click();
|
|
|
|
|
|
window.URL.revokeObjectURL(url);
|
|
|
|
|
|
document.body.removeChild(a);
|
|
|
|
|
|
this.$message.success("导出成功!");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.crm-sales-report-page {
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
background-color: #f5f7fa;
|
|
|
|
|
|
min-height: calc(100vh - 120px);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 时间筛选区域样式 */
|
|
|
|
|
|
.date-filter-container {
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.04);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.date-form {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-29 17:21:32 +08:00
|
|
|
|
/* 图表容器布局样式 */
|
2025-12-29 11:11:23 +08:00
|
|
|
|
.echarts-container {
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|