ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Vue和Django/Flask的问卷调查系统设计与实现全解析

基于Vue和Django/Flask的问卷调查系统设计与实现全解析 做“大学生问卷调查系统”这类题目在课程设计和毕业设计里出现频率相当高。表面上看它只是一个增删改查的经典管理系统但真要交付一套能演示、能答辩、能写进论文的系统从前端交互到后端接口再到数据统计每一层都有值得仔细打磨的地方。这篇文章就以“Python 基于 Vue 的大学生问卷调查系统的设计与实现”为线索把 Django 和 Flask 两条技术路线的取舍、PyCharm 的开发环境配置、问卷系统里最容易被忽视的数据建模细节都过一遍。无论你是刚开始学 Web 开发的新手还是已经写过几个小项目、想系统梳理全栈流程的进阶者都可以照着这条链路复现一遍。我会尽量把里面“为什么这么做”的原因也讲清楚毕竟答辩时老师问的最多的就是“你怎么想到选这个方案的”。我给这类项目定过一个基本判断标准不求功能花哨但整个链路必须是通的——学生能登录、能创建问卷、能发布、别人能填写、后台能看到统计结果这五件事串起来就已经是一个完整可演示的项目了。下面的内容就是围绕这条主链路展开的。1. 整体设计需求拆解与技术选型1.1 问卷调查系统到底要解决什么问题先把需求看清楚再谈技术。大学生问卷调查系统最常见的业务场景是学生会、社团或者老师在学生群体里发起调查比如课程满意度、食堂服务评价、活动反馈等。这类调查有几个共同特点问卷数量不固定题目类型多样参与者数量可能短时间内激增而且组织者往往需要尽快看到统计结果。所以系统核心要处理的无非是三件事问卷的创建与管理、问卷的发布与填写、数据的汇总与展示。不要一上来就想着做权限矩阵、做消息推送那些都属于加分项。基础版把这三件事做扎实就已经超越大多数停留在“能运行”层面的同类项目了。角色设计上我建议至少要分清两类人管理员或者叫问卷创建者和普通参与者。管理员负责创建问卷、发布问卷、查看统计参与者负责填写问卷。登录方式可以做成简单的账号密码不一定要接第三方登录。如果后续想扩展再考虑做成微信扫码登录或者邮箱验证也不迟。1.2 Vue Django/Flask 这套组合为什么合理选型上标题里同时出现了 Django 和 Flask这其实是很多人在最终答辩前纠结过的问题。我的建议很简单想快速把完整功能跑起来、又不排斥框架自带的后台管理界面选 Django想把接口和逻辑完全掌控在自己手里或者对性能有更高要求选 Flask。两者都能和 Vue 配合得很好区别只是后端代码的组织方式和开发速度。Django 的优势在于“全家桶”自带 ORM、自带 Admin 后台、自带表单处理创建问卷这种需要大量表单校验的场景Django 能省掉很多重复代码。而且python manage.py startapp一键创建子应用结构清晰写论文时也方便描述项目分层。我见过不少毕业设计用 Django主要原因就是它能让团队里每个人都快速上手不用重复造轮子。Flask 的优势则是“轻”。它不像 Django 那样强制你用一套固定的项目结构问卷系统这种规模的项目Flask 单文件或者分蓝图的写法都很灵活。另外如果你打算在小主机上部署Flask 的运行时开销更小。但代价是数据库迁移、表单验证、Admin 这些都要自己找第三方库拼装开发体验没那么“顺手”。前端使用 Vue 则是因为问卷系统天然适合组件化开发每一道题目可以抽象成一个组件单选、多选、填空、评分各自独立封装再通过一个问卷容器组件组合起来。这种交互密集的页面用 Vue 比用服务端模板渲染顺手太多了。再加上 Vue 的响应式机制填写者的实时校验、题目之间的联动显示比如选了“是”才会显示后续问题实现起来非常省事。1.3 PyCharm 在开发链路里的定位很多新手容易忽略开发环境直接写代码结果在包依赖、解释器版本上折腾掉一晚上。PyCharm 在这条链路里的作用不是“编辑器”这么简单它要做的事包括管理虚拟环境、识别 Django/Flask 项目结构、调试接口、甚至直接跑 Vue 的 npm 命令。建议用 Community 版本就可以了没必要纠结“激活”的事社区版对 Django 和 Flask 的支持已经足够。创建项目后第一步要做的是在 PyCharm 里配置 Python 解释器选New environment创建虚拟环境。这一步很关键因为虚拟环境能把项目的依赖和系统 Python 隔离开避免和别的项目互相干扰。前端代码我一般不直接用 PyCharm 写 Vue 单文件组件而是在 PyCharm 里开终端跑npm run serve用浏览器预览效果。但 PyCharm 对 Vue 文件也有语法高亮和自动补全前提是安装了对应的插件并启用了 Node.js 支持。如果你更习惯 WebStorm 的体验可以把两个工具配合着用——后端调试在 PyCharm前端开发在 WebStorm这属于舒适区问题。2. 功能模块与数据建模动手之前先把地基打牢2.1 核心功能模块拆解把业务需求翻译成功能模块是很多人容易跳过的步骤但我强烈建议你画一张简单的模块图或者哪怕在纸上写几个大字再动手。问卷系统至少需要以下几个模块用户模块注册、登录、退出。管理员和普通学生可以共用一张用户表用字段区分角色。问卷管理模块创建问卷、编辑问卷、发布/下架问卷、删除问卷。问卷的基本信息包括标题、说明、截止时间。题目管理模块在一个问卷下维护题目列表支持单选、多选、填空、评分等题型还要能调整题目顺序。填写模块参与者打开问卷链接后填写并提交系统要保证同一人对同一问卷只能提交一次或限制提交次数。统计模块当问卷有结果后按题汇总选项占比以图表形式展示必要时还能导出原始数据为 Excel。这五个模块对应到页面大致是登录注册页、问卷列表页、问卷编辑器创建题目、问卷填写页、结果统计页。整个系统的复杂度和工作量基本就固定在这个范围内了评估自己一天能写完多少功能的时候可以按这个清单去拆。2.2 数据库表设计Django ORM 版数据建模是问卷系统里最不该偷懒的部分。我见过很多同学的方案是“把答案存成一整段 JSON 塞进一个字段”这种做法在演示时没问题但一旦要做选项占比统计写起来就非常痛苦。正确的做法是把问卷、题目、选项、答案分层建模。以 Django 为例核心模型我们可以这样设计from django.db import models from django.contrib.auth.models import User class Survey(models.Model): title models.CharField(问卷标题, max_length200) description models.TextField(问卷描述, blankTrue, default) creator models.ForeignKey(User, on_deletemodels.CASCADE, verbose_name创建者) status models.SmallIntegerField(状态, choices((0, 未发布), (1, 进行中), (2, 已结束)), default0) start_at models.DateTimeField(开始时间, nullTrue, blankTrue) end_at models.DateTimeField(截止时间, nullTrue, blankTrue) created_at models.DateTimeField(创建时间, auto_now_addTrue) class Meta: db_table survey verbose_name 问卷 class Question(models.Model): survey models.ForeignKey(Survey, on_deletemodels.CASCADE, related_namequestions, verbose_name所属问卷) title models.CharField(题目标题, max_length255) question_type models.SmallIntegerField(题目类型, choices((1, 单选), (2, 多选), (3, 填空), (4, 评分))) is_required models.BooleanField(是否必答, defaultTrue) sort_order models.IntegerField(排序值, default0) class Meta: db_table question ordering [sort_order]注意我把问卷和题目用外键关联并且给questions设置了related_name这样通过survey.questions.all()就能拿到题目列表非常方便。题目不分表而是用question_type字段区分题型这样对前后端联调也简单前端根据题型渲染不同组件后端根据题型做不同的校验逻辑。接下来是填答记录。这里有两种主流设计一种是“主表 明细表”提交一条记录时往两张表同时插入数据另一种是“答案表内嵌 JSON”。我倾向于前者同时用一个answerJSON 字段做冗余存储——这样既方便统计又方便查看原始答案。class Submission(models.Model): survey models.ForeignKey(Survey, on_deletemodels.CASCADE, related_namesubmissions, verbose_name所属问卷) user models.ForeignKey(User, on_deletemodels.CASCADE, verbose_name填写人) submitted_at models.DateTimeField(提交时间, auto_now_addTrue) answer_data models.JSONField(答案数据, defaultdict)answer_data的结构可以设计为{question_id: 选项ID 或 文本内容}。比如单选的题目 ID 是 5用户选了选项 3就存{5: 3}填空是{6: 我觉得可以更辣一点}。统计模块只需要遍历问卷下的所有题目再逐条读取answer_data对应的值就能完成汇总。2.3 API 接口怎么设计前后端分离的项目接口设计决定了工作量。问卷系统接口数量不多按照 RESTful 风格来写就行POST /api/register注册POST /api/login登录GET /api/surveys/当前用户创建的问卷列表POST /api/surveys/创建问卷GET /api/surveys/{id}/问卷详情含题目列表PUT /api/surveys/{id}/更新问卷与题目DELETE /api/surveys/{id}/删除问卷POST /api/surveys/{id}/publish发布问卷GET /api/public/surveys/{id}/填写页的问卷信息不需要登录POST /api/public/surveys/{id}/submit提交答卷GET /api/surveys/{id}/stats/获取统计结果在 Django 里实现这些接口最简单的方式是直接用JsonResponserequire_http_methods也可以用 Django REST Framework。如果你只有一两周时间我不推荐引入 DRF它的序列化器和视图集需要额外学习成本而且这个项目的接口复杂度用不上那么重的抽象。直接用 Django 原生视图写反而更直观答辩时也能说得清楚每一步在干什么。3. 实操过程从空目录到前后端联调3.1 环境准备与项目初始化先统一版本环境。这步别图省事我自己的标准操作是先确认 Python 版本推荐 3.10 或 3.11再创建虚拟环境然后装依赖。PyCharm 里新建项目时选择虚拟环境后后面的pip install都会装在项目内部不会污染系统环境。后端依赖用pip install django django-cors-headers即可。如果你选 Flask 路线则是pip install flask flask-cors flask-sqlalchemy flask-migrate。注意 Flask 的数据库迁移需要安装flask-migrate否则后面改表结构时会非常痛苦。前端用 Vue 脚手架初始化npm create vuelatest survey-front cd survey-front npm install npm install axios element-plus在 Vue 3 项目里我一般用 Element Plus 组件库它的表单、对话框、表格、上传组件都能直接拿过来用能省大量时间。如果不喜欢组件库也可以只用原生表单元素但开发速度会慢不少。启动顺序建议是先启动后端 Django再启动前端 Vue。Django 默认跑在 8000 端口Vue 脚手架默认跑在 5173 端口。因为前端和后端端口不同跨域问题几乎必然出现所以我们在后端一定要装上django-cors-headers并在settings.py里配置允许的来源INSTALLED_APPS [ ... corsheaders, ] MIDDLEWARE [ corsheaders.middleware.CorsMiddleware, ... ] CORS_ALLOWED_ORIGINS [ http://localhost:5173, ]Flask 下的做法是from flask_cors import CORS CORS(app, resources{r/api/*: {origins: http://localhost:5173}})3.2 后端实现创建 app 与核心接口创建 Django 项目和应用django-admin startproject survey_backend cd survey_backend python manage.py startapp survey_api记得把survey_api加进INSTALLED_APPS然后还要做数据库迁移python manage.py makemigrations python manage.py migrate这里有个经常被忽略的点如果你修改了模型字段但又忘了makemigrations启动项目时模型和数据库表就会出现不一致接口一跑就报错。所以养成习惯——改完模型立刻迁移。登录接口可以用 Django 自带的authenticate和login也可以自己写一套 Token 方案。我建议简单点用 Django session 或者自定义一个轻量 Token 都行。核心代码如下import json from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.contrib.auth import authenticate, login from .models import Survey, Question, Submission csrf_exempt def api_login(request): if request.method ! POST: return JsonResponse({code: 1, msg: method not allowed}) data json.loads(request.body) user authenticate(usernamedata.get(username), passworddata.get(password)) if user is not None: login(request, user) return JsonResponse({code: 0, data: {username: user.username, role: admin if user.is_staff else user}}) return JsonResponse({code: 1, msg: 用户名或密码错误})创建问卷时一个比较复杂的问题是题目列表的保存。前端传过来的数据结构可以设计为{ title: 课程满意度调查, description: 这是一份关于课程质量的问卷, questions: [ {title: 你对这门课的整体评分是, question_type: 4, is_required: true, sort_order: 1}, {title: 最喜欢的教学环节是, question_type: 2, is_required: true, sort_order: 2} ] }后端拿到这个结构后先创建Survey对象再循环创建Question对象并绑定外键。保存逻辑并不复杂但要注意用事务包裹避免中途失败导致“问卷创建了但题目只存了一半”的脏数据from django.db import transaction csrf_exempt transaction.atomic def create_survey(request): if request.method ! POST: return JsonResponse({code: 1, msg: method not allowed}) data json.loads(request.body) survey Survey.objects.create( titledata[title], descriptiondata.get(description, ), creatorrequest.user ) for q in data.get(questions, []): Question.objects.create( surveysurvey, titleq[title], question_typeq[question_type], is_requiredq.get(is_required, True), sort_orderq.get(sort_order, 0) ) return JsonResponse({code: 0, data: {survey_id: survey.id}})3.3 前端 Vue 实现页面组件与请求封装Vue 部分的重点在于页面路由和组件划分。按功能拆成这些页面Login、Register、SurveyList、SurveyEdit、SurveyFill、SurveyStats。然后在router/index.js里配置路由const routes [ { path: /, redirect: /surveys }, { path: /login, component: Login }, { path: /surveys, component: SurveyList }, { path: /surveys/edit/:id?, component: SurveyEdit }, { path: /fill/:id, component: SurveyFill }, { path: /surveys/:id/stats, component: SurveyStats }, ]这里的SurveyEdit是核心页面同时承担“新建”和“编辑”两种职责通过路由参数id是否存在来判断。问卷编辑器里我用 Element Plus 的动态表单来实现题目的增删排序每道题渲染成一行卡片类型下拉框切换时对应不同的编辑控件。问卷填写页是参与者看到的面孔它要优雅地处理各种题型。对应的题目组件可以按question_type分发template el-form-item :labelquestion.title :requiredquestion.is_required el-radio-group v-ifquestion.question_type 1 v-modelanswer el-radio v-foropt in options :keyopt.value :labelopt.value{{ opt.label }}/el-radio /el-radio-group el-checkbox-group v-else-ifquestion.question_type 2 v-modelanswer el-checkbox v-foropt in options :keyopt.value :labelopt.value{{ opt.label }}/el-checkbox /el-checkbox-group el-input v-else-ifquestion.question_type 3 v-modelanswer typetextarea / el-rate v-else-ifquestion.question_type 4 v-modelanswer / /el-form-item /template答题选项我通常不单独建表而是直接塞在Question模型的一个 JSON 字段里。如果不愿意加字段就写入title旁边的新字段options。这样读取题目时选项和题型一起返回前端渲染逻辑就非常简洁。axios 请求需要统一封装比如设置baseURL、携带 Cookie 或 Token、统一处理错误码。这个封装一旦写好后面所有页面都会用到import axios from axios const http axios.create({ baseURL: http://127.0.0.1:8000/api, timeout: 10000, }) http.interceptors.request.use(config { config.headers[X-Requested-With] XMLHttpRequest return config }) http.interceptors.response.use(res { if (res.data.code ! 0) { ElMessage.error(res.data.msg || 请求失败) return Promise.reject(new Error(res.data.msg)) } return res.data.data }, err { ElMessage.error(网络异常请检查后端服务是否启动) return Promise.reject(err) }) export default http3.4 前后端联调与细节处理前后端联调阶段最常见的现象是“前端拿到数据了但界面不显示”“登录成功了下次刷新又掉登录状态”。这些基本都和安全机制有关。Django 默认开启了 CSRF 防护但前后端分离项目里Cookie 里没有 CSRF Token 会导致 POST 请求被拒绝。我上面的例子里直接用了csrf_exempt这是从便捷性出发的选择。如果希望保留 CSRF 防护可以在前端拿到 Django 设置的csrftoken后在 axios 拦截器里加到请求头。但为了课程设计和答辩演示的流畅度用csrf_exempt配合 Session 登录已经足够。刷新后掉登录状态这个问题多半是前端没有在请求里携带 Cookie。检查 axios 的withCredentials字段const http axios.create({ baseURL: http://127.0.0.1:8000/api, withCredentials: true, })同时后端 Django 的CORS_ALLOW_CREDENTIALS True也要开启并且不能使用CORS_ALLOW_ALL_ORIGINS True必须指定具体的CORS_ALLOWED_ORIGINS。两者配合登录态的维持和跨域资源访问都能正常工作。统计接口是问卷系统的亮点也最容易在答辩时被追问。以单选题目为例后端统计逻辑可以写成def get_question_stats(survey_id): from collections import Counter questions Question.objects.filter(survey_idsurvey_id) submissions Submission.objects.filter(survey_idsurvey_id) stats {} for q in questions: if q.question_type 1: # 单选 counter Counter() for sub in submissions: ans sub.answer_data.get(str(q.id)) if ans is not None: counter[str(ans)] 1 stats[q.id] dict(counter) elif q.question_type 4: # 评分 values [] for sub in submissions: ans sub.answer_data.get(str(q.id)) if ans is not None: values.append(int(ans)) stats[q.id] { avg: sum(values) / len(values) if values else 0, count: len(values) } return stats前端拿到这些结构化数据后用 ECharts 的饼图、柱状图去渲染。这里说一句题外话问卷统计页面是整个项目里性价比最高的功能因为它视觉效果强还涉及真正的数据处理逻辑往往也是答辩老师最关注的部分。4. 常见问题与排查实录4.1 跨域、端口、中文乱码这类问题根治办法写这类项目时很多报错其实是环境问题不是代码逻辑问题。这里我把高频问题集中整理一遍。跨域报错浏览器控制台出现 Access-Control-Allow-Origin 的话第一反应是去后端检查 CORS 配置。Django 项目确认django-cors-headers装没装Flask 项目确认flask-cors是否生效。最常见的坑是配置写在了错误的文件或者没重启服务因为改完配置后必须重启后端进程才生效。前端请求 404检查前端发的请求地址和后端实际路由是否一致。路径里多一个斜杠、少一个斜杠都很常见。Django 对尾部斜杠要求比较严格RESTful 接口建议统一风格/api/surveys/带斜杠避免混乱。中文乱码JSON 响应里的中文如果变成\uXXXX这不是乱码只是 JSON 序列化的转义效果。在 Django 的settings.py里设置JSON_AS_ASCII False可以让接口直接返回中文。如果是数据库表里的中文乱码那是字符集问题建库时用 UTF-8 并确保连接串也指定了 UTF-8。端口占用8000 端口被占用时可以用python manage.py runserver 8001换端口。前端 5173 端口也可以改npm run serve -- --port 3000。别在端口问题上耗太多时间直接换端口是最高效的方案。4.2 开发过程中踩过的几个坑第一坑提交答卷没有做重复性校验。问卷系统很容易出现一个人反复提交的问题。最简单的处理是在Submission表上给survey和user加联合唯一约束。Django 里这么写class Meta: constraints [ models.UniqueConstraint(fields[survey, user], nameunique_submission) ]如果需求允许修改答案那就改成“存在则更新不存在则创建”用update_or_create就能实现。第二坑问卷发布时间和截止时间没有校验。发布问卷时前端按钮叫“发布”但如果不做状态判断提交答卷的接口可能连已结束的问卷也照单全收。在后端提交接口里加上这样的判断from django.utils import timezone survey Survey.objects.get(idsurvey_id) if survey.status ! 1: return JsonResponse({code: 1, msg: 问卷不在进行中无法填写}) if survey.end_at and survey.end_at timezone.now(): return JsonResponse({code: 1, msg: 问卷已截止})第三坑修改问卷时的题目增量更新。编辑问卷后前端把整个题目列表提交过来后端需要判断哪些题目是新增的、哪些是需要更新的、哪些是被删除的。一个稳妥的笨方法是先删除该问卷所有旧题目再按新列表重建。数据量小重来一遍代价不高而且逻辑简单不会出 bug。这个方法在很多管理系统里都被低估它省掉一堆 diff 逻辑Question.objects.filter(surveysurvey).delete() for q in data.get(questions, []): Question.objects.create(surveysurvey, ...)第四坑PyCharm 运行时提示没有 Django 支持或者找不到模型关系。这通常是虚拟环境和解释器配置的问题。检查 PyCharm 右上角解释器是不是项目虚拟环境最简单的方式是打开终端看命令行前缀是否有(venv)。没有的话到Settings - Project - Python Interpreter里重新选择。4.3 扩展思路统计可视化与导出功能怎么往上加如果想让项目在答辩时更有亮点可以再往后延伸两步。导出 Excel是一个很实际的功能。Django 可以用openpyxl生成.xlsx文件返回下载Flask 则可以用pandas的to_excel配合BytesIO。前端只需要拿到返回的文件流触发浏览器下载即可window.open(http://127.0.0.1:8000/api/surveys/${id}/export/, _blank)实时刷新是另一个加分项。如果填写者提交问卷后管理员页面的统计图表能自动更新观感会非常好。简单方案就是前端定时轮询比如每 30 秒重新请求一次统计接口复杂一点的方案用 WebSocket 或者 Server-Sent Events。对于课程设计轮询已经足够还能顺带讲讲自己的实现思路。问卷模板复用也可以考虑把一套常用问题保存为模板建新问卷时一键加载。这个功能主要考验组件复用和数据拷贝逻辑写起来不难但能让系统看起来更完整。最后的经验回到最开始说的那条链路创建问卷、发布、填写、统计。整个系统真正难的点从来不是某个框架的某个 API而是你如何把数据从创建者手里一路流转到填写者手里最终变成图表上的一根柱子、一个百分比。我个人在完成这类项目后有个习惯在数据库里插入一条真实的模拟数据手动跑一遍完整流程再观察哪些接口慢、哪些操作不符合直觉。问卷系统这种偏业务型的项目一旦业务逻辑理清楚剩下的技术细节反而都是水到渠成的事。希望这篇文章能把你的系统从“能跑”推到“讲得清楚、经得起问”的位置上。
返回列表