Browse Source

语音播报

ds_hxl
宋杰 2 weeks ago
parent
commit
b9197ca47e
  1. 233
      src/views/AiEmotion.vue
  2. 8
      src/views/components/StockTabs.vue

233
src/views/AiEmotion.vue

@ -5,6 +5,7 @@
<img src="@/assets/img/AiEmotion/金轮.png" class="golden-wheel-img" alt="金轮图标"
:class="{ 'rotating-image': isRotating }" />
</div>
<!-- 消息显示区域 -->
<div class="user-input-display">
<div v-for="(message, index) in messages" :key="index" class="message-container">
@ -118,7 +119,7 @@
</div>
</div>
<!-- 场景应用 -->
<div class="class09">
<div class="class09" ref="scenarioApplicationRef">
<img src="@/assets/img/AiEmotion/场景应用.png" alt="场景应用标题">
<div class="bk-image">
<div class="conclusion-container" v-if="parsedConclusion">
@ -167,7 +168,7 @@ import { ElMessage } from 'element-plus';
import { useEmotionStore } from '@/store/emotion'; // Pinia store
import { useAudioStore } from '@/store/audio.js'; // store
import { Howl, Howler } from 'howler'; //
import { reactive } from 'vue';
// 使Pinia store
const emotionStore = useEmotionStore();
const audioStore = useAudioStore();
@ -186,6 +187,15 @@ const isRotating = ref(false);//控制旋转
const version1 = ref(2); //
const conclusionData = ref(''); //
//
const isAutoScrolling = ref(false);
const currentSection = ref(0);
const sectionRefs = ref([]);
const scenarioApplicationRef = ref(null); //
const hasTriggeredAudio = ref(false); //
const hasTriggeredTypewriter = ref(false); //
const intersectionObserver = ref(null); // observer
//
const displayedTexts = ref({
one1: '',
@ -232,10 +242,24 @@ const parsedConclusion = computed(() => {
}
});
//
watch(currentStock, (newStock) => {
if (newStock && newStock.apiData) {
isPageLoaded.value = true;
//
hasTriggeredAudio.value = false;
hasTriggeredTypewriter.value = false;
//
displayedTexts.value = {
one1: '',
one2: '',
two: '',
three: '',
four: '',
disclaimer: ''
};
nextTick(() => {
renderCharts(newStock.apiData);
});
@ -244,11 +268,11 @@ watch(currentStock, (newStock) => {
}
}, { immediate: true });
// parsedConclusion
// parsedConclusion
watch(parsedConclusion, (newConclusion) => {
if (newConclusion) {
console.log('场景应用结论数据:', newConclusion);
startTypewriterEffect(newConclusion);
//
// URL
let voiceUrl = null;
@ -266,13 +290,13 @@ watch(parsedConclusion, (newConclusion) => {
}
if (voiceUrl && voiceUrl.startsWith('http')) {
console.log('找到并清理后的语音URL:', voiceUrl);
audioUrl.value = voiceUrl;
playAudio(voiceUrl);
} else {
console.log('未找到有效的语音URL,原始URL:', newConclusion.url);
console.log('结论数据中的所有字段:', Object.keys(newConclusion));
}
console.log('找到并清理后的语音URL:', voiceUrl);
audioUrl.value = voiceUrl;
console.log('音频URL已准备,等待滚动触发播放');
} else {
console.log('未找到有效的语音URL,原始URL:', newConclusion.url);
console.log('结论数据中的所有字段:', Object.keys(newConclusion));
}
}
}, { immediate: true });
@ -293,7 +317,7 @@ function startTypewriterEffect(conclusion) {
};
//
const typeSpeed = 50;
const typeSpeed = 300;
let totalDelay = 0;
//
@ -404,8 +428,6 @@ function stopAudio() {
}
isAudioPlaying.value = false;
}
//使
defineExpose({ handleSendMessage })
//
function startImageRotation() {
isRotating.value = true;
@ -620,16 +642,175 @@ const scrollToBottom = async () => {
};
//
onMounted(() => {
startImageRotation();
});
//
function isDataLoaded() {
//
if (!isPageLoaded.value) {
console.log('页面数据尚未加载完成');
return false;
}
//
onUnmounted(() => {
clearTypewriterTimers();
stopAudio();
});
//
if (!currentStock.value || !currentStock.value.apiData) {
console.log('股票数据尚未加载完成');
return false;
}
//
const requiredRefs = [
marketTemperatureRef.value,
emotionDecodRef.value,
emotionalBottomRadarRef.value,
emoEnergyConverterRef.value
];
const allRefsLoaded = requiredRefs.every(ref => ref !== null);
if (!allRefsLoaded) {
console.log('图表组件尚未完全加载');
return false;
}
console.log('所有数据和组件已加载完成,可以开始滚动');
return true;
}
//
function startAutoScroll() {
if (isAutoScrolling.value) return;
//
if (!isDataLoaded()) {
console.log('数据尚未加载完成,延迟1秒后重试');
setTimeout(() => {
startAutoScroll();
}, 1000);
return;
}
isAutoScrolling.value = true;
const sections = document.querySelectorAll('.class02, .class03, .class04, .class05, .class06, .class08, .class09');
let currentIndex = 0;
function scrollToNextSection() {
if (currentIndex < sections.length) {
const section = sections[currentIndex];
// 使
section.scrollIntoView({
behavior: 'smooth',
block: 'center', //
inline: 'nearest'
});
console.log(`滚动到第${currentIndex + 1}个部分`);
currentSection.value = currentIndex;
currentIndex++;
//
setTimeout(scrollToNextSection, 4000); // 24
} else {
console.log('自动滚动完成');
isAutoScrolling.value = false;
}
}
// 2
console.log('自动滚动将在2秒后开始');
setTimeout(scrollToNextSection, 2000);
}
// Intersection Observer
function setupIntersectionObserver() {
if (!scenarioApplicationRef.value) return;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting && (!hasTriggeredAudio.value || !hasTriggeredTypewriter.value)) {
console.log('场景应用部分进入视口,开始打字机效果和音频播放');
//
if (!hasTriggeredTypewriter.value && parsedConclusion.value) {
console.log('开始场景应用打字机效果');
hasTriggeredTypewriter.value = true;
startTypewriterEffect(parsedConclusion.value);
}
//
if (!hasTriggeredAudio.value && audioUrl.value && parsedConclusion.value) {
console.log('自动触发场景应用音频播放');
hasTriggeredAudio.value = true;
playAudio(audioUrl.value);
}
}
});
},
{
threshold: 0.3, // 30%
rootMargin: '0px 0px -100px 0px' // 100px
}
);
observer.observe(scenarioApplicationRef.value);
intersectionObserver.value = observer;
}
//
function triggerAutoScroll() {
//
if (isAutoScrolling.value) {
console.log('自动滚动正在进行中,请稍候');
return;
}
//
if (!isDataLoaded()) {
console.log('数据尚未准备完成,请等待数据加载后再试');
//
return;
}
console.log('手动触发自动滚动');
startAutoScroll();
}
//
onMounted(() => {
startImageRotation();
// DOM
nextTick(() => {
setupIntersectionObserver();
//
setTimeout(() => {
triggerAutoScroll();
}, 1000); // 1
});
});
// observer
onUnmounted(() => {
clearTypewriterTimers();
stopAudio();
//
hasTriggeredAudio.value = false;
hasTriggeredTypewriter.value = false;
// Intersection Observer
if (intersectionObserver.value) {
intersectionObserver.value.disconnect();
intersectionObserver.value = null;
}
});
// 使
defineExpose({
handleSendMessage,
triggerAutoScroll
});
</script>
<style scoped>
@ -683,6 +864,8 @@ onUnmounted(() => {
height: auto;
}
/* 定义旋转动画 */
@keyframes rotate {
from {
@ -1191,7 +1374,7 @@ onUnmounted(() => {
/* 添加内边距,确保内容与边界有间距 */
box-sizing: border-box;
/* 包括内边距在宽度和高度计算中 */
background-color: #5e81a7;
background-color: #02107d;
margin: 0 auto;
/* 居中容器 */
margin-bottom: 10rem;
@ -1404,7 +1587,7 @@ onUnmounted(() => {
min-height: 100px;
height: auto;
box-sizing: border-box;
background-color: #5e81a7;
background-color: #02107d;
margin-bottom: 10rem;
}

8
src/views/components/StockTabs.vue

@ -90,11 +90,11 @@ const clearAllStocks = async () => {
<style scoped>
.stock-tabs {
background: #f8f9fa;
background: #02107d;
border-radius: 8px;
padding: 12px;
margin-bottom: 16px;
border: 1px solid #e9ecef;
border: 1px solid #02107d;
}
.tabs-container {
@ -178,12 +178,12 @@ const clearAllStocks = async () => {
justify-content: space-between;
align-items: center;
padding-top: 8px;
border-top: 1px solid #e9ecef;
border-top: 1px solid #02107d;
}
.stock-count {
font-size: 12px;
color: #6c757d;
color: #FFFFFF;
}
.clear-all-btn {

Loading…
Cancel
Save