爱(AI)答题是一款基于Vue 3+Spring Boot+Redis+ChatGLM+RxJava+SSE的AI答题应用平台。
用户可以基于AI快速制作并发布答题应用,支持检索、分享、在线答题并基于AI得到回答总结;
管理员可以集中管理和审核应用。
目录
MBTI性格测试小程序
一、页面模块
1)主页:
2)答题页面:
3)查看结果页面:
二、实现方案介绍
核心组成部分:题目、用户答案、评分规则
1、题目结构
一道题目它应该是以Json结构形式呈现,选项为数组,例如:
[
{
"title":"你通常更喜欢",
"options":[
{
"result":"I",
"value":"独自工作",
"key":"A"
},
{
"result":"E",
"value":"与他人合作",
"key":"B"
}
]
}
]
相较于直接用key-value结构,Json结构更易于理解和拓展;缺点就是占用空间。
2、用户答案结构
用户填写完答案后怎么将其传递给判题逻辑怎么传递给后端。
用户提交答案的时候,仅需传递一个数组,数组内是选项:["A"],按数组顺序匹配对应的题目。
["A","A","B","B"]
优点:不用再完整传递题目的结构,节省传输体积,提高性能。
3、评分规则
判题思路
第一题判断 I or E,第二题判断 J or P,第三题判断 T or F,第四题判断 S or N。
按照这个思路,我们可以统计题目中所有的偏好,比如答题一共选了10个I,2个E,很明显10大于2,因此我们得出他是 I 人,同理判断是S/N、T/F和J/P
所以我们出题的时候,给每个选择的答案对应设置一个属性。
• 第一题 A 对应 I, B 对应 E
• 第二题 A 对应 E , B 对应丨
• 第三题 A 对应 s, B 对应 N
• 第四题 A 对应 T , B 对应 F
• 第五题 A 对应 p, B 对应 J
如果用户选择了[A,B,A,A,A],可以算出用户有两个I,一个S,一个T,一个P,很明显他是ISTP人格。
评分结果计算原理
1)首先要有一个题目评分结果集合,这里预先创建了很多结果,包括了MBTI的所有16种角色。
• resultName: ISTJ
• resultDesc :忠诚可靠,被公认为务实,注重细节。
• resultlcon: 预留字段,如果想界面好看点,可以给 result 设定图片
• resuItProp : [I,S,T,J]
题目评分结果对应的 JSON 结构如下:
[
{
"resultProp":[
"I",
"S",
"T",
"J"
],
"resultDesc":"忠诚可靠,被公认为务实,注重细节。",
"resultPicture":"icon_url_istj",
"resultName":"ISTJ(物流师)"
},
{
"resultProp":[
"I",
"S",
"F",
"J"
],
"resultDesc":"善良贴心,以同情心和责任心为特点。",
"resultPicture":"icon_url_isfj",
"resultName":"ISFJ(守护者)"
}
]
2)怎么根据每道题目的选项计算出结果呢?
每个结果有一个 resultprop 字段,是一个元素不重复的数组(属性集合),里面的内容和题目选项的 result 字段匹配。
[
{
"title":"你通常更喜欢",
"options":[
{
"result":"I",
"value":"独自工作",
"key":"A"
},
{
"result":"E",
"value":"与他人合作",
"key":"B"
}
]
}
]
此时用户第一题选了 A, 对应的属性如果是I,那么我们遍历这 16 种结果,然后判断角色对应的 resultprop 里面是否包含I,如果包含则对应的角色就+ 1 分,不包含则不得分。最终遍历完所有题目后,我们就能知道这 16 种结果中,哪个角色得分最高,它就是最终的评分结果了。
三、MBTI 小程序 Demo 数据
可以使用AI生成。
1、题目列表
每个选项包含了对应的结果,questions.json:
[
{
"options": [
{
"result": "I",
"value": "独自工作",
"key": "A"
},
{
"result": "E",
"value": "与他人合作",
"key": "B"
}
],
"title": "你通常更喜欢"
},
{
"options": [
{
"result": "J",
"value": "喜欢有明确的计划",
"key": "A"
},
{
"result": "P",
"value": "更愿意随机应变",
"key": "B"
}
],
"title": "当安排活动时"
},
{
"options": [
{
"result": "T",
"value": "认为应该严格遵守",
"key": "A"
},
{
"result": "F",
"value": "认为应灵活运用",
"key": "B"
}
],
"title": "你如何看待规则"
},
{
"options": [
{
"result": "E",
"value": "经常是说话的人",
"key": "A"
},
{
"result": "I",
"value": "更倾向于倾听",
"key": "B"
}
],
"title": "在社交场合中"
},
{
"options": [
{
"result": "J",
"value": "先研究再行动",
"key": "A"
},
{
"result": "P",
"value": "边做边学习",
"key": "B"
}
],
"title": "面对新的挑战"
},
{
"options": [
{
"result": "S",
"value": "注重细节和事实",
"key": "A"
},
{
"result": "N",
"value": "注重概念和想象",
"key": "B"
}
],
"title": "在日常生活中"
},
{
"options": [
{
"result": "T",
"value": "更多基于逻辑分析",
"key": "A"
},
{
"result": "F",
"value": "更多基于个人情感",
"key": "B"
}
],
"title": "做决定时"
},
{
"options": [
{
"result": "S",
"value": "喜欢有结构和常规",
"key": "A"
},
{
"result": "N",
"value": "喜欢自由和灵活性",
"key": "B"
}
],
"title": "对于日常安排"
},
{
"options": [
{
"result": "P",
"value": "首先考虑可能性",
"key": "A"
},
{
"result": "J",
"value": "首先考虑后果",
"key": "B"
}
],
"title": "当遇到问题时"
},
{
"options": [
{
"result": "T",
"value": "时间是一种宝贵的资源",
"key": "A"
},
{
"result": "F",
"value": "时间是相对灵活的概念",
"key": "B"
}
],
"title": "你如何看待时间"
}
]
2、题目结果表
16种结果
question_results.json:
[
{
"resultProp": [
"I",
"S",
"T",
"J"
],
"resultDesc": "忠诚可靠,被公认为务实,注重细节。",
"resultPicture": "icon_url_istj",
"resultName": "ISTJ(物流师)"
},
{
"resultProp": [
"I",
"S",
"F",
"J"
],
"resultDesc": "善良贴心,以同情心和责任为特点。",
"resultPicture": "icon_url_isfj",
"resultName": "ISFJ(守护者)"
},
{
"resultProp": [
"I",
"N",
"F",
"J"
],
"resultDesc": "理想主义者,有着深刻的洞察力,善于理解他人。",
"resultPicture": "icon_url_infj",
"resultName": "INFJ(占有者)"
},
{
"resultProp": [
"I",
"N",
"T",
"J"
],
"resultDesc": "独立思考者,善于规划和实现目标,理性而果断。",
"resultPicture": "icon_url_intj",
"resultName": "INTJ(设计师)"
},
{
"resultProp": [
"I",
"S",
"T",
"P"
],
"resultDesc": "冷静自持,善于解决问题,擅长实践技能。",
"resultPicture": "icon_url_istp",
"resultName": "ISTP(运动员)"
},
{
"resultProp": [
"I",
"S",
"F",
"P"
],
"resultDesc": "具有艺术感和敏感性,珍视个人空间和自由。",
"resultPicture": "icon_url_isfp",
"resultName": "ISFP(艺术家)"
},
{
"resultProp": [
"I",
"N",
"F",
"P"
],
"resultDesc": "理想主义者,富有创造力,以同情心和理解他人著称。",
"resultPicture": "icon_url_infp",
"resultName": "INFP(治愈者)"
},
{
"resultProp": [
"I",
"N",
"T",
"P"
],
"resultDesc": "思维清晰,探索精神,独立思考且理性。",
"resultPicture": "icon_url_intp",
"resultName": "INTP(学者)"
},
{
"resultProp": [
"E",
"S",
"T",
"P"
],
"resultDesc": "敢于冒险,乐于冒险,思维敏捷,行动果断。",
"resultPicture": "icon_url_estp",
"resultName": "ESTP(拓荒者)"
},
{
"resultProp": [
"E",
"S",
"F",
"P"
],
"resultDesc": "热情开朗,善于社交,热爱生活,乐于助人。",
"resultPicture": "icon_url_esfp",
"resultName": "ESFP(表演者)"
},
{
"resultProp": [
"E",
"N",
"F",
"P"
],
"resultDesc": "富有想象力,充满热情,善于激发他人的活力和潜力。",
"resultPicture": "icon_url_enfp",
"resultName": "ENFP(倡导者)"
},
{
"resultProp": [
"E",
"N",
"T",
"P"
],
"resultDesc": "充满创造力,善于辩论,挑战传统,喜欢探索新领域。",
"resultPicture": "icon_url_entp",
"resultName": "ENTP(发明家)"
},
{
"resultProp": [
"E",
"S",
"T",
"J"
],
"resultDesc": "务实果断,善于组织和管理,重视效率和目标。",
"resultPicture": "icon_url_estj",
"resultName": "ESTJ(主管)"
},
{
"resultProp": [
"E",
"S",
"F",
"J"
],
"resultDesc": "友善热心,以协调、耐心和关怀为特点,善于团队合作。",
"resultPicture": "icon_url_esfj",
"resultName": "ESFJ(尽责者)"
},
{
"resultProp": [
"E",
"N",
"F",
"J"
],
"resultDesc": "热情关爱,善于帮助他人,具有领导力和社交能力。",
"resultPicture": "icon_url_enfj",
"resultName": "ENFJ(教导着)"
},
{
"resultProp": [
"E",
"N",
"T",
"J"
],
"resultDesc": "果断自信,具有领导才能,善于规划和执行目标。",
"resultPicture": "icon_url_entj",
"resultName": "ENTJ(统帅)"
}
]
四、Taro 跨端小程序开发入门
技术选型
Taro 官方文档:Taro 文档 (跨端开发框架)
Taro UI 组件库
React
TypeScript
做项目一定要选用一个组件库,提高开发效率!
推荐和 Taro 官方框架兼容的组件库,否则会出现跨端后样式丢失的问题:
- taro-ui:Taro UI | O2Team (最推荐,兼容性最好)
- nut-ui
开发准备
微信开发者工具下载:微信开发者工具下载地址与更新日志 | 微信开放文档
微信开发者工具介绍
实战
用不了组件一般是没导入。
1. 确认页面中的数据结构
2. 开发准备
微信开发者工具:微信开发者工具下载地址与更新日志 | 微信开放文档
Taro框架:Taro 文档
Taro UI:Taro UI | O2Team
创建空文件夹:在根目录下打开终端输入 git init 使项目由git来托管;输入 taro init mbti-test-mini(项目名称)
安装依赖项:使用WebStorm打开项目,点击 npm install 自动安装依赖项;
如果出现这个错误
则在终端输入 npm install --force
测试微信小程序项目启动:
微信开发者工具中导入项目:
选择测试号,以后需要发布,申请权限等真正上线的时候再弄。
代码规范
3. 页面开发
a. 引入组件
Taro UI中
在入口文件app.ts中引入
选择你需要的组件(例如徽标)
在主页 index.tsx中插入
b. 配置页面路由(创建页面)
可以根据创建项目脚手架时的默认index页面复制一份新的页面。
例如:在app.config.ts文件中
哪个页面放在第一条就会加载哪个页面;
我们可以创建一个user页面:
其中可以写跳转到index页面地面。
需要什么都可以在Taro文档中搜索到各种组件。
c. 修改应用标题
app.config.ts中修改小程序的主体标题(这是个全局配置,可以给每个页面进行局部配置,因此单个特殊页面不会生效)
export default defineAppConfig({
pages: [
'pages/ex/ex',
'pages/index/index'
],
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
navigationBarTitleText: 'MBTI 性格测试',
navigationBarTextStyle: 'black'
}
})
4. MBTI 主页
a. 先写页面中的内容(文字)
b. 然后添加图片
首先创建一个静态资源目录,将图片放入此目录下
然后使用一个Image组件
import { View, Image } from '@tarojs/components'
<Image
src={mbti}
/>
c. 然后填写三个文件
tsx文件
import { View, Image } from '@tarojs/components'
import {AtButton} from "taro-ui"
import './ex.scss'
import mbti from '../../assets/mbti.png'
import GlobalFooter from '../../components/GlobalFooter/gf'
/**
* 主页
*/
export default () => {
return (
<View className='indexPage'>
<View className='at-article__h1 title'>MBTI 性格测试</View>
<View className='at-article__h2 subTitle'>
只需2分钟,就能非常准确地描述出你是I人还是E人及你的性格特点
</View>
<AtButton type='primary' circle className="enterBtn">开始测试</AtButton>
<Image className="mbti" src={mbti}/>
<GlobalFooter />
</View>
)
}
scss文件
.indexPage{
background: #A2C7D7;
height: 100vh;
.title{
color:white;
padding-top: 48px;
text-align: center;
}
.subTitle{
color: white;
margin-bottom: 48px;
}
.enterBtn{
width: 60vw;
}
}
config.ts文件
export default definePageConfig({
//navigationBarTitleText: '当前首页'
})
效果图
5. 答题页面
开发步骤:
1)先写组件HTML
2)再调整样式
3)最后再写逻辑(上一题、下一题)
a. 题目列表和题目结果表
题目列表
[
{
"options": [
{
"result": "I",
"value": "独自工作",
"key": "A"
},
{
"result": "E",
"value": "与他人合作",
"key": "B"
}
],
"title": "你通常更喜欢"
},
{
"options": [
{
"result": "J",
"value": "喜欢有明确的计划",
"key": "A"
},
{
"result": "P",
"value": "更愿意随机应变",
"key": "B"
}
],
"title": "当安排活动时"
},
{
"options": [
{
"result": "T",
"value": "认为应该严格遵守",
"key": "A"
},
{
"result": "F",
"value": "认为应灵活运用",
"key": "B"
}
],
"title": "你如何看待规则"
},
{
"options": [
{
"result": "E",
"value": "经常是说话的人",
"key": "A"
},
{
"result": "I",
"value": "更倾向于倾听",
"key": "B"
}
],
"title": "在社交场合中"
},
{
"options": [
{
"result": "J",
"value": "先研究再行动",
"key": "A"
},
{
"result": "P",
"value": "边做边学习",
"key": "B"
}
],
"title": "面对新的挑战"
},
{
"options": [
{
"result": "S",
"value": "注重细节和事实",
"key": "A"
},
{
"result": "N",
"value": "注重概念和想象",
"key": "B"
}
],
"title": "在日常生活中"
},
{
"options": [
{
"result": "T",
"value": "更多基于逻辑分析",
"key": "A"
},
{
"result": "F",
"value": "更多基于个人情感",
"key": "B"
}
],
"title": "做决定时"
},
{
"options": [
{
"result": "S",
"value": "喜欢有结构和常规",
"key": "A"
},
{
"result": "N",
"value": "喜欢自由和灵活性",
"key": "B"
}
],
"title": "对于日常安排"
},
{
"options": [
{
"result": "P",
"value": "首先考虑可能性",
"key": "A"
},
{
"result": "J",
"value": "首先考虑后果",
"key": "B"
}
],
"title": "当遇到问题时"
},
{
"options": [
{
"result": "T",
"value": "时间是一种宝贵的资源",
"key": "A"
},
{
"result": "F",
"value": "时间是相对灵活的概念",
"key": "B"
}
],
"title": "你如何看待时间"
}
]
题目结果表
[
{
"resultProp": [
"I",
"S",
"T",
"J"
],
"resultDesc": "忠诚可靠,被公认为务实,注重细节。",
"resultPicture": "icon_url_istj",
"resultName": "ISTJ(物流师)",
},
{
"resultProp": [
"I",
"S",
"F",
"J"
],
"resultDesc": "善良贴心,以同情心和责任心为特点。",
"resultPicture": "icon_url_isfj",
"resultName": "ISFJ(守护者)"
},
{
"resultProp": [
"I",
"N",
"F",
"J"
],
"resultDesc": "理想主义者,有着深刻的洞察力,善于理解他人:",
"resultPicture": "icon_url_infj",
"resultName": "INFJ(占有者)"
},
{
"resultProp": [
"I",
"N",
"T",
"J"
],
"resultDesc": "忠诚可靠,有远见,注重细节。",
"resultPicture": "icon_url_intj",
"resultName": "INTJ(建筑师)"
},
{
"resultProp": [
"I",
"S",
"T",
"P"
],
"resultDesc": "冷静自持,善于解决问题,擅长实践技能。",
"resultPicture": "icon_url_istp",
"resultName": "ISTP(运动员)"
},
{
"resultProp": [
"I",
"S",
"F",
"P"
],
"resultDesc": "忠诚可靠,善于表达,注重细节。",
"resultPicture": "icon_url_isfp",
"resultName": "ISFP(艺术家)"
},
{
"resultProp": [
"I",
"N",
"F",
"P"
],
"resultDesc": "理想主义者,善于表达,富有同情心。",
"resultPicture": "icon_url_infp",
"resultName": "INFP(调停者)"
},
{
"resultProp": [
"I",
"N",
"T",
"P"
],
"resultDesc": "理想主义者,善于解决问题,富有同情心。",
"resultPicture": "icon_url_intp",
"resultName": "INTP(逻辑学家)"
},
{
"resultProp": [
"E",
"S",
"T",
"P"
],
"resultDesc": "善于表达,善于交际,注重细节。",
"resultPicture": "icon_url_estp",
"resultName": "ESTP(企业家)"
},
{
"resultProp": [
"E",
"S",
"F",
"P"
],
"resultDesc": "善于表达,善于交际,富有同情心。",
"resultPicture": "icon_url_esfp",
"resultName": "ESFP(表演者)"
},
{
"resultProp": [
"E",
"N",
"F",
"P"
],
"resultDesc": "善于表达,善于交际,富有同情心。",
"resultPicture": "icon_url_enfp",
"resultName": "ENFP(倡导者)"
},
{
"resultProp": [
"E",
"N",
"T",
"P"
],
"resultDesc": "善于交际,善于解决问题,富有同情心。",
"resultPicture": "icon_url_entp",
"resultName": "ENTP(发明家)"
},
{
"resultProp": [
"E",
"S",
"T",
"J"
],
"resultDesc": "善于交际,注重细节,注重实践。",
"resultPicture": "icon_url_estj",
"resultName": "ESTJ(经理)"
},
{
"resultProp": [
"E",
"S",
"F",
"J"
],
"resultDesc": "善于交际,注重细节,注重实践。",
"resultPicture": "icon_url_esfj",
"resultName": "ESFJ(执政官)"
},
{
"resultProp": [
"E",
"N",
"F",
"J"
],
"resultDesc": "善于交际,注重细节,注重实践。",
"resultPicture": "icon_url_enfj",
"resultName": "ENFJ(教育家)"
},
{
"resultProp": [
"E",
"N",
"T",
"J"
],
"resultDesc": "善于交际,注重细节,注重实践。",
"resultPicture": "icon_url_entj",
"resultName": "ENTJ(指挥官)"
}
]
b. 导入题目
questions.json
[
{
"options": [
{
"result": "I",
"value": "独自工作",
"key": "A"
},
{
"result": "E",
"value": "与他人合作",
"key": "B"
}
],
"title": "你通常更喜欢"
},
{
"options": [
{
"result": "J",
"value": "喜欢有明确的计划",
"key": "A"
},
{
"result": "P",
"value": "更愿意随机应变",
"key": "B"
}
],
"title": "当安排活动时"
},
{
"options": [
{
"result": "T",
"value": "认为应该严格遵守",
"key": "A"
},
{
"result": "F",
"value": "认为应灵活运用",
"key": "B"
}
],
"title": "你如何看待规则"
},
{
"options": [
{
"result": "E",
"value": "经常是说话的人",
"key": "A"
},
{
"result": "I",
"value": "更倾向于倾听",
"key": "B"
}
],
"title": "在社交场合中"
},
{
"options": [
{
"result": "J",
"value": "先研究再行动",
"key": "A"
},
{
"result": "P",
"value": "边做边学习",
"key": "B"
}
],
"title": "面对新的挑战"
},
{
"options": [
{
"result": "S",
"value": "注重细节和事实",
"key": "A"
},
{
"result": "N",
"value": "注重概念和想象",
"key": "B"
}
],
"title": "在日常生活中"
},
{
"options": [
{
"result": "T",
"value": "更多基于逻辑分析",
"key": "A"
},
{
"result": "F",
"value": "更多基于个人情感",
"key": "B"
}
],
"title": "做决定时"
},
{
"options": [
{
"result": "S",
"value": "喜欢有结构和常规",
"key": "A"
},
{
"result": "N",
"value": "喜欢自由和灵活性",
"key": "B"
}
],
"title": "对于日常安排"
},
{
"options": [
{
"result": "P",
"value": "首先考虑可能性",
"key": "A"
},
{
"result": "J",
"value": "首先考虑后果",
"key": "B"
}
],
"title": "当遇到问题时"
},
{
"options": [
{
"result": "T",
"value": "时间是一种宝贵的资源",
"key": "A"
},
{
"result": "F",
"value": "时间是相对灵活的概念",
"key": "B"
}
],
"title": "你如何看待时间"
}
]
question_results.json
[
{
"resultProp": [
"I",
"S",
"T",
"J"
],
"resultDesc": "忠诚可靠,被公认为务实,注重细节。",
"resultPicture": "icon_url_istj",
"resultName": "ISTJ(物流师)"
},
{
"resultProp": [
"I",
"S",
"F",
"J"
],
"resultDesc": "善良贴心,以同情心和责任心为特点。",
"resultPicture": "icon_url_isfj",
"resultName": "ISFJ(守护者)"
},
{
"resultProp": [
"I",
"N",
"F",
"J"
],
"resultDesc": "理想主义者,有着深刻的洞察力,善于理解他人:",
"resultPicture": "icon_url_infj",
"resultName": "INFJ(占有者)"
},
{
"resultProp": [
"I",
"N",
"T",
"J"
],
"resultDesc": "忠诚可靠,有远见,注重细节。",
"resultPicture": "icon_url_intj",
"resultName": "INTJ(建筑师)"
},
{
"resultProp": [
"I",
"S",
"T",
"P"
],
"resultDesc": "冷静自持,善于解决问题,擅长实践技能。",
"resultPicture": "icon_url_istp",
"resultName": "ISTP(运动员)"
},
{
"resultProp": [
"I",
"S",
"F",
"P"
],
"resultDesc": "忠诚可靠,善于表达,注重细节。",
"resultPicture": "icon_url_isfp",
"resultName": "ISFP(艺术家)"
},
{
"resultProp": [
"I",
"N",
"F",
"P"
],
"resultDesc": "理想主义者,善于表达,富有同情心。",
"resultPicture": "icon_url_infp",
"resultName": "INFP(调停者)"
},
{
"resultProp": [
"I",
"N",
"T",
"P"
],
"resultDesc": "理想主义者,善于解决问题,富有同情心。",
"resultPicture": "icon_url_intp",
"resultName": "INTP(逻辑学家)"
},
{
"resultProp": [
"E",
"S",
"T",
"P"
],
"resultDesc": "善于表达,善于交际,注重细节。",
"resultPicture": "icon_url_estp",
"resultName": "ESTP(企业家)"
},
{
"resultProp": [
"E",
"S",
"F",
"P"
],
"resultDesc": "善于表达,善于交际,富有同情心。",
"resultPicture": "icon_url_esfp",
"resultName": "ESFP(表演者)"
},
{
"resultProp": [
"E",
"N",
"F",
"P"
],
"resultDesc": "善于表达,善于交际,富有同情心。",
"resultPicture": "icon_url_enfp",
"resultName": "ENFP(倡导者)"
},
{
"resultProp": [
"E",
"N",
"T",
"P"
],
"resultDesc": "善于交际,善于解决问题,富有同情心。",
"resultPicture": "icon_url_entp",
"resultName": "ENTP(发明家)"
},
{
"resultProp": [
"E",
"S",
"T",
"J"
],
"resultDesc": "善于交际,注重细节,注重实践。",
"resultPicture": "icon_url_estj",
"resultName": "ESTJ(经理)"
},
{
"resultProp": [
"E",
"S",
"F",
"J"
],
"resultDesc": "善于交际,注重细节,注重实践。",
"resultPicture": "icon_url_esfj",
"resultName": "ESFJ(执政官)"
},
{
"resultProp": [
"E",
"N",
"F",
"J"
],
"resultDesc": "善于交际,注重细节,注重实践。",
"resultPicture": "icon_url_enfj",
"resultName": "ENFJ(教育家)"
},
{
"resultProp": [
"E",
"N",
"T",
"J"
],
"resultDesc": "善于交际,注重细节,注重实践。",
"resultPicture": "icon_url_entj",
"resultName": "ENTJ(指挥官)"
}
]
c. 页面逻辑
{
current > 1 &&
(<AtButton circle className="controlBtn">上一题</AtButton>)
}
{
current == question.length &&
(<AtButton type='primary' circle className="controlBtn">查看结果</AtButton>)
}
{
current < questions.length-1 &&
(<AtButton type='primary' circle className="controlBtn">下一题</AtButton>)
}
完整
import { View } from '@tarojs/components'
import GlobalFooter from '../../components/GlobalFooter/gf'
import {AtRadio,AtButton} from "taro-ui"
import questions from '../../data/questions.json'
import './index.scss'
import {useEffect, useState } from 'react'
/**
* 答题页面
*/
export default () => {
//当前题目序号(从1开始)
const [current,setCurrent] = useState<number>(1);
// 当前题目
const [currentQuestion,setCurrentQuestion] = useState(questions[0]);
// 当前答案
const [currentAnswer,setCurrentAnswer] = useState<string>();
// 答案列表
const [answerList] = useState<string[]>([])
//显示题目和选项
const questionOptions = currentQuestion.options.map(option => {
return { label: `${option.key}.${option.value}`, value: option.key };
});
//序号变化时,切换当前题目和当前选项
useEffect(() => {
setCurrentQuestion(questions[current-1]);
setCurrentAnswer(answerList[current-1]);
}, [current]);
return (
<View className='doQuestionPage'>
{ JSON.stringify(answerList)}
<View className='at-article__h1 title'>{current}、{currentQuestion.title}</View>
<View className='options-wrapper'>
<AtRadio options={questionOptions} value={currentAnswer} onClick={(value)=>{
setCurrentAnswer(value);
// 记录回答
answerList[current-1] = value;
}}/>
</View>
{
current > 1 &&
(<AtButton circle className="controlBtn" onClick={()=>setCurrent(current-1)}>上一题</AtButton>)
}
{
current == questions.length &&
(<AtButton type='primary' circle className="controlBtn"
disabled={!currentAnswer}
//TODO 传递结果给评分页面
>查看结果</AtButton>)
}
{
current < questions.length &&
(<AtButton type='primary' circle className="controlBtn"
disabled={!currentAnswer}
onClick={()=>setCurrent(current+1)}>下一题</AtButton>)
}
<GlobalFooter />
</View>
)
}
6. 查看结果页面
a. 导入题目结果表
import { View, Image } from '@tarojs/components'
import {AtButton} from "taro-ui"
import './index.scss'
import mbti from '../../assets/mbti.png'
import GlobalFooter from '../../components/GlobalFooter/gf'
import questionResults from '../../data/question_results.json'
/**
* 主页
*/
export default () => {
//结果数据表
const result = questionResults[0];
return (
<View className='resultPage'>
<View className='at-article__h1 title'>{result.resultName}</View>
<View className='at-article__h2 subTitle'>{result.resultDesc}</View>
<AtButton type='primary' circle className="enterBtn">返回主页</AtButton>
<Image className="mbti" src={mbti}/>
<GlobalFooter />
</View>
)
}
b. 主页、答题页面、结果页面跳转互动逻辑
主页
给开始测试按钮绑定事件
import { View, Image } from '@tarojs/components'
import {AtButton} from "taro-ui"
import './ex.scss'
import mbti from '../../assets/mbti.png'
import GlobalFooter from '../../components/GlobalFooter/gf'
import Taro from '@tarojs/taro'
/**
* 主页
*/
export default () => {
return (
<View className='indexPage'>
<View className='at-article__h1 title'>MBTI 性格测试</View>
<View className='at-article__h2 subTitle'>
只需2分钟,就能非常准确地描述出你是I人还是E人及你的性格特点
</View>
<AtButton type='primary' circle className="enterBtn"
onClick={() => {Taro.navigateTo({url: '/pages/doQuestion/index'})}}
>开始测试</AtButton>
<Image className="mbti" src={mbti}/>
<GlobalFooter />
</View>
)
}
做题页面
给查看结果按钮绑定事件
import { View } from '@tarojs/components'
import GlobalFooter from '../../components/GlobalFooter/gf'
import {AtRadio,AtButton} from "taro-ui"
import questions from '../../data/questions.json'
import './index.scss'
import {useEffect, useState } from 'react'
import Taro from '@tarojs/taro'
/**
* 答题页面
*/
export default () => {
//当前题目序号(从1开始)
const [current,setCurrent] = useState<number>(1);
// 当前题目
const [currentQuestion,setCurrentQuestion] = useState(questions[0]);
// 当前答案
const [currentAnswer,setCurrentAnswer] = useState<string>();
// 答案列表
const [answerList] = useState<string[]>([])
//显示题目和选项
const questionOptions = currentQuestion.options.map(option => {
return { label: `${option.key}.${option.value}`, value: option.key };
});
//序号变化时,切换当前题目和当前选项
useEffect(() => {
setCurrentQuestion(questions[current-1]);
setCurrentAnswer(answerList[current-1]);
}, [current]);
return (
<View className='doQuestionPage'>
<View className='at-article__h1 title'>{current}、{currentQuestion.title}</View>
<View className='options-wrapper'>
<AtRadio options={questionOptions} value={currentAnswer} onClick={(value)=>{
setCurrentAnswer(value);
// 记录回答
answerList[current-1] = value;
}}/>
</View>
{
current > 1 &&
(<AtButton circle className="controlBtn" onClick={()=>setCurrent(current-1)}>上一题</AtButton>)
}
{
current == questions.length &&
(<AtButton type='primary' circle className="controlBtn"
disabled={!currentAnswer}
// 传递结果给评分页面
onClick={()=>{Taro.navigateTo({url:'/pages/result/index'})}}
>查看结果</AtButton>)
}
{
current < questions.length &&
(<AtButton type='primary' circle className="controlBtn"
disabled={!currentAnswer}
onClick={()=>setCurrent(current+1)}>下一题</AtButton>)
}
<GlobalFooter />
</View>
)
}
结果页面跳转
小程序禁止有过多页面的跳转,因此这个页面的跳转事件不能和上两个页面相同
import { View, Image } from '@tarojs/components'
import {AtButton} from "taro-ui"
import './index.scss'
import mbti from '../../assets/mbti.png'
import GlobalFooter from '../../components/GlobalFooter/gf'
import questionResults from '../../data/question_results.json'
import Taro from '@tarojs/taro'
/**
* 结果页面
*/
export default () => {
//结果数据表
const result = questionResults[0];
return (
<View className='resultPage'>
<View className='at-article__h1 title'>{result.resultName}</View>
<View className='at-article__h2 subTitle'>{result.resultDesc}</View>
<AtButton type='primary' circle className="enterBtn"
onClick={() => {Taro.reLaunch({url: '/pages/ex/ex'})}}
>返回主页</AtButton>
<Image className="mbti" src={mbti}/>
<GlobalFooter />
</View>
)
}
7. 判题
判题逻辑:根据题目列表中的属性,选项对应I、S、T、J,根据选择对应的属性加分,最后遍历一遍哪个分高,即得出最后结果。
a. 根据AI写出代码
AI Prompt
请根据我下面的题目评分算法原理,帮我用 js 获取到得分最高的题目评分结果,要求算法清晰易懂,性能要求高,多补充一些注释。用户提交的答案 answerList: ["A"]
题目列表 questions:
[
{
"options": [
{
"result": "I",
"value": "独自工作",
"key": "A"
},
{
"result": "E",
"value": "与他人合作",
"key": "B"
}
],
"title": "你通常更喜欢"
},
{
"options": [
{
"result": "J",
"value": "喜欢有明确的计划",
"key": "A"
},
{
"result": "P",
"value": "更愿意随机应变",
"key": "B"
}
],
"title": "当安排活动时"
}
]
题目评分结果question_results:
[
{
"resultProp": [
"I",
"S",
"T",
"J"
],
"resultDesc": "忠诚可靠,被公认为务实,注重细节。",
"resultPicture": "icon_url_istj",
"resultName": "ISTJ(物流师)",
}
]
评分原理:答案数组的每个元素和题目数组中每个元素的某个选项的 key 对应,从而获取到对应的 result 属性;题目评分结果的 resultProp 集合中如果包含该属性,就+ 1 分,最后计算哪个评分结果 (question result) 分数最高。
AI 结果
function getMaxScore(answerList, questions, question_results) {
// 初始化答案计分板
const scoreBoard = {};
question_results.forEach(result => {
scoreBoard[result.resultName] = 0;
});
// 遍历答案列表
answerList.forEach(answer => {
// 遍历题目
questions.forEach(question => {
// 遍历题目选项
question.options.forEach(option => {
// 如果答案和选项 key 匹配
if (option.key === answer) {
// 遍历评分结果
question_results.forEach(result => {
// 如果评分结果的 resultProp 集合包含选项 result
if (result.resultProp.includes(option.result)) {
// 答案计分板对应评分结果加 1 分
scoreBoard[result.resultName] += 1;
}
});
}
});
});
});
// 找到得分最高的评分结果
let maxScore = 0;
let maxScoreResult = null;
for (const [resultName, score] of Object.entries(scoreBoard)) {
if (score > maxScore) {
maxScore = score;
maxScoreResult = resultName;
}
}
return maxScoreResult;
}
const answerList = ["A"];
const questions = [
{
options: [
{
result: "I",
value: "独自工作",
key: "A"
},
{
result: "E",
value: "与他人合作",
key: "B"
}
],
title: "你通常更喜欢"
},
{
options: [
{
result: "J",
value: "喜欢有明确的计划",
key: "A"
},
{
result: "P",
value: "更愿意随机应变",
key: "B"
}
],
title: "当安排活动时"
}
];
const question_results = [
{
resultProp: ["I", "S", "T", "J"],
resultDesc: "忠诚可靠,被公认为务实,注重细节。",
resultPicture: "icon_url_istj",
resultName: "ISTJ(物流师)"
}
];
console.log(getMaxScore(answerList, questions, question_results)); // 输出: ISTJ(物流师)
b. 调整代码写入文件
/**
* 获取最佳问题评分结果
* @param answerList 答案列表
* @param questions 题目列表
* @param question_results 结果列表
*/
export function getBestQuestionResult(answerList, questions, question_results){
// 初始化一个对象,用于存储每个选项的计数
const optionCount = {};
// 遍历答案列表
for (const answer of answerList) {
// 遍历题目列表
for (const question of questions) {
// 遍历题目中的选项
for (const option of question.options) {
// 如果答案和选项的key匹配
if (option.key === answer) {
// 获取选项的result属性
const result = option.result;
// 如果result属性不在optionCount中,初始化为0
if (!optionCount[result]) {
optionCount[result] = 0;
}
// 将计数加1
optionCount[result]++;
}
}
}
}
// 初始化最高分数和最高分数对应的评分结果
let maxScore = 0;
let maxScoreResult = question_results[0];
// 遍历评分结果列表
for (const result of question_results) {
// 计算当前评分结果的分数
const score = result.resultProp.reduce((count: number, prop: string) => {
return count + (optionCount[prop] || 0);
}, 0);
// 如果当前分数大于最高分数,更新最高分数和最高分数对应的评分结果
if (score > maxScore) {
maxScore = score;
maxScoreResult = result;
}
}
// 返回最高分数和最高分数对应的评分结果
return maxScoreResult;
}
c. 页面间数据传递
根据评分算法函数调用需要传递答案列表参数,而答案列表参数来自于做题页面,因此需要将做题页面中的数据传递给结果页面。
页面间数据传递有三种方法:
方法1: url params
比如:result?answerList=[A,B,C]
方法2:全局状态
方法3:本地数据存储(推荐,较为简单)
修改做题页面(取出)
import { View } from '@tarojs/components'
import GlobalFooter from '../../components/GlobalFooter/gf'
import {AtRadio,AtButton} from "taro-ui"
import questions from '../../data/questions.json'
import './index.scss'
import {useEffect, useState } from 'react'
import Taro from '@tarojs/taro'
/**
* 答题页面
*/
export default () => {
//当前题目序号(从1开始)
const [current,setCurrent] = useState<number>(1);
// 当前题目
const [currentQuestion,setCurrentQuestion] = useState(questions[0]);
// 当前答案
const [currentAnswer,setCurrentAnswer] = useState<string>();
// 答案列表
const [answerList] = useState<string[]>([])
//显示题目和选项
const questionOptions = currentQuestion.options.map(option => {
return { label: `${option.key}.${option.value}`, value: option.key };
});
//序号变化时,切换当前题目和当前选项
useEffect(() => {
setCurrentQuestion(questions[current-1]);
setCurrentAnswer(answerList[current-1]);
}, [current]);
return (
<View className='doQuestionPage'>
<View className='at-article__h1 title'>{current}、{currentQuestion.title}</View>
<View className='options-wrapper'>
<AtRadio options={questionOptions} value={currentAnswer} onClick={(value)=>{
setCurrentAnswer(value);
// 记录回答
answerList[current-1] = value;
}}/>
</View>
{
current > 1 &&
(<AtButton circle className="controlBtn" onClick={()=>setCurrent(current-1)}>上一题</AtButton>)
}
{
current == questions.length &&
(<AtButton type='primary' circle className="controlBtn"
disabled={!currentAnswer}
onClick={()=>{
// 传递答案
Taro.setStorageSync('answerList',answerList);
// 传递结果给评分页面
Taro.navigateTo({
url:'/pages/result/index'
})
}}
>查看结果</AtButton>)
}
{
current < questions.length &&
(<AtButton type='primary' circle className="controlBtn"
disabled={!currentAnswer}
onClick={()=>setCurrent(current+1)}>下一题</AtButton>)
}
<GlobalFooter />
</View>
)
}
修改结果页面(接收)
import { View, Image } from '@tarojs/components'
import {AtButton} from "taro-ui"
import './index.scss'
import mbti from '../../assets/mbti.png'
import GlobalFooter from '../../components/GlobalFooter/gf'
import questions from '../../data/questions.json'
import questionResults from '../../data/question_results.json'
import Taro from '@tarojs/taro'
import { getBestQuestionResult } from '../../utils/bizUtils'
/**
* 结果页面
*/
export default () => {
// 接收答案
const answerList = Taro.getStorageSync('answerList');
if(!answerList || answerList.length < 1){
Taro.showToast({
title:'答案为空,请返回重新答题',
icon:'error',
duration:3000
})
}
//获取测试结果
const result = getBestQuestionResult(answerList,questions,questionResults);
return (
<View className='resultPage'>
<View className='at-article__h1 title'>{result.resultName}</View>
<View className='at-article__h2 subTitle'>{result.resultDesc}</View>
<AtButton type='primary' circle className="enterBtn"
onClick={() => {Taro.reLaunch({url: '/pages/ex/ex'})}}
>返回主页</AtButton>
<Image className="mbti" src={mbti}/>
<GlobalFooter />
</View>
)
}