feat: add consumer behavior questionnaire editor

dev-QQq
chenyuan 3 weeks ago
parent 10147d5f5c
commit a4177c98d3

@ -371,3 +371,39 @@ export function checkConsumerBehaviorStepTwo(payload) {
headers: { repeatSubmit: false }, headers: { repeatSubmit: false },
}); });
} }
export function checkConsumerBehaviorStepThree(payload) {
if (isStudentDemo()) {
const importedDocumentUrl = String(payload?.importedDocumentUrl || "").trim();
const questions = Array.isArray(payload?.questions) ? payload.questions : [];
const requiredSections = ["基本信息", "购买行为", "需求偏好", "开放建议"];
const validQuestions = questions.filter((item) => String(item?.title || "").trim());
const complete = requiredSections.every((section) => validQuestions.some((item) => item.section === section))
&& validQuestions.some((item) => item.section === "需求偏好" && item.type === "LIKERT");
if (!importedDocumentUrl && !complete) {
return Promise.reject(new Error("请完整设计包含四个部分且含Likert五级量表的问卷或导入Word问卷"));
}
return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } });
}
return request({
url: "/api/student-training-answers/consumer-behavior/step-3/validate",
method: "post",
data: payload,
headers: { repeatSubmit: false },
});
}
export function previewConsumerBehaviorQuestionnaireWord(data) {
if (isStudentDemo()) {
return Promise.reject(new Error("演示模式不支持 Word 文档预览"));
}
return request({
url: "/api/student-training-answers/consumer-behavior/step-3/word-preview",
method: "post",
data,
headers: {
"Content-Type": "multipart/form-data",
repeatSubmit: false,
},
});
}

@ -49,6 +49,13 @@
:accept="uploadAccept" :accept="uploadAccept"
@change="handleFileChange" @change="handleFileChange"
/> />
<input
ref="questionnaireImportInputRef"
type="file"
class="file-input"
accept=".docx"
@change="handleQuestionnaireImport"
/>
<section class="step-card consumer-behavior-step-card"> <section class="step-card consumer-behavior-step-card">
<div v-if="activeStepName" class="step-title-row"> <div v-if="activeStepName" class="step-title-row">
@ -127,27 +134,63 @@
</section> </section>
<section v-else-if="form.activeStep === 2" class="step-panel"> <section v-else-if="form.activeStep === 2" class="step-panel">
<p class="task-desc"> <div class="questionnaire-design-head">
请根据上一步骤整理的Excel文件利用Excel进行数据可视化上传Excel可视化操作截图 <p class="task-desc questionnaire-design-desc">
结合前面的实训分析设计一份完整的消费者需求调研问卷包含基本信息购买行为需求偏好和开放建议四部分需求偏好部分请用 Likert 五级量表评价第 2 步归类的功能
</p> </p>
<el-button class="questionnaire-import-action" :loading="questionnaireImporting" @click="triggerQuestionnaireImport">
<div class="visual-group" v-for="group in visualGroups" :key="group.title"> <el-icon><Upload /></el-icon>
<h5>{{ group.title }}</h5> 导入问卷Word 文档
<div class="visual-grid">
<div v-for="item in group.items" :key="item.key" class="visual-card">
<span>{{ item.label }}</span>
<el-button class="mini-upload" :loading="uploading" @click="triggerFileSelect('chart', item.key)">
上传Excel图形截图
</el-button> </el-button>
<small v-if="form.chartFiles[item.key]?.name">{{ form.chartFiles[item.key].name }}</small>
</div> </div>
<div v-if="form.questionnaireImport?.name" class="questionnaire-imported-file">
<el-icon><Document /></el-icon>
<span>{{ form.questionnaireImport.name }}</span>
<button type="button" @click="showQuestionnairePreview = true">预览问卷</button>
</div>
<div class="questionnaire-editor-launcher">
<div class="questionnaire-editor-launcher__copy">
<span class="section-eyebrow">Questionnaire Studio</span>
<h4>在线编写消费者需求调研问卷</h4>
<p>通过分区题型与选项设置快速完成可执行的问卷结构</p>
</div> </div>
<el-button class="questionnaire-editor-launcher__action" @click="questionnaireEditorVisible = true">进入问卷编辑器</el-button>
</div> </div>
<h5 class="analysis-heading"> <p class="questionnaire-editor-summary">
4.结合开放性问题建议以及上述数据可视化结果进行用户购买智能产品决策因素分析给出分析报告注意结合不同类别调研数据进行综合性交叉分析 已编辑 {{ form.questionnaireQuestions.length }} {{ form.questionnaireImport?.name ? "已导入 1 份 Word 问卷,可随时预览。" : "也可导入已完成的 .docx 问卷。" }}
</h5> </p>
<ReportFields v-model="form.report" />
<el-dialog v-model="questionnaireEditorVisible" title="消费者需求调研问卷编辑器" width="min(1120px, 94vw)" class="questionnaire-editor-dialog" append-to-body>
<div class="questionnaire-editor-dialog__intro">
<p>请覆盖基本信息购买行为需求偏好和开放建议四部分需求偏好至少设置一题 Likert 五级量表</p>
<el-button class="questionnaire-editor-add" @click="addQuestionnaireQuestion"></el-button>
</div>
<div class="questionnaire-question-list">
<article v-for="(question, index) in form.questionnaireQuestions" :key="question.id" class="questionnaire-question-card">
<span class="questionnaire-question-card__number">{{ String(index + 1).padStart(2, "0") }}</span>
<div class="questionnaire-question-card__fields">
<el-select v-model="question.section" aria-label="问卷分区">
<el-option v-for="section in questionnaireSections" :key="section.value" :label="section.label" :value="section.value" />
</el-select>
<el-select v-model="question.type" aria-label="题目类型">
<el-option v-for="type in questionnaireQuestionTypes" :key="type.value" :label="type.label" :value="type.value" />
</el-select>
<el-input v-model="question.title" :placeholder="questionPlaceholder(question.section)" aria-label="" />
<el-input v-if="question.type === 'SINGLE' || question.type === 'MULTIPLE'" v-model="question.options" placeholder="选项以中文逗号或换行分隔" aria-label="" />
<p v-else-if="question.type === 'LIKERT'" class="likert-tip">量表固定为非常不重要较不重要一般较重要非常重要</p>
</div>
<button type="button" class="questionnaire-question-card__remove" :disabled="form.questionnaireQuestions.length <= 4" @click="removeQuestionnaireQuestion(index)"></button>
</article>
</div>
</el-dialog>
<el-dialog v-model="showQuestionnairePreview" title="导入问卷预览" width="min(900px, 94vw)" class="questionnaire-preview-dialog" append-to-body>
<div v-if="form.questionnaireImport?.previewContent" class="questionnaire-word-preview">{{ form.questionnaireImport.previewContent }}</div>
<p v-else class="questionnaire-preview-empty">暂未解析到可预览内容</p>
</el-dialog>
</section> </section>
<section v-else class="step-panel"> <section v-else class="step-panel">
@ -201,10 +244,11 @@ import TrainingAiSidebar from "@/views/components/TrainingAiSidebar.vue";
import TrainingMaterialButton from "@/views/components/TrainingMaterialButton.vue"; import TrainingMaterialButton from "@/views/components/TrainingMaterialButton.vue";
import TrainingTaskBrief from "@/views/components/TrainingTaskBrief.vue"; import TrainingTaskBrief from "@/views/components/TrainingTaskBrief.vue";
import { getTrainingTaskByKey } from "@/api/trainingTask"; 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 TASK_KEY = "consumer-behavior";
const KANO_NEED_LIMIT = 20; const KANO_NEED_LIMIT = 20;
let questionnaireQuestionSequence = 0;
const { proxy } = getCurrentInstance(); const { proxy } = getCurrentInstance();
const defaultRequirement = "消费者需求与行为分析用于识别目标用户的购买动机、决策因素、渠道偏好和使用场景,为智能产品功能设计与市场切入提供依据。"; const defaultRequirement = "消费者需求与行为分析用于识别目标用户的购买动机、决策因素、渠道偏好和使用场景,为智能产品功能设计与市场切入提供依据。";
@ -389,9 +433,13 @@ const ReportFields = defineComponent({
const taskConfig = ref(null); const taskConfig = ref(null);
const saving = ref(false); const saving = ref(false);
const uploading = ref(false); const uploading = ref(false);
const questionnaireImporting = ref(false);
const fileInputRef = ref(null); const fileInputRef = ref(null);
const questionnaireImportInputRef = ref(null);
const questionnaireEditorRef = ref(null); const questionnaireEditorRef = ref(null);
const uploadContext = ref({ type: "", key: "" }); const uploadContext = ref({ type: "", key: "" });
const questionnaireEditorVisible = ref(false);
const showQuestionnairePreview = ref(false);
const uploadAccept = computed(() => { const uploadAccept = computed(() => {
if (uploadContext.value.type === "questionnaire") return ".pdf"; if (uploadContext.value.type === "questionnaire") return ".pdf";
@ -405,6 +453,8 @@ function createForm() {
activeStep: 0, activeStep: 0,
targetHypotheses: createTargetHypotheses(), targetHypotheses: createTargetHypotheses(),
kanoNeeds: createKanoNeeds(), kanoNeeds: createKanoNeeds(),
questionnaireQuestions: createQuestionnaireQuestions(),
questionnaireImport: null,
questionnaireText: questionnaireTemplate, questionnaireText: questionnaireTemplate,
questionnaireFileName: "", questionnaireFileName: "",
questionnaireFileUrl: "", questionnaireFileUrl: "",
@ -428,6 +478,18 @@ function createEmptyTargetHypothesis() {
} }
const kanoTypeOptions = ["基本型", "期望型", "兴奋型", "无差异型", "反向型"]; 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() { function createKanoNeeds() {
return Array.from({ length: 3 }, createEmptyKanoNeed); return Array.from({ length: 3 }, createEmptyKanoNeed);
@ -437,6 +499,20 @@ function createEmptyKanoNeed() {
return { feature: "", kanoType: "", featureDirection: "" }; 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 form = ref(createForm());
const taskTitle = computed(() => taskConfig.value?.taskName || "消费者需求与行为"); 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 activeStepName = computed(() => flowSteps.value[form.value.activeStep] || "");
const progressItems = computed(() => [ 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: 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: Object.keys(form.value.chartFiles).length ? "进行中:数据可视化截图" : "待完成:数据可视化截图", status: Object.keys(form.value.chartFiles).length ? "done" : "" },
{ text: hasReportContent() ? "进行中:分析报告" : "待完成:分析报告", status: hasReportContent() ? "done" : "" }, { text: hasReportContent() ? "进行中:分析报告" : "待完成:分析报告", status: hasReportContent() ? "done" : "" },
@ -507,6 +583,8 @@ function applySavedAnswer(value) {
questionnaireText: normalizeQuestionnaireContent(parsed.questionnaireText || questionnaireTemplate), questionnaireText: normalizeQuestionnaireContent(parsed.questionnaireText || questionnaireTemplate),
targetHypotheses: normalizeTargetHypotheses(parsed.targetHypotheses), targetHypotheses: normalizeTargetHypotheses(parsed.targetHypotheses),
kanoNeeds: normalizeKanoNeeds(parsed.kanoNeeds), kanoNeeds: normalizeKanoNeeds(parsed.kanoNeeds),
questionnaireQuestions: normalizeQuestionnaireQuestions(parsed.questionnaireQuestions),
questionnaireImport: normalizeQuestionnaireImport(parsed.questionnaireImport),
chartFiles: parsed.chartFiles || {}, chartFiles: parsed.chartFiles || {},
report: normalizeReport(parsed.report), 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 = {}) { function normalizeReport(value = {}) {
return Object.keys(emptyReport).reduce((report, key) => { return Object.keys(emptyReport).reduce((report, key) => {
const currentValue = value?.[key] || ""; const currentValue = value?.[key] || "";
@ -617,6 +715,29 @@ function removeKanoNeed() {
form.value.kanoNeeds.pop(); 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() { function viewTargetUserProfile() {
window.open("/demand/people", "_blank", "noopener"); 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() { function goPrev() {
if (form.value.activeStep > 0) setActiveStep(form.value.activeStep - 1); if (form.value.activeStep > 0) setActiveStep(form.value.activeStep - 1);
} }
async function goNext() { 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); 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()); 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) { function buildAnswerPayload(status) {
syncQuestionnaireText(); syncQuestionnaireText();
const saveAction = status === "SUBMITTED" ? "SUBMIT" : status === "RESET" ? "RESET" : "SAVE"; 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")) { if (status !== "RESET" && (form.value.activeStep === 1 || status === "SUBMITTED")) {
await checkConsumerBehaviorStepTwo({ needs: form.value.kanoNeeds }); 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)); await saveStudentTrainingAnswer(TASK_KEY, buildAnswerPayload(status));
proxy?.$modal?.msgSuccess(status === "SUBMITTED" ? "提交成功" : "保存成功"); proxy?.$modal?.msgSuccess(status === "SUBMITTED" ? "提交成功" : "保存成功");
} finally { } finally {
@ -724,6 +884,10 @@ function exportOutcome() {
"KANO 用户需求分类:", "KANO 用户需求分类:",
...form.value.kanoNeeds.map((item, index) => `${index + 1}. 功能:${item.feature || ""}\nKANO类型${item.kanoType || ""}\n功能方向${item.featureDirection || ""}`), ...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(), questionnaireTextForExport(),
"", "",
@ -1324,6 +1488,229 @@ function exportOutcome() {
color: #8da5b2; 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 { .questionnaire-desc {
flex: 1; flex: 1;
margin: 0; margin: 0;
@ -1739,6 +2126,35 @@ function exportOutcome() {
flex-direction: column; 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 { .step-indicator {
align-items: stretch; align-items: stretch;
flex-direction: column; flex-direction: column;

Loading…
Cancel
Save