Przeglądaj źródła

Merge branch 'beta'

lsc 23 godzin temu
rodzic
commit
53c3cbe4bb

+ 0 - 1
package-lock.json

@@ -6656,7 +6656,6 @@
       "resolved": "https://registry.npmjs.org/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-5.4.0.tgz",
       "integrity": "sha512-Iq/024CydcE46FZqWPU4t4lw4uYOdLnFSO1RNxJVt2qY9zxIjmnkBqhHnYaReWM82kmNnaXs7OkfgRrV2GEjyw==",
       "dev": true,
-      "license": "MIT",
       "dependencies": {
         "@babel/core": "^7.23.0",
         "@babel/plugin-proposal-decorators": "^7.23.0",

BIN
src/assets/img/tool_photo.png


BIN
src/assets/img/tool_vote.png


+ 3 - 1
src/components/CollapsibleToolbar/index.vue

@@ -284,8 +284,10 @@ const getTypeLabel = (type?: number) => {
     72: lang.ssAiApp,
     73: lang.ssHPage,
     74: lang.ssVideo,
-    75: lang.ssBiliVideo,
+    75: lang.lang == 'cn' ? lang.ssBiliVideo : lang.ssYouTube,
     76: lang.ssCreative,
+    77: lang.ssEnglishSpeakingTool,
+    78: lang.ssVote,
   }
   return typeMap[type || 0] || lang.ssUnknown
 }

+ 19 - 4
src/components/CollapsibleToolbar/index2.vue

@@ -231,6 +231,7 @@
         </div>
         <img class="submenu-img" v-else-if="hoveredTool === 'qa'" key="qa" :src="toolAnswer" alt="">
         <img class="submenu-img" v-else-if="hoveredTool === 'choice'" key="choice" :src="toolChoice" alt="">
+        <img class="submenu-img" v-else-if="hoveredTool === 'vote'" key="vote" :src="toolVote" alt="">
       </transition>
       <div class="submenu-item-box">
         <div class="submenu-item" @click="handleToolClick('choice')" @mouseenter="hoveredTool = 'choice'"
@@ -248,6 +249,14 @@
           </svg>
           <span class="submenu-label">{{ lang.ssQandA }}</span>
         </div>
+        <div class="submenu-item" @click="handleToolClick('vote')" @mouseenter="hoveredTool = 'vote'"
+          @mouseleave="hoveredTool = null" v-show="false">
+          <svg class="submenu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+              <polyline points="9 11 12 14 22 4"></polyline>
+              <path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"></path>
+          </svg>
+          <span class="submenu-label">{{ lang.ssVote }}</span>
+        </div>
         <div class="submenu-item" @click="handleToolClick('creative')" @mouseenter="hoveredTool = null"
           @mouseleave="hoveredTool = null">
           <svg class="submenu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@@ -545,6 +554,7 @@ import SpeakingPanel from '@/views/Editor/EnglishSpeaking/SpeakingPanel.vue'
 import { lang } from '@/main'
 import toolChoice from '@/assets/img/tool_choice.jpeg'
 import toolAnswer from '@/assets/img/tool_answer.png'
+import toolVote from '@/assets/img/tool_vote.png'
 
 interface ContentItem {
   tool?: number
@@ -801,7 +811,7 @@ import ImagePage from './page/ImagePage.json'
 import ContentPage from './page/ContentPage.json'
 import ImageTextPage from './page/ImageTextPage.json'
 
-const handleToolClick = (tool: string) => {
+const handleToolClick = _.debounce((tool: string) => {
   interface ParentWindowWithToolList extends Window {
     addTool?: (id: number) => void;
     openVideoUploadDialog?: () => void;
@@ -835,6 +845,9 @@ const handleToolClick = (tool: string) => {
   else if (tool === 'choice') {
     parentWindow?.addTool?.(45)
   }
+  else if (tool === 'vote') {
+    parentWindow?.addTool?.(78)
+  }
   else if (tool === 'qa') {
     parentWindow?.addTool?.(15)
   }
@@ -864,7 +877,7 @@ const handleToolClick = (tool: string) => {
   else if (tool === 'uploadWebpage') {
     activeSubmenu.value = 'uploadWebpage'
   }
-}
+}, 300)
 
 const loadContentList = () => {
   try {
@@ -889,7 +902,7 @@ const addContent = (data: ContentItem, type: number) => {
   // contentList.value.push(data)
   if (type === 2) {
     const elements = currentSlide.value?.elements || []
-    const frameElement = elements.find((el: any) => el.type === 'frame' && (el.toolType === 45 || el.toolType === 15))
+    const frameElement = elements.find((el: any) => el.type === 'frame' && (el.toolType === 45 || el.toolType === 15 || el.toolType === 78))
     if (frameElement) {
       slidesStore.updateElement({
         id: frameElement.id,
@@ -946,8 +959,10 @@ const getTypeLabel = (type?: number) => {
     72: lang.ssAiApp,
     73: lang.ssHPage,
     74: lang.ssVideo,
-    75: lang.ssBiliVideo,
+    75: lang.lang == 'cn' ? lang.ssBiliVideo : lang.ssYouTube,
     76: lang.ssCreative,
+    77: lang.ssEnglishSpeakingTool,
+    78: lang.ssVote,
   }
   return typeMap[type || 0] || lang.ssUnknown
 }

+ 4 - 2
src/views/Editor/CanvasTool/WebpageInput.vue

@@ -104,8 +104,10 @@ const getTypeLabel = (type: number) => {
     72: lang.ssAiApp,
     73: lang.ssHPage,
     74: lang.ssVideo,
-    75: lang.ssBiliVideo,
-    76: lang.ssCreative
+    75: lang.lang == 'cn' ? lang.ssBiliVideo : lang.ssYouTube,
+    76: lang.ssCreative,
+    77: lang.ssEnglishSpeakingTool,
+    78: lang.ssVote,
   }
   return typeMap[type] || lang.ssUnknown
 }

+ 17 - 4
src/views/Editor/CanvasTool/index2.vue

@@ -17,6 +17,13 @@
             </svg>
             <span>{{ lang.ssQandA }}</span>
           </div>
+          <!-- <div class="popover-item" @click="editContent(78)" v-if="frametype != 78">
+            <svg width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <polyline points="9 11 12 14 22 4"></polyline>
+                <path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"></path>
+            </svg>
+            <span>{{ lang.ssVote }}</span>
+          </div> -->
         </template>
         <div class="handler-item" :class="{ active: toolVisible }">
           <span class="svg-icon">
@@ -29,6 +36,10 @@
               stroke-width="2">
               <path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z" />
             </svg>
+            <svg v-if="frametype == 78" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <polyline points="9 11 12 14 22 4"></polyline>
+                <path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"></path>
+            </svg>
           </span>
           <span>{{ iframeLabel }}</span>
           <svg t="1776672009773" class="xia-icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
@@ -211,7 +222,7 @@ const viewMode = computed(() => getInitialViewMode())
 
 const hasInteractiveTool = computed(() => {
   const elements = currentSlide.value?.elements || []
-  return elements.some((el: any) => el.type === 'frame' && (el.toolType === 45 || el.toolType === 15))
+  return elements.some((el: any) => el.type === 'frame' && (el.toolType === 45 || el.toolType === 15 || el.toolType === 78))
 })
 
 const iframeLabel = computed(() => {
@@ -231,8 +242,10 @@ const getTypeLabel = (type: number) => {
     72: lang.ssAiApp,
     73: lang.ssHPage,
     74: lang.ssVideo,
-    75: lang.ssBiliVideo,
-    76: lang.ssCreative
+    75: lang.lang == 'cn' ? lang.ssBiliVideo : lang.ssYouTube,
+    76: lang.ssCreative,
+    77: lang.ssEnglishSpeakingTool,
+    78: lang.ssVote,
   }
   return typeMap[type] || lang.ssUnknown
 }
@@ -428,7 +441,7 @@ const editContent = (toolType: number) => {
     }
     const parentWindow = window.parent as ParentWindowWithToolList
     const elements = currentSlide.value?.elements || []
-    const frameElement = elements.find((el: any) => el.type === 'frame' && (el.toolType === 45 || el.toolType === 15))
+    const frameElement = elements.find((el: any) => el.type === 'frame' && (el.toolType === 45 || el.toolType === 15 || el.toolType === 78))
     parentWindow?.toolBtn2?.(0, frameElement?.url || '', toolType)
     toolVisible.value = false
   })

+ 8 - 3
src/views/Student/components/answerTheResult.vue

@@ -16,6 +16,11 @@
 				<span v-if="choiceQuestionListData[workIndex]">{{choiceQuestionListData[workIndex].accuracyRate}}%({{choiceQuestionListData[workIndex].yes}}/{{choiceQuestionListData[workIndex].all}})</span>
 			</div>
 
+			<div class="atr_d_msg" v-if="workDetail && workDetail.type === '78'">
+				<div>{{ lang.ssAccuracy }}</div>
+				<span v-if="choiceQuestionListData[workIndex]">{{choiceQuestionListData[workIndex].accuracyRate}}%({{choiceQuestionListData[workIndex].yes}}/{{choiceQuestionListData[workIndex].all}})</span>
+			</div>
+
 			<div class="atr_d_msg" v-if="choiceQuestionAnswer">
 				<div>{{ lang.ssCorrectAns }}</div>
 				<span style="color: #03ae2b">{{ choiceQuestionAnswer }}</span>
@@ -74,7 +79,7 @@
 			class="atr_type45Area"
 			v-if="
 				workDetail &&
-				workDetail.type === '45' &&
+				(workDetail.type === '45' || workDetail.type === '78') &&
 				choiceQuestionListData[workIndex] &&
 				choiceQuestionListData[workIndex].choiceUser
 			"
@@ -244,7 +249,7 @@ const getWorkDetail = async () => {
 // 选择题题目数组
 const choiceQuestionList = () => {
   let _result: any[] = []
-  if (workDetail.value && workDetail.value.type === '45') {
+  if (workDetail.value && (workDetail.value.type === '45' || workDetail.value.type === '78')) {
     const _workData = workDetail.value.json.testJson
     _workData.forEach((item: any, index: number) => {
       // 修复 props.workArray 可能为 null 的问题
@@ -400,7 +405,7 @@ const changeShow = (op: any, idx: number) => {
 
 const lookDetail = () => {
   console.log(props.toolType)
-  if ([45, 15, 72, 73].includes(props.toolType)) {
+  if ([45, 15, 72, 73, 78].includes(props.toolType)) {
     emit('openChoiceQuestionDetail', props.slideIndex)
   }
 }

+ 181 - 7
src/views/Student/components/choiceQuestionDetailDialog.vue

@@ -101,6 +101,99 @@
         </div>
       </div>
 
+      <!-- 投票 -->
+      <div class="c_t45" v-if="workDetail && workDetail.type === '78' && props.showData">
+        <div class="c_t45_title">
+          <div v-if="props.showData.choiceQuestionListData[props.showData.workIndex]">{{
+            props.showData.choiceQuestionListData[props.showData.workIndex]
+              .teststitle
+          }}</div>
+          <div class="c_t45_msg">
+            <div>{{ lang.ssAnswerCount }} {{ props.showData.workArray.length }}<span
+                v-if="props.showData.unsubmittedStudents.length > 0">/{{ props.showData.workArray.length +
+                  props.showData.unsubmittedStudents.length
+                }}</span></div>
+            <span v-if="props.showData.unsubmittedStudents.length > 0" @click="viewUnsubmittedStudents()">{{
+              lang.ssViewSubmitStatus2 }}</span>
+          </div>
+          <!--<span class="c_t45_t_btn" :class="{'c_t45_t_btn_noActive': props.showData.workIndex <= 0}" @click="changeWorkIndex(0)">{{ lang.ssPrevQ }}</span>-->
+          <!--<span class="c_t45_t_btn" :class="{'c_t45_t_btn_noActive': props.showData.workIndex >= props.showData.choiceQuestionListData.length - 1}" @click="changeWorkIndex(1)">{{ lang.ssNextQ }}</span>-->
+        </div>
+        <img class="c_t45_img" :src="props.showData.choiceQuestionListData[props.showData.workIndex]
+          .timuList[0].src
+          " v-if="props.showData.choiceQuestionListData[props.showData.workIndex] &&
+            props.showData.choiceQuestionListData[props.showData.workIndex]
+              .timuList.length > 0
+          " @click="previewImageToolRef.previewImage(props.showData.choiceQuestionListData[props.showData.workIndex]
+            .timuList[0].src)" />
+        <!-- <span class="c_t45_type" v-if="
+          props.showData.choiceQuestionListData[props.showData.workIndex]
+            .type === '1'
+        ">{{ lang.ssSingleSel }}</span>
+        <span class="c_t45_type" v-if="
+          props.showData.choiceQuestionListData[props.showData.workIndex]
+            .type === '2'
+        ">{{ lang.ssMultiOpt }}</span> -->
+        <!-- <span class="c_t45_type">{{ lang.ssChoiceQuestion }}</span> -->
+        <div class="c_t45_echarts" :style="{
+          width: slideWidth - 40 + 'px',
+        }">
+          <div id="echartsArea1" ref="echartsArea1"></div>
+        </div>
+        <div class="aiAnalysis" v-if="props.workArray.length > 0">
+          <div class="ai_header">
+            <div class="ai_title">
+              <svg viewBox="0 0 1024 1024" width="200" height="200">
+                <path
+                  d="M512 170.666667C323.477333 170.666667 170.666667 323.477333 170.666667 512s152.810667 341.333333 341.333333 341.333333 341.333333-152.810667 341.333333-341.333333S700.522667 170.666667 512 170.666667zM85.333333 512C85.333333 276.352 276.352 85.333333 512 85.333333s426.666667 191.018667 426.666667 426.666667-191.018667 426.666667-426.666667 426.666667S85.333333 747.648 85.333333 512z">
+                </path>
+                <path
+                  d="M693.013333 330.986667a42.666667 42.666667 0 0 1 10.304 43.648l-75.413333 226.282666a42.666667 42.666667 0 0 1-26.986667 26.986667l-226.282666 75.413333a42.666667 42.666667 0 0 1-53.973334-53.973333l75.434667-226.261333a42.666667 42.666667 0 0 1 26.986667-26.986667l226.282666-75.413333a42.666667 42.666667 0 0 1 43.648 10.304z m-222.72 139.306666l-41.685333 125.098667 125.077333-41.706667 41.706667-125.077333-125.077333 41.706667z">
+                </path>
+              </svg>{{ lang.ssAnalysis }}
+            </div>
+            <div class="ai_refresh" :class="{ 'disabled': currentAnalysis && currentAnalysis.loading }"
+              @click="aiAnalysisRefresh78()">
+              {{ lang.ssAIGenerate }}
+              <svg viewBox="0 0 1024 1024" width="200" height="200">
+                <path
+                  d="M875 483c-33.4 0-60.5 27.1-60.5 60.5v0.1C814.4 710.3 678.8 846 512 846S209.5 710.3 209.5 543.5 345.2 241 512 241c36.8 0 71.7 7.6 104.4 19.7-32 3-57.4 29.1-57.4 61.9 0 34.8 28.2 63 63 63h201.9c34.8 0 63-28.2 63-63V120c0-34.8-28.2-63-63-63s-63 28.2-63 63v81.4C691 150.5 605.2 120 512 120 278.1 120 88.5 309.6 88.5 543.5S278.1 967 512 967s423.5-189.6 423.5-423.5c0-33.4-27.1-60.5-60.5-60.5z">
+                </path>
+              </svg>
+
+            </div>
+          </div>
+          <div class="ai_content" v-if="currentAnalysis && currentAnalysis.json">
+            {{ currentAnalysis.json.text }}
+          </div>
+          <div class="ai_echartsData" v-if="currentAnalysis && currentAnalysis.json.keyword">
+            {{ currentAnalysis.json.keyword }}
+          </div>
+          <div class="generatingContent" v-if="currentAnalysis && currentAnalysis.loading">
+            {{ lang.ssGeneratingContent }}...</div>
+          <div class="ai_updateTime" v-if="currentAnalysis">{{ lang.ssUpdateTime }}:{{ currentAnalysis.update_at }}
+          </div>
+        </div>
+        <div class="cq_changeBtn" v-if="props.showData.choiceQuestionListData.length > 1">
+          <div :class="{ cq_cb_disabled: props.showData.workIndex <= 0 }" @click="changeWorkIndex(0)">
+            <svg style="transform: rotate(-90deg);" viewBox="0 0 1024 1024" version="1.1" width="200" height="200">
+              <path
+                d="M512 330.666667c14.933333 0 29.866667 4.266667 40.533333 14.933333l277.33333399 234.666667c27.733333 23.466667 29.866667 64 8.53333301 89.6-23.466667 27.733333-64 29.866667-89.6 8.53333299L512 477.866667l-236.8 200.53333299c-27.733333 23.466667-68.266667 19.19999999-89.6-8.53333299-23.466667-27.733333-19.19999999-68.266667 8.53333301-89.6l277.33333399-234.666667c10.666667-10.666667 25.6-14.933333 40.533333-14.933333z"
+                fill=""></path>
+            </svg>
+          </div>
+          <span>{{ props.showData.workIndex + 1 }}/{{ props.showData.choiceQuestionListData.length }}</span>
+          <div :class="{ cq_cb_disabled: props.showData.workIndex >= props.showData.choiceQuestionListData.length - 1 }"
+            @click="changeWorkIndex(1)">
+            <svg style="transform: rotate(90deg);" viewBox="0 0 1024 1024" version="1.1" width="200" height="200">
+              <path
+                d="M512 330.666667c14.933333 0 29.866667 4.266667 40.533333 14.933333l277.33333399 234.666667c27.733333 23.466667 29.866667 64 8.53333301 89.6-23.466667 27.733333-64 29.866667-89.6 8.53333299L512 477.866667l-236.8 200.53333299c-27.733333 23.466667-68.266667 19.19999999-89.6-8.53333299-23.466667-27.733333-19.19999999-68.266667 8.53333301-89.6l277.33333399-234.666667c10.666667-10.666667 25.6-14.933333 40.533333-14.933333z"
+                fill=""></path>
+            </svg>
+          </div>
+        </div>
+      </div>
+
       <!-- 问答题 -->
       <div class="c_t15" v-if="workDetail && workDetail.type === '15' && props.showData">
         <div class="c_t15_title">{{ workDetail.json.answerQ }}</div>
@@ -395,7 +488,7 @@ const processWorkContent = async (content: string, toolType: number): Promise<an
   }
 
   try {
-    if ([45, 15].includes(toolType)) {
+    if ([45, 15, 78].includes(toolType)) {
       return JSON.parse(decodeURIComponent(contentToParse))
     }
     else if (toolType === 72) {
@@ -537,7 +630,7 @@ const handleChartResize = () => {
           if (
             props.showData &&
             props.showData.workDetail &&
-            props.showData.workDetail.type === '45'
+            (props.showData.workDetail.type === '45' || props.showData.workDetail.type === '78')
           ) {
             setEchartsArea1()
           }
@@ -766,7 +859,7 @@ watch(
     if (
       newVal &&
       newVal.choiceQuestionListData[newVal.workIndex] &&
-      props.showData.workDetail.type === '45'
+      (props.showData.workDetail.type === '45' || props.showData.workDetail.type === '78')
     ) {
       if (
         oldVal &&
@@ -804,7 +897,7 @@ watch(
     if (
       (newVal || newVal === 0) &&
       props.showData.workDetail &&
-      props.showData.workDetail.type === '45'
+      (props.showData.workDetail.type === '45' || props.showData.workDetail.type === '78')
     ) {
       if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) {
         setEchartsArea1()
@@ -827,7 +920,7 @@ watch(
     if (
       (newVal || newVal === 0) &&
       props.showData.workDetail &&
-      props.showData.workDetail.type === '45'
+      (props.showData.workDetail.type === '45' || props.showData.workDetail.type === '78')
     ) {
       setEchartsArea1()
     }
@@ -845,7 +938,7 @@ watch(
       newVal &&
       props.showData &&
       props.showData.workDetail &&
-      props.showData.workDetail.type === '45'
+      (props.showData.workDetail.type === '45' || props.showData.workDetail.type === '78')
     ) {
       setEchartsArea1()
     }
@@ -863,7 +956,7 @@ watch(
       newVal &&
       props.showData &&
       props.showData.workDetail &&
-      props.showData.workDetail.type === '45'
+      (props.showData.workDetail.type === '45' || props.showData.workDetail.type === '78')
     ) {
       handleChartResize()
     }
@@ -959,6 +1052,87 @@ const aiAnalysisRefresh45 = () => {
   })
 }
 
+// ai生成投票分析 投票
+const aiAnalysisRefresh78 = () => {
+  const _work = props.showData.choiceQuestionListData[props.showData.workIndex]
+  const msg = `# CONTEXT #
+你是K-12阶段的AI教育课堂分析助手,基于上传的课件、逐字稿,以及当页的学生答题数据(选择题/问答题/智能体对话/投票)进行智能分析。
+
+# OBJECTIVE #
+输出当前学生答题的分析报告:整体表现、核心发现(共性问题/误区)、教学优化(改进方向),为教师提供教学策略的建议和支持。
+
+#INPUT#
+课程数据:
+- 课程名称:${props.courseDetail.title}
+- 课程学科:${props.courseDetail.name}
+当前页面答题数据(投票):【分析重点】
+- 投票题目:${_work.teststitle}
+- 选项数据:${JSON.stringify(_work.choiceUser)}
+- 题目图片:${_work.timuList.lenght ? _work.timuList[0].src : ''}
+- 未提交学生:${JSON.stringify(props.showData.unsubmittedStudents.map((item: any) => item.name))}
+
+# ANALYSIS RULES #
+1. **问题定位**:明确指出哪个知识点/概念/步骤掌握不佳
+2. **建议具体**:给出可执行的教学动作(如“增加XX实例演练”“补充XX对比图”)
+
+# RESPONSE #
+采用段落叙述,数据量化呈现,突出可操作建议,严格控制输出为80词内。采用三段式论述:
+1. 整体表现-1句话
+2. 核心发现-1-2句,指出共性问题+典型案例
+3. 改进建议-1句话,提出具体教学动作
+
+# EXAMPLES #
+样例:
+选择题正确率62%,核心概念“机器学习三步骤”(输入数据→训练模型→预测结果)掌握尚可,但“训练与预测区分”混淆率达38%;建议强化训练vs预测对比教学。`
+
+  if (!currentAnalysis.value) {
+    aiAnalysisData.value.push({
+      pid: props.workId + (props.cid ? ',' + props.cid : ''),
+      index: props.showData.workIndex,
+      loading: true,
+      generatingContent: true,
+      json: { text: '', echartsData: '' },
+      noEnd: true,
+      update_at: '',
+      create_at: '',
+    })
+  }
+  else {
+    aiAnalysisData.value.find((item: any) => {
+      return item.pid === props.workId + (props.cid ? ',' + props.cid : '') && item.index === props.showData.workIndex
+    }).loading = true
+    aiAnalysisData.value.find((item: any) => {
+      return item.pid === props.workId + (props.cid ? ',' + props.cid : '') && item.index === props.showData.workIndex
+    }).json = { text: '', echartsData: '' }
+  }
+
+  chat_stream(msg, 'a7741704-ba56-40b7-a6b8-62a423ef9376', props.userId, lang.lang, (event) => {
+    if (event.type === 'message') {
+      aiAnalysisData.value.find((item: any) => {
+        return item.pid === props.workId + (props.cid ? ',' + props.cid : '') && item.index === props.showData.workIndex
+      }).json.text = event.data
+
+      aiAnalysisData.value.find((item: any) => {
+        return item.pid === props.workId + (props.cid ? ',' + props.cid : '') && item.index === props.showData.workIndex
+      }).loading = false
+    }
+    else if (event.type === 'messageEnd') {
+      aiAnalysisData.value.find((item: any) => {
+        return item.pid === props.workId + (props.cid ? ',' + props.cid : '') && item.index === props.showData.workIndex
+      }).json.text = event.data
+      aiAnalysisData.value.find((item: any) => {
+        return item.pid === props.workId + (props.cid ? ',' + props.cid : '') && item.index === props.showData.workIndex
+      }).noEnd = false
+      aiAnalysisData.value.find((item: any) => {
+        return item.pid === props.workId + (props.cid ? ',' + props.cid : '') && item.index === props.showData.workIndex
+      }).update_at = new Date().toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }).replace(/\//g, '-')
+      saveAnalysis()
+    }
+  }).catch(err => {
+    console.log('err', err)
+  })
+}
+
 // 问答题
 const aiAnalysisRefresh15 = () => {
   let flag = 0

+ 4 - 1
src/views/Student/components/selectUserDialog.vue

@@ -55,7 +55,7 @@ defineExpose({
 })
 </script>
 
-<style lang="scss">
+<style lang="scss" scoped>
 .modalArea{
   &>.modal-content {
     border-radius: 1rem;
@@ -101,6 +101,9 @@ defineExpose({
   display: flex;
   gap: .8rem;
   flex-wrap: wrap;
+
+  max-height: calc(100vh - 8.6rem);
+  overflow: auto;
   &>div{
     padding: .25rem .5rem;
     font-weight: 500;

+ 3 - 1
src/views/Student/index.vue

@@ -1222,7 +1222,7 @@ const getWorkId = () => {
     typeof element === 'object' &&
     ('toolType' in element) &&
     (element as any).toolType !== undefined &&
-    ((element as any).toolType === 45 || (element as any).toolType === 15 || (element as any).toolType === 73 || (element as any).toolType === 72)
+    ((element as any).toolType === 45 || (element as any).toolType === 15 || (element as any).toolType === 73 || (element as any).toolType === 72 || (element as any).toolType === 78)
   ) {
     // 提取链接中的id参数
     const url = (element as any).url
@@ -2546,6 +2546,7 @@ const handleKeydown = (e: KeyboardEvent) => {
         previousSlide()
       }
       break
+    case 'PageUp':
     case 'ArrowUp':
       e.preventDefault()
       if (!isFollowModeActive.value || props.type == '1') {
@@ -2558,6 +2559,7 @@ const handleKeydown = (e: KeyboardEvent) => {
         nextSlide()
       }
       break
+    case 'PageDown':
     case 'ArrowDown':
     case ' ':
       e.preventDefault()

+ 8 - 7
src/views/components/element/FrameElement/BaseFrameElement.vue

@@ -102,12 +102,12 @@
   </div>
 </template>
   
-  <script lang="ts" setup>
-import type { PropType } from "vue";
-import type { PPTFrameElement } from "@/types/slides";
-import { lang } from "@/main";
-import TopicDiscussionPreview from "@/views/Editor/EnglishSpeaking/preview/TopicDiscussionPreview.vue";
-import { ref, watch, nextTick } from "vue";
+<script lang="ts" setup>
+import type { PropType } from 'vue'
+import type { PPTFrameElement } from '@/types/slides'
+import { lang } from '@/main'
+import TopicDiscussionPreview from '@/views/Editor/EnglishSpeaking/preview/TopicDiscussionPreview.vue'
+import { ref, watch, nextTick } from 'vue'
 import { computed } from 'vue'
 
 const props = defineProps({
@@ -172,7 +172,8 @@ const getTypeLabel = (type: number): string => {
     74: 'ssVideo',
     75: lang.lang == 'cn' ? 'ssBiliVideo' : 'ssYouTube',
     76: 'ssCreateSpace',
-    77: "ssEnglishSpeakingTool",
+    77: 'ssEnglishSpeakingTool',
+    78: 'ssVote',
   }
   const key = typeMap[type]
   return (key ? lang[key] : lang.ssUnknown) as string

+ 1 - 0
src/views/lang/cn.json

@@ -161,6 +161,7 @@
   "ssContentList": "内容列表",
   "ssChoiceQ": "选择",
   "ssQandA": "问答",
+  "ssVote": "投票",
   "ssNoLearn": "暂无学习内容",
   "ssNeedUpload": "请先上传或创建学习内容",
   "ssPreview": "预览",

+ 1 - 0
src/views/lang/en.json

@@ -160,6 +160,7 @@
   "ssCreative": "CocoFlow Creative Space",
   "ssContentList": "Content list",
   "ssQandA": "Q&A",
+  "ssVote": "Vote",
   "ssNoLearn": "No learning content",
   "ssNeedUpload": "Please upload or create learning content first",
   "ssPreview": "Preview",

+ 1 - 0
src/views/lang/hk.json

@@ -160,6 +160,7 @@
   "ssCreative": "創作空間",
   "ssContentList": "內容列表",
   "ssQandA": "問答",
+  "ssVote": "投票",
   "ssNoLearn": "暫無學習內容",
   "ssNeedUpload": "請先上傳或創建學習內容",
   "ssPreview": "預覽",

+ 3 - 3
vite.config.ts

@@ -14,9 +14,9 @@ export default defineConfig({
   server: {
     host: '0.0.0.0',
     port: 5173,
-    headers: {
-      'Origin-Agent-Cluster': '?0',
-    },
+    // headers: {
+    //   'Origin-Agent-Cluster': '?0',
+    // },
     proxy: {
       '/api': {
         target: 'https://server.pptist.cn',