Browse Source

查看详细

SanHQin 1 month ago
parent
commit
ce19d6857b

BIN
src/assets/img/arrow_left.png


+ 4 - 4
src/views/Student/components/ChoiceWorkModal.vue

@@ -302,11 +302,12 @@ const onDragEnd = () => {
   height: auto;
   padding: 15px 15px 15px 15px;
   display: flex;
-  flex-wrap: wrap;
+  /* flex-wrap: wrap; */
   background-color: #f3f7fd;
   border-radius: 30px;
   margin: 10px 0 10px 0px;
   box-sizing: border-box;
+  cursor: pointer;
 }
 
 .s_b_m_ti_option > span > img {
@@ -376,6 +377,7 @@ const onDragEnd = () => {
 .s_b_m_ti_title {
   display: flex;
   align-items: flex-start;
+  
 }
 
 .s_b_m_ti_title > span:nth-of-type(1) {
@@ -383,10 +385,10 @@ const onDragEnd = () => {
   font-weight: bold;
   color: #3681fc;
   min-height: 30px;
+  height: 30px;
   display: flex;
   align-items: center;
   justify-content: center;
-  margin-top: -3px;
 }
 
 .s_b_m_ti_title > svg {
@@ -406,8 +408,6 @@ const onDragEnd = () => {
   min-height: 30px;
   line-height: 30px;
   color: #1f1f1f;
-
-
 }
 
 .s_b_m_ti_title > div {

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

@@ -83,16 +83,18 @@
 						</div>
 						<img
 							@click="changeShow(op, idx)"
+              v-if="op.user.length > 0"
 							:class="{ show_active: !op.show }"
 							src="../../../assets/img/arrow_up.png"
 						/>
 					</div>
 					<div
 						class="atr_t45a_i_bottom"
-						v-if="op.user.length > 0"
+						v-if="op.user.length > 0 && op.show"
 						:class="{ atr_t45a_i_b_active: op.show }"
 					>
 						<div
+              
 							v-for="(name, uIdx) in op.user"
 							:key="`${workIndex}_${idx}_${uIdx}`"
 						>
@@ -114,8 +116,8 @@
 </template>
 
 <script lang="ts" setup>
-import { ref, computed, watch, defineExpose } from 'vue'
-import previewImageTool from '@/views/components/tool/previewImageTool.vue'
+import { ref, computed, watch } from 'vue'
+import previewImageTool from '../../components/tool/previewImageTool.vue'
 import api from '../../../services/course'
 interface Props {
 	workArray?: object[] | null;
@@ -350,7 +352,9 @@ const changeShow = (op: any, idx: number) => {
 }
 
 const lookDetail = () => {
-  // emit('openChoiceQuestionDetail', props.slideIndex)
+  if (['45', '15'].includes(workDetail.value.type)) {
+    emit('openChoiceQuestionDetail', props.slideIndex)
+  }
 }
 
 const previewImageToolRef = ref<any>(null)

+ 524 - 92
src/views/Student/components/choiceQuestionDetailDialog.vue

@@ -1,46 +1,128 @@
 <template>
-   <div class="choiceQuestionDetailDialog">
-    <div class="content" :style="{
-          width: slideWidth + 'px',
-          height: slideHeight + 'px',
-        }">
-          <span class="closeIcon" @click="closeSlideIndex()">
-            <img src="../../../assets/img/close.png">
-          </span>
-        <div class="c_t45" v-if="workDetail && workDetail.type==='45' && props.showData">
-          <div class="c_t45_title">{{ props.showData.choiceQuestionListData[props.showData.workIndex].teststitle  }}</div>
-          <img class="c_t45_img" :src="props.showData.choiceQuestionListData[props.showData.workIndex].timuList[0].src" v-if="props.showData.choiceQuestionListData[props.showData.workIndex].timuList.length>0">
-          <span class="c_t45_type" v-if="props.showData.choiceQuestionListData[props.showData.workIndex].type==='1'">单选题</span>
-          <span class="c_t45_type" v-if="props.showData.choiceQuestionListData[props.showData.workIndex].type==='2'">多选项</span>
-          <div class="c_t45_echarts" :style="{
-            width: slideWidth-40 + 'px',
-          }">
-            <div id="echartsArea1" ref="echartsArea1"></div>
+  <div class="choiceQuestionDetailDialog">
+    <div
+      class="content"
+      :style="{
+        width: slideWidth + 'px',
+        height: slideHeight + 'px',
+      }"
+    >
+      <span class="closeIcon" @click="closeSlideIndex()">
+        <img src="../../../assets/img/close.png" />
+      </span>
+      <div
+        class="c_t45"
+        v-if="workDetail && workDetail.type === '45' && props.showData"
+      >
+        <div class="c_t45_title">
+          {{
+            props.showData.choiceQuestionListData[props.showData.workIndex]
+              .teststitle
+          }}
+        </div>
+        <img
+          class="c_t45_img"
+          :src="
+            props.showData.choiceQuestionListData[props.showData.workIndex]
+              .timuList[0].src
+          "
+          v-if="
+            props.showData.choiceQuestionListData[props.showData.workIndex]
+              .timuList.length > 0
+          "
+        />
+        <span
+          class="c_t45_type"
+          v-if="
+            props.showData.choiceQuestionListData[props.showData.workIndex]
+              .type === '1'
+          "
+          >单选题</span
+        >
+        <span
+          class="c_t45_type"
+          v-if="
+            props.showData.choiceQuestionListData[props.showData.workIndex]
+              .type === '2'
+          "
+          >多选项</span
+        >
+        <div
+          class="c_t45_echarts"
+          :style="{
+            width: slideWidth - 40 + 'px',
+          }"
+        >
+          <div id="echartsArea1" ref="echartsArea1"></div>
+        </div>
+      </div>
+
+      <div
+        class="c_t15"
+        v-if="workDetail && workDetail.type === '15' && props.showData"
+      >
+        <div class="c_t15_title">{{ workDetail.json.answerQ }}</div>
+        <span class="c_t15_type">问答题</span>
+        <div class="c_t15_content" v-show="!lookWorkData">
+          <div
+            class="c_t15_c_item"
+            v-for="item in workArray"
+            :key="item.id"
+            @click="lookWork(item.id)"
+          >
+            <div class="c_t15_c_i_top">
+              <span>S</span>
+              <div>{{ item.name }}</div>
+            </div>
+            <div class="c_t15_c_i_bottom">
+              <span>{{ item.content.answer }}</span>
+            </div>
+          </div>
+        </div>
+
+        <div class="c_t15_workDetail" v-if="lookWorkData">
+          <div class="c_t15_wd_top">
+            <img
+              src="../../../assets/img/arrow_left.png"
+              @click="lookWork('')"
+            />
+            <span>S</span>
+            <div>{{ lookWorkData.name }}</div>
+          </div>
+          <div class="c_t15_wd_content">
+            <span>{{ lookWorkData.content.answer }}</span>
+            <div class="c_t15_wd_c_imageList" v-if="lookWorkData.content.fileList.length > 0">
+              <img v-for="item in lookWorkData.content.fileList" :src="item.url" :key="item.uploadTime" @click="lookImage(item.url)"/>
+            </div>
           </div>
         </div>
       </div>
+    </div>
+    <previewImageTool ref="previewImageToolRef"/>
   </div>
+ 
 </template>
 
 <script setup lang="ts">
-import { computed, ref, watch, onUnmounted } from 'vue'
+import { computed, ref, watch, onUnmounted, nextTick } from 'vue'
 import * as echarts from 'echarts'
+import previewImageTool from '../../components/tool/previewImageTool.vue'
 const props = defineProps<{
-  visible: number[]
-  workIndex: number
-  slideWidth: number
-  slideHeight: number
-  slideIndex: number
-  showData:any
+  visible: number[];
+  workIndex: number;
+  slideWidth: number;
+  slideHeight: number;
+  slideIndex: number;
+  showData: any;
 }>()
 
 const emit = defineEmits<{
-  (e: 'update:visible', v: number[]): void
+  (e: 'update:visible', v: number[]): void;
 }>()
 
 const visible = computed({
   get: () => props.visible,
-  set: (v: number[]) => emit('update:visible', v)
+  set: (v: number[]) => emit('update:visible', v),
 })
 
 const workDetail = computed(() => {
@@ -50,6 +132,22 @@ const workDetail = computed(() => {
   return null
 })
 
+// 预览图片组件
+const previewImageToolRef = ref<any>(null)
+
+const workArray = computed(() => {
+  let _result = []
+  if (props.showData && props.showData.workArray) {
+    const _workArray = JSON.parse(JSON.stringify(props.showData.workArray))
+    _workArray.forEach((i: any) => {
+      i.content = JSON.parse(decodeURIComponent(i.content))
+    })
+    _result = _workArray
+  }
+
+  return _result
+})
+
 // 关闭对应的作业详细页面
 const closeSlideIndex = () => {
   visible.value = visible.value.filter((v) => v !== props.slideIndex)
@@ -58,22 +156,101 @@ const closeSlideIndex = () => {
 // 选择题图表div
 const echartsArea1 = ref<any>(null)
 
+// 查看的作业详细id
+const lookWorkDetail = ref<string>('')
+
+// 查看作业详细的数据
+const lookWorkData = computed(() => {
+  let _result = null
+
+  if (lookWorkDetail.value && workArray.value.length > 0) {
+    const _workFind = workArray.value.find(
+      (i: any) => i.id === lookWorkDetail.value
+    )
+    if (_workFind) {
+      _result = _workFind
+    }
+  }
+
+  return _result
+})
+
+
+// 查看图片
+const lookImage = (url: string) => {
+  if (previewImageToolRef.value) {
+    previewImageToolRef.value.previewImage(url)
+  }
+}
+
+// 查看作业
+const lookWork = (id: string) => {
+  lookWorkDetail.value = id
+}
+
 // 选择题图表实例
 const myChart = ref<any>(null)
 
+// resize防抖定时器
+let resizeTimer: ReturnType<typeof setTimeout> | null = null
+
+// 处理ECharts resize
+const handleChartResize = () => {
+  // 清除之前的定时器
+  if (resizeTimer) {
+    clearTimeout(resizeTimer)
+  }
+
+  resizeTimer = setTimeout(() => {
+    nextTick(() => {
+      if (
+        myChart.value &&
+        typeof myChart.value.resize === 'function' &&
+        !myChart.value.isDisposed()
+      ) {
+        try {
+          myChart.value.resize()
+        }
+        catch (e) {
+          // console.error('myChart resize error:', e)
+          // 如果resize失败,重新初始化图表
+          if (
+            props.showData &&
+            props.showData.workDetail &&
+            props.showData.workDetail.type === '45'
+          ) {
+            setEchartsArea1()
+          }
+        }
+      }
+    })
+  }, 200) // 防抖延迟200ms
+}
+
 // 设置选择题图表
 const setEchartsArea1 = () => {
-  if ( !myChart.value && echartsArea1.value) {
+  // 如果已有实例且未销毁,先销毁
+  if (myChart.value && !myChart.value.isDisposed()) {
+    myChart.value.dispose()
+  }
+
+  if (echartsArea1.value) {
     myChart.value = echarts.init(echartsArea1.value)
   }
-  else myChart.value = null
+  else {
+    myChart.value = null
+  }
+
   if (myChart.value) {
-    const _work = props.showData.choiceQuestionListData[props.showData.workIndex]
-    console.log(_work)
+    const _work =
+      props.showData.choiceQuestionListData[props.showData.workIndex]
+    // 修正版,处理xAxis.data内为图片对象的case,formatter始终只拿到src或自定义label,保证无[object Object]问题
     const option = {
-      tooltip: {},
+      tooltip: {
+        show: false, // 禁用鼠标移动到柱体时的内容显示
+      },
       yAxis: {
-        show: false // 不显示最左边的y轴
+        show: false, // 不显示最左边的y轴
       },
       xAxis: {
         data: [],
@@ -81,8 +258,58 @@ const setEchartsArea1 = () => {
           color: 'rgba(0, 0, 0, 0.9)',
           fontWeight: 600,
           fontSize: 18,
-          lineHeight: 24
-        }
+          lineHeight: 24,
+          interval: 0,
+          formatter: function(value: any, idx: number) {
+            // 如果是字符串且格式为JSON(图片),则解析处理
+            if (typeof value === 'string') {
+              try {
+                const obj = JSON.parse(value)
+                if (obj && typeof obj === 'object' && obj.imgType && obj.src) {
+                  return '{img' + idx + '|}'
+                }
+              }
+              catch (e) {
+                // 非JSON字符串,直接返回
+                return value
+              }
+              return value
+            }
+            // 兼容老格式(容错):value本身是对象,并有图片信息
+            if (
+              value &&
+              typeof value === 'object' &&
+              value.imgType &&
+              value.src
+            ) {
+              return '{img' + idx + '|}'
+            }
+            // 其他类型直接空
+            return ''
+          },
+          rich: (() => {
+            // 动态生成所有图片的 rich 格式
+            const richObj: any = {}
+            _work.choiceUser.forEach((op: any, idx: number) => {
+              if (
+                op.option &&
+                typeof op.option === 'object' &&
+                op.option.imgType &&
+                op.option.src
+              ) {
+                richObj['img' + idx] = {
+                  height: 40,
+                  width: 40,
+                  align: 'center',
+                  backgroundColor: {
+                    image: op.option.src,
+                  },
+                }
+              }
+            })
+            return richObj
+          })(),
+        },
       },
       series: [
         {
@@ -91,7 +318,7 @@ const setEchartsArea1 = () => {
           data: [],
           barWidth: '50%', // 柱体宽度缩小40%
           itemStyle: {
-            color: 'rgba(252, 207, 0, 1)'
+            color: 'rgba(252, 207, 0, 1)',
           },
           label: {
             show: true,
@@ -99,16 +326,33 @@ const setEchartsArea1 = () => {
             color: 'rgba(116, 139, 115, 1)',
             fontSize: 22,
             fontWeight: 500,
-            lineHeight: 24
-          }
-        }
-      ]
+            lineHeight: 24,
+          },
+        },
+      ],
     }
-    _work.choiceUser.forEach((i:any) => {
-      option.xAxis.data.push(i.option && typeof i.option === 'object' ? '图片' : i.option)
-      option.series[0].data.push(i.user.length)
+    _work.choiceUser.forEach((i: any, idx: number) => {
+      // 如果是图片,存src对象,否则为字符串
+      if (
+        i.option &&
+        typeof i.option === 'object' &&
+        i.option.imgType &&
+        i.option.src
+      ) {
+        (option.xAxis.data as any[]).push(
+          JSON.stringify({ imgType: i.option.imgType, src: i.option.src })
+        ) // 仅保留相关字段
+      }
+      else if (typeof i.option === 'string') {
+        (option.xAxis.data as any[]).push(i.option)
+      }
+      else {
+        (option.xAxis.data as any[]).push('')
+      }
+      (option.series[0].data as any[]).push(i.user.length)
     })
-    console.log('设置echarts')
+
+    console.log(option)
     // {
     //   title: {
     //     text: 'ECharts 入门示例'
@@ -128,65 +372,111 @@ const setEchartsArea1 = () => {
     // }
     myChart.value.setOption(option)
   }
-
 }
 
 // 监听选择题数据变化
-watch(() => props.showData, (newVal, oldVal) => {
-  if (newVal && newVal.choiceQuestionListData[newVal.workIndex] && props.showData.workDetail.type === '45') {
-    if (oldVal && newVal.choiceQuestionListData[newVal.workIndex] && oldVal.choiceQuestionListData[oldVal.workIndex]) {
-      if (JSON.stringify(newVal.choiceQuestionListData[newVal.workIndex]) !== JSON.stringify(oldVal.choiceQuestionListData[oldVal.workIndex])) {
-        setEchartsArea1()
+watch(
+  () => props.showData,
+  (newVal, oldVal) => {
+    if (
+      newVal &&
+      newVal.choiceQuestionListData[newVal.workIndex] &&
+      props.showData.workDetail.type === '45'
+    ) {
+      if (
+        oldVal &&
+        newVal.choiceQuestionListData[newVal.workIndex] &&
+        oldVal.choiceQuestionListData[oldVal.workIndex]
+      ) {
+        if (
+          JSON.stringify(newVal.choiceQuestionListData[newVal.workIndex]) !==
+          JSON.stringify(oldVal.choiceQuestionListData[oldVal.workIndex])
+        ) {
+          setEchartsArea1()
+        }
       }
     }
+    else {
+      myChart.value = null
+    }
+  },
+  { immediate: true }
+)
+
+// 监听作业变化
+watch(
+  () => props.showData?.workIndex,
+  (newVal) => {
+    if (
+      newVal &&
+      props.showData.workDetail &&
+      props.showData.workDetail.type === '45'
+    ) {
+      setEchartsArea1()
+    }
+    else {
+      myChart.value = null
+    }
   }
-  else {
-    myChart.value = null
-  }
-}, {immediate: true})
+)
 
-watch(() => props.showData?.workIndex, (newVal, oldVal) => {
-  if (newVal && props.showData.workDetail && props.showData.workDetail.type === '45') {
-    setEchartsArea1()
+// 监听echartsArea1变化
+watch(
+  () => echartsArea1.value,
+  (newVal) => {
+    if (
+      newVal &&
+      props.showData &&
+      props.showData.workDetail &&
+      props.showData.workDetail.type === '45'
+    ) {
+      setEchartsArea1()
+    }
+    else {
+      myChart.value = null
+    }
   }
-  else {
-    myChart.value = null
+)
+
+// 监听页面宽度变化
+watch(
+  () => props.slideWidth,
+  (newVal) => {
+    if (
+      newVal &&
+      props.showData &&
+      props.showData.workDetail &&
+      props.showData.workDetail.type === '45'
+    ) {
+      handleChartResize()
+    }
   }
-  
-})
+)
 
-watch(() => echartsArea1.value, (newVal, oldVal) => {
-  if (newVal && props.showData && props.showData.workDetail && props.showData.workDetail.type === '45') {
-    setEchartsArea1()
+// 组件卸载时清理ECharts实例
+onUnmounted(() => {
+  // 清除定时器
+  if (resizeTimer) {
+    clearTimeout(resizeTimer)
+    resizeTimer = null
   }
-  else {
+
+  // 销毁ECharts实例
+  if (myChart.value && !myChart.value.isDisposed()) {
+    myChart.value.dispose()
     myChart.value = null
   }
 })
-
-watch(() => props.slideWidth, (newVal) => {
-  // if (newVal && props.showData.workDetail && props.showData.workDetail.type === '45') {
-  //   console.log('宽度变化', newVal)
-  //   if (myChart.value) {
-  //     myChart.value.resize()
-  //   }
-  // setTimeout(() => {
-  //   setEchartsArea1()
-  // }, 1000)
-  // }
-})
-
-
 </script>
 
 <style lang="scss" scoped>
-.choiceQuestionDetailDialog{
+.choiceQuestionDetailDialog {
   background: none;
   position: relative;
   width: 100%;
   height: 100%;
   z-index: 1;
-  .content{
+  .content {
     width: 100%;
     height: 100%;
     position: relative;
@@ -194,59 +484,201 @@ watch(() => props.slideWidth, (newVal) => {
     box-sizing: border-box;
     padding: 40px;
     overflow: auto;
-    .closeIcon{
+    .closeIcon {
       position: absolute;
       right: 20px;
       top: 20px;
       cursor: pointer;
       width: 20px;
       height: 20px;
-      img{
+      img {
         width: 100%;
         height: 100%;
       }
     }
-    .c_t45{
+    .c_t45 {
       width: 100%;
       min-height: 100%;
       display: flex;
       align-items: center;
       flex-direction: column;
       height: auto;
-      .c_t45_title{
+      .c_t45_title {
         color: rgba(0, 0, 0, 0.9);
         font-weight: 600;
-        font-size: 20px;
+        font-size: 24px;
         line-height: 24px;
       }
-      .c_t45_img{
+      .c_t45_img {
         max-width: 200px;
         object-fit: cover;
         margin-top: 20px;
       }
-      .c_t45_type{
+      .c_t45_type {
         font-weight: 400;
         font-size: 15px;
         line-height: 21px;
-        letter-spacing: .5px;
+        letter-spacing: 0.5px;
         color: rgba(0, 0, 0, 1);
-        opacity: .5;
+        opacity: 0.5;
         margin-top: 20px;
       }
 
-      .c_t45_echarts{
+      .c_t45_echarts {
         width: 100%;
         flex: 1;
         min-height: 400px;
         display: flex;
         align-items: center;
         box-sizing: border-box;
-        &>div{
+        & > div {
           width: 100%;
           height: 400px;
         }
       }
     }
+    .c_t15 {
+      width: 100%;
+      min-height: 100%;
+      display: flex;
+      align-items: center;
+      flex-direction: column;
+      height: auto;
+      padding: 40px;
+      .c_t15_title {
+        color: rgba(0, 0, 0, 0.9);
+        font-weight: 600;
+        font-size: 24px;
+        line-height: 24px;
+      }
+      .c_t15_type {
+        font-weight: 400;
+        font-size: 15px;
+        line-height: 21px;
+        letter-spacing: 0.5px;
+        color: rgba(0, 0, 0, 1);
+        opacity: 0.5;
+        margin-top: 20px;
+      }
+      .c_t15_content {
+        width: 100%;
+        height: auto;
+        display: grid;
+        grid-template-columns: repeat(4, 1fr);
+        gap: 20px;
+        margin-top: 40px;
+
+        .c_t15_c_item {
+          width: 100%;
+          height: auto;
+          box-shadow: 2px 4px 20px 0px rgba(0, 0, 0, 0.2);
+          box-sizing: border-box;
+          border-radius: 12px;
+          padding: 16px;
+          background: rgba(255, 255, 255, 0.6);
+          transition: 0.3s;
+          cursor: pointer;
+          &:hover {
+            box-shadow: 4px 4px 14px 0px rgba(252, 207, 0, 0.5);
+            background: rgba(255, 255, 255, 0.6);
+          }
+          .c_t15_c_i_top {
+            display: flex;
+            align-items: center;
+            gap: 10px;
+            & > span {
+              display: block;
+              width: 25px;
+              height: 25px;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              background: rgba(252, 207, 0, 1);
+              border-radius: 4px;
+              color: rgba(255, 255, 255, 1);
+              font-weight: bold;
+              font-size: 14px;
+            }
+            & > div {
+              color: rgba(0, 0, 0, 0.7);
+              font-weight: 800;
+            }
+          }
+          .c_t15_c_i_bottom {
+            margin-top: 15px;
+            font-weight: 300;
+            font-size: 14px;
+            height: 40px;
+            max-width: 100%;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            display: -webkit-box;
+            -webkit-line-clamp: 2;
+            -webkit-box-orient: vertical;
+          }
+        }
+      }
+      .c_t15_workDetail {
+        width: 100%;
+        flex: 1;
+        min-height: 400px;
+        margin-top: 40px;
+        box-shadow: 4px 4px 14px 0px rgba(252, 207, 0, 0.5);
+        box-sizing: border-box;
+        padding: 16px;
+        border-radius: 12px;
+        display: flex;
+        flex-direction: column;
+        .c_t15_wd_top{
+          width: 100%;
+          display: flex;
+          align-items: center;
+          gap: 15px;
+          &>img{
+            width: 25px;
+            height: 25px;
+            cursor: pointer;
+          }
+          & > span {
+              display: block;
+              width: 30px;
+              height: 30px;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              background: rgba(252, 207, 0, 1);
+              border-radius: 4px;
+              color: rgba(255, 255, 255, 1);
+              font-weight: bold;
+              font-size: 16px;
+            }
+            & > div {
+              color: rgba(0, 0, 0, 0.7);
+              font-weight: 800;
+              font-size: 18px;
+            }
+        }
+        .c_t15_wd_content{
+          width: 100%;
+          margin-top: 20px;
+          max-height: 100%;
+          overflow: auto;
+          flex-wrap: wrap;
+          .c_t15_wd_c_imageList{
+            width: 100%;
+            gap: 20px;
+            margin-top: 20px;
+            &>img{
+              width: 100px;
+              height: auto;
+              cursor: pointer;
+              margin-right: 20px;
+              object-fit: cover;
+            }
+          }
+        }
+      }
+    }
   }
 }
-</style>
+</style>