| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307 |
- <script lang="ts" setup>
- import * as echarts from 'echarts'
- import { IotStatApi } from '@/api/pms/stat'
- import { ANIMATION, CHART_RENDERER, createTooltip, FONT_FAMILY, THEME } from '@/utils/kb'
- interface DeviceCategoryItem {
- name: string
- count: number
- type: string
- code?: string | number
- }
- interface DeviceCategoryResponseItem {
- className?: string
- count?: number | string
- type?: string
- code?: string | number
- }
- const { push } = useRouter()
- const chartRef = ref<HTMLDivElement>()
- const loading = ref(false)
- const categoryData = ref<DeviceCategoryItem[]>([])
- let chart: echarts.ECharts | null = null
- const TOP_BAR_COLOR = new echarts.graphic.LinearGradient(0, 0, 1, 0, [
- { offset: 0, color: '#ffd166' },
- { offset: 0.58, color: '#ff9f43' },
- { offset: 1, color: '#f76707' }
- ])
- const DEFAULT_BAR_COLOR = new echarts.graphic.LinearGradient(0, 0, 1, 0, [
- { offset: 0, color: THEME.color.blue.light },
- { offset: 0.58, color: THEME.color.blue.mid },
- { offset: 1, color: THEME.color.blue.line }
- ])
- function sortCategoryData(data: DeviceCategoryItem[]) {
- const typeOrder = (type: string) => {
- if (type === 'top') return 0
- if (type === '') return 1
- return 2
- }
- return data.sort((a, b) => typeOrder(a.type) - typeOrder(b.type))
- }
- function getChartLayout() {
- const { clientWidth = 0, clientHeight = 0 } = chartRef.value ?? {}
- const compact = clientHeight > 0 && (clientHeight < 210 || clientWidth < 520)
- return {
- gridTop: compact ? 20 : 26,
- gridRight: compact ? 32 : 40,
- gridBottom: compact ? 22 : 28,
- gridLeft: compact ? 12 : 12,
- barWidth: compact ? 10 : 14,
- axisFontSize: compact ? 10 : 12,
- labelFontSize: compact ? 10 : 12
- }
- }
- function formatValue(value: number) {
- return Number(value || 0).toLocaleString('en-US')
- }
- function getChartOption(): echarts.EChartsOption {
- const layout = getChartLayout()
- const values = categoryData.value.map((item) => item.count)
- const maxValue = Math.max(...values, 1)
- const xAxisMax = Math.ceil((maxValue * 1.12) / 10) * 10
- return {
- ...ANIMATION,
- grid: {
- ...THEME.grid,
- top: layout.gridTop,
- right: layout.gridRight,
- bottom: layout.gridBottom,
- left: layout.gridLeft
- },
- tooltip: createTooltip({
- trigger: 'axis',
- axisPointer: {
- type: 'shadow',
- shadowStyle: {
- color: THEME.split
- }
- },
- formatter(params: any[]) {
- const first = params[0]
- const row = categoryData.value[first?.dataIndex ?? 0]
- if (!row) return ''
- return [
- `<div style="min-width:132px;font-weight:700;color:#24364f;margin-bottom:6px;">${row.name}</div>`,
- `<div style="display:flex;justify-content:space-between;gap:18px;line-height:22px;"><span>设备数量</span><b style="color:#1f5bb8;">${row.count} 台</b></div>`
- ].join('')
- }
- }),
- xAxis: {
- type: 'value',
- name: '台',
- max: xAxisMax,
- axisLine: {
- show: false
- },
- axisTick: {
- show: false
- },
- axisLabel: {
- color: THEME.text.regular,
- fontSize: layout.axisFontSize,
- fontFamily: FONT_FAMILY,
- formatter(value: number) {
- return formatValue(value)
- }
- },
- nameTextStyle: {
- color: '#4c6c9b',
- fontSize: layout.axisFontSize,
- fontWeight: 600,
- fontFamily: FONT_FAMILY
- },
- splitLine: {
- lineStyle: {
- color: 'rgba(31, 91, 184, 0.24)',
- type: 'dashed'
- }
- }
- },
- yAxis: {
- type: 'category',
- data: categoryData.value.map((item) => item.name),
- inverse: true,
- axisLine: {
- show: false
- },
- axisTick: {
- show: false
- },
- axisLabel: {
- color: '#102a43',
- fontSize: layout.axisFontSize,
- fontWeight: 600,
- fontFamily: FONT_FAMILY,
- width: 80,
- overflow: 'truncate'
- }
- },
- series: [
- {
- name: '设备数量',
- type: 'bar',
- data: categoryData.value.map((item) => ({
- value: item.count,
- code: item.code,
- itemStyle: {
- color: item.type === 'top' ? TOP_BAR_COLOR : DEFAULT_BAR_COLOR
- }
- })),
- barWidth: layout.barWidth,
- cursor: 'pointer',
- showBackground: true,
- backgroundStyle: {
- color: 'rgba(31, 91, 184, 0.08)',
- borderRadius: 999
- },
- itemStyle: {
- borderRadius: 999
- },
- label: {
- show: true,
- position: 'right',
- distance: 8,
- color: THEME.text.strong,
- fontSize: layout.labelFontSize,
- fontWeight: 700,
- fontFamily: FONT_FAMILY,
- formatter(params: any) {
- return `${formatValue(params.value)}`
- }
- },
- emphasis: {
- itemStyle: {
- shadowBlur: 2
- // shadowColor: THEME.color.blue.shadow
- }
- }
- }
- ]
- }
- }
- function initChart() {
- if (!chartRef.value) return
- chart?.dispose()
- chart = echarts.init(chartRef.value, undefined, {
- renderer: CHART_RENDERER
- })
- bindChartEvents()
- renderChart()
- }
- function bindChartEvents() {
- chart?.off('click')
- chart?.on('click', (params) => {
- const row = categoryData.value[params.dataIndex ?? -1]
- if (!row?.code) return
- push({
- name: 'IotDevicePms',
- query: {
- code: String(row.code)
- }
- })
- })
- }
- function renderChart() {
- chart?.setOption(getChartOption(), true)
- }
- function resizeChart() {
- chart?.resize()
- renderChart()
- }
- function destroyChart() {
- chart?.dispose()
- chart = null
- }
- async function getDeviceCategory() {
- loading.value = true
- try {
- const res = await IotStatApi.getRdNewClassify()
- categoryData.value = Array.isArray(res)
- ? sortCategoryData(
- res.map((item: DeviceCategoryResponseItem) => ({
- name: item.className || '未知',
- count: Number(item.count ?? 0),
- type: item.type || '',
- code: item.code
- }))
- ).slice(0, 10)
- : []
- renderChart()
- } catch (error) {
- console.error('获取瑞都设备分类失败:', error)
- categoryData.value = []
- renderChart()
- } finally {
- loading.value = false
- }
- }
- onMounted(() => {
- initChart()
- getDeviceCategory()
- window.addEventListener('resize', resizeChart)
- window.addEventListener('rdkb:resize', resizeChart)
- })
- onUnmounted(() => {
- window.removeEventListener('resize', resizeChart)
- window.removeEventListener('rdkb:resize', resizeChart)
- destroyChart()
- })
- </script>
- <template>
- <div class="panel flex flex-col">
- <div class="panel-title">
- <div class="icon-decorator">
- <span></span>
- <span></span>
- </div>
- 设备分类
- </div>
- <div
- v-loading="loading"
- element-loading-text="加载中..."
- element-loading-background="rgb(222 236 252 / 72%)"
- class="device-category-body">
- <div ref="chartRef" class="device-category-chart"></div>
- </div>
- </div>
- </template>
- <style lang="scss" scoped>
- @import url('@/styles/kb.scss');
- .device-category-body {
- display: flex;
- width: 100%;
- min-height: 0;
- flex: 1;
- }
- .device-category-chart {
- width: 100%;
- min-height: 0;
- flex: 1;
- }
- </style>
|