|
@@ -1,4 +1,5 @@
|
|
|
-import { computed, type Ref, ref } from 'vue'
|
|
|
+import { type Ref, ref } from 'vue'
|
|
|
+// import type { EChartsOption } from 'echarts/types/dist/shared'
|
|
|
import type { EChartsOption } from 'echarts/types/dist/shared'
|
|
|
import axiosInstance from '@/utils/axios/axiosInstance.ts'
|
|
|
import type { ResponseInfo } from '@/types/res.ts'
|
|
@@ -28,7 +29,8 @@ export function useUserBehaviorChart(
|
|
|
chartNeedFields: Array<string>,
|
|
|
isPie: Ref<boolean>,
|
|
|
chartInstance: Ref<MChartType | null>,
|
|
|
- isLog: Ref<boolean>
|
|
|
+ isLog: Ref<boolean>,
|
|
|
+ chartsOptions: Ref<EChartsOption | null>
|
|
|
) {
|
|
|
const chartInfo = ref<ChartInfo>({
|
|
|
PieChart: [],
|
|
@@ -38,13 +40,57 @@ export function useUserBehaviorChart(
|
|
|
}
|
|
|
})
|
|
|
|
|
|
+ // 生成接近正方形的行列数
|
|
|
+ function computeGrid(n: number) {
|
|
|
+ const cols = Math.ceil(Math.sqrt(n))
|
|
|
+ const rows = Math.ceil(n / cols)
|
|
|
+ return { rows, cols }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据行列数和索引计算中心点,返回百分比数字
|
|
|
+ function generateCenters(n: number): { x: number; y: number }[] {
|
|
|
+ const { rows, cols } = computeGrid(n)
|
|
|
+ const centers: { x: number; y: number }[] = []
|
|
|
+ for (let i = 0; i < n; i++) {
|
|
|
+ const row = Math.floor(i / cols)
|
|
|
+ const col = i % cols
|
|
|
+ const x = ((col + 0.5) / cols) * 80
|
|
|
+ const y = ((row + 0.5) / rows) * 100
|
|
|
+ centers.push({ x, y })
|
|
|
+ }
|
|
|
+ return centers
|
|
|
+ }
|
|
|
+
|
|
|
+ const transformData = (data: { name: string; value: number }[]) => {
|
|
|
+ const result: {
|
|
|
+ [key: string]: {
|
|
|
+ [key: string]: number
|
|
|
+ }
|
|
|
+ } = {}
|
|
|
+
|
|
|
+ data.forEach(({ name, value }) => {
|
|
|
+ const [timePart, adPart] = name.split('&&')
|
|
|
+ const timeKey = timePart.trim()
|
|
|
+
|
|
|
+ const adKey = adPart.trim()
|
|
|
+
|
|
|
+ if (!result[timeKey]) {
|
|
|
+ result[timeKey] = {}
|
|
|
+ }
|
|
|
+
|
|
|
+ result[timeKey][adKey] = value
|
|
|
+ })
|
|
|
+
|
|
|
+ return result
|
|
|
+ }
|
|
|
+
|
|
|
const updateChartData = async () => {
|
|
|
try {
|
|
|
if (chartNeedFields.length > 0) {
|
|
|
- const hasFilter = chartNeedFields.every((item) => {
|
|
|
+ const noFilter = chartNeedFields.every((item) => {
|
|
|
return queryFormData.value[item] === ''
|
|
|
})
|
|
|
- if (hasFilter) {
|
|
|
+ if (noFilter) {
|
|
|
const tip = filterInfo
|
|
|
.filter((item) => {
|
|
|
return chartNeedFields.includes(item.name as any)
|
|
@@ -64,13 +110,12 @@ export function useUserBehaviorChart(
|
|
|
|
|
|
const params = {} as any
|
|
|
Object.assign(params, queryFormData.value)
|
|
|
- // if (queryFormData.value.createTime) {
|
|
|
- // params.createTime = queryFormData.value.createTime
|
|
|
- // .map((item: any) => {
|
|
|
- // return new Date(item).getTime().toString()
|
|
|
- // })
|
|
|
- // .join(',')
|
|
|
- // }
|
|
|
+ if (queryFormData.value.createTime) {
|
|
|
+ params.createTime = queryFormData.value.createTime
|
|
|
+ .map((item: Date) => new Date(item).getTime() / 1000)
|
|
|
+
|
|
|
+ .join(',')
|
|
|
+ }
|
|
|
|
|
|
const res = (await axiosInstance.post(url, params)) as ResponseInfo
|
|
|
if (res.code !== 0) {
|
|
@@ -97,85 +142,116 @@ export function useUserBehaviorChart(
|
|
|
labelList: [],
|
|
|
valueList: []
|
|
|
}
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- chartInfo.value.PieChart = data.map((item) => ({
|
|
|
- name: item.name,
|
|
|
- value: item.count
|
|
|
- }))
|
|
|
- chartInfo.value.BarChart = {
|
|
|
- labelList: data.map((item) => item.name),
|
|
|
- valueList: data.map((item) => item.count)
|
|
|
+ } else {
|
|
|
+ chartInfo.value.PieChart = data.map((item) => ({
|
|
|
+ name: item.name,
|
|
|
+ value: item.count
|
|
|
+ }))
|
|
|
+ chartInfo.value.BarChart = {
|
|
|
+ labelList: data.map((item) => item.name),
|
|
|
+ valueList: data.map((item) => item.count)
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
+ updateOptions()
|
|
|
} catch (err) {
|
|
|
console.log(err)
|
|
|
} finally {
|
|
|
- console.log(chartInstance)
|
|
|
if (chartInstance.value) {
|
|
|
chartInstance.value.stopLoading()
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- const pieChartOptions = computed<EChartsOption>(() => {
|
|
|
- const colors = generateUniqueColors(chartInfo.value.PieChart.length)
|
|
|
+ const createPieChartOptions = (): EChartsOption => {
|
|
|
const pieData = chartInfo.value.PieChart
|
|
|
- const seriesData: {
|
|
|
- [key: string]: any[]
|
|
|
- } = {}
|
|
|
- pieData.forEach((item) => {
|
|
|
- const name = item.name.split('&&')[0]
|
|
|
- if (!seriesData[name]) {
|
|
|
- seriesData[name] = []
|
|
|
+
|
|
|
+ // 判断是不是只有一个筛选条件
|
|
|
+ let hasCount = 0
|
|
|
+ for (const [k, v] of Object.entries(queryFormData.value)) {
|
|
|
+ if (chartNeedFields.includes(k) && v) {
|
|
|
+ hasCount++
|
|
|
}
|
|
|
- seriesData[name].push(item)
|
|
|
- })
|
|
|
- const resultSeries: any[] = []
|
|
|
- let count = 1
|
|
|
- for (const [k, v] of Object.entries(seriesData)) {
|
|
|
- console.log(`${count * 5}% `, `${count * 10}% `)
|
|
|
- resultSeries.push({
|
|
|
- name: k,
|
|
|
- type: 'pie',
|
|
|
- radius: [`${count * 10}% `, `${(count + 1) * 10}% `],
|
|
|
- center: ['50%', '50%'],
|
|
|
- avoidLabelOverlap: false,
|
|
|
- itemStyle: {
|
|
|
- borderRadius: 10,
|
|
|
- borderColor: '#fff',
|
|
|
- borderWidth: 1
|
|
|
+ }
|
|
|
+ if (hasCount === 1) {
|
|
|
+ return {
|
|
|
+ color: generateUniqueColors(pieData.length), // 根据条目数生成配色
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'item',
|
|
|
+ formatter: '{b}: {c} ({d}%)'
|
|
|
},
|
|
|
- label: {
|
|
|
- show: false,
|
|
|
- position: 'center'
|
|
|
+ legend: {
|
|
|
+ orient: 'vertical',
|
|
|
+ left: 10,
|
|
|
+ top: 'center',
|
|
|
+ data: pieData.map((item) => item.name)
|
|
|
},
|
|
|
- emphasis: {
|
|
|
- label: {
|
|
|
- show: true,
|
|
|
- fontSize: 20,
|
|
|
- fontWeight: 'bold'
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: '在线时长分布',
|
|
|
+ type: 'pie',
|
|
|
+ radius: ['40%', '70%'], // 内外半径,也可以用 '50%' 单值
|
|
|
+ center: ['50%', '50%'], // 居中
|
|
|
+ avoidLabelOverlap: false,
|
|
|
+ itemStyle: {
|
|
|
+ borderRadius: 6,
|
|
|
+ borderColor: '#fff',
|
|
|
+ borderWidth: 2
|
|
|
+ },
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ position: 'outside',
|
|
|
+ formatter: '{b}: {c} ({d}%)'
|
|
|
+ },
|
|
|
+ emphasis: {
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ fontSize: 18,
|
|
|
+ fontWeight: 'bold'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ data: pieData.map(({ name, value }) => ({
|
|
|
+ name,
|
|
|
+ value
|
|
|
+ }))
|
|
|
}
|
|
|
- },
|
|
|
- labelLine: {
|
|
|
- show: false
|
|
|
- },
|
|
|
- data: v
|
|
|
- })
|
|
|
- count++
|
|
|
+ ]
|
|
|
+ }
|
|
|
}
|
|
|
- console.log(resultSeries)
|
|
|
+ // if(queryFormData.value.)
|
|
|
+ const tsData = transformData(pieData)
|
|
|
+
|
|
|
+ const timeKeys = Object.keys(tsData)
|
|
|
+ const count = timeKeys.length
|
|
|
+ const centers = generateCenters(count)
|
|
|
+
|
|
|
+ const colors = generateUniqueColors(count)
|
|
|
+ // 1. 生成 series
|
|
|
+ const series = timeKeys.map((timeKey, idx) => ({
|
|
|
+ name: timeKey,
|
|
|
+ type: 'pie',
|
|
|
+ radius: '30%', // 每个饼图半径
|
|
|
+ center: [`${centers[idx].x}%`, `${centers[idx].y}%`],
|
|
|
+ data: Object.entries(tsData[timeKey]).map(([adKey, val]) => ({
|
|
|
+ name: adKey.split(':')[1],
|
|
|
+ value: val
|
|
|
+ })),
|
|
|
+ label: { formatter: '{b}: {c} ({d}%)' }
|
|
|
+ }))
|
|
|
+ const titles = timeKeys.map((timeKey, idx) => {
|
|
|
+ const { x, y } = centers[idx]
|
|
|
+
|
|
|
+ return {
|
|
|
+ text: timeKey,
|
|
|
+ left: `${x}%`,
|
|
|
+ top: `${y - 3}%`,
|
|
|
+ textAlign: 'center'
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
return {
|
|
|
color: colors,
|
|
|
- // tooltip: {
|
|
|
- // trigger: 'item',
|
|
|
- // formatter: (params: any) => {
|
|
|
- // const data = chartInfo.value.PieChart[params.dataIndex]
|
|
|
- // return `${data.name}<br/>
|
|
|
- // 数量: ${data.value}`
|
|
|
- // }
|
|
|
- // },
|
|
|
+ title: titles,
|
|
|
tooltip: {
|
|
|
trigger: 'item',
|
|
|
formatter: '{a} <br/>{b}: {c} ({d})'
|
|
@@ -186,31 +262,24 @@ export function useUserBehaviorChart(
|
|
|
right: 10,
|
|
|
top: 20,
|
|
|
bottom: 20
|
|
|
- // top: '8%',
|
|
|
- // left: 'center',
|
|
|
- // itemGap: 35,
|
|
|
- // textStyle: {
|
|
|
- // fontSize: 14
|
|
|
- // },
|
|
|
- // padding: [0, 50]
|
|
|
},
|
|
|
- series: resultSeries
|
|
|
- }
|
|
|
- })
|
|
|
+ series: series
|
|
|
+ } as EChartsOption
|
|
|
+ }
|
|
|
|
|
|
- const barChartOptions = computed<EChartsOption>(() => {
|
|
|
+ const createBarChartsOptions = (): EChartsOption => {
|
|
|
const barChartInfo = chartInfo.value.BarChart
|
|
|
const yType: any = isLog.value ? 'log' : 'value'
|
|
|
return {
|
|
|
xAxis: {
|
|
|
type: 'category',
|
|
|
- data: barChartInfo.labelList,
|
|
|
- axisLabel: {
|
|
|
- interval: 30,
|
|
|
- // rotate: 30,
|
|
|
- // width: 10, // 增加标签宽度
|
|
|
- overflow: 'truncate' // 过长时显示省略号
|
|
|
- }
|
|
|
+ data: barChartInfo.labelList
|
|
|
+ // axisLabel: {
|
|
|
+ // interval: 30,
|
|
|
+ // // rotate: 30,
|
|
|
+ // width: 10, // 增加标签宽度
|
|
|
+ // overflow: 'truncate' // 过长时显示省略号
|
|
|
+ // }
|
|
|
},
|
|
|
yAxis: {
|
|
|
type: yType,
|
|
@@ -234,7 +303,7 @@ export function useUserBehaviorChart(
|
|
|
},
|
|
|
{
|
|
|
type: 'slider',
|
|
|
- show: true,
|
|
|
+ show: false,
|
|
|
yAxisIndex: 0,
|
|
|
filterMode: 'empty',
|
|
|
width: 12,
|
|
@@ -273,26 +342,28 @@ export function useUserBehaviorChart(
|
|
|
show: true,
|
|
|
position: 'top',
|
|
|
formatter: '{c}'
|
|
|
- },
|
|
|
- itemStyle: {
|
|
|
- // color: (params) => {
|
|
|
- // return colors[params.dataIndex]
|
|
|
- // }
|
|
|
}
|
|
|
}
|
|
|
]
|
|
|
}
|
|
|
- })
|
|
|
+ }
|
|
|
|
|
|
- const chartOptions = computed(() => {
|
|
|
+ const updateOptions = () => {
|
|
|
const pieChartInfo = chartInfo.value.PieChart.length ?? null
|
|
|
const barChartInfo = chartInfo.value.BarChart.valueList.length ?? null
|
|
|
- if (!pieChartInfo && !barChartInfo) return null
|
|
|
- return isPie.value ? pieChartOptions.value : barChartOptions.value
|
|
|
- })
|
|
|
+
|
|
|
+ if (!pieChartInfo && !barChartInfo) {
|
|
|
+ chartsOptions.value = null
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const pieChartOptions = createPieChartOptions()
|
|
|
+ const barChartOptions = createBarChartsOptions()
|
|
|
+
|
|
|
+ chartsOptions.value = isPie.value ? pieChartOptions : barChartOptions
|
|
|
+ }
|
|
|
|
|
|
return {
|
|
|
updateChartData,
|
|
|
- chartOptions
|
|
|
+ updateOptions
|
|
|
}
|
|
|
}
|