Ver código fonte

feat(学生组件): 新增词云图功能并优化AI分析展示

添加echarts-wordcloud依赖包实现词云图功能
新增echartsDialog组件用于展示关键词词云
在choiceQuestionDetailDialog中增加关键词展示和词云图弹窗
优化AI分析数据的JSON解析和展示逻辑
添加多语言支持词云相关文本
SanHQin 5 dias atrás
pai
commit
1b0f65ee78

+ 1 - 0
package.json

@@ -23,6 +23,7 @@
     "crypto-js": "^4.2.0",
     "crypto-js": "^4.2.0",
     "dexie": "^4.0.11",
     "dexie": "^4.0.11",
     "echarts": "^5.5.1",
     "echarts": "^5.5.1",
+    "echarts-wordcloud": "^2.1.0",
     "file-saver": "^2.0.5",
     "file-saver": "^2.0.5",
     "hfmath": "^0.0.2",
     "hfmath": "^0.0.2",
     "html-to-image": "^1.11.13",
     "html-to-image": "^1.11.13",

+ 141 - 26
src/views/Student/components/choiceQuestionDetailDialog.vue

@@ -69,8 +69,11 @@
 
 
             </div>
             </div>
           </div>
           </div>
-          <div class="ai_content" v-if="currentAnalysis">
-            {{ currentAnalysis.json }}
+          <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>
           <div class="ai_updateTime" v-if="currentAnalysis">{{ lang.ssUpdateTime }}:{{ currentAnalysis.update_at }}
           <div class="ai_updateTime" v-if="currentAnalysis">{{ lang.ssUpdateTime }}:{{ currentAnalysis.update_at }}
           </div>
           </div>
@@ -111,7 +114,8 @@
           </div>
           </div>
         </div>
         </div>
 
 
-        <div class="aiAnalysis" style="margin-top:1rem ;" v-if="processedWorkArray.length > 0 && lookWorkData===null && workDetail.type === '15'">
+        <div class="aiAnalysis" style="margin-top:1rem ;"
+          v-if="processedWorkArray.length > 0 && lookWorkData === null && workDetail.type === '15'">
           <div class="ai_header">
           <div class="ai_header">
             <div class="ai_title">
             <div class="ai_title">
               <svg viewBox="0 0 1024 1024" width="200" height="200">
               <svg viewBox="0 0 1024 1024" width="200" height="200">
@@ -134,8 +138,13 @@
 
 
             </div>
             </div>
           </div>
           </div>
-          <div class="ai_content" v-if="currentAnalysis">
-            {{ currentAnalysis.json }}
+          <div class="ai_content" v-if="currentAnalysis && currentAnalysis.json">
+            {{ currentAnalysis.json.text }}
+          </div>
+          <div class="ai_echartsData" v-if="currentAnalysis && currentAnalysis.json.keyword">
+            <div class="title">{{ lang.ssKeyword }}:</div>
+            <span v-for="(item,index) in currentAnalysis.json.keyword" :key="index">{{ item }}</span>
+            <div class="btn" @click="openEchatsDialog()">{{ lang.ssViewKeywordCloud }}</div>
           </div>
           </div>
           <div class="ai_updateTime" v-if="currentAnalysis">{{ lang.ssUpdateTime }}:{{ currentAnalysis.update_at }}
           <div class="ai_updateTime" v-if="currentAnalysis">{{ lang.ssUpdateTime }}:{{ currentAnalysis.update_at }}
           </div>
           </div>
@@ -171,7 +180,8 @@
           </div>
           </div>
         </div>
         </div>
 
 
-        <div class="aiAnalysis" style="margin-top:1rem ;" v-if="processedWorkArray.length > 0 && lookWorkData===null && props.showData.toolType === 72">
+        <div class="aiAnalysis" style="margin-top:1rem ;"
+          v-if="processedWorkArray.length > 0 && lookWorkData === null && props.showData.toolType === 72">
           <div class="ai_header">
           <div class="ai_header">
             <div class="ai_title">
             <div class="ai_title">
               <svg viewBox="0 0 1024 1024" width="200" height="200">
               <svg viewBox="0 0 1024 1024" width="200" height="200">
@@ -194,8 +204,8 @@
 
 
             </div>
             </div>
           </div>
           </div>
-          <div class="ai_content" v-if="currentAnalysis">
-            {{ currentAnalysis.json }}
+          <div class="ai_content" v-if="currentAnalysis && currentAnalysis.json">
+            {{ currentAnalysis.json.text }}
           </div>
           </div>
           <div class="ai_updateTime" v-if="currentAnalysis">{{ lang.ssUpdateTime }}:{{ currentAnalysis.update_at }}
           <div class="ai_updateTime" v-if="currentAnalysis">{{ lang.ssUpdateTime }}:{{ currentAnalysis.update_at }}
           </div>
           </div>
@@ -278,6 +288,7 @@
     </div>
     </div>
     <previewImageTool ref="previewImageToolRef" />
     <previewImageTool ref="previewImageToolRef" />
     <selectUserDialog ref="selectUserDialogRef" />
     <selectUserDialog ref="selectUserDialogRef" />
+    <echartsDialog ref="echartsDialogRef" />
   </div>
   </div>
 </template>
 </template>
 
 
@@ -289,7 +300,8 @@ import MarkdownIt from 'markdown-it'
 import useImport from '@/hooks/useImport'
 import useImport from '@/hooks/useImport'
 import { lang } from '@/main'
 import { lang } from '@/main'
 import selectUserDialog from './selectUserDialog.vue'
 import selectUserDialog from './selectUserDialog.vue'
-import { chat_stream } from '@/tools/aiChat'
+import echartsDialog from './echartsDialog.vue'
+import { chat_stream, chat_no_stream } from '@/tools/aiChat'
 import axios from '@/services/config'
 import axios from '@/services/config'
 const props = defineProps<{
 const props = defineProps<{
   visible: number[];
   visible: number[];
@@ -325,6 +337,8 @@ const workDetail = computed(() => {
 const previewImageToolRef = ref<any>(null)
 const previewImageToolRef = ref<any>(null)
 // 选择用户组件
 // 选择用户组件
 const selectUserDialogRef = ref<any>(null)
 const selectUserDialogRef = ref<any>(null)
+// 词云图组件
+const echartsDialogRef = ref<any>(null)
 
 
 // ai分析数据
 // ai分析数据
 const aiAnalysisData = ref<Array<any>>([])
 const aiAnalysisData = ref<Array<any>>([])
@@ -579,7 +593,7 @@ const setEchartsArea1 = () => {
                   let displayValue = value
                   let displayValue = value
                   const maxLength = isAllEnglish ? 30 : 20
                   const maxLength = isAllEnglish ? 30 : 20
                   const lineLength = isAllEnglish ? 16 : 8
                   const lineLength = isAllEnglish ? 16 : 8
-                  
+
                   if (value.length > maxLength) {
                   if (value.length > maxLength) {
                     displayValue = value.substr(0, maxLength) + '...'
                     displayValue = value.substr(0, maxLength) + '...'
                   }
                   }
@@ -720,6 +734,14 @@ const getAnalysis = () => {
   axios.get('https://pbl.cocorobo.cn/api/pbl/select_pptAnalysisByPid?pid=' + params.pid).then(res => {
   axios.get('https://pbl.cocorobo.cn/api/pbl/select_pptAnalysisByPid?pid=' + params.pid).then(res => {
     const data = res[0]
     const data = res[0]
     if (data.length) {
     if (data.length) {
+      data.forEach((item: any) => {
+        try {
+          item.json = JSON.parse(item.json)
+        }
+        catch (error) {
+          item.json = { text: item.json, echartsData: '' }
+        }
+      })
       aiAnalysisData.value = data
       aiAnalysisData.value = data
     }
     }
     else {
     else {
@@ -888,7 +910,7 @@ const aiAnalysisRefresh45 = () => {
       pid: props.workId,
       pid: props.workId,
       index: props.showData.workIndex,
       index: props.showData.workIndex,
       loading: true,
       loading: true,
-      json: '',
+      json: { text: '', echartsData: '' },
       noEnd: true,
       noEnd: true,
       update_at: '',
       update_at: '',
       create_at: '',
       create_at: '',
@@ -900,7 +922,7 @@ const aiAnalysisRefresh45 = () => {
     }).loading = true
     }).loading = true
     aiAnalysisData.value.find((item: any) => {
     aiAnalysisData.value.find((item: any) => {
       return item.pid === props.workId && item.index === props.showData.workIndex
       return item.pid === props.workId && item.index === props.showData.workIndex
-    }).json = ''
+    }).json = { text: '', echartsData: '' }
   }
   }
 
 
   chat_stream(msg, 'a7741704-ba56-40b7-a6b8-62a423ef9376', props.userId, lang.lang, (event) => {
   chat_stream(msg, 'a7741704-ba56-40b7-a6b8-62a423ef9376', props.userId, lang.lang, (event) => {
@@ -932,6 +954,7 @@ const aiAnalysisRefresh45 = () => {
 
 
 // 问答题
 // 问答题
 const aiAnalysisRefresh15 = () => {
 const aiAnalysisRefresh15 = () => {
+  let flag = 0
   const msg = `# CONTEXT #
   const msg = `# CONTEXT #
 你是K-12阶段的AI教育课堂分析助手,基于上传的课件、逐字稿,以及当页的学生答题数据(选择题/问答题/智能体对话)进行智能分析。
 你是K-12阶段的AI教育课堂分析助手,基于上传的课件、逐字稿,以及当页的学生答题数据(选择题/问答题/智能体对话)进行智能分析。
 
 
@@ -965,7 +988,7 @@ const aiAnalysisRefresh15 = () => {
       pid: props.workId,
       pid: props.workId,
       index: props.showData.workIndex,
       index: props.showData.workIndex,
       loading: true,
       loading: true,
-      json: '',
+      json: { text: '', echartsData: '' },
       noEnd: true,
       noEnd: true,
       update_at: '',
       update_at: '',
       create_at: '',
       create_at: '',
@@ -977,14 +1000,19 @@ const aiAnalysisRefresh15 = () => {
     }).loading = true
     }).loading = true
     aiAnalysisData.value.find((item: any) => {
     aiAnalysisData.value.find((item: any) => {
       return item.pid === props.workId && item.index === props.showData.workIndex
       return item.pid === props.workId && item.index === props.showData.workIndex
-    }).json = ''
+    }).json = { text: '', echartsData: '' }
   }
   }
-
+  getWordCloud15().then((res) => {
+    flag += 1
+    if (flag == 2) {
+      saveAnalysis()
+    }
+  })
   chat_stream(msg, 'a7741704-ba56-40b7-a6b8-62a423ef9376', props.userId, lang.lang, (event) => {
   chat_stream(msg, 'a7741704-ba56-40b7-a6b8-62a423ef9376', props.userId, lang.lang, (event) => {
     if (event.type === 'message') {
     if (event.type === 'message') {
       aiAnalysisData.value.find((item: any) => {
       aiAnalysisData.value.find((item: any) => {
         return item.pid === props.workId && item.index === props.showData.workIndex
         return item.pid === props.workId && item.index === props.showData.workIndex
-      }).json = event.data
+      }).json.text = event.data
 
 
       aiAnalysisData.value.find((item: any) => {
       aiAnalysisData.value.find((item: any) => {
         return item.pid === props.workId && item.index === props.showData.workIndex
         return item.pid === props.workId && item.index === props.showData.workIndex
@@ -993,20 +1021,70 @@ const aiAnalysisRefresh15 = () => {
     else if (event.type === 'messageEnd') {
     else if (event.type === 'messageEnd') {
       aiAnalysisData.value.find((item: any) => {
       aiAnalysisData.value.find((item: any) => {
         return item.pid === props.workId && item.index === props.showData.workIndex
         return item.pid === props.workId && item.index === props.showData.workIndex
-      }).json = event.data
+      }).json.text = event.data
       aiAnalysisData.value.find((item: any) => {
       aiAnalysisData.value.find((item: any) => {
         return item.pid === props.workId && item.index === props.showData.workIndex
         return item.pid === props.workId && item.index === props.showData.workIndex
       }).noEnd = false
       }).noEnd = false
       aiAnalysisData.value.find((item: any) => {
       aiAnalysisData.value.find((item: any) => {
         return item.pid === props.workId && item.index === props.showData.workIndex
         return item.pid === props.workId && 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, '-')
       }).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()
+      flag += 1
+      if (flag == 2) {
+        saveAnalysis()
+
+      }
     }
     }
   }).catch(err => {
   }).catch(err => {
     console.log('err', err)
     console.log('err', err)
   })
   })
 }
 }
 
 
+// 打开词云图弹窗
+const openEchatsDialog = () => {
+  if (echartsDialogRef.value && currentAnalysis.value.json.echartsData) {
+    echartsDialogRef.value.open(lang.ssKeywordCloud,currentAnalysis.value.json.echartsData)
+  }
+}
+
+
+
+//问答题获取词云图与关键词
+const getWordCloud15 = () => {
+
+  return new Promise((resolve,) => {
+    const msg = `## 任务 请基于以下文本,提炼出10 - 30个关键词,用于绘制词云图。请给出相应的关键词,以及关键词出现的频次。请确保输出的关键字准确反映该段文本的主要内容和主题。
+## 要求
+1. ** 提取关键词 **:从提供的文本中提取出10 - 30个最具代表性的关键字。关键词应该涵盖该文本的主要概念、重要术语和核心主题。尽量选择多样化的关键词,避免过于集中在某一个主题或概念上。
+    2. ** 词频统计 **:计算每个关键字在文本中出现的频率。
+    3. ** 词汇大小 **:根据词频数量,确定每个关键字在词云图中的大小。词频越高,词汇大小数值越大,数值范围1 - 100。
+    4. ** 输出格式 **:输出结果应包含输出相应的关键词或元话语、对应的词频数量以及词汇大小数值,请以json格式输出,严格按照输出示例输出。
+
+## 文本
+课程数据:
+- 课程名称:${props.courseDetail.title}
+- 课程学科:${props.courseDetail.name}
+当前页面答题数据(问答题):【分析重点】
+- 问答题题目:${props.showData.workDetail.json.answerQ}
+- 回答数据:${JSON.stringify(processedWorkArray.value.map((i) => ({ user: i.name, answer: i.content.answer })))}
+
+## 输出示例
+{"tooltip":{"show":false},"series":[{"type":"wordCloud","sizeRange":[14,38],"rotationRange":[0,0],"keepAspect":false,"shape":"circle","left":"center","top":"center","right":null,"bottom":null,"width":"100%","height":"100%","rotationStep":20,"data":[{"value":"词汇大小,数值范围1-100","name":"词汇","textStyle":{"color":"词汇颜色(16进制)"}},{"value":"词汇大小,数值范围1-100","name":"词汇","textStyle":{"color":"(16进制)"}}]}]}`
+
+
+    chat_no_stream(msg, 'a7741704-ba56-40b7-a6b8-62a423ef9376', props.userId, lang.lang).promise.then((res) => {
+      aiAnalysisData.value.find((item: any) => {
+        return item.pid === props.workId && item.index === props.showData.workIndex
+      }).json.echartsData = JSON.parse(res)
+      resolve(1)
+    }).catch(err => {
+      console.log('err', err)
+      resolve(0)
+    })
+  })
+
+
+}
+
 // ai应用
 // ai应用
 const aiAnalysisRefresh72 = async () => {
 const aiAnalysisRefresh72 = async () => {
 
 
@@ -1024,7 +1102,7 @@ ${a.content}\n`
 
 
 
 
 
 
-// - 未提交学生:${JSON.stringify(props.showData.unsubmittedStudents.map((item: any) => item.name))}
+  // - 未提交学生:${JSON.stringify(props.showData.unsubmittedStudents.map((item: any) => item.name))}
   const msg = `# CONTEXT #
   const msg = `# CONTEXT #
 你是K-12阶段的AI教育课堂分析助手,基于上传的课件、逐字稿,以及当页的学生答题数据(选择题/问答题/智能体对话)进行智能分析。
 你是K-12阶段的AI教育课堂分析助手,基于上传的课件、逐字稿,以及当页的学生答题数据(选择题/问答题/智能体对话)进行智能分析。
 
 
@@ -1053,13 +1131,13 @@ ${a.content}\n`
 # EXAMPLES #
 # EXAMPLES #
 样例:
 样例:
 智能体对话显示学生对“模型训练”概念模糊,多次询问“为什么不能直接告诉机器答案”。针对概念混淆学生,补充“人类学习类比”相关解释,巩固“从数据中学习规律”核心认知。`
 智能体对话显示学生对“模型训练”概念模糊,多次询问“为什么不能直接告诉机器答案”。针对概念混淆学生,补充“人类学习类比”相关解释,巩固“从数据中学习规律”核心认知。`
-console.log("cs",msg)
+  console.log("cs", msg)
   if (!currentAnalysis.value) {
   if (!currentAnalysis.value) {
     aiAnalysisData.value.push({
     aiAnalysisData.value.push({
       pid: props.workId,
       pid: props.workId,
       index: props.showData.workIndex,
       index: props.showData.workIndex,
       loading: true,
       loading: true,
-      json: '',
+      json: { text: '', echartsData: '' },
       noEnd: true,
       noEnd: true,
       update_at: '',
       update_at: '',
       create_at: '',
       create_at: '',
@@ -1071,14 +1149,14 @@ console.log("cs",msg)
     }).loading = true
     }).loading = true
     aiAnalysisData.value.find((item: any) => {
     aiAnalysisData.value.find((item: any) => {
       return item.pid === props.workId && item.index === props.showData.workIndex
       return item.pid === props.workId && item.index === props.showData.workIndex
-    }).json = ''
+    }).json = { text: '', echartsData: '' }
   }
   }
 
 
   chat_stream(msg, 'a7741704-ba56-40b7-a6b8-62a423ef9376', props.userId, lang.lang, (event) => {
   chat_stream(msg, 'a7741704-ba56-40b7-a6b8-62a423ef9376', props.userId, lang.lang, (event) => {
     if (event.type === 'message') {
     if (event.type === 'message') {
       aiAnalysisData.value.find((item: any) => {
       aiAnalysisData.value.find((item: any) => {
         return item.pid === props.workId && item.index === props.showData.workIndex
         return item.pid === props.workId && item.index === props.showData.workIndex
-      }).json = event.data
+      }).json.text = event.data
 
 
       aiAnalysisData.value.find((item: any) => {
       aiAnalysisData.value.find((item: any) => {
         return item.pid === props.workId && item.index === props.showData.workIndex
         return item.pid === props.workId && item.index === props.showData.workIndex
@@ -1087,7 +1165,7 @@ console.log("cs",msg)
     else if (event.type === 'messageEnd') {
     else if (event.type === 'messageEnd') {
       aiAnalysisData.value.find((item: any) => {
       aiAnalysisData.value.find((item: any) => {
         return item.pid === props.workId && item.index === props.showData.workIndex
         return item.pid === props.workId && item.index === props.showData.workIndex
-      }).json = event.data
+      }).json.text = event.data
       aiAnalysisData.value.find((item: any) => {
       aiAnalysisData.value.find((item: any) => {
         return item.pid === props.workId && item.index === props.showData.workIndex
         return item.pid === props.workId && item.index === props.showData.workIndex
       }).noEnd = false
       }).noEnd = false
@@ -1104,9 +1182,21 @@ console.log("cs",msg)
 
 
 // 当前分析
 // 当前分析
 const currentAnalysis = computed(() => {
 const currentAnalysis = computed(() => {
-  return aiAnalysisData.value.find((item: any) => {
+  let _result = aiAnalysisData.value.find((item: any) => {
     return item.pid === props.workId && item.index === props.showData.workIndex
     return item.pid === props.workId && item.index === props.showData.workIndex
   })
   })
+
+  if (_result?.json?.echartsData) {
+    _result.json.keyword = _result.json.echartsData?.series?.[0]?.data?.map((item: any) => item.name)
+  }
+
+
+  if (_result) {
+    return _result
+  }
+  else {
+    return null
+  }
 })
 })
 
 
 // 保存分析
 // 保存分析
@@ -1117,9 +1207,10 @@ const saveAnalysis = () => {
   const params = [{
   const params = [{
     pid: props.workId,
     pid: props.workId,
     idx: props.showData.workIndex,
     idx: props.showData.workIndex,
-    json: currentAnalysis.value.json,
+    json: JSON.stringify(currentAnalysis.value.json),
   }]
   }]
 
 
+  console.log(params)
   axios.post('https://pbl.cocorobo.cn/api/pbl/insert_pptAnalysis', params).then(res => {
   axios.post('https://pbl.cocorobo.cn/api/pbl/insert_pptAnalysis', params).then(res => {
     if (res == 1) {
     if (res == 1) {
       console.log('保存成功')
       console.log('保存成功')
@@ -1919,6 +2010,30 @@ onUnmounted(() => {
     font-size: 1rem;
     font-size: 1rem;
     font-weight: 500;
     font-weight: 500;
   }
   }
+  &>.ai_echartsData{
+    display: flex;
+    flex-wrap: wrap;
+    gap: .5rem;
+    &>span{
+      padding: .2rem .5rem;
+      border-radius: .25rem;
+      background: #E6F4FF;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      color: #141517;
+      font-size: .75rem;
+    }
+    &>.title{
+      font-weight: 600;
+    }
+    &>.btn{
+      font-size: .9rem;
+      text-decoration: underline;
+      color: #F6C82B;
+      cursor: pointer;
+    }
+  }
 
 
   &>.ai_updateTime {
   &>.ai_updateTime {
     font-size: .8rem;
     font-size: .8rem;

+ 140 - 0
src/views/Student/components/echartsDialog.vue

@@ -0,0 +1,140 @@
+<template>
+   <Modal :visible="show" :class="'modalArea'" style="padding: 0;background: none;" :width="400" :closeButton="false" :closeOnClickMask="true" @closed="close()">
+    <div class="header">
+      <div class="title" v-html="title"></div>
+      <div class="close" @click="close()"><svg  viewBox="0 0 1024 1024" width="200" height="200"><path d="M999.819275 905.894092c26.805506 27.003004 26.78811 70.77902-0.051166 97.756441-13.398148 13.484106-30.970362 20.234857-48.534389 20.234857-17.58961 0-35.171034-6.758937-48.577369-20.277836L511.657192 609.698113 120.30704 1003.263723c-13.407358 13.475919-30.970362 20.217461-48.53439 20.217461-17.58961 0-35.17922-6.758937-48.585555-20.269649-26.813692-27.003004-26.78811-70.77902 0.042979-97.764628l391.33378-393.557424L23.572882 117.989251c-26.813692-27.012214-26.796296-70.780043 0.034792-97.764627 26.839275-26.985608 70.332858-26.960025 97.137341 0.042979l390.989949 393.909441L903.07693 20.61962c26.831089-26.977421 70.315462-26.960025 97.129154 0.042979 26.813692 27.01119 26.78811 70.770833-0.042979 97.764627L608.812953 511.98465l391.006322 393.909442z"></path></svg></div>
+    </div>
+    <div class="content">
+      <div ref="echartsRef" class="echartsArea"></div>
+    </div>
+  </Modal>
+</template>
+
+<script setup>
+import { ref, nextTick, watch } from 'vue'
+import Modal from '@/components/Modal.vue'
+import * as echarts from 'echarts'
+import 'echarts-wordcloud'
+
+const show = ref(false)
+const data = ref({})
+const title = ref("")
+const echartsRef = ref(null)
+let chartInstance = null
+
+// 打开
+const open = (titleText, option) => {
+  init()
+  show.value = true
+  title.value = titleText
+  data.value = JSON.parse(JSON.stringify(option))
+  // 等待DOM渲染完成后初始化词云图
+  nextTick(() => {
+    initWordCloud()
+  })
+}
+
+// 关闭
+const close = () => {
+  show.value = false
+  init()
+}
+
+const init = () => {
+  title.value = ""
+  data.value = {}
+  if (chartInstance) {
+    chartInstance.dispose()
+    chartInstance = null
+  }
+}
+
+// 初始化词云图
+const initWordCloud = () => {
+  if (!echartsRef.value) return
+  
+  chartInstance = echarts.init(echartsRef.value)
+  
+  chartInstance.setOption(data.value)
+  
+  // 点击事件
+  chartInstance.on('click', function (params) {
+    console.log('点击了词云:', params.name, params.value)
+  })
+  
+  // 窗口大小改变时重绘
+  window.addEventListener('resize', handleResize)
+}
+
+// 处理窗口大小变化
+const handleResize = () => {
+  if (chartInstance) {
+    chartInstance.resize()
+  }
+}
+
+// 监听show变化,关闭时移除resize监听
+watch(show, (newVal) => {
+  if (!newVal) {
+    window.removeEventListener('resize', handleResize)
+  }
+})
+
+defineExpose({
+  open,
+  close,
+})
+</script>
+
+<style lang="scss">
+.modalArea{
+  &>.modal-content {
+    border-radius: 1rem;
+    box-shadow: 0 0 10px #FDF6DE;
+    padding: 0;
+    border: solid 1px #FDF6DE;
+  }
+}
+
+.header{
+  padding: 1.2rem 1.5rem;
+  border-bottom: solid 1px #FDF6DE;
+  font-weight: bold;
+  font-size: 1.2rem;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  &>.title{
+    display: flex;
+    align-items: center;
+    gap: .5rem;
+  }
+  &>.close{
+    cursor: pointer;
+    padding: .6rem;
+    border-radius: 2rem;
+    border: solid 1px #FCEFC8;
+    width: fit-content;
+    height: fit-content;
+    box-sizing: border-box;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    &>svg{
+      width: .7rem;
+      height: .7rem;
+    }
+  }
+}
+
+.content{
+  padding: 1rem;
+  display: flex;
+  gap: .8rem;
+  flex-wrap: wrap;
+  &>.echartsArea{
+    width: 100%;
+    height: 300px;
+  }
+}
+</style>

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

@@ -748,5 +748,8 @@
   "ssPublish": "发布",
   "ssPublish": "发布",
   "ssSave": "保存",
   "ssSave": "保存",
   "ssNewCourse": "新建课程",
   "ssNewCourse": "新建课程",
-  "ssPageSizeMismatch": "待添加页面与当前页面大小不一致,是否继续?"
+  "ssPageSizeMismatch": "待添加页面与当前页面大小不一致,是否继续?",
+  "ssViewKeywordCloud":"点击查看词云",
+  "ssKeyword":"关键词",
+  "ssKeywordCloud":"词云"
 }
 }

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

@@ -748,5 +748,8 @@
   "ssPublish": "Publish",
   "ssPublish": "Publish",
   "ssSave": "Save",
   "ssSave": "Save",
   "ssNewCourse": "New Course",
   "ssNewCourse": "New Course",
-  "ssPageSizeMismatch": "Page size mismatch, continue?"
+  "ssPageSizeMismatch": "Page size mismatch, continue?",
+  "ssViewKeywordCloud": "Click to view keyword cloud",
+  "ssKeyword": "Keyword",
+  "ssKeywordCloud": "Word Cloud"
 }
 }

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

@@ -748,5 +748,8 @@
   "ssPublish": "發布",
   "ssPublish": "發布",
   "ssSave": "保存",
   "ssSave": "保存",
   "ssNewCourse": "新建課程",
   "ssNewCourse": "新建課程",
-  "ssPageSizeMismatch": "頁面大小不一致,是否繼續?"
+  "ssPageSizeMismatch": "頁面大小不一致,是否繼續?",
+  "ssViewKeywordCloud":"點擊查看詞雲",
+  "ssKeyword":"關鍵詞",
+  "ssKeywordCloud":"詞雲"
 }
 }