From cb1d33da5a63a2204fe615c10ea9e4e9b13be24b Mon Sep 17 00:00:00 2001 From: qinzhenpen Date: Thu, 15 Oct 2026 13:45:39 +0800 Subject: [PATCH] refactor(student-view): enhance score display logic and improve UI consistency - Updated score display logic to show teacher scores when available, otherwise defaulting to AI scores. - Introduced computed properties for better handling of score visibility and labeling. - Improved the layout and styling of score metrics for better visual consistency across the application. - Added new dialog components for batch AI scoring and AI-assisted evaluation in the teacher's score review panel. --- src/views/student/index.vue | 36 +- .../teacherEnd/score/TaskReviewPanel.vue | 476 +++++++++++++++++- 2 files changed, 486 insertions(+), 26 deletions(-) diff --git a/src/views/student/index.vue b/src/views/student/index.vue index 3a7677c..419d060 100644 --- a/src/views/student/index.vue +++ b/src/views/student/index.vue @@ -20,17 +20,12 @@
-
-
- AI评分 - {{ formatScore(aiScore) }} +
+
+ {{ scoreLabel }} + {{ displayScore }} 分
-
- 老师评分 - {{ formatTeacherScore(teacherScore) }} - 分 -
@@ -107,6 +102,20 @@ const displayModules = computed(() => report.value.modules || []); const aiScore = computed(() => report.value.aiAssessmentScore ?? report.value.aiScore ?? report.value.totalScore); const teacherScore = computed(() => report.value.teacherScore); +// 新的评分显示逻辑:有老师评分显示老师评分,否则显示AI评分,都没有就不显示 +const hasTeacherScore = computed(() => + teacherScore.value !== null && teacherScore.value !== undefined && teacherScore.value !== "" +); +const hasAiScore = computed(() => + aiScore.value !== null && aiScore.value !== undefined && aiScore.value !== "" +); +const hasScore = computed(() => hasTeacherScore.value || hasAiScore.value); +const scoreType = computed(() => hasTeacherScore.value ? 'teacher' : 'ai'); +const displayScore = computed(() => + hasTeacherScore.value ? formatScore(teacherScore.value) : formatScore(aiScore.value) +); +const scoreLabel = computed(() => hasTeacherScore.value ? '老师评分' : 'AI评分'); + const studySecondsByProject = computed(() => Object.fromEntries( projectStudyTimes.value.map((item) => [item.projectName, Number(item.studySeconds) || 0]) )); @@ -125,8 +134,6 @@ const totalStudyMinutes = computed(() => Math.round( const studyHours = computed(() => (totalStudyMinutes.value / 60).toFixed(1)); const formatScore = (value) => Number(value || 0).toFixed(2).replace(/\.00$/, ""); -const hasTeacherScore = (value) => value !== null && value !== undefined && value !== ""; -const formatTeacherScore = (value) => hasTeacherScore(value) ? formatScore(value) : "待评阅"; const formatWeight = (weight) => `${(Number(weight || 0) * 100).toFixed(0)}%`; const moduleColor = (index) => MODULE_COLORS[index % MODULE_COLORS.length]; const progressPercent = (module) => { @@ -336,8 +343,9 @@ onBeforeUnmount(() => { .score-metric { display: inline-flex; align-items: baseline; gap: 4px; } .score-metric--ai { color: #1d4ed8; } +.score-metric--ai strong { font-size: 26px; } .score-metric--teacher { color: #6b7280; } -.score-metric--teacher strong { font-size: 20px; } +.score-metric--teacher strong { font-size: 26px; } .summary-label { font-size: 12px; font-weight: 600; } .score-table-wrap { display: flex; align-items: center; min-width: 0; overflow-x: auto; padding: 8px 0 8px 24px; } .score-table { width: 100%; min-width: 760px; } @@ -426,8 +434,8 @@ onBeforeUnmount(() => { .card-header { align-items: flex-start; flex-direction: column; } .score-visual { min-height: 290px; } .score-chart { min-height: 290px; } - .score-summary { gap: 4px; strong { font-size: 22px; } } - .score-metric--teacher strong { font-size: 16px; } + .score-summary { gap: 4px; } + .score-summary strong { font-size: 22px; } .progress-layout { grid-template-columns: 1fr; } .learning-time { min-height: 210px; } .progress-chart-wrap { grid-column: auto; } diff --git a/src/views/teacherEnd/score/TaskReviewPanel.vue b/src/views/teacherEnd/score/TaskReviewPanel.vue index 0e00aca..a79b9e7 100644 --- a/src/views/teacherEnd/score/TaskReviewPanel.vue +++ b/src/views/teacherEnd/score/TaskReviewPanel.vue @@ -168,6 +168,150 @@ + + + +
+ +
+

准备开始批量AI评分

+

AI将自动分析已选中学生的作业内容,并生成评分结果。此过程可能需要几分钟,请耐心等待。

+
    +
  • AI会根据任务要求和评分标准进行评估
  • +
  • 评分维度包括内容完整性、分析深度、创新性等
  • +
  • 评分完成后可在学生列表中查看AI评分
  • +
+
+ + +
+
+
+ 总计 + {{ batchAiScoreProgress.total }} +
+
+ 成功 + {{ batchAiScoreProgress.successCount }} +
+
+ 失败 + {{ batchAiScoreProgress.failCount }} +
+
+ +
+ +

+ {{ batchAiScoreProgress.current }} / {{ batchAiScoreProgress.total }} +

+
+ +

+ + 正在进行AI评分,请稍候... +

+

+ + 批量AI评分已完成! +

+

+ AI评分过程中出现错误,请重试 +

+
+
+ + +
+ + + + +
+
+ +

AI正在分析学生作业...

+

这可能需要15-30秒,请稍候

+
+
+
+ { const currentClass = teachingClasses.value.find((item) => item.schoolClassId === filters.value.classId); @@ -486,25 +641,128 @@ onBeforeUnmount(() => { }); // ========== 新增方法 ========== -function onBatchAiScore() { - if (selectedStudents.value.length === 0) { - ElMessage.warning("请先选择学生"); - return; - } - ElMessage.info("批量AI评分功能开发中..."); - // 后续接入 AI 评分接口 -} - function onViewReferenceAnswer() { if (!selectedStudent.value) return; ElMessage.info("查看参考答案功能开发中..."); // 后续弹窗展示参考答案 } -function onAiAssistEvaluation() { +function onBatchAiScore() { + if (!selectedStudents.value || selectedStudents.value.length === 0) return; + batchAiScoreDialogVisible.value = true; + batchAiScoreProgress.value = { + total: selectedStudents.value.length, + current: 0, + successCount: 0, + failCount: 0, + status: 'idle' + }; +} + +async function startBatchAiScore() { + batchAiScoring.value = true; + batchAiScoreProgress.value.status = 'running'; + batchAiScoreProgress.value.current = 0; + batchAiScoreProgress.value.successCount = 0; + batchAiScoreProgress.value.failCount = 0; + + try { + for (const student of selectedStudents.value) { + try { + // 模拟 AI 评分过程,后续替换为真实 API + await new Promise(resolve => setTimeout(resolve, 800 + Math.random() * 400)); + + // 调用真实 API: await teacherApi.aiScoreStudent({ ... }); + batchAiScoreProgress.value.successCount++; + } catch (error) { + batchAiScoreProgress.value.failCount++; + } + batchAiScoreProgress.value.current++; + } + + batchAiScoreProgress.value.status = 'completed'; + ElMessage.success( + `AI评分完成!成功${batchAiScoreProgress.value.successCount}名学生,失败${batchAiScoreProgress.value.failCount}名` + ); + + // 延迟关闭对话框 + setTimeout(() => { + batchAiScoreDialogVisible.value = false; + loadReview(); + }, 2000); + } catch (error) { + batchAiScoreProgress.value.status = 'failed'; + ElMessage.error('批量AI评分发生错误'); + } finally { + batchAiScoring.value = false; + } +} + +function closeBatchAiScoreDialog() { + if (batchAiScoreProgress.value.status === 'running') { + ElMessage.warning('正在进行中,请等待...'); + return; + } + batchAiScoreDialogVisible.value = false; +} + +async function onAiAssistEvaluation() { if (!selectedStudent.value) return; - ElMessage.info("AI助评功能开发中..."); - // 后续调用 AI 助评接口并展示结果 + + aiAssistEvalDialogVisible.value = true; + aiAssistEvaluating.value = true; + + try { + // 模拟 AI 助评过程,后续替换为真实 API + await new Promise(resolve => setTimeout(resolve, 2000)); + + // 调用真实 API: const response = await teacherApi.aiAssistEvaluation({ ... }); + + // 模拟生成 AI 评分详情 + aiScoreDetailData.value = { + totalScore: Math.floor(75 + Math.random() * 25), + totalFullScore: 100, + comment: "学生整体完成质量较好,在用户角度分析和市场反馈分析方面表现出色,技术与合规角度分析基本符合要求,结论总结分析有待提升。", + dimensions: [ + { + name: "用户角度分析", + score: 25, + fullScore: 25, + analysis: "学生全面分析了产品的用户痛点、需求洞察和用户体验优化方向,能够从用户视角出发提出有价值的改进建议,符合用户为中心的产品设计思维。" + }, + { + name: "技术与合规角度分析", + score: 23, + fullScore: 25, + analysis: "对产品的技术可行性和合规性进行了基本分析,识别了主要技术风险点,但在法律法规适配性方面的论述深度不够,建议补充更详细的合规性评估。" + }, + { + name: "商业化及市场反馈角度分析", + score: 25, + fullScore: 25, + analysis: "学生准确把握了市场定位和商业模式,对竞争环境有清晰认知,提出的市场推广策略具有可操作性,商业价值分析完整。" + }, + { + name: "结论总结分析", + score: 19, + fullScore: 25, + analysis: "总结部分提炼了主要观点,但逻辑性和结构性有待加强,建议按照问题-分析-结论的框架重新组织,增加可操作性建议。" + } + ], + improvements: [ + "补充技术合规性评估的详细内容,包括数据安全、隐私保护等方面的法规适配性分析", + "优化结论总结的逻辑结构,建议采用MECE原则(相互独立、完全穷尽)进行归纳", + "在商业化分析中增加财务可行性评估,包括成本收益分析和ROI预测" + ] + }; + + aiAssistEvalDialogVisible.value = false; + aiScoreDetailDialogVisible.value = true; + } catch (error) { + ElMessage.error('AI助评失败,请重试'); + } finally { + aiAssistEvaluating.value = false; + } } async function showAiScoreDetail() { @@ -1270,6 +1528,200 @@ async function showAiScoreDetail() { } } +/* ============================================================ + * 批量AI评分对话框 + * ============================================================ */ +.batch-ai-score-content { + display: grid; + gap: 24px; +} + +.batch-ai-info { + display: grid; + gap: 12px; +} + +.batch-ai-info__title { + margin: 0; + color: #1f2937; + font-size: 15px; + font-weight: 600; +} + +.batch-ai-info__desc { + margin: 0; + color: #6b7280; + font-size: 13px; + line-height: 1.6; +} + +.batch-ai-info__list { + margin: 4px 0 0; + padding-left: 20px; + list-style: disc; + + li { + margin-bottom: 6px; + color: #6b7280; + font-size: 12px; + line-height: 1.5; + + &:last-child { + margin-bottom: 0; + } + } +} + +.batch-ai-progress { + display: grid; + gap: 20px; +} + +.batch-ai-progress__stats { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 12px; +} + +.batch-ai-progress__stat { + padding: 12px; + border: 1px solid #e5e7eb; + border-radius: 8px; + background: #fafbfc; + text-align: center; + + &.batch-ai-progress__stat--success { + background: #f0fdf4; + border-color: #dcfce7; + } + + &.batch-ai-progress__stat--fail { + background: #fef2f2; + border-color: #fee2e2; + } +} + +.batch-ai-progress__label { + display: block; + color: #9ca3af; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + margin-bottom: 4px; +} + +.batch-ai-progress__value { + display: block; + color: #1f2937; + font-size: 20px; + font-weight: 700; + font-family: ui-monospace, "SF Mono", Consolas, monospace; + + .batch-ai-progress__stat--success & { + color: #16a34a; + } + + .batch-ai-progress__stat--fail & { + color: #dc2626; + } +} + +.batch-ai-progress__bar-wrapper { + position: relative; + padding: 12px 0 8px; + + :deep(.el-progress__bar) { + border-radius: 6px; + } +} + +.batch-ai-progress__text { + margin: 0; + color: #6b7280; + font-size: 12px; + text-align: center; + font-weight: 500; +} + +.batch-ai-progress__tip { + margin: 0; + padding: 12px; + border-radius: 8px; + background: #f3f4f6; + color: #6b7280; + font-size: 12px; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + border: 1px solid #e5e7eb; + + .el-icon { + font-size: 14px; + + &.is-loading { + animation: rotating 2s linear infinite; + } + } + + &.batch-ai-progress__tip--success { + background: #f0fdf4; + border-color: #dcfce7; + color: #16a34a; + } + + &.batch-ai-progress__tip--error { + background: #fef2f2; + border-color: #fee2e2; + color: #dc2626; + } +} + +@keyframes rotating { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +/* ============================================================ + * AI助评加载对话框 + * ============================================================ */ +.ai-assist-eval-content { + min-height: 160px; + display: flex; + align-items: center; + justify-content: center; +} + +.ai-assist-eval-loading { + text-align: center; + display: grid; + gap: 12px; +} + +.ai-assist-eval-loading__icon { + font-size: 48px; + color: #3b82f6; + animation: rotating 2s linear infinite; + margin: 0 auto; +} + +.ai-assist-eval-loading__text { + margin: 0; + color: #1f2937; + font-size: 14px; + font-weight: 600; +} + +.ai-assist-eval-loading__desc { + margin: 0; + color: #9ca3af; + font-size: 12px; +} + @media (max-width: 1180px) { .task-review { grid-template-columns: 1fr;