123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337 |
- <template>
- <div class="bar-chart-container">
- <div class="chart-wrapper">
- <v-chart class="chart" :option="chartOption" :autoresize="true" :style="{height: chartHeight || 244 + 'px'}" />
- </div>
- <div v-if="!isMultiSeries" class="title">{{ title }}</div>
- </div>
- </template>
- <script lang="ts" setup>
- import { ref, computed, onMounted, watch } from 'vue'
- import { use } from 'echarts/core'
- import { CanvasRenderer } from 'echarts/renderers'
- import { BarChart } from 'echarts/charts'
- import {
- TitleComponent,
- TooltipComponent,
- LegendComponent,
- GridComponent
- } from 'echarts/components'
- import VChart from 'vue-echarts'
- use([
- CanvasRenderer,
- BarChart,
- TitleComponent,
- TooltipComponent,
- LegendComponent,
- GridComponent
- ])
- // 支持两种数据格式
- interface BarItem {
- name: string
- value: number
- percentage?: string
- }
- interface MultiBarItem {
- name: string
- values: Array<{
- value: number
- percentage?: string
- seriesName?: string
- }>
- }
- const props = withDefaults(defineProps<{
- data?: BarItem[] | MultiBarItem[]
- title?: string
- seriesNames?: string[] // 系列名称,用于多系列对比
- isMultiSeries?: boolean // 是否为多系列数据
- chartHeight?: number // 图表高度
- }>(), {
- data: () => [],
- seriesNames: () => ['系列1', '系列2'],
- isMultiSeries: false
- })
- // 默认数据
- const defaultData: BarItem[] = [
- // { name: '1次', value: 3, percentage: '3.0%' }
- ]
- const displayData = computed(() => {
- return props.data && props.data.length > 0 ? props.data : defaultData
- })
- // 获取x轴数据
- const xAxisData = computed(() => {
- return displayData.value.map(item => item.name)
- })
- // 生成系列数据
- const seriesData = computed(() => {
- if (!props.isMultiSeries) {
- // 单系列数据
- const singleData = displayData.value as BarItem[]
- return [{
- name: '数据',
- type: 'bar',
- data: singleData.map((item) => ({
- value: item.value,
- percentage: item.percentage || ''
- })),
- itemStyle: {
- color: {
- type: 'linear',
- x: 0,
- y: 0,
- x2: 0,
- y2: 1,
- colorStops: [
- { offset: 0, color: 'rgba(109, 173, 249, 1)' },
- { offset: 1, color: 'rgba(109, 173, 249, 1)' }
- ]
- },
- borderRadius: [0, 0, 0, 0]
- },
- emphasis: {
- itemStyle: {
- color: {
- type: 'linear',
- x: 0,
- y: 0,
- x2: 0,
- y2: 1,
- colorStops: [
- { offset: 0, color: 'rgba(109, 173, 249, 1)' },
- { offset: 1, color: 'rgba(109, 173, 249, 1)' }
- ]
- }
- }
- },
- barWidth: '54px'
- }]
- } else {
- // 多系列数据
- const multiData = displayData.value as MultiBarItem[]
- const colors = [
- 'rgba(109, 173, 249, 1)',
- 'rgba(255, 107, 107, 1)',
- 'rgba(255, 193, 7, 1)',
- 'rgba(40, 167, 69, 1)',
- 'rgba(220, 53, 69, 1)'
- ]
- return multiData[0].values.map((_, index) => ({
- name: props.seriesNames[index] || `系列${index + 1}`,
- type: 'bar',
- data: multiData.map((item) => ({
- value: item.values[index]?.value || 0,
- percentage: item.values[index]?.percentage || ''
- })),
- itemStyle: {
- color: {
- type: 'linear',
- x: 0,
- y: 0,
- x2: 0,
- y2: 1,
- colorStops: [
- { offset: 0, color: colors[index % colors.length] },
- { offset: 1, color: colors[index % colors.length] }
- ]
- },
- borderRadius: [0, 0, 0, 0]
- },
- emphasis: {
- itemStyle: {
- color: {
- type: 'linear',
- x: 0,
- y: 0,
- x2: 0,
- y2: 1,
- colorStops: [
- { offset: 0, color: colors[index % colors.length] },
- { offset: 1, color: colors[index % colors.length] }
- ]
- }
- }
- },
- barWidth: props.isMultiSeries ? 60 / props.seriesNames.length + 'px' : '54px'
- }))
- }
- })
- const chartOption = computed(() => {
- return {
- tooltip: {
- trigger: 'axis',
- axisPointer: {
- type: 'shadow'
- },
- position: function (point: any, params: any, dom: any, rect: any, size: any) {
- // 自定义位置逻辑
- // point: 鼠标位置
- // params: 数据项参数
- // dom: tooltip的dom对象
- // rect: 只有鼠标在图形上时有效,是一个用x, y, width, height四个属性描述的矩形
- // size: 包含 contentSize(tooltip内容区域的大小)和 viewSize(可视区域的大小)
-
- // 示例:将tooltip显示在鼠标上方
- // return [point[0], point[1] - 10];
-
- // 其他位置选项:
- // return 'top'; // 固定在顶部
- // return 'bottom'; // 固定在底部
- // return 'left'; // 固定在左侧
- // return 'right'; // 固定在右侧
- // return [10, 10]; // 固定坐标位置
- },
- formatter: function (params: any) {
- if (!props.isMultiSeries) {
- return params.map((item: any) => {
- return `<i style="display: inline-block; width: 10px; height: 10px; background-color:rgba(22, 122, 240, 1); border-radius: 50%;"></i>
- ${item.name}: <span style="color:rgba(22, 122, 240, 1);">${item.value}</span>`
- }).join('\n')
- } else {
- let result = `<div style="text-align: left;">${params[0].name}<br/>`
- params.forEach((param: any) => {
- const percentageText = param.data.percentage ? ` (${param.data.percentage})` : ''
- result += `${param.seriesName}:${param.value}${percentageText}<br/>`
- })
- return result + '</div>'
- }
- },
- backgroundColor: 'rgba(255, 255, 255, 0.9)',
- borderColor: '#e6e6e6',
- borderWidth: 1,
- textStyle: {
- color: '#333'
- }
- },
- legend: props.isMultiSeries ? {
- data: props.seriesNames,
- bottom: '0%',
- textStyle: {
- color: 'rgba(100, 100, 100, 1)',
- fontSize: 12
- },
- icon: 'rect',
- itemWidth: 8,
- itemHeight: 8,
- itemGap: 20,
- selectedMode: true,
- itemStyle: {
- borderWidth: 0,
- },
- } : undefined,
- grid: {
- left: '0',
- right: '0',
- bottom: props.isMultiSeries ? '15%' : '0',
- top: '3%',
- containLabel: true
- },
- xAxis: {
- type: 'category',
- data: xAxisData.value,
- axisLine: {
- lineStyle: {
- color: '#e6e6e6'
- }
- },
- axisTick: {
- show: false
- },
- axisLabel: {
- color: 'rgba(100, 100, 100, 1)',
- fontSize: 14
- }
- },
- yAxis: {
- type: 'value',
- name: '次数',
- nameTextStyle: {
- color: 'rgba(100, 100, 100, 1)',
- fontSize: 14
- },
- axisLine: {
- show: false
- },
- axisTick: {
- show: false
- },
- axisLabel: {
- color: 'rgba(100, 100, 100, 1)',
- fontSize: 13
- },
- splitLine: {
- lineStyle: {
- color: '#f0f0f0',
- type: 'dashed'
- }
- }
- },
- series: seriesData.value
- }
- })
- onMounted(() => {
- // 组件挂载后的初始化逻辑
- })
- // 监听数据变化
- watch(displayData, (newData) => {
- console.log('Bar chart data updated:', newData)
- }, { deep: true })
- </script>
- <style scoped lang="scss">
- .bar-chart-container {
- width: 100%;
- height: 100%;
- text-align: center;
- }
- .chart-wrapper {
- width: 100%;
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .chart {
- width: 100%;
- height: 100%;
- }
- .title {
- display: inline-block;
- color: rgba(18, 18, 18, 1);
- font-family: Source Han Sans SC;
- font-weight: 400;
- font-style: Regular;
- font-size: 14px;
- padding-left: 14px;
- position: relative;
- margin-top: 30px;
- &::before {
- content: '';
- position: absolute;
- left: 0;
- top: 50%;
- transform: translateY(-50%);
- width: 8px;
- height: 8px;
- background-color: rgba(109, 173, 249, 1);
- }
- }
- </style>
|