feat(crm): 新增销售报表功能并优化订单异议处理
- 新增销售报表功能,包括汇总数据展示、图表统计和订单明细 - 优化订单异议处理流程,增加处理状态显示和操作按钮条件渲染 - 重构订单状态枚举导入和使用方式 - 移除不必要的查询条件和冗余代码 - 修复部分UI样式问题
This commit is contained in:
116
klp-ui/src/api/crm/report.js
Normal file
116
klp-ui/src/api/crm/report.js
Normal file
@@ -0,0 +1,116 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 查询销售报表汇总数据
|
||||
*/
|
||||
export function getSummary(query) {
|
||||
return request({
|
||||
url: '/crm/salesReport/summary',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询销售报表订单明细
|
||||
*/
|
||||
export function getOrderDetails(query) {
|
||||
return request({
|
||||
url: '/crm/salesReport/orderDetails',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询完整销售报表数据
|
||||
*/
|
||||
export function getFullSalesReport(query) {
|
||||
return request({
|
||||
url: '/crm/salesReport/fullReport',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询销售员统计数据
|
||||
*/
|
||||
export function getSalesmanStats(query) {
|
||||
return request({
|
||||
url: '/crm/salesReport/salesmanStats',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询客户等级统计数据
|
||||
*/
|
||||
export function getCustomerLevelStats(query) {
|
||||
return request({
|
||||
url: '/crm/salesReport/customerLevelStats',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询行业统计数据
|
||||
*/
|
||||
export function getIndustryStats(query) {
|
||||
return request({
|
||||
url: '/crm/salesReport/industryStats',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出销售报表订单明细
|
||||
*/
|
||||
export function exportOrderDetails(query) {
|
||||
return request({
|
||||
url: '/crm/salesReport/exportOrderDetails',
|
||||
method: 'post',
|
||||
data: query,
|
||||
// 导出文件需指定响应类型(可选,根据项目ExcelUtil配置调整)
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出销售员统计数据
|
||||
*/
|
||||
export function exportSalesmanStats(query) {
|
||||
return request({
|
||||
url: '/crm/salesReport/exportSalesmanStats',
|
||||
method: 'post',
|
||||
data: query,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出客户等级统计数据
|
||||
*/
|
||||
export function exportCustomerLevelStats(query) {
|
||||
return request({
|
||||
url: '/crm/salesReport/exportCustomerLevelStats',
|
||||
method: 'post',
|
||||
data: query,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出行业统计数据
|
||||
*/
|
||||
export function exportIndustryStats(query) {
|
||||
return request({
|
||||
url: '/crm/salesReport/exportIndustryStats',
|
||||
method: 'post',
|
||||
data: query,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
@@ -39,6 +39,15 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="订单状态" prop="orderStatus">
|
||||
<el-select v-model="form.orderStatus" placeholder="请选择订单状态">
|
||||
<el-option v-for="(value, label) in ORDER_STATUS" :key="value" :label="label"
|
||||
:value="value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" placeholder="请输入备注" type="textarea" :rows="4" />
|
||||
@@ -50,6 +59,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ORDER_STATUS } from '../js/enum'
|
||||
|
||||
export default {
|
||||
name: 'OrderEdit',
|
||||
props: {
|
||||
@@ -64,6 +75,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ORDER_STATUS,
|
||||
form: {
|
||||
...this.initValue
|
||||
},
|
||||
|
||||
@@ -2,20 +2,11 @@
|
||||
<div>
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item label="异议编号" prop="objectionCode">
|
||||
<el-input
|
||||
v-model="queryParams.objectionCode"
|
||||
placeholder="请输入异议编号"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
<el-input v-model="queryParams.objectionCode" placeholder="请输入异议编号" clearable
|
||||
@keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="处理人" prop="handleUser">
|
||||
<el-input
|
||||
v-model="queryParams.handleUser"
|
||||
placeholder="请输入处理人"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
<el-input v-model="queryParams.handleUser" 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>
|
||||
@@ -25,22 +16,10 @@
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="el-icon-plus"
|
||||
size="mini"
|
||||
@click="handleAdd"
|
||||
>新增</el-button>
|
||||
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="el-icon-download"
|
||||
size="mini"
|
||||
@click="handleExport"
|
||||
>导出</el-button>
|
||||
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
@@ -71,41 +50,24 @@
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-edit"
|
||||
@click="handleUpdate(scope.row)"
|
||||
></el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-check"
|
||||
@click="handleDo(scope.row)"
|
||||
></el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"></el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-check" @click="handleDo(scope.row)"
|
||||
v-if="scope.row.objectionStatus == 0"></el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row)"
|
||||
v-if="scope.row.objectionStatus == 1"></el-button>
|
||||
<!-- <el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-check"
|
||||
@click="handleFinish(scope.row)"
|
||||
>结案</el-button> -->
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
></el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(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"
|
||||
/>
|
||||
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
|
||||
@pagination="getList" />
|
||||
|
||||
<!-- 添加或修改销售异议管理对话框 -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
|
||||
@@ -114,7 +76,7 @@
|
||||
<el-input v-model="form.objectionCode" placeholder="请输入异议编号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="异议内容">
|
||||
<editor v-model="form.objectionContent" :min-height="192"/>
|
||||
<editor v-model="form.objectionContent" :min-height="192" />
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="处理内容">
|
||||
<editor v-model="form.handleContent" :min-height="192"/>
|
||||
@@ -147,6 +109,42 @@
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="处理订单" :visible.sync="checkOpen" width="500px" append-to-body>
|
||||
<el-form ref="form" :model="checkForm" :rules="rules" label-width="80px">
|
||||
<el-form-item label="处理内容">
|
||||
<editor v-model="checkForm.handleContent" :min-height="192" />
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="处理人" prop="handleUser">
|
||||
<el-input v-model="form.handleUser" placeholder="请输入处理人" />
|
||||
</el-form-item>
|
||||
<el-form-item label="处理时间" prop="handleTime">
|
||||
<el-date-picker clearable
|
||||
v-model="form.handleTime"
|
||||
type="datetime"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
placeholder="请选择处理时间">
|
||||
</el-date-picker>
|
||||
</el-form-item> -->
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitCheckForm">确 定</el-button>
|
||||
<el-button @click="checkOpen = false">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="查看处理结果" :visible.sync="viewOpen" width="500px" append-to-body>
|
||||
<el-descriptions :column="2" :data="viewForm" label-width="80px">
|
||||
<el-descriptions-item label="处理人">{{ viewForm.handleUser }}</el-descriptions-item>
|
||||
<el-descriptions-item label="处理时间">{{ viewForm.handleTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="处理内容" :span="2">
|
||||
<div v-html="viewForm.handleContent"></div>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="viewOpen = false">关 闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -158,7 +156,7 @@ export default {
|
||||
props: {
|
||||
order: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
default: () => { }
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -168,6 +166,9 @@ export default {
|
||||
orderId() {
|
||||
console.log(this.order, 'order')
|
||||
return this.order.orderId
|
||||
},
|
||||
currentUserName() {
|
||||
return this.$store.getters.name;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -223,7 +224,13 @@ export default {
|
||||
form: {},
|
||||
// 表单校验
|
||||
rules: {
|
||||
}
|
||||
},
|
||||
checkForm: {
|
||||
handleContent: undefined,
|
||||
},
|
||||
checkOpen: false,
|
||||
viewForm: {},
|
||||
viewOpen: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
@@ -241,6 +248,10 @@ export default {
|
||||
this.open = false;
|
||||
this.reset();
|
||||
},
|
||||
handleView(row) {
|
||||
this.viewForm = row;
|
||||
this.viewOpen = true;
|
||||
},
|
||||
// 表单重置
|
||||
reset() {
|
||||
this.form = {
|
||||
@@ -277,11 +288,13 @@ export default {
|
||||
// 多选框选中数据
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.objectionId)
|
||||
this.single = selection.length!==1
|
||||
this.single = selection.length !== 1
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
handleDo(row) {
|
||||
console.log(row, '处理')
|
||||
this.checkForm = row;
|
||||
this.checkOpen = true;
|
||||
},
|
||||
/** 新增按钮操作 */
|
||||
handleAdd() {
|
||||
@@ -326,6 +339,22 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
/** 提交处理按钮 */
|
||||
submitCheckForm() {
|
||||
this.buttonLoading = true;
|
||||
updateSalesObjection({
|
||||
...this.checkForm,
|
||||
handleUser: this.currentUserName,
|
||||
handleTime: this.parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}'),
|
||||
objectionStatus: 1,
|
||||
}).then(response => {
|
||||
this.$modal.msgSuccess("处理成功");
|
||||
this.checkOpen = false;
|
||||
this.getList();
|
||||
}).finally(() => {
|
||||
this.buttonLoading = false;
|
||||
});
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const objectionIds = row.objectionId || this.ids;
|
||||
|
||||
@@ -1,170 +1,116 @@
|
||||
import { addOrder, updateOrder } from '@/api/crm/order'
|
||||
import { addOrderItem, updateOrderItem } from '@/api/crm/orderItem'
|
||||
import { ORDER_STATUS, PRE_ORDER_STATUS, ORDER_TYPE } from './enum'
|
||||
import { addOrderOperationTrace } from '@/api/crm/orderOperationTrace'
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 需要被记录的操作
|
||||
const ORDER_ACTIONS = {
|
||||
createPreOrder: 'createPreOrder',
|
||||
updatePreOrder: 'updatePreOrder',
|
||||
approvePreOrder: 'approvePreOrder',
|
||||
createOrder: 'createOrder',
|
||||
updateOrder: 'updateOrder',
|
||||
createOrderdetail: 'createOrderdetail',
|
||||
updateOrderdetail: 'updateOrderdetail',
|
||||
/**
|
||||
* 查询销售报表汇总数据
|
||||
*/
|
||||
export function getSummary(query) {
|
||||
return request({
|
||||
url: '/crm/salesReport/summary',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
const actions = {
|
||||
// 创建预订单
|
||||
createPreOrder: {
|
||||
type: 'createPreOrder',
|
||||
name: '创建预订单',
|
||||
description: '创建一个预订单',
|
||||
// 预订单的相关信息
|
||||
async handler(payload) {
|
||||
const { data: order } = await addOrder({
|
||||
...payload,
|
||||
})
|
||||
if (order.orderId) {
|
||||
addOrderOperationTrace({
|
||||
orderId: order.orderId,
|
||||
operationType: ORDER_ACTIONS.createPreOrder,
|
||||
newStatus: order.orderId,
|
||||
operationContent: JSON.stringify(payload)
|
||||
})
|
||||
}
|
||||
return order
|
||||
}
|
||||
},
|
||||
// 修改预订单
|
||||
updatePreOrder: {
|
||||
type: 'updatePreOrder',
|
||||
name: '修改预订单',
|
||||
description: '修改预订单的相关信息',
|
||||
// 预订单的相关信息
|
||||
async handler(payload) {
|
||||
await updateOrder({
|
||||
...payload,
|
||||
})
|
||||
|
||||
addOrderOperationTrace({
|
||||
orderId: payload.orderId,
|
||||
operationType: ORDER_ACTIONS.updatePreOrder,
|
||||
newStatus: payload.orderId,
|
||||
operationContent: JSON.stringify(payload)
|
||||
})
|
||||
|
||||
return payload
|
||||
}
|
||||
},
|
||||
// 创建订单明细
|
||||
createOrderdetail: {
|
||||
type: 'createOrderdetail',
|
||||
name: '创建订单明细',
|
||||
description: '创建订单明细的相关信息',
|
||||
// 订单明细的相关信息
|
||||
async handler(payload) {
|
||||
const { data: orderItem } = await addOrderItem({
|
||||
...payload,
|
||||
})
|
||||
if (orderItem.orderItemId) {
|
||||
addOrderOperationTrace({
|
||||
orderId: orderItem.orderId,
|
||||
operationType: ORDER_ACTIONS.createOrderdetail,
|
||||
newStatus: payload.orderItemId,
|
||||
operationContent: JSON.stringify(payload)
|
||||
})
|
||||
}
|
||||
return orderItem
|
||||
}
|
||||
},
|
||||
// 修改订单明细
|
||||
updateOrderdetail: {
|
||||
type: 'updateOrderdetail',
|
||||
name: '修改订单明细',
|
||||
description: '修改订单明细的相关信息',
|
||||
// 订单明细的相关信息
|
||||
async handler(payload) {
|
||||
await updateOrderItem({
|
||||
...payload,
|
||||
})
|
||||
addOrderOperationTrace({
|
||||
orderId: payload.orderId,
|
||||
operationType: ORDER_ACTIONS.updateOrderdetail,
|
||||
newStatus: payload.orderItemId,
|
||||
operationContent: JSON.stringify(payload)
|
||||
})
|
||||
|
||||
return payload
|
||||
}
|
||||
},
|
||||
|
||||
// 预订单审批为正式订单
|
||||
approvePreOrder: {
|
||||
type: 'approvePreOrder',
|
||||
name: '审批预订单',
|
||||
description: '审批预订单为正式订单',
|
||||
// 预订单的相关信息
|
||||
async handler(payload) {
|
||||
await updateOrder({
|
||||
...payload,
|
||||
})
|
||||
if (payload.orderId) {
|
||||
addOrderOperationTrace({
|
||||
orderId: payload.orderId,
|
||||
operationType: ORDER_ACTIONS.approvePreOrder,
|
||||
newStatus: payload.orderId,
|
||||
operationContent: JSON.stringify(payload)
|
||||
})
|
||||
}
|
||||
return payload
|
||||
}
|
||||
},
|
||||
// 直接创建正式订单
|
||||
createOrder: {
|
||||
type: 'createOrder',
|
||||
name: '创建正式订单',
|
||||
description: '直接创建一个正式订单',
|
||||
// 正式订单的相关信息
|
||||
async handler(payload) {
|
||||
const { data: order } = await addOrder({
|
||||
...payload,
|
||||
})
|
||||
if (order.orderId) {
|
||||
addOrderOperationTrace({
|
||||
orderId: order.orderId,
|
||||
operationType: ORDER_ACTIONS.createOrder,
|
||||
newStatus: order.orderId,
|
||||
operationContent: JSON.stringify(payload)
|
||||
})
|
||||
}
|
||||
return order
|
||||
}
|
||||
},
|
||||
// 正式订单修改
|
||||
updateOrder: {
|
||||
type: 'updateOrder',
|
||||
name: '修改正式订单',
|
||||
description: '修改正式订单的相关信息',
|
||||
// 正式订单的相关信息
|
||||
async handler(payload) {
|
||||
await updateOrder({
|
||||
...payload,
|
||||
})
|
||||
if (payload.orderId) {
|
||||
addOrderOperationTrace({
|
||||
orderId: payload.orderId,
|
||||
operationType: ORDER_ACTIONS.updateOrder,
|
||||
newStatus: payload.orderId,
|
||||
operationContent: JSON.stringify(payload)
|
||||
})
|
||||
}
|
||||
return payload
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 分页查询销售报表订单明细
|
||||
*/
|
||||
export function getOrderDetails(query) {
|
||||
return request({
|
||||
url: '/crm/salesReport/orderDetails',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export {
|
||||
ORDER_ACTIONS,
|
||||
actions,
|
||||
/**
|
||||
* 查询完整销售报表数据
|
||||
*/
|
||||
export function getFullSalesReport(query) {
|
||||
return request({
|
||||
url: '/crm/salesReport/fullReport',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询销售员统计数据
|
||||
*/
|
||||
export function getSalesmanStats(query) {
|
||||
return request({
|
||||
url: '/crm/salesReport/salesmanStats',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询客户等级统计数据
|
||||
*/
|
||||
export function getCustomerLevelStats(query) {
|
||||
return request({
|
||||
url: '/crm/salesReport/customerLevelStats',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询行业统计数据
|
||||
*/
|
||||
export function getIndustryStats(query) {
|
||||
return request({
|
||||
url: '/crm/salesReport/industryStats',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出销售报表订单明细
|
||||
*/
|
||||
export function exportOrderDetails(query) {
|
||||
return request({
|
||||
url: '/crm/salesReport/exportOrderDetails',
|
||||
method: 'post',
|
||||
data: query,
|
||||
// 导出文件需指定响应类型(可选,根据项目ExcelUtil配置调整)
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出销售员统计数据
|
||||
*/
|
||||
export function exportSalesmanStats(query) {
|
||||
return request({
|
||||
url: '/crm/salesReport/exportSalesmanStats',
|
||||
method: 'post',
|
||||
data: query,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出客户等级统计数据
|
||||
*/
|
||||
export function exportCustomerLevelStats(query) {
|
||||
return request({
|
||||
url: '/crm/salesReport/exportCustomerLevelStats',
|
||||
method: 'post',
|
||||
data: query,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出行业统计数据
|
||||
*/
|
||||
export function exportIndustryStats(query) {
|
||||
return request({
|
||||
url: '/crm/salesReport/exportIndustryStats',
|
||||
method: 'post',
|
||||
data: query,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
@@ -14,18 +14,18 @@
|
||||
style="display: flex; align-items: center; gap: 5px; margin-top: 10px; flex-wrap: wrap;">
|
||||
<!-- 查询区,通过上方的查询按钮控制显示隐藏 -->
|
||||
<!-- 客户行业和客户等级的下拉选 -->
|
||||
<el-select style="width: 100px;" v-model="queryParams.customerId" placeholder="客户" clearable>
|
||||
<!-- <el-select style="width: 100px;" v-model="queryParams.customerId" placeholder="客户" clearable>
|
||||
<el-option v-for="item in dict.type.customer_industry" :key="item.value" :label="item.label"
|
||||
:value="item.value" />
|
||||
</el-select>
|
||||
</el-select> -->
|
||||
<el-input style="width: 100px;" v-model="queryParams.salesman" placeholder="销售员" clearable />
|
||||
<el-select style="width: 100px;" v-model="queryParams.orderStatus" placeholder="订单状态" clearable>
|
||||
<el-option v-for="(value, key) in ORDER_STATUS" :key="value" :label="key" :value="value" />
|
||||
</el-select>
|
||||
<el-select style="width: 100px;" v-model="queryParams.financeStatus" placeholder="财务状态" clearable>
|
||||
<!-- <el-select style="width: 100px;" v-model="queryParams.financeStatus" placeholder="财务状态" clearable>
|
||||
<el-option v-for="item in dict.type.finance_status" :key="item.value" :label="item.label"
|
||||
:value="item.value" />
|
||||
</el-select>
|
||||
</el-select> -->
|
||||
</div>
|
||||
<div>
|
||||
<!-- 列表区域 -->
|
||||
@@ -126,9 +126,10 @@
|
||||
|
||||
<script>
|
||||
import KLPList from '@/components/KLPUI/KLPList/index.vue'
|
||||
import { listOrder, addOrder, delOrder, updateOrder } from "@/api/crm/order";
|
||||
import { listOrder, delOrder } from "@/api/crm/order";
|
||||
import { listCustomer } from "@/api/crm/customer";
|
||||
import { ORDER_STATUS, ORDER_TYPE, ORDER_ACTIONS, actions } from '../js/enum'
|
||||
import { ORDER_STATUS, ORDER_TYPE } from '../js/enum'
|
||||
import { ORDER_ACTIONS, actions } from '../js/actions'
|
||||
import OrderDetail from '../components/OrderDetail.vue';
|
||||
import OrderEdit from '../components/OrderEdit.vue';
|
||||
import OrderObjection from '../components/OrderObjection.vue';
|
||||
|
||||
791
klp-ui/src/views/crm/report/index.vue
Normal file
791
klp-ui/src/views/crm/report/index.vue
Normal file
@@ -0,0 +1,791 @@
|
||||
<template>
|
||||
<div class="crm-sales-report-page">
|
||||
<!-- 1. 时间筛选区域(单独一行,默认当月第一天到今天) -->
|
||||
<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>
|
||||
|
||||
<!-- 2. 统计区域(销售汇总关键指标) -->
|
||||
<div class="summary-stat-container" v-loading="summaryLoading">
|
||||
<el-card class="summary-card" shadow="hover">
|
||||
<div class="summary-list">
|
||||
<div class="summary-item">
|
||||
<span class="item-label">总订单数</span>
|
||||
<span class="item-value">{{ summaryData.totalOrderCount || 0 }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="item-label">总销售金额(元)</span>
|
||||
<span class="item-value">{{ formatAmount(summaryData.totalSalesAmount) }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="item-label">已完成订单数</span>
|
||||
<span class="item-value">{{ summaryData.completedOrderCount || 0 }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="item-label">已完成销售金额(元)</span>
|
||||
<span class="item-value">{{ formatAmount(summaryData.completedSalesAmount) }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="item-label">未结款总金额(元)</span>
|
||||
<span class="item-value">{{ formatAmount(summaryData.totalUnpaidAmount) }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="item-label">平均订单金额(元)</span>
|
||||
<span class="item-value">{{ formatAmount(summaryData.avgOrderAmount) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 3. ECharts图表区域(销售员+客户等级+行业统计) -->
|
||||
<div class="echarts-container" v-loading="chartLoading">
|
||||
<el-row :gutter="20">
|
||||
<!-- 销售员统计图表 -->
|
||||
<el-col :span="8">
|
||||
<el-card class="echarts-card" shadow="hover">
|
||||
<div slot="header" class="card-header">
|
||||
<span>销售员统计</span>
|
||||
</div>
|
||||
<!-- 确保DOM元素唯一且存在 -->
|
||||
<div ref="salesmanChartRef" class="chart-item"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<!-- 客户等级统计图表 -->
|
||||
<el-col :span="8">
|
||||
<el-card class="echarts-card" shadow="hover">
|
||||
<div slot="header" class="card-header">
|
||||
<span>客户等级统计</span>
|
||||
</div>
|
||||
<div ref="customerLevelChartRef" class="chart-item"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<!-- 行业统计图表 -->
|
||||
<el-col :span="8">
|
||||
<el-card class="echarts-card" shadow="hover">
|
||||
<div slot="header" class="card-header">
|
||||
<span>行业统计</span>
|
||||
</div>
|
||||
<div ref="industryChartRef" class="chart-item"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 4. 订单明细表格区域 -->
|
||||
<div class="order-detail-container" v-loading="orderLoading">
|
||||
<div class="table-toolbar">
|
||||
<el-button
|
||||
type="success"
|
||||
icon="el-icon-download"
|
||||
@click="exportOrderDetails"
|
||||
v-hasPermi="['crm:salesReport:export']"
|
||||
>
|
||||
导出订单明细
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
:data="orderDetailList"
|
||||
border
|
||||
style="width: 100%;"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column prop="orderCode" label="订单编号" min-width="120"></el-table-column>
|
||||
<el-table-column prop="customerCode" label="客户编码" min-width="100"></el-table-column>
|
||||
<el-table-column prop="companyName" label="公司名称" min-width="150"></el-table-column>
|
||||
<el-table-column prop="contactPerson" label="联系人" min-width="80"></el-table-column>
|
||||
<el-table-column prop="customerLevel" label="客户等级" min-width="100"></el-table-column>
|
||||
<el-table-column prop="industry" label="所属行业"></el-table-column>
|
||||
<el-table-column prop="orderAmount" label="订单金额">
|
||||
<template #default="scope">
|
||||
{{ formatAmount(scope.row.orderAmount) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="salesman" label="销售员"></el-table-column>
|
||||
<el-table-column prop="deliveryDate" label="交货日期" min-width="100">
|
||||
<template #default="scope">
|
||||
{{ scope.row.deliveryDate ? formatDate(scope.row.deliveryDate) : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="orderStatus" label="订单状态">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.orderStatus === 1" type="success">已完成</el-tag>
|
||||
<el-tag v-else-if="scope.row.orderStatus === 0" type="warning">待处理</el-tag>
|
||||
<el-tag v-else type="danger">已取消</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="financeStatus" label="财务状态">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.financeStatus === 1" type="success">已结款</el-tag>
|
||||
<el-tag v-else type="danger">未结款</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="unpaidAmount" label="未结金额">
|
||||
<template #default="scope">
|
||||
{{ formatAmount(scope.row.unpaidAmount) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间">
|
||||
<template #default="scope">
|
||||
{{ scope.row.createTime ? formatDateTime(scope.row.createTime) : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="itemCount" label="明细数量"></el-table-column>
|
||||
<el-table-column prop="objectionCount" label="异议数量"></el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<el-pagination
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="pageParams.pageNum"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:page-size="pageParams.pageSize"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
style="margin-top: 20px; text-align: right;"
|
||||
></el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 导入API函数
|
||||
import {
|
||||
getSummary,
|
||||
getOrderDetails,
|
||||
getSalesmanStats,
|
||||
getCustomerLevelStats,
|
||||
getIndustryStats,
|
||||
exportOrderDetails
|
||||
} from "@/api/crm/report";
|
||||
// 导入ECharts
|
||||
import * as echarts from "echarts";
|
||||
|
||||
export default {
|
||||
name: "CrmSalesReport",
|
||||
dicts: ['customer_level'],
|
||||
data() {
|
||||
return {
|
||||
// 时间查询参数(单独抽离,默认当月第一天到今天)
|
||||
dateQuery: {
|
||||
dateRange: []
|
||||
},
|
||||
// 分页参数
|
||||
pageParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10
|
||||
},
|
||||
// 汇总数据
|
||||
summaryData: {},
|
||||
// 订单明细数据
|
||||
orderDetailList: [],
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 图表数据
|
||||
salesmanStatList: [],
|
||||
customerLevelStatList: [],
|
||||
industryStatList: [],
|
||||
// 加载状态
|
||||
summaryLoading: false,
|
||||
chartLoading: false,
|
||||
orderLoading: false,
|
||||
// 表格勾选数据
|
||||
multipleSelection: [],
|
||||
// ECharts实例
|
||||
salesmanChart: null,
|
||||
customerLevelChart: null,
|
||||
industryChart: null
|
||||
};
|
||||
},
|
||||
created() {
|
||||
// 初始化默认时间:当月第一天到今天
|
||||
this.initDefaultDateRange();
|
||||
// 页面加载时查询所有数据
|
||||
this.queryAllData();
|
||||
},
|
||||
mounted() {
|
||||
// 延迟初始化ECharts(确保DOM完全渲染)
|
||||
this.$nextTick(() => {
|
||||
this.initECharts();
|
||||
// 统一绑定resize事件(避免重复绑定)
|
||||
window.addEventListener("resize", this.handleChartResize);
|
||||
});
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 移除resize事件监听
|
||||
window.removeEventListener("resize", this.handleChartResize);
|
||||
// 销毁ECharts实例,避免内存泄漏
|
||||
if (this.salesmanChart) {
|
||||
this.salesmanChart.dispose();
|
||||
this.salesmanChart = null;
|
||||
}
|
||||
if (this.customerLevelChart) {
|
||||
this.customerLevelChart.dispose();
|
||||
this.customerLevelChart = null;
|
||||
}
|
||||
if (this.industryChart) {
|
||||
this.industryChart.dispose();
|
||||
this.industryChart = null;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 初始化默认时间范围:当月第一天到今天
|
||||
initDefaultDateRange() {
|
||||
const now = new Date();
|
||||
// 当月第一天
|
||||
const monthFirstDay = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
// 格式化日期为yyyy-MM-dd
|
||||
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}`;
|
||||
},
|
||||
// 格式化金额(处理BigDecimal空值)
|
||||
formatAmount(amount) {
|
||||
if (!amount) {
|
||||
return "0.00";
|
||||
}
|
||||
return Number(amount).toFixed(2);
|
||||
},
|
||||
// 导入时间格式化函数(简单优化,可替换为项目真实工具函数)
|
||||
formatDate(timestamp) {
|
||||
if (!timestamp) return "-";
|
||||
return timestamp;
|
||||
},
|
||||
formatDateTime(timestamp) {
|
||||
if (!timestamp) return "-";
|
||||
return timestamp;
|
||||
},
|
||||
// 重置日期范围
|
||||
resetDateRange() {
|
||||
this.initDefaultDateRange();
|
||||
this.queryAllData();
|
||||
},
|
||||
// 统一查询所有数据(汇总+图表+订单明细)
|
||||
queryAllData() {
|
||||
this.loadSummaryData();
|
||||
this.loadChartData();
|
||||
this.loadOrderDetailData();
|
||||
},
|
||||
// 组装公共查询参数(时间+分页<仅订单明细>)
|
||||
getCommonParams(needPage = false) {
|
||||
const params = {};
|
||||
// 处理时间范围参数
|
||||
if (this.dateQuery.dateRange && this.dateQuery.dateRange.length === 2) {
|
||||
params.beginDate = this.dateQuery.dateRange[0];
|
||||
params.endDate = this.dateQuery.dateRange[1];
|
||||
}
|
||||
// 是否需要分页参数
|
||||
if (needPage) {
|
||||
params.pageNum = this.pageParams.pageNum;
|
||||
params.pageSize = this.pageParams.pageSize;
|
||||
}
|
||||
return params;
|
||||
},
|
||||
// 1. 加载销售汇总数据
|
||||
loadSummaryData() {
|
||||
this.summaryLoading = true;
|
||||
const params = this.getCommonParams();
|
||||
getSummary(params)
|
||||
.then((res) => {
|
||||
if (res.code === 200) {
|
||||
this.summaryData = res.data || {};
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.summaryLoading = false;
|
||||
});
|
||||
},
|
||||
// 2. 加载图表数据并更新ECharts
|
||||
loadChartData() {
|
||||
this.chartLoading = true;
|
||||
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 || [];
|
||||
}
|
||||
// 数据加载完成后更新图表(确保实例已创建)
|
||||
this.$nextTick(() => {
|
||||
this.updateSalesmanChart();
|
||||
this.updateCustomerLevelChart();
|
||||
this.updateIndustryChart();
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
this.chartLoading = false;
|
||||
});
|
||||
},
|
||||
// 3. 加载订单明细数据
|
||||
loadOrderDetailData() {
|
||||
this.orderLoading = true;
|
||||
const params = this.getCommonParams(true); // 需要分页参数
|
||||
getOrderDetails(params)
|
||||
.then((res) => {
|
||||
if (res.code === 200) {
|
||||
this.orderDetailList = res.rows || [];
|
||||
this.total = res.total || 0;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.orderLoading = false;
|
||||
});
|
||||
},
|
||||
// 初始化ECharts实例(使用ref获取DOM,更可靠)
|
||||
initECharts() {
|
||||
// 销售员统计图表(判断DOM是否存在)
|
||||
const salesmanDom = this.$refs.salesmanChartRef;
|
||||
const customerLevelDom = this.$refs.customerLevelChartRef;
|
||||
const industryDom = this.$refs.industryChartRef;
|
||||
|
||||
if (salesmanDom) {
|
||||
this.salesmanChart = echarts.init(salesmanDom);
|
||||
}
|
||||
if (customerLevelDom) {
|
||||
this.customerLevelChart = echarts.init(customerLevelDom);
|
||||
}
|
||||
if (industryDom) {
|
||||
this.industryChart = echarts.init(industryDom);
|
||||
}
|
||||
|
||||
// 初始化默认配置(空数据状态)
|
||||
this.initDefaultEChartsOption();
|
||||
},
|
||||
// 初始化ECharts默认配置
|
||||
initDefaultEChartsOption() {
|
||||
const barEmptyOption = {
|
||||
tooltip: {
|
||||
trigger: "axis"
|
||||
},
|
||||
legend: {
|
||||
show: false
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: []
|
||||
},
|
||||
yAxis: {
|
||||
type: "value"
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [],
|
||||
type: "bar"
|
||||
}
|
||||
],
|
||||
graphic: {
|
||||
type: "text",
|
||||
left: "center",
|
||||
top: "center",
|
||||
style: {
|
||||
text: "暂无数据",
|
||||
fontSize: 14,
|
||||
color: "#999"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const pieEmptyOption = {
|
||||
tooltip: {
|
||||
trigger: "item"
|
||||
},
|
||||
legend: {
|
||||
orient: "vertical",
|
||||
left: "left",
|
||||
textStyle: {
|
||||
fontSize: 12 // 正确配置字体大小
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "销售金额",
|
||||
type: "pie",
|
||||
radius: ["40%", "70%"],
|
||||
center: ["50%", "50%"],
|
||||
data: [],
|
||||
label: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
],
|
||||
graphic: {
|
||||
type: "text",
|
||||
left: "center",
|
||||
top: "center",
|
||||
style: {
|
||||
text: "暂无数据",
|
||||
fontSize: 14,
|
||||
color: "#999"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 给三个图表设置默认配置
|
||||
if (this.salesmanChart) {
|
||||
this.salesmanChart.setOption(barEmptyOption);
|
||||
}
|
||||
if (this.customerLevelChart) {
|
||||
this.customerLevelChart.setOption(pieEmptyOption);
|
||||
}
|
||||
if (this.industryChart) {
|
||||
this.industryChart.setOption(barEmptyOption);
|
||||
}
|
||||
},
|
||||
// 更新销售员统计图表(柱状图:修复tooltip格式化,移除无效extraData)
|
||||
updateSalesmanChart() {
|
||||
if (!this.salesmanChart || this.salesmanStatList.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const salesmanNames = this.salesmanStatList.map(item => item.salesman);
|
||||
const salesAmounts = this.salesmanStatList.map(item => Number(item.salesAmount || 0));
|
||||
// 单独提取订单数量(用于tooltip格式化)
|
||||
const orderCounts = this.salesmanStatList.map(item => item.orderCount || 0);
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "shadow"
|
||||
},
|
||||
// 回调函数格式化tooltip,正确获取订单数量
|
||||
formatter: (params) => {
|
||||
const index = params[0].dataIndex;
|
||||
const salesmanName = salesmanNames[index];
|
||||
const salesAmount = salesAmounts[index].toFixed(2);
|
||||
const orderCount = orderCounts[index];
|
||||
return `${salesmanName}<br/>销售金额:${salesAmount} 元<br/>订单数量:${orderCount} 单`;
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: false
|
||||
},
|
||||
grid: {
|
||||
left: "3%",
|
||||
right: "4%",
|
||||
bottom: "3%",
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: salesmanNames,
|
||||
axisLabel: {
|
||||
rotate: 30, // 文字旋转,避免重叠
|
||||
fontSize: 12
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
name: "销售金额(元)"
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "销售金额",
|
||||
type: "bar",
|
||||
data: salesAmounts,
|
||||
itemStyle: {
|
||||
color: "#4895ef"
|
||||
}
|
||||
}
|
||||
],
|
||||
graphic: null // 有数据时隐藏暂无数据提示
|
||||
};
|
||||
|
||||
this.salesmanChart.setOption(option, true);
|
||||
},
|
||||
// 更新客户等级统计图表(饼图:修复legend配置,正常渲染)
|
||||
updateCustomerLevelChart() {
|
||||
if (!this.customerLevelChart || this.customerLevelStatList.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pieData = this.customerLevelStatList.map(item => ({
|
||||
name: this.dict.type.customer_level.find(d => d.value === item.customerLevel)?.label || item.customerLevel,
|
||||
value: Number(item.salesAmount || 0)
|
||||
}));
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: "item",
|
||||
formatter: "{b}<br/>销售金额:{c} 元<br/>占比:{d}%"
|
||||
},
|
||||
legend: {
|
||||
orient: "vertical",
|
||||
left: "left",
|
||||
textStyle: {
|
||||
fontSize: 12 // 正确配置字体大小
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "销售金额",
|
||||
type: "pie",
|
||||
radius: ["40%", "70%"],
|
||||
center: ["50%", "50%"],
|
||||
avoidLabelOverlap: false,
|
||||
label: {
|
||||
show: false,
|
||||
position: "center"
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 14,
|
||||
fontWeight: "bold"
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: pieData
|
||||
}
|
||||
],
|
||||
graphic: null // 有数据时隐藏暂无数据提示
|
||||
};
|
||||
|
||||
this.customerLevelChart.setOption(option, true);
|
||||
},
|
||||
// 更新行业统计图表(柱状图:修复tooltip格式化,移除无效extraData)
|
||||
updateIndustryChart() {
|
||||
if (!this.industryChart || this.industryStatList.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const industryNames = this.industryStatList.map(item => item.industry);
|
||||
const salesAmounts = this.industryStatList.map(item => Number(item.salesAmount || 0));
|
||||
// 单独提取客户数量(用于tooltip格式化)
|
||||
const customerCounts = this.industryStatList.map(item => item.customerCount || 0);
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "shadow"
|
||||
},
|
||||
// 回调函数格式化tooltip,正确获取客户数量
|
||||
formatter: (params) => {
|
||||
const index = params[0].dataIndex;
|
||||
const industryName = industryNames[index];
|
||||
const salesAmount = salesAmounts[index].toFixed(2);
|
||||
const customerCount = customerCounts[index];
|
||||
return `${industryName}<br/>销售金额:${salesAmount} 元<br/>客户数量:${customerCount} 家`;
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: false
|
||||
},
|
||||
grid: {
|
||||
left: "3%",
|
||||
right: "4%",
|
||||
bottom: "3%",
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: industryNames,
|
||||
axisLabel: {
|
||||
rotate: 30,
|
||||
fontSize: 12
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
name: "销售金额(元)"
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "销售金额",
|
||||
type: "bar",
|
||||
data: salesAmounts,
|
||||
itemStyle: {
|
||||
color: "#f72585"
|
||||
}
|
||||
}
|
||||
],
|
||||
graphic: null // 有数据时隐藏暂无数据提示
|
||||
};
|
||||
|
||||
this.industryChart.setOption(option, true);
|
||||
},
|
||||
// 统一处理图表自适应(避免重复绑定resize事件)
|
||||
handleChartResize() {
|
||||
if (this.salesmanChart) {
|
||||
this.salesmanChart.resize();
|
||||
}
|
||||
if (this.customerLevelChart) {
|
||||
this.customerLevelChart.resize();
|
||||
}
|
||||
if (this.industryChart) {
|
||||
this.industryChart.resize();
|
||||
}
|
||||
},
|
||||
// 分页大小改变
|
||||
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();
|
||||
exportOrderDetails(params).then((res) => {
|
||||
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;
|
||||
}
|
||||
|
||||
/* 统计区域样式 */
|
||||
.summary-stat-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
border: none;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.summary-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
.summary-item {
|
||||
width: calc(16.666% - 20px);
|
||||
text-align: center;
|
||||
padding: 15px 10px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.item-label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.item-value {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #1f2d3d;
|
||||
}
|
||||
|
||||
/* ECharts图表区域样式:确保容器高度足够且可见 */
|
||||
.echarts-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.echarts-card {
|
||||
height: 400px;
|
||||
border: none;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #1f2d3d;
|
||||
}
|
||||
|
||||
.chart-item {
|
||||
width: 100%;
|
||||
height: calc(100% - 50px); /* 减去卡片头部高度,确保高度足够 */
|
||||
min-height: 300px; /* 最小高度兜底 */
|
||||
}
|
||||
|
||||
/* 订单明细区域样式 */
|
||||
.order-detail-container {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.table-toolbar {
|
||||
padding: 15px 20px;
|
||||
background-color: #fafafa;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
@@ -320,7 +320,7 @@
|
||||
|
||||
|
||||
<!-- 编辑绑定弹窗 -->
|
||||
<el-dialog title="编辑绑定参数" :visible.sync="editBindingDialogVisible" width="500px" append-to-body">
|
||||
<el-dialog title="编辑绑定参数" :visible.sync="editBindingDialogVisible" width="500px" append-to-body>
|
||||
<div v-if="editingBinding" class="edit-binding-form">
|
||||
<div class="binding-info">
|
||||
<div class="info-row">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<div>
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item label="设备名称" prop="equipmentName">
|
||||
<el-input v-model="queryParams.equipmentName" placeholder="请输入设备名称" clearable
|
||||
|
||||
Reference in New Issue
Block a user