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

背景

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

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

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

然后技术要求是 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,

     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)。所以,轻喷。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容