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

小程序 0

前后端效果图
在这里插入图片描述
后端: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.jsconst 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;

也许您对下面的内容还感兴趣: