一个简单的问卷调查系统(Node+Vue3+element-plus)

124 阅读4分钟

一个简单的问卷调查系统(Node+Vue3+element-plus)

背景

大家好,今天给大家分享一个问卷调查系统。

前不久接到一个小单子,开发一个问卷调查系统,具体要求如下:

用户端:注册,登录。填写在线调查问卷。查看填写了的问卷 管理端:用户管理,编写问卷。查看填写问卷列表

然后技术要求是 Node.js +express+vue3

最近花了点时间写完,这里和大家分享一下。有需要代码的可以联系。不过不免费哦

项目演示

[# 问卷调查 Node+express+vue3+element-plus-哔哩哔哩(www.bilibili.com/video/BV1fU…)

系统展示

系统首页(用户登录)

image.png

image.png

问卷显示页面

image.png

答题

image.png

我答题的问卷

image.png

查看我的答案

image.png

管理端功能

管理端首页 数据可视化

image.png

对用户管理

image.png

添加问卷

image.png

问卷信息信息统计

image.png

答案统计

image.png

部分代码
<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,
      email
    });
​
    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)。所以,轻喷。