| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362 |
- <template>
- <!-- 智能台灯 -->
- <div v-if="showLampPreview" class="desk-lamp-container">
- <!-- 标题框 -->
- <div class="desk-lamp-title-box">
- <div class="desk-lamp-box-icon" @click="goBack">
- <el-icon class="left-icon"><ArrowLeftBold /></el-icon>
- 返回虚拟实验室
- </div>
- </div>
- <!-- 收音状态显示区域 - 添加这段代码 -->
- <div v-if="isRecording" class="recording-status-container">
- <div class="recording-text">正在收音...</div>
- <div class="equalizer">
- <div class="bar bar-1"></div>
- <div class="bar bar-2"></div>
- <div class="bar bar-3"></div>
- <div class="bar bar-4"></div>
- <div class="bar bar-5"></div>
- <div class="bar bar-6"></div>
- <div class="bar bar-7"></div>
- </div>
- <div v-if="recordingCountdown <= 5" class="recording-countdown">{{ recordingCountdown }}秒</div>
- </div>
- <!-- 智能家居 -->
- <div class="image-overlay-container">
- <img :src="baseMap" alt="智能家居" class="full-screen-image base-image" />
- <img :src="curtainLeft" alt="白窗帘左" class="full-screen-image overlay-image" />
- <img :src="curtainRight" alt="白窗帘右" class="full-screen-image overlay-image" />
- <img :src="curtainFront" alt="窗帘前" class="full-screen-image overlay-image" />
- <img :src="television" alt="电视画面" class="full-screen-image overlay-image" />
- <img :src="lightOpen" alt="灯光打开" class="full-screen-image overlay-image" />
- <img :src="lightClose" alt="灯光关闭" class="full-screen-image overlay-image" />
- </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">
- <el-button class="control-button run-button" @click="toggleLight">运行</el-button>
- <el-button class="control-button code-button" @click="handleViewCode">代码</el-button>
- </div>
- <!-- 显示当前灯光信息 -->
- <div v-if="state.lamp.isLightOn" class="lamp-info">
- <p>颜色: {{ state.lamp.colorLog }}色</p>
- <p>亮度: {{ state.lamp.brightness }}%</p>
- <!-- 音乐播放状态显示和控制按钮 -->
- <div v-if="state.isMusicPlaying" class="music-info">
- <p>正在播放: {{ state.currentMusicName }}</p>
- <button class="stop-music-btn" @click="handleStopMusic">
- 停止播放
- </button>
- </div>
- </div>
- </div>
- <!-- Blockly编程界面 -->
- <div v-show="!showLampPreview" class="container">
- <!-- 返回智能台灯 -->
- <div class="title-box">
- <div class="box-icon" @click="goLabShow">
- <el-icon class="left-icon"><ArrowLeftBold /></el-icon>
- 返回智能家居
- </div>
- </div>
- <!-- 工具箱-->
- <div class="content">
- <div class="toolbox-section">
- <h2>工具箱</h2>
- <div id="toolbox" style="display: none;">
- <!-- 添加AI模块分类 -->
- <category name="AI模块" categorystyle="ai_category">
- <block type="ai_voice_input"></block>
- <block type="ai_text_to_image"></block>
- <block type="ai_text_to_video"></block>
- <block type="ai_text_to_text"></block>
- <block type="ai_smart_lamp_single_param"></block>
- <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>
- <block type="ai_turn_on_tv"></block>
- <block type="ai_turn_off_tv"></block>
- <!-- 添加窗帘控制积木 -->
- <block type="ai_curtain_open"></block>
- <block type="ai_curtain_close"></block>
- </category>
- <category name="逻辑" colour="%{BKY_LOGIC_HUE}">
- <block type="controls_if"></block>
- <block type="logic_compare"></block>
- <block type="logic_operation"></block>
- <block type="logic_negate"></block>
- <block type="logic_boolean"></block>
- </category>
- <category name="循环" colour="%{BKY_LOOPS_HUE}">
- <block type="controls_repeat_ext">
- <value name="TIMES">
- <shadow type="math_number">
- <field name="NUM">10</field>
- </shadow>
- </value>
- </block>
- <block type="controls_whileUntil"></block>
- </category>
- <category name="数学" colour="%{BKY_MATH_HUE}">
- <block type="math_number"></block>
- <block type="math_arithmetic"></block>
- <block type="math_single"></block>
- </category>
- <category name="文本" colour="%{BKY_TEXTS_HUE}">
- <block type="text"></block>
- <block type="text_length"></block>
- <block type="text_print"></block>
- </category>
- <category name="变量" colour="%{BKY_VARIABLES_HUE}" custom="VARIABLE"></category>
- </div>
- <div class="json-section">
- <h3> 数据</h3>
- <textarea v-model="jsonDataString" rows="10" placeholder="在此输入JSON格式的积木块数据..."></textarea>
- <div class="controls">
- <button @click="loadWorkspaceFromJson">加载JSON到工作区</button>
- <button @click="exportWorkspaceToJson">导出工作区为JSON</button>
- <button id="generateCode" @click="generateCode('javascript')">生成JavaScript代码</button>
- <button id="generateCode" @click="generateCode('python')">生成Python代码</button>
- </div>
- <div v-if="statusMessage" :class="['status', statusType]">
- {{ statusMessage }}
- </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 class="workspace-section">
- <h2>工作区</h2>
- <div id="blocklyDiv"></div>
- <div class="controls">
- <button id="runCode" @click="runCode">运行代码</button>
- <button @click="clearWorkspace">清空工作区</button>
- </div>
- </div>
- <!-- 输出-->
- <div class="output-section">
- <h2>输出</h2>
- <div class="controls">
- <button @click="clearOutput">清空输出</button>
- </div>
- <pre id="output">{{ output }}</pre>
- </div>
- <!-- AI结果预览模态框 -->
- <div v-if="state.previewVisible" class="preview-modal" @click="handleClosePreview">
- <div class="preview-content" @click.stop>
- <button class="close-button" @click="handleClosePreview">×</button>
- <div v-if="state.previewType === 'image'" class="preview-image-container">
- <img :src="state.previewContent" alt="AI生成图片" class="preview-image">
- </div>
- <div v-else-if="state.previewType === 'video'" class="preview-video-container">
- <video :src="state.previewContent" controls class="preview-video"></video>
- </div>
- <div v-else-if="state.previewType === 'text'" class="preview-text-container">
- {{ state.previewContent }}
- </div>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script setup>
- // 仅保留必要的导入和主组件逻辑
- import {ref, onMounted, onUnmounted, reactive, computed} from 'vue';
- import { useRouter } from 'vue-router';
- import { ArrowLeftBold } from '@element-plus/icons-vue';
- import * as Blockly from "blockly";
- import 'blockly/msg/zh-hans';
- import { javascriptGenerator } from "blockly/javascript";
- import { pythonGenerator } from "blockly/python";
- // 【文生图】文生图
- 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";
- import {ElButton} from "element-plus";
- // 智能家居图片
- import baseMap from '@/assets/images/smart-home/base-map.png' // 底图
- import curtainLeft from '@/assets/images/smart-home/curtain-left.png' // 白窗帘左
- import curtainRight from '@/assets/images/smart-home/curtain-right.png' // 白窗帘右
- import lightOpen from '@/assets/images/smart-home/light-open.png' // 灯光打开
- import lightClose from '@/assets/images/smart-home/light-close.png' // 灯光关闭
- import television from '@/assets/images/smart-home/television.png' // 电视画面
- import curtainFront from '@/assets/images/smart-home/curtain-front.png' // 前面窗帘遮挡
- const router = useRouter();
- // 设备信息
- const device = ref({
- name: "",
- image: "",
- jsonData: {}
- });
- // 台灯预览显示状态
- const showLampPreview = ref(true);
- // 语音识别
- const isRecording = ref(false);
- const recordingCountdown = ref(10);
- let countdownInterval = null;
- // 返回虚拟实验室
- const goLabShow = () => {
- showLampPreview.value = true;
- };
- const goBack = () => {
- window.location.href = "/virtual-laboratory";
- };
- // 切换灯光状态
- const toggleLight = () => {
- state.lamp.isLightOn = true;
- generateCode('javascript');
- // 在运行前设置为正在录音状态
- startRecordingStatus();
- runCode();
- };
- // 添加开始录音状态函数
- const handleMusicEnded = () => {
- onMusicEnded(state);
- };
- // 添加开始录音状态函数
- function startRecordingStatus() {
- isRecording.value = true;
- recordingCountdown.value = 10;
- // 清除之前的定时器
- if (countdownInterval) {
- clearInterval(countdownInterval);
- }
- // 设置新的倒计时
- countdownInterval = setInterval(() => {
- recordingCountdown.value--;
- if (recordingCountdown.value <= 0) {
- clearInterval(countdownInterval);
- endRecordingStatus();
- }
- }, 1000);
- }
- // 添加结束录音状态函数
- function endRecordingStatus() {
- isRecording.value = false;
- if (countdownInterval) {
- clearInterval(countdownInterval);
- countdownInterval = null;
- }
- }
- // 查看代码编程界面显示状态
- const handleViewCode = () => {
- showLampPreview.value = false;
- };
- // 创建计算属性处理 JSON 字符串的序列化和反序列化
- const jsonDataString = computed({
- get() {
- // 获取时序列化对象为字符串
- return JSON.stringify(device.value.jsonData, null, 2);
- },
- set(value) {
- // 设置时解析字符串为对象
- try {
- device.value.jsonData = JSON.parse(value);
- } catch (e) {
- console.error("无效的JSON格式", e);
- // 可以添加错误提示给用户
- }
- }
- });
- // 响应式变量
- // const jsonData = ref({
- // "blocks": {
- // "languageVersion": 0,
- // "blocks": [
- // {
- // "type": "variables_set",
- // "id": "kM:Fgf:wd4U3Z$j0x8oK",
- // "x": 90,
- // "y": 130,
- // "fields": {
- // "VAR": {
- // "id": "MHW(ZbOKhL!/An`5N@6`"
- // }
- // },
- // "inputs": {
- // "VALUE": {
- // "block": {
- // "type": "ai_voice_input",
- // "id": "l5E=g|1L+4hThQ8v})lQ",
- // "fields": {
- // "LANGUAGE": "zh-CN"
- // },
- // "inputs": {
- // "PROMPT": {
- // "block": {
- // "type": "text",
- // "id": "Q*n.c_)@7j^E2=s5/X!n",
- // "fields": {
- // "TEXT": "请发言:"
- // }
- // }
- // }
- // }
- // }
- // }
- // },
- // "next": {
- // "block": {
- // "type": "variables_set",
- // "id": "]g.xbBe.i=a9B*Kfw@|`",
- // "fields": {
- // "VAR": {
- // "id": "zn.7{ZqbUaH1?P,R05hF"
- // }
- // },
- // "inputs": {
- // "VALUE": {
- // "block": {
- // "type": "ai_text_to_text",
- // "id": "R$h+R!6#@+4=+WX1*nvh",
- // "inputs": {
- // "PROMPT": {
- // "block": {
- // "type": "variables_get",
- // "id": "h$S$nt)3VU.=nX*W-mo~",
- // "fields": {
- // "VAR": {
- // "id": "MHW(ZbOKhL!/An`5N@6`"
- // }
- // }
- // }
- // },
- // "提示词": {
- // "block": {
- // "type": "text",
- // "id": "7k%sgLP?i]e[,m^49P++",
- // "fields": {
- // "TEXT": "请只回复我指定格式:白,100,热闹"
- // }
- // }
- // }
- // }
- // }
- // }
- // },
- // "next": {
- // "block": {
- // "type": "ai_smart_lamp_single_param",
- // "id": "!.0;Ktwm+Z?o8_9FRa}G",
- // "inputs": {
- // "PARAMS": {
- // "block": {
- // "type": "variables_get",
- // "id": "d{cIJ-kEFFQcn~%A,g@g",
- // "fields": {
- // "VAR": {
- // "id": "zn.7{ZqbUaH1?P,R05hF"
- // }
- // }
- // }
- // }
- // }
- // }
- // }
- // }
- // }
- // }
- // ]
- // },
- // "variables": [
- // {
- // "name": "inputText",
- // "id": "MHW(ZbOKhL!/An`5N@6`"
- // },
- // {
- // "name": "lampConfig",
- // "id": "zn.7{ZqbUaH1?P,R05hF"
- // }
- // ]
- // });
- //输出结果
- const output = ref('');
- const statusMessage = ref('');
- const statusType = ref('');
- let workspace = null;
- // 创建音乐播放器引用
- const musicPlayer = ref(null);
- // 状态管理
- const state = reactive({
- workspace: null,
- generatedContent: {
- imageUrl: "",
- videoUrl: "",
- text: "",
- },
- previewVisible: false,
- previewType: "",
- previewContent: "",
- isProcessing: false,
- //年级
- gradeId: "",
- //【文生图】文生图
- inProgressImageMap: {},
- //【文生视频】文生视频
- inProgressVideoMap: {},
- // 台灯状态
- lamp: {
- isLightOn: false,// 台灯是否亮着
- brightness: 50, // 默认亮度50%
- color: "#ffffff", // 默认颜色白色
- colorLog: "白", // 默认颜色白色
- },
- // 【文本文】对话相关状态
- activeConversationId: null,
- conversationInAbortController: null,
- // 独立的音乐播放状态
- currentMusicUrl: '',
- currentMusicName: '',
- isMusicPlaying: false,
- });
- // 统一轮询管理器
- const pollingManager = {
- timers: {},
- // 启动轮询
- startPolling(type, callback, interval = 3000) {
- // 如果已有相同类型的轮询,先清除
- this.stopPolling(type);
- this.timers[type] = setInterval(async () => {
- try {
- await callback();
- } catch (error) {
- console.error(`${type}轮询失败:`, error);
- }
- }, interval);
- return this.timers[type];
- },
- // 停止轮询
- stopPolling(type) {
- if (this.timers[type]) {
- clearInterval(this.timers[type]);
- this.timers[type] = null;
- }
- },
- // 停止所有轮询
- stopAll() {
- Object.keys(this.timers).forEach(type => this.stopPolling(type));
- }
- };
- // 统一的错误处理包装器
- function withErrorHandling(operationName, fn, errorMessage = null) {
- return async function(...args) {
- try {
- state.isProcessing = true;
- return await fn.apply(this, args);
- } catch (error) {
- console.error(`${operationName}失败:`, error);
- showStatus(errorMessage || `${operationName}发生错误: ${error.message || '未知错误'}`);
- return null;
- } finally {
- state.isProcessing = false;
- }
- };
- }
- // 任务状态轮询公共函数
- async function pollTaskStatus(taskType, taskIds, fetchApi, onSuccess, onFailure) {
- if (taskIds.length === 0) {
- pollingManager.stopPolling(taskType);
- return {};
- }
- try {
- const list = await fetchApi(taskIds);
- const activeTasks = {};
- list.data.forEach((task) => {
- if (task.status === AiImageStatusEnum.IN_PROGRESS) {
- activeTasks[task.id] = task;
- } else if (task.status === AiImageStatusEnum.SUCCESS) {
- // 任务成功完成
- if (onSuccess) {
- onSuccess(task);
- }
- } else if (task.status === AiImageStatusEnum.FAIL) {
- // 任务失败
- if (onFailure) {
- onFailure(task);
- }
- }
- });
- return activeTasks;
- } catch (error) {
- console.error(`${taskType}状态轮询失败:`, error);
- return {};
- }
- }
- // AI服务模块 - 统一管理
- const aiService = {
- // 语音识别
- recognizeVoice: withErrorHandling('语音识别', async function(promptText = "", language = "zh-CN") {
- console.log("语音识别开始");
- // 前端语音采集
- const recognitionResult = await this.captureVoice(language, promptText);
- return recognitionResult || "";
- }, '语音识别失败'),
- // 前端语音采集
- captureVoice(language, promptText) {
- return new Promise((resolve) => {
- if (
- !"webkitSpeechRecognition" in window &&
- !"SpeechRecognition" in window
- ) {
- showStatus("您的浏览器不支持语音识别功能");
- resolve("");
- return;
- }
- const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
- const recognition = new SpeechRecognition();
- recognition.lang = language;
- recognition.interimResults = false;
- recognition.maxAlternatives = 1;
- let countdown = 10;
- // 固定的消息提示框
- const messageText = promptText ? `${promptText}\n请开始说话...(${countdown}秒)` : `请开始说话...(${countdown}秒)`;
- showStatus(messageText);
- // 倒计时
- const timer = setInterval(() => {
- countdown--;
- if (countdown > 0) {
- const newText = promptText ? `${promptText}\n请开始说话...(${countdown}秒)` : `请开始说话...(${countdown}秒)`;
- showStatus(newText);
- } else {
- clearInterval(timer);
- // 倒计时结束时也要调用endRecordingStatus
- endRecordingStatus();
- }
- }, 1000);
- recognition.onresult = (event) => {
- const speechResult = event.results[0][0].transcript;
- console.log("语音识别结果:", speechResult);
- showStatus("语音识别完成");
- endRecordingStatus(); // 添加这行,在识别成功时结束语音状态
- resolve(speechResult);
- };
- recognition.onerror = (event) => {
- console.error("语音识别错误:", event.error);
- showStatus("语音识别发生错误: " + event.error, 'error');
- endRecordingStatus(); // 添加这行,在识别错误时结束语音状态
- resolve("");
- };
- // 添加onend事件处理程序,确保语音识别无论如何结束都会清除状态
- recognition.onend = () => {
- console.log("语音识别结束");
- endRecordingStatus(); // 确保在识别结束时调用
- clearInterval(timer); // 确保清除倒计时定时器
- };
- recognition.start();
- });
- },
- // 文本生成图片
- textToImage: withErrorHandling('AI图片生成', async function(prompt, waitForCompletion = true) {
- console.log("AI图片生成中,提示词:", prompt);
- //获取文生图-模型id
- const modelRes = await getModelIdByType({
- type: ModelTypeEnum.TEXT_TO_IMAGE,
- platform: ModelPlatformEnum.DOUBAO,
- });
- if (!modelRes.data) {
- showStatus("获取模型ID失败", 'error');
- return null;
- }
- // 使用CreatePainting API创建图片任务
- const createRes = await CreatePainting({
- modelId: modelRes.data,
- prompt: prompt,
- width: 1024,
- height: 1024,
- });
- // 记录任务ID到映射中
- state.inProgressImageMap[createRes.data] = {
- id: createRes.data,
- status: AiImageStatusEnum.IN_PROGRESS,
- };
- // 开始轮询任务状态
- this.startPollingTasks('image');
- // 如果需要等待完成,等待图片生成完成
- if (waitForCompletion) {
- console.log("AI图片生成中,请等待。。。:");
- return await this.waitForImageCompletion(createRes.data);
- }
- return createRes.data; // 返回任务ID
- }, '生成图片失败'),
- // 【文生图】等待图片生成完成
- waitForImageCompletion(imageId) {
- return new Promise((resolve, reject) => {
- const checkInterval = setInterval(async () => {
- try {
- const list = await PaintingGetMys([imageId]);
- if (list.data && list.data.length > 0) {
- const image = list.data[0];
- if (image.status === AiImageStatusEnum.SUCCESS) {
- clearInterval(checkInterval);
- resolve(image.picUrl);
- } else if (image.status === AiImageStatusEnum.FAIL) {
- clearInterval(checkInterval);
- reject(new Error(image.error || "图片生成失败"));
- }
- }
- } catch (error) {
- clearInterval(checkInterval);
- reject(error);
- }
- }, 3000);
- });
- },
- // 文本生成视频
- textToVideo: withErrorHandling('AI视频生成', async function(prompt, waitForCompletion = true) {
- console.log("AI视频生成中,提示词:", prompt);
- //获取视频生成模型id
- const modelRes = await getModelIdByType({
- type: ModelTypeEnum.IMAGE_TO_VIDEO,
- platform: ModelPlatformEnum.DOUBAO,
- });
- if (!modelRes.data) {
- showStatus("获取模型ID失败", 'error');
- return null;
- }
- // 使用CreateVideo API创建视频任务
- const createRes = await CreateVideo({
- modelId: modelRes.data,
- prompt: prompt,
- duration: 4,
- resolution: "1080P",
- });
- // 记录任务ID
- state.inProgressVideoMap[createRes.data] = {
- id: createRes.data,
- status: AiImageStatusEnum.IN_PROGRESS,
- };
- console.log("AI视频生成中,请等待。。。");
- // 启动统一的轮询机制
- this.startPollingTasks('video');
- // 如果需要等待完成,使用Promise封装结果
- if (waitForCompletion) {
- return new Promise((resolve, reject) => {
- // 设置一次性的状态检查
- const checkStatus = () => {
- const videoInfo = state.generatedContent.videoUrl;
- if (videoInfo && videoInfo.includes(createRes.data)) {
- resolve(videoInfo);
- } else if (state.inProgressVideoMap[createRes.data]?.status === AiImageStatusEnum.FAIL) {
- reject(new Error("视频生成失败"));
- } else if (!state.inProgressVideoMap[createRes.data]) {
- reject(new Error("视频任务已不存在"));
- } else {
- // 继续检查
- setTimeout(checkStatus, 1000);
- }
- };
- checkStatus();
- });
- }
- return createRes.data; // 返回任务ID
- }, '生成视频失败'),
- // 文本生成文本(如AI对话)
- textToText: withErrorHandling('AI大模型调用', async function(prompt, model = "default") {
- console.log("AI智能体请求,输入文本:", prompt);
- // 如果没有活跃的对话ID,创建新对话
- if (!state.activeConversationId) {
- // 使用与TextToText.vue相同的方式创建对话
- const res = await CreateDialogue({ roleId: 75 });
- state.activeConversationId = res.data;
- console.log("AI智能体创建成功,请等待。。。");
- }
- // 创建AbortController实例
- state.conversationInAbortController = new AbortController();
- // 使用流式API发送消息
- let resultText = "";
- let isFirstChunk = true;
- await sendChatMessageStream(
- state.activeConversationId,
- prompt,
- null,
- state.conversationInAbortController,
- true, // 启用上下文
- async (res) => {
- try {
- const { code, data, msg } = JSON.parse(res.data);
- if (code !== 0) {
- console.log(`对话异常! ${msg}`);
- return;
- }
- // 根据事件类型处理
- if (data.eventType === "TEXT") {
- // 如果内容为空,就不处理
- if (data.receive?.content === "") {
- return;
- }
- // 处理文本消息
- resultText += data.receive.content;
- // 首次返回时更新预览内容
- if (isFirstChunk) {
- isFirstChunk = false;
- // 设置预览内容
- state.generatedContent.text = resultText;
- state.previewType = "text";
- state.previewContent = resultText;
- } else {
- // 更新预览内容
- state.generatedContent.text = resultText;
- state.previewContent = resultText;
- }
- }
- } catch (error) {
- console.error("处理流式响应失败:", error);
- }
- },
- (error) => {
- console.log(`对话异常! ${error}`);
- this.stopTextToTextStream();
- throw error;
- },
- () => {
- // console.log(`结束对话!`);
- this.stopTextToTextStream();
- }
- );
- // 确保最终结果被设置
- if (resultText) {
- console.log("AI大模型调用成功,返回结果:", resultText);
- state.generatedContent.text = resultText;
- state.previewType = "text";
- state.previewContent = resultText;
- }
- return resultText;
- }, 'AI大模型调用失败'),
- // 停止文本生成流
- stopTextToTextStream() {
- if (state.conversationInAbortController) {
- state.conversationInAbortController.abort();
- }
- },
- // 设置台灯亮度
- setLampBrightness: withErrorHandling('设置台灯亮度', async function(brightness) {
- // 验证亮度值在0-100之间
- const validBrightness = Math.max(0, Math.min(100, parseInt(brightness) || 0));
- // 更新状态
- state.lamp.brightness = validBrightness;
- // 模拟API调用(实际项目中可替换为真实API)
- console.log(`智能台灯亮度已设置为: ${validBrightness}%`);
- return validBrightness;
- }, '设置台灯亮度失败'),
- // 设置台灯颜色
- setLampColor: withErrorHandling('设置台灯颜色', async function(color) {
- // 预定义的颜色映射
- const colorMap = {
- '紫': '#D886F0',
- '橙': '#F89E35',
- '黄': '#F9E67E',
- '青': '#6BF5E6',
- '白': '#ffffff',
- };
- // 获取有效的颜色值
- let validColor = colorMap[color] || color;
- // 检查是否是有效的颜色格式
- if (!/^#[0-9A-F]{6}$/i.test(validColor)) {
- validColor = "#ffffff"; // 默认白色
- }
- // 更新状态
- state.lamp.color = validColor;
- state.lamp.colorLog = color;
- // 模拟API调用(实际项目中可替换为真实API)
- console.log(`智能台灯颜色已设置为: ${color}`);
- return validColor;
- }, '设置台灯颜色失败'),
- /**
- * 播放音乐功能
- * @param {string} musicType - 音乐类型
- * @returns {Promise<void>} - 返回Promise
- */
- playMusic: withErrorHandling('播放音乐', async function(musicType) {
- return playMusic(musicType, state, musicPlayer);
- }, '播放音乐失败'),
- /**
- * 停止音乐播放
- * @returns {Promise<void>} - 返回Promise
- */
- stopMusic: withErrorHandling('停止音乐', async function() {
- return stopMusic(state, musicPlayer);
- }, '停止音乐失败'),
- // 综合控制台灯(参数格式:"颜色, 亮度, 音乐")
- controlLampWithSingleParam: withErrorHandling('智能台灯综合控制', async function(params) {
- // 解析参数字符串
- let color = '白'; // 默认颜色
- let brightness = 0; // 默认亮度
- let music = ''; // 音乐信息
- if (params && typeof params === 'string') {
- // 根据逗号分割参数
- const paramArray = params.split(',').map(p => p.trim());
- // 提取颜色(第一个参数)
- if (paramArray.length > 0 && paramArray[0]) {
- color = paramArray[0];
- }
- // 提取亮度(第二个参数)
- if (paramArray.length > 1 && paramArray[1]) {
- brightness = paramArray[1];
- }
- // 提取音乐(第三个参数)
- if (paramArray.length > 2 && paramArray[2]) {
- music = paramArray[2];
- // 调用音乐播放函数
- await this.playMusic(music);
- }
- }
- // 调用控制台灯方法
- return await this.controlLamp(brightness, color);
- }, '智能台灯综合控制失败'),
- // 综合控制台灯(参数格式:"颜色, 亮度")
- controlLamp: withErrorHandling('智能台灯控制', async function(brightness, color) {
- // 先设置亮度
- await this.setLampBrightness(brightness);
- // 再设置颜色
- await this.setLampColor(color);
- return { brightness: state.lamp.brightness, color: state.lamp.color };
- }, '智能台灯控制失败'),
- // 打开电视
- turnOnTv: withErrorHandling('打开电视', async function() {
- // 模拟API调用(实际项目中可替换为真实API)
- console.log('电视已打开');
- showStatus('电视已打开');
- return true;
- }, '打开电视失败'),
- // 关闭电视
- turnOffTv: withErrorHandling('关闭电视', async function() {
- // 模拟API调用(实际项目中可替换为真实API)
- console.log('电视已关闭');
- showStatus('电视已关闭');
- return true;
- }, '关闭电视失败'),
- // 打开窗帘
- openCurtain: withErrorHandling('打开窗帘', async function() {
- console.log("打开窗帘");
- // 这里可以添加实际的窗帘控制逻辑
- showStatus("窗帘已打开");
- return true;
- }, '打开窗帘失败'),
- // 关闭窗帘
- closeCurtain: withErrorHandling('关闭窗帘', async function() {
- console.log("关闭窗帘");
- // 这里可以添加实际的窗帘控制逻辑
- showStatus("窗帘已关闭");
- return true;
- }, '关闭窗帘失败'),
- // 启动任务轮询
- startPollingTasks(type) {
- if (type === 'image' || type === 'all') {
- pollingManager.startPolling('image', async () => {
- const imageIds = Object.keys(state.inProgressImageMap).map(Number);
- state.inProgressImageMap = await pollTaskStatus(
- 'image',
- imageIds,
- PaintingGetMys,
- (image) => {
- state.generatedContent.imageUrl = image.picUrl;
- state.previewType = "image";
- state.previewContent = image.picUrl;
- state.previewVisible = true;
- console.log("AI图片生成完成:", image.picUrl);
- },
- (image) => {
- showStatus("图片生成失败: " + (image.error || "未知错误"), 'error');
- console.error("图片生成失败:", image.id, image.error);
- }
- );
- });
- }
- if (type === 'video' || type === 'all') {
- pollingManager.startPolling('video', async () => {
- const videoIds = Object.keys(state.inProgressVideoMap).map(Number);
- state.inProgressVideoMap = await pollTaskStatus(
- 'video',
- videoIds,
- VideoGetMys,
- (video) => {
- state.generatedContent.videoUrl = video.videoUrl;
- state.previewType = "video";
- state.previewContent = video.videoUrl;
- state.previewVisible = true;
- console.log("AI视频生成完成:", video.videoUrl);
- },
- (video) => {
- showStatus("视频生成失败: " + (video.error || "未知错误"), 'error');
- console.error("视频生成失败:", video.id, video.error);
- }
- );
- });
- }
- }
- };
- // 专门的停止音乐处理函数
- const handleStopMusic = () => {
- // 直接调用导入的stopMusic函数并传递正确的参数
- stopMusic(state, musicPlayer);
- // 提示信息
- showStatus('音乐已停止播放');
- };
- // 关闭预览
- const handleClosePreview = () => {
- state.previewVisible = false;
- state.previewContent = "";
- state.previewType = "";
- state.generatedContent.text = null;
- state.generatedContent.imageUrl = null;
- state.generatedContent.videoUrl = null;
- };
- // 组件挂载后初始化Blockly
- onMounted(() => {
- // 从全局状态初始化年级ID
- state.gradeId = globalState.initGradeId();
- device.value.name = router.currentRoute.value.query.name || "";
- device.value.image = router.currentRoute.value.query.image || "";
- device.value.jsonData = JSON.parse(router.currentRoute.value.query.jsonData || {});
- // 注册AI语音识别积木
- Blockly.Blocks["ai_voice_input"] = {
- init: function () {
- this.appendDummyInput().appendField("语音识别");
- this.appendValueInput("PROMPT")
- .setCheck("String")
- .appendField("提示文字:");
- this.appendDummyInput()
- .appendField("语言:")
- .appendField(
- new Blockly.FieldDropdown([
- ["中文", "zh-CN"],
- // ["英文", "en-US"],
- ]),
- "LANGUAGE"
- );
- this.setOutput(true, "String");
- this.setColour(310);
- this.setTooltip("使用语音识别获取文本输入");
- this.setHelpUrl("");
- },
- };
- // 注册AI文本生成图片积木
- Blockly.Blocks["ai_text_to_image"] = {
- init: function () {
- this.appendDummyInput().appendField("AI生成图片");
- this.appendValueInput("PROMPT").setCheck("String").appendField("提示词:");
- this.appendDummyInput()
- .appendField("等待完成:", "WAIT_LABEL")
- .appendField(new Blockly.FieldCheckbox("TRUE"), "WAIT_FOR_COMPLETION");
- this.setInputsInline(false);
- this.setPreviousStatement(true, null);
- this.setNextStatement(true, null);
- this.setColour(340);
- this.setTooltip("使用AI将文本描述转换为图片");
- this.setHelpUrl("");
- },
- };
- // 注册AI文本生成视频积木
- Blockly.Blocks["ai_text_to_video"] = {
- init: function () {
- this.appendDummyInput().appendField("AI生成视频");
- this.appendValueInput("PROMPT").setCheck("String").appendField("提示词:");
- this.appendDummyInput()
- .appendField("等待完成:", "WAIT_LABEL")
- .appendField(new Blockly.FieldCheckbox("TRUE"), "WAIT_FOR_COMPLETION");
- this.setInputsInline(false);
- this.setPreviousStatement(true, null);
- this.setNextStatement(true, null);
- this.setColour(340);
- this.setTooltip("使用AI将文本描述转换为视频");
- this.setHelpUrl("");
- },
- };
- // 注册AI文本生成文本积木
- Blockly.Blocks["ai_text_to_text"] = {
- init: function () {
- this.appendDummyInput().appendField("AI大模型调用");
- this.appendValueInput("PROMPT")
- .setCheck("String")
- .appendField("输入文本:");
- this.appendValueInput("提示词")
- .setCheck("String")
- .appendField("提示词:");
- this.setOutput(true, "String");
- this.setColour(300);
- this.setTooltip("使用AI大模型调用并返回结果");
- this.setHelpUrl("");
- },
- };
- //AI智能台灯单参数积木
- Blockly.Blocks['ai_smart_lamp_single_param'] = {
- init: function() {
- this.appendDummyInput()
- .appendField('智能台灯控制(单参数)');
- this.appendValueInput('PARAMS')
- .setCheck('String')
- .appendField('参数(格式: 颜色,亮度,音乐):');
- this.appendDummyInput()
- .appendField('例如: 蓝,50,平静');
- this.setInputsInline(false);
- this.setPreviousStatement(true, null);
- this.setNextStatement(true, null);
- this.setColour(280);
- this.setTooltip('通过一个参数字符串控制智能台灯的亮度、颜色和音乐\n格式: 颜色,亮度,音乐\n例如: 蓝,50,平静');
- this.setHelpUrl('');
- }
- };
- // 注册AI智能台灯积木
- Blockly.Blocks["ai_smart_lamp"] = {
- init: function () {
- this.appendDummyInput().appendField("智能台灯控制");
- this.appendValueInput("BRIGHTNESS")
- .setCheck(["Number", "String"])
- .appendField("亮度 (0-100):");
- this.appendValueInput("COLOR").setCheck("String").appendField("颜色:");
- this.setInputsInline(false);
- this.setPreviousStatement(true, null);
- this.setNextStatement(true, null);
- this.setColour(280);
- this.setTooltip("控制智能台灯的亮度和颜色");
- this.setHelpUrl("");
- },
- };
- // 注册AI台灯设置亮度积木
- Blockly.Blocks["ai_lamp_set_brightness"] = {
- init: function () {
- this.appendDummyInput().appendField("设置台灯亮度");
- this.appendValueInput("BRIGHTNESS")
- .setCheck(["Number", "String"])
- .appendField("亮度 (0-100):");
- this.setInputsInline(false);
- this.setPreviousStatement(true, null);
- this.setNextStatement(true, null);
- this.setColour(270);
- this.setTooltip("设置智能台灯的亮度");
- this.setHelpUrl("");
- },
- };
- // 注册AI台灯设置颜色积木
- Blockly.Blocks["ai_lamp_set_color"] = {
- init: function () {
- this.appendDummyInput().appendField("设置台灯颜色");
- this.appendValueInput("COLOR").setCheck("String").appendField("颜色:");
- this.appendDummyInput().appendField("可选颜色: 白,黄,紫,橙,青");
- this.setInputsInline(false);
- this.setPreviousStatement(true, null);
- this.setNextStatement(true, null);
- this.setColour(275);
- this.setTooltip("设置智能台灯的颜色");
- this.setHelpUrl("");
- },
- };
- // 注册音乐播放积木
- 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('');
- }
- };
- // 注册打开电视积木
- Blockly.Blocks['ai_turn_on_tv'] = {
- init: function() {
- this.appendDummyInput()
- .appendField('打开电视');
- this.setInputsInline(false);
- this.setPreviousStatement(true, null);
- this.setNextStatement(true, null);
- this.setColour(260);
- this.setTooltip('打开电视');
- this.setHelpUrl('');
- }
- };
- // 注册关闭电视积木
- Blockly.Blocks['ai_turn_off_tv'] = {
- init: function() {
- this.appendDummyInput()
- .appendField('关闭电视');
- this.setInputsInline(false);
- this.setPreviousStatement(true, null);
- this.setNextStatement(true, null);
- this.setColour(260);
- this.setTooltip('关闭电视');
- this.setHelpUrl('');
- }
- };
- // 注册打开窗帘积木
- Blockly.Blocks['ai_curtain_open'] = {
- init: function() {
- this.appendDummyInput()
- .appendField('打开窗帘');
- this.setInputsInline(false);
- this.setPreviousStatement(true, null);
- this.setNextStatement(true, null);
- this.setColour(200);
- this.setTooltip('打开窗帘');
- this.setHelpUrl('');
- }
- };
- // 注册关闭窗帘积木
- Blockly.Blocks['ai_curtain_close'] = {
- init: function() {
- this.appendDummyInput()
- .appendField('关闭窗帘');
- this.setInputsInline(false);
- this.setPreviousStatement(true, null);
- this.setNextStatement(true, null);
- this.setColour(200);
- this.setTooltip('关闭窗帘');
- this.setHelpUrl('');
- }
- };
- // 注册JavaScript代码生成器
- registerJavaScriptGenerators();
- // 注册Python代码生成器
- registerPythonGenerators();
- // 初始化Blockly工作区
- const blocklyDiv = document.getElementById('blocklyDiv');
- const toolbox = document.getElementById('toolbox');
- workspace = Blockly.inject(blocklyDiv, {
- toolbox: toolbox,
- collapse: true,
- comments: true,
- disable: false, // 设为false以允许编辑
- maxBlocks: Infinity,
- trashcan: true,
- horizontalLayout: false,
- toolboxPosition: 'start',
- css: true,
- media: 'https://unpkg.com/blockly/media/',
- rtl: false,
- scrollbars: true,
- sounds: false, // 禁用声音以提高性能
- oneBasedIndex: true,
- grid: {
- spacing: 20,
- length: 3,
- colour: "#ccc",
- snap: true
- },
- zoom: {
- controls: true,
- wheel: true,
- startScale: 1.0,
- maxScale: 3,
- minScale: 0.3,
- scaleSpeed: 1.2
- }
- });
- // 使用state.workspace替代workspace
- state.workspace = workspace;
- // 加载初始JSON数据
- loadWorkspaceFromJson();
- // 修改工作区变化监听器,使其包含拖拽修复逻辑
- workspace.addChangeListener((event) => {
- // 生成代码
- generateCode("javascript");
- // 拖拽修复逻辑
- if (event.type === Blockly.Events.BLOCK_CREATE) {
- const block = workspace.getBlockById(event.blockId);
- if (block) {
- block.setEditable(true);
- }
- }
- });
- // 将aiService挂载到window,以便执行生成的代码时可以访问
- window.aiService = aiService;
- });
- // 组件卸载时清除所有资源
- onUnmounted(() => {
- // 清除所有定时器
- if (countdownInterval) {
- clearInterval(countdownInterval);
- }
- // 停止所有轮询
- pollingManager.stopAll();
- // 关闭语音识别(如果正在进行)
- if (recognition) {
- recognition.stop();
- }
- // 清理工作区
- if (workspace) {
- workspace.dispose();
- }
- // 清理音频资源
- if (musicPlayer.value) {
- musicPlayer.value.pause();
- }
- });
- // 注册JavaScript代码生成器
- function registerJavaScriptGenerators() {
- // 语音识别
- javascriptGenerator.forBlock['ai_voice_input'] = function(block, generator) {
- const prompt = generator.valueToCode(block, 'PROMPT', javascriptGenerator.ORDER_ATOMIC);
- const language = block.getFieldValue('LANGUAGE');
- const code = `await aiService.recognizeVoice(${prompt || "''"}, '${language}')`;
- return [code, javascriptGenerator.ORDER_ATOMIC];
- };
- // 文本生成图片
- javascriptGenerator.forBlock['ai_text_to_image'] = function(block, generator) {
- const prompt = generator.valueToCode(block, 'PROMPT', javascriptGenerator.ORDER_ATOMIC);
- const waitForCompletion = block.getFieldValue('WAIT_FOR_COMPLETION') === 'TRUE';
- const code = `await aiService.textToImage(${prompt}, ${waitForCompletion});`;
- return code;
- };
- // 文本生成视频
- javascriptGenerator.forBlock['ai_text_to_video'] = function(block, generator) {
- const prompt = generator.valueToCode(block, 'PROMPT', javascriptGenerator.ORDER_ATOMIC);
- const waitForCompletion = block.getFieldValue('WAIT_FOR_COMPLETION') === 'TRUE';
- const code = `await aiService.textToVideo(${prompt}, ${waitForCompletion});`;
- return code;
- };
- // 文本生成文本
- javascriptGenerator.forBlock['ai_text_to_text'] = function(block, generator) {
- const prompt = generator.valueToCode(block, 'PROMPT', javascriptGenerator.ORDER_ATOMIC);
- const model = block.getFieldValue('MODEL');
- const code = `await aiService.textToText(${prompt}, '${model}')`;
- return [code, javascriptGenerator.ORDER_ATOMIC];
- };
- // 智能台灯控制(单参数)
- javascriptGenerator.forBlock['ai_smart_lamp_single_param'] = function(block, generator) {
- const params = generator.valueToCode(block, 'PARAMS', javascriptGenerator.ORDER_ATOMIC);
- const code = `await aiService.controlLampWithSingleParam(${params || "'白,0,平静'"});`;
- return code;
- };
- // 智能台灯控制(多参数)
- javascriptGenerator.forBlock['ai_smart_lamp'] = function(block, generator) {
- const brightness = generator.valueToCode(block, 'BRIGHTNESS', javascriptGenerator.ORDER_ATOMIC);
- const color = generator.valueToCode(block, 'COLOR', javascriptGenerator.ORDER_ATOMIC);
- const code = `await aiService.controlLamp(${brightness || '0'}, ${color || "'白'"});`;
- return code;
- };
- // 设置台灯亮度
- javascriptGenerator.forBlock['ai_lamp_set_brightness'] = function(block, generator) {
- const brightness = generator.valueToCode(block, 'BRIGHTNESS', javascriptGenerator.ORDER_ATOMIC);
- const code = `await aiService.setLampBrightness(${brightness || '0'});`;
- return code;
- };
- // 设置台灯颜色
- javascriptGenerator.forBlock['ai_lamp_set_color'] = function(block, generator) {
- const color = generator.valueToCode(block, 'COLOR', javascriptGenerator.ORDER_ATOMIC);
- 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;
- };
- // 打开电视
- javascriptGenerator.forBlock['ai_turn_on_tv'] = function(block, generator) {
- const code = `await aiService.turnOnTv();`;
- return code;
- };
- // 关闭电视
- javascriptGenerator.forBlock['ai_turn_off_tv'] = function(block, generator) {
- const code = `await aiService.turnOffTv();`;
- return code;
- };
- // 打开窗帘
- javascriptGenerator.forBlock['ai_curtain_open'] = function(block, generator) {
- const code = `await aiService.openCurtain();`;
- return code;
- };
- // 关闭窗帘
- javascriptGenerator.forBlock['ai_curtain_close'] = function(block, generator) {
- const code = `await aiService.closeCurtain();`;
- return code;
- };
- }
- // 注册Python代码生成器
- function registerPythonGenerators() {
- // 语音识别
- pythonGenerator.forBlock['ai_voice_input'] = function(block, generator) {
- const prompt = generator.valueToCode(block, 'PROMPT', pythonGenerator.ORDER_ATOMIC);
- const language = block.getFieldValue('LANGUAGE');
- const code = `ai_service.recognize_voice(${prompt || "''"}, '${language}')`;
- return [code, pythonGenerator.ORDER_ATOMIC];
- };
- // 文本生成图片
- pythonGenerator.forBlock['ai_text_to_image'] = function(block, generator) {
- const prompt = generator.valueToCode(block, 'PROMPT', pythonGenerator.ORDER_ATOMIC);
- const waitForCompletion = block.getFieldValue('WAIT_FOR_COMPLETION') === 'TRUE';
- const code = `ai_service.text_to_image(${prompt}, ${waitForCompletion})\n`;
- return code;
- };
- // 文本生成视频
- pythonGenerator.forBlock['ai_text_to_video'] = function(block, generator) {
- const prompt = generator.valueToCode(block, 'PROMPT', pythonGenerator.ORDER_ATOMIC);
- const waitForCompletion = block.getFieldValue('WAIT_FOR_COMPLETION') === 'TRUE';
- const code = `ai_service.text_to_video(${prompt}, ${waitForCompletion})\n`;
- return code;
- };
- // 文本生成文本
- pythonGenerator.forBlock['ai_text_to_text'] = function(block, generator) {
- const prompt = generator.valueToCode(block, 'PROMPT', pythonGenerator.ORDER_ATOMIC);
- const model = block.getFieldValue('MODEL');
- const code = `ai_service.text_to_text(${prompt}, '${model}')`;
- 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 = `ai_service.control_lamp_with_single_param(${params || "'白,0,平静'"})\n`;
- return code;
- };
- // 智能台灯控制
- pythonGenerator.forBlock['ai_smart_lamp'] = function(block, generator) {
- const brightness = generator.valueToCode(block, 'BRIGHTNESS', pythonGenerator.ORDER_ATOMIC);
- const color = generator.valueToCode(block, 'COLOR', pythonGenerator.ORDER_ATOMIC);
- const code = `ai_service.control_lamp(${brightness || '0'}, ${color || "'白'"})\n`;
- return code;
- };
- // 设置台灯亮度
- pythonGenerator.forBlock['ai_lamp_set_brightness'] = function(block, generator) {
- const brightness = generator.valueToCode(block, 'BRIGHTNESS', pythonGenerator.ORDER_ATOMIC);
- const code = `ai_service.set_lamp_brightness(${brightness || '0'})\n`;
- return code;
- };
- // 设置台灯颜色
- pythonGenerator.forBlock['ai_lamp_set_color'] = function(block, generator) {
- const color = generator.valueToCode(block, 'COLOR', pythonGenerator.ORDER_ATOMIC);
- const code = `ai_service.set_lamp_color(${color || "'白'"})\n`;
- return code;
- };
- // 音乐播放
- pythonGenerator.forBlock['ai_music_play'] = function(block, generator) {
- const musicType = block.getFieldValue('MUSIC_TYPE');
- const code = `ai_service.play_music('${musicType}')\n`;
- return code;
- };
- // 打开电视
- pythonGenerator.forBlock['ai_turn_on_tv'] = function(block, generator) {
- const code = `ai_service.turn_on_tv()\n`;
- return code;
- };
- // 关闭电视
- pythonGenerator.forBlock['ai_turn_off_tv'] = function(block, generator) {
- const code = `ai_service.turn_off_tv()\n`;
- return code;
- };
- // 打开窗帘
- pythonGenerator.forBlock['ai_curtain_open'] = function(block, generator) {
- const code = `ai_service.open_curtain()\n`;
- return code;
- };
- // 关闭窗帘
- pythonGenerator.forBlock['ai_curtain_close'] = function(block, generator) {
- const code = `ai_service.close_curtain()\n`;
- return code;
- };
- }
- // 从JSON加载工作区
- const loadWorkspaceFromJson = () => {
- try {
- // const json = JSON.parse(jsonData.value);
- const json = device.value.jsonData;
- console.log(typeof json);
- Blockly.serialization.workspaces.load(json, workspace);
- showStatus('工作区已成功从JSON加载!');
- } catch (error) {
- showStatus('JSON解析错误: ' + error.message, 'error');
- console.error('JSON解析错误:', error);
- }
- };
- // 导出工作区为JSON
- const exportWorkspaceToJson = () => {
- try {
- const state = Blockly.serialization.workspaces.save(workspace);
- // jsonData.value = JSON.stringify(state, null, 2);
- device.value.jsonData = state;
- showStatus('工作区已成功导出为JSON!');
- } catch (error) {
- showStatus('导出错误: ' + error.message, 'error');
- console.error('导出错误:', error);
- }
- };
- // 生成代码
- const generateCode = (language = 'javascript') => {
- try {
- let generator;
- if (language == "javascript") {
- generator = javascriptGenerator;
- } else if (language == "python") {
- generator = pythonGenerator;
- } else {
- console.error("不支持的语言类型");
- return;
- }
- const code = generator.workspaceToCode(workspace);
- output.value = code;
- // 将生成的代码也输出到JSON框中
- // 创建一个包含代码的JSON对象
- const codeJson = {
- "generated_code": {
- "language": language,
- "code": code
- }
- };
- // 将JSON对象格式化为字符串并设置到JSON框
- // jsonData.value = JSON.stringify(codeJson, null, 2);
- device.value.jsonData = codeJson;
- showStatus('代码生成成功!已同时输出到JSON框');
- } catch (error) {
- output.value = '// 代码生成错误: ' + error.message;
- showStatus('代码生成错误: ' + error.message, 'error');
- console.error('代码生成错误:', error);
- }
- };
- // 运行代码
- const runCode = async () => {
- try {
- const code = javascriptGenerator.workspaceToCode(workspace);
- // 初始化输出区域,显示生成的代码和执行结果标题
- output.value = code + '\n\n// 执行结果:\n';
- // 保存原始console方法
- const originalConsoleLog = console.log;
- const originalConsoleError = console.error;
- const originalConsoleWarn = console.warn;
- // 创建输出缓冲区
- let outputBuffer = "";
- // 重定义console.log方法,确保所有日志都输出到output变量
- console.log = (...args) => {
- // 将参数转换为字符串
- const message = args.map(arg => {
- if (typeof arg === 'object') {
- try {
- return JSON.stringify(arg, null, 2);
- } catch (e) {
- return String(arg);
- }
- }
- return arg;
- }).join(' ');
- // 过滤掉Vue警告信息
- if (!message.includes('[Vue warn]')) {
- outputBuffer += message + '\n';
- // 实时更新输出区域内容
- output.value = code + '\n\n// 执行结果:\n' + outputBuffer;
- }
- // 保留原始console.log功能
- originalConsoleLog.apply(console, args);
- };
- // 重定义console.error方法
- console.error = (...args) => {
- const message = args.map(arg => {
- if (typeof arg === 'object') {
- try {
- return JSON.stringify(arg, null, 2);
- } catch (e) {
- return String(arg);
- }
- }
- return arg;
- }).join(' ');
- if (!message.includes('[Vue warn]')) {
- outputBuffer += '// 错误: ' + message + '\n';
- output.value = code + '\n\n// 执行结果:\n' + outputBuffer;
- }
- originalConsoleError.apply(console, args);
- };
- // 重定义console.warn方法
- console.warn = (...args) => {
- const message = args.map(arg => {
- if (typeof arg === 'object') {
- try {
- return JSON.stringify(arg, null, 2);
- } catch (e) {
- return String(arg);
- }
- }
- return arg;
- }).join(' ');
- if (!message.includes('[Vue warn]')) {
- outputBuffer += '// 警告: ' + message + '\n';
- output.value = code + '\n\n// 执行结果:\n' + outputBuffer;
- }
- originalConsoleWarn.apply(console, args);
- };
- try {
- // 安全检查
- if (code.includes('eval(') || code.includes('Function(') ||
- code.includes('document.write') || code.includes('window.location')) {
- throw new Error('代码包含不安全的操作');
- }
- // 包装代码为异步函数执行,支持await
- const wrappedCode = `(async () => { ${code} })()`;
- await new Function(wrappedCode)();
- // 确保最终结果被正确显示
- output.value = code + '\n\n// 执行结果:\n' + outputBuffer;
- } catch (error) {
- // 捕获并显示执行错误
- outputBuffer += '\n// 执行错误: ' + error.message;
- output.value = code + '\n\n// 执行结果:\n' + outputBuffer;
- } finally {
- // 恢复原始console方法
- // console.log = originalConsoleLog;
- // console.error = originalConsoleError;
- // console.warn = originalConsoleWarn;
- }
- // 显示状态消息
- showStatus('代码执行成功!');
- } catch (error) {
- // 处理runCode函数本身的错误
- output.value += '\n// 执行错误: ' + error.message;
- showStatus('代码执行错误: ' + error.message, 'error');
- console.error('代码执行错误:', error);
- }
- };
- // 清空工作区
- const clearWorkspace = () => {
- workspace.clear();
- showStatus('工作区已清空!');
- };
- // 清空输出
- const clearOutput = () => {
- output.value = '// 输出已清空\n';
- };
- // 显示状态消息
- const showStatus = (message, type = 'success') => {
- statusMessage.value = message;
- statusType.value = type;
- // 3秒后自动清除状态消息
- // setTimeout(() => {
- // statusMessage.value = '';
- // }, 3000);
- };
- </script>
- <style scoped lang="scss">
- @use "sass:math";
- @function rpx($px) {
- @return math.div($px, 750) * 100vw;
- }
- * {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
- }
- .container {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-image: url('@/assets/images/desklamp.png');
- background-size: cover;
- background-position: center;
- background-repeat: no-repeat;
- /* 添加黑色透明遮挡层 */
- background-color: rgba(0, 0, 0, 0.5);
- background-blend-mode: overlay;
- overflow-y: auto;
- }
- /* 自定义滚动条样式 */
- .container::-webkit-scrollbar {
- width: rpx(2); /* 滚动条宽度 */
- }
- .container::-webkit-scrollbar-track {
- background: #f1effd; /* 滚动条轨道背景色 */
- border-radius: rpx(4);
- }
- .container::-webkit-scrollbar-thumb {
- background: #e2ddfc; /* 滚动条滑块颜色 */
- border-radius: rpx(4);
- }
- .container::-webkit-scrollbar-thumb:hover {
- background: #e2ddfc; /* 滚动条滑块 hover 状态颜色 */
- }
- .content {
- display: flex;
- flex-wrap: wrap;
- min-height: 600px;
- }
- //工具箱
- .toolbox-section {
- flex: 1;
- //min-width: 250px;
- background: rgba(248, 249, 250, 0.82);
- padding: 15px;
- border-radius: 15px;
- display: flex;
- flex-direction: column;
- width: 30%;
- margin-left: 10px;
- margin-right: 10px;
- }
- //工作区
- .workspace-section {
- flex: 3;
- //min-width: 400px;
- padding: 15px;
- position: relative;
- width: 65%;
- height: 70%;
- background: rgba(248, 249, 250, 0.82);
- border-radius: 10px;
- margin-left: 10px;
- margin-right: 10px;
- }
- .output-section {
- background: rgba(241, 248, 255, 84%);
- color: black;
- padding: 15px;
- border-radius: 8px;
- border: 1px solid #d1e7ff;
- width: 100%;
- margin: 15px 10px;
- }
- /* AI模块样式 */
- [categorystyle="ai_category"] > .blocklyTreeRow {
- background-color: #9c27b0 !important;
- }
- /* 预览样式 */
- .preview-modal {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: rgba(0, 0, 0, 0.7);
- display: flex;
- justify-content: center;
- align-items: center;
- z-index: 1000;
- }
- .preview-content {
- background-color: white;
- padding: 20px;
- border-radius: 8px;
- max-width: 80%;
- max-height: 80%;
- overflow: auto;
- position: relative;
- }
- .close-button {
- position: absolute;
- top: 10px;
- right: 10px;
- font-size: 24px;
- background: none;
- border: none;
- cursor: pointer;
- color: #333;
- }
- .preview-image-container,
- .preview-video-container {
- display: flex;
- justify-content: center;
- }
- .preview-image,
- .preview-video {
- max-width: 100%;
- max-height: 60vh;
- border-radius: 4px;
- }
- .preview-text-container {
- max-height: 60vh;
- overflow-y: auto;
- padding: 10px;
- background-color: #f5f5f5;
- border-radius: 4px;
- color: #333;
- }
- /* 文生图预览 */
- .extra-image-preview {
- color: black;
- margin-top: 10px;
- padding: 10px;
- border: 1px solid #ddd;
- border-radius: 5px;
- background-color: #f9f9f9;
- }
- .extra-preview-image {
- max-width: 100%;
- max-height: 400px;
- border-radius: 4px;
- }
- /* 音乐播放器 */
- .music-player-container {
- margin-top: 20px;
- padding: 15px;
- background-color: #f9f9f9;
- border-radius: 8px;
- border: 1px solid #e0e0e0;
- color: #333;
- }
- .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;
- }
- h2 {
- margin-bottom: 15px;
- color: #2c3e50;
- border-bottom: 2px solid #3498db;
- padding-bottom: 8px;
- }
- .output-section h2 {
- color: black;
- border-bottom: 2px solid #3498db;
- }
- #blocklyDiv {
- height: 500px;
- width: 100%;
- background: white;
- border: 1px solid #ddd;
- border-radius: 8px;
- }
- .controls {
- display: flex;
- gap: 10px;
- margin-top: 15px;
- flex-wrap: wrap;
- }
- button {
- padding: 10px 20px;
- border: none;
- border-radius: 5px;
- background: #3498db;
- color: white;
- font-weight: bold;
- cursor: pointer;
- transition: all 0.3s ease;
- }
- button:hover {
- background: #2980b9;
- transform: translateY(-2px);
- box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
- }
- #generateCode {
- background: #2ecc71;
- }
- #generateCode:hover {
- background: #27ae60;
- }
- #runCode {
- background: #e74c3c;
- }
- #runCode:hover {
- background: #c0392b;
- }
- #output {
- // background: #1a2530;
- background: rgba($color: #ffffff, $alpha: 0.5);
- padding: 10px;
- border-radius: 5px;
- min-height: 100px;
- max-height: 300px;
- margin-top: 10px;
- font-family: 'Courier New', monospace;
- white-space: pre-wrap;
- overflow-y: auto;
- font-size: 12px;
- }
- .json-section {
- margin-top: 15px;
- padding: 15px;
- background: #f1f8ff;
- border-radius: 8px;
- border: 1px solid #d1e7ff;
- }
- .json-section h3 {
- margin-bottom: 10px;
- color: #2c3e50;
- }
- textarea {
- width: 100%;
- min-height: 120px;
- padding: 10px;
- border: 1px solid #ddd;
- border-radius: 5px;
- font-family: 'Courier New', monospace;
- resize: vertical;
- }
- .status {
- margin-top: 10px;
- padding: 8px 12px;
- border-radius: 4px;
- font-weight: bold;
- }
- .success {
- background: #d4edda;
- color: #155724;
- border: 1px solid #c3e6cb;
- }
- .error {
- background: #f8d7da;
- color: #721c24;
- border: 1px solid #f5c6cb;
- }
- /* 智能台灯 */
- .desk-lamp-container {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- overflow: hidden;
- display: flex;
- justify-content: center;
- align-items: center;
- z-index: 1000;
- }
- .image-overlay-container {
- position: relative;
- width: 100%;
- height: 100%;
- }
- .base-image{
- position: absolute;
- top: 0;
- left: 0;
- z-index: 1;
- }
- .overlay-image{
- position: absolute;
- top: 0;
- left: 0;
- z-index: 2;
- }
- .full-screen-image {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- /* 台灯灯光样式 - 使用动态颜色 */
- .lamp-light-mask {
- position: absolute;
- /* 使用更精确的定位,并针对不同屏幕尺寸进行优化 */
- top: 56%; /* 调整这个值以匹配台灯的位置 */
- left: 64%; /* 调整这个值以匹配台灯的位置 */
- width: 18vmin; /* 使用视口单位使灯光大小随屏幕变化 */
- height: 60vmin;
- /* 使用transform进行精确定位 */
- transform: translate(-50%, -50%) rotate(9deg);
- transform-origin: top center;
- background: radial-gradient(circle at center top, var(--lamp-color) 0%, var(--lamp-color) 20%, rgba(255, 255, 255, 0) 70%);
- filter: blur(40px);
- opacity: var(--lamp-opacity, 0.6);
- /* 创建扇形效果 */
- clip-path: polygon(0% 0%, 100% 0%, 200% 100%, -100% 100%);
- z-index: 1; /* 确保在图片上方但在UI控件下方 */
- /* 固定定位确保在全屏时也能正确显示 */
- will-change: transform;
- }
- /* 响应式调整:在不同屏幕尺寸下微调灯光位置 */
- @media (max-aspect-ratio: 4/3) {
- .lamp-light-mask {
- top: 56%;
- left: 64%;
- }
- }
- @media (min-aspect-ratio: 16/9) {
- .lamp-light-mask {
- top: 55%;
- left: 63%;
- }
- }
- /* 灯光信息显示 */
- .lamp-info {
- position: absolute;
- bottom: 100px;
- right: 30px;
- background-color: rgba(255, 255, 255, 0.2);
- padding: 10px 20px;
- border-radius: 8px;
- backdrop-filter: blur(10px);
- color: white;
- font-size: 14px;
- z-index: 1000;
- }
- /* 标题框样式 */
- .desk-lamp-title-box {
- position: absolute;
- top: 20px;
- left: 20px;
- z-index: 1000;
- }
- .desk-lamp-box-icon {
- display: flex;
- align-items: center;
- gap: 10px;
- padding: 10px 20px;
- background-color: rgba(255, 255, 255, 0.2);
- border-radius: 30px;
- backdrop-filter: blur(10px);
- cursor: pointer;
- transition: all 0.3s ease;
- font-size: 16px;
- color: white;
- font-weight: 500;
- }
- .desk-lamp-box-icon:hover {
- background-color: rgba(255, 255, 255, 0.3);
- transform: translateX(-3px);
- }
- .left-icon {
- font-size: 18px;
- }
- /* 右下角按钮组样式 */
- .button-group {
- position: absolute;
- bottom: 30px;
- right: 30px;
- display: flex;
- gap: 15px;
- z-index: 1000;
- }
- .control-button {
- padding: 12px 24px;
- border-radius: 8px;
- font-size: 16px;
- font-weight: 500;
- transition: all 0.3s ease;
- backdrop-filter: blur(5px);
- }
- .run-button {
- background-color: rgba(64, 169, 255, 0.8);
- color: white;
- border: none;
- }
- .run-button:hover {
- background-color: rgba(64, 169, 255, 1);
- transform: translateY(-2px);
- }
- .code-button {
- background-color: rgba(132, 94, 255, 0.8);
- color: white;
- border: none;
- }
- .code-button:hover {
- background-color: rgba(132, 94, 255, 1);
- transform: translateY(-2px);
- }
- /* 返回按钮样式 */
- .title-box {
- position: relative;
- top: 10px;
- padding-left: 15px;
- margin-bottom: 20px;
- z-index: 10;
- }
- .box-icon {
- display: flex;
- align-items: center;
- gap: 10px;
- padding: 10px 20px;
- background-color: rgba(255, 255, 255, 80%);
- border-radius: 30px;
- backdrop-filter: blur(10px);
- cursor: pointer;
- transition: all 0.3s ease;
- font-size: 16px;
- color: #333;
- font-weight: 500;
- width: fit-content;
- }
- .box-icon:hover {
- background-color: rgba(255, 255, 255, 90%);
- transform: translateX(-3px);
- }
- .left-icon {
- font-size: 18px;
- }
- /* 语音识别-录音状态容器 */
- .recording-status-container {
- position: absolute;
- top: 20%;
- left: 50%;
- transform: translateX(-50%);
- background: rgba(0, 0, 0, 0.7);
- color: white;
- padding: 20px 40px;
- border-radius: 10px;
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
- text-align: center;
- z-index: 1000;
- backdrop-filter: blur(5px);
- }
- .recording-text {
- font-size: 18px;
- margin-bottom: 15px;
- font-weight: 500;
- }
- .recording-countdown {
- font-size: 16px;
- color: #4CAF50;
- margin-top: 15px;
- font-weight: bold;
- }
- .equalizer {
- display: flex;
- justify-content: center;
- align-items: flex-end;
- height: 60px;
- gap: 4px;
- }
- .bar {
- width: 8px;
- background: linear-gradient(180deg, #4CAF50 0%, #8BC34A 100%);
- border-radius: 4px;
- animation: equalize 1s infinite ease-in-out;
- }
- .bar-1 { animation-delay: 0s; }
- .bar-2 { animation-delay: 0.1s; }
- .bar-3 { animation-delay: 0.2s; }
- .bar-4 { animation-delay: 0.3s; }
- .bar-5 { animation-delay: 0.4s; }
- .bar-6 { animation-delay: 0.5s; }
- .bar-7 { animation-delay: 0.6s; }
- @keyframes equalize {
- 0%, 100% { height: 10px; }
- 25% { height: 40px; }
- 50% { height: 60px; }
- 75% { height: 25px; }
- }
- //台灯播放音乐
- .music-info {
- margin-top: 10px;
- padding: 8px;
- background-color: rgba(255, 255, 255, 0.1);
- border-radius: 6px;
- }
- .music-info p {
- margin: 0 0 8px 0;
- color: #ffffff;
- font-size: 14px;
- }
- .stop-music-btn {
- background-color: #ff4d4f;
- color: white;
- border: none;
- padding: 6px 12px;
- border-radius: 4px;
- cursor: pointer;
- font-size: 12px;
- transition: background-color 0.3s;
- }
- .stop-music-btn:hover {
- background-color: #ff7875;
- }
- </style>
|