2026山东大学软件学院项目实训个人blog(三)
2026山东大学软件学院项目实训个人blog三目录一、写在前面二、页面设计如何让用户愿意填完一份长问卷2.1 核心痛点分析2.2 分组渐进式答题2.3 进度可视化2.4 自动保存——解决“填了一半不小心关了”的痛点2.5 结果可视化2.6 美观性细节三、核心技术代码解析3.1 API接口封装3.2 状态管理核心逻辑四、前后端联调经验4.1 接口数据格式约定4.2 跨域配置4.3 踩坑与解决五、总结一、写在前面这次做的是中医智能体的体质评估功能核心任务是让用户通过填写问卷系统自动判断其属于哪种中医体质类型平和质、气虚质、阳虚质等九种。接手这个需求时我面临一个问题九种体质、六十多道题目怎么让用户愿意填完而不是中途放弃下面我从页面设计、状态管理、前后端联调三个方面分享一下我的做法和思考。二、页面设计如何让用户愿意填完一份长问卷2.1 核心痛点分析中医体质问卷有个天然劣势题目多。标准版本有60道题如果全部堆在一个页面上用户大概率看一眼就关掉了。所以我的核心设计目标是——把用户的认知负担降到最低。2.2 分组渐进式答题我的方案是按九种体质将题目分组展示而不是一次性全抛出来。用户一次只面对一个体质组的题目约6-8道心理压力小很多。左侧做一个体质导航栏列出全部九种体质右侧展示当前选中体质的题目。每组旁边显示完成进度如“5/8”用户能有很强的掌控感也可以自由跳转到任何一组。2.3 进度可视化顶部做了整体进度条每完成一组就往前走一步。底部操作栏放“上一组”“下一组”按钮逻辑简单直接用户不需要思考下一步该干嘛。这种正反馈会推动他继续往下做。2.4 自动保存——解决“填了一半不小心关了”的痛点这是我作为用户时最痛的点。我做了答案自动暂存到localStorage每选一道题就存一次。哪怕用户刷新页面或不小心关掉浏览器下次进来答案还在直接接着填就行。2.5 结果可视化评估完成后结果页用雷达图展示九种体质的得分对比。主要体质会单独用大卡片展示配上体质图标、详细解读和调理建议。如果有历史记录还会展示与前一次的对比变化曲线。2.6 美观性细节配色用了中式美学墨绿、檀棕、米白温润内敛。卡片用了16px圆角和柔和阴影选项按钮hover时有轻微放大效果这些细节让页面用起来更舒服。三、核心技术代码解析3.1 API接口封装// frontend/src/api/constitution.jsconstconstitutionApi{// 获取问卷题目getQuestions(){returnrequest.get(/constitution/questions)},// 提交评估evaluate(data){returnrequest.post(/constitution/evaluate,data)},// 获取历史记录getHistory(profileId,limit10){returnrequest.get(/constitution/profiles/${profileId}/history,{params:{limit}})},// 获取变化趋势getTrend(profileId){returnrequest.get(/constitution/profiles/${profileId}/trend)},// 对比两次评估compare(recordId1,recordId2){returnrequest.post(/constitution/compare,{record_id_1:recordId1,record_id_2:recordId2})}}设计思路把所有接口调用集中管理每个接口语义清晰。组件里只关心调用哪个接口、传什么参数不关心请求细节。这样后端接口改了只需要改这一个文件。3.2 状态管理核心逻辑// frontend/src/store/constitutionStore.jsexportconstuseConstitutionStoredefineStore(constitution,(){// 状态 constquestionsref([])// 所有题目constconstitutionTypesref([])// 体质类型定义constcurrentEvaluationref(null)// 当前评估结果consthistoryListref([])// 历史记录// 计算属性按体质分组 constgroupedQuestionscomputed((){constgroups{}questions.value.forEach(q{constcodeq.constitution_codeif(!groups[code])groups[code][]groups[code].push(q)})returngroups})// 加载题目带缓存constloadQuestionsasync(){if(questions.value.length0)return// 已加载则跳过constresawaitconstitutionApi.getQuestions()questions.valueres.data.questions||[]constitutionTypes.valueres.data.constitution_types||[]}// 提交评估 constsubmitEvaluationasync(profileId,basicInfo,answers,freeText){constresawaitconstitutionApi.evaluate({profile_id:profileId,basic_info:basicInfo,answers:answers,free_text:freeText})currentEvaluation.valueres.data.datareturncurrentEvaluation.value}// 自动保存到本地 constsaveAnswersToLocalStorage(profileId,answers){localStorage.setItem(constitution_answers_${profileId},JSON.stringify(answers))}// 辅助方法 constgetConstitutionName(code){returnconstitutionTypes.value.find(cc.codecode)?.name||code}})设计思路groupedQuestions是核心计算属性把后端返回的扁平题目列表按体质分组组件直接循环这个分组就能渲染不用自己处理分组逻辑loadQuestions加了缓存判断避免组件重复加载浪费请求自动保存用profileId区分不同用户页面刷新后能精准恢复自己的答案四、前后端联调经验4.1 接口数据格式约定开始开发前我和后端对齐了格式请求体提交评估{profile_id:123,basic_info:{age:30,gender:male},answers:{1:3,2:4},free_text:平时容易疲劳}响应体评估结果{code:0,data:{primary_constitutions:[qixu,yangxu],scores:{pinghe:65,qixu:82},recommendations:{diet:[],exercise:[]}}}4.2 跨域配置// vite.config.jsexportdefault{server:{proxy:{/api:{target:http://localhost:8080,changeOrigin:true}}}}4.3 踩坑与解决问题解决方案历史记录越积越多加limit参数只取最近10条60道题一次性渲染卡顿分组渲染一次只渲染当前体质组重复调用loadQuestions加缓存判断已加载则跳过用户意外关闭页面丢失答案localStorage自动暂存五、总结这个体质评估模块的核心设计思路页面设计分组渐进式答题 自动保存 进度可视化核心是降低用户认知负担状态管理用computed处理分组逻辑用缓存避免重复请求接口对接API层集中管理格式提前约定代理解决跨域