Ver código fonte

Merge branch 'master' of http://59.110.91.129:3000/zhangmengying/AIClass into wanzi

丸子 10 meses atrás
pai
commit
e6ea17cbd9
4 arquivos alterados com 1856 adições e 5 exclusões
  1. 1 1
      src/views/AIPainting.vue
  2. 1550 0
      src/views/Blockly.vue
  3. 301 0
      src/views/Blockly_bak.vue
  4. 4 4
      src/views/Login.vue

+ 1 - 1
src/views/AIPainting.vue

@@ -399,7 +399,7 @@ const sendMessage = async() => {
 
     try {
       CreatePainting({
-        "modelId": 57,
+        "modelId": 56,
         "prompt":content,
         "width":1024,
         "height":1024

+ 1550 - 0
src/views/Blockly.vue

@@ -0,0 +1,1550 @@
+<template>
+  <!-- AI发展历程 -->
+  <div class="home-container">
+    <!-- 展开收起侧边栏 -->
+    <div class="icon-expand">
+      <el-icon
+          @click="toggleDrawer"
+          :style="{ color: drawerVisible ? 'white' : '#B6B0D8' }"
+      >
+        <component :is="drawerVisible ? Fold : Expand" />
+        <!-- <Tickets /> -->
+      </el-icon>
+    </div>
+
+    <transition name="drawer-slide">
+      <div class="main-content" v-if="drawerVisible">
+        <!-- 添加抽屉 -->
+        <div class="drawer-box">
+          <el-row class="tac">
+            <el-col :span="12">
+              <h3 class="mb-2">
+                <el-icon><Memo /></el-icon>
+                课程小节
+              </h3>
+              <el-menu
+                  :default-active="currentIndex"
+                  @open="handleOpen"
+                  @close="handleClose"
+                  @select="handleSelect"
+                  :default-openeds="['1']"
+              >
+                <template v-for="item in menuItems" :key="item.index">
+                  <el-menu-item v-if="!item.children" :index="item.index">{{
+                      item.title
+                    }}</el-menu-item>
+                  <el-sub-menu v-else :index="item.index">
+                    <template #title>
+                      <span>{{ item.title }}</span>
+                    </template>
+                    <el-menu-item-group v-if="item.children">
+                      <template
+                          v-for="child in item.children"
+                          :key="child.index"
+                      >
+                        <el-menu-item :index="child.index">{{
+                            child.title
+                          }}</el-menu-item>
+                      </template>
+                    </el-menu-item-group>
+                  </el-sub-menu>
+                </template>
+              </el-menu>
+            </el-col>
+          </el-row>
+        </div>
+      </div>
+    </transition>
+
+    <div class="content-box">
+      <div class="box-1">
+        <div class="inner-box left-box">
+          <div class="box-icon" @click="goBack">
+            <el-icon class="left-icon"><ArrowLeftBold /></el-icon>
+            {{ boxIconTitle }}
+          </div>
+        </div>
+        <div class="inner-box right-box">
+          <div class="top-right-box">
+            <el-input
+                v-model="SearchInput"
+                class="search-input"
+                placeholder="搜索"
+            >
+              <template #prefix>
+                <el-icon class="el-input__icon"><search /></el-icon>
+              </template>
+            </el-input>
+          </div>
+        </div>
+      </div>
+      <div class="box-2">
+        <!-- 课程标题 autoplay自动播放   @ended="playNextVideo"-->
+        <div class="small-title">
+          <span>{{ smallTitle }}</span>
+        </div>
+        <div class="box-blockly">
+          <div id="blocklyDiv" style="height: 100%; width: 100%; background-color: #cccccc"></div>
+
+          <!-- blockly工具栏 -->
+          <!-- 使用 template 标签包裹 XML 内容 -->
+          <template >
+            <xml id="toolbox" style="display: none">
+<!--              <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>
+              <sep></sep>
+              <category name="数学" colour="%{BKY_MATH_HUE}">
+                <block type="math_number">
+                  <field name="NUM">123</field>
+                </block>
+                <block type="math_number">
+                  <field name="NUM">213312</field>
+                </block>
+                <block type="math_arithmetic"></block>
+                <block type="math_single"></block>
+              </category>-->
+              <category name="逻辑" categorystyle="logic_category">
+                <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>
+                <block type="logic_null" disabled="true"></block>
+                <block type="logic_ternary"></block>
+              </category>
+              <category name="循环" categorystyle="loop_category">
+                <block type="controls_repeat_ext">
+                  <value name="TIMES">
+                    <shadow type="math_number">
+                      <field name="NUM">10</field>
+                    </shadow>
+                  </value>
+                </block>
+                <block type="controls_repeat" disabled="true"></block>
+                <block type="controls_whileUntil"></block>
+                <block type="controls_for">
+                  <value name="FROM">
+                    <shadow type="math_number">
+                      <field name="NUM">1</field>
+                    </shadow>
+                  </value>
+                  <value name="TO">
+                    <shadow type="math_number">
+                      <field name="NUM">10</field>
+                    </shadow>
+                  </value>
+                  <value name="BY">
+                    <shadow type="math_number">
+                      <field name="NUM">1</field>
+                    </shadow>
+                  </value>
+                </block>
+                <block type="controls_forEach"></block>
+                <block type="controls_flow_statements"></block>
+              </category>
+              <category name="数值" categorystyle="math_category">
+                <block type="math_number" gap="32">
+                  <field name="NUM">123</field>
+                </block>
+                <block type="math_arithmetic">
+                  <value name="A">
+                    <shadow type="math_number">
+                      <field name="NUM">1</field>
+                    </shadow>
+                  </value>
+                  <value name="B">
+                    <shadow type="math_number">
+                      <field name="NUM">1</field>
+                    </shadow>
+                  </value>
+                </block>
+                <block type="math_single">
+                  <value name="NUM">
+                    <shadow type="math_number">
+                      <field name="NUM">9</field>
+                    </shadow>
+                  </value>
+                </block>
+                <block type="math_trig">
+                  <value name="NUM">
+                    <shadow type="math_number">
+                      <field name="NUM">45</field>
+                    </shadow>
+                  </value>
+                </block>
+                <block type="math_constant"></block>
+                <block type="math_number_property">
+                  <value name="NUMBER_TO_CHECK">
+                    <shadow type="math_number">
+                      <field name="NUM">0</field>
+                    </shadow>
+                  </value>
+                </block>
+                <block type="math_round">
+                  <value name="NUM">
+                    <shadow type="math_number">
+                      <field name="NUM">3.1</field>
+                    </shadow>
+                  </value>
+                </block>
+                <block type="math_on_list"></block>
+                <block type="math_modulo">
+                  <value name="DIVIDEND">
+                    <shadow type="math_number">
+                      <field name="NUM">64</field>
+                    </shadow>
+                  </value>
+                  <value name="DIVISOR">
+                    <shadow type="math_number">
+                      <field name="NUM">10</field>
+                    </shadow>
+                  </value>
+                </block>
+                <block type="math_constrain">
+                  <value name="VALUE">
+                    <shadow type="math_number">
+                      <field name="NUM">50</field>
+                    </shadow>
+                  </value>
+                  <value name="LOW">
+                    <shadow type="math_number">
+                      <field name="NUM">1</field>
+                    </shadow>
+                  </value>
+                  <value name="HIGH">
+                    <shadow type="math_number">
+                      <field name="NUM">100</field>
+                    </shadow>
+                  </value>
+                </block>
+                <block type="math_random_int">
+                  <value name="FROM">
+                    <shadow type="math_number">
+                      <field name="NUM">1</field>
+                    </shadow>
+                  </value>
+                  <value name="TO">
+                    <shadow type="math_number">
+                      <field name="NUM">100</field>
+                    </shadow>
+                  </value>
+                </block>
+                <block type="math_random_float"></block>
+                <block type="math_atan2">
+                  <value name="X">
+                    <shadow type="math_number">
+                      <field name="NUM">1</field>
+                    </shadow>
+                  </value>
+                  <value name="Y">
+                    <shadow type="math_number">
+                      <field name="NUM">1</field>
+                    </shadow>
+                  </value>
+                </block>
+              </category>
+              <category name="文本" categorystyle="text_category">
+                <block type="text"></block>
+                <block type="text_join"></block>
+                <block type="text_append">
+                  <value name="TEXT">
+                    <shadow type="text"></shadow>
+                  </value>
+                </block>
+                <block type="text_length">
+                  <value name="VALUE">
+                    <shadow type="text">
+                      <field name="TEXT">abc</field>
+                    </shadow>
+                  </value>
+                </block>
+                <block type="text_isEmpty">
+                  <value name="VALUE">
+                    <shadow type="text">
+                      <field name="TEXT"></field>
+                    </shadow>
+                  </value>
+                </block>
+                <block type="text_indexOf">
+                  <value name="VALUE">
+                    <block type="variables_get">
+                      <field name="VAR">text</field>
+                    </block>
+                  </value>
+                  <value name="FIND">
+                    <shadow type="text">
+                      <field name="TEXT">abc</field>
+                    </shadow>
+                  </value>
+                </block>
+                <block type="text_charAt">
+                  <value name="VALUE">
+                    <block type="variables_get">
+                      <field name="VAR">text</field>
+                    </block>
+                  </value>
+                </block>
+                <block type="text_getSubstring">
+                  <value name="STRING">
+                    <block type="variables_get">
+                      <field name="VAR">text</field>
+                    </block>
+                  </value>
+                </block>
+                <block type="text_changeCase">
+                  <value name="TEXT">
+                    <shadow type="text">
+                      <field name="TEXT">abc</field>
+                    </shadow>
+                  </value>
+                </block>
+                <block type="text_trim">
+                  <value name="TEXT">
+                    <shadow type="text">
+                      <field name="TEXT">abc</field>
+                    </shadow>
+                  </value>
+                </block>
+                <block type="text_count">
+                  <value name="SUB">
+                    <shadow type="text"></shadow>
+                  </value>
+                  <value name="TEXT">
+                    <shadow type="text"></shadow>
+                  </value>
+                </block>
+                <block type="text_replace">
+                  <value name="FROM">
+                    <shadow type="text"></shadow>
+                  </value>
+                  <value name="TO">
+                    <shadow type="text"></shadow>
+                  </value>
+                  <value name="TEXT">
+                    <shadow type="text"></shadow>
+                  </value>
+                </block>
+                <block type="text_reverse">
+                  <value name="TEXT">
+                    <shadow type="text"></shadow>
+                  </value>
+                </block>
+                <label text="Input/Output:" web-class="ioLabel"></label>
+                <block type="text_print">
+                  <value name="TEXT">
+                    <shadow type="text">
+                      <field name="TEXT">abc</field>
+                    </shadow>
+                  </value>
+                </block>
+                <block type="text_prompt_ext">
+                  <value name="TEXT">
+                    <shadow type="text">
+                      <field name="TEXT">abc</field>
+                    </shadow>
+                  </value>
+                </block>
+              </category>
+              <category name="列表" categorystyle="list_category">
+                <block type="lists_create_with">
+                  <mutation items="0"></mutation>
+                </block>
+                <block type="lists_create_with"></block>
+                <block type="lists_repeat">
+                  <value name="NUM">
+                    <shadow type="math_number">
+                      <field name="NUM">5</field>
+                    </shadow>
+                  </value>
+                </block>
+                <block type="lists_length"></block>
+                <block type="lists_isEmpty"></block>
+                <block type="lists_indexOf">
+                  <value name="VALUE">
+                    <block type="variables_get">
+                      <field name="VAR">list</field>
+                    </block>
+                  </value>
+                </block>
+                <block type="lists_getIndex">
+                  <value name="VALUE">
+                    <block type="variables_get">
+                      <field name="VAR">list</field>
+                    </block>
+                  </value>
+                </block>
+                <block type="lists_setIndex">
+                  <value name="LIST">
+                    <block type="variables_get">
+                      <field name="VAR">list</field>
+                    </block>
+                  </value>
+                </block>
+                <block type="lists_getSublist">
+                  <value name="LIST">
+                    <block type="variables_get">
+                      <field name="VAR">list</field>
+                    </block>
+                  </value>
+                </block>
+                <block type="lists_split">
+                  <value name="DELIM">
+                    <shadow type="text">
+                      <field name="TEXT">,</field>
+                    </shadow>
+                  </value>
+                </block>
+                <block type="lists_sort"></block>
+                <block type="lists_reverse"></block>
+              </category>
+              <sep></sep>
+              <category
+                  name="Variables"
+                  categorystyle="variable_category"
+                  custom="VARIABLE"></category>
+              <category
+                  name="Functions"
+                  categorystyle="procedure_category"
+                  custom="PROCEDURE"></category>
+            </xml>
+          </template>
+        </div>
+
+        <div class="box-code">
+          <el-button type="info" plain id="save-json" @click="saveJson()">保存 JSON</el-button>
+          <el-button type="info" plain id="save-xml" @click="saveXml()">保存 XML</el-button>
+          <el-button type="warning" plain id="import" @click="load()">重新加载</el-button>
+          <br/><br/>
+
+          <!-- 添加生成 JavaScript 和 Python 代码的按钮 -->
+          <el-button type="primary" plain id="to-code-py" @click="generateCode('javascript')">生成 JavaScript</el-button>
+          <el-button type="success" plain id="to-code-py" @click="generateCode('python')">生成 Python</el-button>
+          <br/><br/>
+          <!-- blockly代码区 -->
+          <textarea name="" id="textarea" class="box-code-textarea"></textarea>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+import {
+  Expand,
+  Fold,
+  Memo,
+  Delete
+} from '@element-plus/icons-vue'
+import { Search, ArrowLeftBold } from '@element-plus/icons-vue'
+import {Message} from "@/utils/message/Message.js";
+import Blockly from 'blockly';
+import {javascriptGenerator} from 'blockly/javascript';
+// 引入 Python 代码生成器
+import {pythonGenerator} from 'blockly/python';
+// 引入想要转换的语言,语言有php python dart lua javascript
+// import 'blockly/javascript'
+// 引入语言包并使用
+import * as hans from 'blockly/msg/zh-hans'
+Blockly.setLocale(hans);
+
+const router = useRouter() // 获取当前路由对象
+// 搜索框
+const SearchInput = ref('')
+// 添加抽屉显示状态
+const drawerVisible = ref(true)
+// 添加切换抽屉显示状态的函数
+const toggleDrawer = () => {
+  drawerVisible.value = !drawerVisible.value
+}
+
+// 返回上一页
+const goBack = () => {
+  router.go(-1)
+}
+// 渲染页面标题
+const boxIconTitle = ref('')
+// 使用 ref 存储 workspace
+const workspace = ref(null)
+
+onMounted(async () => {
+  const typeId = router.currentRoute.value.query.typeId
+  console.log(typeId,"----")
+  if (typeId) {
+
+  }
+  const title = router.currentRoute.value.query.typeName
+  if (title) {
+    boxIconTitle.value = String(title)
+  }
+
+  workspace.value = Blockly.inject('blocklyDiv',
+      {
+        //工具栏
+        toolbox: document.getElementById('toolbox'),
+        //网格效果
+        grid:{spacing: 20,length: 3,colour: '#ccc',snap: true}
+      }
+  );
+  // 工作区监听代码生成器
+  // 修改为传递一个函数
+  workspace.value.addChangeListener(() => generateCode("python"));
+})
+
+//保存Json
+function saveJson() {
+  var output = document.getElementById('textarea');
+  var state = Blockly.serialization.workspaces.save(workspace.value);
+  output.value = JSON.stringify(state, null, 2);
+  output.focus();
+  output.select();
+  taChange();
+}
+
+//保存Xml
+function saveXml() {
+  var output = document.getElementById('textarea');
+  var xml = Blockly.Xml.workspaceToDom(workspace.value);
+  output.value = Blockly.Xml.domToPrettyText(xml);
+  output.focus();
+  output.select();
+  taChange();
+}
+
+//重新加载
+function load() {
+  var input = document.getElementById('textarea');
+  if (!input.value) {
+    return;
+  }
+  var valid = saveIsValid(input.value);
+  if (valid.json) {
+    var state = JSON.parse(input.value);
+    Blockly.serialization.workspaces.load(state, workspace.value);
+  } else if (valid.xml) {
+    var xml = Blockly.utils.xml.textToDom(input.value);
+    Blockly.Xml.domToWorkspace(xml, workspace.value);
+  }
+  taChange();
+}
+
+//
+function taChange() {
+  var textarea = document.getElementById('textarea');
+  if (sessionStorage) {
+    sessionStorage.setItem('textarea', textarea.value);
+  }
+  var valid = saveIsValid(textarea.value);
+  document.getElementById('import').disabled = !valid.json && !valid.xml;
+}
+
+function saveIsValid(save) {
+  var validJson = true;
+  try {
+    JSON.parse(save);
+  } catch (e) {
+    validJson = false;
+  }
+  var validXml = true;
+  try {
+    Blockly.utils.xml.textToDom(save);
+  } catch (e) {
+    validXml = false;
+  }
+  return {
+    json: validJson,
+    xml: validXml,
+  };
+}
+
+// 定义生成代码的函数
+const generateCode = (language) => {
+  if (!workspace.value) {
+    console.error('workspace 未正确初始化');
+    return;
+  }
+  let generator;
+  if (language === 'javascript') {
+    generator = javascriptGenerator;
+  } else if (language === 'python') {
+    generator = pythonGenerator;
+  } else {
+    console.error('不支持的语言类型');
+    return;
+  }
+  const code = generator.workspaceToCode(workspace.value);
+  document.getElementById('textarea').value = code;
+};
+// 代码生成器
+const myUpdateFunction = () => {
+  // 修改此处,传入 workspace.value
+  const code = javascriptGenerator.workspaceToCode(workspace.value);
+  document.getElementById('textarea').value = code;
+};
+
+// 获取 blockly 工作区中的 code 和 xml 结构
+const getBlockData = () => {
+  if (workspace.value) {
+    const code = Blockly.JavaScript.workspaceToCode(workspace.value);
+    const xml = Blockly.Xml.workspaceToDom(workspace.value)
+    const xmlText = Blockly.Xml.domToText(xml);
+    // 可以在这里返回或处理 code 和 xmlText
+    return { code, xmlText };
+  } else {
+    console.error('workspace 或 Blockly.JavaScript 未正确初始化');
+  }
+};
+
+// 回显工作区中的 xml 结构
+const setBlockData = (xmlText) => {
+  if (workspace.value) {
+    clearBlockData();
+    const xml = Blockly.Xml.textToDom(xmlText);
+    Blockly.Xml.domToWorkspace(xml, workspace.value);
+  } else {
+    console.error('workspace 未正确初始化');
+  }
+};
+
+// 清空工作区
+const clearBlockData = () => {
+  if (workspace.value) {
+    workspace.value.clear();
+  } else {
+    console.error('workspace 未正确初始化');
+  }
+};
+
+
+// 菜单打开和关闭的处理函数
+const handleOpen = () => {}
+const handleClose = () => {}
+
+// 动态渲染树型结构
+const menuItems = ref([
+  { index: '1-1', title: '课前回顾' },
+  { index: '1-2', title: '课程引入' },
+  {
+    index: '1',
+    title: '知识讲解',
+    children: [
+      { index: '1-3', title: '图灵测试' },
+      { index: '1-4', title: '课堂提问' },
+      { index: '1-5', title: '达特茅斯会议' },
+      { index: '1-6', title: '课堂选择' },
+      { index: '1-7', title: '聊天机器人' },
+      { index: '1-8', title: '专家系统' },
+      { index: '1-9', title: '课堂互动' },
+      { index: '1-10', title: '互联网快速发展' },
+      { index: '1-11', title: '黄金时代' },
+      { index: '1-12', title: '课堂提问' },
+      { index: '1-13', title: '大模型时代' }
+    ]
+  },
+  { index: '1-14', title: '趣味实操' },
+  { index: '1-15', title: '课程总结' }
+])
+
+// 当前播放的索引
+const currentIndex = ref('1-1')
+
+// 渲染课程标题到视频上方
+const smallTitle = ref('课前回顾')
+const handleSelect = index => {
+  //测试账号禁用视频
+  if (disableVideo(index))return;
+
+  const findTitle = items => {
+    for (const item of items) {
+      if (item.index === index) {
+        return item.title
+      }
+      if (item.children) {
+        const result = findTitle(item.children)
+        if (result) {
+          return result
+        }
+      }
+    }
+    return null
+  }
+  const title = findTitle(menuItems.value)
+  if (title) {
+    smallTitle.value = title
+  }
+  // 根据索引切换视频
+  if (videoMap[index]) {
+    videoSrc.value = videoMap[index]
+    currentIndex.value = index
+  }
+
+}
+
+// 展平所有菜单项索引
+const flattenMenuItems = () => {
+  const indices = []
+  const traverse = items => {
+    for (const item of items) {
+      if (!item.children) {
+        indices.push(item.index)
+      } else {
+        traverse(item.children)
+      }
+    }
+  }
+  traverse(menuItems.value)
+  return indices
+}
+
+//禁用视频
+const disableVideo = (index = currentIndex.value) => {
+  let dis = ["1-6","1-7","1-8","1-9","1-10","1-11","1-12","1-13","1-14","1-15"]
+
+  if (localStorage.getItem('userName') === "aiTest" &&
+      dis.indexOf(index) !== -1) {
+
+    if (videoRef.value) {
+      // 记录当前播放时间
+      videoRef.value.pause()
+      // 阻止用户跳转到新的时间点,将播放时间重置为之前的时间
+      videoRef.value.currentTime = 0;
+    }
+    //提示禁用// 显示消息框
+    Message().notifyWarning('您的账号并未开放此课程!', true);
+    return true;
+  }
+  return false;
+}
+</script>
+
+<style scoped lang="scss">
+@use 'sass:math';
+@use 'sass:color'; // 引入 color 模块
+// 定义rpx转换函数
+@function rpx($px) {
+  @return math.div($px, 750) * 100vw;
+}
+
+// 定义儿童风格的蓝紫色调
+$primary-color: rgba(106, 90, 205, 0.52); // 主色调:蓝紫色
+$secondary-color: rgba(147, 112, 219, 0.66); // 辅助色:亮蓝紫色
+$accent-color: rgb(133, 89, 220); // 强调色:暗蓝紫色
+$light-color: #e6e6fa; // 浅色背景:淡紫色
+$text-color: #483d8b; // 文本颜色:靛蓝色
+
+/* 添加过渡样式 */
+.drawer-slide-enter-active,
+.drawer-slide-leave-active {
+  transition: all 0.3s ease;
+}
+.drawer-slide-enter-from,
+.drawer-slide-leave-to {
+  transform: translateX(-100%);
+  opacity: 0;
+}
+.main-content {
+  width: rpx(135);
+  height: 100%;
+  position: relative;
+  background: linear-gradient(to bottom, #001169, #8a78d0);
+
+}
+.content-box {
+  flex: 1;
+  height: 100%;
+  display: flex;
+  flex-direction: column; /* 子元素上下排列 */
+  background: linear-gradient(
+          to bottom,
+          #e2ddfc,
+          #f1effd
+  ); /* 设置悬停、聚焦、点击状态下的背景色 */
+}
+.icon-expand {
+  width: rpx(30);
+  height: rpx(30);
+  z-index: 9999;
+  position: absolute;
+  cursor: pointer; // 添加鼠标指针样式
+}
+.icon-expand .el-icon {
+  font-size: rpx(15);
+  position: absolute;
+  color: white;
+  left: rpx(9);
+  margin-top: rpx(17);
+}
+.icon-expand .el-icon:active {
+  color: black;
+}
+.home-container {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: linear-gradient(to bottom, #001169, #b4a8e1);
+  display: flex;
+  flex-direction: row;
+  gap: rpx(0);
+}
+.el-row {
+  margin: auto;
+  margin-top: rpx(40);
+}
+.tac ::v-deep(.el-menu) {
+  background-color: transparent;
+  border: none;
+  width: 100%;
+}
+
+/* 取消点击后的蓝色字体 el-sub-menu__title*/
+.tac ::v-deep(.el-menu-item.is-active),
+.tac ::v-deep(.el-sub-menu__title.is-active) {
+  color: white;
+}
+// 取消鼠标悬浮颜色
+.tac ::v-deep(.el-sub-menu__title) {
+  width: rpx(130);
+  height: rpx(20);
+  margin-bottom: rpx(5);
+  border-radius: rpx(6);
+}
+.el-menu ::v-deep(.el-sub-menu__title:hover),
+.el-menu ::v-deep(.el-sub-menu__title:focus),
+.el-menu ::v-deep(.el-sub-menu__title:active) {
+  background: linear-gradient(
+          to bottom,
+          #fee78a,
+          #ffce1b
+  );
+  background-color: transparent;
+}
+.mb-2 {
+  color: white;
+  margin-top: rpx(1);
+}
+.mb-2 .el-icon {
+  font-size: rpx(10);
+  margin-left: rpx(5);
+}
+.el-menu-item {
+  width: rpx(115);
+  height: rpx(20);
+  margin-bottom: rpx(5);
+  border-radius: rpx(6);
+  color: white;
+  font-size: rpx(7);
+
+}
+.el-menu ::v-deep(.el-sub-menu__title){
+  color: white;
+  width: rpx(115);
+  height: rpx(20);
+  margin-bottom: rpx(5);
+  font-size: rpx(7);
+}
+.el-menu ::v-deep(.el-sub-menu__title:hover),
+.el-menu ::v-deep(.el-sub-menu__title:focus),
+.el-menu ::v-deep(.el-sub-menu__title:active) {
+  background: linear-gradient(
+          to bottom,
+          #fee78a,
+          #ffce1b
+  );
+  color: black;
+  font-size: rpx(8);
+}
+.el-menu ::v-deep(.el-menu-item:hover),
+.el-menu ::v-deep(.el-menu-item:focus),
+.el-menu ::v-deep(.el-menu-item:active) {
+  background: linear-gradient(
+          to bottom,
+          #fee78a,
+          #ffce1b
+  );
+  color: black;
+  font-size: rpx(8);
+}
+.drawer-box {
+  position: absolute;
+  display: flex;
+  align-items: center;
+  height: 100%;
+  width: 100%;
+  overflow-y: auto; /* 添加垂直滚动条 */
+  max-height: 100%; /* 设置最大高度 */
+  /* 添加滚动条样式 */
+  &::-webkit-scrollbar {
+    width: rpx(1);
+  }
+  &::-webkit-scrollbar-thumb {
+    background-color:#b4a8e1;
+    border-radius: rpx(5);
+  }
+  &::-webkit-scrollbar-track {
+    background-color: rgba(255, 255, 255, 0.2);
+    border-radius: rpx(5);
+  }
+}
+.drawer-box .toggle-button {
+  width: rpx(10);
+  height: rpx(50);
+  font-size: rpx(7);
+  background-color: rgba(17, 23, 29, 0.2);
+  border: none;
+  position: relative;
+  writing-mode: vertical-lr; // 文字垂直排列,从左到右
+  text-orientation: upright; // 文字保持正立
+}
+.toggle-button:hover {
+  left: 0;
+}
+
+
+.toggle-button.is-active,
+.toggle-button:active,
+.toggle-button:focus {
+  background-color: rgba(165, 209, 247, 0.8);
+  color: white;
+  border: none; // 移除点击时的边框
+  outline: none; // 移除点击时的外边框
+}
+
+.box-1 {
+  width: 100%;
+  height: rpx(50);
+  margin-top: rpx(10);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  box-sizing: border-box;
+  font-size: rpx(15); // 默认字体大小
+}
+
+.inner-box {
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  font-size: rpx(16); // 默认字体大小
+}
+
+.left-box {
+  position: relative;
+  justify-content: flex-start;
+  align-items: flex-start;
+  flex: 1; // 设置左侧盒子占比为 2
+  cursor: pointer; // 添加鼠标指针样式
+}
+.box-icon {
+  width: 100%;
+  height: 100%;
+  flex: 1;
+  display: flex; // 添加 flex 布局
+  align-items: center; // 垂直居中
+  color: black; // 设置图标颜色为白色
+  padding-left: rpx(15);
+  font-size: rpx(10); // 设置图标大小,可按需调整
+}
+.box-icon .left-icon {
+  margin-left: rpx(10);
+  margin-right: rpx(5); // 设置图标和文字之间的间距 ;
+}
+
+.left-box span {
+  position: absolute;
+  font-size: rpx(11); // 默认字体大小
+  color: black;
+}
+
+.right-box {
+  flex: 1;
+  position: relative; // 添加相对定位;
+}
+.top-right-box {
+  position: absolute; // 添加绝对定位
+  margin-top: rpx(20); // 调整上边距离
+  margin-right: rpx(50); // 调整右边距离
+  width: 100%; // 设置盒子宽度,可按需调整
+  height: 60px; // 设置盒子高度,可按需调整
+  display: flex;
+  justify-content: flex-end;
+}
+.top-right-box {
+  ::v-deep(.el-input__wrapper) {
+    background-color: rgb(255, 255, 255, 0.5);
+    border-radius: rpx(12);
+    border: white 1px solid;
+    color: #aaa5c5;
+  }
+  ::v-deep(.el-input__icon) {
+    color: #aaa5c5; // 设置输入框图标颜色为白色
+  }
+  // 添加占位符样式
+  ::v-deep(.el-input__inner::placeholder) {
+    color: #aaa5c5;
+  }
+  // 添加输入框文字颜色样式
+  ::v-deep(.el-input__inner) {
+    color: black;
+  }
+}
+// 搜索框
+.search-input {
+  width: rpx(100);
+  height: rpx(15);
+  font-size: rpx(7);
+}
+.box-2 {
+  width: 100%;
+  flex: 1;
+  box-shadow: 0 4px 8px rgba(202, 52, 52, 0.1);
+  box-sizing: border-box;
+  display: flex; // 确保子元素水平排列
+  flex-wrap: wrap; // 允许子元素换行;
+  align-content: center; // 顶部对齐;
+  cursor: pointer; // 添加鼠标指针样式
+}
+.box-blockly {
+  width: 60%;
+  height: rpx(300);
+  padding: 0px 20px;
+  float: left;
+}
+.box-code{
+  width: 35%;
+  height: rpx(300);
+
+  .box-code-textarea{
+    height: 80%;
+    width: 100%;
+  }
+}
+
+/* 隐藏 Chrome 视频控件的渐变背景等默认样式 */
+video::-webkit-media-controls-panel {
+  background: transparent !important; /* 去掉背景渐变,设为透明 */
+}
+
+.small-title {
+  width: 100%;
+  margin-top: rpx(-20);
+  height: rpx(20);
+  color: black;
+  font-size: rpx(10);
+  justify-content: center; //使子元素水平居中
+}
+.video-switch {
+  width: 100%;
+  // height: rpx(50);
+  display: flex;
+  margin-top: rpx(-20);
+  // background-color: saddlebrown;
+}
+.caret-right,
+.caret-left {
+  width: rpx(50);
+  margin: auto;
+  display: flex;
+  margin-top: rpx(5);
+  margin-bottom: rpx(15);
+}
+.caret-left ::v-deep(.el-button.is-round),
+.caret-right ::v-deep(.el-button.is-round) {
+  width: rpx(50);
+  height: rpx(15);
+  color: #aaa5c5;
+  border-radius: none;
+  border: 1px white solid;
+  background-color: rgb(255, 255, 255, 0.5); // 进度条背景颜色,调浅为半透明白色
+  box-shadow: 0 4px 8px rgba(202, 52, 52, 0.1);
+}
+
+// 儿童风格试题弹框样式
+.child-dialog {
+  .el-dialog__header {
+    display: none; // 隐藏原有的标题栏
+  }
+
+  .el-dialog__body {
+    padding: rpx(20);
+    position: relative;
+  }
+
+  .el-dialog__footer {
+    border-top: none;
+    padding: rpx(10) rpx(20);
+    text-align: center;
+  }
+
+  .el-dialog__wrapper {
+    // 修改半透明背景色
+    background-color: rgba(0, 0, 0, 0.6);
+  }
+
+  .el-dialog {
+    border: none;
+    border-radius: rpx(20);
+    background: linear-gradient(
+            135deg,
+            $light-color,
+            #d8bfd8
+    ); // 柔和的蓝紫色渐变
+    // 添加边框边晕效果
+    box-shadow: 0 0 20px 5px rgba($primary-color, 0.6),
+    0 0 40px 10px rgba($primary-color, 0.4),
+    0 0 60px 15px rgba($primary-color, 0.2);
+    overflow: hidden;
+    // 添加装饰元素
+    &::before {
+      content: '';
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: rpx(10);
+      background: linear-gradient(
+              90deg,
+              $primary-color,
+              $secondary-color,
+              $accent-color
+      );
+    }
+  }
+}
+
+// 问题标题样式
+.question-title {
+  background: linear-gradient(
+          90deg,
+          rgba($primary-color, 0.2),
+          rgba($secondary-color, 0.2)
+  );
+  padding: rpx(15);
+  border-radius: rpx(12);
+  margin-bottom: rpx(20);
+  color: $accent-color;
+  font-weight: bold;
+  font-size: rpx(14);
+  box-shadow: 0 rpx(3) rpx(10) rgba($primary-color, 0.1);
+  position: relative;
+  display: flex;
+  align-items: center;
+
+  .question-icon {
+    background-color: $accent-color;
+    color: white;
+    width: rpx(24);
+    height: rpx(24);
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-right: rpx(10);
+    font-weight: bold;
+    box-shadow: 0 rpx(2) rpx(5) rgba($accent-color, 0.3);
+  }
+}
+
+// 选项容器样式
+.options-container {
+  margin-bottom: rpx(20);
+}
+
+// 问题选项样式
+.question-option {
+  margin: rpx(8) 0;
+  padding: rpx(10) rpx(15);
+  border-radius: rpx(12);
+  border: rpx(1) solid rgba($primary-color, 0.3);
+  transition: all 0.3s ease;
+  display: flex;
+  align-items: center;
+  background-color: white;
+  box-shadow: 0 rpx(2) rpx(5) rgba($primary-color, 0.05);
+
+  ::v-deep(.el-radio__label) {
+    color: $text-color;
+    margin-left: rpx(8);
+    flex: 1;
+    text-align: left;
+    // 增大字体大小
+    font-size: rpx(12);
+  }
+
+  // 选中时的样式变化
+  .el-radio__input.is-checked + .el-radio__label {
+    font-weight: bold;
+    color: $accent-color;
+  }
+
+  &:hover {
+    background-color: rgba($primary-color, 0.05);
+    border-color: rgba($primary-color, 0.5);
+    transform: translateY(-rpx(1));
+  }
+}
+
+// 暂无选项样式
+.no-options {
+  color: rgba($text-color, 0.7);
+  text-align: center;
+  padding: rpx(20);
+  font-size: rpx(12);
+}
+
+// 底部按钮样式
+.child-button {
+  min-width: rpx(80);
+  height: rpx(30);
+  border-radius: rpx(15);
+  font-size: rpx(12);
+  font-weight: 500;
+  transition: all 0.3s ease;
+  box-shadow: 0 rpx(2) rpx(8) rgba(0, 0, 0, 0.1);
+
+  &.confirm {
+    background: linear-gradient(to right, $primary-color, $secondary-color);
+    border: none;
+    border-right: 15px;
+    color: white;
+
+    &:hover {
+      background: linear-gradient(
+              to right,
+              color.adjust($primary-color, $lightness: -5%),
+              color.adjust($secondary-color, $lightness: -5%)
+      );
+      box-shadow: 0 rpx(3) rpx(10) rgba($primary-color, 0.4);
+      transform: translateY(-rpx(1));
+      color: white;
+    }
+  }
+
+  &.cancel {
+    background: white;
+    border: rpx(1) solid rgba($primary-color, 0.3);
+    color: $text-color;
+
+    &:hover {
+      background: rgba($primary-color, 0.05);
+      border-color: rgba($primary-color, 0.5);
+      transform: translateY(-rpx(1));
+    }
+  }
+}
+
+// AI对话图标样式
+.ai-icon-container {
+  position: absolute;
+  bottom: rpx(20);
+  right: rpx(20);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  cursor: pointer;
+  transition: all 0.3s ease;
+
+  &:hover {
+    transform: translateY(-rpx(2));
+  }
+
+  .ai-icon {
+    width: rpx(30);
+    height: rpx(30);
+    margin-bottom: rpx(0);
+    filter: drop-shadow(0 rpx(2) rpx(4) rgba($primary-color, 0.3));
+    // 添加过渡动画
+    transition: transform 0.3s ease;
+  }
+
+  // 悬浮时放大效果
+  .ai-icon:hover {
+    transform: scale(1.5);
+  }
+
+  .ai-text {
+    color: $text-color;
+    font-size: rpx(8);
+    background-color: rgba(255, 255, 255, 0.7);
+    padding: rpx(2) rpx(5);
+    border-radius: rpx(5);
+  }
+}
+
+// AI消息样式
+.ai-message {
+  display: flex;
+  align-items: flex-start;
+  margin-bottom: rpx(15);
+
+  .ai-avatar {
+    width: rpx(30);
+    height: rpx(30);
+    border-radius: 50%;
+    margin-right: rpx(10);
+    background-color: $primary-color;
+    padding: rpx(5);
+    box-shadow: 0 rpx(2) rpx(5) rgba($primary-color, 0.2);
+  }
+
+  .ai-text-content {
+    background-color: $light-color;
+    padding: rpx(8) rpx(12);
+    border-radius: rpx(10);
+    font-size: rpx(10);
+    color: $text-color;
+    max-width: 80%;
+    box-shadow: 0 rpx(1) rpx(3) rgba(0, 0, 0, 0.05);
+  }
+}
+
+// 用户输入框样式
+.user-input {
+  ::v-deep(.el-input__wrapper) {
+    border-radius: rpx(15);
+    border-color: rgba($primary-color, 0.3);
+    margin-right: 10px;
+
+    &:focus-within {
+      box-shadow: 0 0 0 rpx(1) rgba($primary-color, 0.5);
+    }
+  }
+
+  ::v-deep(.el-input__inner) {
+    font-size: rpx(10);
+    color: $text-color;
+    text-indent: 1em;
+  }
+}
+/* 定义淡入和缩放动画 */
+.fade-scale-enter-active,
+.fade-scale-leave-active {
+  transition: all 0.5s ease;
+}
+
+.fade-scale-enter-from,
+.fade-scale-leave-to {
+  opacity: 0.1;
+  transform: scale(0.9);
+}
+
+// 自定义试题弹框背景
+.child-dialog-wrapper {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-color: rgba(0, 0, 0, 0.6); // 半透明背景
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  z-index: 1000;
+}
+
+.child-dialog {
+  border: none;
+  border-radius: rpx(20);
+  background: linear-gradient(
+          135deg,
+          $light-color,
+          #d8bfd8
+  ); // 柔和的蓝紫色渐变
+  box-shadow: 0 0 20px 5px rgba($primary-color, 0.6),
+  0 0 40px 10px rgba($primary-color, 0.4),
+  0 0 60px 15px rgba($primary-color, 0.2);
+  overflow: hidden;
+  padding: rpx(20);
+  width: 60%;
+  position: relative;
+}
+
+// AI对话弹框样式
+.ai-dialog-wrapper {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+  z-index: 1001;
+  pointer-events: none;
+}
+
+.ai-dialog {
+  border: none;
+  border-radius: rpx(20);
+  background: linear-gradient(135deg, $light-color, #d8bfd8);
+  box-shadow: 0 0 20px 5px rgba($primary-color, 0.6),
+  0 0 40px 10px rgba($primary-color, 0.4),
+  0 0 60px 15px rgba($primary-color, 0.2);
+  overflow: hidden;
+  padding: rpx(20);
+  width: 30%;
+  // 增加高度
+  height: 80%;
+  margin-right: rpx(50);
+  pointer-events: auto;
+  position: relative;
+  display: flex;
+  flex-direction: column;
+
+  &::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: rpx(10);
+    background: linear-gradient(
+            90deg,
+            $primary-color,
+            $secondary-color,
+            $accent-color
+    );
+  }
+}
+
+.ai-dialog-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: rpx(15);
+
+  h3 {
+    color: $text-color;
+    font-size: rpx(12);
+  }
+
+  .close-btn {
+    padding: 0;
+    width: rpx(20);
+    height: rpx(20);
+    font-size: rpx(16);
+    line-height: 1;
+  }
+}
+
+.ai-dialog-content {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+}
+
+.ai-message-history {
+  flex: 1;
+  // 当内容超出容器高度时,显示垂直滚动条
+  overflow-y: auto;
+  margin-bottom: rpx(15);
+  // 可以根据实际情况调整最大高度
+  max-height: 50vh;
+  padding: 5px 10px;
+
+  .message {
+    display: flex;
+    align-items: flex-start;
+    margin-bottom: rpx(10);
+
+    &.user {
+      flex-direction: row-reverse;
+    }
+
+    .avatar {
+      width: rpx(30);
+      height: rpx(30);
+      border-radius: 50%;
+      margin: 0 rpx(10);
+    }
+
+    .user {
+      width: 15px;
+      height: 15px;
+    }
+
+    .message-content {
+      background-color: $light-color;
+      padding: rpx(8) rpx(12);
+      border-radius: rpx(10);
+      font-size: rpx(10);
+      color: $text-color;
+      max-width: 80%;
+      box-shadow: 0 rpx(1) rpx(3) rgba(0, 0, 0, 0.05);
+    }
+  }
+  // 滚动条整体样式
+  &::-webkit-scrollbar {
+    width: rpx(4); // 滚动条宽度
+  }
+
+  // 滚动条滑块样式
+  &::-webkit-scrollbar-thumb {
+    background-color: $primary-color; // 滑块颜色
+    border-radius: rpx(4); // 滑块圆角
+    transition: background-color 0.3s ease; // 颜色过渡效果
+
+    &:hover {
+      //background-color: darken($primary-color, 10%); // 悬停时滑块颜色加深
+    }
+  }
+
+  // 滚动条轨道样式
+  &::-webkit-scrollbar-track {
+    background-color: rgba($primary-color, 0.2); // 轨道颜色
+    border-radius: rpx(4); // 轨道圆角
+  }
+
+  .message {
+    display: flex;
+    align-items: flex-start;
+    margin-bottom: rpx(10);
+
+    &.user {
+      flex-direction: row-reverse;
+    }
+
+    .avatar {
+      width: rpx(30);
+      height: rpx(30);
+      border-radius: 50%;
+      margin: 0 rpx(10);
+    }
+
+    .message-content {
+      background-color: $light-color;
+      padding: rpx(8) rpx(12);
+      border-radius: rpx(10);
+      font-size: rpx(10);
+      color: $text-color;
+      max-width: 80%;
+      box-shadow: 0 rpx(1) rpx(3) rgba(0, 0, 0, 0.05);
+    }
+  }
+}
+
+// 优化发送按钮样式
+.send-button {
+  //background: linear-gradient(90deg, $primary-color, $secondary-color);
+  border: none;
+  color: white;
+
+  &:hover {
+    //background: linear-gradient(90deg, darken($primary-color, 5%), darken($secondary-color, 5%));
+  }
+}
+.el-menu .el-menu-item.is-active {
+  background: linear-gradient(to bottom, #fee78a, #ffce1b);
+  color: black;
+  font-size: rpx(8);
+  box-shadow: 0 4px 8px rgba(3, 3, 3, 0.3);
+}
+</style>

+ 301 - 0
src/views/Blockly_bak.vue

@@ -0,0 +1,301 @@
+<template>
+  <div class="min-h-screen p-6 bg-gray-50">
+    <!-- 标题与操作 -->
+    <div class="flex items-center justify-between mb-6">
+      <h1 class="text-2xl font-bold text-gray-800">迷宫题目管理</h1>
+      <button
+          class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
+          @click="openCreateModal"
+      >
+        新建题目
+      </button>
+    </div>
+
+    <!-- 题目列表 -->
+    <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
+      <div
+          v-for="(problem, index) in problems"
+          :key="problem.id"
+          class="bg-white rounded-lg shadow p-4 transition hover:shadow-lg"
+      >
+        <h2 class="text-xl font-semibold text-gray-800 mb-2">{{ problem.title }}</h2>
+        <p class="text-gray-600 mb-4">{{ problem.description }}</p>
+        <div class="flex items-center justify-between">
+          <button
+              class="px-3 py-2 bg-green-500 text-white rounded hover:bg-green-600"
+              @click="openEditModal(problem, index)"
+          >
+            编辑
+          </button>
+          <button
+              class="px-3 py-2 bg-red-500 text-white rounded hover:bg-red-600"
+              @click="deleteProblem(index)"
+          >
+            删除
+          </button>
+        </div>
+      </div>
+    </div>
+
+    <!-- 新建/编辑弹窗 -->
+    <Teleport to="body">
+      <div
+          v-if="showModal"
+          class="fixed inset-0 flex items-center justify-center bg-black/50"
+      >
+        <div class="w-full max-w-xl bg-white rounded-lg shadow p-6">
+          <h2 class="text-xl font-bold text-gray-800 mb-4">
+            {{ isEdit ? "编辑题目" : "新建题目" }}
+          </h2>
+          <!-- 基础信息 -->
+          <div class="mb-4">
+            <label class="block text-gray-700 font-medium mb-1">题目标题</label>
+            <input
+                v-model="form.title"
+                class="w-full border border-gray-300 rounded p-2"
+                placeholder="输入题目标题"
+            />
+          </div>
+          <div class="mb-4">
+            <label class="block text-gray-700 font-medium mb-1">题目描述</label>
+            <textarea
+                v-model="form.description"
+                class="w-full border border-gray-300 rounded p-2"
+                rows="3"
+                placeholder="输入题目描述"
+            />
+          </div>
+
+          <!-- 迷宫地图配置 -->
+          <div class="mb-4">
+            <label class="block text-gray-700 font-medium mb-1">迷宫尺寸</label>
+            <div class="flex gap-2">
+              <input
+                  v-model.number="form.mazeConfig.width"
+                  class="w-1/2 border border-gray-300 rounded p-2"
+                  type="number"
+                  min="5"
+                  max="20"
+                  placeholder="宽度"
+              />
+              <input
+                  v-model.number="form.mazeConfig.height"
+                  class="w-1/2 border border-gray-300 rounded p-2"
+                  type="number"
+                  min="5"
+                  max="20"
+                  placeholder="高度"
+              />
+            </div>
+          </div>
+          <div class="mb-4">
+            <label class="block text-gray-700 font-medium mb-1">墙坐标(JSON 数组,如 [[0,1]])</label>
+            <textarea
+                v-model="form.mazeConfig.walls"
+                class="w-full border border-gray-300 rounded p-2"
+                rows="3"
+                placeholder="输入墙坐标"
+            />
+          </div>
+          <div class="mb-4">
+            <label class="block text-gray-700 font-medium mb-1">起点坐标</label>
+            <div class="flex gap-2">
+              <input
+                  v-model.number="form.mazeConfig.start[0]"
+                  class="w-1/2 border border-gray-300 rounded p-2"
+                  type="number"
+                  placeholder="X"
+              />
+              <input
+                  v-model.number="form.mazeConfig.start[1]"
+                  class="w-1/2 border border-gray-300 rounded p-2"
+                  type="number"
+                  placeholder="Y"
+              />
+            </div>
+          </div>
+          <div class="mb-4">
+            <label class="block text-gray-700 font-medium mb-1">终点坐标</label>
+            <div class="flex gap-2">
+              <input
+                  v-model.number="form.mazeConfig.end[0]"
+                  class="w-1/2 border border-gray-300 rounded p-2"
+                  type="number"
+                  placeholder="X"
+              />
+              <input
+                  v-model.number="form.mazeConfig.end[1]"
+                  class="w-1/2 border border-gray-300 rounded p-2"
+                  type="number"
+                  placeholder="Y"
+              />
+            </div>
+          </div>
+
+          <!-- 初始积木配置 -->
+          <div class="mb-4">
+            <label class="block text-gray-700 font-medium mb-1">初始积木(JSON 数组,如 {"type":"move_forward","count":3} )</label>
+            <textarea
+                v-model="form.initialBlocks"
+                class="w-full border border-gray-300 rounded p-2"
+                rows="3"
+                placeholder="输入初始积木配置"
+            />
+          </div>
+
+          <!-- 答案配置 -->
+          <div class="mb-4">
+            <label class="block text-gray-700 font-medium mb-1">参考解法代码</label>
+            <textarea
+                v-model="form.solutionCode"
+                class="w-full border border-gray-300 rounded p-2"
+                rows="3"
+                placeholder="输入参考代码(如 moveForward(3); turnRight(); )"
+            />
+          </div>
+
+          <!-- 操作按钮 -->
+          <div class="flex justify-end gap-2">
+            <button
+                class="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600"
+                @click="closeModal"
+            >
+              取消
+            </button>
+            <button
+                class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
+                @click="saveProblem"
+            >
+              保存
+            </button>
+          </div>
+        </div>
+      </div>
+    </Teleport>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive } from 'vue';
+
+// 模拟已有题目列表(实际可从接口获取)
+const problems = ref([
+  {
+    id: 1,
+    title: "入门迷宫",
+    description: "帮助角色找到出口",
+    mazeConfig: {
+      width: 8,
+      height: 8,
+      walls: [[1,0], [1,1], [1,2], [1,3], [2,3], [3,3], [4,3], [5,3], [5,4], [5,5], [5,6]],
+      start: [0, 0],
+      end: [7, 7]
+    },
+    initialBlocks: '[{"type":"move_forward","count":5},{"type":"turn_right","count":3},{"type":"turn_left","count":2}]',
+    solutionCode: "moveForward(3); turnRight(); moveForward(5); turnRight(); moveForward(3);"
+  }
+]);
+
+const showModal = ref(false);
+const isEdit = ref(false);
+const form = reactive({
+  title: "",
+  description: "",
+  mazeConfig: {
+    width: 8,
+    height: 8,
+    walls: '[]',
+    start: [0, 0],
+    end: [7, 7]
+  },
+  initialBlocks: '[]',
+  solutionCode: ""
+});
+
+// 打开新建弹窗
+function openCreateModal() {
+  showModal.value = true;
+  isEdit.value = false;
+  resetForm();
+}
+
+// 打开编辑弹窗
+function openEditModal(problem, index) {
+  showModal.value = true;
+  isEdit.value = true;
+  form.title = problem.title;
+  form.description = problem.description;
+  form.mazeConfig = { ...problem.mazeConfig };
+  // 转成字符串方便展示编辑
+  form.mazeConfig.walls = JSON.stringify(problem.mazeConfig.walls);
+  form.initialBlocks = JSON.stringify(problem.initialBlocks);
+  form.solutionCode = problem.solutionCode;
+}
+
+// 重置表单
+function resetForm() {
+  form.title = "";
+  form.description = "";
+  form.mazeConfig = {
+    width: 8,
+    height: 8,
+    walls: '[]',
+    start: [0, 0],
+    end: [7, 7]
+  };
+  form.initialBlocks = '[]';
+  form.solutionCode = "";
+}
+
+// 关闭弹窗
+function closeModal() {
+  showModal.value = false;
+  resetForm();
+}
+
+// 保存题目
+function saveProblem() {
+  // 处理配置格式(转成 JSON )
+  try {
+    form.mazeConfig.walls = JSON.parse(form.mazeConfig.walls);
+    form.initialBlocks = JSON.parse(form.initialBlocks);
+  } catch (err) {
+    alert("配置格式错误,请检查 JSON 语法!");
+    return;
+  }
+
+  if (isEdit.value) {
+    // 模拟更新,实际调接口
+    const index = problems.value.findIndex(item => item.id === form.id);
+    if (index !== -1) {
+      problems.value[index] = { ...form };
+    }
+  } else {
+    // 模拟新增,实际调接口,这里简单赋 ID
+    form.id = Date.now();
+    problems.value.push({ ...form });
+  }
+  closeModal();
+  alert("保存成功!");
+}
+
+// 删除题目
+function deleteProblem(index) {
+  if (confirm("确定删除该题目吗?")) {
+    problems.value.splice(index, 1);
+    alert("删除成功!");
+  }
+}
+</script>
+
+<style scoped>
+/* 可自定义额外样式覆盖,Tailwind 已能满足基础布局,这里简单加个过渡 */
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity 0.3s ease;
+}
+.fade-enter-from,
+.fade-leave-to {
+  opacity: 0;
+}
+</style>

+ 4 - 4
src/views/Login.vue

@@ -17,13 +17,13 @@
           label-width="0px"
           class="input-item"
         >
-          <!-- <el-form-item prop="tenantName">
+          <el-form-item prop="tenantName">
             <el-input
               v-model="loginData.loginForm.tenantName"
               :prefix-icon="HomeFilled"
               placeholder="租户"
             />
-          </el-form-item> -->
+          </el-form-item>
           <el-form-item prop="username">
             <el-input
               v-model="loginData.loginForm.username"
@@ -85,7 +85,7 @@ const loginData = ref({
 
 const loginLoading = ref(false)
 const loading = ref() // ElLoading.service 返回的实例
-const tenantId = ref('1')
+const tenantId = ref('')
 // 登录状态标识
 const isLoggedIn = ref(false)
 
@@ -120,7 +120,7 @@ const handleLogin = async params => {
     if (valid) {
       loginLoading.value = true
       try {
-        // await getTenantId()
+        await getTenantId()
         const loginDataLoginForm = { ...loginData.value.loginForm }
         // 构建包含 headers 的请求参数
         const res = await login(