ylj20011123 6e2bd12f8c update
2025-10-29 14:47:21 +08:00

331 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="customer-group">
<!-- 客群特征分析标题 -->
<view class="section-header">
<text class="section-title">客群特征分析</text>
</view>
<!-- 客群特征分析图表 -->
<view class="chart-container">
<!-- 图表加载效果 -->
<ChartLoading v-if="!hasChartData" text="数据加载中..." />
<!-- 实际图表 -->
<QiunDataCharts v-else type="bubble" :opts="chartOpts" :chartData="chartData" :canvas2d="true"
:inScrollView="true" canvasId="customerGroupChart" :animation="false" :ontap="true" :ontouch="true" />
</view>
</view>
</template>
<script>
import QiunDataCharts from './qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue'
import ChartLoading from './ChartLoading.vue'
import request from "@/util/index.js";
import moment from 'moment'
export default {
components: {
QiunDataCharts,
ChartLoading
},
data() {
return {
// 图表原始数据
rawData: {
seriesData: [],
xAxisMin: 0,
xAxisMax: 100,
yAxisMin: 0,
yAxisMax: 100
}
}
},
props: {
selectTime: {
type: String,
default: ""
},
},
computed: {
// 判断图表数据是否已加载
hasChartData() {
return this.rawData.seriesData && this.rawData.seriesData.length > 0;
},
// 图表数据
chartData() {
return {
series: this.rawData.seriesData
}
},
// 图表配置
chartOpts() {
return {
padding: [15, 15, 0, 15],
dataLabel: false,
xAxis: {
disableGrid: false,
gridColor: '#E6E6E6',
gridType: 'solid',
min: this.rawData.xAxisMin,
max: this.rawData.xAxisMax,
splitNumber: 5,
tickCount: 5,
name: '年龄',
nameLocation: 'middle',
nameGap: 25,
titleFontColor: '#666666',
axisLine: true,
axisLineColor: '#666666',
formatter: (value) => {
// 强制显示为整数
return Math.round(value).toString()
}
},
yAxis: {
disableGrid: false,
gridColor: '#E6E6E6',
gridType: 'solid',
data: [{
min: this.rawData.yAxisMin,
max: this.rawData.yAxisMax,
splitNumber: 5,
title: '交易金额(元)',
titleFontColor: '#666666',
axisLineColor: '#666666',
formatter: (value) => {
// 强制显示为整数
return Math.round(value).toString()
}
}]
},
legend: {
show: true,
position: 'bottom',
float: 'center',
backgroundColor: 'rgba(0,0,0,0)',
borderColor: 'rgba(0,0,0,0)',
fontSize: 12,
fontColor: '#333333',
margin: 0,
padding: 0,
itemGap: 10,
textAlign: 'left'
},
extra: {
bubble: {
border: 2,
opacity: 0.8,
},
}
}
}
},
onReady() {
this.handleGetCustomerGroupData()
},
methods: {
// 获取客群特征数据
async handleGetCustomerGroupData() {
const req = {
provinceCode: "530000",
statisticsMonth: this.selectTime ? moment(this.selectTime).subtract(1, 'M').format('YYYYMM') : moment().subtract(1, 'M').format('YYYYMM'),
serverpartId: "", // 暂时为空如果需要传入服务区ID可以在这里添加
}
const data = await this.getCustomerGroupRatio(req);
// 处理数据
this.processChartData(data.Result_Data.List)
},
// 发起API请求获取客群特征分析数据
async getCustomerGroupRatio(params) {
const data = await request.$webGet(
"CommercialApi/Customer/GetCustomerGroupRatio",
params
);
return data || []
},
// 处理图表数据
processChartData(data) {
let seriesData = []
const colors = ['#46B8F3', '#FF5E5E'] // 蓝色男性,红色女性
// 收集所有数据点用于计算坐标轴范围
let allXValues = []
let allYValues = []
// 处理数据:获取客群特征数据
if (data && data.length > 0) {
data.forEach((item, index) => {
if (item.data && item.data.length > 0) {
let bubblePoints = []
item.data.forEach((subItem) => {
// subItem 格式: [年龄, 交易金额, 占比]
// 转换为气泡图数据格式: [年龄, 交易金额, 半径, 文本标签]
// bubble 图表要求数组格式: [x值, y值, 半径r, 文本t]
let xValue = Number(subItem[0]) // 年龄作为x轴
let yValue = Number(subItem[1]) // 交易金额作为y轴
allXValues.push(xValue)
allYValues.push(yValue)
let bubblePoint = [
xValue,
yValue,
Number(Math.sqrt(subItem[2])) * 5, // 气泡半径,基于占比计算
`${item.name}: ${subItem[2]}%` // 文本标签:性别和占比
]
bubblePoints.push(bubblePoint)
})
// 为每个性别创建一个系列
seriesData.push({
name: item.name,
color: colors[index] || '#999999',
data: bubblePoints
})
}
})
}
// 动态计算坐标轴范围
let xAxisMin, xAxisMax, yAxisMin, yAxisMax
if (allXValues.length > 0) {
let minX = Math.min(...allXValues)
let maxX = Math.max(...allXValues)
let xRange = maxX - minX
// 计算合适的10的倍数刻度
xAxisMin = this.calculateNiceAxisMin(minX, xRange)
xAxisMax = this.calculateNiceAxisMax(maxX, xRange)
// 如果范围太小,设置最小范围
if (xRange < 5) {
xAxisMin = Math.floor(minX / 5) * 5
xAxisMax = Math.ceil(maxX / 5) * 5
}
} else {
xAxisMin = 15
xAxisMax = 60
}
if (allYValues.length > 0) {
let minY = Math.min(...allYValues)
let maxY = Math.max(...allYValues)
let yRange = maxY - minY
// 计算合适的10的倍数刻度
yAxisMin = this.calculateNiceAxisMin(minY, yRange, true)
yAxisMax = this.calculateNiceAxisMax(maxY, yRange)
// 如果范围太小,设置最小范围
if (yRange < 10) {
yAxisMin = 0
yAxisMax = Math.ceil(maxY / 10) * 10
}
} else {
yAxisMin = 0
yAxisMax = 130
}
// 更新图表数据
this.rawData = {
seriesData: seriesData,
xAxisMin: xAxisMin,
xAxisMax: xAxisMax,
yAxisMin: yAxisMin,
yAxisMax: yAxisMax
}
},
// 计算合适的坐标轴最小值10的倍数
calculateNiceAxisMin(minValue, range, isYAxis = false) {
if (isYAxis) {
// Y轴最小值通常为0
return 0
} else {
// X轴年龄调整到5的倍数
let niceMin = Math.floor(minValue / 5) * 5
// 如果最小值接近边界,再向下调整一个单位
if (minValue - niceMin < 2) {
niceMin = Math.max(0, niceMin - 5)
}
return niceMin
}
},
// 计算合适的坐标轴最大值10的倍数
calculateNiceAxisMax(maxValue, range) {
// 根据数据范围选择合适的步长,确保是整数
let step = this.calculateNiceStep(range)
let niceMax = Math.ceil(maxValue / step) * step
// 确保至少有几个刻度
if (niceMax <= maxValue + step) {
niceMax += step
}
return niceMax
},
// 计算合适的步长
calculateNiceStep(range) {
if (range <= 10) return 2
if (range <= 20) return 5
if (range <= 50) return 10
if (range <= 100) return 20
return 20
},
}
}
</script>
<style scoped lang="less">
@primary-color: #46B8F3;
@secondary-color: #3CD495;
@danger-color: #FF5E5E;
@success-color: #52C41A;
@text-primary: #333;
@text-secondary: #666;
@text-light: #999;
@bg-white: #ffffff;
@border-radius: 16rpx;
@shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
.customer-group {
margin-top: 24rpx;
.section-header {
display: flex;
align-items: center;
margin-bottom: 20rpx;
padding: 0 8rpx;
.section-title {
font-size: 30rpx;
font-weight: 600;
color: @text-primary;
}
}
.chart-container {
background: @bg-white;
border-radius: @border-radius;
padding: 24rpx;
box-shadow: @shadow;
margin-bottom: 24rpx;
width: 100%;
box-sizing: border-box;
height: 400rpx;
}
}
</style>