Browse Source

Merge branch 'develop' of http://52.130.252.100:10000/TEAMMODEL/TEAMModelOS into develop

CrazyIter_Bin 9 months ago
parent
commit
3f7bdc4625

+ 11 - 3
TEAMModelOS.Function/IESServiceBusTrigger.cs

@@ -2608,7 +2608,7 @@ namespace TEAMModelOS.Function
                                 //List<dynamic> users = new List<dynamic>();
                                 //if (jointEventSchedule.type.Equals("exam"))
                                 //{
-                                //    //一般競賽 ※挑戰賽 名單架構確定後再取
+                                //    //一般競賽 ※賽 名單架構確定後再取
                                 //    if (jointEventSchedule.examType.Equals("regular"))
                                 //    {
                                 //        await foreach (var jc in client.GetContainer(Constant.TEAMModelOS, Constant.Teacher).GetItemQueryStreamIteratorSql(queryText: $"SELECT DISTINCT c.creatorId, c.creatorName, c.creatorEmail FROM c WHERE c.jointEventId = '{jointEvent.id}' ", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"JointCourse") }))
@@ -2661,11 +2661,19 @@ namespace TEAMModelOS.Function
                             case "finish":
                                 string pk = string.Format("{0}{1}{2}{3}{4}{5}{6}", "JointEvent", "-", $"{jointEventId}", "-", "schedule", "-", "going");
                                 await table.DeleteSingle<ChangeRecord>(pk, jointEventSchedule.id);
-                                //寄發Email
+                                //寄發Email [待模板ID]
                                 //熱身賽行程結束、決賽名單計算
                                 if(jointEventSchedule.type.Equals("exam") && jointEventSchedule.examType.Equals("regular"))
                                 {
-
+                                    string scope = "private"; //先寫死為個人
+                                    List<string> jointGroupIds = jointEvent.groups.Select(g => g.id).ToList();
+                                    if (jointGroupIds.Count > 0)
+                                    {
+                                        foreach (string jointGroupId in jointGroupIds)
+                                        {
+                                            List<JointEventGroupDb> addResult = await JointService.CreatePassJointCourseBySchedule(client, $"{jointEventId}", jointGroupId, jointEventSchedule.id, scope);
+                                        }
+                                    }
                                 }
 
                                 break;

+ 1 - 0
TEAMModelOS/ClientApp/public/lang/en-US.js

@@ -3333,6 +3333,7 @@ const LANG_EN_US = {
             doubleGreen: 'Double Green Lesson',
             singleGreen: 'Single Green Lesson',
             doubleRed: 'Double Red Lesson',
+            doubleGray: 'Double gray light lesson',
             classCount: 'Total Classes',
             type: 'Type',
             cate: 'Lesson Category',

+ 1 - 0
TEAMModelOS/ClientApp/public/lang/zh-CN.js

@@ -3332,6 +3332,7 @@ const LANG_ZH_CN = {
             doubleGreen: '双绿灯课例',
             singleGreen: '单绿灯课例',
             doubleRed: '双红灯课例',
+            doubleGray: '双灰灯课例',
             classCount: '班级总数',
             type: '类型',
             cate: '课例类别',

+ 1 - 0
TEAMModelOS/ClientApp/public/lang/zh-TW.js

@@ -3335,6 +3335,7 @@ const LANG_ZH_TW = {
             doubleGreen: '雙綠燈課堂',
             singleGreen: '單綠燈課堂',
             doubleRed: '雙紅燈課堂',
+            doubleGray: '雙灰燈課例',
             classCount: '班級總數',
             type: '類型',
             cate: '課堂類別',

+ 2 - 1
TEAMModelOS/ClientApp/src/view/htcommunity/htMgtExam.vue

@@ -674,8 +674,9 @@ export default {
 		},
 		// 點擊左側個人評量列表項目的動作
 		selectEvaluation(index) {
+			debugger
 			this.checkScoreSave(this.toEvaluation, index);
-			this.$EventBus.$emit("onEvaChange", this.evaListShow[index]);
+			this.$EventBus.$emit("onEvaChange", this.htEvaListShow[index]);
 		},
 		// 
 		toEvaluation(index) {

+ 588 - 0
TEAMModelOS/ClientApp/src/view/learnactivity/byStu/htByStuMark.vue

@@ -0,0 +1,588 @@
+<template>
+  <div class="paper-score-container">
+    <Loading :top="200" type="1" style="text-align:center" v-show="dataLoading"></Loading>
+    <div class="scoring-paper-header">
+      <div>
+        <span class="base-info-item">
+          {{$t('learnActivity.score.stuName')}}
+          <span class="analysis-info">{{studentAnswer.name}}</span>
+        </span>
+        <span class="base-info-item">
+          {{$t('learnActivity.score.score')}}
+          <span class="analysis-info">
+            {{scoreTotal}}{{$t('learnActivity.score.scoreUnit')}}
+          </span>
+        </span>
+        <!-- 保存分数 -->
+        <span :class="['base-info-btn', isUpd ? 'base-info-btn-active' : '']" type="success" @click="saveScore">
+          <Icon type="ios-albums-outline" />
+          {{$t('learnActivity.score.saveScore')}}
+        </span>
+        <!-- 未阅满分 -->
+        <span class="base-info-btn" @click="batchScore('zero')">
+          <Icon type="ios-create" />
+          {{$t('learnActivity.score.fastScore4')}}
+        </span>
+        <!-- 未阅零分 -->
+        <span class="base-info-btn" @click="batchScore('full')">
+          <Icon type="ios-create" />
+          {{$t('learnActivity.score.fastScore5')}}
+        </span>
+        <span v-show="examInfo.source === '2'" class="base-info-btn" type="success" @click="viewOriginal">
+          <Icon type="md-eye" />
+          {{$t('learnActivity.score.viewOrigin')}}
+        </span>
+        <span class="base-info-btn" @click="config.showAnswer = !config.showAnswer">
+          <Icon :type="config.showAnswer ? 'md-eye-off':'md-eye'" />
+          {{ config.showAnswer ? $t('learnActivity.score.hideAns') : $t('learnActivity.score.showAns')}}
+        </span>
+        <span class="base-info-btn" @click="config.showQu = !config.showQu">
+          <Icon :type="config.showQu ? 'md-eye-off':'md-eye'" />
+          {{ config.showQu ? $t('learnActivity.score.hideQu') : $t('learnActivity.score.showQu')}}
+        </span>
+      </div>
+      <!-- 题号 -->
+      <div class="question-index-box" v-if="studentAnswer.scores">
+        <div class="base-info-item" style="white-space: nowrap;">{{$t('learnActivity.score.quIndex')}}</div>
+        <div>
+          <span v-for="quNoItem in quNoList" :key="quNoItem.label" :class="studentAnswer.scores[quNoItem.index] >= 0 ? 'qu-order-tag':'qu-order-tag-def'">
+            {{quNoItem.label}}
+          </span>
+        </div>
+      </div>
+      <!-- 渲染题目和打分相关UI -->
+      <template v-if="!isComplete && studentAnswer.stuAnswers && studentAnswer.stuAnswers.length">
+        <!-- 默认题目顺序 -->
+        <template v-if="itemSort == 1">
+          <div v-for="(quItem) in defaultList" :key="quItem.quNo" ref="mathJaxContainer">
+            <template v-if="quItem.children && quItem.children.length">
+              <QuAndScore :ref="'QuAndScore'+ child.index" :stuMark="studentAnswer.mark[child.index]" @click.native="config.activeIndex = child.index" v-model="studentAnswer.scores[child.index]" v-for="(child) in quItem.children" :key="child.quNo" :stuAnswer="studentAnswer.stuAnswers[child.index]" :questionItem="child" :config="config" @on-save-mark="saveMark" @on-del-mark="delMark"></QuAndScore>
+            </template>
+            <template v-else>
+              <QuAndScore :ref="'QuAndScore'+ quItem.index" :stuMark="studentAnswer.mark[quItem.index]" @click.native="config.activeIndex = quItem.index" v-model="studentAnswer.scores[quItem.index]" :questionItem="quItem" :config="config" :stuAnswer="studentAnswer.stuAnswers[quItem.index]" @on-save-mark="saveMark" @on-del-mark="delMark"></QuAndScore>
+            </template>
+          </div>
+        </template>
+        <!-- 按题型排序 -->
+        <template v-else>
+          <div v-for="(typeItem) in groupList" :key="typeItem.type" ref="mathJaxContainer">
+            <p class="type-name">
+              {{ $tools.getChineseByNum(getLatestTypeIndex(typeItem.type) + 1) }}
+              {{ exersicesType[typeItem.type] }}
+              ({{ typeItem.score || 0 }} {{$t('learnActivity.score.scoreUnit')}})
+            </p>
+            <div v-for="(quItem) in typeItem.list" :key="quItem.quNo">
+              <template v-if="quItem.children && quItem.children.length">
+                <QuAndScore :ref="'QuAndScore'+ child.index" :stuMark="studentAnswer.mark[child.index]" @click.native="config.activeIndex = child.index" v-model="studentAnswer.scores[child.index]" v-for="(child) in quItem.children" :key="child.quNo" :stuAnswer="studentAnswer.stuAnswers[child.index]" :questionItem="child" :config="config" @on-save-mark="saveMark" @on-del-mark="delMark"></QuAndScore>
+              </template>
+              <template v-else>
+                <QuAndScore :ref="'QuAndScore'+ quItem.index" :stuMark="studentAnswer.mark[quItem.index]" @click.native="config.activeIndex = quItem.index" v-model="studentAnswer.scores[quItem.index]" :questionItem="quItem" :config="config" :stuAnswer="studentAnswer.stuAnswers[quItem.index]" @on-save-mark="saveMark" @on-del-mark="delMark"></QuAndScore>
+              </template>
+            </div>
+          </div>
+        </template>
+      </template>
+      <div v-show="isComplete" class="complete-score-box">
+        <Icon class="complete-icon" type="md-checkmark-circle" />
+        <p class="complete-stu-info">{{studentAnswer.name}} ({{scoreTotal}}{{$t('learnActivity.score.scoreUnit')}})</p>
+        <p class="complete-next" @click="nextStu">{{$t('learnActivity.score.nextStu')}}</p>
+      </div>
+    </div>
+    <Modal v-model="originalStatus" :title="$t('learnActivity.score.viewOrigin')" :width="900" footer-hide>
+      <img v-for="item in sourceList" :src="item.url" :key="item.name" width="850">
+      <EmptyData v-show="!sourceList.length"></EmptyData>
+    </Modal>
+  </div>
+</template>
+
+<script>
+import QuAndScore from "./QuAndScore.vue"
+import BlobTool from '@/utils/blobTool.js'
+export default {
+  components: {
+    QuAndScore
+  },
+  props: {
+    examId: {
+      type: String,
+      default: '',
+      required: true
+    },
+    examInfo: {
+      type: Object,
+      default: () => {
+        return {}
+      }
+    },
+    subjectId: {
+      type: String,
+      default: '',
+      required: true
+    },
+    paper: {
+      type: Object,
+      default: () => { }
+    },
+    studentInfo: {
+      type: Object,
+      default: () => { }
+    },
+    defaultIndex: {
+      type: Number,
+      default: 0
+    }
+  },
+  data() {
+    let _this = this
+    return {
+      originalStatus: false,
+      isComplete: false,
+      exersicesType: this.$GLOBAL.EXERCISE_TYPES(),
+      typeList: ['single', 'multiple', 'judge', 'complete', 'subjective', 'connector', 'correct', 'compose'],
+      itemSort: 1,
+      quNoList: [],
+      sourceList: [],
+      dataLoading: false,
+      paperScore: [],
+      groupList: [],
+      defaultList: [],
+      studentAnswer: {},
+      config: {
+        showAnswer: false,
+        showQu: false,
+        activeIndex: -1,
+        examInfo: _this.examInfo,
+        stuInfo: _this.studentInfo,
+        subjectId: _this.subjectId,
+      },
+      isUpd: false
+    }
+  },
+  computed: {
+    scoreTotal() {
+      if (this.studentAnswer && this.studentAnswer.scores) {
+        let s = this.studentAnswer.scores.reduce((prev, cur) => {
+          cur = cur < 0 ? 0 : cur
+          return prev + cur
+        }, 0)
+        return s
+      }
+      return 0
+    },
+  },
+  watch: {
+    examInfo: {
+      immediate: true,
+      handler(n, o) {
+        if (n) {
+          this.config.examInfo = n
+          this.config.subjectId = this.subjectId
+        }
+      }
+    },
+    paper: {
+      handler(newPaper, oldValue) {
+        this.handleInitPaper(newPaper)
+      },
+      deep: true,
+      immediate: true
+    },
+    studentInfo: {
+      async handler(newValue, oldValue) {
+        this.studentAnswer = {}
+        this.studentAnswer = this._.cloneDeep(newValue || {})
+        this.isComplete = false
+        //处理默认显示批注
+        this.markFlag = {}
+        if (this.studentAnswer.mark) {
+          //后端阅卷批注数据异常,暂时这里过滤处理
+          for (let i = 0; i < this.studentAnswer.mark.length; i++) {
+            if (this.studentAnswer.mark[i]) {
+              this.studentAnswer.mark[i] = this.studentAnswer.mark[i].filter(m => {
+                return !!m.mark
+              })
+            }
+          }
+        }
+        if (!this.studentAnswer.status && this.studentAnswer.answers && this.studentAnswer.scores) {
+          if (this.studentAnswer.answers.length) {
+            let sourceBlob = this.studentAnswer.answers[0].replace('ans.json', 'source')
+            this.$set(this.studentAnswer, 'sourceBlob', sourceBlob)
+            try {
+              let sas = this.examInfo.scope == 'school' ? this.$store.state.user.schoolProfile.blob_sas : this.$store.state.user.userProfile.blob_sas
+              let blobUrl = this.examInfo.scope == 'school' ? JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri : JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri
+              /**
+               * 1、批注逻辑调整,每个题目的批注单独保存成图片;
+               * 2、这里就直接读取原始作答数据;
+               * 3、在渲染的时候再判断当前题目是否有批注数据。
+               */
+              let urlPrefix = `${blobUrl}/exam/${this.studentAnswer.answers[0].replace('/ans.json', '')}`
+              let fullUrl = `${blobUrl}/exam/${this.studentAnswer.answers[0]}?${sas}`
+              let ansRes = await this.$jsFn.handleStudentAnswer(fullUrl, urlPrefix, sas)
+              // 问答题:课中的地址需截取文件名称重新拼接地址,因此统一重新拼接处理
+              ansRes  = ansRes.map((item, index) => {
+                if(this.quNoList[index].type === 'subjective' && this.quNoList[index].answerType && this.quNoList[index].answerType != 'text' && item.length) {
+                  let name = item[0].substr(item[0].lastIndexOf(`/${this.studentInfo.id}/`) + (this.studentInfo.id.length + 2))
+                  // 暂时处理切换学生带来的字符串重复拼接问题
+                  if(name.includes('/')){
+                    return
+                  }
+                  item = this.quNoList[index].answerType === 'file' ? [name] : [`${blobUrl}/exam/${this.examId}/${this.subjectId}/${this.studentInfo.id}/${name}?${sas}`]
+                }
+                return item
+              })
+              this.$set(this.studentAnswer, 'stuAnswers', ansRes)
+            } catch (e) {
+              let full = []
+              let i = 0
+              while (i < this.paperInfo.item.length) {
+                full.push(`<p style="color:red">${this.$t('learnActivity.score.ansErr')}</p>`)
+                i++
+              }
+              this.$set(this.studentAnswer, 'stuAnswers', full)
+            }
+          } else {
+            let a = this.studentAnswer.scores.map(item => {
+              return []
+            })
+            this.$set(this.studentAnswer, 'stuAnswers', a)
+          }
+          this.studentAnswer.status = true
+        }
+        // 切换学生后,上面步骤会触发studentAnswer.scores的监控事件,需重置编辑(isUpd)状态,否则会下一次切换学生时保存成绩
+        this.isUpd = false
+      },
+      deep: true,
+      immediate: true
+    },
+    "studentAnswer.scores": {
+      handler(n, o) {
+        if (n && o && n.length === o.length) {
+          this.isUpd = true
+        }
+      }
+    },
+    'config.showQu' : {
+      handler(n, o) {
+        if(n) {
+          window.MathJax.startup.promise.then(() => {
+						window.MathJax.typesetPromise([this.$refs.mathJaxContainer])
+					})
+        }
+      }
+    },
+  },
+  methods: {
+    handleInitPaper(newPaper) {
+      this.paperInfo = this._.cloneDeep(newPaper)
+      let that = this
+      this.groupList = []
+      this.defaultList = []
+      this.paperScore = []
+      this.quNoList = []
+      this.itemSort = newPaper?.itemSort
+      if (newPaper && newPaper.item) {
+        this.dataLoading = true
+        if (newPaper.item.length) {
+          let index = 0 //题目索引
+          let quIndex = 1 // 题号
+          newPaper.item.forEach((i) => {
+            //记录题目原始位置
+            if (i.type == 'compose') {
+              i.children.forEach((cItem, cIndex) => {
+                cItem.quNo = `${quIndex}-${cIndex + 1}`
+                cItem.index = index++
+                this.paperScore.push(cItem.score || 0)
+              })
+              quIndex++
+            } else {
+              i.quNo = (quIndex++).toString()
+              i.index = index++
+              this.paperScore.push(i.score || 0)
+            }
+            if (!i.score) i.score = 0
+          })
+          //默认排序
+          if (this.itemSort == 1) {
+            this.defaultList = newPaper.item
+            this.defaultList.forEach(item => {
+              if (item.children && item.children.length) {
+                item.children.forEach(child => {
+                  this.quNoList.push({
+                    label: child.quNo,
+                    index: child.index,
+                    type: child.type,
+                    answerType: child?.answerType || undefined
+                  })
+                })
+              } else {
+                this.quNoList.push({
+                  label: item.quNo,
+                  index: item.index,
+                  type: item.type,
+                  answerType: item?.answerType || undefined
+                })
+              }
+            })
+          }
+
+          // 处理试卷内题目按照题型排序
+          else {
+            this.typeList.forEach(item => {
+              this._.mapKeys(this._.groupBy(newPaper.item, 'type'), function (value, key) {
+                if (key === item) { /* 按照题型排序,并且计算每种题型的总分 */
+                  that.groupList.push({
+                    type: key,
+                    list: value,
+                    score: value.reduce((p, e) => parseFloat(p) + parseFloat(e.score), 0)
+                  })
+                }
+              })
+            })
+            this.groupList.forEach((typeItem, typeIndex) => {
+              if (typeItem.list) {
+                typeItem.list.forEach((quItem, quIndex) => {
+                  if (quItem.children && quItem.children.length) {
+                    quItem.children.forEach(child => {
+                      this.quNoList.push({
+                        label: child.quNo,
+                        index: child.index,
+                        type: child.type,
+                        answerType: child?.answerType || undefined
+                      })
+                    })
+                  } else {
+                    this.quNoList.push({
+                      label: quItem.quNo,
+                      index: quItem.index,
+                      type: quItem.type,
+                      answerType: quItem?.answerType || undefined
+                    })
+                  }
+                })
+              }
+            })
+          }
+        }
+        this.dataLoading = false
+      }
+    },
+    getLatestTypeIndex(type) {
+      let arr = []
+      this.groupList.forEach(i => {
+        if (i.list.length) {
+          arr.push(i.type)
+        }
+      })
+      return arr.indexOf(type)
+    },
+    //点评下一位学生
+    nextStu() {
+      this.$emit('nextStu')
+    },
+    // 查阅原卷
+    viewOriginal() {
+      console.log(`output->this.studentAnswer`, this.studentAnswer)
+      if (!this.studentAnswer.sourceBlob) {
+        this.$Message.warning(this.$t('learnActivity.score.noStuAnswer'))
+        return
+      }
+      let sas = this.examInfo.scope == 'school' ? this.$store.state.user.schoolProfile.blob_sas : this.$store.state.user.userProfile.blob_sas
+      let blobUrl = this.examInfo.scope == 'school' ? JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri : JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri
+      let host = blobUrl.substring(0, blobUrl.lastIndexOf('/'))
+      let cont = blobUrl.substring(blobUrl.lastIndexOf('/') + 1)
+      let blobTool = new BlobTool(host, cont, '?' + sas, this.examInfo.scope)
+      let path = `exam/${this.studentAnswer.sourceBlob}` //原卷路径
+      this.sourceList = []
+      blobTool.listBlob({
+        prefix: path
+      }).then(
+        res => {
+          if (res.blobList && res.blobList.length) {
+            res.blobList.forEach(item => {
+              item.url = item.url + '?' + sas
+            })
+            this.sourceList = res.blobList
+          }
+        },
+        err => {
+          console.log(`output->err`, err)
+        }
+      )
+      console.log(`output->sourceList`, this.sourceList)
+      this.originalStatus = true
+    },
+    /**
+     * type  full未阅满分 zero未阅零分
+     */
+    batchScore(type) {
+      if (!this.studentAnswer.scores.includes(-1)) {
+        this.$Message.warning({
+          content: this.$t('learnActivity.score.noQuMark'),
+          duration: 2
+        })
+        return
+      }
+      if (type === 'full') {
+        for (let i = 0; i < this.studentAnswer.scores.length; i++) {
+          if (this.studentAnswer.scores[i] === -1) {
+            this.$set(this.studentAnswer.scores, i, this.paperScore[i] || 0)
+          }
+        }
+      } else if (type === 'zero') {
+        for (let i = 0; i < this.studentAnswer.scores.length; i++) {
+          if (this.studentAnswer.scores[i] === -1) {
+            this.$set(this.studentAnswer.scores, i, 0)
+          }
+        }
+      }
+    },
+    //保存学生分数
+    saveScore() {
+      if (!this.isUpd) {
+        this.$Message.warning(this.$t('learnActivity.score.noUpd'))
+        return
+      }
+      let d = this._.cloneDeep(this.studentAnswer)
+      this.examInfo.stuLists
+      let requestData = {
+        "id": this.examId,
+        "code": this.examInfo.owner == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
+        "point": [this.studentAnswer.scores],
+        "studentId": [{
+          id: this.studentAnswer.id,
+          type: this.studentAnswer.type
+        }],
+        "classId": this.studentAnswer.classId,
+        "school": this.examInfo.scope == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
+        // "school": this.$store.state.userInfo.schoolCode,
+        "subjectId": this.subjectId
+      }
+      this.$api.learnActivity.UpsertAllRecord(requestData).then(res => {
+        if (!res.error) {
+          this.$Message.success(this.$t('learnActivity.score.saveScoreOk'))
+          this.isUpd = false
+          this.$emit('updScore', [d])
+          // 切换学生时保存成绩,不需要展示下一位学生页面
+          if (!this.studentAnswer.scores.includes(-1) && this.studentInfo.id === requestData.studentId[0].id) {
+            this.isComplete = true
+          }
+          if (res.isScore) this.$EventBus.$emit('onStatusChange')
+        } else {
+          this.$Message.error(this.$t('learnActivity.score.saveSocreErr'))
+        }
+      })
+    },
+    delMark(index) {
+      this.$Modal.confirm({
+        title: this.$t('learnActivity.score.delMark'),
+        content: this.$t('learnActivity.score.delMarkContent'),
+        onOk: () => {
+          let mark = this.studentAnswer.mark[index]?.find(item => {
+            return item.tmdId == this.$store.state.userInfo.TEAMModelId
+          })
+          let blob = mark ? mark.mark : ""
+          //保存批注数据
+          this.$api.learnActivity.delAnswer({
+            "id": this.examId,
+            "studentId": this.studentAnswer.id,
+            "subjectId": this.subjectId,
+            "classId": this.studentAnswer.classId,
+            "code": this.examInfo.scope == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
+            "tmdId": this.$store.state.userInfo.TEAMModelId,
+            "index": index,//题号
+            "blob": blob
+          }).then(
+            res => {
+              this.$Message.success(this.$t('learnActivity.mark.deleteOk'))
+              let i = this.studentAnswer.mark[index].findIndex(item => item.tmdId === this.$store.state.userInfo.TEAMModelId)
+              if (i > -1) {
+                this.studentAnswer.mark[index].splice(i, 1)
+                this.$set(this.studentAnswer.mark, index, this._.cloneDeep(this.studentAnswer.mark[index]))
+              }
+              console.log('**********', this.studentAnswer)
+            },
+            err => {
+              this.$Message.error(this.$t('learnActivity.mark.deleteErr'))
+            }
+          )
+        }
+      })
+    },
+    // 保存批注
+    // data: {markInfo,quIndex}
+    saveMark(data) {
+      let { markInfo, quIndex } = data
+      let fileName, hasMarked
+      if (this.studentAnswer.mark && this.studentAnswer.mark[quIndex]) {
+        let d = this._.cloneDeep(this.studentAnswer.mark[quIndex])
+        let markData = d.find(item => {
+          return item.tmdId == this.$store.state.userInfo.TEAMModelId
+        })
+        if (markData) {
+          let blob = markData.mark
+          fileName = blob.substring(blob.lastIndexOf('/') + 1, blob.length)
+          hasMarked = true
+        } else {
+          fileName = this.$jsFn.uuid() + '.png'
+        }
+      } else {
+        fileName = this.$jsFn.uuid() + '.png'
+      }
+      let markPng = this.$jsFn.dataURLtoFile(markInfo.base64, fileName)
+      //保存批注图片
+      let sas = this.examInfo.scope == 'school' ? this.$store.state.user.schoolProfile.blob_sas : this.$store.state.user.userProfile.blob_sas
+      let blobUrl = this.examInfo.scope == 'school' ? JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri : JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri
+      let host = blobUrl.substring(0, blobUrl.lastIndexOf('/'))
+      let cont = blobUrl.substring(blobUrl.lastIndexOf('/') + 1)
+      let blobTool = new BlobTool(host, cont, '?' + sas, this.examInfo.scope)
+      let path = `exam/${this.examId}/${this.subjectId}/${this.studentAnswer.id}`
+      blobTool.upload(markPng, { path }).then(
+        res => {
+          if (!hasMarked) {
+            this.studentAnswer.mark[quIndex].push({
+              mark: path + '/' + fileName,
+              tmdId: this.$store.state.userInfo.TEAMModelId
+            })
+          }
+          //保存批注数据
+          this.$api.learnActivity.upsertAnswer({
+            "id": this.examId,
+            "studentId": this.studentAnswer.id,
+            "subjectId": this.subjectId,
+            "classId": this.studentAnswer.classId,
+            "code": this.examInfo.scope == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
+            "tmdId": this.$store.state.userInfo.TEAMModelId,
+            "index": quIndex,//题号
+            "mark": {
+              sc: 0,//分数
+              tmdId: this.$store.state.userInfo.TEAMModelId,
+              mark: path + '/' + fileName,//批注BLOB地址
+              identity: 'admin',//老师身份,这是是固定管理员身份
+              index: quIndex //题号
+            }
+          }).then(
+            res => {
+              this.$Message.success(this.$t('learnActivity.score.markOk'))
+              let r = 'QuAndScore' + quIndex
+              console.log(this.$refs['QuAndScore5'])
+              console.log(this.$refs[r])
+              this.$refs[r][0]?.saveMarkOk()
+            },
+            err => {
+              this.$Message.error(this.$t('learnActivity.score.markErr'))
+            }
+          )
+        },
+        err => {
+          this.$Message.error(this.$t('learnActivity.mark.saveErr'))
+        }
+      )
+    },
+  }
+}
+</script>
+
+<style scoped lang="less">
+@import "./ByStuMark.less";
+</style>

+ 50 - 23
TEAMModelOS/ClientApp/src/view/learnactivity/tabs/htAnswerTable.vue

@@ -137,9 +137,9 @@
 						</Option>
 					</Select>
 					<!-- 导出表格 -->
-					<Button size="small" @click="exportData" :loading="exportLoading" custom-icon="iconfont icon-download" type="primary" v-show="examInfo.progress != 'pending' && !isMarkView" class="export-btn">
+					<!-- <Button size="small" @click="exportData" :loading="exportLoading" custom-icon="iconfont icon-download" type="primary" v-show="examInfo.progress != 'pending' && !isMarkView" class="export-btn">
 						{{ exportLoading ? $t("learnActivity.score.exporting") : $t("learnActivity.score.exportData") }}
-					</Button>
+					</Button> -->
 					<!-- 切换打分UI -->
 					<span class="common-icon-text" @click="toggleScoreStatus">
 						<Icon :custom="isMarkView ? 'iconfont icon-table' : 'iconfont icon-scoring'" size="14" />
@@ -152,9 +152,9 @@
 					</span>
 				</div>
 				<!-- 按人批阅 -->
-				<ByStuMark ref="byStuMark" v-if="markType == 'byStu'" :examInfo="examInfo" :defaultIndex="defaultIndex" :paper="paperInfo" :studentInfo="chooseStudent" :subjectId="chooseSubject" @updScore="updScore" @nextStu="getNextStu"></ByStuMark>
+				<ByStuMark ref="byStuMark" v-if="markType == 'byStu'" :examInfo="examInfo" :defaultIndex="defaultIndex" :paper="paperInfo" :studentInfo="chooseStudent" :subjectId="subjectIdForMark" @updScore="updScore" @nextStu="getNextStu" :examId="examIdForMark"></ByStuMark>
 				<!-- 按题批阅 -->
-				<ByQuMark v-else-if="markType == 'byQu'" @updScore="updScore" :paper="paperInfo" :examInfo="examInfo" ref="byQuMark" :stusInfo="studentScore" :classId="chooseClass" :subjectId="chooseSubject"></ByQuMark>
+				<ByQuMark v-else-if="markType == 'byQu'" @updScore="updScore" :paper="paperInfo" :examInfo="examInfo" ref="byQuMark" :stusInfo="studentScore" :classId="chooseClass" :subjectId="subjectIdForMark" :examId="examIdForMark" ></ByQuMark>
 			</div>
 		</vuescroll>
 		
@@ -255,7 +255,7 @@
 <script>
 	import StuReport from "../StuReport.vue";
 	import CorrectRate from "../echarts/CorrectRate.vue";
-	import ByStuMark from "../byStu/ByStuMark.vue";
+	import ByStuMark from "../byStu/htByStuMark.vue";
 	import ByQuMark from "../ByQuMark.vue";
 	import excel from "@/utils/excel.js";
 	export default {
@@ -282,6 +282,8 @@
 		},
 		data() {
 			return {
+				examIdForMark:"",
+				subjectIdForMark:"",
 				groupSelectValue: "",
 				courseSelectValue: "",
 				teacherSelectValue:"",
@@ -612,12 +614,14 @@
 			// 課程下拉選單切換值 
 			changeCourseSelectData(){				
 				this.groupSelectData = [];
-				// 取得課程的班級
+				// 取得課程的班級  // 根據目前選中的課程班級  設定 examId 跟 subjectId
 				let stu = this.examInfo.stuLists.find((item) =>  item.creatorId == this.teacherSelectValue );
 				let course = stu.courseLists.find((item) =>  item.courseId == this.courseSelectValue );
 				course.groupLists.forEach(item =>{
 					this.groupSelectData.push({name: item.name, id: item.id});
 				})
+				this.examIdForMark = course.examId;
+				this.subjectIdForMark = course.subjectId;
 			},
 			// 課程下拉選單切換值 
 			changeGroupSelectData(){	
@@ -849,24 +853,46 @@
 				this.exportLoading = true;
 				//TODO 导出数据
 				let promises = [];
-				this.examInfo.subjects.forEach((subject) => {
-					let cList = [];
-					if (this.examInfo.classes.length) {
-						cList = this.examInfo.classes;
-					} else {
-						cList = this.examInfo.stuLists;
-					}
-					cList.forEach((c) => {
-						let requestData = {
-							id: this.examInfo.id,
-							code: this.examInfo.scope == "school" ? this.$store.state.userInfo.schoolCode : this.examInfo.code.substr(this.examInfo.code.lastIndexOf("-") + 1),
-							subjectId: subject.id,
-							classId: c,
+				// this.examInfo.stuLists.courseLists.forEach((course) => {
+				// 	let cList = [];
+				// 	// if (this.examInfo.classes.length) {
+				// 	// 	cList = this.examInfo.classes;
+				// 	// } else {
+				// 	// 	cList = this.examInfo.stuLists;
+				// 	// }
+
+				// 	cList.forEach((c) => {
+				// 		let requestData = {
+				// 			id: this.examInfo.id,
+				// 			code: this.examInfo.scope == "school" ? this.$store.state.userInfo.schoolCode : this.examInfo.code.substr(this.examInfo.code.lastIndexOf("-") + 1),
+				// 			subjectId: course.subjectId,
+				// 			classId: c,
+				// 			startTime: this.examInfo.startTime
+				// 		};
+				// 		promises.push(this.$api.learnActivity.FindAllStudent(requestData));
+				// 	});
+				// });
+
+				this.examInfo.stuLists.forEach(stu => {
+					stu.courseLists.forEach(course => {
+						course.groupLists.forEach(group => {
+							let requestData = {
+							id: course.examId,
+							code: stu.creatorId,
+							subjectId: course.subjectId,
+							classId: group.id,
 							startTime: this.examInfo.startTime
-						};
-						promises.push(this.$api.learnActivity.FindAllStudent(requestData));
-					});
-				});
+						    };
+							promises.push(this.$api.learnActivity.FindAllStudent(requestData));
+						})
+					})
+				})
+
+
+
+
+
+
 				// 20240407 处理导出学生各题作答内容
 				let sas = this.examInfo.scope == "school" ? this.$store.state.user.schoolProfile.blob_sas : this.$store.state.user.userProfile.blob_sas;
 				let blobUrl = this.examInfo.scope == "school" ? JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri : JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri;
@@ -1164,6 +1190,7 @@
 				// 	startTime: this.examInfo.startTime
 				// };	
 				//debugger
+				// 根據選中的老師>課程 取得需要的參數
 				let stu = this.examInfo.stuLists.find((item) =>  item.creatorId == this.teacherSelectValue );
 				let course = stu.courseLists.find((item) =>  item.courseId == this.courseSelectValue );
 				let requestData = {

+ 2 - 1
TEAMModelOS/ClientApp/src/view/research-center/ResearchMgt.vue

@@ -398,7 +398,7 @@
 				classArr: [],
 				rangeArr: [vm.$t("lessonRecord.all"), vm.$t("lessonRecord.today"), vm.$t("lessonRecord.week"), vm.$t("lessonRecord.month"), vm.$t("lessonRecord.semester"), "按学期查询"],
 				statusArr: [vm.$t("lessonRecord.normalStatus"), vm.$t("lessonRecord.expireStatus"), vm.$t("lessonRecord.deleteStatus")],
-				typeArr: [vm.$t("lessonRecord.all"), vm.$t("lessonRecord.mgt.doubleGreen"), vm.$t("lessonRecord.mgt.singleGreen"), vm.$t("lessonRecord.mgt.doubleRed")],
+				typeArr: [vm.$t("lessonRecord.all"), vm.$t("lessonRecord.mgt.doubleGreen"), vm.$t("lessonRecord.mgt.singleGreen"), vm.$t("lessonRecord.mgt.doubleRed"), vm.$t("lessonRecord.mgt.doubleGray")],
 				curRangeIndex: 0,
 				curStatusIndex: 0,
 				curTypeIndex: 0,
@@ -1408,6 +1408,7 @@
 					this.filterParams.doubleGreen = index === 1;
 					this.filterParams.singleGreen = index === 2;
 					this.filterParams.doubleRed = index === 3;
+					this.filterParams.doubleGray = index === 4;
 				}
 				this.doFilter();
 			},

+ 1 - 1
TEAMModelOS/Controllers/Common/ExamController.cs

@@ -519,7 +519,7 @@ namespace TEAMModelOS.Controllers
                 var (userId, _, _, school) = HttpContext.GetAuthTokenInfo();
 
 
-                StringBuilder stringBuilder = new($"select c.id,c.name,c.code,c.period,c.startTime,c.endTime,c.stuCount,c.type,c.progress,c.examType,c.createTime,c.source, c.subjects, c.grades,c.owner, c.scope,c.classes, c.stuLists, c.sRate,c.lostStu,c.sStatus,c.qamode,c.school,c.cloudas from c where (c.status<>404 or IS_DEFINED(c.status) = false) and c.pk = 'Exam'");
+                StringBuilder stringBuilder = new($"select c.id,c.name,c.code,c.period,c.startTime,c.endTime,c.stuCount,c.type,c.progress,c.examType,c.createTime,c.source, c.subjects, c.grades,c.owner, c.scope,c.classes, c.stuLists, c.sRate,c.lostStu,c.sStatus,c.qamode,c.school,c.cloudas, c.jointExamId, c.jointVisiable from c where (c.status<>404 or IS_DEFINED(c.status) = false) and c.pk = 'Exam'");
                 /*if (request.TryGetProperty("classIds", out JsonElement classIds))
                 {
                     List<string> ids = classIds.ToObject<List<string>>();

+ 2 - 3
TEAMModelOS/Controllers/Teacher/JointEventController.cs

@@ -870,7 +870,7 @@ namespace TEAMModelOS.Controllers.Common
                                     
                                     //課程ID => 活動ID、個人評量ID對應表製作
                                     List<ExamInfo> examByCreator = exams.Where(e => e.subjects.Select(s => s.id).ToList().Contains(course.courseId) && e.creatorId.Equals(jointEventGroupRow.creatorId)).ToList();
-                                    if(jointEventGroupRow.type.Equals("custom")) //決賽
+                                    if(!string.IsNullOrWhiteSpace(jointEventGroupRow.type) && jointEventGroupRow.type.Equals("custom")) //決賽
                                     {
                                         foreach (ExamInfo exam in examByCreator)
                                         {
@@ -886,7 +886,6 @@ namespace TEAMModelOS.Controllers.Common
                                     }
                                     else //報名
                                     {
-
                                         foreach (ExamInfo exam in examByCreator)
                                         {
                                             if (!courseToExamIdRegularDic.ContainsKey(exam.jointExamId))
@@ -942,7 +941,7 @@ namespace TEAMModelOS.Controllers.Common
                                 stuListRow.schoolId = stu.schoolId;
                                 stuListRow.schoolName = stu.schoolName;
                                 stuListRow.courseLists = Newtonsoft.Json.JsonConvert.DeserializeObject<List<JointEventGroupBase.JointEventGroupCourse>>(Newtonsoft.Json.JsonConvert.SerializeObject(stu.courseLists));
-                                Dictionary<string, Dictionary<string, string>> courseToExamIdDic = (stu.type.Equals("custom")) ? courseToExamIdCustomDic : courseToExamIdRegularDic;
+                                Dictionary<string, Dictionary<string, string>> courseToExamIdDic = (!string.IsNullOrWhiteSpace(stu.type) && stu.type.Equals("custom")) ? courseToExamIdCustomDic : courseToExamIdRegularDic;
                                 foreach (JointEventGroupBase.JointEventGroupCourse course in stuListRow.courseLists)
                                 {
                                     course.examId = (courseToExamIdDic.ContainsKey(jointExam.id) && courseToExamIdDic[jointExam.id].ContainsKey(course.courseId)) ? courseToExamIdDic[jointExam.id][course.courseId] : null;