Forráskód Böngészése

错题重做的第一版

zhangyujun 3 éve
szülő
commit
3d2bd53d77

+ 2 - 2
package.json

@@ -3,9 +3,9 @@
 	"version": "0.0.0",
 	"scripts": {
 		"dev": "vite",
-		"build:dev":"vite build --mode development",
+		"build:dev":"vite build --mode test",
 		"prod": "vite --mode production",
-		"build": "vite build",
+		"build": "vite build --mode production",
 		"serve": "vite preview --port 443"
 	},
 	"dependencies": {

+ 1 - 1
src/api/modules/test.ts

@@ -11,7 +11,7 @@ class Test {
 
 	/**获取免费题列表 */
 	getFreeList(params: Test.listParams): AxiosPromise<Test.listRes> {
-		return request("/student/qustion/info/normalList", {
+		return request("/student/qustion/info/selectFreeQuestionInfo", {
 			params,
 		});
 	}

+ 15 - 5
src/api/request.ts

@@ -3,7 +3,7 @@ import { requestLogger, responseLogger } from "axios-logger";
 import store from "@/store";
 import { Toast } from "vant";
 const request = axios.create({
-	baseURL: import.meta.env.MODE === "development" ? "/dev-api" : "/prod-api",
+	baseURL: import.meta.env.MODE === "production" ? "/prod-api" : "/dev-api",
 });
 
 request.interceptors.request.use((config) => {
@@ -22,15 +22,25 @@ request.interceptors.response.use((res) => {
 		switch (res.data.code) {
 			case 401:
 				Toast("登录失败,需要再次登录")
+				
 
 				window.setTimeout(() => {
-					import.meta.env.MODE === "development" ? 
-					location.replace(`https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx67ca1b8c9816ef28&redirect_uri=https://jpcj-h51.zzxcx.net/home/test&response_type=code&scope=snsapi_userinfo&state=LOGIN#wechat_redirect`)
-					:location.replace(`https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx67ca1b8c9816ef28&redirect_uri=https://jpcj-h5.zzxcx.net/home/test&response_type=code&scope=snsapi_userinfo&state=LOGIN#wechat_redirect`);
+
+					switch (import.meta.env.MODE) {
+						case "development":
+							location.replace(`https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx67ca1b8c9816ef28&redirect_uri=https://jpcj-h5.zzxcx.net/home/test&response_type=code&scope=snsapi_userinfo&state=LOGIN#wechat_redirect`)
+							break;
+						case "test":
+							location.replace(`https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx67ca1b8c9816ef28&redirect_uri=https://jpcj-h51.zzxcx.net/home/test&response_type=code&scope=snsapi_userinfo&state=LOGIN#wechat_redirect`)
+							break;
+						case "production":
+							location.replace(`https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx67ca1b8c9816ef28&redirect_uri=https://jpcj-h5.zzxcx.net/home/test&response_type=code&scope=snsapi_userinfo&state=LOGIN#wechat_redirect`);
+							break;
+					}
 				}, 2000)
 				break;
 			case 502:
-				if(res.data.msg=="微信授权无效,请重新授权"){
+				if (res.data.msg == "微信授权无效,请重新授权") {
 					location.replace("https://jpcj-h5.zzxcx.net/home/test")
 				}
 

+ 2 - 1
src/components/m-exercise/components/bottomBar.vue

@@ -37,8 +37,9 @@ export default defineComponent({
 	setup(props, { emit }) {
 		const router = useRouter();
 		const isVip = store.getters.getIsVip;
+		console.log(router.currentRoute.value)
 		const vipClick = () => {
-			if (store.getters.getIsVip) {
+			if (store.getters.getIsVip||router.currentRoute.value.path=="/exerciseFree") {
 				emit("update:officialShow", true);
 			} else {
 				Dialog.confirm({

+ 1 - 1
src/components/m-exercise/components/functionList.vue

@@ -37,7 +37,7 @@
 
 					
 				}]
-				if(store.getters.getIsVip){
+				if (store.getters.getIsVip||router.currentRoute.value.path=="/exerciseFree") {
 					
 					switch(index){
 						case 0:

+ 149 - 0
src/components/m-wrong-preview/components/bottomBar.vue

@@ -0,0 +1,149 @@
+<script lang="tsx">
+import { defineComponent } from "vue";
+import store from "@/store";
+import { Dialog } from "vant";
+import { useRouter } from "vue-router";
+import { json } from "stream/consumers";
+import { Console } from "console";
+export default defineComponent({
+	props: {
+		subjectList:{
+			type:Array,
+			default:[]
+		},
+		currentSubjectIndex: {
+			type: Number,
+			default: 0,
+		},
+		subjectTotal: {
+			type: Number,
+			default: 0,
+		},
+		trueNum: {
+			type: Number,
+			default: 0,
+		},
+		falseNum: {
+			type: Number,
+			default: 0,
+		},
+		officialShow: Boolean,
+		showSubjectChangePopup: Boolean,
+	},
+	emits: {
+		"update:officialShow": (value: boolean) => {
+			return value;
+		},
+		"update:showSubjectChangePopup": (value: boolean) => {
+			return value;
+		},
+		nextSubject: () => {},
+		lastSubject: () => {},
+	},
+	setup(props, { emit }) {
+		console.log(props.subjectList,"props")
+		const router = useRouter();
+		const vehicle = useRouter().currentRoute.value.query.vehicle as string; //路由qvehicle参数
+		const subject = useRouter().currentRoute.value.query.subject as string; //路由subject参数
+		const isVip = store.getters.getIsVip;
+		console.log(router.currentRoute.value);
+		// const vipClick = () => {
+		// 	if (store.getters.getIsVip || router.currentRoute.value.path == "/exerciseFree") {
+		// 		emit("update:officialShow", true);
+		// 	} else {
+		// 		Dialog.confirm({
+		// 			title: "温馨提示",
+		// 			message: "请购买会员后使用",
+		// 			confirmButtonText: "购买",
+		// 		}).then(() => {
+		// 			router.push("/buyVip");
+		// 		});
+		// 	}
+		// };
+		const goWrongPreviewScore = () => {
+			Dialog.confirm({
+				message: "确认交卷吗?",
+			})
+				.then(() => {
+					
+				let wrongSet =	props.subjectList.filter((item:any,index)=>{
+						
+						return item.answer !== item.userAnswer
+					})
+					wrongSet.forEach((item:any)=>{
+						item.userAnswer = ""
+					})
+					window.localStorage.setItem(vehicle+subject,JSON.stringify(wrongSet))
+					router.push({
+						path:"/wrongPreviewRes",
+						query:router.currentRoute.value.query
+					})
+			
+
+					
+
+
+				})
+				.catch(() => {
+					console.log(props.subjectList,"props")
+				});
+		};
+
+		return () => {
+			return (
+				<van-tabbar placeholder route>
+					<van-tabbar-item
+						onClick={() => {
+							emit("lastSubject");
+						}}
+						v-slots={{
+							icon: () => <m-icon type="shangyiti" />,
+						}}>
+						上一题
+					</van-tabbar-item>
+					<van-tabbar-item
+						v-slots={{
+							icon: () => <m-icon type="dui" />,
+						}}>
+						{0}
+					</van-tabbar-item>
+					<van-tabbar-item
+						v-slots={{
+							icon: () => <m-icon type="cuo" />,
+						}}>
+						{0}
+					</van-tabbar-item>
+					<van-tabbar-item
+						onClick={() => {
+							emit("update:showSubjectChangePopup", true);
+						}}
+						v-slots={{
+							icon: () => <m-icon type="zongtishu" />,
+						}}>
+						{props.currentSubjectIndex + 1}/{props.subjectTotal}
+					</van-tabbar-item>
+
+					<van-tabbar-item
+						onClick={() => {
+							goWrongPreviewScore();
+						}}
+						v-slots={{
+							icon: () => <m-icon type="jiaojuan" />,
+						}}>
+						交卷
+					</van-tabbar-item>
+					<van-tabbar-item
+						onClick={() => {
+							emit("nextSubject");
+						}}
+						v-slots={{
+							icon: () => <m-icon type="xiayiti" />,
+						}}>
+						下一题
+					</van-tabbar-item>
+				</van-tabbar>
+			);
+		};
+	},
+});
+</script>

+ 123 - 0
src/components/m-wrong-preview/components/explainJq.vue

@@ -0,0 +1,123 @@
+<script lang="tsx">
+	import { defineComponent } from "vue";
+	export default defineComponent({
+		props: {
+			currentSubject: Object as any,
+			skillsShow: Boolean,
+		},
+		emits: {
+			subjectAudioPlay: (type: "读题" | "读官方解释" | "读技巧解释" | "读题+答案") => {
+				return undefined;
+			},
+			"update:skillsShow": (value: boolean) => {
+				return value;
+			},
+		},
+		setup(props, { emit }) {
+			return () => {
+				return (
+					<van-overlay show={props.skillsShow} z-index="10">
+						<div class="skills-box">
+							<div class="skills">
+								<div class="title">技巧讲解</div>
+								{!!props.currentSubject.explainGif && <img src={props.currentSubject.explainGif} class="img" />}
+								<van-divider class="divider">本题速记口诀</van-divider>
+								<div class="text">{props.currentSubject.explainJq}</div>
+								<div class="btn">
+									<span
+										onClick={() => {
+											emit("update:skillsShow", false);
+										}}>
+										关闭
+									</span>
+									<span
+										onClick={() => {
+											emit("subjectAudioPlay", "读技巧解释");
+										}}>
+										语音重播
+									</span>
+								</div>
+							</div>
+						</div>
+					</van-overlay>
+				);
+			};
+		},
+	});
+</script>
+
+<style lang="scss">
+	.skills-box {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		height: 100%;
+		.skills {
+			width: 290px;
+			background: #ffffff;
+			box-shadow: 0px 0px 8px rgba(124, 129, 136, 0.16);
+			border-radius: 10px;
+			display: flex;
+			flex-direction: column;
+			align-items: center;
+			padding: 20px 16px;
+			box-sizing: border-box;
+			.title {
+				font-size: 15px;
+				font-family: PingFang SC;
+				font-weight: bold;
+				line-height: 21px;
+				color: #0a1a33;
+			}
+			.img {
+				width: 258px;
+				height: 129px;
+				border: 1px solid #e8e8e8;
+				margin-top: 16px;
+			}
+			.divider {
+				margin-top: 20px;
+				color: #0a1a33;
+				background: #ffffff;
+			}
+			.text {
+				font-size: 13px;
+				font-family: PingFang SC;
+				font-weight: 400;
+				line-height: 19px;
+				color: #5c6066;
+				margin-top: 10px;
+			}
+			.btn {
+				width: 100%;
+				display: flex;
+				justify-content: space-between;
+				padding: 0 40px;
+				box-sizing: border-box;
+				margin-top: 20px;
+				span {
+					width: 76px;
+					height: 30px;
+					border-radius: 15px;
+					font-size: 13px;
+					display: flex;
+					justify-content: center;
+					align-items: center;
+					&:active {
+						background-color: #afaaaa;
+						filter: brightness(50%);
+					}
+					&:nth-of-type(1) {
+						border: 1px solid #707070;
+						color: #5c6066;
+					}
+					&:nth-of-type(2) {
+						background: #498ef5;
+						border: 1px solid #498ef5;
+						color: #ffffff;
+					}
+				}
+			}
+		}
+	}
+</style>

+ 121 - 0
src/components/m-wrong-preview/components/explainJs.vue

@@ -0,0 +1,121 @@
+<script lang="tsx">
+	import { defineComponent } from "vue";
+	export default defineComponent({
+		props: {
+			currentSubject: Object as any,
+			officialShow: Boolean,
+		},
+		emits: {
+			subjectAudioPlay: (type: "读题" | "读官方解释" | "读技巧解释" | "读题+答案") => {
+				return undefined;
+			},
+			"update:officialShow": (value: boolean) => {
+				return value;
+			},
+		},
+		setup(props, { emit }) {
+			return () => {
+				return (
+					<van-overlay show={props.officialShow} z-index="10">
+						<div class="skills-box">
+							<div class="skills">
+								<div class="title">官方解释</div>
+								<div class="text">{props.currentSubject.explainJs}</div>
+								<div class="btn">
+									<span
+										onClick={() => {
+											emit("update:officialShow", false);
+										}}>
+										关闭
+									</span>
+									<span
+										onClick={() => {
+											emit("subjectAudioPlay", "读官方解释");
+										}}>
+										语音重播
+									</span>
+								</div>
+							</div>
+						</div>
+					</van-overlay>
+				);
+			};
+		},
+	});
+</script>
+
+<style lang="scss">
+	.skills-box {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		height: 100%;
+		.skills {
+			width: 290px;
+			background: #ffffff;
+			box-shadow: 0px 0px 8px rgba(124, 129, 136, 0.16);
+			border-radius: 10px;
+			display: flex;
+			flex-direction: column;
+			align-items: center;
+			padding: 20px 16px;
+			box-sizing: border-box;
+			.title {
+				font-size: 15px;
+				font-family: PingFang SC;
+				font-weight: bold;
+				line-height: 21px;
+				color: #0a1a33;
+			}
+			.img {
+				width: 258px;
+				height: 129px;
+				border: 1px solid #e8e8e8;
+				margin-top: 16px;
+			}
+			.divider {
+				margin-top: 20px;
+				color: #0a1a33;
+				background: #ffffff;
+			}
+			.text {
+				font-size: 13px;
+				font-family: PingFang SC;
+				font-weight: 400;
+				line-height: 19px;
+				color: #5c6066;
+				margin-top: 10px;
+			}
+			.btn {
+				width: 100%;
+				display: flex;
+				justify-content: space-between;
+				padding: 0 40px;
+				box-sizing: border-box;
+				margin-top: 20px;
+				span {
+					width: 76px;
+					height: 30px;
+					border-radius: 15px;
+					font-size: 13px;
+					display: flex;
+					justify-content: center;
+					align-items: center;
+					&:active {
+						background-color: #afaaaa;
+						filter: brightness(50%);
+					}
+					&:nth-of-type(1) {
+						border: 1px solid #707070;
+						color: #5c6066;
+					}
+					&:nth-of-type(2) {
+						background: #498ef5;
+						border: 1px solid #498ef5;
+						color: #ffffff;
+					}
+				}
+			}
+		}
+	}
+</style>

+ 134 - 0
src/components/m-wrong-preview/components/functionList.vue

@@ -0,0 +1,134 @@
+<script lang="tsx">
+	import { defineComponent } from "vue";
+	import store from "@/store"
+	import { Dialog } from 'vant';
+	import { useRouter } from "vue-router";
+	export default defineComponent({
+		props: {
+			currentSubject: Object as any,
+			skillsShow: Boolean,
+		},
+		emits: {
+			subjectAudioPlay: (type: "读题" | "读官方解释" | "读技巧解释" | "读题+答案") => {
+				return undefined;
+			},
+			addCurrentQuestion: () => {
+				return undefined;
+			},
+			"update:skillsShow": (value: boolean) => {
+				return value;
+			},
+		},
+		setup(props, { emit }) {
+			//该功能就是vip专门使用
+	const router = useRouter()
+	const vipClick =(index :number)=>{
+				let vipButtonList :[any] = [{
+					name:"读题+答案"					
+				},
+				{
+					name:"读题"
+
+
+				},
+				{
+					name:"读技巧解释"
+
+
+					
+				}]
+				if (store.getters.getIsVip||router.currentRoute.value.path=="/exerciseFree") {
+					
+					switch(index){
+						case 0:
+							console.log("触发vip")
+							emit("subjectAudioPlay", vipButtonList[index].name);
+						break;
+						case 1:
+							emit("subjectAudioPlay", vipButtonList[index].name);
+						break;
+						case 2:
+								emit("subjectAudioPlay", vipButtonList[index].name);
+								emit("update:skillsShow", true);
+						break;
+
+					}
+
+				}
+				else{
+					Dialog.confirm({
+						 title: '温馨提示',
+  						  message: '请购买会员后使用',
+							confirmButtonText:"购买"
+   
+					}).then(()=>{
+						router.push("/buyVip")
+					})
+				}
+			}
+			return () => {
+				return (
+					<div class="function-list">
+						<div
+							class="function-item"
+							onClick={() => {
+								
+								emit("addCurrentQuestion");
+							}}>
+							<m-icon type={props.currentSubject?.isCollection ? "shoucanghuang" : "shoucanghui"} size="25px" />
+							<span>收藏</span>
+						</div>
+						<div
+							class="function-item"
+							onClick={()=>{
+								// emit("subjectAudioPlay", "读题+答案");
+								vipClick(0);
+							}}>
+							<m-icon type="a-dtda" size="25px" />
+							<span>读题+答案</span>
+						</div>
+						<div
+							class="function-item"
+							onClick={() => {
+								//emit("subjectAudioPlay", "读题");
+								vipClick(1);
+							}}>
+							<m-icon type="duti" size="25px" />
+							<span>读题</span>
+						</div>
+						<div
+							class="function-item"
+							onClick={() => {
+								vipClick(2);
+							}}>
+							<m-icon type="jqjj" size="25px" />
+							<span>技巧讲解</span>
+						</div>
+					</div>
+				);
+			};
+		},
+	});
+</script>
+
+<style lang="scss">
+.function-list {
+	width: 100%;
+	font-size: 13px;
+	display: flex;
+	justify-content: space-around;
+	padding: 15px;
+	box-sizing: border-box;
+	.function-item {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		font-size: 13px;
+		font-weight: 400;
+		color: #8a9099;
+		span {
+			margin-top: 5px;
+		}
+	}
+}
+</style>

+ 25 - 0
src/components/m-wrong-preview/components/modeSelection.vue

@@ -0,0 +1,25 @@
+<script lang="tsx">
+	import { Image, Loading } from "vant";
+	import { defineComponent } from "vue";
+	export default defineComponent({
+		props: {
+			currentSubject: Object as any,
+		},
+		setup(props) {
+			return () => {
+				return (
+					<div class="answerType">
+						<span v-for="(answerType, index) in answerTypeList" key={index} class={{ selected: currentType == index }} onClick={(currentType = index)}>
+							{answerType.name}
+						</span>
+						<span  class={{ selected: aotuPlayFlag }} onClick={aotuPlaySet}>
+							自动读题
+						</span>
+					</div>
+				);
+			};
+		},
+	});
+</script>
+
+<style lang="scss" scoped></style>

+ 63 - 0
src/components/m-wrong-preview/components/reciteMode.vue

@@ -0,0 +1,63 @@
+<script lang="tsx">
+	import { defineComponent } from "vue";
+	export default defineComponent({
+		props: {
+			currentSubject: Object as any,
+		},
+		setup(props) {
+			return () => {
+				return (
+					<>
+						<div>
+							{props.currentSubject.opts.map((item: any, index: number) => (
+								<div class="answer-box" key={Number(index)}>
+									<div class="choose-icon">{String.fromCharCode(65 + Number(index))}</div>
+									<span class={{ "answer-text": true, true: props.currentSubject.answer.includes(item) }}>{item}</span>
+								</div>
+							))}
+						</div>
+						<div class="checkbox-answer">答案: {props.currentSubject.answer.toString()}</div>
+					</>
+				);
+			};
+		},
+	});
+</script>
+
+<style lang="scss">
+	.choose-icon {
+		width: 30px;
+		height: 30px;
+		border-radius: 50%;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		font-size: 17px;
+		box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.16);
+		box-sizing: border-box;
+		margin-left: 5px;
+		margin-top: 1px;
+	}
+	.checkbox-answer {
+		padding: 8px 10px;
+		background-color: #f2f3f5;
+		margin-top: 25px;
+	}
+	.answer-box {
+		display: flex;
+		margin-top: 25px;
+		align-items: center;
+		.iconTrue {
+			background-color: #01c18d;
+		}
+		.answer-text {
+			margin-left: 10px;
+		}
+		.true {
+			color: #01c18d;
+		}
+		.false {
+			color: #ff4d53;
+		}
+	}
+</style>

+ 106 - 0
src/components/m-wrong-preview/components/subjectChangePopup.vue

@@ -0,0 +1,106 @@
+<template>
+	<div>
+		<van-cell center>
+			<template #title>
+				<div style="margin-top: 5px">
+					<span class="flex-center">
+						<m-icon type="dui" class="icon-size" />
+						<span style="margin-left: 5px">{{ trueNum }}</span>
+					</span>
+					<span class="flex-center" style="margin-left: 21px">
+						<m-icon type="cuo" class="icon-size" />
+						<span style="margin-left: 5px">{{ falseNum }}</span>
+					</span>
+				</div>
+			</template>
+			<!-- 使用 right-icon 插槽来自定义右侧图标 -->
+			<template #right-icon>
+				<div class="flex-center">
+					<m-icon type="zongtishu" class="icon-size" />
+					<span style="margin-left: 5px">{{ currentSubjectIndex + 1 }}/{{ subjectList.length }}</span>
+				</div>
+			</template>
+		</van-cell>
+		<div class="list-box">
+			<span :key="index" @click="changeCurrentSubjectIndex(index)" class="item" :class="subjectClass(item, index)" v-for="(item, index) in subjectList">{{ index + 1 }}</span>
+		</div>
+	</div>
+</template>
+
+<script setup lang="ts">
+	const props = defineProps<{
+		trueNum: number;
+		falseNum: number;
+		currentSubjectIndex: number;
+		subjectList: any[];
+	}>();
+
+	const emit = defineEmits<{
+		(event: "update:currentSubjectIndex", id: number): void;
+	}>();
+
+	const subjectClass = (item: any, index: number) => {
+		if (index === props.currentSubjectIndex) {
+			return "select";
+		}
+		if (item.isTrue == null) {
+			return "";
+		}
+		if (item.isTrue) {
+			return "true";
+		} else {
+			return "false";
+		}
+	};
+
+	const changeCurrentSubjectIndex = (index: number) => {
+		emit("update:currentSubjectIndex", index);
+	};
+</script>
+
+<style lang="scss" scoped>
+	.false {
+		background: rgba(255, 219, 220, 0.39);
+		border: 1px solid #ff4d53 !important;
+	}
+	.true {
+		background: rgba(204, 243, 232, 0.39);
+		border: 1px solid #01c18d !important;
+	}
+	.select {
+		background: rgba(138, 144, 153, 0.08);
+		border: 1px solid #bfbfbf;
+	}
+	.icon-size {
+		font-size: 22px;
+	}
+	.flex-center {
+		display: inline-flex;
+		justify-content: center;
+		align-items: center;
+	}
+	.list-box {
+		display: flex;
+		justify-content: space-around;
+		align-items: center;
+		height: 60vh;
+		flex-wrap: wrap;
+		overflow: auto;
+		.item {
+			width: 38px;
+			height: 38px;
+			border-radius: 50%;
+			border: 1px solid #bfbfbf;
+			font-size: 17px;
+			font-family: PingFang SC;
+			font-weight: 500;
+			line-height: 24px;
+			color: #5c6066;
+			letter-spacing: 0.2800000011920929px;
+			display: flex;
+			justify-content: center;
+			align-items: center;
+			margin: 10px;
+		}
+	}
+</style>

+ 53 - 0
src/components/m-wrong-preview/components/subjectContext.vue

@@ -0,0 +1,53 @@
+<script lang="tsx">
+	import { Image, Loading } from "vant";
+	import { defineComponent } from "vue";
+
+	export default defineComponent({
+		props: {
+			currentSubject: Object as any,
+			currentSubjectIndex: { require: true, type: Number, default: 0 },
+		},
+		setup(props) {
+			const slots = {
+				loading: () => <Loading type="spinner" size="20" />,
+				error: () => <span>加载失败</span>,
+			};
+
+			return () => {
+				return (
+					<div class="problem">
+						<span class="type">{props.currentSubject.type}</span>
+						<span class="text">
+							{props.currentSubjectIndex + 1}、{props.currentSubject.explain}
+						</span>
+						{props.currentSubject.image && <Image src={props.currentSubject.image} class="img" v-slots={slots} />}
+					</div>
+				);
+			};
+		},
+	});
+</script>
+
+<style lang="scss" scoped>
+	.problem {
+		.type {
+			width: 47px;
+			height: 24px;
+			background: #498ef5;
+			border-radius: 10px 10px 0px 10px;
+			font-size: 11px;
+			padding: 2px 7px;
+			margin-right: 5px;
+		}
+		.text {
+			font-family: PingFang SC;
+			font-weight: 400;
+			color: #0a1a33;
+			letter-spacing: 0.3px;
+		}
+		.img {
+			width: 100%;
+			margin-top: 10px;
+		}
+	}
+</style>

+ 237 - 0
src/components/m-wrong-preview/index.vue

@@ -0,0 +1,237 @@
+<template>
+	<!-- 导航栏 -->
+	<van-nav-bar :title="query.vehicle + '>' + query.name" left-arrow @click-left="back" fixed placeholder>
+		<!-- <template #right>
+			<m-icon type="shezhi" @click="setShow = true" />
+		</template> -->
+	</van-nav-bar>
+	<!-- 导航栏end -->
+	<!-- 答题模式选择 -->
+	<!-- <div class="answerType">
+		<span v-for="(answerType, index) in answerTypeList" :key="index" :class="{ selected: currentType == index }" @click="currentType = index">{{ answerType.name }}</span>
+		<span v-if="isVip" :class="{ selected: aotuPlayFlag }" @click="aotuPlaySet">自动读题</span>
+		 不是vip隐藏自动读题 
+		<span v-else class="visibility-hidden" :class="{ selected: aotuPlayFlag }">自动读题</span>
+	</div> -->
+	<!-- 答题模式选择end -->
+	<!-- 分割线 -->
+	<div class="divider" />
+	<!-- 题目模块 -->
+	<!-- 题目预加载 -->
+	<m-empty v-if="!currentSubject" />
+	<!-- 题目预加载end -->
+	<div class="problem-box" v-else>
+		<!-- 题目内容 -->
+		<subjectContext :currentSubject="currentSubject" :currentSubjectIndex="currentSubjectIndex" />
+		<!-- 背题模式展示 -->
+		<div v-if="typeParams.answerShow">
+			<reciteMode :currentSubject="currentSubject" />
+		</div>
+		<!-- 背题模式展示end -->
+		<!-- 选择内容 -->
+		<div v-else-if="currentSubject.isTrue === null">
+			<!-- 单选 -->
+			<van-radio-group v-model="currentSubject.userAnswer" v-if="currentSubject.type != '多选题'" @change="userAnswerChange" icon-size="35px">
+				<van-radio v-for="(item, index) in currentSubject.opts" :key="Number(index)" :name="item" class="answer"
+					>{{ item }}
+					<template #icon="props">
+						<div class="choose-icon" :class="{ selected: props.checked }">
+							{{ String.fromCharCode(65 + Number(index)) }}
+						</div>
+					</template>
+				</van-radio>
+			</van-radio-group>
+			<!-- 多选 -->
+			<div v-else>
+				<van-checkbox-group v-model="currentSubject.userAnswer" icon-size="35px">
+					<van-checkbox v-for="(item, index) in currentSubject.opts" :key="Number(index)" :name="item" class="answer"
+						>{{ item }}
+						<template #icon="props">
+							<div class="choose-icon" :class="{ selected: props.checked }">
+								{{ String.fromCharCode(65 + Number(index)) }}
+							</div>
+						</template>
+					</van-checkbox>
+				</van-checkbox-group>
+				<van-button round type="primary" class="checkbox-btn" :disabled="currentSubject.userAnswer.length == 0" @click="userAnswerChange">确定</van-button>
+			</div>
+		</div>
+		<!-- 展示答题后选择内容 -->
+		<div v-else>
+			<div>
+				<div v-for="(item, index) in currentSubject.optsBack" :key="Number(index)" class="answer-box">
+					<div class="choose-icon" :class="{ iconTrue: item.status % 2 !== 0 }" v-if="item.status < 2">
+						{{ String.fromCharCode(65 + Number(index)) }}
+					</div>
+					<m-icon v-else-if="item.status == 3" type="dui" size="30px" style="margin-left: 5px" />
+					<m-icon v-else-if="item.status == 2" size="30px" type="cuo" style="margin-left: 5px" />
+					<span class="answer-text" :class="{ true: item.status % 2 !== 0, false: item.status == 2 }">
+						{{ item.opt }}
+					</span>
+				</div>
+			</div>
+			<!-- <div class="checkbox-answer">答案: {{ currentSubject.answer.toString() }}</div> -->
+		</div>
+		<!-- 展示答题后选择内容end -->
+	</div>
+	<!-- 选择内容End -->
+	<!-- 分割线 -->
+	<!-- <van-divider /> -->
+	<!-- 功能选择列表 -->
+	<!-- <functionList :currentSubject="currentSubject" @subjectAudioPlay="subjectAudioPlay" @addCurrentQuestion="addCurrentQuestion" v-model:skillsShow="skillsShow" /> -->
+	<!-- 技巧讲解 -->
+	<!-- <explainJq :currentSubject="currentSubject" v-model:skillsShow="skillsShow" @subjectAudioPlay="subjectAudioPlay" /> -->
+	<!-- 官方解释 -->
+	<!-- <explainJs :currentSubject="currentSubject" v-model:officialShow="officialShow" @subjectAudioPlay="subjectAudioPlay" /> -->
+	<!-- 设置操作栏 -->
+	<!-- <van-popup v-model:show="setShow" position="bottom">
+		<van-cell center title="答对跳转下一题">
+			<template #right-icon>
+				<van-switch v-model="isJumpNext" size="24" />
+			</template>
+		</van-cell>
+		<van-cell center title="答题音效提示">
+			<template #right-icon>
+				<van-switch v-model="isSoundEffect" size="24" />
+			</template>
+		</van-cell>
+	</van-popup> -->
+	<!-- 设置操作栏end -->
+	<!-- 底部弹出题目选择栏 -->
+	<van-popup v-model:show="showSubjectChangePopup" position="bottom">
+		<subjectChangePopup :trueNum="trueNum" :falseNum="falseNum" v-model:currentSubjectIndex="currentSubjectIndex" :subjectList="subjectList" />
+	</van-popup>
+	<!-- 底部操作栏 -->
+	<bottomBar :currentSubjectIndex="currentSubjectIndex" :subjectList="subjectList" :subjectTotal="subjectTotal" :trueNum="trueNum" :falseNum="falseNum" v-model:showSubjectChangePopup="showSubjectChangePopup" v-model:officialShow="officialShow" @lastSubject="lastSubject" @nextSubject="nextSubject" />
+</template>
+
+<script lang="ts" setup>
+import { ref, defineProps } from "vue";
+import subjectChangePopup from "./components/subjectChangePopup.vue";
+import { useTopicMode, useAudioSet, useSubjectShowLogic } from "@/hooks/exercise";
+import { RouterBus } from "@/hooks";
+import subjectContext from "./components/subjectContext.vue";
+import functionList from "./components/functionList.vue";
+import explainJq from "./components/explainJq.vue";
+import explainJs from "./components/explainJs.vue";
+import reciteMode from "./components/reciteMode.vue";
+import bottomBar from "./components/bottomBar.vue";
+import store from "@/store";
+const {
+	router: { back },
+	route: { query },
+} = new RouterBus();
+
+const props = defineProps<{
+	listType: ExerciseType.ListType;
+}>();
+
+const isVip = store.getters.getIsVip;
+
+//答题模式选择逻辑
+const { answerTypeList, currentType, typeParams } = useTopicMode();
+
+//设置操作栏
+/**显示设置栏 */
+const setShow = ref(false);
+/**答题音效 */
+const isSoundEffect = ref(true);
+// 显示题目选择栏
+const showSubjectChangePopup = ref(false);
+
+
+
+//题目展示逻辑
+const { subjectList, currentSubject, currentSubjectIndex, subjectTotal, nextSubject, lastSubject, trueNum, falseNum, isJumpNext, userAnswerChange, addCurrentQuestion, skillsShow, officialShow } = useSubjectShowLogic(props.listType);
+//本地的数据
+console.log(subjectList)
+//音频模块
+const { aotuPlayFlag, subjectAudioPlay, aotuPlaySet } = useAudioSet(currentSubject);
+</script>
+
+<style lang="scss" scoped>
+.visibility-hidden{
+	visibility: hidden;
+
+}
+.parsing-img {
+	width: 100%;
+	margin-top: 10px;
+}
+.answerType {
+	width: 100%;
+	font-size: 13px;
+	display: flex;
+	justify-content: space-around;
+	padding: 15px;
+	box-sizing: border-box;
+	span {
+		border-radius: 20px;
+		padding: 3px 10px;
+		background-color: #b8c0cc;
+		color: #ffffff;
+	}
+	.selected {
+		background-color: #498ef5;
+	}
+}
+.divider {
+	width: 100%;
+	height: 10px;
+	background-color: #f2f3f5;
+}
+.problem-box {
+	font-size: 17px;
+	padding: 15px;
+	.answer {
+		margin-top: 25px;
+	}
+	.answer-box {
+		display: flex;
+		margin-top: 25px;
+		align-items: center;
+		.iconTrue {
+			background-color: #01c18d;
+		}
+		.answer-text {
+			margin-left: 10px;
+		}
+		.true {
+			color: #01c18d;
+		}
+		.false {
+			color: #ff4d53;
+		}
+	}
+
+	.choose-icon {
+		width: 30px;
+		height: 30px;
+		border-radius: 50%;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		font-size: 17px;
+		box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.16);
+		box-sizing: border-box;
+		margin-left: 5px;
+		margin-top: 1px;
+	}
+	.selected {
+		background-color: #498ef5;
+	}
+	.checkbox-btn {
+		width: 266px;
+		height: 40px;
+		margin: auto;
+		margin-top: 25px;
+		left: 50%;
+		transform: translateX(-50%);
+	}
+	.checkbox-answer {
+		padding: 8px 10px;
+		background-color: #f2f3f5;
+		margin-top: 25px;
+	}
+}
+</style>

+ 1 - 2
src/hooks/exercise/index.ts

@@ -21,8 +21,7 @@ export const useSubjectShowLogic = (type: ExerciseType.ListType) => {
 	const lastSubject = () => {
 		currentSubjectIndex.value > 0 && currentSubjectIndex.value--;
 	};
-
-	const { trueNum, falseNum, isJumpNext, userAnswerChange, addCurrentQuestion, skillsShow, officialShow } = useSubjectCheck(currentSubject, nextSubject);
+	const { trueNum, falseNum, isJumpNext, userAnswerChange, addCurrentQuestion, skillsShow, officialShow } = useSubjectCheck(currentSubject, nextSubject,type);
 
 	return {
 		currentSubject,

+ 18 - 3
src/hooks/exercise/list.ts

@@ -1,7 +1,8 @@
 import { ref, onBeforeMount, computed } from "vue";
-import { useRoute } from "vue-router";
+import { useRoute,useRouter } from "vue-router";
 import testModel from "@/model/test";
 
+
 /**获取题目列表 */
 export const useSubjectList = (type: ExerciseType.ListType) => {
 	type TestModelListResType = Awaited<ReturnType<typeof testModel.getList | typeof testModel.getFreeList>>;
@@ -9,14 +10,28 @@ export const useSubjectList = (type: ExerciseType.ListType) => {
 	const subjectList = ref<TestModelListResType["list"]>([]); //题目列表
 	const subjectTotal = ref(0); //题目总数
 	const query = useRoute().query; //路由query参数
+
+	const vehicle = useRouter().currentRoute.value.query.vehicle as string; //路由qvehicle参数
+	const subject = useRouter().currentRoute.value.query.subject as string; //路由subject参数
 	onBeforeMount(async () => {
 		let res: TestModelListResType;
 
-		if (type !== "free") {
+		if (type == "normal"||type=="test") {
 			res = await testModel.getList({
 				...query,
 			});
-		} else {
+		}
+		else if(type=="wrong"){
+			let wrongSet:[any] =  JSON.parse(window.localStorage.getItem(vehicle+subject)||"[]")
+			res  = {
+				total:wrongSet.length,
+				list:wrongSet
+			} as TestModelListResType
+			
+			
+
+		} 
+		else {
 			res = await testModel.getFreeList({
 				...query,
 			});

+ 5 - 3
src/hooks/exercise/wrong.ts

@@ -6,7 +6,7 @@ import { isArrSubset } from "@/utils/utils";
 import store from "@/store";
 
 /**错题与收藏 */
-export const useSubjectCheck = (currentSubject: ComputedRef<any>, nextSubject: () => Promise<void>) => {
+export const useSubjectCheck = (currentSubject: ComputedRef<any>, nextSubject: () => Promise<void>, _type?: ExerciseType.ListType) => {
 	const trueNum = ref(0); //正确数量
 	const falseNum = ref(0); //错误数量
 	const skillsShow = ref(false); //显示技巧讲解
@@ -81,7 +81,8 @@ export const useSubjectCheck = (currentSubject: ComputedRef<any>, nextSubject: (
 
 		if (JSON.stringify(currentSubject.value.answer) == JSON.stringify(currentSubject.value.userAnswer)) {
 			//答案正确
-			currentSubject.value.isTrue = true;
+			//类型是错误时候不显示对错
+			_type == "wrong" ? currentSubject.value.isTrue = null : currentSubject.value.isTrue = true
 			trueNum.value++;
 			if (isJumpNext.value) {
 				nextTick(() => {
@@ -97,7 +98,8 @@ export const useSubjectCheck = (currentSubject: ComputedRef<any>, nextSubject: (
 					questionId: currentSubject.value.id,
 				},
 			]);
-			currentSubject.value.isTrue = false;
+			//类型是错误时候不显示对错
+			_type == "wrong" ? currentSubject.value.isTrue = null : currentSubject.value.isTrue = false
 			//vip才显示错误解析
 			if (store.getters.getIsVip) {
 

+ 2 - 1
src/views/home/children/test/index.vue

@@ -33,7 +33,8 @@ import { Toast } from "vant";
 import { useRouter } from "vue-router";
 const store = useStore();
 const carTypeRef = ref<any>(null);
-
+//打印环境变量
+console.log(import.meta.env.MODE,"环境变量");
 nextTick(() => {
 	carTypeRef.value.scrollTo(store.state.carType);
 });

+ 20 - 6
src/views/mockTest/components/hooks.ts

@@ -1,7 +1,7 @@
 import { ref, watch, onBeforeMount, Ref, computed, nextTick, ComputedRef } from "vue";
 import * as API from "@/api";
 import { Howl, Howler } from "howler";
-import { useRoute } from "vue-router";
+import { useRoute, useRouter } from "vue-router";
 import testModel from "@/model/test";
 
 //答题模式切换
@@ -137,11 +137,13 @@ const useSubjectList = () => {
 	};
 };
 
-const useSubjectCheck = (nextSubject: () => Promise<void>) => {
+const useSubjectCheck = (nextSubject: () => Promise<void>, _type?: ExerciseType.ListType) => {
 	const trueNum = ref(0); //正确数量
 	const falseNum = ref(0); //错误数量
 	const isJumpNext = ref(false); //答对跳转下一题
-
+	const vehicle = useRouter().currentRoute.value.query.vehicle as string; //路由qvehicle参数
+	const subject = useRouter().currentRoute.value.query.subject as string; //路由subject参数
+	console.log(vehicle, subject)
 	/**
 	 * 选择答案后进行校验
 	 */
@@ -171,9 +173,21 @@ const useSubjectCheck = (nextSubject: () => Promise<void>) => {
 					});
 				}
 			} else {
-				console.log("错误");
-				currentSubject.isTrue = false;
 				falseNum.value++;
+				if (_type == "wrong") {
+					let wrongSet :[any] = JSON.parse(window.localStorage.getItem(vehicle + subject) || "[]") || []
+					currentSubject.userAnswer = ""
+					wrongSet.push(currentSubject)
+					window.localStorage.setItem(vehicle+subject,JSON.stringify(wrongSet)||"[]")
+					currentSubject.isTrue = null;
+			
+				}
+				currentSubject.isTrue = false;
+				
+				//错误题目的合集
+				console.log("答案错误");
+			
+			
 			}
 		} catch (error) {
 			console.log(error);
@@ -210,7 +224,7 @@ export const useSubjectShowLogic = () => {
 		}
 	};
 
-	const { trueNum, falseNum, isJumpNext, userAnswerChange } = useSubjectCheck(nextSubject);
+	const { trueNum, falseNum, isJumpNext, userAnswerChange } = useSubjectCheck(nextSubject, "wrong");
 
 	return {
 		currentSubject,

+ 106 - 93
src/views/mockTest/components/mockTestEnd.vue

@@ -23,118 +23,131 @@
 		<span class="evaluation">学车之路,任重而道远!路漫漫其修远兮,吾将上下而求索。</span>
 	</div>
 	<div class="btn-box">
-		<m-button class="btn1" text="错题重做" />
+		<m-button @click="goWrongPreview" class="btn1" text="错题重做" />
 		<m-button @click="againTest" class="btn2" text="重新测试" />
 	</div>
 </template>
 
 <script setup lang="ts">
-	import { computed } from "vue";
-	import dayjs from "dayjs";
-	import { createTestScores } from "@/api";
-	import { RouterBus } from "@/hooks";
-	const props = defineProps<{
-		userTestData: {
-			testScores: number;
-			useTime: number;
-		};
-	}>();
-	const emits = defineEmits(["next"]);
+import { computed } from "vue";
+import dayjs from "dayjs";
+import { createTestScores } from "@/api";
+import { RouterBus } from "@/hooks";
+import { useRouter } from "vue-router";
+const router = useRouter();
+const goWrongPreview = ()=>{
+	router.push({
+		path:"/wrongReview",
+		query:{
+			...router.currentRoute.value.query
+		}
+
+	})
+
 
-	const {
-		route: { query },
-	} = new RouterBus();
+}
+const props = defineProps<{
+	userTestData: {
+		testScores: number;
+		useTime: number;
+	};
+}>();
+const emits = defineEmits(["next"]);
 
-	const againTest = () => void emits("next");
+const {
+	route: { query },
+} = new RouterBus();
 
-	//是否通过考试
-	const testResult = computed(() => {
-		return props.userTestData.testScores >= 90;
-	});
-	//用时多久
-	const useTime = computed(() => {
-		return dayjs(props.userTestData.useTime).format("mm分ss秒");
-	});
+const againTest = () => void emits("next");
 
-	//提交考试成绩
-	createTestScores({
-		type: query.vehicle as string,
-		kskm: query.name as string,
-		score: props.userTestData.testScores,
-	});
+//是否通过考试
+const testResult = computed(() => {
+	return props.userTestData.testScores >= 90;
+});
+//用时多久
+const useTime = computed(() => {
+	return dayjs(props.userTestData.useTime).format("mm分ss秒");
+});
+
+//提交考试成绩
+createTestScores({
+	type: query.vehicle as string,
+	kskm: query.name as string,
+	score: props.userTestData.testScores,
+});
 </script>
 
 <style lang="scss" scoped>
-	.result-box {
-		display: flex;
-		flex-direction: column;
-		align-items: center;
-		padding: 30px 15px;
-		.img {
-			width: 246px;
-			height: 216px;
-		}
-		.text {
-			margin-top: 23px;
-			font-size: 15px;
-			font-family: PingFang SC;
-			font-weight: 400;
-			color: #5c6066;
-			line-height: 23px;
-		}
-		.fraction-box {
-			font-size: 15px;
+.result-box {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	padding: 30px 15px;
+	.img {
+		width: 246px;
+		height: 216px;
+	}
+	.text {
+		margin-top: 23px;
+		font-size: 15px;
+		font-family: PingFang SC;
+		font-weight: 400;
+		color: #5c6066;
+		line-height: 23px;
+	}
+	.fraction-box {
+		font-size: 15px;
+		font-family: PingFang SC;
+		font-weight: bold;
+		color: #8a9099;
+		line-height: 23px;
+		margin-top: 30px;
+		.fraction {
+			font-size: 60px;
 			font-family: PingFang SC;
 			font-weight: bold;
-			color: #8a9099;
-			line-height: 23px;
-			margin-top: 30px;
-			.fraction {
-				font-size: 60px;
-				font-family: PingFang SC;
-				font-weight: bold;
-				color: #ff4d53;
-			}
-		}
-		.hint {
-			font-size: 13px;
-			font-family: PingFang SC;
-			font-weight: 400;
-			color: #8a9099;
-			line-height: 23px;
-		}
-		.time {
-			font-size: 13px;
-			font-family: PingFang SC;
-			font-weight: 400;
-			color: #498ef5;
-			line-height: 23px;
-		}
-		.evaluation {
-			margin-top: 30px;
-			font-size: 15px;
-			font-family: PingFang SC;
-			font-weight: 400;
-			color: #5c6066;
-			line-height: 23px;
+			color: #ff4d53;
 		}
 	}
-	.btn-box {
-		margin: 45px auto;
-		width: 230px;
-		display: flex;
-		justify-content: space-between;
-		align-items: center;
-		color: #ffffff;
+	.hint {
+		font-size: 13px;
+		font-family: PingFang SC;
+		font-weight: 400;
+		color: #8a9099;
+		line-height: 23px;
+	}
+	.time {
+		font-size: 13px;
+		font-family: PingFang SC;
+		font-weight: 400;
+		color: #498ef5;
+		line-height: 23px;
+	}
+	.evaluation {
+		margin-top: 30px;
 		font-size: 15px;
 		font-family: PingFang SC;
 		font-weight: 400;
+		color: #5c6066;
 		line-height: 23px;
-		.btn1 {
-			background-color: #498ef5;
-		}
-		.btn2 {
-			background-color: #01c18d;
-		}
 	}
+}
+.btn-box {
+	margin: 45px auto;
+	width: 230px;
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	color: #ffffff;
+	font-size: 15px;
+	font-family: PingFang SC;
+	font-weight: 400;
+	line-height: 23px;
+	.btn1 {
+		background-color: #498ef5;
+	}
+	.btn2 {
+		background-color: #01c18d;
+	}
+}
 </style>

+ 47 - 0
src/views/wrongPreviewRes/index.vue

@@ -0,0 +1,47 @@
+<template>
+   	<div class="btn-box">
+		<m-button @click="goWrongPreview" class="btn1" text="错题重做" />
+		<m-button @click="againTest" class="btn2" text="放弃错题" />
+	</div>
+</template>
+
+<script lang="ts" setup>
+import { useRouter } from "vue-router"
+
+const router = useRouter()
+const goWrongPreview = ()=>{
+    router.push({
+        path:"/wrongReview",
+        query:router.currentRoute.value.query
+    })
+
+
+}
+const againTest = ()=>{
+
+}
+
+
+</script>
+
+<style lang="scss" scoped>
+.btn-box {
+	margin: 45px auto;
+	width: 230px;
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	color: #ffffff;
+	font-size: 15px;
+	font-family: PingFang SC;
+	font-weight: 400;
+	line-height: 23px;
+	.btn1 {
+		background-color: #498ef5;
+	}
+	.btn2 {
+		background-color: #01c18d;
+	}
+}
+
+</style>

+ 4 - 2
src/views/wrongReview/index.vue

@@ -1,8 +1,10 @@
 <template>
-	<m-exercise listType="free" />
+	<m-wrong-preview listType="wrong" />
 </template>
 
-<script></script>
+<script lang="ts" setup>
+
+</script>
 
 <style lang="scss" scoped>
 

+ 61 - 0
test.json

@@ -0,0 +1,61 @@
+[
+    {
+        "createTime": "2021-11-05 10:25:51",
+        "updateTime": null,
+        "id": 1746,
+        "number": 1522,
+        "answer": "×",
+        "answerkeyword": null,
+        "explainGif": null,
+        "explainJq": "题目中看到“可悬挂”.直接“答错”。",
+        "explainJs": "《道路交通安全法实施条例》第六十二条:驾驶机动车不得有在机动车驾驶室的前后窗范围内悬挂、放置妨碍驾驶人视线的物品的行为。",
+        "explainMp3": "https://t1-1305573081.file.myqcloud.com/qb/mp3/explain1747.mp3",
+        "image": null,
+        "imageYdt": null,
+        "issue": "在驾驶室的前窗范围内可放置玩偶、抱枕,后视镜上可悬挂平安扣等小挂件。",
+        "opts": [
+            "√",
+            "×"
+        ],
+        "skillkeyword": "可悬挂",
+        "titlekeyword": "可悬挂",
+        "issuemp3": "https://t1-1305573081.file.myqcloud.com/qb/issue/issue1747.mp3",
+        "answermp3": "https://t1-1305573081.file.myqcloud.com/qb/answer/answer1747.mp3",
+        "explainjsmp3": "https://t1-1305573081.file.myqcloud.com/qb/explainjs/explainJS1747.mp3",
+        "liceCar": "1",
+        "liceBus": "1",
+        "liceTruck": "1",
+        "liceMoto": "0",
+        "sequeIssue": "15",
+        "classIssue": "27",
+        "placeIssue": "0",
+        "excellIssue": "5",
+        "copyIssue": "0",
+        "mockIssue": "0",
+        "sequeIssueName": "驾驶证相关",
+        "placeIssueName": null,
+        "excellIssueName": "必学题五",
+        "classIssueName": "驾驶习惯",
+        "questionType": 1,
+        "subject": 1,
+        "classSort": 12,
+        "excellSort": 96,
+        "sequeSort": null,
+        "placeSort": null,
+        "explain": "在驾驶室的前窗范围内可放置玩偶、抱枕,后视镜上可悬挂平安扣等小挂件。",
+        "type": "判断题",
+        "userAnswer": "√",
+        "isTrue": null,
+        "isCollection": false,
+        "optsBack": [
+            {
+                "opt": "√",
+                "status": 2
+            },
+            {
+                "opt": "×",
+                "status": 1
+            }
+        ]
+    }
+]