背景
大家好,今天给大家分享一个问卷调查系统。
前不久接到一个小单子,开发一个问卷调查系统,具体要求如下:
用户端:注册,登录。填写在线调查问卷。查看填写了的问卷管理端:用户管理,编写问卷。查看填写问卷列表
然后技术要求是 Node.js +express+vue3
最近花了点时间写完,这里和大家分享一下。有需要代码的可以联系。不过不免费哦
项目演示
https://www.bilibili.com/video/BV1fUuAzUEr7/?vd_source=fa59f37655fd22e2e054559dbbe303a1
系统展示
系统首页(用户登录)
问卷显示页面
答题
我答题的问卷
查看我的答案
管理端功能
管理端首页 数据可视化
对用户管理
添加问卷
问卷信息信息统计
答案统计
部分代码
<template>
<div class="surveys-container">
<div class="page-header">
<h1>问卷列表</h1>
<p>选择您感兴趣的问卷进行填写</p>
</div>
<div class="surveys-content">
<div v-if="loading" class="loading-container">
<el-skeleton :rows="3" animated />
</div>
<div v-else-if="surveys.length === 0" class="empty-container">
<el-empty description="暂无可用问卷" />
</div>
<div v-else class="surveys-grid">
<div
v-for="survey in surveys"
:key="survey.id"
class="survey-card"
>
<el-card shadow="hover">
<div class="survey-header">
<h3>{{ survey.title }}</h3>
<div class="survey-status">
<el-tag
:type="survey.status === 'active' ? 'success' : 'info'"
size="small"
>
{{ survey.status === 'active' ? '进行中' : '已结束' }}
</el-tag>
<el-tag
v-if="survey.userCompleted"
type="warning"
size="small"
style="margin-left: 5px;"
>
已完成
</el-tag>
</div>
</div>
<div class="survey-description">
<p>{{ survey.description || '暂无描述' }}</p>
</div>
<div class="survey-meta">
<div class="meta-item">
<el-icon><User /></el-icon>
<span>创建者: {{ survey.creator_name }}</span>
</div>
<div class="meta-item">
<el-icon><Calendar /></el-icon>
<span>创建时间: {{ formatDate(survey.created_at) }}</span>
</div>
</div>
<div class="survey-actions">
<el-button
type="primary"
@click="viewSurvey(survey.id)"
:disabled="survey.status !== 'active'"
>
查看详情
</el-button>
<el-button
v-if="!survey.userCompleted"
type="success"
@click="takeSurvey(survey.id)"
:disabled="survey.status !== 'active'"
>
开始填写
</el-button>
<el-button
v-else
type="info"
@click="viewMyAnswer(survey.id)"
plain
>
查看我的回答
</el-button>
</div>
</el-card>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapActions, mapState } from 'vuex'
import { User, Calendar } from '@element-plus/icons-vue'
export default {
name: 'Surveys',
components: {
User,
Calendar
},
computed: {
...mapState('surveys', ['surveys', 'loading'])
},
methods: {
...mapActions('surveys', ['fetchSurveys', 'checkUserSurveyStatus']),
formatDate(dateString) {
return new Date(dateString).toLocaleDateString('zh-CN')
},
viewSurvey(surveyId) {
this.$router.push(`/surveys/${surveyId}`)
},
takeSurvey(surveyId) {
this.$router.push(`/surveys/${surveyId}/take`)
},
viewMyAnswer(surveyId) {
this.$router.push(`/surveys/${surveyId}/my-answer`)
},
async checkSurveysCompletionStatus() {
// 检查所有问卷的完成状态
for (const survey of this.surveys) {
try {
const hasCompleted = await this.checkUserSurveyStatus(survey.id)
// Vue 3中直接赋值
survey.userCompleted = hasCompleted
} catch (error) {
console.error(`检查问卷 ${survey.id} 完成状态失败:`, error)
}
}
}
},
async mounted() {
await this.fetchSurveys()
// 检查每个问卷的完成状态
await this.checkSurveysCompletionStatus()
},
async beforeRouteUpdate(to, from, next) {
if (from.path.includes('/take/')) {
// 从填写页面返回,重新加载状态
await this.fetchSurveys()
await this.checkSurveysCompletionStatus()
}
next()
}
}
</script>
<style scoped>
.surveys-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.page-header {
text-align: center;
margin-bottom: 30px;
}
.page-header h1 {
color: #333;
margin-bottom: 10px;
}
.page-header p {
color: #666;
font-size: 16px;
}
.surveys-content {
min-height: 400px;
}
.loading-container {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.empty-container {
background: white;
padding: 40px;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.surveys-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 20px;
}
.survey-card {
height: 100%;
}
.survey-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.survey-header h3 {
margin: 0;
color: #333;
font-size: 18px;
}
.survey-status {
display: flex;
align-items: center;
}
.survey-description {
margin-bottom: 15px;
min-height: 40px;
}
.survey-description p {
color: #666;
line-height: 1.5;
margin: 0;
}
.survey-meta {
margin-bottom: 20px;
}
.meta-item {
display: flex;
align-items: center;
color: #999;
font-size: 14px;
margin-bottom: 8px;
}
.meta-item .el-icon {
margin-right: 5px;
}
.survey-actions {
display: flex;
gap: 10px;
justify-content: flex-end;
}
/* 响应式设计 */
@media (max-width: 768px) {
.surveys-container {
padding: 15px;
}
.surveys-grid {
grid-template-columns: 1fr;
}
.survey-actions {
flex-direction: column;
}
.survey-actions .el-button {
width: 100%;
}
}
</style>
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
// 用户注册
exports.register = async (req, res) => {
try {
const { username, password, email } = req.body;
// 检查用户是否已存在
const existingUser = await User.findByUsername(username);
if (existingUser) {
return res.status(400).json({ message: '用户名已存在' });
}
// 创建新用户(密码不加密)
const result = await User.create({
username,
password,
});
res.status(201).json({
message: '注册成功',
userId: result.insertId
});
} catch (error) {
console.error('注册错误:', error);
res.status(500).json({ message: '服务器错误' });
}
};
// 用户登录
exports.login = async (req, res) => {
try {
const { username, password } = req.body;
// 查找用户
const user = await User.findByUsername(username);
if (!user) {
return res.status(401).json({ message: '用户名或密码错误' });
}
// 验证密码(不加密比较)
if (user.password !== password) {
return res.status(401).json({ message: '用户名或密码错误' });
}
// 生成JWT token
const token = jwt.sign(
{
userId: user.id,
username: user.username,
isAdmin: user.is_admin
},
JWT_SECRET,
{ expiresIn: '24h' }
);
res.json({
message: '登录成功',
token,
user: {
id: user.id,
username: user.username,
email: user.email,
isAdmin: user.is_admin
}
});
} catch (error) {
console.error('登录错误:', error);
res.status(500).json({ message: '服务器错误' });
}
};
// 获取用户信息
exports.getProfile = async (req, res) => {
try {
const user = await User.findById(req.user.userId);
if (!user) {
return res.status(404).json({ message: '用户不存在' });
}
res.json({
id: user.id,
username: user.username,
email: user.email,
isAdmin: user.is_admin,
createdAt: user.created_at
});
} catch (error) {
console.error('获取用户信息错误:', error);
res.status(500).json({ message: '服务器错误' });
}
};
全职创业(失业)程序员,到处接单,什么单子都接,语言也不限,java,python,安卓,前端,鸿蒙都来。
上面的项目也许有人不屑接。但是,35的坎我是遇到了(虽然还没有35)。所以,轻喷。