Explorar el Código

feat(student): add KWL learning reflection tool feature

1. 新增多语言配置项用于KWL模块文本展示
2. 新增toolTypeArray统一管理工具类型标识,简化条件判断逻辑
3. 为toolType 831添加KWL三栏式学习反思组件及样式
4. 适配KWL工具类型到内容解析逻辑中
lsc hace 4 días
padre
commit
199355facf

+ 302 - 2
src/views/Student/components/choiceQuestionDetailDialog.vue

@@ -613,6 +613,135 @@
           </div>
         </div>
       </div>
+
+      <!-- kwl表格 -->
+      <div class="c_t72" v-if="props.showData && props.showData.toolType === 831">
+        <div class="c_t72_title">
+          <div class="c_title">
+            <span>{{ lang.ssKwlTitle }}</span>
+            <div class="switch_btn" v-if="props.isCreator && props.roleType == 1">
+              <div class="switch">
+                <Switch :value="canValue" @update:value="handleCanChange" />
+                <span>{{ lang.ssShowResult }}</span>
+              </div>
+              <div class="switch">
+                <Switch :value="likeValue" @update:value="handleLikeChange" />
+                <span>{{ lang.ssLike }}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+        <span class="c_t72_type">{{ lang.ssKwlTitle }}</span>
+        <div class="c_t72_msg" v-if="props.roleType == 1">
+          <div>{{ lang.ssAnswerCount }} {{ props.workArray.length }}<span
+              v-if="props.unsubmittedStudents.length > 0">/{{ props.workArray.length +
+                props.unsubmittedStudents.length
+              }}</span></div>
+          <span v-if="props.unsubmittedStudents.length > 0" @click.stop="viewUnsubmittedStudents()">{{
+            lang.ssViewSubmitStatus2 }}</span>
+        </div>
+        <div class="c_t72_content" v-show="!lookWorkData">
+          <div class="c_t72_c_item" v-for="item in processedWorkArray" :key="item.id" @click.stop="lookWork(item.id)">
+            <div class="c_t72_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="bottom_btn" v-if="likeValue">
+              <div class="bottom_like" :class="{'active': item.isLikes}"  @click.stop="handleLikeClick(item)">
+                <IconThumbsUp />
+                <span>{{ item.likesCount }}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <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('')" 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_t831_wd_content">
+            <!-- KWL 三栏 -->
+            <div class="kwl-grid" v-if="lookWorkData.content">
+              <!-- K - 已知 -->
+              <div class="kwl-col kwl-col--k">
+                <div class="kwl-col__header">
+                  <span class="kwl-col__badge">K</span>
+                  <span class="kwl-col__label">{{ lang.ssKwlKnown }}</span>
+                </div>
+                <div class="kwl-col__body">
+                  <template v-if="lookWorkData.content.know.length">
+                    <div
+                      v-for="(item, idx) in lookWorkData.content.know"
+                      :key="'k-'+idx"
+                      class="kwl-item"
+                    >{{ item }}</div>
+                  </template>
+                  <div v-else class="kwl-empty">{{ lang.ssNone }}</div>
+                </div>
+              </div>
+
+              <!-- W - 想知 -->
+              <div class="kwl-col kwl-col--w">
+                <div class="kwl-col__header">
+                  <span class="kwl-col__badge">W</span>
+                  <span class="kwl-col__label">{{ lang.ssKwlWant }}</span>
+                </div>
+                <div class="kwl-col__body">
+                  <template v-if="lookWorkData.content.want.length">
+                    <div
+                      v-for="(item, idx) in lookWorkData.content.want"
+                      :key="'w-'+idx"
+                      class="kwl-item"
+                    >{{ item }}</div>
+                  </template>
+                  <div v-else class="kwl-empty">{{ lang.ssNone }}</div>
+                </div>
+              </div>
+
+              <!-- L - 已学 -->
+              <div class="kwl-col kwl-col--l">
+                <div class="kwl-col__header">
+                  <span class="kwl-col__badge">L</span>
+                  <span class="kwl-col__label">{{ lang.ssKwlLearned }}</span>
+                </div>
+                <div class="kwl-col__body">
+                  <template v-if="lookWorkData.content.learned.length">
+                    <div
+                      v-for="(item, idx) in lookWorkData.content.learned"
+                      :key="'l-'+idx"
+                      class="kwl-item"
+                    >{{ item }}</div>
+                  </template>
+                  <div v-else class="kwl-empty">{{ lang.ssNone }}</div>
+                </div>
+              </div>
+            </div>
+
+            <!-- 学习反思 -->
+            <div class="kwl-reflection">
+              <div class="kwl-reflection__header">
+                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z" />
+                </svg>
+                <span>{{ lang.ssKwlReflection }}</span>
+              </div>
+              <div class="kwl-reflection__body">
+                {{ lookWorkData.content.reflection ? lookWorkData.content.reflection : lang.ssNone }}
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
     </div>
     <previewImageTool ref="previewImageToolRef" />
     <selectUserDialog ref="selectUserDialogRef" />
@@ -804,7 +933,7 @@ const processWorkContent = async (content: string, toolType: number): Promise<an
   }
 
   try {
-    if ([45, 15, 78, 79].includes(toolType)) {
+    if ([45, 15, 78, 79, 831].includes(toolType)) {
       return JSON.parse(decodeURIComponent(contentToParse))
     }
     else if (toolType === 72) {
@@ -904,7 +1033,7 @@ const lookWorkData = computed(() => {
     }
 
   }
-
+  console.log(_result)
   return _result
 })
 
@@ -3504,4 +3633,175 @@ onUnmounted(() => {
   min-width: 65px;
   justify-content: center;
 }
+
+/* KWL 展示样式 */
+.c_t831_wd_content {
+  margin-top: 16px;
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+
+.kwl-grid {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 12px;
+}
+
+.kwl-col {
+  border-radius: 12px;
+  overflow: hidden;
+  border: 1px solid #e5e7eb;
+  background: #fff;
+  display: flex;
+  flex-direction: column;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
+  transition: box-shadow 0.2s ease;
+
+  &:hover {
+    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.08);
+  }
+
+  &__header {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    padding: 10px 12px;
+    color: #fff;
+    font-weight: 600;
+    font-size: 14px;
+  }
+
+  &__badge {
+    width: 24px;
+    height: 24px;
+    border-radius: 6px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background: rgba(255, 255, 255, 0.25);
+    font-size: 13px;
+    font-weight: 700;
+  }
+
+  &__label {
+    font-size: 14px;
+  }
+
+  &__body {
+    padding: 12px;
+    flex: 1;
+    min-height: 80px;
+    max-height: 220px;
+    overflow-y: auto;
+  }
+}
+
+.kwl-col--k {
+  .kwl-col__header {
+    background: linear-gradient(135deg, #3b82f6, #2563eb);
+  }
+}
+
+.kwl-col--w {
+  .kwl-col__header {
+    background: linear-gradient(135deg, #f59e0b, #d97706);
+  }
+}
+
+.kwl-col--l {
+  .kwl-col__header {
+    background: linear-gradient(135deg, #10b981, #059669);
+  }
+}
+
+.kwl-item {
+  position: relative;
+  padding: 6px 10px 6px 22px;
+  margin-bottom: 6px;
+  font-size: 13px;
+  color: #374151;
+  line-height: 1.5;
+  background: #f9fafb;
+  border-radius: 6px;
+  transition: background 0.2s ease;
+  word-break: break-word;
+
+  &:last-child {
+    margin-bottom: 0;
+  }
+
+  &::before {
+    content: '';
+    position: absolute;
+    left: 10px;
+    top: 13px;
+    width: 5px;
+    height: 5px;
+    border-radius: 50%;
+    background: #d1d5db;
+  }
+
+  &:hover {
+    background: #f3f4f6;
+  }
+}
+
+.kwl-col--k .kwl-item::before {
+  background: #3b82f6;
+}
+
+.kwl-col--w .kwl-item::before {
+  background: #f59e0b;
+}
+
+.kwl-col--l .kwl-item::before {
+  background: #10b981;
+}
+
+.kwl-empty {
+  text-align: center;
+  color: #9ca3af;
+  font-size: 13px;
+  padding: 20px 0;
+  font-style: italic;
+}
+
+/* 学习反思 */
+.kwl-reflection {
+  border-radius: 12px;
+  background: linear-gradient(135deg, #fef3c7, #fde68a);
+  border: 1px solid #fcd34d;
+  overflow: hidden;
+
+  &__header {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    padding: 10px 14px;
+    color: #92400e;
+    font-weight: 600;
+    font-size: 14px;
+    background: rgba(255, 255, 255, 0.5);
+
+    svg {
+      color: #f59e0b;
+    }
+  }
+
+  &__body {
+    padding: 12px 14px;
+    font-size: 13px;
+    color: #78350f;
+    line-height: 1.7;
+    white-space: pre-wrap;
+  }
+}
+
+/* 响应式:移动端单列 */
+@media (max-width: 480px) {
+  .kwl-grid {
+    grid-template-columns: 1fr;
+  }
+}
 </style>

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

@@ -594,6 +594,7 @@ type WorkItem = {
   [key: string]: any
 }
 const workArray = ref<WorkItem[]>([])
+const toolTypeArray = ref<number[]>([45, 15, 73, 72, 78, 79, 831])
 
 // 作业弹窗相关
 const selectedWork = ref<any>(null)
@@ -1274,7 +1275,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 === 78 || (element as any).toolType === 79)
+    toolTypeArray.value.includes((element as any).toolType)
   ) {
     // 提取链接中的id参数
     const url = (element as any).url
@@ -2554,7 +2555,7 @@ const handleRefreshPage = () => {
     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 === 78 || (element as any).toolType === 79)
+    toolTypeArray.value.includes((element as any).toolType)
     ) {
       getWork()
     }

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

@@ -1085,5 +1085,9 @@
   "ssKwlShowReflection": "显示反思区",
   "ssKwlMinEntries": "每栏最少条目数",
   "ssKwlAddToSlide": "添加到幻灯片",
-  "ssKwlUploadFail": "上传失败"
+  "ssKwlUploadFail": "上传失败",
+  "ssKwlKnown": "K-已知",
+  "ssKwlWant": "W-想知",
+  "ssKwlLearned": "L-已学",
+  "ssKwlReflection": "学习反思"
 }

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

@@ -1085,5 +1085,9 @@
   "ssKwlShowReflection": "Show Reflection Area",
   "ssKwlMinEntries": "Min entries per column",
   "ssKwlAddToSlide": "Add to Slide",
-  "ssKwlUploadFail": "Upload failed"
+  "ssKwlUploadFail": "Upload failed",
+  "ssKwlKnown": "K-Known",
+  "ssKwlWant": "W-Want to know",
+  "ssKwlLearned": "L-Learned",
+  "ssKwlReflection": "Learning Reflection"
 }

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

@@ -1085,5 +1085,9 @@
   "ssKwlShowReflection": "顯示反思區",
   "ssKwlMinEntries": "每欄最少條目數",
   "ssKwlAddToSlide": "添加到幻燈片",
-  "ssKwlUploadFail": "上傳失敗"
+  "ssKwlUploadFail": "上傳失敗",
+  "ssKwlKnown": "K-已知",
+  "ssKwlWant": "W-想知",
+  "ssKwlLearned": "L-已學",
+  "ssKwlReflection": "學習反思"
 }