Procházet zdrojové kódy

优化问卷答题

htc před 21 hodinami
rodič
revize
53d168a500
3 změnil soubory, kde provedl 389 přidání a 268 odebrání
  1. 116 0
      components/QuestionItem/index.vue
  2. 135 135
      manifest.json
  3. 138 133
      pages/questionnaireFill.vue

+ 116 - 0
components/QuestionItem/index.vue

@@ -0,0 +1,116 @@
+<template>
+	<div class="li_box" :class="{'active':item.warn}">
+		<div class="lb_title adf">
+			<span>*</span>
+			{{index+1}}. {{item.question}}
+		</div>
+		<div class="lb_answers">
+			<u-radio-group
+				:value="item.answer"
+				placement="column"
+				@change="radioChange"
+			>
+				<view class="la_item" v-for="(pre,idx) in item.userAnswer" :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>
+</template>
+
+<script>
+	export default {
+		name: "QuestionItem",
+		props: {
+			item: {
+				type: Object,
+				required: true
+			},
+			index: {
+				type: Number,
+				required: true
+			}
+		},
+		methods: {
+			radioChange(value) {
+				this.$emit('change', {
+					value: value,
+					index: this.index
+				});
+			}
+		}
+	}
+</script>
+
+<style scoped lang="less">
+	.li_box{
+		width: 100%;
+		padding: 32rpx 34rpx;
+		box-sizing: border-box;
+		&.active{
+			border: 2rpx dotted #FD4F66;
+		}
+		.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_answers{
+			width: calc(100% - 40rpx);
+			margin: 30rpx 20rpx 0;
+			box-sizing: border-box;
+			border: 1rpx solid #E5E7EB;
+			.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>

+ 135 - 135
manifest.json

@@ -1,135 +1,135 @@
-{
-    "name" : "创衡汇教练AI智能体",
-    "appid" : "__UNI__A0C8B31",
-    "description" : "智慧生活",
-    "versionName" : "1.0.3",
-    "versionCode" : 102,
-    "transformPx" : false,
-    /* 5+App特有相关 */
-    "app-plus" : {
-        "usingComponents" : true,
-        "nvueStyleCompiler" : "uni-app",
-        "compilerVersion" : 3,
-        "splashscreen" : {
-            "alwaysShowBeforeRender" : true,
-            "waiting" : true,
-            "autoclose" : true,
-            "delay" : 0
-        },
-        /* 模块配置 */
-        "modules" : {
-            "Barcode" : {},
-            "Camera" : {}
-        },
-        /* 应用发布信息 */
-        "distribute" : {
-            /* android打包配置 */
-            "android" : {
-                "permissions" : [
-                    "<uses-feature android:name=\"android.hardware.camera\"/>",
-                    "<uses-permission android:name=\"android.permission.CAMERA\"/>"
-                ],
-                "abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ],
-                "autoSdkPermissions" : false,
-                "permissionExternalStorage" : {
-                    "request" : "none",
-                    "prompt" : "应用保存运行状态等信息,需要获取读写手机存储(系统提示为访问设备上的照片、媒体内容和文件)权限,请允许。"
-                },
-                "permissionPhoneState" : {
-                    "request" : "none",
-                    "prompt" : "为保证您正常、安全地使用,需要获取设备识别码(部分手机提示为获取手机号码)使用权限,请允许。"
-                }
-            },
-            /* ios打包配置 */
-            "ios" : {
-                "dSYMs" : false,
-                "privacyDescription" : {
-                    "NSPhotoLibraryUsageDescription" : "报修文件上传",
-                    "NSPhotoLibraryAddUsageDescription" : "拍照需要报修的地方",
-                    "NSCameraUsageDescription" : "扫码二维码"
-                },
-                "idfa" : false
-            },
-            /* SDK配置 */
-            "sdkConfigs" : {
-                "ad" : {},
-                "payment" : {
-                    "weixin" : {
-                        "__platform__" : [ "ios", "android" ],
-                        "appid" : "",
-                        "UniversalLinks" : ""
-                    }
-                }
-            },
-            "icons" : {
-                "android" : {
-                    "hdpi" : "unpackage/res/icons/72x72.png",
-                    "xhdpi" : "unpackage/res/icons/96x96.png",
-                    "xxhdpi" : "unpackage/res/icons/144x144.png",
-                    "xxxhdpi" : "unpackage/res/icons/192x192.png"
-                },
-                "ios" : {
-                    "appstore" : "unpackage/res/icons/1024x1024.png",
-                    "ipad" : {
-                        "app" : "unpackage/res/icons/76x76.png",
-                        "app@2x" : "unpackage/res/icons/152x152.png",
-                        "notification" : "unpackage/res/icons/20x20.png",
-                        "notification@2x" : "unpackage/res/icons/40x40.png",
-                        "proapp@2x" : "unpackage/res/icons/167x167.png",
-                        "settings" : "unpackage/res/icons/29x29.png",
-                        "settings@2x" : "unpackage/res/icons/58x58.png",
-                        "spotlight" : "unpackage/res/icons/40x40.png",
-                        "spotlight@2x" : "unpackage/res/icons/80x80.png"
-                    },
-                    "iphone" : {
-                        "app@2x" : "unpackage/res/icons/120x120.png",
-                        "app@3x" : "unpackage/res/icons/180x180.png",
-                        "notification@2x" : "unpackage/res/icons/40x40.png",
-                        "notification@3x" : "unpackage/res/icons/60x60.png",
-                        "settings@2x" : "unpackage/res/icons/58x58.png",
-                        "settings@3x" : "unpackage/res/icons/87x87.png",
-                        "spotlight@2x" : "unpackage/res/icons/80x80.png",
-                        "spotlight@3x" : "unpackage/res/icons/120x120.png"
-                    }
-                }
-            },
-            "splashscreen" : {
-                "useOriginalMsgbox" : true
-            }
-        }
-    },
-    /* 快应用特有相关 */
-    "quickapp" : {},
-    /* 小程序特有相关 */
-    "mp-weixin" : {
-        "appid" : "wxb0ecfcf0c3e50402",
-        "setting" : {
-            "urlCheck" : false,
-            "es6" : false,
-            "minified" : true,
-            "bigPackageSizeSupport" : true
-        },
-        "usingComponents" : true,
-        "requiredPrivateInfos" : [ "getLocation" ],
-        "optimization" : {
-            "subPackages" : true
-        },
-        "permission" : {}
-    },
-    "mp-alipay" : {
-        "usingComponents" : true
-    },
-    "mp-baidu" : {
-        "usingComponents" : true
-    },
-    "mp-toutiao" : {
-        "usingComponents" : true
-    },
-    "uniStatistics" : {
-        "enable" : false
-    },
-    "vueVersion" : "2",
-    "fallbackLocale" : "zh-Hans"
-}
-/* ios打包配置 *//* SDK配置 */
-
+{
+    "name" : "创衡正念企业教练",
+    "appid" : "__UNI__A0C8B31",
+    "description" : "智慧生活",
+    "versionName" : "1.0.3",
+    "versionCode" : 102,
+    "transformPx" : false,
+    /* 5+App特有相关 */
+    "app-plus" : {
+        "usingComponents" : true,
+        "nvueStyleCompiler" : "uni-app",
+        "compilerVersion" : 3,
+        "splashscreen" : {
+            "alwaysShowBeforeRender" : true,
+            "waiting" : true,
+            "autoclose" : true,
+            "delay" : 0
+        },
+        /* 模块配置 */
+        "modules" : {
+            "Barcode" : {},
+            "Camera" : {}
+        },
+        /* 应用发布信息 */
+        "distribute" : {
+            /* android打包配置 */
+            "android" : {
+                "permissions" : [
+                    "<uses-feature android:name=\"android.hardware.camera\"/>",
+                    "<uses-permission android:name=\"android.permission.CAMERA\"/>"
+                ],
+                "abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ],
+                "autoSdkPermissions" : false,
+                "permissionExternalStorage" : {
+                    "request" : "none",
+                    "prompt" : "应用保存运行状态等信息,需要获取读写手机存储(系统提示为访问设备上的照片、媒体内容和文件)权限,请允许。"
+                },
+                "permissionPhoneState" : {
+                    "request" : "none",
+                    "prompt" : "为保证您正常、安全地使用,需要获取设备识别码(部分手机提示为获取手机号码)使用权限,请允许。"
+                }
+            },
+            /* ios打包配置 */
+            "ios" : {
+                "dSYMs" : false,
+                "privacyDescription" : {
+                    "NSPhotoLibraryUsageDescription" : "报修文件上传",
+                    "NSPhotoLibraryAddUsageDescription" : "拍照需要报修的地方",
+                    "NSCameraUsageDescription" : "扫码二维码"
+                },
+                "idfa" : false
+            },
+            /* SDK配置 */
+            "sdkConfigs" : {
+                "ad" : {},
+                "payment" : {
+                    "weixin" : {
+                        "__platform__" : [ "ios", "android" ],
+                        "appid" : "",
+                        "UniversalLinks" : ""
+                    }
+                }
+            },
+            "icons" : {
+                "android" : {
+                    "hdpi" : "unpackage/res/icons/72x72.png",
+                    "xhdpi" : "unpackage/res/icons/96x96.png",
+                    "xxhdpi" : "unpackage/res/icons/144x144.png",
+                    "xxxhdpi" : "unpackage/res/icons/192x192.png"
+                },
+                "ios" : {
+                    "appstore" : "unpackage/res/icons/1024x1024.png",
+                    "ipad" : {
+                        "app" : "unpackage/res/icons/76x76.png",
+                        "app@2x" : "unpackage/res/icons/152x152.png",
+                        "notification" : "unpackage/res/icons/20x20.png",
+                        "notification@2x" : "unpackage/res/icons/40x40.png",
+                        "proapp@2x" : "unpackage/res/icons/167x167.png",
+                        "settings" : "unpackage/res/icons/29x29.png",
+                        "settings@2x" : "unpackage/res/icons/58x58.png",
+                        "spotlight" : "unpackage/res/icons/40x40.png",
+                        "spotlight@2x" : "unpackage/res/icons/80x80.png"
+                    },
+                    "iphone" : {
+                        "app@2x" : "unpackage/res/icons/120x120.png",
+                        "app@3x" : "unpackage/res/icons/180x180.png",
+                        "notification@2x" : "unpackage/res/icons/40x40.png",
+                        "notification@3x" : "unpackage/res/icons/60x60.png",
+                        "settings@2x" : "unpackage/res/icons/58x58.png",
+                        "settings@3x" : "unpackage/res/icons/87x87.png",
+                        "spotlight@2x" : "unpackage/res/icons/80x80.png",
+                        "spotlight@3x" : "unpackage/res/icons/120x120.png"
+                    }
+                }
+            },
+            "splashscreen" : {
+                "useOriginalMsgbox" : true
+            }
+        }
+    },
+    /* 快应用特有相关 */
+    "quickapp" : {},
+    /* 小程序特有相关 */
+    "mp-weixin" : {
+        "appid" : "wxb0ecfcf0c3e50402",
+        "setting" : {
+            "urlCheck" : false,
+            "es6" : false,
+            "minified" : true,
+            "bigPackageSizeSupport" : true
+        },
+        "usingComponents" : true,
+        "requiredPrivateInfos" : [ "getLocation" ],
+        "optimization" : {
+            "subPackages" : true
+        },
+        "permission" : {}
+    },
+    "mp-alipay" : {
+        "usingComponents" : true
+    },
+    "mp-baidu" : {
+        "usingComponents" : true
+    },
+    "mp-toutiao" : {
+        "usingComponents" : true
+    },
+    "uniStatistics" : {
+        "enable" : false
+    },
+    "vueVersion" : "2",
+    "fallbackLocale" : "zh-Hans"
+}
+/* ios打包配置 *//* SDK配置 */
+

+ 138 - 133
pages/questionnaireFill.vue

@@ -6,102 +6,165 @@
 			<p class="tip">共 <span>{{list.length}}</span> 题,已作答 <span style="font-weight: bold;">{{answerNum}}</span> 题,请耐心选择!</p>
 		</div>
 		<div class="list" :style="{'height':(h-180-mt)+'px'}">
-			<div class="l_item" v-for="(item,index) in list" :key="index">
-				<div class="li_box" :class="{'active':item.warn}">
-					<div class="lb_title adf">
-						<span>*</span>
-						{{index+1}}. {{item.question}}
-					</div>
-					<div class="lb_answers">
-						<u-radio-group v-model="item.answer" placement="column">
-							<div class="la_item" v-for="(pre,idx) in item.userAnswer" :key="idx">
-								<u-radio :label="pre.questionOption" :name="pre.questionOption" activeColor="#833478" size="36rpx" iconSize="32rpx" labelSize="32rpx"></u-radio>
-							</div>
-						</u-radio-group>
-					</div>
+			<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>
+				<div class="l_item" v-for="(item,index) in list" :key="item.id">
+					<QuestionItem :item="item" :index="index" @change="handleAnswerChange"></QuestionItem>
+				</div>
+			</template>
 		</div>
-		<div class="bottom">
-			<div class="zt_btn" @tap="submitWj">提交问卷</div>
+		<div class="bottom">
+			<view class="zt_btn" @tap="submitWj">{{isSubmitting ? '提交中...' : '提交问卷'}}</view>
 		</div>
 	</view>
 </template>
 
 <script>
+	import QuestionItem from '@/components/QuestionItem/index.vue'
 	export default {
+		components:{QuestionItem},
 		data(){
 			return {
 				title:'',
 				teamQuestionnaireId:'',
 				list:[],
-				answerNum:0,
-				questionnaire:null
+				questionnaire:null,
+				isLoading: true, // 新增加载状态
+				isSubmitting: false // 新增提交状态
 			}
 		},
 		onLoad(option) {
 			this.title = option.title;
 			this.teamQuestionnaireId = option.teamQuestionnaireId;
 			this.getList();
+		},
+		computed: {
+			answerNum() {
+				return this.list.filter(l => !!l.answer).length || 0;
+			}
 		},
-		watch:{
-			list:{
-				handler(newval){
-					this.answerNum = this.list.filter(l=>l.answer!='')?.length||0;
-				},
-				deep:true
-			}
-		},
-		methods:{
+		methods:{
+			handleAnswerChange(e) {
+				const item = this.list[e.index];
+				if (item) {
+					this.$set(item, 'answer', e.value);
+					if (item.warn) {
+						this.$set(item, 'warn', false);
+					}
+				}
+			},
 			getList(){
-				if(uni.getStorageSync('questionnaire')) this.questionnaire = JSON.parse(uni.getStorageSync('questionnaire'))
-				this.$api.get('/core/team/member/answer/listByUser/'+this.teamQuestionnaireId).then(res=>{
-					if(res.data.code!==0) return this.$showToast(res.data.msg)
-					this.list = res.data.data;
-					this.list.forEach((l,i)=>{
-						this.$set(this.list[i],'warn',false);
-						this.$set(this.list[i],'answer','');
-						if(this.questionnaire&&this.teamQuestionnaireId==this.questionnaire.key){
-							let t = this.questionnaire.list.find(q=>q.id==l.id)
-							this.$set(this.list[i],'answer',t?t.answer:'');
-						}
-					})
-				})
+				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.list = res.data.data.map(item => ({
+						...item,
+						warn: false,
+						answer: answerMap.get(item.id) || '' // 直接从 Map 中获取答案
+					}));
+				}).catch(()=>{
+					this.isLoading = false;
+					this.$showToast('网络异常,请稍后重试');
+				}).finally(() => {
+					this.isLoading = false;
+				});
 			},
-			submitWj(){
-				this.list.forEach((l,i)=>{
-					this.$set(this.list[i],'warn',!l.answer?true:false)
-				})
-				let idx = this.list.findIndex(l=>l.warn);
-				if(idx>-1) return this.$showModal(`第${idx+1}项未选择答案,请选择。`)
-				let newList = JSON.parse(JSON.stringify(this.list));
-				newList.forEach(l=>{
-					l.isAnswer = '1';
-					let i = l.userAnswer.findIndex(a=>a.questionOption===l.answer)
-					if(i>-1) l.userAnswer[i].userAnswer = true;
-					delete l.answer
-					delete l.warn
-				})
-				this.$api.post('/core/team/member/answer/submit',newList).then(res=>{
-					if(res.data.code!==0) return this.$showToast(res.data.msg)
-					uni.removeStorageSync('questionnaire')
-					uni.navigateTo({
-						url:'/pages/questionnaireResult'
-					})
-				})
+			submitWj() {
+			    if (this.isSubmitting) return;
+			
+			    let firstUnansweredIndex = -1;
+			    this.list.forEach((l, i) => {
+			        const isAnswered = !!l.answer;
+			        this.$set(l, 'warn', !isAnswered);
+			        if (!isAnswered && firstUnansweredIndex === -1) {
+			            firstUnansweredIndex = i;
+			        }
+			    });
+			
+			    if (firstUnansweredIndex > -1) {
+			        // 使用 uni.showModal 以提供更好的用户体验
+			        return uni.showModal({
+			            title: '提示',
+			            content: `第 ${firstUnansweredIndex + 1} 项未选择答案,请选择。`,
+			            showCancel: false
+			        });
+			    }
+			
+			    // 使用更清晰的逻辑构建提交数据
+			    const submitList = this.list.map(question => {
+			        // 映射 userAnswer 数组,并添加一个清晰的 `isSelected` 字段
+			        const formattedUserAnswer = question.userAnswer.map(option => ({
+			            ...option,
+			            // 假设后端需要一个布尔值来表示是否选中
+			            isSelected: option.questionOption === question.answer
+			        }));
+			
+			        // 剔除前端专用的字段(answer, warn),保持提交数据纯净
+			        const { answer, warn, ...restOfQuestion } = question;
+			
+			        return {
+			            ...restOfQuestion,
+			            isAnswer: '1', // 标记为已作答
+			            userAnswer: formattedUserAnswer // 使用格式化后的数组
+			        };
+			    });
+			
+			    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');
+			        // 提交成功后建议使用 redirectTo 或 reLaunch,防止用户返回到填写页
+			        uni.redirectTo({
+			            url: '/pages/questionnaireResult'
+			        });
+			    }).catch(err => {
+			        // this.isSubmitting = false; // .finally() 中已经处理
+			        this.$showToast('网络异常,请稍后重试');
+			    })
+			    .finally(() => {
+			        this.isSubmitting = false; // 确保无论成功或失败,提交状态都会被重置
+			    });
 			},
 			setQuestionnaireCache(){
-				if(uni.getStorageSync('questionnaire')) uni.removeStorageSync('questionnaire')
-				let list = [];
-				this.list.forEach(l=>{
-					if(l.answer) list.push({id:l.id,answer:l.answer})
-				})
-				let qinfo = {
-					key:this.teamQuestionnaireId,
-					list
+				const answeredList = this.list
+					.filter(l => l.answer) // 过滤出已回答的
+					.map(l => ({ id: l.id, answer: l.answer })); // 转换成需要的格式
+				
+				if (answeredList.length === 0) {
+					uni.removeStorageSync('questionnaire');
+					return;
 				}
-				if(list.length===0) return
-				uni.setStorageSync('questionnaire',JSON.stringify(qinfo));
+				
+				const qinfo = {
+					key: this.teamQuestionnaireId,
+					list: answeredList
+				};
+				uni.setStorageSync('questionnaire', JSON.stringify(qinfo));
 			}
 		},
 		onUnload() {
@@ -109,11 +172,16 @@
 		},
 		onBackPress() {
 			this.setQuestionnaireCache();
+			return false; 
 		}
 	}
 </script>
 
 <style scoped lang="less">
+	.loading-container {
+		width: 100%;
+		height: 100%;
+	}
 	.page{
 		background: #F7F2F6;
 		box-sizing: border-box;
@@ -152,69 +220,6 @@
 				background: #FFFFFF;
 				padding: 6rpx;
 				box-sizing: border-box;
-				.li_box{
-					width: 100%;
-					padding: 32rpx 34rpx;
-					box-sizing: border-box;
-					&.active{
-						border: 2rpx dotted #FD4F66;
-					}
-					.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_answers{
-						width: calc(100% - 40rpx);
-						margin: 30rpx 20rpx 0;
-						box-sizing: border-box;
-						border: 1rpx solid #E5E7EB;
-						.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;
-						}
-					}
-				}
 			}
 		}