【Vue3.0实战逐步深入系列】基于localStorage为问卷调查添加保存及回填功能

1,009 阅读7分钟

这是我参与11月更文挑战的第24天,活动详情查看:2021最后一次更文挑战

【千字长文,熬夜更新,原创不易,多多支持,感谢大家】

前言

hello,小伙伴们大家好。到目前为止在【vue3.0实战逐步深入系列】文章中,我们已经实现问卷调查的配置、填写、获取结果以及结果展示功能。看似已经完成了简单的问卷调查功能,但实际上体验还不够友好。为什么这么说呢,我们先来看一个场景。假如一个问卷调查中有很多问卷主题十几个甚至几十个都有可能,正当我们填到一半的时候突然出现故障(浏览器崩溃或者电脑断电等),当我们再次重新打开浏览器时发现问卷已经变成了空白,原来辛辛苦苦填写的全都不见了,是不是瞬间崩溃。为了解决这一问题我们继续完善我们的问卷调查功能,为我们的问卷添加一个保存及回填功能。

思路分析

本次分享中主要涉及到两大功能点:保存和回填

  • 保存:在我们填写每个主题的时候,我们都应该将问卷主题及对应的答案保存起来,而保存起来的目的就是下次进来是还能够将已选的结果回填回去,因此需要做持久化保存,这里我们将问卷主题以及对应的结果封装成一个对象,然后持久化保存到localStorage中。
  • 回填:在问卷打开的时候首先判断是否存在localStorage,如果存在着直接从localStorage中将问卷主题对应的结果解析出来并回传给组件,如果localStorage不存在则作为一个全新的问卷加载。这里需要特别注意的是,我们在获取localStorage的时候是从Home.vue组件中获取的,而获取到的结果需要给各个子组件使用,因此这里又涉及到了父组件向子组件传值的问题,本案例中我们将采用props属性的方式实现父组件向子组件传值。

给子组件添加props属性及改造

为了方便接收父组件传过来的值进行结果回填,我们需要给每个主题组件再添加一个value prop,value的数据类型和默认值可以根据不同类型的组件进行不同的设置

  • myradio.vue:value类型为字符串类型,默认值为空字符串。同时删除原来的响应式属性selectedValue并替换成value
  • mycheck.vue:value类型为数组类型,默认值为空数组。同时删除原来的响应式属性selectedValue并替换成value
  • myinput.vue:value类型为字符串类型,默认值为空字符串。同时删除原来的响应式属性selectedValue并替换成value
  • mystar.vue:value类型为数值类型,默认值为0。同时删除原来的响应式属性selectedValue并替换成value
  • vote.vue:vote组件与其它几个组件略有不同,因为vote组件会涉及到多个值的传递,因此我们把value属性定义为对象类型,默认值为{supValue:0, oppValue:0}

改造后的代码如下:

  • myradio.vue
<!--...省略-->
<el-radio-group v-model='value' @change='valChange'></el-radio-group>
<!--...省略-->
// ... 省略
props:{
	value:{
		type:String,
		default:''
	}
}
// ...省略
  • mycheck.vue
<!--...省略-->
<el-checkbox-group v-model='value' @change='valChange'></el-checkbox-group>
<!--...省略-->
// ... 省略
props:{
	value:{
		type:Array,
		default:[]
	}
}
// ...省略
  • myinput.vue
<!--...省略-->
<el-input v-model='value' @change='valChange'></el-input>
<!--...省略-->
// ... 省略
props:{
	value:{
		type:String,
		default:''
	}
}
// ...省略
  • myinput.vue
<!--...省略-->
<el-rate v-model='value' @change='valChange'></el-rate>
<!--...省略-->
// ... 省略
props:{
	value:{
		type:Number,
		default:0
	}
}
// ...省略
  • vote.vue

投票组件改动稍微有点复杂,除了添加value props外我们还需要做3处修改:

  • 在setup函数中解构出props中的value值
  • 修改响应式属性supCount和oppCount的默认值(从上一步的value中获取)
  • 修改calcResult函数将result及字符串拼接的结果删除,并直接将supCount和oppCount以对象的形式通过自定义事件传递给父组件

代码如下:

// ... 省略
props:{
	 value: {
      default: {
        supValue: 0,
        oppValue: 0,
      },
    },
},
setup(props, context) {
    let { value } = props;//新增
    let supCount = ref(value.supValue);//修改
    let oppCount = ref(value.oppValue);//修改
   //...省略
    const calcResult = () => {
      emit("select", { supValue: supCount.value, oppValue: oppCount.value });//修改
    };
// ...省略

Home.vue组件改造

子组件都已改造完成,接下来就要对主页面进行修改了。

  • 首先需要在模板中添加一个“保存”按钮,同时绑定click事件
  • 在setup函数中定义一个history变量,用于保存问卷主题及对应的结果,在定义history时首先看下localStorage中是否已经保存,如果已保存则直接从localStorage中获取结果并赋值给history,如果不存在则直接将空对象{}赋值给history
  • 修改原有的getValue方法,在该方法中将已选的结果以键值对的形式保存到history中,其中把该方法中的第三个参数flag作为history的键,第一个参数val也就是已选的问卷结果作为值保存,同时在该方法中还需对vote组件做特殊处理(把结果拼接成一个字符串,因为我们已经对vote的自定义事件返回值做了修改,返回的是一个对象而不再是拼接好的字符串了)
  • 定义一个save方法用于在点击保存按钮时将问卷结果数据持久化保存到localStorage中
  • 修改submit方法,在点击提交按钮将问卷结果提交后需要删除localStorage,一遍下次重新加载
  • 定义一个计算属性recoveryResult,用于恢复localStorage中已经保存的问卷结果,该属性将与每个子组件的value props进行绑定。另外因为localStorage中存储的是键值对的对象,并且主题组件又是通过v-for指令循环生成的,因此在恢复结果的时候需要传递一个键以便获取对应的结果,为了能够接收参数因此该计算属性的返回值应该是一个函数,而在该函数中还需要调用一下getValue方法用于重新给result赋值(因为页面重新加载过以后result会被重新初始化,因此在恢复页面结果的同时也应该将result对象中的数据恢复)
  • 最后再在模板中给每个组件绑定value属性,其值就是上一步中定义的计算属性recoveryResult

主要涉及到修改的代码如下:

<template>
  <Vote
    :value="
      recoveryResult(vote.title, `vote${vote.id}`) || {
        supValue: 0,
        oppValue: 0,
      }
    " 
    />
  <myradio
    :value="recoveryResult(radio.title, `radio${radio.id}`) || ''"
  />
  <mycheck
    :value="recoveryResult(check.title, `check${check.id}`) || []"
  />
  <myinput
    :value="recoveryResult(inp.title, `input${inp.id}`) || ''"
  />
  <mystar
    :value="recoveryResult(star.title, `star${star.id}`) || 0"
  />
  <div style="text-align: center">
    <el-button type="success" @click="save">保存</el-button>
  </div>
</template>
 const submit = () => {
      //...省略
      localStorage.removeItem("QUESTION");
    };
    const getValue = (val, title, flag) => {
      if (flag.indexOf("vote") >= 0) {
        let vote_result = `支持人数:${val.supValue}/${
          val.supValue + val.oppValue
        };反对人数:${val.oppValue}/${val.supValue + val.oppValue};支持率:${(
          (val.supValue / (val.supValue + val.oppValue || 1)) *
          100
        ).toFixed(2)}%`;
        result[flag] = `${title} >>> ${vote_result}`;
      } else {
        result[flag] = `${title} >>> ${val}`;
      }
      history[flag] = val;
    };

    const save = () => {
      localStorage.setItem("QUESTION", JSON.stringify(history));
    };

    const recoveryResult = computed(() => {
      return function (title, flag) {
        if (history.hasOwnProperty(flag)) {
          getValue(history[flag], title, flag); //同时恢复已选结果result
        }

        return history.hasOwnProperty(flag) && history[flag];
      };
    });

遇到的两个小坑

  • 在给每个组件的value属性绑定recoveryResult的时候发现页面结果会发生错乱(比如复选框无法勾选,input框中出现布尔值等),原因是在每个子组件中定义的value props都是有类型限制的,而在recoveryResult恢复时有的值是不存在的(比如一个问卷中只填了一半,那么后面的一半是没有保存的,因此在恢复的时候找不到对应的值就会返回一个false)这样就与value定义的类型不匹配,从而导致错乱。解决办法就是:当recoveryResult的返回值是false时给对应的value属性指定一个默认值,比如:"recoveryResult(check.title, check${check.id}) || []"
  • 当问卷结果保存后,刷新页面重新加载的时候发现保存的结果是可以被恢复回来的,但是再点提交按钮发现提交的内容却是空的。原因是页面刷新重新加载后result变量会被重新初始化(即,之前临时保存的值都会被清空),而提交的内容又是从这个变量中获取的,所以会得到一个空的结果。解决措施:在调用recoveryResult恢复的同时再调用一下getValue方法同时将result也恢复掉。

总结

本文我们继续扩展了问卷调查小功能,借助localStorage实现了问卷结果的持久化保存功能,那么在填写问卷时不管是临时有事走开,还是浏览器崩溃,甚至是断电电脑关机,那么只要问卷结果进行了保存都可以从localStorage中恢复回来并可以继续填写,直到结果提交后才会把localStorage删除。 本次分享就到这里了,喜欢的小伙伴欢迎点赞评论加关注哦!