| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- <template>
- <div ref="chartRef" class="group-area-compare-bar-chart" :style="rootStyle"></div>
- </template>
- <script setup>
- import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick } from 'vue';
- import * as echarts from 'echarts';
- import { defineProps, defineEmits } from 'vue';
- import { useEnterpriseStore } from '@/store/enterprise';
- import { useFilterStore } from '@/store/filter';
- import { GROUP_ORDER, GROUP_DISPLAY_NAMES, normalizeGroupName } from '@/utils/aggregateReportRecords';
- import { toWanSquareMeters } from '@/utils/areaMath';
- const props = defineProps({
- clickable: {
- type: Boolean,
- default: false,
- },
- activeGroup: {
- type: String,
- default: '',
- },
- height: {
- type: [Number, String],
- default: 300,
- },
- });
- const emit = defineEmits(['group-click']);
- const SERIES_CONFIG = [
- { name: '实际建筑面积', color: 'rgba(1, 118, 255, 1)', dimColor: 'rgba(1, 118, 255, 0.22)' },
- { name: '已使用面积', color: 'rgba(255, 121, 98, 1)', dimColor: 'rgba(255, 121, 98, 0.22)' },
- ];
- const rootStyle = computed(() => ({
- width: '100%',
- height: typeof props.height === 'number' ? `${props.height}px` : props.height,
- }));
- const enterpriseStore = useEnterpriseStore();
- const filterStore = useFilterStore();
- const chartRef = ref(null);
- let chartInstance = null;
- const filteredList = computed(() =>
- enterpriseStore.filteredByGroup('全部', filterStore.selectDate)
- );
- const getUsedAreaSqm = (item) =>
- Number(item.c_zymj || 0) + Number(item.c_cjmj || 0) + Number(item.c_czmj || 0);
- const resolveGroupIndex = (item) => {
- const raw = normalizeGroupName(item.c_ssqpjt || item.c_ssjt || '');
- return GROUP_ORDER.indexOf(raw);
- };
- const chartData = computed(() => {
- const buildingSqm = GROUP_ORDER.map(() => 0);
- const usedSqm = GROUP_ORDER.map(() => 0);
- filteredList.value.forEach((item) => {
- const groupIndex = resolveGroupIndex(item);
- if (groupIndex < 0) return;
- buildingSqm[groupIndex] += Number(item.c_sjjzmj || 0);
- usedSqm[groupIndex] += getUsedAreaSqm(item);
- });
- return {
- xAxisData: GROUP_ORDER.map((group) => GROUP_DISPLAY_NAMES[group] ?? group),
- series: [
- buildingSqm.map((val) => toWanSquareMeters(val)),
- usedSqm.map((val) => toWanSquareMeters(val)),
- ],
- };
- });
- const buildBarData = (values, seriesIndex) => {
- const series = SERIES_CONFIG[seriesIndex];
- return values.map((val, dataIndex) => {
- const category = chartData.value.xAxisData[dataIndex];
- const isActive = props.activeGroup && category === props.activeGroup;
- const isDimmed = props.activeGroup && !isActive;
- return {
- value: val,
- itemStyle: {
- color: isDimmed ? series.dimColor : series.color,
- borderRadius: [4, 4, 0, 0],
- ...(isActive
- ? { shadowBlur: 8, shadowColor: 'rgba(0, 117, 255, 0.35)' }
- : {}),
- },
- };
- });
- };
- const buildOption = () => ({
- legend: {
- data: SERIES_CONFIG.map((item) => item.name),
- top: 0,
- right: 0,
- itemWidth: 12,
- itemHeight: 12,
- textStyle: {
- fontSize: 14,
- color: 'rgba(102, 112, 133, 1)',
- fontFamily: 'PingFangSC-Regular',
- },
- },
- tooltip: {
- trigger: 'axis',
- axisPointer: { type: 'shadow' },
- formatter(params) {
- const lines = [`${params[0]?.axisValue ?? ''}`];
- params.forEach((item) => {
- lines.push(`${item.marker}${item.seriesName}: ${item.value} 万m²`);
- });
- return lines.join('<br/>');
- },
- },
- grid: {
- left: 0,
- right: 0,
- top: 36,
- bottom: 0,
- containLabel: true,
- },
- xAxis: {
- type: 'category',
- boundaryGap: true,
- axisTick: {
- show: false,
- alignWithLabel: true,
- },
- axisLine: {
- show: true,
- lineStyle: {
- width: 1,
- color: 'rgba(51, 71, 230, 1)',
- type: 'solid',
- },
- },
- data: chartData.value.xAxisData,
- },
- yAxis: {
- type: 'value',
- axisLabel: {
- show: true,
- color: 'rgba(102, 112, 133, 1)',
- fontSize: 14,
- fontWeight: 400,
- fontFamily: 'PingFangSC-Regular',
- },
- axisLine: { show: false },
- splitLine: { show: false },
- },
- series: SERIES_CONFIG.map((item, index) => ({
- name: item.name,
- type: 'bar',
- color: item.color,
- barWidth: 24,
- barGap: '15%',
- barCategoryGap: '28%',
- data: buildBarData(chartData.value.series[index], index),
- cursor: props.clickable ? 'pointer' : 'default',
- emphasis: props.clickable
- ? {
- itemStyle: {
- shadowBlur: 8,
- shadowColor: 'rgba(0, 117, 255, 0.35)',
- },
- }
- : undefined,
- })),
- });
- const renderChart = async () => {
- await nextTick();
- if (!chartRef.value) return;
- if (!chartInstance) {
- chartInstance = echarts.init(chartRef.value);
- }
- chartInstance.setOption(buildOption(), true);
- bindChartClick();
- };
- const bindChartClick = () => {
- if (!chartInstance || !props.clickable) return;
- chartInstance.off('click');
- chartInstance.on('click', (params) => {
- if (params?.seriesType !== 'bar' || params.dataIndex == null) return;
- const name = chartData.value.xAxisData[params.dataIndex];
- if (name) emit('group-click', name);
- });
- };
- const resizeChart = () => {
- chartInstance?.resize();
- };
- watch(chartData, () => {
- renderChart();
- });
- watch(
- () => [props.clickable, props.activeGroup],
- () => {
- renderChart();
- }
- );
- onMounted(() => {
- renderChart();
- window.addEventListener('resize', resizeChart);
- });
- onBeforeUnmount(() => {
- window.removeEventListener('resize', resizeChart);
- chartInstance?.dispose();
- });
- </script>
|