Selaa lähdekoodia

feat: 增加模版评语功能,增加评分限制警告

Jiang Pengyu 1 vuosi sitten
vanhempi
commit
97bb7bd53b

+ 114 - 1
src/views/AiSpeakingPage/SpeakingRecord.vue

@@ -270,6 +270,13 @@
                 >
                   下一个
                 </el-button>
+                <!-- 新增:评语模板 -->
+                <el-button
+                  style="background-color:#597D3B;color:white;width:200px;margin-top:50px;"
+                  @click="showTemplateDialog"
+                >
+                  评语模板
+                </el-button>
 
                 <el-button
                   style="background-color:#597D3B;color:white;width:200px;margin-top:50px;"
@@ -288,11 +295,47 @@
         </template>
       </div>
     </el-card>
+
+    <!-- 添加评语模板弹窗 -->
+    <el-dialog
+      v-model="templateDialogVisible"
+      title="评语模板"
+      width="50%"
+      :close-on-click-modal="false"
+    >
+      <div class="template-list">
+        <div 
+          v-for="(template, index) in remarkTemplates" 
+          :key="index"
+          class="template-item"
+          @click="selectTemplate(template)"
+        >
+          <el-tooltip
+            v-if="isTruncatedList[index]"
+            :content="template"
+            placement="top"
+            :show-after="500"
+            effect="light"
+          >
+            <div class="template-content" :ref="el => setTemplateContentRef(el, index)">
+              {{ index + 1 }}. {{ template }}
+            </div>
+          </el-tooltip>
+          <div
+            v-else
+            class="template-content"
+            :ref="el => setTemplateContentRef(el, index)"
+          >
+            {{ index + 1 }}. {{ template }}
+          </div>
+        </div>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
-import { ref, onMounted, onBeforeUnmount, nextTick, defineProps, computed } from "vue";
+import { ref, onMounted, onBeforeUnmount, nextTick, defineProps, computed, watch } from "vue";
 import doubaoApis from "@/apis/doubao";
 import engagementApis from "@/apis/engagement";
 import mediaApis from "@/apis/media";
@@ -901,6 +944,46 @@ const goBack = () => {
 };
 
 const ROLE = store.user.role;
+
+const templateDialogVisible = ref(false);
+const remarkTemplates = [
+  'Our pronunciation is generally clear and understandable.',
+  'Try to speak more slowly and clearly to enhance fluency.',
+  'Work on intonation and stress to sound more natural.',
+  'Practice difficult sounds more to improve articulation.',
+  'Good effort! Keep practicing to build confidence in speaking.',
+  'Your argument is clear, but could be strengthened with more supporting details.'
+];
+
+const showTemplateDialog = () => {
+  templateDialogVisible.value = true;
+};
+
+const selectTemplate = (template) => {
+  engagement.value.remark = template;
+  templateDialogVisible.value = false;
+};
+
+const isTruncatedList = ref([]);
+const templateContentRefs = ref([]);
+const setTemplateContentRef = (el, index) => {
+  templateContentRefs.value[index] = el;
+};
+const checkTruncated = () => {
+  isTruncatedList.value = remarkTemplates.map((_, idx) => {
+    const el = templateContentRefs.value[idx];
+    if (!el || !(el instanceof HTMLElement)) return false;
+    return el.scrollWidth > el.clientWidth;
+  });
+};
+watch(templateDialogVisible, (val) => {
+  if (val) {
+    nextTick(() => {
+      checkTruncated();
+    });
+  }
+});
+
 // 在数据加载完成后渲染图表
 onMounted(async () => {
   try {
@@ -1250,4 +1333,34 @@ onMounted(async () => {
   font-weight: 700;
   margin-top: 10px;
 }
+/* 新增:评语模板 css样式*/
+.template-list {
+  display: flex;
+  flex-direction: column;
+  gap: 15px;
+  padding: 10px;
+  .template-item {
+    padding: 12px;
+    border: 1px solid #e0e0e0;
+    border-radius: 4px;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    display: flex;
+    align-items: center;
+    &:hover {
+      background-color: #f5f7fa;
+      border-color: #597D3B;
+    }
+    .template-content {
+      width: 100%;
+      text-align: left;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      font-size: 14px;
+      line-height: 1.5;
+      display: block;
+    }
+  }
+}
 </style>

+ 164 - 4
src/views/Home/component/Remark/Remark.vue

@@ -3,11 +3,12 @@
     <div class="rating-component">
       <span>评分:&nbsp;&nbsp;</span>
       <input type="number" class="score-input" :min="1" :max="100" placeholder="暂无分数" title="请输入0~100间的数字"
-        v-model="correction.score" :disabled="isStudent" />
+        v-model="correction.score" :disabled="isStudent" @input="validateScore" />
+      <span v-if="scoreError" class="error-message">{{ scoreError }}</span>
     </div>
 
     <div class="textarea-container">
-      <textarea v-model="correction.remark" placeholder="暂无评语" :readonly="isStudent"></textarea>
+      <textarea v-model="correction.remark" placeholder="请输入评语" :readonly="isStudent"></textarea>
     </div>
 
     <div class="correction-actions">
@@ -49,16 +50,55 @@
         </div>
 
         <div class="right-buttons">
+          <!-- 新增:评语模板 -->
+          <el-button color="#597D3B" @click="showTemplateDialog" v-if="!isStudent">评语模板</el-button> 
           <el-button color="#597D3B" @click="goBack">返回</el-button>
         </div>
       </div>
       
     </div>
+
+    <!-- 添加评语模板弹窗 -->
+    <el-dialog
+      v-model="templateDialogVisible"
+      title="评语模板"
+      width="50%"
+      :close-on-click-modal="false"
+      class="template-dialog"
+    >
+      <div class="template-list">
+        <div 
+          v-for="(template, index) in remarkTemplates" 
+          :key="index"
+          class="template-item"
+          @click="selectTemplate(template)"
+        >
+          <el-tooltip
+            v-if="isTruncatedList[index]"
+            :content="template"
+            placement="top"
+            :show-after="500"
+            effect="light"
+          >
+            <div class="template-content" :ref="el => setTemplateContentRef(el, index)">
+              {{ index + 1 }}. {{ template }}
+            </div>
+          </el-tooltip>
+          <div
+            v-else
+            class="template-content"
+            :ref="el => setTemplateContentRef(el, index)"
+          >
+            {{ index + 1 }}. {{ template }}
+          </div>
+        </div>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
 <script lang="ts">
-import { defineComponent, ref, onMounted, computed } from 'vue';
+import { defineComponent, ref, onMounted, computed, nextTick, watch } from 'vue';
 import useApis from "@/apis";
 import { ElMessage } from "element-plus";
 import router from '@/router';
@@ -81,6 +121,7 @@ export default defineComponent({
   setup(props) {
     const isStudent = computed(() => store.user.role === 'STUDENT');
     const submitting = ref(false);
+    const scoreError = ref('');
 
     //定义一个correction接收后端传过来的批改信息(如果有) 
     const correction = ref({
@@ -224,6 +265,60 @@ export default defineComponent({
         params: { assignmentId: props.assignmentId }
       });
     };
+    //新增:验证评分是否在1-100之间
+    const validateScore = () => {
+      const score = Number(correction.value.score);
+      if (isNaN(score) || score < 1 || score > 100) {
+        scoreError.value = '评分必须在1-100之间';
+      } else {
+        scoreError.value = '';
+      }
+    };
+
+    //新增:评语模板
+    const templateDialogVisible = ref(false);
+    const remarkTemplates = [
+      'Great job! Your writing is clear and well-organized.',
+      'There are some grammar mistakes that affect clarity. Please revise. ',
+      'Watch out for tense consistency throughout your writing.',
+      'You used some good vocabulary, but try to expand your word choice.',
+      'Your argument is clear, but could be strengthened with more supporting details.Use more precise words to convey your ideas more effectively.'
+    ];
+
+    const showTemplateDialog = () => {
+      templateDialogVisible.value = true;
+    };
+
+    const selectTemplate = (template: string) => {
+      correction.value.remark = template;
+      templateDialogVisible.value = false;
+    };
+
+    // 新增:判断每个选项是否被截断
+    const isTruncatedList = ref<boolean[]>([]);
+    const templateContentRefs = ref<(HTMLElement|null)[]>([]);
+
+    const setTemplateContentRef = (el: any, index: number) => {
+      templateContentRefs.value[index] = el;
+    };
+
+    // 计算截断
+    const checkTruncated = () => {
+      isTruncatedList.value = remarkTemplates.map((_, idx) => {
+        const el = templateContentRefs.value[idx];
+        if (!el || !(el instanceof HTMLElement)) return false;
+        return el.scrollWidth > el.clientWidth;
+      });
+    };
+
+    // 弹窗打开后检查
+    watch(templateDialogVisible, (val) => {
+      if (val) {
+        nextTick(() => {
+          checkTruncated();
+        });
+      }
+    });
 
     return {
       correction,
@@ -235,7 +330,15 @@ export default defineComponent({
       progressPercentage,
       uncorrectedNumber,
       isAllCorrected,
-      goBack
+      goBack,//返回
+      scoreError,//评分错误
+      validateScore,//验证评分
+      templateDialogVisible,//评语模板弹窗
+      remarkTemplates,//评语模板
+      showTemplateDialog,//显示评语模板弹窗
+      selectTemplate,//选择评语模板
+      isTruncatedList,
+      setTemplateContentRef,
     };
   }
 });  
@@ -274,4 +377,61 @@ export default defineComponent({
     margin-top: 10px;
   }
 }
+/* 新增:评分错误 css样式*/
+.rating-component {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  margin-bottom: 15px;
+
+  .error-message {
+    color: #ff4d4f;
+    font-size: 14px;
+    font-weight: 700;
+  }
+}
+/* 新增:评语模板 css样式*/
+.template-dialog {
+  :deep(.el-dialog__body) {
+    padding: 20px 0; // 上下20px,左右0
+  }
+}
+
+.template-list {
+  display: flex;
+  flex-direction: column;
+  gap: 15px;
+  padding: 0 24px; // 左右留白对称
+
+  .template-item {
+    box-sizing: border-box;
+    padding: 0 16px;
+    border: 1px solid #e0e0e0;
+    border-radius: 4px;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    height: 50px;
+    overflow: hidden;
+    display: flex;
+    align-items: center;
+    margin: 0 auto;
+    width: 100%;
+
+    &:hover {
+      background-color: #f5f7fa;
+      border-color: #597D3B;
+    }
+
+    .template-content {
+      width: 100%;
+      text-align: left;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      font-size: 14px;
+      line-height: 1.5;
+      display: block;
+    }
+  }
+}
 </style>