Browse Source

英语题追加音频播放

wangxy 4 months ago
parent
commit
2471460863

+ 8 - 1
components/chengji/chengji.vue

@@ -54,11 +54,13 @@
 			</view>
 		<!-- 解析浮层数据 -->
 		<questionJiexi ref="jiexiRef" :cardId="cardId"></questionJiexi>
+		<questionJiexiYingyu ref="jiexiRefYingyu" :cardId="cardId"></questionJiexiYingyu>
 	</uni-popup>
 </template>
 
 <script setup>
 	import questionJiexi from '@/components/questionJiexi/questionJiexi.vue';
+	import questionJiexiYingyu from '@/components/questionJiexi/questionJiexiYingyu.vue';
 	import wSwiper from '@/components/wSwiper/wSwiper.vue';
 	import danxuan from "@/components/question/danxuan.vue";
 	import panduan from "@/components/question/panduan.vue";
@@ -91,6 +93,7 @@
 	const current = ref(0)
 	const popupRef = ref(null)
 	const jiexiRef = ref(null);
+	const jiexiRefYingyu = ref(null);
 
 	// 切换成绩
 	function showPopup() {
@@ -103,7 +106,11 @@
 
 	// 展示
 	function showJiexiPopup(data) {
-		jiexiRef.value.showPopup(data);
+		if (data.type != 4 ) {
+			jiexiRef.value.showPopup(data);
+		} else {
+			jiexiRefYingyu.value.showPopup(data);
+		}
 	}
 
 	function handleBack() {

+ 88 - 0
components/question/yingyu/danxuan.vue

@@ -0,0 +1,88 @@
+<template>
+	<view v-if="question" class="ezy-yingyu-danxuan-box">
+		<!-- 标题区域 -->
+		<view class="yingyu-danxuan-title"></view>
+		<!-- 题干区域 -->
+		<textReplaceIconVue :question="question" :text="question.name" :placeholders="placeholders">
+		</textReplaceIconVue>
+		<!-- 选项区域 -->
+		<view v-for="(item,index) in data.contents" class="yingyu-danxuan-option-box" :class="formatClass(index)" :key="index" @click="onSelect(index)">
+			<text class="option-change">{{item.number}}</text>
+			<textReplaceIconVue :question="question" :text="item.label" :placeholders="placeholders" @itemclick="onSelect(index)">
+			</textReplaceIconVue>
+		</view>
+	</view>
+</template>
+
+<script setup>
+	import {
+		ref,
+		reactive,
+		watch
+	} from 'vue';
+	import {
+		useQuestionTools
+	} from "../useQuestionTools"
+
+	import textReplaceIconVue from './textReplaceIcon.vue'
+	
+	const {
+		getLetterByIndex
+	} = useQuestionTools();
+	
+	const props = defineProps({
+		question: {
+			type: Object,
+		},
+		showError: {
+			type: Boolean,
+			default: false
+		},
+		placeholders: { // 占位符
+			type: Array,
+			required: true
+		},
+	})
+	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.optList.map((item, index) => {
+				return {
+					label: item,
+					number: getLetterByIndex(index)
+				}
+			})
+		}
+	}
+	
+	function onSelect(index) {
+		console.log('onSelect',index)
+		if (props.showError) {
+			return;
+		}
+		props.question.reply = index;
+	}
+
+</script>
+

+ 32 - 0
components/question/yingyu/mtaRadio.vue

@@ -0,0 +1,32 @@
+<template>
+	<view></view>
+</template>
+
+<script setup>
+	import {
+		useAudio
+	} from './useAudio';
+	let historyData = null;
+	const {
+		handlePlay,
+		handleStop,
+		innerAudioContext
+	} = useAudio();
+	// 播放
+	uni.$on('do-yy-audio-play', (data) => {
+		// console.log(`playing ${data.stId}:`, data)
+		handlePlay(data)
+	})
+	// 暂停
+	uni.$on('do-yy-audio-stop', () => {
+		// console.log(`ending ${data.stId}:`, data)
+		handleStop()
+	})
+	// 销毁
+	uni.$on('destory-stop', () => {
+		handleStop()
+	})
+</script>
+
+<style>
+</style>

+ 182 - 0
components/question/yingyu/textReplaceIcon.vue

@@ -0,0 +1,182 @@
+<template>
+	<view v-html="formattedText"  :myflag="myflag" :question="question" @click.native="handleClick"
+			:change:myflag="YY.updateFlag" :change:question="YY.watchQuestionChange"></view>
+</template>
+
+<script>
+	import { debounce } from '@/utils/common';
+	export default {
+		props: {
+			placeholders: { // 占位符
+				type: Array,
+				required: true
+			},
+			text: {
+				type: String,
+			},
+			showError: {
+				type: Boolean,
+				default: false
+			},
+			question: {
+				type: Object,
+			},
+		},
+		data() {
+			return {
+				myflag: 0,
+				isplaying: null,
+				num: 0,
+			}
+		},
+		computed: {
+			// 计算属性,用于生成填空后的文本
+			formattedText() {
+				let result = this.text;
+				this.placeholders.forEach((placeholder, index) => {
+					// 使用正则表达式全局替换占位符
+					const regex = new RegExp(`\\${placeholder}`, 'g');
+					if (!this.isplaying) {
+						result = result.replace(regex,
+							`<view class="yy-box"><view	class="yingyu canplay" id="t_${this.question.stId}_${index}">【图片1】</view></view>`
+						);
+					} else if (this.question.stId == this.isplaying.stId && this.isplaying.index == index) {
+						result = result.replace(regex,
+							`<view class="yy-box"><view	class="yingyu playing" id="t_${this.question.stId}_${index}">【图片2】</view></view>`
+						);
+					} else {
+						result = result.replace(regex,
+							`<view class="yy-box"><view	class="yingyu canplay" id="t_${this.question.stId}_${index}">【图片1】</view></view>`
+						);
+					}
+				});
+				this.myflag++;
+				return result;
+			},
+		},
+		methods: {
+			handleClick() {
+				this.$emit('@itemclick')
+			},
+			audioClick(data) {
+				if (this.isplaying) {
+					uni.$emit('do-yy-audio-stop', data);
+					return;
+				}
+			
+				// 重复播放
+				if (this.isplaying && data.stId == this.isplaying.stId && data.index == this.isplaying.index) {
+					uni.$emit('do-yy-audio-stop', data);
+					return;
+				}
+				
+				// 初次播放
+				if (!this.isplaying) {
+					uni.$emit('do-yy-audio-play', data);
+					return;
+				}
+		
+			}
+		},
+		created() {
+			// 音频播放 
+			uni.$on('yy-audio-playing',(data) => {
+				console.log('ccc1')
+				if (this.isplaying) {
+					// 存在播放实例 并且播放非同一音频
+					if (this.isplaying.stId != data.value.stId) {
+						this.isplaying = null;
+					} 
+				} else {
+					// 不存在播放实例 
+					this.isplaying = data;
+				}
+			})
+			// 音频异常  重置音频
+			uni.$on('yy-audio-error',(data) => {
+				this.isplaying = null;
+			})
+			// 音频自然播放结束  重置音频
+			uni.$on('yy-audio-end',(data) => {
+				this.isplaying = null;
+			})
+			// 音频销毁 重置音频
+			uni.$on('destory-stop',(data) => {
+				this.isplaying = null;
+			})
+			// 试题切换 重置音频
+			uni.$on('swiper-change',() => {
+				this.isplaying = null;
+			})
+			// 解析关闭 重置音频
+			uni.$on('question-jiexi-close',() => {
+				this.isplaying = null;
+			})
+			uni.$on('unitTest-submit',() => {
+				this.isplaying = null;
+			})
+		}
+	}
+</script>
+<script module="YY" lang="renderjs">
+	import {debounce} from "@/utils/common.js"
+	export default {
+		props: {
+			showError: {
+				type: Boolean,
+				default: false
+			},
+			placeholders: { // 占位符
+				type: Array,
+				required: true
+			},
+		},
+		data() {
+			return {
+				myQ: null,
+				callbacks: []
+			}
+		},
+		methods: {
+			updateFlag() {
+				this.initListener(this.myQ)
+			},
+			updateFn({stId,index}) {
+				const url = this.myQ.audioList[index]
+				this.$ownerInstance.callMethod('audioClick', {stId,index,url});
+			},
+
+			watchQuestionChange(newValue, oldValue, ownerInstance, instance) {
+				if (newValue) {
+					this.myQ = newValue;
+					this.initListener(newValue)
+				}
+			},
+			initListener(question) {
+				if (!question) {
+					return;
+				}
+				if (this.showError) {
+					return;
+				}
+				question.placeholders.forEach((item, index) => {
+					const dom = document.getElementById(`t_${question.stId}_${index}`)
+					if (dom) {
+						const qaindex = this.callbacks.findIndex(item => item.index === index)
+						if (qaindex>-1) {
+							dom && dom.removeEventListener('click', this.callbacks[qaindex].callback);
+							this.callbacks = this.callbacks.filter(item => item.index !=qaindex);
+						}
+						const callbackFn = debounce(() => { this.updateFn({ stId: question.stId, index})},100)
+						this.callbacks.push({index: index, callback: callbackFn
+						})
+						dom && dom.addEventListener('click', this.callbacks.find(cIte => cIte.index === index).callback)
+					}
+					
+				})
+			},
+		},
+	}
+</script>
+<style>
+</style>

+ 72 - 0
components/question/yingyu/useAudio.js

@@ -0,0 +1,72 @@
+import {nextTick, ref} from "vue";
+import { debounce } from "@/utils/common";
+
+export function useAudio() {
+	const innerAudioContext = ref(null);
+	const options = ref(null);
+	function createAudio(audioUrl) {
+		const innerAudioContext = uni.createInnerAudioContext();
+		innerAudioContext.autoplay = false;
+		innerAudioContext.src = audioUrl;
+		innerAudioContext.onPlay(() => {
+			// 播放
+			uni.$emit('yy-audio-playing', options);
+		});
+		innerAudioContext.onError((res) => {
+		  uni.$emit('yy-audio-error')
+		});
+		innerAudioContext.onEnded(() => {
+			// 播放结束
+			uni.$emit('yy-audio-end',options)
+		})
+		return innerAudioContext;
+	}
+	function changeSource(url) {
+		innerAudioContext.src= url;
+		innerAudioContext.currentTime = 0;
+	}
+	
+	const debuncePlay = debounce((myOption) => {
+		innerAudioContext.value = createAudio(myOption.url);
+		innerAudioContext.value.play()
+	},50)
+	
+	function handlePlay(data) {
+		if (innerAudioContext.value) {
+			innerAudioContext.value.stop();
+			innerAudioContext.value.destroy();
+			innerAudioContext.value = null;
+			uni.$emit('destory-stop')	
+		}
+		options.value = data;
+		debuncePlay(data);
+	}
+	
+	function handleStop() {
+		// console.log('stop')
+		if (innerAudioContext.value) {
+			innerAudioContext.value.destroy();
+			innerAudioContext.value = null;
+			uni.$emit('destory-stop')	
+		}
+	}
+	// 试题切换停止播放 
+	uni.$on('swiper-change',() => {
+		handleStop();
+	})
+	// 解析关闭 停止播放
+	uni.$on('question-jiexi-close',() => {
+		handleStop();
+	})
+	// 交卷 停止播放
+	uni.$on('unitTest-submit', () => {
+		handleStop();
+	})
+	
+	return {
+		options,
+		handlePlay,
+		handleStop,
+		innerAudioContext
+	}
+}

+ 40 - 27
pages/test/test.vue

@@ -1,42 +1,55 @@
 <template>
 	<view>
-		<tiankongVue :question="qa" :placeholders="['[bank]', '[bank1]']"></tiankongVue>	
-		<!-- <danxuan v-for="item in list" :question="item" ></danxuan>	 -->
+		<view>试题1:</view>
+		<yingyu v-if="qa" :question="qa" :placeholders="qa.placeholders"></yingyu>
+		<hr>
+		<view>试题2:</view>
+		<yingyu v-if="qa2" :question="qa2" :placeholders="qa2.placeholders"></yingyu>
 	</view>
+	<!-- <mtaRadio></mtaRadio> -->
 </template>
 
 <script setup>
-	import tiankongVue from '@/components/question/tiankong.vue'
-	import panduan from '@/components/question/panduan.vue'
-	import danxuan from '@/components/question/danxuan.vue'
-	import * as httpUnit from "@/api/unitTest.js"
-	import {onLoad} from "@dcloudio/uni-app"
+	import yingyu from '@/components/question/yingyu/danxuan.vue'
+	import mtaRadio from '@/components/question/yingyu/mtaRadio.vue'
+	import {
+		onLoad
+	} from "@dcloudio/uni-app"
 	import {
 		catchError,
 		toast
 	} from "@/utils/common.js"
-	import { ref } from "vue"
-	
-	const list = ref([]);
-	
-	const qa = ref({name: '<p>这是题干[bank]!hahah[bank1],aaa</p>',stId: 1,reply: ['',''],result: ['', '']})
-	
-	async function initPage() {
-		const [err, cdata] = await catchError(httpUnit.getExamData({
-			jieId: 1
-		}));
-		if (err) {
-			toast("单元测试数据获取异常");
-			return;
-		}
-		console.log(cdata)
-		list.value = cdata;
-	}
-	onLoad(() => {
-		initPage()
+	import {
+		ref
+	} from "vue"
+
+	const qa = ref({
+		answer: "1111",
+		jiangjie: "1111",
+		name: "这是文本区域 Hello [radio1] 哈哈,你看到 World [radio2] 就是音频,听我说 Good [radio3]。",
+		optList: [
+			"string"
+		],
+		result: 0,
+		stId: 0,
+		type: 5,
+		placeholders: ['[radio1]','[radio2]','[radio3]']
 	})
+	const qa2 = ref({
+		answer: "1111",
+		jiangjie: "1111",
+		name: "这是文本区域 Yes [radio1]哈哈,你看到 No [radio2]就是音频,听我说 So [radio3]。",
+		optList: [
+			"string"
+		],
+		result: 0,
+		stId: 1,
+		type: 5,
+		placeholders: ['[radio1]','[radio2]','[radio3]']
+	})
+
 </script>
 
 <style>
 
-</style>
+</style>

+ 5 - 4
pages/unitTest/index.vue

@@ -12,6 +12,7 @@
 						<danxuan :question="item" v-if="item.type == '1'"></danxuan>
 						<panduan :question="item" v-if="item.type == '2'"></panduan>
 						<tiankong :question="item" v-if="item.type == '3'" :placeholders="item.placeholders"></tiankong>
+						<yingyuDanxuan :question="item" v-if="item.type == '4'" :placeholders="item.placeholders"></yingyuDanxuan>
 					</view>
 				</template>
 			</w-swiper>
@@ -32,17 +33,20 @@
 		<!-- 填空 -->
 		<FillItem :value="result" ref="popupRef" @blur="onBlur"></FillItem>
 
-
+		<!-- 音频播放组件 -->
+		<mtaRadio></mtaRadio>
 	</view>
 
 </template>
 
 <script setup>
+	import mtaRadio from '@/components/question/yingyu/mtaRadio.vue'
 	import FillItem from "@/components/question/FillItem.vue";
 	import wSwiper from '@/components/wSwiper/wSwiper.vue';
 	import danxuan from "@/components/question/danxuan.vue";
 	import panduan from "@/components/question/panduan.vue";
 	import tiankong from "@/components/question/tiankong.vue";
+	import yingyuDanxuan from "@/components/question/yingyu/danxuan.vue";
 	import chengji from "@/components/chengji/chengji.vue";
 	import uniPointsVue from '@/components/points/uni-points.vue';
 	import * as httpUnit from "@/api/unitTest.js"
@@ -125,8 +129,6 @@
 		})
 	}
 	
-	
-	
 	function handleBack() {
 		// 数学
 		uni.redirectTo({
@@ -151,7 +153,6 @@
 				break;
 		}
 	}
-	
 
 </script>
 

+ 10 - 0
pages/unitTest/useUnit.js

@@ -129,8 +129,17 @@ export function useExam() {
 				item.placeholders = item.result.map((item, cindex) => `[bank${cindex+1}]`)
 				item.reply = item.reply ? JSON.parse(item.reply): item.result.map(() => '');
 			}
+			
+			if (item.type == 4) {
+				// 特殊题型英语题
+				const audioList = item.audios ? item.audios.split(',') : [];
+				item.placeholders = audioList.map((item, cindex) => `[yingyu${cindex+1}]`)
+				item.audioList = audioList;
+			}
 		})
 	}
+	
+	
 
 	// 数据赋值
 	function refreshExam(list) {
@@ -144,6 +153,7 @@ export function useExam() {
 
 	// 交卷
 	async function handleSubmit(dom) {
+		uni.$emit('unitTest-submit')
 		const result = [];
 		data.list.forEach(item => {
 			if (item.type == 1) {

+ 14 - 0
utils/common.js

@@ -82,4 +82,18 @@ export function hasUserIdentity() {
 		// 游客
 		return 'Visitor';
 	}
+}
+
+export function debounce(func, wait) {
+  let timeout;
+
+  return function(...args) {
+    // 清除之前的定时器
+    clearTimeout(timeout);
+
+    // 设置新的定时器
+    timeout = setTimeout(() => {
+      func.apply(this, args);
+    }, wait);
+  };
 }