liyanbo преди 6 месеца
родител
ревизия
6654f844d0
променени са 3 файла, в които са добавени 313 реда и са изтрити 98 реда
  1. 106 0
      src/api/blockly/music.js
  2. BIN
      src/assets/music/pingjing.mp3
  3. 207 98
      src/views/Blockly.vue

+ 106 - 0
src/api/blockly/music.js

@@ -0,0 +1,106 @@
+// 导入ElMessage组件
+import { ElMessage } from 'element-plus';
+
+// 音乐类型配置(独立配置,不直接关联颜色)
+export const musicTypeConfig = {
+    '平静': {
+        url: '/src/assets/music/pingjing.mp3',
+        name: '平静音乐'
+    },
+    '欢快': {
+        url: '/src/assets/music/pingjing.mp3',
+        name: '欢快音乐'
+    },
+    '舒缓': {
+        url: '/src/assets/music/pingjing.mp3',
+        name: '舒缓音乐'
+    },
+    '激情': {
+        url: '/src/assets/music/pingjing.mp3',
+        name: '激情音乐'
+    },
+    '冥想': {
+        url: '/src/assets/music/pingjing.mp3',
+        name: '冥想音乐'
+    },
+    '自然': {
+        url: '/src/assets/music/pingjing.mp3',
+        name: '自然音乐'
+    }
+};
+
+// 播放音乐函数 - 修改为接受参数而不是直接使用外部变量
+export const playMusic = (musicType, state, musicPlayer) => {
+    console.log(`尝试播放音乐类型: ${musicType}`);
+
+    // 检查是否有对应的音乐配置
+    if (musicTypeConfig[musicType]) {
+        const musicInfo = musicTypeConfig[musicType];
+
+        // 更新音乐播放状态
+        state.currentMusicUrl = musicInfo.url;
+        state.currentMusicName = musicInfo.name;
+
+        // 延迟播放,确保audio元素已经更新了src
+        setTimeout(() => {
+            if (musicPlayer.value) {
+                try {
+                    musicPlayer.value.play();
+                    state.isMusicPlaying = true;
+                    ElMessage.success(`正在播放: ${musicInfo.name}`);
+                } catch (error) {
+                    console.error('播放音乐失败:', error);
+                    ElMessage.error('播放音乐失败,请稍后重试');
+                }
+            } else {
+                console.error('音乐播放器元素未找到');
+                ElMessage.error('音乐播放器初始化失败');
+            }
+        }, 100);
+    } else {
+        // 如果没有找到对应类型的音乐,使用默认音乐
+        const defaultMusicType = '平静';
+        const musicInfo = musicTypeConfig[defaultMusicType];
+
+        state.currentMusicUrl = musicInfo.url;
+        state.currentMusicName = musicInfo.name;
+
+        setTimeout(() => {
+            if (musicPlayer.value) {
+                try {
+                    musicPlayer.value.play();
+                    state.isMusicPlaying = true;
+                    ElMessage.success(`正在播放默认音乐: ${musicInfo.name}`);
+                } catch (error) {
+                    console.error('播放音乐失败:', error);
+                    ElMessage.error('播放音乐失败,请稍后重试');
+                }
+            } else {
+                console.error('音乐播放器元素未找到');
+                ElMessage.error('音乐播放器初始化失败');
+            }
+        }, 100);
+    }
+};
+
+// 停止播放音乐 - 修改为接受参数
+export const stopMusic = (state, musicPlayer) => {
+    if (musicPlayer.value) {
+        musicPlayer.value.pause();
+        // 重置播放进度
+        musicPlayer.value.currentTime = 0;
+        // 更新状态
+        state.isMusicPlaying = false;
+        state.currentMusicUrl = '';
+        state.currentMusicName = '';
+        console.log('音乐已停止播放');
+    }
+};
+
+// 处理音乐播放结束事件
+export const onMusicEnded = (state) => {
+    state.isMusicPlaying = false;
+    state.currentMusicUrl = '';
+    state.currentMusicName = '';
+    console.log('音乐播放结束');
+};

BIN
src/assets/music/pingjing.mp3


+ 207 - 98
src/views/Blockly.vue

@@ -11,7 +11,7 @@
 
     <img src="@/assets/images/desklamp.png" alt="智能台灯" class="full-screen-image" />
     <!-- 使用动态样式设置灯光遮罩 -->
-     <div v-if="isLightOn"  :style="{ '--lamp-color': state.lamp.color,'--lamp-opacity': state.lamp.brightness / 100 }" class="lamp-light-mask"></div>
+     <div v-if="state.lamp.isLightOn"  :style="{ '--lamp-color': state.lamp.color,'--lamp-opacity': state.lamp.brightness / 100 }" class="lamp-light-mask"></div>
 
     <!-- 右下角按钮组 -->
     <div class="button-group">
@@ -20,8 +20,8 @@
     </div>
 
     <!-- 显示当前灯光信息 -->
-    <div v-if="isLightOn" class="lamp-info">
-      <p>颜色: {{ state.lamp.color }}</p>
+    <div v-if="state.lamp.isLightOn" class="lamp-info">
+      <p>颜色: {{ state.lamp.colorLog }}</p>
       <p>亮度: {{ state.lamp.brightness }}%</p>
     </div>
   </div>
@@ -56,6 +56,7 @@
             <block type="ai_smart_lamp"></block>
             <block type="ai_lamp_set_brightness"></block>
             <block type="ai_lamp_set_color"></block>
+            <block type="ai_music_play"></block>
           </category>
 
             <!-- 其他原有分类保持不变 -->
@@ -438,32 +439,38 @@
             ></video>
           </div>
 
-          <!-- 添加台灯显示区域 -->
-          <!-- <div class="lamp-preview-container">
-                <h5>AI智能台灯</h5>
-                <div
-                  class="lamp-display"
-                  :style="{
-                    filter: `brightness(${state.lamp.brightness / 100})`,
-                  }"
-                >
-                  <div
-                    class="lamp-image"
-                    :style="{ boxShadow: `0 0 40px 20px ${state.lamp.color}80` }"
-                  ></div>
-                </div>
-                <div class="lamp-info">
-                  <p>亮度: {{ state.lamp.brightness }}%</p>
-                  <p>颜色: {{ state.lamp.color }}</p>
-                </div>
-              </div> -->
+          <!-- 在template部分的适当位置音频播放器组件 -->
+          <div class="music-player-container" v-if="state.currentMusicUrl">
+            <h5>音乐播放</h5>
+            <audio
+                ref="musicPlayer"
+                :src="state.currentMusicUrl"
+                @ended="handleMusicEnded"
+                preload="metadata">
+              您的浏览器不支持音频元素
+            </audio>
+            <div class="music-status">
+              <p v-if="state.isMusicPlaying">正在播放: {{ state.currentMusicName }}</p>
+              <p v-else>准备就绪</p>
+            </div>
+            <!-- 停止播放按钮 - 修复点击事件 -->
+            <el-button
+                v-if="state.isMusicPlaying"
+                type="danger"
+                size="small"
+                @click="handleStopMusic"
+                style="margin-top: 10px;">
+              停止播放
+            </el-button>
+          </div>
+
         </div>
       </div>
 
       <!-- AI结果预览模态框 -->
       <el-dialog
+        v-if="state.previewConten"
         title="AI生成结果"
-        :visible.sync="state.previewVisible"
         width="80%"
         :before-close="handleClosePreview"
       >
@@ -508,14 +515,28 @@ import { javascriptGenerator } from "blockly/javascript";
 import { pythonGenerator } from "blockly/python";
 import * as hans from "blockly/msg/zh-hans";
 import { ElDialog, ElButton, ElMessage } from "element-plus";
-// 引入DeskLampView组件
-// import DeskLampView from "./virtuallaboratory/DeskLampView.vue";
+
+//【文生图】文生图
+import {
+  AiImageStatusEnum,
+  CreatePainting,
+  PaintingGetMys,
+  CreateVideo,
+  VideoGetMys,
+  sendChatMessageStream,
+  CreateDialogue,
+} from "@/api/questions.js";
+import { getModelIdByType, ModelPlatformEnum } from "@/api/teachers.js";
+import { ModelTypeEnum } from "@/api/teachers.js";
+import { globalState } from "@/utils/globalState.js";
+//音乐
+import { playMusic, stopMusic, onMusicEnded } from "@/api/blockly/music.js";
+
 
 const router = useRouter();
-const isLightOn = ref(false)
-const lampColor = ref('#fff') // 默认白色
-const lampBrightness = ref(50) // 默认亮度50%
+// 台灯预览显示状态
 const showLampPreview = ref(true)
+
 // 返回虚拟实验室
 const goBackLab = () => {
   router.push("/virtual-laboratory");
@@ -525,7 +546,7 @@ const goBack = () => {
 };
 // 切换灯光状态
 const toggleLight = () => {
-  isLightOn.value = true;
+  state.lamp.isLightOn = true;
 
   generateCode('javascript');
   runCode()
@@ -535,20 +556,6 @@ const handleViewCode = () =>{
   showLampPreview.value = false
 }
 
-//【文生图】文生图
-import {
-  AiImageStatusEnum,
-  CreatePainting,
-  PaintingGetMys,
-  CreateVideo,
-  VideoGetMys,
-  sendChatMessageStream,
-  CreateDialogue,
-} from "@/api/questions.js";
-import { getModelIdByType, ModelPlatformEnum } from "@/api/teachers.js";
-import { ModelTypeEnum } from "@/api/teachers.js";
-import { globalState } from "@/utils/globalState.js";
-
 Blockly.setLocale(hans);
 
 // 状态管理
@@ -575,14 +582,20 @@ const state = reactive({
 
   // 台灯状态
   lamp: {
+    isLightOn: false,// 台灯是否亮着
     brightness: 0, // 默认亮度50%
     color: "#ffffff", // 默认颜色白色
+    colorLog: "白", // 默认颜色白色
   },
 
   // 【文本文】对话相关状态
   activeConversationId: null,
-  conversationInProgress: false,
   conversationInAbortController: null,
+
+  // 独立的音乐播放状态
+  currentMusicUrl: '',
+  currentMusicName: '',
+  isMusicPlaying: false,
 });
 
 // 【文生图】自动刷新image列表的定时器
@@ -591,6 +604,22 @@ const inProgressTimer = ref();
 // 【文生视频】自动刷新video列表的定时器
 const inProgressVideoTimer = ref();
 
+// 创建音乐播放器引用
+const musicPlayer = ref(null);
+// 音乐相关的处理函数
+const handleMusicEnded = () => {
+  onMusicEnded(state);
+};
+
+// 专门的停止音乐处理函数
+const handleStopMusic = () => {
+  // 直接调用导入的stopMusic函数并传递正确的参数
+  stopMusic(state, musicPlayer);
+  // 提示信息
+  ElMessage.success('音乐已停止播放');
+};
+
+
 // 初始化Blockly工作区和自定义积木
 onMounted(async () => {
   // 从全局状态初始化年级ID
@@ -744,6 +773,33 @@ onMounted(async () => {
     },
   };
 
+  // 注册音乐播放积木
+  Blockly.Blocks['ai_music_play'] = {
+    init: function() {
+      this.appendDummyInput()
+          .appendField('播放音乐');
+      this.appendDummyInput()
+          .appendField('音乐类型:')
+          .appendField(
+              new Blockly.FieldDropdown([
+                ['平静', '平静'],
+                ['欢快', '欢快'],
+                ['舒缓', '舒缓'],
+                ['激情', '激情'],
+                ['冥想', '冥想'],
+                ['自然', '自然']
+              ]),
+              'MUSIC_TYPE'
+          );
+      this.setInputsInline(false);
+      this.setPreviousStatement(true, null);
+      this.setNextStatement(true, null);
+      this.setColour(290);
+      this.setTooltip('播放指定类型的音乐');
+      this.setHelpUrl('');
+    }
+  };
+
   // 注册JavaScript代码生成器
   registerJavaScriptGenerators();
 
@@ -758,7 +814,7 @@ onMounted(async () => {
   });
 
 // 加载初始XML内容
-  try {
+  /*try {
     // 使用字符串拼接代替模板字符串,避免特殊字符问题
     const initialXml = '<xml xmlns="https://developers.google.com/blockly/xml">\n' +
         '  <block type="ai_smart_lamp_single_param" id="]t3dfZuv34^1VI%c+Vww" x="50" y="90">\n' +
@@ -780,13 +836,19 @@ onMounted(async () => {
         '  </block>\n' +
         '</xml>';
 
-    const xml = Blockly.utils.xml.textToDom(initialXml);
+    // 移除可能导致问题的块ID中的特殊字符
+    const sanitizedXml = initialXml.replace(/id="[^"]+"/g, (match) => {
+      // 替换非字母数字字符为下划线
+      return match.replace(/[^a-zA-Z0-9"]/g, '_');
+    });
+
+    const xml = Blockly.utils.xml.textToDom(sanitizedXml);
     Blockly.Xml.domToWorkspace(xml, state.workspace);
 
     // 强制重新渲染工作区
     state.workspace.render();
 
-    // 添加更长的延迟确保DOM完全更新后再设置属性
+    // 使用两层嵌套setTimeout确保DOM完全加载和节点注册完成
     setTimeout(() => {
       // 确保所有块都可移动和可删除
       const blocks = state.workspace.getAllBlocks();
@@ -799,10 +861,16 @@ onMounted(async () => {
           block.inputList.forEach(input => {
             if (input.connection) {
               input.connection.setCheck(null); // 允许任何类型的连接
-              input.connection.setHangingPriority(10); // 设置较高的连接优先级
             }
           });
         }
+
+        // 确保块已正确注册到DOM和FocusManager
+        if (block.svgGroup_ && !block.svgGroup_.isConnected) {
+          block.svgGroup_.isConnected = true;
+          // 手动触发渲染更新
+          block.render();
+        }
       });
 
       // 再次渲染以应用属性更改
@@ -811,21 +879,20 @@ onMounted(async () => {
       // 初始化完成后手动生成一次代码
       generateCode("javascript");
 
-      // 初始化完成后添加工作区变化监听器
+      // 适当的延迟后再工作区变化监听器,避免与拖拽事件冲突
       setTimeout(() => {
+        // 初始化完成后工作区变化监听器
         state.workspace.addChangeListener(() => generateCode("javascript"));
       }, 500);
-    }, 1500);
+    }, 800);
   } catch (error) {
     console.error('加载初始XML内容失败:', error);
-  }
+  }*/
+
+  state.workspace.addChangeListener(() => generateCode("javascript"));
 
-// 工作区变化时自动生成JavaScript代码
-// 注意:这里设置监听器的位置移到了初始化完成后
-  setTimeout(() => {
-    state.workspace.addChangeListener(() => generateCode("javascript"));
-  }, 1500);
 });
+
 // 组件卸载时清除定时器
 onUnmounted(() => {
   if (inProgressTimer.value) {
@@ -835,19 +902,11 @@ onUnmounted(() => {
   if (inProgressVideoTimer.value) {
     clearInterval(inProgressVideoTimer.value);
   }
+
+  // 停止音乐播放
+  stopMusic(state, musicPlayer);
 });
 
-// AI服务配置
-const aiServiceConfig = {
-  baseUrl: import.meta.env.VITE_BASE_URL + "/bjdxWeb/ai",
-  endpoints: {
-    voiceRecognition: "/voice-recognition",
-    textToImage: "/create-painting",
-    textToVideo: "/text-to-video",
-    textToText: "/text-to-text",
-    semanticAnalysis: "/semantic-analysis",
-  },
-};
 
 // AI服务模块 - 统一管理API调用
 const aiService = {
@@ -858,25 +917,6 @@ const aiService = {
     const recognitionResult = await this.captureVoice(language, promptText);
     if (!recognitionResult) return "";
 
-    // 可选:将语音识别结果发送到后端进行优化处理
-    // try {
-    //   const response = await fetch(`${aiServiceConfig.baseUrl}${aiServiceConfig.endpoints.voiceRecognition}`, {
-    //     method: 'POST',
-    //     headers: { 'Content-Type': 'application/json' },
-    //     body: JSON.stringify({
-    //       text: recognitionResult,
-    //       language
-    //     })
-    //   });
-    //
-    //   if (response.ok) {
-    //     const data = await response.json();
-    //     return data.optimizedText || recognitionResult;
-    //   }
-    // } catch (error) {
-    //   console.warn('语音识别优化失败,使用原始结果', error);
-    // }
-
     return recognitionResult;
   },
 
@@ -1220,7 +1260,6 @@ const aiService = {
 
       // 创建AbortController实例
       state.conversationInAbortController = new AbortController();
-      state.conversationInProgress = true;
 
       // 使用流式API发送消息
       let resultText = "";
@@ -1306,12 +1345,10 @@ const aiService = {
     if (state.conversationInAbortController) {
       state.conversationInAbortController.abort();
     }
-    state.conversationInProgress = false;
   },
 
   // 设置台灯亮度
   async setLampBrightness(brightness) {
-    // console.log('setLampBrightness', brightness);
 
     // 验证亮度值在0-100之间
     const validBrightness = Math.max(0, Math.min(100, parseInt(brightness) || 0));
@@ -1352,13 +1389,23 @@ const aiService = {
 
     // 更新状态
     state.lamp.color = validColor;
+    state.lamp.colorLog = color;
 
     // 模拟API调用(实际项目中可替换为真实API)
-    console.log(`台灯颜色已设置为: ${validColor}`);
+    console.log(`台灯颜色已设置为: ${color}`);
 
     return validColor;
   },
 
+
+  // 音乐播放相关方法
+  playMusic: (musicType) => {
+    return playMusic(musicType, state, musicPlayer);
+  },
+  stopMusic : () => {
+    return stopMusic(state, musicPlayer);
+  },
+
   // 综合控制台灯(参数格式:"颜色, 亮度, 音乐")
   async controlLampWithSingleParam(params) {
     // 解析参数字符串
@@ -1384,13 +1431,16 @@ const aiService = {
       if (paramArray.length > 2 && paramArray[2]) {
         music = paramArray[2];
         console.log('音乐信息(暂不处理):', music);
+
+        // 调用音乐播放函数,传入必要的参数
+        playMusic(music, state, musicPlayer);
       }
     }
     // 调用控制台灯方法
     return await this.controlLamp(brightness, color);
   },
 
-  // 综合控制台灯(参数格式:"颜色, 亮度, 音乐")
+  // 综合控制台灯(参数格式:"颜色, 亮度")
   async controlLamp(brightness, color) {
     console.log("controlLamp", brightness, color);
 
@@ -1402,6 +1452,7 @@ const aiService = {
 
     return { brightness: state.lamp.brightness, color: state.lamp.color };
   },
+
 };
 
 // 注册JavaScript代码生成器
@@ -1470,6 +1521,14 @@ function registerJavaScriptGenerators() {
     const code = `await aiService.setLampColor(${color || "'白'"});`;
     return code;
   };
+
+  // 音乐播放
+  javascriptGenerator.forBlock['ai_music_play'] = function(block, generator) {
+    const musicType = block.getFieldValue('MUSIC_TYPE');
+    const code = `await aiService.playMusic('${musicType}');`;
+    return code;
+  };
+
 }
 
 // 注册Python代码生成器
@@ -1506,6 +1565,13 @@ function registerPythonGenerators() {
     return [code, pythonGenerator.ORDER_ATOMIC];
   };
 
+  // 智能台灯控制(单参数)
+  pythonGenerator.forBlock['ai_smart_lamp_single_param'] = function(block, generator) {
+    const params = generator.valueToCode(block, 'PARAMS', pythonGenerator.ORDER_ATOMIC);
+    const code = `await aiService.controlLampWithSingleParam(${params || "'白,0,平静'"});`;
+    return code;
+  };
+
   // 智能台灯控制
   pythonGenerator.forBlock['ai_smart_lamp'] = function(block, generator) {
     const brightness = generator.valueToCode(block, 'BRIGHTNESS', pythonGenerator.ORDER_ATOMIC);
@@ -1527,6 +1593,14 @@ function registerPythonGenerators() {
     const code = `ai_service.set_lamp_color(${color || "'白'"})`;
     return code;
   };
+
+  // 音乐播放
+  pythonGenerator.forBlock['ai_music_play'] = function(block, generator) {
+    const musicType = block.getFieldValue('MUSIC_TYPE');
+    const code = `ai_service.play_music('${musicType}')`;
+    return code;
+  };
+
 }
 
 // 生成代码
@@ -1606,7 +1680,7 @@ async function runCode() {
     };
 
     try {
-      // 添加安全检查
+      // 安全检查
       if (code.includes('eval(') || code.includes('Function(') ||
           code.includes('document.write') || code.includes('window.location')) {
         throw new Error('代码包含不安全的操作');
@@ -1654,12 +1728,18 @@ function saveJson() {
 
 // 保存XML
 function saveXml() {
-  const output = document.getElementById('textarea');
-  const xml = Blockly.Xml.workspaceToDom(state.workspace);
-  output.value = Blockly.Xml.domToPrettyText(xml);
-  output.focus();
-  output.select();
-  taChange();
+  try {
+    const output = document.getElementById('textarea');
+    const xml = Blockly.Xml.workspaceToDom(state.workspace);
+    output.value = Blockly.Xml.domToPrettyText(xml);
+    output.focus();
+    output.select();
+    taChange();
+    ElMessage.success('XML保存成功');
+  } catch (error) {
+    console.error('保存XML失败:', error);
+    ElMessage.error('保存XML失败: ' + error.message);
+  }
 }
 
 // 重新加载
@@ -1686,6 +1766,7 @@ function load() {
   }
 }
 
+// 文本域变化时触发
 function taChange() {
   const textarea = document.getElementById("textarea");
   if (sessionStorage) {
@@ -1695,6 +1776,7 @@ function taChange() {
   document.getElementById("import").disabled = !valid.json && !valid.xml;
 }
 
+// 检查保存内容是否为有效JSON或XML
 function saveIsValid(save) {
   let validJson = true;
   try {
@@ -2070,4 +2152,31 @@ window.aiService = aiService;
   font-size: 14px;
   color: #ffffff;
 }
+
+//音乐播放器
+.music-player-container {
+  margin-top: 20px;
+  padding: 15px;
+  background-color: #f9f9f9;
+  border-radius: 8px;
+  border: 1px solid #e0e0e0;
+}
+
+.music-player-container h5 {
+  margin-top: 0;
+  margin-bottom: 10px;
+  color: #333;
+  font-size: 16px;
+}
+
+.music-player-container audio {
+  width: 100%;
+  margin-bottom: 10px;
+}
+
+.music-status {
+  font-size: 14px;
+  color: #666;
+  padding: 5px 0;
+}
 </style>