pages_asset_category_chart.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import React from 'react';
  2. import {
  3. Table, Card, Typography
  4. } from 'antd';
  5. import {
  6. useQuery,
  7. } from '@tanstack/react-query';
  8. import { Pie } from "@ant-design/plots";
  9. import 'dayjs/locale/zh-cn';
  10. import type {
  11. CategoryChartDataWithPercent,
  12. } from '../share/monitorTypes.ts';
  13. import { MonitorChartsAPI } from './api/index.ts';
  14. interface ChartTooltipInfo {
  15. items: Array<Record<string, any>>;
  16. title: string;
  17. }
  18. export const AssetCategoryChartPage = () => {
  19. const { data: categoryData, isLoading } = useQuery({
  20. queryKey: ['adminZichanCategory'],
  21. queryFn: MonitorChartsAPI.fetchCategoryData
  22. });
  23. const { Title } = Typography;
  24. return (
  25. <div>
  26. <Title level={2}>资产分类分布</Title>
  27. <Card loading={isLoading}>
  28. <div style={{ height: '500px' }}>
  29. {categoryData && (
  30. <Pie
  31. data={categoryData}
  32. angleField="设备数"
  33. colorField="设备分类"
  34. radius={0.9}
  35. innerRadius={0.8}
  36. label={{
  37. position: 'outside',
  38. text: ({ 设备分类, 设备数, 百分比, percent }: CategoryChartDataWithPercent & { percent: number }) => {
  39. // 只有占比超过5%的项才显示标签
  40. if (percent < 0.05) return null;
  41. return `${设备分类}\n(${设备数})`;
  42. },
  43. style: {
  44. fill: '#000',
  45. fontSize: 12,
  46. fontWeight: 500,
  47. },
  48. transform: [{ type: 'overlapDodgeY' }],
  49. }}
  50. theme={{
  51. colors10: ['#36cfc9', '#ff4d4f', '#ffa940', '#73d13d', '#4096ff'],
  52. }}
  53. interactions={[
  54. { type: 'element-active' },
  55. { type: 'element-selected' }
  56. ]}
  57. interaction={{
  58. tooltip: {
  59. render: (_: unknown, { items, title }: ChartTooltipInfo) => {
  60. if (!items || items.length === 0) return '';
  61. // 获取当前选中项的数据
  62. const item = items[0];
  63. // 根据value找到对应的完整数据项
  64. const fullData = categoryData?.find(d => d['设备数'] === item.value);
  65. if (!fullData) return '';
  66. return `<div class="bg-white p-2 rounded">
  67. <div class="flex items-center">
  68. <div class="w-3 h-3 rounded-full mr-2" style="background:${item.color}"></div>
  69. <span class="font-semibold text-gray-900">${fullData['设备分类']}</span>
  70. </div>
  71. <p class="text-sm text-gray-800">数量: ${item.value}</p>
  72. <p class="text-sm text-gray-800">占比: ${fullData['百分比']}%</p>
  73. </div>`;
  74. }
  75. }
  76. }}
  77. />
  78. )}
  79. </div>
  80. </Card>
  81. <Card style={{ marginTop: '16px' }}>
  82. <Title level={4}>数据明细</Title>
  83. <Table
  84. dataSource={categoryData}
  85. rowKey={(record) => record['设备分类']}
  86. columns={[
  87. {
  88. title: '设备分类',
  89. dataIndex: '设备分类',
  90. key: '设备分类',
  91. },
  92. {
  93. title: '设备数量',
  94. dataIndex: '设备数',
  95. key: '设备数',
  96. sorter: (a, b) => a['设备数'] - b['设备数'],
  97. },
  98. {
  99. title: '占比',
  100. dataIndex: '百分比',
  101. key: '百分比',
  102. render: (text) => `${text}%`,
  103. sorter: (a, b) => parseFloat(a['百分比']) - parseFloat(b['百分比']),
  104. }
  105. ]}
  106. pagination={false}
  107. />
  108. </Card>
  109. </div>
  110. );
  111. };