MrShi
8 小时以前 658107c8a09e2ef74bab1ad7114eeaf367848a25
bicycle/pages/businessOverview/businessOverview.vue
@@ -4,28 +4,28 @@
         <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>329</text>
               <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>143</text>
               <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>700</text>
               <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>12839.23</text>
               <text>{{ overviewInfo.eleBikeCount || 0 }}</text>
               <text>电动车数量</text>
            </view>
         </view>
@@ -38,28 +38,45 @@
               <text>收入统计</text>
            </view>
            <view class="right">
               <view class="right_item active">近7天</view>
               <view class="right_item">近15天</view>
               <view class="right_item">近30天</view>
               <view class="right_item">自定义</view>
               <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">34500.00</view>
               <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">3.12%</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">11.12%</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 canvasId="incomeColumnChart" type="column" :opts="chart1Opts" :chartData="chart1Data" />
            <qiun-data-charts :key="incomeChartKey" :ontouch="true" canvasId="incomeColumnChart" type="column" :opts="chart1Opts"
               :chartData="chart1Data" />
         </view>
      </view>
      <view class="box_xian"></view>
@@ -70,14 +87,29 @@
               <text>收入车型分析</text>
            </view>
            <view class="right">
               <view class="right_item active">近7天</view>
               <view class="right_item">近15天</view>
               <view class="right_item">近30天</view>
               <view class="right_item">自定义</view>
               <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 canvasId="vehicleRingChart" type="ring" :opts="vehicleChartOpts" :chartData="vehicleChartData" />
            <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">
@@ -85,44 +117,109 @@
               <view class="table_head_row">车辆收入(元)</view>
               <view class="table_head_row">占比(%)</view>
            </view>
            <view class="table_content">
               <view class="table_content_row">单人自行车</view>
               <view class="table_content_row">8393.45</view>
               <view class="table_content_row">30.00</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_content">
               <view class="table_content_row">单人自行车</view>
               <view class="table_content_row">8393.45</view>
               <view class="table_content_row">30.00</view>
            </view>
            <view class="table_empty" v-if="!vehicleSummary.length">暂无收入车型分析数据</view>
         </view>
      </view>
   </view>
</template>
<script>
   export default {
      computed: {
         totalVehicleCount() {
            return this.vehicleChartData.series[0].data.reduce((sum, item) => sum + item.value, 0)
   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: {
               categories: ['双排车', '单人车', '亲子车', '山地车', '助力车'],
               series: [{
                  name: '收入',
                  data: [8.5, 6.8, 5.2, 3.9, 2.6],
                  color: '#01B6AD',
                  textColor: '#01B6AD',
                  textSize: 11
               }]
               ...defaultIncomeChartData
            },
            chart1Opts: {
               color: ['#01B6AD'],
               padding: [36, 20, 12, 18],
               enableScroll: false,
               enableScroll: true,
               fontSize: 12,
               fontColor: '#999999',
               dataLabel: true,
@@ -132,7 +229,11 @@
               xAxis: {
                  disableGrid: true,
                  fontColor: '#666666',
                  marginTop: 10
                  marginTop: 10,
                  scrollShow: true,
                  itemCount: 7,
                  rotateLabel: true,
                  rotateAngle: 35
               },
               yAxis: {
                  showTitle: true,
@@ -141,7 +242,7 @@
                  gridColor: '#E8EEF2',
                  data: [{
                     min: 0,
                     title: '收入(万元)',
                     title: '收入(元)',
                     titleFontColor: '#666666',
                     titleFontSize: 12,
                     titleOffsetY: -4
@@ -152,50 +253,28 @@
                     type: 'group',
                     width: 20,
                     labelPosition: 'top'
                  }
                  },
                  scrollPosition: 'right'
               }
            },
            vehicleChartData: {
               series: [{
                  data: [{
                     name: '单人自行车',
                     value: 700,
                     color: '#11D5CF'
                  }, {
                     name: '单人亲子车',
                     value: 12839,
                     color: '#3183FF'
                  }, {
                     name: '前后双人车',
                     value: 12839,
                     color: '#FFD369'
                  }, {
                     name: '双人亲子车',
                     value: 12839,
                     color: '#FE8F52'
                  }, {
                     name: '单排家庭车',
                     value: 12839,
                     color: '#F24679'
                  }, {
                     name: '双排四人车',
                     value: 12839,
                     color: '#F377B5'
                  }, {
                     name: '双排三座电动代步车',
                     value: 12839,
                     color: '#DB77F3'
                  }, {
                     name: '双排四座电动代步车',
                     value: 12839,
                     color: '#8477F3'
                  }]
                  data: []
               }]
            },
            vehicleChartOpts: {
               // color: ['#01B6AD', '#5ED9D2'],
               padding: [5,5,5,5],
               padding: [5, 5, 5, 5],
               dataLabel: false,
               title: {
                  name: '',
                  fontSize: 15,
                  color: '#666666'
               },
               subtitle: {
                  name: '',
                  fontSize: 25,
                  color: '#7cb5ec'
               },
               legend: {
                  show: true,
                  position: 'bottom',
@@ -216,42 +295,239 @@
                  }
               }
            },
            vehicleSummary: [
               {
                  label: '单人自行车',
                  value: '700辆',
                  color: '#11D5CF'
               }, {
                  label: '单人亲子车',
                  value: '12839辆',
                  color: '#3183FF'
               }, {
                  label: '前后双人车',
                  value: '12839辆',
                  color: '#FFD369'
               }, {
                  label: '双人亲子车',
                  value: '12839辆',
                  color: '#FE8F52'
               }, {
                  label: '单排家庭车',
                  value: '12839辆',
                  color: '#F24679'
               }, {
                  label: '双排四人车',
                  value: '12839辆',
                  color: '#F377B5'
               }, {
                  label: '双排三座电动代步车',
                  value: '12839辆',
                  color: '#DB77F3'
               }, {
                  label: '双排四座电动代步车',
                  value: '12839辆',
                  color: '#8477F3'
               }
            ]
            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>
@@ -360,6 +636,34 @@
            }
         }
         .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;
@@ -401,6 +705,12 @@
                     font-weight: bold;
                     font-size: 26rpx;
                     color: #222222;
                     display: flex;
                     align-items: center;
                     image {
                        width: 20rpx;
                        height: 20rpx;
                     }
                  }
               }
            }
@@ -412,6 +722,7 @@
         }
         .chart1 {
            position: relative;
            width: 100%;
            padding: 24rpx 0 8rpx;
            box-sizing: border-box;
@@ -429,7 +740,7 @@
               &__label {
                  font-size: 26rpx;
                  color: #666666;
                  color: #6C717A;
                  line-height: 36rpx;
               }
@@ -437,17 +748,24 @@
                  margin-top: 10rpx;
                  font-size: 44rpx;
                  font-weight: 700;
                  color: #111111;
                  color: #222222;
                  line-height: 60rpx;
               }
            }
         }
         .table {
            width: 100%;
            margin-top: 40rpx;
            .table {
               width: 100%;
               margin-top: 40rpx;
            .table_head {
               .table_empty {
                  padding: 40rpx 0 10rpx;
                  font-size: 26rpx;
                  color: #999999;
                  text-align: center;
               }
               .table_head {
               width: 100%;
               height: 80rpx;
               display: flex;