463 lines
13 KiB
TypeScript
463 lines
13 KiB
TypeScript
import { ChangeDetectorRef, Component, ElementRef, NgZone, OnInit, ViewChild } from '@angular/core';
|
||
import { ModalHelper, _HttpClient } from '@delon/theme';
|
||
import { G2MiniAreaClickItem, G2MiniAreaData } from '@delon/chart/mini-area';
|
||
import { G2PieClickItem, G2PieComponent, G2PieData } from '@delon/chart/pie';
|
||
import { format } from 'date-fns';
|
||
import { DataService } from '../../services/data.service';
|
||
import { Chart, registerShape, Util } from '@antv/g2';
|
||
import { G2TimelineClickItem, G2TimelineData } from '@delon/chart/timeline';
|
||
import { CurrencyPipe } from '@angular/common';
|
||
import { LOGS } from '_mock';
|
||
import { G2CustomComponent } from '@delon/chart/custom';
|
||
import { G2BarData } from '@delon/chart/bar';
|
||
import { GeometryLabelCfg } from '@antv/g2/lib/interface';
|
||
|
||
@Component({
|
||
selector: 'app-datatable-dataindex',
|
||
templateUrl: './dataindex.component.html',
|
||
styleUrls: ['./dataindex.component.less'],
|
||
providers: [CurrencyPipe]
|
||
})
|
||
export class DatatableDataindexComponent implements OnInit {
|
||
@ViewChild('g2custom', { static: false }) g2custom!: G2CustomComponent;
|
||
@ViewChild('RegionalPerforman', { static: false }) RegionalPerforman!: G2CustomComponent;
|
||
@ViewChild('BillDirectProportion', { static: false }) BillDirectProportion!: G2CustomComponent;
|
||
@ViewChild('SaleProportion', { static: false }) SaleProportion!: G2CustomComponent;
|
||
salesData: any[] = [];
|
||
|
||
totalAdvanceDeposit: { totalAmount: string; list: G2MiniAreaData[] } = { totalAmount: '0', list: this.genData() };
|
||
totalPerformanceVolume: { totalAmount: string; list: G2MiniAreaData[] } = { totalAmount: '0', list: this.genData() };
|
||
totalFreight: { totalAmount: string; list: G2MiniAreaData[] } = { totalAmount: '0', list: this.genData() };
|
||
totalSurcharge: { totalAmount: string; list: G2MiniAreaData[] } = { totalAmount: '0', list: this.genData() };
|
||
|
||
billTypeDatas: any = [
|
||
{ item: '货源单', count: 0, percent: 0 },
|
||
{ item: '合同单', count: 0, percent: 0 }
|
||
];
|
||
|
||
regionalPerformanceCompletion: DataPerformanceTrendVO[] = [];
|
||
|
||
constructor(private service: DataService, private currency: CurrencyPipe) {}
|
||
|
||
ngOnInit(): void {
|
||
this.initMiniAreaData();
|
||
this.initOthersData();
|
||
}
|
||
|
||
private initMiniAreaData() {
|
||
// 客户预存款总额
|
||
this.service.request(this.service.$api_total_advance_deposit).subscribe((res: DataTotalVO) => {
|
||
if (res) {
|
||
this.totalAdvanceDeposit = this.formatMiniAreaData(res);
|
||
}
|
||
});
|
||
// 业绩量总额
|
||
this.service.request(this.service.$api_total_performance_volume).subscribe((res: DataTotalVO) => {
|
||
if (res) {
|
||
this.totalPerformanceVolume = this.formatMiniAreaData(res);
|
||
}
|
||
});
|
||
// 司机应付总额
|
||
this.service.request(this.service.$api_total_freight).subscribe((res: DataTotalVO) => {
|
||
if (res) {
|
||
this.totalFreight = this.formatMiniAreaData(res);
|
||
}
|
||
});
|
||
// 业绩量总额
|
||
this.service.request(this.service.$api_total_surcharge).subscribe((res: DataTotalVO) => {
|
||
if (res) {
|
||
this.totalSurcharge = this.formatMiniAreaData(res);
|
||
}
|
||
});
|
||
}
|
||
|
||
private initOthersData() {
|
||
// 订单类型比例
|
||
this.service.request(this.service.$api_getBillTypeProportion).subscribe(res => {
|
||
if (res) {
|
||
this.billTypeDatas = this.formatCoordinateData(res);
|
||
this.initBillChart(this.g2custom['el'].nativeElement as any);
|
||
}
|
||
});
|
||
// 大区业绩完成情况
|
||
this.service.request(this.service.$api_getBillAmount).subscribe((res: DataPerformanceTrendVO[]) => {
|
||
if (res) {
|
||
this.regionalPerformanceCompletion = res.map(item => ({ ...item, time: new Date(item.time)?.getTime() }));
|
||
this.initRegionalPerformanceChart(this.RegionalPerforman['el'].nativeElement as any, this.regionalPerformanceCompletion);
|
||
}
|
||
});
|
||
// 订单类型比例
|
||
this.service.request(this.service.$api_getWayBillDirectProportion).subscribe(res => {
|
||
if (res) {
|
||
const billTypeDatas = this.formatCoordinateData(res.map((item: any) => ({ ...item, billType: item.wayBillType })));
|
||
this.initBillChart(this.BillDirectProportion['el'].nativeElement as any, billTypeDatas);
|
||
}
|
||
});
|
||
// 统计订单结算金额-趋势
|
||
this.service.request(this.service.$api_get_bill_payment_amount).subscribe(res => {
|
||
if (res) {
|
||
this.salesData = this.formatBarData(res);
|
||
this.initBiaxialChart(this.SaleProportion['el'].nativeElement as any, this.salesData);
|
||
}
|
||
});
|
||
}
|
||
|
||
handleClick(data: G2MiniAreaClickItem): void {
|
||
this.service.msgSrv.info(`${data.item.x} - ${data.item.y}`);
|
||
}
|
||
|
||
/**
|
||
* 构建订单类型秘鲁图
|
||
* @param el
|
||
*/
|
||
private initBillChart(el: HTMLElement, datas?: any[]): void {
|
||
const data = datas || this.billTypeDatas;
|
||
|
||
const chart = new Chart({
|
||
container: el,
|
||
autoFit: true,
|
||
height: 400
|
||
});
|
||
// 新建一个 view 用来单独渲染Annotation
|
||
const innerView = chart.createView();
|
||
chart.coordinate('theta', {
|
||
radius: 0.6,
|
||
innerRadius: 0.7
|
||
});
|
||
|
||
chart.data(data);
|
||
|
||
chart.scale('percent', {
|
||
formatter: val => {
|
||
val = val * 100 + '%';
|
||
return val;
|
||
}
|
||
});
|
||
|
||
chart.tooltip(false);
|
||
|
||
// 声明需要进行自定义图例字段: 'item'
|
||
chart.legend('item', {
|
||
position: 'right', // 配置图例显示位置
|
||
custom: true, // 关键字段,告诉 G2,要使用自定义的图例
|
||
items: data.map((obj: any, index: any) => {
|
||
return {
|
||
name: obj.item, // 对应 itemName
|
||
value: obj.percent, // 对应 itemValue
|
||
marker: {
|
||
symbol: 'square', // marker 的形状
|
||
style: {
|
||
r: 5, // marker 图形半径
|
||
fill: chart.getTheme().colors10[index] // marker 颜色,使用默认颜色,同图形对应
|
||
}
|
||
} // marker 配置
|
||
};
|
||
}),
|
||
itemValue: {
|
||
style: {
|
||
fill: '#999'
|
||
}, // 配置 itemValue 样式
|
||
formatter: (val: any) => `${val * 100}%` // 格式化 itemValue 内容
|
||
}
|
||
});
|
||
|
||
chart
|
||
.interval()
|
||
.adjust('stack')
|
||
.position('percent')
|
||
.color('item')
|
||
.style({
|
||
fillOpacity: 1,
|
||
stroke: 'white',
|
||
lineWidth: 8
|
||
})
|
||
.state({
|
||
active: {
|
||
style: element => {
|
||
const shape = element.shape;
|
||
return {
|
||
lineWidth: 1,
|
||
stroke: 'white',
|
||
strokeOpacity: shape.attr('fillOpacity')
|
||
};
|
||
}
|
||
}
|
||
});
|
||
|
||
innerView
|
||
.annotation()
|
||
.text({
|
||
position: ['50%', '50%'],
|
||
content: data[0].item,
|
||
style: {
|
||
fontSize: 20,
|
||
fill: '#8c8c8c',
|
||
textAlign: 'center'
|
||
},
|
||
offsetY: -20
|
||
})
|
||
.text({
|
||
position: ['50%', '50%'],
|
||
content: data[0].count,
|
||
style: {
|
||
fontSize: 28,
|
||
fill: '#8c8c8c',
|
||
textAlign: 'center'
|
||
},
|
||
offsetY: 20
|
||
});
|
||
innerView.render(true);
|
||
|
||
// 移除图例点击过滤交互
|
||
chart.removeInteraction('legend-filter');
|
||
chart.interaction('element-active');
|
||
|
||
chart.render();
|
||
|
||
// 监听 element 上状态的变化来动态更新 Annotation 信息
|
||
chart.on('element:statechange', (ev: any) => {
|
||
const { state, stateStatus, element } = ev.gEvent.originalEvent;
|
||
|
||
// 本示例只需要监听 active 的状态变化
|
||
if (state === 'active') {
|
||
const data = element.getData();
|
||
if (stateStatus) {
|
||
// 更新 Annotation
|
||
updateAnnotation(data);
|
||
} else {
|
||
// 隐藏 Annotation
|
||
// clearAnnotation();
|
||
}
|
||
}
|
||
});
|
||
|
||
// 绘制 annotation
|
||
let lastItem: any;
|
||
function updateAnnotation(data: any) {
|
||
if (data.item !== lastItem) {
|
||
innerView.annotation().clear(true);
|
||
innerView
|
||
.annotation()
|
||
.text({
|
||
position: ['50%', '50%'],
|
||
content: data.item,
|
||
style: {
|
||
fontSize: 20,
|
||
fill: '#8c8c8c',
|
||
textAlign: 'center'
|
||
},
|
||
offsetY: -20
|
||
})
|
||
.text({
|
||
position: ['50%', '50%'],
|
||
content: data.count,
|
||
style: {
|
||
fontSize: 28,
|
||
fill: '#8c8c8c',
|
||
textAlign: 'center'
|
||
},
|
||
offsetY: 20
|
||
});
|
||
innerView.render(true);
|
||
lastItem = data.item;
|
||
}
|
||
}
|
||
|
||
// 清空 annotation
|
||
function clearAnnotation() {
|
||
innerView.annotation().clear(true);
|
||
innerView.render(true);
|
||
lastItem = null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 构建大区业绩完成情况柱折双轴图
|
||
* @param el
|
||
* @param data
|
||
*/
|
||
private initRegionalPerformanceChart(el: HTMLElement, data: DataPerformanceTrendVO[]) {
|
||
const chart = new Chart({
|
||
container: el,
|
||
autoFit: true,
|
||
height: 400
|
||
});
|
||
|
||
chart.data(data);
|
||
chart.scale({
|
||
time: {
|
||
alias: '日期',
|
||
type: 'time'
|
||
},
|
||
quantity: {
|
||
alias: '金额',
|
||
min: 0,
|
||
sync: true, // 将 pv 字段数值同 time 字段数值进行同步
|
||
nice: true
|
||
}
|
||
});
|
||
|
||
chart.axis('quantity', {
|
||
title: {}
|
||
});
|
||
|
||
chart.tooltip({
|
||
showCrosshairs: true,
|
||
shared: true
|
||
});
|
||
|
||
// 声明需要进行自定义图例字段: 'item'
|
||
chart.legend({
|
||
offsetY: 10,
|
||
position: 'bottom', // 配置图例显示位置
|
||
custom: true, // 关键字段,告诉 G2,要使用自定义的图例
|
||
items: [
|
||
{
|
||
name: '订单金额 (元)', // 对应 itemName
|
||
value: '' // 对应 itemValue
|
||
}
|
||
],
|
||
itemValue: {
|
||
style: {
|
||
fill: '#999'
|
||
} // 配置 itemValue 样式
|
||
}
|
||
});
|
||
|
||
chart.line().position('time*quantity').color('#4FAAEB');
|
||
chart.render();
|
||
}
|
||
|
||
/**
|
||
* 构建业绩完成情况柱折双轴图
|
||
* @param el
|
||
* @param data
|
||
*/
|
||
private initBiaxialChart(el: HTMLElement, data: any[]) {
|
||
const chart = new Chart({
|
||
container: el,
|
||
autoFit: true,
|
||
height: 400,
|
||
|
||
});
|
||
chart.data(data);
|
||
chart.scale({
|
||
pre: { alias: '同期业绩完成率', min: 0, max: 1 },
|
||
pre2: { min: 0, max: 1 },
|
||
quantity: { min: 0, max: 1000000 },
|
||
});
|
||
chart.legend({
|
||
custom: true,
|
||
items: [
|
||
{ value: 'quantity', name: '业绩量 (万)', marker: { symbol: 'hyphen', style: { stroke: '#3182bd', r: 15, lineWidth: 5 } } },
|
||
{ value: 'pre2', name: '业绩完成率', marker: { symbol: 'hyphen', style: { stroke: '#fdae6b', r: 15, lineWidth: 5 } } },
|
||
{ value: 'pre', name: '同期业绩完成率', marker: { symbol: 'hyphen', style: { stroke: '#ff4d4f', r: 15, lineWidth: 5 } } }
|
||
]
|
||
});
|
||
chart.axis('pre', {
|
||
grid: null,
|
||
title: null,
|
||
label: {
|
||
formatter: val => {
|
||
return +val * 100 + '%';
|
||
}
|
||
}
|
||
});
|
||
chart.axis('pre2', false);
|
||
chart.tooltip({
|
||
shared: true
|
||
});
|
||
chart.interval().position('time*quantity').label('quantity').color('#3182bd');
|
||
chart
|
||
.line()
|
||
.position('time*pre')
|
||
.label('pre', val => ({ content: (val * 100).toFixed(0) + '%' }))
|
||
.color('#ff4d4f')
|
||
.size(3);
|
||
chart.point().position('time*pre').color('#ff4d4f').size(3).shape('circle');
|
||
chart
|
||
.line()
|
||
.position('time*pre2')
|
||
.label('pre2', val => ({ content: (val * 100).toFixed(0) + '%' }))
|
||
.color('#fdae6b')
|
||
.size(3);
|
||
chart.point().position('time*pre2').color('#fdae6b').size(3).shape('circle');
|
||
|
||
chart.interaction('active-region');
|
||
chart.removeInteraction('legend-filter'); // 自定义图例,移除默认的分类图例筛选交互
|
||
chart.render();
|
||
}
|
||
|
||
private formatMiniAreaData(data: DataTotalVO) {
|
||
return {
|
||
totalAmount: `${this.currency.transform(data.totalAmount || 0)}万`,
|
||
list: data.list?.map(item => ({
|
||
x: item.time,
|
||
y: item.quantity
|
||
}))
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 初始化饼图数据格式
|
||
* @param data
|
||
* @returns
|
||
*/
|
||
private formatCoordinateData(data: DataBillTypeProportion[]): any[] {
|
||
const total = data.map(item => item.quantity).reduce((pre, next) => pre + next);
|
||
const rs: any[] = [];
|
||
data.forEach(item => {
|
||
rs.push({
|
||
item: (BILLTYPE as any)[item.billType],
|
||
count: item.quantity,
|
||
percent: Number((item.quantity / total).toFixed(2))
|
||
});
|
||
});
|
||
return rs;
|
||
}
|
||
|
||
private formatBarData(data: DataPerformanceTrendVO[]): any[] {
|
||
return data.map(item => ({
|
||
time: item.time,
|
||
quantity: item.quantity,
|
||
color: undefined,
|
||
pre: Math.floor(Math.random() * 100) / 100,
|
||
pre2: Math.floor(Math.random() * 100) / 100
|
||
}));
|
||
}
|
||
|
||
private genData(): G2MiniAreaData[] {
|
||
const beginDay = new Date().getTime();
|
||
const res: G2MiniAreaData[] = [];
|
||
for (let i = 0; i < 20; i += 1) {
|
||
res.push({
|
||
x: format(new Date(beginDay + 1000 * 60 * 60 * 24 * i), 'yyyy-MM-dd'),
|
||
y: Math.floor(Math.random() * 1)
|
||
});
|
||
}
|
||
return res;
|
||
}
|
||
}
|
||
|
||
interface DataTotalVO {
|
||
totalAmount: number;
|
||
list: DataPerformanceTrendVO[];
|
||
}
|
||
|
||
interface DataPerformanceTrendVO {
|
||
quantity: number;
|
||
time: number;
|
||
}
|
||
|
||
interface DataBillTypeProportion {
|
||
billType: BILLTYPE;
|
||
quantity: number;
|
||
}
|
||
|
||
enum BILLTYPE {
|
||
'HY' = '货源单',
|
||
'HT' = '合同单',
|
||
'ZF' = '直付',
|
||
'CDZ' = '车队长'
|
||
}
|