瀏覽代碼

增加扫码进入页面;问卷作答最新需求修改

htc 5 天之前
父節點
當前提交
62d306be1c
共有 6 個文件被更改,包括 521 次插入3 次删除
  1. 145 0
      components/QuestionItem/index2.vue
  2. 1 0
      http/baseApi.js
  3. 12 0
      pages.json
  4. 9 3
      pages/questionnaire.vue
  5. 306 0
      pages/questionnaireFill2.vue
  6. 48 0
      pages/scan.vue

+ 145 - 0
components/QuestionItem/index2.vue

@@ -0,0 +1,145 @@
+<template>
+	<div class="li_box" :class="{'active':item.warn}">
+		<div class="lb_title adf">
+			<span>*</span>
+			{{index+1}}. {{item.question}}
+		</div>
+		<div class="lb_box" v-for="(ua,ui) in item.userAnswer" :key="ui">
+			<div class="memo">{{assessmentMethodCfg[ua.assessmentMethod]|''}}</div>
+			<div class="lb_answers">
+				<u-radio-group
+					:value="ua.answer"
+					placement="column"
+					@change="e=>radioChange(e,ua.assessmentMethod,item.id)"
+				>
+					<view class="la_item" v-for="(pre,idx) in ua.questionOption" :key="idx">
+						<u-radio
+							:label="pre.questionOption"
+							:name="pre.questionOption"
+							activeColor="#833478"
+							size="36rpx"
+							iconSize="32rpx"
+							labelSize="32rpx"
+						></u-radio>
+					</view>
+				</u-radio-group>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+	export default {
+		name: "QuestionItem",
+		props: {
+			item: {
+				type: Object,
+				required: true
+			},
+			index: {
+				type: Number,
+				required: true
+			}
+		},
+		data(){
+			return {
+				assessmentMethodCfg:{
+					'1':'您对团队当前在此项陈述上所描述的实际表现的同意程度为:',
+					'2':'此项陈述对该团队当前的重要性: '
+				}
+			}
+		},
+		methods: {
+			radioChange(value,assessmentMethod,id) {
+				this.$emit('change', {
+					value,
+					assessmentMethod,
+					id,
+					index: this.index
+				});
+			}
+		}
+	}
+</script>
+
+<style scoped lang="less">
+	.li_box{
+		width: 100%;
+		padding: 32rpx 34rpx;
+		box-sizing: border-box;
+		&.active{
+			border: 2rpx dotted #FD4F66;
+			padding: 30rpx 32rpx;
+		}
+		.lb_title{
+			font-family: PingFang-SC, PingFang-SC;
+			font-weight: bold;
+			font-size: 32rpx;
+			color: #252525;
+			line-height: 48rpx;
+			span{
+				font-family: PingFangSC, PingFang SC;
+				font-weight: 400;
+				font-size: 32rpx;
+				color: #FD4F66;
+				line-height: 51rpx;
+			}
+		}
+		.lb_box{
+			width: calc(100% - 40rpx);
+			margin: 30rpx 20rpx 0;
+			.memo{
+				background: rgba(118, 30, 106, .2);
+				border-radius: 4rpx;
+				padding: 7rpx 20rpx;
+				font-family: PingFangSC, PingFang SC;
+				font-weight: 400;
+				font-size: 24rpx;
+				color: #761E6A;
+				line-height: 51rpx;
+				margin-top: 40rpx;
+			}
+			.lb_answers{
+				width: 100%;
+				box-sizing: border-box;
+				border: 1rpx solid #E5E7EB;
+				margin-top: 30rpx;
+				.la_item{
+					padding: 34rpx 24rpx;
+					border-bottom: 1rpx solid #E5E7EB;
+					&:last-child{
+						border-bottom: none;
+					}
+				}
+			}
+		}
+		.la_inp{
+			width: 100%;
+			height: 96rpx;
+			border-radius: 24rpx;
+			border: 1rpx solid #DFCDDC;
+			padding: 24rpx 30rpx;
+			box-sizing: border-box;
+			margin-top: 30rpx;
+		}
+		.la_warn{
+			padding: 7rpx 23rpx;
+			margin-top: 20rpx;
+			background: #FFECEC;
+			.lw{
+				width: 36rpx;
+				height: 36rpx;
+				border-radius: 50%;
+				background: #FD4F66;
+			}
+			span{
+				font-family: PingFangSC, PingFang SC;
+				font-weight: 400;
+				font-size: 24rpx;
+				color: #FD4F66;
+				line-height: 51rpx;
+				margin-left: 17rpx;
+			}
+		}
+	}
+</style>

+ 1 - 0
http/baseApi.js

@@ -1,6 +1,7 @@
 // const BaseApi = 'https://transcend.ringzle.com/chuangheng-app/app' //线上
 const BaseApi = 'https://wxapp.transcend-intl.cn/chuangheng-app/app' //生产
 // const BaseApi = 'http://192.168.2.19:9023/chuangheng-app/app' //李勇
+// const BaseApi = 'http://192.168.2.16:9023/chuangheng-app/app' //严总
 
 export {
 	BaseApi

+ 12 - 0
pages.json

@@ -6,6 +6,12 @@
 				"navigationStyle": "custom"
 			}
 		},
+		{
+			"path": "pages/scan",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
 		{
 			"path": "pages/launch",
 			"style": {
@@ -36,6 +42,12 @@
 				"navigationStyle": "custom"
 			}
 		},
+		{
+			"path": "pages/questionnaireFill2",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
 		{
 			"path": "pages/questionnaireResult",
 			"style": {

+ 9 - 3
pages/questionnaire.vue

@@ -67,9 +67,15 @@
 			},
 			toFill(item){
 				if(Date.parse(new Date())-Date.parse(item.endTime)>0) return this.$showToast('已过截止时间,无法作答!')
-				uni.navigateTo({
-					url:`/pages/questionnaireFill?teamQuestionnaireId=${item.teamQuestionnaireId}&title=${item.title}`
-				})
+				if(item.type==2){
+					uni.navigateTo({
+						url:`/pages/questionnaireFill2?teamQuestionnaireId=${item.teamQuestionnaireId}&title=${item.title}`
+					})
+				}else{
+					uni.navigateTo({
+						url:`/pages/questionnaireFill?teamQuestionnaireId=${item.teamQuestionnaireId}&title=${item.title}`
+					})
+				}
 			},
 			toRepoer(item){
 				uni.redirectTo({

+ 306 - 0
pages/questionnaireFill2.vue

@@ -0,0 +1,306 @@
+<template>
+	<view class="page" :style="{ 'min-height': h + 'px', 'padding-top': mt + 'px' }">
+		<cus-header title="问卷填写" :backAlert="true"></cus-header>
+		<div class="top adffcacjc">
+			<p>{{ title }}</p>
+			<p class="tip">
+				共
+				<span>{{ list.length }}</span>
+				题,已作答
+				<span style="font-weight: bold">{{ answerNum }}</span>
+				题,请耐心选择!
+			</p>
+		</div>
+		<scroll-view class="list" scroll-y="true" :scroll-top="scrollTop" :style="{ height: h - 180 - mt + 'px' }">
+			<div v-if="isLoading" class="loading-container adfacjc">
+				<div class="adfac">
+					<u-loading-icon size="42"></u-loading-icon>
+					<text style="margin-left: 10rpx; font-size: 34rpx; color: #666666">问卷加载中...</text>
+				</div>
+			</div>
+			<template v-else>
+				<view>
+					<div class="l_item" v-for="(item, index) in list" :key="item.id" :id="'question-' + index">
+						<QuestionItem :item="item" :index="index" @change="handleAnswerChange"></QuestionItem>
+					</div>
+				</view>
+			</template>
+		</scroll-view>
+		<div class="bottom">
+			<view class="zt_btn" @tap="submitWj">{{ isSubmitting ? '提交中...' : '提交问卷' }}</view>
+		</div>
+	</view>
+</template>
+
+<script>
+import QuestionItem from '@/components/QuestionItem/index2.vue';
+export default {
+	components: { QuestionItem },
+	data() {
+		return {
+			title: '',
+			teamQuestionnaireId: '',
+			list: [],
+			questionnaire: null,
+			isLoading: true,
+			isSubmitting: false,
+			scrollTop: 0, // scroll-view 的滚动位置
+			oldScrollTop: 0, // 辅助值,确保即使滚动到相同位置也能触发
+			userAnswerTemp: []
+		};
+	},
+	onLoad(option) {
+		this.title = option.title;
+		this.teamQuestionnaireId = option.teamQuestionnaireId;
+		this.getList();
+	},
+	computed: {
+		answerNum() {
+			return this.list.filter(l => l?.answer.filter(a => a?.value).length == this.userAnswerTemp.length).length || 0;
+		}
+	},
+	methods: {
+		handleAnswerChange(e) {
+			const item = this.list[e.index];
+			if (item) {
+				let answer = JSON.parse(JSON.stringify(item.answer));
+				answer.forEach(a=>{
+					a.value = a.assessmentMethod==e.assessmentMethod?e.value:a.value
+				})
+				this.$set(item, 'answer', answer);
+				if (item.warn) {
+					this.$set(item, 'warn', false);
+				}
+			}
+			this.setQuestionnaireCache();
+		},
+		getList() {
+			let questionnaire = null;
+			try {
+				const cacheStr = uni.getStorageSync('questionnaire');
+				if (cacheStr) {
+					questionnaire = JSON.parse(cacheStr);
+				}
+			} catch (e) {
+				console.error('解析问卷缓存失败:', e);
+				uni.removeStorageSync('questionnaire');
+			}
+
+			this.isLoading = true;
+			this.$api
+				.get('/core/team/member/answer/listByUser/' + this.teamQuestionnaireId)
+				.then((res) => {
+					if (res.data.code !== 0) return this.$showToast(res.data.msg);
+					const answerMap = new Map();
+					if (questionnaire && this.teamQuestionnaireId == questionnaire.key) {
+						questionnaire.list.forEach((q) => answerMap.set(q.id, q.answer));
+					}
+					
+					this.userAnswerTemp = res.data.data[0].userAnswer.map(u=>{
+						return {
+							assessmentMethod:u.assessmentMethod,
+							value:''
+						}
+					}) 
+					let uaTemp = JSON.parse(JSON.stringify(this.userAnswerTemp))
+					this.list = res.data.data.map(item => {
+						let _a = answerMap.get(item.id);
+						return {
+							...item,
+							answer:_a?uaTemp.map(u=>{
+								return {
+									assessmentMethod:u.assessmentMethod,
+									value:_a.find(a=>a.assessmentMethod==u.assessmentMethod)?.value||''
+								}
+							}):this.userAnswerTemp,
+							warn: false
+						}
+					});
+					this.list.forEach(l=>{
+						let _a = answerMap.get(l.id);
+						l.userAnswer.forEach(u=>{
+							u.answer = _a?(_a.find(a=>a.assessmentMethod==u.assessmentMethod)?.value||''):''
+						})
+					})
+				})
+				.catch(() => {
+					this.isLoading = false;
+					this.$showToast('网络异常,请稍后重试');
+				})
+				.finally(() => {
+					this.isLoading = false;
+				});
+		},
+		scrollToQuestion(index) {
+			const query = uni.createSelectorQuery().in(this);
+			const targetId = `#question-${index}`;
+			query.select(targetId).boundingClientRect();
+			query.select('.list').scrollOffset(); // 获取 scroll-view 的滚动信息
+			query.select('.list').boundingClientRect(); // 获取 scroll-view 自身的位置信息
+			query.exec((res) => {
+				// 增加安全校验
+				if (!res || !res[0] || !res[1] || !res[2]) {
+					return;
+				}
+
+				// res[0]: 目标元素 (#question-N) 的位置信息
+				const itemRect = res[0];
+				// res[1]: 滚动容器 (.list) 的滚动信息
+				const scrollViewScroll = res[1];
+				// res[2]: 滚动容器 (.list) 自身的位置信息
+				const scrollViewRect = res[2];
+
+				// 计算目标滚动位置 = 当前滚动距离 + (目标元素顶部 - 滚动容器顶部) - 预留间距
+				const targetPosition = scrollViewScroll.scrollTop + itemRect.top - scrollViewRect.top - 10; // 减10px作为顶部留白
+
+				// 通过先设置为旧值,再在 nextTick 中设置为新值的方式,确保滚动能够触发
+				this.scrollTop = this.oldScrollTop;
+				this.$nextTick(() => {
+					this.scrollTop = targetPosition;
+					this.oldScrollTop = targetPosition;
+				});
+			});
+		},
+		submitWj() {
+			if (this.isSubmitting) return;
+
+			let firstUnansweredIndex = -1;
+			this.list.forEach((l, i) => {
+				const isAnswered = l.answer.filter(a => a?.value).length==this.userAnswerTemp.length?true:false;
+				this.$set(l, 'warn', !isAnswered);
+				if (!isAnswered && firstUnansweredIndex === -1) {
+					firstUnansweredIndex = i;
+				}
+			});
+
+			if (firstUnansweredIndex > -1) {
+				uni.showModal({
+					title: '提示',
+					content: `第 ${firstUnansweredIndex + 1} 项未选择答案,请选择。`,
+					showCancel: false,
+					success: (res) => {
+						if (res.confirm) {
+							// 调用新的滚动方法
+							this.scrollToQuestion(firstUnansweredIndex);
+						}
+					}
+				});
+				return; // 终止提交
+			}
+
+			const submitList = this.list.map((l) => {
+				let {answer,userAnswer,warn,...other} = JSON.parse(JSON.stringify(l));
+				userAnswer.forEach(u=>{
+					u.questionOption.forEach(q=>{
+						q.userAnswer=answer.find(a=>a.assessmentMethod==u.assessmentMethod)?.value==q.questionOption;
+					})
+				})
+				return {
+					...other,
+					isAnswer: '1',
+					userAnswer
+				};
+			});
+			
+			this.isSubmitting = true;
+			this.$api
+				.post('/core/team/member/answer/submit', submitList)
+				.then((res) => {
+					if (res.data.code !== 0) {
+						this.isSubmitting = false;
+						return this.$showToast(res.data.msg);
+					}
+					uni.removeStorageSync('questionnaire');
+					uni.redirectTo({
+						url: '/pages/questionnaireResult'
+					});
+				})
+				.catch((err) => {
+					this.$showToast('网络异常,请稍后重试');
+				})
+				.finally(() => {
+					this.isSubmitting = false;
+				});
+		},
+		setQuestionnaireCache() {
+			const answeredList = this.list.filter((l) => l.answer.filter(a => a?.value).length>0).map((l) => ({ id: l.id, answer: l.answer }));
+			if (answeredList.length === 0) {
+				uni.removeStorageSync('questionnaire');
+				return;
+			}
+
+			const qinfo = {
+				key: this.teamQuestionnaireId,
+				list: answeredList
+			};
+			uni.setStorageSync('questionnaire', JSON.stringify(qinfo));
+		}
+	}
+};
+</script>
+
+<style scoped lang="less">
+.loading-container {
+	width: 100%;
+	height: 100%;
+}
+.page {
+	background: #f7f2f6;
+	box-sizing: border-box;
+	/* 【重要】让页面本身不可滚动,所有滚动都交给scroll-view处理 */
+	height: 100vh;
+	overflow: hidden;
+	display: flex;
+	flex-direction: column;
+
+	.top {
+		p {
+			font-family: PingFang-SC, PingFang-SC;
+			font-weight: bold;
+			font-size: 42rpx;
+			color: #252525;
+			line-height: 51rpx;
+			text-align: center;
+			margin-top: 48rpx;
+		}
+		.tip {
+			font-family: PingFangSC, PingFang SC;
+			font-weight: 400;
+			font-size: 26rpx;
+			color: #646464;
+			line-height: 26rpx;
+			text-align: center;
+			margin-top: 36rpx;
+			span {
+				margin: 0 10rpx;
+			}
+		}
+	}
+
+	.list {
+		width: 100%;
+		/* flex: 1;  如果使用flex布局,可以这样自适应高度 */
+		/* overflow-y: auto;  scroll-view自带滚动,无需此属性 */
+		margin-top: 28rpx;
+		.l_item {
+			margin-top: 20rpx;
+			width: 100%;
+			background: #ffffff;
+			padding: 6rpx;
+			box-sizing: border-box;
+
+			&:first-child {
+				margin-top: 0;
+			}
+		}
+	}
+
+	.bottom {
+		width: calc(100% - 80rpx);
+		height: 88rpx;
+		position: fixed;
+		left: 40rpx;
+		bottom: 40rpx;
+	}
+}
+</style>

+ 48 - 0
pages/scan.vue

@@ -0,0 +1,48 @@
+<template>
+	<view class="page" :style="{'height':h+'px', 'padding-top':mt+'px'}">
+		<cus-header title='欢迎' backUrl="/pages/home"></cus-header>
+		<div class="text">欢迎进入创衡汇AI智能教练小程序<br>该页面正在紧锣密鼓建设中,敬请期待~</div>
+	</view>
+</template>
+
+<script>
+	export default {
+		data(){
+			return {
+				
+			}
+		},
+		onLoad(option) {
+			if(option.q){
+				try{
+					console.log("扫码进入参数:", options.q);
+					const decodedUrl = decodeURIComponent(qrString);
+					console.log("decodedUrl:", decodedUrl);
+					const codeMatch = decodedUrl.match(/code=([^&]+)/);
+					console.log("codeMatch:", codeMatch);
+					const stationCode = codeMatch ? codeMatch[1] : null;
+					console.log("stationCode:", stationCode);
+				}catch(e){
+					//TODO handle the exception
+					console.log(e,'e');
+				}
+			}	
+		},
+		methods:{
+			
+		}
+	}
+</script>
+
+<style scoped lang="less">
+	.page{
+		.text{
+			font-size: 32rpx;
+			color: #333333;
+			line-height: 50rpx;
+			letter-spacing: 2rpx;
+			padding: 300rpx 60rpx 0;
+			text-align: center;
+		}
+	}
+</style>