首页已完成
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
import { useRef, useEffect, useState } from 'react';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
const chartData = [
|
||||
{ value: 25, name: '校企益邦' },
|
||||
{ value: 25, name: '科研管理系统' },
|
||||
{ value: 25, name: '智慧党建平台' },
|
||||
{ value: 25, name: '商咖五级进阶' },
|
||||
];
|
||||
|
||||
const color = '#004ec4';
|
||||
|
||||
// ECharts startAngle=90, 各扇形视觉中心 (0°=12点, 顺时针)
|
||||
// slice0: 1:30(45°) slice1: 4:30(135°) slice2: 7:30(225°) slice3: 10:30(315°)
|
||||
// 要让选中扇形中心对准右侧90°(3点方向), CSS rotate需要的角度
|
||||
const rotationNeeded = [45, -45, -135, -225];
|
||||
|
||||
function normalizeAngle(a) {
|
||||
return ((a % 360) + 360) % 360;
|
||||
}
|
||||
|
||||
function shortestRotation(from, to) {
|
||||
const fromN = normalizeAngle(from);
|
||||
const toN = normalizeAngle(to);
|
||||
let diff = toN - fromN;
|
||||
if (diff > 180) diff -= 360;
|
||||
if (diff < -180) diff += 360;
|
||||
return from + diff;
|
||||
}
|
||||
|
||||
export default function RosePieChart({ onSelect, activeIndex }) {
|
||||
const containerRef = useRef(null);
|
||||
const chartRef = useRef(null);
|
||||
const rotationRef = useRef(0);
|
||||
const [rotation, setRotation] = useState(0);
|
||||
|
||||
// 初始化图表(只执行一次)
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
chartRef.current = echarts.init(containerRef.current, null, { renderer: 'canvas' });
|
||||
|
||||
chartRef.current.setOption({
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
backgroundColor: '#fff',
|
||||
borderColor: '#e2e8f0',
|
||||
borderWidth: 1,
|
||||
textStyle: { color: '#334155', fontSize: 13 },
|
||||
formatter: '{b}',
|
||||
},
|
||||
series: [{
|
||||
type: 'pie',
|
||||
radius: ['20%', '68%'],
|
||||
center: ['50%', '50%'],
|
||||
startAngle: 90,
|
||||
padAngle: 2,
|
||||
itemStyle: { borderRadius: 4, borderColor: '#fff', borderWidth: 3 },
|
||||
label: {
|
||||
show: true,
|
||||
position: 'inside',
|
||||
color: '#004ec4',
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
rotate: 'tangential',
|
||||
formatter: '{b}',
|
||||
},
|
||||
emphasis: {
|
||||
scaleSize: 10,
|
||||
itemStyle: {
|
||||
color: '#0088cc',
|
||||
shadowBlur: 30,
|
||||
shadowColor: 'rgba(0,78,196,0.35)',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 4,
|
||||
},
|
||||
label: { fontSize: 15, fontWeight: 'bold', color: '#0f172a' },
|
||||
},
|
||||
data: chartData.map(d => ({
|
||||
...d,
|
||||
itemStyle: { color, opacity: 1, borderRadius: 4, borderColor: '#fff', borderWidth: 3 },
|
||||
})),
|
||||
animationType: 'scale',
|
||||
animationEasing: 'elasticOut',
|
||||
animationDuration: 1000,
|
||||
}],
|
||||
});
|
||||
|
||||
const handleResize = () => chartRef.current?.resize();
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
chartRef.current?.dispose();
|
||||
chartRef.current = null;
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 更新颜色高亮 + CSS旋转
|
||||
useEffect(() => {
|
||||
const chart = chartRef.current;
|
||||
if (!chart) return;
|
||||
|
||||
const isActive = activeIndex !== null && activeIndex !== undefined;
|
||||
|
||||
const target = isActive ? rotationNeeded[activeIndex] : 0;
|
||||
const newAngle = shortestRotation(rotationRef.current, target);
|
||||
rotationRef.current = newAngle;
|
||||
setRotation(newAngle);
|
||||
|
||||
chart.setOption({
|
||||
series: [{
|
||||
data: chartData.map((d, i) => ({
|
||||
...d,
|
||||
label: { color: isActive && activeIndex === i ? '#fff' : '#004ec4' },
|
||||
itemStyle: {
|
||||
color: isActive && activeIndex !== i ? '#dfedf8' : color,
|
||||
opacity: 1,
|
||||
borderRadius: 4,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 3,
|
||||
},
|
||||
})),
|
||||
}],
|
||||
});
|
||||
}, [activeIndex]);
|
||||
|
||||
// 点击事件
|
||||
useEffect(() => {
|
||||
const chart = chartRef.current;
|
||||
if (!chart) return;
|
||||
|
||||
const handler = (params) => {
|
||||
if (onSelect && params.dataIndex !== undefined) {
|
||||
onSelect(params.dataIndex);
|
||||
}
|
||||
};
|
||||
chart.on('click', handler);
|
||||
return () => chart.off('click', handler);
|
||||
}, [onSelect]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="inline-block w-full"
|
||||
style={{
|
||||
transform: `rotate(${rotation}deg)`,
|
||||
transition: 'transform 1s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
}}
|
||||
>
|
||||
<div ref={containerRef} className="w-full h-[420px] md:h-[480px]" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user