|
|
@@ -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>
|