本文共 5644 字,大约阅读时间需要 18 分钟。
为了练习Vue,写了一个小项目,主要内容是答题。
cli3.x
,选择router
即可element
,详见axios
import Vue from 'vue'import VueRouter from 'vue-router'Vue.use(VueRouter)const Main = () => import('@/views/Main')const Quiz = () => import('@/views/quiz/Quiz')const Start = () => import('@/views/quiz/Start')const routes = [ { path: '/', redirect: '/index', component: Main, children: [ { path: '/index', component: Start }, { path: '/quiz', component: Quiz }, ] },]const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes})export default router
非常简单的路由设计,其中的路由的加载方式为懒加载。
你好,请选择答题范围
全选 { { city }} 开始答题
export default { name: "Start", data() { return { checkAll: false, btnDisabled: false, checkedCities: ['文科', '理科'], cities: ['文科', '理科', '娱乐', '生活', '文艺', '流行'], isIndeterminate: true }; }, created() { this.checkedCities = localStorage.checkedWords ? localStorage.checkedWords.split(',') : this.checkedCities }, methods: { isDisabled() { this.btnDisabled = this.checkedCities.length === 0 }, handleCheckAllChange(val) { this.checkedCities = val ? this.cities : []; this.isIndeterminate = false; this.isDisabled() }, handleCheckedCitiesChange(value) { let checkedCount = value.length; this.checkAll = checkedCount === this.cities.length; this.isIndeterminate = checkedCount > 0 && checkedCount < this.cities.length; }, start() { this.$set(localStorage, 'checkedWords', this.checkedCities) this.$router.push('/quiz') this.$emit('checked', this.checkedCities, true) } }}
created
函数,在创建组件完成后,检查浏览器缓存localStorage
中是否已经存在选择的题目范围。如果有则直接使用,没有就使用默认选择的。 localStorage
中,是以,
隔开的字符串形式,所以要用split
函数来分割一下。用法详见isDisabled
函数是用来判断选中题目种类的个数是否为0,如果是则按钮不可点击。start
函数按钮点击后触发,目的是跳转路由至/quiz
答题页,并将题目种类数组传给父组件Main
Quiz
,即子传父,父传其他子,也可以用过vuex
实现。Vue答题
--zed 制作历史排行榜
- { { item }}
布局采用ElementUI
中如下布局
Header Aside Main
export default { name: "Main", data() { return { info: [], scores: localStorage.scores ? localStorage.scores.split(',') : [] } }, methods: { setInfo(data) { this.info = data }, setScore(data) { this.scores.push(data) this.sortArr(this.scores) if (this.scores.length > 10) { this.scores.splice(10) } localStorage.scores = this.scores }, sortArr(arr) { return arr.sort((x, y) => y - x) } }}
data
中的info
数组,就是前面Start
组件传过来的数据data
中的scores
,每次打完题之后,Quiz
数组都会将分数传过来。然后将这些分数压入数组,存储到localStorage
中setInfo
函数捕获子组件Start
传过来的数据,并赋值给该组件setScore
函数捕获子组件Quiz
传过来的数据,并压入分数数组,且要排序。如果分数数组的长度超过10,则只截取分数最高的十项sort
函数为数组排序,用法详见score:{ { score }}
hp:
科目范围:{ { info }} 本题属:{ { currentQuiz.school }} { { currentQuiz.type }}{ { currentQuiz.quiz }}
{ { item }} next
score:{ { score }}
hp:
科目范围:{ { info }} 本题属:{ { currentQuiz.school }} { { currentQuiz.type }}{ { currentQuiz.quiz }}
{ { item }} next
created
函数 info
,即题目种类数组是否为空,如果是空则返回到首页,重新选择。fetch
函数用来发送axios
请求,这里的axios
被笔者挂载到Vue 的原型上,这样便可全局使用。也可以只在该组件中引入axios
async && await
用法详见轻松理解 async 与 awaitmain.js
文件中书写import axios from "axios"Vue.prototype.$http = axios
filter
函数用来过滤题目列表,如果题目列表的长度为6,也就意味着全选,则不用筛选,直接返回。randomQuiz
函数用于随机出题,出题之后,为了防止重复,直接在题目列表中删除此题killProgress
用于管理进度条计时器 answer
值,因为answer
只有1,2,3,4
,所以恢复为0是可以的;恢复进度条百分比nextBtn
函数中完成。也就是说,进度条走完和点击下一题按钮的效果相同checkAnswer
函数用于判定答案的对与错 Promise
用法详见1,2,3,4
。咱们选择的答案是0,1,2,3
,所以要在题目正确答案-1或者再咱们的答案+1,都是可以的。label
值不直接绑定索引,而是题目的_id
再加索引值。这是因为vue读取缓存的机制,这道题的选项的label
值如果绑定了1,2,3,4
,下道题也是1,2,3,4
,这样vue会直接将缓存中的四个选项捞出来,而不是重新创建。这就意味着,咱们上一题的选中效果,切换到下一题的时候,依旧存在。this.$set
赋值,可见true
代表答对了,而是代表hp
还是有的,也就是说可以继续出题;false
代表hp
用完了,不能再继续出题nextBtn
函数,一旦点击按钮或者进度条结束,就把按钮变为不可点击,这是为了屏蔽用户的无效操作,而且多次点击有可能导致计时器的混乱。一旦点击按钮或者进度条结束,就要判定是否继续出题;如果继续,则需要把按钮恢复可点击,调用出题函数;如果结束了,则把分数传给父组件Main
,并且跳转路由至/index
this.hp
为何是一个数组呢?这个数组是用来渲染那个灯泡图标的。 isAnswer
,配合选项v-for
渲染来绑定样式类。初始该数组里有4个false
,一旦进度条结束或点击按钮,则会将正确的那个选项绑定样式类。什么?你说选正确了就不用绑定了是吧?不错,但是正确没有延迟1s切换下一题,用户也就看不到这个效果了。.isAnswer { border: 2px solid #0f0;}
总的来说,这个项目难度不大,但是一些基础琐碎的知识挺多的,适合练手。
链接:https://pan.baidu.com/s/1HKvGly1H2lpQCkxfm2Onlw
提取码:z1ed