@@ -716,7 +716,9 @@
< el -option label = "本科组" value = "benke" / >
< el -option label = "本科组" value = "benke" / >
< el-option label = "教师组" value = "jiaoshi" / >
< el-option label = "教师组" value = "jiaoshi" / >
< / el-select >
< / el-select >
< el-input v-model = "submissionSearch" placeholder="搜索选手姓名" clearable size="small" class="search-input" @input="onSubmissionFilterChange" / >
<!-- R194 : 列表已按小组聚合 ( 一行 = 一个小组 ) , 搜索口径同步改为小组编号 / 目录名 ,
⛔ 不再按选手姓名过滤 — — 姓名已不在列表展示 , 命中也无法解释 。 -- >
< el-input v-model = "submissionSearch" placeholder="搜索小组编号" clearable size="small" class="search-input" @input="onSubmissionFilterChange" / >
<!-- R87 : 提交状态筛选 。 判据与表格内 「 未提交 」 文案同源 ( row . files . length === 0 ) ;
<!-- R87 : 提交状态筛选 。 判据与表格内 「 未提交 」 文案同源 ( row . files . length === 0 ) ;
选项文案与 「 成绩管理 · 成绩总览 」 的提交状态下拉逐字对齐 。 -- >
选项文案与 「 成绩管理 · 成绩总览 」 的提交状态下拉逐字对齐 。 -- >
<!-- R178 : value - on - clear = "" 让 「 清除 」 回到与 ref 初值一致的空字符串
<!-- R178 : value - on - clear = "" 让 「 清除 」 回到与 ref 初值一致的空字符串
@@ -734,19 +736,24 @@
title = "下载该组全部作品(不受上方筛选影响)"
title = "下载该组全部作品(不受上方筛选影响)"
> 批量下载作品 < / el-button >
> 批量下载作品 < / el-button >
< span class = "work-summary" v-if = "submissions.length" >
< span class = "work-summary" v-if = "submissions.length" >
共 {{ submissionStatusCount.total }} 人 · 已提交 {{ submissionStatusCount.submitted }} · 未提交 {{ submissionStatusCount.unsubmitted }}
共 {{ submissionStatusCount.total }} 组 · 已提交 {{ submissionStatusCount.submitted }} · 未提交 {{ submissionStatusCount.unsubmitted }}
< template v-if = "submissionStatusFilter === 'true' || submissionStatusFilter === 'false'" > | 当前筛选出 {{ filteredSubmissions.length }} 人 < / template >
< template v-if = "submissionStatusFilter === 'true' || submissionStatusFilter === 'false'" > | 当前筛选出 {{ filteredSubmissions.length }} 组 < / template >
< / span >
< / span >
< / div >
< / div >
< ! - - 🔴 R194 : 作品管理改 「 小组维度 」 ( 用户实测 : 比赛以小组为准 , 原表格按人平铺 ,
同组 6 人 6 行 、 5 行是 「 未提交 」 , 管理人员看不出小组整体是否已交 ) 。
一行 = 一个小组 : 小组编号 / 作品目录 + 提交状态 + 该组文件与评分 。 - - >
< el-table :data = "pagedSubmissions" stripe v-loading = "submissionsLoading" class="admin-table" >
< el-table :data = "pagedSubmissions" stripe v-loading = "submissionsLoading" class="admin-table" >
<el -table -column prop = "name" label = "选手姓名" width = "120" />
<! - - R190 / R194 : 小组编号 / 作品目录 。
<!-- R190 : 小组编号 / 作品目录 。
展示值 = 后端落盘用的目录名 ( teamFolder ) , 与 「 批量下载 」 压缩包内的子目录
展示值 = 后端落盘用的目录名 ( teamFolder ) , 与 「 批量下载 」 压缩包内的子目录
以及 「 单组压缩包 」 文件名 * * 完全同源 * * , 管理人员据此对得上号 。 - - >
以及 「 单组压缩包 」 文件名 * * 完全同源 * * , 管理人员据此对得上号 。 - - >
< el-table-column label = "小组编号 / 作品目录" width = "24 0" >
< el-table-column label = "小组编号 / 作品目录" width = "26 0" >
< template # default = "{ row }" >
< template # default = "{ row }" >
< div class = "team-cell" >
< div class = "team-cell" >
< span class = "team-no" :title = "row.teamFolder" > { { row . teamFolder } } < / span >
< span class = "team-no" :title = "row.teamFolder" > { { row . teamFolder } } < / span >
< span v-if = "row.memberCount > 1" class="team-meta" > {{ row.memberCount }} 人参赛 < / span >
< ! - - R194 : 提交人 ( = 组锁口径下的归属人 , 最早提交者 ) ; 未提交则不显示 - - >
< span v-if = "row.submitterName" class="team-meta" > 提交人 : {{ row.submitterName }} < / span >
< el -button
< el -button
text
text
type = "primary"
type = "primary"
@@ -758,7 +765,14 @@
< / div >
< / div >
< / template >
< / template >
< / el-table-column >
< / el-table-column >
< el-table-column label = "上传文件 / 评分" min - width =" 420 ">
< el-table-column label = "提交状态" width ="110" align = "center ">
< template # default = "{ row }" >
< el-tag size = "small" : type = "isWorkSubmitted(row) ? 'success' : 'info'" >
{ { isWorkSubmitted ( row ) ? '已提交' : '未提交' } }
< / el-tag >
< / template >
< / el-table-column >
< el-table-column label = "上传文件 / 评分" min -width = " 460 " >
< template # default = "{ row }" >
< template # default = "{ row }" >
< div v-for = "f in row.files" :key="f.id" class="work-file-row" >
< div v-for = "f in row.files" :key="f.id" class="work-file-row" >
< span class = "work-file-name" :title = "f.fileName" > { { f . fileName } } < / span >
< span class = "work-file-name" :title = "f.fileName" > { { f . fileName } } < / span >
@@ -794,6 +808,13 @@
< el-dialog v-model = "previewVisible" title="文件预览" width="80%" top="5vh" @closed="closePreview" >
< el-dialog v-model = "previewVisible" title="文件预览" width="80%" top="5vh" @closed="closePreview" >
< div v-if = "previewFile" >
< div v-if = "previewFile" >
< p class = "preview-file-name" > { { previewFile . fileName } } ( { { previewFile . kind === 'ppt' ? 'PPT' : previewFile . kind === 'answer' ? 'Word答案' : '文档' } } ) < / p >
< p class = "preview-file-name" > { { previewFile . fileName } } ( { { previewFile . kind === 'ppt' ? 'PPT' : previewFile . kind === 'answer' ? 'Word答案' : '文档' } } ) < / p >
<!-- R194 : 空白兜底 。 iframe 渲染 PDF 依赖浏览器内置阅读器 ( Chrome / Edge 内置 ,
被策略禁用 / 精简版系统移除时 iframe 会显示纯白且 * * 不报错 * * ) , 故常驻一条说明 +
「 在新标签打开 」 出口 , 避免管理人员误判 「 系统没加载 」 。 -- >
< div v-if = "previewIsPdf && previewBlobUrl" class="preview-bar" >
< el -button text type = "primary" size = "small" v-guard = "() => openPreviewInNewTab()" > 在新标签打开 < / el -button >
< span class = "preview-hint" > 若下方空白 , 通常是浏览器未启用内置 PDF 阅读器 , 可点左侧按钮或下载后查看 < / span >
< / div >
< iframe v-if = "previewIsPdf && previewBlobUrl" :src="previewBlobUrl" class="preview-frame" > < / iframe >
< iframe v-if = "previewIsPdf && previewBlobUrl" :src="previewBlobUrl" class="preview-frame" > < / iframe >
< div v-else >
< div v-else >
< el -alert type = "info" :closable = "false" show -icon style = "margin-bottom:12px" >
< el -alert type = "info" :closable = "false" show -icon style = "margin-bottom:12px" >
@@ -1081,6 +1102,17 @@
class = "date-range"
class = "date-range"
@change ="onScoreFilterChange"
@change ="onScoreFilterChange"
/ >
/ >
<!-- R195 : 默认只显示参赛过的人 ( 有作答 / 有大题文件 / 已交卷 ) 。 中职 / 高职按小组参赛 ,
一组报名多人只一人上场 , 其余组员全 0 分占行 ⇒ 勾选后才列出全部报名者 。 -- >
< el-checkbox
v-if = "isExamScoreGroup"
v-model = "scoreIncludeIdle"
size = "small"
class = "idle-toggle"
@change ="onScoreIncludeIdleChange"
>
显示未参赛
< / el-checkbox >
< / div >
< / div >
<!-- 成绩明细表 -- >
<!-- 成绩明细表 -- >
@@ -1204,6 +1236,15 @@
class = "date-range"
class = "date-range"
@change ="onExamSubsFilterChange"
@change ="onExamSubsFilterChange"
/ >
/ >
<!-- R195 : 默认只列出参赛过的人 ; 勾选后显示全部报名者 ( 含从未进考场的组员 ) 。 -- >
< el-checkbox
v-model = "examSubsIncludeIdle"
size = "small"
class = "idle-toggle"
@change ="onExamSubsIncludeIdleChange"
>
显示未参赛
< / el-checkbox >
< el-button text type = "primary" v-guard = "() => fetchExamSubmissions()" :loading="examSubmissionsLoading" > 刷新 < / el -button >
< el-button text type = "primary" v-guard = "() => fetchExamSubmissions()" :loading="examSubmissionsLoading" > 刷新 < / el -button >
< / div >
< / div >
< el-table :data = "pagedExamSubmissions" stripe v-loading = "examSubmissionsLoading" class="admin-table" >
< el-table :data = "pagedExamSubmissions" stripe v-loading = "examSubmissionsLoading" class="admin-table" >
@@ -4011,6 +4052,8 @@ async function downloadBigQuestionTpl() {
// 考生答题记录
// 考生答题记录
const examSubmissions = ref ( [ ] )
const examSubmissions = ref ( [ ] )
// R195: 同「成绩总览」——默认隐藏从未参赛的报名者( includeIdle=1 才看全量)
const examSubsIncludeIdle = ref ( false )
const examSubmissionsLoading = ref ( false )
const examSubmissionsLoading = ref ( false )
const examSubmissionsPage = ref ( 1 )
const examSubmissionsPage = ref ( 1 )
const examSubmissionsPageSize = ref ( 10 )
const examSubmissionsPageSize = ref ( 10 )
@@ -4058,7 +4101,10 @@ async function fetchExamSubmissions() {
// R29: 本板块已迁入「成绩管理」⇒ 组别跟随成绩页
// R29: 本板块已迁入「成绩管理」⇒ 组别跟随成绩页
examSubmissionsLoading . value = true
examSubmissionsLoading . value = true
try {
try {
const data = await adminApi . getSubmissions ( scoreExportGroup . value )
const data = await adminApi . getSubmissions (
scoreExportGroup . value ,
examSubsIncludeIdle . value ? { includeIdle : 1 } : undefined
)
examSubmissions . value = Array . isArray ( data ) ? data : ( data ? . list || [ ] )
examSubmissions . value = Array . isArray ( data ) ? data : ( data ? . list || [ ] )
} catch ( e ) {
} catch ( e ) {
console . warn ( '获取答题记录失败' , e )
console . warn ( '获取答题记录失败' , e )
@@ -4070,6 +4116,12 @@ function onExamSubsFilterChange() {
examSubmissionsPage . value = 1
examSubmissionsPage . value = 1
}
}
// R195: 服务端过滤( includeIdle) , 切换需重新请求
async function onExamSubsIncludeIdleChange ( ) {
examSubmissionsPage . value = 1
await fetchExamSubmissions ( )
}
// R30: 原「考生答题评分弹窗」的 state 与 openExamScoreDialog / saveExamScore 已整体移除。
// R30: 原「考生答题评分弹窗」的 state 与 openExamScoreDialog / saveExamScore 已整体移除。
// 大题评分改由「考生大题作答查证」按小问给分( adminApi.setBigAnswerScores
// 大题评分改由「考生大题作答查证」按小问给分( adminApi.setBigAnswerScores
// → service.bigQuestion.setSubScores → service.admin.updateScores) ,
// → service.bigQuestion.setSubScores → service.admin.updateScores) ,
@@ -4182,8 +4234,10 @@ const submissionStatusCount = computed(() => {
const filteredSubmissions = computed ( ( ) => {
const filteredSubmissions = computed ( ( ) => {
let list = submissions . value
let list = submissions . value
if ( submissionSearch . value ) {
if ( submissionSearch . value ) {
// R194: 行已按小组聚合 ⇒ 搜索口径改为「小组编号 / 作品目录」(姓名不再展示)
const s = submissionSearch . value . trim ( ) . toLowerCase ( )
const s = submissionSearch . value . trim ( ) . toLowerCase ( )
list = list . filter ( item => ( item . name || '' ) . toLowerCase ( ) . includes ( s ) )
list = list . filter ( item => String ( item . teamFolder || '' ) . toLowerCase ( ) . includes ( s )
|| String ( item . teamNumber || '' ) . toLowerCase ( ) . includes ( s ) )
}
}
// 🔴 R178 同「成绩总览」:⛔ 不用 `!== ''` 判断是否启用筛选(清空值可能是 undefined) ,
// 🔴 R178 同「成绩总览」:⛔ 不用 `!== ''` 判断是否启用筛选(清空值可能是 undefined) ,
// 只用白名单;否则点「清除」后会静默变成「只看未提交」。
// 只用白名单;否则点「清除」后会静默变成「只看未提交」。
@@ -4199,13 +4253,77 @@ const pagedSubmissions = computed(() => {
return filteredSubmissions . value . slice ( start , start + submissionsPageSize . value )
return filteredSubmissions . value . slice ( start , start + submissionsPageSize . value )
} )
} )
/**
* 把后端 getWorks()「按人返回」的结果折叠成「按小组一行」( R194) 。
*
* 【业务来源】用户实测:比赛以小组为准,但管理端把同组 6 个人平铺成 6 行,
* 其中 5 行是「未提交」,管理人员无法一眼判断「这个小组到底交没交」。
* 管理侧只关心:小组编号 → 是否已提交 → 文件与评分。
*
* 【分组键】与 R190 组锁 / 落盘口径**保持一致**,不另立一套:
* teamUid( 有队伍号) → aggregationUid( 无队伍号的单人) → contestantId( 兜底,
* 保证任何一行都不会被吞掉——漏行比多行更难被发现)。
* ⚠️ teamUid 仅**组内**唯一且可重复,但本函数只在单一 group 的列表内聚合
* ( submissionGroup 单选),故不存在跨组串号。
*
* @param {Array<Object>} people - getWorks 的选手级数据
* @returns {Array<Object>} 小组级行
*/
function groupSubmissionsByTeam ( people ) {
const buckets = new Map ( )
people . forEach ( p => {
const key = ( p . teamUid !== null && p . teamUid !== undefined )
? ` t: ${ p . teamUid } `
: ( p . aggregationUid !== null && p . aggregationUid !== undefined )
? ` a: ${ p . aggregationUid } `
: ` c: ${ p . contestantId } `
let g = buckets . get ( key )
if ( ! g ) {
g = {
teamKey : key ,
teamUid : p . teamUid ,
teamNumber : p . teamNumber ,
teamFolder : p . teamFolder ,
aggregationUid : p . aggregationUid ,
memberCount : 0 ,
files : [ ] ,
_zipLoading : false ,
_order : p . contestantId ,
}
buckets . set ( key , g )
}
g . memberCount += 1
// contestantId 必须跟着文件走:批量保存评分要按「人」回写 wordScore / pptScore
// _uploaderName 同理:聚合后要靠它回答「这组是谁提交的」
; ( p . files || [ ] ) . forEach ( f => g . files . push ( { ... f , contestantId : p . contestantId , _uploaderName : p . name } ) )
} )
const list = Array . from ( buckets . values ( ) )
list . forEach ( g => {
// 跨成员合并后重排一次:后端按 upload_time DESC, 合并会打乱顺序
g . files . sort ( ( a , b ) => String ( b . uploadedAt || '' ) . localeCompare ( String ( a . uploadedAt || '' ) ) )
// 🔴 提交人 = 归属人,口径与 R190 组锁一致(**最早提交者**)。
// 组锁下正常只有一人能提交,这里取最早者是为了兼容历史越权上传留下的多份文件。
const owners = g . files . filter ( f => f . _uploaderName )
if ( owners . length ) {
const ts = f => ( f . uploadedAt ? String ( f . uploadedAt ) : '9999-12-31 23:59:59' )
owners . sort ( ( a , b ) => ts ( a ) . localeCompare ( ts ( b ) ) )
g . submitterName = owners [ 0 ] . _uploaderName
} else {
g . submitterName = ''
}
} )
list . sort ( ( a , b ) => a . _order - b . _order )
return list
}
async function fetchSubmissions ( ) {
async function fetchSubmissions ( ) {
if ( ! submissionGroup . value ) return
if ( ! submissionGroup . value ) return
submissionsLoading . value = true
submissionsLoading . value = true
try {
try {
const data = await adminApi . getWorks ( submissionGroup . value )
const data = await adminApi . getWorks ( submissionGroup . value )
const raw = Array . isArray ( data ) ? data : ( data ? . list || [ ] )
const raw = Array . isArray ( data ) ? data : ( data ? . list || [ ] )
submissions . valu e = raw . map ( s => ( {
const peopl e = raw . map ( s => ( {
contestantId : s . contestantId ,
contestantId : s . contestantId ,
name : s . name ,
name : s . name ,
idNumber : s . idNumber ,
idNumber : s . idNumber ,
@@ -4218,6 +4336,7 @@ async function fetchSubmissions() {
aggregationUid : s . aggregationUid === undefined ? null : s . aggregationUid ,
aggregationUid : s . aggregationUid === undefined ? null : s . aggregationUid ,
files : ( s . files || [ ] ) . map ( f => ( { ... f , _score : f . score != null ? Number ( f . score ) : null } ) ) ,
files : ( s . files || [ ] ) . map ( f => ( { ... f , _score : f . score != null ? Number ( f . score ) : null } ) ) ,
} ) )
} ) )
submissions . value = groupSubmissionsByTeam ( people )
} catch ( e ) { console . warn ( '获取作品列表失败' , e ) } finally { submissionsLoading . value = false }
} catch ( e ) { console . warn ( '获取作品列表失败' , e ) } finally { submissionsLoading . value = false }
}
}
@@ -4256,7 +4375,14 @@ async function previewWork(row, f) {
if ( previewIsPdf . value ) {
if ( previewIsPdf . value ) {
const r = await fetchBlobGuarded ( fileDownloadUrl ( submissionGroup . value , f ) )
const r = await fetchBlobGuarded ( fileDownloadUrl ( submissionGroup . value , f ) )
if ( r . ok ) {
if ( r . ok ) {
previewBlobUrl . value = URL . createObjectURL ( r . blob )
// 🔴 R194( 用户实测: 预览 PDF 时浏览器反复下载「无扩展名文件」):
// 下载端点为了让「点下载」必然落盘,统一声明 Content-Type: application/octet-stream
// (见后端 admin.js#downloadSubmissionFile) 。axios 的 blob 会**继承该响应头**,
// 于是 blob: URL 的 MIME 也是 octet-stream ⇒ iframe 无法渲染、Chrome 直接把它当
// 附件下载,弹窗内就是一片空白。
// 修法:预览通道把同一份字节重新包成 application/pdf( 只改本地 Blob 的 type,
// 不碰后端契约,下载按钮行为不变)。字节完整性已由 fetchBlobGuarded 校验过。
previewBlobUrl . value = URL . createObjectURL ( new Blob ( [ r . blob ] , { type : 'application/pdf' } ) )
} else if ( r . ignored ) {
} else if ( r . ignored ) {
// 同一文件正在预览加载,被去重忽略——静默跳过,不报错
// 同一文件正在预览加载,被去重忽略——静默跳过,不报错
} else {
} else {
@@ -4271,6 +4397,16 @@ function closePreview() {
previewRow . value = null
previewRow . value = null
}
}
/**
* R194: 把当前预览的 blob 交给新标签页打开( iframe 空白时的确定出口)。
* ⚠️ 不要在这里 revoke: 新标签可能仍在加载, revoke 会让它拿到空文档。
*/
function openPreviewInNewTab ( ) {
if ( ! previewBlobUrl . value ) return
const w = window . open ( previewBlobUrl . value , '_blank' )
if ( ! w ) ElMessage . warning ( '浏览器拦截了新标签,请允许本站弹窗后重试' )
}
// 批量下载作品( ZIP)
// 批量下载作品( ZIP)
async function batchDownloadWorks ( ) {
async function batchDownloadWorks ( ) {
if ( ! submissionGroup . value ) return
if ( ! submissionGroup . value ) return
@@ -4312,7 +4448,10 @@ async function batchSaveWorkScores() {
submissions . value . forEach ( s => {
submissions . value . forEach ( s => {
; ( s . files || [ ] ) . forEach ( f => {
; ( s . files || [ ] ) . forEach ( f => {
if ( f . _score == null || f . _score === '' ) return
if ( f . _score == null || f . _score === '' ) return
const c = ( perContestant [ s . contestantId ] = perContestant [ s . contestantId ] || { } )
// R194: 行已按小组聚合 ⇒ 回写主体不是「行」而是「文件所属选手」,
// contestantId 由 groupSubmissionsByTeam 逐文件注入(⛔ 不要读 s.contestantId)
if ( f . contestantId == null ) return
const c = ( perContestant [ f . contestantId ] = perContestant [ f . contestantId ] || { } )
if ( f . scoreField === 'ppt' ) c . pptScore = Number ( f . _score )
if ( f . scoreField === 'ppt' ) c . pptScore = Number ( f . _score )
else c . wordScore = Number ( f . _score )
else c . wordScore = Number ( f . _score )
} )
} )
@@ -4536,6 +4675,9 @@ watch(scoreExportGroup, () => {
} )
} )
const scoreList = ref ( [ ] )
const scoreList = ref ( [ ] )
// R195: 默认只看「参赛过的人」。中职/高职按小组参赛,一组报名多人只一人上场,
// 未上场组员在成绩表里各占一行 0 分 ⇒ 默认隐藏,需核对全量时打开本开关。
const scoreIncludeIdle = ref ( false )
const scoreListLoading = ref ( false )
const scoreListLoading = ref ( false )
const scoreListPage = ref ( 1 )
const scoreListPage = ref ( 1 )
const scoreListPageSize = ref ( 10 )
const scoreListPageSize = ref ( 10 )
@@ -4590,6 +4732,12 @@ function onScoreFilterChange() {
scoreListPage . value = 1
scoreListPage . value = 1
}
}
// R195: 「显示未参赛」是**服务端过滤**( includeIdle 参数),切换必须重新拉数据,⛔ 不能只在本地过滤。
async function onScoreIncludeIdleChange ( ) {
scoreListPage . value = 1
await fetchScoreList ( )
}
async function fetchPublishStatus ( ) {
async function fetchPublishStatus ( ) {
if ( ! scoreExportGroup . value ) return
if ( ! scoreExportGroup . value ) return
try {
try {
@@ -4604,7 +4752,10 @@ async function fetchScoreList() {
if ( ! scoreExportGroup . value ) return
if ( ! scoreExportGroup . value ) return
scoreListLoading . value = true
scoreListLoading . value = true
try {
try {
const data = await adminApi . getScores ( scoreExportGroup . value )
const data = await adminApi . getScores (
scoreExportGroup . value ,
scoreIncludeIdle . value ? { includeIdle : 1 } : undefined
)
scoreList . value = ( Array . isArray ( data ) ? data : ( data ? . list || [ ] ) ) . map ( item => ( {
scoreList . value = ( Array . isArray ( data ) ? data : ( data ? . list || [ ] ) ) . map ( item => ( {
... item ,
... item ,
idNumber : item . idNumber ? ? item . id _number ,
idNumber : item . idNumber ? ? item . id _number ,
@@ -5548,6 +5699,9 @@ onMounted(async () => {
. score - inline { display : inline - flex ; align - items : center ; gap : 4 px ; color : # 606266 ; font - size : 13 px ; }
. score - inline { display : inline - flex ; align - items : center ; gap : 4 px ; color : # 606266 ; font - size : 13 px ; }
. score - input { width : 110 px ; }
. score - input { width : 110 px ; }
. preview - frame { width : 100 % ; height : 70 vh ; border : 0 ; background : # fff ; }
. preview - frame { width : 100 % ; height : 70 vh ; border : 0 ; background : # fff ; }
/* R194: 预览操作条( 新标签打开 + 空白兜底说明) */
. preview - bar { display : flex ; align - items : center ; gap : 10 px ; margin - bottom : 6 px ; }
. preview - hint { color : # 909399 ; font - size : 12 px ; line - height : 1.5 ; }
. preview - file - name { color : # 303133 ; font - weight : 600 ; margin - bottom : 8 px ; }
. preview - file - name { color : # 303133 ; font - weight : 600 ; margin - bottom : 8 px ; }
. work - summary { color : # 606266 ; font - size : 13 px ; margin - left : 8 px ; }
. work - summary { color : # 606266 ; font - size : 13 px ; margin - left : 8 px ; }
@@ -5561,6 +5715,8 @@ onMounted(async () => {
color : # 303133 ;
color : # 303133 ;
font - weight : 500 ;
font - weight : 500 ;
}
}
/* R194: 小组副信息( 参赛人数) , 只报数量、不列成员姓名 */
. team - meta { color : # 909399 ; font - size : 12 px ; }
/* ==================== 统计卡片 ==================== */
/* ==================== 统计卡片 ==================== */
. stats - row { margin - bottom : 20 px ; }
. stats - row { margin - bottom : 20 px ; }