Jelajahi Sumber

update 页面调整

wangxy 3 bulan lalu
induk
melakukan
ba52f6f33c

+ 59 - 0
pages/newEnglish/components/audioFour.vue

@@ -0,0 +1,59 @@
+<template>
+	<view @click="handlePlay">
+		<view>{{YItem.cigen}}</view>
+		<view>{{YItem.yinbiao}}</view>
+	</view>
+</template>
+
+<script setup>
+	// yinjie
+	import {
+		reactive,
+		computed,
+		onUnmounted
+	} from 'vue';
+	import {
+		onLoad
+	} from "@dcloudio/uni-app"
+
+	const props = defineProps({
+		YItem: {
+			type: Object,
+		},
+	})
+
+	const emits = defineEmits(['play-audio'])
+
+	const data = reactive({
+		code: null
+	})
+
+	onLoad(() => {
+		uni.$on('danci-audio-ended', (code) => {
+			data.isPlaying = false;
+		})
+		uni.$on('danci-audio-play', (code) => {
+			if (data.code == code) {
+				data.isPlaying = true;
+			} else {
+				data.isPlaying = false;
+			}
+		})
+	})
+
+	onUnmounted(() => {
+		uni.$off('danci-audio-ended')
+		uni.$off('danci-audio-play')
+	})
+
+	function handlePlay() {
+		data.code = new Date().getTime()
+		emits('play-audio', {
+			url: props.YItem.yinpin,
+			code: data.code
+		})
+	}
+</script>
+
+<style>
+</style>

+ 60 - 0
pages/newEnglish/components/audioOne.vue

@@ -0,0 +1,60 @@
+<template>
+	<!-- 待播放 -->
+	<view class="audio-play-btn" v-if="!data.isPlaying" @click="handlePlay"></view>
+	<!-- 播放中 -->
+	<view class="audio-playing-btn" v-else></view>
+</template>
+
+<script setup>
+	import {
+		reactive,
+		computed,
+		onUnmounted
+	} from 'vue';
+	import {
+		onLoad
+	} from "@dcloudio/uni-app"
+
+	const props = defineProps({
+		activeWord: {
+			type: Object,
+		},
+	})
+
+	const emits = defineEmits(['play-audio'])
+
+	const data = reactive({
+		isPlaying: false,
+		code: null
+	})
+
+	onLoad(() => {
+		uni.$on('danci-audio-ended', (code) => {
+			data.isPlaying = false;
+		})
+		uni.$on('danci-audio-play', (code) => {
+			if (data.code == code) {
+				data.isPlaying = true;
+			} else {
+				data.isPlaying = false;
+			}
+		})
+	})
+
+	onUnmounted(() => {
+		uni.$off('danci-audio-ended')
+		uni.$off('danci-audio-play')
+	})
+
+	function handlePlay() {
+		console.log('播放')
+		data.code = new Date().getTime()
+		emits('play-audio', {
+			url: props.activeWord.yinpin,
+			code: data.code
+		})
+	}
+</script>
+
+<style>
+</style>

+ 58 - 0
pages/newEnglish/components/audioThree.vue

@@ -0,0 +1,58 @@
+<template>
+	<view @click="handlePlay">
+		{{YItem.cigen}}
+	</view>
+</template>
+
+<script setup>
+	// pindu
+	import {
+		reactive,
+		computed,
+		onUnmounted
+	} from 'vue';
+	import {
+		onLoad
+	} from "@dcloudio/uni-app"
+
+	const props = defineProps({
+		YItem: {
+			type: Object,
+		},
+	})
+
+	const emits = defineEmits(['play-audio'])
+
+	const data = reactive({
+		code: null
+	})
+
+	onLoad(() => {
+		uni.$on('danci-audio-ended', (code) => {
+			data.isPlaying = false;
+		})
+		uni.$on('danci-audio-play', (code) => {
+			if (data.code == code) {
+				data.isPlaying = true;
+			} else {
+				data.isPlaying = false;
+			}
+		})
+	})
+
+	onUnmounted(() => {
+		uni.$off('danci-audio-ended')
+		uni.$off('danci-audio-play')
+	})
+
+	function handlePlay() {
+		data.code = new Date().getTime()
+		emits('play-audio', {
+			url: props.YItem.yinpin,
+			code: data.code
+		})
+	}
+</script>
+
+<style>
+</style>

+ 59 - 0
pages/newEnglish/components/audioTwo.vue

@@ -0,0 +1,59 @@
+<template>
+	<!-- 待播放 -->
+	<icon class="yb-play-btn" v-if="!data.isPlaying" @click="handlePlay"></icon>
+	<!-- 播放中 -->
+	<icon class="yb-playing-btn" v-else></icon>
+</template>
+
+<script setup>
+	import {
+		reactive,
+		computed,
+		onUnmounted
+	} from 'vue';
+	import {
+		onLoad
+	} from "@dcloudio/uni-app"
+
+	const props = defineProps({
+		activeWord: {
+			type: Object,
+		},
+	})
+
+	const emits = defineEmits(['play-audio'])
+
+	const data = reactive({
+		isPlaying: false,
+		code: null
+	})
+
+	onLoad(() => {
+		uni.$on('danci-audio-ended', (code) => {
+			data.isPlaying = false;
+		})
+		uni.$on('danci-audio-play', (code) => {
+			if (data.code == code) {
+				data.isPlaying = true;
+			} else {
+				data.isPlaying = false;
+			}
+		})
+	})
+
+	onUnmounted(() => {
+		uni.$off('danci-audio-ended')
+		uni.$off('danci-audio-play')
+	})
+
+	function handlePlay() {
+		data.code = new Date().getTime()
+		emits('play-audio', {
+			url: props.activeWord.yinpin,
+			code: data.code
+		})
+	}
+</script>
+
+<style>
+</style>

+ 21 - 12
pages/newEnglish/components/beiPage.vue

@@ -3,21 +3,16 @@
 		<!-- 显示区 -->
 		<selectTypesVue activeSelect="5"></selectTypesVue>
 		<!-- 输入区 -->
-		<input class="words-answer-box" placeholder="请输入答案" v-model.trim="data.answer" readonly/>
+		<input class="words-answer-box" placeholder="请输入答案" v-model.trim="data.answer" readonly />
 		<!-- 清空按钮 -->
 		<view class="clean-btn" @click="handleReset('all')" v-if="data.answer.length"></view>
 		<view class="bei-body-box">
-			<!-- 提示 -->
-			<!-- <view class="pin-tip">提示:请点击页面下方键盘,输入正确字母。</view> -->
-			<!-- 解释区 需要分成多行 大哥看这里-->
+			<!-- 解释区 -->
 			<view class="pin-words-explain-box">
 				<view class="words-explain-item" v-for="item in activeWord.jianyi" :key="item">{{item}}</view>
 			</view>
-			<!-- 播放和待播放需要写个切换 大哥看这里 -->
-			<!-- 待播放 -->
-			<view class="audio-play-btn"></view>
-			<!-- 播放中 -->
-			<view class="audio-playing-btn" v-if="false"></view>
+			<!-- 播放和待播 -->
+			<audioOneVue :active-word="activeWord" @play-audio="handlePlay"></audioOneVue>
 		</view>
 		<!-- 浮层输入区 -->
 		<view class="words-keyboard-box">
@@ -64,16 +59,23 @@
 	import selectWordsVue from './selectWords.vue';
 	import selectTypesVue from './selectTypes.vue';
 	import btnTxtVue from './btnTxt.vue';
+	import audioOneVue from './audioOne.vue';
 	import {
 		reactive,
-		computed
+		computed,
+		onUnmounted
 	} from 'vue';
 	import {
 		getUserIdentity,
 	} from "@/utils/common.js"
 	import * as httpApi from "@/api/word.js"
+	import {
+		onLoad
+	} from "@dcloudio/uni-app"
 
 	const userCode = getUserIdentity();
+	
+	const emits = defineEmits(['play-audio'])
 
 	const props = defineProps({
 		activeWord: { // 单词数据
@@ -87,8 +89,15 @@
 	const data = reactive({
 		answer: '',
 		result: false, // 正确性
+		isPlaying: false,
+		code: null
 	})
 
+
+	function handlePlay(opt) {
+		emits('play-audio', opt)
+	}
+
 	// 选择单词
 
 	function checkIsRight() {
@@ -118,13 +127,13 @@
 			data.answer = '';
 		} else {
 			// 单个清空
-			data.answer = data.answer ? data.answer.slice(0, -1):''
+			data.answer = data.answer ? data.answer.slice(0, -1) : ''
 		}
 
 	}
 
 	function handleSelect(word) {
-		data.answer+=word;
+		data.answer += word;
 	}
 </script>
 

+ 10 - 5
pages/newEnglish/components/mainCard.vue

@@ -2,12 +2,12 @@
 	<swiper class="word-view-swiper-box" :indicator-dots="false" :autoplay="false" :circular="false">
 		<swiper-item>
 			<view class="swiper-item uni-bg-red">
-				<learnContent :active-word="activeWord" :active-words="activeWords"></learnContent>
+				<xuePage :active-word="activeWord" :active-words="activeWords" @play-audio="handlePlayAudio"></xuePage>
 			</view>
 		</swiper-item>
 		<swiper-item>
 			<view class="swiper-item uni-bg-red">
-				<pinPageVue :active-word="activeWord" :active-words="activeWords"></pinPageVue>
+				<pinPageVue :active-word="activeWord" :active-words="activeWords" @play-audio="handlePlayAudio"></pinPageVue>
 			</view>
 		</swiper-item>
 		<swiper-item>
@@ -18,12 +18,12 @@
 		</swiper-item>
 		<swiper-item>
 			<view class="swiper-item uni-bg-blue">
-				<selectPageVue :active-word="activeWord" :active-words="activeWords"></selectPageVue>
+				<selectPageVue :active-word="activeWord" :active-words="activeWords" @play-audio="handlePlayAudio"></selectPageVue>
 			</view>
 		</swiper-item>
 		<swiper-item>
 			<view class="swiper-item uni-bg-blue">
-				<beiPageVue :active-word="activeWord" :active-words="activeWords"></beiPageVue>
+				<beiPageVue :active-word="activeWord" :active-words="activeWords" @play-audio="handlePlayAudio"></beiPageVue>
 			</view>
 		</swiper-item>
 	</swiper>
@@ -33,7 +33,7 @@
 	import pinPageVue from './pinPage.vue';
 	import selectPageVue from './selectPage.vue';
 	import beiPageVue from './beiPage.vue';
-	import learnContent from './learnContent.vue';
+	import xuePage from './xuePage.vue';
 	const props = defineProps({
 		activeWord: {
 			type: Object,
@@ -42,6 +42,11 @@
 			type: Array
 		},
 	})
+	const emits = defineEmits(['play-audio'])
+
+	function handlePlayAudio({url,code}) {
+		emits('play-audio',{url,code})
+	}
 </script>
 
 <style lang="scss" scoped>

+ 47 - 59
pages/newEnglish/components/pinPage.vue

@@ -4,7 +4,8 @@
 		<selectTypesVue activeSelect="2"></selectTypesVue>
 		<!-- 拼读区 -->
 		<!-- 单词字母多余6个需要追加 class:pin-small-words-box  -->
-		<view class="pin-words-box" :class="{'pin-small-words-box': wordLength > 6, 'isAll': data.isAll, 'pin-right-words-box':  data.isAll && data.result, 'pin-error-words-box':  data.isAll && !data.result}">
+		<view class="pin-words-box"
+			:class="{'pin-small-words-box': wordLength > 6, 'isAll': data.isAll, 'pin-right-words-box':  data.isAll && data.result, 'pin-error-words-box':  data.isAll && !data.result}">
 			<view class="words-item" v-for="item in data.selectList">{{item}}</view>
 		</view>
 		<view class="pin-body-box">
@@ -12,40 +13,39 @@
 			<view class="clean-btn" v-if="isAlreadyAnswer" @click="handleReset"></view>
 			<!-- 提示 -->
 			<view class="pin-tip" v-else>提示:请点击页面下方字母,选择正确答案。</view>
-			<!-- 解释区 需要分成多行 -->
+			<!-- 解释区-->
 			<view class="pin-words-explain-box">
 				<view class="words-explain-item" v-for="item in activeWord.jianyi" :key="item">{{item}}</view>
 			</view>
-			<!-- 播放和待播放需要写个切换 大哥看这里 -->
-			<!-- 待播放 -->
-			<view class="audio-play-btn"></view>
-			<!-- 播放中 -->
-			<view class="audio-playing-btn" v-if="false"></view>
+			<audioOneVue @play-audio="handlePlay" :activeWord="activeWord"></audioOneVue>
 		</view>
-		<!-- 选择区 单词字母多余6个需要追加 class:pin-small-words-box -->
+		<!-- 选择区 -->
 		<view class="pin-words-box pin-words-change-box" :class="{'pin-small-words-box': wordLength>6}">
-			<view class="words-item words-change-item" v-for="item in data.randomList" :key="item" :class="{disabled:  isSelect(item)}" @click="handleSelect(item)">{{item}}</view>
+			<view class="words-item words-change-item" v-for="item in data.randomList" :key="item"
+				:class="{disabled:  isSelect(item)}" @click="handleSelect(item)">{{item}}</view>
 		</view>
 	</view>
 </template>
 
 <script setup>
 	import selectTypesVue from './selectTypes.vue';
+	import audioOneVue from './audioOne.vue';
 	import {
 		reactive,
-		computed
+		computed,
 	} from 'vue';
-	import {onLoad} from "@dcloudio/uni-app"
+	import {
+		onLoad
+	} from "@dcloudio/uni-app"
 	import * as httpApi from "@/api/word.js"
 	import {
 		getUserIdentity,
 	} from "@/utils/common.js"
-	import {useAudio} from "./useAudio.js"
-	
 
-	
+	const emits = defineEmits(['play-audio'])
+
 	const userCode = getUserIdentity();
-	
+
 	const props = defineProps({
 		activeWord: {
 			type: Object,
@@ -54,8 +54,10 @@
 			type: Array
 		},
 	})
-	
-	const { handlePlay } = useAudio(props.activeWord.yinpin)
+
+	function handlePlay(opt) {
+		emits('play-audio', opt)
+	}
 
 	const data = reactive({
 		list: [],
@@ -64,49 +66,45 @@
 		result: false, // 正确性
 		isAll: false, // 是否全答
 	})
-	
+
 	onLoad(() => {
 		initItem()
 	})
 
-	setTimeout(() => {
-		console.log('activeWord', props.activeWord)
-	})
-	
-	const wordLength = computed(() =>  props.activeWord.name.length)
+	const wordLength = computed(() => props.activeWord.name.length)
 	const isAlreadyAnswer = computed(() => {
 		return data.selectList.some(item => item != '')
 	})
 
-  function isSelect(item) {
-    return data.selectList.some(ite => ite == item)
-  }
+	function isSelect(item) {
+		return data.selectList.some(ite => ite == item)
+	}
 
-  function handleReset() {
-    data.list.forEach((item, index) => {
-      data.selectList[index] = ''
-    })
+	function handleReset() {
+		data.list.forEach((item, index) => {
+			data.selectList[index] = ''
+		})
 
-    data.result = false;
-    data.isAll = false;
-  }
+		data.result = false;
+		data.isAll = false;
+	}
 
 	function shuffleArray(array) {
-	  for (let i = array.length - 1; i > 0; i--) {
-		const j = Math.floor(Math.random() * (i + 1));
-		[array[i], array[j]] = [array[j], array[i]]; // ES6解构赋值交换元素
-	  }
-	  return array;
+		for (let i = array.length - 1; i > 0; i--) {
+			const j = Math.floor(Math.random() * (i + 1));
+			[array[i], array[j]] = [array[j], array[i]]; // ES6解构赋值交换元素
+		}
+		return array;
 	}
-	
+
 	function randomClone(arr) {
-	  const clone = [...arr];
-	  return shuffleArray(clone); // 复用方法一的洗牌算法
+		const clone = [...arr];
+		return shuffleArray(clone); // 复用方法一的洗牌算法
 	}
 
 	// 初始化 单词列表
 	function initItem() {
-		
+
 		data.list = props.activeWord.chaifen;
 		data.randomList = randomClone(data.list);
 		data.list.forEach((item, index) => {
@@ -115,16 +113,6 @@
 	}
 
 	function handleSelect(word) {
-
-
-    // 点击触发取消
-		/*if (data.selectList.find(item => item == word)) {
-			data.selectList[data.selectList.findIndex(item => item == word)] = '';
-			// 校验正确性
-			checkIsRight();
-			return;
-		}*/
-
 		// 覆盖状态
 		let status = false;
 		data.selectList.forEach((item, index) => {
@@ -138,13 +126,13 @@
 		})
 		// 校验正确性
 		checkIsRight();
-		
+
 		if (data.selectList.some(item => item == '')) {
-		  data.isAll = false;
+			data.isAll = false;
 		} else {
-		  data.isAll = true;
+			data.isAll = true;
 		}
-		
+
 	}
 
 	function checkIsRight() {
@@ -170,7 +158,7 @@
 </script>
 
 <style lang="scss" scoped>
-	
-	
-	
+
+
+
 </style>

+ 23 - 17
pages/newEnglish/components/selectPage.vue

@@ -1,8 +1,6 @@
 <template>
 	<!-- 单词最多16个字母 选项最多两行 tx---不要删除 -->
 	<view class="words-xuan-box">
-		<!-- 单词区 -->
-		<!-- <selectWordsVue :active-words="activeWords" :activeWord="activeWord"></selectWordsVue> -->
 		<!-- 显示区 -->
 		<selectTypesVue activeSelect="4"></selectTypesVue>
 		<view class="xuan-body-box">
@@ -12,22 +10,23 @@
 			<view class="yb-play-box">
 				<text>{{activeWord.yinbiao}}</text>
 				<!-- active -->
-				<!-- 播放未播放互斥控制 大哥看这里-->
-				<icon class="yb-play-btn"></icon>
-				<icon class="yb-playing-btn" v-if="false"></icon>
+				<audioTwoVue @play-audio="handlePlay" :active-word="activeWord"></audioTwoVue>
 			</view>
 		</view>
-		<!-- 图片区 -->
-		<!-- <view>
-			{{ data.result ?'正确':'错误' }}
-		</view> -->
 		<!-- 选择区 -->
-		<!-- 选中选项正确class select-right 选中选项错误 class select-error  大哥看这里-->
 		<view class="select-change-box">
-			<view class="select-item" :class="{active: data.answer == 'A'}" @click="handleSelect('A')"><text>{{data.opa}}</text></view>
-			<view class="select-item" :class="{active: data.answer == 'B'}" @click="handleSelect('B')"><text>{{data.opb}}</text></view>
-			<view class="select-item" :class="{active: data.answer == 'C'}" @click="handleSelect('C')"><text>{{data.opc}}</text></view>
-			<view class="select-item" :class="{active: data.answer == 'D'}" @click="handleSelect('D')"><text>{{data.opd}}</text></view>
+			<view class="select-item"
+				:class="{active: data.answer == 'A', 'select-error': data.answer =='A' && !data.result, 'select-right':data.answer =='A' && data.result}"
+				@click="handleSelect('A')"><text>{{data.opa}}</text></view>
+			<view class="select-item"
+				:class="{active: data.answer == 'B', 'select-error':  data.answer =='B' && !data.result, 'select-right':data.answer =='B' && data.result}"
+				@click="handleSelect('B')"><text>{{data.opb}}</text></view>
+			<view class="select-item"
+				:class="{active: data.answer == 'C', 'select-error':  data.answer =='C' && !data.result, 'select-right':data.answer =='C' && data.result}"
+				@click="handleSelect('C')"><text>{{data.opc}}</text></view>
+			<view class="select-item"
+				:class="{active: data.answer == 'D', 'select-error':  data.answer =='D' && !data.result, 'select-right':data.answer =='D' && data.result}"
+				@click="handleSelect('D')"><text>{{data.opd}}</text></view>
 		</view>
 	</view>
 </template>
@@ -35,20 +34,23 @@
 <script setup>
 	import selectWordsVue from './selectWords.vue';
 	import selectTypesVue from './selectTypes.vue';
+	import audioTwoVue from './audioTwo.vue';
 	import * as httpApi from "@/api/word.js"
 	import {
 		onLoad
 	} from "@dcloudio/uni-app"
 	import {
 		reactive,
-		computed
+		computed,
 	} from 'vue';
 	import {
 		getUserIdentity,
 	} from "@/utils/common.js"
-	
+
 	const userCode = getUserIdentity();
 	
+	const emits = defineEmits(['play-audio'])
+
 	const props = defineProps({
 		activeWord: { // 单词数据
 			type: Object,
@@ -71,6 +73,10 @@
 		initItem()
 	})
 
+	function handlePlay(opt) {
+		emits('play-audio', opt)
+	}
+
 	function initItem() {
 		data.name = props.activeWord.name;
 		data.opa = props.activeWord.opa;
@@ -104,4 +110,4 @@
 			wordId: props.activeWord.id
 		})
 	}
-</script>
+</script>

+ 107 - 50
pages/newEnglish/components/useAudio.js

@@ -1,55 +1,112 @@
-	import {
-		reactive,
-		ref
-	} from 'vue';
-	import { onHide, onUnload} from "@dcloudio/uni-app"
-
-export function useAudio (url) {
-	const innerAudioContext = ref(null);
-	function createAudio() {
-		const innerAudioContext = uni.createInnerAudioContext();
-		innerAudioContext.autoplay = false;
-		innerAudioContext.src = url;
-		innerAudioContext.onPlay(() => {
-			// 播放
-			uni.$emit('play-audio');
-		});
-		innerAudioContext.onError((res) => {
-		  uni.$emit('play-error')
-		});
-		innerAudioContext.onEnded(() => {
-			// 播放结束
-			uni.$emit('play-audio-ended')
-		})
-		return innerAudioContext;
+import {
+	reactive,
+	ref
+} from 'vue';
+import {
+	onHide,
+	onUnload
+} from "@dcloudio/uni-app"
+import {
+	nextTick
+} from 'vue';
+
+let audioContext = null;
+let code = null; // 身份标识
+audioContext = uni.createInnerAudioContext(); // 单例模式[3](@ref)
+audioContext.onEnded(() => {
+	// console.log('触发播放结束')
+	// 播放结束
+	uni.$emit('danci-audio-ended')
+})
+audioContext.onPlay(() => {
+	// 播放
+	// console.log('播放事件:', code)
+	uni.$emit('danci-audio-play', code);
+});
+
+export class audioPlayer {
+	// 播放音频
+	play(path, code1) {
+		// console.log('播放文件地址', path)
+		code = code1;
+		if (audioContext.src === path && !audioContext.paused) return;
+		audioContext.src = path;
+		audioContext.play();
 	}
-	
-	onHide(() => {
-		innerAudioContext.value.stop();
-	})
-	
-	onUnload(() => {
-		innerAudioContext.value.stop();
-	})
-	
-
-	// 展开食物停止播放
-	uni.$on('swiper-change', () => {
-		innerAudioContext.value.stop();
-	})
-	
-	innerAudioContext.value = createAudio();
-	
-	function handlePlay() {
-		innerAudioContext.value.destroy();
-		innerAudioContext.value = null;
-		innerAudioContext.value = createAudio();
-		innerAudioContext.value.play()
+
+	// 暂停播放
+	pause() {
+		audioContext?.pause();
 	}
-	
-	return {
-		innerAudioContext,
-		handlePlay
+
+	// 停止播放(释放资源)
+	stop() {
+		audioContext?.stop();
+		audioContext = null;
 	}
+
 }
 
+export function useAudioCache() {
+	const cacheMapKey = 'audio_cache_map'; // Storage 中缓存映射的键名
+
+	// 下载并缓存音频
+	async function cacheAudio(url) {
+		try {
+			let cacheMap = uni.getStorageSync(cacheMapKey) || {};
+
+			if (cacheMap[url]) {
+				// console.log('已缓存音频地址', cacheMap[url])
+				return cacheMap[url]; // 返回有效缓存路径
+			}
+
+			// 3. 下载音频文件
+			const {
+				tempFilePath
+			} = await new Promise((resolve, reject) => {
+				uni.downloadFile({
+					url,
+					success: resolve,
+					fail: reject
+				});
+			});
+
+			if (!tempFilePath.includes('.mp3')) {
+				// console.log(`文件下载失败${url}`)
+				return null;
+			}
+
+			// 4. 持久化存储到本地
+			const savedFilePath = tempFilePath; // 跨平台安全路径[2](@ref)
+			await uni.saveFile({
+				tempFilePath,
+				toSavedFilePath: savedFilePath
+			});
+			cacheMap[url] = savedFilePath;
+			uni.setStorageSync(cacheMapKey, cacheMap);
+			// console.log('当前音频地址', savedFilePath)
+			return savedFilePath;
+		} catch (err) {
+			// console.error('音频缓存失败:', err);
+			return null;
+		}
+	}
+
+	function clearAudioCache() {
+		// uni.setStorageSync(cacheMapKey, {})
+		const cacheMap = uni.getStorageSync(cacheMapKey) || {};
+		Object.entries(cacheMap).forEach(([key, path]) => {
+			uni.removeSavedFile({
+				filePath: path
+			}); // 删除文件
+			delete cacheMap[key];
+		})
+		// console.log('已清理完成', cacheMap)
+		uni.setStorageSync(cacheMapKey, cacheMap)
+	}
+
+	return {
+		cacheAudio,
+		clearAudioCache
+	}
+}

+ 119 - 0
pages/newEnglish/components/xuePage.vue

@@ -0,0 +1,119 @@
+<template>
+	<view>
+		<selectTypesVue activeSelect="1"></selectTypesVue>
+		<!-- 单词区 -->
+		<view>
+			<view v-for="(item,index) in activeWord.chaifen" :key="index">{{item}}</view>
+		</view>
+		<!-- 纯显示 -->
+		<view>
+			<view v-for="(item,index) in activeWord.chaifen" :key="index">{{item}}</view>
+		</view>
+		<!-- 音标区 -->
+		<view>
+			<view>{{activeWord.yinbiao}}</view>
+			<!-- 音频播放 -->
+			<audioTwoVue :active-word="activeWord" @play-audio="handlePlay"></audioTwoVue>
+		</view>
+		<!-- 注释区 -->
+		<view>
+			<view v-for="item in activeWord.xiangyi" :key="item">{{item}}</view>
+		</view>
+		<!-- 详解触发 -->
+		<view>
+			详解>
+		</view>
+		<!-- 音标拆分区 -->
+		<view v-if="data.isPindu">
+			<!-- pindu -->
+			<audioThreeVue v-for="(item,index) in activeWord.pindu" :key="index" :YItem="item" @play-audio="handlePlay"></audioThreeVue>
+		</view>
+		<view v-else>
+			<!-- yinjie -->
+			<audioFourVue v-for="(item,index) in activeWord.yinjie" :key="index" :YItem="item" @play-audio="handlePlay"></audioFourVue>
+		</view>
+
+		<!-- 音标按钮 -->
+		<view>
+			<view :class="{active: data.isPindu}" @click="handlePindu">自然拼读</view>
+			<view :class="{active: !data.isPindu}" @click="handleYinjie">音节拆分</view>
+		</view>
+		<!-- 词根+实用口语 -->
+		<view v-if="activeWord.cigenzhuji.length">
+			<view v-for="(item,index) in activeWord.cigenzhuji" :key="index"> 
+				<view>
+					<view>{{item.en}}</view>
+					<view>{{item.zn}}</view>
+				</view>
+				<view v-if="index<activeWord.cigenzhuji.length-2">+</view>
+				<view v-if="index == activeWord.cigenzhuji.length - 2">=</view>
+			</view>
+		</view>
+		<!-- 实用语句 -->
+		<view>
+			<view v-for="(item,index) in activeWord.kouyu" :key="index">
+				<view>{{item.en}}</view>
+				<view>{{item.zn}}</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script setup>
+	import selectTypesVue from './selectTypes.vue';
+	import audioTwoVue from './audioTwo.vue';
+	import audioThreeVue from './audioThree.vue';
+	import audioFourVue from './audioFour.vue';
+	import {
+		reactive,
+		computed,
+	} from 'vue';
+	import {
+		getUserIdentity,
+	} from "@/utils/common.js"
+	import * as httpApi from "@/api/word.js"
+	import {
+		audioPlayer,
+		useAudioCache
+	} from './useAudio.js';
+
+	const emits = defineEmits(['play-audio'])
+
+	const {
+		cacheAudio,
+		clearAudioCache
+	} = useAudioCache();
+
+
+	const data = reactive({
+		isPlaying:false,
+		isPindu:true,
+	})
+
+	const userCode = getUserIdentity();
+
+	const props = defineProps({
+		activeWord: { // 单词数据
+			type: Object,
+		},
+		activeWords: {
+			type: Array
+		},
+	})
+
+	
+	async function handlePlay(opt) {
+		emits('play-audio', opt)
+	}
+	
+	function handlePindu() {
+		data.isPindu = true
+	}
+	
+	function handleYinjie() {
+		data.isPindu = false
+	}
+</script>
+
+<style>
+</style>

+ 46 - 0
pages/newEnglish/index.vue

@@ -8,6 +8,7 @@
 			<view class="ezy-border-body">
 				<template v-for="item in data.wordList">
 					<mainCardVue :active-word="data.activeWord" :active-words="activeWords"
+						@play-audio="handlePlayAudio"
 						v-if="item.id == data.activeId" :key="item.id">
 					</mainCardVue>
 				</template>
@@ -40,8 +41,13 @@
 	import {
 		getUserIdentity,
 	} from "@/utils/common.js"
+	import {useAudioCache,audioPlayer } from "./components/useAudio.js"
 
 	const userCode = getUserIdentity();
+	
+	const {cacheAudio,clearAudioCache} = useAudioCache();
+	
+	const AudioP = new audioPlayer();
 
 	function courseBjFun() {
 		return 'static/images/course/course-cjdc-bj.png'
@@ -89,6 +95,8 @@
 
 		// 获取单词列表数据
 		initWordInfo();
+		// 清理过期文件
+		clearAudioCache();
 	})
 
 	// 是否是最后一题
@@ -195,6 +203,9 @@
 				if (data.wordList.length) {
 					data.arrayList = chunkArray(data.wordList, 5); // 将1维单词数组转换为2维数组
 				}
+				console.log('res.data',res.data)
+				handleCacheAudio()
+				
 			})
 		} else {
 			httpApi.getCommonWordInfo({
@@ -207,9 +218,44 @@
 				if (data.wordList.length) {
 					data.arrayList = chunkArray(data.wordList, 5); // 将1维单词数组转换为2维数组
 				}
+				
+				handleCacheAudio()
 			})
 		}
 	}
+	
+	function handleCacheAudio() {
+
+		const arr = []
+		// yinpin
+		arr.push(data.activeWord.yinpin);
+		// yinjie
+		data.activeWord.yinjie.forEach(item => {
+			arr.push(item.yinpin)
+		})
+		// pindu
+		data.activeWord.pindu.forEach(item => {
+			arr.push(item.yinpin)
+		})
+		// 缓存音频
+		arr.map(item => cacheAudio(item));
+		// console.log('arr',arr)
+	}
+	
+	async function handlePlayAudio({url,code}) {
+		// console.log('播放', url)
+		const cachedPath = await cacheAudio(url);
+		if (cachedPath.includes('.mp3')) {
+			// console.log('地址:', cachedPath)
+			AudioP.play(cachedPath, code);
+		} else {
+			uni.showToast({
+				title: '音频加载失败',
+				icon: 'none'
+			});
+		}
+	}
+	
 </script>
 
 <style>

+ 10 - 10
pages/study/index.vue

@@ -421,7 +421,7 @@
 			translateData(res.data)
 			youkeImage.value = res.data.icon
 			youkeImageBook.value = res.data.zhangIcon
-			zhangList.value = res.data.zhangList[0].zList
+			zhangList.value = res.data.zhangList[0].zList
 			zhangName.value = res.data.zhangList[0].zhangName
 			current.value = 0
 		}).catch((err) => {
@@ -502,12 +502,12 @@
 		})
 	}
 
-	function goDanciList(data, index) {
-		
-		uni.redirectTo({
-			url: 	'/pages/newEnglish/index?jieId=666&wordId=42'
-		})
-
+	function goDanciList(data, index) {
+		
+		uni.redirectTo({
+			url: 	'/pages/newEnglish/index?jieId=666&wordId=42'
+		})
+
 		return
 		if (!cacheManager.get('auth')) {
 			let youkeData = {
@@ -597,8 +597,8 @@
 
 	function chooseMethodListClick(data, data2, index) {
 
-		console.log('data', data); // 节内容
-		console.log('data2', data2); //章内容
+		// console.log('data', data); // 节内容
+		// console.log('data2', data2); //章内容
 		const authCode = getUserIdentity();
 		if (currentProduct.value == 2 && typeId.value == 1) {
 			const isFirst = data.jieName == data2.jieList[0].jieName
@@ -606,7 +606,7 @@
 				youkeDialogRef.value.handleShow();
 				return false;
 			}
-			console.log('authCode', authCode);
+			// console.log('authCode', authCode);
 			if (!(authCode == 'VIP' || isFirst)) {
 				goPayDialogRef.value.handleShow();
 				return false