<template>
<div class="home-container">
<total-product-info
:merchantCount="merchantCount"
:todayConsume="todayConsume"
:todayRecharge="todayRecharge"
:todayFeeYuan="todayFeeYuan"
></total-product-info>
<div id="curve">
<div class="content">
<div class="header">
<span>产品-消费金额</span>
<el-form ref="form" :rules="formRules" :model="query">
<el-row :gutter="24">
<el-col class="joker-col">
<el-form-item
label="产品名称:"
prop="productId"
label-width="110px"
>
<el-select
v-model="query.productId"
placeholder="请选择"
clearable
@change="productChange"
>
<el-option
v-for="item in productListData"
:key="item.id"
:label="item.name"
:value="item.id"
>
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col class="joker-col">
<el-form-item
prop="time"
label="查询时间:"
label-width="110px"
>
<el-date-picker
v-model="query.time"
type="daterange"
range-separator="-"
start-placeholder="查询时间"
end-placeholder="查询时间"
value-format="yyyy-MM-dd"
:picker-options="pickerOptions"
@change="timeChange"
></el-date-picker>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<div id="paint"></div>
</div>
</div>
<div class="pie-box">
<div class="content">
<div class="search">
<el-form ref="formpie" :rules="formRules" :model="query">
<el-row :gutter="24">
<el-col class="joker-col">
<el-form-item
prop="timePie"
label="查询时间:"
label-width="110px"
>
<el-date-picker
v-model="query.timePie"
type="daterange"
range-separator="-"
start-placeholder="查询时间"
end-placeholder="查询时间"
value-format="yyyy-MM-dd"
:picker-options="pickerOptions"
@change="timePieChange"
></el-date-picker>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<el-row class="section-m" :gutter="24">
<el-col :span="12" :lg="12" :sm="24" :xs="24">
<div class="left">
<div class="title">各产品-消费总额占比图</div>
<div id="pie"></div>
</div>
</el-col>
<el-col :span="12" :lg="12" :sm="24" :xs="24">
<div class="right">
<div class="title">各产品-技术服务费用占比图</div>
<div class="line"></div>
<ul>
<li v-for="item in barList" :key="item.name">
<el-row class="section-m" :gutter="24">
<el-col :span="4">
<span class="name">{{ item.name }}</span>
</el-col>
<el-col :span="12">
<el-progress
type="line"
:text-inside="true"
:stroke-width="14"
:percentage="item.rate"
:color="item.color"
></el-progress>
</el-col>
<el-col :span="8">
<span class="right-desc">消费₦{{ item.value }}</span>
<span class="right-desc" :style="{ color: item.color }"
>占{{ item.rate }}%</span
>
</el-col>
</el-row>
</li>
</ul>
</div>
</el-col>
</el-row>
</div>
</div>
</div>
</template>
<script>
import api from '@/api/api.js';
import TotalProductInfo from './totalProductInfo';
import currency from 'currency.js';
import * as d3 from 'd3';
import moment from 'moment';
import { getRateCalculate } from '@/utils';
import { debounce } from 'lodash';
import { mapGetters } from 'vuex';
const echarts = require('echarts');
const D30 = 3600 * 1000 * 24 * 30;
const colors = [
'#30D2FF',
'#347DFF',
'#F2A821',
'#47D568',
'#07BDB4',
'#6C74FF',
];
export default {
components: { TotalProductInfo },
data() {
const timeRange = (rule, value, callback) => {
if (!value) {
callback(new Error('时间不能为空'));
}
const start = value[0];
const end = value[1];
if (moment(end).diff(moment(start)) > D30) {
callback(new Error('选择时间不大于30天,请重新选择时间'));
} else {
callback();
}
};
return {
pickerOptions: {
disabledDate(time) {
return time.getTime() > Date.now() - 8.64e7; // 设置选择今天之前的日期(不能选择当天)
},
},
productListData: [],
query: {
productId: '',
time: [],
timePie: [],
},
formRules: {
time: [{ validator: timeRange, trigger: 'blur' }],
timePie: [{ validator: timeRange, trigger: 'blur' }],
},
//
merchantCount: '',
todayConsume: '',
todayRecharge: '',
todayFeeYuan: '',
barList: [],
pieCanvas: null,
splinesCanvas: null,
};
},
computed: {
...mapGetters(['sidebar']),
},
watch: {
sidebar: {
handler(val) {
setTimeout(() => {
this.resize();
}, 300);
},
deep: true,
},
},
created() {
this.initTime();
this.init();
},
mounted() {
let that = this;
window.addEventListener('resize', debounce(that.resize, 30), true);
},
methods: {
async init() {
await this.productList();
this.getData();
this.getPieData();
},
initTime() {
let end = new Date();
let start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 31);
end.setTime(end.getTime() - 3600 * 1000 * 24 * 1);
this.query.time = [
moment(start).format('YYYY-MM-DD'),
moment(end).format('YYYY-MM-DD'),
];
this.query.timePie = [
moment(start).format('YYYY-MM-DD'),
moment(end).format('YYYY-MM-DD'),
];
},
getData() {
api.homeProductInfo().then(res => {
if (res.code == 200) {
this.merchantCount = res.data.merchantCount;
this.todayConsume = currency(res.data.consume).format({
symbol: '',
separator: '',
});
this.todayRecharge = currency(res.data.recharge).format({
symbol: '',
separator: '',
});
this.todayFeeYuan = currency(res.data.serviceFee).format({
symbol: '',
separator: '',
});
}
});
},
getSplinesData() {
api
.productConsume({
productId: this.query.productId,
startDate: this.query.time?.[0],
endDate: this.query.time?.[1],
})
.then(res => {
if (res.code == 200) {
const amountYuan = res.data.map(
item => item.amountYuan || item.amount,
);
const date = res.data.map(item => item.date);
this.paint(amountYuan, date);
}
});
},
getPieData() {
api
.consumePercent({
startDate: this.query.timePie?.[0],
endDate: this.query.timePie?.[1],
})
.then(res => {
if (res.code == 200) {
const piedata = res.data.productConsumePercent.map(item => {
for (let i = 0; i < this.productListData.length; i++) {
const id = this.productListData[i].id;
if (item.id == id) {
item.name = this.productListData[i].name;
item.value = item.amountYuan || item.amount;
return item;
}
}
});
this.pie(piedata);
this.barList = res.data.productServiceFeePercent.map(
(item, index) => {
for (let i = 0; i < this.productListData.length; i++) {
const id = this.productListData[i].id;
if (item.id == id) {
item.name = this.productListData[i].name;
item.value = item.amount;
item.color = this.productListData[i].color;
return item;
}
}
},
);
getRateCalculate(this.barList.map(_ => _.value)).forEach((v, i) => {
this.barList[i].rate = v;
});
}
});
},
async productList() {
await api.productList().then(res => {
if (res.code == 200) {
this.productListData = res.data.map((item, i) => {
item.color = colors[i];
return item;
});
if (res.data.length) {
this.query.productId = res.data[0].id;
this.getSplinesData();
}
}
});
},
paint(value, xAxisData) {
let option = {
backgroundColor: '#fff',
xAxis: {
name: '日期',
type: 'category',
boundaryGap: false,
axisTick: {
show: false,
},
axisLabel: {
color: '#AFAEC0',
},
nameTextStyle: {
color: '#666666',
fontSize: 16,
align: 'right',
verticalAlign: 'top',
lineHeight: 76,
},
axisLine: {
lineStyle: {
type: 'dashed',
color: '#D1D1D1',
width: 1,
},
},
},
yAxis: {
name: '消费金额(元)',
type: 'value',
axisTick: {
show: false, //不显示刻度线
},
axisLabel: {
color: '#AFAEC0', //y轴上的字体颜色
},
axisLine: {
lineStyle: {
width: 1,
color: '#D1D1D1', //y轴的轴线的宽度和颜色
},
},
nameTextStyle: {
color: '#666666',
fontSize: 16,
lineHeight: 50,
},
splitLine: {
show: true,
lineStyle: {
type: 'dashed',
width: 1,
color: '#D1D1D1',
},
},
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
show: false,
},
},
formatter: function (params, a, b) {
const value = params[0].value;
return `<div style="text-align:center;">
<b style="font-size:30px;color:#333333;margin-bottom:2px;">${value}</b>
<div style="font-size:14px;color:#999999;">消费金额</div>
</div>`;
},
},
grid: {
left: '80',
right: '50',
},
series: [
{
type: 'line',
symbol: 'circle',
symbolSize: 1,
smooth: true,
itemStyle: {
color: '#5995FF',
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: '#347DFF',
},
{
offset: 1,
color: '#FFFFFF',
},
]),
},
},
],
};
option.xAxis.data = xAxisData;
option.series[0].data = value;
let chart = this.getSplines();
chart.setOption(option);
},
pie(data) {
const names = data.map(_ => _.name);
let rich = {
tt: {
fontWeight: 'bold',
fontSize: 18,
color: '#337DFF',
},
};
let option = {
title: {
// text: '各产品-消费总额占比图',
subtext: '',
left: 50,
top: 110,
borderLeftWidth: 1,
borderColor: 'red',
},
tooltip: {
trigger: 'item',
formatter: '{b} : {c} ({d}%)',
},
legend: {
icon: 'circle',
itemHeight: 6,
right: 43,
bottom: 21,
textStyle: {
fontSize: 12,
color: '#999',
},
borderColor: '#F0EFEF',
borderWidth: 1,
padding: [16, 23, 16, 23],
data: names,
},
color: colors,
series: [
{
type: 'pie',
selectedMode: 'single',
radius: ['30%', '50%'],
center: ['50%', '50%'],
label: {
show: true,
formatter: '{tt|{d}%}\n消费¥{c}',
rich: rich,
lineHeight: 22,
fontSize: 14,
color: '#333333',
},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
},
],
};
option.series[0].data = data; // item.selected: true
let chart = this.getPie();
chart.setOption(option);
},
timeChange() {
this.getPaintData();
},
productChange() {
this.getPaintData();
},
getPaintData() {
this.$refs['form'].validate(valid => {
if (valid) {
this.getSplinesData();
}
});
},
timePieChange() {
this.$refs['formpie'].validate(valid => {
if (valid) {
this.getPieData();
}
});
},
getPie() {
if (this.pieCanvas) {
return this.pieCanvas;
}
this.pieCanvas = echarts.init(document.getElementById('pie'));
return this.pieCanvas;
},
getSplines() {
if (this.splinesCanvas) {
return this.splinesCanvas;
}
this.splinesCanvas = echarts.init(document.getElementById('paint'));
return this.splinesCanvas;
},
resize() {
this.pieCanvas && this.pieCanvas.resize();
this.splinesCanvas && this.splinesCanvas.resize();
},
},
destrbeforeDestroy() {
window.removeEventListener('resize', this.resize, true);
},
};
</script>
<style lang="scss" scoped>
.home-container {
background-color: #f3f6f9;
}
.customer {
margin: 0 30px;
box-shadow: 0 7px 17px 0 #e5ecf0;
border-radius: 4px;
.content-box {
min-width: 1500px;
background-color: #fff;
padding: 30px;
margin: 0;
box-sizing: border-box;
.pagination {
margin-top: 30px;
}
}
}
.el-form-item {
margin-bottom: 0;
}
/deep/ .el-form-item__label {
line-height: 40px !important;
font-size: 16px;
}
#curve {
padding: 0 30px;
/deep/ .el-col-24 {
width: auto;
}
.content {
background-color: #fff;
padding-top: 30px;
margin-bottom: 30px;
border-radius: 4px;
#paint {
width: 100%;
height: 400px;
}
}
.header {
display: flex;
padding: 0 45px 0 39px;
justify-content: space-between;
margin-bottom: 20px;
span {
font-size: 16px;
font-weight: bold;
line-height: 22px;
color: #333;
}
span::before {
content: ' ';
display: inline-block;
background-color: #347dff;
width: 4px;
height: 20px;
vertical-align: middle;
line-height: 22px;
margin-right: 10px;
position: relative;
top: -2px;
}
}
}
.section-m {
background: #fff;
margin: 0 !important;
width: 100%;
}
.pie-box {
padding: 0 30px 30px;
.content {
background: #fff;
padding-top: 40px;
.search {
display: flex;
justify-content: flex-end;
padding-right: 45px;
margin-bottom: 20px;
}
.left {
position: relative;
#pie {
width: 100%;
height: 550px;
}
.title {
position: absolute;
left: 40px;
top: 50px;
font-size: 16px;
color: #333;
font-weight: bold;
&::before {
content: ' ';
display: inline-block;
width: 4px;
height: 20px;
background-color: #347dff;
vertical-align: middle;
margin-right: 10px;
position: relative;
top: -2px;
}
}
}
.right {
width: 100%;
height: 500px;
padding-right: 45px;
position: relative;
.title {
position: absolute;
left: 40px;
top: 50px;
font-size: 16px;
color: #333;
font-weight: bold;
&::before {
content: ' ';
display: inline-block;
width: 4px;
height: 20px;
background-color: #347dff;
vertical-align: middle;
margin-right: 10px;
position: relative;
top: -2px;
}
}
.line {
position: absolute;
top: 40px;
left: 0;
width: 1px;
background-color: #d1d1d1;
height: 412px;
}
ul {
position: relative;
top: 180px;
left: 48px;
width: 100%;
li {
width: 100%;
display: flex;
margin-bottom: 34px;
span.name {
width: 102px;
color: #666;
font-size: 14px;
}
.el-progress {
height: 14px;
/deep/ .el-progress-bar__outer,
/deep/ .el-progress-bar__inner {
border-radius: 0;
}
}
.el-col {
padding: 0 !important;
}
.right-desc {
margin-left: 15px;
word-break: keep-all;
}
}
}
}
}
}
@media screen and (max-width: 1199px) {
.line {
display: none !important;
}
}
</style>