uniapp + node.js 开发问卷调查小程序

前后端效果图
在这里插入图片描述
后端:nodejs 12.8 ; mongoDB 4.0
前端:uniapp
开发工具:HBuilderX 3.99

  • 前端首页代码 index.vue
<!-- 源码下载地址  https://pan.baidu.com/s/1AVB71AjEX06wpc4wbcV_tQ?pwd=l9zp -->

<template>
	<view class="container">
		<view class="content">
			<view class="question" v-for="(item,index) in qusetionList" :key='index'>
				<view class="question_header">
					<view class="header_title">
						{{item.subjectContent}}
						<text style="font-weight: 500;">({{item.type==0?'单选':'多选'}})</text>
					</view>
				</view>
				<view class="question_option">
					<view :class="{option_item:true,active_option:items.id==items.active}"
						v-for="(items,indexs) in item.optionList" :key='indexs' @tap.stop="optionItem(items)">
						<view class="option_box">
							<image src="@/static/hook.png" mode=""></image>
						</view>
						<text>{{items.optionContent}}</text>
					</view>
				</view>
			</view>
			<view style="height: 180rpx;">
				<!-- 占位框,避免内容被提交按键遮挡 -->
			</view>
		</view>
		<!-- 底部提交按键,@tap.stop阻止冒泡事件 -->
		<view class="submit_box" @longpress="goAdmin">
			<button class="sub_btn" type="default" @tap.stop="subQuestion">提交</button>
		</view>
	</view>
</template>

<script>
	export default {
		data() {
			return {
				baseUrl:'',
				active: 0,
				qusetionList: [],
			}
		},

		onLoad() {
			// 获取全局变量 baseUrl
			this.baseUrl = getApp().globalData.baseUrl;
			// 调用方法
			this.getData()
		},

		methods: {
			//获取用户信息
			getUserInfo(param) {},
			
			//获取题目、选项
			getData() {
				uni.request({
					url: this.baseUrl + 'query',
					method: "GET",
					data: {},
					success: (res) => {
						var arr =res.data.dataArr
						var dataList = arr.sort(this.compare('sort')) //按对象内的sort字段进行排序数组
						
						// 每个问卷都加上状态字段active
						for (let i in dataList) {
							var optionList = []
							for (let j in dataList[i].optionList) {
								dataList[i].optionList[j].active = ''
								optionList.push(dataList[i].optionList[j])
							}
							dataList[i].optionList = optionList
						}
						this.qusetionList = dataList
					},
					fail: () => {
						uni.showToast({
							title: "网络请求失败!",
							icon: 'none',
							duration: 2000
						})
					}
				})
			},
			
			//--- 数组内的对象按某个字段进行排序 ---//
			compare(property){
				return function(a,b){
					var value1 = a[property];
					var value2 = b[property];
					return value1 - value2;  //升序,  降序为value2 - value1
				}
			},
			
			// 选择及未选择样式切换
			optionItem(param) {
				// 根据每个字段的id作为唯一状态标识是否选中
				this.active = param.id
				for (var i in this.qusetionList) {
					// 单项选择
					if (this.qusetionList[i].type == 0) {
						if (this.qusetionList[i].groudId == param.subjectId) {
							for (var j in this.qusetionList[i].optionList) {
								if (this.qusetionList[i].optionList[j].id == param.id && this.qusetionList[i].optionList[j].active =='') {
									this.qusetionList[i].optionList[j].active = param.id
								} else {
									this.qusetionList[i].optionList[j].active = ''
								}
							}
						}
						// 多项选择
					} else if (this.qusetionList[i].type == 1) {
						for (var j in this.qusetionList[i].optionList) {
							if (this.qusetionList[i].optionList[j].id == param.id) {
								if (this.qusetionList[i].optionList[j].active == '') {
									this.qusetionList[i].optionList[j].active = param.id
								} else if (this.qusetionList[i].optionList[j].active != '') {
									this.qusetionList[i].optionList[j].active = ''
								}
							}
						}
					}
				}
			},
			
			// 提交问卷
			subQuestion() {
				var subTime = Date.now()
				var userName = '名字' + subTime.toString ().slice(-3)
				var activeQuestion = [] //已选择的数据列表
				
				// 循环判断active是否为空,单选和多选因为传参格式需要区分判断
				for (var i in this.qusetionList) {
					// 单选判断循环
					if (this.qusetionList[i].type == 0) {
						for (var j in this.qusetionList[i].optionList) {
							if (this.qusetionList[i].optionList[j].active != '') {
								// 把已选择的数据追加到列表
								activeQuestion.push({
									subTime:subTime,
									userName: userName,
									// groudId: this.qusetionList[i].groudId,
									sort: this.qusetionList[i].sort,
									subjectContent: this.qusetionList[i].subjectContent,
									optionContent: this.qusetionList[i].optionList[j].optionContent
								})
							}
						}
					} else {
						// 多选判断循环,选项ID以逗号拼接成字符串
						var optionArr = []
						for (var j in this.qusetionList[i].optionList) {
							if (this.qusetionList[i].optionList[j].active != '') {
								// optionArr.push(this.qusetionList[i].optionList[j].id)
								optionArr.push(this.qusetionList[i].optionList[j].optionContent)
							}
						}
						// 把已选择的数据追加到列表
						if (optionArr != '') {
							activeQuestion.push({
								subTime:subTime,
								userName: userName,
								// groudId: this.qusetionList[i].groudId,
								sort: this.qusetionList[i].sort,
								subjectContent: this.qusetionList[i].subjectContent,
								//optionId: optionArr.join()
								optionContent:optionArr.join()
							})
						}
					}
				}

				//console.log(activeQuestion)
				
				if(activeQuestion.length < this.qusetionList.length){
					uni.showToast({
						title: "问题还没有回答完!",
						icon: 'none',
						duration: 2000
					});
				} else {
					//提交数据给后端
					uni.request({
						url: this.baseUrl + 'addAnswer',
						method: 'POST',
						header: {'content-type' : "application/x-www-form-urlencoded"},
						data: {
							formData: JSON.stringify(activeQuestion) //转换为JSON格式字符串
						},
						success: (res) => {
							// 服务器返回数据,后续业务逻辑处理
							console.log(res)
							// 调用方法,刷新数据
							this.getData()
							uni.showToast({
								title: "保存成功", 
								icon : "success",
								duration:3000
							})
						},
						fail: (err) => {
							console.log(err)
							uni.showToast({ 
								title: "服务器响应失败,请稍后再试!", 
								icon : "none",
							})
						},
						complete: () => {
							
						}
					})
					
				}
			},
			
			// 跳转到页面
			goAdmin() {
				uni.navigateTo({
					url: '../admin/admin'
				})
			}
			
		}
	}
</script>

<style lang="less" scoped>
	.question {
		.question_header {
			// height: 90rpx;固定高度之后,长内容换行不能自动增加高度
			background-color: #f1f1f1;
			font-size: 34rpx;
			font-weight: 700;
			color: #333333;

			.header_title {
				width: 95%;
				margin-left: 37rpx;
				line-height: 90rpx;
			}
		}

		.question_option {
			width: 650rpx;
			margin-top: 7rpx;
			// background-color: #F0AD4E;
			display: flex;
			justify-content: space-between;
			flex-wrap: wrap;
			margin: 0 auto;
			margin-bottom: 40rpx;

			.option_item {
				width: 300rpx;
				margin-top: 34rpx;
				// background-color: #DD524D;
				font-size: 30rpx;
				color: #666666;
				display: flex;
				align-items: center;

				.option_box {
					width: 35rpx;
					height: 35rpx;
					border: 1rpx solid #999999;
					border-radius: 5px;
					margin-right: 10rpx;
					// background-color: #FF852A;
					display: flex;
					justify-content: center;
					align-items: center;

					image {
						width: 20rpx;
						height: 20rpx;
					}
				}
			}
		}
	}

	.active_option {
		.option_box {
			background: linear-gradient(-30deg, #ff7029 0%, #faa307 100%);
			border: 1rpx solid #faa307 !important;
		}

		text {
			color: #ff7029;
		}
	}

	.submit_box {
		width: 750rpx;
		height: 160rpx;
		background-color: #F1F1F1;
		position: fixed;
		bottom: 0;
	}

	.sub_btn {
		width: 80%;
		height: 88rpx;
		background: linear-gradient(-30deg, #dc4011 0%, #faa307 100%);
		border-radius: 44rpx;
		margin: 40rpx auto;
		font-size: 32rpx;
		font-weight: 700;
		color: #ffffff;
		text-align: center;
		line-height: 88rpx;
	}

	// 按钮原生会存在上下黑线,该属性去除
	button::after {
		border: none;
	}
</style>
  • 后台管理部分页面代码 charts.vue
<template>
	<view>
		<block v-for="(item,index) in dataList" :key="index">
			<view style="margin: 50rpx;">{{item.subjectContent}}</view>
			<canvas :canvas-id="'id'+index" style="width: 350px; height: 300px;" ></canvas>
		</block>
	</view>
</template>

<script>
	// 引入外部 js
	import canvas from '@/static/canvas.js'

	export default {
		data() {
			return {
				baseUrl: '',
				dataList: []
			}
		},
		onReady() {
			// 获取全局变量 baseUrl
			this.baseUrl = getApp().globalData.baseUrl;
			// 调用方法
			this.getData()
		},

		methods: {
			//从后端获取数据
			getData() {
				uni.showLoading({
					title: '数据加载中...'
				})
				uni.request({
					url: this.baseUrl + 'queryByGroup',
					method: "GET",
					data: {},
					success: (res) => {
						//console.log(res)
						let tempArr = res.data
						this.dataList = tempArr
						let arr = tempArr.sort(this.compare('sort')) //按对象内的sort字段进行排序数组

						// 延迟1秒等待canvas组件渲染完成,再调用方法绘画,否则绘画不成功
						setTimeout(function(){
							for (let x in arr) {
								// 调用外部方法并传入参数: canvas-id,数组,总数量
								canvas.canvasGraph('id'+x, arr[x].list, arr[x].list[0].total)
							}
						},1000)
					},
					fail: (err) => {
						uni.showToast({
							title: "网络请求失败!",
							icon: 'none',
							duration: 2000
						})
					},
					complete: () => {
						setTimeout(function(){
							uni.hideLoading()
						},1000)
					}
				})
			},
			
			//--- 数组内的对象按某个字段进行排序 ---//
			compare(property){
				return function(a,b){
					var value1 = a[property];
					var value2 = b[property];
					return value1 - value2;  //升序,  降序为value2 - value1
				}
			}
			
		}
	}
</script>

<style>

</style>
  • 后端使用 nodejs + mongoDB 搭建服务
  • 程序入口文件 app.js
const express = require('express');
const cors=require('cors');
const bodyParser = require('body-parser');
const app = express();

//全局变量,数据库地址
global.G_url = "mongodb://127.0.0.1:27017";

//处理跨域
app.use(cors()) 
//对post请求的请求体进行解析
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())

//设置share文件夹下的所有文件能通过网址访问,用作静态文件web服务
app.use(express.static("./share"))

//路由配置
const index=require('./routes/index.js')
const query=require('./routes/query.js')
const add=require('./routes/add.js')
const del=require('./routes/del.js')
const edit=require('./routes/edit.js')
const update=require('./routes/update.js')
const addAnswer=require('./routes/addAnswer.js')
const queryAnswer=require('./routes/queryAnswer.js')
const queryByGroup=require('./routes/queryByGroup.js')
const delAll=require('./routes/delAll.js')

app.use('/index',index)
app.use('/query',query)
app.use('/add',add)
app.use('/del',del)
app.use('/edit',edit)
app.use('/update',update)
app.use('/addAnswer',addAnswer)
app.use('/queryAnswer',queryAnswer)
app.use('/queryByGroup',queryByGroup)
app.use('/delAll',delAll)
 
//启动服务器
app.listen(3000,()=>{
  console.log('http://127.0.0.1:3000')
})
  • 对原始数据按题目名称进行分组,然后追加需要用到的字段,再把处理好的数据发给前端进行渲染。
// queryByGroup.js

const express = require('express');
const router = express.Router();
const MongoClient = require("mongodb").MongoClient;

const url = G_url; //G_url是全局变量,在app.js定义

router.get('/', function(req, res, next) {
	// 调用方法
	dataOperate()

	/*操作数据库,异步方法*/
	async function dataOperate() {
		var allArr = []
		var arr = null
		var conn = null

		try {
			conn = await MongoClient.connect(url)
			// 定义使用的数据库和表
			const dbo = conn.db("mydb").collection("answer")
			// 查询所有
			arr = await dbo.find().toArray()

			// 调用 byGroup方法对原始数组按指定字段进行分组
			let groupBySubjectContent = byGroup(arr, 'subjectContent')

			// 循环执行
			for (var n in groupBySubjectContent) {
				let subjectContent = groupBySubjectContent[n].subjectContent
				let nameList = groupBySubjectContent[n].list
				
				// 从原数组中过滤字段等于subjectContent ,取最后一个元素
				let lastArr = (arr.filter(item => item.subjectContent == subjectContent)).slice(-1)
				let sort = lastArr[0].sort

				// 计算数组中某个元素的累计数量
				let countedNameObj = nameList.reduce((prev, item) => {
					if (item in prev) {
						prev[item]++
					} else {
						prev[item] = 1
					}
					return prev
				}, {})

				// 一个对象分割为多个对象
				let list = []
				for (var key in countedNameObj) {
					var temp = {}
					temp.title = key
					temp.money = countedNameObj[key]
					list.push(temp)
				}

				// 所有对象 money字段求和
				let listSum = list.reduce((prev, item) => {
					prev += item.money
					return prev
				}, 0)

				// 对象循环追加键值对
				for (var k in list) {
					list[k].total = listSum
					list[k].value = (list[k].money / listSum).toFixed(4) //计算比例,保留4位小数
					list[k].color = randomColor(k) //指定颜色
					//list[k].color = '#' + ('00000' + (Math.random() * 0x1000000 << 0).toString(16)).substr(-6) //随机颜色
				}

				// 对象追加到数组
				allArr.push({
					"sort": sort,
					"subjectContent": subjectContent,
					"list": list
				})
			}

			//给前端返回数据
			res.send(allArr)
			
		} catch (err) {
			console.log("错误:" + err.message)
			
		} finally {
			//关闭数据库连接
			if (conn != null) conn.close()
		}
	}

	/**
	 * 数据按字段分组处理
	 * @param arr [Array] 被处理的数组
	 * @param group_key [String] 分组字段
	 */
	function byGroup(arr, group_key) {
		let map = {}
		let res = []

		for (let i = 0; i < arr.length; i++) {
			let ai = arr[i]
			if (!map[ai[group_key]]) {
				// map[ai[group_key]] = [ai] //原始代码
				//optionContent是要筛选出来的字段
				map[ai[group_key]] = ai.optionContent.split(',')
			} else {
				// map[ai[group_key]].push(ai) //原始代码
				// split()通过指定分隔符对字符串进行分割,生成新的数组; arr = [...arr, ...arr2]  数组合并
				map[ai[group_key]] = [...map[ai[group_key]], ...ai.optionContent.split(',')]
			}
		}
		
		Object.keys(map).forEach(item => {
			res.push({
				[group_key]: item,
				list: map[item]
			})
		})
		
		return res
	}

	/**随机指定颜色**/
	function randomColor(index) {
		let colorList = ["#63b2ee","#76da91","#f8cb7f","#7cd6cf","#f89588","#9192ab","#efa666","#7898e1","#eddd86","#9987ce","#76da91","#63b2ee"]
		// let index = Math.floor(Math.random() * colorList.length)
		return colorList[index]
	}

});

module.exports = router;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/329211.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ubuntu系统(10):使用samba共享linux主机中文件

目录 一、samba安装步骤 1、Linux主机端操作 &#xff08;1&#xff09;安装sabma &#xff08;2&#xff09;修改samba配置文件 &#xff08;3&#xff09;为user_name用户设置samba访问的密码 &#xff08;4&#xff09;重启samba服务 2、Windows端 二、使用 1、代码…

CMake TcpServer项目 生成静态库.a / 动态库.so

CMake 实战构建TcpServer项目 静态库/动态库-CSDN博客https://blog.csdn.net/weixin_41987016/article/details/135608829?spm1001.2014.3001.5501 在这篇博客的基础上&#xff0c;我们把头文件放在include里边&#xff0c;把源文件放在src里边&#xff0c;重新构建 hehedali…

网页设计(一)开发环境配置与HTML基础

一、使用HBuilderX创建项目和文件 加粗样式HX创建HTML文档的模板位置 template.html模板内容 1.创建HTML项目 创建项目引导界面 新建项目窗口 Web-2009030199项目所在位置图 2.在项目下新建prj_1_2.html文件 创建文件引导界面 新建html文件界面 3.新建prj_1_2.ht…

什么是DNS(域名系统)

域名系统&#xff08;DNS&#xff09;像是互联网的电话簿&#xff0c;将人们容易记住的网址转换成计算机能理解的IP地址。 没有DNS&#xff0c;我们就需要记住复杂的数字序列来访问网站 DNS的基本概念 定义&#xff1a;DNS是一个分布式数据库&#xff0c;它将域名&#xff08…

智能小程序小部件(Widget)导航、地图、画布等组件,以及开放能力、原生组件说明

智能小程序小部件(Widget)导航、地图、画布等组件&#xff0c;以及开放能力、原生组件说明。 导航组件 navigator 页面链接&#xff0c;控制小程序的跳转。navigator 子节点的背景色应为透明色。 属性说明 属性名类型默认值必填说明urlstring是跳转地址deltanumber1否当 …

【开发实践】前端jQuery+gif图片实现载入界面

一、需求分析 载入界面&#xff08;Loading screen&#xff09;是指在计算机程序或电子游戏中&#xff0c;当用户启动应用程序或切换到新的场景时&#xff0c;显示在屏幕上的过渡界面。它的主要作用是向用户传达程序正在加载或准备就绪的信息&#xff0c;以及提供一种视觉上的反…

Vue3中provide,inject使用

一&#xff0c;provide,inject使用&#xff1a; 应用场景&#xff1a;向孙组件传数据 应用Vue3碎片&#xff1a; ref&#xff0c;reactive&#xff0c;isRef&#xff0c;provide, inject 1.provide,inject使用 a.爷组件引入 import {ref,provide} from vue const drinkListre…

Spring Boot 中实现定时任务(quartz)功能实战

&#x1f3c6;作者简介&#xff0c;普修罗双战士&#xff0c;一直追求不断学习和成长&#xff0c;在技术的道路上持续探索和实践。 &#x1f3c6;多年互联网行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &#x1f389;欢迎 &#x1f44d;点赞✍评论…

BuildRoot配置RTL8822CE WIFIBT模块(WIFI部分)

TinkerBoard2主板自带的无线模块为RTL8822CE&#xff0c;PCIe接口 之前在风火轮下载的Linux源码编译出来的BuildRoot根文件系统没有相关的驱动文件 [rootrk3399:/]# find . -name *.ko [rootrk3399:/]# lsmod Module Size Used by Not tainted [rootrk33…

第二百七十一回

文章目录 1. 概念介绍2. 实现方法2.1 主要步骤2.1 注意事项 3. 示例代码4. 内容总结 我们在上一章回中介绍了"如何加载网络图片"相关的内容&#xff0c;本章回中将介绍如何加载本地图片.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在上一章回…

Scrum敏捷研发管理解决方案

Leangoo领歌是一款永久免费的专业的敏捷开发管理工具&#xff0c;提供端到端敏捷研发管理解决方案&#xff0c;涵盖敏捷需求管理、任务协同、进展跟踪、统计度量等。 Leangoo领歌上手快、实施成本低&#xff0c;可帮助企业快速落地敏捷&#xff0c;提质增效、缩短周期、加速创新…

力扣刷MySQL-第一弹(详细解析)

&#x1f389;欢迎您来到我的MySQL基础复习专栏 ☆* o(≧▽≦)o *☆哈喽~我是小小恶斯法克&#x1f379; ✨博客主页&#xff1a;小小恶斯法克的博客 &#x1f388;该系列文章专栏&#xff1a;力扣刷题讲解-MySQL &#x1f379;文章作者技术和水平很有限&#xff0c;如果文中出…

C++学习笔记(四十二):c++ 结构化绑定

本节介绍c结构化绑定&#xff0c;改知识点是c17中增加的新特性&#xff0c;主要是用来处理c多返回值的问题。 原来c17之前返回多个值的方法之前专门有一节内容在描述&#xff0c;简单回顾一下原来使用tuple返回多个返回值的方法&#xff0c;代码如下&#xff1a; #include &l…

条件风险价值CVaR内容介绍(MATLAB例程)

在对微电网优化调度过程中&#xff0c;由于新能源机组出力和负荷的不确定性&#xff0c;若采用确定性的优化调度方案会存在一定的风险。当风电和光伏输出功率的实际值低于预测值时&#xff0c;需要增加可控机组出力或切除部分负荷&#xff1b;反之&#xff0c;若风电和光伏输出…

Mybatis----面向接口

让mybatis自动生成dao层接口的实现类 这是dao层接口的实现类&#xff0c;在mybatis中我们可以省略这种实现接口的方式&#xff0c;直接面向接口操作数据库&#xff0c;mybatis可以帮我们自动生成接口的实现类&#xff0c;也就是下面这个实现类mybatis帮我们生成了。 1、修改se…

如何提高匹配的速度

基于形状的匹配&#xff0c;改进了无数次&#xff0c;其实已经很满意了。 想起刚入门机器视觉那会儿&#xff0c;我们写的代码&#xff0c;无论c还是c#的&#xff0c;都是调用人家的函数&#xff0c;从来没想过&#xff0c;自己有一天把这些函数代替了。 那时候调用evision的…

ES 之索引和文档

本文主要介绍ES中的数据组成结构单元。 一、文档(Document) 1、概念 ES的数据存储单元是面向文档的&#xff0c;文档是所有数据存储&#xff0c;搜索的最小单元。 你可以把ES中的文档对应成mysql中的一条条数据记录。到时候你存进ES的数据就是一个个文档。 文档存入ES是序列…

读书笔记:单载波频域均衡技术 SC-FDE

原文&#xff1a;https://mp.weixin.qq.com/s?__bizMzU5NzgxNDgwMg&mid2247486891&idx1&sn51e0dc3d28bcf356126dc8ae922f5533&chksmfe4cf6d6c93b7fc0237d4e2107b5671a401db8ed7a8527159ef18333d2b1f48c56b381f846ae&mpshare1&scene1&srcid0117h6K…

Windows Redis图形客户端 Another Redis Desktop Manager的简单使用教程

1、 Redis官方文档 2、 Redis国内中文版文档 3、 Redis客户端 Another Redis Desktop Manager 4、连接redis服务 我直接使用的是公司搭建好的服务。连接服务需要以下几个信息&#xff1a; HostPortPasswordSSL 5、New Key 5.1 如何创建一个Key&#xff1f; 点击New k…

《SPSS统计学基础与实证研究应用精解》视频讲解:SPSS中用到的概率分布

《SPSS统计学基础与实证研究应用精解》2.2 视频讲解 视频为《SPSS统计学基础与实证研究应用精解》张甜 杨维忠著 清华大学出版社 一书的随书赠送视频讲解2.2节内容。本书已正式出版上市&#xff0c;当当、京东、淘宝等平台热销中&#xff0c;搜索书名即可。本书旨在手把手教会使…
最新文章