feat: add consumer behavior survey report

dev-QQq
chenyuan 3 weeks ago
parent a4177c98d3
commit acf42b3b8b

@ -407,3 +407,19 @@ export function previewConsumerBehaviorQuestionnaireWord(data) {
},
});
}
export function checkConsumerBehaviorStepFour(payload) {
if (isStudentDemo()) {
const rows = Array.isArray(payload?.rows) ? payload.rows : [];
if (rows.length < 5 || rows.some((item) => !String(item?.title || "").trim() || !String(item?.content || "").trim())) {
return Promise.reject(new Error("请完整填写 5 个问卷调研报告模块的标题和内容"));
}
return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } });
}
return request({
url: "/api/student-training-answers/consumer-behavior/step-4/validate",
method: "post",
data: payload,
headers: { repeatSubmit: false },
});
}

@ -194,10 +194,41 @@
</section>
<section v-else class="step-panel">
<p class="task-desc">
请根据问卷设计数据收集与数据可视化结果整理形成最终分析报告
<p class="task-desc survey-report-desc">
将步骤三设计的调查问卷导入问卷星等在线问卷调查平台开展问卷调查基于收集到的数据填写问卷调查报告
</p>
<ReportFields v-model="form.report" />
<div class="survey-report-table-wrap">
<table class="survey-report-table">
<thead>
<tr>
<th>报告模块标题</th>
<th>内容</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) in form.surveyReportRows" :key="index">
<td>
<input v-model="row.title" class="survey-report-title-input" :placeholder="surveyReportTitlePlaceholder(index)" :aria-label="`${index + 1}`" />
</td>
<td>
<textarea v-model="row.content" class="survey-report-content-input" :placeholder="surveyReportContentPlaceholder(index)" :aria-label="`${index + 1}`" />
</td>
</tr>
</tbody>
</table>
</div>
<div class="survey-report-guide">
<div>
<h5>可参考以下方法</h5>
<p>1. 根据报告模板自行分析撰写问卷调查报告</p>
<p>2. 使用通用 AI 大模型辅助完成报告请根据用户调研数据从用户购买核心决策因素不同群体决策因素差异未购买用户的顾虑与市场机会信息获取渠道与购买场景等方面撰写报告</p>
<p>3. 如问卷调查收集的数据不理想可使用本实训提供的数据进行分析生成调查报告</p>
</div>
<el-button class="survey-report-template-action" @click="downloadSurveyReportTemplate">
<el-icon><Download /></el-icon>
下载报告模板
</el-button>
</div>
</section>
<div class="step-actions">
@ -238,13 +269,13 @@
</template>
<script setup>
import { computed, defineComponent, getCurrentInstance, h, nextTick, onMounted, ref } from "vue";
import { computed, getCurrentInstance, nextTick, onMounted, ref } from "vue";
import { ArrowLeft, ArrowRight, CircleCheck, Document, Download, Upload } from "@element-plus/icons-vue";
import TrainingAiSidebar from "@/views/components/TrainingAiSidebar.vue";
import TrainingMaterialButton from "@/views/components/TrainingMaterialButton.vue";
import TrainingTaskBrief from "@/views/components/TrainingTaskBrief.vue";
import { getTrainingTaskByKey } from "@/api/trainingTask";
import { checkConsumerBehaviorStepOne, checkConsumerBehaviorStepTwo, checkConsumerBehaviorStepThree, getStudentTrainingAnswer, previewConsumerBehaviorQuestionnaireWord, saveStudentTrainingAnswer, uploadStudentTrainingFile } from "@/api/studentTrainingAnswer";
import { checkConsumerBehaviorStepOne, checkConsumerBehaviorStepTwo, checkConsumerBehaviorStepThree, checkConsumerBehaviorStepFour, getStudentTrainingAnswer, previewConsumerBehaviorQuestionnaireWord, saveStudentTrainingAnswer, uploadStudentTrainingFile } from "@/api/studentTrainingAnswer";
const TASK_KEY = "consumer-behavior";
const KANO_NEED_LIMIT = 20;
@ -356,80 +387,6 @@ const visualGroups = [
},
];
const reportPlaceholders = {
coreDecisionFactors:
"从调研数据可以看出功能实用性和产品质量与稳定性是用户购买智能产品时最为关注的因素选择“重要”和“非常重要”的人数占比分别达到80%和75%。这表明用户在购买智能产品时,更加注重产品能否切实解决实际问题,以及产品在长期使用过程中的可靠性。\n\n价格也是用户重点考虑的因素之一有65%的用户认为价格重要或非常重要。不同收入群体对价格的敏感度有所差异,中低收入群体在购买智能产品时,价格仍是主要的制约因素,而高收入群体则更愿意为高品质、高性能的产品支付更高价格。",
groupDifferences:
"1. 年龄差异19-25岁的年轻群体更注重产品的外观设计和生态兼容性他们追求个性化和时尚感同时希望智能产品能够与其他设备实现联动。\n2. 收入差异:高收入群体对品牌知名度和数据安全与隐私保护的关注度更高;低收入群体则主要聚焦于价格和功能实用性。\n3. 城市级别差异:一线城市和新一线城市用户对生态兼容性和创新功能的接受度更高,三线及以下城市用户更关注价格和产品质量。",
nonPurchaseOpportunity:
"未购买智能产品的用户中价格过高是最主要的原因占比达到43.75%。这反映出当前部分智能产品的价格超出了一些用户的承受范围,市场上存在对高性价比智能产品的需求。\n\n此外对智能产品功能不了解也是未购买的重要原因之一。企业需要加强市场教育通过产品介绍视频、使用教程和线下体验降低用户认知门槛。同时数据安全与隐私保护也可以作为产品差异化卖点。",
channelScenario:
"电商平台和社交媒体是用户获取智能产品信息的主要渠道分别有60%和50%的用户选择。企业应加大在电商平台和社交媒体上的内容投放,通过评测、种草、场景演示等方式提高产品曝光。\n\n购买场景方面自己使用是最主要场景占比70%。但赠送家人、朋友也占据一定比例,企业可以针对礼品场景设计套装和包装,提高产品附加值。",
};
const emptyReport = {
coreDecisionFactors: "",
groupDifferences: "",
nonPurchaseOpportunity: "",
channelScenario: "",
};
const reportSampleMarkers = {
coreDecisionFactors: ["从调研数据可以看出", "价格也是用户重点考虑的因素之一"],
groupDifferences: ["年龄差异", "收入差异", "城市级别差异"],
nonPurchaseOpportunity: ["未购买智能产品的用户中", "价格过高是最主要的原因"],
channelScenario: ["电商平台和社交媒体是用户获取智能产品信息的主要渠道", "购买场景方面"],
};
function isDefaultReportSample(key, value) {
const currentValue = String(value || "");
return (
currentValue === reportPlaceholders[key] ||
reportSampleMarkers[key]?.some((marker) => currentValue.includes(marker))
);
}
const ReportFields = defineComponent({
name: "ReportFields",
props: {
modelValue: {
type: Object,
required: true,
},
},
emits: ["update:modelValue"],
setup(props, { emit }) {
const update = (key, value) => {
emit("update:modelValue", {
...props.modelValue,
[key]: value,
});
};
const fields = [
["coreDecisionFactors", "核心决策因素", reportPlaceholders.coreDecisionFactors],
["groupDifferences", "不同群体决策因素差异", reportPlaceholders.groupDifferences],
["nonPurchaseOpportunity", "未购买用户的顾虑与市场机会", reportPlaceholders.nonPurchaseOpportunity],
["channelScenario", "信息获取渠道与购买场景分析", reportPlaceholders.channelScenario],
];
return () =>
h(
"div",
{ class: "report-fields" },
fields.map(([key, label, placeholder]) =>
h("label", { class: "field-block", key }, [
h("span", label),
h("textarea", {
class: "large-textarea",
value: isDefaultReportSample(key, props.modelValue[key]) ? "" : props.modelValue[key],
placeholder,
onInput: (event) => update(key, event.target.value),
}),
])
)
);
},
});
const taskConfig = ref(null);
const saving = ref(false);
const uploading = ref(false);
@ -455,13 +412,13 @@ function createForm() {
kanoNeeds: createKanoNeeds(),
questionnaireQuestions: createQuestionnaireQuestions(),
questionnaireImport: null,
surveyReportRows: createSurveyReportRows(),
questionnaireText: questionnaireTemplate,
questionnaireFileName: "",
questionnaireFileUrl: "",
dataExcelFileName: "",
dataExcelFileUrl: "",
chartFiles: {},
report: { ...emptyReport },
};
}
@ -513,6 +470,10 @@ function createQuestionnaireQuestion(section = "基本信息", type = "SINGLE")
return { id: `question-${Date.now()}-${questionnaireQuestionSequence}`, section, type, title: "", options: "" };
}
function createSurveyReportRows() {
return Array.from({ length: 5 }, () => ({ title: "", content: "" }));
}
const form = ref(createForm());
const taskTitle = computed(() => taskConfig.value?.taskName || "消费者需求与行为");
@ -526,7 +487,7 @@ const progressItems = computed(() => [
{ text: hasQuestionnaireDesign() ? "已完成:问卷设计" : "待完成:问卷设计", status: hasQuestionnaireDesign() ? "done" : "" },
{ text: form.value.dataExcelFileName ? "已完成:数据整理上传" : "待完成整理并上传Excel", status: form.value.dataExcelFileName ? "done" : "" },
{ text: Object.keys(form.value.chartFiles).length ? "进行中:数据可视化截图" : "待完成:数据可视化截图", status: Object.keys(form.value.chartFiles).length ? "done" : "" },
{ text: hasReportContent() ? "进行中:分析报告" : "待完成:分析报告", status: hasReportContent() ? "done" : "" },
{ text: hasSurveyReport() ? "已完成:问卷调研报告" : "待完成:问卷调研报告", status: hasSurveyReport() ? "done" : "" },
]);
onMounted(async () => {
@ -585,8 +546,8 @@ function applySavedAnswer(value) {
kanoNeeds: normalizeKanoNeeds(parsed.kanoNeeds),
questionnaireQuestions: normalizeQuestionnaireQuestions(parsed.questionnaireQuestions),
questionnaireImport: normalizeQuestionnaireImport(parsed.questionnaireImport),
surveyReportRows: normalizeSurveyReportRows(parsed.surveyReportRows),
chartFiles: parsed.chartFiles || {},
report: normalizeReport(parsed.report),
};
nextTick(renderQuestionnaireEditor);
} catch (error) {
@ -632,12 +593,12 @@ function normalizeQuestionnaireImport(value) {
};
}
function normalizeReport(value = {}) {
return Object.keys(emptyReport).reduce((report, key) => {
const currentValue = value?.[key] || "";
report[key] = isDefaultReportSample(key, currentValue) ? "" : currentValue;
return report;
}, { ...emptyReport });
function normalizeSurveyReportRows(rows) {
if (!Array.isArray(rows) || !rows.length) return createSurveyReportRows();
return Array.from({ length: 5 }, (_, index) => ({
title: String(rows[index]?.title || ""),
content: String(rows[index]?.content || ""),
}));
}
function syncQuestionnaireText() {
@ -808,8 +769,8 @@ async function goNext() {
if (form.value.activeStep < flowSteps.value.length - 1) setActiveStep(form.value.activeStep + 1);
}
function hasReportContent() {
return Object.values(form.value.report || {}).some((value) => String(value || "").trim());
function hasSurveyReport() {
return form.value.surveyReportRows.every((item) => String(item?.title || "").trim() && String(item?.content || "").trim());
}
function hasQuestionnaireDesign() {
@ -849,6 +810,9 @@ async function persist(status = "IN_PROGRESS") {
importedDocumentUrl: form.value.questionnaireImport?.url || "",
});
}
if (status !== "RESET" && (form.value.activeStep === 3 || status === "SUBMITTED")) {
await checkConsumerBehaviorStepFour({ rows: form.value.surveyReportRows });
}
await saveStudentTrainingAnswer(TASK_KEY, buildAnswerPayload(status));
proxy?.$modal?.msgSuccess(status === "SUBMITTED" ? "提交成功" : "保存成功");
} finally {
@ -897,11 +861,8 @@ function exportOutcome() {
"数据收集文件:",
form.value.dataExcelFileName || "未上传",
"",
"分析报告:",
`核心决策因素:\n${form.value.report.coreDecisionFactors || ""}`,
`不同群体决策因素差异:\n${form.value.report.groupDifferences || ""}`,
`未购买用户的顾虑与市场机会:\n${form.value.report.nonPurchaseOpportunity || ""}`,
`信息获取渠道与购买场景分析:\n${form.value.report.channelScenario || ""}`,
"问卷调研报告:",
...form.value.surveyReportRows.map((item, index) => `${index + 1}. ${item.title || "未命名模块"}\n${item.content || ""}`),
];
const blob = new Blob([lines.join("\n")], { type: "text/plain;charset=utf-8" });
const url = URL.createObjectURL(blob);
@ -911,6 +872,26 @@ function exportOutcome() {
link.click();
URL.revokeObjectURL(url);
}
function surveyReportTitlePlaceholder(index) {
return ["例如:调研背景与目的", "例如:样本与调研方法", "例如:用户购买决策因素", "例如:不同群体需求差异", "例如:产品建议与结论"][index] || "请输入报告模块标题";
}
function surveyReportContentPlaceholder(index) {
return ["说明本次问卷调研的背景、目标用户与调研目的。", "说明样本数量、调研时间、调研渠道与数据来源。", "结合问卷数据,分析影响用户购买决策的核心因素。", "比较不同人群在需求、价格、渠道或购买场景上的差异。", "总结调研结论,并提出产品功能、定价或营销建议。"][index] || "请输入报告内容";
}
function downloadSurveyReportTemplate() {
const rows = Array.from({ length: 5 }, (_, index) => `<tr><td>${surveyReportTitlePlaceholder(index)}</td><td>${surveyReportContentPlaceholder(index)}</td></tr>`).join("");
const html = `<!doctype html><html><head><meta charset="utf-8"><title>消费者需求调研报告模板</title><style>body{font-family:SimSun,serif;padding:32px;color:#222}h1{font-size:22px;text-align:center}p{line-height:1.75}table{width:100%;border-collapse:collapse;margin-top:24px}th,td{border:1px solid #777;padding:14px;vertical-align:top}th{background:#14a3df;color:#fff}td:first-child{width:22%}</style></head><body><h1>消费者需求调研报告</h1><p>请将步骤三设计的调查问卷导入在线问卷调查平台开展调研,并基于收集到的数据完成本报告。</p><table><thead><tr><th>报告模块标题</th><th>内容</th></tr></thead><tbody>${rows}</tbody></table></body></html>`;
const blob = new Blob([html], { type: "application/msword;charset=utf-8" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "消费者需求调研报告模板.doc";
link.click();
URL.revokeObjectURL(url);
}
</script>
<style scoped lang="scss">
@ -1711,6 +1692,106 @@ function exportOutcome() {
color: #b9d3e0;
}
.survey-report-desc {
max-width: 1050px;
margin-bottom: 24px;
}
.survey-report-table-wrap {
overflow-x: auto;
border: 1px solid rgba(65, 169, 214, 0.52);
border-radius: 16px;
background: rgba(2, 23, 36, 0.56);
}
.survey-report-table {
width: 100%;
min-width: 760px;
border-collapse: collapse;
th,
td {
border: 1px solid rgba(77, 148, 183, 0.5);
}
th {
padding: 14px 18px;
color: #e9fbff;
background: rgba(9, 111, 157, 0.68);
font-size: 15px;
letter-spacing: 0.04em;
}
th:first-child,
td:first-child {
width: 22%;
}
td {
padding: 10px;
background: rgba(5, 31, 47, 0.42);
}
}
.survey-report-title-input,
.survey-report-content-input {
display: block;
width: 100%;
border: 0;
outline: 0;
color: #e7f8ff;
background: transparent;
font: inherit;
&::placeholder {
color: #668a9b;
}
}
.survey-report-title-input {
min-height: 88px;
padding: 8px;
text-align: center;
font-weight: 700;
}
.survey-report-content-input {
min-height: 88px;
padding: 8px 12px;
resize: vertical;
line-height: 1.75;
}
.survey-report-guide {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 28px;
margin-top: 26px;
padding: 20px 22px;
border: 1px solid rgba(69, 164, 207, 0.27);
border-radius: 14px;
color: #b8d2de;
background: linear-gradient(105deg, rgba(6, 48, 69, 0.75), rgba(5, 24, 38, 0.52));
h5,
p {
margin: 0;
line-height: 1.85;
}
h5 {
color: #ffffff;
font-size: 15px;
}
}
.survey-report-template-action {
flex: 0 0 auto;
min-width: 146px;
min-height: 42px;
}
.questionnaire-desc {
flex: 1;
margin: 0;
@ -2155,6 +2236,14 @@ function exportOutcome() {
justify-self: end;
}
.survey-report-guide {
flex-direction: column;
}
.survey-report-template-action {
width: 100%;
}
.step-indicator {
align-items: stretch;
flex-direction: column;

Loading…
Cancel
Save