| | |
| | | <template> |
| | | <div class="questionnaire-optimized"> |
| | | <div v-if="loading" class="loading-container"> |
| | | <div v-if="loading && !accomplish" class="loading-container"> |
| | | <div class="loading-content"> |
| | | <i class="el-icon-loading loading-icon"></i> |
| | | <div class="loading-text">问卷加载中,请稍候...</div> |
| | |
| | | |
| | | <el-divider class="custom-divider"></el-divider> |
| | | |
| | | <!-- 问卷题目区域 --> |
| | | <div class="questions-section"> |
| | | <!-- 按维度分组的题目区域 --> |
| | | <div class="dimension-section" v-if="hasDimension"> |
| | | <!-- 遍历维度分组 --> |
| | | <div |
| | | class="dimension-group" |
| | | v-for="(group, dimensionKey) in dimensionGroups" |
| | | :key="dimensionKey" |
| | | > |
| | | <!-- 维度标题 - 只显示有维度标签的 --> |
| | | <div class="dimension-title" v-if="group.dimensionLabel"> |
| | | <h3>{{ group.dimensionLabel }}</h3> |
| | | </div> |
| | | |
| | | <!-- 该维度下的题目 --> |
| | | <div class="questions-section"> |
| | | <div |
| | | class="question-item" |
| | | v-for="(item, index) in group.questions" |
| | | :key="item.id" |
| | | :class="{ |
| | | 'has-warning': |
| | | item.prompt && |
| | | item.scriptResult && |
| | | (item.scriptType !== 2 || item.scriptResult.length > 0), |
| | | }" |
| | | > |
| | | <!-- 题目题干 --> |
| | | <div class="question-stem"> |
| | | <span class="question-number" |
| | | >{{ getQuestionNumber(group, index) }}.</span |
| | | > |
| | | <span class="question-text">{{ item.scriptContent }}</span> |
| | | <span class="question-type-tag"> |
| | | {{ |
| | | item.scriptType == 1 |
| | | ? "[单选]" |
| | | : item.scriptType == 2 |
| | | ? "[多选]" |
| | | : "[问答]" |
| | | }} |
| | | </span> |
| | | </div> |
| | | |
| | | <!-- 单选题目 --> |
| | | <div |
| | | class="question-options" |
| | | v-if="item.scriptType == 1 && !item.ishide" |
| | | > |
| | | <el-radio-group |
| | | class="options-group" |
| | | v-model="item.scriptResult" |
| | | > |
| | | <el-radio |
| | | v-for="( |
| | | option, optionIndex |
| | | ) in item.svyTaskTemplateTargetoptions" |
| | | :key="optionIndex" |
| | | :label="option.optioncontent" |
| | | :class="{ |
| | | 'abnormal-option': |
| | | option.isabnormal && |
| | | item.scriptResult == option.optioncontent, |
| | | }" |
| | | @click.native.prevent=" |
| | | handleRadioToggle( |
| | | item, |
| | | getGlobalIndex(dimension, group, index), |
| | | item.svyTaskTemplateTargetoptions, |
| | | option.optioncontent |
| | | ) |
| | | " |
| | | class="option-radio" |
| | | > |
| | | <span class="option-text">{{ |
| | | option.optioncontent |
| | | }}</span> |
| | | </el-radio> |
| | | </el-radio-group> |
| | | </div> |
| | | |
| | | <!-- 多选题目 --> |
| | | <div class="question-options" v-if="item.scriptType == 2"> |
| | | <el-checkbox-group |
| | | class="options-group" |
| | | v-model="item.scriptResult" |
| | | > |
| | | <el-checkbox |
| | | v-for="( |
| | | option, optionIndex |
| | | ) in item.svyTaskTemplateTargetoptions" |
| | | :key="optionIndex" |
| | | :label="option.optioncontent" |
| | | :class="{ |
| | | 'abnormal-option': option.isabnormal, |
| | | }" |
| | | @change="$forceUpdate()" |
| | | class="option-checkbox" |
| | | > |
| | | <span class="option-text">{{ |
| | | option.optioncontent |
| | | }}</span> |
| | | </el-checkbox> |
| | | </el-checkbox-group> |
| | | </div> |
| | | |
| | | <!-- 填空题目 --> |
| | | <div class="question-input" v-if="item.scriptType == 4"> |
| | | <el-input |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请输入您的回答" |
| | | v-model="item.scriptResult" |
| | | clearable |
| | | class="answer-textarea" |
| | | ></el-input> |
| | | </div> |
| | | |
| | | <!-- 提示信息 --> |
| | | <div |
| | | class="question-warning" |
| | | v-show=" |
| | | item.prompt && |
| | | item.scriptResult && |
| | | (item.scriptType !== 2 || item.scriptResult.length > 0) |
| | | " |
| | | > |
| | | <el-alert |
| | | :title="item.prompt" |
| | | type="warning" |
| | | :closable="false" |
| | | class="warning-alert" |
| | | ></el-alert> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 无维度时的题目区域(保持原样) --> |
| | | <div class="questions-section" v-else> |
| | | <div |
| | | class="question-item" |
| | | v-for="(item, index) in visibleQuestions" |
| | |
| | | <span class="question-text">{{ item.scriptContent }}</span> |
| | | <span class="question-type-tag"> |
| | | {{ |
| | | item.scriptType === 1 |
| | | item.scriptType == 1 |
| | | ? "[单选]" |
| | | : item.scriptType === 2 |
| | | : item.scriptType == 2 |
| | | ? "[多选]" |
| | | : "[问答]" |
| | | }} |
| | |
| | | getExternalfollowup, |
| | | getCachequestionnaire, |
| | | Cachequestionnaire, |
| | | gettypeout, |
| | | Submitaquestionnaire, |
| | | geturlinfo, |
| | | } from "@/api/AiCentre/index"; |
| | |
| | | dialogVisible: false, |
| | | Endornot: true, |
| | | accomplish: false, |
| | | dimensionTypes: [ |
| | | { |
| | | value: 1, |
| | | label: "维度1", |
| | | listClass: "primary", |
| | | }, |
| | | { |
| | | value: 2, |
| | | label: "维度二", |
| | | listClass: "primary", |
| | | }, |
| | | { |
| | | value: 3, |
| | | label: "维度三", |
| | | listClass: "primary", |
| | | }, |
| | | ], |
| | | // 前端公钥 |
| | | publicKey: |
| | | "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKR0yHv0rbJWQE+Sc7/FwpW66qMd9qX2k6z+SDgkSdxWh/1GbBoAP7bDQQRF6vXmoKsD2ya42H6XRLSDXAoayuMCAwEAAQ== ", |
| | |
| | | // window.removeEventListener("beforeunload", this.cache); |
| | | }, |
| | | created() { |
| | | this.gettypeout(); |
| | | this.geturlinfo(); |
| | | }, |
| | | computed: { |
| | |
| | | return []; |
| | | } |
| | | return this.questionList.filter((question) => !question.ishide); |
| | | }, |
| | | |
| | | // 计算属性:检查是否有维度字段 |
| | | hasDimension() { |
| | | if (!this.questionList || this.questionList.length == 0) { |
| | | return false; |
| | | } |
| | | // 检查任意一个题目是否有dimension字段 |
| | | return this.questionList.some( |
| | | (question) => |
| | | question.dimension && this.getDimensionLabel(question.dimension) |
| | | ); |
| | | }, |
| | | |
| | | // 计算属性:按维度整理题目 |
| | | dimensionGroups() { |
| | | if (!this.questionList || this.questionList.length == 0) { |
| | | return {}; |
| | | } |
| | | |
| | | const groups = {}; |
| | | let currentGroupKey = null; |
| | | let groupStartIndex = 0; // 记录当前维度组的开始索引 |
| | | |
| | | this.visibleQuestions.forEach((question, index) => { |
| | | const dimensionValue = question.dimension; |
| | | const dimensionLabel = this.getDimensionLabel(dimensionValue); |
| | | |
| | | // 生成组key:有维度用维度值,无维度用特殊标记+起始索引 |
| | | let groupKey; |
| | | if (dimensionValue && dimensionLabel) { |
| | | groupKey = `dimension-${dimensionValue}`; |
| | | } else { |
| | | // 无维度或维度映射不存在的题目单独分组 |
| | | groupKey = `no-dimension-${groupStartIndex}`; |
| | | } |
| | | |
| | | // 创建或获取维度组 |
| | | if (!groups[groupKey]) { |
| | | groups[groupKey] = { |
| | | questions: [], |
| | | startIndex: groupStartIndex, |
| | | dimensionValue: dimensionValue, // 保存维度值用于后续处理 |
| | | dimensionLabel: dimensionLabel, // 保存维度标签 |
| | | isNoDimension: !dimensionValue || !dimensionLabel, |
| | | }; |
| | | currentGroupKey = groupKey; |
| | | } |
| | | |
| | | // 将题目添加到对应的维度组 |
| | | groups[groupKey].questions.push(question); |
| | | |
| | | // 如果下一个题目维度不同,重新开始计数 |
| | | const nextQuestion = this.visibleQuestions[index + 1]; |
| | | if (nextQuestion) { |
| | | const nextDimensionValue = nextQuestion.dimension; |
| | | const nextDimensionLabel = this.getDimensionLabel(nextDimensionValue); |
| | | const currentDimensionLabel = this.getDimensionLabel(dimensionValue); |
| | | |
| | | // 判断维度是否相同 |
| | | if ( |
| | | dimensionValue !== nextDimensionValue || |
| | | (dimensionValue && |
| | | nextDimensionValue && |
| | | ((!dimensionValue && nextDimensionValue) || |
| | | (dimensionValue && !nextDimensionValue))) |
| | | ) { |
| | | groupStartIndex = index + 1; |
| | | } |
| | | } |
| | | }); |
| | | |
| | | return groups; |
| | | }, |
| | | }, |
| | | methods: { |
| | |
| | | this.param6 = res.data.param6; |
| | | } |
| | | }); |
| | | }, |
| | | // 获取字典 |
| | | gettypeout() { |
| | | gettypeout("dimensionality_type").then((res) => { |
| | | if (res.code == 200) { |
| | | this.dimensionTypes = res.data; |
| | | } |
| | | }); |
| | | }, |
| | | // 根据维度值获取维度标签 |
| | | getDimensionLabel(dimensionValue) { |
| | | if (!dimensionValue) return ""; |
| | | |
| | | const dimensionType = this.dimensionTypes.find( |
| | | (item) => item.dictValue == Number(dimensionValue) |
| | | ); |
| | | return dimensionType ? dimensionType.dictLabel : ""; |
| | | }, |
| | | // extractLastSegmentFromUrl(url) { |
| | | // // 找到最后一个'/'的位置 |
| | |
| | | getVisibleQuestionIndex(index) { |
| | | return index + 1; |
| | | }, |
| | | |
| | | // 获取维度内题目的序号 |
| | | getQuestionNumber(group, index) { |
| | | return group.startIndex + index + 1; |
| | | }, |
| | | |
| | | // 获取题目在全量数组中的索引 |
| | | getGlobalIndex(dimension, group, localIndex) { |
| | | return group.startIndex + localIndex; |
| | | }, |
| | | // 在模板中使用 |
| | | getDimensionLabelForDisplay(dimensionKey) { |
| | | if (dimensionKey.startsWith("no-dimension-")) { |
| | | return ""; |
| | | } |
| | | const dimensionValue = dimensionKey.replace("dimension-", ""); |
| | | return this.getDimensionLabel(dimensionValue); |
| | | }, |
| | | // 新增的切换选中/取消选中方法 |
| | | handleRadioToggle(questionItem, index, options, optionValue) { |
| | | handleRadioToggle(questionItem, globalIndex, options, optionValue) { |
| | | // 保存当前状态以便后续比较 |
| | | const previousState = JSON.parse(JSON.stringify(this.questionList)); |
| | | |
| | | // 原有的处理逻辑 |
| | | if (questionItem.scriptResult === optionValue) { |
| | | if (questionItem.scriptResult == optionValue) { |
| | | questionItem.scriptResult = ""; |
| | | questionItem.isabnormal = 0; |
| | | questionItem.showAppendInput = false; |
| | | } else { |
| | | questionItem.scriptResult = optionValue; |
| | | this.handleOptionChange(optionValue, index, options, questionItem); |
| | | this.handleOptionChange( |
| | | optionValue, |
| | | globalIndex, |
| | | options, |
| | | questionItem |
| | | ); |
| | | } |
| | | |
| | | // 处理完成后,确保重新计算可见题目的序号 |
| | |
| | | } |
| | | |
| | | // 当前题目总是可见 |
| | | if (index === questionIndex) { |
| | | if (index == questionIndex) { |
| | | return { ...item, ishide: 0, hiddenByEnd: false }; |
| | | } |
| | | |
| | | // 显示目标下一题 |
| | | if (index === nextQuestionIndex) { |
| | | if (index == nextQuestionIndex) { |
| | | return { ...item, ishide: 0, hiddenByEnd: false }; |
| | | } |
| | | |
| | |
| | | // 如果没有跳转,只需确保下一题可见 |
| | | this.questionList = this.questionList.map((item, index) => ({ |
| | | ...item, |
| | | ishide: index === questionIndex + 1 ? 0 : item.ishide, |
| | | hiddenByEnd: index === questionIndex + 1 ? false : item.hiddenByEnd, |
| | | ishide: index == questionIndex + 1 ? 0 : item.ishide, |
| | | hiddenByEnd: index == questionIndex + 1 ? false : item.hiddenByEnd, |
| | | })); |
| | | } |
| | | |
| | |
| | | this.$forceUpdate(); |
| | | }); |
| | | }, |
| | | // 处理单选选项 |
| | | // handleOptionChange(selectedvalue, index, arr) { |
| | | // // 查找选中的选项对象 |
| | | // const selectedOption = arr.svyTaskTemplateTargetoptions.find( |
| | | // (option) => option.optioncontent == selectedvalue |
| | | // ); |
| | | // if (selectedOption) { |
| | | // this.questionList[index].nextScriptno = selectedOption.nextQuestion; |
| | | // this.questionList[index].score = selectedOption.score; |
| | | // this.questionList[index].prompt = selectedOption.prompt; |
| | | // } |
| | | // }, |
| | | // 处理多选选项 |
| | | // updateScore(selectedvalues, index, arr) { |
| | | // // ��加分数 |
| | | // let score = 0; |
| | | // selectedvalues.forEach((value) => { |
| | | // const selectedOption = arr.svyTaskTemplateTargetoptions.find( |
| | | // (option) => option.optioncontent == value |
| | | // ); |
| | | // if (selectedOption) { |
| | | // score += Number(selectedOption.score); |
| | | // } |
| | | // }); |
| | | // this.questionList[index].score = score; |
| | | // }, |
| | | }, |
| | | }; |
| | | </script> |
| | |
| | | background-color: #eaeef2; |
| | | } |
| | | |
| | | /* 维度分组样式 */ |
| | | .dimension-section { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .dimension-group { |
| | | margin-bottom: 30px; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | } |
| | | |
| | | .dimension-title { |
| | | background: linear-gradient(135deg, #e8f4ff 0%, #d1e7ff 100%); |
| | | padding: 12px 20px; |
| | | border-radius: 8px; |
| | | margin-bottom: 20px; |
| | | border-left: 4px solid #175997; |
| | | |
| | | h3 { |
| | | color: #175997; |
| | | font-size: 20px; |
| | | font-weight: 600; |
| | | margin: 0; |
| | | } |
| | | } |
| | | |
| | | .questions-section { |
| | | margin-bottom: 40px; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .question-item { |
| | | margin-bottom: 35px; |
| | | margin-bottom: 25px; |
| | | padding: 20px; |
| | | border-radius: 8px; |
| | | border: 1px solid #eaeef2; |
| | |
| | | |
| | | &.has-warning { |
| | | border-left: 4px solid #e6a23c; |
| | | } |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | } |
| | | |
| | |
| | | .submit-section { |
| | | text-align: center; |
| | | padding: 20px 0 10px; |
| | | border-top: 1px solid #eaeef2; |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .submit-button { |
| | |
| | | padding: 15px; |
| | | } |
| | | |
| | | .dimension-title { |
| | | padding: 10px 15px; |
| | | |
| | | h3 { |
| | | font-size: 18px; |
| | | } |
| | | } |
| | | |
| | | .completion-content { |
| | | padding: 30px 20px; |
| | | } |
| | |
| | | .completion-icon { |
| | | font-size: 60px; |
| | | } |
| | | |
| | | .dimension-title h3 { |
| | | font-size: 16px; |
| | | } |
| | | } |
| | | .loading-container { |
| | | display: flex; |