文章目录
回答问卷的部分参考 微信小程序做问卷——前端部分(回答问卷)
实现效果
界面功能
最后实现的界面如下
最右上角的蓝色加号是增加一种新的题型,一共三种题型,单选,多选,问答。
选择一种题型之后,点击确定,会下页面的下面生成一个新的题型。比如选择多选:
一开始是三种题型,单选,多选和问答,现在变成四个题目了。
这些textarea
里面的值都是默认设置的,所以新加的时候也会带有默认值,这是为了方便调试用的,正式用的时候都置为空就可以。
每个题型有个灰色加号(问答题没有),点击加号会增加该题型选项数量,比如在第二题再添加一项
在灰色加号后面还要一个红底白叉,表示删除这一个题型,比如删除第三个,那么第四个就会自动变成第三个
如果选择每个题型的的选项后面的蓝色减号,那么就会删除这一项,比如删除Q2的第一项OK
:
由于使用的是textarea
,所以是支持自动换行以及自适应高度的(虽然模拟器的适配很不好看,但是真机调试的话是没问题的)。
数据功能
因为页面最后是要生成发向服务端的,所以最后要生成一个数据。
设计的数据结构是(其中id是暂时用不到的)
questionnaireArray : [
{
"type": "SCQ",
"content": {
"description": "Which fruit do you like best?",
"options":
[
{ "id": 1, "name": "Lua", "isSelected": false },
{ "id": 2, "name": "Java", "isSelected": true },
{ "id": 3, "name": "C++", "isSelected": false }
]
}
},
{
"type": "MCQ",
"content": {
"description": "Which fruit do you like?",
"options":
[
{ "id": 1, "name": "OK", "isSelected": true },
{ "id": 2, "name": "Java", "isSelected": false },
{ "id": 3, "name": "C++", "isSelected": true }
]
}
},
{
"type": "SAQ",
"content": {
"description": "What's your name?",
"answer":"i dont know"
}
}
],
最终要实现的目的就是,改变问卷中的值,点击下一步的时候,可以自动生成适配的数据结构。
看一下效果
接着上面的操作,点击下一步,直接打印出questionnaire
,可以发现数据是设配的。
也可以改动每一项数据使得界面适配,比如在Q2的C++后面加几个哈哈哈,再点下一步,可以看到效果也是匹配的。
PS:只是为了展示功能,至于其他的小问题可以自己手动改,比如没有显示是单选还是多选,因为这是发布问卷的时候,而不是做问卷的时候,所以不需要出现radio
和checkbox
,但是内部数据还是记录着,为了方便,可以手动在wxml
上面添加具体的类别,这里SCQ
代表单选题,MCQ
代表多选题,SAQ
代表问答题。
各个组件的实现
右上角的蓝色加号
<picker value="{
{newIndex}}" range='{
{typeArray}}' bindchange="bindTypeChange">
<image class='addNewQuestion' src='{
{addIconPath}}'></image>
</picker>
picker
是微信小程序支持的原生组件,效果就是之前展示的,可以在底部生成选项,然后点击确定。在js里面有对应的代码。
这是数据绑定部分
typeArray: ['单选', '多选', '问答'],
newIndex: 0,
addIconPath: "../../../images/addIcon.png",
这是逻辑部分
bindTypeChange:function(e){
this.setData({
newIndex: e.detail.value
});
console.log(e.detail.value);
var tempArray = this.data.questionnaireArray;
if(this.data.newIndex == 0){
var temp0 = {
"type": "SCQ",
"content": {
"description": "Which fruit do you like best?",
"options":
[
{ "id": 1, "name": "Lua", "isSelected": false },
]
}
};
tempArray.push(temp0);
}
else if (this.data.newIndex == 1){
var temp0 = {
"type": "MCQ",
"content": {
"description": "Which fruit do you like best?",
"options":
[
{ "id": 1, "name": "Lua", "isSelected": false },
]
}
};
tempArray.push(temp0);
}
else if (this.data.newIndex == 2){
var temp0 = {
"type": "SAQ",
"content": {
"description": "Which fruit do you like best?",
}
};
tempArray.push(temp0);
}
this.setData({
questionnaireArray: tempArray,
});
},
在点击确定的时候会触发bindchange
函数,该函数首先通过e.detail.value
改掉newIndex
的值,来记录当前选中的是第几个选项,所有的数据修改都在this.setData({})
中,这样变化会显示在界面中,还可以用this.data =
这样的方式赋值,但是不会影响到界面,反而造成数据不一致性,所以不能这样用。得到newIndex
之后通过该值判断,如果是0就生成单选题,如果是1就生成多选题,如果是2就生成问答题。通过var tempArray = this.data.questionnaireArray;
取得中间变量,然后对中间变量操作,操作完再赋值回去,该项目的所有的改变数据的逻辑都遵循该逻辑,即先取中间变量,对中间变量操作,将中间变量赋值回去。
问卷结构部分
整体框架如下,其中questionnaireArray
,就是整个问卷的数据,生成的页面其实是数据驱动的,通过改变数据自动生成问卷,而不是在js端生成wxml
,所以只需要专注于数据即可。通过wx:for
和wx:if
自动循环数据生成.
当然我这里是二重循环,所以比一重循环更复杂一点,最后的结构是
以单选部分举例
由于单选和多选类似,而问答部分只涉及一重循环,所以只说单选部分。单选部分的结果如下:
<block wx:if="{
{item.type === 'SCQ'}}">
<view class = 'SCQ' data-id='{
{fatherIndex}}'>
<view class='SCQTitle'>
<view class='SCQQ'>Q</view>
<view class='SCQindex'>{
{fatherIndex+1}}</view>
<view class='SCQquto'>:</view>
<textarea auto-height='true' class='SCQDiscription' value='{
{item.content.description}}' bindblur='bindblurSCQ' data-id='{
{fatherIndex}}'></textarea>
<image class='SCQaddIcon1' src='{
{addIconPath1}}' mode='widthFix' catchtap='addSCQ' data-id='{
{fatherIndex}}'></image>
<image class='SCQdeleteIcon1' src='{
{deletePath1}}' mode='widthFix' catchtap='deleteSCQ' data-id='{
{fatherIndex}}'></image>
</view>
<view class='SCQOption' wx:for="{
{item.content.options}}" wx:key="SCQID" data-id='{
{fatherIndex}}' bindtouchstart='getTempFatherIndex'>
<!-- <image class='SCQselectIcon' src="{
{item.isSelected?'../../../images/SAQ2.png':'../../../images/SAQ1.png'}}" mode='widthFix'></image> -->
<textarea auto-height='true' value='{
{item.name}}' class='SCQText' bindblur='bindblurOneOfSCQ' data-id='{
{index}}'></textarea>
<image class='SCQdeleteIcon' src='{
{deletePath}}' mode='widthFix' data-id='{
{index}}' catchtap='deleteOneOfSCQ'></image>
</view>
</view>
</block>
其中有行image
被注释掉了,那是显示是否被选中的,由于在填问卷的时候会直接使用微信小程序的控件radio-group
以及checkbox-group
,会自带图标,以及多选和单选的功能,所以这里就不加了,可以展示一下没有被注释的效果:
注意到有这样的语句data-id='{
{fatherIndex}}'
,这是为该元素设置一个id
,方便js找到具体的元素,由于在外层的循环中设置了wx:for-index='fatherIndex'
,所以下面再用fatherIndex
的时候就是该值,如果不设置的话默认是index
,wx:for
默认的两个属性是index
以及item
,分别代表默认序号和对应的元素,而这个序号是根据数组中的位置变化的,比如已经有了4个元素编号从0-3,这时候删除第三个那么序号会变成0-2,而不是变成0,1,3。利用这种特性,删除的时候会方便很多。
每个题型的灰色加号
<image class='SCQaddIcon1' src='{
{addIconPath1}}' mode='widthFix' catchtap='addSCQ' data-id='{
{fatherIndex}}'></image>
mode='widthFix'
,是根据宽度自适应大小。
catchtap
是点击事件,没有用bindtap
,这里牵扯到冒泡的问题,冒泡的意思就是父级元素和子元素都绑定了按键函数,这时候点击子元素,事件会像冒泡一样向上级传递,所以会先调用子元素函数,再调用父元素函数,如果不冒泡就用catchtap
,冒泡就用bindtap
addSCQ:function(input){
var tempIndex = input.currentTarget.dataset.id;
var tempArray = this.data.questionnaireArray;
var tempSCQ = { "id": 1, "name": "Lua", "isSelected": false };
console.log(tempIndex);
tempArray[tempIndex].content.options.push(tempSCQ);
this.s