<template>
|
<div>
|
<PanelHeader title="销售品分布" />
|
<div class="main-panel panel-item-customers">
|
<CarouselCards :items="cardItems" :visible-count="3" />
|
<div class="pie-chart-wrapper" ref="pieWrapperRef">
|
<div class="pie-background" ref="pieBackgroundRef"></div>
|
<Echarts
|
ref="echartsRef"
|
:chartStyle="chartStyle"
|
:legend="pieLegend"
|
:series="pieSeries"
|
:tooltip="pieTooltip"
|
:color="pieColors"
|
:options="pieOptions"
|
style="height: 320px"
|
/>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, onMounted, onBeforeUnmount, computed, inject, watch } from 'vue'
|
import { productSalesAnalysis } from '@/api/viewIndex.js'
|
import PanelHeader from './PanelHeader.vue'
|
import CarouselCards from './CarouselCards.vue'
|
import Echarts from '@/components/Echarts/echarts.vue'
|
import { useChartBackground } from '@/hooks/useChartBackground.js'
|
|
const pieWrapperRef = ref(null)
|
const pieBackgroundRef = ref(null)
|
|
/**
|
* @introduction 把数组中key值相同的那一项提取出来,组成一个对象
|
* @param {参数类型} array 传入的数组 [{a:"1",b:"2"},{a:"2",b:"3"}]
|
* @param {参数类型} key 属性名 a
|
* @return {返回类型说明}
|
*/
|
function array2obj(array, key) {
|
const resObj = {}
|
for (let i = 0; i < array.length; i++) {
|
resObj[array[i][key]] = array[i]
|
}
|
return resObj
|
}
|
|
const chartStyle = {
|
width: '100%',
|
height: '100%',
|
}
|
|
const echartsRef = ref(null)
|
const pieDatas = ref([])
|
const pieColors = ['#D1E4F5', '#5782F7', '#2F67EF', '#82BAFF', '#43e8fc', '#27EBE7']
|
|
const pieObjData = computed(() => array2obj(pieDatas.value, 'name'))
|
|
const pieLegend = computed(() => {
|
const data = pieDatas.value.map((d, idx) => ({
|
name: d.name,
|
icon: 'circle',
|
textStyle: {
|
fontSize: 18,
|
color: pieColors[idx % pieColors.length],
|
},
|
}))
|
|
return {
|
orient: 'vertical',
|
top: 'center',
|
left: '52%',
|
itemGap: 30,
|
data: data,
|
formatter: function (name) {
|
const item = pieObjData.value[name]
|
if (!item) return name
|
return `{title|${name}}{value|${item.value}}{unit|元}{percent|${item.rate}}{unit|%}`
|
},
|
textStyle: {
|
rich: {
|
value: {
|
color: '#43e8fc',
|
fontSize: 14,
|
fontWeight: 600,
|
padding: [0, 0, 0, 10],
|
},
|
unit: {
|
color: '#82baff',
|
fontSize: 12,
|
fontWeight: 600,
|
padding: [0, 10, 0, 0],
|
},
|
percent: {
|
color: '#43e8fc',
|
fontSize: 14,
|
fontWeight: 600,
|
padding: [0, 0, 0, 0],
|
},
|
title: {
|
fontSize: 12,
|
padding: [0, 0, 0, 0],
|
},
|
},
|
},
|
}
|
})
|
|
const pieTooltip = {
|
trigger: 'item',
|
formatter: '{a} <br/>{b} : {c}元 ({d}%)',
|
}
|
|
const pieSeries = computed(() => [
|
{
|
name: '产品销售金额分析',
|
type: 'pie',
|
radius: '60%',
|
center: ['25%', '50%'],
|
itemStyle: {
|
borderColor: '#0a1c3a',
|
borderWidth: 2,
|
},
|
label: {
|
show: false
|
},
|
minAngle: 15,
|
data: pieDatas.value,
|
animationType: 'scale',
|
animationEasing: 'elasticOut',
|
animationDelay: function () {
|
return Math.random() * 200
|
},
|
},
|
])
|
|
const pieOptions = {
|
backgroundColor: 'transparent',
|
textStyle: { color: '#B8C8E0' },
|
}
|
|
const cardItems = ref([])
|
|
// 使用封装的背景位置调整方法(与其他文件保持一致)
|
const { init: initBackground, cleanup: cleanupBackground } = useChartBackground({
|
wrapperRef: pieWrapperRef,
|
backgroundRef: pieBackgroundRef,
|
left: '25%', // 图表中心 X 是 25%
|
top: '50%', // 图表中心 Y 是 50%
|
offsetX: '-51.5%', // X 轴偏移
|
offsetY: '-50%', // Y 轴偏移
|
watchData: pieDatas // 监听数据变化,自动调整位置
|
})
|
|
const fetchData = () => {
|
productSalesAnalysis()
|
.then((res) => {
|
if (res.code === 200 && Array.isArray(res.data)) {
|
const items = res.data
|
cardItems.value = items.map((item) => ({
|
label: item.name,
|
value: item.value,
|
unit: '元',
|
rate: item.rate,
|
}))
|
pieDatas.value = items.map((item) => ({
|
name: item.name,
|
value: parseFloat(item.value) || 0,
|
rate: item.rate,
|
}))
|
}
|
})
|
.catch((err) => {
|
console.error('获取产品销售金额分析失败:', err)
|
})
|
}
|
|
const dataDashboardRefreshTick = inject('dataDashboardRefreshTick', null)
|
if (dataDashboardRefreshTick) {
|
watch(dataDashboardRefreshTick, () => {
|
fetchData()
|
})
|
}
|
|
onMounted(() => {
|
fetchData()
|
initBackground()
|
})
|
|
onBeforeUnmount(() => {
|
cleanupBackground()
|
})
|
</script>
|
|
<style scoped>
|
.main-panel {
|
display: flex;
|
flex-direction: column;
|
gap: 20px;
|
}
|
|
.panel-item-customers {
|
border: 1px solid #1a58b0;
|
padding: 18px;
|
width: 100%;
|
height: 449px;
|
position: relative;
|
overflow: hidden;
|
}
|
|
/* 面板角落装饰 */
|
.panel-item-customers::before,
|
.panel-item-customers::after {
|
content: '';
|
position: absolute;
|
width: 15px;
|
height: 15px;
|
border-color: rgba(0, 212, 255, 0.5);
|
border-style: solid;
|
pointer-events: none;
|
}
|
|
.panel-item-customers::before {
|
top: -1px;
|
left: -1px;
|
border-width: 2px 0 0 2px;
|
}
|
|
.panel-item-customers::after {
|
bottom: -1px;
|
right: -1px;
|
border-width: 0 2px 2px 0;
|
}
|
|
.pie-chart-wrapper {
|
position: relative;
|
width: 100%;
|
height: 320px;
|
animation: pieFloat 4s ease-in-out infinite;
|
}
|
|
@keyframes pieFloat {
|
0%, 100% { transform: translateY(0); }
|
50% { transform: translateY(-3px); }
|
}
|
|
.pie-background {
|
position: absolute;
|
left: 25%;
|
top: 50%;
|
transform: translate(-51.5%, -50%);
|
width: 310px;
|
height: 310px;
|
background-image: url('@/assets/BI/玫瑰图边框.png');
|
background-size: contain;
|
background-position: center;
|
background-repeat: no-repeat;
|
z-index: 1;
|
pointer-events: none;
|
animation: pieBgRotate 30s linear infinite;
|
}
|
|
@keyframes pieBgRotate {
|
from { transform: translate(-51.5%, -50%) rotate(0deg); }
|
to { transform: translate(-51.5%, -50%) rotate(360deg); }
|
}
|
</style>
|