-
- {{ item.label }}
-
- 上传Excel图形截图
-
- {{ form.chartFiles[item.key].name }}
-
+
+
+
Questionnaire Studio
+
在线编写消费者需求调研问卷
+
通过分区、题型与选项设置,快速完成可执行的问卷结构。
+
进入问卷编辑器
-
- 4.结合开放性问题建议以及上述数据可视化结果,进行用户购买智能产品决策因素分析,给出分析报告,注意结合不同类别调研数据进行综合性交叉分析。
-
-
+
+ 已编辑 {{ form.questionnaireQuestions.length }} 题;{{ form.questionnaireImport?.name ? "已导入 1 份 Word 问卷,可随时预览。" : "也可导入已完成的 .docx 问卷。" }}
+
+
+
+
+
请覆盖基本信息、购买行为、需求偏好和开放建议四部分;需求偏好至少设置一题 Likert 五级量表。
+
新增题目
+
+
+
+ {{ String(index + 1).padStart(2, "0") }}
+
+
+
+
+
+
+
+
+
+
量表固定为:非常不重要、较不重要、一般、较重要、非常重要
+
+
+
+
+
+
+
+ {{ form.questionnaireImport.previewContent }}
+ 暂未解析到可预览内容。
+
@@ -201,10 +244,11 @@ 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, getStudentTrainingAnswer, saveStudentTrainingAnswer, uploadStudentTrainingFile } from "@/api/studentTrainingAnswer";
+import { checkConsumerBehaviorStepOne, checkConsumerBehaviorStepTwo, checkConsumerBehaviorStepThree, getStudentTrainingAnswer, previewConsumerBehaviorQuestionnaireWord, saveStudentTrainingAnswer, uploadStudentTrainingFile } from "@/api/studentTrainingAnswer";
const TASK_KEY = "consumer-behavior";
const KANO_NEED_LIMIT = 20;
+let questionnaireQuestionSequence = 0;
const { proxy } = getCurrentInstance();
const defaultRequirement = "消费者需求与行为分析用于识别目标用户的购买动机、决策因素、渠道偏好和使用场景,为智能产品功能设计与市场切入提供依据。";
@@ -389,9 +433,13 @@ const ReportFields = defineComponent({
const taskConfig = ref(null);
const saving = ref(false);
const uploading = ref(false);
+const questionnaireImporting = ref(false);
const fileInputRef = ref(null);
+const questionnaireImportInputRef = ref(null);
const questionnaireEditorRef = ref(null);
const uploadContext = ref({ type: "", key: "" });
+const questionnaireEditorVisible = ref(false);
+const showQuestionnairePreview = ref(false);
const uploadAccept = computed(() => {
if (uploadContext.value.type === "questionnaire") return ".pdf";
@@ -405,6 +453,8 @@ function createForm() {
activeStep: 0,
targetHypotheses: createTargetHypotheses(),
kanoNeeds: createKanoNeeds(),
+ questionnaireQuestions: createQuestionnaireQuestions(),
+ questionnaireImport: null,
questionnaireText: questionnaireTemplate,
questionnaireFileName: "",
questionnaireFileUrl: "",
@@ -428,6 +478,18 @@ function createEmptyTargetHypothesis() {
}
const kanoTypeOptions = ["基本型", "期望型", "兴奋型", "无差异型", "反向型"];
+const questionnaireSections = [
+ { value: "基本信息", label: "基本信息" },
+ { value: "购买行为", label: "购买行为" },
+ { value: "需求偏好", label: "需求偏好" },
+ { value: "开放建议", label: "开放建议" },
+];
+const questionnaireQuestionTypes = [
+ { value: "SINGLE", label: "单选题" },
+ { value: "MULTIPLE", label: "多选题" },
+ { value: "LIKERT", label: "Likert 五级量表" },
+ { value: "TEXT", label: "开放题" },
+];
function createKanoNeeds() {
return Array.from({ length: 3 }, createEmptyKanoNeed);
@@ -437,6 +499,20 @@ function createEmptyKanoNeed() {
return { feature: "", kanoType: "", featureDirection: "" };
}
+function createQuestionnaireQuestions() {
+ return [
+ createQuestionnaireQuestion("基本信息", "SINGLE"),
+ createQuestionnaireQuestion("购买行为", "SINGLE"),
+ createQuestionnaireQuestion("需求偏好", "LIKERT"),
+ createQuestionnaireQuestion("开放建议", "TEXT"),
+ ];
+}
+
+function createQuestionnaireQuestion(section = "基本信息", type = "SINGLE") {
+ questionnaireQuestionSequence += 1;
+ return { id: `question-${Date.now()}-${questionnaireQuestionSequence}`, section, type, title: "", options: "" };
+}
+
const form = ref(createForm());
const taskTitle = computed(() => taskConfig.value?.taskName || "消费者需求与行为");
@@ -447,7 +523,7 @@ const flowSteps = computed(() => parseArray(taskConfig.value?.steps, []).slice(0
const activeStepName = computed(() => flowSteps.value[form.value.activeStep] || "");
const progressItems = computed(() => [
- { text: form.value.questionnaireFileName ? "已完成:问卷上传" : "待完成:问卷设计与上传", status: form.value.questionnaireFileName ? "done" : "" },
+ { 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" : "" },
@@ -507,6 +583,8 @@ function applySavedAnswer(value) {
questionnaireText: normalizeQuestionnaireContent(parsed.questionnaireText || questionnaireTemplate),
targetHypotheses: normalizeTargetHypotheses(parsed.targetHypotheses),
kanoNeeds: normalizeKanoNeeds(parsed.kanoNeeds),
+ questionnaireQuestions: normalizeQuestionnaireQuestions(parsed.questionnaireQuestions),
+ questionnaireImport: normalizeQuestionnaireImport(parsed.questionnaireImport),
chartFiles: parsed.chartFiles || {},
report: normalizeReport(parsed.report),
};
@@ -534,6 +612,26 @@ function normalizeKanoNeeds(rows) {
}));
}
+function normalizeQuestionnaireQuestions(rows) {
+ if (!Array.isArray(rows) || !rows.length) return createQuestionnaireQuestions();
+ return rows.map((row, index) => ({
+ id: String(row?.id || `question-restored-${index}`),
+ section: questionnaireSections.some((item) => item.value === row?.section) ? row.section : "基本信息",
+ type: questionnaireQuestionTypes.some((item) => item.value === row?.type) ? row.type : "SINGLE",
+ title: String(row?.title || ""),
+ options: String(row?.options || ""),
+ }));
+}
+
+function normalizeQuestionnaireImport(value) {
+ if (!value || typeof value !== "object" || !value.name) return null;
+ return {
+ name: String(value.name),
+ url: String(value.url || ""),
+ previewContent: String(value.previewContent || ""),
+ };
+}
+
function normalizeReport(value = {}) {
return Object.keys(emptyReport).reduce((report, key) => {
const currentValue = value?.[key] || "";
@@ -617,6 +715,29 @@ function removeKanoNeed() {
form.value.kanoNeeds.pop();
}
+function questionPlaceholder(section) {
+ const placeholders = {
+ 基本信息: "例如:您的年龄处于以下哪个区间?",
+ 购买行为: "例如:您通常通过什么渠道购买智能助眠产品?",
+ 需求偏好: "例如:白噪声可定时关闭对您有多重要?",
+ 开放建议: "例如:您希望智能助眠产品增加哪些功能?",
+ };
+ return placeholders[section] || "请输入题目内容";
+}
+
+function addQuestionnaireQuestion() {
+ form.value.questionnaireQuestions.push(createQuestionnaireQuestion());
+}
+
+function removeQuestionnaireQuestion(index) {
+ if (form.value.questionnaireQuestions.length <= 4) return;
+ form.value.questionnaireQuestions.splice(index, 1);
+}
+
+function triggerQuestionnaireImport() {
+ questionnaireImportInputRef.value?.click();
+}
+
function viewTargetUserProfile() {
window.open("/demand/people", "_blank", "noopener");
}
@@ -650,12 +771,40 @@ async function handleFileChange(event) {
}
}
+async function handleQuestionnaireImport(event) {
+ const file = event.target.files?.[0];
+ if (!file) return;
+ if (!/\.docx$/i.test(file.name)) {
+ proxy?.$modal?.msgWarning("请导入 .docx 格式的 Word 问卷,以便在线预览");
+ event.target.value = "";
+ return;
+ }
+ const data = new FormData();
+ data.append("file", file);
+ questionnaireImporting.value = true;
+ try {
+ const previewRes = await previewConsumerBehaviorQuestionnaireWord(data);
+ const uploadRes = await uploadStudentTrainingFile(data);
+ const url = uploadRes?.url || uploadRes?.fileName || uploadRes?.data?.url || uploadRes?.data?.fileName || "";
+ form.value.questionnaireImport = {
+ name: file.name,
+ url,
+ previewContent: String(previewRes?.data?.content || ""),
+ };
+ showQuestionnairePreview.value = true;
+ proxy?.$modal?.msgSuccess("问卷导入成功");
+ } finally {
+ questionnaireImporting.value = false;
+ event.target.value = "";
+ }
+}
+
function goPrev() {
if (form.value.activeStep > 0) setActiveStep(form.value.activeStep - 1);
}
async function goNext() {
- if (form.value.activeStep <= 1) await persist("IN_PROGRESS");
+ if (form.value.activeStep <= 2) await persist("IN_PROGRESS");
if (form.value.activeStep < flowSteps.value.length - 1) setActiveStep(form.value.activeStep + 1);
}
@@ -663,6 +812,11 @@ function hasReportContent() {
return Object.values(form.value.report || {}).some((value) => String(value || "").trim());
}
+function hasQuestionnaireDesign() {
+ return Boolean(form.value.questionnaireImport?.name)
+ || form.value.questionnaireQuestions.some((item) => String(item?.title || "").trim());
+}
+
function buildAnswerPayload(status) {
syncQuestionnaireText();
const saveAction = status === "SUBMITTED" ? "SUBMIT" : status === "RESET" ? "RESET" : "SAVE";
@@ -689,6 +843,12 @@ async function persist(status = "IN_PROGRESS") {
if (status !== "RESET" && (form.value.activeStep === 1 || status === "SUBMITTED")) {
await checkConsumerBehaviorStepTwo({ needs: form.value.kanoNeeds });
}
+ if (status !== "RESET" && (form.value.activeStep === 2 || status === "SUBMITTED")) {
+ await checkConsumerBehaviorStepThree({
+ questions: form.value.questionnaireQuestions,
+ importedDocumentUrl: form.value.questionnaireImport?.url || "",
+ });
+ }
await saveStudentTrainingAnswer(TASK_KEY, buildAnswerPayload(status));
proxy?.$modal?.msgSuccess(status === "SUBMITTED" ? "提交成功" : "保存成功");
} finally {
@@ -724,6 +884,10 @@ function exportOutcome() {
"KANO 用户需求分类:",
...form.value.kanoNeeds.map((item, index) => `${index + 1}. 功能:${item.feature || ""}\nKANO类型:${item.kanoType || ""}\n功能方向:${item.featureDirection || ""}`),
"",
+ "消费者需求调研问卷:",
+ form.value.questionnaireImport?.name ? `导入问卷:${form.value.questionnaireImport.name}` : "",
+ ...form.value.questionnaireQuestions.map((item, index) => `${index + 1}. [${item.section}] ${item.title || ""}(${questionnaireQuestionTypes.find((type) => type.value === item.type)?.label || ""})`),
+ "",
"问卷设计:",
questionnaireTextForExport(),
"",
@@ -1324,6 +1488,229 @@ function exportOutcome() {
color: #8da5b2;
}
+.questionnaire-design-head {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 18px 28px;
+ margin-bottom: 20px;
+}
+
+.questionnaire-design-desc {
+ max-width: 820px;
+ margin: 0;
+ color: #d9eaf4;
+ font-size: 16px;
+ font-weight: 600;
+}
+
+.questionnaire-import-action,
+.questionnaire-editor-launcher__action {
+ min-width: 178px;
+ height: 44px;
+ border: 1px solid rgba(92, 224, 255, 0.7);
+ border-radius: 10px;
+ color: #ffffff;
+ background: linear-gradient(95deg, #0b84bd, #096491);
+ font-size: 15px;
+ font-weight: 800;
+}
+
+.questionnaire-imported-file {
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ max-width: 100%;
+ min-height: 42px;
+ padding: 0 13px;
+ margin-bottom: 18px;
+ border: 1px solid rgba(87, 215, 255, 0.4);
+ border-radius: 12px;
+ color: #dff9ff;
+ background: rgba(10, 97, 137, 0.28);
+
+ span {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ button {
+ flex: 0 0 auto;
+ border: 0;
+ color: #75e5ff;
+ background: transparent;
+ cursor: pointer;
+ font-weight: 800;
+ }
+}
+
+.questionnaire-editor-launcher {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 32px;
+ min-height: 190px;
+ padding: 32px 40px;
+ overflow: hidden;
+ border: 1px solid rgba(54, 195, 243, 0.36);
+ border-radius: 20px;
+ background:
+ radial-gradient(circle at 86% 18%, rgba(31, 207, 255, 0.22), transparent 34%),
+ linear-gradient(115deg, rgba(7, 59, 87, 0.94), rgba(2, 20, 34, 0.9));
+}
+
+.questionnaire-editor-launcher__copy {
+ position: relative;
+ z-index: 1;
+
+ h4 {
+ margin: 7px 0;
+ color: #ffffff;
+ font-size: 23px;
+ }
+
+ p {
+ max-width: 600px;
+ margin: 0;
+ color: #b6d1df;
+ line-height: 1.7;
+ }
+}
+
+.questionnaire-editor-launcher__action {
+ flex: 0 0 auto;
+ min-width: 170px;
+ height: 52px;
+}
+
+.questionnaire-editor-summary {
+ margin: 13px 0 0;
+ color: #72deff;
+ font-size: 13px;
+}
+
+:deep(.questionnaire-editor-dialog .el-dialog),
+:deep(.questionnaire-preview-dialog .el-dialog) {
+ overflow: hidden;
+ border: 1px solid rgba(70, 200, 247, 0.35);
+ border-radius: 18px;
+ color: #e7f8ff;
+ background: #061724;
+}
+
+:deep(.questionnaire-editor-dialog .el-dialog__title),
+:deep(.questionnaire-preview-dialog .el-dialog__title) {
+ color: #ffffff;
+ font-weight: 800;
+}
+
+.questionnaire-editor-dialog__intro {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 20px;
+ margin-bottom: 18px;
+
+ p {
+ margin: 0;
+ color: #b9d3e0;
+ line-height: 1.7;
+ }
+}
+
+.questionnaire-editor-add {
+ flex: 0 0 auto;
+ border-color: rgba(92, 224, 255, 0.7);
+ color: #ffffff;
+ background: linear-gradient(95deg, #0b84bd, #096491);
+}
+
+.questionnaire-question-list {
+ display: grid;
+ gap: 12px;
+ max-height: min(58vh, 700px);
+ padding-right: 5px;
+ overflow: auto;
+}
+
+.questionnaire-question-card {
+ display: grid;
+ grid-template-columns: 36px minmax(0, 1fr) auto;
+ gap: 12px;
+ align-items: start;
+ padding: 14px;
+ border: 1px solid rgba(67, 144, 184, 0.34);
+ border-radius: 14px;
+ background: rgba(13, 45, 63, 0.66);
+}
+
+.questionnaire-question-card__number {
+ display: grid;
+ place-items: center;
+ width: 32px;
+ height: 32px;
+ border: 1px solid rgba(80, 222, 255, 0.45);
+ border-radius: 50%;
+ color: #74e5ff;
+ font-size: 12px;
+ font-weight: 800;
+}
+
+.questionnaire-question-card__fields {
+ display: grid;
+ grid-template-columns: 180px 180px minmax(0, 1fr);
+ gap: 10px;
+
+ :deep(.el-select__wrapper),
+ :deep(.el-input__wrapper) {
+ min-height: 38px;
+ border: 1px solid rgba(67, 134, 166, 0.8);
+ box-shadow: none;
+ color: #eaf7ff;
+ background: rgba(5, 26, 39, 0.9);
+ }
+
+ :deep(.el-input__inner) { color: #eaf7ff; }
+}
+
+.likert-tip {
+ grid-column: 1 / -1;
+ margin: 0;
+ color: #7ce0f8;
+ font-size: 12px;
+}
+
+.questionnaire-question-card__remove {
+ padding: 8px 10px;
+ border: 1px solid rgba(104, 161, 187, 0.5);
+ border-radius: 8px;
+ color: #bddfeb;
+ background: transparent;
+ cursor: pointer;
+
+ &:disabled {
+ cursor: not-allowed;
+ opacity: 0.45;
+ }
+}
+
+.questionnaire-word-preview {
+ max-height: min(62vh, 760px);
+ padding: 20px;
+ overflow: auto;
+ border: 1px solid rgba(77, 167, 220, 0.28);
+ border-radius: 12px;
+ color: #e2f3fa;
+ background: rgba(3, 25, 38, 0.86);
+ white-space: pre-wrap;
+ line-height: 1.85;
+}
+
+.questionnaire-preview-empty {
+ color: #b9d3e0;
+}
+
.questionnaire-desc {
flex: 1;
margin: 0;
@@ -1739,6 +2126,35 @@ function exportOutcome() {
flex-direction: column;
}
+ .questionnaire-design-head,
+ .questionnaire-editor-launcher,
+ .questionnaire-editor-dialog__intro {
+ align-items: stretch;
+ flex-direction: column;
+ }
+
+ .questionnaire-import-action,
+ .questionnaire-editor-launcher__action {
+ width: 100%;
+ }
+
+ .questionnaire-editor-launcher {
+ padding: 26px;
+ }
+
+ .questionnaire-question-card {
+ grid-template-columns: 32px minmax(0, 1fr);
+ }
+
+ .questionnaire-question-card__fields {
+ grid-template-columns: 1fr;
+ }
+
+ .questionnaire-question-card__remove {
+ grid-column: 2;
+ justify-self: end;
+ }
+
.step-indicator {
align-items: stretch;
flex-direction: column;