|
@@ -15,10 +15,16 @@ use([CanvasRenderer, BarChart, GridComponent, TooltipComponent, TitleComponent])
|
|
|
defineOptions({
|
|
defineOptions({
|
|
|
name: 'SatisfactionStatisticsPage',
|
|
name: 'SatisfactionStatisticsPage',
|
|
|
});
|
|
});
|
|
|
-
|
|
|
|
|
|
|
+const questionnaireList = ref<any[]>([
|
|
|
|
|
+ { label: '问卷1', value: '1' },
|
|
|
|
|
+ { label: '问卷2', value: '2' },
|
|
|
|
|
+ { label: '问卷3', value: '3' },
|
|
|
|
|
+]);
|
|
|
|
|
+const questionnaireId = ref<Array<{ label: string; value: string }>>([]);
|
|
|
type RangeValue = [Dayjs, Dayjs] | null;
|
|
type RangeValue = [Dayjs, Dayjs] | null;
|
|
|
|
|
|
|
|
const selectedRange = ref<RangeValue>(null);
|
|
const selectedRange = ref<RangeValue>(null);
|
|
|
|
|
+
|
|
|
const statistics = ref<any[]>([]);
|
|
const statistics = ref<any[]>([]);
|
|
|
|
|
|
|
|
const mockStatistics = [
|
|
const mockStatistics = [
|
|
@@ -37,13 +43,7 @@ const mockStatistics = [
|
|
|
// });
|
|
// });
|
|
|
|
|
|
|
|
function normalizeResponse(res: any): any[] {
|
|
function normalizeResponse(res: any): any[] {
|
|
|
- const list = Array.isArray(res?.data)
|
|
|
|
|
- ? res.data
|
|
|
|
|
- : Array.isArray(res)
|
|
|
|
|
- ? res
|
|
|
|
|
- : Array.isArray(res?.records)
|
|
|
|
|
- ? res.records
|
|
|
|
|
- : [];
|
|
|
|
|
|
|
+ const list = Array.isArray(res?.data) ? res.data : Array.isArray(res) ? res : Array.isArray(res?.records) ? res.records : [];
|
|
|
return list.map((item: any & Record<string, any>) => ({
|
|
return list.map((item: any & Record<string, any>) => ({
|
|
|
score: Number(item.score ?? item.rating ?? item.result ?? 0),
|
|
score: Number(item.score ?? item.rating ?? item.result ?? 0),
|
|
|
count: Number(item.count ?? item.total ?? item.value ?? 0),
|
|
count: Number(item.count ?? item.total ?? item.value ?? 0),
|
|
@@ -87,58 +87,105 @@ const aggregatedCounts = computed(() => {
|
|
|
|
|
|
|
|
const totalSamples = computed(() => Object.values(aggregatedCounts.value).reduce((sum, val) => sum + val, 0));
|
|
const totalSamples = computed(() => Object.values(aggregatedCounts.value).reduce((sum, val) => sum + val, 0));
|
|
|
|
|
|
|
|
-const chartOption = computed(() => {
|
|
|
|
|
|
|
+// 为每个选中的问卷生成图表配置
|
|
|
|
|
+const chartOptions = computed(() => {
|
|
|
|
|
+ const selected = questionnaireId.value;
|
|
|
|
|
+ if (!selected || !Array.isArray(selected) || selected.length === 0) {
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const counts = aggregatedCounts.value;
|
|
|
const categories = ['1', '2', '3', '4', '5'];
|
|
const categories = ['1', '2', '3', '4', '5'];
|
|
|
- const seriesData = categories.map((score) => aggregatedCounts.value[Number(score)] ?? 0);
|
|
|
|
|
- return {
|
|
|
|
|
- title: {
|
|
|
|
|
- text: '就诊体验满意度',
|
|
|
|
|
- left: 'left',
|
|
|
|
|
- top: 0,
|
|
|
|
|
- textStyle: { fontSize: 16, fontWeight: 600 },
|
|
|
|
|
- },
|
|
|
|
|
- tooltip: { trigger: 'axis' },
|
|
|
|
|
- grid: { left: 60, right: 40, top: 60, bottom: 50 },
|
|
|
|
|
- xAxis: {
|
|
|
|
|
- type: 'category',
|
|
|
|
|
- data: categories,
|
|
|
|
|
- name: '分',
|
|
|
|
|
- nameGap: 25,
|
|
|
|
|
- axisTick: { alignWithLabel: true },
|
|
|
|
|
- axisLabel: { fontSize: 12 },
|
|
|
|
|
- },
|
|
|
|
|
- yAxis: {
|
|
|
|
|
- type: 'value',
|
|
|
|
|
- name: '人',
|
|
|
|
|
- minInterval: 1,
|
|
|
|
|
- splitLine: {
|
|
|
|
|
- lineStyle: { type: 'dashed', color: '#eaeaea' },
|
|
|
|
|
- },
|
|
|
|
|
- axisLine: { lineStyle: { color: '#d9d9d9' } },
|
|
|
|
|
- },
|
|
|
|
|
- series: [
|
|
|
|
|
- {
|
|
|
|
|
- type: 'bar',
|
|
|
|
|
- data: seriesData,
|
|
|
|
|
- barWidth: 40,
|
|
|
|
|
- itemStyle: {
|
|
|
|
|
- color: '#69c0ff',
|
|
|
|
|
- borderRadius: [6, 6, 0, 0],
|
|
|
|
|
- },
|
|
|
|
|
- emphasis: {
|
|
|
|
|
- focus: 'series',
|
|
|
|
|
- itemStyle: {
|
|
|
|
|
- color: '#4096ff',
|
|
|
|
|
|
|
+
|
|
|
|
|
+ return selected.map((questionnaire) => {
|
|
|
|
|
+ // labelInValue 返回的数据结构:{label: string, value: string}
|
|
|
|
|
+ let qValue: string;
|
|
|
|
|
+ let qLabel: string;
|
|
|
|
|
+
|
|
|
|
|
+ if (typeof questionnaire === 'object' && questionnaire !== null) {
|
|
|
|
|
+ qValue = String(questionnaire.value || '');
|
|
|
|
|
+
|
|
|
|
|
+ // 尝试获取 label,确保是字符串类型
|
|
|
|
|
+ let tempLabel: any = questionnaire.label;
|
|
|
|
|
+ if (tempLabel && typeof tempLabel === 'string') {
|
|
|
|
|
+ qLabel = tempLabel;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 如果 label 不存在或不是字符串,从 questionnaireList 中查找
|
|
|
|
|
+ const found = questionnaireList.value.find(item => String(item.value) === qValue);
|
|
|
|
|
+ qLabel = found?.label || `问卷 ${qValue}`;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ qValue = String(questionnaire);
|
|
|
|
|
+ // 从 questionnaireList 中查找对应的 label
|
|
|
|
|
+ const found = questionnaireList.value.find(item => String(item.value) === qValue);
|
|
|
|
|
+ qLabel = found?.label || `问卷 ${qValue}`;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 最终确保 qLabel 是有效的字符串(防止任何意外情况)
|
|
|
|
|
+ qLabel = String(qLabel || `问卷 ${qValue}`);
|
|
|
|
|
+
|
|
|
|
|
+ // 这里可以根据不同的问卷ID加载不同的统计数据
|
|
|
|
|
+ // 目前使用相同的统计数据作为示例
|
|
|
|
|
+ const seriesData = categories.map((score) => counts[Number(score)] ?? 0);
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ questionnaireId: qValue,
|
|
|
|
|
+ questionnaireLabel: qLabel,
|
|
|
|
|
+ option: {
|
|
|
|
|
+ title: {
|
|
|
|
|
+ text: String(qLabel), // 确保是字符串
|
|
|
|
|
+ left: 'center',
|
|
|
|
|
+ top: 0,
|
|
|
|
|
+ textStyle: {
|
|
|
|
|
+ fontSize: 16,
|
|
|
|
|
+ fontWeight: 600,
|
|
|
|
|
+ color: '#333'
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
- label: {
|
|
|
|
|
- show: true,
|
|
|
|
|
- position: 'top',
|
|
|
|
|
- formatter: '{c}',
|
|
|
|
|
|
|
+ tooltip: { trigger: 'axis' },
|
|
|
|
|
+ grid: { left: 60, right: 40, top: 50, bottom: 50 },
|
|
|
|
|
+ xAxis: {
|
|
|
|
|
+ type: 'category',
|
|
|
|
|
+ data: categories,
|
|
|
|
|
+ name: '分',
|
|
|
|
|
+ nameGap: 25,
|
|
|
|
|
+ axisTick: { alignWithLabel: true },
|
|
|
|
|
+ axisLabel: { fontSize: 12 },
|
|
|
},
|
|
},
|
|
|
|
|
+ yAxis: {
|
|
|
|
|
+ type: 'value',
|
|
|
|
|
+ name: '人',
|
|
|
|
|
+ minInterval: 1,
|
|
|
|
|
+ splitLine: {
|
|
|
|
|
+ lineStyle: { type: 'dashed', color: '#eaeaea' },
|
|
|
|
|
+ },
|
|
|
|
|
+ axisLine: { lineStyle: { color: '#d9d9d9' } },
|
|
|
|
|
+ },
|
|
|
|
|
+ series: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: 'bar',
|
|
|
|
|
+ data: seriesData,
|
|
|
|
|
+ barWidth: 40,
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ color: '#69c0ff',
|
|
|
|
|
+ borderRadius: [6, 6, 0, 0],
|
|
|
|
|
+ },
|
|
|
|
|
+ emphasis: {
|
|
|
|
|
+ focus: 'series',
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ color: '#4096ff',
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ label: {
|
|
|
|
|
+ show: true,
|
|
|
|
|
+ position: 'top',
|
|
|
|
|
+ formatter: '{c}',
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
},
|
|
},
|
|
|
- ],
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ };
|
|
|
|
|
+ });
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
onMounted(() => loadStatistics());
|
|
onMounted(() => loadStatistics());
|
|
@@ -151,27 +198,27 @@ defineExpose({
|
|
|
<template>
|
|
<template>
|
|
|
<div class="statistics-page">
|
|
<div class="statistics-page">
|
|
|
<section class="filter-card">
|
|
<section class="filter-card">
|
|
|
- <div class="filter-item">
|
|
|
|
|
|
|
+ <div class="filter-item mr-10">
|
|
|
<span class="label">发送日期:</span>
|
|
<span class="label">发送日期:</span>
|
|
|
- <a-range-picker
|
|
|
|
|
- v-model:value="selectedRange"
|
|
|
|
|
- allow-clear
|
|
|
|
|
- format="YYYY-MM-DD"
|
|
|
|
|
- :placeholder="['开始日期', '结束日期']"
|
|
|
|
|
- @change="handleRangeChange"
|
|
|
|
|
- />
|
|
|
|
|
|
|
+ <a-range-picker v-model:value="selectedRange" allow-clear format="YYYY-MM-DD" :placeholder="['开始日期', '结束日期']" @change="handleRangeChange" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="filter-item">
|
|
|
|
|
+ <span class="label">选择问卷:</span>
|
|
|
|
|
+ <a-select v-model:value="questionnaireId" placeholder="请选择问卷名称" style="width: 300px" mode="multiple" :labelInValue="true" >
|
|
|
|
|
+ <a-select-option v-for="item in questionnaireList" :key="item.value" :value="item.value" :label="item.label">{{ item.label }}</a-select-option>
|
|
|
|
|
+ </a-select>
|
|
|
</div>
|
|
</div>
|
|
|
</section>
|
|
</section>
|
|
|
|
|
|
|
|
<section class="chart-card">
|
|
<section class="chart-card">
|
|
|
<main>
|
|
<main>
|
|
|
<!-- <a-spin :spinning="loading"> -->
|
|
<!-- <a-spin :spinning="loading"> -->
|
|
|
- <template v-if="totalSamples">
|
|
|
|
|
- <VChart class="chart" :option="chartOption" autoresize />
|
|
|
|
|
- <!-- <v-chart :option="option" style="width: 350px; height: 200px" /> -->
|
|
|
|
|
- <!-- <VChart class="chart" :option="chartOption" autoresize /> -->
|
|
|
|
|
- </template>
|
|
|
|
|
- <a-empty v-else description="暂无数据" />
|
|
|
|
|
|
|
+ <template v-if="chartOptions.length > 0">
|
|
|
|
|
+ <div v-for="(chartConfig, index) in chartOptions" :key="`chart-${String(chartConfig.questionnaireId)}-${index}`" class="chart-wrapper">
|
|
|
|
|
+ <VChart class="chart" :option="chartConfig.option" autoresize />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <a-empty v-else description="请选择问卷查看统计" />
|
|
|
<!-- </a-spin> -->
|
|
<!-- </a-spin> -->
|
|
|
</main>
|
|
</main>
|
|
|
</section>
|
|
</section>
|
|
@@ -194,7 +241,7 @@ defineExpose({
|
|
|
border-radius: 8px;
|
|
border-radius: 8px;
|
|
|
padding: 16px 24px;
|
|
padding: 16px 24px;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
- justify-content: space-between;
|
|
|
|
|
|
|
+ // justify-content: space-between;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
// border: 1px solid #f0f0f0;
|
|
// border: 1px solid #f0f0f0;
|
|
|
}
|
|
}
|
|
@@ -233,11 +280,21 @@ defineExpose({
|
|
|
main {
|
|
main {
|
|
|
flex: 1;
|
|
flex: 1;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 24px;
|
|
|
// align-items: center;
|
|
// align-items: center;
|
|
|
// justify-content: center;
|
|
// justify-content: center;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+.chart-wrapper {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ padding: 16px;
|
|
|
|
|
+ border: 1px solid #f0f0f0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
.chart-title {
|
|
.chart-title {
|
|
|
font-size: 16px;
|
|
font-size: 16px;
|
|
|
font-weight: 600;
|
|
font-weight: 600;
|
|
@@ -264,4 +321,4 @@ defineExpose({
|
|
|
width: 100%;
|
|
width: 100%;
|
|
|
height: 420px;
|
|
height: 420px;
|
|
|
}
|
|
}
|
|
|
-</style>
|
|
|
|
|
|
|
+</style>
|