feat(Analysis): 优化饼图组件功能和数据处理

-重构了饼图组件的逻辑,增加了维度数据处理和表格数据生成功能
- 优化了饼图数据格式化和更新机制,提高了图表的动态响应能力
- 新增了表格数据处理相关函数,提升了表格展示的灵活性和准确性- 调整了图表和表格的样式,增强了可视化效果
This commit is contained in:
陈昱达
2025-05-16 17:04:33 +08:00
parent 00e3e67819
commit 720648274e
5 changed files with 161 additions and 39 deletions

View File

@@ -1,19 +1,65 @@
<script setup lang="ts">
import { ref, useTemplateRef } from 'vue';
import { ref, useTemplateRef, watch } from 'vue';
import { useSetPieChart } from '@/hooks/chart/usePieChart';
import {
formatData,
getTableData,
setDimensionData
} from '@/views/Survey/views/Analysis/components/AnalysisInfo/hooks/pieSeries';
// series 信息
const series = defineModel<any>('series', { required: true });
// 饼图 dom 结构
const pieChart = useTemplateRef<HTMLSpanElement>('pieChart');
const tableData = ref([]);
const analysis = defineModel('analysis');
const series = ref([]);
const dimension = defineModel('dimension');
useSetPieChart(pieChart, series, { title: false, legend: false });
const index = ref(0);
// 饼图 dom 结构
// 当 keyword 变动的时候,标记脏数据
watch(
() => analysis.value,
async () => {
tableData.value = {
...analysis.value,
option: getTableData(analysis.value)
};
series.value = formatData(dimension.value ? tableData.value : analysis.value, index.value);
const pieChart = useTemplateRef<HTMLSpanElement>('pieChart');
useSetPieChart(pieChart, series, { title: false, legend: false });
},
{ immediate: true }
);
const changeChart = (i: Number) => {
index.value = i;
series.value = formatData(tableData.value, index.value);
// const pieChart = useTemplateRef<HTMLSpanElement>('pieChart');
// useSetPieChart(pieChart, series, { title: false, legend: false });
};
</script>
<template>
<section>
<div v-if="dimension">
<van-radio-group v-model="index" @change="changeChart">
<van-radio v-for="(item, index) in tableData.option" :name="index">{{
item.option
}}</van-radio>
</van-radio-group>
</div>
<!-- 图表部分 -->
<div style="display: flex; height: 300px; width: 100%; justify-content: center; margin: 16px 0">
<div
class="charts"
style="
display: flex;
height: 300px;
justify-content: center;
margin: 16px 0;
"
>
<span ref="pieChart" style="width: 100%; height: 300px"></span>
</div>
</section>
@@ -26,4 +72,7 @@ useSetPieChart(pieChart, series, { title: false, legend: false });
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.charts {
width: calc(100vw - 50px);
}
</style>

View File

@@ -11,18 +11,14 @@ const option = {
// orient: 'vertical',
// left: 'left'
// },
label: {
formatter: '{b},{c}'
},
series: [
{
name: 'Access From',
type: 'pie',
radius: '50%',
data: [
{ value: 1048, name: 'Search Engine' },
{ value: 735, name: 'Direct' },
{ value: 580, name: 'Email' },
{ value: 484, name: 'Union Ads' },
{ value: 300, name: 'Video Ads' }
],
data: [],
emphasis: {
itemStyle: {
shadowBlur: 10,
@@ -34,4 +30,4 @@ const option = {
]
};
export const pieOption = option;
export const pieOption = option;

View File

@@ -23,16 +23,24 @@ function useSetPieChart(
// 在 dom 挂载之后,显示饼图
chartInstance = chart.init(dom.value);
pieOption.series = JSON.parse(JSON.stringify(series.value));
console.log(pieOption);
// 设置图表选项
chartInstance.setOption(pieOption, opts);
});
// 如果 data 变动重新生成图表w
watch(series, (value) => {
chartInstance.setOption(value, opts);
console.log(value);
const currentOptions = chartInstance.getOption();
// 合并新的 series 数据到现有配置中
chartInstance.setOption(
{
...currentOptions,
series: JSON.parse(JSON.stringify(value)) // 确保深拷贝防止引用污染
},
opts
);
});
}

View File

@@ -6,11 +6,15 @@
}}</el-tag>
{{ analysis.stem }}
<chart-msg :series="formatData(analysis)" v-if="showChart.includes(analysis.question_type)" />
<chart-msg
v-if="showChart.includes(analysis.question_type)"
:dimension="analysis.option && analysis.option[0].option"
:analysis="analysis"
/>
<yl-table
:props="getTableHeadProps(analysis.head)"
:data="analysis.option"
:props="getTableHeadProps(analysis.head, analysis.option)"
:data="getTableData(analysis)"
v-if="analysis.head"
/>
@@ -23,21 +27,39 @@
import { questionAnalysis } from '../../hooks/useAnalysis';
import { questionTypeMap } from '@/utils/question/typeMapping';
import ChartMsg from '@/components/Analysis/Index.vue';
import { formatData } from './hooks/pieSeries';
import { getTableData } from './hooks/pieSeries';
import YlTable from '@/components/YlTable/Index.vue';
import { ref } from 'vue';
// questionTypeMap 自己去对应
const showChart = ref([1, 2, 5, 106, 8, 9, 10]);
const showChart = ref([1, 2, 5, 106, 9, 10]);
// const showTable = ref([1,2,4])
const getTableHeadProps = (values: any[]) => {
return values.map((item: Object) => {
return {
label: item.title,
prop: item.key
};
});
// 构建表头
const getTableHeadProps = (values: any[], option) => {
const head = [];
if (values && values.length > 0) {
values.forEach((item: any) => {
if (item.key !== 'option') {
head.push({
label: item.title,
prop: item.key,
width: 120
});
}
});
}
if (option && option[0].option) {
head.unshift({
label: '选项',
prop: 'option',
width: 150
});
}
return head;
};
// 构建表格数据
</script>
<style scoped lang="scss">
.mt10 {

View File

@@ -3,7 +3,7 @@ import { ref } from 'vue';
export const series = ref({
name: 'Access From',
type: 'pie',
radius: '70%',
radius: ['30%', '50%'],
data: [
{ value: 1048, name: 'Search Engine' },
{ value: 735, name: 'Direct' },
@@ -14,18 +14,16 @@ export const series = ref({
emphasis: {
itemStyle: {
// shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
shadowOffsetX: 0
// shadowColor: 'rgba(0, 0, 0, 0.5)'
}
},
data: []
}
});
export function formatData(data: any) {
export function formatData(data: any, index) {
const _series = JSON.parse(JSON.stringify(series.value));
// 当内容为单选的时候处理方式
if (data.question_index === 2 || data.question_index === 1) {
if (data.question_type === 1 || data.question_type === 2) {
const { option } = data;
_series.data = option.map((item: any) => {
return {
@@ -34,6 +32,55 @@ export function formatData(data: any) {
};
});
}
if (
data.question_type === 5 ||
data.question_type === 106 ||
data.question_type === 9 ||
data.question_type === 10
) {
const copyData = setDimensionData(data);
_series.data = copyData[index ? index : 0];
}
return _series;
}
export function getTableData(data: any) {
let analysis = JSON.parse(JSON.stringify(data));
const rows = analysis.option || [];
return rows.map((rowItem: any) => {
const rowData = {
option: rowItem.option_title || '未知行'
};
// 遍历每个列字段,填充对应值
analysis.head.forEach((col: any) => {
if (col.key !== 'option') {
const matched = rowItem.option
? rowItem.option.find((opt: any) => opt.index === col.key)
: '';
rowData[col.key] = matched ? `${matched.number}` : rowItem[col.key];
}
});
return rowData;
});
}
export function setDimensionData(data) {
const { option } = data;
return option.map((item: any) => {
let array = [];
for (let keys in item) {
let obj = {};
if (keys !== 'option' && keys !== 'avg' && item[keys] != 0) {
obj.name = data.head.find((head: any) => head.key === keys)?.title;
obj.value = item[keys];
array.push(obj);
}
}
return array;
});
}