<template>
|
<div
|
class="question-chart"
|
v-loading="loading"
|
element-loading-text="正在汇总随访问卷题目..."
|
>
|
<div class="chart-header">
|
<div class="chart-title">
|
{{ view === "pie" ? "问题详情占比饼图" : "问题详情占比图表" }}
|
</div>
|
<div class="chart-header-right">
|
<div v-if="topics.length" class="chart-meta">
|
共 {{ topics.length }} 道题
|
</div>
|
<el-button
|
type="warning"
|
plain
|
size="mini"
|
icon="el-icon-download"
|
:disabled="!topics.length"
|
@click="exportExcel"
|
>导出</el-button
|
>
|
</div>
|
</div>
|
|
<div v-if="!loading && !topics.length" class="chart-empty">
|
<el-empty description="暂无问题详情数据"></el-empty>
|
</div>
|
|
<div v-else class="chart-grid" :style="gridStyle">
|
<question-chart-item
|
v-for="(topic, index) in topics"
|
:key="topic.scriptid || topic.id || index"
|
:ref="'chartItem-' + index"
|
:topic="topic"
|
:index="index"
|
:view="view"
|
:hide-index="hideIndex"
|
:chart-height="chartHeight"
|
/>
|
</div>
|
</div>
|
</template>
|
|
<script>
|
import ExcelJS from "exceljs";
|
import { saveAs } from "file-saver";
|
import QuestionChartItem from "./QuestionChartItem.vue";
|
import { formatPercentage, normalizeTopicList } from "./topicUtils";
|
|
export default {
|
name: "QuestionChart",
|
components: {
|
QuestionChartItem,
|
},
|
props: {
|
topicList: {
|
type: [Array, Object],
|
default: () => [],
|
},
|
view: {
|
type: String,
|
default: "pie",
|
},
|
loading: {
|
type: Boolean,
|
default: false,
|
},
|
hideIndex: {
|
type: Boolean,
|
default: false,
|
},
|
columns: {
|
type: Number,
|
default: 0,
|
},
|
chartHeight: {
|
type: Number,
|
default: 230,
|
},
|
},
|
computed: {
|
topics() {
|
return normalizeTopicList(this.topicList);
|
},
|
gridStyle() {
|
if (this.columns === 2) {
|
return { gridTemplateColumns: "repeat(2, minmax(0, 1fr))" };
|
}
|
return {};
|
},
|
},
|
methods: {
|
fileDate() {
|
const now = new Date();
|
const pad = (value) => String(value).padStart(2, "0");
|
return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(
|
now.getDate()
|
)}`;
|
},
|
async exportExcel() {
|
if (!this.topics.length) {
|
this.$message.warning("暂无可导出的数据");
|
return;
|
}
|
|
try {
|
const workbook = new ExcelJS.Workbook();
|
const border = {
|
top: { style: "thin", color: { argb: "FFD0D0D0" } },
|
left: { style: "thin", color: { argb: "FFD0D0D0" } },
|
bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
|
right: { style: "thin", color: { argb: "FFD0D0D0" } },
|
};
|
|
this.topics.forEach((topic, index) => {
|
const worksheet = workbook.addWorksheet(`第${index + 1}题`);
|
|
worksheet.mergeCells(1, 1, 1, 3);
|
const titleCell = worksheet.getCell(1, 1);
|
titleCell.value = this.hideIndex
|
? topic.scriptContent || ""
|
: `第${index + 1}题:${topic.scriptContent || ""}`;
|
titleCell.font = { size: 12, bold: true };
|
titleCell.alignment = {
|
horizontal: "left",
|
vertical: "middle",
|
wrapText: true,
|
};
|
worksheet.getRow(1).height = 40;
|
|
const headerRow = worksheet.addRow(["选项", "人数", "占比"]);
|
headerRow.height = 22;
|
headerRow.eachCell((cell) => {
|
cell.font = { bold: true };
|
cell.fill = {
|
type: "pattern",
|
pattern: "solid",
|
fgColor: { argb: "FFF5F7FA" },
|
};
|
cell.border = border;
|
cell.alignment = { horizontal: "center", vertical: "middle" };
|
});
|
|
const details = topic.details || [];
|
const rows = details.length
|
? details
|
: [{ optionText: "暂无数据", chosenQuantity: 0, chosenPercentage: "" }];
|
|
rows.forEach((detail) => {
|
const dataRow = worksheet.addRow([
|
detail.optionText || "",
|
Number(detail.chosenQuantity) || 0,
|
detail.optionText
|
? formatPercentage(detail.chosenPercentage)
|
: "-",
|
]);
|
dataRow.height = 22;
|
dataRow.eachCell((cell) => {
|
cell.border = border;
|
cell.alignment = {
|
horizontal: "center",
|
vertical: "middle",
|
wrapText: true,
|
};
|
});
|
});
|
|
worksheet.columns = [
|
{ width: 36 },
|
{ width: 12 },
|
{ width: 12 },
|
];
|
|
const chartItem = this.$refs[`chartItem-${index}`];
|
const dataUrl =
|
chartItem && chartItem.getChartDataURL
|
? chartItem.getChartDataURL()
|
: "";
|
|
if (dataUrl) {
|
const imageId = workbook.addImage({
|
base64: dataUrl.split(",")[1],
|
extension: "png",
|
});
|
worksheet.addImage(imageId, {
|
tl: { col: 0, row: rows.length + 3 },
|
ext: { width: 600, height: 340 },
|
});
|
}
|
});
|
|
const buffer = await workbook.xlsx.writeBuffer();
|
const blob = new Blob([buffer], {
|
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
});
|
const viewName = this.view === "pie" ? "饼图" : "图表";
|
saveAs(blob, `问题详情占比${viewName}_${this.fileDate()}.xlsx`);
|
this.$message.success("导出成功");
|
} catch (error) {
|
console.error("导出问题详情图表失败:", error);
|
this.$message.error("导出失败");
|
}
|
},
|
},
|
};
|
</script>
|
|
<style lang="scss" scoped>
|
.question-chart {
|
padding: 4px 0 8px;
|
min-height: 240px;
|
|
.chart-header {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
flex-wrap: wrap;
|
gap: 12px;
|
margin-bottom: 14px;
|
}
|
|
.chart-title {
|
font-size: 15px;
|
font-weight: 600;
|
color: #303133;
|
}
|
|
.chart-meta {
|
font-size: 13px;
|
color: #909399;
|
}
|
|
.chart-header-right {
|
display: flex;
|
align-items: center;
|
gap: 10px;
|
}
|
|
.chart-empty {
|
padding: 40px 0;
|
}
|
|
.chart-grid {
|
display: grid;
|
grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
|
gap: 14px;
|
}
|
}
|
|
@media (max-width: 1400px) {
|
.question-chart .chart-grid {
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
}
|
}
|
</style>
|