(
好好好,csdn没有这种调查问卷是吧!那就谁都别写了!!)
全文可复制。
这里的数据结构多少有点不正常,很少遇到,但是可以根据你们的需求改进。
先看下结构,因为需要很多层嵌套,这里拿出第一道和第二道大题的数据。(可直接使用,创建一个data.js文件放进去就OK)
重要字段:
answer:这个字段是答完提交后,回显的用户答案
uniqueId:这个一直指向老祖宗的id,后面用它来划分是否属于同一道大题。
necessaryWriteNum:用于遍历填空题的个数。
type:1代表单选,2代表多选,3代表填空,4代表简答。
export const data={
"status": 200,
"message": "",
"data": {
"title": "天天情况调查表",
"time":"2024年6月",
"optionList": [
{
"id": 450,
"answer":null,
"necessaryWriteNum":null,
"parentId": 0,
"type": 1,
"uniqueId":450,
"subjectTitle": "1系统建设进展",
"optionList": [
{
"optionKey": "A",
"optionContent": "未建设且暂无立项计划",
"optionList": [],
"uniqueId":450
},
{
"optionKey": "B",
"optionContent": "已建成————建成时间:__年__月__日",
"optionList": [
{
"id": 451,
"answer":null,
"necessaryWriteNum":3,
"type":3,
"parentId": 450,
"optionList":[],
"subjectCondition":"B",
"subjectTitle":"系统建设进展-已建成-建成时间:_①_年_②_月_③_日",
"uniqueId":450,
}
],
"uniqueId":450
},
{
"optionKey": "C",
"optionContent": "计划立项",
"optionList": [
{
"id": 452,
"answer":null,
"necessaryWriteNum":1,
"optionList":[],
"parentId": 450,
"subjectCondition":"C",
"subjectTitle":"系统建设进展-计划立项-计划年度:_①_年",
"type":3,
"uniqueId":450,
}
],
"uniqueId":450,
},
{
"optionKey": "D",
"optionContent": "正在立项",
"optionList": [
{
"id": 453,
"answer":null,
"necessaryWriteNum":null,
"optionList":[
{
"optionKey": "A",
"optionContent": "正在编制立项文件",
"optionList": [],
"uniqueId":450,
},
{
"optionKey": "B",
"optionContent": "本单位已批准立项",
"optionList": [],
"uniqueId":450,
},
],
"parentId": 450,
"subjectCondition":"D",
"subjectTitle":"系统建设进展-正在立项-立项阶段",
"type":1,
"uniqueId":450,
}
],
"uniqueId":450,
},
{
"optionKey": "E",
"optionContent": "各评审机关已批准",
"optionList": [],
"uniqueId":450,
},
{
"optionKey": "F",
"optionContent": "正在开展采购工作",
"optionList": [
{
"id": 454,
"answer":null,
"necessaryWriteNum":1,
"optionList":[],
"parentId": 450,
"subjectCondition":"F",
"subjectTitle":"系统建设进展-正在开展采购工作-计划建成年度:_①_年",
"type":3,
"uniqueId":450,
}
],
"uniqueId":450,
},
{
"optionKey": "G",
"optionContent": "立项未通过",
"optionList": [
{
"id": 455,
"answer":null,
"necessaryWriteNum":2,
"optionList":[],
"parentId": 450,
"subjectCondition":"G",
"subjectTitle":"系统建设进展-立项未通过:未通过机关_①_;未通过原因_②_;",
"type":3,
"uniqueId":450,
}
],
"uniqueId":450,
},
]
},
{
"id": 456,
"answer":null,
"necessaryWriteNum":null,
"parentId": 0,
"type": 1,
"uniqueId":456,
"subjectCondition":null,
"subjectTitle": "2系统建设类型",
"optionList": [
{
"optionKey": "A",
"optionContent": "未建设且暂无立项计划",
"optionList": [],
"uniqueId":456
},
{
"optionKey": "B",
"optionContent": "单机系统",
"optionList": [],
"uniqueId":456
},
{
"optionKey": "C",
"optionContent": "档案管理一体机",
"optionList": [
{
"id": 457,
"answer":null,
"necessaryWriteNum":null,
"parentId": 456,
"type": 1,
"uniqueId":456,
"subjectCondition":"C",
"subjectTitle": "系统建设类型-档案管理一体机",
"optionList": [
{
"optionKey": "A",
"optionContent": "单机版(无网络连接)",
"optionList": [],
"uniqueId":456
},
{
"optionKey": "B",
"optionContent": "接入网络",
"optionList": [],
"uniqueId":456
}
]
}
]
},
{
"optionKey": "D",
"optionContent": "网络版系统",
"uniqueId":456,
"optionList": [
{
"id": 459,
"answer":null,
"necessaryWriteNum":null,
"parentId": 456,
"type": 2,
"uniqueId":456,
"subjectCondition":"D",
"subjectTitle": "系统建设类型-网络版系统-部署网络",
"optionList": [
{
"optionKey": "A",
"optionContent": "政务内网",
"optionList":[],
"uniqueId":456,
},
{
"optionKey": "B",
"optionContent": "政务外网",
"optionList":[],
"uniqueId":456,
},
{
"optionKey": "C",
"optionContent": "办公专网",
"optionList": [],
"uniqueId":456,
},
{
"optionKey": "D",
"optionContent": "因特网",
"optionList":[],
"uniqueId":456,
}
]
},
{
"id": 460,
"answer":null,
"necessaryWriteNum":"1",
"parentId": 456,
"type": 3,
"uniqueId":456,
"subjectCondition":"D",
"subjectTitle": "系统建设类型-网络版系统-主运行网络(多网络部署填写):_①_",
"optionList":[]
},
{
"id": 461,
"answer":null,
"necessaryWriteNum":null,
"parentId": 456,
"type": 2,
"uniqueId":456,
"subjectCondition":"D",
"subjectTitle": "系统建设类型-网络版系统-安全测评(多选)",
"optionList":[
{
"optionKey": "A",
"optionContent": "分保",
"optionList":[],
"uniqueId":456,
},
{
"optionKey": "B",
"optionContent": "等保",
"optionList":[
{
"id": 462,
"answer":null,
"necessaryWriteNum":"1",
"parentId": 461,
"type": 2,
"uniqueId":456,
"subjectCondition":"B",
"subjectTitle": "系统建设类型-网络版系统-安全测评-等保_①_级",
"optionList":[],
}
],
"uniqueId":456,
},
]
},
{
"id": 463,
"answer":null,
"necessaryWriteNum":null,
"parentId": 456,
"type": 1,
"uniqueId":456,
"subjectCondition":"D",
"subjectTitle": "系统建设类型-网络版系统-第三方软件测试",
"optionList":[
{
"optionKey": "A",
"optionContent": "是",
"optionList":[],
"uniqueId":456,
},
{
"optionKey": "B",
"optionContent": "否",
"optionList":[],
"uniqueId":456,
},
]
}
],
},
]
}
],
"total": 0,
"name": null,
"flag": null
}
}
1.创建两个文件,一个父,一个子,先是父文件father.vue。(回显内容,需要和后端协商)
<template> <div> <div @click="getData()">若已提交问卷记录,可点我回显上次填写记录,继续答</div> <RadioGroup :List="List" v-if="show"></RadioGroup> <el-button @click="importData">提交问卷</el-button> </div> </template> <script> import {data} from './data' import RadioGroup from './QesComponent/radioGroup.vue' export default { name: 'QuestionNaire', data() { return { List: {}, show: false, } }, components: { RadioGroup }, methods: { //请求问卷数据 getData(){ this.List=data.data this.show = true }, //提交问卷数据 importData() { //把存在vuex里的答案取出来弄成后端需要的格式 let answer = this.$store.state.examAnswer //这里后端要求,[{},{}]这种的格式,所以进行了修改(到这里你们都用不到了。) //原本的传给后端数据格式是[[{},{},{}],[{},{},{}]], //一整个大题是大数组内的一个小数组,里面的对象是所选择的答案。 let answerList = [] answer.forEach(element => { element.forEach(item => { answerList.push(item) }) }) // 然后请求传参,提交即可 } }, created() { this.getData() }, } </script>
2.子组件,RadioGroup.vue, 利用递归的思想去写这个结构会很方便。(每个逻辑都有注释,看自己是否需要,不需要就跳过)
<template> <div> <div class="unit" v-for="(single,index) in List.optionList" :key="single.id"> <div class="unit-title" v-if="single.hasOwnProperty('subjectTitle')">{{single.subjectTitle}}</div> <ul class="unit-select" v-if="single.type==1"> <li class="select-item"> <el-radio-group v-model="userAnswer.singleList[single.id]" @input="singleChange(single.id,single.uniqueId,single.optionKey,single.answer)"> <div class="radioContent" v-for="option in single.optionList" :key="option.id"> <el-radio class="radioContent-item" :label="option.optionKey"> <span class="radioContent-text">{{option.optionKey}}.{{ option.optionContent }}</span> </el-radio> <div class="child-content" v-show="option.hasOwnProperty('optionList') && option.optionList.length > 0 && optionKeyList.includes(single.id+option.optionKey)" > <RadioGroup :id="index" class="RadioGroup-child" :List="option" @childMessage="handleChildMessage"/> </div> </div> </el-radio-group> </li> </ul> <ul class="unit-select" v-if="single.type==2"> <li class="select-item"> <el-checkbox-group v-model="userAnswer.mutiList[index]" @change="mutiListChange(single.id,single.uniqueId,single.optionKey,index)"> <div class="radioContent" v-for="option in single.optionList" :key="option.id"> <el-checkbox class="radioContent-item" :label="option.optionKey"> <span class="radioContent-text">{{option.optionKey}}.{{ option.optionContent }}</span> </el-checkbox> <div class="child-content" v-show="option.hasOwnProperty('optionList') && option.optionList.length > 0 && userAnswer.mutiList[index].includes(option.optionKey)" > <RadioGroup class="RadioGroup-child" :List="option" /> </div> </div> </el-checkbox-group> </li> </ul> <ul class="unit-select" v-show="single.type==3"> <li class="select-item" v-for="(item,index) in single.necessaryWriteNum-0" :key="item"> ({{index+1}}) <el-input class="inputContent" @input="inputFake(single.id,single.uniqueId,single.optionKey)" autosize type="text" v-model="userAnswer.input[index]" placeholder="请输入内容"></el-input> </li> </ul> <ul class="unit-select" v-show="single.type==4"> <li class="select-item"> <el-input class="inputTextarea" @input="inputTextarea(single.id,single.uniqueId,single.optionKey)" :autosize="{ minRows: 4}" type="textarea" v-model="userAnswer.textarea" placeholder="请输入内容"></el-input> </li> </ul> </div> </div> </template> <script> import {getTreeIds,getParentNodes,dfs} from '../data'; export default { name:'', data() { return { userAnswer:{ singleList:{}, mutiList:[[],[],[],[],[],[],[],[],[],[]], input:[], textarea:'' }, selectValue:[], selectList:[], optionKeyList:[], newList:[], answerAll:[], } }, components:{ //一定要这样引用自己才会递归。 RadioGroup:()=>import('./radioGroup.vue') }, props:['List'], methods: { //单选,这地方留下了一小部分上一套代码的内容,但是有用,没有整合,所以有些乱。 singleChange(id,uniqueId,optionKey,ownAnswer){ //判断是否selectList是否有选择题,有就对比替换,没有就push(遗留内容) if(this.selectList.length>0){ for(let [index,item] of this.selectList.entries()){ let key='' for (let value in item) { key=value if(key==id){ this.selectList.splice(index,1,this.userAnswer.singleList) } } } }else{ this.selectList.push(this.userAnswer.singleList) } //要传给后端的单选,newList有数据就比较替换,没有就push if(this.newList.length>0){ //如果数据结构内有optionKey,直接用它进行替换 if(optionKey){ let obj={ 'subjectId':id, 'answer':optionKey, 'parentId':uniqueId, } //遍历id相同则替换 this.newList.forEach((item,index)=>{ if(item.subjectId==id){ this.newList.splice(index,1,obj) } }) return }else{ //如果没有optionKey,就在userAnswer.singleList内取出来 let obj={} for (let value in this.userAnswer.singleList) { if(value==id){ let answer=this.userAnswer.singleList[value] obj={ 'subjectId':id, 'answer':answer, 'parentId':uniqueId, } } } this.newList.forEach((item,index)=>{ if(item.subjectId==id){ this.newList.splice(index,1,obj) } }) } }else{ for(let key in this.userAnswer.singleList){ if(key==id){ let obj={ 'subjectId':id, 'answer':this.userAnswer.singleList[key], 'parentId':uniqueId, } this.newList.push(obj) } } } //将选择的答案和id存在optionKeyList,这样你选择题目就不会出现点一个都显示的问题(遗留内容) if(this.selectList.length>0){ for(let [index,item] of this.selectList.entries()){ let key='' for (let value in item) { key=value let option=value+item[key] if(this.optionKeyList.length>0){ let keysList=[] this.optionKeyList.forEach(item=>{ keysList.push(item.slice(0,-1)) }) if(keysList.includes(value)){ let index= this.optionKeyList.findIndex(i=>i.slice(0,-1)==value) this.optionKeyList.splice(index,1,option) }else{ this.optionKeyList.push(option) } }else{ this.optionKeyList.push(option) } } } } //当前用户选择的题目答案,传给vuex用的。 let userSingle={} for(let key in this.userAnswer.singleList){ if(key==id){ userSingle={ 'subjectId':id, 'answer':this.userAnswer.singleList[key], 'parentId':uniqueId, } } } //当前用户的所有题目内容,传给vuex用的。 let newListS=this.newList //如果id与当前uniqueId相同,则证明是大题进行了修改,那么整个题目就要替换,若不相同,则证明修改了当前题目中的某个小题 if(id==uniqueId){ this.$store.commit("addOldAnswer", {id,newListS,uniqueId,userSingle}) }else{ this.$store.commit("addAnswer", {id,newListS,uniqueId,userSingle}) } //传递给第二层的当前组件,也就是递归组件 this.$emit('childMessage',userSingle) }, //多选 mutiListChange(id,uniqueId,optionKey,index){ let userSingle={ 'subjectId':id, 'answer':this.userAnswer.mutiList[index].join(','), 'parentId':uniqueId, } let newListS=this.newList if(id==uniqueId){ this.$store.commit("addOldAnswer", {id,newListS,uniqueId,userSingle}) }else{ console.log(this.userAnswer.mutiList) this.$store.commit("addAnswer", {id,newListS,uniqueId,userSingle}) } }, //填空 inputFake(id,uniqueId,optionKey){ let userSingle={ 'subjectId':id, 'answer':this.userAnswer.input.join(","), 'parentId':uniqueId, } let newListS=this.newList if(id==uniqueId){ this.$store.commit("addOldAnswer", {id,newListS,uniqueId,userSingle}) }else{ console.log(userSingle) this.$store.commit("addAnswer", {id,newListS,uniqueId,userSingle}) } }, //简答 inputTextarea(id,uniqueId,optionKey){ let userSingle={ 'subjectId':id, 'answer':this.userAnswer.textarea, 'parentId':uniqueId, } let newListS=this.newList if(id==uniqueId){ this.$store.commit("addOldAnswer", {id,newListS,uniqueId,userSingle}) }else{ this.$store.commit("addAnswer", {id,newListS,uniqueId,userSingle}) } }, //递归的组件传给上一层组件的函数 handleChildMessage(msg){ //将递归中的单选传递到上一层组件,如果id相同则替换 let ids=[] this.newList.forEach((item,index)=>{ ids.push(item.subjectId) if(msg.subjectId==item.subjectId){ this.newList.splice(index,1,msg) } }) //如果不包含这个id就存进newList里 if(ids.includes(msg.subjectId)==false){ this.newList.push(msg) } }, }, mounted(){ // 过滤 if(!this.List.optionList || this.List.optionList.length == 0 ){ return } //递归收到的答案,存到userAnswer内,进行回显 let that=this function answerTo(optionList,num) { optionList.forEach((option,i)=> { if(option.optionList){ num=num+1 answerTo(option.optionList,num) } if(option.answer){ if(option.type == 1 ){ that.$set(that.userAnswer.singleList, option.id, option.answer) that.optionKeyList.push(option.id + option.answer) } if(option.type == 2 ){ that.$set(that.userAnswer.mutiList, i, option.answer.split(',')) that.optionKeyList.push(option.id + option.answer) } if(option.type == 3 ){ for (let j = 0; j < option.answer.split(',').length; j++) { that.userAnswer.input.push(option.answer.split(',')[j]) } } if(option.type == 4 ){ that.userAnswer.textarea =option.answer } } return num--; }) } answerTo(this.List.optionList,1) }, } </script> <style lang="scss" scoped> .unit{ width: 900px; height: 100%; font-size: 22px; margin: 0px auto; .unit-title{ margin-top: 30px; margin-bottom: 20px; } .unit-select{ width: 100%; list-style: none; margin: 0; padding: 0; .select-item{ .radioContent{ display: flex; flex-wrap: wrap; align-items: center; .radioContent-item{ display: flex; align-items: center; height: 34px; } .radioContent-text{ display: inline-block; word-break: break-all; white-space: normal; width:810px; font-size: 16px; } } .inputContent{ width: 500px; margin-bottom: 5px; } } } } .child-content{ padding-left:20px; .RadioGroup-child{ .unit{ font-size: 16px; line-height: 30px; .unit-title{ margin-top: 10px; margin-bottom: 10px; } } } } </style>
3.在store的index.js中。
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { examAnswer:[], }, getters: { }, mutations: { // 修改大题中的小题 addAnswer(state,answerList){ //有数据就进行替换工作,没有就push if(state.examAnswer.length>0){ state.examAnswer.forEach((item,index) => { item.forEach((element,i)=>{ if(answerList.id==element.subjectId){ item.splice(i,1,answerList.userSingle) }else if(i+1==item.length && answerList.uniqueId==element.parentId){ item.push(answerList.userSingle) } }) }); }else{ state.examAnswer.push([answerList.userSingle]) } }, // 修改一整道大题 addOldAnswer(state,answerList){ //有数据就进行替换工作,没有就push if(state.examAnswer.length>0){ let values=[] state.examAnswer.forEach((item,index) => { values = item.flatMap(arr => Object.values(arr)); }) if(values.includes(answerList.uniqueId)==false){ let array=[] array.push(answerList.userSingle) state.examAnswer.push(array) }else{ state.examAnswer.forEach((item,index) => { item.forEach((element,i)=>{ if(element.parentId==answerList.uniqueId && answerList.uniqueId==element.subjectId){ state.examAnswer.splice(index,1) let array=[] array.push(answerList.userSingle) state.examAnswer.push(array) } }) }); } }else{ state.examAnswer.push(answerList.newListS) } } }, actions: { }, modules: { } })
注意:子组件RadioGroup.vue的mounted()中回显的递归代码中有一个逻辑。
这里有一个逻辑,递归回显的数据无法把答案遍历到递归的子组件中,
因为每一层定义的变量都是独立的,所以回显的数据是一直存在当前第一层RadioGroup组件内的,导致的问题就是多选和填空有些答案的内容会不显示,因为找不到相应数据显示。
所以此处用了this.$set设置数据响应式可以回显,但是其实是把原本之前的数据进行了覆盖,所以回显成功。
但进行回显后修改提交时,只会把当前修改的答案提交(因为回显的答案没存到数组里),如果回显后想要修改提交的话,就需要后端再进行逻辑编写,
保存前端提交过的答案,等用户想回显修改的时候,
把用户修改过的答案再附加或覆盖到原本的答案上就完成了回显后的修改内容。
我有可能写问卷调查,但我写问卷调查不太可能。