<template>
|
<view class="box">
|
<view class="yy_cate">
|
<view class="yy_cate_item" style="margin-bottom: 40rpx;">
|
<image src="/static/images/ic_users@2x.png" mode="widthFix"></image>
|
<view class="yy_cate_item_info">
|
<text>{{ overviewInfo.totalMembers || 0 }}</text>
|
<text>总注册用户</text>
|
</view>
|
</view>
|
<view class="yy_cate_item" style="margin-bottom: 40rpx;">
|
<image src="/static/images/ic_newuser@2x.png" mode="widthFix"></image>
|
<view class="yy_cate_item_info">
|
<text>{{ overviewInfo.todayMembers || 0 }}</text>
|
<text>今日新增用户</text>
|
</view>
|
</view>
|
<view class="yy_cate_item">
|
<image src="/static/images/ic_bike@2x.png" mode="widthFix"></image>
|
<view class="yy_cate_item_info">
|
<text>{{ overviewInfo.bikeCount || 0 }}</text>
|
<text>自行车数量</text>
|
</view>
|
</view>
|
<view class="yy_cate_item">
|
<image src="/static/images/ic_ebike@2x.png" mode="widthFix"></image>
|
<view class="yy_cate_item_info">
|
<text>{{ overviewInfo.eleBikeCount || 0 }}</text>
|
<text>电动车数量</text>
|
</view>
|
</view>
|
</view>
|
<view class="box_xian"></view>
|
<view class="box_info">
|
<view class="box_info_title">
|
<view class="box_info_title_val">
|
<view class="box_info_title_val_l"></view>
|
<text>收入统计</text>
|
</view>
|
<view class="right">
|
<view v-for="item in incomeDateOptions" :key="item.value"
|
:class="incomeDateType === item.value ? 'right_item active' : 'right_item'"
|
@click="changeIncomeDateType(item.value)">
|
{{ item.label }}
|
</view>
|
</view>
|
</view>
|
<view v-if="incomeDateType === 4" class="custom-date-row">
|
<picker mode="date" style="flex: 1;" :value="incomeStartDate" @change="onIncomeStartDateChange">
|
<view class="custom-date-picker" :style="{ color: incomeStartDate ? '' : '#999999' }">{{ incomeStartDate || '开始日期' }}</view>
|
</picker>
|
<view class="custom-date-separator">至</view>
|
<picker mode="date" style="flex: 1;" :value="incomeEndDate" @change="onIncomeEndDateChange">
|
<view class="custom-date-picker" :style="{ color: incomeEndDate ? '' : '#999999' }">{{ incomeEndDate || '结束日期' }}</view>
|
</picker>
|
</view>
|
<view class="content">
|
<view class="content_title">累计收入(元):</view>
|
<view class="content_info">
|
<view class="content_info_a">{{ formatAmount(incomeInfo.totalIncome) }}</view>
|
<view class="content_info_b">
|
<view class="content_info_b_label">同比</view>
|
<view class="content_info_b_val">
|
{{ incomeInfo.yearOnYearRate }}%
|
<image :src="incomeInfo.yearOnYearRate > 0 ? '/static/icon/ic_up@2x.png' : '/static/icon/ic_down@2x.png'" mode="widthFix"></image>
|
</view>
|
</view>
|
<view class="content_info_b">
|
<view class="content_info_b_label">环比</view>
|
<view class="content_info_b_val">
|
{{ incomeInfo.chainRate }}%
|
<image :src="incomeInfo.chainRate > 0 ? '/static/icon/ic_up@2x.png' : '/static/icon/ic_down@2x.png'" mode="widthFix"></image>
|
</view>
|
</view>
|
</view>
|
</view>
|
<view class="chart">
|
<qiun-data-charts :key="incomeChartKey" :ontouch="true" canvasId="incomeColumnChart" type="column" :opts="chart1Opts"
|
:chartData="chart1Data" />
|
</view>
|
</view>
|
<view class="box_xian"></view>
|
<view class="box_info">
|
<view class="box_info_title">
|
<view class="box_info_title_val">
|
<view class="box_info_title_val_l"></view>
|
<text>收入车型分析</text>
|
</view>
|
<view class="right">
|
<view v-for="item in bikeIncomeDateOptions" :key="item.value"
|
:class="bikeIncomeDateType === item.value ? 'right_item active' : 'right_item'"
|
@click="changeBikeIncomeDateType(item.value)">
|
{{ item.label }}
|
</view>
|
</view>
|
</view>
|
<view v-if="bikeIncomeDateType === 4" class="custom-date-row">
|
<picker mode="date" style="flex: 1;" :value="bikeIncomeStartDate" @change="onBikeIncomeStartDateChange">
|
<view class="custom-date-picker" :style="{ color: bikeIncomeStartDate ? '' : '#999999' }">{{ bikeIncomeStartDate || '开始日期' }}</view>
|
</picker>
|
<view class="custom-date-separator">至</view>
|
<picker mode="date" style="flex: 1;" :value="bikeIncomeEndDate" @change="onBikeIncomeEndDateChange">
|
<view class="custom-date-picker" :style="{ color: bikeIncomeEndDate ? '' : '#999999' }">{{ bikeIncomeEndDate || '结束日期' }}</view>
|
</picker>
|
</view>
|
<view class="chart1">
|
<qiun-data-charts :key="vehicleChartKey" canvasId="vehicleRingChart" type="ring" :opts="vehicleChartOpts"
|
:chartData="vehicleChartData" />
|
<view class="ring-center">
|
<view class="ring-center__label">车辆总数</view>
|
<view class="ring-center__value">{{ totalVehicleCount }}</view>
|
</view>
|
</view>
|
<view class="table">
|
<view class="table_head">
|
<view class="table_head_row">车辆名称</view>
|
<view class="table_head_row">车辆收入(元)</view>
|
<view class="table_head_row">占比(%)</view>
|
</view>
|
<view class="table_content" v-for="item in vehicleSummary" :key="item.paramId">
|
<view class="table_content_row">{{ item.paramName }}</view>
|
<view class="table_content_row">{{ formatAmount(item.income) }}</view>
|
<view class="table_content_row">{{ item.percent }}</view>
|
</view>
|
<view class="table_empty" v-if="!vehicleSummary.length">暂无收入车型分析数据</view>
|
</view>
|
</view>
|
</view>
|
</template>
|
|
<script>
|
const defaultIncomeChartData = {
|
categories: [],
|
series: [{
|
name: '收入',
|
data: [],
|
color: '#01B6AD',
|
textColor: '#01B6AD',
|
textSize: 11
|
}]
|
}
|
|
export default {
|
computed: {
|
incomeChartKey() {
|
return `income-column-${this.incomeDateType}-${this.incomeStartDate || ''}-${this.incomeEndDate || ''}-${this.chart1Data.categories.join('_')}-${this.incomeInfo.dailyList.map(item => `${item.date || ''}-${item.income || 0}`).join('_')}`
|
},
|
vehicleChartKey() {
|
return `vehicle-ring-${this.bikeIncomeDateType}-${this.bikeIncomeStartDate || ''}-${this.bikeIncomeEndDate || ''}-${this.vehicleSummary.map(item => `${item.paramId}-${item.income}`).join('_')}`
|
},
|
totalVehicleCount() {
|
return this.vehicleSummary.reduce((sum, item) => sum + Number(item.bikeCount || 0), 0)
|
},
|
incomeChartItemCount() {
|
if (this.incomeDateType === 1) {
|
return 7
|
}
|
|
if (this.incomeDateType === 2) {
|
return 8
|
}
|
|
if (this.incomeDateType === 3) {
|
return 10
|
}
|
|
return 7
|
},
|
incomeRangeText() {
|
if (this.incomeDateType === 4) {
|
if (this.incomeStartDate && this.incomeEndDate) {
|
return `${this.incomeStartDate} 至 ${this.incomeEndDate}`
|
}
|
|
return '请选择日期范围'
|
}
|
|
const current = this.incomeDateOptions.find(item => item.value === this.incomeDateType)
|
return current ? current.label : '-'
|
}
|
},
|
data() {
|
return {
|
overviewInfo: {
|
bikeCount: 0,
|
eleBikeCount: 0,
|
todayMembers: 0,
|
totalMembers: 0
|
},
|
incomeDateType: 1,
|
incomeDateOptions: [{
|
label: '近7天',
|
value: 1
|
},
|
{
|
label: '近15天',
|
value: 2
|
},
|
{
|
label: '近30天',
|
value: 3
|
},
|
{
|
label: '自定义',
|
value: 4
|
}
|
],
|
incomeStartDate: '',
|
incomeEndDate: '',
|
incomeInfo: {
|
totalIncome: 0,
|
chainRate: 0,
|
yearOnYearRate: 0,
|
dailyList: []
|
},
|
chart1Data: {
|
...defaultIncomeChartData
|
},
|
chart1Opts: {
|
color: ['#01B6AD'],
|
padding: [36, 20, 12, 18],
|
enableScroll: true,
|
fontSize: 12,
|
fontColor: '#999999',
|
dataLabel: true,
|
legend: {
|
show: false
|
},
|
xAxis: {
|
disableGrid: true,
|
fontColor: '#666666',
|
marginTop: 10,
|
scrollShow: true,
|
itemCount: 7,
|
rotateLabel: true,
|
rotateAngle: 35
|
},
|
yAxis: {
|
showTitle: true,
|
gridType: 'dash',
|
dashLength: 4,
|
gridColor: '#E8EEF2',
|
data: [{
|
min: 0,
|
title: '收入(元)',
|
titleFontColor: '#666666',
|
titleFontSize: 12,
|
titleOffsetY: -4
|
}]
|
},
|
extra: {
|
column: {
|
type: 'group',
|
width: 20,
|
labelPosition: 'top'
|
},
|
scrollPosition: 'right'
|
}
|
},
|
vehicleChartData: {
|
series: [{
|
data: []
|
}]
|
},
|
vehicleChartOpts: {
|
padding: [5, 5, 5, 5],
|
dataLabel: false,
|
title: {
|
name: '',
|
fontSize: 15,
|
color: '#666666'
|
},
|
subtitle: {
|
name: '',
|
fontSize: 25,
|
color: '#7cb5ec'
|
},
|
legend: {
|
show: true,
|
position: 'bottom',
|
float: 'center',
|
lineHeight: 22,
|
itemGap: 26,
|
padding: 0,
|
margin: 12,
|
fontColor: '#666666'
|
},
|
extra: {
|
ring: {
|
ringWidth: 20,
|
border: false,
|
activeOpacity: 1,
|
activeRadius: 0,
|
offsetAngle: 0
|
}
|
}
|
},
|
bikeIncomeDateType: 1,
|
bikeIncomeDateOptions: [{
|
label: '近7天',
|
value: 1
|
}, {
|
label: '近15天',
|
value: 2
|
}, {
|
label: '近30天',
|
value: 3
|
}, {
|
label: '自定义',
|
value: 4
|
}],
|
bikeIncomeStartDate: '',
|
bikeIncomeEndDate: '',
|
bikeIncomeColors: ['#11D5CF', '#3183FF', '#FFD369', '#FE8F52', '#F24679', '#F377B5', '#DB77F3', '#8477F3', '#01B6AD', '#5DBB63'],
|
vehicleSummary: []
|
};
|
},
|
onLoad() {
|
this.getOverview()
|
this.getIncomeStat()
|
this.getData()
|
},
|
methods: {
|
changeBikeIncomeDateType(value) {
|
this.bikeIncomeDateType = value
|
|
if (value === 4) {
|
this.bikeIncomeStartDate = ''
|
this.bikeIncomeEndDate = ''
|
} else {
|
this.getData()
|
}
|
},
|
onBikeIncomeStartDateChange(event) {
|
this.bikeIncomeStartDate = event.detail.value
|
|
if (this.bikeIncomeStartDate && this.bikeIncomeEndDate) {
|
this.getData()
|
}
|
},
|
onBikeIncomeEndDateChange(event) {
|
this.bikeIncomeEndDate = event.detail.value
|
|
if (this.bikeIncomeStartDate && this.bikeIncomeEndDate) {
|
this.getData()
|
}
|
},
|
buildBikeIncomeChartData(list = []) {
|
return {
|
series: [{
|
data: list.map((item, index) => ({
|
name: item.paramName || item.category || '-',
|
value: Number(item.income || 0),
|
color: this.bikeIncomeColors[index % this.bikeIncomeColors.length]
|
}))
|
}]
|
}
|
},
|
buildBikeIncomeSummary(list = []) {
|
const total = list.reduce((sum, item) => sum + Number(item.income || 0), 0)
|
|
return list.map((item, index) => ({
|
...item,
|
paramId: item.paramId || `${item.category || 'category'}-${index}`,
|
paramName: item.paramName || item.category || '-',
|
bikeCount: Number(item.bikeCount || 0),
|
income: Number(item.income || 0),
|
percent: total ? ((Number(item.income || 0) / total) * 100).toFixed(2) : '0.00'
|
}))
|
},
|
async getData() {
|
const data = {
|
dateType: this.bikeIncomeDateType
|
}
|
|
if (this.bikeIncomeDateType === 4) {
|
if (!this.bikeIncomeStartDate || !this.bikeIncomeEndDate) {
|
uni.showToast({
|
title: '请选择开始和结束日期',
|
icon: 'none'
|
})
|
return
|
}
|
|
if (this.bikeIncomeStartDate > this.bikeIncomeEndDate) {
|
uni.showToast({
|
title: '开始日期不能大于结束日期',
|
icon: 'none'
|
})
|
return
|
}
|
|
data.startDate = `${this.bikeIncomeStartDate} 00:00:00`
|
data.endDate = `${this.bikeIncomeEndDate} 23:59:59`
|
}
|
const res = await this.$u.api.bikeIncome(data)
|
|
if (res.code === 200) {
|
const list = res.data || []
|
this.vehicleSummary = this.buildBikeIncomeSummary(list)
|
this.vehicleChartData = this.buildBikeIncomeChartData(this.vehicleSummary)
|
}
|
},
|
formatChartDate(dateText) {
|
if (!dateText) {
|
return ''
|
}
|
|
const text = `${dateText}`
|
const match = text.match(/\d{4}-(\d{2})-(\d{2})/)
|
|
if (match) {
|
return `${match[1]}/${match[2]}`
|
}
|
|
if (text.length >= 10) {
|
return `${text.slice(5, 7)}/${text.slice(8, 10)}`
|
}
|
|
return text
|
},
|
formatDate(date) {
|
const year = date.getFullYear()
|
const month = `${date.getMonth() + 1}`.padStart(2, '0')
|
const day = `${date.getDate()}`.padStart(2, '0')
|
|
return `${year}-${month}-${day}`
|
},
|
formatAmount(value) {
|
if (value === '' || value === null || value === undefined) {
|
return '0'
|
}
|
|
return `${value}`
|
},
|
changeIncomeDateType(value) {
|
this.incomeDateType = value
|
|
if (value === 4) {
|
this.incomeStartDate = ''
|
this.incomeEndDate = ''
|
} else {
|
this.getIncomeStat()
|
}
|
},
|
onIncomeStartDateChange(event) {
|
this.incomeStartDate = event.detail.value
|
|
if (this.incomeStartDate && this.incomeEndDate) {
|
this.getIncomeStat()
|
}
|
},
|
onIncomeEndDateChange(event) {
|
this.incomeEndDate = event.detail.value
|
|
if (this.incomeStartDate && this.incomeEndDate) {
|
this.getIncomeStat()
|
}
|
},
|
buildIncomeChartData(dailyList = []) {
|
return {
|
categories: dailyList.map(item => this.formatChartDate(item.date)),
|
series: [{
|
name: '收入',
|
data: dailyList.map(item => Number(item.income || 0)),
|
color: '#01B6AD',
|
textColor: '#01B6AD',
|
textSize: 11
|
}]
|
}
|
},
|
async getOverview() {
|
const res = await this.$u.api.overview()
|
|
if (res.code === 200) {
|
this.overviewInfo = {
|
bikeCount: res.data ? res.data.bikeCount : 0,
|
eleBikeCount: res.data ? res.data.eleBikeCount : 0,
|
todayMembers: res.data ? res.data.todayMembers : 0,
|
totalMembers: res.data ? res.data.totalMembers : 0
|
}
|
}
|
},
|
async getIncomeStat() {
|
const data = {
|
dateType: this.incomeDateType
|
}
|
|
if (this.incomeDateType === 4) {
|
if (!this.incomeStartDate || !this.incomeEndDate) {
|
uni.showToast({
|
title: '请选择开始和结束日期',
|
icon: 'none'
|
})
|
return
|
}
|
|
if (this.incomeStartDate > this.incomeEndDate) {
|
uni.showToast({
|
title: '开始日期不能大于结束日期',
|
icon: 'none'
|
})
|
return
|
}
|
|
data.startDate = this.incomeStartDate + ' 00:00:00'
|
data.endDate = this.incomeEndDate + ' 23:59:59'
|
}
|
const res = await this.$u.api.incomeStat(data)
|
|
if (res.code === 200) {
|
const incomeInfo = res.data || {}
|
const dailyList = incomeInfo.dailyList || []
|
|
this.incomeInfo = {
|
totalIncome: incomeInfo.totalIncome || 0,
|
chainRate: incomeInfo.chainRate || 0,
|
yearOnYearRate: incomeInfo.yearOnYearRate || 0,
|
dailyList
|
}
|
this.chart1Opts = {
|
...this.chart1Opts,
|
xAxis: {
|
...this.chart1Opts.xAxis,
|
itemCount: this.incomeChartItemCount
|
}
|
}
|
this.chart1Data = this.buildIncomeChartData(dailyList)
|
}
|
}
|
}
|
}
|
</script>
|
|
<style lang="scss" scoped>
|
.box {
|
width: 100%;
|
|
.yy_cate {
|
width: 100%;
|
padding: 30rpx;
|
box-sizing: border-box;
|
background: #ffffff;
|
border-radius: 20rpx;
|
margin-top: 30rpx;
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
flex-wrap: wrap;
|
|
.yy_cate_item {
|
width: 50%;
|
display: flex;
|
align-items: center;
|
|
image {
|
width: 68rpx;
|
height: 68rpx;
|
margin-right: 24rpx;
|
}
|
|
.yy_cate_item_info {
|
flex: 1;
|
display: flex;
|
flex-direction: column;
|
|
text {
|
&:nth-child(1) {
|
font-weight: normal;
|
font-size: 32rpx;
|
color: #222222;
|
}
|
|
&:nth-child(2) {
|
font-weight: 400;
|
font-size: 24rpx;
|
color: #999999;
|
margin-top: 12rpx;
|
}
|
}
|
}
|
}
|
}
|
|
.box_xian {
|
width: 100%;
|
height: 20rpx;
|
background-color: #F8F9FB;
|
}
|
|
.box_info {
|
width: 100%;
|
padding: 30rpx;
|
box-sizing: border-box;
|
|
.box_info_title {
|
width: 100%;
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
|
.box_info_title_val {
|
display: flex;
|
align-items: center;
|
|
.box_info_title_val_l {
|
width: 6rpx;
|
height: 32rpx;
|
background: #01B6AD;
|
border-radius: 294rpx;
|
margin-right: 20rpx;
|
}
|
|
text {
|
font-weight: 600;
|
font-size: 32rpx;
|
color: #111111;
|
}
|
}
|
|
.right {
|
display: flex;
|
align-items: center;
|
|
.right_item {
|
font-weight: 400;
|
font-size: 24rpx;
|
color: #777777;
|
margin-left: 42rpx;
|
}
|
|
.active {
|
color: #01B6AD;
|
font-weight: 600;
|
}
|
}
|
}
|
|
.custom-date-row {
|
width: 100%;
|
margin-top: 20rpx;
|
display: flex;
|
align-items: center;
|
flex-wrap: wrap;
|
}
|
|
.custom-date-picker {
|
flex: 1;
|
text-align: center;
|
height: 64rpx;
|
padding: 0 20rpx;
|
line-height: 64rpx;
|
font-size: 24rpx;
|
color: #333333;
|
background: #F8F9FB;
|
border-radius: 12rpx;
|
box-sizing: border-box;
|
}
|
|
.custom-date-separator {
|
flex-shrink: 0;
|
margin: 0 12rpx;
|
font-size: 24rpx;
|
color: #777777;
|
}
|
|
.content {
|
width: 100%;
|
padding: 24rpx;
|
box-sizing: border-box;
|
background: #F8F9FB;
|
border-radius: 20rpx;
|
margin-top: 24rpx;
|
|
.content_title {
|
font-size: 26rpx;
|
color: #666666;
|
}
|
|
.content_info {
|
display: flex;
|
align-items: center;
|
margin-top: 16rpx;
|
|
.content_info_a {
|
font-weight: bold;
|
font-size: 36rpx;
|
color: #222222;
|
margin-right: 40rpx;
|
}
|
|
.content_info_b {
|
display: flex;
|
align-items: center;
|
margin-right: 24rpx;
|
|
.content_info_b_label {
|
font-weight: 400;
|
font-size: 26rpx;
|
color: #666666;
|
margin-right: 8rpx;
|
}
|
|
.content_info_b_val {
|
font-weight: bold;
|
font-size: 26rpx;
|
color: #222222;
|
display: flex;
|
align-items: center;
|
image {
|
width: 20rpx;
|
height: 20rpx;
|
}
|
}
|
}
|
}
|
}
|
|
.chart {
|
width: 100%;
|
height: 348rpx;
|
}
|
|
.chart1 {
|
position: relative;
|
width: 100%;
|
padding: 24rpx 0 8rpx;
|
box-sizing: border-box;
|
margin-top: 24rpx;
|
|
.ring-center {
|
position: absolute;
|
left: 50%;
|
top: 50%;
|
transform: translate(-50%, -50%);
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
|
&__label {
|
font-size: 26rpx;
|
color: #6C717A;
|
line-height: 36rpx;
|
}
|
|
&__value {
|
margin-top: 10rpx;
|
font-size: 44rpx;
|
font-weight: 700;
|
color: #222222;
|
line-height: 60rpx;
|
}
|
}
|
}
|
|
.table {
|
width: 100%;
|
margin-top: 40rpx;
|
|
.table_empty {
|
padding: 40rpx 0 10rpx;
|
font-size: 26rpx;
|
color: #999999;
|
text-align: center;
|
}
|
|
.table_head {
|
width: 100%;
|
height: 80rpx;
|
display: flex;
|
align-items: center;
|
background: #F8F9FB;
|
border-radius: 8rpx 8rpx 0rpx 0rpx;
|
border: 1rpx solid #EEEEEE;
|
|
.table_head_row {
|
line-height: 80rpx;
|
font-weight: 400;
|
font-size: 28rpx;
|
color: #777777;
|
|
&:nth-child(1) {
|
flex: 1;
|
}
|
|
&:nth-child(2) {
|
flex: 0.8;
|
}
|
|
&:nth-child(3) {
|
flex: 0.6;
|
}
|
}
|
}
|
|
.table_content {
|
width: 100%;
|
height: 80rpx;
|
display: flex;
|
align-items: center;
|
|
.table_content_row {
|
line-height: 80rpx;
|
font-weight: 400;
|
font-size: 28rpx;
|
color: #333333;
|
|
&:nth-child(1) {
|
flex: 1;
|
}
|
|
&:nth-child(2) {
|
flex: 0.8;
|
}
|
|
&:nth-child(3) {
|
flex: 0.6;
|
}
|
}
|
}
|
}
|
}
|
}
|
</style>
|