Explorar el Código

管理端考试

wangxy hace 3 meses
padre
commit
329b70d5fd

+ 31 - 11
components/custom-tabbar/custom-tabbar-admin.vue

@@ -1,8 +1,11 @@
 <template>
 	<view class="ezy-custom-tabbar">
 		<view class="tabbar-item-box">
-			<view class="tabbar-item" v-for="(item, index) in tabList" :key="index" @click="switchTab(item.path,index)"
+			<!-- <view class="tabbar-item" v-for="(item, index) in tabList" :key="index" @click="switchTab(item.path,index)"
 				:style="{ backgroundImage: 'url(' + (currentTab == index ? item.activePath : item.iconPath) + ')' }">
+			</view> -->
+			<view class="tabbar-item" v-for="(item, index) in tabList" :key="index" @click="switchTab(item.path,index)">
+				{{item.text}}
 			</view>
 		</view>
 	</view>
@@ -16,19 +19,32 @@
 	export default {
 		data() {
 			return {
+				/* 		tabList: [{
+								iconPath: 'static/images/tabbar/unselect/plan-sj.png',
+								activePath: 'static/images/tabbar/select/plan-sj.png',
+								path: `/pages/study/index`
+							},
+							{
+								iconPath: 'static/images/tabbar/unselect/partner-sj.png',
+								activePath: 'static/images/tabbar/select/partner-sj.png',
+								path: '/pages/game/index'
+							},
+							{
+								iconPath: 'static/images/tabbar/unselect/my-sj.png',
+								activePath: 'static/images/tabbar/select/my-sj.png',
+								path: `/pages/my/index`
+							},
+						], */
 				tabList: [{
-						iconPath: 'static/images/tabbar/unselect/plan-sj.png',
-						activePath: 'static/images/tabbar/select/plan-sj.png',
+						text: '首页',
 						path: `/pages/study/index`
 					},
 					{
-						iconPath: 'static/images/tabbar/unselect/partner-sj.png',
-						activePath: 'static/images/tabbar/select/partner-sj.png',
+						text: '家政人员',
 						path: '/pages/game/index'
 					},
 					{
-						iconPath: 'static/images/tabbar/unselect/my-sj.png',
-						activePath: 'static/images/tabbar/select/my-sj.png',
+						text: '考证人员',
 						path: `/pages/my/index`
 					},
 				],
@@ -37,7 +53,9 @@
 		},
 		methods: {
 			switchTab(path, index) {
-				uni.redirectTo({url: path});
+				uni.redirectTo({
+					url: path
+				});
 				/* uni.navigateTo({
 					url: path,
 					"animationType": "fade-in",
@@ -48,17 +66,19 @@
 		},
 		created() {
 
-			this.currentTab  =this.currentTabNumber
+			this.currentTab = this.currentTabNumber
 		}
 	}
 </script>
 
 
 <style scoped>
-	.custom-tabbar {
+	.tabbar-item-box {
 		display: flex;
 		justify-content: space-around;
 		align-items: center;
+		height: 60px;
+		line-height: 60px;
 		/* 其他样式 */
 	}
 
@@ -70,4 +90,4 @@
 	.tab-icon {
 		/* 图标样式 */
 	}
-</style>
+</style>

+ 83 - 0
components/questions/danxuan.vue

@@ -0,0 +1,83 @@
+<template>
+	<view v-if="question" class="phone-danxuan-box">
+		<view>{{question.onlyNum}}、</view>
+		<!-- 题干区域 -->
+		<rich-text :nodes="data.name" class="phone-shiti-question"></rich-text>
+		<!-- 选项区域 -->
+		<view v-for="(item,index) in data.contents" class="danxuan-option-box" :class="formatClass(index)" :key="index">
+			<text class="option-change"  @click="onSelect(index)">{{item.number}}</text>
+			<rich-text :nodes="item.label" class="option-question"></rich-text>
+		</view>
+	</view>
+</template>
+
+<script setup>
+	import {
+		ref,
+		reactive,
+		watch
+	} from 'vue';
+	import {
+		useQuestionTools
+	} from "./useQuestionTools"
+	const {
+		getLetterByIndex
+	} = useQuestionTools();
+
+	const props = defineProps({
+		question: {
+			type: Object,
+		},
+		showError: {
+			type: Boolean,
+			default: false
+		}
+	})
+
+	const data = reactive({
+		name: '', //题干数据
+		contents: [], // 选项数据
+	})
+
+	watch(() => props.question, (val) => formatData(val), {
+		immediate: true
+	})
+
+	function formatClass(index) {
+		if (props.showError) {
+			return {
+				active_right: props.question.result == index,
+				showError: props.question.reply == index && props.question.result != index
+			}
+		} else {
+			return {
+				active: props.question.reply == index
+			}
+		}
+	}
+
+	function formatData(val) {
+		if (val) {
+			data.name = val.name;
+			data.contents = val.content.map((item, index) => {
+				return {
+					label: item,
+					number: getLetterByIndex(index)
+				}
+			})
+		}
+	}
+
+	function onSelect(index) {
+		if (props.showError) {
+			return;
+		}
+		props.question.reply = index;
+	}
+</script>
+
+<style lang="scss" scoped>
+	.active {
+		background-color: yellowgreen;
+	}
+</style>

+ 91 - 0
components/questions/duoxuan.vue

@@ -0,0 +1,91 @@
+<template>
+	<view v-if="question" class="phone-duoxuan-box">
+		<view>{{question.onlyNum}}、</view>
+		<!-- 题干区域 -->
+		<rich-text :nodes="data.name" class="phone-shiti-question"></rich-text>
+		<!-- 选项区域 -->
+		<view v-for="(item,index) in data.contents" class="duoxuan-option-box" :class="formatClass(index)" :key="index">
+			<text class="option-change" @click="onSelect(index)">{{item.number}}</text>
+			<rich-text :nodes="item.label" class="option-question"></rich-text>
+		</view>
+	</view>
+</template>
+
+<script setup>
+	import {
+		ref,
+		reactive,
+		watch
+	} from 'vue';
+	import {
+		useQuestionTools
+	} from "./useQuestionTools"
+	const {
+		getLetterByIndex,
+		haveSameElements
+	} = useQuestionTools();
+
+	const props = defineProps({
+		question: {
+			type: Object,
+		},
+		showError: {
+			type: Boolean,
+			default: false
+		}
+	})
+
+	const data = reactive({
+		name: '', //题干数据
+		contents: [], // 选项数据
+	})
+
+	watch(() => props.question, (val) => formatData(val), {
+		immediate: true
+	})
+
+
+	function formatClass(index) {
+		if (props.showError) {
+			return {
+				active_right: props.question.result.some(item => item == index),
+				showError: !props.question.result.some(item => item == index)
+			}
+		} else {
+			return {
+				active: props.question.reply.some(item => item == index)
+			}
+		}
+	}
+
+	function formatData(val) {
+		if (val) {
+			data.name = val.name;
+			data.contents = val.content.map((item, index) => {
+				return {
+					label: item,
+					number: getLetterByIndex(index)
+				}
+			})
+		}
+	}
+
+	function onSelect(index) {
+		if (props.showError) {
+			return;
+		}
+		if (props.question.reply) {
+			if (props.question.reply.some(item => item == index)) {
+				props.question.reply = props.question.reply.filter(item => item != index);
+			} else {
+				props.question.reply.push(index);
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.active {
+		background-color: yellowgreen;
+	}
+</style>

+ 50 - 0
components/questions/panduan.vue

@@ -0,0 +1,50 @@
+<template>
+	<view class="ezy-panduan-box">
+		<view>{{question.onlyNum}}、</view>
+		<!-- 题干区域 -->
+		<rich-text :nodes="question.name" class="ezy-shiti-question"></rich-text>
+		<!-- 选项区域 -->
+		<radio-group @change="radioChange" class="danxuan-option-box">
+			<label class="option-question" :class="formatClass('1')">
+				<radio value="1" :disabled="showError" :checked="question.reply == '1'"/>
+				<view>正确</view>
+			</label>
+			<label class="option-question" :class="formatClass('0')"> 
+				<radio value="0" :disabled="showError" :checked="question.reply == '0'"/>
+				<view>错误</view>
+			</label>
+		</radio-group>
+	</view>
+</template>
+
+<script setup>
+	const props = defineProps({
+		question: {
+			type: Object,
+		},
+		showError: {
+			type: Boolean,
+			default: false
+		}
+	})
+
+	function radioChange(e) {
+		if (props.showError) {
+			return;
+		}
+		props.question.reply = e.detail.value;
+	}
+	
+	function formatClass(index) {
+		if (props.showError) {
+			return {
+				active_right: props.question.result == index,
+				showError: props.question.reply == index && props.question.result != index
+			}
+		} else {
+			return {
+				active: props.question.reply == index
+			}
+		}
+	}
+</script>

+ 43 - 0
components/questions/tiankong.vue

@@ -0,0 +1,43 @@
+<template>
+	<view v-if="question" class="phone-tiankong-box">
+		<view>{{question.onlyNum}}、</view>
+		<!-- 题干区域 -->
+		<rich-text :nodes="question.name" class="phone-shiti-question"></rich-text>
+		<!-- 选项区域 -->
+		<view v-for="(item,index) in question.reply" class="tiankong-option-box" :key="index" :class="formatClass(index)">
+			<text>填空{{index+1}}:</text>
+			<input type="text" v-model="question.reply[index]" class="tk-answer-text" :placeholder="`请输入填空${index+1}答案`">
+		</view>
+	</view>
+</template>
+
+<script setup>
+	const props = defineProps({
+		question: {
+			type: Object,
+		},
+		showError: {
+			type: Boolean,
+			default: false
+		}
+	})
+
+	function formatClass(index) {
+		if (props.showError) {
+			return {
+				active_right: props.question.result[index].some(item => item == props.question.reply[index]?props.question.reply[index].trim(): ''),
+				showError: !props.question.result[index].some(item => item == props.question.reply[index]?props.question.reply[index].trim(): '')
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.tiankong-option-box {
+		margin-top: 10px;
+	}
+	.tk-answer-text {
+		border: 1px solid #ccc;
+		padding: 5px;
+	}
+</style>

+ 82 - 0
components/questions/useQuestionTools.js

@@ -0,0 +1,82 @@
+export function useQuestionTools() {
+	function getLetterByIndex(index) {
+		let letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+		if (index < 0 || index > 26) {
+			return '?';
+		}
+		return letters.charAt(index);
+	}
+
+	// 判断两个数组是否具有相同元素
+	function haveSameElements(arr1, arr2) {
+		// 如果两个数组的长度不同,它们不可能包含相同的元素
+		if (arr1.length !== arr2.length) {
+			return false;
+		}
+
+		// 对两个数组进行排序
+		arr1.sort((a, b) => a - b);
+		arr2.sort((a, b) => a - b);
+
+		// 比较排序后的数组是否相同
+		for (let i = 0; i < arr1.length; i++) {
+			if (arr1[i] != arr2[i]) {
+				return false;
+			}
+		}
+
+		// 如果所有元素都相同,返回 true
+		return true;
+	}
+
+	function checkDanxuanReply(item) {
+		if (!item.reply || item.reply === '' || item.reply === [] || item.reply.length === 0) {
+			return false;
+		} else {
+			return true;
+		}
+	}
+
+	function checkDuoxuanReply(item) {
+		if (!item.reply || item.reply === '' || item.reply === [] || item.reply.length === 0) {
+			return false;
+		} else {
+			for (const _item of item.reply) {
+				if (_item === '') {
+					return false;
+				}
+			}
+			return true;
+		}
+	}
+
+	function checkPanduanReply(item) {
+		if (!item.reply || item.reply === '' || item.reply === [] || item.reply.length === 0) {
+			return false;
+		} else {
+			return true;
+		}
+	}
+
+	function checkTiankongReply(item) {
+		if (!item.reply || item.reply === '' || item.reply === [] || item.reply.length === 0) {
+			return false;
+		} else {
+			for (const _item of item.reply) {
+				if (_item === '') {
+					return false;
+				}
+			}
+			return true;
+		}
+	}
+
+	return {
+		getLetterByIndex,
+		haveSameElements,
+		checkDanxuanReply,
+		checkDuoxuanReply,
+		checkPanduanReply,
+		checkTiankongReply
+	}
+}

+ 39 - 0
components/scoreAndAnswer/scoreAndAnswerAdmin.vue

@@ -0,0 +1,39 @@
+<template>
+	<uni-popup ref="popupRef" background-color="#fff" >
+		<view class="question-score-answer-admin">
+			<view>本体得分: <text>{{data.score}}分</text></view>
+			<view>您的答案:<text>{{data.reply}}</text></view>
+			<view>正确答案: <text>{{data.result}}</text></view>
+			<view>答案解析: 
+				<rich-text :nodes="data.answer"></rich-text>
+			</view>
+		</view>
+	</uni-popup>
+</template>
+
+<script setup>
+	import {ref,reactive} from "vue";
+	const popupRef = ref('popupRef');
+	const data = reactive({
+		score: 0,
+		reply: '',
+		result: '',
+		answer: '',
+	})
+	
+	function showPopup(options) {
+		console.log('options',options)
+		data.score = options.score;
+		data.reply = options.reply;
+		data.result = options.result;
+		data.answer = options.answer;
+		
+		popupRef.value.open('bottom')
+	}
+	
+	defineExpose({showPopup})
+</script>
+
+<style>
+
+</style>

+ 22 - 0
pages.json

@@ -25,6 +25,28 @@
 			{
 				"navigationBarTitleText" : "家政"
 			}
+		},
+		{
+			"path" : "pages/admin/Kaoshi/list",
+			"style" : 
+			{
+				"navigationBarTitleText" : "考试列表"
+			}
+		},
+		{
+			"path" : "pages/admin/Kaoshi/exam",
+			"style" : 
+			{
+				"navigationBarTitleText" : "考试",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path" : "pages/admin/Kecheng/list",
+			"style" : 
+			{
+				"navigationBarTitleText" : ""
+			}
 		}
 	],
 	 "tabBar": {

+ 580 - 0
pages/admin/Kaoshi/exam.vue

@@ -0,0 +1,580 @@
+<template>
+	<view class="phone-kaoshi-page">
+		<!-- 导航区域 -->
+		<view class="phone-navBar-box">
+			<view @click="handleBack" class="nav-bar-icon"><uni-icons type="left" size="20"></uni-icons></view>
+			<text class="nav-bar-title">{{data.ksName}}</text>
+		</view>
+		<!-- 第一行 -->
+		<view class="kaoshi-page-title">
+			<view v-if="activeSt">{{stTypes[activeSt.stTypeId]}}</view>
+			<view>100分钟</view>
+		</view>
+
+
+		<view class="kaoshi-shiti-content">
+			<!-- 内容区域 -->
+			<!-- 试题区域 -->
+			<view v-if="activeSt">
+				<template v-if="activeSt.stTypeId == 1">
+					<!-- 单选 -->
+					<danxuan :question="activeSt" :key="activeSt.stId"></danxuan>
+				</template>
+				<template v-if="activeSt.stTypeId == 2" >
+					<!-- 多选 -->
+					<duoxuan :question="activeSt" :key="activeSt.stId"></duoxuan>
+				</template>
+				<template v-if="activeSt.stTypeId == 3">
+					<!-- 判断 -->
+					<panduan :question="activeSt" :key="activeSt.stId"></panduan>
+				</template>
+				<template v-if="activeSt.stTypeId == 4">
+					<!-- 填空 -->
+					<tiankong :question="activeSt" :key="activeSt.stId"></tiankong>
+				</template>
+			</view>
+
+		</view>
+
+		<view class="kaoshi-bottom-card">
+			<view style="margin:0 10px">
+				<button class="biaoji-btn" type="primary" size="mini" @click="handleBiaoji">标记</button>
+			</view>
+			<view @click="showAnswerCard">
+				<text style="color: green;">{{activeSt ? activeSt.onlyNum: 0}}</text>/<text>{{data.StListForSearch.length}}</text>
+			</view>
+			<view style="flex:1;text-align: right;">
+				<button type="primary" size="mini" @click="handleCheckJiexi" style="margin-right: 10px">解析</button>
+				<button type="primary" size="mini" @click="handleBack">完成</button>
+			</view>
+		</view>
+		<template v-if="activeSt">
+			<button type="primary" size="mini" class="ks-btn-prev" @click="handlePrev" v-if="!isFistStId">上一题</button>
+			<button type="primary" size="mini" class="ks-btn-next" @click="handleNext" v-if="!isLastStId">下一题</button>
+		</template>
+
+		<!-- 答题卡 -->
+		<uni-popup ref="popupRef" background-color="#fff" :is-mask-click="false" :mask-click="false">
+			<view class="popup-content" style="height: 100vh">
+				<view class="popup-phone-navBar-box">
+					<view @click="handlePopupBack" class="nav-bar-icon"><uni-icons type="left" size="20"></uni-icons>
+					</view>
+					<text class="nav-bar-title">答题卡</text>
+				</view>
+				<view class="main-container">
+					<view class="paragraph" v-for="(paragraph,paragraphIndex) in questionData" :key="paragraphIndex">
+						<view class="paragraph-title">
+							{{paragraph.name}}
+						</view>
+						<view class="paragraph-qa-content">
+							<view class="paragraph-qa" v-for="(qa,qaIndex) in paragraph.qas" :key="qaIndex">
+								<view class="paragraph-qa-block">
+									<view :class="getQaClass(qa)" @click="answerCardItemClick(qa)">
+										{{qa.onlyNum}}
+									</view>
+								</view>
+							</view>
+						</view>
+
+					</view>
+				</view>
+			</view>
+		</uni-popup>
+		<!--
+		// 倒计时
+		<view v-if="!!data.endSecond">
+			<text>考试倒计时:</text>
+			<uni-countdown :show-day="false" :second="1000" @timeup="onTimeUp" :start="startCountDown"></uni-countdown>
+		</view>
+		-->
+		<!-- 答案解析 -->
+		<scoreAndAnswerVue ref="scoreAnswerRef"></scoreAndAnswerVue>
+		
+	</view>
+
+
+
+</template>
+
+<script setup>
+	import {
+		ref,
+		reactive,
+		computed,
+		watch
+	} from "vue";
+	import {
+		onLoad
+	} from "@dcloudio/uni-app";
+	import * as ksApi from "@/api/kaoshi.js"
+	import danxuan from "@/components/questions/danxuan.vue";
+	import duoxuan from "@/components/questions/duoxuan.vue";
+	import tiankong from "@/components/questions/tiankong.vue";
+	import panduan from "@/components/questions/panduan.vue";
+	import scoreAndAnswerVue from "@/components/scoreAndAnswer/scoreAndAnswerAdmin.vue";
+	import {useQuestionTools} from "@/components/questions/useQuestionTools.js";
+
+	const {
+		checkDanxuanReply,
+		checkDuoxuanReply,
+		checkPanduanReply,
+		checkTiankongReply,
+		getLetterByIndex
+	} = useQuestionTools();
+
+	const stTypes = {
+		1: '单选题',
+		2: '多选题',
+		3: '判断题',
+		4: '填空题',
+	}
+
+	const popupRef = ref(null)
+	const scoreAnswerRef = ref(null)
+
+	const startCountDown = ref(false);
+
+	const data = reactive({
+		ksId: null,
+		ksName: '',
+		stTotal: 0,
+		stScore: 0,
+		biaoji: null,
+		endSecond: 0,
+		pageSize: 0,
+		toggleScreenFlag: 0,
+		toggleScreenSecond: 0,
+		zhuapai: 0,
+		duanluo: [],
+		markDB: [],
+		StListForSearch: [],
+	})
+
+	const questionData = ref([]);
+
+	const progress = reactive({
+		dlIndex: 0,
+		dtIndex: 0
+	})
+
+	watch(() => data.duanluo, (newVal) => {
+		// 计算已答试题数量
+	}, {
+		deep: true
+	})
+
+	const activeSt = computed(() => {
+		if (questionData.value.length) {
+			return questionData.value.length && questionData.value[progress.dlIndex].qas[progress.dtIndex];
+		} else {
+			return null
+		}
+	})
+
+	const isFistStId = computed(() => {
+		if (data.StListForSearch.length) {
+			return data.StListForSearch[0].stId == activeSt.value.stId
+		} else {
+			return false
+		}
+	});
+	const isLastStId = computed(() => {
+		if (data.StListForSearch.length) {
+			return data.StListForSearch[data.StListForSearch.length - 1].stId == activeSt.value.stId
+		} else {
+			return false
+		}
+	});
+
+	onLoad((option) => {
+		data.ksId = option.ksId;
+		initKaoshi();
+	})
+
+	function getQaClass(qa) {
+		if (qa.marked && qa.marked === true) {
+			return 'paragraph-qa-block-mark';
+		} else {
+			if (qa.stTypeId == 1) {
+				if (checkDanxuanReply(qa)) {
+					return 'paragraph-qa-block-done';
+				} else {
+					return 'paragraph-qa-block-init';
+				}
+			} else if (qa.stTypeId == 2) {
+				if (checkDuoxuanReply(qa)) {
+					return 'paragraph-qa-block-done';
+				} else {
+					return 'paragraph-qa-block-init';
+				}
+			} else if (qa.stTypeId == 3) {
+				if (checkPanduanReply(qa)) {
+					return 'paragraph-qa-block-done';
+				} else {
+					return 'paragraph-qa-block-init';
+				}
+			} else if (qa.stTypeId == 4) {
+				if (checkTiankongReply(qa)) {
+					return 'paragraph-qa-block-done';
+				} else {
+					return 'paragraph-qa-block-init';
+				}
+			}
+		}
+	}
+
+	function skipQuestion(dlIndex, dtIndex) {
+		progress.dlIndex = dlIndex;
+		progress.dtIndex = dtIndex;
+		handlePopupBack()
+	}
+
+	function answerCardItemClick(qa) {
+		const actQa = data.StListForSearch.find(item => item.stId == qa.stId);
+		skipQuestion(actQa.dlIndex, actQa.dtIndex)
+
+	}
+
+	function handleBack() {
+		uni.redirectTo({
+			url: "/pages/admin/Kaoshi/list"
+		})
+	}
+
+	function onTimeUp() {
+		console.log('end')
+	}
+
+	function showAnswerCard() {
+		popupRef.value.open('bottom')
+	}
+
+	function handlePopupBack() {
+		popupRef.value.close()
+	}
+
+	function handlePrev() {
+		const qa = data.StListForSearch.find(item => item.stId == activeSt.value.stId);
+		const index = qa.num - 1;
+		if (index > 0) {
+			const result = data.StListForSearch[index - 1];
+			progress.dlIndex = result.dlIndex;
+			progress.dtIndex = result.dtIndex
+		}
+
+	}
+
+	function handleNext() {
+		const qa = data.StListForSearch.find(item => item.stId == activeSt.value.stId);
+		const index = qa.num - 1;
+		if (index < data.StListForSearch.length) {
+			const result = data.StListForSearch[index + 1];
+			progress.dlIndex = result.dlIndex;
+			progress.dtIndex = result.dtIndex
+		}
+	}
+
+	function formatDuanluoList(dlData) {
+		let uIndex = 0; // 试题num
+		let iDuanluo = 0; // 段落num
+		let result = [];
+		for (const duanluo of data.duanluo) {
+			let paragraph = {
+				qas: [],
+			};
+			paragraph.name = duanluo.name;
+
+			let iQa = 0; // 当前试题序号
+			let order = 0; // 当前题型中第几题
+			for (const iDanxuan of duanluo.danxuan) {
+				iDanxuan.type = 'danxuan';
+				iDanxuan.marked = false;
+				iDanxuan.onlyNum = uIndex + 1;
+				iDanxuan.order = order;
+				iDanxuan.iQa = iQa;
+				iDanxuan.reply = iDanxuan.result;
+				paragraph.qas.push(iDanxuan);
+				uIndex++;
+				order++;
+				iQa++;
+
+				data.StListForSearch.push({
+					stId: iDanxuan.stId,
+					paragraphName: paragraph.name,
+					dlIndex: iDuanluo,
+					dtIndex: iDanxuan.iQa,
+					num: iDanxuan.onlyNum
+				})
+			}
+			order = 0;
+			for (const iDuoxuan of duanluo.duoxuan) {
+				iDuoxuan.type = 'duoxuan';
+				iDuoxuan.marked = false;
+				iDuoxuan.onlyNum = uIndex + 1;
+				iDuoxuan.order = order;
+				paragraph.qas.push(iDuoxuan);
+				iDuoxuan.reply = iDuoxuan.result;
+				iDuoxuan.iQa = iQa;
+				uIndex++;
+				order++;
+				iQa++;
+
+				data.StListForSearch.push({
+					stId: iDuoxuan.stId,
+					paragraphName: paragraph.name,
+					dlIndex: iDuanluo,
+					dtIndex: iDuoxuan.iQa,
+					num: iDuoxuan.onlyNum
+				})
+			}
+			order = 0;
+			for (const iPanduan of duanluo.panduan) {
+				iPanduan.type = 'panduan';
+				iPanduan.marked = false;
+				iPanduan.onlyNum = uIndex + 1;
+				iPanduan.order = order;
+				paragraph.qas.push(iPanduan);
+				iPanduan.reply = iPanduan.result;
+				iPanduan.iQa = iQa;
+				uIndex++;
+				order++;
+				iQa++;
+
+				data.StListForSearch.push({
+					stId: iPanduan.stId,
+					paragraphName: paragraph.name,
+					dlIndex: iDuanluo,
+					dtIndex: iPanduan.iQa,
+					num: iPanduan.onlyNum
+				})
+			}
+			order = 0;
+			for (const iTiankong of duanluo.tiankong) {
+				iTiankong.type = 'tiankong';
+				iTiankong.marked = false;
+				iTiankong.onlyNum = uIndex + 1;
+				iTiankong.order = order;
+				paragraph.qas.push(iTiankong);
+				iTiankong.reply = iTiankong.result.map(item => item[0]);
+				iTiankong.iQa = iQa;
+				uIndex++;
+				order++;
+				iQa++;
+
+				data.StListForSearch.push({
+					stId: iTiankong.stId,
+					paragraphName: paragraph.name,
+					dlIndex: iDuanluo,
+					dtIndex: iTiankong.iQa,
+					num: iTiankong.onlyNum
+				})
+			}
+			iDuanluo++;
+			questionData.value.push(paragraph)
+
+			console.log(questionData.value)
+		}
+	}
+	
+	function handleBiaoji() {
+		activeSt.value.marked = !activeSt.value.marked;
+	}
+	
+	function handleCheckJiexi() {
+		const qa = activeSt.value ;
+		let score = qa.score;
+		let reply = '';
+		let result = '';
+		let answer = qa.answer;
+		if (qa.stTypeId == 1) {
+			// 单选题
+			if (qa.reply && qa.reply.trim() !== '') {
+				reply = getLetterByIndex(qa.reply)
+			}
+			
+			if (qa.result) {
+				result = getLetterByIndex(qa.result)
+			}
+		}
+		if (qa.stTypeId == 2) {
+			// 多选题
+			
+			if (qa.reply && qa.reply.length) {
+				reply = reply.map(item => {
+					if (item.trim()) {
+						return getLetterByIndex(item.trim())
+					}
+				})
+			}
+			if (qa.result) {
+				result = result.map(item => {
+					if (item.trim()) {
+						return getLetterByIndex(item.trim())
+					}
+				})
+			}
+		}
+		if (qa.stTypeId == 3) {
+			// 判断题
+			if (qa.reply == 0) {
+				reply = '错误'
+			}else if (qa.reply == 1) {
+				reply = '正确'
+			}
+			if (qa.result == 0) {
+				result = '错误'
+			}else if (qa.result == 1) {
+				result = '正确'
+			}
+		}
+		if (qa.stTypeId == 4) {
+			// 填空题
+		}
+		scoreAnswerRef.value.showPopup({
+			score,reply,result,answer
+		})
+	}
+
+	function initKaoshi() {
+		ksApi.getKaoshiInfo({
+			ksId: data.ksId
+		}).then(res => {
+			const {
+				ksId,
+				ksName,
+				stTotal,
+				stScore,
+				biaoji,
+				endSecond,
+				pageSize,
+				toggleScreenFlag,
+				toggleScreenSecond,
+				zhuapai,
+				duanluoList
+			} = res.data;
+			data.ksId = ksId;
+			data.ksName = ksName;
+			data.stTotal = stTotal;
+			data.stScore = stScore;
+			data.biaoji = biaoji;
+			data.endSecond = endSecond;
+			data.pageSize = pageSize;
+			data.toggleScreenFlag = toggleScreenFlag;
+			data.toggleScreenSecond = toggleScreenSecond;
+			data.zhuapai = zhuapai;
+			data.duanluo = duanluoList;
+			formatDuanluoList(data.duanluo);
+			uni.setNavigationBarTitle({
+				title: data.ksName
+			});
+			startCountDown.value = true;
+		})
+	}
+</script>
+
+<style lang="scss">
+	.phone-kaoshi-page {
+		background-color: #ccc;
+		position: relative;
+
+		.phone-navBar-box {
+			width: 100%;
+			height: 60px;
+			background-color: #fff;
+			text-align: center;
+			position: relative;
+			line-height: 60px;
+
+			.nav-bar-icon {
+				position: absolute;
+				left: 0;
+				top: 0
+			}
+		}
+
+		.kaoshi-page-title {
+			margin-top: 10px;
+			background-color: #fff;
+			height: 60px;
+			line-height: 60px;
+			display: flex;
+			justify-content: space-between;
+			padding: 0 10px;
+			border-bottom: 1px solid #333;
+		}
+
+		.kaoshi-shiti-content {
+			padding: 20px 20px 0 20px;
+			height: calc(100vh - 211px);
+			background-color: #fff;
+		}
+
+		.kaoshi-bottom-card {
+			height: 60px;
+			width: 100%;
+			background-color: #ccc;
+			display: flex;
+			align-items: center;
+			justify-content: flex-start;
+			text-align: left;
+			padding: 0 10px;
+			box-sizing: border-box;
+		}
+
+		.ks-btn-prev {
+			position: absolute;
+			bottom: 150px;
+			left: 0;
+		}
+
+		.ks-btn-next {
+			position: absolute;
+			bottom: 150px;
+			right: 0;
+		}
+
+		.popup-phone-navBar-box {
+			width: 100%;
+			height: 60px;
+			background-color: #fff;
+			text-align: center;
+			position: relative;
+			line-height: 60px;
+
+			.nav-bar-icon {
+				position: absolute;
+				left: 0;
+				top: 0
+			}
+		}
+
+		.paragraph-qa-content {
+			display: flex;
+			flex-wrap: wrap;
+
+			.paragraph-qa {
+				margin: 10px;
+				width: 40px;
+				height: 40px;
+				text-align: center;
+				line-height: 40px;
+				border: 1px solid #ccc;
+				border-radius: 50%;
+				overflow: hidden;
+			}
+			// 答题卡 答完样式
+			.paragraph-qa-block-done {
+				background-color: skyblue;
+			}
+			// 答题卡 未答完样式
+			.paragraph-qa-block-init {
+				background-color: #ccc;
+			}
+			// 答题卡 标记样式
+			.paragraph-qa-block-mark {
+				background-color: orange;
+			}
+		}
+	
+	}
+</style>

+ 232 - 0
pages/admin/Kaoshi/list.vue

@@ -0,0 +1,232 @@
+<template>
+	<view class="phone-kaoshi-page">
+		<!-- 查询职业 -->
+		<view style="padding: 10px">
+			<view class="phone-search-content">
+				<input class="search-input" placeholder="请输入职业" v-model="data.zyName" />
+				<view class="search-icon" @click="handleSearch">
+					<uni-icons type="search" size="20"></uni-icons>
+				</view>
+			</view>
+		</view>
+		<!-- 考试列表 -->
+		<view class="kaoshi-content-box">
+			<scroll-view scroll-y="true" refresher-enabled="true" :refresher-triggered="data.loading"
+				:refresher-threshold="50" refresher-background="transparent" @refresherrefresh="onRefresh"
+				class="kaoshi-scroll-view">
+				<uni-list>
+					<uni-list-item v-for="item in data.list" class="list-item-box">
+						<template v-slot:body>
+							<!-- 考试项 -->
+							<view class="item-kaoshi-row">
+								<!-- 考试名 + 等级 -->
+								<view class="ks-item-top row-item">
+									<view class="ks-name">{{item.ksName}}</view>
+									<view class="ks-zyLevelName">{{item.zyLevelName}}</view>
+								</view>
+								<!-- 时间 -->
+								<view class="ks-totalTm row-item">时间:{{item.totalTm}} 分钟</view>
+								<!-- 分数 -->
+								<view class="ks-score-content row-item">
+									<text class="ks-score">总分: {{item.ksScore}}</text>
+									<text class="ks-okScore">及格分: {{item.okScore}}</text>
+								</view>
+								<button type="primary" size="mini" @click="checkKaoshi(item)" class="kaoshi-btn">查看内容</button>
+							</view>
+						</template>
+					</uni-list-item>
+					<uni-load-more :status="data.state" @click="getMore(0)"
+						:contentText="data.contentText"></uni-load-more>
+				</uni-list>
+			</scroll-view>
+		</view>
+		<!-- 页面底端 -->
+		<customTabbarAdminVue></customTabbarAdminVue>
+	</view>
+</template>
+
+<script setup>
+	import customTabbarAdminVue from "@/components/custom-tabbar/custom-tabbar-admin.vue";
+	import {
+		ref,
+		reactive
+	} from "vue";
+	import {
+		onLoad
+	} from "@dcloudio/uni-app";
+	import {
+		getKaoshiList
+	} from "@/api/kaoshi.js"
+
+	const data = reactive({
+		zyName: '', // 职业名称
+		list: [], // 考试列表
+		loading: false,
+		page: 0,
+		size: 2,
+		state: 'more',
+		contentText: {
+			contentdown: '查看更多',
+			contentrefresh: '加载中',
+			contentnomore: '没有更多'
+		}
+	})
+
+	function handleSearch() {
+		data.page = 0;
+		refreshData();
+	}
+
+	function checkKaoshi(item) {
+		uni.navigateTo({
+			url: `/pages/admin/Kaoshi/exam?ksId=${item.ksId}`
+		})
+	}
+
+	function onRefresh() {
+		data.page = 0;
+		data.list = [];
+		data.loading = true;
+		refreshData();
+	}
+
+	function refreshData() {
+		const opt = {
+			page: 1,
+			size: data.size, // 固定查询10条
+			zyName: data.zyName
+		}
+		data.list = [];
+		// 数学
+		data.state = 'loading';
+		data.page++;
+		opt.page = data.page;
+
+		getKaoshiList(opt).then(res => {
+			data.list = data.list.concat(res.data.data);
+			data.loading = false;
+
+			if (res.data.total > data.list.length) {
+				data.state = 'more';
+				data.loading = false;
+			} else {
+				data.state = 'no-more';
+				data.loading = false;
+		
+			}
+		}).catch(err => {
+			data.state = 'more';
+			data.loading = false;
+		})
+	}
+
+	function getMore() {
+		const opt = {
+			page: 1,
+			size: data.size, // 固定查询10条
+			zyName: data.zyName
+		}
+		if (data.state == 'no-more') return;
+		data.state = 'loading';
+		data.page++;
+		opt.page = data.page;
+		getKaoshiList(opt).then(res => {
+			data.list = data.list.concat(res.data.data);
+			data.loading = false;
+
+			if (res.data.total > data.list.length) {
+				data.state = 'more';
+				data.loading = false;
+			} else {
+				data.state = 'no-more';
+				data.loading = false;
+			}
+		})
+	}
+
+	onLoad(() => {
+		getMore()
+	})
+</script>
+
+<style lang="scss" scoped>
+	.phone-kaoshi-page {
+		background-color: #ccc;
+		box-sizing: border-box;
+
+		// 查询区域
+		.phone-search-content {
+			position: relative;
+			background-color: #fff;
+			height: 42px;
+
+			.search-input {
+				height: 42px;
+				line-height: 40px;
+				border-radius: 20px;
+				border: 1px solid #ccc;
+				padding: 0 70px 0 20px;
+			}
+
+			.search-icon {
+				position: absolute;
+				right: 5px;
+				top: 4px;
+				padding: 6px;
+				background-color: #ccc;
+				border-radius: 20px;
+				width: 50px;
+				text-align: center;
+			}
+		}
+
+		// 列表区域
+		.item-kaoshi-row {
+			width: 100%;
+
+			.ks-item-top {
+				display: flex;
+				justify-content: space-between;
+
+				.ks-name {
+					font-weight: bold;
+					font-size: 18px;
+				}
+
+				.ks-zyLevelName {
+					padding: 2px 20px;
+					background-color: #ccc;
+					border-radius: 4px;
+				}
+			}
+
+			.ks-totalTm {}
+
+			.ks-score-content {
+				background-color: #ccc;
+				width: calc(100vw - 140px);
+
+				.ks-score {
+					padding-right: 10px
+				}
+
+				.ks-okScore {}
+			}
+
+			.row-item {
+				margin-bottom: 10px;
+			}
+
+			.kaoshi-btn {
+				width: 120px;
+				font-size: 16px;
+				margin-left: 0;
+			}
+
+		}
+	}
+	
+	.kaoshi-scroll-view {
+		height: calc(100vh - 166px);
+	}
+</style>

+ 170 - 0
pages/admin/Kecheng/list.vue

@@ -0,0 +1,170 @@
+<template>
+	<view class="phone-kecheng-page">
+		<!-- 查询职业 -->
+		<view style="padding: 10px">
+			<view class="phone-search-content">
+				<input class="search-input" placeholder="请输入职业名称" v-model="data.zyName" />
+				<view class="search-icon" @click="handleSearch">
+					<uni-icons type="search" size="20"></uni-icons>
+				</view>
+			</view>
+		</view>
+		<!-- 考试列表 -->
+		<view class="cuoti-content-box">
+			<scroll-view scroll-y="true" refresher-enabled="true" :refresher-triggered="data.loading" 
+				:refresher-threshold="50" refresher-background="transparent" @refresherrefresh="onRefresh"
+				class="cuoti-scroll-view">
+				<uni-list>
+					<uni-list-item v-for="item in data.list" class="list-item-box">
+						<template v-slot:body>
+							<!-- 数量 -->
+							<view class="item-cuoti-row">
+								<view>{{item.ksName}}</view>
+								<view>时间:{{item.totalTm}} 分钟</view>
+								<view>次数: {{item.maxTimes}}</view>
+								<view>总分: {{item.ksScore}}</view>
+								<view>及格分: {{item.okScore}}</view>
+							</view>
+							<view @click="checkKaoshi(item)" class="cuoti-btn">查看考试</view>
+						</template>
+					</uni-list-item>
+					<uni-load-more :status="data.state" @click="getMore(0)"
+						:contentText="data.contentText"></uni-load-more>
+				</uni-list>
+			</scroll-view>
+		</view>
+	</view>
+</template>
+
+<script setup>
+	import {
+		ref,reactive
+	} from "vue";
+	import {
+		onLoad
+	} from "@dcloudio/uni-app";
+	import * as kechengApi from "@/api/kecheng.js"
+
+	const data = reactive({
+		zyName: '', // 职业名称
+		list: [],  // 考试列表
+		loading: false,
+		page: 0,
+		size: 10,
+		state: 'more',
+		contentText: {
+			contentdown: '查看更多',
+			contentrefresh: '加载中',
+			contentnomore: '没有更多'
+		}
+	})
+	
+	function handleSearch() {
+		data.page = 0;
+		refreshData();
+	}
+
+	function checkKaoshi(item) {
+		uni.navigateTo({
+			url: `/pages/admin/Kaoshi/exam?ksId=${item.ksId}`
+		})
+	}
+	
+	function onRefresh() {
+		data.page = 0;
+		data.list = [];
+		data.loading = true;
+		refreshData();
+	}
+	
+	function refreshData() {
+		const opt = {
+			page: 1,
+			size: 10, // 固定查询10条
+			zyName: data.zyName
+		}
+		data.list = [];
+		// 数学
+		data.state = 'loading';
+		data.page++;
+		opt.page = data.page;
+		
+		kechengApi.getKechengGlList(opt).then(res =>{
+			data.list = data.list.concat(res.data.data);
+			data.loading = false;
+			
+			if (res.data.total >= data.list.length) {
+				data.state = 'no-more';
+				data.loading = false;
+			} else {
+				data.state = 'more';
+				data.loading = false;
+			}
+		}).catch(err => {
+			data.state = 'more';
+			data.loading = false;
+		})
+	}
+
+	function getMore() {
+		const opt = {
+			page: 1,
+			size: 10, // 固定查询10条
+			zyName: data.zyName
+		}
+		if (data.state == 'no-more') return;
+		data.state = 'loading';
+		data.page++;
+		opt.page = data.page;
+		kechengApi.getKechengGlList(opt).then(res =>{
+			data.list = data.list.concat(res.data.data);
+			data.loading = false;
+			
+			if (res.data.total >= data.list.length) {
+				// 数学
+				data.state = 'no-more';
+				data.loading = false;
+			} else {
+				// 数学
+				data.state = 'more';
+				data.loading = false;
+			}
+		})
+	}
+	
+	onLoad(() => {
+		getMore()
+	})
+</script>
+
+<style lang="scss" scoped>
+	.phone-kecheng-page {
+		background-color: #ccc;
+		box-sizing: border-box;
+	}
+
+	.phone-search-content {
+		position: relative;
+		background-color: #fff;
+		height: 42px;
+
+		.search-input {
+			height: 42px;
+			line-height: 40px;
+			border-radius: 20px;
+			border: 1px solid #ccc;
+			padding: 0 70px 0 20px;
+		}
+
+		.search-icon {
+			position: absolute;
+			right: 5px;
+			top: 4px;
+			padding: 6px;
+			background-color: #ccc;
+			border-radius: 20px;
+			width: 50px;
+			text-align: center;
+		}
+	}
+</style>