@ -26,7 +26,7 @@
< el -button type = "primary" size = "small" :disabled ="!canBatchScore" @ click = "batchDialogVisible = true" > 批量打分 < / e l - b u t t o n >
< / div >
< / div >
< el -table ref = "studentTableRef" class = "task-review__student-table" :data =" review.s tudents" border size = "small" row -key = " studentUserId " highlight -current -row :row-class-name ="studentRowClass" @selection-change ="onSelectionChange" @row-click ="selectStudent" >
< el -table ref = "studentTableRef" class = "task-review__student-table" :data =" paginatedS tudents" border size = "small" row -key = " studentUserId " highlight -current -row :row-class-name ="studentRowClass" @selection-change ="onSelectionChange" @row-click ="selectStudent" >
< el -table -column type = "selection" width = "42" :selectable ="canSelectStudent" / >
< el -table -column prop = "name" label = "姓名" width = "86" show -overflow -tooltip / >
< el -table -column prop = "username" label = "学号" width = "102" show -overflow -tooltip / >
@ -41,6 +41,7 @@
< / template >
< / e l - t a b l e - c o l u m n >
< / e l - t a b l e >
< el -pagination v -if = " review.students.length > pagination . pageSize " v-model:current-page=" pagination . currentPage " v-model:page-size=" pagination . pageSize " :page-sizes=" [ 10 , 20 , 50 , 100 ] " :total=" review . students . length " layout=" total , sizes , prev , pager , next , jumper " class=" task - review _ _pagination " small background @current-change=" onPageChange " @size-change=" onPageSizeChange " / >
< p v-if ="!canManageClass && filters.classId" class="task-review__readonly-tip" > 仅 班 级 创 建 教 师 可 评 分 < / p >
< / aside >
@ -168,15 +169,7 @@
< / template >
< / e l - d i a l o g >
< el -dialog
v - model = "batchAiScoreDialogVisible"
width = "520px"
append - to - body
: show - close = "false"
: close - on - click - modal = "false"
: close - on - press - escape = "false"
class = "score-review-dialog batch-ai-score-dialog"
>
< el -dialog v-model ="batchAiScoreDialogVisible" width="520px" append-to-body :show-close="false" :close-on-click-modal="false" :close-on-press-escape="false" class="score-review-dialog batch-ai-score-dialog" >
< template # header >
< div class = "dialog-header" >
< div class = "dialog-header-main" >
@ -186,12 +179,7 @@
< / span >
< h3 class = "dialog-title" > 已选择 { { selectedStudents . length } } 名学生 < / h3 >
< / div >
< button
v - if = "batchAiScoreProgress.status !== 'running'"
type = "button"
class = "dialog-close"
@ click = "closeBatchAiScoreDialog"
>
< button v-if ="batchAiScoreProgress.status !== 'running'" type="button" class="dialog-close" @click="closeBatchAiScoreDialog" >
< el -icon > < Close / > < / e l - i c o n >
< / button >
< / div >
@ -227,14 +215,8 @@
< / div >
< div class = "batch-ai-progress__bar-wrapper" >
< el -progress
: percentage = "Math.floor((batchAiScoreProgress.current / batchAiScoreProgress.total) * 100)"
: status = "batchAiScoreProgress.status === 'completed' ? 'success' : batchAiScoreProgress.status === 'failed' ? 'exception' : undefined"
: stroke - width = "12"
/ >
< p class = "batch-ai-progress__text" >
{ { batchAiScoreProgress . current } } / { { batchAiScoreProgress . total } }
< / p >
< el -progress : percentage = "Math.floor((batchAiScoreProgress.current / batchAiScoreProgress.total) * 100)" : status = "batchAiScoreProgress.status === 'completed' ? 'success' : batchAiScoreProgress.status === 'failed' ? 'exception' : undefined" :stroke-width ="12" / >
< p class = "batch-ai-progress__text" > { { batchAiScoreProgress . current } } / { { batchAiScoreProgress . total } } < / p >
< / div >
< p v-if ="batchAiScoreProgress.status === 'running'" class="batch-ai-progress__tip" >
@ -245,52 +227,23 @@
< el -icon > < CircleCheck / > < / e l - i c o n >
批量AI评分已完成 !
< / p >
< p v-if ="batchAiScoreProgress.status === 'failed'" class="batch-ai-progress__tip batch-ai-progress__tip--error" >
AI评分过程中出现错误 , 请重试
< / p >
< p v-if ="batchAiScoreProgress.status === 'failed'" class="batch-ai-progress__tip batch-ai-progress__tip--error" > AI 评 分 过 程 中 出 现 错 误 , 请 重 试 < / p >
< / div >
< / div >
< template # footer >
< div class = "dialog-footer" >
< button
v - if = "batchAiScoreProgress.status === 'idle'"
type = "button"
class = "dialog-btn dialog-btn--cancel"
@ click = "batchAiScoreDialogVisible = false"
>
取消
< / button >
< button
v - if = "batchAiScoreProgress.status === 'idle'"
type = "button"
class = "dialog-btn dialog-btn--primary"
@ click = "startBatchAiScore"
>
< button v-if ="batchAiScoreProgress.status === 'idle'" type="button" class="dialog-btn dialog-btn--cancel" @click="batchAiScoreDialogVisible = false" > 取 消 < / button >
< button v-if ="batchAiScoreProgress.status === 'idle'" type="button" class="dialog-btn dialog-btn--primary" @click="startBatchAiScore" >
< el -icon > < TrendCharts / > < / e l - i c o n >
开始AI评分
< / button >
< button
v - if = "batchAiScoreProgress.status === 'completed' || batchAiScoreProgress.status === 'failed'"
type = "button"
class = "dialog-btn dialog-btn--primary"
@ click = "closeBatchAiScoreDialog"
>
关闭
< / button >
< button v-if ="batchAiScoreProgress.status === 'completed' || batchAiScoreProgress.status === 'failed'" type="button" class="dialog-btn dialog-btn--primary" @click="closeBatchAiScoreDialog" > 关 闭 < / button >
< / div >
< / template >
< / e l - d i a l o g >
< el -dialog
v - model = "aiAssistEvalDialogVisible"
width = "420px"
append - to - body
: show - close = "false"
: close - on - click - modal = "false"
: close - on - press - escape = "false"
class = "score-review-dialog ai-assist-eval-dialog"
>
< el -dialog v-model ="aiAssistEvalDialogVisible" width="420px" append-to-body :show-close="false" :close-on-click-modal="false" :close-on-press-escape="false" class="score-review-dialog ai-assist-eval-dialog" >
< template # header >
< div class = "dialog-header" >
< div class = "dialog-header-main" >
@ -312,13 +265,7 @@
< / div >
< / e l - d i a l o g >
< el -dialog
v - model = "aiScoreDetailDialogVisible"
width = "720px"
append - to - body
: show - close = "false"
class = "ai-score-detail-dialog"
>
< el -dialog v-model ="aiScoreDetailDialogVisible" width="720px" append-to-body :show-close="false" class="ai-score-detail-dialog" >
< template # header >
< div class = "dialog-header" >
< div class = "dialog-header-main" >
@ -340,7 +287,7 @@
< div class = "ai-score-summary__main" >
< span class = "ai-score-summary__label" > 综合评分 < / span >
< div class = "ai-score-summary__score" >
< span class = "ai-score-summary__number" > { { aiScoreDetailData . totalScore ? ? '--' } } < / span >
< span class = "ai-score-summary__number" > { { aiScoreDetailData . totalScore ? ? "--" } } < / span >
< span class = "ai-score-summary__total" > / { { aiScoreDetailData . totalFullScore } } < / span >
< / div >
< / div >
@ -349,15 +296,11 @@
<!-- 各维度评分 -- >
< div v-if ="aiScoreDetailData.dimensions.length > 0" class="ai-score-dimensions" >
< div
v - for = "(dimension, index) in aiScoreDetailData.dimensions"
: key = "index"
class = "ai-score-dimension"
>
< div v-for ="(dimension, index) in aiScoreDetailData.dimensions" :key="index" class="ai-score-dimension" >
< div class = "ai-score-dimension__head" >
< h4 class = "ai-score-dimension__title" > { { dimension . name } } < / h4 >
< span class = "ai-score-dimension__score" >
< strong > { { dimension . score ? ? '--' } } < / strong >
< strong > { { dimension . score ? ? "--" } } < / strong >
< span > / { { dimension . fullScore } } < / span >
< / span >
< / div >
@ -424,7 +367,7 @@ const aiScoreDetailData = ref({
totalFullScore : 100 ,
comment : "" ,
dimensions : [ ] ,
improvements : [ ]
improvements : [ ] ,
} ) ;
const batchAiScoreDialogVisible = ref ( false ) ;
const batchAiScoring = ref ( false ) ;
@ -433,10 +376,14 @@ const batchAiScoreProgress = ref({
current : 0 ,
successCount : 0 ,
failCount : 0 ,
status : 'idle' // ' i d l e ' | ' r u n n i n g ' | ' c o m p l e t e d ' | ' f a i l e d '
status : "idle" , // ' i d l e ' | ' r u n n i n g ' | ' c o m p l e t e d ' | ' f a i l e d '
} ) ;
const aiAssistEvalDialogVisible = ref ( false ) ;
const aiAssistEvaluating = ref ( false ) ;
const pagination = ref ( {
currentPage : 1 ,
pageSize : 10 ,
} ) ;
const canManageClass = computed ( ( ) => {
const currentClass = teachingClasses . value . find ( ( item ) => item . schoolClassId === filters . value . classId ) ;
@ -444,6 +391,12 @@ const canManageClass = computed(() => {
} ) ;
const selectedStudent = computed ( ( ) => review . value . students . find ( ( row ) => row . studentUserId === selectedStudentId . value ) || review . value . students [ 0 ] || null ) ;
const canBatchScore = computed ( ( ) => canManageClass . value && selectedStudents . value . length > 0 && Boolean ( filters . value . taskId ) ) ;
const paginatedStudents = computed ( ( ) => {
const students = review . value . students ;
const start = ( pagination . value . currentPage - 1 ) * pagination . value . pageSize ;
const end = start + pagination . value . pageSize ;
return students . slice ( start , end ) ;
} ) ;
watch (
teachingClasses ,
@ -505,6 +458,8 @@ function normalizeReview(data) {
if ( ! students . some ( ( row ) => row . studentUserId === selectedStudentId . value ) ) selectedStudentId . value = students [ 0 ] ? . studentUserId || "" ;
selectedStudents . value = [ ] ;
studentTableRef . value ? . clearSelection ( ) ;
/ / 重 置 分 页 到 第 一 页
pagination . value . currentPage = 1 ;
}
async function loadReview ( ) {
if ( ! filters . value . classId ) return ;
@ -565,6 +520,13 @@ function canSelectStudent(row) {
function selectStudent ( row ) {
selectedStudentId . value = row . studentUserId ;
}
function onPageChange ( page ) {
pagination . value . currentPage = page ;
}
function onPageSizeChange ( pageSize ) {
pagination . value . pageSize = pageSize ;
pagination . value . currentPage = 1 ;
}
function studentRowClass ( { row } ) {
return row . studentUserId === selectedStudentId . value ? "task-review__selected-row" : "" ;
}
@ -655,13 +617,13 @@ function onBatchAiScore() {
current : 0 ,
successCount : 0 ,
failCount : 0 ,
status : 'idle'
status : "idle" ,
} ;
}
async function startBatchAiScore ( ) {
batchAiScoring . value = true ;
batchAiScoreProgress . value . status = 'running' ;
batchAiScoreProgress . value . status = "running" ;
batchAiScoreProgress . value . current = 0 ;
batchAiScoreProgress . value . successCount = 0 ;
batchAiScoreProgress . value . failCount = 0 ;
@ -670,7 +632,7 @@ async function startBatchAiScore() {
for ( const student of selectedStudents . value ) {
try {
/ / 模 拟 A I 评 分 过 程 , 后 续 替 换 为 真 实 A P I
await new Promise ( resolve => setTimeout ( resolve , 800 + Math . random ( ) * 400 ) ) ;
await new Promise ( ( resolve ) => setTimeout ( resolve , 800 + Math . random ( ) * 400 ) ) ;
/ / 调 用 真 实 A P I : a w a i t t e a c h e r A p i . a i S c o r e S t u d e n t ( { . . . } ) ;
batchAiScoreProgress . value . successCount ++ ;
@ -680,10 +642,8 @@ async function startBatchAiScore() {
batchAiScoreProgress . value . current ++ ;
}
batchAiScoreProgress . value . status = 'completed' ;
ElMessage . success (
` AI评分完成! 成功 ${ batchAiScoreProgress . value . successCount } 名学生,失败 ${ batchAiScoreProgress . value . failCount } 名 `
) ;
batchAiScoreProgress . value . status = "completed" ;
ElMessage . success ( ` AI评分完成! 成功 ${ batchAiScoreProgress . value . successCount } 名学生,失败 ${ batchAiScoreProgress . value . failCount } 名 ` ) ;
/ / 延 迟 关 闭 对 话 框
setTimeout ( ( ) => {
@ -691,16 +651,16 @@ async function startBatchAiScore() {
loadReview ( ) ;
} , 2000 ) ;
} catch ( error ) {
batchAiScoreProgress . value . status = 'failed' ;
ElMessage . error ( '批量AI评分发生错误' ) ;
batchAiScoreProgress . value . status = "failed" ;
ElMessage . error ( "批量AI评分发生错误" ) ;
} finally {
batchAiScoring . value = false ;
}
}
function closeBatchAiScoreDialog ( ) {
if ( batchAiScoreProgress . value . status === 'running' ) {
ElMessage . warning ( '正在进行中,请等待...' ) ;
if ( batchAiScoreProgress . value . status === "running" ) {
ElMessage . warning ( "正在进行中,请等待..." ) ;
return ;
}
batchAiScoreDialogVisible . value = false ;
@ -714,7 +674,7 @@ async function onAiAssistEvaluation() {
try {
/ / 模 拟 A I 助 评 过 程 , 后 续 替 换 为 真 实 A P I
await new Promise ( resolve => setTimeout ( resolve , 2000 ) ) ;
await new Promise ( ( resolve ) => setTimeout ( resolve , 2000 ) ) ;
/ / 调 用 真 实 A P I : c o n s t r e s p o n s e = a w a i t t e a c h e r A p i . a i A s s i s t E v a l u a t i o n ( { . . . } ) ;
@ -728,38 +688,34 @@ async function onAiAssistEvaluation() {
name : "用户角度分析" ,
score : 25 ,
fullScore : 25 ,
analysis : "学生全面分析了产品的用户痛点、需求洞察和用户体验优化方向,能够从用户视角出发提出有价值的改进建议,符合用户为中心的产品设计思维。"
analysis : "学生全面分析了产品的用户痛点、需求洞察和用户体验优化方向,能够从用户视角出发提出有价值的改进建议,符合用户为中心的产品设计思维。" ,
} ,
{
name : "技术与合规角度分析" ,
score : 23 ,
fullScore : 25 ,
analysis : "对产品的技术可行性和合规性进行了基本分析,识别了主要技术风险点,但在法律法规适配性方面的论述深度不够,建议补充更详细的合规性评估。"
analysis : "对产品的技术可行性和合规性进行了基本分析,识别了主要技术风险点,但在法律法规适配性方面的论述深度不够,建议补充更详细的合规性评估。" ,
} ,
{
name : "商业化及市场反馈角度分析" ,
score : 25 ,
fullScore : 25 ,
analysis : "学生准确把握了市场定位和商业模式,对竞争环境有清晰认知,提出的市场推广策略具有可操作性,商业价值分析完整。"
analysis : "学生准确把握了市场定位和商业模式,对竞争环境有清晰认知,提出的市场推广策略具有可操作性,商业价值分析完整。" ,
} ,
{
name : "结论总结分析" ,
score : 19 ,
fullScore : 25 ,
analysis : "总结部分提炼了主要观点,但逻辑性和结构性有待加强,建议按照问题-分析-结论的框架重新组织,增加可操作性建议。"
}
analysis : "总结部分提炼了主要观点,但逻辑性和结构性有待加强,建议按照问题-分析-结论的框架重新组织,增加可操作性建议。" ,
} ,
] ,
improvements : [
"补充技术合规性评估的详细内容,包括数据安全、隐私保护等方面的法规适配性分析" ,
"优化结论总结的逻辑结构, 建议采用MECE原则( 相互独立、完全穷尽) 进行归纳" ,
"在商业化分析中增加财务可行性评估, 包括成本收益分析和ROI预测"
]
improvements : [ "补充技术合规性评估的详细内容,包括数据安全、隐私保护等方面的法规适配性分析" , "优化结论总结的逻辑结构, 建议采用MECE原则( 相互独立、完全穷尽) 进行归纳" , "在商业化分析中增加财务可行性评估, 包括成本收益分析和ROI预测" ] ,
} ;
aiAssistEvalDialogVisible . value = false ;
aiScoreDetailDialogVisible . value = true ;
} catch ( error ) {
ElMessage . error ( 'AI助评失败, 请重试' ) ;
ElMessage . error ( "AI助评失败, 请重试" ) ;
} finally {
aiAssistEvaluating . value = false ;
}
@ -778,32 +734,28 @@ async function showAiScoreDetail() {
name : "用户角度分析" ,
score : 25 ,
fullScore : 25 ,
analysis : "学生全面分析了产品的用户痛点、需求洞察和用户体验优化方向,能够从用户视角出发提出有价值的改进建议,符合用户为中心的产品设计思维。"
analysis : "学生全面分析了产品的用户痛点、需求洞察和用户体验优化方向,能够从用户视角出发提出有价值的改进建议,符合用户为中心的产品设计思维。" ,
} ,
{
name : "技术与合规角度分析" ,
score : 23 ,
fullScore : 25 ,
analysis : "对产品的技术可行性和合规性进行了基本分析,识别了主要技术风险点,但在法律法规适配性方面的论述深度不够,建议补充更详细的合规性评估。"
analysis : "对产品的技术可行性和合规性进行了基本分析,识别了主要技术风险点,但在法律法规适配性方面的论述深度不够,建议补充更详细的合规性评估。" ,
} ,
{
name : "商业化及市场反馈角度分析" ,
score : 25 ,
fullScore : 25 ,
analysis : "学生准确把握了市场定位和商业模式,对竞争环境有清晰认知,提出的市场推广策略具有可操作性,商业价值分析完整。"
analysis : "学生准确把握了市场定位和商业模式,对竞争环境有清晰认知,提出的市场推广策略具有可操作性,商业价值分析完整。" ,
} ,
{
name : "结论总结分析" ,
score : 19 ,
fullScore : 25 ,
analysis : "总结部分提炼了主要观点,但逻辑性和结构性有待加强,建议按照问题-分析-结论的框架重新组织,增加可操作性建议。"
}
analysis : "总结部分提炼了主要观点,但逻辑性和结构性有待加强,建议按照问题-分析-结论的框架重新组织,增加可操作性建议。" ,
} ,
] ,
improvements : [
"补充技术合规性评估的详细内容,包括数据安全、隐私保护等方面的法规适配性分析" ,
"优化结论总结的逻辑结构, 建议采用MECE原则( 相互独立、完全穷尽) 进行归纳" ,
"在商业化分析中增加财务可行性评估, 包括成本收益分析和ROI预测"
]
improvements : [ "补充技术合规性评估的详细内容,包括数据安全、隐私保护等方面的法规适配性分析" , "优化结论总结的逻辑结构, 建议采用MECE原则( 相互独立、完全穷尽) 进行归纳" , "在商业化分析中增加财务可行性评估, 包括成本收益分析和ROI预测" ] ,
} ;
aiScoreDetailDialogVisible . value = true ;
@ -923,6 +875,16 @@ async function showAiScoreDetail() {
font - size : var ( -- fs - body ) ;
}
. task - review _ _pagination {
margin : 12 px 0 0 ;
display : flex ;
justify - content : flex - end ;
: deep ( . el - pagination ) {
font - size : var ( -- fs - body ) ;
}
}
/* ===== 右侧预览区 ===== */
. task - review _ _content {
min - width : 0 ;