Ver código fonte

feat: 添加AI对话、投屏相关国际化与功能实现

1. 新增多语言包中AI对话、投屏相关翻译文案
2. 为AI聊天按钮替换为国际化文本
3. 实现根据环境和语言加载对应AI网页地址
4. 添加投屏相关功能:投屏按钮、投屏/退出投屏事件处理,以及弹窗投屏状态UI
lsc 4 dias atrás
pai
commit
5927850b7b

+ 14 - 1
src/components/CollapsibleToolbar/componets/aiWeb.vue

@@ -39,8 +39,21 @@ const emit = defineEmits<{
 }>()
 
 const iframeUrl = computed(() => {
+  let url = 'https://beta.app.cocorobo.cn'
+  if (window.location.href.includes('beta')) {
+    url = 'https://beta.app.cocorobo.cn'
+  }
+  else if (lang.lang === 'cn') {
+    url = 'https://app.cocorobo.cn'
+  }
+  else if (lang.lang === 'hk') {
+    url = 'https://app.cocorobo.hk'
+  }
+  else if (lang.lang === 'en') {
+    url = 'https://app.cocorobo.com'
+  }
   if (!props.webId) return ''
-  return `https://beta.app.cocorobo.cn/#/web?id=${props.webId}&create=false&isPPT=true`
+  return `${url}/#/web?id=${props.webId}&create=false&isPPT=true`
 })
 
 const closeModal = () => {

+ 3 - 1
src/plugins/icon.ts

@@ -131,7 +131,8 @@ import {
   LoadingFour, // 引入loadingIcon
   UpTwo,
   Refresh,
-  ThumbsUp
+  ThumbsUp,
+  CastScreen
 } from '@icon-park/vue-next'
 
 export interface Icons {
@@ -269,6 +270,7 @@ export const icons: Icons = {
   UpTwo: UpTwo,
   IconRefresh: Refresh,
   IconThumbsUp: ThumbsUp,
+  IconCastScreen: CastScreen,
 }
 
 export default {

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

@@ -246,6 +246,10 @@
             <div class="c_t15_c_i_top">
               <span>{{ item.name.charAt(0) }}</span>
               <div>{{ item.name }}</div>
+              <div class="cast_sreen_btn" @click.stop="castScreen(item)"  v-if="props.isCreator && props.roleType == 1 && isFollowModeActive && canValue">
+                <IconCastScreen />
+                {{ lang.ssCastScreen }}
+              </div>
             </div>
             <div class="c_t15_c_i_bottom">
               <span v-html="item.content.answer"></span>
@@ -301,9 +305,13 @@
 
         <div class="c_t15_workDetail" v-if="lookWorkData" @click.stop="clickContent(false)">
           <div class="c_t15_wd_top">
-            <img src="../../../assets/img/arrow_left.png" @click.stop="lookWork('')" />
-            <span>S</span>
+            <img src="../../../assets/img/arrow_left.png" @click.stop="lookWork('')"  v-if="!isCastScreen"/>
+            <span>{{ lookWorkData.name.charAt(0) }}</span>
             <div>{{ lookWorkData.name }}</div>
+            <div class="cast_sreen_btn" @click.stop="exitCastScreen"  v-if="(props.isCreator && props.roleType == 1 && isCastScreen) || (!isFollowModeActive && isCastScreen)">
+              <IconCastScreen />
+              {{ lang.ssExitCastScreen }}
+            </div>
           </div>
           <div class="c_t15_wd_content">
             <span v-html="lookWorkData.content.answer"></span>
@@ -366,7 +374,7 @@
         <div class="c_t79_workDetail" v-if="lookWorkData" @click.stop="clickContent(false)">
           <div class="c_t79_wd_top">
             <img src="../../../assets/img/arrow_left.png" @click.stop="lookWork('')" />
-            <span>S</span>
+            <span>{{ lookWorkData.name.charAt(0) }}</span>
             <div>{{ lookWorkData.name }}</div>
           </div>
           <div class="c_t79_wd_content">
@@ -473,7 +481,7 @@
         <div class="c_t72_workDetail" v-if="lookWorkData" @click.stop="clickContent(false)">
           <div class="c_t72_wd_top">
             <img src="../../../assets/img/arrow_left.png" @click.stop="lookWork('')" />
-            <span>S</span>
+            <span>{{ lookWorkData.name.charAt(0) }}</span>
             <div>{{ lookWorkData.name }}</div>
           </div>
           <div class="c_t72_wd_content">
@@ -565,7 +573,7 @@
         <div class="c_t73_workDetail" v-if="lookWorkData" @click.stop="clickContent(false)">
           <div class="c_t73_wd_top">
             <img src="../../../assets/img/arrow_left.png" @click.stop="lookWork('')" />
-            <span>S</span>
+            <span>{{ lookWorkData.name.charAt(0) }}</span>
             <div>{{ lookWorkData.name }}</div>
           </div>
           <div class="c_t73_wd_content">
@@ -610,6 +618,8 @@ const props = defineProps<{
   roleType: number;
   resultArray: { [key: string]: any };
   isCreator: boolean;
+  isFollowModeActive: boolean;
+  courseid: string;
 }>()
 
 const emit = defineEmits<{
@@ -617,6 +627,7 @@ const emit = defineEmits<{
   (e: 'changeWorkIndex', v: number): void;
   (e: 'setIsResultArray', v: boolean, key: string): void;
   (e: 'successLike'): void;
+  (e: 'sendMessage', v: any): void;
 }>()
 
 const visible = computed({
@@ -674,6 +685,7 @@ const handleAnonymousChange = (value: boolean) => {
 
 
 import _ from 'lodash'
+import type { workerData } from 'worker_threads'
 
 const handleLikeClick = _.debounce((item: any) => {
   likeWork({
@@ -856,8 +868,40 @@ const lookWorkIndex = ref<number>(0)
 const lookWork = (id: string) => {
   lookWorkIndex.value = 0
   lookWorkDetail.value = id
+  if (isCastScreen.value && !id) {
+    emit('sendMessage', {
+      type: 'exit_cast_screen',
+      courseid: props.courseid,
+    })
+    isCastScreen.value = false
+  }
 }
 
+const isCastScreen = ref<boolean>(false)
+// 投屏
+const castScreen = (item: any) => {
+  lookWorkIndex.value = 0
+  lookWorkDetail.value = item.id
+  isCastScreen.value = true
+  emit('sendMessage', {
+    type: 'cast_screen',
+    courseid: props.courseid,
+    workerData: item
+  })
+  console.log(item)
+}
+
+// 退出投屏
+const exitCastScreen = () => {
+  if (props.roleType != 1) {
+    isCastScreen.value = false
+  }
+  lookWork('')
+}
+
+// 暴露给父级(Student/index.vue)用 ref 调用
+defineExpose({ castScreen, exitCastScreen })
+
 // 切换查看作业图片
 const changelookWorkIndex = (type: number) => {
   if (type === 0 && lookWorkIndex.value > 0) {
@@ -2203,6 +2247,9 @@ const saveAnalysis = () => {
 // 点击边框
 const clickContent = (flag: boolean) => {
   if (flag && lookWorkDetail.value && workDetail.value?.type !== '45' && workDetail.value?.type !== '78') {
+    if (props.roleType != 1 && isCastScreen.value) {
+      return
+    }
     lookWork('')
   }
 }
@@ -2452,6 +2499,7 @@ onUnmounted(() => {
           background: rgba(255, 255, 255, 0.6);
           transition: 0.3s;
           cursor: pointer;
+          overflow: hidden;
 
           &:hover {
             box-shadow: 4px 4px 14px 0px rgba(252, 207, 0, 0.5);
@@ -2462,10 +2510,11 @@ onUnmounted(() => {
             display: flex;
             align-items: center;
             gap: 10px;
+            width: 100%;
+            overflow: hidden;
 
             &>span {
-              display: block;
-              width: 25px;
+              min-width: 25px;
               height: 25px;
               display: flex;
               align-items: center;
@@ -2480,6 +2529,9 @@ onUnmounted(() => {
             &>div {
               color: rgba(0, 0, 0, 0.7);
               font-weight: 800;
+              overflow: hidden;
+              text-overflow: ellipsis;
+              white-space: nowrap;
             }
           }
 
@@ -3285,4 +3337,20 @@ onUnmounted(() => {
     }
   }
 }
+
+.cast_sreen_btn{
+  display: flex;
+  align-items: center;
+  gap: 5px;
+  font-weight: 300;
+  font-size: 14px;
+  color: rgba(0, 0, 0, 0.7);
+  cursor: pointer;
+  margin-left: auto;
+  border-radius: 5px;
+  border: solid 1px rgba(252, 207, 0, .7);
+  padding: .5rem 0.6rem;
+  min-width: 65px;
+  justify-content: center;
+}
 </style>

+ 19 - 2
src/views/Student/index.vue

@@ -91,7 +91,7 @@
           <div class="aiBtn" ref="aiBtnRef" v-if="isQuestionFrame && hasWork && props.type == '2' && aiAssistant" 
             :style="{ right: aiBtnPosition.x + 'px', bottom: aiBtnPosition.y + 'px' }" @click="openAiChat">
             <IconComment class="aiBtn-icon" />
-            <span>AI对话</span>
+            <span>{{ lang.ssAiChat }}</span>
           </div>
           <aiChat v-show="visibleAIChat" :position="aiBtnPosition" @close="visibleAIChat = false" :userid="props.userid" :workJson="myWork" :visible="visibleAIChat" :cid="props.cid"/>
          <!--  -->
@@ -106,7 +106,7 @@
           <ScreenSlideList :style="{ width: isFullscreen ? '100%' : slideWidth2 * canvasScale + 'px', height: isFullscreen ? '100%' : slideHeight2 * canvasScale + 'px', margin: '0 auto' }" :slideWidth="isFullscreen ? slideWidth * canvasScale : slideWidth2 * canvasScale" :slideHeight="isFullscreen ? slideHeight * canvasScale : slideHeight2 * canvasScale"
             :animationIndex="0" :turnSlideToId="() => { }" :manualExitFullscreen="() => { }"  :slideIndex="slideIndex" v-show="!choiceQuestionDetailDialogOpenList.includes(slideIndex)"/>
 
-          <choiceQuestionDetailDialog v-if="choiceQuestionDetailDialogOpenList.includes(slideIndex) && currentSlideToolType !== 77" :roleType="props.type" :cid="props.cid" :workId="workId" :workUrl="workUrl" :userId="props.userid" :courseDetail="courseDetail" :workArray="workArray" @changeWorkIndex="changeWorkIndex" v-model:visible="choiceQuestionDetailDialogOpenList" :showData="answerTheResultRef" :slideIndex="slideIndex" :workIndex="0" :style="{ width: isFullscreen ? '100%' : slideWidth2 * canvasScale + 'px', height: isFullscreen ? '100%' : slideHeight2 * canvasScale + 'px', margin: '0 auto' }" :slideWidth="isFullscreen ? slideWidth * canvasScale : slideWidth2 * canvasScale" :slideHeight="isFullscreen ? slideHeight * canvasScale : slideHeight2 * canvasScale" :resultArray="currentIsResultArray" @setIsResultArray="setIsResultArray2" :isCreator="isCreator" @successLike="successLike"/>
+          <choiceQuestionDetailDialog ref="choiceQuestionDetailDialogRef" v-if="choiceQuestionDetailDialogOpenList.includes(slideIndex) && currentSlideToolType !== 77" :roleType="props.type" :cid="props.cid" :courseid="props.courseid" :workId="workId" :workUrl="workUrl" :userId="props.userid" :courseDetail="courseDetail" :workArray="workArray" @changeWorkIndex="changeWorkIndex" v-model:visible="choiceQuestionDetailDialogOpenList" :showData="answerTheResultRef" :slideIndex="slideIndex" :workIndex="0" :style="{ width: isFullscreen ? '100%' : slideWidth2 * canvasScale + 'px', height: isFullscreen ? '100%' : slideHeight2 * canvasScale + 'px', margin: '0 auto' }" :slideWidth="isFullscreen ? slideWidth * canvasScale : slideWidth2 * canvasScale" :slideHeight="isFullscreen ? slideHeight * canvasScale : slideHeight2 * canvasScale" :resultArray="currentIsResultArray" @setIsResultArray="setIsResultArray2" :isCreator="isCreator" @successLike="successLike" @sendMessage="sendMessage" :isFollowModeActive="isFollowModeActive"/>
           <SpeakingClassPanel
             v-else-if="choiceQuestionDetailDialogOpenList.includes(slideIndex) && currentSlideToolType === 77"
             ref="speakingPanelRef"
@@ -585,6 +585,7 @@ const visibleChoice = ref(false)
 const visibleAI = ref(false)
 const choiceQuestionDetailDialogOpenList = ref<number[]>([])
 const speakingPanelRef = ref<InstanceType<typeof SpeakingClassPanel> | null>(null)
+const choiceQuestionDetailDialogRef = ref<InstanceType<typeof choiceQuestionDetailDialog> | null>(null)
 
 provide('notifySpeakingProgress', (status: 'active' | 'completed', payload: { configId: string; sessionId: string }) => {
   if (props.type !== '2') return // 只有学生客户端发广播
@@ -3690,6 +3691,22 @@ const getMessages = (msgObj: any) => {
   if (props.type == '2' && msgObj.type === 'isResultArray' && msgObj.courseid === props.courseid) {
     isResultArray.value = msgObj.isResultArray || []
   }
+
+  // 投屏
+  if (props.type == '2' && msgObj.type === 'cast_screen' && msgObj.courseid === props.courseid) {
+    openChoiceQuestionDetail3(slideIndex.value)
+    setTimeout(() => {
+      choiceQuestionDetailDialogRef.value?.castScreen?.(msgObj.workerData)
+    }, 500)
+  }
+
+  // 退出投屏
+  if (props.type == '2' && msgObj.type === 'exit_cast_screen' && msgObj.courseid === props.courseid) {
+    openChoiceQuestionDetail3(slideIndex.value)
+    setTimeout(() => {
+      choiceQuestionDetailDialogRef.value?.exitCastScreen?.()
+    }, 500)
+  }
 }
 
 

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

@@ -915,5 +915,8 @@
   "ssLike": "点赞",
   "ssAnonymous": "匿名",
   "ssRealName": "实名",
-  "ssAnonymousUser": "匿名用户"
+  "ssAnonymousUser": "匿名用户",
+  "ssAiChat": "AI对话",
+  "ssCastScreen": "投屏",
+  "ssExitCastScreen": "退出投屏"
 }

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

@@ -915,5 +915,8 @@
   "ssLike": "Like",
   "ssAnonymous": "Anonymous",
   "ssRealName": "Real Name",
-  "ssAnonymousUser": "Anonymous User"
+  "ssAnonymousUser": "Anonymous User",
+  "ssAiChat": "AI Chat",
+  "ssCastScreen": "Cast Screen",
+  "ssExitCastScreen": "Exit Cast Screen"
 }

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

@@ -915,5 +915,8 @@
   "ssLike": "點贊",
   "ssAnonymous": "匿名",
   "ssRealName": "實名",
-  "ssAnonymousUser": "匿名用戶"
+  "ssAnonymousUser": "匿名用戶",
+  "ssAiChat": "AI對話",
+  "ssCastScreen": "投屏",
+  "ssExitCastScreen": "退出投屏"
 }